diff --git a/README.md b/README.md index 9398a511e..e858fe591 100644 --- a/README.md +++ b/README.md @@ -182,12 +182,16 @@ shortcut. ![Install plugin from disk](./docs/images/install_plugin_from_disk.png) -## Java 21 Support +## Java 26 Support -In [1211](https://github.com/palantir/palantir-java-format/pull/1211) we shipped Java 21 support. In order to use the -Java 21 formatting capabilities, ensure that either: +In [1211](https://github.com/palantir/palantir-java-format/pull/1211) we shipped Java 21 support. Since then we've +kept up with the language and now also support formatting the newer syntax introduced up through Java 26, including +module import declarations, compact source files and instance main methods, flexible constructor bodies, markdown +documentation comments, and unnamed patterns in deconstruction. In order to use these formatting capabilities, ensure +that either: -- the Gradle daemon and the Intellij Project SDK are set to Java 21 +- the Gradle daemon and the Intellij Project SDK are set to Java 26 (or, at minimum, to the JDK version in which the + feature you're relying on was finalized) - or that the gradle property `palantir.native.formatter=true`. This will run the formatter as a native image, - independent of the Gradle daemon/Intellij project JDK version. diff --git a/build.gradle b/build.gradle index af55b7e26..785718632 100644 --- a/build.gradle +++ b/build.gradle @@ -81,10 +81,43 @@ javaVersions { jdks { daemonTarget = 21 + jdk(22) { + distribution = 'azul-zulu' + jdkVersion = '22.32.15-22.0.2' + } + jdk(23) { distribution = 'graalvm-ce' jdkVersion = '23.0.1' } + jdk(25) { + distribution = 'azul-zulu' + jdkVersion = '25.32.21-25.0.2' + + // The bundled com.palantir.jdks.latest defaults (applied earlier in this file) + // pre-populate major version 25 with amazon-corretto, pinned per-OS/arch. JdkExtension + // configuration actions accumulate rather than replace, so those per-arch jdkVersion + // values are hard-set before this block runs and don't pick up the top-level override + // above. Re-pin every OS/arch explicitly to the desired Zulu build. + ['macos', 'linux-glibc', 'linux-musl', 'windows'].each { osName -> + os(osName) { + ['x86', 'x86-64', 'aarch64'].each { archName -> + arch(archName) { + jdkVersion = '25.32.21-25.0.2' + } + } + } + } + } + + jdk(26) { + distribution = 'azul-zulu' + jdkVersion = '26.30.11-26.0.1' + } + + jdkMajorVersionsToUse().add(JavaLanguageVersion.of("22")) jdkMajorVersionsToUse().add(JavaLanguageVersion.of("23")) + jdkMajorVersionsToUse().add(JavaLanguageVersion.of("25")) + jdkMajorVersionsToUse().add(JavaLanguageVersion.of("26")) } diff --git a/gradle/jdks/22/linux-glibc/aarch64/download-url b/gradle/jdks/22/linux-glibc/aarch64/download-url new file mode 100644 index 000000000..ebcf219b5 --- /dev/null +++ b/gradle/jdks/22/linux-glibc/aarch64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu22.32.15-ca-jdk22.0.2-linux_aarch64.tar.gz diff --git a/gradle/jdks/22/linux-glibc/aarch64/local-path b/gradle/jdks/22/linux-glibc/aarch64/local-path new file mode 100644 index 000000000..675f9344d --- /dev/null +++ b/gradle/jdks/22/linux-glibc/aarch64/local-path @@ -0,0 +1 @@ +azul-zulu-22.32.15-22.0.2-glibc diff --git a/gradle/jdks/22/linux-glibc/x86-64/download-url b/gradle/jdks/22/linux-glibc/x86-64/download-url new file mode 100644 index 000000000..12925be39 --- /dev/null +++ b/gradle/jdks/22/linux-glibc/x86-64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu22.32.15-ca-jdk22.0.2-linux_x64.tar.gz diff --git a/gradle/jdks/22/linux-glibc/x86-64/local-path b/gradle/jdks/22/linux-glibc/x86-64/local-path new file mode 100644 index 000000000..675f9344d --- /dev/null +++ b/gradle/jdks/22/linux-glibc/x86-64/local-path @@ -0,0 +1 @@ +azul-zulu-22.32.15-22.0.2-glibc diff --git a/gradle/jdks/22/linux-glibc/x86/download-url b/gradle/jdks/22/linux-glibc/x86/download-url new file mode 100644 index 000000000..760b58771 --- /dev/null +++ b/gradle/jdks/22/linux-glibc/x86/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu22.32.15-ca-jdk22.0.2-linux_i686.tar.gz diff --git a/gradle/jdks/22/linux-glibc/x86/local-path b/gradle/jdks/22/linux-glibc/x86/local-path new file mode 100644 index 000000000..675f9344d --- /dev/null +++ b/gradle/jdks/22/linux-glibc/x86/local-path @@ -0,0 +1 @@ +azul-zulu-22.32.15-22.0.2-glibc diff --git a/gradle/jdks/22/linux-musl/aarch64/download-url b/gradle/jdks/22/linux-musl/aarch64/download-url new file mode 100644 index 000000000..f90d012b7 --- /dev/null +++ b/gradle/jdks/22/linux-musl/aarch64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu22.32.15-ca-jdk22.0.2-linux_musl_aarch64.tar.gz diff --git a/gradle/jdks/22/linux-musl/aarch64/local-path b/gradle/jdks/22/linux-musl/aarch64/local-path new file mode 100644 index 000000000..e6f386f1d --- /dev/null +++ b/gradle/jdks/22/linux-musl/aarch64/local-path @@ -0,0 +1 @@ +azul-zulu-22.32.15-22.0.2-musl diff --git a/gradle/jdks/22/linux-musl/x86-64/download-url b/gradle/jdks/22/linux-musl/x86-64/download-url new file mode 100644 index 000000000..4a4149970 --- /dev/null +++ b/gradle/jdks/22/linux-musl/x86-64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu22.32.15-ca-jdk22.0.2-linux_musl_x64.tar.gz diff --git a/gradle/jdks/22/linux-musl/x86-64/local-path b/gradle/jdks/22/linux-musl/x86-64/local-path new file mode 100644 index 000000000..e6f386f1d --- /dev/null +++ b/gradle/jdks/22/linux-musl/x86-64/local-path @@ -0,0 +1 @@ +azul-zulu-22.32.15-22.0.2-musl diff --git a/gradle/jdks/22/linux-musl/x86/download-url b/gradle/jdks/22/linux-musl/x86/download-url new file mode 100644 index 000000000..b36072a01 --- /dev/null +++ b/gradle/jdks/22/linux-musl/x86/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu22.32.15-ca-jdk22.0.2-linux_musl_i686.tar.gz diff --git a/gradle/jdks/22/linux-musl/x86/local-path b/gradle/jdks/22/linux-musl/x86/local-path new file mode 100644 index 000000000..e6f386f1d --- /dev/null +++ b/gradle/jdks/22/linux-musl/x86/local-path @@ -0,0 +1 @@ +azul-zulu-22.32.15-22.0.2-musl diff --git a/gradle/jdks/22/macos/aarch64/download-url b/gradle/jdks/22/macos/aarch64/download-url new file mode 100644 index 000000000..45ee86890 --- /dev/null +++ b/gradle/jdks/22/macos/aarch64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu22.32.15-ca-jdk22.0.2-macosx_aarch64.zip diff --git a/gradle/jdks/22/macos/aarch64/local-path b/gradle/jdks/22/macos/aarch64/local-path new file mode 100644 index 000000000..345aa67b2 --- /dev/null +++ b/gradle/jdks/22/macos/aarch64/local-path @@ -0,0 +1 @@ +azul-zulu-22.32.15-22.0.2 diff --git a/gradle/jdks/22/macos/x86-64/download-url b/gradle/jdks/22/macos/x86-64/download-url new file mode 100644 index 000000000..cd3cd9e96 --- /dev/null +++ b/gradle/jdks/22/macos/x86-64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu22.32.15-ca-jdk22.0.2-macosx_x64.zip diff --git a/gradle/jdks/22/macos/x86-64/local-path b/gradle/jdks/22/macos/x86-64/local-path new file mode 100644 index 000000000..345aa67b2 --- /dev/null +++ b/gradle/jdks/22/macos/x86-64/local-path @@ -0,0 +1 @@ +azul-zulu-22.32.15-22.0.2 diff --git a/gradle/jdks/22/macos/x86/download-url b/gradle/jdks/22/macos/x86/download-url new file mode 100644 index 000000000..cb109acc8 --- /dev/null +++ b/gradle/jdks/22/macos/x86/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu22.32.15-ca-jdk22.0.2-macosx_i686.zip diff --git a/gradle/jdks/22/macos/x86/local-path b/gradle/jdks/22/macos/x86/local-path new file mode 100644 index 000000000..345aa67b2 --- /dev/null +++ b/gradle/jdks/22/macos/x86/local-path @@ -0,0 +1 @@ +azul-zulu-22.32.15-22.0.2 diff --git a/gradle/jdks/22/windows/aarch64/download-url b/gradle/jdks/22/windows/aarch64/download-url new file mode 100644 index 000000000..9391ec9e9 --- /dev/null +++ b/gradle/jdks/22/windows/aarch64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu22.32.15-ca-jdk22.0.2-win_aarch64.zip diff --git a/gradle/jdks/22/windows/aarch64/local-path b/gradle/jdks/22/windows/aarch64/local-path new file mode 100644 index 000000000..345aa67b2 --- /dev/null +++ b/gradle/jdks/22/windows/aarch64/local-path @@ -0,0 +1 @@ +azul-zulu-22.32.15-22.0.2 diff --git a/gradle/jdks/22/windows/x86-64/download-url b/gradle/jdks/22/windows/x86-64/download-url new file mode 100644 index 000000000..f18afcc9b --- /dev/null +++ b/gradle/jdks/22/windows/x86-64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu22.32.15-ca-jdk22.0.2-win_x64.zip diff --git a/gradle/jdks/22/windows/x86-64/local-path b/gradle/jdks/22/windows/x86-64/local-path new file mode 100644 index 000000000..345aa67b2 --- /dev/null +++ b/gradle/jdks/22/windows/x86-64/local-path @@ -0,0 +1 @@ +azul-zulu-22.32.15-22.0.2 diff --git a/gradle/jdks/22/windows/x86/download-url b/gradle/jdks/22/windows/x86/download-url new file mode 100644 index 000000000..9890b09c5 --- /dev/null +++ b/gradle/jdks/22/windows/x86/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu22.32.15-ca-jdk22.0.2-win_i686.zip diff --git a/gradle/jdks/22/windows/x86/local-path b/gradle/jdks/22/windows/x86/local-path new file mode 100644 index 000000000..345aa67b2 --- /dev/null +++ b/gradle/jdks/22/windows/x86/local-path @@ -0,0 +1 @@ +azul-zulu-22.32.15-22.0.2 diff --git a/gradle/jdks/25/linux-glibc/aarch64/download-url b/gradle/jdks/25/linux-glibc/aarch64/download-url new file mode 100644 index 000000000..3bde8cd3f --- /dev/null +++ b/gradle/jdks/25/linux-glibc/aarch64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu25.32.21-ca-jdk25.0.2-linux_aarch64.tar.gz diff --git a/gradle/jdks/25/linux-glibc/aarch64/local-path b/gradle/jdks/25/linux-glibc/aarch64/local-path new file mode 100644 index 000000000..ff5679715 --- /dev/null +++ b/gradle/jdks/25/linux-glibc/aarch64/local-path @@ -0,0 +1 @@ +azul-zulu-25.32.21-25.0.2-glibc diff --git a/gradle/jdks/25/linux-glibc/x86-64/download-url b/gradle/jdks/25/linux-glibc/x86-64/download-url new file mode 100644 index 000000000..917813c83 --- /dev/null +++ b/gradle/jdks/25/linux-glibc/x86-64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu25.32.21-ca-jdk25.0.2-linux_x64.tar.gz diff --git a/gradle/jdks/25/linux-glibc/x86-64/local-path b/gradle/jdks/25/linux-glibc/x86-64/local-path new file mode 100644 index 000000000..ff5679715 --- /dev/null +++ b/gradle/jdks/25/linux-glibc/x86-64/local-path @@ -0,0 +1 @@ +azul-zulu-25.32.21-25.0.2-glibc diff --git a/gradle/jdks/25/linux-glibc/x86/download-url b/gradle/jdks/25/linux-glibc/x86/download-url new file mode 100644 index 000000000..77f9d7b31 --- /dev/null +++ b/gradle/jdks/25/linux-glibc/x86/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu25.32.21-ca-jdk25.0.2-linux_i686.tar.gz diff --git a/gradle/jdks/25/linux-glibc/x86/local-path b/gradle/jdks/25/linux-glibc/x86/local-path new file mode 100644 index 000000000..ff5679715 --- /dev/null +++ b/gradle/jdks/25/linux-glibc/x86/local-path @@ -0,0 +1 @@ +azul-zulu-25.32.21-25.0.2-glibc diff --git a/gradle/jdks/25/linux-musl/aarch64/download-url b/gradle/jdks/25/linux-musl/aarch64/download-url new file mode 100644 index 000000000..85214b695 --- /dev/null +++ b/gradle/jdks/25/linux-musl/aarch64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu25.32.21-ca-jdk25.0.2-linux_musl_aarch64.tar.gz diff --git a/gradle/jdks/25/linux-musl/aarch64/local-path b/gradle/jdks/25/linux-musl/aarch64/local-path new file mode 100644 index 000000000..ebac669dd --- /dev/null +++ b/gradle/jdks/25/linux-musl/aarch64/local-path @@ -0,0 +1 @@ +azul-zulu-25.32.21-25.0.2-musl diff --git a/gradle/jdks/25/linux-musl/x86-64/download-url b/gradle/jdks/25/linux-musl/x86-64/download-url new file mode 100644 index 000000000..6f1edc957 --- /dev/null +++ b/gradle/jdks/25/linux-musl/x86-64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu25.32.21-ca-jdk25.0.2-linux_musl_x64.tar.gz diff --git a/gradle/jdks/25/linux-musl/x86-64/local-path b/gradle/jdks/25/linux-musl/x86-64/local-path new file mode 100644 index 000000000..ebac669dd --- /dev/null +++ b/gradle/jdks/25/linux-musl/x86-64/local-path @@ -0,0 +1 @@ +azul-zulu-25.32.21-25.0.2-musl diff --git a/gradle/jdks/25/linux-musl/x86/download-url b/gradle/jdks/25/linux-musl/x86/download-url new file mode 100644 index 000000000..2bf982c93 --- /dev/null +++ b/gradle/jdks/25/linux-musl/x86/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu25.32.21-ca-jdk25.0.2-linux_musl_i686.tar.gz diff --git a/gradle/jdks/25/linux-musl/x86/local-path b/gradle/jdks/25/linux-musl/x86/local-path new file mode 100644 index 000000000..ebac669dd --- /dev/null +++ b/gradle/jdks/25/linux-musl/x86/local-path @@ -0,0 +1 @@ +azul-zulu-25.32.21-25.0.2-musl diff --git a/gradle/jdks/25/macos/aarch64/download-url b/gradle/jdks/25/macos/aarch64/download-url new file mode 100644 index 000000000..aa5b68d6e --- /dev/null +++ b/gradle/jdks/25/macos/aarch64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu25.32.21-ca-jdk25.0.2-macosx_aarch64.zip diff --git a/gradle/jdks/25/macos/aarch64/local-path b/gradle/jdks/25/macos/aarch64/local-path new file mode 100644 index 000000000..5227b59b2 --- /dev/null +++ b/gradle/jdks/25/macos/aarch64/local-path @@ -0,0 +1 @@ +azul-zulu-25.32.21-25.0.2 diff --git a/gradle/jdks/25/macos/x86-64/download-url b/gradle/jdks/25/macos/x86-64/download-url new file mode 100644 index 000000000..281116f91 --- /dev/null +++ b/gradle/jdks/25/macos/x86-64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu25.32.21-ca-jdk25.0.2-macosx_x64.zip diff --git a/gradle/jdks/25/macos/x86-64/local-path b/gradle/jdks/25/macos/x86-64/local-path new file mode 100644 index 000000000..5227b59b2 --- /dev/null +++ b/gradle/jdks/25/macos/x86-64/local-path @@ -0,0 +1 @@ +azul-zulu-25.32.21-25.0.2 diff --git a/gradle/jdks/25/macos/x86/download-url b/gradle/jdks/25/macos/x86/download-url new file mode 100644 index 000000000..d2c5c3f9e --- /dev/null +++ b/gradle/jdks/25/macos/x86/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu25.32.21-ca-jdk25.0.2-macosx_i686.zip diff --git a/gradle/jdks/25/macos/x86/local-path b/gradle/jdks/25/macos/x86/local-path new file mode 100644 index 000000000..5227b59b2 --- /dev/null +++ b/gradle/jdks/25/macos/x86/local-path @@ -0,0 +1 @@ +azul-zulu-25.32.21-25.0.2 diff --git a/gradle/jdks/25/windows/aarch64/download-url b/gradle/jdks/25/windows/aarch64/download-url new file mode 100644 index 000000000..2b0265ac9 --- /dev/null +++ b/gradle/jdks/25/windows/aarch64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu25.32.21-ca-jdk25.0.2-win_aarch64.zip diff --git a/gradle/jdks/25/windows/aarch64/local-path b/gradle/jdks/25/windows/aarch64/local-path new file mode 100644 index 000000000..5227b59b2 --- /dev/null +++ b/gradle/jdks/25/windows/aarch64/local-path @@ -0,0 +1 @@ +azul-zulu-25.32.21-25.0.2 diff --git a/gradle/jdks/25/windows/x86-64/download-url b/gradle/jdks/25/windows/x86-64/download-url new file mode 100644 index 000000000..d21da7ffd --- /dev/null +++ b/gradle/jdks/25/windows/x86-64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu25.32.21-ca-jdk25.0.2-win_x64.zip diff --git a/gradle/jdks/25/windows/x86-64/local-path b/gradle/jdks/25/windows/x86-64/local-path new file mode 100644 index 000000000..5227b59b2 --- /dev/null +++ b/gradle/jdks/25/windows/x86-64/local-path @@ -0,0 +1 @@ +azul-zulu-25.32.21-25.0.2 diff --git a/gradle/jdks/25/windows/x86/download-url b/gradle/jdks/25/windows/x86/download-url new file mode 100644 index 000000000..9f6d9f9a2 --- /dev/null +++ b/gradle/jdks/25/windows/x86/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu25.32.21-ca-jdk25.0.2-win_i686.zip diff --git a/gradle/jdks/25/windows/x86/local-path b/gradle/jdks/25/windows/x86/local-path new file mode 100644 index 000000000..5227b59b2 --- /dev/null +++ b/gradle/jdks/25/windows/x86/local-path @@ -0,0 +1 @@ +azul-zulu-25.32.21-25.0.2 diff --git a/gradle/jdks/26/linux-glibc/aarch64/download-url b/gradle/jdks/26/linux-glibc/aarch64/download-url new file mode 100644 index 000000000..95944f7a9 --- /dev/null +++ b/gradle/jdks/26/linux-glibc/aarch64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu26.30.11-ca-jdk26.0.1-linux_aarch64.tar.gz diff --git a/gradle/jdks/26/linux-glibc/aarch64/local-path b/gradle/jdks/26/linux-glibc/aarch64/local-path new file mode 100644 index 000000000..02da9293e --- /dev/null +++ b/gradle/jdks/26/linux-glibc/aarch64/local-path @@ -0,0 +1 @@ +azul-zulu-26.30.11-26.0.1-glibc diff --git a/gradle/jdks/26/linux-glibc/x86-64/download-url b/gradle/jdks/26/linux-glibc/x86-64/download-url new file mode 100644 index 000000000..3da44a7ae --- /dev/null +++ b/gradle/jdks/26/linux-glibc/x86-64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu26.30.11-ca-jdk26.0.1-linux_x64.tar.gz diff --git a/gradle/jdks/26/linux-glibc/x86-64/local-path b/gradle/jdks/26/linux-glibc/x86-64/local-path new file mode 100644 index 000000000..02da9293e --- /dev/null +++ b/gradle/jdks/26/linux-glibc/x86-64/local-path @@ -0,0 +1 @@ +azul-zulu-26.30.11-26.0.1-glibc diff --git a/gradle/jdks/26/linux-glibc/x86/download-url b/gradle/jdks/26/linux-glibc/x86/download-url new file mode 100644 index 000000000..3a135a15e --- /dev/null +++ b/gradle/jdks/26/linux-glibc/x86/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu26.30.11-ca-jdk26.0.1-linux_i686.tar.gz diff --git a/gradle/jdks/26/linux-glibc/x86/local-path b/gradle/jdks/26/linux-glibc/x86/local-path new file mode 100644 index 000000000..02da9293e --- /dev/null +++ b/gradle/jdks/26/linux-glibc/x86/local-path @@ -0,0 +1 @@ +azul-zulu-26.30.11-26.0.1-glibc diff --git a/gradle/jdks/26/linux-musl/aarch64/download-url b/gradle/jdks/26/linux-musl/aarch64/download-url new file mode 100644 index 000000000..a40fab332 --- /dev/null +++ b/gradle/jdks/26/linux-musl/aarch64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu26.30.11-ca-jdk26.0.1-linux_musl_aarch64.tar.gz diff --git a/gradle/jdks/26/linux-musl/aarch64/local-path b/gradle/jdks/26/linux-musl/aarch64/local-path new file mode 100644 index 000000000..581f02be5 --- /dev/null +++ b/gradle/jdks/26/linux-musl/aarch64/local-path @@ -0,0 +1 @@ +azul-zulu-26.30.11-26.0.1-musl diff --git a/gradle/jdks/26/linux-musl/x86-64/download-url b/gradle/jdks/26/linux-musl/x86-64/download-url new file mode 100644 index 000000000..00bfc67e8 --- /dev/null +++ b/gradle/jdks/26/linux-musl/x86-64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu26.30.11-ca-jdk26.0.1-linux_musl_x64.tar.gz diff --git a/gradle/jdks/26/linux-musl/x86-64/local-path b/gradle/jdks/26/linux-musl/x86-64/local-path new file mode 100644 index 000000000..581f02be5 --- /dev/null +++ b/gradle/jdks/26/linux-musl/x86-64/local-path @@ -0,0 +1 @@ +azul-zulu-26.30.11-26.0.1-musl diff --git a/gradle/jdks/26/linux-musl/x86/download-url b/gradle/jdks/26/linux-musl/x86/download-url new file mode 100644 index 000000000..1f2d71b05 --- /dev/null +++ b/gradle/jdks/26/linux-musl/x86/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu26.30.11-ca-jdk26.0.1-linux_musl_i686.tar.gz diff --git a/gradle/jdks/26/linux-musl/x86/local-path b/gradle/jdks/26/linux-musl/x86/local-path new file mode 100644 index 000000000..581f02be5 --- /dev/null +++ b/gradle/jdks/26/linux-musl/x86/local-path @@ -0,0 +1 @@ +azul-zulu-26.30.11-26.0.1-musl diff --git a/gradle/jdks/26/macos/aarch64/download-url b/gradle/jdks/26/macos/aarch64/download-url new file mode 100644 index 000000000..d3866121a --- /dev/null +++ b/gradle/jdks/26/macos/aarch64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu26.30.11-ca-jdk26.0.1-macosx_aarch64.zip diff --git a/gradle/jdks/26/macos/aarch64/local-path b/gradle/jdks/26/macos/aarch64/local-path new file mode 100644 index 000000000..fa72b6eb5 --- /dev/null +++ b/gradle/jdks/26/macos/aarch64/local-path @@ -0,0 +1 @@ +azul-zulu-26.30.11-26.0.1 diff --git a/gradle/jdks/26/macos/x86-64/download-url b/gradle/jdks/26/macos/x86-64/download-url new file mode 100644 index 000000000..de3944af3 --- /dev/null +++ b/gradle/jdks/26/macos/x86-64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu26.30.11-ca-jdk26.0.1-macosx_x64.zip diff --git a/gradle/jdks/26/macos/x86-64/local-path b/gradle/jdks/26/macos/x86-64/local-path new file mode 100644 index 000000000..fa72b6eb5 --- /dev/null +++ b/gradle/jdks/26/macos/x86-64/local-path @@ -0,0 +1 @@ +azul-zulu-26.30.11-26.0.1 diff --git a/gradle/jdks/26/macos/x86/download-url b/gradle/jdks/26/macos/x86/download-url new file mode 100644 index 000000000..67820c2e1 --- /dev/null +++ b/gradle/jdks/26/macos/x86/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu26.30.11-ca-jdk26.0.1-macosx_i686.zip diff --git a/gradle/jdks/26/macos/x86/local-path b/gradle/jdks/26/macos/x86/local-path new file mode 100644 index 000000000..fa72b6eb5 --- /dev/null +++ b/gradle/jdks/26/macos/x86/local-path @@ -0,0 +1 @@ +azul-zulu-26.30.11-26.0.1 diff --git a/gradle/jdks/26/windows/aarch64/download-url b/gradle/jdks/26/windows/aarch64/download-url new file mode 100644 index 000000000..e5aa35709 --- /dev/null +++ b/gradle/jdks/26/windows/aarch64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu26.30.11-ca-jdk26.0.1-win_aarch64.zip diff --git a/gradle/jdks/26/windows/aarch64/local-path b/gradle/jdks/26/windows/aarch64/local-path new file mode 100644 index 000000000..fa72b6eb5 --- /dev/null +++ b/gradle/jdks/26/windows/aarch64/local-path @@ -0,0 +1 @@ +azul-zulu-26.30.11-26.0.1 diff --git a/gradle/jdks/26/windows/x86-64/download-url b/gradle/jdks/26/windows/x86-64/download-url new file mode 100644 index 000000000..4fc5c3d92 --- /dev/null +++ b/gradle/jdks/26/windows/x86-64/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu26.30.11-ca-jdk26.0.1-win_x64.zip diff --git a/gradle/jdks/26/windows/x86-64/local-path b/gradle/jdks/26/windows/x86-64/local-path new file mode 100644 index 000000000..fa72b6eb5 --- /dev/null +++ b/gradle/jdks/26/windows/x86-64/local-path @@ -0,0 +1 @@ +azul-zulu-26.30.11-26.0.1 diff --git a/gradle/jdks/26/windows/x86/download-url b/gradle/jdks/26/windows/x86/download-url new file mode 100644 index 000000000..6d93dd426 --- /dev/null +++ b/gradle/jdks/26/windows/x86/download-url @@ -0,0 +1 @@ +https://cdn.azul.com/zulu/bin/zulu26.30.11-ca-jdk26.0.1-win_i686.zip diff --git a/gradle/jdks/26/windows/x86/local-path b/gradle/jdks/26/windows/x86/local-path new file mode 100644 index 000000000..fa72b6eb5 --- /dev/null +++ b/gradle/jdks/26/windows/x86/local-path @@ -0,0 +1 @@ +azul-zulu-26.30.11-26.0.1 diff --git a/palantir-java-format/build.gradle b/palantir-java-format/build.gradle index 470e5cbaf..418481119 100644 --- a/palantir-java-format/build.gradle +++ b/palantir-java-format/build.gradle @@ -64,3 +64,45 @@ tasks.named("test") { javaVersion { target = 21 } + +// The default `test` task runs on JDK 21 (see the root `javaVersions.runtime`), which exercises +// the Java21InputAstVisitor dispatch branch and all Java-21-and-earlier golden tests; newer-feature +// golden tests self-skip on JDK 21 via FileBasedTests#assumeJavaVersionForTest. To get *native* +// coverage of the newer dispatch branches (Java25InputAstVisitor, Java26InputAstVisitor) and the +// golden tests gated on newer feature JDKs (MarkdownDoc @ 23, UnnamedPattern @ 22, +// FlexibleConstructor/ModuleImport/CompactSource @ 25), run the same version-gated golden suites +// again on each additional JDK registered in the root `jdks { }` block. The JDK-independent unit +// tests are already covered once by the default JDK-21 `test`, so these extra legs only re-run the +// file-based integration suites. +[22, 23, 25, 26].each { jdkVersion -> + tasks.register("testJdk${jdkVersion}", Test) { + group = 'verification' + description = "Runs the JDK-version-gated golden test suites on JDK ${jdkVersion}." + + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(jdkVersion) + } + + testClassesDirs = sourceSets.test.output.classesDirs + classpath = sourceSets.test.runtimeClasspath + useJUnitPlatform() + systemProperty 'recreate', System.getProperty('recreate', 'false') + + filter { + includeTestsMatching 'com.palantir.javaformat.java.FormatterIntegrationTest' + includeTestsMatching 'com.palantir.javaformat.java.StringWrapperIntegrationTest' + if (jdkVersion >= 25) { + // The module-import case in RemoveUnusedImportsTest guards a JDK-25+-only + // ClassCastException fix for JCModuleImport and self-skips below JDK 25, so it + // needs native coverage on these legs (it's otherwise never exercised by the + // JDK-21 default `test`, which skips it, or by the integration suites above, + // which never call removeUnusedImports). + includeTestsMatching 'com.palantir.javaformat.java.RemoveUnusedImportsTest' + } + } + } +} + +tasks.named('check') { + dependsOn tasks.named('testJdk22'), tasks.named('testJdk23'), tasks.named('testJdk25'), tasks.named('testJdk26') +} diff --git a/palantir-java-format/src/main/java/com/palantir/javaformat/java/Formatter.java b/palantir-java-format/src/main/java/com/palantir/javaformat/java/Formatter.java index ef8dab408..bfe4de0b7 100644 --- a/palantir-java-format/src/main/java/com/palantir/javaformat/java/Formatter.java +++ b/palantir-java-format/src/main/java/com/palantir/javaformat/java/Formatter.java @@ -132,7 +132,11 @@ static JavaOutput format( OpsBuilder opsBuilder = new OpsBuilder(javaInput); JavaInputAstVisitor visitor; - if (getRuntimeVersion() >= 21) { + if (getRuntimeVersion() >= 26) { + visitor = createVisitor("com.palantir.javaformat.java.java26.Java26InputAstVisitor", opsBuilder, options); + } else if (getRuntimeVersion() >= 25) { + visitor = createVisitor("com.palantir.javaformat.java.java25.Java25InputAstVisitor", opsBuilder, options); + } else if (getRuntimeVersion() >= 21) { visitor = createVisitor("com.palantir.javaformat.java.java21.Java21InputAstVisitor", opsBuilder, options); } else if (getRuntimeVersion() >= 14) { visitor = createVisitor("com.palantir.javaformat.java.java14.Java14InputAstVisitor", opsBuilder, options); diff --git a/palantir-java-format/src/main/java/com/palantir/javaformat/java/ImportOrderer.java b/palantir-java-format/src/main/java/com/palantir/javaformat/java/ImportOrderer.java index 1ba037e11..0687c9ae3 100644 --- a/palantir-java-format/src/main/java/com/palantir/javaformat/java/ImportOrderer.java +++ b/palantir-java-format/src/main/java/com/palantir/javaformat/java/ImportOrderer.java @@ -123,16 +123,25 @@ private String reorderImports() throws FormatterException { /** * A {@link Comparator} that orders {@link Import}s by Google Style, defined at * https://google.github.io/styleguide/javaguide.html#s3.3.3-import-ordering-and-spacing. + * + *

Module imports ({@code import module foo.bar;}, JEP 511) are sorted into their own leading group, ahead of + * both static and non-static type imports: they bind whole modules' worth of exported packages, so they read + * most naturally as the broadest, first declarations in the file. */ - private static final Comparator GOOGLE_IMPORT_COMPARATOR = - Comparator.comparing(Import::isStatic, trueFirst()).thenComparing(Import::imported); + private static final Comparator GOOGLE_IMPORT_COMPARATOR = Comparator.comparing( + Import::isModule, trueFirst()) + .thenComparing(Import::isStatic, trueFirst()) + .thenComparing(Import::imported); /** * A {@link Comparator} that orders {@link Import}s by AOSP Style, defined at * https://source.android.com/setup/contribute/code-style#order-import-statements and implemented in IntelliJ at * https://android.googlesource.com/platform/development/+/master/ide/intellij/codestyles/AndroidStyle.xml. + * + *

As with {@link #GOOGLE_IMPORT_COMPARATOR}, module imports sort into their own leading group. */ - private static final Comparator AOSP_IMPORT_COMPARATOR = Comparator.comparing(Import::isStatic, trueFirst()) + private static final Comparator AOSP_IMPORT_COMPARATOR = Comparator.comparing(Import::isModule, trueFirst()) + .thenComparing(Import::isStatic, trueFirst()) .thenComparing(Import::isAndroid, trueFirst()) .thenComparing(Import::isThirdParty, trueFirst()) .thenComparing(Import::isJava, trueFirst()) @@ -143,6 +152,11 @@ private String reorderImports() throws FormatterException { * Google style. */ private static boolean shouldInsertBlankLineGoogle(Import prev, Import curr) { + // Module imports (JEP 511) form their own leading group; separate it from whatever follows, + // the same way static imports are separated from non-static imports below. + if (prev.isModule() && !curr.isModule()) { + return true; + } return prev.isStatic() && !curr.isStatic(); } @@ -151,6 +165,11 @@ private static boolean shouldInsertBlankLineGoogle(Import prev, Import curr) { * style. */ private static boolean shouldInsertBlankLineAosp(Import prev, Import curr) { + // Module imports (JEP 511) form their own leading group; separate it from whatever follows, + // consistent with the other group boundaries below. + if (prev.isModule() && !curr.isModule()) { + return true; + } if (prev.isStatic() && !curr.isStatic()) { return true; } @@ -186,12 +205,14 @@ private ImportOrderer(String text, ImmutableList toks, Style style) { class Import { private final String imported; private final boolean isStatic; + private final boolean isModule; private final String trailing; - Import(String imported, String trailing, boolean isStatic) { + Import(String imported, String trailing, boolean isStatic, boolean isModule) { this.imported = imported; this.trailing = trailing; this.isStatic = isStatic; + this.isModule = isModule; } /** The name being imported, for example {@code java.util.List}. */ @@ -204,6 +225,11 @@ boolean isStatic() { return isStatic; } + /** True if this is {@code import module} (JEP 511). */ + boolean isModule() { + return isModule; + } + /** The top-level package of the import. */ String topLevel() { return DOT_SPLITTER.split(imported()).iterator().next(); @@ -246,7 +272,9 @@ public boolean isThirdParty() { public String toString() { StringBuilder sb = new StringBuilder(); sb.append("import "); - if (isStatic()) { + if (isModule()) { + sb.append("module "); + } else if (isStatic()) { sb.append("static "); } sb.append(imported()).append(';'); @@ -282,7 +310,7 @@ private static class ImportsAndIndex { * *

{@code
      *  -> ( | )*
-     *  -> "import"  ("static" )?
+     *  -> "import"  (("static" | "module") )?
      *     ("." )* ("." "*")? ? ";"
      *    ? ? ( )*
      * }
@@ -302,7 +330,14 @@ private ImportsAndIndex scanImports(int i) throws FormatterException { if (isSpaceToken(i)) { i++; } - boolean isStatic = tokenAt(i).equals("static"); + boolean isModule = isModuleKeyword(i); + if (isModule) { + i++; + if (isSpaceToken(i)) { + i++; + } + } + boolean isStatic = !isModule && tokenAt(i).equals("static"); if (isStatic) { i++; if (isSpaceToken(i)) { @@ -344,7 +379,7 @@ private ImportsAndIndex scanImports(int i) throws FormatterException { i++; } } - imports.add(new Import(importedName, trailing.toString(), isStatic)); + imports.add(new Import(importedName, trailing.toString(), isStatic, isModule)); // Remember the position just after the import we just saw, before skipping blank lines. // If the next thing after the blank lines is not another import then we don't want to // include those blank lines in the text to be replaced. @@ -452,6 +487,25 @@ private String tokenAt(int i) { return toks.get(i).getOriginalText(); } + /** + * Returns true if the token at {@code i} is the {@code module} contextual keyword introducing a module import + * declaration ({@code import module foo.bar;}, JEP 511), as opposed to an ordinary import whose first segment + * happens to be an identifier literally named {@code module} (for example {@code import module.Foo;}). As with + * other contextual keywords ({@code var}, {@code yield}, ...), this is disambiguated by lookahead: {@code + * module} only introduces a module import when it is immediately followed by another identifier, rather than + * {@code .} or {@code ;}. + */ + private boolean isModuleKeyword(int i) { + if (!tokenAt(i).equals("module")) { + return false; + } + int next = i + 1; + if (isSpaceToken(next)) { + next++; + } + return isIdentifierToken(next); + } + private boolean isIdentifierToken(int i) { String s = tokenAt(i); return !s.isEmpty() && Character.isJavaIdentifierStart(s.codePointAt(0)); diff --git a/palantir-java-format/src/main/java/com/palantir/javaformat/java/JavaInputAstVisitor.java b/palantir-java-format/src/main/java/com/palantir/javaformat/java/JavaInputAstVisitor.java index 3917911ee..1c8186440 100644 --- a/palantir-java-format/src/main/java/com/palantir/javaformat/java/JavaInputAstVisitor.java +++ b/palantir-java-format/src/main/java/com/palantir/javaformat/java/JavaInputAstVisitor.java @@ -132,6 +132,7 @@ import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.TreeScanner; +import java.lang.reflect.Method; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; @@ -376,8 +377,15 @@ public Void visitCompilationUnit(CompilationUnitTree node, Void unused) { builder.blankLineWanted(BlankLineWanted.YES); } markForPartialFormat(); - scan(type, null); - builder.forcedBreak(); + if (isCompactSourceFile(type)) { + // JEP 512 (Java 25): a "compact source file" has top-level fields/methods and no + // explicit class declaration. javac models this by synthesizing an implicit wrapper + // class; format its member list directly, at column zero, with no enclosing braces. + addBodyDeclarations(((ClassTree) type).getMembers(), BracesOrNot.NO, FirstDeclarationsOrNot.YES, ZERO); + } else { + scan(type, null); + builder.forcedBreak(); + } first = false; dropEmptyDeclarations(); } @@ -1381,6 +1389,17 @@ public Void visitAnnotatedType(AnnotatedTypeTree node, Void unused) { protected static final long RECORD = 1L << 61; + // TODO: Use Flags.IMPLICIT_CLASS once if/when we drop support for Java 11. javac sets this flag + // on the synthetic wrapper class it generates for a "compact source file" (JEP 512, Java 25): + // top-level fields/methods with no explicit enclosing class declaration. + protected static final long IMPLICIT_CLASS = 1L << 19; + + /** Is {@code type} the implicit wrapper class javac synthesizes for a compact source file? */ + private static boolean isCompactSourceFile(Tree type) { + return type instanceof JCTree.JCClassDecl + && (((JCTree.JCClassDecl) type).mods.flags & IMPLICIT_CLASS) == IMPLICIT_CLASS; + } + @SuppressWarnings("for-rollout:NullAway") @Override public Void visitMethod(MethodTree node, Void unused) { @@ -2609,7 +2628,7 @@ public Void visitUses(UsesTree node, Void unused) { } /** Helper method for import declarations, names, and qualified names. */ - private void visitName(Tree node) { + protected void visitName(Tree node) { Deque stack = new ArrayDeque<>(); for (; node instanceof MemberSelectTree; node = ((MemberSelectTree) node).getExpression()) { stack.addFirst(((MemberSelectTree) node).getIdentifier()); @@ -3741,9 +3760,22 @@ private void declareMany(List fragments, Direction annotationDirec } /** Add a list of declarations. */ - @SuppressWarnings("for-rollout:NullAway") protected void addBodyDeclarations( List bodyDeclarations, BracesOrNot braces, FirstDeclarationsOrNot first0) { + addBodyDeclarations(bodyDeclarations, braces, first0, plusTwo); + } + + /** + * Add a list of declarations, indenting the member list by {@code memberIndent}. Used both for ordinary + * class/interface/enum bodies (indented by {@link #plusTwo}) and for the bare top-level member list of a + * compact source file (JEP 512), which is indented by {@link Indent.Const#ZERO}. + */ + @SuppressWarnings("for-rollout:NullAway") + protected void addBodyDeclarations( + List bodyDeclarations, + BracesOrNot braces, + FirstDeclarationsOrNot first0, + Indent memberIndent) { if (bodyDeclarations.isEmpty()) { if (braces.isYes()) { builder.space(); @@ -3759,7 +3791,7 @@ protected void addBodyDeclarations( tokenBreakTrailingComment("{", plusTwo); builder.open(ZERO, BreakBehaviours.breakThisLevel(), LastLevelBreakability.ACCEPT_INLINE_CHAIN); } - builder.open(plusTwo); + builder.open(memberIndent); boolean first = first0.isYes(); boolean lastOneGotBlankLineBefore = false; PeekingIterator it = Iterators.peekingIterator(bodyDeclarations.iterator()); @@ -3958,6 +3990,28 @@ protected void sync(Tree node) { builder.sync(((JCTree) node).getStartPosition()); } + /** + * Looks up a no-arg method that may not exist on older language levels (e.g. {@code + * ImportTree#isModule()}, added in JDK 23). Used together with {@link #invoke} so that newer AST accessors can + * be reached reflectively without raising the source/target level of this module. + */ + @SuppressWarnings("for-rollout:NullAway") + protected static Method maybeGetMethod(Class c, String name) { + try { + return c.getMethod(name); + } catch (ReflectiveOperationException e) { + return null; + } + } + + protected static Object invoke(Method m, Object target) { + try { + return m.invoke(target); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + @Override public String toString() { return MoreObjects.toStringHelper(this).add("builder", builder).toString(); diff --git a/palantir-java-format/src/main/java/com/palantir/javaformat/java/RemoveUnusedImports.java b/palantir-java-format/src/main/java/com/palantir/javaformat/java/RemoveUnusedImports.java index a66b6b559..3da480bbb 100644 --- a/palantir-java-format/src/main/java/com/palantir/javaformat/java/RemoveUnusedImports.java +++ b/palantir-java-format/src/main/java/com/palantir/javaformat/java/RemoveUnusedImports.java @@ -42,7 +42,6 @@ import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCIdent; -import com.sun.tools.javac.tree.JCTree.JCImport; import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Options; import java.lang.reflect.Method; @@ -226,13 +225,20 @@ private static RangeMap buildReplacements( Set usedNames, Multimap> usedInJavadoc) { RangeMap replacements = TreeRangeMap.create(); - for (JCImport importTree : unit.getImports()) { + // Since JDK 23, JCCompilationUnit#getImports() elements are no longer all JCImport: a module + // import (JEP 511, `import module foo.bar;`) parses to the sibling node JCModuleImport, which is + // not a JCImport subtype, so an element typed JCImport here would throw ClassCastException. + // JCTree is the common supertype of both and still exposes the position accessors we need below; + // ImportTree (the public, stable API both node kinds implement) gives us getQualifiedIdentifier() + // and isModule(). + for (JCTree importDecl : unit.getImports()) { + ImportTree importTree = (ImportTree) importDecl; String simpleName = getSimpleName(importTree); if (!isUnused(unit, usedNames, usedInJavadoc, importTree, simpleName)) { continue; } // delete the import - int endPosition = importTree.getEndPosition(unit.endPositions); + int endPosition = importDecl.getEndPosition(unit.endPositions); endPosition = Math.max(CharMatcher.isNot(' ').indexIn(contents, endPosition), endPosition); String sep = Newlines.guessLineSeparator(contents); if (endPosition + sep.length() < contents.length() @@ -241,11 +247,36 @@ private static RangeMap buildReplacements( .equals(sep)) { endPosition += sep.length(); } - replacements.put(Range.closedOpen(importTree.getStartPosition(), endPosition), ""); + replacements.put(Range.closedOpen(importDecl.getStartPosition(), endPosition), ""); } return replacements; } + // ImportTree#isModule() (JEP 511, module import declarations) was added in JDK 23. This module + // compiles at an older --release, so it can't be referenced directly and is reached reflectively, + // matching the CASE_TREE_GET_LABELS idiom above. + private static final Method IMPORT_TREE_IS_MODULE = importTreeIsModule(); + + @SuppressWarnings("for-rollout:NullAway") + private static Method importTreeIsModule() { + try { + return ImportTree.class.getMethod("isModule"); + } catch (NoSuchMethodException e) { + return null; + } + } + + private static boolean isModuleImport(ImportTree importTree) { + if (IMPORT_TREE_IS_MODULE == null) { + return false; + } + try { + return Boolean.TRUE.equals(IMPORT_TREE_IS_MODULE.invoke(importTree)); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e.getMessage(), e); + } + } + private static String getSimpleName(ImportTree importTree) { return importTree.getQualifiedIdentifier() instanceof JCIdent ? ((JCIdent) importTree.getQualifiedIdentifier()).getName().toString() @@ -260,6 +291,13 @@ private static boolean isUnused( Multimap> usedInJavadoc, ImportTree importTree, String simpleName) { + if (isModuleImport(importTree)) { + // A module import (JEP 511, `import module foo.bar;`) binds every exported package of the + // module by wildcard. This scanner only records simple names actually referenced in the + // source, so it has no way to tell whether any particular module import is needed - same + // situation as a `.*` wildcard import below. Never remove it. + return false; + } String qualifier = ((JCFieldAccess) importTree.getQualifiedIdentifier()) .getExpression() .toString(); diff --git a/palantir-java-format/src/main/java/com/palantir/javaformat/java/java14/Java14InputAstVisitor.java b/palantir-java-format/src/main/java/com/palantir/javaformat/java/java14/Java14InputAstVisitor.java index 76bb0d578..24ab861a0 100644 --- a/palantir-java-format/src/main/java/com/palantir/javaformat/java/java14/Java14InputAstVisitor.java +++ b/palantir-java-format/src/main/java/com/palantir/javaformat/java/java14/Java14InputAstVisitor.java @@ -351,23 +351,6 @@ public Void visitLambdaExpression(LambdaExpressionTree node, Void unused) { return null; } - @SuppressWarnings("for-rollout:NullAway") - private static Method maybeGetMethod(Class c, String name) { - try { - return c.getMethod(name); - } catch (ReflectiveOperationException e) { - return null; - } - } - - private static Object invoke(Method m, Object target) { - try { - return m.invoke(target); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e.getMessage(), e); - } - } - @SuppressWarnings({"NullableProblems", "for-rollout:NullAway"}) protected ExpressionTree getGuard(final CaseTree node) { return null; diff --git a/palantir-java-format/src/main/java/com/palantir/javaformat/java/java21/Java21InputAstVisitor.java b/palantir-java-format/src/main/java/com/palantir/javaformat/java/java21/Java21InputAstVisitor.java index 5f089c4e6..559138e8c 100644 --- a/palantir-java-format/src/main/java/com/palantir/javaformat/java/java21/Java21InputAstVisitor.java +++ b/palantir-java-format/src/main/java/com/palantir/javaformat/java/java21/Java21InputAstVisitor.java @@ -25,6 +25,7 @@ import com.sun.source.tree.ExpressionTree; import com.sun.source.tree.PatternCaseLabelTree; import com.sun.source.tree.PatternTree; +import com.sun.source.tree.Tree; import javax.lang.model.element.Name; /** @@ -33,10 +34,33 @@ */ @SuppressWarnings("Since21") public class Java21InputAstVisitor extends Java14InputAstVisitor { + // AnyPatternTree (the unnamed pattern `_`, JEP 456) remains a preview API through JDK 21 and is + // only finalized in JDK 22, so it cannot be referenced by type here without breaking compilation + // on JDK 21 toolchains. Detect it by Tree.Kind name instead, and short-circuit dispatch before it + // reaches the (unimplementable) visitAnyPattern method, whose default no-op implementation would + // otherwise silently drop the `_` token and corrupt the output. + private static final String ANY_PATTERN_KIND_NAME = "ANY_PATTERN"; + public Java21InputAstVisitor(OpsBuilder builder, int indentMultiplier) { super(builder, indentMultiplier); } + @Override + public Void scan(Tree tree, Void unused) { + if (tree != null && tree.getKind().name().equals(ANY_PATTERN_KIND_NAME)) { + // Deliberately not calling sync(tree) here: javac's parser records an off-by-one start + // position for AnyPatternTree (one past the `_` character), which would make sync() think + // a token was skipped and throw. token("_") alone is sufficient since it matches against + // the next pending input token regardless of position. Returning here instead of falling + // through to super.scan() also bypasses its inExpression tracking, checkClosed check, and + // exception-wrapping logic; that's benign here because this branch only ever emits a single + // leaf token("_") and never recurses into child trees. + token("_"); + return null; + } + return super.scan(tree, null); + } + @Override protected ExpressionTree getGuard(final CaseTree node) { return node.getGuard(); diff --git a/palantir-java-format/src/main/java/com/palantir/javaformat/java/java25/Java25InputAstVisitor.java b/palantir-java-format/src/main/java/com/palantir/javaformat/java/java25/Java25InputAstVisitor.java new file mode 100644 index 000000000..d45a554d1 --- /dev/null +++ b/palantir-java-format/src/main/java/com/palantir/javaformat/java/java25/Java25InputAstVisitor.java @@ -0,0 +1,55 @@ +/* + * (c) Copyright 2026 Palantir Technologies Inc. All rights reserved. + * + * 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.palantir.javaformat.java.java25; + +import com.palantir.javaformat.OpsBuilder; +import com.palantir.javaformat.java.java21.Java21InputAstVisitor; +import com.sun.source.tree.ImportTree; +import java.lang.reflect.Method; + +/** + * Extends {@link Java21InputAstVisitor} with support for AST nodes that were added or modified in + * Java 25. + */ +public class Java25InputAstVisitor extends Java21InputAstVisitor { + // ImportTree#isModule() was added in JDK 23 (JEP 511, module import declarations, GA in Java + // 25). This module compiles at an older --release, so it can't be referenced directly. + private static final Method IMPORT_TREE_IS_MODULE = maybeGetMethod(ImportTree.class, "isModule"); + + public Java25InputAstVisitor(OpsBuilder builder, int indentMultiplier) { + super(builder, indentMultiplier); + } + + @Override + public Void visitImport(ImportTree node, Void unused) { + sync(node); + token("import"); + builder.space(); + if (IMPORT_TREE_IS_MODULE != null && Boolean.TRUE.equals(invoke(IMPORT_TREE_IS_MODULE, node))) { + token("module"); + builder.space(); + } else if (node.isStatic()) { + token("static"); + builder.space(); + } + visitName(node.getQualifiedIdentifier()); + token(";"); + // TODO(cushon): remove this if https://bugs.openjdk.java.net/browse/JDK-8027682 is fixed + dropEmptyDeclarations(); + return null; + } +} diff --git a/palantir-java-format/src/main/java/com/palantir/javaformat/java/java26/Java26InputAstVisitor.java b/palantir-java-format/src/main/java/com/palantir/javaformat/java/java26/Java26InputAstVisitor.java new file mode 100644 index 000000000..5a2877932 --- /dev/null +++ b/palantir-java-format/src/main/java/com/palantir/javaformat/java/java26/Java26InputAstVisitor.java @@ -0,0 +1,30 @@ +/* + * (c) Copyright 2026 Palantir Technologies Inc. All rights reserved. + * + * 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.palantir.javaformat.java.java26; + +import com.palantir.javaformat.OpsBuilder; +import com.palantir.javaformat.java.java25.Java25InputAstVisitor; + +/** + * Extends {@link Java25InputAstVisitor} with support for AST nodes that were added or modified in + * Java 26. + */ +public class Java26InputAstVisitor extends Java25InputAstVisitor { + public Java26InputAstVisitor(OpsBuilder builder, int indentMultiplier) { + super(builder, indentMultiplier); + } +} diff --git a/palantir-java-format/src/test/java/com/palantir/javaformat/java/AospImportStyleTest.java b/palantir-java-format/src/test/java/com/palantir/javaformat/java/AospImportStyleTest.java index 9401b1299..9cbbd1436 100644 --- a/palantir-java-format/src/test/java/com/palantir/javaformat/java/AospImportStyleTest.java +++ b/palantir-java-format/src/test/java/com/palantir/javaformat/java/AospImportStyleTest.java @@ -277,6 +277,37 @@ public static List parameters() { "public class Blim {}", }, }, + + // Module imports (JEP 511) form their own leading group, ahead of static, android, + // third-party and java imports alike, and are separated from the following group by a + // blank line, consistent with the other group boundaries in AOSP style. + { + { + "package foo;", + "", + "import java.util.List;", + "import static android.Bar.baz;", + "import module java.desktop;", + "import android.Bar;", + "import module java.base;", + "", + "public class Blim {}", + }, + { + "package foo;", + "", + "import module java.base;", + "import module java.desktop;", + "", + "import static android.Bar.baz;", + "", + "import android.Bar;", + "", + "import java.util.List;", + "", + "public class Blim {}", + }, + }, }; ImmutableList.Builder builder = ImmutableList.builder(); Arrays.stream(inputsOutputs).forEach(input -> builder.add(ImportOrdererUtils.createRow(input))); diff --git a/palantir-java-format/src/test/java/com/palantir/javaformat/java/FileBasedTests.java b/palantir-java-format/src/test/java/com/palantir/javaformat/java/FileBasedTests.java index 4edb8fa12..2c3c58758 100644 --- a/palantir-java-format/src/test/java/com/palantir/javaformat/java/FileBasedTests.java +++ b/palantir-java-format/src/test/java/com/palantir/javaformat/java/FileBasedTests.java @@ -60,6 +60,9 @@ public final class FileBasedTests { "I880", "I1309", "Unnamed") + .putAll(22, "UnnamedPattern") + .putAll(23, "MarkdownDoc") + .putAll(25, "FlexibleConstructor", "ModuleImport", "CompactSource") .build(); private final Class testClass; diff --git a/palantir-java-format/src/test/java/com/palantir/javaformat/java/GoogleImportStyleTest.java b/palantir-java-format/src/test/java/com/palantir/javaformat/java/GoogleImportStyleTest.java index e3b0cb412..e0a4ba639 100644 --- a/palantir-java-format/src/test/java/com/palantir/javaformat/java/GoogleImportStyleTest.java +++ b/palantir-java-format/src/test/java/com/palantir/javaformat/java/GoogleImportStyleTest.java @@ -524,6 +524,34 @@ public static List parameters() { "class Test {}", } }, + + // Module imports (JEP 511) form their own leading group, ahead of both static and + // non-static type imports, and are separated from the following group by a blank line, + // just like static imports are separated from non-static imports. + { + { + "package foo;", + "", + "import java.util.List;", + "import static com.google.truth.Truth.assertThat;", + "import module java.desktop;", + "import module java.base;", + "", + "public class Blim {}", + }, + { + "package foo;", + "", + "import module java.base;", + "import module java.desktop;", + "", + "import static com.google.truth.Truth.assertThat;", + "", + "import java.util.List;", + "", + "public class Blim {}", + }, + }, }; ImmutableList.Builder builder = ImmutableList.builder(); diff --git a/palantir-java-format/src/test/java/com/palantir/javaformat/java/RemoveUnusedImportsTest.java b/palantir-java-format/src/test/java/com/palantir/javaformat/java/RemoveUnusedImportsTest.java index cdea6fe04..89150eb7e 100644 --- a/palantir-java-format/src/test/java/com/palantir/javaformat/java/RemoveUnusedImportsTest.java +++ b/palantir-java-format/src/test/java/com/palantir/javaformat/java/RemoveUnusedImportsTest.java @@ -21,6 +21,7 @@ import com.google.common.truth.Truth; import com.palantir.javaformat.jupiter.ParameterizedClass; import java.util.List; +import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.parallel.Execution; @@ -254,6 +255,21 @@ public static List parameters() { "interface Test { private static void foo() {} }", }, }, + { + // Module imports (JEP 511, `import module foo.bar;`) parse to JCModuleImport, a + // sibling of JCImport rather than a subtype. They must never be reported as unused + // (see isUnused), and must not crash buildReplacements when mixed with ordinary + // imports, some used and some not. + { + "import module java.base;", + "import java.util.List;", + "import java.util.Map;", + "class T { List xs; }", + }, + { + "import module java.base;", "import java.util.List;", "class T { List xs; }", + }, + }, }; ImmutableList.Builder builder = ImmutableList.builder(); for (String[][] inputAndOutput : inputsOutputs) { @@ -278,6 +294,10 @@ public RemoveUnusedImportsTest(String input, String expected) { @TestTemplate public void removeUnused() throws FormatterException { + // Module imports (JEP 511, `import module foo.bar;`) are only parseable on JDK 25+. + Assumptions.assumeTrue( + !input.contains("import module") || Formatter.getRuntimeVersion() >= 25, + "import module requires running on JDK 25 or later"); Truth.assertThat(RemoveUnusedImports.removeUnusedImports(input)).isEqualTo(expected); } } diff --git a/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/CompactSource.input b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/CompactSource.input new file mode 100644 index 000000000..b590b4c59 --- /dev/null +++ b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/CompactSource.input @@ -0,0 +1,5 @@ +String greeting = "hello"; + +void main() { + System.out.println(greeting); +} diff --git a/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/CompactSource.output b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/CompactSource.output new file mode 100644 index 000000000..14476ebe0 --- /dev/null +++ b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/CompactSource.output @@ -0,0 +1,5 @@ +String greeting = "hello"; + +void main() { + System.out.println(greeting); +} diff --git a/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/FlexibleConstructor.input b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/FlexibleConstructor.input new file mode 100644 index 000000000..e01857a19 --- /dev/null +++ b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/FlexibleConstructor.input @@ -0,0 +1,12 @@ +class FlexibleConstructor { + private final int value; + + FlexibleConstructor(int raw) { + if (raw < 0) { + throw new IllegalArgumentException("negative"); + } + var normalized = Math.max(raw, 1); + super(); + this.value = normalized; + } +} diff --git a/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/FlexibleConstructor.output b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/FlexibleConstructor.output new file mode 100644 index 000000000..798bb786c --- /dev/null +++ b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/FlexibleConstructor.output @@ -0,0 +1,12 @@ +class FlexibleConstructor { + private final int value; + + FlexibleConstructor(int raw) { + if (raw < 0) { + throw new IllegalArgumentException("negative"); + } + var normalized = Math.max(raw, 1); + super(); + this.value = normalized; + } +} diff --git a/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/MarkdownDoc.input b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/MarkdownDoc.input new file mode 100644 index 000000000..557fd468e --- /dev/null +++ b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/MarkdownDoc.input @@ -0,0 +1,10 @@ +/// A greeter. +/// +/// Says hello. See [String]. +class MarkdownDoc { + /// Returns the greeting. + /// @return the text + String greeting() { + return "hi"; + } +} diff --git a/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/MarkdownDoc.output b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/MarkdownDoc.output new file mode 100644 index 000000000..2b5d30db2 --- /dev/null +++ b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/MarkdownDoc.output @@ -0,0 +1,10 @@ +/// A greeter. +/// +/// Says hello. See [String]. +class MarkdownDoc { + /// Returns the greeting. + /// @return the text + String greeting() { + return "hi"; + } +} diff --git a/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/ModuleImport.input b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/ModuleImport.input new file mode 100644 index 000000000..ec8b64f85 --- /dev/null +++ b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/ModuleImport.input @@ -0,0 +1,7 @@ +import module java.base; +import module java.sql; +import java.util.List; + +class ModuleImport { + List xs; +} diff --git a/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/ModuleImport.output b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/ModuleImport.output new file mode 100644 index 000000000..2250d4049 --- /dev/null +++ b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/ModuleImport.output @@ -0,0 +1,7 @@ +import module java.base; +import module java.sql; +import java.util.List; + +class ModuleImport { + List xs; +} diff --git a/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/UnnamedPattern.input b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/UnnamedPattern.input new file mode 100644 index 000000000..3c40e3763 --- /dev/null +++ b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/UnnamedPattern.input @@ -0,0 +1,19 @@ +class UnnamedPattern { + sealed interface Shape permits Box {} + record Box(Object a, Object b) implements Shape {} + + int classify(Shape s) { + return switch (s) { + case Box(_, _) -> 0; + case Box(Integer i, _) -> i; + }; + } + + void swallow(Runnable r) { + try { + r.run(); + } catch (Exception _) { + // ignored + } + } +} diff --git a/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/UnnamedPattern.output b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/UnnamedPattern.output new file mode 100644 index 000000000..f10ed3ceb --- /dev/null +++ b/palantir-java-format/src/test/resources/com/palantir/javaformat/java/testdata/UnnamedPattern.output @@ -0,0 +1,20 @@ +class UnnamedPattern { + sealed interface Shape permits Box {} + + record Box(Object a, Object b) implements Shape {} + + int classify(Shape s) { + return switch (s) { + case Box(_, _) -> 0; + case Box(Integer i, _) -> i; + }; + } + + void swallow(Runnable r) { + try { + r.run(); + } catch (Exception _) { + // ignored + } + } +}