Skip to content
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

Merged
merged 2 commits into from
Oct 11, 2023

Conversation

Stephan202
Copy link
Member

@Stephan202 Stephan202 commented Aug 30, 2023

Suggested commit message:

Introduce `PatternRules` Refaster rule collection (#771)

While there, configure `StaticImport` to not require static importing of
`com.google.common.base.Predicates.contains`.

The new rules triggered cleanup of some `CompilationTestHelper` usages.

See google/guava#6483.

@Stephan202 Stephan202 added this to the 0.14.0 milestone Aug 30, 2023
@Stephan202 Stephan202 requested review from rickie and benhalasi August 30, 2023 08:25
@github-actions
Copy link

Looks good. No mutations were possible for these changes.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

Comment on lines +168 to +170
if (expectedSeverities.isEmpty()) {
compilationTestHelper.doTest();
} else {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously this wasn't necessary, because the expectErrorMessage declarations would trigger a "I didn't see anything" AssertionError, which would then cause the satisfies clause to succeed. But that's obscure.

Copy link
Member

@rickie rickie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only one suggestion. Will approve nonetheless. Feel free when the second approval is in 😉.

@@ -155,6 +155,7 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
@VisibleForTesting
static final ImmutableSetMultimap<String, String> STATIC_IMPORT_EXEMPTED_MEMBERS =
ImmutableSetMultimap.<String, String>builder()
.put("com.google.common.base.Predicates", "contains")
Copy link
Member

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 and empty to STATIC_IMPORT_EXEMPTED_IDENTIFIERS would maybe be even better :).

Copy link
Member Author

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?

Copy link
Member

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 :).

Copy link
Member Author

@Stephan202 Stephan202 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tnx for the review!

@@ -155,6 +155,7 @@ public final class StaticImport extends BugChecker implements MemberSelectTreeMa
@VisibleForTesting
static final ImmutableSetMultimap<String, String> STATIC_IMPORT_EXEMPTED_MEMBERS =
ImmutableSetMultimap.<String, String>builder()
.put("com.google.common.base.Predicates", "contains")
Copy link
Member Author

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?

@rickie
Copy link
Member

rickie commented Sep 1, 2023

Rebased.

@rickie rickie force-pushed the sschroevers/introduce-pattern-rules branch from 8583a5e to 55c299a Compare September 1, 2023 09:27
@sonarqubecloud
Copy link

sonarqubecloud bot commented Sep 1, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

100.0% 100.0% Coverage
0.0% 0.0% Duplication

@github-actions
Copy link

github-actions bot commented Sep 1, 2023

Looks good. No mutations were possible for these changes.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@rickie rickie modified the milestones: 0.14.0, 0.15.0 Oct 4, 2023

@AfterTemplate
Predicate<String> after(String pattern) {
return Pattern.compile(pattern).asPredicate();
Copy link
Contributor

Choose a reason for hiding this comment

The 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 Lists.transform)

Copy link
Member Author

Choose a reason for hiding this comment

The 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:

  1. This code makes explicit that the operation involves pattern compilation; something one will generally want to move to a static field whenever possible.
    • Or, as in the cleaned-up test code, avoid altogether by simply calling String#contains instead.
  2. This code better conveys (through the explicit Pattern reference) that the predicate performs regex matching, rather than any other kind of pattern matching.

W.r.t. List#transform: indeed we don't rewrite that variant, but that's primarily because realistically it can only be rewritten when considering the surrounding context. That method's documentation does suggest that Java 8+ users should prefer Stream#map.


@AfterTemplate
Predicate<String> after(Pattern pattern) {
return pattern.asPredicate();
Copy link
Contributor

Choose a reason for hiding this comment

The 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 String vs. Guava's CharSequence

Copy link
Member Author

Choose a reason for hiding this comment

The 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)

On descriptiveness I guess we'll just have to disagree 😅. W.r.t. recommendations: while contains itself doesn't call it out, the returned com.google.common.base.Predicate is described as a legacy type that should be avoided whenever possible.

I'm also not sure it's 100% equivalent as JDK uses String vs. Guava's CharSequence

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.)

Copy link
Contributor

@Venorcis Venorcis Oct 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the returned com.google.common.base.Predicate is described as a legacy type that should be avoided whenever possible.

good point, should that be 'attacked' even more generically? essentially the entire Predicates class shouldn't be used then 😅

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally yes! git grep com.google.common.base.Predicate -- '*.java' shows that internally not too many usages remain, so it's doable. But in some cases the result may be passed to a com.google.common.base.Predicate-expecting method, in which case a Refaster approach isn't great. That's the same issue as we have here, but I expect that the impact will be larger when replacing or and not. So propose to defer that to a separate PR.

Copy link
Member Author

@Stephan202 Stephan202 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partially the discussion may be moot; a git grep 'Predicates.contains' against the Picnic code base yields only the Error Prone Support code modified in this PR 😄


@AfterTemplate
Predicate<String> after(String pattern) {
return Pattern.compile(pattern).asPredicate();
Copy link
Member Author

Choose a reason for hiding this comment

The 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:

  1. This code makes explicit that the operation involves pattern compilation; something one will generally want to move to a static field whenever possible.
    • Or, as in the cleaned-up test code, avoid altogether by simply calling String#contains instead.
  2. This code better conveys (through the explicit Pattern reference) that the predicate performs regex matching, rather than any other kind of pattern matching.

W.r.t. List#transform: indeed we don't rewrite that variant, but that's primarily because realistically it can only be rewritten when considering the surrounding context. That method's documentation does suggest that Java 8+ users should prefer Stream#map.


@AfterTemplate
Predicate<String> after(Pattern pattern) {
return pattern.asPredicate();
Copy link
Member Author

Choose a reason for hiding this comment

The 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)

On descriptiveness I guess we'll just have to disagree 😅. W.r.t. recommendations: while contains itself doesn't call it out, the returned com.google.common.base.Predicate is described as a legacy type that should be avoided whenever possible.

I'm also not sure it's 100% equivalent as JDK uses String vs. Guava's CharSequence

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.)

Copy link
Contributor

@benhalasi benhalasi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool stuff!

Left just one question about the testing.


/** Refaster rules related to code dealing with regular expressions. */
@OnlineDocumentation
final class PatternRules {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noticed that only this rule is not in RefasterRulesTest#RULE_COLLECTIONS, shouldn't it be included?

Copy link
Member

Choose a reason for hiding this comment

The 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.
Now that I think of it, we forgot that part for the #811 PR 🤯, will open a PR right now.

Copy link
Member Author

Choose a reason for hiding this comment

The 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 😅)

Comment on lines -23 to +21
and(
containsPattern("SuppressWarnings"),
containsPattern("toImmutableList"),
containsPattern("toList")))
m -> Stream.of("SuppressWarnings", "toImmutableList", "toList").allMatch(m::contains))
Copy link
Contributor

@benhalasi benhalasi Oct 8, 2023

Choose a reason for hiding this comment

The 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 contains("foo") is much more nicer to read than str -> str.contains("foo").

Seeing that we don't have Predicates#contains in Picnic codebase, I myself questioning the usefulness of these utilities, but maybe only for eps? Wdyt?

Copy link
Member Author

Choose a reason for hiding this comment

The 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 str -> str.contains("foo") I unambiguously know what that code will do, while with contains("foo") I'm like "that's funny, a helper method for such a simple operation; let me check the implementation to see whether it does anything special". So all-in-all I favour the current approach.

Copy link
Member Author

@Stephan202 Stephan202 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tnx for all the careful reviews! ❤️


/** Refaster rules related to code dealing with regular expressions. */
@OnlineDocumentation
final class PatternRules {
Copy link
Member Author

Choose a reason for hiding this comment

The 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 😅)


@AfterTemplate
Predicate<String> after(Pattern pattern) {
return pattern.asPredicate();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally yes! git grep com.google.common.base.Predicate -- '*.java' shows that internally not too many usages remain, so it's doable. But in some cases the result may be passed to a com.google.common.base.Predicate-expecting method, in which case a Refaster approach isn't great. That's the same issue as we have here, but I expect that the impact will be larger when replacing or and not. So propose to defer that to a separate PR.

Comment on lines -23 to +21
and(
containsPattern("SuppressWarnings"),
containsPattern("toImmutableList"),
containsPattern("toList")))
m -> Stream.of("SuppressWarnings", "toImmutableList", "toList").allMatch(m::contains))
Copy link
Member Author

Choose a reason for hiding this comment

The 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 str -> str.contains("foo") I unambiguously know what that code will do, while with contains("foo") I'm like "that's funny, a helper method for such a simple operation; let me check the implementation to see whether it does anything special". So all-in-all I favour the current approach.

@Stephan202 Stephan202 force-pushed the sschroevers/introduce-pattern-rules branch from 55c299a to a16548c Compare October 9, 2023 11:27
@github-actions
Copy link

github-actions bot commented Oct 9, 2023

Looks good. No mutations were possible for these changes.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@benhalasi benhalasi self-requested a review October 9, 2023 15:13
@rickie rickie force-pushed the sschroevers/introduce-pattern-rules branch from a16548c to b33c1d9 Compare October 10, 2023 06:38
@github-actions
Copy link

Looks good. No mutations were possible for these changes.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@rickie rickie force-pushed the sschroevers/introduce-pattern-rules branch from b33c1d9 to ec6a790 Compare October 10, 2023 11:19
@rickie
Copy link
Member

rickie commented Oct 10, 2023

Rebased, will merge once 📗 , nice discussions 🚀 !

@github-actions
Copy link

Looks good. No mutations were possible for these changes.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

While there, configure `StaticImport` to not require static importing of
`com.google.common.base.Predicates.contains`.

The new rules triggered cleanup of some `CompilationTestHelper` usages.

See google/guava#6483.
@rickie rickie force-pushed the sschroevers/introduce-pattern-rules branch from ec6a790 to 45dafa8 Compare October 11, 2023 06:19
@sonarqubecloud
Copy link

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 0 Code Smells

100.0% 100.0% Coverage
0.0% 0.0% Duplication

@github-actions
Copy link

Looks good. No mutations were possible for these changes.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@rickie rickie merged commit 7567939 into master Oct 11, 2023
17 checks passed
@rickie rickie deleted the sschroevers/introduce-pattern-rules branch October 11, 2023 06:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

4 participants