diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/CanonicalConstantFieldName.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/CanonicalConstantFieldName.java new file mode 100644 index 00000000000..c2472920a2f --- /dev/null +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/CanonicalConstantFieldName.java @@ -0,0 +1,114 @@ +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.LIKELY_ERROR; +import static com.google.errorprone.matchers.Matchers.allOf; +import static com.google.errorprone.matchers.Matchers.hasModifier; +import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL; + +import com.google.auto.service.AutoService; +import com.google.common.collect.ImmutableList; +import com.google.errorprone.BugPattern; +import com.google.errorprone.ErrorProneFlags; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.bugpatterns.BugChecker.VariableTreeMatcher; +import com.google.errorprone.fixes.SuggestedFix; +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.Tree; +import com.sun.source.tree.VariableTree; +import com.sun.tools.javac.code.Symbol.VarSymbol; +import java.util.Locale; +import javax.inject.Inject; +import javax.lang.model.element.Modifier; +import tech.picnic.errorprone.bugpatterns.util.Flags; + +/** + * A {@link BugChecker} that warns and suggests the fix when a constant variable does not follow the + * upper snake case naming convention. + * + *

Example: + * + *

This checker will re-write the following variables with all its references: + * + *

+ * + *

To the following: + * + *

+ */ +@AutoService(BugChecker.class) +@BugPattern( + summary = + "Make sure that all constant variables follow the `UPPER_SNAKE_CASE` naming convention.", + link = BUG_PATTERNS_BASE_URL + "CanonicalConstantFieldName", + linkType = CUSTOM, + severity = WARNING, + tags = LIKELY_ERROR) +public final class CanonicalConstantFieldName extends BugChecker implements VariableTreeMatcher { + private static final long serialVersionUID = 1L; + private static final Matcher IS_CONSTANT = + allOf(hasModifier(Modifier.STATIC), hasModifier(Modifier.FINAL)); + private static final String EXCLUDED_CONSTANT_FIELD_NAMES = + "CanonicalConstantFieldName:ExcludedConstantFliedNames"; + + private final ImmutableList excludedConstantFliedNames; + + /** Instantiates a default {@link CanonicalConstantFieldName} instance. */ + public CanonicalConstantFieldName() { + this(ErrorProneFlags.empty()); + } + + /** + * Instantiates a customized {@link CanonicalConstantFieldName}. + * + * @param flags Any provided command line flags. + */ + @Inject + CanonicalConstantFieldName(ErrorProneFlags flags) { + excludedConstantFliedNames = getCanonicalizedLoggerName(flags); + } + + @Override + public Description matchVariable(VariableTree tree, VisitorState state) { + if (!IS_CONSTANT.matches(tree, state)) { + return Description.NO_MATCH; + } + SuggestedFix.Builder fixBuilder = SuggestedFix.builder(); + + VarSymbol variableSymbol = ASTHelpers.getSymbol(tree); + String variableName = variableSymbol.getSimpleName().toString(); + + if (!isVariableUpperSnakeCase(variableName) && !isVariableNameExcluded(variableName)) { + fixBuilder.merge(SuggestedFixes.renameVariable(tree, toUpperSnakeCase(variableName), state)); + } + + return fixBuilder.isEmpty() ? Description.NO_MATCH : describeMatch(tree, fixBuilder.build()); + } + + private static boolean isVariableUpperSnakeCase(String variableName) { + return variableName.equals(toUpperSnakeCase(variableName)); + } + + private boolean isVariableNameExcluded(String variableName) { + return excludedConstantFliedNames.contains(variableName); + } + + private static String toUpperSnakeCase(String variableName) { + return variableName.replaceAll("([a-z])([A-Z])", "$1_$2").toUpperCase(Locale.ROOT); + } + + private static ImmutableList getCanonicalizedLoggerName(ErrorProneFlags flags) { + return Flags.getList(flags, EXCLUDED_CONSTANT_FIELD_NAMES); + } +} diff --git a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/CanonicalConstantFieldNameTest.java b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/CanonicalConstantFieldNameTest.java new file mode 100644 index 00000000000..56dadbcc51a --- /dev/null +++ b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/CanonicalConstantFieldNameTest.java @@ -0,0 +1,39 @@ +package tech.picnic.errorprone.bugpatterns; + +import com.google.errorprone.BugCheckerRefactoringTestHelper; +import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode; +import org.junit.jupiter.api.Test; + +final class CanonicalConstantFieldNameTest { + @Test + void replacement() { + BugCheckerRefactoringTestHelper.newInstance(CanonicalConstantFieldName.class, getClass()) + .addInputLines( + "A.java", + "class A {", + " private static final int number = 1;", + "", + " static final int otherNumber = 2;", + "", + " static final int ANOTHER_NUMBER = 3;", + "", + " static int getNumber() {", + " return number;", + " }", + "}") + .addOutputLines( + "A.java", + "class A {", + " private static final int NUMBER = 1;", + "", + " static final int OTHER_NUMBER = 2;", + "", + " static final int ANOTHER_NUMBER = 3;", + "", + " static int getNumber() {", + " return NUMBER;", + " }", + "}") + .doTest(TestMode.TEXT_MATCH); + } +} diff --git a/pom.xml b/pom.xml index f073c04cc19..b1e69ec2cb4 100644 --- a/pom.xml +++ b/pom.xml @@ -1732,6 +1732,7 @@ readable than the alternative. --> -Xep:YodaCondition:OFF -XepOpt:CheckReturnValue:CheckAllConstructors=true + -XepOpt:CanonicalConstantFieldName:ExcludedConstantFliedNames=serialVersionUID