mirror of
https://github.com/jlengrand/error-prone-support.git
synced 2026-03-10 08:11:25 +00:00
Compare commits
21 Commits
v0.20.0
...
sschroever
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1971d00dd0 | ||
|
|
4b5f985f59 | ||
|
|
8184a2565a | ||
|
|
b9557c7612 | ||
|
|
a925289854 | ||
|
|
7d4f71d806 | ||
|
|
74ccae2d71 | ||
|
|
29b06ba502 | ||
|
|
a6999e3c32 | ||
|
|
49b5d2f51f | ||
|
|
77501067b5 | ||
|
|
6423d397fa | ||
|
|
245dcf903a | ||
|
|
e98cc88c2d | ||
|
|
0f24503754 | ||
|
|
bec7cddcb6 | ||
|
|
7f78ac52fe | ||
|
|
1d2d286e07 | ||
|
|
7e649bd9ae | ||
|
|
747af1b8d1 | ||
|
|
ffeeeee7a5 |
@@ -83,5 +83,10 @@
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package tech.picnic.errorprone.documentation;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
// XXX: Rename all this stuff.
|
||||
@Data
|
||||
@SuppressWarnings("EqualsMissingNullable") // XXX: Drop after EP upgrade.
|
||||
final class LombokCanary {
|
||||
@JsonProperty("custom_field_name")
|
||||
private @Nullable String field;
|
||||
}
|
||||
@@ -181,6 +181,11 @@
|
||||
<artifactId>mongodb-driver-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.reactivestreams</groupId>
|
||||
<artifactId>reactive-streams</artifactId>
|
||||
|
||||
@@ -51,6 +51,11 @@ public final class CanonicalAnnotationSyntax extends BugChecker implements Annot
|
||||
|
||||
@Override
|
||||
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
|
||||
// if (!isSourceAccurateCodeAvailable(tree, state)) {
|
||||
// /* Without the source code there's not much we can do. */
|
||||
// return Description.NO_MATCH;
|
||||
// }
|
||||
|
||||
return FIX_FACTORIES.stream()
|
||||
.map(op -> op.apply(tree, state))
|
||||
.flatMap(Optional::stream)
|
||||
@@ -59,6 +64,14 @@ public final class CanonicalAnnotationSyntax extends BugChecker implements Annot
|
||||
.orElse(Description.NO_MATCH);
|
||||
}
|
||||
|
||||
// XXX: Explain, link to Lombok issue.
|
||||
// private static boolean isSourceAccurateCodeAvailable(AnnotationTree tree, VisitorState state)
|
||||
// {
|
||||
// String source = state.getSourceForNode(tree);
|
||||
// String parentSource = state.getSourceForNode(state.getPath().getParentPath().getLeaf());
|
||||
// return source != null && parentSource != null && parentSource.contains(source);
|
||||
// }
|
||||
|
||||
private static Optional<Fix> dropRedundantParentheses(AnnotationTree tree, VisitorState state) {
|
||||
if (!tree.getArguments().isEmpty()) {
|
||||
/* Parentheses are necessary. */
|
||||
@@ -98,8 +111,8 @@ public final class CanonicalAnnotationSyntax extends BugChecker implements Annot
|
||||
}
|
||||
|
||||
ExpressionTree expr = AnnotationMatcherUtils.getArgument(tree, "value");
|
||||
if (expr == null) {
|
||||
/* This is not an explicit assignment to the `value` attribute. */
|
||||
if (expr == null || !SourceCode.isAccurateSourceLikelyAvailable(state)) {
|
||||
/* This is (probably) not an explicit assignment to the `value` attribute. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package tech.picnic.errorprone.bugpatterns;
|
||||
|
||||
import static com.google.common.base.Verify.verify;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.CompilationUnitTreeMatcher;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import tech.picnic.errorprone.bugpatterns.util.SourceCode;
|
||||
|
||||
/** A {@link BugChecker} that files for which no accurate source code appears to be available. */
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "This type's source code is unavailable or inaccurate",
|
||||
link = BUG_PATTERNS_BASE_URL + "SourceCodeAvailability",
|
||||
linkType = CUSTOM,
|
||||
severity = SUGGESTION)
|
||||
public final class SourceCodeAvailability extends BugChecker implements CompilationUnitTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** Instantiates a new {@link SourceCodeAvailability} instance. */
|
||||
public SourceCodeAvailability() {}
|
||||
|
||||
@Override
|
||||
public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) {
|
||||
Deque<Boolean> seenAccurateSource = new ArrayDeque<>();
|
||||
Set<Tree> maximalAccurateSubtrees = new LinkedHashSet<>();
|
||||
new TreeScanner<@Nullable Void, TreePath>() {
|
||||
@Override
|
||||
public @Nullable Void scan(Tree node, TreePath treePath) {
|
||||
if (node == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
TreePath path = new TreePath(treePath, node);
|
||||
boolean isAccurate = SourceCode.isAccurateSourceLikelyAvailable(state.withPath(path));
|
||||
if (!isAccurate) {
|
||||
verify(!Boolean.TRUE.equals(seenAccurateSource.peek()));
|
||||
} else if (!Boolean.TRUE.equals(seenAccurateSource.peek())) {
|
||||
maximalAccurateSubtrees.add(node);
|
||||
}
|
||||
|
||||
seenAccurateSource.push(isAccurate);
|
||||
try {
|
||||
return super.scan(node, path);
|
||||
} finally {
|
||||
seenAccurateSource.pop();
|
||||
}
|
||||
}
|
||||
}.scan(tree, state.getPath());
|
||||
|
||||
if (maximalAccurateSubtrees.equals(ImmutableSet.of(tree))) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
String accurateSubtrees =
|
||||
maximalAccurateSubtrees.stream()
|
||||
.map(
|
||||
t ->
|
||||
String.format(
|
||||
"%s tree at position %s:\n%s",
|
||||
t.getKind(), ASTHelpers.getStartPosition(t), state.getSourceForNode(t)))
|
||||
.collect(joining("\n\n", "Maximally accurate subtrees found:\n\n", ""));
|
||||
|
||||
return buildDescription(tree).setMessage(accurateSubtrees).build();
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,40 @@
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import static com.sun.source.tree.Tree.Kind.*;
|
||||
import static com.sun.tools.javac.parser.Tokens.TokenKind.RPAREN;
|
||||
import static com.sun.tools.javac.util.Position.NOPOS;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Range;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.google.errorprone.util.ErrorProneToken;
|
||||
import com.google.errorprone.util.ErrorProneTokens;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.AssignmentTree;
|
||||
import com.sun.source.tree.ExpressionStatementTree;
|
||||
import com.sun.source.tree.IdentifierTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.ModifiersTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import com.sun.tools.javac.code.Kinds;
|
||||
import com.sun.tools.javac.code.Symbol;
|
||||
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
|
||||
import com.sun.tools.javac.util.Position;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A collection of Error Prone utility methods for dealing with the source code representation of
|
||||
@@ -26,8 +44,136 @@ public final class SourceCode {
|
||||
/** The complement of {@link CharMatcher#whitespace()}. */
|
||||
private static final CharMatcher NON_WHITESPACE_MATCHER = CharMatcher.whitespace().negate();
|
||||
|
||||
// XXX: Document.
|
||||
// XXX: Once we raise the baseline to JDK 17+, review whether `BINDING_PATTERN` and
|
||||
// `GUARDED_PATTERN` should be added.
|
||||
// XXX: Review which JDK >17 tree kinds should be added.
|
||||
private static final ImmutableSet<Tree.Kind> TREE_KINDS_WITHOUT_EXPLICIT_SYNTAX =
|
||||
Sets.immutableEnumSet(COMPILATION_UNIT, MODIFIERS, ERRONEOUS, OTHER);
|
||||
|
||||
private SourceCode() {}
|
||||
|
||||
// XXX: Document that that the test is relative to the state's current path.
|
||||
public static boolean isAccurateSourceLikelyAvailable(VisitorState state) {
|
||||
return new IsAccurateSourceLikelyAvailable().test(state);
|
||||
}
|
||||
|
||||
// XXX: Move this class down or to a separate file.
|
||||
// XXX: Cache data in `VisitorState.config`.
|
||||
private static final class IsAccurateSourceLikelyAvailable
|
||||
extends TreeScanner<@Nullable Boolean, VisitorState> implements Predicate<VisitorState> {
|
||||
@Override
|
||||
public boolean test(VisitorState state) {
|
||||
return isValidAncestorSourceContainment(state) && isValidDescendantSourceContainment(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Boolean scan(@Nullable Tree tree, VisitorState parentState) {
|
||||
if (tree == null) {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
VisitorState childState = parentState.withPath(new TreePath(parentState.getPath(), tree));
|
||||
return isValidSourceContainment(childState) ? super.scan(tree, childState) : Boolean.FALSE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Boolean reduce(@Nullable Boolean a, @Nullable Boolean b) {
|
||||
return !(Boolean.FALSE.equals(a) || Boolean.FALSE.equals(b));
|
||||
}
|
||||
|
||||
private boolean isValidDescendantSourceContainment(VisitorState state) {
|
||||
return !Boolean.FALSE.equals(super.scan(state.getPath().getLeaf(), state));
|
||||
}
|
||||
|
||||
private static boolean isValidAncestorSourceContainment(VisitorState state) {
|
||||
return Stream.iterate(state.getPath(), Objects::nonNull, TreePath::getParentPath)
|
||||
.allMatch(path -> isValidSourceContainment(state.withPath(path)));
|
||||
}
|
||||
|
||||
private static boolean isValidSourceContainment(VisitorState state) {
|
||||
Tree tree = state.getPath().getLeaf();
|
||||
TreePath parentPath = state.getPath().getParentPath();
|
||||
if (parentPath == null) {
|
||||
return getSourceRange(tree, state) != null;
|
||||
}
|
||||
|
||||
Tree enclosingTree = parentPath.getLeaf();
|
||||
@Nullable Range<Integer> enclosingSourceRange = getSourceRange(enclosingTree, state);
|
||||
@Nullable Range<Integer> sourceRange = getSourceRange(tree, state);
|
||||
if (enclosingSourceRange == null) {
|
||||
return sourceRange == null || mayBeImplicit(enclosingTree, state);
|
||||
// XXX: Test code; drop.
|
||||
// return sourceRange == null && mayBeImplicit(enclosingTree, state);
|
||||
// (sourceRange == null || mayBeImplicit(enclosingTree, state)) != (sourceRange ==
|
||||
// null && mayBeImplicit(enclosingTree, state))
|
||||
}
|
||||
|
||||
if (sourceRange == null) {
|
||||
return mayBeImplicit(tree, state);
|
||||
}
|
||||
|
||||
return enclosingSourceRange.encloses(sourceRange)
|
||||
&& (TREE_KINDS_WITHOUT_EXPLICIT_SYNTAX.contains(enclosingTree.getKind())
|
||||
|| !enclosingSourceRange.equals(sourceRange));
|
||||
}
|
||||
|
||||
private static boolean mayBeImplicit(Tree tree, VisitorState state) {
|
||||
if (tree instanceof ModifiersTree) {
|
||||
ModifiersTree modifiers = (ModifiersTree) tree;
|
||||
return modifiers.getAnnotations().isEmpty() && modifiers.getFlags().isEmpty();
|
||||
}
|
||||
|
||||
// XXX: We could even *require* the generated constructor subtree to not have any associated
|
||||
// source.
|
||||
if (ASTHelpers.isGeneratedConstructor(state.findEnclosing(MethodTree.class))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
AnnotationTree annotation = state.findEnclosing(AnnotationTree.class);
|
||||
if (annotation != null && annotation.getArguments().size() == 1) {
|
||||
// XXX: The `IdentifierTree` check here has no impact because its parent will in practice be
|
||||
// `null`, which prevents `mayBeImplicit` being called review how we can be stricter.
|
||||
Symbol symbol =
|
||||
tree instanceof AssignmentTree
|
||||
? ASTHelpers.getSymbol(((AssignmentTree) tree).getVariable())
|
||||
: tree instanceof IdentifierTree ? ASTHelpers.getSymbol(tree) : null;
|
||||
|
||||
/* The `value` attribute may be omitted from single-argument annotation declarations. */
|
||||
return symbol != null
|
||||
&& symbol.kind == Kinds.Kind.MTH
|
||||
&& symbol.name.contentEquals("value");
|
||||
}
|
||||
|
||||
// XXX: Many branches aren't covered yet.
|
||||
return isSuperInvocation(tree)
|
||||
|| (tree instanceof IdentifierTree
|
||||
&& isSuperInvocation(state.getPath().getParentPath().getLeaf()));
|
||||
}
|
||||
|
||||
private static boolean isSuperInvocation(Tree tree) {
|
||||
if (tree instanceof ExpressionStatementTree) {
|
||||
return isSuperInvocation(((ExpressionStatementTree) tree).getExpression());
|
||||
}
|
||||
|
||||
if (!(tree instanceof MethodInvocationTree)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MethodInvocationTree invocation = (MethodInvocationTree) tree;
|
||||
return invocation.getArguments().isEmpty()
|
||||
&& ASTHelpers.isSuper(invocation.getMethodSelect());
|
||||
}
|
||||
|
||||
private static @Nullable Range<Integer> getSourceRange(Tree tree, VisitorState state) {
|
||||
int startPosition = ASTHelpers.getStartPosition(tree);
|
||||
int endPosition = state.getEndPosition(tree);
|
||||
return startPosition == Position.NOPOS || endPosition == Position.NOPOS
|
||||
? null
|
||||
: Range.closedOpen(startPosition, endPosition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of the given {@link Tree}, preferring the original source code
|
||||
* (if available) over its prettified representation.
|
||||
@@ -58,7 +204,7 @@ public final class SourceCode {
|
||||
public static SuggestedFix deleteWithTrailingWhitespace(Tree tree, VisitorState state) {
|
||||
CharSequence sourceCode = state.getSourceCode();
|
||||
int endPos = state.getEndPosition(tree);
|
||||
if (sourceCode == null || endPos == NOPOS) {
|
||||
if (sourceCode == null || endPos == Position.NOPOS) {
|
||||
/* We can't identify the trailing whitespace; delete just the tree. */
|
||||
return SuggestedFix.delete(tree);
|
||||
}
|
||||
|
||||
@@ -276,4 +276,25 @@ final class CanonicalAnnotationSyntaxTest {
|
||||
"}")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
|
||||
@Test
|
||||
void ignoreLombokViolations() {
|
||||
CompilationTestHelper.newInstance(CanonicalAnnotationSyntax.class, getClass())
|
||||
.setArgs("-processor", "lombok.launch.AnnotationProcessorHider$AnnotationProcessor")
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import com.fasterxml.jackson.annotation.JsonProperty;",
|
||||
"import lombok.Data;",
|
||||
"",
|
||||
"@Data",
|
||||
"class A {",
|
||||
" @JsonProperty(\"custom_field_name\")",
|
||||
" private String field;",
|
||||
"",
|
||||
" // Should be flagged because of the redundant parentheses, but Lombok doesn't properly link the",
|
||||
" // annotation to its source code.",
|
||||
" @JsonProperty() private String field2;",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package tech.picnic.errorprone.bugpatterns.util;
|
||||
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper;
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
|
||||
@@ -8,18 +10,92 @@ import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.CompilationUnitTreeMatcher;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFix;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.CompilationUnitTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.TreePath;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import javax.lang.model.element.Name;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class SourceCodeTest {
|
||||
@Test
|
||||
void isAccurateSourceLikelyAvailable() {
|
||||
BugCheckerRefactoringTestHelper.newInstance(
|
||||
IsAccurateSourceLikelyAvailableTestChecker.class, getClass())
|
||||
.setArgs("-processor", "lombok.launch.AnnotationProcessorHider$AnnotationProcessor")
|
||||
.allowBreakingChanges()
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import com.fasterxml.jackson.annotation.JsonProperty;",
|
||||
"import lombok.Data;",
|
||||
"",
|
||||
"class A {",
|
||||
" class WithExplicitNullaryConstructor {",
|
||||
" WithExplicitNullaryConstructor() {}",
|
||||
" }",
|
||||
"",
|
||||
" class WithoutLombok {",
|
||||
" @JsonProperty(\"custom_field_name\")",
|
||||
" private String field;",
|
||||
" }",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Data",
|
||||
" class WithLombok {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @JsonProperty(\"custom_field_name\")",
|
||||
" private String field;",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"Maximally accurate subtrees found:",
|
||||
"",
|
||||
"IMPORT tree at position 0:",
|
||||
"import com.fasterxml.jackson.annotation.JsonProperty;",
|
||||
"",
|
||||
"IMPORT tree at position 54:",
|
||||
"import lombok.Data;",
|
||||
"",
|
||||
"MODIFIERS tree at position -1:",
|
||||
"null",
|
||||
"",
|
||||
"METHOD tree at position 75:",
|
||||
"null",
|
||||
"",
|
||||
"CLASS tree at position 87:",
|
||||
"class WithExplicitNullaryConstructor {",
|
||||
" WithExplicitNullaryConstructor() {}",
|
||||
" }",
|
||||
"",
|
||||
"CLASS tree at position 173:",
|
||||
"class WithoutLombok {",
|
||||
" @JsonProperty(\"custom_field_name\")",
|
||||
" private String field;",
|
||||
" }",
|
||||
"",
|
||||
"MODIFIERS tree at position 298:",
|
||||
"@Data",
|
||||
"",
|
||||
"IDENTIFIER tree at position 409:",
|
||||
"String")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteWithTrailingWhitespaceAnnotations() {
|
||||
BugCheckerRefactoringTestHelper.newInstance(
|
||||
@@ -228,6 +304,56 @@ final class SourceCodeTest {
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that uses {@link SourceCode#isAccurateSourceLikelyAvailable(VisitorState)}
|
||||
* to flag AST nodes for which accurate source code does not appear to be available.
|
||||
*/
|
||||
@BugPattern(severity = ERROR, summary = "Interacts with `SourceCode` for testing purposes")
|
||||
public static final class IsAccurateSourceLikelyAvailableTestChecker extends BugChecker
|
||||
implements CompilationUnitTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public Description matchCompilationUnit(CompilationUnitTree tree, VisitorState state) {
|
||||
Deque<Boolean> seenAccurateSource = new ArrayDeque<>();
|
||||
Set<Tree> maximalAccurateSubtrees = new LinkedHashSet<>();
|
||||
new TreeScanner<@Nullable Void, TreePath>() {
|
||||
@Override
|
||||
public @Nullable Void scan(Tree node, TreePath treePath) {
|
||||
if (node == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
TreePath path = new TreePath(treePath, node);
|
||||
boolean isAccurate = SourceCode.isAccurateSourceLikelyAvailable(state.withPath(path));
|
||||
if (!isAccurate) {
|
||||
assertThat(seenAccurateSource.peek()).isNotIn(Boolean.TRUE);
|
||||
} else if (!Boolean.TRUE.equals(seenAccurateSource.peek())) {
|
||||
maximalAccurateSubtrees.add(node);
|
||||
}
|
||||
|
||||
seenAccurateSource.push(isAccurate);
|
||||
try {
|
||||
return super.scan(node, path);
|
||||
} finally {
|
||||
seenAccurateSource.pop();
|
||||
}
|
||||
}
|
||||
}.scan(tree, state.getPath());
|
||||
|
||||
String accurateSubtrees =
|
||||
maximalAccurateSubtrees.stream()
|
||||
.map(
|
||||
t ->
|
||||
String.format(
|
||||
"%s tree at position %s:\n%s",
|
||||
t.getKind(), ASTHelpers.getStartPosition(t), state.getSourceForNode(t)))
|
||||
.collect(joining("\n\n", "Maximally accurate subtrees found:\n\n", ""));
|
||||
|
||||
return describeMatch(tree, SuggestedFix.replace(tree, accurateSubtrees));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that uses {@link SourceCode#deleteWithTrailingWhitespace(Tree,
|
||||
* VisitorState)} to suggest the deletion of annotations and methods with a name containing
|
||||
|
||||
30
pom.xml
30
pom.xml
@@ -201,6 +201,7 @@
|
||||
<version.auto-value>1.10.3</version.auto-value>
|
||||
<version.error-prone>${version.error-prone-orig}</version.error-prone>
|
||||
<version.error-prone-fork>v${version.error-prone-orig}-picnic-1</version.error-prone-fork>
|
||||
<!--<version.error-prone-orig>HEAD-SNAPSHOT</version.error-prone-orig>-->
|
||||
<version.error-prone-orig>2.21.1</version.error-prone-orig>
|
||||
<version.error-prone-slf4j>0.1.20</version.error-prone-slf4j>
|
||||
<version.guava-beta-checker>1.0</version.guava-beta-checker>
|
||||
@@ -456,6 +457,11 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.28</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
@@ -918,16 +924,17 @@
|
||||
<artifactId>nullaway</artifactId>
|
||||
<version>${version.nullaway}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>jp.skypencil.errorprone.slf4j</groupId>
|
||||
<artifactId>errorprone-slf4j</artifactId>
|
||||
<version>${version.error-prone-slf4j}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-errorprone</artifactId>
|
||||
<version>${version.mockito}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<!-- Replace version. -->
|
||||
<version>1.18.28</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
<compilerArgs>
|
||||
<arg>--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
|
||||
@@ -1427,6 +1434,19 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
</plugin>
|
||||
<!--<plugin>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok-maven-plugin</artifactId>
|
||||
<version>1.18.20.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>delombok</goal>
|
||||
</goals>
|
||||
<phase>generate-sources</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>-->
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user