-
Notifications
You must be signed in to change notification settings - Fork 1
Fix removeUnused mode not loading plugin checks and alt names #278
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
base: develop
Are you sure you want to change the base?
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 | ||||
|---|---|---|---|---|---|---|
|
|
@@ -16,33 +16,63 @@ | |||||
|
|
||||||
| package com.palantir.suppressibleerrorprone; | ||||||
|
|
||||||
| import com.google.common.base.Suppliers; | ||||||
| import com.google.errorprone.BugCheckerInfo; | ||||||
| import com.google.errorprone.VisitorState; | ||||||
| import com.google.errorprone.bugpatterns.BugChecker; | ||||||
| import com.google.errorprone.scanner.BuiltInCheckerSuppliers; | ||||||
| import com.google.errorprone.suppliers.Supplier; | ||||||
| import com.sun.tools.javac.processing.JavacProcessingEnvironment; | ||||||
| import java.util.List; | ||||||
| import java.util.Map; | ||||||
| import java.util.Optional; | ||||||
| import java.util.ServiceLoader; | ||||||
| import java.util.ServiceLoader.Provider; | ||||||
| import java.util.Set; | ||||||
| import java.util.function.Supplier; | ||||||
| import java.util.stream.Collectors; | ||||||
| import java.util.stream.Stream; | ||||||
| import one.util.streamex.EntryStream; | ||||||
| import one.util.streamex.StreamEx; | ||||||
|
|
||||||
| public final class AllErrorprones { | ||||||
| private static Supplier<Set<String>> cachedAllBugcheckerNames = Suppliers.memoize(() -> { | ||||||
| Stream<BugChecker> pluginChecks = | ||||||
| ServiceLoader.load(BugChecker.class).stream().map(ServiceLoader.Provider::get); | ||||||
| Stream<String> pluginCheckNames = | ||||||
| StreamEx.of(pluginChecks).flatMap(bugchecker -> bugchecker.allNames().stream()); | ||||||
| private static Supplier<Map<String, Set<String>>> canonicalToAllNames = VisitorState.memoize(state -> { | ||||||
|
Contributor
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.
Suggested change
|
||||||
| // Use the same classloader that Error Prone was loaded from to avoid classloader skew | ||||||
| // when using Error Prone plugins together with the Error Prone javac plugin. | ||||||
| JavacProcessingEnvironment processingEnvironment = JavacProcessingEnvironment.instance(state.context); | ||||||
| ClassLoader loader = processingEnvironment.getProcessorClassLoader(); | ||||||
|
Contributor
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 wonder if there's any real difference between this and |
||||||
| List<BugChecker> pluginChecks = ServiceLoader.load(BugChecker.class, loader).stream() | ||||||
| .map(Provider::get) | ||||||
| .collect(Collectors.toList()); | ||||||
| EntryStream<String, Set<String>> pluginCheckNames = | ||||||
| StreamEx.of(pluginChecks).mapToEntry(BugChecker::canonicalName, BugChecker::allNames); | ||||||
|
Contributor
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.
Contributor
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. Which may be what we want... |
||||||
|
|
||||||
| Stream<BugCheckerInfo> builtInChecks = BuiltInCheckerSuppliers.allChecks().getAllChecks().values().stream(); | ||||||
| Stream<String> builtInCheckNames = | ||||||
| StreamEx.of(builtInChecks).flatMap(bugchecker -> bugchecker.allNames().stream()); | ||||||
| EntryStream<String, Set<String>> builtInCheckNames = | ||||||
| StreamEx.of(builtInChecks).mapToEntry(BugCheckerInfo::canonicalName, BugCheckerInfo::allNames); | ||||||
|
|
||||||
| return Stream.concat(pluginCheckNames, builtInCheckNames).collect(Collectors.toSet()); | ||||||
| return pluginCheckNames.append(builtInCheckNames).toMap(); | ||||||
| }); | ||||||
|
|
||||||
| public static Set<String> allBugcheckerNames() { | ||||||
| return cachedAllBugcheckerNames.get(); | ||||||
| private static Supplier<Set<String>> allNames = | ||||||
|
Contributor
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.
Suggested change
|
||||||
| VisitorState.memoize(state -> EntryStream.of(canonicalToAllNames.get(state)) | ||||||
| .flatMap(entry -> entry.getValue().stream()) | ||||||
| .collect(Collectors.toSet())); | ||||||
|
|
||||||
| private static Supplier<Map<String, Set<String>>> nameToPossibleCanonicalNames = | ||||||
|
Contributor
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.
Suggested change
Contributor
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. Is it possible to have multiple checks share the same alt name? I imagine so, though it's also pretty surprising |
||||||
| VisitorState.memoize(state -> EntryStream.of(canonicalToAllNames.get(state)) | ||||||
| .flatMapValues(Set::stream) | ||||||
| .invert() | ||||||
| .grouping(Collectors.toSet())); | ||||||
|
|
||||||
| public static Set<String> allBugcheckerNames(VisitorState state) { | ||||||
| return allNames.get(state); | ||||||
| } | ||||||
|
|
||||||
| public static Optional<Set<String>> allNames(VisitorState state, String canonicalName) { | ||||||
| return Optional.ofNullable(canonicalToAllNames.get(state).get(canonicalName)); | ||||||
| } | ||||||
|
|
||||||
| public static Set<String> possibleCanonicalNames(VisitorState state, String name) { | ||||||
| return nameToPossibleCanonicalNames.get(state).getOrDefault(name, Set.of()); | ||||||
| } | ||||||
|
|
||||||
| private AllErrorprones() {} | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,7 +40,9 @@ public Void visitAnnotation(AnnotationTree node, Void unused) { | |
| if (AnnotationUtils.isSuppressWarningsAnnotation(node)) { | ||
| TreePath declaration = getCurrentPath().getParentPath().getParentPath(); | ||
|
|
||
| reportedFixes.getOrReportNew(declaration, state, ReportedFixCache.NOT_AN_ERRORPRONE); | ||
| reportedFixes.getOrReportNew( | ||
| declaration, state, suppression -> !AllErrorprones.allBugcheckerNames(state) | ||
| .contains(suppression)); | ||
|
Comment on lines
+44
to
+45
Contributor
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. You were previously removing the for-rollout prefix and aren't doing so anymore. Is that intentional? |
||
| } | ||
|
|
||
| return super.visitAnnotation(node, unused); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,10 +24,12 @@ | |
| import com.sun.source.tree.AnnotationTree; | ||
| import com.sun.source.tree.CompilationUnitTree; | ||
| import com.sun.source.util.TreePath; | ||
| import java.util.List; | ||
| import java.util.Optional; | ||
| import java.util.Set; | ||
| import java.util.logging.Logger; | ||
| import java.util.stream.Stream; | ||
| import one.util.streamex.MoreCollectors; | ||
|
|
||
| // CHECKSTYLE:ON | ||
|
|
||
|
|
@@ -83,13 +85,23 @@ public static Description interceptDescription(VisitorState visitorState, Descri | |
|
|
||
| Optional<TreePath> firstSuppressibleWhichSuppressesDescription = Stream.iterate( | ||
| pathToActualError, treePath -> treePath.getParentPath() != null, TreePath::getParentPath) | ||
| .dropWhile(path -> !suppresses(path, description)) | ||
| .dropWhile(path -> !suppresses(path, description, visitorState)) | ||
| .findFirst(); | ||
|
|
||
| if (firstSuppressibleWhichSuppressesDescription.isPresent() && modes.contains("RemoveUnused")) { | ||
| // In RemoveUnused mode, removeAllSuppressionsOnErrorprones guarantees that a fix must already exist on | ||
| // this suppressible. | ||
| FIXES.getExisting(firstSuppressibleWhichSuppressesDescription.get()).addSuppression(description.checkName); | ||
| TreePath declaration = firstSuppressibleWhichSuppressesDescription.get(); | ||
| Set<String> allNames = | ||
| AllErrorprones.allNames(visitorState, description.checkName).get(); | ||
| // Use the existing suppression, rather than changing it to the canonical suppression | ||
| String existingSuppression = AnnotationUtils.annotationStringValues( | ||
| SuppressWarningsUtils.getSuppressWarnings(declaration) | ||
| .get()) | ||
| .filter(suppression -> allNames.contains(SuppressWarningsUtils.stripForRollout(suppression))) | ||
| .collect(MoreCollectors.first()) | ||
| .get(); | ||
|
Comment on lines
+98
to
+103
Contributor
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. Could this leverage |
||
| FIXES.getExisting(declaration).addSuppression(existingSuppression); | ||
|
Comment on lines
+98
to
+104
Contributor
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. Shouldn't you keep all the ones that might already exist, rather than just the first one? (especially the non-rollout ones) |
||
| return Description.NO_MATCH; | ||
| } | ||
|
|
||
|
|
@@ -124,26 +136,31 @@ public static Description interceptDescription(VisitorState visitorState, Descri | |
| // this, we make sure we only make one fix per source element we put the suppression on by using a Map. This | ||
| // way we have our own mutable Fix that we can add errors to, and only once the file has been visited by all | ||
| // the error-prone checks it will then produce a replacement with all the checks suppressed. | ||
| LazySuppressionFix suppressingFix = | ||
| FIXES.getOrReportNew(firstSuppressible.get(), visitorState, ReportedFixCache.KEEP_EVERYTHING); | ||
| LazySuppressionFix suppressingFix = FIXES.getOrReportNew(firstSuppressible.get(), visitorState, bc -> true); | ||
| suppressingFix.addSuppression(CommonConstants.AUTOMATICALLY_ADDED_PREFIX + description.checkName); | ||
| return Description.NO_MATCH; | ||
| } | ||
|
|
||
| private static boolean suppresses(TreePath declaration, Description description) { | ||
| private static boolean suppresses(TreePath declaration, Description description, VisitorState state) { | ||
| return !suppressionsOn(declaration, description, state).isEmpty(); | ||
| } | ||
|
|
||
| private static List<String> suppressionsOn(TreePath declaration, Description description, VisitorState state) { | ||
| if (!SuppressWarningsUtils.suppressibleTreePath(declaration)) { | ||
| return false; | ||
| return List.of(); | ||
| } | ||
|
|
||
| Optional<? extends AnnotationTree> suppressWarningsMaybe = | ||
| SuppressWarningsUtils.getSuppressWarnings(declaration); | ||
| if (suppressWarningsMaybe.isEmpty()) { | ||
| return false; | ||
| return List.of(); | ||
| } | ||
|
|
||
| String checkName = description.checkName; | ||
| return AnnotationUtils.annotationStringValues(suppressWarningsMaybe.get()) | ||
| .anyMatch(checkName::equals); | ||
| .filter(suppression -> AllErrorprones.possibleCanonicalNames( | ||
| state, SuppressWarningsUtils.stripForRollout(suppression)) | ||
| .contains(description.checkName)) | ||
| .toList(); | ||
| } | ||
|
|
||
| private static Set<String> getModes(VisitorState state) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| /* | ||
| * (c) Copyright 2025 Palantir Technologies Inc. All rights reserved. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package com.palantir.gradle.suppressibleerrorprone; | ||
|
|
||
| import com.google.auto.service.AutoService; | ||
| import com.google.errorprone.BugPattern; | ||
| import com.google.errorprone.VisitorState; | ||
| import com.google.errorprone.bugpatterns.BugChecker; | ||
| import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher; | ||
| import com.google.errorprone.matchers.Description; | ||
| import com.sun.source.tree.IdentifierTree; | ||
| import com.sun.source.tree.MethodTree; | ||
|
|
||
| @AutoService(BugChecker.class) | ||
| @BugPattern( | ||
| severity = BugPattern.SeverityLevel.ERROR, | ||
| summary = "This check is meant only for testing suppressible-error-prone functionality") | ||
| public final class ShouldNotReturn extends BugChecker implements MethodTreeMatcher { | ||
|
Contributor
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. Did you use this anywhere? |
||
| @Override | ||
| public Description matchMethod(MethodTree tree, VisitorState _state) { | ||
| if (!(tree.getReturnType() instanceof IdentifierTree id)) { | ||
| return Description.NO_MATCH; | ||
| } | ||
|
|
||
| if (!id.getName().contentEquals("DontReturnMe")) { | ||
| return Description.NO_MATCH; | ||
| } | ||
|
|
||
| return buildDescription(id).setMessage("Don't return me").build(); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.
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.
RemoveUnused doesn't take arguments — this argument was being ignored