Skip to content
Merged
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
Expand Up @@ -24,8 +24,7 @@
915
],
"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnectionStatistics.java": [
24,
30
24
],
"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/ServletRequestHttpWrapper.java": [
193
Expand Down
3 changes: 1 addition & 2 deletions its/ruling/src/test/resources/eclipse-jetty/java-S1133.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@
915
],
"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnectionStatistics.java": [
24,
30
24
],
"org.eclipse.jetty:jetty-project:jetty-server/src/main/java/org/eclipse/jetty/server/ServletRequestHttpWrapper.java": [
193
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
class Foo {

@Deprecated(forRemoval = true)
public String getName; // Noncompliant

@Deprecated(forRemoval = false)
public String getName; // Compliant

@Deprecated
public int foo; // Noncompliant {{Do not forget to remove this deprecated code someday.}}
// ^^^

public void foo1() { // Compliant
}

@Deprecated
public void foo2() { // Noncompliant
}

/**
* @deprecated
*/
public void foo3() { // Noncompliant

}

/**
* @deprecated
*/
@Ignore
@Deprecated
public void foo4() { // Noncompliant
// ^^^^
}

@Deprecated
/**
* @deprecated
*/
public void foo5() { // Noncompliant
}

/*
* @deprecated
*/
@Deprecated
public int foo7() { // Noncompliant
}

/**
*
*/
@Deprecated
public void foo8() { // Noncompliant
}

@java.lang.Deprecated // Compliant - no one does this
public void foo9() {

@Deprecated
int local1 = 0; // Noncompliant

}

}

interface Bar {

@Deprecated
int foo(); // Noncompliant

}

// Test cases for SONARJAVA-6070: Legitimate deprecation documentation should NOT be flagged

class LegitimateDeprecation {

/**
* @deprecated Use the new DynEnum instead
*/
public void oldMethod1() { // Compliant
}

/**
* @deprecated Will be removed in Tomcat 10.
*/
public int getPollerThreadCount() { // Compliant
return 1;
}

/**
* @deprecated Please use {@link NewClass} instead
*/
public void oldMethod2() { // Compliant
}

/**
* @deprecated Replaced by newMethod()
*/
public void oldMethod3() { // Compliant
}

/**
* @deprecated Scheduled for removal in version 2.0
*/
public void oldMethod4() { // Compliant
}

/**
* @deprecated Use newApi() instead.
*/
public void oldMethod5() { // Compliant
}

/**
* @deprecated See {@link NewApi#betterMethod}
*/
public void oldMethod6() { // Compliant
}

/**
* @deprecated Prefer using modernMethod()
*/
public void oldMethod7() { // Compliant
}

/**
* @deprecated Migrate to the new API
*/
public void oldMethod8() { // Compliant
}

/**
* @deprecated Removed in version 3.0
*/
public void oldMethod9() { // Compliant
}

/**
* @deprecated To be removed in future releases
*/
public void oldMethod10() { // Compliant
}

/**
* @deprecated deprecated since version 1.5, use newMethod() instead
*/
public void oldMethod11() { // Compliant
}

/**
* @deprecated This is old and not useful
*/
public void oldMethod12() { // Noncompliant
}

/**
* @deprecated
*/
public void oldMethod13() { // Noncompliant
}

/**
* Some javadoc
* @deprecated This method is outdated
*/
public void oldMethod14() { // Noncompliant
}

// Use multiline comments

/**
* Returns the empty iterator.
*
* @deprecated Use
* newApi() instead.
* @since 1.0
*/
public static void oldMethod15() { // Compliant when having new tag after comment
}

/**
* Returns the empty iterator.
*
* @deprecated Use
* newApi() instead.
*/
public static void oldMethod16() { // Compliant when having endline after comment
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,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.hasJavadocDeprecatedTag;
import static org.sonar.java.checks.helpers.DeprecatedCheckerHelper.hasJavadocDeprecatedTagWithoutLegitimateDocumentation;
import static org.sonar.java.checks.helpers.DeprecatedCheckerHelper.reportTreeForDeprecatedTree;

@Rule(key = "S1133")
Expand All @@ -40,7 +40,7 @@ public List<Tree.Kind> nodesToVisit() {

@Override
public void visitNode(Tree tree) {
if (hasDeprecatedAnnotation(tree) || hasJavadocDeprecatedTag(tree)) {
if (hasDeprecatedAnnotation(tree) || hasJavadocDeprecatedTagWithoutLegitimateDocumentation(tree)) {
reportIssue(reportTreeForDeprecatedTree(tree), "Do not forget to remove this deprecated code someday.");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@
*/
package org.sonar.java.checks.helpers;

import java.util.Locale;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.CheckForNull;

import org.sonar.java.annotations.VisibleForTesting;
import org.sonar.java.ast.visitors.PublicApiChecker;
import org.sonar.java.model.expression.AssignmentExpressionTreeImpl;
import org.sonar.plugins.java.api.tree.AnnotationTree;
Expand All @@ -33,15 +39,83 @@

public class DeprecatedCheckerHelper {

private static final String DEPRECATED_TAG = "@deprecated";
private static final Kind[] CLASS_KINDS = PublicApiChecker.classKinds();
private static final Kind[] METHOD_KINDS = PublicApiChecker.methodKinds();

private static final Set<String> MIGRATION_GUIDANCE_KEYWORDS = Set.of(
"replaced by",
"see ",
"prefer",
"migrate to",
"{@link"
);

private static final Set<String> REMOVAL_TIMELINE_TERMS = Set.of(
"will be removed in",
"removed in version",
"to be removed in",
"scheduled for removal",
"deprecated since"
);

private static final Pattern DEPRECATED_TAG_CONTENT_PATTERN = Pattern.compile(
DEPRECATED_TAG + "(.*)(?:\\n\\s*\\*\\s*@|$)",
Pattern.DOTALL
);

private DeprecatedCheckerHelper() {
// Helper class, should not be implemented.
}

public static boolean hasJavadocDeprecatedTag(Tree tree) {
return PublicApiChecker.getApiJavadoc(tree).filter(comment -> comment.contains("@deprecated")).isPresent();
return PublicApiChecker.getApiJavadoc(tree)
.filter(comment -> comment.contains(DEPRECATED_TAG))
.isPresent();
}

public static boolean hasJavadocDeprecatedTagWithoutLegitimateDocumentation(Tree tree) {
return PublicApiChecker.getApiJavadoc(tree)
.filter(comment -> comment.contains(DEPRECATED_TAG))
.filter(comment -> !hasLegitimateDeprecationDocumentation(comment))
.isPresent();
}

@VisibleForTesting
static boolean hasLegitimateDeprecationDocumentation(String javadoc) {
String deprecatedSection = extractDeprecatedTagContent(javadoc);
if (deprecatedSection.isEmpty()) {
return false;
}

// Check for migration guidance indicators or removal timeline
return hasMigrationGuidance(deprecatedSection) || hasRemovalTimeline(deprecatedSection);
}

private static String extractDeprecatedTagContent(String javadoc) {
// Extract content from @deprecated (including linebreaks) until next javadoc tag or end
// Pattern: @deprecated followed by everything until (newline + whitespaces + * + whitespaces + @) or end
Matcher matcher = DEPRECATED_TAG_CONTENT_PATTERN.matcher(javadoc);

if (!matcher.find()) {
return "";
}

String content = matcher.group(1);
// Clean up javadoc formatting: remove leading asterisks and extra whitespace from continuation lines
return content.replaceAll("(?m)^\\s*\\*\\s*", " ").trim();
}

private static boolean hasMigrationGuidance(String deprecatedContent) {
String lowerContent = deprecatedContent.toLowerCase(Locale.ROOT);
return (lowerContent.contains("use") && (lowerContent.contains("instead") || lowerContent.contains("new")))
|| MIGRATION_GUIDANCE_KEYWORDS.stream().anyMatch(lowerContent::contains);
}

private static boolean hasRemovalTimeline(String deprecatedContent) {
String lowerContent = deprecatedContent.toLowerCase(Locale.ROOT);
return REMOVAL_TIMELINE_TERMS.stream()
.anyMatch(lowerContent::contains);
}

@CheckForNull
Expand Down
Loading
Loading