-
Notifications
You must be signed in to change notification settings - Fork 134
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
Error prone RedundantModifier check supports more interface components #1021
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 |
---|---|---|
|
@@ -26,8 +26,8 @@ | |
import com.google.errorprone.matchers.Matchers; | ||
import com.sun.source.tree.ClassTree; | ||
import com.sun.source.tree.MethodTree; | ||
import com.sun.source.tree.ModifiersTree; | ||
import com.sun.source.tree.Tree; | ||
import com.sun.source.tree.VariableTree; | ||
import java.util.Locale; | ||
import javax.lang.model.element.Modifier; | ||
|
||
|
@@ -45,38 +45,65 @@ | |
severity = BugPattern.SeverityLevel.ERROR, | ||
summary = "Avoid using redundant modifiers") | ||
public final class RedundantModifier extends BugChecker | ||
implements BugChecker.ClassTreeMatcher, BugChecker.MethodTreeMatcher { | ||
implements BugChecker.ClassTreeMatcher, BugChecker.MethodTreeMatcher, BugChecker.VariableTreeMatcher { | ||
|
||
private static final Matcher<ClassTree> STATIC_ENUM_OR_INTERFACE = Matchers.allOf( | ||
Matchers.anyOf(Matchers.kindIs(Tree.Kind.ENUM), Matchers.kindIs(Tree.Kind.INTERFACE)), | ||
classHasExplicitModifier(Modifier.STATIC)); | ||
MoreMatchers.hasExplicitModifier(Modifier.STATIC)); | ||
|
||
private static final Matcher<MethodTree> PRIVATE_ENUM_CONSTRUCTOR = Matchers.allOf( | ||
Matchers.methodIsConstructor(), | ||
Matchers.enclosingClass(Matchers.kindIs(Tree.Kind.ENUM)), | ||
methodHasExplicitModifier(Modifier.PRIVATE)); | ||
MoreMatchers.hasExplicitModifier(Modifier.PRIVATE)); | ||
|
||
private static final Matcher<MethodTree> STATIC_FINAL_METHOD = Matchers.allOf( | ||
methodHasExplicitModifier(Modifier.STATIC), | ||
methodHasExplicitModifier(Modifier.FINAL)); | ||
MoreMatchers.hasExplicitModifier(Modifier.STATIC), | ||
MoreMatchers.hasExplicitModifier(Modifier.FINAL)); | ||
|
||
private static final Matcher<MethodTree> UNNECESSARY_INTERFACE_METHOD_MODIFIERS = Matchers.allOf( | ||
Matchers.enclosingClass(Matchers.kindIs(Tree.Kind.INTERFACE)), | ||
Matchers.not(Matchers.isStatic()), | ||
Matchers.not(Matchers.hasModifier(Modifier.DEFAULT)), | ||
Matchers.anyOf(methodHasExplicitModifier(Modifier.PUBLIC), methodHasExplicitModifier(Modifier.ABSTRACT))); | ||
Matchers.anyOf( | ||
MoreMatchers.hasExplicitModifier(Modifier.PUBLIC), | ||
MoreMatchers.hasExplicitModifier(Modifier.ABSTRACT))); | ||
|
||
private static final Matcher<MethodTree> INTERFACE_STATIC_METHOD_MODIFIERS = Matchers.allOf( | ||
Matchers.enclosingClass(Matchers.kindIs(Tree.Kind.INTERFACE)), | ||
Matchers.isStatic(), | ||
MoreMatchers.hasExplicitModifier(Modifier.PUBLIC)); | ||
|
||
private static final Matcher<VariableTree> INTERFACE_FIELD_MODIFIERS = Matchers.allOf( | ||
Matchers.enclosingClass(Matchers.kindIs(Tree.Kind.INTERFACE)), | ||
Matchers.isStatic(), | ||
Matchers.anyOf( | ||
MoreMatchers.hasExplicitModifier(Modifier.PUBLIC), | ||
MoreMatchers.hasExplicitModifier(Modifier.STATIC), | ||
MoreMatchers.hasExplicitModifier(Modifier.FINAL))); | ||
|
||
private static final Matcher<ClassTree> INTERFACE_NESTED_CLASS_MODIFIERS = Matchers.allOf( | ||
MoreMatchers.classEnclosingClass(Matchers.kindIs(Tree.Kind.INTERFACE)), | ||
Matchers.anyOf( | ||
MoreMatchers.hasExplicitModifier(Modifier.PUBLIC), | ||
MoreMatchers.hasExplicitModifier(Modifier.STATIC))); | ||
|
||
private static final Matcher<MethodTree> UNNECESSARY_FINAL_METHOD_ON_FINAL_CLASS = Matchers.allOf( | ||
Matchers.not(Matchers.isStatic()), | ||
Matchers.enclosingClass(Matchers.allOf( | ||
Matchers.kindIs(Tree.Kind.CLASS), | ||
classHasExplicitModifier(Modifier.FINAL))), | ||
MoreMatchers.hasExplicitModifier(Modifier.FINAL))), | ||
Matchers.allOf( | ||
methodHasExplicitModifier(Modifier.FINAL), | ||
MoreMatchers.hasExplicitModifier(Modifier.FINAL), | ||
Matchers.not(Matchers.hasAnnotation(SafeVarargs.class)))); | ||
|
||
@Override | ||
public Description matchClass(ClassTree tree, VisitorState state) { | ||
if (INTERFACE_NESTED_CLASS_MODIFIERS.matches(tree, state)) { | ||
return buildDescription(tree) | ||
.setMessage("Types nested in interfaces are public and static by default.") | ||
.addFix(SuggestedFixes.removeModifiers(tree, state, Modifier.PUBLIC, Modifier.STATIC)) | ||
.build(); | ||
} | ||
if (STATIC_ENUM_OR_INTERFACE.matches(tree, state)) { | ||
return buildDescription(tree) | ||
.setMessage(tree.getKind().name().toLowerCase(Locale.ENGLISH) | ||
|
@@ -108,6 +135,12 @@ public Description matchMethod(MethodTree tree, VisitorState state) { | |
.addFix(SuggestedFixes.removeModifiers(tree, state, Modifier.PUBLIC, Modifier.ABSTRACT)) | ||
.build(); | ||
} | ||
if (INTERFACE_STATIC_METHOD_MODIFIERS.matches(tree, state)) { | ||
return buildDescription(tree) | ||
.setMessage("Interface components are public by default. The 'public' modifier is unnecessary.") | ||
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. interesting that java9 lets people define This should be all fine though, because we're explicitly only detecting and fixing the 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. Yep, I designed this with JEP 213 in mind. |
||
.addFix(SuggestedFixes.removeModifiers(tree, state, Modifier.PUBLIC)) | ||
.build(); | ||
} | ||
if (UNNECESSARY_FINAL_METHOD_ON_FINAL_CLASS.matches(tree, state)) { | ||
return buildDescription(tree) | ||
.setMessage("Redundant 'final' modifier on an instance method of a final class.") | ||
|
@@ -117,26 +150,16 @@ public Description matchMethod(MethodTree tree, VisitorState state) { | |
return Description.NO_MATCH; | ||
} | ||
|
||
private static Matcher<MethodTree> methodHasExplicitModifier(Modifier modifier) { | ||
return (Matcher<MethodTree>) (methodTree, state) -> | ||
containsModifier(methodTree.getModifiers(), state, modifier); | ||
} | ||
|
||
private static Matcher<ClassTree> classHasExplicitModifier(Modifier modifier) { | ||
return (Matcher<ClassTree>) (classTree, state) -> | ||
containsModifier(classTree.getModifiers(), state, modifier); | ||
} | ||
|
||
private static boolean containsModifier(ModifiersTree tree, VisitorState state, Modifier modifier) { | ||
if (!tree.getFlags().contains(modifier)) { | ||
return false; | ||
} | ||
String source = state.getSourceForNode(tree); | ||
// getSourceForNode returns null when there are no modifiers specified | ||
if (source == null) { | ||
return false; | ||
@Override | ||
public Description matchVariable(VariableTree tree, VisitorState state) { | ||
if (INTERFACE_FIELD_MODIFIERS.matches(tree, state)) { | ||
return buildDescription(tree) | ||
.setMessage("Interface fields are public, static, and final by default. " | ||
+ "These modifiers are unnecessary to specify.") | ||
.addFix(SuggestedFixes.removeModifiers( | ||
tree, state, Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)) | ||
.build(); | ||
} | ||
// nested interfaces report a static modifier despite not being present | ||
return source.contains(modifier.name().toLowerCase(Locale.ENGLISH)); | ||
return Description.NO_MATCH; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
type: improvement | ||
improvement: | ||
description: Error prone RedundantModifier check supports interface static methods | ||
and fields. | ||
links: | ||
- https://github.com/palantir/gradle-baseline/pull/1021 |
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.
i'm a tiny bit concerned that having this at
ERROR
level might introduce unnecessary friction when people just want to run things like./gradlew test
locally. We're just detecting redundant code, not actually 'wrong' in anywa way... given that excavator can come around and auto-fix, what do you think about putting this as suggestion?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.
Agreed, I initially used error because this in some part replaces a checkstyle check which we verify premerge.
Given that robots can fix this for us, and it's not a correctness issue, I don't mind moving it to a suggestion, and allowing minor regressions until the robots sweep through.