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
Expand Up @@ -20,11 +20,13 @@
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.util.ASTHelpers;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
Expand All @@ -37,7 +39,6 @@
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.tools.FileObject;

/**
Expand All @@ -54,9 +55,20 @@ public abstract class AbstractDeprecatedApiCheck extends BugChecker

private static final Logger log = Logger.getLogger(AbstractDeprecatedApiCheck.class.getName());

protected abstract boolean isDeprecationWarning(Tree tree, VisitorState state);
/**
* Returns true if the given symbol is deprecated in a way that should trigger this check.
*/
protected abstract boolean isDeprecationWarning(Symbol symbol);

protected abstract String getErrorDescription(Optional<String> qualifiedName);
/**
* Returns true if the enclosing context should suppress this warning.
*/
protected abstract boolean isEnclosingDeprecatedForSuppression(Symbol symbol);

/**
* Returns the error description to show in the diagnostic, using the qualified name of the deprecated symbol.
*/
protected abstract String getErrorDescription(String qualifiedName);

@Override
public final Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
Expand Down Expand Up @@ -84,13 +96,24 @@ private Description checkTree(Tree tree, VisitorState state) {
return Description.NO_MATCH;
}

if (!isDeprecationWarning(tree, state)) {
Symbol symbol = ASTHelpers.getSymbol(tree);

if (symbol == null) {
return Description.NO_MATCH;
}

if (!isDeprecationWarning(symbol)) {
return Description.NO_MATCH;
}

Optional<Symbol> symbol = Optional.ofNullable(ASTHelpers.getSymbol(tree));
if (isEnclosingDeprecated(state)) {
// Suppress this warning if the enclosing method or class is deprecated.
return Description.NO_MATCH;
}

Optional<ClassSymbol> owningClass = symbol.map(this::getOwningClass);
// Note: Symbol#enclClass() returns the class itself if symbol is a class, rather than
// the potentially enclosing class (for nested classes). This is what we want here.
Optional<ClassSymbol> owningClass = Optional.ofNullable(symbol.enclClass());
Optional<URI> sourceFileUri = owningClass.map(c -> c.sourcefile).map(FileObject::toUri);
if (sourceFileUri.isPresent() && isRegularFileOnSystem(sourceFileUri.get())) {
// If the source file is a regular file on the local file system, this means we're calling a deprecated API
Expand All @@ -115,8 +138,8 @@ && isRegularFileOnSystem(classFileUri.get())
return Description.NO_MATCH;
}

Optional<String> qualifiedName = symbol.map(
s -> s.owner.getQualifiedName() + "#" + s.getQualifiedName().toString());
String qualifiedName = symbol.owner.getQualifiedName() + "#"
+ symbol.getQualifiedName().toString();
String description = getErrorDescription(qualifiedName);
return buildDescription(tree).setMessage(description).build();
}
Expand All @@ -125,14 +148,25 @@ private boolean isImportStatement(VisitorState state) {
return ASTHelpers.findEnclosingNode(state.getPath(), ImportTree.class) != null;
}

@Nullable
private ClassSymbol getOwningClass(Symbol symbol) {
// The symbol itself may be a class symbol, in which case, there might not even be an owner.
Symbol owner = symbol;
while (owner != null && !(owner instanceof ClassSymbol)) {
owner = owner.owner;
/**
* Returns true if any of the enclosing nodes (methods/classes/etc) is deprecated
* (in a way that should suppress this warning).
*/
private boolean isEnclosingDeprecated(VisitorState state) {
for (Tree parent : state.getPath()) {
if (!(parent instanceof MethodTree || parent instanceof ClassTree)) {
// Only check for deprecation on methods and classes/interfaces/records/etc
continue;
}
Symbol symbol = ASTHelpers.getSymbol(parent);
if (symbol == null) {
continue;
}
if (isEnclosingDeprecatedForSuppression(symbol)) {
return true;
}
}
return (ClassSymbol) owner;
return false;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,8 @@

import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.Matchers;
import com.sun.source.tree.Tree;
import java.util.Optional;
import com.sun.tools.javac.code.Symbol;

/**
* This check is meant to replace usage of the `-Werror` and `-Xlint:deprecation` compiler flags, which cannot be
Expand Down Expand Up @@ -52,19 +48,21 @@ public final class DeprecatedApiUsage extends AbstractDeprecatedApiCheck {
+ "the DeprecatedApiUsage error-prone check, replacing the java compiler flag '-Xlint:deprecation.'"
+ " Use @SuppressWarnings(\"deprecation\") to suppress this error.";

private static final Matcher<Tree> DEPRECATED_SYMBOL =
Matchers.symbolMatcher((symbol, state) -> symbol.isDeprecated() && !symbol.isDeprecatedForRemoval());
@Override
protected boolean isDeprecationWarning(Symbol symbol) {
// Only trigger on deprecated symbols that are not deprecated for removal, to avoid conflicting with
// DeprecatedForRemovalApiUsage.
return symbol.isDeprecated() && !symbol.isDeprecatedForRemoval();
}

@Override
protected boolean isDeprecationWarning(Tree tree, VisitorState state) {
return DEPRECATED_SYMBOL.matches(tree, state);
protected boolean isEnclosingDeprecatedForSuppression(Symbol symbol) {
// Suppress this check if the enclosing context is deprecated, whether for removal or not.
return symbol.isDeprecated() || symbol.isDeprecatedForRemoval();
}

@Override
protected String getErrorDescription(Optional<String> qualifiedName) {
return qualifiedName
.map(name -> String.format("%s is deprecated", name))
.orElse("Deprecated API usage")
+ MESSAGE_DETAILS;
protected String getErrorDescription(String qualifiedName) {
return String.format("%s is deprecated", qualifiedName) + MESSAGE_DETAILS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,8 @@

import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.Matchers;
import com.sun.source.tree.Tree;
import java.util.Optional;
import com.sun.tools.javac.code.Symbol;

/**
* This check is meant to replace usage of the `-Werror` and `-Xlint:removal` compiler flags (the latter being default),
Expand Down Expand Up @@ -52,19 +48,19 @@ public final class DeprecatedForRemovalApiUsage extends AbstractDeprecatedApiChe
+ "the DeprecatedForRemovalApiUsage error-prone check, replacing the default-on java compiler flag "
+ "'-Xlint:removal'. Use @SuppressWarnings(\"removal\") to suppress this error.";

private static final Matcher<Tree> DEPRECATED_FOR_REMOVAL_SYMBOL =
Matchers.symbolMatcher((symbol, state) -> symbol.isDeprecatedForRemoval());
@Override
protected boolean isDeprecationWarning(Symbol symbol) {
return symbol.isDeprecatedForRemoval();
}

@Override
protected boolean isDeprecationWarning(Tree tree, VisitorState state) {
return DEPRECATED_FOR_REMOVAL_SYMBOL.matches(tree, state);
protected boolean isEnclosingDeprecatedForSuppression(Symbol symbol) {
// Suppress this check if the enclosing context is deprecated for removal
return symbol.isDeprecatedForRemoval();
}

@Override
protected String getErrorDescription(Optional<String> qualifiedName) {
return qualifiedName
.map(name -> String.format("%s is deprecated for removal", name))
.orElse("Deprecated-for-removal API usage")
+ MESSAGE_DETAILS;
protected String getErrorDescription(String qualifiedName) {
return String.format("%s is deprecated for removal", qualifiedName) + MESSAGE_DETAILS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.palantir.baseline.errorprone;

import com.google.errorprone.CompilationTestHelper;
import com.google.errorprone.scanner.ScannerSupplier;
import org.junit.jupiter.api.Test;

public class DeprecatedApiUsageTest {
Expand Down Expand Up @@ -419,6 +420,137 @@ public void fun() {
.doTest();
}

@Test
public void compiler_allows_deprecations_within_deprecated_methods() {
// Using a raw compilation helper here to verify the compiler behavior, rather than the error-prone check
CompilationTestHelper.newInstance(ScannerSupplier.fromBugCheckerClasses(), getClass())
.setArgs("-Xlint:deprecation", "-Werror")
.addSourceLines(
"Helper.java",
// language=Java
"""
class Helper {
@Deprecated
public void deprecatedMethod() {}
}
""")
.addSourceLines(
"Test.java",
// language=Java
"""
class Test {
@Deprecated
public void fun() {
new Helper().deprecatedMethod();
}
}
""")
.doTest();
}

@Test
public void error_prone_check_allows_deprecations_within_deprecated_methods() {
helper().addSourceLines(
"Helper.java",
// language=Java
"""
class Helper {
@Deprecated
public void deprecatedMethod() {}
}
""")
.addSourceLines(
"Test.java",
// language=Java
"""
class Test {
@Deprecated
public void fun() {
new Helper().deprecatedMethod();
}
}
""")
.doTest();
}

@Test
public void compiler_allows_deprecations_within_deprecated_classes() {
// Using a raw compilation helper here to verify the compiler behavior, rather than the error-prone check
CompilationTestHelper.newInstance(ScannerSupplier.fromBugCheckerClasses(), getClass())
.setArgs("-Xlint:deprecation", "-Werror")
.addSourceLines(
"Helper.java",
// language=Java
"""
class Helper {
@Deprecated
public void deprecatedMethod() {}
}
""")
.addSourceLines(
"Test.java",
// language=Java
"""
@Deprecated
class Test {
public void fun() {
new Helper().deprecatedMethod();
}
}
""")
.doTest();
}

@Test
public void error_prone_check_allows_deprecations_within_deprecated_classes() {
helper().addSourceLines(
"Helper.java",
// language=Java
"""
class Helper {
@Deprecated
public void deprecatedMethod() {}
}
""")
.addSourceLines(
"Test.java",
// language=Java
"""
@Deprecated
class Test {
public void fun() {
new Helper().deprecatedMethod();
}
}
""")
.doTest();
}

@Test
public void error_prone_check_allows_deprecations_within_deprecated_interfaces() {
helper().addSourceLines(
"Helper.java",
// language=Java
"""
class Helper {
@Deprecated
public void deprecatedMethod() {}
}
""")
.addSourceLines(
"Test.java",
// language=Java
"""
@Deprecated
interface Test {
default void fun() {
new Helper().deprecatedMethod();
}
}
""")
.doTest();
}

private CompilationTestHelper helper() {
return CompilationTestHelper.newInstance(DeprecatedApiUsage.class, getClass());
}
Expand Down
Loading