Skip to content

SONARJAVA-7240: Improve S9398 to allow helper and builder types nested in interfaces - #6403

Open
romainbrenguier wants to merge 3 commits into
masterfrom
romain/sonarjava-7240
Open

romainbrenguier wants to merge 3 commits into
masterfrom
romain/sonarjava-7240

Conversation

@romainbrenguier

@romainbrenguier romainbrenguier commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

S9398 (Inner classes should not be declared inside interfaces) now uses a better heuristic to tell legitimate nested types from implementation code:

  • Still reported: a concrete nested class that implements the enclosing interface. That is implementation code living inside the contract. With semantic it uses isSubtypeOf; without semantic it compares the names of the direct supertypes.
  • No longer reported:
    • Helper types: classes that appear in the signature of a method declared by the enclosing interface (return type, parameter types, thrown exceptions, including inside type arguments and qualified names). These let the contract and its argument and result types live in the same file.
    • Builders: classes whose name ends with Builder.
  • Any other concrete nested class is still reported (unchanged).

Notes

  • Ruling expectations (eclipse-jetty, sonarqube-6.5) will probably change. Per the repo's process, the ruling PR is created automatically by CI.
  • The RSPEC description should get an "Exceptions" section listing the helper and builder cases.

🤖 Generated with Claude Code

Agent workflow

Tool link: https://github.com/SonarSource/languages-experimental-tooling/tree/romain/my-tickets/personal/romain-brenguier
PR created using uv run create_with_claude.py SONARJAVA-7240.txt

PR updated using uv run update_with_claude.py --prompt "Implement the action plan described in the document /tmp/action_plan_romain/sonarjava-7240.txt." -a "/tmp/action_plan_romain/sonarjava-7240.txt" -g "claude"

PR updated using uv run update_with_claude.py --prompt "Implement the action plan described in the document /tmp/action_plan_romain/sonarjava-7240.txt." -a "/tmp/action_plan_romain/sonarjava-7240.txt" -g "claude"

Iterated on the PR with uv run ci_loop.py for 2 iterations.
✔️ The PR is now ready for review.

…n interfaces

Nested classes are no longer reported when they are builders or appear in the
signature (return type, parameter types, thrown exceptions) of a method of the
enclosing interface, unless they implement that interface.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@hashicorp-vault-sonar-prod

hashicorp-vault-sonar-prod Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

SONARJAVA-7240

Comment thread java-checks/src/main/java/org/sonar/java/checks/InnerClassInInterfaceCheck.java Outdated
Comment thread java-checks/src/main/java/org/sonar/java/checks/InnerClassInInterfaceCheck.java Outdated
@datadog-sonarsource

datadog-sonarsource Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Pipelines

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: af2783e | Docs | Give us feedback!

@github-actions

Copy link
Copy Markdown
Contributor

⚖️ Ruling update ready for review: #6404

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Ruling Diff Summary

Detected changes in 3 rule files: 10 issues removed, 0 issues added.

S9398 (java) on eclipse-jetty - 1 issues removed, 0 issues added

Removed jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslHandshakeListener.java (line 57)

(source file not found at this revision: jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslHandshakeListener.java)
S9398 (java) on eclipse-jetty-similar-to-main - 1 issues removed, 0 issues added

Removed jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslHandshakeListener.java (line 57)

(source file not found at this revision: jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslHandshakeListener.java)
S9398 (java) on sonarqube-6.5 - 8 issues removed, 0 issues added

Removed src/main/java/org/sonar/server/authentication/event/AuthenticationEvent.java (line 97)

(source file not found at this revision: src/main/java/org/sonar/server/authentication/event/AuthenticationEvent.java)

Removed src/main/java/org/sonar/server/computation/task/projectanalysis/filemove/FileSimilarity.java (line 28)

(source file not found at this revision: src/main/java/org/sonar/server/computation/task/projectanalysis/filemove/FileSimilarity.java)

Removed src/main/java/org/sonar/server/computation/task/projectanalysis/filemove/MovedFilesRepository.java (line 37)

(source file not found at this revision: src/main/java/org/sonar/server/computation/task/projectanalysis/filemove/MovedFilesRepository.java)

Removed src/main/java/org/sonar/server/es/IndexDefinition.java (line 31)

(source file not found at this revision: src/main/java/org/sonar/server/es/IndexDefinition.java)

Removed src/main/java/org/sonar/server/organization/BillingValidations.java (line 47)

(source file not found at this revision: src/main/java/org/sonar/server/organization/BillingValidations.java)

Removed src/main/java/org/sonar/server/organization/OrganizationCreation.java (line 120)

(source file not found at this revision: src/main/java/org/sonar/server/organization/OrganizationCreation.java)

Removed src/main/java/org/sonar/server/organization/OrganizationCreation.java (line 126)

(source file not found at this revision: src/main/java/org/sonar/server/organization/OrganizationCreation.java)

Removed src/main/java/org/sonar/server/permission/ws/template/DefaultTemplatesResolver.java (line 43)

(source file not found at this revision: src/main/java/org/sonar/server/permission/ws/template/DefaultTemplatesResolver.java)

romainbrenguier and others added 2 commits October 26, 2026 13:29
…erface once

Helper types are now matched by symbol instead of simple name, so unrelated
qualified types and method type parameters no longer hide issues. The check
visits interfaces and collects signature types once per interface. The
name-based fallback for unknown types is removed, and ruling expectations
are updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ilar-to-main

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gitar-bot

gitar-bot Bot commented Oct 26, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 2 closed / 2 findings

🟡 Medium risk · Changing S9398’s helper-type exemptions could suppress findings for nested implementation classes misidentified by signature or Builder name.

Refines S9398 so helper types appearing in an interface method's signature and classes named *Builder are no longer reported, while concrete implementing nested classes still are. Helper detection now matches resolved symbols instead of simple names, and interface method signatures are scanned once rather than per nested class. No open issues remain.

✅ 2 closed
✅ Edge Case: Helper detection matches simple names, so unrelated types hide issues

📄 java-checks/src/main/java/org/sonar/java/checks/InnerClassInInterfaceCheck.java:95-109
TypeNamesCollector.visitIdentifier adds every identifier in a method signature to a set of bare strings, and isHelperType only checks names.contains(className). That set also picks up package segments, the simple names of unrelated qualified types, and method type-parameter names. For example, interface Parser { Optional<org.other.Result> parse(); class Result {} } adds Result from org.other.Result, so the unused nested Result is treated as compliant and its issue is suppressed. implementsInterface already uses semantic types when they are available, so this matching can do the same: compare the resolved signature types' symbols with classTree.symbol(), and fall back to names only when the type is unknown.

✅ Performance: Interface method signatures are re-scanned for every nested class

📄 java-checks/src/main/java/org/sonar/java/checks/InnerClassInInterfaceCheck.java:60 📄 java-checks/src/main/java/org/sonar/java/checks/InnerClassInInterfaceCheck.java:100-105
visitNode calls isHelperType once per concrete nested class. Each call builds a new TypeNamesCollector and walks every method signature in the enclosing interface again, so the work is O(nested classes × methods) when the interface's set of signature names only needs to be built once. Fix: compute the name set once per interface, for example by caching it in a map keyed by the interface ClassTree and clearing that map in leaveFile.

Verification Rules 2/2 met Repos 4 inspected

Functional validation No results

Rules 2 passed 1 not applicable

✅ Internal artifact registries
✅ Repository license compliance

Broader impact Repos 4 inspected
Cross-repo 4 repositories inspected 0 open impacts

SonarSource/sonarlint-vscode Inspected
SonarSource/sonarlint-intellij Inspected
SonarSource/sonarlint-language-server Inspected
SonarSource/sonar-java-symbolic-execution 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.

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

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

Was this helpful? React with 👍 / 👎 | Gitar

@github-actions

Copy link
Copy Markdown
Contributor

⚖️ Ruling update ready for review: #6405

@sonarqube-next

Copy link
Copy Markdown
Contributor

@romainbrenguier
romainbrenguier marked this pull request as ready for review October 26, 2026 13:08
@romainbrenguier
romainbrenguier requested review from asya-vorobeva, nathsou and rombirli and removed request for asya-vorobeva and rombirli October 26, 2026 13:08
* or when it appears in the signature (return type, parameter types or thrown exceptions)
* of a method declared by the interface.
*/
private static boolean isHelperType(ClassTree classTree, Set<Symbol> signatureTypes) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[nit] Generic helper types are still reported

interface GenericDto {
  Helper<String> helper();
  class Helper<T> {}
}

Helper is incorrectly reported despite being an interface return type. The collected Helper<String> symbol differs from the Helper<T> declaration, so signatureTypes.contains(classTree.symbol()) returns false. Normalize both symbols to their declarations or erased types before comparing, and add regression coverage for generic return and parameter types.

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.

2 participants