-
Notifications
You must be signed in to change notification settings - Fork 39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce CanonicalClassNameUsage
check
#881
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package tech.picnic.errorprone.bugpatterns; | ||
|
||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM; | ||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; | ||
import static com.google.errorprone.BugPattern.StandardTags.FRAGILE_CODE; | ||
import static com.google.errorprone.matchers.Matchers.allOf; | ||
import static com.google.errorprone.matchers.Matchers.anything; | ||
import static com.google.errorprone.matchers.Matchers.classLiteral; | ||
import static com.google.errorprone.matchers.Matchers.instanceMethod; | ||
import static com.google.errorprone.matchers.Matchers.receiverOfInvocation; | ||
import static com.google.errorprone.matchers.Matchers.toType; | ||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL; | ||
|
||
import com.google.auto.service.AutoService; | ||
import com.google.errorprone.BugPattern; | ||
import com.google.errorprone.VisitorState; | ||
import com.google.errorprone.annotations.Var; | ||
import com.google.errorprone.bugpatterns.BugChecker; | ||
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher; | ||
import com.google.errorprone.fixes.SuggestedFixes; | ||
import com.google.errorprone.matchers.Description; | ||
import com.google.errorprone.matchers.Matcher; | ||
import com.google.errorprone.util.ASTHelpers; | ||
import com.sun.source.tree.BinaryTree; | ||
import com.sun.source.tree.ExpressionTree; | ||
import com.sun.source.tree.MethodInvocationTree; | ||
import com.sun.source.util.TreePath; | ||
import com.sun.tools.javac.code.Symbol.MethodSymbol; | ||
import java.util.regex.Pattern; | ||
|
||
/** | ||
* A {@link BugChecker} that flags invocations of {@link Class#getName()} where {@link | ||
* Class#getCanonicalName()} was likely meant. | ||
* | ||
* <p>For top-level types these two methods generally return the same result, but for nested types | ||
* the former separates identifiers using a dollar sign ({@code $}) rather than a dot ({@code .}). | ||
* | ||
* @implNote This check currently only flags {@link Class#getName()} invocations on class literals, | ||
* and doesn't flag method references. This avoids false positives, such as suggesting use of | ||
* {@link Class#getCanonicalName()} in contexts where the canonical name is {@code null}. | ||
*/ | ||
@AutoService(BugChecker.class) | ||
@BugPattern( | ||
summary = "This code should likely use the type's canonical name", | ||
link = BUG_PATTERNS_BASE_URL + "CanonicalClassNameUsage", | ||
linkType = CUSTOM, | ||
severity = WARNING, | ||
tags = FRAGILE_CODE) | ||
public final class CanonicalClassNameUsage extends BugChecker | ||
implements MethodInvocationTreeMatcher { | ||
private static final long serialVersionUID = 1L; | ||
private static final Matcher<ExpressionTree> GET_NAME_INVOCATION = | ||
toType( | ||
MethodInvocationTree.class, | ||
allOf( | ||
receiverOfInvocation(classLiteral(anything())), | ||
instanceMethod().onExactClass(Class.class.getCanonicalName()).named("getName"))); | ||
private static final Pattern CANONICAL_NAME_USING_TYPES = | ||
Pattern.compile("(com\\.google\\.errorprone|tech\\.picnic\\.errorprone)\\..*"); | ||
|
||
/** Instantiates a new {@link CanonicalClassNameUsage} instance. */ | ||
public CanonicalClassNameUsage() {} | ||
|
||
@Override | ||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) { | ||
if (!GET_NAME_INVOCATION.matches(tree, state) || !isPassedToCanonicalNameUsingType(state)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Acording to I wonder do we need to add a But I guess the idea is that methods that accept canonical names are aware of the API... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oeh nice that you think about such edge cases 🚀 ! Interesting it will indeed not work if we rewrite There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice catch @mohamedsamehsalah! In practice we're interested in matching |
||
/* | ||
* This is not a `class.getName()` invocation of which the result is passed to another method | ||
* known to accept canonical type names. | ||
*/ | ||
return Description.NO_MATCH; | ||
} | ||
|
||
return describeMatch( | ||
tree, SuggestedFixes.renameMethodInvocation(tree, "getCanonicalName", state)); | ||
} | ||
|
||
private static boolean isPassedToCanonicalNameUsingType(VisitorState state) { | ||
@Var TreePath path = state.getPath().getParentPath(); | ||
while (path.getLeaf() instanceof BinaryTree) { | ||
path = path.getParentPath(); | ||
} | ||
|
||
return path.getLeaf() instanceof MethodInvocationTree | ||
&& isOwnedByCanonicalNameUsingType( | ||
ASTHelpers.getSymbol((MethodInvocationTree) path.getLeaf())); | ||
} | ||
|
||
private static boolean isOwnedByCanonicalNameUsingType(MethodSymbol symbol) { | ||
return CANONICAL_NAME_USING_TYPES.matcher(symbol.owner.getQualifiedName()).matches(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For these situations we use both
_INVOCATION
and_METHOD
. I feelINVOCATION
fits better here. Maybe we should apply this consistently 🤔.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed; I had the same feeling. Last I looked into this (won't do another pass now) there were more inconsistencies, so perhaps this is something to review in a separate PR.