Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"ruleKey": "S8491",
"hasTruePositives": true,
"falseNegatives": 0,
"falsePositives": 0
}
5 changes: 5 additions & 0 deletions its/ruling/src/test/resources/guava/java-S8491.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"com.google.guava:guava:src/com/google/common/util/concurrent/Monitor.java": [
1017
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;

import org.sonar.java.ast.visitors.PublicApiChecker;
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
import org.sonar.plugins.java.api.tree.ClassTree;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.VariableTree;


public class TreeHelper {
private TreeHelper() {
Expand All @@ -48,6 +53,18 @@ public static Tree findClosestParentOfKind(Tree tree, Set<Tree.Kind> nodeKinds)
return null;
}

public static Tree reportTree(Tree tree) {
Tree reportTree = tree;
if (reportTree.is(PublicApiChecker.classKinds())) {
reportTree = ExpressionsHelper.reportOnClassTree((ClassTree) reportTree);
} else if (reportTree.is(PublicApiChecker.methodKinds())) {
reportTree = ((MethodTree) reportTree).simpleName();
} else if (reportTree.is(Tree.Kind.VARIABLE)) {
reportTree = ((VariableTree) reportTree).simpleName();
}
return reportTree;
}

private static class ReachableMethodsFinder extends BaseTreeVisitor {
private final Map<MethodTree, Void> reachableMethods = new IdentityHashMap<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,17 @@
package org.sonar.java.checks.helpers;

import java.util.Set;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.sonar.java.model.statement.BlockTreeImpl;
import org.sonar.plugins.java.api.tree.BlockTree;
import org.sonar.plugins.java.api.tree.ExpressionStatementTree;
import org.sonar.plugins.java.api.tree.ForEachStatement;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.IfStatementTree;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.MethodTree;
Expand Down Expand Up @@ -95,4 +100,88 @@ private static Tree assertFoundNode(Tree tree, Set<Tree.Kind> kinds, @Nullable T
}
return actual;
}

@ParameterizedTest(name = "[{index}] {1}")
@MethodSource("reportTreeTestCases")
void reportTree(String code, String description, TreeExtractor treeExtractor, @Nullable String expectedName) {
Tree tree = treeExtractor.extract(code);
Tree reportTree = TreeHelper.reportTree(tree);

if (expectedName != null) {
assertThat(reportTree).isInstanceOf(IdentifierTree.class);
assertThat(((IdentifierTree) reportTree).name()).isEqualTo(expectedName);
} else {
assertThat(reportTree).isSameAs(tree);
}
}

private static Stream<Arguments> reportTreeTestCases() {
return Stream.of(
Arguments.of(
"class MyClass { }",
"Class",
(TreeExtractor) TreeHelperTest::classTree,
"MyClass"
),
Arguments.of(
"interface MyInterface { }",
"Interface",
(TreeExtractor) TreeHelperTest::classTree,
"MyInterface"
),
Arguments.of(
"enum MyEnum { }",
"Enum",
(TreeExtractor) TreeHelperTest::classTree,
"MyEnum"
),
Arguments.of(
"record MyRecord() { }",
"Record",
(TreeExtractor) TreeHelperTest::classTree,
"MyRecord"
),
Arguments.of(
"@interface MyAnnotation { }",
"Annotation",
(TreeExtractor) TreeHelperTest::classTree,
"MyAnnotation"
),
Arguments.of(
newCode("void myMethod() { }"),
"Method",
(TreeExtractor) TreeHelperTest::methodTree,
"myMethod"
),
Arguments.of(
"class A { A() { } }",
"Constructor",
(TreeExtractor) code -> classTree(code).members().get(0),
"A"
),
Arguments.of(
"class A { String myField; }",
"Field",
(TreeExtractor) code -> classTree(code).members().get(0),
"myField"
),
Arguments.of(
newCode("void method() { int localVar = 42; }"),
"Local variable",
(TreeExtractor) code -> methodTree(code).block().body().get(0),
"localVar"
),
Arguments.of(
newCode("void method() { if (true) { } }"),
"Other tree (if statement)",
(TreeExtractor) code -> methodTree(code).block().body().get(0),
null
)
);
}

@FunctionalInterface
private interface TreeExtractor {
Tree extract(String code);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package checks;

class DanglingJavadocCheckSample {
private String name;

/**
* This is a dangling javadoc comment that will be ignored.
*/
/**
* This is the actual javadoc that will be used.
* @param name the name parameter
*/
public void setName(String name) { // Noncompliant {{Remove or merge the dangling Javadoc comment(s).}}
// ^^^^^^^
this.name = name;
}

/**
* Sets the name parameter.
* @param name the name parameter
*/
public void setNameCompliant(String name) { // Compliant
this.name = name;
}

/**
* First dangling comment
*/
/**
* Second dangling comment
*/
/**
* The actual comment for this field
*/
public String multipleJavadocs; // Noncompliant
// ^^^^^^^^^^^^^^^^
/**
* Dangling method comment
*/
/**
* Actual method comment
* @return the value
*/
public int getValue() { // Noncompliant
return 42;
}
}

/**
* Old documentation that is no longer relevant.
*/
/**
* Updated documentation for this class.
*/
class OldUser { // Noncompliant
private String name;
}

/**
* This traditional javadoc will be ignored.
*/
/// This markdown javadoc will be used.
/// Represents a customer entity.
class Customer { // Noncompliant
// ^^^^^^^^
private String customerId;
}

/**
* Updated documentation for this class.
*/
class UserCompliant { // Compliant
private String name;
}

/// Represents a customer entity.
class CustomerCompliant { // Compliant
private String customerId;
}

class NoJavadoc { // Compliant
private String field;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* SonarQube Java
* Copyright (C) 2012-2025 SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks;

import java.util.Arrays;
import java.util.List;
import org.sonar.check.Rule;
import org.sonar.java.ast.visitors.PublicApiChecker;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.tree.Tree;

import static org.sonar.java.checks.helpers.TreeHelper.reportTree;

@Rule(key = "S8491")
public class DanglingJavadocCheck extends IssuableSubscriptionVisitor {

@Override
public List<Tree.Kind> nodesToVisit() {
return Arrays.asList(PublicApiChecker.apiKinds());
}

@Override
public void visitNode(Tree tree) {
int javadocCount = PublicApiChecker.getApiJavadocsCount(tree);
if (javadocCount > 1) {
reportIssue(reportTree(tree), "Remove or merge the dangling Javadoc comment(s).");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import static org.sonar.java.checks.helpers.DeprecatedCheckerHelper.deprecatedAnnotation;
import static org.sonar.java.checks.helpers.DeprecatedCheckerHelper.getAnnotationAttributeValue;
import static org.sonar.java.checks.helpers.DeprecatedCheckerHelper.hasJavadocDeprecatedTagWithoutLegitimateDocumentation;
import static org.sonar.java.checks.helpers.DeprecatedCheckerHelper.reportTreeForDeprecatedTree;
import static org.sonar.java.checks.helpers.TreeHelper.reportTree;

@Rule(key = "S1133")
public class DeprecatedTagPresenceCheck extends IssuableSubscriptionVisitor {
Expand All @@ -41,7 +41,7 @@ public List<Tree.Kind> nodesToVisit() {
@Override
public void visitNode(Tree tree) {
if (hasDeprecatedAnnotation(tree) || hasJavadocDeprecatedTagWithoutLegitimateDocumentation(tree)) {
reportIssue(reportTreeForDeprecatedTree(tree), "Do not forget to remove this deprecated code someday.");
reportIssue(reportTree(tree), "Do not forget to remove this deprecated code someday.");
}
}

Expand All @@ -51,12 +51,8 @@ private static boolean hasDeprecatedAnnotation(Tree tree) {
return false;
}
Optional<Boolean> forRemovalValue = getAnnotationAttributeValue(annotation, "forRemoval", Boolean.class);
// If forRemoval was not specified, we consider the deprecated code should be removed in the future.
if (forRemovalValue.isEmpty()) {
return true;
}
// Otherwise, we check the value of forRemoval and return that, so that only @Deprecated(forRemoval = true) is considered.
return Boolean.TRUE.equals(forRemovalValue.get());
// If forRemoval was not specified or equals true, we consider the deprecated code should be removed in the future.
return forRemovalValue.map(Boolean.TRUE::equals).orElse(true);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import org.sonar.plugins.java.api.tree.VariableTree;
import org.sonarsource.analyzer.commons.annotations.DeprecatedRuleKey;

import static org.sonar.java.checks.helpers.DeprecatedCheckerHelper.reportTreeForDeprecatedTree;
import static org.sonar.java.checks.helpers.TreeHelper.reportTree;

@DeprecatedRuleKey(ruleKey = "MissingDeprecatedCheck", repositoryKey = "squid")
@Rule(key = "S1123")
Expand All @@ -39,10 +39,10 @@ void handleDeprecatedElement(Tree tree, @CheckForNull AnnotationTree deprecatedA
boolean hasDeprecatedAnnotation = deprecatedAnnotation != null;
if (hasDeprecatedAnnotation) {
if (!hasJavadocDeprecatedTag) {
reportIssue(reportTreeForDeprecatedTree(tree), "Add the missing @deprecated Javadoc tag.");
reportIssue(reportTree(tree), "Add the missing @deprecated Javadoc tag.");
}
} else if (hasJavadocDeprecatedTag) {
reportIssue(reportTreeForDeprecatedTree(tree), "Add the missing @Deprecated annotation.");
reportIssue(reportTree(tree), "Add the missing @Deprecated annotation.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,18 +156,6 @@ private static AnnotationTree deprecatedAnnotation(Iterable<AnnotationTree> anno
return null;
}

public static Tree reportTreeForDeprecatedTree(Tree tree) {
Tree reportTree = tree;
if (reportTree.is(PublicApiChecker.classKinds())) {
reportTree = ExpressionsHelper.reportOnClassTree((ClassTree) reportTree);
} else if (reportTree.is(PublicApiChecker.methodKinds())) {
reportTree = ((MethodTree) reportTree).simpleName();
} else if (reportTree.is(Tree.Kind.VARIABLE)) {
reportTree = ((VariableTree) reportTree).simpleName();
}
return reportTree;
}

private static boolean isDeprecated(AnnotationTree tree) {
return tree.annotationType().is(Kind.IDENTIFIER) &&
"Deprecated".equals(((IdentifierTree) tree.annotationType()).name());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* SonarQube Java
* Copyright (C) 2012-2025 SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks;

import org.junit.jupiter.api.Test;
import org.sonar.java.checks.verifier.CheckVerifier;

import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath;

class DanglingJavadocCheckTest {

@Test
void test() {
CheckVerifier.newVerifier()
.onFile(mainCodeSourcesPath("checks/DanglingJavadocCheckSample.java"))
.withCheck(new DanglingJavadocCheck())
.verifyIssues();
}

}
Loading
Loading