diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index a5ca735442..0000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,385 +0,0 @@ -version: 2.1 -orbs: - win: circleci/windows@2.4.1 - -anchors: - env_gradle: &env_gradle - environment: - # we're only allowed to use 2 vCPUs - GRADLE_OPTS: "-Dorg.gradle.workers.max=2" - docker: - - image: cimg/openjdk:11.0 - env_gradle_large: &env_gradle_large - << : *env_gradle - resource_class: large # https://circleci.com/docs/2.0/configuration-reference/#resource_class - environment: - GRADLE_OPTS: "-Dorg.gradle.workers.max=4" - - restore_cache_wrapper: &restore_cache_wrapper - restore_cache: - key: gradle-wrapper2-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }} - restore_cache_deps: &restore_cache_deps - restore_cache: - keys: - - gradle-deps3-{{ checksum "build.gradle" }}-{{ checksum "gradle.properties" }} - - gradle-deps3- - set_git_origin_to_https: &set_git_origin_to_https - run: - name: set git origin to https - command: git remote set-url --push origin https://github.com/diffplug/spotless - - test_nomaven: &test_nomaven - steps: - - checkout - - *restore_cache_wrapper - - *restore_cache_deps - - run: - name: gradlew check -x spotlessCheck - command: export SPOTLESS_EXCLUDE_MAVEN=true && ./gradlew check -x spotlessCheck --build-cache - - store_test_results: - path: testlib/build/test-results/test - - store_test_results: - path: lib-extra/build/test-results/test - - store_test_results: - path: plugin-gradle/build/test-results/test - - store_artifacts: - path: lib/build/spotbugs - - store_artifacts: - path: lib-extra/build/spotbugs - - store_artifacts: - path: testlib/build/spotbugs - - store_artifacts: - path: plugin-gradle/build/spotbugs - -jobs: - # gradlew spotlessCheck assemble testClasses - assemble_testClasses: - <<: *env_gradle_large - steps: - - checkout - - *restore_cache_wrapper - - *restore_cache_deps - - run: - name: gradlew spotlessCheck assemble testClasses - command: ./gradlew spotlessCheck assemble testClasses --build-cache - - save_cache: - paths: - - ~/.gradle/wrapper - key: gradle-wrapper2-{{ checksum "gradle/wrapper/gradle-wrapper.properties" }} - - save_cache: - paths: - - ~/.gradle/caches - - ~/.m2 - key: gradle-deps3-{{ checksum "build.gradle" }}-{{ checksum "gradle.properties" }} - test_nomaven_11: - # latest LTS version - <<: *env_gradle_large - docker: - - image: cimg/openjdk:11.0 - <<: *test_nomaven - test_nomaven_17: - # latest JDK - <<: *env_gradle_large - docker: - - image: cimg/openjdk:17.0 - <<: *test_nomaven - test_justmaven_11: - << : *env_gradle - steps: - - checkout - - *restore_cache_wrapper - - *restore_cache_deps - - run: - name: gradlew :plugin-maven:check - command: ./gradlew :plugin-maven:check --build-cache - - store_test_results: - path: plugin-maven/build/test-results/test - test_npm_8: - << : *env_gradle - environment: - # java doesn't play nice with containers, it tries to hog the entire machine - # https://circleci.com/blog/how-to-handle-java-oom-errors/ - # try the experimental JVM option - _JAVA_OPTIONS: "-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap" - docker: - - image: cimg/openjdk:8.0-node - steps: - - checkout - - *restore_cache_wrapper - - *restore_cache_deps - - run: - name: gradlew testNpm - command: export SPOTLESS_EXCLUDE_MAVEN=true && ./gradlew testNpm --build-cache - - store_test_results: - path: testlib/build/test-results/testNpm - - store_test_results: - path: plugin-maven/build/test-results/testNpm - - store_test_results: - path: plugin-gradle/build/test-results/testNpm - - run: - name: gradlew test - command: export SPOTLESS_EXCLUDE_MAVEN=true && ./gradlew test --build-cache - - store_test_results: - path: testlib/build/test-results/test - - store_test_results: - path: lib-extra/build/test-results/test - - store_test_results: - path: plugin-gradle/build/test-results/test - test_windows: - executor: - name: win/default - shell: cmd.exe - steps: - - checkout - - run: - name: gradlew test - command: gradlew test --build-cache -PSPOTLESS_EXCLUDE_MAVEN=true - - store_test_results: - path: testlib/build/test-results/test - - store_test_results: - path: lib-extra/build/test-results/test - - store_test_results: - path: plugin-gradle/build/test-results/test - - run: - name: gradlew testNpm - command: gradlew testNpm --build-cache -PSPOTLESS_EXCLUDE_MAVEN=true - - store_test_results: - path: testlib/build/test-results/testNpm - - store_test_results: - path: plugin-gradle/build/test-results/testNpm - changelog_print: - << : *env_gradle - steps: - - checkout - - *restore_cache_wrapper - - *restore_cache_deps - - run: - name: gradlew changelogPrint - command: ./gradlew changelogPrint - do_release_all: - << : *env_gradle - steps: - - checkout - - *restore_cache_wrapper - - *restore_cache_deps - - *set_git_origin_to_https - - run: - name: gradlew :changelogPush - command: ./gradlew :changelogPush -Prelease=true --stacktrace --warning-mode all - - run: - name: gradlew :plugin-gradle:changelogPush - command: ./gradlew :plugin-gradle:changelogPush -Prelease=true -Pgradle.publish.key=${gradle_key} -Pgradle.publish.secret=${gradle_secret} --stacktrace --warning-mode all - - run: - name: gradlew :plugin-maven:changelogPush - command: ./gradlew :plugin-maven:changelogPush -Prelease=true --stacktrace --warning-mode all - do_release_lib: - << : *env_gradle - steps: - - checkout - - *restore_cache_wrapper - - *restore_cache_deps - - *set_git_origin_to_https - - run: - name: gradlew :changelogPush - command: ./gradlew :changelogPush -Prelease=true --stacktrace --warning-mode all - do_release_plugin_gradle: - << : *env_gradle - steps: - - checkout - - *restore_cache_wrapper - - *restore_cache_deps - - *set_git_origin_to_https - - run: - name: gradlew :plugin-gradle:changelogPush - command: ./gradlew :plugin-gradle:changelogPush -Prelease=true -Pgradle.publish.key=${gradle_key} -Pgradle.publish.secret=${gradle_secret} --stacktrace - do_release_plugin_maven: - << : *env_gradle - steps: - - checkout - - *restore_cache_wrapper - - *restore_cache_deps - - *set_git_origin_to_https - - run: - name: gradlew :plugin-maven:changelogPush - command: ./gradlew :plugin-maven:changelogPush -Prelease=true --stacktrace - ext_changelog_print: - << : *env_gradle - steps: - - checkout - - *restore_cache_wrapper - - *restore_cache_deps - - *set_git_origin_to_https - - run: - name: gradlew -Pcom.diffplug.spotless.include.ext.nop2=true changelogPrint - command: ./gradlew -Pcom.diffplug.spotless.include.ext.nop2=true changelogPrint - ext_do_release_base: - << : *env_gradle - steps: - - checkout - - *restore_cache_wrapper - - *restore_cache_deps - - *set_git_origin_to_https - - run: - name: gradlew :eclipse-base:changelogPush - command: ./gradlew -Pcom.diffplug.spotless.include.ext.nop2=true :eclipse-base:changelogPush -Prelease=true --stacktrace - ext_do_release_jdt: - << : *env_gradle - steps: - - checkout - - *restore_cache_wrapper - - *restore_cache_deps - - *set_git_origin_to_https - - run: - name: gradlew :eclipse-jdt:changelogPush - command: ./gradlew -Pcom.diffplug.spotless.include.ext.nop2=true :eclipse-jdt:changelogPush -Prelease=true --stacktrace - ext_do_release_cdt: - << : *env_gradle - steps: - - checkout - - *restore_cache_wrapper - - *restore_cache_deps - - *set_git_origin_to_https - - run: - name: gradlew :eclipse-cdt:changelogPush - command: ./gradlew -Pcom.diffplug.spotless.include.ext.cdt=true :eclipse-cdt:changelogPush -Prelease=true --stacktrace - ext_do_release_groovy: - << : *env_gradle - steps: - - checkout - - *restore_cache_wrapper - - *restore_cache_deps - - *set_git_origin_to_https - - run: - name: gradlew :eclipse-groovy:changelogPush - command: ./gradlew -Pcom.diffplug.spotless.include.ext.groovy=true :eclipse-groovy:changelogPush -Prelease=true --stacktrace - ext_do_release_wtp: - << : *env_gradle - steps: - - checkout - - *restore_cache_wrapper - - *restore_cache_deps - - *set_git_origin_to_https - - run: - name: gradlew :eclipse-wtp:changelogPush - command: ./gradlew -Pcom.diffplug.spotless.include.ext.wtp=true :eclipse-wtp:changelogPush -Prelease=true --stacktrace - -workflows: - version: 2 - assemble_and_test: - jobs: - - test_windows - - assemble_testClasses - - test_justmaven_11: - requires: - - assemble_testClasses - - test_nomaven_11: - requires: - - assemble_testClasses - - test_nomaven_17: - requires: - - assemble_testClasses - - test_npm_8: - requires: - - assemble_testClasses - deploy: - jobs: - - changelog_print: - filters: - branches: - only: main - - release_all: - type: approval - requires: - - changelog_print - - do_release_all: - requires: - - release_all - context: - - SonatypeDeploy - - release_plugin_gradle: - type: approval - requires: - - changelog_print - - do_release_plugin_gradle: - requires: - - release_plugin_gradle - context: - - SonatypeDeploy - - release_plugin_maven: - type: approval - requires: - - changelog_print - - do_release_plugin_maven: - requires: - - release_plugin_maven - context: - - SonatypeDeploy - - release_lib: - type: approval - requires: - - changelog_print - - do_release_lib: - requires: - - release_lib - context: - - SonatypeDeploy - ext_deploy: - jobs: - - ext_changelog_print: - filters: - branches: - only: main - - ext_release_base: - type: approval - requires: - - ext_changelog_print - - ext_do_release_base: - requires: - - ext_release_base - context: - - SonatypeDeploy - - ext_release_jdt: - type: approval - requires: - - ext_changelog_print - - ext_do_release_jdt: - requires: - - ext_release_jdt - context: - - SonatypeDeploy - - ext_release_cdt: - type: approval - requires: - - ext_changelog_print - - ext_do_release_cdt: - filters: - branches: - only: main - requires: - - ext_release_cdt - context: - - SonatypeDeploy - - ext_release_groovy: - type: approval - requires: - - ext_changelog_print - - ext_do_release_groovy: - filters: - branches: - only: main - requires: - - ext_release_groovy - context: - - SonatypeDeploy - - ext_release_wtp: - type: approval - requires: - - ext_changelog_print - - ext_do_release_wtp: - filters: - branches: - only: main - requires: - - ext_release_wtp - context: - - SonatypeDeploy diff --git a/.editorconfig b/.editorconfig index 6712fd66cc..9fe2fbc6c6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,13 +11,28 @@ charset = utf-8 [*.md] indent_style = space indent_size = 2 +trim_trailing_whitespace = false [*.java] # Doc: https://youtrack.jetbrains.com/issue/IDEA-170643#focus=streamItem-27-3708697.0-0 -ij_java_imports_layout = java.**,|,javax.**,|,org.**,|,com.**,|,com.diffplug.**,|,* +ij_java_imports_layout = $*,|,java.**,|,javax.**,|,org.**,|,com.**,|,com.diffplug.**,|,* ij_java_use_single_class_imports = true ij_java_class_count_to_use_import_on_demand = 999 ij_java_names_count_to_use_import_on_demand = 999 [*.xml.mustache] indent_style = space + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 + +# Prevent unexpected automatic indentation when crafting test-cases +[/testlib/src/main/resources/**] +charset = unset +end_of_line = unset +insert_final_newline = unset +trim_trailing_whitespace = unset +indent_style = unset +indent_size = unset +ij_formatter_enabled = false diff --git a/.gitattributes b/.gitattributes index e6141d8894..dc4cb6b0bd 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ * text eol=lf +*.bat eol=crlf *.png binary *.jar binary diff --git a/.github/workflows/changelog-print.yml b/.github/workflows/changelog-print.yml new file mode 100644 index 0000000000..01bbb88afa --- /dev/null +++ b/.github/workflows/changelog-print.yml @@ -0,0 +1,22 @@ +name: changelogPrint + +on: + push: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + name: changelogPrint + steps: + - uses: actions/checkout@v4 + - name: jdk 11 + uses: actions/setup-java@v4 + with: + java-version: 11 + distribution: 'temurin' + - name: gradle caching + uses: gradle/gradle-build-action@v2 + with: + gradle-home-cache-cleanup: true + - run: ./gradlew changelogPrint diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..e6c9d91b8c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,88 @@ +# BUILDCACHE_USER +# BUILDCACHE_PASS +# - rw access to buildcache.diffplug.com + +on: + pull_request: + push: + branches: [main] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + sanityCheck: + name: spotlessCheck assemble testClasses + runs-on: ubuntu-latest + env: + buildcacheuser: ${{ secrets.BUILDCACHE_USER }} + buildcachepass: ${{ secrets.BUILDCACHE_PASS }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Install JDK 11 + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: 11 + - name: gradle caching + uses: gradle/gradle-build-action@v2 + with: + gradle-home-cache-cleanup: true + - name: spotlessCheck + run: ./gradlew spotlessCheck + - name: assemble testClasses + run: ./gradlew assemble testClasses + build: + needs: sanityCheck + strategy: + fail-fast: false + matrix: + kind: [maven, gradle] + # Test on the latest Java version once Gradle & Maven support it. + jre: [11, 17, 21] + os: [ubuntu-latest] + include: + # test windows at the diagonals of the above matrix + - kind: maven + jre: 11 + os: windows-latest + - kind: gradle + jre: 17 + os: windows-latest + # npm on linux only (crazy slow on windows) + - kind: npm + jre: 11 + os: ubuntu-latest + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install JDK ${{ matrix.distribution }} ${{ matrix.java_version }} + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: ${{ matrix.jre }} + - name: gradle caching + uses: gradle/gradle-build-action@v2 + with: + gradle-home-cache-cleanup: true + - name: build (maven-only) + if: matrix.kind == 'maven' + run: ./gradlew :plugin-maven:build -x spotlessCheck + - name: build (everything-but-maven) + if: matrix.kind == 'gradle' + run: ./gradlew build -x spotlessCheck -PSPOTLESS_EXCLUDE_MAVEN=true + - name: test npm + if: matrix.kind == 'npm' + run: ./gradlew testNpm + - name: junit result + uses: mikepenz/action-junit-report@v4 + if: always() # always run even if the previous step fails + with: + check_name: JUnit ${{ matrix.kind }} ${{ matrix.jre }} ${{ matrix.os }} + report_paths: '*/build/test-results/*/TEST-*.xml' + check_retries: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index b2f2a259c3..7fb9daff0d 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000000..e899a2d270 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,68 @@ +# GH_TOKEN +# NEXUS_USER +# NEXUS_PASS64 (base64 NOTE: `base64` and `openssl base64` failed, had to use Java +# byte[] data = "{{password}}".getBytes(StandardCharsets.UTF_8); +# String encoded = new String(Base64.getEncoder().encode(data), StandardCharsets.UTF_8); +# System.out.println(encoded); +# GPG_PASSPHRASE +# GPG_KEY64 (base64) +# gpg --export-secret-keys --armor KEY_ID | openssl base64 | pbcopy +# GRADLE_KEY +# GRADLE_SECRET + +name: deploy +on: + workflow_dispatch: + inputs: + to_publish: + description: 'What to publish' + required: true + default: 'all' + type: choice + options: + - plugin-gradle + - plugin-maven + - all + - lib + +jobs: + build: + runs-on: ubuntu-latest + name: deploy + env: + gh_token: ${{ secrets.GH_TOKEN }} + ORG_GRADLE_PROJECT_nexus_user: ${{ secrets.NEXUS_USER }} + ORG_GRADLE_PROJECT_nexus_pass64: ${{ secrets.NEXUS_PASS64 }} + ORG_GRADLE_PROJECT_gpg_passphrase: ${{ secrets.GPG_PASSPHRASE }} + ORG_GRADLE_PROJECT_gpg_key64: ${{ secrets.GPG_KEY64 }} + steps: + - uses: actions/checkout@v4 + - name: jdk 11 + uses: actions/setup-java@v4 + with: + java-version: 11 + distribution: 'temurin' + - name: gradle caching + uses: gradle/gradle-build-action@v2 + with: + gradle-home-cache-cleanup: true + - name: git fetch origin main + run: git fetch origin main + - name: publish all + if: "${{ github.event.inputs.to_publish == 'all' }}" + run: | + ./gradlew :changelogPush -Prelease=true --stacktrace --warning-mode all --no-configuration-cache + ./gradlew :plugin-gradle:changelogPush -Prelease=true -Pgradle.publish.key=${{ secrets.GRADLE_KEY }} -Pgradle.publish.secret=${{ secrets.GRADLE_SECRET }} --stacktrace --warning-mode all --no-configuration-cache + ./gradlew :plugin-maven:changelogPush -Prelease=true --stacktrace --warning-mode all --no-configuration-cache + - name: publish just plugin-gradle + if: "${{ github.event.inputs.to_publish == 'plugin-gradle' }}" + run: | + ./gradlew :plugin-gradle:changelogPush -Prelease=true -Pgradle.publish.key=${{ secrets.GRADLE_KEY }} -Pgradle.publish.secret=${{ secrets.GRADLE_SECRET }} --stacktrace --warning-mode all --no-configuration-cache + - name: publish just plugin-maven + if: "${{ github.event.inputs.to_publish == 'plugin-maven' }}" + run: | + ./gradlew :plugin-maven:changelogPush -Prelease=true --stacktrace --warning-mode all --no-configuration-cache + - name: publish just lib + if: "${{ github.event.inputs.to_publish == 'lib' }}" + run: | + ./gradlew :changelogPush -Prelease=true --stacktrace --warning-mode all --no-configuration-cache diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index c80a7e5278..d8be29afda 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -1,5 +1,15 @@ name: "Validate Gradle Wrapper" -on: [push, pull_request] +on: + push: + paths: + - 'gradlew' + - 'gradlew.bat' + - 'gradle/wrapper/' + pull_request: + paths: + - 'gradlew' + - 'gradlew.bat' + - 'gradle/wrapper/' permissions: contents: read @@ -9,5 +19,5 @@ jobs: name: "Validation" runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: gradle/wrapper-validation-action@v1 diff --git a/.gitignore b/.gitignore index 1a95765d37..ab04bc7021 100644 --- a/.gitignore +++ b/.gitignore @@ -19,9 +19,6 @@ gradle-app.setting # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) !gradle-wrapper.jar -#Gradle-Lock files in _ext just serving as an input for lib-extra -_ext/*/gradle/dependency-locks/*.lockfile - ### Eclipse ### .metadata .gradle @@ -121,3 +118,9 @@ nbdist/ nbactions.xml nb-configuration.xml .nb-gradle/ + +# MacOS jenv +.java-version + +# VS Code +.vscode/ diff --git a/CHANGES.md b/CHANGES.md index 23c20dc957..adf6d109e7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,227 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] +## [2.45.0] - 2024-01-23 +### Added +* Support for `gofmt` ([#2001](https://github.com/diffplug/spotless/pull/2001)) +* Support for formatting Java Docs for the Palantir formatter ([#2009](https://github.com/diffplug/spotless/pull/2009)) + +## [2.44.0] - 2024-01-15 +### Added +* New static method to `DiffMessageFormatter` which allows to retrieve diffs with their line numbers ([#1960](https://github.com/diffplug/spotless/issues/1960)) +* Gradle - Support for formatting shell scripts via [shfmt](https://github.com/mvdan/sh). ([#1994](https://github.com/diffplug/spotless/pull/1994)) +### Fixed +* Fix empty files with biome >= 1.5.0 when formatting files that are in the ignore list of the biome configuration file. ([#1989](https://github.com/diffplug/spotless/pull/1989) fixes [#1987](https://github.com/diffplug/spotless/issues/1987)) +* Fix a regression in BufStep where the same arguments were being provided to every `buf` invocation. ([#1976](https://github.com/diffplug/spotless/issues/1976)) +### Changes +* Use palantir-java-format 2.39.0 on Java 21. ([#1948](https://github.com/diffplug/spotless/pull/1948)) +* Bump default `ktlint` version to latest `1.0.1` -> `1.1.1`. ([#1973](https://github.com/diffplug/spotless/pull/1973)) +* Bump default `googleJavaFormat` version to latest `1.18.1` -> `1.19.2`. ([#1971](https://github.com/diffplug/spotless/pull/1971)) +* Bump default `diktat` version to latest `1.2.5` -> `2.0.0`. ([#1972](https://github.com/diffplug/spotless/pull/1972)) + +## [2.43.1] - 2023-12-04 +### Fixed +* Eclipse-based steps which contained any jars with a `+` in their path were broken, now fixed. ([#1860](https://github.com/diffplug/spotless/issues/1860#issuecomment-1826113332)) +### Changes +* Bump default `palantir-java-format` version to latest `2.28.0` -> `2.38.0` on Java 21. ([#1920](https://github.com/diffplug/spotless/pull/1920)) +* Bump default `googleJavaFormat` version to latest `1.17.0` -> `1.18.1`. ([#1920](https://github.com/diffplug/spotless/pull/1920)) +* Bump default `ktfmt` version to latest `0.44` -> `0.46`. ([#1927](https://github.com/diffplug/spotless/pull/1927)) +* Bump default `eclipse` version to latest `4.27` -> `4.29`. ([#1939](https://github.com/diffplug/spotless/pull/1939)) +* Bump default `greclipse` version to latest `4.28` -> `4.29`. ([#1939](https://github.com/diffplug/spotless/pull/1939)) +* Bump default `cdt` version to latest `11.1` -> `11.3`. ([#1939](https://github.com/diffplug/spotless/pull/1939)) + +## [2.43.0] - 2023-11-27 +### Added +* Support custom rule sets for Ktlint. ([#1896](https://github.com/diffplug/spotless/pull/1896)) +### Fixed +* Fix Eclipse JDT on some settings files. ([#1864](https://github.com/diffplug/spotless/pull/1864) fixes [#1638](https://github.com/diffplug/spotless/issues/1638)) +### Changes +* Bump default `ktlint` version to latest `1.0.0` -> `1.0.1`. ([#1855](https://github.com/diffplug/spotless/pull/1855)) +* Add a Step to remove semicolons from Groovy files. ([#1881](https://github.com/diffplug/spotless/pull/1881)) + +## [2.42.0] - 2023-09-28 +### Added +* Support for biome. The Rome project [was renamed to Biome](https://biomejs.dev/blog/annoucing-biome/). + The configuration is still the same, but you should switch to the new `biome` tag / function and adjust + the version accordingly. ([#1804](https://github.com/diffplug/spotless/issues/1804)). +* Support for `google-java-format`'s `skip-javadoc-formatting` option. ([#1793](https://github.com/diffplug/spotless/pull/1793)) +* Support configuration of mirrors for P2 repositories in Maven DSL ([#1697](https://github.com/diffplug/spotless/issues/1697)). +* New line endings mode `GIT_ATTRIBUTES_FAST_ALLSAME`. ([#1838](https://github.com/diffplug/spotless/pull/1838)) +### Fixed +* Fix support for plugins when using Prettier version `3.0.0` and newer. ([#1802](https://github.com/diffplug/spotless/pull/1802)) +* Fix configuration cache issue around `external process started '/usr/bin/git --version'`. ([#1806](https://github.com/diffplug/spotless/issues/1806)) +### Changes +* Bump default `flexmark` version to latest `0.64.0` -> `0.64.8`. ([#1801](https://github.com/diffplug/spotless/pull/1801)) +* Bump default `ktlint` version to latest `0.50.0` -> `1.0.0`. ([#1808](https://github.com/diffplug/spotless/pull/1808)) + +## [2.41.0] - 2023-08-29 +### Added +* Add a `jsonPatch` step to `json` formatter configurations. This allows patching of JSON documents using [JSON Patches](https://jsonpatch.com). ([#1753](https://github.com/diffplug/spotless/pull/1753)) +* Support GJF own import order. ([#1780](https://github.com/diffplug/spotless/pull/1780)) +### Fixed +* Use latest versions of popular style guides for `eslint` tests to fix failing `useEslintXoStandardRules` test. ([#1761](https://github.com/diffplug/spotless/pull/1761), [#1756](https://github.com/diffplug/spotless/issues/1756)) +* Add support for `prettier` version `3.0.0` and newer. ([#1760](https://github.com/diffplug/spotless/pull/1760), [#1751](https://github.com/diffplug/spotless/issues/1751)) +* Fix npm install calls when npm cache is not up-to-date. ([#1760](https://github.com/diffplug/spotless/pull/1760), [#1750](https://github.com/diffplug/spotless/issues/1750)) +### Changes +* Bump default `eslint` version to latest `8.31.0` -> `8.45.0` ([#1761](https://github.com/diffplug/spotless/pull/1761)) +* Bump default `prettier` version to latest (v2) `2.8.1` -> `2.8.8`. ([#1760](https://github.com/diffplug/spotless/pull/1760)) +* Bump default `greclipse` version to latest `4.27` -> `4.28`. ([#1775](https://github.com/diffplug/spotless/pull/1775)) + +## [2.40.0] - 2023-07-17 +### Added +* Added support for Protobuf formatting based on [Buf](https://buf.build/). (#1208) +* `enum OnMatch { INCLUDE, EXCLUDE }` so that `FormatterStep.filterByContent` can not only include based on the pattern but also exclude. ([#1749](https://github.com/diffplug/spotless/pull/1749)) +### Fixed +* Update documented default `semanticSort` to `false`. ([#1728](https://github.com/diffplug/spotless/pull/1728)) +### Changes +* Bump default `cleanthat` version to latest `2.13` -> `2.17`. ([#1734](https://github.com/diffplug/spotless/pull/1734)) +* Bump default `ktlint` version to latest `0.49.1` -> `0.50.0`. ([#1741](https://github.com/diffplug/spotless/issues/1741)) + * Dropped support for `ktlint 0.47.x` following our policy of supporting two breaking changes at a time. + * Dropped support for deprecated `useExperimental` parameter in favor of the `ktlint_experimental` property. + +## [2.39.0] - 2023-05-24 +### Added +* `Jvm.Support` now accepts `-SNAPSHOT` versions, treated as the non`-SNAPSHOT`. ([#1583](https://github.com/diffplug/spotless/issues/1583)) +* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. ([#1663](https://github.com/diffplug/spotless/pull/1663)) +* Add semantics-aware Java import ordering (i.e. sort by package, then class, then member). ([#522](https://github.com/diffplug/spotless/issues/522)) +### Fixed +* Fixed a regression which changed the import sorting order in `googleJavaFormat` introduced in `2.38.0`. ([#1680](https://github.com/diffplug/spotless/pull/1680)) +* Equo-based formatters now work on platforms unsupported by Eclipse such as PowerPC (fixes [durian-swt#20](https://github.com/diffplug/durian-swt/issues/20)) +* When P2 download fails, indicate the responsible formatter. ([#1698](https://github.com/diffplug/spotless/issues/1698)) +### Changes +* Equo-based formatters now download metadata to `~/.m2/repository/dev/equo/p2-data` rather than `~/.equo`, and for CI machines without a home directory the p2 data goes to `$GRADLE_USER_HOME/caches/p2-data`. ([#1714](https://github.com/diffplug/spotless/pull/1714)) +* Bump default `googleJavaFormat` version to latest `1.16.0` -> `1.17.0`. ([#1710](https://github.com/diffplug/spotless/pull/1710)) +* Bump default `ktfmt` version to latest `0.43` -> `0.44`. ([#1691](https://github.com/diffplug/spotless/pull/1691)) +* Bump default `ktlint` version to latest `0.48.2` -> `0.49.1`. ([#1696](https://github.com/diffplug/spotless/issues/1696)) + * Dropped support for `ktlint 0.46.x` following our policy of supporting two breaking changes at a time. +* Bump default `sortpom` version to latest `3.0.0` -> `3.2.1`. ([#1675](https://github.com/diffplug/spotless/pull/1675)) + +## [2.38.0] - 2023-04-06 +### Added +* Support configuration of mirrors for P2 repositories in `EquoBasedStepBuilder` ([#1629](https://github.com/diffplug/spotless/issues/1629)). +* The `style` option in Palantir Java Format ([#1654](https://github.com/diffplug/spotless/pull/1654)). +* Added formatter for Gherkin feature files ([#1649](https://github.com/diffplug/spotless/issues/1649)). +### Changes +* **POTENTIALLY BREAKING** Converted `googleJavaFormat` to a compile-only dependency and drop support for versions < `1.8`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) +* Bump default `cleanthat` version to latest `2.6` -> `2.13`. ([#1589](https://github.com/diffplug/spotless/pull/1589) and [#1661](https://github.com/diffplug/spotless/pull/1661)) +* Bump default `diktat` version `1.2.4.2` -> `1.2.5`. ([#1631](https://github.com/diffplug/spotless/pull/1631)) +* Bump default `flexmark` version `0.62.2` -> `0.64.0`. ([#1302](https://github.com/diffplug/spotless/pull/1302)) +* Bump default `googleJavaFormat` version `1.15.0` -> `1.16.0`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) +* Bump default `scalafmt` version `3.7.1` -> `3.7.3`. ([#1584](https://github.com/diffplug/spotless/pull/1584)) +* Bump default Eclipse formatters for the 2023-03 release. ([#1662](https://github.com/diffplug/spotless/pull/1662)) + * JDT and GrEclipse `4.26` -> `4.27` + * Improve GrEclipse error reporting. ([#1660](https://github.com/diffplug/spotless/pull/1660)) + * CDT `11.0` -> `11.1` + +## [2.37.0] - 2023-03-13 +### Added +* You can now put the filename into a license header template with `$FILE`. ([#1605](https://github.com/diffplug/spotless/pull/1605) fixes [#1147](https://github.com/diffplug/spotless/issues/1147)) +### Changes +* We are now opting in to Gradle's new stable configuration cache. ([#1591](https://github.com/diffplug/spotless/pull/1591)) +* Adopt [Equo Solstice OSGi and p2 shim](https://github.com/equodev/equo-ide/tree/main/solstice) to update all Eclipse-based plugins. ([#1524](https://github.com/diffplug/spotless/pull/1524)) + * Eclipse JDT now supports `4.9` through `4.26`. Also we now recommend dropping the last `.0`, e.g. `4.26` instead of `4.26.0`, you'll get warnings to help you switch. + * Eclipse Groovy now supports `4.18` through `4.26`. Also we now recommend dropping the last `.0`, e.g. `4.26` instead of `4.26.0`, you'll get warnings to help you switch. + * Eclipse CDT now supports `10.6` through `11.0`. + * Eclipse WTP is still WIP at [#1622](https://github.com/diffplug/spotless/pull/1622). + +## [2.36.0] - 2023-02-27 +### Added +* `gradlew equoIde` opens a repeatable clean Spotless dev environment. ([#1523](https://github.com/diffplug/spotless/pull/1523)) +* `cleanthat` added `includeDraft` option, to include draft mutators from composite mutators. ([#1574](https://github.com/diffplug/spotless/pull/1574)) +* `npm`-based formatters now support caching of `node_modules` directory ([#1590](https://github.com/diffplug/spotless/pull/1590)) +### Fixed +* `JacksonJsonFormatterFunc` handles json files with an Array as root. ([#1585](https://github.com/diffplug/spotless/pull/1585)) +### Changes +* Bump default `cleanthat` version to latest `2.1` -> `2.6` ([#1569](https://github.com/diffplug/spotless/pull/1569) and [#1574](https://github.com/diffplug/spotless/pull/1574)) +* Reduce logging-noise created by `npm`-based formatters ([#1590](https://github.com/diffplug/spotless/pull/1590) fixes [#1582](https://github.com/diffplug/spotless/issues/1582)) + +## [2.35.0] - 2023-02-10 +### Added +* CleanThat Java Refactorer. ([#1560](https://github.com/diffplug/spotless/pull/1560)) +* Introduce `LazyArgLogger` to allow for lazy evaluation of log messages in slf4j logging. ([#1565](https://github.com/diffplug/spotless/pull/1565)) +### Fixed +* Allow multiple instances of the same npm-based formatter to be used by separating their `node_modules` directories. ([#1565](https://github.com/diffplug/spotless/pull/1565)) +* `ktfmt` default style uses correct continuation indent. ([#1562](https://github.com/diffplug/spotless/pull/1562)) +### Changes +* Bump default `ktfmt` version to latest `0.42` -> `0.43` ([#1561](https://github.com/diffplug/spotless/pull/1561)) +* Bump default `jackson` version to latest `2.14.1` -> `2.14.2` ([#1536](https://github.com/diffplug/spotless/pull/1536)) + +## [2.34.1] - 2023-02-05 +### Changes +* **POTENTIALLY BREAKING** Bump bytecode from Java 8 to 11 ([#1530](https://github.com/diffplug/spotless/pull/1530) part 2 of [#1337](https://github.com/diffplug/spotless/issues/1337)) +### Fixed +* **POTENTIALLY BREAKING** `sortByKeys` for JSON formatting now takes into account objects inside arrays ([#1546](https://github.com/diffplug/spotless/pull/1546)) +* `freshmark` fixed on java 15+ ([#1304](https://github.com/diffplug/spotless/pull/1304) fixes [#803](https://github.com/diffplug/spotless/issues/803)) + +## [2.34.0] - 2023-01-26 +### Added +* `Formatter` now has a field `public static final File NO_FILE_SENTINEL` which can be used to pass string content to a Formatter or FormatterStep when there is no actual File to format. ([#1525](https://github.com/diffplug/spotless/pull/1525)) + +## [2.33.0] - 2023-01-26 +### Added +* `ProcessRunner` has added some convenience methods so it can be used for Maven testing. ([#1496](https://github.com/diffplug/spotless/pull/1496)) +* `ProcessRunner` allows to limit captured output to a certain number of bytes. ([#1511](https://github.com/diffplug/spotless/pull/1511)) +* `ProcessRunner` is now capable of handling long-running tasks where waiting for exit is delegated to the caller. ([#1511](https://github.com/diffplug/spotless/pull/1511)) +* Allow to specify node executable for node-based formatters using `nodeExecutable` parameter ([#1500](https://github.com/diffplug/spotless/pull/1500)) +### Fixed +* The default list of type annotations used by `formatAnnotations` has had 8 more annotations from the Checker Framework added [#1494](https://github.com/diffplug/spotless/pull/1494) +### Changes +* **POTENTIALLY BREAKING** Bump minimum JRE from 8 to 11, next release likely to bump bytecode to Java 11 ([#1514](https://github.com/diffplug/spotless/pull/1514) part 1 of [#1337](https://github.com/diffplug/spotless/issues/1337)) +* Rename `YamlJacksonStep` into `JacksonYamlStep` while normalizing Jackson usage ([#1492](https://github.com/diffplug/spotless/pull/1492)) +* Convert `gson` integration to use a compile-only source set ([#1510](https://github.com/diffplug/spotless/pull/1510)). +* ** POTENTIALLY BREAKING** Removed support for KtLint 0.3x and 0.45.2 ([#1475](https://github.com/diffplug/spotless/pull/1475)) + * `KtLint` does not maintain a stable API - before this PR, we supported every breaking change in the API since 2019. + * From now on, we will support no more than 2 breaking changes at a time. +* NpmFormatterStepStateBase delays `npm install` call until the formatter is first used. This enables better integration + with `gradle-node-plugin`. ([#1522](https://github.com/diffplug/spotless/pull/1522)) +* Bump default `ktlint` version to latest `0.48.1` -> `0.48.2` ([#1529](https://github.com/diffplug/spotless/pull/1529)) +* Bump default `scalafmt` version to latest `3.6.1` -> `3.7.1` ([#1529](https://github.com/diffplug/spotless/pull/1529)) + +## [2.32.0] - 2023-01-13 +### Added +* Add option `editorConfigFile` for `ktLint` [#142](https://github.com/diffplug/spotless/issues/142) + * **POTENTIALLY BREAKING** `ktlint` step now modifies license headers. Make sure to put `licenseHeader` *after* `ktlint`. +* Added `skipLinesMatching` option to `licenseHeader` to support formats where license header cannot be immediately added to the top of the file (e.g. xml, sh). ([#1441](https://github.com/diffplug/spotless/pull/1441)). +* Add YAML support through Jackson ([#1478](https://github.com/diffplug/spotless/pull/1478)) +* Added support for npm-based [ESLint](https://eslint.org/)-formatter for javascript and typescript ([#1453](https://github.com/diffplug/spotless/pull/1453)) +* Better suggested messages when user's default is set by JVM limitation. ([#995](https://github.com/diffplug/spotless/pull/995)) +### Fixed +* Support `ktlint` 0.48+ new rule disabling syntax ([#1456](https://github.com/diffplug/spotless/pull/1456)) fixes ([#1444](https://github.com/diffplug/spotless/issues/1444)) +* Fix subgroups leading catch all matcher. +### Changes +* Bump default version for `prettier` from `2.0.5` to `2.8.1` ([#1453](https://github.com/diffplug/spotless/pull/1453)) +* Bump the dev version of Gradle from `7.5.1` to `7.6` ([#1409](https://github.com/diffplug/spotless/pull/1409)) + * We also removed the no-longer-required dependency `org.codehaus.groovy:groovy-xml` +* Breaking changes to Spotless' internal testing infrastructure `testlib` ([#1443](https://github.com/diffplug/spotless/pull/1443)) + * `ResourceHarness` no longer has any duplicated functionality which was also present in `StepHarness` + * `StepHarness` now operates on `Formatter` rather than a `FormatterStep` + * `StepHarnessWithFile` now takes a `ResourceHarness` in its constructor to handle the file manipulation parts + * Standardized that we test exception *messages*, not types, which will ease the transition to linting later on + * Bump default `ktlint` version to latest `0.47.1` -> `0.48.1` ([#1456](https://github.com/diffplug/spotless/pull/1456)) +* Switch our publishing infrastructure from CircleCI to GitHub Actions ([#1462](https://github.com/diffplug/spotless/pull/1462)). + * Help wanted for moving our tests too ([#1472](https://github.com/diffplug/spotless/issues/1472)) + +## [2.31.1] - 2023-01-02 +### Fixed +* Improve memory usage when using git ratchet ([#1426](https://github.com/diffplug/spotless/pull/1426)) +* Support `ktlint` 0.48+ ([#1432](https://github.com/diffplug/spotless/pull/1432)) fixes ([#1430](https://github.com/diffplug/spotless/issues/1430)) +### Changes +* Bump default `ktlint` version to latest `0.47.1` -> `0.48.0` ([#1432](https://github.com/diffplug/spotless/pull/1432)) +* Bump default `ktfmt` version to latest `0.41` -> `0.42` ([#1421](https://github.com/diffplug/spotless/pull/1421)) + +## [2.31.0] - 2022-11-24 +### Added +* `importOrder` now support groups of imports without blank lines ([#1401](https://github.com/diffplug/spotless/pull/1401)) +### Fixed +* Don't treat `@Value` as a type annotation [#1367](https://github.com/diffplug/spotless/pull/1367) +* Support `ktlint_disabled_rules` in `ktlint` 0.47.x [#1378](https://github.com/diffplug/spotless/pull/1378) +* Share git repositories across projects when using ratchet ([#1426](https://github.com/diffplug/spotless/pull/1426)) +### Changes +* Bump default `ktfmt` version to latest `0.40` -> `0.41` ([#1340](https://github.com/diffplug/spotless/pull/1340)) +* Bump default `scalafmt` version to latest `3.5.9` -> `3.6.1` ([#1373](https://github.com/diffplug/spotless/pull/1373)) +* Bump default `diktat` version to latest `1.2.3` -> `1.2.4.2` ([#1393](https://github.com/diffplug/spotless/pull/1393)) +* Bump default `palantir-java-format` version to latest `2.10` -> `2.28` ([#1393](https://github.com/diffplug/spotless/pull/1393)) + ## [2.30.0] - 2022-09-14 ### Added * `formatAnnotations()` step to correct formatting of Java type annotations. It puts type annotations on the same line as the type that they qualify. Run it after a Java formatting step, such as `googleJavaFormat()`. ([#1275](https://github.com/diffplug/spotless/pull/1275)) @@ -21,7 +242,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [2.29.0] - 2022-08-23 ### Added -* `scalafmt` integration now has a configuration option `majorScalaVersion` that allows you to configure the Scala version that gets resolved from the maven artifact ([#1283](https://github.com/diffplug/spotless/pull/1283)) +* `scalafmt` integration now has a configuration option `majorScalaVersion` that allows you to configure the Scala version that gets resolved from the Maven artifact ([#1283](https://github.com/diffplug/spotless/pull/1283)) * Converted `scalafmt` integration to use a compile-only source set (fixes [#524](https://github.com/diffplug/spotless/issues/524)) ### Changes * Add the `ktlint` rule in error messages when `ktlint` fails to apply a fix ([#1279](https://github.com/diffplug/spotless/pull/1279)) @@ -246,7 +467,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ### Changed * Update ktfmt from 0.21 to 0.24 ### Fixed -* The `` field in the maven POM is now set correctly ([#798](https://github.com/diffplug/spotless/issues/798)) +* The `` field in the Maven POM is now set correctly ([#798](https://github.com/diffplug/spotless/issues/798)) * Node is re-installed if some other build step removed it ([#863](https://github.com/diffplug/spotless/issues/863)) ## [2.13.4] - 2021-04-21 @@ -320,7 +541,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [2.8.0] - 2020-10-05 ### Added -* Exposed new methods in `GitRatchet` to support faster ratcheting in the maven plugin ([#706](https://github.com/diffplug/spotless/pull/706)). +* Exposed new methods in `GitRatchet` to support faster ratcheting in the Maven plugin ([#706](https://github.com/diffplug/spotless/pull/706)). ## [2.7.0] - 2020-09-21 ### Added @@ -391,10 +612,10 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * `FormatterFunc.NeedsFile` for implementing file-based formatters more cleanly than we have so far ([#637](https://github.com/diffplug/spotless/issues/637)). ### Changed * **BREAKING** `FileSignature` can no longer sign folders, only files. Signatures are now based only on filename (not path), size, and a content hash. It throws an error if a signature is attempted on a folder or on multiple files with different paths but the same filename - it never breaks silently. This change does not break any of Spotless' internal logic, so it is unlikely to affect any of Spotless' consumers either. ([#571](https://github.com/diffplug/spotless/pull/571)) - * This change allows the maven plugin to cache classloaders across subprojects when loading config resources from the classpath (fixes [#559](https://github.com/diffplug/spotless/issues/559)). - * This change also allows the gradle plugin to work with the remote buildcache (fixes [#280](https://github.com/diffplug/spotless/issues/280)). + * This change allows the Maven plugin to cache classloaders across subprojects when loading config resources from the classpath (fixes [#559](https://github.com/diffplug/spotless/issues/559)). + * This change also allows the Gradle plugin to work with the remote buildcache (fixes [#280](https://github.com/diffplug/spotless/issues/280)). * **BREAKING** `FormatterFunc` no longer `extends ThrowingEx.Function` and `BiFunction`. In a major win for Java's idea of ["target typing"](https://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html), this required no changes anywhere in the codebase except deleting the `extends` part of `FormatterFunc` ([#638](https://github.com/diffplug/spotless/issues/638)). -* **BREAKING** Heavy refactor of the `LicenseHeaderStep` public API. Doesn't change internal behavior, but makes implementation of the gradle and maven plugins much easier. ([#628](https://github.com/diffplug/spotless/pull/628)) +* **BREAKING** Heavy refactor of the `LicenseHeaderStep` public API. Doesn't change internal behavior, but makes implementation of the Gradle and Maven plugins much easier. ([#628](https://github.com/diffplug/spotless/pull/628)) * **BREAKING** Removed all deprecated methods and classes from `lib` and `lib-extra`. * [#629](https://github.com/diffplug/spotless/pull/629) removes the code which wasn't being used in plugin-gradle or plugin-maven. * [#630](https://github.com/diffplug/spotless/pull/630) moves the code which was still being used in deprecated parts of plugin-gradle into `.deprecated` package in `plugin-gradle`, which allows us to break `lib` without a breaking change in `plugin-gradle`. @@ -427,7 +648,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * `NodeJsGlobal.setSharedLibFolder` allows to set the location of nodejs shared libs. ([#586](https://github.com/diffplug/spotless/pull/586)) * `PaddedCell.isClean()` returns the instance of `PaddedCell.DirtyState` which represents clean. ([#590](https://github.com/diffplug/spotless/pull/590)) ### Fixed -* Previously, the nodejs-based steps would throw `UnsatisfiedLinkError` if they were ever used from more than one classloader. Now they can be used from any number of classloaders (important for gradle build daemon). ([#586](https://github.com/diffplug/spotless/pull/586)) +* Previously, the nodejs-based steps would throw `UnsatisfiedLinkError` if they were ever used from more than one classloader. Now they can be used from any number of classloaders (important for Gradle build daemon). ([#586](https://github.com/diffplug/spotless/pull/586)) ## [1.31.0] - 2020-05-21 ### Added @@ -449,8 +670,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ### Changed * Updated a bunch of dependencies, most notably: ([#564](https://github.com/diffplug/spotless/pull/564)) * jgit `5.5.0.201909110433-r` -> `5.7.0.202003110725-r` - * gradle `6.2.2` -> `6.3` - * spotbugs gradle plugin `2.0.0` -> `4.0.8` + * Gradle `6.2.2` -> `6.3` + * spotbugs Gradle plugin `2.0.0` -> `4.0.8` ## [1.28.1] - 2020-04-02 ### Fixed @@ -467,12 +688,12 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * We now use [javadoc.io](https://javadoc.io/) instead of github pages. ([#508](https://github.com/diffplug/spotless/pull/508)) * We no longer publish `-SNAPSHOT` for every build to `main`, since we have good [JitPack integration](https://github.com/diffplug/spotless/blob/main/CONTRIBUTING.md#gradle---any-commit-in-a-public-github-repo-this-one-or-any-fork). ([#508](https://github.com/diffplug/spotless/pull/508)) * Improved how we use Spotless on itself. ([#509](https://github.com/diffplug/spotless/pull/509)) -* Fix build warnings when building on Gradle 6+, bump build gradle to 6.2.2, and fix javadoc links. ([#536](https://github.com/diffplug/spotless/pull/536)) +* Fix build warnings when building on Gradle 6+, bump build Gradle to 6.2.2, and fix javadoc links. ([#536](https://github.com/diffplug/spotless/pull/536)) ## [1.27.0] - 2020-01-01 * Ignored `KtLintStepTest`, because [gradle/gradle#11752](https://github.com/gradle/gradle/issues/11752) is causing too many CI failures. ([#499](https://github.com/diffplug/spotless/pull/499)) * Also fixed a minor problem in TestProvisioner. -* If you set the environment variable `SPOTLESS_EXCLUDE_MAVEN=true` then the maven plugin will be excluded from the build. ([#502](https://github.com/diffplug/spotless/pull/502)) +* If you set the environment variable `SPOTLESS_EXCLUDE_MAVEN=true` then the Maven plugin will be excluded from the build. ([#502](https://github.com/diffplug/spotless/pull/502)) * We have set this in JitPack, as a workaround for [jitpack/jitpack.io#4112](https://github.com/jitpack/jitpack.io/issues/4112) * Deprecated `SpotlessCache.clear()` in favor of the new `SpotlessCache.clearOnce(Object key)`. ([#501](https://github.com/diffplug/spotless/issues/#501)) @@ -507,7 +728,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( - but incorrectly marked as good by "check" - this led to "check" says all good, but then "apply" still causes format (https://github.com/diffplug/spotless/issues/453) - combined with up-to-date checking, could lead to even more confusing results (https://github.com/diffplug/spotless/issues/338) - - only affects the gradle plugin, since that was the only plugin to use this feature + - only affects the Gradle plugin, since that was the only plugin to use this feature * Minor change to `TestProvisioner`, which should fix the cache-breaking issues, allowing us to speed-up the CI builds a bit. * Bumped `scalafmt` default version from `1.1.0` to `2.0.1`, since there are [bugs](https://github.com/diffplug/spotless/issues/454) in the old default ([#458](https://github.com/diffplug/spotless/pull/458)). @@ -520,11 +741,11 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * Updated default eclipse-jdt from 4.11.0 to 4.12.0 ([#423](https://github.com/diffplug/spotless/pull/423)). * Updated default eclipse-cdt from 4.11.0 to 4.12.0 ([#423](https://github.com/diffplug/spotless/pull/423)). * **KNOWN BUG - accidentally published CDT 9.7 rather than 9.8 - fixed in 1.26.0** -* Added new maven coordinates for scalafmt 2.0.0+, maintains backwards compatability ([#415](https://github.com/diffplug/spotless/issues/415)) +* Added new Maven coordinates for scalafmt 2.0.0+, maintains backwards compatability ([#415](https://github.com/diffplug/spotless/issues/415)) ## [1.23.1] - 2019-06-17 * Fixes incorrect M2 cache directory path handling of Eclipse based formatters ([#401](https://github.com/diffplug/spotless/issues/401)) -* Update jgit from `4.9.0.201710071750-r` to `5.3.2.201906051522-r` because gradle project is sometimes broken by `apache httpcomponents` in transitive dependency. ([#407](https://github.com/diffplug/spotless/pull/407)) +* Update jgit from `4.9.0.201710071750-r` to `5.3.2.201906051522-r` because Gradle project is sometimes broken by `apache httpcomponents` in transitive dependency. ([#407](https://github.com/diffplug/spotless/pull/407)) ## [1.23.0] - 2019-04-24 * Updated default ktlint from 0.21.0 to 0.32.0, and Maven coords to com.pinterest ([#394](https://github.com/diffplug/spotless/pull/394)) @@ -600,7 +821,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [1.12.0] - 2018-05-14 * Fixed a bug in `LicenseHeaderStep` which caused an exception with some malformed date-aware licenses. ([#222](https://github.com/diffplug/spotless/pull/222)) * Updated default ktlint from 0.14.0 to 0.21.0 -* Add ability to pass custom options to ktlint in gradle plugin. See plugin-gradle/README for details. +* Add ability to pass custom options to ktlint in Gradle plugin. See plugin-gradle/README for details. ## [1.11.0] - 2018-02-26 * Added default indentation of `4` to `IndentStep`. ([#209](https://github.com/diffplug/spotless/pull/209)) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bf1996778d..5fce5a9460 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to Spotless -Pull requests are welcome, preferably against `main`. Feel free to develop spotless any way you like. +Pull requests are welcome, preferably against `main`. Feel free to develop spotless any way you like, but if you like Eclipse and Gradle Buildship then [`gradlew equoIde` will install an IDE and set it up for you](https://github.com/equodev/equo-ide). ## How Spotless works @@ -27,7 +27,7 @@ Spotless also provides `PaddedCell`, which makes it easy to diagnose and correct ## Project layout -For the folders below in monospace text, they are published on maven central at the coordinate `com.diffplug.spotless:spotless-${FOLDER_NAME}`. The other folders are dev infrastructure. +For the folders below in monospace text, they are published on MavenCentral at the coordinate `com.diffplug.spotless:spotless-${FOLDER_NAME}`. The other folders are dev infrastructure. | Folder | Description | | ------ | ----------- | @@ -36,7 +36,6 @@ For the folders below in monospace text, they are published on maven central at | `lib-extra` | Contains the optional parts of Spotless which require external dependencies. `LineEnding.GIT_ATTRIBUTES` won't work unless `lib-extra` is available. | | `plugin-gradle` | Integrates spotless and all of its formatters into Gradle. | | `plugin-maven` | Integrates spotless and all of its formatters into Maven. | -| _ext | Folder for generating glue jars (specifically packaging Eclipse jars from p2 for consumption using maven). ## How to add a new FormatterStep @@ -119,24 +118,6 @@ There are many great formatters (prettier, clang-format, black, etc.) which live Because of Spotless' up-to-date checking and [git ratcheting](https://github.com/diffplug/spotless/tree/main/plugin-gradle#ratchet), Spotless actually doesn't have to call formatters very often, so even an expensive shell call for every single invocation isn't that bad. Anything that works is better than nothing, and we can always speed things up later if it feels too slow (but it probably won't). -## How to enable the _ext projects - -The `_ext` projects are disabled per default, since: - -* some of the projects perform vast downloads at configuration time -* the downloaded content may change on server side and break CI builds - - -The `_ext` can be activated via the root project property `com.diffplug.spotless.include.ext`. - -Activate the property via command line, like for example: - -``` -gradlew -Pcom.diffplug.spotless.include.ext=true build -``` - -Or set the property in your user `gradle.properties` file, which is especially recommended if you like to work with the `_ext` projects using IDEs. - ## How to add a new plugin for a build system The gist of it is that you will have to: @@ -151,6 +132,37 @@ The gist of it is that you will have to: If you get something running, we'd love to host your plugin within this repo as a peer to `plugin-gradle` and `plugin-maven`. +## Run tests + +To run all tests, simply do + +> gradlew test + +Since that takes some time, you might only want to run the tests +concerning what you are working on: + +```shell +# Run only from test from the "lib" project +gradlew :testlib:test --tests com.diffplug.spotless.generic.IndentStepTest + +# Run only one test from the "plugin-maven" project +gradlew :plugin-maven:test --tests com.diffplug.spotless.maven.pom.SortPomMavenTest + +# Run only one test from the "plugin-gradle" project +gradlew :plugin-gradle:test --tests com.diffplug.gradle.spotless.FreshMarkExtensionTest +``` + +## Check and format code + +Before creating a pull request, you might want to format (yes, spotless is formatted by spotless) +the code and check for possible bugs + +* `./gradlew spotlessApply` +* `./gradlew spotbugsMain` + +These checks are also run by the automated pipeline when you submit a pull request, if +the pipeline fails, first check if the code is formatted and no bugs were found. + ## Integration testing ### Gradle - locally @@ -207,7 +219,7 @@ If it doesn't work, you can check the JitPack log at `https://jitpack.io/com/git ### Maven -Run `./gradlew publishToMavenLocal` to publish this to your local repository. The maven plugin is not published to JitPack due to [jitpack/jitpack.io#4112](https://github.com/jitpack/jitpack.io/issues/4112). +Run `./gradlew publishToMavenLocal` to publish this to your local repository. You can also use the JitPack artifacts, using the same principles as Gradle above. ## License diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 0ae5167a13..76d0ce19ab 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -1,7 +1,7 @@ If you are submitting a **bug**, please include the following: - [ ] summary of problem -- [ ] gradle or maven version +- [ ] Gradle or Maven version - [ ] spotless version - [ ] operating system and version - [ ] copy-paste your full Spotless configuration block(s), and a link to a public git repo that reproduces the problem if possible diff --git a/README.md b/README.md index fb1a3670e6..3c5d13b179 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,45 @@ # Spotless: Keep your code spotless - -[![Circle CI](https://circleci.com/gh/diffplug/spotless/tree/main.svg?style=shield)](https://circleci.com/gh/diffplug/spotless/tree/main) -[![Live chat](https://img.shields.io/badge/gitter-chat-brightgreen.svg)](https://gitter.im/diffplug/spotless) -[![License Apache](https://img.shields.io/badge/license-apache-brightgreen.svg)](https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)) - +[![Gradle Plugin](https://img.shields.io/gradle-plugin-portal/v/com.diffplug.spotless?color=blue&label=gradle%20plugin)](plugin-gradle) +[![Maven Plugin](https://img.shields.io/maven-central/v/com.diffplug.spotless/spotless-maven-plugin?color=blue&label=maven%20plugin)](plugin-maven) +[![SBT Plugin](https://img.shields.io/badge/sbt%20plugin-0.1.3-blue)](https://github.com/moznion/sbt-spotless) -Spotless can format <antlr | c | c# | c++ | css | flow | graphql | groovy | html | java | javascript | json | jsx | kotlin | less | license headers | markdown | objective-c | protobuf | python | scala | scss | sql | typeScript | vue | yaml | anything> using <gradle | maven | anything>. +Spotless can format <antlr | c | c# | c++ | css | flow | graphql | groovy | html | java | javascript | json | jsx | kotlin | less | license headers | markdown | objective-c | protobuf | python | scala | scss | shell | sql | typeScript | vue | yaml | anything> using <gradle | maven | sbt | anything>. You probably want one of the links below: ## [❇️ Spotless for Gradle](plugin-gradle) (with integrations for [VS Code](https://marketplace.visualstudio.com/items?itemName=richardwillis.vscode-spotless-gradle) and [IntelliJ](https://plugins.jetbrains.com/plugin/18321-spotless-gradle)) + +```console +user@machine repo % ./gradlew build +:spotlessJavaCheck FAILED + The following files had format violations: + src\main\java\com\diffplug\gradle\spotless\FormatExtension.java + -\t\t····if·(targets.length·==·0)·{ + +\t\tif·(targets.length·==·0)·{ + Run './gradlew spotlessApply' to fix these violations. +user@machine repo % ./gradlew spotlessApply +:spotlessApply +BUILD SUCCESSFUL +user@machine repo % ./gradlew build +BUILD SUCCESSFUL +``` + ## [❇️ Spotless for Maven](plugin-maven) + +```console +user@machine repo % mvn spotless:check +[ERROR] > The following files had format violations: +[ERROR] src\main\java\com\diffplug\gradle\spotless\FormatExtension.java +[ERROR] -\t\t····if·(targets.length·==·0)·{ +[ERROR] +\t\tif·(targets.length·==·0)·{ +[ERROR] Run 'mvn spotless:apply' to fix these violations. +user@machine repo % mvn spotless:apply +[INFO] BUILD SUCCESS +user@machine repo % mvn spotless:check +[INFO] BUILD SUCCESS +``` + ## [❇️ Spotless for SBT (external for now)](https://github.com/moznion/sbt-spotless) ## [Other build systems](CONTRIBUTING.md#how-to-add-a-new-plugin-for-a-build-system) @@ -53,8 +75,11 @@ lib('generic.ReplaceRegexStep') +'{{yes}} | {{yes}} lib('generic.ReplaceStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', lib('generic.TrimTrailingWhitespaceStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', lib('antlr4.Antlr4FormatterStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', +lib('biome.BiomeStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', lib('cpp.ClangFormatStep') +'{{yes}} | {{no}} | {{no}} | {{no}} |', extra('cpp.EclipseFormatterStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |', +lib('go.GofmtFormatStep') +'{{yes}} | {{no}} | {{no}} | {{no}} |', +lib('gherkin.GherkinUtilsStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', extra('groovy.GrEclipseFormatterStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |', lib('java.GoogleJavaFormatStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |', lib('java.ImportOrderStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |', @@ -62,20 +87,27 @@ lib('java.PalantirJavaFormatStep') +'{{yes}} | {{yes}} lib('java.RemoveUnusedImportsStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |', extra('java.EclipseJdtFormatterStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |', lib('java.FormatAnnotationsStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', -lib('json.gson.GsonStep') +'{{yes}} | {{no}} | {{no}} | {{no}} |', -lib('json.JsonSimpleStep') +'{{yes}} | {{no}} | {{no}} | {{no}} |', +lib('java.CleanthatJavaStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', +lib('json.gson.GsonStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', +lib('json.JacksonJsonStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', +lib('json.JsonSimpleStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', +lib('json.JsonPatchStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', lib('kotlin.KtLintStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |', lib('kotlin.KtfmtStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', lib('kotlin.DiktatStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', lib('markdown.FreshMarkStep') +'{{yes}} | {{no}} | {{no}} | {{no}} |', -lib('markdown.FlexmarkStep') +'{{no}} | {{yes}} | {{no}} | {{no}} |', +lib('markdown.FlexmarkStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', +lib('npm.EslintFormatterStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', lib('npm.PrettierFormatterStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', lib('npm.TsFmtFormatterStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', lib('pom.SortPomStepStep') +'{{no}} | {{yes}} | {{no}} | {{no}} |', +lib('protobuf.BufStep') +'{{yes}} | {{no}} | {{no}} | {{no}} |', lib('python.BlackStep') +'{{yes}} | {{no}} | {{no}} | {{no}} |', lib('scala.ScalaFmtStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |', +lib('shell.ShfmtStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', lib('sql.DBeaverSQLFormatterStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |', extra('wtp.EclipseWtpFormatterStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', +lib('yaml.JacksonYamlStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', '| [(Your FormatterStep here)](CONTRIBUTING.md#how-to-add-a-new-formatterstep) | {{no}} | {{no}} | {{no}} | {{no}} |', ].join('\n'); --> @@ -97,8 +129,11 @@ extra('wtp.EclipseWtpFormatterStep') +'{{yes}} | {{yes}} | [`generic.ReplaceStep`](lib/src/main/java/com/diffplug/spotless/generic/ReplaceStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | | [`generic.TrimTrailingWhitespaceStep`](lib/src/main/java/com/diffplug/spotless/generic/TrimTrailingWhitespaceStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | | [`antlr4.Antlr4FormatterStep`](lib/src/main/java/com/diffplug/spotless/antlr4/Antlr4FormatterStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | +| [`biome.BiomeStep`](lib/src/main/java/com/diffplug/spotless/biome/BiomeStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | | [`cpp.ClangFormatStep`](lib/src/main/java/com/diffplug/spotless/cpp/ClangFormatStep.java) | :+1: | :white_large_square: | :white_large_square: | :white_large_square: | | [`cpp.EclipseFormatterStep`](lib-extra/src/main/java/com/diffplug/spotless/extra/cpp/EclipseFormatterStep.java) | :+1: | :+1: | :+1: | :white_large_square: | +| [`go.GofmtFormatStep`](lib/src/main/java/com/diffplug/spotless/go/GofmtFormatStep.java) | :+1: | :white_large_square: | :white_large_square: | :white_large_square: | +| [`gherkin.GherkinUtilsStep`](lib/src/main/java/com/diffplug/spotless/gherkin/GherkinUtilsStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | | [`groovy.GrEclipseFormatterStep`](lib-extra/src/main/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStep.java) | :+1: | :+1: | :+1: | :white_large_square: | | [`java.GoogleJavaFormatStep`](lib/src/main/java/com/diffplug/spotless/java/GoogleJavaFormatStep.java) | :+1: | :+1: | :+1: | :white_large_square: | | [`java.ImportOrderStep`](lib/src/main/java/com/diffplug/spotless/java/ImportOrderStep.java) | :+1: | :+1: | :+1: | :white_large_square: | @@ -106,20 +141,27 @@ extra('wtp.EclipseWtpFormatterStep') +'{{yes}} | {{yes}} | [`java.RemoveUnusedImportsStep`](lib/src/main/java/com/diffplug/spotless/java/RemoveUnusedImportsStep.java) | :+1: | :+1: | :+1: | :white_large_square: | | [`java.EclipseJdtFormatterStep`](lib-extra/src/main/java/com/diffplug/spotless/extra/java/EclipseJdtFormatterStep.java) | :+1: | :+1: | :+1: | :white_large_square: | | [`java.FormatAnnotationsStep`](lib/src/main/java/com/diffplug/spotless/java/FormatAnnotationsStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | -| [`json.gson.GsonStep`](lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java) | :+1: | :white_large_square: | :white_large_square: | :white_large_square: | -| [`json.JsonSimpleStep`](lib/src/main/java/com/diffplug/spotless/json/JsonSimpleStep.java) | :+1: | :white_large_square: | :white_large_square: | :white_large_square: | +| [`java.CleanthatJavaStep`](lib/src/main/java/com/diffplug/spotless/java/CleanthatJavaStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | +| [`json.gson.GsonStep`](lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | +| [`json.JacksonJsonStep`](lib/src/main/java/com/diffplug/spotless/json/JacksonJsonStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | +| [`json.JsonSimpleStep`](lib/src/main/java/com/diffplug/spotless/json/JsonSimpleStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | +| [`json.JsonPatchStep`](lib/src/main/java/com/diffplug/spotless/json/JsonPatchStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | | [`kotlin.KtLintStep`](lib/src/main/java/com/diffplug/spotless/kotlin/KtLintStep.java) | :+1: | :+1: | :+1: | :white_large_square: | | [`kotlin.KtfmtStep`](lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | | [`kotlin.DiktatStep`](lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | | [`markdown.FreshMarkStep`](lib/src/main/java/com/diffplug/spotless/markdown/FreshMarkStep.java) | :+1: | :white_large_square: | :white_large_square: | :white_large_square: | -| [`markdown.FlexmarkStep`](lib/src/main/java/com/diffplug/spotless/markdown/FlexmarkStep.java) | :white_large_square: | :+1: | :white_large_square: | :white_large_square: | +| [`markdown.FlexmarkStep`](lib/src/main/java/com/diffplug/spotless/markdown/FlexmarkStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | +| [`npm.EslintFormatterStep`](lib/src/main/java/com/diffplug/spotless/npm/EslintFormatterStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | | [`npm.PrettierFormatterStep`](lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | | [`npm.TsFmtFormatterStep`](lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | | [`pom.SortPomStepStep`](lib/src/main/java/com/diffplug/spotless/pom/SortPomStepStep.java) | :white_large_square: | :+1: | :white_large_square: | :white_large_square: | +| [`protobuf.BufStep`](lib/src/main/java/com/diffplug/spotless/protobuf/BufStep.java) | :+1: | :white_large_square: | :white_large_square: | :white_large_square: | | [`python.BlackStep`](lib/src/main/java/com/diffplug/spotless/python/BlackStep.java) | :+1: | :white_large_square: | :white_large_square: | :white_large_square: | | [`scala.ScalaFmtStep`](lib/src/main/java/com/diffplug/spotless/scala/ScalaFmtStep.java) | :+1: | :+1: | :+1: | :white_large_square: | +| [`shell.ShfmtStep`](lib/src/main/java/com/diffplug/spotless/shell/ShfmtStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | | [`sql.DBeaverSQLFormatterStep`](lib/src/main/java/com/diffplug/spotless/sql/DBeaverSQLFormatterStep.java) | :+1: | :+1: | :+1: | :white_large_square: | | [`wtp.EclipseWtpFormatterStep`](lib-extra/src/main/java/com/diffplug/spotless/extra/wtp/EclipseWtpFormatterStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | +| [`yaml.JacksonYamlStep`](lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | | [(Your FormatterStep here)](CONTRIBUTING.md#how-to-add-a-new-formatterstep) | :white_large_square: | :white_large_square: | :white_large_square: | :white_large_square: | @@ -131,14 +173,14 @@ Once someone has filled in one square of the formatter/build system matrix, it's ## Acknowledgements -- Thanks to [Konstantin Lutovich](https://github.com/lutovich) for [implementing and maintaining the maven plugin](https://github.com/diffplug/spotless/pull/188), as well as fixing [remote-build cache support for Gradle](https://github.com/diffplug/spotless/pull/571). +- Thanks to [Konstantin Lutovich](https://github.com/lutovich) for [implementing and maintaining the Maven plugin](https://github.com/diffplug/spotless/pull/188), as well as fixing [remote-build cache support for Gradle](https://github.com/diffplug/spotless/pull/571). - Thanks to [Frank Vennemeyer](https://github.com/fvgh) for [Groovy support via greclipse](https://github.com/diffplug/spotless/issues/13), [C++ support via CDT](https://github.com/diffplug/spotless/issues/232), [XML support via WTP](https://github.com/diffplug/spotless/pull/241) and a huge body of work with other eclipse-based formatters. - Thanks to [Jonathan Bluett-Duncan](https://github.com/jbduncan) for - implementing up-to-date checking [#31](https://github.com/diffplug/spotless/issues/31) - breaking spotless into libraries [#56](https://github.com/diffplug/spotless/issues/56) - lots of other things, but especially the diff support in `spotlessCheck` - constant improvements on a variety of topics with high-quality code reviews -- Thanks to [Daz DeBoer](https://github.com/bigdaz) for the reworking the guts of our gradle plugin to support [buildcache](https://github.com/diffplug/spotless/pull/576), [InputChanges](https://github.com/diffplug/spotless/pull/607), and [lazy configuration](https://github.com/diffplug/spotless/pull/617). +- Thanks to [Daz DeBoer](https://github.com/bigdaz) for the reworking the guts of our Gradle plugin to support [buildcache](https://github.com/diffplug/spotless/pull/576), [InputChanges](https://github.com/diffplug/spotless/pull/607), and [lazy configuration](https://github.com/diffplug/spotless/pull/617). - Thanks to [Richard Willis](https://github.com/badsyntax) for creating the [VS Code extension for Spotless Gradle](https://marketplace.visualstudio.com/items?itemName=richardwillis.vscode-spotless-gradle). - Thanks to [Ryan Gurney](https://github.com/ragurney) for creating the [IntelliJ plugin for Spotless Gradle](https://plugins.jetbrains.com/plugin/18321-spotless-gradle). - Thanks to [Markus Heberling](https://github.com/tisoft) for adding [generic native formatters](https://github.com/diffplug/spotless/pull/949), [jsr-223 formatters](https://github.com/diffplug/spotless/pull/945), and [maven pom sorting](https://github.com/diffplug/spotless/pull/946). @@ -157,7 +199,7 @@ Once someone has filled in one square of the formatter/build system matrix, it's - Thanks to [Baptiste Mesta](https://github.com/baptistemesta) for - porting the DBeaver formatter to Spotless, and thanks to [DBeaver](https://dbeaver.jkiss.org/) and [its authors](https://github.com/serge-rider/dbeaver/graphs/contributors) for their excellent SQL formatter. - making license headers date-aware [#179](https://github.com/diffplug/spotless/pull/179) -- Thanks to [vmdominguez](https://github.com/vmdominguez) and [Luis Fors](https://github.com/luis-fors-cb) for adding the ability to limit formatting to specific files in gradle ([#322](https://github.com/diffplug/spotless/pull/322)) and maven ([#392](https://github.com/diffplug/spotless/pull/392)), respectively. +- Thanks to [vmdominguez](https://github.com/vmdominguez) and [Luis Fors](https://github.com/luis-fors-cb) for adding the ability to limit formatting to specific files in Gradle ([#322](https://github.com/diffplug/spotless/pull/322)) and Maven ([#392](https://github.com/diffplug/spotless/pull/392)), respectively. - Thanks to [bender316](https://github.com/bender316) for fixing classloading on Java 9 ([#426](https://github.com/diffplug/spotless/pull/426)). - Thanks to [Stefan Oehme](https://github.com/oehme) for tons of help on the internal mechanics of Gradle. - Thanks to [eyalkaspi](https://github.com/eyalkaspi) for adding configurable date ranges to the date-aware license headers. @@ -165,7 +207,7 @@ Once someone has filled in one square of the formatter/build system matrix, it's - Thanks to [Oliver Horn](https://github.com/ohorn) for adding AOSP support for Spotless' google-java-format integration. - Formatting by Eclipse - Special thanks to [Mateusz Matela](https://waynebeaton.wordpress.com/2015/03/15/great-fixes-for-mars-winners-part-i/) for huge improvements to the eclipse code formatter! -- Thanks to [Zac Sweers](https://github.com/ZacSweers) for fixing the highly requested ktlint 0.34+ support ([#469](https://github.com/diffplug/spotless/pull/469)), multiple build updates and fixing a gradle deprecation warning ([#434](https://github.com/diffplug/spotless/pull/434) and others). +- Thanks to [Zac Sweers](https://github.com/ZacSweers) for fixing the highly requested ktlint 0.34+ support ([#469](https://github.com/diffplug/spotless/pull/469)), multiple build updates and fixing a Gradle deprecation warning ([#434](https://github.com/diffplug/spotless/pull/434) and others). - Thanks to [Stephen Panaro](https://github.com/smpanaro) for adding support for ktlint FilenameRule ([#974](https://github.com/diffplug/spotless/pull/974)). - Thanks to [Nelson Osacky](https://github.com/runningcode) for android doc improvements, versions bump, and a build improvement. - Thanks to [Stanley Shyiko](https://github.com/shyiko) for his help integrating [ktlint](https://github.com/shyiko/ktlint). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..56294d0a51 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1 @@ +For security disclosures, please send an email to spotless-security@diffplug.com diff --git a/_ext/eclipse-base/CHANGES.md b/_ext/eclipse-base/CHANGES.md deleted file mode 100644 index 796ed584fd..0000000000 --- a/_ext/eclipse-base/CHANGES.md +++ /dev/null @@ -1,53 +0,0 @@ -# spotless-eclipse-base - -We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.2.1`). - -## [Unreleased] - -## [3.5.2] - 2021-10-23 -### Fixed -* Racing condition when cleaning up temporary workspace on JVM runtime shutdown (see [#967](https://github.com/diffplug/spotless/issues/967)). Can lead to error logs and remaining files in workspace. - -## [3.5.1] - 2021-10-16 -### Fixed -* ~~Racing condition when cleaning up temporary workspace on JVM runtime shutdown (see [#967](https://github.com/diffplug/spotless/issues/967)). Can lead to error logs and remaining files in workspace.~~ - -## [3.5.0] - 2021-06-20 -### Added -* Support of `org.eclipse.core.resources` version `3.15.0` required by Eclipse `4.20`. -* Minimum required Java version changed from 8 to 11. - -## [3.4.2] - 2020-12-26 -### Fixed -* `org.eclipse.osgi` version `3.16.100` does not allow `null` as Debug service for `CloseableBundleFile`. - -## [3.4.1] - 2020-09-24 -### Fixed -* Restored scope of transitive dependencies to 'compile'. - -## [3.4.0] - 2020-09-23 -### Added -* Upgraded to `org.eclipse.osgi` version `3.16`. - -## [3.3.0] - 2020-03-04 -### Added -* Added support of plugin extensions without activator ([#533](https://github.com/diffplug/spotless/issues/533)). Required since `org.eclipse.core.filesystem` version `1.7.600` (see -[Bug 550548](https://bugs.eclipse.org/bugs/show_bug.cgi?id=550548)) -* Updated configuration via single interface implementation. Functional based configuration still supported. - -## [3.2.1] - 2019-09-03 -* Fixed deletion of temporary workspace. ([#447](https://github.com/diffplug/spotless/issues/447)) - -## [3.2.0] - 2019-06-30 -* Added support of Eclipse 4.12 framework wiring. ([#413](https://github.com/diffplug/spotless/issues/413)) - -## [3.1.1] - 2019-06-04 -* Fixed problem handling URL escaped characters in JAR file location. ([#401](https://github.com/diffplug/spotless/issues/401)) - -## [3.1.0] - 2019-02-10 -* Added logging service based on SLF4J. ([#236](https://github.com/diffplug/spotless/issues/236)) -* Updated internal interfaces to support `org.eclipse.osgi` version 3.13. -* Corrected documentation of version number usage in `gradle.properties`. - -## [3.0.0] - 2018-06-18 -* Initial release! diff --git a/_ext/eclipse-base/LICENSE.txt b/_ext/eclipse-base/LICENSE.txt deleted file mode 100644 index 3d967aee74..0000000000 --- a/_ext/eclipse-base/LICENSE.txt +++ /dev/null @@ -1,70 +0,0 @@ -Eclipse Public License - v 1.0 - -THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. - -1. DEFINITIONS - -"Contribution" means: - -a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and -b) in the case of each subsequent Contributor: -i) changes to the Program, and -ii) additions to the Program; -where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. -"Contributor" means any person or entity that distributes the Program. - -"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. - -"Program" means the Contributions distributed in accordance with this Agreement. - -"Recipient" means anyone who receives the Program under this Agreement, including all Contributors. - -2. GRANT OF RIGHTS - -a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. -b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. -c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. -d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. -3. REQUIREMENTS - -A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: - -a) it complies with the terms and conditions of this Agreement; and -b) its license agreement: -i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; -ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; -iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and -iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. -When the Program is made available in source code form: - -a) it must be made available under this Agreement; and -b) a copy of this Agreement must be included with each copy of the Program. -Contributors may not remove or alter any copyright notices contained within the Program. - -Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. - -4. COMMERCIAL DISTRIBUTION - -Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. - -For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. - -5. NO WARRANTY - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. - -6. DISCLAIMER OF LIABILITY - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -7. GENERAL - -If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. - -If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. - -All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. - -Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. - -This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. \ No newline at end of file diff --git a/_ext/eclipse-base/README.md b/_ext/eclipse-base/README.md deleted file mode 100644 index 39a03591ab..0000000000 --- a/_ext/eclipse-base/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# spotless-eclipse-base - -Eclipse formatters are embedded in plugins serving multiple purposes and not necessarily supporting headless builds. Hence the plugin interfaces are depending on various Eclipse plugins and services not required for the formatting purpose. - -Spotless provides its own plugin framework with `com.diffplug.spotless.JarState`. This allows Spotless to use different Eclipse versions in parallel. - - -The `com.diffplug.gradle.spotless:spotless-eclipse-base` artifact mocks the redundant Eclipse OSGI/plugin framework for Spotless. Furthermore it provides Eclipse services adapted for Spotless's own use, which avoids for example the creation of a permanent Eclipse workspace and reduces dependencies on Eclipse modules unused by the Eclipse code formatters. - -## Usage - -Include the artifact in your Spotless Eclipse formatter project, where the major version must match the Eclipse core version your formatter supports. The exact default version should be selected by the **lib-extra**. -Minor versions indicate a change in the minimum Eclipse (core/resource) dependencies. -Patch versions are always backward compatible. - - -```Gradle -dependencies { - compile "com.diffplug.spotless:spotless-eclipse-base:3.+" -} -``` - -In the constructor of your formatter, the Spotless Eclipse Framework can be configured depending on your formatter's requirements. For example the JDT formatter can be configured like: - -```Java -public EclipseFormatterStepImpl(Properties settings) throws Exception { - SpotlessEclipseFramework.setup(plugins -> { - plugins.applyDefault(); - plugins.add(new org.eclipse.jdt.core.JavaCore()); - }); - ... -``` - -The framework also supports fat JARs, providing multiple plugins. -In this cases the resources required by plugins, especially the `META-INF` and plugin information, must be located in locations unique to the plugin. -For this purpose the framework expects that these resources are stored in a sub-directory -which has the name of the package containing the plugin. For example in case the JDT plugin -is included in your formatter fat JAR, the directory structure should be: - -``` -+ resources -| -+--+ org.eclipse.jdt.core -| | -| +--+ META-INF -| | | -| | +-- MANIFEST.MF -| | -| +--- plugin.properties -| | -| +--- plugin.xml - -``` - -## Build - -To publish a new version, update the `_ext/eclipse-base/gradle.properties` appropriately and see [CONTRIBUTING.md](../../CONTRIBUTING.md) how to enable -`_ext` projects. - -## License - -Spotless at large is under the Apache 2.0 license, but this jar is under the EPL v1. diff --git a/_ext/eclipse-base/build.gradle b/_ext/eclipse-base/build.gradle deleted file mode 100644 index 88255b57b3..0000000000 --- a/_ext/eclipse-base/build.gradle +++ /dev/null @@ -1,32 +0,0 @@ -apply from: rootProject.file('_ext/gradle/java-setup.gradle') -apply from: rootProject.file('gradle/java-publish.gradle') - -ext { - developers = [ - fvgh: [ name: 'Frank Vennemeyer', email: 'frankgh@zoho.com' ] - ] -} - -dependencies { - api("org.eclipse.platform:org.eclipse.core.resources:${VER_ECLIPSE_CORE_RESOURCES}") { - exclude group: 'org.eclipse.platform', module: 'org.eclipse.ant.core' - exclude group: 'org.eclipse.platform', module: 'org.eclipse.core.expressions' - } - api("org.slf4j:slf4j-api:${VER_SLF4J}") - - testImplementation("org.slf4j:slf4j-simple:${VER_SLF4J}") -} - -jar { - manifest { - from 'src/main/resources/META-INF/MANIFEST.MF' - } -} - -////////// -// Test // -////////// -sourceSets { - // Use JAR file with all resources for Eclipse-Base integration-tests - test.runtimeClasspath = jar.outputs.files + sourceSets.test.output + sourceSets.test.compileClasspath -} diff --git a/_ext/eclipse-base/gradle.properties b/_ext/eclipse-base/gradle.properties deleted file mode 100644 index 9f8774a417..0000000000 --- a/_ext/eclipse-base/gradle.properties +++ /dev/null @@ -1,8 +0,0 @@ -artifactId=spotless-eclipse-base -description=Eclipse bundle controller and services for Spotless - -# Build requirements -VER_JAVA=11 - -# Compile dependencies -VER_ECLIPSE_CORE_RESOURCES=[3.15.0,4.0.0[ diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/SpotlessEclipseConfig.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/SpotlessEclipseConfig.java deleted file mode 100644 index c8d1fb4db3..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/SpotlessEclipseConfig.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base; - -/** - * Obtain configuration for Eclipse framework setup - *

- * The setup consitst of 4 phases: - *

    - *
  1. Registration of framework bundles
  2. - *
  3. Registration of servicves
  4. - *
  5. Resitration of extension points, activation of bundles/plugins
  6. - *
  7. Customize bundle/plugin configuration
  8. - *
- */ -public interface SpotlessEclipseConfig { - default public void registerBundles(SpotlessEclipseCoreConfig config) { - config.applyDefault(); - } - - default public void registerServices(SpotlessEclipseServiceConfig config) { - config.applyDefault(); - } - - default public void activatePlugins(SpotlessEclipsePluginConfig config) { - config.applyDefault(); - } - - default public void customize() {} -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/SpotlessEclipseCoreConfig.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/SpotlessEclipseCoreConfig.java deleted file mode 100644 index 4f3aa9c2a2..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/SpotlessEclipseCoreConfig.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base; - -import org.osgi.framework.BundleActivator; - -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework.DefaultBundles; -import com.diffplug.spotless.extra.eclipse.base.osgi.BundleConfig; - -/** - * Configuration of core bundles which shall be provided by the - * {@link SpotlessEclipseFramework}. - *

- * Core bundles are not accessed via the plugin registry, but by static methods. - * Hence they do not require a registration, which allows a lightweight - * setup. - *

- * See {@code org.eclipse.core.internal.runtime.PlatformActivator} implementation for details. - */ -public class SpotlessEclipseCoreConfig extends BundleConfig { - - /** - * Don't instantiate and call {@link SpotlessEclipseConfig} directly. - * Registered bundles should only be instantiated once, since - * older bundles still abusing singletons for access. - */ - SpotlessEclipseCoreConfig() {} - - @Override - public void applyDefault() { - add(SpotlessEclipseFramework.DefaultBundles.createAll()); - } - - @Override - protected BundleActivator create(DefaultBundles bundle) { - return bundle.create(); - } - - @Override - protected int getDefaultState(DefaultBundles bundle) { - return bundle.getDesiredState(); - } - -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/SpotlessEclipseFramework.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/SpotlessEclipseFramework.java deleted file mode 100644 index 2eaf9e568e..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/SpotlessEclipseFramework.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright 2016-2021 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.Arrays; -import java.util.List; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Collectors; - -import javax.xml.parsers.SAXParserFactory; - -import org.eclipse.core.internal.runtime.InternalPlatform; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleActivator; -import org.osgi.framework.BundleContext; -import org.osgi.framework.BundleException; - -import com.diffplug.spotless.extra.eclipse.base.osgi.BundleConfig; -import com.diffplug.spotless.extra.eclipse.base.osgi.BundleController; -import com.diffplug.spotless.extra.eclipse.base.runtime.PluginRegistrar; - -/** Setup a framework for Spotless Eclipse based formatters */ -public final class SpotlessEclipseFramework { - /** Spotless demands for internal formatter chains Unix (LF) line endings. */ - public static final String LINE_DELIMITER = "\n"; - - /** - * Default core bundles required by most plugins. - */ - public enum DefaultBundles { - /** - * Plugins ask the platform whether core runtime bundle is in debug mode. - *

- * Note that the bundle requires the - * {@link org.eclipse.osgi.service.environment.EnvironmentInfo} - * service. - *

- *

- * Per default, the platform is not activated. Some plugins use this information - * to determine whether they are running in a headless modes (without IDE). - *

- */ - PLATFORM(org.eclipse.core.internal.runtime.PlatformActivator.class, Bundle.RESOLVED), - /** The Spotless {@link BundleController} wraps the OSGi layer. But the plugin/extension look-up still uses the Eclipse OSGi registry.*/ - REGISTRY(org.eclipse.core.internal.registry.osgi.Activator.class), - /** Eclipse preferences always check whether this bundle has been activated before preference are set.*/ - PREFERENCES(org.eclipse.core.internal.preferences.Activator.class), - /** The common runtime provides common services, like log and service adapters registry. */ - COMMON(org.eclipse.core.internal.runtime.Activator.class); - - private final Class activatorClass; - private final int state; - - private DefaultBundles(Class clazz) { - this(clazz, Bundle.ACTIVE); - } - - private DefaultBundles(Class clazz, int state) { - activatorClass = clazz; - this.state = state; - } - - /** Create new bundle activator instance. */ - public BundleActivator create() { - return createInstance(activatorClass); - } - - public int getDesiredState() { - return state; - } - - /** Create bundle activator instances for all enumerated values. */ - public static List createAll() { - return Arrays.stream(values()) - .map(value -> new BundleConfig.Entry(value.create(), value.getDesiredState())).collect(Collectors.toList()); - } - } - - /** - * Default plugins required by most Spotless formatters. - *

- * Eclipse plugins are OSGI bundles themselves and do not necessarily derive from the Eclipse Plugin class. - * {@link BundleActivator} implementation may as well serve as plugins. - * All plugins must provide a MANIFEST.MF, plugin.properties and plugin.xml description file, - * required for the plugin registration. - *

- */ - public enum DefaultPlugins { - /** - * The resources plugin initialized the Eclipse workspace and allows URL look-up. - * Most formatters using the workspace to resolve URLs or create - * file interfaces. - * See {@code org.eclipse.core.resources.IFile} implementation for details. - */ - RESOURCES(org.eclipse.core.resources.ResourcesPlugin.class); - - private final Class pluginClass; - - DefaultPlugins(Class clazz) { - pluginClass = clazz; - } - - /** Create new plugin instance. */ - public BundleActivator create() { - return createInstance(pluginClass); - } - - /** Create plugin instances for all enumerated values. */ - public static List createAll() { - return Arrays.stream(values()).map(value -> value.create()).collect(Collectors.toList()); - } - } - - private static T createInstance(Class clazz) { - try { - Constructor ctor = clazz.getConstructor(); - return ctor.newInstance(new Object[]{}); - } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { - throw new RuntimeException("Failed to create instance for: " + clazz.getCanonicalName(), e); - } - } - - private static SpotlessEclipseFramework INSTANCE = null; - - /** - * Creates and configures a new {@link SpotlessEclipseFramework} using - * {@link DefaultBundles}, {@link DefaultPlugins} and default {@link SpotlessEclipseServiceConfig}. - * If there is already a an instance, the call is ignored. - * @return False if the {@link SpotlessEclipseFramework} instance already exists, true otherwise. - * @throws BundleException Throws exception in case the setup failed. - * @deprecated Use {@link #setup(SpotlessEclipseConfig)} instead. - */ - @Deprecated - public synchronized static boolean setup() throws BundleException { - return setup(plugins -> plugins.applyDefault()); - } - - /** - * Creates and configures a new {@link SpotlessEclipseFramework} using - * {@link DefaultBundles} and {@link DefaultPlugins}. - * If there is already a an instance, the call is ignored. - * @param plugins Eclipse plugins (which are also OSGi bundles) to start - * @return False if the {@link SpotlessEclipseFramework} instance already exists, true otherwise. - * @throws BundleException Throws exception in case the setup failed. - * @deprecated Use {@link #setup(SpotlessEclipseConfig)} instead. - */ - @Deprecated - public synchronized static boolean setup(Consumer plugins) throws BundleException { - return setup(config -> config.applyDefault(), plugins); - } - - /** - * Creates and configures a new {@link SpotlessEclipseFramework} using {@link DefaultBundles}. - * If there is already a an instance, the call is ignored. - * @param config Framework service configuration - * @param plugins Eclipse plugins (which are also OSGi bundles) to start - * @return False if the {@link SpotlessEclipseFramework} instance already exists, true otherwise. - * @throws BundleException Throws exception in case the setup failed. - * @deprecated Use {@link #setup(SpotlessEclipseConfig)} instead. - */ - @Deprecated - public synchronized static boolean setup(Consumer config, Consumer plugins) throws BundleException { - return setup(core -> core.applyDefault(), config, plugins); - } - - /** - * Creates and configures a new {@link SpotlessEclipseFramework} if there is none. - * If there is already a an instance, the call is ignored. - * @param core Activators of core bundles - * @param services Framework service configuration - * @param plugins Eclipse plugins to start - * @return False if the {@link SpotlessEclipseFramework} instance already exists, true otherwise. - * @throws BundleException Throws exception in case the setup failed. - * @deprecated Use {@link #setup(SpotlessEclipseConfig)} instead. - */ - @Deprecated - public synchronized static boolean setup(Consumer core, Consumer services, Consumer plugins) throws BundleException { - if (null != INSTANCE) { - return false; - } - setup(new SpotlessEclipseConfig() { - @Override - public void registerBundles(SpotlessEclipseCoreConfig config) { - core.accept(config); - } - - @Override - public void registerServices(SpotlessEclipseServiceConfig config) { - services.accept(config); - } - - @Override - public void activatePlugins(SpotlessEclipsePluginConfig config) { - plugins.accept(config); - } - }); - return true; - } - - /** - * Creates and configures a new {@link SpotlessEclipseFramework} if there is none. - * If there is already a an instance, the call is ignored. - * @param config Configuration - * @throws BundleException Throws exception in case the setup failed. - */ - public synchronized static void setup(SpotlessEclipseConfig config) throws BundleException { - if (null == INSTANCE) { - INSTANCE = new SpotlessEclipseFramework(); - Runtime.getRuntime().addShutdownHook(new Thread(() -> { - try { - INSTANCE.shutdown(); - } catch (Exception e) { - //At shutdown everything is just done on best-efforts basis - } - })); - - config.registerServices(INSTANCE.getServiceConfig()); - - SpotlessEclipseCoreConfig coreConfig = new SpotlessEclipseCoreConfig(); - config.registerBundles(coreConfig); - INSTANCE.startCoreBundles(coreConfig); - - SpotlessEclipsePluginConfig pluginConfig = new SpotlessEclipsePluginConfig(); - config.activatePlugins(pluginConfig); - for (Class extension : pluginConfig.getExtensions()) { - INSTANCE.addExtension(extension); - } - for (BundleConfig.Entry plugin : pluginConfig.get()) { - INSTANCE.addPlugin(plugin.state, plugin.activator); - } - - config.customize(); - } - } - - private final Function registry; - private final BundleController controller; - - private SpotlessEclipseFramework() throws BundleException { - - controller = new BundleController(); - registry = (pluginBundle) -> { - return PluginRegistrar.register(pluginBundle); - }; - } - - void shutdown() { - controller.shutdown(); - } - - private SpotlessEclipseServiceConfig getServiceConfig() { - return controller.getServices(); - } - - private void addExtension(Class clazzInBundleJar) throws BundleException { - controller.addBundle(clazzInBundleJar, registry); - } - - private void addPlugin(int state, BundleActivator plugin) throws BundleException { - controller.addBundle(state, plugin, registry); - } - - private void startCoreBundles(SpotlessEclipseCoreConfig coreConfig) throws BundleException { - //The SAXParserFactory.class is required for parsing the plugin XML files - addMandatoryServiceIfMissing(SAXParserFactory.class, SAXParserFactory.newInstance()); - - /* - * Since org.eclipse.core.runtime version 3.15.300, the Eclipse bundle look-up is accomplished - * via the wiring framework, which requires starting the InternalPlatform. - * The internal platform initialization is customized by the services - * registered to the controller. - */ - InternalPlatform.getDefault().start(controller); - - for (BundleConfig.Entry coreBundle : coreConfig.get()) { - try { - BundleContext context = controller.createContext(coreBundle.state); - coreBundle.activator.start(context); - } catch (Exception e) { - throw new BundleException(String.format("Failed to start %s", coreBundle.activator.getClass().getName()), BundleException.ACTIVATOR_ERROR, e); - } - } - } - - private void addMandatoryServiceIfMissing(Class interfaceClass, S service) { - if (null == controller.getServiceReference(interfaceClass)) { - controller.getServices().add(interfaceClass, service); - } - } -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/SpotlessEclipsePluginConfig.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/SpotlessEclipsePluginConfig.java deleted file mode 100644 index 2569c12cd9..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/SpotlessEclipsePluginConfig.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; - -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleActivator; - -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework.DefaultPlugins; -import com.diffplug.spotless.extra.eclipse.base.osgi.BundleConfig; - -/** - * Configuration of plugins which shall be provided by the - * {@link SpotlessEclipseFramework}. - *

- * Plugins are registered via the Eclipse registry. Therefore they - * required a {@code plugin.xml} configuration description. - * Note that a plugin does not necessarily derive from the - * Eclipse {@link org.eclipse.core.runtime.Plugin} class. - *

- *

- * Some plugins are pure extensions without any activator. - * For resource and plugin information lookup, a class can be - * specified which is in the JAR containing the resources and - * plugin information. This lookup procedure also supports - * fat JAR lookups as described in the ReadMe.md. - *

- * @see org.eclipse.core.runtime.RegistryFactory - */ -public class SpotlessEclipsePluginConfig extends BundleConfig { - private final List> extensions; - - /** - * Don't instantiate and call {@link SpotlessEclipseConfig} directly. - * Registered plugins and extensions should only be instantiated once, since - * some still abusing singletons for access. - */ - SpotlessEclipsePluginConfig() { - extensions = new ArrayList<>(); - } - - /** Add an extension plugin, identified by a class of the plugin. */ - public void add(Class extensionClass) { - Objects.requireNonNull(extensionClass, "Plugin extension class must nor be null"); - extensions.add(extensionClass); - } - - /** Add a set of default bundles with their default states */ - public void add(Class... extensionClasses) { - Arrays.asList(extensionClasses).forEach(extensionClass -> add(extensionClass)); - } - - /** Returns the current configuration */ - public List> getExtensions() { - return extensions; - } - - @Override - public void applyDefault() { - add(SpotlessEclipseFramework.DefaultPlugins.createAll()); - } - - @Override - protected BundleActivator create(DefaultPlugins bundle) { - return bundle.create(); - } - - @Override - protected int getDefaultState(DefaultPlugins bundle) { - return Bundle.ACTIVE; - } - -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/SpotlessEclipseServiceConfig.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/SpotlessEclipseServiceConfig.java deleted file mode 100644 index f0b9a97cc4..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/SpotlessEclipseServiceConfig.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base; - -import static com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework.LINE_DELIMITER; - -import java.util.Map; -import java.util.function.BiFunction; - -import org.eclipse.core.runtime.content.IContentTypeManager; -import org.eclipse.core.runtime.preferences.IPreferencesService; -import org.eclipse.equinox.log.ExtendedLogReaderService; -import org.eclipse.equinox.log.ExtendedLogService; -import org.eclipse.osgi.service.datalocation.Location; -import org.eclipse.osgi.service.debug.DebugOptions; -import org.eclipse.osgi.service.environment.EnvironmentInfo; -import org.osgi.framework.ServiceException; -import org.osgi.service.log.LogLevel; - -import com.diffplug.spotless.extra.eclipse.base.service.*; - -/** - * Configuration/Provision of services which shall be provided by the {@link SpotlessEclipseFramework}. - *

- * The services provide basic functions/configuration to the {@link SpotlessEclipseFramework} bundles use the services. - * Hence the services can be used to customize the behavior of core bundles and plugins configured - * in the {@link SpotlessEclipseCoreConfig} and {@link SpotlessEclipsePluginConfig}. - *

- */ -public interface SpotlessEclipseServiceConfig { - - /** Sets property/preference value available to all bundles, plugins and services. */ - void set(String key, String value); - - /** Sets property/preference values available to all bundles, plugins and services. */ - default void set(Map properties) { - properties.forEach((k, v) -> this.set(k, v)); - } - - /** - * Add custom service to collection. - *

- * Only one service per interface is allowed. - * A service instance implementing multiple interfaces, can be added for each interface. - *

- * Please refer to the default method implementation for examples. - * - * @param interfaceClass Service interface - * @param service Service instance - * @throws ServiceException in case service has already been configured - */ - public void add(Class interfaceClass, S service) throws ServiceException; - - /** - * Spotless formatters should not be configured by environment variables, and - * they shall be OS independent. - * @throws ServiceException in case service has already been configured - */ - default public void hideEnvironment() throws ServiceException { - add(EnvironmentInfo.class, new HiddenEnvironment()); - } - - /** - * Eclipse provides means to lookup the file content type, e.g. by file name extensions. - * This possibility is not required by most Spotless formatters. - * @throws ServiceException in case service has already been configured - */ - default public void ignoreContentType() throws ServiceException { - add(IContentTypeManager.class, new NoContentTypeSpecificHandling()); - } - - /** - * Disable Eclipse internal debugging. - * @throws ServiceException in case service has already been configured - */ - default public void disableDebugging() throws ServiceException { - add(DebugOptions.class, new NoDebugging()); - } - - /** - * Ignore accesses of unsupported preference. - * @throws ServiceException in case service has already been configured - */ - default public void ignoreUnsupportedPreferences() throws ServiceException { - add(IPreferencesService.class, new NoEclipsePreferences()); - } - - /** - * Use temporary locations in case plugins require to store file. - * These files (for example workspace preferences) will be deleted - * as soon as the application terminates. - * @throws ServiceException in case service has already been configured - */ - default public void useTemporaryLocations() throws ServiceException { - add(Location.class, new TemporaryLocation()); - } - - /** - * In case the string which will be formatted does not contain any - * line delimiter (single line), Eclipse falls back to use the - * system property. - * Change the system default to the UNIX line separator as required - * by Spotless. - * @throws ServiceException in case service has already been configured - */ - default public void changeSystemLineSeparator() throws ServiceException { - System.setProperty("line.separator", LINE_DELIMITER); - } - - /** - * Adds Eclipse logging service Slf4J binding. - * @throws ServiceException in case service has already been configured - */ - default public void useSlf4J(String loggerName) { - useSlf4J(loggerName, (s, l) -> s); - } - - /** - * Adds Eclipse logging service Slf4J binding with a message customizer function. - * @throws ServiceException in case service has already been configured - */ - default public void useSlf4J(String loggerName, BiFunction messageCustomizer) throws ServiceException { - SingleSlf4JService slf4jServce = new SingleSlf4JService(loggerName, messageCustomizer); - add(ExtendedLogService.class, slf4jServce); - add(ExtendedLogReaderService.class, slf4jServce); - slf4jServce.debug("Initialized Eclipse logging service."); - } - - /** - * Applies the default configurations. - * @throws ServiceException in case a service has already been configured - */ - default public void applyDefault() throws ServiceException { - hideEnvironment(); - ignoreContentType(); - disableDebugging(); - ignoreUnsupportedPreferences(); - useTemporaryLocations(); - changeSystemLineSeparator(); - } -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/BundleConfig.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/BundleConfig.java deleted file mode 100644 index 8e4b12c8d0..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/BundleConfig.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.osgi; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Objects; - -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleActivator; - -/** A bundle configuration is given by its activator and the desired state */ -public abstract class BundleConfig> { - public static class Entry { - public final BundleActivator activator; - public final int state; - - public Entry(BundleActivator activator, int state) { - Objects.requireNonNull(activator, "activator"); - this.activator = activator; - this.state = state; - } - } - - private final List config; - - protected BundleConfig() { - config = new ArrayList(); - } - - /** - * Activate a bundle with a certain state. A non-active state is used by - * some bundles to allow a slim instantiation (for example in a headless - * Eclipse). - */ - public void add(BundleActivator activator, int state) { - config.add(new Entry(activator, state)); - } - - /** Returns the current configuration */ - public List get() { - return config; - } - - /** Activate a set of bundles with certain states */ - public void add(List config) { - this.config.addAll(config); - } - - /** Activate a bundle in active state, which is the nominal choice */ - public void add(BundleActivator activator) { - add(activator, Bundle.ACTIVE); - } - - /** Activate a set of bundles with in active state */ - public void add(Collection config) { - config.stream().forEach(entry -> add(entry)); - } - - /** Activate a default bundle with its default state */ - public void add(T bundle) { - add(create(bundle), getDefaultState(bundle)); - } - - /** Activate a set of default bundles with their default states */ - @SuppressWarnings("unchecked") - public void add(T... bundles) { - Arrays.asList(bundles).forEach(bundle -> add(bundle)); - } - - /** Activate a default bundle with a custom state */ - public void add(T bundle, int state) { - add(create(bundle), state); - } - - /** Applies the default configurations. */ - public abstract void applyDefault(); - - protected abstract BundleActivator create(T bundle); - - protected abstract int getDefaultState(T bundle); - -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/BundleController.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/BundleController.java deleted file mode 100644 index 3589bd7082..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/BundleController.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright 2016-2021 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.osgi; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; - -import org.eclipse.core.internal.jobs.JobManager; -import org.eclipse.osgi.internal.location.LocationHelper; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleActivator; -import org.osgi.framework.BundleContext; -import org.osgi.framework.BundleException; -import org.osgi.framework.Constants; -import org.osgi.framework.Filter; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; -import org.osgi.framework.wiring.FrameworkWiring; - -/** - * OSGi bundle controller allowing a minimal Eclipse platform setup - * by bypassing the Eclipse internal platform. - * - * Bundles are loaded by the same class loader, sharing the same context. - * Services do not provide individual properties, but making use of the framework properties. - * All bundles have a persistent state. The OSGi life-cycle is not supported. - */ -public final class BundleController implements StaticBundleContext { - private static final String ECLIPSE_LAUNCHER_SYMBOLIC_NAME = "org.eclipse.osgi"; - - private final SimpleBundle systemBundle; - private final Map properties; - private final ServiceCollection services; - private final BundleSet bundles; - - @SuppressWarnings("deprecation") - public BundleController() throws BundleException { - //OSGI locks are not required, since this framework does not allow changes after initialization. - System.setProperty(LocationHelper.PROP_OSGI_LOCKING, LocationHelper.LOCKING_NONE); - - this.properties = new HashMap(); - //Don't activate all plugin bundles. Activation is triggered by this controller where needed. - properties.put(org.eclipse.core.internal.runtime.InternalPlatform.PROP_ACTIVATE_PLUGINS, Boolean.toString(false)); - - /* - * Used to set-up an internal member of the Eclipse runtime FindSupport, - * which is used during resources look-up from different version of bundles. - * Since the concept of the Spotless Eclipse framework does not allow multiple versions - * for a bundle, this property is never used. - */ - properties.put(org.eclipse.core.internal.runtime.FindSupport.PROP_NL, ""); - - bundles = new BundleSet(); - systemBundle = new SimpleBundle(this, Bundle.ACTIVE); - bundles.add(systemBundle); - - services = new ServiceCollection(systemBundle, properties); - - //Eclipse core (InternalPlatform) still uses PackageAdmin for looking up bundles - EclipseBundleLookup bundleLookup = new EclipseBundleLookup(systemBundle, bundles); - services.add(org.osgi.service.packageadmin.PackageAdmin.class, bundleLookup); - services.add(FrameworkWiring.class, bundleLookup); - - //Redirect framework activator requests to the org.eclipse.osgi bundle to this instance. - bundles.add(new SimpleBundle(systemBundle, ECLIPSE_LAUNCHER_SYMBOLIC_NAME, Bundle.ACTIVE)); - FrameworkBundleRegistry.initialize(this); - } - - /** - * Stop {@link org.eclipse.core.internal.jobs.JobManager} worker pool - * and clean up resources of Spotless bundles and services. - * - * @implNote The {@link org.osgi.framework.BundleActivator}s - * are not stopped, since they are not completely started. - * For example services are suppressed by {@link StaticBundleContext}. - */ - public void shutdown() { - JobManager.shutdown(); - bundles.getAll().forEach(b -> { - try { - b.stop(); - } catch (IllegalStateException | BundleException e) { - //Stop on best effort basis - } - }); - services.stop(); - } - - public ServiceCollection getServices() { - return services; - } - - /** Adds and starts a new bundle. */ - public void addBundle(int bundleState, BundleActivator activator, Function register) throws BundleException { - BundleContext contextFacade = new BundleControllerContextFacade(this, bundleState, activator); - bundles.add(contextFacade.getBundle()); - BundleException exception = register.apply(contextFacade.getBundle()); - if (null != exception) - throw exception; - try { - activator.start(contextFacade); - } catch (Exception e) { - throw new BundleException(String.format("Failed do start %s.", activator.getClass().getName()), BundleException.ACTIVATOR_ERROR, e); - } - } - - /** Adds new bundel whithout activator (e.g. used for only for extensions) */ - public void addBundle(Class clazzInBundleJar, Function register) throws BundleException { - BundleContext contextFacade = new BundleControllerContextFacade(this, clazzInBundleJar); - bundles.add(contextFacade.getBundle()); - BundleException exception = register.apply(contextFacade.getBundle()); - if (null != exception) - throw exception; - } - - /** Creates a context with an individual state if required. */ - public BundleContext createContext(int state) { - if (state == systemBundle.getState()) { - return this; - } - return new BundleControllerContextFacade(systemBundle, state); - } - - @Override - public String getProperty(String key) { - return properties.get(key); - } - - @Override - public Bundle getBundle() { - return systemBundle; - } - - @Override - public Bundle[] getBundles() { - Collection shallowCopy = bundles.getAll(); - return shallowCopy.toArray(new Bundle[shallowCopy.size()]); - } - - @Override - public Bundle getBundle(long id) { - return bundles.get(id); - } - - @Override - public Bundle getBundle(String location) { - if (Constants.SYSTEM_BUNDLE_LOCATION.equals(location)) { - return systemBundle; - } - return null; - } - - @Override - public ServiceReference[] getAllServiceReferences(String clazz, String filter) throws InvalidSyntaxException { - //Filters are based on class names - String interfaceClassName = (null == clazz) ? filter : clazz; - return services.getReferences(interfaceClassName); - } - - @Override - public S getService(ServiceReference reference) { - return services.getService(reference); - } - - @Override - public Filter createFilter(String filter) throws InvalidSyntaxException { - return services.createFilter(filter); - } - - /** - * Facade providing access to an existing controller for a bundle - *

- * All bundles have unrestricted access to the framework services and properties. - * However, each bundle and its context needs to maintain its individual - * symbolic name for look-up. - *

- */ - private static class BundleControllerContextFacade implements StaticBundleContext { - - private final BundleContext context; - private final Bundle bundle; - - private BundleControllerContextFacade(BundleContext context, int bundleState, BundleActivator activator) throws BundleException { - this.context = context; - bundle = new SimpleBundle(this, bundleState, activator); - } - - private BundleControllerContextFacade(BundleContext context, Class clazzInBundleJar) throws BundleException { - this.context = context; - bundle = new SimpleBundle(this, clazzInBundleJar); - } - - /** Fakes an individual bundle state */ - private BundleControllerContextFacade(SimpleBundle bundle, int bundleState) { - this.context = bundle.getBundleContext(); - this.bundle = new SimpleBundle(bundle, bundleState); - } - - @Override - public String getProperty(String key) { - return context.getProperty(key); - } - - @Override - public Bundle getBundle() { - return bundle; - } - - @Override - public Bundle[] getBundles() { - return context.getBundles(); - } - - @Override - public Bundle getBundle(long id) { - return context.getBundle(id); - } - - @Override - public Bundle getBundle(String location) { - return context.getBundle(location); - } - - @Override - public ServiceReference[] getAllServiceReferences(String clazz, String filter) throws InvalidSyntaxException { - return context.getAllServiceReferences(clazz, filter); - } - - @Override - public S getService(ServiceReference reference) { - return context.getService(reference); - } - - @Override - public Filter createFilter(String filter) throws InvalidSyntaxException { - return context.createFilter(filter); - } - - } - -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/BundleSet.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/BundleSet.java deleted file mode 100644 index 59e15929fd..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/BundleSet.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.osgi; - -import java.util.Collection; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleException; - -/** Thread save set of bundles, whereas each bundle has a unique symbolic name and a unique ID. */ -class BundleSet { - private final Map symbolicName2bundle; - private final Map id2bundle; - - BundleSet() { - symbolicName2bundle = new ConcurrentHashMap(); - id2bundle = new ConcurrentHashMap(); - } - - /** Get all bundles in collection */ - Collection getAll() { - return symbolicName2bundle.values(); - } - - /** Get bundle by symbolic name or null if collection does not contain the corresponding bundle. */ - Bundle get(String symbolicName) { - return symbolicName2bundle.get(symbolicName); - } - - /** Get bundle by its ID or null if collection does not contain the corresponding bundle. */ - Bundle get(long id) { - return id2bundle.get(id); - } - - /** Add bundle to collection. - * @throws BundleException */ - void add(Bundle bundle) throws BundleException { - Bundle existingBundle = symbolicName2bundle.put(bundle.getSymbolicName(), bundle); - if (null != existingBundle) { - throw new BundleException( - String.format("Bundle '%s' (ID: %d) is already part of collection with ID %d.", - bundle.getSymbolicName(), bundle.getBundleId(), existingBundle.getBundleId()), - BundleException.DUPLICATE_BUNDLE_ERROR); - } - Bundle bundleWithSameID = id2bundle.put(bundle.getBundleId(), bundle); - if (null != bundleWithSameID) { - throw new BundleException( - String.format("Bundle ID '%d' for '%s' is already used by '%s'.", - bundle.getBundleId(), - bundle.getSymbolicName(), - bundleWithSameID.getSymbolicName()), - BundleException.DUPLICATE_BUNDLE_ERROR); - } - } - -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/EclipseBundleLookup.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/EclipseBundleLookup.java deleted file mode 100644 index 0ec091a58b..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/EclipseBundleLookup.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.osgi; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.eclipse.osgi.internal.framework.FilterImpl; -import org.osgi.framework.Bundle; -import org.osgi.framework.FrameworkListener; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.namespace.IdentityNamespace; -import org.osgi.framework.wiring.BundleCapability; -import org.osgi.framework.wiring.FrameworkWiring; -import org.osgi.resource.Namespace; -import org.osgi.resource.Requirement; -import org.osgi.service.packageadmin.ExportedPackage; -import org.osgi.service.packageadmin.PackageAdmin; -import org.osgi.service.packageadmin.RequiredBundle; - -/** - * - * {@link PackageAdmin} and {@link FrameworkWiring} service for bundle look-up. - *

- * The wiring information will always claim that all required bundles are present, since - * Spotlss does on purpose not provide all dependencies requested by plugins, since - * only small parts of the plugins are used. - * Removal and addition requests for bundles will always claim that there is nothing to do. - *

- * PackageAdmin interface is deprecated, but might still be used by bundles. - * It is kept for backward compatibility until removed from Eclipse. - */ -@SuppressWarnings("deprecation") -class EclipseBundleLookup implements FrameworkWiring, PackageAdmin { - - private static final Set OSGI_KEYS_FOR_SYMBOLIC_NAMES = Collections.unmodifiableSet(Stream.of(IdentityNamespace.IDENTITY_NAMESPACE, IdentityNamespace.TYPE_BUNDLE).collect(Collectors.toSet())); - private final Bundle systemBundle; - private final BundleSet bundles; - - EclipseBundleLookup(final Bundle systemBundle, final BundleSet bundles) { - this.systemBundle = systemBundle; - this.bundles = bundles; - } - - @Override - @Deprecated - public ExportedPackage[] getExportedPackages(Bundle bundle) { - return null; - } - - @Override - @Deprecated - public ExportedPackage[] getExportedPackages(String name) { - return null; - } - - @Override - @Deprecated - public ExportedPackage getExportedPackage(String name) { - return null; - } - - @Override - @Deprecated - public void refreshPackages(Bundle[] bundles) {} - - @Override - public boolean resolveBundles(Bundle[] bundles) { - return true; //All required bundles can be considered available (to be confirmed by UT) - } - - @Override - public RequiredBundle[] getRequiredBundles(String symbolicName) { - return null; // No unresolved required bundles exist - } - - @Override - public Bundle[] getBundles(String symbolicName, String versionRange) { - //Bundles with different versions cannot be supported due to the usage of same class-loader - Bundle bundle = bundles.get(symbolicName); - return (null == bundle) ? null : new Bundle[]{bundle}; - } - - @Override - public Bundle[] getFragments(Bundle bundle) { - return null; //No fragments - } - - @Override - public Bundle[] getHosts(Bundle bundle) { - return null; //No fragments - } - - @Override - public Bundle getBundle(@SuppressWarnings("rawtypes") Class clazz) { - return bundles.get(clazz.getName()); - } - - @Override - public int getBundleType(Bundle bundle) { - return 0; //No fragments - } - - @Override - public Bundle getBundle() { - return systemBundle; - } - - @Override - public void refreshBundles(Collection bundles, FrameworkListener... listeners) { - //Spotless bundles cannot be loaded dynamically - } - - @Override - public boolean resolveBundles(Collection bundles) { - return true; - } - - @Override - public Collection getRemovalPendingBundles() { - return Collections.emptyList(); //Nothing to remove - } - - @Override - public Collection getDependencyClosure(Collection bundles) { - return Collections.emptyList(); //No dependencies - } - - @Override - public Collection findProviders(Requirement requirement) { - // requirement must not be null (according to interface description)! - String filterSpec = requirement.getDirectives().get(Namespace.REQUIREMENT_FILTER_DIRECTIVE); - if (null == filterSpec) { - throw new IllegalArgumentException("Requirement filter diretive '" + Namespace.REQUIREMENT_FILTER_DIRECTIVE + "' not found."); - } - try { - FilterImpl requirementFilter = FilterImpl.newInstance(filterSpec); - Collection requiredSymbolicNames = getRequestedSymbolicNames(requirementFilter); - Collection capabilities = new ArrayList(requiredSymbolicNames.size()); - requiredSymbolicNames.forEach(symbolicName -> { - Bundle bundle = bundles.get(symbolicName); - if (bundle != null) { - capabilities.add(new SimpleBundleCapability(bundle)); - } - }); - return capabilities; - } catch (InvalidSyntaxException e) { - throw new IllegalArgumentException("Filter specifiation invalid:\n" + filterSpec, e); - } - } - - /** - * Simplified parser irgnoreing the version. - * Parser is incomplete since it ignores the filter operation. - * It basicall implements the bespoke way Eclipse maps its old style bundle handling to OSGI. - */ - private static Collection getRequestedSymbolicNames(FilterImpl filter) { - List symbolicNames = filter.getStandardOSGiAttributes().entrySet().stream().filter(entry -> OSGI_KEYS_FOR_SYMBOLIC_NAMES.contains(entry.getKey())).map(entry -> entry.getValue()).collect(Collectors.toList()); - filter.getChildren().forEach(childFilter -> { - symbolicNames.addAll(getRequestedSymbolicNames(childFilter)); - }); - return symbolicNames; - } -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/FrameworkBundleRegistry.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/FrameworkBundleRegistry.java deleted file mode 100644 index 18d5c4bed4..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/FrameworkBundleRegistry.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2016-2020 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.osgi; - -import java.util.Optional; - -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleException; -import org.osgi.framework.connect.FrameworkUtilHelper; - -/** - * Framework bundle registry service for bundles which do not come with an activator. - * - * Instead of returning the system bundle, a new bundle is created - * to provide an individual resource accessor, so that - * the correct JAR is searched for relative resource paths. - * Note that this can also be used to override - * original resources by providing a resource in the - * corresponding fat JAR location. - */ -public class FrameworkBundleRegistry implements FrameworkUtilHelper { - static BundleController INSTANCE = null; - - static void initialize(BundleController bundleController) { - if (INSTANCE != null) { - throw new RuntimeException(FrameworkBundleRegistry.class.getName() + " already initialized."); - } - INSTANCE = bundleController; - } - - @Override - public Optional getBundle(Class classFromBundle) { - try { - return Optional.of(new SimpleBundle(INSTANCE, classFromBundle)); - } catch (BundleException e) { - //If the class cannot be associated to a JAR or fat JAR resource location, just retun null - } - return Optional.empty(); - } -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/ResourceAccessor.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/ResourceAccessor.java deleted file mode 100644 index b75a2835ac..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/ResourceAccessor.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2016-2021 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.osgi; - -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Enumeration; -import java.util.jar.JarFile; -import java.util.jar.Manifest; - -import org.eclipse.osgi.internal.debug.Debug; -import org.eclipse.osgi.storage.bundlefile.BundleEntry; -import org.eclipse.osgi.storage.bundlefile.BundleFile; -import org.eclipse.osgi.storage.bundlefile.DirBundleFile; -import org.eclipse.osgi.storage.bundlefile.ZipBundleFile; -import org.eclipse.osgi.util.ManifestElement; -import org.osgi.framework.BundleException; -import org.osgi.framework.Constants; - -import com.diffplug.spotless.extra.eclipse.base.service.NoDebugging; - -/** - * Helper to access resources. - *

- * Spotless Eclipse formatters using in many cases fat JARs, compressing multiple - * bundles within one JAR. Their resources can get overridden in a fat JAR. - * Hence they provided under the bundle activators path name. - *

- *

- * The resource overriding prevention is required in case the Spotless - * Eclipse formatters integrate third party JARs, not available via M2. - * Additionally it can be used in case existing third party Eclipse plugins - * are mocked by Spotless, requiring the provision of multiple customized - * plugin information (META-INF, plugin.xml) within one JAR. - *

- */ -class ResourceAccessor { - private static final Debug NO_DEBUGGING = new Debug(new NoDebugging()); - private final String fatJarResourcePath; - private final BundleFile bundleFile; - - /** Resources are located in the SpotlessFramework JAR */ - ResourceAccessor() throws BundleException { - this(ResourceAccessor.class); - } - - /** Resources are located in the JAR of the given class - * @throws BundleException */ - ResourceAccessor(Class clazz) throws BundleException { - String bundleObjPath = clazz.getName(); - fatJarResourcePath = bundleObjPath.substring(0, bundleObjPath.lastIndexOf('.')); - try { - bundleFile = getBundlFile(clazz); - } catch (BundleException e) { - throw new BundleException(String.format("Failed to locate resources for bundle '%s'.", clazz.getName()), e); - } - } - - private static BundleFile getBundlFile(Class clazz) throws BundleException { - URI objUri = getBundleUri(clazz); - File jarOrDirectory = new File(objUri.getPath()); - if (!(jarOrDirectory.exists() && jarOrDirectory.canRead())) { - throw new BundleException(String.format("Path '%s' for '%s' is not accessible exist on local file system.", objUri, clazz.getName()), BundleException.READ_ERROR); - } - try { - return jarOrDirectory.isDirectory() ? new DirBundleFile(jarOrDirectory, false) : new ZipBundleFile(jarOrDirectory, null, null, NO_DEBUGGING, false); - } catch (IOException e) { - throw new BundleException(String.format("Cannot access bundle at '%s'.", jarOrDirectory), BundleException.READ_ERROR, e); - } - } - - private static URI getBundleUri(Class clazz) throws BundleException { - try { - URL objUrl = clazz.getProtectionDomain().getCodeSource().getLocation(); - return objUrl.toURI(); - } catch (NullPointerException e) { - //No bunlde should be used for RT classes lookup. See also org.eclipse.core.runtime.PerformanceStats. - throw new BundleException(String.format("No code source can be located for class '%s'. Class is probably not within a bundle, but part of the RT.", clazz.getName()), BundleException.READ_ERROR, e); - } catch (SecurityException e) { - throw new BundleException(String.format("Access to class '%s' is denied.", clazz.getName()), BundleException.READ_ERROR, e); - } catch (URISyntaxException e) { - throw new BundleException(String.format("Path for '%s' is invalid.", clazz.getName()), BundleException.READ_ERROR, e); - } - } - - /** Get the manifest name from the resources. */ - String getManifestName() throws BundleException { - URL manifestUrl = getEntry(JarFile.MANIFEST_NAME); - if (null != manifestUrl) { - try { - Manifest manifest = new Manifest(manifestUrl.openStream()); - String headerValue = manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME); - if (null == headerValue) { - throw new BundleException(String.format("Symbolic values not found in '%s'.", manifestUrl), BundleException.MANIFEST_ERROR); - } - ManifestElement[] elements = ManifestElement.parseHeader(Constants.BUNDLE_SYMBOLICNAME, headerValue); - if (null == elements) { - throw new BundleException(String.format("Symbolic name not found '%s'. Value is '%s'.", manifestUrl, headerValue), BundleException.MANIFEST_ERROR); - } - //The parser already checked that at least one value exists - return elements[0].getValueComponents()[0]; - - } catch (IOException e) { - throw new BundleException(String.format("Failed to parse Manifest '%s' in '%s'.", manifestUrl, bundleFile.toString()), BundleException.MANIFEST_ERROR, e); - } - } - throw new BundleException(String.format("'%s' in '%s' not found. Tried also fat JAR location '%s'.", JarFile.MANIFEST_NAME, bundleFile.toString(), fatJarResourcePath), BundleException.MANIFEST_ERROR); - } - - /** Get resource URL for relative path, or null if the path is not present */ - URL getEntry(String path) { - BundleEntry entry = bundleFile.getEntry(getFatJarPath(path)); - if (null == entry) { - entry = bundleFile.getEntry(path); - } - return null == entry ? null : entry.getLocalURL(); - } - - /** - * Enumeration of Strings that indicate the paths found or null if the path does not exist. - */ - Enumeration getEntries(String path) { - Enumeration entries = bundleFile.getEntryPaths(getFatJarPath(path)); - if (null == entries) { - entries = bundleFile.getEntryPaths(path); - } - return entries; - } - - private String getFatJarPath(String path) { - return fatJarResourcePath + "/" + path; - } - -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/ServiceCollection.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/ServiceCollection.java deleted file mode 100644 index 2ea67f712f..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/ServiceCollection.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright 2016-2021 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.osgi; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Dictionary; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.eclipse.osgi.internal.framework.DTOBuilder; -import org.osgi.framework.Bundle; -import org.osgi.framework.Filter; -import org.osgi.framework.ServiceException; -import org.osgi.framework.ServiceReference; -import org.osgi.framework.dto.ServiceReferenceDTO; - -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseServiceConfig; - -/** - * Collection of services. - * Eclipse service are not expected to hold any resources. Spotless services - * can implement AutoCloseable in case a resource release is required on shutdown. - * - * Note that the collection access is not thread save, since it is expected - * that the collection is completed before starting any bundles. - */ -public class ServiceCollection implements SpotlessEclipseServiceConfig { - private final Map> className2Service; - private final List servicesWithResources; - private final Bundle systemBundle; - private final Map properties; - - /** - * Collection of services - * @param systemBundle All services will belong to the system bundles - * @param All services share the same properties - */ - ServiceCollection(Bundle systemBundle, Map properties) { - className2Service = new HashMap>(); - servicesWithResources = new ArrayList<>(); - this.systemBundle = systemBundle; - this.properties = properties; - } - - void stop() { - servicesWithResources.stream().forEach(s -> { - try { - s.close(); - } catch (Exception e) { - //Stop on best effort basis - } - }); - } - - @Override - public void set(String key, String value) { - properties.put(key, value); - } - - @Override - public void add(Class interfaceClass, S service) throws ServiceException { - String className = interfaceClass.getName(); - if (null != className2Service.put(interfaceClass.getName(), new FrameworkServiceReference(className, service))) { - throw new ServiceException( - String.format("Service '%s' is already registered.", interfaceClass.getName()), ServiceException.FACTORY_ERROR); - } - if (service instanceof AutoCloseable) { - servicesWithResources.add((AutoCloseable) service); - } - } - - /** Creates filter object suitable to lookup service by its interface name. */ - Filter createFilter(String filterDescr) { - Optional serviceClassName = className2Service.keySet().stream().filter(serviceClazzName -> filterDescr.contains(serviceClazzName)).findFirst(); - return new ClassNameBasedFilter(serviceClassName); - } - - /** - * Get reference matching interface class name or all references - * @param interfaceClassName Class name filter. All references are returned in case filter is null - * @return Matching or all interfaces. If no interface is matching the filter, null is returned. - */ - ServiceReference[] getReferences(String interfaceClassName) { - if (null == interfaceClassName) { - Collection> allServices = className2Service.values(); - return allServices.toArray(new ServiceReference[allServices.size()]); - } - ServiceReference singleService = className2Service.get(interfaceClassName); - return (null == singleService) ? null : new ServiceReference[]{singleService}; - } - - /** - * Return service for reference if it belongs to the system bundle. - * @param reference Service reference - * @return null, if service does not belong to the system bundle - */ - S getService(ServiceReference reference) { - if (systemBundle == reference.getBundle()) { - return ((FrameworkServiceReference) reference).getService(); - } - return null; - } - - /** References to static services (not modifiable at run-time */ - private class FrameworkServiceReference implements ServiceReference { - - private final String className; - private final S service; - - private FrameworkServiceReference(String className, S service) { - this.className = className; - this.service = service; - } - - private S getService() { - return service; - } - - @Override - public java.lang.Object getProperty(String key) { - return properties.get(key); - } - - @Override - public String[] getPropertyKeys() { - return properties.keySet().toArray(new String[properties.size()]); - } - - @Override - public Bundle getBundle() { - return systemBundle; - } - - @Override - public Bundle[] getUsingBundles() { - return new Bundle[]{systemBundle}; - } - - @Override - public boolean isAssignableTo(Bundle bundle, String className) { - // Since only one class loader is used, same class come from the same package - return this.className.equals(className); - } - - @Override - @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(value = "EQ_COMPARETO_USE_OBJECT_EQUALS") - public int compareTo(Object reference) { - return (this == reference) ? 0 : 1; - } - - @Override - public Dictionary getProperties() { - return new Hashtable(properties); - } - - @SuppressWarnings("unchecked") - @Override - public A adapt(Class type) { - if (ServiceReferenceDTO.class.equals(type)) { - return (A) DTOBuilder.newServiceReferenceDTO(this); - } - return null; - } - - } - - /** - * Class name based service filter - *

- * Dictionary and capability look-ups are not supported and marked as deprecated. - */ - private class ClassNameBasedFilter implements Filter { - - private final static String NO_MATCH_CLASS_NAME = ""; - - private final String className; - - private ClassNameBasedFilter(Optional className) { - this.className = className.orElse(NO_MATCH_CLASS_NAME); - } - - @Override - public boolean match(ServiceReference reference) { - return reference.isAssignableTo(systemBundle, className); - } - - @Override - @Deprecated - public boolean match(Dictionary dictionary) { - throw new UnsupportedOperationException("Dictionary based service look-up is not supported."); - } - - @Override - @Deprecated - public boolean matchCase(Dictionary dictionary) { - throw new UnsupportedOperationException("Dictionary based service look-up is not supported."); - } - - @Override - @Deprecated - public boolean matches(Map map) { - throw new UnsupportedOperationException("Capability based service look-up is not supported."); - } - - @Override - public String toString() { - return className; - } - - } - -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/SimpleBundle.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/SimpleBundle.java deleted file mode 100644 index bdf8888b30..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/SimpleBundle.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.osgi; - -import java.net.URL; -import java.util.Enumeration; - -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleActivator; -import org.osgi.framework.BundleContext; -import org.osgi.framework.BundleException; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; - -/** Fixed state simple bundle. */ -class SimpleBundle implements StaticBundle, TemporaryBundle { - private final String name; - private final int state; - private final BundleContext context; - private final int id; - private final ResourceAccessor resources; - - /** System bundle corresponding to the SpotlessFramework JARs manifest */ - SimpleBundle(BundleContext context, int state) throws BundleException { - this(context, state, new ResourceAccessor()); - } - - /** System bundle for a dedicated bundle activator */ - SimpleBundle(BundleContext context, int state, BundleActivator activator) throws BundleException { - this(context, state, new ResourceAccessor(activator.getClass())); - } - - /** System bundle providing only extensions and therefore does not require an activator */ - SimpleBundle(BundleContext context, Class clazzInBundleJar) throws BundleException { - //These bundles are always active (means that resources have been resolved) - this(context, Bundle.ACTIVE, new ResourceAccessor(clazzInBundleJar)); - } - - /** Internal constructor */ - private SimpleBundle(BundleContext context, int state, ResourceAccessor resources) throws BundleException { - this.state = state; - this.context = context; - this.resources = resources; - id = context.getBundles().length; - name = resources.getManifestName(); - } - - /** Additional bundle with a different symbolic name and state */ - SimpleBundle(SimpleBundle master, String name, int state) { - this.name = name; - this.state = state; - context = master.context; - resources = master.resources; - id = context.getBundles().length; - } - - /** Bundle clone with a different state */ - SimpleBundle(SimpleBundle master, int state) { - this.state = state; - context = master.context; - resources = master.resources; - id = master.id; - name = master.name; - } - - @Override - public A adapt(Class type) { - /* - * The adaptation is currently used by the InternalPlugin to get the framework wiring - * implementation from the system bundle. - * The original purpose to provide more specialized access to the Bundle object, - * seems not be used by Eclipse at all. - * Hence the call is mapped to old-style Eclipse services. - */ - try { - - ServiceReference[] references = context.getAllServiceReferences(type.getName(), ""); - if ((null != references) && (0 != references.length)) { - if (1 != references.length) { - throw new IllegalArgumentException("Multiple services found for " + type.getName()); //In Spotless services should always be unique - } - Object obj = context.getService(references[0]); - try { - return type.cast(obj); - } catch (ClassCastException e) { - throw new IllegalArgumentException("Received unexpected class for reference filter " + type.getName(), e); - } - } - return null; - } catch (InvalidSyntaxException e) { - throw new IllegalArgumentException("Unexpected syntax exception", e); //Should never be thrown by Spotless bundle controller - } - } - - @Override - public int getState() { - return state; - } - - @Override - public long getBundleId() { - return id; - } - - @Override - public ServiceReference[] getRegisteredServices() { - try { - return context.getAllServiceReferences(null, null); - } catch (InvalidSyntaxException e) { - throw new RuntimeException(e); //Filter 'null' is valid for 'select all'. - } - } - - @Override - public String getSymbolicName() { - return name; - } - - @Override - public BundleContext getBundleContext() { - return context; - } - - @Override - public Enumeration getEntryPaths(String path) { - return resources.getEntries(path); - } - - @Override - public URL getEntry(String path) { - return resources.getEntry(path); - } - -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/SimpleBundleCapability.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/SimpleBundleCapability.java deleted file mode 100644 index 66f331807d..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/SimpleBundleCapability.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.osgi; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import org.osgi.framework.Bundle; -import org.osgi.framework.Version; -import org.osgi.framework.wiring.BundleCapability; -import org.osgi.framework.wiring.BundleRequirement; -import org.osgi.framework.wiring.BundleRevision; -import org.osgi.framework.wiring.BundleWiring; -import org.osgi.resource.Capability; -import org.osgi.resource.Requirement; - -/** - * Simplified bundle capability ignoring internal wiring and versions - *

- * Since multiple versions/implementations of bundles for the same - * capability is not supported a split of bundle capability and revision is not required. - */ -class SimpleBundleCapability implements BundleCapability, BundleRevision { - private final Bundle bundle; - - SimpleBundleCapability(Bundle bundle) { - this.bundle = bundle; - } - - @Override - public BundleRevision getRevision() { - return this; - } - - @Override - public String getNamespace() { - return this.getClass().getName(); //All bundles live in th same namespace - } - - @Override - public Map getDirectives() { - return Collections.emptyMap(); - } - - @Override - public Map getAttributes() { - return Collections.emptyMap(); - } - - @Override - public BundleRevision getResource() { - return this; - } - - @Override - public Bundle getBundle() { - return bundle; - } - - @Override - public String getSymbolicName() { - return bundle.getSymbolicName(); - } - - @Override - public Version getVersion() { - return bundle.getVersion(); - } - - @Override - public List getDeclaredCapabilities(String namespace) { - return Collections.emptyList(); - } - - @Override - public List getDeclaredRequirements(String namespace) { - return Collections.emptyList(); - } - - @Override - public int getTypes() { - return 0; //It does not matter whether this bunddle is a fragment of not since all bundles are initially provided - } - - @Override - public BundleWiring getWiring() { - return null; //No wiring information - } - - @Override - public List getCapabilities(String namespace) { - return Collections.emptyList(); - } - - @Override - public List getRequirements(String namespace) { - return Collections.emptyList(); - } - -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/StaticBundle.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/StaticBundle.java deleted file mode 100644 index 282804a332..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/StaticBundle.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.osgi; - -import java.io.InputStream; - -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleException; -import org.osgi.framework.ServiceReference; - -/** - * Unmodifiable bundle with a fixed life-cycle. - *

- * All state related modifications are ignored. - * Installation related methods (update/uninstall) are unsupported. - * Unsupported methods are marked as deprecated and causing an exception. - */ -public interface StaticBundle extends Bundle { - - @Override - default public void start(int options) throws BundleException {} - - @Override - default public void start() throws BundleException {} - - @Override - default public void stop(int options) throws BundleException {} - - @Override - default public void stop() throws BundleException {} - - @Override - @Deprecated - default public void update(InputStream input) throws BundleException { - update(); - } - - @Override - @Deprecated - default public void update() throws BundleException { - throw new UnsupportedOperationException("Bundle modifications are not supported."); - } - - @Override - @Deprecated - default public void uninstall() throws BundleException { - throw new UnsupportedOperationException("Bundles cannot be uninstalled."); - } - - @Override - default public long getLastModified() { - return 0; - } - - @Override - @Deprecated - default public String getLocation() { - throw new UnsupportedOperationException("Bundle lookup by location only required for installation/update."); - } - - @Override - default public ServiceReference[] getServicesInUse() { - return getRegisteredServices(); //There is no distinction between available services and services in use. - } - -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/StaticBundleContext.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/StaticBundleContext.java deleted file mode 100644 index 040ef98444..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/StaticBundleContext.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.osgi; - -import java.io.File; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Dictionary; -import java.util.Objects; -import java.util.stream.Collectors; - -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.BundleException; -import org.osgi.framework.BundleListener; -import org.osgi.framework.FrameworkListener; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceFactory; -import org.osgi.framework.ServiceListener; -import org.osgi.framework.ServiceObjects; -import org.osgi.framework.ServiceReference; -import org.osgi.framework.ServiceRegistration; - -/** - * Restriction of the {@link BundleContext} interface rejecting run-time provision of bundles. - * Services provided at run-time are ignored. - *

- * Multiple service instances per class are not supported, hence the services can be filtered by class name. - * Unsupported methods are marked as deprecated an causing an exception. - * Registration and removal of bundle/service listeners are ignored, since a run-time - * provision of bundles or services is not supported. - */ -interface StaticBundleContext extends BundleContext { - - @Override - @Deprecated - default public Bundle installBundle(String location, InputStream input) throws BundleException { - throw new UnsupportedOperationException("Run-time installation of bundles is not supported."); - } - - @Override - @Deprecated - default public Bundle installBundle(String location) throws BundleException { - throw new UnsupportedOperationException("Run-time installation of bundles is not supported."); - } - - @Override - default public void addServiceListener(ServiceListener listener, String filter) throws InvalidSyntaxException {} - - @Override - default public void addServiceListener(ServiceListener listener) {} - - @Override - default public void removeServiceListener(ServiceListener listener) {} - - @Override - default public void addBundleListener(BundleListener listener) {} - - @Override - default public void removeBundleListener(BundleListener listener) {} - - @Override - default public void addFrameworkListener(FrameworkListener listener) {} - - @Override - default public void removeFrameworkListener(FrameworkListener listener) {} - - @Override - @Deprecated - default public ServiceRegistration registerService(String[] clazzes, Object service, Dictionary properties) { - throw new UnsupportedOperationException("Run-time provision of services is not supported."); - } - - @Override - @Deprecated - default public ServiceRegistration registerService(String clazz, Object service, Dictionary properties) { - return null; //Ignore additional services - } - - @Deprecated - @Override - default public ServiceRegistration registerService(Class clazz, S service, Dictionary properties) { - return null; //Ignore additional services - } - - @Override - @Deprecated - default public ServiceRegistration registerService(Class clazz, ServiceFactory factory, Dictionary properties) { - return null; //Ignore additional services - } - - @SuppressWarnings("unchecked") - @Override - default public ServiceReference getServiceReference(Class clazz) { - Objects.requireNonNull(clazz, "The class under whose name the service was registered must not be null."); - return (ServiceReference) getServiceReference(clazz.getName()); - } - - @Override - default public ServiceReference getServiceReference(String clazz) { - Objects.requireNonNull(clazz, "The class under whose name the service was registered must not be null."); - ServiceReference[] references; - try { - references = getServiceReferences(clazz, null); - } catch (InvalidSyntaxException e) { - throw new RuntimeException(e); //null is always valid - } - return (null == references) ? null : references[0]; - } - - @Override - default public ServiceReference[] getServiceReferences(String clazz, String filter) throws InvalidSyntaxException { - return getAllServiceReferences(clazz, filter); //Services are always considered compatible - } - - @Override - default public ServiceReference[] getAllServiceReferences(String clazz, String filter) throws InvalidSyntaxException { - //Filters are based on class names - ServiceReference reference = (null == clazz) ? getServiceReference(filter) : getServiceReference(clazz); - return (reference == null) ? null : new ServiceReference[]{reference}; - } - - @SuppressWarnings("unchecked") - @Override - default public Collection> getServiceReferences(Class clazz, String filter) throws InvalidSyntaxException { - ServiceReference[] references = getServiceReferences(clazz.getName(), filter); - Collection> result = new ArrayList>(0); - if (null != references) { - result = Arrays.stream(references).map(r -> (ServiceReference) r).collect(Collectors.toList()); - } - return result; - } - - @Override - default public boolean ungetService(ServiceReference reference) { - return true; //Services are persistent and never unregistered - } - - @Override - @Deprecated - default public ServiceObjects getServiceObjects(ServiceReference reference) { - throw new UnsupportedOperationException("Service specific objects are not supported."); - } - - @Override - @Deprecated - default public File getDataFile(String filename) { - throw new UnsupportedOperationException("Persistent data storage provision is handled by the Location service."); - } - -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/TemporaryBundle.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/TemporaryBundle.java deleted file mode 100644 index 24cfadc8c2..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/TemporaryBundle.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.osgi; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.security.cert.X509Certificate; -import java.util.Dictionary; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.osgi.framework.Bundle; -import org.osgi.framework.Version; - -/** - * Temporary bundle loaded by common class loader and added temporarily. - * It shall not be recognized as a Eclipse plugin (providing plugin.xml) - * but as a mere OSGI bundle. - *

- * META data look-ups are not supported (only required for Eclipse plugins). - * Entry look-up in the bundle space (findEntries) is not supported. - * Installation related methods (update/uninstall) are not supported. - * Unsupported methods are marked as deprecated and causing an exception. - */ -public interface TemporaryBundle extends Bundle { - - @Override - default public Version getVersion() { - return Version.emptyVersion; //Cannot support multiple version using single class loader. - } - - @Override - default public int compareTo(Bundle o) { - //Symbolic name is sufficient to distinguish bundles - return getSymbolicName().compareTo(o.getSymbolicName()); - } - - @Override - default public A adapt(Class type) { - return null; //Adaptation is not successful - } - - @Override - @Deprecated - default public Dictionary getHeaders() { - throw new UnsupportedOperationException("Bundle META information is not available."); - } - - @Override - @Deprecated - default public Dictionary getHeaders(String locale) { - return getHeaders(); - } - - @Override - default public URL getResource(String name) { - return getClass().getClassLoader().getResource(name); - } - - @Override - default public Enumeration getResources(String name) throws IOException { - return getClass().getClassLoader().getResources(name); - } - - @Override - @Deprecated - default public Enumeration findEntries(String path, String filePattern, boolean recurse) { - return null; //Local JAR look-up are not supported per default - } - - @Override - default public Class loadClass(String name) throws ClassNotFoundException { - return getClass().getClassLoader().loadClass(name); - } - - @Override - default public boolean hasPermission(Object permission) { - return true; //Dedicated permissions are not supported - } - - @Override - default public Map> getSignerCertificates(int signersType) { - return new HashMap>(0); //Bundle is not signed - } - - @Override - default public File getDataFile(String filename) { - return null; //No file system support for persistent files - } - -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/runtime/PluginRegistrar.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/runtime/PluginRegistrar.java deleted file mode 100644 index cdee9893dc..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/runtime/PluginRegistrar.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.runtime; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.PropertyResourceBundle; -import java.util.ResourceBundle; - -import org.eclipse.core.internal.registry.ExtensionRegistry; -import org.eclipse.core.runtime.IContributor; -import org.eclipse.core.runtime.IExtensionRegistry; -import org.eclipse.core.runtime.RegistryFactory; -import org.eclipse.core.runtime.spi.RegistryContributor; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleException; - -/** Registers Eclipse plugins at runtime based on a loaded bundle. - *

- * Note that the functionality provided by this class uses incubation features of the Eclipse core. - */ -public class PluginRegistrar { - private static final String PLUGIN_XML = "plugin.xml"; - private static final String PLUGIN_PROPERTIES = "plugin.properties"; - - public static BundleException register(Bundle bundle) { - PluginRegistrar registrar = new PluginRegistrar(bundle); - try { - registrar.register(); - } catch (BundleException e) { - return e; - } - return null; - } - - private final Bundle bundle; - - private PluginRegistrar(Bundle bundle) { - this.bundle = bundle; - } - - private void register() throws BundleException { - IExtensionRegistry reg = RegistryFactory.getRegistry(); - Object registryUser = ((ExtensionRegistry) reg).getTemporaryUserToken(); - if (!reg.addContribution(getStreamForEntry(PLUGIN_XML), createContributor(), false, null, getPluginProperties(), registryUser)) { - throw new BundleException("Could not add plugin: " + bundle.getSymbolicName(), BundleException.REJECTED_BY_HOOK); - } - } - - private IContributor createContributor() { - return new RegistryContributor( - Long.toString(bundle.getBundleId()), - bundle.getSymbolicName(), - // Local host - null, - null); - } - - private ResourceBundle getPluginProperties() throws BundleException { - //Some plugins, like the org.codehaus.groovy.eclipse.core, do not provide a property file. - InputStream is = entryExists(PLUGIN_PROPERTIES) ? getStreamForEntry(PLUGIN_PROPERTIES) : new ByteArrayInputStream(new byte[0]); - try { - return new PropertyResourceBundle(is); - } catch (IOException e) { - throw new BundleException(String.format("Bund resource '%s' is not encoded with ISO-8859-1.", PLUGIN_PROPERTIES), BundleException.MANIFEST_ERROR, e); - } - } - - private InputStream getStreamForEntry(String path) throws BundleException { - try { - return getEntry(path).openStream(); - } catch (IOException e) { - throw new BundleException(String.format("Cannot access mandatory resource '%s'.", path), BundleException.MANIFEST_ERROR, e); - } - } - - private URL getEntry(String path) throws BundleException { - URL url = bundle.getEntry(path); - if (null == url) { - throw new BundleException(String.format("Cannot find mandatory resource '%s'.", path), BundleException.MANIFEST_ERROR); - } - return url; - } - - private boolean entryExists(String path) { - return null != bundle.getEntry(path); - } -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/service/HiddenEnvironment.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/service/HiddenEnvironment.java deleted file mode 100644 index 8c9c1a6c91..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/service/HiddenEnvironment.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.service; - -import java.util.Locale; - -import org.eclipse.osgi.service.environment.Constants; -import org.eclipse.osgi.service.environment.EnvironmentInfo; - -/** Empty default Eclipse environment. No system information is accessible. */ -public class HiddenEnvironment implements EnvironmentInfo { - - @Override - public String[] getCommandLineArgs() { - return new String[0]; - } - - @Override - public String[] getFrameworkArgs() { - return new String[0]; - } - - @Override - public String[] getNonFrameworkArgs() { - return new String[0]; - } - - @Override - public String getOSArch() { - return System.getProperty("os.arch"); - } - - @Override - public String getNL() { - return Locale.getDefault().getLanguage(); - } - - @Override - public String getOS() { - return Constants.OS_UNKNOWN; - } - - @Override - public String getWS() { - return null; //No window system - } - - @Override - public boolean inDebugMode() { - return false; - } - - @Override - public boolean inDevelopmentMode() { - return false; - } - - @Override - public String setProperty(String key, String value) { - return value; //Launcher information is not stored - } - - @Override - public String getProperty(String key) { - return null; //Launcher information/configuration is not required - } - -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/service/NoContentTypeSpecificHandling.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/service/NoContentTypeSpecificHandling.java deleted file mode 100644 index 87e1f2f91b..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/service/NoContentTypeSpecificHandling.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.service; - -import java.io.IOException; -import java.io.InputStream; -import java.io.Reader; - -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.QualifiedName; -import org.eclipse.core.runtime.content.IContentDescription; -import org.eclipse.core.runtime.content.IContentType; -import org.eclipse.core.runtime.content.IContentTypeManager; -import org.eclipse.core.runtime.content.IContentTypeMatcher; -import org.eclipse.core.runtime.preferences.IScopeContext; - -/** No content type specific handling is supported. */ -public class NoContentTypeSpecificHandling implements IContentTypeManager { - - @Override - public IContentType findContentTypeFor(InputStream contents, String fileName) throws IOException { - return null; - } - - @Override - public IContentType findContentTypeFor(String fileName) { - return null; - } - - @Override - public IContentType[] findContentTypesFor(InputStream contents, String fileName) throws IOException { - return null; - } - - @Override - public IContentType[] findContentTypesFor(String fileName) { - return null; - } - - @Override - public IContentDescription getDescriptionFor(InputStream contents, String fileName, QualifiedName[] options) throws IOException { - return null; - } - - @Override - public IContentDescription getDescriptionFor(Reader contents, String fileName, QualifiedName[] options) throws IOException { - return null; - } - - @Override - public void addContentTypeChangeListener(IContentTypeChangeListener listener) {} - - @Override - public IContentType[] getAllContentTypes() { - return null; - } - - @Override - public IContentType getContentType(String contentTypeIdentifier) { - return null; - } - - @Override - public IContentTypeMatcher getMatcher(ISelectionPolicy customPolicy, IScopeContext context) { - return null; - } - - @Override - public void removeContentTypeChangeListener(IContentTypeChangeListener listener) { - - } - - @Override - public IContentType addContentType(String contentTypeIdentifier, String name, IContentType baseType) - throws CoreException { - return null; - } - - @Override - public void removeContentType(String contentTypeIdentifier) throws CoreException {} - -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/service/NoDebugging.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/service/NoDebugging.java deleted file mode 100644 index 095be889e9..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/service/NoDebugging.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.service; - -import java.io.File; -import java.util.Map; - -import org.eclipse.osgi.service.debug.DebugOptions; -import org.eclipse.osgi.service.debug.DebugTrace; - -/** No debugging shall be performed */ -public class NoDebugging implements DebugOptions { - - @Override - public boolean getBooleanOption(String option, boolean defaultValue) { - return false; - } - - @Override - public String getOption(String option) { - return null; - } - - @Override - public String getOption(String option, String defaultValue) { - return null; - } - - @Override - public int getIntegerOption(String option, int defaultValue) { - return 0; - } - - @Override - public Map getOptions() { - return null; - } - - @Override - public void setOption(String option, String value) {} - - @Override - public void setOptions(Map options) {} - - @Override - public void removeOption(String option) {} - - @Override - public boolean isDebugEnabled() { - return false; - } - - @Override - public void setDebugEnabled(boolean value) {} - - @Override - public void setFile(File newFile) {} - - @Override - public File getFile() { - return null; - } - - @Override - public DebugTrace newDebugTrace(String bundleSymbolicName) { - return null; - } - - @Override - public DebugTrace newDebugTrace(String bundleSymbolicName, Class traceEntryClass) { - return null; - } - -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/service/NoEclipsePreferences.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/service/NoEclipsePreferences.java deleted file mode 100644 index 7f6bed44ed..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/service/NoEclipsePreferences.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.service; - -import java.io.InputStream; -import java.io.OutputStream; - -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.preferences.DefaultScope; -import org.eclipse.core.runtime.preferences.IEclipsePreferences; -import org.eclipse.core.runtime.preferences.IExportedPreferences; -import org.eclipse.core.runtime.preferences.IPreferenceFilter; -import org.eclipse.core.runtime.preferences.IPreferencesService; -import org.eclipse.core.runtime.preferences.IScopeContext; -import org.osgi.service.prefs.Preferences; - -/** - * The formatters dependents on plugins which access Eclipse core functionality, - * configurable by preferences. This functionality is never used by the formatters itself. - * Hence modifications are ignored and default values are provided on request. - */ -public class NoEclipsePreferences implements IPreferencesService { - private static final String UNUSED = "unused"; - - @Override - public IEclipsePreferences getRootNode() { - //Return value is not effectively used. - return DefaultScope.INSTANCE.getNode(UNUSED); - } - - @Override - public String get(String key, String defaultValue, Preferences[] nodes) { - return null; - } - - @Override - public boolean getBoolean(String qualifier, String key, boolean defaultValue, IScopeContext[] contexts) { - return false; - } - - @Override - public byte[] getByteArray(String qualifier, String key, byte[] defaultValue, IScopeContext[] contexts) { - return null; - } - - @Override - public double getDouble(String qualifier, String key, double defaultValue, IScopeContext[] contexts) { - return 0; - } - - @Override - public float getFloat(String qualifier, String key, float defaultValue, IScopeContext[] contexts) { - return 0; - } - - @Override - public int getInt(String qualifier, String key, int defaultValue, IScopeContext[] contexts) { - return 0; - } - - @Override - public long getLong(String qualifier, String key, long defaultValue, IScopeContext[] contexts) { - return 0; - } - - @Override - public String getString(String qualifier, String key, String defaultValue, IScopeContext[] contexts) { - return null; - } - - @Override - public IStatus exportPreferences(IEclipsePreferences node, OutputStream output, String[] excludesList) throws CoreException { - return null; - } - - @Override - public IStatus importPreferences(InputStream input) throws CoreException { - return null; - } - - @Override - public IStatus applyPreferences(IExportedPreferences preferences) throws CoreException { - return null; - } - - @Override - public IExportedPreferences readPreferences(InputStream input) throws CoreException { - return null; - } - - @Override - public String[] getDefaultLookupOrder(String qualifier, String key) { - return null; - } - - @Override - public String[] getLookupOrder(String qualifier, String key) { - return null; - } - - @Override - public void setDefaultLookupOrder(String qualifier, String key, String[] order) {} - - @Override - public void exportPreferences(IEclipsePreferences node, IPreferenceFilter[] filters, OutputStream output) throws CoreException {} - - @Override - public IPreferenceFilter[] matches(IEclipsePreferences node, IPreferenceFilter[] filters) throws CoreException { - return null; - } - - @Override - public void applyPreferences(IEclipsePreferences node, IPreferenceFilter[] filters) throws CoreException {} - -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/service/SingleSlf4JService.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/service/SingleSlf4JService.java deleted file mode 100644 index 6928621c83..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/service/SingleSlf4JService.java +++ /dev/null @@ -1,533 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.service; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.function.Supplier; - -import javax.annotation.Nullable; - -import org.eclipse.core.internal.runtime.InternalPlatform; -import org.eclipse.equinox.log.ExtendedLogReaderService; -import org.eclipse.equinox.log.ExtendedLogService; -import org.eclipse.equinox.log.LogFilter; -import org.eclipse.equinox.log.Logger; -import org.osgi.framework.Bundle; -import org.osgi.framework.ServiceReference; -import org.osgi.service.log.LogEntry; -import org.osgi.service.log.LogLevel; -import org.osgi.service.log.LogListener; -import org.osgi.service.log.LoggerConsumer; - -/** - * Eclipse log service facade that delegates to a Slf4J logger. - * The service does not provide historical log entries (empty history reported). - * No factory service is provided for OSGI logger extensions (method are marked - * as deprecated and raise UnsupportedOperationException). - * All Eclipse logger are delegated to a single Slf4J logger instance. - * The log messages can be formatted by customizer. - */ -public class SingleSlf4JService implements ExtendedLogService, ExtendedLogReaderService { - - private final org.slf4j.Logger delegate; - private final Map logLevel2methods; - private final Set listener; - private final BiFunction messageCustomizer; - - /** Create facade for named logger with customized messages. */ - public SingleSlf4JService(String name, BiFunction messageCustomizer) { - delegate = org.slf4j.LoggerFactory.getLogger(name); - logLevel2methods = new HashMap(); - /* - * Audit message are treated as normal info-messages and might not get logged. - * Logging of Eclipse messages in Spotless formatter is meant for debugging purposes and - * detection of erroneous usage/override of internal Eclipse methods. - * Hence the concept of Audit is not required. - */ - logLevel2methods.put(LogLevel.AUDIT, - create(() -> true, m -> delegate.info(m), (m, e) -> delegate.info(m, e))); - logLevel2methods.put(LogLevel.DEBUG, - create(() -> delegate.isDebugEnabled(), m -> delegate.debug(m), (m, e) -> delegate.debug(m, e))); - logLevel2methods.put(LogLevel.ERROR, - create(() -> delegate.isErrorEnabled(), m -> delegate.error(m), (m, e) -> delegate.error(m, e))); - logLevel2methods.put(LogLevel.INFO, - create(() -> delegate.isInfoEnabled(), m -> delegate.info(m), (m, e) -> delegate.info(m, e))); - logLevel2methods.put(LogLevel.TRACE, - create(() -> delegate.isTraceEnabled(), m -> delegate.trace(m), (m, e) -> delegate.trace(m, e))); - logLevel2methods.put(LogLevel.WARN, - create(() -> delegate.isWarnEnabled(), m -> delegate.warn(m), (m, e) -> delegate.warn(m, e))); - listener = new HashSet(); - this.messageCustomizer = messageCustomizer; - } - - @Override - @Deprecated - //Backward compatibility with Eclipse OSGI 3.12 - public void log(int level, String message) { - log(this, level, message); - } - - @Override - @Deprecated - //Backward compatibility with Eclipse OSGI 3.12 - public void log(int level, String message, @Nullable Throwable exception) { - log(this, level, message, exception); - } - - @SuppressWarnings("rawtypes") - @Override - @Deprecated - //Backward compatibility with Eclipse OSGI 3.12 - public void log(ServiceReference sr, int level, String message) { - log(this, level, message); - } - - @SuppressWarnings("rawtypes") - @Override - @Deprecated - //Backward compatibility with Eclipse OSGI 3.12 - public void log(ServiceReference sr, int level, String message, Throwable exception) { - log(this, level, message, exception); - } - - @Override - public void log(Object context, int level, String message) { - log(context, level, message, null); - } - - @Override - public void log(Object context, int level, String message, @Nullable Throwable exception) { - LogLevel logLevel = convertDeprectatedOsgiLevel(level); - log(new SimpleLogEntry(logLevel, message, exception)); - } - - @Override - public boolean isLoggable(int level) { - LogLevel logLevel = convertDeprectatedOsgiLevel(level); - return logLevel2methods.get(logLevel).isEnabled(); - } - - @SuppressWarnings("deprecation") ////Backward compatibility with Eclipse OSGI 3.12 - private static LogLevel convertDeprectatedOsgiLevel(int level) { - switch (level) { - case SingleSlf4JService.LOG_DEBUG: - return LogLevel.DEBUG; - case SingleSlf4JService.LOG_INFO: - return LogLevel.INFO; - case SingleSlf4JService.LOG_ERROR: - return LogLevel.ERROR; - case SingleSlf4JService.LOG_WARNING: - return LogLevel.WARN; - default: - return LogLevel.AUDIT; - } - } - - @Override - public String getName() { - return delegate.getName(); - } - - @Override - public Logger getLogger(String loggerName) { - return this; - } - - @Override - public Logger getLogger(Bundle bundle, String loggerName) { - return this; - } - - @Override - public void addLogListener(LogListener listener) { - synchronized (this.listener) { - this.listener.add(listener); - } - } - - @Override - public void removeLogListener(LogListener listener) { - synchronized (this.listener) { - this.listener.remove(listener); - } - } - - private void log(LogEntry entry) { - synchronized (listener) { - listener.stream().forEach(l -> l.logged(entry)); - } - String customMessage = messageCustomizer.apply(entry.getMessage(), entry.getLogLevel()); - logLevel2methods.get(entry.getLogLevel()).log(customMessage, entry.getException()); - } - - @Override - @Deprecated - //Backward compatibility with Eclipse OSGI 3.12 - public Enumeration getLog() { - return Collections.emptyEnumeration(); //We do not provide historical information - } - - @Override - public void addLogListener(LogListener listener, LogFilter filter) { - addLogListener(listener); //Listener must filter if required - - } - - @Override - public org.osgi.service.log.Logger getLogger(Class clazz) { - return this; - } - - @Override - @Deprecated - public L getLogger(String name, Class loggerType) { - throw new UnsupportedOperationException("Logger factory for indifivaul types currently not supported."); - } - - @Override - @Deprecated - public L getLogger(Class clazz, Class loggerType) { - return getLogger(getName(), loggerType); - } - - @Override - @Deprecated - public L getLogger(Bundle bundle, String name, Class loggerType) { - return getLogger(getName(), loggerType); - } - - @Override - public boolean isTraceEnabled() { - return delegate.isTraceEnabled(); - } - - @Override - public void trace(String message) { - log(new SimpleLogEntry(LogLevel.TRACE, message)); - } - - @Override - public void trace(String format, Object arg) { - trace(String.format(format, arg)); - } - - @Override - public void trace(String format, Object arg1, Object arg2) { - trace(String.format(format, arg1, arg2)); - } - - @Override - public void trace(String format, Object... arguments) { - trace(String.format(format, arguments)); - } - - @Override - public void trace(LoggerConsumer consumer) throws E { - consumer.accept(this); - } - - @Override - public boolean isDebugEnabled() { - return delegate.isDebugEnabled(); - } - - @Override - public void debug(String message) { - log(new SimpleLogEntry(LogLevel.DEBUG, message)); - } - - @Override - public void debug(String format, Object arg) { - debug(String.format(format, arg)); - } - - @Override - public void debug(String format, Object arg1, Object arg2) { - debug(String.format(format, arg1, arg2)); - } - - @Override - public void debug(String format, Object... arguments) { - debug(String.format(format, arguments)); - } - - @Override - public void debug(LoggerConsumer consumer) throws E { - consumer.accept(this); - } - - @Override - public boolean isInfoEnabled() { - return delegate.isInfoEnabled(); - } - - @Override - public void info(String message) { - log(new SimpleLogEntry(LogLevel.INFO, message)); - } - - @Override - public void info(String format, Object arg) { - info(String.format(format, arg)); - } - - @Override - public void info(String format, Object arg1, Object arg2) { - info(String.format(format, arg1, arg2)); - } - - @Override - public void info(String format, Object... arguments) { - info(String.format(format, arguments)); - } - - @Override - public void info(LoggerConsumer consumer) throws E { - consumer.accept(this); - } - - @Override - public boolean isWarnEnabled() { - return delegate.isWarnEnabled(); - } - - @Override - public void warn(String message) { - log(new SimpleLogEntry(LogLevel.WARN, message)); - } - - @Override - public void warn(String format, Object arg) { - warn(String.format(format, arg)); - } - - @Override - public void warn(String format, Object arg1, Object arg2) { - warn(String.format(format, arg1, arg2)); - } - - @Override - public void warn(String format, Object... arguments) { - warn(String.format(format, arguments)); - } - - @Override - public void warn(LoggerConsumer consumer) throws E { - consumer.accept(this); - } - - @Override - public boolean isErrorEnabled() { - return delegate.isErrorEnabled(); - } - - @Override - public void error(String message) { - log(new SimpleLogEntry(LogLevel.ERROR, message)); - } - - @Override - public void error(String format, Object arg) { - error(String.format(format, arg)); - } - - @Override - public void error(String format, Object arg1, Object arg2) { - error(String.format(format, arg1, arg2)); - } - - @Override - public void error(String format, Object... arguments) { - error(String.format(format, arguments)); - } - - @Override - public void error(LoggerConsumer consumer) throws E { - consumer.accept(this); - } - - @Override - public void audit(String message) { - log(new SimpleLogEntry(LogLevel.AUDIT, message)); - } - - @Override - public void audit(String format, Object arg) { - audit(String.format(format, arg)); - } - - @Override - public void audit(String format, Object arg1, Object arg2) { - audit(String.format(format, arg1, arg2)); - } - - @Override - public void audit(String format, Object... arguments) { - audit(String.format(format, arguments)); - } - - /** Internal wrapper for Eclipse OSGI 3.12 based logs and new log services. */ - private static class SimpleLogEntry implements LogEntry { - - private final LogLevel level; - private final String message; - private final Optional execption; - - public SimpleLogEntry(LogLevel level, String message) { - this(level, message, Optional.empty()); - } - - public SimpleLogEntry(LogLevel level, String message, @Nullable Throwable execption) { - this(level, message, Optional.ofNullable(execption)); - } - - private SimpleLogEntry(LogLevel level, String message, Optional execption) { - this.level = level; - this.message = message; - this.execption = execption; - } - - @Override - public Bundle getBundle() { - //Return the spotless framework bundle - return InternalPlatform.getDefault().getBundleContext().getBundle(); - } - - @SuppressWarnings({"rawtypes", "unchecked"}) - @Override - public ServiceReference getServiceReference() { - return null; - } - - @Override - @Deprecated - //Backward compatibility with Eclipse OSGI 3.12 - public int getLevel() { - switch (level) { - case DEBUG: - case TRACE: - return SingleSlf4JService.LOG_DEBUG; - case AUDIT: - case INFO: - return SingleSlf4JService.LOG_INFO; - case ERROR: - return SingleSlf4JService.LOG_ERROR; - case WARN: - return SingleSlf4JService.LOG_WARNING; - } - return SingleSlf4JService.LOG_ERROR; //Don't fail here. Just log it as error. This is anyway just for debugging internal problems. - } - - @Override - public String getMessage() { - return message; - } - - @Override - public Throwable getException() { - return execption.orElse(null); - } - - @Override - public long getTime() { - return 0; - } - - @Override - public String toString() { - StringWriter result = new StringWriter(); - result.write(message); - if (execption.isPresent()) { - result.write('\n'); - result.write(execption.get().toString()); - result.write('\n'); - execption.get().printStackTrace(new PrintWriter(result)); - } - return result.toString(); - } - - @Override - public LogLevel getLogLevel() { - return level; - } - - @Override - public String getLoggerName() { - return this.getClass().getSimpleName(); - } - - @Override - public long getSequence() { - return 0; - } - - @Override - public String getThreadInfo() { - return null; - } - - @Override - public StackTraceElement getLocation() { - return null; // Not used by SingleSlf4JService - } - - } - - private static LogMethods create(Supplier enabled, Consumer log, BiConsumer logException) { - return new LogMethods(enabled, log, logException); - } - - private static class LogMethods { - private final Supplier enabled; - private final Consumer log; - private final BiConsumer logException; - - private LogMethods(Supplier enabled, Consumer log, BiConsumer logException) { - this.enabled = enabled; - this.log = log; - this.logException = logException; - } - - public boolean isEnabled() { - return enabled.get(); - } - - public void log(String message) { - log.accept(message); - } - - public void log(String message, @Nullable Throwable exception) { - if (null == exception) { - log(message); - } else { - logException.accept(message, exception); - } - } - - }; - -} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/service/TemporaryLocation.java b/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/service/TemporaryLocation.java deleted file mode 100644 index 2999014d23..0000000000 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/service/TemporaryLocation.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2016-2021 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.service; - -import java.io.File; -import java.io.IOError; -import java.io.IOException; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Comparator; - -import org.eclipse.osgi.service.datalocation.Location; - -/** All files generated at runtime are stored in a temporary location. */ -public class TemporaryLocation implements Location, AutoCloseable { - private static final String TEMP_PREFIX = "com_diffplug_spotless_extra_eclipse"; - private final URL location; - private Location parent; - - public TemporaryLocation() { - this(null, createTemporaryDirectory()); - } - - private TemporaryLocation(Location parent, URL defaultValue) { - this.location = defaultValue; - this.parent = parent; - } - - private static URL createTemporaryDirectory() { - try { - Path location = Files.createTempDirectory(TEMP_PREFIX); - return location.toUri().toURL(); - } catch (IOException e) { - throw new IOError(e); - } - } - - @Override - public boolean allowsDefault() { - return false; - } - - @Override - public URL getDefault() { - return null; - } - - @Override - public Location getParentLocation() { - return parent; - } - - @Override - public URL getURL() { - return location; - } - - @Override - public boolean isSet() { - return true; - } - - @Override - public boolean isReadOnly() { - return false; - } - - @Override - @Deprecated - public boolean setURL(URL value, boolean lock) throws IllegalStateException { - throw new IllegalStateException("URL not modifyable."); - } - - @Override - public boolean set(URL value, boolean lock) throws IllegalStateException, IOException { - throw new IllegalStateException("URL not modifyable."); - } - - @Override - public boolean set(URL value, boolean lock, String lockFilePath) throws IllegalStateException, IOException { - throw new IllegalStateException("URL not modifyable."); - } - - @Override - public boolean lock() throws IOException { - return false; //Lock not supported - } - - @Override - public void release() { - //Lock not supported - } - - @Override - public boolean isLocked() throws IOException { - return false; //Lock not supported - } - - @Override - public Location createLocation(Location parent, URL defaultValue, boolean readonly) { - return new TemporaryLocation(parent, defaultValue); - } - - @Override - public URL getDataArea(String path) throws IOException { - try { - Path locationPath = Paths.get(location.toURI()); - return locationPath.resolve(path).toUri().toURL(); - } catch (URISyntaxException e) { - throw new IOException("Location not correctly formatted.", e); - } - } - - @Override - public void close() throws Exception { - try { - Path path = Path.of(location.toURI()); - Files.walk(path) - .sorted(Comparator.reverseOrder()) - .map(Path::toFile) - .forEach(File::delete); - path.toFile().delete(); - } catch (IOException e) { - //At shutdown everything is just done on best-efforts basis - } - } - -} diff --git a/_ext/eclipse-base/src/main/resources/META-INF/MANIFEST.MF b/_ext/eclipse-base/src/main/resources/META-INF/MANIFEST.MF deleted file mode 100644 index 348dc4c453..0000000000 --- a/_ext/eclipse-base/src/main/resources/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Bundle-ManifestVersion: 2 -Bundle-SymbolicName: com.diffplug.gradle.spotless.eclipse; singleton:=true diff --git a/_ext/eclipse-base/src/main/resources/META-INF/services/org.osgi.framework.connect.FrameworkUtilHelper b/_ext/eclipse-base/src/main/resources/META-INF/services/org.osgi.framework.connect.FrameworkUtilHelper deleted file mode 100644 index 0bd6c29641..0000000000 --- a/_ext/eclipse-base/src/main/resources/META-INF/services/org.osgi.framework.connect.FrameworkUtilHelper +++ /dev/null @@ -1 +0,0 @@ -com.diffplug.spotless.extra.eclipse.base.osgi.FrameworkBundleRegistry \ No newline at end of file diff --git a/_ext/eclipse-base/src/test/java/com/diffplug/spotless/extra/eclipse/base/SpotlessEclipseFrameworkTest.java b/_ext/eclipse-base/src/test/java/com/diffplug/spotless/extra/eclipse/base/SpotlessEclipseFrameworkTest.java deleted file mode 100644 index ca76c92ce1..0000000000 --- a/_ext/eclipse-base/src/test/java/com/diffplug/spotless/extra/eclipse/base/SpotlessEclipseFrameworkTest.java +++ /dev/null @@ -1,330 +0,0 @@ -/* - * Copyright 2016-2021 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import org.assertj.core.api.AbstractAssert; -import org.assertj.core.api.ObjectArrayAssert; -import org.assertj.core.api.StringAssert; -import org.eclipse.core.resources.ResourcesPlugin; -import org.eclipse.core.runtime.ILog; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; -import org.eclipse.equinox.log.ExtendedLogReaderService; -import org.eclipse.equinox.log.ExtendedLogService; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.BundleException; -import org.osgi.framework.ServiceReference; -import org.osgi.service.log.LogEntry; -import org.osgi.service.log.LogLevel; -import org.osgi.service.log.LogListener; -import org.slf4j.simple.SimpleLogger; - -import com.diffplug.spotless.extra.eclipse.base.service.SingleSlf4JService; - -/** Integration tests */ -class SpotlessEclipseFrameworkTest { - - private final static String TEST_LOGGER_NAME = SpotlessEclipseFrameworkTest.class.getSimpleName(); - private final static String CUSTOM_PREFIX = "prefix\t"; - private final static String CUSTOM_POSTFIX = "\tpostfix"; - private final static String TEST_EXCEPTION_MESSAGE = "MY TEST-EXCEPTION"; - private static Slf4JMesssageListener SLF4J_RECEIVER = null; - - private static boolean TREAT_ERROR_AS_EXCEPTION = false; - - @BeforeAll - static void frameworkTestSetup() throws BundleException { - //Prepare interception of SLF4J messages to System.out - SLF4J_RECEIVER = new Slf4JMesssageListener(); - - //Configure SLF4J-Simple - System.setProperty(SimpleLogger.LOG_FILE_KEY, "System.out"); - System.setProperty(SimpleLogger.SHOW_SHORT_LOG_NAME_KEY, "true"); - System.setProperty(SimpleLogger.DEFAULT_LOG_LEVEL_KEY, "warn"); - - //Instantiate default framework + SLF4J logger - SpotlessEclipseFramework.setup(new SpotlessEclipseConfig() { - @Override - public void registerServices(SpotlessEclipseServiceConfig config) { - config.applyDefault(); - config.useSlf4J(TEST_LOGGER_NAME, (s, l) -> { - if (TREAT_ERROR_AS_EXCEPTION && (LogLevel.ERROR == l)) { - throw new IllegalArgumentException(TEST_EXCEPTION_MESSAGE); - } - return CUSTOM_PREFIX + s + CUSTOM_POSTFIX; - }); - } - }); - } - - @AfterAll - static void deregisterSlf4JReceiver() { - SLF4J_RECEIVER.deregister(); - } - - private EclipseMessageListener eclipseMessageListener; - - @BeforeEach - void eclipseReceiverSetup() { - ExtendedLogReaderService service = getService(ExtendedLogReaderService.class); - eclipseMessageListener = new EclipseMessageListener(); - service.addLogListener(eclipseMessageListener); - SLF4J_RECEIVER.clear(); - } - - @AfterEach - void logReceiverTearDown() { - ExtendedLogReaderService service = getService(ExtendedLogReaderService.class); - service.removeLogListener(eclipseMessageListener); - } - - @Test - void testCustomizedLogMessage() { - ResourcesPlugin.getPlugin().getLog().log(new Status(IStatus.ERROR, "Some plugin", "Hello World!")); - assertSlf4J() - .as("Error message logged.").received("Hello World!").contains(TEST_LOGGER_NAME) - .as("Customization method has been applied.").contains(CUSTOM_PREFIX, CUSTOM_POSTFIX) - .as("Status level has been converted to simple SLF4J level").contains("ERROR"); - assertEclipse() - .as("Warning message received.").received("Hello World!") - .as("Customization method is only for SLF4J").doesNotContain(CUSTOM_PREFIX); - } - - @Test - void testCustomizedLogException() { - assertThatExceptionOfType(IllegalArgumentException.class) - .isThrownBy(() -> { - try { - TREAT_ERROR_AS_EXCEPTION = true; - ResourcesPlugin.getPlugin().getLog().log(new Status(IStatus.ERROR, "Some plugin", "Hello World!")); - } finally { - TREAT_ERROR_AS_EXCEPTION = false; - } - }) - .withMessage(TEST_EXCEPTION_MESSAGE); - } - - @Test - void testPluginLog() { - List logLevels = Arrays.asList(IStatus.CANCEL, IStatus.ERROR, IStatus.INFO, IStatus.OK, IStatus.WARNING); - List enabledLogLevels = Arrays.asList(IStatus.ERROR, IStatus.WARNING); - List disabledLogLevels = logLevels.stream().filter(level -> !enabledLogLevels.contains(level)).collect(Collectors.toList()); - ILog logger = ResourcesPlugin.getPlugin().getLog(); - logLevels.forEach(logLevel -> logger.log(new Status(logLevel, "Some plugin", logLevel.toString()))); - assertSlf4J() - .as("Messages for all enabled levels are logged.").received( - enabledLogLevels.stream().map(i -> i.toString()).collect(Collectors.toList())); - assertSlf4J() - .as("Messages all disabled levels are not logged.").notReceived( - disabledLogLevels.stream().map(i -> i.toString()).collect(Collectors.toList())); - assertEclipse() - .as("All messages received.").received( - logLevels.stream().map(i -> i.toString()).collect(Collectors.toList())); - } - - @Test - void testLogServiceLevel() { - ExtendedLogService service = getService(ExtendedLogService.class); - assertThat(service.isErrorEnabled()).as("Error log level is enabled").isTrue(); - assertThat(service.isWarnEnabled()).as("Warning log level is enabled").isTrue(); - assertThat(service.isInfoEnabled()).as("Info log level is disabled").isFalse(); - assertThat(service.isDebugEnabled()).as("Debug log level is disabled").isFalse(); - assertThat(service.isTraceEnabled()).as("Trace log level is disabled").isFalse(); - } - - @Test - void testLogServiceLog() { - ExtendedLogService service = getService(ExtendedLogService.class); - service.info("Log Info"); - service.warn("Log Warn"); - assertSlf4J().received("Log Warn"); - assertSlf4J().notReceived("Log Info"); - assertEclipse().received(Arrays.asList("Log Warn", "Log Info")); - } - - @Test - @Deprecated - void testLogServiceLog_3_12() { - ExtendedLogService service = getService(ExtendedLogService.class); - service.log(SingleSlf4JService.LOG_INFO, "Log Info"); - try { - throw new IllegalArgumentException(TEST_EXCEPTION_MESSAGE); - } catch (Exception e) { - service.log(SingleSlf4JService.LOG_WARNING, "Log Warn", e); - } - assertSlf4J().received(Arrays.asList("Log Warn", TEST_EXCEPTION_MESSAGE)); - assertSlf4J().notReceived("Log Info"); - assertEclipse().received(Arrays.asList("Log Warn", TEST_EXCEPTION_MESSAGE, "Log Info")); - } - - private static T getService(Class serviceClass) { - ResourcesPlugin plugin = ResourcesPlugin.getPlugin(); - assertThat(plugin).as("ResourcesPlugin instantiated as part of framework defaults").isNotNull(); - Bundle bundle = plugin.getBundle(); - assertThat(bundle).as("ResourcesPlugin has been started.").isNotNull(); - BundleContext context = bundle.getBundleContext(); - assertThat(context).as("ResourcesPlugin has been started.").isNotNull(); - ServiceReference reference = context.getServiceReference(serviceClass); - assertThat(reference).as(serviceClass.getSimpleName() + " has been registered.").isNotNull(); - T service = context.getService(reference); - assertThat(service).as(serviceClass.getSimpleName() + " can be resolved.").isNotNull(); - return service; - } - - private static interface IMessageListener { - Collection getMessages(); - } - - private static class EclipseMessageListener implements LogListener, IMessageListener { - - private final List messages; - - public EclipseMessageListener() { - messages = new ArrayList(); - } - - @Override - public Collection getMessages() { - return Collections.unmodifiableList(messages); - } - - @Override - public void logged(LogEntry entry) { - messages.add(entry.getMessage()); - if (null != entry.getException()) { - messages.add(entry.getException().getMessage()); - } - } - } - - private final static class Slf4JMesssageListener extends PrintStream implements IMessageListener { - - private final List messages; - private final PrintStream originalStream; - - public Slf4JMesssageListener() { - super(System.out); - messages = new ArrayList(); - originalStream = System.out; - System.setOut(this); - } - - @Override - public void println(String x) { - if (x.contains(TEST_LOGGER_NAME)) { - messages.add(x); - } else { - super.println(x); - } - } - - @Override - public void println(Object x) { - if (x instanceof Exception) { - Exception e = (Exception) x; - if (TEST_EXCEPTION_MESSAGE == e.getMessage()) { - messages.add(TEST_EXCEPTION_MESSAGE); - } - } - super.println(x); - } - - public void deregister() { - System.setOut(originalStream); - } - - public void clear() { - messages.clear(); - } - - @Override - public Collection getMessages() { - return Collections.unmodifiableList(messages); - } - } - - private static class MessageListenerAssert extends AbstractAssert, T> { - - public MessageListenerAssert(T actual) { - super(actual, MessageListenerAssert.class); - } - - public ObjectArrayAssert received(Collection unformattedMessages) { - List formattedMessages = unformattedMessages.stream() - .map(unformattedMessage -> getReceivedMessage(unformattedMessage, false)) - .collect(Collectors.toList()); - return new ObjectArrayAssert(formattedMessages.toArray(new String[0])); - } - - public StringAssert received(String unformattedMessage) { - return new StringAssert(getReceivedMessage(unformattedMessage, false)); - } - - public MessageListenerAssert notReceived(String unformattedMessage) { - getReceivedMessage(unformattedMessage, true); - return this; - } - - public MessageListenerAssert notReceived(Collection unformattedMessages) { - unformattedMessages.forEach(unformattedMessage -> getReceivedMessage(unformattedMessage, true)); - return this; - } - - private String getReceivedMessage(String unformattedMessage, boolean negate) { - isNotNull(); - String receivedMessage = null; - for (String formattedMessage : actual.getMessages()) { - if (formattedMessage.contains(unformattedMessage)) { - receivedMessage = formattedMessage; - break; - } - } - if ((!negate) && (null == receivedMessage)) { - failWithMessage("Message <%s> not received.", unformattedMessage); - } - if (negate && (null != receivedMessage)) { - failWithMessage("Message <%s> has been received. Formatted message is: %s", unformattedMessage, receivedMessage); - } - return receivedMessage; - } - - } - - private static MessageListenerAssert assertSlf4J() { - return new MessageListenerAssert(SLF4J_RECEIVER).as("SLF4J"); - } - - private MessageListenerAssert assertEclipse() { - return new MessageListenerAssert(eclipseMessageListener).as("Eclipse"); - } -} diff --git a/_ext/eclipse-base/src/test/java/com/diffplug/spotless/extra/eclipse/base/osgi/BundleSetTest.java b/_ext/eclipse-base/src/test/java/com/diffplug/spotless/extra/eclipse/base/osgi/BundleSetTest.java deleted file mode 100644 index 9c5c97ddb8..0000000000 --- a/_ext/eclipse-base/src/test/java/com/diffplug/spotless/extra/eclipse/base/osgi/BundleSetTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2016-2021 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.osgi; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; - -import java.util.Arrays; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleException; - -class BundleSetTest { - - BundleSet instance; - - @BeforeEach - void initialize() { - instance = new BundleSet(); - } - - @Test - void testAddGet() throws BundleException { - Bundle testBundle1 = new TestBundle(1, "a"); - Bundle testBundle2 = new TestBundle(2, "b"); - instance.add(testBundle1); - instance.add(testBundle2); - assertEquals(testBundle1, instance.get(1), "Get by ID 1"); - assertEquals(testBundle2, instance.get(2), "Get by ID 2"); - assertEquals(testBundle1, instance.get("a"), "Get by symbolic name 'a'"); - assertEquals(testBundle2, instance.get("b"), "Get by symbolic name 'b'"); - assertTrue(instance.getAll().containsAll(Arrays.asList(testBundle1, testBundle2)), "Contains all"); - } - - @Test - void testSameSymbolicName() throws BundleException { - final String symbolicName = "sym.a"; - final long id1 = 12345; - final long id2 = 23456; - Bundle testBundle1 = new TestBundle(id1, symbolicName); - Bundle testBundle2 = new TestBundle(id2, symbolicName); - instance.add(testBundle1); - BundleException e = assertThrows(BundleException.class, () -> instance.add(testBundle2)); - assertThat(e.getMessage()).as("BundleException does not contain symbolic name.").contains(symbolicName); - assertThat(e.getMessage()).as("BundleException does not contain ID of exisiting bundle.").contains(Long.toString(id1)); - assertThat(e.getMessage()).as("BundleException does not contain ID of new bundle.").contains(Long.toString(id2)); - } - - @Test - void testSameID() throws BundleException { - final String symbolicName1 = "sym.a"; - final String symbolicName2 = "sym.b"; - final long id = 12345; - Bundle testBundle1 = new TestBundle(id, symbolicName1); - Bundle testBundle2 = new TestBundle(id, symbolicName2); - instance.add(testBundle1); - BundleException e = assertThrows(BundleException.class, () -> instance.add(testBundle2)); - assertThat(e.getMessage()).as("BundleException does not contain ID.").contains(Long.toString(id)); - assertThat(e.getMessage()).as("BundleException does not contain symbolic name of exisiting bundle.").contains(symbolicName1); - assertThat(e.getMessage()).as("BundleException does not contain symbolic name of new bundle.").contains(symbolicName2); - } - -} diff --git a/_ext/eclipse-base/src/test/java/com/diffplug/spotless/extra/eclipse/base/osgi/ServiceCollectionTest.java b/_ext/eclipse-base/src/test/java/com/diffplug/spotless/extra/eclipse/base/osgi/ServiceCollectionTest.java deleted file mode 100644 index 05f18c475c..0000000000 --- a/_ext/eclipse-base/src/test/java/com/diffplug/spotless/extra/eclipse/base/osgi/ServiceCollectionTest.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2016-2021 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.osgi; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; - -import java.util.HashMap; - -import org.assertj.core.api.AbstractAssert; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.osgi.framework.Bundle; -import org.osgi.framework.ServiceException; -import org.osgi.framework.ServiceReference; - -class ServiceCollectionTest { - - ServiceCollection instance; - - @BeforeEach - void initialize() { - Bundle systemBundle = new TestBundle(0, "test.system"); - instance = new ServiceCollection(systemBundle, new HashMap()); - } - - @Test - void testAddGet() { - Service1 service1 = new Service1(); - Service2 service2 = new Service2(); - instance.add(Interf1.class, service1); - instance.add(Interf2a.class, service2); - instance.add(Interf2b.class, service2); - assertFor(instance).getServiceForReferences(Interf1.class).matchesService(service1); - assertFor(instance).getServiceForReferences(Interf2a.class).matchesService(service2); - assertFor(instance).getServiceForReferences(Interf2b.class).matchesService(service2); - } - - @Test - void testMultipleServicesPerInterface() { - Service1 serviceX = new Service1(); - Service1 serviceY = new Service1(); - instance.add(Interf1.class, serviceX); - ServiceException e = assertThrows(ServiceException.class, () -> instance.add(Interf1.class, serviceY)); - assertThat(e.getMessage()).as("ServiceException does not contain interface class name.").contains(Interf1.class.getName()); - } - - private static class ServiceReferenceAssert extends AbstractAssert { - - private final ServiceCollection actual; - private final ServiceReference reference; - - public ServiceReferenceAssert(ServiceCollection actual) { - this(actual, null); - } - - public ServiceReferenceAssert(ServiceCollection actual, ServiceReference reference) { - super(actual, ServiceReferenceAssert.class); - this.reference = reference; - this.actual = actual; - - } - - ServiceReferenceAssert getServiceForReferences(Class interfaceClass) { - ServiceReference[] references = actual.getReferences(interfaceClass.getName()); - int numberOfFoundReferences = null == references ? 0 : references.length; - if (numberOfFoundReferences != 1) { - failWithMessage("Expected to find exactly 1 reference for <%s> , but found %d.", interfaceClass.getName(), numberOfFoundReferences); - } - return new ServiceReferenceAssert(actual, references[0]); - } - - ServiceReferenceAssert matchesService(Object expected) { - if (null == reference) { - failWithMessage("No reference requested."); - } - Object serviceForRef = actual.getService(reference); - if (null == serviceForRef) { - failWithMessage("No service provided for reference."); - } - if (!serviceForRef.equals(expected)) { - failWithMessage("Unexpected service found."); - } - - return this; - } - } - - private static ServiceReferenceAssert assertFor(ServiceCollection actual) { - return new ServiceReferenceAssert(actual); - } - - private static interface Interf1 {}; - - private static interface Interf2a {}; - - private static interface Interf2b {}; - - private static class Service1 implements Interf1 {}; - - private static class Service2 implements Interf2a, Interf2b {}; -} diff --git a/_ext/eclipse-base/src/test/java/com/diffplug/spotless/extra/eclipse/base/osgi/TestBundle.java b/_ext/eclipse-base/src/test/java/com/diffplug/spotless/extra/eclipse/base/osgi/TestBundle.java deleted file mode 100644 index 177442101d..0000000000 --- a/_ext/eclipse-base/src/test/java/com/diffplug/spotless/extra/eclipse/base/osgi/TestBundle.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.base.osgi; - -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.security.cert.X509Certificate; -import java.util.Dictionary; -import java.util.Enumeration; -import java.util.List; -import java.util.Map; - -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceReference; -import org.osgi.framework.Version; - -/** Helper for testing */ -public class TestBundle implements StaticBundle, TemporaryBundle { - private final String symbolicName; - private final long id; - - public TestBundle(long id, String symbolicName) { - this.id = id; - this.symbolicName = symbolicName; - } - - @Override - public int getState() { - return 0; - } - - @Override - @Deprecated - public Dictionary getHeaders() { - return null; - } - - @Override - public long getBundleId() { - return id; - } - - @Override - public ServiceReference[] getRegisteredServices() { - return null; - } - - @Override - public boolean hasPermission(Object permission) { - return false; - } - - @Override - public URL getResource(String name) { - return null; - } - - @Override - @Deprecated - public Dictionary getHeaders(String locale) { - return null; - } - - @Override - public String getSymbolicName() { - return symbolicName; - } - - @Override - public Class loadClass(String name) throws ClassNotFoundException { - return null; - } - - @Override - public Enumeration getResources(String name) throws IOException { - return null; - } - - @Override - public Enumeration getEntryPaths(String path) { - return null; - } - - @Override - public URL getEntry(String path) { - return null; - } - - @Override - @Deprecated - public Enumeration findEntries(String path, String filePattern, boolean recurse) { - return null; - } - - @Override - public BundleContext getBundleContext() { - return null; - } - - @Override - public Map> getSignerCertificates(int signersType) { - return null; - } - - @Override - public Version getVersion() { - return null; - } - - @Override - public A adapt(Class type) { - return null; - } - - @Override - public File getDataFile(String filename) { - return null; - } - - @Override - public int compareTo(Bundle o) { - return 0; - } - -} diff --git a/_ext/eclipse-cdt/CHANGES.md b/_ext/eclipse-cdt/CHANGES.md deleted file mode 100644 index bdc7d99ea9..0000000000 --- a/_ext/eclipse-cdt/CHANGES.md +++ /dev/null @@ -1,61 +0,0 @@ -# spotless-eclipse-cdt - -We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `9.9.0`). - -## [Unreleased] - -## [10.5.0] - 2021-12-13 -### Added -* Switch to Eclipse CDT release 10.5 for Eclipse 2021-12. - -## [10.4.0] - 2021-09-23 -### Added -* Switch to Eclipse CDT release 10.4 for Eclipse 4.21. - -## [10.3.0] - 2021-06-27 -### Added -* Switch to Eclipse CDT release 10.3 for Eclipse 4.20. - -## [10.2.0] - 2021-06-07 -### Added -* Switch to Eclipse CDT release 10.2 for Eclipse 4.19. - -## [10.1.0] - 2020-12-26 -### Added -* Switch to Eclipse CDT release 10.1 for Eclipse 4.18. - -## [10.0.0] - 2020-10-17 -### Added -* Switch to Eclipse CDT release 10.0 for Eclipse 4.17. -* **BREAKING** Minimum required Java version changed from 8 to 11. - -## [9.11.0] - 2020-10-03 -### Added -* Switch to Eclipse CDT release 9.11.1 for Eclipse 4.16. - -## [9.10.0] - 2020-09-25 -### Added -* Switch to Eclipse CDT release 9.10 for Eclipse 4.14. - -## [9.9.0] - 2019-11-01 -* Switch to Eclipse CDT release 9.9 for Eclipse 4.13 ([#480](https://github.com/diffplug/spotless/issues/480)). - -## [9.8.1] - 2019-10-31 -* Really publish Eclipse CDT release 9.8 for Eclipse 4.12 ([#482](https://github.com/diffplug/spotless/pull/482)). - -## [9.8.0] - 2019-07-24 -* **Known bug** - we actually published Eclipse CDT 9.7 instead of 9.8 - fixed in 9.8.1 -* Switch to Eclipse CDT release 9.8 for Eclipse 4.12 ([#423](https://github.com/diffplug/spotless/pull/423)). - -## [9.7.0] - 2019-03-31 -* Switch to Eclipse CDT release 9.7 for Eclipse 4.11 ([#389](https://github.com/diffplug/spotless/pull/389)). -* Include Eclipse logging allowing formatter warnings/errors to be logged via SLF4J ([#236](https://github.com/diffplug/spotless/issues/236)). - -## [9.4.5] - 2019-02-25 -* Replaced `http` update-site with `https` ([#360](https://github.com/diffplug/spotless/issues/360)). - -## [9.4.4] - 2018-09-04 -* Added missing log service, which caused exceptions on AST warnings ([#286](https://github.com/diffplug/spotless/pull/286)). - -## [9.4.3] - 2018-08-08 -* Initial release! diff --git a/_ext/eclipse-cdt/LICENSE.txt b/_ext/eclipse-cdt/LICENSE.txt deleted file mode 100644 index 3d967aee74..0000000000 --- a/_ext/eclipse-cdt/LICENSE.txt +++ /dev/null @@ -1,70 +0,0 @@ -Eclipse Public License - v 1.0 - -THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. - -1. DEFINITIONS - -"Contribution" means: - -a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and -b) in the case of each subsequent Contributor: -i) changes to the Program, and -ii) additions to the Program; -where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. -"Contributor" means any person or entity that distributes the Program. - -"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. - -"Program" means the Contributions distributed in accordance with this Agreement. - -"Recipient" means anyone who receives the Program under this Agreement, including all Contributors. - -2. GRANT OF RIGHTS - -a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. -b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. -c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. -d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. -3. REQUIREMENTS - -A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: - -a) it complies with the terms and conditions of this Agreement; and -b) its license agreement: -i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; -ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; -iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and -iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. -When the Program is made available in source code form: - -a) it must be made available under this Agreement; and -b) a copy of this Agreement must be included with each copy of the Program. -Contributors may not remove or alter any copyright notices contained within the Program. - -Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. - -4. COMMERCIAL DISTRIBUTION - -Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. - -For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. - -5. NO WARRANTY - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. - -6. DISCLAIMER OF LIABILITY - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -7. GENERAL - -If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. - -If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. - -All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. - -Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. - -This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. \ No newline at end of file diff --git a/_ext/eclipse-cdt/README.md b/_ext/eclipse-cdt/README.md deleted file mode 100644 index 842fbad606..0000000000 --- a/_ext/eclipse-cdt/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# spotless-eclipse-cdt - -Eclipse CDT is not available in a form which can be easily consumed by maven or gradle. To fix this, we publish Eclipse's formatter and all its dependencies, along with a small amount of glue code, into the `com.diffplug.gradle.spotless:spotless-eclipse-cdt` artifact. - -To publish a new version, update the `_ext/eclipse-cdt/gradle.properties` appropriately and see [CONTRIBUTING.md](../../CONTRIBUTING.md) how to enable -`_ext` projects. - -## License - -Spotless at large is under the Apache 2.0 license, but this jar is under the EPL v1. diff --git a/_ext/eclipse-cdt/build.gradle b/_ext/eclipse-cdt/build.gradle deleted file mode 100644 index 17819f4a2e..0000000000 --- a/_ext/eclipse-cdt/build.gradle +++ /dev/null @@ -1,40 +0,0 @@ -ext { - developers = [ - fvgh: [ name: 'Frank Vennemeyer', email: 'frankgh@zoho.com' ], - ] - - p2Repository = "https://download.eclipse.org/tools/cdt/releases/${VER_ECLIPSE_CDT}" - - p2Dependencies = [ - 'org.eclipse.cdt.core':'+', // CodeFormatter and related - ] - -} - -apply from: rootProject.file('_ext/gradle/update-lockfile.gradle') -apply from: rootProject.file('_ext/gradle/p2-fat-jar-setup.gradle') -apply from: rootProject.file('gradle/java-publish.gradle') - - -dependencies { - implementation "com.diffplug.spotless:spotless-eclipse-base:${VER_SPOTLESS_ECLISPE_BASE}" - // Provides text partitioners for formatters - implementation ("org.eclipse.platform:org.eclipse.jface.text:${VER_ECLISPE_JFACE}") { - exclude group: 'org.eclipse.platform', module: 'org.eclipse.swt' - } - // Required to by CCorePlugin calling CDTLogWriter - implementation "com.ibm.icu:icu4j:${VER_IBM_ICU}" - // Required to by CCorePlugin calling PositionTrackerManager - implementation "org.eclipse.platform:org.eclipse.core.filebuffers:${VER_ECLISPE_EFS}" - - testImplementation("org.slf4j:slf4j-simple:${VER_SLF4J}") -} - - -////////// -// Test // -////////// -sourceSets { - // Use JAR file with all resources for Eclipse-CDT integration-tests - test.runtimeClasspath = jar.outputs.files + sourceSets.test.output + sourceSets.test.compileClasspath -} diff --git a/_ext/eclipse-cdt/gradle.properties b/_ext/eclipse-cdt/gradle.properties deleted file mode 100644 index 9c1c8a7ee6..0000000000 --- a/_ext/eclipse-cdt/gradle.properties +++ /dev/null @@ -1,12 +0,0 @@ -artifactId=spotless-eclipse-cdt -description=Eclipse's CDT C/C++ formatter bundled for Spotless - -# Build requirements -VER_JAVA=11 - -# Compile dependencies -VER_ECLIPSE_CDT=10.5 -VER_SPOTLESS_ECLISPE_BASE=[3.5.0,4.0.0[ -VER_ECLISPE_JFACE=[3.18.0,4.0.0[ -VER_ECLISPE_EFS=[3.7.0,4.0.0[ -VER_IBM_ICU=[67.1,68[ diff --git a/_ext/eclipse-cdt/src/test/java/com/diffplug/spotless/extra/eclipse/cdt/EclipseCdtFormatterStepImplTest.java b/_ext/eclipse-cdt/src/test/java/com/diffplug/spotless/extra/eclipse/cdt/EclipseCdtFormatterStepImplTest.java deleted file mode 100644 index 05637a4e22..0000000000 --- a/_ext/eclipse-cdt/src/test/java/com/diffplug/spotless/extra/eclipse/cdt/EclipseCdtFormatterStepImplTest.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2016-2021 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.cdt; - -import static com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework.LINE_DELIMITER; -import static org.junit.jupiter.api.Assertions.*; - -import java.util.Properties; -import java.util.function.Consumer; - -import org.eclipse.cdt.core.CCorePlugin; -import org.eclipse.cdt.core.formatter.DefaultCodeFormatterConstants; -import org.junit.jupiter.api.Test; - -/** Eclipse CDT wrapper integration tests */ -class EclipseCdtFormatterStepImplTest { - - private final static String CPP_UNFORMATTED = "#include \n" + - "using namespace std;\n" + - "int main()\n{\n" + - " cout <<\n\"Hello, World!\";\n" + - " return 0;\n" + - "}".replaceAll("\n", LINE_DELIMITER); - private final static String CPP_FORMATTED = "#include \n" + - "using namespace std;\n" + - "int main() {\n" + - "\tcout << \"Hello, World!\";\n" + - "\treturn 0;\n" + - "}\n".replaceAll("\n", LINE_DELIMITER); - - private final static String DOXYGEN_HTML = "/**\n *

void f() {int a =1;} 
\n */\n".replaceAll("\n", LINE_DELIMITER); - - private final static String ILLEGAL_CHAR = Character.toString((char) 254); - - private final static String FUNCT_PTR_UNFORMATTED = "void (*getFunc(void)) (int);"; - private final static String FUNCT_PTR_FORMATTED = "void (* getFunc(void)) (int);"; - - @Test - void defaultFormat() throws Throwable { - String output = format(CPP_UNFORMATTED, config -> {}); - assertEquals(CPP_FORMATTED, - output, "Unexpected formatting with default preferences."); - } - - @Test - void invalidFormat() throws Throwable { - String output = format(CPP_FORMATTED.replace("int main() {", "int main() "), config -> {}); - assertTrue(output.contains("int main()" + LINE_DELIMITER), "Incomplete CPP not formatted on best effort basis."); - } - - @Test - void invalidCharater() throws Throwable { - String output = format(CPP_FORMATTED.replace("int main() {", "int main()" + ILLEGAL_CHAR + " {"), config -> {}); - assertTrue(output.contains("int main()" + LINE_DELIMITER), "Invalid charater not formatted on best effort basis."); - } - - @Test - void invalidConfiguration() throws Throwable { - String output = format(CPP_FORMATTED, config -> { - config.setProperty(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR, CCorePlugin.SPACE); - config.setProperty(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE, "noInteger"); - }); - assertEquals(CPP_FORMATTED.replace("\t", " "), - output, "Invalid indentation configuration not replaced by default value (4 spaces)"); - } - - @Test - void htmlCommentFormat() throws Throwable { - String output = format(DOXYGEN_HTML + CPP_FORMATTED, config -> {}); - assertEquals(DOXYGEN_HTML + CPP_FORMATTED, - output, "HTML comments not ignored by formatter."); - } - - @Test - void regionWarning() throws Throwable { - String output = format(FUNCT_PTR_UNFORMATTED, config -> {}); - assertEquals(FUNCT_PTR_FORMATTED, output, "Code not formatted at all due to regional error."); - } - - private static String format(final String input, final Consumer config) throws Exception { - Properties properties = new Properties(); - config.accept(properties); - EclipseCdtFormatterStepImpl formatter = new EclipseCdtFormatterStepImpl(properties); - return formatter.format(input); - } -} diff --git a/_ext/eclipse-groovy/CHANGES.md b/_ext/eclipse-groovy/CHANGES.md deleted file mode 100644 index c80ce061d5..0000000000 --- a/_ext/eclipse-groovy/CHANGES.md +++ /dev/null @@ -1,64 +0,0 @@ -# spotless-eclipse-groovy - -We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.5.0`). - -## [Unreleased] - -## [4.3.0] - 2021-10-13 -### Added -* Switch to Groovy-Eclipse release 4.3.0 for Eclipse 4.21 using Groovy 4.0.0 Beta 1. - -## [4.2.0] - 2021-09-14 -### Added -* Switch to Groovy-Eclipse release 4.2.0 for Eclipse 4.20 using Groovy 4.0.0 Alpha 3. - -### Fixed -* Fixed IndexOutOfBoundsException in parallel execution of `eclipse-groovy` formatter ([#877](https://github.com/diffplug/spotless/issues/877)) - -## [4.1.0] - 2021-06-05 -### Added -* Switch to Groovy-Eclipse release 4.1.0 for Eclipse 4.19 using Groovy 4.0.0 Alpha 2. - -## [4.0.0] - 2021-04-10 -### Added -* Switch to Groovy-Eclipse release 4.0.0 for Eclipse 4.18 using Groovy 4.0.0 Alpha 2. -* **BREAKING** Keep spotless-eclipse-groovy major version in sync with Groovy-Eclipse version. - -## [3.9.0] - 2020-10-17 -### Added -* Fixed version number determined by change log. - -## [3.8.1] - 2020-10-17 -### Added -* Switch to Groovy-Eclipse release 3.9.0 for Eclipse 4.17 using Groovy-Indy 3.0.6. - -## [3.8.0] - 2020-10-04 -### Added -* Switch to Groovy-Eclipse release 3.8.0 for Eclipse 4.16 using Groovy-Indy 3.0.4. - -## [3.7.0] - 2020-10-03 -### Added -* Switch to Groovy-Eclipse release 3.7.0 for Eclipse 4.15 using Groovy-Indy 3.0.2. - -## [3.6.0] - 2020-09-26 -### Added -* Switch to Groovy-Eclipse release 3.6.0 for Eclipse 4.14. - -## [3.5.0] - 2019-09-01 -* Switch to Groovy-Eclipse release 3.5.0 for Eclipse 4.13 ([#480](https://github.com/diffplug/spotless/issues/480)). - -## [3.4.0] - 2019-07-24 -* Switch to Groovy-Eclipse release 3.4.0 for Eclipse 4.12 using Groovy-Indy 3.0.0 ([#423](https://github.com/diffplug/spotless/pull/423)). - -## [3.2.0] - 2019-03-16 -* Switch to Groovy-Eclipse release 3.2.0 for Eclipse 4.10 ([#375](https://github.com/diffplug/spotless/pull/375)). -* Include Eclipse logging allowing formatter warnings/errors to be logged via SLF4J ([#236](https://github.com/diffplug/spotless/issues/236)). - -## [3.0.1] - 2019-02-25 -* Replaced `http` update-site with `https` ([#360](https://github.com/diffplug/spotless/issues/360)). - -## [3.0.0] - 2018-09-04 -* Switch to Groovy-Eclipse release 3.0.0 and Groovy 2.6 ([#288](https://github.com/diffplug/spotless/issues/288)). - -## [2.9.2] - 2018-07-19 -* Initial release! diff --git a/_ext/eclipse-groovy/LICENSE.txt b/_ext/eclipse-groovy/LICENSE.txt deleted file mode 100644 index 3d967aee74..0000000000 --- a/_ext/eclipse-groovy/LICENSE.txt +++ /dev/null @@ -1,70 +0,0 @@ -Eclipse Public License - v 1.0 - -THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. - -1. DEFINITIONS - -"Contribution" means: - -a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and -b) in the case of each subsequent Contributor: -i) changes to the Program, and -ii) additions to the Program; -where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. -"Contributor" means any person or entity that distributes the Program. - -"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. - -"Program" means the Contributions distributed in accordance with this Agreement. - -"Recipient" means anyone who receives the Program under this Agreement, including all Contributors. - -2. GRANT OF RIGHTS - -a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. -b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. -c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. -d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. -3. REQUIREMENTS - -A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: - -a) it complies with the terms and conditions of this Agreement; and -b) its license agreement: -i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; -ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; -iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and -iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. -When the Program is made available in source code form: - -a) it must be made available under this Agreement; and -b) a copy of this Agreement must be included with each copy of the Program. -Contributors may not remove or alter any copyright notices contained within the Program. - -Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. - -4. COMMERCIAL DISTRIBUTION - -Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. - -For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. - -5. NO WARRANTY - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. - -6. DISCLAIMER OF LIABILITY - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -7. GENERAL - -If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. - -If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. - -All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. - -Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. - -This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. \ No newline at end of file diff --git a/_ext/eclipse-groovy/README.md b/_ext/eclipse-groovy/README.md deleted file mode 100644 index 687ed7a132..0000000000 --- a/_ext/eclipse-groovy/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# spotless-eclipse-groovy - -Groovy-Eclipse is not available in a form which can be easily consumed by maven or gradle. -To fix this, we publish Groovy-Eclipse's formatter and all its dependencies, along with a small amount of glue code, into the `com.diffplug.gradle.spotless:spotless-eclipse-groovy` artifact. - -## Build - -To publish a new version, update the `_ext/eclipse-groovy/gradle.properties` appropriately and see [CONTRIBUTING.md](../../CONTRIBUTING.md) how to enable -`_ext` projects. - -## License - -Spotless at large is under the Apache 2.0 license, but this jar is under the EPL v1. diff --git a/_ext/eclipse-groovy/build.gradle b/_ext/eclipse-groovy/build.gradle deleted file mode 100644 index e1382acd76..0000000000 --- a/_ext/eclipse-groovy/build.gradle +++ /dev/null @@ -1,56 +0,0 @@ -ext { - developers = [ - fvgh: [ name: 'Frank Vennemeyer', email: 'frankgh@zoho.com' ], - ] - - p2Repository = "https://dist.springsource.org/release/GRECLIPSE/${VER_GRECLIPSE}/e${VER_ECLIPSE}" - - p2Dependencies = [ - 'org.codehaus.groovy.eclipse.refactoring':'+', // GroovyFormatter and related - - // The following lists does not reflect the complete transitive required packages, but - // the once used during code formatting - 'org.codehaus.groovy':'+', // Groovy compiler patches supporting use within GrEclipse and Groovy itself - 'org.codehaus.groovy.eclipse.core':'+', // Groovy core classes (provides central logging used by formatter) - 'org.eclipse.jdt.core':"${VAR_GRECLIPSE_JDT_PATCH}", // Patches org.eclipse.jdt.core classes supporting use within GrEclipse (provides AST generator) - 'org.eclipse.jdt.groovy.core':'+' // Extends org.eclipse.jdt.core for Groovy - ] - - internalJars = [ - //Jars included by org.codehaus.groovy - ////////////////////////////////////////////////////////////////////////// - // Use Groovy compiler compatible with GrEclipse instead of localGroovy - "**/groovy-${VER_GROOVY}", - "**/groovy-parser2", - // Patches/Overrides some of the Groovy compiler classes - '**/groovy-eclipse', - // Provides logging capabilities for groovy-eclipse - '**/eclipse-trace', - //Jars included by org.eclipse.jdt.groovy.core - ////////////////////////////////////////////////////////////////////////// - //Non locking class loader used by groovy compiler - '**/nlcl' - ] - -} - -apply from: rootProject.file('_ext/gradle/update-lockfile.gradle') -apply from: rootProject.file('_ext/gradle/p2-fat-jar-setup.gradle') -apply from: rootProject.file('gradle/java-publish.gradle') - -dependencies { - implementation "com.diffplug.spotless:spotless-eclipse-base:${VER_SPOTLESS_ECLISPE_BASE}" - // Provides text partitioners for formatters - implementation ("org.eclipse.platform:org.eclipse.jface.text:${VER_ECLISPE_JFACE}") { - exclude group: 'org.eclipse.platform', module: 'org.eclipse.swt' - } - testImplementation("org.slf4j:slf4j-simple:${VER_SLF4J}") -} - -////////// -// Test // -////////// -sourceSets { - // Use JAR file with all resources for Eclipse-Groovy integration-tests - test.runtimeClasspath = jar.outputs.files + sourceSets.test.output + sourceSets.test.compileClasspath -} diff --git a/_ext/eclipse-groovy/gradle.properties b/_ext/eclipse-groovy/gradle.properties deleted file mode 100644 index b8a21001fd..0000000000 --- a/_ext/eclipse-groovy/gradle.properties +++ /dev/null @@ -1,14 +0,0 @@ -artifactId=spotless-eclipse-groovy -description=Groovy Eclipse's formatter bundled for Spotless - -# Build requirements -VER_JAVA=11 - -# Compile -VER_ECLIPSE=4.21 -VER_SPOTLESS_ECLISPE_BASE=[3.4.2,4.0.0[ -VER_ECLISPE_JFACE=[3.15.300,4.0.0[ -VER_GRECLIPSE=4.3.0 -VER_GROOVY=4.0.0 -# Use org.eclipse.jdt.core patched for Groovy-Eclipse -VAR_GRECLIPSE_JDT_PATCH=3.27.0.v202109301420-e2109-RELEASE diff --git a/_ext/eclipse-groovy/src/test/java/com/diffplug/spotless/extra/eclipse/groovy/GrEclipseFormatterStepImplTest.java b/_ext/eclipse-groovy/src/test/java/com/diffplug/spotless/extra/eclipse/groovy/GrEclipseFormatterStepImplTest.java deleted file mode 100644 index 011c68e148..0000000000 --- a/_ext/eclipse-groovy/src/test/java/com/diffplug/spotless/extra/eclipse/groovy/GrEclipseFormatterStepImplTest.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2016-2021 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.groovy; - -import static com.diffplug.spotless.extra.eclipse.groovy.GrEclipseFormatterStepImpl.IGNORE_FORMATTER_PROBLEMS; -import static org.codehaus.groovy.eclipse.refactoring.PreferenceConstants.*; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.Properties; -import java.util.function.Consumer; - -import org.eclipse.jdt.core.JavaCore; -import org.junit.jupiter.api.Test; - -/** Smoke test checking that transitive dependencies are complete. */ -class GrEclipseFormatterStepImplTest { - - private final static TestData TEST_DATA = TestData.getTestDataOnFileSystem(); - private final static String PARSER_EXCEPTION = "class Test { void method() {} "; - private final static String SCANNER_EXCEPTION = "{"; - private final static String BOUNDED_WILDCARDS_UNFORMATTED = "foo(Map e)\n{\ne.clear();\n}"; - private final static String BOUNDED_WILDCARDS_FORMATTED = "foo(Map e) {\n\te.clear();\n}"; - - @Test - void defaultFormat() throws Throwable { - String output = format(TEST_DATA.input("nominal.test"), config -> {}); - assertEquals(TEST_DATA.expected("nominal.test"), - output, "Unexpected default formatting."); - } - - @Test - void validConfiguration() throws Throwable { - String output = format(TEST_DATA.input("nominal.test"), config -> { - config.put(GROOVY_FORMATTER_REMOVE_UNNECESSARY_SEMICOLONS, "true"); - }); - assertEquals(TEST_DATA.expected("nominal.test").replace(";", ""), - output, "Unexpected formatting for custom configuration."); - } - - @Test - void invalidConfiguration() throws Throwable { - String output = format(TEST_DATA.input("nominal.test"), config -> { - config.put(GROOVY_FORMATTER_INDENTATION, JavaCore.SPACE); - config.put(GROOVY_FORMATTER_INDENTATION_SIZE, "noInteger"); - }); - assertEquals(TEST_DATA.expected("nominal.test").replace("\t", " "), - output, "Groovy formatter does not replace invalid preferences by their defaults."); - } - - /** Test the handling AntlrParserPlugin exceptions by GroovyLogManager.manager logging */ - @Test - void parserException() throws Throwable { - assertThrows(IllegalArgumentException.class, () -> format(PARSER_EXCEPTION, config -> {})); - } - - /** Test the handling GroovyDocumentScanner exceptions by GroovyCore logging */ - @Test - void scannerException() throws Throwable { - assertThrows(IllegalArgumentException.class, () -> format(SCANNER_EXCEPTION, config -> {})); - } - - /** - * Test the handling bounded wildcards templates - * No exception since Groovy-Eclipse 3.0.0. - * Formatting fixed with Groovy-Eclipse 3.14 (org.codehaus.groovy:groovy[3.+]). - */ - @Test - void boundedWildCards() throws Throwable { - String output = format(BOUNDED_WILDCARDS_UNFORMATTED, config -> {}); - assertEquals(BOUNDED_WILDCARDS_FORMATTED, - output, "Unexpected formatting after bounded wildcards."); - } - - @Test - void ignoreCompilerProblems() throws Throwable { - Consumer ignoreCompilerProblems = config -> { - config.setProperty(IGNORE_FORMATTER_PROBLEMS, "true"); - }; - format(PARSER_EXCEPTION, ignoreCompilerProblems); - format(SCANNER_EXCEPTION, ignoreCompilerProblems); - //Test is passed if it does not throw an exception. See issue 237. - } - - private static String format(final String input, final Consumer config) throws Exception { - Properties properties = new Properties(); - config.accept(properties); - GrEclipseFormatterStepImpl formatter = new GrEclipseFormatterStepImpl(properties); - return formatter.format(input); - } - -} diff --git a/_ext/eclipse-groovy/src/test/java/com/diffplug/spotless/extra/eclipse/groovy/TestData.java b/_ext/eclipse-groovy/src/test/java/com/diffplug/spotless/extra/eclipse/groovy/TestData.java deleted file mode 100644 index b0e4f525da..0000000000 --- a/_ext/eclipse-groovy/src/test/java/com/diffplug/spotless/extra/eclipse/groovy/TestData.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2016-2021 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.groovy; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -public class TestData { - public static TestData getTestDataOnFileSystem() { - final String userDir = System.getProperty("user.dir", "."); - Path dataPath = Paths.get(userDir, "src", "test", "resources"); - if (Files.isDirectory(dataPath)) { - return new TestData(dataPath); - } - return null; - } - - private final Path inputPath; - private final Path expectedPath; - - private TestData(Path dataPath) { - inputPath = dataPath.resolve("input").toAbsolutePath(); - expectedPath = dataPath.resolve("expected").toAbsolutePath(); - for (Path testDataDir : new Path[]{inputPath, expectedPath}) { - if (!Files.isDirectory(testDataDir)) { - throw new IllegalArgumentException(String.format("'%1$s' is not a directory.", testDataDir)); - } - } - } - - public String input(final String fileName) throws Exception { - return read(inputPath.resolve(fileName)); - } - - public String expected(final String fileName) { - Path path = expectedPath.resolve(fileName); - return read(path); - } - - private String read(final Path xmlPath) { - if (!Files.isRegularFile(xmlPath)) { - throw new IllegalArgumentException(String.format("'%1$s' is not a regular file.", xmlPath)); - } - try { - String checkedOutFileContent = new String(java.nio.file.Files.readAllBytes(xmlPath), "UTF8"); - return checkedOutFileContent.replace("\r", ""); //Align GIT end-of-line normalization - } catch (IOException e) { - throw new IllegalArgumentException(String.format("Failed to read '%1$s'.", xmlPath), e); - } - } - -} diff --git a/_ext/eclipse-groovy/src/test/resources/expected/nominal.test b/_ext/eclipse-groovy/src/test/resources/expected/nominal.test deleted file mode 100644 index a40d247bad..0000000000 --- a/_ext/eclipse-groovy/src/test/resources/expected/nominal.test +++ /dev/null @@ -1,13 +0,0 @@ -class TestClass { - def a; - - TestClass(String s) { - this.a = s - } - def methodNamedArgs(Map args) { - "named args: $args" - } -} - -def t = new TestClass() -def arr = [4, 'string1', 'string2'] diff --git a/_ext/eclipse-groovy/src/test/resources/input/nominal.test b/_ext/eclipse-groovy/src/test/resources/input/nominal.test deleted file mode 100644 index 85f742677f..0000000000 --- a/_ext/eclipse-groovy/src/test/resources/input/nominal.test +++ /dev/null @@ -1,15 +0,0 @@ -class TestClass { - def a; - -TestClass(String s) { -this.a = s -} - def methodNamedArgs(Map args) { - "named args: $args" - } -} - -def t = new TestClass() -def arr = [4, -'string1', -'string2'] diff --git a/_ext/eclipse-jdt/CHANGES.md b/_ext/eclipse-jdt/CHANGES.md deleted file mode 100644 index d698ce2c6c..0000000000 --- a/_ext/eclipse-jdt/CHANGES.md +++ /dev/null @@ -1,15 +0,0 @@ -# spotless-eclipse-jdt - -We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `4.8.0`). - -## [Unreleased] - -## [4.8.1] - 2021-10-04 -### Changed -* Bumped minimum supported Eclipse JDT core version to 3.27.0 -* `format` interface requires source file information to distinguish module-info from compilation unit. Old interface marked as deprecated. -### Fixed -* Fixed module-info formatting. Previous versions did not recognized content and skipped formatting. - -## [4.8.0] - 2018-07-19 -* Initial release! diff --git a/_ext/eclipse-jdt/LICENSE.txt b/_ext/eclipse-jdt/LICENSE.txt deleted file mode 100644 index 3d967aee74..0000000000 --- a/_ext/eclipse-jdt/LICENSE.txt +++ /dev/null @@ -1,70 +0,0 @@ -Eclipse Public License - v 1.0 - -THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. - -1. DEFINITIONS - -"Contribution" means: - -a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and -b) in the case of each subsequent Contributor: -i) changes to the Program, and -ii) additions to the Program; -where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. -"Contributor" means any person or entity that distributes the Program. - -"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. - -"Program" means the Contributions distributed in accordance with this Agreement. - -"Recipient" means anyone who receives the Program under this Agreement, including all Contributors. - -2. GRANT OF RIGHTS - -a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. -b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. -c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. -d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. -3. REQUIREMENTS - -A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: - -a) it complies with the terms and conditions of this Agreement; and -b) its license agreement: -i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; -ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; -iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and -iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. -When the Program is made available in source code form: - -a) it must be made available under this Agreement; and -b) a copy of this Agreement must be included with each copy of the Program. -Contributors may not remove or alter any copyright notices contained within the Program. - -Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. - -4. COMMERCIAL DISTRIBUTION - -Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. - -For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. - -5. NO WARRANTY - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. - -6. DISCLAIMER OF LIABILITY - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -7. GENERAL - -If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. - -If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. - -All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. - -Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. - -This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. \ No newline at end of file diff --git a/_ext/eclipse-jdt/README.md b/_ext/eclipse-jdt/README.md deleted file mode 100644 index 624e577e0d..0000000000 --- a/_ext/eclipse-jdt/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# spotless-eclipse-jdt - -Eclipse JDT and its dependencies require a large amount of byte code. -Hence they should not be directly be required by the Spotless, but only be requested in case -they are configured by the Spotless configuration. Hence we publish Eclipse's formatter and all its dependencies, along with a small amount of glue code, into the `com.diffplug.gradle.spotless:spotless-eclipse-jdt` artifact. - -To publish a new version, update the `_ext/eclipse-jdt/gradle.properties` appropriately and see [CONTRIBUTING.md](../../CONTRIBUTING.md) how to enable -`_ext` projects. - -## License - -Spotless at large is under the Apache 2.0 license, but this jar is under the EPL v1. diff --git a/_ext/eclipse-jdt/build.gradle b/_ext/eclipse-jdt/build.gradle deleted file mode 100644 index b0967822a9..0000000000 --- a/_ext/eclipse-jdt/build.gradle +++ /dev/null @@ -1,19 +0,0 @@ -apply from: rootProject.file('_ext/gradle/update-lockfile.gradle') -apply from: rootProject.file('_ext/gradle/java-setup.gradle') -apply from: rootProject.file('gradle/java-publish.gradle') - -ext { - developers = [ - fvgh: [ name: 'Frank Vennemeyer', email: 'frankgh@zoho.com' ], - nedtwigg: [ name: 'Ned Twigg', email: 'ned.twigg@diffplug.com' ], - ] -} - -dependencies { - implementation "com.diffplug.spotless:spotless-eclipse-base:${VER_SPOTLESS_ECLISPE_BASE}" - implementation("org.eclipse.jdt:org.eclipse.jdt.core:${VER_ECLIPSE_JDT_CORE}") { - exclude group: 'org.eclipse.platform', module: 'org.eclipse.ant.core' - exclude group: 'org.eclipse.platform', module: 'org.eclipse.core.expressions' - exclude group: 'org.eclipse.platform', module: 'org.eclipse.core.filesystem' - } -} \ No newline at end of file diff --git a/_ext/eclipse-jdt/gradle.properties b/_ext/eclipse-jdt/gradle.properties deleted file mode 100644 index 82a4423140..0000000000 --- a/_ext/eclipse-jdt/gradle.properties +++ /dev/null @@ -1,9 +0,0 @@ -artifactId=spotless-eclipse-jdt -description=Eclipse's JDT formatter bundled for Spotless - -# Build requirements -VER_JAVA=11 - -# Compile -VER_ECLIPSE_JDT_CORE=[3.13.0,4.0.0[ -VER_SPOTLESS_ECLISPE_BASE=[3.5.0,4.0.0[ diff --git a/_ext/eclipse-jdt/src/test/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtFormatterStepImplTest.java b/_ext/eclipse-jdt/src/test/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtFormatterStepImplTest.java deleted file mode 100644 index cef63072c9..0000000000 --- a/_ext/eclipse-jdt/src/test/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtFormatterStepImplTest.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2016-2021 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.java; - -import static org.junit.jupiter.api.Assertions.*; - -import java.io.File; -import java.io.IOException; -import java.util.Properties; - -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import com.diffplug.spotless.FormatterProperties; -import com.diffplug.spotless.ResourceHarness; - -/** Eclipse JDT wrapper integration tests */ -class EclipseJdtFormatterStepImplTest extends ResourceHarness { - - private static String UNFORMATTED; - private static String FORMATTED; - - @BeforeAll - static void beforeAll() throws IOException { - UNFORMATTED = getTestResource("java/eclipse/JavaCodeUnformatted.test"); - FORMATTED = getTestResource("java/eclipse/JavaCodeFormatted.test"); - } - - private Properties config; - - @BeforeEach - void beforeEach() throws IOException { - File settingsFile = createTestFile("java/eclipse/formatter.xml"); - config = FormatterProperties.from(settingsFile).getProperties(); - } - - private final static String ILLEGAL_CHAR = Character.toString((char) 254); - - @Test - void nominal() throws Throwable { - assertEquals(FORMATTED, format(UNFORMATTED)); - } - - @Test - public void invalidSyntax() throws Throwable { - try { - String invalidSyntax = FORMATTED.replace("{", ""); - assertEquals(invalidSyntax, format(invalidSyntax)); - } catch (IndexOutOfBoundsException e) { - /* - * Some JDT versions throw exception, but this changed again in later versions. - * Anyhow, exceptions are acceptable, since Spotless should format valid Java code. - */ - } - } - - @Test - void invalidCharater() throws Throwable { - String invalidInput = UNFORMATTED + ILLEGAL_CHAR; - assertEquals(invalidInput, format(invalidInput)); - } - - @Test - void invalidConfiguration() throws Throwable { - config.setProperty(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR, JavaCore.SPACE); - config.setProperty(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE, "noInteger"); - String defaultTabReplacement = " "; - assertEquals(FORMATTED.replace("\t", defaultTabReplacement), format(FORMATTED)); - } - - @Test - void htmlPreTag() throws Throwable { - config.clear(); - config.setProperty( - DefaultCodeFormatterConstants.FORMATTER_COMMENT_FORMAT_HEADER, - DefaultCodeFormatterConstants.TRUE); - String formatted = getTestResource("java/eclipse/HtmlPreTagFormatted.test"); - String unformatted = getTestResource("java/eclipse/HtmlPreTagUnformatted.test"); - assertEquals(formatted, format(unformatted), "Failed to create internal code formatter. See Spotless issue #191"); - } - - @Test - void moduleInfo() throws Throwable { - File settingsFile = createTestFile("java/eclipse/ModuleInfo.prefs"); - config = FormatterProperties.from(settingsFile).getProperties(); - String formatted = getTestResource("java/eclipse/ModuleInfoFormatted.test"); - String unformatted = getTestResource("java/eclipse/ModuleInfoUnformatted.test"); - assertEquals(formatted, format(unformatted, "whatever/module-info.java"), "Jvm9 module info not formatted."); - } - - private String format(final String input) throws Exception { - return format(input, ""); - } - - private String format(final String input, final String fileName) throws Exception { - EclipseJdtFormatterStepImpl formatter = new EclipseJdtFormatterStepImpl(config); - return formatter.format(input, new File(fileName)); - } - -} diff --git a/_ext/eclipse-wtp/CHANGES.md b/_ext/eclipse-wtp/CHANGES.md deleted file mode 100644 index 6fc919aa0e..0000000000 --- a/_ext/eclipse-wtp/CHANGES.md +++ /dev/null @@ -1,74 +0,0 @@ -# spotless-eclipse-wtp - -We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.15.1`). - -## [Unreleased] - -## [3.23.0] - 2021-09-22 -### Added -* Switch to Web Tools Platform release 3.23.0 for Eclipse 4.21. - -## [3.22.0] - 2021-06-27 -### Added -* Switch to Web Tools Platform release 3.22.0 for Eclipse 4.20. - -## [3.21.0] - 2021-06-07 -### Added -* Switch to Web Tools Platform release 3.21.0 for Eclipse 4.19. Minimum required Java version changed from 8 to 11. - -## [3.20.0] - 2020-12-26 -### Added -* Switch to Web Tools Platform release 3.20.0 for Eclipse 4.18. - -## [3.19.0] - 2020-10-17 -### Added -* Fixed version number determined by change log. - -## [3.18.1] - 2020-10-17 -### Added -* Switch to Web Tools Platform release 3.19.0 for Eclipse 4.17. - -## [3.18.0] - 2020-10-03 -### Added -* Switch to Web Tools Platform release 3.18.0 for Eclipse 4.16. - -## [3.17.0] - 2020-10-03 -### Added -* Switch to Web Tools Platform release 3.17.0 for Eclipse 4.15. - -## [3.16.0] - 2020-09-26 -### Added -* Switch to Web Tools Platform release 3.16.0 for Eclipse 4.14. - -## [3.15.3] - 2020-03-26 -### Fixed -* Handling of character encodings on OS with non-unicode default file encoding format. CSS, HTML and JSON formatter steps encoded intermediately the input using the default file encoding, which was not lossless if the default file encoding did not support the full unicode character set. ([#545](https://github.com/diffplug/spotless/issues/545)). - -## [3.15.2] - 2020-03-04 -### Fixed -* Racing conditions in WTP formatter configurations. Multiple configurations within the same project are no longer supported. ([#492](https://github.com/diffplug/spotless/pull/492)). - -## [3.15.1] - 2019-11-27 -* Bugfix: Fix NPE in EclipseXmlFormatterStepImpl ([#490](https://github.com/diffplug/spotless/pull/490)). - -## [3.15.0] - 2019-11-06 -* Switch to Web Tools Platform release 3.15.0 for Eclipse 4.13 ([#480](https://github.com/diffplug/spotless/issues/480)). - -## [3.14.0] - 2019-06-24 -* Switch to Web Tools Platform release 3.14.0 for Eclipse 4.12 ([#423](https://github.com/diffplug/spotless/pull/423)). - -## [3.10.0] - 2019-03-17 -* Switch to Web Tools Platform release 3.10.0 for Eclipse 4.8 ([#378](https://github.com/diffplug/spotless/pull/378)). -* Include Eclipse logging allowing formatter warnings/errors to be logged via SLF4J ([#236](https://github.com/diffplug/spotless/issues/236)). - -## [3.9.8] - 2019-03-10 -* XML formatter ignores external URIs per default. ([#369](https://github.com/diffplug/spotless/issues/369)). Add `resolveExternalURI=true` property to switch to previous behavior. - -## [3.9.7] - 2019-02-25 -* Replaced `http` update-site with `https` ([#360](https://github.com/diffplug/spotless/issues/360)). - -## [3.9.6] - 2019-02-11 -* Fixed formatting of JSON arrays ([#344](https://github.com/diffplug/spotless/issues/344)). - -## [3.9.5] - 2018-08-08 -* Initial release! diff --git a/_ext/eclipse-wtp/LICENSE.txt b/_ext/eclipse-wtp/LICENSE.txt deleted file mode 100644 index 3d967aee74..0000000000 --- a/_ext/eclipse-wtp/LICENSE.txt +++ /dev/null @@ -1,70 +0,0 @@ -Eclipse Public License - v 1.0 - -THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. - -1. DEFINITIONS - -"Contribution" means: - -a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and -b) in the case of each subsequent Contributor: -i) changes to the Program, and -ii) additions to the Program; -where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. -"Contributor" means any person or entity that distributes the Program. - -"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. - -"Program" means the Contributions distributed in accordance with this Agreement. - -"Recipient" means anyone who receives the Program under this Agreement, including all Contributors. - -2. GRANT OF RIGHTS - -a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. -b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. -c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. -d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. -3. REQUIREMENTS - -A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: - -a) it complies with the terms and conditions of this Agreement; and -b) its license agreement: -i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; -ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; -iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and -iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. -When the Program is made available in source code form: - -a) it must be made available under this Agreement; and -b) a copy of this Agreement must be included with each copy of the Program. -Contributors may not remove or alter any copyright notices contained within the Program. - -Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. - -4. COMMERCIAL DISTRIBUTION - -Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. - -For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. - -5. NO WARRANTY - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. - -6. DISCLAIMER OF LIABILITY - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -7. GENERAL - -If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. - -If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. - -All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. - -Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. - -This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. \ No newline at end of file diff --git a/_ext/eclipse-wtp/README.md b/_ext/eclipse-wtp/README.md deleted file mode 100644 index 41dd58ff9b..0000000000 --- a/_ext/eclipse-wtp/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# spotless-eclipse-wtp - -Eclipse WTP is not available in a form which can be easily consumed by maven or gradle. To fix this, we publish Eclipse's WTP formatters, along with a small amount of glue code, into the `com.diffplug.spotless.extra:spotless-eclipse-wtp` artifact. - -To publish a new version, update the `_ext/eclipse-wtp/gradle.properties` appropriately and see [CONTRIBUTING.md](../../CONTRIBUTING.md) how to enable -`_ext` projects. - -## License - -Spotless at large is under the Apache 2.0 license, but this jar is under the EPL v1. diff --git a/_ext/eclipse-wtp/build.gradle b/_ext/eclipse-wtp/build.gradle deleted file mode 100644 index 4001e100c2..0000000000 --- a/_ext/eclipse-wtp/build.gradle +++ /dev/null @@ -1,126 +0,0 @@ -ext { - developers = [ - fvgh: [ name: 'Frank Vennemeyer', email: 'frankgh@zoho.com' ], - ] - - p2Repository = "https://download.eclipse.org/webtools/repository/${VER_ECLIPSE_WTP}" - - p2Dependencies = [ - // XML/HTML Formatter - Dependencies - 'org.eclipse.wst.xml.core':'+', // DefaultXMLPartitionFormatter and XMLAssociationProvider - 'org.eclipse.wst.sse.core':'+', // Structure models - 'org.eclipse.wst.common.uriresolver':'+', // URI resolver for model queries - 'org.eclipse.wst.dtd.core':'+', // Support DTD extensions - - // XML Formatter - Dependencies - 'org.eclipse.wst.xsd.core':'+', // Support XSD extensions - - // JS Formatter - Dependencies - 'org.eclipse.wst.jsdt.core':'+', // DefaultCodeFormatter and related - 'org.eclipse.wst.jsdt.ui':'+', // Functionality to format comments - - // JSON Formatter - Dependencies - 'org.eclipse.wst.json.core':'+', // FormatProcessorJSON and related - 'org.eclipse.json':'+', // Provides JSON node interfaces - - // CSS Formatter - Dependencies - 'org.eclipse.wst.css.core':'+', // FormatProcessorCSS and related - - // HTML Formatter - Dependencies - 'org.eclipse.wst.html.core':'+', // HTMLFormatProcessorImpl and related - ] - - jarInclude = [ - '**/*.class', - // Take all classes - '**/*.properties', - // Text resources (for messages, etc) - '**/*.rsc', - // JSDT requires gramar files - '**/*.xml', - // Plugin XML and other resources - '*.html', - // License information about the included JARs, - 'META-INF/**' // Plugin manifest and addtional information - ] - - fatJarResourcesMap = [ - 'org.eclipse.wst.common.uriresolver': 'org.eclipse.wst.common.uriresolver.internal.provisional', - 'org.eclipse.wst.css.core': 'org.eclipse.wst.css.core.internal', - 'org.eclipse.wst.dtd.core': 'org.eclipse.wst.dtd.core.internal', - 'org.eclipse.wst.html.core': 'org.eclipse.wst.html.core.internal', - 'org.eclipse.wst.sse.core': 'org.eclipse.wst.sse.core.internal.encoding.util', - 'org.eclipse.wst.xml.core': 'org.eclipse.wst.xml.core.internal', - 'org.eclipse.wst.xsd.core': 'org.eclipse.wst.xsd.core.internal' - ] -} - -apply from: rootProject.file('_ext/gradle/update-lockfile.gradle') -apply from: rootProject.file('_ext/gradle/p2-fat-jar-setup.gradle') -apply from: rootProject.file('gradle/java-publish.gradle') - -dependencies { - implementation "com.diffplug.spotless:spotless-eclipse-base:${VER_SPOTLESS_ECLISPE_BASE}" - // Required by most WPT formatters - implementation "com.ibm.icu:icu4j:${VER_IBM_ICU}" - // The XSD/DTD and other models are defined with EMF. - implementation "org.eclipse.emf:org.eclipse.emf.common:${VER_ECLISPE_EMF}" - implementation "org.eclipse.emf:org.eclipse.emf.ecore:${VER_ECLISPE_EMF}" - // Some WPT plugins requires OSGI bundle interfaces (but not effectively used) - implementation "org.eclipse.platform:org.eclipse.osgi.services:${VER_ECLIPSE_OSGI_SERVICES}" - // Provides document data structure and file buffers for formatters - implementation "org.eclipse.platform:org.eclipse.core.filebuffers:${VER_ECLIPSE_FILE_BUFFERS}" - // Provides text partitioners for formatters - implementation ("org.eclipse.platform:org.eclipse.jface.text:${VER_ECLISPE_JFACE}") { - exclude group: 'org.eclipse.platform', module: 'org.eclipse.swt' - } - // Some WPT plugins use the EFS for storing temporary worspace data - implementation "org.eclipse.platform:org.eclipse.core.filesystem:${VER_ECLISPE_EFS}" - // Required by org.eclipse.wst.xsd.core - implementation "org.eclipse.emf:org.eclipse.xsd:${VER_ECLISPE_XSD}" - - testImplementation("org.slf4j:slf4j-simple:${VER_SLF4J}") -} - -jar { - manifest { - from 'src/main/resources/META-INF/MANIFEST.MF' - } -} - -////////// -// Test // -////////// -sourceSets { - // Use JAR file with all resources for Eclipse-WTP integration-tests - test.runtimeClasspath = jar.outputs.files + sourceSets.test.output + sourceSets.test.compileClasspath -} - -/* - * All test classes need to run separately since they all instatiate different setups of the - * Eclipse framework. - */ -test { - //Skip default tests, which would run every test case. - exclude '**' -} - -//Instead make a separate test task per case -def testLocation = 'src/test/java' -fileTree(dir: testLocation).include('**/*Test.java').each { file -> - def testFile = file.getName().replace(".java", "") - def filePath = file.getAbsolutePath().replace(".java", "**") //Don't ask me why the task is not happy when it gets no asterisk - filePath = filePath.substring(filePath.lastIndexOf(testLocation) + testLocation.length() + 1) - task "${testFile}"(type: Test) { - group = LifecycleBasePlugin.VERIFICATION_GROUP - description = "Runs ${testFile} integration test." - include "${filePath}" - reports { - html.destination = new File("$buildDir/reports/${testFile}") - junitXml.destination = new File("$buildDir/${testFile}") - } - //classpath = jar.outputs.files + sourceSets.test.output + sourceSets.test.compileClasspath - mustRunAfter tasks.jar - } - test.dependsOn "${testFile}" -} diff --git a/_ext/eclipse-wtp/gradle.properties b/_ext/eclipse-wtp/gradle.properties deleted file mode 100644 index 743748d1ba..0000000000 --- a/_ext/eclipse-wtp/gradle.properties +++ /dev/null @@ -1,16 +0,0 @@ -artifactId=spotless-eclipse-wtp -description=Eclipse's WTP formatters bundled for Spotless - -# Build requirements -VER_JAVA=11 - -# Compile -VER_ECLIPSE_WTP=2021-09 -VER_SPOTLESS_ECLISPE_BASE=[3.5.0,4.0.0[ -VER_IBM_ICU=[67.1,68[ -VER_ECLISPE_EMF=[2.22.0,3.0.0[ -VER_ECLIPSE_OSGI_SERVICES=[3.10.0,4.0.0[ -VER_ECLIPSE_FILE_BUFFERS=[3.7.0,4.0.0[ -VER_ECLISPE_JFACE=[3.18.0,4.0.0[ -VER_ECLISPE_EFS=[1.9.0,2.0.0[ -VER_ECLISPE_XSD=[2.18.0,3.0.0[ diff --git a/_ext/eclipse-wtp/spotless.xmlformat.prefs b/_ext/eclipse-wtp/spotless.xmlformat.prefs deleted file mode 100644 index 3bbd7ecd72..0000000000 --- a/_ext/eclipse-wtp/spotless.xmlformat.prefs +++ /dev/null @@ -1,4 +0,0 @@ -eclipse.preferences.version=1 -indentationChar=space -indentationSize=2 -lineWidth=999 diff --git a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseCssFormatterStepImpl.java b/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseCssFormatterStepImpl.java deleted file mode 100644 index fb0e1ffeac..0000000000 --- a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseCssFormatterStepImpl.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.wtp; - -import java.util.Properties; - -import org.eclipse.wst.css.core.internal.CSSCorePlugin; -import org.eclipse.wst.css.core.internal.cleanup.CleanupProcessorCSS; -import org.eclipse.wst.css.core.internal.preferences.CSSCorePreferenceInitializer; -import org.eclipse.wst.sse.core.internal.cleanup.AbstractStructuredCleanupProcessor; - -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipsePluginConfig; -import com.diffplug.spotless.extra.eclipse.wtp.sse.CleanupStep; -import com.diffplug.spotless.extra.eclipse.wtp.sse.PluginPreferences; - -/** Formatter step which calls out to the Eclipse CSS cleanup and formatter. */ -public class EclipseCssFormatterStepImpl extends CleanupStep { - - public EclipseCssFormatterStepImpl(Properties properties) throws Exception { - super(new CleanupProcessor(), new FrameworkConfig(properties)); - PluginPreferences.assertNoChanges(CSSCorePlugin.getDefault(), properties); - } - - /** - * The FormatProcessorCSS does not allow a strict case formatting. - * Hence additionally the CleanupProcessorCSS is used. - */ - private static class CleanupProcessor extends CleanupProcessorCSS implements CleanupStep.ProcessorAccessor { - @Override - public String getTypeId() { - return getContentType(); - } - - @Override - public void refreshPreferences() { - refreshCleanupPreferences(); - } - - @Override - public AbstractStructuredCleanupProcessor get() { - return this; - } - } - - static class FrameworkConfig extends CleanupStep.FrameworkConfig { - private final Properties properties; - - FrameworkConfig(Properties properties) { - this.properties = properties; - } - - @Override - public void activatePlugins(SpotlessEclipsePluginConfig config) { - super.activatePlugins(config); - activateCssPlugins(config); - } - - static void activateCssPlugins(SpotlessEclipsePluginConfig config) { - config.add(new CSSCorePlugin()); - } - - @Override - public void customize() { - PluginPreferences.configure(CSSCorePlugin.getDefault(), new CSSCorePreferenceInitializer(), properties); - } - } -} diff --git a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseHtmlFormatterStepImpl.java b/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseHtmlFormatterStepImpl.java deleted file mode 100644 index 4b7f592c3c..0000000000 --- a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseHtmlFormatterStepImpl.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.wtp; - -import static com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework.LINE_DELIMITER; - -import java.util.Arrays; -import java.util.List; -import java.util.Properties; - -import org.eclipse.wst.html.core.internal.HTMLCorePlugin; -import org.eclipse.wst.html.core.internal.cleanup.HTMLCleanupProcessorImpl; -import org.eclipse.wst.html.core.internal.encoding.HTMLDocumentLoader; -import org.eclipse.wst.html.core.internal.format.HTMLFormatProcessorImpl; -import org.eclipse.wst.html.core.internal.preferences.HTMLCorePreferenceInitializer; -import org.eclipse.wst.html.core.text.IHTMLPartitions; -import org.eclipse.wst.jsdt.core.JavaScriptCore; -import org.eclipse.wst.jsdt.core.ToolFactory; -import org.eclipse.wst.jsdt.core.formatter.CodeFormatter; -import org.eclipse.wst.sse.core.internal.cleanup.AbstractStructuredCleanupProcessor; -import org.eclipse.wst.sse.core.internal.format.IStructuredFormatProcessor; -import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; - -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseConfig; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseCoreConfig; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipsePluginConfig; -import com.diffplug.spotless.extra.eclipse.wtp.html.JsRegionProcessor; -import com.diffplug.spotless.extra.eclipse.wtp.html.StructuredDocumentProcessor; -import com.diffplug.spotless.extra.eclipse.wtp.sse.CleanupStep; -import com.diffplug.spotless.extra.eclipse.wtp.sse.PluginPreferences; - -/** Formatter step which calls out to the Eclipse HTML cleanup and formatter. */ -public class EclipseHtmlFormatterStepImpl extends CleanupStep { - - private final String htmlFormatterIndent; - private final CodeFormatter jsFormatter; - - public EclipseHtmlFormatterStepImpl(Properties properties) throws Exception { - super(new CleanupProcessor(), new FrameworkConfig(properties)); - PluginPreferences.assertNoChanges(HTMLCorePlugin.getDefault(), properties); - htmlFormatterIndent = ((CleanupProcessor) processorAccessor).getIndent(); - jsFormatter = ToolFactory.createCodeFormatter(JavaScriptCore.getOptions(), ToolFactory.M_FORMAT_EXISTING); - } - - @Override - public String format(String raw) throws Exception { - raw = super.format(raw); - - // Not sure how Eclipse binds the JS formatter to HTML. The formatting is accomplished manually instead. - IStructuredDocument document = (IStructuredDocument) new HTMLDocumentLoader().createNewStructuredDocument(); - document.setPreferredLineDelimiter(LINE_DELIMITER); - document.set(raw); - StructuredDocumentProcessor jsProcessor = new StructuredDocumentProcessor( - document, IHTMLPartitions.SCRIPT, JsRegionProcessor.createFactory(htmlFormatterIndent)); - jsProcessor.apply(jsFormatter); - - return document.get(); - } - - /** - * * The WTP {@link HTMLFormatProcessorImpl} does not allow a strict case formatting. - * Hence additionally the {@link HTMLCleanupProcessorImpl} is used. - *

- * Note that a preferences like {@code TAG_NAME_CASE} are not used by the - * formatter, though configurable in the formatters preference GUI. - * The user must instead configure for example {@code CLEANUP_TAG_NAME_CASE} - * in the cleanup GUI. - *

- */ - private static class CleanupProcessor extends HTMLCleanupProcessorImpl implements CleanupStep.ProcessorAccessor { - private HTMLFormatProcessorImpl processor; - - CleanupProcessor() { - processor = new HTMLFormatProcessorImpl(); - } - - @Override - public String getTypeId() { - return getContentType(); - } - - @Override - public void refreshPreferences() { - refreshCleanupPreferences(); - processor = new HTMLFormatProcessorImpl(); //Constructor reads new preferences - processor.refreshFormatPreferences = false; //Don't refresh when cloning - } - - @Override - public AbstractStructuredCleanupProcessor get() { - return this; - } - - @Override - protected IStructuredFormatProcessor getFormatProcessor() { - return processor; - } - - String getIndent() { - return processor.getFormatPreferences().getIndent(); - } - - } - - private static class FrameworkConfig extends CleanupStep.FrameworkConfig { - private final List dependentConfigs; - private final Properties properties; - - public FrameworkConfig(Properties properties) { - dependentConfigs = Arrays.asList( - new EclipseCssFormatterStepImpl.FrameworkConfig(properties), - new EclipseJsFormatterStepImpl.FrameworkConfig(properties), - new EclipseXmlFormatterStepImpl.FrameworkConfig(properties)); - this.properties = properties; - } - - @Override - public void registerBundles(SpotlessEclipseCoreConfig config) { - EclipseJsFormatterStepImpl.FrameworkConfig.registerNonHeadlessBundles(config); - } - - @Override - public void activatePlugins(SpotlessEclipsePluginConfig config) { - super.activatePlugins(config); - EclipseCssFormatterStepImpl.FrameworkConfig.activateCssPlugins(config); - EclipseJsFormatterStepImpl.FrameworkConfig.activateJsPlugins(config); - /* - * The HTML formatter only uses the DOCTYPE/SCHEMA for content model selection. - * Hence no external URIs are required. - */ - boolean allowExternalURI = false; - EclipseXmlFormatterStepImpl.FrameworkConfig.activateXmlPlugins(config, allowExternalURI); - config.add(new HTMLCorePlugin()); - } - - @Override - public void customize() { - dependentConfigs.stream().forEach(c -> { - c.customize(); - }); - PluginPreferences.configure(HTMLCorePlugin.getDefault(), new HTMLCorePreferenceInitializer(), properties); - } - - } - -} diff --git a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseJsFormatterStepImpl.java b/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseJsFormatterStepImpl.java deleted file mode 100644 index c101b1a27a..0000000000 --- a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseJsFormatterStepImpl.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.wtp; - -import static com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework.DefaultBundles.*; -import static com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework.LINE_DELIMITER; - -import java.util.AbstractMap.SimpleEntry; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.Document; -import org.eclipse.jface.text.IDocument; -import org.eclipse.jface.text.IDocumentPartitioner; -import org.eclipse.jface.text.ITypedRegion; -import org.eclipse.jface.text.TextUtilities; -import org.eclipse.jface.text.TypedPosition; -import org.eclipse.jface.text.formatter.FormattingContextProperties; -import org.eclipse.jface.text.formatter.IFormattingContext; -import org.eclipse.jface.text.rules.FastPartitioner; -import org.eclipse.text.edits.MultiTextEdit; -import org.eclipse.text.edits.TextEdit; -import org.eclipse.wst.jsdt.core.JavaScriptCore; -import org.eclipse.wst.jsdt.core.ToolFactory; -import org.eclipse.wst.jsdt.core.formatter.CodeFormatter; -import org.eclipse.wst.jsdt.core.formatter.DefaultCodeFormatterConstants; -import org.eclipse.wst.jsdt.internal.ui.text.FastJavaPartitionScanner; -import org.eclipse.wst.jsdt.internal.ui.text.comment.CommentFormattingContext; -import org.eclipse.wst.jsdt.internal.ui.text.comment.CommentFormattingStrategy; -import org.eclipse.wst.jsdt.ui.text.IJavaScriptPartitions; -import org.osgi.framework.Bundle; - -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseConfig; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseCoreConfig; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipsePluginConfig; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseServiceConfig; -import com.diffplug.spotless.extra.eclipse.wtp.sse.PluginPreferences; - -/** Formatter step which calls out to the Eclipse JS formatter. */ -public class EclipseJsFormatterStepImpl { - - private final static String[] COMMENT_TYPES = { - IJavaScriptPartitions.JAVA_DOC, - IJavaScriptPartitions.JAVA_MULTI_LINE_COMMENT, - IJavaScriptPartitions.JAVA_SINGLE_LINE_COMMENT, - IJavaScriptPartitions.JAVA_STRING, - IJavaScriptPartitions.JAVA_CHARACTER - }; - - private final static Map OPTION_2_COMMENT_TYPE = Collections.unmodifiableMap(Stream.of( - new SimpleEntry<>(DefaultCodeFormatterConstants.FORMATTER_COMMENT_FORMAT_LINE_COMMENT, IJavaScriptPartitions.JAVA_SINGLE_LINE_COMMENT), - new SimpleEntry<>(DefaultCodeFormatterConstants.FORMATTER_COMMENT_FORMAT_BLOCK_COMMENT, IJavaScriptPartitions.JAVA_MULTI_LINE_COMMENT), - new SimpleEntry<>(DefaultCodeFormatterConstants.FORMATTER_COMMENT_FORMAT_JAVADOC_COMMENT, IJavaScriptPartitions.JAVA_DOC)).collect(Collectors.toMap((e) -> e.getKey(), (e) -> e.getValue()))); - - private final CodeFormatter formatter; - private final Hashtable options; - private final Set commentTypesToBeFormatted; - - public EclipseJsFormatterStepImpl(Properties properties) throws Exception { - SpotlessEclipseFramework.setup(new FrameworkConfig(properties)); - PluginPreferences.assertNoChanges(JavaScriptCore.getPlugin(), properties); - options = JavaScriptCore.getOptions(); - commentTypesToBeFormatted = OPTION_2_COMMENT_TYPE.entrySet().stream().filter(x -> DefaultCodeFormatterConstants.TRUE.equals(options.get(x.getKey()))).map(x -> x.getValue()).collect(Collectors.toSet()); - formatter = ToolFactory.createCodeFormatter(options, ToolFactory.M_FORMAT_EXISTING); - } - - /** Formatting JavaScript string */ - public String format(String raw) throws Exception { - raw = formatComments(raw); - // The comment formatter messed up the code a little bit (adding some line breaks). Now we format the code. - IDocument doc = new Document(raw); - TextEdit edit = formatter.format(CodeFormatter.K_JAVASCRIPT_UNIT, raw, 0, raw.length(), 0, LINE_DELIMITER); - if (edit == null) { - throw new IllegalArgumentException("Invalid JavaScript syntax for formatting."); - } else { - edit.apply(doc); - } - return doc.get(); - } - - /** - * Comment formats like it would be accomplished by the JDTS UI, without setting up the UI. - * @see org.eclipse.wst.jsdt.internal.ui.fix.CommentFormatFix - */ - private String formatComments(String raw) { - Document doc = new Document(raw); - IDocumentPartitioner commentPartitioner = new FastPartitioner(new FastJavaPartitionScanner(), COMMENT_TYPES); - doc.setDocumentPartitioner(IJavaScriptPartitions.JAVA_PARTITIONING, commentPartitioner); - commentPartitioner.connect(doc); - CommentFormattingStrategy commentFormatter = new CommentFormattingStrategy(); - IFormattingContext context = new CommentFormattingContext(); - context.setProperty(FormattingContextProperties.CONTEXT_PREFERENCES, options); - context.setProperty(FormattingContextProperties.CONTEXT_DOCUMENT, Boolean.TRUE); - context.setProperty(FormattingContextProperties.CONTEXT_MEDIUM, doc); - try { - ITypedRegion[] regions = TextUtilities.computePartitioning(doc, IJavaScriptPartitions.JAVA_PARTITIONING, 0, doc.getLength(), false); - MultiTextEdit resultEdit = new MultiTextEdit(); - Arrays.asList(regions).stream().filter(reg -> commentTypesToBeFormatted.contains(reg.getType())).forEach(region -> { - TypedPosition typedPosition = new TypedPosition(region.getOffset(), region.getLength(), region.getType()); - context.setProperty(FormattingContextProperties.CONTEXT_PARTITION, typedPosition); - commentFormatter.formatterStarts(context); - TextEdit edit = commentFormatter.calculateTextEdit(); - commentFormatter.formatterStops(); - if (null != edit && edit.hasChildren()) { - resultEdit.addChild(edit); - } - }); - resultEdit.apply(doc); - return doc.get(); - } catch (BadLocationException e) { - //Silently ignore comment formatting exceptions and return the original string - return raw; - } - } - - static class FrameworkConfig implements SpotlessEclipseConfig { - private final Properties properties; - - FrameworkConfig(Properties properties) { - this.properties = properties; - } - - @Override - public void registerServices(SpotlessEclipseServiceConfig config) { - config.applyDefault(); - config.useSlf4J(this.getClass().getPackage().getName()); - } - - @Override - public void registerBundles(SpotlessEclipseCoreConfig config) { - registerNonHeadlessBundles(config); - } - - static void registerNonHeadlessBundles(SpotlessEclipseCoreConfig config) { - //The JS model requires the JDT indexer, hence a headless Eclipse cannot be used. - config.add(PLATFORM, Bundle.ACTIVE); - config.add(REGISTRY, PREFERENCES, COMMON); - } - - @Override - public void activatePlugins(SpotlessEclipsePluginConfig config) { - config.applyDefault(); - activateJsPlugins(config); - } - - static void activateJsPlugins(SpotlessEclipsePluginConfig config) { - // The JS core uses EFS for determination of temporary storage location - config.add(org.eclipse.core.filesystem.EFS.class); - // The JS core provides the JSDT formatter - config.add(new org.eclipse.wst.jsdt.core.JavaScriptCore()); - } - - @Override - @SuppressWarnings("unchecked") - public void customize() { - PluginPreferences.store(JavaScriptCore.getPlugin(), properties); - Hashtable options = JavaScriptCore.getDefaultOptions(); - options.putAll(DefaultCodeFormatterConstants.getJSLintConventionsSettings()); - options.putAll(new HashMap(properties)); - JavaScriptCore.setOptions(options); - } - - } - -} diff --git a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseJsonFormatterStepImpl.java b/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseJsonFormatterStepImpl.java deleted file mode 100644 index 91d8516b24..0000000000 --- a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseJsonFormatterStepImpl.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.wtp; - -import java.io.IOException; -import java.util.Properties; - -import org.eclipse.core.runtime.CoreException; -import org.eclipse.wst.json.core.JSONCorePlugin; -import org.eclipse.wst.json.core.cleanup.CleanupProcessorJSON; -import org.eclipse.wst.json.core.format.FormatProcessorJSON; -import org.eclipse.wst.json.core.internal.preferences.JSONCorePreferenceInitializer; -import org.eclipse.wst.sse.core.internal.cleanup.AbstractStructuredCleanupProcessor; - -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipsePluginConfig; -import com.diffplug.spotless.extra.eclipse.wtp.sse.CleanupStep; -import com.diffplug.spotless.extra.eclipse.wtp.sse.PluginPreferences; - -/** - * Formatter step which calls out to the Eclipse JSON cleanup processor and formatter. - * Note that the cleanup is escaped, since it has known bugs and is currently not used by Eclipse. - */ -public class EclipseJsonFormatterStepImpl extends CleanupStep { - - public EclipseJsonFormatterStepImpl(Properties properties) throws Exception { - super(new CleanupProcessor(), new FrameworkConfig(properties)); - PluginPreferences.assertNoChanges(JSONCorePlugin.getDefault(), properties); - } - - /** - * The JSON CleanUp is partly implemented. - *

- * For example the abstract formatter supports the - * CASE_PROPERTY_NAME configuration item. - * However, this seems to be all dead code and there seems - * to be no way in the latest Eclipse GUI to configure - * or trigger the clean-up process. - *

- *

- * Here we just use the CleanupProcessorJSON to reuse the common - * interface to trigger the formatting. - *

- * See {@code org.eclipse.wst.json.core.internal.format.AbstractJSONSourceFormatter} for details. - */ - private static class CleanupProcessor extends CleanupProcessorJSON implements CleanupStep.ProcessorAccessor { - private final FormatProcessorJSON formatter; - - CleanupProcessor() { - formatter = new FormatProcessorJSON(); - } - - @Override - public String getTypeId() { - return getContentType(); - } - - @Override - public AbstractStructuredCleanupProcessor get() { - return this; - } - - @Override - public void refreshPreferences() { - refreshCleanupPreferences(); - } - - @Override - public String cleanupContent(String input) throws IOException, CoreException { - /* - * The CleanupProcessorJSON.cleanupContent is erroneous and disabled in IDE. - * Hence the clean-up itself is replaced by a format processor. - * The SpotlessJsonCleanup still derives from the CleanupStep base class - * to use the common Spotless WTP configuration. - * - * See Spotless issue #344 for details. - */ - return formatter.formatContent(input); - } - } - - private static class FrameworkConfig extends CleanupStep.FrameworkConfig { - private final Properties properties; - - FrameworkConfig(Properties properties) { - this.properties = properties; - } - - @Override - public void activatePlugins(SpotlessEclipsePluginConfig config) { - super.activatePlugins(config); - config.add(new JSONCorePlugin()); - } - - @Override - public void customize() { - PluginPreferences.configure(JSONCorePlugin.getDefault(), new JSONCorePreferenceInitializer(), properties); - } - } - -} diff --git a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseXmlFormatterStepImpl.java b/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseXmlFormatterStepImpl.java deleted file mode 100644 index 57fce0d503..0000000000 --- a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseXmlFormatterStepImpl.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.wtp; - -import static com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework.LINE_DELIMITER; -import static org.eclipse.wst.xml.core.internal.preferences.XMLCorePreferenceNames.*; - -import java.util.Properties; - -import org.eclipse.jface.text.IDocumentPartitioner; -import org.eclipse.text.edits.TextEdit; -import org.eclipse.wst.common.uriresolver.internal.provisional.URIResolverPlugin; -import org.eclipse.wst.dtd.core.internal.DTDCorePlugin; -import org.eclipse.wst.sse.core.internal.provisional.INodeAdapterFactory; -import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; -import org.eclipse.wst.sse.core.internal.text.BasicStructuredDocument; -import org.eclipse.wst.xml.core.internal.XMLCorePlugin; -import org.eclipse.wst.xml.core.internal.catalog.Catalog; -import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.CMDocumentManager; -import org.eclipse.wst.xml.core.internal.contentmodel.modelquery.ModelQuery; -import org.eclipse.wst.xml.core.internal.document.DOMModelImpl; -import org.eclipse.wst.xml.core.internal.formatter.DefaultXMLPartitionFormatter; -import org.eclipse.wst.xml.core.internal.formatter.XMLFormattingPreferences; -import org.eclipse.wst.xml.core.internal.modelquery.ModelQueryAdapterFactoryForXML; -import org.eclipse.wst.xml.core.internal.modelquery.ModelQueryUtil; -import org.eclipse.wst.xml.core.internal.parser.XMLSourceParser; -import org.eclipse.wst.xml.core.internal.preferences.XMLCorePreferenceInitializer; -import org.eclipse.wst.xml.core.internal.text.rules.StructuredTextPartitionerForXML; -import org.eclipse.wst.xsd.core.internal.XSDCorePlugin; -import org.eclipse.xsd.util.XSDSchemaBuildingTools; - -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseConfig; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipsePluginConfig; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseServiceConfig; -import com.diffplug.spotless.extra.eclipse.wtp.sse.PluginPreferences; -import com.diffplug.spotless.extra.eclipse.wtp.sse.PreventExternalURIResolverExtension; - -/** Formatter step which calls out to the Eclipse XML formatter. */ -public class EclipseXmlFormatterStepImpl { - private final DefaultXMLPartitionFormatter formatter; - private final XMLFormattingPreferences preferences; - private final INodeAdapterFactory xmlAdapterFactory; - - public EclipseXmlFormatterStepImpl(Properties properties) throws Exception { - SpotlessEclipseFramework.setup(new FrameworkConfig(properties)); - PluginPreferences.assertNoChanges(XMLCorePlugin.getDefault(), properties); - preferences = new XMLFormattingPreferences(); - formatter = new DefaultXMLPartitionFormatter(); - //The adapter factory maintains the common CMDocumentCache - xmlAdapterFactory = new ModelQueryAdapterFactoryForXML(); - } - - static class FrameworkConfig implements SpotlessEclipseConfig { - private final Properties properties; - - FrameworkConfig(Properties properties) { - /* - * The cache is only used for system catalogs, but not for user catalogs. - * It requires the SSECorePLugin, which has either a big performance overhead, - * or needs a dirty mocking (we don't really require its functions but it needs to be there). - * So we disable the cache for now. - * This might cause a performance drop in case for example XSDs are formatted. - * But these standard/system restriction files contain anyway no formatting rules. - * So in case of performance inconveniences, we could add a Spotless property to disable the - * XSD/DTD parsing entirely (for example by adding an own URI resolver). - */ - properties.setProperty(CMDOCUMENT_GLOBAL_CACHE_ENABLED, Boolean.toString(false)); - this.properties = properties; - } - - @Override - public void registerServices(SpotlessEclipseServiceConfig config) { - config.applyDefault(); - config.useSlf4J(EclipseXmlFormatterStepImpl.class.getPackage().getName()); - } - - @Override - public void activatePlugins(SpotlessEclipsePluginConfig config) { - config.applyDefault(); - activateXmlPlugins(config, PluginPreferences.isExternalUriAllowed(properties)); - } - - static void activateXmlPlugins(SpotlessEclipsePluginConfig config, boolean allowExternalURI) { - //The WST XML formatter - config.add(new XMLCorePlugin()); - //XSDs/DTDs must be resolved by URI - config.add(new URIResolverPlugin()); - //Support formatting based on DTD restrictions - config.add(new DTDCorePlugin()); - //Support formatting based on XSD restrictions - config.add(new XSDCorePlugin()); - if (!allowExternalURI) { - config.add(new PreventExternalURIResolverExtension()); - } - } - - @Override - public void customize() { - //Register required EMF factories - XSDSchemaBuildingTools.getXSDFactory(); - PluginPreferences.configure(XMLCorePlugin.getDefault(), new XMLCorePreferenceInitializer(), properties); - PluginPreferences.configureCatalog(properties, (Catalog) XMLCorePlugin.getDefault().getDefaultXMLCatalog()); - } - - }; - - /** Formatting XML string resolving URIs according its base location */ - public String format(String raw, String baseLocation) throws Exception { - IStructuredDocument document = new BasicStructuredDocument(new XMLSourceParser()); - document.setPreferredLineDelimiter(LINE_DELIMITER); - IDocumentPartitioner partitioner = new StructuredTextPartitionerForXML(); - document.setDocumentPartitioner(new StructuredTextPartitionerForXML()); - partitioner.connect(document); - document.set(raw); - DOMModelImpl xmlDOM = new DOMModelImpl(); - xmlDOM.setBaseLocation(baseLocation); - xmlDOM.getFactoryRegistry().addFactory(xmlAdapterFactory); - xmlDOM.setStructuredDocument(document); - ModelQuery modelQuery = ModelQueryUtil.getModelQuery(xmlDOM); - modelQuery.getCMDocumentManager().setPropertyEnabled(CMDocumentManager.PROPERTY_USE_CACHED_RESOLVED_URI, true); - TextEdit formatterChanges = formatter.format(xmlDOM, 0, document.getLength(), preferences); - formatterChanges.apply(document); - return document.get(); - } - -} diff --git a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/html/JsRegionProcessor.java b/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/html/JsRegionProcessor.java deleted file mode 100644 index d856335c1b..0000000000 --- a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/html/JsRegionProcessor.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.wtp.html; - -import static com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework.LINE_DELIMITER; - -import java.util.function.BiFunction; - -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.ITypedRegion; -import org.eclipse.text.edits.MalformedTreeException; -import org.eclipse.text.edits.MultiTextEdit; -import org.eclipse.text.edits.TextEdit; -import org.eclipse.wst.jsdt.core.formatter.CodeFormatter; -import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; - -import com.diffplug.spotless.extra.eclipse.wtp.html.StructuredDocumentProcessor.RegionProcessor; - -/** - * Provides additional formating to the plain JS {@link CodeFormatter}: - *
    - *
  • Eclipse HTML places the embedded JS in separated lines by adding a line break after/before <script/> tag.
  • - *
  • Eclipse HTML treats the text before the closing </script> tag as part of the script region.
  • - *
- *

- * Note that the closing tag is indented by Eclipse using the embedded formatters indentation, - * whereas the opening tag indentation is configured by the HTML preferences. - * This is more a bug than a feature, but Spotless formatter output shall be identical - * to the one of Eclipse. - *

- */ -public class JsRegionProcessor extends RegionProcessor { - public JsRegionProcessor(IStructuredDocument document, ITypedRegion scriptRegion, String htmlIndent) { - super(document, scriptRegion, htmlIndent); - } - - @Override - protected void applyFirst(CodeFormatter formatter) throws MalformedTreeException, BadLocationException { - MultiTextEdit modifications = new MultiTextEdit(); - String jsSource = document.get(region.getOffset(), region.getLength()); - TextEdit jsEdit = formatter.format(CodeFormatter.K_JAVASCRIPT_UNIT, jsSource, 0, jsSource.length(), indentationLevel + 1, LINE_DELIMITER); - if (null != jsEdit) { - jsEdit.moveTree(region.getOffset()); - modifications.addChild(jsEdit); - } - modifications.apply(document); - } - - @Override - protected void applySecond(CodeFormatter formatter) throws MalformedTreeException, BadLocationException { - MultiTextEdit modifications = new MultiTextEdit(); - int regionEnd = region.getOffset() + region.getLength(); - regionEnd += fixDelimiter(modifications, region.getOffset(), false); - regionEnd += fixDelimiter(modifications, region.getOffset() + region.getLength() - 1, true); - modifications.apply(document); - modifications.removeChildren(); - fixTagIndent(modifications, regionEnd, formatter.createIndentationString(indentationLevel)); - modifications.apply(document); - } - - /** Factory for {@link StructuredDocumentProcessor}*/ - public static BiFunction createFactory(String htmlIndent) { - return new BiFunction() { - - @Override - public JsRegionProcessor apply(IStructuredDocument document, ITypedRegion region) { - return new JsRegionProcessor(document, region, htmlIndent); - } - - }; - } - -} diff --git a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/html/StructuredDocumentProcessor.java b/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/html/StructuredDocumentProcessor.java deleted file mode 100644 index 6adc258c78..0000000000 --- a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/html/StructuredDocumentProcessor.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.wtp.html; - -import static com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework.LINE_DELIMITER; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.function.BiFunction; -import java.util.stream.Collectors; - -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.ITypedRegion; -import org.eclipse.text.edits.InsertEdit; -import org.eclipse.text.edits.MalformedTreeException; -import org.eclipse.text.edits.MultiTextEdit; -import org.eclipse.text.edits.ReplaceEdit; -import org.eclipse.text.edits.TextEdit; -import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument; - -/** - * The HTML formatter uses for different regions of the structured document, - * other formatters, explicitly for CSS and JS. - * Adaptations of the current formatter modifications are tedious, - * since the adaptations e.g. overlap with the required additional modifications. - * Hence the modifications is split in steps, whereas the regions of the - * structured documents and their offsets are regenerate between the first - * ad second step. - */ -public class StructuredDocumentProcessor { - private final String type; - private final BiFunction> factory; - private final IStructuredDocument document; - private final int numberOfRegions; - - /** - * Constructs a document processor - * @param document Document to be processed - * @param type Document type ID recognized by {@code IContentTypeManager} service - * @param factory Factory for structured document processor - */ - public StructuredDocumentProcessor(IStructuredDocument document, String type, - BiFunction> factory) { - this.type = type; - this.factory = factory; - this.document = document; - numberOfRegions = getRegions().size(); - } - - /** Applies processor on document, using a given formatter */ - public void apply(T formatter) { - for (int currentRegionId = 0; currentRegionId < numberOfRegions; currentRegionId++) { - applyOnRegion(currentRegionId, formatter); - } - } - - private List getRegions() { - try { - return Arrays.asList(document.computePartitioning(0, document.getLength())).stream().filter(reg -> type == reg.getType()).collect(Collectors.toList()); - } catch (BadLocationException e) { - /* - * This prevents the processing in case the entire document - * cannot be processed (e.g. the document is incomplete). - */ - return new ArrayList(0); - } - } - - private void applyOnRegion(int number, T formatter) { - RegionProcessor adapter = getRegionProcessor(number); - try { - adapter.applyFirst(formatter); - adapter = getRegionProcessor(number); - adapter.applySecond(formatter); - } catch (MalformedTreeException | BadLocationException e) { - throw new IllegalArgumentException( - String.format("%s formatting failed between lines %d and %d. Most likely the syntax is not recognized.", - type, adapter.getFirstLine(), adapter.getLastLine()), - e); - } - } - - private RegionProcessor getRegionProcessor(int number) { - List regions = getRegions(); - if (numberOfRegions != regions.size()) { - //Don't catch this. This is a severe internal bug! - throw new IllegalArgumentException( - String.format( - "During first '%s' formatting step, the number of detected regions changed from '%d' to '%d'", - type, numberOfRegions, regions.size())); - } - ITypedRegion region = regions.get(number); - return factory.apply(document, region); - } - - /** Base class for region adaptations. */ - public static abstract class RegionProcessor { - protected final IStructuredDocument document; - protected final ITypedRegion region; - protected final int indentationLevel; - protected final int firstLine; - protected final int lastLine; - - protected RegionProcessor(IStructuredDocument document, ITypedRegion region, String htmlIndent) { - this.document = document; - this.region = region; - indentationLevel = computeIndent(document, region, htmlIndent); - firstLine = document.getLineOfOffset(region.getOffset()); - lastLine = document.getLineOfOffset(region.getOffset() + region.getLength()); - } - - public int getFirstLine() { - return firstLine; - } - - public int getLastLine() { - return lastLine; - } - - private static int computeIndent(IStructuredDocument document, ITypedRegion region, String htmlIndent) { - int indent = 0; - try { - int lineNumber = document.getLineOfOffset(region.getOffset()); - document.getNumberOfLines(); - int lineOffset = document.getLineOffset(lineNumber); - String lineStart = document.get(lineOffset, region.getOffset() - lineOffset); - while (lineStart.length() > htmlIndent.length()) { - if (lineStart.startsWith(htmlIndent)) { - indent++; - } else { - break; - } - lineStart = lineStart.substring(htmlIndent.length()); - } - } catch (BadLocationException e) { - /* - * Skip addition indentation. This normally indicates a malformed HTML - * outside of this region, which cannot be handled here. - */ - indent = 0; - } - return indent; - } - - /** Add delimiter at or after given offset, if there is none at the offset position. Returns the number of characters inserted. */ - protected int fixDelimiter(MultiTextEdit modifications, int offset, boolean addAfter) throws BadLocationException { - int delimiterLength = LINE_DELIMITER.length(); - String delimiter = document.get(offset, delimiterLength); - if (!LINE_DELIMITER.equals(delimiter)) { - if (addAfter) { - offset += 1; - } - modifications.addChild(new InsertEdit(offset, LINE_DELIMITER)); - return LINE_DELIMITER.length(); - } - return 0; - } - - /** Fix the tag indentation at a given position with a predefined indentation. */ - protected void fixTagIndent(MultiTextEdit modifications, int offset, String indentString) throws BadLocationException { - int lineNumber = document.getLineOfOffset(offset); - if (lineNumber >= document.getNumberOfLines()) { - //Nothing to change for last line. If syntax is correct, there is no indentation. If syntax is not correct, there is nothing to do. - return; - } - int lineStart = document.getLineOffset(lineNumber); - int lineEnd = document.getLineOffset(lineNumber + 1); - String lineContent = document.get(lineStart, lineEnd - lineStart); - StringBuilder currentIndent = new StringBuilder(); - lineContent.chars().filter(c -> { - if (c == ' ' || c == '\t') { - currentIndent.append(c); - return false; - } - return true; - }).findFirst(); - if (!indentString.equals(currentIndent.toString())) { - TextEdit replaceIndent = new ReplaceEdit(lineStart, currentIndent.length(), indentString); - replaceIndent.apply(document); - } - } - - /** First application of modifications */ - abstract protected void applyFirst(T formatter) throws MalformedTreeException, BadLocationException; - - /** Second application of modifications (based on new regions) */ - abstract protected void applySecond(T formatter) throws MalformedTreeException, BadLocationException; - - } - -} diff --git a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/package-info.java b/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/package-info.java deleted file mode 100644 index 6cf6f0bfff..0000000000 --- a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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. - */ -/** Eclipse WTP based Spotless formatters */ -@ParametersAreNonnullByDefault -package com.diffplug.spotless.extra.eclipse.wtp; - -import javax.annotation.ParametersAreNonnullByDefault; diff --git a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/sse/CleanupStep.java b/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/sse/CleanupStep.java deleted file mode 100644 index 01be583bd8..0000000000 --- a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/sse/CleanupStep.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.wtp.sse; - -import org.eclipse.core.internal.preferences.PreferencesService; -import org.eclipse.core.runtime.content.IContentTypeManager; -import org.eclipse.core.runtime.preferences.IPreferencesService; -import org.eclipse.wst.sse.core.internal.cleanup.AbstractStructuredCleanupProcessor; - -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseConfig; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipsePluginConfig; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseServiceConfig; - -/** - * Common base class for step implementations based on an SSE cleanup processor. - *

- * Some WTP formatters do not apply all formatting within a formatter process, - * but provide a cleanup and a formatter process, whereas the cleanup process - * calls the formatter process. - *

- *

- * Not all cleanup processes provided by WTP are suitable for Spotless formatting. - * For example the XML cleanup processor focus on syntax and line delimiter - * corrections. The syntax correction is based on the corresponding XSD/DTD, - * but it must be assumed by the Spotless formatter that the provided files - * are valid. It cannot be the purpose of a formatter to correct syntax. - * A Java formatter should also not attempt to correct compilation errors. - * A line delimiter correction would furthermore violate the Spotless rule - * that the strings processed by a Spotless formatter chain must use - * UNIX style delimiters. - *

- * @see org.eclipse.wst.sse.core.internal.cleanup.AbstractStructuredCleanupProcessor - */ -public class CleanupStep { - - /** Make some of the protected SEE AbstractStructuredCleanupProcessor methods public. */ - public interface ProcessorAccessor { - - /** Configure from Eclipse framework preferences */ - void refreshPreferences(); - - /** Get underlying processor */ - AbstractStructuredCleanupProcessor get(); - - /** Get processor content type */ - String getTypeId(); - } - - /** Framework configuration public to all formatters using the Cleanup step */ - public static abstract class FrameworkConfig implements SpotlessEclipseConfig { - - private String processorContentTypeID; - - protected FrameworkConfig() { - processorContentTypeID = "none"; - } - - void setProcessorTypeID(String contentTypeID) { - processorContentTypeID = contentTypeID; - } - - @Override - public void registerServices(SpotlessEclipseServiceConfig config) { - config.disableDebugging(); - config.hideEnvironment(); - config.useTemporaryLocations(); - config.changeSystemLineSeparator(); - config.useSlf4J(this.getClass().getPackage().getName() + "-" + processorContentTypeID); - - //Assure that all file content processed by this formatter step are associated to this cleanup-step - config.add(IContentTypeManager.class, new ContentTypeManager(processorContentTypeID)); - - //The preference lookup via the ContentTypeManager, requires a preference service - config.add(IPreferencesService.class, PreferencesService.getDefault()); - } - - @Override - public void activatePlugins(SpotlessEclipsePluginConfig config) { - config.applyDefault(); - /* - * The core preferences require do lookup the resources "config/override.properties" - * from the plugin ID. - * The values are never used, nor do we require the complete SSE core plugin to be started. - * Hence we just provide the internal plugin. - */ - config.add(new org.eclipse.wst.sse.core.internal.encoding.util.CodedResourcePlugin()); - } - } - - protected final ProcessorAccessor processorAccessor; - - protected CleanupStep(ProcessorAccessor processorAccessor, FrameworkConfig config) throws Exception { - config.setProcessorTypeID(processorAccessor.getTypeId()); - SpotlessEclipseFramework.setup(config); - this.processorAccessor = processorAccessor; - this.processorAccessor.refreshPreferences(); - /* - * Don't refresh the preferences every time a clone of the processor is created. - * All processors shall use the preferences of its parent. - */ - processorAccessor.get().refreshCleanupPreferences = false; - } - - /** - * Calls the cleanup and formatting task of the processor and returns the formatted string. - * @param raw Dirty string - * @return Formatted string - * @throws Exception All exceptions are considered fatal to the build process (Gradle, Maven, ...) and should not be caught. - */ - public String format(String raw) throws Exception { - return processorAccessor.get().cleanupContent(raw); - } - -} diff --git a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/sse/ContentTypeManager.java b/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/sse/ContentTypeManager.java deleted file mode 100644 index 499a147ae9..0000000000 --- a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/sse/ContentTypeManager.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.wtp.sse; - -import java.io.IOException; -import java.io.InputStream; -import java.io.Reader; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -import org.eclipse.core.runtime.CoreException; -import org.eclipse.core.runtime.QualifiedName; -import org.eclipse.core.runtime.content.IContentDescription; -import org.eclipse.core.runtime.content.IContentType; -import org.eclipse.core.runtime.content.IContentTypeSettings; -import org.eclipse.core.runtime.preferences.IScopeContext; -import org.eclipse.wst.css.core.internal.provisional.contenttype.ContentTypeIdForCSS; -import org.eclipse.wst.html.core.internal.provisional.contenttype.ContentTypeIdForHTML; -import org.eclipse.wst.json.core.contenttype.ContentTypeIdForJSON; -import org.eclipse.wst.xml.core.internal.provisional.contenttype.ContentTypeIdForXML; - -import com.diffplug.spotless.extra.eclipse.base.service.NoContentTypeSpecificHandling; - -/** - - * WTP ModelHandlerRegistry uses the content type mamanger clean-up formatters - * to provide association of content to content type related functionality. - *

- * Preference lookup per content type is accomplished via the - * Eclipse PreferencesService, which must be provided in combination with - * this service. - *

- * The input byte steam encoding detection is accomplished by the - * content type manager. Normally the encoding is bount do a document/file. - * Spotless applies the formatting on strings already decoded. - * The WTP AbstractStructuredCleanupProcessor provides for non-documents - * a clean-up function converting the decoded string into an UTF-8 encoded byte stream. - * WTP AbstractDocumentLoader uses content type mamanger to determine the encoding - * of the input stream. - * Only the steps are affected that are using the - * AbstractStructuredCleanupProcessor. All other steps creating an empty document - * (e.g. via WTP AbstractDocumentLoader) and setting the textual content of the new document. - * - * @see org.eclipse.core.internal.preferences.PreferencesService - * @see org.eclipse.wst.sse.core.internal.cleanup.AbstractStructuredCleanupProcessor - * @see org.eclipse.wst.sse.core.internal.document.AbstractDocumentLoader - * @see org.eclipse.wst.sse.core.internal.modelhandler.ModelHandlerRegistry - */ -class ContentTypeManager extends NoContentTypeSpecificHandling { - private final Map id2Object; - private final IContentType processorStepType; - private final IContentDescription processorStepDescription; - - /** - * Content type manager as required for cleanup steps. - * @param formatterContentTypeID The content type of the formatter step - */ - ContentTypeManager(String formatterContentTypeID) { - id2Object = new HashMap(); - Arrays.asList( - ContentTypeIdForCSS.ContentTypeID_CSS, - ContentTypeIdForXML.ContentTypeID_XML, - ContentTypeIdForHTML.ContentTypeID_HTML, - ContentTypeIdForJSON.ContentTypeID_JSON) - .stream().forEach(id -> id2Object.put(id, new ContentTypeId(id))); - processorStepType = id2Object.get(formatterContentTypeID); - if (null == processorStepType) { - throw new IllegalArgumentException("The manager does not support content type " + formatterContentTypeID); - } - processorStepDescription = new StringDescription(processorStepType); - } - - @Override - public IContentType getContentType(String contentTypeIdentifier) { - /* - * It is OK to return null here since the manager is only used as an additional - * helper to alter default behavior. - */ - return id2Object.get(contentTypeIdentifier); - } - - @Override - public IContentType findContentTypeFor(InputStream contents, String fileName) throws IOException { - //We only format things here with the given processor, so this answer is always correct. - return processorStepType; - } - - @Override - public IContentDescription getDescriptionFor(InputStream contents, String fileName, QualifiedName[] options) throws IOException { - return processorStepDescription; - } - - private static class StringDescription implements IContentDescription { - - private final IContentType type; - - public StringDescription(IContentType type) { - this.type = type; - } - - @Override - public boolean isRequested(QualifiedName key) { - return false; //Don't use set Property - } - - @Override - public String getCharset() { - //Called by AbstractDocumentLoader.readInputStream - return "UTF-8"; //UTF-8 encoded by AbstractStructuredCleanupProcessor.cleanupContent - } - - @Override - public IContentType getContentType() { - return type; - } - - @Override - public Object getProperty(QualifiedName key) { - return null; //Assume that the property map is empty - } - - @Override - public void setProperty(QualifiedName key, Object value) { - throw new IllegalArgumentException("Content description key cannot be set: " + key); - } - } - - /** - * The WTP uses the manager mainly for ID mapping, so most of the methods are not used. - * Actually it has a hand stitched way for transforming the content type ID - * {@code org.eclipse.wst...source} to the plugin ID {@code org.eclipse.wst...core}. - * @see org.eclipse.wst.sse.core.internal.encoding.ContentBasedPreferenceGateway - */ - private static class ContentTypeId implements IContentType { - - private final String id; - - ContentTypeId(String id) { - this.id = id; - } - - @Override - public void addFileSpec(String fileSpec, int type) throws CoreException {} - - @Override - public void removeFileSpec(String fileSpec, int type) throws CoreException {} - - @Override - public void setDefaultCharset(String userCharset) throws CoreException {} - - @Override - public boolean isUserDefined() { - return false; - } - - @Override - public IContentType getBaseType() { - return null; - } - - @Override - public IContentDescription getDefaultDescription() { - return null; - } - - @Override - public IContentDescription getDescriptionFor(InputStream contents, QualifiedName[] options) throws IOException { - return null; - } - - @Override - public IContentDescription getDescriptionFor(Reader contents, QualifiedName[] options) throws IOException { - return null; - } - - @Override - public String getDefaultCharset() { - return null; - } - - @Override - public String[] getFileSpecs(int type) { - return null; - } - - @Override - public String getId() { - return id; - } - - @Override - public String getName() { - return id; - } - - @Override - public boolean isAssociatedWith(String fileName) { - return false; - } - - @Override - public boolean isAssociatedWith(String fileName, IScopeContext context) { - return false; - } - - @Override - public boolean isKindOf(IContentType another) { - if (null == another) { - return false; - } - return this.id.equals(another.getId()); - } - - @Override - public IContentTypeSettings getSettings(IScopeContext context) throws CoreException { - return null; - } - - } - -} diff --git a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/sse/PluginPreferences.java b/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/sse/PluginPreferences.java deleted file mode 100644 index 81aa772617..0000000000 --- a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/sse/PluginPreferences.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.wtp.sse; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Properties; - -import org.eclipse.core.runtime.Plugin; -import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer; -import org.eclipse.core.runtime.preferences.DefaultScope; -import org.eclipse.core.runtime.preferences.IEclipsePreferences; -import org.eclipse.wst.xml.core.internal.catalog.Catalog; -import org.eclipse.wst.xml.core.internal.catalog.CatalogReader; -import org.eclipse.wst.xml.core.internal.catalog.provisional.ICatalog; - -/** - * The plugin preference configuration of most WTP formatters is accomplished via the - * globabl Eclipse preference lookup. - * Spotless allows different formatter configurations per sub-projects. - * Fortunately most formatters only perform a lookup on instantiation and not afterwards. - * This is sometimes not true for all preferences of the formatter. - * - * - */ -public class PluginPreferences { - /** - * Optional XML catalog for XSD/DTD lookup. - * Catalog versions 1.0 and 1.1 are supported. - *

- * Value is of type {@code Path}. - *

- */ - public static final String USER_CATALOG = "userCatalog"; - - /** - * Indicates if external URIs (location hints) should be resolved - * and the referenced DTD/XSD shall be applied. Per default - * external URIs are ignored. - *

- * Value is of type Boolean. - *

- */ - public static final String RESOLVE_EXTERNAL_URI = "resolveExternalURI"; - - /** Storage of latest global configuration */ - private static final Map CONFIG = new HashMap<>(); - - /** \return True if user explicitly set the */ - public static boolean isExternalUriAllowed(Properties properties) { - return Boolean.parseBoolean(properties.getProperty(PluginPreferences.RESOLVE_EXTERNAL_URI, "false")); - } - - /** Configures persistent Eclipse properties */ - public static void configure(Plugin plugin, AbstractPreferenceInitializer defaultInitializer, Properties properties) { - defaultInitializer.initializeDefaultPreferences(); - IEclipsePreferences globalPreferences = DefaultScope.INSTANCE.getNode(getPluginId(plugin)); - properties.forEach((key, value) -> { - globalPreferences.put((String) key, (String) value); - }); - store(plugin, properties); - } - - /** Stores Eclipse properties for later comparison */ - public static void store(Plugin plugin, Properties properties) { - CONFIG.put(getPluginId(plugin), properties); - } - - /** Keep in mind that the ID is e.g. equal for all plugins. So assure that all user properties ge stored. */ - private static String getPluginId(Plugin plugin) { - return plugin.getBundle().getSymbolicName(); - } - - public static void configureCatalog(final Properties properties, final ICatalog defaultCatalogInterface) { - Objects.requireNonNull(properties, "Property values are missing."); - Objects.requireNonNull(defaultCatalogInterface, "Default catalog missing."); - if (!(defaultCatalogInterface instanceof Catalog)) { - throw new IllegalArgumentException("Internal error: Catalog implementation '" + defaultCatalogInterface.getClass().getCanonicalName() + "' unsupported."); - } - Catalog defaultCatalog = (Catalog) defaultCatalogInterface; - String catalogProperty = properties.getProperty(USER_CATALOG, ""); - if (!catalogProperty.isEmpty()) { - final File catalogFile = new File(catalogProperty); - try { - InputStream inputStream = new FileInputStream(catalogFile); - String orgBase = defaultCatalog.getBase(); - defaultCatalog.setBase(catalogFile.toURI().toString()); - CatalogReader.read((Catalog) defaultCatalog, inputStream); - defaultCatalog.setBase(orgBase); - } catch (IOException e) { - throw new IllegalArgumentException( - String.format("Value of '%s' refers to '%s', which cannot be read.", USER_CATALOG, catalogFile)); - } - } else { - defaultCatalog.clear(); - } - } - - /** Throws exception in case configuration has changed */ - public static void assertNoChanges(Plugin plugin, Properties properties) { - Objects.requireNonNull(properties, "Property values are missing."); - final String preferenceId = plugin.getBundle().getSymbolicName(); - Properties originalValues = CONFIG.get(preferenceId); - if (null == originalValues) { - throw new IllegalArgumentException("No configuration found for " + preferenceId); - } - if (!originalValues.equals(properties)) { - throw new IllegalArgumentException("The Eclipse formatter does not support multiple configurations."); - } - } - -} diff --git a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/sse/PreventExternalURIResolverExtension.java b/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/sse/PreventExternalURIResolverExtension.java deleted file mode 100644 index d0b95e5632..0000000000 --- a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/sse/PreventExternalURIResolverExtension.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.wtp.sse; - -import org.eclipse.core.resources.IFile; -import org.eclipse.emf.common.util.URI; -import org.eclipse.wst.common.uriresolver.internal.provisional.URIResolverExtension; -import org.osgi.framework.BundleActivator; -import org.osgi.framework.BundleContext; - -/** - * The URI resolver extension - */ -public class PreventExternalURIResolverExtension implements URIResolverExtension, BundleActivator { - - private static final String REFUSE_EXTERNAL_URI = "file://refused.external.uri"; - - /** - * @param file the in-workspace base resource, if one exists - * @param baseLocation - the location of the resource that contains the uri - * @param publicId - an optional public identifier (i.e. namespace name), or null if none - * @param systemId - an absolute or relative URI, or null if none - * - * @return an absolute URI or null if this extension can not resolve this reference - */ - @Override - public String resolve(IFile file, String baseLocation, String publicId, String systemId) { - if (null != systemId) { - try { - URI proposalByPreviousResolver = org.eclipse.emf.common.util.URI.createURI(systemId); - String host = proposalByPreviousResolver.host(); - /* - * The host is empty (not null) - */ - if (!(null == host || host.isEmpty())) { - return REFUSE_EXTERNAL_URI; - } - } catch (IllegalArgumentException ignore) { - //If it is no a valid URI, there is nothing to do here. - } - } - return null; //Don't alter the proposal of previous resolver extensions by proposing something else - } - - @Override - public void start(BundleContext context) throws Exception { - //Nothing to do. The bundle-activator interface only allows to load this extension as a stand-alone plugin. - } - - @Override - public void stop(BundleContext context) throws Exception { - //Nothing to do. The bundle-activator interface only allows to load this extension as a stand-alone plugin. - } - -} diff --git a/_ext/eclipse-wtp/src/main/resources/META-INF/MANIFEST.MF b/_ext/eclipse-wtp/src/main/resources/META-INF/MANIFEST.MF deleted file mode 100644 index 9f785fea6c..0000000000 --- a/_ext/eclipse-wtp/src/main/resources/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Bundle-ManifestVersion: 2 -Bundle-SymbolicName: com.diffplug.gradle.spotless.eclipse.wtp; singleton:=true diff --git a/_ext/eclipse-wtp/src/main/resources/plugin.xml b/_ext/eclipse-wtp/src/main/resources/plugin.xml deleted file mode 100644 index 7ffd251c94..0000000000 --- a/_ext/eclipse-wtp/src/main/resources/plugin.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseCssFormatterStepImplTest.java b/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseCssFormatterStepImplTest.java deleted file mode 100644 index 8dda3149c0..0000000000 --- a/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseCssFormatterStepImplTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2016-2021 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.wtp; - -import static com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework.LINE_DELIMITER; -import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.wst.css.core.internal.preferences.CSSCorePreferenceNames.*; -import static org.junit.jupiter.api.Assertions.*; - -import java.util.Properties; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class EclipseCssFormatterStepImplTest { - - private final static String ILLEGAL_CHAR = Character.toString((char) 254); - private final static String UNFORMATTED = " body {a: v; b: v;}\n".replaceAll("\n", LINE_DELIMITER); - private final static String FORMATTED = "BODY {\n a: v;\n b: v;\n}".replaceAll("\n", LINE_DELIMITER); - private final static String PRE_CODE_UNFORMATTED = "/**
\"Hello\"
*/\n".replaceAll("\n", LINE_DELIMITER); - - private EclipseCssFormatterStepImpl formatter; - - @BeforeEach - void initialize() throws Exception { - /* - * The instantiation can be repeated for each step, but only with the same configuration - * All formatter configuration is stored in - * org.eclipse.core.runtime/.settings/org.eclipse.wst.css.core.prefs. - */ - Properties properties = new Properties(); - properties.put(INDENTATION_SIZE, "3"); - properties.put(INDENTATION_CHAR, SPACE); //Default is TAB - properties.put(CLEANUP_CASE_SELECTOR, Integer.toString(UPPER)); //Done by cleanup - formatter = new EclipseCssFormatterStepImpl(properties); - } - - @Test - void format() throws Exception { - String output = formatter.format(UNFORMATTED); - assertEquals(FORMATTED, - output, "Unexpected formatting with default preferences."); - } - - @Test - void illegalCharacter() throws Exception { - String output = formatter.format(ILLEGAL_CHAR + UNFORMATTED); - assertThat(output).as("Illeagl characters are not handled on best effort basis.").contains("BODY {"); - } - - @Test - void illegalSyntax() throws Exception { - String output = formatter.format("{" + UNFORMATTED); - assertEquals("{" + LINE_DELIMITER + FORMATTED, - output, "Illeagl syntax is not handled on best effort basis."); - } - - @Test - void formatComment() throws Exception { - String output = formatter.format(PRE_CODE_UNFORMATTED + UNFORMATTED); - assertEquals(PRE_CODE_UNFORMATTED + FORMATTED, - output, "Unexpected formatting of cpomments."); - } - - @Test - void configurationChange() throws Exception { - assertThrows(IllegalArgumentException.class, () -> new EclipseCssFormatterStepImpl(new Properties())); - } - -} diff --git a/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseHtmlFormatterStepImplTest.java b/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseHtmlFormatterStepImplTest.java deleted file mode 100644 index b75421692a..0000000000 --- a/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseHtmlFormatterStepImplTest.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2016-2021 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.wtp; - -import static org.eclipse.wst.html.core.internal.preferences.HTMLCorePreferenceNames.*; -import static org.eclipse.wst.jsdt.core.formatter.DefaultCodeFormatterConstants.*; -import static org.junit.jupiter.api.Assertions.*; - -import java.util.Properties; - -import org.eclipse.wst.html.core.internal.preferences.HTMLCorePreferenceNames; -import org.eclipse.wst.jsdt.core.JavaScriptCore; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class EclipseHtmlFormatterStepImplTest { - - private TestData testData = null; - private EclipseHtmlFormatterStepImpl formatter; - - @BeforeEach - void initialize() throws Exception { - testData = TestData.getTestDataOnFileSystem("html"); - /* - * The instantiation can be repeated for each step, but only with the same configuration - * All formatter configuration is stored in - * org.eclipse.core.runtime/.settings/org.eclipse.wst.xml.core.prefs. - * So a simple test of one configuration item change is considered sufficient. - */ - Properties properties = new Properties(); - properties.put(CLEANUP_TAG_NAME_CASE, Integer.toString(HTMLCorePreferenceNames.UPPER)); //HTML config - properties.put(FORMATTER_INSERT_SPACE_BEFORE_SEMICOLON, JavaScriptCore.INSERT); //JS config - properties.put(QUOTE_ATTR_VALUES, "TRUE"); //CSS config - formatter = new EclipseHtmlFormatterStepImpl(properties); - } - - @Test - void formatHtml4() throws Exception { - String[] input = testData.input("html4.html"); - String output = formatter.format(input[0]); - assertEquals(testData.expected("html4.html"), - output, "Unexpected HTML4 formatting."); - } - - @Test - void formatHtml5() throws Exception { - String[] input = testData.input("html5.html"); - String output = formatter.format(input[0]); - assertEquals(testData.expected("html5.html"), - output, "Unexpected HTML5 formatting."); - } - - @Test - void invalidSyntax() throws Exception { - String[] input = testData.input("invalid_syntax.html"); - String output = formatter.format(input[0]); - assertEquals(testData.expected("invalid_syntax.html"), - output, "Unexpected HTML formatting in case syntax is not valid."); - } - - @Test - void formatJavaScript() throws Exception { - String[] input = testData.input("javascript.html"); - String output = formatter.format(input[0]); - assertEquals(testData.expected("javascript.html"), - output, "Unexpected JS formatting."); - } - - @Test - void formatCSS() throws Exception { - String[] input = testData.input("css.html"); - String output = formatter.format(input[0]); - assertEquals(testData.expected("css.html"), - output, "Unexpected CSS formatting."); - } - - @Test - void checkCleanupForNonUtf8() throws Exception { - String osEncoding = System.getProperty("file.encoding"); - System.setProperty("file.encoding", "ISO-8859-1"); //Simulate a non UTF-8 OS - String[] input = testData.input("utf-8.html"); - String output = formatter.format(input[0]); - System.setProperty("file.encoding", osEncoding); - assertEquals(testData.expected("utf-8.html"), output, "Unexpected formatting of UTF-8"); - } - - @Test - void checkBOMisStripped() throws Exception { - String[] input = testData.input("bom.html"); - String[] inputWithoutBom = testData.input("utf-8.html"); - //The UTF-8 BOM is interpreted as on UTF-16 character. - assertEquals(input[0].length() - 1, inputWithoutBom[0].length(), "BOM input invalid"); - String output = formatter.format(input[0]); - assertEquals(testData.expected("utf-8.html"), output, "BOM is not stripped"); - } - - @Test - void configurationChange() throws Exception { - assertThrows(IllegalArgumentException.class, () -> new EclipseHtmlFormatterStepImpl(new Properties())); - } -} diff --git a/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseJsFormatterStepImplTest.java b/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseJsFormatterStepImplTest.java deleted file mode 100644 index 0858c3a04d..0000000000 --- a/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseJsFormatterStepImplTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2016-2021 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.wtp; - -import static com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework.LINE_DELIMITER; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.*; - -import java.util.Arrays; -import java.util.Properties; - -import org.eclipse.wst.jsdt.core.JavaScriptCore; -import org.eclipse.wst.jsdt.core.formatter.DefaultCodeFormatterConstants; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class EclipseJsFormatterStepImplTest { - private final static String ILLEGAL_CHAR = Character.toString((char) 254); - private final static String UNFORMATTED = "var TEST = TEST || {};\n" + - "TEST.say = function() {\n" + - " console.log(\"Hello world!\"); }\n".replaceAll("\n", LINE_DELIMITER); - private final static String FORMATTED = "var TEST = TEST || {};\n" + - "TEST.say = function () {\n" + - "\tconsole.log(\"Hello world!\");\n}\n".replaceAll("\n", LINE_DELIMITER); - // Single line comment remains untouched - private final static String SINGLE_LINE_COMMENT = "// One line \"hello world\"\n".replaceAll("\n", LINE_DELIMITER); - // JavaDoc comment get indentation. Within PPRE code, HTML entities are escaped. - private final static String PRE_CODE_UNFORMATTED = "/**
\"Hello\"
*/\n".replaceAll("\n", LINE_DELIMITER); - private final static String PRE_CODE_FORMATTED = "/**\n *
\n * "Hello"\n * 
\n */\n".replaceAll("\n", LINE_DELIMITER); - - private EclipseJsFormatterStepImpl formatter; - - @BeforeEach - void initialize() throws Exception { - /* - * The instantiation can be repeated for each step, but only with the same configuration - * All formatter configuration is stored in - * org.eclipse.core.runtime/.settings/org.eclipse.jst.jsdt.core.prefs. - */ - Properties properties = new Properties(); - properties.setProperty(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR, JavaScriptCore.TAB); - formatter = new EclipseJsFormatterStepImpl(properties); - } - - @Test - void invalidSyntax() throws Exception { - IllegalArgumentException error = assertThrows(IllegalArgumentException.class, () -> formatter.format(UNFORMATTED.replace("()", ""))); - assertThat(error.getMessage()).as("Exception has no hint about invalid syntax.").contains(Arrays.asList("Invalid", "syntax")); - } - - @Test - void illegalCharacter() throws Exception { - String output = formatter.format(UNFORMATTED.replace("function", "function" + ILLEGAL_CHAR)); - assertThat(output).as("Illegal ASCII charactes are not treated on best effort basis.").contains("function" + ILLEGAL_CHAR); - } - - @Test - void nominal() throws Exception { - String output = formatter.format(UNFORMATTED); - assertEquals(FORMATTED, output, "User configuration ignored by formatter."); - } - - @Test - void formatComments() throws Exception { - String output = formatter.format(SINGLE_LINE_COMMENT + PRE_CODE_UNFORMATTED + UNFORMATTED); - assertEquals(SINGLE_LINE_COMMENT + PRE_CODE_FORMATTED + FORMATTED, - output, "Invalid user configuration not treated on best effort basis."); - } - - @Test - void configurationChange() throws Exception { - assertThrows(IllegalArgumentException.class, () -> new EclipseJsFormatterStepImpl(new Properties())); - } - -} diff --git a/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseJsonFormatterStepImplTest.java b/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseJsonFormatterStepImplTest.java deleted file mode 100644 index 3bb3b7c988..0000000000 --- a/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseJsonFormatterStepImplTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2016-2021 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.wtp; - -import static com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework.LINE_DELIMITER; -import static org.eclipse.wst.json.core.preferences.JSONCorePreferenceNames.*; -import static org.junit.jupiter.api.Assertions.*; - -import java.util.Properties; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -class EclipseJsonFormatterStepImplTest { - private final static String ILLEGAL_CHAR = Character.toString((char) 254); - private final static String UNFORMATTED_OBJECT = "{\n \"x\": { \"a\" : \"v\",\"properties\" : \"v\" }}".replaceAll("\n", LINE_DELIMITER); - private final static String FORMATTED_OBJECT = "{\n \"x\": {\n \"a\": \"v\",\n \"properties\": \"v\"\n }\n}".replaceAll("\n", LINE_DELIMITER); - private final static String UNFORMATTED_ARRAY = "[\n { \"a\" : \"v\",\"properties\" : \"v\" }]".replaceAll("\n", LINE_DELIMITER); - private final static String FORMATTED_ARRAY = "[\n {\n \"a\": \"v\",\n \"properties\": \"v\"\n }\n]".replaceAll("\n", LINE_DELIMITER); - - private EclipseJsonFormatterStepImpl formatter; - - @BeforeEach - void initialize() throws Exception { - /* - * The instantiation can be repeated for each step, but only with the same configuration - * All formatter configuration is stored in - * org.eclipse.core.runtime/.settings/org.eclipse.wst.json.core.prefs. - * So a simple test of one configuration item change is considered sufficient. - */ - Properties properties = new Properties(); - properties.put(INDENTATION_SIZE, "3"); //Default is 1 - properties.put(INDENTATION_CHAR, SPACE); //Default is TAB - properties.put(CASE_PROPERTY_NAME, Integer.toString(UPPER)); //Dead code, ignored - formatter = new EclipseJsonFormatterStepImpl(properties); - } - - @Test - void formatObject() throws Exception { - String output = formatter.format(UNFORMATTED_OBJECT); - assertEquals(FORMATTED_OBJECT, - output, "Unexpected formatting with default preferences."); - } - - @Test - void formatArray() throws Exception { - String output = formatter.format(UNFORMATTED_ARRAY); - assertEquals(FORMATTED_ARRAY, - output, "Unexpected formatting with default preferences."); - } - - @Test - void illegalCharacter() throws Exception { - String output = formatter.format(ILLEGAL_CHAR + UNFORMATTED_OBJECT); - assertEquals(ILLEGAL_CHAR + FORMATTED_OBJECT, - output, "Illeagl characteds are not ignored."); - } - - @Test - void illegalSyntax() throws Exception { - String output = formatter.format("{" + UNFORMATTED_OBJECT); - assertEquals(FORMATTED_OBJECT, - output, "Illeagl syntax is not handled on best effort basis."); - } - - @Test - void configurationChange() throws Exception { - assertThrows(IllegalArgumentException.class, () -> new EclipseJsonFormatterStepImpl(new Properties())); - } -} diff --git a/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseXmlFormatterStepImplAllowExternalURIsTest.java b/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseXmlFormatterStepImplAllowExternalURIsTest.java deleted file mode 100644 index ac3b2c45f6..0000000000 --- a/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseXmlFormatterStepImplAllowExternalURIsTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2016-2021 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.wtp; - -import static org.eclipse.wst.xml.core.internal.preferences.XMLCorePreferenceNames.INDENTATION_CHAR; -import static org.eclipse.wst.xml.core.internal.preferences.XMLCorePreferenceNames.INDENTATION_SIZE; -import static org.eclipse.wst.xml.core.internal.preferences.XMLCorePreferenceNames.SPACE; -import static org.junit.jupiter.api.Assertions.*; - -import java.util.Properties; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import com.diffplug.spotless.extra.eclipse.wtp.sse.PluginPreferences; - -/** Test configuration allowExternalURI=false */ -class EclipseXmlFormatterStepImplAllowExternalURIsTest { - private TestData testData = null; - private EclipseXmlFormatterStepImpl formatter; - - @BeforeEach - void initializeStatic() throws Exception { - testData = TestData.getTestDataOnFileSystem("xml"); - /* - * The instantiation can be repeated for each step, but only with the same configuration - * All formatter configuration is stored in - * org.eclipse.core.runtime/.settings/org.eclipse.wst.xml.core.prefs. - * So a simple test of one configuration item change is considered sufficient. - */ - Properties properties = new Properties(); - properties.put(PluginPreferences.RESOLVE_EXTERNAL_URI, "TRUE"); - properties.put(INDENTATION_SIZE, "2"); - properties.put(INDENTATION_CHAR, SPACE); //Default is TAB - formatter = new EclipseXmlFormatterStepImpl(properties); - } - - @Test - void dtdExternalPath() throws Throwable { - String[] input = testData.input("dtd_external.test"); - String output = formatter.format(input[0], input[1]); - assertEquals(testData.expected("dtd_external.test"), - output, "External DTD not resolved. Restrictions are not applied by formatter."); - } - - @Test - void xsdExternalPath() throws Throwable { - String[] input = testData.input("xsd_external.test"); - String output = formatter.format(input[0], input[1]); - assertEquals(testData.expected("xsd_external.test"), - output, "External XSD not resolved. Restrictions are not applied by formatter."); - } -} diff --git a/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseXmlFormatterStepImplCatalogLookupTest.java b/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseXmlFormatterStepImplCatalogLookupTest.java deleted file mode 100644 index a9ccd85720..0000000000 --- a/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseXmlFormatterStepImplCatalogLookupTest.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2016-2021 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.wtp; - -import static org.eclipse.wst.xml.core.internal.preferences.XMLCorePreferenceNames.INDENTATION_CHAR; -import static org.eclipse.wst.xml.core.internal.preferences.XMLCorePreferenceNames.INDENTATION_SIZE; -import static org.eclipse.wst.xml.core.internal.preferences.XMLCorePreferenceNames.SPACE; -import static org.junit.jupiter.api.Assertions.*; - -import java.util.Properties; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import com.diffplug.spotless.extra.eclipse.wtp.sse.PluginPreferences; - -/** Test configuration allowExternalURI=false */ -class EclipseXmlFormatterStepImplCatalogLookupTest { - private TestData testData = null; - private EclipseXmlFormatterStepImpl formatter; - - @BeforeEach - void initializeStatic() throws Exception { - testData = TestData.getTestDataOnFileSystem("xml"); - /* - * The instantiation can be repeated for each step, but only with the same configuration - * All formatter configuration is stored in - * org.eclipse.core.runtime/.settings/org.eclipse.wst.xml.core.prefs. - * So a simple test of one configuration item change is considered sufficient. - */ - Properties properties = new Properties(); - properties.put(INDENTATION_SIZE, "2"); - properties.put(INDENTATION_CHAR, SPACE); //Default is TAB - properties.put(PluginPreferences.USER_CATALOG, testData.getRestrictionsPath("catalog.xml").toString()); - formatter = new EclipseXmlFormatterStepImpl(properties); - } - - @Test - void catalogLookup() throws Throwable { - String[] input = testData.input("xsd_not_found.test"); - String output = formatter.format(input[0], input[1]); - assertEquals(testData.expected("xsd_not_found.test").replace(" remove spaces ", "remove spaces"), - output, "XSD not resolved by catalog. Restrictions are not applied by formatter."); - } -} diff --git a/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseXmlFormatterStepImplTest.java b/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseXmlFormatterStepImplTest.java deleted file mode 100644 index 88b15c42e2..0000000000 --- a/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/EclipseXmlFormatterStepImplTest.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2016-2021 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.wtp; - -import static com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework.LINE_DELIMITER; -import static org.eclipse.wst.xml.core.internal.preferences.XMLCorePreferenceNames.*; -import static org.junit.jupiter.api.Assertions.*; - -import java.util.Properties; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -/** Eclipse WST wrapper integration tests */ -class EclipseXmlFormatterStepImplTest { - - private final static String INCOMPLETE = ""; - private final static String ILLEGAL_CHAR = "\0"; - - private TestData testData = null; - private EclipseXmlFormatterStepImpl formatter; - - @BeforeEach - void initialize() throws Exception { - testData = TestData.getTestDataOnFileSystem("xml"); - /* - * The instantiation can be repeated for each step, but only with the same configuration - * All formatter configuration is stored in - * org.eclipse.core.runtime/.settings/org.eclipse.wst.xml.core.prefs. - * So a simple test of one configuration item change is considered sufficient. - */ - Properties properties = new Properties(); - properties.put(INDENTATION_SIZE, "2"); - properties.put(INDENTATION_CHAR, SPACE); //Default is TAB - formatter = new EclipseXmlFormatterStepImpl(properties); - } - - @Test - void simpleDefaultFormat() throws Throwable { - String[] input = testData.input("xml_space.test"); - String output = formatter.format(input[0], input[1]); - assertEquals(testData.expected("xml_space.test"), - output, "Unexpected formatting with default preferences."); - } - - @Test - void invalidXmlFormat() throws Throwable { - String[] input = testData.input("xml_space.test"); - input[0] += INCOMPLETE; - String output = formatter.format(input[0], input[1]); - String expected = testData.expected("xml_space.test") + LINE_DELIMITER + INCOMPLETE; - assertEquals(expected, - output, "Incomplete XML not formatted on best effort basis."); - } - - @Test - void illegalXmlCharater() throws Throwable { - String[] input = testData.input("xml_space.test"); - input[0] = ILLEGAL_CHAR + input[0]; - String output = formatter.format(input[0], input[1]); - String expected = LINE_DELIMITER + LINE_DELIMITER + testData.expected("xml_space.test"); - assertEquals(expected, output, "Illegal character not replaced by line delimiter."); - } - - @Test - void dtdRelativePath() throws Throwable { - String[] input = testData.input("dtd_relative.test"); - String output = formatter.format(input[0], input[1]); - assertEquals(testData.expected("dtd_relative.test"), - output, "Relative DTD not resolved. Restrictions are not applied by formatter."); - } - - @Test - void dtdExternalPath() throws Throwable { - String[] input = testData.input("dtd_external.test"); - String output = formatter.format(input[0], input[1]); - assertNotEquals(testData.expected("dtd_external.test"), - output, "External DTD resolved by default. Restrictions are applied by formatter."); - } - - @Test - void xsdRelativePath() throws Throwable { - String[] input = testData.input("xsd_relative.test"); - String output = formatter.format(input[0], input[1]); - assertEquals(testData.expected("xsd_relative.test"), - output, "Relative XSD not resolved. Restrictions are not applied by formatter."); - } - - @Test - void xsdExternalPath() throws Throwable { - String[] input = testData.input("xsd_external.test"); - String output = formatter.format(input[0], input[1]); - assertNotEquals(testData.expected("xsd_external.test"), - output, "External XSD resolved per default. Restrictions are applied by formatter."); - } - - @Test - void xsdNotFound() throws Throwable { - String[] input = testData.input("xsd_not_found.test"); - String output = formatter.format(input[0], input[1]); - assertEquals(testData.expected("xsd_not_found.test"), - output, "Unresolved XSD/DTD not silently ignored."); - } - - @Test - void configurationChange() throws Exception { - assertThrows(IllegalArgumentException.class, () -> new EclipseXmlFormatterStepImpl(new Properties())); - } -} diff --git a/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/TestData.java b/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/TestData.java deleted file mode 100644 index d767337970..0000000000 --- a/_ext/eclipse-wtp/src/test/java/com/diffplug/spotless/extra/eclipse/wtp/TestData.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2016 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.extra.eclipse.wtp; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; - -public class TestData { - public static TestData getTestDataOnFileSystem(String kind) { - final String userDir = System.getProperty("user.dir", "."); - Path dataPath = Paths.get(userDir, "src", "test", "resources", kind); - if (Files.isDirectory(dataPath)) { - return new TestData(dataPath); - } - return null; - } - - private final Path inputPath; - private final Path expectedPath; - private final Path restrictionsPath; - - private TestData(Path dataPath) { - inputPath = dataPath.resolve("input").toAbsolutePath(); - expectedPath = dataPath.resolve("expected").toAbsolutePath(); - restrictionsPath = dataPath.resolve("restrictions").toAbsolutePath(); - for (Path testDataDir : new Path[]{inputPath, expectedPath, restrictionsPath}) { - if (!Files.isDirectory(testDataDir)) { - throw new IllegalArgumentException(String.format("'%1$s' is not a directory.", testDataDir)); - } - } - } - - public String[] input(final String fileName) throws Exception { - Path xmlPath = inputPath.resolve(fileName); - return new String[]{read(xmlPath), xmlPath.toString()}; - } - - public String expected(final String fileName) { - Path xmlPath = expectedPath.resolve(fileName); - return read(xmlPath); - } - - private String read(final Path xmlPath) { - if (!Files.isRegularFile(xmlPath)) { - throw new IllegalArgumentException(String.format("'%1$s' is not a regular file.", xmlPath)); - } - try { - String checkedOutFileContent = new String(java.nio.file.Files.readAllBytes(xmlPath), "UTF8"); - return checkedOutFileContent.replace("\r", ""); //Align GIT end-of-line normalization - } catch (IOException e) { - throw new IllegalArgumentException(String.format("Failed to read '%1$s'.", xmlPath), e); - } - } - - public Path getRestrictionsPath(String fileName) { - Path filePath = restrictionsPath.resolve(fileName); - if (!Files.exists(filePath)) { - throw new IllegalArgumentException(String.format("'%1$s' is not a restrictions file.", fileName)); - } - return filePath; - } -} diff --git a/_ext/eclipse-wtp/src/test/resources/html/expected/css.html b/_ext/eclipse-wtp/src/test/resources/html/expected/css.html deleted file mode 100644 index cfaea67cd0..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/html/expected/css.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/html/expected/html4.html b/_ext/eclipse-wtp/src/test/resources/html/expected/html4.html deleted file mode 100644 index a344ed08a0..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/html/expected/html4.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - Before -
After - -
- - \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/html/expected/html5.html b/_ext/eclipse-wtp/src/test/resources/html/expected/html5.html deleted file mode 100644 index c79dfdce62..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/html/expected/html5.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - Before -
After - - - - \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/html/expected/invalid_syntax.html b/_ext/eclipse-wtp/src/test/resources/html/expected/invalid_syntax.html deleted file mode 100644 index 9c05557bb7..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/html/expected/invalid_syntax.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - -T<WHATSOEVER></WHATSOEVER> - - \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/html/expected/javascript.html b/_ext/eclipse-wtp/src/test/resources/html/expected/javascript.html deleted file mode 100644 index a33ea60503..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/html/expected/javascript.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/html/expected/utf-8.html b/_ext/eclipse-wtp/src/test/resources/html/expected/utf-8.html deleted file mode 100644 index 4bcd6a03c1..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/html/expected/utf-8.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - -ÄÜ€ - - \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/html/input/bom.html b/_ext/eclipse-wtp/src/test/resources/html/input/bom.html deleted file mode 100644 index 3fe6abe797..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/html/input/bom.html +++ /dev/null @@ -1,2 +0,0 @@ - -ÄÜ€ \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/html/input/css.html b/_ext/eclipse-wtp/src/test/resources/html/input/css.html deleted file mode 100644 index 8ccbc4dcc8..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/html/input/css.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/html/input/html4.html b/_ext/eclipse-wtp/src/test/resources/html/input/html4.html deleted file mode 100644 index 45aa7b56f7..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/html/input/html4.html +++ /dev/null @@ -1,10 +0,0 @@ - - - -Before -
-After - - - - \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/html/input/html5.html b/_ext/eclipse-wtp/src/test/resources/html/input/html5.html deleted file mode 100644 index 216c53e8e3..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/html/input/html5.html +++ /dev/null @@ -1,10 +0,0 @@ - - - -Before -
-After - - - - \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/html/input/invalid_syntax.html b/_ext/eclipse-wtp/src/test/resources/html/input/invalid_syntax.html deleted file mode 100644 index 04c6cf7f2a..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/html/input/invalid_syntax.html +++ /dev/null @@ -1,4 +0,0 @@ - - - -T</whatsoever> \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/html/input/javascript.html b/_ext/eclipse-wtp/src/test/resources/html/input/javascript.html deleted file mode 100644 index dccde2c2dd..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/html/input/javascript.html +++ /dev/null @@ -1,34 +0,0 @@ -<!DOCTYPE html> -<html> -<head> -<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> -<script type="text/javascript">console.log("One line")</script> -<script type="text/javascript"> -console.log("Line break, no indent"); -</script> -<script type="text/javascript"> - console.log("Line break, wrong indent") -</script> -</head> -<body> -<nav> -<script type="text/javascript">console.log("One line, nested")</script> -<script type="text/javascript"> -console.log("Line break, no indent, nested") -</script> -<script type="text/javascript"> - console.log("Line break, wrong indent, nested"); -</script> - <script type="text/javascript"> -console.log("Wrong tag indents") - </script> -<script type="text/javascript"> -console.log("Empty lines when closing."); - - </script> -<script>console.log("Script without type")</script> -<script type="text/javascript"></script> -<script type="text/javascript"> if (condition) { console.log("Missing end of JS block")</script> -</nav> -</body> -</html> \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/html/input/utf-8.html b/_ext/eclipse-wtp/src/test/resources/html/input/utf-8.html deleted file mode 100644 index abf1a7d9a7..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/html/input/utf-8.html +++ /dev/null @@ -1,2 +0,0 @@ -<!DOCTYPE html> -<html><head><meta charset="UTF-8"><title>ÄÜ€ \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/html/restrictions/ReadMe.txt b/_ext/eclipse-wtp/src/test/resources/html/restrictions/ReadMe.txt deleted file mode 100644 index 37c58641d8..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/html/restrictions/ReadMe.txt +++ /dev/null @@ -1,9 +0,0 @@ -Various manual tests have been performed with external and internal DTDs. -They seem not to be used by the HTML formatter. Instead they are a -helper to parse the DOCTYPE and select the right content model. -The DTDParser.parse throws a SAXParseException when parsing the -HTML DTDs provided with the WST JARs (accessed by the System catalog). -This nominal and expected exception is eaten (DTDParser.currentDTD not set). - -Since currently there seems to be no use case for DTD/XSD/catalog, -we omit it for Spotless integration. \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/xml/expected/dtd_external.test b/_ext/eclipse-wtp/src/test/resources/xml/expected/dtd_external.test deleted file mode 100644 index 6f383afaa7..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/xml/expected/dtd_external.test +++ /dev/null @@ -1,8 +0,0 @@ - - - - indent - this text - preserve - spaces - \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/xml/expected/dtd_relative.test b/_ext/eclipse-wtp/src/test/resources/xml/expected/dtd_relative.test deleted file mode 100644 index 37c565592b..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/xml/expected/dtd_relative.test +++ /dev/null @@ -1,8 +0,0 @@ - - - - indent - this text - preserve - spaces - \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/xml/expected/xml_space.test b/_ext/eclipse-wtp/src/test/resources/xml/expected/xml_space.test deleted file mode 100644 index 8bc506f692..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/xml/expected/xml_space.test +++ /dev/null @@ -1,4 +0,0 @@ - - foo bar - preserve space - \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/xml/expected/xsd_external.test b/_ext/eclipse-wtp/src/test/resources/xml/expected/xsd_external.test deleted file mode 100644 index 0456632618..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/xml/expected/xsd_external.test +++ /dev/null @@ -1,7 +0,0 @@ - - - remove spaces - preserve spaces - \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/xml/expected/xsd_not_found.test b/_ext/eclipse-wtp/src/test/resources/xml/expected/xsd_not_found.test deleted file mode 100644 index 78a27d902c..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/xml/expected/xsd_not_found.test +++ /dev/null @@ -1,7 +0,0 @@ - - - remove spaces - preserve spaces - \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/xml/expected/xsd_relative.test b/_ext/eclipse-wtp/src/test/resources/xml/expected/xsd_relative.test deleted file mode 100644 index 93e86ab38e..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/xml/expected/xsd_relative.test +++ /dev/null @@ -1,7 +0,0 @@ - - - remove spaces - preserve spaces - \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/xml/input/dtd_external.test b/_ext/eclipse-wtp/src/test/resources/xml/input/dtd_external.test deleted file mode 100644 index 1293b55e18..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/xml/input/dtd_external.test +++ /dev/null @@ -1,8 +0,0 @@ - - - - indent - this text - preserve - spaces - \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/xml/input/dtd_relative.test b/_ext/eclipse-wtp/src/test/resources/xml/input/dtd_relative.test deleted file mode 100644 index 431354868c..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/xml/input/dtd_relative.test +++ /dev/null @@ -1,8 +0,0 @@ - - - - indent - this text - preserve - spaces - \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/xml/input/xml_space.test b/_ext/eclipse-wtp/src/test/resources/xml/input/xml_space.test deleted file mode 100644 index 2ddd865f1c..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/xml/input/xml_space.test +++ /dev/null @@ -1 +0,0 @@ - foo bar preserve space \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/xml/input/xsd_external.test b/_ext/eclipse-wtp/src/test/resources/xml/input/xsd_external.test deleted file mode 100644 index e75b7ca06f..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/xml/input/xsd_external.test +++ /dev/null @@ -1,4 +0,0 @@ - - - remove spaces preserve spaces - \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/xml/input/xsd_not_found.test b/_ext/eclipse-wtp/src/test/resources/xml/input/xsd_not_found.test deleted file mode 100644 index 483be84785..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/xml/input/xsd_not_found.test +++ /dev/null @@ -1,4 +0,0 @@ - - - remove spaces preserve spaces - \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/xml/input/xsd_relative.test b/_ext/eclipse-wtp/src/test/resources/xml/input/xsd_relative.test deleted file mode 100644 index ce8392adf8..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/xml/input/xsd_relative.test +++ /dev/null @@ -1,4 +0,0 @@ - - - remove spaces preserve spaces - \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/xml/restrictions/catalog.xml b/_ext/eclipse-wtp/src/test/resources/xml/restrictions/catalog.xml deleted file mode 100644 index 2fba8dc2b9..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/xml/restrictions/catalog.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/_ext/eclipse-wtp/src/test/resources/xml/restrictions/test.dtd b/_ext/eclipse-wtp/src/test/resources/xml/restrictions/test.dtd deleted file mode 100644 index 5076fd91e0..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/xml/restrictions/test.dtd +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/_ext/eclipse-wtp/src/test/resources/xml/restrictions/test.xsd b/_ext/eclipse-wtp/src/test/resources/xml/restrictions/test.xsd deleted file mode 100644 index 6b0e3b5e00..0000000000 --- a/_ext/eclipse-wtp/src/test/resources/xml/restrictions/test.xsd +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/_ext/gradle/java-setup.gradle b/_ext/gradle/java-setup.gradle deleted file mode 100644 index 598077d73c..0000000000 --- a/_ext/gradle/java-setup.gradle +++ /dev/null @@ -1,25 +0,0 @@ -////////// -// JAVA // -////////// -apply from: rootProject.file('gradle/changelog.gradle') -version = spotlessChangelog.versionNext -apply from: rootProject.file('gradle/java-setup.gradle') - -// Allow declaration of api/compile dependencies -apply plugin: 'java-library' - -// Show warning locations, fail on warnings -tasks.withType(JavaCompile) { - options.compilerArgs << "-Xlint:unchecked" - options.compilerArgs << "-Xlint:deprecation" - options.compilerArgs << "-Werror" -} - -//Currently testlib is not used by _ext. Hence the external dependencies are added here. -dependencies { - testImplementation "org.junit.jupiter:junit-jupiter:${VER_JUNIT}" - testImplementation "org.assertj:assertj-core:${VER_ASSERTJ}" - testImplementation project(':testlib') -} - -test { useJUnitPlatform() } diff --git a/_ext/gradle/p2-fat-jar-setup.gradle b/_ext/gradle/p2-fat-jar-setup.gradle deleted file mode 100644 index a8f4e242af..0000000000 --- a/_ext/gradle/p2-fat-jar-setup.gradle +++ /dev/null @@ -1,165 +0,0 @@ -apply from: rootProject.file('_ext/gradle/java-setup.gradle') - -apply plugin: 'com.diffplug.p2.asmaven' - -ext { - // P2 Repository URL - if (!project.hasProperty('p2Repository')) { - p2Repository = "p2Repository not defined in project" - } - // P2 dependencies - if (!project.hasProperty('p2Dependencies')) { - p2Dependencies = "p2Dependencies not defined in project" - } - - // Some JARs may include JARs themselfs, which shall be unpacked and added to the embedded class folder. - if (!project.hasProperty('internalJars')) { - internalJars = [] - } - - // Include form the JARs, which goes into a fat-jar with the spottless formatter interface. - if (!project.hasProperty('jarInclude')) { - jarInclude = [ - '**/*.class', // Take all classes - '**/*.properties', // Text resources (for messages, etc) - '**/*.xml', // Plugin XML and other resources - '*.html', // License information about the included JARs, - 'META-INF/**' // Plugin manifest and addtional information - ] - } - - // Exclude form the JARs, which goes into a fat-jar with the spottless formatter interface. - if (!project.hasProperty('jarExclude')) { - jarExclude = [ - 'META-INF/*.RSA', // The eclipse jars are signed, and our fat-jar breaks the signatures - 'META-INF/*.SF', // ... so all signatures are filtered - ] - } - - // Map fat-JAR resources path if JAR file name does not correspond to plugin package name (e.g. required for internal plugins) - if (!project.hasProperty('fatJarResourcesMap')) { - fatJarResourcesMap = [:] - } - - - // The directory contains all external classes for the fat-jar - embeddedClassesDirName = 'build/embeddedClasses' - embeddedClassesDir = project.file(embeddedClassesDirName) -} - -if (gradle.startParameter.projectProperties.get('com.diffplug.spotless.include.ext.nop2') != 'true') { - // build a maven repo in our build folder containing these artifacts - p2AsMaven { - group 'p2', { - repo project.p2Repository - p2Dependencies.keySet.each { p2.addIU(it) } - p2ant { - /* - Define p2ant proxy settings as a closure. Refer to the API documents for instructions: - https://diffplug.github.io/goomph/javadoc/3.17.4/com/diffplug/gradle/p2/AsMavenPlugin.html - */ - if (project.hasProperty('setP2AntProxy')) { - setP2AntProxy(it) - } - } - } - } -} - -configurations { - embeddedJars // P2 JARs the fat-JAR is based uppon -} - -dependencies { - p2Dependencies.each { groupArtifact, version -> - embeddedJars "p2:${groupArtifact}:${version}" - } - // Includes the classes from P2 JARs during compilation - implementation files(embeddedClassesDir) -} - -jar { - // Add P2 clases to fat-JAR - from embeddedClassesDir -} - -////////////////////////// -// Unpack External Deps // -////////////////////////// -import java.io.File - -task unjarEmbeddedClasses { - description = "Copies filtered set of embedded classes from the Eclise/GrEclipse dependencies to '${project.relativePath(embeddedClassesDir)}'." - inputs.files(configurations.embeddedJars) - inputs.property('internalJars', internalJars) - inputs.property('jarInclude', jarInclude) - inputs.property('jarExclude', jarExclude) - inputs.property('fatJarResourcesMap', fatJarResourcesMap) - outputs.dir(embeddedClassesDir) - - doLast { - embeddedClassesDir.deleteDir() - embeddedClassesDir.mkdirs() - configurations.embeddedJars.each { - unjar(it, embeddedClassesDir) - } - // Unpack internal JARs. Maintain the order defined in internalJars - internalJars.each { - fileTree(embeddedClassesDir).include("${it}.jar").each { - unjar(it, embeddedClassesDir) - delete(it) - } - } - } -} - -def unjar(File jarFile, File destDir) { - ant.unjar(src: jarFile, dest: destDir) { - patternset { - jarInclude.each { - include(name: "${it}") - } - internalJars.each { - include(name: "**/${it}.jar") - } - jarExclude.each { - exclude(name: "${it}") - } - } - } - // Provide Fat JAR resources (following naming convention of spotless-eclipse-base) - def fat_jar_resource_dir = jarFile.getName().split('-')[0] - fat_jar_resource_dir = fatJarResourcesMap.getOrDefault(fat_jar_resource_dir, fat_jar_resource_dir) - ant.move(todir: "${destDir}/${fat_jar_resource_dir}/META-INF", quiet: 'true', failonerror: 'false') { - fileset(dir: "${destDir}/META-INF") - } - //Keep licenses and other human readable information for transparency - ant.move(todir: "${destDir}/${fat_jar_resource_dir}", quiet: 'true') { - fileset(dir: destDir) { - include(name: '*') - type(type: 'file') - exclude(name: '*jar-*') - exclude(name: '*.jar') - } - } - -} - -tasks.compileJava.dependsOn(unjarEmbeddedClasses) - -///////// -// IDE // -///////// - -apply plugin: 'eclipse' - -// always create fresh projects -tasks.eclipse.dependsOn(cleanEclipse) -// Encure that the dependent classes are preovided for compilation if project is build via Eclipse instead of command line -tasks.eclipseClasspath.dependsOn(unjarEmbeddedClasses) - -apply plugin: 'idea' - -// Encure that the dependent classes are preovided for compilation if project is build via Eclipse instead of command line -tasks.idea.dependsOn(unjarEmbeddedClasses) - diff --git a/_ext/gradle/update-lockfile.gradle b/_ext/gradle/update-lockfile.gradle deleted file mode 100644 index 098d960eff..0000000000 --- a/_ext/gradle/update-lockfile.gradle +++ /dev/null @@ -1,6 +0,0 @@ -// Use file locking -configurations.all { - resolutionStrategy { - activateDependencyLocking() - } -} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 16136e07ec..0ae84bd9c4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,3 +1,14 @@ +apply plugin: 'dev.equo.ide' +equoIde { + branding().title('Spotless').icon(file('_images/spotless_logo.png')) + welcome().openUrl('https://github.com/diffplug/spotless/blob/main/CONTRIBUTING.md') + gradleBuildship().autoImport('.') +} + +repositories { + mavenCentral() +} + apply from: rootProject.file('gradle/java-publish.gradle') apply from: rootProject.file('gradle/changelog.gradle') allprojects { @@ -5,9 +16,6 @@ allprojects { } apply from: rootProject.file('gradle/spotless-freshmark.gradle') -repositories { - mavenCentral() -} spotless { groovyGradle { target '*.gradle', 'gradle/*.gradle' diff --git a/gradle.properties b/gradle.properties index 09d152d886..222012d500 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,9 @@ # To fix metaspace errors -org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=1024m -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=1024m -Dfile.encoding=UTF-8 +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configuration-cache=true + name=spotless description=Spotless - keep your code spotless with Gradle org=diffplug @@ -16,8 +20,7 @@ artifactIdMaven=spotless-maven-plugin artifactIdGradle=spotless-plugin-gradle # Build requirements -VER_JAVA=1.8 -VER_SPOTBUGS=4.5.0 +VER_JAVA=11 VER_JSR_305=3.0.2 # Dependencies provided by Spotless plugin @@ -25,13 +28,7 @@ VER_SLF4J=[1.6,2.0[ # Used in multiple places VER_DURIAN=1.2.0 -VER_JGIT=5.13.1.202206130422-r -VER_JUNIT=5.9.0 -VER_ASSERTJ=3.23.1 -VER_MOCKITO=3.12.4 - -# Used for Maven Plugin -VER_MAVEN_API=3.0 -VER_ECLIPSE_AETHER=0.9.1.v20140329 -VER_MUSTACHE=0.9.10 -VER_PLEXUS_RESOURCES=1.2.0 +VER_JGIT=6.7.0.202309050840-r +VER_JUNIT=5.10.1 +VER_ASSERTJ=3.24.2 +VER_MOCKITO=5.8.0 diff --git a/gradle/changelog.gradle b/gradle/changelog.gradle index 316e98a2d7..83c16f474e 100644 --- a/gradle/changelog.gradle +++ b/gradle/changelog.gradle @@ -17,6 +17,7 @@ spotlessChangelog { // need -Prelease=true in order to do a publish appendDashSnapshotUnless_dashPrelease=true + branch 'release' tagPrefix "${kind}/" commitMessage "Published ${kind}/{{version}}" // {{version}} will be replaced } diff --git a/gradle/java-publish.gradle b/gradle/java-publish.gradle index 78e73a351f..d6f0afa5a0 100644 --- a/gradle/java-publish.gradle +++ b/gradle/java-publish.gradle @@ -1,6 +1,20 @@ +import java.nio.charset.StandardCharsets + +def decode64(String varName) { + String envValue = System.env[varName] + if (envValue == null) { + return "" + } else { + return new String(envValue.decodeBase64(), "UTF-8") + } +} if (project.parent == null) { group = 'com.diffplug.spotless' + def pass = System.env['ORG_GRADLE_PROJECT_nexus_pass64'] + if (pass != null) { + pass = pass.decodeBase64() + } // it's the root project apply plugin: 'io.github.gradle-nexus.publish-plugin' nexusPublishing { @@ -9,7 +23,7 @@ if (project.parent == null) { nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) username = System.env['ORG_GRADLE_PROJECT_nexus_user'] - password = System.env['ORG_GRADLE_PROJECT_nexus_pass'] + password = decode64('ORG_GRADLE_PROJECT_nexus_pass64') } } } @@ -63,12 +77,11 @@ javadoc { // // Thus, no javadoc warnings. options.addStringOption('Xdoclint:none', '-quiet') - options.addStringOption('source', '8') + options.addStringOption('source', '11') // setup the header options.header javadocInfo // setup links - options.linksOffline('https://docs.oracle.com/javase/8/docs/api/', "${dotdotGradle}/javadoc/java8") - options.linksOffline('https://docs.gradle.org/2.14/javadoc/', "${dotdotGradle}/javadoc/gradle") + options.linksOffline('https://docs.gradle.org/6.1.1/javadoc/', "${dotdotGradle}/javadoc/gradle") // links to javadoc from the other versions options.linksOffline("https://javadoc.io/static/com.diffplug.spotless/spotless-lib/${rootProject.spotlessChangelog.versionLast}", "${dotdotGradle}/javadoc/spotless-lib") @@ -156,9 +169,13 @@ model { } } -if (!version.endsWith('-SNAPSHOT')) { +if (System.env['JITPACK'] == 'true' || version.endsWith('-SNAPSHOT')) { signing { - String gpg_key = new String(System.env['ORG_GRADLE_PROJECT_gpg_key64'].decodeBase64()) + setRequired(false) + } +} else { + signing { + String gpg_key = decode64('ORG_GRADLE_PROJECT_gpg_key64') useInMemoryPgpKeys(gpg_key, System.env['ORG_GRADLE_PROJECT_gpg_passphrase']) sign(publishing.publications) } @@ -172,14 +189,12 @@ if (!version.endsWith('-SNAPSHOT')) { } // ensures that changelog bump and push only happens if the publish was successful def thisProj = project - afterEvaluate { - changelogTasks.named('changelogBump').configure { - dependsOn thisProj.tasks.named('publishPluginMavenPublicationToSonatypeRepository') - dependsOn rootProject.tasks.named('closeAndReleaseSonatypeStagingRepository') - // if we have a gradle plugin, we need to push it up to the plugin portal too - if (thisProj.tasks.names.contains('publishPlugins')) { - dependsOn thisProj.tasks.named('publishPlugins') - } + changelogTasks.named('changelogBump').configure { + dependsOn ":${thisProj.path}:publishPluginMavenPublicationToSonatypeRepository" + dependsOn ":closeAndReleaseSonatypeStagingRepository" + // if we have a Gradle plugin, we need to push it up to the plugin portal too + if (thisProj.tasks.names.contains('publishPlugins')) { + dependsOn thisProj.tasks.named('publishPlugins') } } } diff --git a/gradle/java-setup.gradle b/gradle/java-setup.gradle index 9c9f394405..6663447ba4 100644 --- a/gradle/java-setup.gradle +++ b/gradle/java-setup.gradle @@ -1,25 +1,28 @@ ////////// // JAVA // ////////// -repositories { mavenCentral() } // setup java apply plugin: 'java' - -sourceCompatibility = VER_JAVA -targetCompatibility = VER_JAVA -tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' + options.release = Integer.parseInt(VER_JAVA) +} ////////////// // SPOTBUGS // ////////////// apply plugin: 'com.github.spotbugs' spotbugs { - toolVersion = VER_SPOTBUGS ignoreFailures = false // bug free or it doesn't ship! - reportLevel = 'medium' // low|medium|high (low = sensitive to even minor mistakes) + // LOW|MEDIUM|DEFAULT|HIGH (low = sensitive to even minor mistakes). + reportLevel = com.github.spotbugs.snom.Confidence.valueOf('MEDIUM') omitVisitors = [ - 'FindReturnRef'] // https://spotbugs.readthedocs.io/en/latest/detectors.html#findreturnref + // https://spotbugs.readthedocs.io/en/latest/detectors.html#constructorthrow + 'ConstructorThrow', + // https://spotbugs.readthedocs.io/en/latest/detectors.html#findreturnref + 'FindReturnRef', + ] } tasks.named('spotbugsTest') { enabled = false @@ -33,15 +36,8 @@ tasks.withType(com.github.spotbugs.snom.SpotBugsTask).configureEach { } } -tasks.named('spotbugsMain') { - // only run on Java 8 (no benefit to running twice) - enabled = org.gradle.api.JavaVersion.current() == org.gradle.api.JavaVersion.VERSION_11 - reports { - html.enabled = true - } -} dependencies { compileOnly 'net.jcip:jcip-annotations:1.0' - compileOnly "com.github.spotbugs:spotbugs-annotations:${VER_SPOTBUGS}" + compileOnly "com.github.spotbugs:spotbugs-annotations:${spotbugs.toolVersion.get()}" compileOnly "com.google.code.findbugs:jsr305:${VER_JSR_305}" } diff --git a/gradle/javadoc/java8/package-list b/gradle/javadoc/java8/package-list deleted file mode 100644 index 496d90dbfa..0000000000 --- a/gradle/javadoc/java8/package-list +++ /dev/null @@ -1,216 +0,0 @@ -java.applet -java.awt -java.awt.color -java.awt.datatransfer -java.awt.dnd -java.awt.event -java.awt.font -java.awt.geom -java.awt.im -java.awt.im.spi -java.awt.image -java.awt.image.renderable -java.awt.print -java.beans -java.beans.beancontext -java.io -java.lang -java.lang.annotation -java.lang.instrument -java.lang.invoke -java.lang.management -java.lang.ref -java.lang.reflect -java.math -java.net -java.nio -java.nio.channels -java.nio.channels.spi -java.nio.charset -java.nio.charset.spi -java.nio.file -java.nio.file.attribute -java.nio.file.spi -java.rmi -java.rmi.activation -java.rmi.dgc -java.rmi.registry -java.rmi.server -java.security -java.security.acl -java.security.cert -java.security.interfaces -java.security.spec -java.sql -java.text -java.text.spi -java.time -java.time.chrono -java.time.format -java.time.temporal -java.time.zone -java.util -java.util.concurrent -java.util.concurrent.atomic -java.util.concurrent.locks -java.util.function -java.util.jar -java.util.prefs -java.util.regex -java.util.spi -java.util.stream -java.util.zip -javax.accessibility -javax.activation -javax.activity -javax.annotation -javax.annotation.processing -javax.crypto -javax.crypto.interfaces -javax.crypto.spec -javax.imageio -javax.imageio.event -javax.imageio.metadata -javax.imageio.plugins.bmp -javax.imageio.plugins.jpeg -javax.imageio.spi -javax.imageio.stream -javax.jws -javax.jws.soap -javax.lang.model -javax.lang.model.element -javax.lang.model.type -javax.lang.model.util -javax.management -javax.management.loading -javax.management.modelmbean -javax.management.monitor -javax.management.openmbean -javax.management.relation -javax.management.remote -javax.management.remote.rmi -javax.management.timer -javax.naming -javax.naming.directory -javax.naming.event -javax.naming.ldap -javax.naming.spi -javax.net -javax.net.ssl -javax.print -javax.print.attribute -javax.print.attribute.standard -javax.print.event -javax.rmi -javax.rmi.CORBA -javax.rmi.ssl -javax.script -javax.security.auth -javax.security.auth.callback -javax.security.auth.kerberos -javax.security.auth.login -javax.security.auth.spi -javax.security.auth.x500 -javax.security.cert -javax.security.sasl -javax.sound.midi -javax.sound.midi.spi -javax.sound.sampled -javax.sound.sampled.spi -javax.sql -javax.sql.rowset -javax.sql.rowset.serial -javax.sql.rowset.spi -javax.swing -javax.swing.border -javax.swing.colorchooser -javax.swing.event -javax.swing.filechooser -javax.swing.plaf -javax.swing.plaf.basic -javax.swing.plaf.metal -javax.swing.plaf.multi -javax.swing.plaf.nimbus -javax.swing.plaf.synth -javax.swing.table -javax.swing.text -javax.swing.text.html -javax.swing.text.html.parser -javax.swing.text.rtf -javax.swing.tree -javax.swing.undo -javax.tools -javax.transaction -javax.transaction.xa -javax.xml -javax.xml.bind -javax.xml.bind.annotation -javax.xml.bind.annotation.adapters -javax.xml.bind.attachment -javax.xml.bind.helpers -javax.xml.bind.util -javax.xml.crypto -javax.xml.crypto.dom -javax.xml.crypto.dsig -javax.xml.crypto.dsig.dom -javax.xml.crypto.dsig.keyinfo -javax.xml.crypto.dsig.spec -javax.xml.datatype -javax.xml.namespace -javax.xml.parsers -javax.xml.soap -javax.xml.stream -javax.xml.stream.events -javax.xml.stream.util -javax.xml.transform -javax.xml.transform.dom -javax.xml.transform.sax -javax.xml.transform.stax -javax.xml.transform.stream -javax.xml.validation -javax.xml.ws -javax.xml.ws.handler -javax.xml.ws.handler.soap -javax.xml.ws.http -javax.xml.ws.soap -javax.xml.ws.spi -javax.xml.ws.spi.http -javax.xml.ws.wsaddressing -javax.xml.xpath -org.ietf.jgss -org.omg.CORBA -org.omg.CORBA.DynAnyPackage -org.omg.CORBA.ORBPackage -org.omg.CORBA.TypeCodePackage -org.omg.CORBA.portable -org.omg.CORBA_2_3 -org.omg.CORBA_2_3.portable -org.omg.CosNaming -org.omg.CosNaming.NamingContextExtPackage -org.omg.CosNaming.NamingContextPackage -org.omg.Dynamic -org.omg.DynamicAny -org.omg.DynamicAny.DynAnyFactoryPackage -org.omg.DynamicAny.DynAnyPackage -org.omg.IOP -org.omg.IOP.CodecFactoryPackage -org.omg.IOP.CodecPackage -org.omg.Messaging -org.omg.PortableInterceptor -org.omg.PortableInterceptor.ORBInitInfoPackage -org.omg.PortableServer -org.omg.PortableServer.CurrentPackage -org.omg.PortableServer.POAManagerPackage -org.omg.PortableServer.POAPackage -org.omg.PortableServer.ServantLocatorPackage -org.omg.PortableServer.portable -org.omg.SendingContext -org.omg.stub.java.rmi -org.w3c.dom -org.w3c.dom.bootstrap -org.w3c.dom.events -org.w3c.dom.ls -org.w3c.dom.views -org.xml.sax -org.xml.sax.ext -org.xml.sax.helpers diff --git a/gradle/special-tests.gradle b/gradle/special-tests.gradle index c435bc7e81..8d9004a709 100644 --- a/gradle/special-tests.gradle +++ b/gradle/special-tests.gradle @@ -1,25 +1,35 @@ -apply plugin: 'org.gradle.test-retry' apply plugin: 'com.adarshr.test-logger' + +// See com.diffplug.spotless.tag package for available JUnit 5 @Tag annotations def special = [ - 'Npm', 'Black', - 'Clang' + 'Buf', + 'Clang', + 'gofmt', + 'Npm', + 'Shfmt' ] boolean isCiServer = System.getenv().containsKey("CI") -tasks.named('test') { - // See com.diffplug.spotless.tag package for available JUnit 5 @Tag annotations - useJUnitPlatform { - excludeTags special as String[] - } +tasks.withType(Test).configureEach { if (isCiServer) { retry { maxRetries = 2 maxFailures = 10 } + + // There are only 2 cores in each GitHub Action Runner, we use all of them here. + maxParallelForks = Runtime.getRuntime().availableProcessors() + } else { + // https://docs.gradle.org/8.4/userguide/performance.html#execute_tests_in_parallel + maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 + } +} +tasks.named('test').configure { + useJUnitPlatform { + excludeTags special as String[] } } - special.forEach { tag -> tasks.register("test${tag}", Test) { useJUnitPlatform { includeTags tag } diff --git a/gradle/spotless-freshmark.gradle b/gradle/spotless-freshmark.gradle index 30c5ab6306..54623eb05a 100644 --- a/gradle/spotless-freshmark.gradle +++ b/gradle/spotless-freshmark.gradle @@ -1,30 +1,4 @@ -import java.util.regex.Matcher -import java.util.regex.Pattern - - -def thisVm() { - String jre = System.getProperty("java.version") - if (jre.startsWith("1.8")) { - return 8 - } else { - Matcher matcher = Pattern.compile("(\\d+)").matcher(jre) - if (!matcher.find()) { - throw new IllegalArgumentException("Expected " + jre + " to start with an integer") - } - int version = Integer.parseInt(matcher.group(1)) - if (version <= 8) { - throw new IllegalArgumentException("Expected " + jre + " to start with an integer greater than 8") - } - return version - } -} - -if (thisVm() >= 15) { - // freshmark doesn't run on JRE 15+ - return -} - apply plugin: 'com.diffplug.spotless' import com.diffplug.gradle.spotless.FreshMarkExtension @@ -37,7 +11,6 @@ Action freshmarkSetup = { it.put('no', ':white_large_square:') } it.indentWithSpaces(2) - it.trimTrailingWhitespace() it.endWithNewline() } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar old mode 100755 new mode 100644 index 249e5832f0..d64cd49177 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ae04661ee7..1af9e0930b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index a69d9cb6c2..1aa94a4269 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +131,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index f127cfd49d..93e3f59f13 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 0000000000..adb3fe10c8 --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,2 @@ +jdk: + - openjdk11 diff --git a/lib-extra/build.gradle b/lib-extra/build.gradle index 38e7c37403..35952873a5 100644 --- a/lib-extra/build.gradle +++ b/lib-extra/build.gradle @@ -1,36 +1,98 @@ plugins { id 'java-library' + id 'dev.equo.p2deps' } ext.artifactId = project.artifactIdLibExtra version = rootProject.spotlessChangelog.versionNext apply from: rootProject.file('gradle/java-setup.gradle') apply from: rootProject.file('gradle/java-publish.gradle') +String VER_SOLSTICE = '1.7.5' dependencies { - api project(':lib') + api projects.lib // misc useful utilities implementation "com.diffplug.durian:durian-core:${VER_DURIAN}" implementation "com.diffplug.durian:durian-collect:${VER_DURIAN}" // needed by GitAttributesLineEndings implementation "org.eclipse.jgit:org.eclipse.jgit:${VER_JGIT}" implementation "com.googlecode.concurrent-trees:concurrent-trees:2.6.1" - // used for xml parsing in EclipseFormatter - implementation "org.codehaus.groovy:groovy-xml:3.0.10" + // for eclipse + implementation "dev.equo.ide:solstice:${VER_SOLSTICE}" // testing - testImplementation project(':testlib') + testImplementation projects.testlib testImplementation "org.junit.jupiter:junit-jupiter:${VER_JUNIT}" testImplementation "org.assertj:assertj-core:${VER_ASSERTJ}" testImplementation "com.diffplug.durian:durian-testlib:${VER_DURIAN}" + testRuntimeOnly "org.junit.platform:junit-platform-launcher" +} +spotless { + java { + replaceRegex 'enforceSolsticeVersion', '"dev.equo.ide:solstice:(.*)"', '"dev.equo.ide:solstice:' + VER_SOLSTICE + '"' + } } -// we'll hold the core lib to a high standard -spotbugs { reportLevel = 'low' } // low|medium|high (low = sensitive to even minor mistakes) - -test { - useJUnitPlatform() +apply from: rootProject.file('gradle/special-tests.gradle') +tasks.withType(Test).configureEach { if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_16)) { // needed for EclipseCdtFormatterStepTest jvmArgs '--add-opens=java.base/java.lang=ALL-UNNAMED' } } + +def NEEDS_P2_DEPS = [ + // (alphabetic order please) + 'cdt', + 'groovy', + 'jdt' +] +for (needsP2 in NEEDS_P2_DEPS) { + sourceSets.register(needsP2) { + compileClasspath += sourceSets.main.output + runtimeClasspath += sourceSets.main.output + java {} + } + dependencies { + add("${needsP2}CompileOnly", "dev.equo.ide:solstice:${VER_SOLSTICE}") + } +} + +def jar = tasks.named('jar', Jar) { + for (needsP2 in NEEDS_P2_DEPS) { + from sourceSets.named(needsP2).map { it.output.classesDirs } + } +} + +tasks.withType(Test).configureEach { + dependsOn jar + classpath += jar.get().outputs.files +} + +apply plugin: 'dev.equo.p2deps' +p2deps { + // (alphabetic order please) + into 'cdtCompileOnly', { + p2repo 'https://download.eclipse.org/eclipse/updates/4.26/' + p2repo 'https://download.eclipse.org/tools/cdt/releases/10.7/' + install 'org.eclipse.cdt.core' + } + into 'groovyCompileOnly', { + p2repo 'https://download.eclipse.org/eclipse/updates/4.26/' + p2repo 'https://groovy.jfrog.io/artifactory/plugins-release/org/codehaus/groovy/groovy-eclipse-integration/4.8.0/e4.26/' + install 'org.codehaus.groovy.eclipse.refactoring' + install 'org.codehaus.groovy.eclipse.core' + install 'org.eclipse.jdt.groovy.core' + install 'org.codehaus.groovy' + } + into 'jdtCompileOnly', { + p2repo 'https://download.eclipse.org/eclipse/updates/4.26/' + install 'org.eclipse.jdt.core' + } +} + +// we'll hold the core lib to a high standard +spotbugs { + // LOW|MEDIUM|DEFAULT|HIGH (low = sensitive to even minor mistakes). + reportLevel = com.github.spotbugs.snom.Confidence.valueOf('LOW') +} + diff --git a/_ext/eclipse-cdt/src/main/java/com/diffplug/spotless/extra/eclipse/cdt/EclipseCdtFormatterStepImpl.java b/lib-extra/src/cdt/java/com/diffplug/spotless/extra/glue/cdt/EclipseCdtFormatterStepImpl.java similarity index 66% rename from _ext/eclipse-cdt/src/main/java/com/diffplug/spotless/extra/eclipse/cdt/EclipseCdtFormatterStepImpl.java rename to lib-extra/src/cdt/java/com/diffplug/spotless/extra/glue/cdt/EclipseCdtFormatterStepImpl.java index 4482f3e752..045860f535 100644 --- a/_ext/eclipse-cdt/src/main/java/com/diffplug/spotless/extra/eclipse/cdt/EclipseCdtFormatterStepImpl.java +++ b/lib-extra/src/cdt/java/com/diffplug/spotless/extra/glue/cdt/EclipseCdtFormatterStepImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.diffplug.spotless.extra.eclipse.cdt; +package com.diffplug.spotless.extra.glue.cdt; import java.util.Map; import java.util.Map.Entry; @@ -21,23 +21,16 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.eclipse.cdt.core.CCorePlugin; import org.eclipse.cdt.core.formatter.CodeFormatter; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.text.edits.TextEdit; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseConfig; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipsePluginConfig; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseServiceConfig; - /** Formatter step which calls out to the Eclipse CDT formatter. */ public class EclipseCdtFormatterStepImpl { private final CodeFormatter codeFormatter; public EclipseCdtFormatterStepImpl(Properties settings) throws Exception { - SpotlessEclipseFramework.setup(new FrameworkConfig()); Stream> stream = settings.entrySet().stream(); Map settingsMap = stream.collect(Collectors.toMap( e -> String.valueOf(e.getKey()), @@ -45,24 +38,10 @@ public EclipseCdtFormatterStepImpl(Properties settings) throws Exception { codeFormatter = org.eclipse.cdt.core.ToolFactory.createDefaultCodeFormatter(settingsMap); } - private static class FrameworkConfig implements SpotlessEclipseConfig { - @Override - public void registerServices(SpotlessEclipseServiceConfig config) { - config.applyDefault(); - config.useSlf4J(EclipseCdtFormatterStepImpl.class.getPackage().getName()); - } - - @Override - public void activatePlugins(SpotlessEclipsePluginConfig config) { - config.applyDefault(); - config.add(new CCorePlugin()); - } - } - /** Formatting C/C++ string */ public String format(String raw) throws Exception { //The 'kind' can be set to CodeFormatter.K_UNKNOWN, since it is anyway ignored by the internal formatter - TextEdit edit = codeFormatter.format(CodeFormatter.K_UNKNOWN, raw, 0, raw.length(), 0, SpotlessEclipseFramework.LINE_DELIMITER); + TextEdit edit = codeFormatter.format(CodeFormatter.K_UNKNOWN, raw, 0, raw.length(), 0, "\n"); if (edit == null) { throw new IllegalArgumentException("Invalid C/C++ syntax for formatting."); } else { diff --git a/_ext/eclipse-groovy/src/main/java/com/diffplug/spotless/extra/eclipse/groovy/GrEclipseFormatterStepImpl.java b/lib-extra/src/groovy/java/com/diffplug/spotless/extra/glue/groovy/GrEclipseFormatterStepImpl.java similarity index 74% rename from _ext/eclipse-groovy/src/main/java/com/diffplug/spotless/extra/eclipse/groovy/GrEclipseFormatterStepImpl.java rename to lib-extra/src/groovy/java/com/diffplug/spotless/extra/glue/groovy/GrEclipseFormatterStepImpl.java index 1d2b71c69f..163816c1a4 100644 --- a/_ext/eclipse-groovy/src/main/java/com/diffplug/spotless/extra/eclipse/groovy/GrEclipseFormatterStepImpl.java +++ b/lib-extra/src/groovy/java/com/diffplug/spotless/extra/glue/groovy/GrEclipseFormatterStepImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,14 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.diffplug.spotless.extra.eclipse.groovy; +package com.diffplug.spotless.extra.glue.groovy; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Properties; import org.codehaus.groovy.eclipse.GroovyLogManager; @@ -37,15 +39,36 @@ import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.TextSelection; +import org.eclipse.osgi.internal.location.EquinoxLocations; import org.eclipse.text.edits.TextEdit; +import org.osgi.framework.Constants; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseConfig; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipsePluginConfig; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseServiceConfig; +import dev.equo.solstice.NestedJars; +import dev.equo.solstice.ShimIdeBootstrapServices; +import dev.equo.solstice.Solstice; +import dev.equo.solstice.p2.CacheLocations; /** Spotless-Formatter step which calls out to the Groovy-Eclipse formatter. */ public class GrEclipseFormatterStepImpl { + static { + NestedJars.setToWarnOnly(); + NestedJars.onClassPath().confirmAllNestedJarsArePresentOnClasspath(CacheLocations.p2nestedJars()); + try { + var solstice = Solstice.findBundlesOnClasspath(); + solstice.warnAndModifyManifestsToFix(); + var props = Map.of("osgi.nl", "en_US", + Constants.FRAMEWORK_STORAGE_CLEAN, Constants.FRAMEWORK_STORAGE_CLEAN_ONFIRSTINIT, + EquinoxLocations.PROP_INSTANCE_AREA, Files.createTempDirectory("spotless-groovy").toAbsolutePath().toString()); + solstice.openShim(props); + ShimIdeBootstrapServices.apply(props, solstice.getContext()); + solstice.start("org.apache.felix.scr"); + solstice.startAllWithLazy(false); + solstice.start("org.codehaus.groovy.eclipse.core"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + /** * Groovy compiler problems can be ignored. *

@@ -58,25 +81,11 @@ public class GrEclipseFormatterStepImpl { private final boolean ignoreFormatterProblems; public GrEclipseFormatterStepImpl(final Properties properties) throws Exception { - SpotlessEclipseFramework.setup(new FrameworkConfig()); PreferenceStore preferences = createPreferences(properties); preferencesStore = new FormatterPreferencesOnStore(preferences); ignoreFormatterProblems = Boolean.parseBoolean(properties.getProperty(IGNORE_FORMATTER_PROBLEMS, "false")); } - private static class FrameworkConfig implements SpotlessEclipseConfig { - @Override - public void registerServices(SpotlessEclipseServiceConfig config) { - config.applyDefault(); - config.useSlf4J(GrEclipseFormatterStepImpl.class.getPackage().getName()); - } - - @Override - public void activatePlugins(SpotlessEclipsePluginConfig config) { - config.add(new GroovyCoreActivator()); - } - } - /** Formatting Groovy string */ public String format(String raw) throws Exception { IDocument doc = new Document(raw); @@ -94,32 +103,31 @@ public String format(String raw) throws Exception { /** * Eclipse Groovy formatter does not signal problems by its return value, but by logging errors. */ - private static class GroovyErrorListener implements ILogListener, IGroovyLogger { - - private final List errors; + private static final class GroovyErrorListener implements ILogListener, IGroovyLogger { + private final List errors; public GroovyErrorListener() { /* * We need a synchronized list here, in case multiple instantiations * run in parallel. */ - errors = Collections.synchronizedList(new ArrayList()); + errors = Collections.synchronizedList(new ArrayList<>()); ILog groovyLogger = GroovyCoreActivator.getDefault().getLog(); groovyLogger.addLogListener(this); - synchronized(GroovyLogManager.manager) { + synchronized (GroovyLogManager.manager) { GroovyLogManager.manager.addLogger(this); } } @Override public void logging(final IStatus status, final String plugin) { - errors.add(status.getMessage()); + errors.add(status.getException()); } public boolean errorsDetected() { ILog groovyLogger = GroovyCoreActivator.getDefault().getLog(); groovyLogger.removeLogListener(this); - synchronized(GroovyLogManager.manager) { + synchronized (GroovyLogManager.manager) { GroovyLogManager.manager.removeLogger(this); } return 0 != errors.size(); @@ -133,9 +141,10 @@ public String toString() { } else if (0 == errors.size()) { string.append("Step sucesfully executed."); } - for (String error : errors) { + for (Throwable error : errors) { + error.printStackTrace(); string.append(System.lineSeparator()); - string.append(error); + string.append(error.getMessage()); } return string.toString(); @@ -154,9 +163,12 @@ public boolean isCategoryEnabled(TraceCategory cat) { @Override public void log(TraceCategory arg0, String arg1) { - errors.add(arg1); + try { + throw new RuntimeException(arg1); + } catch (RuntimeException e) { + errors.add(e); + } } - } private static PreferenceStore createPreferences(final Properties properties) throws IOException { @@ -167,5 +179,4 @@ private static PreferenceStore createPreferences(final Properties properties) th preferences.load(input); return preferences; } - } diff --git a/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtFormatterStepImpl.java b/lib-extra/src/jdt/java/com/diffplug/spotless/extra/glue/jdt/EclipseJdtFormatterStepImpl.java similarity index 56% rename from _ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtFormatterStepImpl.java rename to lib-extra/src/jdt/java/com/diffplug/spotless/extra/glue/jdt/EclipseJdtFormatterStepImpl.java index b3fb7acaf3..b394b6278d 100644 --- a/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/EclipseJdtFormatterStepImpl.java +++ b/lib-extra/src/jdt/java/com/diffplug/spotless/extra/glue/jdt/EclipseJdtFormatterStepImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,60 +13,42 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.diffplug.spotless.extra.eclipse.java; +package com.diffplug.spotless.extra.glue.jdt; import java.io.File; +import java.util.HashMap; +import java.util.Map; import java.util.Properties; +import java.util.stream.Collectors; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.ToolFactory; import org.eclipse.jdt.core.formatter.CodeFormatter; import org.eclipse.jdt.internal.compiler.env.IModule; +import org.eclipse.jdt.internal.formatter.DefaultCodeFormatter; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.text.edits.TextEdit; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseConfig; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseFramework; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipsePluginConfig; -import com.diffplug.spotless.extra.eclipse.base.SpotlessEclipseServiceConfig; - /** Formatter step which calls out to the Eclipse JDT formatter. */ public class EclipseJdtFormatterStepImpl { + /** Spotless demands for internal formatter chains Unix (LF) line endings. */ + public static final String LINE_DELIMITER = "\n"; private final CodeFormatter codeFormatter; - public EclipseJdtFormatterStepImpl(Properties settings) throws Exception { - SpotlessEclipseFramework.setup(new FrameworkConfig()); - this.codeFormatter = ToolFactory.createCodeFormatter(settings, ToolFactory.M_FORMAT_EXISTING); - } - - private static class FrameworkConfig implements SpotlessEclipseConfig { - @Override - public void registerServices(SpotlessEclipseServiceConfig config) { - config.applyDefault(); - config.useSlf4J(EclipseJdtFormatterStepImpl.class.getPackage().getName()); - } - - @Override - public void activatePlugins(SpotlessEclipsePluginConfig config) { - config.applyDefault(); - config.add(new JavaCore()); - } - } - - /** @deprecated Use {@link #format(String, File)} instead. */ - @Deprecated - public String format(String raw) throws Exception { - return format(raw, new File("")); + public EclipseJdtFormatterStepImpl(Properties settings) { + Map options = settings.entrySet().stream().collect(Collectors.toMap( + e -> String.valueOf(e.getKey()), + e -> String.valueOf(e.getValue()), + (prev, next) -> next, + HashMap::new)); + this.codeFormatter = new DefaultCodeFormatter(options); } /** Formatting Java string, distinguishing module-info and compilation unit by file name */ public String format(String raw, File file) throws Exception { int kind = (file.getName().equals(IModule.MODULE_INFO_JAVA) ? CodeFormatter.K_MODULE_INFO : CodeFormatter.K_COMPILATION_UNIT) | CodeFormatter.F_INCLUDE_COMMENTS; - - TextEdit edit = codeFormatter.format(kind, raw, 0, raw.length(), 0, SpotlessEclipseFramework.LINE_DELIMITER); + TextEdit edit = codeFormatter.format(kind, raw, 0, raw.length(), 0, LINE_DELIMITER); if (edit == null) { throw new IllegalArgumentException("Invalid java syntax for formatting."); } else { @@ -75,5 +57,4 @@ public String format(String raw, File file) throws Exception { return doc.get(); } } - } diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/EquoBasedStepBuilder.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/EquoBasedStepBuilder.java new file mode 100644 index 0000000000..3d3bb68241 --- /dev/null +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/EquoBasedStepBuilder.java @@ -0,0 +1,183 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.extra; + +import static java.util.stream.Collectors.toMap; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import javax.annotation.Nullable; + +import com.diffplug.spotless.FileSignature; +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterProperties; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.JarState; +import com.diffplug.spotless.Provisioner; +import com.diffplug.spotless.ThrowingEx; + +import dev.equo.solstice.NestedJars; +import dev.equo.solstice.p2.P2ClientCache; +import dev.equo.solstice.p2.P2Model; +import dev.equo.solstice.p2.P2QueryCache; +import dev.equo.solstice.p2.P2QueryResult; + +/** + * Generic Eclipse based formatter step {@link State} builder. + */ +public abstract class EquoBasedStepBuilder { + private final String formatterName; + private final Provisioner mavenProvisioner; + private final ThrowingEx.Function stateToFormatter; + private String formatterVersion; + private Iterable settingsFiles = new ArrayList<>(); + private Map p2Mirrors = Map.of(); + + /** @deprecated if you use this constructor you *must* call {@link #setVersion(String)} before calling {@link #build()} */ + @Deprecated + public EquoBasedStepBuilder(String formatterName, Provisioner mavenProvisioner, ThrowingEx.Function stateToFormatter) { + this(formatterName, mavenProvisioner, null, stateToFormatter); + } + + /** Initialize valid default configuration, taking latest version */ + public EquoBasedStepBuilder(String formatterName, Provisioner mavenProvisioner, @Nullable String defaultVersion, ThrowingEx.Function stateToFormatter) { + this.formatterName = formatterName; + this.mavenProvisioner = mavenProvisioner; + this.formatterVersion = defaultVersion; + this.stateToFormatter = stateToFormatter; + } + + public void setVersion(String version) { + formatterVersion = version; + } + + public void setPreferences(Iterable settingsFiles) { + this.settingsFiles = settingsFiles; + } + + public void setP2Mirrors(Map p2Mirrors) { + this.p2Mirrors = Map.copyOf(p2Mirrors); + } + + public void setP2Mirrors(Collection p2Mirrors) { + this.p2Mirrors = p2Mirrors.stream().collect(toMap(P2Mirror::getPrefix, P2Mirror::getUrl)); + } + + /** Returns the FormatterStep (whose state will be calculated lazily). */ + public FormatterStep build() { + return FormatterStep.createLazy(formatterName, this::get, stateToFormatter); + } + + protected abstract P2Model model(String version); + + protected void addPlatformRepo(P2Model model, String version) { + if (!version.startsWith("4.")) { + throw new IllegalArgumentException("Expected 4.x"); + } + int minorVersion = Integer.parseInt(version.substring("4.".length())); + + model.addP2Repo("https://download.eclipse.org/eclipse/updates/" + version + "/"); + model.getInstall().addAll(List.of( + "org.apache.felix.scr", + "org.eclipse.equinox.event")); + if (minorVersion >= 25) { + model.getInstall().addAll(List.of( + "org.osgi.service.cm", + "org.osgi.service.metatype")); + } + } + + /** Creates the state of the configuration. */ + EquoBasedStepBuilder.State get() throws Exception { + P2QueryResult query; + try { + query = createModelWithMirrors().query(P2ClientCache.PREFER_OFFLINE, P2QueryCache.ALLOW); + } catch (Exception x) { + throw new IOException("Failed to load " + formatterName + ": " + x, x); + } + var classpath = new ArrayList(); + var mavenDeps = new ArrayList(); + mavenDeps.add("dev.equo.ide:solstice:1.7.4"); + mavenDeps.add("com.diffplug.durian:durian-swt.os:4.2.0"); + mavenDeps.addAll(query.getJarsOnMavenCentral()); + classpath.addAll(mavenProvisioner.provisionWithTransitives(false, mavenDeps)); + classpath.addAll(query.getJarsNotOnMavenCentral()); + for (var nested : NestedJars.inFiles(query.getJarsNotOnMavenCentral()).extractAllNestedJars()) { + classpath.add(nested.getValue()); + } + var jarState = JarState.preserveOrder(classpath); + return new State(formatterVersion, jarState, FileSignature.signAsList(settingsFiles)); + } + + private P2Model createModelWithMirrors() { + P2Model model = model(formatterVersion); + if (p2Mirrors.isEmpty()) { + return model; + } + + ArrayList p2Repos = new ArrayList<>(model.getP2repo()); + p2Repos.replaceAll(url -> { + for (Map.Entry mirror : p2Mirrors.entrySet()) { + String prefix = mirror.getKey(); + if (url.startsWith(prefix)) { + return mirror.getValue() + url.substring(prefix.length()); + } + } + + throw new IllegalStateException("no mirror configured for P2 repository: " + url); + }); + + model.getP2repo().clear(); + model.getP2repo().addAll(p2Repos); + return model; + } + + /** + * State of Eclipse configuration items, providing functionality to derived information + * based on the state. + */ + public static class State implements Serializable { + private static final long serialVersionUID = 584400372246020995L; + final String semanticVersion; + final JarState jarState; + final FileSignature settingsFiles; + + public State(String semanticVersion, JarState jarState, FileSignature settingsFiles) { + this.semanticVersion = semanticVersion; + this.jarState = jarState; + this.settingsFiles = settingsFiles; + } + + public JarState getJarState() { + return jarState; + } + + public String getSemanticVersion() { + return semanticVersion; + } + + public Properties getPreferences() { + return FormatterProperties.from(settingsFiles.files()).getProperties(); + } + } +} diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/GitAttributesLineEndings.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/GitAttributesLineEndings.java index c4ca8edacb..93a95bd6ac 100644 --- a/lib-extra/src/main/java/com/diffplug/spotless/extra/GitAttributesLineEndings.java +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/GitAttributesLineEndings.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,8 @@ import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.SystemReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.googlecode.concurrenttrees.radix.ConcurrentRadixTree; import com.googlecode.concurrenttrees.radix.node.concrete.DefaultCharSequenceNodeFactory; @@ -61,9 +63,47 @@ * back to the platform native. */ public final class GitAttributesLineEndings { + private static final Logger LOGGER = LoggerFactory.getLogger(GitAttributesLineEndings.class); + // prevent direct instantiation private GitAttributesLineEndings() {} + /** + * Creates a line-endings policy which matches {@link #create(File, Supplier)}, + * which is much faster at the cost that every file under the policy + * is assumed to have the same line endings as the first file. + */ + public static LineEnding.Policy createFastAllSame(File projectDir, Supplier> toFormat) { + return new LazyAllTheSame(projectDir, toFormat); + } + + static class LazyAllTheSame extends LazyForwardingEquality implements LineEnding.Policy { + private static final long serialVersionUID = 727912266173243664L; + transient File projectDir; + transient Supplier> toFormat; + + public LazyAllTheSame(File projectDir, Supplier> toFormat) { + this.projectDir = projectDir; + this.toFormat = toFormat; + } + + @Override + protected String calculateState() throws Exception { + var files = toFormat.get().iterator(); + if (files.hasNext()) { + Runtime runtime = new RuntimeInit(projectDir).atRuntime(); + return runtime.getEndingFor(files.next()); + } else { + return LineEnding.UNIX.str(); + } + } + + @Override + public String getEndingFor(File file) { + return state(); + } + } + /** * Creates a line-endings policy whose serialized state is relativized against projectDir, * at the cost of eagerly evaluating the line-ending state of every target file when the @@ -206,7 +246,7 @@ static class Runtime { /** * Default line ending, determined in this order (paths are a teensy different platform to platform). - * + *

* - .git/config (per-repo) * - ~/.gitconfig (per-user) * - /etc/gitconfig (system-wide) @@ -261,7 +301,7 @@ private static String convertEolToLineEnding(String eol, File file) { case "crlf": return LineEnding.WINDOWS.str(); default: - System.err.println(".gitattributes file has unspecified eol value: " + eol + " for " + file + ", defaulting to platform native"); + LOGGER.warn(".gitattributes file has unspecified eol value: {} for {}, defaulting to platform native", eol, file); return LineEnding.PLATFORM_NATIVE.str(); } } @@ -341,8 +381,7 @@ private static List parseRules(@Nullable File file) { return parsed.getRules(); } catch (IOException e) { // no need to crash the whole plugin - System.err.println("Problem parsing " + file.getAbsolutePath()); - e.printStackTrace(); + LOGGER.warn("Problem parsing {}", file.getAbsolutePath(), e); } } return Collections.emptyList(); diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/GitRatchet.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/GitRatchet.java index 6b8d5a9f41..037d4d847b 100644 --- a/lib-extra/src/main/java/com/diffplug/spotless/extra/GitRatchet.java +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/GitRatchet.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; -import java.util.Objects; import java.util.Optional; import javax.annotation.Nullable; @@ -137,7 +136,7 @@ private static boolean worktreeIsCleanCheckout(TreeWalk treeWalk) { private final static int INDEX = 1; private final static int WORKDIR = 2; - Map gitRoots = new HashMap<>(); + Map gitRoots = new HashMap<>(); Table rootTreeShaCache = HashBasedTable.create(); Map subtreeShaCache = new HashMap<>(); @@ -147,25 +146,14 @@ private static boolean worktreeIsCleanCheckout(TreeWalk treeWalk) { * We cache the Repository for every Project in {@code gitRoots}, and use dynamic programming to populate it. */ protected Repository repositoryFor(Project project) throws IOException { - Repository repo = gitRoots.get(project); + File projectGitDir = GitWorkarounds.getDotGitDir(getDir(project)); + if (projectGitDir == null || !RepositoryCache.FileKey.isGitRepository(projectGitDir, FS.DETECTED)) { + throw new IllegalArgumentException("Cannot find git repository in any parent directory"); + } + Repository repo = gitRoots.get(projectGitDir); if (repo == null) { - if (isGitRoot(getDir(project))) { - repo = createRepo(getDir(project)); - } else { - Project parentProj = getParent(project); - if (parentProj == null) { - repo = traverseParentsUntil(getDir(project).getParentFile(), null); - if (repo == null) { - throw new IllegalArgumentException("Cannot find git repository in any parent directory"); - } - } else { - repo = traverseParentsUntil(getDir(project).getParentFile(), getDir(parentProj)); - if (repo == null) { - repo = repositoryFor(parentProj); - } - } - } - gitRoots.put(project, repo); + repo = FileRepositoryBuilder.create(projectGitDir); + gitRoots.put(projectGitDir, repo); } return repo; } @@ -174,26 +162,6 @@ protected Repository repositoryFor(Project project) throws IOException { protected abstract @Nullable Project getParent(Project project); - private static @Nullable Repository traverseParentsUntil(File startWith, @Nullable File file) throws IOException { - while (startWith != null && !Objects.equals(startWith, file)) { - if (isGitRoot(startWith)) { - return createRepo(startWith); - } else { - startWith = startWith.getParentFile(); - } - } - return null; - } - - private static boolean isGitRoot(File dir) { - File dotGit = GitWorkarounds.getDotGitDir(dir); - return dotGit != null && RepositoryCache.FileKey.isGitRepository(dotGit, FS.DETECTED); - } - - static Repository createRepo(File dir) throws IOException { - return FileRepositoryBuilder.create(GitWorkarounds.getDotGitDir(dir)); - } - /** * Fast way to return treeSha of the given ref against the git repository which stores the given project. * Because of parallel project evaluation, there may be races here, so we synchronize on ourselves. However, this method diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/GitWorkarounds.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/GitWorkarounds.java index 70f0b9cb99..94065f0059 100644 --- a/lib-extra/src/main/java/com/diffplug/spotless/extra/GitWorkarounds.java +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/GitWorkarounds.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +42,7 @@ private GitWorkarounds() {} /** * Finds the .git directory for the given project directory. - * + *

* Ordinarily one would just use JGit for this, but it doesn't support worktrees properly. * So this applies an additional workaround for that. * @@ -55,7 +55,7 @@ private GitWorkarounds() {} /** * Creates a {@link RepositorySpecificResolver} for the given project directory. - * + *

* This applies a workaround for JGit not supporting worktrees properly. * * @param projectDir the project directory. @@ -67,7 +67,7 @@ static RepositorySpecificResolver fileRepositoryResolverForProject(File projectD /** * Creates a {@link RepositorySpecificResolver} for the given project directory. - * + *

* This applies a workaround for JGit not supporting worktrees properly. * * @param projectDir the project directory. @@ -86,7 +86,7 @@ static RepositorySpecificResolver fileRepositoryResolverForProject(File projectD /** * Piggyback on the {@link FileRepositoryBuilder} mechanics for finding the git directory. - * + *

* Here we take into account that git repositories can share a common directory. This directory * will contain ./config ./objects/, ./info/, and ./refs/. */ diff --git a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/html/package-info.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/P2Mirror.java similarity index 62% rename from _ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/html/package-info.java rename to lib-extra/src/main/java/com/diffplug/spotless/extra/P2Mirror.java index 770e126a1b..172857422b 100644 --- a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/html/package-info.java +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/P2Mirror.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,8 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/** Eclipse WTP HTML formatter helper */ -@ParametersAreNonnullByDefault -package com.diffplug.spotless.extra.eclipse.wtp.html; +package com.diffplug.spotless.extra; -import javax.annotation.ParametersAreNonnullByDefault; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +@SuppressFBWarnings("UWF_UNWRITTEN_FIELD") +public class P2Mirror { + + private String prefix; + private String url; + + public String getPrefix() { + return prefix; + } + + public String getUrl() { + return url; + } +} diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/cpp/EclipseCdtFormatterStep.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/cpp/EclipseCdtFormatterStep.java index 4c2b06fc9a..569dc05e54 100644 --- a/lib-extra/src/main/java/com/diffplug/spotless/extra/cpp/EclipseCdtFormatterStep.java +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/cpp/EclipseCdtFormatterStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,18 +15,19 @@ */ package com.diffplug.spotless.extra.cpp; -import java.lang.reflect.Method; +import java.lang.reflect.InvocationTargetException; import java.util.Properties; import com.diffplug.spotless.FormatterFunc; import com.diffplug.spotless.Jvm; import com.diffplug.spotless.Provisioner; -import com.diffplug.spotless.extra.EclipseBasedStepBuilder; -import com.diffplug.spotless.extra.EclipseBasedStepBuilder.State; +import com.diffplug.spotless.extra.EquoBasedStepBuilder; + +import dev.equo.solstice.p2.P2Model; /** * Formatter step which calls out to the Eclipse CDT formatter. - * + *

* Eclipse-CDT org.eclipse.core.contenttype.contentTypes * extension cSource, cHeader, cxxSource and cxxHeader. * can handle: "c", "h", "C", "cpp", "cxx", "cc", "c++", "h", "hpp", "hh", "hxx", "inc" @@ -36,25 +37,40 @@ public final class EclipseCdtFormatterStep { private EclipseCdtFormatterStep() {} private static final String NAME = "eclipse cdt formatter"; - private static final String FORMATTER_CLASS = "com.diffplug.spotless.extra.eclipse.cdt.EclipseCdtFormatterStepImpl"; - private static final String FORMATTER_METHOD = "format"; - private static final Jvm.Support JVM_SUPPORT = Jvm. support(NAME).add(8, "4.16.0").add(11, "4.21.0"); + private static final Jvm.Support JVM_SUPPORT = Jvm. support(NAME).add(11, "10.7").add(17, "11.3"); public static String defaultVersion() { return JVM_SUPPORT.getRecommendedFormatterVersion(); } /** Provides default configuration */ - public static EclipseBasedStepBuilder createBuilder(Provisioner provisioner) { - return new EclipseBasedStepBuilder(NAME, provisioner, EclipseCdtFormatterStep::apply); + public static EquoBasedStepBuilder createBuilder(Provisioner provisioner) { + return new EquoBasedStepBuilder(NAME, provisioner, defaultVersion(), EclipseCdtFormatterStep::apply) { + @Override + protected P2Model model(String version) { + var model = new P2Model(); + addPlatformRepo(model, "4.26"); + model.addP2Repo("https://download.eclipse.org/tools/cdt/releases/" + version + "/"); + model.getInstall().add("org.eclipse.cdt.core"); + return model; + } + }; } - private static FormatterFunc apply(State state) throws Exception { + private static FormatterFunc apply(EquoBasedStepBuilder.State state) throws Exception { JVM_SUPPORT.assertFormatterSupported(state.getSemanticVersion()); - Class formatterClazz = state.loadClass(FORMATTER_CLASS); - Object formatter = formatterClazz.getConstructor(Properties.class).newInstance(state.getPreferences()); - Method method = formatterClazz.getMethod(FORMATTER_METHOD, String.class); - return JVM_SUPPORT.suggestLaterVersionOnError(state.getSemanticVersion(), input -> (String) method.invoke(formatter, input)); + Class formatterClazz = state.getJarState().getClassLoader().loadClass("com.diffplug.spotless.extra.glue.cdt.EclipseCdtFormatterStepImpl"); + var formatter = formatterClazz.getConstructor(Properties.class).newInstance(state.getPreferences()); + var method = formatterClazz.getMethod("format", String.class); + return JVM_SUPPORT.suggestLaterVersionOnError(state.getSemanticVersion(), + input -> { + try { + return (String) method.invoke(formatter, input); + } catch (InvocationTargetException exceptionWrapper) { + Throwable throwable = exceptionWrapper.getTargetException(); + Exception exception = (throwable instanceof Exception) ? (Exception) throwable : null; + throw (null == exception) ? exceptionWrapper : exception; + } + }); } - } diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStep.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStep.java index 013f5b5c2c..0a38ff5e64 100644 --- a/lib-extra/src/main/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStep.java +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,15 @@ package com.diffplug.spotless.extra.groovy; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; +import java.util.List; import java.util.Properties; import com.diffplug.spotless.FormatterFunc; import com.diffplug.spotless.Jvm; import com.diffplug.spotless.Provisioner; -import com.diffplug.spotless.extra.EclipseBasedStepBuilder; -import com.diffplug.spotless.extra.EclipseBasedStepBuilder.State; +import com.diffplug.spotless.extra.EquoBasedStepBuilder; + +import dev.equo.solstice.p2.P2Model; /** Formatter step which calls out to the Groovy-Eclipse formatter. */ public final class GrEclipseFormatterStep { @@ -31,26 +32,62 @@ public final class GrEclipseFormatterStep { private GrEclipseFormatterStep() {} private static final String NAME = "eclipse groovy formatter"; - private static final String FORMATTER_CLASS = "com.diffplug.spotless.extra.eclipse.groovy.GrEclipseFormatterStepImpl"; - private static final String FORMATTER_CLASS_OLD = "com.diffplug.gradle.spotless.groovy.eclipse.GrEclipseFormatterStepImpl"; - private static final String MAVEN_GROUP_ARTIFACT = "com.diffplug.spotless:spotless-eclipse-groovy"; - private static final Jvm.Support JVM_SUPPORT = Jvm. support(NAME).add(8, "4.19.0").add(11, "4.21.0"); - private static final String FORMATTER_METHOD = "format"; + private static final Jvm.Support JVM_SUPPORT = Jvm. support(NAME).add(11, "4.26").add(17, "4.29"); public static String defaultVersion() { return JVM_SUPPORT.getRecommendedFormatterVersion(); } - /** Provides default configuration */ - public static EclipseBasedStepBuilder createBuilder(Provisioner provisioner) { - return new EclipseBasedStepBuilder(NAME, provisioner, GrEclipseFormatterStep::apply); + public static EquoBasedStepBuilder createBuilder(Provisioner provisioner) { + return new EquoBasedStepBuilder(NAME, provisioner, defaultVersion(), GrEclipseFormatterStep::apply) { + @Override + protected P2Model model(String version) { + if (!version.startsWith("4.")) { + throw new IllegalArgumentException("Expected version 4.x"); + } + int eVersion = Integer.parseInt(version.substring("4.".length())); + if (eVersion < 8) { + throw new IllegalArgumentException("4.8 is the oldest version we support, this was " + version); + } + String greclipseVersion; + if (eVersion >= 28) { + greclipseVersion = "5." + (eVersion - 28) + ".0"; + } else if (eVersion >= 18) { + greclipseVersion = "4." + (eVersion - 18) + ".0"; + } else { + greclipseVersion = "3." + (eVersion - 8) + ".0"; + } + var model = new P2Model(); + addPlatformRepo(model, version); + model.addP2Repo("https://groovy.jfrog.io/artifactory/plugins-release/org/codehaus/groovy/groovy-eclipse-integration/" + greclipseVersion + "/e" + version + "/"); + model.getInstall().addAll(List.of( + "org.codehaus.groovy.eclipse.refactoring", + "org.codehaus.groovy.eclipse.core", + "org.eclipse.jdt.groovy.core", + "org.codehaus.groovy")); + model.addFilterAndValidate("no-debug", filter -> { + filter.exclude("org.eclipse.jdt.debug"); + }); + return model; + } + + @Override + public void setVersion(String version) { + if (version.endsWith(".0")) { + String newVersion = version.substring(0, version.length() - 2); + System.err.println("Recommend replacing '" + version + "' with '" + newVersion + "' for eclipse JDT"); + version = newVersion; + } + super.setVersion(version); + } + }; } - private static FormatterFunc apply(EclipseBasedStepBuilder.State state) throws Exception { + private static FormatterFunc apply(EquoBasedStepBuilder.State state) throws Exception { JVM_SUPPORT.assertFormatterSupported(state.getSemanticVersion()); - Class formatterClazz = getClass(state); - Object formatter = formatterClazz.getConstructor(Properties.class).newInstance(state.getPreferences()); - Method method = formatterClazz.getMethod(FORMATTER_METHOD, String.class); + Class formatterClazz = state.getJarState().getClassLoader().loadClass("com.diffplug.spotless.extra.glue.groovy.GrEclipseFormatterStepImpl"); + var formatter = formatterClazz.getConstructor(Properties.class).newInstance(state.getPreferences()); + var method = formatterClazz.getMethod("format", String.class); return JVM_SUPPORT.suggestLaterVersionOnError(state.getSemanticVersion(), input -> { try { @@ -62,12 +99,4 @@ private static FormatterFunc apply(EclipseBasedStepBuilder.State state) throws E } }); } - - private static Class getClass(State state) { - if (state.getMavenCoordinate(MAVEN_GROUP_ARTIFACT).isPresent()) { - return state.loadClass(FORMATTER_CLASS); - } - return state.loadClass(FORMATTER_CLASS_OLD); - } - } diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/integration/DiffMessageFormatter.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/integration/DiffMessageFormatter.java index 15c43c815b..9294055a4f 100644 --- a/lib-extra/src/main/java/com/diffplug/spotless/extra/integration/DiffMessageFormatter.java +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/integration/DiffMessageFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,9 +24,11 @@ import java.nio.file.Path; import java.util.List; import java.util.ListIterator; +import java.util.Map; import java.util.Objects; import org.eclipse.jgit.diff.DiffFormatter; +import org.eclipse.jgit.diff.Edit; import org.eclipse.jgit.diff.EditList; import org.eclipse.jgit.diff.MyersDiff; import org.eclipse.jgit.diff.RawText; @@ -143,7 +145,7 @@ public String getMessage() { Objects.requireNonNull(runToFix, "runToFix"); Objects.requireNonNull(formatter, "formatter"); Objects.requireNonNull(problemFiles, "problemFiles"); - DiffMessageFormatter diffFormater = new DiffMessageFormatter(this); + DiffMessageFormatter diffFormater = new DiffMessageFormatter(formatter, problemFiles); return "The following files had format violations:\n" + diffFormater.buffer + runToFix; @@ -151,10 +153,6 @@ public String getMessage() { throw Errors.asRuntime(e); } } - - String relativePath(File file) { - return formatter.getRootDir().relativize(file.toPath()).toString(); - } } private static final int MAX_CHECK_MESSAGE_LINES = 50; @@ -163,25 +161,32 @@ String relativePath(File file) { private final StringBuilder buffer = new StringBuilder(MAX_CHECK_MESSAGE_LINES * 64); private int numLines = 0; - private DiffMessageFormatter(Builder builder) throws IOException { - ListIterator problemIter = builder.problemFiles.listIterator(); + private final CleanProvider formatter; + + private DiffMessageFormatter(CleanProvider formatter, List problemFiles) throws IOException { + this.formatter = Objects.requireNonNull(formatter, "formatter"); + ListIterator problemIter = problemFiles.listIterator(); while (problemIter.hasNext() && numLines < MAX_CHECK_MESSAGE_LINES) { File file = problemIter.next(); - addFile(builder.relativePath(file) + "\n" + DiffMessageFormatter.diff(builder, file)); + addFile(relativePath(file) + "\n" + diff(file)); } if (problemIter.hasNext()) { - int remainingFiles = builder.problemFiles.size() - problemIter.nextIndex(); + int remainingFiles = problemFiles.size() - problemIter.nextIndex(); if (remainingFiles >= MAX_FILES_TO_LIST) { buffer.append("Violations also present in ").append(remainingFiles).append(" other files.\n"); } else { buffer.append("Violations also present in:\n"); while (problemIter.hasNext()) { - addIntendedLine(NORMAL_INDENT, builder.relativePath(problemIter.next())); + addIntendedLine(NORMAL_INDENT, relativePath(problemIter.next())); } } } } + private String relativePath(File file) { + return formatter.getRootDir().relativize(file.toPath()).toString(); + } + private static final int MIN_LINES_PER_FILE = 4; private static final Splitter NEWLINE_SPLITTER = Splitter.on('\n'); @@ -230,10 +235,23 @@ private void addIntendedLine(String indent, String line) { * look like if formatted using the given formatter. Does not end with any newline * sequence (\n, \r, \r\n). */ - private static String diff(Builder builder, File file) throws IOException { - String raw = new String(Files.readAllBytes(file.toPath()), builder.formatter.getEncoding()); + private String diff(File file) throws IOException { + return diff(formatter, file).getValue(); + } + + /** + * Returns a map entry with value being a git-style diff between the contents of the given file and what those contents would + * look like if formatted using the given formatter. Does not end with any newline + * sequence (\n, \r, \r\n). The key of the map entry is the 0-based line where the first difference occurred. + */ + public static Map.Entry diff(Formatter formatter, File file) throws IOException { + return diff(new CleanProviderFormatter(formatter), file); + } + + private static Map.Entry diff(CleanProvider formatter, File file) throws IOException { + String raw = new String(Files.readAllBytes(file.toPath()), formatter.getEncoding()); String rawUnix = LineEnding.toUnix(raw); - String formatted = builder.formatter.getFormatted(file, rawUnix); + String formatted = formatter.getFormatted(file, rawUnix); String formattedUnix = LineEnding.toUnix(formatted); if (rawUnix.equals(formattedUnix)) { @@ -245,13 +263,13 @@ private static String diff(Builder builder, File file) throws IOException { } /** - * Returns a git-style diff between the two unix strings. - * + * Returns a map entry with value being a git-style diff between the two unix strings and key being the 0-based line of the first difference (in the dirty string) + *

* Output has no trailing newlines. - * + *

* Boolean args determine whether whitespace or line endings will be visible. */ - private static String diffWhitespaceLineEndings(String dirty, String clean, boolean whitespace, boolean lineEndings) throws IOException { + private static Map.Entry diffWhitespaceLineEndings(String dirty, String clean, boolean whitespace, boolean lineEndings) throws IOException { dirty = visibleWhitespaceLineEndings(dirty, whitespace, lineEndings); clean = visibleWhitespaceLineEndings(clean, whitespace, lineEndings); @@ -268,14 +286,18 @@ private static String diffWhitespaceLineEndings(String dirty, String clean, bool // we don't need the diff to show this, since we display newlines ourselves formatted = formatted.replace("\\ No newline at end of file\n", ""); - return NEWLINE_MATCHER.trimTrailingFrom(formatted); + return Map.entry(getLineOfFirstDifference(edits), NEWLINE_MATCHER.trimTrailingFrom(formatted)); + } + + private static int getLineOfFirstDifference(EditList edits) { + return edits.stream().mapToInt(Edit::getBeginA).min().getAsInt(); } private static final CharMatcher NEWLINE_MATCHER = CharMatcher.is('\n'); /** * Makes the whitespace and/or the lineEndings visible. - * + *

* MyersDiff wants inputs with only unix line endings. So this ensures that that is the case. */ private static String visibleWhitespaceLineEndings(String input, boolean whitespace, boolean lineEndings) { diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/java/EclipseJdtFormatterStep.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/java/EclipseJdtFormatterStep.java index d0d42f2814..fc477ed881 100644 --- a/lib-extra/src/main/java/com/diffplug/spotless/extra/java/EclipseJdtFormatterStep.java +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/java/EclipseJdtFormatterStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,14 @@ package com.diffplug.spotless.extra.java; import java.io.File; -import java.lang.reflect.Method; import java.util.Properties; import com.diffplug.spotless.FormatterFunc; import com.diffplug.spotless.Jvm; import com.diffplug.spotless.Provisioner; -import com.diffplug.spotless.extra.EclipseBasedStepBuilder; -import com.diffplug.spotless.extra.EclipseBasedStepBuilder.State; +import com.diffplug.spotless.extra.EquoBasedStepBuilder; + +import dev.equo.solstice.p2.P2Model; /** Formatter step which calls out to the Eclipse JDT formatter. */ public final class EclipseJdtFormatterStep { @@ -31,43 +31,40 @@ public final class EclipseJdtFormatterStep { private EclipseJdtFormatterStep() {} private static final String NAME = "eclipse jdt formatter"; - private static final String FORMATTER_CLASS_OLD = "com.diffplug.gradle.spotless.java.eclipse.EclipseFormatterStepImpl"; - private static final String FORMATTER_CLASS = "com.diffplug.spotless.extra.eclipse.java.EclipseJdtFormatterStepImpl"; - private static final String MAVEN_GROUP_ARTIFACT = "com.diffplug.spotless:spotless-eclipse-jdt"; - private static final String FORMATTER_METHOD = "format"; - private static final Jvm.Support JVM_SUPPORT = Jvm. support(NAME).add(8, "4.19.0").add(11, "4.21.0"); + private static final Jvm.Support JVM_SUPPORT = Jvm. support(NAME).add(11, "4.26").add(17, "4.29"); public static String defaultVersion() { return JVM_SUPPORT.getRecommendedFormatterVersion(); } - /** Provides default configuration */ - public static EclipseBasedStepBuilder createBuilder(Provisioner provisioner) { - return new EclipseBasedStepBuilder(NAME, provisioner, EclipseJdtFormatterStep::apply); + public static EquoBasedStepBuilder createBuilder(Provisioner provisioner) { + return new EquoBasedStepBuilder(NAME, provisioner, defaultVersion(), EclipseJdtFormatterStep::apply) { + @Override + protected P2Model model(String version) { + var model = new P2Model(); + addPlatformRepo(model, version); + model.getInstall().add("org.eclipse.jdt.core"); + return model; + } + + @Override + public void setVersion(String version) { + if (version.endsWith(".0")) { + String newVersion = version.substring(0, version.length() - 2); + System.err.println("Recommend replacing '" + version + "' with '" + newVersion + "' for Eclipse JDT"); + version = newVersion; + } + super.setVersion(version); + } + }; } - private static FormatterFunc apply(State state) throws Exception { + private static FormatterFunc apply(EquoBasedStepBuilder.State state) throws Exception { JVM_SUPPORT.assertFormatterSupported(state.getSemanticVersion()); - Class formatterClazz = getClass(state); - Object formatter = formatterClazz.getConstructor(Properties.class).newInstance(state.getPreferences()); - FormatterFunc formatterFunc = getFormatterFunc(formatter, formatterClazz); + Class formatterClazz = state.getJarState().getClassLoader().loadClass("com.diffplug.spotless.extra.glue.jdt.EclipseJdtFormatterStepImpl"); + var formatter = formatterClazz.getConstructor(Properties.class).newInstance(state.getPreferences()); + var method = formatterClazz.getMethod("format", String.class, File.class); + FormatterFunc formatterFunc = (FormatterFunc.NeedsFile) (input, file) -> (String) method.invoke(formatter, input, file); return JVM_SUPPORT.suggestLaterVersionOnError(state.getSemanticVersion(), formatterFunc); } - - private static Class getClass(State state) { - if (state.getMavenCoordinate(MAVEN_GROUP_ARTIFACT).isPresent()) { - return state.loadClass(FORMATTER_CLASS); - } - return state.loadClass(FORMATTER_CLASS_OLD); - } - - private static FormatterFunc getFormatterFunc(Object formatter, Class formatterClazz) throws NoSuchMethodException, SecurityException { - try { - Method method = formatterClazz.getMethod(FORMATTER_METHOD, String.class, File.class); - return (FormatterFunc.NeedsFile) (input, file) -> (String) method.invoke(formatter, input, file); - } catch (NoSuchMethodException e) { - Method method = formatterClazz.getMethod(FORMATTER_METHOD, String.class); - return input -> (String) method.invoke(formatter, input); - } - } } diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.11.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.11.0.lockfile deleted file mode 100644 index 7cad5634fe..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.11.0.lockfile +++ /dev/null @@ -1,21 +0,0 @@ -# Spotless formatter based on CDT version 9.7.0 (see https://www.eclipse.org/cdt/) -com.diffplug.spotless:spotless-eclipse-cdt:9.7.0 -com.diffplug.spotless:spotless-eclipse-base:3.1.1 -com.google.code.findbugs:annotations:3.0.0 -com.google.code.findbugs:jsr305:3.0.0 -com.ibm.icu:icu4j:61.1 -org.eclipse.platform:org.eclipse.core.commands:3.9.300 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.300 -org.eclipse.platform:org.eclipse.core.filebuffers:3.6.500 -org.eclipse.platform:org.eclipse.core.filesystem:1.7.300 -org.eclipse.platform:org.eclipse.core.jobs:3.10.300 -org.eclipse.platform:org.eclipse.core.resources:3.13.300 -org.eclipse.platform:org.eclipse.core.runtime:3.15.200 -org.eclipse.platform:org.eclipse.equinox.app:1.4.100 -org.eclipse.platform:org.eclipse.equinox.common:3.10.300 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.300 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.300 -org.eclipse.platform:org.eclipse.jface.text:3.15.100 -org.eclipse.platform:org.eclipse.jface:3.15.100 -org.eclipse.platform:org.eclipse.osgi:3.13.300 -org.eclipse.platform:org.eclipse.text:3.8.100 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.12.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.12.0.lockfile deleted file mode 100644 index 6fd639528a..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.12.0.lockfile +++ /dev/null @@ -1,21 +0,0 @@ -# Spotless formatter based on CDT version 9.8.0 (see https://www.eclipse.org/cdt/) -com.diffplug.spotless:spotless-eclipse-cdt:9.8.1 -com.diffplug.spotless:spotless-eclipse-base:3.2.1 -com.google.code.findbugs:annotations:3.0.0 -com.google.code.findbugs:jsr305:3.0.0 -com.ibm.icu:icu4j:61.2 -org.eclipse.platform:org.eclipse.core.commands:3.9.400 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.300 -org.eclipse.platform:org.eclipse.core.filebuffers:3.6.600 -org.eclipse.platform:org.eclipse.core.filesystem:1.7.400 -org.eclipse.platform:org.eclipse.core.jobs:3.10.400 -org.eclipse.platform:org.eclipse.core.resources:3.13.400 -org.eclipse.platform:org.eclipse.core.runtime:3.15.300 -org.eclipse.platform:org.eclipse.equinox.app:1.4.200 -org.eclipse.platform:org.eclipse.equinox.common:3.10.400 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.400 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.400 -org.eclipse.platform:org.eclipse.jface.text:3.15.200 -org.eclipse.platform:org.eclipse.jface:3.16.0 -org.eclipse.platform:org.eclipse.osgi:3.14.0 -org.eclipse.platform:org.eclipse.text:3.8.200 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.13.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.13.0.lockfile deleted file mode 100644 index 1d169ff087..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.13.0.lockfile +++ /dev/null @@ -1,21 +0,0 @@ -# Spotless formatter based on CDT version 9.9.0 (see https://www.eclipse.org/cdt/) -com.diffplug.spotless:spotless-eclipse-cdt:9.9.0 -com.diffplug.spotless:spotless-eclipse-base:3.2.1 -com.google.code.findbugs:annotations:3.0.0 -com.google.code.findbugs:jsr305:3.0.0 -com.ibm.icu:icu4j:61.2 -org.eclipse.platform:org.eclipse.core.commands:3.9.500 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.400 -org.eclipse.platform:org.eclipse.core.filebuffers:3.6.700 -org.eclipse.platform:org.eclipse.core.filesystem:1.7.500 -org.eclipse.platform:org.eclipse.core.jobs:3.10.500 -org.eclipse.platform:org.eclipse.core.resources:3.13.500 -org.eclipse.platform:org.eclipse.core.runtime:3.16.0 -org.eclipse.platform:org.eclipse.equinox.app:1.4.300 -org.eclipse.platform:org.eclipse.equinox.common:3.10.500 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.500 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.500 -org.eclipse.platform:org.eclipse.jface.text:3.15.300 -org.eclipse.platform:org.eclipse.jface:3.17.0 -org.eclipse.platform:org.eclipse.osgi:3.15.0 -org.eclipse.platform:org.eclipse.text:3.9.0 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.14.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.14.0.lockfile deleted file mode 100644 index 43cb98774d..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.14.0.lockfile +++ /dev/null @@ -1,22 +0,0 @@ -# Spotless formatter based on CDT version 9.10.0 (see https://www.eclipse.org/cdt/) -com.diffplug.spotless:spotless-eclipse-cdt:9.10.0 -com.diffplug.spotless:spotless-eclipse-base:3.3.0 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -com.ibm.icu:icu4j:64.2 -net.jcip:jcip-annotations:1.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.600 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.500 -org.eclipse.platform:org.eclipse.core.filebuffers:3.6.800 -org.eclipse.platform:org.eclipse.core.filesystem:1.7.600 -org.eclipse.platform:org.eclipse.core.jobs:3.10.600 -org.eclipse.platform:org.eclipse.core.resources:3.13.600 -org.eclipse.platform:org.eclipse.core.runtime:3.17.0 -org.eclipse.platform:org.eclipse.equinox.app:1.4.300 -org.eclipse.platform:org.eclipse.equinox.common:3.10.600 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.600 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.600 -org.eclipse.platform:org.eclipse.jface.text:3.16.100 -org.eclipse.platform:org.eclipse.jface:3.18.0 -org.eclipse.platform:org.eclipse.osgi:3.15.100 -org.eclipse.platform:org.eclipse.text:3.10.0 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.16.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.16.0.lockfile deleted file mode 100644 index 3bf6b1ca01..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.16.0.lockfile +++ /dev/null @@ -1,22 +0,0 @@ -# Spotless formatter based on CDT version 9.11.1 (see https://www.eclipse.org/cdt/) -com.diffplug.spotless:spotless-eclipse-cdt:9.11.0 -com.diffplug.spotless:spotless-eclipse-base:3.3.0 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -com.google.j2objc:j2objc-annotations:1.3 -com.ibm.icu:icu4j:64.2 -org.eclipse.platform:org.eclipse.core.commands:3.9.700 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.700 -org.eclipse.platform:org.eclipse.core.filebuffers:3.6.1000 -org.eclipse.platform:org.eclipse.core.filesystem:1.7.700 -org.eclipse.platform:org.eclipse.core.jobs:3.10.800 -org.eclipse.platform:org.eclipse.core.resources:3.13.700 -org.eclipse.platform:org.eclipse.core.runtime:3.18.0 -org.eclipse.platform:org.eclipse.equinox.app:1.4.500 -org.eclipse.platform:org.eclipse.equinox.common:3.12.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.8.0 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.800 -org.eclipse.platform:org.eclipse.jface.text:3.16.300 -org.eclipse.platform:org.eclipse.jface:3.20.0 -org.eclipse.platform:org.eclipse.osgi:3.15.300 -org.eclipse.platform:org.eclipse.text:3.10.200 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.17.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.17.0.lockfile deleted file mode 100644 index 78a6db147f..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.17.0.lockfile +++ /dev/null @@ -1,22 +0,0 @@ -# Spotless formatter based on CDT version 10.0 (see https://www.eclipse.org/cdt/) -com.diffplug.spotless:spotless-eclipse-cdt:10.0.0 -com.diffplug.spotless:spotless-eclipse-base:3.4.1 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -com.ibm.icu:icu4j:64.2 -net.jcip:jcip-annotations:1.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.700 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.800 -org.eclipse.platform:org.eclipse.core.filebuffers:3.6.1000 -org.eclipse.platform:org.eclipse.core.filesystem:1.7.700 -org.eclipse.platform:org.eclipse.core.jobs:3.10.800 -org.eclipse.platform:org.eclipse.core.resources:3.13.800 -org.eclipse.platform:org.eclipse.core.runtime:3.19.0 -org.eclipse.platform:org.eclipse.equinox.app:1.5.0 -org.eclipse.platform:org.eclipse.equinox.common:3.13.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.8.0 -org.eclipse.platform:org.eclipse.equinox.registry:3.9.0 -org.eclipse.platform:org.eclipse.jface.text:3.16.400 -org.eclipse.platform:org.eclipse.jface:3.21.0 -org.eclipse.platform:org.eclipse.osgi:3.16.0 -org.eclipse.platform:org.eclipse.text:3.10.300 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.18.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.18.0.lockfile deleted file mode 100644 index 7135a6826d..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.18.0.lockfile +++ /dev/null @@ -1,22 +0,0 @@ -# Spotless formatter based on CDT version 10.1 (see https://www.eclipse.org/cdt/) -com.diffplug.spotless:spotless-eclipse-cdt:10.1.0 -com.diffplug.spotless:spotless-eclipse-base:3.4.2 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -com.ibm.icu:icu4j:64.2 -net.jcip:jcip-annotations:1.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.800 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.800 -org.eclipse.platform:org.eclipse.core.filebuffers:3.6.1100 -org.eclipse.platform:org.eclipse.core.filesystem:1.7.700 -org.eclipse.platform:org.eclipse.core.jobs:3.10.1000 -org.eclipse.platform:org.eclipse.core.resources:3.13.900 -org.eclipse.platform:org.eclipse.core.runtime:3.20.0 -org.eclipse.platform:org.eclipse.equinox.app:1.5.0 -org.eclipse.platform:org.eclipse.equinox.common:3.14.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.8.100 -org.eclipse.platform:org.eclipse.equinox.registry:3.10.0 -org.eclipse.platform:org.eclipse.jface.text:3.16.500 -org.eclipse.platform:org.eclipse.jface:3.22.0 -org.eclipse.platform:org.eclipse.osgi:3.16.100 -org.eclipse.platform:org.eclipse.text:3.10.400 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.19.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.19.0.lockfile deleted file mode 100644 index 7c508e64bd..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.19.0.lockfile +++ /dev/null @@ -1,22 +0,0 @@ -# Spotless formatter based on CDT version 10.2 (see https://www.eclipse.org/cdt/) -com.diffplug.spotless:spotless-eclipse-cdt:10.2.0 -com.diffplug.spotless:spotless-eclipse-base:3.4.2 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -com.ibm.icu:icu4j:67.1 -net.jcip:jcip-annotations:1.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.800 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.900 -org.eclipse.platform:org.eclipse.core.filebuffers:3.6.1100 -org.eclipse.platform:org.eclipse.core.filesystem:1.7.700 -org.eclipse.platform:org.eclipse.core.jobs:3.10.1100 -org.eclipse.platform:org.eclipse.core.resources:3.14.0 -org.eclipse.platform:org.eclipse.core.runtime:3.20.100 -org.eclipse.platform:org.eclipse.equinox.app:1.5.100 -org.eclipse.platform:org.eclipse.equinox.common:3.14.100 -org.eclipse.platform:org.eclipse.equinox.preferences:3.8.200 -org.eclipse.platform:org.eclipse.equinox.registry:3.10.100 -org.eclipse.platform:org.eclipse.jface.text:3.17.0 -org.eclipse.platform:org.eclipse.jface:3.22.100 -org.eclipse.platform:org.eclipse.osgi:3.16.200 -org.eclipse.platform:org.eclipse.text:3.11.0 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.20.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.20.0.lockfile deleted file mode 100644 index 6cdb507d00..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.20.0.lockfile +++ /dev/null @@ -1,22 +0,0 @@ -# Spotless formatter based on CDT version 10.3 (see https://www.eclipse.org/cdt/) -com.diffplug.spotless:spotless-eclipse-cdt:10.3.0 -com.diffplug.spotless:spotless-eclipse-base:3.5.2 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -com.ibm.icu:icu4j:67.1 -net.jcip:jcip-annotations:1.0 -org.eclipse.platform:org.eclipse.core.commands:3.10.0 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.1000 -org.eclipse.platform:org.eclipse.core.filebuffers:3.7.0 -org.eclipse.platform:org.eclipse.core.filesystem:1.9.0 -org.eclipse.platform:org.eclipse.core.jobs:3.11.0 -org.eclipse.platform:org.eclipse.core.resources:3.15.0 -org.eclipse.platform:org.eclipse.core.runtime:3.22.0 -org.eclipse.platform:org.eclipse.equinox.app:1.5.100 -org.eclipse.platform:org.eclipse.equinox.common:3.15.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.8.200 -org.eclipse.platform:org.eclipse.equinox.registry:3.10.200 -org.eclipse.platform:org.eclipse.jface.text:3.18.0 -org.eclipse.platform:org.eclipse.jface:3.22.200 -org.eclipse.platform:org.eclipse.osgi:3.16.300 -org.eclipse.platform:org.eclipse.text:3.12.0 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.21.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.21.0.lockfile deleted file mode 100644 index 4acd5a98ca..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.21.0.lockfile +++ /dev/null @@ -1,22 +0,0 @@ -# Spotless formatter based on CDT version 10.4 (see https://www.eclipse.org/cdt/) -com.diffplug.spotless:spotless-eclipse-cdt:10.4.0 -com.diffplug.spotless:spotless-eclipse-base:3.5.2 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -com.ibm.icu:icu4j:67.1 -net.jcip:jcip-annotations:1.0 -org.eclipse.platform:org.eclipse.core.commands:3.10.100 -org.eclipse.platform:org.eclipse.core.contenttype:3.8.0 -org.eclipse.platform:org.eclipse.core.filebuffers:3.7.0 -org.eclipse.platform:org.eclipse.core.filesystem:1.9.100 -org.eclipse.platform:org.eclipse.core.jobs:3.12.0 -org.eclipse.platform:org.eclipse.core.resources:3.15.100 -org.eclipse.platform:org.eclipse.core.runtime:3.23.0 -org.eclipse.platform:org.eclipse.equinox.app:1.6.0 -org.eclipse.platform:org.eclipse.equinox.common:3.15.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.9.0 -org.eclipse.platform:org.eclipse.equinox.registry:3.11.0 -org.eclipse.platform:org.eclipse.jface.text:3.18.100 -org.eclipse.platform:org.eclipse.jface:3.23.0 -org.eclipse.platform:org.eclipse.osgi:3.17.0 -org.eclipse.platform:org.eclipse.text:3.12.0 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.7.3a.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.7.3a.lockfile deleted file mode 100644 index 92a0d32cc7..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_cdt_formatter/v4.7.3a.lockfile +++ /dev/null @@ -1,21 +0,0 @@ -# Spotless formatter based on CDT version 9.4.3 (see https://www.eclipse.org/cdt/) -com.diffplug.spotless:spotless-eclipse-cdt:9.4.5 -com.diffplug.spotless:spotless-eclipse-base:3.1.1 -com.google.code.findbugs:annotations:3.0.0 -com.google.code.findbugs:jsr305:3.0.0 -com.ibm.icu:icu4j:61.1 -org.eclipse.platform:org.eclipse.core.commands:3.9.100 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.0 -org.eclipse.platform:org.eclipse.core.filebuffers:3.6.200 -org.eclipse.platform:org.eclipse.core.filesystem:1.7.100 -org.eclipse.platform:org.eclipse.core.jobs:3.10.0 -org.eclipse.platform:org.eclipse.core.resources:3.13.0 -org.eclipse.platform:org.eclipse.core.runtime:3.14.0 -org.eclipse.platform:org.eclipse.equinox.app:1.3.500 -org.eclipse.platform:org.eclipse.equinox.common:3.10.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.100 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.0 -org.eclipse.platform:org.eclipse.jface.text:3.13.0 -org.eclipse.platform:org.eclipse.jface:3.14.0 -org.eclipse.platform:org.eclipse.osgi:3.13.0 -org.eclipse.platform:org.eclipse.text:3.6.300 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v2.3.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v2.3.0.lockfile deleted file mode 100644 index 878ddbe76a..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v2.3.0.lockfile +++ /dev/null @@ -1,5 +0,0 @@ -# Spotless formatter based on Groovy-Eclipse version 2.3.0 (see https://github.com/groovy/groovy-eclipse/wiki) -# -# This version is deprecated since the new version system is based on the Eclipse versioning. -# Use the corresponding Eclipse version 4.6.3 instead. -com.diffplug.spotless:spotless-ext-greclipse:2.3.0 \ No newline at end of file diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.10.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.10.0.lockfile deleted file mode 100644 index 33f672b306..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.10.0.lockfile +++ /dev/null @@ -1,18 +0,0 @@ -# Spotless formatter based on Groovy-Eclipse version 3.0.0 (see https://github.com/groovy/groovy-eclipse/releases) -com.diffplug.spotless:spotless-eclipse-groovy:3.2.0 -com.diffplug.spotless:spotless-eclipse-base:3.1.1 -com.google.code.findbugs:annotations:3.0.0 -com.google.code.findbugs:jsr305:3.0.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.300 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.300 -org.eclipse.platform:org.eclipse.core.jobs:3.10.300 -org.eclipse.platform:org.eclipse.core.resources:3.13.300 -org.eclipse.platform:org.eclipse.core.runtime:3.15.200 -org.eclipse.platform:org.eclipse.equinox.app:1.4.100 -org.eclipse.platform:org.eclipse.equinox.common:3.10.300 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.300 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.300 -org.eclipse.platform:org.eclipse.jface.text:3.15.100 -org.eclipse.platform:org.eclipse.jface:3.15.100 -org.eclipse.platform:org.eclipse.osgi:3.13.300 -org.eclipse.platform:org.eclipse.text:3.8.100 \ No newline at end of file diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.12.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.12.0.lockfile deleted file mode 100644 index a60e5e8fb0..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.12.0.lockfile +++ /dev/null @@ -1,18 +0,0 @@ -# Spotless formatter based on Groovy-Eclipse version 3.4.0 (see https://github.com/groovy/groovy-eclipse/releases) -com.diffplug.spotless:spotless-eclipse-groovy:3.4.0 -com.diffplug.spotless:spotless-eclipse-base:3.2.1 -com.google.code.findbugs:annotations:3.0.0 -com.google.code.findbugs:jsr305:3.0.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.400 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.300 -org.eclipse.platform:org.eclipse.core.jobs:3.10.400 -org.eclipse.platform:org.eclipse.core.resources:3.13.400 -org.eclipse.platform:org.eclipse.core.runtime:3.15.300 -org.eclipse.platform:org.eclipse.equinox.app:1.4.200 -org.eclipse.platform:org.eclipse.equinox.common:3.10.400 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.400 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.400 -org.eclipse.platform:org.eclipse.jface.text:3.15.200 -org.eclipse.platform:org.eclipse.jface:3.16.0 -org.eclipse.platform:org.eclipse.osgi:3.14.0 -org.eclipse.platform:org.eclipse.text:3.8.200 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.13.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.13.0.lockfile deleted file mode 100644 index aede5b0a76..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.13.0.lockfile +++ /dev/null @@ -1,18 +0,0 @@ -# Spotless formatter based on Groovy-Eclipse version 3.5.0 (see https://github.com/groovy/groovy-eclipse/releases) -com.diffplug.spotless:spotless-eclipse-groovy:3.5.0 -com.diffplug.spotless:spotless-eclipse-base:3.2.1 -com.google.code.findbugs:annotations:3.0.0 -com.google.code.findbugs:jsr305:3.0.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.500 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.400 -org.eclipse.platform:org.eclipse.core.jobs:3.10.500 -org.eclipse.platform:org.eclipse.core.resources:3.13.500 -org.eclipse.platform:org.eclipse.core.runtime:3.16.0 -org.eclipse.platform:org.eclipse.equinox.app:1.4.300 -org.eclipse.platform:org.eclipse.equinox.common:3.10.500 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.500 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.500 -org.eclipse.platform:org.eclipse.jface.text:3.15.300 -org.eclipse.platform:org.eclipse.jface:3.17.0 -org.eclipse.platform:org.eclipse.osgi:3.15.0 -org.eclipse.platform:org.eclipse.text:3.9.0 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.14.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.14.0.lockfile deleted file mode 100644 index bca432c337..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.14.0.lockfile +++ /dev/null @@ -1,18 +0,0 @@ -# Spotless formatter based on Groovy-Eclipse version 3.6.0 (see https://github.com/groovy/groovy-eclipse/releases) -com.diffplug.spotless:spotless-eclipse-groovy:3.6.0 -com.diffplug.spotless:spotless-eclipse-base:3.3.0 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -org.eclipse.platform:org.eclipse.core.commands:3.9.600 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.500 -org.eclipse.platform:org.eclipse.core.jobs:3.10.600 -org.eclipse.platform:org.eclipse.core.resources:3.13.600 -org.eclipse.platform:org.eclipse.core.runtime:3.17.0 -org.eclipse.platform:org.eclipse.equinox.app:1.4.300 -org.eclipse.platform:org.eclipse.equinox.common:3.10.600 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.600 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.600 -org.eclipse.platform:org.eclipse.jface.text:3.16.100 -org.eclipse.platform:org.eclipse.jface:3.18.0 -org.eclipse.platform:org.eclipse.osgi:3.15.100 -org.eclipse.platform:org.eclipse.text:3.10.0 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.15.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.15.0.lockfile deleted file mode 100644 index fc91fae066..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.15.0.lockfile +++ /dev/null @@ -1,18 +0,0 @@ -# Spotless formatter based on Groovy-Eclipse version 3.7.0 (see https://github.com/groovy/groovy-eclipse/releases) -com.diffplug.spotless:spotless-eclipse-groovy:3.7.0 -com.diffplug.spotless:spotless-eclipse-base:3.3.0 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -org.eclipse.platform:org.eclipse.core.commands:3.9.700 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.600 -org.eclipse.platform:org.eclipse.core.jobs:3.10.700 -org.eclipse.platform:org.eclipse.core.resources:3.13.700 -org.eclipse.platform:org.eclipse.core.runtime:3.17.100 -org.eclipse.platform:org.eclipse.equinox.app:1.4.400 -org.eclipse.platform:org.eclipse.equinox.common:3.11.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.700 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.700 -org.eclipse.platform:org.eclipse.jface.text:3.16.200 -org.eclipse.platform:org.eclipse.jface:3.19.0 -org.eclipse.platform:org.eclipse.osgi:3.15.200 -org.eclipse.platform:org.eclipse.text:3.10.100 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.16.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.16.0.lockfile deleted file mode 100644 index 2c5f4ccd03..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.16.0.lockfile +++ /dev/null @@ -1,18 +0,0 @@ -# Spotless formatter based on Groovy-Eclipse version 3.8.0 (see https://github.com/groovy/groovy-eclipse/releases) -com.diffplug.spotless:spotless-eclipse-groovy:3.8.0 -com.diffplug.spotless:spotless-eclipse-base:3.3.0 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -org.eclipse.platform:org.eclipse.core.commands:3.9.700 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.700 -org.eclipse.platform:org.eclipse.core.jobs:3.10.800 -org.eclipse.platform:org.eclipse.core.resources:3.13.700 -org.eclipse.platform:org.eclipse.core.runtime:3.18.0 -org.eclipse.platform:org.eclipse.equinox.app:1.4.500 -org.eclipse.platform:org.eclipse.equinox.common:3.12.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.8.0 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.800 -org.eclipse.platform:org.eclipse.jface.text:3.16.300 -org.eclipse.platform:org.eclipse.jface:3.20.0 -org.eclipse.platform:org.eclipse.osgi:3.15.300 -org.eclipse.platform:org.eclipse.text:3.10.200 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.17.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.17.0.lockfile deleted file mode 100644 index 5242d0da8c..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.17.0.lockfile +++ /dev/null @@ -1,19 +0,0 @@ -# Spotless formatter based on Groovy-Eclipse version 3.9.0 (see https://github.com/groovy/groovy-eclipse/releases) -com.diffplug.spotless:spotless-eclipse-groovy:3.9.0 -com.diffplug.spotless:spotless-eclipse-base:3.4.1 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -net.jcip:jcip-annotations:1.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.700 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.800 -org.eclipse.platform:org.eclipse.core.jobs:3.10.800 -org.eclipse.platform:org.eclipse.core.resources:3.13.800 -org.eclipse.platform:org.eclipse.core.runtime:3.19.0 -org.eclipse.platform:org.eclipse.equinox.app:1.5.0 -org.eclipse.platform:org.eclipse.equinox.common:3.13.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.8.0 -org.eclipse.platform:org.eclipse.equinox.registry:3.9.0 -org.eclipse.platform:org.eclipse.jface.text:3.16.400 -org.eclipse.platform:org.eclipse.jface:3.21.0 -org.eclipse.platform:org.eclipse.osgi:3.16.0 -org.eclipse.platform:org.eclipse.text:3.10.300 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.18.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.18.0.lockfile deleted file mode 100644 index 6bfec53756..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.18.0.lockfile +++ /dev/null @@ -1,19 +0,0 @@ -# Spotless formatter based on Groovy-Eclipse version 4.0.0 (see https://github.com/groovy/groovy-eclipse/releases) -com.diffplug.spotless:spotless-eclipse-groovy:4.0.0 -com.diffplug.spotless:spotless-eclipse-base:3.4.2 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -net.jcip:jcip-annotations:1.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.800 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.800 -org.eclipse.platform:org.eclipse.core.jobs:3.10.1000 -org.eclipse.platform:org.eclipse.core.resources:3.13.900 -org.eclipse.platform:org.eclipse.core.runtime:3.20.0 -org.eclipse.platform:org.eclipse.equinox.app:1.5.0 -org.eclipse.platform:org.eclipse.equinox.common:3.14.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.8.100 -org.eclipse.platform:org.eclipse.equinox.registry:3.10.0 -org.eclipse.platform:org.eclipse.jface.text:3.16.500 -org.eclipse.platform:org.eclipse.jface:3.22.0 -org.eclipse.platform:org.eclipse.osgi:3.16.100 -org.eclipse.platform:org.eclipse.text:3.10.400 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.19.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.19.0.lockfile deleted file mode 100644 index 880d55ac8d..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.19.0.lockfile +++ /dev/null @@ -1,19 +0,0 @@ -# Spotless formatter based on Groovy-Eclipse version 4.1.0 (see https://github.com/groovy/groovy-eclipse/releases) -com.diffplug.spotless:spotless-eclipse-groovy:4.1.0 -com.diffplug.spotless:spotless-eclipse-base:3.4.2 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -net.jcip:jcip-annotations:1.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.800 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.900 -org.eclipse.platform:org.eclipse.core.jobs:3.10.1100 -org.eclipse.platform:org.eclipse.core.resources:3.14.0 -org.eclipse.platform:org.eclipse.core.runtime:3.20.100 -org.eclipse.platform:org.eclipse.equinox.app:1.5.100 -org.eclipse.platform:org.eclipse.equinox.common:3.14.100 -org.eclipse.platform:org.eclipse.equinox.preferences:3.8.200 -org.eclipse.platform:org.eclipse.equinox.registry:3.10.100 -org.eclipse.platform:org.eclipse.jface.text:3.17.0 -org.eclipse.platform:org.eclipse.jface:3.22.100 -org.eclipse.platform:org.eclipse.osgi:3.16.200 -org.eclipse.platform:org.eclipse.text:3.11.0 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.20.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.20.0.lockfile deleted file mode 100644 index 2836c05982..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.20.0.lockfile +++ /dev/null @@ -1,20 +0,0 @@ -# Spotless formatter based on Groovy-Eclipse version 4.2.0 (see https://github.com/groovy/groovy-eclipse/releases) -com.diffplug.spotless:spotless-eclipse-groovy:4.2.0 -com.diffplug.spotless:spotless-eclipse-base:3.5.2 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -net.jcip:jcip-annotations:1.0 -org.eclipse.platform:org.eclipse.core.commands:3.10.0 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.1000 -org.eclipse.platform:org.eclipse.core.filesystem:1.9.0 -org.eclipse.platform:org.eclipse.core.jobs:3.11.0 -org.eclipse.platform:org.eclipse.core.resources:3.15.0 -org.eclipse.platform:org.eclipse.core.runtime:3.22.0 -org.eclipse.platform:org.eclipse.equinox.app:1.5.100 -org.eclipse.platform:org.eclipse.equinox.common:3.15.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.8.200 -org.eclipse.platform:org.eclipse.equinox.registry:3.10.200 -org.eclipse.platform:org.eclipse.jface.text:3.18.0 -org.eclipse.platform:org.eclipse.jface:3.22.200 -org.eclipse.platform:org.eclipse.osgi:3.16.300 -org.eclipse.platform:org.eclipse.text:3.12.0 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.21.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.21.0.lockfile deleted file mode 100644 index 38376d1b32..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.21.0.lockfile +++ /dev/null @@ -1,20 +0,0 @@ -# Spotless formatter based on Groovy-Eclipse version 4.3.0 (see https://github.com/groovy/groovy-eclipse/releases) -com.diffplug.spotless:spotless-eclipse-groovy:4.3.0 -com.diffplug.spotless:spotless-eclipse-base:3.5.2 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -net.jcip:jcip-annotations:1.0 -org.eclipse.platform:org.eclipse.core.commands:3.10.100 -org.eclipse.platform:org.eclipse.core.contenttype:3.8.0 -org.eclipse.platform:org.eclipse.core.filesystem:1.9.100 -org.eclipse.platform:org.eclipse.core.jobs:3.12.0 -org.eclipse.platform:org.eclipse.core.resources:3.15.100 -org.eclipse.platform:org.eclipse.core.runtime:3.23.0 -org.eclipse.platform:org.eclipse.equinox.app:1.6.0 -org.eclipse.platform:org.eclipse.equinox.common:3.15.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.9.0 -org.eclipse.platform:org.eclipse.equinox.registry:3.11.0 -org.eclipse.platform:org.eclipse.jface.text:3.18.100 -org.eclipse.platform:org.eclipse.jface:3.23.0 -org.eclipse.platform:org.eclipse.osgi:3.17.0 -org.eclipse.platform:org.eclipse.text:3.12.0 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.6.3.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.6.3.lockfile deleted file mode 100644 index a6299ec57d..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.6.3.lockfile +++ /dev/null @@ -1,2 +0,0 @@ -# Spotless formatter based on Groovy-Eclipse version 2.3.0 (see https://github.com/groovy/groovy-eclipse/wiki) -com.diffplug.spotless:spotless-ext-greclipse:2.3.0 \ No newline at end of file diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.8.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.8.0.lockfile deleted file mode 100644 index bb95c71104..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.8.0.lockfile +++ /dev/null @@ -1,18 +0,0 @@ -# Spotless formatter based on Groovy-Eclipse version 2.9.2 (see https://github.com/groovy/groovy-eclipse/releases) -com.diffplug.spotless:spotless-eclipse-groovy:2.9.2 -com.diffplug.spotless:spotless-eclipse-base:3.1.1 -com.google.code.findbugs:annotations:3.0.0 -com.google.code.findbugs:jsr305:3.0.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.100 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.0 -org.eclipse.platform:org.eclipse.core.jobs:3.10.0 -org.eclipse.platform:org.eclipse.core.resources:3.13.0 -org.eclipse.platform:org.eclipse.core.runtime:3.14.0 -org.eclipse.platform:org.eclipse.equinox.app:1.3.500 -org.eclipse.platform:org.eclipse.equinox.common:3.10.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.100 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.0 -org.eclipse.platform:org.eclipse.jface.text:3.13.0 -org.eclipse.platform:org.eclipse.jface:3.14.0 -org.eclipse.platform:org.eclipse.osgi:3.13.0 -org.eclipse.platform:org.eclipse.text:3.6.300 \ No newline at end of file diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.8.1.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.8.1.lockfile deleted file mode 100644 index 9198eaccb9..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter/v4.8.1.lockfile +++ /dev/null @@ -1,18 +0,0 @@ -# Spotless formatter based on Groovy-Eclipse version 3.0.0 (see https://github.com/groovy/groovy-eclipse/releases) -com.diffplug.spotless:spotless-eclipse-groovy:3.0.1 -com.diffplug.spotless:spotless-eclipse-base:3.1.1 -com.google.code.findbugs:annotations:3.0.0 -com.google.code.findbugs:jsr305:3.0.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.100 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.0 -org.eclipse.platform:org.eclipse.core.jobs:3.10.0 -org.eclipse.platform:org.eclipse.core.resources:3.13.0 -org.eclipse.platform:org.eclipse.core.runtime:3.14.0 -org.eclipse.platform:org.eclipse.equinox.app:1.3.500 -org.eclipse.platform:org.eclipse.equinox.common:3.10.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.100 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.0 -org.eclipse.platform:org.eclipse.jface.text:3.13.0 -org.eclipse.platform:org.eclipse.jface:3.14.0 -org.eclipse.platform:org.eclipse.osgi:3.13.0 -org.eclipse.platform:org.eclipse.text:3.6.300 \ No newline at end of file diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.10.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.10.0.lockfile deleted file mode 100644 index 42d81d7324..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.10.0.lockfile +++ /dev/null @@ -1,18 +0,0 @@ -# Spotless formatter based on JDT version 4.10.0 (see https://projects.eclipse.org/projects/eclipse.jdt) -# Compare tag in M2 pom with https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/tag/?h=R4_10 to determine core version. -com.diffplug.spotless:spotless-eclipse-jdt:4.8.0 -com.diffplug.spotless:spotless-eclipse-base:3.1.1 -com.google.code.findbugs:annotations:3.0.0 -com.google.code.findbugs:jsr305:3.0.0 -org.eclipse.jdt:org.eclipse.jdt.core:3.16.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.100 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.0 -org.eclipse.platform:org.eclipse.core.jobs:3.10.0 -org.eclipse.platform:org.eclipse.core.resources:3.13.0 -org.eclipse.platform:org.eclipse.core.runtime:3.14.0 -org.eclipse.platform:org.eclipse.equinox.app:1.3.500 -org.eclipse.platform:org.eclipse.equinox.common:3.10.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.100 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.0 -org.eclipse.platform:org.eclipse.osgi:3.13.0 -org.eclipse.platform:org.eclipse.text:3.6.300 \ No newline at end of file diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.11.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.11.0.lockfile deleted file mode 100644 index 3bbf59db23..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.11.0.lockfile +++ /dev/null @@ -1,19 +0,0 @@ -# Spotless formatter based on JDT version 4.11.0 (see https://projects.eclipse.org/projects/eclipse.jdt) -# Compare tag in M2 pom with https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/tag/?h=R4_11 to determine core version. -com.diffplug.spotless:spotless-eclipse-jdt:4.8.0 -com.diffplug.spotless:spotless-eclipse-base:3.1.1 -com.google.code.findbugs:annotations:3.0.0 -com.google.code.findbugs:jsr305:3.0.0 -org.eclipse.jdt:org.eclipse.jdt.core:3.17.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.100 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.0 -org.eclipse.platform:org.eclipse.core.jobs:3.10.0 -org.eclipse.platform:org.eclipse.core.resources:3.13.0 -org.eclipse.platform:org.eclipse.core.runtime:3.14.0 -org.eclipse.platform:org.eclipse.equinox.app:1.3.500 -org.eclipse.platform:org.eclipse.equinox.common:3.10.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.100 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.0 -org.eclipse.platform:org.eclipse.osgi:3.13.0 -org.eclipse.platform:org.eclipse.text:3.6.300 - diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.12.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.12.0.lockfile deleted file mode 100644 index c9bfc9a57d..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.12.0.lockfile +++ /dev/null @@ -1,18 +0,0 @@ -# Spotless formatter based on JDT version 4.12.0 (see https://projects.eclipse.org/projects/eclipse.jdt) -# Compare tag in M2 pom with https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/tag/?h=R4_12 to determine core version. -com.diffplug.spotless:spotless-eclipse-jdt:4.8.0 -com.diffplug.spotless:spotless-eclipse-base:3.2.1 -com.google.code.findbugs:annotations:3.0.0 -com.google.code.findbugs:jsr305:3.0.0 -org.eclipse.jdt:org.eclipse.jdt.core:3.18.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.400 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.300 -org.eclipse.platform:org.eclipse.core.jobs:3.10.400 -org.eclipse.platform:org.eclipse.core.resources:3.13.400 -org.eclipse.platform:org.eclipse.core.runtime:3.15.300 -org.eclipse.platform:org.eclipse.equinox.app:1.4.200 -org.eclipse.platform:org.eclipse.equinox.common:3.10.400 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.400 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.400 -org.eclipse.platform:org.eclipse.osgi:3.14.0 -org.eclipse.platform:org.eclipse.text:3.8.200 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.13.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.13.0.lockfile deleted file mode 100644 index 4ee0adb3cf..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.13.0.lockfile +++ /dev/null @@ -1,18 +0,0 @@ -# Spotless formatter based on JDT version 4.13.0 (see https://projects.eclipse.org/projects/eclipse.jdt) -# Compare tag in M2 pom with https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/tag/?h=R4_13 to determine core version. -com.diffplug.spotless:spotless-eclipse-jdt:4.8.0 -com.diffplug.spotless:spotless-eclipse-base:3.2.1 -com.google.code.findbugs:annotations:3.0.0 -com.google.code.findbugs:jsr305:3.0.0 -org.eclipse.jdt:org.eclipse.jdt.core:3.19.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.500 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.400 -org.eclipse.platform:org.eclipse.core.jobs:3.10.500 -org.eclipse.platform:org.eclipse.core.resources:3.13.500 -org.eclipse.platform:org.eclipse.core.runtime:3.16.0 -org.eclipse.platform:org.eclipse.equinox.app:1.4.300 -org.eclipse.platform:org.eclipse.equinox.common:3.10.500 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.500 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.500 -org.eclipse.platform:org.eclipse.osgi:3.15.0 -org.eclipse.platform:org.eclipse.text:3.9.0 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.14.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.14.0.lockfile deleted file mode 100644 index b42352633a..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.14.0.lockfile +++ /dev/null @@ -1,19 +0,0 @@ -# Spotless formatter based on JDT version 4.14.0 (see https://projects.eclipse.org/projects/eclipse.jdt) -# Compare tag in M2 pom with https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/log/?h=R4_14 to determine core version. -com.diffplug.spotless:spotless-eclipse-jdt:4.8.0 -com.diffplug.spotless:spotless-eclipse-base:3.3.0 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -net.jcip:jcip-annotations:1.0 -org.eclipse.jdt:org.eclipse.jdt.core:3.20.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.600 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.500 -org.eclipse.platform:org.eclipse.core.jobs:3.10.600 -org.eclipse.platform:org.eclipse.core.resources:3.13.600 -org.eclipse.platform:org.eclipse.core.runtime:3.17.0 -org.eclipse.platform:org.eclipse.equinox.app:1.4.300 -org.eclipse.platform:org.eclipse.equinox.common:3.10.600 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.600 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.600 -org.eclipse.platform:org.eclipse.osgi:3.15.100 -org.eclipse.platform:org.eclipse.text:3.10.0 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.15.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.15.0.lockfile deleted file mode 100644 index ee21631962..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.15.0.lockfile +++ /dev/null @@ -1,19 +0,0 @@ -# Spotless formatter based on JDT version 4.15.0 (see https://projects.eclipse.org/projects/eclipse.jdt) -# Compare tag in M2 pom with https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/log/?h=R4_15 to determine core version. -com.diffplug.spotless:spotless-eclipse-jdt:4.8.0 -com.diffplug.spotless:spotless-eclipse-base:3.3.0 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -net.jcip:jcip-annotations:1.0 -org.eclipse.jdt:org.eclipse.jdt.core:3.21.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.700 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.600 -org.eclipse.platform:org.eclipse.core.jobs:3.10.700 -org.eclipse.platform:org.eclipse.core.resources:3.13.600 -org.eclipse.platform:org.eclipse.core.runtime:3.17.100 -org.eclipse.platform:org.eclipse.equinox.app:1.4.400 -org.eclipse.platform:org.eclipse.equinox.common:3.11.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.700 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.700 -org.eclipse.platform:org.eclipse.osgi:3.15.200 -org.eclipse.platform:org.eclipse.text:3.10.100 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.16.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.16.0.lockfile deleted file mode 100644 index 7ff27b440a..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.16.0.lockfile +++ /dev/null @@ -1,19 +0,0 @@ -# Spotless formatter based on JDT version 4.16.0 (see https://projects.eclipse.org/projects/eclipse.jdt) -# Compare tag in M2 pom with https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/log/?h=R4_16 to determine core version. -com.diffplug.spotless:spotless-eclipse-jdt:4.8.0 -com.diffplug.spotless:spotless-eclipse-base:3.3.0 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -net.jcip:jcip-annotations:1.0 -org.eclipse.jdt:org.eclipse.jdt.core:3.22.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.700 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.700 -org.eclipse.platform:org.eclipse.core.jobs:3.10.800 -org.eclipse.platform:org.eclipse.core.resources:3.13.700 -org.eclipse.platform:org.eclipse.core.runtime:3.18.0 -org.eclipse.platform:org.eclipse.equinox.app:1.4.500 -org.eclipse.platform:org.eclipse.equinox.common:3.12.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.8.0 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.800 -org.eclipse.platform:org.eclipse.osgi:3.15.300 -org.eclipse.platform:org.eclipse.text:3.10.200 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.17.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.17.0.lockfile deleted file mode 100644 index 47cb5f10dd..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.17.0.lockfile +++ /dev/null @@ -1,19 +0,0 @@ -# Spotless formatter based on JDT version 4.17.0 (see https://projects.eclipse.org/projects/eclipse.jdt) -# Compare tag in M2 pom with https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/log/?h=R4_17 to determine core version. -com.diffplug.spotless:spotless-eclipse-jdt:4.8.0 -com.diffplug.spotless:spotless-eclipse-base:3.4.1 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -net.jcip:jcip-annotations:1.0 -org.eclipse.jdt:org.eclipse.jdt.core:3.23.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.700 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.800 -org.eclipse.platform:org.eclipse.core.jobs:3.10.800 -org.eclipse.platform:org.eclipse.core.resources:3.13.800 -org.eclipse.platform:org.eclipse.core.runtime:3.19.0 -org.eclipse.platform:org.eclipse.equinox.app:1.5.0 -org.eclipse.platform:org.eclipse.equinox.common:3.13.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.8.0 -org.eclipse.platform:org.eclipse.equinox.registry:3.9.0 -org.eclipse.platform:org.eclipse.osgi:3.16.0 -org.eclipse.platform:org.eclipse.text:3.10.300 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.18.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.18.0.lockfile deleted file mode 100644 index c5b2799c11..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.18.0.lockfile +++ /dev/null @@ -1,19 +0,0 @@ -# Spotless formatter based on JDT version 4.18.0 (see https://projects.eclipse.org/projects/eclipse.jdt) -# Compare tag in M2 pom with https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/log/?h=R4_18 to determine core version. -com.diffplug.spotless:spotless-eclipse-jdt:4.8.0 -com.diffplug.spotless:spotless-eclipse-base:3.4.2 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -net.jcip:jcip-annotations:1.0 -org.eclipse.jdt:org.eclipse.jdt.core:3.24.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.800 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.800 -org.eclipse.platform:org.eclipse.core.jobs:3.10.1000 -org.eclipse.platform:org.eclipse.core.resources:3.13.900 -org.eclipse.platform:org.eclipse.core.runtime:3.20.0 -org.eclipse.platform:org.eclipse.equinox.app:1.5.0 -org.eclipse.platform:org.eclipse.equinox.common:3.14.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.8.100 -org.eclipse.platform:org.eclipse.equinox.registry:3.10.0 -org.eclipse.platform:org.eclipse.osgi:3.16.100 -org.eclipse.platform:org.eclipse.text:3.10.400 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.19.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.19.0.lockfile deleted file mode 100644 index fcc96b8520..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.19.0.lockfile +++ /dev/null @@ -1,19 +0,0 @@ -# Spotless formatter based on JDT version 4.19.0 (see https://projects.eclipse.org/projects/eclipse.jdt) -# Compare tag in M2 pom with https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/log/?h=R4_19 to determine core version. -com.diffplug.spotless:spotless-eclipse-jdt:4.8.0 -com.diffplug.spotless:spotless-eclipse-base:3.4.2 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -net.jcip:jcip-annotations:1.0 -org.eclipse.jdt:org.eclipse.jdt.core:3.25.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.800 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.900 -org.eclipse.platform:org.eclipse.core.jobs:3.10.1100 -org.eclipse.platform:org.eclipse.core.resources:3.14.0 -org.eclipse.platform:org.eclipse.core.runtime:3.20.100 -org.eclipse.platform:org.eclipse.equinox.app:1.5.100 -org.eclipse.platform:org.eclipse.equinox.common:3.14.100 -org.eclipse.platform:org.eclipse.equinox.preferences:3.8.200 -org.eclipse.platform:org.eclipse.equinox.registry:3.10.100 -org.eclipse.platform:org.eclipse.osgi:3.16.200 -org.eclipse.platform:org.eclipse.text:3.11.0 \ No newline at end of file diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.20.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.20.0.lockfile deleted file mode 100644 index b06a9bc617..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.20.0.lockfile +++ /dev/null @@ -1,20 +0,0 @@ -# Spotless formatter based on JDT version 4.20.0 (see https://projects.eclipse.org/projects/eclipse.jdt) -# Compare tag in M2 pom with https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/log/?h=R4_20 to determine core version. -com.diffplug.spotless:spotless-eclipse-jdt:4.8.1 -com.diffplug.spotless:spotless-eclipse-base:3.5.2 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -net.jcip:jcip-annotations:1.0 -org.eclipse.jdt:org.eclipse.jdt.core:3.26.0 -org.eclipse.platform:org.eclipse.core.commands:3.10.0 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.1000 -org.eclipse.platform:org.eclipse.core.filesystem:1.9.0 -org.eclipse.platform:org.eclipse.core.jobs:3.11.0 -org.eclipse.platform:org.eclipse.core.resources:3.15.0 -org.eclipse.platform:org.eclipse.core.runtime:3.22.0 -org.eclipse.platform:org.eclipse.equinox.app:1.5.100 -org.eclipse.platform:org.eclipse.equinox.common:3.15.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.8.200 -org.eclipse.platform:org.eclipse.equinox.registry:3.10.200 -org.eclipse.platform:org.eclipse.osgi:3.16.300 -org.eclipse.platform:org.eclipse.text:3.12.0 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.21.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.21.0.lockfile deleted file mode 100644 index 84a90489c9..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.21.0.lockfile +++ /dev/null @@ -1,20 +0,0 @@ -# Spotless formatter based on JDT version 4.21.0 (see https://projects.eclipse.org/projects/eclipse.jdt) -# Compare tag in M2 pom with https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/log/?h=R4_21 to determine core version. -com.diffplug.spotless:spotless-eclipse-jdt:4.8.1 -com.diffplug.spotless:spotless-eclipse-base:3.5.2 -com.github.spotbugs:spotbugs-annotations:4.0.2 -com.google.code.findbugs:jsr305:3.0.2 -net.jcip:jcip-annotations:1.0 -org.eclipse.jdt:org.eclipse.jdt.core:3.27.0 -org.eclipse.platform:org.eclipse.core.commands:3.10.100 -org.eclipse.platform:org.eclipse.core.contenttype:3.8.0 -org.eclipse.platform:org.eclipse.core.filesystem:1.9.100 -org.eclipse.platform:org.eclipse.core.jobs:3.12.0 -org.eclipse.platform:org.eclipse.core.resources:3.15.100 -org.eclipse.platform:org.eclipse.core.runtime:3.23.0 -org.eclipse.platform:org.eclipse.equinox.app:1.6.0 -org.eclipse.platform:org.eclipse.equinox.common:3.15.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.9.0 -org.eclipse.platform:org.eclipse.equinox.registry:3.11.0 -org.eclipse.platform:org.eclipse.osgi:3.17.0 -org.eclipse.platform:org.eclipse.text:3.12.0 diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.6.1.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.6.1.lockfile deleted file mode 100644 index fd408e7ecc..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.6.1.lockfile +++ /dev/null @@ -1,2 +0,0 @@ -# Spotless formatter based on JDT version 4.6.1 (see https://projects.eclipse.org/projects/eclipse.jdt) -com.diffplug.spotless:spotless-ext-eclipse-jdt:4.6.1 \ No newline at end of file diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.6.2.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.6.2.lockfile deleted file mode 100644 index a042b7e734..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.6.2.lockfile +++ /dev/null @@ -1,18 +0,0 @@ -# Spotless formatter based on JDT version 4.6.2 (see https://projects.eclipse.org/projects/eclipse.jdt) -# Compare tag in M2 pom with https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/log/?h=R4_6_maintenance to determine core version. -com.diffplug.spotless:spotless-eclipse-jdt:4.8.0 -com.diffplug.spotless:spotless-eclipse-base:3.1.1 -com.google.code.findbugs:annotations:3.0.0 -com.google.code.findbugs:jsr305:3.0.0 -org.eclipse.jdt:org.eclipse.jdt.core:3.12.2 -org.eclipse.platform:org.eclipse.core.commands:3.9.100 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.0 -org.eclipse.platform:org.eclipse.core.jobs:3.10.0 -org.eclipse.platform:org.eclipse.core.resources:3.13.0 -org.eclipse.platform:org.eclipse.core.runtime:3.14.0 -org.eclipse.platform:org.eclipse.equinox.app:1.3.500 -org.eclipse.platform:org.eclipse.equinox.common:3.10.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.100 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.0 -org.eclipse.platform:org.eclipse.osgi:3.13.0 -org.eclipse.platform:org.eclipse.text:3.6.300 \ No newline at end of file diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.6.3.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.6.3.lockfile deleted file mode 100644 index 714ff708c8..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.6.3.lockfile +++ /dev/null @@ -1,2 +0,0 @@ -# Spotless formatter based on JDT version 4.6.3 (see https://projects.eclipse.org/projects/eclipse.jdt) -com.diffplug.spotless:spotless-ext-eclipse-jdt:4.6.3 \ No newline at end of file diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.7.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.7.0.lockfile deleted file mode 100644 index 9e3eefbcb1..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.7.0.lockfile +++ /dev/null @@ -1,2 +0,0 @@ -# Spotless formatter based on JDT version 4.7 (see https://projects.eclipse.org/projects/eclipse.jdt) -com.diffplug.spotless:spotless-ext-eclipse-jdt:4.7.0 \ No newline at end of file diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.7.1.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.7.1.lockfile deleted file mode 100644 index 650c67e13b..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.7.1.lockfile +++ /dev/null @@ -1,2 +0,0 @@ -# Spotless formatter based on JDT version 4.7.1 (see https://projects.eclipse.org/projects/eclipse.jdt) -com.diffplug.spotless:spotless-ext-eclipse-jdt:4.7.1 \ No newline at end of file diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.7.2.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.7.2.lockfile deleted file mode 100644 index 35abd036aa..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.7.2.lockfile +++ /dev/null @@ -1,2 +0,0 @@ -# Spotless formatter based on JDT version 4.7.2 (see https://projects.eclipse.org/projects/eclipse.jdt) -com.diffplug.spotless:spotless-ext-eclipse-jdt:4.7.2 \ No newline at end of file diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.7.3a.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.7.3a.lockfile deleted file mode 100644 index a5e7aaa57b..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.7.3a.lockfile +++ /dev/null @@ -1,18 +0,0 @@ -# Spotless formatter based on JDT version 4.7.3a (see https://projects.eclipse.org/projects/eclipse.jdt) -# Compare tag in M2 pom with https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/log/?h=R4_7_maintenance to determine core version. -com.diffplug.spotless:spotless-eclipse-jdt:4.8.0 -com.diffplug.spotless:spotless-eclipse-base:3.1.1 -com.google.code.findbugs:annotations:3.0.0 -com.google.code.findbugs:jsr305:3.0.0 -org.eclipse.jdt:org.eclipse.jdt.core:3.13.101 -org.eclipse.platform:org.eclipse.core.commands:3.9.100 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.0 -org.eclipse.platform:org.eclipse.core.jobs:3.10.0 -org.eclipse.platform:org.eclipse.core.resources:3.13.0 -org.eclipse.platform:org.eclipse.core.runtime:3.14.0 -org.eclipse.platform:org.eclipse.equinox.app:1.3.500 -org.eclipse.platform:org.eclipse.equinox.common:3.10.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.100 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.0 -org.eclipse.platform:org.eclipse.osgi:3.13.0 -org.eclipse.platform:org.eclipse.text:3.6.300 \ No newline at end of file diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.8.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.8.0.lockfile deleted file mode 100644 index a9204f5a45..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.8.0.lockfile +++ /dev/null @@ -1,18 +0,0 @@ -# Spotless formatter based on JDT version 4.8.0 (see https://projects.eclipse.org/projects/eclipse.jdt) -# Compare tag in M2 pom with https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/tag/?h=R4_8 to determine core version. -com.diffplug.spotless:spotless-eclipse-jdt:4.8.0 -com.diffplug.spotless:spotless-eclipse-base:3.1.1 -com.google.code.findbugs:annotations:3.0.0 -com.google.code.findbugs:jsr305:3.0.0 -org.eclipse.jdt:org.eclipse.jdt.core:3.14.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.100 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.0 -org.eclipse.platform:org.eclipse.core.jobs:3.10.0 -org.eclipse.platform:org.eclipse.core.resources:3.13.0 -org.eclipse.platform:org.eclipse.core.runtime:3.14.0 -org.eclipse.platform:org.eclipse.equinox.app:1.3.500 -org.eclipse.platform:org.eclipse.equinox.common:3.10.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.100 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.0 -org.eclipse.platform:org.eclipse.osgi:3.13.0 -org.eclipse.platform:org.eclipse.text:3.6.300 \ No newline at end of file diff --git a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.9.0.lockfile b/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.9.0.lockfile deleted file mode 100644 index 5a19c69254..0000000000 --- a/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_jdt_formatter/v4.9.0.lockfile +++ /dev/null @@ -1,18 +0,0 @@ -# Spotless formatter based on JDT version 4.9.0 (see https://projects.eclipse.org/projects/eclipse.jdt) -# Compare tag in M2 pom with https://git.eclipse.org/c/jdt/eclipse.jdt.core.git/tag/?h=R4_9 to determine core version. -com.diffplug.spotless:spotless-eclipse-jdt:4.8.0 -com.diffplug.spotless:spotless-eclipse-base:3.1.1 -com.google.code.findbugs:annotations:3.0.0 -com.google.code.findbugs:jsr305:3.0.0 -org.eclipse.jdt:org.eclipse.jdt.core:3.15.0 -org.eclipse.platform:org.eclipse.core.commands:3.9.100 -org.eclipse.platform:org.eclipse.core.contenttype:3.7.0 -org.eclipse.platform:org.eclipse.core.jobs:3.10.0 -org.eclipse.platform:org.eclipse.core.resources:3.13.0 -org.eclipse.platform:org.eclipse.core.runtime:3.14.0 -org.eclipse.platform:org.eclipse.equinox.app:1.3.500 -org.eclipse.platform:org.eclipse.equinox.common:3.10.0 -org.eclipse.platform:org.eclipse.equinox.preferences:3.7.100 -org.eclipse.platform:org.eclipse.equinox.registry:3.8.0 -org.eclipse.platform:org.eclipse.osgi:3.13.0 -org.eclipse.platform:org.eclipse.text:3.6.300 \ No newline at end of file diff --git a/lib-extra/src/test/java/com/diffplug/spotless/extra/GitAttributesTest.java b/lib-extra/src/test/java/com/diffplug/spotless/extra/GitAttributesTest.java index ed657c365a..a7cde9e58f 100644 --- a/lib-extra/src/test/java/com/diffplug/spotless/extra/GitAttributesTest.java +++ b/lib-extra/src/test/java/com/diffplug/spotless/extra/GitAttributesTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,24 +26,19 @@ import org.eclipse.jgit.api.errors.GitAPIException; import org.junit.jupiter.api.Test; -import com.diffplug.common.base.Errors; import com.diffplug.common.base.StringPrinter; import com.diffplug.spotless.LineEnding; import com.diffplug.spotless.ResourceHarness; class GitAttributesTest extends ResourceHarness { private List testFiles(String prefix) { - try { - List result = new ArrayList<>(); - for (String path : TEST_PATHS) { - String prefixedPath = prefix + path; - setFile(prefixedPath).toContent(""); - result.add(newFile(prefixedPath)); - } - return result; - } catch (IOException e) { - throw Errors.asRuntime(e); + List result = new ArrayList<>(); + for (String path : TEST_PATHS) { + String prefixedPath = prefix + path; + setFile(prefixedPath).toContent(""); + result.add(newFile(prefixedPath)); } + return result; } private List testFiles() { @@ -53,7 +48,7 @@ private List testFiles() { private static final List TEST_PATHS = Arrays.asList("someFile", "subfolder/someFile", "MANIFEST.MF", "subfolder/MANIFEST.MF"); @Test - void cacheTest() throws IOException { + void cacheTest() { setFile(".gitattributes").toContent(StringPrinter.buildStringFromLines( "* eol=lf", "*.MF eol=crlf")); @@ -84,7 +79,7 @@ void cacheTest() throws IOException { } @Test - void policyTest() throws IOException { + void policyTest() { setFile(".gitattributes").toContent(StringPrinter.buildStringFromLines( "* eol=lf", "*.MF eol=crlf")); @@ -96,7 +91,7 @@ void policyTest() throws IOException { } @Test - void policyDefaultLineEndingTest() throws GitAPIException, IOException { + void policyDefaultLineEndingTest() throws GitAPIException { Git git = Git.init().setDirectory(rootFolder()).call(); git.close(); setFile(".git/config").toContent(StringPrinter.buildStringFromLines( diff --git a/lib-extra/src/test/java/com/diffplug/spotless/extra/cpp/EclipseCdtFormatterStepTest.java b/lib-extra/src/test/java/com/diffplug/spotless/extra/cpp/EclipseCdtFormatterStepTest.java index f39673cdc8..9ed9f2f227 100644 --- a/lib-extra/src/test/java/com/diffplug/spotless/extra/cpp/EclipseCdtFormatterStepTest.java +++ b/lib-extra/src/test/java/com/diffplug/spotless/extra/cpp/EclipseCdtFormatterStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,12 +20,10 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import com.diffplug.spotless.Jvm; import com.diffplug.spotless.TestProvisioner; -import com.diffplug.spotless.extra.eclipse.EclipseResourceHarness; +import com.diffplug.spotless.extra.eclipse.EquoResourceHarness; -class EclipseCdtFormatterStepTest extends EclipseResourceHarness { - private final static Jvm.Support JVM_SUPPORT = Jvm. support("Oldest Version").add(8, "4.11.0"); +class EclipseCdtFormatterStepTest extends EquoResourceHarness { private final static String INPUT = "#include ;\nint main(int argc, \nchar *argv[]) {}"; private final static String EXPECTED = "#include ;\nint main(int argc, char *argv[]) {\n}\n"; @@ -40,6 +38,6 @@ void formatWithVersion(String version) throws Exception { } private static Stream formatWithVersion() { - return Stream.of(JVM_SUPPORT.getRecommendedFormatterVersion(), EclipseCdtFormatterStep.defaultVersion()); + return Stream.of("10.6", "10.7", EclipseCdtFormatterStep.defaultVersion()); } } diff --git a/lib-extra/src/test/java/com/diffplug/spotless/extra/eclipse/EquoResourceHarness.java b/lib-extra/src/test/java/com/diffplug/spotless/extra/eclipse/EquoResourceHarness.java new file mode 100644 index 0000000000..8d5d955c02 --- /dev/null +++ b/lib-extra/src/test/java/com/diffplug/spotless/extra/eclipse/EquoResourceHarness.java @@ -0,0 +1,99 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.extra.eclipse; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.util.Arrays; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.LineEnding; +import com.diffplug.spotless.ResourceHarness; +import com.diffplug.spotless.extra.EquoBasedStepBuilder; + +/** + * Provides a common formatter test for all Spotless Eclipse Formatter steps + * provided by lib-extra. + *

+ * The external Spotless Eclipse Formatter Step implementations are responsible + * to test the functionality of the Spotless adaptations for the Eclipse formatter. + * This explicitly includes a check of various forms of valid and invalid input + * to cover all relevant execution paths during the code formatting and internal + * exception handling. + *

+ *

+ * The lib-extra users, like plugin-gradle and plugin-maven are responsible + * to test the correct provision of user settings to the generic + * Spotless Eclipse Formatter steps provided by lib-extra. + *

+ */ +public class EquoResourceHarness extends ResourceHarness { + private final EquoBasedStepBuilder stepBuilder; + private final String fileName; + private final String input; + private final String expected; + + /** + * Create harness to be used for several versions of the formatter step + * @param builder Eclipse Formatter step builder + * @param unformatted Simple unformatted input + * @param formatted Expected formatted output + */ + public EquoResourceHarness(EquoBasedStepBuilder builder, String unformatted, String formatted) { + this(builder, "someSourceFile", unformatted, formatted); + } + + /** + * Create harness to be used for several versions of the formatter step + * @param builder Eclipse Formatter step builder + * @param sourceFileName File name of the source file + * @param unformatted Simple unformatted input + * @param formatted Expected formatted output + */ + public EquoResourceHarness(EquoBasedStepBuilder builder, String sourceFileName, String unformatted, String formatted) { + stepBuilder = builder; + fileName = sourceFileName; + input = unformatted; + expected = formatted; + } + + /** + * Assert that formatting input results in expected output + * @param formatterVersion Formatter version + * @param settingsFiles Formatter settings + * @return Formatted string + */ + protected String assertFormatted(String formatterVersion, File... settingsFiles) throws Exception { + String output = format(formatterVersion, settingsFiles); + assertThat(output).isEqualTo(expected); + return output; + } + + /** + * Formatting input results and returns output + * @param formatterVersion Formatter version + * @param settingsFiles Formatter settings + * @return Formatted string + */ + protected String format(String formatterVersion, File... settingsFiles) throws Exception { + File inputFile = setFile(fileName).toContent(input); + stepBuilder.setVersion(formatterVersion); + stepBuilder.setPreferences(Arrays.asList(settingsFiles)); + FormatterStep step = stepBuilder.build(); + return LineEnding.toUnix(step.format(input, inputFile)); + } +} diff --git a/lib-extra/src/test/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStepSpecialCaseTest.java b/lib-extra/src/test/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStepSpecialCaseTest.java new file mode 100644 index 0000000000..0702881ef9 --- /dev/null +++ b/lib-extra/src/test/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStepSpecialCaseTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.extra.groovy; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.StepHarness; +import com.diffplug.spotless.TestProvisioner; + +public class GrEclipseFormatterStepSpecialCaseTest { + /** + * https://github.com/diffplug/spotless/issues/1657 + *

+ * broken: ${parm == null ? "" : "$parm"} + * works: ${parm == null ? "" : "parm"} + */ + @Test + public void issue_1657() { + Assertions.assertThrows(RuntimeException.class, () -> { + StepHarness.forStep(GrEclipseFormatterStep.createBuilder(TestProvisioner.mavenCentral()).build()) + .testResourceUnaffected("groovy/greclipse/format/SomeClass.test"); + }); + } + + @Test + public void issue_1657_fixed() { + StepHarness.forStep(GrEclipseFormatterStep.createBuilder(TestProvisioner.mavenCentral()).build()) + .testResourceUnaffected("groovy/greclipse/format/SomeClass.fixed"); + } +} diff --git a/lib-extra/src/test/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStepTest.java b/lib-extra/src/test/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStepTest.java index f1823e5f52..2bd68d0281 100644 --- a/lib-extra/src/test/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStepTest.java +++ b/lib-extra/src/test/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,12 +20,10 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import com.diffplug.spotless.Jvm; import com.diffplug.spotless.TestProvisioner; -import com.diffplug.spotless.extra.eclipse.EclipseResourceHarness; +import com.diffplug.spotless.extra.eclipse.EquoResourceHarness; -class GrEclipseFormatterStepTest extends EclipseResourceHarness { - private final static Jvm.Support JVM_SUPPORT = Jvm. support("Oldest Version").add(8, "2.3.0").add(11, "4.17.0"); +public class GrEclipseFormatterStepTest extends EquoResourceHarness { private final static String INPUT = "class F{ def m(){} }"; private final static String EXPECTED = "class F{\n\tdef m(){}\n}"; @@ -40,6 +38,6 @@ void formatWithVersion(String version) throws Exception { } private static Stream formatWithVersion() { - return Stream.of(JVM_SUPPORT.getRecommendedFormatterVersion(), GrEclipseFormatterStep.defaultVersion()); + return Stream.of("4.18", GrEclipseFormatterStep.defaultVersion()); } } diff --git a/lib-extra/src/test/java/com/diffplug/spotless/extra/java/EclipseJdtFormatterStepSpecialCaseTest.java b/lib-extra/src/test/java/com/diffplug/spotless/extra/java/EclipseJdtFormatterStepSpecialCaseTest.java new file mode 100644 index 0000000000..21ec889ed6 --- /dev/null +++ b/lib-extra/src/test/java/com/diffplug/spotless/extra/java/EclipseJdtFormatterStepSpecialCaseTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.extra.java; + +import java.io.File; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.StepHarness; +import com.diffplug.spotless.TestProvisioner; +import com.diffplug.spotless.extra.EquoBasedStepBuilder; + +public class EclipseJdtFormatterStepSpecialCaseTest { + /** https://github.com/diffplug/spotless/issues/1638 */ + @Test + public void issue_1638() { + ClassLoader classLoader = getClass().getClassLoader(); + File file = new File(classLoader.getResource("eclipse_formatter_issue_1638.xml").getFile()); + EquoBasedStepBuilder builder = EclipseJdtFormatterStep.createBuilder(TestProvisioner.mavenCentral()); + builder.setPreferences(List.of(file)); + StepHarness.forStep(builder.build()) + .testResource("java/eclipse/AbstractType.test", "java/eclipse/AbstractType.clean"); + } +} diff --git a/lib-extra/src/test/java/com/diffplug/spotless/extra/java/EclipseJdtFormatterStepTest.java b/lib-extra/src/test/java/com/diffplug/spotless/extra/java/EclipseJdtFormatterStepTest.java index 3e5e1853c7..91bc8740ab 100644 --- a/lib-extra/src/test/java/com/diffplug/spotless/extra/java/EclipseJdtFormatterStepTest.java +++ b/lib-extra/src/test/java/com/diffplug/spotless/extra/java/EclipseJdtFormatterStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,29 +15,23 @@ */ package com.diffplug.spotless.extra.java; -import static org.junit.jupiter.api.condition.JRE.JAVA_11; - import java.io.File; import java.util.stream.Stream; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledForJreRange; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import com.diffplug.spotless.Jvm; import com.diffplug.spotless.TestProvisioner; -import com.diffplug.spotless.extra.EclipseBasedStepBuilder; -import com.diffplug.spotless.extra.eclipse.EclipseResourceHarness; +import com.diffplug.spotless.extra.EquoBasedStepBuilder; +import com.diffplug.spotless.extra.eclipse.EquoResourceHarness; -class EclipseJdtFormatterStepTest extends EclipseResourceHarness { - private final static String NON_SEMANTIC_ECLIPSE_VERSION = "4.7.3a"; - private final static Jvm.Support JVM_SUPPORT = Jvm. support("Oldest Version").add(8, "4.6.1").add(11, "4.20.0"); +class EclipseJdtFormatterStepTest extends EquoResourceHarness { private final static String INPUT = "package p; class C{}"; private final static String EXPECTED = "package p;\nclass C {\n}"; - private static EclipseBasedStepBuilder createBuilder() { + private static EquoBasedStepBuilder createBuilder() { return EclipseJdtFormatterStep.createBuilder(TestProvisioner.mavenCentral()); } @@ -52,21 +46,20 @@ void formatWithVersion(String version) throws Exception { } private static Stream formatWithVersion() { - return Stream.of(NON_SEMANTIC_ECLIPSE_VERSION, JVM_SUPPORT.getRecommendedFormatterVersion(), EclipseJdtFormatterStep.defaultVersion()); + return Stream.of("4.9", EclipseJdtFormatterStep.defaultVersion()); } /** New format interface requires source file information to distinguish module-info from compilation unit */ @Nested - @EnabledForJreRange(min = JAVA_11) - class NewFormatInterface extends EclipseResourceHarness { - public NewFormatInterface() throws Exception { + class NewFormatInterface extends EquoResourceHarness { + public NewFormatInterface() { super(createBuilder(), "module-info.java", getTestResource("java/eclipse/ModuleInfoUnformatted.test"), getTestResource("java/eclipse/ModuleInfoFormatted.test")); } @Test void formatModuleInfo() throws Exception { File settingsFile = createTestFile("java/eclipse/ModuleInfo.prefs"); - assertFormatted(JVM_SUPPORT.getRecommendedFormatterVersion(), settingsFile); + assertFormatted("4.11", settingsFile); } } } diff --git a/lib-extra/src/test/resources/eclipse_formatter_issue_1638.xml b/lib-extra/src/test/resources/eclipse_formatter_issue_1638.xml new file mode 100644 index 0000000000..583df63026 --- /dev/null +++ b/lib-extra/src/test/resources/eclipse_formatter_issue_1638.xml @@ -0,0 +1,391 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/build.gradle b/lib/build.gradle index dfb2a38d73..738e2975fc 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -8,13 +8,20 @@ apply from: rootProject.file('gradle/java-setup.gradle') apply from: rootProject.file('gradle/java-publish.gradle') def NEEDS_GLUE = [ - 'sortPom', - 'palantirJavaFormat', + // (alphabetic order please) + 'cleanthat', + 'diktat', + 'flexmark', + 'gherkin', + 'googleJavaFormat', + 'gson', + 'jackson', 'ktfmt', 'ktlint', - 'flexmark', - 'diktat', - 'scalafmt' + 'palantirJavaFormat', + 'scalafmt', + 'sortPom', + 'zjsonPatch', ] for (glue in NEEDS_GLUE) { sourceSets.register(glue) { @@ -26,85 +33,120 @@ for (glue in NEEDS_GLUE) { versionCompatibility { adapters { + // (alphabetic order please) + namespaces.register('Cleanthat') { + versions = [ + '2.1', + ] + targetSourceSetName = 'cleanthat' + } namespaces.register('KtLint') { + // as discussed at https://github.com/diffplug/spotless/pull/1475 + // we will support no more than 2 breaking changes at a time = 3 incompatible versions + // we will try to drop down to only one version if a stable API can be maintained for a full year versions = [ - '0.31.0', - '0.32.0', - '0.34.2', - '0.45.2', - '0.46.0', - '0.47.0', + '0.48.0', + '0.49.0', + '0.50.0', + '1.0.0', ] targetSourceSetName = 'ktlint' } + namespaces.register('Diktat') { + versions = [ + '1.2.5', + '2.0.0', + ] + targetSourceSetName = 'diktat' + } } } +tasks.named("check").configure { + dependsOn(tasks.named("testCompatibilityAdapters")) + dependsOn(tasks.named("testCompatibility")) +} + dependencies { compileOnly 'org.slf4j:slf4j-api:2.0.0' + testCommonImplementation 'org.slf4j:slf4j-api:2.0.0' + // zero runtime reqs is a hard requirements for spotless-lib // if you need a dep, put it in lib-extra - testImplementation "org.junit.jupiter:junit-jupiter:$VER_JUNIT" - testImplementation "org.assertj:assertj-core:$VER_ASSERTJ" - testImplementation "com.diffplug.durian:durian-testlib:$VER_DURIAN" - - // used for pom sorting - sortPomCompileOnly 'com.github.ekryd.sortpom:sortpom-sorter:3.0.0' - sortPomCompileOnly 'org.slf4j:slf4j-api:2.0.0' + testCommonImplementation "org.junit.jupiter:junit-jupiter:$VER_JUNIT" + testCommonImplementation "org.assertj:assertj-core:$VER_ASSERTJ" + testCommonImplementation "com.diffplug.durian:durian-testlib:$VER_DURIAN" + testCommonRuntimeOnly "org.junit.platform:junit-platform-launcher" - palantirJavaFormatCompileOnly 'com.palantir.javaformat:palantir-java-format:1.1.0' - - String VER_KTFMT = '0.40' - ktfmtCompileOnly "com.facebook:ktfmt:$VER_KTFMT" - String VER_KTLINT_GOOGLE_JAVA_FORMAT = '1.7' // for JDK 8 compatibility + // GLUE CODE (alphabetic order please) + // cleanthat + String VER_CLEANTHAT='2.17' + cleanthatCompileOnly "io.github.solven-eu.cleanthat:java:$VER_CLEANTHAT" + compatCleanthat2Dot1CompileAndTestOnly "io.github.solven-eu.cleanthat:java:$VER_CLEANTHAT" + // diktat old supported version 1.x + compatDiktat1Dot2Dot5CompileOnly "org.cqfn.diktat:diktat-rules:1.2.5" + // diktat latest supported version 2.x + compatDiktat2Dot0Dot0CompileOnly "com.saveourtool.diktat:diktat-runner:2.0.0" + // flexmark + flexmarkCompileOnly 'com.vladsch.flexmark:flexmark-all:0.64.0' + // gherkin + gherkinCompileOnly 'io.cucumber:gherkin-utils:8.0.2' + gherkinCompileOnly 'org.slf4j:slf4j-api:2.0.0' + // googleJavaFormat + googleJavaFormatCompileOnly 'com.google.googlejavaformat:google-java-format:1.19.2' + // gson + gsonCompileOnly 'com.google.code.gson:gson:2.10.1' + // jackson + String VER_JACKSON='2.14.2' + jacksonCompileOnly "com.fasterxml.jackson.core:jackson-databind:$VER_JACKSON" + jacksonCompileOnly "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$VER_JACKSON" + // ktfmt + ktfmtCompileOnly "com.facebook:ktfmt:0.46" ktfmtCompileOnly("com.google.googlejavaformat:google-java-format") { version { - strictly VER_KTLINT_GOOGLE_JAVA_FORMAT + strictly '1.7' // for JDK 8 compatibility } } - - String VER_KTLINT='0.46.1' - ktlintCompileOnly "com.pinterest:ktlint:$VER_KTLINT" - ktlintCompileOnly "com.pinterest.ktlint:ktlint-core:$VER_KTLINT" - ktlintCompileOnly "com.pinterest.ktlint:ktlint-ruleset-experimental:$VER_KTLINT" - ktlintCompileOnly "com.pinterest.ktlint:ktlint-ruleset-standard:$VER_KTLINT" - - compatKtLint0Dot31Dot0CompileOnly 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.20' - compatKtLint0Dot31Dot0CompileOnly 'com.github.shyiko.ktlint:ktlint-core:0.31.0' - compatKtLint0Dot31Dot0CompileOnly 'com.github.shyiko.ktlint:ktlint-ruleset-experimental:0.31.0' - compatKtLint0Dot31Dot0CompileOnly 'com.github.shyiko.ktlint:ktlint-ruleset-standard:0.31.0' - compatKtLint0Dot32Dot0CompileOnly 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.20' - compatKtLint0Dot32Dot0CompileOnly 'com.pinterest.ktlint:ktlint-core:0.32.0' - compatKtLint0Dot32Dot0CompileOnly 'com.pinterest.ktlint:ktlint-ruleset-experimental:0.32.0' - compatKtLint0Dot32Dot0CompileOnly 'com.pinterest.ktlint:ktlint-ruleset-standard:0.32.0' - compatKtLint0Dot34Dot2CompileOnly 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.20' - compatKtLint0Dot34Dot2CompileOnly 'com.pinterest.ktlint:ktlint-core:0.34.2' - compatKtLint0Dot34Dot2CompileOnly 'com.pinterest.ktlint:ktlint-ruleset-experimental:0.34.2' - compatKtLint0Dot34Dot2CompileOnly 'com.pinterest.ktlint:ktlint-ruleset-standard:0.34.2' - compatKtLint0Dot45Dot2CompileOnly 'com.pinterest.ktlint:ktlint-core:0.45.2' - compatKtLint0Dot45Dot2CompileOnly 'com.pinterest.ktlint:ktlint-ruleset-experimental:0.45.2' - compatKtLint0Dot45Dot2CompileOnly 'com.pinterest.ktlint:ktlint-ruleset-standard:0.45.2' - compatKtLint0Dot46Dot0CompileOnly 'com.pinterest.ktlint:ktlint-core:0.46.0' - compatKtLint0Dot46Dot0CompileOnly 'com.pinterest.ktlint:ktlint-ruleset-experimental:0.46.0' - compatKtLint0Dot46Dot0CompileOnly 'com.pinterest.ktlint:ktlint-ruleset-standard:0.46.0' - compatKtLint0Dot47Dot0CompileOnly 'com.pinterest.ktlint:ktlint-core:0.47.0' - compatKtLint0Dot47Dot0CompileOnly 'com.pinterest.ktlint:ktlint-ruleset-experimental:0.47.0' - compatKtLint0Dot47Dot0CompileOnly 'com.pinterest.ktlint:ktlint-ruleset-standard:0.47.0' - - String VER_SCALAFMT="3.5.9" - scalafmtCompileOnly "org.scalameta:scalafmt-core_2.13:$VER_SCALAFMT" - - String VER_DIKTAT = "1.2.3" - diktatCompileOnly "org.cqfn.diktat:diktat-rules:$VER_DIKTAT" - - // used for markdown formatting - flexmarkCompileOnly 'com.vladsch.flexmark:flexmark-all:0.62.2' + // ktlint oldest supported version + compatKtLint0Dot48Dot0CompileAndTestOnly 'com.pinterest.ktlint:ktlint-core:0.48.0' + compatKtLint0Dot48Dot0CompileAndTestOnly 'com.pinterest.ktlint:ktlint-ruleset-experimental:0.48.0' + compatKtLint0Dot48Dot0CompileAndTestOnly 'com.pinterest.ktlint:ktlint-ruleset-standard:0.48.0' + // ktlint previous supported version + compatKtLint0Dot49Dot0CompileAndTestOnly 'com.pinterest.ktlint:ktlint-rule-engine:0.49.0' + compatKtLint0Dot49Dot0CompileAndTestOnly 'com.pinterest.ktlint:ktlint-ruleset-standard:0.49.0' + compatKtLint0Dot49Dot0CompileAndTestOnly 'org.slf4j:slf4j-api:2.0.0' + // ktlint previous supported version + compatKtLint0Dot50Dot0CompileAndTestOnly 'com.pinterest.ktlint:ktlint-rule-engine:0.50.0' + compatKtLint0Dot50Dot0CompileAndTestOnly 'com.pinterest.ktlint:ktlint-ruleset-standard:0.50.0' + compatKtLint0Dot50Dot0CompileAndTestOnly 'org.slf4j:slf4j-api:2.0.0' + // ktlint latest supported version + compatKtLint1Dot0Dot0CompileAndTestOnly 'com.pinterest.ktlint:ktlint-rule-engine:1.0.0' + compatKtLint1Dot0Dot0CompileAndTestOnly 'com.pinterest.ktlint:ktlint-ruleset-standard:1.0.0' + compatKtLint1Dot0Dot0CompileAndTestOnly 'org.slf4j:slf4j-api:2.0.0' + // palantirJavaFormat + palantirJavaFormatCompileOnly 'com.palantir.javaformat:palantir-java-format:1.1.0' // this version needs to stay compilable against Java 8 for CI Job testNpm + // scalafmt + scalafmtCompileOnly "org.scalameta:scalafmt-core_2.13:3.7.3" + // sortPom + sortPomCompileOnly 'com.github.ekryd.sortpom:sortpom-sorter:3.2.1' + sortPomCompileOnly 'org.slf4j:slf4j-api:2.0.0' + // zjsonPatch + zjsonPatchCompileOnly 'com.flipkart.zjsonpatch:zjsonpatch:0.4.14' } // we'll hold the core lib to a high standard -spotbugs { reportLevel = 'low' } // low|medium|high (low = sensitive to even minor mistakes) +spotbugs { + // LOW|MEDIUM|DEFAULT|HIGH (low = sensitive to even minor mistakes). + reportLevel = com.github.spotbugs.snom.Confidence.valueOf('LOW') +} -test { useJUnitPlatform() } +apply from: rootProject.file('gradle/special-tests.gradle') +tasks.withType(Test).configureEach { + if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_16)) { + // https://docs.gradle.org/7.5/userguide/upgrading_version_7.html#removes_implicit_add_opens_for_test_workers + jvmArgs "--add-opens=java.base/java.lang=ALL-UNNAMED" + } +} jar { for (glue in NEEDS_GLUE) { diff --git a/lib/src/cleanthat/java/com/diffplug/spotless/glue/java/JavaCleanthatRefactorerFunc.java b/lib/src/cleanthat/java/com/diffplug/spotless/glue/java/JavaCleanthatRefactorerFunc.java new file mode 100644 index 0000000000..1137604264 --- /dev/null +++ b/lib/src/cleanthat/java/com/diffplug/spotless/glue/java/JavaCleanthatRefactorerFunc.java @@ -0,0 +1,94 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.glue.java; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.diffplug.spotless.FormatterFunc; + +import eu.solven.cleanthat.config.pojo.CleanthatEngineProperties; +import eu.solven.cleanthat.config.pojo.SourceCodeProperties; +import eu.solven.cleanthat.engine.java.IJdkVersionConstants; +import eu.solven.cleanthat.engine.java.refactorer.JavaRefactorer; +import eu.solven.cleanthat.engine.java.refactorer.JavaRefactorerProperties; +import eu.solven.cleanthat.formatter.LineEnding; + +/** + * The glue for CleanThat: it is build over the version in build.gradle, but at runtime it will be executed over + * the version loaded in JarState, which is by default defined in com.diffplug.spotless.java.CleanthatJavaStep#JVM_SUPPORT + */ +public class JavaCleanthatRefactorerFunc implements FormatterFunc { + private static final Logger LOGGER = LoggerFactory.getLogger(JavaCleanthatRefactorerFunc.class); + + private String jdkVersion; + private List included; + private List excluded; + private boolean includeDraft; + + public JavaCleanthatRefactorerFunc(String jdkVersion, List included, List excluded, boolean includeDraft) { + this.jdkVersion = jdkVersion == null ? IJdkVersionConstants.JDK_8 : jdkVersion; + this.included = included == null ? Collections.emptyList() : included; + this.excluded = excluded == null ? Collections.emptyList() : excluded; + this.includeDraft = includeDraft; + } + + public JavaCleanthatRefactorerFunc() { + this(IJdkVersionConstants.JDK_8, Arrays.asList("SafeAndConsensual"), Arrays.asList(), false); + } + + @Override + public String apply(String input) throws Exception { + // https://stackoverflow.com/questions/1771679/difference-between-threads-context-class-loader-and-normal-classloader + ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); + try { + // Ensure CleanThat main Thread has its custom classLoader while executing its refactoring + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + return doApply(input); + } finally { + // Restore the originalClassLoader + Thread.currentThread().setContextClassLoader(originalClassLoader); + } + } + + private String doApply(String input) throws InterruptedException, IOException { + // call some API that uses reflection without taking ClassLoader param + CleanthatEngineProperties engineProperties = CleanthatEngineProperties.builder().engineVersion(jdkVersion).build(); + + // Spotless will push us LF content + engineProperties.setSourceCode(SourceCodeProperties.builder().lineEnding(LineEnding.LF).build()); + + JavaRefactorerProperties refactorerProperties = new JavaRefactorerProperties(); + + refactorerProperties.setIncluded(included); + refactorerProperties.setExcluded(excluded); + + refactorerProperties.setIncludeDraft(includeDraft); + + JavaRefactorer refactorer = new JavaRefactorer(engineProperties, refactorerProperties); + + LOGGER.debug("Processing sourceJdk={} included={} excluded={}", jdkVersion, included, excluded, includeDraft); + LOGGER.debug("Available mutators: {}", JavaRefactorer.getAllIncluded()); + + return refactorer.doFormat(input); + } + +} diff --git a/lib/src/compatDiktat1Dot2Dot5/java/com/diffplug/spotless/glue/diktat/compat/DiktatCompat1Dot2Dot5Adapter.java b/lib/src/compatDiktat1Dot2Dot5/java/com/diffplug/spotless/glue/diktat/compat/DiktatCompat1Dot2Dot5Adapter.java new file mode 100644 index 0000000000..749d0a1f61 --- /dev/null +++ b/lib/src/compatDiktat1Dot2Dot5/java/com/diffplug/spotless/glue/diktat/compat/DiktatCompat1Dot2Dot5Adapter.java @@ -0,0 +1,85 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.glue.diktat.compat; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.annotation.Nullable; + +import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider; + +import com.pinterest.ktlint.core.KtLint; +import com.pinterest.ktlint.core.LintError; +import com.pinterest.ktlint.core.RuleSet; +import com.pinterest.ktlint.core.api.EditorConfigOverride; + +import kotlin.Unit; +import kotlin.jvm.functions.Function2; + +public class DiktatCompat1Dot2Dot5Adapter implements DiktatCompatAdapter { + private final List ruleSets; + private final Function2 formatterCallback; + private final ArrayList errors = new ArrayList<>(); + + public DiktatCompat1Dot2Dot5Adapter(@Nullable File configFile) { + if (configFile != null) { + System.setProperty("diktat.config.path", configFile.getAbsolutePath()); + } + this.ruleSets = Collections.singletonList(new DiktatRuleSetProvider().get()); + this.formatterCallback = new FormatterCallback(errors); + } + + static class FormatterCallback implements Function2 { + private final ArrayList errors; + + FormatterCallback(ArrayList errors) { + this.errors = errors; + } + + @Override + public Unit invoke(LintError lintError, Boolean corrected) { + if (!corrected) { + errors.add(lintError); + } + return null; + } + } + + @Override + public String format(final File file, final String content, final boolean isScript) { + errors.clear(); + String result = KtLint.INSTANCE.format(new KtLint.ExperimentalParams( + // Unlike Ktlint, Diktat requires full path to the file. + // See https://github.com/diffplug/spotless/issues/1189, https://github.com/analysis-dev/diktat/issues/1202 + file.getAbsolutePath(), + content, + ruleSets, + Collections.emptyMap(), + formatterCallback, + isScript, + null, + false, + new EditorConfigOverride(), + false)); + + DiktatReporting.reportIfRequired(errors, LintError::getLine, LintError::getCol, LintError::getDetail); + + return result; + } +} diff --git a/lib/src/compatDiktat2Dot0Dot0/java/com/diffplug/spotless/glue/diktat/compat/DiktatCompat2Dot0Dot0Adapter.java b/lib/src/compatDiktat2Dot0Dot0/java/com/diffplug/spotless/glue/diktat/compat/DiktatCompat2Dot0Dot0Adapter.java new file mode 100644 index 0000000000..5b264a46ed --- /dev/null +++ b/lib/src/compatDiktat2Dot0Dot0/java/com/diffplug/spotless/glue/diktat/compat/DiktatCompat2Dot0Dot0Adapter.java @@ -0,0 +1,96 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.glue.diktat.compat; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import com.saveourtool.diktat.DiktatFactoriesKt; +import com.saveourtool.diktat.DiktatProcessor; +import com.saveourtool.diktat.api.DiktatCallback; +import com.saveourtool.diktat.api.DiktatError; +import com.saveourtool.diktat.api.DiktatRuleConfig; +import com.saveourtool.diktat.api.DiktatRuleSet; + +import kotlin.Unit; + +public class DiktatCompat2Dot0Dot0Adapter implements DiktatCompatAdapter { + private final DiktatProcessor processor; + private final DiktatCallback formatterCallback; + private final ArrayList errors = new ArrayList<>(); + + public DiktatCompat2Dot0Dot0Adapter(@Nullable File configFile) { + this.processor = getDiktatReporter(configFile); + this.formatterCallback = new FormatterCallback(errors); + } + + @Override + public String format(File file, String content, boolean isScript) { + errors.clear(); + String result = processor.fix(content, file.toPath(), formatterCallback); + DiktatReporting.reportIfRequired(errors, DiktatError::getLine, DiktatError::getCol, DiktatError::getDetail); + return result; + } + + private static class FormatterCallback implements DiktatCallback { + private final ArrayList errors; + + FormatterCallback(ArrayList errors) { + this.errors = errors; + } + + @Override + public Unit invoke(DiktatError diktatError, Boolean corrected) { + doInvoke(diktatError, corrected); + return Unit.INSTANCE; + } + + @Override + public void invoke(@NotNull DiktatError diktatError, boolean corrected) { + doInvoke(diktatError, corrected); + } + + private void doInvoke(@NotNull DiktatError diktatError, boolean corrected) { + if (!corrected) { + errors.add(diktatError); + } + } + } + + private static DiktatProcessor getDiktatReporter(File configFile) { + final DiktatRuleSet ruleSet = DiktatFactoriesKt.getDiktatRuleSetFactory().invoke(readRuleConfigs(configFile)); + return DiktatFactoriesKt.getDiktatProcessorFactory().invoke(ruleSet); + } + + private static List readRuleConfigs(File configFile) { + if (configFile == null) { + return Collections.emptyList(); + } + try (final InputStream configInputStream = new FileInputStream(configFile)) { + return DiktatFactoriesKt.getDiktatRuleConfigReader().invoke(configInputStream); + } catch (IOException e) { + throw new IllegalArgumentException("Fail to read configFile", e); + } + } +} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/runtime/package-info.java b/lib/src/compatDiktatApi/java/com/diffplug/spotless/glue/diktat/compat/DiktatCompatAdapter.java similarity index 73% rename from _ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/runtime/package-info.java rename to lib/src/compatDiktatApi/java/com/diffplug/spotless/glue/diktat/compat/DiktatCompatAdapter.java index 5ed08616b9..fdc6e77afa 100644 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/runtime/package-info.java +++ b/lib/src/compatDiktatApi/java/com/diffplug/spotless/glue/diktat/compat/DiktatCompatAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/** - * Eclipse Core Runtime adapters allowing runtime modifications which normally requires an Eclipse restart. - */ -package com.diffplug.spotless.extra.eclipse.base.runtime; +package com.diffplug.spotless.glue.diktat.compat; + +import java.io.File; + +public interface DiktatCompatAdapter { + String format(File file, String content, boolean isScript); +} diff --git a/lib/src/compatDiktatApi/java/com/diffplug/spotless/glue/diktat/compat/DiktatReporting.java b/lib/src/compatDiktatApi/java/com/diffplug/spotless/glue/diktat/compat/DiktatReporting.java new file mode 100644 index 0000000000..fdc7e56614 --- /dev/null +++ b/lib/src/compatDiktatApi/java/com/diffplug/spotless/glue/diktat/compat/DiktatReporting.java @@ -0,0 +1,42 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.glue.diktat.compat; + +import java.util.List; +import java.util.function.Function; +import java.util.function.ToIntFunction; + +interface DiktatReporting { + static void reportIfRequired( + List errors, + ToIntFunction lineGetter, + ToIntFunction columnGetter, + Function detailGetter) { + if (!errors.isEmpty()) { + StringBuilder error = new StringBuilder(); + error.append("There are ").append(errors.size()).append(" unfixed errors:"); + for (T er : errors) { + error.append(System.lineSeparator()) + .append("Error on line: ").append(lineGetter.applyAsInt(er)) + .append(", column: ").append(columnGetter.applyAsInt(er)) + .append(" cannot be fixed automatically") + .append(System.lineSeparator()) + .append(detailGetter.apply(er)); + } + throw new AssertionError(error); + } + } +} diff --git a/lib/src/compatKtLint0Dot31Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot31Dot0Adapter.java b/lib/src/compatKtLint0Dot31Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot31Dot0Adapter.java deleted file mode 100644 index 56b8da2d20..0000000000 --- a/lib/src/compatKtLint0Dot31Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot31Dot0Adapter.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2022 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.glue.ktlint.compat; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import com.github.shyiko.ktlint.core.KtLint; -import com.github.shyiko.ktlint.core.LintError; -import com.github.shyiko.ktlint.core.RuleSet; -import com.github.shyiko.ktlint.ruleset.experimental.ExperimentalRuleSetProvider; -import com.github.shyiko.ktlint.ruleset.standard.StandardRuleSetProvider; - -import kotlin.Unit; -import kotlin.jvm.functions.Function2; - -public class KtLintCompat0Dot31Dot0Adapter implements KtLintCompatAdapter { - - static class FormatterCallback implements Function2 { - @Override - public Unit invoke(LintError lint, Boolean corrected) { - if (!corrected) { - KtLintCompatReporting.report(lint.getLine(), lint.getCol(), lint.getRuleId(), lint.getDetail()); - } - return null; - } - } - - @Override - public String format(final String text, final String name, final boolean isScript, - final boolean useExperimental, - final Map userData, - final Map editorConfigOverrideMap) { - final FormatterCallback formatterCallback = new FormatterCallback(); - - final List rulesets = new ArrayList<>(); - rulesets.add(new StandardRuleSetProvider().get()); - - if (useExperimental) { - rulesets.add(new ExperimentalRuleSetProvider().get()); - } - - return KtLint.INSTANCE.format( - text, - rulesets, - userData, - formatterCallback); - } -} diff --git a/lib/src/compatKtLint0Dot32Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot32Dot0Adapter.java b/lib/src/compatKtLint0Dot32Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot32Dot0Adapter.java deleted file mode 100644 index 6f69fcc7ce..0000000000 --- a/lib/src/compatKtLint0Dot32Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot32Dot0Adapter.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2022 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.glue.ktlint.compat; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import com.pinterest.ktlint.core.KtLint; -import com.pinterest.ktlint.core.LintError; -import com.pinterest.ktlint.core.RuleSet; -import com.pinterest.ktlint.ruleset.experimental.ExperimentalRuleSetProvider; -import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider; - -import kotlin.Unit; -import kotlin.jvm.functions.Function2; - -public class KtLintCompat0Dot32Dot0Adapter implements KtLintCompatAdapter { - - static class FormatterCallback implements Function2 { - @Override - public Unit invoke(LintError lint, Boolean corrected) { - if (!corrected) { - KtLintCompatReporting.report(lint.getLine(), lint.getCol(), lint.getRuleId(), lint.getDetail()); - } - return null; - } - } - - @Override - public String format(final String text, final String name, final boolean isScript, - final boolean useExperimental, - final Map userData, - final Map editorConfigOverrideMap) { - final FormatterCallback formatterCallback = new FormatterCallback(); - - final List rulesets = new ArrayList<>(); - rulesets.add(new StandardRuleSetProvider().get()); - - if (useExperimental) { - rulesets.add(new ExperimentalRuleSetProvider().get()); - } - - return KtLint.INSTANCE.format( - text, - rulesets, - userData, - formatterCallback); - } -} diff --git a/lib/src/compatKtLint0Dot34Dot2/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot34Dot2Adapter.java b/lib/src/compatKtLint0Dot34Dot2/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot34Dot2Adapter.java deleted file mode 100644 index a3c8c8df3b..0000000000 --- a/lib/src/compatKtLint0Dot34Dot2/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot34Dot2Adapter.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2022 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.glue.ktlint.compat; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import com.pinterest.ktlint.core.KtLint; -import com.pinterest.ktlint.core.LintError; -import com.pinterest.ktlint.core.RuleSet; -import com.pinterest.ktlint.ruleset.experimental.ExperimentalRuleSetProvider; -import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider; - -import kotlin.Unit; -import kotlin.jvm.functions.Function2; - -public class KtLintCompat0Dot34Dot2Adapter implements KtLintCompatAdapter { - - static class FormatterCallback implements Function2 { - @Override - public Unit invoke(LintError lint, Boolean corrected) { - if (!corrected) { - KtLintCompatReporting.report(lint.getLine(), lint.getCol(), lint.getRuleId(), lint.getDetail()); - } - return null; - } - } - - @Override - public String format(final String text, final String name, final boolean isScript, - final boolean useExperimental, - final Map userData, - final Map editorConfigOverrideMap) { - final FormatterCallback formatterCallback = new FormatterCallback(); - - final List rulesets = new ArrayList<>(); - rulesets.add(new StandardRuleSetProvider().get()); - - if (useExperimental) { - rulesets.add(new ExperimentalRuleSetProvider().get()); - } - - return KtLint.INSTANCE.format(new KtLint.Params( - name, - text, - rulesets, - userData, - formatterCallback, - isScript, - null, - false)); - } -} diff --git a/lib/src/compatKtLint0Dot45Dot2/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot45Dot2Adapter.java b/lib/src/compatKtLint0Dot45Dot2/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot45Dot2Adapter.java deleted file mode 100644 index f7eadada3d..0000000000 --- a/lib/src/compatKtLint0Dot45Dot2/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot45Dot2Adapter.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2022 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.glue.ktlint.compat; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import com.pinterest.ktlint.core.KtLint; -import com.pinterest.ktlint.core.LintError; -import com.pinterest.ktlint.core.RuleSet; -import com.pinterest.ktlint.core.api.DefaultEditorConfigProperties; -import com.pinterest.ktlint.core.api.EditorConfigOverride; -import com.pinterest.ktlint.core.api.UsesEditorConfigProperties; -import com.pinterest.ktlint.ruleset.experimental.ExperimentalRuleSetProvider; -import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider; - -import kotlin.Pair; -import kotlin.Unit; -import kotlin.jvm.functions.Function2; - -public class KtLintCompat0Dot45Dot2Adapter implements KtLintCompatAdapter { - - static class FormatterCallback implements Function2 { - @Override - public Unit invoke(LintError lint, Boolean corrected) { - if (!corrected) { - KtLintCompatReporting.report(lint.getLine(), lint.getCol(), lint.getRuleId(), lint.getDetail()); - } - return null; - } - } - - @Override - public String format(final String text, final String name, final boolean isScript, - final boolean useExperimental, - final Map userData, - final Map editorConfigOverrideMap) { - final FormatterCallback formatterCallback = new FormatterCallback(); - - final List rulesets = new ArrayList<>(); - rulesets.add(new StandardRuleSetProvider().get()); - - if (useExperimental) { - rulesets.add(new ExperimentalRuleSetProvider().get()); - } - - EditorConfigOverride editorConfigOverride; - if (editorConfigOverrideMap.isEmpty()) { - editorConfigOverride = EditorConfigOverride.Companion.getEmptyEditorConfigOverride(); - } else { - editorConfigOverride = createEditorConfigOverride(rulesets, editorConfigOverrideMap); - } - - return KtLint.INSTANCE.format(new KtLint.ExperimentalParams( - name, - text, - rulesets, - userData, - formatterCallback, - isScript, - null, - false, - editorConfigOverride, - false)); - } - - /** - * Create EditorConfigOverride from user provided parameters. - * Calling this method requires KtLint 0.45.2. - */ - private static EditorConfigOverride createEditorConfigOverride(final List rulesets, Map editorConfigOverrideMap) { - // Get properties from rules in the rule sets - Stream> ruleProperties = rulesets.stream() - .flatMap(ruleSet -> Arrays.stream(ruleSet.getRules())) - .filter(rule -> rule instanceof UsesEditorConfigProperties) - .flatMap(rule -> ((UsesEditorConfigProperties) rule).getEditorConfigProperties().stream()); - - // Create a mapping of properties to their names based on rule properties and default properties - Map> supportedProperties = Stream - .concat(ruleProperties, DefaultEditorConfigProperties.INSTANCE.getDefaultEditorConfigProperties().stream()) - .distinct() - .collect(Collectors.toMap(property -> property.getType().getName(), property -> property)); - - // Create config properties based on provided property names and values - @SuppressWarnings("unchecked") - Pair, ?>[] properties = editorConfigOverrideMap.entrySet().stream() - .map(entry -> { - UsesEditorConfigProperties.EditorConfigProperty property = supportedProperties.get(entry.getKey()); - if (property != null) { - return new Pair<>(property, entry.getValue()); - } else { - return null; - } - }) - .filter(Objects::nonNull) - .toArray(Pair[]::new); - - return EditorConfigOverride.Companion.from(properties); - } -} diff --git a/lib/src/compatKtLint0Dot46Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot46Dot0Adapter.java b/lib/src/compatKtLint0Dot46Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot46Dot0Adapter.java deleted file mode 100644 index 873b91af80..0000000000 --- a/lib/src/compatKtLint0Dot46Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot46Dot0Adapter.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2022 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.glue.ktlint.compat; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import com.pinterest.ktlint.core.KtLint; -import com.pinterest.ktlint.core.LintError; -import com.pinterest.ktlint.core.RuleSet; -import com.pinterest.ktlint.core.api.DefaultEditorConfigProperties; -import com.pinterest.ktlint.core.api.EditorConfigOverride; -import com.pinterest.ktlint.core.api.UsesEditorConfigProperties; -import com.pinterest.ktlint.ruleset.experimental.ExperimentalRuleSetProvider; -import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider; - -import kotlin.Pair; -import kotlin.Unit; -import kotlin.jvm.functions.Function2; - -public class KtLintCompat0Dot46Dot0Adapter implements KtLintCompatAdapter { - - static class FormatterCallback implements Function2 { - @Override - public Unit invoke(LintError lint, Boolean corrected) { - if (!corrected) { - KtLintCompatReporting.report(lint.getLine(), lint.getCol(), lint.getRuleId(), lint.getDetail()); - } - return null; - } - } - - @Override - public String format(final String text, final String name, final boolean isScript, - final boolean useExperimental, - final Map userData, - final Map editorConfigOverrideMap) { - final FormatterCallback formatterCallback = new FormatterCallback(); - - final List rulesets = new ArrayList<>(); - rulesets.add(new StandardRuleSetProvider().get()); - - if (useExperimental) { - rulesets.add(new ExperimentalRuleSetProvider().get()); - } - - EditorConfigOverride editorConfigOverride; - if (editorConfigOverrideMap.isEmpty()) { - editorConfigOverride = EditorConfigOverride.Companion.getEmptyEditorConfigOverride(); - } else { - editorConfigOverride = createEditorConfigOverride(rulesets, editorConfigOverrideMap); - } - - return KtLint.INSTANCE.format(new KtLint.ExperimentalParams( - name, - text, - rulesets, - userData, - formatterCallback, - isScript, - null, - false, - editorConfigOverride, - false)); - } - - /** - * Create EditorConfigOverride from user provided parameters. - * Calling this method requires KtLint 0.45.2. - */ - private static EditorConfigOverride createEditorConfigOverride(final List rulesets, Map editorConfigOverrideMap) { - // Get properties from rules in the rule sets - Stream> ruleProperties = rulesets.stream() - .flatMap(ruleSet -> Arrays.stream(ruleSet.getRules())) - .filter(rule -> rule instanceof UsesEditorConfigProperties) - .flatMap(rule -> ((UsesEditorConfigProperties) rule).getEditorConfigProperties().stream()); - - // Create a mapping of properties to their names based on rule properties and default properties - Map> supportedProperties = Stream - .concat(ruleProperties, DefaultEditorConfigProperties.INSTANCE.getEditorConfigProperties().stream()) - .distinct() - .collect(Collectors.toMap(property -> property.getType().getName(), property -> property)); - - // Create config properties based on provided property names and values - @SuppressWarnings("unchecked") - Pair, ?>[] properties = editorConfigOverrideMap.entrySet().stream() - .map(entry -> { - UsesEditorConfigProperties.EditorConfigProperty property = supportedProperties.get(entry.getKey()); - if (property != null) { - return new Pair<>(property, entry.getValue()); - } else { - return null; - } - }) - .filter(Objects::nonNull) - .toArray(Pair[]::new); - - return EditorConfigOverride.Companion.from(properties); - } -} diff --git a/lib/src/compatKtLint0Dot47Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot47Dot0Adapter.java b/lib/src/compatKtLint0Dot47Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot47Dot0Adapter.java deleted file mode 100644 index 842294a5cc..0000000000 --- a/lib/src/compatKtLint0Dot47Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot47Dot0Adapter.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright 2022 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.glue.ktlint.compat; - -import static java.util.Collections.emptySet; - -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import com.pinterest.ktlint.core.KtLint; -import com.pinterest.ktlint.core.LintError; -import com.pinterest.ktlint.core.Rule; -import com.pinterest.ktlint.core.RuleProvider; -import com.pinterest.ktlint.core.api.DefaultEditorConfigProperties; -import com.pinterest.ktlint.core.api.EditorConfigDefaults; -import com.pinterest.ktlint.core.api.EditorConfigOverride; -import com.pinterest.ktlint.core.api.UsesEditorConfigProperties; -import com.pinterest.ktlint.ruleset.experimental.ExperimentalRuleSetProvider; -import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider; - -import kotlin.Pair; -import kotlin.Unit; -import kotlin.jvm.functions.Function2; - -public class KtLintCompat0Dot47Dot0Adapter implements KtLintCompatAdapter { - - static class FormatterCallback implements Function2 { - @Override - public Unit invoke(LintError lint, Boolean corrected) { - if (!corrected) { - KtLintCompatReporting.report(lint.getLine(), lint.getCol(), lint.getRuleId(), lint.getDetail()); - } - return null; - } - } - - @Override - public String format(final String text, final String name, final boolean isScript, - final boolean useExperimental, - final Map userData, - final Map editorConfigOverrideMap) { - final FormatterCallback formatterCallback = new FormatterCallback(); - - Set allRuleProviders = new LinkedHashSet<>( - new StandardRuleSetProvider().getRuleProviders()); - if (useExperimental) { - allRuleProviders.addAll(new ExperimentalRuleSetProvider().getRuleProviders()); - } - - EditorConfigOverride editorConfigOverride; - if (editorConfigOverrideMap.isEmpty()) { - editorConfigOverride = EditorConfigOverride.Companion.getEmptyEditorConfigOverride(); - } else { - editorConfigOverride = createEditorConfigOverride(allRuleProviders.stream().map( - RuleProvider::createNewRuleInstance).collect( - Collectors.toList()), - editorConfigOverrideMap); - } - - return KtLint.INSTANCE.format(new KtLint.ExperimentalParams( - name, - text, - emptySet(), - allRuleProviders, - userData, - formatterCallback, - isScript, - null, - false, - EditorConfigDefaults.Companion.getEmptyEditorConfigDefaults(), - editorConfigOverride, - false)); - } - - /** - * Create EditorConfigOverride from user provided parameters. - * Calling this method requires KtLint 0.45.2. - */ - private static EditorConfigOverride createEditorConfigOverride(final List rules, Map editorConfigOverrideMap) { - // Get properties from rules in the rule sets - Stream> ruleProperties = rules.stream() - .filter(rule -> rule instanceof UsesEditorConfigProperties) - .flatMap(rule -> ((UsesEditorConfigProperties) rule).getEditorConfigProperties().stream()); - - // Create a mapping of properties to their names based on rule properties and default properties - Map> supportedProperties = Stream - .concat(ruleProperties, DefaultEditorConfigProperties.INSTANCE.getEditorConfigProperties().stream()) - .distinct() - .collect(Collectors.toMap(property -> property.getType().getName(), property -> property)); - - // Create config properties based on provided property names and values - @SuppressWarnings("unchecked") - Pair, ?>[] properties = editorConfigOverrideMap.entrySet().stream() - .map(entry -> { - UsesEditorConfigProperties.EditorConfigProperty property = supportedProperties.get(entry.getKey()); - if (property != null) { - return new Pair<>(property, entry.getValue()); - } else { - return null; - } - }) - .filter(Objects::nonNull) - .toArray(Pair[]::new); - - return EditorConfigOverride.Companion.from(properties); - } -} diff --git a/lib/src/compatKtLint0Dot48Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot48Dot0Adapter.java b/lib/src/compatKtLint0Dot48Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot48Dot0Adapter.java new file mode 100644 index 0000000000..32ef497de8 --- /dev/null +++ b/lib/src/compatKtLint0Dot48Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot48Dot0Adapter.java @@ -0,0 +1,156 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.glue.ktlint.compat; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.pinterest.ktlint.core.KtLintRuleEngine; +import com.pinterest.ktlint.core.LintError; +import com.pinterest.ktlint.core.Rule; +import com.pinterest.ktlint.core.RuleProvider; +import com.pinterest.ktlint.core.RuleSetProviderV2; +import com.pinterest.ktlint.core.api.EditorConfigDefaults; +import com.pinterest.ktlint.core.api.EditorConfigOverride; +import com.pinterest.ktlint.core.api.UsesEditorConfigProperties; +import com.pinterest.ktlint.core.api.editorconfig.CodeStyleEditorConfigPropertyKt; +import com.pinterest.ktlint.core.api.editorconfig.DisabledRulesEditorConfigPropertyKt; +import com.pinterest.ktlint.core.api.editorconfig.EditorConfigProperty; +import com.pinterest.ktlint.core.api.editorconfig.IndentSizeEditorConfigPropertyKt; +import com.pinterest.ktlint.core.api.editorconfig.IndentStyleEditorConfigPropertyKt; +import com.pinterest.ktlint.core.api.editorconfig.InsertFinalNewLineEditorConfigPropertyKt; +import com.pinterest.ktlint.core.api.editorconfig.MaxLineLengthEditorConfigPropertyKt; +import com.pinterest.ktlint.core.api.editorconfig.RuleExecutionEditorConfigPropertyKt; + +import kotlin.Pair; +import kotlin.Unit; +import kotlin.jvm.functions.Function2; + +public class KtLintCompat0Dot48Dot0Adapter implements KtLintCompatAdapter { + + private static final List> DEFAULT_EDITOR_CONFIG_PROPERTIES; + + static { + //noinspection deprecation + DEFAULT_EDITOR_CONFIG_PROPERTIES = List.of( + CodeStyleEditorConfigPropertyKt.getCODE_STYLE_PROPERTY(), + DisabledRulesEditorConfigPropertyKt.getDISABLED_RULES_PROPERTY(), + DisabledRulesEditorConfigPropertyKt.getKTLINT_DISABLED_RULES_PROPERTY(), + IndentStyleEditorConfigPropertyKt.getINDENT_STYLE_PROPERTY(), + IndentSizeEditorConfigPropertyKt.getINDENT_SIZE_PROPERTY(), + InsertFinalNewLineEditorConfigPropertyKt.getINSERT_FINAL_NEWLINE_PROPERTY(), + MaxLineLengthEditorConfigPropertyKt.getMAX_LINE_LENGTH_PROPERTY()); + } + + static class FormatterCallback implements Function2 { + @Override + public Unit invoke(LintError lint, Boolean corrected) { + if (!corrected) { + KtLintCompatReporting.report(lint.getLine(), lint.getCol(), lint.getRuleId(), lint.getDetail()); + } + return Unit.INSTANCE; + } + } + + @Override + public String format( + Path path, + Path editorConfigPath, + Map editorConfigOverrideMap) { + final FormatterCallback formatterCallback = new FormatterCallback(); + + Set allRuleProviders = ServiceLoader.load(RuleSetProviderV2.class, RuleSetProviderV2.class.getClassLoader()) + .stream() + .flatMap(loader -> loader.get().getRuleProviders().stream()) + .collect(Collectors.toUnmodifiableSet()); + + EditorConfigOverride editorConfigOverride; + if (editorConfigOverrideMap.isEmpty()) { + editorConfigOverride = EditorConfigOverride.Companion.getEMPTY_EDITOR_CONFIG_OVERRIDE(); + } else { + editorConfigOverride = createEditorConfigOverride(allRuleProviders.stream().map( + RuleProvider::createNewRuleInstance).collect( + Collectors.toList()), + editorConfigOverrideMap); + } + EditorConfigDefaults editorConfig; + if (editorConfigPath == null || !Files.exists(editorConfigPath)) { + editorConfig = EditorConfigDefaults.Companion.getEMPTY_EDITOR_CONFIG_DEFAULTS(); + } else { + editorConfig = EditorConfigDefaults.Companion.load(editorConfigPath); + } + + return new KtLintRuleEngine( + allRuleProviders, + editorConfig, + editorConfigOverride, + false) + .format(path, formatterCallback); + } + + /** + * Create EditorConfigOverride from user provided parameters. + */ + private static EditorConfigOverride createEditorConfigOverride(final List rules, Map editorConfigOverrideMap) { + // Get properties from rules in the rule sets + Stream> ruleProperties = rules.stream() + .filter(rule -> rule instanceof UsesEditorConfigProperties) + .flatMap(rule -> ((UsesEditorConfigProperties) rule).getEditorConfigProperties().stream()); + + // Create a mapping of properties to their names based on rule properties and default properties + Map> supportedProperties = Stream + .concat(ruleProperties, DEFAULT_EDITOR_CONFIG_PROPERTIES.stream()) + .distinct() + .collect(Collectors.toMap(EditorConfigProperty::getName, property -> property)); + + // Create config properties based on provided property names and values + @SuppressWarnings("unchecked") + Pair, ?>[] properties = editorConfigOverrideMap.entrySet().stream() + .map(entry -> { + EditorConfigProperty property = supportedProperties.get(entry.getKey()); + + if (property == null && entry.getKey().startsWith("ktlint_")) { + String[] parts = entry.getKey().substring(7).split("_", 2); + if (parts.length == 1) { + // convert ktlint_{ruleset} to {ruleset} + String qualifiedRuleId = parts[0] + ":"; + property = RuleExecutionEditorConfigPropertyKt.createRuleSetExecutionEditorConfigProperty(qualifiedRuleId); + } else { + // convert ktlint_{ruleset}_{rulename} to {ruleset}:{rulename} + String qualifiedRuleId = parts[0] + ":" + parts[1]; + property = RuleExecutionEditorConfigPropertyKt.createRuleExecutionEditorConfigProperty(qualifiedRuleId); + } + } + + if (property == null) { + return null; + } else { + return new Pair<>(property, entry.getValue()); + } + }) + .filter(Objects::nonNull) + .toArray(Pair[]::new); + + return EditorConfigOverride.Companion.from(properties); + } +} diff --git a/lib/src/compatKtLint0Dot49Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot49Dot0Adapter.java b/lib/src/compatKtLint0Dot49Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot49Dot0Adapter.java new file mode 100644 index 0000000000..1ad52da661 --- /dev/null +++ b/lib/src/compatKtLint0Dot49Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot49Dot0Adapter.java @@ -0,0 +1,201 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.glue.ktlint.compat; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3; +import com.pinterest.ktlint.rule.engine.api.Code; +import com.pinterest.ktlint.rule.engine.api.EditorConfigDefaults; +import com.pinterest.ktlint.rule.engine.api.EditorConfigOverride; +import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine; +import com.pinterest.ktlint.rule.engine.api.LintError; +import com.pinterest.ktlint.rule.engine.core.api.Rule; +import com.pinterest.ktlint.rule.engine.core.api.RuleProvider; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleEditorConfigPropertyKt; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfigProperty; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EndOfLinePropertyKt; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.IndentSizeEditorConfigPropertyKt; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.IndentStyleEditorConfigPropertyKt; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.InsertFinalNewLineEditorConfigPropertyKt; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.MaxLineLengthEditorConfigPropertyKt; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.RuleExecution; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.RuleExecutionEditorConfigPropertyKt; + +import kotlin.Pair; +import kotlin.Unit; +import kotlin.jvm.functions.Function2; + +public class KtLintCompat0Dot49Dot0Adapter implements KtLintCompatAdapter { + + private static final Logger logger = LoggerFactory.getLogger(KtLintCompat0Dot49Dot0Adapter.class); + + private static final List> DEFAULT_EDITOR_CONFIG_PROPERTIES; + + static { + DEFAULT_EDITOR_CONFIG_PROPERTIES = List.of( + CodeStyleEditorConfigPropertyKt.getCODE_STYLE_PROPERTY(), + EndOfLinePropertyKt.getEND_OF_LINE_PROPERTY(), + IndentSizeEditorConfigPropertyKt.getINDENT_SIZE_PROPERTY(), + IndentStyleEditorConfigPropertyKt.getINDENT_STYLE_PROPERTY(), + InsertFinalNewLineEditorConfigPropertyKt.getINSERT_FINAL_NEWLINE_PROPERTY(), + MaxLineLengthEditorConfigPropertyKt.getMAX_LINE_LENGTH_PROPERTY(), + RuleExecutionEditorConfigPropertyKt.getEXPERIMENTAL_RULES_EXECUTION_PROPERTY()); + } + + private static final Method RULEID_METHOD; + private static final Method CREATE_RULESET_EXECUTION_METHOD; + private static final Method CREATE_RULE_EXECUTION_METHOD; + + static { + try { + RULEID_METHOD = LintError.class.getMethod("getRuleId-6XN97os"); + CREATE_RULESET_EXECUTION_METHOD = RuleExecutionEditorConfigPropertyKt.class.getMethod("createRuleSetExecutionEditorConfigProperty-fqiwTpU", String.class, RuleExecution.class); + CREATE_RULE_EXECUTION_METHOD = RuleExecutionEditorConfigPropertyKt.class.getMethod("createRuleExecutionEditorConfigProperty-U7AdEiY", String.class, RuleExecution.class); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + private static String getRuleId(LintError lint) { + try { + return (String) RULEID_METHOD.invoke(lint); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + private static EditorConfigProperty createRuleSetExecution(String id, RuleExecution execution) { + try { + return (EditorConfigProperty) CREATE_RULESET_EXECUTION_METHOD.invoke(null, id, execution); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + private static EditorConfigProperty createRuleExecution(String id, RuleExecution execution) { + try { + return (EditorConfigProperty) CREATE_RULE_EXECUTION_METHOD.invoke(null, id, execution); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + static class FormatterCallback implements Function2 { + + @Override + public Unit invoke(LintError lint, Boolean corrected) { + if (!corrected) { + KtLintCompatReporting.report(lint.getLine(), lint.getCol(), getRuleId(lint), lint.getDetail()); + } + return Unit.INSTANCE; + } + } + + @Override + public String format( + Path path, + Path editorConfigPath, + Map editorConfigOverrideMap) { + final FormatterCallback formatterCallback = new FormatterCallback(); + + Set allRuleProviders = ServiceLoader.load(RuleSetProviderV3.class, RuleSetProviderV3.class.getClassLoader()) + .stream() + .flatMap(loader -> loader.get().getRuleProviders().stream()) + .collect(Collectors.toUnmodifiableSet()); + + EditorConfigOverride editorConfigOverride; + if (editorConfigOverrideMap.isEmpty()) { + editorConfigOverride = EditorConfigOverride.Companion.getEMPTY_EDITOR_CONFIG_OVERRIDE(); + } else { + editorConfigOverride = createEditorConfigOverride(allRuleProviders.stream().map( + RuleProvider::createNewRuleInstance).collect(Collectors.toList()), + editorConfigOverrideMap); + } + EditorConfigDefaults editorConfig; + if (editorConfigPath == null || !Files.exists(editorConfigPath)) { + editorConfig = EditorConfigDefaults.Companion.getEMPTY_EDITOR_CONFIG_DEFAULTS(); + } else { + editorConfig = EditorConfigDefaults.Companion.load(editorConfigPath); + } + + return new KtLintRuleEngine( + allRuleProviders, + editorConfig, + editorConfigOverride, + false, + path.getFileSystem()) + .format(Code.Companion.fromPath(path), formatterCallback); + } + + /** + * Create EditorConfigOverride from user provided parameters. + */ + private static EditorConfigOverride createEditorConfigOverride(final List rules, Map editorConfigOverrideMap) { + // Get properties from rules in the rule sets + Stream> ruleProperties = rules.stream() + .flatMap(rule -> rule.getUsesEditorConfigProperties().stream()); + + // Create a mapping of properties to their names based on rule properties and default properties + Map> supportedProperties = Stream + .concat(ruleProperties, DEFAULT_EDITOR_CONFIG_PROPERTIES.stream()) + .distinct() + .collect(Collectors.toMap(EditorConfigProperty::getName, property -> property)); + + // Create config properties based on provided property names and values + @SuppressWarnings("unchecked") + Pair, ?>[] properties = editorConfigOverrideMap.entrySet().stream() + .map(entry -> { + EditorConfigProperty property = supportedProperties.get(entry.getKey()); + + if (property == null && entry.getKey().startsWith("ktlint_")) { + String[] parts = entry.getKey().substring(7).split("_", 2); + if (parts.length == 1) { + // convert ktlint_{ruleset} to {ruleset} + String id = parts[0]; + property = createRuleSetExecution(id, RuleExecution.enabled); + } else { + // convert ktlint_{ruleset}_{rulename} to {ruleset}:{rulename} + String id = parts[0] + ":" + parts[1]; + property = createRuleExecution(id, RuleExecution.enabled); + } + } + + if (property == null) { + return null; + } else { + return new Pair<>(property, entry.getValue()); + } + }) + .filter(Objects::nonNull) + .toArray(Pair[]::new); + + return EditorConfigOverride.Companion.from(properties); + } +} diff --git a/lib/src/compatKtLint0Dot50Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot50Dot0Adapter.java b/lib/src/compatKtLint0Dot50Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot50Dot0Adapter.java new file mode 100644 index 0000000000..2c7fff3f2e --- /dev/null +++ b/lib/src/compatKtLint0Dot50Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot50Dot0Adapter.java @@ -0,0 +1,165 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.glue.ktlint.compat; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3; +import com.pinterest.ktlint.rule.engine.api.Code; +import com.pinterest.ktlint.rule.engine.api.EditorConfigDefaults; +import com.pinterest.ktlint.rule.engine.api.EditorConfigOverride; +import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine; +import com.pinterest.ktlint.rule.engine.api.LintError; +import com.pinterest.ktlint.rule.engine.core.api.Rule; +import com.pinterest.ktlint.rule.engine.core.api.RuleId; +import com.pinterest.ktlint.rule.engine.core.api.RuleProvider; +import com.pinterest.ktlint.rule.engine.core.api.RuleSetId; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleEditorConfigPropertyKt; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfigProperty; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EndOfLinePropertyKt; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.IndentSizeEditorConfigPropertyKt; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.IndentStyleEditorConfigPropertyKt; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.InsertFinalNewLineEditorConfigPropertyKt; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.MaxLineLengthEditorConfigPropertyKt; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.RuleExecution; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.RuleExecutionEditorConfigPropertyKt; + +import kotlin.Pair; +import kotlin.Unit; +import kotlin.jvm.functions.Function2; + +public class KtLintCompat0Dot50Dot0Adapter implements KtLintCompatAdapter { + + private static final Logger logger = LoggerFactory.getLogger(KtLintCompat0Dot50Dot0Adapter.class); + + private static final List> DEFAULT_EDITOR_CONFIG_PROPERTIES; + + static { + DEFAULT_EDITOR_CONFIG_PROPERTIES = List.of( + CodeStyleEditorConfigPropertyKt.getCODE_STYLE_PROPERTY(), + EndOfLinePropertyKt.getEND_OF_LINE_PROPERTY(), + IndentSizeEditorConfigPropertyKt.getINDENT_SIZE_PROPERTY(), + IndentStyleEditorConfigPropertyKt.getINDENT_STYLE_PROPERTY(), + InsertFinalNewLineEditorConfigPropertyKt.getINSERT_FINAL_NEWLINE_PROPERTY(), + MaxLineLengthEditorConfigPropertyKt.getMAX_LINE_LENGTH_PROPERTY(), + RuleExecutionEditorConfigPropertyKt.getEXPERIMENTAL_RULES_EXECUTION_PROPERTY()); + } + + static class FormatterCallback implements Function2 { + + @Override + public Unit invoke(LintError lint, Boolean corrected) { + if (!corrected) { + KtLintCompatReporting.report(lint.getLine(), lint.getCol(), lint.getRuleId().getValue(), lint.getDetail()); + } + return Unit.INSTANCE; + } + } + + @Override + public String format( + Path path, + Path editorConfigPath, + Map editorConfigOverrideMap) { + final FormatterCallback formatterCallback = new FormatterCallback(); + + Set allRuleProviders = ServiceLoader.load(RuleSetProviderV3.class, RuleSetProviderV3.class.getClassLoader()) + .stream() + .flatMap(loader -> loader.get().getRuleProviders().stream()) + .collect(Collectors.toUnmodifiableSet()); + + EditorConfigOverride editorConfigOverride; + if (editorConfigOverrideMap.isEmpty()) { + editorConfigOverride = EditorConfigOverride.Companion.getEMPTY_EDITOR_CONFIG_OVERRIDE(); + } else { + editorConfigOverride = createEditorConfigOverride(allRuleProviders.stream().map( + RuleProvider::createNewRuleInstance).collect(Collectors.toList()), + editorConfigOverrideMap); + } + EditorConfigDefaults editorConfig; + if (editorConfigPath == null || !Files.exists(editorConfigPath)) { + editorConfig = EditorConfigDefaults.Companion.getEMPTY_EDITOR_CONFIG_DEFAULTS(); + } else { + editorConfig = EditorConfigDefaults.Companion.load(editorConfigPath, Collections.emptySet()); + } + + return new KtLintRuleEngine( + allRuleProviders, + editorConfig, + editorConfigOverride, + true, + false, + path.getFileSystem()) + .format(Code.Companion.fromPath(path), formatterCallback); + } + + /** + * Create EditorConfigOverride from user provided parameters. + */ + private static EditorConfigOverride createEditorConfigOverride(final List rules, Map editorConfigOverrideMap) { + // Get properties from rules in the rule sets + Stream> ruleProperties = rules.stream() + .flatMap(rule -> rule.getUsesEditorConfigProperties().stream()); + + // Create a mapping of properties to their names based on rule properties and default properties + Map> supportedProperties = Stream + .concat(ruleProperties, DEFAULT_EDITOR_CONFIG_PROPERTIES.stream()) + .distinct() + .collect(Collectors.toMap(EditorConfigProperty::getName, property -> property)); + + // Create config properties based on provided property names and values + @SuppressWarnings("unchecked") + Pair, ?>[] properties = editorConfigOverrideMap.entrySet().stream() + .map(entry -> { + EditorConfigProperty property = supportedProperties.get(entry.getKey()); + + if (property == null && entry.getKey().startsWith("ktlint_")) { + String[] parts = entry.getKey().substring(7).split("_", 2); + if (parts.length == 1) { + // convert ktlint_{ruleset} to RuleSetId + RuleSetId id = new RuleSetId(parts[0]); + property = RuleExecutionEditorConfigPropertyKt.createRuleSetExecutionEditorConfigProperty(id, RuleExecution.enabled); + } else { + // convert ktlint_{ruleset}_{rulename} to RuleId + RuleId id = new RuleId(parts[0] + ":" + parts[1]); + property = RuleExecutionEditorConfigPropertyKt.createRuleExecutionEditorConfigProperty(id, RuleExecution.enabled); + } + } + + if (property == null) { + return null; + } else { + return new Pair<>(property, entry.getValue()); + } + }) + .filter(Objects::nonNull) + .toArray(Pair[]::new); + + return EditorConfigOverride.Companion.from(properties); + } +} diff --git a/lib/src/compatKtLint1Dot0Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat1Dot0Dot0Adapter.java b/lib/src/compatKtLint1Dot0Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat1Dot0Dot0Adapter.java new file mode 100644 index 0000000000..e65d0503ba --- /dev/null +++ b/lib/src/compatKtLint1Dot0Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat1Dot0Dot0Adapter.java @@ -0,0 +1,169 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.glue.ktlint.compat; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.pinterest.ktlint.cli.ruleset.core.api.RuleSetProviderV3; +import com.pinterest.ktlint.rule.engine.api.Code; +import com.pinterest.ktlint.rule.engine.api.EditorConfigDefaults; +import com.pinterest.ktlint.rule.engine.api.EditorConfigOverride; +import com.pinterest.ktlint.rule.engine.api.KtLintRuleEngine; +import com.pinterest.ktlint.rule.engine.api.LintError; +import com.pinterest.ktlint.rule.engine.core.api.Rule; +import com.pinterest.ktlint.rule.engine.core.api.RuleId; +import com.pinterest.ktlint.rule.engine.core.api.RuleProvider; +import com.pinterest.ktlint.rule.engine.core.api.RuleSetId; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.CodeStyleEditorConfigPropertyKt; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EditorConfigProperty; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.EndOfLinePropertyKt; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.IndentSizeEditorConfigPropertyKt; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.IndentStyleEditorConfigPropertyKt; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.InsertFinalNewLineEditorConfigPropertyKt; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.MaxLineLengthEditorConfigPropertyKt; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.RuleExecution; +import com.pinterest.ktlint.rule.engine.core.api.editorconfig.RuleExecutionEditorConfigPropertyKt; + +import kotlin.Pair; +import kotlin.Unit; +import kotlin.jvm.functions.Function2; + +public class KtLintCompat1Dot0Dot0Adapter implements KtLintCompatAdapter { + + private static final Logger logger = LoggerFactory.getLogger(KtLintCompat1Dot0Dot0Adapter.class); + + private static final List> DEFAULT_EDITOR_CONFIG_PROPERTIES; + + static { + DEFAULT_EDITOR_CONFIG_PROPERTIES = List.of( + CodeStyleEditorConfigPropertyKt.getCODE_STYLE_PROPERTY(), + EndOfLinePropertyKt.getEND_OF_LINE_PROPERTY(), + IndentSizeEditorConfigPropertyKt.getINDENT_SIZE_PROPERTY(), + IndentStyleEditorConfigPropertyKt.getINDENT_STYLE_PROPERTY(), + InsertFinalNewLineEditorConfigPropertyKt.getINSERT_FINAL_NEWLINE_PROPERTY(), + MaxLineLengthEditorConfigPropertyKt.getMAX_LINE_LENGTH_PROPERTY(), + RuleExecutionEditorConfigPropertyKt.getEXPERIMENTAL_RULES_EXECUTION_PROPERTY()); + } + + static class FormatterCallback implements Function2 { + + @Override + public Unit invoke(LintError lint, Boolean corrected) { + if (!corrected) { + KtLintCompatReporting.report(lint.getLine(), lint.getCol(), lint.getRuleId().getValue(), lint.getDetail()); + } + return Unit.INSTANCE; + } + } + + @Override + public String format( + Path path, + Path editorConfigPath, + Map editorConfigOverrideMap) { + final FormatterCallback formatterCallback = new FormatterCallback(); + + Set allRuleProviders = ServiceLoader.load(RuleSetProviderV3.class, RuleSetProviderV3.class.getClassLoader()) + .stream() + .flatMap(loader -> loader.get().getRuleProviders().stream()) + .collect(Collectors.toUnmodifiableSet()); + + EditorConfigOverride editorConfigOverride; + if (editorConfigOverrideMap.isEmpty()) { + editorConfigOverride = EditorConfigOverride.Companion.getEMPTY_EDITOR_CONFIG_OVERRIDE(); + } else { + editorConfigOverride = createEditorConfigOverride(allRuleProviders.stream().map( + RuleProvider::createNewRuleInstance).collect(Collectors.toList()), + editorConfigOverrideMap); + } + EditorConfigDefaults editorConfig; + if (editorConfigPath == null || !Files.exists(editorConfigPath)) { + editorConfig = EditorConfigDefaults.Companion.getEMPTY_EDITOR_CONFIG_DEFAULTS(); + } else { + editorConfig = EditorConfigDefaults.Companion.load(editorConfigPath, Collections.emptySet()); + } + + return new KtLintRuleEngine( + allRuleProviders, + editorConfig, + editorConfigOverride, + false, + path.getFileSystem()) + .format(Code.Companion.fromPath(path), formatterCallback); + } + + /** + * Create EditorConfigOverride from user provided parameters. + */ + private static EditorConfigOverride createEditorConfigOverride(final List rules, Map editorConfigOverrideMap) { + // Get properties from rules in the rule sets + Stream> ruleProperties = rules.stream() + .flatMap(rule -> rule.getUsesEditorConfigProperties().stream()); + + // Create a mapping of properties to their names based on rule properties and default properties + Map> supportedProperties = Stream + .concat(ruleProperties, DEFAULT_EDITOR_CONFIG_PROPERTIES.stream()) + .distinct() + .collect(Collectors.toMap(EditorConfigProperty::getName, property -> property)); + + // The default style had been changed from intellij_idea to ktlint_official in version 1.0.0 + if (!editorConfigOverrideMap.containsKey("ktlint_code_style")) { + editorConfigOverrideMap.put("ktlint_code_style", "intellij_idea"); + } + + // Create config properties based on provided property names and values + @SuppressWarnings("unchecked") + Pair, ?>[] properties = editorConfigOverrideMap.entrySet().stream() + .map(entry -> { + EditorConfigProperty property = supportedProperties.get(entry.getKey()); + + if (property == null && entry.getKey().startsWith("ktlint_")) { + String[] parts = entry.getKey().substring(7).split("_", 2); + if (parts.length == 1) { + // convert ktlint_{ruleset} to RuleSetId + RuleSetId id = new RuleSetId(parts[0]); + property = RuleExecutionEditorConfigPropertyKt.createRuleSetExecutionEditorConfigProperty(id, RuleExecution.enabled); + } else { + // convert ktlint_{ruleset}_{rulename} to RuleId + RuleId id = new RuleId(parts[0] + ":" + parts[1]); + property = RuleExecutionEditorConfigPropertyKt.createRuleExecutionEditorConfigProperty(id, RuleExecution.enabled); + } + } + + if (property == null) { + return null; + } else { + return new Pair<>(property, entry.getValue()); + } + }) + .filter(Objects::nonNull) + .toArray(Pair[]::new); + + return EditorConfigOverride.Companion.from(properties); + } +} diff --git a/lib/src/compatKtLintApi/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompatAdapter.java b/lib/src/compatKtLintApi/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompatAdapter.java index 5097cac135..1bfa0da6da 100644 --- a/lib/src/compatKtLintApi/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompatAdapter.java +++ b/lib/src/compatKtLintApi/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompatAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 DiffPlug + * Copyright 2022-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,10 +15,13 @@ */ package com.diffplug.spotless.glue.ktlint.compat; +import java.nio.file.Path; import java.util.Map; public interface KtLintCompatAdapter { - String format(String text, String name, boolean isScript, boolean useExperimental, Map userData, + String format( + Path path, + Path editorConfigPath, Map editorConfigOverrideMap); } diff --git a/lib/src/diktat/java/com/diffplug/spotless/glue/diktat/DiktatFormatterFunc.java b/lib/src/diktat/java/com/diffplug/spotless/glue/diktat/DiktatFormatterFunc.java index 0b9d115c2b..1f1c15c97d 100644 --- a/lib/src/diktat/java/com/diffplug/spotless/glue/diktat/DiktatFormatterFunc.java +++ b/lib/src/diktat/java/com/diffplug/spotless/glue/diktat/DiktatFormatterFunc.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,81 +16,30 @@ package com.diffplug.spotless.glue.diktat; import java.io.File; -import java.util.*; - -import org.cqfn.diktat.ruleset.rules.DiktatRuleSetProvider; - -import com.pinterest.ktlint.core.KtLint; -import com.pinterest.ktlint.core.LintError; -import com.pinterest.ktlint.core.RuleSet; -import com.pinterest.ktlint.core.api.EditorConfigOverride; import com.diffplug.spotless.FormatterFunc; - -import kotlin.Unit; -import kotlin.jvm.functions.Function2; +import com.diffplug.spotless.glue.diktat.compat.DiktatCompat1Dot2Dot5Adapter; +import com.diffplug.spotless.glue.diktat.compat.DiktatCompat2Dot0Dot0Adapter; +import com.diffplug.spotless.glue.diktat.compat.DiktatCompatAdapter; public class DiktatFormatterFunc implements FormatterFunc.NeedsFile { - - private final List rulesets; - private final Function2 formatterCallback; + private final DiktatCompatAdapter adapter; private final boolean isScript; - private final ArrayList errors = new ArrayList<>(); - - public DiktatFormatterFunc(boolean isScript) { - rulesets = Collections.singletonList(new DiktatRuleSetProvider().get()); - this.formatterCallback = new FormatterCallback(errors); - this.isScript = isScript; - } - - static class FormatterCallback implements Function2 { - private final ArrayList errors; - - FormatterCallback(ArrayList errors) { - this.errors = errors; - } - - @Override - public Unit invoke(LintError lintError, Boolean corrected) { - if (!corrected) { - errors.add(lintError); - } - return null; + public DiktatFormatterFunc( + String version, + File configFile, + boolean isScript) { + if (version.startsWith("1.")) { + this.adapter = new DiktatCompat1Dot2Dot5Adapter(configFile); + } else { + this.adapter = new DiktatCompat2Dot0Dot0Adapter(configFile); } + this.isScript = isScript; } @Override - public String applyWithFile(String unix, File file) throws Exception { - errors.clear(); - String result = KtLint.INSTANCE.format(new KtLint.ExperimentalParams( - // Unlike Ktlint, Diktat requires full path to the file. - // See https://github.com/diffplug/spotless/issues/1189, https://github.com/analysis-dev/diktat/issues/1202 - file.getAbsolutePath(), - unix, - rulesets, - Collections.emptyMap(), - formatterCallback, - isScript, - null, - false, - new EditorConfigOverride(), - false)); - - if (!errors.isEmpty()) { - StringBuilder error = new StringBuilder(); - error.append("There are ").append(errors.size()).append(" unfixed errors:"); - for (LintError er : errors) { - error.append(System.lineSeparator()) - .append("Error on line: ").append(er.getLine()) - .append(", column: ").append(er.getCol()) - .append(" cannot be fixed automatically") - .append(System.lineSeparator()) - .append(er.getDetail()); - } - throw new AssertionError(error); - } - - return result; + public String applyWithFile(String unix, File file) { + return adapter.format(file, unix, isScript); } } diff --git a/lib/src/gherkin/java/com/diffplug/spotless/glue/gherkin/GherkinUtilsFormatterFunc.java b/lib/src/gherkin/java/com/diffplug/spotless/glue/gherkin/GherkinUtilsFormatterFunc.java new file mode 100644 index 0000000000..4b79347b29 --- /dev/null +++ b/lib/src/gherkin/java/com/diffplug/spotless/glue/gherkin/GherkinUtilsFormatterFunc.java @@ -0,0 +1,60 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.glue.gherkin; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.gherkin.GherkinUtilsConfig; + +import io.cucumber.gherkin.GherkinParser; +import io.cucumber.gherkin.utils.pretty.Pretty; +import io.cucumber.gherkin.utils.pretty.Syntax; +import io.cucumber.messages.types.Envelope; +import io.cucumber.messages.types.GherkinDocument; +import io.cucumber.messages.types.Source; +import io.cucumber.messages.types.SourceMediaType; + +public class GherkinUtilsFormatterFunc implements FormatterFunc { + private static final Logger LOGGER = LoggerFactory.getLogger(GherkinUtilsFormatterFunc.class); + + private final GherkinUtilsConfig gherkinSimpleConfig; + + public GherkinUtilsFormatterFunc(GherkinUtilsConfig gherkinSimpleConfig) { + this.gherkinSimpleConfig = gherkinSimpleConfig; + } + + // Follows https://github.com/cucumber/gherkin-utils/blob/main/java/src/test/java/io/cucumber/gherkin/utils/pretty/PrettyTest.java + private GherkinDocument parse(String gherkin) { + GherkinParser parser = GherkinParser + .builder() + .includeSource(false) + .build(); + return parser.parse(Envelope.of(new Source("test.feature", gherkin, SourceMediaType.TEXT_X_CUCUMBER_GHERKIN_PLAIN))) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No envelope")) + .getGherkinDocument() + .orElseThrow(() -> new IllegalArgumentException("No gherkin document")); + } + + @Override + public String apply(String inputString) { + GherkinDocument gherkinDocument = parse(inputString); + + return Pretty.prettyPrint(gherkinDocument, Syntax.gherkin); + } +} diff --git a/lib/src/googleJavaFormat/java/com/diffplug/spotless/glue/java/GoogleJavaFormatFormatterFunc.java b/lib/src/googleJavaFormat/java/com/diffplug/spotless/glue/java/GoogleJavaFormatFormatterFunc.java new file mode 100644 index 0000000000..d13f8d9d40 --- /dev/null +++ b/lib/src/googleJavaFormat/java/com/diffplug/spotless/glue/java/GoogleJavaFormatFormatterFunc.java @@ -0,0 +1,78 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.glue.java; + +import java.util.Objects; + +import javax.annotation.Nonnull; + +import com.google.googlejavaformat.java.Formatter; +import com.google.googlejavaformat.java.FormatterException; +import com.google.googlejavaformat.java.ImportOrderer; +import com.google.googlejavaformat.java.JavaFormatterOptions; +import com.google.googlejavaformat.java.JavaFormatterOptions.Style; +import com.google.googlejavaformat.java.RemoveUnusedImports; +import com.google.googlejavaformat.java.StringWrapper; + +import com.diffplug.spotless.FormatterFunc; + +// Used via reflection by the Gradle plugin. +@SuppressWarnings("unused") +public class GoogleJavaFormatFormatterFunc implements FormatterFunc { + + @Nonnull + private final Formatter formatter; + + @Nonnull + private final String version; + + @Nonnull + private final Style formatterStyle; + + private final boolean reflowStrings; + + private final boolean reorderImports; + + public GoogleJavaFormatFormatterFunc(@Nonnull String version, @Nonnull String style, boolean reflowStrings, boolean reorderImports, boolean formatJavadoc) { + this.version = Objects.requireNonNull(version); + this.formatterStyle = Style.valueOf(Objects.requireNonNull(style)); + this.reflowStrings = reflowStrings; + this.reorderImports = reorderImports; + + JavaFormatterOptions.Builder builder = JavaFormatterOptions.builder().style(formatterStyle); + if (!formatJavadoc) { + builder = builder.formatJavadoc(false); + } + this.formatter = new Formatter(builder.build()); + } + + @Override + @Nonnull + public String apply(@Nonnull String input) throws Exception { + String formatted = formatter.formatSource(input); + String removedUnused = RemoveUnusedImports.removeUnusedImports(formatted); + String sortedImports = ImportOrderer.reorderImports(removedUnused, reorderImports ? formatterStyle : Style.GOOGLE); + return reflowLongStrings(sortedImports); + } + + private String reflowLongStrings(String input) throws FormatterException { + if (reflowStrings) { + return StringWrapper.wrap(input, formatter); + } else { + return input; + } + } +} diff --git a/lib/src/googleJavaFormat/java/com/diffplug/spotless/glue/java/GoogleJavaFormatRemoveUnusedImporterFormatterFunc.java b/lib/src/googleJavaFormat/java/com/diffplug/spotless/glue/java/GoogleJavaFormatRemoveUnusedImporterFormatterFunc.java new file mode 100644 index 0000000000..de21a18795 --- /dev/null +++ b/lib/src/googleJavaFormat/java/com/diffplug/spotless/glue/java/GoogleJavaFormatRemoveUnusedImporterFormatterFunc.java @@ -0,0 +1,40 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.glue.java; + +import java.util.Objects; + +import javax.annotation.Nonnull; + +import com.google.googlejavaformat.java.RemoveUnusedImports; + +import com.diffplug.spotless.FormatterFunc; + +public class GoogleJavaFormatRemoveUnusedImporterFormatterFunc implements FormatterFunc { + + @Nonnull + private final String version; + + public GoogleJavaFormatRemoveUnusedImporterFormatterFunc(@Nonnull String version) { + this.version = Objects.requireNonNull(version); + } + + @Override + @Nonnull + public String apply(@Nonnull String input) throws Exception { + return RemoveUnusedImports.removeUnusedImports(input); + } +} diff --git a/lib/src/gson/java/com/diffplug/spotless/glue/gson/GsonFormatterFunc.java b/lib/src/gson/java/com/diffplug/spotless/glue/gson/GsonFormatterFunc.java new file mode 100644 index 0000000000..0ff5037cba --- /dev/null +++ b/lib/src/gson/java/com/diffplug/spotless/glue/gson/GsonFormatterFunc.java @@ -0,0 +1,110 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.glue.gson; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.Collections; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.stream.JsonWriter; + +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.ThrowingEx; +import com.diffplug.spotless.json.gson.GsonConfig; + +public class GsonFormatterFunc implements FormatterFunc { + + private static final String FAILED_TO_PARSE_ERROR_MESSAGE = "Unable to format JSON"; + + private final Gson gson; + private final GsonConfig gsonConfig; + private final String generatedIndent; + + public GsonFormatterFunc(GsonConfig gsonConfig) { + GsonBuilder gsonBuilder = new GsonBuilder().serializeNulls(); + if (!gsonConfig.isEscapeHtml()) { + gsonBuilder = gsonBuilder.disableHtmlEscaping(); + } + this.gson = gsonBuilder.create(); + this.gsonConfig = gsonConfig; + this.generatedIndent = generateIndent(gsonConfig.getIndentSpaces()); + } + + @Override + public String apply(String inputString) { + String result; + if (inputString.isEmpty()) { + result = ""; + } else { + JsonElement jsonElement = gson.fromJson(inputString, JsonElement.class); + if (jsonElement == null) { + throw new AssertionError(FAILED_TO_PARSE_ERROR_MESSAGE); + } + if (gsonConfig.isSortByKeys()) { + jsonElement = sortByKeys(jsonElement); + } + try (StringWriter stringWriter = new StringWriter()) { + JsonWriter jsonWriter = new JsonWriter(stringWriter); + jsonWriter.setIndent(this.generatedIndent); + gson.toJson(jsonElement, jsonWriter); + result = stringWriter + "\n"; + } catch (IOException ioException) { + throw ThrowingEx.asRuntime(ioException); + } + } + return result; + } + + private JsonElement sortByKeys(JsonElement jsonElement) { + if (jsonElement.isJsonArray()) { + return sortByKeys(jsonElement.getAsJsonArray()); + } else if (jsonElement.isJsonObject()) { + return sortByKeys(jsonElement.getAsJsonObject()); + } else { + return jsonElement; + } + } + + private JsonElement sortByKeys(JsonObject jsonObject) { + JsonObject result = new JsonObject(); + jsonObject.keySet().stream().sorted() + .forEach(key -> { + JsonElement sorted = sortByKeys(jsonObject.get(key)); + result.add(key, sorted); + }); + return result; + } + + private JsonElement sortByKeys(JsonArray jsonArray) { + var result = new JsonArray(); + for (JsonElement element : jsonArray) { + JsonElement sorted = sortByKeys(element); + result.add(sorted); + } + + return result; + } + + private String generateIndent(int indentSpaces) { + return String.join("", Collections.nCopies(indentSpaces, " ")); + } + +} diff --git a/lib/src/jackson/java/com/diffplug/spotless/glue/json/AJacksonFormatterFunc.java b/lib/src/jackson/java/com/diffplug/spotless/glue/json/AJacksonFormatterFunc.java new file mode 100644 index 0000000000..1f317fa3b4 --- /dev/null +++ b/lib/src/jackson/java/com/diffplug/spotless/glue/json/AJacksonFormatterFunc.java @@ -0,0 +1,94 @@ +/* + * Copyright 2021-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.glue.json; + +import java.io.IOException; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.PrettyPrinter; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.json.JacksonConfig; + +/** + * A {@link FormatterFunc} based on Jackson library + */ +// https://github.com/FasterXML/jackson-dataformats-text/issues/372 +public abstract class AJacksonFormatterFunc implements FormatterFunc { + private JacksonConfig jacksonConfig; + + public AJacksonFormatterFunc(JacksonConfig jacksonConfig) { + this.jacksonConfig = jacksonConfig; + } + + @Override + public String apply(String input) throws Exception { + ObjectMapper objectMapper = makeObjectMapper(); + + return format(objectMapper, input); + } + + protected String format(ObjectMapper objectMapper, String input) throws IllegalArgumentException, IOException { + try { + // ObjectNode is not compatible with SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS + Object objectNode = objectMapper.readValue(input, inferType(input)); + String output = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(objectNode); + + return output; + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("Unable to format. input='" + input + "'", e); + } + } + + /** + * + * @param input + * @return the {@link Class} into which the String has to be deserialized + */ + protected abstract Class inferType(String input); + + /** + * @return a {@link JsonFactory}. May be overridden to handle alternative formats. + * @see jackson-dataformats-text + */ + protected abstract JsonFactory makeJsonFactory(); + + protected ObjectMapper makeObjectMapper() { + JsonFactory jsonFactory = makeJsonFactory(); + ObjectMapper objectMapper = new ObjectMapper(jsonFactory); + + objectMapper.setDefaultPrettyPrinter(makePrettyPrinter()); + + // Configure the ObjectMapper + // https://github.com/FasterXML/jackson-databind#commonly-used-features + jacksonConfig.getFeatureToToggle().forEach((rawFeature, toggle) -> { + // https://stackoverflow.com/questions/3735927/java-instantiating-an-enum-using-reflection + SerializationFeature feature = SerializationFeature.valueOf(rawFeature); + + objectMapper.configure(feature, toggle); + }); + + return objectMapper; + } + + protected PrettyPrinter makePrettyPrinter() { + return new DefaultPrettyPrinter(); + } +} diff --git a/lib/src/jackson/java/com/diffplug/spotless/glue/json/JacksonJsonFormatterFunc.java b/lib/src/jackson/java/com/diffplug/spotless/glue/json/JacksonJsonFormatterFunc.java new file mode 100644 index 0000000000..71215eb4a3 --- /dev/null +++ b/lib/src/jackson/java/com/diffplug/spotless/glue/json/JacksonJsonFormatterFunc.java @@ -0,0 +1,110 @@ +/* + * Copyright 2021-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.glue.json; + +import java.util.Collection; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonFactoryBuilder; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.util.DefaultIndenter; +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.core.util.Separators; + +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.json.JacksonJsonConfig; + +/** + * A {@link FormatterFunc} based on Jackson library + */ +// https://github.com/FasterXML/jackson-dataformats-text/issues/372 +public class JacksonJsonFormatterFunc extends AJacksonFormatterFunc { + private JacksonJsonConfig jacksonConfig; + + public JacksonJsonFormatterFunc(JacksonJsonConfig jacksonConfig) { + super(jacksonConfig); + this.jacksonConfig = jacksonConfig; + } + + @Override + protected Class inferType(String input) { + if (input.trim().startsWith("[")) { + return Collection.class; + } else { + return Map.class; + } + } + + /** + * @return a {@link JsonFactory}. May be overridden to handle alternative formats. + * @see jackson-dataformats-text + */ + protected JsonFactory makeJsonFactory() { + JsonFactory jsonFactory = new JsonFactoryBuilder().build(); + + // Configure the ObjectMapper + // https://github.com/FasterXML/jackson-databind#commonly-used-features + jacksonConfig.getJsonFeatureToToggle().forEach((rawFeature, toggle) -> { + // https://stackoverflow.com/questions/3735927/java-instantiating-an-enum-using-reflection + JsonGenerator.Feature feature = JsonGenerator.Feature.valueOf(rawFeature); + + jsonFactory.configure(feature, toggle); + }); + + return jsonFactory; + } + + @Override + protected DefaultPrettyPrinter makePrettyPrinter() { + boolean spaceBeforeSeparator = jacksonConfig.isSpaceBeforeSeparator(); + + // DefaultIndenter default constructor relies on 2 whitespaces as default tabulation + // By we want to force '\n' as eol given Spotless provides LF-input (whatever the actual File content/current OS) + DefaultPrettyPrinter.Indenter indenter = new DefaultIndenter(" ", "\n"); + DefaultPrettyPrinter printer = new SpotlessJsonPrettyPrinter(spaceBeforeSeparator); + + printer.indentObjectsWith(indenter); + printer.indentArraysWith(indenter); + return printer; + } + + protected static class SpotlessJsonPrettyPrinter extends DefaultPrettyPrinter { + private static final long serialVersionUID = 1L; + private final boolean spaceBeforeSeparator; + + public SpotlessJsonPrettyPrinter(boolean spaceBeforeSeparator) { + this.spaceBeforeSeparator = spaceBeforeSeparator; + } + + @Override + public DefaultPrettyPrinter createInstance() { + return new SpotlessJsonPrettyPrinter(spaceBeforeSeparator); + } + + @Override + public DefaultPrettyPrinter withSeparators(Separators separators) { + this._separators = separators; + if (spaceBeforeSeparator) { + // This is Jackson default behavior + this._objectFieldValueSeparatorWithSpaces = " " + separators.getObjectFieldValueSeparator() + " "; + } else { + this._objectFieldValueSeparatorWithSpaces = separators.getObjectFieldValueSeparator() + " "; + } + return this; + } + } +} diff --git a/lib/src/jackson/java/com/diffplug/spotless/glue/yaml/JacksonYamlFormatterFunc.java b/lib/src/jackson/java/com/diffplug/spotless/glue/yaml/JacksonYamlFormatterFunc.java new file mode 100644 index 0000000000..b4f8ca6aee --- /dev/null +++ b/lib/src/jackson/java/com/diffplug/spotless/glue/yaml/JacksonYamlFormatterFunc.java @@ -0,0 +1,83 @@ +/* + * Copyright 2021-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.glue.yaml; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.List; + +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactoryBuilder; +import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; + +import com.diffplug.spotless.glue.json.AJacksonFormatterFunc; +import com.diffplug.spotless.yaml.JacksonYamlConfig; + +public class JacksonYamlFormatterFunc extends AJacksonFormatterFunc { + final JacksonYamlConfig yamlConfig; + + public JacksonYamlFormatterFunc(JacksonYamlConfig jacksonConfig) { + super(jacksonConfig); + this.yamlConfig = jacksonConfig; + + if (jacksonConfig == null) { + throw new IllegalArgumentException("ARG"); + } + } + + protected JsonFactory makeJsonFactory() { + YAMLFactoryBuilder yamlFactoryBuilder = new YAMLFactoryBuilder(new YAMLFactory()); + + // Configure the ObjectMapper + // https://github.com/FasterXML/jackson-databind#commonly-used-features + yamlConfig.getYamlFeatureToToggle().forEach((rawFeature, toggle) -> { + // https://stackoverflow.com/questions/3735927/java-instantiating-an-enum-using-reflection + YAMLGenerator.Feature feature = YAMLGenerator.Feature.valueOf(rawFeature); + + yamlFactoryBuilder.configure(feature, toggle); + }); + + return yamlFactoryBuilder.build(); + } + + @Override + protected Class inferType(String input) { + return JsonNode.class; + } + + @Override + protected String format(ObjectMapper objectMapper, String input) throws IllegalArgumentException, IOException { + try { + // https://stackoverflow.com/questions/25222327/deserialize-pojos-from-multiple-yaml-documents-in-a-single-file-in-jackson + // https://github.com/FasterXML/jackson-dataformats-text/issues/66#issuecomment-375328648 + JsonParser yamlParser = objectMapper.getFactory().createParser(input); + List documents = objectMapper.readValues(yamlParser, inferType(input)).readAll(); + + // https://github.com/FasterXML/jackson-dataformats-text/issues/66#issuecomment-554265055 + // https://github.com/FasterXML/jackson-dataformats-text/issues/66#issuecomment-554265055 + StringWriter stringWriter = new StringWriter(); + objectMapper.writer().writeValues(stringWriter).writeAll(documents).close(); + return stringWriter.toString(); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("Unable to format. input='" + input + "'", e); + } + } +} diff --git a/lib/src/ktfmt/java/com/diffplug/spotless/glue/ktfmt/KtfmtFormatterFunc.java b/lib/src/ktfmt/java/com/diffplug/spotless/glue/ktfmt/KtfmtFormatterFunc.java index 72e0e50e3c..80f4a42fcd 100644 --- a/lib/src/ktfmt/java/com/diffplug/spotless/glue/ktfmt/KtfmtFormatterFunc.java +++ b/lib/src/ktfmt/java/com/diffplug/spotless/glue/ktfmt/KtfmtFormatterFunc.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 DiffPlug + * Copyright 2022-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -78,7 +78,7 @@ private FormattingOptions createFormattingOptions() { formattingOptions.getStyle(), ktfmtFormattingOptions.getMaxWidth().orElse(formattingOptions.getMaxWidth()), ktfmtFormattingOptions.getBlockIndent().orElse(formattingOptions.getBlockIndent()), - ktfmtFormattingOptions.getContinuationIndent().orElse(formattingOptions.getBlockIndent()), + ktfmtFormattingOptions.getContinuationIndent().orElse(formattingOptions.getContinuationIndent()), ktfmtFormattingOptions.getRemoveUnusedImport().orElse(formattingOptions.getRemoveUnusedImports()), formattingOptions.getDebuggingPrintOpsAfterFormatting()); } diff --git a/lib/src/ktlint/java/com/diffplug/spotless/glue/ktlint/KtlintFormatterFunc.java b/lib/src/ktlint/java/com/diffplug/spotless/glue/ktlint/KtlintFormatterFunc.java index cc64eb9d66..b81e107609 100644 --- a/lib/src/ktlint/java/com/diffplug/spotless/glue/ktlint/KtlintFormatterFunc.java +++ b/lib/src/ktlint/java/com/diffplug/spotless/glue/ktlint/KtlintFormatterFunc.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,58 +16,58 @@ package com.diffplug.spotless.glue.ktlint; import java.io.File; +import java.nio.file.Path; import java.util.Map; -import org.jetbrains.annotations.NotNull; - +import com.diffplug.spotless.FileSignature; import com.diffplug.spotless.FormatterFunc; -import com.diffplug.spotless.glue.ktlint.compat.KtLintCompat0Dot31Dot0Adapter; -import com.diffplug.spotless.glue.ktlint.compat.KtLintCompat0Dot32Dot0Adapter; -import com.diffplug.spotless.glue.ktlint.compat.KtLintCompat0Dot34Dot2Adapter; -import com.diffplug.spotless.glue.ktlint.compat.KtLintCompat0Dot45Dot2Adapter; -import com.diffplug.spotless.glue.ktlint.compat.KtLintCompat0Dot46Dot0Adapter; -import com.diffplug.spotless.glue.ktlint.compat.KtLintCompat0Dot47Dot0Adapter; -import com.diffplug.spotless.glue.ktlint.compat.KtLintCompatAdapter; +import com.diffplug.spotless.glue.ktlint.compat.*; public class KtlintFormatterFunc implements FormatterFunc.NeedsFile { - - private final Map userData; - private final boolean isScript; - @NotNull private final KtLintCompatAdapter adapter; - private final boolean useExperimental; + private final FileSignature editorConfigPath; private final Map editorConfigOverrideMap; - public KtlintFormatterFunc(String version, boolean isScript, boolean useExperimental, Map userData, + public KtlintFormatterFunc( + String version, + FileSignature editorConfigPath, Map editorConfigOverrideMap) { - int minorVersion = Integer.parseInt(version.split("\\.")[1]); - if (minorVersion >= 47) { - // rename RuleSet to RuleProvider - this.adapter = new KtLintCompat0Dot47Dot0Adapter(); - } else if (minorVersion >= 46) { - // DefaultEditorConfigProperties.INSTANCE.getDefaultEditorConfigProperties() renamed to .getEditorConfigProperties() - this.adapter = new KtLintCompat0Dot46Dot0Adapter(); - } else if (version.equals("0.45.2")) { - // add editorConfigOverride - this.adapter = new KtLintCompat0Dot45Dot2Adapter(); - } else if (minorVersion >= 34) { - // KtLint.INSTANCE.format() now needs more parameters - this.adapter = new KtLintCompat0Dot34Dot2Adapter(); - } else if (minorVersion >= 32) { - // rename packages from `com.github.shyiko` to `com.pinterest` - this.adapter = new KtLintCompat0Dot32Dot0Adapter(); + String[] versions = version.split("\\."); + int majorVersion = Integer.parseInt(versions[0]); + int minorVersion = Integer.parseInt(versions[1]); + if (majorVersion == 1) { + this.adapter = new KtLintCompat1Dot0Dot0Adapter(); } else { - // the OG - this.adapter = new KtLintCompat0Dot31Dot0Adapter(); + if (minorVersion >= 50) { + // Fixed `RuleId` and `RuleSetId` issues + // New argument to `EditorConfigDefaults.Companion.load(...)` for custom property type parsing + // New argument to `new KtLintRuleEngine(...)` to fail on usage of `treeCopyHandler` extension point + this.adapter = new KtLintCompat0Dot50Dot0Adapter(); + } else if (minorVersion == 49) { + // Packages and modules moved around (`ktlint-core` -> `ktlint-rule-engine`) + // Experimental ruleset was replaced by implementing `Rule.Experimental` and checking the `ktlint_experimental` `.editorconfig` property + // `RuleId` and `RuleSetId` became inline classes (mangled to be unrepresentable in Java source code, so reflection is needed), tracked here: https://github.com/pinterest/ktlint/issues/2041 + this.adapter = new KtLintCompat0Dot49Dot0Adapter(); + } else if (minorVersion == 48) { + // ExperimentalParams lost two constructor arguments, EditorConfigProperty moved to its own class + this.adapter = new KtLintCompat0Dot48Dot0Adapter(); + } else { + throw new IllegalStateException("Ktlint versions < 0.48.0 not supported!"); + } } - this.useExperimental = useExperimental; + this.editorConfigPath = editorConfigPath; this.editorConfigOverrideMap = editorConfigOverrideMap; - this.userData = userData; - this.isScript = isScript; } @Override - public String applyWithFile(String unix, File file) throws Exception { - return adapter.format(unix, file.getName(), isScript, useExperimental, userData, editorConfigOverrideMap); + public String applyWithFile(String unix, File file) { + Path absoluteEditorConfigPath = null; + if (editorConfigPath != null) { + absoluteEditorConfigPath = editorConfigPath.getOnlyFile().toPath(); + } + return adapter.format( + file.toPath(), + absoluteEditorConfigPath, + editorConfigOverrideMap); } } diff --git a/lib/src/main/java/com/diffplug/spotless/EncodingErrorMsg.java b/lib/src/main/java/com/diffplug/spotless/EncodingErrorMsg.java index 168bf9acff..3e4bb3423b 100644 --- a/lib/src/main/java/com/diffplug/spotless/EncodingErrorMsg.java +++ b/lib/src/main/java/com/diffplug/spotless/EncodingErrorMsg.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,6 @@ */ package com.diffplug.spotless; -import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; @@ -116,8 +115,8 @@ private static void addIfAvailable(Collection charsets, String name) { } private void appendExample(Charset charset, boolean must) { - java8fix(byteBuf).clear(); - java8fix(charBuf).clear(); + byteBuf.clear(); + charBuf.clear(); CharsetDecoder decoder = charset.newDecoder(); if (!must) { @@ -135,7 +134,7 @@ private void appendExample(Charset charset, boolean must) { .onUnmappableCharacter(CodingErrorAction.REPLACE) .decode(byteBuf, charBuf, true); } - java8fix(charBuf).flip(); + charBuf.flip(); int start = Math.max(unrepresentable - CONTEXT, 0); int end = Math.min(charBuf.limit(), unrepresentable + CONTEXT + 1); @@ -147,9 +146,4 @@ private void appendExample(Charset charset, boolean must) { message.append(" <- "); message.append(charset.name()); } - - /** Fixes https://jira.mongodb.org/browse/JAVA-2559, as reported in https://github.com/diffplug/spotless/issues/1081 */ - private static Buffer java8fix(Buffer b) { - return b; - } } diff --git a/lib/src/main/java/com/diffplug/spotless/FeatureClassLoader.java b/lib/src/main/java/com/diffplug/spotless/FeatureClassLoader.java index b4a69bef11..0eb5aa4d2e 100644 --- a/lib/src/main/java/com/diffplug/spotless/FeatureClassLoader.java +++ b/lib/src/main/java/com/diffplug/spotless/FeatureClassLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,8 +24,6 @@ import java.security.ProtectionDomain; import java.util.Objects; -import javax.annotation.Nullable; - /** * This class loader is used to load classes of Spotless features from a search * path of URLs.
@@ -36,7 +34,7 @@ * but linked against the `Url[]`. This allows us to ship classfiles which function as glue * code but delay linking/definition to runtime after the user has specified which version * of the formatter they want. - * + *

* For `"org.slf4j.` and (`com.diffplug.spotless.` but not `com.diffplug.spotless.extra.`) * the classes are loaded from the buildToolClassLoader. */ @@ -64,7 +62,7 @@ class FeatureClassLoader extends URLClassLoader { @Override protected Class findClass(String name) throws ClassNotFoundException { - if (name.startsWith("com.diffplug.spotless.glue.")) { + if (name.startsWith("com.diffplug.spotless.glue.") || name.startsWith("com.diffplug.spotless.extra.glue.")) { String path = name.replace('.', '/') + ".class"; URL url = findResource(path); if (url == null) { @@ -103,35 +101,14 @@ public URL findResource(String name) { private static ByteBuffer urlToByteBuffer(URL url) throws IOException { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - int nRead; - byte[] data = new byte[1024]; - InputStream inputStream = url.openStream(); - while ((nRead = inputStream.read(data, 0, data.length)) != -1) { - buffer.write(data, 0, nRead); + try (InputStream inputStream = url.openStream()) { + inputStream.transferTo(buffer); } buffer.flush(); return ByteBuffer.wrap(buffer.toByteArray()); } - /** - * Making spotless Java 9+ compatible. In Java 8 (and minor) the bootstrap - * class loader saw every platform class. In Java 9+ it was changed so the - * bootstrap class loader does not see all classes anymore. This might lead - * to ClassNotFoundException in formatters (e.g. freshmark). - * - * @return null on Java 8 (and minor), otherwise PlatformClassLoader - */ - @Nullable private static ClassLoader getParentClassLoader() { - double version = Double.parseDouble(System.getProperty("java.specification.version")); - if (version > 1.8) { - try { - return (ClassLoader) ClassLoader.class.getMethod("getPlatformClassLoader").invoke(null); - } catch (Exception e) { - throw ThrowingEx.asRuntime(e); - } - } else { - return null; - } + return ThrowingEx.get(() -> (ClassLoader) ClassLoader.class.getMethod("getPlatformClassLoader").invoke(null)); } } diff --git a/lib/src/main/java/com/diffplug/spotless/FilterByContentPatternFormatterStep.java b/lib/src/main/java/com/diffplug/spotless/FilterByContentPatternFormatterStep.java index d9a6fe4093..4cc336e101 100644 --- a/lib/src/main/java/com/diffplug/spotless/FilterByContentPatternFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/FilterByContentPatternFormatterStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,10 +23,12 @@ import javax.annotation.Nullable; final class FilterByContentPatternFormatterStep extends DelegateFormatterStep { + final OnMatch onMatch; final Pattern contentPattern; - FilterByContentPatternFormatterStep(FormatterStep delegateStep, String contentPattern) { + FilterByContentPatternFormatterStep(FormatterStep delegateStep, OnMatch onMatch, String contentPattern) { super(delegateStep); + this.onMatch = onMatch; this.contentPattern = Pattern.compile(Objects.requireNonNull(contentPattern)); } @@ -35,7 +37,7 @@ final class FilterByContentPatternFormatterStep extends DelegateFormatterStep { Objects.requireNonNull(raw, "raw"); Objects.requireNonNull(file, "file"); Matcher matcher = contentPattern.matcher(raw); - if (matcher.find()) { + if (matcher.find() == (onMatch == OnMatch.INCLUDE)) { return delegateStep.format(raw, file); } else { return raw; @@ -52,13 +54,14 @@ public boolean equals(Object o) { } FilterByContentPatternFormatterStep that = (FilterByContentPatternFormatterStep) o; return Objects.equals(delegateStep, that.delegateStep) && + Objects.equals(onMatch, that.onMatch) && Objects.equals(contentPattern.pattern(), that.contentPattern.pattern()); } @Override public int hashCode() { - return Objects.hash(delegateStep, contentPattern.pattern()); + return Objects.hash(delegateStep, onMatch, contentPattern.pattern()); } - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 2L; } diff --git a/lib/src/main/java/com/diffplug/spotless/ForeignExe.java b/lib/src/main/java/com/diffplug/spotless/ForeignExe.java index d1f8abd3ee..4cb15d1a8a 100644 --- a/lib/src/main/java/com/diffplug/spotless/ForeignExe.java +++ b/lib/src/main/java/com/diffplug/spotless/ForeignExe.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ * Finds a foreign executable and checks its version. * If either part of that fails, it shows you why * and helps you fix it. - * + *

* Usage: {@code ForeignExe.nameAndVersion("grep", "2.5.7").confirmVersionAndGetAbsolutePath()} * will find grep, confirm that it is version 2.5.7, and then return. */ diff --git a/lib/src/main/java/com/diffplug/spotless/FormatExceptionPolicy.java b/lib/src/main/java/com/diffplug/spotless/FormatExceptionPolicy.java index da23f74432..50f49e41db 100644 --- a/lib/src/main/java/com/diffplug/spotless/FormatExceptionPolicy.java +++ b/lib/src/main/java/com/diffplug/spotless/FormatExceptionPolicy.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ public interface FormatExceptionPolicy extends Serializable, NoLambda { /** * Returns a byte array representation of everything inside this {@code FormatExceptionPolicy}. - * + *

* The main purpose of this method is to ensure one can't instantiate this class with lambda * expressions, which are notoriously difficult to serialize and deserialize properly. */ diff --git a/lib/src/main/java/com/diffplug/spotless/Formatter.java b/lib/src/main/java/com/diffplug/spotless/Formatter.java index 038e77fa64..68d9c443bf 100644 --- a/lib/src/main/java/com/diffplug/spotless/Formatter.java +++ b/lib/src/main/java/com/diffplug/spotless/Formatter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,13 +39,16 @@ public final class Formatter implements Serializable, AutoCloseable { private static final long serialVersionUID = 1L; + // The name is used for logging purpose. It does not convey any applicative purpose + private String name; private LineEnding.Policy lineEndingsPolicy; private Charset encoding; private Path rootDir; private List steps; private FormatExceptionPolicy exceptionPolicy; - private Formatter(LineEnding.Policy lineEndingsPolicy, Charset encoding, Path rootDirectory, List steps, FormatExceptionPolicy exceptionPolicy) { + private Formatter(String name, LineEnding.Policy lineEndingsPolicy, Charset encoding, Path rootDirectory, List steps, FormatExceptionPolicy exceptionPolicy) { + this.name = name; this.lineEndingsPolicy = Objects.requireNonNull(lineEndingsPolicy, "lineEndingsPolicy"); this.encoding = Objects.requireNonNull(encoding, "encoding"); this.rootDir = Objects.requireNonNull(rootDirectory, "rootDir"); @@ -55,6 +58,7 @@ private Formatter(LineEnding.Policy lineEndingsPolicy, Charset encoding, Path ro // override serialize output private void writeObject(ObjectOutputStream out) throws IOException { + out.writeObject(name); out.writeObject(lineEndingsPolicy); out.writeObject(encoding.name()); out.writeObject(rootDir.toString()); @@ -65,6 +69,7 @@ private void writeObject(ObjectOutputStream out) throws IOException { // override serialize input @SuppressWarnings("unchecked") private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + name = (String) in.readObject(); lineEndingsPolicy = (LineEnding.Policy) in.readObject(); encoding = Charset.forName((String) in.readObject()); rootDir = Paths.get((String) in.readObject()); @@ -78,6 +83,10 @@ private void readObjectNoData() throws ObjectStreamException { throw new UnsupportedOperationException(); } + public String getName() { + return name; + } + public LineEnding.Policy getLineEndingsPolicy() { return lineEndingsPolicy; } @@ -103,6 +112,8 @@ public static Formatter.Builder builder() { } public static class Builder { + // optional parameters + private String name = "unnamed"; // required parameters private LineEnding.Policy lineEndingsPolicy; private Charset encoding; @@ -112,6 +123,11 @@ public static class Builder { private Builder() {} + public Builder name(String name) { + this.name = name; + return this; + } + public Builder lineEndingsPolicy(LineEnding.Policy lineEndingsPolicy) { this.lineEndingsPolicy = lineEndingsPolicy; return this; @@ -138,7 +154,7 @@ public Builder exceptionPolicy(FormatExceptionPolicy exceptionPolicy) { } public Formatter build() { - return new Formatter(lineEndingsPolicy, encoding, rootDir, steps, + return new Formatter(name, lineEndingsPolicy, encoding, rootDir, steps, exceptionPolicy == null ? FormatExceptionPolicy.failOnlyOnError() : exceptionPolicy); } } @@ -177,7 +193,7 @@ public void applyTo(File file) throws IOException { /** * Applies formatting to the given file. - * + *

* Returns null if the file was already clean, or the * formatted result with unix newlines if it was not. */ @@ -237,8 +253,13 @@ public String compute(String unix, File file) { unix = LineEnding.toUnix(formatted); } } catch (Throwable e) { - String relativePath = rootDir.relativize(file.toPath()).toString(); - exceptionPolicy.handleError(e, step, relativePath); + if (file == NO_FILE_SENTINEL) { + exceptionPolicy.handleError(e, step, ""); + } else { + // Path may be forged from a different FileSystem than Filesystem.default + String relativePath = rootDir.relativize(rootDir.getFileSystem().getPath(file.getPath())).toString(); + exceptionPolicy.handleError(e, step, relativePath); + } } } return unix; @@ -248,6 +269,7 @@ public String compute(String unix, File file) { public int hashCode() { final int prime = 31; int result = 1; + result = prime * result + name.hashCode(); result = prime * result + encoding.hashCode(); result = prime * result + lineEndingsPolicy.hashCode(); result = prime * result + rootDir.hashCode(); @@ -268,7 +290,8 @@ public boolean equals(Object obj) { return false; } Formatter other = (Formatter) obj; - return encoding.equals(other.encoding) && + return name.equals(other.name) && + encoding.equals(other.encoding) && lineEndingsPolicy.equals(other.lineEndingsPolicy) && rootDir.equals(other.rootDir) && steps.equals(other.steps) && @@ -284,4 +307,7 @@ public void close() { } } } + + /** This Sentinel reference may be used to pass string content to a Formatter or FormatterStep when there is no actual File to format */ + public static final File NO_FILE_SENTINEL = new File("NO_FILE_SENTINEL"); } diff --git a/lib/src/main/java/com/diffplug/spotless/FormatterFunc.java b/lib/src/main/java/com/diffplug/spotless/FormatterFunc.java index 48a8e810ee..acd05e031b 100644 --- a/lib/src/main/java/com/diffplug/spotless/FormatterFunc.java +++ b/lib/src/main/java/com/diffplug/spotless/FormatterFunc.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,11 +42,11 @@ interface Closeable extends FormatterFunc, AutoCloseable { /** * Dangerous way to create a {@link Closeable} from an AutoCloseable and a function. - * + *

* It's important for FormatterStep's to allocate their resources as lazily as possible. * It's easy to create a resource inside the state, and not realize that it may not be * released. It's far better to use one of the non-deprecated {@code of()} methods below. - * + *

* The bug (and its fix) which is easy to write using this method: https://github.com/diffplug/spotless/commit/7f16ecca031810b5e6e6f647e1f10a6d2152d9f4 * How the {@code of()} methods below make the correct thing easier to write and safer: https://github.com/diffplug/spotless/commit/18c10f9c93d6f18f753233d0b5f028d5f0961916 */ @@ -127,7 +127,7 @@ public String apply(String unix, File file) throws Exception { @Override public String apply(String unix) throws Exception { - return apply(unix, FormatterStepImpl.SENTINEL); + return apply(unix, Formatter.NO_FILE_SENTINEL); } }; } @@ -156,7 +156,7 @@ default String apply(String unix, File file) throws Exception { @Override default String apply(String unix) throws Exception { - return apply(unix, FormatterStepImpl.SENTINEL); + return apply(unix, Formatter.NO_FILE_SENTINEL); } } } diff --git a/lib/src/main/java/com/diffplug/spotless/FormatterStep.java b/lib/src/main/java/com/diffplug/spotless/FormatterStep.java index 5729f676b2..ca37303b1f 100644 --- a/lib/src/main/java/com/diffplug/spotless/FormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/FormatterStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ /** * An implementation of this class specifies a single step in a formatting process. - * + *

* The input is guaranteed to have unix-style newlines, and the output is required * to not introduce any windows-style newlines as well. */ @@ -37,8 +37,8 @@ public interface FormatterStep extends Serializable { * @param rawUnix * the content to format, guaranteed to have unix-style newlines ('\n'); never null * @param file - * the file which {@code rawUnix} was obtained from; never null. Pass an empty file using - * {@code new File("")} if and only if no file is actually associated with {@code rawUnix} + * the file which {@code rawUnix} was obtained from; never null. Pass the reference + * {@link Formatter#NO_FILE_SENTINEL} if and only if no file is actually associated with {@code rawUnix} * @return the formatted content, guaranteed to only have unix-style newlines; may return null * if the formatter step doesn't have any changes to make * @throws Exception if the formatter step experiences a problem @@ -53,14 +53,29 @@ public interface FormatterStep extends Serializable { * java regular expression used to filter out files which content doesn't contain pattern * @return FormatterStep */ + @Deprecated public default FormatterStep filterByContentPattern(String contentPattern) { - return new FilterByContentPatternFormatterStep(this, contentPattern); + return filterByContent(OnMatch.INCLUDE, contentPattern); + } + + /** + * Returns a new {@code FormatterStep} which, observing the value of {@code formatIfMatches}, + * will only apply, or not, its changes to files which pass the given filter. + * + * @param onMatch + * determines if matches are included or excluded + * @param contentPattern + * java regular expression used to filter in or out files which content contain pattern + * @return FormatterStep + */ + public default FormatterStep filterByContent(OnMatch onMatch, String contentPattern) { + return new FilterByContentPatternFormatterStep(this, onMatch, contentPattern); } /** * Returns a new FormatterStep which will only apply its changes * to files which pass the given filter. - * + *

* The provided filter must be serializable. */ public default FormatterStep filterByFile(SerializableFileFilter filter) { @@ -71,7 +86,7 @@ public default FormatterStep filterByFile(SerializableFileFilter filter) { * Implements a FormatterStep in a strict way which guarantees correct and lazy implementation * of up-to-date checks. This maximizes performance for cases where the FormatterStep is not * actually needed (e.g. don't load eclipse setting file unless this step is actually running) - * while also ensuring that gradle can detect changes in a step's settings to determine that + * while also ensuring that Gradle can detect changes in a step's settings to determine that * it needs to rerun a format. */ abstract class Strict extends LazyForwardingEquality implements FormatterStep { diff --git a/lib/src/main/java/com/diffplug/spotless/FormatterStepImpl.java b/lib/src/main/java/com/diffplug/spotless/FormatterStepImpl.java index 7663145f51..869e7b7966 100644 --- a/lib/src/main/java/com/diffplug/spotless/FormatterStepImpl.java +++ b/lib/src/main/java/com/diffplug/spotless/FormatterStepImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ /** * Standard implementation of FormatExtension which cleanly enforces * separation of serializable configuration and a pure format function. - * + *

* Not an inner-class of FormatterStep so that it can stay entirely private * from the API. */ @@ -116,11 +116,8 @@ protected String format(Integer state, String rawUnix, File file) throws Excepti } } - /** A dummy SENTINEL file. */ - static final File SENTINEL = new File(""); - static void checkNotSentinel(File file) { - if (file == SENTINEL) { + if (file == Formatter.NO_FILE_SENTINEL) { throw new IllegalArgumentException("This step requires the underlying file. If this is a test, use StepHarnessWithFile"); } } diff --git a/lib/src/main/java/com/diffplug/spotless/JarState.java b/lib/src/main/java/com/diffplug/spotless/JarState.java index abe595e608..48686c49f8 100644 --- a/lib/src/main/java/com/diffplug/spotless/JarState.java +++ b/lib/src/main/java/com/diffplug/spotless/JarState.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,18 +32,19 @@ * Grabs a jar and its dependencies from maven, * and makes it easy to access the collection in * a classloader. - * + *

* Serializes the full state of the jar, so it can * catch changes in a SNAPSHOT version. */ public final class JarState implements Serializable { private static final long serialVersionUID = 1L; + @Deprecated private final Set mavenCoordinates; private final FileSignature fileSignature; private JarState(Collection mavenCoordinates, FileSignature fileSignature) { - this.mavenCoordinates = new TreeSet(mavenCoordinates); + this.mavenCoordinates = new TreeSet<>(mavenCoordinates); this.fileSignature = fileSignature; } @@ -73,6 +74,12 @@ private static JarState provisionWithTransitives(boolean withTransitives, Collec return new JarState(mavenCoordinates, fileSignature); } + /** Wraps the given collection of a files as a JarState, maintaining the order in the Collection. */ + public static JarState preserveOrder(Collection jars) throws IOException { + FileSignature fileSignature = FileSignature.signAsList(jars); + return new JarState(Collections.emptySet(), fileSignature); + } + URL[] jarUrls() { return fileSignature.files().stream().map(File::toURI).map(ThrowingEx.wrap(URI::toURL)).toArray(URL[]::new); } @@ -102,6 +109,7 @@ public ClassLoader getClassLoader(Serializable key) { } /** Returns unmodifiable view on sorted Maven coordinates */ + @Deprecated public Set getMavenCoordinates() { return Collections.unmodifiableSet(mavenCoordinates); } diff --git a/lib/src/main/java/com/diffplug/spotless/Jvm.java b/lib/src/main/java/com/diffplug/spotless/Jvm.java index 14686c1e4c..880575c3b8 100644 --- a/lib/src/main/java/com/diffplug/spotless/Jvm.java +++ b/lib/src/main/java/com/diffplug/spotless/Jvm.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,8 +63,9 @@ public static int version() { public static class Support { private final String fmtName; private final Comparator fmtVersionComparator; - private final NavigableMap jvm2fmtVersion; - private final NavigableMap fmt2jvmVersion; + private final NavigableMap jvm2fmtMaxVersion; + private final NavigableMap jvm2fmtMinVersion; + private final NavigableMap fmtMaxVersion2jvmVersion; private Support(String fromatterName) { this(fromatterName, new SemanticVersionComparator()); @@ -73,40 +74,60 @@ private Support(String fromatterName) { private Support(String formatterName, Comparator formatterVersionComparator) { fmtName = formatterName; fmtVersionComparator = formatterVersionComparator; - jvm2fmtVersion = new TreeMap(); - fmt2jvmVersion = new TreeMap(formatterVersionComparator); + jvm2fmtMaxVersion = new TreeMap<>(); + jvm2fmtMinVersion = new TreeMap<>(); + fmtMaxVersion2jvmVersion = new TreeMap<>(formatterVersionComparator); } /** - * Add supported formatter version + * Add maximum supported formatter version * @param minimumJvmVersion Minimum Java version required * @param maxFormatterVersion Maximum formatter version supported by the Java version * @return this */ public Support add(int minimumJvmVersion, V maxFormatterVersion) { Objects.requireNonNull(maxFormatterVersion); - if (null != jvm2fmtVersion.put(minimumJvmVersion, maxFormatterVersion)) { + if (null != jvm2fmtMaxVersion.put(minimumJvmVersion, maxFormatterVersion)) { throw new IllegalArgumentException(String.format("Added duplicate entry for JVM %d+.", minimumJvmVersion)); } - if (null != fmt2jvmVersion.put(maxFormatterVersion, minimumJvmVersion)) { + if (null != fmtMaxVersion2jvmVersion.put(maxFormatterVersion, minimumJvmVersion)) { throw new IllegalArgumentException(String.format("Added duplicate entry for formatter version %s.", maxFormatterVersion)); } + verifyVersionRangesDoNotIntersect(jvm2fmtMaxVersion, minimumJvmVersion, maxFormatterVersion); + return this; + } + + public Support addMin(int minimumJvmVersion, V minFormatterVersion) { + Objects.requireNonNull(minFormatterVersion); + if (null != jvm2fmtMinVersion.put(minimumJvmVersion, minFormatterVersion)) { + throw new IllegalArgumentException(String.format("Added duplicate entry for JVM %d+.", minimumJvmVersion)); + } + verifyVersionRangesDoNotIntersect(jvm2fmtMinVersion, minimumJvmVersion, minFormatterVersion); + return this; + } + + private void verifyVersionRangesDoNotIntersect(NavigableMap jvm2fmtVersion, int minimumJvmVersion, V formatterVersion) { Map.Entry lower = jvm2fmtVersion.lowerEntry(minimumJvmVersion); - if ((null != lower) && (fmtVersionComparator.compare(maxFormatterVersion, lower.getValue()) <= 0)) { - throw new IllegalArgumentException(String.format("%d/%s should be lower than %d/%s", minimumJvmVersion, maxFormatterVersion, lower.getKey(), lower.getValue())); + if ((null != lower) && (fmtVersionComparator.compare(formatterVersion, lower.getValue()) <= 0)) { + throw new IllegalArgumentException(String.format("%d/%s should be lower than %d/%s", minimumJvmVersion, formatterVersion, lower.getKey(), lower.getValue())); } Map.Entry higher = jvm2fmtVersion.higherEntry(minimumJvmVersion); - if ((null != higher) && (fmtVersionComparator.compare(maxFormatterVersion, higher.getValue()) >= 0)) { - throw new IllegalArgumentException(String.format("%d/%s should be higher than %d/%s", minimumJvmVersion, maxFormatterVersion, higher.getKey(), higher.getValue())); + if ((null != higher) && (fmtVersionComparator.compare(formatterVersion, higher.getValue()) >= 0)) { + throw new IllegalArgumentException(String.format("%d/%s should be higher than %d/%s", minimumJvmVersion, formatterVersion, higher.getKey(), higher.getValue())); } - return this; } /** @return Highest formatter version recommended for this JVM (null, if JVM not supported) */ @Nullable public V getRecommendedFormatterVersion() { - Integer configuredJvmVersionOrNull = jvm2fmtVersion.floorKey(Jvm.version()); - return (null == configuredJvmVersionOrNull) ? null : jvm2fmtVersion.get(configuredJvmVersionOrNull); + Integer configuredJvmVersionOrNull = jvm2fmtMaxVersion.floorKey(Jvm.version()); + return (null == configuredJvmVersionOrNull) ? null : jvm2fmtMaxVersion.get(configuredJvmVersionOrNull); + } + + @Nullable + public V getMinimumRequiredFormatterVersion() { + Integer configuredJvmVersionOrNull = jvm2fmtMinVersion.floorKey(Jvm.version()); + return (null == configuredJvmVersionOrNull) ? null : jvm2fmtMinVersion.get(configuredJvmVersionOrNull); } /** @@ -123,10 +144,17 @@ public void assertFormatterSupported(V formatterVersion) { } private String buildUnsupportedFormatterMessage(V fmtVersion) { + // check if the jvm version is to low for the formatter version int requiredJvmVersion = getRequiredJvmVersion(fmtVersion); if (Jvm.version() < requiredJvmVersion) { return buildUpgradeJvmMessage(fmtVersion) + "Upgrade your JVM or try " + toString(); } + // check if the formatter version is too low for the jvm version + V minimumFormatterVersion = getMinimumRequiredFormatterVersion(); + if ((null != minimumFormatterVersion) && (fmtVersionComparator.compare(fmtVersion, minimumFormatterVersion) < 0)) { + return String.format("You are running Spotless on JVM %d. This requires %s of at least %s (you are using %s).%n", Jvm.version(), fmtName, minimumFormatterVersion, fmtVersion); + } + // otherwise all is well return ""; } @@ -137,7 +165,7 @@ private String buildUpgradeJvmMessage(V fmtVersion) { if (null != recommendedFmtVersionOrNull) { builder.append(String.format(", which limits you to %s %s.%n", fmtName, recommendedFmtVersionOrNull)); } else { - Entry nextFmtVersionOrNull = fmt2jvmVersion.ceilingEntry(fmtVersion); + Entry nextFmtVersionOrNull = fmtMaxVersion2jvmVersion.ceilingEntry(fmtVersion); if (null != nextFmtVersionOrNull) { builder.append(String.format(". %s %s requires JVM %d+", fmtName, fmtVersion, nextFmtVersionOrNull.getValue())); } @@ -147,12 +175,12 @@ private String buildUpgradeJvmMessage(V fmtVersion) { } private int getRequiredJvmVersion(V fmtVersion) { - Entry entry = fmt2jvmVersion.ceilingEntry(fmtVersion); + Entry entry = fmtMaxVersion2jvmVersion.ceilingEntry(fmtVersion); if (null == entry) { - entry = fmt2jvmVersion.lastEntry(); + entry = fmtMaxVersion2jvmVersion.lastEntry(); } if (null != entry) { - V maxKnownFmtVersion = jvm2fmtVersion.get(entry.getValue()); + V maxKnownFmtVersion = jvm2fmtMaxVersion.get(entry.getValue()); if (fmtVersionComparator.compare(fmtVersion, maxKnownFmtVersion) <= 0) { return entry.getValue(); } @@ -170,15 +198,15 @@ public FormatterFunc suggestLaterVersionOnError(V formatterVersion, FormatterFun Objects.requireNonNull(formatterVersion); Objects.requireNonNull(originalFunc); final String hintUnsupportedProblem = buildUnsupportedFormatterMessage(formatterVersion); - final String proposeDiffererntFormatter = hintUnsupportedProblem.isEmpty() ? buildUpgradeFormatterMessage(formatterVersion) : hintUnsupportedProblem; - return proposeDiffererntFormatter.isEmpty() ? originalFunc : new FormatterFunc() { + final String proposeDifferentFormatter = hintUnsupportedProblem.isEmpty() ? buildUpgradeFormatterMessage(formatterVersion) : hintUnsupportedProblem; + return proposeDifferentFormatter.isEmpty() ? originalFunc : new FormatterFunc() { @Override public String apply(String unix, File file) throws Exception { try { return originalFunc.apply(unix, file); } catch (Exception e) { - throw new Exception(proposeDiffererntFormatter, e); + throw new Exception(proposeDifferentFormatter, e); } } @@ -187,7 +215,7 @@ public String apply(String input) throws Exception { try { return originalFunc.apply(input); } catch (Exception e) { - throw new Exception(proposeDiffererntFormatter, e); + throw new Exception(proposeDifferentFormatter, e); } } @@ -196,15 +224,25 @@ public String apply(String input) throws Exception { private String buildUpgradeFormatterMessage(V fmtVersion) { StringBuilder builder = new StringBuilder(); + // check if the formatter is not supported on this jvm + V minimumFormatterVersion = getMinimumRequiredFormatterVersion(); V recommendedFmtVersionOrNull = getRecommendedFormatterVersion(); - if (null != recommendedFmtVersionOrNull && (fmtVersionComparator.compare(fmtVersion, recommendedFmtVersionOrNull) < 0)) { - builder.append(String.format("You are not using latest version on JVM %d+.%n", getRequiredJvmVersion(recommendedFmtVersionOrNull))); - builder.append(String.format("Try to upgrade to %s %s, which may have fixed this problem.", fmtName, getRecommendedFormatterVersion())); + if ((null != minimumFormatterVersion) && (fmtVersionComparator.compare(fmtVersion, minimumFormatterVersion) < 0)) { + builder.append(String.format("You are running Spotless on JVM %d. This requires %s of at least %s.%n", Jvm.version(), fmtName, minimumFormatterVersion)); + builder.append(String.format("You are using %s %s.%n", fmtName, fmtVersion)); + if (null != recommendedFmtVersionOrNull) { + builder.append(String.format("%s %s is the recommended version, which may have fixed this problem.%n", fmtName, recommendedFmtVersionOrNull)); + } + // check if the formatter is outdated on this jvm + } else if (null != recommendedFmtVersionOrNull && (fmtVersionComparator.compare(fmtVersion, recommendedFmtVersionOrNull) < 0)) { + builder.append(String.format("%s %s is currently being used, but outdated.%n", fmtName, fmtVersion)); + builder.append(String.format("%s %s is the recommended version, which may have fixed this problem.%n", fmtName, recommendedFmtVersionOrNull)); + builder.append(String.format("%s %s requires JVM %d+.", fmtName, recommendedFmtVersionOrNull, getRequiredJvmVersion(recommendedFmtVersionOrNull))); } else { - V higherFormatterVersionOrNull = fmt2jvmVersion.higherKey(fmtVersion); + V higherFormatterVersionOrNull = fmtMaxVersion2jvmVersion.higherKey(fmtVersion); if (null != higherFormatterVersionOrNull) { builder.append(buildUpgradeJvmMessage(fmtVersion)); - Integer higherJvmVersion = fmt2jvmVersion.get(higherFormatterVersionOrNull); + Integer higherJvmVersion = fmtMaxVersion2jvmVersion.get(higherFormatterVersionOrNull); builder.append(String.format("If you upgrade your JVM to %d+, then you can use %s %s, which may have fixed this problem.", higherJvmVersion, fmtName, higherFormatterVersionOrNull)); } } @@ -214,7 +252,7 @@ private String buildUpgradeFormatterMessage(V fmtVersion) { @Override public String toString() { return String.format("%s alternatives:%n", fmtName) + - jvm2fmtVersion.entrySet().stream().map( + jvm2fmtMaxVersion.entrySet().stream().map( e -> String.format("- Version %s requires JVM %d+", e.getValue(), e.getKey())).collect(Collectors.joining(System.lineSeparator())); } @@ -242,7 +280,11 @@ public int compare(V version0, V version1) { private static int[] convert(V versionObject) { try { - return Arrays.asList(versionObject.toString().split("\\.")).stream().mapToInt(Integer::parseInt).toArray(); + String versionString = versionObject.toString(); + if (versionString.endsWith("-SNAPSHOT")) { + versionString = versionString.substring(0, versionString.length() - "-SNAPSHOT".length()); + } + return Arrays.asList(versionString.split("\\.")).stream().mapToInt(Integer::parseInt).toArray(); } catch (Exception e) { throw new IllegalArgumentException(String.format("Not a semantic version: %s", versionObject), e); } diff --git a/lib/src/main/java/com/diffplug/spotless/LazyArgLogger.java b/lib/src/main/java/com/diffplug/spotless/LazyArgLogger.java new file mode 100644 index 0000000000..e3456a2317 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/LazyArgLogger.java @@ -0,0 +1,40 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless; + +import java.util.function.Supplier; + +/** + * This is a utility class to allow for lazy evaluation of arguments to be passed to a logger + * and thus avoid unnecessary computation of the arguments if the log level is not enabled. + */ +public final class LazyArgLogger { + + private final Supplier argSupplier; + + private LazyArgLogger(Supplier argSupplier) { + this.argSupplier = argSupplier; + } + + public static LazyArgLogger lazy(Supplier argSupplier) { + return new LazyArgLogger(argSupplier); + } + + @Override + public String toString() { + return String.valueOf(argSupplier.get()); + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/LazyForwardingEquality.java b/lib/src/main/java/com/diffplug/spotless/LazyForwardingEquality.java index e2d6ba8988..8727be3a3c 100644 --- a/lib/src/main/java/com/diffplug/spotless/LazyForwardingEquality.java +++ b/lib/src/main/java/com/diffplug/spotless/LazyForwardingEquality.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,7 +41,7 @@ public abstract class LazyForwardingEquality implements /** * This function is guaranteed to be called at most once. * If the state is never required, then it will never be called at all. - * + *

* Throws exception because it's likely that there will be some IO going on. */ protected abstract T calculateState() throws Exception; diff --git a/lib/src/main/java/com/diffplug/spotless/LineEnding.java b/lib/src/main/java/com/diffplug/spotless/LineEnding.java index 72b2532f2a..74d9c80618 100644 --- a/lib/src/main/java/com/diffplug/spotless/LineEnding.java +++ b/lib/src/main/java/com/diffplug/spotless/LineEnding.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,11 +19,8 @@ import java.io.Serializable; import java.lang.reflect.Method; import java.util.Objects; -import java.util.function.BiFunction; import java.util.function.Supplier; -import javax.annotation.Nullable; - /** * Represents the line endings which should be written by the tool. */ @@ -37,6 +34,14 @@ public Policy createPolicy() { return super.createPolicy(); } }, + /** Uses the same line endings as Git, and assumes that every single file being formatted will have the same line ending. */ + GIT_ATTRIBUTES_FAST_ALLSAME { + /** .gitattributes is path-specific, so you must use {@link LineEnding#createPolicy(File, Supplier)}. */ + @Override @Deprecated + public Policy createPolicy() { + return super.createPolicy(); + } + }, /** {@code \n} on unix systems, {@code \r\n} on windows systems. */ PLATFORM_NATIVE, /** {@code \r\n} */ @@ -51,25 +56,23 @@ public Policy createPolicy() { public Policy createPolicy(File projectDir, Supplier> toFormat) { Objects.requireNonNull(projectDir, "projectDir"); Objects.requireNonNull(toFormat, "toFormat"); - if (this != GIT_ATTRIBUTES) { - return createPolicy(); + String gitAttributesMethod; + if (this == GIT_ATTRIBUTES) { + gitAttributesMethod = "create"; + } else if (this == GIT_ATTRIBUTES_FAST_ALLSAME) { + gitAttributesMethod = "createFastAllSame"; } else { - if (gitAttributesPolicyCreator == null) { - try { - Class clazz = Class.forName("com.diffplug.spotless.extra.GitAttributesLineEndings"); - Method method = clazz.getMethod("create", File.class, Supplier.class); - gitAttributesPolicyCreator = (proj, target) -> ThrowingEx.get(() -> (Policy) method.invoke(null, proj, target)); - } catch (ClassNotFoundException | NoSuchMethodException | SecurityException e) { - throw new IllegalStateException("LineEnding.GIT_ATTRIBUTES requires the spotless-lib-extra library, but it is not on the classpath", e); - } - } - // gitAttributesPolicyCreator will always be nonnull at this point - return gitAttributesPolicyCreator.apply(projectDir, toFormat); + return createPolicy(); + } + try { + Class clazz = Class.forName("com.diffplug.spotless.extra.GitAttributesLineEndings"); + Method method = clazz.getMethod(gitAttributesMethod, File.class, Supplier.class); + return ThrowingEx.get(() -> (Policy) method.invoke(null, projectDir, toFormat)); + } catch (ClassNotFoundException | NoSuchMethodException | SecurityException e) { + throw new IllegalStateException("LineEnding.GIT_ATTRIBUTES requires the spotless-lib-extra library, but it is not on the classpath", e); } } - private static volatile @Nullable BiFunction>, Policy> gitAttributesPolicyCreator; - // @formatter:off /** Should use {@link #createPolicy(File, Supplier)} instead, but this will work iff its a path-independent LineEnding policy. */ public Policy createPolicy() { @@ -77,7 +80,7 @@ public Policy createPolicy() { case PLATFORM_NATIVE: return _platformNativePolicy; case WINDOWS: return WINDOWS_POLICY; case UNIX: return UNIX_POLICY; - case MAC_CLASSIC: return MAC_CLASSIC_POLICY; + case MAC_CLASSIC: return MAC_CLASSIC_POLICY; default: throw new UnsupportedOperationException(this + " is a path-specific line ending."); } } @@ -121,7 +124,7 @@ public String str() { case PLATFORM_NATIVE: return _platformNative; case WINDOWS: return "\r\n"; case UNIX: return "\n"; - case MAC_CLASSIC: return "\r"; + case MAC_CLASSIC: return "\r"; default: throw new UnsupportedOperationException(this + " is a path-specific line ending."); } } diff --git a/lib/src/main/java/com/diffplug/spotless/NoLambda.java b/lib/src/main/java/com/diffplug/spotless/NoLambda.java index ce326a6ceb..d2ca6c1401 100644 --- a/lib/src/main/java/com/diffplug/spotless/NoLambda.java +++ b/lib/src/main/java/com/diffplug/spotless/NoLambda.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,12 +21,12 @@ /** * Marker interface to prevent lambda implementations of * single-method interfaces that require serializability. - * + *

* In order for Spotless to support up-to-date checks, all * of its parameters must be {@link Serializable} so that * entries can be written to file, and they must implement * equals and hashCode correctly. - * + *

* This interface and its standard implementation, * {@link EqualityBasedOnSerialization}, are a quick way * to accomplish these goals. @@ -34,7 +34,7 @@ public interface NoLambda extends Serializable { /** * Returns a byte array representation of everything inside this {@code SerializableFileFilter}. - * + *

* The main purpose of this method is to ensure one can't instantiate this class with lambda * expressions, which are notoriously difficult to serialize and deserialize properly. (See * {@code SerializableFileFilterImpl.SkipFilesNamed} for an example of how to make a serializable diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/service/package-info.java b/lib/src/main/java/com/diffplug/spotless/OnMatch.java similarity index 79% rename from _ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/service/package-info.java rename to lib/src/main/java/com/diffplug/spotless/OnMatch.java index b444c0c76a..098fb28f13 100644 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/service/package-info.java +++ b/lib/src/main/java/com/diffplug/spotless/OnMatch.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,5 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/** Mocking Eclipse Equinox OSGi services. */ -package com.diffplug.spotless.extra.eclipse.base.service; +package com.diffplug.spotless; + +/** Enum to make boolean logic more readable. */ +public enum OnMatch { + INCLUDE, EXCLUDE +} diff --git a/lib/src/main/java/com/diffplug/spotless/PaddedCell.java b/lib/src/main/java/com/diffplug/spotless/PaddedCell.java index 91914b7908..910fa940d3 100644 --- a/lib/src/main/java/com/diffplug/spotless/PaddedCell.java +++ b/lib/src/main/java/com/diffplug/spotless/PaddedCell.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ /** * Models the result of applying a {@link Formatter} on a given {@link File} * while characterizing various failure modes (slow convergence, cycles, and divergence). - * + *

* See {@link #check(Formatter, File)} as the entry point to this class. */ public final class PaddedCell { @@ -78,9 +78,9 @@ public List steps() { /** * Applies the given formatter to the given file, checking that * F(F(input)) == F(input). - * + *

* If it meets this test, {@link #misbehaved()} will return false. - * + *

* If it fails the test, {@link #misbehaved()} will return true, and you can find * out more about the misbehavior based on its {@link Type}. * diff --git a/lib/src/main/java/com/diffplug/spotless/ProcessRunner.java b/lib/src/main/java/com/diffplug/spotless/ProcessRunner.java index c798ed6c07..48307d7d3a 100644 --- a/lib/src/main/java/com/diffplug/spotless/ProcessRunner.java +++ b/lib/src/main/java/com/diffplug/spotless/ProcessRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,26 +15,35 @@ */ package com.diffplug.spotless; +import static java.util.Objects.requireNonNull; + import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * Shelling out to a process is harder than it ought to be in Java. * If you don't read stdout and stderr on their own threads, you risk * deadlock on a clogged buffer. - * + *

* ProcessRunner allocates two threads specifically for the purpose of * flushing stdout and stderr to buffers. These threads will remain alive until * the ProcessRunner is closed, so it is especially useful for repeated @@ -43,10 +52,21 @@ public class ProcessRunner implements AutoCloseable { private final ExecutorService threadStdOut = Executors.newSingleThreadExecutor(); private final ExecutorService threadStdErr = Executors.newSingleThreadExecutor(); - private final ByteArrayOutputStream bufStdOut = new ByteArrayOutputStream(); - private final ByteArrayOutputStream bufStdErr = new ByteArrayOutputStream(); + private final ByteArrayOutputStream bufStdOut; + private final ByteArrayOutputStream bufStdErr; - public ProcessRunner() {} + public ProcessRunner() { + this(-1); + } + + public static ProcessRunner usingRingBuffersOfCapacity(int limit) { + return new ProcessRunner(limit); + } + + private ProcessRunner(int limitedBuffers) { + this.bufStdOut = limitedBuffers >= 0 ? new RingBufferByteArrayOutputStream(limitedBuffers) : new ByteArrayOutputStream(); + this.bufStdErr = limitedBuffers >= 0 ? new RingBufferByteArrayOutputStream(limitedBuffers) : new ByteArrayOutputStream(); + } /** Executes the given shell command (using {@code cmd} on windows and {@code sh} on unix). */ public Result shell(String cmd) throws IOException, InterruptedException { @@ -55,13 +75,18 @@ public Result shell(String cmd) throws IOException, InterruptedException { /** Executes the given shell command (using {@code cmd} on windows and {@code sh} on unix). */ public Result shellWinUnix(String cmdWin, String cmdUnix) throws IOException, InterruptedException { + return shellWinUnix(null, null, cmdWin, cmdUnix); + } + + /** Executes the given shell command (using {@code cmd} on windows and {@code sh} on unix). */ + public Result shellWinUnix(@Nullable File cwd, @Nullable Map environment, String cmdWin, String cmdUnix) throws IOException, InterruptedException { List args; if (FileSignature.machineIsWin()) { args = Arrays.asList("cmd", "/c", cmdWin); } else { args = Arrays.asList("sh", "-c", cmdUnix); } - return exec(args); + return exec(cwd, environment, null, args); } /** Creates a process with the given arguments. */ @@ -70,32 +95,76 @@ public Result exec(String... args) throws IOException, InterruptedException { } /** Creates a process with the given arguments, the given byte array is written to stdin immediately. */ - public Result exec(byte[] stdin, String... args) throws IOException, InterruptedException { + public Result exec(@Nullable byte[] stdin, String... args) throws IOException, InterruptedException { return exec(stdin, Arrays.asList(args)); } /** Creates a process with the given arguments. */ public Result exec(List args) throws IOException, InterruptedException { - return exec(new byte[0], args); + return exec(null, args); } /** Creates a process with the given arguments, the given byte array is written to stdin immediately. */ - public Result exec(byte[] stdin, List args) throws IOException, InterruptedException { + public Result exec(@Nullable byte[] stdin, List args) throws IOException, InterruptedException { + return exec(null, null, stdin, args); + } + + /** Creates a process with the given arguments, the given byte array is written to stdin immediately. */ + public Result exec(@Nullable File cwd, @Nullable Map environment, @Nullable byte[] stdin, List args) throws IOException, InterruptedException { + LongRunningProcess process = start(cwd, environment, stdin, args); + try { + // wait for the process to finish + process.waitFor(); + // collect the output + return process.result(); + } catch (ExecutionException e) { + throw ThrowingEx.asRuntime(e); + } + } + + /** + * Creates a process with the given arguments, the given byte array is written to stdin immediately. + *
+ * Delegates to {@link #start(File, Map, byte[], boolean, List)} with {@code false} for {@code redirectErrorStream}. + */ + public LongRunningProcess start(@Nullable File cwd, @Nullable Map environment, @Nullable byte[] stdin, List args) throws IOException { + return start(cwd, environment, stdin, false, args); + } + + /** + * Creates a process with the given arguments, the given byte array is written to stdin immediately. + *
+ * The process is not waited for, so the caller is responsible for calling {@link LongRunningProcess#waitFor()} (if needed). + *
+ * To dispose this {@code ProcessRunner} instance, either call {@link #close()} or {@link LongRunningProcess#close()}. After + * {@link #close()} or {@link LongRunningProcess#close()} has been called, this {@code ProcessRunner} instance must not be used anymore. + */ + public LongRunningProcess start(@Nullable File cwd, @Nullable Map environment, @Nullable byte[] stdin, boolean redirectErrorStream, List args) throws IOException { + checkState(); ProcessBuilder builder = new ProcessBuilder(args); + if (cwd != null) { + builder.directory(cwd); + } + if (environment != null) { + builder.environment().putAll(environment); + } + if (stdin == null) { + stdin = new byte[0]; + } + if (redirectErrorStream) { + builder.redirectErrorStream(true); + } + Process process = builder.start(); Future outputFut = threadStdOut.submit(() -> drainToBytes(process.getInputStream(), bufStdOut)); - Future errorFut = threadStdErr.submit(() -> drainToBytes(process.getErrorStream(), bufStdErr)); + Future errorFut = null; + if (!redirectErrorStream) { + errorFut = threadStdErr.submit(() -> drainToBytes(process.getErrorStream(), bufStdErr)); + } // write stdin process.getOutputStream().write(stdin); process.getOutputStream().close(); - // wait for the process to finish - int exitCode = process.waitFor(); - try { - // collect the output - return new Result(args, exitCode, outputFut.get(), errorFut.get()); - } catch (ExecutionException e) { - throw ThrowingEx.asRuntime(e); - } + return new LongRunningProcess(process, args, outputFut, errorFut); } private static void drain(InputStream input, OutputStream output) throws IOException { @@ -118,17 +187,24 @@ public void close() { threadStdErr.shutdown(); } + /** Checks if this {@code ProcessRunner} instance is still usable. */ + private void checkState() { + if (threadStdOut.isShutdown() || threadStdErr.isShutdown()) { + throw new IllegalStateException("ProcessRunner has been closed and must not be used anymore."); + } + } + @SuppressFBWarnings({"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}) public static class Result { private final List args; private final int exitCode; private final byte[] stdOut, stdErr; - public Result(List args, int exitCode, byte[] stdOut, byte[] stdErr) { + public Result(@Nonnull List args, int exitCode, @Nonnull byte[] stdOut, @Nullable byte[] stdErr) { this.args = args; this.exitCode = exitCode; this.stdOut = stdOut; - this.stdErr = stdErr; + this.stdErr = (stdErr == null ? new byte[0] : stdErr); } public List args() { @@ -147,6 +223,14 @@ public byte[] stdErr() { return stdErr; } + public String stdOutUtf8() { + return new String(stdOut, StandardCharsets.UTF_8); + } + + public String stdErrUtf8() { + return new String(stdErr, StandardCharsets.UTF_8); + } + /** Returns true if the exit code was not zero. */ public boolean exitNotZero() { return exitCode != 0; @@ -155,7 +239,7 @@ public boolean exitNotZero() { /** * Asserts that the exit code was zero, and if so, returns * the content of stdout encoded with the given charset. - * + *

* If the exit code was not zero, throws an exception * with useful debugging information. */ @@ -191,8 +275,86 @@ public String toString() { } }; perStream.accept(" stdout", stdOut); - perStream.accept(" stderr", stdErr); + if (stdErr.length > 0) { + perStream.accept(" stderr", stdErr); + } return builder.toString(); } } + + /** + * A long-running process that can be waited for. + */ + public class LongRunningProcess extends Process implements AutoCloseable { + + private final Process delegate; + private final List args; + private final Future outputFut; + private final Future errorFut; + + public LongRunningProcess(@Nonnull Process delegate, @Nonnull List args, @Nonnull Future outputFut, @Nullable Future errorFut) { + this.delegate = requireNonNull(delegate); + this.args = args; + this.outputFut = outputFut; + this.errorFut = errorFut; + } + + @Override + public OutputStream getOutputStream() { + return delegate.getOutputStream(); + } + + @Override + public InputStream getInputStream() { + return delegate.getInputStream(); + } + + @Override + public InputStream getErrorStream() { + return delegate.getErrorStream(); + } + + @Override + public int waitFor() throws InterruptedException { + return delegate.waitFor(); + } + + @Override + public boolean waitFor(long timeout, TimeUnit unit) throws InterruptedException { + return delegate.waitFor(timeout, unit); + } + + @Override + public int exitValue() { + return delegate.exitValue(); + } + + @Override + public void destroy() { + delegate.destroy(); + } + + @Override + public Process destroyForcibly() { + return delegate.destroyForcibly(); + } + + @Override + public boolean isAlive() { + return delegate.isAlive(); + } + + public Result result() throws ExecutionException, InterruptedException { + int exitCode = waitFor(); + return new Result(args, exitCode, this.outputFut.get(), (this.errorFut != null ? this.errorFut.get() : null)); + } + + @Override + public void close() { + if (isAlive()) { + destroy(); + } + ProcessRunner.this.close(); + } + } } diff --git a/lib/src/main/java/com/diffplug/spotless/RingBufferByteArrayOutputStream.java b/lib/src/main/java/com/diffplug/spotless/RingBufferByteArrayOutputStream.java new file mode 100644 index 0000000000..da4fc6aa04 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/RingBufferByteArrayOutputStream.java @@ -0,0 +1,135 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +class RingBufferByteArrayOutputStream extends ByteArrayOutputStream { + + private final int limit; + + private int zeroIndexPointer = 0; + + private boolean isOverLimit = false; + + public RingBufferByteArrayOutputStream(int limit) { + this(limit, 32); + } + + public RingBufferByteArrayOutputStream(int limit, int initialCapacity) { + super(initialCapacity); + if (limit < initialCapacity) { + throw new IllegalArgumentException("Limit must be greater than initial capacity. Limit: " + limit + ", initial capacity: " + initialCapacity); + } + if (limit < 2) { + throw new IllegalArgumentException("Limit must be greater than or equal to 2 but is " + limit); + } + if (limit % 2 != 0) { + throw new IllegalArgumentException("Limit must be an even number but is " + limit); // to fit 16 bit unicode chars + } + this.limit = limit; + } + + // ---- writing + @Override + public synchronized void write(int b) { + if (count < limit) { + super.write(b); + return; + } + isOverLimit = true; + buf[zeroIndexPointer] = (byte) b; + zeroIndexPointer = (zeroIndexPointer + 1) % limit; + } + + @Override + public synchronized void write(byte[] b, int off, int len) { + int remaining = limit - count; + if (remaining >= len) { + super.write(b, off, len); + return; + } + if (remaining > 0) { + // write what we can "normally" + super.write(b, off, remaining); + // rest delegated + write(b, off + remaining, len - remaining); + return; + } + // we are over the limit + isOverLimit = true; + // write till limit is reached + int writeTillLimit = Math.min(len, limit - zeroIndexPointer); + System.arraycopy(b, off, buf, zeroIndexPointer, writeTillLimit); + zeroIndexPointer = (zeroIndexPointer + writeTillLimit) % limit; + if (writeTillLimit < len) { + // write rest + write(b, off + writeTillLimit, len - writeTillLimit); + } + } + + @Override + public synchronized void reset() { + super.reset(); + zeroIndexPointer = 0; + isOverLimit = false; + } + + // ---- output + @Override + public synchronized void writeTo(OutputStream out) throws IOException { + if (!isOverLimit) { + super.writeTo(out); + return; + } + out.write(buf, zeroIndexPointer, limit - zeroIndexPointer); + out.write(buf, 0, zeroIndexPointer); + } + + @Override + public synchronized byte[] toByteArray() { + if (!isOverLimit) { + return super.toByteArray(); + } + byte[] result = new byte[limit]; + System.arraycopy(buf, zeroIndexPointer, result, 0, limit - zeroIndexPointer); + System.arraycopy(buf, 0, result, limit - zeroIndexPointer, zeroIndexPointer); + return result; + } + + @SuppressFBWarnings(value = "DM_DEFAULT_ENCODING", justification = "We want to use the default encoding here since this is contract on ByteArrayOutputStream") + @Override + public synchronized String toString() { + if (!isOverLimit) { + return super.toString(); + } + return new String(buf, zeroIndexPointer, limit - zeroIndexPointer) + new String(buf, 0, zeroIndexPointer); + } + + @Override + public synchronized String toString(String charsetName) throws UnsupportedEncodingException { + if (!isOverLimit) { + return super.toString(charsetName); + } + return new String(buf, zeroIndexPointer, limit - zeroIndexPointer, charsetName) + new String(buf, 0, zeroIndexPointer, charsetName); + } + +} diff --git a/lib/src/main/java/com/diffplug/spotless/SpotlessCache.java b/lib/src/main/java/com/diffplug/spotless/SpotlessCache.java index b90682f1b6..237e326482 100644 --- a/lib/src/main/java/com/diffplug/spotless/SpotlessCache.java +++ b/lib/src/main/java/com/diffplug/spotless/SpotlessCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,9 @@ import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** @@ -34,6 +37,8 @@ * when Spotless is no longer in use to release any resources it has grabbed. */ public final class SpotlessCache { + private static final Logger LOGGER = LoggerFactory.getLogger(SpotlessCache.class); + /** Allows comparing keys based on their serialization. */ static final class SerializedKey { final byte[] serialized; @@ -68,7 +73,10 @@ synchronized ClassLoader classloader(JarState state) { synchronized ClassLoader classloader(Serializable key, JarState state) { SerializedKey serializedKey = new SerializedKey(key); return cache - .computeIfAbsent(serializedKey, k -> new FeatureClassLoader(state.jarUrls(), this.getClass().getClassLoader())); + .computeIfAbsent(serializedKey, k -> { + LOGGER.debug("Allocating an additional FeatureClassLoader for key={} Cache.size was {}", key, cache.size()); + return new FeatureClassLoader(state.jarUrls(), this.getClass().getClassLoader()); + }); } static SpotlessCache instance() { diff --git a/lib/src/main/java/com/diffplug/spotless/ThrowingEx.java b/lib/src/main/java/com/diffplug/spotless/ThrowingEx.java index f664c62bf3..6eb573b5d8 100644 --- a/lib/src/main/java/com/diffplug/spotless/ThrowingEx.java +++ b/lib/src/main/java/com/diffplug/spotless/ThrowingEx.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ /** * Basic functional interfaces which throw exception, along with * static helper methods for calling them. - * + *

* Contains most of the functionality of Durian's Throwing and Errors * classes, but stripped down and renamed to avoid any confusion. */ @@ -80,7 +80,7 @@ public static java.util.function.Function wrap(ThrowingEx.Function< /** * Casts or wraps the given exception to be a RuntimeException. - * + *

* If the input exception is a RuntimeException, it is simply * cast and returned. Otherwise, it wrapped in a * {@link WrappedAsRuntimeException} and returned. diff --git a/lib/src/main/java/com/diffplug/spotless/annotations/Internal.java b/lib/src/main/java/com/diffplug/spotless/annotations/Internal.java index 77a318f268..d87265fe2d 100644 --- a/lib/src/main/java/com/diffplug/spotless/annotations/Internal.java +++ b/lib/src/main/java/com/diffplug/spotless/annotations/Internal.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ /** * Signifies that a {@code public} API is actually an implementation detail, and should be treated as if it * were {@code private}. - * + *

* The user of the API should be warned that it may unexpectedly disappear in future versions of * Spotless. Usually the best place to put this warning is in the API's class JavaDoc. */ diff --git a/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java b/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java index 6a34c08828..6d04c25962 100644 --- a/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java +++ b/lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,10 +31,14 @@ import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.diffplug.spotless.FileSignature; import com.diffplug.spotless.FormatterFunc; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.LineEnding; +import com.diffplug.spotless.OnMatch; import com.diffplug.spotless.SerializableFileFilter; import com.diffplug.spotless.ThrowingEx; @@ -42,6 +46,9 @@ /** Prefixes a license header before the package statement. */ public final class LicenseHeaderStep { + public static final String DEFAULT_JAVA_HEADER_DELIMITER = "(package|import|public|class|module) "; + private static final Logger LOGGER = LoggerFactory.getLogger(LicenseHeaderStep.class); + public enum YearMode { PRESERVE, UPDATE_TO_TODAY, SET_FROM_GIT } @@ -51,7 +58,7 @@ public static LicenseHeaderStep headerDelimiter(String header, String delimiter) } public static LicenseHeaderStep headerDelimiter(ThrowingEx.Supplier headerLazy, String delimiter) { - return new LicenseHeaderStep(null, null, headerLazy, delimiter, DEFAULT_YEAR_DELIMITER, () -> YearMode.PRESERVE); + return new LicenseHeaderStep(null, null, headerLazy, delimiter, DEFAULT_YEAR_DELIMITER, () -> YearMode.PRESERVE, null); } final String name; @@ -60,14 +67,16 @@ public static LicenseHeaderStep headerDelimiter(ThrowingEx.Supplier head final String delimiter; final String yearSeparator; final Supplier yearMode; + final @Nullable String skipLinesMatching; - private LicenseHeaderStep(@Nullable String name, @Nullable String contentPattern, ThrowingEx.Supplier headerLazy, String delimiter, String yearSeparator, Supplier yearMode) { + private LicenseHeaderStep(@Nullable String name, @Nullable String contentPattern, ThrowingEx.Supplier headerLazy, String delimiter, String yearSeparator, Supplier yearMode, @Nullable String skipLinesMatching) { this.name = sanitizeName(name); - this.contentPattern = sanitizeContentPattern(contentPattern); + this.contentPattern = sanitizePattern(contentPattern); this.headerLazy = Objects.requireNonNull(headerLazy); this.delimiter = Objects.requireNonNull(delimiter); this.yearSeparator = Objects.requireNonNull(yearSeparator); this.yearMode = Objects.requireNonNull(yearMode); + this.skipLinesMatching = sanitizePattern(skipLinesMatching); } public String getName() { @@ -75,11 +84,11 @@ public String getName() { } public LicenseHeaderStep withName(String name) { - return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode); + return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode, skipLinesMatching); } public LicenseHeaderStep withContentPattern(String contentPattern) { - return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode); + return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode, skipLinesMatching); } public LicenseHeaderStep withHeaderString(String header) { @@ -87,15 +96,15 @@ public LicenseHeaderStep withHeaderString(String header) { } public LicenseHeaderStep withHeaderLazy(ThrowingEx.Supplier headerLazy) { - return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode); + return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode, skipLinesMatching); } public LicenseHeaderStep withDelimiter(String delimiter) { - return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode); + return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode, skipLinesMatching); } public LicenseHeaderStep withYearSeparator(String yearSeparator) { - return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode); + return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode, skipLinesMatching); } public LicenseHeaderStep withYearMode(YearMode yearMode) { @@ -103,7 +112,11 @@ public LicenseHeaderStep withYearMode(YearMode yearMode) { } public LicenseHeaderStep withYearModeLazy(Supplier yearMode) { - return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode); + return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode, skipLinesMatching); + } + + public LicenseHeaderStep withSkipLinesMatching(@Nullable String skipLinesMatching) { + return new LicenseHeaderStep(name, contentPattern, headerLazy, delimiter, yearSeparator, yearMode, skipLinesMatching); } public FormatterStep build() { @@ -112,7 +125,7 @@ public FormatterStep build() { if (yearMode.get() == YearMode.SET_FROM_GIT) { formatterStep = FormatterStep.createNeverUpToDateLazy(name, () -> { boolean updateYear = false; // doesn't matter - Runtime runtime = new Runtime(headerLazy.get(), delimiter, yearSeparator, updateYear); + Runtime runtime = new Runtime(headerLazy.get(), delimiter, yearSeparator, updateYear, skipLinesMatching); return FormatterFunc.needsFile(runtime::setLicenseHeaderYearsFromGitHistory); }); } else { @@ -130,15 +143,15 @@ public FormatterStep build() { default: throw new IllegalStateException(yearMode.toString()); } - return new Runtime(headerLazy.get(), delimiter, yearSeparator, updateYear); - }, step -> step::format); + return new Runtime(headerLazy.get(), delimiter, yearSeparator, updateYear, skipLinesMatching); + }, step -> FormatterFunc.needsFile(step::format)); } if (contentPattern == null) { return formatterStep; } - return formatterStep.filterByContentPattern(contentPattern); + return formatterStep.filterByContent(OnMatch.INCLUDE, contentPattern); } private String sanitizeName(@Nullable String name) { @@ -156,18 +169,18 @@ private String sanitizeName(@Nullable String name) { } @Nullable - private String sanitizeContentPattern(@Nullable String contentPattern) { - if (contentPattern == null) { - return contentPattern; + private String sanitizePattern(@Nullable String pattern) { + if (pattern == null) { + return pattern; } - contentPattern = contentPattern.trim(); + pattern = pattern.trim(); - if (contentPattern.isEmpty()) { + if (pattern.isEmpty()) { return null; } - return contentPattern; + return pattern; } private static final String DEFAULT_NAME_PREFIX = LicenseHeaderStep.class.getName(); @@ -195,15 +208,19 @@ private static class Runtime implements Serializable { private static final long serialVersionUID = 1475199492829130965L; private final Pattern delimiterPattern; + private final @Nullable Pattern skipLinesMatching; private final String yearSepOrFull; private final @Nullable String yearToday; private final @Nullable String beforeYear; private final @Nullable String afterYear; private final boolean updateYearWithLatest; private final boolean licenseHeaderWithRange; + private final boolean hasFileToken; + + private static final Pattern FILENAME_PATTERN = Pattern.compile("\\$FILE"); /** The license that we'd like enforced. */ - private Runtime(String licenseHeader, String delimiter, String yearSeparator, boolean updateYearWithLatest) { + private Runtime(String licenseHeader, String delimiter, String yearSeparator, boolean updateYearWithLatest, @Nullable String skipLinesMatching) { if (delimiter.contains("\n")) { throw new IllegalArgumentException("The delimiter must not contain any newlines."); } @@ -213,6 +230,8 @@ private Runtime(String licenseHeader, String delimiter, String yearSeparator, bo licenseHeader = licenseHeader + "\n"; } this.delimiterPattern = Pattern.compile('^' + delimiter, Pattern.UNIX_LINES | Pattern.MULTILINE); + this.skipLinesMatching = skipLinesMatching == null ? null : Pattern.compile(skipLinesMatching); + this.hasFileToken = FILENAME_PATTERN.matcher(licenseHeader).find(); Optional yearToken = getYearToken(licenseHeader); if (yearToken.isPresent()) { @@ -253,7 +272,38 @@ private static Optional getYearToken(String licenseHeader) { } /** Formats the given string. */ - private String format(String raw) { + private String format(String raw, File file) { + if (skipLinesMatching == null) { + return addOrUpdateLicenseHeader(raw, file); + } else { + String[] lines = raw.split("\n"); + StringBuilder skippedLinesBuilder = new StringBuilder(); + StringBuilder remainingLinesBuilder = new StringBuilder(); + boolean lastMatched = true; + for (String line : lines) { + if (lastMatched) { + Matcher matcher = skipLinesMatching.matcher(line); + if (matcher.find()) { + skippedLinesBuilder.append(line).append('\n'); + } else { + remainingLinesBuilder.append(line).append('\n'); + lastMatched = false; + } + } else { + remainingLinesBuilder.append(line).append('\n'); + } + } + return skippedLinesBuilder + addOrUpdateLicenseHeader(remainingLinesBuilder.toString(), file); + } + } + + private String addOrUpdateLicenseHeader(String raw, File file) { + raw = replaceYear(raw); + raw = replaceFileName(raw, file); + return raw; + } + + private String replaceYear(String raw) { Matcher contentMatcher = delimiterPattern.matcher(raw); if (!contentMatcher.find()) { throw new IllegalArgumentException("Unable to find delimiter regex " + delimiterPattern); @@ -348,7 +398,7 @@ private String calculateYearBySearching(String content) { } } } else { - System.err.println("Can't parse copyright year '" + content + "', defaulting to " + yearToday); + LOGGER.warn("Can't parse copyright year '{}', defaulting to {}", content, yearToday); // couldn't recognize the year format return yearToday; } @@ -383,6 +433,19 @@ private String setLicenseHeaderYearsFromGitHistory(String raw, File file) throws return beforeYear + yearRange + afterYear + raw.substring(contentMatcher.start()); } + private String replaceFileName(String raw, File file) { + if (!hasFileToken) { + return raw; + } + Matcher contentMatcher = delimiterPattern.matcher(raw); + if (!contentMatcher.find()) { + throw new IllegalArgumentException("Unable to find delimiter regex " + delimiterPattern); + } + String header = raw.substring(0, contentMatcher.start()); + String content = raw.substring(contentMatcher.start()); + return FILENAME_PATTERN.matcher(header).replaceAll(file.getName()) + content; + } + private static String parseYear(String cmd, File file) throws IOException { String fullCmd = cmd + " -- " + file.getAbsolutePath(); ProcessBuilder builder = new ProcessBuilder().directory(file.getParentFile()); diff --git a/lib/src/main/java/com/diffplug/spotless/gherkin/GherkinUtilsConfig.java b/lib/src/main/java/com/diffplug/spotless/gherkin/GherkinUtilsConfig.java new file mode 100644 index 0000000000..d7962c6543 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/gherkin/GherkinUtilsConfig.java @@ -0,0 +1,38 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.gherkin; + +import java.io.Serializable; + +public class GherkinUtilsConfig implements Serializable { + private static final long serialVersionUID = 1L; + + public static int defaultIndentSpaces() { + // https://cucumber.io/docs/gherkin/reference/ + // Recommended indentation is 2 spaces + return 2; + } + + final int indentSpaces; + + public GherkinUtilsConfig(int indentSpaces) { + this.indentSpaces = indentSpaces; + } + + public int getIndentSpaces() { + return indentSpaces; + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/gherkin/GherkinUtilsStep.java b/lib/src/main/java/com/diffplug/spotless/gherkin/GherkinUtilsStep.java new file mode 100644 index 0000000000..78f5eacd32 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/gherkin/GherkinUtilsStep.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.gherkin; + +import java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Objects; + +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.JarState; +import com.diffplug.spotless.Provisioner; + +public class GherkinUtilsStep { + private static final String MAVEN_COORDINATE = "io.cucumber:gherkin-utils:"; + private static final String DEFAULT_VERSION = "8.0.2"; + + public static String defaultVersion() { + return DEFAULT_VERSION; + } + + public static FormatterStep create(GherkinUtilsConfig gherkinSimpleConfig, + String formatterVersion, Provisioner provisioner) { + Objects.requireNonNull(provisioner, "provisioner cannot be null"); + return FormatterStep.createLazy("gherkin", () -> new GherkinUtilsStep.State(gherkinSimpleConfig, formatterVersion, provisioner), GherkinUtilsStep.State::toFormatter); + } + + private static final class State implements Serializable { + private static final long serialVersionUID = 1L; + + private final GherkinUtilsConfig gherkinSimpleConfig; + private final JarState jarState; + + private State(GherkinUtilsConfig gherkinSimpleConfig, String formatterVersion, Provisioner provisioner) throws IOException { + this.gherkinSimpleConfig = gherkinSimpleConfig; + this.jarState = JarState.from(MAVEN_COORDINATE + formatterVersion, provisioner); + } + + FormatterFunc toFormatter() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, + InstantiationException, IllegalAccessException { + Class formatterFunc = jarState.getClassLoader().loadClass("com.diffplug.spotless.glue.gherkin.GherkinUtilsFormatterFunc"); + Constructor constructor = formatterFunc.getConstructor(GherkinUtilsConfig.class); + return (FormatterFunc) constructor.newInstance(gherkinSimpleConfig); + } + } + + private GherkinUtilsStep() { + // cannot be directly instantiated + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/go/GofmtFormatStep.java b/lib/src/main/java/com/diffplug/spotless/go/GofmtFormatStep.java new file mode 100644 index 0000000000..18e388cf5b --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/go/GofmtFormatStep.java @@ -0,0 +1,111 @@ +/* + * Copyright 2024 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.go; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import javax.annotation.Nullable; + +import com.diffplug.spotless.ForeignExe; +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.ProcessRunner; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * Note: gofmt doesn't have a version flag, because it's part of standard Go distribution. + * So `go` executable can be used to determine base path and version, and path to gofmt can be built from it. + */ +public class GofmtFormatStep { + public static String name() { + return "gofmt"; + } + + public static String defaultVersion() { + return "go1.20.0"; + } + + private final String version; + private final @Nullable String pathToExe; + + private GofmtFormatStep(String version, String pathToExe) { + this.version = version; + this.pathToExe = pathToExe; + } + + public static GofmtFormatStep withVersion(String version) { + return new GofmtFormatStep(version, null); + } + + public GofmtFormatStep withGoExecutable(String pathToExe) { + return new GofmtFormatStep(version, pathToExe); + } + + public FormatterStep create() { + return FormatterStep.createLazy(name(), this::createState, GofmtFormatStep.State::toFunc); + } + + private State createState() throws IOException, InterruptedException { + String howToInstall = "gofmt is a part of standard go distribution. If spotless can't discover it automatically, " + + "you can point Spotless to the go binary with {@code pathToExe('/path/to/go')}"; + final ForeignExe exe = ForeignExe.nameAndVersion("go", version) + .pathToExe(pathToExe) + .versionFlag("version") + .fixCantFind(howToInstall) + .fixWrongVersion( + "You can tell Spotless to use the version you already have with {@code gofmt('{versionFound}')}" + + "or you can install the currently specified Go version, {version}.\n" + howToInstall); + return new State(this, exe); + } + + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + static class State implements Serializable { + private static final long serialVersionUID = -1825662355363926318L; + // used for up-to-date checks and caching + final String version; + final transient ForeignExe exe; + + public State(GofmtFormatStep step, ForeignExe goExecutable) { + this.version = step.version; + this.exe = Objects.requireNonNull(goExecutable); + } + + String format(ProcessRunner runner, String input, File file) throws IOException, InterruptedException { + final List processArgs = new ArrayList<>(); + String pathToGoBinary = exe.confirmVersionAndGetAbsolutePath(); + Path goBasePath = Path.of(pathToGoBinary).getParent(); + if (goBasePath == null) { + throw new IllegalStateException("Unable to resolve base path of Go installation directory"); + } + String pathToGoFmt = goBasePath.resolve("gofmt").toString(); + processArgs.add(pathToGoFmt); + return runner.exec(input.getBytes(StandardCharsets.UTF_8), processArgs).assertExitZero(StandardCharsets.UTF_8); + } + + FormatterFunc.Closeable toFunc() { + ProcessRunner runner = new ProcessRunner(); + return FormatterFunc.Closeable.of(runner, this::format); + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/groovy/RemoveSemicolonsStep.java b/lib/src/main/java/com/diffplug/spotless/groovy/RemoveSemicolonsStep.java new file mode 100644 index 0000000000..e24feb5cd3 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/groovy/RemoveSemicolonsStep.java @@ -0,0 +1,76 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.groovy; + +import java.io.BufferedReader; +import java.io.Serializable; +import java.io.StringReader; + +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterStep; + +/** + * Removes all semicolons from the end of lines. + * + * @author Jose Luis Badano + */ +public final class RemoveSemicolonsStep { + private static final String NAME = "Remove unnecessary semicolons"; + + private RemoveSemicolonsStep() { + // do not instantiate + } + + public static FormatterStep create() { + return FormatterStep.createLazy(NAME, + State::new, + RemoveSemicolonsStep.State::toFormatter); + } + + private static final class State implements Serializable { + private static final long serialVersionUID = 1L; + + FormatterFunc toFormatter() { + return raw -> { + try (BufferedReader reader = new BufferedReader(new StringReader(raw))) { + StringBuilder result = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + result.append(removeSemicolon(line)); + result.append(System.lineSeparator()); + } + return result.toString(); + } + }; + } + + /** + * Removes the last semicolon in a line if it exists. + * + * @param line the line to remove the semicolon from + * @return the line without the last semicolon + */ + private String removeSemicolon(String line) { + // find last semicolon in a string a remove it + int lastSemicolon = line.lastIndexOf(";"); + if (lastSemicolon != -1 && lastSemicolon == line.length() - 1) { + return line.substring(0, lastSemicolon); + } else { + return line; + } + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/java/CleanthatJavaStep.java b/lib/src/main/java/com/diffplug/spotless/java/CleanthatJavaStep.java new file mode 100644 index 0000000000..91439a1767 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/java/CleanthatJavaStep.java @@ -0,0 +1,167 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.java; + +import java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Objects; + +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.JarState; +import com.diffplug.spotless.Jvm; +import com.diffplug.spotless.Provisioner; + +/** + * Enables CleanThat as a SpotLess step. + * + * @author Benoit Lacelle + */ +// https://github.com/diffplug/spotless/blob/main/CONTRIBUTING.md#how-to-add-a-new-formatterstep +public final class CleanthatJavaStep { + + private static final String NAME = "cleanthat"; + private static final String MAVEN_COORDINATE = "io.github.solven-eu.cleanthat:java"; + + // CleanThat changelog is available at https://github.com/solven-eu/cleanthat/blob/master/CHANGES.MD + private static final Jvm.Support JVM_SUPPORT = Jvm. support(NAME).add(11, "2.16"); + + // prevent direct instantiation + private CleanthatJavaStep() {} + + /** Creates a step which apply default CleanThat mutators. */ + public static FormatterStep create(Provisioner provisioner) { + return create(defaultVersion(), provisioner); + } + + /** Creates a step which apply default CleanThat mutators. */ + public static FormatterStep create(String version, Provisioner provisioner) { + return create(MAVEN_COORDINATE, version, defaultSourceJdk(), defaultMutators(), defaultExcludedMutators(), defaultIncludeDraft(), provisioner); + } + + public static String defaultSourceJdk() { + // see IJdkVersionConstants.JDK_7 + // https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#source + // 1.7 is the default for 'maven-compiler-plugin' since 3.9.0 + return "1.7"; + } + + /** + * By default, we include only safe and consensual mutators + * @return + */ + public static List defaultMutators() { + // see ICleanthatStepParametersProperties.SAFE_AND_CONSENSUAL + return List.of("SafeAndConsensual"); + } + + public static List defaultExcludedMutators() { + return List.of(); + } + + public static boolean defaultIncludeDraft() { + return false; + } + + /** Creates a step which apply selected CleanThat mutators. */ + public static FormatterStep create(String groupArtifact, + String version, + String sourceJdkVersion, + List included, + List excluded, + boolean includeDraft, + Provisioner provisioner) { + Objects.requireNonNull(groupArtifact, "groupArtifact"); + if (groupArtifact.chars().filter(ch -> ch == ':').count() != 1) { + throw new IllegalArgumentException("groupArtifact must be in the form 'groupId:artifactId'. it was: " + groupArtifact); + } + Objects.requireNonNull(version, "version"); + Objects.requireNonNull(provisioner, "provisioner"); + return FormatterStep.createLazy(NAME, + () -> new JavaRefactorerState(NAME, groupArtifact, version, sourceJdkVersion, included, excluded, includeDraft, provisioner), + JavaRefactorerState::createFormat); + } + + /** Get default formatter version */ + public static String defaultVersion() { + return JVM_SUPPORT.getRecommendedFormatterVersion(); + } + + public static String defaultGroupArtifact() { + return MAVEN_COORDINATE; + } + + static final class JavaRefactorerState implements Serializable { + private static final long serialVersionUID = 1L; + + final JarState jarState; + final String stepName; + final String version; + + final String sourceJdkVersion; + final List included; + final List excluded; + final boolean includeDraft; + + JavaRefactorerState(String stepName, String version, Provisioner provisioner) throws IOException { + this(stepName, MAVEN_COORDINATE, version, defaultSourceJdk(), defaultMutators(), defaultExcludedMutators(), defaultIncludeDraft(), provisioner); + } + + JavaRefactorerState(String stepName, + String groupArtifact, + String version, + String sourceJdkVersion, + List included, + List excluded, + boolean includeDraft, + Provisioner provisioner) throws IOException { + JVM_SUPPORT.assertFormatterSupported(version); + ModuleHelper.doOpenInternalPackagesIfRequired(); + this.jarState = JarState.from(groupArtifact + ":" + version, provisioner); + this.stepName = stepName; + this.version = version; + + this.sourceJdkVersion = sourceJdkVersion; + this.included = included; + this.excluded = excluded; + this.includeDraft = includeDraft; + } + + @SuppressWarnings("PMD.UseProperClassLoader") + FormatterFunc createFormat() { + ClassLoader classLoader = jarState.getClassLoader(); + + Object formatter; + Method formatterMethod; + try { + Class formatterClazz = classLoader.loadClass("com.diffplug.spotless.glue.java.JavaCleanthatRefactorerFunc"); + Constructor formatterConstructor = formatterClazz.getConstructor(String.class, List.class, List.class, boolean.class); + + formatter = formatterConstructor.newInstance(sourceJdkVersion, included, excluded, includeDraft); + formatterMethod = formatterClazz.getMethod("apply", String.class); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Issue executing the formatter", e); + } + + return JVM_SUPPORT.suggestLaterVersionOnError(version, input -> { + return (String) formatterMethod.invoke(formatter, input); + }); + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/java/FormatAnnotationsStep.java b/lib/src/main/java/com/diffplug/spotless/java/FormatAnnotationsStep.java index 2d01e305a0..5a2d856850 100644 --- a/lib/src/main/java/com/diffplug/spotless/java/FormatAnnotationsStep.java +++ b/lib/src/main/java/com/diffplug/spotless/java/FormatAnnotationsStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 DiffPlug + * Copyright 2022-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,6 +55,13 @@ public final class FormatAnnotationsStep { "Acceleration", "ACCTop", "AinferBottom", + "AinferDefaultType", + "AinferParent", + "AinferSibling1", + "AinferSibling2", + "AinferTop", + "AinferImplicitAnno", + "AinferSiblingWithFields", "AlwaysSafe", "Angle", "AnnoWithStringArg", @@ -102,6 +109,7 @@ public final class FormatAnnotationsStep { "DefaultType", "degrees", "Det", + "DoesNotMatchRegex", "DotSeparatedIdentifiers", "DotSeparatedIdentifiersOrPrimitiveType", "DoubleVal", @@ -371,7 +379,7 @@ public final class FormatAnnotationsStep { "UpperBoundBottom", "UpperBoundLiteral", "UpperBoundUnknown", - "Value", + "ValueTypeAnno", "VariableNameDefaultBottom", "VariableNameDefaultMiddle", "VariableNameDefaultTop", diff --git a/lib/src/main/java/com/diffplug/spotless/java/GoogleJavaFormatStep.java b/lib/src/main/java/com/diffplug/spotless/java/GoogleJavaFormatStep.java index 6390c619a9..d0312dcc1e 100644 --- a/lib/src/main/java/com/diffplug/spotless/java/GoogleJavaFormatStep.java +++ b/lib/src/main/java/com/diffplug/spotless/java/GoogleJavaFormatStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,17 +16,14 @@ package com.diffplug.spotless.java; import java.io.Serializable; -import java.lang.reflect.Method; +import java.lang.reflect.Constructor; import java.util.Objects; import com.diffplug.spotless.FormatterFunc; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.JarState; import com.diffplug.spotless.Jvm; -import com.diffplug.spotless.LineEnding; import com.diffplug.spotless.Provisioner; -import com.diffplug.spotless.ThrowingEx.BiFunction; -import com.diffplug.spotless.ThrowingEx.Function; /** Wraps up google-java-format as a FormatterStep. */ public class GoogleJavaFormatStep { @@ -35,29 +32,10 @@ private GoogleJavaFormatStep() {} private static final String DEFAULT_STYLE = "GOOGLE"; private static final boolean DEFAULT_REFLOW_LONG_STRINGS = false; + private static final boolean DEFAULT_REORDER_IMPORTS = false; + private static final boolean DEFAULT_FORMAT_JAVADOC = true; static final String NAME = "google-java-format"; - static final String MAVEN_COORDINATE = "com.google.googlejavaformat:google-java-format"; - static final String FORMATTER_CLASS = "com.google.googlejavaformat.java.Formatter"; - static final String FORMATTER_METHOD = "formatSource"; - - private static final String OPTIONS_CLASS = "com.google.googlejavaformat.java.JavaFormatterOptions"; - private static final String OPTIONS_BUILDER_METHOD = "builder"; - private static final String OPTIONS_BUILDER_CLASS = "com.google.googlejavaformat.java.JavaFormatterOptions$Builder"; - private static final String OPTIONS_BUILDER_STYLE_METHOD = "style"; - private static final String OPTIONS_BUILDER_BUILD_METHOD = "build"; - private static final String OPTIONS_Style = "com.google.googlejavaformat.java.JavaFormatterOptions$Style"; - - private static final String REMOVE_UNUSED_CLASS = "com.google.googlejavaformat.java.RemoveUnusedImports"; - private static final String REMOVE_UNUSED_METHOD = "removeUnusedImports"; - - private static final String REMOVE_UNUSED_IMPORT_JavadocOnlyImports = "com.google.googlejavaformat.java.RemoveUnusedImports$JavadocOnlyImports"; - private static final String REMOVE_UNUSED_IMPORT_JavadocOnlyImports_Keep = "KEEP"; - - private static final String IMPORT_ORDERER_CLASS = "com.google.googlejavaformat.java.ImportOrderer"; - private static final String IMPORT_ORDERER_METHOD = "reorderImports"; - - private static final String STRING_WRAPPER_CLASS = "com.google.googlejavaformat.java.StringWrapper"; - private static final String STRING_WRAPPER_METHOD = "wrap"; + public static final String MAVEN_COORDINATE = "com.google.googlejavaformat:google-java-format"; /** Creates a step which formats everything - code, import order, and unused imports. */ public static FormatterStep create(Provisioner provisioner) { @@ -79,8 +57,16 @@ public static FormatterStep create(String version, String style, Provisioner pro return create(MAVEN_COORDINATE, version, style, provisioner, reflowLongStrings); } - /** Creates a step which formats everything - groupArtifact, code, import order, and unused imports - and optionally reflows long strings. */ public static FormatterStep create(String groupArtifact, String version, String style, Provisioner provisioner, boolean reflowLongStrings) { + return create(groupArtifact, version, style, provisioner, reflowLongStrings, DEFAULT_REORDER_IMPORTS); + } + + public static FormatterStep create(String groupArtifact, String version, String style, Provisioner provisioner, boolean reflowLongStrings, boolean reorderImports) { + return create(groupArtifact, version, style, provisioner, reflowLongStrings, reorderImports, DEFAULT_FORMAT_JAVADOC); + } + + /** Creates a step which formats everything - groupArtifact, code, import order, and unused imports - and optionally reflows long strings. */ + public static FormatterStep create(String groupArtifact, String version, String style, Provisioner provisioner, boolean reflowLongStrings, boolean reorderImports, boolean formatJavadoc) { Objects.requireNonNull(groupArtifact, "groupArtifact"); if (groupArtifact.chars().filter(ch -> ch == ':').count() != 1) { throw new IllegalArgumentException("groupArtifact must be in the form 'groupId:artifactId'"); @@ -89,11 +75,15 @@ public static FormatterStep create(String groupArtifact, String version, String Objects.requireNonNull(style, "style"); Objects.requireNonNull(provisioner, "provisioner"); return FormatterStep.createLazy(NAME, - () -> new State(NAME, groupArtifact, version, style, provisioner, reflowLongStrings), + () -> new State(NAME, groupArtifact, version, style, provisioner, reflowLongStrings, reorderImports, formatJavadoc), State::createFormat); } - static final Jvm.Support JVM_SUPPORT = Jvm. support(NAME).add(8, "1.7").add(11, "1.15.0"); + static final Jvm.Support JVM_SUPPORT = Jvm. support(NAME) + .addMin(11, "1.8") // we only support google-java-format >= 1.8 due to api changes + .addMin(16, "1.10.0") // java 16 requires at least 1.10.0 due to jdk api changes in JavaTokenizer + .addMin(21, "1.17.0") // java 21 requires at least 1.17.0 due to https://github.com/google/google-java-format/issues/898 + .add(11, "1.19.2"); // default version public static String defaultGroupArtifact() { return MAVEN_COORDINATE; @@ -112,6 +102,14 @@ public static boolean defaultReflowLongStrings() { return DEFAULT_REFLOW_LONG_STRINGS; } + public static boolean defaultReorderImports() { + return DEFAULT_REORDER_IMPORTS; + } + + public static boolean defaultFormatJavadoc() { + return DEFAULT_FORMAT_JAVADOC; + } + static final class State implements Serializable { private static final long serialVersionUID = 1L; @@ -121,6 +119,8 @@ static final class State implements Serializable { final String version; final String style; final boolean reflowLongStrings; + final boolean reorderImports; + final boolean formatJavadoc; State(String stepName, String version, Provisioner provisioner) throws Exception { this(stepName, version, DEFAULT_STYLE, provisioner); @@ -131,10 +131,14 @@ static final class State implements Serializable { } State(String stepName, String version, String style, Provisioner provisioner, boolean reflowLongStrings) throws Exception { - this(stepName, MAVEN_COORDINATE, version, style, provisioner, reflowLongStrings); + this(stepName, version, style, provisioner, reflowLongStrings, DEFAULT_REORDER_IMPORTS); } - State(String stepName, String groupArtifact, String version, String style, Provisioner provisioner, boolean reflowLongStrings) throws Exception { + State(String stepName, String version, String style, Provisioner provisioner, boolean reflowLongStrings, boolean reorderImports) throws Exception { + this(stepName, MAVEN_COORDINATE, version, style, provisioner, reflowLongStrings, reorderImports, DEFAULT_FORMAT_JAVADOC); + } + + State(String stepName, String groupArtifact, String version, String style, Provisioner provisioner, boolean reflowLongStrings, boolean reorderImports, boolean formatJavadoc) throws Exception { JVM_SUPPORT.assertFormatterSupported(version); ModuleHelper.doOpenInternalPackagesIfRequired(); this.jarState = JarState.from(groupArtifact + ":" + version, provisioner); @@ -142,122 +146,28 @@ static final class State implements Serializable { this.version = version; this.style = style; this.reflowLongStrings = reflowLongStrings; + this.reorderImports = reorderImports; + this.formatJavadoc = formatJavadoc; } - @SuppressWarnings({"unchecked", "rawtypes"}) FormatterFunc createFormat() throws Exception { - ClassLoader classLoader = jarState.getClassLoader(); - - // instantiate the formatter and get its format method - Class optionsClass = classLoader.loadClass(OPTIONS_CLASS); - Class optionsBuilderClass = classLoader.loadClass(OPTIONS_BUILDER_CLASS); - Method optionsBuilderMethod = optionsClass.getMethod(OPTIONS_BUILDER_METHOD); - Object optionsBuilder = optionsBuilderMethod.invoke(null); - - Class optionsStyleClass = classLoader.loadClass(OPTIONS_Style); - Object styleConstant = Enum.valueOf((Class) optionsStyleClass, style); - Method optionsBuilderStyleMethod = optionsBuilderClass.getMethod(OPTIONS_BUILDER_STYLE_METHOD, optionsStyleClass); - optionsBuilderStyleMethod.invoke(optionsBuilder, styleConstant); - - Method optionsBuilderBuildMethod = optionsBuilderClass.getMethod(OPTIONS_BUILDER_BUILD_METHOD); - Object options = optionsBuilderBuildMethod.invoke(optionsBuilder); - - Class formatterClazz = classLoader.loadClass(FORMATTER_CLASS); - Object formatter = formatterClazz.getConstructor(optionsClass).newInstance(options); - Method formatterMethod = formatterClazz.getMethod(FORMATTER_METHOD, String.class); - - Function removeUnused = constructRemoveUnusedFunction(classLoader); - - Class importOrdererClass = classLoader.loadClass(IMPORT_ORDERER_CLASS); - Method importOrdererMethod = importOrdererClass.getMethod(IMPORT_ORDERER_METHOD, String.class); + final ClassLoader classLoader = jarState.getClassLoader(); + Class formatterFunc = classLoader.loadClass("com.diffplug.spotless.glue.java.GoogleJavaFormatFormatterFunc"); + Constructor constructor = formatterFunc.getConstructor(String.class, String.class, boolean.class, boolean.class, boolean.class); + FormatterFunc googleJavaFormatFormatterFunc = (FormatterFunc) constructor.newInstance(version, style, reflowLongStrings, reorderImports, formatJavadoc); - BiFunction reflowLongStrings = this.reflowLongStrings ? constructReflowLongStringsFunction(classLoader, formatterClazz) : (s, f) -> s; - - return JVM_SUPPORT.suggestLaterVersionOnError(version, (input -> { - String formatted = (String) formatterMethod.invoke(formatter, input); - String removedUnused = removeUnused.apply(formatted); - String sortedImports = (String) importOrdererMethod.invoke(null, removedUnused); - String reflowedLongStrings = reflowLongStrings.apply(sortedImports, formatter); - return fixWindowsBug(reflowedLongStrings, version); - })); + return JVM_SUPPORT.suggestLaterVersionOnError(version, googleJavaFormatFormatterFunc); } FormatterFunc createRemoveUnusedImportsOnly() throws Exception { ClassLoader classLoader = jarState.getClassLoader(); - Function removeUnused = constructRemoveUnusedFunction(classLoader); - return JVM_SUPPORT.suggestLaterVersionOnError(version, (input -> fixWindowsBug(removeUnused.apply(input), version))); - } + Class formatterFunc = classLoader.loadClass("com.diffplug.spotless.glue.java.GoogleJavaFormatRemoveUnusedImporterFormatterFunc"); + Constructor constructor = formatterFunc.getConstructor(String.class); //version + FormatterFunc googleJavaFormatRemoveUnusedImporterFormatterFunc = (FormatterFunc) constructor.newInstance(version); - private static Function constructRemoveUnusedFunction(ClassLoader classLoader) - throws NoSuchMethodException, ClassNotFoundException { - Class removeUnusedClass = classLoader.loadClass(REMOVE_UNUSED_CLASS); - Class removeJavadocOnlyClass; - try { - // google-java-format 1.7 or lower - removeJavadocOnlyClass = classLoader.loadClass(REMOVE_UNUSED_IMPORT_JavadocOnlyImports); - } catch (ClassNotFoundException e) { - // google-java-format 1.8+ - removeJavadocOnlyClass = null; - } - - Function removeUnused; - if (removeJavadocOnlyClass != null) { - @SuppressWarnings({"unchecked", "rawtypes"}) - Object removeJavadocConstant = Enum.valueOf((Class) removeJavadocOnlyClass, REMOVE_UNUSED_IMPORT_JavadocOnlyImports_Keep); - Method removeUnusedMethod = removeUnusedClass.getMethod(REMOVE_UNUSED_METHOD, String.class, removeJavadocOnlyClass); - removeUnused = (x) -> (String) removeUnusedMethod.invoke(null, x, removeJavadocConstant); - } else { - Method removeUnusedMethod = removeUnusedClass.getMethod(REMOVE_UNUSED_METHOD, String.class); - removeUnused = (x) -> (String) removeUnusedMethod.invoke(null, x); - } - return removeUnused; + return JVM_SUPPORT.suggestLaterVersionOnError(version, googleJavaFormatRemoveUnusedImporterFormatterFunc); } - private static BiFunction constructReflowLongStringsFunction(ClassLoader classLoader, Class formatterClazz) throws NoSuchMethodException { - Class stringWrapperClass; - try { - stringWrapperClass = classLoader.loadClass(STRING_WRAPPER_CLASS); - } catch (ClassNotFoundException e) { - // google-java-format 1.7 or lower, which happen to be LATEST_VERSION_JRE_8, so rely on suggestJre11 for the error - return (s, f) -> { - throw e; - }; - } - Method stringWrapperMethod = stringWrapperClass.getMethod(STRING_WRAPPER_METHOD, String.class, formatterClazz); - return (s, f) -> (String) stringWrapperMethod.invoke(null, s, f); - } } - private static final boolean IS_WINDOWS = LineEnding.PLATFORM_NATIVE.str().equals("\r\n"); - - /** - * google-java-format-1.1's removeUnusedImports does *wacky* stuff on Windows. - * The beauty of normalizing all line endings to unix! - */ - static String fixWindowsBug(String input, String version) { - if (IS_WINDOWS && version.equals("1.1")) { - int firstImport = input.indexOf("\nimport "); - if (firstImport == 0) { - return input; - } else if (firstImport > 0) { - int numToTrim = 0; - char prevChar; - do { - ++numToTrim; - prevChar = input.charAt(firstImport - numToTrim); - } while (Character.isWhitespace(prevChar) && (firstImport - numToTrim) > 0); - if (firstImport - numToTrim == 0) { - // import was the very first line, and we'd like to maintain a one-line gap - ++numToTrim; - } else if (prevChar == ';' || prevChar == '/') { - // import came after either license or a package declaration - --numToTrim; - } - if (numToTrim > 0) { - return input.substring(0, firstImport - numToTrim + 2) + input.substring(firstImport + 1); - } - } - } - return input; - } } diff --git a/lib/src/main/java/com/diffplug/spotless/java/ImportOrderStep.java b/lib/src/main/java/com/diffplug/spotless/java/ImportOrderStep.java index 686c9cfcaf..e1e63458d9 100644 --- a/lib/src/main/java/com/diffplug/spotless/java/ImportOrderStep.java +++ b/lib/src/main/java/com/diffplug/spotless/java/ImportOrderStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -39,6 +41,9 @@ public final class ImportOrderStep { private static final boolean WILDCARDS_LAST_DEFAULT = false; + private static final boolean SEMANTIC_SORT_DEFAULT = false; + private static final Set TREAT_AS_PACKAGE_DEFAULT = Set.of(); + private static final Set TREAT_AS_CLASS_DEFAULT = Set.of(); private final String lineFormat; @@ -55,27 +60,33 @@ private ImportOrderStep(String lineFormat) { } public FormatterStep createFrom(String... importOrder) { - return createFrom(WILDCARDS_LAST_DEFAULT, importOrder); + return createFrom(WILDCARDS_LAST_DEFAULT, SEMANTIC_SORT_DEFAULT, TREAT_AS_PACKAGE_DEFAULT, + TREAT_AS_CLASS_DEFAULT, importOrder); } - public FormatterStep createFrom(boolean wildcardsLast, String... importOrder) { + public FormatterStep createFrom(boolean wildcardsLast, boolean semanticSort, Set treatAsPackage, + Set treatAsClass, String... importOrder) { // defensive copying and null checking List importOrderList = requireElementsNonNull(Arrays.asList(importOrder)); - return createFrom(wildcardsLast, () -> importOrderList); + return createFrom(wildcardsLast, semanticSort, treatAsPackage, treatAsClass, () -> importOrderList); } public FormatterStep createFrom(File importsFile) { - return createFrom(WILDCARDS_LAST_DEFAULT, importsFile); + return createFrom(WILDCARDS_LAST_DEFAULT, SEMANTIC_SORT_DEFAULT, TREAT_AS_PACKAGE_DEFAULT, + TREAT_AS_CLASS_DEFAULT, importsFile); } - public FormatterStep createFrom(boolean wildcardsLast, File importsFile) { + public FormatterStep createFrom(boolean wildcardsLast, boolean semanticSort, Set treatAsPackage, + Set treatAsClass, File importsFile) { Objects.requireNonNull(importsFile); - return createFrom(wildcardsLast, () -> getImportOrder(importsFile)); + return createFrom(wildcardsLast, semanticSort, treatAsPackage, treatAsClass, () -> getImportOrder(importsFile)); } - private FormatterStep createFrom(boolean wildcardsLast, Supplier> importOrder) { + private FormatterStep createFrom(boolean wildcardsLast, boolean semanticSort, Set treatAsPackage, + Set treatAsClass, Supplier> importOrder) { return FormatterStep.createLazy("importOrder", - () -> new State(importOrder.get(), lineFormat, wildcardsLast), + () -> new State(importOrder.get(), lineFormat, wildcardsLast, semanticSort, treatAsPackage, + treatAsClass), State::toFormatter); } @@ -106,15 +117,23 @@ private static final class State implements Serializable { private final List importOrder; private final String lineFormat; private final boolean wildcardsLast; + private final boolean semanticSort; + private final TreeSet treatAsPackage; + private final TreeSet treatAsClass; - State(List importOrder, String lineFormat, boolean wildcardsLast) { + State(List importOrder, String lineFormat, boolean wildcardsLast, boolean semanticSort, + Set treatAsPackage, Set treatAsClass) { this.importOrder = importOrder; this.lineFormat = lineFormat; this.wildcardsLast = wildcardsLast; + this.semanticSort = semanticSort; + this.treatAsPackage = treatAsPackage == null ? null : new TreeSet<>(treatAsPackage); + this.treatAsClass = treatAsClass == null ? null : new TreeSet<>(treatAsClass); } FormatterFunc toFormatter() { - return raw -> new ImportSorter(importOrder, wildcardsLast).format(raw, lineFormat); + return raw -> new ImportSorter(importOrder, wildcardsLast, semanticSort, treatAsPackage, treatAsClass) + .format(raw, lineFormat); } } } diff --git a/lib/src/main/java/com/diffplug/spotless/java/ImportSorter.java b/lib/src/main/java/com/diffplug/spotless/java/ImportSorter.java index edfc948487..6cd7dd4978 100644 --- a/lib/src/main/java/com/diffplug/spotless/java/ImportSorter.java +++ b/lib/src/main/java/com/diffplug/spotless/java/ImportSorter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Scanner; +import java.util.Set; /** * @author Vojtech Krasa @@ -30,10 +31,17 @@ final class ImportSorter { private final List importsOrder; private final boolean wildcardsLast; + private final boolean semanticSort; + private final Set treatAsPackage; + private final Set treatAsClass; - ImportSorter(List importsOrder, boolean wildcardsLast) { + ImportSorter(List importsOrder, boolean wildcardsLast, boolean semanticSort, Set treatAsPackage, + Set treatAsClass) { this.importsOrder = new ArrayList<>(importsOrder); this.wildcardsLast = wildcardsLast; + this.semanticSort = semanticSort; + this.treatAsPackage = treatAsPackage; + this.treatAsClass = treatAsClass; } String format(String raw, String lineFormat) { @@ -81,7 +89,8 @@ String format(String raw, String lineFormat) { } scanner.close(); - List sortedImports = ImportSorterImpl.sort(imports, importsOrder, wildcardsLast, lineFormat); + List sortedImports = ImportSorterImpl.sort(imports, importsOrder, wildcardsLast, semanticSort, + treatAsPackage, treatAsClass, lineFormat); return applyImportsToDocument(raw, firstImportLine, lastImportLine, sortedImports); } diff --git a/lib/src/main/java/com/diffplug/spotless/java/ImportSorterImpl.java b/lib/src/main/java/com/diffplug/spotless/java/ImportSorterImpl.java index ec075ffedd..4d343d41d8 100644 --- a/lib/src/main/java/com/diffplug/spotless/java/ImportSorterImpl.java +++ b/lib/src/main/java/com/diffplug/spotless/java/ImportSorterImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,8 @@ import java.io.Serializable; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.annotation.Nullable; @@ -25,14 +27,45 @@ // which itself is licensed under the Apache 2.0 license. final class ImportSorterImpl { - private final List template = new ArrayList<>(); + private static final String CATCH_ALL_SUBGROUP = ""; + private static final String STATIC_KEYWORD = "static "; + private static final String STATIC_SYMBOL = "\\#"; + private static final String SUBGROUP_SEPARATOR = "|"; + + private final List importsGroups; private final Map> matchingImports = new HashMap<>(); private final List notMatching = new ArrayList<>(); private final Set allImportOrderItems = new HashSet<>(); private final Comparator ordering; - static List sort(List imports, List importsOrder, boolean wildcardsLast, String lineFormat) { - ImportSorterImpl importsSorter = new ImportSorterImpl(importsOrder, wildcardsLast); + // An ImportsGroup is a group of imports ; each group is separated by blank lines. + // A group is composed of subgroups : imports are sorted by subgroup. + private static class ImportsGroup { + + private final List subGroups; + + public ImportsGroup(String importOrder) { + this.subGroups = Stream.of(importOrder.split("\\" + SUBGROUP_SEPARATOR, -1)) + .map(this::normalizeStatic) + .collect(Collectors.toList()); + } + + private String normalizeStatic(String subgroup) { + if (subgroup.startsWith(STATIC_SYMBOL)) { + return subgroup.replace(STATIC_SYMBOL, STATIC_KEYWORD); + } + return subgroup; + } + + public List getSubGroups() { + return subGroups; + } + } + + static List sort(List imports, List importsOrder, boolean wildcardsLast, + boolean semanticSort, Set treatAsPackage, Set treatAsClass, String lineFormat) { + ImportSorterImpl importsSorter = new ImportSorterImpl(importsOrder, wildcardsLast, semanticSort, treatAsPackage, + treatAsClass); return importsSorter.sort(imports, lineFormat); } @@ -40,43 +73,47 @@ private List sort(List imports, String lineFormat) { filterMatchingImports(imports); mergeNotMatchingItems(false); mergeNotMatchingItems(true); - mergeMatchingItems(); + List sortedImported = mergeMatchingItems(); - return getResult(lineFormat); + return getResult(sortedImported, lineFormat); } - private ImportSorterImpl(List importOrder, boolean wildcardsLast) { - List importOrderCopy = new ArrayList<>(importOrder); - normalizeStaticOrderItems(importOrderCopy); - putStaticItemIfNotExists(importOrderCopy); - template.addAll(importOrderCopy); - ordering = new OrderingComparator(wildcardsLast); - this.allImportOrderItems.addAll(importOrderCopy); + private ImportSorterImpl(List importOrder, boolean wildcardsLast, boolean semanticSort, + Set treatAsPackage, Set treatAsClass) { + importsGroups = importOrder.stream().filter(Objects::nonNull).map(ImportsGroup::new).collect(Collectors.toList()); + putStaticItemIfNotExists(importsGroups); + putCatchAllGroupIfNotExists(importsGroups); + + if (semanticSort) { + ordering = new SemanticOrderingComparator(wildcardsLast, treatAsPackage, treatAsClass); + } else { + ordering = new LexicographicalOrderingComparator(wildcardsLast); + } + + List subgroups = importsGroups.stream().map(ImportsGroup::getSubGroups).flatMap(Collection::stream).collect(Collectors.toList()); + this.allImportOrderItems.addAll(subgroups); } - private static void putStaticItemIfNotExists(List allImportOrderItems) { - boolean contains = false; + private void putStaticItemIfNotExists(List importsGroups) { + boolean catchAllSubGroupExist = importsGroups.stream().anyMatch(group -> group.getSubGroups().contains(STATIC_KEYWORD)); + if (catchAllSubGroupExist) { + return; + } + int indexOfFirstStatic = 0; - for (int i = 0; i < allImportOrderItems.size(); i++) { - String allImportOrderItem = allImportOrderItems.get(i); - if (allImportOrderItem.equals("static ")) { - contains = true; - } - if (allImportOrderItem.startsWith("static ")) { + for (int i = 0; i < importsGroups.size(); i++) { + boolean subgroupMatch = importsGroups.get(i).getSubGroups().stream().anyMatch(subgroup -> subgroup.startsWith(STATIC_KEYWORD)); + if (subgroupMatch) { indexOfFirstStatic = i; } } - if (!contains) { - allImportOrderItems.add(indexOfFirstStatic, "static "); - } + importsGroups.add(indexOfFirstStatic, new ImportsGroup(STATIC_KEYWORD)); } - private static void normalizeStaticOrderItems(List allImportOrderItems) { - for (int i = 0; i < allImportOrderItems.size(); i++) { - String s = allImportOrderItems.get(i); - if (s.startsWith("\\#")) { - allImportOrderItems.set(i, s.replace("\\#", "static ")); - } + private void putCatchAllGroupIfNotExists(List importsGroups) { + boolean catchAllSubGroupExist = importsGroups.stream().anyMatch(group -> group.getSubGroups().contains(CATCH_ALL_SUBGROUP)); + if (!catchAllSubGroupExist) { + importsGroups.add(new ImportsGroup(CATCH_ALL_SUBGROUP)); } } @@ -87,9 +124,7 @@ private void filterMatchingImports(List imports) { for (String anImport : imports) { String orderItem = getBestMatchingImportOrderItem(anImport); if (orderItem != null) { - if (!matchingImports.containsKey(orderItem)) { - matchingImports.put(orderItem, new ArrayList<>()); - } + matchingImports.computeIfAbsent(orderItem, key -> new ArrayList<>()); matchingImports.get(orderItem).add(anImport); } else { notMatching.add(anImport); @@ -116,34 +151,14 @@ private void filterMatchingImports(List imports) { * not matching means it does not match any order item, so it will be appended before or after order items */ private void mergeNotMatchingItems(boolean staticItems) { - sort(notMatching); - - int firstIndexOfOrderItem = getFirstIndexOfOrderItem(notMatching, staticItems); - int indexOfOrderItem = 0; for (String notMatchingItem : notMatching) { if (!matchesStatic(staticItems, notMatchingItem)) { continue; } boolean isOrderItem = isOrderItem(notMatchingItem, staticItems); - if (isOrderItem) { - indexOfOrderItem = template.indexOf(notMatchingItem); - } else { - if (indexOfOrderItem == 0 && firstIndexOfOrderItem != 0) { - // insert before alphabetically first order item - template.add(firstIndexOfOrderItem, notMatchingItem); - firstIndexOfOrderItem++; - } else if (firstIndexOfOrderItem == 0) { - // no order is specified - if (template.size() > 0 && (template.get(template.size() - 1).startsWith("static"))) { - // insert N after last static import - template.add(ImportSorter.N); - } - template.add(notMatchingItem); - } else { - // insert after the previous order item - template.add(indexOfOrderItem + 1, notMatchingItem); - indexOfOrderItem++; - } + if (!isOrderItem) { + matchingImports.computeIfAbsent(CATCH_ALL_SUBGROUP, key -> new ArrayList<>()); + matchingImports.get(CATCH_ALL_SUBGROUP).add(notMatchingItem); } } } @@ -153,76 +168,44 @@ private boolean isOrderItem(String notMatchingItem, boolean staticItems) { return contains && matchesStatic(staticItems, notMatchingItem); } - /** - * gets first order item from sorted input list, and finds out it's index in template. - */ - private int getFirstIndexOfOrderItem(List notMatching, boolean staticItems) { - int firstIndexOfOrderItem = 0; - for (String notMatchingItem : notMatching) { - if (!matchesStatic(staticItems, notMatchingItem)) { - continue; - } - boolean isOrderItem = isOrderItem(notMatchingItem, staticItems); - if (isOrderItem) { - firstIndexOfOrderItem = template.indexOf(notMatchingItem); - break; - } - } - return firstIndexOfOrderItem; - } - private static boolean matchesStatic(boolean staticItems, String notMatchingItem) { - boolean isStatic = notMatchingItem.startsWith("static "); + boolean isStatic = notMatchingItem.startsWith(STATIC_KEYWORD); return (isStatic && staticItems) || (!isStatic && !staticItems); } - private void mergeMatchingItems() { - for (int i = 0; i < template.size(); i++) { - String item = template.get(i); - if (allImportOrderItems.contains(item)) { - // find matching items for order item - List strings = matchingImports.get(item); + private List mergeMatchingItems() { + List template = new ArrayList<>(); + for (ImportsGroup group : importsGroups) { + boolean groupIsNotEmpty = false; + for (String subgroup : group.getSubGroups()) { + List strings = matchingImports.get(subgroup); if (strings == null || strings.isEmpty()) { - // if there is none, just remove order item - template.remove(i); - i--; continue; } + groupIsNotEmpty = true; List matchingItems = new ArrayList<>(strings); sort(matchingItems); - - // replace order item by matching import statements - // this is a mess and it is only a luck that it works :-] - template.remove(i); - if (i != 0 && !template.get(i - 1).equals(ImportSorter.N)) { - template.add(i, ImportSorter.N); - i++; - } - if (i + 1 < template.size() && !template.get(i + 1).equals(ImportSorter.N) - && !template.get(i).equals(ImportSorter.N)) { - template.add(i, ImportSorter.N); - } - template.addAll(i, matchingItems); - if (i != 0 && !template.get(i - 1).equals(ImportSorter.N)) { - template.add(i, ImportSorter.N); - } - + template.addAll(matchingItems); + } + if (groupIsNotEmpty) { + template.add(ImportSorter.N); } } // if there is \n on the end, remove it - if (template.size() > 0 && template.get(template.size() - 1).equals(ImportSorter.N)) { + if (!template.isEmpty() && template.get(template.size() - 1).equals(ImportSorter.N)) { template.remove(template.size() - 1); } + return template; } private void sort(List items) { items.sort(ordering); } - private List getResult(String lineFormat) { + private List getResult(List sortedImported, String lineFormat) { List strings = new ArrayList<>(); - for (String s : template) { + for (String s : sortedImported) { if (s.equals(ImportSorter.N)) { strings.add(s); } else { @@ -257,30 +240,187 @@ private List getResult(String lineFormat) { return null; } - private static class OrderingComparator implements Comparator, Serializable { + private static int compareWithWildcare(String string1, String string2, boolean wildcardsLast) { + int string1WildcardIndex = string1.indexOf('*'); + int string2WildcardIndex = string2.indexOf('*'); + boolean string1IsWildcard = string1WildcardIndex >= 0; + boolean string2IsWildcard = string2WildcardIndex >= 0; + if (string1IsWildcard == string2IsWildcard) { + return string1.compareTo(string2); + } + int prefixLength = string1IsWildcard ? string1WildcardIndex : string2WildcardIndex; + boolean samePrefix = string1.regionMatches(0, string2, 0, prefixLength); + if (!samePrefix) { + return string1.compareTo(string2); + } + return (string1IsWildcard == wildcardsLast) ? 1 : -1; + } + + private static class LexicographicalOrderingComparator implements Comparator, Serializable { private static final long serialVersionUID = 1; private final boolean wildcardsLast; - private OrderingComparator(boolean wildcardsLast) { + private LexicographicalOrderingComparator(boolean wildcardsLast) { this.wildcardsLast = wildcardsLast; } @Override public int compare(String string1, String string2) { - int string1WildcardIndex = string1.indexOf('*'); - int string2WildcardIndex = string2.indexOf('*'); - boolean string1IsWildcard = string1WildcardIndex >= 0; - boolean string2IsWildcard = string2WildcardIndex >= 0; - if (string1IsWildcard == string2IsWildcard) { - return string1.compareTo(string2); + return compareWithWildcare(string1, string2, wildcardsLast); + } + } + + private static class SemanticOrderingComparator implements Comparator, Serializable { + private static final long serialVersionUID = 1; + + private final boolean wildcardsLast; + private final Set treatAsPackage; + private final Set treatAsClass; + + private SemanticOrderingComparator(boolean wildcardsLast, Set treatAsPackage, + Set treatAsClass) { + this.wildcardsLast = wildcardsLast; + this.treatAsPackage = treatAsPackage; + this.treatAsClass = treatAsClass; + } + + @Override + public int compare(String string1, String string2) { + /* + * Ordering uses semantics of the import string by splitting it into package, + * class name(s) and static member (for static imports) and then comparing by + * each of those three substrings in sequence. + * + * When comparing static imports, the last segment in the dot-separated string + * is considered to be the member (field, method, type) name. + * + * The first segment starting with an upper case letter is considered to be the + * (first) class name. Since this comparator has no actual type information, + * this auto-detection will fail for upper case package names and lower case + * class names. treatAsPackage and treatAsClass can be used respectively to + * provide hints to the auto-detection. + */ + if (string1.startsWith(STATIC_KEYWORD)) { + String[] split = splitFqcnAndMember(string1); + String fqcn1 = split[0]; + String member1 = split[1]; + + split = splitFqcnAndMember(string2); + String fqcn2 = split[0]; + String member2 = split[1]; + + int result = compareFullyQualifiedClassName(fqcn1, fqcn2); + if (result != 0) + return result; + + return compareWithWildcare(member1, member2, wildcardsLast); + } else { + return compareFullyQualifiedClassName(string1, string2); + } + } + + /** + * Compares two fully qualified class names by splitting them into package and + * (nested) class names. + */ + private int compareFullyQualifiedClassName(String fqcn1, String fqcn2) { + String[] split = splitPackageAndClasses(fqcn1); + String p1 = split[0]; + String c1 = split[1]; + + split = splitPackageAndClasses(fqcn2); + String p2 = split[0]; + String c2 = split[1]; + + int result = p1.compareTo(p2); + if (result != 0) + return result; + + return compareWithWildcare(c1, c2, wildcardsLast); + } + + /** + * Splits the provided static import string into fully qualified class name and + * the imported static member (field, method or type). + */ + private String[] splitFqcnAndMember(String importString) { + String s = importString.substring(STATIC_KEYWORD.length()).trim(); + + /* + * Static imports always contain a member or wildcard and it's always the last + * segment. + */ + int dot = s.lastIndexOf("."); + String fqcn = s.substring(0, dot); + String member = s.substring(dot + 1); + return new String[]{fqcn, member}; + } + + /** + * Splits the fully qualified class name into package and class name(s). + */ + private String[] splitPackageAndClasses(String fqcn) { + String packageNames = null; + String classNames = null; + + /* + * The first segment that starts with an upper case letter starts the class + * name(s), unless it matches treatAsPackage (then it's explicitly declared as + * package via configuration). If no segment starts with an upper case letter + * then the last segment must be a class name (unless the method input is + * garbage). + */ + int dot = fqcn.indexOf('.'); + while (dot > -1) { + int nextDot = fqcn.indexOf('.', dot + 1); + if (nextDot > -1) { + if (Character.isUpperCase(fqcn.charAt(dot + 1))) { + // if upper case, check if should be treated as package nonetheless + if (!treatAsPackage(fqcn.substring(0, nextDot))) { + packageNames = fqcn.substring(0, dot); + classNames = fqcn.substring(dot + 1); + break; + } + } else { + // if lower case, check if should be treated as class nonetheless + if (treatAsClass(fqcn.substring(0, nextDot))) { + packageNames = fqcn.substring(0, dot); + classNames = fqcn.substring(dot + 1); + break; + } + } + } + + dot = nextDot; } - int prefixLength = string1IsWildcard ? string1WildcardIndex : string2WildcardIndex; - boolean samePrefix = string1.regionMatches(0, string2, 0, prefixLength); - if (!samePrefix) { - return string1.compareTo(string2); + + if (packageNames == null) { + int i = fqcn.lastIndexOf("."); + packageNames = fqcn.substring(0, i); + classNames = fqcn.substring(i + 1); } - return (string1IsWildcard == wildcardsLast) ? 1 : -1; + + return new String[]{packageNames, classNames}; } + + /** + * Returns whether the provided prefix matches any entry of + * {@code treatAsPackage}. + */ + private boolean treatAsPackage(String prefix) { + // This would be the place to introduce wild cards or even regex matching. + return treatAsPackage != null && treatAsPackage.contains(prefix); + } + + /** + * Returns whether the provided prefix name matches any entry of + * {@code treatAsClass}. + */ + private boolean treatAsClass(String prefix) { + // This would be the place to introduce wild cards or even regex matching. + return treatAsClass != null && treatAsClass.contains(prefix); + } + } } diff --git a/lib/src/main/java/com/diffplug/spotless/java/ModuleHelper.java b/lib/src/main/java/com/diffplug/spotless/java/ModuleHelper.java index 415c58d69b..e05a6a8b7f 100644 --- a/lib/src/main/java/com/diffplug/spotless/java/ModuleHelper.java +++ b/lib/src/main/java/com/diffplug/spotless/java/ModuleHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,12 +28,17 @@ import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.diffplug.spotless.Jvm; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import sun.misc.Unsafe; final class ModuleHelper { + private static final Logger LOGGER = LoggerFactory.getLogger(ModuleHelper.class); + // prevent direct instantiation private ModuleHelper() {} @@ -66,11 +71,11 @@ public static synchronized void doOpenInternalPackagesIfRequired() { for (String name : failedToOpen) { message.append(String.format("--add-opens jdk.compiler/%s=ALL-UNNAMED", name)); } - System.err.println(message); + LOGGER.warn("{}", message); } } } catch (Throwable e) { - System.err.println("WARNING: Failed to check for unavailable JDK packages. Reason: " + e.getMessage()); + LOGGER.error("WARNING: Failed to check for available JDK packages.", e); } } diff --git a/lib/src/main/java/com/diffplug/spotless/java/PalantirJavaFormatStep.java b/lib/src/main/java/com/diffplug/spotless/java/PalantirJavaFormatStep.java index 89673787d6..a670a6fea7 100644 --- a/lib/src/main/java/com/diffplug/spotless/java/PalantirJavaFormatStep.java +++ b/lib/src/main/java/com/diffplug/spotless/java/PalantirJavaFormatStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,9 +27,11 @@ public class PalantirJavaFormatStep { // prevent direct instantiation private PalantirJavaFormatStep() {} + private static final boolean DEFAULT_FORMAT_JAVADOC = false; + private static final String DEFAULT_STYLE = "PALANTIR"; private static final String NAME = "palantir-java-format"; - private static final String MAVEN_COORDINATE = "com.palantir.javaformat:palantir-java-format:"; - private static final Jvm.Support JVM_SUPPORT = Jvm. support(NAME).add(8, "1.1.0").add(11, "2.10.0"); + public static final String MAVEN_COORDINATE = "com.palantir.javaformat:palantir-java-format:"; + private static final Jvm.Support JVM_SUPPORT = Jvm. support(NAME).add(8, "1.1.0").add(11, "2.28.0").add(21, "2.39.0"); /** Creates a step which formats everything - code, import order, and unused imports. */ public static FormatterStep create(Provisioner provisioner) { @@ -38,11 +40,28 @@ public static FormatterStep create(Provisioner provisioner) { /** Creates a step which formats everything - code, import order, and unused imports. */ public static FormatterStep create(String version, Provisioner provisioner) { + return create(version, defaultStyle(), provisioner); + } + + /** + * Creates a step which formats code, import order, and unused imports, but not Java docs. And with the given format + * style. + */ + public static FormatterStep create(String version, String style, Provisioner provisioner) { + return create(version, style, DEFAULT_FORMAT_JAVADOC, provisioner); + } + + /** + * Creates a step which formats everything - code, import order, unused imports, and Java docs. And with the given + * format style. + */ + public static FormatterStep create(String version, String style, boolean formatJavadoc, Provisioner provisioner) { Objects.requireNonNull(version, "version"); + Objects.requireNonNull(style, "style"); Objects.requireNonNull(provisioner, "provisioner"); return FormatterStep.createLazy(NAME, - () -> new State(JarState.from(MAVEN_COORDINATE + version, provisioner), version), + () -> new State(JarState.from(MAVEN_COORDINATE + version, provisioner), version, style, formatJavadoc), State::createFormat); } @@ -51,6 +70,16 @@ public static String defaultVersion() { return JVM_SUPPORT.getRecommendedFormatterVersion(); } + /** Get default style */ + public static String defaultStyle() { + return DEFAULT_STYLE; + } + + /** Get default for whether Java docs should be formatted */ + public static boolean defaultFormatJavadoc() { + return DEFAULT_FORMAT_JAVADOC; + } + private static final class State implements Serializable { private static final long serialVersionUID = 1L; @@ -58,18 +87,24 @@ private static final class State implements Serializable { private final JarState jarState; /** Version of the formatter jar. */ private final String formatterVersion; + private final String style; + /** Whether to format Java docs. */ + private final boolean formatJavadoc; - State(JarState jarState, String formatterVersion) { + State(JarState jarState, String formatterVersion, String style, boolean formatJavadoc) { ModuleHelper.doOpenInternalPackagesIfRequired(); this.jarState = jarState; this.formatterVersion = formatterVersion; + this.style = style; + this.formatJavadoc = formatJavadoc; } FormatterFunc createFormat() throws Exception { final ClassLoader classLoader = jarState.getClassLoader(); final Class formatterFunc = classLoader.loadClass("com.diffplug.spotless.glue.pjf.PalantirJavaFormatFormatterFunc"); - final Constructor constructor = formatterFunc.getConstructor(); - return JVM_SUPPORT.suggestLaterVersionOnError(formatterVersion, (FormatterFunc) constructor.newInstance()); + // 1st arg is "style", 2nd arg is "formatJavadoc" + final Constructor constructor = formatterFunc.getConstructor(String.class, boolean.class); + return JVM_SUPPORT.suggestLaterVersionOnError(formatterVersion, (FormatterFunc) constructor.newInstance(style, formatJavadoc)); } } } diff --git a/lib/src/main/java/com/diffplug/spotless/java/RemoveUnusedImportsStep.java b/lib/src/main/java/com/diffplug/spotless/java/RemoveUnusedImportsStep.java index 91ce3cc023..a4c460e929 100644 --- a/lib/src/main/java/com/diffplug/spotless/java/RemoveUnusedImportsStep.java +++ b/lib/src/main/java/com/diffplug/spotless/java/RemoveUnusedImportsStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,22 +15,47 @@ */ package com.diffplug.spotless.java; +import java.util.Arrays; import java.util.Objects; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.Provisioner; -/** Uses google-java-format, but only to remove unused imports. */ +/** Uses google-java-format or cleanthat.UnnecessaryImport, but only to remove unused imports. */ public class RemoveUnusedImportsStep { + static final String NAME = "removeUnusedImports"; + + static final String GJF = "google-java-format"; + static final String CLEANTHAT = "cleanthat-javaparser-unnecessaryimport"; + + // https://github.com/solven-eu/cleanthat/blob/master/java/src/main/java/eu/solven/cleanthat/engine/java/refactorer/mutators/UnnecessaryImport.java + private static final String CLEANTHAT_MUTATOR = "UnnecessaryImport"; + // prevent direct instantiation private RemoveUnusedImportsStep() {} - static final String NAME = "removeUnusedImports"; + public static final String defaultFormatter() { + return GJF; + } public static FormatterStep create(Provisioner provisioner) { + // The default importRemover is GJF + return create(GJF, provisioner); + } + + public static FormatterStep create(String unusedImportRemover, Provisioner provisioner) { Objects.requireNonNull(provisioner, "provisioner"); - return FormatterStep.createLazy(NAME, - () -> new GoogleJavaFormatStep.State(NAME, GoogleJavaFormatStep.defaultVersion(), provisioner), - GoogleJavaFormatStep.State::createRemoveUnusedImportsOnly); + + if (GJF.equals(unusedImportRemover)) { + return FormatterStep.createLazy(NAME, + () -> new GoogleJavaFormatStep.State(NAME, GoogleJavaFormatStep.defaultVersion(), provisioner), + GoogleJavaFormatStep.State::createRemoveUnusedImportsOnly); + } else if (CLEANTHAT.equals(unusedImportRemover)) { + return FormatterStep.createLazy(NAME, + () -> new CleanthatJavaStep.JavaRefactorerState(NAME, CleanthatJavaStep.defaultGroupArtifact(), CleanthatJavaStep.defaultVersion(), "99.9", Arrays.asList(CLEANTHAT_MUTATOR), Arrays.asList(), false, provisioner), + CleanthatJavaStep.JavaRefactorerState::createFormat); + } else { + throw new IllegalArgumentException("Invalid unusedImportRemover: " + unusedImportRemover); + } } } diff --git a/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java b/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java new file mode 100644 index 0000000000..f0dba8f072 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/json/JacksonConfig.java @@ -0,0 +1,52 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.json; + +import java.io.Serializable; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * A DTO holding the basic for Jackson-based formatters + */ +public class JacksonConfig implements Serializable { + private static final long serialVersionUID = 1L; + + private static final Map DEFAULT_FEATURE_TOGGLES; + + static { + Map defaultFeatureToggles = new LinkedHashMap<>(); + // We activate by default the PrettyPrinter from Jackson + // @see com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT + defaultFeatureToggles.put("INDENT_OUTPUT", true); + DEFAULT_FEATURE_TOGGLES = defaultFeatureToggles; + } + + protected Map featureToToggle = new LinkedHashMap<>(DEFAULT_FEATURE_TOGGLES); + + public Map getFeatureToToggle() { + return Collections.unmodifiableMap(featureToToggle); + } + + public void setFeatureToToggle(Map featureToToggle) { + this.featureToToggle = featureToToggle; + } + + public void appendFeatureToToggle(Map features) { + this.featureToToggle.putAll(features); + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonConfig.java b/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonConfig.java new file mode 100644 index 0000000000..efff594663 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonConfig.java @@ -0,0 +1,59 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.json; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Specialization of {@link JacksonConfig} for JSON documents + */ +public class JacksonJsonConfig extends JacksonConfig { + private static final long serialVersionUID = 1L; + + protected Map jsonFeatureToToggle = new LinkedHashMap<>(); + + // https://github.com/revelc/formatter-maven-plugin/pull/280 + // By default, Jackson adds a ' ' before separator, which is not standard with most IDE/JSON libraries + protected boolean spaceBeforeSeparator = false; + + public Map getJsonFeatureToToggle() { + return Collections.unmodifiableMap(jsonFeatureToToggle); + } + + /** + * Refers to com.fasterxml.jackson.core.JsonGenerator.Feature + */ + public void setJsonFeatureToToggle(Map jsonFeatureToToggle) { + this.jsonFeatureToToggle = jsonFeatureToToggle; + } + + /** + * Refers to com.fasterxml.jackson.core.JsonGenerator.Feature + */ + public void appendJsonFeatureToToggle(Map features) { + this.jsonFeatureToToggle.putAll(features); + } + + public boolean isSpaceBeforeSeparator() { + return spaceBeforeSeparator; + } + + public void setSpaceBeforeSeparator(boolean spaceBeforeSeparator) { + this.spaceBeforeSeparator = spaceBeforeSeparator; + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonStep.java b/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonStep.java new file mode 100644 index 0000000000..94c48e0c5c --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/json/JacksonJsonStep.java @@ -0,0 +1,79 @@ +/* + * Copyright 2021-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.json; + +import java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Objects; + +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.JarState; +import com.diffplug.spotless.Provisioner; + +/** + * Simple YAML formatter which reformats the file according to Jackson YAMLFactory. + */ +// https://stackoverflow.com/questions/14515994/convert-json-string-to-pretty-print-json-output-using-jackson +public class JacksonJsonStep { + static final String MAVEN_COORDINATE = "com.fasterxml.jackson.core:jackson-databind:"; + // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind + static final String DEFAULT_VERSION = "2.14.2"; + + private JacksonJsonStep() {} + + public static String defaultVersion() { + return DEFAULT_VERSION; + } + + public static FormatterStep create(JacksonJsonConfig jacksonConfig, + String jacksonVersion, + Provisioner provisioner) { + Objects.requireNonNull(provisioner, "provisioner cannot be null"); + return FormatterStep.createLazy("json", + () -> new State(jacksonConfig, jacksonVersion, provisioner), + State::toFormatter); + } + + public static FormatterStep create(Provisioner provisioner) { + return create(new JacksonJsonConfig(), defaultVersion(), provisioner); + } + + private static final class State implements Serializable { + private static final long serialVersionUID = 1L; + + private final JacksonConfig jacksonConfig; + + private final JarState jarState; + + private State(JacksonConfig jacksonConfig, + String jacksonVersion, + Provisioner provisioner) throws IOException { + this.jacksonConfig = jacksonConfig; + + this.jarState = JarState.from(JacksonJsonStep.MAVEN_COORDINATE + jacksonVersion, provisioner); + } + + FormatterFunc toFormatter() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, + InstantiationException, IllegalAccessException { + Class formatterFunc = jarState.getClassLoader().loadClass("com.diffplug.spotless.glue.json.JacksonJsonFormatterFunc"); + Constructor constructor = formatterFunc.getConstructor(JacksonJsonConfig.class); + return (FormatterFunc) constructor.newInstance(jacksonConfig); + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/json/JsonPatchStep.java b/lib/src/main/java/com/diffplug/spotless/json/JsonPatchStep.java new file mode 100644 index 0000000000..71f4380264 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/json/JsonPatchStep.java @@ -0,0 +1,90 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.json; + +import java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.JarState; +import com.diffplug.spotless.Provisioner; + +public class JsonPatchStep { + // https://mvnrepository.com/artifact/com.flipkart.zjsonpatch/zjsonpatch + static final String MAVEN_COORDINATE = "com.flipkart.zjsonpatch:zjsonpatch"; + static final String DEFAULT_VERSION = "0.4.14"; + + private JsonPatchStep() {} + + public static FormatterStep create(String patchString, Provisioner provisioner) { + return create(DEFAULT_VERSION, patchString, provisioner); + } + + public static FormatterStep create(String zjsonPatchVersion, String patchString, Provisioner provisioner) { + Objects.requireNonNull(zjsonPatchVersion, "zjsonPatchVersion cannot be null"); + Objects.requireNonNull(patchString, "patchString cannot be null"); + Objects.requireNonNull(provisioner, "provisioner cannot be null"); + return FormatterStep.createLazy("apply-json-patch", () -> new State(zjsonPatchVersion, patchString, provisioner), State::toFormatter); + } + + public static FormatterStep create(List> patch, Provisioner provisioner) { + return create(DEFAULT_VERSION, patch, provisioner); + } + + public static FormatterStep create(String zjsonPatchVersion, List> patch, Provisioner provisioner) { + Objects.requireNonNull(zjsonPatchVersion, "zjsonPatchVersion cannot be null"); + Objects.requireNonNull(patch, "patch cannot be null"); + Objects.requireNonNull(provisioner, "provisioner cannot be null"); + return FormatterStep.createLazy("apply-json-patch", () -> new State(zjsonPatchVersion, patch, provisioner), State::toFormatter); + } + + static final class State implements Serializable { + private static final long serialVersionUID = 1L; + + private final JarState jarState; + private final List> patch; + private final String patchString; + + private State(String zjsonPatchVersion, List> patch, Provisioner provisioner) throws IOException { + this.jarState = JarState.from(MAVEN_COORDINATE + ":" + zjsonPatchVersion, provisioner); + this.patch = patch; + this.patchString = null; + } + + private State(String zjsonPatchVersion, String patchString, Provisioner provisioner) throws IOException { + this.jarState = JarState.from(MAVEN_COORDINATE + ":" + zjsonPatchVersion, provisioner); + this.patch = null; + this.patchString = patchString; + } + + FormatterFunc toFormatter() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + Class formatterFunc = jarState.getClassLoader().loadClass("com.diffplug.spotless.glue.json.JsonPatchFormatterFunc"); + if (this.patch != null) { + Constructor constructor = formatterFunc.getConstructor(List.class); + return (FormatterFunc) constructor.newInstance(patch); + } else { + Constructor constructor = formatterFunc.getConstructor(String.class); + return (FormatterFunc) constructor.newInstance(patchString); + } + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/json/JsonSimpleStep.java b/lib/src/main/java/com/diffplug/spotless/json/JsonSimpleStep.java index a970149133..85ccbfa6e2 100644 --- a/lib/src/main/java/com/diffplug/spotless/json/JsonSimpleStep.java +++ b/lib/src/main/java/com/diffplug/spotless/json/JsonSimpleStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -81,7 +81,7 @@ FormatterFunc toFormatter() { return format(arrayConstructor, arrayToString, s); } - throw new AssertionError(String.format("Unable to determine JSON type, expected a '{' or '[' but found '%s'", first)); + throw new IllegalArgumentException(String.format("Unable to determine JSON type, expected a '{' or '[' but found '%s'", first)); }; } @@ -89,8 +89,8 @@ private String format(Constructor constructor, Method toString, String input) try { Object parsed = constructor.newInstance(input); return toString.invoke(parsed, indentSpaces) + "\n"; - } catch (InvocationTargetException ex) { - throw new AssertionError("Unable to format JSON", ex.getCause()); + } catch (InvocationTargetException e) { + throw new IllegalArgumentException("Unable to format JSON", e); } } } diff --git a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonBuilderWrapper.java b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonBuilderWrapper.java deleted file mode 100644 index c2e56f39b8..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonBuilderWrapper.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2022 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.json.gson; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; - -import com.diffplug.spotless.JarState; - -class GsonBuilderWrapper extends GsonWrapperBase { - - private final Constructor constructor; - private final Method serializeNullsMethod; - private final Method disableHtmlEscapingMethod; - private final Method createMethod; - - GsonBuilderWrapper(JarState jarState) { - Class clazz = loadClass(jarState.getClassLoader(), "com.google.gson.GsonBuilder"); - this.constructor = getConstructor(clazz); - this.serializeNullsMethod = getMethod(clazz, "serializeNulls"); - this.disableHtmlEscapingMethod = getMethod(clazz, "disableHtmlEscaping"); - this.createMethod = getMethod(clazz, "create"); - } - - Object createGsonBuilder() { - return newInstance(constructor); - } - - Object serializeNulls(Object gsonBuilder) { - return invoke(serializeNullsMethod, gsonBuilder); - } - - Object disableHtmlEscaping(Object gsonBuilder) { - return invoke(disableHtmlEscapingMethod, gsonBuilder); - } - - Object create(Object gsonBuilder) { - return invoke(createMethod, gsonBuilder); - } - -} diff --git a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonConfig.java b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonConfig.java new file mode 100644 index 0000000000..a11c1ee296 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonConfig.java @@ -0,0 +1,66 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.json.gson; + +import java.io.Serializable; + +public class GsonConfig implements Serializable { + private static final long serialVersionUID = 6039715618937332633L; + + private boolean sortByKeys; + private boolean escapeHtml; + private int indentSpaces; + private String version; + + public GsonConfig(boolean sortByKeys, boolean escapeHtml, int indentSpaces, String version) { + this.sortByKeys = sortByKeys; + this.escapeHtml = escapeHtml; + this.indentSpaces = indentSpaces; + this.version = version; + } + + public boolean isSortByKeys() { + return sortByKeys; + } + + public void setSortByKeys(boolean sortByKeys) { + this.sortByKeys = sortByKeys; + } + + public boolean isEscapeHtml() { + return escapeHtml; + } + + public void setEscapeHtml(boolean escapeHtml) { + this.escapeHtml = escapeHtml; + } + + public int getIndentSpaces() { + return indentSpaces; + } + + public void setIndentSpaces(int indentSpaces) { + this.indentSpaces = indentSpaces; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java index 06519ae39d..ec90255b77 100644 --- a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java +++ b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 DiffPlug + * Copyright 2022-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,8 @@ import java.io.IOException; import java.io.Serializable; -import java.io.StringWriter; -import java.util.Collections; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.util.Objects; import com.diffplug.spotless.FormatterFunc; @@ -28,78 +28,38 @@ public class GsonStep { private static final String MAVEN_COORDINATES = "com.google.code.gson:gson"; + private static final String INCOMPATIBLE_ERROR_MESSAGE = "There was a problem interacting with Gson; maybe you set an incompatible version?"; + @Deprecated public static FormatterStep create(int indentSpaces, boolean sortByKeys, boolean escapeHtml, String version, Provisioner provisioner) { + return create(new GsonConfig(sortByKeys, escapeHtml, indentSpaces, version), provisioner); + } + + public static FormatterStep create(GsonConfig gsonConfig, Provisioner provisioner) { Objects.requireNonNull(provisioner, "provisioner cannot be null"); - return FormatterStep.createLazy("gson", () -> new State(indentSpaces, sortByKeys, escapeHtml, version, provisioner), State::toFormatter); + return FormatterStep.createLazy("gson", () -> new State(gsonConfig, provisioner), State::toFormatter); } private static final class State implements Serializable { - private static final long serialVersionUID = -1493479043249379485L; + private static final long serialVersionUID = -3240568265160440420L; - private final int indentSpaces; - private final boolean sortByKeys; - private final boolean escapeHtml; private final JarState jarState; + private final GsonConfig gsonConfig; - private State(int indentSpaces, boolean sortByKeys, boolean escapeHtml, String version, Provisioner provisioner) throws IOException { - this.indentSpaces = indentSpaces; - this.sortByKeys = sortByKeys; - this.escapeHtml = escapeHtml; - this.jarState = JarState.from(MAVEN_COORDINATES + ":" + version, provisioner); + private State(GsonConfig gsonConfig, Provisioner provisioner) throws IOException { + this.gsonConfig = gsonConfig; + this.jarState = JarState.from(MAVEN_COORDINATES + ":" + gsonConfig.getVersion(), provisioner); } FormatterFunc toFormatter() { - JsonWriterWrapper jsonWriterWrapper = new JsonWriterWrapper(jarState); - JsonElementWrapper jsonElementWrapper = new JsonElementWrapper(jarState); - JsonObjectWrapper jsonObjectWrapper = new JsonObjectWrapper(jarState, jsonElementWrapper); - GsonBuilderWrapper gsonBuilderWrapper = new GsonBuilderWrapper(jarState); - GsonWrapper gsonWrapper = new GsonWrapper(jarState, jsonElementWrapper, jsonWriterWrapper); - - Object gsonBuilder = gsonBuilderWrapper.serializeNulls(gsonBuilderWrapper.createGsonBuilder()); - if (!escapeHtml) { - gsonBuilder = gsonBuilderWrapper.disableHtmlEscaping(gsonBuilder); + try { + Class formatterFunc = jarState.getClassLoader().loadClass("com.diffplug.spotless.glue.gson.GsonFormatterFunc"); + Constructor constructor = formatterFunc.getConstructor(GsonConfig.class); + return (FormatterFunc) constructor.newInstance(gsonConfig); + } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException + | InstantiationException | IllegalAccessException | NoClassDefFoundError cause) { + throw new IllegalStateException(INCOMPATIBLE_ERROR_MESSAGE, cause); } - Object gson = gsonBuilderWrapper.create(gsonBuilder); - - return inputString -> { - String result; - if (inputString.isEmpty()) { - result = ""; - } else { - Object jsonElement = gsonWrapper.fromJson(gson, inputString, jsonElementWrapper.getWrappedClass()); - if (jsonElement == null) { - throw new AssertionError(GsonWrapperBase.FAILED_TO_PARSE_ERROR_MESSAGE); - } - if (sortByKeys && jsonElementWrapper.isJsonObject(jsonElement)) { - jsonElement = sortByKeys(jsonObjectWrapper, jsonElementWrapper, jsonElement); - } - try (StringWriter stringWriter = new StringWriter()) { - Object jsonWriter = jsonWriterWrapper.createJsonWriter(stringWriter); - jsonWriterWrapper.setIndent(jsonWriter, generateIndent(indentSpaces)); - gsonWrapper.toJson(gson, jsonElement, jsonWriter); - result = stringWriter + "\n"; - } - } - return result; - }; - } - - private Object sortByKeys(JsonObjectWrapper jsonObjectWrapper, JsonElementWrapper jsonElementWrapper, Object jsonObject) { - Object result = jsonObjectWrapper.createJsonObject(); - jsonObjectWrapper.keySet(jsonObject).stream().sorted() - .forEach(key -> { - Object element = jsonObjectWrapper.get(jsonObject, key); - if (jsonElementWrapper.isJsonObject(element)) { - element = sortByKeys(jsonObjectWrapper, jsonElementWrapper, element); - } - jsonObjectWrapper.add(result, key, element); - }); - return result; - } - - private String generateIndent(int indentSpaces) { - return String.join("", Collections.nCopies(indentSpaces, " ")); } } diff --git a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonWrapper.java b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonWrapper.java deleted file mode 100644 index eaca499eed..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonWrapper.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2022 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.json.gson; - -import java.lang.reflect.Method; - -import com.diffplug.spotless.JarState; - -class GsonWrapper extends GsonWrapperBase { - - private final Method fromJsonMethod; - private final Method toJsonMethod; - - GsonWrapper(JarState jarState, JsonElementWrapper jsonElementWrapper, JsonWriterWrapper jsonWriterWrapper) { - Class clazz = loadClass(jarState.getClassLoader(), "com.google.gson.Gson"); - this.fromJsonMethod = getMethod(clazz, "fromJson", String.class, Class.class); - this.toJsonMethod = getMethod(clazz, "toJson", jsonElementWrapper.getWrappedClass(), jsonWriterWrapper.getWrappedClass()); - } - - Object fromJson(Object gson, String json, Class type) { - return invoke(fromJsonMethod, gson, json, type); - } - - void toJson(Object gson, Object jsonElement, Object jsonWriter) { - invoke(toJsonMethod, gson, jsonElement, jsonWriter); - } - -} diff --git a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonWrapperBase.java b/lib/src/main/java/com/diffplug/spotless/json/gson/GsonWrapperBase.java deleted file mode 100644 index 24d12a5722..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/json/gson/GsonWrapperBase.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2022 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.json.gson; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -abstract class GsonWrapperBase { - - static final String INCOMPATIBLE_ERROR_MESSAGE = "There was a problem interacting with Gson; maybe you set an incompatible version?"; - static final String FAILED_TO_PARSE_ERROR_MESSAGE = "Unable to format JSON"; - - protected final Class loadClass(ClassLoader classLoader, String className) { - try { - return classLoader.loadClass(className); - } catch (ClassNotFoundException cause) { - throw new IllegalStateException(INCOMPATIBLE_ERROR_MESSAGE, cause); - } - } - - protected final Constructor getConstructor(Class clazz, Class... argumentTypes) { - try { - return clazz.getConstructor(argumentTypes); - } catch (NoSuchMethodException cause) { - throw new IllegalStateException(INCOMPATIBLE_ERROR_MESSAGE, cause); - } - } - - protected final Method getMethod(Class clazz, String name, Class... argumentTypes) { - try { - return clazz.getMethod(name, argumentTypes); - } catch (NoSuchMethodException cause) { - throw new IllegalStateException(INCOMPATIBLE_ERROR_MESSAGE, cause); - } - } - - protected final T newInstance(Constructor constructor, Object... args) { - try { - return constructor.newInstance(args); - } catch (InstantiationException | IllegalAccessException cause) { - throw new IllegalStateException(INCOMPATIBLE_ERROR_MESSAGE, cause); - } catch (InvocationTargetException cause) { - throw new AssertionError(FAILED_TO_PARSE_ERROR_MESSAGE, cause.getCause()); - } - } - - protected Object invoke(Method method, Object targetObject, Object... args) { - try { - return method.invoke(targetObject, args); - } catch (IllegalAccessException cause) { - throw new IllegalStateException(INCOMPATIBLE_ERROR_MESSAGE, cause); - } catch (InvocationTargetException cause) { - throw new AssertionError(FAILED_TO_PARSE_ERROR_MESSAGE, cause.getCause()); - } - } - -} diff --git a/lib/src/main/java/com/diffplug/spotless/json/gson/JsonElementWrapper.java b/lib/src/main/java/com/diffplug/spotless/json/gson/JsonElementWrapper.java deleted file mode 100644 index ffdfa649ce..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/json/gson/JsonElementWrapper.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2022 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.json.gson; - -import java.lang.reflect.Method; - -import com.diffplug.spotless.JarState; - -class JsonElementWrapper extends GsonWrapperBase { - - private final Class clazz; - private final Method isJsonObjectMethod; - - JsonElementWrapper(JarState jarState) { - this.clazz = loadClass(jarState.getClassLoader(), "com.google.gson.JsonElement"); - this.isJsonObjectMethod = getMethod(clazz, "isJsonObject"); - } - - boolean isJsonObject(Object jsonElement) { - return (boolean) invoke(isJsonObjectMethod, jsonElement); - } - - Class getWrappedClass() { - return clazz; - } - -} diff --git a/lib/src/main/java/com/diffplug/spotless/json/gson/JsonObjectWrapper.java b/lib/src/main/java/com/diffplug/spotless/json/gson/JsonObjectWrapper.java deleted file mode 100644 index 35ec0d876b..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/json/gson/JsonObjectWrapper.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2022 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.json.gson; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; -import java.util.Set; - -import com.diffplug.spotless.JarState; - -class JsonObjectWrapper extends GsonWrapperBase { - - private final Constructor constructor; - private final Method keySetMethod; - private final Method getMethod; - private final Method addMethod; - - JsonObjectWrapper(JarState jarState, JsonElementWrapper jsonElementWrapper) { - Class clazz = loadClass(jarState.getClassLoader(), "com.google.gson.JsonObject"); - this.constructor = getConstructor(clazz); - this.keySetMethod = getMethod(clazz, "keySet"); - this.getMethod = getMethod(clazz, "get", String.class); - this.addMethod = getMethod(clazz, "add", String.class, jsonElementWrapper.getWrappedClass()); - } - - Object createJsonObject() { - return newInstance(constructor); - } - - @SuppressWarnings("unchecked") - Set keySet(Object jsonObject) { - return (Set) invoke(keySetMethod, jsonObject); - } - - Object get(Object jsonObject, String key) { - return invoke(getMethod, jsonObject, key); - } - - void add(Object jsonObject, String key, Object element) { - invoke(addMethod, jsonObject, key, element); - } - -} diff --git a/lib/src/main/java/com/diffplug/spotless/json/gson/JsonWriterWrapper.java b/lib/src/main/java/com/diffplug/spotless/json/gson/JsonWriterWrapper.java deleted file mode 100644 index c9d682e2c2..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/json/gson/JsonWriterWrapper.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2022 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.json.gson; - -import java.io.Writer; -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; - -import com.diffplug.spotless.JarState; - -class JsonWriterWrapper extends GsonWrapperBase { - - private final Class clazz; - private final Constructor constructor; - private final Method setIndentMethod; - - JsonWriterWrapper(JarState jarState) { - this.clazz = loadClass(jarState.getClassLoader(), "com.google.gson.stream.JsonWriter"); - this.constructor = getConstructor(clazz, Writer.class); - this.setIndentMethod = getMethod(clazz, "setIndent", String.class); - } - - Object createJsonWriter(Writer writer) { - return newInstance(constructor, writer); - } - - void setIndent(Object jsonWriter, String indent) { - invoke(setIndentMethod, jsonWriter, indent); - } - - Class getWrappedClass() { - return clazz; - } - -} diff --git a/lib/src/main/java/com/diffplug/spotless/json/package-info.java b/lib/src/main/java/com/diffplug/spotless/json/package-info.java index 0d62356d77..625de57180 100644 --- a/lib/src/main/java/com/diffplug/spotless/json/package-info.java +++ b/lib/src/main/java/com/diffplug/spotless/json/package-info.java @@ -1,6 +1,6 @@ @ParametersAreNonnullByDefault @ReturnValuesAreNonnullByDefault -package com.diffplug.spotless.extra.json.java; +package com.diffplug.spotless.json; import javax.annotation.ParametersAreNonnullByDefault; diff --git a/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java b/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java index f1cd3103d9..afd479b4c5 100644 --- a/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java +++ b/lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package com.diffplug.spotless.kotlin; +import java.io.File; import java.io.IOException; import java.io.Serializable; import java.lang.reflect.Constructor; @@ -24,7 +25,7 @@ import com.diffplug.spotless.*; -/** Wraps up diktat as a FormatterStep. */ +/** Wraps up diktat as a FormatterStep. */ public class DiktatStep { // prevent direct instantiation @@ -32,10 +33,12 @@ private DiktatStep() {} private static final String MIN_SUPPORTED_VERSION = "1.2.1"; - private static final String DEFAULT_VERSION = "1.2.3"; - static final String NAME = "diktat"; - static final String PACKAGE_DIKTAT = "org.cqfn.diktat"; - static final String MAVEN_COORDINATE = PACKAGE_DIKTAT + ":diktat-rules:"; + private static final String PACKAGE_RELOCATED_VERSION = "2.0.0"; + + private static final String DEFAULT_VERSION = "2.0.0"; + private static final String NAME = "diktat"; + private static final String MAVEN_COORDINATE_PRE_2_0_0 = "org.cqfn.diktat:diktat-rules:"; + private static final String MAVEN_COORDINATE = "com.saveourtool.diktat:diktat-runner:"; public static String defaultVersionDiktat() { return DEFAULT_VERSION; @@ -53,10 +56,6 @@ public static FormatterStep create(String versionDiktat, Provisioner provisioner return create(versionDiktat, provisioner, false, config); } - public static FormatterStep createForScript(String versionDiktat, Provisioner provisioner, @Nullable FileSignature config) { - return create(versionDiktat, provisioner, true, config); - } - public static FormatterStep create(String versionDiktat, Provisioner provisioner, boolean isScript, @Nullable FileSignature config) { if (BadSemver.version(versionDiktat) < BadSemver.version(MIN_SUPPORTED_VERSION)) { throw new IllegalStateException("Minimum required Diktat version is " + MIN_SUPPORTED_VERSION + ", you tried " + versionDiktat + " which is too old"); @@ -72,29 +71,33 @@ static final class State implements Serializable { private static final long serialVersionUID = 1L; + private final String versionDiktat; /** Are the files being linted Kotlin script files. */ private final boolean isScript; private final @Nullable FileSignature config; final JarState jar; State(String versionDiktat, Provisioner provisioner, boolean isScript, @Nullable FileSignature config) throws IOException { - - HashSet pkgSet = new HashSet<>(); - pkgSet.add(MAVEN_COORDINATE + versionDiktat); - - this.jar = JarState.from(pkgSet, provisioner); + final String diktatCoordinate; + if (BadSemver.version(versionDiktat) >= BadSemver.version(PACKAGE_RELOCATED_VERSION)) { + diktatCoordinate = MAVEN_COORDINATE + versionDiktat; + } else { + diktatCoordinate = MAVEN_COORDINATE_PRE_2_0_0 + versionDiktat; + } + this.jar = JarState.from(diktatCoordinate, provisioner); + this.versionDiktat = versionDiktat; this.isScript = isScript; this.config = config; } FormatterFunc createFormat() throws Exception { - if (config != null) { - System.setProperty("diktat.config.path", config.getOnlyFile().getAbsolutePath()); - } - + final File configFile = (config != null) ? config.getOnlyFile() : null; Class formatterFunc = jar.getClassLoader().loadClass("com.diffplug.spotless.glue.diktat.DiktatFormatterFunc"); - Constructor constructor = formatterFunc.getConstructor(boolean.class); - return (FormatterFunc.NeedsFile) constructor.newInstance(isScript); + Constructor constructor = formatterFunc.getConstructor( + String.class, + File.class, + boolean.class); + return (FormatterFunc.NeedsFile) constructor.newInstance(versionDiktat, configFile, isScript); } } } diff --git a/lib/src/main/java/com/diffplug/spotless/kotlin/KtLintStep.java b/lib/src/main/java/com/diffplug/spotless/kotlin/KtLintStep.java index 5627913e4a..6a5374bd68 100644 --- a/lib/src/main/java/com/diffplug/spotless/kotlin/KtLintStep.java +++ b/lib/src/main/java/com/diffplug/spotless/kotlin/KtLintStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,10 +19,16 @@ import java.io.Serializable; import java.lang.reflect.Constructor; import java.util.Collections; +import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.TreeMap; +import javax.annotation.Nullable; + +import com.diffplug.spotless.FileSignature; import com.diffplug.spotless.FormatterFunc; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.JarState; @@ -33,41 +39,28 @@ public class KtLintStep { // prevent direct instantiation private KtLintStep() {} - private static final String DEFAULT_VERSION = "0.47.1"; - static final String NAME = "ktlint"; - static final String PACKAGE_PRE_0_32 = "com.github.shyiko"; - static final String PACKAGE = "com.pinterest"; - static final String MAVEN_COORDINATE_PRE_0_32 = PACKAGE_PRE_0_32 + ":ktlint:"; - static final String MAVEN_COORDINATE = PACKAGE + ":ktlint:"; + private static final String DEFAULT_VERSION = "1.1.1"; + private static final String NAME = "ktlint"; + private static final String MAVEN_COORDINATE_0_DOT = "com.pinterest:ktlint:"; + private static final String MAVEN_COORDINATE_1_DOT = "com.pinterest.ktlint:ktlint-cli:"; public static FormatterStep create(Provisioner provisioner) { return create(defaultVersion(), provisioner); } public static FormatterStep create(String version, Provisioner provisioner) { - return create(version, provisioner, false, Collections.emptyMap(), Collections.emptyMap()); - } - - public static FormatterStep create(String version, Provisioner provisioner, boolean useExperimental, - Map userData, Map editorConfigOverride) { - return create(version, provisioner, false, useExperimental, userData, editorConfigOverride); - } - - public static FormatterStep createForScript(String version, Provisioner provisioner) { - return create(version, provisioner, true, false, Collections.emptyMap(), Collections.emptyMap()); + return create(version, provisioner, null, Collections.emptyMap(), Collections.emptyList()); } - public static FormatterStep createForScript(String version, Provisioner provisioner, boolean useExperimental, - Map userData, Map editorConfigOverride) { - return create(version, provisioner, true, useExperimental, userData, editorConfigOverride); - } - - private static FormatterStep create(String version, Provisioner provisioner, boolean isScript, boolean useExperimental, - Map userData, Map editorConfigOverride) { + public static FormatterStep create(String version, + Provisioner provisioner, + @Nullable FileSignature editorConfig, + Map editorConfigOverride, + List customRuleSets) { Objects.requireNonNull(version, "version"); Objects.requireNonNull(provisioner, "provisioner"); return FormatterStep.createLazy(NAME, - () -> new State(version, provisioner, isScript, useExperimental, userData, editorConfigOverride), + () -> new State(version, provisioner, editorConfig, editorConfigOverride, customRuleSets), State::createFormat); } @@ -75,44 +68,35 @@ public static String defaultVersion() { return DEFAULT_VERSION; } - static final class State implements Serializable { + private static final class State implements Serializable { private static final long serialVersionUID = 1L; - - /** Are the files being linted Kotlin script files. */ - private final boolean isScript; /** The jar that contains the formatter. */ - final JarState jarState; - private final boolean useExperimental; - private final TreeMap userData; + private final JarState jarState; private final TreeMap editorConfigOverride; private final String version; - - State(String version, Provisioner provisioner, boolean isScript, boolean useExperimental, - Map userData, Map editorConfigOverride) throws IOException { + @Nullable + private final FileSignature editorConfigPath; + + State(String version, + Provisioner provisioner, + @Nullable FileSignature editorConfigPath, + Map editorConfigOverride, + List customRuleSets) throws IOException { this.version = version; - - String coordinate; - if (BadSemver.version(version) < BadSemver.version(0, 32)) { - coordinate = MAVEN_COORDINATE_PRE_0_32; - } else { - coordinate = MAVEN_COORDINATE; - } - if (BadSemver.version(version) < BadSemver.version(0, 31, 0)) { - throw new IllegalStateException("KtLint versions < 0.31.0 not supported!"); - } - this.useExperimental = useExperimental; - this.userData = new TreeMap<>(userData); this.editorConfigOverride = new TreeMap<>(editorConfigOverride); - this.jarState = JarState.from(coordinate + version, provisioner); - this.isScript = isScript; + String ktlintCoordinate = (version.startsWith("0.") ? MAVEN_COORDINATE_0_DOT : MAVEN_COORDINATE_1_DOT) + version; + Set mavenCoordinates = new HashSet<>(customRuleSets); + mavenCoordinates.add(ktlintCoordinate); + this.jarState = JarState.from(mavenCoordinates, provisioner); + this.editorConfigPath = editorConfigPath; } FormatterFunc createFormat() throws Exception { final ClassLoader classLoader = jarState.getClassLoader(); Class formatterFunc = classLoader.loadClass("com.diffplug.spotless.glue.ktlint.KtlintFormatterFunc"); Constructor constructor = formatterFunc.getConstructor( - String.class, boolean.class, boolean.class, Map.class, Map.class); - return (FormatterFunc.NeedsFile) constructor.newInstance(version, isScript, useExperimental, userData, editorConfigOverride); + String.class, FileSignature.class, Map.class); + return (FormatterFunc.NeedsFile) constructor.newInstance(version, editorConfigPath, editorConfigOverride); } } } diff --git a/lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java b/lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java index 9e52bd0435..cf9a2dd231 100644 --- a/lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java +++ b/lib/src/main/java/com/diffplug/spotless/kotlin/KtfmtStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,7 +39,7 @@ public class KtfmtStep { // prevent direct instantiation private KtfmtStep() {} - private static final String DEFAULT_VERSION = "0.40"; + private static final String DEFAULT_VERSION = "0.46"; static final String NAME = "ktfmt"; static final String PACKAGE = "com.facebook"; static final String MAVEN_COORDINATE = PACKAGE + ":ktfmt:"; diff --git a/lib/src/main/java/com/diffplug/spotless/markdown/FlexmarkStep.java b/lib/src/main/java/com/diffplug/spotless/markdown/FlexmarkStep.java index 19550b6e44..65d903cba3 100644 --- a/lib/src/main/java/com/diffplug/spotless/markdown/FlexmarkStep.java +++ b/lib/src/main/java/com/diffplug/spotless/markdown/FlexmarkStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ public class FlexmarkStep { // prevent direct instantiation private FlexmarkStep() {} - private static final String DEFAULT_VERSION = "0.62.2"; + private static final String DEFAULT_VERSION = "0.64.8"; private static final String NAME = "flexmark-java"; private static final String MAVEN_COORDINATE = "com.vladsch.flexmark:flexmark-all:"; diff --git a/lib/src/main/java/com/diffplug/spotless/markdown/FreshMarkStep.java b/lib/src/main/java/com/diffplug/spotless/markdown/FreshMarkStep.java index 2591cac7a5..4477c640c7 100644 --- a/lib/src/main/java/com/diffplug/spotless/markdown/FreshMarkStep.java +++ b/lib/src/main/java/com/diffplug/spotless/markdown/FreshMarkStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,8 @@ import java.io.Serializable; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.NavigableMap; import java.util.Objects; @@ -31,6 +33,7 @@ import com.diffplug.spotless.FormatterFunc; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.JarState; +import com.diffplug.spotless.Jvm; import com.diffplug.spotless.Provisioner; import com.diffplug.spotless.ThrowingEx.Supplier; @@ -42,6 +45,10 @@ private FreshMarkStep() {} private static final String DEFAULT_VERSION = "1.3.1"; private static final String NAME = "freshmark"; private static final String MAVEN_COORDINATE = "com.diffplug.freshmark:freshmark:"; + + private static final String NASHORN_MAVEN_COORDINATE = "org.openjdk.nashorn:nashorn-core:"; + + private static final String NASHORN_VERSION = "15.4"; private static final String FORMATTER_CLASS = "com.diffplug.freshmark.FreshMark"; private static final String FORMATTER_METHOD = "compile"; @@ -55,8 +62,16 @@ public static FormatterStep create(String version, Supplier> prop Objects.requireNonNull(version, "version"); Objects.requireNonNull(properties, "properties"); Objects.requireNonNull(provisioner, "provisioner"); + + List mavenCoordinates = new ArrayList<>(); + mavenCoordinates.add(MAVEN_COORDINATE + version); + if (Jvm.version() >= 15) { + mavenCoordinates.add("com.diffplug.jscriptbox:jscriptbox:3.0.1"); + mavenCoordinates.add(NASHORN_MAVEN_COORDINATE + NASHORN_VERSION); + } + return FormatterStep.createLazy(NAME, - () -> new State(JarState.from(MAVEN_COORDINATE + version, provisioner), properties.get()), + () -> new State(JarState.from(mavenCoordinates, provisioner), properties.get()), State::createFormat); } diff --git a/_ext/eclipse-cdt/src/main/java/com/diffplug/spotless/extra/eclipse/cdt/package-info.java b/lib/src/main/java/com/diffplug/spotless/npm/BaseNpmRestService.java similarity index 63% rename from _ext/eclipse-cdt/src/main/java/com/diffplug/spotless/extra/eclipse/cdt/package-info.java rename to lib/src/main/java/com/diffplug/spotless/npm/BaseNpmRestService.java index 831ab2b427..a6d93182a1 100644 --- a/_ext/eclipse-cdt/src/main/java/com/diffplug/spotless/extra/eclipse/cdt/package-info.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/BaseNpmRestService.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,8 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/** Eclipse CDT based Spotless formatter */ -@ParametersAreNonnullByDefault -package com.diffplug.spotless.extra.eclipse.cdt; +package com.diffplug.spotless.npm; -import javax.annotation.ParametersAreNonnullByDefault; +abstract class BaseNpmRestService { + + protected final SimpleRestClient restClient; + + BaseNpmRestService(String baseUrl) { + this.restClient = SimpleRestClient.forBaseUrl(baseUrl); + } + + public String shutdown() { + return restClient.post("/shutdown"); + } + +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/EslintConfig.java b/lib/src/main/java/com/diffplug/spotless/npm/EslintConfig.java new file mode 100644 index 0000000000..3499b3face --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/EslintConfig.java @@ -0,0 +1,72 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.npm; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; + +import javax.annotation.Nullable; + +import com.diffplug.spotless.FileSignature; +import com.diffplug.spotless.ThrowingEx; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +public class EslintConfig implements Serializable { + + private static final long serialVersionUID = -6196834313082791248L; + + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + @Nullable + private final transient File eslintConfigPath; + + @SuppressWarnings("unused") + private final FileSignature eslintConfigPathSignature; + + private final String eslintConfigJs; + + public EslintConfig(@Nullable File eslintConfigPath, @Nullable String eslintConfigJs) { + try { + this.eslintConfigPath = eslintConfigPath; + this.eslintConfigPathSignature = eslintConfigPath != null ? FileSignature.signAsList(this.eslintConfigPath) : FileSignature.signAsList(); + this.eslintConfigJs = eslintConfigJs; + } catch (IOException e) { + throw ThrowingEx.asRuntime(e); + } + } + + public EslintConfig withEslintConfigPath(@Nullable File eslintConfigPath) { + return new EslintConfig(eslintConfigPath, this.eslintConfigJs); + } + + @Nullable + public File getEslintConfigPath() { + return eslintConfigPath; + } + + @Nullable + public String getEslintConfigJs() { + return eslintConfigJs; + } + + public EslintConfig verify() { + if (eslintConfigPath == null && eslintConfigJs == null) { + throw new IllegalArgumentException("ESLint must be configured using either a configFile or a configJs - but both are null."); + } + return this; + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/EslintFormatterStep.java b/lib/src/main/java/com/diffplug/spotless/npm/EslintFormatterStep.java new file mode 100644 index 0000000000..cee51b8518 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/EslintFormatterStep.java @@ -0,0 +1,189 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.npm; + +import static java.util.Objects.requireNonNull; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; + +import javax.annotation.Nonnull; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterFunc.Closeable; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.Provisioner; +import com.diffplug.spotless.ThrowingEx; +import com.diffplug.spotless.npm.EslintRestService.FormatOption; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +public class EslintFormatterStep { + + private static final Logger logger = LoggerFactory.getLogger(EslintFormatterStep.class); + + public static final String NAME = "eslint-format"; + + public static final String DEFAULT_ESLINT_VERSION = "^8.45.0"; + + public static Map defaultDevDependenciesForTypescript() { + return defaultDevDependenciesTypescriptWithEslint(DEFAULT_ESLINT_VERSION); + } + + public static Map defaultDevDependenciesTypescriptWithEslint(String eslintVersion) { + Map dependencies = new LinkedHashMap<>(); + dependencies.put("@typescript-eslint/eslint-plugin", "^6.1.0"); + dependencies.put("@typescript-eslint/parser", "^6.1.0"); + dependencies.put("typescript", "^5.1.6"); + dependencies.put("eslint", Objects.requireNonNull(eslintVersion)); + return dependencies; + } + + public static Map defaultDevDependencies() { + return defaultDevDependenciesWithEslint(DEFAULT_ESLINT_VERSION); + } + + public static Map defaultDevDependenciesWithEslint(String version) { + return Collections.singletonMap("eslint", version); + } + + public static FormatterStep create(Map devDependencies, Provisioner provisioner, File projectDir, File buildDir, File cacheDir, NpmPathResolver npmPathResolver, EslintConfig eslintConfig) { + requireNonNull(devDependencies); + requireNonNull(provisioner); + requireNonNull(projectDir); + requireNonNull(buildDir); + return FormatterStep.createLazy(NAME, + () -> new State(NAME, devDependencies, projectDir, buildDir, cacheDir, npmPathResolver, eslintConfig), + State::createFormatterFunc); + } + + private static class State extends NpmFormatterStepStateBase implements Serializable { + + private static final long serialVersionUID = -539537027004745812L; + private final EslintConfig origEslintConfig; + + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + private transient EslintConfig eslintConfigInUse; + + State(String stepName, Map devDependencies, File projectDir, File buildDir, File cacheDir, NpmPathResolver npmPathResolver, EslintConfig eslintConfig) throws IOException { + super(stepName, + new NpmConfig( + replaceDevDependencies( + NpmResourceHelper.readUtf8StringFromClasspath(EslintFormatterStep.class, "/com/diffplug/spotless/npm/eslint-package.json"), + new TreeMap<>(devDependencies)), + NpmResourceHelper.readUtf8StringFromClasspath(EslintFormatterStep.class, + "/com/diffplug/spotless/npm/common-serve.js", + "/com/diffplug/spotless/npm/eslint-serve.js"), + npmPathResolver.resolveNpmrcContent()), + new NpmFormatterStepLocations( + projectDir, + buildDir, + cacheDir, + npmPathResolver::resolveNpmExecutable, + npmPathResolver::resolveNodeExecutable)); + this.origEslintConfig = requireNonNull(eslintConfig.verify()); + this.eslintConfigInUse = eslintConfig; + } + + @Override + protected void prepareNodeServerLayout() throws IOException { + super.prepareNodeServerLayout(); + if (origEslintConfig.getEslintConfigPath() != null) { + // If any config files are provided, we need to make sure they are at the same location as the node modules + // as eslint will try to resolve plugin/config names relatively to the config file location and some + // eslint configs contain relative paths to additional config files (such as tsconfig.json e.g.) + logger.debug("Copying config file <{}> to <{}> and using the copy", origEslintConfig.getEslintConfigPath(), nodeServerLayout.nodeModulesDir()); + File configFileCopy = NpmResourceHelper.copyFileToDir(origEslintConfig.getEslintConfigPath(), nodeServerLayout.nodeModulesDir()); + this.eslintConfigInUse = this.origEslintConfig.withEslintConfigPath(configFileCopy).verify(); + } + } + + @Override + @Nonnull + public FormatterFunc createFormatterFunc() { + try { + logger.info("Creating formatter function (starting server)"); + ServerProcessInfo eslintRestServer = npmRunServer(); + EslintRestService restService = new EslintRestService(eslintRestServer.getBaseUrl()); + return Closeable.ofDangerous(() -> endServer(restService, eslintRestServer), new EslintFilePathPassingFormatterFunc(locations.projectDir(), nodeServerLayout.nodeModulesDir(), eslintConfigInUse, restService)); + } catch (IOException e) { + throw ThrowingEx.asRuntime(e); + } + } + + private void endServer(BaseNpmRestService restService, ServerProcessInfo restServer) throws Exception { + logger.info("Closing formatting function (ending server)."); + try { + restService.shutdown(); + } catch (Throwable t) { + logger.info("Failed to request shutdown of rest service via api. Trying via process.", t); + } + restServer.close(); + } + + } + + private static class EslintFilePathPassingFormatterFunc implements FormatterFunc.NeedsFile { + private final File projectDir; + private final File nodeModulesDir; + private final EslintConfig eslintConfig; + private final EslintRestService restService; + + public EslintFilePathPassingFormatterFunc(File projectDir, File nodeModulesDir, EslintConfig eslintConfig, EslintRestService restService) { + this.projectDir = requireNonNull(projectDir); + this.nodeModulesDir = requireNonNull(nodeModulesDir); + this.eslintConfig = requireNonNull(eslintConfig); + this.restService = requireNonNull(restService); + } + + @Override + public String applyWithFile(String unix, File file) throws Exception { + Map eslintCallOptions = new HashMap<>(); + setConfigToCallOptions(eslintCallOptions); + setFilePathToCallOptions(eslintCallOptions, file); + return restService.format(unix, eslintCallOptions); + } + + private void setFilePathToCallOptions(Map eslintCallOptions, File fileToBeFormatted) { + eslintCallOptions.put(FormatOption.FILE_PATH, fileToBeFormatted.getAbsolutePath()); + } + + private void setConfigToCallOptions(Map eslintCallOptions) { + if (eslintConfig.getEslintConfigPath() != null) { + eslintCallOptions.put(FormatOption.ESLINT_OVERRIDE_CONFIG_FILE, eslintConfig.getEslintConfigPath().getAbsolutePath()); + } + if (eslintConfig.getEslintConfigJs() != null) { + eslintCallOptions.put(FormatOption.ESLINT_OVERRIDE_CONFIG, eslintConfig.getEslintConfigJs()); + } + if (eslintConfig instanceof EslintTypescriptConfig) { + // if we are a ts config, see if we need to use specific paths or use default projectDir + File tsConfigFilePath = ((EslintTypescriptConfig) eslintConfig).getTypescriptConfigPath(); + File tsConfigRootDir = tsConfigFilePath != null ? tsConfigFilePath.getParentFile() : projectDir; + eslintCallOptions.put(FormatOption.TS_CONFIG_ROOT_DIR, nodeModulesDir.getAbsoluteFile().toPath().relativize(tsConfigRootDir.getAbsoluteFile().toPath()).toString()); + } + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/EslintRestService.java b/lib/src/main/java/com/diffplug/spotless/npm/EslintRestService.java new file mode 100644 index 0000000000..e17237ecbf --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/EslintRestService.java @@ -0,0 +1,46 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.npm; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +public class EslintRestService extends BaseNpmRestService { + + EslintRestService(String baseUrl) { + super(baseUrl); + } + + public String format(String fileContent, Map formatOptions) { + Map jsonProperties = new LinkedHashMap<>(); + jsonProperties.put("file_content", fileContent); + for (Entry option : formatOptions.entrySet()) { + jsonProperties.put(option.getKey().backendName, option.getValue()); + } + return restClient.postJson("/eslint/format", jsonProperties); + } + + enum FormatOption { + ESLINT_OVERRIDE_CONFIG("eslint_override_config"), ESLINT_OVERRIDE_CONFIG_FILE("eslint_override_config_file"), FILE_PATH("file_path"), TS_CONFIG_ROOT_DIR("ts_config_root_dir"); + + private final String backendName; + + FormatOption(String backendName) { + this.backendName = backendName; + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/EslintTypescriptConfig.java b/lib/src/main/java/com/diffplug/spotless/npm/EslintTypescriptConfig.java new file mode 100644 index 0000000000..ea3e2046b3 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/EslintTypescriptConfig.java @@ -0,0 +1,58 @@ +/* + * Copyright 2022-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.npm; + +import java.io.File; +import java.io.IOException; + +import javax.annotation.Nullable; + +import com.diffplug.spotless.FileSignature; +import com.diffplug.spotless.ThrowingEx; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +public class EslintTypescriptConfig extends EslintConfig { + + private static final long serialVersionUID = -126864670181617006L; + + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + @Nullable + private final transient File typescriptConfigPath; + + @SuppressWarnings("unused") + private final FileSignature typescriptConfigPathSignature; + + public EslintTypescriptConfig(@Nullable File eslintConfigPath, @Nullable String eslintConfigJs, @Nullable File typescriptConfigPath) { + super(eslintConfigPath, eslintConfigJs); + try { + this.typescriptConfigPath = typescriptConfigPath; + this.typescriptConfigPathSignature = typescriptConfigPath != null ? FileSignature.signAsList(this.typescriptConfigPath) : FileSignature.signAsList(); + } catch (IOException e) { + throw ThrowingEx.asRuntime(e); + } + } + + @Override + public EslintConfig withEslintConfigPath(@Nullable File eslintConfigPath) { + return new EslintTypescriptConfig(eslintConfigPath, this.getEslintConfigJs(), this.typescriptConfigPath); + } + + @Nullable + public File getTypescriptConfigPath() { + return typescriptConfigPath; + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/FileFinder.java b/lib/src/main/java/com/diffplug/spotless/npm/FileFinder.java index 76016d68db..fce1497de6 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/FileFinder.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/FileFinder.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ class FileFinder { private final List>> fileCandidateFinders; private FileFinder(Builder builder) { - this.fileCandidateFinders = Collections.unmodifiableList(new ArrayList<>(builder.candidateFinders)); + this.fileCandidateFinders = List.copyOf(builder.candidateFinders); } static Builder finderForFilename(String fileName) { diff --git a/lib/src/main/java/com/diffplug/spotless/npm/FormattedPrinter.java b/lib/src/main/java/com/diffplug/spotless/npm/FormattedPrinter.java deleted file mode 100644 index 97d5cdb4c6..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/npm/FormattedPrinter.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2020 DiffPlug - * - * 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 - * - * http://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 com.diffplug.spotless.npm; - -import static java.util.Objects.requireNonNull; - -import java.io.PrintStream; -import java.time.LocalDateTime; - -enum FormattedPrinter { - SYSOUT(System.out); - - private static final boolean enabled = false; - - private final PrintStream printStream; - - FormattedPrinter(PrintStream printStream) { - this.printStream = requireNonNull(printStream); - } - - public void print(String msg, Object... paramsForStringFormat) { - if (!enabled) { - return; - } - String formatted = String.format(msg, paramsForStringFormat); - String prefixed = String.format("[%s] %s", LocalDateTime.now().toString(), formatted); - this.printStream.println(prefixed); - } -} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/JsonEscaper.java b/lib/src/main/java/com/diffplug/spotless/npm/JsonEscaper.java index 163818d0e7..848dd030dd 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/JsonEscaper.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/JsonEscaper.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,6 +34,22 @@ public static String jsonEscape(Object val) { if (val instanceof String) { return jsonEscape((String) val); } + if (ListableAdapter.canAdapt(val)) { + // create an array + StringBuilder sb = new StringBuilder(); + sb.append('['); + boolean first = true; + for (Object o : ListableAdapter.adapt(val)) { + if (first) { + first = false; + } else { + sb.append(", "); + } + sb.append(jsonEscape(o)); + } + sb.append(']'); + return sb.toString(); + } return val.toString(); } @@ -44,7 +60,7 @@ private static String jsonEscape(JsonRawValue jsonRawValue) { private static String jsonEscape(String unescaped) { /** * the following characters are reserved in JSON and must be properly escaped to be used in strings: - * + *

* Backspace is replaced with \b * Form feed is replaced with \f * Newline is replaced with \n @@ -52,7 +68,7 @@ private static String jsonEscape(String unescaped) { * Tab is replaced with \t * Double quote is replaced with \" * Backslash is replaced with \\ - * + *

* additionally we handle xhtml '' string * and non-ascii chars */ diff --git a/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java b/lib/src/main/java/com/diffplug/spotless/npm/JsonWriter.java similarity index 77% rename from lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java rename to lib/src/main/java/com/diffplug/spotless/npm/JsonWriter.java index 846f7f1cf3..bbdf9b63d4 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/SimpleJsonWriter.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/JsonWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,33 +28,46 @@ import com.diffplug.spotless.ThrowingEx; -public class SimpleJsonWriter { +class JsonWriter { private final LinkedHashMap valueMap = new LinkedHashMap<>(); - public static SimpleJsonWriter of(Map values) { - SimpleJsonWriter writer = new SimpleJsonWriter(); + public static JsonWriter of(Map values) { + JsonWriter writer = new JsonWriter(); writer.putAll(values); return writer; } - SimpleJsonWriter putAll(Map values) { + JsonWriter putAll(Map values) { verifyValues(values); this.valueMap.putAll(values); return this; } - SimpleJsonWriter put(String name, Object value) { + JsonWriter put(String name, Object value) { verifyValues(Collections.singletonMap(name, value)); this.valueMap.put(name, value); return this; } private void verifyValues(Map values) { - if (values.values() - .stream() - .anyMatch(val -> !(val instanceof String || val instanceof JsonRawValue || val instanceof Number || val instanceof Boolean))) { - throw new IllegalArgumentException("Only values of type 'String', 'JsonRawValue', 'Number' and 'Boolean' are supported. You provided: " + values.values()); + for (Object value : values.values()) { + verifyValue(value); + } + } + + private void verifyValue(Object val) { + if (val == null) { + return; + } + if (ListableAdapter.canAdapt(val)) { + for (Object o : ListableAdapter.adapt(val)) { + verifyValue(o); + } + return; + } + if (!(val instanceof String || val instanceof JsonRawValue || val instanceof Number || val instanceof Boolean)) { + throw new IllegalArgumentException("Only values of type 'String', 'JsonRawValue', 'Number' and 'Boolean' are supported. You provided: " + val); } } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/ListableAdapter.java b/lib/src/main/java/com/diffplug/spotless/npm/ListableAdapter.java new file mode 100644 index 0000000000..24ae62fde6 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/ListableAdapter.java @@ -0,0 +1,58 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.npm; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +import javax.annotation.Nonnull; + +class ListableAdapter implements Iterable { + + private final List delegate; + + @SuppressWarnings("unchecked") + private ListableAdapter(Object delegate) { + Objects.requireNonNull(delegate); + if (!canAdapt(delegate)) { + throw new IllegalArgumentException("Cannot create ListableAdapter from " + delegate.getClass() + ". Use canAdapt() to check first."); + } + if (delegate instanceof List) { + this.delegate = (List) delegate; + } else if (delegate.getClass().isArray()) { + this.delegate = Arrays.asList((T[]) delegate); + } else { + throw new IllegalArgumentException("Cannot create IterableAdapter from " + delegate.getClass()); + } + } + + static Iterable adapt(Object delegate) { + return new ListableAdapter<>(delegate); + } + + @Override + @Nonnull + public Iterator iterator() { + return delegate.iterator(); + } + + static boolean canAdapt(Object delegate) { + Objects.requireNonNull(delegate); + return delegate instanceof List || delegate.getClass().isArray(); + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NodeApp.java b/lib/src/main/java/com/diffplug/spotless/npm/NodeApp.java new file mode 100644 index 0000000000..932a148626 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/NodeApp.java @@ -0,0 +1,115 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.npm; + +import static com.diffplug.spotless.npm.NpmProcessFactory.OnlinePreferrence.PREFER_OFFLINE; +import static com.diffplug.spotless.npm.NpmProcessFactory.OnlinePreferrence.PREFER_ONLINE; + +import java.util.Objects; + +import javax.annotation.Nonnull; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.diffplug.spotless.ProcessRunner; + +public class NodeApp { + + private static final Logger logger = LoggerFactory.getLogger(NodeApp.class); + + private static final TimedLogger timedLogger = TimedLogger.forLogger(logger); + + @Nonnull + protected final NodeServerLayout nodeServerLayout; + + @Nonnull + protected final NpmConfig npmConfig; + + @Nonnull + protected final NpmProcessFactory npmProcessFactory; + + @Nonnull + protected final NpmFormatterStepLocations formatterStepLocations; + + public NodeApp(@Nonnull NodeServerLayout nodeServerLayout, @Nonnull NpmConfig npmConfig, @Nonnull NpmFormatterStepLocations formatterStepLocations) { + this.nodeServerLayout = Objects.requireNonNull(nodeServerLayout); + this.npmConfig = Objects.requireNonNull(npmConfig); + this.npmProcessFactory = processFactory(formatterStepLocations); + this.formatterStepLocations = Objects.requireNonNull(formatterStepLocations); + } + + private static NpmProcessFactory processFactory(NpmFormatterStepLocations formatterStepLocations) { + if (formatterStepLocations.cacheDir() != null) { + logger.info("Caching npm install results in {}.", formatterStepLocations.cacheDir()); + return NodeModulesCachingNpmProcessFactory.create(formatterStepLocations.cacheDir()); + } + logger.debug("Not caching npm install results."); + return StandardNpmProcessFactory.INSTANCE; + } + + boolean needsNpmInstall() { + return !this.nodeServerLayout.isNodeModulesPrepared(); + } + + boolean needsPrepareNodeAppLayout() { + return !this.nodeServerLayout.isLayoutPrepared(); + } + + void prepareNodeAppLayout() { + timedLogger.withInfo("Preparing {} for npm step {}.", this.nodeServerLayout, getClass().getName()).run(() -> { + NpmResourceHelper.assertDirectoryExists(nodeServerLayout.nodeModulesDir()); + NpmResourceHelper.writeUtf8StringToFile(nodeServerLayout.packageJsonFile(), this.npmConfig.getPackageJsonContent()); + if (this.npmConfig.getServeScriptContent() != null) { + NpmResourceHelper.writeUtf8StringToFile(nodeServerLayout.serveJsFile(), this.npmConfig.getServeScriptContent()); + } else { + NpmResourceHelper.deleteFileIfExists(nodeServerLayout.serveJsFile()); + } + if (this.npmConfig.getNpmrcContent() != null) { + NpmResourceHelper.writeUtf8StringToFile(nodeServerLayout.npmrcFile(), this.npmConfig.getNpmrcContent()); + } else { + NpmResourceHelper.deleteFileIfExists(nodeServerLayout.npmrcFile()); + } + }); + } + + void npmInstall() { + timedLogger.withInfo("Installing npm dependencies for {} with {}.", this.nodeServerLayout, this.npmProcessFactory.describe()) + .run(this::optimizedNpmInstall); + } + + private void optimizedNpmInstall() { + try { + npmProcessFactory.createNpmInstallProcess(nodeServerLayout, formatterStepLocations, PREFER_OFFLINE).waitFor(); + } catch (NpmProcessException e) { + if (!offlineInstallFailed(e.getResult())) { + throw e; // pass through + } + // if the npm install fails with message "No matching version found for @", we try again without the offline flag + npmProcessFactory.createNpmInstallProcess(nodeServerLayout, formatterStepLocations, PREFER_ONLINE).waitFor(); + } + } + + private boolean offlineInstallFailed(ProcessRunner.Result result) { + if (result == null) { + return false; // no result, something else must have happened + } + if (result.exitCode() == 0) { + return false; // all is well + } + return result.stdOutUtf8().contains("code ETARGET") && result.stdOutUtf8().contains("No matching version found for"); // offline install failed, needs online install + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NodeExecutableResolver.java b/lib/src/main/java/com/diffplug/spotless/npm/NodeExecutableResolver.java new file mode 100644 index 0000000000..eb30ae78b8 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/NodeExecutableResolver.java @@ -0,0 +1,50 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.npm; + +import java.io.File; +import java.util.Optional; + +class NodeExecutableResolver { + + private NodeExecutableResolver() { + // no instance + } + + static String nodeExecutableName() { + String nodeName = "node"; + if (PlatformInfo.normalizedOS() == PlatformInfo.OS.WINDOWS) { + nodeName += ".exe"; + } + return nodeName; + } + + static Optional tryFindNextTo(File npmExecutable) { + if (npmExecutable == null) { + return Optional.empty(); + } + File nodeExecutable = new File(npmExecutable.getParentFile(), nodeExecutableName()); + if (nodeExecutable.exists() && nodeExecutable.isFile() && nodeExecutable.canExecute()) { + return Optional.of(nodeExecutable); + } + return Optional.empty(); + } + + public static String explainMessage() { + return "Spotless was unable to find a node executable.\n" + + "Either specify the node executable explicitly or make sure it can be found next to the npm executable."; + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NodeModulesCachingNpmProcessFactory.java b/lib/src/main/java/com/diffplug/spotless/npm/NodeModulesCachingNpmProcessFactory.java new file mode 100644 index 0000000000..8a3af5fdf2 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/NodeModulesCachingNpmProcessFactory.java @@ -0,0 +1,118 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.npm; + +import java.io.File; +import java.util.List; +import java.util.Objects; + +import javax.annotation.Nonnull; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.diffplug.spotless.ProcessRunner.Result; + +public class NodeModulesCachingNpmProcessFactory implements NpmProcessFactory { + + private static final Logger logger = LoggerFactory.getLogger(NodeModulesCachingNpmProcessFactory.class); + + private static final TimedLogger timedLogger = TimedLogger.forLogger(logger); + + private final File cacheDir; + + private final ShadowCopy shadowCopy; + + private NodeModulesCachingNpmProcessFactory(@Nonnull File cacheDir) { + this.cacheDir = Objects.requireNonNull(cacheDir); + assertDir(cacheDir); + this.shadowCopy = new ShadowCopy(cacheDir); + } + + private void assertDir(File cacheDir) { + if (cacheDir.exists() && !cacheDir.isDirectory()) { + throw new IllegalArgumentException("Cache dir must be a directory"); + } + if (!cacheDir.exists()) { + if (!cacheDir.mkdirs()) { + throw new IllegalArgumentException("Cache dir could not be created."); + } + } + } + + public static NodeModulesCachingNpmProcessFactory create(@Nonnull File cacheDir) { + return new NodeModulesCachingNpmProcessFactory(cacheDir); + } + + @Override + public NpmProcess createNpmInstallProcess(NodeServerLayout nodeServerLayout, NpmFormatterStepLocations formatterStepLocations, OnlinePreferrence onlinePreferrence) { + NpmProcess actualNpmInstallProcess = StandardNpmProcessFactory.INSTANCE.createNpmInstallProcess(nodeServerLayout, formatterStepLocations, onlinePreferrence); + return new CachingNmpInstall(actualNpmInstallProcess, nodeServerLayout); + } + + @Override + public NpmLongRunningProcess createNpmServeProcess(NodeServerLayout nodeServerLayout, NpmFormatterStepLocations formatterStepLocations) { + return StandardNpmProcessFactory.INSTANCE.createNpmServeProcess(nodeServerLayout, formatterStepLocations); + } + + private class CachingNmpInstall implements NpmProcess { + + private final NpmProcess actualNpmInstallProcess; + private final NodeServerLayout nodeServerLayout; + + public CachingNmpInstall(NpmProcess actualNpmInstallProcess, NodeServerLayout nodeServerLayout) { + this.actualNpmInstallProcess = actualNpmInstallProcess; + this.nodeServerLayout = nodeServerLayout; + } + + @Override + public Result waitFor() { + String entryName = entryName(); + if (shadowCopy.entryExists(entryName, NodeServerLayout.NODE_MODULES)) { + timedLogger.withInfo("Using cached node_modules for {} from {}", entryName, cacheDir) + .run(() -> shadowCopy.copyEntryInto(entryName(), NodeServerLayout.NODE_MODULES, nodeServerLayout.nodeModulesDir())); + return new CachedResult(); + } else { + Result result = timedLogger.withInfo("calling actual npm install {}", actualNpmInstallProcess.describe()) + .call(actualNpmInstallProcess::waitFor); + assert result.exitCode() == 0; + storeShadowCopy(entryName); + return result; + } + } + + private void storeShadowCopy(String entryName) { + timedLogger.withInfo("Caching node_modules for {} in {}", entryName, cacheDir) + .run(() -> shadowCopy.addEntry(entryName(), new File(nodeServerLayout.nodeModulesDir(), NodeServerLayout.NODE_MODULES))); + } + + private String entryName() { + return nodeServerLayout.nodeModulesDir().getName(); + } + + @Override + public String describe() { + return String.format("Wrapper around [%s] to cache node_modules in [%s]", actualNpmInstallProcess.describe(), cacheDir.getAbsolutePath()); + } + } + + private class CachedResult extends Result { + + public CachedResult() { + super(List.of("(from cache dir " + cacheDir + ")"), 0, new byte[0], new byte[0]); + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NodeServeApp.java b/lib/src/main/java/com/diffplug/spotless/npm/NodeServeApp.java new file mode 100644 index 0000000000..c9311a5589 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/NodeServeApp.java @@ -0,0 +1,40 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.npm; + +import javax.annotation.Nonnull; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.diffplug.spotless.ProcessRunner; + +public class NodeServeApp extends NodeApp { + + private static final Logger logger = LoggerFactory.getLogger(NodeApp.class); + + private static final TimedLogger timedLogger = TimedLogger.forLogger(logger); + + public NodeServeApp(@Nonnull NodeServerLayout nodeServerLayout, @Nonnull NpmConfig npmConfig, @Nonnull NpmFormatterStepLocations formatterStepLocations) { + super(nodeServerLayout, npmConfig, formatterStepLocations); + } + + ProcessRunner.LongRunningProcess startNpmServeProcess() { + return timedLogger.withInfo("Starting npm based server in {} with {}.", this.nodeServerLayout.nodeModulesDir(), this.npmProcessFactory.describe()) + .call(() -> npmProcessFactory.createNpmServeProcess(nodeServerLayout, formatterStepLocations).start()); + } + +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NodeServerLayout.java b/lib/src/main/java/com/diffplug/spotless/npm/NodeServerLayout.java index a175080517..850ea4eb6b 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NodeServerLayout.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NodeServerLayout.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,21 +16,45 @@ package com.diffplug.spotless.npm; import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import com.diffplug.spotless.ThrowingEx; class NodeServerLayout { + private static final Pattern PACKAGE_JSON_NAME_PATTERN = Pattern.compile("\"name\"\\s*:\\s*\"([^\"]+)\""); + static final String NODE_MODULES = "node_modules"; + private final File nodeModulesDir; private final File packageJsonFile; + + private final File packageLockJsonFile; + private final File serveJsFile; private final File npmrcFile; - NodeServerLayout(File buildDir, String stepName) { - this.nodeModulesDir = new File(buildDir, "spotless-node-modules-" + stepName); + NodeServerLayout(File buildDir, String packageJsonContent) { + this.nodeModulesDir = new File(buildDir, nodeModulesDirName(packageJsonContent)); this.packageJsonFile = new File(nodeModulesDir, "package.json"); + this.packageLockJsonFile = new File(nodeModulesDir, "package-lock.json"); this.serveJsFile = new File(nodeModulesDir, "serve.js"); this.npmrcFile = new File(nodeModulesDir, ".npmrc"); } + private static String nodeModulesDirName(String packageJsonContent) { + String md5Hash = NpmResourceHelper.md5(packageJsonContent); + Matcher matcher = PACKAGE_JSON_NAME_PATTERN.matcher(packageJsonContent); + if (!matcher.find()) { + throw new IllegalArgumentException("package.json must contain a name property"); + } + String packageName = matcher.group(1); + return String.format("%s-node-modules-%s", packageName, md5Hash); + } + File nodeModulesDir() { return nodeModulesDir; } @@ -47,7 +71,43 @@ public File npmrcFile() { return npmrcFile; } - static File getBuildDirFromNodeModulesDir(File nodeModulesDir) { - return nodeModulesDir.getParentFile(); + public boolean isLayoutPrepared() { + if (!nodeModulesDir().isDirectory()) { + return false; + } + if (!packageJsonFile().isFile()) { + return false; + } + if (!packageLockJsonFile.isFile()) { + return false; + } + if (!serveJsFile().isFile()) { + return false; + } + // npmrc is optional, so must not be checked here + return true; + } + + public boolean isNodeModulesPrepared() { + Path nodeModulesInstallDirPath = new File(nodeModulesDir(), NODE_MODULES).toPath(); + if (!Files.isDirectory(nodeModulesInstallDirPath)) { + return false; + } + // check if it is NOT empty + return ThrowingEx.get(() -> { + try (Stream entries = Files.list(nodeModulesInstallDirPath)) { + return entries.findFirst().isPresent(); + } + }); + } + + @Override + public String toString() { + return String.format( + "NodeServerLayout[nodeModulesDir=%s, packageJsonFile=%s, serveJsFile=%s, npmrcFile=%s]", + this.nodeModulesDir, + this.packageJsonFile, + this.serveJsFile, + this.npmrcFile); } } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmConfig.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmConfig.java index 1492fe7a99..863be41193 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmConfig.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package com.diffplug.spotless.npm; import java.io.Serializable; +import java.util.Objects; import javax.annotation.Nonnull; @@ -23,30 +24,24 @@ class NpmConfig implements Serializable { private static final long serialVersionUID = 684264546497914877L; + @Nonnull private final String packageJsonContent; - private final String npmModule; - private final String serveScriptContent; private final String npmrcContent; - public NpmConfig(String packageJsonContent, String npmModule, String serveScriptContent, String npmrcContent) { - this.packageJsonContent = packageJsonContent; - this.npmModule = npmModule; + public NpmConfig(@Nonnull String packageJsonContent, String serveScriptContent, String npmrcContent) { + this.packageJsonContent = Objects.requireNonNull(packageJsonContent); this.serveScriptContent = serveScriptContent; this.npmrcContent = npmrcContent; } + @Nonnull public String getPackageJsonContent() { return packageJsonContent; } - public String getNpmModule() { - return npmModule; - } - - @Nonnull public String getServeScriptContent() { return serveScriptContent; } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepLocations.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepLocations.java new file mode 100644 index 0000000000..67763e59ec --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepLocations.java @@ -0,0 +1,73 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.npm; + +import static java.util.Objects.requireNonNull; + +import java.io.File; +import java.io.Serializable; +import java.util.function.Supplier; + +import javax.annotation.Nonnull; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +class NpmFormatterStepLocations implements Serializable { + + private static final long serialVersionUID = -1055408537924029969L; + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + private final transient File projectDir; + + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + private final transient File buildDir; + + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + private final transient File cacheDir; + + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + private final transient Supplier npmExecutable; + + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + private final transient Supplier nodeExecutable; + + public NpmFormatterStepLocations(@Nonnull File projectDir, @Nonnull File buildDir, File cacheDir, @Nonnull Supplier npmExecutable, @Nonnull Supplier nodeExecutable) { + this.projectDir = requireNonNull(projectDir); + this.buildDir = requireNonNull(buildDir); + this.cacheDir = cacheDir; + this.npmExecutable = requireNonNull(npmExecutable); + this.nodeExecutable = requireNonNull(nodeExecutable); + } + + public File projectDir() { + return projectDir; + } + + public File buildDir() { + return buildDir; + } + + public File cacheDir() { + return cacheDir; + } + + public File npmExecutable() { + return npmExecutable.get(); + } + + public File nodeExecutable() { + return nodeExecutable.get(); + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java index 2e8d80e471..3f262c813c 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,8 +31,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.diffplug.spotless.FileSignature; import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.ProcessRunner.LongRunningProcess; +import com.diffplug.spotless.ThrowingEx; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -40,65 +41,66 @@ abstract class NpmFormatterStepStateBase implements Serializable { private static final Logger logger = LoggerFactory.getLogger(NpmFormatterStepStateBase.class); - private static final long serialVersionUID = 1460749955865959948L; + private static final TimedLogger timedLogger = TimedLogger.forLogger(logger); - @SuppressWarnings("unused") - private final FileSignature packageJsonSignature; + private static final long serialVersionUID = 1460749955865959948L; @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") - public final transient File nodeModulesDir; + protected final transient NodeServerLayout nodeServerLayout; - @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") - private final transient File npmExecutable; + public final NpmFormatterStepLocations locations; private final NpmConfig npmConfig; private final String stepName; - protected NpmFormatterStepStateBase(String stepName, NpmConfig npmConfig, File buildDir, File npm) throws IOException { + private final transient NodeServeApp nodeServeApp; + + protected NpmFormatterStepStateBase(String stepName, NpmConfig npmConfig, NpmFormatterStepLocations locations) throws IOException { this.stepName = requireNonNull(stepName); this.npmConfig = requireNonNull(npmConfig); - this.npmExecutable = npm; + this.locations = locations; + this.nodeServerLayout = new NodeServerLayout(locations.buildDir(), npmConfig.getPackageJsonContent()); + this.nodeServeApp = new NodeServeApp(nodeServerLayout, npmConfig, locations); + } + + protected void prepareNodeServerLayout() throws IOException { + nodeServeApp.prepareNodeAppLayout(); + } - NodeServerLayout layout = prepareNodeServer(buildDir); - this.nodeModulesDir = layout.nodeModulesDir(); - this.packageJsonSignature = FileSignature.signAsList(layout.packageJsonFile()); + protected void prepareNodeServer() throws IOException { + nodeServeApp.npmInstall(); } - private NodeServerLayout prepareNodeServer(File buildDir) throws IOException { - NodeServerLayout layout = new NodeServerLayout(buildDir, stepName); - NpmResourceHelper.assertDirectoryExists(layout.nodeModulesDir()); - NpmResourceHelper.writeUtf8StringToFile(layout.packageJsonFile(), - this.npmConfig.getPackageJsonContent()); - NpmResourceHelper - .writeUtf8StringToFile(layout.serveJsFile(), this.npmConfig.getServeScriptContent()); - if (this.npmConfig.getNpmrcContent() != null) { - NpmResourceHelper.writeUtf8StringToFile(layout.npmrcFile(), this.npmConfig.getNpmrcContent()); - } else { - NpmResourceHelper.deleteFileIfExists(layout.npmrcFile()); + protected void assertNodeServerDirReady() throws IOException { + if (needsPrepareNodeServerLayout()) { + // reinstall if missing + prepareNodeServerLayout(); + } + if (needsPrepareNodeServer()) { + // run npm install if node_modules is missing + prepareNodeServer(); } - FormattedPrinter.SYSOUT.print("running npm install"); - runNpmInstall(layout.nodeModulesDir()); - FormattedPrinter.SYSOUT.print("npm install finished"); - return layout; } - private void runNpmInstall(File npmProjectDir) throws IOException { - new NpmProcess(npmProjectDir, this.npmExecutable).install(); + protected boolean needsPrepareNodeServer() { + return nodeServeApp.needsNpmInstall(); } - protected ServerProcessInfo npmRunServer() throws ServerStartException, IOException { - if (!this.nodeModulesDir.exists()) { - prepareNodeServer(NodeServerLayout.getBuildDirFromNodeModulesDir(this.nodeModulesDir)); - } + protected boolean needsPrepareNodeServerLayout() { + return nodeServeApp.needsPrepareNodeAppLayout(); + } + protected ServerProcessInfo npmRunServer() throws ServerStartException, IOException { + assertNodeServerDirReady(); + LongRunningProcess server = null; try { // The npm process will output the randomly selected port of the http server process to 'server.port' file // so in order to be safe, remove such a file if it exists before starting. - final File serverPortFile = new File(this.nodeModulesDir, "server.port"); + final File serverPortFile = new File(this.nodeServerLayout.nodeModulesDir(), "server.port"); NpmResourceHelper.deleteFileIfExists(serverPortFile); // start the http server in node - Process server = new NpmProcess(this.nodeModulesDir, this.npmExecutable).start(); + server = nodeServeApp.startNpmServeProcess(); // await the readiness of the http server - wait for at most 60 seconds try { @@ -119,7 +121,7 @@ protected ServerProcessInfo npmRunServer() throws ServerStartException, IOExcept String serverPort = NpmResourceHelper.readUtf8StringFromFile(serverPortFile).trim(); return new ServerProcessInfo(server, serverPort, serverPortFile); } catch (IOException | TimeoutException e) { - throw new ServerStartException(e); + throw new ServerStartException("Starting server failed." + (server != null ? "\n\nProcess result:\n" + ThrowingEx.get(server::result) : ""), e); } } @@ -188,8 +190,8 @@ public void close() throws Exception { protected static class ServerStartException extends RuntimeException { private static final long serialVersionUID = -8803977379866483002L; - public ServerStartException(Throwable cause) { - super(cause); + public ServerStartException(String message, Throwable cause) { + super(message, cause); } } } diff --git a/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/package-info.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmLongRunningProcess.java similarity index 72% rename from _ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/package-info.java rename to lib/src/main/java/com/diffplug/spotless/npm/NpmLongRunningProcess.java index 190557cc74..f5bece3e06 100644 --- a/_ext/eclipse-jdt/src/main/java/com/diffplug/spotless/extra/eclipse/java/package-info.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmLongRunningProcess.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,8 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/** Eclipse JDT based Spotless formatter */ -@ParametersAreNonnullByDefault -package com.diffplug.spotless.extra.eclipse.java; +package com.diffplug.spotless.npm; -import javax.annotation.ParametersAreNonnullByDefault; +import com.diffplug.spotless.ProcessRunner.LongRunningProcess; + +interface NpmLongRunningProcess { + + String describe(); + + LongRunningProcess start(); + +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmPathResolver.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmPathResolver.java index a9a15bd49d..1a7db1c9b8 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmPathResolver.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmPathResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,6 @@ package com.diffplug.spotless.npm; import java.io.File; -import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -24,20 +23,58 @@ public class NpmPathResolver { private final File explicitNpmExecutable; + private final File explicitNodeExecutable; + private final File explicitNpmrcFile; private final List additionalNpmrcLocations; - public NpmPathResolver(File explicitNpmExecutable, File explicitNpmrcFile, File... additionalNpmrcLocations) { + public NpmPathResolver(File explicitNpmExecutable, File explicitNodeExecutable, File explicitNpmrcFile, List additionalNpmrcLocations) { this.explicitNpmExecutable = explicitNpmExecutable; + this.explicitNodeExecutable = explicitNodeExecutable; this.explicitNpmrcFile = explicitNpmrcFile; - this.additionalNpmrcLocations = Arrays.asList(additionalNpmrcLocations); + this.additionalNpmrcLocations = List.copyOf(additionalNpmrcLocations); } + /** + * Finds the npm executable to use. + *
+ * Either the explicit npm executable is returned, or - if an explicit node executable is configured - tries to find + * the npm executable relative to the node executable. + * Falls back to looking for npm on the user's system using {@link NpmExecutableResolver} + * + * @return the npm executable to use + * @throws IllegalStateException if no npm executable could be found + */ public File resolveNpmExecutable() { - return Optional.ofNullable(this.explicitNpmExecutable) - .orElseGet(() -> NpmExecutableResolver.tryFind() - .orElseThrow(() -> new IllegalStateException("Can't automatically determine npm executable and none was specifically supplied!\n\n" + NpmExecutableResolver.explainMessage()))); + if (this.explicitNpmExecutable != null) { + return this.explicitNpmExecutable; + } + if (this.explicitNodeExecutable != null) { + File nodeExecutableCandidate = new File(this.explicitNodeExecutable.getParentFile(), NpmExecutableResolver.npmExecutableName()); + if (nodeExecutableCandidate.canExecute()) { + return nodeExecutableCandidate; + } + } + return NpmExecutableResolver.tryFind() + .orElseThrow(() -> new IllegalStateException("Can't automatically determine npm executable and none was specifically supplied!\n\n" + NpmExecutableResolver.explainMessage())); + } + + /** + * Finds the node executable to use. + *
+ * Either the explicit node executable is returned, or tries to find the node executable relative to the npm executable + * found by {@link #resolveNpmExecutable()}. + * @return the node executable to use + * @throws IllegalStateException if no node executable could be found + */ + public File resolveNodeExecutable() { + if (this.explicitNodeExecutable != null) { + return this.explicitNodeExecutable; + } + File npmExecutable = resolveNpmExecutable(); + return NodeExecutableResolver.tryFindNextTo(npmExecutable) + .orElseThrow(() -> new IllegalStateException("Can't automatically determine node executable and none was specifically supplied!\n\n" + NodeExecutableResolver.explainMessage())); } public String resolveNpmrcContent() { diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java index e1bf8c8673..473057a769 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcess.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,78 +15,12 @@ */ package com.diffplug.spotless.npm; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; +import com.diffplug.spotless.ProcessRunner.Result; -class NpmProcess { +interface NpmProcess { - private final File workingDir; + String describe(); - private final File npmExecutable; + Result waitFor(); - NpmProcess(File workingDir, File npmExecutable) { - this.workingDir = workingDir; - this.npmExecutable = npmExecutable; - } - - void install() { - npmAwait("install", "--no-audit", "--no-package-lock"); - } - - Process start() { - // adding --scripts-prepend-node-path=true due to https://github.com/diffplug/spotless/issues/619#issuecomment-648018679 - return npm("start", "--scripts-prepend-node-path=true"); - } - - private void npmAwait(String... args) { - final Process npmProcess = npm(args); - - try { - if (npmProcess.waitFor() != 0) { - throw new NpmProcessException("Running npm command '" + commandLine(args) + "' failed with exit code: " + npmProcess.exitValue()); - } - } catch (InterruptedException e) { - throw new NpmProcessException("Running npm command '" + commandLine(args) + "' was interrupted.", e); - } - } - - private Process npm(String... args) { - List processCommand = processCommand(args); - try { - return new ProcessBuilder() - .inheritIO() - .directory(this.workingDir) - .command(processCommand) - .start(); - } catch (IOException e) { - throw new NpmProcessException("Failed to launch npm command '" + commandLine(args) + "'.", e); - } - } - - private List processCommand(String... args) { - List command = new ArrayList<>(args.length + 1); - command.add(this.npmExecutable.getAbsolutePath()); - command.addAll(Arrays.asList(args)); - return command; - } - - private String commandLine(String... args) { - return "npm " + Arrays.stream(args).collect(Collectors.joining(" ")); - } - - static class NpmProcessException extends RuntimeException { - private static final long serialVersionUID = 6424331316676759525L; - - public NpmProcessException(String message) { - super(message); - } - - public NpmProcessException(String message, Throwable cause) { - super(message, cause); - } - } } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmProcessException.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcessException.java new file mode 100644 index 0000000000..c2e8444bcc --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcessException.java @@ -0,0 +1,40 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.npm; + +import javax.annotation.CheckForNull; + +import com.diffplug.spotless.ProcessRunner; + +public class NpmProcessException extends RuntimeException { + private static final long serialVersionUID = 6424331316676759525L; + private final transient ProcessRunner.Result result; + + public NpmProcessException(String message, ProcessRunner.Result result) { + super(message); + this.result = result; + } + + public NpmProcessException(String message, Throwable cause) { + super(message, cause); + this.result = null; + } + + @CheckForNull + public ProcessRunner.Result getResult() { + return result; + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmProcessFactory.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcessFactory.java new file mode 100644 index 0000000000..dbc9a807d5 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmProcessFactory.java @@ -0,0 +1,42 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.npm; + +public interface NpmProcessFactory { + + enum OnlinePreferrence { + PREFER_OFFLINE("--prefer-offline"), PREFER_ONLINE("--prefer-online"); + + private final String option; + + OnlinePreferrence(String option) { + this.option = option; + } + + public String option() { + return option; + } + } + + NpmProcess createNpmInstallProcess(NodeServerLayout nodeServerLayout, NpmFormatterStepLocations formatterStepLocations, OnlinePreferrence onlinePreferrence); + + NpmLongRunningProcess createNpmServeProcess(NodeServerLayout nodeServerLayout, NpmFormatterStepLocations formatterStepLocations); + + default String describe() { + return getClass().getSimpleName(); + } + +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/NpmResourceHelper.java b/lib/src/main/java/com/diffplug/spotless/npm/NpmResourceHelper.java index ad1211d717..7a28685de0 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/NpmResourceHelper.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/NpmResourceHelper.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,15 @@ import java.io.*; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.security.MessageDigest; import java.time.Duration; +import java.util.Arrays; +import java.util.Objects; import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; import com.diffplug.spotless.ThrowingEx; @@ -45,6 +52,12 @@ static void deleteFileIfExists(File file) throws IOException { } } + static String readUtf8StringFromClasspath(Class clazz, String... resourceNames) { + return Arrays.stream(resourceNames) + .map(resourceName -> readUtf8StringFromClasspath(clazz, resourceName)) + .collect(Collectors.joining("\n")); + } + static String readUtf8StringFromClasspath(Class clazz, String resourceName) { try (InputStream input = clazz.getResourceAsStream(resourceName)) { return readUtf8StringFromInputStream(input); @@ -90,6 +103,52 @@ static void awaitReadableFile(File file, Duration maxWaitTime) throws TimeoutExc if ((System.currentTimeMillis() - startedAt) > maxWaitTime.toMillis()) { throw new TimeoutException("The file did not appear within " + maxWaitTime); } + ThrowingEx.run(() -> Thread.sleep(100)); + } + } + + static void awaitFileDeleted(File file, Duration maxWaitTime) throws TimeoutException { + final long startedAt = System.currentTimeMillis(); + while (file.exists()) { + // wait for at most maxWaitTime + if ((System.currentTimeMillis() - startedAt) > maxWaitTime.toMillis()) { + throw new TimeoutException("The file did not disappear within " + maxWaitTime); + } + ThrowingEx.run(() -> Thread.sleep(100)); + } + } + + static File copyFileToDir(File file, File targetDir) { + return copyFileToDirAtSubpath(file, targetDir, file.getName()); + } + + static File copyFileToDirAtSubpath(File file, File targetDir, String relativePath) { + Objects.requireNonNull(relativePath); + try { + // create file pointing to relativePath in targetDir + final Path relativeTargetFile = Paths.get(targetDir.getAbsolutePath(), relativePath); + assertDirectoryExists(relativeTargetFile.getParent().toFile()); + + Files.copy(file.toPath(), relativeTargetFile, StandardCopyOption.REPLACE_EXISTING); + return relativeTargetFile.toFile(); + } catch (IOException e) { + throw ThrowingEx.asRuntime(e); + } + } + + static String md5(File file) { + return md5(readUtf8StringFromFile(file)); + } + + static String md5(String fileContent) { + MessageDigest md = ThrowingEx.get(() -> MessageDigest.getInstance("MD5")); + md.update(fileContent.getBytes(StandardCharsets.UTF_8)); + byte[] digest = md.digest(); + // convert byte array digest to hex string + StringBuilder sb = new StringBuilder(); + for (byte b : digest) { + sb.append(String.format("%02x", b & 0xff)); } + return sb.toString(); } } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java b/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java index 49d692d62c..72fecded1f 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,20 +41,22 @@ public class PrettierFormatterStep { public static final String NAME = "prettier-format"; + public static final String DEFAULT_VERSION = "2.8.8"; + public static final Map defaultDevDependencies() { - return defaultDevDependenciesWithPrettier("2.0.5"); + return defaultDevDependenciesWithPrettier(DEFAULT_VERSION); } public static final Map defaultDevDependenciesWithPrettier(String version) { return Collections.singletonMap("prettier", version); } - public static FormatterStep create(Map devDependencies, Provisioner provisioner, File buildDir, NpmPathResolver npmPathResolver, PrettierConfig prettierConfig) { + public static FormatterStep create(Map devDependencies, Provisioner provisioner, File projectDir, File buildDir, File cacheDir, NpmPathResolver npmPathResolver, PrettierConfig prettierConfig) { requireNonNull(devDependencies); requireNonNull(provisioner); requireNonNull(buildDir); return FormatterStep.createLazy(NAME, - () -> new State(NAME, devDependencies, buildDir, npmPathResolver, prettierConfig), + () -> new State(NAME, devDependencies, projectDir, buildDir, cacheDir, npmPathResolver, prettierConfig), State::createFormatterFunc); } @@ -63,17 +65,22 @@ private static class State extends NpmFormatterStepStateBase implements Serializ private static final long serialVersionUID = -539537027004745812L; private final PrettierConfig prettierConfig; - State(String stepName, Map devDependencies, File buildDir, NpmPathResolver npmPathResolver, PrettierConfig prettierConfig) throws IOException { + State(String stepName, Map devDependencies, File projectDir, File buildDir, File cacheDir, NpmPathResolver npmPathResolver, PrettierConfig prettierConfig) throws IOException { super(stepName, new NpmConfig( replaceDevDependencies( NpmResourceHelper.readUtf8StringFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/prettier-package.json"), new TreeMap<>(devDependencies)), - "prettier", - NpmResourceHelper.readUtf8StringFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/prettier-serve.js"), + NpmResourceHelper.readUtf8StringFromClasspath(PrettierFormatterStep.class, + "/com/diffplug/spotless/npm/common-serve.js", + "/com/diffplug/spotless/npm/prettier-serve.js"), npmPathResolver.resolveNpmrcContent()), - buildDir, - npmPathResolver.resolveNpmExecutable()); + new NpmFormatterStepLocations( + projectDir, + buildDir, + cacheDir, + npmPathResolver::resolveNpmExecutable, + npmPathResolver::resolveNodeExecutable)); this.prettierConfig = requireNonNull(prettierConfig); } @@ -81,7 +88,7 @@ private static class State extends NpmFormatterStepStateBase implements Serializ @Nonnull public FormatterFunc createFormatterFunc() { try { - FormattedPrinter.SYSOUT.print("creating formatter function (starting server)"); + logger.info("creating formatter function (starting server)"); ServerProcessInfo prettierRestServer = npmRunServer(); PrettierRestService restService = new PrettierRestService(prettierRestServer.getBaseUrl()); String prettierConfigOptions = restService.resolveConfig(this.prettierConfig.getPrettierConfigPath(), this.prettierConfig.getOptions()); @@ -92,7 +99,7 @@ public FormatterFunc createFormatterFunc() { } private void endServer(PrettierRestService restService, ServerProcessInfo restServer) throws Exception { - FormattedPrinter.SYSOUT.print("Closing formatting function (ending server)."); + logger.info("Closing formatting function (ending server)."); try { restService.shutdown(); } catch (Throwable t) { @@ -114,10 +121,15 @@ public PrettierFilePathPassingFormatterFunc(String prettierConfigOptions, Pretti @Override public String applyWithFile(String unix, File file) throws Exception { - FormattedPrinter.SYSOUT.print("formatting String '" + unix.substring(0, Math.min(50, unix.length())) + "[...]' in file '" + file + "'"); - final String prettierConfigOptionsWithFilepath = assertFilepathInConfigOptions(file); - return restService.format(unix, prettierConfigOptionsWithFilepath); + try { + return restService.format(unix, prettierConfigOptionsWithFilepath); + } catch (SimpleRestClient.SimpleRestResponseException e) { + if (e.getStatusCode() != 200 && e.getResponseMessage().contains("No parser could be inferred")) { + throw new PrettierMissingParserException(file, e); + } + throw e; + } } private String assertFilepathInConfigOptions(File file) { @@ -136,4 +148,5 @@ private String assertFilepathInConfigOptions(File file) { return "{" + filePathOption + (hasAnyConfigOption ? "," : "") + prettierConfigOptions.substring(startOfConfigOption + 1); } } + } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/PrettierMissingParserException.java b/lib/src/main/java/com/diffplug/spotless/npm/PrettierMissingParserException.java new file mode 100644 index 0000000000..cc04c7b182 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierMissingParserException.java @@ -0,0 +1,117 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.npm; + +import java.io.File; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import javax.annotation.Nonnull; + +class PrettierMissingParserException extends RuntimeException { + private static final long serialVersionUID = 1L; + + private static final Map EXTENSIONS_TO_PLUGINS; + + static { + Map plugins = new HashMap<>(); + // ---- official plugins + plugins.put(".php", "@prettier/plugin-php"); + plugins.put(".pug", "@prettier/plugin-pug"); + plugins.put(".rb", "@prettier/plugin-ruby"); + plugins.put(".xml", "@prettier/plugin-xml"); + + // ---- community plugins + // default namings: astro, elm, java, jsonata, prisma, properties, sh, sql, svelte, toml + plugins.put(".trigger", "prettier-plugin-apex"); + plugins.put(".cls", "prettier-plugin-apex"); + plugins.put(".html.erb", "prettier-plugin-erb"); + Arrays.asList(".glsl", + ".fp", + ".frag", + ".frg", + ".fs", + ".fsh", + ".fshader", + ".geo", + ".geom", + ".glslf", + ".glslv", + ".gs", + ".gshader", + ".rchit", + ".rmiss", + ".shader", + ".tesc", + ".tese", + ".vert", + ".vrx", + ".vsh", + ".vshader").forEach(ext -> plugins.put(ext, "prettier-plugin-glsl")); + Arrays.asList(".go.html", + ".gohtml", + ".gotmpl", + ".go.tmpl", + ".tmpl", + ".tpl", + ".html.tmpl", + ".html.tpl").forEach(ext -> plugins.put(ext, "prettier-plugin-go-template")); + plugins.put(".kt", "kotlin"); + plugins.put(".mo", "motoko"); + Arrays.asList(".nginx", ".nginxconf").forEach(ext -> plugins.put(ext, "prettier-plugin-nginx")); + plugins.put(".sol", "prettier-plugin-solidity"); + + EXTENSIONS_TO_PLUGINS = Collections.unmodifiableMap(plugins); + } + + private final File file; + + public PrettierMissingParserException(@Nonnull File file, Exception cause) { + super("Prettier could not infer a parser for file '" + file + "'. Maybe you need to include a prettier plugin in devDependencies?\n\n" + recommendPlugin(file), cause); + this.file = Objects.requireNonNull(file); + } + + private static String recommendPlugin(File file) { + String pluginName = guessPlugin(file); + return "A good candidate for file '" + file + "' is '" + pluginName + "\n" + + "See if you can find it on \n" + + "or search on npmjs.com for a plugin matching that name: " + + String.format("", pluginName) + + "\n\n" + + "For instructions on how to include plugins for prettier in spotless see our documentation:\n" + + "- for Gradle \n" + + "- for Maven "; + } + + private static String guessPlugin(File file) { + return EXTENSIONS_TO_PLUGINS.entrySet().stream() + .filter(entry -> file.getName().endsWith(entry.getKey())) + .findFirst() + .map(entry -> entry.getValue()) + .orElse("prettier-plugin-" + extension(file)); + } + + public String fileType() { + return extension(file); + } + + private static String extension(File file) { + return file.getName().substring(file.getName().lastIndexOf('.') + 1); + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java b/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java index ced08b013f..11fd29c68a 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/PrettierRestService.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,12 +19,10 @@ import java.util.LinkedHashMap; import java.util.Map; -public class PrettierRestService { - - private final SimpleRestClient restClient; +public class PrettierRestService extends BaseNpmRestService { PrettierRestService(String baseUrl) { - this.restClient = SimpleRestClient.forBaseUrl(baseUrl); + super(baseUrl); } public String resolveConfig(File prettierConfigPath, Map prettierConfigOptions) { @@ -33,8 +31,7 @@ public String resolveConfig(File prettierConfigPath, Map prettie jsonProperties.put("prettier_config_path", prettierConfigPath.getAbsolutePath()); } if (prettierConfigOptions != null) { - jsonProperties.put("prettier_config_options", SimpleJsonWriter.of(prettierConfigOptions).toJsonRawValue()); - + jsonProperties.put("prettier_config_options", JsonWriter.of(prettierConfigOptions).toJsonRawValue()); } return restClient.postJson("/prettier/config-options", jsonProperties); } @@ -48,9 +45,4 @@ public String format(String fileContent, String configOptionsJsonString) { return restClient.postJson("/prettier/format", jsonProperties); } - - public String shutdown() { - return restClient.post("/shutdown"); - } - } diff --git a/lib/src/main/java/com/diffplug/spotless/npm/ShadowCopy.java b/lib/src/main/java/com/diffplug/spotless/npm/ShadowCopy.java new file mode 100644 index 0000000000..044b5d70ea --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/ShadowCopy.java @@ -0,0 +1,175 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.npm; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.FileSystemException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.time.Duration; +import java.util.concurrent.TimeoutException; + +import javax.annotation.Nonnull; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.diffplug.spotless.ThrowingEx; + +class ShadowCopy { + + private static final Logger logger = LoggerFactory.getLogger(ShadowCopy.class); + + private final File shadowCopyRoot; + + public ShadowCopy(@Nonnull File shadowCopyRoot) { + this.shadowCopyRoot = shadowCopyRoot; + if (!shadowCopyRoot.isDirectory()) { + throw new IllegalArgumentException("Shadow copy root must be a directory: " + shadowCopyRoot); + } + } + + public void addEntry(String key, File orig) { + // prevent concurrent adding of entry with same key + if (!reserveSubFolder(key)) { + logger.debug("Shadow copy entry already in progress: {}. Awaiting finalization.", key); + try { + NpmResourceHelper.awaitFileDeleted(markerFilePath(key).toFile(), Duration.ofSeconds(120)); + } catch (TimeoutException e) { + throw new RuntimeException(e); + } + } + try { + storeEntry(key, orig); + } finally { + cleanupReservation(key); + } + } + + public File getEntry(String key, String fileName) { + return entry(key, fileName); + } + + private void storeEntry(String key, File orig) { + File target = entry(key, orig.getName()); + if (target.exists()) { + logger.debug("Shadow copy entry already exists: {}", key); + // delete directory "target" recursively + // https://stackoverflow.com/questions/3775694/deleting-folder-from-java + ThrowingEx.run(() -> Files.walkFileTree(target.toPath(), new DeleteDirectoryRecursively())); + } + // copy directory "orig" to "target" using hard links if possible or a plain copy otherwise + ThrowingEx.run(() -> Files.walkFileTree(orig.toPath(), new CopyDirectoryRecursively(target, orig))); + } + + private void cleanupReservation(String key) { + ThrowingEx.run(() -> Files.delete(markerFilePath(key))); + } + + private Path markerFilePath(String key) { + return Paths.get(shadowCopyRoot.getAbsolutePath(), key + ".marker"); + } + + private File entry(String key, String origName) { + return Paths.get(shadowCopyRoot.getAbsolutePath(), key, origName).toFile(); + } + + private boolean reserveSubFolder(String key) { + // put a marker file named "key".marker in "shadowCopyRoot" to make sure no other process is using it or return false if it already exists + try { + Files.createFile(Paths.get(shadowCopyRoot.getAbsolutePath(), key + ".marker")); + return true; + } catch (FileAlreadyExistsException e) { + return false; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public File copyEntryInto(String key, String origName, File targetParentFolder) { + File target = Paths.get(targetParentFolder.getAbsolutePath(), origName).toFile(); + if (target.exists()) { + logger.warn("Shadow copy destination already exists, deleting! {}: {}", key, target); + ThrowingEx.run(() -> Files.walkFileTree(target.toPath(), new DeleteDirectoryRecursively())); + } + // copy directory "orig" to "target" using hard links if possible or a plain copy otherwise + ThrowingEx.run(() -> Files.walkFileTree(entry(key, origName).toPath(), new CopyDirectoryRecursively(target, entry(key, origName)))); + return target; + } + + public boolean entryExists(String key, String origName) { + return entry(key, origName).exists(); + } + + private static class CopyDirectoryRecursively extends SimpleFileVisitor { + private final File target; + private final File orig; + + private boolean tryHardLink = true; + + public CopyDirectoryRecursively(File target, File orig) { + this.target = target; + this.orig = orig; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + // create directory on target + Files.createDirectories(target.toPath().resolve(orig.toPath().relativize(dir))); + return super.preVisitDirectory(dir, attrs); + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + // first try to hardlink, if that fails, copy + if (tryHardLink) { + try { + Files.createLink(target.toPath().resolve(orig.toPath().relativize(file)), file); + return super.visitFile(file, attrs); + } catch (UnsupportedOperationException | SecurityException | FileSystemException e) { + logger.debug("Shadow copy entry does not support hard links: {}. Switching to 'copy'.", file, e); + tryHardLink = false; // remember that hard links are not supported + } catch (IOException e) { + logger.debug("Shadow copy entry failed to create hard link: {}. Switching to 'copy'.", file, e); + tryHardLink = false; // remember that hard links are not supported + } + } + // copy file to target + Files.copy(file, target.toPath().resolve(orig.toPath().relativize(file))); + return super.visitFile(file, attrs); + } + } + + private static class DeleteDirectoryRecursively extends SimpleFileVisitor { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return super.visitFile(file, attrs); + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + Files.delete(dir); + return super.postVisitDirectory(dir, exc); + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java b/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java index 561839c757..2f413e4b3a 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/SimpleRestClient.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,7 @@ static SimpleRestClient forBaseUrl(String baseUrl) { } String postJson(String endpoint, Map jsonParams) throws SimpleRestException { - final SimpleJsonWriter jsonWriter = SimpleJsonWriter.of(jsonParams); + final JsonWriter jsonWriter = JsonWriter.of(jsonParams); final String jsonString = jsonWriter.toJsonString(); return postJson(endpoint, jsonString); diff --git a/lib/src/main/java/com/diffplug/spotless/npm/StandardNpmProcessFactory.java b/lib/src/main/java/com/diffplug/spotless/npm/StandardNpmProcessFactory.java new file mode 100644 index 0000000000..91cf80ad69 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/StandardNpmProcessFactory.java @@ -0,0 +1,144 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.npm; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import com.diffplug.spotless.ProcessRunner; + +public class StandardNpmProcessFactory implements NpmProcessFactory { + + public static final StandardNpmProcessFactory INSTANCE = new StandardNpmProcessFactory(); + + private StandardNpmProcessFactory() { + // only one instance neeeded + } + + @Override + public NpmProcess createNpmInstallProcess(NodeServerLayout nodeServerLayout, NpmFormatterStepLocations formatterStepLocations, OnlinePreferrence onlinePreferrence) { + return new NpmInstall(nodeServerLayout.nodeModulesDir(), formatterStepLocations, onlinePreferrence); + } + + @Override + public NpmLongRunningProcess createNpmServeProcess(NodeServerLayout nodeServerLayout, NpmFormatterStepLocations formatterStepLocations) { + return new NpmServe(nodeServerLayout.nodeModulesDir(), formatterStepLocations); + } + + private static abstract class AbstractStandardNpmProcess { + protected final ProcessRunner processRunner = ProcessRunner.usingRingBuffersOfCapacity(100 * 1024); // 100kB + + protected final File workingDir; + protected final NpmFormatterStepLocations formatterStepLocations; + + public AbstractStandardNpmProcess(File workingDir, NpmFormatterStepLocations formatterStepLocations) { + this.formatterStepLocations = formatterStepLocations; + this.workingDir = workingDir; + } + + protected String npmExecutable() { + return formatterStepLocations.npmExecutable().getAbsolutePath(); + } + + protected abstract List commandLine(); + + protected Map environmentVariables() { + return Map.of( + "PATH", formatterStepLocations.nodeExecutable().getParentFile().getAbsolutePath() + File.pathSeparator + System.getenv("PATH")); + } + + protected ProcessRunner.LongRunningProcess doStart() { + try { + return processRunner.start(workingDir, environmentVariables(), null, true, commandLine()); + } catch (IOException e) { + throw new NpmProcessException("Failed to launch npm command '" + describe() + "'.", e); + } + } + + protected abstract String describe(); + + public String doDescribe() { + return String.format("%s in %s [%s]", getClass().getSimpleName(), workingDir, String.join(" ", commandLine())); + } + } + + private static class NpmInstall extends AbstractStandardNpmProcess implements NpmProcess { + + private final OnlinePreferrence onlinePreferrence; + + public NpmInstall(File workingDir, NpmFormatterStepLocations formatterStepLocations, OnlinePreferrence onlinePreferrence) { + super(workingDir, formatterStepLocations); + this.onlinePreferrence = onlinePreferrence; + } + + @Override + protected List commandLine() { + return List.of( + npmExecutable(), + "install", + "--no-audit", + "--no-fund", + onlinePreferrence.option()); + } + + @Override + public String describe() { + return doDescribe(); + } + + @Override + public ProcessRunner.Result waitFor() { + try (ProcessRunner.LongRunningProcess npmProcess = doStart()) { + if (npmProcess.waitFor() != 0) { + throw new NpmProcessException("Running npm command '" + describe() + "' failed with exit code: " + npmProcess.exitValue() + "\n\n" + npmProcess.result(), npmProcess.result()); + } + return npmProcess.result(); + } catch (InterruptedException e) { + throw new NpmProcessException("Running npm command '" + describe() + "' was interrupted.", e); + } catch (ExecutionException e) { + throw new NpmProcessException("Running npm command '" + describe() + "' failed.", e); + } + } + } + + private static class NpmServe extends AbstractStandardNpmProcess implements NpmLongRunningProcess { + + public NpmServe(File workingDir, NpmFormatterStepLocations formatterStepLocations) { + super(workingDir, formatterStepLocations); + } + + @Override + protected List commandLine() { + return List.of( + npmExecutable(), + "start", + "--scripts-prepend-node-path=true"); + } + + @Override + public String describe() { + return doDescribe(); + } + + @Override + public ProcessRunner.LongRunningProcess start() { + return doStart(); + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/TimedLogger.java b/lib/src/main/java/com/diffplug/spotless/npm/TimedLogger.java new file mode 100644 index 0000000000..537234fcee --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/npm/TimedLogger.java @@ -0,0 +1,228 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.npm; + +import static com.diffplug.spotless.LazyArgLogger.lazy; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +import javax.annotation.Nonnull; + +import org.slf4j.Logger; + +import com.diffplug.spotless.ThrowingEx; + +/** + * A logger that logs the time it took to execute a block of code. + */ +class TimedLogger { + + public static final String MESSAGE_PREFIX_BEGIN = "[BEGIN] "; + + public static final String MESSAGE_PREFIX_END = "[END] "; + + public static final String MESSAGE_SUFFIX_TOOK = " (took {})"; + + private final Logger logger; + private final Ticker ticker; + + private TimedLogger(@Nonnull Logger logger, Ticker ticker) { + this.logger = Objects.requireNonNull(logger); + this.ticker = ticker; + } + + public static TimedLogger forLogger(@Nonnull Logger logger) { + return forLogger(logger, Ticker.systemTicker()); + } + + public static TimedLogger forLogger(@Nonnull Logger logger, Ticker ticker) { + return new TimedLogger(logger, ticker); + } + + public TimedExec withInfo(@Nonnull String message, Object... args) { + return new TimedExec(logger::isInfoEnabled, logger::info, ticker, message, args); + } + + public TimedExec withDebug(@Nonnull String message, Object... args) { + return new TimedExec(logger::isDebugEnabled, logger::debug, ticker, message, args); + } + + public TimedExec withTrace(@Nonnull String message, Object... args) { + return new TimedExec(logger::isTraceEnabled, logger::trace, ticker, message, args); + } + + public TimedExec withWarn(@Nonnull String message, Object... args) { + return new TimedExec(logger::isWarnEnabled, logger::warn, ticker, message, args); + } + + public TimedExec withError(@Nonnull String message, Object... args) { + return new TimedExec(logger::isErrorEnabled, logger::error, ticker, message, args); + } + + public static class Timed implements AutoCloseable { + + @Nonnull + private final String msg; + + @Nonnull + private final List params; + @Nonnull + private final LogToLevelMethod delegatedLogger; + @Nonnull + private final Ticker ticker; + + private final long startedAt; + + public Timed(@Nonnull Ticker ticker, @Nonnull String msg, @Nonnull List params, @Nonnull LogToLevelMethod delegatedLogger) { + this.ticker = Objects.requireNonNull(ticker); + this.msg = Objects.requireNonNull(msg); + this.params = List.copyOf(Objects.requireNonNull(params)); + this.delegatedLogger = Objects.requireNonNull(delegatedLogger); + this.startedAt = ticker.read(); + logStart(); + } + + private void logStart() { + delegatedLogger.log(MESSAGE_PREFIX_BEGIN + msg, params.toArray()); + } + + private void logEnd() { + delegatedLogger.log(MESSAGE_PREFIX_END + msg + MESSAGE_SUFFIX_TOOK, paramsForEnd()); + } + + @Override + public final void close() { + logEnd(); + } + + private Object[] paramsForEnd() { + if (params.isEmpty() || !(params.get(params.size() - 1) instanceof Throwable)) { + // if the last element is not a throwable, we can add the duration as the last element + return Stream.concat(params.stream(), Stream.of(lazy(this::durationString))).toArray(); + } + // if the last element is a throwable, we have to add the duration before the last element + return Stream.concat( + params.stream().limit(params.size() - 1), + Stream.of(lazy(this::durationString), + params.get(params.size() - 1))) + .toArray(); + } + + private String durationString() { + long duration = ticker.read() - startedAt; + if (duration < 1000) { + return duration + "ms"; + } else if (duration < 1000 * 60) { + long seconds = duration / 1000; + long millis = duration - seconds * 1000; + return seconds + "." + millis + "s"; + } else { + // output in the format 3m 4.321s + long minutes = duration / (1000 * 60); + long seconds = (duration - minutes * 1000 * 60) / 1000; + long millis = duration - minutes * 1000 * 60 - seconds * 1000; + return minutes + "m" + (seconds + millis > 0 ? " " + seconds + "." + millis + "s" : ""); + } + } + } + + public static final class NullStopWatchLogger extends Timed { + private static final NullStopWatchLogger INSTANCE = new NullStopWatchLogger(); + + private NullStopWatchLogger() { + super(Ticker.systemTicker(), "", List.of(), (m, a) -> {}); + } + } + + interface Ticker { + long read(); + + static Ticker systemTicker() { + return System::currentTimeMillis; + } + } + + static class TestTicker implements Ticker { + private long time = 0; + + @Override + public long read() { + return time; + } + + public void tickMillis(long millis) { + time += millis; + } + } + + public static class TimedExec { + @Nonnull + private final LogActiveMethod logActiveMethod; + @Nonnull + private final LogToLevelMethod logMethod; + @Nonnull + private final Ticker ticker; + @Nonnull + private final String message; + @Nonnull + private final Object[] args; + + public TimedExec(LogActiveMethod logActiveMethod, LogToLevelMethod logMethod, Ticker ticker, String message, Object... args) { + this.logActiveMethod = Objects.requireNonNull(logActiveMethod); + this.logMethod = Objects.requireNonNull(logMethod); + this.ticker = Objects.requireNonNull(ticker); + this.message = Objects.requireNonNull(message); + this.args = Objects.requireNonNull(args); + } + + public void run(ThrowingEx.Runnable r) { + try (Timed ignore = timed()) { + ThrowingEx.run(r); + } + } + + public T call(ThrowingEx.Supplier s) { + try (Timed ignore = timed()) { + return ThrowingEx.get(s); + } + } + + public void runChecked(ThrowingEx.Runnable r) throws Exception { + try (Timed ignore = timed()) { + r.run(); + } + } + + private Timed timed() { + if (logActiveMethod.isLogLevelActive()) { + return new Timed(ticker, message, List.of(args), logMethod); + } + return NullStopWatchLogger.INSTANCE; + } + } + + @FunctionalInterface + private interface LogActiveMethod { + boolean isLogLevelActive(); + } + + @FunctionalInterface + private interface LogToLevelMethod { + void log(String message, Object... args); + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java index 14080ecf56..6b55610be0 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,11 +40,11 @@ public class TsFmtFormatterStep { public static final String NAME = "tsfmt-format"; - public static FormatterStep create(Map versions, Provisioner provisioner, File buildDir, NpmPathResolver npmPathResolver, @Nullable TypedTsFmtConfigFile configFile, @Nullable Map inlineTsFmtSettings) { + public static FormatterStep create(Map versions, Provisioner provisioner, File projectDir, File buildDir, File cacheDir, NpmPathResolver npmPathResolver, @Nullable TypedTsFmtConfigFile configFile, @Nullable Map inlineTsFmtSettings) { requireNonNull(provisioner); requireNonNull(buildDir); return FormatterStep.createLazy(NAME, - () -> new State(NAME, versions, buildDir, npmPathResolver, configFile, inlineTsFmtSettings), + () -> new State(NAME, versions, projectDir, buildDir, cacheDir, npmPathResolver, configFile, inlineTsFmtSettings), State::createFormatterFunc); } @@ -71,15 +71,20 @@ public static class State extends NpmFormatterStepStateBase implements Serializa @Nullable private final TypedTsFmtConfigFile configFile; - public State(String stepName, Map versions, File buildDir, NpmPathResolver npmPathResolver, @Nullable TypedTsFmtConfigFile configFile, @Nullable Map inlineTsFmtSettings) throws IOException { + public State(String stepName, Map versions, File projectDir, File buildDir, File cacheDir, NpmPathResolver npmPathResolver, @Nullable TypedTsFmtConfigFile configFile, @Nullable Map inlineTsFmtSettings) throws IOException { super(stepName, new NpmConfig( replaceDevDependencies(NpmResourceHelper.readUtf8StringFromClasspath(TsFmtFormatterStep.class, "/com/diffplug/spotless/npm/tsfmt-package.json"), new TreeMap<>(versions)), - "typescript-formatter", - NpmResourceHelper.readUtf8StringFromClasspath(PrettierFormatterStep.class, "/com/diffplug/spotless/npm/tsfmt-serve.js"), + NpmResourceHelper.readUtf8StringFromClasspath(PrettierFormatterStep.class, + "/com/diffplug/spotless/npm/common-serve.js", + "/com/diffplug/spotless/npm/tsfmt-serve.js"), npmPathResolver.resolveNpmrcContent()), - buildDir, - npmPathResolver.resolveNpmExecutable()); + new NpmFormatterStepLocations( + projectDir, + buildDir, + cacheDir, + npmPathResolver::resolveNpmExecutable, + npmPathResolver::resolveNodeExecutable)); this.buildDir = requireNonNull(buildDir); this.configFile = configFile; this.inlineTsFmtSettings = inlineTsFmtSettings == null ? new TreeMap<>() : new TreeMap<>(inlineTsFmtSettings); @@ -102,7 +107,7 @@ private Map unifyOptions() { Map unified = new HashMap<>(); if (!this.inlineTsFmtSettings.isEmpty()) { File targetFile = new File(this.buildDir, "inline-tsfmt.json"); - SimpleJsonWriter.of(this.inlineTsFmtSettings).toJsonFile(targetFile); + JsonWriter.of(this.inlineTsFmtSettings).toJsonFile(targetFile); unified.put("tsfmt", true); unified.put("tsfmtFile", targetFile.getAbsolutePath()); } else if (this.configFile != null) { diff --git a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java index a47b608c36..61a53b4637 100644 --- a/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java +++ b/lib/src/main/java/com/diffplug/spotless/npm/TsFmtRestService.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,26 +18,19 @@ import java.util.LinkedHashMap; import java.util.Map; -public class TsFmtRestService { - - private final SimpleRestClient restClient; +public class TsFmtRestService extends BaseNpmRestService { TsFmtRestService(String baseUrl) { - this.restClient = SimpleRestClient.forBaseUrl(baseUrl); + super(baseUrl); } public String format(String fileContent, Map configOptions) { Map jsonProperties = new LinkedHashMap<>(); jsonProperties.put("file_content", fileContent); if (configOptions != null && !configOptions.isEmpty()) { - jsonProperties.put("config_options", SimpleJsonWriter.of(configOptions).toJsonRawValue()); + jsonProperties.put("config_options", JsonWriter.of(configOptions).toJsonRawValue()); } return restClient.postJson("/tsfmt/format", jsonProperties); } - - public String shutdown() { - return restClient.post("/shutdown"); - } - } diff --git a/lib/src/main/java/com/diffplug/spotless/pom/SortPomCfg.java b/lib/src/main/java/com/diffplug/spotless/pom/SortPomCfg.java index 93c11e1e31..8315035dc7 100644 --- a/lib/src/main/java/com/diffplug/spotless/pom/SortPomCfg.java +++ b/lib/src/main/java/com/diffplug/spotless/pom/SortPomCfg.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ public class SortPomCfg implements Serializable { private static final long serialVersionUID = 1L; + public String version = "3.2.1"; + public String encoding = "UTF-8"; public String lineSeparator = System.getProperty("line.separator"); @@ -43,6 +45,8 @@ public class SortPomCfg implements Serializable { public String sortDependencies = null; + public String sortDependencyManagement = null; + public String sortDependencyExclusions = null; public String sortPlugins = null; diff --git a/lib/src/main/java/com/diffplug/spotless/pom/SortPomStep.java b/lib/src/main/java/com/diffplug/spotless/pom/SortPomStep.java index 69d833f8e7..4325d16516 100644 --- a/lib/src/main/java/com/diffplug/spotless/pom/SortPomStep.java +++ b/lib/src/main/java/com/diffplug/spotless/pom/SortPomStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,8 @@ public class SortPomStep { public static final String NAME = "sortPom"; + static final String PACKAGE = "com.github.ekryd.sortpom"; + static final String MAVEN_COORDINATE = PACKAGE + ":sortpom-sorter:"; private SortPomStep() {} @@ -42,7 +44,7 @@ static class State implements Serializable { public State(SortPomCfg cfg, Provisioner provisioner) throws IOException { this.cfg = cfg; - this.jarState = JarState.from("com.github.ekryd.sortpom:sortpom-sorter:3.0.0", provisioner); + this.jarState = JarState.from(MAVEN_COORDINATE + cfg.version, provisioner); } FormatterFunc createFormat() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { diff --git a/lib/src/main/java/com/diffplug/spotless/protobuf/BufStep.java b/lib/src/main/java/com/diffplug/spotless/protobuf/BufStep.java new file mode 100644 index 0000000000..ac39eac935 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/protobuf/BufStep.java @@ -0,0 +1,101 @@ +/* + * Copyright 2022-2024 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.protobuf; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Objects; +import java.util.regex.Pattern; + +import javax.annotation.Nullable; + +import com.diffplug.spotless.ForeignExe; +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.ProcessRunner; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +public class BufStep { + public static String name() { + return "buf"; + } + + public static String defaultVersion() { + return "1.24.0"; + } + + private final String version; + private final @Nullable String pathToExe; + + private BufStep(String version, @Nullable String pathToExe) { + this.version = version; + this.pathToExe = pathToExe; + } + + public static BufStep withVersion(String version) { + return new BufStep(version, null); + } + + public BufStep withPathToExe(String pathToExe) { + return new BufStep(version, pathToExe); + } + + public FormatterStep create() { + return FormatterStep.createLazy(name(), this::createState, State::toFunc); + } + + private State createState() { + String instructions = "https://docs.buf.build/installation"; + ForeignExe exe = ForeignExe.nameAndVersion("buf", version) + .pathToExe(pathToExe) + .versionRegex(Pattern.compile("(\\S*)")) + .fixCantFind("Try following the instructions at " + instructions + ", or else tell Spotless where it is with {@code buf().pathToExe('path/to/executable')}"); + return new State(this, exe); + } + + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + static class State implements Serializable { + private static final long serialVersionUID = -1825662356883926318L; + // used for up-to-date checks and caching + final String version; + + // used for executing + private final transient ForeignExe exe; + private transient String exeAbsPath; + + State(BufStep step, ForeignExe exe) { + this.version = step.version; + this.exe = Objects.requireNonNull(exe); + } + + String format(ProcessRunner runner, String input, File file) throws IOException, InterruptedException { + if (exeAbsPath == null) { + exeAbsPath = exe.confirmVersionAndGetAbsolutePath(); + } + List args = List.of(exeAbsPath, "format", file.getAbsolutePath()); + return runner.exec(input.getBytes(StandardCharsets.UTF_8), args).assertExitZero(StandardCharsets.UTF_8); + } + + FormatterFunc.Closeable toFunc() { + ProcessRunner runner = new ProcessRunner(); + return FormatterFunc.Closeable.of(runner, this::format); + } + } +} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/package-info.java b/lib/src/main/java/com/diffplug/spotless/protobuf/ProtobufConstants.java similarity index 76% rename from _ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/package-info.java rename to lib/src/main/java/com/diffplug/spotless/protobuf/ProtobufConstants.java index da74c0471e..cd91e2671c 100644 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/osgi/package-info.java +++ b/lib/src/main/java/com/diffplug/spotless/protobuf/ProtobufConstants.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2022-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,5 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/** Simplified OSGIi implementation bypassing Equinox module layer. */ -package com.diffplug.spotless.extra.eclipse.base.osgi; +package com.diffplug.spotless.protobuf; + +public class ProtobufConstants { + public static final String LICENSE_HEADER_DELIMITER = "syntax"; +} diff --git a/lib/src/main/java/com/diffplug/spotless/rome/Architecture.java b/lib/src/main/java/com/diffplug/spotless/rome/Architecture.java new file mode 100644 index 0000000000..9ceaeb8632 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/rome/Architecture.java @@ -0,0 +1,69 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.rome; + +/** + * Enumeration of possible computer architectures. + */ +enum Architecture { + /** The arm64 architecture */ + ARM64, + /** Either x64 or x64_32 architecture */ + X64; + + /** + * Attempts to guess the architecture of the environment running the JVM. + * + * @return The best guess for the architecture. + */ + public static Architecture guess() { + var arch = System.getProperty("os.arch"); + var version = System.getProperty("os.version"); + + if (arch == null || arch.isBlank()) { + throw new IllegalStateException("No OS information is available, specify the Rome executable manually"); + } + + var msg = "Unsupported architecture " + arch + "/" + version + + ", specify the path to the Rome executable manually"; + + if (arch.equals("ppc64le")) { + throw new IllegalStateException(msg); + } + if (arch.equals("s390x")) { + throw new IllegalStateException(msg); + } + if (arch.equals("ppc64")) { + throw new IllegalStateException(msg); + } + if (arch.equals("ppc")) { + throw new IllegalStateException(msg); + } + if (arch.equals("aarch64")) { + return ARM64; + } + if (arch.equals("arm")) { + if (version.contains("v7")) { + throw new IllegalStateException(msg); + } + return ARM64; + } + if (arch.contains("64")) { + return X64; + } + throw new IllegalStateException(msg); + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/rome/BiomeFlavor.java b/lib/src/main/java/com/diffplug/spotless/rome/BiomeFlavor.java new file mode 100644 index 0000000000..dbfd43eee9 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/rome/BiomeFlavor.java @@ -0,0 +1,95 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.rome; + +/** + * The flavor of Biome to use. Exists for compatibility reason, may be removed + * shortly. + *

+ * Will be removed once the old Rome project is not supported anymore. + */ +public enum BiomeFlavor { + /** The new forked Biome project. */ + BIOME("biome", "1.2.0", "biome.json", "biome-%s-%s-%s", + "https://github.com/biomejs/biome/releases/download/cli%%2Fv%s/biome-%s"), + + /** + * The old deprecated Rome project. + * + * @deprecated Will be removed once the old Rome project is not supported + * anymore. + */ + @Deprecated + ROME("rome", "12.0.0", "rome.json", "rome-%s-%s-%s", + "https://github.com/rome/tools/releases/download/cli%%2Fv%s/rome-%s"); + + private final String configName; + private final String defaultVersion; + private final String downloadFilePattern; + private final String shortName; + private final String urlPattern; + + BiomeFlavor(String shortName, String defaultVersion, String configName, String downloadFilePattern, + String urlPattern) { + this.shortName = shortName; + this.defaultVersion = defaultVersion; + this.configName = configName; + this.downloadFilePattern = downloadFilePattern; + this.urlPattern = urlPattern; + } + + /** + * @return The name of the default config file. + */ + public String configName() { + return configName; + } + + /** + * @return Default version to use when no version was set explicitly. + */ + public String defaultVersion() { + return defaultVersion; + } + + /** + * @return The pattern for {@link String#format(String, Object...) + * String.format()} for the file name of a Biome executable for a + * certain version and architecure. The first parameter is the platform, + * the second is the OS, the third is the architecture. + */ + public String getDownloadFilePattern() { + return downloadFilePattern; + } + + /** + * @return The pattern for {@link String#format(String, Object...) + * String.format()} for the URL where the executables can be downloaded. + * The first parameter is the version, the second parameter is the OS / + * platform. + */ + public String getUrlPattern() { + return urlPattern; + } + + /** + * @return The short name of this flavor, i.e. rome or + * biome. + */ + public String shortName() { + return shortName; + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/rome/OS.java b/lib/src/main/java/com/diffplug/spotless/rome/OS.java new file mode 100644 index 0000000000..44c87d9ece --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/rome/OS.java @@ -0,0 +1,56 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.rome; + +import java.util.Locale; + +/** + * Enumeration of possible computer operation systems. + */ +enum OS { + /** Any derivate of a Linux operation system. */ + LINUX, + /** The Macintosh operating system/ */ + MAC_OS, + /** The Microsoft Windows operating system. */ + WINDOWS,; + + /** + * Attempts to guess the OS of the environment running the JVM. + * + * @return The best guess for the architecture. + * @throws IllegalStateException When the OS is either unsupported or no + * information about the OS could be retrieved. + */ + public static OS guess() { + var osName = System.getProperty("os.name"); + if (osName == null || osName.isBlank()) { + throw new IllegalStateException("No OS information is available, specify the Rome executable manually"); + } + var osNameUpper = osName.toUpperCase(Locale.ROOT); + if (osNameUpper.contains("SUNOS") || osName.contains("AIX")) { + throw new IllegalStateException( + "Unsupported OS " + osName + ", specify the path to the Rome executable manually"); + } + if (osNameUpper.contains("WINDOWS")) { + return OS.WINDOWS; + } else if (osNameUpper.contains("MAC")) { + return OS.MAC_OS; + } else { + return OS.LINUX; + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/rome/Platform.java b/lib/src/main/java/com/diffplug/spotless/rome/Platform.java new file mode 100644 index 0000000000..c50608830a --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/rome/Platform.java @@ -0,0 +1,91 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.rome; + +/** + * Represents a platform where code is run, consisting of an operating system + * and an architecture. + */ +class Platform { + /** + * Attempts to guess the platform of the hosting environment running the JVM + * machine. + * + * @return The best guess for the current OS and architecture. + * @throws IllegalStateException When no OS information is available, or when + * the OS or architecture is unsupported. + */ + public static Platform guess() { + var os = OS.guess(); + var architecture = Architecture.guess(); + return new Platform(os, architecture); + } + + private final Architecture architecture; + + private final OS os; + + /** + * Creates a new Platform descriptor for the given OS and architecture. + * + * @param os Operating system of the platform. + * @param architecture Architecture of the platform. + */ + public Platform(OS os, Architecture architecture) { + this.os = os; + this.architecture = architecture; + } + + /** + * @return The architecture of this platform. + */ + public Architecture getArchitecture() { + return architecture; + } + + /** + * @return The operating system of this platform. + */ + public OS getOs() { + return os; + } + + /** + * @return Whether the operating system is Linux. + */ + public boolean isLinux() { + return os == OS.LINUX; + } + + /** + * @return Whether the operating system is Mac. + */ + public boolean isMac() { + return os == OS.MAC_OS; + } + + /** + * @return Whether the operating system is Windows. + */ + public boolean isWindows() { + return os == OS.WINDOWS; + } + + @Override + public String toString() { + return String.format("Platform[os=%s,architecture=%s]", os, architecture); + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java b/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java new file mode 100644 index 0000000000..052f72def4 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java @@ -0,0 +1,373 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.rome; + +import java.io.IOException; +import java.math.BigInteger; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpClient.Redirect; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse.BodyHandlers; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Locale; +import java.util.Objects; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Downloader for the Biome executable: + * https://github.com/biomejs/biome. + */ +final class RomeExecutableDownloader { + private static final Logger logger = LoggerFactory.getLogger(RomeExecutableDownloader.class); + + /** + * The checksum algorithm to use for checking the integrity of downloaded files. + */ + private static final String CHECKSUM_ALGORITHM = "SHA-256"; + + /** + * The pattern for {@link String#format(String, Object...) String.format()} for + * the platform part of the Biome executable download URL. First parameter is the + * OS, second parameter the architecture, the third the file extension. + */ + private static final String PLATFORM_PATTERN = "%s-%s%s"; + + /** + * {@link OpenOption Open options} for reading an existing file without write + * access. + */ + private static final OpenOption[] READ_OPTIONS = {StandardOpenOption.READ}; + + /** + * {@link OpenOption Open options} for creating a new file, overwriting the + * existing file if present. + */ + private static final OpenOption[] WRITE_OPTIONS = {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, + StandardOpenOption.WRITE}; + + private final Path downloadDir; + + private final BiomeFlavor flavor; + + /** + * Creates a new downloader for the Biome executable. The executable files are + * stored in the given download directory. + * + * @param flavor Flavor of Biome to use. + * @param downloadDir Directory where to store the downloaded executable. + */ + public RomeExecutableDownloader(BiomeFlavor flavor, Path downloadDir) { + this.flavor = flavor; + this.downloadDir = downloadDir; + } + + /** + * Downloads the Biome executable for the current platform from the network to + * the download directory. When the executable exists already, it is + * overwritten. + * + * @param version Desired Biome version. + * @return The path to the Biome executable. + * @throws IOException When the executable cannot be downloaded from + * the network or the file system could not be + * accessed. + * @throws InterruptedException When this thread was interrupted while + * downloading the file. + * @throws IllegalStateException When no information about the current OS and + * architecture could be obtained, or when the OS + * or architecture is not supported. + */ + public Path download(String version) throws IOException, InterruptedException { + var platform = Platform.guess(); + var url = getDownloadUrl(version, platform); + var executablePath = getExecutablePath(version, platform); + var checksumPath = getChecksumPath(executablePath); + var executableDir = executablePath.getParent(); + if (executableDir != null) { + Files.createDirectories(executableDir); + } + logger.info("Attempting to download Biome from '{}' to '{}'", url, executablePath); + var request = HttpRequest.newBuilder(URI.create(url)).GET().build(); + var handler = BodyHandlers.ofFile(executablePath, WRITE_OPTIONS); + var response = HttpClient.newBuilder().followRedirects(Redirect.NORMAL).build().send(request, handler); + if (response.statusCode() != 200) { + throw new IOException("Failed to download file from " + url + ", server returned " + response.statusCode()); + } + var downloadedFile = response.body(); + if (!Files.exists(downloadedFile) || Files.size(downloadedFile) == 0) { + throw new IOException("Failed to download file from " + url + ", file is empty or does not exist"); + } + writeChecksumFile(downloadedFile, checksumPath); + logger.debug("Biome was downloaded successfully to '{}'", downloadedFile); + return downloadedFile; + } + + /** + * Ensures that the Biome executable for the current platform exists in the + * download directory. When the executable does not exist in the download + * directory, an attempt is made to download the Biome executable from the + * network. When the executable exists already, no attempt to download it again + * is made. + * + * @param version Desired Biome version. + * @return The path to the Biome executable. + * @throws IOException When the executable cannot be downloaded from + * the network or the file system could not be + * accessed. + * @throws InterruptedException When this thread was interrupted while + * downloading the file. + * @throws IllegalStateException When no information about the current OS and + * architecture could be obtained, or when the OS + * or architecture is not supported. + */ + public Path ensureDownloaded(String version) throws IOException, InterruptedException { + var platform = Platform.guess(); + logger.debug("Ensuring that Biome for platform '{}' is downloaded", platform); + var existing = findDownloaded(version); + if (existing.isPresent()) { + logger.debug("Biome was already downloaded, using executable at '{}'", existing.get()); + return existing.get(); + } else { + logger.debug("Biome was not yet downloaded, attempting to download executable"); + return download(version); + } + } + + /** + * Attempts to find the Biome executable for the current platform in the download + * directory. No attempt is made to download the executable from the network. + * + * @param version Desired Biome version. + * @return The path to the Biome executable. + * @throws IOException When the executable does not exists in the + * download directory, or when the file system + * could not be accessed. + * @throws IllegalStateException When no information about the current OS and + * architecture could be obtained, or when the OS + * or architecture is not supported. + */ + public Optional findDownloaded(String version) throws IOException { + var platform = Platform.guess(); + var executablePath = getExecutablePath(version, platform); + logger.debug("Checking Biome executable at {}", executablePath); + return checkFileWithChecksum(executablePath) ? Optional.ofNullable(executablePath) : Optional.empty(); + } + + /** + * Checks whether the given file exists and matches the checksum. The checksum + * must be contained in a file next to the file to check. + * + * @param filePath File to check. + * @return true if the file exists and matches the checksum, + * false otherwise. + */ + private boolean checkFileWithChecksum(Path filePath) { + if (!Files.exists(filePath)) { + logger.debug("File '{}' does not exist yet", filePath); + return false; + } + if (Files.isDirectory(filePath)) { + logger.debug("File '{}' exists, but is a directory", filePath); + return false; + } + var checksumPath = getChecksumPath(filePath); + if (!Files.exists(checksumPath)) { + logger.debug("File '{}' exists, but checksum file '{}' does not", filePath, checksumPath); + return false; + } + if (Files.isDirectory(checksumPath)) { + logger.debug("Checksum file '{}' exists, but is a directory", checksumPath); + return false; + } + try { + var actualChecksum = computeChecksum(filePath, CHECKSUM_ALGORITHM); + var expectedChecksum = readTextFile(checksumPath, StandardCharsets.ISO_8859_1); + logger.debug("Expected checksum: {}, actual checksum: {}", expectedChecksum, actualChecksum); + return Objects.equals(expectedChecksum, actualChecksum); + } catch (final IOException ignored) { + return false; + } + } + + /** + * Computes the checksum of the given file. + * + * @param file File to process. + * @param algorithm The checksum algorithm to use. + * @return The checksum of the given file. + * @throws IOException When the file does not exist or could not be read. + */ + private String computeChecksum(Path file, String algorithm) throws IOException { + var buffer = new byte[4192]; + try (var in = Files.newInputStream(file, READ_OPTIONS)) { + var digest = MessageDigest.getInstance(algorithm); + int result; + while ((result = in.read(buffer, 0, buffer.length)) != -1) { + digest.update(buffer, 0, result); + } + var bytes = digest.digest(); + return String.format("%0" + (bytes.length * 2) + "X", new BigInteger(1, bytes)); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + /** + * Finds the code name for the given operating system used by the Biome + * executable download URL. + * + * @param os Desired operating system. + * @return Code name for the Biome download URL. + * @throws IOException When the given OS is not supported by Biome. + */ + private String getArchitectureCodeName(Architecture architecture) throws IOException { + switch (architecture) { + case ARM64: + return "arm64"; + case X64: + return "x64"; + default: + throw new IOException("Unsupported architecture: " + architecture); + } + } + + /** + * Derives a path for the file which contains the checksum of the given file. + * + * @param file A file for which to derive the checksum file path. + * @return The path with the checksum for the given file. + */ + private Path getChecksumPath(Path file) { + var parent = file.getParent(); + var base = parent != null ? parent : file; + var fileName = file.getFileName(); + var checksumName = fileName != null ? fileName.toString() + ".sha256" : "checksum.sha256"; + return base.resolve(checksumName); + } + + /** + * Finds the URL from which the Biome executable can be downloaded. + * + * @param version Desired Biome version. + * @param platform Desired platform. + * @return The URL for the Biome executable. + * @throws IOException When the platform is not supported by Biome. + */ + private String getDownloadUrl(String version, Platform platform) throws IOException { + var osCodeName = getOsCodeName(platform.getOs()); + var architectureCodeName = getArchitectureCodeName(platform.getArchitecture()); + var extension = getDownloadUrlExtension(platform.getOs()); + var platformString = String.format(PLATFORM_PATTERN, osCodeName, architectureCodeName, extension); + return String.format(flavor.getUrlPattern(), version, platformString); + } + + /** + * Finds the file extension of the Biome download URL for the given operating + * system. + * + * @param os Desired operating system. + * @return Extension for the Biome download URL. + * @throws IOException When the given OS is not supported by Biome. + */ + private String getDownloadUrlExtension(OS os) throws IOException { + switch (os) { + case LINUX: + return ""; + case MAC_OS: + return ""; + case WINDOWS: + return ".exe"; + default: + throw new IOException("Unsupported OS: " + os); + } + } + + /** + * Finds the path on the file system for the Biome executable with a given + * version and platform. + * + * @param version Desired Biome version. + * @param platform Desired platform. + * @return The path for the Biome executable. + */ + private Path getExecutablePath(String version, Platform platform) { + var os = platform.getOs().name().toLowerCase(Locale.ROOT); + var arch = platform.getArchitecture().name().toLowerCase(Locale.ROOT); + var fileName = String.format(flavor.getDownloadFilePattern(), os, arch, version); + return downloadDir.resolve(fileName); + } + + /** + * Finds the code name for the given operating system used by the Biome + * executable download URL. + * + * @param os Desired operating system. + * @return Code name for the Biome download URL. + * @throws IOException When the given OS is not supported by Biome. + */ + private String getOsCodeName(OS os) throws IOException { + switch (os) { + case LINUX: + return "linux"; + case MAC_OS: + return "darwin"; + case WINDOWS: + return "win32"; + default: + throw new IOException("Unsupported OS: " + os); + } + } + + /** + * Reads a plain text file with the given encoding into a string. + * + * @param file File to read. + * @param charset Encoding to use. + * @return The contents of the file as a string. + * @throws IOException When the file could not be read. + */ + private String readTextFile(Path file, Charset charset) throws IOException { + try (var in = Files.newInputStream(file, READ_OPTIONS)) { + return new String(in.readAllBytes(), charset); + } + } + + /** + * Computes the checksum of the given file and writes it to the target checksum + * file, using the {@code ISO_8859_1} encoding. + * + * @param file + * @param checksumPath + * @throws IOException + */ + private void writeChecksumFile(Path file, Path checksumPath) throws IOException { + var checksum = computeChecksum(file, CHECKSUM_ALGORITHM); + try (var out = Files.newOutputStream(checksumPath, WRITE_OPTIONS)) { + out.write(checksum.getBytes(StandardCharsets.ISO_8859_1)); + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java b/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java new file mode 100644 index 0000000000..de5b3e80ee --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java @@ -0,0 +1,500 @@ +/* + * Copyright 2016-2024 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.rome; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFilePermission; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.diffplug.spotless.FileSignature; +import com.diffplug.spotless.ForeignExe; +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.ProcessRunner; + +/** + * formatter step that formats JavaScript and TypeScript code with Biome: + * https://github.com/biomejs/biome. + * It delegates to the Biome executable. The Rome executable is downloaded from + * the network when no executable path is provided explicitly. + */ +public class RomeStep { + private static final Logger logger = LoggerFactory.getLogger(RomeStep.class); + + /** + * Path to the directory with the {@code biome.json} config file, can be + * null, in which case the defaults are used. + */ + private String configPath; + + /** + * The language (syntax) of the input files to format. When null or + * the empty string, the language is detected automatically from the file name. + * Currently the following languages are supported by Biome: + *

    + *
  • js (JavaScript)
  • + *
  • jsx (JavaScript + JSX)
  • + *
  • js? (JavaScript or JavaScript + JSX, depending on the file + * extension)
  • + *
  • ts (TypeScript)
  • + *
  • tsx (TypeScript + JSX)
  • + *
  • ts? (TypeScript or TypeScript + JSX, depending on the file + * extension)
  • + *
  • json (JSON)
  • + *
+ */ + private String language; + + /** + * Biome flavor to use. Will be removed once we stop supporting the deprecated Rome project. + */ + @Deprecated + private final BiomeFlavor flavor; + + /** + * Path to the Biome executable. Can be null, but either a path to + * the executable of a download directory and version must be given. The path + * must be either an absolute path, or a file name without path separators. If + * the latter, it is interpreted as a command in the user's path. + */ + private final String pathToExe; + + /** + * Absolute path to the download directory for storing the download Biome + * executable. Can be null, but either a path to the executable of + * a download directory and version must be given. + */ + private final String downloadDir; + + /** + * Version of Biome to download. Can be null, but either a path to + * the executable of a download directory and version must be given. + */ + private final String version; + + /** + * @return The name of this format step, i.e. biome or rome. + */ + public String name() { + return flavor.shortName(); + } + + /** + * Creates a Biome step that format code by downloading to the given Biome + * version. The executable is downloaded from the network. + * + * @param flavor Flavor of Biome to use. + * @param version Version of the Biome executable to download. + * @param downloadDir Directory where to place the downloaded executable. + * @return A new Biome step that download the executable from the network. + */ + public static RomeStep withExeDownload(BiomeFlavor flavor, String version, String downloadDir) { + return new RomeStep(flavor, version, null, downloadDir); + } + + /** + * Creates a Biome step that formats code by delegating to the Biome executable + * located at the given path. + * + * @param flavor Flavor of Biome to use. + * @param pathToExe Path to the Biome executable to use. + * @return A new Biome step that format with the given executable. + */ + public static RomeStep withExePath(BiomeFlavor flavor, String pathToExe) { + return new RomeStep(flavor, null, pathToExe, null); + } + + /** + * Attempts to add a POSIX permission to the given file, ignoring any errors. + * All existing permissions on the file are preserved and the new permission is + * added, if possible. + * + * @param file File or directory to which to add a permission. + * @param permission The POSIX permission to add. + */ + private static void attemptToAddPosixPermission(Path file, PosixFilePermission permission) { + try { + var newPermissions = new HashSet<>(Files.getPosixFilePermissions(file)); + newPermissions.add(permission); + Files.setPosixFilePermissions(file, newPermissions); + } catch (final Exception ignore) { + logger.debug("Unable to add POSIX permission '{}' to file '{}'", permission, file); + } + } + + /** + * Finds the default version for Biome when no version is specified explicitly. + * Over time this will become outdated -- people should always specify the + * version explicitly! + * + * @return The default version for Biome. + */ + private static String defaultVersion(BiomeFlavor flavor) { + return flavor.defaultVersion(); + } + + /** + * Attempts to make the given file executable. This is a best-effort attempt, + * any errors are swallowed. Depending on the OS, the file might still be + * executable even if this method fails. The user will get a descriptive error + * later when we attempt to execute the Biome executable. + * + * @param filePath Path to the file to make executable. + */ + private static void makeExecutable(String filePath) { + var exePath = Paths.get(filePath); + attemptToAddPosixPermission(exePath, PosixFilePermission.GROUP_EXECUTE); + attemptToAddPosixPermission(exePath, PosixFilePermission.OTHERS_EXECUTE); + attemptToAddPosixPermission(exePath, PosixFilePermission.OWNER_EXECUTE); + } + + /** + * Finds the absolute path of a command on the user's path. Uses {@code which} + * for Linux and {@code where} for Windows. + * + * @param name Name of the command to resolve. + * @return The absolute path of the command's executable. + * @throws IOException When the command could not be resolved. + * @throws InterruptedException When this thread was interrupted while waiting + * to the which command to finish. + */ + private static String resolveNameAgainstPath(String name) throws IOException, InterruptedException { + try (var runner = new ProcessRunner()) { + var cmdWhich = runner.shellWinUnix("where " + name, "which " + name); + if (cmdWhich.exitNotZero()) { + throw new IOException("Unable to find " + name + " on path via command " + cmdWhich); + } else { + return cmdWhich.assertExitZero(Charset.defaultCharset()).trim(); + } + } + } + + /** + * Checks the Biome config path. When the config path does not exist or when it + * does not contain a file named {@code biome.json}, an error is thrown. + */ + private static void validateBiomeConfigPath(BiomeFlavor flavor, String configPath) { + if (configPath == null) { + return; + } + var path = Paths.get(configPath); + var config = path.resolve(flavor.configName()); + if (!Files.exists(path)) { + throw new IllegalArgumentException("Biome config directory does not exist: " + path); + } + if (!Files.exists(config)) { + throw new IllegalArgumentException("Biome config does not exist: " + config); + } + } + + /** + * Checks the Biome executable file. When the file does not exist, an error is + * thrown. + */ + private static void validateBiomeExecutable(String resolvedPathToExe) { + if (!new File(resolvedPathToExe).isFile()) { + throw new IllegalArgumentException("Biome executable does not exist: " + resolvedPathToExe); + } + } + + /** + * Creates a new Biome step with the configuration from the given builder. + * + * @param flavor Flavor of Biome to use. + * @param version Version of the Biome executable to download. + * @param pathToExe Path to the Biome executable to use. + * @param downloadDir Directory where to place the downloaded executable. + */ + private RomeStep(BiomeFlavor flavor, String version, String pathToExe, String downloadDir) { + this.flavor = flavor; + this.version = version != null && !version.isBlank() ? version : defaultVersion(flavor); + this.pathToExe = pathToExe; + this.downloadDir = downloadDir; + } + + /** + * Creates a formatter step with the current configuration, which formats code + * by passing it to the Biome executable. + * + * @return A new formatter step for formatting with Biome. + */ + public FormatterStep create() { + return FormatterStep.createLazy(name(), this::createState, State::toFunc); + } + + /** + * Sets the path to the directory with the {@code biome.json} config file. When + * no config path is set, the default configuration is used. + * + * @param configPath Config path to use. Must point to a directory which contains + * a file named {@code biome.json}. + * @return This builder instance for chaining method calls. + */ + public RomeStep withConfigPath(String configPath) { + this.configPath = configPath; + return this; + } + + /** + * Sets the language of the files to format When no language is set, it is + * determined automatically from the file name. The following languages are + * currently supported by Biome. + * + *
    + *
  • js (JavaScript)
  • + *
  • jsx (JavaScript + JSX)
  • + *
  • js? (JavaScript or JavaScript + JSX, depending on the file + * extension)
  • + *
  • ts (TypeScript)
  • + *
  • tsx (TypeScript + JSX)
  • + *
  • ts? (TypeScript or TypeScript + JSX, depending on the file + * extension)
  • + *
  • json (JSON)
  • + *
+ * + * @param language The language of the files to format. + * @return This builder instance for chaining method calls. + */ + public RomeStep withLanguage(String language) { + this.language = language; + return this; + } + + /** + * Resolves the Biome executable, possibly downloading it from the network, and + * creates a new state instance with the resolved executable that can format + * code via Biome. + * + * @return The state instance for formatting code via Biome. + * @throws IOException When any file system or network operations + * failed, such as when the Biome executable could + * not be downloaded, or when the given executable + * does not exist. + * @throws InterruptedException When the Biome executable needs to be downloaded + * and this thread was interrupted while waiting + * for the download to complete. + */ + private State createState() throws IOException, InterruptedException { + var resolvedPathToExe = resolveExe(); + validateBiomeExecutable(resolvedPathToExe); + validateBiomeConfigPath(flavor, configPath); + logger.debug("Using Biome executable located at '{}'", resolvedPathToExe); + var exeSignature = FileSignature.signAsList(Collections.singleton(new File(resolvedPathToExe))); + makeExecutable(resolvedPathToExe); + return new State(resolvedPathToExe, exeSignature, configPath, language); + } + + /** + * Resolves the path to the Biome executable, given the configuration of this + * step. When the path to the Biome executable is given explicitly, that path is + * used as-is. Otherwise, at attempt is made to download the Biome executable for + * the configured version from the network, unless it was already downloaded and + * is available in the cache. + * + * @return The path to the resolved Biome executable. + * @throws IOException When any file system or network operations + * failed, such as when the Biome executable could + * not be downloaded. + * @throws InterruptedException When the Biome executable needs to be downloaded + * and this thread was interrupted while waiting + * for the download to complete. + */ + private String resolveExe() throws IOException, InterruptedException { + new ForeignExe(); + if (pathToExe != null) { + if (Paths.get(pathToExe).getNameCount() == 1) { + return resolveNameAgainstPath(pathToExe); + } else { + return pathToExe; + } + } else { + var downloader = new RomeExecutableDownloader(flavor, Paths.get(downloadDir)); + var downloaded = downloader.ensureDownloaded(version).toString(); + makeExecutable(downloaded); + return downloaded; + } + } + + /** + * The internal state used by the Biome formatter. A state instance is created + * when the spotless plugin for Maven or Gradle is executed, and reused for all + * formatting requests for different files. The lifetime of the instance ends + * when the Maven or Gradle plugin was successfully executed. + *

+ * The state encapsulated a particular executable. It is serializable for + * caching purposes. Spotless keeps a cache of which files need to be formatted. + * The cache is busted when the serialized form of a state instance changes. + */ + private static class State implements Serializable { + private static final long serialVersionUID = 6846790911054484379L; + + /** Path to the exe file */ + private final String pathToExe; + + /** The signature of the exe file, if any, used for caching. */ + @SuppressWarnings("unused") + private final FileSignature exeSignature; + + /** + * The optional path to the directory with the {@code biome.json} config file. + */ + private final String configPath; + + /** + * The language of the files to format. When null or the empty + * string, the language is detected from the file name. + */ + private final String language; + + /** + * Creates a new state for instance which can format code with the given Biome + * executable. + * + * @param exe Path to the Biome executable. + * @param exeSignature Signature (e.g. SHA-256 checksum) of the Biome executable. + * @param configPath Path to the optional directory with the {@code biome.json} + * config file, can be null, in which case the + * defaults are used. + */ + private State(String exe, FileSignature exeSignature, String configPath, String language) { + this.pathToExe = exe; + this.exeSignature = exeSignature; + this.configPath = configPath; + this.language = language; + } + + /** + * Builds the list of arguments for the command that executes Biome to format a + * piece of code passed via stdin. + * + * @param file File to format. + * @return The Biome command to use for formatting code. + */ + private String[] buildBiomeCommand(File file) { + var fileName = resolveFileName(file); + var argList = new ArrayList(); + argList.add(pathToExe); + argList.add("format"); + argList.add("--stdin-file-path"); + argList.add(fileName); + if (configPath != null) { + argList.add("--config-path"); + argList.add(configPath); + } + return argList.toArray(String[]::new); + } + + /** + * Formats the given piece of code by delegating to the Biome executable. The + * code is passed to Biome via stdin, the file name is used by Biome only to + * determine the code syntax (e.g. JavaScript or TypeScript). + * + * @param runner Process runner for invoking the Biome executable. + * @param input Code to format. + * @param file File to format. + * @return The formatted code. + * @throws IOException When a file system error occurred while + * executing Biome. + * @throws InterruptedException When this thread was interrupted while waiting + * for Biome to finish formatting. + */ + private String format(ProcessRunner runner, String input, File file) throws IOException, InterruptedException { + var stdin = input.getBytes(StandardCharsets.UTF_8); + var args = buildBiomeCommand(file); + if (logger.isDebugEnabled()) { + logger.debug("Running Biome command to format code: '{}'", String.join(", ", args)); + } + var runnerResult = runner.exec(stdin, args); + var stdErr = runnerResult.stdErrUtf8(); + if (!stdErr.isEmpty()) { + logger.warn("Biome stderr ouptut for file '{}'\n{}", file, stdErr.trim()); + } + var formatted = runnerResult.assertExitZero(StandardCharsets.UTF_8); + // When biome encounters an ignored file, it does not output any formatted code + // Ignored files come from (a) the biome.json configuration file and (b) from + // a list of hard-coded file names, such as package.json or tsconfig.json. + if (formatted.isEmpty()) { + return input; + } else { + return formatted; + } + } + + /** + * The Biome executable currently does not have a parameter to specify the + * expected language / syntax. Biome always determined the language from the file + * extension. This method returns the file name for the desired language when a + * language was requested explicitly, or the file name of the input file for + * auto detection. + * + * @param file File to be formatted. + * @return The file name to pass to the Biome executable. + */ + private String resolveFileName(File file) { + var name = file.getName(); + if (language == null || language.isBlank()) { + return name; + } + var dot = name.lastIndexOf("."); + var ext = dot >= 0 ? name.substring(dot + 1) : name; + switch (language) { + case "js?": + return "jsx".equals(ext) || "js".equals(ext) || "mjs".equals(ext) || "cjs".equals(ext) ? name + : "file.js"; + case "ts?": + return "tsx".equals(ext) || "ts".equals(ext) || "mts".equals(ext) || "cts".equals(ext) ? name + : "file.js"; + case "js": + return "js".equals(ext) || "mjs".equals(ext) || "cjs".equals(ext) ? name : "file.js"; + case "jsx": + return "jsx".equals(ext) ? name : "file.jsx"; + case "ts": + return "ts".equals(ext) || "mts".equals(ext) || "cts".equals(ext) ? name : "file.ts"; + case "tsx": + return "tsx".equals(ext) ? name : "file.tsx"; + case "json": + return "json".equals(ext) ? name : "file.json"; + // so that we can support new languages such as css or yaml when Biome adds + // support for them without having to change the code + default: + return "file." + language; + } + } + + /** + * Creates a new formatter function for formatting a piece of code by delegating + * to the Biome executable. + * + * @return A formatter function for formatting code. + */ + private FormatterFunc.Closeable toFunc() { + var runner = new ProcessRunner(); + return FormatterFunc.Closeable.of(runner, this::format); + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/scala/ScalaFmtStep.java b/lib/src/main/java/com/diffplug/spotless/scala/ScalaFmtStep.java index 6ef9fcd5a7..114f4a9f1a 100644 --- a/lib/src/main/java/com/diffplug/spotless/scala/ScalaFmtStep.java +++ b/lib/src/main/java/com/diffplug/spotless/scala/ScalaFmtStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ public class ScalaFmtStep { // prevent direct instantiation private ScalaFmtStep() {} - private static final String DEFAULT_VERSION = "3.5.9"; + static final String DEFAULT_VERSION = "3.7.3"; private static final String DEFAULT_SCALA_MAJOR_VERSION = "2.13"; static final String NAME = "scalafmt"; diff --git a/lib/src/main/java/com/diffplug/spotless/shell/ShfmtStep.java b/lib/src/main/java/com/diffplug/spotless/shell/ShfmtStep.java new file mode 100644 index 0000000000..cc452fce69 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/shell/ShfmtStep.java @@ -0,0 +1,110 @@ +/* + * Copyright 2024 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.shell; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Objects; +import java.util.regex.Pattern; + +import javax.annotation.Nullable; + +import com.diffplug.spotless.ForeignExe; +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.ProcessRunner; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +public class ShfmtStep { + public static String name() { + return "shfmt"; + } + + public static String defaultVersion() { + return "3.7.0"; + } + + private final String version; + private final @Nullable String pathToExe; + + private ShfmtStep(String version, @Nullable String pathToExe) { + this.version = version; + this.pathToExe = pathToExe; + } + + public static ShfmtStep withVersion(String version) { + return new ShfmtStep(version, null); + } + + public ShfmtStep withPathToExe(String pathToExe) { + return new ShfmtStep(version, pathToExe); + } + + public FormatterStep create() { + return FormatterStep.createLazy(name(), this::createState, State::toFunc); + } + + private State createState() throws IOException, InterruptedException { + String howToInstall = "" + + "You can download shfmt from https://github.com/mvdan/sh and " + + "then point Spotless to it with {@code pathToExe('/path/to/shfmt')} " + + "or you can use your platform's package manager:" + + "\n win: choco install shfmt" + + "\n mac: brew install shfmt" + + "\n linux: apt install shfmt" + + "\n github issue to handle this better: https://github.com/diffplug/spotless/issues/673"; + final ForeignExe exe = ForeignExe.nameAndVersion("shfmt", version) + .pathToExe(pathToExe) + .versionRegex(Pattern.compile("(\\S*)")) + .fixCantFind(howToInstall) + .fixWrongVersion( + "You can tell Spotless to use the version you already have with {@code shfmt('{versionFound}')}" + + "or you can download the currently specified version, {version}.\n" + howToInstall); + return new State(this, exe); + } + + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + static class State implements Serializable { + private static final long serialVersionUID = -1825662356883926318L; + // used for up-to-date checks and caching + final String version; + final transient ForeignExe exe; + // used for executing + private transient @Nullable List args; + + State(ShfmtStep step, ForeignExe pathToExe) { + this.version = step.version; + this.exe = Objects.requireNonNull(pathToExe); + } + + String format(ProcessRunner runner, String input, File file) throws IOException, InterruptedException { + if (args == null) { + args = List.of(exe.confirmVersionAndGetAbsolutePath(), "-i", "2", "-ci"); + } + + return runner.exec(input.getBytes(StandardCharsets.UTF_8), args).assertExitZero(StandardCharsets.UTF_8); + } + + FormatterFunc.Closeable toFunc() { + ProcessRunner runner = new ProcessRunner(); + return FormatterFunc.Closeable.of(runner, this::format); + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/DBPKeywordType.java b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/DBPKeywordType.java index 93c9afb5f8..d31b7520f7 100644 --- a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/DBPKeywordType.java +++ b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/DBPKeywordType.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ * Forked from * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) - * + *

* Based on DBPKeywordType from https://github.com/serge-rider/dbeaver, * which itself is licensed under the Apache 2.0 license. */ diff --git a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/DBeaverSQLFormatterConfiguration.java b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/DBeaverSQLFormatterConfiguration.java index f488bd5a00..58a0119a7f 100644 --- a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/DBeaverSQLFormatterConfiguration.java +++ b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/DBeaverSQLFormatterConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,11 +22,11 @@ /** * **Warning:** Use this class at your own risk. It is an implementation detail and is not * guaranteed to exist in future versions. - * + *

* Forked from * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) - * + *

* Based on SQLFormatterConfiguration from https://github.com/serge-rider/dbeaver, * which itself is licensed under the Apache 2.0 license. */ diff --git a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/Pair.java b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/Pair.java index c06ea5a1c0..4ab9f54a0b 100644 --- a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/Pair.java +++ b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/Pair.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ * Forked from * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) - * + *

* Based on Pair from https://github.com/serge-rider/dbeaver, * which itself is licensed under the Apache 2.0 license. */ diff --git a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLConstants.java b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLConstants.java index 2dc0b61aa0..698c87458f 100644 --- a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLConstants.java +++ b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLConstants.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ * Forked from * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) - * + *

* Based on SQLConstants from https://github.com/serge-rider/dbeaver, * which itself is licensed under the Apache 2.0 license. */ diff --git a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLDialect.java b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLDialect.java index 3c973885f6..daabd5386b 100644 --- a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLDialect.java +++ b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLDialect.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ * Forked from * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) - * + *

* Based on SQLDialect from https://github.com/serge-rider/dbeaver, * which itself is licensed under the Apache 2.0 license. */ diff --git a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLTokenizedFormatter.java b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLTokenizedFormatter.java index f2c98eebc1..01f0fd759e 100644 --- a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLTokenizedFormatter.java +++ b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLTokenizedFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,11 +25,11 @@ /** * **Warning:** Use this class at your own risk. It is an implementation detail and is not * guaranteed to exist in future versions. - * + *

* Forked from * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) - * + *

* Based on SQLTokenizedFormatter from https://github.com/serge-rider/dbeaver, * which itself is licensed under the Apache 2.0 license. */ diff --git a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLTokensParser.java b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLTokensParser.java index 72080d22fc..b1931c3c4c 100644 --- a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLTokensParser.java +++ b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/SQLTokensParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ * Forked from * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) - * + *

* Based on SQLTokensParser from https://github.com/serge-rider/dbeaver, * which itself is licensed under the Apache 2.0 license. */ diff --git a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/TokenType.java b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/TokenType.java index 5f727db571..d6d75faf9a 100644 --- a/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/TokenType.java +++ b/lib/src/main/java/com/diffplug/spotless/sql/dbeaver/TokenType.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ * Forked from * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) - * + *

* Based on TokenType from https://github.com/serge-rider/dbeaver, * which itself is licensed under the Apache 2.0 license. */ diff --git a/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlConfig.java b/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlConfig.java new file mode 100644 index 0000000000..166ee5f307 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlConfig.java @@ -0,0 +1,50 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.yaml; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.diffplug.spotless.json.JacksonConfig; + +/** + * Specialization of {@link JacksonConfig} for YAML documents + */ +public class JacksonYamlConfig extends JacksonConfig { + private static final long serialVersionUID = 1L; + + protected Map yamlFeatureToToggle = new LinkedHashMap<>(); + + public Map getYamlFeatureToToggle() { + return Collections.unmodifiableMap(yamlFeatureToToggle); + } + + /** + * Refers to com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature + */ + public void setYamlFeatureToToggle(Map yamlFeatureToToggle) { + this.yamlFeatureToToggle = yamlFeatureToToggle; + } + + /** + * Refers to com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature + */ + public void appendYamlFeatureToToggle(Map features) { + this.yamlFeatureToToggle.putAll(features); + } + +} diff --git a/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlStep.java b/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlStep.java new file mode 100644 index 0000000000..a87e40f420 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/yaml/JacksonYamlStep.java @@ -0,0 +1,81 @@ +/* + * Copyright 2021-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.yaml; + +import java.io.IOException; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Objects; + +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.JarState; +import com.diffplug.spotless.Provisioner; + +/** + * Simple YAML formatter which reformats the file according to Jackson YAMLFactory. + */ +// https://stackoverflow.com/questions/14515994/convert-json-string-to-pretty-print-json-output-using-jackson +// https://stackoverflow.com/questions/60891174/i-want-to-load-a-yaml-file-possibly-edit-the-data-and-then-dump-it-again-how +public class JacksonYamlStep { + static final String MAVEN_COORDINATE = "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:"; + // https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml + static final String DEFAULT_VERSION = "2.14.1"; + + private JacksonYamlStep() {} + + public static String defaultVersion() { + return DEFAULT_VERSION; + } + + public static FormatterStep create(JacksonYamlConfig jacksonConfig, + String jacksonVersion, + Provisioner provisioner) { + Objects.requireNonNull(jacksonConfig, "jacksonConfig cannot be null"); + Objects.requireNonNull(provisioner, "provisioner cannot be null"); + return FormatterStep.createLazy("yaml", + () -> new State(jacksonConfig, jacksonVersion, provisioner), + State::toFormatter); + } + + public static FormatterStep create(Provisioner provisioner) { + return create(new JacksonYamlConfig(), defaultVersion(), provisioner); + } + + private static final class State implements Serializable { + private static final long serialVersionUID = 1L; + + private final JacksonYamlConfig jacksonConfig; + + private final JarState jarState; + + private State(JacksonYamlConfig jacksonConfig, + String jacksonVersion, + Provisioner provisioner) throws IOException { + this.jacksonConfig = jacksonConfig; + + this.jarState = JarState.from(JacksonYamlStep.MAVEN_COORDINATE + jacksonVersion, provisioner); + } + + FormatterFunc toFormatter() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, + InstantiationException, IllegalAccessException { + Class formatterFunc = jarState.getClassLoader().loadClass("com.diffplug.spotless.glue.yaml.JacksonYamlFormatterFunc"); + Constructor constructor = formatterFunc.getConstructor(JacksonYamlConfig.class); + return (FormatterFunc) constructor.newInstance(jacksonConfig); + } + } +} diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/common-serve.js b/lib/src/main/resources/com/diffplug/spotless/npm/common-serve.js new file mode 100644 index 0000000000..c1c9d62757 --- /dev/null +++ b/lib/src/main/resources/com/diffplug/spotless/npm/common-serve.js @@ -0,0 +1,39 @@ +// this file will be glued to the top of the specific xy-serve.js file +const debug_serve = false; // set to true for debug log output in node process +const GracefulShutdownManager = require("@moebius/http-graceful-shutdown").GracefulShutdownManager; +const express = require("express"); +const app = express(); + +app.use(express.json({ limit: "50mb" })); + +const fs = require("fs"); + +function debugLog() { + if (debug_serve) { + console.log.apply(this, arguments) + } +} + +var listener = app.listen(0, "127.0.0.1", () => { + debugLog("Server running on port " + listener.address().port); + fs.writeFile("server.port.tmp", "" + listener.address().port, function(err) { + if (err) { + return console.log(err); + } else { + fs.rename("server.port.tmp", "server.port", function(err) { + if (err) { + return console.log(err); + } + }); // try to be as atomic as possible + } + }); +}); +const shutdownManager = new GracefulShutdownManager(listener); + +app.post("/shutdown", (req, res) => { + res.status(200).send("Shutting down"); + setTimeout(function() { + shutdownManager.terminate(() => debugLog("graceful shutdown finished.")); + }, 200); +}); + diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/eslint-package.json b/lib/src/main/resources/com/diffplug/spotless/npm/eslint-package.json new file mode 100644 index 0000000000..0d7ce930f7 --- /dev/null +++ b/lib/src/main/resources/com/diffplug/spotless/npm/eslint-package.json @@ -0,0 +1,19 @@ +{ + "name": "spotless-eslint", + "version": "2.0.0", + "description": "Spotless formatter step for running eslint as a rest service.", + "repository": "https://github.com/diffplug/spotless", + "license": "Apache-2.0", + "scripts": { + "start": "node serve.js" + }, + "devDependencies": { +${devDependencies}, + "express": "4.18.2", + "@moebius/http-graceful-shutdown": "1.1.0" + }, + "dependencies": {}, + "engines": { + "node": ">=6" + } +} diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/eslint-serve.js b/lib/src/main/resources/com/diffplug/spotless/npm/eslint-serve.js new file mode 100644 index 0000000000..8b60e56dc8 --- /dev/null +++ b/lib/src/main/resources/com/diffplug/spotless/npm/eslint-serve.js @@ -0,0 +1,74 @@ +const {ESLint} = require("eslint"); + +app.post("/eslint/format", async (req, res) => { + try { + const format_data = req.body; + + const ESLintOverrideConfig = format_data.eslint_override_config; + + const ESLintOverrideConfigFile = format_data.eslint_override_config_file; + + if (!ESLintOverrideConfig && !ESLintOverrideConfigFile) { + res.status(400).send("Error while formatting: No config provided"); + return; + } + + const filePath = format_data.file_path; + + if (!filePath) { + res.status(400).send("Error while formatting: No file path provided"); + return; + } + + const ESLintOptions = { + fix: true, + useEslintrc: false, // would result in (gradle) cache issues + }; + + if (format_data.ts_config_root_dir) { + ESLintOptions.baseConfig = { + parserOptions: { + tsconfigRootDir: format_data.ts_config_root_dir + } + }; + } + + + if (ESLintOverrideConfigFile) { + ESLintOptions.overrideConfigFile = ESLintOverrideConfigFile; + } + if (ESLintOverrideConfig) { + eval("ESLintOptions.overrideConfig = " + ESLintOverrideConfig); + } + + debugLog("using options: " + JSON.stringify(ESLintOptions)); + debugLog("format input: ", format_data.file_content); + + const eslint = new ESLint(ESLintOptions); + + + const lintTextOptions = { + filePath: filePath, + } + debugLog("lintTextOptions", lintTextOptions); + + // LintResult[] // https://eslint.org/docs/latest/developer-guide/nodejs-api#-lintresult-type + const results = await eslint.lintText(format_data.file_content, lintTextOptions); + if (results.length !== 1) { + res.status(500).send("Error while formatting: Unexpected number of results: " + JSON.stringify(results)); + return; + } + const result = results[0]; + debugLog("result: " + JSON.stringify(result)); + if (result.fatalErrorCount && result.fatalErrorCount > 0) { + res.status(500).send("Fatal error while formatting: " + JSON.stringify(result.messages)); + return; + } + const formatted = result.output || result.source || format_data.file_content; + res.set("Content-Type", "text/plain"); + res.send(formatted); + } catch (err) { + console.log("error", err); + res.status(500).send("Error while formatting: " + err); + } +}); diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json index 113f20bc3f..395a05da67 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json +++ b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-package.json @@ -1,5 +1,5 @@ { - "name": "spotless-prettier-formatter-step", + "name": "spotless-prettier", "version": "2.0.0", "description": "Spotless formatter step for running prettier as a rest service.", "repository": "https://github.com/diffplug/spotless", @@ -9,7 +9,7 @@ }, "devDependencies": { ${devDependencies}, - "express": "4.17.1", + "express": "4.18.2", "@moebius/http-graceful-shutdown": "1.1.0" }, "dependencies": {}, diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js index 351ef73f9a..ac1f26790d 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js +++ b/lib/src/main/resources/com/diffplug/spotless/npm/prettier-serve.js @@ -1,45 +1,15 @@ -const GracefulShutdownManager = require("@moebius/http-graceful-shutdown").GracefulShutdownManager; -const express = require("express"); -const app = express(); - -app.use(express.json({ limit: "50mb" })); const prettier = require("prettier"); -const fs = require("fs"); - -var listener = app.listen(0, "127.0.0.1", () => { - console.log("Server running on port " + listener.address().port); - fs.writeFile("server.port.tmp", "" + listener.address().port, function(err) { - if (err) { - return console.log(err); - } else { - fs.rename("server.port.tmp", "server.port", function(err) { - if (err) { - return console.log(err); - } - }); // try to be as atomic as possible - } - }); -}); -const shutdownManager = new GracefulShutdownManager(listener); - -app.post("/shutdown", (req, res) => { - res.status(200).send("Shutting down"); - setTimeout(function() { - shutdownManager.terminate(() => console.log("graceful shutdown finished.")); - }, 200); -}); - app.post("/prettier/config-options", (req, res) => { - var config_data = req.body; - var prettier_config_path = config_data.prettier_config_path; - var prettier_config_options = config_data.prettier_config_options || {}; + const config_data = req.body; + const prettier_config_path = config_data.prettier_config_path; + const prettier_config_options = config_data.prettier_config_options || {}; if (prettier_config_path) { prettier .resolveConfig(undefined, { config: prettier_config_path }) .then(options => { - var mergedConfigOptions = mergeConfigOptions(options, prettier_config_options); + const mergedConfigOptions = mergeConfigOptions(options, prettier_config_options); res.set("Content-Type", "application/json") res.json(mergedConfigOptions); }) @@ -50,21 +20,34 @@ app.post("/prettier/config-options", (req, res) => { res.json(prettier_config_options); }); -app.post("/prettier/format", (req, res) => { - var format_data = req.body; +app.post("/prettier/format", async (req, res) => { + const format_data = req.body; - var formatted_file_content = ""; + let formatted_file_content = ""; try { - formatted_file_content = prettier.format(format_data.file_content, format_data.config_options); + formatted_file_content = await prettierFormat(format_data.file_content, format_data.config_options); } catch(err) { - res.status(501).send("Error while formatting: " + err); + res.status(500).send("Error while formatting: " + err); return; } res.set("Content-Type", "text/plain"); res.send(formatted_file_content); }); -var mergeConfigOptions = function(resolved_config_options, config_options) { +const prettierFormat = async function(file_content, config_options) { + const result = prettier.format(file_content, config_options); + + // Check if result is a Promise (version 3.0.0 and above) + if (typeof result.then === 'function') { + return result; + } + + // If it's not a Promise (meaning it's a string), wrap it in a Promise (< 3.0.0) + return Promise.resolve(result); +} + + +const mergeConfigOptions = function(resolved_config_options, config_options) { if (resolved_config_options !== undefined && config_options !== undefined) { return extend(resolved_config_options, config_options); } @@ -76,15 +59,15 @@ var mergeConfigOptions = function(resolved_config_options, config_options) { } }; -var extend = function() { +const extend = function() { // Variables - var extended = {}; - var i = 0; - var length = arguments.length; + const extended = {}; + let i = 0; + const length = arguments.length; // Merge the object into the extended object - var merge = function(obj) { - for (var prop in obj) { + const merge = function (obj) { + for (const prop in obj) { if (Object.prototype.hasOwnProperty.call(obj, prop)) { extended[prop] = obj[prop]; } @@ -93,9 +76,8 @@ var extend = function() { // Loop through each object and conduct a merge for (; i < length; i++) { - var obj = arguments[i]; + const obj = arguments[i]; merge(obj); } - return extended; }; diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json index d6e5eff3b2..483dd0753d 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json +++ b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-package.json @@ -1,5 +1,5 @@ { - "name": "spotless-tsfmt-formatter-step", + "name": "spotless-tsfmt", "version": "2.0.0", "description": "Spotless formatter step for running tsfmt as a rest service.", "repository": "https://github.com/diffplug/spotless", @@ -9,7 +9,7 @@ }, "devDependencies": { ${devDependencies}, - "express": "4.17.1", + "express": "4.18.2", "@moebius/http-graceful-shutdown": "1.1.0" }, "dependencies": {}, diff --git a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js index b25048d410..b9f20a1472 100644 --- a/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js +++ b/lib/src/main/resources/com/diffplug/spotless/npm/tsfmt-serve.js @@ -1,36 +1,5 @@ -const GracefulShutdownManager = require("@moebius/http-graceful-shutdown").GracefulShutdownManager; -const express = require("express"); -const app = express(); -app.use(express.json({ limit: "50mb" })); - const tsfmt = require("typescript-formatter"); -const fs = require("fs"); - -var listener = app.listen(0, "127.0.0.1", () => { - console.log("Server running on port " + listener.address().port); - fs.writeFile("server.port.tmp", "" + listener.address().port, function(err) { - if (err) { - return console.log(err); - } else { - fs.rename("server.port.tmp", "server.port", function(err) { - if (err) { - return console.log(err); - } - }); // try to be as atomic as possible - } - }); -}); - -const shutdownManager = new GracefulShutdownManager(listener); - -app.post("/shutdown", (req, res) => { - res.status(200).send("Shutting down"); - setTimeout(function() { - shutdownManager.terminate(() => console.log("graceful shutdown finished.")); - }, 200); -}); - app.post("/tsfmt/format", (req, res) => { var format_data = req.body; tsfmt.processString("spotless-format-string.ts", format_data.file_content, format_data.config_options).then(resultMap => { @@ -56,6 +25,6 @@ app.post("/tsfmt/format", (req, res) => { res.set("Content-Type", "text/plain"); res.send(resultMap.dest); }).catch(reason => { - res.status(501).send(reason); + res.status(500).send(reason); }); }); diff --git a/lib/src/palantirJavaFormat/java/com/diffplug/spotless/glue/pjf/PalantirJavaFormatFormatterFunc.java b/lib/src/palantirJavaFormat/java/com/diffplug/spotless/glue/pjf/PalantirJavaFormatFormatterFunc.java index 6824cdbc48..bdec215435 100644 --- a/lib/src/palantirJavaFormat/java/com/diffplug/spotless/glue/pjf/PalantirJavaFormatFormatterFunc.java +++ b/lib/src/palantirJavaFormat/java/com/diffplug/spotless/glue/pjf/PalantirJavaFormatFormatterFunc.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 DiffPlug + * Copyright 2022-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,9 @@ */ package com.diffplug.spotless.glue.pjf; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + import com.palantir.javaformat.java.Formatter; import com.palantir.javaformat.java.ImportOrderer; import com.palantir.javaformat.java.JavaFormatterOptions; @@ -26,16 +29,28 @@ public class PalantirJavaFormatFormatterFunc implements FormatterFunc { private final Formatter formatter; - public PalantirJavaFormatFormatterFunc() { - formatter = Formatter.createFormatter(JavaFormatterOptions.builder() - .style(JavaFormatterOptions.Style.PALANTIR) - .build()); + private final JavaFormatterOptions.Style formatterStyle; + + /** + * Creates a new formatter func that formats code via Palantir. + * @param style The style to use for formatting. + * @param formatJavadoc Whether to format Java docs. Requires at least Palantir 2.36.0 or later, otherwise the + * constructor will throw. + */ + public PalantirJavaFormatFormatterFunc(String style, boolean formatJavadoc) { + this.formatterStyle = JavaFormatterOptions.Style.valueOf(style); + JavaFormatterOptions.Builder builder = JavaFormatterOptions.builder(); + builder.style(formatterStyle); + if (formatJavadoc) { + applyFormatJavadoc(builder); + } + formatter = Formatter.createFormatter(builder.build()); } @Override public String apply(String input) throws Exception { String source = input; - source = ImportOrderer.reorderImports(source, JavaFormatterOptions.Style.PALANTIR); + source = ImportOrderer.reorderImports(source, formatterStyle); source = RemoveUnusedImports.removeUnusedImports(source); return formatter.formatSource(source); } @@ -44,4 +59,15 @@ public String apply(String input) throws Exception { public String toString() { return "PalantirJavaFormatFormatterFunc{formatter=" + formatter + '}'; } + + private static void applyFormatJavadoc(JavaFormatterOptions.Builder builder) { + // The formatJavadoc option is available since Palantir 2.36.0 + // To support older versions for now, attempt to invoke the builder method via reflection. + try { + Method formatJavadoc = JavaFormatterOptions.Builder.class.getMethod("formatJavadoc", boolean.class); + formatJavadoc.invoke(builder, true); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + throw new IllegalStateException("Cannot enable formatJavadoc option, make sure you are using Palantir with version 2.36.0 or later", e); + } + } } diff --git a/lib/src/sortPom/java/com/diffplug/spotless/glue/pom/SortPomFormatterFunc.java b/lib/src/sortPom/java/com/diffplug/spotless/glue/pom/SortPomFormatterFunc.java index 22a0249634..4dcde62bae 100644 --- a/lib/src/sortPom/java/com/diffplug/spotless/glue/pom/SortPomFormatterFunc.java +++ b/lib/src/sortPom/java/com/diffplug/spotless/glue/pom/SortPomFormatterFunc.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,11 +15,10 @@ */ package com.diffplug.spotless.glue.pom; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; +import java.io.*; +import java.nio.charset.Charset; +import java.nio.file.Files; -import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,10 +39,12 @@ public SortPomFormatterFunc(SortPomCfg cfg) { @Override public String apply(String input) throws Exception { - // SortPom expects a file to sort, so we write the inpout into a temporary file + // SortPom expects a file to sort, so we write the input into a temporary file File pom = File.createTempFile("pom", ".xml"); pom.deleteOnExit(); - IOUtils.write(input, new FileOutputStream(pom), cfg.encoding); + try (BufferedWriter writer = new BufferedWriter(new FileWriter(pom, Charset.forName(cfg.encoding)))) { + writer.write(input); + } SortPomImpl sortPom = new SortPomImpl(); sortPom.setup(new MySortPomLogger(), PluginParameters.builder() .setPomFile(pom) @@ -52,11 +53,12 @@ public String apply(String input) throws Exception { .setFormatting(cfg.lineSeparator, cfg.expandEmptyElements, cfg.spaceBeforeCloseEmptyElement, cfg.keepBlankLines) .setIndent(cfg.nrOfIndentSpace, cfg.indentBlankLines, cfg.indentSchemaLocation) .setSortOrder(cfg.sortOrderFile, cfg.predefinedSortOrder) - .setSortEntities(cfg.sortDependencies, cfg.sortDependencyExclusions, cfg.sortPlugins, cfg.sortProperties, cfg.sortModules, cfg.sortExecutions) - .setTriggers(false) + .setSortEntities(cfg.sortDependencies, cfg.sortDependencyExclusions, cfg.sortDependencyManagement, + cfg.sortPlugins, cfg.sortProperties, cfg.sortModules, cfg.sortExecutions) + .setIgnoreLineSeparators(false) .build()); sortPom.sortPom(); - return IOUtils.toString(new FileInputStream(pom), cfg.encoding); + return Files.readString(pom.toPath(), Charset.forName(cfg.encoding)); } private static class MySortPomLogger implements SortPomLogger { diff --git a/lib/src/test/java/com/diffplug/spotless/RingBufferByteArrayOutputStreamTest.java b/lib/src/test/java/com/diffplug/spotless/RingBufferByteArrayOutputStreamTest.java new file mode 100644 index 0000000000..94fa49dbc1 --- /dev/null +++ b/lib/src/test/java/com/diffplug/spotless/RingBufferByteArrayOutputStreamTest.java @@ -0,0 +1,179 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.stream.Stream; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class RingBufferByteArrayOutputStreamTest { + + private final byte[] bytes = new byte[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; + + @ParameterizedTest(name = "{index} writeStrategy: {0}") + @MethodSource("writeStrategies") + void toStringBehavesNormallyWithinLimit(String name, ByteWriteStrategy writeStrategy) { + RingBufferByteArrayOutputStream stream = new RingBufferByteArrayOutputStream(12, 1); + writeStrategy.write(stream, bytes); + Assertions.assertThat(stream.toString()).isEqualTo("0123456789"); + } + + @ParameterizedTest(name = "{index} writeStrategy: {0}") + @MethodSource("writeStrategies") + void toStringBehavesOverwritingOverLimit(String name, ByteWriteStrategy writeStrategy) { + RingBufferByteArrayOutputStream stream = new RingBufferByteArrayOutputStream(4, 1); + writeStrategy.write(stream, bytes); + Assertions.assertThat(stream.toString()).hasSize(4); + Assertions.assertThat(stream.toString()).isEqualTo("6789"); + } + + @ParameterizedTest(name = "{index} writeStrategy: {0}") + @MethodSource("writeStrategies") + void toStringBehavesNormallyAtExactlyLimit(String name, ByteWriteStrategy writeStrategy) { + RingBufferByteArrayOutputStream stream = new RingBufferByteArrayOutputStream(bytes.length, 1); + writeStrategy.write(stream, bytes); + Assertions.assertThat(stream.toString()).isEqualTo("0123456789"); + } + + @ParameterizedTest(name = "{index} writeStrategy: {0}") + @MethodSource("writeStrategies") + void toByteArrayBehavesNormallyWithinLimit(String name, ByteWriteStrategy writeStrategy) { + RingBufferByteArrayOutputStream stream = new RingBufferByteArrayOutputStream(12, 1); + writeStrategy.write(stream, bytes); + Assertions.assertThat(stream.toByteArray()).isEqualTo(bytes); + } + + @ParameterizedTest(name = "{index} writeStrategy: {0}") + @MethodSource("writeStrategies") + void toByteArrayBehavesOverwritingOverLimit(String name, ByteWriteStrategy writeStrategy) { + RingBufferByteArrayOutputStream stream = new RingBufferByteArrayOutputStream(4, 1); + writeStrategy.write(stream, bytes); + Assertions.assertThat(stream.toByteArray()).hasSize(4); + Assertions.assertThat(stream.toByteArray()).isEqualTo(new byte[]{'6', '7', '8', '9'}); + } + + @ParameterizedTest(name = "{index} writeStrategy: {0}") + @MethodSource("writeStrategies") + void toByteArrayBehavesOverwritingAtExactlyLimit(String name, ByteWriteStrategy writeStrategy) { + RingBufferByteArrayOutputStream stream = new RingBufferByteArrayOutputStream(bytes.length, 1); + writeStrategy.write(stream, bytes); + Assertions.assertThat(stream.toByteArray()).isEqualTo(bytes); + } + + @ParameterizedTest(name = "{index} writeStrategy: {0}") + @MethodSource("writeStrategies") + void writeToBehavesNormallyWithinLimit(String name, ByteWriteStrategy writeStrategy) throws IOException { + RingBufferByteArrayOutputStream stream = new RingBufferByteArrayOutputStream(12, 1); + writeStrategy.write(stream, bytes); + ByteArrayOutputStream target = new ByteArrayOutputStream(); + stream.writeTo(target); + Assertions.assertThat(target.toByteArray()).isEqualTo(bytes); + } + + @ParameterizedTest(name = "{index} writeStrategy: {0}") + @MethodSource("writeStrategies") + void writeToBehavesOverwritingOverLimit(String name, ByteWriteStrategy writeStrategy) throws IOException { + RingBufferByteArrayOutputStream stream = new RingBufferByteArrayOutputStream(4, 1); + writeStrategy.write(stream, bytes); + ByteArrayOutputStream target = new ByteArrayOutputStream(); + stream.writeTo(target); + Assertions.assertThat(target.toByteArray()).hasSize(4); + Assertions.assertThat(target.toByteArray()).isEqualTo(new byte[]{'6', '7', '8', '9'}); + } + + @ParameterizedTest(name = "{index} writeStrategy: {0}") + @MethodSource("writeStrategies") + void writeToBehavesNormallyAtExactlyLimit(String name, ByteWriteStrategy writeStrategy) throws IOException { + RingBufferByteArrayOutputStream stream = new RingBufferByteArrayOutputStream(bytes.length, 1); + writeStrategy.write(stream, bytes); + ByteArrayOutputStream target = new ByteArrayOutputStream(); + stream.writeTo(target); + Assertions.assertThat(target.toByteArray()).isEqualTo(bytes); + } + + @Test + void writeToBehavesCorrectlyWhenOverLimitMultipleCalls() { + // this test explicitly captures a border case where the buffer is not empty but can exactly fit what we are writing + RingBufferByteArrayOutputStream stream = new RingBufferByteArrayOutputStream(2, 1); + stream.write('0'); + stream.write(new byte[]{'1', '2'}, 0, 2); + Assertions.assertThat(stream.toString()).hasSize(2); + Assertions.assertThat(stream.toString()).isEqualTo("12"); + } + + private static Stream writeStrategies() { + return Stream.of( + Arguments.of("writeAllAtOnce", allAtOnce()), + Arguments.of("writeOneByteAtATime", oneByteAtATime()), + Arguments.of("writeTwoBytesAtATime", twoBytesAtATime()), + Arguments.of("writeOneAndThenTwoBytesAtATime", oneAndThenTwoBytesAtATime()), + Arguments.of("firstFourBytesAndThenTheRest", firstFourBytesAndThenTheRest())); + } + + private static ByteWriteStrategy allAtOnce() { + return (stream, bytes) -> stream.write(bytes, 0, bytes.length); + } + + private static ByteWriteStrategy oneByteAtATime() { + return (stream, bytes) -> { + for (byte b : bytes) { + stream.write(b); + } + }; + } + + private static ByteWriteStrategy twoBytesAtATime() { + return (stream, bytes) -> { + for (int i = 0; i < bytes.length; i += 2) { + stream.write(bytes, i, 2); + } + }; + } + + private static ByteWriteStrategy oneAndThenTwoBytesAtATime() { + return (stream, bytes) -> { + int written = 0; + for (int i = 0; i + 3 < bytes.length; i += 3) { + stream.write(bytes, i, 1); + stream.write(bytes, i + 1, 2); + written += 3; + } + if (written < bytes.length) { + stream.write(bytes, written, bytes.length - written); + } + + }; + } + + private static ByteWriteStrategy firstFourBytesAndThenTheRest() { + return (stream, bytes) -> { + stream.write(bytes, 0, 4); + stream.write(bytes, 4, bytes.length - 4); + }; + } + + @FunctionalInterface + private interface ByteWriteStrategy { + void write(RingBufferByteArrayOutputStream stream, byte[] bytes); + } + +} diff --git a/lib/src/test/java/com/diffplug/spotless/npm/TimedLoggerTest.java b/lib/src/test/java/com/diffplug/spotless/npm/TimedLoggerTest.java new file mode 100644 index 0000000000..f08d4f1622 --- /dev/null +++ b/lib/src/test/java/com/diffplug/spotless/npm/TimedLoggerTest.java @@ -0,0 +1,269 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.npm; + +import static com.diffplug.spotless.npm.TimedLogger.MESSAGE_PREFIX_BEGIN; +import static com.diffplug.spotless.npm.TimedLogger.MESSAGE_PREFIX_END; +import static com.diffplug.spotless.npm.TimedLogger.MESSAGE_SUFFIX_TOOK; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import org.assertj.core.api.Assertions; +import org.assertj.core.api.Condition; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Marker; +import org.slf4j.event.Level; +import org.slf4j.helpers.LegacyAbstractLogger; + +import com.diffplug.spotless.npm.TimedLogger.TestTicker; + +class TimedLoggerTest { + + private TestLogger testLogger; + + private TestTicker testTicker; + + private TimedLogger timedLogger; + + @BeforeEach + void setUp() { + testLogger = new TestLogger(); + testTicker = new TestTicker(); + timedLogger = TimedLogger.forLogger(testLogger, testTicker); + } + + @Test + void itDoesNotLogWhenLevelDisabled() { + + TestLogger logger = new TestLogger() { + @Override + public boolean isInfoEnabled() { + return false; + } + + @Override + public boolean isDebugEnabled() { + return false; + } + + @Override + public boolean isTraceEnabled() { + return false; + } + }; + TimedLogger timedLogger = TimedLogger.forLogger(logger); + + timedLogger.withInfo("This should not be logged").run(() -> Thread.sleep(1)); + logger.assertNoEvents(); + } + + @Test + void itLogsMillisWhenTakingMillis() { + timedLogger.withInfo("This should be logged").run(() -> testTicker.tickMillis(999)); + + testLogger.assertEvents(2); + testLogger.assertHasEventWithMessageAndArguments(MESSAGE_SUFFIX_TOOK, "999ms"); + } + + @Test + void itLogsSecondsOnlyWhenTakingSeconds() { + timedLogger.withInfo("This should be logged").run(() -> testTicker.tickMillis(2_000)); + + testLogger.assertEvents(2); + testLogger.assertHasEventWithMessageAndArguments(MESSAGE_SUFFIX_TOOK, "2.0s"); + } + + @Test + void itLogsMinutesOnlyWhenTakingMinutes() { + timedLogger.withInfo("This should be logged").run(() -> testTicker.tickMillis(2 * 60 * 1_000)); + + testLogger.assertEvents(2); + testLogger.assertHasEventWithMessageAndArguments(MESSAGE_SUFFIX_TOOK, "2m"); + } + + @Test + void itLogsMinutesAndSecondsWhenTakingMinutesAndSeconds() { + timedLogger.withInfo("This should be logged").run(() -> testTicker.tickMillis(2 * 60 * 1_000 + 3 * 1_000)); + + testLogger.assertEvents(2); + testLogger.assertHasEventWithMessageAndArguments(MESSAGE_SUFFIX_TOOK, "2m 3.0s"); + } + + @Test + void itLogsBeginAndEndPrefixes() { + timedLogger.withInfo("This should be logged").run(() -> testTicker.tickMillis(1)); + + testLogger.assertHasEventWithMessageAndArguments(MESSAGE_PREFIX_BEGIN); + testLogger.assertHasEventWithMessageAndArguments(MESSAGE_PREFIX_END, "1ms"); + } + + @Test + void itThrowsExceptionsInChecked() { + Assertions.assertThatThrownBy(() -> timedLogger.withInfo("This should be logged").runChecked(() -> { + throw new Exception("This is an exception"); + })).isInstanceOf(Exception.class).hasMessage("This is an exception"); + } + + @Test + void itLogsEvenWhenExceptionsAreThrown() { + Assertions.assertThatThrownBy(() -> timedLogger.withInfo("This should be logged").run(() -> { + testTicker.tickMillis(2); + throw new Exception("This is an exception"); + })).isInstanceOf(RuntimeException.class) + .hasMessageContaining("This is an exception") + .hasCauseInstanceOf(Exception.class); + + testLogger.assertEvents(2); + testLogger.assertHasEventWithMessageAndArguments(MESSAGE_PREFIX_BEGIN); + testLogger.assertHasEventWithMessageAndArguments(MESSAGE_PREFIX_END, "2ms"); + } + + @Test + void itReturnsValueOfCallableWhileStillLogging() { + String result = timedLogger.withInfo("This should be logged").call(() -> { + testTicker.tickMillis(2); + return "This is the result"; + }); + + Assertions.assertThat(result).isEqualTo("This is the result"); + + testLogger.assertEvents(2); + testLogger.assertHasEventWithMessageAndArguments(MESSAGE_PREFIX_BEGIN); + testLogger.assertHasEventWithMessageAndArguments(MESSAGE_PREFIX_END, "2ms"); + } + + private static class TestLogger extends LegacyAbstractLogger { + + private final List events = new LinkedList<>(); + + @Override + protected String getFullyQualifiedCallerName() { + return TestLogger.class.getName(); + } + + @Override + protected void handleNormalizedLoggingCall(Level level, Marker marker, String msg, Object[] arguments, Throwable throwable) { + events.add(new TestLoggingEvent(level, marker, msg, arguments, throwable)); + } + + @Override + public boolean isTraceEnabled() { + return true; + } + + @Override + public boolean isDebugEnabled() { + return true; + } + + @Override + public boolean isInfoEnabled() { + return true; + } + + @Override + public boolean isWarnEnabled() { + return true; + } + + @Override + public boolean isErrorEnabled() { + return true; + } + + public List getEvents() { + return events; + } + + public void assertNoEvents() { + Assertions.assertThat(getEvents()).isEmpty(); + } + + public void assertEvents(int eventCount) { + Assertions.assertThat(getEvents()).hasSize(eventCount); + } + + public void assertHasEventWithMessageAndArguments(String message, Object... arguments) { + + Assertions.assertThat(getEvents()).haveAtLeastOne(new Condition<>(event -> { + if (!event.msg().contains(message)) { + return false; + } + if (event.arguments().length != arguments.length) { + return false; + } + for (int i = 0; i < arguments.length; i++) { + if (!String.valueOf(event.arguments()[i]).equals(arguments[i])) { + return false; + } + } + return true; + }, "Event with message containing '%s' and arguments '%s'", message, Arrays.toString(arguments))); + } + } + + private static class TestLoggingEvent { + + private final Level level; + private final Marker marker; + private final String msg; + private final Object[] arguments; + private final Throwable throwable; + + public TestLoggingEvent(Level level, Marker marker, String msg, Object[] arguments, Throwable throwable) { + this.level = level; + this.marker = marker; + this.msg = msg; + this.arguments = arguments; + this.throwable = throwable; + } + + public Level level() { + return level; + } + + public Marker marker() { + return marker; + } + + public String msg() { + return msg; + } + + public Object[] arguments() { + return arguments; + } + + public Throwable throwable() { + return throwable; + } + + @Override + public String toString() { + return String.format( + "TestLoggingEvent[level=%s, marker=%s, msg=%s, arguments=%s, throwable=%s]", + this.level, + this.marker, + this.msg, + Arrays.toString(this.arguments), + this.throwable); + } + } + +} diff --git a/lib/src/testCompatCleanthat2Dot1/java/com/diffplug/spotless/glue/java/JavaCleanthatRefactorerFuncTest.java b/lib/src/testCompatCleanthat2Dot1/java/com/diffplug/spotless/glue/java/JavaCleanthatRefactorerFuncTest.java new file mode 100644 index 0000000000..2f3cdc9646 --- /dev/null +++ b/lib/src/testCompatCleanthat2Dot1/java/com/diffplug/spotless/glue/java/JavaCleanthatRefactorerFuncTest.java @@ -0,0 +1,28 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.glue.java; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import eu.solven.cleanthat.engine.java.refactorer.JavaRefactorer; + +public class JavaCleanthatRefactorerFuncTest { + @Test + public void testMutatorsDetection() { + Assertions.assertThat(JavaRefactorer.getAllIncluded()).isNotEmpty(); + } +} diff --git a/lib/src/testCompatKtLint0Dot48Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot48Dot0AdapterTest.java b/lib/src/testCompatKtLint0Dot48Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot48Dot0AdapterTest.java new file mode 100644 index 0000000000..17fe40fb9c --- /dev/null +++ b/lib/src/testCompatKtLint0Dot48Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot48Dot0AdapterTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.glue.ktlint.compat; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class KtLintCompat0Dot48Dot0AdapterTest { + @Test + public void testDefaults(@TempDir Path path) throws IOException { + KtLintCompat0Dot48Dot0Adapter ktLintCompat0Dot48Dot0Adapter = new KtLintCompat0Dot48Dot0Adapter(); + loadAndWriteText(path, "empty_class_body.kt"); + final Path filePath = Paths.get(path.toString(), "empty_class_body.kt"); + + Map editorConfigOverrideMap = new HashMap<>(); + + String formatted = ktLintCompat0Dot48Dot0Adapter.format(filePath, null, editorConfigOverrideMap); + assertEquals("class empty_class_body\n", formatted); + } + + @Test + public void testEditorConfigCanDisable(@TempDir Path path) throws IOException { + KtLintCompat0Dot48Dot0Adapter ktLintCompat0Dot48Dot0Adapter = new KtLintCompat0Dot48Dot0Adapter(); + loadAndWriteText(path, "fails_no_semicolons.kt"); + final Path filePath = Paths.get(path.toString(), "fails_no_semicolons.kt"); + + Map editorConfigOverrideMap = new HashMap<>(); + editorConfigOverrideMap.put("indent_style", "tab"); + editorConfigOverrideMap.put("ktlint_standard_no-semi", "disabled"); + // ktlint_filename is an invalid rule in ktlint 0.48.0 + editorConfigOverrideMap.put("ktlint_filename", "disabled"); + + String formatted = ktLintCompat0Dot48Dot0Adapter.format(filePath, null, editorConfigOverrideMap); + assertEquals("class fails_no_semicolons {\n\tval i = 0;\n}\n", formatted); + } + + private static String loadAndWriteText(Path path, String name) throws IOException { + try (InputStream is = KtLintCompat0Dot48Dot0AdapterTest.class.getResourceAsStream("/" + name)) { + Files.copy(is, path.resolve(name)); + } + return new String(Files.readAllBytes(path.resolve(name)), StandardCharsets.UTF_8); + } + +} diff --git a/lib/src/testCompatKtLint0Dot48Dot0/resources/empty_class_body.kt b/lib/src/testCompatKtLint0Dot48Dot0/resources/empty_class_body.kt new file mode 100644 index 0000000000..b84774d572 --- /dev/null +++ b/lib/src/testCompatKtLint0Dot48Dot0/resources/empty_class_body.kt @@ -0,0 +1,3 @@ +class empty_class_body { + +} diff --git a/lib/src/testCompatKtLint0Dot48Dot0/resources/fails_no_semicolons.kt b/lib/src/testCompatKtLint0Dot48Dot0/resources/fails_no_semicolons.kt new file mode 100644 index 0000000000..20ab460913 --- /dev/null +++ b/lib/src/testCompatKtLint0Dot48Dot0/resources/fails_no_semicolons.kt @@ -0,0 +1,3 @@ +class fails_no_semicolons { + val i = 0; +} diff --git a/lib/src/testCompatKtLint0Dot49Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot49Dot0AdapterTest.java b/lib/src/testCompatKtLint0Dot49Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot49Dot0AdapterTest.java new file mode 100644 index 0000000000..b34a83ebc3 --- /dev/null +++ b/lib/src/testCompatKtLint0Dot49Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot49Dot0AdapterTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.glue.ktlint.compat; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class KtLintCompat0Dot49Dot0AdapterTest { + @Test + public void testDefaults(@TempDir Path path) throws IOException { + KtLintCompat0Dot49Dot0Adapter ktLintCompat0Dot49Dot0Adapter = new KtLintCompat0Dot49Dot0Adapter(); + loadAndWriteText(path, "EmptyClassBody.kt"); + final Path filePath = Paths.get(path.toString(), "EmptyClassBody.kt"); + + Map editorConfigOverrideMap = new HashMap<>(); + + String formatted = ktLintCompat0Dot49Dot0Adapter.format(filePath, null, editorConfigOverrideMap); + assertEquals("class EmptyClassBody\n", formatted); + } + + @Test + public void testEditorConfigCanDisable(@TempDir Path path) throws IOException { + KtLintCompat0Dot49Dot0Adapter ktLintCompat0Dot49Dot0Adapter = new KtLintCompat0Dot49Dot0Adapter(); + loadAndWriteText(path, "FailsNoSemicolons.kt"); + final Path filePath = Paths.get(path.toString(), "FailsNoSemicolons.kt"); + + Map editorConfigOverrideMap = new HashMap<>(); + editorConfigOverrideMap.put("indent_style", "tab"); + editorConfigOverrideMap.put("ktlint_standard_no-semi", "disabled"); + + String formatted = ktLintCompat0Dot49Dot0Adapter.format(filePath, null, editorConfigOverrideMap); + assertEquals("class FailsNoSemicolons {\n\tval i = 0;\n}\n", formatted); + } + + private static String loadAndWriteText(Path path, String name) throws IOException { + try (InputStream is = KtLintCompat0Dot49Dot0AdapterTest.class.getResourceAsStream("/" + name)) { + Files.copy(is, path.resolve(name)); + } + return new String(Files.readAllBytes(path.resolve(name)), StandardCharsets.UTF_8); + } + +} diff --git a/lib/src/testCompatKtLint0Dot49Dot0/resources/EmptyClassBody.kt b/lib/src/testCompatKtLint0Dot49Dot0/resources/EmptyClassBody.kt new file mode 100644 index 0000000000..7da53fb78d --- /dev/null +++ b/lib/src/testCompatKtLint0Dot49Dot0/resources/EmptyClassBody.kt @@ -0,0 +1,3 @@ +class EmptyClassBody { + +} diff --git a/lib/src/testCompatKtLint0Dot49Dot0/resources/FailsNoSemicolons.kt b/lib/src/testCompatKtLint0Dot49Dot0/resources/FailsNoSemicolons.kt new file mode 100644 index 0000000000..4cf05ceacf --- /dev/null +++ b/lib/src/testCompatKtLint0Dot49Dot0/resources/FailsNoSemicolons.kt @@ -0,0 +1,3 @@ +class FailsNoSemicolons { + val i = 0; +} diff --git a/lib/src/testCompatKtLint0Dot50Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot50Dot0AdapterTest.java b/lib/src/testCompatKtLint0Dot50Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot50Dot0AdapterTest.java new file mode 100644 index 0000000000..0bb501a06e --- /dev/null +++ b/lib/src/testCompatKtLint0Dot50Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot50Dot0AdapterTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.glue.ktlint.compat; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class KtLintCompat0Dot50Dot0AdapterTest { + @Test + public void testDefaults(@TempDir Path path) throws IOException { + KtLintCompat0Dot50Dot0Adapter KtLintCompat0Dot50Dot0Adapter = new KtLintCompat0Dot50Dot0Adapter(); + loadAndWriteText(path, "EmptyClassBody.kt"); + final Path filePath = Paths.get(path.toString(), "EmptyClassBody.kt"); + + Map editorConfigOverrideMap = new HashMap<>(); + + String formatted = KtLintCompat0Dot50Dot0Adapter.format(filePath, null, editorConfigOverrideMap); + assertEquals("class EmptyClassBody\n", formatted); + } + + @Test + public void testEditorConfigCanDisable(@TempDir Path path) throws IOException { + KtLintCompat0Dot50Dot0Adapter KtLintCompat0Dot50Dot0Adapter = new KtLintCompat0Dot50Dot0Adapter(); + loadAndWriteText(path, "FailsNoSemicolons.kt"); + final Path filePath = Paths.get(path.toString(), "FailsNoSemicolons.kt"); + + Map editorConfigOverrideMap = new HashMap<>(); + editorConfigOverrideMap.put("indent_style", "tab"); + editorConfigOverrideMap.put("ktlint_standard_no-semi", "disabled"); + + String formatted = KtLintCompat0Dot50Dot0Adapter.format(filePath, null, editorConfigOverrideMap); + assertEquals("class FailsNoSemicolons {\n\tval i = 0;\n}\n", formatted); + } + + private static String loadAndWriteText(Path path, String name) throws IOException { + try (InputStream is = KtLintCompat0Dot50Dot0AdapterTest.class.getResourceAsStream("/" + name)) { + Files.copy(is, path.resolve(name)); + } + return new String(Files.readAllBytes(path.resolve(name)), StandardCharsets.UTF_8); + } + +} diff --git a/lib/src/testCompatKtLint0Dot50Dot0/resources/EmptyClassBody.kt b/lib/src/testCompatKtLint0Dot50Dot0/resources/EmptyClassBody.kt new file mode 100644 index 0000000000..7da53fb78d --- /dev/null +++ b/lib/src/testCompatKtLint0Dot50Dot0/resources/EmptyClassBody.kt @@ -0,0 +1,3 @@ +class EmptyClassBody { + +} diff --git a/lib/src/testCompatKtLint0Dot50Dot0/resources/FailsNoSemicolons.kt b/lib/src/testCompatKtLint0Dot50Dot0/resources/FailsNoSemicolons.kt new file mode 100644 index 0000000000..4cf05ceacf --- /dev/null +++ b/lib/src/testCompatKtLint0Dot50Dot0/resources/FailsNoSemicolons.kt @@ -0,0 +1,3 @@ +class FailsNoSemicolons { + val i = 0; +} diff --git a/lib/src/testCompatKtLint1Dot0Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat1Dot0Dot0AdapterTest.java b/lib/src/testCompatKtLint1Dot0Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat1Dot0Dot0AdapterTest.java new file mode 100644 index 0000000000..0f5f9cf16c --- /dev/null +++ b/lib/src/testCompatKtLint1Dot0Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat1Dot0Dot0AdapterTest.java @@ -0,0 +1,66 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.glue.ktlint.compat; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +public class KtLintCompat1Dot0Dot0AdapterTest { + @Test + public void testDefaults(@TempDir Path path) throws IOException { + KtLintCompat1Dot0Dot0Adapter KtLintCompat1Dot0Dot0Adapter = new KtLintCompat1Dot0Dot0Adapter(); + loadAndWriteText(path, "EmptyClassBody.kt"); + final Path filePath = Paths.get(path.toString(), "EmptyClassBody.kt"); + + Map editorConfigOverrideMap = new HashMap<>(); + + String formatted = KtLintCompat1Dot0Dot0Adapter.format(filePath, null, editorConfigOverrideMap); + assertEquals("class EmptyClassBody\n", formatted); + } + + @Test + public void testEditorConfigCanDisable(@TempDir Path path) throws IOException { + KtLintCompat1Dot0Dot0Adapter KtLintCompat1Dot0Dot0Adapter = new KtLintCompat1Dot0Dot0Adapter(); + loadAndWriteText(path, "FailsNoSemicolons.kt"); + final Path filePath = Paths.get(path.toString(), "FailsNoSemicolons.kt"); + + Map editorConfigOverrideMap = new HashMap<>(); + editorConfigOverrideMap.put("indent_style", "tab"); + editorConfigOverrideMap.put("ktlint_standard_no-semi", "disabled"); + + String formatted = KtLintCompat1Dot0Dot0Adapter.format(filePath, null, editorConfigOverrideMap); + assertEquals("class FailsNoSemicolons {\n\tval i = 0;\n}\n", formatted); + } + + private static String loadAndWriteText(Path path, String name) throws IOException { + try (InputStream is = KtLintCompat1Dot0Dot0AdapterTest.class.getResourceAsStream("/" + name)) { + Files.copy(is, path.resolve(name)); + } + return new String(Files.readAllBytes(path.resolve(name)), StandardCharsets.UTF_8); + } + +} diff --git a/lib/src/testCompatKtLint1Dot0Dot0/resources/EmptyClassBody.kt b/lib/src/testCompatKtLint1Dot0Dot0/resources/EmptyClassBody.kt new file mode 100644 index 0000000000..7da53fb78d --- /dev/null +++ b/lib/src/testCompatKtLint1Dot0Dot0/resources/EmptyClassBody.kt @@ -0,0 +1,3 @@ +class EmptyClassBody { + +} diff --git a/lib/src/testCompatKtLint1Dot0Dot0/resources/FailsNoSemicolons.kt b/lib/src/testCompatKtLint1Dot0Dot0/resources/FailsNoSemicolons.kt new file mode 100644 index 0000000000..4cf05ceacf --- /dev/null +++ b/lib/src/testCompatKtLint1Dot0Dot0/resources/FailsNoSemicolons.kt @@ -0,0 +1,3 @@ +class FailsNoSemicolons { + val i = 0; +} diff --git a/lib/src/zjsonPatch/java/com/diffplug/spotless/glue/json/JsonPatchFormatterFunc.java b/lib/src/zjsonPatch/java/com/diffplug/spotless/glue/json/JsonPatchFormatterFunc.java new file mode 100644 index 0000000000..dd2f053791 --- /dev/null +++ b/lib/src/zjsonPatch/java/com/diffplug/spotless/glue/json/JsonPatchFormatterFunc.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.glue.json; + +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.flipkart.zjsonpatch.JsonPatch; + +import com.diffplug.spotless.FormatterFunc; + +public class JsonPatchFormatterFunc implements FormatterFunc { + private final ObjectMapper objectMapper; + private final List> patch; + private final String patchString; + + public JsonPatchFormatterFunc(String patchString) { + this.objectMapper = new ObjectMapper(); + this.patch = null; + this.patchString = patchString; + } + + public JsonPatchFormatterFunc(List> patch) { + this.objectMapper = new ObjectMapper(); + this.patch = patch; + this.patchString = null; + } + + @Override + public String apply(String input) throws Exception { + var patchNode = this.patch == null + ? objectMapper.readTree(patchString) + : objectMapper.valueToTree(patch); + + var inputNode = objectMapper.readTree(input); + + var patchedNode = JsonPatch.apply(patchNode, inputNode); + + return objectMapper.writeValueAsString(patchedNode); + } +} diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index 78a9b46693..ac5be538c7 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -3,6 +3,252 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.27.0`). ## [Unreleased] +* Support for `gofmt` ([#2001](https://github.com/diffplug/spotless/pull/2001)) + +### Added +* Maven / Gradle - Support for formatting Java Docs for the Palantir formatter ([#2009](https://github.com/diffplug/spotless/pull/2009)) + +## [6.24.0] - 2024-01-15 +### Added +* Support for shell formatting via [shfmt](https://github.com/mvdan/sh). ([#1994](https://github.com/diffplug/spotless/pull/1994)) +### Fixed +* Fix empty files with biome >= 1.5.0 when formatting files that are in the ignore list of the biome configuration file. ([#1989](https://github.com/diffplug/spotless/pull/1989) fixes [#1987](https://github.com/diffplug/spotless/issues/1987)) +* Fix a regression in BufStep where the same arguments were being provided to every `buf` invocation. ([#1976](https://github.com/diffplug/spotless/issues/1976)) +### Changes +* Use palantir-java-format 2.39.0 on Java 21. ([#1948](https://github.com/diffplug/spotless/pull/1948)) +* Bump default `ktlint` version to latest `1.0.1` -> `1.1.1`. ([#1973](https://github.com/diffplug/spotless/pull/1973)) +* Bump default `googleJavaFormat` version to latest `1.18.1` -> `1.19.2`. ([#1971](https://github.com/diffplug/spotless/pull/1971)) +* Bump default `diktat` version to latest `1.2.5` -> `2.0.0`. ([#1972](https://github.com/diffplug/spotless/pull/1972)) + +## [6.23.3] - 2023-12-04 +**BREAKING CHANGE** `6.23.0` made breaking changes to the ABI of the `KotlinExtension` and `GroovyExtension`. Those are reflected retroactively now. + - Previously, we had done semver on the Gradle plugin based only on buildscript compatibility. + - From now on, we will consider ABI for the benefit of convention-based plugins. +### Fixed +* Eclipse-based steps which contained any jars with a `+` in their path were broken, now fixed. ([#1860](https://github.com/diffplug/spotless/issues/1860#issuecomment-1826113332)) +* Make `KtfmtConfig.ConfigurableStyle#configure` public. ([#1926](https://github.com/diffplug/spotless/pull/1926)) +### Changes +* Bump default `palantir-java-format` version to latest `2.28.0` -> `2.38.0` on Java 21. ([#1920](https://github.com/diffplug/spotless/pull/1920)) +* Bump default `googleJavaFormat` version to latest `1.17.0` -> `1.18.1`. ([#1920](https://github.com/diffplug/spotless/pull/1920)) +* Bump default `ktfmt` version to latest `0.44` -> `0.46`. ([#1927](https://github.com/diffplug/spotless/pull/1927)) +* Bump default `eclipse` version to latest `4.27` -> `4.29`. ([#1939](https://github.com/diffplug/spotless/pull/1939)) +* Bump default `greclipse` version to latest `4.28` -> `4.29`. ([#1939](https://github.com/diffplug/spotless/pull/1939)) +* Bump default `cdt` version to latest `11.1` -> `11.3`. ([#1939](https://github.com/diffplug/spotless/pull/1939)) + +## [6.23.2] - 2023-12-01 +### Fixed +* Fix a stuck MavenCentral sync from `6.23.1`. + +## [6.23.1] - 2023-11-29 +### Fixed +* Make `BaseGroovyExtension` and `BaseKotlinExtension` public. ([#1912](https://github.com/diffplug/spotless/pull/1912)) + +## [6.23.0] - 2023-11-27 +### Added +* Support custom rule sets for Ktlint. ([#1896](https://github.com/diffplug/spotless/pull/1896)) +### Fixed +* Fix `GoogleJavaFormatConfig.reorderImports` not working. ([#1872](https://github.com/diffplug/spotless/issues/1872)) +* Fix Eclipse JDT on some settings files. ([#1864](https://github.com/diffplug/spotless/pull/1864) fixes [#1638](https://github.com/diffplug/spotless/issues/1638)) +* Check if EditorConfig file exist for Ktlint in KotlinGradleExtension. ([#1889](https://github.com/diffplug/spotless/pull/1889)) +### Changes +* Bump default `ktlint` version to latest `1.0.0` -> `1.0.1`. ([#1855](https://github.com/diffplug/spotless/pull/1855)) +* Add a Step to remove semicolons from Groovy files. ([#1881](https://github.com/diffplug/spotless/pull/1881)) +* **POSSIBLY BREAKING** `userData` method has been removed from Ktlint step in ([#1891](https://github.com/diffplug/spotless/pull/1891)), you may use `editorConfigOverride` instead. +* **POSSIBLY BREAKING** Reuse configs for `KotlinExtension` and `KotlinGradleExtension` in ([#1890](https://github.com/diffplug/spotless/pull/1890)), this may break your integrations in Gradle Kotlin DSL, wait for Spotless 6.23.1 to fix this. +* **POSSIBLY BREAKING** Reuse configs for `GroovyExtension` and `GroovyGradleExtension` in ([#1892](https://github.com/diffplug/spotless/pull/1892)), this may break your integrations in Gradle Kotlin DSL, wait for Spotless 6.23.1 to fix this. + +## [6.22.0] - 2023-09-28 +### Added +* Added support for `google-java-format`'s `skip-javadoc-formatting` option ([#1793](https://github.com/diffplug/spotless/pull/1793)) +* Add support for `flexmark` in gradle. Previously only Maven was supported. ([#1801](https://github.com/diffplug/spotless/pull/1801)) +* Add support for biome. The Rome project [was renamed to Biome](https://biomejs.dev/blog/annoucing-biome/). + The configuration is still the same, but you should switch to the new `biome(...)` function and adjust + the version accordingly. ([#1804](https://github.com/diffplug/spotless/issues/1804)). +### Fixed +* Fixed support for plugins when using Prettier version `3.0.0` and newer. ([#1802](https://github.com/diffplug/spotless/pull/1802)) +### Changes +* Bump default `ktlint` version to latest `0.50.0` -> `1.0.0`. ([#1808](https://github.com/diffplug/spotless/pull/1808)) +* **POSSIBLY BREAKING** the default line endings are now `GIT_ATTRIBUTES_FAST_ALLSAME` instead of `GIT_ATTRIBUTES`. ([#1838](https://github.com/diffplug/spotless/pull/1838)) + * If all the files within a format have the same line endings, then there is no change in behavior. + * Fixes large performance regression. ([#1527](https://github.com/diffplug/spotless/issues/1527)) + +## [6.21.0] - 2023-08-29 +### Added +* Add a `jsonPatch` step to `json` formatter configurations. This allows patching of JSON documents using [JSON Patches](https://jsonpatch.com). ([#1753](https://github.com/diffplug/spotless/pull/1753)) +* Support GJF own import order. ([#1780](https://github.com/diffplug/spotless/pull/1780)) +### Fixed +* Add support for `prettier` version `3.0.0` and newer. ([#1760](https://github.com/diffplug/spotless/pull/1760), [#1751](https://github.com/diffplug/spotless/issues/1751)) +* Fix npm install calls when npm cache is not up-to-date. ([#1760](https://github.com/diffplug/spotless/pull/1760), [#1750](https://github.com/diffplug/spotless/issues/1750)) +* Fix configuration cache failure when using LineEnding.GIT_ATTRIBUTES ([#1644](https://github.com/diffplug/spotless/issues/1644)) +* Fix configuration cache failure when formatting proto files with Buf. ([#1779](https://github.com/diffplug/spotless/pull/1779)) +* Check if EditorConfig file exist for Ktlint. ([#1788](https://github.com/diffplug/spotless/pull/1788)) +### Changes +* Bump default `eslint` version to latest `8.31.0` -> `8.45.0` ([#1761](https://github.com/diffplug/spotless/pull/1761)) +* Bump default `prettier` version to latest (v2) `2.8.1` -> `2.8.8`. ([#1760](https://github.com/diffplug/spotless/pull/1760)) +* Bump default `greclipse` version to latest `4.27` -> `4.28`. ([#1775](https://github.com/diffplug/spotless/pull/1775)) + +## [6.20.0] - 2023-07-17 +### Added +* Add target option `targetExcludeIfContentContains` and `targetExcludeIfContentContainsRegex` to exclude files based on their text content. ([#1749](https://github.com/diffplug/spotless/pull/1749)) +* Add support for Protobuf formatting based on [Buf](https://buf.build/) ([#1208](https://github.com/diffplug/spotless/pull/1208)). +* Add an overload for `FormatExtension.addStep` which provides access to the `FormatExtension`'s `Provisioner`, enabling custom steps to make use of third-party dependencies. +### Fixed +* Correctly support the syntax + ``` + spotless { + yaml { + jackson().yamlFeature("MINIMIZE_QUOTES", true) + } + } + ``` +### Changes +* Bump default `cleanthat` version to latest `2.13` -> `2.17`. ([#1734](https://github.com/diffplug/spotless/pull/1734)) +* Bump default `ktlint` version to latest `0.49.1` -> `0.50.0`. ([#1741](https://github.com/diffplug/spotless/issues/1741)) + * Dropped support for `ktlint 0.47.x` following our policy of supporting two breaking changes at a time. + * Dropped support for deprecated `useExperimental` parameter in favor of the `ktlint_experimental` property. + +## [6.19.0] - 2023-05-24 +### Added +* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. ([#1663](https://github.com/diffplug/spotless/pull/1663)) +* Add semantics-aware Java import ordering (i.e. sort by package, then class, then member). ([#522](https://github.com/diffplug/spotless/issues/522)) +### Fixed +* Added `@DisableCachingByDefault` to `RegisterDependenciesTask`. ([#1666](https://github.com/diffplug/spotless/pull/1666)) +* Fixed a regression which changed the import sorting order in `googleJavaFormat` introduced in `6.18.0`. ([#1680](https://github.com/diffplug/spotless/pull/1680)) +* Equo-based formatters now work on platforms unsupported by Eclipse such as PowerPC (fixes [durian-swt#20](https://github.com/diffplug/durian-swt/issues/20)) +* When P2 download fails, indicate the responsible formatter. ([#1698](https://github.com/diffplug/spotless/issues/1698)) +### Changes +* Equo-based formatters now download metadata to `~/.m2/repository/dev/equo/p2-data` rather than `~/.equo`, and for CI machines without a home directory the p2 data goes to `$GRADLE_USER_HOME/caches/p2-data`. ([#1714](https://github.com/diffplug/spotless/pull/1714)) +* Bump default `googleJavaFormat` version to latest `1.16.0` -> `1.17.0`. ([#1710](https://github.com/diffplug/spotless/pull/1710)) +* Bump default `ktfmt` version to latest `0.43` -> `0.44`. ([#1691](https://github.com/diffplug/spotless/pull/1691)) +* Bump default `ktlint` version to latest `0.48.2` -> `0.49.1`. ([#1696](https://github.com/diffplug/spotless/issues/1696)) + * Dropped support for `ktlint 0.46.x` following our policy of supporting two breaking changes at a time. +* Bump default `sortpom` version to latest `3.0.0` -> `3.2.1`. ([#1675](https://github.com/diffplug/spotless/pull/1675)) + +## [6.18.0] - 2023-04-06 +### Added +* `removeUnusedImport` can be configured to rely on `cleanthat-javaparser-unnecessaryimport`. Default remains `google-java-format`. ([#1589](https://github.com/diffplug/spotless/pull/1589)) +* Added formatter for Gherkin feature files ([#1649](https://github.com/diffplug/spotless/issues/1649)). +* Support configuration of mirrors for P2 repositories ([#1629](https://github.com/diffplug/spotless/issues/1629)): + ``` + spotless { + java { + eclipse().withP2Mirrors(['https://download.eclipse.org/', 'https://some.internal.mirror/eclipse']) + } + } + ``` + Mirrors are selected by prefix match, for example `https://download.eclipse.org/eclipse/updates/4.26/` will be redirected to `https://some.internal.mirror/eclipse/eclipse/updates/4.26/`. + The same configuration exists for `greclipse` and `eclipseCdt`. +* The `style` option in Palantir Java Format ([#1654](https://github.com/diffplug/spotless/pull/1654)). +### Fixed +* Stop using deprecated conventions when used in Gradle >= `7.1`. ([#1618](https://github.com/diffplug/spotless/pull/1618)) +### Changes +* **POTENTIALLY BREAKING** Drop support for `googleJavaFormat` versions < `1.8`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) +* Bump default `cleanthat` version to latest `2.6` -> `2.13`. ([#1589](https://github.com/diffplug/spotless/pull/1589) and [#1661](https://github.com/diffplug/spotless/pull/1661)) +* Bump default `diktat` version `1.2.4.2` -> `1.2.5`. ([#1631](https://github.com/diffplug/spotless/pull/1631)) +* Bump default `flexmark` version `0.62.2` -> `0.64.0`. ([#1302](https://github.com/diffplug/spotless/pull/1302)) +* Bump default `googleJavaFormat` version `1.15.0` -> `1.16.0`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) +* Bump default `scalafmt` version `3.7.1` -> `3.7.3`. ([#1584](https://github.com/diffplug/spotless/pull/1584)) +* Bump default Eclipse formatters for the 2023-03 release. ([#1662](https://github.com/diffplug/spotless/pull/1662)) + * JDT and GrEclipse `4.26` -> `4.27` + * Improve GrEclipse error reporting. ([#1660](https://github.com/diffplug/spotless/pull/1660)) + * CDT `11.0` -> `11.1` + +## [6.17.0] - 2023-03-13 +### Added +* You can now put the filename into a license header template with `$FILE`. ([#1605](https://github.com/diffplug/spotless/pull/1605) fixes [#1147](https://github.com/diffplug/spotless/issues/1147)) +* `licenseHeader` default pattern for Java files is updated to `(package|import|public|class|module) `. ([#1614](https://github.com/diffplug/spotless/pull/1614)) +### Changes +* All Eclipse formatters are now based on [Equo Solstice OSGi and p2 shim](https://github.com/equodev/equo-ide/tree/main/solstice). ([#1524](https://github.com/diffplug/spotless/pull/1524)) + * Eclipse JDT bumped default to `4.26` from `4.21`, oldest supported is `4.9`. + * We now recommend dropping the last `.0`, e.g. `4.26` instead of `4.26.0`, you'll get warnings to help you switch. + * Eclipse Groovy bumped default to `4.26` from `4.21`, oldest supported is `4.18`. + * Eclipse CDT bumped default to `11.0` from `4.21`, oldest supported is `10.6`. + * Eclipse WTP is still WIP at [#1622](https://github.com/diffplug/spotless/pull/1622). + +## [6.16.0] - 2023-02-27 +### Added +* `cleanthat` now has `includeDraft` option, to include draft mutators from composite mutators. ([#1574](https://github.com/diffplug/spotless/pull/1574)) +* `npm`-based formatters (`prettier`, `tsfmt` and `eslint`) now support caching of `node_modules` directory. + To enable it, provide `npmInstallCache()` option. ([#1590](https://github.com/diffplug/spotless/pull/1590)) +### Fixed +* `json { jackson()` can now handle `Array` as a root element. ([#1585](https://github.com/diffplug/spotless/pull/1585)) +* Reduce logging-noise created by `npm`-based formatters ([#1590](https://github.com/diffplug/spotless/pull/1590) fixes [#1582](https://github.com/diffplug/spotless/issues/1582)) +### Changes +* Bump default `cleanthat` version to latest `2.1` -> `2.6`. ([#1569](https://github.com/diffplug/spotless/pull/1569) and [#1574](https://github.com/diffplug/spotless/pull/1574)) + +## [6.15.0] - 2023-02-10 +### Added +* CleanThat Java Refactorer. ([#1560](https://github.com/diffplug/spotless/pull/1560)) +### Fixed +* Allow multiple instances of the same npm-based formatter to be used simultaneously. E.g. use prettier for typescript + *and* Java (using the community prettier-plugin-java) without messing up their respective `node_module` dependencies. ([#1565](https://github.com/diffplug/spotless/pull/1565)) +* `ktfmt` default style uses correct continuation indent. ([#1562](https://github.com/diffplug/spotless/pull/1562)) +### Changes +* Bump default `ktfmt` version to latest `0.42` -> `0.43` ([#1561](https://github.com/diffplug/spotless/pull/1561)) +* Bump default `jackson` version to latest `2.14.1` -> `2.14.2` ([#1536](https://github.com/diffplug/spotless/pull/1536)) + +## [6.14.1] - 2023-02-05 +### Fixed +* `freshmark` fixed on java 15+ ([#1304](https://github.com/diffplug/spotless/pull/1304) fixes [#803](https://github.com/diffplug/spotless/issues/803)) +* **POTENTIALLY BREAKING** `sortByKeys` for JSON formatting now takes into account objects inside arrays ([#1546](https://github.com/diffplug/spotless/pull/1546)) + +## [6.14.0] - 2023-01-26 +### Added +* Support `jackson()` for YAML and JSON files ([#1492](https://github.com/diffplug/spotless/pull/1492)) +* Prettier will now suggest to install plugins if a parser cannot be inferred from the file extension ([#1511](https://github.com/diffplug/spotless/pull/1511)) +* Allow to specify node executable for node-based formatters using `nodeExecutable` parameter ([#1500](https://github.com/diffplug/spotless/pull/1500)) +### Fixed +* **POTENTIALLY BREAKING** Generate the correct qualifiedRuleId for Ktlint 0.48.x [#1495](https://github.com/diffplug/spotless/pull/1495) +* The default list of type annotations used by `formatAnnotations` has had 8 more annotations from the Checker Framework added [#1494](https://github.com/diffplug/spotless/pull/1494) +### Changes +* **POTENTIALLY BREAKING** Bump minimum JRE from 8 to 11 ([#1514](https://github.com/diffplug/spotless/pull/1514) part 1 of [#1337](https://github.com/diffplug/spotless/issues/1337)) + * You can bump your build JRE without bumping your requirements ([docs](https://docs.gradle.org/current/userguide/building_java_projects.html#sec:java_cross_compilation)). +* Prettier will now suggest to install plugins if a parser cannot be inferred from the file extension ([#1511](https://github.com/diffplug/spotless/pull/1511)) +* **POTENTIALLY BREAKING** Removed support for KtLint 0.3x and 0.45.2 ([#1475](https://github.com/diffplug/spotless/pull/1475)) + * `KtLint` does not maintain a stable API - before this PR, we supported every breaking change in the API since 2019. + * From now on, we will support no more than 2 breaking changes at a time. +* `npm`-based formatters `ESLint`, `prettier` and `tsfmt` delay their `npm install` call until the formatters are first + used. For Gradle this effectively moves the `npm install` call out of the configuration phase and as such enables + better integration with `gradle-node-plugin`. ([#1522](https://github.com/diffplug/spotless/pull/1522)) +* Bump default `ktlint` version to latest `0.48.1` -> `0.48.2` ([#1529](https://github.com/diffplug/spotless/pull/1529)) +* Bump default `scalafmt` version to latest `3.6.1` -> `3.7.1` ([#1529](https://github.com/diffplug/spotless/pull/1529)) + +## [6.13.0] - 2023-01-14 +### Added +* **POTENTIALLY BREAKING** `ktlint` step now supports `.editorconfig` ([#1442](https://github.com/diffplug/spotless/pull/1442) implements [#142](https://github.com/diffplug/spotless/issues/142)) + * **POTENTIALLY BREAKING** `ktlint` step now modifies license headers. Make sure to put `licenseHeader` *after* `ktlint`. +* Added `skipLinesMatching` option to `licenseHeader` to support formats where license header cannot be immediately added to the top of the file (e.g. xml, sh). ([#1441](https://github.com/diffplug/spotless/pull/1441)) +* Added support for npm-based [ESLint](https://eslint.org/) formatter for javascript and typescript ([#1453](https://github.com/diffplug/spotless/pull/1453)) +* Better suggested messages when user's default is set by JVM limitation. ([#995](https://github.com/diffplug/spotless/pull/995)) +### Fixed +* Prevent tool configurations from being resolved outside project ([#1447](https://github.com/diffplug/spotless/pull/1447) fixes [#1215](https://github.com/diffplug/spotless/issues/1215)) +* Support `ktlint` 0.48+ new rule disabling syntax ([#1456](https://github.com/diffplug/spotless/pull/1456)) fixes ([#1444](https://github.com/diffplug/spotless/issues/1444)) +* Fix subgroups leading catch all matcher. +### Changes +* Bump default `ktlint` version to latest `0.47.1` -> `0.48.1` ([#1456](https://github.com/diffplug/spotless/pull/1456)) +* Bump default version for `prettier` from `2.0.5` to `2.8.1` ([#1453](https://github.com/diffplug/spotless/pull/1453)) + +## [6.12.1] - 2023-01-02 +### Fixed +* Improve memory usage when using git ratchet ([#1426](https://github.com/diffplug/spotless/pull/1426)) +* Support `ktlint` 0.48+ ([#1432](https://github.com/diffplug/spotless/pull/1432)) fixes ([#1430](https://github.com/diffplug/spotless/issues/1430)) +### Changes +* Bump default `ktlint` version to latest `0.47.1` -> `0.48.0` ([#1432](https://github.com/diffplug/spotless/pull/1432)) +* Bump default `ktfmt` version to latest `0.41` -> `0.42` ([#1421](https://github.com/diffplug/spotless/pull/1421)) + +## [6.12.0] - 2022-11-24 +### Added +* `importOrder` now support groups of imports without blank lines ([#1401](https://github.com/diffplug/spotless/pull/1401)) +### Fixed +* Don't treat `@Value` as a type annotation [#1367](https://github.com/diffplug/spotless/pull/1367) +* Support `ktlint_disabled_rules` in `ktlint` 0.47.x [#1378](https://github.com/diffplug/spotless/pull/1378) +### Changes +* Bump default `ktfmt` version to latest `0.40` -> `0.41` ([#1340](https://github.com/diffplug/spotless/pull/1340)) +* Bump default `scalafmt` version to latest `3.5.9` -> `3.6.1` ([#1373](https://github.com/diffplug/spotless/pull/1373)) +* Bump default `diktat` version to latest `1.2.3` -> `1.2.4.2` ([#1393](https://github.com/diffplug/spotless/pull/1393)) +* Bump default `palantir-java-format` version to latest `2.10` -> `2.28` ([#1393](https://github.com/diffplug/spotless/pull/1393)) + +## [6.11.0] - 2022-09-14 ### Added * `formatAnnotations()` step to correct formatting of Java type annotations. It puts type annotations on the same line as the type that they qualify. Run it after a Java formatting step, such as `googleJavaFormat()`. ([#1275](https://github.com/diffplug/spotless/pull/1275)) ### Changes @@ -12,7 +258,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [6.10.0] - 2022-08-23 ### Added -* `scalafmt` integration now has a configuration option `majorScalaVersion` that allows you to configure the Scala version that gets resolved from the maven artifact ([#1283](https://github.com/diffplug/spotless/pull/1283)) +* `scalafmt` integration now has a configuration option `majorScalaVersion` that allows you to configure the Scala version that gets resolved from the Maven artifact ([#1283](https://github.com/diffplug/spotless/pull/1283)) ### Changes * Add the `ktlint` rule in error messages when `ktlint` fails to apply a fix ([#1279](https://github.com/diffplug/spotless/pull/1279)) * Bump default `scalafmt` to latest `3.0.8` -> `3.5.9` (removed support for pre-`3.0.0`) ([#1283](https://github.com/diffplug/spotless/pull/1283)) @@ -172,7 +418,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [6.0.4] - 2021-12-07 ### Fixed -* Fix gradle composite builds ([#860](https://github.com/diffplug/spotless/issues/860)). +* Fix Gradle composite builds ([#860](https://github.com/diffplug/spotless/issues/860)). ## [6.0.3] - 2021-12-06 ### Fixed @@ -297,7 +543,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ### Changed * Update ktfmt from 0.21 to 0.24 ### Fixed -* The `` field in the maven POM is now set correctly ([#798](https://github.com/diffplug/spotless/issues/798)) +* The `` field in the Maven POM is now set correctly ([#798](https://github.com/diffplug/spotless/issues/798)) * Node is re-installed if some other build step removed it ([#863](https://github.com/diffplug/spotless/issues/863)) ## [5.12.4] - 2021-04-21 @@ -488,7 +734,7 @@ println "isEager $isEager" * LineEndings.GIT_ATTRIBUTES is now a bit more efficient, and paves the way for remote build cache support in Gradle. ([#621](https://github.com/diffplug/spotless/pull/621)) * `ratchetFrom` now ratchets from the merge base of `HEAD` and the specified branch. This fixes the surprising behavior when a remote branch advanced ([#631](https://github.com/diffplug/spotless/pull/631) fixes [#627](https://github.com/diffplug/spotless/issues/627)). ### Deprecated -* The default targets for `C/C++`, `freshmark`, `sql`, and `typescript` now generate a warning, asking the user to specify a target manually. There is no well-established convention for these languages in the gradle ecosystem, and the performance of the default target is far worse than a user-provided one. If you dislike this change, please complain in [#634](https://github.com/diffplug/spotless/pull/634). +* The default targets for `C/C++`, `freshmark`, `sql`, and `typescript` now generate a warning, asking the user to specify a target manually. There is no well-established convention for these languages in the Gradle ecosystem, and the performance of the default target is far worse than a user-provided one. If you dislike this change, please complain in [#634](https://github.com/diffplug/spotless/pull/634). * `customLazy` and `customLazyGroovy` now generate a warning, asking the user to migrate to `custom`. There is no longer a performance advantage to `customLazy` in the new modern plugin. See [#635](https://github.com/diffplug/spotless/pull/635/files) for example migrations. * inside the `cpp { }` block, the `eclipse` step now generates a warning, asking you to switch to `eclipseCdt`. It is the same underlying step, but the new name clears up any confusion with the more common Java `eclipse`. [#636](https://github.com/diffplug/spotless/pull/635/files) @@ -566,7 +812,7 @@ spotless { **TLDR: This version improves performance and adds support for the local Gradle Build Cache. You will not need to make any changes in your buildscript.** It is a breaking change only for a few users who have built *other* plugins on top of this one. ### Added -* Support for the gradle build cache. ([#576](https://github.com/diffplug/spotless/pull/576)) +* Support for the Gradle build cache. ([#576](https://github.com/diffplug/spotless/pull/576)) * The local cache will work great, but the remote cache will always miss until [#566](https://github.com/diffplug/spotless/issues/566) is resolved. ### Removed * **BREAKING** it used to be possible for any project to format files in any other project. For example, `:a` could format files in `:b`. It is now only possible to format files within the project directory. It is okay (but not advised) to format files in subprojects, since they are within the project directory. @@ -574,7 +820,7 @@ spotless { * Previously, the `check` and `apply` tasks were just marker tasks, and they called `setCheck` and `setApply` on the "worker" task. Now `check` and `apply` are real tasks in their own right, so the marker-task kludge is no longer necessary. ### Changed * (Power users only) **BREAKING** `SpotlessTask FormatExtension::createIndependentTask` has been removed, and replaced with `SpotlessApply::createIndependentApplyTask`. ([#576](https://github.com/diffplug/spotless/pull/576)) -* Improve suggested gradle invocation for running `spotlessApply`. ([#578](https://github.com/diffplug/spotless/pull/578)) +* Improve suggested Gradle invocation for running `spotlessApply`. ([#578](https://github.com/diffplug/spotless/pull/578)) ## [3.30.0] - 2020-05-11 ### Added @@ -665,11 +911,11 @@ spotless { * Updated default eclipse-jdt from 4.11.0 to 4.12.0 ([#423](https://github.com/diffplug/spotless/pull/423)). * Updated default eclipse-cdt from 4.11.0 to 4.12.0 ([#423](https://github.com/diffplug/spotless/pull/423)). * **KNOWN BUG - accidentally published CDT 9.7 rather than 9.8 - fixed in 3.26.0** -* Added new maven coordinates for scalafmt 2.0.0+, maintains backwards compatability ([#415](https://github.com/diffplug/spotless/issues/415)) +* Added new Maven coordinates for scalafmt 2.0.0+, maintains backwards compatability ([#415](https://github.com/diffplug/spotless/issues/415)) ## [3.23.1] - 2019-06-17 * Fixes incorrect M2 cache directory path handling of Eclipse based formatters ([#401](https://github.com/diffplug/spotless/issues/401)) -* Update jgit from `4.9.0.201710071750-r` to `5.3.2.201906051522-r` because gradle project is sometimes broken by `apache httpcomponents` in transitive dependency. ([#407](https://github.com/diffplug/spotless/pull/407)) +* Update jgit from `4.9.0.201710071750-r` to `5.3.2.201906051522-r` because Gradle project is sometimes broken by `apache httpcomponents` in transitive dependency. ([#407](https://github.com/diffplug/spotless/pull/407)) ## [3.23.0] - 2019-04-24 * Updated default ktlint from 0.21.0 to 0.32.0, and Maven coords to com.pinterest ([#394](https://github.com/diffplug/spotless/pull/394)) @@ -829,7 +1075,7 @@ spotless { ## [3.0.0] - 2017-01-09 * BREAKING CHANGE: `customReplace` and `customReplaceRegex` renamed to just `replace` and `replaceRegex`. -* BREAKING CHANGE: Plugin portal ID is still `com.diffplug.gradle.spotless`, but maven coordinate has changed to `com.diffplug.spotless:spotless-plugin-gradle`. +* BREAKING CHANGE: Plugin portal ID is still `com.diffplug.gradle.spotless`, but Maven coordinate has changed to `com.diffplug.spotless:spotless-plugin-gradle`. * HUGE SPEEDUP: Now supports incremental build / up-to-date-checking. + If you are using `custom` or `customLazy`, you might want to take a look at [this javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/3.27.0/com/diffplug/gradle/spotless/FormatExtension.html#bumpThisNumberIfACustomStepChanges-int-). * BREAKING CHANGE: `freshmark` no longer includes all project properties by default. All properties must now be added manually: diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md index a6d05daa9d..e80d143653 100644 --- a/plugin-gradle/README.md +++ b/plugin-gradle/README.md @@ -4,26 +4,20 @@ [![Gradle plugin](https://img.shields.io/badge/plugins.gradle.org-com.diffplug.spotless-blue.svg)](https://plugins.gradle.org/plugin/com.diffplug.spotless) -[![Maven central](https://img.shields.io/badge/mavencentral-yes-blue.svg)](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.diffplug.spotless%22%20AND%20a%3A%22spotless-plugin-gradle%22) -[![Javadoc](https://img.shields.io/badge/javadoc-yes-blue.svg)](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/index.html) -[![License Apache](https://img.shields.io/badge/license-apache-blue.svg)](https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)) -[![Changelog](https://img.shields.io/badge/changelog-6.10.0-blue.svg)](CHANGES.md) +[![Changelog](https://img.shields.io/badge/changelog-6.24.0-blue.svg)](CHANGES.md) +[![MavenCentral](https://img.shields.io/badge/mavencentral-here-blue.svg)](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.diffplug.spotless%22%20AND%20a%3A%22spotless-plugin-gradle%22) +[![Javadoc](https://img.shields.io/badge/javadoc-here-blue.svg)](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/index.html) -[![Circle CI](https://circleci.com/gh/diffplug/spotless/tree/main.svg?style=shield)](https://circleci.com/gh/diffplug/spotless/tree/main) -[![Live chat](https://img.shields.io/badge/gitter-chat-brightgreen.svg)](https://gitter.im/diffplug/spotless) [![VS Code plugin](https://img.shields.io/badge/IDE-VS_Code-blueviolet.svg)](https://marketplace.visualstudio.com/items?itemName=richardwillis.vscode-spotless-gradle) [![IntelliJ plugin](https://img.shields.io/badge/IDE-IntelliJ-blueviolet.svg)](https://plugins.jetbrains.com/plugin/18321-spotless-gradle) [![Add other IDE](https://img.shields.io/badge/IDE-add_yours-blueviolet.svg)](IDE_HOOK.md) @@ -33,7 +27,7 @@ output = [ output = prefixDelimiterReplace(input, 'https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/', '/', versionLast) --> -Spotless is a general-purpose formatting plugin used by [4,000 projects on GitHub (August 2020)](https://github.com/search?l=gradle&q=spotless&type=Code). It is completely à la carte, but also includes powerful "batteries-included" if you opt-in. +Spotless is a general-purpose formatting plugin used by [15,000 projects on GitHub (Jan 2023)](https://github.com/search?l=gradle&q=spotless&type=Code). It is completely à la carte, but also includes powerful "batteries-included" if you opt-in. To people who use your build, it looks like this ([IDE support also available](IDE_HOOK.md)): @@ -60,24 +54,31 @@ Spotless supports all of Gradle's built-in performance features (incremental bui - [**Quickstart**](#quickstart) - [Requirements](#requirements) - **Languages** - - [Java](#java) ([google-java-format](#google-java-format), [eclipse jdt](#eclipse-jdt), [clang-format](#clang-format), [prettier](#prettier), [palantir-java-format](#palantir-java-format), [formatAnnotations](#formatAnnotations)) + - [Java](#java) ([google-java-format](#google-java-format), [eclipse jdt](#eclipse-jdt), [clang-format](#clang-format), [prettier](#prettier), [palantir-java-format](#palantir-java-format), [formatAnnotations](#formatAnnotations), [cleanthat](#cleanthat)) - [Groovy](#groovy) ([eclipse groovy](#eclipse-groovy)) - [Kotlin](#kotlin) ([ktfmt](#ktfmt), [ktlint](#ktlint), [diktat](#diktat), [prettier](#prettier)) - [Scala](#scala) ([scalafmt](#scalafmt)) - [C/C++](#cc) ([clang-format](#clang-format), [eclipse cdt](#eclipse-cdt)) + - [Protobuf](#protobuf) ([buf](#buf), [clang-format](#clang-format)) - [Python](#python) ([black](#black)) - [FreshMark](#freshmark) aka markdown + - [Flexmark](#flexmark) aka markdown - [Antlr4](#antlr4) ([antlr4formatter](#antlr4formatter)) - [SQL](#sql) ([dbeaver](#dbeaver), [prettier](#prettier)) - - [Typescript](#typescript) ([tsfmt](#tsfmt), [prettier](#prettier)) - - [JSON](#json) + - [Typescript](#typescript) ([tsfmt](#tsfmt), [prettier](#prettier), [ESLint](#eslint-typescript), [Biome](#biome)) + - [Javascript](#javascript) ([prettier](#prettier), [ESLint](#eslint-javascript), [Biome](#biome)) + - [JSON](#json) ([simple](#simple), [gson](#gson), [jackson](#jackson), [Biome](#biome), [jsonPatch](#jsonPatch)) + - [YAML](#yaml) + - [Shell](#shell) + - [Gherkin](#gherkin) - Multiple languages - - [Prettier](#prettier) ([plugins](#prettier-plugins), [npm detection](#npm-detection), [`.npmrc` detection](#npmrc-detection)) + - [Prettier](#prettier) ([plugins](#prettier-plugins), [npm detection](#npm-detection), [`.npmrc` detection](#npmrc-detection), [caching `npm install` results](#caching-results-of-npm-install)) - javascript, jsx, angular, vue, flow, typescript, css, less, scss, html, json, graphql, markdown, ymaml - [clang-format](#clang-format) - c, c++, c#, objective-c, protobuf, javascript, java - [eclipse web tools platform](#eclipse-web-tools-platform) - css, html, js, json, xml + - [Biome](#biome) ([binary detection](#biome-binary), [config file](#biome-configuration-file), [input language](#biome-input-language)) - **Language independent** - [Generic steps](#generic-steps) - [License header](#license-header) ([slurp year from git](#retroactively-slurp-years-from-git-history)) @@ -105,7 +106,7 @@ spotless { format 'misc', { // define the files to apply `misc` to - target '*.gradle', '*.md', '.gitignore' + target '*.gradle', '.gitattributes', '.gitignore' // define the steps to apply to those files trimTrailingWhitespace() @@ -116,7 +117,7 @@ spotless { // don't need to set target, it is inferred from java // apply a specific flavor of google-java-format - googleJavaFormat('1.8').aosp().reflowLongStrings() + googleJavaFormat('1.8').aosp().reflowLongStrings().skipJavadocFormatting() // fix formatting of type annotations formatAnnotations() // make sure every file has the following copyright header. @@ -128,22 +129,23 @@ spotless { ``` Spotless consists of a list of formats (in the example above, `misc` and `java`), and each format has: -- a `target` (the files to format), which you set with [`target`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/FormatExtension.html#target-java.lang.Object...-) and [`targetExclude`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/FormatExtension.html#targetExclude-java.lang.Object...-) -- a list of `FormatterStep`, which are just `String -> String` functions, such as [`replace`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/FormatExtension.html#replace-java.lang.String-java.lang.CharSequence-java.lang.CharSequence-), [`replaceRegex`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/FormatExtension.html#replaceRegex-java.lang.String-java.lang.String-java.lang.String-), [`trimTrailingWhitespace`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/FormatExtension.html#replace-java.lang.String-java.lang.CharSequence-java.lang.CharSequence-), [`custom`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/FormatExtension.html#custom-java.lang.String-groovy.lang.Closure-), [`prettier`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/FormatExtension.html#prettier--), [`eclipseWtp`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/FormatExtension.html#eclipseWtp-com.diffplug.spotless.extra.wtp.EclipseWtpFormatterStep-), [`licenseHeader`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/FormatExtension.html#licenseHeader-java.lang.String-java.lang.String-) etc. +- a `target` (the files to format), which you set with [`target`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/FormatExtension.html#target-java.lang.Object...-) and [`targetExclude`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/FormatExtension.html#targetExclude-java.lang.Object...-) +- a list of `FormatterStep`, which are just `String -> String` functions, such as [`replace`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/FormatExtension.html#replace-java.lang.String-java.lang.CharSequence-java.lang.CharSequence-), [`replaceRegex`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/FormatExtension.html#replaceRegex-java.lang.String-java.lang.String-java.lang.String-), [`trimTrailingWhitespace`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/FormatExtension.html#replace-java.lang.String-java.lang.CharSequence-java.lang.CharSequence-), [`custom`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/FormatExtension.html#custom-java.lang.String-groovy.lang.Closure-), [`prettier`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/FormatExtension.html#prettier--), [`eclipseWtp`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/FormatExtension.html#eclipseWtp-com.diffplug.spotless.extra.wtp.EclipseWtpFormatterStep-), [`licenseHeader`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/FormatExtension.html#licenseHeader-java.lang.String-java.lang.String-) etc. -All the generic steps live in [`FormatExtension`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/FormatExtension.html), and there are many language-specific steps which live in its language-specific subclasses, which are described below. +All the generic steps live in [`FormatExtension`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/FormatExtension.html), and there are many language-specific steps which live in its language-specific subclasses, which are described below. ### Requirements -Spotless requires JRE 8+, and Gradle 6.1.1+. Some steps require JRE 11+, `Unsupported major.minor version` means you're using a step that needs a newer JRE. +Spotless requires JRE 11+ and Gradle 6.1.1 or newer. -If you're stuck on an older version of Gradle, `id 'com.diffplug.gradle.spotless' version '4.5.1'` supports all the way back to Gradle 2.x. +- If you're stuck on JRE 8, use [`id 'com.diffplug.spotless' version '6.13.0'` or older](https://github.com/diffplug/spotless/blob/main/plugin-gradle/CHANGES.md#6130---2023-01-14). +- If you're stuck on an older version of Gradle, [`id 'com.diffplug.gradle.spotless' version '4.5.1'` supports all the way back to Gradle 2.x](https://github.com/diffplug/spotless/blob/main/plugin-gradle/CHANGES.md#451---2020-07-04). ## Java -`com.diffplug.gradle.spotless.JavaExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/JavaExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java) +`com.diffplug.gradle.spotless.JavaExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/JavaExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java) ```gradle spotless { @@ -151,14 +153,17 @@ spotless { // Use the default importOrder configuration importOrder() // optional: you can specify import groups directly - // note: you can use an empty string for all the imports you didn't specify explicitly, and '\\#` prefix for static imports - importOrder('java', 'javax', 'com.acme', '', '\\#com.acme', '\\#') + // note: you can use an empty string for all the imports you didn't specify explicitly, '|' to join group without blank line, and '\\#` prefix for static imports + importOrder('java|javax', 'com.acme', '', '\\#com.acme', '\\#') // optional: instead of specifying import groups directly you can specify a config file // export config file: https://github.com/diffplug/spotless/blob/main/ECLIPSE_SCREENSHOTS.md#creating-spotlessimportorder importOrderFile('eclipse-import-order.txt') // import order file as exported from eclipse removeUnusedImports() + // Cleanthat will refactor your code, but it may break your style: apply it before your formatter + cleanthat() // has its own section below + // Choose one of these formatters. googleJavaFormat() // has its own section below eclipse() // has its own section below @@ -182,6 +187,17 @@ spotless { target 'src/*/java/**/*.java' ``` +### removeUnusedImports + +``` +spotless { + java { + removeUnusedImports() + // optional: you may switch for `google-java-format` as underlying engine to `cleanthat-javaparser-unnecessaryimport` + // which enables processing any language level source file with a JDK8+ Runtime + removeUnusedImports('cleanthat-javaparser-unnecessaryimport') +``` + ### google-java-format [homepage](https://github.com/google/google-java-format). [changelog](https://github.com/google/google-java-format/releases). @@ -189,10 +205,10 @@ spotless { spotless { java { googleJavaFormat() - // optional: you can specify a specific version and/or switch to AOSP style - // and/or reflow long strings (requires at least 1.8) + // optional: you can specify a specific version (>= 1.8) and/or switch to AOSP style + // and/or reflow long strings // and/or use custom group artifact (you probably don't need this) - googleJavaFormat('1.8').aosp().reflowLongStrings().groupArtifact('com.google.googlejavaformat:google-java-format') + googleJavaFormat('1.8').aosp().reflowLongStrings().formatJavadoc(false).reorderImports(false).groupArtifact('com.google.googlejavaformat:google-java-format') ``` ### palantir-java-format @@ -202,8 +218,10 @@ spotless { spotless { java { palantirJavaFormat() - // optional: you can specify a specific version - palantirJavaFormat('2.9.0') + // optional: you can specify a specific version and/or switch to AOSP/GOOGLE style + palantirJavaFormat('2.9.0').style("GOOGLE") + // optional: you can also format Javadocs, requires at least Palantir 2.39.0 + palantirJavaFormat('2.39.0').formatJavadoc(true) ``` ### eclipse jdt @@ -215,7 +233,7 @@ spotless { java { eclipse() // optional: you can specify a specific version and/or config file - eclipse('4.17').configFile('eclipse-prefs.xml') + eclipse('4.26').configFile('eclipse-prefs.xml') ``` @@ -262,13 +280,32 @@ You can use `addTypeAnnotation()` and `removeTypeAnnotation()` to override its d You can make a pull request to add new annotations to Spotless's default list. +### cleanthat + +[homepage](https://github.com/solven-eu/cleanthat). CleanThat enables automatic refactoring of Java code. [ChangeLog](https://github.com/solven-eu/cleanthat/blob/master/CHANGES.MD) + +```gradle +spotless { + java { + cleanthat() + // optional: you can specify a specific version and/or config file + cleanthat() + .groupArtifact('io.github.solven-eu.cleanthat:java') // Optional. Default is 'io.github.solven-eu.cleanthat:java' + .version('2.8') // You may force a custom version of Cleanthat + .sourceCompatibility('1.7') // default is '1.7' + .addMutator('SafeAndConsensual') // Default includes the SafeAndConsensual composite mutator + .addMutator('your.custom.MagicMutator') // List of mutators: https://github.com/solven-eu/cleanthat/blob/master/MUTATORS.generated.MD + .excludeMutator('UseCollectionIsEmpty') // You may exclude some mutators (from Composite ones) + .includeDraft(false) // You may exclude draft mutators (from Composite ones) +``` + ## Groovy -- `com.diffplug.gradle.spotless.GroovyExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/GroovyExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyExtension.java) -- `com.diffplug.gradle.spotless.GroovyGradleExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/GroovyGradleExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyGradleExtension.java) +- `com.diffplug.gradle.spotless.GroovyExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/GroovyExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyExtension.java) +- `com.diffplug.gradle.spotless.GroovyGradleExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/GroovyGradleExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyGradleExtension.java) Configuration for Groovy is similar to [Java](#java), in that it also supports `licenseHeader` and `importOrder`. @@ -286,13 +323,18 @@ spotless { // optional: instead of specifying import groups directly you can specify a config file // export config file: https://github.com/diffplug/spotless/blob/main/ECLIPSE_SCREENSHOTS.md#creating-spotlessimportorder importOrderFile('eclipse-import-order.txt') // import order file as exported from eclipse - - excludeJava() // excludes all Java sources within the Groovy source dirs from formatting + // removes semicolons at the end of lines + removeSemicolons() // the Groovy Eclipse formatter extends the Java Eclipse formatter, // so it formats Java files by default (unless `excludeJava` is used). greclipse() // has its own section below + + licenseHeader('/* (C) $YEAR */') // or licenseHeaderFile - licenseHeader '/* (C) $YEAR */' // or licenseHeaderFile + //---- Below is for `groovy` only ---- + + // excludes all Java sources within the Groovy source dirs from formatting + excludeJava() } groovyGradle { target '*.gradle' // default target of groovyGradle @@ -303,24 +345,23 @@ spotless { ### eclipse groovy -[homepage](https://github.com/groovy/groovy-eclipse/wiki). [changelog](https://github.com/groovy/groovy-eclipse/releases). [compatible versions](https://github.com/diffplug/spotless/tree/main/lib-extra/src/main/resources/com/diffplug/spotless/extra/groovy_eclipse_formatter). The Groovy formatter uses some of the [eclipse jdt](#eclipse-jdt) configuration parameters in addition to groovy-specific ones. All parameters can be configured within a single file, like the Java properties file [greclipse.properties](../testlib/src/main/resources/groovy/greclipse/format/greclipse.properties) in the previous example. The formatter step can also load the [exported Eclipse properties](../ECLIPSE_SCREENSHOTS.md) and augment it with the `.metadata/.plugins/org.eclipse.core.runtime/.settings/org.codehaus.groovy.eclipse.ui.prefs` from your Eclipse workspace as shown below. +[homepage](https://github.com/groovy/groovy-eclipse/wiki). [changelog](https://github.com/groovy/groovy-eclipse/releases). [compatible versions](https://github.com/diffplug/spotless/tree/main/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_groovy_formatter). The Groovy formatter uses some of the [eclipse jdt](#eclipse-jdt) configuration parameters in addition to groovy-specific ones. All parameters can be configured within a single file, like the Java properties file [greclipse.properties](../testlib/src/main/resources/groovy/greclipse/format/greclipse.properties) in the previous example. The formatter step can also load the [exported Eclipse properties](../ECLIPSE_SCREENSHOTS.md) and augment it with the `.metadata/.plugins/org.eclipse.core.runtime/.settings/org.codehaus.groovy.eclipse.ui.prefs` from your Eclipse workspace as shown below. ```gradle spotless { groovy { // Use the default version and Groovy-Eclipse default configuration greclipse() - // optional: you can specify a specific version or config file(s) - // compatible versions: https://github.com/diffplug/spotless/tree/main/lib-extra/src/main/resources/com/diffplug/spotless/extra/groovy_eclipse_formatter - greclipse('2.3.0').configFile('spotless.eclipseformat.xml', 'org.codehaus.groovy.eclipse.ui.prefs') + // optional: you can specify a specific version or config file(s), version matches the Eclipse Platform + greclipse('4.26').configFile('spotless.eclipseformat.xml', 'org.codehaus.groovy.eclipse.ui.prefs') ``` Groovy-Eclipse formatting errors/warnings lead per default to a build failure. This behavior can be changed by adding the property/key value `ignoreFormatterProblems=true` to a configuration file. In this scenario, files causing problems, will not be modified by this formatter step. ## Kotlin -- `com.diffplug.gradle.spotless.KotlinExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/KotlinExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java) -- `com.diffplug.gradle.spotless.KotlinGradleExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/KotlinGradleExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinGradleExtension.java) +- `com.diffplug.gradle.spotless.KotlinExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/KotlinExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java) +- `com.diffplug.gradle.spotless.KotlinGradleExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/KotlinGradleExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinGradleExtension.java) ```gradle spotless { // if you are using build.gradle.kts, instead of 'spotless {' use: @@ -354,16 +395,31 @@ spotless { ### ktlint -[homepage](https://github.com/pinterest/ktlint). [changelog](https://github.com/pinterest/ktlint/releases). Spotless does not ([yet](https://github.com/diffplug/spotless/issues/142)) respect the `.editorconfig` settings ([ktlint docs](https://github.com/pinterest/ktlint#editorconfig)), but you can provide them manually as `editorConfigOverride`. +[homepage](https://github.com/pinterest/ktlint). [changelog](https://github.com/pinterest/ktlint/releases). + +Spotless respects the `.editorconfig` settings by providing `editorConfigPath` option. +([ktlint docs](https://github.com/pinterest/ktlint#editorconfig)). +Default value is the `.editorconfig` file located in the top project. +Passing `null` will clear the option. + +Additionally, `editorConfigOverride` options will override what's supplied in `.editorconfig` file. ```kotlin spotless { kotlin { - // version, setUseExperimental, userData and editorConfigOverride are all optional - ktlint("0.45.2") - .setUseExperimental(true) - .userData(mapOf("android" to "true")) - .editorConfigOverride(mapOf("indent_size" to 2)) + // version, editorConfigPath, editorConfigOverride and customRuleSets are all optional + ktlint("1.0.0") + .setEditorConfigPath("$projectDir/config/.editorconfig") // sample unusual placement + .editorConfigOverride( + mapOf( + "indent_size" to 2, + ) + ) + .customRuleSets( + listOf( + "io.nlopez.compose.rules:ktlint:0.3.3" + ) + ) } } ``` @@ -383,7 +439,7 @@ spotless { ## Scala -`com.diffplug.gradle.spotless.ScalaExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/ScalaExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ScalaExtension.java) +`com.diffplug.gradle.spotless.ScalaExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/ScalaExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ScalaExtension.java) ```gradle spotless { @@ -407,15 +463,15 @@ spotless { ```gradle spotless { scala { - // version and configFile, majorScalaVersion are all optional - scalafmt('3.5.9').configFile('scalafmt.conf').majorScalaVersion('2.13') + // version and configFile, scalaMajorVersion are all optional + scalafmt('3.5.9').configFile('scalafmt.conf').scalaMajorVersion('2.13') ``` ## C/C++ -`com.diffplug.gradle.spotless.CppExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/CppExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/CppExtension.java) +`com.diffplug.gradle.spotless.CppExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/CppExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/CppExtension.java) ```gradle spotless { @@ -447,7 +503,7 @@ spotles { ## Python -`com.diffplug.gradle.spotless.PythonExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/PythonExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/PythonExtension.java) +`com.diffplug.gradle.spotless.PythonExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/PythonExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/PythonExtension.java) ```gradle spotless { @@ -479,9 +535,43 @@ black().pathToExe('C:/myuser/.pyenv/versions/3.8.0/scripts/black.exe') +## Protobuf + +### buf + +`com.diffplug.gradle.spotless.ProtobufExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/ProtobufExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ProtobufExtension.java) + +**WARNING** this step **must** be the first step in the chain, steps before it will be ignored. Thumbs up [this issue](https://github.com/bufbuild/buf/issues/1035) for a resolution, see [here](https://github.com/diffplug/spotless/pull/1208#discussion_r1264439669) for more details on the problem. + +```gradle +spotless { + protobuf { + // by default the target is every '.proto' file in the project + buf() + + licenseHeader '/* (C) $YEAR */' // or licenseHeaderFile + } +} +``` + +When used in conjunction with the [buf-gradle-plugin](https://github.com/bufbuild/buf-gradle-plugin), the `buf` executable can be resolved from its `bufTool` configuration: + +```gradle +spotless { + protobuf { + buf().pathToExe(configurations.getByName(BUF_BINARY_CONFIGURATION_NAME).getSingleFile().getAbsolutePath()) + } +} + +// Be sure to disable the buf-gradle-plugin's execution of `buf format`: +buf { + enforceFormat = false +} +``` + ## FreshMark -`com.diffplug.gradle.spotless.FreshMarkExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/FreshMarkExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FreshMarkExtension.java) +`com.diffplug.gradle.spotless.FreshMarkExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/FreshMarkExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FreshMarkExtension.java) [homepage](https://github.com/diffplug/freshmark). [changelog](https://github.com/diffplug/freshmark/blob/master/CHANGES.md). FreshMark lets you generate markdown in the comments of your markdown. This helps to keep badges and links up-to-date (see the source for this file), and can also be helpful for generating complex tables (see the source for [the parent readme](../README.md)). @@ -500,9 +590,28 @@ spotless { } ``` +## Flexmark + +`com.diffplug.gradle.spotless.FlexmarkExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/FlexmarkExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FlexmarkExtension.java) + +[homepage](https://github.com/vsch/flexmark-java). Flexmark is a flexible Commonmark/Markdown parser that can be used to format Markdown files. It supports different [flavors of Markdown](https://github.com/vsch/flexmark-java#markdown-processor-emulation) and [many formatting options](https://github.com/vsch/flexmark-java/wiki/Markdown-Formatter#options). + +Currently, none of the available options can be configured yet. It uses only the default options together with `COMMONMARK` as `FORMATTER_EMULATION_PROFILE`. + +To apply flexmark to all of the `.md` files in your project, use this snippet: + +```gradle +spotless { + flexmark { + target '**/*.md' // you have to set the target manually + flexmark() // or flexmark('0.64.8') // version is optional + } +} +``` + ## Antlr4 -`com.diffplug.gradle.spotless.Antlr4Extension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/Antlr4Extension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/Antlr4Extension.java) +`com.diffplug.gradle.spotless.Antlr4Extension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/Antlr4Extension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/Antlr4Extension.java) ```gradle spotless { @@ -527,7 +636,7 @@ antlr4formatter('1.2.1') // version is optional ## SQL -`com.diffplug.gradle.spotless.SqlExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/SqlExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SqlExtension.java) +`com.diffplug.gradle.spotless.SqlExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/SqlExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SqlExtension.java) ```gradle spotless { @@ -567,7 +676,7 @@ sql.formatter.indent.size=4 ## Typescript -- `com.diffplug.gradle.spotless.TypescriptExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/TypescriptExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java) +- `com.diffplug.gradle.spotless.TypescriptExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/TypescriptExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java) ```gradle spotless { @@ -576,6 +685,8 @@ spotless { tsfmt() // has its own section below prettier() // has its own section below + eslint() // has its own section below + biome() // has its own section below licenseHeader '/* (C) $YEAR */', '(import|const|declare|export|var) ' // or licenseHeaderFile // note the '(import|const|...' argument - this is a regex which identifies the top @@ -606,20 +717,137 @@ spotless { **Prerequisite: tsfmt requires a working NodeJS version** -For details, see the [npm detection](#npm-detection) and [`.npmrc` detection](#npmrc-detection) sections of prettier, which apply also to tsfmt. +For details, see the [npm detection](#npm-detection), [`.npmrc` detection](#npmrc-detection) and [caching results of `npm install`](#caching-results-of-npm-install) sections of prettier, which apply also to tsfmt. + + +### ESLint (Typescript) + +[npm](https://www.npmjs.com/package/eslint). [changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md). *Please note:* +The auto-discovery of config files (up the file tree) will not work when using ESLint within spotless, +hence you are required to provide resolvable file paths for config files, or alternatively provide the configuration inline. + +The configuration is very similar to the [ESLint (Javascript)](#eslint-javascript) configuration. In typescript, a +reference to a `tsconfig.json` is required. + +```gradle +spotless { + typescript { + eslint('8.30.0') // version is optional + eslint(['my-eslint-fork': '1.2.3', 'my-eslint-plugin': '1.2.1']) // can specify exactly which npm packages to use + + eslint() + // configuration is mandatory. Provide inline config or a config file. + // a) inline-configuration + .configJs(''' + { + env: { + browser: true, + es2021: true + }, + extends: 'standard-with-typescript', + overrides: [ + ], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: './tsconfig.json', + }, + rules: { + } + } + ''') + // b) config file + .configFile('.eslintrc.js') + // recommended: provide a tsconfig.json - especially when using the styleguides + .tsconfigFile('tsconfig.json') + } +} +``` + +**Prerequisite: ESLint requires a working NodeJS version** + +For details, see the [npm detection](#npm-detection), [`.npmrc` detection](#npmrc-detection) and [caching results of `npm install`](#caching-results-of-npm-install) sections of prettier, which apply also to ESLint. + +## Javascript + +- `com.diffplug.gradle.spotless.JavascriptExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/JavascriptExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavascriptExtension.java) + +```gradle +spotless { + javascript { + target 'src/**/*.js' // you have to set the target manually + + prettier() // has its own section below + eslint() // has its own section below + biome() // has its own section below + + licenseHeader '/* (C) $YEAR */', 'REGEX_TO_DEFINE_TOP_OF_FILE' // or licenseHeaderFile + } +} +``` + +### ESLint (Javascript) + +[npm](https://www.npmjs.com/package/eslint). [changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md). *Please note:* +The auto-discovery of config files (up the file tree) will not work when using ESLint within spotless, +hence you are required to provide resolvable file paths for config files, or alternatively provide the configuration inline. + +The configuration is very similar to the [ESLint (Typescript)](#eslint-typescript) configuration. In javascript, *no* +`tsconfig.json` is supported. + +```gradle + +```gradle +spotless { + javascript { + eslint('8.30.0') // version is optional + eslint(['my-eslint-fork': '1.2.3', 'my-eslint-plugin': '1.2.1']) // can specify exactly which npm packages to use + + eslint() + // configuration is mandatory. Provide inline config or a config file. + // a) inline-configuration + .configJs(''' + { + env: { + browser: true, + es2021: true + }, + extends: 'standard', + overrides: [ + ], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module' + }, + rules: { + } + } + ''') + // b) config file + .configFile('.eslintrc.js') + } +} +``` + +**Prerequisite: ESLint requires a working NodeJS version** + +For details, see the [npm detection](#npm-detection), [`.npmrc` detection](#npmrc-detection) and [caching results of `npm install`](#caching-results-of-npm-install) sections of prettier, which apply also to ESLint. ## JSON -- `com.diffplug.gradle.spotless.JsonExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/JsonExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java) +- `com.diffplug.gradle.spotless.JsonExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/JsonExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java) ```gradle spotless { json { - target 'src/**/*.json' // you have to set the target manually - simple() // has its own section below + target 'src/**/*.json' // you have to set the target manually + simple() // has its own section below prettier().config(['parser': 'json']) // see Prettier section below - eclipseWtp('json') // see Eclipse web tools platform section - gson() // has its own section below + eclipseWtp('json') // see Eclipse web tools platform section + gson() // has its own section below + jackson() // has its own section below + biome() // has its own section below + jsonPatch([]) // has its own section below } } ``` @@ -658,12 +886,165 @@ spotless { Notes: * There's no option in Gson to leave HTML as-is (i.e. escaped HTML would remain escaped, raw would remain raw). Either -all HTML characters are written escaped or none. Set `escapeHtml` if you prefer the former. + all HTML characters are written escaped or none. Set `escapeHtml` if you prefer the former. * `sortByKeys` will apply lexicographic order on the keys of the input JSON. See the -[javadoc of String](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html#compareTo(java.lang.String)) -for details. + [javadoc of String](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html#compareTo(java.lang.String)) + for details. + +### Jackson + +Uses Jackson for json files. + +```gradle +spotless { + json { + target 'src/**/*.json' + jackson() + .spaceBeforeSeparator(false) // optional: add a whitespace before key separator. False by default + .feature('INDENT_OUTPUT', true) // optional: true by default + .feature('ORDER_MAP_ENTRIES_BY_KEYS', true) // optional: false by default + .feature('ANY_OTHER_FEATURE', true|false) // optional: any SerializationFeature can be toggled on or off + .jsonFeature('ANY_OTHER_FEATURE', true|false) // any JsonGenerator.Feature can be toggled on or off + } +} +``` + +### jsonPatch + +Uses [zjsonpatch](https://github.com/flipkart-incubator/zjsonpatch) to apply [JSON Patches](https://jsonpatch.com/) as per [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902/) to JSON documents. + +This enables you to add, replace or remove properties at locations in the JSON document that you specify using [JSON Pointers](https://datatracker.ietf.org/doc/html/rfc6901/). + +In Spotless Gradle, these JSON patches are represented as a `List>`, or a list of patch operations. + +Each patch operation must be a map with the following properties: + +* `"op"` - the operation to apply, one of `"replace"`, `"add"` or `"remove"`. +* `"path"` - a JSON Pointer string, for example `"/foo"` +* `"value"` - the value to `"add"` or `"replace"` at the specified path. Not needed for `"remove"` operations. + +For example, to apply the patch from the [JSON Patch homepage](https://jsonpatch.com/#the-patch): + +```gradle +spotless { + json { + target 'src/**/*.json' + jsonPatch([ + [op: 'replace', path: '/baz', value: 'boo'], + [op: 'add', path: '/hello', value: ['world']], + [op: 'remove', path: '/foo'] + ]) + } +} +``` + +Or using the Kotlin DSL: + +```kotlin +spotless { + json { + target("src/**/*.json") + jsonPatch(listOf( + mapOf("op" to "replace", "path" to "/baz", "value" to "boo"), + mapOf("op" to "add", "path" to "/hello", "value" to listOf("world")), + mapOf("op" to "remove", "path" to "/foo") + )) + } +} +``` + +## YAML + +- `com.diffplug.gradle.spotless.YamlExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/JsonExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java) + +```gradle +spotless { + yaml { + target 'src/**/*.yaml' // you have to set the target manually + jackson() // has its own section below + prettier() // has its own section below + } +} +``` + +### Jackson + +Uses Jackson for `yaml` files. + +```gradle +spotless { + yaml { + target 'src/**/*.yaml' + jackson() + .spaceBeforeSeparator(false) // optional: add a whitespace before key separator. False by default + .feature('INDENT_OUTPUT', true) // optional: true by default + .feature('ORDER_MAP_ENTRIES_BY_KEYS', true) // optional: false by default + .feature('ANY_OTHER_FEATURE', true|false) // optional: any SerializationFeature can be toggled on or off + .yamlFeature('ANY_OTHER_FEATURE', true|false) // any YAMLGenerator.Feature can be toggled on or off + } +} +``` + +## Shell - +`com.diffplug.gradle.spotless.ShellExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/ShellExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ShellExtension.java) + +```gradle +spotless { + shell { + target 'scripts/**/*.sh' // default: '*.sh' + + shfmt() // has its own section below + } +} +``` + +### shfmt + +[homepage](https://github.com/mvdan/sh). [changelog](https://github.com/mvdan/sh/blob/master/CHANGELOG.md). + +```gradle +shfmt('3.7.0') // version is optional + +// if shfmt is not on your path, you must specify its location manually +shfmt().pathToExe('/opt/homebrew/bin/shfmt') +// Spotless always checks the version of the shfmt it is using +// and will fail with an error if it does not match the expected version +// (whether manually specified or default). If there is a problem, Spotless +// will suggest commands to help install the correct version. +// TODO: handle installation & packaging automatically - https://github.com/diffplug/spotless/issues/674 +``` + + + +## Gherkin + +- `com.diffplug.gradle.spotless.GherkinExtension` [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/GherkinExtension.html), [code](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GherkinExtension.java) + +```gradle +spotless { + gherkin { + target 'src/**/*.feature' // you have to set the target manually + gherkinUtils() // has its own section below + } +} +``` + +### gherkinUtils + +[homepage](https://github.com/cucumber/gherkin-utils). [changelog](https://github.com/cucumber/gherkin-utils/blob/main/CHANGELOG.md). + +Uses a Gherkin pretty-printer that optionally allows configuring the number of spaces that are used to pretty print objects: + +```gradle +spotless { + gherkin { + target 'src/**/*.feature' // required to be set explicitly + gherkinUtils() + .version('8.0.2') // optional: custom version of 'io.cucumber:gherkin-utils' + } +} +``` ## Prettier @@ -707,14 +1088,34 @@ To apply prettier to more kinds of files, just add more formats Since spotless uses the actual npm prettier package behind the scenes, it is possible to use prettier with [plugins](https://prettier.io/docs/en/plugins.html#official-plugins) or [community-plugins](https://www.npmjs.com/search?q=prettier-plugin) in order to support even more file types. +#### prettier version below 3 + +```gradle +spotless { + java { + prettier(['prettier': '2.8.8', 'prettier-plugin-java': '2.2.0']).config(['parser': 'java', 'tabWidth': 4]) + } + format 'php', { + target 'src/**/*.php' + prettier(['prettier': '2.8.8', '@prettier/plugin-php': '0.19.6']).config(['parser': 'php', 'tabWidth': 3]) + } +} +``` + +#### prettier version 3+ + +With version 3 prettier it is required to pass in an additional 'plugins' parameter to the config block with a list of plugins you want to use. + ```gradle spotless { java { - prettier(['prettier': '2.0.5', 'prettier-plugin-java': '0.8.0']).config(['parser': 'java', 'tabWidth': 4]) + prettier(['prettier': '3.0.3', 'prettier-plugin-java': '2.3.0']) + .config(['parser': 'java', 'tabWidth': 4, 'plugins': ['prettier-plugin-java']]) } format 'php', { target 'src/**/*.php' - prettier(['prettier': '2.0.5', '@prettier/plugin-php': '0.14.2']).config(['parser': 'php', 'tabWidth': 3]) + prettier(['prettier': '3.0.3', '@prettier/plugin-php': '0.20.1']) + .config(['parser': 'php', 'tabWidth': 3, 'plugins': ['@prettier/plugin-php']]) } } ``` @@ -722,18 +1123,28 @@ spotless { ### npm detection Prettier is based on NodeJS, so a working NodeJS installation (especially npm) is required on the host running spotless. -Spotless will try to auto-discover an npm installation. If that is not working for you, it is possible to directly configure the npm binary to use. +Spotless will try to auto-discover an npm installation. If that is not working for you, it is possible to directly configure the npm +and/or node binary to use. ```gradle spotless { format 'javascript', { - prettier().npmExecutable('/usr/bin/npm').config(...) + prettier().npmExecutable('/usr/bin/npm').nodeExecutable('/usr/bin/node').config(...) ``` +If you provide both `npmExecutable` and `nodeExecutable`, spotless will use these paths. If you specify only one of the +two, spotless will assume the other one is in the same directory. + +If you use the `gradle-node-plugin` ([github](https://github.com/node-gradle/gradle-node-plugin)), it is possible to use the +node- and npm-binaries dynamically installed by this plugin. See +[this](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/test/resources/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest_gradle_node_plugin_example_1.gradle) +or [this](https://github.com/diffplug/spotless/blob/main/plugin-gradle/src/test/resources/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest_gradle_node_plugin_example_2.gradle) example. + ### `.npmrc` detection Spotless picks up npm configuration stored in a `.npmrc` file either in the project directory or in your user home. -Alternatively you can supply spotless with a location of the `.npmrc` file to use. (This can be combined with `npmExecutable`, of course.) +Alternatively you can supply spotless with a location of the `.npmrc` file to use. (This can be combined with +`npmExecutable` and `nodeExecutable`, of course.) ```gradle spotless { @@ -741,6 +1152,22 @@ spotless { prettier().npmrc("$projectDir/config/.npmrc").config(...) ``` +### Caching results of `npm install` + +Spotless uses `npm` behind the scenes to install `prettier`. This can be a slow process, especially if you are using a slow internet connection or +if you need large plugins. You can instruct spotless to cache the results of the `npm install` calls, so that for the next installation, +it will not need to download the packages again, but instead reuse the cached version. + +```gradle +spotless { + typescript { + prettier().npmInstallCache() // will use the default cache directory (the build-directory of the respective module) + prettier().npmInstallCache("${rootProject.rootDir}/.gradle/spotless-npm-cache") // will use the specified directory (creating it if not existing) +``` + +Depending on your filesystem and the location of the cache directory, spotless will use hardlinks when caching the npm packages. If that is not +possible, it will fall back to copying the files. + ## clang-format [homepage](https://clang.llvm.org/docs/ClangFormat.html). [changelog](https://releases.llvm.org/download.html). `clang-format` is a formatter for c, c++, c#, objective-c, protobuf, javascript, and java. You can use clang-format in any language-specific format, but usually you will be creating a generic format. @@ -779,7 +1206,7 @@ spotless { format 'xml', { target 'src/**/*.xml' // must specify target eclipseWtp('xml') // must specify a type (table below) - eclipseWtp('xml', '4.13.0') // optional version + eclipseWtp('xml', '11.0') // optional version, others at https://download.eclipse.org/tools/cdt/releases/ // you can also specify an arbitrary number of config files eclipseWtp('xml').configFile('spotless.xml.prefs', 'spotless.common.properties' } @@ -809,6 +1236,181 @@ Unlike Eclipse, Spotless WTP ignores per default external URIs in schema locatio external entities. To allow the access of external URIs, set the property `resolveExternalURI` to true. +## Biome + +[homepage](https://biomejs.dev/). [changelog](https://github.com/biomejs/biome/blob/main/CHANGELOG.md). Biome is +a formatter that for the frontend written in Rust, which has a native binary, does not require Node.js and as such, +is pretty fast. It can currently format JavaScript, TypeScript, JSX, and JSON, and may support +[more frontend languages](https://biomejs.dev/internals/language-support/) such as CSS in the future. + +Note: Biome [was formerly called Rome](https://biomejs.dev/blog/annoucing-biome/). Configurations with +the old `` tag and `rome(...)` function are still supported for the time being. This will be removed +in a future version, you should migrate to the new `` tag or `biome(...)` function. The configuration +remains the same, you only need to update the version. If you are using a custom `rome.json` configuration file, +you need to rename it to `biome.json`. + +You can use Biome in any language-specific format for supported languages, but +usually you will be creating a generic format. + +```gradle +spotless { + format 'styling', { + // you have to set the target manually + target 'src/*/webapp/**/*.js' + + // Download Biome from the network if not already downloaded, see below for more info + biome('1.2.0') + + // (optional) Path to the directory with the biome.json conig file + biome('1.2.0').configPath("path/config/dir") + + // (optional) Biome will auto detect the language based on the file extension. + // See below for possible values. + biome('1.2.0').language("js") + } +} +``` + +To apply Biome to more kinds of files with a different configuration, just add +more formats: + +```gradle +spotless { + format 'biome-js', { + target '**/*.js' + biome('1.2.0') + } + format 'biome-ts', { + target '**/*.ts' + biome('1.2.0') + } + format 'biome-json', { + target '**/*.json' + biome('1.2.0') + } +} +``` + +**Limitations:** + +- The auto-discovery of config files (up the file tree) will not work when using + Biome within spotless. +- The `ignore` option of the `biome.json` configuration file will not be applied. + Include and exclude patterns are configured in the spotless configuration in the + Gradle settings file instead. + +Note: Due to a limitation of biome, if the name of a file matches a pattern in +the `ignore` option of the specified `biome.json` configuration file, it will not be +formatted, even if included in the biome configuration section of the Gradle settings +file. +You could specify a different `biome.json` configuration file without an `ignore` +pattern to circumvent this. + +Note 2: Biome is hard-coded to ignore certain special files, such as `package.json` +or `tsconfig.json`. These files will never be formatted. + +### Biome binary + +To format with Biome, spotless needs to find the Biome binary. By default, +spotless downloads the binary for the given version from the network. This +should be fine in most cases, but may not work e.g. when there is not connection +to the internet. + +To download the Biome binary from the network, just specify a version: + +```gradle +spotless { + format 'biome', { + target '**/*.js','**/*.ts','**/*.json' + biome('1.2.0') + } +} +``` + +Spotless uses a default version when you do not specify a version, but this +may change at any time, so we recommend that you always set the Biome version +you want to use. Optionally, you can also specify a directory for the downloaded +Biome binaries (defaults to `~/.m2/repository/com/diffplug/spotless/spotless-data/biome`): + +```gradle +spotless { + format 'biome', { + target '**/*.js','**/*.ts','**/*.json' + // Relative paths are resolved against the project's base directory + biome('1.2.0').downloadDir("${project.gradle.gradleUserHomeDir}/biome") + } +} +``` + +To use a fixed binary, omit the `version` and specify a `pathToExe`: + +```gradle +spotless { + format 'biome', { + target '**/*.js','**/*.ts','**/*.json' + biome().pathToExe("${project.layout.buildDirectory.asFile.get().absolutePath}/bin/biome") + } +} +``` + +Absolute paths are used as-is. Relative paths are resolved against the project's +base directory. To use a pre-installed Biome binary on the user's path, specify +just a name without any slashes / backslashes: + +```gradle +spotless { + format 'biome', { + target '**/*.js','**/*.ts','**/*.json' + // Uses the "biome" command, which must be on the user's path. --> + biome().pathToExe('biome') + } +} +``` + +### Biome configuration file + +Biome is a biased formatter and linter without many options, but there are a few +basic options. Biome uses a file named [biome.json](https://biomejs.dev/reference/configuration/) +for its configuration. When none is specified, the default configuration from +Biome is used. To use a custom configuration: + +```gradle +spotless { + format 'biome', { + target '**/*.js','**/*.ts','**/*.json' + // Must point to the directory with the "biome.json" config file --> + // Relative paths are resolved against the project's base directory --> + biome('1.2.0').configPath('./config') + } +} +``` + +### Biome input language + +By default, Biome detects the language / syntax of the files to format +automatically from the file extension. This may fail if your source code files +have unusual extensions for some reason. If you are using the generic format, +you can force a certain language like this: + +```xml +spotless { + format 'biome', { + target 'src/**/typescript/**/*.mjson' + biome('1.2.0').language('json') + } +} +``` + +The following languages are currently recognized: + +* `js` -- JavaScript +* `jsx` -- JavaScript + JSX (React) +* `js?` -- JavaScript, with or without JSX, depending on the file extension +* `ts` -- TypeScript +* `tsx` -- TypeScript + JSX (React) +* `ts?` -- TypeScript, with or without JSX, depending on the file extension +* `json` -- JSON + ## Generic steps [Prettier](#prettier), [eclipse wtp](#eclipse-web-tools-platform), and [license header](#license-header) are available in every format, and they each have their own section. As mentioned in the [quickstart](#quickstart), there are a variety of simple generic steps which are also available in every format, here are examples of these: @@ -835,7 +1437,7 @@ Once a file's license header has a valid year, whether it is a year (`2020`) or * `2017` -> `2017-2020` * `2017-2019` -> `2017-2020` -See the [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/FormatExtension.LicenseHeaderConfig.html) for a complete listing of options. +See the [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/FormatExtension.LicenseHeaderConfig.html) for a complete listing of options. @@ -843,6 +1445,12 @@ See the [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-g If your project has not been rigorous with copyright headers, and you'd like to use git history to repair this retroactively, you can do so with `-PspotlessSetLicenseHeaderYearsFromGitHistory=true`. When run in this mode, Spotless will do an expensive search through git history for each file, and set the copyright header based on the oldest and youngest commits for that file. This is intended to be a one-off sort of thing. +### Files with fixed header lines + +Some files have fixed header lines (e.g. ` ## How can I enforce formatting gradually? (aka "ratchet") @@ -860,6 +1468,13 @@ However, we strongly recommend that you use a non-local branch, such as a tag or This is especially helpful for injecting accurate copyright dates using the [license step](#license-header). +### Using `ratchetFrom` on CI systems + +Many popular CI systems (GitHub, GitLab, BitBucket, and Travis) use a "shallow clone". This means that `ratchetFrom 'origin/main'` will fail with `No such reference`. You can fix this by: + +- calling `git fetch origin main` before you call Spotless +- disabling the shallow clone [like so](https://github.com/diffplug/spotless/issues/710) + ## `spotless:off` and `spotless:on` Sometimes there is a chunk of code which you have carefully handcrafted, and you would like to exclude just this one little part from getting clobbered by the autoformat. Some formatters have a way to do this, many don't, but who cares. If you setup your spotless like this: @@ -885,12 +1500,12 @@ spotless { encoding 'Cp1252' // except java, which will be Cp1252 ``` -Line endings can also be set globally or per-format using the `lineEndings` property. Spotless supports four line ending modes: `UNIX`, `WINDOWS`, `MAC_CLASSIC`, `PLATFORM_NATIVE`, and `GIT_ATTRIBUTES`. The default value is `GIT_ATTRIBUTES`, and *we highly recommend that you* ***do not change*** *this value*. Git has opinions about line endings, and if Spotless and git disagree, then you're going to have a bad time. +Line endings can also be set globally or per-format using the `lineEndings` property. Spotless supports four line ending modes: `UNIX`, `WINDOWS`, `MAC_CLASSIC`, `PLATFORM_NATIVE`, `GIT_ATTRIBUTES`, and `GIT_ATTRIBUTES_FAST_ALLSAME`. The default value is `GIT_ATTRIBUTES_FAST_ALLSAME`, and *we highly recommend that you* ***do not change*** *this value*. Git has opinions about line endings, and if Spotless and git disagree, then you're going to have a bad time. `FAST_ALLSAME` just means that Spotless can assume that every file being formatted has the same line endings ([more info](https://github.com/diffplug/spotless/pull/1838)). You can easily set the line endings of different files using [a `.gitattributes` file](https://help.github.com/articles/dealing-with-line-endings/). Here's an example `.gitattributes` which sets all files to unix newlines: `* text eol=lf`. - + ## Custom steps @@ -902,9 +1517,9 @@ spotless { custom 'lowercase', { str -> str.toLowerCase() } ``` -However, custom rules will disable up-to-date checking and caching, unless you read [this javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/FormatExtension.html#bumpThisNumberIfACustomStepChanges-int-) and follow its instructions carefully. +However, custom rules will disable up-to-date checking and caching, unless you read [this javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/FormatExtension.html#bumpThisNumberIfACustomStepChanges-int-) and follow its instructions carefully. -Another option is to create proper `FormatterStep` in your `buildSrc`, and then call [`addStep`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/FormatExtension.html#addStep-com.diffplug.spotless.FormatterStep-). The contributing guide describes [how to do this](https://github.com/diffplug/spotless/blob/main/CONTRIBUTING.md#how-to-add-a-new-formatterstep). If the step is generally-useful, we hope you'll open a PR to share it! +Another option is to create proper `FormatterStep` in your `buildSrc`, and then call [`addStep`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/FormatExtension.html#addStep-com.diffplug.spotless.FormatterStep-). The contributing guide describes [how to do this](https://github.com/diffplug/spotless/blob/main/CONTRIBUTING.md#how-to-add-a-new-formatterstep). If the step is generally-useful, we hope you'll open a PR to share it! ```gradle @@ -937,11 +1552,11 @@ spotless { format 'foo', com.acme.FooLanguageExtension, { ``` -If you'd like to create a one-off Spotless task outside of the `check`/`apply` framework, see [`FormatExtension.createIndependentApplyTask`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/FormatExtension.html#createIndependentApplyTask-java.lang.String-). +If you'd like to create a one-off Spotless task outside of the `check`/`apply` framework, see [`FormatExtension.createIndependentApplyTask`](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/FormatExtension.html#createIndependentApplyTask-java.lang.String-). ## Inception (languages within languages within...) -In very rare cases, you might want to format e.g. javascript which is written inside JSP templates, or maybe java within a markdown file, or something wacky like that. You can specify hunks within a file using either open/close tags or a regex with a single capturing group, and then specify rules within it, like so. See [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.10.0/com/diffplug/gradle/spotless/FormatExtension.html#withinBlocks-java.lang.String-java.lang.String-java.lang.String-org.gradle.api.Action-) for more details. +In very rare cases, you might want to format e.g. javascript which is written inside JSP templates, or maybe java within a markdown file, or something wacky like that. You can specify hunks within a file using either open/close tags or a regex with a single capturing group, and then specify rules within it, like so. See [javadoc](https://javadoc.io/doc/com.diffplug.spotless/spotless-plugin-gradle/6.24.0/com/diffplug/gradle/spotless/FormatExtension.html#withinBlocks-java.lang.String-java.lang.String-java.lang.String-org.gradle.api.Action-) for more details. ```gradle import com.diffplug.gradle.spotless.JavaExtension diff --git a/plugin-gradle/build.gradle b/plugin-gradle/build.gradle index 8fec4beb92..9f6ff31cc9 100644 --- a/plugin-gradle/build.gradle +++ b/plugin-gradle/build.gradle @@ -1,17 +1,17 @@ apply from: rootProject.file('gradle/changelog.gradle') ext.artifactId = project.artifactIdGradle version = spotlessChangelog.versionNext -apply from: rootProject.file('gradle/java-setup.gradle') -apply from: rootProject.file('gradle/spotless-freshmark.gradle') - apply plugin: 'java-library' apply plugin: 'com.gradle.plugin-publish' apply plugin: 'java-gradle-plugin' +apply from: rootProject.file('gradle/java-setup.gradle') +apply from: rootProject.file('gradle/spotless-freshmark.gradle') + dependencies { if (version.endsWith('-SNAPSHOT') || (rootProject.spotlessChangelog.versionNext == rootProject.spotlessChangelog.versionLast)) { - api project(':lib') - api project(':lib-extra') + api projects.lib + api projects.libExtra } else { api "com.diffplug.spotless:spotless-lib:${rootProject.spotlessChangelog.versionLast}" api "com.diffplug.spotless:spotless-lib-extra:${rootProject.spotlessChangelog.versionLast}" @@ -21,63 +21,64 @@ dependencies { implementation "com.diffplug.durian:durian-collect:${VER_DURIAN}" implementation "org.eclipse.jgit:org.eclipse.jgit:${VER_JGIT}" - testImplementation project(':testlib') + testImplementation projects.testlib testImplementation "org.junit.jupiter:junit-jupiter:${VER_JUNIT}" testImplementation "org.assertj:assertj-core:${VER_ASSERTJ}" testImplementation "com.diffplug.durian:durian-testlib:${VER_DURIAN}" + testImplementation 'org.owasp.encoder:encoder:1.2.3' + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } -test { - useJUnitPlatform() +apply from: rootProject.file('gradle/special-tests.gradle') +tasks.withType(Test).configureEach { testLogging.showStandardStreams = true } -apply from: rootProject.file('gradle/special-tests.gradle') - ////////////////////////// // GRADLE PLUGIN PORTAL // ////////////////////////// gradlePlugin { + website = "https://github.com/diffplug/spotless" + vcsUrl = "https://github.com/diffplug/spotless" plugins { spotlessPlugin { id = 'com.diffplug.spotless' implementationClass = 'com.diffplug.gradle.spotless.SpotlessPlugin' displayName = 'Spotless formatting plugin' description = project.description + tags.set([ + 'format', + 'style', + 'license', + 'header', + 'google-java-format', + 'eclipse', + 'ktlint', + 'ktfmt', + 'diktat', + 'tsfmt', + 'prettier', + 'scalafmt', + 'scalafix', + 'black', + 'clang-format' + ]) } spotlessPluginLegacy { id = 'com.diffplug.gradle.spotless' implementationClass = 'com.diffplug.gradle.spotless.SpotlessPluginRedirect' displayName = 'Spotless formatting plugin (legacy)' description = project.description + tags.set([ + 'format' + ]) } } } -if (version.endsWith('-SNAPSHOT')) { - publishPlugins.enabled = false -} else { - pluginBundle { - // These settings are set for the whole plugin bundle - website = "https://github.com/diffplug/spotless" - vcsUrl = "https://github.com/diffplug/spotless" - tags = [ - 'format', - 'style', - 'license', - 'header', - 'google-java-format', - 'eclipse', - 'ktlint', - 'ktfmt', - 'diktat', - 'tsfmt', - 'prettier', - 'scalafmt', - 'scalafix', - 'black', - 'clang-format' - ] - } + +tasks.named("publishPlugins") { + enabled = !version.endsWith('-SNAPSHOT') + notCompatibleWithConfigurationCache("https://github.com/gradle/gradle/issues/21283") } // have to apply java-publish after setting up the pluginBundle diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/AJacksonGradleConfig.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/AJacksonGradleConfig.java new file mode 100644 index 0000000000..2185f3693a --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/AJacksonGradleConfig.java @@ -0,0 +1,53 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import java.util.Collections; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.json.JacksonConfig; +import com.diffplug.spotless.json.JacksonJsonStep; + +public abstract class AJacksonGradleConfig { + protected final FormatExtension formatExtension; + + protected JacksonConfig jacksonConfig; + + protected String version = JacksonJsonStep.defaultVersion(); + + // Make sure to call 'formatExtension.addStep(createStep());' in the extented constructors + public AJacksonGradleConfig(JacksonConfig jacksonConfig, FormatExtension formatExtension) { + this.formatExtension = formatExtension; + + this.jacksonConfig = jacksonConfig; + } + + public T feature(String feature, boolean toggle) { + this.jacksonConfig.appendFeatureToToggle(Collections.singletonMap(feature, toggle)); + formatExtension.replaceStep(createStep()); + return self(); + } + + public T version(String version) { + this.version = version; + formatExtension.replaceStep(createStep()); + return self(); + } + + public abstract T self(); + + protected abstract FormatterStep createStep(); +} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/BaseGroovyExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/BaseGroovyExtension.java new file mode 100644 index 0000000000..5b302bbe0f --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/BaseGroovyExtension.java @@ -0,0 +1,80 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import static com.diffplug.gradle.spotless.PluginGradlePreconditions.requireElementsNonNull; + +import java.util.Map; +import java.util.Objects; + +import org.gradle.api.Project; + +import com.diffplug.spotless.extra.EquoBasedStepBuilder; +import com.diffplug.spotless.extra.groovy.GrEclipseFormatterStep; +import com.diffplug.spotless.groovy.RemoveSemicolonsStep; +import com.diffplug.spotless.java.ImportOrderStep; + +public abstract class BaseGroovyExtension extends FormatExtension { + protected BaseGroovyExtension(SpotlessExtension spotless) { + super(spotless); + } + + public void importOrder(String... importOrder) { + addStep(ImportOrderStep.forGroovy().createFrom(importOrder)); + } + + public void importOrderFile(Object importOrderFile) { + Objects.requireNonNull(importOrderFile); + addStep(ImportOrderStep.forGroovy().createFrom(getProject().file(importOrderFile))); + } + + public void removeSemicolons() { + addStep(RemoveSemicolonsStep.create()); + } + + public GrEclipseConfig greclipse() { + return greclipse(GrEclipseFormatterStep.defaultVersion()); + } + + public GrEclipseConfig greclipse(String version) { + return new GrEclipseConfig(version, this); + } + + public static class GrEclipseConfig { + private final EquoBasedStepBuilder builder; + private final FormatExtension extension; + + private GrEclipseConfig(String version, FormatExtension extension) { + this.extension = extension; + builder = GrEclipseFormatterStep.createBuilder(extension.provisioner()); + builder.setVersion(version); + extension.addStep(builder.build()); + } + + public void configFile(Object... configFiles) { + requireElementsNonNull(configFiles); + Project project = extension.getProject(); + builder.setPreferences(project.files(configFiles).getFiles()); + extension.replaceStep(builder.build()); + } + + public GrEclipseConfig withP2Mirrors(Map mirrors) { + builder.setP2Mirrors(mirrors); + extension.replaceStep(builder.build()); + return this; + } + } +} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/BaseKotlinExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/BaseKotlinExtension.java new file mode 100644 index 0000000000..4be814a470 --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/BaseKotlinExtension.java @@ -0,0 +1,205 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Consumer; + +import javax.annotation.Nullable; + +import com.diffplug.common.collect.ImmutableList; +import com.diffplug.common.collect.ImmutableSortedMap; +import com.diffplug.spotless.FileSignature; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.kotlin.DiktatStep; +import com.diffplug.spotless.kotlin.KtLintStep; +import com.diffplug.spotless.kotlin.KtfmtStep; + +public abstract class BaseKotlinExtension extends FormatExtension { + protected BaseKotlinExtension(SpotlessExtension spotless) { + super(spotless); + } + + public DiktatConfig diktat() { + return diktat(DiktatStep.defaultVersionDiktat()); + } + + /** Adds the specified version of diktat. */ + public DiktatConfig diktat(String version) { + return new DiktatConfig(version); + } + + public KtlintConfig ktlint() throws IOException { + return ktlint(KtLintStep.defaultVersion()); + } + + /** Adds the specified version of ktlint. */ + public KtlintConfig ktlint(String version) throws IOException { + return new KtlintConfig(version, Collections.emptyMap(), Collections.emptyList()); + } + + /** Uses the ktfmt jar to format source code. */ + public KtfmtConfig ktfmt() { + return ktfmt(KtfmtStep.defaultVersion()); + } + + /** + * Uses the given version of ktfmt and applies the dropbox style + * option to format source code. + */ + public KtfmtConfig ktfmt(String version) { + return new KtfmtConfig(version); + } + + protected abstract boolean isScript(); + + public class DiktatConfig { + private final String version; + private FileSignature config; + + private DiktatConfig(String version) { + Objects.requireNonNull(version, "version"); + this.version = version; + addStep(createStep()); + } + + public DiktatConfig configFile(Object file) throws IOException { + // Specify the path to the configuration file + if (file == null) { + this.config = null; + } else { + this.config = FileSignature.signAsList(getProject().file(file)); + } + replaceStep(createStep()); + return this; + } + + private FormatterStep createStep() { + return DiktatStep.create(version, provisioner(), isScript(), config); + } + } + + public class KtfmtConfig { + private final String version; + private final ConfigurableStyle configurableStyle = new ConfigurableStyle(); + private KtfmtStep.Style style; + private KtfmtStep.KtfmtFormattingOptions options; + + private KtfmtConfig(String version) { + Objects.requireNonNull(version); + this.version = Objects.requireNonNull(version); + addStep(createStep()); + } + + public ConfigurableStyle dropboxStyle() { + return style(KtfmtStep.Style.DROPBOX); + } + + public ConfigurableStyle googleStyle() { + return style(KtfmtStep.Style.GOOGLE); + } + + public ConfigurableStyle kotlinlangStyle() { + return style(KtfmtStep.Style.KOTLINLANG); + } + + public void configure(Consumer optionsConfiguration) { + this.configurableStyle.configure(optionsConfiguration); + } + + private ConfigurableStyle style(KtfmtStep.Style style) { + this.style = style; + replaceStep(createStep()); + return configurableStyle; + } + + private FormatterStep createStep() { + return KtfmtStep.create(version, provisioner(), style, options); + } + + public class ConfigurableStyle { + public void configure(Consumer optionsConfiguration) { + KtfmtStep.KtfmtFormattingOptions ktfmtFormattingOptions = new KtfmtStep.KtfmtFormattingOptions(); + optionsConfiguration.accept(ktfmtFormattingOptions); + options = ktfmtFormattingOptions; + replaceStep(createStep()); + } + } + } + + public class KtlintConfig { + private final String version; + private FileSignature editorConfigPath; + private Map editorConfigOverride; + private List customRuleSets; + + private KtlintConfig( + String version, + Map editorConfigOverride, + List customRuleSets) throws IOException { + Objects.requireNonNull(version); + File defaultEditorConfig = getProject().getRootProject().file(".editorconfig"); + FileSignature editorConfigPath = defaultEditorConfig.exists() ? FileSignature.signAsList(defaultEditorConfig) : null; + this.version = version; + this.editorConfigPath = editorConfigPath; + this.editorConfigOverride = editorConfigOverride; + this.customRuleSets = customRuleSets; + addStep(createStep()); + } + + public KtlintConfig setEditorConfigPath(@Nullable Object editorConfigPath) throws IOException { + if (editorConfigPath == null) { + this.editorConfigPath = null; + } else { + File editorConfigFile = getProject().file(editorConfigPath); + if (!editorConfigFile.exists()) { + throw new IllegalArgumentException("EditorConfig file does not exist: " + editorConfigFile); + } + this.editorConfigPath = FileSignature.signAsList(editorConfigFile); + } + replaceStep(createStep()); + return this; + } + + public KtlintConfig editorConfigOverride(Map editorConfigOverride) { + // Copy the map to a sorted map because up-to-date checking is based on binary-equals of the serialized + // representation. + this.editorConfigOverride = ImmutableSortedMap.copyOf(editorConfigOverride); + replaceStep(createStep()); + return this; + } + + public KtlintConfig customRuleSets(List customRuleSets) { + this.customRuleSets = ImmutableList.copyOf(customRuleSets); + replaceStep(createStep()); + return this; + } + + private FormatterStep createStep() { + return KtLintStep.create( + version, + provisioner(), + editorConfigPath, + editorConfigOverride, + customRuleSets); + } + } +} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/CppExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/CppExtension.java index 64dabc9ca5..6a08946c48 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/CppExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/CppExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,14 @@ import static com.diffplug.gradle.spotless.PluginGradlePreconditions.requireElementsNonNull; +import java.util.Map; + import javax.inject.Inject; import org.gradle.api.Project; import com.diffplug.spotless.cpp.CppDefaults; -import com.diffplug.spotless.extra.EclipseBasedStepBuilder; +import com.diffplug.spotless.extra.EquoBasedStepBuilder; import com.diffplug.spotless.extra.cpp.EclipseCdtFormatterStep; public class CppExtension extends FormatExtension implements HasBuiltinDelimiterForLicense { @@ -42,7 +44,7 @@ public EclipseConfig eclipseCdt(String version) { } public class EclipseConfig { - private final EclipseBasedStepBuilder builder; + private final EquoBasedStepBuilder builder; EclipseConfig(String version) { builder = EclipseCdtFormatterStep.createBuilder(provisioner()); @@ -56,6 +58,12 @@ public void configFile(Object... configFiles) { builder.setPreferences(project.files(configFiles).getFiles()); replaceStep(builder.build()); } + + public EclipseConfig withP2Mirrors(Map mirrors) { + builder.setP2Mirrors(mirrors); + replaceStep(builder.build()); + return this; + } } @Override diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FlexmarkExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FlexmarkExtension.java new file mode 100644 index 0000000000..d1f941613b --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FlexmarkExtension.java @@ -0,0 +1,64 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import java.util.Objects; + +import javax.inject.Inject; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.markdown.FlexmarkStep; + +public class FlexmarkExtension extends FormatExtension { + static final String NAME = "flexmark"; + + @Inject + public FlexmarkExtension(SpotlessExtension spotless) { + super(spotless); + } + + public FlexmarkFormatterConfig flexmark() { + return flexmark(FlexmarkStep.defaultVersion()); + } + + public FlexmarkFormatterConfig flexmark(String version) { + return new FlexmarkFormatterConfig(version); + } + + @Override + protected void setupTask(SpotlessTask task) { + // defaults to all markdown files + if (target == null) { + throw noDefaultTargetException(); + } + super.setupTask(task); + } + + public class FlexmarkFormatterConfig { + + private final String version; + + FlexmarkFormatterConfig(String version) { + this.version = Objects.requireNonNull(version); + addStep(createStep()); + } + + private FormatterStep createStep() { + return FlexmarkStep.create(this.version, provisioner()); + } + } + +} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java index de0313eab4..eae8ac667d 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,18 +16,22 @@ package com.diffplug.gradle.spotless; import static com.diffplug.gradle.spotless.PluginGradlePreconditions.requireElementsNonNull; +import static java.util.Objects.requireNonNull; import java.io.File; import java.io.Serializable; import java.nio.charset.Charset; import java.nio.file.Files; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Random; import java.util.TreeMap; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.regex.Pattern; import javax.annotation.Nullable; import javax.inject.Inject; @@ -36,6 +40,7 @@ import org.gradle.api.GradleException; import org.gradle.api.Project; import org.gradle.api.file.ConfigurableFileTree; +import org.gradle.api.file.Directory; import org.gradle.api.file.FileCollection; import org.gradle.api.plugins.BasePlugin; import org.gradle.api.tasks.TaskProvider; @@ -46,6 +51,7 @@ import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.LazyForwardingEquality; import com.diffplug.spotless.LineEnding; +import com.diffplug.spotless.OnMatch; import com.diffplug.spotless.Provisioner; import com.diffplug.spotless.cpp.ClangFormatStep; import com.diffplug.spotless.extra.EclipseBasedStepBuilder; @@ -61,6 +67,7 @@ import com.diffplug.spotless.generic.TrimTrailingWhitespaceStep; import com.diffplug.spotless.npm.NpmPathResolver; import com.diffplug.spotless.npm.PrettierFormatterStep; +import com.diffplug.spotless.rome.BiomeFlavor; import groovy.lang.Closure; @@ -71,7 +78,7 @@ public class FormatExtension { @Inject public FormatExtension(SpotlessExtension spotless) { - this.spotless = Objects.requireNonNull(spotless); + this.spotless = requireNonNull(spotless); } protected final Provisioner provisioner() { @@ -89,29 +96,44 @@ private String formatName() { LineEnding lineEndings; - /** Returns the line endings to use (defaults to {@link SpotlessExtensionImpl#getLineEndings()}. */ + /** + * Returns the line endings to use (defaults to + * {@link SpotlessExtensionImpl#getLineEndings()}. + */ public LineEnding getLineEndings() { return lineEndings == null ? spotless.getLineEndings() : lineEndings; } - /** Sets the line endings to use (defaults to {@link SpotlessExtensionImpl#getLineEndings()}. */ + /** + * Sets the line endings to use (defaults to + * {@link SpotlessExtensionImpl#getLineEndings()}. + */ public void setLineEndings(LineEnding lineEndings) { - this.lineEndings = Objects.requireNonNull(lineEndings); + this.lineEndings = requireNonNull(lineEndings); } Charset encoding; - /** Returns the encoding to use (defaults to {@link SpotlessExtensionImpl#getEncoding()}. */ + /** + * Returns the encoding to use (defaults to + * {@link SpotlessExtensionImpl#getEncoding()}. + */ public Charset getEncoding() { return encoding == null ? spotless.getEncoding() : encoding; } - /** Sets the encoding to use (defaults to {@link SpotlessExtensionImpl#getEncoding()}. */ + /** + * Sets the encoding to use (defaults to + * {@link SpotlessExtensionImpl#getEncoding()}. + */ public void setEncoding(String name) { - setEncoding(Charset.forName(Objects.requireNonNull(name))); + setEncoding(Charset.forName(requireNonNull(name))); } - /** Sentinel to distinguish between "don't ratchet this format" and "use spotless parent format". */ + /** + * Sentinel to distinguish between "don't ratchet this format" and "use spotless + * parent format". + */ private static final String RATCHETFROM_NOT_SET_AT_FORMAT_LEVEL = " not set at format level "; private String ratchetFrom = RATCHETFROM_NOT_SET_AT_FORMAT_LEVEL; @@ -122,8 +144,8 @@ public String getRatchetFrom() { } /** - * Allows you to override the value from the parent {@link SpotlessExtension#setRatchetFrom(String)} - * for this specific format. + * Allows you to override the value from the parent + * {@link SpotlessExtension#setRatchetFrom(String)} for this specific format. */ public void setRatchetFrom(String ratchetFrom) { this.ratchetFrom = ratchetFrom; @@ -134,24 +156,30 @@ public void ratchetFrom(String ratchetFrom) { setRatchetFrom(ratchetFrom); } - /** Sets the encoding to use (defaults to {@link SpotlessExtensionImpl#getEncoding()}. */ + /** + * Sets the encoding to use (defaults to + * {@link SpotlessExtensionImpl#getEncoding()}. + */ public void setEncoding(Charset charset) { - encoding = Objects.requireNonNull(charset); + encoding = requireNonNull(charset); } final FormatExceptionPolicyStrict exceptionPolicy = new FormatExceptionPolicyStrict(); /** Ignores errors in the given step. */ public void ignoreErrorForStep(String stepName) { - exceptionPolicy.excludeStep(Objects.requireNonNull(stepName)); + exceptionPolicy.excludeStep(requireNonNull(stepName)); } /** Ignores errors for the given relative path. */ public void ignoreErrorForPath(String relativePath) { - exceptionPolicy.excludePath(Objects.requireNonNull(relativePath)); + exceptionPolicy.excludePath(requireNonNull(relativePath)); } - /** Sets encoding to use (defaults to {@link SpotlessExtensionImpl#getEncoding()}). */ + /** + * Sets encoding to use (defaults to + * {@link SpotlessExtensionImpl#getEncoding()}). + */ public void encoding(String charset) { setEncoding(charset); } @@ -159,6 +187,10 @@ public void encoding(String charset) { /** The files to be formatted = (target - targetExclude). */ protected FileCollection target, targetExclude; + /** The value from which files will be excluded if their content contain it. */ + @Nullable + protected String targetExcludeContentPattern = null; + protected boolean isLicenseHeaderStep(FormatterStep formatterStep) { String formatterStepName = formatterStep.getName(); @@ -170,36 +202,56 @@ protected boolean isLicenseHeaderStep(FormatterStep formatterStep) { } /** - * Sets which files should be formatted. Files to be formatted = (target - targetExclude). - * + * Sets which files should be formatted. Files to be formatted = (target - + * targetExclude). + *

* When this method is called multiple times, only the last call has any effect. - * - * FileCollections pass through raw. - * Strings are treated as the 'include' arg to fileTree, with project.rootDir as the dir. - * List are treated as the 'includes' arg to fileTree, with project.rootDir as the dir. - * Anything else gets passed to getProject().files(). - * - * If you pass any strings that start with "**\/*", this method will automatically filter out - * "build", ".gradle", and ".git" folders. + *

+ * FileCollections pass through raw. Strings are treated as the 'include' arg to + * fileTree, with project.rootDir as the dir. List are treated as the + * 'includes' arg to fileTree, with project.rootDir as the dir. Anything else + * gets passed to getProject().files(). + *

+ * If you pass any strings that start with "**\/*", this method will + * automatically filter out "build", ".gradle", and ".git" folders. */ public void target(Object... targets) { this.target = parseTargetsIsExclude(targets, false); } /** - * Sets which files will be excluded from formatting. Files to be formatted = (target - targetExclude). - * + * Sets which files will be excluded from formatting. Files to be formatted = + * (target - targetExclude). + *

* When this method is called multiple times, only the last call has any effect. - * - * FileCollections pass through raw. - * Strings are treated as the 'include' arg to fileTree, with project.rootDir as the dir. - * List are treated as the 'includes' arg to fileTree, with project.rootDir as the dir. - * Anything else gets passed to getProject().files(). + *

+ * FileCollections pass through raw. Strings are treated as the 'include' arg to + * fileTree, with project.rootDir as the dir. List are treated as the + * 'includes' arg to fileTree, with project.rootDir as the dir. Anything else + * gets passed to getProject().files(). */ public void targetExclude(Object... targets) { this.targetExclude = parseTargetsIsExclude(targets, true); } + /** + * Excludes all files whose content contains {@code string}. + *

+ * When this method is called multiple times, only the last call has any effect. + */ + public void targetExcludeIfContentContains(String string) { + targetExcludeIfContentContainsRegex(Pattern.quote(string)); + } + + /** + * Excludes all files whose content contains the given regex. + *

+ * When this method is called multiple times, only the last call has any effect. + */ + public void targetExcludeIfContentContainsRegex(String regex) { + this.targetExcludeContentPattern = regex; + } + private FileCollection parseTargetsIsExclude(Object[] targets, boolean isExclude) { requireElementsNonNull(targets); if (targets.length == 0) { @@ -216,10 +268,10 @@ private FileCollection parseTargetsIsExclude(Object[] targets, boolean isExclude } /** - * FileCollections pass through raw. - * Strings are treated as the 'include' arg to fileTree, with project.rootDir as the dir. - * List are treated as the 'includes' arg to fileTree, with project.rootDir as the dir. - * Anything else gets passed to getProject().files(). + * FileCollections pass through raw. Strings are treated as the 'include' arg to + * fileTree, with project.rootDir as the dir. List are treated as the + * 'includes' arg to fileTree, with project.rootDir as the dir. Anything else + * gets passed to getProject().files(). */ protected final FileCollection parseTarget(Object target) { return parseTargetIsExclude(target, false); @@ -227,7 +279,7 @@ protected final FileCollection parseTarget(Object target) { private final FileCollection parseTargetIsExclude(Object target, boolean isExclude) { if (target instanceof Collection) { - return parseTargetsIsExclude(((Collection) target).toArray(), isExclude); + return parseTargetsIsExclude(((Collection) target).toArray(), isExclude); } else if (target instanceof FileCollection) { return (FileCollection) target; } else if (target instanceof String) { @@ -236,7 +288,8 @@ private final FileCollection parseTargetIsExclude(Object target, boolean isExclu String targetString = (String) target; matchedFiles.include(targetString); - // since people are likely to do '**/*.md', we want to make sure to exclude folders + // since people are likely to do '**/*.md', we want to make sure to exclude + // folders // they don't want to format which will slow down the operation greatly // but we only want to do that if they are *including* - if they are specifying // what they want to exclude, we shouldn't filter at all @@ -251,10 +304,11 @@ private final FileCollection parseTargetIsExclude(Object target, boolean isExclu if (getProject() == getProject().getRootProject()) { excludes.add(".gradle"); } - // no build folders (flatInclude means that subproject might not be subfolders, see https://github.com/diffplug/spotless/issues/121) - relativizeIfSubdir(excludes, dir, getProject().getBuildDir()); + // no build folders (flatInclude means that subproject might not be subfolders, + // see https://github.com/diffplug/spotless/issues/121) + relativizeIfSubdir(excludes, dir, getProject().getLayout().getBuildDirectory().getAsFile().get()); for (Project subproject : getProject().getSubprojects()) { - relativizeIfSubdir(excludes, dir, subproject.getBuildDir()); + relativizeIfSubdir(excludes, dir, subproject.getLayout().getBuildDirectory().getAsFile().get()); } matchedFiles.exclude(excludes); } @@ -272,8 +326,8 @@ private static void relativizeIfSubdir(List relativePaths, File root, Fi } /** - * Returns the relative path between root and dest, - * or null if dest is not a child of root. + * Returns the relative path between root and dest, or null if dest is not a + * child of root. */ static @Nullable String relativize(File root, File dest) { String rootPath = root.getAbsolutePath(); @@ -290,15 +344,26 @@ private static void relativizeIfSubdir(List relativePaths, File root, Fi /** Adds a new step. */ public void addStep(FormatterStep newStep) { - Objects.requireNonNull(newStep); + requireNonNull(newStep); int existingIdx = getExistingStepIdx(newStep.getName()); if (existingIdx != -1) { - throw new GradleException("Multiple steps with name '" + newStep.getName() + "' for spotless format '" + formatName() + "'"); + throw new GradleException( + "Multiple steps with name '" + newStep.getName() + "' for spotless format '" + formatName() + "'"); } steps.add(newStep); } - /** Returns the index of the existing step with the given name, or -1 if no such step exists. */ + /** Adds a new step that requires a Provisioner. */ + public void addStep(Function createStepFn) { + requireNonNull(createStepFn); + FormatterStep newStep = createStepFn.apply(provisioner()); + addStep(newStep); + } + + /** + * Returns the index of the existing step with the given name, or -1 if no such + * step exists. + */ protected int getExistingStepIdx(String stepName) { for (int i = 0; i < steps.size(); ++i) { if (steps.get(i).getName().equals(stepName)) { @@ -312,7 +377,8 @@ protected int getExistingStepIdx(String stepName) { protected void replaceStep(FormatterStep replacementStep) { int existingIdx = getExistingStepIdx(replacementStep.getName()); if (existingIdx == -1) { - throw new GradleException("Cannot replace step '" + replacementStep.getName() + "' for spotless format '" + formatName() + "' because it hasn't been added yet."); + throw new GradleException("Cannot replace step '" + replacementStep.getName() + "' for spotless format '" + + formatName() + "' because it hasn't been added yet."); } steps.set(existingIdx, replacementStep); } @@ -323,20 +389,21 @@ public void clearSteps() { } /** - * An optional performance optimization if you are using any of the {@code custom} - * methods. If you aren't explicitly calling {@code custom}, then this method - * has no effect. - * - * Spotless tracks what files have changed from run to run, so that it can run faster - * by only checking files which have changed, or whose formatting steps have changed. - * If you use the {@code custom} methods, then gradle can never mark - * your files as {@code up-to-date}, because it can't know if perhaps the behavior of your - * custom function has changed. - * - * If you set {@code bumpThisNumberIfACustomStepChanges( )}, then spotless will - * assume that the custom rules have not changed if the number has not changed. If a - * custom rule does change, then you must bump the number so that spotless will know - * that it must recheck the files it has already checked. + * An optional performance optimization if you are using any of the + * {@code custom} methods. If you aren't explicitly calling {@code custom}, then + * this method has no effect. + *

+ * Spotless tracks what files have changed from run to run, so that it can run + * faster by only checking files which have changed, or whose formatting steps + * have changed. If you use the {@code custom} methods, then Gradle can never + * mark your files as {@code up-to-date}, because it can't know if perhaps the + * behavior of your custom function has changed. + *

+ * If you set {@code bumpThisNumberIfACustomStepChanges( )}, then + * spotless will assume that the custom rules have not changed if the number has + * not changed. If a custom rule does change, then you must bump the number so + * that spotless will know that it must recheck the files it has already + * checked. */ public void bumpThisNumberIfACustomStepChanges(int number) { globalState = number; @@ -354,15 +421,21 @@ protected Integer calculateState() throws Exception { } } - /** Adds a custom step. Receives a string with unix-newlines, must return a string with unix newlines. */ + /** + * Adds a custom step. Receives a string with unix-newlines, must return a + * string with unix newlines. + */ public void custom(String name, Closure formatter) { - Objects.requireNonNull(formatter, "formatter"); + requireNonNull(formatter, "formatter"); custom(name, formatter::call); } - /** Adds a custom step. Receives a string with unix-newlines, must return a string with unix newlines. */ + /** + * Adds a custom step. Receives a string with unix-newlines, must return a + * string with unix newlines. + */ public void custom(String name, FormatterFunc formatter) { - Objects.requireNonNull(formatter, "formatter"); + requireNonNull(formatter, "formatter"); addStep(FormatterStep.createLazy(name, () -> globalState, unusedState -> formatter)); } @@ -412,9 +485,11 @@ public void nativeCmd(String name, String pathToExe, List arguments) { } /** - * Created by {@link FormatExtension#licenseHeader(String, String)} or {@link FormatExtension#licenseHeaderFile(Object, String)}. - * For most language-specific formats (e.g. java, scala, etc.) you can omit the second {@code delimiter} argument, because it is supplied - * automatically ({@link HasBuiltinDelimiterForLicense}). + * Created by {@link FormatExtension#licenseHeader(String, String)} or + * {@link FormatExtension#licenseHeaderFile(Object, String)}. For most + * language-specific formats (e.g. java, scala, etc.) you can omit the second + * {@code delimiter} argument, because it is supplied automatically + * ({@link HasBuiltinDelimiterForLicense}). */ public class LicenseHeaderConfig { LicenseHeaderStep builder; @@ -443,8 +518,8 @@ public LicenseHeaderConfig(LicenseHeaderStep builder) { } /** - * @param delimiter - * Spotless will look for a line that starts with this regular expression pattern to know what the "top" is. + * @param delimiter Spotless will look for a line that starts with this regular + * expression pattern to know what the "top" is. */ public LicenseHeaderConfig delimiter(String delimiter) { builder = builder.withDelimiter(delimiter); @@ -453,8 +528,8 @@ public LicenseHeaderConfig delimiter(String delimiter) { } /** - * @param yearSeparator - * The characters used to separate the first and last years in multi years patterns. + * @param yearSeparator The characters used to separate the first and last years + * in multi years patterns. */ public LicenseHeaderConfig yearSeparator(String yearSeparator) { builder = builder.withYearSeparator(yearSeparator); @@ -462,10 +537,18 @@ public LicenseHeaderConfig yearSeparator(String yearSeparator) { return this; } + public LicenseHeaderConfig skipLinesMatching(String skipLinesMatching) { + builder = builder.withSkipLinesMatching(skipLinesMatching); + replaceStep(createStep()); + return this; + } + /** - * @param updateYearWithLatest - * Will turn {@code 2004} into {@code 2004-2020}, and {@code 2004-2019} into {@code 2004-2020} - * Default value is false, unless {@link SpotlessExtensionImpl#ratchetFrom(String)} is used, in which case default value is true. + * @param updateYearWithLatest Will turn {@code 2004} into {@code 2004-2020}, + * and {@code 2004-2019} into {@code 2004-2020} + * Default value is false, unless + * {@link SpotlessExtensionImpl#ratchetFrom(String)} + * is used, in which case default value is true. */ public LicenseHeaderConfig updateYearWithLatest(boolean updateYearWithLatest) { this.updateYearWithLatest = updateYearWithLatest; @@ -475,7 +558,8 @@ public LicenseHeaderConfig updateYearWithLatest(boolean updateYearWithLatest) { FormatterStep createStep() { return builder.withYearModeLazy(() -> { - if ("true".equals(spotless.project.findProperty(LicenseHeaderStep.FLAG_SET_LICENSE_HEADER_YEARS_FROM_GIT_HISTORY()))) { + if ("true".equals(spotless.project + .findProperty(LicenseHeaderStep.FLAG_SET_LICENSE_HEADER_YEARS_FROM_GIT_HISTORY()))) { return YearMode.SET_FROM_GIT; } else { boolean updateYear = updateYearWithLatest == null ? getRatchetFrom() != null : updateYearWithLatest; @@ -486,22 +570,22 @@ FormatterStep createStep() { } /** - * @param licenseHeader - * Content that should be at the top of every file. - * @param delimiter - * Spotless will look for a line that starts with this regular expression pattern to know what the "top" is. + * @param licenseHeader Content that should be at the top of every file. + * @param delimiter Spotless will look for a line that starts with this + * regular expression pattern to know what the "top" is. */ public LicenseHeaderConfig licenseHeader(String licenseHeader, String delimiter) { - LicenseHeaderConfig config = new LicenseHeaderConfig(LicenseHeaderStep.headerDelimiter(licenseHeader, delimiter)); + LicenseHeaderConfig config = new LicenseHeaderConfig( + LicenseHeaderStep.headerDelimiter(licenseHeader, delimiter)); addStep(config.createStep()); return config; } /** - * @param licenseHeaderFile - * Content that should be at the top of every file. - * @param delimiter - * Spotless will look for a line that starts with this regular expression pattern to know what the "top" is. + * @param licenseHeaderFile Content that should be at the top of every file. + * @param delimiter Spotless will look for a line that starts with this + * regular expression pattern to know what the "top" + * is. */ public LicenseHeaderConfig licenseHeaderFile(Object licenseHeaderFile, String delimiter) { LicenseHeaderConfig config = new LicenseHeaderConfig(LicenseHeaderStep.headerDelimiter(() -> { @@ -513,23 +597,61 @@ public LicenseHeaderConfig licenseHeaderFile(Object licenseHeaderFile, String de return config; } - public abstract class NpmStepConfig> { + public abstract static class NpmStepConfig> { + + public static final String SPOTLESS_NPM_INSTALL_CACHE_DEFAULT_NAME = "spotless-npm-install-cache"; + @Nullable protected Object npmFile; + @Nullable + protected Object nodeFile; + + @Nullable + protected Object npmInstallCache; + @Nullable protected Object npmrcFile; + protected Project project; + + private Consumer replaceStep; + + public NpmStepConfig(Project project, Consumer replaceStep) { + this.project = requireNonNull(project); + this.replaceStep = requireNonNull(replaceStep); + } + @SuppressWarnings("unchecked") public T npmExecutable(final Object npmFile) { this.npmFile = npmFile; - replaceStep(createStep()); + replaceStep(); + return (T) this; + } + + @SuppressWarnings("unchecked") + public T nodeExecutable(final Object nodeFile) { + this.nodeFile = nodeFile; + replaceStep(); return (T) this; } public T npmrc(final Object npmrcFile) { this.npmrcFile = npmrcFile; - replaceStep(createStep()); + replaceStep(); + return (T) this; + } + + public T npmInstallCache(final Object npmInstallCache) { + this.npmInstallCache = npmInstallCache; + replaceStep(); + return (T) this; + } + + public T npmInstallCache() { + this.npmInstallCache = new File(project.getLayout().getBuildDirectory().getAsFile().get(), + SPOTLESS_NPM_INSTALL_CACHE_DEFAULT_NAME); + replaceStep(); return (T) this; } @@ -537,15 +659,27 @@ File npmFileOrNull() { return fileOrNull(npmFile); } + File nodeFileOrNull() { + return fileOrNull(nodeFile); + } + File npmrcFileOrNull() { return fileOrNull(npmrcFile); } + File npmModulesCacheOrNull() { + return fileOrNull(npmInstallCache); + } + private File fileOrNull(Object npmFile) { - return npmFile != null ? getProject().file(npmFile) : null; + return npmFile != null ? project.file(npmFile) : null; + } + + protected void replaceStep() { + replaceStep.accept(createStep()); } - abstract FormatterStep createStep(); + abstract protected FormatterStep createStep(); } @@ -560,34 +694,150 @@ public class PrettierConfig extends NpmStepConfig { final Map devDependencies; PrettierConfig(Map devDependencies) { - this.devDependencies = Objects.requireNonNull(devDependencies); + super(getProject(), FormatExtension.this::replaceStep); + this.devDependencies = requireNonNull(devDependencies); } public PrettierConfig configFile(final Object prettierConfigFile) { this.prettierConfigFile = prettierConfigFile; - replaceStep(createStep()); + replaceStep(); return this; } public PrettierConfig config(final Map prettierConfig) { this.prettierConfig = new TreeMap<>(prettierConfig); - replaceStep(createStep()); + replaceStep(); return this; } - FormatterStep createStep() { + @Override + protected FormatterStep createStep() { final Project project = getProject(); - return PrettierFormatterStep.create( - devDependencies, - provisioner(), - project.getBuildDir(), - new NpmPathResolver(npmFileOrNull(), npmrcFileOrNull(), project.getProjectDir(), project.getRootDir()), + return PrettierFormatterStep.create(devDependencies, provisioner(), project.getProjectDir(), + project.getLayout().getBuildDirectory().getAsFile().get(), npmModulesCacheOrNull(), + new NpmPathResolver(npmFileOrNull(), nodeFileOrNull(), npmrcFileOrNull(), + Arrays.asList(project.getProjectDir(), project.getRootDir())), new com.diffplug.spotless.npm.PrettierConfig( this.prettierConfigFile != null ? project.file(this.prettierConfigFile) : null, this.prettierConfig)); } } + /** + * Generic Biome formatter step that detects the language of the input file from + * the file name. It should be specified as a formatter step for a generic + * format{ ... }. + */ + public class BiomeGeneric extends RomeStepConfig { + @Nullable + String language; + + /** + * Creates a new Rome config that downloads the Rome executable for the given + * version from the network. + * + * @param version Rome version to use. The default version is used when + * null. + */ + public BiomeGeneric(String version) { + super(getProject(), FormatExtension.this::replaceStep, BiomeFlavor.BIOME, version); + } + + /** + * Sets the language (syntax) of the input files to format. When + * null or the empty string, the language is detected automatically + * from the file name. Currently the following languages are supported by Biome: + *

    + *
  • js (JavaScript)
  • + *
  • jsx (JavaScript + JSX)
  • + *
  • js? (JavaScript or JavaScript + JSX, depending on the file + * extension)
  • + *
  • ts (TypeScript)
  • + *
  • tsx (TypeScript + JSX)
  • + *
  • ts? (TypeScript or TypeScript + JSX, depending on the file + * extension)
  • + *
  • json (JSON)
  • + *
+ * + * @param language The language of the files to format. + * @return This step for further configuration. + */ + public BiomeGeneric language(String language) { + this.language = language; + replaceStep(); + return this; + } + + @Override + protected String getLanguage() { + return language; + } + + @Override + protected BiomeGeneric getThis() { + return this; + } + } + + /** + * Generic Rome formatter step that detects the language of the input file from + * the file name. It should be specified as a formatter step for a generic + * format{ ... }. + * + * @deprecated Rome has transitioned to Biome. This will be removed shortly. + */ + @Deprecated + public class RomeGeneric extends RomeStepConfig { + @Nullable + String language; + + /** + * Creates a new Rome config that downloads the Rome executable for the given + * version from the network. + * + * @param version Rome version to use. The default version is used when + * null. + */ + public RomeGeneric(String version) { + super(getProject(), FormatExtension.this::replaceStep, BiomeFlavor.ROME, version); + } + + /** + * Sets the language (syntax) of the input files to format. When + * null or the empty string, the language is detected automatically + * from the file name. Currently the following languages are supported by Rome: + *
    + *
  • js (JavaScript)
  • + *
  • jsx (JavaScript + JSX)
  • + *
  • js? (JavaScript or JavaScript + JSX, depending on the file + * extension)
  • + *
  • ts (TypeScript)
  • + *
  • tsx (TypeScript + JSX)
  • + *
  • ts? (TypeScript or TypeScript + JSX, depending on the file + * extension)
  • + *
  • json (JSON)
  • + *
+ * + * @param language The language of the files to format. + * @return This step for further configuration. + */ + public RomeGeneric language(String language) { + this.language = language; + replaceStep(); + return this; + } + + @Override + protected String getLanguage() { + return language; + } + + @Override + protected RomeGeneric getThis() { + return this; + } + } + /** Uses the default version of prettier. */ public PrettierConfig prettier() { return prettier(PrettierFormatterStep.defaultDevDependencies()); @@ -605,6 +855,46 @@ public PrettierConfig prettier(Map devDependencies) { return prettierConfig; } + /** + * Defaults to downloading the default Biome version from the network. To work + * offline, you can specify the path to the Biome executable via + * {@code biome().pathToExe(...)}. + */ + public RomeStepConfig biome() { + return biome(null); + } + + /** Downloads the given Biome version from the network. */ + public RomeStepConfig biome(String version) { + var biomeConfig = new BiomeGeneric(version); + addStep(biomeConfig.createStep()); + return biomeConfig; + } + + /** + * Defaults to downloading the default Rome version from the network. To work + * offline, you can specify the path to the Rome executable via + * {@code rome().pathToExe(...)}. + * + * @deprecated Use {@link #biome(String)}. + */ + @Deprecated + public RomeStepConfig rome() { + return rome(null); + } + + /** + * Downloads the given Rome version from the network. + * + * @deprecated Use {@link #biome(String)}. + */ + @Deprecated + public RomeStepConfig rome(String version) { + var romeConfig = new RomeGeneric(version); + addStep(romeConfig.createStep()); + return romeConfig; + } + /** Uses the default version of clang-format. */ public ClangFormatConfig clangFormat() { return clangFormat(ClangFormatStep.defaultVersion()); @@ -682,8 +972,9 @@ public void withinBlocks(String name, String open, String close, Action * spotless { @@ -696,33 +987,44 @@ public void withinBlocks(String name, String open, String close, Action */ - public void withinBlocks(String name, String open, String close, Class clazz, Action configure) { + public void withinBlocks(String name, String open, String close, Class clazz, + Action configure) { withinBlocksHelper(PipeStepPair.named(name).openClose(open, close), clazz, configure); } - /** Same as {@link #withinBlocks(String, String, String, Action)}, except instead of an open/close pair, you specify a regex with exactly one capturing group. */ + /** + * Same as {@link #withinBlocks(String, String, String, Action)}, except instead + * of an open/close pair, you specify a regex with exactly one capturing group. + */ public void withinBlocksRegex(String name, String regex, Action configure) { withinBlocksRegex(name, regex, FormatExtension.class, configure); } - /** Same as {@link #withinBlocksRegex(String, String, Action)}, except you can specify any language-specific subclass of {@link FormatExtension} to get language-specific steps. */ - public void withinBlocksRegex(String name, String regex, Class clazz, Action configure) { + /** + * Same as {@link #withinBlocksRegex(String, String, Action)}, except you can + * specify any language-specific subclass of {@link FormatExtension} to get + * language-specific steps. + */ + public void withinBlocksRegex(String name, String regex, Class clazz, + Action configure) { withinBlocksHelper(PipeStepPair.named(name).regex(regex), clazz, configure); } - private void withinBlocksHelper(PipeStepPair.Builder builder, Class clazz, Action configure) { + private void withinBlocksHelper(PipeStepPair.Builder builder, Class clazz, + Action configure) { // create the sub-extension T formatExtension = spotless.instantiateFormatExtension(clazz); // configure it configure.execute(formatExtension); // create a step which applies all of those steps as sub-steps - FormatterStep step = builder.buildStepWhichAppliesSubSteps(spotless.project.getRootDir().toPath(), formatExtension.steps); + FormatterStep step = builder.buildStepWhichAppliesSubSteps(spotless.project.getRootDir().toPath(), + formatExtension.steps); addStep(step); } /** - * Given a regex with *exactly one capturing group*, disables formatting - * inside that captured group. + * Given a regex with *exactly one capturing group*, disables formatting inside + * that captured group. */ public void toggleOffOnRegex(String regex) { this.togglePair = PipeStepPair.named(PipeStepPair.defaultToggleName()).regex(regex).buildPair(); @@ -738,7 +1040,10 @@ public void toggleOffOn() { toggleOffOn(PipeStepPair.defaultToggleOff(), PipeStepPair.defaultToggleOn()); } - /** Undoes all previous calls to {@link #toggleOffOn()} and {@link #toggleOffOn(String, String)}. */ + /** + * Undoes all previous calls to {@link #toggleOffOn()} and + * {@link #toggleOffOn(String, String)}. + */ public void toggleOffOnDisable() { this.togglePair = null; } @@ -760,8 +1065,14 @@ protected void setupTask(SpotlessTask task) { } else { steps = this.steps; } + if (targetExcludeContentPattern != null) { + steps.replaceAll( + formatterStep -> formatterStep.filterByContent(OnMatch.EXCLUDE, targetExcludeContentPattern)); + } task.setSteps(steps); - task.setLineEndingsPolicy(getLineEndings().createPolicy(getProject().getProjectDir(), () -> totalTarget)); + Directory projectDir = getProject().getLayout().getProjectDirectory(); + task.setLineEndingsPolicy( + getProject().provider(() -> getLineEndings().createPolicy(projectDir.getAsFile(), () -> totalTarget))); spotless.getRegisterDependenciesTask().hookSubprojectTask(task); task.setupRatchet(getRatchetFrom() != null ? getRatchetFrom() : ""); } @@ -777,31 +1088,38 @@ public SpotlessApply createIndependentApplyTask(String taskName) { } /** - * Creates an independent {@link SpotlessApply} for (very) unusual circumstances. - * - * Most users will not want this method. In the rare case that you want to create - * a {@code SpotlessApply} which is independent of the normal Spotless machinery, this will - * let you do that. - * - * The returned task will not be hooked up to the global {@code spotlessApply}, and there will be no corresponding {@code check} task. - * + * Creates an independent {@link SpotlessApply} for (very) unusual + * circumstances. + *

+ * Most users will not want this method. In the rare case that you want to + * create a {@code SpotlessApply} which is independent of the normal Spotless + * machinery, this will let you do that. + *

+ * The returned task will not be hooked up to the global {@code spotlessApply}, + * and there will be no corresponding {@code check} task. + *

* The task name must not end with `Apply`. - * - * NOTE: does not respect the rarely-used {@code spotlessFiles} property. + *

+ * NOTE: does not respect the rarely-used {@code spotlessFiles} + * property. */ public TaskProvider createIndependentApplyTaskLazy(String taskName) { - Preconditions.checkArgument(!taskName.endsWith(SpotlessExtension.APPLY), "Task name must not end with " + SpotlessExtension.APPLY); - TaskProvider spotlessTask = spotless.project.getTasks().register(taskName + SpotlessTaskService.INDEPENDENT_HELPER, SpotlessTaskImpl.class, task -> { - task.init(spotless.getRegisterDependenciesTask().getTaskService()); - setupTask(task); - // clean removes the SpotlessCache, so we have to run after clean - task.mustRunAfter(BasePlugin.CLEAN_TASK_NAME); - }); + Preconditions.checkArgument(!taskName.endsWith(SpotlessExtension.APPLY), + "Task name must not end with " + SpotlessExtension.APPLY); + TaskProvider spotlessTask = spotless.project.getTasks() + .register(taskName + SpotlessTaskService.INDEPENDENT_HELPER, SpotlessTaskImpl.class, task -> { + task.init(spotless.getRegisterDependenciesTask().getTaskService()); + setupTask(task); + // clean removes the SpotlessCache, so we have to run after clean + task.mustRunAfter(BasePlugin.CLEAN_TASK_NAME); + }); // create the apply task - TaskProvider applyTask = spotless.project.getTasks().register(taskName, SpotlessApply.class, task -> { - task.dependsOn(spotlessTask); - task.init(spotlessTask.get()); - }); + TaskProvider applyTask = spotless.project.getTasks().register(taskName, SpotlessApply.class, + task -> { + task.dependsOn(spotlessTask); + task.init(spotlessTask.get()); + }); return applyTask; } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GherkinExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GherkinExtension.java new file mode 100644 index 0000000000..6305289818 --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GherkinExtension.java @@ -0,0 +1,63 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import javax.inject.Inject; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.gherkin.GherkinUtilsStep; + +public class GherkinExtension extends FormatExtension { + static final String NAME = "gherkin"; + + @Inject + public GherkinExtension(SpotlessExtension spotless) { + super(spotless); + } + + @Override + protected void setupTask(SpotlessTask task) { + if (target == null) { + throw noDefaultTargetException(); + } + super.setupTask(task); + } + + public GherkinUtilsConfig gherkinUtils() { + return new GherkinUtilsConfig(); + } + + public class GherkinUtilsConfig { + private String version; + private int indent; + + public GherkinUtilsConfig() { + this.version = GherkinUtilsStep.defaultVersion(); + this.indent = com.diffplug.spotless.gherkin.GherkinUtilsConfig.defaultIndentSpaces(); + addStep(createStep()); + } + + public void version(String version) { + this.version = version; + replaceStep(createStep()); + } + + private FormatterStep createStep() { + return GherkinUtilsStep.create(new com.diffplug.spotless.gherkin.GherkinUtilsConfig(indent), version, provisioner()); + } + } + +} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GitRatchetGradle.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GitRatchetGradle.java index 8c4f88e7da..b2c3adc459 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GitRatchetGradle.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GitRatchetGradle.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,10 +19,33 @@ import javax.annotation.Nullable; +import org.eclipse.jgit.lib.Config; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.SystemReader; + import com.diffplug.spotless.extra.GitRatchet; /** Gradle implementation of GitRatchet. */ public class GitRatchetGradle extends GitRatchet { + static { + preventJGitFromCallingExecutables(); + } + + static void preventJGitFromCallingExecutables() { + SystemReader reader = SystemReader.getInstance(); + SystemReader.setInstance(new DelegatingSystemReader(reader) { + @Override + public String getenv(String variable) { + if ("PATH".equals(variable)) { + return ""; + } else { + return super.getenv(variable); + } + } + }); + } + @Override protected File getDir(File project) { return project; @@ -32,4 +55,52 @@ protected File getDir(File project) { protected @Nullable File getParent(File project) { return project.getParentFile(); } + + static class DelegatingSystemReader extends SystemReader { + final SystemReader reader; + + DelegatingSystemReader(SystemReader reader) { + this.reader = reader; + } + + @Override + public String getHostname() { + return reader.getHostname(); + } + + @Override + public String getenv(String variable) { + return reader.getProperty(variable); + } + + @Override + public String getProperty(String key) { + return reader.getProperty(key); + } + + @Override + public FileBasedConfig openUserConfig(Config parent, FS fs) { + return reader.openUserConfig(parent, fs); + } + + @Override + public FileBasedConfig openSystemConfig(Config parent, FS fs) { + return reader.openSystemConfig(parent, fs); + } + + @Override + public FileBasedConfig openJGitConfig(Config parent, FS fs) { + return reader.openJGitConfig(parent, fs); + } + + @Override + public long getCurrentTime() { + return reader.getCurrentTime(); + } + + @Override + public int getTimezone(long when) { + return reader.getTimezone(when); + } + } } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GoExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GoExtension.java new file mode 100644 index 0000000000..145e90c19a --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GoExtension.java @@ -0,0 +1,57 @@ +/* + * Copyright 2024 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import javax.inject.Inject; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.go.GofmtFormatStep; + +public class GoExtension extends FormatExtension { + public static final String NAME = "go"; + + @Inject + public GoExtension(SpotlessExtension spotless) { + super(spotless); + } + + public GofmtConfig gofmt() { + return new GofmtConfig(GofmtFormatStep.defaultVersion()); + } + + public GofmtConfig gofmt(String version) { + return new GofmtConfig(version); + } + + public class GofmtConfig { + GofmtFormatStep stepCfg; + + public GofmtConfig(String version) { + stepCfg = GofmtFormatStep.withVersion(version); + addStep(createStep()); + } + + public GofmtConfig withGoExecutable(String pathToGo) { + stepCfg = stepCfg.withGoExecutable(pathToGo); + replaceStep(createStep()); + return this; + } + + private FormatterStep createStep() { + return stepCfg.create(); + } + } +} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GradleProvisioner.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GradleProvisioner.java index 97f051cd86..5d5c07f4fe 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GradleProvisioner.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GradleProvisioner.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,9 @@ import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.dsl.DependencyHandler; +import org.gradle.api.attributes.Attribute; import org.gradle.api.attributes.Bundling; +import org.gradle.api.attributes.Category; import org.gradle.api.initialization.dsl.ScriptHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -118,8 +120,14 @@ private static Provisioner forConfigurationContainer(Project project, Configurat .forEach(config.getDependencies()::add); config.setDescription(mavenCoords.toString()); config.setTransitive(withTransitives); + config.setCanBeConsumed(false); + config.setVisible(false); config.attributes(attr -> { + attr.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); attr.attribute(Bundling.BUNDLING_ATTRIBUTE, project.getObjects().named(Bundling.class, Bundling.EXTERNAL)); + // TODO: This is a copy-paste from org.gradle.api.attributes.java.TargetJvmEnvironment which is added in Gradle 7.0, remove this once we drop support for Gradle 6.x. + // Add this attribute for resolving Guava dependency, see https://github.com/google/guava/issues/6801. + attr.attribute(Attribute.of("org.gradle.jvm.environment", String.class), "standard-jvm"); }); return config.resolve(); } catch (Exception e) { diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyExtension.java index 5e528e1b79..676bed0aea 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,27 +15,19 @@ */ package com.diffplug.gradle.spotless; -import static com.diffplug.gradle.spotless.PluginGradlePreconditions.requireElementsNonNull; - -import java.util.Objects; - import javax.inject.Inject; import org.gradle.api.GradleException; -import org.gradle.api.Project; -import org.gradle.api.file.FileCollection; import org.gradle.api.internal.plugins.DslObject; import org.gradle.api.plugins.GroovyBasePlugin; -import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.tasks.GroovySourceDirectorySet; import org.gradle.api.tasks.GroovySourceSet; -import org.gradle.api.tasks.SourceSet; +import org.gradle.util.GradleVersion; -import com.diffplug.spotless.extra.EclipseBasedStepBuilder; -import com.diffplug.spotless.extra.groovy.GrEclipseFormatterStep; import com.diffplug.spotless.generic.LicenseHeaderStep; -import com.diffplug.spotless.java.ImportOrderStep; -public class GroovyExtension extends FormatExtension implements HasBuiltinDelimiterForLicense { +public class GroovyExtension extends BaseGroovyExtension implements HasBuiltinDelimiterForLicense, JvmLang { + private boolean excludeJava = false; static final String NAME = "groovy"; @Inject @@ -43,8 +35,6 @@ public GroovyExtension(SpotlessExtension spotless) { super(spotless); } - boolean excludeJava = false; - /** Excludes .java files, to focus on only .groovy files. */ public void excludeJava() { excludeJava(true); @@ -65,62 +55,32 @@ public LicenseHeaderConfig licenseHeaderFile(Object licenseHeaderFile) { return licenseHeaderFile(licenseHeaderFile, JavaExtension.LICENSE_HEADER_DELIMITER); } - public void importOrder(String... importOrder) { - addStep(ImportOrderStep.forGroovy().createFrom(importOrder)); - } - - public void importOrderFile(Object importOrderFile) { - Objects.requireNonNull(importOrderFile); - addStep(ImportOrderStep.forGroovy().createFrom(getProject().file(importOrderFile))); - } - - public GrEclipseConfig greclipse() { - return greclipse(GrEclipseFormatterStep.defaultVersion()); - } - - public GrEclipseConfig greclipse(String version) { - return new GrEclipseConfig(version, this); - } - - public static class GrEclipseConfig { - private final EclipseBasedStepBuilder builder; - private final FormatExtension extension; - - GrEclipseConfig(String version, FormatExtension extension) { - this.extension = extension; - builder = GrEclipseFormatterStep.createBuilder(extension.provisioner()); - builder.setVersion(version); - extension.addStep(builder.build()); - } - - public void configFile(Object... configFiles) { - requireElementsNonNull(configFiles); - Project project = extension.getProject(); - builder.setPreferences(project.files(configFiles).getFiles()); - extension.replaceStep(builder.build()); - } - } - /** If the user hasn't specified the files yet, we'll assume he/she means all of the groovy files. */ @Override protected void setupTask(SpotlessTask task) { if (target == null) { - JavaPluginConvention convention = getProject().getConvention().getPlugin(JavaPluginConvention.class); - if (convention == null || !getProject().getPlugins().hasPlugin(GroovyBasePlugin.class)) { - throw new GradleException("You must apply the groovy plugin before the spotless plugin if you are using the groovy extension."); - } - //Add all Groovy files (may contain Java files as well) - - FileCollection union = getProject().files(); - for (SourceSet sourceSet : convention.getSourceSets()) { - GroovySourceSet groovySourceSet = new DslObject(sourceSet).getConvention().getPlugin(GroovySourceSet.class); - if (excludeJava) { - union = union.plus(groovySourceSet.getAllGroovy()); - } else { - union = union.plus(groovySourceSet.getGroovy()); - } + final String message = "You must either specify 'target' manually or apply the 'groovy' plugin."; + if (!getProject().getPlugins().hasPlugin(GroovyBasePlugin.class)) { + throw new GradleException(message); } - target = union; + target = getSources(getProject(), + message, + sourceSet -> { + if (GradleVersion.current().compareTo(GradleVersion.version(SpotlessPlugin.VER_GRADLE_javaPluginExtension)) >= 0) { + return sourceSet.getExtensions().getByType(GroovySourceDirectorySet.class); + } else { + final GroovySourceSet groovySourceSet = new DslObject(sourceSet).getConvention().getPlugin(GroovySourceSet.class); + return groovySourceSet.getGroovy(); + } + }, + file -> { + final String name = file.getName(); + if (excludeJava) { + return name.endsWith(".groovy"); + } else { + return name.endsWith(".groovy") || name.endsWith(".java"); + } + }); } else if (excludeJava) { throw new IllegalArgumentException("'excludeJava' is not supported in combination with a custom 'target'."); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyGradleExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyGradleExtension.java index 30cb75a2a3..120f99f27a 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyGradleExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyGradleExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,14 +15,9 @@ */ package com.diffplug.gradle.spotless; -import java.util.Objects; - import javax.inject.Inject; -import com.diffplug.spotless.extra.groovy.GrEclipseFormatterStep; -import com.diffplug.spotless.java.ImportOrderStep; - -public class GroovyGradleExtension extends FormatExtension { +public class GroovyGradleExtension extends BaseGroovyExtension { private static final String GRADLE_FILE_EXTENSION = "*.gradle"; static final String NAME = "groovyGradle"; @@ -31,23 +26,6 @@ public GroovyGradleExtension(SpotlessExtension spotless) { super(spotless); } - public void importOrder(String... importOrder) { - addStep(ImportOrderStep.forGroovy().createFrom(importOrder)); - } - - public void importOrderFile(Object importOrderFile) { - Objects.requireNonNull(importOrderFile); - addStep(ImportOrderStep.forGroovy().createFrom(getProject().file(importOrderFile))); - } - - public GroovyExtension.GrEclipseConfig greclipse() { - return new GroovyExtension.GrEclipseConfig(GrEclipseFormatterStep.defaultVersion(), this); - } - - public GroovyExtension.GrEclipseConfig greclipse(String version) { - return new GroovyExtension.GrEclipseConfig(version, this); - } - @Override protected void setupTask(SpotlessTask task) { if (target == null) { diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java index 0f5926ec66..db869dc4e6 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,28 +19,31 @@ import java.io.File; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Set; import javax.inject.Inject; -import org.gradle.api.GradleException; import org.gradle.api.Project; -import org.gradle.api.file.FileCollection; -import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.SourceSet; import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.extra.EclipseBasedStepBuilder; +import com.diffplug.spotless.extra.EquoBasedStepBuilder; import com.diffplug.spotless.extra.java.EclipseJdtFormatterStep; import com.diffplug.spotless.generic.LicenseHeaderStep; +import com.diffplug.spotless.java.CleanthatJavaStep; import com.diffplug.spotless.java.FormatAnnotationsStep; import com.diffplug.spotless.java.GoogleJavaFormatStep; import com.diffplug.spotless.java.ImportOrderStep; import com.diffplug.spotless.java.PalantirJavaFormatStep; import com.diffplug.spotless.java.RemoveUnusedImportsStep; -public class JavaExtension extends FormatExtension implements HasBuiltinDelimiterForLicense { +public class JavaExtension extends FormatExtension implements HasBuiltinDelimiterForLicense, JvmLang { static final String NAME = "java"; @Inject @@ -48,9 +51,7 @@ public JavaExtension(SpotlessExtension spotless) { super(spotless); } - // If this constant changes, don't forget to change the similarly-named one in - // testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java as well - static final String LICENSE_HEADER_DELIMITER = "package "; + static final String LICENSE_HEADER_DELIMITER = LicenseHeaderStep.DEFAULT_JAVA_HEADER_DELIMITER; @Override public LicenseHeaderConfig licenseHeader(String licenseHeader) { @@ -76,6 +77,9 @@ public class ImportOrderConfig { final File importOrderFile; boolean wildcardsLast = false; + boolean semanticSort = false; + Set treatAsPackage = Set.of(); + Set treatAsClass = Set.of(); ImportOrderConfig(String[] importOrder) { this.importOrder = importOrder; @@ -100,18 +104,52 @@ public ImportOrderConfig wildcardsLast(boolean wildcardsLast) { return this; } + public ImportOrderConfig semanticSort() { + return semanticSort(true); + } + + public ImportOrderConfig semanticSort(boolean semanticSort) { + this.semanticSort = semanticSort; + replaceStep(createStep()); + return this; + } + + public ImportOrderConfig treatAsPackage(String... treatAsPackage) { + return treatAsPackage(Arrays.asList(treatAsPackage)); + } + + public ImportOrderConfig treatAsPackage(Collection treatAsPackage) { + this.treatAsPackage = new HashSet<>(treatAsPackage); + replaceStep(createStep()); + return this; + } + + public ImportOrderConfig treatAsClass(String... treatAsClass) { + return treatAsClass(Arrays.asList(treatAsClass)); + } + + public ImportOrderConfig treatAsClass(Collection treatAsClass) { + this.treatAsClass = new HashSet<>(treatAsClass); + replaceStep(createStep()); + return this; + } + private FormatterStep createStep() { ImportOrderStep importOrderStep = ImportOrderStep.forJava(); return importOrderFile != null - ? importOrderStep.createFrom(wildcardsLast, getProject().file(importOrderFile)) - : importOrderStep.createFrom(wildcardsLast, importOrder); + ? importOrderStep.createFrom(wildcardsLast, semanticSort, treatAsPackage, treatAsClass, getProject().file(importOrderFile)) + : importOrderStep.createFrom(wildcardsLast, semanticSort, treatAsPackage, treatAsClass, importOrder); } } /** Removes any unused imports. */ public void removeUnusedImports() { - addStep(RemoveUnusedImportsStep.create(provisioner())); + addStep(RemoveUnusedImportsStep.create(RemoveUnusedImportsStep.defaultFormatter(), provisioner())); + } + + public void removeUnusedImports(String formatter) { + addStep(RemoveUnusedImportsStep.create(formatter, provisioner())); } /** Uses the google-java-format jar to format source code. */ @@ -121,7 +159,7 @@ public GoogleJavaFormatConfig googleJavaFormat() { /** * Uses the given version of google-java-format to format source code. - * + *

* Limited to published versions. See issue #33 * for a workaround for using snapshot versions. */ @@ -135,6 +173,8 @@ public class GoogleJavaFormatConfig { String groupArtifact; String style; boolean reflowLongStrings; + boolean reorderImports; + boolean formatJavadoc = true; GoogleJavaFormatConfig(String version) { this.version = Objects.requireNonNull(version); @@ -169,13 +209,31 @@ public GoogleJavaFormatConfig reflowLongStrings(boolean reflowLongStrings) { return this; } + public GoogleJavaFormatConfig reorderImports(boolean reorderImports) { + this.reorderImports = reorderImports; + replaceStep(createStep()); + return this; + } + + public GoogleJavaFormatConfig skipJavadocFormatting() { + return formatJavadoc(false); + } + + public GoogleJavaFormatConfig formatJavadoc(boolean formatJavadoc) { + this.formatJavadoc = formatJavadoc; + replaceStep(createStep()); + return this; + } + private FormatterStep createStep() { return GoogleJavaFormatStep.create( groupArtifact, version, style, provisioner(), - reflowLongStrings); + reflowLongStrings, + reorderImports, + formatJavadoc); } } @@ -186,7 +244,7 @@ public PalantirJavaFormatConfig palantirJavaFormat() { /** * Uses the given version of palantir-java-format to format source code. - * + *

* Limited to published versions. See issue #33 * for a workaround for using snapshot versions. */ @@ -197,14 +255,29 @@ public PalantirJavaFormatConfig palantirJavaFormat(String version) { public class PalantirJavaFormatConfig { final String version; + String style; + boolean formatJavadoc; PalantirJavaFormatConfig(String version) { this.version = Objects.requireNonNull(version); + this.style = PalantirJavaFormatStep.defaultStyle(); addStep(createStep()); } + public PalantirJavaFormatConfig style(String style) { + this.style = Objects.requireNonNull(style); + replaceStep(createStep()); + return this; + } + + public PalantirJavaFormatConfig formatJavadoc(boolean formatJavadoc) { + this.formatJavadoc = formatJavadoc; + replaceStep(createStep()); + return this; + } + private FormatterStep createStep() { - return PalantirJavaFormatStep.create(version, provisioner()); + return PalantirJavaFormatStep.create(version, style, formatJavadoc, provisioner()); } } @@ -217,7 +290,7 @@ public EclipseConfig eclipse(String version) { } public class EclipseConfig { - private final EclipseBasedStepBuilder builder; + private final EquoBasedStepBuilder builder; EclipseConfig(String version) { builder = EclipseJdtFormatterStep.createBuilder(provisioner()); @@ -232,6 +305,12 @@ public void configFile(Object... configFiles) { replaceStep(builder.build()); } + public EclipseConfig withP2Mirrors(Map mirrors) { + builder.setP2Mirrors(mirrors); + replaceStep(builder.build()); + return this; + } + } /** Removes newlines between type annotations and types. */ @@ -270,19 +349,99 @@ private FormatterStep createStep() { } } + /** Apply CleanThat refactoring rules. */ + public CleanthatJavaConfig cleanthat() { + return new CleanthatJavaConfig(); + } + + public class CleanthatJavaConfig { + private String groupArtifact = CleanthatJavaStep.defaultGroupArtifact(); + + private String version = CleanthatJavaStep.defaultVersion(); + + private String sourceJdk = CleanthatJavaStep.defaultSourceJdk(); + + private List mutators = new ArrayList<>(CleanthatJavaStep.defaultMutators()); + + private List excludedMutators = new ArrayList<>(CleanthatJavaStep.defaultExcludedMutators()); + + private boolean includeDraft = CleanthatJavaStep.defaultIncludeDraft(); + + CleanthatJavaConfig() { + addStep(createStep()); + } + + public CleanthatJavaConfig groupArtifact(String groupArtifact) { + Objects.requireNonNull(groupArtifact); + this.groupArtifact = groupArtifact; + replaceStep(createStep()); + return this; + } + + public CleanthatJavaConfig version(String version) { + Objects.requireNonNull(version); + this.version = version; + replaceStep(createStep()); + return this; + } + + public CleanthatJavaConfig sourceCompatibility(String jdkVersion) { + Objects.requireNonNull(jdkVersion); + this.sourceJdk = jdkVersion; + replaceStep(createStep()); + return this; + } + + // Especially useful to clear default mutators + public CleanthatJavaConfig clearMutators() { + this.mutators.clear(); + replaceStep(createStep()); + return this; + } + + // An id of a mutator (see IMutator.getIds()) or + // tThe fully qualified name of a class implementing eu.solven.cleanthat.engine.java.refactorer.meta.IMutator + public CleanthatJavaConfig addMutator(String mutator) { + this.mutators.add(mutator); + replaceStep(createStep()); + return this; + } + + public CleanthatJavaConfig addMutators(Collection mutators) { + this.mutators.addAll(mutators); + replaceStep(createStep()); + return this; + } + + // useful to exclude a mutator amongst the default list of mutators + public CleanthatJavaConfig excludeMutator(String mutator) { + this.excludedMutators.add(mutator); + replaceStep(createStep()); + return this; + } + + public CleanthatJavaConfig includeDraft(boolean includeDraft) { + this.includeDraft = includeDraft; + replaceStep(createStep()); + return this; + } + + private FormatterStep createStep() { + return CleanthatJavaStep.create( + groupArtifact, + version, + sourceJdk, mutators, excludedMutators, includeDraft, provisioner()); + } + } + /** If the user hasn't specified the files yet, we'll assume he/she means all of the java files. */ @Override protected void setupTask(SpotlessTask task) { if (target == null) { - JavaPluginConvention javaPlugin = getProject().getConvention().findPlugin(JavaPluginConvention.class); - if (javaPlugin == null) { - throw new GradleException("You must either specify 'target' manually or apply the 'java' plugin."); - } - FileCollection union = getProject().files(); - for (SourceSet sourceSet : javaPlugin.getSourceSets()) { - union = union.plus(sourceSet.getAllJava()); - } - target = union; + target = getSources(getProject(), + "You must either specify 'target' manually or apply the 'java' plugin.", + SourceSet::getAllJava, + file -> file.getName().endsWith(".java")); } steps.replaceAll(step -> { diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavascriptExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavascriptExtension.java new file mode 100644 index 0000000000..b14bd29ab8 --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavascriptExtension.java @@ -0,0 +1,280 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import static java.util.Objects.requireNonNull; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Consumer; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import org.gradle.api.Project; + +import com.diffplug.common.collect.ImmutableList; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.npm.EslintConfig; +import com.diffplug.spotless.npm.EslintFormatterStep; +import com.diffplug.spotless.npm.NpmPathResolver; +import com.diffplug.spotless.npm.PrettierFormatterStep; +import com.diffplug.spotless.rome.BiomeFlavor; + +public class JavascriptExtension extends FormatExtension { + + static final String NAME = "javascript"; + + @Inject + public JavascriptExtension(SpotlessExtension spotless) { + super(spotless); + } + + public JavascriptEslintConfig eslint() { + return eslint(EslintFormatterStep.defaultDevDependenciesForTypescript()); + } + + public JavascriptEslintConfig eslint(String version) { + return eslint(EslintFormatterStep.defaultDevDependenciesTypescriptWithEslint(version)); + } + + public JavascriptEslintConfig eslint(Map devDependencies) { + JavascriptEslintConfig eslint = new JavascriptEslintConfig(devDependencies); + addStep(eslint.createStep()); + return eslint; + } + + public static abstract class EslintBaseConfig> + extends NpmStepConfig> { + Map devDependencies = new LinkedHashMap<>(); + + @Nullable + Object configFilePath = null; + + @Nullable + String configJs = null; + + public EslintBaseConfig(Project project, Consumer replaceStep, + Map devDependencies) { + super(project, replaceStep); + this.devDependencies.putAll(requireNonNull(devDependencies)); + } + + @SuppressWarnings("unchecked") + protected T devDependencies(Map devDependencies) { + this.devDependencies.putAll(devDependencies); + replaceStep(); + return (T) this; + } + + @SuppressWarnings("unchecked") + public T configJs(String configJs) { + this.configJs = requireNonNull(configJs); + replaceStep(); + return (T) this; + } + + @SuppressWarnings("unchecked") + public T configFile(Object configFilePath) { + this.configFilePath = requireNonNull(configFilePath); + replaceStep(); + return (T) this; + } + } + + public class JavascriptEslintConfig extends EslintBaseConfig { + + public JavascriptEslintConfig(Map devDependencies) { + super(getProject(), JavascriptExtension.this::replaceStep, devDependencies); + } + + public FormatterStep createStep() { + final Project project = getProject(); + + return EslintFormatterStep.create(devDependencies, provisioner(), project.getProjectDir(), + project.getLayout().getBuildDirectory().getAsFile().get(), npmModulesCacheOrNull(), + new NpmPathResolver(npmFileOrNull(), nodeFileOrNull(), npmrcFileOrNull(), + Arrays.asList(project.getProjectDir(), project.getRootDir())), + eslintConfig()); + } + + protected EslintConfig eslintConfig() { + return new EslintConfig(configFilePath != null ? getProject().file(configFilePath) : null, configJs); + } + } + + /** Uses the default version of prettier. */ + @Override + public PrettierConfig prettier() { + return prettier(PrettierFormatterStep.defaultDevDependencies()); + } + + /** Uses the specified version of prettier. */ + @Override + public PrettierConfig prettier(String version) { + return prettier(PrettierFormatterStep.defaultDevDependenciesWithPrettier(version)); + } + + /** Uses exactly the npm packages specified in the map. */ + @Override + public PrettierConfig prettier(Map devDependencies) { + PrettierConfig prettierConfig = new JavascriptPrettierConfig(devDependencies); + addStep(prettierConfig.createStep()); + return prettierConfig; + } + + /** + * Defaults to downloading the default Biome version from the network. To work + * offline, you can specify the path to the Biome executable via + * {@code biome().pathToExe(...)}. + */ + public BiomeJs biome() { + return biome(null); + } + + /** Downloads the given Biome version from the network. */ + public BiomeJs biome(String version) { + var biomeConfig = new BiomeJs(version); + addStep(biomeConfig.createStep()); + return biomeConfig; + } + + /** + * Defaults to downloading the default Rome version from the network. To work + * offline, you can specify the path to the Rome executable via + * {@code rome().pathToExe(...)}. + * + * @deprecated Use {@link #biome()}. + */ + @Deprecated + public RomeJs rome() { + return rome(null); + } + + /** + * Downloads the given Rome version from the network. + * + * @deprecated Use {@link #biome(String)}. + */ + @Deprecated + public RomeJs rome(String version) { + var romeConfig = new RomeJs(version); + addStep(romeConfig.createStep()); + return romeConfig; + } + + private static final String DEFAULT_PRETTIER_JS_PARSER = "babel"; + private static final ImmutableList PRETTIER_JS_PARSERS = ImmutableList.of(DEFAULT_PRETTIER_JS_PARSER, + "babel-flow", "flow"); + + /** + * Biome formatter step for JavaScript. + */ + public class BiomeJs extends RomeStepConfig { + /** + * Creates a new Biome formatter step config for formatting JavaScript files. + * Unless overwritten, the given Biome version is downloaded from the network. + * + * @param version Biome version to use. + */ + public BiomeJs(String version) { + super(getProject(), JavascriptExtension.this::replaceStep, BiomeFlavor.BIOME, version); + } + + @Override + protected String getLanguage() { + return "js?"; + } + + @Override + protected BiomeJs getThis() { + return this; + } + } + + /** + * Rome formatter step for JavaScript. + * + * @deprecated Rome has transitioned to Biome. This will be removed shortly. + */ + @Deprecated + public class RomeJs extends RomeStepConfig { + /** + * Creates a new Rome formatter step config for formatting JavaScript files. + * Unless overwritten, the given Rome version is downloaded from the network. + * + * @param version Rome version to use. + */ + public RomeJs(String version) { + super(getProject(), JavascriptExtension.this::replaceStep, BiomeFlavor.ROME, version); + } + + @Override + protected String getLanguage() { + return "js?"; + } + + @Override + protected RomeJs getThis() { + return this; + } + } + + /** + * Overrides the parser to be set to a js parser. + */ + public class JavascriptPrettierConfig extends PrettierConfig { + + JavascriptPrettierConfig(Map devDependencies) { + super(devDependencies); + } + + @Override + protected FormatterStep createStep() { + fixParserToJavascript(); + return super.createStep(); + } + + private void fixParserToJavascript() { + if (this.prettierConfig == null) { + this.prettierConfig = Collections.singletonMap("parser", DEFAULT_PRETTIER_JS_PARSER); + } else { + final Object currentParser = this.prettierConfig.get("parser"); + if (PRETTIER_JS_PARSERS.contains(String.valueOf(currentParser))) { + getProject().getLogger().debug("Already javascript parser set, not overriding."); + } else { + this.prettierConfig.put("parser", DEFAULT_PRETTIER_JS_PARSER); + if (currentParser != null) { + getProject().getLogger().warn( + "Overriding parser option to '{}'. (Was set to '{}'.) Set it to another js parser if you have problems with '{}'.", + DEFAULT_PRETTIER_JS_PARSER, currentParser, DEFAULT_PRETTIER_JS_PARSER); + } + } + + } + } + } + + @Override + protected void setupTask(SpotlessTask task) { + if (target == null) { + throw noDefaultTargetException(); + } + super.setupTask(task); + } +} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java index 25182fa4bc..441e25b93d 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,24 @@ */ package com.diffplug.gradle.spotless; +import java.util.Collections; +import java.util.List; +import java.util.Map; + import javax.inject.Inject; import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.json.JacksonJsonConfig; +import com.diffplug.spotless.json.JacksonJsonStep; +import com.diffplug.spotless.json.JsonPatchStep; import com.diffplug.spotless.json.JsonSimpleStep; import com.diffplug.spotless.json.gson.GsonStep; +import com.diffplug.spotless.rome.BiomeFlavor; public class JsonExtension extends FormatExtension { private static final int DEFAULT_INDENTATION = 4; - private static final String DEFAULT_GSON_VERSION = "2.8.9"; + private static final String DEFAULT_GSON_VERSION = "2.10.1"; + private static final String DEFAULT_ZJSONPATCH_VERSION = "0.4.14"; static final String NAME = "json"; @Inject @@ -47,6 +56,58 @@ public GsonConfig gson() { return new GsonConfig(); } + public JacksonJsonGradleConfig jackson() { + return new JacksonJsonGradleConfig(this); + } + + /** + * Defaults to downloading the default Biome version from the network. To work + * offline, you can specify the path to the Biome executable via + * {@code biome().pathToExe(...)}. + */ + public BiomeJson biome() { + return biome(null); + } + + /** Downloads the given Biome version from the network. */ + public BiomeJson biome(String version) { + var biomeConfig = new BiomeJson(version); + addStep(biomeConfig.createStep()); + return biomeConfig; + } + + /** + * Defaults to downloading the default Rome version from the network. To work + * offline, you can specify the path to the Rome executable via + * {@code rome().pathToExe(...)}. + * + * @deprecated Use {@link #biome()}. + */ + @Deprecated + public RomeJson rome() { + return rome(null); + } + + /** + * Downloads the given Rome version from the network. + * + * @deprecated Use {@link #biome(String)}. + */ + @Deprecated + public RomeJson rome(String version) { + var romeConfig = new RomeJson(version); + addStep(romeConfig.createStep()); + return romeConfig; + } + + public JsonPatchConfig jsonPatch(List> patch) { + return new JsonPatchConfig(patch); + } + + public JsonPatchConfig jsonPatch(String zjsonPatchVersion, List> patch) { + return new JsonPatchConfig(zjsonPatchVersion, patch); + } + public class SimpleConfig { private int indent; @@ -104,8 +165,122 @@ public GsonConfig version(String version) { } private FormatterStep createStep() { - return GsonStep.create(indentSpaces, sortByKeys, escapeHtml, version, provisioner()); + return GsonStep.create( + new com.diffplug.spotless.json.gson.GsonConfig(sortByKeys, escapeHtml, indentSpaces, version), + provisioner()); + } + } + + public static class JacksonJsonGradleConfig extends AJacksonGradleConfig { + protected JacksonJsonConfig jacksonConfig; + + public JacksonJsonGradleConfig(JacksonJsonConfig jacksonConfig, FormatExtension formatExtension) { + super(jacksonConfig, formatExtension); + this.jacksonConfig = jacksonConfig; + + formatExtension.addStep(createStep()); + } + + public JacksonJsonGradleConfig(FormatExtension formatExtension) { + this(new JacksonJsonConfig(), formatExtension); + } + + /** + * Refers to com.fasterxml.jackson.core.JsonGenerator.Feature + */ + public JacksonJsonGradleConfig jsonFeature(String feature, boolean toggle) { + this.jacksonConfig.appendJsonFeatureToToggle(Collections.singletonMap(feature, toggle)); + formatExtension.replaceStep(createStep()); + return this; + } + + @Override + public JacksonJsonGradleConfig self() { + return this; + } + + // 'final' as it is called in the constructor + @Override + protected final FormatterStep createStep() { + return JacksonJsonStep.create(jacksonConfig, version, formatExtension.provisioner()); } } + /** + * Biome formatter step for JSON. + */ + public class BiomeJson extends RomeStepConfig { + /** + * Creates a new Biome formatter step config for formatting JSON files. Unless + * overwritten, the given Biome version is downloaded from the network. + * + * @param version Biome version to use. + */ + public BiomeJson(String version) { + super(getProject(), JsonExtension.this::replaceStep, BiomeFlavor.BIOME, version); + } + + @Override + protected String getLanguage() { + return "json"; + } + + @Override + protected BiomeJson getThis() { + return this; + } + } + + /** + * Rome formatter step for JSON. + * + * @deprecated Rome has transitioned to Biome. This will be removed shortly. + */ + @Deprecated + public class RomeJson extends RomeStepConfig { + /** + * Creates a new Rome formatter step config for formatting JSON files. Unless + * overwritten, the given Rome version is downloaded from the network. + * + * @param version Rome version to use. + */ + public RomeJson(String version) { + super(getProject(), JsonExtension.this::replaceStep, BiomeFlavor.ROME, version); + } + + @Override + protected String getLanguage() { + return "json"; + } + + @Override + protected RomeJson getThis() { + return this; + } + } + + public class JsonPatchConfig { + private String zjsonPatchVersion; + private List> patch; + + public JsonPatchConfig(List> patch) { + this(DEFAULT_ZJSONPATCH_VERSION, patch); + } + + public JsonPatchConfig(String zjsonPatchVersion, List> patch) { + this.zjsonPatchVersion = zjsonPatchVersion; + this.patch = patch; + addStep(createStep()); + } + + public JsonPatchConfig version(String zjsonPatchVersion) { + this.zjsonPatchVersion = zjsonPatchVersion; + replaceStep(createStep()); + return this; + } + + private FormatterStep createStep() { + return JsonPatchStep.create(zjsonPatchVersion, patch, provisioner()); + } + } } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JvmLang.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JvmLang.java new file mode 100644 index 0000000000..1a9a06b986 --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JvmLang.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import java.io.File; +import java.util.function.Function; + +import org.gradle.api.GradleException; +import org.gradle.api.Project; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.SourceDirectorySet; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.specs.Spec; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.util.GradleVersion; + +interface JvmLang { + + default FileCollection getSources(Project project, String message, Function sourceSetSourceDirectory, Spec filterSpec) { + final SourceSetContainer sourceSets; + FileCollection union = project.files(); + if (GradleVersion.current().compareTo(GradleVersion.version(SpotlessPlugin.VER_GRADLE_javaPluginExtension)) >= 0) { + final JavaPluginExtension javaPluginExtension = project.getExtensions().findByType(JavaPluginExtension.class); + if (javaPluginExtension == null) { + throw new GradleException(message); + } + sourceSets = javaPluginExtension.getSourceSets(); + } else { + final JavaPluginConvention javaPluginConvention = project.getConvention().findPlugin(JavaPluginConvention.class); + if (javaPluginConvention == null) { + throw new GradleException(message); + } + sourceSets = javaPluginConvention.getSourceSets(); + } + for (SourceSet sourceSet : sourceSets) { + union = union.plus(sourceSetSourceDirectory.apply(sourceSet).filter(filterSpec)); + } + return union; + } +} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java index 546efab46d..21ccb8dd0f 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,29 +17,11 @@ import static com.diffplug.spotless.kotlin.KotlinConstants.LICENSE_HEADER_DELIMITER; -import java.io.IOException; -import java.util.Collections; -import java.util.Map; -import java.util.Objects; -import java.util.function.Consumer; - import javax.inject.Inject; -import org.gradle.api.GradleException; -import org.gradle.api.file.FileCollection; -import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.SourceSet; -import com.diffplug.common.collect.ImmutableSortedMap; -import com.diffplug.spotless.FileSignature; -import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.kotlin.DiktatStep; -import com.diffplug.spotless.kotlin.KtLintStep; -import com.diffplug.spotless.kotlin.KtfmtStep; -import com.diffplug.spotless.kotlin.KtfmtStep.KtfmtFormattingOptions; -import com.diffplug.spotless.kotlin.KtfmtStep.Style; - -public class KotlinExtension extends FormatExtension implements HasBuiltinDelimiterForLicense { +public class KotlinExtension extends BaseKotlinExtension implements HasBuiltinDelimiterForLicense, JvmLang { static final String NAME = "kotlin"; @Inject @@ -57,174 +39,22 @@ public LicenseHeaderConfig licenseHeaderFile(Object licenseHeaderFile) { return licenseHeaderFile(licenseHeaderFile, LICENSE_HEADER_DELIMITER); } - /** Adds the specified version of ktlint. */ - public KotlinFormatExtension ktlint(String version) { - Objects.requireNonNull(version); - return new KotlinFormatExtension(version, false, Collections.emptyMap(), Collections.emptyMap()); - } - - public KotlinFormatExtension ktlint() { - return ktlint(KtLintStep.defaultVersion()); - } - - public class KotlinFormatExtension { - - private final String version; - private boolean useExperimental; - private Map userData; - private Map editorConfigOverride; - - KotlinFormatExtension(String version, boolean useExperimental, Map config, - Map editorConfigOverride) { - this.version = version; - this.useExperimental = useExperimental; - this.userData = config; - this.editorConfigOverride = editorConfigOverride; - addStep(createStep()); - } - - public KotlinFormatExtension setUseExperimental(boolean useExperimental) { - this.useExperimental = useExperimental; - replaceStep(createStep()); - return this; - } - - public KotlinFormatExtension userData(Map userData) { - // Copy the map to a sorted map because up-to-date checking is based on binary-equals of the serialized - // representation. - this.userData = ImmutableSortedMap.copyOf(userData); - replaceStep(createStep()); - return this; - } - - public KotlinFormatExtension editorConfigOverride(Map editorConfigOverride) { - // Copy the map to a sorted map because up-to-date checking is based on binary-equals of the serialized - // representation. - this.editorConfigOverride = ImmutableSortedMap.copyOf(editorConfigOverride); - replaceStep(createStep()); - return this; - } - - private FormatterStep createStep() { - return KtLintStep.create(version, provisioner(), useExperimental, userData, editorConfigOverride); - } - } - - /** Uses the ktfmt jar to format source code. */ - public KtfmtConfig ktfmt() { - return ktfmt(KtfmtStep.defaultVersion()); - } - - /** - * Uses the given version of ktfmt and applies the dropbox style - * option to format source code. - */ - public KtfmtConfig ktfmt(String version) { - Objects.requireNonNull(version); - return new KtfmtConfig(version); - } - - public class KtfmtConfig { - final String version; - Style style; - KtfmtFormattingOptions options; - - private final ConfigurableStyle configurableStyle = new ConfigurableStyle(); - - KtfmtConfig(String version) { - this.version = Objects.requireNonNull(version); - addStep(createStep()); - } - - private ConfigurableStyle style(Style style) { - this.style = style; - replaceStep(createStep()); - return configurableStyle; - } - - public ConfigurableStyle dropboxStyle() { - return style(Style.DROPBOX); - } - - public ConfigurableStyle googleStyle() { - return style(Style.GOOGLE); - } - - public ConfigurableStyle kotlinlangStyle() { - return style(Style.KOTLINLANG); - } - - public void configure(Consumer optionsConfiguration) { - this.configurableStyle.configure(optionsConfiguration); - } - - private FormatterStep createStep() { - return KtfmtStep.create(version, provisioner(), style, options); - } - - public class ConfigurableStyle { - - public void configure(Consumer optionsConfiguration) { - KtfmtFormattingOptions ktfmtFormattingOptions = new KtfmtFormattingOptions(); - optionsConfiguration.accept(ktfmtFormattingOptions); - options = ktfmtFormattingOptions; - replaceStep(createStep()); - } - } - } - - /** Adds the specified version of diktat. */ - public DiktatFormatExtension diktat(String version) { - Objects.requireNonNull(version); - return new DiktatFormatExtension(version); - } - - public DiktatFormatExtension diktat() { - return diktat(DiktatStep.defaultVersionDiktat()); - } - - public class DiktatFormatExtension { - - private final String version; - private FileSignature config; - - DiktatFormatExtension(String version) { - this.version = version; - addStep(createStep()); - } - - public DiktatFormatExtension configFile(Object file) throws IOException { - // Specify the path to the configuration file - if (file == null) { - this.config = null; - } else { - this.config = FileSignature.signAsList(getProject().file(file)); - } - replaceStep(createStep()); - return this; - } - - private FormatterStep createStep() { - return DiktatStep.create(version, provisioner(), config); - } + @Override + protected boolean isScript() { + return false; } /** If the user hasn't specified the files yet, we'll assume he/she means all of the kotlin files. */ @Override protected void setupTask(SpotlessTask task) { if (target == null) { - JavaPluginConvention javaPlugin = getProject().getConvention().findPlugin(JavaPluginConvention.class); - if (javaPlugin == null) { - throw new GradleException("You must either specify 'target' manually or apply a kotlin plugin."); - } - FileCollection union = getProject().files(); - for (SourceSet sourceSet : javaPlugin.getSourceSets()) { - union = union.plus(sourceSet.getAllSource().filter(file -> { - String name = file.getName(); - return name.endsWith(".kt") || name.endsWith(".kts"); - })); - } - target = union; + target = getSources(getProject(), + "You must either specify 'target' manually or apply a kotlin plugin.", + SourceSet::getAllSource, + file -> { + final String name = file.getName(); + return name.endsWith(".kt") || name.endsWith(".kts"); + }); } super.setupTask(task); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinGradleExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinGradleExtension.java index c1abe62bd1..5cf2847d62 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinGradleExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinGradleExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,24 +15,9 @@ */ package com.diffplug.gradle.spotless; -import java.io.IOException; -import java.util.Collections; -import java.util.Map; -import java.util.Objects; -import java.util.function.Consumer; - import javax.inject.Inject; -import com.diffplug.common.collect.ImmutableSortedMap; -import com.diffplug.spotless.FileSignature; -import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.kotlin.DiktatStep; -import com.diffplug.spotless.kotlin.KtLintStep; -import com.diffplug.spotless.kotlin.KtfmtStep; -import com.diffplug.spotless.kotlin.KtfmtStep.KtfmtFormattingOptions; -import com.diffplug.spotless.kotlin.KtfmtStep.Style; - -public class KotlinGradleExtension extends FormatExtension { +public class KotlinGradleExtension extends BaseKotlinExtension { private static final String GRADLE_KOTLIN_DSL_FILE_EXTENSION = "*.gradle.kts"; static final String NAME = "kotlinGradle"; @@ -42,157 +27,9 @@ public KotlinGradleExtension(SpotlessExtension spotless) { super(spotless); } - /** Adds the specified version of ktlint. */ - public KotlinFormatExtension ktlint(String version) { - Objects.requireNonNull(version, "version"); - return new KotlinFormatExtension(version, false, Collections.emptyMap(), Collections.emptyMap()); - } - - public KotlinFormatExtension ktlint() { - return ktlint(KtLintStep.defaultVersion()); - } - - public class KotlinFormatExtension { - - private final String version; - private boolean useExperimental; - private Map userData; - private Map editorConfigOverride; - - KotlinFormatExtension(String version, boolean useExperimental, Map config, - Map editorConfigOverride) { - this.version = version; - this.useExperimental = useExperimental; - this.userData = config; - this.editorConfigOverride = editorConfigOverride; - addStep(createStep()); - } - - public KotlinFormatExtension setUseExperimental(boolean useExperimental) { - this.useExperimental = useExperimental; - replaceStep(createStep()); - return this; - } - - public KotlinFormatExtension userData(Map userData) { - // Copy the map to a sorted map because up-to-date checking is based on binary-equals of the serialized - // representation. - this.userData = ImmutableSortedMap.copyOf(userData); - replaceStep(createStep()); - return this; - } - - public KotlinFormatExtension editorConfigOverride(Map editorConfigOverride) { - // Copy the map to a sorted map because up-to-date checking is based on binary-equals of the serialized - // representation. - this.editorConfigOverride = ImmutableSortedMap.copyOf(editorConfigOverride); - replaceStep(createStep()); - return this; - } - - private FormatterStep createStep() { - return KtLintStep.createForScript(version, provisioner(), useExperimental, userData, editorConfigOverride); - } - } - - /** Uses the ktfmt jar to format source code. */ - public KtfmtConfig ktfmt() { - return ktfmt(KtfmtStep.defaultVersion()); - } - - /** - * Uses the given version of ktfmt to format source - * code. - */ - public KtfmtConfig ktfmt(String version) { - Objects.requireNonNull(version); - return new KtfmtConfig(version); - } - - public class KtfmtConfig { - final String version; - Style style; - KtfmtFormattingOptions options; - - private final ConfigurableStyle configurableStyle = new ConfigurableStyle(); - - KtfmtConfig(String version) { - this.version = Objects.requireNonNull(version); - this.style = Style.DEFAULT; - addStep(createStep()); - } - - private ConfigurableStyle style(Style style) { - this.style = style; - replaceStep(createStep()); - return configurableStyle; - } - - public ConfigurableStyle dropboxStyle() { - return style(Style.DROPBOX); - } - - public ConfigurableStyle googleStyle() { - return style(Style.GOOGLE); - } - - public ConfigurableStyle kotlinlangStyle() { - return style(Style.KOTLINLANG); - } - - public void configure(Consumer optionsConfiguration) { - this.configurableStyle.configure(optionsConfiguration); - } - - private FormatterStep createStep() { - return KtfmtStep.create(version, provisioner(), style, options); - } - - public class ConfigurableStyle { - - public void configure(Consumer optionsConfiguration) { - KtfmtFormattingOptions ktfmtFormattingOptions = new KtfmtFormattingOptions(); - optionsConfiguration.accept(ktfmtFormattingOptions); - options = ktfmtFormattingOptions; - replaceStep(createStep()); - } - } - } - - /** Adds the specified version of diktat. */ - public DiktatFormatExtension diktat(String version) { - Objects.requireNonNull(version, "version"); - return new DiktatFormatExtension(version); - } - - public DiktatFormatExtension diktat() { - return diktat(DiktatStep.defaultVersionDiktat()); - } - - public class DiktatFormatExtension { - - private final String version; - private FileSignature config; - - DiktatFormatExtension(String version) { - this.version = version; - addStep(createStep()); - } - - public DiktatFormatExtension configFile(Object file) throws IOException { - // Specify the path to the configuration file - if (file == null) { - this.config = null; - } else { - this.config = FileSignature.signAsList(getProject().file(file)); - } - replaceStep(createStep()); - return this; - } - - private FormatterStep createStep() { - return DiktatStep.createForScript(version, provisioner(), config); - } + @Override + protected boolean isScript() { + return true; } @Override diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ProtobufExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ProtobufExtension.java new file mode 100644 index 0000000000..7b0a6bb30a --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ProtobufExtension.java @@ -0,0 +1,109 @@ +/* + * Copyright 2022-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import static com.diffplug.spotless.protobuf.ProtobufConstants.LICENSE_HEADER_DELIMITER; + +import java.util.Objects; + +import javax.inject.Inject; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.protobuf.BufStep; + +public class ProtobufExtension extends FormatExtension implements HasBuiltinDelimiterForLicense { + static final String NAME = "protobuf"; + + @Inject + public ProtobufExtension(SpotlessExtension spotless) { + super(spotless); + } + + @Override + public LicenseHeaderConfig licenseHeader(String licenseHeader) { + return licenseHeader(licenseHeader, LICENSE_HEADER_DELIMITER); + } + + @Override + public LicenseHeaderConfig licenseHeaderFile(Object licenseHeaderFile) { + return licenseHeaderFile(licenseHeaderFile, LICENSE_HEADER_DELIMITER); + } + + /** If the user hasn't specified files, assume all protobuf files should be checked. */ + @Override + protected void setupTask(SpotlessTask task) { + if (target == null) { + target = parseTarget("**/*.proto"); + } + super.setupTask(task); + } + + /** Adds the specified version of buf. */ + public BufFormatExtension buf(String version) { + Objects.requireNonNull(version); + return new BufFormatExtension(version); + } + + public BufFormatExtension buf() { + return buf(BufStep.defaultVersion()); + } + + public class BufFormatExtension { + BufStep step; + + BufFormatExtension(String version) { + this.step = BufStep.withVersion(version); + if (!steps.isEmpty()) { + throw new IllegalArgumentException("buf() must be the first step, move other steps after it. Thumbs up [this issue](https://github.com/bufbuild/buf/issues/1035) for a resolution, see [here](https://github.com/diffplug/spotless/pull/1208#discussion_r1264439669) for more details on the problem."); + } + addStep(createStep()); + } + + /** + * When used in conjunction with the {@code buf-gradle-plugin}, + * the {@code buf} executable can be resolved from its {@code bufTool} configuration: + * + *

+		 * {@code
+		 * spotless {
+		 *   protobuf {
+		 *     buf().pathToExe(configurations.getByName(BUF_BINARY_CONFIGURATION_NAME).getSingleFile().getAbsolutePath())
+		 *   }
+		 * }
+		 * }
+		 * 
+ * + * Be sure to disable the {@code buf-gradle-plugin}'s execution of {@code buf format}: + * + *
+		 * {@code
+		 * buf {
+		 *   enforceFormat = false
+		 * }
+		 * }
+		 * 
+ */ + public BufFormatExtension pathToExe(String pathToExe) { + step = step.withPathToExe(pathToExe); + replaceStep(createStep()); + return this; + } + + private FormatterStep createStep() { + return step.create(); + } + } +} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RegisterDependenciesTask.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RegisterDependenciesTask.java index 75bd06406b..33aded8bdd 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RegisterDependenciesTask.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RegisterDependenciesTask.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,7 @@ import org.gradle.api.tasks.OutputFile; import org.gradle.api.tasks.TaskAction; import org.gradle.build.event.BuildEventsListenerRegistry; +import org.gradle.work.DisableCachingByDefault; import com.diffplug.common.base.Preconditions; import com.diffplug.common.io.Files; @@ -38,7 +39,7 @@ /** * NOT AN END-USER TASK, DO NOT USE FOR ANYTHING! - * + *

* - When a user asks for a formatter, we need to download the jars for that formatter * - Gradle wants us to resolve all our dependencies in the root project - no new dependencies in subprojects * - So, whenever a SpotlessTask in a subproject gets configured, we call {@link #hookSubprojectTask(SpotlessTask)}, @@ -46,6 +47,7 @@ * - When this "registerDependencies" task does its up-to-date check, it queries the task execution graph to see which * SpotlessTasks are at risk of being executed, and causes them all to be evaluated safely in the root buildscript. */ +@DisableCachingByDefault(because = "This task coordinates the setup and execution of other tasks, and should not be cached") public abstract class RegisterDependenciesTask extends DefaultTask { static final String TASK_NAME = "spotlessInternalRegisterDependencies"; @@ -66,7 +68,7 @@ void setup() { taskService = buildServices.registerIfAbsent("SpotlessTaskService" + compositeBuildSuffix, SpotlessTaskService.class, spec -> {}); usesService(taskService); getBuildEventsListenerRegistry().onTaskCompletion(taskService); - unitOutput = new File(getProject().getBuildDir(), "tmp/spotless-register-dependencies"); + unitOutput = new File(getProject().getLayout().getBuildDirectory().getAsFile().get(), "tmp/spotless-register-dependencies"); } List steps = new ArrayList<>(); diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RomeStepConfig.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RomeStepConfig.java new file mode 100644 index 0000000000..98c4000a8b --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RomeStepConfig.java @@ -0,0 +1,279 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import static java.util.Objects.requireNonNull; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.function.Consumer; + +import javax.annotation.Nullable; + +import org.gradle.api.Project; +import org.gradle.api.artifacts.repositories.MavenArtifactRepository; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.rome.BiomeFlavor; +import com.diffplug.spotless.rome.RomeStep; + +public abstract class RomeStepConfig> { + /** + * Optional path to the directory with configuration file for Biome. The file + * must be named {@code biome.json}. When none is given, the default + * configuration is used. If this is a relative path, it is resolved against the + * project's base directory. + */ + @Nullable + private Object configPath; + + /** + * Optional directory where the downloaded Biome executable is placed. If this + * is a relative path, it is resolved against the project's base directory. + * Defaults to + * ~/.m2/repository/com/diffplug/spotless/spotless-data/biome. + */ + @Nullable + private Object downloadDir; + + /** + * The flavor of Biome to use. Will be removed when we stop support the + * deprecated Rome project. + */ + private final BiomeFlavor flavor; + + /** + * Optional path to the Biome executable. Either a version or a + * pathToExe should be specified. When not given, an attempt is + * made to download the executable for the given version from the network. When + * given, the executable is used and the version parameter is + * ignored. + *

+ * When an absolute path is given, that path is used as-is. When a relative path + * is given, it is resolved against the project's base directory. When only a + * file name (i.e. without any slashes or back slash path separators such as + * biome) is given, this is interpreted as the name of a command + * with executable that is in your path environment variable. Use + * ./executable-name if you want to use an executable in the + * project's base directory. + */ + @Nullable + private Object pathToExe; + + /** + * A reference to the Gradle project for which spotless is executed. + */ + private final Project project; + + /** + * Replaces the current Biome formatter step with the given step. + */ + private final Consumer replaceStep; + + /** + * Biome version to download, applies only when no pathToExe is + * specified explicitly. Either a version or a + * pathToExe should be specified. When not given, a default known + * version is used. For stable builds, it is recommended that you always set the + * version explicitly. This parameter is ignored when you specify a + * pathToExe explicitly. + */ + @Nullable + private String version; + + protected RomeStepConfig(Project project, Consumer replaceStep, BiomeFlavor flavor, + String version) { + this.project = requireNonNull(project); + this.replaceStep = requireNonNull(replaceStep); + this.flavor = flavor; + this.version = version; + } + + /** + * Optional path to the directory with configuration file for Biome. The file + * must be named {@code biome.json}. When none is given, the default + * configuration is used. If this is a relative path, it is resolved against the + * project's base directory. + * + * @return This step for further configuration. + */ + public Self configPath(Object configPath) { + this.configPath = configPath; + replaceStep(); + return getThis(); + } + + /** + * Optional directory where the downloaded Biome executable is placed. If this + * is a relative path, it is resolved against the project's base directory. + * Defaults to + * ~/.m2/repository/com/diffplug/spotless/spotless-data/biome. + * + * @return This step for further configuration. + */ + public Self downloadDir(Object downloadDir) { + this.downloadDir = downloadDir; + replaceStep(); + return getThis(); + } + + /** + * Optional path to the Biome executable. Overwrites the configured version. No + * attempt is made to download the Biome executable from the network. + *

+ * When an absolute path is given, that path is used as-is. When a relative path + * is given, it is resolved against the project's base directory. When only a + * file name (i.e. without any slashes or back slash path separators such as + * biome) is given, this is interpreted as the name of a command + * with executable that is in your path environment variable. Use + * ./executable-name if you want to use an executable in the + * project's base directory. + * + * @return This step for further configuration. + */ + public Self pathToExe(Object pathToExe) { + this.pathToExe = pathToExe; + replaceStep(); + return getThis(); + } + + /** + * Creates a new formatter step that formats code by calling the Biome + * executable, using the current configuration. + * + * @return A new formatter step for the Biome formatter. + */ + protected FormatterStep createStep() { + var builder = newBuilder(); + if (configPath != null) { + var resolvedConfigPath = project.file(configPath); + builder.withConfigPath(resolvedConfigPath.toString()); + } + builder.withLanguage(getLanguage()); + return builder.create(); + } + + /** + * Gets the language (syntax) of the input files to format. When + * null or the empty string, the language is detected automatically + * from the file name. Currently the following languages are supported by Biome: + *

    + *
  • js (JavaScript)
  • + *
  • jsx (JavaScript + JSX)
  • + *
  • js? (JavaScript or JavaScript + JSX, depending on the file + * extension)
  • + *
  • ts (TypeScript)
  • + *
  • tsx (TypeScript + JSX)
  • + *
  • ts? (TypeScript or TypeScript + JSX, depending on the file + * extension)
  • + *
  • json (JSON)
  • + *
+ * + * @return The language of the input files. + */ + protected abstract String getLanguage(); + + /** + * @return This Biome config instance. + */ + protected abstract Self getThis(); + + /** + * Creates a new Biome step and replaces the existing Biome step in the list of + * format steps. + */ + protected void replaceStep() { + replaceStep.accept(createStep()); + } + + /** + * Finds the data directory that can be used for storing shared data such as + * Biome executable globally. This is a directory in the local repository, e.g. + * ~/.m2/repository/com/diffplus/spotless/spotless-data. + * + * @return The directory for storing shared data. + */ + private File findDataDir() { + var currentRepo = project.getRepositories().stream().filter(r -> r instanceof MavenArtifactRepository) + .map(r -> (MavenArtifactRepository) r).filter(r -> "file".equals(r.getUrl().getScheme())).findAny() + .orElse(null); + // Temporarily add mavenLocal() repository to get its file URL + var localRepo = currentRepo != null ? (MavenArtifactRepository) currentRepo + : project.getRepositories().mavenLocal(); + try { + // e.g. ~/.m2/repository/ + var repoPath = Path.of(localRepo.getUrl()); + var dataPath = repoPath.resolve("com").resolve("diffplug").resolve("spotless").resolve("spotless-data"); + return dataPath.toAbsolutePath().toFile(); + } finally { + // Remove mavenLocal() repository again if it was not part of the project + if (currentRepo == null) { + project.getRepositories().remove(localRepo); + } + } + } + + /** + * A new builder for configuring a Biome step that either downloads the Biome + * executable with the given version from the network, or uses the executable + * from the given path. + * + * @return A builder for a Biome step. + */ + private RomeStep newBuilder() { + if (pathToExe != null) { + var resolvedPathToExe = resolvePathToExe(); + return RomeStep.withExePath(flavor, resolvedPathToExe); + } else { + var downloadDir = resolveDownloadDir(); + return RomeStep.withExeDownload(flavor, version, downloadDir); + } + } + + /** + * Resolves the path to the Biome executable. When the path is only a file name, + * do not perform any resolution and interpret it as a command that must be on + * the user's path. Otherwise resolve the executable path against the project's + * base directory. + * + * @return The resolved path to the Biome executable. + */ + private String resolvePathToExe() { + var fileNameOnly = pathToExe instanceof String && Paths.get(pathToExe.toString()).getNameCount() == 1; + if (fileNameOnly) { + return pathToExe.toString(); + } else { + return project.file(pathToExe).toString(); + } + } + + /** + * Resolves the directory to use for storing downloaded Biome executable. When a + * {@link #downloadDir} is given, use that directory, resolved against the + * current project's directory. Otherwise, use the {@code biome} sub folder in + * the shared data directory. + * + * @return The download directory for the Biome executable. + */ + private String resolveDownloadDir() { + if (downloadDir != null) { + return project.file(downloadDir).toString(); + } else { + return findDataDir().toPath().resolve(flavor.shortName()).toString(); + } + } +} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ScalaExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ScalaExtension.java index 8a8765b539..1639f3473c 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ScalaExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ScalaExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,15 +21,12 @@ import javax.annotation.Nullable; import javax.inject.Inject; -import org.gradle.api.GradleException; -import org.gradle.api.file.FileCollection; -import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.SourceSet; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.scala.ScalaFmtStep; -public class ScalaExtension extends FormatExtension { +public class ScalaExtension extends FormatExtension implements JvmLang { static final String NAME = "scala"; @Inject @@ -79,18 +76,13 @@ private FormatterStep createStep() { @Override protected void setupTask(SpotlessTask task) { if (target == null) { - JavaPluginConvention javaPlugin = getProject().getConvention().findPlugin(JavaPluginConvention.class); - if (javaPlugin == null) { - throw new GradleException("You must either specify 'target' manually or apply the 'scala' plugin."); - } - FileCollection union = getProject().files(); - for (SourceSet sourceSet : javaPlugin.getSourceSets()) { - union = union.plus(sourceSet.getAllSource().filter(file -> { - String name = file.getName(); - return name.endsWith(".scala") || name.endsWith(".sc"); - })); - } - target = union; + target = getSources(getProject(), + "You must either specify 'target' manually or apply the 'scala' plugin.", + SourceSet::getAllSource, + file -> { + final String name = file.getName(); + return name.endsWith(".scala") || name.endsWith(".sc"); + }); } super.setupTask(task); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ShellExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ShellExtension.java new file mode 100644 index 0000000000..9149467eaa --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ShellExtension.java @@ -0,0 +1,73 @@ +/* + * Copyright 2024 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import java.util.Objects; + +import javax.inject.Inject; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.shell.ShfmtStep; + +public class ShellExtension extends FormatExtension { + private static final String SHELL_FILE_EXTENSION = "*.sh"; + + static final String NAME = "shell"; + + @Inject + public ShellExtension(SpotlessExtension spotless) { + super(spotless); + } + + /** If the user hasn't specified files, assume all shell files should be checked. */ + @Override + protected void setupTask(SpotlessTask task) { + if (target == null) { + target = parseTarget(SHELL_FILE_EXTENSION); + } + super.setupTask(task); + } + + /** Adds the specified version of shfmt. */ + public ShfmtExtension shfmt(String version) { + Objects.requireNonNull(version); + return new ShfmtExtension(version); + } + + /** Adds the specified version of shfmt. */ + public ShfmtExtension shfmt() { + return shfmt(ShfmtStep.defaultVersion()); + } + + public class ShfmtExtension { + ShfmtStep step; + + ShfmtExtension(String version) { + this.step = ShfmtStep.withVersion(version); + addStep(createStep()); + } + + public ShfmtExtension pathToExe(String pathToExe) { + step = step.withPathToExe(pathToExe); + replaceStep(createStep()); + return this; + } + + private FormatterStep createStep() { + return step.create(); + } + } +} diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java index d0e27d60bf..95f051371b 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessCheck.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -86,8 +86,8 @@ public void visitFile(FileVisitDetails fileVisitDetails) { // in its output directory which ought to have been removed. As // best I can tell, this is a filesytem race which is very hard // to trigger. GitRatchetGradleTest can *sometimes* reproduce it - // but it's very erratic, and that test writes both to gradle cache - // and git cache very quickly. Either of gradle or jgit might be + // but it's very erratic, and that test writes both to Gradle cache + // and git cache very quickly. Either of Gradle or jgit might be // caching something wrong because of the fast repeated writes. if (!Arrays.equals(userFile, formatted)) { // If the on-disk content is equal to the formatted content, diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiagnoseTask.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiagnoseTask.java index f20957e650..6dcba72864 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiagnoseTask.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessDiagnoseTask.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,7 +43,8 @@ public SpotlessTask getSource() { @SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE") public void performAction() throws IOException { Path srcRoot = getProject().getProjectDir().toPath(); - Path diagnoseRoot = getProject().getBuildDir().toPath().resolve("spotless-diagnose-" + source.formatName()); + Path diagnoseRoot = getProject().getLayout().getBuildDirectory().getAsFile().get() + .toPath().resolve("spotless-diagnose-" + source.formatName()); getProject().delete(diagnoseRoot.toFile()); try (Formatter formatter = source.buildFormatter()) { for (File file : source.target) { diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java index 57aa3b2d83..f02ac28548 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,7 +57,7 @@ RegisterDependenciesTask getRegisterDependenciesTask() { } /** Line endings (if any). */ - LineEnding lineEndings = LineEnding.GIT_ATTRIBUTES; + LineEnding lineEndings = LineEnding.GIT_ATTRIBUTES_FAST_ALLSAME; public LineEnding getLineEndings() { return lineEndings; @@ -147,6 +147,12 @@ public void freshmark(Action closure) { format(FreshMarkExtension.NAME, FreshMarkExtension.class, closure); } + /** Configures the special flexmark-specific extension. */ + public void flexmark(Action closure) { + requireNonNull(closure); + format(FlexmarkExtension.NAME, FlexmarkExtension.class, closure); + } + /** Configures the special groovy-specific extension. */ public void groovy(Action closure) { format(GroovyExtension.NAME, GroovyExtension.class, closure); @@ -167,6 +173,11 @@ public void cpp(Action closure) { format(CppExtension.NAME, CppExtension.class, closure); } + /** Configures the special javascript-specific extension for javascript files. */ + public void javascript(Action closure) { + format(JavascriptExtension.NAME, JavascriptExtension.class, closure); + } + /** Configures the special typescript-specific extension for typescript files. */ public void typescript(Action closure) { format(TypescriptExtension.NAME, TypescriptExtension.class, closure); @@ -188,6 +199,35 @@ public void json(Action closure) { format(JsonExtension.NAME, JsonExtension.class, closure); } + /** Configures the special protobuf-specific extension. */ + public void protobuf(Action closure) { + requireNonNull(closure); + format(ProtobufExtension.NAME, ProtobufExtension.class, closure); + } + + /** Configures the special shell-specific extension. */ + public void shell(Action closure) { + requireNonNull(closure); + format(ShellExtension.NAME, ShellExtension.class, closure); + } + + /** Configures the special YAML-specific extension. */ + public void yaml(Action closure) { + requireNonNull(closure); + format(YamlExtension.NAME, YamlExtension.class, closure); + } + + /** Configures the special Gherkin-specific extension. */ + public void gherkin(Action closure) { + requireNonNull(closure); + format(GherkinExtension.NAME, GherkinExtension.class, closure); + } + + public void go(Action closure) { + requireNonNull(closure); + format(GoExtension.NAME, GoExtension.class, closure); + } + /** Configures a custom extension. */ public void format(String name, Action closure) { requireNonNull(name, "name"); @@ -205,7 +245,7 @@ public boolean isEnforceCheck() { /** * Configures Gradle's {@code check} task to run {@code spotlessCheck} if {@code true}, * but to not do so if {@code false}. - * + *

* {@code true} by default. */ public void setEnforceCheck(boolean enforceCheck) { diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessPlugin.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessPlugin.java index 868502d216..0043e2b274 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessPlugin.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessPlugin.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,20 +16,29 @@ package com.diffplug.gradle.spotless; import org.gradle.api.GradleException; +import org.gradle.api.JavaVersion; import org.gradle.api.Plugin; import org.gradle.api.Project; import org.gradle.api.plugins.BasePlugin; +import com.diffplug.spotless.Jvm; import com.diffplug.spotless.SpotlessCache; public class SpotlessPlugin implements Plugin { static final String SPOTLESS_MODERN = "spotlessModern"; - static final String MINIMUM_GRADLE = "6.1.1"; + static final String VER_GRADLE_min = "6.1.1"; + static final String VER_GRADLE_javaPluginExtension = "7.1"; + private static final int MINIMUM_JRE = 11; @Override public void apply(Project project) { if (SpotlessPluginRedirect.gradleIsTooOld(project)) { - throw new GradleException("Spotless requires Gradle " + MINIMUM_GRADLE + " or newer, this was " + project.getGradle().getGradleVersion()); + throw new GradleException("Spotless requires Gradle " + VER_GRADLE_min + " or newer, this was " + project.getGradle().getGradleVersion()); + } + if (Jvm.version() < MINIMUM_JRE) { + throw new GradleException("Spotless requires JRE " + MINIMUM_JRE + " or newer, this was " + JavaVersion.current() + ".\n" + + "You can upgrade your build JRE and still compile for older targets, see below\n" + + "https://docs.gradle.org/current/userguide/building_java_projects.html#sec:java_cross_compilation"); } // if -PspotlessModern=true, then use the modern stuff instead of the legacy stuff if (project.hasProperty(SPOTLESS_MODERN)) { diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessPluginRedirect.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessPluginRedirect.java index da4d3dcd8b..bca8ea7c70 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessPluginRedirect.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessPluginRedirect.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,7 +46,7 @@ private static int badSemver(int major, int minor) { static boolean gradleIsTooOld(Project project) { if (gradleIsTooOld == null) { - gradleIsTooOld = badSemver(project.getGradle().getGradleVersion()) < badSemver(SpotlessPlugin.MINIMUM_GRADLE); + gradleIsTooOld = badSemver(project.getGradle().getGradleVersion()) < badSemver(SpotlessPlugin.VER_GRADLE_min); } return gradleIsTooOld.booleanValue(); } @@ -73,7 +73,7 @@ public void apply(Project project) { "If you like the idea behind 'ratchetFrom', you should checkout spotless-changelog", "https://github.com/diffplug/spotless-changelog"); if (gradleIsTooOld(project)) { - errorMsg = errorMsg.replace("To migrate:\n", "To migrate:\n- Upgrade gradle to " + SpotlessPlugin.MINIMUM_GRADLE + " or newer (you're on " + project.getGradle().getGradleVersion() + ")\n"); + errorMsg = errorMsg.replace("To migrate:\n", "To migrate:\n- Upgrade Gradle to " + SpotlessPlugin.VER_GRADLE_min + " or newer (you're on " + project.getGradle().getGradleVersion() + ")\n"); } throw new GradleException(errorMsg); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTask.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTask.java index 6f2279b2e0..7b225eacd0 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTask.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTask.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Internal; @@ -64,14 +65,14 @@ public void setEncoding(String encoding) { this.encoding = Objects.requireNonNull(encoding); } - protected final LiveCache lineEndingsPolicy = createLive("lineEndingsPolicy"); + protected final LiveCache> lineEndingsPolicy = createLive("lineEndingsPolicy"); @Input - public LineEnding.Policy getLineEndingsPolicy() { + public Provider getLineEndingsPolicy() { return lineEndingsPolicy.get(); } - public void setLineEndingsPolicy(LineEnding.Policy lineEndingsPolicy) { + public void setLineEndingsPolicy(Provider lineEndingsPolicy) { this.lineEndingsPolicy.set(lineEndingsPolicy); } @@ -147,7 +148,7 @@ public void setTarget(Iterable target) { } } - protected File outputDirectory = new File(getProject().getBuildDir(), "spotless/" + getName()); + protected File outputDirectory = new File(getProject().getLayout().getBuildDirectory().getAsFile().get(), "spotless/" + getName()); @OutputDirectory public File getOutputDirectory() { @@ -184,7 +185,8 @@ String formatName() { Formatter buildFormatter() { return Formatter.builder() - .lineEndingsPolicy(lineEndingsPolicy.get()) + .name(formatName()) + .lineEndingsPolicy(lineEndingsPolicy.get().get()) .encoding(Charset.forName(encoding)) .rootDir(getProjectDir().get().getAsFile().toPath()) .steps(steps.get()) diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskImpl.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskImpl.java index b37e9f283a..04951a4577 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskImpl.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ import org.gradle.work.FileChange; import org.gradle.work.InputChanges; +import com.diffplug.common.annotations.VisibleForTesting; import com.diffplug.common.base.StringPrinter; import com.diffplug.spotless.Formatter; import com.diffplug.spotless.PaddedCell; @@ -47,7 +48,7 @@ public abstract class SpotlessTaskImpl extends SpotlessTask { void init(Provider service) { taskServiceProvider = service; - usesService(service); + SpotlessTaskService.usesServiceTolerateTestFailure(this, service); getTaskService().set(service); getProjectDir().set(getProject().getProjectDir()); } @@ -92,28 +93,37 @@ public void performAction(InputChanges inputs) throws Exception { } } - private void processInputFile(@Nullable GitRatchet ratchet, Formatter formatter, File input) throws IOException { + @VisibleForTesting + void processInputFile(@Nullable GitRatchet ratchet, Formatter formatter, File input) throws IOException { File output = getOutputFile(input); - getLogger().debug("Applying format to " + input + " and writing to " + output); + getLogger().debug("Applying format to {} and writing to {}", input, output); PaddedCell.DirtyState dirtyState; if (ratchet != null && ratchet.isClean(getProjectDir().get().getAsFile(), getRootTreeSha(), input)) { dirtyState = PaddedCell.isClean(); } else { - dirtyState = PaddedCell.calculateDirtyState(formatter, input); + try { + dirtyState = PaddedCell.calculateDirtyState(formatter, input); + } catch (IOException e) { + throw new IOException("Issue processing file: " + input, e); + } catch (RuntimeException e) { + throw new IllegalArgumentException("Issue processing file: " + input, e); + } } if (dirtyState.isClean()) { // Remove previous output if it exists Files.deleteIfExists(output.toPath()); } else if (dirtyState.didNotConverge()) { - getLogger().warn("Skipping '" + input + "' because it does not converge. Run {@code spotlessDiagnose} to understand why"); + getLogger().warn("Skipping '{}' because it does not converge. Run {@code spotlessDiagnose} to understand why", input); } else { Path parentDir = output.toPath().getParent(); if (parentDir == null) { - throw new IllegalStateException("Every file has a parent folder."); + throw new IllegalStateException("Every file has a parent folder. But not: " + output); } Files.createDirectories(parentDir); // Need to copy the original file to the tmp location just to remember the file attributes Files.copy(input.toPath(), output.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES); + + getLogger().info(String.format("Writing clean file: %s", output)); dirtyState.writeCanonicalTo(output); } } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskService.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskService.java index 67de4b946c..b5a1300ead 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskService.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessTaskService.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import org.gradle.api.file.DirectoryProperty; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Property; +import org.gradle.api.provider.Provider; import org.gradle.api.services.BuildService; import org.gradle.api.services.BuildServiceParameters; import org.gradle.api.tasks.Internal; @@ -91,6 +92,16 @@ public void close() throws Exception { static String INDEPENDENT_HELPER = "Helper"; + static void usesServiceTolerateTestFailure(DefaultTask task, Provider serviceProvider) { + try { + task.usesService(serviceProvider); + } catch (ClassCastException e) { + // this happens only in our test mocking, e.g. DiffMessageFormatterTest + // https://github.com/diffplug/spotless/pull/1570/commits/c45e1f2322c78f272689feb35753bbc633422bfa + // it's fine to swallow these exceptions + } + } + static abstract class ClientTask extends DefaultTask { @Internal abstract Property getSpotlessOutDirectory(); @@ -105,7 +116,7 @@ static abstract class ClientTask extends DefaultTask { protected abstract ObjectFactory getConfigCacheWorkaround(); void init(SpotlessTaskImpl impl) { - usesService(impl.getTaskServiceProvider()); + usesServiceTolerateTestFailure(this, impl.getTaskServiceProvider()); getSpotlessOutDirectory().set(impl.getOutputDirectory()); getTaskService().set(impl.getTaskService()); getProjectDir().set(impl.getProjectDir()); diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java index 58b9d4acbb..190a2f8e20 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ import static java.util.Objects.requireNonNull; +import java.util.Arrays; import java.util.Collections; import java.util.Map; import java.util.Objects; @@ -27,12 +28,17 @@ import org.gradle.api.Project; +import com.diffplug.gradle.spotless.JavascriptExtension.EslintBaseConfig; import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.npm.EslintConfig; +import com.diffplug.spotless.npm.EslintFormatterStep; +import com.diffplug.spotless.npm.EslintTypescriptConfig; import com.diffplug.spotless.npm.NpmPathResolver; import com.diffplug.spotless.npm.PrettierFormatterStep; import com.diffplug.spotless.npm.TsConfigFileType; import com.diffplug.spotless.npm.TsFmtFormatterStep; import com.diffplug.spotless.npm.TypedTsFmtConfigFile; +import com.diffplug.spotless.rome.BiomeFlavor; public class TypescriptExtension extends FormatExtension { @@ -53,7 +59,10 @@ public TypescriptFormatExtension tsfmt(String version) { return tsfmt(TsFmtFormatterStep.defaultDevDependenciesWithTsFmt(version)); } - /** Creates a {@code TypescriptFormatExtension} using exactly the specified npm packages. */ + /** + * Creates a {@code TypescriptFormatExtension} using exactly the specified npm + * packages. + */ public TypescriptFormatExtension tsfmt(Map devDependencies) { TypescriptFormatExtension tsfmt = new TypescriptFormatExtension(devDependencies); addStep(tsfmt.createStep()); @@ -73,46 +82,48 @@ public class TypescriptFormatExtension extends NpmStepConfig devDependencies; TypescriptFormatExtension(Map devDependencies) { + super(getProject(), TypescriptExtension.this::replaceStep); this.devDependencies = Objects.requireNonNull(devDependencies); } - public void config(final Map config) { + public TypescriptFormatExtension config(final Map config) { this.config = new TreeMap<>(requireNonNull(config)); - replaceStep(createStep()); + replaceStep(); + return this; } - public void tsconfigFile(final Object path) { - configFile(TsConfigFileType.TSCONFIG, path); + public TypescriptFormatExtension tsconfigFile(final Object path) { + return configFile(TsConfigFileType.TSCONFIG, path); } - public void tslintFile(final Object path) { - configFile(TsConfigFileType.TSLINT, path); + public TypescriptFormatExtension tslintFile(final Object path) { + return configFile(TsConfigFileType.TSLINT, path); } - public void vscodeFile(final Object path) { - configFile(TsConfigFileType.VSCODE, path); + public TypescriptFormatExtension vscodeFile(final Object path) { + return configFile(TsConfigFileType.VSCODE, path); } - public void tsfmtFile(final Object path) { - configFile(TsConfigFileType.TSFMT, path); + public TypescriptFormatExtension tsfmtFile(final Object path) { + return configFile(TsConfigFileType.TSFMT, path); } - private void configFile(TsConfigFileType filetype, Object path) { + private TypescriptFormatExtension configFile(TsConfigFileType filetype, Object path) { this.configFileType = requireNonNull(filetype); this.configFilePath = requireNonNull(path); - replaceStep(createStep()); + replaceStep(); + return this; } public FormatterStep createStep() { final Project project = getProject(); - return TsFmtFormatterStep.create( - devDependencies, - provisioner(), - project.getBuildDir(), - new NpmPathResolver(npmFileOrNull(), npmrcFileOrNull(), project.getProjectDir(), project.getRootDir()), - typedConfigFile(), - config); + return TsFmtFormatterStep + .create(devDependencies, provisioner(), project.getProjectDir(), + project.getLayout().getBuildDirectory().getAsFile().get(), npmModulesCacheOrNull(), + new NpmPathResolver(npmFileOrNull(), nodeFileOrNull(), npmrcFileOrNull(), + Arrays.asList(project.getProjectDir(), project.getRootDir())), + typedConfigFile(), config); } private TypedTsFmtConfigFile typedConfigFile() { @@ -144,7 +155,8 @@ public PrettierConfig prettier(Map devDependencies) { } /** - * Overrides the parser to be set to typescript, no matter what the user's config says. + * Overrides the parser to be set to typescript, no matter what the user's + * config says. */ public class TypescriptPrettierConfig extends PrettierConfig { TypescriptPrettierConfig(Map devDependencies) { @@ -152,26 +164,164 @@ public class TypescriptPrettierConfig extends PrettierConfig { } @Override - FormatterStep createStep() { + protected FormatterStep createStep() { fixParserToTypescript(); return super.createStep(); } private void fixParserToTypescript() { if (this.prettierConfig == null) { - this.prettierConfig = Collections.singletonMap("parser", "typescript"); + this.prettierConfig = new TreeMap<>(Collections.singletonMap("parser", "typescript")); } else { final Object replaced = this.prettierConfig.put("parser", "typescript"); if (replaced != null) { - getProject().getLogger().warn("overriding parser option to 'typescript'. Was set to '{}'", replaced); + getProject().getLogger().warn("overriding parser option to 'typescript'. Was set to '{}'", + replaced); } } } } + public TypescriptEslintConfig eslint() { + return eslint(EslintFormatterStep.defaultDevDependenciesForTypescript()); + } + + public TypescriptEslintConfig eslint(String version) { + return eslint(EslintFormatterStep.defaultDevDependenciesTypescriptWithEslint(version)); + } + + public TypescriptEslintConfig eslint(Map devDependencies) { + TypescriptEslintConfig eslint = new TypescriptEslintConfig(devDependencies); + addStep(eslint.createStep()); + return eslint; + } + + public class TypescriptEslintConfig extends EslintBaseConfig { + + @Nullable + Object typescriptConfigFilePath = null; + + public TypescriptEslintConfig(Map devDependencies) { + super(getProject(), TypescriptExtension.this::replaceStep, devDependencies); + } + + public TypescriptEslintConfig tsconfigFile(Object path) { + this.typescriptConfigFilePath = requireNonNull(path); + replaceStep(); + return this; + } + + public FormatterStep createStep() { + final Project project = getProject(); + + return EslintFormatterStep.create(devDependencies, provisioner(), project.getProjectDir(), + project.getLayout().getBuildDirectory().getAsFile().get(), npmModulesCacheOrNull(), + new NpmPathResolver(npmFileOrNull(), nodeFileOrNull(), npmrcFileOrNull(), + Arrays.asList(project.getProjectDir(), project.getRootDir())), + eslintConfig()); + } + + protected EslintConfig eslintConfig() { + return new EslintTypescriptConfig(configFilePath != null ? getProject().file(configFilePath) : null, + configJs, typescriptConfigFilePath != null ? getProject().file(typescriptConfigFilePath) : null); + } + } + + /** + * Defaults to downloading the default Biome version from the network. To work + * offline, you can specify the path to the Biome executable via + * {@code biome().pathToExe(...)}. + */ + public BiomeTs biome() { + return biome(null); + } + + /** Downloads the given Biome version from the network. */ + public BiomeTs biome(String version) { + var biomeConfig = new BiomeTs(version); + addStep(biomeConfig.createStep()); + return biomeConfig; + } + + /** + * Defaults to downloading the default Rome version from the network. To work + * offline, you can specify the path to the Rome executable via + * {@code rome().pathToExe(...)}. + * + * @deprecated Use {@link #biome()}. + */ + @Deprecated + public RomeTs rome() { + return rome(null); + } + + /** + * Downloads the given Rome version from the network. + * + * @deprecated Use {@link #biome(String)}. + */ + @Deprecated + public RomeTs rome(String version) { + var romeConfig = new RomeTs(version); + addStep(romeConfig.createStep()); + return romeConfig; + } + + /** + * Biome formatter step for TypeScript. + */ + public class BiomeTs extends RomeStepConfig { + /** + * Creates a new Biome formatter step config for formatting TypeScript files. + * Unless overwritten, the given Biome version is downloaded from the network. + * + * @param version Biome version to use. + */ + public BiomeTs(String version) { + super(getProject(), TypescriptExtension.this::replaceStep, BiomeFlavor.BIOME, version); + } + + @Override + protected String getLanguage() { + return "ts?"; + } + + @Override + protected BiomeTs getThis() { + return this; + } + } + + /** + * Rome formatter step for TypeScript. + * + * @deprecated Rome has transitioned to Biome. This will be removed shortly. + */ + @Deprecated + public class RomeTs extends RomeStepConfig { + /** + * Creates a new Rome formatter step config for formatting TypeScript files. + * Unless overwritten, the given Rome version is downloaded from the network. + * + * @param version Rome version to use. + */ + public RomeTs(String version) { + super(getProject(), TypescriptExtension.this::replaceStep, BiomeFlavor.ROME, version); + } + + @Override + protected String getLanguage() { + return "ts?"; + } + + @Override + protected RomeTs getThis() { + return this; + } + } + @Override protected void setupTask(SpotlessTask task) { - // defaults to all typescript files if (target == null) { throw noDefaultTargetException(); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java new file mode 100644 index 0000000000..c0872e8fb9 --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/YamlExtension.java @@ -0,0 +1,81 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import java.util.Collections; + +import javax.inject.Inject; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.yaml.JacksonYamlConfig; +import com.diffplug.spotless.yaml.JacksonYamlStep; + +public class YamlExtension extends FormatExtension { + static final String NAME = "yaml"; + + @Inject + public YamlExtension(SpotlessExtension spotless) { + super(spotless); + } + + @Override + protected void setupTask(SpotlessTask task) { + if (target == null) { + throw noDefaultTargetException(); + } + super.setupTask(task); + } + + public JacksonYamlGradleConfig jackson() { + return new JacksonYamlGradleConfig(this); + } + + public class JacksonYamlGradleConfig extends AJacksonGradleConfig { + protected JacksonYamlConfig jacksonConfig; + + public JacksonYamlGradleConfig(JacksonYamlConfig jacksonConfig, FormatExtension formatExtension) { + super(jacksonConfig, formatExtension); + + this.jacksonConfig = jacksonConfig; + + formatExtension.addStep(createStep()); + } + + public JacksonYamlGradleConfig(FormatExtension formatExtension) { + this(new JacksonYamlConfig(), formatExtension); + } + + /** + * Refers to com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature + */ + public JacksonYamlGradleConfig yamlFeature(String feature, boolean toggle) { + this.jacksonConfig.appendYamlFeatureToToggle(Collections.singletonMap(feature, toggle)); + formatExtension.replaceStep(createStep()); + return this; + } + + @Override + public JacksonYamlGradleConfig self() { + return this; + } + + // 'final' as it is called in the constructor + @Override + protected final FormatterStep createStep() { + return JacksonYamlStep.create(jacksonConfig, version, provisioner()); + } + } +} diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/BiomeIntegrationTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/BiomeIntegrationTest.java new file mode 100644 index 0000000000..db4ca4b027 --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/BiomeIntegrationTest.java @@ -0,0 +1,371 @@ +/* + * Copyright 2023-2024 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; +import org.owasp.encoder.Encode; + +class BiomeIntegrationTest extends GradleIntegrationHarness { + /** + * Tests that biome can be used as a generic formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void asGenericStep() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'mybiome', {", + " target '**/*.js'", + " biome('1.2.0')", + " }", + "}"); + setFile("biome_test.js").toResource("biome/js/fileBefore.js"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("biome_test.js").sameAsResource("biome/js/fileAfter.js"); + } + + /** + * Tests that biome can be used as a JavaScript formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void asJavaScriptStep() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " javascript {", + " target '**/*.js'", + " biome('1.2.0')", + " }", + "}"); + setFile("biome_test.js").toResource("biome/js/fileBefore.js"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("biome_test.js").sameAsResource("biome/js/fileAfter.js"); + } + + /** + * Tests that biome can be used as a JSON formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void asJsonStep() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " json {", + " target '**/*.json'", + " biome('1.2.0')", + " }", + "}"); + setFile("biome_test.json").toResource("biome/json/fileBefore.json"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("biome_test.json").sameAsResource("biome/json/fileAfter.json"); + } + + /** + * Tests that biome can be used as a TypeScript formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void asTypeScriptStep() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " typescript {", + " target '**/*.ts'", + " biome('1.2.0')", + " }", + "}"); + setFile("biome_test.ts").toResource("biome/ts/fileBefore.ts"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("biome_test.ts").sameAsResource("biome/ts/fileAfter.ts"); + } + + /** + * Tests that the language can be specified for the generic format step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void canSetLanguageForGenericStep() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'mybiome', {", + " target '**/*.nosj'", + " biome('1.2.0').language('json')", + " }", + "}"); + setFile("biome_test.nosj").toResource("biome/json/fileBefore.json"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("biome_test.nosj").sameAsResource("biome/json/fileAfter.json"); + } + + /** + * Tests that an absolute config path can be specified. + * + * @throws Exception When a test failure occurs. + */ + @Test + void configPathAbsolute() throws Exception { + var path = newFile("configs").getAbsolutePath(); + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'mybiome', {", + " target '**/*.js'", + " biome('1.2.0').configPath('" + Encode.forJava(path) + "')", + " }", + "}"); + setFile("biome_test.js").toResource("biome/js/longLineBefore.js"); + setFile("configs/biome.json").toResource("biome/config/line-width-120.json"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("biome_test.js").sameAsResource("biome/js/longLineAfter120.js"); + } + + /** + * Tests that a path to the directory with the biome.json config file can be + * specified. Uses a config file with a line width of 120. + * + * @throws Exception When a test failure occurs. + */ + @Test + void configPathLineWidth120() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'mybiome', {", + " target '**/*.js'", + " biome('1.2.0').configPath('configs')", + " }", + "}"); + setFile("biome_test.js").toResource("biome/js/longLineBefore.js"); + setFile("configs/biome.json").toResource("biome/config/line-width-120.json"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("biome_test.js").sameAsResource("biome/js/longLineAfter120.js"); + } + + /** + * Tests that a path to the directory with the biome.json config file can be + * specified. Uses a config file with a line width of 80. + * + * @throws Exception When a test failure occurs. + */ + @Test + void configPathLineWidth80() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'mybiome', {", + " target '**/*.js'", + " biome('1.2.0').configPath('configs')", + " }", + "}"); + setFile("biome_test.js").toResource("biome/js/longLineBefore.js"); + setFile("configs/biome.json").toResource("biome/config/line-width-80.json"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("biome_test.js").sameAsResource("biome/js/longLineAfter80.js"); + } + + /** + * Tests that the download directory can be an absolute path. + * + * @throws Exception When a test failure occurs. + */ + @Test + void downloadDirAbsolute() throws Exception { + var path = newFile("target/bin/biome").getAbsoluteFile().toString(); + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'mybiome', {", + " target '**/*.js'", + " biome('1.2.0').downloadDir('" + Encode.forJava(path) + "')", + " }", + "}"); + setFile("biome_test.js").toResource("biome/js/fileBefore.js"); + assertTrue(!newFile("target/bin/biome").exists() || newFile("target/bin/biome").list().length == 0); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("biome_test.js").sameAsResource("biome/js/fileAfter.js"); + assertEquals(2, newFile("target/bin/biome").list().length); + } + + /** + * Tests that the download directory can be changed to a path relative to the + * project's base directory. + * + * @throws Exception When a test failure occurs. + */ + @Test + void downloadDirRelative() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'mybiome', {", + " target '**/*.js'", + " biome('1.2.0').downloadDir('target/bin/biome')", + " }", + "}"); + setFile("biome_test.js").toResource("biome/js/fileBefore.js"); + assertTrue(!newFile("target/bin/biome").exists() || newFile("target/bin/biome").list().length == 0); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("biome_test.js").sameAsResource("biome/js/fileAfter.js"); + assertEquals(2, newFile("target/bin/biome").list().length); + } + + /** + * Tests that the build fails when given Biome executable does not exist. + * + * @throws Exception When a test failure occurs. + */ + @Test + void failureWhenExeNotFound() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'mybiome', {", + " target '**/*.js'", + " biome('1.2.0').pathToExe('biome/is/missing')", + " }", + "}"); + setFile("biome_test.js").toResource("biome/js/fileBefore.js"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").buildAndFail(); + assertThat(spotlessApply.getOutput()).contains("Build failed with an exception"); + assertFile("biome_test.js").sameAsResource("biome/js/fileBefore.js"); + assertThat(spotlessApply.getOutput()).contains("Could not create task ':spotlessMybiomeApply'"); + assertThat(spotlessApply.getOutput()).contains("Biome executable does not exist"); + } + + /** + * Tests that the build fails when the input file could not be parsed. + * + * @throws Exception When a test failure occurs. + */ + @Test + void failureWhenNotParseable() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'mybiome', {", + " target '**/*.js'", + " biome('1.2.0').language('json')", + " }", + "}"); + setFile("biome_test.js").toResource("biome/js/fileBefore.js"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").buildAndFail(); + assertThat(spotlessApply.getOutput()).contains("spotlessMybiome FAILED"); + assertFile("biome_test.js").sameAsResource("biome/js/fileBefore.js"); + assertThat(spotlessApply.getOutput()).contains("Format with errors is disabled."); + assertThat(spotlessApply.getOutput()).contains("Step 'biome' found problem in 'biome_test.js'"); + } + + /** + * Biome is hard-coded to ignore certain files, such as package.json. Since version 1.5.0, + * the biome CLI does not output any formatted code anymore, whereas previously it printed + * the input as-is. This tests checks that when the biome formatter outputs an empty string, + * the contents of the file to format are used instead. + * + * @throws Exception When a test failure occurs. + */ + @Test + void preservesIgnoredFiles() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " json {", + " target '**/*.json'", + " biome('1.5.0')", + " }", + "}"); + setFile("package.json").toResource("biome/json/packageBefore.json"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("package.json").sameAsResource("biome/json/packageAfter.json"); + } +} diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/BufIntegrationTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/BufIntegrationTest.java new file mode 100644 index 0000000000..b91512c7ed --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/BufIntegrationTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2022-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.tag.BufTest; + +@BufTest +class BufIntegrationTest extends GradleIntegrationHarness { + @Test + void buf() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "spotless {", + " protobuf {", + " buf()", + " }", + "}"); + setFile("buf.proto").toResource("protobuf/buf/buf.proto"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("buf.proto").sameAsResource("protobuf/buf/buf.proto.clean"); + } + + @Test + void bufWithLicense() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "spotless {", + " protobuf {", + " buf()", + " licenseHeader '/* (C) 2022 */'", + " }", + "}"); + setFile("license.proto").toResource("protobuf/buf/license.proto"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("license.proto").sameAsResource("protobuf/buf/license.proto.clean"); + } +} diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/CleanthatJavaIntegrationTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/CleanthatJavaIntegrationTest.java new file mode 100644 index 0000000000..ad6fff9112 --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/CleanthatJavaIntegrationTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2022-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +class CleanthatJavaIntegrationTest extends GradleIntegrationHarness { + @Test + void integration() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "", + "spotless {", + " java {", + " target file('test.java')", + " cleanthat()", + " .sourceCompatibility('11')", + " .addMutators(['LiteralsFirstInComparisons', 'OptionalNotEmpty'])", + " }", + "}"); + + setFile("test.java").toResource("java/cleanthat/MultipleMutators.dirty.test"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("test.java").sameAsResource("java/cleanthat/MultipleMutators.clean.test"); + } +} diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ConfigAvoidanceTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ConfigAvoidanceTest.java index d5422a3631..bd521bd864 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ConfigAvoidanceTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ConfigAvoidanceTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ void noConfigOnHelp() throws IOException { "apply plugin: 'java'", "spotless {", " java {", - " googleJavaFormat('1.2')", + " googleJavaFormat()", " }", "}", "", diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ConfigurationCacheTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ConfigurationCacheTest.java index 4006752af2..70be5adb82 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ConfigurationCacheTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ConfigurationCacheTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,8 @@ import org.gradle.testkit.runner.BuildResult; import org.gradle.testkit.runner.GradleRunner; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; public class ConfigurationCacheTest extends GradleIntegrationHarness { @Override @@ -39,7 +41,7 @@ public void helpConfigures() throws IOException { "apply plugin: 'java'", "spotless {", " java {", - " googleJavaFormat('1.2')", + " googleJavaFormat()", " }", "}"); gradleRunner().withArguments("help").build(); @@ -55,7 +57,7 @@ public void helpConfiguresIfTasksAreCreated() throws IOException { "apply plugin: 'java'", "spotless {", " java {", - " googleJavaFormat('1.2')", + " googleJavaFormat()", " }", "}", "tasks.named('spotlessJavaApply').get()"); @@ -63,6 +65,7 @@ public void helpConfiguresIfTasksAreCreated() throws IOException { } @Test + @EnabledForJreRange(max = JRE.JAVA_20) public void jvmLocalCache() throws IOException { setFile("build.gradle").toLines( "plugins {", @@ -72,7 +75,7 @@ public void jvmLocalCache() throws IOException { "spotless {", " java {", " target file('test.java')", - " googleJavaFormat('1.2')", + " googleJavaFormat()", " }", "}"); @@ -91,6 +94,10 @@ public void jvmLocalCache() throws IOException { gradleRunner().withArguments("spotlessApply").build(); assertFile("test.java").sameAsResource("java/googlejavaformat/JavaCodeFormatted.test"); + // the withDebug forces it to start a new deamon, but only in Gradle 8.3 and older + // starting with Gradle 8.5 this doesn't work anymore + // and we need Gradle 8.5 for Java 21 + // so we can't test this on Java 21 for now BuildResult failure = gradleRunner().withDebug(true).withArguments("spotlessApply", "--stacktrace").buildAndFail(); failure.getOutput().contains("Spotless daemon-local cache is stale. Regenerate the cache with\n" + " rm -rf .gradle/configuration-cache\n" + diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/DiffMessageFormatterTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/DiffMessageFormatterTest.java index ef72043a47..bc556ac06e 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/DiffMessageFormatterTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/DiffMessageFormatterTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,7 +62,7 @@ public BuildServiceParameters.None getParameters() { private SpotlessTaskImpl createFormatTask(String name) { SpotlessTaskImpl task = project.getTasks().create("spotless" + SpotlessPlugin.capitalize(name), SpotlessTaskImpl.class); task.init(taskService); - task.setLineEndingsPolicy(LineEnding.UNIX.createPolicy()); + task.setLineEndingsPolicy(project.provider(LineEnding.UNIX::createPolicy)); task.setTarget(Collections.singletonList(file)); return task; } @@ -100,7 +100,7 @@ private Bundle create(File... files) throws IOException { private Bundle create(List files) throws IOException { Bundle bundle = new Bundle("underTest"); - bundle.task.setLineEndingsPolicy(LineEnding.UNIX.createPolicy()); + bundle.task.setLineEndingsPolicy(bundle.project.provider(LineEnding.UNIX::createPolicy)); bundle.task.setTarget(files); return bundle; } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/FilePermissionsTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/FilePermissionsTest.java index a5ce2d11cd..d342348a5b 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/FilePermissionsTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/FilePermissionsTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,7 @@ void spotlessApplyShouldPreservePermissions() throws IOException { "spotless {", " java {", " target file('test.java')", - " googleJavaFormat('1.2')", + " googleJavaFormat()", " }", "}"); setFile("test.java").toResource("java/googlejavaformat/JavaCodeUnformatted.test"); diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/FlexmarkExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/FlexmarkExtensionTest.java new file mode 100644 index 0000000000..6264c0c230 --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/FlexmarkExtensionTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +class FlexmarkExtensionTest extends GradleIntegrationHarness { + @Test + void integration() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'java'", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " flexmark {", + " target '*.md'", + " flexmark()", + " }", + "}"); + setFile("markdown_test.md").toResource("markdown/flexmark/FlexmarkUnformatted.md"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("markdown_test.md").sameAsResource("markdown/flexmark/FlexmarkFormatted.md"); + } +} diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/FormatTaskTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/FormatTaskTest.java index 8985cd7a14..ddd813468d 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/FormatTaskTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/FormatTaskTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,7 @@ class FormatTaskTest extends ResourceHarness { void createTask() { Project project = TestProvisioner.gradleProject(rootFolder()); spotlessTask = project.getTasks().create("spotlessTaskUnderTest", SpotlessTaskImpl.class); - spotlessTask.setLineEndingsPolicy(LineEnding.UNIX.createPolicy()); + spotlessTask.setLineEndingsPolicy(project.provider(LineEnding.UNIX::createPolicy)); spotlessTask.init(GradleIntegrationHarness.providerOf(new SpotlessTaskService() { @Override public BuildServiceParameters.None getParameters() { diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GherkinExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GherkinExtensionTest.java new file mode 100644 index 0000000000..b78094cf18 --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GherkinExtensionTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +public class GherkinExtensionTest extends GradleIntegrationHarness { + @Test + public void defaultFormatting() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'java'", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " gherkin {", + " target 'examples/**/*.feature'", + " gherkinUtils()", + " }", + "}"); + setFile("src/main/resources/example.feature").toResource("gherkin/minimalBefore.feature"); + setFile("examples/main/resources/example.feature").toResource("gherkin/minimalBefore.feature"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("src/main/resources/example.feature").sameAsResource("gherkin/minimalBefore.feature"); + assertFile("examples/main/resources/example.feature").sameAsResource("gherkin/minimalAfter.feature"); + } + +} diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GitRatchetGradleTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GitRatchetGradleTest.java index 51f4808259..0398809f90 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GitRatchetGradleTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GitRatchetGradleTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,7 +54,8 @@ protected GradleRunner gradleRunner() throws IOException { } @ParameterizedTest - @ValueSource(ints = {0, 1}) + //@ValueSource(ints = {0, 1}) // TODO: this is a flaky configuration cache issue that started with Gradle 8.5 + @ValueSource(ints = {0}) void singleProjectExhaustive(int useConfigCache) throws Exception { try (Git git = initRepo()) { if (useConfigCache == 1) { @@ -153,7 +154,8 @@ private BuildResultAssertion assertFail(String... tasks) throws Exception { private static final String BASELINE_DIRTY = "4cfc3358ccbf186738b82a60276b1e5306bc3870"; @ParameterizedTest - @ValueSource(ints = {0, 1}) + //@ValueSource(ints = {0, 1}) // TODO: this is a flaky configuration cache issue that started with Gradle 8.5 + @ValueSource(ints = {0}) void multiProject(int useConfigCache) throws Exception { try (Git git = initRepo()) { if (useConfigCache == 1) { diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GoGradleTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GoGradleTest.java new file mode 100644 index 0000000000..94bd345854 --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GoGradleTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 2020-2024 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.tag.GofmtTest; + +@GofmtTest +class GoGradleTest extends GradleIntegrationHarness { + @Test + void gofmt() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "spotless {", + " go {", + " target 'src/**/*.go'", + " gofmt(\"go1.21.5\")", + " }", + "}"); + setFile("src/test.go").toResource("go/gofmt/go.dirty"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("src/test.go").sameAsResource("go/gofmt/go.clean"); + } +} diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GoogleJavaFormatIntegrationTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GoogleJavaFormatIntegrationTest.java index 1f5cb6af05..e7fc9d4b74 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GoogleJavaFormatIntegrationTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GoogleJavaFormatIntegrationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ void integration() throws IOException { "spotless {", " java {", " target file('test.java')", - " googleJavaFormat('1.2')", + " googleJavaFormat('1.17.0')", " }", "}"); @@ -41,8 +41,60 @@ void integration() throws IOException { checkRunsThenUpToDate(); replace("build.gradle", - "googleJavaFormat('1.2')", - "googleJavaFormat('1.3')"); + "googleJavaFormat('1.17.0')", + "googleJavaFormat()"); + checkRunsThenUpToDate(); + } + + @Test + void integrationWithReorderImports() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "", + "spotless {", + " java {", + " target file('test.java')", + " googleJavaFormat('1.17.0').aosp().reorderImports(true)", + " }", + "}"); + + setFile("test.java").toResource("java/googlejavaformat/JavaWithReorderImportsUnformatted.test"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("test.java").sameAsResource("java/googlejavaformat/JavaWithReorderImportsEnabledFormatted.test"); + + checkRunsThenUpToDate(); + replace("build.gradle", + "googleJavaFormat('1.17.0')", + "googleJavaFormat()"); + checkRunsThenUpToDate(); + } + + @Test + void integrationWithSkipJavadocFormatting() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "", + "spotless {", + " java {", + " target file('test.java')", + " googleJavaFormat('1.17.0').skipJavadocFormatting()", + " }", + "}"); + + setFile("test.java").toResource("java/googlejavaformat/JavaCodeUnformatted.test"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("test.java").sameAsResource("java/googlejavaformat/JavaCodeFormattedSkipJavadocFormatting.test"); + + checkRunsThenUpToDate(); + replace("build.gradle", + "googleJavaFormat('1.17.0')", + "googleJavaFormat()"); checkRunsThenUpToDate(); } } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GradleIntegrationHarness.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GradleIntegrationHarness.java index 6cc35290db..417bc23b52 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GradleIntegrationHarness.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GradleIntegrationHarness.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import java.io.IOException; import java.util.List; import java.util.ListIterator; +import java.util.function.BiConsumer; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -42,7 +43,7 @@ public class GradleIntegrationHarness extends ResourceHarness { public enum GradleVersionSupport { - JRE_11("5.0"), MINIMUM(SpotlessPlugin.MINIMUM_GRADLE), + JRE_11("5.0"), MINIMUM(SpotlessPlugin.VER_GRADLE_min), // technically, this API exists in 6.5, but the flags for it change in 6.6, so we build to that CONFIGURATION_CACHE("6.6"), // https://docs.gradle.org/7.5/userguide/configuration_cache.html#config_cache:stable @@ -53,9 +54,17 @@ public enum GradleVersionSupport { GradleVersionSupport(String version) { String minVersionForRunningJRE; switch (Jvm.version()) { + case 22: + // TODO: https://docs.gradle.org/current/userguide/compatibility.html + case 21: + minVersionForRunningJRE = "8.5"; + break; case 20: + minVersionForRunningJRE = "8.3"; + break; case 19: - // TODO: https://docs.gradle.org/current/userguide/compatibility.html + minVersionForRunningJRE = "7.6"; + break; case 18: minVersionForRunningJRE = "7.5"; break; @@ -90,12 +99,12 @@ public static Provider providerOf(T value) { /** * Each test gets its own temp folder, and we create a gradle * build there and run it. - * + *

* Because those test folders don't have a .gitattributes file, * git (on windows) will default to \r\n. So now if you read a * test file from the spotless test resources, and compare it * to a build result, the line endings won't match. - * + *

* By sticking this .gitattributes file into the test directory, * we ensure that the default Spotless line endings policy of * GIT_ATTRIBUTES will use \n, so that tests match the test @@ -110,15 +119,39 @@ protected GradleRunner gradleRunner() throws IOException { return GradleRunner.create() .withGradleVersion(GradleVersionSupport.MINIMUM.version) .withProjectDir(rootFolder()) + .withTestKitDir(getTestKitDir()) .withPluginClasspath(); } /** Dumps the complete file contents of the folder to the console. */ - protected String getContents() throws IOException { + protected String getContents() { return getContents(subPath -> !subPath.startsWith(".gradle")); } - protected String getContents(Predicate subpathsToInclude) throws IOException { + protected String getContents(Predicate subpathsToInclude) { + return StringPrinter.buildString(printer -> Errors.rethrow().run(() -> iterateFiles(subpathsToInclude, (subpath, file) -> { + printer.println("### " + subpath + " ###"); + try { + printer.println(read(subpath)); + } catch (IOException e) { + throw new RuntimeException(e); + } + }))); + } + + /** Dumps the filtered file listing of the folder to the console. */ + protected String listFiles(Predicate subpathsToInclude) { + return StringPrinter.buildString(printer -> iterateFiles(subpathsToInclude, (subPath, file) -> { + printer.println(subPath + " [" + getFileAttributes(file) + "]"); + })); + } + + /** Dumps the file listing of the folder to the console. */ + protected String listFiles() { + return listFiles(subPath -> !subPath.startsWith(".gradle")); + } + + protected void iterateFiles(Predicate subpathsToInclude, BiConsumer consumer) { TreeDef treeDef = TreeDef.forFile(Errors.rethrow()); List files = TreeStream.depthFirst(treeDef, rootFolder()) .filter(File::isFile) @@ -126,16 +159,17 @@ protected String getContents(Predicate subpathsToInclude) throws IOExcep ListIterator iterator = files.listIterator(files.size()); int rootLength = rootFolder().getAbsolutePath().length() + 1; - return StringPrinter.buildString(printer -> Errors.rethrow().run(() -> { - while (iterator.hasPrevious()) { - File file = iterator.previous(); - String subPath = file.getAbsolutePath().substring(rootLength); - if (subpathsToInclude.test(subPath)) { - printer.println("### " + subPath + " ###"); - printer.println(read(subPath)); - } + while (iterator.hasPrevious()) { + File file = iterator.previous(); + String subPath = file.getAbsolutePath().substring(rootLength); + if (subpathsToInclude.test(subPath)) { + consumer.accept(subPath, file); } - })); + } + } + + protected String getFileAttributes(File file) { + return (file.canRead() ? "r" : "-") + (file.canWrite() ? "w" : "-") + (file.canExecute() ? "x" : "-"); } protected void checkRunsThenUpToDate() throws IOException { @@ -188,4 +222,12 @@ static String buildResultToString(BuildResult result) { } }); } + + private static File getTestKitDir() { + String gradleUserHome = System.getenv("GRADLE_USER_HOME"); + if (gradleUserHome == null || gradleUserHome.isEmpty()) { + gradleUserHome = new File(System.getProperty("user.home"), ".gradle").getAbsolutePath(); + } + return new File(gradleUserHome, "testkit"); + } } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GroovyExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GroovyExtensionTest.java index b9c7d2f66e..7f87d8793c 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GroovyExtensionTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GroovyExtensionTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -88,6 +88,27 @@ void excludeJavaWithCustomTarget() throws IOException { assertThat(error).hasMessageContaining("'excludeJava' is not supported"); } + @Test + void removeSemicolons() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "apply plugin: 'groovy'", + "", + "spotless {", + " groovy {", + " removeSemicolons()", + " }", + "}"); + + String withSemicolons = getTestResource("groovy/removeSemicolons/GroovyCodeWithSemicolons.test"); + String withoutSemicolons = getTestResource("groovy/removeSemicolons/GroovyCodeWithSemicolonsFormatted.test"); + setFile("src/main/groovy/test.groovy").toContent(withSemicolons); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("src/main/groovy/test.groovy").hasContent(withoutSemicolons); + } + @Test void groovyPluginMissingCheck() throws IOException { setFile("build.gradle").toLines( @@ -103,7 +124,7 @@ void groovyPluginMissingCheck() throws IOException { Throwable error = assertThrows(Throwable.class, () -> gradleRunner().withArguments("spotlessApply").build()); - assertThat(error).hasMessageContaining("must apply the groovy plugin before"); + assertThat(error).hasMessageContaining("You must either specify 'target' manually or apply the 'groovy' plugin."); } } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GroovyGradleExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GroovyGradleExtensionTest.java index 7f4d593a9c..e94d269644 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GroovyGradleExtensionTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GroovyGradleExtensionTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,24 +17,20 @@ import java.io.IOException; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import com.diffplug.common.base.StringPrinter; -class GroovyGradleExtensionTest extends GradleIntegrationHarness { +/** + * We test GroovyGradleExtension only behaviors here. + */ +class GroovyGradleExtensionTest extends GroovyExtensionTest { private static final String HEADER = "//My tests header"; - @Test - void defaultTarget() throws IOException { - testTarget(true); - } - - @Test - void customTarget() throws IOException { - testTarget(false); - } - - private void testTarget(boolean useDefaultTarget) throws IOException { + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void testTarget(boolean useDefaultTarget) throws IOException { String target = useDefaultTarget ? "" : "target 'other.gradle'"; String buildContent = StringPrinter.buildStringFromLines( "plugins {", diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/IndependentTaskTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/IndependentTaskTest.java index 459b871b94..4062aae51d 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/IndependentTaskTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/IndependentTaskTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,7 @@ void independent() throws IOException { "", "def underTest = new JavaExtension(spotless)", "underTest.target file('test.java')", - "underTest.googleJavaFormat('1.2')", + "underTest.googleJavaFormat()", "", "def independent = underTest.createIndependentApplyTask('independent')"); setFile("test.java").toResource("java/googlejavaformat/JavaCodeUnformatted.test"); diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JavaDefaultTargetTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JavaDefaultTargetTest.java index 898008c27a..eed4cf1a4d 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JavaDefaultTargetTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JavaDefaultTargetTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,7 +32,7 @@ void integration() throws IOException { "", "spotless {", " java {", - " googleJavaFormat('1.2')", + " googleJavaFormat()", " }", "}"); setFile("src/main/java/test.java").toResource("java/googlejavaformat/JavaCodeUnformatted.test"); @@ -62,4 +62,22 @@ void multipleBlocksShouldWork() throws IOException { gradleRunner().withArguments("spotlessApply").build(); gradleRunner().withArguments("spotlessApply").build(); } + + @Test + void removeUnusedImportsWithCleanthat() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + " id 'java'", + "}", + "repositories { mavenCentral() }", + "", + "spotless {", + " java { removeUnusedImports('cleanthat-javaparser-unnecessaryimport') }", + "}"); + + setFile("src/main/java/test.java").toResource("java/removeunusedimports/Jdk17TextBlockUnformatted.test"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("src/main/java/test.java").sameAsResource("java/removeunusedimports/Jdk17TextBlockFormatted.test"); + } } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JavascriptExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JavascriptExtensionTest.java new file mode 100644 index 0000000000..f1b00706d3 --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JavascriptExtensionTest.java @@ -0,0 +1,208 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import java.io.IOException; + +import org.assertj.core.api.Assertions; +import org.gradle.testkit.runner.BuildResult; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.diffplug.spotless.npm.EslintFormatterStep; +import com.diffplug.spotless.npm.EslintStyleGuide; +import com.diffplug.spotless.tag.NpmTest; + +@NpmTest +class JavascriptExtensionTest extends GradleIntegrationHarness { + + private static String styleGuideMapString(String styleGuideName) { + return EslintStyleGuide.fromNameOrNull(styleGuideName).asGradleMapStringMergedWith(EslintFormatterStep.defaultDevDependencies()); + } + + @NpmTest + @Nested + class EslintGeneralJavascriptTests extends GradleIntegrationHarness { + @Test + void supportsEslintFormattingForJavascript() throws IOException { + setFile(".eslintrc.js").toResource("npm/eslint/javascript/styleguide/standard/.eslintrc.js"); + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " javascript {", + " target 'test.js'", + " eslint(" + styleGuideMapString("standard") + ").configFile('.eslintrc.js')", + " }", + "}"); + setFile("test.js").toResource("npm/eslint/javascript/styleguide/standard/javascript-es6.dirty"); + gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertFile("test.js").sameAsResource("npm/eslint/javascript/styleguide/standard/javascript-es6.clean"); + } + + @Test + void eslintAllowsToSpecifyEslintVersionForJavascript() throws IOException { + setFile(".eslintrc.js").toResource("npm/eslint/javascript/custom_rules/.eslintrc.js"); + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " javascript {", + " target 'test.js'", + " eslint('8.28.0').configFile('.eslintrc.js')", + " }", + "}"); + setFile("test.js").toResource("npm/eslint/javascript/custom_rules/javascript-es6.dirty"); + gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertFile("test.js").sameAsResource("npm/eslint/javascript/custom_rules/javascript-es6.clean"); + } + + @Test + void esllintAllowsToSpecifyInlineConfig() throws IOException { + final String eslintConfigJs = String.join("\n", + "{", + " env: {", + " browser: true,", + " es2021: true", + " },", + " extends: 'standard',", + " overrides: [", + " ],", + " parserOptions: {", + " ecmaVersion: 'latest',", + " sourceType: 'module'", + " },", + " rules: {", + " }", + "}"); + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " javascript {", + " target 'test.js'", + " eslint(" + styleGuideMapString("standard") + ").configJs('''" + eslintConfigJs + "''')", + " }", + "}"); + setFile("test.js").toResource("npm/eslint/javascript/styleguide/standard/javascript-es6.dirty"); + gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertFile("test.js").sameAsResource("npm/eslint/javascript/styleguide/standard/javascript-es6.clean"); + } + + @Test + void eslintRequiresAnExplicitEslintConfig() throws IOException { + setFile(".eslintrc.js").toResource("npm/eslint/javascript/styleguide/standard/.eslintrc.js"); + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " javascript {", + " target 'test.js'", + " eslint(" + styleGuideMapString("standard") + ")", + " }", + "}"); + setFile("test.js").toResource("npm/eslint/javascript/styleguide/standard/javascript-es6.dirty"); + BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").buildAndFail(); + Assertions.assertThat(spotlessApply.getOutput()).contains("ESLint must be configured"); + } + + @Test + void eslintAllowsSpecifyingCustomLibraryVersions() throws IOException { + setFile(".eslintrc.js").toResource("npm/eslint/javascript/styleguide/standard/.eslintrc.js"); + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " javascript {", + " target 'test.js'", + " eslint([", + " 'eslint': '8.28.0',", + " 'eslint-config-standard': '17.0.0',", + " 'eslint-plugin-import': '2.26.0',", + " 'eslint-plugin-n': '15.6.0',", + " 'eslint-plugin-promise': '6.1.1'", + " ]).configFile('.eslintrc.js')", + " }", + "}"); + setFile("test.js").toResource("npm/eslint/javascript/styleguide/standard/javascript-es6.dirty"); + gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertFile("test.js").sameAsResource("npm/eslint/javascript/styleguide/standard/javascript-es6.clean"); + } + + } + + @NpmTest + @Nested + class EslintPopularJsStyleGuideTests extends GradleIntegrationHarness { + @ParameterizedTest(name = "{index}: eslint can be applied using styleguide {0}") + @ValueSource(strings = {"airbnb", "google", "standard", "xo"}) + void formattingUsingStyleguide(String styleguide) throws Exception { + + final String styleguidePath = "npm/eslint/javascript/styleguide/" + styleguide + "/"; + + setFile(".eslintrc.js").toResource(styleguidePath + ".eslintrc.js"); + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " javascript {", + " target 'test.js'", + " eslint(" + styleGuideMapString(styleguide) + ").configFile('.eslintrc.js')", + " }", + "}"); + setFile("test.js").toResource(styleguidePath + "javascript-es6.dirty"); + gradleRunner().forwardOutput().withArguments("--info", "--stacktrace", "spotlessApply").build(); + assertFile("test.js").sameAsResource(styleguidePath + "javascript-es6.clean"); + } + } + + @NpmTest + @Nested + class JavascriptPrettierTests extends GradleIntegrationHarness { + @Test + void supportsPrettierFormattingForJavascript() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " javascript {", + " target 'test.js'", + " prettier()", + " }", + "}"); + setFile("test.js").toResource("npm/prettier/filetypes/javascript-es6/javascript-es6.dirty"); + gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertFile("test.js").sameAsResource("npm/prettier/filetypes/javascript-es6/javascript-es6.clean"); + } + } + +} diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JsonExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JsonExtensionTest.java index e2fb9f70aa..73e443b915 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JsonExtensionTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/JsonExtensionTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,9 +30,9 @@ void simpleDefaultFormatting() throws IOException { "repositories { mavenCentral() }", "spotless {", " json {", - " target 'examples/**/*.json'", - " simple()", - "}", + " target 'examples/**/*.json'", + " simple()", + " }", "}"); setFile("src/main/resources/example.json").toResource("json/nestedObjectBefore.json"); setFile("examples/main/resources/example.json").toResource("json/nestedObjectBefore.json"); @@ -51,9 +51,9 @@ void simpleFormattingWithCustomNumberOfSpaces() throws IOException { "repositories { mavenCentral() }", "spotless {", " json {", - " target 'src/**/*.json'", - " simple().indentWithSpaces(6)", - "}", + " target 'src/**/*.json'", + " simple().indentWithSpaces(6)", + " }", "}"); setFile("src/main/resources/example.json").toResource("json/singletonArrayBefore.json"); gradleRunner().withArguments("spotlessApply").build(); @@ -70,9 +70,9 @@ void gsonDefaultFormatting() throws IOException { "repositories { mavenCentral() }", "spotless {", " json {", - " target 'examples/**/*.json'", - " gson()", - "}", + " target 'examples/**/*.json'", + " gson()", + " }", "}"); setFile("src/main/resources/example.json").toResource("json/nestedObjectBefore.json"); setFile("examples/main/resources/example.json").toResource("json/nestedObjectBefore.json"); @@ -91,9 +91,9 @@ void gsonFormattingWithCustomNumberOfSpaces() throws IOException { "repositories { mavenCentral() }", "spotless {", " json {", - " target 'src/**/*.json'", - " gson().indentWithSpaces(6)", - "}", + " target 'src/**/*.json'", + " gson().indentWithSpaces(6)", + " }", "}"); setFile("src/main/resources/example.json").toResource("json/singletonArrayBefore.json"); gradleRunner().withArguments("spotlessApply").build(); @@ -110,9 +110,9 @@ void gsonFormattingWithSortingByKeys() throws IOException { "repositories { mavenCentral() }", "spotless {", " json {", - " target 'src/**/*.json'", - " gson().sortByKeys()", - "}", + " target 'src/**/*.json'", + " gson().sortByKeys()", + " }", "}"); setFile("src/main/resources/example.json").toResource("json/sortByKeysBefore.json"); gradleRunner().withArguments("spotlessApply").build(); @@ -129,13 +129,71 @@ void gsonFormattingWithHtmlEscape() throws IOException { "repositories { mavenCentral() }", "spotless {", " json {", - " target 'src/**/*.json'", - " gson().escapeHtml()", - "}", + " target 'src/**/*.json'", + " gson().escapeHtml()", + " }", "}"); setFile("src/main/resources/example.json").toResource("json/escapeHtmlGsonBefore.json"); gradleRunner().withArguments("spotlessApply").build(); assertFile("src/main/resources/example.json").sameAsResource("json/escapeHtmlGsonAfter.json"); } + @Test + void jacksonFormattingWithSortingByKeys() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'java'", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " json {", + " target 'src/**/*.json'", + " jackson().feature('ORDER_MAP_ENTRIES_BY_KEYS', true)", + " }", + "}"); + setFile("src/main/resources/example.json").toResource("json/sortByKeysBefore.json"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("src/main/resources/example.json").sameAsResource("json/sortByKeysAfter_Jackson.json"); + } + + @Test + void jsonPatchReplaceString() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'java'", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " json {", + " target 'src/**/*.json'", + " jsonPatch([[op: 'replace', path: '/abc', value: 'ghi']])", + " gson()", + " }", + "}"); + setFile("src/main/resources/example.json").toResource("json/patchObjectBefore.json"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("src/main/resources/example.json").sameAsResource("json/patchObjectAfterReplaceString.json"); + } + + @Test + void jsonPatchReplaceWithObject() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'java'", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " json {", + " target 'src/**/*.json'", + " jsonPatch([[op: 'replace', path: '/abc', value: [def: 'ghi']]])", + " gson()", + " }", + "}"); + setFile("src/main/resources/example.json").toResource("json/patchObjectBefore.json"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("src/main/resources/example.json").sameAsResource("json/patchObjectAfterReplaceWithObject.json"); + } } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/KotlinExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/KotlinExtensionTest.java index 5da722ea44..1e96d34574 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/KotlinExtensionTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/KotlinExtensionTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,40 +15,22 @@ */ package com.diffplug.gradle.spotless; -import static org.junit.jupiter.api.condition.JRE.JAVA_11; +import static org.assertj.core.api.Assertions.assertThat; +import java.io.File; import java.io.IOException; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledForJreRange; class KotlinExtensionTest extends GradleIntegrationHarness { private static final String HEADER = "// License Header"; private static final String HEADER_WITH_YEAR = "// License Header $YEAR"; - @Test - void integration() throws IOException { - setFile("build.gradle").toLines( - "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", - " id 'com.diffplug.spotless'", - "}", - "repositories { mavenCentral() }", - "spotless {", - " kotlin {", - " ktlint()", - " }", - "}"); - setFile("src/main/kotlin/Main.kt").toResource("kotlin/ktlint/basic.dirty"); - gradleRunner().withArguments("spotlessApply").build(); - assertFile("src/main/kotlin/Main.kt").sameAsResource("kotlin/ktlint/basic.clean"); - } - @Test void integrationDiktat() throws IOException { setFile("build.gradle").toLines( "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.4.30'", + " id 'org.jetbrains.kotlin.jvm' version '1.6.21'", " id 'com.diffplug.spotless'", "}", "repositories { mavenCentral() }", @@ -63,113 +45,43 @@ void integrationDiktat() throws IOException { } @Test - @EnabledForJreRange(min = JAVA_11) // ktfmt's dependency, google-java-format 1.8 requires a minimum of JRE 11+. - void integrationKtfmt() throws IOException { - setFile("build.gradle").toLines( - "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", - " id 'com.diffplug.spotless'", - "}", - "repositories { mavenCentral() }", - "spotless {", - " kotlin {", - " ktfmt()", - " }", - "}"); - setFile("src/main/kotlin/basic.kt").toResource("kotlin/ktfmt/basic.dirty"); - gradleRunner().withArguments("spotlessApply").build(); - assertFile("src/main/kotlin/basic.kt").sameAsResource("kotlin/ktfmt/basic.clean"); - } - - @Test - @EnabledForJreRange(min = JAVA_11) // ktfmt's dependency, google-java-format 1.8 requires a minimum of JRE 11+. - void integrationKtfmt_dropboxStyle_0_18() throws IOException { - setFile("build.gradle").toLines( - "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", - " id 'com.diffplug.spotless'", - "}", - "repositories { mavenCentral() }", - "spotless {", - " kotlin {", - " ktfmt('0.18').dropboxStyle()", - " }", - "}"); - setFile("src/main/kotlin/basic.kt").toResource("kotlin/ktfmt/basic.dirty"); - gradleRunner().withArguments("spotlessApply").build(); - assertFile("src/main/kotlin/basic.kt").sameAsResource("kotlin/ktfmt/basic-dropboxstyle.clean"); - } - - @Test - @EnabledForJreRange(min = JAVA_11) // ktfmt's dependency, google-java-format 1.8 requires a minimum of JRE 11+. - void integrationKtfmt_dropboxStyle_0_19() throws IOException { - setFile("build.gradle").toLines( + void integrationKtfmtDropboxStyleWithPublicApi() throws IOException { + setFile("build.gradle.kts").toLines( "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", - " id 'com.diffplug.spotless'", + " id(\"org.jetbrains.kotlin.jvm\") version \"1.6.21\"", + " id(\"com.diffplug.spotless\")", "}", "repositories { mavenCentral() }", "spotless {", " kotlin {", - " ktfmt('0.19').dropboxStyle()", + " ktfmt().dropboxStyle().configure {", + " it.setMaxWidth(4)", + " it.setBlockIndent(4)", + " it.setContinuationIndent(4)", + " it.setRemoveUnusedImport(false)", + " }", " }", "}"); setFile("src/main/kotlin/basic.kt").toResource("kotlin/ktfmt/basic.dirty"); gradleRunner().withArguments("spotlessApply").build(); - assertFile("src/main/kotlin/basic.kt").sameAsResource("kotlin/ktfmt/basic-dropboxstyle.clean"); - } - - @Test - void testWithIndentation() throws IOException { - setFile("build.gradle").toLines( - "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", - " id 'com.diffplug.spotless'", - "}", - "repositories { mavenCentral() }", - "spotless {", - " kotlin {", - " ktlint().editorConfigOverride(['indent_size': '6'])", - " }", - "}"); - setFile("src/main/kotlin/Main.kt").toResource("kotlin/ktlint/basic.dirty"); - gradleRunner().withArguments("spotlessApply").build(); - assertFile("src/main/kotlin/Main.kt").sameAsResource("kotlin/ktlint/basic.clean-indent6"); - } - - @Test - void withExperimental() throws IOException { - setFile("build.gradle").toLines( - "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", - " id 'com.diffplug.spotless'", - "}", - "repositories { mavenCentral() }", - "spotless {", - " kotlin {", - " ktlint().setUseExperimental(true)", - " }", - "}"); - setFile("src/main/kotlin/Main.kt").toResource("kotlin/ktlint/experimental.dirty"); - gradleRunner().withArguments("spotlessApply").build(); - assertFile("src/main/kotlin/Main.kt").sameAsResource("kotlin/ktlint/experimental.clean"); + assertFile("src/main/kotlin/basic.kt").sameAsResource("kotlin/ktfmt/basic-dropbox-style.clean"); } @Test void withExperimentalEditorConfigOverride() throws IOException { setFile("build.gradle").toLines( "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", + " id 'org.jetbrains.kotlin.jvm' version '1.6.21'", " id 'com.diffplug.spotless'", "}", "repositories { mavenCentral() }", "spotless {", " kotlin {", - " ktlint().setUseExperimental(true)", - " .editorConfigOverride([", - " ij_kotlin_allow_trailing_comma: true,", - " ij_kotlin_allow_trailing_comma_on_call_site: true", - " ])", + " ktlint().editorConfigOverride([", + " ktlint_experimental: \"enabled\",", + " ij_kotlin_allow_trailing_comma: true,", + " ij_kotlin_allow_trailing_comma_on_call_site: true", + " ])", " }", "}"); setFile("src/main/kotlin/Main.kt").toResource("kotlin/ktlint/experimentalEditorConfigOverride.dirty"); @@ -178,142 +90,110 @@ void withExperimentalEditorConfigOverride() throws IOException { } @Test - void testWithHeader() throws IOException { - setFile("build.gradle").toLines( - "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", - " id 'com.diffplug.spotless'", - "}", - "repositories { mavenCentral() }", - "spotless {", - " kotlin {", - " licenseHeader('" + HEADER + "')", - " ktlint()", - " }", - "}"); - setFile("src/main/kotlin/AnObject.kt").toResource("kotlin/licenseheader/KotlinCodeWithoutHeader.test"); - gradleRunner().withArguments("spotlessApply").build(); - assertFile("src/main/kotlin/AnObject.kt").hasContent(HEADER + "\n" + getTestResource("kotlin/licenseheader/KotlinCodeWithoutHeader.test")); - } + void testWithInvalidEditorConfigFile() throws IOException { + String invalidPath = "invalid/path/to/.editorconfig".replace('/', File.separatorChar); - @Test - @EnabledForJreRange(min = JAVA_11) // ktfmt's dependency, google-java-format 1.8 requires a minimum of JRE 11+. - void testWithHeaderKtfmt() throws IOException { setFile("build.gradle").toLines( "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", + " id 'org.jetbrains.kotlin.jvm' version '1.6.21'", " id 'com.diffplug.spotless'", "}", "repositories { mavenCentral() }", "spotless {", " kotlin {", - " licenseHeader('" + HEADER + "')", - " ktfmt()", + " ktlint().setEditorConfigPath('" + invalidPath.replace("\\", "\\\\") + "')", " }", "}"); - setFile("src/main/kotlin/AnObject.kt").toResource("kotlin/licenseheader/KotlinCodeWithoutHeader.test"); - gradleRunner().withArguments("spotlessApply").build(); - assertFile("src/main/kotlin/AnObject.kt").hasContent(HEADER + "\n" + getTestResource("kotlin/licenseheader/KotlinCodeWithoutHeaderKtfmt.test")); + setFile("src/main/kotlin/Main.kt").toResource("kotlin/ktlint/experimentalEditorConfigOverride.dirty"); + String buildOutput = gradleRunner().withArguments("spotlessApply").buildAndFail().getOutput(); + assertThat(buildOutput).contains("EditorConfig file does not exist: "); + assertThat(buildOutput).contains(invalidPath); } @Test - void testWithCustomHeaderSeparator() throws IOException { + void testReadCodeStyleFromEditorConfigFile() throws IOException { + setFile(".editorconfig").toResource("kotlin/ktlint/ktlint_official/.editorconfig"); setFile("build.gradle").toLines( "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", + " id 'org.jetbrains.kotlin.jvm' version '1.6.21'", " id 'com.diffplug.spotless'", "}", "repositories { mavenCentral() }", "spotless {", " kotlin {", - " licenseHeader ('" + HEADER + "', '@file')", " ktlint()", " }", "}"); - setFile("src/main/kotlin/AnObject.kt").toResource("kotlin/licenseheader/KotlinCodeWithoutHeader.test"); - gradleRunner().withArguments("spotlessApply").build(); - assertFile("src/main/kotlin/AnObject.kt").hasContent(HEADER + "\n" + getTestResource("kotlin/licenseheader/KotlinCodeWithoutHeader.test")); + checkKtlintOfficialStyle(); } @Test - @EnabledForJreRange(min = JAVA_11) // ktfmt's dependency, google-java-format 1.8 requires a minimum of JRE 11+. - void testWithCustomHeaderSeparatorKtfmt() throws IOException { + void testSetEditorConfigCanOverrideEditorConfigFile() throws IOException { + setFile(".editorconfig").toResource("kotlin/ktlint/intellij_idea/.editorconfig"); setFile("build.gradle").toLines( "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", + " id 'org.jetbrains.kotlin.jvm' version '1.6.21'", " id 'com.diffplug.spotless'", "}", "repositories { mavenCentral() }", "spotless {", " kotlin {", - " licenseHeader ('" + HEADER + "', '@file')", - " ktfmt()", + " ktlint().editorConfigOverride([", + " ktlint_code_style: \"ktlint_official\",", + " ])", " }", "}"); - setFile("src/main/kotlin/AnObject.kt").toResource("kotlin/licenseheader/KotlinCodeWithoutHeader.test"); - gradleRunner().withArguments("spotlessApply").build(); - assertFile("src/main/kotlin/AnObject.kt").hasContent(HEADER + "\n" + getTestResource("kotlin/licenseheader/KotlinCodeWithoutHeaderKtfmt.test")); + checkKtlintOfficialStyle(); } @Test - void testWithNonStandardYearSeparator() throws IOException { - setFile("build.gradle").toLines( + void withCustomRuleSetApply() throws IOException { + setFile("build.gradle.kts").toLines( "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", - " id 'com.diffplug.spotless'", + " id(\"org.jetbrains.kotlin.jvm\") version \"1.6.21\"", + " id(\"com.diffplug.spotless\")", "}", "repositories { mavenCentral() }", "spotless {", " kotlin {", - " licenseHeader('" + HEADER_WITH_YEAR + "').yearSeparator(', ')", - " ktlint()", + " ktlint(\"1.0.1\")", + " .customRuleSets(listOf(", + " \"io.nlopez.compose.rules:ktlint:0.3.3\"", + " ))", + " .editorConfigOverride(mapOf(", + " \"ktlint_function_naming_ignore_when_annotated_with\" to \"Composable\"", + " ))", " }", "}"); - - setFile("src/main/kotlin/AnObject.kt").toResource("kotlin/licenseheader/KotlinCodeWithMultiYearHeader.test"); - setFile("src/main/kotlin/AnObject2.kt").toResource("kotlin/licenseheader/KotlinCodeWithMultiYearHeader2.test"); - gradleRunner().withArguments("spotlessApply").build(); - assertFile("src/main/kotlin/AnObject.kt").matches(matcher -> { - matcher.startsWith("// License Header 2012, 2014"); - }); - assertFile("src/main/kotlin/AnObject2.kt").matches(matcher -> { - matcher.startsWith("// License Header 2012, 2014"); - }); + setFile("src/main/kotlin/Main.kt").toResource("kotlin/ktlint/listScreen.dirty"); + String buildOutput = gradleRunner().withArguments("spotlessCheck").buildAndFail().getOutput(); + assertThat(buildOutput).contains("Composable functions that return Unit should start with an uppercase letter."); } @Test - @EnabledForJreRange(min = JAVA_11) // ktfmt's dependency, google-java-format 1.8 requires a minimum of JRE 11+. - void testWithNonStandardYearSeparatorKtfmt() throws IOException { + void testWithHeader() throws IOException { setFile("build.gradle").toLines( "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", + " id 'org.jetbrains.kotlin.jvm' version '1.6.21'", " id 'com.diffplug.spotless'", "}", "repositories { mavenCentral() }", "spotless {", " kotlin {", - " licenseHeader('" + HEADER_WITH_YEAR + "').yearSeparator(', ')", - " ktfmt()", + " ktlint()", + " licenseHeader('" + HEADER + "')", " }", "}"); - - setFile("src/main/kotlin/AnObject.kt").toResource("kotlin/licenseheader/KotlinCodeWithMultiYearHeader.test"); - setFile("src/main/kotlin/AnObject2.kt").toResource("kotlin/licenseheader/KotlinCodeWithMultiYearHeader2.test"); + setFile("src/main/kotlin/AnObject.kt").toResource("kotlin/licenseheader/KotlinCodeWithoutHeader.test"); gradleRunner().withArguments("spotlessApply").build(); - assertFile("src/main/kotlin/AnObject.kt").matches(matcher -> { - matcher.startsWith("// License Header 2012, 2014"); - }); - assertFile("src/main/kotlin/AnObject2.kt").matches(matcher -> { - matcher.startsWith("// License Header 2012, 2014"); - }); + assertFile("src/main/kotlin/AnObject.kt").hasContent(HEADER + "\n" + getTestResource("kotlin/licenseheader/KotlinCodeWithoutHeader.test")); } @Test - @EnabledForJreRange(min = JAVA_11) // ktfmt's dependency, google-java-format 1.8 requires a minimum of JRE 11+. void testWithCustomMaxWidthDefaultStyleKtfmt() throws IOException { setFile("build.gradle").toLines( "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", + " id 'org.jetbrains.kotlin.jvm' version '1.6.21'", " id 'com.diffplug.spotless'", "}", "repositories { mavenCentral() }", @@ -330,69 +210,10 @@ void testWithCustomMaxWidthDefaultStyleKtfmt() throws IOException { assertFile("src/main/kotlin/max-width.kt").sameAsResource("kotlin/ktfmt/max-width.clean"); } - @Test - @EnabledForJreRange(min = JAVA_11) // ktfmt's dependency, google-java-format 1.8 requires a minimum of JRE 11+. - void testWithCustomMaxWidthDefaultStyleKtfmtGradleKts() throws IOException { - setFile("build.gradle.kts").toLines( - "plugins {", - " id(\"org.jetbrains.kotlin.jvm\") version \"1.5.31\"", - " id(\"com.diffplug.spotless\")", - "}", - "repositories { mavenCentral() }", - "spotless {", - " kotlin {", - " ktfmt().configure { options ->", - " options.setMaxWidth(120)", - " }", - " }", - "}"); - - setFile("src/main/kotlin/max-width.kt").toResource("kotlin/ktfmt/max-width.dirty"); - gradleRunner().withArguments("spotlessApply").build(); - assertFile("src/main/kotlin/max-width.kt").sameAsResource("kotlin/ktfmt/max-width.clean"); - } - - @Test - @EnabledForJreRange(min = JAVA_11) // ktfmt's dependency, google-java-format 1.8 requires a minimum of JRE 11+. - void testWithCustomMaxWidthDropboxStyleKtfmt() throws IOException { - setFile("build.gradle").toLines( - "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", - " id 'com.diffplug.spotless'", - "}", - "repositories { mavenCentral() }", - "spotless {", - " kotlin {", - " ktfmt().dropboxStyle().configure { options ->", - " options.maxWidth = 120", - " }", - " }", - "}"); - - setFile("src/main/kotlin/max-width.kt").toResource("kotlin/ktfmt/max-width.dirty"); - gradleRunner().withArguments("spotlessApply").build(); - assertFile("src/main/kotlin/max-width.kt").sameAsResource("kotlin/ktfmt/max-width-dropbox.clean"); - } - - @Test - @EnabledForJreRange(min = JAVA_11) // ktfmt's dependency, google-java-format 1.8 requires a minimum of JRE 11+. - void testWithCustomMaxWidthDropboxStyleKtfmtGradleKts() throws IOException { - setFile("build.gradle.kts").toLines( - "plugins {", - " id(\"org.jetbrains.kotlin.jvm\") version \"1.5.31\"", - " id(\"com.diffplug.spotless\")", - "}", - "repositories { mavenCentral() }", - "spotless {", - " kotlin {", - " ktfmt().dropboxStyle().configure { options ->", - " options.setMaxWidth(120)", - " }", - " }", - "}"); - - setFile("src/main/kotlin/max-width.kt").toResource("kotlin/ktfmt/max-width.dirty"); + private void checkKtlintOfficialStyle() throws IOException { + String path = "src/main/kotlin/Main.kt"; + setFile(path).toResource("kotlin/ktlint/experimentalEditorConfigOverride.dirty"); gradleRunner().withArguments("spotlessApply").build(); - assertFile("src/main/kotlin/max-width.kt").sameAsResource("kotlin/ktfmt/max-width-dropbox.clean"); + assertFile(path).sameAsResource("kotlin/ktlint/experimentalEditorConfigOverride.ktlintOfficial.clean"); } } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/KotlinGradleExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/KotlinGradleExtensionTest.java index 0f3438ffff..c607c36912 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/KotlinGradleExtensionTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/KotlinGradleExtensionTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,194 +15,44 @@ */ package com.diffplug.gradle.spotless; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.condition.JRE.JAVA_11; - import java.io.IOException; -import org.gradle.testkit.runner.BuildResult; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledForJreRange; - -class KotlinGradleExtensionTest extends GradleIntegrationHarness { - @Test - void integration() throws IOException { - testInDirectory(null); - } - - @Test - void integration_script_in_subdir() throws IOException { - testInDirectory("companionScripts"); - } - - private void testInDirectory(final String directory) throws IOException { - setFile("build.gradle").toLines( - "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", - " id 'com.diffplug.spotless'", - "}", - "repositories { mavenCentral() }", - "spotless {", - " kotlinGradle {", - " ktlint()", - " target '**/*.gradle.kts'", - " }", - "}"); - String filePath = "configuration.gradle.kts"; - if (directory != null) { - filePath = directory + "/" + filePath; - } - setFile(filePath).toResource("kotlin/ktlint/basic.dirty"); - gradleRunner().withArguments("spotlessApply").build(); - assertFile(filePath).sameAsResource("kotlin/ktlint/basic.clean"); - } +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; - @Test - void integration_default() throws IOException { - setFile("build.gradle").toLines( - "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", - " id 'com.diffplug.spotless'", - "}", - "repositories { mavenCentral() }", - "spotless {", - " kotlinGradle {", - " ktlint()", - " }", - "}"); - setFile("configuration.gradle.kts").toResource("kotlin/ktlint/basic.dirty"); - gradleRunner().withArguments("spotlessApply").build(); - assertFile("configuration.gradle.kts").sameAsResource("kotlin/ktlint/basic.clean"); - } - - @Test - void integration_default_diktat() throws IOException { - setFile("build.gradle").toLines( - "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.4.30'", - " id 'com.diffplug.spotless'", - "}", - "repositories { mavenCentral() }", - "spotless {", - " kotlinGradle {", - " diktat()", - " }", - "}"); - setFile("configuration.gradle.kts").toResource("kotlin/diktat/basic.dirty"); - BuildResult result = gradleRunner().withArguments("spotlessApply").buildAndFail(); - assertThat(result.getOutput()).contains("[AVOID_NESTED_FUNCTIONS] try to avoid using nested functions"); - } - - @Test - void indentStep() throws IOException { - setFile("build.gradle").toLines( - "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", - " id 'com.diffplug.spotless'", - "}", - "repositories { mavenCentral() }", - "spotless {", - " kotlinGradle {", - " ktlint().userData(['indent_size': '6'])", - " }", - "}"); - setFile("configuration.gradle.kts").toResource("kotlin/ktlint/basic.dirty"); - gradleRunner().withArguments("spotlessCheck").buildAndFail(); - } - - @Test - void withExperimental() throws IOException { - setFile("build.gradle").toLines( - "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", - " id 'com.diffplug.spotless'", - "}", - "repositories { mavenCentral() }", - "spotless {", - " kotlinGradle {", - " ktlint().setUseExperimental(true)", - " }", - "}"); - setFile("configuration.gradle.kts").toResource("kotlin/ktlint/experimental.dirty"); - gradleRunner().withArguments("spotlessApply").build(); - assertFile("configuration.gradle.kts").sameAsResource("kotlin/ktlint/experimental.clean"); - } +/** + * We test KotlinGradleExtension only behaviors here. + */ +class KotlinGradleExtensionTest extends KotlinExtensionTest { - @Test - void withExperimentalEditorConfigOverride() throws IOException { + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void testTarget(boolean useDefaultTarget) throws IOException { setFile("build.gradle").toLines( "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", + " id 'org.jetbrains.kotlin.jvm' version '1.6.21'", " id 'com.diffplug.spotless'", "}", "repositories { mavenCentral() }", "spotless {", " kotlinGradle {", - " ktlint().setUseExperimental(true)", - " .editorConfigOverride([", - " ij_kotlin_allow_trailing_comma: true,", - " ij_kotlin_allow_trailing_comma_on_call_site: true", - " ])", + " " + (useDefaultTarget ? "" : "target \"*.kts\""), + " ktlint().editorConfigOverride([", + " ktlint_experimental: \"enabled\",", + " ij_kotlin_allow_trailing_comma: true,", + " ij_kotlin_allow_trailing_comma_on_call_site: true", + " ])", " }", "}"); setFile("configuration.gradle.kts").toResource("kotlin/ktlint/experimentalEditorConfigOverride.dirty"); + setFile("configuration.kts").toResource("kotlin/ktlint/experimentalEditorConfigOverride.dirty"); gradleRunner().withArguments("spotlessApply").build(); - assertFile("configuration.gradle.kts").sameAsResource("kotlin/ktlint/experimentalEditorConfigOverride.clean"); - } - - @Test - @EnabledForJreRange(min = JAVA_11) // ktfmt's dependency, google-java-format 1.8 requires a minimum of JRE 11+. - void integration_ktfmt() throws IOException { - setFile("build.gradle").toLines( - "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", - " id 'com.diffplug.spotless'", - "}", - "repositories { mavenCentral() }", - "spotless {", - " kotlinGradle {", - " ktfmt()", - " }", - "}"); - setFile("configuration.gradle.kts").toResource("kotlin/ktfmt/basic.dirty"); - gradleRunner().withArguments("spotlessApply").build(); - assertFile("configuration.gradle.kts").sameAsResource("kotlin/ktfmt/basic.clean"); - } - - @Test - @EnabledForJreRange(min = JAVA_11) // ktfmt's dependency, google-java-format 1.8 requires a minimum of JRE 11+. - void integration_ktfmt_with_dropbox_style() throws IOException { - setFile("build.gradle").toLines( - "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", - " id 'com.diffplug.spotless'", - "}", - "repositories { mavenCentral() }", - "spotless {", - " kotlinGradle {", - " ktfmt().dropboxStyle()", - " }", - "}"); - setFile("configuration.gradle.kts").toResource("kotlin/ktfmt/dropboxstyle.dirty"); - gradleRunner().withArguments("spotlessApply").build(); - assertFile("configuration.gradle.kts").sameAsResource("kotlin/ktfmt/dropboxstyle.clean"); - } - - @Test - void integration_lint_script_files_without_top_level_declaration() throws IOException { - setFile("build.gradle").toLines( - "plugins {", - " id 'org.jetbrains.kotlin.jvm' version '1.5.31'", - " id 'com.diffplug.spotless'", - "}", - "repositories { mavenCentral() }", - "spotless {", - " kotlinGradle {", - " ktlint()", - " }", - "}"); - setFile("configuration.gradle.kts").toContent("buildscript {}"); - gradleRunner().withArguments("spotlessApply").build(); - assertFile("configuration.gradle.kts").hasContent("buildscript {}"); + if (useDefaultTarget) { + assertFile("configuration.gradle.kts").sameAsResource("kotlin/ktlint/experimentalEditorConfigOverride.clean"); + assertFile("configuration.kts").sameAsResource("kotlin/ktlint/experimentalEditorConfigOverride.dirty"); + } else { + assertFile("configuration.gradle.kts").sameAsResource("kotlin/ktlint/experimentalEditorConfigOverride.clean"); + assertFile("configuration.kts").sameAsResource("kotlin/ktlint/experimentalEditorConfigOverride.clean"); + } } } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/MultiProjectTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/MultiProjectTest.java index 086a2ddb1d..5d184e15e5 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/MultiProjectTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/MultiProjectTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,7 +47,7 @@ void createSubproject(String name) throws IOException { "spotless {", " java {", " target file('test.java')", - " googleJavaFormat('1.2')", + " googleJavaFormat('1.17.0')", " }", "}"); setFile(name + "/test.java").toResource("java/googlejavaformat/JavaCodeUnformatted.test"); @@ -71,7 +71,7 @@ public void hasRootSpotless() throws IOException { "spotless {", " java {", " target file('test.java')", - " googleJavaFormat('1.2')", + " googleJavaFormat('1.17.0')", " }", "}"); setFile("test.java").toResource("java/googlejavaformat/JavaCodeUnformatted.test"); @@ -88,7 +88,7 @@ public void predeclaredFails() throws IOException { "spotless { predeclareDeps() }"); createNSubprojects(); Assertions.assertThat(gradleRunner().withArguments("spotlessApply").buildAndFail().getOutput()) - .contains("Add a step with [com.google.googlejavaformat:google-java-format:1.2] into the `spotlessPredeclare` block in the root project."); + .contains("Add a step with [com.google.googlejavaformat:google-java-format:1.17.0] into the `spotlessPredeclare` block in the root project."); } @Test @@ -100,7 +100,7 @@ public void predeclaredSucceeds() throws IOException { "repositories { mavenCentral() }", "spotless { predeclareDeps() }", "spotlessPredeclare {", - " java { googleJavaFormat('1.2') }", + " java { googleJavaFormat('1.17.0') }", "}"); createNSubprojects(); gradleRunner().withArguments("spotlessApply").build(); @@ -115,7 +115,7 @@ public void predeclaredFromBuildscriptSucceeds() throws IOException { "repositories { mavenCentral() }", "spotless { predeclareDepsFromBuildscript() }", "spotlessPredeclare {", - " java { googleJavaFormat('1.2') }", + " java { googleJavaFormat('1.17.0') }", "}"); createNSubprojects(); gradleRunner().withArguments("spotlessApply").build(); @@ -129,7 +129,7 @@ public void predeclaredOrdering() throws IOException { "}", "repositories { mavenCentral() }", "spotlessPredeclare {", - " java { googleJavaFormat('1.2') }", + " java { googleJavaFormat('1.17.0') }", "}", "spotless { predeclareDepsFromBuildscript() }"); createNSubprojects(); @@ -145,7 +145,7 @@ public void predeclaredUndeclared() throws IOException { "}", "repositories { mavenCentral() }", "spotlessPredeclare {", - " java { googleJavaFormat('1.2') }", + " java { googleJavaFormat('1.17.0') }", "}"); createNSubprojects(); Assertions.assertThat(gradleRunner().withArguments("spotlessApply").buildAndFail().getOutput()) diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmInstallCacheIntegrationTests.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmInstallCacheIntegrationTests.java new file mode 100644 index 0000000000..e7ad5f1654 --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmInstallCacheIntegrationTests.java @@ -0,0 +1,249 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import static com.diffplug.gradle.spotless.FormatExtension.NpmStepConfig.SPOTLESS_NPM_INSTALL_CACHE_DEFAULT_NAME; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; + +import org.assertj.core.api.Assertions; +import org.gradle.testkit.runner.BuildResult; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.io.TempDir; + +import com.diffplug.common.base.Errors; +import com.diffplug.spotless.tag.NpmTest; + +@TestMethodOrder(OrderAnnotation.class) +@NpmTest +class NpmInstallCacheIntegrationTests extends GradleIntegrationHarness { + + static File pertainingCacheDir; + + private static final File DEFAULT_DIR_FOR_NPM_INSTALL_CACHE_DO_NEVER_WRITE_TO_THIS = new File("."); + + @BeforeAll + static void beforeAll(@TempDir File pertainingCacheDir) { + NpmInstallCacheIntegrationTests.pertainingCacheDir = Errors.rethrow().get(pertainingCacheDir::getCanonicalFile); + } + + @Test + void prettierCachesNodeModulesToADefaultFolderWhenCachingEnabled() throws IOException { + File dir1 = newFolder("npm-prettier-1"); + File cacheDir = DEFAULT_DIR_FOR_NPM_INSTALL_CACHE_DO_NEVER_WRITE_TO_THIS; + BuildResult result = runPhpPrettierOnDir(dir1, cacheDir); + Assertions.assertThat(result.getOutput()) + .doesNotContain("Using cached node_modules for") + .contains("Caching node_modules for ") + .contains(Paths.get(dir1.getAbsolutePath(), "build", SPOTLESS_NPM_INSTALL_CACHE_DEFAULT_NAME).toString()); + + } + + @Test + void prettierCachesAndReusesNodeModulesInSpecificInstallCacheFolder() throws IOException { + File dir1 = newFolder("npm-prettier-1"); + File cacheDir = newFolder("npm-prettier-cache"); + BuildResult result = runPhpPrettierOnDir(dir1, cacheDir); + Assertions.assertThat(result.getOutput()).doesNotContainPattern("Using cached node_modules for .*\\Q" + cacheDir.getAbsolutePath() + "\\E"); + File dir2 = newFolder("npm-prettier-2"); + BuildResult result2 = runPhpPrettierOnDir(dir2, cacheDir); + Assertions.assertThat(result2.getOutput()).containsPattern("Using cached node_modules for .*\\Q" + cacheDir.getAbsolutePath() + "\\E"); + } + + @Test + void prettierDoesNotCacheNodeModulesIfNotExplicitlyEnabled() throws IOException { + File dir2 = newFolder("npm-prettier-1"); + BuildResult result = runPhpPrettierOnDir(dir2, null); + Assertions.assertThat(result.getOutput()) + .doesNotContainPattern("Using cached node_modules for .*") + .doesNotContainPattern("Caching node_modules for .*"); + } + + @Test + @Order(1) + void prettierCachesNodeModuleInGlobalInstallCacheDir() throws IOException { + File dir1 = newFolder("npm-prettier-global-1"); + File cacheDir = pertainingCacheDir; + BuildResult result = runPhpPrettierOnDir(dir1, cacheDir); + Assertions.assertThat(result.getOutput()) + .doesNotContainPattern("Using cached node_modules for .*\\Q" + cacheDir.getAbsolutePath() + "\\E") + .containsPattern("Caching node_modules for .*\\Q" + cacheDir.getAbsolutePath() + "\\E"); + } + + @Test + @Order(2) + void prettierUsesCachedNodeModulesFromGlobalInstallCacheDir() throws IOException { + File dir2 = newFolder("npm-prettier-global-2"); + File cacheDir = pertainingCacheDir; + BuildResult result = runPhpPrettierOnDir(dir2, cacheDir); + Assertions.assertThat(result.getOutput()) + .containsPattern("Using cached node_modules for .*\\Q" + cacheDir.getAbsolutePath() + "\\E") + .doesNotContainPattern("Caching node_modules for .*\\Q" + cacheDir.getAbsolutePath() + "\\E"); + } + + private BuildResult runPhpPrettierOnDir(File projDir, File cacheDir) throws IOException { + String baseDir = projDir.getName(); + String cacheDirEnabled = cacheDirEnabledStringForCacheDir(cacheDir); + setFile(baseDir + "/build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "def prettierConfig = [:]", + "prettierConfig['tabWidth'] = 3", + "prettierConfig['parser'] = 'php'", + "def prettierPackages = [:]", + "prettierPackages['prettier'] = '2.8.8'", + "prettierPackages['@prettier/plugin-php'] = '0.19.6'", + "spotless {", + " format 'php', {", + " target 'php-example.php'", + " prettier(prettierPackages).config(prettierConfig)" + cacheDirEnabled, + " }", + "}"); + setFile(baseDir + "/php-example.php").toResource("npm/prettier/plugins/php.dirty"); + final BuildResult spotlessApply = gradleRunner().withProjectDir(projDir).withArguments("--stacktrace", "--info", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile(baseDir + "/php-example.php").sameAsResource("npm/prettier/plugins/php.clean"); + return spotlessApply; + } + + @Test + @Order(3) + void tsfmtCachesNodeModuleInGlobalInstallCacheDir() throws IOException { + File dir1 = newFolder("npm-tsfmt-global-1"); + File cacheDir = pertainingCacheDir; + BuildResult result = runTsfmtOnDir(dir1, cacheDir); + Assertions.assertThat(result.getOutput()) + .doesNotContainPattern("Using cached node_modules for .*\\Q" + cacheDir.getAbsolutePath() + "\\E") + .containsPattern("Caching node_modules for .*\\Q" + cacheDir.getAbsolutePath() + "\\E"); + } + + @Test + @Order(4) + void tsfmtUsesCachedNodeModulesFromGlobalInstallCacheDir() throws IOException { + File dir2 = newFolder("npm-tsfmt-global-2"); + File cacheDir = pertainingCacheDir; + BuildResult result = runTsfmtOnDir(dir2, cacheDir); + Assertions.assertThat(result.getOutput()) + .containsPattern("Using cached node_modules for .*\\Q" + cacheDir.getAbsolutePath() + "\\E") + .doesNotContainPattern("Caching node_modules for .*\\Q" + cacheDir.getAbsolutePath() + "\\E"); + } + + @Test + void tsfmtDoesNotCacheNodeModulesIfNotExplicitlyEnabled() throws IOException { + File dir2 = newFolder("npm-tsfmt-1"); + BuildResult result = runTsfmtOnDir(dir2, null); + Assertions.assertThat(result.getOutput()) + .doesNotContainPattern("Using cached node_modules for .*") + .doesNotContainPattern("Caching node_modules for .*"); + } + + private BuildResult runTsfmtOnDir(File projDir, File cacheDir) throws IOException { + String baseDir = projDir.getName(); + String cacheDirEnabled = cacheDirEnabledStringForCacheDir(cacheDir); + setFile(baseDir + "/build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "def tsfmtconfig = [:]", + "tsfmtconfig['indentSize'] = 1", + "tsfmtconfig['convertTabsToSpaces'] = true", + "spotless {", + " typescript {", + " target 'test.ts'", + " tsfmt().config(tsfmtconfig)" + cacheDirEnabled, + " }", + "}"); + setFile(baseDir + "/test.ts").toResource("npm/tsfmt/tsfmt/tsfmt.dirty"); + final BuildResult spotlessApply = gradleRunner().withProjectDir(projDir).withArguments("--stacktrace", "--info", "spotlessApply").build(); + assertFile(baseDir + "/test.ts").sameAsResource("npm/tsfmt/tsfmt/tsfmt.clean"); + return spotlessApply; + } + + @Test + @Order(5) + void eslintCachesNodeModuleInGlobalInstallCacheDir() throws IOException { + File dir1 = newFolder("npm-eslint-global-1"); + File cacheDir = pertainingCacheDir; + BuildResult result = runEslintOnDir(dir1, cacheDir); + Assertions.assertThat(result.getOutput()) + .doesNotContainPattern("Using cached node_modules for .*\\Q" + cacheDir.getAbsolutePath() + "\\E") + .containsPattern("Caching node_modules for .*\\Q" + cacheDir.getAbsolutePath() + "\\E"); + } + + @Test + @Order(6) + void eslintUsesCachedNodeModulesFromGlobalInstallCacheDir() throws IOException { + File dir2 = newFolder("npm-eslint-global-2"); + File cacheDir = pertainingCacheDir; + BuildResult result = runEslintOnDir(dir2, cacheDir); + Assertions.assertThat(result.getOutput()) + .containsPattern("Using cached node_modules for .*\\Q" + cacheDir.getAbsolutePath() + "\\E") + .doesNotContainPattern("Caching node_modules for .*\\Q" + cacheDir.getAbsolutePath() + "\\E"); + } + + @Test + void eslintDoesNotCacheNodeModulesIfNotExplicitlyEnabled() throws IOException { + File dir2 = newFolder("npm-eslint-1"); + File cacheDir = null; + BuildResult result = runEslintOnDir(dir2, cacheDir); + Assertions.assertThat(result.getOutput()) + .doesNotContainPattern("Using cached node_modules for .*") + .doesNotContainPattern("Caching node_modules for .*"); + } + + private BuildResult runEslintOnDir(File projDir, File cacheDir) throws IOException { + String baseDir = projDir.getName(); + String cacheDirEnabled = cacheDirEnabledStringForCacheDir(cacheDir); + + setFile(baseDir + "/.eslintrc.js").toResource("npm/eslint/typescript/custom_rules/.eslintrc.js"); + setFile(baseDir + "/build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " typescript {", + " target 'test.ts'", + " eslint().configFile('.eslintrc.js')" + cacheDirEnabled, + " }", + "}"); + setFile(baseDir + "/test.ts").toResource("npm/eslint/typescript/custom_rules/typescript.dirty"); + BuildResult spotlessApply = gradleRunner().withProjectDir(projDir).withArguments("--stacktrace", "--info", "spotlessApply").build(); + assertFile(baseDir + "/test.ts").sameAsResource("npm/eslint/typescript/custom_rules/typescript.clean"); + return spotlessApply; + } + + private static String cacheDirEnabledStringForCacheDir(File cacheDir) { + String cacheDirEnabled; + if (cacheDir == null) { + cacheDirEnabled = ""; + } else if (cacheDir == DEFAULT_DIR_FOR_NPM_INSTALL_CACHE_DO_NEVER_WRITE_TO_THIS) { + cacheDirEnabled = ".npmInstallCache()"; + } else { + cacheDirEnabled = ".npmInstallCache('" + cacheDir.getAbsolutePath() + "')"; + } + return cacheDirEnabled; + } +} diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java new file mode 100644 index 0000000000..d01d1ab006 --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest.java @@ -0,0 +1,180 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import org.assertj.core.api.Assertions; +import org.gradle.testkit.runner.BuildResult; +import org.junit.jupiter.api.Test; + +import com.diffplug.common.base.Predicates; + +class NpmTestsWithoutNpmInstallationTest extends GradleIntegrationHarness { + + @Test + void useNodeAndNpmFromNodeGradlePlugin() throws Exception { + try { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + " id 'com.github.node-gradle.node' version '3.5.1'", + "}", + "repositories { mavenCentral() }", + "node {", + " download = true", + " version = '18.16.1'", + " npmVersion = '9.5.1'", + " workDir = file(\"${buildDir}/nodejs\")", + " npmWorkDir = file(\"${buildDir}/npm\")", + "}", + "def prettierConfig = [:]", + "prettierConfig['printWidth'] = 20", + "prettierConfig['parser'] = 'typescript'", + "def npmExec = System.getProperty('os.name').toLowerCase().contains('windows') ? '/npm.cmd' : '/bin/npm'", + "def nodeExec = System.getProperty('os.name').toLowerCase().contains('windows') ? '/node.exe' : '/bin/node'", + "spotless {", + " format 'mytypescript', {", + " target 'test.ts'", + " prettier()", + " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}${npmExec}\")", + " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}${nodeExec}\")", + " .config(prettierConfig)", + " }", + "}"); + setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); + // make sure node binary is there + gradleRunner().withArguments("nodeSetup", "npmSetup").build(); + // then run spotless using that node installation + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile_prettier_2.clean"); + } catch (Exception e) { + printContents(); + throw e; + } + } + + private void printContents() { + System.out.println("************* Folder contents **************************"); + System.out.println(listFiles(Predicates.and(path -> !path.startsWith(".gradle"), path -> !path.contains("/node_modules/"), path -> !path.contains("/include/")))); + System.out.println("********************************************************"); + } + + @Test + void useNodeAndNpmFromNodeGradlePlugin_example1() throws Exception { + try { + setFile("build.gradle").toResource("com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest_gradle_node_plugin_example_1.gradle"); + setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile_prettier_2.clean"); + } catch (Exception e) { + printContents(); + throw e; + } + } + + @Test + void useNpmFromNodeGradlePlugin_example2() throws Exception { + try { + setFile("build.gradle").toResource("com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest_gradle_node_plugin_example_2.gradle"); + setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile_prettier_2.clean"); + } catch (Exception e) { + printContents(); + throw e; + } + } + + @Test + void useNpmFromNodeGradlePlugin() throws Exception { + try { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + " id 'com.github.node-gradle.node' version '3.5.1'", + "}", + "repositories { mavenCentral() }", + "node {", + " download = true", + " version = '18.16.1'", + " workDir = file(\"${buildDir}/nodejs\")", + "}", + "def prettierConfig = [:]", + "prettierConfig['printWidth'] = 20", + "prettierConfig['parser'] = 'typescript'", + "def npmExec = System.getProperty('os.name').toLowerCase().contains('windows') ? '/npm.cmd' : '/bin/npm'", + "spotless {", + " format 'mytypescript', {", + " target 'test.ts'", + " prettier()", + " .npmExecutable(\"${tasks.named('npmSetup').get().npmDir.get()}${npmExec}\")", + " .config(prettierConfig)", + " }", + "}"); + setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); + // make sure node binary is there + gradleRunner().withArguments("nodeSetup", "npmSetup").build(); + // then run spotless using that node installation + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile_prettier_2.clean"); + } catch (Exception e) { + printContents(); + throw e; + } + } + + @Test + void useNpmNextToConfiguredNodePluginFromNodeGradlePlugin() throws Exception { + try { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + " id 'com.github.node-gradle.node' version '3.5.1'", + "}", + "repositories { mavenCentral() }", + "node {", + " download = true", + " version = '18.13.0'", + " workDir = file(\"${buildDir}/nodejs\")", + "}", + "def prettierConfig = [:]", + "prettierConfig['printWidth'] = 20", + "prettierConfig['parser'] = 'typescript'", + "def nodeExec = System.getProperty('os.name').toLowerCase().contains('windows') ? '/node.exe' : '/bin/node'", + "spotless {", + " format 'mytypescript', {", + " target 'test.ts'", + " prettier()", + " .nodeExecutable(\"${tasks.named('nodeSetup').get().nodeDir.get()}${nodeExec}\")", + " .config(prettierConfig)", + " }", + "}"); + setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); + // make sure node binary is there + gradleRunner().withArguments("nodeSetup", "npmSetup").build(); + // then run spotless using that node installation + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile_prettier_2.clean"); + } catch (Exception e) { + printContents(); + throw e; + } + } +} diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/PaddedCellTaskTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/PaddedCellTaskTest.java index bc7a2fdc96..4fe78d884d 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/PaddedCellTaskTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/PaddedCellTaskTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -66,7 +66,7 @@ private SpotlessTaskImpl createFormatTask(String name, FormatterStep step) { SpotlessTaskImpl task = project.getTasks().create("spotless" + SpotlessPlugin.capitalize(name), SpotlessTaskImpl.class); task.init(taskService); task.addStep(step); - task.setLineEndingsPolicy(LineEnding.UNIX.createPolicy()); + task.setLineEndingsPolicy(project.provider(LineEnding.UNIX::createPolicy)); task.setTarget(Collections.singletonList(file)); return task; } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/PalantirJavaFormatIntegrationTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/PalantirJavaFormatIntegrationTest.java index 2283ce34bf..9990039cd3 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/PalantirJavaFormatIntegrationTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/PalantirJavaFormatIntegrationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 DiffPlug + * Copyright 2022-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,4 +45,26 @@ void integration() throws IOException { "palantirJavaFormat('1.0.1')"); checkRunsThenUpToDate(); } + + @Test + void formatJavaDoc() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "", + "spotless {", + " java {", + " target file('test.java')", + " palantirJavaFormat('2.39.0').formatJavadoc(true)", + " }", + "}"); + + setFile("test.java").toResource("java/palantirjavaformat/JavaCodeWithJavaDocUnformatted.test"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("test.java").sameAsResource("java/palantirjavaformat/JavaCodeWithJavaDocFormatted.test"); + + checkRunsThenUpToDate(); + } } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/PrettierIntegrationTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/PrettierIntegrationTest.java index 404ccf88c8..74b6db9167 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/PrettierIntegrationTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/PrettierIntegrationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,36 +19,52 @@ import org.assertj.core.api.Assertions; import org.gradle.testkit.runner.BuildResult; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import com.diffplug.spotless.npm.PrettierFormatterStep; import com.diffplug.spotless.tag.NpmTest; @NpmTest class PrettierIntegrationTest extends GradleIntegrationHarness { - @Test - void useInlineConfig() throws IOException { + + private static final String PRETTIER_VERSION_2 = PrettierFormatterStep.DEFAULT_VERSION; + + private static final String PRETTIER_VERSION_3 = "3.0.3"; + + @ParameterizedTest(name = "{index}: useInlineConfig with prettier {0}") + @ValueSource(strings = {PRETTIER_VERSION_2, PRETTIER_VERSION_3}) + void useInlineConfig(String prettierVersion) throws IOException { setFile("build.gradle").toLines( "plugins {", " id 'com.diffplug.spotless'", "}", "repositories { mavenCentral() }", "def prettierConfig = [:]", - "prettierConfig['printWidth'] = 50", + "prettierConfig['printWidth'] = 20", "prettierConfig['parser'] = 'typescript'", "spotless {", " format 'mytypescript', {", " target 'test.ts'", - " prettier().config(prettierConfig)", + " prettier('" + prettierVersion + "').config(prettierConfig)", " }", "}"); setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); - assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + switch (prettierVersion) { + case PRETTIER_VERSION_2: + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile_prettier_2.clean"); + break; + case PRETTIER_VERSION_3: + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile_prettier_3.clean"); + break; + } } - @Test - void verifyCleanSpotlessCheckWorks() throws IOException { + @ParameterizedTest(name = "{index}: verifyCleanSpotlessCheckWorks with prettier {0}") + @ValueSource(strings = {PRETTIER_VERSION_2, PRETTIER_VERSION_3}) + void verifyCleanSpotlessCheckWorks(String prettierVersion) throws IOException { setFile("build.gradle").toLines( "plugins {", " id 'com.diffplug.spotless'", @@ -60,19 +76,20 @@ void verifyCleanSpotlessCheckWorks() throws IOException { "spotless {", " format 'mytypescript', {", " target 'test.ts'", - " prettier().config(prettierConfig)", + " prettier('" + prettierVersion + "').config(prettierConfig)", " }", "}"); setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); - BuildResult spotlessCheckFailsGracefully = gradleRunner().withArguments("--stacktrace", "spotlessCheck").buildAndFail(); + final BuildResult spotlessCheckFailsGracefully = gradleRunner().withArguments("--stacktrace", "spotlessCheck").buildAndFail(); Assertions.assertThat(spotlessCheckFailsGracefully.getOutput()).contains("> The following files had format violations:"); gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); gradleRunner().withArguments("--stacktrace", "spotlessCheck").build(); } - @Test - void useFileConfig() throws IOException { + @ParameterizedTest(name = "{index}: useFileConfig with prettier {0}") + @ValueSource(strings = {PRETTIER_VERSION_2, PRETTIER_VERSION_3}) + void useFileConfig(String prettierVersion) throws IOException { setFile(".prettierrc.yml").toResource("npm/prettier/config/.prettierrc.yml"); setFile("build.gradle").toLines( "plugins {", @@ -82,17 +99,25 @@ void useFileConfig() throws IOException { "spotless {", " format 'mytypescript', {", " target 'test.ts'", - " prettier().configFile('.prettierrc.yml')", + " prettier('" + prettierVersion + "').configFile('.prettierrc.yml')", " }", "}"); setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); - assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile.clean"); + switch (prettierVersion) { + case PRETTIER_VERSION_2: + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile_prettier_2.clean"); + break; + case PRETTIER_VERSION_3: + assertFile("test.ts").sameAsResource("npm/prettier/config/typescript.configfile_prettier_3.clean"); + break; + } } - @Test - void chooseParserBasedOnFilename() throws IOException { + @ParameterizedTest(name = "{index}: chooseParserBasedOnFilename with prettier {0}") + @ValueSource(strings = {PRETTIER_VERSION_2, PRETTIER_VERSION_3}) + void chooseParserBasedOnFilename(String prettierVersion) throws IOException { setFile("build.gradle").toLines( "plugins {", " id 'com.diffplug.spotless'", @@ -101,7 +126,7 @@ void chooseParserBasedOnFilename() throws IOException { "spotless {", " format 'webResources', {", " target 'dirty.*'", - " prettier()", + " prettier('" + prettierVersion + "')", " }", "}"); setFile("dirty.json").toResource("npm/prettier/filename/dirty.json"); @@ -110,8 +135,20 @@ void chooseParserBasedOnFilename() throws IOException { assertFile("dirty.json").sameAsResource("npm/prettier/filename/clean.json"); } - @Test - void useJavaCommunityPlugin() throws IOException { + @ParameterizedTest(name = "{index}: useJavaCommunityPlugin with prettier {0}") + @ValueSource(strings = {PRETTIER_VERSION_2, PRETTIER_VERSION_3}) + void useJavaCommunityPlugin(String prettierVersion) throws IOException { + var prettierPluginJava = ""; + var prettierConfigPluginsStr = ""; + switch (prettierVersion) { + case PRETTIER_VERSION_2: + prettierPluginJava = "2.1.0"; // last version to support v2 + break; + case PRETTIER_VERSION_3: + prettierPluginJava = "2.3.0"; // latest to support v3 + prettierConfigPluginsStr = "prettierConfig['plugins'] = ['prettier-plugin-java']"; + break; + } setFile("build.gradle").toLines( "plugins {", " id 'com.diffplug.spotless'", @@ -120,9 +157,10 @@ void useJavaCommunityPlugin() throws IOException { "def prettierConfig = [:]", "prettierConfig['tabWidth'] = 4", "prettierConfig['parser'] = 'java'", + prettierConfigPluginsStr, "def prettierPackages = [:]", - "prettierPackages['prettier'] = '2.0.5'", - "prettierPackages['prettier-plugin-java'] = '0.8.0'", + "prettierPackages['prettier'] = '" + prettierVersion + "'", + "prettierPackages['prettier-plugin-java'] = '" + prettierPluginJava + "'", "spotless {", " format 'java', {", " target 'JavaTest.java'", @@ -135,8 +173,77 @@ void useJavaCommunityPlugin() throws IOException { assertFile("JavaTest.java").sameAsResource("npm/prettier/plugins/java-test.clean"); } - @Test - void usePhpCommunityPlugin() throws IOException { + @ParameterizedTest(name = "{index}: useJavaCommunityPluginFileConfig with prettier {0}") + @ValueSource(strings = {PRETTIER_VERSION_2, PRETTIER_VERSION_3}) + void useJavaCommunityPluginFileConfig(String prettierVersion) throws IOException { + var prettierPluginJava = ""; + switch (prettierVersion) { + case PRETTIER_VERSION_2: + prettierPluginJava = "2.1.0"; // last version to support v2 + break; + case PRETTIER_VERSION_3: + prettierPluginJava = "2.3.0"; // latest to support v3 + break; + } + setFile(".prettierrc.yml").toResource("npm/prettier/config/.prettierrc_java_plugin.yml"); + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "def prettierPackages = [:]", + "prettierPackages['prettier'] = '" + prettierVersion + "'", + "prettierPackages['prettier-plugin-java'] = '" + prettierPluginJava + "'", + "spotless {", + " format 'java', {", + " target 'JavaTest.java'", + " prettier(prettierPackages).configFile('.prettierrc.yml')", + " }", + "}"); + setFile("JavaTest.java").toResource("npm/prettier/plugins/java-test.dirty"); + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("JavaTest.java").sameAsResource("npm/prettier/plugins/java-test.clean"); + } + + @ParameterizedTest(name = "{index}: suggestsMissingJavaCommunityPlugin with prettier {0}") + @ValueSource(strings = {PRETTIER_VERSION_2, PRETTIER_VERSION_3}) + void suggestsMissingJavaCommunityPlugin(String prettierVersion) throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "def prettierConfig = [:]", + "prettierConfig['tabWidth'] = 4", + "def prettierPackages = [:]", + "prettierPackages['prettier'] = '" + prettierVersion + "'", + "spotless {", + " format 'java', {", + " target 'JavaTest.java'", + " prettier(prettierPackages).config(prettierConfig)", + " }", + "}"); + setFile("JavaTest.java").toResource("npm/prettier/plugins/java-test.dirty"); + final BuildResult spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").buildAndFail(); + Assertions.assertThat(spotlessApply.getOutput()).contains("could not infer a parser"); + Assertions.assertThat(spotlessApply.getOutput()).contains("prettier-plugin-java"); + } + + @ParameterizedTest(name = "{index}: usePhpCommunityPlugin with prettier {0}") + @ValueSource(strings = {PRETTIER_VERSION_2, PRETTIER_VERSION_3}) + void usePhpCommunityPlugin(String prettierVersion) throws IOException { + var prettierPluginPhp = ""; + var prettierConfigPluginsStr = ""; + switch (prettierVersion) { + case PRETTIER_VERSION_2: + prettierPluginPhp = "0.19.7"; // last version to support v2 + break; + case PRETTIER_VERSION_3: + prettierPluginPhp = "0.20.1"; // latest to support v3 + prettierConfigPluginsStr = "prettierConfig['plugins'] = ['@prettier/plugin-php']"; + break; + } setFile("build.gradle").toLines( "plugins {", " id 'com.diffplug.spotless'", @@ -145,9 +252,10 @@ void usePhpCommunityPlugin() throws IOException { "def prettierConfig = [:]", "prettierConfig['tabWidth'] = 3", "prettierConfig['parser'] = 'php'", + prettierConfigPluginsStr, "def prettierPackages = [:]", - "prettierPackages['prettier'] = '2.0.5'", - "prettierPackages['@prettier/plugin-php'] = '0.14.2'", + "prettierPackages['prettier'] = '" + prettierVersion + "'", + "prettierPackages['@prettier/plugin-php'] = '" + prettierPluginPhp + "'", "spotless {", " format 'php', {", " target 'php-example.php'", @@ -160,10 +268,77 @@ void usePhpCommunityPlugin() throws IOException { assertFile("php-example.php").sameAsResource("npm/prettier/plugins/php.clean"); } - @Test - void autodetectNpmrcFileConfig() throws IOException { + /** + * This test is to ensure that we can have multiple prettier instances in one spotless config. + * + * @see Issue #1162 on github + */ + @ParameterizedTest(name = "{index}: usePhpAndJavaCommunityPlugin with prettier {0}") + @ValueSource(strings = {PRETTIER_VERSION_2, PRETTIER_VERSION_3}) + void usePhpAndJavaCommunityPlugin(String prettierVersion) throws IOException { + var prettierPluginJava = ""; + var prettierPluginPhp = ""; + var prettierConfigPluginsJavaStr = ""; + var prettierConfigPluginsPhpStr = ""; + switch (prettierVersion) { + case PRETTIER_VERSION_2: + prettierPluginJava = "2.1.0"; // last version to support v2 + prettierPluginPhp = "0.19.7"; // last version to support v2 + break; + case PRETTIER_VERSION_3: + prettierPluginJava = "2.3.0"; // latest to support v3 + prettierPluginPhp = "0.20.1"; // latest to support v3 + prettierConfigPluginsJavaStr = "prettierConfigJava['plugins'] = ['prettier-plugin-java']"; + prettierConfigPluginsPhpStr = "prettierConfigPhp['plugins'] = ['@prettier/plugin-php']"; + break; + } + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "def prettierConfigPhp = [:]", + "prettierConfigPhp['tabWidth'] = 3", + "prettierConfigPhp['parser'] = 'php'", + prettierConfigPluginsPhpStr, + "def prettierPackagesPhp = [:]", + "prettierPackagesPhp['prettier'] = '" + prettierVersion + "'", + "prettierPackagesPhp['@prettier/plugin-php'] = '" + prettierPluginPhp + "'", + "def prettierConfigJava = [:]", + "prettierConfigJava['tabWidth'] = 4", + "prettierConfigJava['parser'] = 'java'", + prettierConfigPluginsJavaStr, + "def prettierPackagesJava = [:]", + "prettierPackagesJava['prettier'] = '" + prettierVersion + "'", + "prettierPackagesJava['prettier-plugin-java'] = '" + prettierPluginJava + "'", + "spotless {", + " format 'php', {", + " target 'php-example.php'", + " prettier(prettierPackagesPhp).config(prettierConfigPhp)", + " }", + " java {", + " target 'JavaTest.java'", + " prettier(prettierPackagesJava).config(prettierConfigJava)", + " }", + "}"); + setFile("php-example.php").toResource("npm/prettier/plugins/php.dirty"); + setFile("JavaTest.java").toResource("npm/prettier/plugins/java-test.dirty"); + final BuildResult spotlessApply = gradleRunner().forwardOutput().withArguments("--stacktrace", "--info", "spotlessApply").build(); + Assertions.assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + final BuildResult spotlessApply2 = gradleRunner().forwardOutput().withArguments("--stacktrace", "--info", "spotlessApply").build(); + Assertions.assertThat(spotlessApply2.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("php-example.php").sameAsResource("npm/prettier/plugins/php.clean"); + assertFile("JavaTest.java").sameAsResource("npm/prettier/plugins/java-test.clean"); + } + + @ParameterizedTest(name = "{index}: autodetectNpmrcFileConfig with prettier {0}") + @ValueSource(strings = {PRETTIER_VERSION_2, PRETTIER_VERSION_3}) + void autodetectNpmrcFileConfig(String prettierVersion) throws IOException { setFile(".npmrc").toLines( - "registry=https://i.do.no.exist.com"); + "registry=https://i.do.not.exist.com", + "fetch-timeout=250", + "fetch-retry-mintimeout=250", + "fetch-retry-maxtimeout=250"); setFile("build.gradle").toLines( "plugins {", " id 'com.diffplug.spotless'", @@ -175,7 +350,7 @@ void autodetectNpmrcFileConfig() throws IOException { "spotless {", " format 'mytypescript', {", " target 'test.ts'", - " prettier().config(prettierConfig)", + " prettier('" + prettierVersion + "').config(prettierConfig)", " }", "}"); setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); @@ -183,10 +358,14 @@ void autodetectNpmrcFileConfig() throws IOException { Assertions.assertThat(spotlessApply.getOutput()).containsPattern("Running npm command.*npm install.* failed with exit code: 1"); } - @Test - void pickupNpmrcFileConfig() throws IOException { + @ParameterizedTest(name = "{index}: autodetectNpmrcFileConfig with prettier {0}") + @ValueSource(strings = {PRETTIER_VERSION_2, PRETTIER_VERSION_3}) + void pickupNpmrcFileConfig(String prettierVersion) throws IOException { setFile(".custom_npmrc").toLines( - "registry=https://i.do.no.exist.com"); + "registry=https://i.do.not.exist.com", + "fetch-timeout=250", + "fetch-retry-mintimeout=250", + "fetch-retry-maxtimeout=250"); setFile("build.gradle").toLines( "plugins {", " id 'com.diffplug.spotless'", @@ -198,7 +377,7 @@ void pickupNpmrcFileConfig() throws IOException { "spotless {", " format 'mytypescript', {", " target 'test.ts'", - " prettier().npmrc('.custom_npmrc').config(prettierConfig)", + " prettier('" + prettierVersion + "').npmrc('.custom_npmrc').config(prettierConfig)", " }", "}"); setFile("test.ts").toResource("npm/prettier/config/typescript.dirty"); diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RegisterDependenciesTaskTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RegisterDependenciesTaskTest.java index 480de4f01b..76d0449060 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RegisterDependenciesTaskTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RegisterDependenciesTaskTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,11 +32,11 @@ void duplicateConfigs() throws IOException { "spotless {", " java {", " target 'src/main/java/**/*.java'", - " googleJavaFormat('1.2')", + " googleJavaFormat()", " }", " format 'javaDupe', com.diffplug.gradle.spotless.JavaExtension, {", " target 'src/boop/java/**/*.java'", - " googleJavaFormat('1.2')", + " googleJavaFormat()", " }", "}"); diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RomeIntegrationTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RomeIntegrationTest.java new file mode 100644 index 0000000000..0c12f9f500 --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RomeIntegrationTest.java @@ -0,0 +1,343 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; +import org.owasp.encoder.Encode; + +class RomeIntegrationTest extends GradleIntegrationHarness { + /** + * Tests that rome can be used as a generic formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void asGenericStep() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'myrome', {", + " target '**/*.js'", + " rome('12.0.0')", + " }", + "}"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("rome_test.js").sameAsResource("rome/js/fileAfter.js"); + } + + /** + * Tests that rome can be used as a JavaScript formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void asJavaScriptStep() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " javascript {", + " target '**/*.js'", + " rome('12.0.0')", + " }", + "}"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("rome_test.js").sameAsResource("rome/js/fileAfter.js"); + } + + /** + * Tests that rome can be used as a JSON formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void asJsonStep() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " json {", + " target '**/*.json'", + " rome('12.0.0')", + " }", + "}"); + setFile("rome_test.json").toResource("rome/json/fileBefore.json"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("rome_test.json").sameAsResource("rome/json/fileAfter.json"); + } + + /** + * Tests that rome can be used as a TypeScript formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void asTypeScriptStep() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " typescript {", + " target '**/*.ts'", + " rome('12.0.0')", + " }", + "}"); + setFile("rome_test.ts").toResource("rome/ts/fileBefore.ts"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("rome_test.ts").sameAsResource("rome/ts/fileAfter.ts"); + } + + /** + * Tests that the language can be specified for the generic format step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void canSetLanguageForGenericStep() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'myrome', {", + " target '**/*.nosj'", + " rome('12.0.0').language('json')", + " }", + "}"); + setFile("rome_test.nosj").toResource("rome/json/fileBefore.json"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("rome_test.nosj").sameAsResource("rome/json/fileAfter.json"); + } + + /** + * Tests that an absolute config path can be specified. + * + * @throws Exception When a test failure occurs. + */ + @Test + void configPathAbsolute() throws Exception { + var path = newFile("configs").getAbsolutePath(); + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'myrome', {", + " target '**/*.js'", + " rome('12.0.0').configPath('" + Encode.forJava(path) + "')", + " }", + "}"); + setFile("rome_test.js").toResource("rome/js/longLineBefore.js"); + setFile("configs/rome.json").toResource("rome/config/line-width-120.json"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("rome_test.js").sameAsResource("rome/js/longLineAfter120.js"); + } + + /** + * Tests that a path to the directory with the rome.json config file can be + * specified. Uses a config file with a line width of 120. + * + * @throws Exception When a test failure occurs. + */ + @Test + void configPathLineWidth120() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'myrome', {", + " target '**/*.js'", + " rome('12.0.0').configPath('configs')", + " }", + "}"); + setFile("rome_test.js").toResource("rome/js/longLineBefore.js"); + setFile("configs/rome.json").toResource("rome/config/line-width-120.json"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("rome_test.js").sameAsResource("rome/js/longLineAfter120.js"); + } + + /** + * Tests that a path to the directory with the rome.json config file can be + * specified. Uses a config file with a line width of 80. + * + * @throws Exception When a test failure occurs. + */ + @Test + void configPathLineWidth80() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'myrome', {", + " target '**/*.js'", + " rome('12.0.0').configPath('configs')", + " }", + "}"); + setFile("rome_test.js").toResource("rome/js/longLineBefore.js"); + setFile("configs/rome.json").toResource("rome/config/line-width-80.json"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("rome_test.js").sameAsResource("rome/js/longLineAfter80.js"); + } + + /** + * Tests that the download directory can be an absolute path. + * + * @throws Exception When a test failure occurs. + */ + @Test + void downloadDirAbsolute() throws Exception { + var path = newFile("target/bin/rome").getAbsoluteFile().toString(); + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'myrome', {", + " target '**/*.js'", + " rome('12.0.0').downloadDir('" + Encode.forJava(path) + "')", + " }", + "}"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + assertTrue(!newFile("target/bin/rome").exists() || newFile("target/bin/rome").list().length == 0); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("rome_test.js").sameAsResource("rome/js/fileAfter.js"); + assertEquals(2, newFile("target/bin/rome").list().length); + } + + /** + * Tests that the download directory can be changed to a path relative to the + * project's base directory. + * + * @throws Exception When a test failure occurs. + */ + @Test + void downloadDirRelative() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'myrome', {", + " target '**/*.js'", + " rome('12.0.0').downloadDir('target/bin/rome')", + " }", + "}"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + assertTrue(!newFile("target/bin/rome").exists() || newFile("target/bin/rome").list().length == 0); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("rome_test.js").sameAsResource("rome/js/fileAfter.js"); + assertEquals(2, newFile("target/bin/rome").list().length); + } + + /** + * Tests that the build fails when given Biome executable does not exist. + * + * @throws Exception When a test failure occurs. + */ + @Test + void failureWhenExeNotFound() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'myrome', {", + " target '**/*.js'", + " rome('12.0.0').pathToExe('rome/is/missing')", + " }", + "}"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").buildAndFail(); + assertThat(spotlessApply.getOutput()).contains("Build failed with an exception"); + assertFile("rome_test.js").sameAsResource("rome/js/fileBefore.js"); + assertThat(spotlessApply.getOutput()).contains("Could not create task ':spotlessMyromeApply'"); + assertThat(spotlessApply.getOutput()).contains("Biome executable does not exist"); + } + + /** + * Tests that the build fails when the input file could not be parsed. + * + * @throws Exception When a test failure occurs. + */ + @Test + void failureWhenNotParseable() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'myrome', {", + " target '**/*.js'", + " rome('12.0.0').language('json')", + " }", + "}"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").buildAndFail(); + assertThat(spotlessApply.getOutput()).contains("spotlessMyrome FAILED"); + assertFile("rome_test.js").sameAsResource("rome/js/fileBefore.js"); + assertThat(spotlessApply.getOutput()).contains("Format with errors is disabled."); + assertThat(spotlessApply.getOutput()).contains("Step 'rome' found problem in 'rome_test.js'"); + } +} diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ShellExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ShellExtensionTest.java new file mode 100644 index 0000000000..1a4f567ccb --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/ShellExtensionTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 2024 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.tag.ShfmtTest; + +@ShfmtTest +public class ShellExtensionTest extends GradleIntegrationHarness { + @Test + void shfmt() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "spotless {", + " shell {", + " shfmt()", + " }", + "}"); + setFile("shfmt.sh").toResource("shell/shfmt/shfmt.sh"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("shfmt.sh").sameAsResource("shell/shfmt/shfmt.clean"); + } +} diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/SpotlessPluginRedirectTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/SpotlessPluginRedirectTest.java index acb57f3763..a427d6f2ec 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/SpotlessPluginRedirectTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/SpotlessPluginRedirectTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -54,7 +54,7 @@ void redirectPluginOldGradle() throws IOException { " > We have moved from 'com.diffplug.gradle.spotless'", " to 'com.diffplug.spotless'", " To migrate:", - " - Upgrade gradle to 6.1.1 or newer (you're on 5.0)", + " - Upgrade Gradle to 6.1.1 or newer (you're on 5.0)", " - Test your build with: id 'com.diffplug.gradle.spotless' version '4.5.1'")); } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/SpotlessTaskImplTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/SpotlessTaskImplTest.java new file mode 100644 index 0000000000..fc7fab9980 --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/SpotlessTaskImplTest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import java.io.File; +import java.nio.file.Paths; + +import org.assertj.core.api.Assertions; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.logging.Logger; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import com.diffplug.spotless.Formatter; + +public class SpotlessTaskImplTest { + @Test + public void testThrowsMessageContainsFilename() throws Exception { + SpotlessTaskImpl task = Mockito.mock(SpotlessTaskImpl.class, Mockito.CALLS_REAL_METHODS); + Mockito.when(task.getLogger()).thenReturn(Mockito.mock(Logger.class)); + + File projectDir = Paths.get("unitTests", "projectDir").toFile(); + DirectoryProperty projectDirProperty = Mockito.mock(DirectoryProperty.class, Mockito.RETURNS_DEEP_STUBS); + Mockito.when(projectDirProperty.get().getAsFile()).thenReturn(projectDir); + + Mockito.when(task.getProjectDir()).thenReturn(projectDirProperty); + + File input = Paths.get("unitTests", "projectDir", "someInput").toFile(); + Formatter formatter = Mockito.mock(Formatter.class); + + Assertions.assertThatThrownBy(() -> task.processInputFile(null, formatter, input)).hasMessageContaining(input.toString()); + } +} diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/TargetExcludeIfContentContainsTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/TargetExcludeIfContentContainsTest.java new file mode 100644 index 0000000000..f1a2cf4298 --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/TargetExcludeIfContentContainsTest.java @@ -0,0 +1,141 @@ +/* + * Copyright 2020-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +class TargetExcludeIfContentContainsTest extends GradleIntegrationHarness { + @Test + void targetExcludeIfContentContainsWithOneValue() throws IOException { + setFile("build.gradle").toLines( + "plugins { id 'com.diffplug.spotless' }", + "spotless {", + " format 'toLower', {", + " target '**/*.md'", + " targetExcludeIfContentContains '// Generated by Mr. Roboto'", + " custom 'lowercase', { str -> str.toLowerCase() }", + " }", + "}"); + String content = "// Generated by Mr. Roboto, do not edit.\n" + + "A B C\n" + + "D E F\n" + + "G H I"; + setFile("test_generated.md").toContent(content); + setFile("test_manual.md").toLines( + "A B C", + "D E F", + "G H I"); + gradleRunner().withArguments("spotlessApply").build(); + // `test_generated` contains the excluding text so didn't change. + assertFile("test_generated.md").hasContent(content); + // `test_manual` does not so it changed. + assertFile("test_manual.md").hasLines( + "a b c", + "d e f", + "g h i"); + } + + @Test + void targetExcludeIfContentContainsWithMultipleSteps() throws IOException { + setFile("build.gradle").toLines( + "plugins { id 'com.diffplug.spotless' }", + "spotless {", + " format 'toLower', {", + " target '**/*.md'", + " targetExcludeIfContentContains '// Generated by Mr. Roboto'", + " custom 'lowercase', { str -> str.toLowerCase() }", + " licenseHeader('" + "// My CopyRights header" + "', '--')", + " }", + "}"); + String generatedContent = "// Generated by Mr. Roboto, do not edit.\n" + + "--\n" + + "public final class MyMessage {}\n"; + setFile("test_generated.md").toContent(generatedContent); + String manualContent = "// Typo in License\n" + + "--\n" + + "public final class MyMessage {\n" + + "}"; + setFile("test_manual.md").toContent(manualContent); + gradleRunner().withArguments("spotlessApply").build(); + + // `test_generated` contains the excluding text so didn't change, including the header. + assertFile("test_generated.md").hasContent(generatedContent); + // `test_manual` does not, it changed. + assertFile("test_manual.md").hasContent( + "// My CopyRights header\n" + + "--\n" + + "public final class mymessage {\n" + + "}"); + } + + @Test + void targetExcludeIfContentContainsRegex() throws IOException { + setFile("build.gradle").toLines( + "plugins { id 'com.diffplug.spotless' }", + "spotless {", + " format 'toLower', {", + " target '**/*.md'", + " targetExcludeIfContentContainsRegex '// Generated by Mr. Roboto|// Generated by Mrs. Call'", + " custom 'lowercase', { str -> str.toLowerCase() }", + " }", + "}"); + String robotoContent = "A B C\n" + + "// Generated by Mr. Roboto, do not edit.\n" + + "D E F\n" + + "G H I"; + setFile("test_generated_roboto.md").toContent(robotoContent); + String callContent = "A B C\n" + + "D E F\n" + + "// Generated by Mrs. Call, do not edit.\n" + + "G H I"; + setFile("test_generated_call.md").toContent(callContent); + String collaborationContent = "A B C\n" + + "// Generated by Mr. Roboto, do not edit.\n" + + "D E F\n" + + "// Generated by Mrs. Call, do not edit.\n" + + "G H I"; + setFile("test_generated_collaboration.md").toContent(collaborationContent); + String intruderContent = "A B C\n" + + "// Generated by K2000, do not edit.\n" + + "D E F\n" + + "G H I"; + setFile("test_generated_intruder.md").toContent(intruderContent); + setFile("test_manual.md").toLines( + "A B C", + "D E F", + "G H I"); + gradleRunner().withArguments("spotlessApply").build(); + // Part of the excluding values so has not changed. + assertFile("test_generated_roboto.md").hasContent(robotoContent); + // Part of the excluding values so has not changed. + assertFile("test_generated_call.md").hasContent(callContent); + // Part of the excluding values so has not changed. + assertFile("test_generated_collaboration.md").hasContent(collaborationContent); + // Not part of the excluding values so has changed. + assertFile("test_generated_intruder.md").hasContent( + "a b c\n" + + "// generated by k2000, do not edit.\n" + + "d e f\n" + + "g h i"); + // `test_manual` does not, it changed. + assertFile("test_manual.md").hasLines( + "a b c", + "d e f", + "g h i"); + } +} diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/TypescriptExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/TypescriptExtensionTest.java index 100c4eb3b0..9b4ec8372e 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/TypescriptExtensionTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/TypescriptExtensionTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,10 +19,17 @@ import org.junit.jupiter.api.Test; +import com.diffplug.spotless.npm.EslintFormatterStep; +import com.diffplug.spotless.npm.EslintStyleGuide; import com.diffplug.spotless.tag.NpmTest; @NpmTest class TypescriptExtensionTest extends GradleIntegrationHarness { + + private static String styleGuideMapString(String styleGuideName) { + return EslintStyleGuide.fromNameOrNull(styleGuideName).asGradleMapStringMergedWith(EslintFormatterStep.defaultDevDependencies()); + } + @Test void allowToSpecifyFormatterVersion() throws IOException { setFile("build.gradle").toLines( @@ -141,4 +148,63 @@ void usePrettier() throws IOException { gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); assertFile("test.ts").sameAsResource("npm/prettier/filetypes/typescript/typescript.clean"); } + + @Test + void useEslint() throws IOException { + setFile(".eslintrc.js").toResource("npm/eslint/typescript/custom_rules/.eslintrc.js"); + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " typescript {", + " target 'test.ts'", + " eslint().configFile('.eslintrc.js')", + " }", + "}"); + setFile("test.ts").toResource("npm/eslint/typescript/custom_rules/typescript.dirty"); + gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertFile("test.ts").sameAsResource("npm/eslint/typescript/custom_rules/typescript.clean"); + } + + @Test + void useEslintXoStandardRules() throws IOException { + setFile(".eslintrc.js").toResource("npm/eslint/typescript/styleguide/xo/.eslintrc.js"); + setFile("tsconfig.json").toResource("npm/eslint/typescript/styleguide/xo/tsconfig.json"); + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " typescript {", + " target 'test.ts'", + " eslint(" + styleGuideMapString("xo-typescript") + ").configFile('.eslintrc.js')", + " }", + "}"); + setFile("test.ts").toResource("npm/eslint/typescript/styleguide/xo/typescript.dirty"); + gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertFile("test.ts").sameAsResource("npm/eslint/typescript/styleguide/xo/typescript.clean"); + } + + @Test + void useEslintStandardWithTypescriptRules() throws IOException { + setFile(".eslintrc.js").toResource("npm/eslint/typescript/styleguide/standard_with_typescript/.eslintrc.js"); + setFile("tsconfig.json").toResource("npm/eslint/typescript/styleguide/standard_with_typescript/tsconfig.json"); + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " typescript {", + " target 'test.ts'", + " eslint(" + styleGuideMapString("standard-with-typescript") + ").configFile('.eslintrc.js')", + " }", + "}"); + setFile("test.ts").toResource("npm/eslint/typescript/styleguide/standard_with_typescript/typescript.dirty"); + gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertFile("test.ts").sameAsResource("npm/eslint/typescript/styleguide/standard_with_typescript/typescript.clean"); + } } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/WithinBlockTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/WithinBlockTest.java index 01fb048b66..d1df8b5f96 100644 --- a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/WithinBlockTest.java +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/WithinBlockTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ void genericFormatTest() throws IOException { "spotless {", " format 'customJava', JavaExtension, {", " target '*.java'", - " googleJavaFormat('1.2')", + " googleJavaFormat()", " }", "}"); setFile("test.java").toResource("java/googlejavaformat/JavaCodeUnformatted.test"); @@ -53,7 +53,7 @@ void withinBlocksTourDeForce() throws IOException { " custom 'lowercase', { str -> str.toLowerCase() }", " }", " withinBlocks 'java only', '\\n```java\\n', '\\n```\\n', JavaExtension, {", - " googleJavaFormat('1.2')", + " googleJavaFormat()", " }", " }", "}"); diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java new file mode 100644 index 0000000000..067c809846 --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/YamlExtensionTest.java @@ -0,0 +1,102 @@ +/* + * Copyright 2021-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.gradle.spotless; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; + +class YamlExtensionTest extends GradleIntegrationHarness { + @Test + void testFormatYaml_WithJackson_defaultConfig_separatorComments() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'java'", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " yaml {", + " target 'src/**/*.yaml'", + " jackson()", + " }", + "}"); + setFile("src/main/resources/example.yaml").toResource("yaml/separator_comments.yaml"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("src/main/resources/example.yaml").sameAsResource("yaml/separator_comments.clean.yaml"); + } + + // see YAMLGenerator.Feature.WRITE_DOC_START_MARKER + @Test + void testFormatYaml_WithJackson_skipDocStartMarker() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'java'", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " yaml {", + " target 'src/**/*.yaml'", + " jackson()", + " .yamlFeature('WRITE_DOC_START_MARKER', false)", + " .yamlFeature('MINIMIZE_QUOTES', true)", + " }", + "}"); + setFile("src/main/resources/example.yaml").toResource("yaml/array_with_bracket.yaml"); + gradleRunner().withArguments("spotlessApply", "--stacktrace").build(); + assertFile("src/main/resources/example.yaml").sameAsResource("yaml/array_with_bracket.clean.no_start_marker.no_quotes.yaml"); + } + + @Test + void testFormatYaml_WithJackson_multipleDocuments() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'java'", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " yaml {", + " target 'src/**/*.yaml'", + " jackson()", + " }", + "}"); + setFile("src/main/resources/example.yaml").toResource("yaml/multiple_documents.yaml"); + gradleRunner().withArguments("spotlessApply", "--stacktrace").build(); + assertFile("src/main/resources/example.yaml").sameAsResource("yaml/multiple_documents.clean.jackson.yaml"); + } + + @Test + void testFormatYaml_WithJackson_arrayAtRoot() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'java'", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " yaml {", + " target 'src/**/*.yaml'", + " jackson()", + " }", + "}"); + setFile("src/main/resources/example.yaml").toResource("yaml/array_at_root.yaml"); + gradleRunner().withArguments("spotlessApply", "--stacktrace").build(); + assertFile("src/main/resources/example.yaml").sameAsResource("yaml/array_at_root.clean.yaml"); + } + +} diff --git a/plugin-gradle/src/test/resources/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest_gradle_node_plugin_example_1.gradle b/plugin-gradle/src/test/resources/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest_gradle_node_plugin_example_1.gradle new file mode 100644 index 0000000000..c0fa255bcc --- /dev/null +++ b/plugin-gradle/src/test/resources/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest_gradle_node_plugin_example_1.gradle @@ -0,0 +1,38 @@ +/** + * This example shows how to use the gradle-node-plugin to install node and npm in separate directories + * and use these binaries with the prettier formatter. + */ +plugins { + id 'com.diffplug.spotless' + id 'com.github.node-gradle.node' version '3.5.1' +} +repositories { mavenCentral() } +node { + download = true + version = '18.16.1' + npmVersion = '9.5.1' + // when setting both these directories, npm and node will be in separate directories + workDir = file("${buildDir}/nodejs") + npmWorkDir = file("${buildDir}/npm") +} +def prettierConfig = [:] +prettierConfig['printWidth'] = 20 +prettierConfig['parser'] = 'typescript' + +// the executable names +def npmExec = System.getProperty('os.name').toLowerCase().contains('windows') ? '/npm.cmd' : '/bin/npm' +def nodeExec = System.getProperty('os.name').toLowerCase().contains('windows') ? '/node.exe' : '/bin/node' + +spotless { + format 'mytypescript', { + target 'test.ts' + prettier() + .npmExecutable("${tasks.named('npmSetup').get().npmDir.get()}${npmExec}") // get the npm executable path from gradle-node-plugin + .nodeExecutable("${tasks.named('nodeSetup').get().nodeDir.get()}${nodeExec}") // get the node executable path from gradle-node-plugin + .config(prettierConfig) + } +} + +tasks.named('spotlessMytypescript').configure { + it.dependsOn('nodeSetup', 'npmSetup') +} diff --git a/plugin-gradle/src/test/resources/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest_gradle_node_plugin_example_2.gradle b/plugin-gradle/src/test/resources/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest_gradle_node_plugin_example_2.gradle new file mode 100644 index 0000000000..6d77bc3d69 --- /dev/null +++ b/plugin-gradle/src/test/resources/com/diffplug/gradle/spotless/NpmTestsWithoutNpmInstallationTest_gradle_node_plugin_example_2.gradle @@ -0,0 +1,35 @@ +/** + * This example shows how to use the gradle-node-plugin to install node and npm together and + * use these binaries with the prettier formatter. + */ +plugins { + id 'com.diffplug.spotless' + id 'com.github.node-gradle.node' version '3.5.1' +} +repositories { mavenCentral() } +node { + download = true + version = '18.16.1' + // when not setting an explicit `npmWorkDir`, the npm binary will be installed next to the node binary + workDir = file("${buildDir}/nodejs") +} +def prettierConfig = [:] +prettierConfig['printWidth'] = 20 +prettierConfig['parser'] = 'typescript' + +// the executable name +def npmExec = System.getProperty('os.name').toLowerCase().contains('windows') ? '/npm.cmd' : '/bin/npm' + +spotless { + typescript { + target 'test.ts' + prettier() + .npmExecutable("${tasks.named('npmSetup').get().npmDir.get()}${npmExec}") // get the npm executable path from gradle-node-plugin + // setting the nodeExecutable is not necessary, since it will be found in the same directory as the npm executable (see above) + .config(prettierConfig) + } +} + +tasks.named('spotlessTypescript').configure { + it.dependsOn('nodeSetup', 'npmSetup') +} diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index 5d47dc4f4f..d1f158d533 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -4,6 +4,254 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### Added +* Support for formatting shell scripts via [shfmt](https://github.com/mvdan/sh). ([#1998](https://github.com/diffplug/spotless/issues/1998)) +* Support for `gofmt` ([#2001](https://github.com/diffplug/spotless/pull/2001)) +* Support for formatting Java Docs for the Palantir formatter ([#2009](https://github.com/diffplug/spotless/pull/2009)) + +## [2.42.0] - 2024-01-15 +### Added +* M2E support: Emit file specific errors during incremental build. ([#1960](https://github.com/diffplug/spotless/issues/1960)) +### Fixed +* Fix empty files with biome >= 1.5.0 when formatting files that are in the ignore list of the biome configuration file. ([#1989](https://github.com/diffplug/spotless/pull/1989) fixes [#1987](https://github.com/diffplug/spotless/issues/1987)) +### Changes +* Use palantir-java-format 2.39.0 on Java 21. ([#1948](https://github.com/diffplug/spotless/pull/1948)) +* Bump default `ktlint` version to latest `1.0.1` -> `1.1.1`. ([#1973](https://github.com/diffplug/spotless/pull/1973)) +* Bump default `googleJavaFormat` version to latest `1.18.1` -> `1.19.2`. ([#1971](https://github.com/diffplug/spotless/pull/1971)) +* Bump default `diktat` version to latest `1.2.5` -> `2.0.0`. ([#1972](https://github.com/diffplug/spotless/pull/1972)) + +## [2.41.1] - 2023-12-04 +### Fixed +* Revert [#1846](https://github.com/diffplug/spotless/issues/1846) from 2.41.0 which causes the plugin to format generated sources in the `target` directory. ([#1928](https://github.com/diffplug/spotless/pull/1928)) +* Eclipse-based steps which contained any jars with a `+` in their path were broken, now fixed. ([#1860](https://github.com/diffplug/spotless/issues/1860#issuecomment-1826113332)) +### Changes +* Bump default `palantir-java-format` version to latest `2.28.0` -> `2.38.0` on Java 21. ([#1920](https://github.com/diffplug/spotless/pull/1920)) +* Bump default `googleJavaFormat` version to latest `1.17.0` -> `1.18.1`. ([#1920](https://github.com/diffplug/spotless/pull/1920)) +* Bump default `ktfmt` version to latest `0.44` -> `0.46`. ([#1927](https://github.com/diffplug/spotless/pull/1927)) +* Bump default `eclipse` version to latest `4.27` -> `4.29`. ([#1939](https://github.com/diffplug/spotless/pull/1939)) +* Bump default `greclipse` version to latest `4.28` -> `4.29`. ([#1939](https://github.com/diffplug/spotless/pull/1939)) +* Bump default `cdt` version to latest `11.1` -> `11.3`. ([#1939](https://github.com/diffplug/spotless/pull/1939)) + +## [2.41.0] - 2023-11-27 +### Added +* ~~CompileSourceRoots and TestCompileSourceRoots are now respected as default includes. These properties are commonly set when adding extra source directories.~~ ([#1846](https://github.com/diffplug/spotless/issues/1846)) + * Reverted in the next release (`2.41.1`) due to backward compatibility problems, see [#1914](https://github.com/diffplug/spotless/issues/1914). +* Support custom rule sets for Ktlint. ([#1896](https://github.com/diffplug/spotless/pull/1896)) +### Fixed +* Fix crash when build dir is a softlink to another directory. ([#1859](https://github.com/diffplug/spotless/pull/1859)) +* Fix Eclipse JDT on some settings files. ([#1864](https://github.com/diffplug/spotless/pull/1864) fixes [#1638](https://github.com/diffplug/spotless/issues/1638)) +### Changes +* Bump default `ktlint` version to latest `1.0.0` -> `1.0.1`. ([#1855](https://github.com/diffplug/spotless/pull/1855)) +* Add a Step to remove semicolons from Groovy files. ([#1881](https://github.com/diffplug/spotless/pull/1881)) + +## [2.40.0] - 2023-09-28 +### Added +* Add `-DspotlessIdeHook` that provides the ability to apply Spotless exclusively to a specified file. It accepts the absolute path of the file. ([#1782](https://github.com/diffplug/spotless/pull/1782)) + * BETA, subject to change until we have proven compatibility with some IDE plugins. +* Added support for `google-java-format`'s `skip-javadoc-formatting` option ([#1793](https://github.com/diffplug/spotless/pull/1793)) +* Added support for biome. The Rome project [was renamed to Biome](https://biomejs.dev/blog/annoucing-biome/). + The configuration is still the same, but you should switch to the new `` tag and adjust + the version accordingly. ([#1804](https://github.com/diffplug/spotless/issues/1804)). +* Support configuration of mirrors for P2 repositories ([#1697](https://github.com/diffplug/spotless/issues/1697)): + ``` + + + + https://download.eclipse.org/ + https://some.internal.mirror/eclipse + + + + ``` + Mirrors are selected by prefix match, for example `https://download.eclipse.org/eclipse/updates/4.26/` will be redirected to `https://some.internal.mirror/eclipse/eclipse/updates/4.26/`. + The same configuration exists for `` and ``. +### Fixed +* Fixed support for plugins when using Prettier version `3.0.0` and newer. ([#1802](https://github.com/diffplug/spotless/pull/1802)) +### Changes +* Bump default `flexmark` version to latest `0.64.0` -> `0.64.8`. ([#1801](https://github.com/diffplug/spotless/pull/1801)) +* Bump default `ktlint` version to latest `0.50.0` -> `1.0.0`. ([#1808](https://github.com/diffplug/spotless/pull/1808)) +* **POSSIBLY BREAKING** the default line endings are now `GIT_ATTRIBUTES_FAST_ALLSAME` instead of `GIT_ATTRIBUTES`. ([#1838](https://github.com/diffplug/spotless/pull/1838)) + * If all the files within a format have the same line endings, then there is no change in behavior. + * Fixes large performance regression. ([#1527](https://github.com/diffplug/spotless/issues/1527)) + +## [2.39.0] - 2023-08-29 +### Added +* Add a `jsonPatch` step to `json` formatter configurations. This allows patching of JSON documents using [JSON Patches](https://jsonpatch.com). ([#1753](https://github.com/diffplug/spotless/pull/1753)) +* Support GJF own import order. ([#1780](https://github.com/diffplug/spotless/pull/1780)) +### Fixed +* Add support for `prettier` version `3.0.0` and newer. ([#1760](https://github.com/diffplug/spotless/pull/1760), [#1751](https://github.com/diffplug/spotless/issues/1751)) +* Fix npm install calls when npm cache is not up-to-date. ([#1760](https://github.com/diffplug/spotless/pull/1760), [#1750](https://github.com/diffplug/spotless/issues/1750)) +### Changes +* Bump default `eslint` version to latest `8.31.0` -> `8.45.0` ([#1761](https://github.com/diffplug/spotless/pull/1761)) +* Bump default `prettier` version to latest (v2) `2.8.1` -> `2.8.8`. ([#1760](https://github.com/diffplug/spotless/pull/1760)) +* Bump default `greclipse` version to latest `4.27` -> `4.28`. ([#1775](https://github.com/diffplug/spotless/pull/1775)) + +## [2.38.0] - 2023-07-17 +### Added +* Support pass skip (`-Dspotless.skip=true`) from command-line. ([#1729](https://github.com/diffplug/spotless/pull/1729)) +### Fixed +* Update documented default `semanticSort` to `false`. ([#1728](https://github.com/diffplug/spotless/pull/1728)) +### Changes +* Bump default `cleanthat` version to latest `2.13` -> `2.17`. ([#1734](https://github.com/diffplug/spotless/pull/1734)) +* Bump default `ktlint` version to latest `0.49.1` -> `0.50.0`. ([#1741](https://github.com/diffplug/spotless/issues/1741)) + * Dropped support for `ktlint 0.47.x` following our policy of supporting two breaking changes at a time. + * Dropped support for deprecated `useExperimental` parameter in favor of the `ktlint_experimental` property. + +## [2.37.0] - 2023-05-24 +### Added +* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. ([#1663](https://github.com/diffplug/spotless/pull/1663)) +* Add semantics-aware Java import ordering (i.e. sort by package, then class, then member). ([#522](https://github.com/diffplug/spotless/issues/522)) +### Fixed +* `palantir` step now accepts a `style` parameter, which is documentation had already claimed to do. ([#1694](https://github.com/diffplug/spotless/pull/1694)) +* Fixed a regression which changed the import sorting order in `googleJavaFormat` introduced in `2.36.0`. ([#1680](https://github.com/diffplug/spotless/pull/1680)) +* Equo-based formatters now work on platforms unsupported by Eclipse such as PowerPC (fixes [durian-swt#20](https://github.com/diffplug/durian-swt/issues/20)) +* When P2 download fails, indicate the responsible formatter. ([#1698](https://github.com/diffplug/spotless/issues/1698)) +### Changes +* Equo-based formatters now download metadata to `~/.m2/repository/dev/equo/p2-data` rather than `~/.equo`, and for CI machines without a home directory the p2 data goes to `$GRADLE_USER_HOME/caches/p2-data`. ([#1714](https://github.com/diffplug/spotless/pull/1714)) +* Bump default `googleJavaFormat` version to latest `1.16.0` -> `1.17.0`. ([#1710](https://github.com/diffplug/spotless/pull/1710)) +* Bump default `ktfmt` version to latest `0.43` -> `0.44`. ([#1691](https://github.com/diffplug/spotless/pull/1691)) +* Bump default `ktlint` version to latest `0.48.2` -> `0.49.1`. ([#1696](https://github.com/diffplug/spotless/issues/1696)) + * Dropped support for `ktlint 0.46.x` following our policy of supporting two breaking changes at a time. +* Bump default `sortpom` version to latest `3.0.0` -> `3.2.1`. ([#1675](https://github.com/diffplug/spotless/pull/1675)) + +## [2.36.0] - 2023-04-06 +### Added +* `removeUnusedImport` can be configured to rely on `cleanthat-javaparser-unnecessaryimport`. Default remains `google-java-format`. ([#1589](https://github.com/diffplug/spotless/pull/1589)) +* The `style` option in Palantir Java Format ([#1654](https://github.com/diffplug/spotless/pull/1654)). +* Added formatter for Gherkin feature files ([#1649](https://github.com/diffplug/spotless/issues/1649)). +### Fixed +* Fix non deterministic computation of cache fingerprint when using multiple formatters. ([#1643](https://github.com/diffplug/spotless/pull/1643) fixes [#1642](https://github.com/diffplug/spotless/pull/1642)) +### Changes +* **POTENTIALLY BREAKING** Drop support for `googleJavaFormat` versions < `1.8`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) +* Bump default `cleanthat` version to latest `2.6` -> `2.13`. ([#1589](https://github.com/diffplug/spotless/pull/1589) and [#1661](https://github.com/diffplug/spotless/pull/1661)) +* Bump default `diktat` version `1.2.4.2` -> `1.2.5`. ([#1631](https://github.com/diffplug/spotless/pull/1631)) +* Bump default `flexmark` version `0.62.2` -> `0.64.0`. ([#1302](https://github.com/diffplug/spotless/pull/1302)) +* Bump default `googleJavaFormat` version `1.15.0` -> `1.16.0`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) +* Bump default `scalafmt` version `3.7.1` -> `3.7.3`. ([#1584](https://github.com/diffplug/spotless/pull/1584)) +* Bump default Eclipse formatters for the 2023-03 release. ([#1662](https://github.com/diffplug/spotless/pull/1662)) + * JDT and GrEclipse `4.26` -> `4.27` + * Improve GrEclipse error reporting. ([#1660](https://github.com/diffplug/spotless/pull/1660)) + * CDT `11.0` -> `11.1` + +## [2.35.0] - 2023-03-13 +### Added +* You can now put the filename into a license header template with `$FILE`. ([#1605](https://github.com/diffplug/spotless/pull/1605) fixes [#1147](https://github.com/diffplug/spotless/issues/1147)) +### Fixed +* `licenseHeader` default pattern for Java files is updated to `(package|import|public|class|module) `. ([#1614](https://github.com/diffplug/spotless/pull/1614)) +### Changes +* Enable incremental up-to-date checking by default. ([#1621](https://github.com/diffplug/spotless/pull/1621)) +* All Eclipse formatters are now based on [Equo Solstice OSGi and p2 shim](https://github.com/equodev/equo-ide/tree/main/solstice). ([#1524](https://github.com/diffplug/spotless/pull/1524)) + * Eclipse JDT bumped default to `4.26` from `4.21`, oldest supported is `4.9`. + * We now recommend dropping the last `.0`, e.g. `4.26` instead of `4.26.0`, you'll get warnings to help you switch. + * Eclipse Groovy bumped default to `4.26` from `4.21`, oldest supported is `4.18`. + * Eclipse CDT bumped default to `11.0` from `4.21`, oldest supported is `10.6`. + * Eclipse WTP is still WIP at [#1622](https://github.com/diffplug/spotless/pull/1622). + +## [2.34.0] - 2023-02-27 +### Added +* `cleanthat` added `includeDraft` option, to include draft mutators from composite mutators. ([#1574](https://github.com/diffplug/spotless/pull/1574)) +* `npm`-based formatters (`prettier`, `tsfmt` and `eslint`) now support caching of `node_modules` directory. + To enable it, provide the `` option. ([#1590](https://github.com/diffplug/spotless/pull/1590)) +### Fixed +* `` can now handle `Array` as a root element. ([#1585](https://github.com/diffplug/spotless/pull/1585)) +* Reduce logging-noise created by `npm`-based formatters ([#1590](https://github.com/diffplug/spotless/pull/1590) fixes [#1582](https://github.com/diffplug/spotless/issues/1582)) +### Changes +* Bump default `cleanthat` version to latest `2.1` -> `2.6` ([#1569](https://github.com/diffplug/spotless/pull/1569) and [#1574](https://github.com/diffplug/spotless/pull/1574)) + +## [2.33.0] - 2023-02-10 +### Added +* CleanThat Java Refactorer. ([#1560](https://github.com/diffplug/spotless/pull/1560)) +### Fixed +* Allow multiple instances of the same npm-based formatter to be used simultaneously. E.g. use prettier for typescript + *and* Java (using the community prettier-plugin-java) without messing up their respective `node_module` dependencies. ([#1565](https://github.com/diffplug/spotless/pull/1565)) +* `ktfmt` default style uses correct continuation indent. ([#1562](https://github.com/diffplug/spotless/pull/1562)) +### Changes +* Bump default `ktfmt` version to latest `0.42` -> `0.43` ([#1561](https://github.com/diffplug/spotless/pull/1561)) +* Bump default `jackson` version to latest `2.14.1` -> `2.14.2` ([#1536](https://github.com/diffplug/spotless/pull/1536)) + +## [2.32.0] - 2023-02-05 +### Added +* A synthesis log with the number of considered files is added after each formatter execution ([#1507](https://github.com/diffplug/spotless/pull/1507)) +### Fixed +* Respect `sourceDirectory` and `testSourceDirectory` POM configurations for Java formatters ([#1553](https://github.com/diffplug/spotless/pull/1553)) +* **POTENTIALLY BREAKING** `sortByKeys` for JSON formatting now takes into account objects inside arrays ([#1546](https://github.com/diffplug/spotless/pull/1546)) +* Any commit of the Spotless Maven plugin now available via JitPack ([#1547](https://github.com/diffplug/spotless/pull/1547)) + +## [2.31.0] - 2023-01-26 +### Added +* Prettier will now suggest to install plugins if a parser cannot be inferred from the file extension ([#1511](https://github.com/diffplug/spotless/pull/1511)) +* Jackson (`json` and `yaml`) has new `spaceBeforeSeparator` option + * **POTENTIALLY BREAKING** `spaceBeforeSeparator` is defaulted to false while the formatter was behaving with `true` +* Introduce `` ([#1492](https://github.com/diffplug/spotless/pull/1492)) + * **POTENTIALLY BREAKING** `JacksonYaml` is now configured with a `Map` to configure features +* Allow to specify node executable for node-based formatters using `nodeExecutable` parameter ([#1500](https://github.com/diffplug/spotless/pull/1500)) +### Fixed +* The default list of type annotations used by `formatAnnotations` has had 8 more annotations from the Checker Framework added [#1494](https://github.com/diffplug/spotless/pull/1494) +* **POTENTIALLY BREAKING** Generate the correct qualifiedRuleId for Ktlint 0.48.x [#1495](https://github.com/diffplug/spotless/pull/1495) +### Changes +* **POTENTIALLY BREAKING** Bump minimum JRE from 8 to 11 ([#1514](https://github.com/diffplug/spotless/pull/1514) part 1 of [#1337](https://github.com/diffplug/spotless/issues/1337)) + * You can bump your build JRE without bumping your requirements ([docs](https://maven.apache.org/plugins/maven-compiler-plugin/examples/set-compiler-source-and-target.html)). +* Spotless' custom build was replaced by [`maven-plugin-development`](https://github.com/britter/maven-plugin-development). ([#1496](https://github.com/diffplug/spotless/pull/1496) fixes [#554](https://github.com/diffplug/spotless/issues/554)) +* **POTENTIALLY BREAKING** Removed support for KtLint 0.3x and 0.45.2 ([#1475](https://github.com/diffplug/spotless/pull/1475)) + * `KtLint` does not maintain a stable API - before this PR, we supported every breaking change in the API since 2019. + * From now on, we will support no more than 2 breaking changes at a time. +* `npm`-based formatters `ESLint`, `prettier` and `tsfmt` delay their `npm install` call until the formatters are first used. ([#1522](https://github.com/diffplug/spotless/pull/1522) +* Bump default `ktlint` version to latest `0.48.1` -> `0.48.2` ([#1529](https://github.com/diffplug/spotless/pull/1529)) +* Bump default `scalafmt` version to latest `3.6.1` -> `3.7.1` ([#1529](https://github.com/diffplug/spotless/pull/1529)) + +## [2.30.0] - 2023-01-13 +### Added +* Add option `editorConfigFile` for `ktLint` [#142](https://github.com/diffplug/spotless/issues/142) + * **POTENTIALLY BREAKING** `ktlint` step now modifies license headers. Make sure to put `licenseHeader` *after* `ktlint`. +* Added `skipLinesMatching` option to `licenseHeader` to support formats where license header cannot be immediately added to the top of the file (e.g. xml, sh). ([#1441](https://github.com/diffplug/spotless/pull/1441)) +* Add JSON support ([#1446](https://github.com/diffplug/spotless/pull/1446)) +* Add YAML support through Jackson ([#1478](https://github.com/diffplug/spotless/pull/1478)) +* Added support for npm-based [ESLint](https://eslint.org/)-formatter for javascript and typescript ([#1453](https://github.com/diffplug/spotless/pull/1453)) +* Better suggested messages when user's default is set by JVM limitation. ([#995](https://github.com/diffplug/spotless/pull/995)) +### Fixed +* Support `ktlint` 0.48+ new rule disabling syntax ([#1456](https://github.com/diffplug/spotless/pull/1456)) fixes ([#1444](https://github.com/diffplug/spotless/issues/1444)) +* Fix subgroups leading catch all matcher. +### Changes +* Bump default `ktlint` version to latest `0.47.1` -> `0.48.1` ([#1456](https://github.com/diffplug/spotless/pull/1456)) +* Reduce spurious invalidations of the up-to-date index file ([#1461](https://github.com/diffplug/spotless/pull/1461)) +* Bump default version for `prettier` from `2.0.5` to `2.8.1` ([#1453](https://github.com/diffplug/spotless/pull/1453)) + +## [2.29.0] - 2023-01-02 +### Added +* Added support for M2E's incremental compilation ([#1414](https://github.com/diffplug/spotless/pull/1414) fixes [#1413](https://github.com/diffplug/spotless/issues/1413)) +### Fixed +* Improve memory usage when using git ratchet ([#1426](https://github.com/diffplug/spotless/pull/1426)) +* Support `ktlint` 0.48+ ([#1432](https://github.com/diffplug/spotless/pull/1432)) fixes ([#1430](https://github.com/diffplug/spotless/issues/1430)) +### Changes +* Bump default `ktlint` version to latest `0.47.1` -> `0.48.0` ([#1432](https://github.com/diffplug/spotless/pull/1432)) +* Bump default `ktfmt` version to latest `0.41` -> `0.42` ([#1421](https://github.com/diffplug/spotless/pull/1421)) + +## [2.28.0] - 2022-11-24 +### Added +* `importOrder` now support groups of imports without blank lines ([#1401](https://github.com/diffplug/spotless/pull/1401)) +### Fixed +* Don't treat `@Value` as a type annotation [#1367](https://github.com/diffplug/spotless/pull/1367) +* Support `ktlint_disabled_rules` in `ktlint` 0.47.x [#1378](https://github.com/diffplug/spotless/pull/1378) +* Share git repositories across projects when using ratchet ([#1426](https://github.com/diffplug/spotless/pull/1426)) +### Changes +* Bump default `ktfmt` version to latest `0.40` -> `0.41` ([#1340](https://github.com/diffplug/spotless/pull/1340)) +* Bump default `scalafmt` version to latest `3.5.9` -> `3.6.1` ([#1373](https://github.com/diffplug/spotless/pull/1373)) +* Bump default `diktat` version to latest `1.2.3` -> `1.2.4.2` ([#1393](https://github.com/diffplug/spotless/pull/1393)) +* Bump default `palantir-java-format` version to latest `2.10` -> `2.28` ([#1393](https://github.com/diffplug/spotless/pull/1393)) + +## [2.27.2] - 2022-10-10 +### Fixed +* `replace` and `replaceRegex` steps now allow you to replace something with an empty string, previously this would generate a null pointer exception. (fixes [#1359](https://github.com/diffplug/spotless/issues/1359)) + +## [2.27.1] - 2022-09-28 +### Fixed +* `skip` config key should work again now. ([#1353](https://github.com/diffplug/spotless/pull/1353) fixes [#1227](https://github.com/diffplug/spotless/issues/1227) and [#491](https://github.com/diffplug/spotless/issues/491)) + +## [2.27.0] - 2022-09-19 +### Added +* Support for `editorConfigOverride` in `ktlint`, `plugin-maven`. ([#1335](https://github.com/diffplug/spotless/pull/1335) fixes [#1334](https://github.com/diffplug/spotless/issues/1334)) + +## [2.26.0] - 2022-09-14 +### Added * `formatAnnotations` step to correct formatting of Java type annotations. It puts type annotations on the same line as the type that they qualify. Run it after a Java formatting step, such as `googleJavaFormat`. ([#1275](https://github.com/diffplug/spotless/pull/1275)) ### Changes * Bump default `ktfmt` version to latest `0.39` -> `0.40` ([#1312](https://github.com/diffplug/spotless/pull/1312)) @@ -12,7 +260,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [2.25.0] - 2022-08-23 ### Added -* `scalafmt` integration now has a configuration option `majorScalaVersion` that allows you to configure the Scala version that gets resolved from the maven artifact ([#1283](https://github.com/diffplug/spotless/pull/1283)) +* `scalafmt` integration now has a configuration option `majorScalaVersion` that allows you to configure the Scala version that gets resolved from the Maven artifact ([#1283](https://github.com/diffplug/spotless/pull/1283)) ### Changes * Add the `ktlint` rule in error messages when `ktlint` fails to apply a fix ([#1279](https://github.com/diffplug/spotless/pull/1279)) * Bump default `scalafmt` to latest `3.0.8` -> `3.5.9` (removed support for pre-`3.0.0`) ([#1283](https://github.com/diffplug/spotless/pull/1283)) @@ -246,7 +494,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ### Changed * Update ktfmt from 0.21 to 0.24 ### Fixed -* The `` field in the maven POM is now set correctly ([#798](https://github.com/diffplug/spotless/issues/798)) +* The `` field in the Maven POM is now set correctly ([#798](https://github.com/diffplug/spotless/issues/798)) ## [2.10.3] - 2021-04-21 ### Fixed @@ -380,7 +628,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * `prettier` will now autodetect the parser (and formatter) to use based on the filename, unless you override this using `config` or `configFile` with the option `parser` or `filepath` ([#620](https://github.com/diffplug/spotless/pull/620)). * Added ANTLR4 support ([#326](https://github.com/diffplug/spotless/issues/326)). ### Removed -* **BREAKING** the default includes for `` and `` were removed, and will now generate an error if an `` is not specified. There is no well-established convention for these languages in the maven ecosystem, and the performance of the default includes is far worse than a user-provided one. If you dislike this change, please complain in [#634](https://github.com/diffplug/spotless/pull/634), it would not be a breaking change to bring the defaults back. +* **BREAKING** the default includes for `` and `` were removed, and will now generate an error if an `` is not specified. There is no well-established convention for these languages in the Maven ecosystem, and the performance of the default includes is far worse than a user-provided one. If you dislike this change, please complain in [#634](https://github.com/diffplug/spotless/pull/634), it would not be a breaking change to bring the defaults back. * **BREAKING** inside the `` block, `` has been renamed to `` to avoid any confusion with the java `` ([#636](https://github.com/diffplug/spotless/pull/636)). * **BREAKING** the long-deprecated `` and `` formats have been removed, in favor of the long-available [``](https://github.com/diffplug/spotless/tree/main/plugin-maven#eclipse-wtp) step which is available in every generic format ([#630](https://github.com/diffplug/spotless/pull/630)). * This probably doesn't affect you, but if it does, you just need to change `...` into `XML...` @@ -428,7 +676,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ### Added * Enable IntelliJ-compatible token `$today.year` for specifying the year in license header files. ([#542](https://github.com/diffplug/spotless/pull/542)) ### Fixed -* Fix scala and kotlin maven config documentation. +* Fix scala and kotlin Maven config documentation. * Eclipse-WTP formatter (web tools platform, not java) could encounter errors in parallel multiproject builds [#492](https://github.com/diffplug/spotless/issues/492). Fixed for Eclipse-WTP formatter Eclipse version 4.13.0 (default version). ## [1.27.0] - 2020-01-01 @@ -476,11 +724,11 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * Updated default eclipse-jdt from 4.11.0 to 4.12.0 ([#423](https://github.com/diffplug/spotless/pull/423)). * Updated default eclipse-cdt from 4.11.0 to 4.12.0 ([#423](https://github.com/diffplug/spotless/pull/423)). * **KNOWN BUG - accidentally published CDT 9.7 rather than 9.8 fixed in 1.26.0** -* Added new maven coordinates for scalafmt 2.0.0+, maintains backwards compatability ([#415](https://github.com/diffplug/spotless/issues/415)) +* Added new Maven coordinates for scalafmt 2.0.0+, maintains backwards compatability ([#415](https://github.com/diffplug/spotless/issues/415)) ## [1.23.1] - 2019-06-17 * Fixes incorrect M2 cache directory path handling of Eclipse based formatters ([#401](https://github.com/diffplug/spotless/issues/401)) -* Update jgit from `4.9.0.201710071750-r` to `5.3.2.201906051522-r` because gradle project is sometimes broken by `apache httpcomponents` in transitive dependency. ([#407](https://github.com/diffplug/spotless/pull/407)) +* Update jgit from `4.9.0.201710071750-r` to `5.3.2.201906051522-r` because Gradle project is sometimes broken by `apache httpcomponents` in transitive dependency. ([#407](https://github.com/diffplug/spotless/pull/407)) ## [1.23.0] - 2019-04-24 * Updated default ktlint from 0.21.0 to 0.32.0, and Maven coords to com.pinterest ([#394](https://github.com/diffplug/spotless/pull/394)) diff --git a/plugin-maven/README.md b/plugin-maven/README.md index d80773f3f1..97128768d9 100644 --- a/plugin-maven/README.md +++ b/plugin-maven/README.md @@ -2,29 +2,21 @@ -[![Maven central](https://img.shields.io/badge/mavencentral-com.diffplug.spotless%3Aspotless--maven--plugin-blue.svg)](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.diffplug.spotless%22%20AND%20a%3A%22spotless-maven-plugin%22) -[![Javadoc](https://img.shields.io/badge/javadoc-yes-blue.svg)](https://javadoc.io/doc/com.diffplug.spotless/spotless-maven-plugin/2.25.0/index.html) -[![Changelog](https://img.shields.io/badge/changelog-2.25.0-brightgreen.svg)](CHANGES.md) - -[![Circle CI](https://circleci.com/gh/diffplug/spotless/tree/main.svg?style=shield)](https://circleci.com/gh/diffplug/spotless/tree/main) -[![Live chat](https://img.shields.io/badge/gitter-chat-brightgreen.svg)](https://gitter.im/diffplug/spotless) -[![License Apache](https://img.shields.io/badge/license-apache-brightgreen.svg)](https://tldrlegal.com/license/apache-license-2.0-(apache-2.0)) +[![MavenCentral](https://img.shields.io/badge/mavencentral-com.diffplug.spotless%3Aspotless--maven--plugin-blue.svg)](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.diffplug.spotless%22%20AND%20a%3A%22spotless-maven-plugin%22) +[![Changelog](https://img.shields.io/badge/changelog-2.42.0-blue.svg)](CHANGES.md) +[![Javadoc](https://img.shields.io/badge/javadoc-here-blue.svg)](https://javadoc.io/doc/com.diffplug.spotless/spotless-maven-plugin/2.42.0/index.html) -Spotless is a general-purpose formatting plugin used by [4,000 projects on GitHub (August 2020)](https://github.com/search?l=gradle&q=spotless&type=Code). It is completely à la carte, but also includes powerful "batteries-included" if you opt-in. Plugin requires a version of Maven higher or equal to 3.1.0. +Spotless is a general-purpose formatting plugin used by [6,000 projects on GitHub (Jan 2023)](https://github.com/search?l=Maven+POM&q=spotless&type=Code). It is completely à la carte, but also includes powerful "batteries-included" if you opt-in. Plugin requires a version of Maven higher or equal to 3.1.0. To people who use your build, it looks like this: @@ -47,7 +39,7 @@ user@machine repo % mvn spotless:check - [Requirements](#requirements) - [Binding to maven phase](#binding-to-maven-phase) - **Languages** - - [Java](#java) ([google-java-format](#google-java-format), [eclipse jdt](#eclipse-jdt), [prettier](#prettier), [palantir-java-format](#palantir-java-format), [formatAnnotations](#formatAnnotations)) + - [Java](#java) ([google-java-format](#google-java-format), [eclipse jdt](#eclipse-jdt), [prettier](#prettier), [palantir-java-format](#palantir-java-format), [formatAnnotations](#formatAnnotations), [cleanthat](#cleanthat)) - [Groovy](#groovy) ([eclipse groovy](#eclipse-groovy)) - [Kotlin](#kotlin) ([ktfmt](#ktfmt), [ktlint](#ktlint), [diktat](#diktat), [prettier](#prettier)) - [Scala](#scala) ([scalafmt](#scalafmt)) @@ -57,10 +49,16 @@ user@machine repo % mvn spotless:check - [Sql](#sql) ([dbeaver](#dbeaver)) - [Maven Pom](#maven-pom) ([sortPom](#sortpom)) - [Markdown](#markdown) ([flexmark](#flexmark)) - - [Typescript](#typescript) ([tsfmt](#tsfmt), [prettier](#prettier)) + - [Typescript](#typescript) ([tsfmt](#tsfmt), [prettier](#prettier), [ESLint](#eslint-typescript), [Biome](#biome)) + - [Javascript](#javascript) ([prettier](#prettier), [ESLint](#eslint-javascript), [Biome](#biome)) + - [JSON](#json) ([simple](#simple), [gson](#gson), [jackson](#jackson), [Biome](#biome), [jsonPatch](#jsonPatch)) + - [YAML](#yaml) + - [Gherkin](#gherkin) + - [Go](#go) - Multiple languages - - [Prettier](#prettier) ([plugins](#prettier-plugins), [npm detection](#npm-detection), [`.npmrc` detection](#npmrc-detection)) + - [Prettier](#prettier) ([plugins](#prettier-plugins), [npm detection](#npm-detection), [`.npmrc` detection](#npmrc-detection), [caching `npm install` results](#caching-results-of-npm-install)) - [eclipse web tools platform](#eclipse-web-tools-platform) + - [Biome](#biome) ([binary detection](#biome-binary), [config file](#biome-configuration-file), [input language](#biome-input-language)) - **Language independent** - [Generic steps](#generic-steps) - [License header](#license-header) ([slurp year from git](#retroactively-slurp-years-from-git-history)) @@ -76,7 +74,7 @@ user@machine repo % mvn spotless:check ## Quickstart -To use it in your pom, just [add the Spotless dependency](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.diffplug.spotless%22%20AND%20a%3A%22spotless-maven-plugin%22), and configure it like so: +To use it in your pom, just [add the Spotless plugin](https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.diffplug.spotless%22%20AND%20a%3A%22spotless-maven-plugin%22), and configure it like so: ```xml @@ -91,7 +89,7 @@ To use it in your pom, just [add the Spotless dependency](https://search.maven.o - *.md + .gitattributes .gitignore @@ -112,6 +110,7 @@ To use it in your pom, just [add the Spotless dependency](https://search.maven.o 1.8 true + false + + + + + + false - java,javax,org,com,com.diffplug,,\\#com.diffplug,\\# - + java|javax,org,com,com.diffplug,,\#com.diffplug,\# + + false + + com.example.MyPackage + + + com.example.myClass + - - - - @@ -200,15 +218,24 @@ any other maven phase (i.e. compile) then it can be configured as below; ``` +### removeUnusedImports + +```xml + + google-java-format + +``` + ### google-java-format [homepage](https://github.com/google/google-java-format). [changelog](https://github.com/google/google-java-format/releases). [code](https://github.com/diffplug/spotless/blob/main/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/GoogleJavaFormat.java). ```xml - 1.8 + 1.8 - true + true + false com.google.googlejavaformat:google-java-format @@ -220,7 +247,9 @@ any other maven phase (i.e. compile) then it can be configured as below; ```xml - 2.10.0 + 2.39.0 + + false ``` @@ -230,7 +259,7 @@ any other maven phase (i.e. compile) then it can be configured as below; ```xml - 4.13.0 + 4.26 ${project.basedir}/eclipse-formatter.xml ``` @@ -270,6 +299,26 @@ list of well-known type annotations. You can make a pull request to add new one In the future there will be mechanisms to add/remove annotations from the list. These mechanisms already exist for the Gradle plugin. +### Cleanthat + +[homepage](https://github.com/solven-eu/cleanthat). CleanThat enables automatic refactoring of Java code. [ChangeLog](https://github.com/solven-eu/cleanthat/blob/master/CHANGES.MD) + +```xml + + 2.8 + ${maven.compiler.source} + + SafeAndConsensual + + + LiteralsFirstInComparisons + + + OptionalNotEmpty + + false + +``` ## Groovy @@ -284,14 +333,16 @@ These mechanisms already exist for the Gradle plugin. src/test/groovy/**/*.groovy - + - java,javax,org,com,com.diffplug,,\\#com.diffplug,\\# - + java|javax,org,com,com.diffplug,,\#com.diffplug,\# + + + + - - + /* (C)$YEAR */ @@ -305,7 +356,7 @@ These mechanisms already exist for the Gradle plugin. ```xml - 4.13.0 + 4.26 ${project.basedir}/greclipse.properties ``` @@ -354,11 +405,24 @@ Groovy-Eclipse formatting errors/warnings lead per default to a build failure. T ### ktlint -[homepage](https://github.com/pinterest/ktlint). [changelog](https://github.com/pinterest/ktlint/releases). [code](https://github.com/diffplug/spotless/blob/main/plugin-maven/src/main/java/com/diffplug/spotless/maven/kotlin/Ktlint.java). Spotless does not ([yet](https://github.com/diffplug/spotless/issues/142)) respect the `.editorconfig` settings. +[homepage](https://github.com/pinterest/ktlint). [changelog](https://github.com/pinterest/ktlint/releases). +[code](https://github.com/diffplug/spotless/blob/main/plugin-maven/src/main/java/com/diffplug/spotless/maven/kotlin/Ktlint.java). + +Spotless respects the `.editorconfig` settings by providing `editorConfigPath` option. +([ktlint docs](https://github.com/pinterest/ktlint#editorconfig)). + +Additionally, `editorConfigOverride` options will override what's supplied in `.editorconfig` file. ```xml - 0.43.2 + 1.0.0 + + true + true + + + io.nlopez.compose.rules:ktlint:0.3.3 + ``` @@ -411,7 +475,7 @@ Groovy-Eclipse formatting errors/warnings lead per default to a build failure. T 3.5.9 ${project.basedir}/scalafmt.conf - 2.13 + 2.13 ``` @@ -427,7 +491,7 @@ Groovy-Eclipse formatting errors/warnings lead per default to a build failure. T - src/native/** + src/native/** @@ -445,7 +509,7 @@ Groovy-Eclipse formatting errors/warnings lead per default to a build failure. T ```xml - 4.13.0 + 11.0 ${project.basedir}/eclipse-cdt.xml ``` @@ -656,6 +720,8 @@ Currently, none of the available options can be configured yet. It uses only the + + /* (C)$YEAR */ @@ -694,10 +760,322 @@ The auto-discovery of config files (up the file tree) will not work when using t **Prerequisite: tsfmt requires a working NodeJS version** -For details, see the [npm detection](#npm-detection) and [`.npmrc` detection](#npmrc-detection) sections of prettier, which apply also to tsfmt. +For details, see the [npm detection](#npm-detection), [`.npmrc` detection](#npmrc-detection) and [caching results of `npm install`](#caching-results-of-npm-install) sections of prettier, which apply also to tsfmt. + +### ESLint (typescript) + +[npm](https://www.npmjs.com/package/eslint). [changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md). *Please note:* +The auto-discovery of config files (up the file tree) will not work when using ESLint within spotless, +hence you are required to provide resolvable file paths for config files, or alternatively provide the configuration inline. + +The configuration is very similar to the [ESLint (Javascript)](#eslint-javascript) configuration. In typescript, a +reference to a `tsconfig.json` is required. + +```xml + + + 8.30.0 + + 8.30.0 + 1.2.1 + + + + eslint + 8.30.0 + + + @eslint/my-plugin-typescript + 0.14.2 + + + + ${project.basedir}/.eslintrc.js + + { + env: { + browser: true, + es2021: true + }, + extends: 'standard-with-typescript', + overrides: [ + ], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: './tsconfig.json', + }, + rules: { + } + } + + + ${project.basedir}/tsconfig.json + +``` + +**Prerequisite: ESLint requires a working NodeJS version** + +For details, see the [npm detection](#npm-detection), [`.npmrc` detection](#npmrc-detection) and [caching results of `npm install`](#caching-results-of-npm-install) sections of prettier, which apply also to ESLint. + + +## Javascript + +[code](https://github.com/diffplug/spotless/blob/main/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/Javascript.java). [available steps](https://github.com/diffplug/spotless/tree/main/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript). + +```xml + + + + src/**/*.js + + + + + + + + /* (C)$YEAR */ + REGEX_TO_DEFINE_TOP_OF_FILE + + + +``` + +### ESLint (Javascript) + +[npm](https://www.npmjs.com/package/eslint). [changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md). *Please note:* +The auto-discovery of config files (up the file tree) will not work when using ESLint within spotless, +hence you are required to provide resolvable file paths for config files, or alternatively provide the configuration inline. + +The configuration is very similar to the [ESLint (Typescript)](#eslint-typescript) configuration. In javascript, *no* +`tsconfig.json` is supported. + +```xml + + + 8.30.0 + + 8.30.0 + 1.2.1 + + + + eslint + 8.30.0 + + + @eslint/my-plugin-javascript + 0.14.2 + + + + ${project.basedir}/.eslintrc.js + + { + env: { + browser: true, + es2021: true + }, + extends: 'standard', + overrides: [ + ], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module' + }, + rules: { + } + } + + +``` + +**Prerequisite: ESLint requires a working NodeJS version** + +For details, see the [npm detection](#npm-detection), [`.npmrc` detection](#npmrc-detection) and [caching results of `npm install`](#caching-results-of-npm-install) sections of prettier, which apply also to ESLint. + +## JSON + +- `com.diffplug.spotless.maven.json.Json` [code](https://github.com/diffplug/spotless/blob/main/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Json.java) + +```xml + + + + src/**/*.json + + + + + + + + + +``` + +### simple + +Uses a JSON pretty-printer that optionally allows configuring the number of spaces that are used to pretty print objects: + +```xml + + 4 + +``` + +### Gson + +Uses Google Gson to also allow sorting by keys besides custom indentation - useful for i18n files. + +```xml + + 4 + false + false + 2.8.1 + +``` + +Notes: +* There's no option in Gson to leave HTML as-is (i.e. escaped HTML would remain escaped, raw would remain raw). Either +all HTML characters are written escaped or none. Set `escapeHtml` if you prefer the former. +* `sortByKeys` will apply lexicographic order on the keys of the input JSON. See the +[javadoc of String](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html#compareTo(java.lang.String)) +for details. + +### Jackson + +Uses Jackson for formatting. + +```xml + + 2.14.1 + + true + false + true|false + + + false + true|false + + false + +``` +### jsonPatch + +Uses [zjsonpatch](https://github.com/flipkart-incubator/zjsonpatch) to apply [JSON Patches](https://jsonpatch.com/) as per [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902/) to JSON documents. + +This enables you to add, replace or remove properties at locations in the JSON document that you specify using [JSON Pointers](https://datatracker.ietf.org/doc/html/rfc6901/). + +For example, to apply the patch from the [JSON Patch homepage](https://jsonpatch.com/#the-patch): + +```xml +[ + { "op": "replace", "path": "/baz", "value": "boo" }, + { "op": "add", "path": "/hello", "value": ["world"] }, + { "op": "remove", "path": "/foo" } +] +``` + +## YAML + +- `com.diffplug.spotless.maven.FormatterFactory.addStepFactory(FormatterStepFactory)` [code](https://github.com/diffplug/spotless/blob/main/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/Yaml.java) + +```xml + + + + src/**/*.yaml + + + + + + +``` + +### jackson + +Uses Jackson and YAMLFactory to pretty print objects: + +```xml + + 2.14.1 + + true + false + true|false + + + true + false + true|false + + false + +``` + +## Gherkin + +- `com.diffplug.spotless.maven.FormatterFactory.addStepFactory(FormatterStepFactory)` [code](https://github.com/diffplug/spotless/blob/main/plugin-maven/src/main/java/com/diffplug/spotless/maven/gherkin/Gherkin.java) + +```gradle + + + + src/**/*.feature + + + + + +``` + +### gherkinUtils + +[homepage](https://github.com/cucumber/gherkin-utils). [changelog](https://github.com/cucumber/gherkin-utils/blob/main/CHANGELOG.md). + +Uses a Gherkin pretty-printer that optionally allows configuring the number of spaces that are used to pretty print objects: + +```xml + + 8.0.2 + +``` + +## Go + +- `com.diffplug.spotless.maven.FormatterFactory.addStepFactory(FormatterStepFactory)` [code](https://github.com/diffplug/spotless/blob/main/plugin-maven/src/main/java/com/diffplug/spotless/maven/go/Go.java) + +```xml + + + + src/**/*.go + + + + + +``` + +### gofmt + +Standard Go formatter, part of Go distribution. + +```xml + + go1.25.1 + /opt/sdks/go1.25.1/bin/go + +``` + ## Prettier [homepage](https://prettier.io/). [changelog](https://github.com/prettier/prettier/blob/master/CHANGELOG.md). [official plugins](https://prettier.io/docs/en/plugins.html#official-plugins). [community plugins](https://prettier.io/docs/en/plugins.html#community-plugins). Prettier is a formatter that can format almost every anything - JavaScript, JSX, Angular, Vue, Flow, TypeScript, CSS, Less, SCSS, HTML, JSON, GraphQL, Markdown (including GFM and MDX), and YAML. It can format even more [using plugins](https://prettier.io/docs/en/plugins.html) (PHP, Ruby, Swift, XML, Apex, Elm, Java (!!), Kotlin, pgSQL, .properties, solidity, svelte, toml, shellscript, ...). @@ -721,17 +1099,19 @@ You can use prettier in any language-specific format, but usually you will be cr prettier - 2.0.5 + 2.8.8 @prettier/plugin-php - 0.14.2 + 0.19.6 ${project.basedir}/path/to/configfile true + + @prettier/plugin-php @@ -771,12 +1151,13 @@ Since spotless uses the actual npm prettier package behind the scenes, it is pos - 2.0.5 - 0.8.0 + 2.8.8 + 2.2.0 4 java + prettier-plugin-java @@ -792,16 +1173,17 @@ Since spotless uses the actual npm prettier package behind the scenes, it is pos prettier - 2.0.5 + 2.8.8 @prettier/plugin-php - 0.14.2 + 0.19.6 3 php + @prettier/plugin-php @@ -813,28 +1195,49 @@ Since spotless uses the actual npm prettier package behind the scenes, it is pos ### npm detection Prettier is based on NodeJS, so to use it, a working NodeJS installation (especially npm) is required on the host running spotless. -Spotless will try to auto-discover an npm installation. If that is not working for you, it is possible to directly configure the npm binary to use. +Spotless will try to auto-discover an npm installation. If that is not working for you, it is possible to directly configure the npm +and/or node binary to use. ```xml /usr/bin/npm + /usr/bin/node ``` +If you provide both `npmExecutable` and `nodeExecutable`, spotless will use these paths. If you specify only one of the +two, spotless will assume the other one is in the same directory. + ### `.npmrc` detection Spotless picks up npm configuration stored in a `.npmrc` file either in the project directory or in your user home. -Alternatively you can supply spotless with a location of the `.npmrc` file to use. (This can be combined with `npmExecutable`, of course.) +Alternatively you can supply spotless with a location of the `.npmrc` file to use. (This can be combined with +`npmExecutable` and `nodeExecutable`, of course.) ```xml /usr/local/shared/.npmrc ``` +### Caching results of `npm install` + +Spotless uses `npm` behind the scenes to install `prettier`. This can be a slow process, especially if you are using a slow internet connection or +if you need large plugins. You can instruct spotless to cache the results of the `npm install` calls, so that for the next installation, +it will not need to download the packages again, but instead reuse the cached version. + +```xml + + true + /usr/local/shared/.spotless-npm-install-cache +``` + +Depending on your filesystem and the location of the cache directory, spotless will use hardlinks when caching the npm packages. If that is not +possible, it will fall back to copying the files. + ## Eclipse web tools platform -[changelog](https://www.eclipse.org/webtools/). [compatible versions](https://github.com/diffplug/spotless/tree/main/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_wtp_formatters). +[changelog](https://www.eclipse.org/webtools/). [compatible versions](https://github.com/diffplug/spotless/tree/main/lib-extra/src/main/resources/com/diffplug/spotless/extra/eclipse_wtp_formatter). ```xml @@ -851,7 +1254,7 @@ Alternatively you can supply spotless with a location of the `.npmrc` file to us ${project.basedir}/xml.prefs ${project.basedir}/additional.properties - 4.13.0 + 4.21.0 @@ -885,6 +1288,171 @@ to true. +## Biome + +[homepage](https://biomejs.dev/). [changelog](https://github.com/biomejs/biome/blob/main/CHANGELOG.md). Biome is +a formatter that for the frontend written in Rust, which has a native binary, does not require Node.js and as such, +is pretty fast. It can currently format JavaScript, TypeScript, JSX, and JSON, and may support +[more frontend languages](https://biomejs.dev/internals/language-support/) such as CSS in the future. + +Note: Biome [was formerly called Rome](https://biomejs.dev/blog/annoucing-biome/). Configurations with +the old `` tag and `rome(...)` function are still supported for the time being. This will be removed +in a future version, you should migrate to the new `` tag or `biome(...)` function. The configuration +remains the same, you only need to update the version. If you are using a custom `rome.json` configuration file, +you need to rename it to `biome.json`. + +You can use Biome in any language-specific format for supported languages, but +usually you will be creating a generic format. + +```xml + + + + + src/**/typescript/**/*.ts + + + + + 1.2.0 + + + ${project.basedir}/path/to/config/dir + + + + ts + + + + + +``` + +To apply Biome to more kinds of files with a different configuration, just add +more formats: + +```xml + + + src/**/*.ts + src/**/*.js + +``` + +**Limitations:** +- The auto-discovery of config files (up the file tree) will not work when using + Biome within spotless. +- The `ignore` option of the `biome.json` configuration file will not be applied. + Include and exclude patterns are configured in the spotless configuration in the + Maven pom instead. + +Note: Due to a limitation of biome, if the name of a file matches a pattern in +the `ignore` option of the specified `biome.json` configuration file, it will not be +formatted, even if included in the biome configuration section of the Maven pom. +You could specify a different `biome.json` configuration file without an `ignore` +pattern to circumvent this. + +Note 2: Biome is hard-coded to ignore certain special files, such as `package.json` +or `tsconfig.json`. These files will never be formatted. + +### Biome binary + +To format with Biome, spotless needs to find the Biome binary. By default, +spotless downloads the binary for the given version from the network. This +should be fine in most cases, but may not work e.g. when there is not connection +to the internet. + +To download the Biome binary from the network, just specify a version: + +```xml + + 1.2.0 + +``` + +Spotless uses a default version when you do not specify a version, but this +may change at any time, so we recommend that you always set the Biome version +you want to use. Optionally, you can also specify a directory for the downloaded +Biome binaries (defaults to `~/.m2/repository/com/diffplug/spotless/spotless-data/biome`): + +```xml + + 1.2.0 + + ${user.home}/biome + +``` + +To use a fixed binary, omit the `version` and specify a `pathToExe`: + +```xml + + ${project.basedir}/bin/biome + +``` + +Absolute paths are used as-is. Relative paths are resolved against the project's +base directory. To use a pre-installed Biome binary on the user's path, specify +just a name without any slashes / backslashes: + + +```xml + + + biome + +``` + +### Biome configuration file + +Biome is a biased formatter and linter without many options, but there are a few +basic options. Biome uses a file named [biome.json](https://biomejs.dev/reference/configuration/) +for its configuration. When none is specified, the default configuration from +Biome is used. To use a custom configuration: + +```xml + + + + ${project.basedir} + +``` + +### Biome input language + +By default, Biome detects the language / syntax of the files to format +automatically from the file extension. This may fail if your source code files +have unusual extensions for some reason. If you are using the generic format, +you can force a certain language like this: + +```xml + + + + + src/**/typescript/**/*.mjson + + + + 1.2.0 + json + + + + +``` + +The following languages are currently recognized: + +* `js` -- JavaScript +* `jsx` -- JavaScript + JSX (React) +* `js?` -- JavaScript, with or without JSX, depending on the file extension +* `ts` -- TypeScript +* `tsx` -- TypeScript + JSX (React) +* `ts?` -- TypeScript, with or without JSX, depending on the file extension +* `json` -- JSON + ## Generic steps [Prettier](#prettier), [eclipse wtp](#eclipse-web-tools-platform), and [license header](#license-header) are available in every format, and they each have their own section. As mentioned in the [quickstart](#quickstart), there are a variety of simple generic steps which are also available in every format, here are examples of these: @@ -952,13 +1520,19 @@ Once a file's license header has a valid year, whether it is a year (`2020`) or If your project has not been rigorous with copyright headers, and you'd like to use git history to repair this retroactively, you can do so with `-DspotlessSetLicenseHeaderYearsFromGitHistory=true`. When run in this mode, Spotless will do an expensive search through git history for each file, and set the copyright header based on the oldest and youngest commits for that file. This is intended to be a one-off sort of thing. +### Files with fixed header lines + +Some files have fixed header lines (e.g. `^#!.+?$` to skip shebangs). + ## Incremental up-to-date checking and formatting -**This feature is turned off by default.** +**This feature is enabled by default starting from version 2.35.0.** Execution of `spotless:check` and `spotless:apply` for large projects can take time. By default, Spotless Maven plugin needs to read and format each source file. @@ -1024,6 +1598,13 @@ You can explicitly disable ratchet functionality by providing the value 'NONE': ``` This is useful for disabling the ratchet functionality in child projects where the parent defines a ratchetFrom value. +### Using `ratchetFrom` on CI systems + +Many popular CI systems (GitHub, GitLab, BitBucket, and Travis) use a "shallow clone". This means that `origin/main` will fail with `No such reference`. You can fix this by: + +- calling `git fetch origin main` before you call Spotless +- disabling the shallow clone [like so](https://github.com/diffplug/spotless/issues/710) + ## `spotless:off` and `spotless:on` Sometimes there is a chunk of code which you have carefully handcrafted, and you would like to exclude just this one little part from getting clobbered by the autoformat. Some formatters have a way to do this, many don't, but who cares. If you setup your spotless like this: @@ -1051,7 +1632,7 @@ Spotless uses UTF-8 by default, but you can use [any encoding which Java support ``` -Line endings can also be set globally or per-format using the `lineEndings` property. Spotless supports four line ending modes: `UNIX`, `WINDOWS`, `MAC_CLASSIC`, `PLATFORM_NATIVE`, and `GIT_ATTRIBUTES`. The default value is `GIT_ATTRIBUTES`, and *we highly recommend that you* ***do not change*** *this value*. Git has opinions about line endings, and if Spotless and git disagree, then you're going to have a bad time. +Line endings can also be set globally or per-format using the `lineEndings` property. Spotless supports four line ending modes: `UNIX`, `WINDOWS`, `MAC_CLASSIC`, `PLATFORM_NATIVE`, `GIT_ATTRIBUTES`, and `GIT_ATTRIBUTES_FAST_ALLSAME`. The default value is `GIT_ATTRIBUTES_FAST_ALLSAME`, and *we highly recommend that you* ***do not change*** *this value*. Git has opinions about line endings, and if Spotless and git disagree, then you're going to have a bad time. `FAST_ALLSAME` just means that Spotless can assume that every file being formatted has the same line endings ([more info](https://github.com/diffplug/spotless/pull/1838)). You can easily set the line endings of different files using [a `.gitattributes` file](https://help.github.com/articles/dealing-with-line-endings/). Here's an example `.gitattributes` which sets all files to unix newlines: `* text eol=lf`. diff --git a/plugin-maven/build.gradle b/plugin-maven/build.gradle index 174f10ef6a..f2f85f0383 100644 --- a/plugin-maven/build.gradle +++ b/plugin-maven/build.gradle @@ -1,70 +1,37 @@ -buildscript { - repositories { mavenCentral() } - dependencies { classpath "com.github.spullara.mustache.java:compiler:${VER_MUSTACHE}" } -} -plugins { - id 'cz.malohlava.visteg' version '1.0.5' // https://github.com/mmalohlava/gradle-visteg -} -apply from: rootProject.file('gradle/changelog.gradle') -apply from: rootProject.file('gradle/spotless-freshmark.gradle') +import de.benediktritter.maven.plugin.development.task.GenerateHelpMojoSourcesTask +import de.benediktritter.maven.plugin.development.task.GenerateMavenPluginDescriptorTask -// to generate taskGraph.pdf -// - set enabled (below) to true -// - run: ./gradlew :plugin-maven:test -// - run: rm plugin-maven/output.pdf -// - run: dot -Tpdf plugin-maven/build/reports/visteg.dot > plugin-maven/taskGraph.pdf -visteg { - enabled = false - nodeShape = 'box' - startNodeShape = 'box' - endNodeShape = 'box' - colorscheme = 'pastel24' // https://www.graphviz.org/doc/info/colors.html +plugins { + // https://www.benediktritter.de/maven-plugin-development/#release-history + id 'de.benediktritter.maven-plugin-development' version '0.4.2' } -import com.github.mustachejava.DefaultMustacheFactory - -import java.nio.file.Files - -import static java.nio.charset.StandardCharsets.UTF_8 -import static java.nio.file.StandardOpenOption.CREATE -import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING - +apply from: rootProject.file('gradle/changelog.gradle') ext.artifactId = project.artifactIdMaven version = spotlessChangelog.versionNext + apply from: rootProject.file("gradle/java-setup.gradle") -apply from: rootProject.file("gradle/java-publish.gradle") +apply from: rootProject.file('gradle/spotless-freshmark.gradle') -final MAVEN_PROJECT_DIR = project.layout.buildDirectory.dir("mavenProject").get() -final LOCAL_MAVEN_REPO_DIR = project.layout.buildDirectory.dir("localMavenRepository").get() +mavenPlugin { + name = 'Spotless Maven Plugin' + artifactId = project.artifactIdMaven + description = project.description +} -def mvnw(String args) { - boolean isWin = System.getProperty('os.name').toLowerCase().contains('win') - if (isWin) { - return [ - 'cmd', - '/c', - 'mvnw.cmd -e ' + args - ] - } else { - return [ - '/bin/sh', - '-c', - './mvnw -e ' + args - ] - } +tasks.withType(GenerateMavenPluginDescriptorTask).configureEach { + notCompatibleWithConfigurationCache('https://github.com/britter/maven-plugin-development/issues/8') +} +tasks.withType(GenerateHelpMojoSourcesTask).configureEach { + notCompatibleWithConfigurationCache('https://github.com/britter/maven-plugin-development/issues/8') } -String libVersion = version.endsWith('-SNAPSHOT') ? - rootProject.spotlessChangelog.versionNext : - rootProject.spotlessChangelog.versionLast +String VER_MAVEN_API = '3.0' +String VER_ECLIPSE_AETHER = '1.1.0' +String VER_PLEXUS_RESOURCES = '1.2.0' dependencies { - if (version.endsWith('-SNAPSHOT') || (rootProject.spotlessChangelog.versionNext == rootProject.spotlessChangelog.versionLast)) { - implementation project(':lib') - implementation project(':lib-extra') - } else { - implementation "com.diffplug.spotless:spotless-lib:${libVersion}" - implementation "com.diffplug.spotless:spotless-lib-extra:${libVersion}" - } + implementation projects.lib + implementation projects.libExtra compileOnly "org.apache.maven:maven-plugin-api:${VER_MAVEN_API}" compileOnly "org.apache.maven.plugin-tools:maven-plugin-annotations:${VER_MAVEN_API}" @@ -72,128 +39,32 @@ dependencies { compileOnly "org.eclipse.aether:aether-api:${VER_ECLIPSE_AETHER}" implementation "com.diffplug.durian:durian-core:${VER_DURIAN}" + implementation "com.diffplug.durian:durian-io:${VER_DURIAN}" implementation "com.diffplug.durian:durian-collect:${VER_DURIAN}" implementation("org.codehaus.plexus:plexus-resources:${VER_PLEXUS_RESOURCES}") implementation "org.eclipse.jgit:org.eclipse.jgit:${VER_JGIT}" + implementation 'org.sonatype.plexus:plexus-build-api:0.0.7' testImplementation project(":testlib") testImplementation "org.junit.jupiter:junit-jupiter:${VER_JUNIT}" testImplementation "org.assertj:assertj-core:${VER_ASSERTJ}" testImplementation "org.mockito:mockito-core:${VER_MOCKITO}" testImplementation "com.diffplug.durian:durian-io:${VER_DURIAN}" - testImplementation "com.github.spullara.mustache.java:compiler:${VER_MUSTACHE}" + testImplementation 'com.github.spullara.mustache.java:compiler:0.9.10' + testImplementation 'org.owasp.encoder:encoder:1.2.3' testImplementation "org.apache.maven:maven-plugin-api:${VER_MAVEN_API}" testImplementation "org.eclipse.aether:aether-api:${VER_ECLIPSE_AETHER}" testImplementation "org.codehaus.plexus:plexus-resources:${VER_PLEXUS_RESOURCES}" testImplementation "org.apache.maven:maven-core:${VER_MAVEN_API}" + testRuntimeOnly "org.junit.platform:junit-platform-launcher" } -task copySourceFiles(type: Sync) { - from "src/main/java" - into MAVEN_PROJECT_DIR.dir("src/main/java") -} - -task copyMvnw(type: Copy, dependsOn: copySourceFiles) { - from 'src/test/resources' - include 'mvnw' - include 'mvnw.cmd' - include '.mvn/**' - into MAVEN_PROJECT_DIR -} - -task installLocalDependencies -def libs = [ - 'lib', - 'lib-extra', - 'testlib' -] -libs.each { - def groupId = 'com.diffplug.spotless' - def artifactId = "spotless-${it}" - def jarTask = tasks.getByPath(":${it}:jar") - def file = jarTask.archivePath - - def installDependency = task "install_${artifactId}"(type: Exec) { - workingDir MAVEN_PROJECT_DIR - - inputs.file(file) - outputs.dir(LOCAL_MAVEN_REPO_DIR.file(groupId.replace('.', '/') + "/" + artifactId + "/" + version)) - commandLine mvnw("org.apache.maven.plugins:maven-install-plugin:2.3.1:install-file " + - "-Dfile=${file} " + - "-DgroupId=${groupId} " + - "-DartifactId=${artifactId} " + - "-Dversion=${libVersion} " + - "-Dpackaging=jar " + - "-DlocalRepositoryPath=${LOCAL_MAVEN_REPO_DIR}") - } - installDependency.dependsOn(jarTask) - - installLocalDependencies.dependsOn installDependency -} - -task createPomXml(dependsOn: installLocalDependencies) { - def newPomXml = MAVEN_PROJECT_DIR.file("pom.xml").asFile.toPath() - - outputs.file(newPomXml) - doLast { - def additionalDependencies = project.configurations.runtimeClasspath.resolvedConfiguration.resolvedArtifacts.findAll { - return !libs.contains(it.moduleVersion.id.name) - }.collect { - return " \n" + - " ${it.moduleVersion.id.group}\n" + - " ${it.moduleVersion.id.name}\n" + - " ${it.moduleVersion.id.version}\n" + - " \n" - }.join() - - def versions = [ - spotlessMavenPluginVersion: version, - mavenApiVersion : VER_MAVEN_API, - eclipseAetherVersion : VER_ECLIPSE_AETHER, - spotlessLibVersion : libVersion, - jsr305Version : VER_JSR_305, - additionalDependencies : additionalDependencies - ] - - def pomXmlTemplate = project.layout.projectDirectory.file("src/test/resources/pom-build.xml.mustache").asFile.toPath() - - Files.newBufferedReader(pomXmlTemplate).withCloseable { reader -> - Files.newBufferedWriter(newPomXml, UTF_8, CREATE, TRUNCATE_EXISTING).withCloseable { writer -> - def mustache = new DefaultMustacheFactory().compile(reader, "pom") - mustache.execute(writer, versions) - } - } - } -} - -task runMavenBuild(type: Exec, dependsOn: [ - copySourceFiles, - copyMvnw, - createPomXml -]) { - outputs.dir(LOCAL_MAVEN_REPO_DIR) - - workingDir MAVEN_PROJECT_DIR - // -B batch mode to make dependency download logging less verbose - commandLine mvnw("clean install -B -Dmaven.repo.local=${LOCAL_MAVEN_REPO_DIR}") -} - -jar.setActions Arrays.asList() -jar.dependsOn(runMavenBuild) -File jarIn = MAVEN_PROJECT_DIR.file("target/spotless-maven-plugin-${version}.jar").asFile -File jarOut = jar.archivePath -jar.inputs.file(jarIn) -jar.outputs.file(jarOut) -jar.doLast { - Files.copy(jarIn.toPath(), jarOut.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING) -} - -test { useJUnitPlatform() } - apply from: rootProject.file('gradle/special-tests.gradle') - -tasks.withType(Test) { - systemProperty "localMavenRepositoryDir", LOCAL_MAVEN_REPO_DIR.asFile - systemProperty "spotlessMavenPluginVersion", project.version - dependsOn(jar) +tasks.withType(Test).configureEach { + systemProperty 'spotlessMavenPluginVersion', project.version + dependsOn 'publishToMavenLocal' + dependsOn ':lib:publishToMavenLocal' + dependsOn ':lib-extra:publishToMavenLocal' } + +apply from: rootProject.file("gradle/java-publish.gradle") diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java index c68733f387..0528cccbdb 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/AbstractSpotlessMojo.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,8 +23,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -50,8 +50,10 @@ import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.repository.RemoteRepository; +import org.sonatype.plexus.build.incremental.BuildContext; import com.diffplug.spotless.Formatter; +import com.diffplug.spotless.Jvm; import com.diffplug.spotless.LineEnding; import com.diffplug.spotless.Provisioner; import com.diffplug.spotless.generic.LicenseHeaderStep; @@ -59,22 +61,28 @@ import com.diffplug.spotless.maven.cpp.Cpp; import com.diffplug.spotless.maven.generic.Format; import com.diffplug.spotless.maven.generic.LicenseHeader; +import com.diffplug.spotless.maven.gherkin.Gherkin; +import com.diffplug.spotless.maven.go.Go; import com.diffplug.spotless.maven.groovy.Groovy; import com.diffplug.spotless.maven.incremental.UpToDateChecker; import com.diffplug.spotless.maven.incremental.UpToDateChecking; import com.diffplug.spotless.maven.java.Java; +import com.diffplug.spotless.maven.javascript.Javascript; +import com.diffplug.spotless.maven.json.Json; import com.diffplug.spotless.maven.kotlin.Kotlin; import com.diffplug.spotless.maven.markdown.Markdown; import com.diffplug.spotless.maven.pom.Pom; import com.diffplug.spotless.maven.python.Python; import com.diffplug.spotless.maven.scala.Scala; +import com.diffplug.spotless.maven.shell.Shell; import com.diffplug.spotless.maven.sql.Sql; import com.diffplug.spotless.maven.typescript.Typescript; +import com.diffplug.spotless.maven.yaml.Yaml; public abstract class AbstractSpotlessMojo extends AbstractMojo { private static final String DEFAULT_INDEX_FILE_NAME = "spotless-index"; private static final String DEFAULT_ENCODING = "UTF-8"; - private static final String DEFAULT_LINE_ENDINGS = "GIT_ATTRIBUTES"; + private static final String DEFAULT_LINE_ENDINGS = "GIT_ATTRIBUTES_FAST_ALLSAME"; /** Value to allow unsetting the ratchet inherited from parent pom configuration. */ static final String RATCHETFROM_NONE = "NONE"; @@ -88,9 +96,15 @@ public abstract class AbstractSpotlessMojo extends AbstractMojo { @Component private ResourceManager resourceManager; + @Component + protected BuildContext buildContext; + @Parameter(defaultValue = "${mojoExecution.goal}", required = true, readonly = true) private String goal; + @Parameter(property = "spotless.skip", defaultValue = "false") + private boolean skip; + @Parameter(property = "spotless.apply.skip", defaultValue = "false") private boolean applySkip; @@ -145,6 +159,9 @@ public abstract class AbstractSpotlessMojo extends AbstractMojo { @Parameter private Typescript typescript; + @Parameter + private Javascript javascript; + @Parameter private Antlr4 antlr4; @@ -160,6 +177,21 @@ public abstract class AbstractSpotlessMojo extends AbstractMojo { @Parameter private Markdown markdown; + @Parameter + private Json json; + + @Parameter + private Shell shell; + + @Parameter + private Yaml yaml; + + @Parameter + private Gherkin gherkin; + + @Parameter + private Go go; + @Parameter(property = "spotlessFiles") private String filePatterns; @@ -167,10 +199,20 @@ public abstract class AbstractSpotlessMojo extends AbstractMojo { private String setLicenseHeaderYearsFromGitHistory; @Parameter - private UpToDateChecking upToDateChecking; + private UpToDateChecking upToDateChecking = UpToDateChecking.enabled(); protected abstract void process(Iterable files, Formatter formatter, UpToDateChecker upToDateChecker) throws MojoExecutionException; + private static final int MINIMUM_JRE = 11; + + protected AbstractSpotlessMojo() { + if (Jvm.version() < MINIMUM_JRE) { + throw new RuntimeException("Spotless requires JRE " + MINIMUM_JRE + " or newer, this was " + Jvm.version() + ".\n" + + "You can upgrade your build JRE and still compile for older targets, see below\n" + + "https://docs.gradle.org/current/userguide/building_java_projects.html#sec:java_cross_compilation"); + } + } + @Override public final void execute() throws MojoExecutionException { if (shouldSkip()) { @@ -181,7 +223,7 @@ public final void execute() throws MojoExecutionException { List formatterFactories = getFormatterFactories(); FormatterConfig config = getFormatterConfig(); - Map>> formatterFactoryToFiles = new HashMap<>(); + Map>> formatterFactoryToFiles = new LinkedHashMap<>(); for (FormatterFactory formatterFactory : formatterFactories) { Supplier> filesToFormat = () -> collectFiles(formatterFactory, config); formatterFactoryToFiles.put(formatterFactory, filesToFormat); @@ -200,6 +242,10 @@ public final void execute() throws MojoExecutionException { } private boolean shouldSkip() { + if (skip) { + return true; + } + switch (goal) { case GOAL_CHECK: return checkSkip; @@ -208,6 +254,7 @@ private boolean shouldSkip() { default: break; } + return false; } @@ -285,7 +332,7 @@ private static String withTrailingSeparator(String path) { private Set getIncludes(FormatterFactory formatterFactory) { Set configuredIncludes = formatterFactory.includes(); - Set includes = configuredIncludes.isEmpty() ? formatterFactory.defaultIncludes() : configuredIncludes; + Set includes = configuredIncludes.isEmpty() ? formatterFactory.defaultIncludes(project) : configuredIncludes; if (includes.isEmpty()) { throw new PluginException("You must specify some files to include, such as 'src/**/*.blah'"); } @@ -319,7 +366,7 @@ private FileLocator getFileLocator() { } private List getFormatterFactories() { - return Stream.concat(formats.stream(), Stream.of(groovy, java, scala, kotlin, cpp, typescript, antlr4, pom, sql, python, markdown)) + return Stream.concat(formats.stream(), Stream.of(groovy, java, scala, kotlin, cpp, typescript, javascript, antlr4, pom, sql, python, markdown, json, shell, yaml, gherkin, go)) .filter(Objects::nonNull) .collect(toList()); } @@ -336,10 +383,13 @@ private UpToDateChecker createUpToDateChecker(Iterable formatters) { Path targetDir = project.getBasedir().toPath().resolve(project.getBuild().getDirectory()); indexFile = targetDir.resolve(DEFAULT_INDEX_FILE_NAME); } + final UpToDateChecker checker; if (upToDateChecking != null && upToDateChecking.isEnabled()) { - getLog().info("Up-to-date checking enabled"); - return UpToDateChecker.forProject(project, indexFile, formatters, getLog()); + checker = UpToDateChecker.forProject(project, indexFile, formatters, getLog()); + } else { + getLog().info("Up-to-date checking disabled"); + checker = UpToDateChecker.noop(project, indexFile, getLog()); } - return UpToDateChecker.noop(project, indexFile, getLog()); + return UpToDateChecker.wrapWithBuildContext(checker, buildContext); } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java index 7ea998dc95..088c35a3d0 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,10 @@ import static java.nio.charset.StandardCharsets.UTF_8; import java.io.File; +import java.net.URISyntaxException; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; @@ -34,17 +38,18 @@ public class FileLocator { static final String TMP_RESOURCE_FILE_PREFIX = "spotless-resource-"; private final ResourceManager resourceManager; - private final File baseDir, buildDir; + private final File baseDir, buildDir, dataDir; public FileLocator(ResourceManager resourceManager, File baseDir, File buildDir) { this.resourceManager = Objects.requireNonNull(resourceManager); this.baseDir = Objects.requireNonNull(baseDir); this.buildDir = Objects.requireNonNull(buildDir); + this.dataDir = findDataDir(); } /** - * If the given path is a local file returns it as such unchanged, - * otherwise extracts the given resource to a randomly-named file in the build folder. + * If the given path is a local file returns it as such unchanged, otherwise + * extracts the given resource to a randomly-named file in the build folder. */ public File locateFile(String path) { if (isNullOrEmpty(path)) { @@ -62,18 +67,42 @@ public File locateFile(String path) { } catch (ResourceNotFoundException e) { throw new RuntimeException("Unable to locate file with path: " + path, e); } catch (FileResourceCreationException e) { - throw new RuntimeException("Unable to create temporary file '" + outputFile + "' in the output directory", e); + throw new RuntimeException("Unable to create temporary file '" + outputFile + "' in the output directory", + e); } } + /** + * Finds the base directory of the Maven or Gradle project on which spotless is + * currently being executed. + * + * @return The base directory of the current Maven or Gradel project. + */ public File getBaseDir() { return baseDir; } + /** + * Finds the build directory (e.g. /target) of the Maven or Gradle + * project on which spotless is currently being executed. + * + * @return The project build directory of the current Maven or Gradle project. + */ public File getBuildDir() { return buildDir; } + /** + * Finds the data directory that can be used for storing shared data such as + * downloaded files globally. This is a directory in the local repository, e.g. + * ~/.m2/repository/com/diffplus/spotless/spotless-data. + * + * @return The directory for storing shared data. + */ + public File getDataDir() { + return dataDir; + } + private static String tmpOutputFileName(String path) { String extension = FileUtils.extension(path); byte[] pathHash = hash(path); @@ -91,4 +120,33 @@ private static byte[] hash(String value) { messageDigest.update(value.getBytes(UTF_8)); return messageDigest.digest(); } + + private static File findDataDir() { + try { + // JAR path is e.g. + // ~/.m2/repository/com/diffplug/spotless/spotless-plugin-maven/1.2.3/spotless-plugin-maven-1.2.3.jar + var codeSource = FileLocator.class.getProtectionDomain().getCodeSource(); + var location = codeSource != null ? codeSource.getLocation() : null; + var locationUri = location != null ? location.toURI() : null; + var jarPath = locationUri != null && "file".equals(locationUri.getScheme()) ? Path.of(locationUri) : null; + var parent1 = jarPath != null ? jarPath.getParent() : null; + var parent2 = parent1 != null ? parent1.getParent() : null; + var base = parent2 != null ? parent2.getParent() : null; + var sub = base != null ? base.resolve("spotless-data") : null; + if (sub != null) { + return sub.toAbsolutePath().toFile(); + } else { + return findUserHome(); + } + } catch (final SecurityException e) { + return findUserHome(); + } catch (final URISyntaxException | FileSystemNotFoundException | IllegalArgumentException e) { + throw new RuntimeException("Unable to determine data directory in local Maven repository", e); + } + } + + private static File findUserHome() { + var home = Paths.get(System.getenv("user.home")); + return home.resolve(".rome").toAbsolutePath().toFile(); + } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java index d5fbe60370..c4a6663087 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormatterFactory.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,6 +29,7 @@ import java.util.stream.Collectors; import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; import com.diffplug.common.collect.Sets; import com.diffplug.spotless.FormatExceptionPolicyStrict; @@ -71,7 +72,7 @@ public abstract class FormatterFactory { private ToggleOffOn toggle; - public abstract Set defaultIncludes(); + public abstract Set defaultIncludes(MavenProject project); public abstract String licenseHeaderDelimiter(); @@ -101,7 +102,9 @@ public final Formatter newFormatter(Supplier> filesToFormat, Form formatterSteps.add(pair.out()); } + String formatterName = this.getClass().getSimpleName(); return Formatter.builder() + .name(formatterName) .encoding(formatterEncoding) .lineEndingsPolicy(formatterLineEndingPolicy) .exceptionPolicy(new FormatExceptionPolicyStrict()) diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormattersHolder.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormattersHolder.java index 1c226423f6..873321f9df 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormattersHolder.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FormattersHolder.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ package com.diffplug.spotless.maven; import java.io.File; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -33,7 +33,7 @@ class FormattersHolder implements AutoCloseable { } static FormattersHolder create(Map>> formatterFactoryToFiles, FormatterConfig config) { - Map>> formatterToFiles = new HashMap<>(); + Map>> formatterToFiles = new LinkedHashMap<>(); try { for (Entry>> entry : formatterFactoryToFiles.entrySet()) { FormatterFactory formatterFactory = entry.getKey(); diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/IdeHook.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/IdeHook.java new file mode 100644 index 0000000000..0001046871 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/IdeHook.java @@ -0,0 +1,83 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +import com.diffplug.common.base.Errors; +import com.diffplug.common.io.ByteStreams; +import com.diffplug.spotless.Formatter; +import com.diffplug.spotless.PaddedCell; + +class IdeHook { + + private static void dumpIsClean() { + System.err.println("IS CLEAN"); + } + + //No need to check ratchet (using isClean()) as it is performed in Gradle's IDE hook, since we have already gathered the available git files from ratchet. + static void performHook(Iterable projectFiles, Formatter formatter, String path, boolean spotlessIdeHookUseStdIn, boolean spotlessIdeHookUseStdOut) { + File file = new File(path); + if (!file.isAbsolute()) { + System.err.println("Argument passed to spotlessIdeHook must be an absolute path"); + return; + } + + if (!projectContainsFile(projectFiles, file)) { + return; + } + + try { + byte[] bytes; + if (spotlessIdeHookUseStdIn) { + bytes = ByteStreams.toByteArray(System.in); + } else { + bytes = Files.readAllBytes(file.toPath()); + } + PaddedCell.DirtyState dirty = PaddedCell.calculateDirtyState(formatter, file, bytes); + if (dirty.isClean()) { + dumpIsClean(); + } else if (dirty.didNotConverge()) { + System.err.println("DID NOT CONVERGE"); + System.err.println("See details https://github.com/diffplug/spotless/blob/main/PADDEDCELL.md"); + } else { + System.err.println("IS DIRTY"); + if (spotlessIdeHookUseStdOut) { + dirty.writeCanonicalTo(System.out); + } else { + dirty.writeCanonicalTo(file); + } + } + } catch (IOException e) { + e.printStackTrace(System.err); + throw Errors.asRuntime(e); + } finally { + System.err.close(); + System.out.close(); + } + } + + private static boolean projectContainsFile(Iterable projectFiles, File file) { + for (File projectFile : projectFiles) { + if (projectFile.equals(file)) { + return true; + } + } + return false; + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/ImpactedFilesTracker.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/ImpactedFilesTracker.java new file mode 100644 index 0000000000..60eb8af762 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/ImpactedFilesTracker.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven; + +/** + * Tracks the number of processed files, typically by a single Formatter for a whole repository + */ +class ImpactedFilesTracker { + protected int nbskippedAsCleanCache = 0; + protected int nbCheckedButAlreadyClean = 0; + protected int nbCleaned = 0; + + /** + * Some cache mechanism may indicate some content is clean, without having to execute the cleaning process + */ + public void skippedAsCleanCache() { + nbskippedAsCleanCache++; + } + + public int getSkippedAsCleanCache() { + return nbskippedAsCleanCache; + } + + public void checkedButAlreadyClean() { + nbCheckedButAlreadyClean++; + } + + public int getCheckedButAlreadyClean() { + return nbCheckedButAlreadyClean; + } + + public void cleaned() { + nbCleaned++; + } + + public int getCleaned() { + return nbCleaned; + } + + public int getTotal() { + return nbskippedAsCleanCache + nbCheckedButAlreadyClean + nbCleaned; + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessApplyMojo.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessApplyMojo.java index 6ea67240ee..07cc2cbc85 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessApplyMojo.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessApplyMojo.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; import com.diffplug.spotless.Formatter; import com.diffplug.spotless.PaddedCell; @@ -31,10 +32,27 @@ @Mojo(name = AbstractSpotlessMojo.GOAL_APPLY, threadSafe = true) public class SpotlessApplyMojo extends AbstractSpotlessMojo { + @Parameter(property = "spotlessIdeHook") + private String spotlessIdeHook; + + @Parameter(property = "spotlessIdeHookUseStdIn") + private boolean spotlessIdeHookUseStdIn; + + @Parameter(property = "spotlessIdeHookUseStdOut") + private boolean spotlessIdeHookUseStdOut; + @Override protected void process(Iterable files, Formatter formatter, UpToDateChecker upToDateChecker) throws MojoExecutionException { + if (isIdeHook()) { + IdeHook.performHook(files, formatter, spotlessIdeHook, spotlessIdeHookUseStdIn, spotlessIdeHookUseStdOut); + return; + } + + ImpactedFilesTracker counter = new ImpactedFilesTracker(); + for (File file : files) { if (upToDateChecker.isUpToDate(file.toPath())) { + counter.skippedAsCleanCache(); if (getLog().isDebugEnabled()) { getLog().debug("Spotless will not format an up-to-date file: " + file); } @@ -44,13 +62,30 @@ protected void process(Iterable files, Formatter formatter, UpToDateChecke try { PaddedCell.DirtyState dirtyState = PaddedCell.calculateDirtyState(formatter, file); if (!dirtyState.isClean() && !dirtyState.didNotConverge()) { + getLog().info(String.format("Writing clean file: %s", file)); dirtyState.writeCanonicalTo(file); + buildContext.refresh(file); + counter.cleaned(); + } else { + counter.checkedButAlreadyClean(); } - } catch (IOException e) { + } catch (IOException | RuntimeException e) { throw new MojoExecutionException("Unable to format file " + file, e); } upToDateChecker.setUpToDate(file.toPath()); } + + // We print the number of considered files which is useful when ratchetFrom is setup + if (counter.getTotal() > 0) { + getLog().info(String.format("Spotless.%s is keeping %s files clean - %s were changed to be clean, %s were already clean, %s were skipped because caching determined they were already clean", + formatter.getName(), counter.getTotal(), counter.getCleaned(), counter.getCheckedButAlreadyClean(), counter.getSkippedAsCleanCache())); + } else { + getLog().debug(String.format("Spotless.%s has no target files. Examine your ``: https://github.com/diffplug/spotless/tree/main/plugin-maven#quickstart", formatter.getName())); + } + } + + private boolean isIdeHook() { + return !(spotlessIdeHook == null || spotlessIdeHook.isEmpty()); } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessCheckMojo.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessCheckMojo.java index ad3230f583..cc83b3f3c9 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessCheckMojo.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/SpotlessCheckMojo.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,10 +19,12 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Map; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; +import org.sonatype.plexus.build.incremental.BuildContext; import com.diffplug.spotless.Formatter; import com.diffplug.spotless.PaddedCell; @@ -38,9 +40,12 @@ public class SpotlessCheckMojo extends AbstractSpotlessMojo { @Override protected void process(Iterable files, Formatter formatter, UpToDateChecker upToDateChecker) throws MojoExecutionException { + ImpactedFilesTracker counter = new ImpactedFilesTracker(); + List problemFiles = new ArrayList<>(); for (File file : files) { if (upToDateChecker.isUpToDate(file.toPath())) { + counter.skippedAsCleanCache(); if (getLog().isDebugEnabled()) { getLog().debug("Spotless will not check an up-to-date file: " + file); } @@ -51,14 +56,28 @@ protected void process(Iterable files, Formatter formatter, UpToDateChecke PaddedCell.DirtyState dirtyState = PaddedCell.calculateDirtyState(formatter, file); if (!dirtyState.isClean() && !dirtyState.didNotConverge()) { problemFiles.add(file); + if (buildContext.isIncremental()) { + Map.Entry diffEntry = DiffMessageFormatter.diff(formatter, file); + buildContext.addMessage(file, diffEntry.getKey() + 1, 0, diffEntry.getValue(), BuildContext.SEVERITY_ERROR, null); + } + counter.cleaned(); } else { + counter.checkedButAlreadyClean(); upToDateChecker.setUpToDate(file.toPath()); } - } catch (IOException e) { - throw new MojoExecutionException("Unable to format file " + file, e); + } catch (IOException | RuntimeException e) { + throw new MojoExecutionException("Unable to check file " + file, e); } } + // We print the number of considered files which is useful when ratchetFrom is setup + if (counter.getTotal() > 0) { + getLog().info(String.format("Spotless.%s is keeping %s files clean - %s needs changes to be clean, %s were already clean, %s were skipped because caching determined they were already clean", + formatter.getName(), counter.getTotal(), counter.getCleaned(), counter.getCheckedButAlreadyClean(), counter.getSkippedAsCleanCache())); + } else { + getLog().debug(String.format("Spotless.%s has no target files. Examine your ``: https://github.com/diffplug/spotless/tree/main/plugin-maven#quickstart", formatter.getName())); + } + if (!problemFiles.isEmpty()) { throw new MojoExecutionException(DiffMessageFormatter.builder() .runToFix("Run 'mvn spotless:apply' to fix these violations.") diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/antlr4/Antlr4.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/antlr4/Antlr4.java index bc24ebcf16..a43416fb4e 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/antlr4/Antlr4.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/antlr4/Antlr4.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,8 @@ import java.util.Set; +import org.apache.maven.project.MavenProject; + import com.diffplug.common.collect.ImmutableSet; import com.diffplug.spotless.antlr4.Antlr4Defaults; import com.diffplug.spotless.maven.FormatterFactory; @@ -30,7 +32,7 @@ */ public class Antlr4 extends FormatterFactory { @Override - public Set defaultIncludes() { + public Set defaultIncludes(MavenProject project) { return ImmutableSet.of(Antlr4Defaults.includes()); } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/cpp/Cpp.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/cpp/Cpp.java index f1d07d8552..44940ae4d8 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/cpp/Cpp.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/cpp/Cpp.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ import java.util.Collections; import java.util.Set; +import org.apache.maven.project.MavenProject; + import com.diffplug.spotless.cpp.CppDefaults; import com.diffplug.spotless.maven.FormatterFactory; import com.diffplug.spotless.maven.generic.LicenseHeader; @@ -30,7 +32,7 @@ */ public class Cpp extends FormatterFactory { @Override - public Set defaultIncludes() { + public Set defaultIncludes(MavenProject project) { return Collections.emptySet(); } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/cpp/EclipseCdt.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/cpp/EclipseCdt.java index 78484abfba..92f4229ed3 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/cpp/EclipseCdt.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/cpp/EclipseCdt.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,15 @@ package com.diffplug.spotless.maven.cpp; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import org.apache.maven.plugins.annotations.Parameter; import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.extra.EclipseBasedStepBuilder; +import com.diffplug.spotless.extra.EquoBasedStepBuilder; +import com.diffplug.spotless.extra.P2Mirror; import com.diffplug.spotless.extra.cpp.EclipseCdtFormatterStep; import com.diffplug.spotless.maven.FormatterStepConfig; import com.diffplug.spotless.maven.FormatterStepFactory; @@ -34,14 +37,18 @@ public class EclipseCdt implements FormatterStepFactory { @Parameter private String version; + @Parameter + private List p2Mirrors = new ArrayList<>(); + @Override public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { - EclipseBasedStepBuilder eclipseConfig = EclipseCdtFormatterStep.createBuilder(stepConfig.getProvisioner()); + EquoBasedStepBuilder eclipseConfig = EclipseCdtFormatterStep.createBuilder(stepConfig.getProvisioner()); eclipseConfig.setVersion(version == null ? EclipseCdtFormatterStep.defaultVersion() : version); if (null != file) { File settingsFile = stepConfig.getFileLocator().locateFile(file); eclipseConfig.setPreferences(Arrays.asList(settingsFile)); } + eclipseConfig.setP2Mirrors(p2Mirrors); return eclipseConfig.build(); } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Biome.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Biome.java new file mode 100644 index 0000000000..e096a14734 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Biome.java @@ -0,0 +1,60 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.generic; + +import org.apache.maven.plugins.annotations.Parameter; + +import com.diffplug.spotless.maven.rome.AbstractRome; +import com.diffplug.spotless.rome.BiomeFlavor; + +/** + * Generic Biome formatter step that detects the language of the input file from + * the file name. It should be specified as a formatter step for a generic + * {@code }. + */ +public class Biome extends AbstractRome { + public Biome() { + super(BiomeFlavor.BIOME); + } + + /** + * Gets the language (syntax) of the input files to format. When + * null or the empty string, the language is detected automatically + * from the file name. Currently the following languages are supported by Biome: + *

    + *
      + *
    • js (JavaScript)
    • + *
    • jsx (JavaScript + JSX)
    • + *
    • js? (JavaScript or JavaScript + JSX, depending on the file + * extension)
    • + *
    • ts (TypeScript)
    • + *
    • tsx (TypeScript + JSX)
    • + *
    • ts? (TypeScript or TypeScript + JSX, depending on the file + * extension)
    • + *
    • json (JSON)
    • + *
    + *
+ * + * @return The language of the input files. + */ + @Parameter + private String language; + + @Override + protected String getLanguage() { + return language; + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Format.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Format.java index 465381fb52..ce7db9b926 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Format.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Format.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ import java.util.Collections; import java.util.Set; +import org.apache.maven.project.MavenProject; + import com.diffplug.spotless.maven.FormatterFactory; /** @@ -29,7 +31,7 @@ public class Format extends FormatterFactory { @Override - public Set defaultIncludes() { + public Set defaultIncludes(MavenProject project) { return Collections.emptySet(); } @@ -38,4 +40,22 @@ public String licenseHeaderDelimiter() { // do not specify a default delimiter return null; } + + /** + * Adds a step to this format that format code with the Biome formatter. + * @param biome Biome configuration to use. + */ + public void addBiome(Biome biome) { + addStepFactory(biome); + } + + /** + * Adds a step to this format that format code with the Rome formatter. + * @param rome Rome configuration to use. + * @deprecated Rome has transitioned to Biome. Use {@link #addBiome(Biome)}. + */ + @Deprecated + public void addRome(Rome rome) { + addStepFactory(rome); + } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/LicenseHeader.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/LicenseHeader.java index ee2ae4fda0..ff89f55ead 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/LicenseHeader.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/LicenseHeader.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,6 +37,9 @@ public class LicenseHeader implements FormatterStepFactory { @Parameter private String delimiter; + @Parameter + private String skipLinesMatching; + @Override public final FormatterStep newFormatterStep(FormatterStepConfig config) { String delimiterString = delimiter != null ? delimiter : config.getLicenseHeaderDelimiter(); @@ -53,6 +56,7 @@ public final FormatterStep newFormatterStep(FormatterStepConfig config) { } return LicenseHeaderStep.headerDelimiter(() -> readFileOrContent(config), delimiterString) .withYearMode(yearMode) + .withSkipLinesMatching(skipLinesMatching) .build() .filterByFile(LicenseHeaderStep.unsupportedJvmFilesFilter()); } else { diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Prettier.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Prettier.java index 85684e85cd..c6b3e46e3c 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Prettier.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Prettier.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,12 +23,12 @@ import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.maven.FormatterStepConfig; -import com.diffplug.spotless.maven.FormatterStepFactory; +import com.diffplug.spotless.maven.npm.AbstractNpmFormatterStepFactory; import com.diffplug.spotless.npm.NpmPathResolver; import com.diffplug.spotless.npm.PrettierConfig; import com.diffplug.spotless.npm.PrettierFormatterStep; -public class Prettier implements FormatterStepFactory { +public class Prettier extends AbstractNpmFormatterStepFactory { public static final String ERROR_MESSAGE_ONLY_ONE_CONFIG = "must specify exactly one prettierVersion, devDependencies or devDependencyProperties"; @@ -47,12 +47,6 @@ public class Prettier implements FormatterStepFactory { @Parameter private String configFile; - @Parameter - private String npmExecutable; - - @Parameter - private String npmrc; - @Override public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { @@ -67,13 +61,9 @@ public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { if (prettierVersion != null && !prettierVersion.isEmpty()) { this.devDependencies = PrettierFormatterStep.defaultDevDependenciesWithPrettier(prettierVersion); } else if (devDependencyProperties != null) { - this.devDependencies = dependencyPropertiesAsMap(); + this.devDependencies = propertiesAsMap(this.devDependencyProperties); } - File npm = npmExecutable != null ? stepConfig.getFileLocator().locateFile(npmExecutable) : null; - - File npmrcFile = npmrc != null ? stepConfig.getFileLocator().locateFile(npmrc) : null; - // process config file or inline config File configFileHandler; if (this.configFile != null) { @@ -95,6 +85,11 @@ public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { if (Boolean.TRUE.toString().equalsIgnoreCase(entry.getValue()) || Boolean.FALSE.toString().equalsIgnoreCase(entry.getValue())) { return new AbstractMap.SimpleEntry<>(entry.getKey(), Boolean.parseBoolean(entry.getValue())); } + // Prettier v3 - plugins config will be a comma delimited list of plugins + if (entry.getKey().equals("plugins")) { + List values = entry.getValue().isEmpty() ? List.of() : Arrays.asList(entry.getValue().split(",")); + return new AbstractMap.SimpleEntry<>(entry.getKey(), values); + } return entry; }) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, LinkedHashMap::new)); @@ -103,24 +98,12 @@ public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { } // create the format step + File baseDir = baseDir(stepConfig); + File buildDir = buildDir(stepConfig); + File cacheDir = cacheDir(stepConfig); PrettierConfig prettierConfig = new PrettierConfig(configFileHandler, configInline); - File buildDir = stepConfig.getFileLocator().getBuildDir(); - NpmPathResolver npmPathResolver = new NpmPathResolver(npm, npmrcFile, stepConfig.getFileLocator().getBaseDir()); - return PrettierFormatterStep.create(devDependencies, stepConfig.getProvisioner(), buildDir, npmPathResolver, prettierConfig); - } - - private boolean moreThanOneNonNull(Object... objects) { - return Arrays.stream(objects) - .filter(Objects::nonNull) - .filter(o -> !(o instanceof String) || !((String) o).isEmpty()) // if it is a string, it should not be empty - .count() > 1; - } - - private Map dependencyPropertiesAsMap() { - return this.devDependencyProperties.stringPropertyNames() - .stream() - .map(name -> new AbstractMap.SimpleEntry<>(name, this.devDependencyProperties.getProperty(name))) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + NpmPathResolver npmPathResolver = npmPathResolver(stepConfig); + return PrettierFormatterStep.create(devDependencies, stepConfig.getProvisioner(), baseDir, buildDir, cacheDir, npmPathResolver, prettierConfig); } private static IllegalArgumentException onlyOneConfig() { diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Replace.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Replace.java index 947ceee8e2..53967fd7d7 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Replace.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Replace.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2022 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,10 +35,12 @@ public class Replace implements FormatterStepFactory { @Override public FormatterStep newFormatterStep(FormatterStepConfig config) { - if (name == null || search == null || replacement == null) { - throw new IllegalArgumentException("Must specify 'name', 'search' and 'replacement'."); + if (name == null || search == null) { + throw new IllegalArgumentException("Must specify 'name' and 'search'."); } - - return ReplaceStep.create(name, search, replacement); + // Use empty string if replacement is not provided. In pom.xml there is no way to specify + // an empty string as a property value as maven will always trim the value and if it is + // empty, maven will consider the property as not provided. + return ReplaceStep.create(name, search, replacement != null ? replacement : ""); } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/ReplaceRegex.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/ReplaceRegex.java index 243f14220e..9983aa8475 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/ReplaceRegex.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/ReplaceRegex.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2022 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,10 +35,12 @@ public class ReplaceRegex implements FormatterStepFactory { @Override public FormatterStep newFormatterStep(FormatterStepConfig config) { - if (name == null || searchRegex == null || replacement == null) { - throw new IllegalArgumentException("Must specify 'name', 'searchRegex' and 'replacement'."); + if (name == null || searchRegex == null) { + throw new IllegalArgumentException("Must specify 'name' and 'searchRegex'."); } - - return ReplaceRegexStep.create(name, searchRegex, replacement); + // Use empty string if replacement is not provided. In pom.xml there is no way to specify + // an empty string as a property value as maven will always trim the value and if it is + // empty, maven will consider the property as not provided. + return ReplaceRegexStep.create(name, searchRegex, replacement != null ? replacement : ""); } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Rome.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Rome.java new file mode 100644 index 0000000000..53080fe546 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Rome.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.generic; + +import org.apache.maven.plugins.annotations.Parameter; + +import com.diffplug.spotless.maven.rome.AbstractRome; +import com.diffplug.spotless.rome.BiomeFlavor; + +/** + * See {@link Biome}. + * @deprecated Rome has transitioned to Biome. This will be removed shortly. + */ +public class Rome extends AbstractRome { + public Rome() { + super(BiomeFlavor.ROME); + } + + /** + * Gets the language (syntax) of the input files to format. When + * null or the empty string, the language is detected automatically + * from the file name. Currently the following languages are supported by Rome: + *
    + *
      + *
    • js (JavaScript)
    • + *
    • jsx (JavaScript + JSX)
    • + *
    • js? (JavaScript or JavaScript + JSX, depending on the file + * extension)
    • + *
    • ts (TypeScript)
    • + *
    • tsx (TypeScript + JSX)
    • + *
    • ts? (TypeScript or TypeScript + JSX, depending on the file + * extension)
    • + *
    • json (JSON)
    • + *
    + *
+ * + * @return The language of the input files. + */ + @Parameter + private String language; + + @Override + protected String getLanguage() { + return language; + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/gherkin/Gherkin.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/gherkin/Gherkin.java new file mode 100644 index 0000000000..60181d048a --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/gherkin/Gherkin.java @@ -0,0 +1,43 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.gherkin; + +import java.util.Collections; +import java.util.Set; + +import org.apache.maven.project.MavenProject; + +import com.diffplug.spotless.maven.FormatterFactory; + +/** + * A {@link FormatterFactory} implementation that corresponds to {@code ...} configuration element. + */ +public class Gherkin extends FormatterFactory { + @Override + public Set defaultIncludes(MavenProject project) { + return Collections.emptySet(); + } + + @Override + public String licenseHeaderDelimiter() { + return null; + } + + public void addGherkinUtils(GherkinUtils gherkinUtils) { + addStepFactory(gherkinUtils); + } + +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/gherkin/GherkinUtils.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/gherkin/GherkinUtils.java new file mode 100644 index 0000000000..2eeabb9ab5 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/gherkin/GherkinUtils.java @@ -0,0 +1,42 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.gherkin; + +import org.apache.maven.plugins.annotations.Parameter; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.gherkin.GherkinUtilsConfig; +import com.diffplug.spotless.gherkin.GherkinUtilsStep; +import com.diffplug.spotless.maven.FormatterFactory; +import com.diffplug.spotless.maven.FormatterStepConfig; +import com.diffplug.spotless.maven.FormatterStepFactory; + +/** + * A {@link FormatterFactory} implementation that corresponds to {@code ...} configuration element. + */ +public class GherkinUtils implements FormatterStepFactory { + + @Parameter + private String version = GherkinUtilsStep.defaultVersion(); + + @Parameter + private int indentWithSpaces = GherkinUtilsConfig.defaultIndentSpaces(); + + @Override + public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { + return GherkinUtilsStep.create(new GherkinUtilsConfig(indentWithSpaces), version, stepConfig.getProvisioner()); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/go/Go.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/go/Go.java new file mode 100644 index 0000000000..bb26bd0355 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/go/Go.java @@ -0,0 +1,39 @@ +/* + * Copyright 2024 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.go; + +import java.util.Collections; +import java.util.Set; + +import org.apache.maven.project.MavenProject; + +import com.diffplug.spotless.maven.FormatterFactory; + +public class Go extends FormatterFactory { + @Override + public Set defaultIncludes(MavenProject project) { + return Collections.emptySet(); + } + + @Override + public String licenseHeaderDelimiter() { + return null; + } + + public void addGofmt(Gofmt gofmt) { + addStepFactory(gofmt); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/go/Gofmt.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/go/Gofmt.java new file mode 100644 index 0000000000..aaa30efb4f --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/go/Gofmt.java @@ -0,0 +1,41 @@ +/* + * Copyright 2024 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.go; + +import org.apache.maven.plugins.annotations.Parameter; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.go.GofmtFormatStep; +import com.diffplug.spotless.maven.FormatterStepConfig; +import com.diffplug.spotless.maven.FormatterStepFactory; + +public class Gofmt implements FormatterStepFactory { + + @Parameter + private String version; + + @Parameter + private String goExecutablePath; + + @Override + public FormatterStep newFormatterStep(FormatterStepConfig config) { + GofmtFormatStep step = GofmtFormatStep.withVersion(version == null ? GofmtFormatStep.defaultVersion() : version); + if (goExecutablePath != null) { + step = step.withGoExecutable(goExecutablePath); + } + return step.create(); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/groovy/GrEclipse.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/groovy/GrEclipse.java index 81da109e04..67ab40fbf2 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/groovy/GrEclipse.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/groovy/GrEclipse.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,15 @@ package com.diffplug.spotless.maven.groovy; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import org.apache.maven.plugins.annotations.Parameter; import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.extra.EclipseBasedStepBuilder; +import com.diffplug.spotless.extra.EquoBasedStepBuilder; +import com.diffplug.spotless.extra.P2Mirror; import com.diffplug.spotless.extra.groovy.GrEclipseFormatterStep; import com.diffplug.spotless.maven.FormatterStepConfig; import com.diffplug.spotless.maven.FormatterStepFactory; @@ -34,14 +37,18 @@ public class GrEclipse implements FormatterStepFactory { @Parameter private String version; + @Parameter + private List p2Mirrors = new ArrayList<>(); + @Override public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { - EclipseBasedStepBuilder grEclipseConfig = GrEclipseFormatterStep.createBuilder(stepConfig.getProvisioner()); + EquoBasedStepBuilder grEclipseConfig = GrEclipseFormatterStep.createBuilder(stepConfig.getProvisioner()); grEclipseConfig.setVersion(version == null ? GrEclipseFormatterStep.defaultVersion() : version); if (null != file) { File settingsFile = stepConfig.getFileLocator().locateFile(file); grEclipseConfig.setPreferences(Arrays.asList(settingsFile)); } + grEclipseConfig.setP2Mirrors(p2Mirrors); return grEclipseConfig.build(); } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/groovy/Groovy.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/groovy/Groovy.java index e9b3c6c412..db4b35dfd6 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/groovy/Groovy.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/groovy/Groovy.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,10 @@ import java.util.Set; +import org.apache.maven.project.MavenProject; + import com.diffplug.common.collect.ImmutableSet; +import com.diffplug.spotless.generic.LicenseHeaderStep; import com.diffplug.spotless.maven.FormatterFactory; import com.diffplug.spotless.maven.generic.LicenseHeader; @@ -30,10 +33,10 @@ public class Groovy extends FormatterFactory { private static final Set DEFAULT_INCLUDES = ImmutableSet.of("src/main/groovy/**/*.groovy", "src/test/groovy/**/*.groovy"); - private static final String LICENSE_HEADER_DELIMITER = "package "; + private static final String LICENSE_HEADER_DELIMITER = LicenseHeaderStep.DEFAULT_JAVA_HEADER_DELIMITER; @Override - public Set defaultIncludes() { + public Set defaultIncludes(MavenProject project) { return DEFAULT_INCLUDES; } @@ -49,4 +52,8 @@ public void addGreclipse(GrEclipse greclipse) { public void addImportOrder(ImportOrder importOrder) { addStepFactory(importOrder); } + + public void addRemoveSemicolons(RemoveSemicolons removeSemicolons) { + addStepFactory(removeSemicolons); + } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/groovy/RemoveSemicolons.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/groovy/RemoveSemicolons.java new file mode 100644 index 0000000000..a96a078692 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/groovy/RemoveSemicolons.java @@ -0,0 +1,29 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.groovy; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.groovy.RemoveSemicolonsStep; +import com.diffplug.spotless.maven.FormatterStepConfig; +import com.diffplug.spotless.maven.FormatterStepFactory; + +public class RemoveSemicolons implements FormatterStepFactory { + + @Override + public FormatterStep newFormatterStep(FormatterStepConfig config) { + return RemoveSemicolonsStep.create(); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/incremental/FileIndex.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/incremental/FileIndex.java index 728f8a57bc..c719419923 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/incremental/FileIndex.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/incremental/FileIndex.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,7 @@ import java.io.PrintWriter; import java.io.UncheckedIOException; import java.nio.file.Files; +import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.Paths; import java.time.Instant; @@ -76,7 +77,7 @@ static FileIndex read(FileIndexConfig config, Log log) { PluginFingerprint computedFingerprint = config.getPluginFingerprint(); PluginFingerprint storedFingerprint = PluginFingerprint.from(firstLine); if (!computedFingerprint.equals(storedFingerprint)) { - log.info("Fingerprint mismatch in the index file. Fallback to an empty index"); + log.info("Index file corresponds to a different configuration of the plugin. Either the plugin version or its configuration has changed. Fallback to an empty index"); return emptyIndexFallback(config); } else { Content content = readIndexContent(reader, config.getProjectDir(), log); @@ -144,7 +145,14 @@ private void ensureParentDirExists() { throw new IllegalStateException("Index file does not have a parent dir: " + indexFile); } try { - Files.createDirectories(parentDir); + if (Files.exists(parentDir, LinkOption.NOFOLLOW_LINKS)) { + Path realPath = parentDir.toRealPath(); + if (!Files.exists(realPath)) { + Files.createDirectories(realPath); + } + } else { + Files.createDirectories(parentDir); + } } catch (IOException e) { throw new UncheckedIOException("Unable to create parent directory for the index file: " + indexFile, e); } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/incremental/PluginFingerprint.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/incremental/PluginFingerprint.java index 3805cf4600..34a01e235a 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/incremental/PluginFingerprint.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/incremental/PluginFingerprint.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,17 +17,22 @@ import java.io.IOException; import java.io.UncheckedIOException; -import java.util.ArrayList; import java.util.Base64; -import java.util.List; import java.util.Objects; -import org.apache.maven.model.Dependency; import org.apache.maven.model.Plugin; +import org.apache.maven.model.PluginManagement; import org.apache.maven.project.MavenProject; import com.diffplug.spotless.Formatter; +/** + * Represents a particular Spotless Maven plugin setup using a Base64-encoded serialized form of: + *
    + *
  1. Plugin version as configured in the POM
  2. + *
  3. Formatter instances created according to the POM configuration
  4. + *
+ */ class PluginFingerprint { private static final String SPOTLESS_PLUGIN_KEY = "com.diffplug.spotless:spotless-maven-plugin"; @@ -39,10 +44,7 @@ private PluginFingerprint(String value) { } static PluginFingerprint from(MavenProject project, Iterable formatters) { - Plugin spotlessPlugin = project.getPlugin(SPOTLESS_PLUGIN_KEY); - if (spotlessPlugin == null) { - throw new IllegalArgumentException("Spotless plugin absent from the project: " + project); - } + Plugin spotlessPlugin = findSpotlessPlugin(project); byte[] digest = digest(spotlessPlugin, formatters); String value = Base64.getEncoder().encodeToString(digest); return new PluginFingerprint(value); @@ -82,13 +84,27 @@ public String toString() { return "PluginFingerprint[" + value + "]"; } + private static Plugin findSpotlessPlugin(MavenProject project) { + // Try to find the plugin instance from XML element + Plugin plugin = project.getPlugin(SPOTLESS_PLUGIN_KEY); + if (plugin == null) { + // Try to find the plugin instance from XML element. Useful when + // the current module is a parent of a multimodule project + PluginManagement pluginManagement = project.getPluginManagement(); + if (pluginManagement != null) { + plugin = pluginManagement.getPluginsAsMap().get(SPOTLESS_PLUGIN_KEY); + } + } + + if (plugin == null) { + throw new IllegalArgumentException("Spotless plugin absent from the project: " + project); + } + return plugin; + } + private static byte[] digest(Plugin plugin, Iterable formatters) { - // dependencies can be an unserializable org.apache.maven.model.merge.ModelMerger$MergingList - // replace it with a serializable ArrayList - List dependencies = plugin.getDependencies(); - plugin.setDependencies(new ArrayList<>(dependencies)); try (ObjectDigestOutputStream out = ObjectDigestOutputStream.create()) { - out.writeObject(plugin); + out.writeObject(plugin.getVersion()); for (Formatter formatter : formatters) { out.writeObject(formatter); } @@ -96,9 +112,6 @@ private static byte[] digest(Plugin plugin, Iterable formatters) { return out.digest(); } catch (IOException e) { throw new UncheckedIOException("Unable to serialize plugin " + plugin, e); - } finally { - // reset the original list - plugin.setDependencies(dependencies); } } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/incremental/UpToDateChecker.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/incremental/UpToDateChecker.java index 2fd2f3a1ea..40dc84c8c6 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/incremental/UpToDateChecker.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/incremental/UpToDateChecker.java @@ -19,6 +19,7 @@ import org.apache.maven.plugin.logging.Log; import org.apache.maven.project.MavenProject; +import org.sonatype.plexus.build.incremental.BuildContext; import com.diffplug.spotless.Formatter; @@ -37,4 +38,28 @@ static UpToDateChecker noop(MavenProject project, Path indexFile, Log log) { static UpToDateChecker forProject(MavenProject project, Path indexFile, Iterable formatters, Log log) { return IndexBasedChecker.create(project, indexFile, formatters, log); } + + static UpToDateChecker wrapWithBuildContext(UpToDateChecker delegate, BuildContext buildContext) { + return new UpToDateChecker() { + + @Override + public void setUpToDate(Path file) { + delegate.setUpToDate(file); + } + + @Override + public boolean isUpToDate(Path file) { + if (buildContext.hasDelta(file.toFile())) { + return delegate.isUpToDate(file); + } + return true; + } + + @Override + public void close() { + delegate.close(); + } + }; + } + } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/incremental/UpToDateChecking.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/incremental/UpToDateChecking.java index d5eb6aa941..f044b847c9 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/incremental/UpToDateChecking.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/incremental/UpToDateChecking.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,4 +38,10 @@ public boolean isEnabled() { public Path getIndexFile() { return indexFile == null ? null : new File(indexFile).toPath(); } + + public static UpToDateChecking enabled() { + UpToDateChecking upToDateChecking = new UpToDateChecking(); + upToDateChecking.enabled = true; + return upToDateChecking; + } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/CleanthatJava.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/CleanthatJava.java new file mode 100644 index 0000000000..4133409919 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/CleanthatJava.java @@ -0,0 +1,54 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.java; + +import java.util.List; + +import org.apache.maven.plugins.annotations.Parameter; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.java.CleanthatJavaStep; +import com.diffplug.spotless.maven.FormatterStepConfig; +import com.diffplug.spotless.maven.FormatterStepFactory; + +public class CleanthatJava implements FormatterStepFactory { + @Parameter + private String groupArtifact; + + @Parameter + private String version; + + // https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#source + @Parameter(property = "maven.compiler.source") + private String sourceJdk = CleanthatJavaStep.defaultSourceJdk(); + + @Parameter + private List mutators = CleanthatJavaStep.defaultMutators(); + + @Parameter + private List excludedMutators = CleanthatJavaStep.defaultExcludedMutators(); + + @Parameter + private boolean includeDraft = CleanthatJavaStep.defaultIncludeDraft(); + + @Override + public FormatterStep newFormatterStep(FormatterStepConfig config) { + String groupArtifact = this.groupArtifact != null ? this.groupArtifact : CleanthatJavaStep.defaultGroupArtifact(); + String version = this.version != null ? this.version : CleanthatJavaStep.defaultVersion(); + + return CleanthatJavaStep.create(groupArtifact, version, sourceJdk, mutators, excludedMutators, includeDraft, config.getProvisioner()); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Eclipse.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Eclipse.java index 1e6508121e..8d5869cc8e 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Eclipse.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Eclipse.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,12 +16,15 @@ package com.diffplug.spotless.maven.java; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import org.apache.maven.plugins.annotations.Parameter; import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.extra.EclipseBasedStepBuilder; +import com.diffplug.spotless.extra.EquoBasedStepBuilder; +import com.diffplug.spotless.extra.P2Mirror; import com.diffplug.spotless.extra.java.EclipseJdtFormatterStep; import com.diffplug.spotless.maven.FormatterStepConfig; import com.diffplug.spotless.maven.FormatterStepFactory; @@ -34,14 +37,18 @@ public class Eclipse implements FormatterStepFactory { @Parameter private String version; + @Parameter + private List p2Mirrors = new ArrayList<>(); + @Override public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { - EclipseBasedStepBuilder eclipseConfig = EclipseJdtFormatterStep.createBuilder(stepConfig.getProvisioner()); + EquoBasedStepBuilder eclipseConfig = EclipseJdtFormatterStep.createBuilder(stepConfig.getProvisioner()); eclipseConfig.setVersion(version == null ? EclipseJdtFormatterStep.defaultVersion() : version); if (null != file) { File settingsFile = stepConfig.getFileLocator().locateFile(file); eclipseConfig.setPreferences(Arrays.asList(settingsFile)); } + eclipseConfig.setP2Mirrors(p2Mirrors); return eclipseConfig.build(); } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/GoogleJavaFormat.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/GoogleJavaFormat.java index 3597e84e25..d0a2b24954 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/GoogleJavaFormat.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/GoogleJavaFormat.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,12 +35,20 @@ public class GoogleJavaFormat implements FormatterStepFactory { @Parameter private Boolean reflowLongStrings; + @Parameter + private Boolean reorderImports; + + @Parameter + private Boolean formatJavadoc; + @Override public FormatterStep newFormatterStep(FormatterStepConfig config) { String groupArtifact = this.groupArtifact != null ? this.groupArtifact : GoogleJavaFormatStep.defaultGroupArtifact(); String version = this.version != null ? this.version : GoogleJavaFormatStep.defaultVersion(); String style = this.style != null ? this.style : GoogleJavaFormatStep.defaultStyle(); boolean reflowLongStrings = this.reflowLongStrings != null ? this.reflowLongStrings : GoogleJavaFormatStep.defaultReflowLongStrings(); - return GoogleJavaFormatStep.create(groupArtifact, version, style, config.getProvisioner(), reflowLongStrings); + boolean reorderImports = this.reorderImports != null ? this.reorderImports : GoogleJavaFormatStep.defaultReorderImports(); + boolean formatJavadoc = this.formatJavadoc != null ? this.formatJavadoc : GoogleJavaFormatStep.defaultFormatJavadoc(); + return GoogleJavaFormatStep.create(groupArtifact, version, style, config.getProvisioner(), reflowLongStrings, reorderImports, formatJavadoc); } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/ImportOrder.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/ImportOrder.java index 8476b83733..88f47a2a64 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/ImportOrder.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/ImportOrder.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package com.diffplug.spotless.maven.java; import java.io.File; +import java.util.HashSet; +import java.util.Set; import org.apache.maven.plugins.annotations.Parameter; @@ -34,17 +36,43 @@ public class ImportOrder implements FormatterStepFactory { @Parameter private boolean wildcardsLast = false; + /** + * Whether imports should be sorted based on semantics (i.e. sorted by package, + * class and then static member). This considers treatAsPackage and + * treatAsClass, and assumes default upper and lower case + * conventions otherwise. When turned off, imports are sorted purely + * lexicographically. + */ + @Parameter + private boolean semanticSort = false; + + /** + * The prefixes that should be treated as packages for + * semanticSort. Useful for upper case package names. + */ + @Parameter + private Set treatAsPackage = new HashSet<>(); + + /** + * The prefixes that should be treated as classes for + * semanticSort. Useful for lower case class names. + */ + @Parameter + private Set treatAsClass = new HashSet<>(); + @Override public FormatterStep newFormatterStep(FormatterStepConfig config) { if (file != null ^ order != null) { if (file != null) { File importsFile = config.getFileLocator().locateFile(file); - return ImportOrderStep.forJava().createFrom(wildcardsLast, importsFile); + return ImportOrderStep.forJava().createFrom(wildcardsLast, semanticSort, treatAsPackage, treatAsClass, + importsFile); } else { - return ImportOrderStep.forJava().createFrom(wildcardsLast, order.split(",", -1)); + return ImportOrderStep.forJava().createFrom(wildcardsLast, semanticSort, treatAsPackage, treatAsClass, + order.split(",", -1)); } } else if (file == null && order == null) { - return ImportOrderStep.forJava().createFrom(wildcardsLast); + return ImportOrderStep.forJava().createFrom(wildcardsLast, semanticSort, treatAsPackage, treatAsClass); } else { throw new IllegalArgumentException("Must specify exactly one of 'file' or 'order'."); } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Java.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Java.java index 4bd018d53c..b0692446b7 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Java.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/Java.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,18 @@ */ package com.diffplug.spotless.maven.java; +import static java.util.stream.Collectors.toSet; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Set; +import java.util.stream.Stream; + +import org.apache.maven.model.Build; +import org.apache.maven.project.MavenProject; -import com.diffplug.common.collect.ImmutableSet; +import com.diffplug.spotless.generic.LicenseHeaderStep; import com.diffplug.spotless.maven.FormatterFactory; import com.diffplug.spotless.maven.generic.LicenseHeader; @@ -29,12 +38,17 @@ */ public class Java extends FormatterFactory { - private static final Set DEFAULT_INCLUDES = ImmutableSet.of("src/main/java/**/*.java", "src/test/java/**/*.java"); - private static final String LICENSE_HEADER_DELIMITER = "package "; + private static final String LICENSE_HEADER_DELIMITER = LicenseHeaderStep.DEFAULT_JAVA_HEADER_DELIMITER; @Override - public Set defaultIncludes() { - return DEFAULT_INCLUDES; + public Set defaultIncludes(MavenProject project) { + Path projectDir = project.getBasedir().toPath(); + Build build = project.getBuild(); + return Stream.of(build.getSourceDirectory(), build.getTestSourceDirectory()) + .map(Paths::get) + .map(projectDir::relativize) + .map(Java::fileMask) + .collect(toSet()); } @Override @@ -65,4 +79,16 @@ public void addRemoveUnusedImports(RemoveUnusedImports removeUnusedImports) { public void addFormatAnnotations(FormatAnnotations formatAnnotations) { addStepFactory(formatAnnotations); } + + public void addCleanthat(CleanthatJava cleanthat) { + addStepFactory(cleanthat); + } + + private static String fileMask(Path path) { + String dir = path.toString(); + if (!dir.endsWith(File.separator)) { + dir += File.separator; + } + return dir + "**" + File.separator + "*.java"; + } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/PalantirJavaFormat.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/PalantirJavaFormat.java index df58764c1d..674238d2e8 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/PalantirJavaFormat.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/PalantirJavaFormat.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,9 +27,17 @@ public class PalantirJavaFormat implements FormatterStepFactory { @Parameter private String version; + @Parameter + private String style; + + @Parameter + private Boolean formatJavadoc; + @Override public FormatterStep newFormatterStep(FormatterStepConfig config) { String version = this.version != null ? this.version : PalantirJavaFormatStep.defaultVersion(); - return PalantirJavaFormatStep.create(version, config.getProvisioner()); + String style = this.style != null ? this.style : PalantirJavaFormatStep.defaultStyle(); + boolean formatJavadoc = this.formatJavadoc != null ? this.formatJavadoc : PalantirJavaFormatStep.defaultFormatJavadoc(); + return PalantirJavaFormatStep.create(version, style, formatJavadoc, config.getProvisioner()); } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/RemoveUnusedImports.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/RemoveUnusedImports.java index 54fd30a167..85c6bbb8fe 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/RemoveUnusedImports.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/java/RemoveUnusedImports.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,8 @@ */ package com.diffplug.spotless.maven.java; +import org.apache.maven.plugins.annotations.Parameter; + import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.java.RemoveUnusedImportsStep; import com.diffplug.spotless.maven.FormatterStepConfig; @@ -22,8 +24,11 @@ public class RemoveUnusedImports implements FormatterStepFactory { + @Parameter + private String engine = RemoveUnusedImportsStep.defaultFormatter(); + @Override public FormatterStep newFormatterStep(FormatterStepConfig config) { - return RemoveUnusedImportsStep.create(config.getProvisioner()); + return RemoveUnusedImportsStep.create(engine, config.getProvisioner()); } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/AbstractEslint.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/AbstractEslint.java new file mode 100644 index 0000000000..ad113de833 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/AbstractEslint.java @@ -0,0 +1,82 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.javascript; + +import java.io.File; +import java.util.Map; +import java.util.Properties; +import java.util.TreeMap; + +import org.apache.maven.plugins.annotations.Parameter; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.maven.FormatterStepConfig; +import com.diffplug.spotless.maven.npm.AbstractNpmFormatterStepFactory; +import com.diffplug.spotless.npm.EslintConfig; +import com.diffplug.spotless.npm.EslintFormatterStep; +import com.diffplug.spotless.npm.NpmPathResolver; + +public abstract class AbstractEslint extends AbstractNpmFormatterStepFactory { + + public static final String ERROR_MESSAGE_ONLY_ONE_CONFIG = "must specify exactly one eslintVersion, devDependencies or devDependencyProperties"; + + @Parameter + protected String configFile; + + @Parameter + protected String configJs; + + @Parameter + protected String eslintVersion; + + @Parameter + protected Map devDependencies; + + @Parameter + protected Properties devDependencyProperties; + + @Override + public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { + // check if config is only setup in one way + if (moreThanOneNonNull(this.eslintVersion, this.devDependencies, this.devDependencyProperties)) { + throw onlyOneConfig(); + } + + Map devDependencies = new TreeMap<>(); + if (this.devDependencies != null) { + devDependencies.putAll(this.devDependencies); + } else if (this.devDependencyProperties != null) { + devDependencies.putAll(propertiesAsMap(this.devDependencyProperties)); + } else { + Map defaultDependencies = createDefaultDependencies(); + devDependencies.putAll(defaultDependencies); + } + + File buildDir = buildDir(stepConfig); + File baseDir = baseDir(stepConfig); + File cacheDir = cacheDir(stepConfig); + NpmPathResolver npmPathResolver = npmPathResolver(stepConfig); + return EslintFormatterStep.create(devDependencies, stepConfig.getProvisioner(), baseDir, buildDir, cacheDir, npmPathResolver, eslintConfig(stepConfig)); + } + + private static IllegalArgumentException onlyOneConfig() { + return new IllegalArgumentException(ERROR_MESSAGE_ONLY_ONE_CONFIG); + } + + protected abstract EslintConfig eslintConfig(FormatterStepConfig stepConfig); + + protected abstract Map createDefaultDependencies(); +} diff --git a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/sse/package-info.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/BiomeJs.java similarity index 59% rename from _ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/sse/package-info.java rename to plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/BiomeJs.java index 931b7e085e..4d52f4cc80 100644 --- a/_ext/eclipse-wtp/src/main/java/com/diffplug/spotless/extra/eclipse/wtp/sse/package-info.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/BiomeJs.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,8 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/** Eclipse WTP Structured Source Editing (SEE) formatter helper */ -@ParametersAreNonnullByDefault -package com.diffplug.spotless.extra.eclipse.wtp.sse; +package com.diffplug.spotless.maven.javascript; -import javax.annotation.ParametersAreNonnullByDefault; +import com.diffplug.spotless.maven.rome.AbstractRome; +import com.diffplug.spotless.rome.BiomeFlavor; + +/** + * Biome formatter step for JavaScript. + */ +public class BiomeJs extends AbstractRome { + public BiomeJs() { + super(BiomeFlavor.BIOME); + } + + @Override + protected String getLanguage() { + return "js?"; + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/EslintJs.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/EslintJs.java new file mode 100644 index 0000000000..483d38ae1e --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/EslintJs.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.javascript; + +import java.util.Map; + +import com.diffplug.spotless.maven.FormatterStepConfig; +import com.diffplug.spotless.npm.EslintConfig; +import com.diffplug.spotless.npm.EslintFormatterStep; + +public class EslintJs extends AbstractEslint { + + protected EslintConfig eslintConfig(FormatterStepConfig stepConfig) { + return new EslintConfig(this.configFile != null ? stepConfig.getFileLocator().locateFile(this.configFile) : null, this.configJs); + } + + protected Map createDefaultDependencies() { + return this.eslintVersion == null ? EslintFormatterStep.defaultDevDependencies() : EslintFormatterStep.defaultDevDependenciesWithEslint(this.eslintVersion); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/Javascript.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/Javascript.java new file mode 100644 index 0000000000..3f0a3f959c --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/Javascript.java @@ -0,0 +1,53 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.javascript; + +import java.util.Collections; +import java.util.Set; + +import org.apache.maven.project.MavenProject; + +import com.diffplug.spotless.maven.FormatterFactory; + +/** + * A {@link FormatterFactory} implementation that corresponds to {@code ...} configuration element. + *

+ * It defines a formatter for typescript source files. + */ +public class Javascript extends FormatterFactory { + @Override + public Set defaultIncludes(MavenProject project) { + return Collections.emptySet(); + } + + @Override + public String licenseHeaderDelimiter() { + return null; + } + + public void addEslint(EslintJs eslint) { + addStepFactory(eslint); + } + + public void addBiome(BiomeJs biome) { + addStepFactory(biome); + } + + @Deprecated + public void addRome(RomeJs rome) { + addStepFactory(rome); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java new file mode 100644 index 0000000000..405809c7a7 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.javascript; + +import com.diffplug.spotless.maven.rome.AbstractRome; +import com.diffplug.spotless.rome.BiomeFlavor; + +/** + * Rome formatter step for JavaScript. + * @deprecated Rome has transitioned to Biome. This will be removed shortly. + */ +@Deprecated +public class RomeJs extends AbstractRome { + public RomeJs() { + super(BiomeFlavor.ROME); + } + + @Override + protected String getLanguage() { + return "js?"; + } +} diff --git a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/package-info.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/BiomeJson.java similarity index 60% rename from _ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/package-info.java rename to plugin-maven/src/main/java/com/diffplug/spotless/maven/json/BiomeJson.java index b82e4d40ca..a8d329d725 100644 --- a/_ext/eclipse-base/src/main/java/com/diffplug/spotless/extra/eclipse/base/package-info.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/BiomeJson.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,12 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package com.diffplug.spotless.maven.json; + +import com.diffplug.spotless.maven.rome.AbstractRome; +import com.diffplug.spotless.rome.BiomeFlavor; + /** - * Common Spotless Eclipse Equinox setup classes. - * Please refer to the {@code eclipse-base} {@code README.md} for - * usage instructions. + * Biome formatter step for JSON. */ -@ParametersAreNonnullByDefault -package com.diffplug.spotless.extra.eclipse.base; +public class BiomeJson extends AbstractRome { + public BiomeJson() { + super(BiomeFlavor.BIOME); + } -import javax.annotation.ParametersAreNonnullByDefault; + @Override + protected String getLanguage() { + return "json"; + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Gson.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Gson.java new file mode 100644 index 0000000000..fb5a38e890 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Gson.java @@ -0,0 +1,46 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.json; + +import org.apache.maven.plugins.annotations.Parameter; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.json.gson.GsonConfig; +import com.diffplug.spotless.json.gson.GsonStep; +import com.diffplug.spotless.maven.FormatterStepConfig; +import com.diffplug.spotless.maven.FormatterStepFactory; + +public class Gson implements FormatterStepFactory { + private static final String DEFAULT_GSON_VERSION = "2.10.1"; + + @Parameter + int indentSpaces = Json.DEFAULT_INDENTATION; + + @Parameter + boolean sortByKeys = false; + + @Parameter + boolean escapeHtml = false; + + @Parameter + String version = DEFAULT_GSON_VERSION; + + @Override + public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { + int indentSpaces = this.indentSpaces; + return GsonStep.create(new GsonConfig(sortByKeys, escapeHtml, indentSpaces, version), stepConfig.getProvisioner()); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/JacksonJson.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/JacksonJson.java new file mode 100644 index 0000000000..ecf925ddbe --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/JacksonJson.java @@ -0,0 +1,58 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.json; + +import java.util.Collections; +import java.util.Map; + +import org.apache.maven.plugins.annotations.Parameter; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.json.JacksonJsonConfig; +import com.diffplug.spotless.json.JacksonJsonStep; +import com.diffplug.spotless.maven.FormatterFactory; +import com.diffplug.spotless.maven.FormatterStepConfig; +import com.diffplug.spotless.maven.FormatterStepFactory; + +/** + * A {@link FormatterFactory} implementation that corresponds to {@code ...} configuration element. + */ +public class JacksonJson implements FormatterStepFactory { + + @Parameter + private String version = JacksonJsonStep.defaultVersion(); + + @Parameter + private boolean spaceBeforeSeparator = new JacksonJsonConfig().isSpaceBeforeSeparator(); + + @Parameter + private Map features = Collections.emptyMap(); + + @Parameter + private Map jsonFeatures = Collections.emptyMap(); + + @Override + public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { + JacksonJsonConfig jacksonConfig = new JacksonJsonConfig(); + + jacksonConfig.appendFeatureToToggle(features); + jacksonConfig.appendJsonFeatureToToggle(jsonFeatures); + jacksonConfig.setSpaceBeforeSeparator(spaceBeforeSeparator); + + return JacksonJsonStep + .create(jacksonConfig, version, stepConfig.getProvisioner()); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Json.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Json.java new file mode 100644 index 0000000000..8baf379a5e --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Json.java @@ -0,0 +1,65 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.json; + +import java.util.Collections; +import java.util.Set; + +import org.apache.maven.project.MavenProject; + +import com.diffplug.spotless.maven.FormatterFactory; + +/** + * A {@link FormatterFactory} implementation that corresponds to {@code ...} configuration element. + */ +public class Json extends FormatterFactory { + public static final int DEFAULT_INDENTATION = 4; + + @Override + public Set defaultIncludes(MavenProject project) { + return Collections.emptySet(); + } + + @Override + public String licenseHeaderDelimiter() { + return null; + } + + public void addSimple(Simple simple) { + addStepFactory(simple); + } + + public void addGson(Gson gson) { + addStepFactory(gson); + } + + public void addJackson(JacksonJson jackson) { + addStepFactory(jackson); + } + + public void addBiome(BiomeJson biome) { + addStepFactory(biome); + } + + @Deprecated + public void addRome(RomeJson rome) { + addStepFactory(rome); + } + + public void addJsonPatch(JsonPatch jsonPatch) { + addStepFactory(jsonPatch); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/JsonPatch.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/JsonPatch.java new file mode 100644 index 0000000000..a822ad09e2 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/JsonPatch.java @@ -0,0 +1,41 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.json; + +import org.apache.maven.plugins.annotations.Parameter; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.json.JsonPatchStep; +import com.diffplug.spotless.maven.FormatterStepConfig; +import com.diffplug.spotless.maven.FormatterStepFactory; + +/** + * A {@link FormatterStepFactory} implementation that corresponds to {@code ...} configuration element. + */ +public class JsonPatch implements FormatterStepFactory { + private static final String DEFAULT_ZJSONPATCH_VERSION = "0.4.14"; + + @Parameter + String zjsonPatchVersion = DEFAULT_ZJSONPATCH_VERSION; + + @Parameter + String patch; + + @Override + public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { + return JsonPatchStep.create(zjsonPatchVersion, patch, stepConfig.getProvisioner()); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/RomeJson.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/RomeJson.java new file mode 100644 index 0000000000..2959d5a055 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/RomeJson.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.json; + +import com.diffplug.spotless.maven.rome.AbstractRome; +import com.diffplug.spotless.rome.BiomeFlavor; + +/** + * Rome formatter step for JSON. + * @deprecated Rome has transitioned to Biome. This will be removed shortly. + */ +@Deprecated +public class RomeJson extends AbstractRome { + public RomeJson() { + super(BiomeFlavor.ROME); + } + + @Override + protected String getLanguage() { + return "json"; + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Simple.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Simple.java new file mode 100644 index 0000000000..f1cc114da2 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Simple.java @@ -0,0 +1,35 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.json; + +import org.apache.maven.plugins.annotations.Parameter; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.json.JsonSimpleStep; +import com.diffplug.spotless.maven.FormatterStepConfig; +import com.diffplug.spotless.maven.FormatterStepFactory; + +public class Simple implements FormatterStepFactory { + + @Parameter + int indentSpaces = Json.DEFAULT_INDENTATION; + + @Override + public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { + int indentSpaces = this.indentSpaces; + return JsonSimpleStep.create(indentSpaces, stepConfig.getProvisioner()); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/kotlin/Kotlin.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/kotlin/Kotlin.java index cfb0aad39e..18eb13773d 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/kotlin/Kotlin.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/kotlin/Kotlin.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,8 @@ import java.util.Set; +import org.apache.maven.project.MavenProject; + import com.diffplug.common.collect.ImmutableSet; import com.diffplug.spotless.maven.FormatterFactory; @@ -27,7 +29,7 @@ public class Kotlin extends FormatterFactory { private static final Set DEFAULT_INCLUDES = ImmutableSet.of("src/main/kotlin/**/*.kt", "src/test/kotlin/**/*.kt"); @Override - public Set defaultIncludes() { + public Set defaultIncludes(MavenProject project) { return DEFAULT_INCLUDES; } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/kotlin/Ktlint.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/kotlin/Ktlint.java index 796523d315..2459a52828 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/kotlin/Ktlint.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/kotlin/Ktlint.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,21 +15,48 @@ */ package com.diffplug.spotless.maven.kotlin; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import org.apache.maven.plugins.annotations.Parameter; +import com.diffplug.spotless.FileSignature; import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.ThrowingEx; import com.diffplug.spotless.kotlin.KtLintStep; import com.diffplug.spotless.maven.FormatterStepConfig; import com.diffplug.spotless.maven.FormatterStepFactory; public class Ktlint implements FormatterStepFactory { - @Parameter private String version; + @Parameter + private String editorConfigPath; + @Parameter + private Map editorConfigOverride; + @Parameter + private List customRuleSets; @Override - public FormatterStep newFormatterStep(FormatterStepConfig config) { + public FormatterStep newFormatterStep(final FormatterStepConfig stepConfig) { String ktlintVersion = version != null ? version : KtLintStep.defaultVersion(); - return KtLintStep.create(ktlintVersion, config.getProvisioner()); + FileSignature configPath = null; + if (editorConfigPath != null) { + configPath = ThrowingEx.get(() -> FileSignature.signAsList(stepConfig.getFileLocator().locateFile(editorConfigPath))); + } + if (editorConfigOverride == null) { + editorConfigOverride = new HashMap<>(); + } + if (customRuleSets == null) { + customRuleSets = Collections.emptyList(); + } + return KtLintStep.create( + ktlintVersion, + stepConfig.getProvisioner(), + configPath, + editorConfigOverride, + customRuleSets); } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/markdown/Markdown.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/markdown/Markdown.java index 2ba9b1f58f..0941beb76a 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/markdown/Markdown.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/markdown/Markdown.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ import java.util.Collections; import java.util.Set; +import org.apache.maven.project.MavenProject; + import com.diffplug.spotless.maven.FormatterFactory; import com.diffplug.spotless.maven.generic.LicenseHeader; @@ -29,7 +31,7 @@ */ public class Markdown extends FormatterFactory { @Override - public Set defaultIncludes() { + public Set defaultIncludes(MavenProject project) { return Collections.emptySet(); } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/npm/AbstractNpmFormatterStepFactory.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/npm/AbstractNpmFormatterStepFactory.java new file mode 100644 index 0000000000..b0645c151e --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/npm/AbstractNpmFormatterStepFactory.java @@ -0,0 +1,101 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.npm; + +import java.io.File; +import java.nio.file.Paths; +import java.util.AbstractMap; +import java.util.Arrays; +import java.util.Collections; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.stream.Collectors; + +import org.apache.maven.plugins.annotations.Parameter; + +import com.diffplug.spotless.maven.FormatterStepConfig; +import com.diffplug.spotless.maven.FormatterStepFactory; +import com.diffplug.spotless.npm.NpmPathResolver; + +public abstract class AbstractNpmFormatterStepFactory implements FormatterStepFactory { + + public static final String SPOTLESS_NPM_INSTALL_CACHE_DEFAULT_NAME = "spotless-npm-install-cache"; + + @Parameter + private String npmExecutable; + + @Parameter + private String nodeExecutable; + + @Parameter + private String npmrc; + + @Parameter + private String npmInstallCache; + + protected File npm(FormatterStepConfig stepConfig) { + File npm = npmExecutable != null ? stepConfig.getFileLocator().locateFile(npmExecutable) : null; + return npm; + } + + protected File node(FormatterStepConfig stepConfig) { + File node = nodeExecutable != null ? stepConfig.getFileLocator().locateFile(nodeExecutable) : null; + return node; + } + + protected File npmrc(FormatterStepConfig stepConfig) { + File npmrc = this.npmrc != null ? stepConfig.getFileLocator().locateFile(this.npmrc) : null; + return npmrc; + } + + protected File buildDir(FormatterStepConfig stepConfig) { + return stepConfig.getFileLocator().getBuildDir(); + } + + protected File cacheDir(FormatterStepConfig stepConfig) { + if (this.npmInstallCache == null) { + return null; + } + if ("true".equals(this.npmInstallCache.toLowerCase(Locale.ROOT))) { + return new File(buildDir(stepConfig), SPOTLESS_NPM_INSTALL_CACHE_DEFAULT_NAME); + } + return Paths.get(this.npmInstallCache).toFile(); + } + + protected File baseDir(FormatterStepConfig stepConfig) { + return stepConfig.getFileLocator().getBaseDir(); + } + + protected NpmPathResolver npmPathResolver(FormatterStepConfig stepConfig) { + return new NpmPathResolver(npm(stepConfig), node(stepConfig), npmrc(stepConfig), Collections.singletonList(baseDir(stepConfig))); + } + + protected boolean moreThanOneNonNull(Object... objects) { + return Arrays.stream(objects) + .filter(Objects::nonNull) + .filter(o -> !(o instanceof String) || !((String) o).isEmpty()) // if it is a string, it should not be empty + .count() > 1; + } + + protected Map propertiesAsMap(Properties devDependencyProperties) { + return devDependencyProperties.stringPropertyNames() + .stream() + .map(name -> new AbstractMap.SimpleEntry<>(name, devDependencyProperties.getProperty(name))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/pom/Pom.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/pom/Pom.java index f083a17c89..9a4d3eea06 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/pom/Pom.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/pom/Pom.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,8 @@ import java.util.Set; +import org.apache.maven.project.MavenProject; + import com.diffplug.common.collect.ImmutableSet; import com.diffplug.spotless.maven.FormatterFactory; import com.diffplug.spotless.maven.generic.LicenseHeader; @@ -29,7 +31,7 @@ */ public class Pom extends FormatterFactory { @Override - public Set defaultIncludes() { + public Set defaultIncludes(MavenProject project) { return ImmutableSet.of("pom.xml"); } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/pom/SortPom.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/pom/SortPom.java index f4fe8cb96b..8620427f79 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/pom/SortPom.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/pom/SortPom.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,9 @@ public class SortPom implements FormatterStepFactory { private final SortPomCfg defaultValues = new SortPomCfg(); + @Parameter + String version = defaultValues.version; + @Parameter String encoding = defaultValues.encoding; @@ -59,6 +62,9 @@ public class SortPom implements FormatterStepFactory { @Parameter String sortDependencies = defaultValues.sortDependencies; + @Parameter + String sortDependencyManagement = defaultValues.sortDependencyManagement; + @Parameter String sortDependencyExclusions = defaultValues.sortDependencyExclusions; @@ -77,6 +83,7 @@ public class SortPom implements FormatterStepFactory { @Override public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { SortPomCfg cfg = new SortPomCfg(); + cfg.version = version; cfg.encoding = encoding; cfg.lineSeparator = lineSeparator; cfg.expandEmptyElements = expandEmptyElements; @@ -88,6 +95,7 @@ public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { cfg.predefinedSortOrder = predefinedSortOrder; cfg.sortOrderFile = sortOrderFile; cfg.sortDependencies = sortDependencies; + cfg.sortDependencyManagement = sortDependencyManagement; cfg.sortDependencyExclusions = sortDependencyExclusions; cfg.sortPlugins = sortPlugins; cfg.sortProperties = sortProperties; diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/python/Python.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/python/Python.java index 09443f070b..df7348c16c 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/python/Python.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/python/Python.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ import java.util.Collections; import java.util.Set; +import org.apache.maven.project.MavenProject; + import com.diffplug.spotless.maven.FormatterFactory; import com.diffplug.spotless.maven.generic.LicenseHeader; @@ -30,7 +32,7 @@ public class Python extends FormatterFactory { @Override - public Set defaultIncludes() { + public Set defaultIncludes(MavenProject project) { return Collections.emptySet(); } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/rome/AbstractRome.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/rome/AbstractRome.java new file mode 100644 index 0000000000..da66bf37a0 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/rome/AbstractRome.java @@ -0,0 +1,194 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.rome; + +import java.nio.file.Paths; + +import org.apache.maven.plugins.annotations.Parameter; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.maven.FormatterStepConfig; +import com.diffplug.spotless.maven.FormatterStepFactory; +import com.diffplug.spotless.rome.BiomeFlavor; +import com.diffplug.spotless.rome.RomeStep; + +/** + * Factory for creating the Biome formatter step that can format format code in + * various types of language with Biome. Currently Biome supports JavaScript, + * TypeScript, JSX, TSX, and JSON. See also https://github.com/biomejs/biome. It + * delegates to the Biome CLI executable. + */ +public abstract class AbstractRome implements FormatterStepFactory { + /** Biome flavor to use. */ + private BiomeFlavor flavor; + + protected AbstractRome(BiomeFlavor flavor) { + this.flavor = flavor; + } + + /** + * Optional path to the directory with configuration file for Biome. The file + * must be named {@code biome.json}. When none is given, the default + * configuration is used. If this is a relative path, it is resolved against the + * project's base directory. + */ + @Parameter + private String configPath; + + /** + * Optional directory where the downloaded Biome executable is placed. If this + * is a relative path, it is resolved against the project's base directory. + * Defaults to + * ~/.m2/repository/com/diffplug/spotless/spotless-data/biome. + *

+ * You can use an expression like ${user.home}/biome if you want to + * use the home directory, or ${project.build.directory if you want + * to use the target directory of the current project. + */ + @Parameter + private String downloadDir; + + /** + * Optional path to the Biome executable. Either a version or a + * pathToExe should be specified. When not given, an attempt is + * made to download the executable for the given version from the network. When + * given, the executable is used and the version parameter is + * ignored. + *

+ * When an absolute path is given, that path is used as-is. When a relative path + * is given, it is resolved against the project's base directory. When only a + * file name (i.e. without any slashes or back slash path separators such as + * biome) is given, this is interpreted as the name of a command + * with executable that is in your path environment variable. Use + * ./executable-name if you want to use an executable in the + * project's base directory. + */ + @Parameter + private String pathToExe; + + /** + * Biome version to download, applies only when no pathToExe is + * specified explicitly. Either a version or a + * pathToExe should be specified. When not given, a default known + * version is used. For stable builds, it is recommended that you always set the + * version explicitly. This parameter is ignored when you specify a + * pathToExe explicitly. + */ + @Parameter + private String version; + + @Override + public FormatterStep newFormatterStep(FormatterStepConfig config) { + var builder = newBuilder(config); + if (configPath != null) { + var resolvedConfigFile = resolveConfigFile(config); + builder.withConfigPath(resolvedConfigFile); + } + if (getLanguage() != null) { + builder.withLanguage(getLanguage()); + } + return builder.create(); + } + + /** + * Gets the language (syntax) of the input files to format. When + * null or the empty string, the language is detected automatically + * from the file name. Currently the following languages are supported by Biome: + *

    + *
  • js (JavaScript)
  • + *
  • jsx (JavaScript + JSX)
  • + *
  • js? (JavaScript or JavaScript + JSX, depending on the file + * extension)
  • + *
  • ts (TypeScript)
  • + *
  • tsx (TypeScript + JSX)
  • + *
  • ts? (TypeScript or TypeScript + JSX, depending on the file + * extension)
  • + *
  • json (JSON)
  • + *
+ * + * @return The language of the input files. + */ + protected abstract String getLanguage(); + + /** + * A new builder for configuring a Biome step that either downloads the Biome + * executable with the given version from the network, or uses the executable + * from the given path. + * + * @param config Configuration from the Maven Mojo execution with details about + * the currently executed project. + * @return A builder for a Biome step. + */ + private RomeStep newBuilder(FormatterStepConfig config) { + if (pathToExe != null) { + var resolvedExePath = resolveExePath(config); + return RomeStep.withExePath(flavor, resolvedExePath); + } else { + var downloadDir = resolveDownloadDir(config); + return RomeStep.withExeDownload(flavor, version, downloadDir); + } + } + + /** + * Resolves the path to the configuration file for Biome. Relative paths are + * resolved against the project's base directory. + * + * @param config Configuration from the Maven Mojo execution with details about + * the currently executed project. + * @return The resolved path to the configuration file. + */ + private String resolveConfigFile(FormatterStepConfig config) { + return config.getFileLocator().getBaseDir().toPath().resolve(configPath).toAbsolutePath().toString(); + } + + /** + * Resolves the path to the Biome executable. When the path is only a file name, + * do not perform any resolution and interpret it as a command that must be on + * the user's path. Otherwise resolve the executable path against the project's + * base directory. + * + * @param config Configuration from the Maven Mojo execution with details about + * the currently executed project. + * @return The resolved path to the Biome executable. + */ + private String resolveExePath(FormatterStepConfig config) { + var path = Paths.get(pathToExe); + if (path.getNameCount() == 1) { + return path.toString(); + } else { + return config.getFileLocator().getBaseDir().toPath().resolve(path).toAbsolutePath().toString(); + } + } + + /** + * Resolves the directory to use for storing downloaded Biome executable. When a + * {@link #downloadDir} is given, use that directory, resolved against the + * current project's directory. Otherwise, use the biome sub folder + * in the shared data directory. + * + * @param config Configuration for this step. + * @return The download directory for the Biome executable. + */ + private String resolveDownloadDir(FormatterStepConfig config) { + final var fileLocator = config.getFileLocator(); + if (downloadDir != null && !downloadDir.isBlank()) { + return fileLocator.getBaseDir().toPath().resolve(downloadDir).toAbsolutePath().toString(); + } else { + return fileLocator.getDataDir().toPath().resolve(flavor.shortName()).toString(); + } + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/scala/Scala.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/scala/Scala.java index 423ca71930..116d89263c 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/scala/Scala.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/scala/Scala.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,10 @@ import java.util.Set; +import org.apache.maven.project.MavenProject; + import com.diffplug.common.collect.ImmutableSet; +import com.diffplug.spotless.generic.LicenseHeaderStep; import com.diffplug.spotless.maven.FormatterFactory; import com.diffplug.spotless.maven.generic.LicenseHeader; @@ -31,10 +34,10 @@ public class Scala extends FormatterFactory { private static final Set DEFAULT_INCLUDES = ImmutableSet.of("src/main/scala/**/*.scala", "src/test/scala/**/*.scala", "src/main/scala/**/*.sc", "src/test/scala/**/*.sc"); - private static final String LICENSE_HEADER_DELIMITER = "package "; + private static final String LICENSE_HEADER_DELIMITER = LicenseHeaderStep.DEFAULT_JAVA_HEADER_DELIMITER; @Override - public Set defaultIncludes() { + public Set defaultIncludes(MavenProject project) { return DEFAULT_INCLUDES; } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/shell/Shell.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/shell/Shell.java new file mode 100644 index 0000000000..6285bfc410 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/shell/Shell.java @@ -0,0 +1,46 @@ +/* + * Copyright 2024 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.shell; + +import java.util.Collections; +import java.util.Set; + +import org.apache.maven.project.MavenProject; + +import com.diffplug.spotless.maven.FormatterFactory; +import com.diffplug.spotless.maven.generic.LicenseHeader; + +/** + * A {@link FormatterFactory} implementation that corresponds to {@code ...} configuration element. + *

+ * It defines a formatter for shell source files that can execute both language agnostic (e.g. {@link LicenseHeader}) + * and shell-specific (e.g. {@link Shfmt}) steps. + */ +public class Shell extends FormatterFactory { + @Override + public Set defaultIncludes(MavenProject project) { + return Collections.emptySet(); + } + + @Override + public String licenseHeaderDelimiter() { + return null; + } + + public void addShfmt(Shfmt shfmt) { + addStepFactory(shfmt); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/shell/Shfmt.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/shell/Shfmt.java new file mode 100644 index 0000000000..09b83240b0 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/shell/Shfmt.java @@ -0,0 +1,41 @@ +/* + * Copyright 2024 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.shell; + +import org.apache.maven.plugins.annotations.Parameter; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.maven.FormatterStepConfig; +import com.diffplug.spotless.maven.FormatterStepFactory; +import com.diffplug.spotless.shell.ShfmtStep; + +public class Shfmt implements FormatterStepFactory { + + @Parameter + private String version; + + @Parameter + private String pathToExe; + + @Override + public FormatterStep newFormatterStep(FormatterStepConfig config) { + ShfmtStep shfmt = ShfmtStep.withVersion(version == null ? ShfmtStep.defaultVersion() : version); + if (pathToExe != null) { + shfmt = shfmt.withPathToExe(pathToExe); + } + return shfmt.create(); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/sql/Sql.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/sql/Sql.java index c49ac074d9..64ebcb55d7 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/sql/Sql.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/sql/Sql.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ import java.util.Collections; import java.util.Set; +import org.apache.maven.project.MavenProject; + import com.diffplug.spotless.maven.FormatterFactory; /** @@ -27,7 +29,7 @@ */ public class Sql extends FormatterFactory { @Override - public Set defaultIncludes() { + public Set defaultIncludes(MavenProject project) { return Collections.emptySet(); } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/BiomeTs.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/BiomeTs.java new file mode 100644 index 0000000000..a99e633a42 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/BiomeTs.java @@ -0,0 +1,33 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.typescript; + +import com.diffplug.spotless.maven.rome.AbstractRome; +import com.diffplug.spotless.rome.BiomeFlavor; + +/** + * Biome formatter step for TypeScript. + */ +public class BiomeTs extends AbstractRome { + public BiomeTs() { + super(BiomeFlavor.BIOME); + } + + @Override + protected String getLanguage() { + return "ts?"; + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/EslintTs.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/EslintTs.java new file mode 100644 index 0000000000..dc43185dcd --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/EslintTs.java @@ -0,0 +1,45 @@ +/* + * Copyright 2022-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.typescript; + +import java.util.Map; + +import org.apache.maven.plugins.annotations.Parameter; + +import com.diffplug.spotless.maven.FormatterStepConfig; +import com.diffplug.spotless.maven.javascript.AbstractEslint; +import com.diffplug.spotless.npm.EslintConfig; +import com.diffplug.spotless.npm.EslintFormatterStep; +import com.diffplug.spotless.npm.EslintTypescriptConfig; + +public class EslintTs extends AbstractEslint { + + @Parameter + private String tsconfigFile; + + @Override + protected EslintConfig eslintConfig(FormatterStepConfig stepConfig) { + return new EslintTypescriptConfig( + configFile != null ? stepConfig.getFileLocator().locateFile(configFile) : null, + configJs, + tsconfigFile != null ? stepConfig.getFileLocator().locateFile(tsconfigFile) : null); + } + + @Override + protected Map createDefaultDependencies() { + return this.eslintVersion == null ? EslintFormatterStep.defaultDevDependenciesForTypescript() : EslintFormatterStep.defaultDevDependenciesTypescriptWithEslint(this.eslintVersion); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/RomeTs.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/RomeTs.java new file mode 100644 index 0000000000..f6ea80581d --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/RomeTs.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.typescript; + +import com.diffplug.spotless.maven.rome.AbstractRome; +import com.diffplug.spotless.rome.BiomeFlavor; + +/** + * Rome formatter step for TypeScript. + * @deprecated Rome has transitioned to Biome. This will be removed shortly. + */ +@Deprecated +public class RomeTs extends AbstractRome { + public RomeTs() { + super(BiomeFlavor.ROME); + } + + @Override + protected String getLanguage() { + return "ts?"; + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/Tsfmt.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/Tsfmt.java index 9ebb9ac503..396c688635 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/Tsfmt.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/Tsfmt.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,13 +23,13 @@ import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.maven.FormatterStepConfig; -import com.diffplug.spotless.maven.FormatterStepFactory; +import com.diffplug.spotless.maven.npm.AbstractNpmFormatterStepFactory; import com.diffplug.spotless.npm.NpmPathResolver; import com.diffplug.spotless.npm.TsConfigFileType; import com.diffplug.spotless.npm.TsFmtFormatterStep; import com.diffplug.spotless.npm.TypedTsFmtConfigFile; -public class Tsfmt implements FormatterStepFactory { +public class Tsfmt extends AbstractNpmFormatterStepFactory { @Parameter private String tslintFile; @@ -52,12 +52,6 @@ public class Tsfmt implements FormatterStepFactory { @Parameter private String tslintVersion; - @Parameter - private String npmExecutable; - - @Parameter - private String npmrc; - @Parameter private Map config; @@ -74,10 +68,6 @@ public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { devDependencies.put("tslint", tslintVersion); } - File npm = npmExecutable != null ? stepConfig.getFileLocator().locateFile(npmExecutable) : null; - - File npmrcFile = npmrc != null ? stepConfig.getFileLocator().locateFile(npmrc) : null; - TypedTsFmtConfigFile configFile; Map configInline; // check that there is only 1 config file or inline config @@ -119,9 +109,11 @@ public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { throw onlyOneConfig(); } - File buildDir = stepConfig.getFileLocator().getBuildDir(); - NpmPathResolver npmPathResolver = new NpmPathResolver(npm, npmrcFile, stepConfig.getFileLocator().getBaseDir()); - return TsFmtFormatterStep.create(devDependencies, stepConfig.getProvisioner(), buildDir, npmPathResolver, configFile, configInline); + File buildDir = buildDir(stepConfig); + File baseDir = baseDir(stepConfig); + File cacheDir = cacheDir(stepConfig); + NpmPathResolver npmPathResolver = npmPathResolver(stepConfig); + return TsFmtFormatterStep.create(devDependencies, stepConfig.getProvisioner(), baseDir, buildDir, cacheDir, npmPathResolver, configFile, configInline); } private static IllegalArgumentException onlyOneConfig() { diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/Typescript.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/Typescript.java index da6e6ddd91..af25b8c773 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/Typescript.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/Typescript.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,16 +18,18 @@ import java.util.Collections; import java.util.Set; +import org.apache.maven.project.MavenProject; + import com.diffplug.spotless.maven.FormatterFactory; /** * A {@link FormatterFactory} implementation that corresponds to {@code ...} configuration element. *

- * It defines a formatter for typescript source files. + * It defines formatters for typescript source files. */ public class Typescript extends FormatterFactory { @Override - public Set defaultIncludes() { + public Set defaultIncludes(MavenProject project) { return Collections.emptySet(); } @@ -39,4 +41,17 @@ public String licenseHeaderDelimiter() { public void addTsfmt(Tsfmt tsfmt) { addStepFactory(tsfmt); } + + public void addEslint(EslintTs eslint) { + addStepFactory(eslint); + } + + public void addBiome(BiomeTs biome) { + addStepFactory(biome); + } + + @Deprecated + public void addRome(RomeTs rome) { + addStepFactory(rome); + } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/JacksonYaml.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/JacksonYaml.java new file mode 100644 index 0000000000..d97cc41647 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/JacksonYaml.java @@ -0,0 +1,54 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.yaml; + +import java.util.Collections; +import java.util.Map; + +import org.apache.maven.plugins.annotations.Parameter; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.maven.FormatterFactory; +import com.diffplug.spotless.maven.FormatterStepConfig; +import com.diffplug.spotless.maven.FormatterStepFactory; +import com.diffplug.spotless.yaml.JacksonYamlConfig; +import com.diffplug.spotless.yaml.JacksonYamlStep; + +/** + * A {@link FormatterFactory} implementation that corresponds to {@code ...} configuration element. + */ +public class JacksonYaml implements FormatterStepFactory { + + @Parameter + private String version = JacksonYamlStep.defaultVersion(); + + @Parameter + private Map features = Collections.emptyMap(); + + @Parameter + private Map yamlFeatures = Collections.emptyMap(); + + @Override + public FormatterStep newFormatterStep(FormatterStepConfig stepConfig) { + JacksonYamlConfig jacksonConfig = new JacksonYamlConfig(); + + jacksonConfig.appendFeatureToToggle(features); + jacksonConfig.appendYamlFeatureToToggle(yamlFeatures); + + return JacksonYamlStep + .create(jacksonConfig, version, stepConfig.getProvisioner()); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/Yaml.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/Yaml.java new file mode 100644 index 0000000000..e22f6d40ce --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/yaml/Yaml.java @@ -0,0 +1,43 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.yaml; + +import java.util.Collections; +import java.util.Set; + +import org.apache.maven.project.MavenProject; + +import com.diffplug.spotless.maven.FormatterFactory; + +/** + * A {@link FormatterFactory} implementation that corresponds to {@code ...} configuration element. + */ +public class Yaml extends FormatterFactory { + @Override + public Set defaultIncludes(MavenProject project) { + return Collections.emptySet(); + } + + @Override + public String licenseHeaderDelimiter() { + return null; + } + + public void addJackson(JacksonYaml jackson) { + addStepFactory(jackson); + } + +} diff --git a/plugin-maven/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml b/plugin-maven/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml new file mode 100644 index 0000000000..9000b83704 --- /dev/null +++ b/plugin-maven/src/main/resources/META-INF/m2e/lifecycle-mapping-metadata.xml @@ -0,0 +1,32 @@ + + + + + + + + apply + check + + + + + true + false + + + + + \ No newline at end of file diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/IdeHookTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/IdeHookTest.java new file mode 100644 index 0000000000..3c9a1a17d6 --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/IdeHookTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven; + +import java.io.File; +import java.io.IOException; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.ProcessRunner; + +class IdeHookTest extends MavenIntegrationHarness { + private String output, error; + private File dirty, clean, diverge, outofbounds; + + @BeforeEach + void before() throws IOException { + writePomWithFormatSteps("\n" + + " DIRTY.md\n" + + " CLEAN.md\n" + + " \n" + + " \n" + + " Greetings to Mars\n" + + " World\n" + + " Mars\n" + + " "); + + dirty = setFile("DIRTY.md").toContent("World"); + clean = setFile("CLEAN.md").toContent("Mars"); + outofbounds = setFile("OUTOFBOUNDS.md").toContent("Mars"); + ; + } + + private void runWith(String... arguments) throws IOException, InterruptedException { + ProcessRunner.Result result = mavenRunner() + .withArguments(arguments) + .runNoError(); + + this.output = result.stdOutUtf8(); + this.error = result.stdErrUtf8(); + } + + @Test + void dirty() throws IOException, InterruptedException { + runWith("spotless:apply", "--quiet", "-DspotlessIdeHook=\"" + dirty.getAbsolutePath() + "\"", "-DspotlessIdeHookUseStdOut=true"); + Assertions.assertThat(output).isEqualTo("Mars"); + Assertions.assertThat(error).startsWith("IS DIRTY"); + } + + @Test + void clean() throws IOException, InterruptedException { + runWith("spotless:apply", "--quiet", "-DspotlessIdeHook=" + clean.getAbsolutePath(), "-DspotlessIdeHookUseStdOut=true"); + Assertions.assertThat(output).isEmpty(); + Assertions.assertThat(error).startsWith("IS CLEAN"); + } + + @Test + void outofbounds() throws IOException, InterruptedException { + runWith("spotless:apply", "--quiet", "-DspotlessIdeHook=" + outofbounds.getAbsolutePath(), "-DspotlessIdeHookUseStdOut=true"); + Assertions.assertThat(output).isEmpty(); + Assertions.assertThat(error).isEmpty(); + } + + @Test + void notAbsolute() throws IOException, InterruptedException { + runWith("spotless:apply", "--quiet", "-DspotlessIdeHook=\"pom.xml\"", "-DspotlessIdeHookUseStdOut=true"); + Assertions.assertThat(output).isEmpty(); + Assertions.assertThat(error).contains("Argument passed to spotlessIdeHook must be an absolute path"); + } +} diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenIntegrationHarness.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenIntegrationHarness.java index 939940a315..9e2e70c60f 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenIntegrationHarness.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenIntegrationHarness.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,7 @@ package com.diffplug.spotless.maven; import static com.diffplug.common.base.Strings.isNullOrEmpty; -import static java.util.Arrays.asList; import static java.util.Arrays.stream; -import static java.util.Collections.emptyMap; import static java.util.Collections.singletonMap; import static java.util.stream.Collectors.toList; import static org.junit.jupiter.api.Assertions.fail; @@ -33,6 +31,8 @@ import java.nio.file.Path; import java.util.*; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import com.github.mustachejava.DefaultMustacheFactory; @@ -42,6 +42,7 @@ import com.diffplug.common.base.Unhandled; import com.diffplug.common.io.Resources; import com.diffplug.spotless.Jvm; +import com.diffplug.spotless.ProcessRunner; import com.diffplug.spotless.ResourceHarness; public class MavenIntegrationHarness extends ResourceHarness { @@ -53,14 +54,15 @@ public class MavenIntegrationHarness extends ResourceHarness { */ private static final String SPOTLESS_MAVEN_VERSION_IDE = null; - private static final String LOCAL_MAVEN_REPOSITORY_DIR = "localMavenRepositoryDir"; + private static final String POM_TEMPLATE = "/pom-test.xml.mustache"; private static final String SPOTLESS_MAVEN_PLUGIN_VERSION = "spotlessMavenPluginVersion"; + private static final String BUILD = "build"; private static final String CONFIGURATION = "configuration"; private static final String EXECUTIONS = "executions"; private static final String MODULES = "modules"; private static final String DEPENDENCIES = "dependencies"; + private static final String PLUGINS = "plugins"; private static final String MODULE_NAME = "name"; - private static final String CHILD_ID = "childId"; private static final int REMOTE_DEBUG_PORT = 5005; private final MustacheFactory mustacheFactory = new DefaultMustacheFactory(); @@ -68,12 +70,12 @@ public class MavenIntegrationHarness extends ResourceHarness { /** * Each test gets its own temp folder, and we create a maven * build there and run it. - * + *

* Because those test folders don't have a .gitattributes file, * git on windows will default to \r\n. So now if you read a * test file from the spotless test resources, and compare it * to a build result, the line endings won't match. - * + *

* By sticking this .gitattributes file into the test directory, * we ensure that the default Spotless line endings policy of * GIT_ATTRIBUTES will use \n, so that tests match the test @@ -136,8 +138,12 @@ protected void writePomWithCppSteps(String... steps) throws IOException { writePom(groupWithSteps("cpp", steps)); } - protected void writePomWithTypescriptSteps(String... steps) throws IOException { - writePom(groupWithSteps("typescript", including("**/*.ts"), steps)); + protected void writePomWithJavascriptSteps(String includes, String... steps) throws IOException { + writePom(groupWithSteps("javascript", including(includes), steps)); + } + + protected void writePomWithTypescriptSteps(String includes, String... steps) throws IOException { + writePom(groupWithSteps("typescript", including(includes), steps)); } protected void writePomWithSqlSteps(String... steps) throws IOException { @@ -148,6 +154,19 @@ protected void writePomWithPrettierSteps(String includes, String... steps) throw writePom(formats(groupWithSteps("format", including(includes), steps))); } + protected void writePomWithBiomeSteps(String includes, String... steps) throws IOException { + writePom(formats(groupWithSteps("format", including(includes), steps))); + } + + @Deprecated + protected void writePomWithRomeSteps(String includes, String... steps) throws IOException { + writePom(formats(groupWithSteps("format", including(includes), steps))); + } + + protected void writePomWithPrettierSteps(String[] plugins, String includes, String... steps) throws IOException { + writePom(null, formats(groupWithSteps("format", including(includes), steps)), null, plugins); + } + protected void writePomWithPomSteps(String... steps) throws IOException { writePom(groupWithSteps("pom", including("pom_test.xml"), steps)); } @@ -156,19 +175,51 @@ protected void writePomWithMarkdownSteps(String... steps) throws IOException { writePom(groupWithSteps("markdown", including("**/*.md"), steps)); } + protected void writePomWithJsonSteps(String... steps) throws IOException { + writePom(groupWithSteps("json", including("**/*.json"), steps)); + } + + protected void writePomWithShellSteps(String... steps) throws IOException { + writePom(groupWithSteps("shell", including("**/*.sh"), steps)); + } + + protected void writePomWithYamlSteps(String... steps) throws IOException { + writePom(groupWithSteps("yaml", including("**/*.yaml"), steps)); + } + + protected void writePomWithGherkinSteps(String... steps) throws IOException { + writePom(groupWithSteps("gherkin", including("**/*.feature"), steps)); + } + + protected void writePomWithGoSteps(String... steps) throws IOException { + writePom(groupWithSteps("go", including("**/*.go"), steps)); + } + protected void writePom(String... configuration) throws IOException { - writePom(null, configuration, null); + writePom(null, configuration, null, null); } - protected void writePom(String[] executions, String[] configuration, String[] dependencies) throws IOException { - String pomXmlContent = createPomXmlContent(null, executions, configuration, dependencies); + protected void writePom(String[] executions, String[] configuration, String[] dependencies, String[] plugins) throws IOException { + String pomXmlContent = createPomXmlContent(null, executions, configuration, dependencies, plugins); setFile("pom.xml").toContent(pomXmlContent); } protected MavenRunner mavenRunner() throws IOException { return MavenRunner.create() .withProjectDir(rootFolder()) - .withLocalRepository(new File(getSystemProperty(LOCAL_MAVEN_REPOSITORY_DIR))); + .withRunner(runner); + } + + private static ProcessRunner runner; + + @BeforeAll + static void setupRunner() throws IOException { + runner = new ProcessRunner(); + } + + @AfterAll + static void closeRunner() throws IOException { + runner.close(); } /** @@ -180,24 +231,25 @@ protected MavenRunner mavenRunnerWithRemoteDebug() throws IOException { return mavenRunner().withRemoteDebug(REMOTE_DEBUG_PORT); } - protected MultiModuleProjectCreator multiModuleProject() { - return new MultiModuleProjectCreator(); - } - - protected String createPomXmlContent(String pluginVersion, String[] executions, String[] configuration, String[] dependencies) throws IOException { - return createPomXmlContent("/pom-test.xml.mustache", pluginVersion, executions, configuration, dependencies); + protected String createPomXmlContent(String pluginVersion, String[] executions, String[] configuration, String[] dependencies, String[] plugins) throws IOException { + return createPomXmlContent(POM_TEMPLATE, pluginVersion, executions, configuration, dependencies, plugins); } - protected String createPomXmlContent(String pomTemplate, String pluginVersion, String[] executions, String[] configuration, String[] dependencies) throws IOException { - Map params = buildPomXmlParams(pluginVersion, executions, configuration, null, dependencies); + protected String createPomXmlContent(String pomTemplate, String pluginVersion, String[] executions, String[] configuration, String[] dependencies, String[] plugins) throws IOException { + Map params = buildPomXmlParams(pluginVersion, null, executions, configuration, null, dependencies, plugins); return createPomXmlContent(pomTemplate, params); } protected String createPomXmlContent(String pluginVersion, String[] executions, String[] configuration) throws IOException { - return createPomXmlContent(pluginVersion, executions, configuration, null); + return createPomXmlContent(pluginVersion, executions, configuration, null, null); + } + + protected String createPomXmlContent(String[] build, String[] configuration) throws IOException { + Map params = buildPomXmlParams(null, build, null, configuration, null, null, null); + return createPomXmlContent(POM_TEMPLATE, params); } - private String createPomXmlContent(String pomTemplate, Map params) throws IOException { + protected String createPomXmlContent(String pomTemplate, Map params) throws IOException { URL url = MavenIntegrationHarness.class.getResource(pomTemplate); try (BufferedReader reader = Resources.asCharSource(url, StandardCharsets.UTF_8).openBufferedStream()) { Mustache mustache = mustacheFactory.compile(reader, "pom"); @@ -207,10 +259,14 @@ private String createPomXmlContent(String pomTemplate, Map param } } - private static Map buildPomXmlParams(String pluginVersion, String[] executions, String[] configuration, String[] modules, String[] dependencies) { + protected static Map buildPomXmlParams(String pluginVersion, String[] build, String[] executions, String[] configuration, String[] modules, String[] dependencies, String[] plugins) { Map params = new HashMap<>(); params.put(SPOTLESS_MAVEN_PLUGIN_VERSION, pluginVersion == null ? getSystemProperty(SPOTLESS_MAVEN_PLUGIN_VERSION) : pluginVersion); + if (build != null) { + params.put(BUILD, String.join("\n", build)); + } + if (configuration != null) { params.put(CONFIGURATION, String.join("\n", configuration)); } @@ -228,6 +284,10 @@ private static Map buildPomXmlParams(String pluginVersion, Strin params.put(DEPENDENCIES, String.join("\n", dependencies)); } + if (plugins != null) { + params.put(PLUGINS, String.join("\n", plugins)); + } + return params; } @@ -235,8 +295,6 @@ private static String getSystemProperty(String name) { if (SPOTLESS_MAVEN_VERSION_IDE != null) { if (name.equals("spotlessMavenPluginVersion")) { return SPOTLESS_MAVEN_VERSION_IDE; - } else if (name.equals("localMavenRepositoryDir")) { - return new File("build/localMavenRepository").getAbsolutePath(); } else { throw Unhandled.stringException(name); } @@ -248,7 +306,7 @@ private static String getSystemProperty(String name) { return value; } - private static String[] groupWithSteps(String group, String[] includes, String... steps) { + protected static String[] groupWithSteps(String group, String[] includes, String... steps) { String[] result = new String[steps.length + includes.length + 2]; result[0] = "<" + group + ">"; System.arraycopy(includes, 0, result, 1, includes.length); @@ -257,97 +315,22 @@ private static String[] groupWithSteps(String group, String[] includes, String.. return result; } - private static String[] groupWithSteps(String group, String... steps) { + protected static String[] groupWithSteps(String group, String... steps) { return groupWithSteps(group, new String[]{}, steps); } - private static String[] including(String... includes) { + protected static String[] including(String... includes) { return groupWithSteps("includes", groupWithSteps("include", includes)); } - private static String[] formats(String... formats) { + protected static String[] formats(String... formats) { return groupWithSteps("formats", formats); } - protected class MultiModuleProjectCreator { - - private String configSubProject; - private SubProjectFile[] configSubProjectFiles; - private String[] configuration; - private final Map> subProjects = new LinkedHashMap<>(); - - protected MultiModuleProjectCreator withConfigSubProject(String name, SubProjectFile... files) { - configSubProject = name; - configSubProjectFiles = files; - return this; - } - - protected MultiModuleProjectCreator withConfiguration(String... lines) { - configuration = lines; - return this; - } - - protected MultiModuleProjectCreator addSubProject(String name, SubProjectFile... files) { - subProjects.put(name, asList(files)); - return this; - } - - protected void create() throws IOException { - createRootPom(); - createConfigSubProject(); - createSubProjects(); - } - - private void createRootPom() throws IOException { - List modulesList = new ArrayList<>(); - modulesList.add(configSubProject); - modulesList.addAll(subProjects.keySet()); - String[] modules = modulesList.toArray(new String[0]); - - Map rootPomParams = buildPomXmlParams(null, null, configuration, modules, null); - setFile("pom.xml").toContent(createPomXmlContent("/multi-module/pom-parent.xml.mustache", rootPomParams)); - } - - private void createConfigSubProject() throws IOException { - if (configSubProject != null) { - String content = createPomXmlContent("/multi-module/pom-config.xml.mustache", emptyMap()); - setFile(configSubProject + "/pom.xml").toContent(content); - - createSubProjectFiles(configSubProject, asList(configSubProjectFiles)); - } - } - - private void createSubProjects() throws IOException { - for (Map.Entry> entry : subProjects.entrySet()) { - String subProjectName = entry.getKey(); - List subProjectFiles = entry.getValue(); - - String content = createPomXmlContent("/multi-module/pom-child.xml.mustache", singletonMap(CHILD_ID, subProjectName)); - setFile(subProjectName + "/pom.xml").toContent(content); - - createSubProjectFiles(subProjectName, subProjectFiles); - } - } - - private void createSubProjectFiles(String subProjectName, List subProjectFiles) throws IOException { - for (SubProjectFile file : subProjectFiles) { - setFile(subProjectName + '/' + file.to).toResource(file.from); - } - } - } - - protected static class SubProjectFile { - - private final String from; - private final String to; - - private SubProjectFile(String from, String to) { - this.from = from; - this.to = to; - } - - protected static SubProjectFile file(String from, String to) { - return new SubProjectFile(from, to); - } + protected static String[] formats(String[]... formats) { + String[] formatsArray = Arrays.stream(formats) + .flatMap(Arrays::stream) + .toArray(String[]::new); + return formats(formatsArray); } } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenProvisionerTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenProvisionerTest.java index 3e769d7705..e1214a0d59 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenProvisionerTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenProvisionerTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ class MavenProvisionerTest extends MavenIntegrationHarness { void testMultipleDependenciesExcludingTransitives() throws Exception { writePomWithJavaSteps( "", - " 4.8.0", + " 4.9", ""); setFile("formatter.xml").toResource("java/eclipse/formatter.xml"); assertResolveDependenciesWorks(); @@ -33,7 +33,6 @@ void testMultipleDependenciesExcludingTransitives() throws Exception { void testSingleDependencyIncludingTransitives() throws Exception { writePomWithJavaSteps( "", - " 1.2", ""); assertResolveDependenciesWorks(); } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenRunner.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenRunner.java index a86e000187..c8d845f8a2 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenRunner.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenRunner.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,25 +17,19 @@ import static org.assertj.core.api.Assertions.assertThat; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Objects; -import com.diffplug.common.base.Throwables; -import com.diffplug.common.io.ByteStreams; -import com.diffplug.spotless.FileSignature; import com.diffplug.spotless.Jvm; +import com.diffplug.spotless.ProcessRunner; /** - * Harness for running a maven build, same idea as the - * GradleRunner from the gradle testkit. + * Harness for running a Maven build, same idea as the + * GradleRunner from the Gradle testkit. */ public class MavenRunner { public static MavenRunner create() { @@ -46,8 +40,8 @@ private MavenRunner() {} private File projectDir; private String[] args; - private File localRepositoryDir; private Map environment = new HashMap<>(); + private ProcessRunner runner; public MavenRunner withProjectDir(File projectDir) { this.projectDir = Objects.requireNonNull(projectDir); @@ -59,8 +53,8 @@ public MavenRunner withArguments(String... args) { return this; } - public MavenRunner withLocalRepository(File localRepositoryDir) { - this.localRepositoryDir = localRepositoryDir; + public MavenRunner withRunner(ProcessRunner runner) { + this.runner = runner; return this; } @@ -70,104 +64,25 @@ public MavenRunner withRemoteDebug(int port) { return this; } - private Result run() throws IOException, InterruptedException { + private ProcessRunner.Result run() throws IOException, InterruptedException { Objects.requireNonNull(projectDir, "Need to call withProjectDir() first"); Objects.requireNonNull(args, "Need to call withArguments() first"); - // run maven with the given args in the given directory - String argsString = String.join(" ", Arrays.asList(args)); - List cmds = getPlatformCmds("-e -Dmaven.repo.local=" + localRepositoryDir + ' ' + argsString); - ProcessBuilder builder = new ProcessBuilder(cmds); - builder.directory(projectDir); - builder.environment().putAll(environment); - Process process = builder.start(); - // slurp and return the stdout, stderr, and exitValue - Slurper output = new Slurper(process.getInputStream()); - Slurper error = new Slurper(process.getErrorStream()); - int exitValue = process.waitFor(); - output.join(); - error.join(); - return new Result(exitValue, output.result(), error.result()); + // run Maven with the given args in the given directory + String argsString = "-e " + String.join(" ", Arrays.asList(args)); + return runner.shellWinUnix(projectDir, environment, "mvnw " + argsString, "./mvnw " + argsString); } /** Runs the command and asserts that exit code is 0. */ - public Result runNoError() throws IOException, InterruptedException { - Result result = run(); - assertThat(result.exitValue()).as("Run without error %s", result).isEqualTo(0); + public ProcessRunner.Result runNoError() throws IOException, InterruptedException { + ProcessRunner.Result result = run(); + assertThat(result.exitCode()).as("Run without error %s", result).isEqualTo(0); return result; } /** Runs the command and asserts that exit code is not 0. */ - public Result runHasError() throws IOException, InterruptedException { - Result result = run(); - assertThat(result.exitValue()).as("Run with error %s", result).isNotEqualTo(0); + public ProcessRunner.Result runHasError() throws IOException, InterruptedException { + ProcessRunner.Result result = run(); + assertThat(result.exitCode()).as("Run with error %s", result).isNotEqualTo(0); return result; } - - public static class Result { - private final int exitValue; - private final String output; - private final String error; - - public Result(int exitValue, String output, String error) { - super(); - this.exitValue = exitValue; - this.output = Objects.requireNonNull(output); - this.error = Objects.requireNonNull(error); - } - - public int exitValue() { - return exitValue; - } - - public String output() { - return output; - } - - public String error() { - return error; - } - - @Override - public String toString() { - return "Result{" + - "exitValue=" + exitValue + - ", output='" + output + '\'' + - ", error='" + error + '\'' + - '}'; - } - } - - /** Prepends any arguments necessary to run a console command. */ - private static List getPlatformCmds(String cmd) { - if (FileSignature.machineIsWin()) { - return Arrays.asList("cmd", "/c", "mvnw " + cmd); - } else { - return Arrays.asList("/bin/sh", "-c", "./mvnw " + cmd); - } - } - - private static class Slurper extends Thread { - private final InputStream input; - private volatile String result; - - Slurper(InputStream input) { - this.input = Objects.requireNonNull(input); - start(); - } - - @Override - public void run() { - try { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - ByteStreams.copy(input, output); - result = output.toString(Charset.defaultCharset().name()); - } catch (Exception e) { - result = Throwables.getStackTraceAsString(e); - } - } - - public String result() { - return result; - } - } } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MultiModuleProjectTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MultiModuleProjectTest.java index e67b1bcad3..a2d491cb2b 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MultiModuleProjectTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MultiModuleProjectTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,15 @@ */ package com.diffplug.spotless.maven; -import static com.diffplug.spotless.maven.MavenIntegrationHarness.SubProjectFile.file; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Test; @@ -24,31 +32,31 @@ class MultiModuleProjectTest extends MavenIntegrationHarness { @Test void testConfigurationDependency() throws Exception { /* - create a multi-module project with the following stucture: + create a multi-module project with the following structure: /junit-tmp-dir ├── config - │   ├── pom.xml - │   └── src/main/resources/configs - │   ├── eclipse-formatter.xml - │   └── scalafmt.conf + │ ├── pom.xml + │ └── src/main/resources/configs + │ ├── eclipse-formatter.xml + │ └── scalafmt.conf ├── mvnw ├── mvnw.cmd ├── one - │   ├── pom.xml - │   └── src - │   ├── main/java/test1.java - │   └── test/java/test2.java + │ ├── pom.xml + │ └── src + │ ├── main/java/test1.java + │ └── test/java/test2.java ├── two - │   ├── pom.xml - │   └── src - │   ├── main/java/test1.java - │   └── test/java/test2.java + │ ├── pom.xml + │ └── src + │ ├── main/java/test1.java + │ └── test/java/test2.java ├── three - │   ├── pom.xml - │   └── src - │   ├── main/scala/test1.scala - │   └── test/scala/test2.scala + │ ├── pom.xml + │ └── src + │ ├── main/scala/test1.scala + │ └── test/scala/test2.scala ├── pom.xml ├── .mvn ├── mvnw @@ -62,7 +70,7 @@ void testConfigurationDependency() throws Exception { "", " ", " configs/eclipse-formatter.xml", - " 4.7.1", + " 4.9", " ", "", "", @@ -96,4 +104,91 @@ void testConfigurationDependency() throws Exception { assertFile("three/src/main/scala/test1.scala").sameAsResource("scala/scalafmt/basic.cleanWithCustomConf_3.0.0"); assertFile("three/src/test/scala/test2.scala").sameAsResource("scala/scalafmt/basic.cleanWithCustomConf_3.0.0"); } + + private static final String CHILD_ID = "childId"; + + protected MultiModuleProjectCreator multiModuleProject() { + return new MultiModuleProjectCreator(); + } + + class MultiModuleProjectCreator { + private String configSubProject; + private SubProjectFile[] configSubProjectFiles; + private String[] configuration; + private final Map> subProjects = new LinkedHashMap<>(); + + protected MultiModuleProjectCreator withConfigSubProject(String name, SubProjectFile... files) { + configSubProject = name; + configSubProjectFiles = files; + return this; + } + + protected MultiModuleProjectCreator withConfiguration(String... lines) { + configuration = lines; + return this; + } + + protected MultiModuleProjectCreator addSubProject(String name, SubProjectFile... files) { + subProjects.put(name, asList(files)); + return this; + } + + protected void create() throws IOException { + createRootPom(); + createConfigSubProject(); + createSubProjects(); + } + + private void createRootPom() throws IOException { + List modulesList = new ArrayList<>(); + modulesList.add(configSubProject); + modulesList.addAll(subProjects.keySet()); + String[] modules = modulesList.toArray(new String[0]); + + Map rootPomParams = buildPomXmlParams(null, null, null, configuration, modules, null, null); + setFile("pom.xml").toContent(createPomXmlContent("/multi-module/pom-parent.xml.mustache", rootPomParams)); + } + + private void createConfigSubProject() throws IOException { + if (configSubProject != null) { + String content = createPomXmlContent("/multi-module/pom-config.xml.mustache", emptyMap()); + setFile(configSubProject + "/pom.xml").toContent(content); + + createSubProjectFiles(configSubProject, asList(configSubProjectFiles)); + } + } + + private void createSubProjects() throws IOException { + for (Map.Entry> entry : subProjects.entrySet()) { + String subProjectName = entry.getKey(); + List subProjectFiles = entry.getValue(); + + String content = createPomXmlContent("/multi-module/pom-child.xml.mustache", singletonMap(CHILD_ID, subProjectName)); + setFile(subProjectName + "/pom.xml").toContent(content); + + createSubProjectFiles(subProjectName, subProjectFiles); + } + } + + private void createSubProjectFiles(String subProjectName, List subProjectFiles) { + for (SubProjectFile file : subProjectFiles) { + setFile(subProjectName + '/' + file.to).toResource(file.from); + } + } + } + + static class SubProjectFile { + + private final String from; + private final String to; + + private SubProjectFile(String from, String to) { + this.from = from; + this.to = to; + } + } + + static SubProjectFile file(String from, String to) { + return new SubProjectFile(from, to); + } } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/SpecificFilesTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/SpecificFilesTest.java index 55cd586582..6ce11d179f 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/SpecificFilesTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/SpecificFilesTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,7 +58,6 @@ private void integration(String patterns, boolean firstFormatted, boolean second " src/**/java/**/*.java", "", "", - " 1.2", ""); setFile(testFile(1)).toResource(fixture(false)); diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/SpotlessCheckMojoTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/SpotlessCheckMojoTest.java index cb37e4a4c5..92a78e3923 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/SpotlessCheckMojoTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/SpotlessCheckMojoTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import org.junit.jupiter.api.Test; +import com.diffplug.spotless.ProcessRunner; + class SpotlessCheckMojoTest extends MavenIntegrationHarness { private static final String UNFORMATTED_FILE = "license/MissingLicense.test"; @@ -60,6 +62,7 @@ void testSpotlessCheckBindingToVerifyPhase() throws Exception { " ${basedir}/license.txt", " ", ""}, + null, null); testSpotlessCheck(UNFORMATTED_FILE, "verify", true); @@ -72,8 +75,8 @@ private void testSpotlessCheck(String fileName, String command, boolean expectEr MavenRunner mavenRunner = mavenRunner().withArguments(command); if (expectError) { - MavenRunner.Result result = mavenRunner.runHasError(); - assertThat(result.output()).contains("The following files had format violations"); + ProcessRunner.Result result = mavenRunner.runHasError(); + assertThat(result.stdOutUtf8()).contains("The following files had format violations"); } else { mavenRunner.runNoError(); } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/biome/BiomeMavenTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/biome/BiomeMavenTest.java new file mode 100644 index 0000000000..ff763c3cf8 --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/biome/BiomeMavenTest.java @@ -0,0 +1,218 @@ +/* + * Copyright 2023-2024 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.biome; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.owasp.encoder.Encode.forXml; + +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.maven.MavenIntegrationHarness; + +class BiomeMavenTest extends MavenIntegrationHarness { + /** + * Tests that Biome can be used as a generic formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void asGenericStep() throws Exception { + writePomWithBiomeSteps("**/*.js", "1.2.0"); + setFile("biome_test.js").toResource("biome/js/fileBefore.js"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("biome_test.js").sameAsResource("biome/js/fileAfter.js"); + } + + /** + * Tests that Biome can be used as a JavaScript formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void asJavaScriptStep() throws Exception { + writePomWithJavascriptSteps("**/*.js", "1.2.0"); + setFile("biome_test.js").toResource("biome/js/fileBefore.js"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("biome_test.js").sameAsResource("biome/js/fileAfter.js"); + } + + /** + * Tests that biome can be used as a JSON formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void asJsonStep() throws Exception { + writePomWithJsonSteps("**/*.json", "1.2.0"); + setFile("biome_test.json").toResource("biome/json/fileBefore.json"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("biome_test.json").sameAsResource("biome/json/fileAfter.json"); + } + + /** + * Tests that biome can be used as a TypeScript formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void asTypeScriptStep() throws Exception { + writePomWithTypescriptSteps("**/*.ts", "1.2.0"); + setFile("biome_test.ts").toResource("biome/ts/fileBefore.ts"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("biome_test.ts").sameAsResource("biome/ts/fileAfter.ts"); + } + + /** + * Tests that the language can be specified for the generic format step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void canSetLanguageForGenericStep() throws Exception { + writePomWithBiomeSteps("**/*.nosj", "1.2.0json"); + setFile("biome_test.nosj").toResource("biome/json/fileBefore.json"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("biome_test.nosj").sameAsResource("biome/json/fileAfter.json"); + } + + /** + * Tests that an absolute config path can be specified. + * + * @throws Exception When a test failure occurs. + */ + @Test + void configPathAbsolute() throws Exception { + var path = newFile("configs").getAbsolutePath(); + writePomWithBiomeSteps("**/*.js", + "1.2.0" + forXml(path) + ""); + setFile("biome_test.js").toResource("biome/js/longLineBefore.js"); + setFile("configs/biome.json").toResource("biome/config/line-width-120.json"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("biome_test.js").sameAsResource("biome/js/longLineAfter120.js"); + } + + /** + * Tests that a path to the directory with the biome.json config file can be + * specified. Uses a config file with a line width of 120. + * + * @throws Exception When a test failure occurs. + */ + @Test + void configPathLineWidth120() throws Exception { + writePomWithBiomeSteps("**/*.js", "1.2.0configs"); + setFile("biome_test.js").toResource("biome/js/longLineBefore.js"); + setFile("configs/biome.json").toResource("biome/config/line-width-120.json"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("biome_test.js").sameAsResource("biome/js/longLineAfter120.js"); + } + + /** + * Tests that a path to the directory with the biome.json config file can be + * specified. Uses a config file with a line width of 80. + * + * @throws Exception When a test failure occurs. + */ + @Test + void configPathLineWidth80() throws Exception { + writePomWithBiomeSteps("**/*.js", "1.2.0configs"); + setFile("biome_test.js").toResource("biome/js/longLineBefore.js"); + setFile("configs/biome.json").toResource("biome/config/line-width-80.json"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("biome_test.js").sameAsResource("biome/js/longLineAfter80.js"); + } + + /** + * Tests that the download directory can be an absolute path. + * + * @throws Exception When a test failure occurs. + */ + @Test + void downloadDirAbsolute() throws Exception { + var path = newFile("target/bin/biome").getAbsoluteFile().toString(); + writePomWithBiomeSteps("**/*.js", + "1.2.0" + forXml(path) + ""); + setFile("biome_test.js").toResource("biome/js/fileBefore.js"); + assertTrue(!newFile("target/bin/biome").exists() || newFile("target/bin/biome").list().length == 0); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("biome_test.js").sameAsResource("biome/js/fileAfter.js"); + assertEquals(2, newFile("target/bin/biome").list().length); + } + + /** + * Tests that the download directory can be changed to a path relative to the + * project's base directory. + * + * @throws Exception When a test failure occurs. + */ + @Test + void downloadDirRelative() throws Exception { + writePomWithBiomeSteps("**/*.js", + "1.2.0target/bin/biome"); + setFile("biome_test.js").toResource("biome/js/fileBefore.js"); + assertTrue(!newFile("target/bin/biome").exists() || newFile("target/bin/biome").list().length == 0); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("biome_test.js").sameAsResource("biome/js/fileAfter.js"); + assertEquals(2, newFile("target/bin/biome").list().length); + } + + /** + * Tests that the build fails when the input file could not be parsed. + * + * @throws Exception When a test failure occurs. + */ + @Test + void failureWhenExeNotFound() throws Exception { + writePomWithBiomeSteps("**/*.js", "1.2.0biome/is/missing"); + setFile("biome_test.js").toResource("biome/js/fileBefore.js"); + var result = mavenRunner().withArguments("spotless:apply").runHasError(); + assertFile("biome_test.js").sameAsResource("biome/js/fileBefore.js"); + assertThat(result.stdOutUtf8()).contains("Biome executable does not exist"); + } + + /** + * Tests that the build fails when the input file could not be parsed. + * + * @throws Exception When a test failure occurs. + */ + @Test + void failureWhenNotParseable() throws Exception { + writePomWithBiomeSteps("**/*.js", "1.2.0json"); + setFile("biome_test.js").toResource("biome/js/fileBefore.js"); + var result = mavenRunner().withArguments("spotless:apply").runHasError(); + assertFile("biome_test.js").sameAsResource("biome/js/fileBefore.js"); + assertThat(result.stdOutUtf8()).contains("Format with errors is disabled."); + assertThat(result.stdOutUtf8()).contains("Unable to format file"); + assertThat(result.stdOutUtf8()).contains("Step 'biome' found problem in 'biome_test.js'"); + } + + /** + * Biome is hard-coded to ignore certain files, such as package.json. Since version 1.5.0, + * the biome CLI does not output any formatted code anymore, whereas previously it printed + * the input as-is. This tests checks that when the biome formatter outputs an empty string, + * the contents of the file to format are used instead. + * + * @throws Exception When a test failure occurs. + */ + @Test + void preservesIgnoredFiles() throws Exception { + writePomWithJsonSteps("**/*.json", "1.5.0"); + setFile("package.json").toResource("biome/json/packageBefore.json"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("package.json").sameAsResource("biome/json/packageAfter.json"); + } +} diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/gherkin/GherkinTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/gherkin/GherkinTest.java new file mode 100644 index 0000000000..95a60097d0 --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/gherkin/GherkinTest.java @@ -0,0 +1,32 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.gherkin; + +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.maven.MavenIntegrationHarness; + +public class GherkinTest extends MavenIntegrationHarness { + @Test + public void testFormatJson_WithSimple_defaultConfig_sortByKeys() throws Exception { + writePomWithGherkinSteps(""); + + setFile("examples/main/resources/example.feature").toResource("gherkin/minimalBefore.feature"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("examples/main/resources/example.feature").sameAsResource("gherkin/minimalAfter.feature"); + } + +} diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/go/GofmtTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/go/GofmtTest.java new file mode 100644 index 0000000000..5cf4398f70 --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/go/GofmtTest.java @@ -0,0 +1,32 @@ +/* + * Copyright 2024 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.go; + +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.maven.MavenIntegrationHarness; + +@com.diffplug.spotless.tag.GofmtTest +public class GofmtTest extends MavenIntegrationHarness { + @Test + void testGofmt() throws Exception { + writePomWithGoSteps("go1.21.5"); + + setFile("src/main/go/example.go").toResource("go/gofmt/go.dirty"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("src/main/go/example.go").sameAsResource("go/gofmt/go.clean"); + } +} diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/groovy/GrEclipseTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/groovy/GrEclipseTest.java index 8807f47cc9..34d95dc7d2 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/groovy/GrEclipseTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/groovy/GrEclipseTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,7 +60,7 @@ private void writePomWithGrEclipse() throws IOException { writePomWithGroovySteps( "", " ${basedir}/greclipse.properties", - " 4.19.0", + " 4.25", ""); setFile("greclipse.properties").toResource("groovy/greclipse/format/greclipse.properties"); } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/groovy/RemoveSemicolonsTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/groovy/RemoveSemicolonsTest.java new file mode 100644 index 0000000000..fc78c87a27 --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/groovy/RemoveSemicolonsTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.groovy; + +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.maven.MavenIntegrationHarness; + +class RemoveSemicolonsTest extends MavenIntegrationHarness { + + @Test + void testRemoveSemicolonsString() throws Exception { + writePomWithGroovySteps(""); + runTest("Hello World;", "Hello World"); + } + + @Test + void testNotRemoveSemicolonsString() throws Exception { + writePomWithGroovySteps(""); + runTest("Hello;World", "Hello;World"); + } + + @Test + void testRemoveSemicolons() throws Exception { + writePomWithGroovySteps(""); + + String path = "src/main/groovy/test.groovy"; + setFile(path).toResource("groovy/removeSemicolons/GroovyCodeWithSemicolons.test"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile(path).sameAsResource("groovy/removeSemicolons/GroovyCodeWithSemicolonsFormatted.test"); + } + + private void runTest(String sourceContent, String targetContent) throws Exception { + String path = "src/main/groovy/test.groovy"; + setFile(path).toContent(sourceContent); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile(path).hasContent(targetContent); + } +} diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/FileIndexTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/FileIndexTest.java index ce9c0077e8..8cd5e8a2f7 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/FileIndexTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/FileIndexTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,7 +60,7 @@ void readFallsBackToEmptyIndexOnFingerprintMismatch() throws Exception { FileIndex index = FileIndex.read(config, log); assertThat(index.size()).isZero(); - verify(log).info("Fingerprint mismatch in the index file. Fallback to an empty index"); + verify(log).info("Index file corresponds to a different configuration of the plugin. Either the plugin version or its configuration has changed. Fallback to an empty index"); } @Test diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/PluginFingerprintTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/PluginFingerprintTest.java index 90e59d657e..ae61dec331 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/PluginFingerprintTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/PluginFingerprintTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,9 +23,12 @@ import java.io.ByteArrayInputStream; import java.nio.file.Paths; import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.apache.maven.model.Model; +import org.apache.maven.model.Plugin; +import org.apache.maven.model.PluginManagement; import org.apache.maven.model.io.xpp3.MavenXpp3Reader; import org.apache.maven.project.MavenProject; import org.codehaus.plexus.util.ReaderFactory; @@ -43,126 +46,22 @@ class PluginFingerprintTest extends MavenIntegrationHarness { private static final String VERSION_1 = "1.0.0"; private static final String VERSION_2 = "2.0.0"; - private static final String[] EXECUTION_1 = { - "", - " check", - " ", - " check", - " ", - "" - }; - private static final String[] EXECUTION_2 = {}; - - private static final String[] CONFIGURATION_1 = { - "", - " 1.2", - "" - }; - private static final String[] CONFIGURATION_2 = { - "", - " 1.8", - " true", - "" - }; - - private static final String[] DEPENDENCIES_1 = { - "", - " ", - " unknown", - " unknown", - " 1.0", - " ", - "" - }; - private static final String[] DEPENDENCIES_2 = { - "", - " ", - " unknown", - " unknown", - " 2.0", - " ", - "" - }; - private static final List FORMATTERS = singletonList(formatter(formatterStep("default"))); @Test - void sameFingerprint() throws Exception { - String xml1 = createPomXmlContent(VERSION_1, EXECUTION_1, CONFIGURATION_1); - String xml2 = createPomXmlContent(VERSION_1, EXECUTION_1, CONFIGURATION_1); - - MavenProject project1 = mavenProject(xml1); - MavenProject project2 = mavenProject(xml2); + void sameFingerprintWhenVersionAndFormattersAreTheSame() throws Exception { + MavenProject project = mavenProject(VERSION_1); - PluginFingerprint fingerprint1 = PluginFingerprint.from(project1, FORMATTERS); - PluginFingerprint fingerprint2 = PluginFingerprint.from(project2, FORMATTERS); - - assertThat(fingerprint1).isEqualTo(fingerprint2); - } - - @Test - void sameFingerprintWithDependencies() throws Exception { - String xml1 = createPomXmlContent(VERSION_1, EXECUTION_1, CONFIGURATION_1, DEPENDENCIES_1); - String xml2 = createPomXmlContent(VERSION_1, EXECUTION_1, CONFIGURATION_1, DEPENDENCIES_1); - - MavenProject project1 = mavenProject(xml1); - MavenProject project2 = mavenProject(xml2); - - PluginFingerprint fingerprint1 = PluginFingerprint.from(project1, FORMATTERS); - PluginFingerprint fingerprint2 = PluginFingerprint.from(project2, FORMATTERS); + PluginFingerprint fingerprint1 = PluginFingerprint.from(project, FORMATTERS); + PluginFingerprint fingerprint2 = PluginFingerprint.from(project, FORMATTERS); assertThat(fingerprint1).isEqualTo(fingerprint2); } @Test - void differentFingerprintForDifferentDependencies() throws Exception { - String xml1 = createPomXmlContent(VERSION_1, EXECUTION_1, CONFIGURATION_1, DEPENDENCIES_1); - String xml2 = createPomXmlContent(VERSION_1, EXECUTION_1, CONFIGURATION_1, DEPENDENCIES_2); - - MavenProject project1 = mavenProject(xml1); - MavenProject project2 = mavenProject(xml2); - - PluginFingerprint fingerprint1 = PluginFingerprint.from(project1, FORMATTERS); - PluginFingerprint fingerprint2 = PluginFingerprint.from(project2, FORMATTERS); - - assertThat(fingerprint1).isNotEqualTo(fingerprint2); - } - - @Test - void differentFingerprintForDifferentPluginVersion() throws Exception { - String xml1 = createPomXmlContent(VERSION_1, EXECUTION_1, CONFIGURATION_1); - String xml2 = createPomXmlContent(VERSION_2, EXECUTION_1, CONFIGURATION_1); - - MavenProject project1 = mavenProject(xml1); - MavenProject project2 = mavenProject(xml2); - - PluginFingerprint fingerprint1 = PluginFingerprint.from(project1, FORMATTERS); - PluginFingerprint fingerprint2 = PluginFingerprint.from(project2, FORMATTERS); - - assertThat(fingerprint1).isNotEqualTo(fingerprint2); - } - - @Test - void differentFingerprintForDifferentExecution() throws Exception { - String xml1 = createPomXmlContent(VERSION_2, EXECUTION_1, CONFIGURATION_1); - String xml2 = createPomXmlContent(VERSION_2, EXECUTION_2, CONFIGURATION_1); - - MavenProject project1 = mavenProject(xml1); - MavenProject project2 = mavenProject(xml2); - - PluginFingerprint fingerprint1 = PluginFingerprint.from(project1, FORMATTERS); - PluginFingerprint fingerprint2 = PluginFingerprint.from(project2, FORMATTERS); - - assertThat(fingerprint1).isNotEqualTo(fingerprint2); - } - - @Test - void differentFingerprintForDifferentConfiguration() throws Exception { - String xml1 = createPomXmlContent(VERSION_1, EXECUTION_2, CONFIGURATION_2); - String xml2 = createPomXmlContent(VERSION_1, EXECUTION_2, CONFIGURATION_1); - - MavenProject project1 = mavenProject(xml1); - MavenProject project2 = mavenProject(xml2); + void differentFingerprintForDifferentPluginVersions() throws Exception { + MavenProject project1 = mavenProject(VERSION_1); + MavenProject project2 = mavenProject(VERSION_2); PluginFingerprint fingerprint1 = PluginFingerprint.from(project1, FORMATTERS); PluginFingerprint fingerprint2 = PluginFingerprint.from(project2, FORMATTERS); @@ -172,11 +71,8 @@ void differentFingerprintForDifferentConfiguration() throws Exception { @Test void differentFingerprintForFormattersWithDifferentSteps() throws Exception { - String xml1 = createPomXmlContent(VERSION_1, EXECUTION_1, CONFIGURATION_1); - String xml2 = createPomXmlContent(VERSION_1, EXECUTION_1, CONFIGURATION_1); - - MavenProject project1 = mavenProject(xml1); - MavenProject project2 = mavenProject(xml2); + MavenProject project1 = mavenProject(VERSION_1); + MavenProject project2 = mavenProject(VERSION_1); FormatterStep step1 = formatterStep("step1"); FormatterStep step2 = formatterStep("step2"); @@ -192,11 +88,8 @@ void differentFingerprintForFormattersWithDifferentSteps() throws Exception { @Test void differentFingerprintForFormattersWithDifferentLineEndings() throws Exception { - String xml1 = createPomXmlContent(VERSION_1, EXECUTION_1, CONFIGURATION_1); - String xml2 = createPomXmlContent(VERSION_1, EXECUTION_1, CONFIGURATION_1); - - MavenProject project1 = mavenProject(xml1); - MavenProject project2 = mavenProject(xml2); + MavenProject project1 = mavenProject(VERSION_1); + MavenProject project2 = mavenProject(VERSION_1); FormatterStep step = formatterStep("step"); List formatters1 = singletonList(formatter(LineEnding.UNIX, step)); @@ -216,7 +109,7 @@ void emptyFingerprint() { } @Test - void failsWhenProjectDoesNotContainSpotlessPlugin() { + void failsForProjectWithoutSpotlessPlugin() { MavenProject projectWithoutSpotless = new MavenProject(); assertThatThrownBy(() -> PluginFingerprint.from(projectWithoutSpotless, FORMATTERS)) @@ -224,7 +117,39 @@ void failsWhenProjectDoesNotContainSpotlessPlugin() { .hasMessageContaining("Spotless plugin absent from the project"); } - private static MavenProject mavenProject(String xml) throws Exception { + @Test + void buildsFingerprintForProjectWithSpotlessPluginInBuildPlugins() { + MavenProject project = new MavenProject(); + Plugin spotlessPlugin = new Plugin(); + spotlessPlugin.setGroupId("com.diffplug.spotless"); + spotlessPlugin.setArtifactId("spotless-maven-plugin"); + spotlessPlugin.setVersion("1.2.3"); + project.getBuild().addPlugin(spotlessPlugin); + + PluginFingerprint fingerprint = PluginFingerprint.from(project, Collections.emptyList()); + + assertThat(fingerprint).isNotNull(); + } + + @Test + void buildsFingerprintForProjectWithSpotlessPluginInPluginManagement() { + MavenProject project = new MavenProject(); + Plugin spotlessPlugin = new Plugin(); + spotlessPlugin.setGroupId("com.diffplug.spotless"); + spotlessPlugin.setArtifactId("spotless-maven-plugin"); + spotlessPlugin.setVersion("1.2.3"); + project.getBuild().addPlugin(spotlessPlugin); + PluginManagement pluginManagement = new PluginManagement(); + pluginManagement.addPlugin(spotlessPlugin); + project.getBuild().setPluginManagement(pluginManagement); + + PluginFingerprint fingerprint = PluginFingerprint.from(project, Collections.emptyList()); + + assertThat(fingerprint).isNotNull(); + } + + private MavenProject mavenProject(String spotlessVersion) throws Exception { + String xml = createPomXmlContent(spotlessVersion, new String[0], new String[0]); return new MavenProject(readPom(xml)); } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/UpToDateCheckingTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/UpToDateCheckingTest.java index f315c7eefe..cfc17594ca 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/UpToDateCheckingTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/incremental/UpToDateCheckingTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,8 +31,10 @@ class UpToDateCheckingTest extends MavenIntegrationHarness { + private static final String DISABLED_MESSAGE = "Up-to-date checking disabled"; + @Test - void upToDateCheckingDisabledByDefault() throws Exception { + void upToDateCheckingEnabledByDefault() throws Exception { writePom( "", " ", @@ -41,74 +43,53 @@ void upToDateCheckingDisabledByDefault() throws Exception { List files = writeUnformattedFiles(1); String output = runSpotlessApply(); - assertThat(output).doesNotContain("Up-to-date checking enabled"); + assertThat(output).doesNotContain(DISABLED_MESSAGE); assertFormatted(files); } @Test - void enableUpToDateChecking() throws Exception { + void explicitlyEnableUpToDateChecking() throws Exception { writePomWithUpToDateCheckingEnabled(true); List files = writeUnformattedFiles(1); String output = runSpotlessApply(); - assertThat(output).contains("Up-to-date checking enabled"); + assertThat(output).doesNotContain(DISABLED_MESSAGE); assertFormatted(files); } @Test - void enableUpToDateCheckingWithPluginDependencies() throws Exception { - writePomWithPluginManagementAndDependency(); + void explicitlyDisableUpToDateChecking() throws Exception { + writePomWithUpToDateCheckingEnabled(false); List files = writeUnformattedFiles(1); String output = runSpotlessApply(); - assertThat(output).contains("Up-to-date checking enabled"); + assertThat(output).contains(DISABLED_MESSAGE); assertFormatted(files); } @Test - void enableUpToDateCheckingWithPluginDependenciesMaven3_6_3() throws Exception { + void enableUpToDateCheckingWithPluginDependencies() throws Exception { writePomWithPluginManagementAndDependency(); - setFile(".mvn/wrapper/maven-wrapper.properties").toContent("distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip\n"); - List files = writeUnformattedFiles(1); String output = runSpotlessApply(); - assertThat(output).contains("Up-to-date checking enabled"); + assertThat(output).doesNotContain(DISABLED_MESSAGE); assertFormatted(files); } - private void writePomWithPluginManagementAndDependency() throws IOException { - setFile("pom.xml").toContent(createPomXmlContent("/pom-test-management.xml.mustache", - null, - null, - new String[]{ - "", - " ", - "", - "", - " true", - ""}, - new String[]{ - "", - " ", - " javax.inject", - " javax.inject", - " 1", - " ", - ""})); - } - @Test - void disableUpToDateChecking() throws Exception { - writePomWithUpToDateCheckingEnabled(false); + void enableUpToDateCheckingWithPluginDependenciesMaven3_6_3() throws Exception { + writePomWithPluginManagementAndDependency(); + + setFile(".mvn/wrapper/maven-wrapper.properties").toContent("distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip\n"); List files = writeUnformattedFiles(1); String output = runSpotlessApply(); - assertThat(output).doesNotContain("Up-to-date checking enabled"); + assertThat(output).doesNotContain(DISABLED_MESSAGE); assertFormatted(files); } @@ -123,7 +104,7 @@ void enableUpToDateCheckingCustomIndexFile() throws Exception { List files = writeUnformattedFiles(1); String output = runSpotlessApply(); - assertThat(output).contains("Up-to-date checking enabled"); + assertThat(output).doesNotContain(DISABLED_MESSAGE); assertFormatted(files); assertThat(indexFile.getParent()).exists(); assertThat(indexFile).exists(); @@ -142,7 +123,7 @@ void disableUpToDateCheckingCustomIndexFile() throws Exception { List files = writeUnformattedFiles(1); String output = runSpotlessApply(); - assertThat(output).doesNotContain("Up-to-date checking enabled"); + assertThat(output).contains(DISABLED_MESSAGE); assertFormatted(files); assertThat(indexFile.getParent()).exists(); assertThat(indexFile).doesNotExist(); @@ -214,6 +195,25 @@ void spotlessCheckRecordsUnformattedFiles() throws Exception { assertSpotlessCheckSkipped(files, checkOutput3); } + private void writePomWithPluginManagementAndDependency() throws IOException { + setFile("pom.xml").toContent(createPomXmlContent("/pom-test-management.xml.mustache", + null, + null, + new String[]{ + "", + " ", + ""}, + new String[]{ + "", + " ", + " javax.inject", + " javax.inject", + " 1", + " ", + ""}, + null)); + } + private void writePomWithUpToDateCheckingEnabled(boolean enabled) throws IOException { writePom( "", @@ -254,15 +254,15 @@ private List writeFiles(String resource, String suffix, int count) throws } private String runSpotlessApply() throws Exception { - return mavenRunnerForGoal("apply").runNoError().output(); + return mavenRunnerForGoal("apply").runNoError().stdOutUtf8(); } private String runSpotlessCheck() throws Exception { - return mavenRunnerForGoal("check").runNoError().output(); + return mavenRunnerForGoal("check").runNoError().stdOutUtf8(); } private String runSpotlessCheckOnUnformattedFiles() throws Exception { - return mavenRunnerForGoal("check").runHasError().output(); + return mavenRunnerForGoal("check").runHasError().stdOutUtf8(); } private MavenRunner mavenRunnerForGoal(String goal) throws IOException { diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/CleanthatJavaRefactorerTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/CleanthatJavaRefactorerTest.java new file mode 100644 index 0000000000..5f6bc2750d --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/CleanthatJavaRefactorerTest.java @@ -0,0 +1,110 @@ +/* + * Copyright 2022-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.java; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.maven.MavenIntegrationHarness; + +class CleanthatJavaRefactorerTest extends MavenIntegrationHarness { + @Test + void testEnableDraft() throws Exception { + writePomWithJavaSteps( + "", + " 11", + " true", + ""); + + runTest("MultipleMutators.dirty.test", "MultipleMutators.clean.onlyOptionalIsPresent.test"); + } + + @Test + void testLiteralsFirstInComparisons() throws Exception { + writePomWithJavaSteps( + "", + " ", + " LiteralsFirstInComparisons", + " ", + ""); + + runTest("LiteralsFirstInComparisons.dirty.test", "LiteralsFirstInComparisons.clean.test"); + } + + @Test + void testMultipleMutators_defaultIsJdk7() throws Exception { + // OptionalNotEmpty will be excluded as it is not compatible with JDK7 + writePomWithJavaSteps( + "", + " ", + " LiteralsFirstInComparisons", + " OptionalNotEmpty", + " ", + ""); + + runTest("MultipleMutators.dirty.test", "MultipleMutators.clean.onlyLiteralsFirst.test"); + } + + @Test + void testMultipleMutators_Jdk11IntroducedOptionalisPresent() throws Exception { + writePomWithJavaSteps( + "", + " 11", + " ", + " LiteralsFirstInComparisons", + " OptionalNotEmpty", + " ", + ""); + + runTest("MultipleMutators.dirty.test", "MultipleMutators.clean.test"); + } + + @Test + void testExcludeOptionalNotEmpty() throws Exception { + writePomWithJavaSteps( + "", + " ", + " LiteralsFirstInComparisons", + " OptionalNotEmpty", + " ", + " ", + " OptionalNotEmpty", + " ", + ""); + + runTest("MultipleMutators.dirty.test", "MultipleMutators.clean.onlyLiteralsFirst.test"); + } + + @Test + void testIncludeOnlyLiteralsFirstInComparisons() throws Exception { + writePomWithJavaSteps( + "", + " ", + " LiteralsFirstInComparisons", + " ", + ""); + + runTest("MultipleMutators.dirty.test", "MultipleMutators.clean.onlyLiteralsFirst.test"); + } + + private void runTest(String dirtyPath, String cleanPath) throws Exception { + String path = "src/main/java/test.java"; + setFile(path).toResource("java/cleanthat/" + dirtyPath); + // .withRemoteDebug(21654) + Assertions.assertThat(mavenRunner().withArguments("spotless:apply").runNoError().stdOutUtf8()).doesNotContain("[ERROR]"); + assertFile(path).sameAsResource("java/cleanthat/" + cleanPath); + } +} diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/FormatAnnotationsStepTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/FormatAnnotationsStepTest.java index 8560b73fd4..972a4b4f7f 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/FormatAnnotationsStepTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/FormatAnnotationsStepTest.java @@ -30,4 +30,14 @@ void testFormatAnnotations() throws Exception { mavenRunner().withArguments("spotless:apply").runNoError(); assertFile(path).sameAsResource("java/formatannotations/FormatAnnotationsTestOutput.test"); } + + @Test + void testFormatAnnotationsAccessModifiers() throws Exception { + writePomWithJavaSteps(""); + + String path = "src/main/java/test.java"; + setFile(path).toResource("java/formatannotations/FormatAnnotationsAccessModifiersInput.test"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile(path).sameAsResource("java/formatannotations/FormatAnnotationsAccessModifiersOutput.test"); + } } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/GoogleJavaFormatTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/GoogleJavaFormatTest.java index 6fbff78cc7..52e89e58db 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/GoogleJavaFormatTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/GoogleJavaFormatTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,10 +15,7 @@ */ package com.diffplug.spotless.maven.java; -import static org.junit.jupiter.api.condition.JRE.JAVA_11; - import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledForJreRange; import com.diffplug.spotless.maven.MavenIntegrationHarness; @@ -27,7 +24,7 @@ class GoogleJavaFormatTest extends MavenIntegrationHarness { void specificVersionDefaultStyle() throws Exception { writePomWithJavaSteps( "", - " 1.2", + " 1.17.0", ""); runTest("java/googlejavaformat/JavaCodeFormatted.test"); @@ -37,7 +34,7 @@ void specificVersionDefaultStyle() throws Exception { void specificVersionSpecificStyle() throws Exception { writePomWithJavaSteps( "", - " 1.2", + " 1.17.0", " ", ""); @@ -45,20 +42,46 @@ void specificVersionSpecificStyle() throws Exception { } @Test - @EnabledForJreRange(min = JAVA_11) void specificVersionReflowLongStrings() throws Exception { writePomWithJavaSteps( "", - " 1.8", + " 1.17.0", " true", ""); runTest("java/googlejavaformat/JavaCodeFormattedReflowLongStrings.test"); } + @Test + void specificVersionReorderImports() throws Exception { + writePomWithJavaSteps( + "", + " 1.17.0", + " ", + " true", + ""); + + runTest("java/googlejavaformat/JavaWithReorderImportsEnabledFormatted.test", "java/googlejavaformat/JavaWithReorderImportsUnformatted.test"); + } + + @Test + void specificVersionSkipJavadocFormatting() throws Exception { + writePomWithJavaSteps( + "", + " 1.17.0", + " false", + ""); + + runTest("java/googlejavaformat/JavaCodeFormattedSkipJavadocFormatting.test"); + } + private void runTest(String targetResource) throws Exception { + runTest(targetResource, "java/googlejavaformat/JavaCodeUnformatted.test"); + } + + private void runTest(String targetResource, String sourceResource) throws Exception { String path = "src/main/java/test.java"; - setFile(path).toResource("java/googlejavaformat/JavaCodeUnformatted.test"); + setFile(path).toResource(sourceResource); mavenRunner().withArguments("spotless:apply").runNoError(); assertFile(path).sameAsResource(targetResource); } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/JavaDefaultIncludesTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/JavaDefaultIncludesTest.java new file mode 100644 index 0000000000..57f717ad07 --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/JavaDefaultIncludesTest.java @@ -0,0 +1,151 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.java; + +import java.io.IOException; +import java.util.Arrays; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.maven.MavenIntegrationHarness; + +public class JavaDefaultIncludesTest extends MavenIntegrationHarness { + + private static final String UNFORMATTED = "java/removeunusedimports/JavaCodeWithPackageUnformatted.test"; + private static final String FORMATTED = "java/removeunusedimports/JavaCodeWithPackageFormatted.test"; + + private static final String FILE_1 = "src/main/java/com/diffplug/spotless/One.java"; + private static final String FILE_2 = "src/test/java/com/diffplug/spotless/Two.java"; + private static final String FILE_3 = "src/com/diffplug/spotless/Three.java"; + private static final String FILE_4 = "test/com/diffplug/spotless/Four.java"; + private static final String FILE_5 = "foo/bar/Five.java"; + + @BeforeEach + void beforeEach() { + for (String file : Arrays.asList(FILE_1, FILE_2, FILE_3, FILE_4, FILE_5)) { + setFile(file).toResource(UNFORMATTED); + } + } + + @Test + void noCustomConfiguration() throws Exception { + writePomWithBuildConfiguration(); + + mavenRunner().withArguments("spotless:apply").runNoError(); + + // Files 1, 2 are formatted because they live under default Maven source & test source dirs + assertFile(FILE_1).sameAsResource(FORMATTED); + assertFile(FILE_2).sameAsResource(FORMATTED); + + // Files 3, 4, 5 are not formatted because they live outside default Maven source & test source dirs + assertFile(FILE_3).sameAsResource(UNFORMATTED); + assertFile(FILE_4).sameAsResource(UNFORMATTED); + assertFile(FILE_5).sameAsResource(UNFORMATTED); + } + + @Test + void customSourceDirConfiguration() throws Exception { + writePomWithBuildConfiguration("src"); + + mavenRunner().withArguments("spotless:apply").runNoError(); + + // Files 1, 2, 3 are formatted because they live under the custom-configured source dir + assertFile(FILE_1).sameAsResource(FORMATTED); + assertFile(FILE_2).sameAsResource(FORMATTED); + assertFile(FILE_3).sameAsResource(FORMATTED); + + // File 4, 5 are not formatted because they live outside the custom-configured source dir and default test source dir + assertFile(FILE_4).sameAsResource(UNFORMATTED); + assertFile(FILE_5).sameAsResource(UNFORMATTED); + } + + @Test + void customTestSourceDirConfiguration() throws Exception { + writePomWithBuildConfiguration("test"); + + mavenRunner().withArguments("spotless:apply").runNoError(); + + // File 1 is formatted because it lives under the default source dir + assertFile(FILE_1).sameAsResource(FORMATTED); + // File 4 is formatted because it lives under the custom-configured test source dir + assertFile(FILE_4).sameAsResource(FORMATTED); + + // Files 2, 3, 5 are not formatted because they live outside the default source dir and custom-configured test source dir + assertFile(FILE_2).sameAsResource(UNFORMATTED); + assertFile(FILE_3).sameAsResource(UNFORMATTED); + assertFile(FILE_5).sameAsResource(UNFORMATTED); + } + + @Test + void customSourceDirAndTestSourceDirConfiguration() throws Exception { + writePomWithBuildConfiguration( + "src", + "test"); + + mavenRunner().withArguments("spotless:apply").runNoError(); + + // Files 1, 2, 3, 4 are formatted because they live under custom-configured source and test source dirs + assertFile(FILE_1).sameAsResource(FORMATTED); + assertFile(FILE_2).sameAsResource(FORMATTED); + assertFile(FILE_3).sameAsResource(FORMATTED); + assertFile(FILE_4).sameAsResource(FORMATTED); + + // File 5 is not formatted because it lives outside custom-configured source and test source dirs + assertFile(FILE_5).sameAsResource(UNFORMATTED); + } + + @Test + void sameCustomSourceDirAndTestSourceDirConfiguration() throws Exception { + writePomWithBuildConfiguration( + "foo/bar", + "foo/bar"); + + mavenRunner().withArguments("spotless:apply").runNoError(); + + // Files 1, 2, 3, 4 are not formatted because they live outside custom-configured source and test source dirs + assertFile(FILE_1).sameAsResource(UNFORMATTED); + assertFile(FILE_2).sameAsResource(UNFORMATTED); + assertFile(FILE_3).sameAsResource(UNFORMATTED); + assertFile(FILE_4).sameAsResource(UNFORMATTED); + + // File 5 is formatted because it lives under the custom-configured source/test source dir + assertFile(FILE_5).sameAsResource(FORMATTED); + } + + @Test + void nestedCustomSourceDirAndTestSourceDirConfiguration() throws Exception { + writePomWithBuildConfiguration( + "foo", + "foo/bar"); + + mavenRunner().withArguments("spotless:apply").runNoError(); + + // Files 1, 2, 3, 4 are not formatted because they live outside custom-configured source and test source dirs + assertFile(FILE_1).sameAsResource(UNFORMATTED); + assertFile(FILE_2).sameAsResource(UNFORMATTED); + assertFile(FILE_3).sameAsResource(UNFORMATTED); + assertFile(FILE_4).sameAsResource(UNFORMATTED); + + // File 5 is formatted because it lives under the custom-configured source/test source dir + assertFile(FILE_5).sameAsResource(FORMATTED); + } + + private void writePomWithBuildConfiguration(String... build) throws IOException { + String xml = createPomXmlContent(build, new String[]{"", "", ""}); + setFile("pom.xml").toContent(xml); + } +} diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/PalantirJavaFormatTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/PalantirJavaFormatTest.java index 33ec8ea84f..eee4fab8c5 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/PalantirJavaFormatTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/java/PalantirJavaFormatTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2022 DiffPlug + * Copyright 2022-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,10 +15,7 @@ */ package com.diffplug.spotless.maven.java; -import static org.junit.jupiter.api.condition.JRE.JAVA_11; - import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledForJreRange; import com.diffplug.spotless.maven.MavenIntegrationHarness; @@ -30,23 +27,33 @@ void specificVersionDefaultStyle() throws Exception { " 1.1.0", ""); - runTest("java/palantirjavaformat/JavaCodeFormatted.test"); + runTest("java/palantirjavaformat/JavaCodeFormatted.test", "java/palantirjavaformat/JavaCodeUnformatted.test"); } @Test - @EnabledForJreRange(min = JAVA_11) void specificJava11Version2() throws Exception { writePomWithJavaSteps( "", - " 2.10.0", + " 2.39.0", + ""); + + runTest("java/palantirjavaformat/JavaCodeFormatted.test", "java/palantirjavaformat/JavaCodeUnformatted.test"); + } + + @Test + void formatJavaDoc() throws Exception { + writePomWithJavaSteps( + "", + " 2.39.0", + " true", ""); - runTest("java/palantirjavaformat/JavaCodeFormatted.test"); + runTest("java/palantirjavaformat/JavaCodeWithJavaDocFormatted.test", "java/palantirjavaformat/JavaCodeWithJavaDocUnformatted.test"); } - private void runTest(String targetResource) throws Exception { + private void runTest(String targetResource, String sourceResource) throws Exception { String path = "src/main/java/test.java"; - setFile(path).toResource("java/palantirjavaformat/JavaCodeUnformatted.test"); + setFile(path).toResource(sourceResource); mavenRunner().withArguments("spotless:apply").runNoError(); assertFile(path).sameAsResource(targetResource); } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/javascript/JavascriptFormatStepTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/javascript/JavascriptFormatStepTest.java new file mode 100644 index 0000000000..d738e17dac --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/javascript/JavascriptFormatStepTest.java @@ -0,0 +1,140 @@ +/* + * Copyright 2016-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.javascript; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.diffplug.spotless.ProcessRunner; +import com.diffplug.spotless.ResourceHarness; +import com.diffplug.spotless.maven.MavenIntegrationHarness; +import com.diffplug.spotless.npm.EslintFormatterStep; +import com.diffplug.spotless.npm.EslintStyleGuide; +import com.diffplug.spotless.tag.NpmTest; + +@NpmTest +class JavascriptFormatStepTest extends MavenIntegrationHarness { + + private static final String TEST_FILE_PATH = "src/main/javascript/test.js"; + + private static String styleGuideDevDependenciesString(String styleGuideName) { + return EslintStyleGuide.fromNameOrNull(styleGuideName).asMavenXmlStringMergedWith(EslintFormatterStep.defaultDevDependencies()); + } + + @NpmTest + @Nested + class EslintCustomRulesTest extends MavenIntegrationHarness { + + @Test + void eslintConfigFile() throws Exception { + writePomWithJavascriptSteps( + TEST_FILE_PATH, + "", + " .eslintrc.js", + ""); + setFile(".eslintrc.js").toResource("npm/eslint/javascript/custom_rules/.eslintrc.js"); + setFile(TEST_FILE_PATH).toResource("npm/eslint/javascript/custom_rules/javascript-es6.dirty"); + + ProcessRunner.Result result = mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile(TEST_FILE_PATH).sameAsResource("npm/eslint/javascript/custom_rules/javascript-es6.clean"); + } + + @Test + void eslintConfigJs() throws Exception { + final String configJs = ResourceHarness.getTestResource("npm/eslint/javascript/custom_rules/.eslintrc.js") + .replace("module.exports = ", ""); + writePomWithJavascriptSteps( + TEST_FILE_PATH, + "", + " " + configJs + "", + ""); + setFile(TEST_FILE_PATH).toResource("npm/eslint/javascript/custom_rules/javascript-es6.dirty"); + + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile(TEST_FILE_PATH).sameAsResource("npm/eslint/javascript/custom_rules/javascript-es6.clean"); + } + + } + + @NpmTest + @Nested + class EslintStyleguidesTest extends MavenIntegrationHarness { + + @ParameterizedTest(name = "{index}: eslint js formatting with configFile using styleguide {0}") + @ValueSource(strings = {"airbnb", "google", "standard", "xo"}) + void eslintJsStyleguideUsingConfigFile(String styleGuide) throws Exception { + final String styleGuidePath = "npm/eslint/javascript/styleguide/" + styleGuide; + + writePomWithJavascriptSteps( + TEST_FILE_PATH, + "", + " .eslintrc.js", + " " + styleGuideDevDependenciesString(styleGuide), + ""); + setFile(".eslintrc.js").toResource(styleGuidePath + "/.eslintrc.js"); + setFile(TEST_FILE_PATH).toResource(styleGuidePath + "/javascript-es6.dirty"); + + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile(TEST_FILE_PATH).sameAsResource(styleGuidePath + "/javascript-es6.clean"); + } + + @ParameterizedTest(name = "{index}: eslint js formatting with inline config using styleguide {0}") + @ValueSource(strings = {"airbnb", "google", "standard", "xo"}) + void eslintJsStyleguideUsingInlineConfig(String styleGuide) throws Exception { + final String styleGuidePath = "npm/eslint/javascript/styleguide/" + styleGuide; + + final String escapedInlineConfig = ResourceHarness.getTestResource(styleGuidePath + "/.eslintrc.js") + .replace("<", "<") + .replace(">", ">"); + writePomWithJavascriptSteps( + TEST_FILE_PATH, + "", + " " + escapedInlineConfig + "", + " " + styleGuideDevDependenciesString(styleGuide), + ""); + setFile(TEST_FILE_PATH).toResource(styleGuidePath + "/javascript-es6.dirty"); + + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile(TEST_FILE_PATH).sameAsResource(styleGuidePath + "/javascript-es6.clean"); + } + + @Test + void provideCustomDependenciesForStyleguideStandard() throws Exception { + final String styleGuidePath = "npm/eslint/javascript/styleguide/standard"; + + writePomWithJavascriptSteps( + TEST_FILE_PATH, + "", + " .eslintrc.js", + " ", + " 8.28.0", + " 17.0.0", + " 2.26.0", + " 15.6.0", + " 6.1.1", + " ", + ""); + setFile(".eslintrc.js").toResource(styleGuidePath + "/.eslintrc.js"); + + setFile(TEST_FILE_PATH).toResource(styleGuidePath + "/javascript-es6.dirty"); + + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile(TEST_FILE_PATH).sameAsResource(styleGuidePath + "/javascript-es6.clean"); + } + } +} diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/json/JsonTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/json/JsonTest.java new file mode 100644 index 0000000000..4c2f05532c --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/json/JsonTest.java @@ -0,0 +1,112 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.json; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.diffplug.spotless.maven.MavenIntegrationHarness; + +public class JsonTest extends MavenIntegrationHarness { + private static final Logger LOGGER = LoggerFactory.getLogger(JsonTest.class); + + @Test + public void testFormatJson_WithSimple_defaultConfig_sortByKeys() throws Exception { + writePomWithJsonSteps(""); + + setFile("json_test.json").toResource("json/sortByKeysBefore.json"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("json_test.json").sameAsResource("json/sortByKeysAfterDisabled_Simple.json"); + } + + @Test + public void testFormatJson_WithSimple_defaultConfig_nestedObject() throws Exception { + writePomWithJsonSteps(""); + + setFile("json_test.json").toResource("json/nestedObjectBefore.json"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("json_test.json").sameAsResource("json/nestedObjectAfter.json"); + } + + @Test + public void testFormatJson_WithGson_defaultConfig_sortByKeys() throws Exception { + writePomWithJsonSteps(""); + + setFile("json_test.json").toResource("json/sortByKeysBefore.json"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("json_test.json").sameAsResource("json/sortByKeysAfterDisabled.json"); + } + + @Test + public void testFormatJson_WithGson_sortByKeys() throws Exception { + writePomWithJsonSteps("true"); + + setFile("json_test.json").toResource("json/sortByKeysBefore.json"); + + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("json_test.json").sameAsResource("json/sortByKeysAfter.json"); + } + + @Test + public void testFormatJson_WithGson_defaultConfig_nestedObject() throws Exception { + writePomWithJsonSteps(""); + + setFile("json_test.json").toResource("json/nestedObjectBefore.json"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("json_test.json").sameAsResource("json/nestedObjectAfter.json"); + } + + @Test + public void testFormatJson_WithJackson_sortByKeys() throws Exception { + writePomWithJsonSteps("true"); + + setFile("json_test.json").toResource("json/sortByKeysBefore.json"); + + mavenRunner().withArguments("spotless:apply", "-X").runNoError(); + assertFile("json_test.json").sameAsResource("json/sortByKeysAfter_Jackson.json"); + } + + @Test + public void testFormatJson_WithJackson_sortByKeys_spaceAfterKeySeparator() throws Exception { + writePomWithJsonSteps("truetrue"); + + setFile("json_test.json").toResource("json/sortByKeysBefore.json"); + + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("json_test.json").sameAsResource("json/sortByKeysAfter_Jackson_spaceAfterKeySeparator.json"); + } + + @Test + public void testFormatJson_JsonPatch_replaceString() throws Exception { + writePomWithJsonSteps("[{\"op\":\"replace\",\"path\":\"/abc\",\"value\":\"ghi\"}]"); + + setFile("json_test.json").toResource("json/patchObjectBefore.json"); + + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("json_test.json").sameAsResource("json/patchObjectAfterReplaceString.json"); + } + + @Test + public void testFormatJson_JsonPatch_replaceWithObject() throws Exception { + writePomWithJsonSteps("[{\"op\":\"replace\",\"path\":\"/abc\",\"value\":{\"def\":\"ghi\"}}]"); + + setFile("json_test.json").toResource("json/patchObjectBefore.json"); + + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("json_test.json").sameAsResource("json/patchObjectAfterReplaceWithObject.json"); + } +} diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/kotlin/KtfmtTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/kotlin/KtfmtTest.java index 88dcd172e0..e452c5d56b 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/kotlin/KtfmtTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/kotlin/KtfmtTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,14 +15,10 @@ */ package com.diffplug.spotless.maven.kotlin; -import static org.junit.jupiter.api.condition.JRE.JAVA_11; - import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledForJreRange; import com.diffplug.spotless.maven.MavenIntegrationHarness; -@EnabledForJreRange(min = JAVA_11) // ktfmt's dependency, google-java-format 1.8 requires a minimum of JRE 11+. class KtfmtTest extends MavenIntegrationHarness { @Test void testKtfmt() throws Exception { @@ -40,6 +36,15 @@ void testKtfmt() throws Exception { assertFile(path2).sameAsResource("kotlin/ktfmt/basic.clean"); } + @Test + void testContinuation() throws Exception { + writePomWithKotlinSteps(""); + + setFile("src/main/kotlin/main.kt").toResource("kotlin/ktfmt/continuation.dirty"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("src/main/kotlin/main.kt").sameAsResource("kotlin/ktfmt/continuation.clean"); + } + @Test void testKtfmtStyle() throws Exception { writePomWithKotlinSteps(""); diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/kotlin/KtlintTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/kotlin/KtlintTest.java index 887f7a3f50..fe5b0e1b57 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/kotlin/KtlintTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/kotlin/KtlintTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,11 @@ */ package com.diffplug.spotless.maven.kotlin; +import static org.junit.jupiter.api.Assertions.assertTrue; + import org.junit.jupiter.api.Test; +import com.diffplug.spotless.ProcessRunner; import com.diffplug.spotless.maven.MavenIntegrationHarness; class KtlintTest extends MavenIntegrationHarness { @@ -29,4 +32,59 @@ void testKtlint() throws Exception { mavenRunner().withArguments("spotless:apply").runNoError(); assertFile(path).sameAsResource("kotlin/ktlint/basic.clean"); } + + @Test + void testKtlintEditorConfigOverride() throws Exception { + writePomWithKotlinSteps("\n" + + " \n" + + " true\n" + + " true\n" + + " \n" + + ""); + + String path = "src/main/kotlin/Main.kt"; + setFile(path).toResource("kotlin/ktlint/experimentalEditorConfigOverride.dirty"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile(path).sameAsResource("kotlin/ktlint/experimentalEditorConfigOverride.clean"); + } + + @Test + void testReadCodeStyleFromEditorConfigFile() throws Exception { + setFile(".editorconfig").toResource("kotlin/ktlint/ktlint_official/.editorconfig"); + writePomWithKotlinSteps(""); + checkKtlintOfficialStyle(); + } + + @Test + void testSetEditorConfigCanOverrideEditorConfigFile() throws Exception { + setFile(".editorconfig").toResource("kotlin/ktlint/intellij_idea/.editorconfig"); + writePomWithKotlinSteps("\n" + + " \n" + + " ktlint_official\n" + + " \n" + + ""); + checkKtlintOfficialStyle(); + } + + @Test + void testWithCustomRuleSetApply() throws Exception { + writePomWithKotlinSteps("\n" + + " \n" + + " io.nlopez.compose.rules:ktlint:0.3.3\n" + + " \n" + + " \n" + + " Composable\n" + + " \n" + + ""); + setFile("src/main/kotlin/Main.kt").toResource("kotlin/ktlint/listScreen.dirty"); + ProcessRunner.Result result = mavenRunner().withArguments("spotless:check").runHasError(); + assertTrue(result.toString().contains("Composable functions that return Unit should start with an uppercase letter.")); + } + + private void checkKtlintOfficialStyle() throws Exception { + String path = "src/main/kotlin/Main.kt"; + setFile(path).toResource("kotlin/ktlint/experimentalEditorConfigOverride.dirty"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile(path).sameAsResource("kotlin/ktlint/experimentalEditorConfigOverride.ktlintOfficial.clean"); + } } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/markdown/FlexmarkMavenTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/markdown/FlexmarkMavenTest.java index a623ca7f3c..eb1c6cc95f 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/markdown/FlexmarkMavenTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/markdown/FlexmarkMavenTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,8 +26,7 @@ public void testFlexmarkWithDefaultConfig() throws Exception { writePomWithMarkdownSteps(""); setFile("markdown_test.md").toResource("markdown/flexmark/FlexmarkUnformatted.md"); - mavenRunner().withArguments("spotless:apply").runNoError().error(); + mavenRunner().withArguments("spotless:apply").runNoError(); assertFile("markdown_test.md").sameAsResource("markdown/flexmark/FlexmarkFormatted.md"); } - } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmFrontendMavenPlugin.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmFrontendMavenPlugin.java new file mode 100644 index 0000000000..de030ab81c --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmFrontendMavenPlugin.java @@ -0,0 +1,66 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.npm; + +/** + * Helper class to configure a maven pom to use frontend-maven-plugin. + * @see frontend-maven-plugin on github + */ +public final class NpmFrontendMavenPlugin { + + public static final String GROUP_ID = "com.github.eirslett"; + public static final String ARTIFACT_ID = "frontend-maven-plugin"; + public static final String VERSION = "1.11.3"; // for now, we stick with this version, as it is the last to support maven 3.1 (see pom-test.xml.mustache) + + public static final String GOAL_INSTALL_NODE_AND_NPM = "install-node-and-npm"; + + public static final String INSTALL_DIRECTORY = "target"; + + private NpmFrontendMavenPlugin() { + // prevent instantiation + } + + public static String[] pomPluginLines(String nodeVersion, String npmVersion) { + return new String[]{ + "", + String.format(" %s", GROUP_ID), + String.format(" %s", ARTIFACT_ID), + String.format(" %s", VERSION), + " ", + " ", + " install node and npm", + " ", + String.format(" %s", GOAL_INSTALL_NODE_AND_NPM), + " ", + " ", + " ", + " ", + (nodeVersion != null ? " " + nodeVersion + "" : ""), + (npmVersion != null ? " " + npmVersion + "" : ""), + String.format(" %s", INSTALL_DIRECTORY), + " ", + "" + }; + } + + public static String installNpmMavenGoal() { + return String.format("%s:%s:%s", GROUP_ID, ARTIFACT_ID, GOAL_INSTALL_NODE_AND_NPM); + } + + public static String installedNpmPath() { + return String.format("%s/node/npm%s", INSTALL_DIRECTORY, System.getProperty("os.name").toLowerCase().contains("win") ? ".cmd" : ""); + } +} diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmStepsWithNpmInstallCacheTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmStepsWithNpmInstallCacheTest.java new file mode 100644 index 0000000000..aae587aadb --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmStepsWithNpmInstallCacheTest.java @@ -0,0 +1,188 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.npm; + +import static com.diffplug.spotless.maven.npm.AbstractNpmFormatterStepFactory.SPOTLESS_NPM_INSTALL_CACHE_DEFAULT_NAME; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.ProcessRunner.Result; +import com.diffplug.spotless.maven.MavenIntegrationHarness; +import com.diffplug.spotless.tag.NpmTest; + +@NpmTest +public class NpmStepsWithNpmInstallCacheTest extends MavenIntegrationHarness { + + // TODO implement tests without cache and with various cache paths + // using only prettier is enough since the other cases are covered by gradle-side integration tests + + @Test + void prettierTypescriptWithoutCache() throws Exception { + String suffix = "ts"; + writePomWithPrettierSteps("**/*." + suffix, + "", + " 1.16.4", + " .prettierrc.yml", + ""); + Result result = run("typescript", suffix); + Assertions.assertThat(result.stdOutUtf8()).doesNotContain("Caching node_modules for").doesNotContain("Using cached node_modules for"); + } + + @Test + void prettierTypescriptWithDefaultCache() throws Exception { + String suffix = "ts"; + writePomWithPrettierSteps("**/*." + suffix, + "", + " 1.16.4", + " .prettierrc.yml", + " true", + ""); + Result result = run("typescript", suffix); + Assertions.assertThat(result.stdOutUtf8()) + .contains("Caching node_modules for") + .contains(SPOTLESS_NPM_INSTALL_CACHE_DEFAULT_NAME) + .doesNotContain("Using cached node_modules for"); + } + + @Test + void prettierTypescriptWithDefaultCacheIsReusedOnSecondRun() throws Exception { + String suffix = "ts"; + writePomWithPrettierSteps("**/*." + suffix, + "", + " 1.16.4", + " .prettierrc.yml", + " true", + ""); + Result result1 = run("typescript", suffix); + Assertions.assertThat(result1.stdOutUtf8()) + .contains("Caching node_modules for") + .contains(SPOTLESS_NPM_INSTALL_CACHE_DEFAULT_NAME) + .doesNotContain("Using cached node_modules for"); + + // recursively delete target folder to simulate a fresh run (except the default cache folder) + recursiveDelete(Paths.get(rootFolder().getAbsolutePath(), "target"), SPOTLESS_NPM_INSTALL_CACHE_DEFAULT_NAME); + + Result result2 = run("typescript", suffix); + Assertions.assertThat(result2.stdOutUtf8()) + .doesNotContain("Caching node_modules for") + .contains(SPOTLESS_NPM_INSTALL_CACHE_DEFAULT_NAME) + .contains("Using cached node_modules for"); + } + + @Test + void prettierTypescriptWithSpecificCache() throws Exception { + String suffix = "ts"; + File cacheDir = newFolder("cache-prettier-1"); + writePomWithPrettierSteps("**/*." + suffix, + "", + " 1.16.4", + " .prettierrc.yml", + " " + cacheDir.getAbsolutePath() + "", + ""); + Result result = run("typescript", suffix); + Assertions.assertThat(result.stdOutUtf8()) + .contains("Caching node_modules for") + .contains(Path.of(cacheDir.getAbsolutePath()).toAbsolutePath().toString()) + .doesNotContain("Using cached node_modules for"); + } + + @Test + void prettierTypescriptWithSpecificCacheIsUsedOnSecondRun() throws Exception { + String suffix = "ts"; + File cacheDir = newFolder("cache-prettier-1"); + writePomWithPrettierSteps("**/*." + suffix, + "", + " 1.16.4", + " .prettierrc.yml", + " " + cacheDir.getAbsolutePath() + "", + ""); + Result result1 = run("typescript", suffix); + Assertions.assertThat(result1.stdOutUtf8()) + .contains("Caching node_modules for") + .contains(Path.of(cacheDir.getAbsolutePath()).toAbsolutePath().toString()) + .doesNotContain("Using cached node_modules for"); + + // recursively delete target folder to simulate a fresh run + recursiveDelete(Paths.get(rootFolder().getAbsolutePath(), "target"), null); + + Result result2 = run("typescript", suffix); + Assertions.assertThat(result2.stdOutUtf8()) + .doesNotContain("Caching node_modules for") + .contains(Path.of(cacheDir.getAbsolutePath()).toAbsolutePath().toString()) + .contains("Using cached node_modules for"); + } + + private void recursiveDelete(Path path, String exclusion) throws IOException { + Files.walkFileTree(path, new RecursiveDelete(exclusion)); + } + + private Result run(String kind, String suffix) throws IOException, InterruptedException { + String path = prepareRun(kind, suffix); + Result result = mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile(path).sameAsResource("npm/prettier/filetypes/" + kind + "/" + kind + ".clean"); + return result; + } + + private String prepareRun(String kind, String suffix) throws IOException { + String configPath = ".prettierrc.yml"; + setFile(configPath).toResource("npm/prettier/filetypes/" + kind + "/" + ".prettierrc.yml"); + String path = "src/main/" + kind + "/test." + suffix; + setFile(path).toResource("npm/prettier/filetypes/" + kind + "/" + kind + ".dirty"); + return path; + } + + private static class RecursiveDelete extends SimpleFileVisitor { + private final String exclusionDirectory; + + public RecursiveDelete(String exclusionDirectory) { + this.exclusionDirectory = exclusionDirectory; + } + + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + if (exclusionDirectory != null && dir.toFile().getName().equals(exclusionDirectory)) { + return FileVisitResult.SKIP_SUBTREE; + } + return super.preVisitDirectory(dir, attrs); + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return super.visitFile(file, attrs); + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + if (dir.toFile().listFiles().length != 0) { + // skip non-empty dir + return super.postVisitDirectory(dir, exc); + } + Files.delete(dir); + return super.postVisitDirectory(dir, exc); + } + } +} diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmTestsWithDynamicallyInstalledNpmInstallationTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmTestsWithDynamicallyInstalledNpmInstallationTest.java new file mode 100644 index 0000000000..78830bf09d --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/npm/NpmTestsWithDynamicallyInstalledNpmInstallationTest.java @@ -0,0 +1,48 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.npm; + +import static com.diffplug.spotless.maven.npm.NpmFrontendMavenPlugin.installNpmMavenGoal; +import static com.diffplug.spotless.maven.npm.NpmFrontendMavenPlugin.installedNpmPath; +import static com.diffplug.spotless.maven.npm.NpmFrontendMavenPlugin.pomPluginLines; + +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.maven.MavenIntegrationHarness; + +public class NpmTestsWithDynamicallyInstalledNpmInstallationTest extends MavenIntegrationHarness { + + @Test + void useDownloadedNpmInstallation() throws Exception { + writePomWithPrettierSteps( + pomPluginLines("v18.13.0", null), + "src/main/typescript/test.ts", + "", + " " + installedNpmPath() + "", + ""); + + String kind = "typescript"; + String suffix = "ts"; + String configPath = ".prettierrc.yml"; + setFile(configPath).toResource("npm/prettier/filetypes/" + kind + "/" + ".prettierrc.yml"); + String path = "src/main/" + kind + "/test." + suffix; + setFile(path).toResource("npm/prettier/filetypes/" + kind + "/" + kind + ".dirty"); + + mavenRunner().withArguments(installNpmMavenGoal(), "spotless:apply").runNoError(); + assertFile(path).sameAsResource("npm/prettier/filetypes/" + kind + "/" + kind + ".clean"); + } + +} diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/pom/SortPomMavenTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/pom/SortPomMavenTest.java index ca9d77a6e3..93eb8a9633 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/pom/SortPomMavenTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/pom/SortPomMavenTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ public void testSortPomWithDefaultConfig() throws Exception { writePomWithPomSteps(""); setFile("pom_test.xml").toResource("pom/pom_dirty.xml"); - mavenRunner().withArguments("spotless:apply").runNoError().error(); + mavenRunner().withArguments("spotless:apply").runNoError(); assertFile("pom_test.xml").sameAsResource("pom/pom_clean_default.xml"); } } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/prettier/PrettierFormatStepTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/prettier/PrettierFormatStepTest.java index 130bbde600..abba35e72c 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/prettier/PrettierFormatStepTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/prettier/PrettierFormatStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,8 @@ import org.junit.jupiter.api.Test; +import com.diffplug.spotless.ProcessRunner; import com.diffplug.spotless.maven.MavenIntegrationHarness; -import com.diffplug.spotless.maven.MavenRunner.Result; import com.diffplug.spotless.maven.generic.Prettier; import com.diffplug.spotless.tag.NpmTest; @@ -43,7 +43,7 @@ private String prepareRun(String kind, String suffix) throws IOException { return path; } - private Result runExpectingError(String kind, String suffix) throws IOException, InterruptedException { + private ProcessRunner.Result runExpectingError(String kind, String suffix) throws IOException, InterruptedException { String path = prepareRun(kind, suffix); return mavenRunner().withArguments("spotless:apply").runHasError(); } @@ -102,8 +102,60 @@ void unique_dependency_config() throws Exception { " 1.16.4", ""); - Result result = mavenRunner().withArguments("spotless:apply").runHasError(); - assertThat(result.output()).contains(Prettier.ERROR_MESSAGE_ONLY_ONE_CONFIG); + ProcessRunner.Result result = mavenRunner().withArguments("spotless:apply").runHasError(); + assertThat(result.stdOutUtf8()).contains(Prettier.ERROR_MESSAGE_ONLY_ONE_CONFIG); + } + + /** + * This test is to ensure that we can have multiple prettier instances in one spotless config. + * + * @see Issue #1162 on github + */ + @Test + void multiple_prettier_configs() throws Exception { + writePom( + formats( + groupWithSteps("format", including("php-example.php"), + "", + " ", + " ", + " prettier", + " 2.8.8", + " ", + " ", + " @prettier/plugin-php", + " 0.19.6", + " ", + " ", + " ", + " 3", + " php", + " ", + ""), + groupWithSteps("java", including("JavaTest.java"), + "", + " ", + " ", + " prettier", + " 2.8.8", + " ", + " ", + " prettier-plugin-java", + " 2.2.0", + " ", + " ", + " ", + " 4", + " java", + " ", + ""))); + + setFile("php-example.php").toResource("npm/prettier/plugins/php.dirty"); + setFile("JavaTest.java").toResource("npm/prettier/plugins/java-test.dirty"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("php-example.php").sameAsResource("npm/prettier/plugins/php.clean"); + assertFile("JavaTest.java").sameAsResource("npm/prettier/plugins/java-test.clean"); + } @Test @@ -114,16 +166,43 @@ void custom_plugin() throws Exception { " ", " ", " prettier", - " 2.0.5", + " 2.8.8", + " ", + " ", + " @prettier/plugin-php", + " 0.19.6", + " ", + " ", + " ", + " 3", + " php", + " ", + ""); + + setFile("php-example.php").toResource("npm/prettier/plugins/php.dirty"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("php-example.php").sameAsResource("npm/prettier/plugins/php.clean"); + } + + @Test + void custom_plugin_prettier3() throws Exception { + writePomWithFormatSteps( + "php-example.php", + "", + " ", + " ", + " prettier", + " 3.0.3", " ", " ", " @prettier/plugin-php", - " 0.14.2", + " 0.20.1", " ", " ", " ", " 3", " php", + " @prettier/plugin-php", " ", ""); @@ -132,6 +211,29 @@ void custom_plugin() throws Exception { assertFile("php-example.php").sameAsResource("npm/prettier/plugins/php.clean"); } + @Test + void custom_plugin_prettier3_file_config() throws Exception { + writePomWithFormatSteps( + "JavaTest.java", + "", + " ", + " ", + " prettier", + " 3.0.3", + " ", + " ", + " prettier-plugin-java", + " 2.3.0", + " ", + " ", + " .prettierrc.yml", + ""); + setFile(".prettierrc.yml").toResource("npm/prettier/config/.prettierrc_java_plugin.yml"); + setFile("JavaTest.java").toResource("npm/prettier/plugins/java-test.dirty"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("JavaTest.java").sameAsResource("npm/prettier/plugins/java-test.clean"); + } + @Test void autodetect_parser_based_on_filename() throws Exception { writePomWithFormatSteps( @@ -145,20 +247,28 @@ void autodetect_parser_based_on_filename() throws Exception { @Test void autodetect_npmrc_file() throws Exception { - setFile(".npmrc").toLines("registry=https://i.do.no.exist.com"); + setFile(".npmrc").toLines( + "registry=https://i.do.not.exist.com", + "fetch-timeout=250", + "fetch-retry-mintimeout=250", + "fetch-retry-maxtimeout=250"); String suffix = "ts"; writePomWithPrettierSteps("**/*." + suffix, "", " 1.16.4", " .prettierrc.yml", ""); - Result result = runExpectingError("typescript", suffix); - assertThat(result.output()).containsPattern("Running npm command.*npm install.* failed with exit code: 1"); + ProcessRunner.Result result = runExpectingError("typescript", suffix); + assertThat(result.stdOutUtf8()).containsPattern("Running npm command.*npm install.* failed with exit code: 1"); } @Test void select_configured_npmrc_file() throws Exception { - setFile(".custom_npmrc").toLines("registry=https://i.do.no.exist.com"); + setFile(".custom_npmrc").toLines( + "registry=https://i.do.not.exist.com", + "fetch-timeout=250", + "fetch-retry-mintimeout=250", + "fetch-retry-maxtimeout=250"); String suffix = "ts"; writePomWithPrettierSteps("**/*." + suffix, "", @@ -166,7 +276,7 @@ void select_configured_npmrc_file() throws Exception { " .prettierrc.yml", " ${basedir}/.custom_npmrc", ""); - Result result = runExpectingError("typescript", suffix); - assertThat(result.output()).containsPattern("Running npm command.*npm install.* failed with exit code: 1"); + ProcessRunner.Result result = runExpectingError("typescript", suffix); + assertThat(result.stdOutUtf8()).containsPattern("Running npm command.*npm install.* failed with exit code: 1"); } } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/rome/RomeMavenTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/rome/RomeMavenTest.java new file mode 100644 index 0000000000..f6a8107be1 --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/rome/RomeMavenTest.java @@ -0,0 +1,203 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.rome; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.owasp.encoder.Encode.forXml; + +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.maven.MavenIntegrationHarness; + +@Deprecated +class RomeMavenTest extends MavenIntegrationHarness { + /** + * Tests that rome can be used as a generic formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void asGenericStep() throws Exception { + writePomWithRomeSteps("**/*.js", "12.0.0"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("rome_test.js").sameAsResource("rome/js/fileAfter.js"); + } + + /** + * Tests that rome can be used as a JavaScript formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void asJavaScriptStep() throws Exception { + writePomWithJavascriptSteps("**/*.js", "12.0.0"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("rome_test.js").sameAsResource("rome/js/fileAfter.js"); + } + + /** + * Tests that rome can be used as a JSON formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void asJsonStep() throws Exception { + writePomWithJsonSteps("**/*.json", "12.0.0"); + setFile("rome_test.json").toResource("rome/json/fileBefore.json"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("rome_test.json").sameAsResource("rome/json/fileAfter.json"); + } + + /** + * Tests that rome can be used as a TypeScript formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void asTypeScriptStep() throws Exception { + writePomWithTypescriptSteps("**/*.ts", "12.0.0"); + setFile("rome_test.ts").toResource("rome/ts/fileBefore.ts"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("rome_test.ts").sameAsResource("rome/ts/fileAfter.ts"); + } + + /** + * Tests that the language can be specified for the generic format step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void canSetLanguageForGenericStep() throws Exception { + writePomWithRomeSteps("**/*.nosj", "12.0.0json"); + setFile("rome_test.nosj").toResource("rome/json/fileBefore.json"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("rome_test.nosj").sameAsResource("rome/json/fileAfter.json"); + } + + /** + * Tests that an absolute config path can be specified. + * + * @throws Exception When a test failure occurs. + */ + @Test + void configPathAbsolute() throws Exception { + var path = newFile("configs").getAbsolutePath(); + writePomWithRomeSteps("**/*.js", + "12.0.0" + forXml(path) + ""); + setFile("rome_test.js").toResource("rome/js/longLineBefore.js"); + setFile("configs/rome.json").toResource("rome/config/line-width-120.json"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("rome_test.js").sameAsResource("rome/js/longLineAfter120.js"); + } + + /** + * Tests that a path to the directory with the rome.json config file can be + * specified. Uses a config file with a line width of 120. + * + * @throws Exception When a test failure occurs. + */ + @Test + void configPathLineWidth120() throws Exception { + writePomWithRomeSteps("**/*.js", "12.0.0configs"); + setFile("rome_test.js").toResource("rome/js/longLineBefore.js"); + setFile("configs/rome.json").toResource("rome/config/line-width-120.json"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("rome_test.js").sameAsResource("rome/js/longLineAfter120.js"); + } + + /** + * Tests that a path to the directory with the rome.json config file can be + * specified. Uses a config file with a line width of 80. + * + * @throws Exception When a test failure occurs. + */ + @Test + void configPathLineWidth80() throws Exception { + writePomWithRomeSteps("**/*.js", "12.0.0configs"); + setFile("rome_test.js").toResource("rome/js/longLineBefore.js"); + setFile("configs/rome.json").toResource("rome/config/line-width-80.json"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("rome_test.js").sameAsResource("rome/js/longLineAfter80.js"); + } + + /** + * Tests that the download directory can be an absolute path. + * + * @throws Exception When a test failure occurs. + */ + @Test + void downloadDirAbsolute() throws Exception { + var path = newFile("target/bin/rome").getAbsoluteFile().toString(); + writePomWithRomeSteps("**/*.js", + "12.0.0" + forXml(path) + ""); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + assertTrue(!newFile("target/bin/rome").exists() || newFile("target/bin/rome").list().length == 0); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("rome_test.js").sameAsResource("rome/js/fileAfter.js"); + assertEquals(2, newFile("target/bin/rome").list().length); + } + + /** + * Tests that the download directory can be changed to a path relative to the + * project's base directory. + * + * @throws Exception When a test failure occurs. + */ + @Test + void downloadDirRelative() throws Exception { + writePomWithRomeSteps("**/*.js", + "12.0.0target/bin/rome"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + assertTrue(!newFile("target/bin/rome").exists() || newFile("target/bin/rome").list().length == 0); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("rome_test.js").sameAsResource("rome/js/fileAfter.js"); + assertEquals(2, newFile("target/bin/rome").list().length); + } + + /** + * Tests that the build fails when the input file could not be parsed. + * + * @throws Exception When a test failure occurs. + */ + @Test + void failureWhenExeNotFound() throws Exception { + writePomWithRomeSteps("**/*.js", "12.0.0rome/is/missing"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + var result = mavenRunner().withArguments("spotless:apply").runHasError(); + assertFile("rome_test.js").sameAsResource("rome/js/fileBefore.js"); + assertThat(result.stdOutUtf8()).contains("Biome executable does not exist"); + } + + /** + * Tests that the build fails when the input file could not be parsed. + * + * @throws Exception When a test failure occurs. + */ + @Test + void failureWhenNotParseable() throws Exception { + writePomWithRomeSteps("**/*.js", "12.0.0json"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + var result = mavenRunner().withArguments("spotless:apply").runHasError(); + assertFile("rome_test.js").sameAsResource("rome/js/fileBefore.js"); + assertThat(result.stdOutUtf8()).contains("Format with errors is disabled."); + assertThat(result.stdOutUtf8()).contains("Unable to format file"); + assertThat(result.stdOutUtf8()).contains("Step 'rome' found problem in 'rome_test.js'"); + } +} diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/shell/ShellTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/shell/ShellTest.java new file mode 100644 index 0000000000..bf0e58fca6 --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/shell/ShellTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2024 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.shell; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.diffplug.spotless.maven.MavenIntegrationHarness; +import com.diffplug.spotless.tag.ShfmtTest; + +@ShfmtTest +public class ShellTest extends MavenIntegrationHarness { + private static final Logger LOGGER = LoggerFactory.getLogger(ShellTest.class); + + @Test + public void testFormatShell() throws Exception { + writePomWithShellSteps(""); + setFile("shfmt.sh").toResource("shell/shfmt/shfmt.sh"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("shfmt.sh").sameAsResource("shell/shfmt/shfmt.clean"); + } +} diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/typescript/TypescriptFormatStepTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/typescript/TypescriptFormatStepTest.java index 600f9818d1..a09111d30d 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/typescript/TypescriptFormatStepTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/typescript/TypescriptFormatStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,84 +21,101 @@ import org.junit.jupiter.api.Test; +import com.diffplug.spotless.ProcessRunner; +import com.diffplug.spotless.ResourceHarness; import com.diffplug.spotless.maven.MavenIntegrationHarness; -import com.diffplug.spotless.maven.MavenRunner.Result; +import com.diffplug.spotless.npm.EslintFormatterStep; +import com.diffplug.spotless.npm.EslintStyleGuide; import com.diffplug.spotless.tag.NpmTest; @NpmTest class TypescriptFormatStepTest extends MavenIntegrationHarness { - private void run(String kind) throws IOException, InterruptedException { - String path = prepareRun(kind); + + private static final String TEST_FILE_PATH = "src/main/typescript/test.ts"; + + private static String styleGuideDevDependenciesString(String styleGuideName) { + return EslintStyleGuide.fromNameOrNull(styleGuideName).asMavenXmlStringMergedWith(EslintFormatterStep.defaultDevDependencies()); + } + + private void runTsfmt(String kind) throws IOException, InterruptedException { + String path = prepareRunTsfmt(kind); mavenRunner().withArguments("spotless:apply").runNoError(); assertFile(path).sameAsResource("npm/tsfmt/" + kind + "/" + kind + ".clean"); } - private String prepareRun(String kind) throws IOException { - String path = "src/main/typescript/test.ts"; - setFile(path).toResource("npm/tsfmt/" + kind + "/" + kind + ".dirty"); - return path; + private String prepareRunTsfmt(String kind) throws IOException { + setFile(TEST_FILE_PATH).toResource("npm/tsfmt/" + kind + "/" + kind + ".dirty"); + return TEST_FILE_PATH; } - private Result runExpectingError(String kind) throws IOException, InterruptedException { - prepareRun(kind); + private ProcessRunner.Result runExpectingErrorTsfmt(String kind) throws IOException, InterruptedException { + prepareRunTsfmt(kind); return mavenRunner().withArguments("spotless:apply").runHasError(); } @Test void tslint() throws Exception { writePomWithTypescriptSteps( + TEST_FILE_PATH, "", " ${basedir}/tslint.json", ""); setFile("tslint.json").toResource("npm/tsfmt/tslint/tslint.json"); - run("tslint"); + runTsfmt("tslint"); } @Test void vscode() throws Exception { writePomWithTypescriptSteps( + TEST_FILE_PATH, "", " ${basedir}/vscode.json", ""); setFile("vscode.json").toResource("npm/tsfmt/vscode/vscode.json"); - run("vscode"); + runTsfmt("vscode"); } @Test void tsfmt() throws Exception { writePomWithTypescriptSteps( + TEST_FILE_PATH, "", " ${basedir}/tsfmt.json", ""); setFile("tsfmt.json").toResource("npm/tsfmt/tsfmt/tsfmt.json"); - run("tsfmt"); + runTsfmt("tsfmt"); } @Test void tsfmtInline() throws Exception { writePomWithTypescriptSteps( + TEST_FILE_PATH, "", " ", " 1", " true", " ", ""); - run("tsfmt"); + runTsfmt("tsfmt"); } @Test void tsconfig() throws Exception { writePomWithTypescriptSteps( + TEST_FILE_PATH, "", " ${project.basedir}/tsconfig.json", ""); setFile("tsconfig.json").toResource("npm/tsfmt/tsconfig/tsconfig.json"); - run("tsconfig"); + runTsfmt("tsconfig"); } @Test void testTypescript_2_Configs() throws Exception { + String path = "src/main/typescript/test.ts"; + writePomWithTypescriptSteps( + path, "", " ${basedir}/tslint.json", " ${basedir}/tslint.json", @@ -106,34 +123,106 @@ void testTypescript_2_Configs() throws Exception { setFile("vscode.json").toResource("npm/tsfmt/vscode/vscode.json"); setFile("tsfmt.json").toResource("npm/tsfmt/tsfmt/tsfmt.json"); - String path = "src/main/typescript/test.ts"; setFile(path).toResource("npm/tsfmt/tsfmt/tsfmt.dirty"); - Result result = mavenRunner().withArguments("spotless:apply").runHasError(); - assertThat(result.output()).contains("must specify exactly one configFile or config"); + ProcessRunner.Result result = mavenRunner().withArguments("spotless:apply").runHasError(); + assertThat(result.stdOutUtf8()).contains("must specify exactly one configFile or config"); } @Test void testNpmrcIsAutoPickedUp() throws Exception { - setFile(".npmrc").toLines("registry=https://i.do.no.exist.com"); + setFile(".npmrc").toLines( + "registry=https://i.do.not.exist.com", + "fetch-timeout=250", + "fetch-retry-mintimeout=250", + "fetch-retry-maxtimeout=250"); writePomWithTypescriptSteps( + TEST_FILE_PATH, "", " ${basedir}/tslint.json", ""); setFile("tslint.json").toResource("npm/tsfmt/tslint/tslint.json"); - Result result = runExpectingError("tslint"); - assertThat(result.output()).containsPattern("Running npm command.*npm install.* failed with exit code: 1"); + ProcessRunner.Result result = runExpectingErrorTsfmt("tslint"); + assertThat(result.stdOutUtf8()).containsPattern("Running npm command.*npm install.* failed with exit code: 1"); } @Test void testNpmrcIsConfigurativelyPickedUp() throws Exception { - setFile(".custom_npmrc").toLines("registry=https://i.do.no.exist.com"); + setFile(".custom_npmrc").toLines( + "registry=https://i.do.not.exist.com", + "fetch-timeout=250", + "fetch-retry-mintimeout=250", + "fetch-retry-maxtimeout=250"); writePomWithTypescriptSteps( + TEST_FILE_PATH, "", " ${basedir}/tslint.json", " ${basedir}/.custom_npmrc", ""); setFile("tslint.json").toResource("npm/tsfmt/tslint/tslint.json"); - Result result = runExpectingError("tslint"); - assertThat(result.output()).containsPattern("Running npm command.*npm install.* failed with exit code: 1"); + ProcessRunner.Result result = runExpectingErrorTsfmt("tslint"); + assertThat(result.stdOutUtf8()).containsPattern("Running npm command.*npm install.* failed with exit code: 1"); + } + + @Test + void eslintConfigFile() throws Exception { + writePomWithTypescriptSteps( + TEST_FILE_PATH, + "", + " .eslintrc.js", + ""); + setFile(".eslintrc.js").toResource("npm/eslint/typescript/custom_rules/.eslintrc.js"); + setFile(TEST_FILE_PATH).toResource("npm/eslint/typescript/custom_rules/typescript.dirty"); + + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile(TEST_FILE_PATH).sameAsResource("npm/eslint/typescript/custom_rules/typescript.clean"); + } + + @Test + void eslintConfigJs() throws Exception { + final String configJs = ResourceHarness.getTestResource("npm/eslint/typescript/custom_rules/.eslintrc.js") + .replace("module.exports = ", ""); + writePomWithTypescriptSteps( + TEST_FILE_PATH, + "", + " " + configJs + "", + ""); + setFile(TEST_FILE_PATH).toResource("npm/eslint/typescript/custom_rules/typescript.dirty"); + + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile(TEST_FILE_PATH).sameAsResource("npm/eslint/typescript/custom_rules/typescript.clean"); + } + + @Test + void eslintStyleguideStandardWithTypescript() throws Exception { + writePomWithTypescriptSteps( + TEST_FILE_PATH, + "", + " .eslintrc.js", + " " + styleGuideDevDependenciesString("standard-with-typescript"), + " ${basedir}/tsconfig.json", + ""); + setFile(".eslintrc.js").toResource("npm/eslint/typescript/styleguide/standard_with_typescript/.eslintrc.js"); + setFile("tsconfig.json").toResource("npm/eslint/typescript/styleguide/standard_with_typescript/tsconfig.json"); + setFile(TEST_FILE_PATH).toResource("npm/eslint/typescript/styleguide/standard_with_typescript/typescript.dirty"); + + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile(TEST_FILE_PATH).sameAsResource("npm/eslint/typescript/styleguide/standard_with_typescript/typescript.clean"); + } + + @Test + void eslintStyleguideXo() throws Exception { + writePomWithTypescriptSteps( + TEST_FILE_PATH, + "", + " .eslintrc.js", + " " + styleGuideDevDependenciesString("xo-typescript"), + " ${basedir}/tsconfig.json", + ""); + setFile(".eslintrc.js").toResource("npm/eslint/typescript/styleguide/xo/.eslintrc.js"); + setFile("tsconfig.json").toResource("npm/eslint/typescript/styleguide/xo/tsconfig.json"); + setFile(TEST_FILE_PATH).toResource("npm/eslint/typescript/styleguide/xo/typescript.dirty"); + + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile(TEST_FILE_PATH).sameAsResource("npm/eslint/typescript/styleguide/xo/typescript.clean"); } } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java new file mode 100644 index 0000000000..6af4d09ce9 --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/yaml/YamlTest.java @@ -0,0 +1,53 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.maven.yaml; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.diffplug.spotless.maven.MavenIntegrationHarness; + +public class YamlTest extends MavenIntegrationHarness { + private static final Logger LOGGER = LoggerFactory.getLogger(YamlTest.class); + + @Test + public void testFormatYaml_WithJackson_defaultConfig_separatorComments() throws Exception { + writePomWithYamlSteps(""); + + setFile("yaml_test.yaml").toResource("yaml/separator_comments.yaml"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("yaml_test.yaml").sameAsResource("yaml/separator_comments.clean.yaml"); + } + + @Test + public void testFormatYaml_WithJackson_defaultConfig_arrayBrackets() throws Exception { + writePomWithYamlSteps(""); + + setFile("yaml_test.yaml").toResource("yaml/array_with_bracket.yaml"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("yaml_test.yaml").sameAsResource("yaml/array_with_bracket.clean.yaml"); + } + + @Test + public void testFormatYaml_WithJackson_defaultConfig_multipleDocuments() throws Exception { + writePomWithYamlSteps(""); + + setFile("yaml_test.yaml").toResource("yaml/multiple_documents.yaml"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("yaml_test.yaml").sameAsResource("yaml/multiple_documents.clean.jackson.yaml"); + } +} diff --git a/plugin-maven/src/test/resources/pom-build.xml.mustache b/plugin-maven/src/test/resources/pom-build.xml.mustache deleted file mode 100644 index 3c1d1a7d5b..0000000000 --- a/plugin-maven/src/test/resources/pom-build.xml.mustache +++ /dev/null @@ -1,96 +0,0 @@ - - 4.0.0 - - com.diffplug.spotless - spotless-maven-plugin - {{spotlessMavenPluginVersion}} - maven-plugin - - Spotless Maven Plugin - - - - 3.1.0 - - - - UTF-8 - 1.8 - 1.8 - - - {{mavenApiVersion}} - {{eclipseAetherVersion}} - {{spotlessLibVersion}} - {{jsr305Version}} - - - - - org.apache.maven - maven-core - ${maven.api.version} - provided - - - org.apache.maven - maven-plugin-api - ${maven.api.version} - provided - - - org.apache.maven.plugin-tools - maven-plugin-annotations - ${maven.api.version} - provided - - - org.eclipse.aether - aether-api - ${eclipse.aether.version} - provided - - - org.eclipse.aether - aether-util - ${eclipse.aether.version} - provided - - - com.google.code.findbugs - jsr305 - ${jsr305.version} - provided - - - - com.diffplug.spotless - spotless-lib - ${spotless.lib.version} - - - com.diffplug.spotless - spotless-lib-extra - ${spotless.lib.version} - - -{{{additionalDependencies}}} - - - - - - - - org.apache.maven.plugins - maven-plugin-plugin - 3.5 - - - - - diff --git a/plugin-maven/src/test/resources/pom-test.xml.mustache b/plugin-maven/src/test/resources/pom-test.xml.mustache index 122f5d9218..f87e8c1a3e 100644 --- a/plugin-maven/src/test/resources/pom-test.xml.mustache +++ b/plugin-maven/src/test/resources/pom-test.xml.mustache @@ -20,7 +20,9 @@ + {{{build}}} + {{{plugins}}} com.diffplug.spotless spotless-maven-plugin diff --git a/renovate.json b/renovate.json index 7bd954555f..2b278dfe17 100644 --- a/renovate.json +++ b/renovate.json @@ -2,5 +2,14 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:base" + ], + "packageRules": [ + { + "groupName": "Ktlint", + "enabled": false, + "matchPackagePatterns": [ + "com.pinterest.ktlint:*" + ] + } ] } diff --git a/settings.gradle b/settings.gradle index 91de4f9e48..24e757bdc2 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,35 +1,36 @@ pluginManagement { - plugins { - id 'com.diffplug.spotless' version '6.5.0' - // https://plugins.gradle.org/plugin/com.gradle.plugin-publish - id 'com.gradle.plugin-publish' version '1.0.0' - // https://github.com/gradle-nexus/publish-plugin/releases - id 'io.github.gradle-nexus.publish-plugin' version '1.1.0' - // https://github.com/spotbugs/spotbugs-gradle-plugin/releases - id 'com.github.spotbugs' version '5.0.12' - // https://github.com/diffplug/spotless-changelog - id 'com.diffplug.spotless-changelog' version '2.3.2' - // https://github.com/diffplug/goomph/blob/main/CHANGES.md - id 'com.diffplug.p2.asmaven' version '3.27.0' // DO NOT UPDATE, see https://github.com/diffplug/spotless/pull/874 - // https://github.com/gradle/test-retry-gradle-plugin/releases - id 'org.gradle.test-retry' version '1.4.1' - // https://github.com/radarsh/gradle-test-logger-plugin/blob/develop/CHANGELOG.md - id 'com.adarshr.test-logger' version '3.2.0' - // https://github.com/davidburstrom/version-compatibility-gradle-plugin/tags - id 'io.github.davidburstrom.version-compatibility' version '0.1.0' + repositories { + mavenCentral() + gradlePluginPortal() } } + plugins { - id 'com.diffplug.spotless' apply false - id 'com.gradle.plugin-publish' apply false - id 'io.github.gradle-nexus.publish-plugin' apply false - id 'com.github.spotbugs' apply false - id 'com.diffplug.spotless-changelog' apply false - id 'com.diffplug.p2.asmaven' apply false - id 'org.gradle.test-retry' apply false - id 'com.adarshr.test-logger' apply false - id 'io.github.davidburstrom.version-compatibility' apply false + id 'com.diffplug.spotless' version '6.23.3' apply false + // https://plugins.gradle.org/plugin/com.gradle.plugin-publish + id 'com.gradle.plugin-publish' version '1.2.1' apply false + // https://github.com/gradle-nexus/publish-plugin/releases + id 'io.github.gradle-nexus.publish-plugin' version '2.0.0-rc-1' apply false + // https://github.com/spotbugs/spotbugs-gradle-plugin/releases + id 'com.github.spotbugs' version '6.0.2' apply false + // https://github.com/diffplug/spotless-changelog/blob/main/CHANGELOG.md + id 'com.diffplug.spotless-changelog' version '3.0.2' apply false + // https://github.com/radarsh/gradle-test-logger-plugin/blob/develop/CHANGELOG.md + id 'com.adarshr.test-logger' version '4.0.0' apply false + // https://github.com/davidburstrom/version-compatibility-gradle-plugin/tags + id 'io.github.davidburstrom.version-compatibility' version '0.5.0' apply false + // https://plugins.gradle.org/plugin/com.gradle.enterprise + id 'com.gradle.enterprise' version '3.16' + // https://github.com/equodev/equo-ide/blob/main/plugin-gradle/CHANGELOG.md + id 'dev.equo.ide' version '1.7.5' apply false } + +dependencyResolutionManagement { + repositories { + mavenCentral() + } +} + if (System.env['CI'] != null) { // use the remote buildcache on all CI builds buildCache { @@ -58,6 +59,17 @@ if (System.env['CI'] != null) { } } +gradleEnterprise { + buildScan { + termsOfServiceUrl = "https://gradle.com/terms-of-service" + termsOfServiceAgree = "yes" + publishAlways() + } +} + +enableFeaturePreview("STABLE_CONFIGURATION_CACHE") +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") + rootProject.name = 'spotless' include 'lib' // reusable library with no dependencies @@ -78,37 +90,8 @@ def getStartProperty(java.lang.String name) { userProperties.load(userPropertiesFile.newReader()) } return userProperties.get(name) - } -if (System.getenv('SPOTLESS_EXCLUDE_MAVEN') != 'true' && getStartProperty('SPOTLESS_EXCLUDE_MAVEN') != 'true' && System.getenv('JITPACK') != 'true') { +if (System.getenv('SPOTLESS_EXCLUDE_MAVEN') != 'true' && getStartProperty('SPOTLESS_EXCLUDE_MAVEN') != 'true') { include 'plugin-maven' // maven-specific glue code } - -// include external (_ext) projects from development builds -if (getStartProperty('com.diffplug.spotless.include.ext') == 'true') { - file('_ext').eachDirMatch(~/^(?!(\.|gradle)).*/) { dir -> - include dir.name - project(":${dir.name}").projectDir = dir - } -} - -// include external (_ext) projects from development builds, but disable the p2 parts -if (getStartProperty('com.diffplug.spotless.include.ext.nop2') == 'true') { - file('_ext').eachDirMatch(~/^(?!(\.|gradle)).*/) { dir -> - include dir.name - project(":${dir.name}").projectDir = dir - } -} - -// the p2-based projects are too expensive for routine CI builds, so they have to be invoked explicitly -for (kind in [ - 'cdt', - 'groovy', - 'wtp' - ]) { - if (getStartProperty("com.diffplug.spotless.include.ext.${kind}") == 'true') { - include "eclipse-${kind}" - project(":eclipse-${kind}").projectDir = file("_ext/eclipse-${kind}") - } -} diff --git a/testlib/build.gradle b/testlib/build.gradle index 30c44ece23..ce36b83adb 100644 --- a/testlib/build.gradle +++ b/testlib/build.gradle @@ -6,12 +6,14 @@ version = rootProject.spotlessChangelog.versionNext apply from: rootProject.file('gradle/java-setup.gradle') dependencies { - api project(':lib') - api files(project(':lib').sourceSets.sortPom.output.classesDirs) + api projects.lib + api files(projects.lib.dependencyProject.sourceSets.sortPom.output.classesDirs) api "com.diffplug.durian:durian-core:${VER_DURIAN}" api "com.diffplug.durian:durian-testlib:${VER_DURIAN}" api "org.junit.jupiter:junit-jupiter:${VER_JUNIT}" api "org.assertj:assertj-core:${VER_ASSERTJ}" + api "org.mockito:mockito-core:$VER_MOCKITO" + runtimeOnly "org.junit.platform:junit-platform-launcher" implementation "com.diffplug.durian:durian-io:${VER_DURIAN}" implementation "com.diffplug.durian:durian-collect:${VER_DURIAN}" @@ -19,26 +21,22 @@ dependencies { } // we'll hold the testlib to a low standard (prize brevity) -spotbugs { reportLevel = 'high' } // low|medium|high (low = sensitive to even minor mistakes) +spotbugs { + // LOW|MEDIUM|DEFAULT|HIGH (low = sensitive to even minor mistakes). + reportLevel = com.github.spotbugs.snom.Confidence.valueOf('HIGH') +} -test { - useJUnitPlatform() +apply from: rootProject.file('gradle/special-tests.gradle') +tasks.withType(Test).configureEach { if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_16)) { - // for GJF https://github.com/diffplug/spotless/issues/834 + // for Antlr4FormatterStepTest and KtLintStepTest def args = [ - '--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.parser=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=java.base/java.lang=ALL-UNNAMED' ] jvmArgs args } } -apply from: rootProject.file('gradle/special-tests.gradle') - javadoc { options.addStringOption('Xdoclint:none', '-quiet') options.addStringOption('Xwerror', '-quiet') diff --git a/testlib/src/main/java/com/diffplug/spotless/ResourceHarness.java b/testlib/src/main/java/com/diffplug/spotless/ResourceHarness.java index b4a8bbae24..0304889e1f 100644 --- a/testlib/src/main/java/com/diffplug/spotless/ResourceHarness.java +++ b/testlib/src/main/java/com/diffplug/spotless/ResourceHarness.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.function.Consumer; import java.util.function.UnaryOperator; @@ -54,7 +55,7 @@ protected File rootFolder() { } /** Returns a new child of the root folder. */ - protected File newFile(String subpath) throws IOException { + protected File newFile(String subpath) { return new File(rootFolder(), subpath); } @@ -85,16 +86,25 @@ protected void replace(String path, String toReplace, String replaceWith) throws } /** Returns the contents of the given file from the src/test/resources directory. */ - protected static String getTestResource(String filename) throws IOException { - URL url = ResourceHarness.class.getResource("/" + filename); - if (url == null) { - throw new IllegalArgumentException("No such resource " + filename); + protected static String getTestResource(String filename) { + Optional resourceUrl = getTestResourceUrl(filename); + if (resourceUrl.isPresent()) { + return ThrowingEx.get(() -> LineEnding.toUnix(Resources.toString(resourceUrl.get(), StandardCharsets.UTF_8))); } - return Resources.toString(url, StandardCharsets.UTF_8); + throw new IllegalArgumentException("No such resource " + filename); + } + + protected static boolean existsTestResource(String filename) { + return getTestResourceUrl(filename).isPresent(); + } + + private static Optional getTestResourceUrl(String filename) { + URL url = ResourceHarness.class.getResource("/" + filename); + return Optional.ofNullable(url); } /** Returns Files (in a temporary folder) which has the contents of the given file from the src/test/resources directory. */ - protected List createTestFiles(String... filenames) throws IOException { + protected List createTestFiles(String... filenames) { List files = new ArrayList<>(filenames.length); for (String filename : filenames) { files.add(createTestFile(filename)); @@ -103,7 +113,7 @@ protected List createTestFiles(String... filenames) throws IOException { } /** Returns a File (in a temporary folder) which has the contents of the given file from the src/test/resources directory. */ - protected File createTestFile(String filename) throws IOException { + protected File createTestFile(String filename) { return createTestFile(filename, UnaryOperator.identity()); } @@ -111,39 +121,22 @@ protected File createTestFile(String filename) throws IOException { * Returns a File (in a temporary folder) which has the contents, possibly processed, of the given file from the * src/test/resources directory. */ - protected File createTestFile(String filename, UnaryOperator fileContentsProcessor) throws IOException { + protected File createTestFile(String filename, UnaryOperator fileContentsProcessor) { int lastSlash = filename.lastIndexOf('/'); String name = lastSlash >= 0 ? filename.substring(lastSlash) : filename; File file = newFile(name); file.getParentFile().mkdirs(); - Files.write(file.toPath(), fileContentsProcessor.apply(getTestResource(filename)).getBytes(StandardCharsets.UTF_8)); + ThrowingEx.run(() -> Files.write(file.toPath(), fileContentsProcessor.apply(getTestResource(filename)).getBytes(StandardCharsets.UTF_8))); return file; } - /** Reads the given resource from "before", applies the step, and makes sure the result is "after". */ - protected void assertOnResources(FormatterStep step, String unformattedPath, String expectedPath) throws Throwable { - assertOnResources(rawUnix -> step.format(rawUnix, new File("")), unformattedPath, expectedPath); - } - - /** Reads the given resource from "before", applies the step, and makes sure the result is "after". */ - protected void assertOnResources(FormatterFunc step, String unformattedPath, String expectedPath) throws Throwable { - String unformatted = LineEnding.toUnix(getTestResource(unformattedPath)); // unix-ified input - String formatted = step.apply(unformatted); - // no windows newlines - assertThat(formatted).doesNotContain("\r"); - - // unix-ify the test resource output in case git screwed it up - String expected = LineEnding.toUnix(getTestResource(expectedPath)); // unix-ified output - assertThat(formatted).isEqualTo(expected); - } - @CheckReturnValue - protected ReadAsserter assertFile(String path) throws IOException { + protected ReadAsserter assertFile(String path) { return new ReadAsserter(newFile(path)); } @CheckReturnValue - protected ReadAsserter assertFile(File file) throws IOException { + protected ReadAsserter assertFile(File file) { return new ReadAsserter(file); } @@ -176,7 +169,7 @@ public void matches(Consumer> conditions) } } - protected WriteAsserter setFile(String path) throws IOException { + protected WriteAsserter setFile(String path) { return new WriteAsserter(newFile(path)); } @@ -188,21 +181,25 @@ private WriteAsserter(File file) { this.file = file; } - public File toLines(String... lines) throws IOException { + public File toLines(String... lines) { return toContent(String.join("\n", Arrays.asList(lines))); } - public File toContent(String content) throws IOException { + public File toContent(String content) { return toContent(content, StandardCharsets.UTF_8); } - public File toContent(String content, Charset charset) throws IOException { - Files.write(file.toPath(), content.getBytes(charset)); + public File toContent(String content, Charset charset) { + ThrowingEx.run(() -> { + Files.write(file.toPath(), content.getBytes(charset)); + }); return file; } - public File toResource(String path) throws IOException { - Files.write(file.toPath(), getTestResource(path).getBytes(StandardCharsets.UTF_8)); + public File toResource(String path) { + ThrowingEx.run(() -> { + Files.write(file.toPath(), getTestResource(path).getBytes(StandardCharsets.UTF_8)); + }); return file; } diff --git a/testlib/src/main/java/com/diffplug/spotless/StepHarness.java b/testlib/src/main/java/com/diffplug/spotless/StepHarness.java index 8755f852d6..c611cc738a 100644 --- a/testlib/src/main/java/com/diffplug/spotless/StepHarness.java +++ b/testlib/src/main/java/com/diffplug/spotless/StepHarness.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,31 +22,21 @@ import java.nio.file.Paths; import java.util.Arrays; import java.util.Objects; -import java.util.function.Consumer; -import org.assertj.core.api.AbstractThrowableAssert; +import org.assertj.core.api.AbstractStringAssert; import org.assertj.core.api.Assertions; /** An api for testing a {@code FormatterStep} that doesn't depend on the File path. DO NOT ADD FILE SUPPORT TO THIS, use {@link StepHarnessWithFile} if you need that. */ public class StepHarness implements AutoCloseable { - private final FormatterFunc formatter; + private final Formatter formatter; - private StepHarness(FormatterFunc formatter) { + private StepHarness(Formatter formatter) { this.formatter = Objects.requireNonNull(formatter); } /** Creates a harness for testing steps which don't depend on the file. */ public static StepHarness forStep(FormatterStep step) { - // We don't care if an individual FormatterStep is misbehaving on line-endings, because - // Formatter fixes that. No reason to care in tests either. It's likely to pop up when - // running tests on Windows from time-to-time - return new StepHarness(FormatterFunc.Closeable.ofDangerous( - () -> { - if (step instanceof FormatterStepImpl.Standard) { - ((FormatterStepImpl.Standard) step).cleanupFormatterFunc(); - } - }, - input -> LineEnding.toUnix(step.format(input, new File(""))))); + return forSteps(step); } /** Creates a harness for testing steps which don't depend on the file. */ @@ -56,60 +46,68 @@ public static StepHarness forSteps(FormatterStep... steps) { .lineEndingsPolicy(LineEnding.UNIX.createPolicy()) .encoding(StandardCharsets.UTF_8) .rootDir(Paths.get("")) + .exceptionPolicy(new FormatExceptionPolicyStrict()) .build()); } /** Creates a harness for testing a formatter whose steps don't depend on the file. */ public static StepHarness forFormatter(Formatter formatter) { - return new StepHarness(FormatterFunc.Closeable.ofDangerous( - formatter::close, - input -> formatter.compute(input, new File("")))); + return new StepHarness(formatter); } /** Asserts that the given element is transformed as expected, and that the result is idempotent. */ - public StepHarness test(String before, String after) throws Exception { - String actual = formatter.apply(before); + public StepHarness test(String before, String after) { + String actual = formatter.compute(LineEnding.toUnix(before), new File("")); assertEquals(after, actual, "Step application failed"); return testUnaffected(after); } /** Asserts that the given element is idempotent w.r.t the step under test. */ - public StepHarness testUnaffected(String idempotentElement) throws Exception { - String actual = formatter.apply(idempotentElement); + public StepHarness testUnaffected(String idempotentElement) { + String actual = formatter.compute(LineEnding.toUnix(idempotentElement), new File("")); assertEquals(idempotentElement, actual, "Step is not idempotent"); return this; } /** Asserts that the given elements in the resources directory are transformed as expected. */ - public StepHarness testResource(String resourceBefore, String resourceAfter) throws Exception { + public StepHarness testResource(String resourceBefore, String resourceAfter) { String before = ResourceHarness.getTestResource(resourceBefore); String after = ResourceHarness.getTestResource(resourceAfter); return test(before, after); } /** Asserts that the given elements in the resources directory are transformed as expected. */ - public StepHarness testResourceUnaffected(String resourceIdempotent) throws Exception { + public StepHarness testResourceUnaffected(String resourceIdempotent) { String idempotentElement = ResourceHarness.getTestResource(resourceIdempotent); return testUnaffected(idempotentElement); } - /** Asserts that the given elements in the resources directory are transformed as expected. */ - public StepHarness testResourceException(String resourceBefore, Consumer> exceptionAssertion) throws Exception { - return testException(ResourceHarness.getTestResource(resourceBefore), exceptionAssertion); + public AbstractStringAssert testResourceExceptionMsg(String resourceBefore) { + return testExceptionMsg(ResourceHarness.getTestResource(resourceBefore)); } - /** Asserts that the given elements in the resources directory are transformed as expected. */ - public StepHarness testException(String before, Consumer> exceptionAssertion) throws Exception { - Throwable t = assertThrows(Throwable.class, () -> formatter.apply(before)); - AbstractThrowableAssert abstractAssert = Assertions.assertThat(t); - exceptionAssertion.accept(abstractAssert); - return this; + public AbstractStringAssert testExceptionMsg(String before) { + try { + formatter.compute(LineEnding.toUnix(before), Formatter.NO_FILE_SENTINEL); + throw new SecurityException("Expected exception"); + } catch (Throwable e) { + if (e instanceof SecurityException) { + throw new AssertionError(e.getMessage()); + } else { + Throwable rootCause = e; + while (rootCause.getCause() != null) { + if (rootCause instanceof IllegalStateException) { + break; + } + rootCause = rootCause.getCause(); + } + return Assertions.assertThat(rootCause.getMessage()); + } + } } @Override public void close() { - if (formatter instanceof FormatterFunc.Closeable) { - ((FormatterFunc.Closeable) formatter).close(); - } + formatter.close(); } } diff --git a/testlib/src/main/java/com/diffplug/spotless/StepHarnessWithFile.java b/testlib/src/main/java/com/diffplug/spotless/StepHarnessWithFile.java index 89be961d04..9a9eb4042b 100644 --- a/testlib/src/main/java/com/diffplug/spotless/StepHarnessWithFile.java +++ b/testlib/src/main/java/com/diffplug/spotless/StepHarnessWithFile.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,78 +18,101 @@ import static org.junit.jupiter.api.Assertions.*; import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.Collections; import java.util.Objects; +import org.assertj.core.api.AbstractStringAssert; +import org.assertj.core.api.Assertions; + /** An api for testing a {@code FormatterStep} that depends on the File path. */ public class StepHarnessWithFile implements AutoCloseable { - private final FormatterFunc formatter; + private final Formatter formatter; + private final ResourceHarness harness; - private StepHarnessWithFile(FormatterFunc formatter) { + private StepHarnessWithFile(ResourceHarness harness, Formatter formatter) { + this.harness = Objects.requireNonNull(harness); this.formatter = Objects.requireNonNull(formatter); } /** Creates a harness for testing steps which do depend on the file. */ - public static StepHarnessWithFile forStep(FormatterStep step) { - // We don't care if an individual FormatterStep is misbehaving on line-endings, because - // Formatter fixes that. No reason to care in tests either. It's likely to pop up when - // running tests on Windows from time-to-time - return new StepHarnessWithFile(FormatterFunc.Closeable.ofDangerous( - () -> { - if (step instanceof FormatterStepImpl.Standard) { - ((FormatterStepImpl.Standard) step).cleanupFormatterFunc(); - } - }, - new FormatterFunc() { - @Override - public String apply(String unix) throws Exception { - return apply(unix, new File("")); - } - - @Override - public String apply(String unix, File file) throws Exception { - return LineEnding.toUnix(step.format(unix, file)); - } - })); + public static StepHarnessWithFile forStep(ResourceHarness harness, FormatterStep step) { + return new StepHarnessWithFile(harness, Formatter.builder() + .name(step.getName()) + .encoding(StandardCharsets.UTF_8) + .lineEndingsPolicy(LineEnding.UNIX.createPolicy()) + .steps(Collections.singletonList(step)) + .rootDir(harness.rootFolder().toPath()) + .exceptionPolicy(new FormatExceptionPolicyStrict()) + .build()); } /** Creates a harness for testing a formatter whose steps do depend on the file. */ - public static StepHarnessWithFile forFormatter(Formatter formatter) { - return new StepHarnessWithFile(FormatterFunc.Closeable.ofDangerous( - formatter::close, - input -> formatter.compute(input, new File("")))); + public static StepHarnessWithFile forFormatter(ResourceHarness harness, Formatter formatter) { + return new StepHarnessWithFile(harness, formatter); } /** Asserts that the given element is transformed as expected, and that the result is idempotent. */ - public StepHarnessWithFile test(File file, String before, String after) throws Exception { - String actual = formatter.apply(before, file); + public StepHarnessWithFile test(File file, String before, String after) { + String actual = formatter.compute(LineEnding.toUnix(before), file); assertEquals(after, actual, "Step application failed"); return testUnaffected(file, after); } /** Asserts that the given element is idempotent w.r.t the step under test. */ - public StepHarnessWithFile testUnaffected(File file, String idempotentElement) throws Exception { - String actual = formatter.apply(idempotentElement, file); + public StepHarnessWithFile testUnaffected(File file, String idempotentElement) { + String actual = formatter.compute(LineEnding.toUnix(idempotentElement), file); assertEquals(idempotentElement, actual, "Step is not idempotent"); return this; } /** Asserts that the given elements in the resources directory are transformed as expected. */ - public StepHarnessWithFile testResource(File file, String resourceBefore, String resourceAfter) throws Exception { - String before = ResourceHarness.getTestResource(resourceBefore); - String after = ResourceHarness.getTestResource(resourceAfter); - return test(file, before, after); + public StepHarnessWithFile testResource(String resourceBefore, String resourceAfter) { + return testResource(resourceBefore, resourceBefore, resourceAfter); + } + + public StepHarnessWithFile testResource(String filename, String resourceBefore, String resourceAfter) { + String contentBefore = ResourceHarness.getTestResource(resourceBefore); + File file = harness.setFile(filename).toContent(contentBefore); + return test(file, contentBefore, ResourceHarness.getTestResource(resourceAfter)); } /** Asserts that the given elements in the resources directory are transformed as expected. */ - public StepHarnessWithFile testResourceUnaffected(File file, String resourceIdempotent) throws Exception { - String idempotentElement = ResourceHarness.getTestResource(resourceIdempotent); - return testUnaffected(file, idempotentElement); + public StepHarnessWithFile testResourceUnaffected(String resourceIdempotent) { + String contentBefore = ResourceHarness.getTestResource(resourceIdempotent); + File file = harness.setFile(resourceIdempotent).toContent(contentBefore); + return testUnaffected(file, contentBefore); + } + + public AbstractStringAssert testResourceExceptionMsg(String resourceBefore) { + return testResourceExceptionMsg(resourceBefore, resourceBefore); + } + + public AbstractStringAssert testResourceExceptionMsg(String filename, String resourceBefore) { + String contentBefore = ResourceHarness.getTestResource(resourceBefore); + File file = harness.setFile(filename).toContent(contentBefore); + return testExceptionMsg(file, contentBefore); + } + + public AbstractStringAssert testExceptionMsg(File file, String before) { + try { + formatter.compute(LineEnding.toUnix(before), file); + throw new SecurityException("Expected exception"); + } catch (Throwable e) { + if (e instanceof SecurityException) { + throw new AssertionError(e.getMessage()); + } else { + Throwable rootCause = e; + while (rootCause.getCause() != null) { + rootCause = rootCause.getCause(); + } + return Assertions.assertThat(rootCause.getMessage()); + } + } } @Override public void close() { - if (formatter instanceof FormatterFunc.Closeable) { - ((FormatterFunc.Closeable) formatter).close(); - } + formatter.close(); } } diff --git a/testlib/src/main/java/com/diffplug/spotless/TestProvisioner.java b/testlib/src/main/java/com/diffplug/spotless/TestProvisioner.java index 0d9ced4c9e..1ba0a745ee 100644 --- a/testlib/src/main/java/com/diffplug/spotless/TestProvisioner.java +++ b/testlib/src/main/java/com/diffplug/spotless/TestProvisioner.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2024 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,9 @@ import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ResolveException; import org.gradle.api.artifacts.dsl.RepositoryHandler; +import org.gradle.api.attributes.Attribute; import org.gradle.api.attributes.Bundling; +import org.gradle.api.attributes.Category; import org.gradle.testfixtures.ProjectBuilder; import com.diffplug.common.base.Errors; @@ -51,10 +53,10 @@ public static Project gradleProject(File dir) { /** * Creates a Provisioner for the given repositories. - * + *

* The first time a project is created, there are ~7 seconds of configuration * which will go away for all subsequent runs. - * + *

* Every call to resolve will take about 1 second, even when all artifacts are resolved. */ private static Provisioner createWithRepositories(Consumer repoConfig) { @@ -70,7 +72,11 @@ private static Provisioner createWithRepositories(Consumer re config.setTransitive(withTransitives); config.setDescription(mavenCoords.toString()); config.attributes(attr -> { + attr.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); attr.attribute(Bundling.BUNDLING_ATTRIBUTE, project.getObjects().named(Bundling.class, Bundling.EXTERNAL)); + // TODO: This is a copy-paste from org.gradle.api.attributes.java.TargetJvmEnvironment which is added in Gradle 7.0, remove this once we drop support for Gradle 6.x. + // Add this attribute for resolving Guava dependency, see https://github.com/google/guava/issues/6801. + attr.attribute(Attribute.of("org.gradle.jvm.environment", String.class), "standard-jvm"); }); try { return config.resolve(); diff --git a/testlib/src/main/java/com/diffplug/spotless/npm/EslintStyleGuide.java b/testlib/src/main/java/com/diffplug/spotless/npm/EslintStyleGuide.java new file mode 100644 index 0000000000..874ae1905d --- /dev/null +++ b/testlib/src/main/java/com/diffplug/spotless/npm/EslintStyleGuide.java @@ -0,0 +1,122 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.npm; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.annotation.Nonnull; + +/** + * A helper class to create dev dependencies for eslint when using one of the popular styleguides in testing. + */ +public enum EslintStyleGuide { + TS_STANDARD_WITH_TYPESCRIPT("standard-with-typescript") { + @Override + public @Nonnull Map devDependencies() { + Map dependencies = new LinkedHashMap<>(); + dependencies.put("@typescript-eslint/eslint-plugin", "^5.62.0"); + dependencies.put("@typescript-eslint/parser", "^5.62.0"); + dependencies.put("eslint-config-standard-with-typescript", "^36.0.1"); + dependencies.put("eslint-plugin-import", "^2.27.5"); + dependencies.put("eslint-plugin-n", "^16.0.1"); + dependencies.put("eslint-plugin-promise", "^6.1.1"); + return dependencies; + } + }, + TS_XO_TYPESCRIPT("xo-typescript") { + @Override + public @Nonnull Map devDependencies() { + Map dependencies = new LinkedHashMap<>(); + dependencies.put("eslint-config-xo", "^0.43.1"); + dependencies.put("eslint-config-xo-typescript", "^1.0.0"); + return dependencies; + } + }, + JS_AIRBNB("airbnb") { + @Override + public @Nonnull Map devDependencies() { + Map dependencies = new LinkedHashMap<>(); + dependencies.put("eslint-config-airbnb-base", "^15.0.0"); + dependencies.put("eslint-plugin-import", "^2.27.5"); + return dependencies; + } + }, + JS_GOOGLE("google") { + @Override + public @Nonnull Map devDependencies() { + Map dependencies = new LinkedHashMap<>(); + dependencies.put("eslint-config-google", "^0.14.0"); + return dependencies; + } + }, + JS_STANDARD("standard") { + @Override + public @Nonnull Map devDependencies() { + Map dependencies = new LinkedHashMap<>(); + dependencies.put("eslint-config-standard", "^17.1.0"); + dependencies.put("eslint-plugin-import", "^2.27.5"); + dependencies.put("eslint-plugin-n", "^16.0.1"); + dependencies.put("eslint-plugin-promise", "^6.1.1"); + return dependencies; + } + }, + JS_XO("xo") { + @Override + public @Nonnull Map devDependencies() { + Map dependencies = new LinkedHashMap<>(); + dependencies.put("eslint-config-xo", "^0.43.1"); + return dependencies; + } + }; + + private final String popularStyleGuideName; + + EslintStyleGuide(String popularStyleGuideName) { + this.popularStyleGuideName = popularStyleGuideName; + } + + public abstract @Nonnull Map devDependencies(); + + public static EslintStyleGuide fromNameOrNull(String popularStyleGuideName) { + for (EslintStyleGuide popularStyleGuide : EslintStyleGuide.values()) { + if (popularStyleGuide.popularStyleGuideName.equals(popularStyleGuideName)) { + return popularStyleGuide; + } + } + return null; + } + + public Map mergedWith(Map devDependencies) { + Map merged = new LinkedHashMap<>(devDependencies); + merged.putAll(devDependencies()); + return merged; + } + + public String asGradleMapStringMergedWith(Map devDependencies) { + return mergedWith(devDependencies).entrySet().stream() + .map(entry -> "'" + entry.getKey() + "': '" + entry.getValue() + "'") + .collect(Collectors.joining(", ", "[", "]")); + } + + public String asMavenXmlStringMergedWith(Map devDependencies) { + return mergedWith(devDependencies).entrySet().stream() + .map(entry -> String.format("%s%s", entry.getKey(), entry.getValue())) + .collect(Collectors.joining("", "", "")); + } + +} diff --git a/testlib/src/main/java/com/diffplug/spotless/tag/BufTest.java b/testlib/src/main/java/com/diffplug/spotless/tag/BufTest.java new file mode 100644 index 0000000000..90f32ec6c3 --- /dev/null +++ b/testlib/src/main/java/com/diffplug/spotless/tag/BufTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2022-2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.tag; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Tag; + +@Target({TYPE, METHOD}) +@Retention(RUNTIME) +@Tag("Buf") +public @interface BufTest {} diff --git a/testlib/src/main/java/com/diffplug/spotless/tag/GofmtTest.java b/testlib/src/main/java/com/diffplug/spotless/tag/GofmtTest.java new file mode 100644 index 0000000000..6228a84895 --- /dev/null +++ b/testlib/src/main/java/com/diffplug/spotless/tag/GofmtTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2021-2024 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.tag; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Tag; + +@Target({TYPE, METHOD}) +@Retention(RUNTIME) +@Tag("gofmt") +public @interface GofmtTest {} diff --git a/testlib/src/main/java/com/diffplug/spotless/tag/ShfmtTest.java b/testlib/src/main/java/com/diffplug/spotless/tag/ShfmtTest.java new file mode 100644 index 0000000000..efea44ec07 --- /dev/null +++ b/testlib/src/main/java/com/diffplug/spotless/tag/ShfmtTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2024 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.tag; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.Tag; + +@Target({TYPE, METHOD}) +@Retention(RUNTIME) +@Tag("Shfmt") +public @interface ShfmtTest {} diff --git a/testlib/src/main/resources/biome/config/line-width-120.json b/testlib/src/main/resources/biome/config/line-width-120.json new file mode 100644 index 0000000000..8f14afa3f8 --- /dev/null +++ b/testlib/src/main/resources/biome/config/line-width-120.json @@ -0,0 +1,11 @@ +{ + "formatter": { + "enabled": true, + "indentStyle": "tab", + "lineWidth": 120, + "formatWithErrors": false + }, + "linter": { + "enabled": false + } + } \ No newline at end of file diff --git a/testlib/src/main/resources/biome/config/line-width-80.json b/testlib/src/main/resources/biome/config/line-width-80.json new file mode 100644 index 0000000000..5ec998bd97 --- /dev/null +++ b/testlib/src/main/resources/biome/config/line-width-80.json @@ -0,0 +1,11 @@ +{ + "formatter": { + "enabled": true, + "indentStyle": "tab", + "lineWidth": 80, + "formatWithErrors": false + }, + "linter": { + "enabled": false + } + } \ No newline at end of file diff --git a/testlib/src/main/resources/biome/js/fileAfter.cjs b/testlib/src/main/resources/biome/js/fileAfter.cjs new file mode 100644 index 0000000000..defc9c85eb --- /dev/null +++ b/testlib/src/main/resources/biome/js/fileAfter.cjs @@ -0,0 +1,3 @@ +function foo(name = "World") { + return "Hello " + name; +} diff --git a/testlib/src/main/resources/biome/js/fileAfter.js b/testlib/src/main/resources/biome/js/fileAfter.js new file mode 100644 index 0000000000..defc9c85eb --- /dev/null +++ b/testlib/src/main/resources/biome/js/fileAfter.js @@ -0,0 +1,3 @@ +function foo(name = "World") { + return "Hello " + name; +} diff --git a/testlib/src/main/resources/biome/js/fileAfter.jsx b/testlib/src/main/resources/biome/js/fileAfter.jsx new file mode 100644 index 0000000000..313aceb6ed --- /dev/null +++ b/testlib/src/main/resources/biome/js/fileAfter.jsx @@ -0,0 +1,3 @@ +export function Panel(cfg = {}) { + return

{1 + 2}
; +} diff --git a/testlib/src/main/resources/biome/js/fileAfter.mjs b/testlib/src/main/resources/biome/js/fileAfter.mjs new file mode 100644 index 0000000000..defc9c85eb --- /dev/null +++ b/testlib/src/main/resources/biome/js/fileAfter.mjs @@ -0,0 +1,3 @@ +function foo(name = "World") { + return "Hello " + name; +} diff --git a/testlib/src/main/resources/biome/js/fileBefore.cjs b/testlib/src/main/resources/biome/js/fileBefore.cjs new file mode 100644 index 0000000000..92539ba751 --- /dev/null +++ b/testlib/src/main/resources/biome/js/fileBefore.cjs @@ -0,0 +1,3 @@ +function foo ( name="World"){ + return "Hello "+name ; + } \ No newline at end of file diff --git a/testlib/src/main/resources/biome/js/fileBefore.js b/testlib/src/main/resources/biome/js/fileBefore.js new file mode 100644 index 0000000000..92539ba751 --- /dev/null +++ b/testlib/src/main/resources/biome/js/fileBefore.js @@ -0,0 +1,3 @@ +function foo ( name="World"){ + return "Hello "+name ; + } \ No newline at end of file diff --git a/testlib/src/main/resources/biome/js/fileBefore.jsx b/testlib/src/main/resources/biome/js/fileBefore.jsx new file mode 100644 index 0000000000..8e5d9834bc --- /dev/null +++ b/testlib/src/main/resources/biome/js/fileBefore.jsx @@ -0,0 +1,4 @@ +export function Panel ( cfg={}){ + return (
{1+2}
+ ) ; + } \ No newline at end of file diff --git a/testlib/src/main/resources/biome/js/fileBefore.mjs b/testlib/src/main/resources/biome/js/fileBefore.mjs new file mode 100644 index 0000000000..92539ba751 --- /dev/null +++ b/testlib/src/main/resources/biome/js/fileBefore.mjs @@ -0,0 +1,3 @@ +function foo ( name="World"){ + return "Hello "+name ; + } \ No newline at end of file diff --git a/testlib/src/main/resources/biome/js/longLineAfter120.js b/testlib/src/main/resources/biome/js/longLineAfter120.js new file mode 100644 index 0000000000..68addc4b65 --- /dev/null +++ b/testlib/src/main/resources/biome/js/longLineAfter120.js @@ -0,0 +1 @@ +const x = ["Hello", "World", "How", "Are", "You", "Doing", "Today", "Such", "A", "Wondrous", "Sunshine"]; diff --git a/testlib/src/main/resources/biome/js/longLineAfter80.js b/testlib/src/main/resources/biome/js/longLineAfter80.js new file mode 100644 index 0000000000..dbdbd157e9 --- /dev/null +++ b/testlib/src/main/resources/biome/js/longLineAfter80.js @@ -0,0 +1,13 @@ +const x = [ + "Hello", + "World", + "How", + "Are", + "You", + "Doing", + "Today", + "Such", + "A", + "Wondrous", + "Sunshine", +]; diff --git a/testlib/src/main/resources/biome/js/longLineBefore.js b/testlib/src/main/resources/biome/js/longLineBefore.js new file mode 100644 index 0000000000..fd59e429c2 --- /dev/null +++ b/testlib/src/main/resources/biome/js/longLineBefore.js @@ -0,0 +1 @@ +const x = ["Hello", "World", "How", "Are", "You", "Doing", "Today", "Such", "A", "Wondrous", "Sunshine"]; \ No newline at end of file diff --git a/testlib/src/main/resources/biome/json/fileAfter.json b/testlib/src/main/resources/biome/json/fileAfter.json new file mode 100644 index 0000000000..468dac3297 --- /dev/null +++ b/testlib/src/main/resources/biome/json/fileAfter.json @@ -0,0 +1,5 @@ +{ + "a": [1, 2, 3], + "b": 9, + "c": null +} diff --git a/testlib/src/main/resources/biome/json/fileBefore.json b/testlib/src/main/resources/biome/json/fileBefore.json new file mode 100644 index 0000000000..77182284c7 --- /dev/null +++ b/testlib/src/main/resources/biome/json/fileBefore.json @@ -0,0 +1,7 @@ + { + "a":[1,2,3 + +], + "b":9, + "c" : null + } \ No newline at end of file diff --git a/testlib/src/main/resources/biome/json/package.json b/testlib/src/main/resources/biome/json/package.json new file mode 100644 index 0000000000..94e8f88de5 --- /dev/null +++ b/testlib/src/main/resources/biome/json/package.json @@ -0,0 +1,3 @@ +{ +"name": "test-package","version": "0.0.1" + } \ No newline at end of file diff --git a/testlib/src/main/resources/biome/json/packageAfter.json b/testlib/src/main/resources/biome/json/packageAfter.json new file mode 100644 index 0000000000..94e8f88de5 --- /dev/null +++ b/testlib/src/main/resources/biome/json/packageAfter.json @@ -0,0 +1,3 @@ +{ +"name": "test-package","version": "0.0.1" + } \ No newline at end of file diff --git a/testlib/src/main/resources/biome/json/packageBefore.json b/testlib/src/main/resources/biome/json/packageBefore.json new file mode 100644 index 0000000000..94e8f88de5 --- /dev/null +++ b/testlib/src/main/resources/biome/json/packageBefore.json @@ -0,0 +1,3 @@ +{ +"name": "test-package","version": "0.0.1" + } \ No newline at end of file diff --git a/testlib/src/main/resources/biome/ts/fileAfter.cts b/testlib/src/main/resources/biome/ts/fileAfter.cts new file mode 100644 index 0000000000..f854953234 --- /dev/null +++ b/testlib/src/main/resources/biome/ts/fileAfter.cts @@ -0,0 +1,4 @@ +type Name = "World" | "Maven" | "Gradle"; +const foo = (name: Name = "World", v: T): string => { + return "Hello " + name; +}; diff --git a/testlib/src/main/resources/biome/ts/fileAfter.mts b/testlib/src/main/resources/biome/ts/fileAfter.mts new file mode 100644 index 0000000000..e6563e3030 --- /dev/null +++ b/testlib/src/main/resources/biome/ts/fileAfter.mts @@ -0,0 +1,4 @@ +export type Name = "World" | "Maven" | "Gradle"; +export const foo = (name: Name = "World", v: T): string => { + return "Hello " + name; +}; diff --git a/testlib/src/main/resources/biome/ts/fileAfter.ts b/testlib/src/main/resources/biome/ts/fileAfter.ts new file mode 100644 index 0000000000..e6563e3030 --- /dev/null +++ b/testlib/src/main/resources/biome/ts/fileAfter.ts @@ -0,0 +1,4 @@ +export type Name = "World" | "Maven" | "Gradle"; +export const foo = (name: Name = "World", v: T): string => { + return "Hello " + name; +}; diff --git a/testlib/src/main/resources/biome/ts/fileAfter.tsx b/testlib/src/main/resources/biome/ts/fileAfter.tsx new file mode 100644 index 0000000000..15ef316142 --- /dev/null +++ b/testlib/src/main/resources/biome/ts/fileAfter.tsx @@ -0,0 +1,7 @@ +export interface Cfg { + classname: string; + message: T; +} +const Panel = (cfg: Cfg): JSX.Element => { + return
{String(cfg.message)}
; +}; diff --git a/testlib/src/main/resources/biome/ts/fileBefore.cts b/testlib/src/main/resources/biome/ts/fileBefore.cts new file mode 100644 index 0000000000..d4304287c0 --- /dev/null +++ b/testlib/src/main/resources/biome/ts/fileBefore.cts @@ -0,0 +1,4 @@ +type Name = "World" | "Maven"|"Gradle"; +const foo = ( name: Name="World", v: T): string => { + return "Hello " + name; + } \ No newline at end of file diff --git a/testlib/src/main/resources/biome/ts/fileBefore.mts b/testlib/src/main/resources/biome/ts/fileBefore.mts new file mode 100644 index 0000000000..96837762a3 --- /dev/null +++ b/testlib/src/main/resources/biome/ts/fileBefore.mts @@ -0,0 +1,5 @@ +export + type Name = "World" | "Maven"|"Gradle"; +export const foo = ( name: Name="World", v: T): string => { + return "Hello " + name; + } \ No newline at end of file diff --git a/testlib/src/main/resources/biome/ts/fileBefore.ts b/testlib/src/main/resources/biome/ts/fileBefore.ts new file mode 100644 index 0000000000..96837762a3 --- /dev/null +++ b/testlib/src/main/resources/biome/ts/fileBefore.ts @@ -0,0 +1,5 @@ +export + type Name = "World" | "Maven"|"Gradle"; +export const foo = ( name: Name="World", v: T): string => { + return "Hello " + name; + } \ No newline at end of file diff --git a/testlib/src/main/resources/biome/ts/fileBefore.tsx b/testlib/src/main/resources/biome/ts/fileBefore.tsx new file mode 100644 index 0000000000..38f24f8440 --- /dev/null +++ b/testlib/src/main/resources/biome/ts/fileBefore.tsx @@ -0,0 +1,8 @@ +export interface Cfg{ +classname:string, +message:T, +} +const Panel = ( cfg:Cfg):JSX.Element =>{ + return (
{String(cfg.message)}
+ ) ; + } \ No newline at end of file diff --git a/testlib/src/main/resources/combined/issue1679.clean b/testlib/src/main/resources/combined/issue1679.clean new file mode 100644 index 0000000000..af86db275e --- /dev/null +++ b/testlib/src/main/resources/combined/issue1679.clean @@ -0,0 +1,106 @@ +/* + * Copyright 2021-2022 Creek Contributors (https://github.com/creek-service) + * + * 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 + * + * http://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.github.creek.service.basic.kafka.streams.demo.services; + +// formatting:off +// begin-snippet: includes-1 +import static io.github.creek.service.basic.kafka.streams.demo.internal.TopicConfigBuilder.withPartitions; +import static io.github.creek.service.basic.kafka.streams.demo.internal.TopicDescriptors.inputTopic; +import static io.github.creek.service.basic.kafka.streams.demo.internal.TopicDescriptors.outputTopic; +// end-snippet +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +// begin-snippet: includes-2 +import org.creekservice.api.kafka.metadata.OwnedKafkaTopicInput; +import org.creekservice.api.kafka.metadata.OwnedKafkaTopicOutput; +// end-snippet +import org.creekservice.api.platform.metadata.ComponentInput; +import org.creekservice.api.platform.metadata.ComponentInternal; +import org.creekservice.api.platform.metadata.ComponentOutput; +import org.creekservice.api.platform.metadata.ServiceDescriptor; +// formatting:on + +// begin-snippet: class-name +public final class HandleOccurrenceServiceDescriptor implements ServiceDescriptor { + // end-snippet + private static final List INPUTS = new ArrayList<>(); + private static final List INTERNALS = new ArrayList<>(); + private static final List OUTPUTS = new ArrayList<>(); + + // formatting:off +// begin-snippet: topic-resources + // Define the tweet-text input topic, conceptually owned by this service: + public static final OwnedKafkaTopicInput TweetTextStream = + register( + inputTopic( + "twitter.tweet.text", // Topic name + Long.class, // Topic key: Tweet id + String.class, // Topic value: Tweet text + withPartitions(5))); // Topic config + + // Define the output topic, again conceptually owned by this service: + public static final OwnedKafkaTopicOutput TweetHandleUsageStream = + register(outputTopic( + "twitter.handle.usage", + String.class, // Twitter handle + Integer.class, // Usage count + withPartitions(6) + .withRetentionTime(Duration.ofHours(12)) + )); +// end-snippet +// formatting:on + + public HandleOccurrenceServiceDescriptor() {} + + @Override + public String dockerImage() { + return "ghcr.io/creek-service/basic-kafka-streams-demo-handle-occurrence-service"; + } + + @Override + public Collection inputs() { + return List.copyOf(INPUTS); + } + + @Override + public Collection internals() { + return List.copyOf(INTERNALS); + } + + @Override + public Collection outputs() { + return List.copyOf(OUTPUTS); + } + + private static T register(final T input) { + INPUTS.add(input); + return input; + } + + // Uncomment if needed: + // private static T register(final T internal) { + // INTERNALS.add(internal); + // return internal; + // } + + private static T register(final T output) { + OUTPUTS.add(output); + return output; + } +} diff --git a/testlib/src/main/resources/combined/issue1679.dirty b/testlib/src/main/resources/combined/issue1679.dirty new file mode 100644 index 0000000000..70754cef6c --- /dev/null +++ b/testlib/src/main/resources/combined/issue1679.dirty @@ -0,0 +1,104 @@ +/* + * Copyright 2021-2022 Creek Contributors (https://github.com/creek-service) + * + * 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 + * + * http://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.github.creek.service.basic.kafka.streams.demo.services; + +// formatting:off +// begin-snippet: includes-1 +import static io.github.creek.service.basic.kafka.streams.demo.internal.TopicConfigBuilder.withPartitions; +import static io.github.creek.service.basic.kafka.streams.demo.internal.TopicDescriptors.inputTopic; +import static io.github.creek.service.basic.kafka.streams.demo.internal.TopicDescriptors.outputTopic; +// end-snippet +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +// begin-snippet: includes-2 +import org.creekservice.api.kafka.metadata.OwnedKafkaTopicInput; +import org.creekservice.api.kafka.metadata.OwnedKafkaTopicOutput; +// end-snippet +import org.creekservice.api.platform.metadata.ComponentInput; +import org.creekservice.api.platform.metadata.ComponentInternal; +import org.creekservice.api.platform.metadata.ComponentOutput; +import org.creekservice.api.platform.metadata.ServiceDescriptor; +// formatting:on + +// begin-snippet: class-name +public final class HandleOccurrenceServiceDescriptor implements ServiceDescriptor { + // end-snippet + private static final List INPUTS = new ArrayList<>(); + private static final List INTERNALS = new ArrayList<>(); + private static final List OUTPUTS = new ArrayList<>(); + + // formatting:off +// begin-snippet: topic-resources + // Define the tweet-text input topic, conceptually owned by this service: + public static final OwnedKafkaTopicInput TweetTextStream = + register( + inputTopic( + "twitter.tweet.text", // Topic name + Long.class, // Topic key: Tweet id + String.class, // Topic value: Tweet text + withPartitions(5))); // Topic config + + // Define the output topic, again conceptually owned by this service: + public static final OwnedKafkaTopicOutput TweetHandleUsageStream = + register(outputTopic( + "twitter.handle.usage", + String.class, // Twitter handle + Integer.class, // Usage count + withPartitions(6) + .withRetentionTime(Duration.ofHours(12)) + )); +// end-snippet +// formatting:on + + public HandleOccurrenceServiceDescriptor() {} + + @Override + public String dockerImage() { + return "ghcr.io/creek-service/basic-kafka-streams-demo-handle-occurrence-service"; + } + + @Override + public Collection inputs() { return List.copyOf(INPUTS); } + + @Override + public Collection internals() { + return List.copyOf(INTERNALS); + } + + @Override + public Collection outputs() { + return List.copyOf(OUTPUTS); + } + + private static T register(final T input) { + INPUTS.add(input); + return input; + } + + // Uncomment if needed: + // private static T register(final T internal) { + // INTERNALS.add(internal); + // return internal; + // } + + private static T register(final T output) { + OUTPUTS.add(output); + return output; + } +} \ No newline at end of file diff --git a/testlib/src/main/resources/gherkin/complex_backgroundAfter.feature b/testlib/src/main/resources/gherkin/complex_backgroundAfter.feature new file mode 100644 index 0000000000..5a9553d98d --- /dev/null +++ b/testlib/src/main/resources/gherkin/complex_backgroundAfter.feature @@ -0,0 +1,24 @@ +Feature: Complex background + We want to ensure PickleStep all have different IDs + + Background: a simple background + Given the minimalism inside a background + + Scenario: minimalistic + Given the minimalism + + Scenario: also minimalistic + Given the minimalism + + Rule: My Rule + + Background: + Given a rule background step + + Scenario: with examples + Given the minimalism + + Examples: + | value | + | 1 | + | 2 | diff --git a/testlib/src/main/resources/gherkin/complex_backgroundBefore.feature b/testlib/src/main/resources/gherkin/complex_backgroundBefore.feature new file mode 100644 index 0000000000..fde7c94d9b --- /dev/null +++ b/testlib/src/main/resources/gherkin/complex_backgroundBefore.feature @@ -0,0 +1,24 @@ +Feature: Complex background + We want to ensure PickleStep all have different IDs + + Background: a simple background + Given the minimalism inside a background + + Scenario: minimalistic + Given the minimalism + + Scenario: also minimalistic + Given the minimalism + + Rule: My Rule + + Background: + Given a rule background step + + Scenario: with examples + Given the minimalism + + Examples: + | value | + | 1 | + | 2 | diff --git a/testlib/src/main/resources/gherkin/descriptionsAfter.feature b/testlib/src/main/resources/gherkin/descriptionsAfter.feature new file mode 100644 index 0000000000..9c0eb7e303 --- /dev/null +++ b/testlib/src/main/resources/gherkin/descriptionsAfter.feature @@ -0,0 +1,55 @@ +Feature: Descriptions everywhere + This is a single line description + + Scenario: two lines + This description + has two lines and indented with two spaces + + Given the minimalism + + Scenario: without indentation +This is a description without indentation + + Given the minimalism + + Scenario: empty lines in the middle + This description + + has an empty line in the middle + + Given the minimalism + + Scenario: empty lines around + This description + has an empty lines around + + Given the minimalism + + Scenario: comment after description + This description + has a comment after + + # this is a comment + Given the minimalism + + Scenario: comment right after description + This description + has a comment right after + + # this is another comment + Given the minimalism + + Scenario: description with escaped docstring separator + This description has an \"\"\" (escaped docstring sparator) + + Given the minimalism + + Scenario Outline: scenario outline with a description +This is a scenario outline description + + Given the minimalism + + Examples: examples with description +This is an examples description + | foo | + | bar | diff --git a/testlib/src/main/resources/gherkin/descriptionsBefore.feature b/testlib/src/main/resources/gherkin/descriptionsBefore.feature new file mode 100644 index 0000000000..690ae3a754 --- /dev/null +++ b/testlib/src/main/resources/gherkin/descriptionsBefore.feature @@ -0,0 +1,51 @@ +Feature: Descriptions everywhere + This is a single line description + + Scenario: two lines + This description + has two lines and indented with two spaces + Given the minimalism + +Scenario: without indentation +This is a description without indentation + Given the minimalism + + Scenario: empty lines in the middle + This description + + has an empty line in the middle + Given the minimalism + + Scenario: empty lines around + + This description + has an empty lines around + + Given the minimalism + + Scenario: comment after description + This description + has a comment after + +# this is a comment + Given the minimalism + + Scenario: comment right after description + This description + has a comment right after + # this is another comment + Given the minimalism + + Scenario: description with escaped docstring separator + This description has an \"\"\" (escaped docstring sparator) + + Given the minimalism + + Scenario Outline: scenario outline with a description +This is a scenario outline description + Given the minimalism + + Examples: examples with description +This is an examples description + | foo | + | bar | diff --git a/testlib/src/main/resources/gherkin/emptyAfter.feature b/testlib/src/main/resources/gherkin/emptyAfter.feature new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testlib/src/main/resources/gherkin/emptyBefore.feature b/testlib/src/main/resources/gherkin/emptyBefore.feature new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/testlib/src/main/resources/gherkin/emptyBefore.feature @@ -0,0 +1 @@ + diff --git a/testlib/src/main/resources/gherkin/invalidGherkinAfter.feature b/testlib/src/main/resources/gherkin/invalidGherkinAfter.feature new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testlib/src/main/resources/gherkin/invalidGherkinBefore.feature b/testlib/src/main/resources/gherkin/invalidGherkinBefore.feature new file mode 100644 index 0000000000..665bf227ca --- /dev/null +++ b/testlib/src/main/resources/gherkin/invalidGherkinBefore.feature @@ -0,0 +1,9 @@ + +invalid line here + +Feature: Multiple parser errors + + Scenario: minimalistic + Given the minimalism + + another invalid line here diff --git a/testlib/src/main/resources/gherkin/minimalAfter.feature b/testlib/src/main/resources/gherkin/minimalAfter.feature new file mode 100644 index 0000000000..9a62d86f80 --- /dev/null +++ b/testlib/src/main/resources/gherkin/minimalAfter.feature @@ -0,0 +1,4 @@ +Feature: Minimal + + Scenario: minimalistic + Given the minimalism diff --git a/testlib/src/main/resources/gherkin/minimalAfter0Spaces.feature b/testlib/src/main/resources/gherkin/minimalAfter0Spaces.feature new file mode 100644 index 0000000000..aa94821bbc --- /dev/null +++ b/testlib/src/main/resources/gherkin/minimalAfter0Spaces.feature @@ -0,0 +1,4 @@ +Feature: Minimal + +Scenario: minimalistic +Given the minimalism diff --git a/testlib/src/main/resources/gherkin/minimalAfter6Spaces.feature b/testlib/src/main/resources/gherkin/minimalAfter6Spaces.feature new file mode 100644 index 0000000000..cbad451b45 --- /dev/null +++ b/testlib/src/main/resources/gherkin/minimalAfter6Spaces.feature @@ -0,0 +1,4 @@ +Feature: Minimal + + Scenario: minimalistic + Given the minimalism diff --git a/testlib/src/main/resources/gherkin/minimalBefore.feature b/testlib/src/main/resources/gherkin/minimalBefore.feature new file mode 100644 index 0000000000..a474cf1418 --- /dev/null +++ b/testlib/src/main/resources/gherkin/minimalBefore.feature @@ -0,0 +1,3 @@ +Feature: Minimal +Scenario: minimalistic +Given the minimalism diff --git a/testlib/src/main/resources/gherkin/notGherkinAfter.feature b/testlib/src/main/resources/gherkin/notGherkinAfter.feature new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testlib/src/main/resources/gherkin/notGherkinBefore.feature b/testlib/src/main/resources/gherkin/notGherkinBefore.feature new file mode 100644 index 0000000000..517db4bd1e --- /dev/null +++ b/testlib/src/main/resources/gherkin/notGherkinBefore.feature @@ -0,0 +1 @@ +not gherkin diff --git a/testlib/src/main/resources/gherkin/rule_with_tagAfter.feature b/testlib/src/main/resources/gherkin/rule_with_tagAfter.feature new file mode 100644 index 0000000000..bba6b044ac --- /dev/null +++ b/testlib/src/main/resources/gherkin/rule_with_tagAfter.feature @@ -0,0 +1,19 @@ +@tag_feature +Feature: Some tagged rules + + Rule: Untagged rule + The untagged rule description + + Scenario: Scenario with only a feature tag + Given a + + @tag_rule + Rule: Tagged rule + The tagged rule description + + Scenario: Scenario with feature and rule tags + Given b + + @tag_scenario + Scenario: Scenario with feature, rule and scenario tags + Given b diff --git a/testlib/src/main/resources/gherkin/rule_with_tagBefore.feature b/testlib/src/main/resources/gherkin/rule_with_tagBefore.feature new file mode 100644 index 0000000000..bba6b044ac --- /dev/null +++ b/testlib/src/main/resources/gherkin/rule_with_tagBefore.feature @@ -0,0 +1,19 @@ +@tag_feature +Feature: Some tagged rules + + Rule: Untagged rule + The untagged rule description + + Scenario: Scenario with only a feature tag + Given a + + @tag_rule + Rule: Tagged rule + The tagged rule description + + Scenario: Scenario with feature and rule tags + Given b + + @tag_scenario + Scenario: Scenario with feature, rule and scenario tags + Given b diff --git a/testlib/src/main/resources/go/gofmt/go.clean b/testlib/src/main/resources/go/gofmt/go.clean new file mode 100644 index 0000000000..321185cd76 --- /dev/null +++ b/testlib/src/main/resources/go/gofmt/go.clean @@ -0,0 +1,14 @@ +package main + +import "fmt" + +func split(sum int) (x int, y int) { + x = sum * 4 / 9 + y = sum - x + return +} + +func main() { + fmt.Println( + split(17)) +} diff --git a/testlib/src/main/resources/go/gofmt/go.dirty b/testlib/src/main/resources/go/gofmt/go.dirty new file mode 100644 index 0000000000..f1f9adaca7 --- /dev/null +++ b/testlib/src/main/resources/go/gofmt/go.dirty @@ -0,0 +1,14 @@ +package main + +import "fmt" + +func split (sum int) (x int , y int) { + x = sum * 4 / 9 + y = sum - x + return +} + +func main() { + fmt.Println( + split(17)) +} diff --git a/testlib/src/main/resources/groovy/greclipse/format/SomeClass.fixed b/testlib/src/main/resources/groovy/greclipse/format/SomeClass.fixed new file mode 100644 index 0000000000..8c93ca4ad0 --- /dev/null +++ b/testlib/src/main/resources/groovy/greclipse/format/SomeClass.fixed @@ -0,0 +1,12 @@ +package com.somepackage + +class SomeClass { + + def func(parm) { + """ + ${parm == null ? "" : "parm"} + ${parm == null ? "" : "parm"} + + """ + } +} diff --git a/testlib/src/main/resources/groovy/greclipse/format/SomeClass.test b/testlib/src/main/resources/groovy/greclipse/format/SomeClass.test new file mode 100644 index 0000000000..62d4df0fc5 --- /dev/null +++ b/testlib/src/main/resources/groovy/greclipse/format/SomeClass.test @@ -0,0 +1,12 @@ +package com.somepackage + +class SomeClass { + + def func(parm) { + """ + ${parm == null ? "" : "$parm"} + ${parm == null ? "" : "$parm"} + + """ + } +} diff --git a/testlib/src/main/resources/groovy/removeSemicolons/GroovyCodeWithSemicolons.test b/testlib/src/main/resources/groovy/removeSemicolons/GroovyCodeWithSemicolons.test new file mode 100644 index 0000000000..b66af4e820 --- /dev/null +++ b/testlib/src/main/resources/groovy/removeSemicolons/GroovyCodeWithSemicolons.test @@ -0,0 +1,10 @@ +import mylib.Unused; +import mylib.UsedB; +import mylib.UsedA; + +public class SomeClass { +System.out.println("hello"); +UsedB.someMethod(); +UsedA.someMethod(); +} +} \ No newline at end of file diff --git a/testlib/src/main/resources/groovy/removeSemicolons/GroovyCodeWithSemicolonsFormatted.test b/testlib/src/main/resources/groovy/removeSemicolons/GroovyCodeWithSemicolonsFormatted.test new file mode 100644 index 0000000000..434addfa68 --- /dev/null +++ b/testlib/src/main/resources/groovy/removeSemicolons/GroovyCodeWithSemicolonsFormatted.test @@ -0,0 +1,10 @@ +import mylib.Unused +import mylib.UsedB +import mylib.UsedA + +public class SomeClass { +System.out.println("hello") +UsedB.someMethod() +UsedA.someMethod() +} +} \ No newline at end of file diff --git a/testlib/src/main/resources/java/cleanthat/LiteralsFirstInComparisons.clean.test b/testlib/src/main/resources/java/cleanthat/LiteralsFirstInComparisons.clean.test new file mode 100644 index 0000000000..8bacfa58db --- /dev/null +++ b/testlib/src/main/resources/java/cleanthat/LiteralsFirstInComparisons.clean.test @@ -0,0 +1,8 @@ +package eu.solven.cleanthat.engine.java.refactorer.cases.do_not_format_me; + +public class LiteralsFirstInComparisonsCases { + + public boolean isHardcoded(String input) { + return "hardcoded".equals(input); + } +} diff --git a/testlib/src/main/resources/java/cleanthat/LiteralsFirstInComparisons.dirty.test b/testlib/src/main/resources/java/cleanthat/LiteralsFirstInComparisons.dirty.test new file mode 100644 index 0000000000..3a1e074c91 --- /dev/null +++ b/testlib/src/main/resources/java/cleanthat/LiteralsFirstInComparisons.dirty.test @@ -0,0 +1,8 @@ +package eu.solven.cleanthat.engine.java.refactorer.cases.do_not_format_me; + +public class LiteralsFirstInComparisonsCases { + + public boolean isHardcoded(String input) { + return input.equals("hardcoded"); + } +} diff --git a/testlib/src/main/resources/java/cleanthat/MultipleMutators.clean.onlyLiteralsFirst.test b/testlib/src/main/resources/java/cleanthat/MultipleMutators.clean.onlyLiteralsFirst.test new file mode 100644 index 0000000000..629d24504b --- /dev/null +++ b/testlib/src/main/resources/java/cleanthat/MultipleMutators.clean.onlyLiteralsFirst.test @@ -0,0 +1,14 @@ +package eu.solven.cleanthat.engine.java.refactorer.cases.do_not_format_me; + +import java.util.Optional; + +public class LiteralsFirstInComparisonsCases { + + public boolean isHardcoded(String input) { + return "hardcoded".equals(input); + } + + public boolean isPresent(Optional optional) { + return !optional.isEmpty(); + } +} diff --git a/testlib/src/main/resources/java/cleanthat/MultipleMutators.clean.onlyOptionalIsPresent.test b/testlib/src/main/resources/java/cleanthat/MultipleMutators.clean.onlyOptionalIsPresent.test new file mode 100644 index 0000000000..0829602dc1 --- /dev/null +++ b/testlib/src/main/resources/java/cleanthat/MultipleMutators.clean.onlyOptionalIsPresent.test @@ -0,0 +1,14 @@ +package eu.solven.cleanthat.engine.java.refactorer.cases.do_not_format_me; + +import java.util.Optional; + +public class LiteralsFirstInComparisonsCases { + + public boolean isHardcoded(String input) { + return input.equals("hardcoded"); + } + + public boolean isPresent(Optional optional) { + return optional.isPresent(); + } +} diff --git a/testlib/src/main/resources/java/cleanthat/MultipleMutators.clean.test b/testlib/src/main/resources/java/cleanthat/MultipleMutators.clean.test new file mode 100644 index 0000000000..318e1efa15 --- /dev/null +++ b/testlib/src/main/resources/java/cleanthat/MultipleMutators.clean.test @@ -0,0 +1,14 @@ +package eu.solven.cleanthat.engine.java.refactorer.cases.do_not_format_me; + +import java.util.Optional; + +public class LiteralsFirstInComparisonsCases { + + public boolean isHardcoded(String input) { + return "hardcoded".equals(input); + } + + public boolean isPresent(Optional optional) { + return optional.isPresent(); + } +} diff --git a/testlib/src/main/resources/java/cleanthat/MultipleMutators.dirty.test b/testlib/src/main/resources/java/cleanthat/MultipleMutators.dirty.test new file mode 100644 index 0000000000..8ac230cabc --- /dev/null +++ b/testlib/src/main/resources/java/cleanthat/MultipleMutators.dirty.test @@ -0,0 +1,14 @@ +package eu.solven.cleanthat.engine.java.refactorer.cases.do_not_format_me; + +import java.util.Optional; + +public class LiteralsFirstInComparisonsCases { + + public boolean isHardcoded(String input) { + return input.equals("hardcoded"); + } + + public boolean isPresent(Optional optional) { + return !optional.isEmpty(); + } +} diff --git a/testlib/src/main/resources/java/eclipse/AbstractType.clean b/testlib/src/main/resources/java/eclipse/AbstractType.clean new file mode 100644 index 0000000000..314dbac1ac --- /dev/null +++ b/testlib/src/main/resources/java/eclipse/AbstractType.clean @@ -0,0 +1,41 @@ +package test; + +public abstract class AbstractType { + + private String _typeName; + + AbstractType(String typeName) { + _typeName = typeName; + } + + private String _type() { + String name = getClass().getSimpleName(); + return name.endsWith("Type") + ? name.substring(0, getClass().getSimpleName().length() - 4) + : name; + } + + AbstractType argument() { + throw new UnsupportedOperationException(getClass().getSimpleName()); + } + + @Override + public boolean equals(Object another) { + if (this == another) { + return true; + } + return another instanceof AbstractType t + && _typeName.equals(t._typeName); + } + + @Override + public int hashCode() { + return _typeName.hashCode(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + "(typeName)"; + } + +} diff --git a/testlib/src/main/resources/java/eclipse/AbstractType.test b/testlib/src/main/resources/java/eclipse/AbstractType.test new file mode 100644 index 0000000000..d4753b2cfc --- /dev/null +++ b/testlib/src/main/resources/java/eclipse/AbstractType.test @@ -0,0 +1,54 @@ +package test; + + + + +public abstract class AbstractType { + + + private String _typeName; + + AbstractType(String typeName) { + _typeName = typeName; + } + + + + private String _type() { + String name = getClass().getSimpleName(); + return name.endsWith("Type") + ? name.substring(0, getClass().getSimpleName().length() - 4) + : name; + } + + AbstractType argument() { + throw new UnsupportedOperationException(getClass().getSimpleName()); + } + + + @Override + public boolean equals(Object another) { + if (this == another) { + return true; + } + return another instanceof AbstractType t + && _typeName.equals(t._typeName); + } + + + + @Override + public int hashCode() { + return _typeName.hashCode(); + } + + + + + @Override + public String toString() { + return getClass().getSimpleName() + "(typeName)"; + } + + +} diff --git a/testlib/src/main/resources/java/formatannotations/FormatAnnotationsAccessModifiersInput.test b/testlib/src/main/resources/java/formatannotations/FormatAnnotationsAccessModifiersInput.test new file mode 100644 index 0000000000..13546f9a84 --- /dev/null +++ b/testlib/src/main/resources/java/formatannotations/FormatAnnotationsAccessModifiersInput.test @@ -0,0 +1,63 @@ +// Annotations in the wrong order. The preferred order is: +// * declaration annotations +// * access modifiers such as `public` +// * type annotations +// * type + +class FormatAnnotationsAccessModifiers { + + @Nullable public Object myMethod1() { + return null; + } + + @Nullable + public Object myMethod2() { + return null; + } + + @Nullable + public + Object myMethod3() { + return null; + } + + @Nullable + @Deprecated + public Object myMethod4() { + return null; + } + + @Deprecated + @Nullable + public Object myMethod4a() { + return null; + } + + @Override + @Nullable + @Deprecated + public Object myMethod5() { + return null; + } + + @Nullable @Deprecated public Object myMethod6() { + return null; + } +} + +@Deprecated +@Interned +@MustCall("close") +@SuppressWarnings +public class MyClass3 { + // No body +} + +public +@Deprecated +@SuppressWarnings +@Interned +@MustCall("close") +class MyClass4 { + // No body +} diff --git a/testlib/src/main/resources/java/formatannotations/FormatAnnotationsAccessModifiersOutput.test b/testlib/src/main/resources/java/formatannotations/FormatAnnotationsAccessModifiersOutput.test new file mode 100644 index 0000000000..ffc6169c4c --- /dev/null +++ b/testlib/src/main/resources/java/formatannotations/FormatAnnotationsAccessModifiersOutput.test @@ -0,0 +1,54 @@ +// Annotations in the wrong order. The preferred order is: +// * declaration annotations +// * access modifiers such as `public` +// * type annotations +// * type + +class FormatAnnotationsAccessModifiers { + + @Nullable public Object myMethod1() { + return null; + } + + @Nullable public Object myMethod2() { + return null; + } + + @Nullable public + Object myMethod3() { + return null; + } + + @Nullable @Deprecated + public Object myMethod4() { + return null; + } + + @Deprecated + @Nullable public Object myMethod4a() { + return null; + } + + @Override + @Nullable @Deprecated + public Object myMethod5() { + return null; + } + + @Nullable @Deprecated public Object myMethod6() { + return null; + } +} + +@Deprecated +@Interned @MustCall("close") @SuppressWarnings +public class MyClass3 { + // No body +} + +public +@Deprecated +@SuppressWarnings +@Interned @MustCall("close") class MyClass4 { + // No body +} diff --git a/testlib/src/main/resources/java/formatannotations/FormatAnnotationsTestInput.test b/testlib/src/main/resources/java/formatannotations/FormatAnnotationsTestInput.test index fb711451d1..b93631db5a 100644 --- a/testlib/src/main/resources/java/formatannotations/FormatAnnotationsTestInput.test +++ b/testlib/src/main/resources/java/formatannotations/FormatAnnotationsTestInput.test @@ -75,3 +75,12 @@ class FormatAnnotationsTest { @Localized String localized; } + +@Deprecated +@SuppressWarnings +public +@Interned +@MustCall("close") +class MyClass1 { + // No body +} diff --git a/testlib/src/main/resources/java/formatannotations/FormatAnnotationsTestOutput.test b/testlib/src/main/resources/java/formatannotations/FormatAnnotationsTestOutput.test index ca37e66d86..6daabf198c 100644 --- a/testlib/src/main/resources/java/formatannotations/FormatAnnotationsTestOutput.test +++ b/testlib/src/main/resources/java/formatannotations/FormatAnnotationsTestOutput.test @@ -49,3 +49,10 @@ class FormatAnnotationsTest { @Localized String localized; } + +@Deprecated +@SuppressWarnings +public +@Interned @MustCall("close") class MyClass1 { + // No body +} diff --git a/testlib/src/main/resources/java/googlejavaformat/JavaCodeFormatted.test b/testlib/src/main/resources/java/googlejavaformat/JavaCodeFormatted.test index 7f7483f1bc..a0aeb21d0e 100644 --- a/testlib/src/main/resources/java/googlejavaformat/JavaCodeFormatted.test +++ b/testlib/src/main/resources/java/googlejavaformat/JavaCodeFormatted.test @@ -1,8 +1,8 @@ - import mylib.UsedA; import mylib.UsedB; public class Java { + /** Some javadoc. */ public static void main(String[] args) { System.out.println( "A very very very very very very very very very very very very very very very very very very very very very long string that goes beyond the 100-character line length."); diff --git a/testlib/src/main/resources/java/googlejavaformat/JavaCodeFormatted18.test b/testlib/src/main/resources/java/googlejavaformat/JavaCodeFormatted18.test index 7a83a0a12c..a0aeb21d0e 100644 --- a/testlib/src/main/resources/java/googlejavaformat/JavaCodeFormatted18.test +++ b/testlib/src/main/resources/java/googlejavaformat/JavaCodeFormatted18.test @@ -2,6 +2,7 @@ import mylib.UsedA; import mylib.UsedB; public class Java { + /** Some javadoc. */ public static void main(String[] args) { System.out.println( "A very very very very very very very very very very very very very very very very very very very very very long string that goes beyond the 100-character line length."); diff --git a/testlib/src/main/resources/java/googlejavaformat/JavaCodeFormattedAOSP.test b/testlib/src/main/resources/java/googlejavaformat/JavaCodeFormattedAOSP.test index dd1493b8c8..6b65d1f897 100644 --- a/testlib/src/main/resources/java/googlejavaformat/JavaCodeFormattedAOSP.test +++ b/testlib/src/main/resources/java/googlejavaformat/JavaCodeFormattedAOSP.test @@ -1,8 +1,8 @@ - import mylib.UsedA; import mylib.UsedB; public class Java { + /** Some javadoc. */ public static void main(String[] args) { System.out.println( "A very very very very very very very very very very very very very very very very very very very very very long string that goes beyond the 100-character line length."); diff --git a/testlib/src/main/resources/java/googlejavaformat/JavaCodeFormattedReflowLongStrings.test b/testlib/src/main/resources/java/googlejavaformat/JavaCodeFormattedReflowLongStrings.test index 24e91a2ac8..8751e7e2d9 100644 --- a/testlib/src/main/resources/java/googlejavaformat/JavaCodeFormattedReflowLongStrings.test +++ b/testlib/src/main/resources/java/googlejavaformat/JavaCodeFormattedReflowLongStrings.test @@ -2,6 +2,7 @@ import mylib.UsedA; import mylib.UsedB; public class Java { + /** Some javadoc. */ public static void main(String[] args) { System.out.println( "A very very very very very very very very very very very very very very very very very" diff --git a/testlib/src/main/resources/java/googlejavaformat/JavaCodeFormattedSkipJavadocFormatting.test b/testlib/src/main/resources/java/googlejavaformat/JavaCodeFormattedSkipJavadocFormatting.test new file mode 100644 index 0000000000..b3d1793729 --- /dev/null +++ b/testlib/src/main/resources/java/googlejavaformat/JavaCodeFormattedSkipJavadocFormatting.test @@ -0,0 +1,14 @@ +import mylib.UsedA; +import mylib.UsedB; + +public class Java { + /** + * Some javadoc. + */ + public static void main(String[] args) { + System.out.println( + "A very very very very very very very very very very very very very very very very very very very very very long string that goes beyond the 100-character line length."); + UsedB.someMethod(); + UsedA.someMethod(); + } +} diff --git a/testlib/src/main/resources/java/googlejavaformat/JavaCodeUnformatted.test b/testlib/src/main/resources/java/googlejavaformat/JavaCodeUnformatted.test index 18f2aca0e2..8e66776be8 100644 --- a/testlib/src/main/resources/java/googlejavaformat/JavaCodeUnformatted.test +++ b/testlib/src/main/resources/java/googlejavaformat/JavaCodeUnformatted.test @@ -4,6 +4,9 @@ import mylib.UsedB; import mylib.UsedA; public class Java { +/** + * Some javadoc. + */ public static void main(String[] args) { System.out.println("A very very very very very very very very very very very very very very very very very very very very very long string that goes beyond the 100-character line length."); UsedB.someMethod(); diff --git a/testlib/src/main/resources/java/googlejavaformat/JavaWithReorderImportsDisabledFormatted.test b/testlib/src/main/resources/java/googlejavaformat/JavaWithReorderImportsDisabledFormatted.test new file mode 100644 index 0000000000..5e62d24108 --- /dev/null +++ b/testlib/src/main/resources/java/googlejavaformat/JavaWithReorderImportsDisabledFormatted.test @@ -0,0 +1,11 @@ +import java.nio.file.Paths; +import my.UsedB; +import org.xml.sax.InputSource; + +public class Java { + public static void main(String[] args) { + UsedB b = new UsedB(); + InputSource inputSource = new InputSource(); + Paths.get("dir"); + } +} diff --git a/testlib/src/main/resources/java/googlejavaformat/JavaWithReorderImportsEnabledFormatted.test b/testlib/src/main/resources/java/googlejavaformat/JavaWithReorderImportsEnabledFormatted.test new file mode 100644 index 0000000000..d0d4951601 --- /dev/null +++ b/testlib/src/main/resources/java/googlejavaformat/JavaWithReorderImportsEnabledFormatted.test @@ -0,0 +1,13 @@ +import my.UsedB; + +import org.xml.sax.InputSource; + +import java.nio.file.Paths; + +public class Java { + public static void main(String[] args) { + UsedB b = new UsedB(); + InputSource inputSource = new InputSource(); + Paths.get("dir"); + } +} diff --git a/testlib/src/main/resources/java/googlejavaformat/JavaWithReorderImportsUnformatted.test b/testlib/src/main/resources/java/googlejavaformat/JavaWithReorderImportsUnformatted.test new file mode 100644 index 0000000000..bfdbe83d88 --- /dev/null +++ b/testlib/src/main/resources/java/googlejavaformat/JavaWithReorderImportsUnformatted.test @@ -0,0 +1,11 @@ +import my.UsedB; +import java.nio.file.Paths; +import org.xml.sax.InputSource; + +public class Java { + public static void main(String[] args) { + UsedB b = new UsedB(); + InputSource inputSource = new InputSource(); + Paths.get("dir"); + } +} \ No newline at end of file diff --git a/testlib/src/main/resources/java/importsorter/JavaCodeSortedImportsSubgroups.test b/testlib/src/main/resources/java/importsorter/JavaCodeSortedImportsSubgroups.test new file mode 100644 index 0000000000..b2346bb19c --- /dev/null +++ b/testlib/src/main/resources/java/importsorter/JavaCodeSortedImportsSubgroups.test @@ -0,0 +1,16 @@ +import java.awt.*; +import java.lang.Runnable; +import java.lang.Thread; +import java.util.*; +import java.util.List; +import javax.annotation.Nullable; +import javax.inject.Inject; + +import org.dooda.Didoo; +import static com.foo.Bar; +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; + +import static java.lang.Exception.*; +import static java.lang.Runnable.*; +import static org.hamcrest.Matchers.*; diff --git a/testlib/src/main/resources/java/importsorter/JavaCodeSortedImportsSubgroupsLeadingCatchAll.test b/testlib/src/main/resources/java/importsorter/JavaCodeSortedImportsSubgroupsLeadingCatchAll.test new file mode 100644 index 0000000000..62efc65e21 --- /dev/null +++ b/testlib/src/main/resources/java/importsorter/JavaCodeSortedImportsSubgroupsLeadingCatchAll.test @@ -0,0 +1,14 @@ +import static com.foo.Bar; +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static java.lang.Exception.*; +import static java.lang.Runnable.*; +import static org.hamcrest.Matchers.*; +import java.awt.*; +import java.lang.Runnable; +import java.lang.Thread; +import java.util.*; +import java.util.List; +import javax.annotation.Nullable; +import javax.inject.Inject; +import org.dooda.Didoo; diff --git a/testlib/src/main/resources/java/importsorter/JavaCodeSortedLexicographic.test b/testlib/src/main/resources/java/importsorter/JavaCodeSortedLexicographic.test new file mode 100644 index 0000000000..2b59d52e58 --- /dev/null +++ b/testlib/src/main/resources/java/importsorter/JavaCodeSortedLexicographic.test @@ -0,0 +1,17 @@ +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import static com.example.Test.*; +import static com.example.Test.A_CONST; +import static com.example.Test.Nested.B_CONST; +import static com.example.Test.Z_CONST; + +import com.example.Test; +import com.example.Test.*; +import com.example.Test.Nested; +import com.example.a.A; +import com.example.b; +import com.example.c.C; +import com.sun.jna.platform.win32.COM.Unknown; +import com.sun.jna.platform.win32.Guid.CLSID; diff --git a/testlib/src/main/resources/java/importsorter/JavaCodeSortedSemanticSort.test b/testlib/src/main/resources/java/importsorter/JavaCodeSortedSemanticSort.test new file mode 100644 index 0000000000..3ab813b227 --- /dev/null +++ b/testlib/src/main/resources/java/importsorter/JavaCodeSortedSemanticSort.test @@ -0,0 +1,17 @@ +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import static com.example.Test.*; +import static com.example.Test.A_CONST; +import static com.example.Test.Z_CONST; +import static com.example.Test.Nested.B_CONST; + +import com.example.Test; +import com.example.Test.*; +import com.example.Test.Nested; +import com.example.b; +import com.example.a.A; +import com.example.c.C; +import com.sun.jna.platform.win32.Guid.CLSID; +import com.sun.jna.platform.win32.COM.Unknown; diff --git a/testlib/src/main/resources/java/importsorter/JavaCodeUnsortedImportsSubgroups.test b/testlib/src/main/resources/java/importsorter/JavaCodeUnsortedImportsSubgroups.test new file mode 100644 index 0000000000..35d8c465e4 --- /dev/null +++ b/testlib/src/main/resources/java/importsorter/JavaCodeUnsortedImportsSubgroups.test @@ -0,0 +1,15 @@ +import static java.lang.Exception.*; +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import org.dooda.Didoo; +import java.util.List; +import javax.inject.Inject; +import java.lang.Thread; +import java.util.*; +import java.lang.Runnable; +import static org.hamcrest.Matchers.*; +import javax.annotation.Nullable; + +import static java.lang.Runnable.*; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.foo.Bar +import java.awt.*; diff --git a/testlib/src/main/resources/java/importsorter/JavaCodeUnsortedSemanticSort.test b/testlib/src/main/resources/java/importsorter/JavaCodeUnsortedSemanticSort.test new file mode 100644 index 0000000000..7bfcfc6f7a --- /dev/null +++ b/testlib/src/main/resources/java/importsorter/JavaCodeUnsortedSemanticSort.test @@ -0,0 +1,19 @@ +import static com.example.Test.*; +import static com.example.Test.A_CONST; +import static com.example.Test.Z_CONST; +import static com.example.Test.Nested.B_CONST; + +import com.example.Test.Nested; +import com.example.Test; +import com.example.Test.*; + +import com.sun.jna.platform.win32.COM.Unknown; +import com.sun.jna.platform.win32.Guid.CLSID; + +import com.example.a.A; +import com.example.b; +import com.example.c.C; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.when; diff --git a/testlib/src/main/resources/java/palantirjavaformat/JavaCodeFormattedGoogle.test b/testlib/src/main/resources/java/palantirjavaformat/JavaCodeFormattedGoogle.test new file mode 100644 index 0000000000..7a83a0a12c --- /dev/null +++ b/testlib/src/main/resources/java/palantirjavaformat/JavaCodeFormattedGoogle.test @@ -0,0 +1,11 @@ +import mylib.UsedA; +import mylib.UsedB; + +public class Java { + public static void main(String[] args) { + System.out.println( + "A very very very very very very very very very very very very very very very very very very very very very long string that goes beyond the 100-character line length."); + UsedB.someMethod(); + UsedA.someMethod(); + } +} diff --git a/testlib/src/main/resources/java/palantirjavaformat/JavaCodeWithJavaDocFormatted.test b/testlib/src/main/resources/java/palantirjavaformat/JavaCodeWithJavaDocFormatted.test new file mode 100644 index 0000000000..9cff5e17a1 --- /dev/null +++ b/testlib/src/main/resources/java/palantirjavaformat/JavaCodeWithJavaDocFormatted.test @@ -0,0 +1,39 @@ +import mylib.Unused; +import mylib.UsedA; +import mylib.UsedB; + +/** + * This is a test class with a long unformatted JavaDoc description. Lorem ipsum dolor sit amet, consectetur adipiscing + * elit. Vestibulum pulvinar condimentum elit, eget mollis magna sollicitudin in. Aenean pharetra nunc nec luctus + * consequat. Donec nec tincidunt quam, in auctor ipsum. Nam in sem orci. Maecenas interdum posuere orci a semper. Cras + * vulputate blandit metus, nec semper urna porttitor at. Praesent velit turpis, consequat in cursus eget, posuere eget + * magna. Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque ante eros, sagittis sed tempus nec, + * rutrum ac arcu. Sed porttitor quam at enim commodo dictum. Sed fringilla tincidunt ex in aliquet. + * + * @author https://www.lipsum.com/ + * @since 0.0.2 + */ +public class Java { + /** + * A very simple method that I really like a lot? + * + *

Care for more details? + * + *

    + *
  • Too + *
  • bad + *
  • I + *
  • don't + *
  • have + *
  • any + *
+ * + * @param args Useless args, but see {@link Unused}, perhaps even {@link UsedA} or even {@link UsedB b }? + */ + public static void main(String[] args) { + System.out.println( + "A very very very very very very very very very very very very very very very very very very very very very long string that goes beyond the 100-character line length."); + UsedB.someMethod(); + UsedA.someMethod(); + } +} diff --git a/testlib/src/main/resources/java/palantirjavaformat/JavaCodeWithJavaDocUnformatted.test b/testlib/src/main/resources/java/palantirjavaformat/JavaCodeWithJavaDocUnformatted.test new file mode 100644 index 0000000000..fcb3ad660c --- /dev/null +++ b/testlib/src/main/resources/java/palantirjavaformat/JavaCodeWithJavaDocUnformatted.test @@ -0,0 +1,30 @@ + +import mylib.Unused; +import mylib.UsedB; +import mylib.UsedA; + +/** This is a test class with a long unformatted JavaDoc description. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum pulvinar condimentum elit, eget mollis magna sollicitudin in. Aenean pharetra nunc nec luctus consequat. Donec nec tincidunt quam, in auctor ipsum. Nam in sem orci. Maecenas interdum posuere orci a semper. Cras vulputate blandit metus, nec semper urna porttitor at. Praesent velit turpis, consequat in cursus eget, posuere eget magna. Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque ante eros, sagittis sed tempus nec, rutrum ac arcu. Sed porttitor quam at enim commodo dictum. Sed fringilla tincidunt ex in aliquet. + * @author https://www.lipsum.com/ + * @since 0.0.2 + */ +public class Java { + /** + * A very simple method that I + * really + * like + * a lot? + * + * Care for more details?
  • Too
  • bad
  • + *
  • I
  • don't
  • have
  • + *
  • any
  • + *
+ * + * @param args Useless args, but + * see {@link Unused}, perhaps even {@link UsedA} or even {@link UsedB b }? + */ +public static void main(String[] args) { +System.out.println("A very very very very very very very very very very very very very very very very very very very very very long string that goes beyond the 100-character line length."); +UsedB.someMethod(); +UsedA.someMethod(); +} +} \ No newline at end of file diff --git a/testlib/src/main/resources/java/palantirjavaformat/JavaCodeWithLicenseFormattedGoogle.test b/testlib/src/main/resources/java/palantirjavaformat/JavaCodeWithLicenseFormattedGoogle.test new file mode 100644 index 0000000000..9ee9bcbad6 --- /dev/null +++ b/testlib/src/main/resources/java/palantirjavaformat/JavaCodeWithLicenseFormattedGoogle.test @@ -0,0 +1,16 @@ +/* + * Some license stuff. + * Very official. + */ + +import mylib.UsedA; +import mylib.UsedB; + +public class Java { + public static void main(String[] args) { + System.out.println( + "A very very very very very very very very very very very very very very very very very very very very very long string that goes beyond the 100-character line length."); + UsedB.someMethod(); + UsedA.someMethod(); + } +} diff --git a/testlib/src/main/resources/java/palantirjavaformat/JavaCodeWithPackageFormattedGoogle.test b/testlib/src/main/resources/java/palantirjavaformat/JavaCodeWithPackageFormattedGoogle.test new file mode 100644 index 0000000000..4992ade147 --- /dev/null +++ b/testlib/src/main/resources/java/palantirjavaformat/JavaCodeWithPackageFormattedGoogle.test @@ -0,0 +1,13 @@ +package hello.world; + +import mylib.UsedA; +import mylib.UsedB; + +public class Java { + public static void main(String[] args) { + System.out.println( + "A very very very very very very very very very very very very very very very very very very very very very long string that goes beyond the 100-character line length."); + UsedB.someMethod(); + UsedA.someMethod(); + } +} diff --git a/testlib/src/main/resources/java/removeunusedimports/Jdk17TextBlockFormatted.test b/testlib/src/main/resources/java/removeunusedimports/Jdk17TextBlockFormatted.test new file mode 100644 index 0000000000..d6abc3685c --- /dev/null +++ b/testlib/src/main/resources/java/removeunusedimports/Jdk17TextBlockFormatted.test @@ -0,0 +1,28 @@ +package io.github.shafthq.shaft.tools.tms; + + + +import static io.restassured.RestAssured.*; + +public class XrayIntegrationHelper { + private static String getLinkJIRATicketRequestBody() { + return """ + { + "update":{ + "issuelinks":[ + { + "add":{ + "type":{ + "name":"Relates" + }, + "outwardIssue":{ + "key":"${TICKET_ID}" + } + } + } + ] + } + } + """; + } +} diff --git a/testlib/src/main/resources/java/removeunusedimports/Jdk17TextBlockUnformatted.test b/testlib/src/main/resources/java/removeunusedimports/Jdk17TextBlockUnformatted.test new file mode 100644 index 0000000000..1bfad32664 --- /dev/null +++ b/testlib/src/main/resources/java/removeunusedimports/Jdk17TextBlockUnformatted.test @@ -0,0 +1,49 @@ +package io.github.shafthq.shaft.tools.tms; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.shaft.cli.FileActions; +import com.shaft.tools.io.ReportManager; +import io.github.shafthq.shaft.tools.io.helpers.ReportManagerHelper; +import io.restassured.config.RestAssuredConfig; +import io.restassured.config.SSLConfig; +import io.restassured.http.ContentType; +import io.restassured.response.Response; +import io.restassured.specification.RequestSpecification; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.text.SimpleDateFormat; +import java.util.Base64; +import java.util.Calendar; +import java.util.List; + +import static io.restassured.RestAssured.*; +import static io.restassured.config.EncoderConfig.encoderConfig; + + +public class XrayIntegrationHelper { + private static String getLinkJIRATicketRequestBody() { + return """ + { + "update":{ + "issuelinks":[ + { + "add":{ + "type":{ + "name":"Relates" + }, + "outwardIssue":{ + "key":"${TICKET_ID}" + } + } + } + ] + } + } + """; + } +} diff --git a/testlib/src/main/resources/java/removeunusedimports/RevelcFormatted.test b/testlib/src/main/resources/java/removeunusedimports/RevelcFormatted.test new file mode 100644 index 0000000000..672400fdb8 --- /dev/null +++ b/testlib/src/main/resources/java/removeunusedimports/RevelcFormatted.test @@ -0,0 +1,81 @@ +/* + * 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 + * + * http://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. + */ + +@XmlSchema( + xmlns = { + @XmlNs(prefix = "order", namespaceURI = "http://www.camel.apache.org/jaxb/example/order/1"), + @XmlNs(prefix = "address", namespaceURI = "http://www.camel.apache.org/jaxb/example/address/1") + } +) +package net.revelc.code.imp; + +import com.foo.Type1; +import com.foo.Type2; +import com.foo.Type3; +import com.foo.Type4; +import com.foo.Type5; +import com.foo.Type6; +import com.foo.Type7; +import com.foo.Type8; +import com.foo.Type9; +import com.foo.Type10; + +import com.google.common.collect.ImmutableMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.springframework.stereotype.Component; + +import com.foo.Type11; +import com.foo.internal.Type12; +import com.foo.params.Type13; +import javax.xml.bind.annotation.XmlNs; +import javax.xml.bind.annotation.XmlSchema; + +import static org.junit.Assert.assertFalse; + +import static org.junit.Assert.*; + +/** + * The import of {@link HashMap} should not be stripped away, since it + * is used in this comment. + */ +// https://github.com/revelc/impsort-maven-plugin/blob/main/src/test/resources/UnusedImports.java +@Component +public class UnusedImports { + ImmutableMap immutable; + + /** + * The following should also not be removed: + * + * @param blah when {@link Type13} blah + * @see Map + */ + public List getList(String blah) { + assertFalse(false); + return null; + } + + /** + * {@link Type1#method()} + * {@link Type2#method(Type3, Type4)} + * {@link #method(Type5, Type6)} + * {@value Type7#field} + * @see Type8#method() + * @see Type9#method(Type10) + * @throws Type11 when {@link Type12} is seen + */ + public void foo() { + } +} diff --git a/testlib/src/main/resources/java/removeunusedimports/RevelcUnformatted.test b/testlib/src/main/resources/java/removeunusedimports/RevelcUnformatted.test new file mode 100644 index 0000000000..bb43884a82 --- /dev/null +++ b/testlib/src/main/resources/java/removeunusedimports/RevelcUnformatted.test @@ -0,0 +1,89 @@ +/* + * 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 + * + * http://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. + */ + +@XmlSchema( + xmlns = { + @XmlNs(prefix = "order", namespaceURI = "http://www.camel.apache.org/jaxb/example/order/1"), + @XmlNs(prefix = "address", namespaceURI = "http://www.camel.apache.org/jaxb/example/address/1") + } +) +package net.revelc.code.imp; + +import com.foo.Type1; +import com.foo.Type2; +import com.foo.Type3; +import com.foo.Type4; +import com.foo.Type5; +import com.foo.Type6; +import com.foo.Type7; +import com.foo.Type8; +import com.foo.Type9; +import com.foo.Type10; +import net.revelc.code.imp.Something; +import net.revelc.code.imp.Something.Else; +import net.revelc.code.imp.*; + +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableMap; +import io.swagger.annotations.ApiOperation; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.foo.Type11; +import com.foo.internal.Type12; +import com.foo.params.Type13; +import javax.xml.bind.annotation.XmlNs; +import javax.xml.bind.annotation.XmlSchema; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import static org.junit.Assert.*; + +/** + * The import of {@link HashMap} should not be stripped away, since it + * is used in this comment. + */ +// https://github.com/revelc/impsort-maven-plugin/blob/main/src/test/resources/UnusedImports.java +@Component +public class UnusedImports { + ImmutableMap immutable; + + /** + * The following should also not be removed: + * + * @param blah when {@link Type13} blah + * @see Map + */ + public List getList(String blah) { + assertFalse(false); + return null; + } + + /** + * {@link Type1#method()} + * {@link Type2#method(Type3, Type4)} + * {@link #method(Type5, Type6)} + * {@value Type7#field} + * @see Type8#method() + * @see Type9#method(Type10) + * @throws Type11 when {@link Type12} is seen + */ + public void foo() { + } +} diff --git a/testlib/src/main/resources/java/removeunusedimports/SealedClassTestsFormatted.test b/testlib/src/main/resources/java/removeunusedimports/SealedClassTestsFormatted.test new file mode 100644 index 0000000000..f0a0459883 --- /dev/null +++ b/testlib/src/main/resources/java/removeunusedimports/SealedClassTestsFormatted.test @@ -0,0 +1,44 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package java.removeunusedimports; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Test; +class SealedClassTests extends AbstractJupiterTestEngineTests { + + @Test + void sealedTestClassesAreTestClasses() { + executeTestsForClass(TestCase.class).testEvents() // + .assertStatistics(stats -> stats.finished(2).succeeded(1).failed(1)); + } + + sealed + abstract static class AbstractTestCase + permits TestCase + { + + @Test + void succeedingTest() { + assertTrue(true); + } + + @Test + void failingTest() { + fail("always fails"); + } + } + + static final class TestCase extends AbstractTestCase { + } + +} diff --git a/testlib/src/main/resources/java/removeunusedimports/SealedClassTestsUnformatted.test b/testlib/src/main/resources/java/removeunusedimports/SealedClassTestsUnformatted.test new file mode 100644 index 0000000000..942aa4d235 --- /dev/null +++ b/testlib/src/main/resources/java/removeunusedimports/SealedClassTestsUnformatted.test @@ -0,0 +1,46 @@ +/* + * Copyright 2015-2023 the original author or authors. + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License v2.0 which + * accompanies this distribution and is available at + * + * https://www.eclipse.org/legal/epl-v20.html + */ + +package java.removeunusedimports; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import org.junit.jupiter.api.Test; +import useless.project.UnusedClass; + +class SealedClassTests extends AbstractJupiterTestEngineTests { + + @Test + void sealedTestClassesAreTestClasses() { + executeTestsForClass(TestCase.class).testEvents() // + .assertStatistics(stats -> stats.finished(2).succeeded(1).failed(1)); + } + + sealed + abstract static class AbstractTestCase + permits TestCase + { + + @Test + void succeedingTest() { + assertTrue(true); + } + + @Test + void failingTest() { + fail("always fails"); + } + } + + static final class TestCase extends AbstractTestCase { + } + +} diff --git a/testlib/src/main/resources/json/patchObjectAfterReplaceString.json b/testlib/src/main/resources/json/patchObjectAfterReplaceString.json new file mode 100644 index 0000000000..1da41d5ac2 --- /dev/null +++ b/testlib/src/main/resources/json/patchObjectAfterReplaceString.json @@ -0,0 +1,11 @@ +{ + "abc": "ghi", + "obj": { + "arr": [ + 1, + 2, + 3 + ], + "val": 5 + } +} diff --git a/testlib/src/main/resources/json/patchObjectAfterReplaceWithObject.json b/testlib/src/main/resources/json/patchObjectAfterReplaceWithObject.json new file mode 100644 index 0000000000..533691261c --- /dev/null +++ b/testlib/src/main/resources/json/patchObjectAfterReplaceWithObject.json @@ -0,0 +1,13 @@ +{ + "abc": { + "def": "ghi" + }, + "obj": { + "arr": [ + 1, + 2, + 3 + ], + "val": 5 + } +} diff --git a/testlib/src/main/resources/json/patchObjectBefore.json b/testlib/src/main/resources/json/patchObjectBefore.json new file mode 100644 index 0000000000..070dfc481e --- /dev/null +++ b/testlib/src/main/resources/json/patchObjectBefore.json @@ -0,0 +1,11 @@ +{ + "abc": "def", + "obj": { + "arr": [ + 1, + 2, + 3 + ], + "val": 5 + } +} diff --git a/testlib/src/main/resources/json/singletonArrayAfter_Jackson.json b/testlib/src/main/resources/json/singletonArrayAfter_Jackson.json new file mode 100644 index 0000000000..243bc2550b --- /dev/null +++ b/testlib/src/main/resources/json/singletonArrayAfter_Jackson.json @@ -0,0 +1 @@ +[ 1, 2, 3, 4 ] \ No newline at end of file diff --git a/testlib/src/main/resources/json/singletonArrayBefore.json b/testlib/src/main/resources/json/singletonArrayBefore.json index 8290d39198..18d09f95fe 100644 --- a/testlib/src/main/resources/json/singletonArrayBefore.json +++ b/testlib/src/main/resources/json/singletonArrayBefore.json @@ -1 +1 @@ -[ 1, 2, 3, 4 ] +[ 1 , 2, 3, 4 ] diff --git a/testlib/src/main/resources/json/sortByKeysAfter.json b/testlib/src/main/resources/json/sortByKeysAfter.json index c4a48de2f2..070904e872 100644 --- a/testlib/src/main/resources/json/sortByKeysAfter.json +++ b/testlib/src/main/resources/json/sortByKeysAfter.json @@ -6,6 +6,12 @@ 2, 1 ], + "_objectsInArraysAreSorted": [ + { + "a": 1, + "b": 2 + } + ], "a": 3, "c": 4, "x": 5, diff --git a/testlib/src/main/resources/json/sortByKeysAfterDisabled.json b/testlib/src/main/resources/json/sortByKeysAfterDisabled.json index de7462bb98..eb9e38241f 100644 --- a/testlib/src/main/resources/json/sortByKeysAfterDisabled.json +++ b/testlib/src/main/resources/json/sortByKeysAfterDisabled.json @@ -15,5 +15,11 @@ 3, 2, 1 + ], + "_objectsInArraysAreSorted": [ + { + "b": 2, + "a": 1 + } ] } diff --git a/testlib/src/main/resources/json/sortByKeysAfterDisabled_Simple.json b/testlib/src/main/resources/json/sortByKeysAfterDisabled_Simple.json new file mode 100644 index 0000000000..cd7ebc2be1 --- /dev/null +++ b/testlib/src/main/resources/json/sortByKeysAfterDisabled_Simple.json @@ -0,0 +1,23 @@ +{ + "A": 1, + "a": 3, + "c": 4, + "x": 5, + "X": 2, + "z": { + "A": 1, + "a": 3, + "c": 4, + "x": 5, + "X": 2 + }, + "_objectsInArraysAreSorted": [{ + "a": 1, + "b": 2 + }], + "_arraysNotSorted": [ + 3, + 2, + 1 + ] +} diff --git a/testlib/src/main/resources/json/sortByKeysAfter_Jackson.json b/testlib/src/main/resources/json/sortByKeysAfter_Jackson.json new file mode 100644 index 0000000000..003b2ba66f --- /dev/null +++ b/testlib/src/main/resources/json/sortByKeysAfter_Jackson.json @@ -0,0 +1,19 @@ +{ + "A": 1, + "X": 2, + "_arraysNotSorted": [ 3, 2, 1 ], + "_objectsInArraysAreSorted": [ { + "a": 1, + "b": 2 + } ], + "a": 3, + "c": 4, + "x": 5, + "z": { + "A": 1, + "X": 2, + "a": 3, + "c": 4, + "x": 5 + } +} diff --git a/testlib/src/main/resources/json/sortByKeysAfter_Jackson_spaceAfterKeySeparator.json b/testlib/src/main/resources/json/sortByKeysAfter_Jackson_spaceAfterKeySeparator.json new file mode 100644 index 0000000000..003b2ba66f --- /dev/null +++ b/testlib/src/main/resources/json/sortByKeysAfter_Jackson_spaceAfterKeySeparator.json @@ -0,0 +1,19 @@ +{ + "A": 1, + "X": 2, + "_arraysNotSorted": [ 3, 2, 1 ], + "_objectsInArraysAreSorted": [ { + "a": 1, + "b": 2 + } ], + "a": 3, + "c": 4, + "x": 5, + "z": { + "A": 1, + "X": 2, + "a": 3, + "c": 4, + "x": 5 + } +} diff --git a/testlib/src/main/resources/json/sortByKeysBefore.json b/testlib/src/main/resources/json/sortByKeysBefore.json index de7462bb98..eb9e38241f 100644 --- a/testlib/src/main/resources/json/sortByKeysBefore.json +++ b/testlib/src/main/resources/json/sortByKeysBefore.json @@ -15,5 +15,11 @@ 3, 2, 1 + ], + "_objectsInArraysAreSorted": [ + { + "b": 2, + "a": 1 + } ] } diff --git a/testlib/src/main/resources/kotlin/ktfmt/basic-dropbox-style.clean b/testlib/src/main/resources/kotlin/ktfmt/basic-dropbox-style.clean new file mode 100644 index 0000000000..428b6d69fb --- /dev/null +++ b/testlib/src/main/resources/kotlin/ktfmt/basic-dropbox-style.clean @@ -0,0 +1,14 @@ +import a.* +import a.b +import a.b.c.* +import kotlinx.android.synthetic.main.layout_name.* + +fun main() { + fun name() { + a() + return b + } + println( + ";") + println() +} diff --git a/testlib/src/main/resources/kotlin/ktfmt/continuation.clean b/testlib/src/main/resources/kotlin/ktfmt/continuation.clean new file mode 100644 index 0000000000..11677928fc --- /dev/null +++ b/testlib/src/main/resources/kotlin/ktfmt/continuation.clean @@ -0,0 +1,6 @@ +fun myFunction() { + val location = + restTemplate.postForLocation( + "/v1/my-api", mapOf("name" to "some-name", "url" to "https://www.google.com")) + return location +} diff --git a/testlib/src/main/resources/kotlin/ktfmt/continuation.dirty b/testlib/src/main/resources/kotlin/ktfmt/continuation.dirty new file mode 100644 index 0000000000..3652274d12 --- /dev/null +++ b/testlib/src/main/resources/kotlin/ktfmt/continuation.dirty @@ -0,0 +1,4 @@ +fun myFunction() { + val location = restTemplate.postForLocation("/v1/my-api", mapOf("name" to "some-name", "url" to "https://www.google.com")) + return location +} diff --git a/testlib/src/main/resources/kotlin/ktlint/basic-old.clean b/testlib/src/main/resources/kotlin/ktlint/basic-old.clean new file mode 100644 index 0000000000..fd5371bed2 --- /dev/null +++ b/testlib/src/main/resources/kotlin/ktlint/basic-old.clean @@ -0,0 +1,5 @@ +fun main() { + fun name() { a(); return b } + println(";") + println() +} diff --git a/testlib/src/main/resources/kotlin/ktlint/basic.clean b/testlib/src/main/resources/kotlin/ktlint/basic.clean index fd5371bed2..b727a7d016 100644 --- a/testlib/src/main/resources/kotlin/ktlint/basic.clean +++ b/testlib/src/main/resources/kotlin/ktlint/basic.clean @@ -1,5 +1,8 @@ fun main() { - fun name() { a(); return b } + fun name() { + a() + return b + } println(";") println() } diff --git a/testlib/src/main/resources/kotlin/ktlint/experimentalEditorConfigOverride.ktlintOfficial.clean b/testlib/src/main/resources/kotlin/ktlint/experimentalEditorConfigOverride.ktlintOfficial.clean new file mode 100644 index 0000000000..2466bb93fc --- /dev/null +++ b/testlib/src/main/resources/kotlin/ktlint/experimentalEditorConfigOverride.ktlintOfficial.clean @@ -0,0 +1,6 @@ +fun main() { + val list = + listOf( + "hello", + ) +} diff --git a/testlib/src/main/resources/kotlin/ktlint/intellij_idea/.editorconfig b/testlib/src/main/resources/kotlin/ktlint/intellij_idea/.editorconfig new file mode 100644 index 0000000000..9efc62a854 --- /dev/null +++ b/testlib/src/main/resources/kotlin/ktlint/intellij_idea/.editorconfig @@ -0,0 +1,6 @@ +root = true + +[*.{kt,kts}] +ij_kotlin_allow_trailing_comma = true +ij_kotlin_allow_trailing_comma_on_call_site = true +ktlint_code_style = intellij_idea diff --git a/testlib/src/main/resources/kotlin/ktlint/ktlint_official/.editorconfig b/testlib/src/main/resources/kotlin/ktlint/ktlint_official/.editorconfig new file mode 100644 index 0000000000..93a01f2e59 --- /dev/null +++ b/testlib/src/main/resources/kotlin/ktlint/ktlint_official/.editorconfig @@ -0,0 +1,6 @@ +root = true + +[*.{kt,kts}] +ij_kotlin_allow_trailing_comma = true +ij_kotlin_allow_trailing_comma_on_call_site = true +ktlint_code_style = ktlint_official diff --git a/testlib/src/main/resources/kotlin/ktlint/listScreen.dirty b/testlib/src/main/resources/kotlin/ktlint/listScreen.dirty new file mode 100644 index 0000000000..14c06d6d43 --- /dev/null +++ b/testlib/src/main/resources/kotlin/ktlint/listScreen.dirty @@ -0,0 +1,6 @@ +import androidx.compose.runtime.Composable + +@Composable +fun listScreen() { + val list: List = mutableListOf() +} diff --git a/testlib/src/main/resources/license/HelloWorld_java.test b/testlib/src/main/resources/license/HelloWorld_java.test new file mode 100644 index 0000000000..d9be2ea939 --- /dev/null +++ b/testlib/src/main/resources/license/HelloWorld_java.test @@ -0,0 +1,5 @@ +public class HelloWorld { + public static void main(String[] args) { + System.out.print("Hello World"); + } +} diff --git a/testlib/src/main/resources/license/HelloWorld_withImport_java.test b/testlib/src/main/resources/license/HelloWorld_withImport_java.test new file mode 100644 index 0000000000..fcfb64117e --- /dev/null +++ b/testlib/src/main/resources/license/HelloWorld_withImport_java.test @@ -0,0 +1,7 @@ +import java.time.LocalDate; + +public class HelloWorld { + public static void main(String[] args) { + System.out.print("Hello World. Date: " + LocalDate.now()); + } +} diff --git a/testlib/src/main/resources/license/SkipLines.test b/testlib/src/main/resources/license/SkipLines.test new file mode 100644 index 0000000000..4048868ac6 --- /dev/null +++ b/testlib/src/main/resources/license/SkipLines.test @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/testlib/src/main/resources/license/SkipLinesHasLicense.test b/testlib/src/main/resources/license/SkipLinesHasLicense.test new file mode 100644 index 0000000000..f645c5b2e2 --- /dev/null +++ b/testlib/src/main/resources/license/SkipLinesHasLicense.test @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/testlib/src/main/resources/license/module-info.test b/testlib/src/main/resources/license/module-info.test new file mode 100644 index 0000000000..6678147b68 --- /dev/null +++ b/testlib/src/main/resources/license/module-info.test @@ -0,0 +1,5 @@ +module java.sql { + exports java.sql; + exports javax.sql; + exports javax.transaction.xa; +} diff --git a/testlib/src/main/resources/npm/eslint/config/.eslintrc.js b/testlib/src/main/resources/npm/eslint/config/.eslintrc.js new file mode 100644 index 0000000000..6cb3070512 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/config/.eslintrc.js @@ -0,0 +1,38 @@ +module.exports = { + "env": { + "browser": true, + "es2021": true + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "overrides": [ + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "indent": [ + "error", + 4 + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "double" + ], + "semi": [ + "error", + "always" + ] + } +}; diff --git a/testlib/src/main/resources/npm/prettier/config/typescript.configfile.clean b/testlib/src/main/resources/npm/eslint/config/typescript.configfile.clean similarity index 100% rename from testlib/src/main/resources/npm/prettier/config/typescript.configfile.clean rename to testlib/src/main/resources/npm/eslint/config/typescript.configfile.clean diff --git a/testlib/src/main/resources/npm/prettier/config/typescript.defaults.clean b/testlib/src/main/resources/npm/eslint/config/typescript.defaults.clean similarity index 100% rename from testlib/src/main/resources/npm/prettier/config/typescript.defaults.clean rename to testlib/src/main/resources/npm/eslint/config/typescript.defaults.clean diff --git a/testlib/src/main/resources/npm/eslint/config/typescript.dirty b/testlib/src/main/resources/npm/eslint/config/typescript.dirty new file mode 100644 index 0000000000..a3a30bf49a --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/config/typescript.dirty @@ -0,0 +1,10 @@ +export class MyVeryOwnControllerWithARatherLongNameThatIsNotReallyNecessary extends AbstractController implements DisposeAware, CallbackAware { + + +public myValue:string[]; + +constructor(private myService:Service,name:string,private field:any){ super(name) ;} + + +//... +} diff --git a/testlib/src/main/resources/npm/prettier/config/typescript.override.clean b/testlib/src/main/resources/npm/eslint/config/typescript.override.clean similarity index 100% rename from testlib/src/main/resources/npm/prettier/config/typescript.override.clean rename to testlib/src/main/resources/npm/eslint/config/typescript.override.clean diff --git a/testlib/src/main/resources/npm/eslint/javascript/custom_rules/.eslintrc.js b/testlib/src/main/resources/npm/eslint/javascript/custom_rules/.eslintrc.js new file mode 100644 index 0000000000..60c5c19f5b --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/javascript/custom_rules/.eslintrc.js @@ -0,0 +1,31 @@ +module.exports = { + "env": { + "browser": true, + "es2021": true + }, + "extends": "eslint:recommended", + "overrides": [ + ], + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "rules": { + "indent": [ + "error", + 2 + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "double" + ], + "semi": [ + "error", + "always" + ] + } +}; diff --git a/testlib/src/main/resources/npm/eslint/javascript/custom_rules/javascript-es6.clean b/testlib/src/main/resources/npm/eslint/javascript/custom_rules/javascript-es6.clean new file mode 100644 index 0000000000..8bbeb50cf3 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/javascript/custom_rules/javascript-es6.clean @@ -0,0 +1,21 @@ +var numbers=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20, +]; + +const p = { + first: "Peter", + last : "Pan", + get fullName() { return this.first + " " + this.last; } +}; + +const str = "Hello, world!" +; + +var str2=str.charAt(3)+str[0]; + +var multilinestr = "Hello \ +World" +; + +function test (a, b = "world") { let combined =a+ b; return combined;} + +test ("Hello"); diff --git a/testlib/src/main/resources/npm/eslint/javascript/custom_rules/javascript-es6.dirty b/testlib/src/main/resources/npm/eslint/javascript/custom_rules/javascript-es6.dirty new file mode 100644 index 0000000000..36a514cc30 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/javascript/custom_rules/javascript-es6.dirty @@ -0,0 +1,21 @@ +var numbers=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20, +]; + +const p = { + first: 'Peter', + last : 'Pan', + get fullName() { return this.first + ' ' + this.last; } +}; + +const str = 'Hello, world!' +; + +var str2=str.charAt(3)+str[0]; + +var multilinestr = 'Hello \ +World' +; + +function test (a, b = 'world') { let combined =a+ b; return combined}; + +test ('Hello'); diff --git a/testlib/src/main/resources/npm/eslint/javascript/styleguide/airbnb/.eslintrc.js b/testlib/src/main/resources/npm/eslint/javascript/styleguide/airbnb/.eslintrc.js new file mode 100644 index 0000000000..d93248ee88 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/javascript/styleguide/airbnb/.eslintrc.js @@ -0,0 +1,15 @@ +module.exports = { + env: { + browser: true, + es2021: true, + }, + extends: 'airbnb-base', + overrides: [ + ], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, + rules: { + }, +}; diff --git a/testlib/src/main/resources/npm/eslint/javascript/styleguide/airbnb/javascript-es6.clean b/testlib/src/main/resources/npm/eslint/javascript/styleguide/airbnb/javascript-es6.clean new file mode 100644 index 0000000000..c8dda3d33f --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/javascript/styleguide/airbnb/javascript-es6.clean @@ -0,0 +1,17 @@ +const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, +]; + +const p = { + first: 'Peter', + last: 'Pan', + get fullName() { return `${this.first} ${this.last}`; }, +}; + +const str = 'Hello, world!'; +const str2 = str.charAt(3) + str[0]; + +const multilinestr = 'Hello \ +World'; +function test(a, b = 'world') { const combined = a + b; return combined; } + +test('Hello'); diff --git a/testlib/src/main/resources/npm/eslint/javascript/styleguide/airbnb/javascript-es6.dirty b/testlib/src/main/resources/npm/eslint/javascript/styleguide/airbnb/javascript-es6.dirty new file mode 100644 index 0000000000..08ebafc1a2 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/javascript/styleguide/airbnb/javascript-es6.dirty @@ -0,0 +1,21 @@ +var numbers=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20, +]; + +const p = { + first: "Peter", + last : "Pan", + get fullName() { return this.first + " " + this.last; } +}; + +const str = "Hello, world!" +; + +var str2=str.charAt(3)+str[0]; + +var multilinestr = "Hello \ +World" +; + +function test (a, b = "world") { let combined =a+ b; return combined}; + +test ("Hello"); diff --git a/testlib/src/main/resources/npm/eslint/javascript/styleguide/google/.eslintrc.js b/testlib/src/main/resources/npm/eslint/javascript/styleguide/google/.eslintrc.js new file mode 100644 index 0000000000..71b0c47016 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/javascript/styleguide/google/.eslintrc.js @@ -0,0 +1,15 @@ +module.exports = { + 'env': { + 'browser': true, + 'es2021': true, + }, + 'extends': 'google', + 'overrides': [ + ], + 'parserOptions': { + 'ecmaVersion': 'latest', + 'sourceType': 'module', + }, + 'rules': { + }, +}; diff --git a/testlib/src/main/resources/npm/eslint/javascript/styleguide/google/javascript-es6.clean b/testlib/src/main/resources/npm/eslint/javascript/styleguide/google/javascript-es6.clean new file mode 100644 index 0000000000..f960a984c2 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/javascript/styleguide/google/javascript-es6.clean @@ -0,0 +1,25 @@ +const numbers=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, +]; + +const p = { + first: 'Peter', + last: 'Pan', + get fullName() { + return this.first + ' ' + this.last; + }, +}; + +const str = 'Hello, world!' +; + +const str2=str.charAt(3)+str[0]; + +const multilinestr = 'Hello \ +World' +; + +function test(a, b = 'world') { + const combined =a+ b; return combined; +}; + +test('Hello'); diff --git a/testlib/src/main/resources/npm/eslint/javascript/styleguide/google/javascript-es6.dirty b/testlib/src/main/resources/npm/eslint/javascript/styleguide/google/javascript-es6.dirty new file mode 100644 index 0000000000..08ebafc1a2 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/javascript/styleguide/google/javascript-es6.dirty @@ -0,0 +1,21 @@ +var numbers=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20, +]; + +const p = { + first: "Peter", + last : "Pan", + get fullName() { return this.first + " " + this.last; } +}; + +const str = "Hello, world!" +; + +var str2=str.charAt(3)+str[0]; + +var multilinestr = "Hello \ +World" +; + +function test (a, b = "world") { let combined =a+ b; return combined}; + +test ("Hello"); diff --git a/testlib/src/main/resources/npm/eslint/javascript/styleguide/standard/.eslintrc.js b/testlib/src/main/resources/npm/eslint/javascript/styleguide/standard/.eslintrc.js new file mode 100644 index 0000000000..cbed25aab9 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/javascript/styleguide/standard/.eslintrc.js @@ -0,0 +1,15 @@ +module.exports = { + env: { + browser: true, + es2021: true + }, + extends: 'standard', + overrides: [ + ], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module' + }, + rules: { + } +} diff --git a/testlib/src/main/resources/npm/eslint/javascript/styleguide/standard/javascript-es6.clean b/testlib/src/main/resources/npm/eslint/javascript/styleguide/standard/javascript-es6.clean new file mode 100644 index 0000000000..757d330a7f --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/javascript/styleguide/standard/javascript-es6.clean @@ -0,0 +1,19 @@ +const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 +] + +const p = { + first: 'Peter', + last: 'Pan', + get fullName () { return this.first + ' ' + this.last } +} + +const str = 'Hello, world!' + +const str2 = str.charAt(3) + str[0] + +const multilinestr = 'Hello \ +World' + +function test (a, b = 'world') { const combined = a + b; return combined }; + +test('Hello') diff --git a/testlib/src/main/resources/npm/eslint/javascript/styleguide/standard/javascript-es6.dirty b/testlib/src/main/resources/npm/eslint/javascript/styleguide/standard/javascript-es6.dirty new file mode 100644 index 0000000000..08ebafc1a2 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/javascript/styleguide/standard/javascript-es6.dirty @@ -0,0 +1,21 @@ +var numbers=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20, +]; + +const p = { + first: "Peter", + last : "Pan", + get fullName() { return this.first + " " + this.last; } +}; + +const str = "Hello, world!" +; + +var str2=str.charAt(3)+str[0]; + +var multilinestr = "Hello \ +World" +; + +function test (a, b = "world") { let combined =a+ b; return combined}; + +test ("Hello"); diff --git a/testlib/src/main/resources/npm/eslint/javascript/styleguide/xo/.eslintrc.js b/testlib/src/main/resources/npm/eslint/javascript/styleguide/xo/.eslintrc.js new file mode 100644 index 0000000000..844e843c41 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/javascript/styleguide/xo/.eslintrc.js @@ -0,0 +1,15 @@ +module.exports = { + env: { + browser: true, + es2021: true, + }, + extends: 'xo', + overrides: [ + ], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, + rules: { + }, +}; diff --git a/testlib/src/main/resources/npm/eslint/javascript/styleguide/xo/javascript-es6.clean b/testlib/src/main/resources/npm/eslint/javascript/styleguide/xo/javascript-es6.clean new file mode 100644 index 0000000000..3cd1bd0865 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/javascript/styleguide/xo/javascript-es6.clean @@ -0,0 +1,20 @@ +const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]; + +const p = { + first: 'Peter', + last: 'Pan', + get fullName() { + return this.first + ' ' + this.last; + }, +}; + +const str = 'Hello, world!'; +const str2 = str.charAt(3) + str[0]; + +const multilinestr = 'Hello \ +World'; +function test(a, b = 'world') { + const combined = a + b; return combined; +} + +test('Hello'); diff --git a/testlib/src/main/resources/npm/eslint/javascript/styleguide/xo/javascript-es6.dirty b/testlib/src/main/resources/npm/eslint/javascript/styleguide/xo/javascript-es6.dirty new file mode 100644 index 0000000000..08ebafc1a2 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/javascript/styleguide/xo/javascript-es6.dirty @@ -0,0 +1,21 @@ +var numbers=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20, +]; + +const p = { + first: "Peter", + last : "Pan", + get fullName() { return this.first + " " + this.last; } +}; + +const str = "Hello, world!" +; + +var str2=str.charAt(3)+str[0]; + +var multilinestr = "Hello \ +World" +; + +function test (a, b = "world") { let combined =a+ b; return combined}; + +test ("Hello"); diff --git a/testlib/src/main/resources/npm/eslint/typescript/custom_rules/.eslintrc.js b/testlib/src/main/resources/npm/eslint/typescript/custom_rules/.eslintrc.js new file mode 100644 index 0000000000..e6ddff7d4c --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/typescript/custom_rules/.eslintrc.js @@ -0,0 +1,59 @@ +module.exports = { + "env": { + "browser": true, + "es2021": true + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "overrides": [ + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "indent": [ + "error", + 4 + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "double" + ], + "semi": [ + "error", + "always" + ], + "curly": [ + "error" + ], + "max-statements-per-line": [ + "error", + { "max": 1 } + ], + "object-curly-newline": [ + "error", + "always" + ], + "comma-spacing": [ + "error", + { "before": false, "after": true } + ], + "object-property-newline": [ + "error", + ], + "no-trailing-spaces": [ + "error" + ], + } +}; diff --git a/testlib/src/main/resources/npm/eslint/typescript/custom_rules/typescript.clean b/testlib/src/main/resources/npm/eslint/typescript/custom_rules/typescript.clean new file mode 100644 index 0000000000..526519cae9 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/typescript/custom_rules/typescript.clean @@ -0,0 +1,11 @@ +export class MyController extends AbstractController implements DisposeAware, CallbackAware { + + + public myValue:string[]; + + constructor(private myService:Service, name:string, private field:any){ super(name) ;} + + + //... + +} diff --git a/testlib/src/main/resources/npm/eslint/typescript/custom_rules/typescript.dirty b/testlib/src/main/resources/npm/eslint/typescript/custom_rules/typescript.dirty new file mode 100644 index 0000000000..0a9201c2e7 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/typescript/custom_rules/typescript.dirty @@ -0,0 +1,11 @@ +export class MyController extends AbstractController implements DisposeAware, CallbackAware { + + +public myValue:string[]; + +constructor(private myService:Service,name:string,private field:any){ super(name) ;} + + +//... + +} diff --git a/testlib/src/main/resources/npm/eslint/typescript/styleguide/standard_with_typescript/.eslintrc.js b/testlib/src/main/resources/npm/eslint/typescript/styleguide/standard_with_typescript/.eslintrc.js new file mode 100644 index 0000000000..0a86d5db86 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/typescript/styleguide/standard_with_typescript/.eslintrc.js @@ -0,0 +1,16 @@ +module.exports = { + env: { + browser: true, + es2021: true + }, + extends: 'standard-with-typescript', + overrides: [ + ], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: './tsconfig.json', + }, + rules: { + } +} diff --git a/testlib/src/main/resources/npm/eslint/typescript/styleguide/standard_with_typescript/tsconfig.json b/testlib/src/main/resources/npm/eslint/typescript/styleguide/standard_with_typescript/tsconfig.json new file mode 100644 index 0000000000..629f834eb5 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/typescript/styleguide/standard_with_typescript/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "module": "ES6", + "noImplicitAny": true, + "outDir": "build/ide/", + "sourceMap": true, + "skipLibCheck": true, + "target": "ES6", + "baseUrl": "./", + "paths": { + }, + "lib": ["es7", "dom", "es2017"] + }, + "include": [ + "**/*" + ] +} diff --git a/testlib/src/main/resources/npm/eslint/typescript/styleguide/standard_with_typescript/typescript.clean b/testlib/src/main/resources/npm/eslint/typescript/styleguide/standard_with_typescript/typescript.clean new file mode 100644 index 0000000000..cbe609b1bb --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/typescript/styleguide/standard_with_typescript/typescript.clean @@ -0,0 +1,7 @@ +export class MyController extends AbstractController implements DisposeAware, CallbackAware { + public myValue: string[] + + constructor (private readonly myService: Service, name: string, private readonly field: any) { super(name) } + + // ... +} diff --git a/testlib/src/main/resources/npm/eslint/typescript/styleguide/standard_with_typescript/typescript.dirty b/testlib/src/main/resources/npm/eslint/typescript/styleguide/standard_with_typescript/typescript.dirty new file mode 100644 index 0000000000..a8f7447af1 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/typescript/styleguide/standard_with_typescript/typescript.dirty @@ -0,0 +1,10 @@ +export class MyController extends AbstractController implements DisposeAware, CallbackAware { + +public myValue:string[]; + +constructor(private myService:Service,name:string,private field:any){ super(name) ;} + + +//... + +} diff --git a/testlib/src/main/resources/npm/eslint/typescript/styleguide/xo/.eslintrc.js b/testlib/src/main/resources/npm/eslint/typescript/styleguide/xo/.eslintrc.js new file mode 100644 index 0000000000..e1ca03f732 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/typescript/styleguide/xo/.eslintrc.js @@ -0,0 +1,26 @@ +module.exports = { + env: { + browser: true, + es2021: true, + }, + extends: 'xo/browser', + overrides: [ + { + extends: [ + 'xo-typescript', + ], + files: [ + '*.ts', + '*.tsx', + ], + }, + ], + parser: "@typescript-eslint/parser", + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: './tsconfig.json', + }, + rules: { + }, +}; diff --git a/testlib/src/main/resources/npm/eslint/typescript/styleguide/xo/tsconfig.json b/testlib/src/main/resources/npm/eslint/typescript/styleguide/xo/tsconfig.json new file mode 100644 index 0000000000..629f834eb5 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/typescript/styleguide/xo/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "module": "ES6", + "noImplicitAny": true, + "outDir": "build/ide/", + "sourceMap": true, + "skipLibCheck": true, + "target": "ES6", + "baseUrl": "./", + "paths": { + }, + "lib": ["es7", "dom", "es2017"] + }, + "include": [ + "**/*" + ] +} diff --git a/testlib/src/main/resources/npm/eslint/typescript/styleguide/xo/typescript.clean b/testlib/src/main/resources/npm/eslint/typescript/styleguide/xo/typescript.clean new file mode 100644 index 0000000000..5c43d7a746 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/typescript/styleguide/xo/typescript.clean @@ -0,0 +1,9 @@ +export class MyController extends AbstractController implements DisposeAware, CallbackAware { + public myValue: string[]; + + constructor(private readonly myService: Service, name: string, private readonly field: any) { + super(name); + } + + // ... +} diff --git a/testlib/src/main/resources/npm/eslint/typescript/styleguide/xo/typescript.dirty b/testlib/src/main/resources/npm/eslint/typescript/styleguide/xo/typescript.dirty new file mode 100644 index 0000000000..a8f7447af1 --- /dev/null +++ b/testlib/src/main/resources/npm/eslint/typescript/styleguide/xo/typescript.dirty @@ -0,0 +1,10 @@ +export class MyController extends AbstractController implements DisposeAware, CallbackAware { + +public myValue:string[]; + +constructor(private myService:Service,name:string,private field:any){ super(name) ;} + + +//... + +} diff --git a/testlib/src/main/resources/npm/prettier/config/.prettierrc.yml b/testlib/src/main/resources/npm/prettier/config/.prettierrc.yml index f078267520..828586c801 100644 --- a/testlib/src/main/resources/npm/prettier/config/.prettierrc.yml +++ b/testlib/src/main/resources/npm/prettier/config/.prettierrc.yml @@ -1,2 +1,2 @@ parser: typescript -printWidth: 50 +printWidth: 20 diff --git a/testlib/src/main/resources/npm/prettier/config/.prettierrc_java_plugin.yml b/testlib/src/main/resources/npm/prettier/config/.prettierrc_java_plugin.yml new file mode 100644 index 0000000000..3f7a801335 --- /dev/null +++ b/testlib/src/main/resources/npm/prettier/config/.prettierrc_java_plugin.yml @@ -0,0 +1,3 @@ +parser: java +tabWidth: 4 +plugins: ['prettier-plugin-java'] diff --git a/testlib/src/main/resources/npm/prettier/config/typescript.configfile_prettier_2.clean b/testlib/src/main/resources/npm/prettier/config/typescript.configfile_prettier_2.clean new file mode 100644 index 0000000000..451b5466a0 --- /dev/null +++ b/testlib/src/main/resources/npm/prettier/config/typescript.configfile_prettier_2.clean @@ -0,0 +1,18 @@ +export class MyVeryOwnControllerWithARatherLongNameThatIsNotReallyNecessary + extends AbstractController + implements + DisposeAware, + CallbackAware +{ + public myValue: string[]; + + constructor( + private myService: Service, + name: string, + private field: any + ) { + super(name); + } + + //... +} diff --git a/testlib/src/main/resources/npm/prettier/config/typescript.configfile_prettier_3.clean b/testlib/src/main/resources/npm/prettier/config/typescript.configfile_prettier_3.clean new file mode 100644 index 0000000000..30d30f8595 --- /dev/null +++ b/testlib/src/main/resources/npm/prettier/config/typescript.configfile_prettier_3.clean @@ -0,0 +1,18 @@ +export class MyVeryOwnControllerWithARatherLongNameThatIsNotReallyNecessary + extends AbstractController + implements + DisposeAware, + CallbackAware +{ + public myValue: string[]; + + constructor( + private myService: Service, + name: string, + private field: any, + ) { + super(name); + } + + //... +} diff --git a/testlib/src/main/resources/npm/prettier/config/typescript.defaults_prettier_2.clean b/testlib/src/main/resources/npm/prettier/config/typescript.defaults_prettier_2.clean new file mode 100644 index 0000000000..61d8e020d6 --- /dev/null +++ b/testlib/src/main/resources/npm/prettier/config/typescript.defaults_prettier_2.clean @@ -0,0 +1,12 @@ +export class MyVeryOwnControllerWithARatherLongNameThatIsNotReallyNecessary + extends AbstractController + implements DisposeAware, CallbackAware +{ + public myValue: string[]; + + constructor(private myService: Service, name: string, private field: any) { + super(name); + } + + //... +} diff --git a/testlib/src/main/resources/npm/prettier/config/typescript.defaults_prettier_3.clean b/testlib/src/main/resources/npm/prettier/config/typescript.defaults_prettier_3.clean new file mode 100644 index 0000000000..9fa0eed3cc --- /dev/null +++ b/testlib/src/main/resources/npm/prettier/config/typescript.defaults_prettier_3.clean @@ -0,0 +1,16 @@ +export class MyVeryOwnControllerWithARatherLongNameThatIsNotReallyNecessary + extends AbstractController + implements DisposeAware, CallbackAware +{ + public myValue: string[]; + + constructor( + private myService: Service, + name: string, + private field: any, + ) { + super(name); + } + + //... +} diff --git a/testlib/src/main/resources/npm/prettier/config/typescript.override_prettier_2.clean b/testlib/src/main/resources/npm/prettier/config/typescript.override_prettier_2.clean new file mode 100644 index 0000000000..3f3a8c30af --- /dev/null +++ b/testlib/src/main/resources/npm/prettier/config/typescript.override_prettier_2.clean @@ -0,0 +1,9 @@ +export class MyVeryOwnControllerWithARatherLongNameThatIsNotReallyNecessary extends AbstractController implements DisposeAware, CallbackAware { + public myValue: string[]; + + constructor(private myService: Service, name: string, private field: any) { + super(name); + } + + //... +} diff --git a/testlib/src/main/resources/npm/prettier/config/typescript.override_prettier_3.clean b/testlib/src/main/resources/npm/prettier/config/typescript.override_prettier_3.clean new file mode 100644 index 0000000000..c49cd544df --- /dev/null +++ b/testlib/src/main/resources/npm/prettier/config/typescript.override_prettier_3.clean @@ -0,0 +1,13 @@ +export class MyVeryOwnControllerWithARatherLongNameThatIsNotReallyNecessary extends AbstractController implements DisposeAware, CallbackAware { + public myValue: string[]; + + constructor( + private myService: Service, + name: string, + private field: any, + ) { + super(name); + } + + //... +} diff --git a/testlib/src/main/resources/npm/prettier/filetypes/html_prettier3/.prettierrc.yml b/testlib/src/main/resources/npm/prettier/filetypes/html_prettier3/.prettierrc.yml new file mode 100644 index 0000000000..f1185441f8 --- /dev/null +++ b/testlib/src/main/resources/npm/prettier/filetypes/html_prettier3/.prettierrc.yml @@ -0,0 +1 @@ +parser: html diff --git a/testlib/src/main/resources/npm/prettier/filetypes/html_prettier3/html_prettier3.clean b/testlib/src/main/resources/npm/prettier/filetypes/html_prettier3/html_prettier3.clean new file mode 100644 index 0000000000..385e636b7b --- /dev/null +++ b/testlib/src/main/resources/npm/prettier/filetypes/html_prettier3/html_prettier3.clean @@ -0,0 +1,13 @@ + + + + + Test + + + + + + + + diff --git a/testlib/src/main/resources/npm/prettier/filetypes/html_prettier3/html_prettier3.dirty b/testlib/src/main/resources/npm/prettier/filetypes/html_prettier3/html_prettier3.dirty new file mode 100644 index 0000000000..35ed1e9d3b --- /dev/null +++ b/testlib/src/main/resources/npm/prettier/filetypes/html_prettier3/html_prettier3.dirty @@ -0,0 +1,17 @@ + + + + + + Test + + + + + + + + + diff --git a/testlib/src/main/resources/npm/prettier/filetypes/javascript-es5/javascript-es5.clean b/testlib/src/main/resources/npm/prettier/filetypes/javascript-es5/javascript-es5.clean index 29002f6b05..0cfac613f1 100644 --- a/testlib/src/main/resources/npm/prettier/filetypes/javascript-es5/javascript-es5.clean +++ b/testlib/src/main/resources/npm/prettier/filetypes/javascript-es5/javascript-es5.clean @@ -1,24 +1,5 @@ var numbers = [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ]; var p = { @@ -32,7 +13,8 @@ var p = { var str = "Hello, world!"; var str2 = str.charAt(3) + str[0]; -var multilinestr = "Hello \ +var multilinestr = + "Hello \ World"; function test(a, b) { return a + b; diff --git a/testlib/src/main/resources/npm/prettier/filetypes/javascript-es6/javascript-es6.clean b/testlib/src/main/resources/npm/prettier/filetypes/javascript-es6/javascript-es6.clean index d4e982d69f..1935faa03c 100644 --- a/testlib/src/main/resources/npm/prettier/filetypes/javascript-es6/javascript-es6.clean +++ b/testlib/src/main/resources/npm/prettier/filetypes/javascript-es6/javascript-es6.clean @@ -1,24 +1,5 @@ var numbers = [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 18, - 19, - 20, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ]; const p = { @@ -32,7 +13,8 @@ const p = { const str = "Hello, world!"; var str2 = str.charAt(3) + str[0]; -var multilinestr = "Hello \ +var multilinestr = + "Hello \ World"; function test(a, b = "world") { let combined = a + b; diff --git a/testlib/src/main/resources/npm/prettier/plugins/java-test.clean b/testlib/src/main/resources/npm/prettier/plugins/java-test.clean index c45ffe5183..93cf43955f 100644 --- a/testlib/src/main/resources/npm/prettier/plugins/java-test.clean +++ b/testlib/src/main/resources/npm/prettier/plugins/java-test.clean @@ -4,6 +4,7 @@ import java.util.List; import java.util.function.Consumer; public class JavaTest { + private static final String NAME = "JavaTest"; private List strings = new ArrayList<>(); @@ -30,8 +31,8 @@ public class JavaTest { JavaTest javaTest = new JavaTest("1", "2", "3"); System.out.println("joined: " + javaTest.join(',')); StringBuilder builder = new StringBuilder(); - javaTest.operateOn( - strings -> builder.append(String.join("---", strings)) + javaTest.operateOn(strings -> + builder.append(String.join("---", strings)) ); } } diff --git a/testlib/src/main/resources/npm/prettier/plugins/php.clean b/testlib/src/main/resources/npm/prettier/plugins/php.clean index 4c8710bbee..067647a029 100644 --- a/testlib/src/main/resources/npm/prettier/plugins/php.clean +++ b/testlib/src/main/resources/npm/prettier/plugins/php.clean @@ -27,17 +27,17 @@ abstract class ReallyReallyReallyLongClassName // variable doc public $test; public $other = 1; - public static $staticTest = ['hi']; + public static $staticTest = ["hi"]; static $cache; protected static $_instance; - protected $fillable = ['title', 'requester_id', 'type', 'summary', 'proof']; + protected $fillable = ["title", "requester_id", "type", "summary", "proof"]; protected $fillable2 = [ - 'title', - 'description', - 'requester_id', - 'type', - 'summary', - 'proof', + "title", + "description", + "requester_id", + "type", + "summary", + "proof", ]; protected $test = [ //test @@ -52,7 +52,7 @@ abstract class ReallyReallyReallyLongClassName * * @return \Some\Test */ - public function __construct($test, $test_int = null, $test_string = 'hi') + public function __construct($test, $test_int = null, $test_string = "hi") { parent::__construct($test_int ?: 1); $this->other = $test_string; @@ -108,7 +108,7 @@ abstract class ReallyReallyReallyLongClassName public function returnTypeTest(): string { - return 'hi'; + return "hi"; } final public static function bar() @@ -125,17 +125,17 @@ abstract class ReallyReallyReallyLongClassName public function method1() { - return 'hi'; + return "hi"; } public function method2() { - return 'hi'; + return "hi"; } public function method3() { - return 'hi'; + return "hi"; } public function testReturn(?string $name): ?string @@ -164,7 +164,7 @@ abstract class ReallyReallyReallyLongClassName string $bar, int $baz ): string { - return 'foo'; + return "foo"; } public function longLongAnotherFunctionOther( @@ -172,7 +172,7 @@ abstract class ReallyReallyReallyLongClassName string $bar, int $baz ) { - return 'foo'; + return "foo"; } public function testReturnTypeDeclaration(): object @@ -297,13 +297,13 @@ class field extends \models\base { protected function pre_save($input, $fields) { - $input['configs'] = json_encode( + $input["configs"] = json_encode( array_merge( $configs, - $field_type->process_field_config_from_user($input['definition']) + $field_type->process_field_config_from_user($input["definition"]) ) ); - unset($input['definition']); + unset($input["definition"]); } } @@ -311,8 +311,8 @@ class test { public function test_method() { - $customer = (object) ['name' => 'Bob']; - $job = (object) ['customer' => $customer]; + $customer = (object) ["name" => "Bob"]; + $job = (object) ["customer" => $customer]; return "The customer for that job, {$job->customer->name} has an error that shows up after the line gets waaaaay toooo long."; } @@ -488,9 +488,9 @@ class User { public int $id; public string $name; - public ?string $b = 'foo'; + public ?string $b = "foo"; private Foo $prop; - protected static string $static = 'default'; + protected static string $static = "default"; public function __construct(int $id, string $name) { diff --git a/testlib/src/main/resources/protobuf/buf/buf.proto b/testlib/src/main/resources/protobuf/buf/buf.proto new file mode 100644 index 0000000000..a90d58bbd0 --- /dev/null +++ b/testlib/src/main/resources/protobuf/buf/buf.proto @@ -0,0 +1,5 @@ +message Testing { + required string field1 = 1; + required int32 field2 = 2; + optional string field3 = 3; +} \ No newline at end of file diff --git a/testlib/src/main/resources/protobuf/buf/buf.proto.clean b/testlib/src/main/resources/protobuf/buf/buf.proto.clean new file mode 100644 index 0000000000..faa8d91f24 --- /dev/null +++ b/testlib/src/main/resources/protobuf/buf/buf.proto.clean @@ -0,0 +1,5 @@ +message Testing { + required string field1 = 1; + required int32 field2 = 2; + optional string field3 = 3; +} diff --git a/testlib/src/main/resources/protobuf/buf/license.proto b/testlib/src/main/resources/protobuf/buf/license.proto new file mode 100644 index 0000000000..aabedc5093 --- /dev/null +++ b/testlib/src/main/resources/protobuf/buf/license.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +message Testing { + required string field1 = 1; + required int32 field2 = 2; + optional string field3 = 3; +} diff --git a/testlib/src/main/resources/protobuf/buf/license.proto.clean b/testlib/src/main/resources/protobuf/buf/license.proto.clean new file mode 100644 index 0000000000..11761efc28 --- /dev/null +++ b/testlib/src/main/resources/protobuf/buf/license.proto.clean @@ -0,0 +1,8 @@ +/* (C) 2022 */ +syntax = "proto3"; + +message Testing { + required string field1 = 1; + required int32 field2 = 2; + optional string field3 = 3; +} diff --git a/testlib/src/main/resources/rome/config/line-width-120.json b/testlib/src/main/resources/rome/config/line-width-120.json new file mode 100644 index 0000000000..8f14afa3f8 --- /dev/null +++ b/testlib/src/main/resources/rome/config/line-width-120.json @@ -0,0 +1,11 @@ +{ + "formatter": { + "enabled": true, + "indentStyle": "tab", + "lineWidth": 120, + "formatWithErrors": false + }, + "linter": { + "enabled": false + } + } \ No newline at end of file diff --git a/testlib/src/main/resources/rome/config/line-width-80.json b/testlib/src/main/resources/rome/config/line-width-80.json new file mode 100644 index 0000000000..5ec998bd97 --- /dev/null +++ b/testlib/src/main/resources/rome/config/line-width-80.json @@ -0,0 +1,11 @@ +{ + "formatter": { + "enabled": true, + "indentStyle": "tab", + "lineWidth": 80, + "formatWithErrors": false + }, + "linter": { + "enabled": false + } + } \ No newline at end of file diff --git a/testlib/src/main/resources/rome/js/fileAfter.cjs b/testlib/src/main/resources/rome/js/fileAfter.cjs new file mode 100644 index 0000000000..defc9c85eb --- /dev/null +++ b/testlib/src/main/resources/rome/js/fileAfter.cjs @@ -0,0 +1,3 @@ +function foo(name = "World") { + return "Hello " + name; +} diff --git a/testlib/src/main/resources/rome/js/fileAfter.js b/testlib/src/main/resources/rome/js/fileAfter.js new file mode 100644 index 0000000000..defc9c85eb --- /dev/null +++ b/testlib/src/main/resources/rome/js/fileAfter.js @@ -0,0 +1,3 @@ +function foo(name = "World") { + return "Hello " + name; +} diff --git a/testlib/src/main/resources/rome/js/fileAfter.jsx b/testlib/src/main/resources/rome/js/fileAfter.jsx new file mode 100644 index 0000000000..313aceb6ed --- /dev/null +++ b/testlib/src/main/resources/rome/js/fileAfter.jsx @@ -0,0 +1,3 @@ +export function Panel(cfg = {}) { + return
{1 + 2}
; +} diff --git a/testlib/src/main/resources/rome/js/fileAfter.mjs b/testlib/src/main/resources/rome/js/fileAfter.mjs new file mode 100644 index 0000000000..defc9c85eb --- /dev/null +++ b/testlib/src/main/resources/rome/js/fileAfter.mjs @@ -0,0 +1,3 @@ +function foo(name = "World") { + return "Hello " + name; +} diff --git a/testlib/src/main/resources/rome/js/fileBefore.cjs b/testlib/src/main/resources/rome/js/fileBefore.cjs new file mode 100644 index 0000000000..92539ba751 --- /dev/null +++ b/testlib/src/main/resources/rome/js/fileBefore.cjs @@ -0,0 +1,3 @@ +function foo ( name="World"){ + return "Hello "+name ; + } \ No newline at end of file diff --git a/testlib/src/main/resources/rome/js/fileBefore.js b/testlib/src/main/resources/rome/js/fileBefore.js new file mode 100644 index 0000000000..92539ba751 --- /dev/null +++ b/testlib/src/main/resources/rome/js/fileBefore.js @@ -0,0 +1,3 @@ +function foo ( name="World"){ + return "Hello "+name ; + } \ No newline at end of file diff --git a/testlib/src/main/resources/rome/js/fileBefore.jsx b/testlib/src/main/resources/rome/js/fileBefore.jsx new file mode 100644 index 0000000000..8e5d9834bc --- /dev/null +++ b/testlib/src/main/resources/rome/js/fileBefore.jsx @@ -0,0 +1,4 @@ +export function Panel ( cfg={}){ + return (
{1+2}
+ ) ; + } \ No newline at end of file diff --git a/testlib/src/main/resources/rome/js/fileBefore.mjs b/testlib/src/main/resources/rome/js/fileBefore.mjs new file mode 100644 index 0000000000..92539ba751 --- /dev/null +++ b/testlib/src/main/resources/rome/js/fileBefore.mjs @@ -0,0 +1,3 @@ +function foo ( name="World"){ + return "Hello "+name ; + } \ No newline at end of file diff --git a/testlib/src/main/resources/rome/js/longLineAfter120.js b/testlib/src/main/resources/rome/js/longLineAfter120.js new file mode 100644 index 0000000000..68addc4b65 --- /dev/null +++ b/testlib/src/main/resources/rome/js/longLineAfter120.js @@ -0,0 +1 @@ +const x = ["Hello", "World", "How", "Are", "You", "Doing", "Today", "Such", "A", "Wondrous", "Sunshine"]; diff --git a/testlib/src/main/resources/rome/js/longLineAfter80.js b/testlib/src/main/resources/rome/js/longLineAfter80.js new file mode 100644 index 0000000000..dbdbd157e9 --- /dev/null +++ b/testlib/src/main/resources/rome/js/longLineAfter80.js @@ -0,0 +1,13 @@ +const x = [ + "Hello", + "World", + "How", + "Are", + "You", + "Doing", + "Today", + "Such", + "A", + "Wondrous", + "Sunshine", +]; diff --git a/testlib/src/main/resources/rome/js/longLineBefore.js b/testlib/src/main/resources/rome/js/longLineBefore.js new file mode 100644 index 0000000000..fd59e429c2 --- /dev/null +++ b/testlib/src/main/resources/rome/js/longLineBefore.js @@ -0,0 +1 @@ +const x = ["Hello", "World", "How", "Are", "You", "Doing", "Today", "Such", "A", "Wondrous", "Sunshine"]; \ No newline at end of file diff --git a/testlib/src/main/resources/rome/json/fileAfter.json b/testlib/src/main/resources/rome/json/fileAfter.json new file mode 100644 index 0000000000..468dac3297 --- /dev/null +++ b/testlib/src/main/resources/rome/json/fileAfter.json @@ -0,0 +1,5 @@ +{ + "a": [1, 2, 3], + "b": 9, + "c": null +} diff --git a/testlib/src/main/resources/rome/json/fileBefore.json b/testlib/src/main/resources/rome/json/fileBefore.json new file mode 100644 index 0000000000..77182284c7 --- /dev/null +++ b/testlib/src/main/resources/rome/json/fileBefore.json @@ -0,0 +1,7 @@ + { + "a":[1,2,3 + +], + "b":9, + "c" : null + } \ No newline at end of file diff --git a/testlib/src/main/resources/rome/ts/fileAfter.cts b/testlib/src/main/resources/rome/ts/fileAfter.cts new file mode 100644 index 0000000000..f854953234 --- /dev/null +++ b/testlib/src/main/resources/rome/ts/fileAfter.cts @@ -0,0 +1,4 @@ +type Name = "World" | "Maven" | "Gradle"; +const foo = (name: Name = "World", v: T): string => { + return "Hello " + name; +}; diff --git a/testlib/src/main/resources/rome/ts/fileAfter.mts b/testlib/src/main/resources/rome/ts/fileAfter.mts new file mode 100644 index 0000000000..e6563e3030 --- /dev/null +++ b/testlib/src/main/resources/rome/ts/fileAfter.mts @@ -0,0 +1,4 @@ +export type Name = "World" | "Maven" | "Gradle"; +export const foo = (name: Name = "World", v: T): string => { + return "Hello " + name; +}; diff --git a/testlib/src/main/resources/rome/ts/fileAfter.ts b/testlib/src/main/resources/rome/ts/fileAfter.ts new file mode 100644 index 0000000000..e6563e3030 --- /dev/null +++ b/testlib/src/main/resources/rome/ts/fileAfter.ts @@ -0,0 +1,4 @@ +export type Name = "World" | "Maven" | "Gradle"; +export const foo = (name: Name = "World", v: T): string => { + return "Hello " + name; +}; diff --git a/testlib/src/main/resources/rome/ts/fileAfter.tsx b/testlib/src/main/resources/rome/ts/fileAfter.tsx new file mode 100644 index 0000000000..15ef316142 --- /dev/null +++ b/testlib/src/main/resources/rome/ts/fileAfter.tsx @@ -0,0 +1,7 @@ +export interface Cfg { + classname: string; + message: T; +} +const Panel = (cfg: Cfg): JSX.Element => { + return
{String(cfg.message)}
; +}; diff --git a/testlib/src/main/resources/rome/ts/fileBefore.cts b/testlib/src/main/resources/rome/ts/fileBefore.cts new file mode 100644 index 0000000000..d4304287c0 --- /dev/null +++ b/testlib/src/main/resources/rome/ts/fileBefore.cts @@ -0,0 +1,4 @@ +type Name = "World" | "Maven"|"Gradle"; +const foo = ( name: Name="World", v: T): string => { + return "Hello " + name; + } \ No newline at end of file diff --git a/testlib/src/main/resources/rome/ts/fileBefore.mts b/testlib/src/main/resources/rome/ts/fileBefore.mts new file mode 100644 index 0000000000..96837762a3 --- /dev/null +++ b/testlib/src/main/resources/rome/ts/fileBefore.mts @@ -0,0 +1,5 @@ +export + type Name = "World" | "Maven"|"Gradle"; +export const foo = ( name: Name="World", v: T): string => { + return "Hello " + name; + } \ No newline at end of file diff --git a/testlib/src/main/resources/rome/ts/fileBefore.ts b/testlib/src/main/resources/rome/ts/fileBefore.ts new file mode 100644 index 0000000000..96837762a3 --- /dev/null +++ b/testlib/src/main/resources/rome/ts/fileBefore.ts @@ -0,0 +1,5 @@ +export + type Name = "World" | "Maven"|"Gradle"; +export const foo = ( name: Name="World", v: T): string => { + return "Hello " + name; + } \ No newline at end of file diff --git a/testlib/src/main/resources/rome/ts/fileBefore.tsx b/testlib/src/main/resources/rome/ts/fileBefore.tsx new file mode 100644 index 0000000000..38f24f8440 --- /dev/null +++ b/testlib/src/main/resources/rome/ts/fileBefore.tsx @@ -0,0 +1,8 @@ +export interface Cfg{ +classname:string, +message:T, +} +const Panel = ( cfg:Cfg):JSX.Element =>{ + return (
{String(cfg.message)}
+ ) ; + } \ No newline at end of file diff --git a/testlib/src/main/resources/scala/scalafmt/scalafmt.conf b/testlib/src/main/resources/scala/scalafmt/scalafmt.conf index bcfec161f4..edea864af1 100644 --- a/testlib/src/main/resources/scala/scalafmt/scalafmt.conf +++ b/testlib/src/main/resources/scala/scalafmt/scalafmt.conf @@ -1,4 +1,4 @@ -version = 3.5.9 +version = 3.7.3 runner.dialect = scala213 style = defaultWithAlign # For pretty alignment. maxColumn = 20 # For my teensy narrow display diff --git a/testlib/src/main/resources/shell/shfmt/shfmt.clean b/testlib/src/main/resources/shell/shfmt/shfmt.clean new file mode 100644 index 0000000000..c1b9b25064 --- /dev/null +++ b/testlib/src/main/resources/shell/shfmt/shfmt.clean @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +function foo() { + if [ -x $file ]; then + myArray=(item1 item2 item3) + elif [ $file1 -nt $file2 ]; then + unset myArray + else + echo "Usage: $0 file ..." + fi +} + +for ((i = 0; i < 5; i++)); do + read -p r + print -n $r + wait $! +done diff --git a/testlib/src/main/resources/shell/shfmt/shfmt.sh b/testlib/src/main/resources/shell/shfmt/shfmt.sh new file mode 100644 index 0000000000..9d15c477d4 --- /dev/null +++ b/testlib/src/main/resources/shell/shfmt/shfmt.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash + +function foo() { + if [ -x $file ]; then + myArray=(item1 item2 item3) + elif [ $file1 -nt $file2 ] + then + unset myArray + else +echo "Usage: $0 file ..." + fi +} + +for ((i = 0; i < 5; i++)); do + read -p r + print -n $r + wait $! +done diff --git a/testlib/src/main/resources/yaml/array_at_root.clean.yaml b/testlib/src/main/resources/yaml/array_at_root.clean.yaml new file mode 100644 index 0000000000..f9bae04114 --- /dev/null +++ b/testlib/src/main/resources/yaml/array_at_root.clean.yaml @@ -0,0 +1,5 @@ +--- +- key1: "value1" + key2: "value2" +- key3: "value3" + key4: "value4" diff --git a/testlib/src/main/resources/yaml/array_at_root.yaml b/testlib/src/main/resources/yaml/array_at_root.yaml new file mode 100644 index 0000000000..b26f6bb246 --- /dev/null +++ b/testlib/src/main/resources/yaml/array_at_root.yaml @@ -0,0 +1,7 @@ +- key1: value1 + key2: value2 + + +- key3: value3 + key4: value4 + diff --git a/testlib/src/main/resources/yaml/array_with_bracket.clean.no_start_marker.no_quotes.yaml b/testlib/src/main/resources/yaml/array_with_bracket.clean.no_start_marker.no_quotes.yaml new file mode 100644 index 0000000000..0e5571531a --- /dev/null +++ b/testlib/src/main/resources/yaml/array_with_bracket.clean.no_start_marker.no_quotes.yaml @@ -0,0 +1,11 @@ +episodes: +- 1 +- 2 +- 3 +- 4 +- 5 +- 6 +- 7 +best-jedi: + name: Obi-Wan + side: light diff --git a/testlib/src/main/resources/yaml/array_with_bracket.clean.yaml b/testlib/src/main/resources/yaml/array_with_bracket.clean.yaml new file mode 100644 index 0000000000..c6f891b9de --- /dev/null +++ b/testlib/src/main/resources/yaml/array_with_bracket.clean.yaml @@ -0,0 +1,12 @@ +--- +episodes: +- 1 +- 2 +- 3 +- 4 +- 5 +- 6 +- 7 +best-jedi: + name: "Obi-Wan" + side: "light" \ No newline at end of file diff --git a/testlib/src/main/resources/yaml/array_with_bracket.yaml b/testlib/src/main/resources/yaml/array_with_bracket.yaml new file mode 100644 index 0000000000..e28373163d --- /dev/null +++ b/testlib/src/main/resources/yaml/array_with_bracket.yaml @@ -0,0 +1,5 @@ +episodes: [1, 2, 3, 4, 5, 6, 7] + + + +best-jedi: {name: Obi-Wan, side: light} \ No newline at end of file diff --git a/testlib/src/main/resources/yaml/multiple_documents.clean.jackson.yaml b/testlib/src/main/resources/yaml/multiple_documents.clean.jackson.yaml new file mode 100644 index 0000000000..ff56b4f77a --- /dev/null +++ b/testlib/src/main/resources/yaml/multiple_documents.clean.jackson.yaml @@ -0,0 +1,6 @@ +--- +key1: "value1" +key2: "value2" +--- +key3: "value3" +key4: "value4" diff --git a/testlib/src/main/resources/yaml/multiple_documents.clean.yaml b/testlib/src/main/resources/yaml/multiple_documents.clean.yaml new file mode 100644 index 0000000000..3651473955 --- /dev/null +++ b/testlib/src/main/resources/yaml/multiple_documents.clean.yaml @@ -0,0 +1,8 @@ +# Document 1 +key1: value1 +key2: value2 + +--- +# Document 2 +key3: value3 +key4: value4 diff --git a/testlib/src/main/resources/yaml/multiple_documents.yaml b/testlib/src/main/resources/yaml/multiple_documents.yaml new file mode 100644 index 0000000000..3651473955 --- /dev/null +++ b/testlib/src/main/resources/yaml/multiple_documents.yaml @@ -0,0 +1,8 @@ +# Document 1 +key1: value1 +key2: value2 + +--- +# Document 2 +key3: value3 +key4: value4 diff --git a/testlib/src/main/resources/yaml/separator_comments.clean.yaml b/testlib/src/main/resources/yaml/separator_comments.clean.yaml new file mode 100644 index 0000000000..35bb8717dd --- /dev/null +++ b/testlib/src/main/resources/yaml/separator_comments.clean.yaml @@ -0,0 +1,7 @@ +--- +hr: +- "Mark McGwire" +- "Sammy Sosa" +rbi: +- "SS" +- "Ken Griffey" \ No newline at end of file diff --git a/testlib/src/main/resources/yaml/separator_comments.yaml b/testlib/src/main/resources/yaml/separator_comments.yaml new file mode 100644 index 0000000000..0213e66406 --- /dev/null +++ b/testlib/src/main/resources/yaml/separator_comments.yaml @@ -0,0 +1,9 @@ + +--- +hr: + - Mark McGwire + # Following node labeled SS + - &SS Sammy Sosa +rbi: + - *SS # Subsequent occurrence + - Ken Griffey \ No newline at end of file diff --git a/testlib/src/test/java/com/diffplug/spotless/FormatterTest.java b/testlib/src/test/java/com/diffplug/spotless/FormatterTest.java index 06b8e64d31..314507bd8d 100644 --- a/testlib/src/test/java/com/diffplug/spotless/FormatterTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/FormatterTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,20 @@ */ package com.diffplug.spotless; +import java.io.File; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import com.diffplug.common.base.StandardSystemProperty; import com.diffplug.spotless.generic.EndWithNewlineStep; @@ -89,4 +94,95 @@ protected Formatter create() { } }.testEquals(); } + + // new File("") as filePath is known to fail + @Test + public void testExceptionWithEmptyPath() throws Exception { + LineEnding.Policy lineEndingsPolicy = LineEnding.UNIX.createPolicy(); + Charset encoding = StandardCharsets.UTF_8; + FormatExceptionPolicy exceptionPolicy = FormatExceptionPolicy.failOnlyOnError(); + + Path rootDir = Paths.get(StandardSystemProperty.USER_DIR.value()); + + FormatterStep step = Mockito.mock(FormatterStep.class); + Mockito.when(step.getName()).thenReturn("someFailingStep"); + Mockito.when(step.format(Mockito.anyString(), Mockito.any(File.class))).thenThrow(new IllegalArgumentException("someReason")); + List steps = Collections.singletonList(step); + + Formatter formatter = Formatter.builder() + .lineEndingsPolicy(lineEndingsPolicy) + .encoding(encoding) + .rootDir(rootDir) + .steps(steps) + .exceptionPolicy(exceptionPolicy) + .build(); + + Assertions.assertThrows(IllegalArgumentException.class, () -> formatter.compute("someFileContent", new File(""))); + } + + // If there is no File actually holding the content, one may rely on Formatter.NO_FILE_ON_DISK + @Test + public void testExceptionWithSentinelNoFileOnDisk() throws Exception { + LineEnding.Policy lineEndingsPolicy = LineEnding.UNIX.createPolicy(); + Charset encoding = StandardCharsets.UTF_8; + FormatExceptionPolicy exceptionPolicy = FormatExceptionPolicy.failOnlyOnError(); + + Path rootDir = Paths.get(StandardSystemProperty.USER_DIR.value()); + + FormatterStep step = Mockito.mock(FormatterStep.class); + Mockito.when(step.getName()).thenReturn("someFailingStep"); + Mockito.when(step.format(Mockito.anyString(), Mockito.any(File.class))).thenThrow(new IllegalArgumentException("someReason")); + List steps = Collections.singletonList(step); + + Formatter formatter = Formatter.builder() + .lineEndingsPolicy(lineEndingsPolicy) + .encoding(encoding) + .rootDir(rootDir) + .steps(steps) + .exceptionPolicy(exceptionPolicy) + .build(); + + formatter.compute("someFileContent", Formatter.NO_FILE_SENTINEL); + } + + // rootDir may be a path not from the default FileSystem + @Test + public void testExceptionWithRootDirIsNotFileSystem() throws Exception { + LineEnding.Policy lineEndingsPolicy = LineEnding.UNIX.createPolicy(); + Charset encoding = StandardCharsets.UTF_8; + FormatExceptionPolicy exceptionPolicy = FormatExceptionPolicy.failOnlyOnError(); + + Path rootDir = Mockito.mock(Path.class); + FileSystem customFileSystem = Mockito.mock(FileSystem.class); + Mockito.when(rootDir.getFileSystem()).thenReturn(customFileSystem); + + Path pathFromFile = Mockito.mock(Path.class); + Mockito.when(customFileSystem.getPath(Mockito.anyString())).thenReturn(pathFromFile); + + Path relativized = Mockito.mock(Path.class); + Mockito.when(rootDir.relativize(Mockito.any(Path.class))).then(invok -> { + Path filePath = invok.getArgument(0); + if (filePath.getFileSystem() == FileSystems.getDefault()) { + throw new IllegalArgumentException("Can not relativize through different FileSystems"); + } + + return relativized; + }); + + FormatterStep step = Mockito.mock(FormatterStep.class); + Mockito.when(step.getName()).thenReturn("someFailingStep"); + Mockito.when(step.format(Mockito.anyString(), Mockito.any(File.class))).thenThrow(new IllegalArgumentException("someReason")); + List steps = Collections.singletonList(step); + + Formatter formatter = Formatter.builder() + .lineEndingsPolicy(lineEndingsPolicy) + .encoding(encoding) + .rootDir(rootDir) + .steps(steps) + .exceptionPolicy(exceptionPolicy) + .build(); + + formatter.compute("someFileContent", new File("/some/folder/some.file")); + } + } diff --git a/testlib/src/test/java/com/diffplug/spotless/JvmTest.java b/testlib/src/test/java/com/diffplug/spotless/JvmTest.java index 2bb374d0ed..1c966c51f4 100644 --- a/testlib/src/test/java/com/diffplug/spotless/JvmTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/JvmTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 DiffPlug + * Copyright 2021-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -93,7 +93,7 @@ void supportListsMinimumJvmIfOnlyHigherJvmSupported() { assertNull(testSupport.getRecommendedFormatterVersion(), "No formatter version is supported"); - for (String fmtVersion : Arrays.asList("1.2", "1.2.3")) { + for (String fmtVersion : Arrays.asList("1.2", "1.2.3", "1.2-SNAPSHOT", "1.2.3-SNAPSHOT")) { String proposal = assertThrows(Exception.class, () -> { testSupport.assertFormatterSupported(fmtVersion); }).getMessage(); @@ -111,7 +111,7 @@ void supportListsMinimumJvmIfOnlyHigherJvmSupported() { assertThat(proposal).contains(String.format("try %s alternatives", TEST_NAME)); } - for (String fmtVersion : Arrays.asList("1.2.4", "2")) { + for (String fmtVersion : Arrays.asList("1.2.4", "2", "1.2.5-SNAPSHOT")) { String proposal = assertThrows(Exception.class, () -> { testSupport.assertFormatterSupported(fmtVersion); }).getMessage(); @@ -132,7 +132,7 @@ void supportProposesFormatterUpgrade() { testSupport.add(Jvm.version() - 2, "1"); testSupport.add(requiredJvm, "2"); testSupport.add(Jvm.version() + 1, "3"); - for (String fmtVersion : Arrays.asList("0", "1", "1.9")) { + for (String fmtVersion : Arrays.asList("0", "1", "1.9", "1.9-SNAPSHOT")) { testSupport.assertFormatterSupported(fmtVersion); String proposal = assertThrows(Exception.class, () -> { @@ -140,9 +140,9 @@ void supportProposesFormatterUpgrade() { throw new Exception("Some test exception"); }).apply(""); }).getMessage(); - assertThat(proposal).contains("not using latest version"); - assertThat(proposal).contains(String.format("on JVM %d+", requiredJvm)); - assertThat(proposal).contains(String.format("upgrade to %s %s", TEST_NAME, "2")); + assertThat(proposal.replace("\r", "")).isEqualTo("My Test Formatter " + fmtVersion + " is currently being used, but outdated.\n" + + "My Test Formatter 2 is the recommended version, which may have fixed this problem.\n" + + "My Test Formatter 2 requires JVM " + (requiredJvm) + "+."); } } @@ -168,7 +168,7 @@ void supportProposesJvmUpgrade() { @Test void supportAllowsExperimentalVersions() { testSupport.add(Jvm.version(), "1.0"); - for (String fmtVersion : Arrays.asList("1", "2.0")) { + for (String fmtVersion : Arrays.asList("1", "2.0", "1.1-SNAPSHOT", "2.0-SNAPSHOT")) { testSupport.assertFormatterSupported(fmtVersion); Exception testException = new Exception("Some test exception"); diff --git a/testlib/src/test/java/com/diffplug/spotless/antlr4/Antlr4FormatterStepTest.java b/testlib/src/test/java/com/diffplug/spotless/antlr4/Antlr4FormatterStepTest.java index a26cbd4425..0f5b1b0bb7 100644 --- a/testlib/src/test/java/com/diffplug/spotless/antlr4/Antlr4FormatterStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/antlr4/Antlr4FormatterStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2021 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,15 +18,13 @@ import org.junit.jupiter.api.Test; import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.ResourceHarness; +import com.diffplug.spotless.StepHarness; import com.diffplug.spotless.TestProvisioner; -class Antlr4FormatterStepTest extends ResourceHarness { - +class Antlr4FormatterStepTest { @Test void formatGrammar() throws Throwable { FormatterStep step = Antlr4FormatterStep.create(TestProvisioner.mavenCentral()); - assertOnResources(step, "antlr4/Hello.unformatted.g4", "antlr4/Hello.formatted.g4"); + StepHarness.forStep(step).testResource("antlr4/Hello.unformatted.g4", "antlr4/Hello.formatted.g4"); } - } diff --git a/testlib/src/test/java/com/diffplug/spotless/combined/CombinedJavaFormatStepTest.java b/testlib/src/test/java/com/diffplug/spotless/combined/CombinedJavaFormatStepTest.java new file mode 100644 index 0000000000..d1eb023400 --- /dev/null +++ b/testlib/src/test/java/com/diffplug/spotless/combined/CombinedJavaFormatStepTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 2023 DiffPlug + * + * 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 + * + * http://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 com.diffplug.spotless.combined; + +import static com.diffplug.spotless.TestProvisioner.mavenCentral; + +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.ResourceHarness; +import com.diffplug.spotless.StepHarness; +import com.diffplug.spotless.generic.EndWithNewlineStep; +import com.diffplug.spotless.generic.IndentStep; +import com.diffplug.spotless.generic.PipeStepPair; +import com.diffplug.spotless.generic.TrimTrailingWhitespaceStep; +import com.diffplug.spotless.java.GoogleJavaFormatStep; +import com.diffplug.spotless.java.ImportOrderStep; +import com.diffplug.spotless.java.RemoveUnusedImportsStep; + +public class CombinedJavaFormatStepTest extends ResourceHarness { + + @Test + void checkIssue1679() { + FormatterStep gjf = GoogleJavaFormatStep.create(GoogleJavaFormatStep.defaultVersion(), "AOSP", mavenCentral()); + FormatterStep indentWithSpaces = IndentStep.Type.SPACE.create(); + FormatterStep importOrder = ImportOrderStep.forJava().createFrom(); + FormatterStep removeUnused = RemoveUnusedImportsStep.create(mavenCentral()); + FormatterStep trimTrailing = TrimTrailingWhitespaceStep.create(); + FormatterStep endWithNewLine = EndWithNewlineStep.create(); + PipeStepPair toggleOffOnPair = PipeStepPair.named(PipeStepPair.defaultToggleName()).openClose("formatting:off", "formatting:on").buildPair(); + try (StepHarness formatter = StepHarness.forSteps( + toggleOffOnPair.in(), + gjf, + indentWithSpaces, + importOrder, + removeUnused, + trimTrailing, + endWithNewLine, + toggleOffOnPair.out())) { + formatter.testResource("combined/issue1679.dirty", "combined/issue1679.clean"); + } + } +} diff --git a/testlib/src/test/java/com/diffplug/spotless/cpp/ClangFormatStepTest.java b/testlib/src/test/java/com/diffplug/spotless/cpp/ClangFormatStepTest.java index cf0f551485..7e76b4eea1 100644 --- a/testlib/src/test/java/com/diffplug/spotless/cpp/ClangFormatStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/cpp/ClangFormatStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2021 DiffPlug + * Copyright 2020-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,26 +15,26 @@ */ package com.diffplug.spotless.cpp; -import java.io.File; import java.util.Arrays; import org.junit.jupiter.api.Test; +import com.diffplug.spotless.ResourceHarness; import com.diffplug.spotless.StepHarnessWithFile; import com.diffplug.spotless.tag.ClangTest; @ClangTest -class ClangFormatStepTest { +class ClangFormatStepTest extends ResourceHarness { @Test - void test() throws Exception { - try (StepHarnessWithFile harness = StepHarnessWithFile.forStep(ClangFormatStep.withVersion(ClangFormatStep.defaultVersion()).create())) { + void test() { + try (StepHarnessWithFile harness = StepHarnessWithFile.forStep(this, ClangFormatStep.withVersion(ClangFormatStep.defaultVersion()).create())) { // can't be named java or it gets compiled into .class file - harness.testResource(new File("example.java"), "clang/example.java.dirty", "clang/example.java.clean"); + harness.testResource("example.java", "clang/example.java.dirty", "clang/example.java.clean"); // test every other language clang supports for (String ext : Arrays.asList("c", "cs", "js", "m", "proto")) { String filename = "example." + ext; String root = "clang/" + filename; - harness.testResource(new File(filename), root, root + ".clean"); + harness.testResource(filename, root, root + ".clean"); } } } diff --git a/testlib/src/test/java/com/diffplug/spotless/generic/IndentStepTest.java b/testlib/src/test/java/com/diffplug/spotless/generic/IndentStepTest.java index c7df4639e3..38503d7182 100644 --- a/testlib/src/test/java/com/diffplug/spotless/generic/IndentStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/generic/IndentStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,46 +15,42 @@ */ package com.diffplug.spotless.generic; -import java.io.File; - -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.ResourceHarness; import com.diffplug.spotless.SerializableEqualityTester; import com.diffplug.spotless.StepHarness; -class IndentStepTest extends ResourceHarness { +class IndentStepTest { @Test - void tabToTab() throws Throwable { + void tabToTab() { FormatterStep indent = IndentStep.Type.TAB.create(4); - assertOnResources(indent, "indent/IndentedWithTab.test", "indent/IndentedWithTab.test"); + StepHarness.forStep(indent).testResource("indent/IndentedWithTab.test", "indent/IndentedWithTab.test"); } @Test - void spaceToSpace() throws Throwable { + void spaceToSpace() { FormatterStep indent = IndentStep.Type.SPACE.create(4); - assertOnResources(indent, "indent/IndentedWithSpace.test", "indent/IndentedWithSpace.test"); + StepHarness.forStep(indent).testResource("indent/IndentedWithSpace.test", "indent/IndentedWithSpace.test"); } @Test - void spaceToTab() throws Throwable { + void spaceToTab() { FormatterStep indent = IndentStep.Type.TAB.create(4); - assertOnResources(indent, "indent/IndentedWithSpace.test", "indent/IndentedWithTab.test"); + StepHarness.forStep(indent).testResource("indent/IndentedWithSpace.test", "indent/IndentedWithTab.test"); } @Test - void tabToSpace() throws Throwable { + void tabToSpace() { FormatterStep indent = IndentStep.Type.SPACE.create(4); - assertOnResources(indent, "indent/IndentedWithTab.test", "indent/IndentedWithSpace.test"); + StepHarness.forStep(indent).testResource("indent/IndentedWithTab.test", "indent/IndentedWithSpace.test"); } @Test - void doesntClipNewlines() throws Throwable { + void doesntClipNewlines() { FormatterStep indent = IndentStep.Type.SPACE.create(4); String blankNewlines = "\n\n\n\n"; - Assertions.assertEquals(blankNewlines, indent.format(blankNewlines, new File(""))); + StepHarness.forStep(indent).testUnaffected(blankNewlines); } @Test diff --git a/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java b/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java index 137937beb9..92c6794643 100644 --- a/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,13 +28,16 @@ import com.diffplug.spotless.ResourceHarness; import com.diffplug.spotless.SerializableEqualityTester; import com.diffplug.spotless.StepHarness; +import com.diffplug.spotless.StepHarnessWithFile; import com.diffplug.spotless.generic.LicenseHeaderStep.YearMode; class LicenseHeaderStepTest extends ResourceHarness { private static final String FILE_NO_LICENSE = "license/FileWithoutLicenseHeader.test"; - private static final String package_ = "package "; + private static final String package_ = LicenseHeaderStep.DEFAULT_JAVA_HEADER_DELIMITER; private static final String HEADER_WITH_$YEAR = "This is a fake license, $YEAR. ACME corp."; private static final String HEADER_WITH_RANGE_TO_$YEAR = "This is a fake license with range, 2009-$YEAR. ACME corp."; + private static final String HEADER_WITH_$FILE = "This is a fake license, $FILE. ACME corp."; + private static final String HEADER_WITH_$YEAR_$FILE = "This is a fake license, $FILE, $YEAR. ACME corp."; @Test void parseExistingYear() throws Exception { @@ -122,6 +125,13 @@ void should_remove_header_when_empty() throws Throwable { .test(getTestResource("license/HasLicense.test"), getTestResource("license/MissingLicense.test")); } + @Test + void should_skip_lines_matching_predefined_pattern() throws Throwable { + StepHarness.forStep(LicenseHeaderStep.headerDelimiter("", "^(?!