-
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 PatternRules
Refaster rule collection
#771
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,46 @@ | ||
package tech.picnic.errorprone.refasterrules; | ||
|
||
import static com.google.common.base.Predicates.containsPattern; | ||
|
||
import com.google.common.base.Predicates; | ||
import com.google.errorprone.refaster.annotation.AfterTemplate; | ||
import com.google.errorprone.refaster.annotation.BeforeTemplate; | ||
import java.util.function.Predicate; | ||
import java.util.regex.Pattern; | ||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation; | ||
|
||
/** Refaster rules related to code dealing with regular expressions. */ | ||
@OnlineDocumentation | ||
final class PatternRules { | ||
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. Noticed that only this rule is not in 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. That means that this one is not actually being tested, so we definitely should add it. 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; will fix! (And indeed, cue the "we should automate this" message 😅) |
||
private PatternRules() {} | ||
|
||
/** Prefer {@link Pattern#asPredicate()} over non-JDK alternatives. */ | ||
// XXX: This rule could also replace `s -> pattern.matcher(s).find()`, though the lambda | ||
// expression may match functional interfaces other than `Predicate`. If we do add such a rule, we | ||
// should also add a rule that replaces `s -> pattern.matcher(s).matches()` with | ||
// `pattern.asMatchPredicate()`. | ||
static final class PatternAsPredicate { | ||
@BeforeTemplate | ||
Predicate<CharSequence> before(Pattern pattern) { | ||
return Predicates.contains(pattern); | ||
} | ||
|
||
@AfterTemplate | ||
Predicate<String> after(Pattern pattern) { | ||
return pattern.asPredicate(); | ||
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. also here I'd personally argue that the Guava variant is more descriptive (nor do they recommend against using it) I'm also not sure it's 100% equivalent as JDK uses 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.
On descriptiveness I guess we'll just have to disagree 😅. W.r.t. recommendations: while
Indeed it isn't; this is a Refaster limitation that unfortunately more of our rules suffer from. (Longer-term I plan to automatically flag and annotate such rules such that they can e.g. be skipped.) 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.
good point, should that be 'attacked' even more generically? essentially the entire 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. Ideally yes! |
||
} | ||
} | ||
|
||
/** Prefer {@link Pattern#asPredicate()} over non-JDK alternatives. */ | ||
static final class PatternCompileAsPredicate { | ||
@BeforeTemplate | ||
Predicate<CharSequence> before(String pattern) { | ||
return containsPattern(pattern); | ||
Venorcis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
@AfterTemplate | ||
Predicate<String> after(String pattern) { | ||
return Pattern.compile(pattern).asPredicate(); | ||
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. IMO the Guava static builder is a nice short alternative; not sure I see the advantage in standardizing here (they also do not recommend themselves to not use it like with some other utilities we don't rewrite like 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. I can see how this suggestion is slightly controversial, but my thinking is that, besides being a JDK alternative:
W.r.t. |
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,12 @@ | ||
package tech.picnic.errorprone.bugpatterns; | ||
|
||
import static com.google.common.base.Predicates.and; | ||
import static com.google.common.base.Predicates.containsPattern; | ||
import static com.google.common.base.Predicates.not; | ||
import static com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers.SECOND; | ||
import static com.google.errorprone.BugCheckerRefactoringTestHelper.FixChoosers.THIRD; | ||
|
||
import com.google.errorprone.BugCheckerRefactoringTestHelper; | ||
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode; | ||
import com.google.errorprone.CompilationTestHelper; | ||
import java.util.stream.Stream; | ||
import org.junit.jupiter.api.Test; | ||
import org.reactivestreams.Publisher; | ||
import reactor.core.CorePublisher; | ||
|
@@ -20,10 +18,7 @@ void identification() { | |
CompilationTestHelper.newInstance(FluxImplicitBlock.class, getClass()) | ||
.expectErrorMessage( | ||
"X", | ||
and( | ||
containsPattern("SuppressWarnings"), | ||
containsPattern("toImmutableList"), | ||
containsPattern("toList"))) | ||
m -> Stream.of("SuppressWarnings", "toImmutableList", "toList").allMatch(m::contains)) | ||
Comment on lines
-23
to
+21
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. Thinking aloud, not in this PR, but I was wondering whether utility functions that return non pattern based predicates would be useful. In this case it wouldn't hide any potentially expensive underlying operation. In my very personal opinion Seeing that we don't have 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. Hmm, I would say that such a utility method has an insufficient utility times ubiquity (or power-to-weight) ratio. Separately from that, when I see |
||
.addSourceLines( | ||
"A.java", | ||
"import com.google.common.collect.ImmutableList;", | ||
|
@@ -63,7 +58,7 @@ void identification() { | |
void identificationWithoutGuavaOnClasspath() { | ||
CompilationTestHelper.newInstance(FluxImplicitBlock.class, getClass()) | ||
.withClasspath(CorePublisher.class, Flux.class, Publisher.class) | ||
.expectErrorMessage("X", not(containsPattern("toImmutableList"))) | ||
.expectErrorMessage("X", m -> !m.contains("toImmutableList")) | ||
.addSourceLines( | ||
"A.java", | ||
"import reactor.core.publisher.Flux;", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package tech.picnic.errorprone.refasterrules; | ||
|
||
import com.google.common.base.Predicates; | ||
import com.google.common.collect.ImmutableSet; | ||
import java.util.function.Predicate; | ||
import java.util.regex.Pattern; | ||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase; | ||
|
||
final class PatternRulesTest implements RefasterRuleCollectionTestCase { | ||
@Override | ||
public ImmutableSet<Object> elidedTypesAndStaticImports() { | ||
return ImmutableSet.of(Predicates.class); | ||
} | ||
|
||
Predicate<?> testPatternAsPredicate() { | ||
return Predicates.contains(Pattern.compile("foo")); | ||
} | ||
|
||
Predicate<?> testPatternCompileAsPredicate() { | ||
return Predicates.containsPattern("foo"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package tech.picnic.errorprone.refasterrules; | ||
|
||
import com.google.common.base.Predicates; | ||
import com.google.common.collect.ImmutableSet; | ||
import java.util.function.Predicate; | ||
import java.util.regex.Pattern; | ||
import tech.picnic.errorprone.refaster.test.RefasterRuleCollectionTestCase; | ||
|
||
final class PatternRulesTest implements RefasterRuleCollectionTestCase { | ||
@Override | ||
public ImmutableSet<Object> elidedTypesAndStaticImports() { | ||
return ImmutableSet.of(Predicates.class); | ||
} | ||
|
||
Predicate<?> testPatternAsPredicate() { | ||
return Pattern.compile("foo").asPredicate(); | ||
} | ||
|
||
Predicate<?> testPatternCompileAsPredicate() { | ||
return Pattern.compile("foo").asPredicate(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,5 @@ | ||
package tech.picnic.errorprone.refaster.runner; | ||
|
||
import static com.google.common.base.Predicates.containsPattern; | ||
import static com.google.common.collect.ImmutableList.toImmutableList; | ||
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR; | ||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION; | ||
|
@@ -24,35 +23,33 @@ | |
import org.junit.jupiter.params.provider.MethodSource; | ||
|
||
final class RefasterTest { | ||
private final CompilationTestHelper compilationHelper = | ||
CompilationTestHelper.newInstance(Refaster.class, getClass()) | ||
.matchAllDiagnostics() | ||
.expectErrorMessage( | ||
"StringOfSizeZeroRule", | ||
containsPattern( | ||
"\\[Refaster Rule\\] FooRules\\.StringOfSizeZeroRule: Refactoring opportunity\\s+.+\\s+")) | ||
.expectErrorMessage( | ||
"StringOfSizeOneRule", | ||
containsPattern( | ||
"\\[Refaster Rule\\] FooRules\\.StringOfSizeOneRule: " | ||
+ "A custom description about matching single-char strings\\s+.+\\s+" | ||
+ "\\(see https://error-prone.picnic.tech/refasterrules/FooRules#StringOfSizeOneRule\\)")) | ||
.expectErrorMessage( | ||
"StringOfSizeTwoRule", | ||
containsPattern( | ||
"\\[Refaster Rule\\] FooRules\\.ExtraGrouping\\.StringOfSizeTwoRule: " | ||
+ "A custom subgroup description\\s+.+\\s+" | ||
+ "\\(see https://example.com/rule/FooRules#ExtraGrouping.StringOfSizeTwoRule\\)")) | ||
.expectErrorMessage( | ||
"StringOfSizeThreeRule", | ||
containsPattern( | ||
"\\[Refaster Rule\\] FooRules\\.ExtraGrouping\\.StringOfSizeThreeRule: " | ||
+ "A custom description about matching three-char strings\\s+.+\\s+" | ||
+ "\\(see https://example.com/custom\\)")); | ||
private static final Pattern DIAGNOSTIC_STRING_OF_SIZE_ZERO = | ||
Pattern.compile( | ||
"\\[Refaster Rule\\] FooRules\\.StringOfSizeZeroRule: Refactoring opportunity\\s+.+\\s+"); | ||
private static final Pattern DIAGNOSTIC_STRING_OF_SIZE_ONE = | ||
Pattern.compile( | ||
"\\[Refaster Rule\\] FooRules\\.StringOfSizeOneRule: " | ||
+ "A custom description about matching single-char strings\\s+.+\\s+" | ||
+ "\\(see https://error-prone.picnic.tech/refasterrules/FooRules#StringOfSizeOneRule\\)"); | ||
private static final Pattern DIAGNOSTIC_STRING_OF_SIZE_TWO = | ||
Pattern.compile( | ||
"\\[Refaster Rule\\] FooRules\\.ExtraGrouping\\.StringOfSizeTwoRule: " | ||
+ "A custom subgroup description\\s+.+\\s+" | ||
+ "\\(see https://example.com/rule/FooRules#ExtraGrouping.StringOfSizeTwoRule\\)"); | ||
private static final Pattern DIAGNOSTIC_STRING_OF_SIZE_THREE = | ||
Pattern.compile( | ||
"\\[Refaster Rule\\] FooRules\\.ExtraGrouping\\.StringOfSizeThreeRule: " | ||
+ "A custom description about matching three-char strings\\s+.+\\s+" | ||
+ "\\(see https://example.com/custom\\)"); | ||
|
||
@Test | ||
void identification() { | ||
compilationHelper | ||
CompilationTestHelper.newInstance(Refaster.class, getClass()) | ||
.matchAllDiagnostics() | ||
.expectErrorMessage("StringOfSizeZeroRule", DIAGNOSTIC_STRING_OF_SIZE_ZERO.asPredicate()) | ||
.expectErrorMessage("StringOfSizeOneRule", DIAGNOSTIC_STRING_OF_SIZE_ONE.asPredicate()) | ||
.expectErrorMessage("StringOfSizeTwoRule", DIAGNOSTIC_STRING_OF_SIZE_TWO.asPredicate()) | ||
.expectErrorMessage("StringOfSizeThreeRule", DIAGNOSTIC_STRING_OF_SIZE_THREE.asPredicate()) | ||
.addSourceLines( | ||
"A.java", | ||
"class A {", | ||
|
@@ -153,7 +150,7 @@ private static Stream<Arguments> severityAssignmentTestCases() { | |
void severityAssignment( | ||
ImmutableList<String> arguments, ImmutableList<SeverityLevel> expectedSeverities) { | ||
CompilationTestHelper compilationTestHelper = | ||
compilationHelper | ||
CompilationTestHelper.newInstance(Refaster.class, getClass()) | ||
.setArgs(arguments) | ||
.addSourceLines( | ||
"A.java", | ||
|
@@ -167,13 +164,18 @@ void severityAssignment( | |
" };", | ||
" }", | ||
"}"); | ||
assertThatThrownBy(compilationTestHelper::doTest) | ||
.isInstanceOf(AssertionError.class) | ||
.message() | ||
.satisfies( | ||
message -> | ||
assertThat(extractRefasterSeverities("A.java", message)) | ||
.containsExactlyElementsOf(expectedSeverities)); | ||
|
||
if (expectedSeverities.isEmpty()) { | ||
compilationTestHelper.doTest(); | ||
} else { | ||
Comment on lines
+168
to
+170
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. Previously this wasn't necessary, because the |
||
assertThatThrownBy(compilationTestHelper::doTest) | ||
.isInstanceOf(AssertionError.class) | ||
.message() | ||
.satisfies( | ||
message -> | ||
assertThat(extractRefasterSeverities("A.java", message)) | ||
.containsExactlyElementsOf(expectedSeverities)); | ||
} | ||
} | ||
|
||
private static ImmutableList<SeverityLevel> extractRefasterSeverities( | ||
|
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 would argue that adding
contains
andempty
toSTATIC_IMPORT_EXEMPTED_IDENTIFIERS
would maybe be even better :).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.
Interesting suggestion. Perhaps yes, though (hypothetical?) static
contains
/empty
methods that deal with "regular" collection membership would not necessarily be problematic when imported statically. (The currently exempted methods instead return predicates with custom semantics.)So... let's revisit if/when we add more such exclusions?
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.
Deal, let's do that :).