Skip to content

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

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

aurelien-coet-sonarsource wants to merge 4 commits into
ac/implementation-agnostic-frontend-1from
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

  • Package restructuring:
    • Moved JDT-specific classes (JParser, JSema, JWarning, JSymbolMetadata, JTypeSymbol) from org.sonar.java.model into new org.sonar.java.jdt package
    • Narrowed JParser and JProblem visibility; made KeywordThis and KeywordSuper public
  • Frontend-agnostic parser API:
    • Added JavaParser and JavaParsers interfaces and ParseResult class, replacing JParserConfig.Mode and JParserConfig.Result
    • Added JavaParser.parse(unitName, source) for parsing a single file
  • Problem abstraction:
    • Introduced JProblem in jdt package with ProblemKind-based classification, replacing the nested JProblem.Type enum
  • Symbol resolution:
    • Added *SymbolOrUnknown helper methods in JSema and routed tree symbol assignment through setSymbol/setSymbolType
    • Added Symbol.MethodSymbol.annotationElementDefaultValue() so default values no longer depend on JDT bindings
  • Utility renames:
    • Renamed JUtils to SemanticUtils and JSymbolMetadataNullabilityHelper to NullabilityHelper, with nullability data moved to NullabilityDataImpl
    • Changed SemanticUtils.directSuperTypes to use the symbol API, so interfaces no longer report a superclass

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

public abstract class JParserConfig {

public static final JavaVersion MAXIMUM_SUPPORTED_JAVA_VERSION = new JavaVersionImpl(JavaVersionImpl.MAX_SUPPORTED, true);
public abstract class JParserConfig implements JavaParser {

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-Repo: SE repo test helper no longer compiles: JParser/JParserConfig API moved

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);
}

Was this helpful? React with 👍 / 👎

JProblem p1 = new JProblem("message1", ProblemKind.UNDEFINED_TYPE);
assertFalse(p1.equals(null));
assertTrue(p1.equals(p1));
assertNotEquals(null, p1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Quality: assertNotEquals(null, p1) no longer calls JProblem.equals(null)

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.

AssertJ calls p1.equals(null), so the null/non-instance branch is still exercised:

assertThat(p1).isNotEqualTo(null);
  • Apply fix

Check the box to apply the fix or reply for a change | 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 3 times, most recently from a82afa6 to 6273e83 Compare November 4, 2026 15:43
@aurelien-coet-sonarsource
aurelien-coet-sonarsource force-pushed the ac/implementation-agnostic-frontend-2 branch from 6273e83 to dd81463 Compare November 4, 2026 15:45
@gitar-bot

gitar-bot Bot commented Nov 4, 2026 •

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

🟡 Medium risk · Refactors Java parsing and symbol resolution behind a frontend-independent API; changed type hierarchies or annotation defaults could affect analysis results.

Decouples the Sonar Java frontend from Eclipse JDT by moving JDT classes into org.sonar.java.jdt, adding frontend-agnostic JavaParser/JavaParsers interfaces, and renaming utilities to SemanticUtils and NullabilityHelper. Changes are requested: the JParser/JParserConfig removal breaks the JParserTestUtils.parse helper in sonar-java-symbolic-execution and needs a migration note plus a coordinated consumer update to JavaParsers. Also, assertNotEquals(null, p1) in JProblemTest no longer calls JProblem.equals(null), leaving that branch untested; put the receiver first so the check runs.

The directSuperTypes fix for interfaces and arrays and the unused import cleanup are resolved.

⚠️ 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);
}
💡 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.

AssertJ calls p1.equals(null), so the null/non-instance branch is still exercised
assertThat(p1).isNotEqualTo(null);
✅ 2 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.

🤖 Prompt for agents
Code Review: Decouples the Sonar Java frontend from Eclipse JDT by moving JDT classes into `org.sonar.java.jdt`, adding frontend-agnostic `JavaParser`/`JavaParsers` interfaces, and renaming utilities to `SemanticUtils` and `NullabilityHelper`. Changes are requested: the `JParser`/`JParserConfig` removal breaks the `JParserTestUtils.parse` helper in sonar-java-symbolic-execution and needs a migration note plus a coordinated consumer update to `JavaParsers`. Also, `assertNotEquals(null, p1)` in `JProblemTest` no longer calls `JProblem.equals(null)`, leaving that branch untested; put the receiver first so the check runs.
  
  The `directSuperTypes` fix for interfaces and arrays and the unused import cleanup are resolved.

1. ⚠️ Cross-Repo: SE repo test helper no longer compiles: JParser/JParserConfig API moved
   Files: 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`](https://github.com/SonarSource/sonar-java-symbolic-execution)**
   
   **Breaks in:** [`SonarSource/sonar-java-symbolic-execution`](https://github.com/SonarSource/sonar-java-symbolic-execution) - [`java-symbolic-execution/java-symbolic-execution-plugin/src/test/java/org/sonar/java/se/utils/JParserTestUtils.java`](https://github.com/SonarSource/sonar-java-symbolic-execution/blob/HEAD/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](https://github.com/SonarSource/sonar-java/pull/6407), [astParser SE dependency](https://github.com/SonarSource/sonar-java/pull/6456)). 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);
   }
   ````

2. 💡 Quality: assertNotEquals(null, p1) no longer calls JProblem.equals(null)
   Files: 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.

   Fix (AssertJ calls p1.equals(null), so the null/non-instance branch is still exercised):
   assertThat(p1).isNotEqualTo(null);

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

Functional validation No results

Rules 1 passed 2 not applicable

✅ 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, open -> 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, open -> SE repo test helper no longer compiles: JParser/JParserConfig API moved

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

PR 6479: SONARJAVA-7297 Decouple Problems and Warnings from Eclipse JDT core specifics Inspected
PR 6459: SONARJAVA-7279 Resolve the JDK API of the target release through the new jdt-package option Inspected
PR 6394: SONARJAVA-7229 Add telemetry for Java semantic degradation and classpath configuration 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 4, 2026 •

Copy link
Copy Markdown
Contributor

Quality Gate failed Quality Gate failed

Failed conditions
1 New issue

See analysis details on SonarQube

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE SonarQube for IDE

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