diff --git a/.github/actions/create-github-release/action.yml b/.github/actions/create-github-release/action.yml index 1d72cd3a..4a28df82 100644 --- a/.github/actions/create-github-release/action.yml +++ b/.github/actions/create-github-release/action.yml @@ -14,8 +14,8 @@ runs: milestone: ${{ inputs.milestone }} token: ${{ inputs.token }} config-file: .github/actions/create-github-release/changelog-generator.yml - - name: Create GitHub Release + - name: Publish GitHub Release env: GITHUB_TOKEN: ${{ inputs.token }} shell: bash - run: gh release create ${{ format('v{0}', inputs.milestone) }} --notes-file changelog.md + run: gh release edit ${{ format('v{0}', inputs.milestone) }} --draft=false --notes-file changelog.md diff --git a/.github/actions/create-github-release/changelog-generator.yml b/.github/actions/create-github-release/changelog-generator.yml index 2ce74a09..bd848fb0 100644 --- a/.github/actions/create-github-release/changelog-generator.yml +++ b/.github/actions/create-github-release/changelog-generator.yml @@ -1,2 +1,2 @@ changelog: - repository: spring-io/spring-javaformat + repository: timothysparg/spring-javaformat diff --git a/.github/actions/setup-native/action.yml b/.github/actions/setup-native/action.yml new file mode 100644 index 00000000..ea166083 --- /dev/null +++ b/.github/actions/setup-native/action.yml @@ -0,0 +1,15 @@ +name: 'Setup Native' +runs: + using: composite + steps: + - name: Set Up GraalVM + uses: graalvm/setup-graalvm@v1 + with: + distribution: liberica + java-version: '17' + cache: maven + components: native-image + github-token: ${{ github.token }} + - name: Disable Java Problem Matcher + shell: bash + run: echo "::remove-matcher owner=java::" diff --git a/.github/workflows/build-and-deploy-snapshot.yml b/.github/workflows/build-and-deploy-snapshot.yml index d269a903..bd7fade6 100644 --- a/.github/workflows/build-and-deploy-snapshot.yml +++ b/.github/workflows/build-and-deploy-snapshot.yml @@ -2,14 +2,13 @@ name: Build and Deploy Snapshot on: push: branches: - - main + - test-ci concurrency: group: ${{ github.workflow }}-${{ github.ref }} jobs: build-and-deploy-snapshot: name: Build and Deploy Snapshot runs-on: ubuntu-latest - if: ${{ github.repository == 'spring-io/spring-javaformat' }} steps: - name: Check Out uses: actions/checkout@v4 @@ -18,6 +17,7 @@ jobs: - name: Build run: ./mvnw clean deploy --batch-mode --no-transfer-progress --update-snapshots -DaltDeploymentRepository=distribution::file://$(pwd)/distribution-repository - name: Deploy + if: ${{ github.repository == 'spring-io/spring-javaformat' }} uses: spring-io/artifactory-deploy-action@v0.0.1 with: folder: 'distribution-repository' diff --git a/.github/workflows/build-cli-native.yml b/.github/workflows/build-cli-native.yml new file mode 100644 index 00000000..0c553f1f --- /dev/null +++ b/.github/workflows/build-cli-native.yml @@ -0,0 +1,64 @@ +name: Build CLI Native +on: + workflow_call: + workflow_dispatch: +permissions: + contents: read +jobs: + build: + name: Build (${{ matrix.name }}) + runs-on: ${{ matrix.runs-on }} + strategy: + fail-fast: false + matrix: + include: + - name: linux-amd64 + os: linux + arch: amd64 + runs-on: ubuntu-latest + - name: linux-arm64 + os: linux + arch: arm64 + runs-on: ubuntu-24.04-arm + - name: windows-amd64 + os: windows + arch: amd64 + runs-on: windows-latest + - name: macos-arm64 + os: macos + arch: arm64 + runs-on: macos-14 + steps: + - name: Check Out + uses: actions/checkout@v4 + - name: Set Up + uses: ./.github/actions/setup-native + - name: Build + if: matrix.os != 'windows' + shell: bash + env: + MAVEN_OPTS: "" + run: ./mvnw -pl spring-javaformat-cli -am -Pnative package --batch-mode --no-transfer-progress --update-snapshots + - name: Build + if: matrix.os == 'windows' + shell: pwsh + env: + MAVEN_OPTS: "" + run: .\mvnw.cmd -pl spring-javaformat-cli -am -Pnative package --batch-mode --no-transfer-progress --update-snapshots + - name: Prepare Artifact + shell: bash + run: | + binary="spring-javaformat-cli/target/spring-javaformat-cli" + extension="" + if [[ "${{ matrix.os }}" == "windows" ]]; then + binary="${binary}.exe" + extension=".exe" + fi + release_name="spring-javaformat-${{ matrix.os }}-${{ matrix.arch }}${extension}" + mkdir -p native-artifacts + cp "${binary}" "native-artifacts/${release_name}" + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: spring-javaformat-${{ matrix.os }}-${{ matrix.arch }} + path: native-artifacts/* diff --git a/.github/workflows/build-pull-request.yml b/.github/workflows/build-pull-request.yml index 9e085b69..ca20b1ec 100644 --- a/.github/workflows/build-pull-request.yml +++ b/.github/workflows/build-pull-request.yml @@ -6,7 +6,7 @@ jobs: build: name: Build Pull Request runs-on: ubuntu-latest - if: ${{ github.repository == 'spring-io/spring-javaformat' }} + if: ${{ github.repository == 'timothysparg/spring-javaformat' || github.repository == 'spring-io/spring-javaformat' }} steps: - name: Check Out uses: actions/checkout@v4 @@ -14,3 +14,14 @@ jobs: uses: ./.github/actions/setup - name: Build run: ./mvnw clean install --batch-mode --no-transfer-progress --update-snapshots + build-cli-native: + name: Build CLI Native + runs-on: ubuntu-latest + if: ${{ github.repository == 'timothysparg/spring-javaformat' || github.repository == 'spring-io/spring-javaformat' }} + steps: + - name: Check Out + uses: actions/checkout@v4 + - name: Set Up Native + uses: ./.github/actions/setup-native + - name: Build CLI Native + run: ./mvnw -pl spring-javaformat-cli -am -Pnative package --batch-mode --no-transfer-progress --update-snapshots diff --git a/.github/workflows/prepare-cli-native-release.yml b/.github/workflows/prepare-cli-native-release.yml new file mode 100644 index 00000000..0348d8e4 --- /dev/null +++ b/.github/workflows/prepare-cli-native-release.yml @@ -0,0 +1,45 @@ +name: Prepare CLI Native Release +on: + workflow_call: + inputs: + version: + type: string + required: true + secrets: + gh-token: + required: true +permissions: + contents: write +jobs: + create-draft-release: + name: Create Draft GitHub Release + runs-on: ubuntu-latest + steps: + - name: Check Out + uses: actions/checkout@v4 + - name: Create Draft GitHub Release + env: + GH_TOKEN: ${{ secrets.gh-token || github.token }} + shell: bash + run: gh release create ${{ format('v{0}', inputs.version) }} --draft --verify-tag --notes "Draft release" + build: + name: Build + needs: create-draft-release + uses: ./.github/workflows/build-cli-native.yml + upload-assets: + name: Upload Assets + needs: + - create-draft-release + - build + runs-on: ubuntu-latest + steps: + - name: Download Native Artifacts + uses: actions/download-artifact@v4 + with: + pattern: spring-javaformat-* + path: native-artifacts + merge-multiple: true + - name: Upload Assets to Draft Release + env: + GH_TOKEN: ${{ secrets.gh-token || github.token }} + run: gh release upload ${{ format('v{0}', inputs.version) }} native-artifacts/* --clobber --repo ${{ github.repository }} diff --git a/.github/workflows/promote.yml b/.github/workflows/promote.yml index e1faac58..ffe17b56 100644 --- a/.github/workflows/promote.yml +++ b/.github/workflows/promote.yml @@ -36,6 +36,7 @@ jobs: - name: Check Out uses: actions/checkout@v4 - name: Set Up JFrog CLI + if: ${{ github.repository == 'spring-io/spring-javaformat' }} uses: jfrog/setup-jfrog-cli@ff5cb544114ffc152db9cea1cd3d5978d5074946 # v4.5.11 env: JF_ENV_SPRING: ${{ secrets.JF_ARTIFACTORY_SPRING }} @@ -50,10 +51,10 @@ jobs: fi echo "status-code=${status_code}" >> $GITHUB_OUTPUT - name: Download Release Artifacts - if: ${{ steps.check-sync-status.outputs.status-code == '404' }} + if: ${{ github.repository == 'spring-io/spring-javaformat' && steps.check-sync-status.outputs.status-code == '404' }} run: jf rt download --spec ./.github/artifacts.spec --spec-vars 'buildName=${{ format('spring-javaformat-{0}', inputs.version) }};buildNumber=${{ inputs.build-number }}' - name: Sync to Maven Central - if: ${{ steps.check-sync-status.outputs.status-code == '404' }} + if: ${{ github.repository == 'spring-io/spring-javaformat' && steps.check-sync-status.outputs.status-code == '404' }} uses: spring-io/nexus-sync-action@v0.0.1 with: username: ${{ secrets.OSSRH_S01_TOKEN_USERNAME }} @@ -65,7 +66,7 @@ jobs: release: true generate-checksums: true - name: Await Maven Central Sync - if: ${{ steps.check-sync-status.outputs.status-code == '404' }} + if: ${{ github.repository == 'spring-io/spring-javaformat' && steps.check-sync-status.outputs.status-code == '404' }} run: | url=${{ format('https://repo.maven.apache.org/maven2/io/spring/javaformat/spring-javaformat/{0}/spring-javaformat-{0}.pom', inputs.version) }} echo "Waiting for $url" @@ -76,17 +77,18 @@ jobs: done echo "$url is available" - name: Promote Build - if: ${{ steps.check-sync-status.outputs.status-code == '404' }} + if: ${{ github.repository == 'spring-io/spring-javaformat' && steps.check-sync-status.outputs.status-code == '404' }} run: jfrog rt build-promote ${{ format('spring-javaformat-{0}', inputs.version)}} ${{ inputs.build-number }} libs-release-local - name: Publish Eclipse Update Site + if: ${{ github.repository == 'spring-io/spring-javaformat' }} uses: ./.github/actions/publish-eclipse-update-site with: version: ${{ inputs.version }} build-number: ${{ inputs.build-number }} artifactory-username: ${{ secrets.ARTIFACTORY_USERNAME }} artifactory-password: ${{ secrets.ARTIFACTORY_PASSWORD }} - - name: Create GitHub Release + - name: Publish GitHub Release uses: ./.github/actions/create-github-release with: milestone: ${{ inputs.version }} - token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} + token: ${{ secrets.GH_ACTIONS_REPO_TOKEN || github.token }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c80a4091..7383718d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,6 +8,8 @@ on: description: Environment type: environment required: true +permissions: + contents: write jobs: stage: name: Stage @@ -34,6 +36,7 @@ jobs: release-version: ${{ steps.deduce-versions.outputs.release-version }} next-version: ${{ steps.deduce-versions.outputs.next-version }} - name: Deploy to Staging + if: ${{ github.repository == 'spring-io/spring-javaformat' }} uses: spring-io/artifactory-deploy-action@v0.0.1 with: folder: distribution-repository @@ -49,9 +52,19 @@ jobs: outputs: release-version: ${{ steps.deduce-versions.outputs.release-version }} release-build-number: ${{ github.run_number }} + prepare-cli-native-release: + name: Prepare CLI Native Release + needs: stage + uses: ./.github/workflows/prepare-cli-native-release.yml + with: + version: ${{ needs.stage.outputs.release-version }} + secrets: + gh-token: ${{ secrets.GH_ACTIONS_REPO_TOKEN || github.token }} promote: name: Promote - needs: stage + needs: + - stage + - prepare-cli-native-release uses: ./.github/workflows/promote.yml with: environment: ${{ inputs.environment }} diff --git a/.github/workflows/rollback.yml b/.github/workflows/rollback.yml index 43e04a4d..7780e177 100644 --- a/.github/workflows/rollback.yml +++ b/.github/workflows/rollback.yml @@ -39,3 +39,10 @@ jobs: build_name=${{ format('spring-javaformat-{0}', inputs.version)}} build_number=${{ inputs.build-number }} jf rt delete --build ${build_name}/${build_number} + - name: Delete Draft GitHub Release + env: + GITHUB_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }} + run: | + if gh release view ${{ format('v{0}', inputs.version) }} > /dev/null 2>&1; then + gh release delete ${{ format('v{0}', inputs.version) }} --yes --cleanup-tag + fi diff --git a/.mvn/jvm.config b/.mvn/jvm.config index 629555a1..d1e0726a 100644 --- a/.mvn/jvm.config +++ b/.mvn/jvm.config @@ -1,2 +1,14 @@ -Xmx1536m -Dtycho.disableP2Mirrors=true +-Djdk.xml.maxGeneralEntitySizeLimit=0 +-Djdk.xml.totalEntitySizeLimit=0 +--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED +--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED +--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED +--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED diff --git a/README.adoc b/README.adoc index 907d9a2c..b1a98f77 100644 --- a/README.adoc +++ b/README.adoc @@ -2,8 +2,6 @@ :checkstyle-version: 9.3 == Spring Java Format - - === What is This? A set of plugins that can be applied to any Java project to provide a consistent "`Spring`" style. The set currently consists of: @@ -211,6 +209,53 @@ springJavaFormat { +=== CLI +The CLI provides formatting and checking support without requiring Maven or Gradle integration. + +WARNING: The CLI is experimental and not yet considered a stable interface. + +==== Download +Native binaries for Linux, Windows, and macOS are published as assets on the corresponding https://github.com/spring-io/spring-javaformat/releases[GitHub release]. + +==== Usage +[source,shell] +---- +spring-javaformat --help +---- + +The CLI provides the following commands: + +* `apply` formats Java source files in place +* `validate` checks formatting without modifying files +* `check` runs Spring style checks + +==== Examples +Format sources in the current project: + +[source,shell] +---- +spring-javaformat apply . +---- + +Validate formatting: + +[source,shell] +---- +spring-javaformat validate . +---- + +Run style checks: + +[source,shell] +---- +spring-javaformat check . +---- + +==== Configuration +The `apply` and `validate` commands honor `.springjavaformatconfig`. + + + === Java 8 Support By default, the formatter requires Java 17. If you are working on an older project, you can use a variation of the formatter based off Eclipse 2021-03 (the latest Eclipse JDT version built with Java 8). diff --git a/pom.xml b/pom.xml index 57d056c6..b04aa8d3 100644 --- a/pom.xml +++ b/pom.xml @@ -48,8 +48,12 @@ 3.6.28 5.8.1 3.21.0-GA + 1.0.0 1.2 + 4.7.7 3.5.1 + 2.7.18 + 0.12.2 1.16.0 1.16.0 3.0.3 @@ -181,6 +185,16 @@ maven-source-plugin 3.2.1 + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + org.springframework.experimental + spring-aot-maven-plugin + ${spring-native.version} + org.apache.maven.plugins maven-javadoc-plugin @@ -495,6 +509,11 @@ log4j-slf4j-impl ${log4j.version} + + org.jspecify + jspecify + ${jspecify.version} + org.junit junit-bom @@ -558,11 +577,21 @@ picocontainer ${picocontainer.version} + + info.picocli + picocli-spring-boot-starter + ${picocli.version} + org.codehaus.plexus plexus-utils ${plexus-utils.version} + + org.springframework.experimental + spring-native + ${spring-native.version} + org.testcontainers testcontainers @@ -595,6 +624,7 @@ spring-javaformat + spring-javaformat-cli spring-javaformat-eclipse spring-javaformat-gradle spring-javaformat-intellij-idea diff --git a/spring-javaformat-cli/.sdkmanrc b/spring-javaformat-cli/.sdkmanrc new file mode 100644 index 00000000..431b64cb --- /dev/null +++ b/spring-javaformat-cli/.sdkmanrc @@ -0,0 +1,3 @@ +# Enable auto-env through the sdkman_auto_env config +# Add key=value pairs of SDKs to use below +java=23.0.11.r17-nik diff --git a/spring-javaformat-cli/pom.xml b/spring-javaformat-cli/pom.xml new file mode 100644 index 00000000..1b9f5dcc --- /dev/null +++ b/spring-javaformat-cli/pom.xml @@ -0,0 +1,274 @@ + + + 4.0.0 + + io.spring.javaformat + spring-javaformat-build + 0.0.48-SNAPSHOT + + spring-javaformat-cli + Spring JavaFormat CLI + + ${basedir}/.. + 17 + 17 + 17 + 3.2.3 + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + + + + org.jspecify + jspecify + + + io.spring.javaformat + spring-javaformat-formatter + ${project.version} + + + io.spring.javaformat + spring-javaformat-formatter-eclipse-runtime + ${project.version} + + + * + * + + + + + io.spring.javaformat + spring-javaformat-config + ${project.version} + + + io.spring.javaformat + spring-javaformat-checkstyle + ${project.version} + + + org.springframework.experimental + spring-native + + + info.picocli + picocli-spring-boot-starter + + + org.codehaus.plexus + plexus-utils + + + + gg.jte + jte-runtime + ${jte.version} + + + gg.jte + jte-models + ${jte.version} + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + src/main/resources + true + + io/spring/javaformat/cli/version.properties + + + + src/main/resources + false + + io/spring/javaformat/cli/version.properties + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + ${java.version} + + + + org.apache.maven.plugins + maven-compiler-plugin + + true + + -XDcompilePolicy=simple + -XDaddTypeAnnotationsToSymbol=true + + -XDshould-stop.ifError=FLOW + -Xplugin:ErrorProne -XepDisableWarningsInGeneratedCode -Xep:NullAway:ERROR -XepOpt:NullAway:JSpecifyMode=true -XepOpt:NullAway:OnlyNullMarked=true -XepOpt:NullAway:ExternalInitAnnotations=io.spring.javaformat.cli.PicocliManaged + -Aproject=${project.groupId}/${project.artifactId} + + + + info.picocli + picocli-codegen + 4.7.7 + + + + com.google.errorprone + error_prone_core + 2.42.0 + + + + com.uber.nullaway + nullaway + 0.12.11 + + + + + + gg.jte + jte-maven-plugin + ${jte.version} + + ${project.basedir}/src/main/jte + Plain + + + gg.jte.models.generator.ModelExtension + + @org.jspecify.annotations.NullMarked + @org.jspecify.annotations.NullMarked + Java + + + + + + + generate + generate-sources + + generate + + + + + + gg.jte + jte-models + ${jte.version} + + + + + org.springframework.experimental + spring-aot-maven-plugin + + + generate + + generate + + + + + + org.springframework.boot + spring-boot-maven-plugin + + exec + + + + + repackage + + + + + + + + + + native + + + + org.graalvm.buildtools + native-maven-plugin + 0.9.28 + true + + + build-native + package + + build + + + + + io.spring.javaformat.cli.SpringJavaFormatCliApplication + + --no-fallback + --allow-incomplete-classpath + --initialize-at-build-time=org.springframework.core.annotation.RepeatableContainers,org.springframework.core.annotation.RepeatableContainers$NoRepeatableContainers,org.springframework.core.annotation.TypeMappedAnnotations + --initialize-at-build-time=io.spring.javaformat.eclipse.jdt.jdk17.internal.compiler + + + + + + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + + spring-milestones + Spring Milestones + https://repo.spring.io/milestone + + false + + + + + diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/CliVersionProvider.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/CliVersionProvider.java new file mode 100644 index 00000000..7259b1fd --- /dev/null +++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/CliVersionProvider.java @@ -0,0 +1,57 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +import picocli.CommandLine.Help.Ansi; +import picocli.CommandLine.IVersionProvider; + +/** + * Provides CLI version text for picocli version help. + * + * @author Tim Sparg + */ +public class CliVersionProvider implements IVersionProvider { + + @Override + public String[] getVersion() { + String version = readVersion(); + return new String[] { Ansi.AUTO.string("@|bold spring-javaformat|@ @|cyan " + version + "|@") }; + } + + private String readVersion() { + try (InputStream in = CliVersionProvider.class + .getResourceAsStream("/io/spring/javaformat/cli/version.properties")) { + if (in != null) { + Properties properties = new Properties(); + properties.load(in); + String version = properties.getProperty("version"); + if (version != null && !version.isBlank()) { + return version; + } + } + } + catch (IOException ex) { + // fall through to unknown + } + return "unknown"; + } + +} diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/InputOptions.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/InputOptions.java new file mode 100644 index 00000000..932be8fa --- /dev/null +++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/InputOptions.java @@ -0,0 +1,113 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli; + +import java.io.File; +import java.nio.charset.Charset; +import java.nio.file.FileSystems; + +import org.jspecify.annotations.Nullable; +import picocli.CommandLine.ITypeConverter; +import picocli.CommandLine.Option; +import picocli.CommandLine.Parameters; +import picocli.CommandLine.TypeConversionException; + +/** + * Shared options for commands that scan and format files. + * + * @author Tim Sparg + */ +@PicocliManaged +public class InputOptions { + + /** Base directory to scan. */ + @Parameters(index = "0", arity = "0..1", defaultValue = ".", paramLabel = "PATH", + description = "Base directory to scan. Default: ${DEFAULT-VALUE}", converter = DirectoryConverter.class) + public File path; + + /** Glob patterns for files to include. */ + @Option(names = { "-i", "--includes" }, split = ",", defaultValue = "**/*.java", + description = "Repeatable or comma-separated include patterns. Default: ${DEFAULT-VALUE}", + converter = GlobConverter.class) + public String[] includes; + + /** Glob patterns for files to exclude. */ + @Option(names = { "-x", "--excludes" }, split = ",", + defaultValue = "**/target/**,**/generated-sources/**,**/generated-test-sources/**", + description = "Repeatable or comma-separated exclude patterns. Default: ${DEFAULT-VALUE}", + converter = GlobConverter.class) + public String[] excludes; + + /** File encoding to use when reading and writing files. */ + @Option(names = { "-e", "--encoding" }, defaultValue = "UTF-8", + description = "File encoding. Default: ${DEFAULT-VALUE}") + public Charset encoding; + + @Option(names = { "-l", "--line-separator" }, + description = "Line separator (${COMPLETION-CANDIDATES}). If not specified, the existing line separator in each file is preserved.") + @Nullable + LineSeparator lineSeparator; + + @Nullable + public String resolveLineSeparator() { + return (this.lineSeparator != null) ? this.lineSeparator.value : null; + } + + enum LineSeparator { + + CR("\r"), LF("\n"), CRLF("\r\n"); + + private final String value; + + LineSeparator(String value) { + this.value = value; + } + + } + + static final class GlobConverter implements ITypeConverter { + + @Override + public String convert(String value) throws TypeConversionException { + try { + FileSystems.getDefault().getPathMatcher("glob:" + value); + } + catch (IllegalArgumentException ex) { + throw new TypeConversionException("Invalid glob pattern '" + value + "': " + ex.getMessage()); + } + return value; + } + + } + + static final class DirectoryConverter implements ITypeConverter { + + @Override + public File convert(String value) throws TypeConversionException { + File dir = new File(value).getAbsoluteFile(); + if (!dir.exists()) { + throw new TypeConversionException("Directory does not exist: " + dir); + } + if (!dir.isDirectory()) { + throw new TypeConversionException("Not a directory: " + dir); + } + return dir; + } + + } + +} diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/PicocliManaged.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/PicocliManaged.java new file mode 100644 index 00000000..6535c00f --- /dev/null +++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/PicocliManaged.java @@ -0,0 +1,41 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks a class whose fields are initialized by picocli via field injection rather than + * via a constructor. This covers {@code @Spec}, {@code @Mixin}, and {@code @Option} + * fields with a {@code defaultValue}, all of which picocli sets before invoking the + * command. + * + *

+ * This annotation is registered with NullAway as an {@code ExternalInitAnnotation}, + * suppressing "field not initialized" errors for the annotated class without requiring + * per-class {@code @SuppressWarnings("NullAway.Init")}. + * + * @author Tim Sparg + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface PicocliManaged { + +} diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/PicocliRunner.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/PicocliRunner.java new file mode 100644 index 00000000..88343625 --- /dev/null +++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/PicocliRunner.java @@ -0,0 +1,54 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.ExitCodeGenerator; +import org.springframework.stereotype.Component; +import picocli.CommandLine; +import picocli.CommandLine.IFactory; + +/** + * Runs the picocli command line. + * + * @author Tim Sparg + */ +@Component +class PicocliRunner implements CommandLineRunner, ExitCodeGenerator { + + private final SpringJavaFormatCommand command; + + private final IFactory factory; + + private int exitCode; + + PicocliRunner(SpringJavaFormatCommand command, IFactory factory) { + this.command = command; + this.factory = factory; + } + + @Override + public void run(String... args) { + this.exitCode = new CommandLine(this.command, this.factory).execute(args); + } + + @Override + public int getExitCode() { + return this.exitCode; + } + +} diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/SpringJavaFormatCliApplication.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/SpringJavaFormatCliApplication.java new file mode 100644 index 00000000..ee8e3294 --- /dev/null +++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/SpringJavaFormatCliApplication.java @@ -0,0 +1,41 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli; + +import org.springframework.boot.Banner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * Main application for the Spring JavaFormat CLI. + * + * @author Tim Sparg + */ +@SpringBootApplication +public class SpringJavaFormatCliApplication { + + protected SpringJavaFormatCliApplication() { + } + + public static void main(String[] args) { + SpringApplication application = new SpringApplication(SpringJavaFormatCliApplication.class); + application.setBannerMode(Banner.Mode.OFF); + application.setLogStartupInfo(false); + System.exit(SpringApplication.exit(application.run(args))); + } + +} diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/SpringJavaFormatCommand.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/SpringJavaFormatCommand.java new file mode 100644 index 00000000..fd206a69 --- /dev/null +++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/SpringJavaFormatCommand.java @@ -0,0 +1,47 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli; + +import org.springframework.stereotype.Component; +import picocli.CommandLine.Command; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Spec; + +import io.spring.javaformat.cli.check.CheckCommand; +import io.spring.javaformat.cli.format.ApplyCommand; + +/** + * Root picocli command. + * + * @author Tim Sparg + */ +@Component +@Command(name = "spring-javaformat", mixinStandardHelpOptions = true, versionProvider = CliVersionProvider.class, + subcommands = { ApplyCommand.class, CheckCommand.class }, + description = "Formats and checks Java source files") +@PicocliManaged +class SpringJavaFormatCommand implements Runnable { + + @Spec + private CommandSpec spec; + + @Override + public void run() { + this.spec.commandLine().usage(System.out); + } + +} diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/CheckCommand.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/CheckCommand.java new file mode 100644 index 00000000..7dff6fc3 --- /dev/null +++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/CheckCommand.java @@ -0,0 +1,137 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli.check; + +import java.util.Properties; +import java.util.concurrent.Callable; + +import org.springframework.stereotype.Component; +import picocli.CommandLine.ArgGroup; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Option; +import picocli.CommandLine.ParameterException; +import picocli.CommandLine.Spec; + +import io.spring.javaformat.cli.InputOptions; + +/** + * Check formatting and checkstyle violations. + * + * @author Tim Sparg + */ +@Component +@Command(name = "check", mixinStandardHelpOptions = true, + description = "Check the codebase for formatting and checkstyle violations") +public class CheckCommand implements Callable { + + @Spec + private CommandSpec spec; + + @Mixin + private InputOptions formatOptions; + + @Option(names = { "-t", "--header-type" }, defaultValue = "APACHE2", + description = "Header type (${COMPLETION-CANDIDATES}). Default: ${DEFAULT-VALUE}") + private HeaderType headerType; + + @Option(names = { "-c", "--header-copyright-pattern" }, defaultValue = "20\\d\\d(-20\\d\\d|-present)?", + description = "Copyright year regex pattern. Default: ${DEFAULT-VALUE}") + private String headerCopyrightPattern; + + @Option(names = { "-f", "--header-file" }, defaultValue = "", + description = "Path to a custom header file. Required when --header-type is 'file'.") + private String headerFile; + + @Option(names = { "-r", "--project-root-package" }, defaultValue = "org.springframework", + description = "Root package used for import ordering. Default: ${DEFAULT-VALUE}") + private String projectRootPackage; + + @Option(names = { "-s", "--avoid-static-import-excludes" }, split = ",", defaultValue = "", + description = "Repeatable or comma-separated static import patterns to allow.") + private String[] avoidStaticImportExcludes; + + @ArgGroup + private RunMode runMode; + + private final CheckRunner checkRunner; + + private final CheckReportRenderer checkReportRenderer; + + @SuppressWarnings("NullAway.Init") + CheckCommand(CheckRunner checkRunner, CheckReportRenderer checkReportRenderer) { + this.checkRunner = checkRunner; + this.checkReportRenderer = checkReportRenderer; + } + + @Override + public Integer call() { + CheckRunner.Inputs inputs = createInputs(); + CheckReport report = this.checkRunner.run(inputs); + this.checkReportRenderer.render(this.spec.commandLine(), report); + return report.exitCode(); + } + + private CheckRunner.Inputs createInputs() { + boolean skipCheckstyle = this.runMode != null && this.runMode.skipCheckstyle; + if (!skipCheckstyle && this.headerType == HeaderType.FILE && this.headerFile.isEmpty()) { + throw new ParameterException(this.spec.commandLine(), + "--header-file is required when --header-type is FILE"); + } + return new CheckRunner.Inputs(this.formatOptions, buildCheckstyleProperties(), + this.runMode != null && this.runMode.skipFormat, skipCheckstyle); + } + + private Properties buildCheckstyleProperties() { + Properties properties = new Properties(); + properties.setProperty("headerType", this.headerType.value); + properties.setProperty("headerCopyrightPattern", this.headerCopyrightPattern); + if (!this.headerFile.isEmpty()) { + properties.setProperty("headerFile", this.headerFile); + } + properties.setProperty("projectRootPackage", this.projectRootPackage); + properties.setProperty("avoidStaticImportExcludes", String.join(",", + java.util.Arrays.stream(this.avoidStaticImportExcludes).filter((s) -> !s.isEmpty()).toList())); + return properties; + } + + static class RunMode { + + @Option(names = { "-S", "--skip-checkstyle" }, defaultValue = "false", + description = "Skip checkstyle checks, only check source formatting") + boolean skipCheckstyle; + + @Option(names = { "-F", "--skip-format" }, defaultValue = "false", + description = "Skip source formatting check, only run checkstyle") + boolean skipFormat; + + } + + enum HeaderType { + + APACHE2("apache2"), NONE("none"), UNCHECKED("unchecked"), REGEXP("regexp"), FILE("file"); + + private final String value; + + HeaderType(String value) { + this.value = value; + } + + } + +} diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/CheckReport.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/CheckReport.java new file mode 100644 index 00000000..c0dd41f1 --- /dev/null +++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/CheckReport.java @@ -0,0 +1,48 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli.check; + +import java.io.File; +import java.util.List; + +import com.puppycrawl.tools.checkstyle.api.AuditEvent; +import org.jspecify.annotations.Nullable; + +record CheckReport(List formattingProblems, List checkstyleViolations, boolean skipFormat, + boolean skipCheckstyle, @Nullable String errorMessage, boolean checkstyleFailure) { + + static CheckReport failure(boolean skipFormat, boolean skipCheckstyle, String errorMessage, boolean checkstyleFailure) { + return new CheckReport(List.of(), List.of(), skipFormat, skipCheckstyle, errorMessage, checkstyleFailure); + } + + boolean hasViolations() { + return !this.formattingProblems.isEmpty() || !this.checkstyleViolations.isEmpty(); + } + + boolean hasError() { + return this.errorMessage != null; + } + + int exitCode() { + return (hasError() || hasViolations()) ? 1 : 0; + } + + boolean combined() { + return !this.skipFormat && !this.skipCheckstyle; + } + +} diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/CheckReportRenderer.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/CheckReportRenderer.java new file mode 100644 index 00000000..f56d36b8 --- /dev/null +++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/CheckReportRenderer.java @@ -0,0 +1,74 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli.check; + +import java.util.Objects; + +import gg.jte.generated.precompiled.StaticTemplates; +import gg.jte.generated.precompiled.Templates; +import gg.jte.output.PrintWriterOutput; +import org.springframework.stereotype.Component; +import picocli.CommandLine; + +@Component +public class CheckReportRenderer { + + private final Templates templates = new StaticTemplates(); + + void render(CommandLine commandLine, CheckReport report) { + if (report.hasError()) { + renderError(commandLine, report); + } + else if (!report.hasViolations()) { + renderSuccess(commandLine); + } + else if (report.combined()) { + renderCombined(commandLine, report); + } + else { + renderSeparate(commandLine, report); + } + commandLine.getOut().flush(); + commandLine.getErr().flush(); + } + + private void renderError(CommandLine commandLine, CheckReport report) { + this.templates.checkError(Objects.requireNonNull(report.errorMessage())) + .render(new PrintWriterOutput(commandLine.getErr())); + } + + private void renderSuccess(CommandLine commandLine) { + this.templates.checkSuccess().render(new PrintWriterOutput(commandLine.getOut())); + } + + private void renderCombined(CommandLine commandLine, CheckReport report) { + this.templates.checkCombined(report.formattingProblems(), report.checkstyleViolations()) + .render(new PrintWriterOutput(commandLine.getErr())); + } + + private void renderSeparate(CommandLine commandLine, CheckReport report) { + if (!report.skipFormat()) { + this.templates.checkFormatting(report.formattingProblems()) + .render(new PrintWriterOutput(commandLine.getErr())); + } + if (!report.skipCheckstyle()) { + this.templates.checkCheckstyleDetailed(report.checkstyleViolations()) + .render(new PrintWriterOutput(commandLine.getErr())); + } + } + +} diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/CheckRunner.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/CheckRunner.java new file mode 100644 index 00000000..38dff656 --- /dev/null +++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/CheckRunner.java @@ -0,0 +1,78 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli.check; + +import java.io.File; +import java.util.List; +import java.util.Properties; +import java.util.stream.Collectors; + +import com.puppycrawl.tools.checkstyle.api.AuditEvent; +import com.puppycrawl.tools.checkstyle.api.CheckstyleException; +import org.springframework.stereotype.Component; + +import io.spring.javaformat.cli.InputOptions; +import io.spring.javaformat.cli.format.FormattingService; +import io.spring.javaformat.cli.scan.FileScanner; +import io.spring.javaformat.formatter.FileEdit; + +@Component +public class CheckRunner { + + private final FormattingService formattingService; + + private final CheckstyleService checkstyleService; + + private final FileScanner fileScanner; + + CheckRunner(FormattingService formattingService, CheckstyleService checkstyleService, FileScanner fileScanner) { + this.formattingService = formattingService; + this.checkstyleService = checkstyleService; + this.fileScanner = fileScanner; + } + + CheckReport run(Inputs inputs) { + try { + List files = this.fileScanner.scan(inputs.formatOptions()); + List formattingProblems = inputs.skipFormat() ? List.of() : collectFormattingProblems(files, inputs); + List checkstyleViolations = inputs.skipCheckstyle() ? List.of() + : this.checkstyleService.run(files, inputs.checkstyleProperties()); + return new CheckReport(formattingProblems, checkstyleViolations, inputs.skipFormat(), inputs.skipCheckstyle(), + null, false); + } + catch (CheckstyleException ex) { + return CheckReport.failure(inputs.skipFormat(), inputs.skipCheckstyle(), + "unable to run checkstyle: " + ex.getMessage(), true); + } + catch (Exception ex) { + return CheckReport.failure(inputs.skipFormat(), inputs.skipCheckstyle(), + "unable to check formatting: " + ex.getMessage(), false); + } + } + + private List collectFormattingProblems(List files, Inputs inputs) throws Exception { + return this.formattingService.format(files, inputs.formatOptions()) + .filter(FileEdit::hasEdits) + .map(FileEdit::getFile) + .collect(Collectors.toList()); + } + + record Inputs(InputOptions formatOptions, Properties checkstyleProperties, + boolean skipFormat, boolean skipCheckstyle) { + } + +} diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/CheckstyleService.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/CheckstyleService.java new file mode 100644 index 00000000..4dbfc718 --- /dev/null +++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/CheckstyleService.java @@ -0,0 +1,116 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli.check; + +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +import com.puppycrawl.tools.checkstyle.Checker; +import com.puppycrawl.tools.checkstyle.ConfigurationLoader; +import com.puppycrawl.tools.checkstyle.ConfigurationLoader.IgnoredModulesOptions; +import com.puppycrawl.tools.checkstyle.ModuleFactory; +import com.puppycrawl.tools.checkstyle.PackageObjectFactory; +import com.puppycrawl.tools.checkstyle.PropertiesExpander; +import com.puppycrawl.tools.checkstyle.ThreadModeSettings; +import com.puppycrawl.tools.checkstyle.api.AuditEvent; +import com.puppycrawl.tools.checkstyle.api.AuditListener; +import com.puppycrawl.tools.checkstyle.api.CheckstyleException; +import com.puppycrawl.tools.checkstyle.api.Configuration; +import com.puppycrawl.tools.checkstyle.api.RootModule; +import com.puppycrawl.tools.checkstyle.api.SeverityLevel; +import org.springframework.stereotype.Component; +import org.xml.sax.InputSource; + +/** + * Runs Spring checkstyle checks against a list of files. + * + * @author Tim Sparg + */ +@Component +class CheckstyleService { + + List run(List files, Properties properties) throws CheckstyleException { + try (InputStream is = getClass().getResourceAsStream("checkstyle.xml")) { + return run(files, is, properties); + } + catch (Exception ex) { + throw new CheckstyleException("Failed to load checkstyle configuration", ex); + } + } + + private List run(List files, InputStream configStream, Properties properties) + throws CheckstyleException { + Configuration config = ConfigurationLoader.loadConfiguration(new InputSource(configStream), + new PropertiesExpander(properties), IgnoredModulesOptions.EXECUTE, + ThreadModeSettings.SINGLE_THREAD_MODE_INSTANCE); + ClassLoader cl = getClass().getClassLoader(); + ModuleFactory factory = new PackageObjectFactory(Checker.class.getPackage().getName(), cl); + RootModule rootModule = (RootModule) factory.createModule(config.getName()); + rootModule.setModuleClassLoader(cl); + rootModule.configure(config); + CollectingAuditListener listener = new CollectingAuditListener(); + rootModule.addListener(listener); + try { + rootModule.process(files); + } + finally { + rootModule.destroy(); + } + return listener.getViolations(); + } + + private static final class CollectingAuditListener implements AuditListener { + + private final List violations = new ArrayList<>(); + + @Override + public void auditStarted(AuditEvent event) { + } + + @Override + public void auditFinished(AuditEvent event) { + } + + @Override + public void fileStarted(AuditEvent event) { + } + + @Override + public void fileFinished(AuditEvent event) { + } + + @Override + public void addError(AuditEvent event) { + if (event.getSeverityLevel() != SeverityLevel.IGNORE) { + this.violations.add(event); + } + } + + @Override + public void addException(AuditEvent event, Throwable throwable) { + } + + List getViolations() { + return this.violations; + } + + } + +} diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/RenderHelper.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/RenderHelper.java new file mode 100644 index 00000000..645c66f6 --- /dev/null +++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/RenderHelper.java @@ -0,0 +1,106 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli.check; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.puppycrawl.tools.checkstyle.api.AuditEvent; +import picocli.CommandLine.Help.Ansi; + +public final class RenderHelper { + + private static final Path CWD = Paths.get("").toAbsolutePath(); + + private RenderHelper() { + } + + public static String plural(long count, String noun) { + return count + " " + noun + (count == 1 ? "" : "s"); + } + + public static String styledPath(File file) { + return Ansi.AUTO.string("@|cyan " + relativize(file) + "|@"); + } + + public static String styledPath(String path) { + return Ansi.AUTO.string("@|cyan " + path + "|@"); + } + + public static String styledLineNumber(int line) { + return Ansi.AUTO.string("@|bold " + line + "|@"); + } + + public static String styledMessage(String message) { + return Ansi.AUTO.string("@|faint " + message + "|@"); + } + + public static String success(String message) { + return Ansi.AUTO.string("@|bold,green " + message + "|@"); + } + + public static String error(String message) { + return Ansi.AUTO.string("@|bold,red error:|@ " + message); + } + + public static String formattingHeader(int count) { + return "\n" + Ansi.AUTO.string("@|bold,underline,red Formatting violations found|@") + " in " + + plural(count, "file") + ":"; + } + + public static String checkstyleHeader(List violations) { + long fileCount = violations.stream().map(AuditEvent::getFileName).distinct().count(); + return "\n" + Ansi.AUTO.string("@|bold,underline,red Checkstyle violations found|@") + " in " + + plural(fileCount, "file") + " (" + plural(violations.size(), "violation") + "):"; + } + + public static String combinedHeader(int count) { + return "\n" + Ansi.AUTO.string("@|bold,underline,red Violations found|@") + " in " + plural(count, "file") + + ":"; + } + + public static List combinedLines(List formattingProblems, List checkstyleViolations) { + return Stream + .concat(formattingProblems.stream().map((f) -> Map.entry(relativize(f), "format")), + checkstyleViolations.stream() + .map((v) -> Map.entry(relativize(new File(v.getFileName())), "checkstyle"))) + .collect(Collectors.groupingBy(Map.Entry::getKey, LinkedHashMap::new, + Collectors.mapping(Map.Entry::getValue, Collectors.toCollection(LinkedHashSet::new)))) + .entrySet() + .stream() + .map((e) -> styledPath(e.getKey()) + " " + + Ansi.AUTO.string(e.getValue().stream().collect(Collectors.joining(", ", "@|faint [", "]|@")))) + .toList(); + } + + public static String relativize(File file) { + try { + return CWD.relativize(file.toPath().toAbsolutePath()).toString(); + } + catch (IllegalArgumentException ex) { + return file.getAbsolutePath(); + } + } + +} diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/package-info.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/package-info.java new file mode 100644 index 00000000..3dedef66 --- /dev/null +++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/check/package-info.java @@ -0,0 +1,25 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Checkstyle-based check command. + * + * @author Tim Sparg + */ +@NullMarked +package io.spring.javaformat.cli.check; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/format/ApplyCommand.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/format/ApplyCommand.java new file mode 100644 index 00000000..7f027e7b --- /dev/null +++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/format/ApplyCommand.java @@ -0,0 +1,99 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli.format; + +import java.io.File; +import java.io.PrintWriter; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.Callable; + +import org.springframework.stereotype.Component; +import picocli.CommandLine.Command; +import picocli.CommandLine.Help.Ansi; +import picocli.CommandLine.Mixin; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Spec; + +import io.spring.javaformat.cli.InputOptions; +import io.spring.javaformat.formatter.FileEdit; +import io.spring.javaformat.formatter.FileFormatterException; + +/** + * Apply formatting command. + * + * @author Tim Sparg + */ +@Component +@Command(name = "apply", mixinStandardHelpOptions = true, description = "Apply formatting to the codebase") +public class ApplyCommand implements Callable { + + private static final Path CWD = Paths.get("").toAbsolutePath(); + + @Spec + private CommandSpec spec; + + @Mixin + private InputOptions options; + + private final FormattingService formattingService; + + @SuppressWarnings("NullAway.Init") + ApplyCommand(FormattingService formattingService) { + this.formattingService = formattingService; + } + + @Override + public Integer call() { + try { + this.formattingService.format(this.options).filter(FileEdit::hasEdits).forEach(this::save); + return 0; + } + catch (FileFormatterException ex) { + err().println(ansi( + "@|bold,red error:|@ unable to format file " + relativize(ex.getFile()) + ": " + ex.getMessage())); + return 1; + } + } + + private PrintWriter err() { + return this.spec.commandLine().getErr(); + } + + private PrintWriter out() { + return this.spec.commandLine().getOut(); + } + + private void save(FileEdit edit) { + out().println(ansi("@|bold,green formatted|@ " + relativize(edit.getFile()))); + edit.save(); + } + + private String ansi(String markup) { + return Ansi.AUTO.string(markup); + } + + private static String relativize(File file) { + try { + return CWD.relativize(file.toPath().toAbsolutePath()).toString(); + } + catch (IllegalArgumentException ex) { + return file.getAbsolutePath(); + } + } + +} diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/format/FormattingService.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/format/FormattingService.java new file mode 100644 index 00000000..601d0b94 --- /dev/null +++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/format/FormattingService.java @@ -0,0 +1,58 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli.format; + +import java.io.File; +import java.util.List; +import java.util.stream.Stream; + +import org.springframework.stereotype.Component; + +import io.spring.javaformat.cli.InputOptions; +import io.spring.javaformat.cli.scan.FileFormatterFactory; +import io.spring.javaformat.cli.scan.FileScanner; +import io.spring.javaformat.formatter.FileEdit; +import io.spring.javaformat.formatter.FileFormatter; +import io.spring.javaformat.formatter.FileFormatterException; + +/** + * Applies formatting to scanned files. + * + * @author Tim Sparg + */ +@Component +public class FormattingService { + + private final FileFormatterFactory fileFormatterFactory; + + private final FileScanner fileScanner; + + FormattingService(FileFormatterFactory fileFormatterFactory, FileScanner fileScanner) { + this.fileFormatterFactory = fileFormatterFactory; + this.fileScanner = fileScanner; + } + + public Stream format(InputOptions options) throws FileFormatterException { + return format(this.fileScanner.scan(options), options); + } + + public Stream format(List files, InputOptions options) throws FileFormatterException { + FileFormatter formatter = this.fileFormatterFactory.create(options.path); + return formatter.formatFiles(files, options.encoding, options.resolveLineSeparator()); + } + +} diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/format/package-info.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/format/package-info.java new file mode 100644 index 00000000..73e21b93 --- /dev/null +++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/format/package-info.java @@ -0,0 +1,25 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Apply formatting command. + * + * @author Tim Sparg + */ +@NullMarked +package io.spring.javaformat.cli.format; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/package-info.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/package-info.java new file mode 100644 index 00000000..7abe78d9 --- /dev/null +++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/package-info.java @@ -0,0 +1,25 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Spring JavaFormat CLI — command-line tool for applying and validating Java formatting. + * + * @author Tim Sparg + */ +@NullMarked +package io.spring.javaformat.cli; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/scan/FileFormatterFactory.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/scan/FileFormatterFactory.java new file mode 100644 index 00000000..4b61b8a3 --- /dev/null +++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/scan/FileFormatterFactory.java @@ -0,0 +1,33 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli.scan; + +import java.io.File; + +import io.spring.javaformat.formatter.FileFormatter; + +/** + * Factory for creating a {@link FileFormatter} from a base directory. + * + * @author Tim Sparg + */ +@FunctionalInterface +public interface FileFormatterFactory { + + FileFormatter create(File basedir); + +} diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/scan/FileScanner.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/scan/FileScanner.java new file mode 100644 index 00000000..f3c4c1da --- /dev/null +++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/scan/FileScanner.java @@ -0,0 +1,51 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli.scan; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.codehaus.plexus.util.DirectoryScanner; +import org.springframework.stereotype.Component; + +import io.spring.javaformat.cli.InputOptions; + +/** + * Scans files from a directory using include and exclude patterns. + * + * @author Tim Sparg + */ +@Component +public class FileScanner { + + public List scan(InputOptions options) { + DirectoryScanner scanner = new DirectoryScanner(); + scanner.setBasedir(options.path); + scanner.setIncludes(options.includes); + scanner.setExcludes(options.excludes); + scanner.addDefaultExcludes(); + scanner.setCaseSensitive(false); + scanner.setFollowSymlinks(false); + scanner.scan(); + return Arrays.stream(scanner.getIncludedFiles()) + .map((name) -> new File(options.path, name)) + .collect(Collectors.toList()); + } + +} diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/scan/ScanConfiguration.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/scan/ScanConfiguration.java new file mode 100644 index 00000000..609d6001 --- /dev/null +++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/scan/ScanConfiguration.java @@ -0,0 +1,40 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli.scan; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import io.spring.javaformat.config.JavaFormatConfig; +import io.spring.javaformat.formatter.FileFormatter; + + + +/** + * Spring configuration for the scan sub-system. + * + * @author Tim Sparg + */ +@Configuration +class ScanConfiguration { + + @Bean + FileFormatterFactory fileFormatterFactory() { + return (basedir) -> new FileFormatter(JavaFormatConfig.findFrom(basedir)); + } + +} diff --git a/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/scan/package-info.java b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/scan/package-info.java new file mode 100644 index 00000000..7a2838e2 --- /dev/null +++ b/spring-javaformat-cli/src/main/java/io/spring/javaformat/cli/scan/package-info.java @@ -0,0 +1,25 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * File scanning and formatting infrastructure shared across commands. + * + * @author Tim Sparg + */ +@NullMarked +package io.spring.javaformat.cli.scan; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-javaformat-cli/src/main/jte/checkCheckstyleDetailed.jte b/spring-javaformat-cli/src/main/jte/checkCheckstyleDetailed.jte new file mode 100644 index 00000000..dd1ea810 --- /dev/null +++ b/spring-javaformat-cli/src/main/jte/checkCheckstyleDetailed.jte @@ -0,0 +1,11 @@ +@import com.puppycrawl.tools.checkstyle.api.AuditEvent +@import java.io.File +@import java.util.List +@import static io.spring.javaformat.cli.check.RenderHelper.checkstyleHeader +@import static io.spring.javaformat.cli.check.RenderHelper.styledLineNumber +@import static io.spring.javaformat.cli.check.RenderHelper.styledMessage +@import static io.spring.javaformat.cli.check.RenderHelper.styledPath +@param List violations +${checkstyleHeader(violations)} +@for(AuditEvent violation : violations) ${styledPath(new File(violation.getFileName()))}:${styledLineNumber(violation.getLine())}: ${styledMessage(violation.getMessage())} +@endfor \ No newline at end of file diff --git a/spring-javaformat-cli/src/main/jte/checkCombined.jte b/spring-javaformat-cli/src/main/jte/checkCombined.jte new file mode 100644 index 00000000..e1587a3a --- /dev/null +++ b/spring-javaformat-cli/src/main/jte/checkCombined.jte @@ -0,0 +1,11 @@ +@import com.puppycrawl.tools.checkstyle.api.AuditEvent +@import java.io.File +@import java.util.List +@import static io.spring.javaformat.cli.check.RenderHelper.combinedHeader +@import static io.spring.javaformat.cli.check.RenderHelper.combinedLines +@param List formattingProblems +@param List checkstyleViolations +!{List lines = combinedLines(formattingProblems, checkstyleViolations);} +${combinedHeader(lines.size())} +@for(String line : lines) ${line} +@endfor \ No newline at end of file diff --git a/spring-javaformat-cli/src/main/jte/checkError.jte b/spring-javaformat-cli/src/main/jte/checkError.jte new file mode 100644 index 00000000..363ae27a --- /dev/null +++ b/spring-javaformat-cli/src/main/jte/checkError.jte @@ -0,0 +1,3 @@ +@import static io.spring.javaformat.cli.check.RenderHelper.error +@param String message +${error(message)} diff --git a/spring-javaformat-cli/src/main/jte/checkFormatting.jte b/spring-javaformat-cli/src/main/jte/checkFormatting.jte new file mode 100644 index 00000000..78c56093 --- /dev/null +++ b/spring-javaformat-cli/src/main/jte/checkFormatting.jte @@ -0,0 +1,8 @@ +@import java.io.File +@import java.util.List +@import static io.spring.javaformat.cli.check.RenderHelper.formattingHeader +@import static io.spring.javaformat.cli.check.RenderHelper.styledPath +@param List files +${formattingHeader(files.size())} +@for(File file : files) ${styledPath(file)} +@endfor \ No newline at end of file diff --git a/spring-javaformat-cli/src/main/jte/checkSuccess.jte b/spring-javaformat-cli/src/main/jte/checkSuccess.jte new file mode 100644 index 00000000..39c2ad9c --- /dev/null +++ b/spring-javaformat-cli/src/main/jte/checkSuccess.jte @@ -0,0 +1,2 @@ +@import static io.spring.javaformat.cli.check.RenderHelper.success +${success("All checks passed.")} diff --git a/spring-javaformat-cli/src/main/resources/META-INF/native-image/io.spring.javaformat/spring-javaformat-cli/reflect-config.json b/spring-javaformat-cli/src/main/resources/META-INF/native-image/io.spring.javaformat/spring-javaformat-cli/reflect-config.json new file mode 100644 index 00000000..dd0f8e1c --- /dev/null +++ b/spring-javaformat-cli/src/main/resources/META-INF/native-image/io.spring.javaformat/spring-javaformat-cli/reflect-config.json @@ -0,0 +1,433 @@ +[ + {"name": "java.util.Date[]", "unsafeAllocated": true}, + {"name": "java.util.Calendar[]", "unsafeAllocated": true}, + {"name": "java.sql.Date[]", "unsafeAllocated": true}, + {"name": "java.sql.Time[]", "unsafeAllocated": true}, + {"name": "java.sql.Timestamp[]", "unsafeAllocated": true}, + {"name": "java.math.BigDecimal[]", "unsafeAllocated": true}, + {"name": "java.math.BigInteger[]", "unsafeAllocated": true}, + {"name": "java.net.URI[]", "unsafeAllocated": true}, + {"name": "java.net.URL[]", "unsafeAllocated": true}, + {"name": "java.time.LocalDate[]", "unsafeAllocated": true}, + {"name": "java.time.LocalTime[]", "unsafeAllocated": true}, + {"name": "java.time.LocalDateTime[]", "unsafeAllocated": true}, + {"name": "java.time.ZonedDateTime[]", "unsafeAllocated": true}, + {"name": "java.time.OffsetDateTime[]", "unsafeAllocated": true}, + {"name": "java.time.OffsetTime[]", "unsafeAllocated": true}, + {"name": "java.time.Instant[]", "unsafeAllocated": true}, + {"name": "java.time.Duration[]", "unsafeAllocated": true}, + {"name": "java.time.Period[]", "unsafeAllocated": true}, + {"name": "java.util.UUID[]", "unsafeAllocated": true}, + {"name": "java.lang.Boolean[]", "unsafeAllocated": true}, + {"name": "java.lang.Integer[]", "unsafeAllocated": true}, + {"name": "java.lang.Long[]", "unsafeAllocated": true}, + {"name": "java.lang.Double[]", "unsafeAllocated": true}, + {"name": "java.lang.Float[]", "unsafeAllocated": true}, + {"name": "java.lang.Byte[]", "unsafeAllocated": true}, + {"name": "java.lang.Short[]", "unsafeAllocated": true}, + {"name": "java.lang.Character[]", "unsafeAllocated": true}, + {"name": "java.lang.Number[]", "unsafeAllocated": true}, + {"name": "java.lang.String[]", "unsafeAllocated": true}, + { + "name": "io.spring.javaformat.eclipse.jdt.jdk17.internal.formatter.DefaultCodeFormatter", + "fields": [ + {"name": "astRoot"}, + {"name": "tokenManager"} + ] + }, + { + "name": "com.puppycrawl.tools.checkstyle.api.TokenTypes", + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.api.AutomaticBean", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.api.AbstractViolationReporter", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.api.AbstractCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.Checker", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.TreeWalker", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.spring.javaformat.checkstyle.SpringChecks", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.spring.javaformat.checkstyle.check.SpringHeaderCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.spring.javaformat.checkstyle.check.SpringTestFileNameCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.spring.javaformat.checkstyle.check.SpringAnnotationLocationCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.spring.javaformat.checkstyle.check.SpringAnnotationAttributeConciseValueCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.spring.javaformat.checkstyle.check.SpringHideUtilityClassConstructor", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.spring.javaformat.checkstyle.check.SpringImportOrderCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.spring.javaformat.checkstyle.check.SpringNoWhitespaceBeforeCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.spring.javaformat.checkstyle.check.SpringAvoidStaticImportCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.spring.javaformat.checkstyle.check.SpringLambdaCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.spring.javaformat.checkstyle.check.SpringTernaryCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.spring.javaformat.checkstyle.check.SpringCatchCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.spring.javaformat.checkstyle.check.SpringJavadocCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.spring.javaformat.checkstyle.check.SpringLeadingWhitespaceCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.spring.javaformat.checkstyle.check.SpringMethodOrderCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.spring.javaformat.checkstyle.check.SpringMethodVisibilityCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.spring.javaformat.checkstyle.check.SpringNullabilityCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "io.spring.javaformat.checkstyle.check.SpringParenPadCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.NewlineAtEndOfFileCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocPackageCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.annotation.AnnotationUseStyleCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.annotation.MissingOverrideCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.annotation.MissingDeprecatedCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.annotation.PackageAnnotationCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.blocks.EmptyBlockCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.blocks.RightCurlyCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.blocks.NeedBracesCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.blocks.AvoidNestedBlocksCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.design.FinalClassCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.design.InterfaceIsTypeCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.design.MutableExceptionCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.design.InnerTypeLastCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.design.OneTopLevelClassCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.coding.CovariantEqualsCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.coding.EmptyStatementCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.coding.EqualsHashCodeCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.coding.InnerAssignmentCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.coding.SimplifyBooleanExpressionCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.coding.SimplifyBooleanReturnCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.coding.StringLiteralEqualityCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.coding.NestedForDepthCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.coding.NestedIfDepthCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.coding.NestedTryDepthCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.coding.MultipleVariableDeclarationsCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.coding.RequireThisCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.coding.OneStatementPerLineCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.coding.UnnecessarySemicolonInEnumerationCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.imports.AvoidStarImportCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.imports.RedundantImportCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTypeCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocVariableCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocStyleCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.javadoc.NonEmptyAtclauseDescriptionCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTagContinuationIndentationCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.javadoc.AtclauseOrderCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.indentation.CommentsIndentationCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.UpperEllCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.ArrayTypeStyleCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.OuterTypeFilenameCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.modifier.RedundantModifierCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.modifier.ModifierOrderCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.regexp.RegexpCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.whitespace.GenericWhitespaceCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.whitespace.MethodParamPadCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.whitespace.NoWhitespaceAfterCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.whitespace.TypecastParenPadCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.whitespace.WhitespaceAfterCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + }, + { + "name": "com.puppycrawl.tools.checkstyle.checks.whitespace.WhitespaceAroundCheck", + "allDeclaredConstructors": true, + "allDeclaredMethods": true + } +] diff --git a/spring-javaformat-cli/src/main/resources/META-INF/native-image/io.spring.javaformat/spring-javaformat-cli/resource-config.json b/spring-javaformat-cli/src/main/resources/META-INF/native-image/io.spring.javaformat/spring-javaformat-cli/resource-config.json new file mode 100644 index 00000000..2f8356f8 --- /dev/null +++ b/spring-javaformat-cli/src/main/resources/META-INF/native-image/io.spring.javaformat/spring-javaformat-cli/resource-config.json @@ -0,0 +1,15 @@ +{ + "resources": [ + {"pattern": "io/spring/javaformat/cli/version.properties"}, + {"pattern": "io/spring/javaformat/formatter/eclipse/formatter.prefs"}, + {"pattern": "io/spring/javaformat/eclipse/jdt/jdk17/.*\\.rsc"}, + {"pattern": "io/spring/javaformat/eclipse/jdt/jdk17/.*\\.props"}, + {"pattern": "io/spring/javaformat/eclipse/jdt/jdk17/.*\\.properties"}, + {"pattern": "io/spring/javaformat/cli/check/checkstyle.xml"}, + {"pattern": "io/spring/javaformat/checkstyle/.*\\.xml"}, + {"pattern": "io/spring/javaformat/checkstyle/.*\\.txt"}, + {"pattern": "io/spring/javaformat/checkstyle/.*\\.properties"}, + {"pattern": "com/puppycrawl/tools/checkstyle/.*\\.dtd"}, + {"pattern": "com/puppycrawl/tools/checkstyle/.*\\.properties"} + ] +} diff --git a/spring-javaformat-cli/src/main/resources/application.properties b/spring-javaformat-cli/src/main/resources/application.properties new file mode 100644 index 00000000..891e4df1 --- /dev/null +++ b/spring-javaformat-cli/src/main/resources/application.properties @@ -0,0 +1,4 @@ +spring.main.web-application-type=none +logging.level.root=ERROR +logging.level.io.spring.javaformat=ERROR +logging.level.org.springframework=ERROR diff --git a/spring-javaformat-cli/src/main/resources/io/spring/javaformat/cli/check/checkstyle.xml b/spring-javaformat-cli/src/main/resources/io/spring/javaformat/cli/check/checkstyle.xml new file mode 100644 index 00000000..8f957724 --- /dev/null +++ b/spring-javaformat-cli/src/main/resources/io/spring/javaformat/cli/check/checkstyle.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/spring-javaformat-cli/src/main/resources/io/spring/javaformat/cli/version.properties b/spring-javaformat-cli/src/main/resources/io/spring/javaformat/cli/version.properties new file mode 100644 index 00000000..a9ca5c52 --- /dev/null +++ b/spring-javaformat-cli/src/main/resources/io/spring/javaformat/cli/version.properties @@ -0,0 +1 @@ +version=@project.version@ diff --git a/spring-javaformat-cli/src/test/java/io/spring/javaformat/cli/AbstractCommandIntegrationTests.java b/spring-javaformat-cli/src/test/java/io/spring/javaformat/cli/AbstractCommandIntegrationTests.java new file mode 100644 index 00000000..17e67689 --- /dev/null +++ b/spring-javaformat-cli/src/test/java/io/spring/javaformat/cli/AbstractCommandIntegrationTests.java @@ -0,0 +1,69 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.Callable; +import java.util.stream.Stream; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import picocli.CommandLine; +import picocli.CommandLine.IFactory; + +/** + * Base class for command integration tests. + * + * @author Tim Sparg + */ +@SpringBootTest(webEnvironment = WebEnvironment.NONE) +abstract class AbstractCommandIntegrationTests { + + @Autowired + IFactory factory; + + abstract Callable command(); + + int execute(StringWriter out, StringWriter err, String... args) { + return new CommandLine(command(), this.factory).setOut(new PrintWriter(out)) + .setErr(new PrintWriter(err)) + .execute(args); + } + + void copyFixture(Path fixturesDir, String name, Path target) throws IOException { + Path fixtureDir = fixturesDir.resolve(name); + try (Stream files = Files.walk(fixtureDir)) { + files.filter(Files::isRegularFile).forEach((source) -> { + try { + Path dest = target.resolve(fixtureDir.relativize(source)); + Files.createDirectories(dest.getParent()); + Files.copy(source, dest); + } + catch (IOException ex) { + throw new UncheckedIOException(ex); + } + }); + } + } + +} diff --git a/spring-javaformat-cli/src/test/java/io/spring/javaformat/cli/ApplyCommandIntegrationTests.java b/spring-javaformat-cli/src/test/java/io/spring/javaformat/cli/ApplyCommandIntegrationTests.java new file mode 100644 index 00000000..b4083564 --- /dev/null +++ b/spring-javaformat-cli/src/test/java/io/spring/javaformat/cli/ApplyCommandIntegrationTests.java @@ -0,0 +1,113 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli; + +import java.io.StringWriter; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.springframework.beans.factory.annotation.Autowired; + +import io.spring.javaformat.cli.format.ApplyCommand; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link ApplyCommand}. + * + * @author Tim Sparg + */ +class ApplyCommandIntegrationTests extends AbstractCommandIntegrationTests { + + private static final Path FIXTURES_DIR = Path.of("src/test/resources/fixtures/format"); + + @Autowired + ApplyCommand applyCommand; + + @Override + ApplyCommand command() { + return this.applyCommand; + } + + @Test + void defaultEncodingFormatsFile(@TempDir Path tempDir) throws Exception { + copyFixture(FIXTURES_DIR, "default", tempDir); + String before = Files.readString(tempDir.resolve("Main.java")); + + execute(new StringWriter(), new StringWriter(), tempDir.toString()); + + assertThat(Files.readString(tempDir.resolve("Main.java"))).isNotEqualTo(before); + } + + @Test + void nonDefaultEncodingFormatsFile(@TempDir Path tempDir) throws Exception { + Charset latin1 = Charset.forName("ISO-8859-1"); + copyFixture(FIXTURES_DIR, "latin1", tempDir); + String before = Files.readString(tempDir.resolve("Main.java"), latin1); + + execute(new StringWriter(), new StringWriter(), "--encoding", "ISO-8859-1", tempDir.toString()); + + String after = Files.readString(tempDir.resolve("Main.java"), latin1); + assertThat(after).isNotEqualTo(before); + assertThat(after).contains("é"); + } + + @Test + void includesOnlyFormatsMatchingFiles(@TempDir Path tempDir) throws Exception { + copyFixture(FIXTURES_DIR, "default", tempDir); + String mainBefore = readFile(tempDir, "Main.java"); + String noteBefore = Files.readString(tempDir.resolve("notes.txt")); + String excludedBefore = readFile(tempDir, "excluded/Excluded.java"); + + execute(new StringWriter(), new StringWriter(), "--includes", "**/Main.java", tempDir.toString()); + + assertThat(readFile(tempDir, "Main.java")).isNotEqualTo(mainBefore); + assertThat(Files.readString(tempDir.resolve("notes.txt"))).isEqualTo(noteBefore); + assertThat(readFile(tempDir, "excluded/Excluded.java")).isEqualTo(excludedBefore); + } + + @Test + void excludesSkipsExcludedFiles(@TempDir Path tempDir) throws Exception { + copyFixture(FIXTURES_DIR, "default", tempDir); + String mainBefore = readFile(tempDir, "Main.java"); + String excludedBefore = readFile(tempDir, "excluded/Excluded.java"); + + execute(new StringWriter(), new StringWriter(), "--excludes", "excluded/**", tempDir.toString()); + + assertThat(readFile(tempDir, "Main.java")).isNotEqualTo(mainBefore); + assertThat(readFile(tempDir, "excluded/Excluded.java")).isEqualTo(excludedBefore); + } + + @Test + void springJavaFormatConfigChangesIndentationStyle(@TempDir Path tempDir) throws Exception { + copyFixture(FIXTURES_DIR, "spaces-config", tempDir); + + execute(new StringWriter(), new StringWriter(), tempDir.toString()); + + String formatted = readFile(tempDir, "Main.java"); + assertThat(formatted).contains(" void method() {"); + assertThat(formatted).doesNotContain("\tvoid method() {"); + } + + private String readFile(Path dir, String path) throws Exception { + return Files.readString(dir.resolve(path)); + } + +} diff --git a/spring-javaformat-cli/src/test/java/io/spring/javaformat/cli/CheckCommandIntegrationTests.java b/spring-javaformat-cli/src/test/java/io/spring/javaformat/cli/CheckCommandIntegrationTests.java new file mode 100644 index 00000000..5c7c243e --- /dev/null +++ b/spring-javaformat-cli/src/test/java/io/spring/javaformat/cli/CheckCommandIntegrationTests.java @@ -0,0 +1,151 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli; + +import java.io.StringWriter; +import java.nio.file.Path; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.springframework.beans.factory.annotation.Autowired; + +import io.spring.javaformat.cli.check.CheckCommand; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for {@link CheckCommand}. + * + * @author Tim Sparg + */ +class CheckCommandIntegrationTests extends AbstractCommandIntegrationTests { + + private static final Path CHECK_FIXTURES_DIR = Path.of("src/test/resources/fixtures/check"); + + private static final Path FORMAT_FIXTURES_DIR = Path.of("src/test/resources/fixtures/format"); + + @Autowired + CheckCommand checkCommand; + + @Override + CheckCommand command() { + return this.checkCommand; + } + + @Nested + class SkipFormat { + + @Test + void violationsAreReported(@TempDir Path tempDir) throws Exception { + copyFixture(CHECK_FIXTURES_DIR, "star-import", tempDir); + + StringWriter err = new StringWriter(); + int exitCode = execute(new StringWriter(), err, "--skip-format", "--project-root-package", + "com.example", tempDir.toString()); + + assertThat(exitCode).isEqualTo(1); + assertThat(err.toString()).contains("WithStarImport.java"); + } + + @Test + void includesOnlyChecksMatchingFiles(@TempDir Path tempDir) throws Exception { + copyFixture(CHECK_FIXTURES_DIR, "includes-filter", tempDir); + + StringWriter err = new StringWriter(); + int exitCode = execute(new StringWriter(), err, "--skip-format", "--project-root-package", + "com.example", "--includes", "**/WithStarImport.java", tempDir.toString()); + + assertThat(exitCode).isEqualTo(1); + assertThat(err.toString()).contains("WithStarImport.java"); + assertThat(err.toString()).doesNotContain("ExcludedFromIncludes.java"); + } + + @Test + void excludesSkipsExcludedFiles(@TempDir Path tempDir) throws Exception { + copyFixture(CHECK_FIXTURES_DIR, "excludes-filter", tempDir); + + StringWriter err = new StringWriter(); + int exitCode = execute(new StringWriter(), err, "--skip-format", "--project-root-package", + "com.example", "--excludes", "excluded/**", tempDir.toString()); + + assertThat(exitCode).isEqualTo(1); + assertThat(err.toString()).contains("WithStarImport.java"); + assertThat(err.toString()).doesNotContain("ExcludedWithStarImport.java"); + } + + @Test + void customHeaderFileIsUsed(@TempDir Path tempDir) throws Exception { + copyFixture(CHECK_FIXTURES_DIR, "custom-header", tempDir); + + int exitCode = execute(new StringWriter(), new StringWriter(), "--skip-format", "--header-type", "FILE", + "--header-file", + tempDir.resolve("header.txt").toString(), tempDir.toString()); + + assertThat(exitCode).isEqualTo(0); + } + + } + + @Nested + class SkipCheckstyle { + + @Test + void violationsAreReported(@TempDir Path tempDir) throws Exception { + copyFixture(FORMAT_FIXTURES_DIR, "default", tempDir); + + StringWriter err = new StringWriter(); + int exitCode = execute(new StringWriter(), err, "--skip-checkstyle", tempDir.toString()); + + assertThat(exitCode).isEqualTo(1); + assertThat(err.toString()).contains("Main.java"); + } + + @Test + void nonDefaultEncodingDetectsViolations(@TempDir Path tempDir) throws Exception { + copyFixture(FORMAT_FIXTURES_DIR, "latin1", tempDir); + + StringWriter err = new StringWriter(); + int exitCode = execute(new StringWriter(), err, "--skip-checkstyle", "--encoding", "ISO-8859-1", + tempDir.toString()); + + assertThat(exitCode).isEqualTo(1); + assertThat(err.toString()).contains("Main.java"); + } + + } + + @Nested + class Combined { + + @Test + void groupsViolationsByFile(@TempDir Path tempDir) throws Exception { + copyFixture(CHECK_FIXTURES_DIR, "combined", tempDir); + + StringWriter err = new StringWriter(); + int exitCode = execute(new StringWriter(), err, "--header-type", "UNCHECKED", tempDir.toString()); + + assertThat(exitCode).isEqualTo(1); + assertThat(err.toString()).contains("Violations found in 1 file"); + assertThat(err.toString()).contains("Main.java").contains("format, checkstyle"); + assertThat(err.toString()).doesNotContain("Checkstyle violations found"); + assertThat(err.toString()).doesNotContain(":4:"); + } + + } + +} diff --git a/spring-javaformat-cli/src/test/java/io/spring/javaformat/cli/InputOptionsTests.java b/spring-javaformat-cli/src/test/java/io/spring/javaformat/cli/InputOptionsTests.java new file mode 100644 index 00000000..37ae8c79 --- /dev/null +++ b/spring-javaformat-cli/src/test/java/io/spring/javaformat/cli/InputOptionsTests.java @@ -0,0 +1,167 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Path; +import java.util.concurrent.Callable; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * Tests for {@link InputOptions} — plain picocli unit tests with no Spring context. + * + * @author Tim Sparg + */ +class InputOptionsTests { + + private TestCommand command; + + private CommandLine commandLine; + + @BeforeEach + void setUp() { + this.command = new TestCommand(); + this.commandLine = new CommandLine(this.command); + } + + @Nested + class PathOption { + + @Test + void acceptsExistingDirectory(@TempDir Path tempDir) { + InputOptionsTests.this.commandLine.parseArgs(tempDir.toString()); + assertThat(InputOptionsTests.this.command.options.path).isEqualTo(tempDir.toFile().getAbsoluteFile()); + } + + @Test + void rejectsNonExistentDirectory() { + assertThatExceptionOfType(CommandLine.ParameterException.class) + .isThrownBy(() -> InputOptionsTests.this.commandLine.parseArgs( + "/nonexistent/path/that/does/not/exist")); + } + + @Test + void rejectsFile(@TempDir Path tempDir) throws IOException { + File file = tempDir.resolve("test.txt").toFile(); + file.createNewFile(); + assertThatExceptionOfType(CommandLine.ParameterException.class) + .isThrownBy(() -> InputOptionsTests.this.commandLine.parseArgs(file.getAbsolutePath())); + } + + } + + @Nested + class EncodingOption { + + @Test + void acceptsValidCharset() { + InputOptionsTests.this.commandLine.parseArgs("--encoding", "ISO-8859-1"); + assertThat(InputOptionsTests.this.command.options.encoding).isEqualTo(Charset.forName("ISO-8859-1")); + } + + @Test + void rejectsInvalidCharset() { + assertThatExceptionOfType(CommandLine.ParameterException.class) + .isThrownBy(() -> InputOptionsTests.this.commandLine.parseArgs("--encoding", "NOT-A-VALID-CHARSET")); + } + + } + + @Nested + class IncludesOption { + + @Test + void acceptsValidGlobPattern() { + InputOptionsTests.this.commandLine.parseArgs("--includes", "**/*.groovy"); + assertThat(InputOptionsTests.this.command.options.includes).containsExactly("**/*.groovy"); + } + + @Test + void acceptsMultiplePatternsSplitByComma() { + InputOptionsTests.this.commandLine.parseArgs("--includes", "**/*.java,**/*.kt"); + assertThat(InputOptionsTests.this.command.options.includes).containsExactly("**/*.java", "**/*.kt"); + } + + @Test + void rejectsInvalidGlobPattern() { + assertThatExceptionOfType(CommandLine.ParameterException.class) + .isThrownBy(() -> InputOptionsTests.this.commandLine.parseArgs("--includes", "[invalid")); + } + + } + + @Nested + class ExcludesOption { + + @Test + void acceptsValidGlobPattern() { + InputOptionsTests.this.commandLine.parseArgs("--excludes", "**/build/**"); + assertThat(InputOptionsTests.this.command.options.excludes).containsExactly("**/build/**"); + } + + @Test + void rejectsInvalidGlobPattern() { + assertThatExceptionOfType(CommandLine.ParameterException.class) + .isThrownBy(() -> InputOptionsTests.this.commandLine.parseArgs("--excludes", "[invalid")); + } + + } + + @Nested + class LineSeparatorOption { + + @Test + void acceptsCr() { + InputOptionsTests.this.commandLine.parseArgs("--line-separator", "CR"); + assertThat(InputOptionsTests.this.command.options.resolveLineSeparator()).isEqualTo("\r"); + } + + @Test + void rejectsInvalidValue() { + assertThatExceptionOfType(CommandLine.ParameterException.class) + .isThrownBy(() -> InputOptionsTests.this.commandLine.parseArgs("--line-separator", "INVALID")); + } + + } + + @PicocliManaged + @Command(name = "test") + private static class TestCommand implements Callable { + + @Mixin + InputOptions options; + + @Override + public Integer call() { + return 0; + } + + } + +} diff --git a/spring-javaformat-cli/src/test/java/io/spring/javaformat/cli/SpringJavaFormatCliApplicationTests.java b/spring-javaformat-cli/src/test/java/io/spring/javaformat/cli/SpringJavaFormatCliApplicationTests.java new file mode 100644 index 00000000..098d1b7a --- /dev/null +++ b/spring-javaformat-cli/src/test/java/io/spring/javaformat/cli/SpringJavaFormatCliApplicationTests.java @@ -0,0 +1,61 @@ +/* + * Copyright 2017-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.javaformat.cli; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import picocli.CommandLine; +import picocli.CommandLine.IFactory; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Smoke test — verifies the Spring application context loads successfully. + * + * @author Tim Sparg + */ +@SpringBootTest(webEnvironment = WebEnvironment.NONE) +class SpringJavaFormatCliApplicationTests { + + @Autowired + private SpringJavaFormatCommand command; + + @Autowired + private IFactory factory; + + @Test + void helpIsPrinted() { + StringWriter out = new StringWriter(); + int exitCode = new CommandLine(this.command, this.factory).setOut(new PrintWriter(out)).execute("--help"); + assertThat(exitCode).isZero(); + assertThat(out.toString()).contains("Usage: spring-javaformat").contains("apply").contains("check"); + } + + @Test + void versionIsPrinted() { + StringWriter out = new StringWriter(); + int exitCode = new CommandLine(this.command, this.factory).setOut(new PrintWriter(out)).execute("--version"); + assertThat(exitCode).isZero(); + assertThat(out.toString()).contains("spring-javaformat").containsPattern("\\S"); + } + +} diff --git a/spring-javaformat-cli/src/test/resources/fixtures/check/apache2-header-missing/Main.java b/spring-javaformat-cli/src/test/resources/fixtures/check/apache2-header-missing/Main.java new file mode 100644 index 00000000..4c57793b --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/check/apache2-header-missing/Main.java @@ -0,0 +1,5 @@ +package test; + +class Main { + +} diff --git a/spring-javaformat-cli/src/test/resources/fixtures/check/apache2-header-missing/package-info.java b/spring-javaformat-cli/src/test/resources/fixtures/check/apache2-header-missing/package-info.java new file mode 100644 index 00000000..e5de2528 --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/check/apache2-header-missing/package-info.java @@ -0,0 +1,4 @@ +/** + * Test package. + */ +package test; diff --git a/spring-javaformat-cli/src/test/resources/fixtures/check/combined/Main.java b/spring-javaformat-cli/src/test/resources/fixtures/check/combined/Main.java new file mode 100644 index 00000000..18782fe2 --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/check/combined/Main.java @@ -0,0 +1,6 @@ +import java.util.*; + +class Unformatted { + void method() { + } +} diff --git a/spring-javaformat-cli/src/test/resources/fixtures/check/custom-header/Main.java b/spring-javaformat-cli/src/test/resources/fixtures/check/custom-header/Main.java new file mode 100644 index 00000000..297a12d8 --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/check/custom-header/Main.java @@ -0,0 +1,7 @@ +// My custom header. + +package test; + +class Main { + +} diff --git a/spring-javaformat-cli/src/test/resources/fixtures/check/custom-header/header.txt b/spring-javaformat-cli/src/test/resources/fixtures/check/custom-header/header.txt new file mode 100644 index 00000000..ec8bf038 --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/check/custom-header/header.txt @@ -0,0 +1 @@ +// My custom header. diff --git a/spring-javaformat-cli/src/test/resources/fixtures/check/custom-header/package-info.java b/spring-javaformat-cli/src/test/resources/fixtures/check/custom-header/package-info.java new file mode 100644 index 00000000..f1c8ced0 --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/check/custom-header/package-info.java @@ -0,0 +1,6 @@ +// My custom header. + +/** + * Test package. + */ +package test; diff --git a/spring-javaformat-cli/src/test/resources/fixtures/check/excludes-filter/WithStarImport.java b/spring-javaformat-cli/src/test/resources/fixtures/check/excludes-filter/WithStarImport.java new file mode 100644 index 00000000..50c566f4 --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/check/excludes-filter/WithStarImport.java @@ -0,0 +1,5 @@ +import java.util.*; + +class WithStarImport { + +} diff --git a/spring-javaformat-cli/src/test/resources/fixtures/check/excludes-filter/excluded/ExcludedWithStarImport.java b/spring-javaformat-cli/src/test/resources/fixtures/check/excludes-filter/excluded/ExcludedWithStarImport.java new file mode 100644 index 00000000..445fdb6c --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/check/excludes-filter/excluded/ExcludedWithStarImport.java @@ -0,0 +1,5 @@ +import java.util.*; + +class ExcludedWithStarImport { + +} diff --git a/spring-javaformat-cli/src/test/resources/fixtures/check/includes-filter/WithStarImport.java b/spring-javaformat-cli/src/test/resources/fixtures/check/includes-filter/WithStarImport.java new file mode 100644 index 00000000..50c566f4 --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/check/includes-filter/WithStarImport.java @@ -0,0 +1,5 @@ +import java.util.*; + +class WithStarImport { + +} diff --git a/spring-javaformat-cli/src/test/resources/fixtures/check/includes-filter/subdir/ExcludedFromIncludes.java b/spring-javaformat-cli/src/test/resources/fixtures/check/includes-filter/subdir/ExcludedFromIncludes.java new file mode 100644 index 00000000..8c58a237 --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/check/includes-filter/subdir/ExcludedFromIncludes.java @@ -0,0 +1,5 @@ +import java.util.*; + +class ExcludedFromIncludes { + +} diff --git a/spring-javaformat-cli/src/test/resources/fixtures/check/no-header-none/Main.java b/spring-javaformat-cli/src/test/resources/fixtures/check/no-header-none/Main.java new file mode 100644 index 00000000..4c57793b --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/check/no-header-none/Main.java @@ -0,0 +1,5 @@ +package test; + +class Main { + +} diff --git a/spring-javaformat-cli/src/test/resources/fixtures/check/no-header-none/package-info.java b/spring-javaformat-cli/src/test/resources/fixtures/check/no-header-none/package-info.java new file mode 100644 index 00000000..e5de2528 --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/check/no-header-none/package-info.java @@ -0,0 +1,4 @@ +/** + * Test package. + */ +package test; diff --git a/spring-javaformat-cli/src/test/resources/fixtures/check/non-spring-import-order/Main.java b/spring-javaformat-cli/src/test/resources/fixtures/check/non-spring-import-order/Main.java new file mode 100644 index 00000000..b8498119 --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/check/non-spring-import-order/Main.java @@ -0,0 +1,17 @@ +package test; + +import java.util.List; + +import com.example.Foo; + +import org.springframework.Bar; + +class Main { + + List list; + + Foo foo; + + Bar bar; + +} diff --git a/spring-javaformat-cli/src/test/resources/fixtures/check/non-spring-import-order/package-info.java b/spring-javaformat-cli/src/test/resources/fixtures/check/non-spring-import-order/package-info.java new file mode 100644 index 00000000..e5de2528 --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/check/non-spring-import-order/package-info.java @@ -0,0 +1,4 @@ +/** + * Test package. + */ +package test; diff --git a/spring-javaformat-cli/src/test/resources/fixtures/check/spring-import-order/Main.java b/spring-javaformat-cli/src/test/resources/fixtures/check/spring-import-order/Main.java new file mode 100644 index 00000000..b8498119 --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/check/spring-import-order/Main.java @@ -0,0 +1,17 @@ +package test; + +import java.util.List; + +import com.example.Foo; + +import org.springframework.Bar; + +class Main { + + List list; + + Foo foo; + + Bar bar; + +} diff --git a/spring-javaformat-cli/src/test/resources/fixtures/check/spring-import-order/package-info.java b/spring-javaformat-cli/src/test/resources/fixtures/check/spring-import-order/package-info.java new file mode 100644 index 00000000..e5de2528 --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/check/spring-import-order/package-info.java @@ -0,0 +1,4 @@ +/** + * Test package. + */ +package test; diff --git a/spring-javaformat-cli/src/test/resources/fixtures/check/star-import/WithStarImport.java b/spring-javaformat-cli/src/test/resources/fixtures/check/star-import/WithStarImport.java new file mode 100644 index 00000000..50c566f4 --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/check/star-import/WithStarImport.java @@ -0,0 +1,5 @@ +import java.util.*; + +class WithStarImport { + +} diff --git a/spring-javaformat-cli/src/test/resources/fixtures/check/static-import-excluded/Main.java b/spring-javaformat-cli/src/test/resources/fixtures/check/static-import-excluded/Main.java new file mode 100644 index 00000000..5f4c9af9 --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/check/static-import-excluded/Main.java @@ -0,0 +1,11 @@ +package test; + +import java.util.List; + +import static java.util.Collections.emptyList; + +class Main { + + List items = emptyList(); + +} diff --git a/spring-javaformat-cli/src/test/resources/fixtures/check/static-import-excluded/package-info.java b/spring-javaformat-cli/src/test/resources/fixtures/check/static-import-excluded/package-info.java new file mode 100644 index 00000000..e5de2528 --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/check/static-import-excluded/package-info.java @@ -0,0 +1,4 @@ +/** + * Test package. + */ +package test; diff --git a/spring-javaformat-cli/src/test/resources/fixtures/check/static-import-violation/Main.java b/spring-javaformat-cli/src/test/resources/fixtures/check/static-import-violation/Main.java new file mode 100644 index 00000000..5f4c9af9 --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/check/static-import-violation/Main.java @@ -0,0 +1,11 @@ +package test; + +import java.util.List; + +import static java.util.Collections.emptyList; + +class Main { + + List items = emptyList(); + +} diff --git a/spring-javaformat-cli/src/test/resources/fixtures/check/static-import-violation/package-info.java b/spring-javaformat-cli/src/test/resources/fixtures/check/static-import-violation/package-info.java new file mode 100644 index 00000000..e5de2528 --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/check/static-import-violation/package-info.java @@ -0,0 +1,4 @@ +/** + * Test package. + */ +package test; diff --git a/spring-javaformat-cli/src/test/resources/fixtures/format/default/Main.java b/spring-javaformat-cli/src/test/resources/fixtures/format/default/Main.java new file mode 100644 index 00000000..273a084c --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/format/default/Main.java @@ -0,0 +1,4 @@ +class Unformatted { + void method() { + } +} diff --git a/spring-javaformat-cli/src/test/resources/fixtures/format/default/excluded/Excluded.java b/spring-javaformat-cli/src/test/resources/fixtures/format/default/excluded/Excluded.java new file mode 100644 index 00000000..273a084c --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/format/default/excluded/Excluded.java @@ -0,0 +1,4 @@ +class Unformatted { + void method() { + } +} diff --git a/spring-javaformat-cli/src/test/resources/fixtures/format/default/notes.txt b/spring-javaformat-cli/src/test/resources/fixtures/format/default/notes.txt new file mode 100644 index 00000000..cf88e040 --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/format/default/notes.txt @@ -0,0 +1 @@ +some notes diff --git a/spring-javaformat-cli/src/test/resources/fixtures/format/latin1/Main.java b/spring-javaformat-cli/src/test/resources/fixtures/format/latin1/Main.java new file mode 100644 index 00000000..493cd23b --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/format/latin1/Main.java @@ -0,0 +1,5 @@ +// é comment +class Unformatted { + void method() { + } +} diff --git a/spring-javaformat-cli/src/test/resources/fixtures/format/latin1/excluded/Excluded.java b/spring-javaformat-cli/src/test/resources/fixtures/format/latin1/excluded/Excluded.java new file mode 100644 index 00000000..273a084c --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/format/latin1/excluded/Excluded.java @@ -0,0 +1,4 @@ +class Unformatted { + void method() { + } +} diff --git a/spring-javaformat-cli/src/test/resources/fixtures/format/latin1/notes.txt b/spring-javaformat-cli/src/test/resources/fixtures/format/latin1/notes.txt new file mode 100644 index 00000000..cf88e040 --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/format/latin1/notes.txt @@ -0,0 +1 @@ +some notes diff --git a/spring-javaformat-cli/src/test/resources/fixtures/format/spaces-config/.springjavaformatconfig b/spring-javaformat-cli/src/test/resources/fixtures/format/spaces-config/.springjavaformatconfig new file mode 100644 index 00000000..881903b2 --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/format/spaces-config/.springjavaformatconfig @@ -0,0 +1 @@ +indentation-style=spaces diff --git a/spring-javaformat-cli/src/test/resources/fixtures/format/spaces-config/Main.java b/spring-javaformat-cli/src/test/resources/fixtures/format/spaces-config/Main.java new file mode 100644 index 00000000..8548401d --- /dev/null +++ b/spring-javaformat-cli/src/test/resources/fixtures/format/spaces-config/Main.java @@ -0,0 +1,4 @@ +class Test { +void method() { +} +}