-
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 ConstantNaming
check
#794
Merged
Stephan202
merged 18 commits into
master
from
mohamedsamehsalah/396-canonical-constant-naming
Oct 27, 2024
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
d82bde3
Introduce `CanonicalConstantFieldName` bug checker
46cbfb4
Address comments: Use Pattern matching instead of `replaceAll`
a5d4cab
Address comments: Exclude `serialVersionUID` field name by default
6191e8d
Address comments: Conditionally include public constant fields
59b145e
Address comments: Identify suggestion blockers
2cd5015
Address comments: Match on compilation unit and bug fixes
a346a6f
Address comments: Fix mutation tests
cfed0c3
Small tweaks
rickie 7b293f4
Add some tweaks and failing test cases
rickie fb475fd
Second round of suggestions
rickie 9cf4c30
Report and fix match on variable level
mohamedsamehsalah 9b9a25c
Simplify and drop public support
rickie eaa0b8c
Suggestions
rickie 31163ff
Re-order and flip for early return
rickie 461f6e1
Add some test cases
rickie 997578a
Format
rickie 20caa7d
Fix build after rebase
Stephan202 d7a3938
Suggestions
Stephan202 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
125 changes: 125 additions & 0 deletions
125
error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/ConstantNaming.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
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.STYLE; | ||
import static com.google.errorprone.matchers.Matchers.allOf; | ||
import static com.google.errorprone.matchers.Matchers.hasModifier; | ||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL; | ||
|
||
import com.google.auto.service.AutoService; | ||
import com.google.common.collect.ImmutableSet; | ||
import com.google.common.collect.Sets; | ||
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.SuggestedFixes; | ||
import com.google.errorprone.matchers.Description; | ||
import com.google.errorprone.matchers.Matcher; | ||
import com.google.errorprone.util.ASTHelpers; | ||
import com.sun.source.tree.VariableTree; | ||
import com.sun.source.util.TreeScanner; | ||
import java.util.Locale; | ||
import java.util.regex.Pattern; | ||
import javax.inject.Inject; | ||
import javax.lang.model.element.Modifier; | ||
import org.jspecify.annotations.Nullable; | ||
import tech.picnic.errorprone.utils.Flags; | ||
|
||
/** | ||
* A {@link BugChecker} that flags static constants that do not follow the upper snake case naming | ||
* convention. | ||
*/ | ||
@AutoService(BugChecker.class) | ||
@BugPattern( | ||
summary = "Constant variables should adhere to the `UPPER_SNAKE_CASE` naming convention", | ||
link = BUG_PATTERNS_BASE_URL + "ConstantNaming", | ||
linkType = CUSTOM, | ||
severity = WARNING, | ||
tags = STYLE) | ||
@SuppressWarnings("java:S2160" /* Super class equality definition suffices. */) | ||
public final class ConstantNaming extends BugChecker implements VariableTreeMatcher { | ||
private static final long serialVersionUID = 1L; | ||
private static final Matcher<VariableTree> IS_CONSTANT = | ||
allOf(hasModifier(Modifier.STATIC), hasModifier(Modifier.FINAL)); | ||
private static final Matcher<VariableTree> IS_PRIVATE = hasModifier(Modifier.PRIVATE); | ||
private static final Pattern SNAKE_CASE = Pattern.compile("([a-z])([A-Z])"); | ||
private static final ImmutableSet<String> DEFAULT_EXEMPTED_NAMES = | ||
ImmutableSet.of("serialVersionUID"); | ||
|
||
/** | ||
* Flag using which constant names that must not be flagged (in addition to those defined by | ||
* {@link #DEFAULT_EXEMPTED_NAMES}) can be specified. | ||
*/ | ||
private static final String ADDITIONAL_EXEMPTED_NAMES_FLAG = | ||
"CanonicalConstantNaming:ExemptedNames"; | ||
|
||
private final ImmutableSet<String> exemptedNames; | ||
|
||
/** Instantiates a default {@link ConstantNaming} instance. */ | ||
public ConstantNaming() { | ||
this(ErrorProneFlags.empty()); | ||
} | ||
|
||
/** | ||
* Instantiates a customized {@link ConstantNaming}. | ||
* | ||
* @param flags Any provided command line flags. | ||
*/ | ||
@Inject | ||
ConstantNaming(ErrorProneFlags flags) { | ||
exemptedNames = | ||
Sets.union(DEFAULT_EXEMPTED_NAMES, Flags.getSet(flags, ADDITIONAL_EXEMPTED_NAMES_FLAG)) | ||
Check warning on line 74 in error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/ConstantNaming.java GitHub Actions / pitestA change can be made to line 74 without causing a test to fail
|
||
.immutableCopy(); | ||
} | ||
|
||
@Override | ||
public Description matchVariable(VariableTree tree, VisitorState state) { | ||
String variableName = tree.getName().toString(); | ||
if (!IS_CONSTANT.matches(tree, state) || exemptedNames.contains(variableName)) { | ||
return Description.NO_MATCH; | ||
} | ||
|
||
String replacement = toUpperSnakeCase(variableName); | ||
if (replacement.equals(variableName)) { | ||
return Description.NO_MATCH; | ||
} | ||
|
||
Description.Builder description = buildDescription(tree); | ||
if (!IS_PRIVATE.matches(tree, state)) { | ||
description.setMessage( | ||
"%s; consider renaming to '%s', though note that this is not a private constant" | ||
.formatted(message(), replacement)); | ||
} else if (isVariableNameInUse(replacement, state)) { | ||
description.setMessage( | ||
"%s; consider renaming to '%s', though note that a variable with this name is already declared" | ||
.formatted(message(), replacement)); | ||
} else { | ||
description.addFix(SuggestedFixes.renameVariable(tree, replacement, state)); | ||
} | ||
|
||
return description.build(); | ||
} | ||
|
||
private static String toUpperSnakeCase(String variableName) { | ||
return SNAKE_CASE.matcher(variableName).replaceAll("$1_$2").toUpperCase(Locale.ROOT); | ||
} | ||
|
||
private static boolean isVariableNameInUse(String name, VisitorState state) { | ||
return Boolean.TRUE.equals( | ||
new TreeScanner<Boolean, @Nullable Void>() { | ||
@Override | ||
public Boolean visitVariable(VariableTree tree, @Nullable Void unused) { | ||
return ASTHelpers.getSymbol(tree).getSimpleName().toString().equals(name) | ||
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. |
||
|| super.visitVariable(tree, null); | ||
} | ||
|
||
@Override | ||
public Boolean reduce(Boolean r1, Boolean r2) { | ||
return Boolean.TRUE.equals(r1) || Boolean.TRUE.equals(r2); | ||
} | ||
}.scan(state.getPath().getCompilationUnit(), null)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/ConstantNamingTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package tech.picnic.errorprone.bugpatterns; | ||
|
||
import com.google.errorprone.BugCheckerRefactoringTestHelper; | ||
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode; | ||
import com.google.errorprone.CompilationTestHelper; | ||
import org.junit.jupiter.api.Test; | ||
|
||
final class ConstantNamingTest { | ||
@Test | ||
void identification() { | ||
CompilationTestHelper.newInstance(ConstantNaming.class, getClass()) | ||
.addSourceLines( | ||
"A.java", | ||
"class A {", | ||
" private static final long serialVersionUID = 1L;", | ||
" private static final int FOO = 1;", | ||
" // BUG: Diagnostic contains: consider renaming to 'BAR', though note that this is not a private", | ||
" // constant", | ||
" static final int bar = 2;", | ||
" // BUG: Diagnostic contains:", | ||
" private static final int baz = 3;", | ||
" // BUG: Diagnostic contains: consider renaming to 'QUX_QUUX', though note that a variable with", | ||
" // this name is already declared", | ||
" private static final int qux_QUUX = 4;", | ||
" // BUG: Diagnostic contains: consider renaming to 'QUUZ', though note that a variable with", | ||
" // this name is already declared", | ||
" private static final int quuz = 3;", | ||
"", | ||
" private final int foo = 4;", | ||
" private final Runnable QUX_QUUX =", | ||
" new Runnable() {", | ||
" private static final int QUUZ = 1;", | ||
"", | ||
" @Override", | ||
" public void run() {}", | ||
" };", | ||
"}") | ||
.doTest(); | ||
} | ||
|
||
@Test | ||
void identificationWithCustomExemption() { | ||
CompilationTestHelper.newInstance(ConstantNaming.class, getClass()) | ||
.setArgs("-XepOpt:CanonicalConstantNaming:ExemptedNames=foo,baz") | ||
.addSourceLines( | ||
"A.java", | ||
"class A {", | ||
" private static final long serialVersionUID = 1L;", | ||
" private static final int foo = 1;", | ||
" // BUG: Diagnostic contains:", | ||
" private static final int bar = 2;", | ||
" private static final int baz = 3;", | ||
"}") | ||
.doTest(); | ||
} | ||
|
||
@Test | ||
void replacement() { | ||
BugCheckerRefactoringTestHelper.newInstance(ConstantNaming.class, getClass()) | ||
.addInputLines( | ||
"A.java", | ||
"class A {", | ||
" static final int foo = 1;", | ||
" private static final int bar = 2;", | ||
" private static final int baz = 3;", | ||
" private static final int BAZ = 4;", | ||
"}") | ||
.addOutputLines( | ||
"A.java", | ||
"class A {", | ||
" static final int foo = 1;", | ||
" private static final int BAR = 2;", | ||
" private static final int baz = 3;", | ||
" private static final int BAZ = 4;", | ||
"}") | ||
.doTest(TestMode.TEXT_MATCH); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Here and below: Pitest correctly flags that the
Sets#union
arguments can be swapped without causing a test failure. That's expected, as set union is a symmetric operation, and this code doesn't care about the created set's iteration order.