Skip to content

SONARJAVA-6944: Implement rule S9387 TestNG Javadoc tags should be converted to annotations - #6124

Merged
romainbrenguier merged 9 commits into
masterfrom
romain/new-rule-s9387-sonarjava-6944
Sep 18, 2026
Merged

romainbrenguier merged 9 commits into
masterfrom
romain/new-rule-s9387-sonarjava-6944

Conversation

@romainbrenguier

@romainbrenguier romainbrenguier commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Implement new rule S9387 that detects TestNG-specific Javadoc tags (@test, @beforeMethod, @afterMethod, @beforeClass, @afterClass, @dataProvider, etc.) used as configuration markers instead of proper TestNG annotations
  • Case-insensitive matching for all 15 TestNG tags with secondary locations when multiple tags appear on a single method
  • Includes test sample with compliant and noncompliant examples covering edge cases (case variants, mixed standard/TestNG tags, non-Javadoc comments)

Test plan

  • Unit tests pass (with and without semantic analysis)
  • CI build passes
  • Ruling tests updated if needed (auto-PR)

🤖 Generated with Claude Code

Agent workflow

Tool link: Tool link: https://github.com/SonarSource/languages-experimental-tooling/tree/romain/my-tickets/personal/romain-brenguier
Addressed review comments in pr_report_6124.md using uv run address_reviews.py pr_report_6124.md

…nverted to annotations

Detect TestNG-specific Javadoc tags (@test, @beforeMethod, @afterMethod, etc.)
used as configuration markers instead of proper TestNG annotations (@test,
@BeforeMethod, @AfterMethod, etc.). Case-insensitive matching with support
for all 15 TestNG tags and secondary locations for multiple tags per method.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@hashicorp-vault-sonar-prod

hashicorp-vault-sonar-prod Bot commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

SONARJAVA-6944

Comment thread java-checks/src/main/java/org/sonar/java/checks/tests/TestNGJavadocTagsCheck.java Outdated
Comment thread java-checks/src/main/java/org/sonar/java/checks/tests/TestNGJavadocTagsCheck.java Outdated
Comment thread java-checks/src/main/java/org/sonar/java/checks/tests/TestNGJavadocTagsCheck.java Outdated
Comment thread java-checks/src/main/java/org/sonar/java/checks/tests/TestNGJavadocTagsCheck.java Outdated
romainbrenguier and others added 2 commits September 14, 2026 10:24
Addresses code review feedback:

1. Single-line Javadoc `/** @test */` is now detected by updating BLOCK_TAG_PATTERN to match tags following the `/**` opener
2. False positives from annotation examples in `<pre>` and `{@code}` blocks are eliminated by removing those regions before pattern matching
3. Issue and secondary locations now report precisely on the method name using `simpleName()` and avoid redundant operations
4. Type-only tags (`@beforeClass`, `@afterClass`, `@parameters`, `@listeners`) removed from method-level checks since they cannot be applied to methods

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…icate test

@BeforeClass and @afterclass are method-level annotations in TestNG
(@target(METHOD)), not type-only. The previous commit incorrectly
removed them from the tag map.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9387.html Outdated
romainbrenguier and others added 4 commits September 14, 2026 10:52
- Revert manually added @beforeClass/@afterclass entries in S9387.html
  (auto-generated from RSPEC, should not be hand-edited)
- Move @beforeClass/@afterclass noncompliant test cases above the
  "Compliant cases" section for clarity
- Add secondary location assertion to multipleTestNGTags test case

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace \s with [\t ] in regex patterns to avoid super-linear
backtracking (SonarQube S8786). Use tree.simpleName() for secondary
locations to match the primary issue location scope.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…in S9387

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@romainbrenguier
romainbrenguier marked this pull request as ready for review September 14, 2026 14:40

@nathsou nathsou left a comment

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.

The rule is well structured. I left four non-blocking coverage and false-positive concerns.

"ruleSpecification": "RSPEC-9387",
"sqKey": "S9387",
"scope": "All",
"quickfix": "unknown",

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.

This is a TestNG test-configuration rule, but All also runs it on production methods where tags such as @factory may be ordinary documentation. Sibling checks under checks/tests use the Tests scope. Could we switch this to Tests and regenerate the derived metadata/profile files?


while (matcher.find()) {
String tagName = matcher.group(1);
String annotation = TESTNG_TAGS_TO_ANNOTATIONS.get(tagName.toLowerCase(Locale.ROOT));

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.

Lowercasing makes annotation names indistinguishable from the legacy case-sensitive tags. For example, documentation containing @Test is the annotation used by TestNG is treated as obsolete @test. Could we match the documented legacy spellings exactly (@test, @beforeMethod, etc.) and keep uppercase annotation names compliant?


private static final Pattern BLOCK_TAG_PATTERN = Pattern.compile("(?:^|/\\*\\*)[\\t ]*+\\*?[\\t ]*+@(\\w+)", Pattern.MULTILINE);
private static final Pattern PRE_BLOCK_PATTERN = Pattern.compile("<pre>.*?</pre>", Pattern.DOTALL);
private static final Pattern CODE_TAG_PATTERN = Pattern.compile("\\{@code[\\t ][^}]*+\\}");

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.

This pattern requires a space or tab after {@code, so a valid multiline block such as `{@code

  • @test
  • void example() {}
  • }is not masked; the inner@Test` can then be reported as a block tag. Could we support line breaks here (or use structured Javadoc parsing) and add a multiline regression test?

.filter(trivia -> trivia.isComment(SyntaxTrivia.CommentKind.JAVADOC))
.forEach(trivia -> checkJavadoc((MethodTree) tree, trivia));
}

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.

Java 23 Markdown documentation comments are represented as SyntaxTrivia.CommentKind.MARKDOWN, so /// @test is currently skipped even though it is still a documentation-based declaration. If the rule is intended to cover all Java documentation comments, could we inspect MARKDOWN trivia too, as the existing documentation checks do?

- Switch rule scope from "All" to "Tests" to match sibling test rules
- Fix multiline {@code} blocks not being masked (support newlines after {@code)
- Add regression test for multiline {@code} block

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread java-checks/src/main/java/org/sonar/java/checks/tests/TestNGJavadocTagsCheck.java Outdated
gitar-bot[bot]

This comment was marked as resolved.

@datadog-sonarsource

This comment has been minimized.

Add missing 'tests' tag for Tests-scoped rule, move test sample to
test sources path, and remove ineffective DOTALL flag from regex.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gitar-bot
gitar-bot Bot dismissed their stale review September 17, 2026 11:38

✅ All code review findings resolved.

Configure merge blocking

@gitar-bot

gitar-bot Bot commented Sep 17, 2026

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

Implements rule S9387 to detect TestNG-specific Javadoc tags (@test, @beforeMethod, @afterMethod, etc.) that should be converted to annotations, with case-insensitive matching and secondary locations for multiple tags. All previously identified issues have been resolved, including fixes for single-line Javadoc detection, annotation example filtering, location spanning, scope configuration, and test placement.

✅ 10 closed
✅ Bug: Single-line Javadoc /* @test / is never detected

📄 java-checks/src/main/java/org/sonar/java/checks/tests/TestNGJavadocTagsCheck.java:36 📄 java-checks/src/main/java/org/sonar/java/checks/tests/TestNGJavadocTagsCheck.java:63-70 📄 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9387.html:19 📄 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9387.html:36-40 📄 java-checks-test-sources/default/src/main/java/checks/tests/TestNGJavadocTagsCheckSample.java:5-9
BLOCK_TAG_PATTERN anchors every match to ^ (MULTILINE), so a tag is only found when it starts a line, optionally after a leading *. For the single-line form /** @test */ the only ^ position is offset 0, where the next character is /, so the match fails and no issue is raised — yet that is exactly the form the rule description presents as noncompliant (Javadoc-style tags such as /** @test */, and the whole "How to fix it" list). Allow the tag to follow the /** opener, e.g. Pattern.compile("(?:^|/\*\*)\s*\*?\s*@(\w+)", Pattern.MULTILINE), and add a /** @test */ case to the sample file. (The same filter also ignores CommentKind.MARKDOWN, so /// @test doc comments are silently skipped.)

✅ Edge Case: Annotation examples in <pre>/{@code} Javadoc blocks are flagged

📄 java-checks/src/main/java/org/sonar/java/checks/tests/TestNGJavadocTagsCheck.java:36 📄 java-checks/src/main/java/org/sonar/java/checks/tests/TestNGJavadocTagsCheck.java:76-90 📄 java-checks-test-sources/default/src/main/java/checks/tests/TestNGJavadocTagsCheckSample.java:97-107
Matching is case-insensitive, so a Javadoc that documents usage by embedding a code sample raises a false positive: in `/**

  • @test
  • public void t() {}

/the line @testsatisfies^\s**?\s*@(\w+)and is reported as a legacy TestNG tag, even though it is illustrating the correct annotation. Skip regions inside

/{@code ...}` (or require the tag line to contain nothing but the tag) before reporting.

✅ Quality: Issue and secondary locations both span the whole method

📄 java-checks/src/main/java/org/sonar/java/checks/tests/TestNGJavadocTagsCheck.java:84-98
reportIssue(tree, ...) on a METHOD/CONSTRUCTOR tree highlights the entire declaration including the body, while every comparable check in this repo reports on ((MethodTree) tree).simpleName(); worse, each secondary location is built from the same tree, so "Also replace "@beforeMethod"..." points at the identical range as the primary message instead of at the second tag (matcher offsets relative to trivia.range().start() are available for that, see MarkdownJavadocSyntaxCheck). The if (secondaryLocations.isEmpty()) branch is also redundant since reportIssue accepts an empty list, and line 86 repeats the map lookup already held in annotation.

✅ Edge Case: Class-level tags unreachable while @listeners is method-only advice

📄 java-checks/src/main/java/org/sonar/java/checks/tests/TestNGJavadocTagsCheck.java:53 📄 java-checks/src/main/java/org/sonar/java/checks/tests/TestNGJavadocTagsCheck.java:57-59 📄 java-checks-test-sources/default/src/main/java/checks/tests/TestNGJavadocTagsCheckSample.java:91-95
nodesToVisit() only registers METHOD and CONSTRUCTOR, but listeners (and parameters) map to TestNG annotations that are declared on a type — org.testng.annotations.Listeners is @Target(TYPE). As a result a class Javadoc carrying @listeners/@beforeClass is never inspected, and when the tag appears on a method the rule advises adding @Listeners there, which does not compile. Either add Tree.Kind.CLASS/INTERFACE/ENUM/RECORD to the visited kinds, or drop the type-only tags from the map.

✅ Quality: Generated rule description S9387.html edited by hand

📄 sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S9387.html:39-40
This commit adds two <li> entries to S9387.html, but that folder's own README states the files are generated from RSPEC and "do not attempt to manually modify files here, nor submit Pull Request (PR) modifying description or rule metatada. Any PR will be systematically rejected." The next rule-api sync will drop the @beforeClass/@afterClass bullets, leaving the shipped rule description inconsistent with the implementation, which now raises on those two tags. Update RSPEC-9387 upstream and regenerate S9387.html/S9387.json rather than editing the resource in this repo.

...and 5 more closed from earlier reviews

Review coverage

Functional validation 1 of 1 objectives covered

Rules No rules evaluated

Auto-approval Not enabled · Set up

Implementation Status ✅ 1 of 1 objectives covered
✅ SONARJAVA-6944 - 1 of 1 objectives covered

This PR implements the rule S9387 to detect TestNG Javadoc tags and recommend converting them to annotations.

✅ 1 covered here
  • ✅ Implement rule S9387 TestNG Javadoc tags should be converted to annotations
Options

Auto-apply is off → Gitar will not commit updates to this branch.
Display: compact → Counting what did not apply, without listing it.

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

@sonarqube-next

Copy link
Copy Markdown
Contributor

@romainbrenguier
romainbrenguier merged commit 5873394 into master Sep 18, 2026
16 checks passed
@romainbrenguier
romainbrenguier deleted the romain/new-rule-s9387-sonarjava-6944 branch September 18, 2026 07:52
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