Skip to content

Commit cedbc59

Browse files
SONARJAVA-5537 implement S7476: comments must start with correct number of slashes (#5151)
1 parent 60ac7ed commit cedbc59

9 files changed

Lines changed: 311 additions & 1 deletion

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"ruleKey": "S7476",
3+
"hasTruePositives": true,
4+
"falseNegatives": 0,
5+
"falsePositives": 0
6+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"com.google.guava:guava:src/com/google/common/util/concurrent/CycleDetectingLockFactory.java": [
3+
477,
4+
799,
5+
811,
6+
884,
7+
896
8+
]
9+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package checks;
2+
3+
public class CommentsMustStartWithCorrectNumberOfSlashesCheckAfterJava23 {
4+
// This is a comment
5+
public void twoSlashes() {}
6+
/// javadoc using markdown
7+
public void threeSlashes() {}
8+
// Noncompliant@+1
9+
////javadoc using markdown {{Markdown documentation should start with exactly three slashes, no more.}}
10+
//^^^^
11+
public void fourSlashes() {}
12+
// Noncompliant@+1
13+
///// javadoc using markdown
14+
//^^^^
15+
public void fiveSlashes() {}
16+
17+
// //
18+
public void twoTimeTwoSlashes() {}
19+
20+
// /// //// /////
21+
/// // /// ////
22+
public void seriesOfSlashes(){}
23+
24+
/*
25+
* multiline comment
26+
*/
27+
28+
/*
29+
* multiline comment with Slashes
30+
//
31+
///
32+
////
33+
*/
34+
35+
/**
36+
//
37+
///
38+
////
39+
* @param input A string input to be processed.
40+
*/
41+
public void javadoc(String input){
42+
}
43+
44+
// Noncompliant@+3
45+
// Noncompliant@+4
46+
/// This is a javadoc
47+
//// invalid
48+
///
49+
//// invalid
50+
//^^^^
51+
public void markdownJavadoc() {
52+
}
53+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package checks;
2+
3+
public class CommentsMustStartWithCorrectNumberOfSlashesCheckBeforeJava23 {
4+
5+
// This is a comment
6+
public void twoSlashes() {}
7+
// Noncompliant@+1 {{A single-line comment should start with exactly two slashes, no more.}}
8+
///This is a comment
9+
//^^^
10+
public void threeSlashes() {}
11+
// Noncompliant@+1 {{A single-line comment should start with exactly two slashes, no more.}}
12+
//// This is a comment
13+
//^^^
14+
public void fourSlashes() {}
15+
16+
17+
// //
18+
public void twoTimeTwoSlashes() {}
19+
20+
// /// //// /////
21+
public void seriesOfSlashes(){}
22+
23+
/*
24+
* multiline comment
25+
*/
26+
27+
/*
28+
* multiline comment with Slashes
29+
//
30+
///
31+
////
32+
*/
33+
34+
/**
35+
*
36+
* @param input A string input to be processed.
37+
//
38+
///
39+
////
40+
*/
41+
42+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* SonarQube Java
3+
* Copyright (C) 2012-2025 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.java.checks;
18+
19+
import java.util.List;
20+
import org.sonar.check.Rule;
21+
import org.sonar.java.model.DefaultModuleScannerContext;
22+
import org.sonar.java.reporting.AnalyzerMessage;
23+
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
24+
import org.sonar.plugins.java.api.tree.SyntaxTrivia;
25+
import org.sonar.plugins.java.api.tree.Tree;
26+
27+
@Rule(key = "S7476")
28+
public class CommentsMustStartWithCorrectNumberOfSlashesCheck extends IssuableSubscriptionVisitor {
29+
private static final String BEFORE_JAVA_23 = "A single-line comment should start with exactly two slashes, no more.";
30+
private static final String AFTER_JAVA_23 = "Markdown documentation should start with exactly three slashes, no more.";
31+
private static final String INCORRECT_SLASHES_BEFORE_JAVA_23 = "///";
32+
private static final String INCORRECT_SLASHES_AFTER_JAVA_23 = "////";
33+
34+
@Override
35+
public List<Tree.Kind> nodesToVisit() {
36+
return List.of(Tree.Kind.TRIVIA);
37+
}
38+
39+
@Override
40+
public void visitTrivia(SyntaxTrivia syntaxTrivia) {
41+
if (syntaxTrivia.isComment(SyntaxTrivia.CommentKind.LINE) && syntaxTrivia.comment().startsWith(INCORRECT_SLASHES_BEFORE_JAVA_23)) {
42+
var span = LineSpan.fromComment(syntaxTrivia, 0, 0, INCORRECT_SLASHES_BEFORE_JAVA_23.length());
43+
reportIssue(span, BEFORE_JAVA_23);
44+
}
45+
46+
if (syntaxTrivia.isComment(SyntaxTrivia.CommentKind.MARKDOWN)) {
47+
String[] lines = syntaxTrivia.comment().split("\\R");
48+
for (int idx = 0; idx < lines.length; idx++) {
49+
String line = lines[idx];
50+
51+
if (line.trim().startsWith(INCORRECT_SLASHES_AFTER_JAVA_23)) {
52+
int startPos = line.indexOf(INCORRECT_SLASHES_AFTER_JAVA_23);
53+
var span = LineSpan.fromComment(syntaxTrivia, idx, startPos, startPos + INCORRECT_SLASHES_AFTER_JAVA_23.length());
54+
reportIssue(span, AFTER_JAVA_23);
55+
}
56+
}
57+
}
58+
}
59+
60+
private void reportIssue(LineSpan span, String message) {
61+
((DefaultModuleScannerContext) this.context).reportIssue(issueSingleLine(span,message));
62+
}
63+
64+
private AnalyzerMessage issueSingleLine(LineSpan span, String message) {
65+
var textSpan = new AnalyzerMessage.TextSpan(span.line, span.start, span.line, span.end);
66+
return new AnalyzerMessage(this, context.getInputFile(), textSpan, message, 0);
67+
}
68+
69+
/*
70+
* Represents a span of text within a single line, defined by its line number and start/end positions.
71+
*
72+
* @param line The line number where the span is located (1-based index).
73+
*
74+
* @param start The starting column position of the span (0-based index).
75+
*
76+
* @param end The ending column position of the span (0-based index, exclusive).
77+
*
78+
* example for line below:
79+
* /// a comment
80+
* ^^^
81+
* the LineSpan corresponds to the caret is LineSpan(aLine, 0, 3)
82+
*/
83+
private record LineSpan(int line, int start, int end) {
84+
public static LineSpan fromComment(SyntaxTrivia comment, int line, int startChar, int endChar) {
85+
int sourceLine = comment.range().start().line() + line;
86+
if (line == 0) {
87+
int offset = comment.range().start().columnOffset();
88+
return new LineSpan(sourceLine, offset + startChar, offset + endChar);
89+
} else {
90+
return new LineSpan(sourceLine, startChar, endChar);
91+
}
92+
}
93+
}
94+
95+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* SonarQube Java
3+
* Copyright (C) 2012-2025 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
8+
*
9+
* This program is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the Sonar Source-Available License for more details.
13+
*
14+
* You should have received a copy of the Sonar Source-Available License
15+
* along with this program; if not, see https://sonarsource.com/license/ssal/
16+
*/
17+
package org.sonar.java.checks;
18+
19+
import org.junit.jupiter.api.Test;
20+
import org.sonar.java.checks.verifier.CheckVerifier;
21+
22+
import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath;
23+
24+
class CommentsMustStartWithCorrectNumberOfSlashesCheckTest {
25+
26+
@Test
27+
void test_before_java23() {
28+
CheckVerifier.newVerifier()
29+
.onFile(mainCodeSourcesPath("checks/CommentsMustStartWithCorrectNumberOfSlashesCheckBeforeJava23.java"))
30+
.withCheck(new CommentsMustStartWithCorrectNumberOfSlashesCheck())
31+
.withJavaVersion(22)
32+
.verifyIssues();
33+
}
34+
35+
@Test
36+
void test_after_java23() {
37+
CheckVerifier.newVerifier()
38+
.onFile(mainCodeSourcesPath("checks/CommentsMustStartWithCorrectNumberOfSlashesCheckAfterJava23.java"))
39+
.withCheck(new CommentsMustStartWithCorrectNumberOfSlashesCheck())
40+
.withJavaVersion(23)
41+
.verifyIssues();
42+
}
43+
44+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<h2>Why is this an issue?</h2>
2+
<p>Starting in Java 23, comments beginning with three slashes <code>///</code> are interpreted as JavaDoc comments using Markdown syntax.</p>
3+
<p>In Java 22 and earlier, comments starting with more than 2 slashes were treated as normal comments. Accidentally writing comments with three or
4+
more slashes can lead to unintended JavaDoc being generated, when migrating to Java 23.</p>
5+
<h3>What is the potential impact?</h3>
6+
<ul>
7+
<li> Accidental JavaDoc generation where normal comments were intended. </li>
8+
<li> Misleading or broken documentation output. </li>
9+
<li> Increased maintenance burden when upgrading to Java 23 or later. </li>
10+
</ul>
11+
<h2>How to fix it</h2>
12+
<p>In versions of Java prior to 23, all comments should always start with exactly 2 slashes, and from Java 23 forward they should not start with more
13+
than 3.</p>
14+
<h3>Code examples</h3>
15+
<h4>Noncompliant code example</h4>
16+
<p>The following code will generate misleading Javadoc comments if migrated to Java 23:</p>
17+
<pre data-diff-id="1" data-diff-type="noncompliant">
18+
/// Some comment for the developers
19+
public abstract void foo();
20+
//// public void foo(String s){}
21+
public void foo(){}
22+
</pre>
23+
<h4>Compliant solution</h4>
24+
<pre data-diff-id="1" data-diff-type="compliant">
25+
// Some comment for the developers
26+
public abstract void foo();
27+
// public void foo(String s){}
28+
public void foo(){}
29+
</pre>
30+
<h2>Resources</h2>
31+
<h3>Documentation</h3>
32+
<ul>
33+
<li> <a href="https://openjdk.org/jeps/467">JEP 467: Markdown Documentation Comments</a> </li>
34+
</ul>
35+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"title": "Comments should start with the appropriate number of slashes",
3+
"type": "CODE_SMELL",
4+
"status": "ready",
5+
"remediation": {
6+
"func": "Constant\/Issue",
7+
"constantCost": "5min"
8+
},
9+
"tags": [
10+
"java23",
11+
"javadoc"
12+
],
13+
"defaultSeverity": "Major",
14+
"ruleSpecification": "RSPEC-7476",
15+
"sqKey": "S7476",
16+
"scope": "All",
17+
"quickfix": "unknown",
18+
"code": {
19+
"impacts": {
20+
"MAINTAINABILITY": "MEDIUM",
21+
"RELIABILITY": "MEDIUM"
22+
},
23+
"attribute": "CONVENTIONAL"
24+
}
25+
}

sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/Sonar_way_profile.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,7 @@
506506
"S7435",
507507
"S7466",
508508
"S7467",
509-
"S7475"
509+
"S7475",
510+
"S7476"
510511
]
511512
}

0 commit comments

Comments
 (0)