Skip to content

SONARJAVA-7300 Decouple the Sonar Java frontend from Eclipse JDT core - #6485

Open
aurelien-coet-sonarsource wants to merge 4 commits into
masterfrom
ac/implementation-agnostic-frontend-2
Open

aurelien-coet-sonarsource wants to merge 4 commits into
masterfrom
ac/implementation-agnostic-frontend-2

Conversation

@aurelien-coet-sonarsource

@aurelien-coet-sonarsource aurelien-coet-sonarsource commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary by Gitar

  • Test coverage:
    • Removed FrontendApiTest and replaced it with JdtClassesLayoutTest and ClassLayoutTestUtils to check the JDT layout
  • Semantic utilities:
    • Made WRAPPER_TO_PRIMITIVE and PRIMITIVE_TO_WRAPPER public immutable maps in JUtils
  • Annotation defaults:
    • Annotation element defaults now resolve through Symbol.MethodSymbol.annotationElementDefaultValue() as semantic API types

This will update automatically on new commits.

@hashicorp-vault-sonar-prod hashicorp-vault-sonar-prod Bot changed the title Decouple the Sonar Java frontend from Eclipse JDT core SONARJAVA-7300 Decouple the Sonar Java frontend from Eclipse JDT core Nov 4, 2026
@hashicorp-vault-sonar-prod

hashicorp-vault-sonar-prod Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

SONARJAVA-7300

@aurelien-coet-sonarsource
aurelien-coet-sonarsource force-pushed the ac/implementation-agnostic-frontend-2 branch from d50bf37 to 80b9ad3 Compare November 4, 2026 14:51
@aurelien-coet-sonarsource

Copy link
Copy Markdown
Contributor Author

/pvf

@hashicorp-vault-sonar-prod

Copy link
Copy Markdown
Contributor

📊 Performance Validation complete
🆕 0 new · 🗑️ 0 lost issues
View dashboard

@aurelien-coet-sonarsource
aurelien-coet-sonarsource force-pushed the ac/implementation-agnostic-frontend-2 branch 6 times, most recently from c0370cd to b88c5c0 Compare November 6, 2026 09:51
Base automatically changed from ac/implementation-agnostic-frontend-1 to master November 6, 2026 10:59
@aurelien-coet-sonarsource
aurelien-coet-sonarsource force-pushed the ac/implementation-agnostic-frontend-2 branch from b88c5c0 to c4b1cf5 Compare November 6, 2026 11:00
@aurelien-coet-sonarsource

Copy link
Copy Markdown
Contributor Author

/pvf

@hashicorp-vault-sonar-prod

Copy link
Copy Markdown
Contributor

📊 Performance Validation complete
🆕 0 new · 🗑️ 0 lost issues
View dashboard

@aurelien-coet-sonarsource
aurelien-coet-sonarsource force-pushed the ac/implementation-agnostic-frontend-2 branch 2 times, most recently from 58bc4a9 to d4d7696 Compare November 6, 2026 13:50
@aurelien-coet-sonarsource

Copy link
Copy Markdown
Contributor Author

/pvf

private static final Logger LOG = LoggerFactory.getLogger(JDTParserConfig.class);

private static final String MAXIMUM_ECJ_WARNINGS = "42000";
private static final Set<String> JRE_JARS = new HashSet<>(Arrays.asList("rt.jar", "jrt-fs.jar", "android.jar"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Cross-PR: Release-API JDK resolution from #6459 lost or uncompilable after refactor

Breaks when this PR and #6459 both merge, and the second one is rebased onto the neutral JParserConfig interface without moving the release-API logic into JDTParserConfig.jdtParser().

How

  1. This PR turns JParserConfig into an interface. Its astParser() now returns JParser, and it no longer has a classpath field (JParserConfig.java:29-35).
  2. The ECJ setup moved to JDTParserConfig.jdtParser(), which still passes the unfiltered classpath to setEnvironment and checks it against the old JRE_JARS set (JDTParserConfig.java:57,110). Nothing in it sets the release option.
  3. #6459 edits the old abstract JParserConfig. It adds OPTION_RELEASE, adds useReleaseApiOfConfiguredJdk(options) operating on classpath, and passes a filtered environment list to setEnvironment. Its tests use astParser() as an ASTParser together with JParser.parse(astParser, ...).
  4. As a result, #6459's code no longer compiles against this layout. If someone resolves the conflict by keeping this PR's version of the file, release-API JDK selection is dropped without any error.

Fix

  • This PR: If it lands second, port #6459's OPTION_RELEASE, useReleaseApiOfConfiguredJdk and filtered-environment logic into JDTParserConfig.jdtParser().
  • #6459: If it lands second, move the logic into JDTParserConfig.jdtParser(). Switch JParserTest/JParserTestUtils to JParserConfig.parse(unitName, source), and move the JParserConfigTest checks to JDTParserConfig.

Compared 96acea5 with #6459 at ac3fd7c

Was this helpful? React with 👍 / 👎

@hashicorp-vault-sonar-prod

Copy link
Copy Markdown
Contributor

📊 Performance Validation complete
🆕 0 new · 🗑️ 0 lost issues
View dashboard

@aurelien-coet-sonarsource
aurelien-coet-sonarsource force-pushed the ac/implementation-agnostic-frontend-2 branch 4 times, most recently from cba5e05 to 7e1aebc Compare November 9, 2026 10:52
@aurelien-coet-sonarsource
aurelien-coet-sonarsource marked this pull request as ready for review November 9, 2026 11:07
Use semantic interfaces for shared operations and expose annotation element defaults through MethodSymbol. Extract neutral nullability data while retaining JUtils and JSymbolMetadataNullabilityHelper.
Move JDT parsing and semantic implementations into org.sonar.java.jdt. Use the existing semantic and diagnostic API contracts directly, retain parser and shared utility contracts, and create environment cleanup callbacks in the JDT parser.
@aurelien-coet-sonarsource
aurelien-coet-sonarsource force-pushed the ac/implementation-agnostic-frontend-2 branch from 7e1aebc to c979713 Compare November 9, 2026 12:41
@aurelien-coet-sonarsource
aurelien-coet-sonarsource force-pushed the ac/implementation-agnostic-frontend-2 branch from c979713 to 5170c8d Compare November 9, 2026 12:56
@gitar-bot

gitar-bot Bot commented Nov 9, 2026

Copy link
Copy Markdown
Code Review ⚠️ Changes requested 4 closed / 6 findings

🟡 Medium risk · Refactoring the Java parser and semantic model behind backend-neutral contracts could alter AST or symbol results across analyses.

Decouples the Sonar Java frontend from Eclipse JDT core by introducing backend-neutral JParser and JParserConfig interfaces, moving JDT implementations into org.sonar.java.jdt, and routing superclass and interface lookup through the symbol API. Changes are requested because two open findings would break release-API JDK resolution from #6459 and source-path type resolution from #6308 if either lands second without porting its logic into JDTParserConfig.jdtParser(). Several earlier issues were addressed, including directSuperTypes now adding java.lang.Object for interfaces and arrays, and the SE repo test helper compiling against the new API.

⚠️ Cross-PR: Release-API JDK resolution from #6459 lost or uncompilable after refactor

📄 java-frontend/src/main/java/org/sonar/java/jdt/JDTParserConfig.java:57 📄 java-frontend/src/main/java/org/sonar/java/jdt/JDTParserConfig.java:90-104 📄 java-frontend/src/main/java/org/sonar/java/model/JParserConfig.java:29-35

Breaks when this PR and #6459 both merge, and the second one is rebased onto the neutral JParserConfig interface without moving the release-API logic into JDTParserConfig.jdtParser().

How

  1. This PR turns JParserConfig into an interface. Its astParser() now returns JParser, and it no longer has a classpath field (JParserConfig.java:29-35).
  2. The ECJ setup moved to JDTParserConfig.jdtParser(), which still passes the unfiltered classpath to setEnvironment and checks it against the old JRE_JARS set (JDTParserConfig.java:57,110). Nothing in it sets the release option.
  3. #6459 edits the old abstract JParserConfig. It adds OPTION_RELEASE, adds useReleaseApiOfConfiguredJdk(options) operating on classpath, and passes a filtered environment list to setEnvironment. Its tests use astParser() as an ASTParser together with JParser.parse(astParser, ...).
  4. As a result, #6459's code no longer compiles against this layout. If someone resolves the conflict by keeping this PR's version of the file, release-API JDK selection is dropped without any error.

Fix

  • This PR: If it lands second, port #6459's OPTION_RELEASE, useReleaseApiOfConfiguredJdk and filtered-environment logic into JDTParserConfig.jdtParser().
  • #6459: If it lands second, move the logic into JDTParserConfig.jdtParser(). Switch JParserTest/JParserTestUtils to JParserConfig.parse(unitName, source), and move the JParserConfigTest checks to JDTParserConfig.

Compared 96acea5 with #6459 at ac3fd7c

⚠️ Cross-PR: Source-path type resolution from #6308 dropped by the new JDT layout

📄 java-frontend/src/main/java/org/sonar/java/jdt/JDTParserConfig.java:69-70 📄 java-frontend/src/main/java/org/sonar/java/jdt/JDTParserConfig.java:110-112 📄 java-frontend/src/main/java/org/sonar/java/model/JParserConfig.java:44-50 📄 java-frontend/src/main/java/org/sonar/java/model/JParserConfig.java:78-81

Breaks when this PR and #6308 both merge, and the second one keeps this PR's Mode.create/JDTParserConfig.create signatures without threading SourcePath through.

How

  1. In this PR, Mode.create(javaVersion, classpath, ignoreUnnamed) delegates to a 3-argument JDTParserConfig.create, and ParserConfigConstructor still takes 3 arguments (JParserConfig.java:44-50,78-81).
  2. JDTParserConfig.jdtParser() always passes empty source-path and encoding arrays to setEnvironment (JDTParserConfig.java:110-112).
  3. #6308 adds a 4-argument Mode.create(..., sourcePath, ...), which JavaFrontend.scanBatch and JavaAstScanner call. It also adds a sourcePath field to the old abstract class, and its JParserConfigTest overrides public ASTParser astParser().
  4. As a result, the 4-argument callers and the astParser() override no longer compile. If the conflict is resolved by keeping this PR's files, sonar.java.sourcepath resolution is lost without any error.

Fix

  • This PR: If it lands second, add the SourcePath overload to Mode.create, ParserConfigConstructor, JDTParserConfig.create and the Batch/FileByFile constructors, and pass its roots and encodings to setEnvironment in jdtParser().
  • #6308: If it lands second, make the same threading changes in JDTParserConfig, and change the test override to target JDTParserConfig.jdtParser() instead of astParser().

Compared 96acea5 with #6308 at 7068bcd

✅ 4 closed
✅ Bug: directSuperTypes now adds java.lang.Object for interfaces and arrays

📄 java-frontend/src/main/java/org/sonar/java/model/SemanticUtils.java:146-157 📄 java-frontend/src/main/java/org/sonar/java/jdt/JTypeSymbol.java:108-119
The old directSuperTypes read typeBinding.getSuperclass(), which JDT returns as null for interfaces and arrays. The new version calls type.symbol().superClass(), and JTypeSymbol.convertSuperClass() returns java.lang.Object for those cases. So for an interface type, directSuperTypes now includes Object next to its interfaces, which is a behaviour change in a refactor that was meant to keep behaviour the same. There are no production callers yet, so nothing breaks today, but anyone who starts using the public utility gets the different result. To keep the old contract, skip the superclass when type.symbol().isInterface() or when the type is an array.

✅ Quality: Unused MapBuilder import left after switching to Map.of

📄 java-frontend/src/main/java/org/sonar/java/model/SemanticUtils.java:33 📄 java-frontend/src/main/java/org/sonar/java/model/SemanticUtils.java:40-54
This commit swaps MapBuilder.newMap()...build() for Map.of(...) in WRAPPER_TO_PRIMITIVE and PRIMITIVE_TO_WRAPPER. Nothing in SemanticUtils uses MapBuilder anymore, but import org.sonarsource.analyzer.commons.collections.MapBuilder; is still there. That is a new unused-import issue (S1128) in a commit whose goal is to fix the quality gate. Delete the import.

✅ Cross-Repo: SE repo test helper no longer compiles: JParser/JParserConfig API moved

📄 java-frontend/src/main/java/org/sonar/java/jdt/JParserConfig.java:53 📄 java-frontend/src/main/java/org/sonar/java/jdt/JParserConfig.java:70-84 📄 java-frontend/src/main/java/org/sonar/java/jdt/JParser.java:264 📄 java-frontend/src/main/java/org/sonar/java/jdt/JParser.java:272 📄 java-frontend/src/main/java/org/sonar/java/JavaParsers.java
Fix here and in SonarSource/sonar-java-symbolic-execution

Breaks in: SonarSource/sonar-java-symbolic-execution - java-symbolic-execution/java-symbolic-execution-plugin/src/test/java/org/sonar/java/se/utils/JParserTestUtils.java - JParserTestUtils.parse

This breaks SonarSource/sonar-java-symbolic-execution, in java-symbolic-execution/java-symbolic-execution-plugin/src/test/java/org/sonar/java/se/utils/JParserTestUtils.java (JParserTestUtils.parse).

That helper imports org.sonar.java.model.JParser and org.sonar.java.model.JParserConfig. It builds its parser with JParserConfig.Mode.FILE_BY_FILE.create(...).astParser(), passes it to JParser.parse(ASTParser, version, unitName, source), and reads JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION. This PR changes all of that:

  • JParser moves to org.sonar.java.jdt as a package-private final class with a package-private parse.
  • JParserConfig moves to org.sonar.java.jdt, loses Mode, and its astParser() becomes package-private.
  • MAXIMUM_SUPPORTED_JAVA_VERSION moves to org.sonar.java.JavaParsers.

Once the SE repo bumps to this frontend, the helper no longer compiles, and neither does any SE test that depends on it. The same pair of APIs came up in earlier reviews (JParser.parse SE dependency, astParser SE dependency). The PR's goal is to decouple the frontend from JDT, so the move is intended and the consumer has to migrate. The replacement already exists: JavaParsers.Mode.FILE_BY_FILE.create(version, classpath).parse(unitName, source).

Here: Record in the release or migration notes that org.sonar.java.model.JParser/JParserConfig were removed, and name JavaParsers.MAXIMUM_SUPPORTED_JAVA_VERSION and JavaParsers.Mode.X.create(...).parse(unitName, source) as the replacements. Coordinate the release with sonar-java-symbolic-execution.

There: In JParserTestUtils.parse, drop the JParserConfig/JParser/ASTParser imports and call the new JavaParsers/JavaParser API.

In SonarSource/sonar-java-symbolic-execution - java-symbolic-execution/java-symbolic-execution-plugin/src/test/java/org/sonar/java/se/utils/JParserTestUtils.java:

import org.sonar.java.JavaParsers;

public static CompilationUnitTree parse(String unitName, String source, List<File> classpath) {
  return JavaParsers.Mode.FILE_BY_FILE
    .create(JavaParsers.MAXIMUM_SUPPORTED_JAVA_VERSION, classpath)
    .parse(unitName, source);
}
Closed: Quality: assertNotEquals(null, p1) no longer calls JProblem.equals(null)

📄 java-frontend/src/test/java/org/sonar/java/jdt/JProblemTest.java:91 📄 java-frontend/src/main/java/org/sonar/java/jdt/JProblem.java:78-86
JUnit 5's assertNotEquals(unexpected, actual) uses AssertionUtils.objectsAreEqual, which returns obj2 == null when the first argument is null and never calls p1.equals(null). The old assertFalse(p1.equals(null)) was the only check that reached the !(o instanceof JProblem that) → return false branch in JProblem.equals (JProblem.java:82-83). Every other comparison in the test passes a JProblem. That branch is now untested, and coverage on the moved JProblem class drops. Since this commit is meant to fix the quality gate, that matters. Put the receiver first so equals actually runs on it.

🤖 Prompt for agents
Code Review: Decouples the Sonar Java frontend from Eclipse JDT core by introducing backend-neutral `JParser` and `JParserConfig` interfaces, moving JDT implementations into `org.sonar.java.jdt`, and routing superclass and interface lookup through the symbol API. Changes are requested because two open findings would break release-API JDK resolution from #6459 and source-path type resolution from #6308 if either lands second without porting its logic into `JDTParserConfig.jdtParser()`. Several earlier issues were addressed, including `directSuperTypes` now adding `java.lang.Object` for interfaces and arrays, and the SE repo test helper compiling against the new API.

1. ⚠️ Cross-PR: Release-API JDK resolution from [#6459](<https://github.com/SonarSource/sonar-java/pull/6459>) lost or uncompilable after refactor
   Files: java-frontend/src/main/java/org/sonar/java/jdt/JDTParserConfig.java:57, java-frontend/src/main/java/org/sonar/java/jdt/JDTParserConfig.java:90-104, java-frontend/src/main/java/org/sonar/java/model/JParserConfig.java:29-35

   **Breaks when** this PR and [#6459](<https://github.com/SonarSource/sonar-java/pull/6459>) both merge, and the second one is rebased onto the neutral `JParserConfig` interface without moving the release-API logic into `JDTParserConfig.jdtParser()`.
   
   **How**
   
   1. This PR turns `JParserConfig` into an interface. Its `astParser()` now returns `JParser`, and it no longer has a `classpath` field (`JParserConfig.java:29-35`).
   2. The ECJ setup moved to `JDTParserConfig.jdtParser()`, which still passes the unfiltered classpath to `setEnvironment` and checks it against the old `JRE_JARS` set (`JDTParserConfig.java:57,110`). Nothing in it sets the release option.
   3. [#6459](<https://github.com/SonarSource/sonar-java/pull/6459>) edits the old abstract `JParserConfig`. It adds `OPTION_RELEASE`, adds `useReleaseApiOfConfiguredJdk(options)` operating on `classpath`, and passes a filtered environment list to `setEnvironment`. Its tests use `astParser()` as an `ASTParser` together with `JParser.parse(astParser, ...)`.
   4. As a result, [#6459](<https://github.com/SonarSource/sonar-java/pull/6459>)'s code no longer compiles against this layout. If someone resolves the conflict by keeping this PR's version of the file, release-API JDK selection is dropped without any error.
   
   **Fix**
   
   - **This PR:** If it lands second, port [#6459](<https://github.com/SonarSource/sonar-java/pull/6459>)'s `OPTION_RELEASE`, `useReleaseApiOfConfiguredJdk` and filtered-environment logic into `JDTParserConfig.jdtParser()`.
   - **[#6459](<https://github.com/SonarSource/sonar-java/pull/6459>):** If it lands second, move the logic into `JDTParserConfig.jdtParser()`. Switch `JParserTest`/`JParserTestUtils` to `JParserConfig.parse(unitName, source)`, and move the `JParserConfigTest` checks to `JDTParserConfig`.
   
   _Compared `96acea5` with [#6459](<https://github.com/SonarSource/sonar-java/pull/6459>) at `ac3fd7c`_

2. ⚠️ Cross-PR: Source-path type resolution from [#6308](<https://github.com/SonarSource/sonar-java/pull/6308>) dropped by the new JDT layout
   Files: java-frontend/src/main/java/org/sonar/java/jdt/JDTParserConfig.java:69-70, java-frontend/src/main/java/org/sonar/java/jdt/JDTParserConfig.java:110-112, java-frontend/src/main/java/org/sonar/java/model/JParserConfig.java:44-50, java-frontend/src/main/java/org/sonar/java/model/JParserConfig.java:78-81

   **Breaks when** this PR and [#6308](<https://github.com/SonarSource/sonar-java/pull/6308>) both merge, and the second one keeps this PR's `Mode.create`/`JDTParserConfig.create` signatures without threading `SourcePath` through.
   
   **How**
   
   1. In this PR, `Mode.create(javaVersion, classpath, ignoreUnnamed)` delegates to a 3-argument `JDTParserConfig.create`, and `ParserConfigConstructor` still takes 3 arguments (`JParserConfig.java:44-50,78-81`).
   2. `JDTParserConfig.jdtParser()` always passes empty source-path and encoding arrays to `setEnvironment` (`JDTParserConfig.java:110-112`).
   3. [#6308](<https://github.com/SonarSource/sonar-java/pull/6308>) adds a 4-argument `Mode.create(..., sourcePath, ...)`, which `JavaFrontend.scanBatch` and `JavaAstScanner` call. It also adds a `sourcePath` field to the old abstract class, and its `JParserConfigTest` overrides `public ASTParser astParser()`.
   4. As a result, the 4-argument callers and the `astParser()` override no longer compile. If the conflict is resolved by keeping this PR's files, `sonar.java.sourcepath` resolution is lost without any error.
   
   **Fix**
   
   - **This PR:** If it lands second, add the `SourcePath` overload to `Mode.create`, `ParserConfigConstructor`, `JDTParserConfig.create` and the Batch/FileByFile constructors, and pass its roots and encodings to `setEnvironment` in `jdtParser()`.
   - **[#6308](<https://github.com/SonarSource/sonar-java/pull/6308>):** If it lands second, make the same threading changes in `JDTParserConfig`, and change the test override to target `JDTParserConfig.jdtParser()` instead of `astParser()`.
   
   _Compared `96acea5` with [#6308](<https://github.com/SonarSource/sonar-java/pull/6308>) at `7068bcd`_

Verification Rules 2/2 met Knowledge 🔎 2 PRs 3 inspected

Functional validation No results

Rules 2 passed 1 not applicable

✅ Internal artifact registries
✅ Repository license compliance

Knowledge 🔎 2 informed a finding

🔎 JParserConfig.astParser() / JParser.parse(ASTParser, String, String, String): dependency with SonarSource/sonar-java-symbolic-execution informed a finding, resolved -> SE repo test helper no longer compiles: JParser/JParserConfig API moved
🔎 org.sonar.java.model.JParser.parse(ASTParser, String, String, String): dependency with SonarSource/sonar-java-symbolic-execution informed a finding, resolved -> SE repo test helper no longer compiles: JParser/JParserConfig API moved

Broader impact PRs 3 inspected
Cross-PR 3 PRs inspected 2 open impacts

PR 6459: SONARJAVA-7279 Resolve the JDK API of the target release through the new jdt-package option Inspected
PR 6308: SONARJAVA-7137 Optionally rely on source paths to resolve types during analyses Inspected
PR 6489: SONARJAVA-7303 Update Sonar JDT Inspected

Auto-approval Not enabled

Set up

For more verification details, comment gitar display:verbose.

Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Showing compact verification results.
Unblock → Override a blocking verdict and allow merging.

Comment with these commands to change the behavior for this request:

Auto-apply Compact Unblock
gitar auto-apply:on         
gitar display:verbose         
gitar unblock         

Was this helpful? React with 👍 / 👎 | Gitar

@sonarqube-next

sonarqube-next Bot commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

@aurelien-coet-sonarsource

Copy link
Copy Markdown
Contributor Author

/pvf

@hashicorp-vault-sonar-prod

Copy link
Copy Markdown
Contributor

⚠️ Performance Validation finished without publishing a dashboard. See run logs.

Projects with unexpected PVF results:

  • org.apache.bcel-bcel
  • org.apache.commons-commons-configuration2
  • org.apache.opennlp-opennlp
  • org.codehaus.sonar-plugins.l10n-sonar-l10n-fr-plugin
  • xwiki-commons_master

@hashicorp-vault-sonar-prod

Copy link
Copy Markdown
Contributor

📊 Performance Validation complete
🆕 0 new · 🗑️ 0 lost issues
View dashboard

Projects with unexpected PVF results:

  • org.apache.opennlp-opennlp
  • org.codehaus.sonar-plugins.l10n-sonar-l10n-fr-plugin
  • xwiki-commons_master

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant