From 3c34c493e85ac3c388ff272ee3b6ddc7731137f8 Mon Sep 17 00:00:00 2001 From: jupblb Date: Sun, 28 Jun 2026 11:26:40 +0200 Subject: [PATCH 1/5] Add Java 25 support --- .github/workflows/nix.yaml | 4 ++-- CONTRIBUTING.md | 1 + MODULE.bazel | 2 +- bin/docker-setup.sh | 4 ++-- docs/getting-started.md | 12 ++++++++---- flake.nix | 1 + gradle/libs.versions.toml | 2 +- scip-java/src/test/kotlin/tests/BuildToolHarness.kt | 4 ++-- .../src/test/kotlin/tests/GradleBuildToolTest.kt | 6 +++--- .../resources/fixtures/gradle/kotlin2/build.gradle | 3 +++ .../src/main/java/minimized/LombokBuilder.java | 5 +++++ 11 files changed, 29 insertions(+), 15 deletions(-) diff --git a/.github/workflows/nix.yaml b/.github/workflows/nix.yaml index f0cdc77d6..2f2d2072b 100644 --- a/.github/workflows/nix.yaml +++ b/.github/workflows/nix.yaml @@ -39,7 +39,7 @@ jobs: strategy: fail-fast: false matrix: - java: [17, 21] + java: [17, 21, 25] steps: - uses: actions/checkout@v4 @@ -135,7 +135,7 @@ jobs: strategy: fail-fast: false matrix: - java: [17, 21] + java: [17, 21, 25] steps: - uses: actions/checkout@v4 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 080d7a5b4..32250378a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,6 +12,7 @@ The recommended way to get a working development environment is via nix develop # default shell (JDK 17) nix develop .#jdk17 # JDK 17 nix develop .#jdk21 # JDK 21 +nix develop .#jdk25 # JDK 25 ``` This drops you into a shell with `gradle`, `maven`, `bazelisk`, `nodejs`, diff --git a/MODULE.bazel b/MODULE.bazel index 15e6ff88e..8a9a0d285 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -9,7 +9,7 @@ maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") maven.install( artifacts = [ "com.google.protobuf:protobuf-java-util:4.34.2", - "org.projectlombok:lombok:1.18.22", + "org.projectlombok:lombok:1.18.46", "org.scip-code:scip-java-bindings:0.8.0", ], known_contributing_modules = ["protobuf"], diff --git a/bin/docker-setup.sh b/bin/docker-setup.sh index 2e34386b4..4d8c46c11 100755 --- a/bin/docker-setup.sh +++ b/bin/docker-setup.sh @@ -15,8 +15,8 @@ unzip -d /opt/gradle gradle.zip rm gradle.zip mv /opt/gradle/*/* /opt/gradle -# pre-install JDK for all major versions -for JVM_VERSION in 21 17 +# pre-install JDK for all supported major versions +for JVM_VERSION in 25 21 17 do coursier java --jvm $JVM_VERSION --jvm-index https://github.com/coursier/jvm-index/blob/master/index.json -- -version done diff --git a/docs/getting-started.md b/docs/getting-started.md index 7b3ec4738..6e4200e95 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -35,23 +35,26 @@ finished indexing the project. > The `sourcegraph/scip-java` Docker image is made available for convenience at > the cost of performance. The `sourcegraph/scip-java` image is a big download -> because it includes pre-installed versions of Java 17 and Java 21. +> because it includes pre-installed versions of Java 17, Java 21, and Java 25. > The `sourcegraph/scip-java` image has slow performance because it needs to > download all external dependencies of your codebase on every invocation. > > For better performance, we recommend using your own Docker image together with > the [Java launcher](#java-launcher) option. -Java 17 is the default Java version in the `sourcegraph/scip-java` Docker image. -Use the following commands to use a different JVM version: +By default, the `sourcegraph/scip-java` Docker image tries Java 21 and falls +back to Java 17. Use the following commands to select a specific JVM version: ```sh -# Java 17 (default) +# Java 17 docker run -v $(pwd):/sources --env JVM_VERSION=17 sourcegraph/scip-java:latest scip-java index # Java 21 docker run -v $(pwd):/sources --env JVM_VERSION=21 sourcegraph/scip-java:latest scip-java index +# Java 25 +docker run -v $(pwd):/sources --env JVM_VERSION=25 sourcegraph/scip-java:latest scip-java index + ``` ### Java launcher @@ -205,6 +208,7 @@ of Java versions. | Java 11 | ❌ | | | Java 17 | ✅, requires `--add-exports` | | | Java 21 | ✅, requires `--add-exports` | | +| Java 25 | ✅, requires `--add-exports` | | For Java 17 and newer versions, the following JVM options are required: diff --git a/flake.nix b/flake.nix index 03de1e07a..f6c2b1ecc 100644 --- a/flake.nix +++ b/flake.nix @@ -65,6 +65,7 @@ default = mkDevShell pkgs.jdk17; jdk17 = mkDevShell pkgs.jdk17; jdk21 = mkDevShell pkgs.jdk21; + jdk25 = mkDevShell pkgs.jdk25; }; } ); diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4b3fa0b7f..b09deff11 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ kctfork = "0.7.1" kotest = "4.6.3" kotlin = "2.2.0" kotlinx-serialization = "1.9.0" -lombok = "1.18.22" +lombok = "1.18.46" maven-plugin-annotations = "3.6.4" maven-plugin-api = "3.6.3" maven-project = "2.2.1" diff --git a/scip-java/src/test/kotlin/tests/BuildToolHarness.kt b/scip-java/src/test/kotlin/tests/BuildToolHarness.kt index 65015a0fd..dbe235879 100644 --- a/scip-java/src/test/kotlin/tests/BuildToolHarness.kt +++ b/scip-java/src/test/kotlin/tests/BuildToolHarness.kt @@ -115,8 +115,8 @@ abstract class BuildToolHarness { substitutions: Map = emptyMap(), ): DynamicTest = dynamicTest(name) { - // Some build tools cap the JDK they support (e.g. Gradle 8.10 tops - // out at JDK 21); skip rather than fail when the JDK is too new. + // Some build tools cap the JDK they support; skip rather than fail + // when the JDK running the test is too new. assumeTrue( maxJdk == null || currentJavaVersion <= maxJdk, "Test $name ignored: JDK $currentJavaVersion exceeds max $maxJdk", diff --git a/scip-java/src/test/kotlin/tests/GradleBuildToolTest.kt b/scip-java/src/test/kotlin/tests/GradleBuildToolTest.kt index 340d51b25..8694a9b16 100644 --- a/scip-java/src/test/kotlin/tests/GradleBuildToolTest.kt +++ b/scip-java/src/test/kotlin/tests/GradleBuildToolTest.kt @@ -5,11 +5,11 @@ import java.nio.file.StandardOpenOption import org.junit.jupiter.api.DynamicTest import org.junit.jupiter.api.TestFactory -private const val GRADLE_VERSION = "8.10" +private const val GRADLE_VERSION = "9.1.0" -// Gradle 8.10 runs on JDK 8 through 21. +// Gradle 9.1.0 runs on JDK 17 through 25. // See https://docs.gradle.org/current/userguide/compatibility.html -private const val GRADLE_MAX_JDK = 21 +private const val GRADLE_MAX_JDK = 25 class GradleBuildToolTest : BuildToolHarness() { diff --git a/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle b/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle index 6fa75dbea..d0a96fa86 100644 --- a/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle +++ b/scip-java/src/test/resources/fixtures/gradle/kotlin2/build.gradle @@ -1,6 +1,9 @@ plugins { id 'org.jetbrains.kotlin.jvm' version '2.2.0' } +kotlin { + jvmToolchain(17) +} repositories { mavenCentral() } diff --git a/scip-snapshots/expected/java/common/scip-snapshots/cases/java/common/src/main/java/minimized/LombokBuilder.java b/scip-snapshots/expected/java/common/scip-snapshots/cases/java/common/src/main/java/minimized/LombokBuilder.java index 21ec359fe..8502dee16 100644 --- a/scip-snapshots/expected/java/common/scip-snapshots/cases/java/common/src/main/java/minimized/LombokBuilder.java +++ b/scip-snapshots/expected/java/common/scip-snapshots/cases/java/common/src/main/java/minimized/LombokBuilder.java @@ -15,6 +15,8 @@ //^^^^^^^^^^^^^^^ reference scip-java maven jdk 17 java/lang/SuppressWarnings# //^^^^^^^^^^^^^^^ reference scip-java maven . . java/lang/ //^^^^^^^^^^^^^^^ reference scip-java maven . . java/ +//^^^^^^^^^^^^^^^ reference scip-java maven . . lombok/Generated# +//^^^^^^^^^^^^^^^ reference scip-java maven . . lombok/ //^^^^^^^^^^^^^^^ reference scip-java maven jdk 17 java/lang/String# //^^^^^^^^^^^^^^^ reference scip-java maven . . minimized/Hello#message. //^^^^^^^^^^^^^^^ reference local 0 @@ -48,6 +50,7 @@ class Hello { // display_name // signature_documentation // > @SuppressWarnings("all") +// > @Generated // > Hello(String message) // ^^^^^ reference scip-java maven . . minimized/Hello#``(). // ⌄ enclosing_range_start scip-java maven . . minimized/Hello#message. @@ -67,12 +70,14 @@ class Hello { // display_name message // signature_documentation // > @SuppressWarnings("all") +// > @Generated // > private String message // ^^^^^^^ definition scip-java maven . . minimized/Hello#HelloBuilder#message(). // kind Method // display_name message // signature_documentation // > @SuppressWarnings("all") +// > @Generated // > public HelloBuilder message(String message) // documentation // > @return {@code this}. From 8f9dc48901375736307b939056d33dd263b7b9a2 Mon Sep 17 00:00:00 2001 From: jupblb Date: Sun, 28 Jun 2026 11:42:04 +0200 Subject: [PATCH 2/5] Add Java 25 snapshots --- scip-snapshots/build.gradle.kts | 41 ++- scip-snapshots/cases/java-25/build.gradle.kts | 30 +++ .../java-25/src/main/java/CompactMain.java | 10 + .../main/java/java25/LanguageFeatures.java | 57 ++++ .../java-25/src/main/java/CompactMain.java | 34 +++ .../main/java/java25/LanguageFeatures.java | 255 ++++++++++++++++++ .../tests/MinimizedSnapshotScipGenerator.java | 115 ++++++-- .../src/main/java/tests/SaveSnapshots.java | 9 +- .../java/tests/MinimizedSnapshotScipTest.java | 6 +- settings.gradle.kts | 2 + 10 files changed, 520 insertions(+), 39 deletions(-) create mode 100644 scip-snapshots/cases/java-25/build.gradle.kts create mode 100644 scip-snapshots/cases/java-25/src/main/java/CompactMain.java create mode 100644 scip-snapshots/cases/java-25/src/main/java/java25/LanguageFeatures.java create mode 100644 scip-snapshots/expected/java-25/scip-snapshots/cases/java-25/src/main/java/CompactMain.java create mode 100644 scip-snapshots/expected/java-25/scip-snapshots/cases/java-25/src/main/java/java25/LanguageFeatures.java diff --git a/scip-snapshots/build.gradle.kts b/scip-snapshots/build.gradle.kts index 09a1ad8da..839ad0ba8 100644 --- a/scip-snapshots/build.gradle.kts +++ b/scip-snapshots/build.gradle.kts @@ -10,22 +10,43 @@ dependencies { implementation(libs.scip.java.bindings) } +data class SnapshotCaseInput( + val id: String, + val producerPath: String, + val minimumJavaFeature: Int = 0, +) { + val targetrootProperty: String = "snapshot.case.$id.targetroot" + val targetrootName: String = "${id.replace("-", "")}Targetroot" +} + +val snapshotCaseInputs = + listOf( + SnapshotCaseInput("java-common", ":scip-snapshots-java-common"), + SnapshotCaseInput("java-25", ":scip-snapshots-java-25", minimumJavaFeature = 25), + SnapshotCaseInput("kotlin-common", ":scip-snapshots-kotlin-common"), + ) + // The snapshot goldens are produced by compiling the sibling "case" projects with the scip compiler // plugins; each publishes its target-root directory as the `scipTargetrootElements` artifact. -val javaTargetroot = projectArtifact(":scip-snapshots-java-common", "scipTargetrootElements", "javaTargetroot") -val kotlinTargetroot = projectArtifact(":scip-snapshots-kotlin-common", "scipTargetrootElements", "kotlinTargetroot") +val currentJavaFeature = JavaVersion.current().majorVersion.toInt() +val enabledSnapshotCaseInputs = + snapshotCaseInputs.filter { currentJavaFeature >= it.minimumJavaFeature } +val snapshotTargetroots = + enabledSnapshotCaseInputs.associateWith { snapshotCase -> + projectArtifact(snapshotCase.producerPath, "scipTargetrootElements", snapshotCase.targetrootName) + } // The case list, expected-golden layout and per-case indexing flags are fixed test metadata defined // in MinimizedSnapshotScipGenerator; the build only supplies the build-time paths it owns. val snapshotProperties = - mapOf( - "snapshot.sourceroot" to rootProject.rootDir.absolutePath, - "snapshot.case.java-common.targetroot" to javaTargetroot.singleFile.absolutePath, - "snapshot.case.kotlin-common.targetroot" to kotlinTargetroot.singleFile.absolutePath, - "scip.jdk.version" to "17", - ) + mutableMapOf("snapshot.sourceroot" to rootProject.rootDir.absolutePath) + .apply { + snapshotTargetroots.forEach { (snapshotCase, targetroot) -> + put(snapshotCase.targetrootProperty, targetroot.singleFile.absolutePath) + } + } tasks.named("test") { - inputs.files(javaTargetroot, kotlinTargetroot) + inputs.files(snapshotTargetroots.values) jvmArgs(JavacInternals.jvmOptions(rootDir)) systemProperties(snapshotProperties) } @@ -33,7 +54,7 @@ tasks.named("test") { tasks.register("saveSnapshots") { group = "verification" description = "Regenerates Java and Kotlin SCIP snapshot goldens." - inputs.files(javaTargetroot, kotlinTargetroot) + inputs.files(snapshotTargetroots.values) val sourceSets = project.extensions.getByType() classpath = sourceSets.named("main").get().runtimeClasspath mainClass.set("tests.SaveSnapshots") diff --git a/scip-snapshots/cases/java-25/build.gradle.kts b/scip-snapshots/cases/java-25/build.gradle.kts new file mode 100644 index 000000000..9fdbe85ff --- /dev/null +++ b/scip-snapshots/cases/java-25/build.gradle.kts @@ -0,0 +1,30 @@ +import com.sourcegraph.buildlogic.cleanDirectoryBeforeRunning +import com.sourcegraph.buildlogic.publishDirectoryArtifact +import com.sourcegraph.buildlogic.shadowJarArtifact +import com.sourcegraph.buildlogic.useScipJavac + +plugins { + id("scip.java-library") +} + +val javacShadowJar = shadowJarArtifact(":scip-javac", "javacShadowJar") + +val minimumJavaFeature = 25 +val currentJavaFeature = JavaVersion.current().majorVersion.toInt() +val scipTargetroot = layout.buildDirectory.dir("scip-targetroot") +val sourceroot = rootProject.rootDir.absolutePath +val targetroot = scipTargetroot.get().asFile.absolutePath + +tasks.named("compileJava") { + enabled = currentJavaFeature >= minimumJavaFeature + options.release.set(25) + options.compilerArgs.add("--enable-preview") + useScipJavac(rootDir, javacShadowJar, scipTargetroot) + options.compilerArgs.add( + "-Xplugin:scip -text:on -verbose -sourceroot:$sourceroot " + + "-targetroot:$targetroot -randomtimestamp=${System.nanoTime()}" + ) + cleanDirectoryBeforeRunning(scipTargetroot) +} + +publishDirectoryArtifact("scipTargetrootElements", scipTargetroot, tasks.named("classes")) diff --git a/scip-snapshots/cases/java-25/src/main/java/CompactMain.java b/scip-snapshots/cases/java-25/src/main/java/CompactMain.java new file mode 100644 index 000000000..c04f2f86c --- /dev/null +++ b/scip-snapshots/cases/java-25/src/main/java/CompactMain.java @@ -0,0 +1,10 @@ +import module java.base; + +/// Compact source file with an instance main method. +void main() { + IO.println(message()); +} + +String message() { + return List.of("Java", "25").get(1); +} diff --git a/scip-snapshots/cases/java-25/src/main/java/java25/LanguageFeatures.java b/scip-snapshots/cases/java-25/src/main/java/java25/LanguageFeatures.java new file mode 100644 index 000000000..88c4aad10 --- /dev/null +++ b/scip-snapshots/cases/java-25/src/main/java/java25/LanguageFeatures.java @@ -0,0 +1,57 @@ +package java25; + +import module java.base; + +/// Exercises language features that require Java 22 through Java 25 source support. +public class LanguageFeatures { + /// Java 22 unnamed variables and unnamed record patterns. + public static int unnamedVariablesAndPatterns(Object input) { + int seen = 0; + for (String _ : List.of("alpha", "beta")) { + seen++; + } + try { + Integer.parseInt("not-a-number"); + } catch (NumberFormatException _) { + seen++; + } + if (input instanceof Range(int start, int _)) { + return start + seen; + } + return seen; + } + + /// Java 24/25 primitive patterns in switch. + public static String primitivePatterns(int value) { + return switch (value) { + case byte _ -> "byte range"; + case int i when i > 0 -> "positive int"; + case int _ -> "int"; + }; + } + + /// Java 25 flexible constructor bodies. + public static final class Child extends Parent { + public Child(String input) { + String normalized = Objects.requireNonNull(input).strip(); + if (normalized.isEmpty()) { + throw new IllegalArgumentException("blank"); + } + super(normalized); + } + } + + public static sealed class Parent permits Child { + private final String value; + + protected Parent(String value) { + this.value = value; + } + + public String value() { + return value; + } + } + + public record Range(int start, int end) {} +} diff --git a/scip-snapshots/expected/java-25/scip-snapshots/cases/java-25/src/main/java/CompactMain.java b/scip-snapshots/expected/java-25/scip-snapshots/cases/java-25/src/main/java/CompactMain.java new file mode 100644 index 000000000..97ad711b2 --- /dev/null +++ b/scip-snapshots/expected/java-25/scip-snapshots/cases/java-25/src/main/java/CompactMain.java @@ -0,0 +1,34 @@ + import module java.base; + + /// Compact source file with an instance main method. +//⌄ enclosing_range_start scip-java maven . . CompactMain#main(). + void main() { +// ^^^^ definition scip-java maven . . CompactMain#main(). +// kind Method +// display_name main +// signature_documentation +// > void main() +// documentation +// > Compact source file with an instance main method. + IO.println(message()); +// ^^ reference scip-java maven jdk 25 java/lang/IO# +// ^^^^^^^ reference scip-java maven jdk 25 java/lang/IO#println(). +// ^^^^^^^ reference scip-java maven . . CompactMain#message(). + } +//⌃ enclosing_range_end scip-java maven . . CompactMain#main(). + +//⌄ enclosing_range_start scip-java maven . . CompactMain#message(). + String message() { +//^^^^^^ reference scip-java maven jdk 25 java/lang/String# +// ^^^^^^^ definition scip-java maven . . CompactMain#message(). +// kind Method +// display_name message +// signature_documentation +// > String message() + return List.of("Java", "25").get(1); +// ^^^^ reference scip-java maven jdk 25 java/util/List# +// ^^ reference scip-java maven jdk 25 java/util/List#of(+2). +// ^^^ reference scip-java maven jdk 25 java/util/List#get(). + } +//⌃ enclosing_range_end scip-java maven . . CompactMain#message(). + diff --git a/scip-snapshots/expected/java-25/scip-snapshots/cases/java-25/src/main/java/java25/LanguageFeatures.java b/scip-snapshots/expected/java-25/scip-snapshots/cases/java-25/src/main/java/java25/LanguageFeatures.java new file mode 100644 index 000000000..bf776f1bb --- /dev/null +++ b/scip-snapshots/expected/java-25/scip-snapshots/cases/java-25/src/main/java/java25/LanguageFeatures.java @@ -0,0 +1,255 @@ + package java25; + + import module java.base; + + /// Exercises language features that require Java 22 through Java 25 source support. +//⌄ enclosing_range_start scip-java maven . . java25/LanguageFeatures# + public class LanguageFeatures { +// ^^^^^^^^^^^^^^^^ definition scip-java maven . . java25/LanguageFeatures# +// kind Class +// display_name LanguageFeatures +// signature_documentation +// > public class LanguageFeatures +// documentation +// > Exercises language features that require Java 22 through Java 25 source support. +// ^^^^^^^^^^^^^^^^ definition scip-java maven . . java25/LanguageFeatures#``(). +// kind Constructor +// display_name +// signature_documentation +// > public LanguageFeatures() + /// Java 22 unnamed variables and unnamed record patterns. +// ⌄ enclosing_range_start scip-java maven . . java25/LanguageFeatures#unnamedVariablesAndPatterns(). +// ⌄ enclosing_range_start local 0 + public static int unnamedVariablesAndPatterns(Object input) { +// ^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition scip-java maven . . java25/LanguageFeatures#unnamedVariablesAndPatterns(). +// kind StaticMethod +// display_name unnamedVariablesAndPatterns +// signature_documentation +// > public static int unnamedVariablesAndPatterns(Object input) +// documentation +// > Java 22 unnamed variables and unnamed record patterns. +// ^^^^^^ reference scip-java maven jdk 25 java/lang/Object# +// ^^^^^ definition local 0 +// display_name input +// signature_documentation +// > Object input +// ⌃ enclosing_range_end local 0 +// ⌄ enclosing_range_start local 1 + int seen = 0; +// ^^^^ definition local 1 +// kind Variable +// display_name seen +// signature_documentation +// > int seen +// ⌃ enclosing_range_end local 1 + for (String _ : List.of("alpha", "beta")) { +// ^^^^^^ reference scip-java maven jdk 25 java/lang/String# +// ^^^^ reference scip-java maven jdk 25 java/util/List# +// ^^ reference scip-java maven jdk 25 java/util/List#of(+2). + seen++; +// ^^^^ reference local 1 + } + try { + Integer.parseInt("not-a-number"); +// ^^^^^^^ reference scip-java maven jdk 25 java/lang/Integer# +// ^^^^^^^^ reference scip-java maven jdk 25 java/lang/Integer#parseInt(+1). + } catch (NumberFormatException _) { +// ^^^^^^^^^^^^^^^^^^^^^ reference scip-java maven jdk 25 java/lang/NumberFormatException# + seen++; +// ^^^^ reference local 1 + } +// ⌄ enclosing_range_start scip-java maven . . java25/LanguageFeatures#unnamedVariablesAndPatterns().start. + if (input instanceof Range(int start, int _)) { +// ^^^^^ reference local 0 +// ^^^^^ reference scip-java maven . . java25/LanguageFeatures#Range# +// ^^^^^ definition scip-java maven . . java25/LanguageFeatures#unnamedVariablesAndPatterns().start. +// display_name start +// signature_documentation +// > int start +// ⌃ enclosing_range_end scip-java maven . . java25/LanguageFeatures#unnamedVariablesAndPatterns().start. + return start + seen; +// ^^^^^ reference scip-java maven . . java25/LanguageFeatures#unnamedVariablesAndPatterns().start. +// ^^^^ reference local 1 + } + return seen; +// ^^^^ reference local 1 + } +// ⌃ enclosing_range_end scip-java maven . . java25/LanguageFeatures#unnamedVariablesAndPatterns(). + + /// Java 24/25 primitive patterns in switch. +// ⌄ enclosing_range_start scip-java maven . . java25/LanguageFeatures#primitivePatterns(). +// ⌄ enclosing_range_start local 2 + public static String primitivePatterns(int value) { +// ^^^^^^ reference scip-java maven jdk 25 java/lang/String# +// ^^^^^^^^^^^^^^^^^ definition scip-java maven . . java25/LanguageFeatures#primitivePatterns(). +// kind StaticMethod +// display_name primitivePatterns +// signature_documentation +// > public static String primitivePatterns(int value) +// documentation +// > Java 24/25 primitive patterns in switch. +// ^^^^^ definition local 2 +// display_name value +// signature_documentation +// > int value +// ⌃ enclosing_range_end local 2 + return switch (value) { +// ^^^^^ reference local 2 + case byte _ -> "byte range"; +// ⌄ enclosing_range_start scip-java maven . . java25/LanguageFeatures#primitivePatterns().i. + case int i when i > 0 -> "positive int"; +// ^ definition scip-java maven . . java25/LanguageFeatures#primitivePatterns().i. +// display_name i +// signature_documentation +// > int i +// ^ reference scip-java maven . . java25/LanguageFeatures#primitivePatterns().i. +// ⌃ enclosing_range_end scip-java maven . . java25/LanguageFeatures#primitivePatterns().i. + case int _ -> "int"; + }; + } +// ⌃ enclosing_range_end scip-java maven . . java25/LanguageFeatures#primitivePatterns(). + + /// Java 25 flexible constructor bodies. +// ⌄ enclosing_range_start scip-java maven . . java25/LanguageFeatures#Child# + public static final class Child extends Parent { +// ^^^^^ definition scip-java maven . . java25/LanguageFeatures#Child# +// kind Class +// display_name Child +// signature_documentation +// > public static final class Child extends Parent +// documentation +// > Java 25 flexible constructor bodies. +// relationship scip-java maven . . java25/LanguageFeatures#Parent# implementation +// ^^^^^^ reference scip-java maven . . java25/LanguageFeatures#Parent# +// ⌄ enclosing_range_start scip-java maven . . java25/LanguageFeatures#Child#``(). +// ⌄ enclosing_range_start local 3 + public Child(String input) { +// ^^^^^ definition scip-java maven . . java25/LanguageFeatures#Child#``(). +// kind Constructor +// display_name +// signature_documentation +// > public Child(String input) +// ^^^^^^ reference scip-java maven jdk 25 java/lang/String# +// ^^^^^ definition local 3 +// display_name input +// signature_documentation +// > String input +// ⌃ enclosing_range_end local 3 +// ⌄ enclosing_range_start local 4 + String normalized = Objects.requireNonNull(input).strip(); +// ^^^^^^ reference scip-java maven jdk 25 java/lang/String# +// ^^^^^^^^^^ definition local 4 +// kind Variable +// display_name normalized +// signature_documentation +// > String normalized +// ^^^^^^^ reference scip-java maven jdk 25 java/util/Objects# +// ^^^^^^^^^^^^^^ reference scip-java maven jdk 25 java/util/Objects#requireNonNull(+1). +// ^^^^^ reference local 3 +// ^^^^^ reference scip-java maven jdk 25 java/lang/String#strip(). +// ⌃ enclosing_range_end local 4 + if (normalized.isEmpty()) { +// ^^^^^^^^^^ reference local 4 +// ^^^^^^^ reference scip-java maven jdk 25 java/lang/String#isEmpty(). + throw new IllegalArgumentException("blank"); +// ^^^^^^^^^^^^^^^^^^^^^^^^ reference scip-java maven jdk 25 java/lang/IllegalArgumentException#``(+1). + } + super(normalized); +// ^^^^^ reference scip-java maven . . java25/LanguageFeatures#Parent#``(). +// ^^^^^^^^^^ reference local 4 + } +// ⌃ enclosing_range_end scip-java maven . . java25/LanguageFeatures#Child#``(). + } +// ⌃ enclosing_range_end scip-java maven . . java25/LanguageFeatures#Child# + +// ⌄ enclosing_range_start scip-java maven . . java25/LanguageFeatures#Parent# + public static sealed class Parent permits Child { +// ^^^^^^ definition scip-java maven . . java25/LanguageFeatures#Parent# +// kind Class +// display_name Parent +// signature_documentation +// > public static class Parent +// ^^^^^ reference scip-java maven . . java25/LanguageFeatures#Child# +// ⌄ enclosing_range_start scip-java maven . . java25/LanguageFeatures#Parent#value. + private final String value; +// ^^^^^^ reference scip-java maven jdk 25 java/lang/String# +// ^^^^^ definition scip-java maven . . java25/LanguageFeatures#Parent#value. +// kind Field +// display_name value +// signature_documentation +// > private final String value +// ⌃ enclosing_range_end scip-java maven . . java25/LanguageFeatures#Parent#value. + +// ⌄ enclosing_range_start scip-java maven . . java25/LanguageFeatures#Parent#``(). +// ⌄ enclosing_range_start local 5 + protected Parent(String value) { +// ^^^^^^ definition scip-java maven . . java25/LanguageFeatures#Parent#``(). +// kind Constructor +// display_name +// signature_documentation +// > protected Parent(String value) +// ^^^^^^ reference scip-java maven jdk 25 java/lang/String# +// ^^^^^ definition local 5 +// display_name value +// signature_documentation +// > String value +// ⌃ enclosing_range_end local 5 + this.value = value; +// ^^^^^ reference scip-java maven . . java25/LanguageFeatures#Parent#value. +// ^^^^^ reference local 5 + } +// ⌃ enclosing_range_end scip-java maven . . java25/LanguageFeatures#Parent#``(). + +// ⌄ enclosing_range_start scip-java maven . . java25/LanguageFeatures#Parent#value(). + public String value() { +// ^^^^^^ reference scip-java maven jdk 25 java/lang/String# +// ^^^^^ definition scip-java maven . . java25/LanguageFeatures#Parent#value(). +// kind Method +// display_name value +// signature_documentation +// > public String value() + return value; +// ^^^^^ reference scip-java maven . . java25/LanguageFeatures#Parent#value. + } +// ⌃ enclosing_range_end scip-java maven . . java25/LanguageFeatures#Parent#value(). + } +// ⌃ enclosing_range_end scip-java maven . . java25/LanguageFeatures#Parent# + +// ⌄ enclosing_range_start scip-java maven . . java25/LanguageFeatures#Range# +// ⌄ enclosing_range_start scip-java maven . . java25/LanguageFeatures#Range#start. +// ⌄ enclosing_range_start scip-java maven . . java25/LanguageFeatures#Range#end. + public record Range(int start, int end) {} +// ^^^^^ definition scip-java maven . . java25/LanguageFeatures#Range# +// display_name Range +// signature_documentation +// > public static final Range +// relationship scip-java maven jdk 25 java/lang/Record# implementation +// ^^^^^ definition scip-java maven . . java25/LanguageFeatures#Range#``(). +// kind Constructor +// display_name +// signature_documentation +// > public Range(int start, int end) +// ^^^^^ definition local 6 +// display_name start +// signature_documentation +// > int start +// ^^^^^ definition scip-java maven . . java25/LanguageFeatures#Range#start. +// kind Field +// display_name start +// signature_documentation +// > private final int start +// ^^^ definition local 7 +// display_name end +// signature_documentation +// > int end +// ^^^ definition scip-java maven . . java25/LanguageFeatures#Range#end. +// kind Field +// display_name end +// signature_documentation +// > private final int end +// ⌃ enclosing_range_end scip-java maven . . java25/LanguageFeatures#Range#start. +// ⌃ enclosing_range_end scip-java maven . . java25/LanguageFeatures#Range#end. +// ⌃ enclosing_range_end scip-java maven . . java25/LanguageFeatures#Range# + } +//⌃ enclosing_range_end scip-java maven . . java25/LanguageFeatures# + diff --git a/scip-snapshots/src/main/java/tests/MinimizedSnapshotScipGenerator.java b/scip-snapshots/src/main/java/tests/MinimizedSnapshotScipGenerator.java index 56907937f..74f06996a 100644 --- a/scip-snapshots/src/main/java/tests/MinimizedSnapshotScipGenerator.java +++ b/scip-snapshots/src/main/java/tests/MinimizedSnapshotScipGenerator.java @@ -11,6 +11,7 @@ import java.util.Arrays; import java.util.Comparator; import java.util.List; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.scip_code.scip.Document; import org.scip_code.scip.Index; @@ -20,21 +21,46 @@ * are supplied as {@code -Dsnapshot.*} system properties by the Gradle build. */ public class MinimizedSnapshotScipGenerator { + private static final List SNAPSHOT_CASES = + Arrays.asList( + new SnapshotCaseSpec( + "java-common", + "scip-snapshots/expected/java/common", + "snapshot.case.java-common.targetroot", + false, + "17"), + new SnapshotCaseSpec( + "java-25", + "scip-snapshots/expected/java-25", + "snapshot.case.java-25.targetroot", + false, + "25", + 25), + new SnapshotCaseSpec( + "kotlin-common", + "scip-snapshots/expected/kotlin/common", + "snapshot.case.kotlin-common.targetroot", + true, + "17")); + public static final class SnapshotCase { public final String id; public final Path expectDirectory; public final Path targetroot; public final boolean aggregateNoEmitInverseRelationships; + public final String jdkVersion; private SnapshotCase( String id, Path expectDirectory, Path targetroot, - boolean aggregateNoEmitInverseRelationships) { + boolean aggregateNoEmitInverseRelationships, + String jdkVersion) { this.id = id; this.expectDirectory = expectDirectory; this.targetroot = targetroot; this.aggregateNoEmitInverseRelationships = aggregateNoEmitInverseRelationships; + this.jdkVersion = jdkVersion; } public SnapshotContext context() { @@ -42,6 +68,58 @@ public SnapshotContext context() { } } + private static final class SnapshotCaseSpec { + private final String id; + private final String expectDirectory; + private final String targetrootProperty; + private final boolean aggregateNoEmitInverseRelationships; + private final String jdkVersion; + private final int minimumJavaFeature; + + private SnapshotCaseSpec( + String id, + String expectDirectory, + String targetrootProperty, + boolean aggregateNoEmitInverseRelationships, + String jdkVersion) { + this( + id, + expectDirectory, + targetrootProperty, + aggregateNoEmitInverseRelationships, + jdkVersion, + 0); + } + + private SnapshotCaseSpec( + String id, + String expectDirectory, + String targetrootProperty, + boolean aggregateNoEmitInverseRelationships, + String jdkVersion, + int minimumJavaFeature) { + this.id = id; + this.expectDirectory = expectDirectory; + this.targetrootProperty = targetrootProperty; + this.aggregateNoEmitInverseRelationships = aggregateNoEmitInverseRelationships; + this.jdkVersion = jdkVersion; + this.minimumJavaFeature = minimumJavaFeature; + } + + private boolean isEnabled() { + return Runtime.version().feature() >= minimumJavaFeature; + } + + private SnapshotCase toSnapshotCase(Path sourceroot) { + return new SnapshotCase( + id, + sourceroot.resolve(expectDirectory), + requiredPathProperty(targetrootProperty), + aggregateNoEmitInverseRelationships, + jdkVersion); + } + } + public void run(List args) { int exit = ScipJava.app.run(args); if (exit != 0) { @@ -50,11 +128,21 @@ public void run(List args) { } public void run(SnapshotCase snapshotCase, SnapshotHandler handler) { - onTargetroot( - snapshotCase.context(), - handler, - snapshotCase.targetroot, - snapshotCase.aggregateNoEmitInverseRelationships); + String previousJdkVersion = System.getProperty("scip.jdk.version"); + System.setProperty("scip.jdk.version", snapshotCase.jdkVersion); + try { + onTargetroot( + snapshotCase.context(), + handler, + snapshotCase.targetroot, + snapshotCase.aggregateNoEmitInverseRelationships); + } finally { + if (previousJdkVersion == null) { + System.clearProperty("scip.jdk.version"); + } else { + System.setProperty("scip.jdk.version", previousJdkVersion); + } + } } public void onTargetroot( @@ -116,17 +204,10 @@ public void onTargetroot( */ public static List snapshotCases() { Path sourceroot = requiredPathProperty("snapshot.sourceroot"); - return Arrays.asList( - new SnapshotCase( - "java-common", - sourceroot.resolve("scip-snapshots/expected/java/common"), - requiredPathProperty("snapshot.case.java-common.targetroot"), - false), - new SnapshotCase( - "kotlin-common", - sourceroot.resolve("scip-snapshots/expected/kotlin/common"), - requiredPathProperty("snapshot.case.kotlin-common.targetroot"), - true)); + return SNAPSHOT_CASES.stream() + .filter(SnapshotCaseSpec::isEnabled) + .map(snapshotCase -> snapshotCase.toSnapshotCase(sourceroot)) + .collect(Collectors.toList()); } public static Path requiredPathProperty(String name) { diff --git a/scip-snapshots/src/main/java/tests/SaveSnapshots.java b/scip-snapshots/src/main/java/tests/SaveSnapshots.java index 7c235f83b..64701b4f0 100644 --- a/scip-snapshots/src/main/java/tests/SaveSnapshots.java +++ b/scip-snapshots/src/main/java/tests/SaveSnapshots.java @@ -8,19 +8,14 @@ public final class SaveSnapshots { private SaveSnapshots() {} public static void main(String[] args) { - // The JDK version embedded in stdlib SCIP symbols is pinned via the - // `-Dscip.jdk.version` flag that the Gradle `saveSnapshots` task passes in, + // Each snapshot case pins the JDK version embedded in stdlib SCIP symbols, // keeping regenerated goldens stable across the supported JDK matrix. MinimizedSnapshotScipGenerator generator = new MinimizedSnapshotScipGenerator(); for (MinimizedSnapshotScipGenerator.SnapshotCase snapshotCase : MinimizedSnapshotScipGenerator.snapshotCases()) { SnapshotContext context = snapshotCase.context(); SaveSnapshotHandler handler = new SaveSnapshotHandler(); - generator.onTargetroot( - context, - handler, - snapshotCase.targetroot, - snapshotCase.aggregateNoEmitInverseRelationships); + generator.run(snapshotCase, handler); handler.onFinished(context); } } diff --git a/scip-snapshots/src/test/java/tests/MinimizedSnapshotScipTest.java b/scip-snapshots/src/test/java/tests/MinimizedSnapshotScipTest.java index 159ef34be..cb273a77a 100644 --- a/scip-snapshots/src/test/java/tests/MinimizedSnapshotScipTest.java +++ b/scip-snapshots/src/test/java/tests/MinimizedSnapshotScipTest.java @@ -12,11 +12,7 @@ List minimizedSnapshots() { MinimizedSnapshotScipGenerator generator = new MinimizedSnapshotScipGenerator(); for (MinimizedSnapshotScipGenerator.SnapshotCase snapshotCase : MinimizedSnapshotScipGenerator.snapshotCases()) { - generator.onTargetroot( - snapshotCase.context(), - handler, - snapshotCase.targetroot, - snapshotCase.aggregateNoEmitInverseRelationships); + generator.run(snapshotCase, handler); } return handler.tests(); } diff --git a/settings.gradle.kts b/settings.gradle.kts index b13f10edb..7a3395b07 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,8 +26,10 @@ include( "scip-java", "scip-snapshots", "scip-snapshots-java-common", + "scip-snapshots-java-25", "scip-snapshots-kotlin-common", ) project(":scip-snapshots-java-common").projectDir = file("scip-snapshots/cases/java/common") +project(":scip-snapshots-java-25").projectDir = file("scip-snapshots/cases/java-25") project(":scip-snapshots-kotlin-common").projectDir = file("scip-snapshots/cases/kotlin/common") From cd2d6f281ecf1d7a18fcc2babf846b2995c62a8d Mon Sep 17 00:00:00 2001 From: jupblb Date: Sun, 28 Jun 2026 14:58:03 +0200 Subject: [PATCH 3/5] Document Java LTS support policy --- docs/getting-started.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 6e4200e95..d87f7d0c5 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -199,7 +199,8 @@ com.sourcegraph.scip_java.ScipJava.INSTANCE.printHelp(Console.out) The `scip-java` indexer is implemented as a Java compiler plugin that runs as part of your regular compilation in the build tool. By using Java compiler APIs, `scip-java` is able to generate accurate indexing information for a broad range -of Java versions. +of Java versions. The project targets support for the three most recent Java LTS +releases. | Java version | Support | Tracking issue | | ------------ | ---------------------------- | -------------- | From f4bc39ca670f6c8f78893f2543a88aa20f779e52 Mon Sep 17 00:00:00 2001 From: jupblb Date: Sun, 28 Jun 2026 15:03:42 +0200 Subject: [PATCH 4/5] Use remote JDK 25 for Bazel --- .bazelrc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.bazelrc b/.bazelrc index 8bdcbd509..ad46cd0ed 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,4 +1,4 @@ common --java_language_version=17 common --tool_java_language_version=17 -common --java_runtime_version=remotejdk_21 -common --tool_java_runtime_version=remotejdk_21 +common --java_runtime_version=remotejdk_25 +common --tool_java_runtime_version=remotejdk_25 From bf778155bc5feebc163aa18afea561d4e84cfd2b Mon Sep 17 00:00:00 2001 From: jupblb Date: Sun, 28 Jun 2026 15:10:08 +0200 Subject: [PATCH 5/5] Let Gradle select snapshot cases --- scip-snapshots/build.gradle.kts | 6 +- .../tests/MinimizedSnapshotScipGenerator.java | 84 +++++++++---------- 2 files changed, 44 insertions(+), 46 deletions(-) diff --git a/scip-snapshots/build.gradle.kts b/scip-snapshots/build.gradle.kts index 839ad0ba8..1238d221d 100644 --- a/scip-snapshots/build.gradle.kts +++ b/scip-snapshots/build.gradle.kts @@ -38,7 +38,11 @@ val snapshotTargetroots = // The case list, expected-golden layout and per-case indexing flags are fixed test metadata defined // in MinimizedSnapshotScipGenerator; the build only supplies the build-time paths it owns. val snapshotProperties = - mutableMapOf("snapshot.sourceroot" to rootProject.rootDir.absolutePath) + mutableMapOf( + "snapshot.sourceroot" to rootProject.rootDir.absolutePath, + "snapshot.case.ids" to snapshotCaseInputs.joinToString(",") { it.id }, + "snapshot.enabledCases" to enabledSnapshotCaseInputs.joinToString(",") { it.id }, + ) .apply { snapshotTargetroots.forEach { (snapshotCase, targetroot) -> put(snapshotCase.targetrootProperty, targetroot.singleFile.absolutePath) diff --git a/scip-snapshots/src/main/java/tests/MinimizedSnapshotScipGenerator.java b/scip-snapshots/src/main/java/tests/MinimizedSnapshotScipGenerator.java index 74f06996a..05eb5a6d1 100644 --- a/scip-snapshots/src/main/java/tests/MinimizedSnapshotScipGenerator.java +++ b/scip-snapshots/src/main/java/tests/MinimizedSnapshotScipGenerator.java @@ -10,7 +10,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.scip_code.scip.Document; @@ -23,25 +25,10 @@ public class MinimizedSnapshotScipGenerator { private static final List SNAPSHOT_CASES = Arrays.asList( + new SnapshotCaseSpec("java-common", "scip-snapshots/expected/java/common", false, "17"), + new SnapshotCaseSpec("java-25", "scip-snapshots/expected/java-25", false, "25"), new SnapshotCaseSpec( - "java-common", - "scip-snapshots/expected/java/common", - "snapshot.case.java-common.targetroot", - false, - "17"), - new SnapshotCaseSpec( - "java-25", - "scip-snapshots/expected/java-25", - "snapshot.case.java-25.targetroot", - false, - "25", - 25), - new SnapshotCaseSpec( - "kotlin-common", - "scip-snapshots/expected/kotlin/common", - "snapshot.case.kotlin-common.targetroot", - true, - "17")); + "kotlin-common", "scip-snapshots/expected/kotlin/common", true, "17")); public static final class SnapshotCase { public final String id; @@ -71,50 +58,25 @@ public SnapshotContext context() { private static final class SnapshotCaseSpec { private final String id; private final String expectDirectory; - private final String targetrootProperty; private final boolean aggregateNoEmitInverseRelationships; private final String jdkVersion; - private final int minimumJavaFeature; private SnapshotCaseSpec( String id, String expectDirectory, - String targetrootProperty, boolean aggregateNoEmitInverseRelationships, String jdkVersion) { - this( - id, - expectDirectory, - targetrootProperty, - aggregateNoEmitInverseRelationships, - jdkVersion, - 0); - } - - private SnapshotCaseSpec( - String id, - String expectDirectory, - String targetrootProperty, - boolean aggregateNoEmitInverseRelationships, - String jdkVersion, - int minimumJavaFeature) { this.id = id; this.expectDirectory = expectDirectory; - this.targetrootProperty = targetrootProperty; this.aggregateNoEmitInverseRelationships = aggregateNoEmitInverseRelationships; this.jdkVersion = jdkVersion; - this.minimumJavaFeature = minimumJavaFeature; - } - - private boolean isEnabled() { - return Runtime.version().feature() >= minimumJavaFeature; } private SnapshotCase toSnapshotCase(Path sourceroot) { return new SnapshotCase( id, sourceroot.resolve(expectDirectory), - requiredPathProperty(targetrootProperty), + requiredPathProperty(targetrootProperty(id)), aggregateNoEmitInverseRelationships, jdkVersion); } @@ -204,16 +166,48 @@ public void onTargetroot( */ public static List snapshotCases() { Path sourceroot = requiredPathProperty("snapshot.sourceroot"); + Set buildCaseIds = requiredCsvProperty("snapshot.case.ids"); + Set enabledCaseIds = requiredCsvProperty("snapshot.enabledCases"); + Set specCaseIds = + SNAPSHOT_CASES.stream() + .map(snapshotCase -> snapshotCase.id) + .collect(Collectors.toCollection(LinkedHashSet::new)); + if (!buildCaseIds.equals(specCaseIds)) { + throw new IllegalStateException( + "Snapshot case metadata mismatch. Gradle cases=" + + buildCaseIds + + ", Java specs=" + + specCaseIds); + } + if (!specCaseIds.containsAll(enabledCaseIds)) { + throw new IllegalStateException( + "Enabled snapshot cases must be a subset of known cases. Enabled=" + + enabledCaseIds + + ", Java specs=" + + specCaseIds); + } return SNAPSHOT_CASES.stream() - .filter(SnapshotCaseSpec::isEnabled) + .filter(snapshotCase -> enabledCaseIds.contains(snapshotCase.id)) .map(snapshotCase -> snapshotCase.toSnapshotCase(sourceroot)) .collect(Collectors.toList()); } + private static String targetrootProperty(String id) { + return "snapshot.case." + id + ".targetroot"; + } + public static Path requiredPathProperty(String name) { return Paths.get(requiredProperty(name)); } + private static Set requiredCsvProperty(String name) { + String value = requiredProperty(name); + return Arrays.stream(value.split(",")) + .map(String::trim) + .filter(entry -> !entry.isEmpty()) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + private static String requiredProperty(String name) { String value = System.getProperty(name); if (value == null || value.trim().isEmpty()) {