From 37d999caa96e1e8506ab69640002e6c2898fe3c9 Mon Sep 17 00:00:00 2001 From: Matthias Kurz Date: Thu, 1 Oct 2026 21:16:43 +0200 Subject: [PATCH] Support google-java-format 1.37.0 and configuration-aware caching Select formatter 1.37.0 for Java 21-compatible execution and expose javafmtMaxLineLength with compatibility validation and documentation. Invalidate caches when the effective invocation changes, including CLI options, formatter classpath, heap settings, and resolved Java-home targets. Separate import-only and full-format caches, ignore irrelevant formatting options for import-only tasks, and defer launcher validation until execution. Keep shared formatter arguments mandatory so full-format callers cannot silently ignore user settings. Add scripted coverage for line lengths, configuration and runtime changes, symlink retargeting, no-source builds, import-only isolation, and version guards across sbt 1 and 2. --- .github/workflows/ci.yml | 8 +- README.md | 29 ++- .../com/github/sbt/JavaFormatterPlugin.scala | 51 ++++-- .../sbt/javaformatter/JavaFormatter.scala | 171 ++++++++++++------ .../cache-runtime-on-java-11/build.sbt | 1 + .../project/plugins.sbt | 1 + .../src/main/java/com/lightbend/Example.java | 3 + .../cache-runtime-on-java-11/test | 11 ++ .../java-runtime/compatible-17/build.sbt | 17 ++ .../sbt-test/java-runtime/compatible-17/test | 19 ++ .../incompatible-21-on-java-17/test | 9 + .../java-runtime/max-line-length/build.sbt | 1 + .../max-line-length/project/plugins.sbt | 1 + .../com/lightbend/LineLength.java | 19 ++ .../com/lightbend/LineLength.java | 20 ++ .../com/lightbend/LineLength.java | 21 +++ .../com/lightbend/LineLength.java | 20 ++ .../com/lightbend/LineLength.java | 19 ++ .../com/lightbend/LineLength.java | 20 ++ .../com/lightbend/LineLength.java | 8 + .../main/java/com/lightbend/LineLength.java | 8 + .../com/lightbend/LineLengthTest.java | 7 + .../java/com/lightbend/LineLengthTest.java | 5 + .../java-runtime/max-line-length/test | 55 ++++++ .../com/lightbend/BadFormatting.java | 5 + .../java-runtime/reorder-modifiers/test | 8 + .../com/lightbend/BadFormatting.java | 9 + .../com/lightbend/BadFormatting.java | 9 + .../sbt-java-formatter/aosp-style/test | 15 ++ .../cache-import-options/build.sbt | 3 + .../cache-import-options/project/plugins.sbt | 1 + .../com/lightbend/BadFormatting.java | 9 + .../com/lightbend/BadFormatting.java | 8 + .../java/com/lightbend/BadFormatting.java | 9 + .../cache-import-options/test | 16 ++ .../sbt-java-formatter/cache-modes/build.sbt | 1 + .../cache-modes/project/plugins.sbt | 1 + .../com/lightbend/BadFormatting.java | 8 + .../com/lightbend/BadFormatting.java | 10 + .../java/com/lightbend/BadFormatting.java | 9 + .../sbt-java-formatter/cache-modes/test | 25 +++ .../imports-only-formatting-options/build.sbt | 36 ++++ .../project/plugins.sbt | 1 + .../com/lightbend/BadFormatting.java | 10 + .../java/com/lightbend/BadFormatting.java | 11 ++ .../imports-only-formatting-options/test | 43 +++++ .../java-home-symlink/build.sbt | 76 ++++++++ .../java-home-symlink/project/plugins.sbt | 1 + .../src/main/java/com/lightbend/Example.java | 3 + .../sbt-java-formatter/java-home-symlink/test | 20 ++ .../max-line-length-validation/build.sbt | 20 ++ .../project/plugins.sbt | 1 + .../src/main/java/com/lightbend/Example.java | 3 + .../max-line-length-validation/test | 31 ++++ .../no-sources-invalid-java-home/build.sbt | 43 +++++ .../project/plugins.sbt | 1 + .../scala-only/src/main/scala/Example.scala | 3 + .../no-sources-invalid-java-home/test | 24 +++ .../com/lightbend/BadFormatting.java | 6 + .../skip-javadoc-formatting/test | 8 + .../com/lightbend/BadFormatting.java | 7 + .../skip-reflowing-long-strings/test | 8 + .../com/lightbend/BadFormatting.java | 7 + .../skip-removing-unused-imports/test | 8 + .../com/lightbend/BadFormatting.java | 8 + .../skip-sorting-imports/test | 8 + 66 files changed, 978 insertions(+), 79 deletions(-) create mode 100644 plugin/src/sbt-test/java-runtime/cache-runtime-on-java-11/build.sbt create mode 100644 plugin/src/sbt-test/java-runtime/cache-runtime-on-java-11/project/plugins.sbt create mode 100644 plugin/src/sbt-test/java-runtime/cache-runtime-on-java-11/src/main/java/com/lightbend/Example.java create mode 100644 plugin/src/sbt-test/java-runtime/cache-runtime-on-java-11/test create mode 100644 plugin/src/sbt-test/java-runtime/max-line-length/build.sbt create mode 100644 plugin/src/sbt-test/java-runtime/max-line-length/project/plugins.sbt create mode 100644 plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-100/com/lightbend/LineLength.java create mode 100644 plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-120/com/lightbend/LineLength.java create mode 100644 plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-80/com/lightbend/LineLength.java create mode 100644 plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-aosp-120/com/lightbend/LineLength.java create mode 100644 plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-fresh-120/com/lightbend/LineLength.java create mode 100644 plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-restored-100/com/lightbend/LineLength.java create mode 100644 plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-original/com/lightbend/LineLength.java create mode 100644 plugin/src/sbt-test/java-runtime/max-line-length/src/main/java/com/lightbend/LineLength.java create mode 100644 plugin/src/sbt-test/java-runtime/max-line-length/src/test/java-expected-80/com/lightbend/LineLengthTest.java create mode 100644 plugin/src/sbt-test/java-runtime/max-line-length/src/test/java/com/lightbend/LineLengthTest.java create mode 100644 plugin/src/sbt-test/java-runtime/max-line-length/test create mode 100644 plugin/src/sbt-test/java-runtime/reorder-modifiers/src/main/java-expected-reordered/com/lightbend/BadFormatting.java create mode 100644 plugin/src/sbt-test/sbt-java-formatter/aosp-style/src/main/java-expected-google/com/lightbend/BadFormatting.java create mode 100644 plugin/src/sbt-test/sbt-java-formatter/aosp-style/src/test/java-expected-google/com/lightbend/BadFormatting.java create mode 100644 plugin/src/sbt-test/sbt-java-formatter/cache-import-options/build.sbt create mode 100644 plugin/src/sbt-test/sbt-java-formatter/cache-import-options/project/plugins.sbt create mode 100644 plugin/src/sbt-test/sbt-java-formatter/cache-import-options/src/main/java-expected-sorted/com/lightbend/BadFormatting.java create mode 100644 plugin/src/sbt-test/sbt-java-formatter/cache-import-options/src/main/java-expected/com/lightbend/BadFormatting.java create mode 100644 plugin/src/sbt-test/sbt-java-formatter/cache-import-options/src/main/java/com/lightbend/BadFormatting.java create mode 100644 plugin/src/sbt-test/sbt-java-formatter/cache-import-options/test create mode 100644 plugin/src/sbt-test/sbt-java-formatter/cache-modes/build.sbt create mode 100644 plugin/src/sbt-test/sbt-java-formatter/cache-modes/project/plugins.sbt create mode 100644 plugin/src/sbt-test/sbt-java-formatter/cache-modes/src/main/java-expected-imports/com/lightbend/BadFormatting.java create mode 100644 plugin/src/sbt-test/sbt-java-formatter/cache-modes/src/main/java-expected/com/lightbend/BadFormatting.java create mode 100644 plugin/src/sbt-test/sbt-java-formatter/cache-modes/src/main/java/com/lightbend/BadFormatting.java create mode 100644 plugin/src/sbt-test/sbt-java-formatter/cache-modes/test create mode 100644 plugin/src/sbt-test/sbt-java-formatter/imports-only-formatting-options/build.sbt create mode 100644 plugin/src/sbt-test/sbt-java-formatter/imports-only-formatting-options/project/plugins.sbt create mode 100644 plugin/src/sbt-test/sbt-java-formatter/imports-only-formatting-options/src/main/java-expected/com/lightbend/BadFormatting.java create mode 100644 plugin/src/sbt-test/sbt-java-formatter/imports-only-formatting-options/src/main/java/com/lightbend/BadFormatting.java create mode 100644 plugin/src/sbt-test/sbt-java-formatter/imports-only-formatting-options/test create mode 100644 plugin/src/sbt-test/sbt-java-formatter/java-home-symlink/build.sbt create mode 100644 plugin/src/sbt-test/sbt-java-formatter/java-home-symlink/project/plugins.sbt create mode 100644 plugin/src/sbt-test/sbt-java-formatter/java-home-symlink/src/main/java/com/lightbend/Example.java create mode 100644 plugin/src/sbt-test/sbt-java-formatter/java-home-symlink/test create mode 100644 plugin/src/sbt-test/sbt-java-formatter/max-line-length-validation/build.sbt create mode 100644 plugin/src/sbt-test/sbt-java-formatter/max-line-length-validation/project/plugins.sbt create mode 100644 plugin/src/sbt-test/sbt-java-formatter/max-line-length-validation/src/main/java/com/lightbend/Example.java create mode 100644 plugin/src/sbt-test/sbt-java-formatter/max-line-length-validation/test create mode 100644 plugin/src/sbt-test/sbt-java-formatter/no-sources-invalid-java-home/build.sbt create mode 100644 plugin/src/sbt-test/sbt-java-formatter/no-sources-invalid-java-home/project/plugins.sbt create mode 100644 plugin/src/sbt-test/sbt-java-formatter/no-sources-invalid-java-home/scala-only/src/main/scala/Example.scala create mode 100644 plugin/src/sbt-test/sbt-java-formatter/no-sources-invalid-java-home/test create mode 100644 plugin/src/sbt-test/sbt-java-formatter/skip-javadoc-formatting/src/main/java-expected-enabled/com/lightbend/BadFormatting.java create mode 100644 plugin/src/sbt-test/sbt-java-formatter/skip-reflowing-long-strings/src/main/java-expected-enabled/com/lightbend/BadFormatting.java create mode 100644 plugin/src/sbt-test/sbt-java-formatter/skip-removing-unused-imports/src/main/java-expected-enabled/com/lightbend/BadFormatting.java create mode 100644 plugin/src/sbt-test/sbt-java-formatter/skip-sorting-imports/src/main/java-expected-enabled/com/lightbend/BadFormatting.java diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 57b5c68..9f507dc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: - name: Build and test (sbt 1.x) if: ${{ matrix.jobtype == 1 }} shell: bash - run: sbt -v '++ 2.12.x' clean scalafmtSbtCheck scalafmtCheckAll test 'scripted sbt-java-formatter/*' 'scripted java-runtime/compatible-11' + run: sbt -v '++ 2.12.x' clean scalafmtSbtCheck scalafmtCheckAll test 'scripted sbt-java-formatter/*' 'scripted java-runtime/compatible-11' 'scripted java-runtime/cache-runtime-on-java-11' - name: Build and test (sbt 2.x) if: ${{ matrix.jobtype == 2 }} shell: bash @@ -65,11 +65,11 @@ jobs: - name: Build and test (sbt 2.x, Java 21) if: ${{ matrix.jobtype == 3 }} shell: bash - run: sbt -v '++ 3.x' clean scalafmtSbtCheck scalafmtCheckAll test 'scripted sbt-java-formatter/*' 'scripted java-runtime/compatible-21' 'scripted java-runtime/reorder-modifiers' + run: sbt -v '++ 3.x' clean scalafmtSbtCheck scalafmtCheckAll test 'scripted sbt-java-formatter/*' 'scripted java-runtime/compatible-21' 'scripted java-runtime/reorder-modifiers' 'scripted java-runtime/max-line-length' - name: Build and test (sbt 2.x, Java 25) if: ${{ matrix.jobtype == 4 }} shell: bash - run: sbt -v '++ 3.x' clean scalafmtSbtCheck scalafmtCheckAll test 'scripted java-runtime/compatible-21' 'scripted java-runtime/reorder-modifiers' + run: sbt -v '++ 3.x' clean scalafmtSbtCheck scalafmtCheckAll test 'scripted java-runtime/compatible-21' 'scripted java-runtime/reorder-modifiers' 'scripted java-runtime/max-line-length' - name: Build and test (sbt 1.x, Java 11 with formatter on Java 25) if: ${{ matrix.jobtype == 5 }} shell: bash @@ -78,4 +78,4 @@ jobs: SBT_JAVAFMT_JAVA_HOME: ${{ env.JAVA_HOME_25_X64 }} run: | export PATH="${JAVA_HOME}/bin:$PATH" - sbt -v '++ 2.12.x' clean scalafmtSbtCheck scalafmtCheckAll test 'scripted java-runtime/compatible-21' 'scripted java-runtime/reorder-modifiers' + sbt -v '++ 2.12.x' clean scalafmtSbtCheck scalafmtCheckAll test 'scripted java-runtime/compatible-21' 'scripted java-runtime/reorder-modifiers' 'scripted java-runtime/max-line-length' diff --git a/README.md b/README.md index f6cf76d..753a71f 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ For available versions see [releases](https://github.com/sbt/sbt-java-formatter/ * The `javafmtOnCompile` setting controls whether the formatter kicks in on compile (`false` by default). * The `javafmtStyle` setting defines the formatting style: Google Java Style (by default) or AOSP style. +* The `javafmtMaxLineLength` setting controls the maximum line length (`100` by default). Custom values require `javafmtFormatterCompatibleJavaVersion := 21`. * The `javafmtSortImports` setting controls whether imports are sorted (`true` by default). * The `javafmtRemoveUnusedImports` setting controls whether unused imports are removed (`true` by default). * The `javafmtReflowLongStrings` setting controls whether long string literals are reflowed (`true` by default). @@ -86,7 +87,7 @@ SBT_JAVAFMT_JAVA_HOME=/path/to/jdk-17 sbt javafmt > [!NOTE] > Many projects use Java 17 as their baseline today. If that is true for your build, `ThisBuild / javafmtFormatterCompatibleJavaVersion := 17` is often the simplest setup. > -> The main Java language support changes in `google-java-format` [after `v1.28.0`](https://github.com/google/google-java-format/compare/v1.28.0...v1.36.1) are: +> The main Java language support changes in `google-java-format` [after `v1.28.0`](https://github.com/google/google-java-format/compare/v1.28.0...v1.37.0) are: > > - [Initial support for import module in google-java-format](https://github.com/google/google-java-format/commit/6afe380707ec16884ec2761763ccec998de403d1) > - [Support Instance Main Methods in google-java-format](https://github.com/google/google-java-format/commit/737b0032b3a18eb6e458271ea440098c166f6c2d) @@ -119,13 +120,35 @@ ThisBuild / javafmtFormatJavadoc := true ThisBuild / javafmtReorderModifiers := true ``` -Set any of them to `false` to pass the corresponding `--skip-...` flag to `google-java-format`. +Set the Boolean formatter options to `false` to pass the corresponding `--skip-...` flag to `google-java-format`. + +Use `javafmtStyle` to select Google or AOSP style, which is equivalent to the CLI's `--style=google` / `--style=aosp` options (`--google-style` / `--aosp`): + +```scala +import com.google.googlejavaformat.java.JavaFormatterOptions + +ThisBuild / javafmtStyle := JavaFormatterOptions.Style.GOOGLE +// Or: ThisBuild / javafmtStyle := JavaFormatterOptions.Style.AOSP +``` + +Use `javafmtMaxLineLength` to customize the line width used for code, comments, and long string reflow: + +```scala +ThisBuild / javafmtFormatterCompatibleJavaVersion := 21 +ThisBuild / javafmtMaxLineLength := 120 +``` + +The value must be positive. The default of `100` works with all formatter runtime lines; custom values require the Java 21 line because the Java 11 and Java 17 lines do not support `--max-line-length`. + +Only `javafmtStyle`, `javafmtSortImports`, and `javafmtRemoveUnusedImports` affect the output of the `javafmtFixImports...` tasks. Full-formatting options (line length, Javadoc formatting, modifier reordering, and long string reflow) are ignored and not validated for import-only tasks, even on older formatter runtime lines. + +Formatting and check caches are invalidated when effective formatter options or the formatter runtime change, even if source files have not changed. Import-only tasks use a separate cache from full-formatting tasks; changes to full-formatting options do not invalidate their cache. `javafmtFormatterCompatibleJavaVersion` maps to these formatter versions: - `11` -> `google-java-format 1.24.0` - `17` -> `google-java-format 1.28.0` -- `21` -> `google-java-format 1.36.1` (default) +- `21` -> `google-java-format 1.37.0` (default) If the selected formatter runtime is newer than the Java used to launch the formatter JVM, either: diff --git a/plugin/src/main/scala/com/github/sbt/JavaFormatterPlugin.scala b/plugin/src/main/scala/com/github/sbt/JavaFormatterPlugin.scala index 5cabaa9..66d55ae 100644 --- a/plugin/src/main/scala/com/github/sbt/JavaFormatterPlugin.scala +++ b/plugin/src/main/scala/com/github/sbt/JavaFormatterPlugin.scala @@ -72,6 +72,9 @@ object JavaFormatterPlugin extends AutoPlugin { "Selects the google-java-format runtime line by compatible Java version. Supported values: 11, 17, 21.") val javafmtStyle = settingKey[JavaFormatterOptions.Style]("Define formatting style, Google Java Style (default) or AOSP") + val javafmtMaxLineLength = + settingKey[Int]( + s"Maximum line length used by google-java-format. Defaults to ${JavaFormatter.DefaultMaxLineLength}; custom values require the Java 21 formatter runtime line.") val javafmtJavaMaxHeap = settingKey[Option[String]]("Maximum heap size passed to the forked google-java-format JVM, e.g. Some(\"256m\").") val javafmtSortImports = @@ -119,6 +122,7 @@ object JavaFormatterPlugin extends AutoPlugin { javafmtOnCompile := false, javafmtFormatterCompatibleJavaVersion := 21, javafmtStyle := JavaFormatterOptions.Style.GOOGLE, + javafmtMaxLineLength := JavaFormatter.DefaultMaxLineLength, javafmtJavaMaxHeap := Some("256m"), javafmtSortImports := true, javafmtRemoveUnusedImports := true, @@ -143,8 +147,9 @@ object JavaFormatterPlugin extends AutoPlugin { val iF = (javafmt / includeFilter).value val eF = (javafmt / excludeFilter).value val cache = streamz.cacheStoreFactory + val maxLineLength = javafmtMaxLineLength.value val options = - validateOptions(javafmtOptions.value, javafmtFormatterCompatibleJavaVersion.value) + validateOptions(javafmtOptions.value, javafmtFormatterCompatibleJavaVersion.value, maxLineLength) val formatterClasspath = javafmtFormatterClasspath.value.toVector val javaMaxHeap = javafmtJavaMaxHeap.value val sortImports = javafmtSortImports.value @@ -163,7 +168,8 @@ object JavaFormatterPlugin extends AutoPlugin { javaMaxHeap, sortImports, removeUnusedImports, - reflowLongStrings) + reflowLongStrings, + maxLineLength) }, javafmtCheck := { val streamz = streams.value @@ -174,8 +180,9 @@ object JavaFormatterPlugin extends AutoPlugin { val iF = (javafmt / includeFilter).value val eF = (javafmt / excludeFilter).value val cache = (javafmt / streams).value.cacheStoreFactory + val maxLineLength = javafmtMaxLineLength.value val options = - validateOptions(javafmtOptions.value, javafmtFormatterCompatibleJavaVersion.value) + validateOptions(javafmtOptions.value, javafmtFormatterCompatibleJavaVersion.value, maxLineLength) val formatterClasspath = javafmtFormatterClasspath.value.toVector val javaMaxHeap = javafmtJavaMaxHeap.value val sortImports = javafmtSortImports.value @@ -195,7 +202,8 @@ object JavaFormatterPlugin extends AutoPlugin { javaMaxHeap, sortImports, removeUnusedImports, - reflowLongStrings) + reflowLongStrings, + maxLineLength) }, javafmtFixImports := { val streamz = streams.value @@ -204,14 +212,12 @@ object JavaFormatterPlugin extends AutoPlugin { val sD = (javafmt / sourceDirectories).value.toList val iF = (javafmt / includeFilter).value val eF = (javafmt / excludeFilter).value - val cache = streamz.cacheStoreFactory - val options = - validateOptions(javafmtOptions.value, javafmtFormatterCompatibleJavaVersion.value) + val cache = (javafmt / streams).value.cacheStoreFactory + val options = javafmtOptions.value val formatterClasspath = javafmtFormatterClasspath.value.toVector val javaMaxHeap = javafmtJavaMaxHeap.value val sortImports = javafmtSortImports.value val removeUnusedImports = javafmtRemoveUnusedImports.value - val reflowLongStrings = javafmtReflowLongStrings.value JavaFormatter.fixImports( projectId, invocationId, @@ -224,8 +230,7 @@ object JavaFormatterPlugin extends AutoPlugin { formatterClasspath, javaMaxHeap, sortImports, - removeUnusedImports, - reflowLongStrings) + removeUnusedImports) }, javafmtFixImportsCheck := { val streamz = streams.value @@ -236,13 +241,11 @@ object JavaFormatterPlugin extends AutoPlugin { val iF = (javafmt / includeFilter).value val eF = (javafmt / excludeFilter).value val cache = (javafmt / streams).value.cacheStoreFactory - val options = - validateOptions(javafmtOptions.value, javafmtFormatterCompatibleJavaVersion.value) + val options = javafmtOptions.value val formatterClasspath = javafmtFormatterClasspath.value.toVector val javaMaxHeap = javafmtJavaMaxHeap.value val sortImports = javafmtSortImports.value val removeUnusedImports = javafmtRemoveUnusedImports.value - val reflowLongStrings = javafmtReflowLongStrings.value JavaFormatter.fixImportsCheck( projectId, invocationId, @@ -256,8 +259,7 @@ object JavaFormatterPlugin extends AutoPlugin { formatterClasspath, javaMaxHeap, sortImports, - removeUnusedImports, - reflowLongStrings) + removeUnusedImports) }, javafmtDoFormatOnCompile := Def.settingDyn { if (javafmtOnCompile.value) { @@ -287,14 +289,27 @@ object JavaFormatterPlugin extends AutoPlugin { compatibleJavaVersion match { case 11 => "1.24.0" case 17 => "1.28.0" - case 21 => "1.36.1" + case 21 => "1.37.0" case other => throw new MessageOnlyException( s"Unsupported javafmtFormatterCompatibleJavaVersion: $other. Expected one of: 11, 17, 21.") } - private def validateOptions(options: JavaFormatterOptions, compatibleJavaVersion: Int): JavaFormatterOptions = { - if (!options.reorderModifiers() && compatibleJavaVersion != 21) { + private def validateOptions( + options: JavaFormatterOptions, + compatibleJavaVersion: Int, + maxLineLength: Int): JavaFormatterOptions = { + if (maxLineLength <= 0) { + throw new MessageOnlyException(s"javafmtMaxLineLength must be positive, but was $maxLineLength.") + } + if (maxLineLength != JavaFormatter.DefaultMaxLineLength && compatibleJavaVersion < 21) { + throw new MessageOnlyException( + "A custom javafmtMaxLineLength requires " + + "ThisBuild / javafmtFormatterCompatibleJavaVersion := 21 " + + "because the Java 11 and Java 17 formatter runtime lines do not support " + + "--max-line-length.") + } + if (!options.reorderModifiers() && compatibleJavaVersion < 21) { throw new MessageOnlyException( "Disabling modifier reordering requires " + "ThisBuild / javafmtFormatterCompatibleJavaVersion := 21 " + diff --git a/plugin/src/main/scala/com/github/sbt/javaformatter/JavaFormatter.scala b/plugin/src/main/scala/com/github/sbt/javaformatter/JavaFormatter.scala index f0cfb22..b23f598 100644 --- a/plugin/src/main/scala/com/github/sbt/javaformatter/JavaFormatter.scala +++ b/plugin/src/main/scala/com/github/sbt/javaformatter/JavaFormatter.scala @@ -16,7 +16,8 @@ package com.github.sbt.javaformatter -import java.io.File +import java.io.{ File, IOException } +import java.nio.file.InvalidPathException import _root_.sbt.Keys._ import _root_.sbt._ @@ -28,6 +29,8 @@ import scala.sys.process.{ Process, ProcessLogger } object JavaFormatter { + private[sbt] val DefaultMaxLineLength = 100 + private val GoogleJavaFormatMain = "com.google.googlejavaformat.java.Main" private val JavaHomeEnvVar = "SBT_JAVAFMT_JAVA_HOME" private val JavaHomeProperty = "sbt-javafmt.java.home" @@ -51,7 +54,8 @@ object JavaFormatter { javaMaxHeap: Option[String], sortImports: Boolean, removeUnusedImports: Boolean, - reflowLongStrings: Boolean): Unit = { + reflowLongStrings: Boolean, + maxLineLength: Int): Unit = { val files = sourceDirectories.descendantsExcept(includeFilter, excludeFilter).get().toList cachedFormatSources( cacheStoreFactory, @@ -65,7 +69,8 @@ object JavaFormatter { fixImportsOnly = false, sortImports, removeUnusedImports, - reflowLongStrings) + reflowLongStrings, + maxLineLength) } def fixImports( @@ -80,8 +85,7 @@ object JavaFormatter { formatterClasspath: Seq[File], javaMaxHeap: Option[String], sortImports: Boolean, - removeUnusedImports: Boolean, - reflowLongStrings: Boolean): Unit = { + removeUnusedImports: Boolean): Unit = { val files = sourceDirectories.descendantsExcept(includeFilter, excludeFilter).get().toList cachedFormatSources( cacheStoreFactory, @@ -95,7 +99,9 @@ object JavaFormatter { fixImportsOnly = true, sortImports, removeUnusedImports, - reflowLongStrings) + // Full-formatting options are ignored in import-only mode. + reflowLongStrings = true, + maxLineLength = DefaultMaxLineLength) } def check( @@ -112,7 +118,8 @@ object JavaFormatter { javaMaxHeap: Option[String], sortImports: Boolean, removeUnusedImports: Boolean, - reflowLongStrings: Boolean): Boolean = { + reflowLongStrings: Boolean, + maxLineLength: Int): Boolean = { val files = sourceDirectories.descendantsExcept(includeFilter, excludeFilter).get().toList val analysis = cachedCheckSources( @@ -128,7 +135,8 @@ object JavaFormatter { fixImportsOnly = false, sortImports, removeUnusedImports, - reflowLongStrings) + reflowLongStrings, + maxLineLength) trueOrBoom(analysis) } @@ -145,8 +153,7 @@ object JavaFormatter { formatterClasspath: Seq[File], javaMaxHeap: Option[String], sortImports: Boolean, - removeUnusedImports: Boolean, - reflowLongStrings: Boolean): Boolean = { + removeUnusedImports: Boolean): Boolean = { val files = sourceDirectories.descendantsExcept(includeFilter, excludeFilter).get().toList val analysis = cachedCheckSources( @@ -162,7 +169,9 @@ object JavaFormatter { fixImportsOnly = true, sortImports, removeUnusedImports, - reflowLongStrings) + // Full-formatting options are ignored in import-only mode. + reflowLongStrings = true, + maxLineLength = DefaultMaxLineLength) trueOrBoom(analysis) } @@ -202,8 +211,11 @@ object JavaFormatter { fixImportsOnly: Boolean, sortImports: Boolean, removeUnusedImports: Boolean, - reflowLongStrings: Boolean): Analysis = { - trackSourcesViaCache(cacheStoreFactory, sources) { (outDiff, prev) => + reflowLongStrings: Boolean, + maxLineLength: Int): Analysis = { + val flags = cliFlags(options, fixImportsOnly, sortImports, removeUnusedImports, reflowLongStrings, maxLineLength) + val inputs = formatterCacheInputs(flags, formatterClasspath, javaMaxHeap) + trackSourcesViaCache(cacheStoreFactory, sources, inputs, fixImportsOnly) { (outDiff, prev) => log.debug(outDiff.toString) val updatedOrAdded = outDiff.modified & outDiff.checked val filesToCheck: Set[File] = updatedOrAdded @@ -221,7 +233,8 @@ object JavaFormatter { fixImportsOnly, sortImports, removeUnusedImports, - reflowLongStrings) + reflowLongStrings, + maxLineLength) prev.copy(failedCheck = result.failedCheck | prevFailed) } } @@ -242,7 +255,8 @@ object JavaFormatter { fixImportsOnly: Boolean, sortImports: Boolean, removeUnusedImports: Boolean, - reflowLongStrings: Boolean): Analysis = { + reflowLongStrings: Boolean, + maxLineLength: Int): Analysis = { if (sources.nonEmpty) { log.info(s"Checking ${sources.size} Java source${plural(sources.size)}...") } @@ -259,7 +273,8 @@ object JavaFormatter { fixImportsOnly, sortImports, removeUnusedImports, - reflowLongStrings) + reflowLongStrings, + maxLineLength) unformatted.foreach { file => warnBadFormat(file.relativeTo(baseDir).getOrElse(file), log) } Analysis(failedCheck = unformatted) } @@ -276,8 +291,11 @@ object JavaFormatter { fixImportsOnly: Boolean, sortImports: Boolean, removeUnusedImports: Boolean, - reflowLongStrings: Boolean): Unit = { - trackSourcesViaCache(cacheStoreFactory, sources) { (outDiff, prev) => + reflowLongStrings: Boolean, + maxLineLength: Int): Unit = { + val flags = cliFlags(options, fixImportsOnly, sortImports, removeUnusedImports, reflowLongStrings, maxLineLength) + val inputs = formatterCacheInputs(flags, formatterClasspath, javaMaxHeap) + trackSourcesViaCache(cacheStoreFactory, sources, inputs, fixImportsOnly) { (outDiff, prev) => log.debug(outDiff.toString) val updatedOrAdded = outDiff.modified & outDiff.checked val filesToFormat: Set[File] = updatedOrAdded | prev.failedCheck @@ -294,7 +312,8 @@ object JavaFormatter { fixImportsOnly, sortImports, removeUnusedImports, - reflowLongStrings) + reflowLongStrings, + maxLineLength) } Analysis(Set.empty) } @@ -311,7 +330,8 @@ object JavaFormatter { fixImportsOnly: Boolean, sortImports: Boolean, removeUnusedImports: Boolean, - reflowLongStrings: Boolean): Unit = { + reflowLongStrings: Boolean, + maxLineLength: Int): Unit = { val changed = runCheck( baseDir = new File("."), @@ -326,6 +346,7 @@ object JavaFormatter { sortImports, removeUnusedImports, reflowLongStrings, + maxLineLength, warnOnFailure = false) if (changed.nonEmpty) { runReplace( @@ -339,22 +360,55 @@ object JavaFormatter { fixImportsOnly, sortImports, removeUnusedImports, - reflowLongStrings) + reflowLongStrings, + maxLineLength) } val cnt = changed.size log.info(s"Reformatted $cnt Java source${plural(cnt)}") } - private def trackSourcesViaCache(cacheStoreFactory: CacheStoreFactory, sources: Seq[File])( - f: (ChangeReport[File], Analysis) => Analysis): Analysis = { - val prevTracker = Tracked.lastOutput[Unit, Analysis](cacheStoreFactory.make("last")) { (_, prev0) => - val prev = prev0.getOrElse(Analysis(Set.empty)) - Tracked.diffOutputs(cacheStoreFactory.make("output-diff"), FileInfo.lastModified)(sources.toSet) { - (outDiff: ChangeReport[File]) => f(outDiff, prev) - } + private def javaHomeCacheInput: String = { + val javaHome = new File(javaHomeSourceAndPath._2) + try { + // Avoid Java 11's canonical-path cache so retargeted symlinks are noticed immediately. + javaHome.toPath.toRealPath().toString + } catch { + // Missing or invalid homes must only fail when the formatter is actually launched. + case _: IOException | _: InvalidPathException => javaHome.getAbsolutePath + } + } + private def formatterCacheInputs( + flags: Seq[String], + formatterClasspath: Seq[File], + javaMaxHeap: Option[String]): Seq[String] = + Seq(javaHomeCacheInput) ++ javaArgs(flags, formatterClasspath, javaMaxHeap) ++ formatterClasspath.flatMap { file => + Seq(file.lastModified().toString, file.length().toString) } - prevTracker(()) + + private def trackSourcesViaCache( + cacheStoreFactory: CacheStoreFactory, + sources: Seq[File], + inputs: Seq[String], + fixImportsOnly: Boolean)(f: (ChangeReport[File], Analysis) => Analysis): Analysis = { + val cache = cacheStoreFactory.sub(if (fixImportsOnly) "imports-only" else "format") + val last = cache.make("last") + val outputDiff = cache.make("output-diff") + val inputsTracker = Tracked.inputChanged[List[String], Analysis](cache.make("inputs")) { (changed, _) => + if (changed) { + // Clear both source stamps and failed checks when the effective formatter invocation changes. + last.delete() + outputDiff.delete() + } + val prevTracker = Tracked.lastOutput[Unit, Analysis](last) { (_, prev0) => + val prev = prev0.getOrElse(Analysis(Set.empty)) + Tracked.diffOutputs(outputDiff, FileInfo.lastModified)(sources.toSet) { (outDiff: ChangeReport[File]) => + f(outDiff, prev) + } + } + prevTracker(()) + } + inputsTracker(inputs.toList) } private def cliFlags( @@ -362,29 +416,35 @@ object JavaFormatter { fixImportsOnly: Boolean, sortImports: Boolean, removeUnusedImports: Boolean, - reflowLongStrings: Boolean): Seq[String] = { + reflowLongStrings: Boolean, + maxLineLength: Int): Seq[String] = { val styleFlags = if (options.style() == JavaFormatterOptions.Style.AOSP) Seq("--aosp") else Nil - val javadocFlags = - if (options.formatJavadoc()) Nil - else Seq("--skip-javadoc-formatting") - val reorderModifiersFlags = - if (options.reorderModifiers()) Nil - else Seq("--skip-reordering-modifiers") - val fixImportsOnlyFlags = - if (fixImportsOnly) Seq("--fix-imports-only") - else Nil val sortImportsFlags = if (sortImports) Nil else Seq("--skip-sorting-imports") val removeUnusedImportsFlags = if (removeUnusedImports) Nil else Seq("--skip-removing-unused-imports") - val reflowLongStringsFlags = - if (reflowLongStrings) Nil - else Seq("--skip-reflowing-long-strings") - styleFlags ++ javadocFlags ++ reorderModifiersFlags ++ fixImportsOnlyFlags ++ sortImportsFlags ++ removeUnusedImportsFlags ++ reflowLongStringsFlags + val importFlags = styleFlags ++ sortImportsFlags ++ removeUnusedImportsFlags + if (fixImportsOnly) { + importFlags ++ Seq("--fix-imports-only") + } else { + val javadocFlags = + if (options.formatJavadoc()) Nil + else Seq("--skip-javadoc-formatting") + val reorderModifiersFlags = + if (options.reorderModifiers()) Nil + else Seq("--skip-reordering-modifiers") + val reflowLongStringsFlags = + if (reflowLongStrings) Nil + else Seq("--skip-reflowing-long-strings") + val maxLineLengthFlags = + if (maxLineLength == DefaultMaxLineLength) Nil + else Seq(s"--max-line-length=$maxLineLength") + importFlags ++ javadocFlags ++ reorderModifiersFlags ++ reflowLongStringsFlags ++ maxLineLengthFlags + } } private case class CliResult(exitCode: Int, stdout: Vector[String], stderr: Vector[String]) @@ -433,14 +493,15 @@ object JavaFormatter { } } - private lazy val javaBin: String = { - val javaHomeSourceAndPath = - sys.props - .get(JavaHomeProperty) - .filter(_.nonEmpty) - .map(path => (JavaHomeProperty, path)) - .orElse(sys.env.get(JavaHomeEnvVar).filter(_.nonEmpty).map(path => (JavaHomeEnvVar, path))) - .getOrElse(("java.home", sys.props("java.home"))) + private def javaHomeSourceAndPath: (String, String) = + sys.props + .get(JavaHomeProperty) + .filter(_.nonEmpty) + .map(path => (JavaHomeProperty, path)) + .orElse(sys.env.get(JavaHomeEnvVar).filter(_.nonEmpty).map(path => (JavaHomeEnvVar, path))) + .getOrElse(("java.home", sys.props("java.home"))) + + private def javaBin: String = { val (javaHomeSource, javaHomePath) = javaHomeSourceAndPath val javaHome = new File(javaHomePath) val unixJava = new File(javaHome, "bin/java") @@ -494,12 +555,13 @@ object JavaFormatter { sortImports: Boolean, removeUnusedImports: Boolean, reflowLongStrings: Boolean, + maxLineLength: Int, warnOnFailure: Boolean = true): Set[File] = { if (sources.isEmpty) { return Set.empty } val args = - cliFlags(options, fixImportsOnly, sortImports, removeUnusedImports, reflowLongStrings) ++ Seq( + cliFlags(options, fixImportsOnly, sortImports, removeUnusedImports, reflowLongStrings, maxLineLength) ++ Seq( "--dry-run", "--set-exit-if-changed") ++ sources.map(_.getAbsolutePath) val result = runCli(args, formatterClasspath, log, javaMaxHeap) @@ -530,12 +592,13 @@ object JavaFormatter { fixImportsOnly: Boolean, sortImports: Boolean, removeUnusedImports: Boolean, - reflowLongStrings: Boolean): Unit = { + reflowLongStrings: Boolean, + maxLineLength: Int): Unit = { if (sources.isEmpty) { return } val args = - cliFlags(options, fixImportsOnly, sortImports, removeUnusedImports, reflowLongStrings) ++ Seq( + cliFlags(options, fixImportsOnly, sortImports, removeUnusedImports, reflowLongStrings, maxLineLength) ++ Seq( "--replace") ++ sources.map(_.getAbsolutePath) val result = runCli(args, formatterClasspath, log, javaMaxHeap) if (result.exitCode != 0) { diff --git a/plugin/src/sbt-test/java-runtime/cache-runtime-on-java-11/build.sbt b/plugin/src/sbt-test/java-runtime/cache-runtime-on-java-11/build.sbt new file mode 100644 index 0000000..40bd009 --- /dev/null +++ b/plugin/src/sbt-test/java-runtime/cache-runtime-on-java-11/build.sbt @@ -0,0 +1 @@ +ThisBuild / javafmtFormatterCompatibleJavaVersion := 11 diff --git a/plugin/src/sbt-test/java-runtime/cache-runtime-on-java-11/project/plugins.sbt b/plugin/src/sbt-test/java-runtime/cache-runtime-on-java-11/project/plugins.sbt new file mode 100644 index 0000000..6e37f4a --- /dev/null +++ b/plugin/src/sbt-test/java-runtime/cache-runtime-on-java-11/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.github.sbt" % "sbt-java-formatter" % System.getProperty("plugin.version")) diff --git a/plugin/src/sbt-test/java-runtime/cache-runtime-on-java-11/src/main/java/com/lightbend/Example.java b/plugin/src/sbt-test/java-runtime/cache-runtime-on-java-11/src/main/java/com/lightbend/Example.java new file mode 100644 index 0000000..3f04050 --- /dev/null +++ b/plugin/src/sbt-test/java-runtime/cache-runtime-on-java-11/src/main/java/com/lightbend/Example.java @@ -0,0 +1,3 @@ +package com.lightbend; + +public class Example {} diff --git a/plugin/src/sbt-test/java-runtime/cache-runtime-on-java-11/test b/plugin/src/sbt-test/java-runtime/cache-runtime-on-java-11/test new file mode 100644 index 0000000..ae5c3f9 --- /dev/null +++ b/plugin/src/sbt-test/java-runtime/cache-runtime-on-java-11/test @@ -0,0 +1,11 @@ +# The Java 17 formatter cannot run on Java 11. Cached success must not hide this mismatch. +> javafmt +> javafmtCheck +> set ThisBuild / javafmtFormatterCompatibleJavaVersion := 17 +-> javafmtCheck +> set ThisBuild / javafmtFormatterCompatibleJavaVersion := 11 +> javafmtCheck +> set ThisBuild / javafmtFormatterCompatibleJavaVersion := 17 +-> javafmt +> set ThisBuild / javafmtFormatterCompatibleJavaVersion := 11 +> javafmtCheck diff --git a/plugin/src/sbt-test/java-runtime/compatible-17/build.sbt b/plugin/src/sbt-test/java-runtime/compatible-17/build.sbt index bc41af5..b910ecd 100644 --- a/plugin/src/sbt-test/java-runtime/compatible-17/build.sbt +++ b/plugin/src/sbt-test/java-runtime/compatible-17/build.sbt @@ -1 +1,18 @@ ThisBuild / javafmtFormatterCompatibleJavaVersion := 17 + +val expectedCheckMessage = settingKey[String]("Expected unsupported full-formatting option error.") +@transient +val expectCheckFailure = taskKey[Unit]("Verify the reason the full-format check fails.") + +expectedCheckMessage := + "A custom javafmtMaxLineLength requires ThisBuild / javafmtFormatterCompatibleJavaVersion := 21" + +expectCheckFailure := { + val expected = expectedCheckMessage.value + javafmtCheckAll.result.value.toEither match { + case Left(incomplete) => + val messages = Incomplete.allExceptions(incomplete).flatMap(error => Option(error.getMessage)) + assert(messages.exists(_.contains(expected)), s"Expected '$expected', got: ${messages.mkString("; ")}") + case Right(_) => sys.error(s"Expected failure: $expected") + } +} diff --git a/plugin/src/sbt-test/java-runtime/compatible-17/test b/plugin/src/sbt-test/java-runtime/compatible-17/test index 5ac6f99..19296e2 100644 --- a/plugin/src/sbt-test/java-runtime/compatible-17/test +++ b/plugin/src/sbt-test/java-runtime/compatible-17/test @@ -9,3 +9,22 @@ $ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected/com/lightbend/BadFormatting.java $ exec diff src/test/java/com/lightbend/BadFormatting.java src/test/java-expected/com/lightbend/BadFormatting.java + +# Import-only tasks also ignore unsupported full-formatting settings on the Java 17 line. +> set ThisBuild / javafmtMaxLineLength := 120 +> set ThisBuild / javafmtReorderModifiers := false +> set ThisBuild / javafmtFormatJavadoc := false +> set ThisBuild / javafmtReflowLongStrings := false +> javafmtFixImportsAll +> javafmtFixImportsCheckAll +$ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected/com/lightbend/BadFormatting.java +$ exec diff src/test/java/com/lightbend/BadFormatting.java src/test/java-expected/com/lightbend/BadFormatting.java +> expectCheckFailure +> set ThisBuild / javafmtMaxLineLength := 100 +> set expectedCheckMessage := "Disabling modifier reordering requires ThisBuild / javafmtFormatterCompatibleJavaVersion := 21" +> expectCheckFailure + +> set ThisBuild / javafmtReorderModifiers := true +> set ThisBuild / javafmtFormatJavadoc := true +> set ThisBuild / javafmtReflowLongStrings := true +> javafmtCheckAll diff --git a/plugin/src/sbt-test/java-runtime/incompatible-21-on-java-17/test b/plugin/src/sbt-test/java-runtime/incompatible-21-on-java-17/test index 733d2b4..386a850 100644 --- a/plugin/src/sbt-test/java-runtime/incompatible-21-on-java-17/test +++ b/plugin/src/sbt-test/java-runtime/incompatible-21-on-java-17/test @@ -1 +1,10 @@ -> javafmtAll + +> set ThisBuild / javafmtFormatterCompatibleJavaVersion := 17 +> javafmtAll +> javafmtCheckAll +> set ThisBuild / javafmtFormatterCompatibleJavaVersion := 21 +-> javafmtAll +-> javafmtCheckAll +> set ThisBuild / javafmtFormatterCompatibleJavaVersion := 17 +> javafmtCheckAll diff --git a/plugin/src/sbt-test/java-runtime/max-line-length/build.sbt b/plugin/src/sbt-test/java-runtime/max-line-length/build.sbt new file mode 100644 index 0000000..641cfe0 --- /dev/null +++ b/plugin/src/sbt-test/java-runtime/max-line-length/build.sbt @@ -0,0 +1 @@ +ThisBuild / javafmtFormatterCompatibleJavaVersion := 21 diff --git a/plugin/src/sbt-test/java-runtime/max-line-length/project/plugins.sbt b/plugin/src/sbt-test/java-runtime/max-line-length/project/plugins.sbt new file mode 100644 index 0000000..6e37f4a --- /dev/null +++ b/plugin/src/sbt-test/java-runtime/max-line-length/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.github.sbt" % "sbt-java-formatter" % System.getProperty("plugin.version")) diff --git a/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-100/com/lightbend/LineLength.java b/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-100/com/lightbend/LineLength.java new file mode 100644 index 0000000..14d9692 --- /dev/null +++ b/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-100/com/lightbend/LineLength.java @@ -0,0 +1,19 @@ +package com.lightbend; + +/** + * This documentation describes a method that checks two strings for their expected contents and + * returns a useful example message for the caller. + */ +public class LineLength { + private static final String MESSAGE = + "This message contains enough words to exercise configurable line wrapping in the formatter" + + " while remaining readable at different column widths."; + + public String message() { + return MESSAGE; + } + + public boolean containsBoth(String firstValue, String secondValue) { + return firstValue.contains("first value") && secondValue.contains("second value"); + } +} diff --git a/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-120/com/lightbend/LineLength.java b/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-120/com/lightbend/LineLength.java new file mode 100644 index 0000000..b347c90 --- /dev/null +++ b/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-120/com/lightbend/LineLength.java @@ -0,0 +1,20 @@ +package com.lightbend; + +/** + * This documentation describes a method that checks two strings for their expected contents and returns a useful + * example message for the caller. + */ +public class LineLength { + private static final String MESSAGE = + "This message contains enough words to exercise configurable line" + + " wrapping in the formatter while remaining readable at different" + + " column widths."; + + public String message() { + return MESSAGE; + } + + public boolean containsBoth(String firstValue, String secondValue) { + return firstValue.contains("first value") && secondValue.contains("second value"); + } +} diff --git a/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-80/com/lightbend/LineLength.java b/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-80/com/lightbend/LineLength.java new file mode 100644 index 0000000..a00a32e --- /dev/null +++ b/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-80/com/lightbend/LineLength.java @@ -0,0 +1,21 @@ +package com.lightbend; + +/** + * This documentation describes a method that checks two strings for their + * expected contents and returns a useful example message for the caller. + */ +public class LineLength { + private static final String MESSAGE = + "This message contains enough words to exercise configurable line" + + " wrapping in the formatter while remaining readable at different" + + " column widths."; + + public String message() { + return MESSAGE; + } + + public boolean containsBoth(String firstValue, String secondValue) { + return firstValue.contains("first value") + && secondValue.contains("second value"); + } +} diff --git a/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-aosp-120/com/lightbend/LineLength.java b/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-aosp-120/com/lightbend/LineLength.java new file mode 100644 index 0000000..5b22ea6 --- /dev/null +++ b/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-aosp-120/com/lightbend/LineLength.java @@ -0,0 +1,20 @@ +package com.lightbend; + +/** + * This documentation describes a method that checks two strings for their expected contents and returns a useful + * example message for the caller. + */ +public class LineLength { + private static final String MESSAGE = + "This message contains enough words to exercise configurable line" + + " wrapping in the formatter while remaining readable at different" + + " column widths."; + + public String message() { + return MESSAGE; + } + + public boolean containsBoth(String firstValue, String secondValue) { + return firstValue.contains("first value") && secondValue.contains("second value"); + } +} diff --git a/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-fresh-120/com/lightbend/LineLength.java b/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-fresh-120/com/lightbend/LineLength.java new file mode 100644 index 0000000..f4812da --- /dev/null +++ b/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-fresh-120/com/lightbend/LineLength.java @@ -0,0 +1,19 @@ +package com.lightbend; + +/** + * This documentation describes a method that checks two strings for their expected contents and returns a useful + * example message for the caller. + */ +public class LineLength { + private static final String MESSAGE = + "This message contains enough words to exercise configurable line wrapping in the formatter while remaining" + + " readable at different column widths."; + + public String message() { + return MESSAGE; + } + + public boolean containsBoth(String firstValue, String secondValue) { + return firstValue.contains("first value") && secondValue.contains("second value"); + } +} diff --git a/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-restored-100/com/lightbend/LineLength.java b/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-restored-100/com/lightbend/LineLength.java new file mode 100644 index 0000000..19e3648 --- /dev/null +++ b/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-expected-restored-100/com/lightbend/LineLength.java @@ -0,0 +1,20 @@ +package com.lightbend; + +/** + * This documentation describes a method that checks two strings for their expected contents and + * returns a useful example message for the caller. + */ +public class LineLength { + private static final String MESSAGE = + "This message contains enough words to exercise configurable line" + + " wrapping in the formatter while remaining readable at different" + + " column widths."; + + public String message() { + return MESSAGE; + } + + public boolean containsBoth(String firstValue, String secondValue) { + return firstValue.contains("first value") && secondValue.contains("second value"); + } +} diff --git a/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-original/com/lightbend/LineLength.java b/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-original/com/lightbend/LineLength.java new file mode 100644 index 0000000..b1b446a --- /dev/null +++ b/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java-original/com/lightbend/LineLength.java @@ -0,0 +1,8 @@ +package com.lightbend; + +/** This documentation describes a method that checks two strings for their expected contents and returns a useful example message for the caller. */ +public class LineLength { + private static final String MESSAGE = "This message contains enough words to exercise configurable line wrapping in the formatter while remaining readable at different column widths."; + public String message() {return MESSAGE;} + public boolean containsBoth(String firstValue, String secondValue) {return firstValue.contains("first value") && secondValue.contains("second value");} +} diff --git a/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java/com/lightbend/LineLength.java b/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java/com/lightbend/LineLength.java new file mode 100644 index 0000000..b1b446a --- /dev/null +++ b/plugin/src/sbt-test/java-runtime/max-line-length/src/main/java/com/lightbend/LineLength.java @@ -0,0 +1,8 @@ +package com.lightbend; + +/** This documentation describes a method that checks two strings for their expected contents and returns a useful example message for the caller. */ +public class LineLength { + private static final String MESSAGE = "This message contains enough words to exercise configurable line wrapping in the formatter while remaining readable at different column widths."; + public String message() {return MESSAGE;} + public boolean containsBoth(String firstValue, String secondValue) {return firstValue.contains("first value") && secondValue.contains("second value");} +} diff --git a/plugin/src/sbt-test/java-runtime/max-line-length/src/test/java-expected-80/com/lightbend/LineLengthTest.java b/plugin/src/sbt-test/java-runtime/max-line-length/src/test/java-expected-80/com/lightbend/LineLengthTest.java new file mode 100644 index 0000000..fa1ca24 --- /dev/null +++ b/plugin/src/sbt-test/java-runtime/max-line-length/src/test/java-expected-80/com/lightbend/LineLengthTest.java @@ -0,0 +1,7 @@ +package com.lightbend; + +public class LineLengthTest { + public boolean check(String firstValue, String secondValue) { + return new LineLength().containsBoth(firstValue, secondValue); + } +} diff --git a/plugin/src/sbt-test/java-runtime/max-line-length/src/test/java/com/lightbend/LineLengthTest.java b/plugin/src/sbt-test/java-runtime/max-line-length/src/test/java/com/lightbend/LineLengthTest.java new file mode 100644 index 0000000..998d79d --- /dev/null +++ b/plugin/src/sbt-test/java-runtime/max-line-length/src/test/java/com/lightbend/LineLengthTest.java @@ -0,0 +1,5 @@ +package com.lightbend; + +public class LineLengthTest { + public boolean check(String firstValue, String secondValue) {return new LineLength().containsBoth(firstValue, secondValue);} +} diff --git a/plugin/src/sbt-test/java-runtime/max-line-length/test b/plugin/src/sbt-test/java-runtime/max-line-length/test new file mode 100644 index 0000000..316d428 --- /dev/null +++ b/plugin/src/sbt-test/java-runtime/max-line-length/test @@ -0,0 +1,55 @@ +# Test 120-column string reflow before an earlier narrow width can split the literal. +> set ThisBuild / javafmtMaxLineLength := 120 +> javafmtAll +> javafmtCheckAll +$ exec diff src/main/java/com/lightbend/LineLength.java src/main/java-expected-fresh-120/com/lightbend/LineLength.java + +# Restore unsplit input before testing width changes against warm caches. +$ copy-file src/main/java-original/com/lightbend/LineLength.java src/main/java/com/lightbend/LineLength.java +> set ThisBuild / javafmtMaxLineLength := 100 +> javafmtAll +> javafmtCheckAll +$ exec diff src/main/java/com/lightbend/LineLength.java src/main/java-expected-100/com/lightbend/LineLength.java + +# Setting changes must invalidate successful formatting and check results without touching sources. +> set ThisBuild / javafmtMaxLineLength := 80 +> javafmtFixImportsAll +> javafmtFixImportsCheckAll +$ exec diff src/main/java/com/lightbend/LineLength.java src/main/java-expected-100/com/lightbend/LineLength.java +-> javafmtCheckAll +-> javafmtCheckAll +> set ThisBuild / javafmtMaxLineLength := 100 +> javafmtCheckAll +> set ThisBuild / javafmtMaxLineLength := 80 +-> javafmtCheckAll +> javafmtAll +> javafmtCheckAll +$ exec diff src/main/java/com/lightbend/LineLength.java src/main/java-expected-80/com/lightbend/LineLength.java +$ exec diff src/test/java/com/lightbend/LineLengthTest.java src/test/java-expected-80/com/lightbend/LineLengthTest.java + +# Widening the layout must also discard cached failures from the previous setting. +> set ThisBuild / javafmtMaxLineLength := 120 +-> javafmtCheckAll +> javafmtAll +> javafmtCheckAll +$ exec diff src/main/java/com/lightbend/LineLength.java src/main/java-expected-120/com/lightbend/LineLength.java + +> set ThisBuild / javafmtStyle := com.google.googlejavaformat.java.JavaFormatterOptions.Style.AOSP +-> javafmtCheckAll +> javafmtAll +> javafmtCheckAll +$ exec diff src/main/java/com/lightbend/LineLength.java src/main/java-expected-aosp-120/com/lightbend/LineLength.java + +# The compatibility options setting must also participate in cache invalidation. +> set Compile / javafmtOptions := com.google.googlejavaformat.java.JavaFormatterOptions.builder().style(com.google.googlejavaformat.java.JavaFormatterOptions.Style.GOOGLE).build() +-> Compile / javafmtCheck +> Compile / javafmt +> Compile / javafmtCheck +$ exec diff src/main/java/com/lightbend/LineLength.java src/main/java-expected-120/com/lightbend/LineLength.java + +> set ThisBuild / javafmtStyle := com.google.googlejavaformat.java.JavaFormatterOptions.Style.GOOGLE +> set ThisBuild / javafmtMaxLineLength := 100 +> set ThisBuild / javafmtOnCompile := true +> Test / compile +> javafmtCheckAll +$ exec diff src/main/java/com/lightbend/LineLength.java src/main/java-expected-restored-100/com/lightbend/LineLength.java diff --git a/plugin/src/sbt-test/java-runtime/reorder-modifiers/src/main/java-expected-reordered/com/lightbend/BadFormatting.java b/plugin/src/sbt-test/java-runtime/reorder-modifiers/src/main/java-expected-reordered/com/lightbend/BadFormatting.java new file mode 100644 index 0000000..5fac172 --- /dev/null +++ b/plugin/src/sbt-test/java-runtime/reorder-modifiers/src/main/java-expected-reordered/com/lightbend/BadFormatting.java @@ -0,0 +1,5 @@ +package com.lightbend; + +public final class BadFormatting { + public static void main(String[] args) {} +} diff --git a/plugin/src/sbt-test/java-runtime/reorder-modifiers/test b/plugin/src/sbt-test/java-runtime/reorder-modifiers/test index dd5c8d8..4f074cc 100644 --- a/plugin/src/sbt-test/java-runtime/reorder-modifiers/test +++ b/plugin/src/sbt-test/java-runtime/reorder-modifiers/test @@ -4,5 +4,13 @@ $ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected/com/lightbend/BadFormatting.java +> set ThisBuild / javafmtReorderModifiers := true +-> javafmtCheck +> javafmt +> javafmtCheck +$ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected-reordered/com/lightbend/BadFormatting.java + +> set ThisBuild / javafmtReorderModifiers := false + > set ThisBuild / javafmtFormatterCompatibleJavaVersion := 17 -> javafmtCheck diff --git a/plugin/src/sbt-test/sbt-java-formatter/aosp-style/src/main/java-expected-google/com/lightbend/BadFormatting.java b/plugin/src/sbt-test/sbt-java-formatter/aosp-style/src/main/java-expected-google/com/lightbend/BadFormatting.java new file mode 100644 index 0000000..871b223 --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/aosp-style/src/main/java-expected-google/com/lightbend/BadFormatting.java @@ -0,0 +1,9 @@ +package com.lightbend; + +public class BadFormatting { + BadFormatting() { + example(); + } + + public void example() {} +} diff --git a/plugin/src/sbt-test/sbt-java-formatter/aosp-style/src/test/java-expected-google/com/lightbend/BadFormatting.java b/plugin/src/sbt-test/sbt-java-formatter/aosp-style/src/test/java-expected-google/com/lightbend/BadFormatting.java new file mode 100644 index 0000000..871b223 --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/aosp-style/src/test/java-expected-google/com/lightbend/BadFormatting.java @@ -0,0 +1,9 @@ +package com.lightbend; + +public class BadFormatting { + BadFormatting() { + example(); + } + + public void example() {} +} diff --git a/plugin/src/sbt-test/sbt-java-formatter/aosp-style/test b/plugin/src/sbt-test/sbt-java-formatter/aosp-style/test index 20edd74..ea19282 100644 --- a/plugin/src/sbt-test/sbt-java-formatter/aosp-style/test +++ b/plugin/src/sbt-test/sbt-java-formatter/aosp-style/test @@ -10,3 +10,18 @@ $ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected/com/lightbend/BadFormatting.java $ exec diff src/test/java/com/lightbend/BadFormatting.java src/test/java-expected/com/lightbend/BadFormatting.java + +> javafmtCheckAll +> set ThisBuild / javafmtStyle := com.google.googlejavaformat.java.JavaFormatterOptions.Style.GOOGLE +-> javafmtCheckAll +> Test / compile +> javafmtCheckAll +$ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected-google/com/lightbend/BadFormatting.java +$ exec diff src/test/java/com/lightbend/BadFormatting.java src/test/java-expected-google/com/lightbend/BadFormatting.java + +> set ThisBuild / javafmtStyle := com.google.googlejavaformat.java.JavaFormatterOptions.Style.AOSP +-> javafmtCheckAll +> javafmtAll +> javafmtCheckAll +$ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected/com/lightbend/BadFormatting.java +$ exec diff src/test/java/com/lightbend/BadFormatting.java src/test/java-expected/com/lightbend/BadFormatting.java diff --git a/plugin/src/sbt-test/sbt-java-formatter/cache-import-options/build.sbt b/plugin/src/sbt-test/sbt-java-formatter/cache-import-options/build.sbt new file mode 100644 index 0000000..6621578 --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/cache-import-options/build.sbt @@ -0,0 +1,3 @@ +ThisBuild / javafmtFormatterCompatibleJavaVersion := 11 +ThisBuild / javafmtSortImports := false +ThisBuild / javafmtRemoveUnusedImports := false diff --git a/plugin/src/sbt-test/sbt-java-formatter/cache-import-options/project/plugins.sbt b/plugin/src/sbt-test/sbt-java-formatter/cache-import-options/project/plugins.sbt new file mode 100644 index 0000000..6e37f4a --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/cache-import-options/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.github.sbt" % "sbt-java-formatter" % System.getProperty("plugin.version")) diff --git a/plugin/src/sbt-test/sbt-java-formatter/cache-import-options/src/main/java-expected-sorted/com/lightbend/BadFormatting.java b/plugin/src/sbt-test/sbt-java-formatter/cache-import-options/src/main/java-expected-sorted/com/lightbend/BadFormatting.java new file mode 100644 index 0000000..f9c7da7 --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/cache-import-options/src/main/java-expected-sorted/com/lightbend/BadFormatting.java @@ -0,0 +1,9 @@ +package com.lightbend; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class BadFormatting { + BadFormatting () {List xs = new ArrayList<>();} +} diff --git a/plugin/src/sbt-test/sbt-java-formatter/cache-import-options/src/main/java-expected/com/lightbend/BadFormatting.java b/plugin/src/sbt-test/sbt-java-formatter/cache-import-options/src/main/java-expected/com/lightbend/BadFormatting.java new file mode 100644 index 0000000..2847200 --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/cache-import-options/src/main/java-expected/com/lightbend/BadFormatting.java @@ -0,0 +1,8 @@ +package com.lightbend; + +import java.util.ArrayList; +import java.util.List; + +public class BadFormatting { + BadFormatting () {List xs = new ArrayList<>();} +} diff --git a/plugin/src/sbt-test/sbt-java-formatter/cache-import-options/src/main/java/com/lightbend/BadFormatting.java b/plugin/src/sbt-test/sbt-java-formatter/cache-import-options/src/main/java/com/lightbend/BadFormatting.java new file mode 100644 index 0000000..76953fc --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/cache-import-options/src/main/java/com/lightbend/BadFormatting.java @@ -0,0 +1,9 @@ +package com.lightbend; + +import java.util.List; +import java.util.Collections; +import java.util.ArrayList; + +public class BadFormatting { + BadFormatting () {List xs = new ArrayList<>();} +} diff --git a/plugin/src/sbt-test/sbt-java-formatter/cache-import-options/test b/plugin/src/sbt-test/sbt-java-formatter/cache-import-options/test new file mode 100644 index 0000000..1bb3b98 --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/cache-import-options/test @@ -0,0 +1,16 @@ +> javafmtFixImports +> javafmtFixImportsCheck + +> set ThisBuild / javafmtSortImports := true +-> javafmtFixImportsCheck +> javafmtFixImports +> javafmtFixImportsCheck +$ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected-sorted/com/lightbend/BadFormatting.java + +> set ThisBuild / javafmtRemoveUnusedImports := true +-> javafmtFixImportsCheck +> javafmtFixImports +> javafmtFixImportsCheck +$ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected/com/lightbend/BadFormatting.java + +-> javafmtCheck diff --git a/plugin/src/sbt-test/sbt-java-formatter/cache-modes/build.sbt b/plugin/src/sbt-test/sbt-java-formatter/cache-modes/build.sbt new file mode 100644 index 0000000..40bd009 --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/cache-modes/build.sbt @@ -0,0 +1 @@ +ThisBuild / javafmtFormatterCompatibleJavaVersion := 11 diff --git a/plugin/src/sbt-test/sbt-java-formatter/cache-modes/project/plugins.sbt b/plugin/src/sbt-test/sbt-java-formatter/cache-modes/project/plugins.sbt new file mode 100644 index 0000000..6e37f4a --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/cache-modes/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.github.sbt" % "sbt-java-formatter" % System.getProperty("plugin.version")) diff --git a/plugin/src/sbt-test/sbt-java-formatter/cache-modes/src/main/java-expected-imports/com/lightbend/BadFormatting.java b/plugin/src/sbt-test/sbt-java-formatter/cache-modes/src/main/java-expected-imports/com/lightbend/BadFormatting.java new file mode 100644 index 0000000..2847200 --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/cache-modes/src/main/java-expected-imports/com/lightbend/BadFormatting.java @@ -0,0 +1,8 @@ +package com.lightbend; + +import java.util.ArrayList; +import java.util.List; + +public class BadFormatting { + BadFormatting () {List xs = new ArrayList<>();} +} diff --git a/plugin/src/sbt-test/sbt-java-formatter/cache-modes/src/main/java-expected/com/lightbend/BadFormatting.java b/plugin/src/sbt-test/sbt-java-formatter/cache-modes/src/main/java-expected/com/lightbend/BadFormatting.java new file mode 100644 index 0000000..78cb04d --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/cache-modes/src/main/java-expected/com/lightbend/BadFormatting.java @@ -0,0 +1,10 @@ +package com.lightbend; + +import java.util.ArrayList; +import java.util.List; + +public class BadFormatting { + BadFormatting() { + List xs = new ArrayList<>(); + } +} diff --git a/plugin/src/sbt-test/sbt-java-formatter/cache-modes/src/main/java/com/lightbend/BadFormatting.java b/plugin/src/sbt-test/sbt-java-formatter/cache-modes/src/main/java/com/lightbend/BadFormatting.java new file mode 100644 index 0000000..76953fc --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/cache-modes/src/main/java/com/lightbend/BadFormatting.java @@ -0,0 +1,9 @@ +package com.lightbend; + +import java.util.List; +import java.util.Collections; +import java.util.ArrayList; + +public class BadFormatting { + BadFormatting () {List xs = new ArrayList<>();} +} diff --git a/plugin/src/sbt-test/sbt-java-formatter/cache-modes/test b/plugin/src/sbt-test/sbt-java-formatter/cache-modes/test new file mode 100644 index 0000000..372cddc --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/cache-modes/test @@ -0,0 +1,25 @@ +-> javafmtCheck +-> javafmtFixImportsCheck +> javafmtFixImports +$ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected-imports/com/lightbend/BadFormatting.java + +> javafmtFixImportsCheck +-> javafmtCheck +> javafmtFixImportsCheck +> javafmtFixImports +-> javafmtCheck + +> javafmt +> javafmtCheck +> javafmtFixImportsCheck +$ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected/com/lightbend/BadFormatting.java + +# Changing JVM arguments must not reuse a successful formatter result. +> set ThisBuild / javafmtJavaMaxHeap := Some("invalid") +-> javafmtCheck +-> javafmt +-> javafmtFixImportsCheck +-> javafmtFixImports +> set ThisBuild / javafmtJavaMaxHeap := Some("256m") +> javafmtCheck +> javafmtFixImportsCheck diff --git a/plugin/src/sbt-test/sbt-java-formatter/imports-only-formatting-options/build.sbt b/plugin/src/sbt-test/sbt-java-formatter/imports-only-formatting-options/build.sbt new file mode 100644 index 0000000..c4d4586 --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/imports-only-formatting-options/build.sbt @@ -0,0 +1,36 @@ +import sbt.util.CacheImplicits._ + +ThisBuild / javafmtFormatterCompatibleJavaVersion := 11 + +@transient +val rememberImportsCache = taskKey[Unit]("Remember the effective import-only cache input.") +@transient +val expectSameImportsCache = taskKey[Unit]("Check that full-formatting settings do not affect the import-only cache.") +val expectedCheckMessage = settingKey[String]("Expected full-format check error.") +@transient +val expectCheckFailure = taskKey[Unit]("Verify the reason the full-format check fails.") + +expectedCheckMessage := "1 file must be formatted" + +expectCheckFailure := { + val expected = expectedCheckMessage.value + javafmtCheckAll.result.value.toEither match { + case Left(incomplete) => + val messages = Incomplete.allExceptions(incomplete).flatMap(error => Option(error.getMessage)) + assert(messages.exists(_.contains(expected)), s"Expected '$expected', got: ${messages.mkString("; ")}") + case Right(_) => sys.error(s"Expected failure: $expected") + } +} + +rememberImportsCache := { + val hash = (Compile / javafmt / streams).value.cacheStoreFactory + .sub("imports-only").make("inputs").read[Long]() + IO.write(target.value / "imports-cache-input", hash.toString) +} + +expectSameImportsCache := { + val hash = (Compile / javafmt / streams).value.cacheStoreFactory + .sub("imports-only").make("inputs").read[Long]() + assert(hash.toString == IO.read(target.value / "imports-cache-input"), + "Full-formatting settings changed the import-only cache input.") +} diff --git a/plugin/src/sbt-test/sbt-java-formatter/imports-only-formatting-options/project/plugins.sbt b/plugin/src/sbt-test/sbt-java-formatter/imports-only-formatting-options/project/plugins.sbt new file mode 100644 index 0000000..6e37f4a --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/imports-only-formatting-options/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.github.sbt" % "sbt-java-formatter" % System.getProperty("plugin.version")) diff --git a/plugin/src/sbt-test/sbt-java-formatter/imports-only-formatting-options/src/main/java-expected/com/lightbend/BadFormatting.java b/plugin/src/sbt-test/sbt-java-formatter/imports-only-formatting-options/src/main/java-expected/com/lightbend/BadFormatting.java new file mode 100644 index 0000000..34fe550 --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/imports-only-formatting-options/src/main/java-expected/com/lightbend/BadFormatting.java @@ -0,0 +1,10 @@ +package com.lightbend; + +import java.util.ArrayList; +import java.util.List; + +/** This long documentation comment must be left untouched when fixing imports, regardless of the configured line length and Javadoc formatting options. */ +final public class BadFormatting { + private static final String MESSAGE = "This long string must be left untouched when fixing imports, regardless of the configured line length and string reflow options."; + BadFormatting () {List xs = new ArrayList<>();} +} diff --git a/plugin/src/sbt-test/sbt-java-formatter/imports-only-formatting-options/src/main/java/com/lightbend/BadFormatting.java b/plugin/src/sbt-test/sbt-java-formatter/imports-only-formatting-options/src/main/java/com/lightbend/BadFormatting.java new file mode 100644 index 0000000..ea54ef5 --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/imports-only-formatting-options/src/main/java/com/lightbend/BadFormatting.java @@ -0,0 +1,11 @@ +package com.lightbend; + +import java.util.List; +import java.util.Collections; +import java.util.ArrayList; + +/** This long documentation comment must be left untouched when fixing imports, regardless of the configured line length and Javadoc formatting options. */ +final public class BadFormatting { + private static final String MESSAGE = "This long string must be left untouched when fixing imports, regardless of the configured line length and string reflow options."; + BadFormatting () {List xs = new ArrayList<>();} +} diff --git a/plugin/src/sbt-test/sbt-java-formatter/imports-only-formatting-options/test b/plugin/src/sbt-test/sbt-java-formatter/imports-only-formatting-options/test new file mode 100644 index 0000000..3180c67 --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/imports-only-formatting-options/test @@ -0,0 +1,43 @@ +> javafmtFixImportsAll +> javafmtFixImportsCheckAll +$ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected/com/lightbend/BadFormatting.java +> rememberImportsCache + +# Unsupported full-formatting flags must not be passed to the Java 11 formatter. +> set ThisBuild / javafmtMaxLineLength := 120 +> set ThisBuild / javafmtReorderModifiers := false +> set ThisBuild / javafmtFormatJavadoc := false +> set ThisBuild / javafmtReflowLongStrings := false +> javafmtFixImportsCheckAll +> javafmtFixImportsAll +> expectSameImportsCache +$ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected/com/lightbend/BadFormatting.java +> set expectedCheckMessage := "A custom javafmtMaxLineLength requires ThisBuild / javafmtFormatterCompatibleJavaVersion := 21" +> expectCheckFailure + +# Irrelevant invalid widths must not be validated or invalidate the import-only cache. +> set ThisBuild / javafmtMaxLineLength := 0 +> javafmtFixImportsCheckAll +> javafmtFixImportsAll +> expectSameImportsCache +> set ThisBuild / javafmtMaxLineLength := -1 +> javafmtFixImportsCheckAll +> javafmtFixImportsAll +> expectSameImportsCache + +# With a cold cache, import-only formatting must still work with these settings. +> clean +> javafmtFixImportsAll +> javafmtFixImportsCheckAll +$ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected/com/lightbend/BadFormatting.java + +> set ThisBuild / javafmtMaxLineLength := 100 +> set expectedCheckMessage := "Disabling modifier reordering requires ThisBuild / javafmtFormatterCompatibleJavaVersion := 21" +> expectCheckFailure + +# With valid full-formatting settings, the imports-only result must still need formatting. +> set ThisBuild / javafmtReorderModifiers := true +> set ThisBuild / javafmtFormatJavadoc := true +> set ThisBuild / javafmtReflowLongStrings := true +> set expectedCheckMessage := "1 file must be formatted" +> expectCheckFailure diff --git a/plugin/src/sbt-test/sbt-java-formatter/java-home-symlink/build.sbt b/plugin/src/sbt-test/sbt-java-formatter/java-home-symlink/build.sbt new file mode 100644 index 0000000..e7a7590 --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/java-home-symlink/build.sbt @@ -0,0 +1,76 @@ +import java.nio.file.Files + +ThisBuild / javafmtFormatterCompatibleJavaVersion := 11 + +@transient +val useValidJavaHome = taskKey[Unit]("Point the formatter Java-home alias to the original JDK.") +@transient +val useMissingJavaLauncher = taskKey[Unit]("Retarget the alias without changing the configured property.") +@transient +val restoreJavaHome = taskKey[Unit]("Restore the original formatter Java-home property.") +@transient +val expectFormatFailure = taskKey[Unit]("Verify that the format cache follows the retargeted alias.") +@transient +val expectCheckFailure = taskKey[Unit]("Verify that the check cache follows the retargeted alias.") +@transient +val expectImportsFailure = taskKey[Unit]("Verify that the import-only format cache follows the retargeted alias.") +@transient +val expectImportsCheckFailure = taskKey[Unit]("Verify that the import-only check cache follows the retargeted alias.") + +val originalJavaHomeProperty = sys.props.get("sbt-javafmt.java.home") +val originalJavaLauncher = { + val home = new File(originalJavaHomeProperty.filter(_.nonEmpty) + .orElse(sys.env.get("SBT_JAVAFMT_JAVA_HOME").filter(_.nonEmpty)) + .getOrElse(sys.props("java.home"))) + val unixJava = new File(home, "bin/java") + val launcher = if (unixJava.isFile) unixJava else new File(home, "bin/java.exe") + assert(launcher.isFile, "The original formatter Java launcher must exist.") + launcher.getAbsolutePath +} + +useValidJavaHome := { + val alias = (baseDirectory.value / "formatter-home").toPath + val validHome = baseDirectory.value / "valid-home" + val launcher = validHome / "bin" / "java" + IO.createDirectory(launcher.getParentFile) + val quotedJava = "'" + originalJavaLauncher.replace("'", "'\"'\"'") + "'" + IO.write(launcher, "#!/bin/sh\nexec " + quotedJava + " \"$@\"\n") + assert(launcher.setExecutable(true), "The fixture launcher must be executable.") + Files.deleteIfExists(alias) + // Directory symlinks stay inside the fixture: sbt cleanup can recursively follow them. + Files.createSymbolicLink(alias, validHome.toPath) + val _ = sys.props.put("sbt-javafmt.java.home", alias.toString) +} + +useMissingJavaLauncher := { + val alias = (baseDirectory.value / "formatter-home").toPath + val missingLauncher = baseDirectory.value / "missing-launcher" + IO.createDirectory(missingLauncher) + Files.delete(alias) + Files.createSymbolicLink(alias, missingLauncher.toPath) + assert(sys.props("sbt-javafmt.java.home") == alias.toString, + "Retargeting must not change the configured Java-home path.") +} + +restoreJavaHome := { + val base = baseDirectory.value + Files.deleteIfExists((base / "formatter-home").toPath) + originalJavaHomeProperty match { + case Some(path) => sys.props.put("sbt-javafmt.java.home", path) + case None => sys.props.remove("sbt-javafmt.java.home") + } + assert(new File(originalJavaLauncher).isFile, "The real Java launcher must remain untouched.") +} + +def assertMissingLauncher[A](result: Result[A]): Unit = result.toEither match { + case Left(incomplete) => + val messages = Incomplete.allExceptions(incomplete).flatMap(error => Option(error.getMessage)) + assert(messages.exists(_.contains("Could not locate a Java launcher under sbt-javafmt.java.home=")), + "Expected a missing launcher error, got: " + messages.mkString("; ")) + case Right(_) => sys.error("Expected the retargeted Java-home alias to invalidate cached success.") +} + +expectFormatFailure := assertMissingLauncher((Compile / javafmt).result.value) +expectCheckFailure := assertMissingLauncher((Compile / javafmtCheck).result.value) +expectImportsFailure := assertMissingLauncher((Compile / javafmtFixImports).result.value) +expectImportsCheckFailure := assertMissingLauncher((Compile / javafmtFixImportsCheck).result.value) diff --git a/plugin/src/sbt-test/sbt-java-formatter/java-home-symlink/project/plugins.sbt b/plugin/src/sbt-test/sbt-java-formatter/java-home-symlink/project/plugins.sbt new file mode 100644 index 0000000..6e37f4a --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/java-home-symlink/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.github.sbt" % "sbt-java-formatter" % System.getProperty("plugin.version")) diff --git a/plugin/src/sbt-test/sbt-java-formatter/java-home-symlink/src/main/java/com/lightbend/Example.java b/plugin/src/sbt-test/sbt-java-formatter/java-home-symlink/src/main/java/com/lightbend/Example.java new file mode 100644 index 0000000..3f04050 --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/java-home-symlink/src/main/java/com/lightbend/Example.java @@ -0,0 +1,3 @@ +package com.lightbend; + +public class Example {} diff --git a/plugin/src/sbt-test/sbt-java-formatter/java-home-symlink/test b/plugin/src/sbt-test/sbt-java-formatter/java-home-symlink/test new file mode 100644 index 0000000..397e85c --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/java-home-symlink/test @@ -0,0 +1,20 @@ +> useValidJavaHome +> javafmtAll +> javafmtCheckAll +> javafmtFixImportsAll +> javafmtFixImportsCheckAll + +# The property and source stamps stay unchanged; only the symlink target changes. +> useMissingJavaLauncher +> expectCheckFailure +> expectFormatFailure +> expectImportsCheckFailure +> expectImportsFailure + +# Repointing the same alias to the valid JDK must also allow recovery. +> useValidJavaHome +> javafmtAll +> javafmtCheckAll +> javafmtFixImportsAll +> javafmtFixImportsCheckAll +> restoreJavaHome diff --git a/plugin/src/sbt-test/sbt-java-formatter/max-line-length-validation/build.sbt b/plugin/src/sbt-test/sbt-java-formatter/max-line-length-validation/build.sbt new file mode 100644 index 0000000..f629b38 --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/max-line-length-validation/build.sbt @@ -0,0 +1,20 @@ +ThisBuild / javafmtFormatterCompatibleJavaVersion := 11 + +val expectedMessage = settingKey[String]("Expected validation error.") +val expectDefaultLineLength = taskKey[Unit]("Verify the default formatter line length.") +val expectFormatFailure = taskKey[Unit]("Verify the formatting validation error.") +val expectCheckFailure = taskKey[Unit]("Verify the check validation error.") + +expectedMessage := s"javafmtMaxLineLength must be positive, but was ${javafmtMaxLineLength.value}." + +expectDefaultLineLength := assert(javafmtMaxLineLength.value == 100) + +def assertFailure[A](result: Result[A], expected: String): Unit = result.toEither match { + case Left(incomplete) => + val messages = Incomplete.allExceptions(incomplete).flatMap(error => Option(error.getMessage)) + assert(messages.exists(_.contains(expected)), s"Expected '$expected', got: ${messages.mkString("; ")}") + case Right(_) => sys.error(s"Expected failure: $expected") +} + +expectFormatFailure := assertFailure((Compile / javafmt).result.value, expectedMessage.value) +expectCheckFailure := assertFailure((Compile / javafmtCheck).result.value, expectedMessage.value) diff --git a/plugin/src/sbt-test/sbt-java-formatter/max-line-length-validation/project/plugins.sbt b/plugin/src/sbt-test/sbt-java-formatter/max-line-length-validation/project/plugins.sbt new file mode 100644 index 0000000..6e37f4a --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/max-line-length-validation/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.github.sbt" % "sbt-java-formatter" % System.getProperty("plugin.version")) diff --git a/plugin/src/sbt-test/sbt-java-formatter/max-line-length-validation/src/main/java/com/lightbend/Example.java b/plugin/src/sbt-test/sbt-java-formatter/max-line-length-validation/src/main/java/com/lightbend/Example.java new file mode 100644 index 0000000..3f04050 --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/max-line-length-validation/src/main/java/com/lightbend/Example.java @@ -0,0 +1,3 @@ +package com.lightbend; + +public class Example {} diff --git a/plugin/src/sbt-test/sbt-java-formatter/max-line-length-validation/test b/plugin/src/sbt-test/sbt-java-formatter/max-line-length-validation/test new file mode 100644 index 0000000..4858332 --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/max-line-length-validation/test @@ -0,0 +1,31 @@ +> expectDefaultLineLength +> javafmt +> javafmtCheck + +> set ThisBuild / javafmtMaxLineLength := 0 +> expectFormatFailure +> expectCheckFailure +> javafmtFixImports +> javafmtFixImportsCheck + +> set ThisBuild / javafmtMaxLineLength := -1 +> expectFormatFailure +> expectCheckFailure +> javafmtFixImports +> javafmtFixImportsCheck + +> set ThisBuild / javafmtMaxLineLength := 120 +> set expectedMessage := "A custom javafmtMaxLineLength requires ThisBuild / javafmtFormatterCompatibleJavaVersion := 21" +> expectFormatFailure +> expectCheckFailure +> javafmtFixImports +> javafmtFixImportsCheck + +> set ThisBuild / javafmtFormatterCompatibleJavaVersion := 17 +> expectFormatFailure +> expectCheckFailure + +> set ThisBuild / javafmtFormatterCompatibleJavaVersion := 11 +> set ThisBuild / javafmtMaxLineLength := 100 +> javafmt +> javafmtCheck diff --git a/plugin/src/sbt-test/sbt-java-formatter/no-sources-invalid-java-home/build.sbt b/plugin/src/sbt-test/sbt-java-formatter/no-sources-invalid-java-home/build.sbt new file mode 100644 index 0000000..fd7ac3e --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/no-sources-invalid-java-home/build.sbt @@ -0,0 +1,43 @@ +ThisBuild / javafmtFormatterCompatibleJavaVersion := 11 +ThisBuild / javafmtOnCompile := true + +lazy val root = project.in(file(".")).aggregate(scalaOnly) +lazy val scalaOnly = project.in(file("scala-only")) + +val useInvalidJavaHome = taskKey[Unit]("Select a missing formatter Java installation.") +@transient +val useUnresolvableJavaHome = taskKey[Unit]("Select a Java-home path that cannot be resolved.") +val restoreJavaHome = taskKey[Unit]("Restore the original formatter Java installation.") +val addJavaSource = taskKey[Unit]("Add a source that requires the formatter JVM.") +val expectMissingJavaLauncher = taskKey[Unit]("Verify that formatting still validates the launcher.") + +val originalJavaHomeProperty = sys.props.get("sbt-javafmt.java.home") + +useInvalidJavaHome := { + val _ = sys.props.put("sbt-javafmt.java.home", (baseDirectory.value / "missing-jdk").getAbsolutePath) +} + +useUnresolvableJavaHome := { + val invalidPath = (baseDirectory.value / "missing-jdk").getAbsolutePath + 0.toChar + val _ = sys.props.put("sbt-javafmt.java.home", invalidPath) +} + +restoreJavaHome := { + originalJavaHomeProperty match { + case Some(path) => sys.props.put("sbt-javafmt.java.home", path) + case None => sys.props.remove("sbt-javafmt.java.home") + } + () +} + +addJavaSource := IO.write(baseDirectory.value / "src/main/java/Example.java", "class Example { }\n") + +expectMissingJavaLauncher := { + (Compile / javafmt).result.value.toEither match { + case Left(incomplete) => + val messages = Incomplete.allExceptions(incomplete).flatMap(error => Option(error.getMessage)) + assert(messages.exists(_.contains("Could not locate a Java launcher under sbt-javafmt.java.home=")), + "Expected a missing launcher error, got: " + messages.mkString("; ")) + case Right(_) => sys.error("Expected formatting to fail for the missing Java launcher.") + } +} diff --git a/plugin/src/sbt-test/sbt-java-formatter/no-sources-invalid-java-home/project/plugins.sbt b/plugin/src/sbt-test/sbt-java-formatter/no-sources-invalid-java-home/project/plugins.sbt new file mode 100644 index 0000000..6e37f4a --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/no-sources-invalid-java-home/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.github.sbt" % "sbt-java-formatter" % System.getProperty("plugin.version")) diff --git a/plugin/src/sbt-test/sbt-java-formatter/no-sources-invalid-java-home/scala-only/src/main/scala/Example.scala b/plugin/src/sbt-test/sbt-java-formatter/no-sources-invalid-java-home/scala-only/src/main/scala/Example.scala new file mode 100644 index 0000000..4172d0d --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/no-sources-invalid-java-home/scala-only/src/main/scala/Example.scala @@ -0,0 +1,3 @@ +object Example { + val answer = 42 +} diff --git a/plugin/src/sbt-test/sbt-java-formatter/no-sources-invalid-java-home/test b/plugin/src/sbt-test/sbt-java-formatter/no-sources-invalid-java-home/test new file mode 100644 index 0000000..fce668d --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/no-sources-invalid-java-home/test @@ -0,0 +1,24 @@ +# Invalid paths must be tolerated just like missing installations when there are no Java sources. +> useUnresolvableJavaHome +> javafmtAll +> javafmtCheckAll +> javafmtFixImportsAll +> javafmtFixImportsCheckAll +> compile +> Test / compile + +> useInvalidJavaHome +> javafmtAll +> javafmtCheckAll +> javafmtFixImportsAll +> javafmtFixImportsCheckAll +> compile +> Test / compile + +# A real Java source must still report the invalid installation. +> addJavaSource +> expectMissingJavaLauncher +> restoreJavaHome +> javafmtAll +> javafmtCheckAll +> compile diff --git a/plugin/src/sbt-test/sbt-java-formatter/skip-javadoc-formatting/src/main/java-expected-enabled/com/lightbend/BadFormatting.java b/plugin/src/sbt-test/sbt-java-formatter/skip-javadoc-formatting/src/main/java-expected-enabled/com/lightbend/BadFormatting.java new file mode 100644 index 0000000..27e64cc --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/skip-javadoc-formatting/src/main/java-expected-enabled/com/lightbend/BadFormatting.java @@ -0,0 +1,6 @@ +package com.lightbend; + +/** hello world */ +public class BadFormatting { + public void example() {} +} diff --git a/plugin/src/sbt-test/sbt-java-formatter/skip-javadoc-formatting/test b/plugin/src/sbt-test/sbt-java-formatter/skip-javadoc-formatting/test index 3b4753f..ad4bb99 100644 --- a/plugin/src/sbt-test/sbt-java-formatter/skip-javadoc-formatting/test +++ b/plugin/src/sbt-test/sbt-java-formatter/skip-javadoc-formatting/test @@ -2,3 +2,11 @@ > javafmt $ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected/com/lightbend/BadFormatting.java + +# Changing an option must invalidate the cache even when sources are unchanged. +> javafmtCheck +> set ThisBuild / javafmtFormatJavadoc := true +-> javafmtCheck +> javafmt +> javafmtCheck +$ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected-enabled/com/lightbend/BadFormatting.java diff --git a/plugin/src/sbt-test/sbt-java-formatter/skip-reflowing-long-strings/src/main/java-expected-enabled/com/lightbend/BadFormatting.java b/plugin/src/sbt-test/sbt-java-formatter/skip-reflowing-long-strings/src/main/java-expected-enabled/com/lightbend/BadFormatting.java new file mode 100644 index 0000000..d3574aa --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/skip-reflowing-long-strings/src/main/java-expected-enabled/com/lightbend/BadFormatting.java @@ -0,0 +1,7 @@ +package com.lightbend; + +public class BadFormatting { + private final String value = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut" + + " labore et dolore magna aliqua."; +} diff --git a/plugin/src/sbt-test/sbt-java-formatter/skip-reflowing-long-strings/test b/plugin/src/sbt-test/sbt-java-formatter/skip-reflowing-long-strings/test index 060e8d2..c823154 100644 --- a/plugin/src/sbt-test/sbt-java-formatter/skip-reflowing-long-strings/test +++ b/plugin/src/sbt-test/sbt-java-formatter/skip-reflowing-long-strings/test @@ -2,3 +2,11 @@ > javafmt $ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected/com/lightbend/BadFormatting.java + +# Changing an option must invalidate the cache even when sources are unchanged. +> javafmtCheck +> set ThisBuild / javafmtReflowLongStrings := true +-> javafmtCheck +> javafmt +> javafmtCheck +$ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected-enabled/com/lightbend/BadFormatting.java diff --git a/plugin/src/sbt-test/sbt-java-formatter/skip-removing-unused-imports/src/main/java-expected-enabled/com/lightbend/BadFormatting.java b/plugin/src/sbt-test/sbt-java-formatter/skip-removing-unused-imports/src/main/java-expected-enabled/com/lightbend/BadFormatting.java new file mode 100644 index 0000000..b2394ee --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/skip-removing-unused-imports/src/main/java-expected-enabled/com/lightbend/BadFormatting.java @@ -0,0 +1,7 @@ +package com.lightbend; + +import java.util.ArrayList; + +public class BadFormatting { + private final ArrayList xs = new ArrayList<>(); +} diff --git a/plugin/src/sbt-test/sbt-java-formatter/skip-removing-unused-imports/test b/plugin/src/sbt-test/sbt-java-formatter/skip-removing-unused-imports/test index 060e8d2..2afe0d3 100644 --- a/plugin/src/sbt-test/sbt-java-formatter/skip-removing-unused-imports/test +++ b/plugin/src/sbt-test/sbt-java-formatter/skip-removing-unused-imports/test @@ -2,3 +2,11 @@ > javafmt $ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected/com/lightbend/BadFormatting.java + +# Changing an option must invalidate the cache even when sources are unchanged. +> javafmtCheck +> set ThisBuild / javafmtRemoveUnusedImports := true +-> javafmtCheck +> javafmt +> javafmtCheck +$ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected-enabled/com/lightbend/BadFormatting.java diff --git a/plugin/src/sbt-test/sbt-java-formatter/skip-sorting-imports/src/main/java-expected-enabled/com/lightbend/BadFormatting.java b/plugin/src/sbt-test/sbt-java-formatter/skip-sorting-imports/src/main/java-expected-enabled/com/lightbend/BadFormatting.java new file mode 100644 index 0000000..8cc8212 --- /dev/null +++ b/plugin/src/sbt-test/sbt-java-formatter/skip-sorting-imports/src/main/java-expected-enabled/com/lightbend/BadFormatting.java @@ -0,0 +1,8 @@ +package com.lightbend; + +import java.util.ArrayList; +import java.util.List; + +public class BadFormatting { + private final List xs = new ArrayList<>(); +} diff --git a/plugin/src/sbt-test/sbt-java-formatter/skip-sorting-imports/test b/plugin/src/sbt-test/sbt-java-formatter/skip-sorting-imports/test index 060e8d2..b663fad 100644 --- a/plugin/src/sbt-test/sbt-java-formatter/skip-sorting-imports/test +++ b/plugin/src/sbt-test/sbt-java-formatter/skip-sorting-imports/test @@ -2,3 +2,11 @@ > javafmt $ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected/com/lightbend/BadFormatting.java + +# Changing an option must invalidate the cache even when sources are unchanged. +> javafmtCheck +> set ThisBuild / javafmtSortImports := true +-> javafmtCheck +> javafmt +> javafmtCheck +$ exec diff src/main/java/com/lightbend/BadFormatting.java src/main/java-expected-enabled/com/lightbend/BadFormatting.java