mirror of
https://github.com/jlengrand/error-prone-support.git
synced 2026-03-10 08:11:25 +00:00
Support in/exclusions for LexicographicalAnnotationAttributeListingCheck
While there: - Also enable more compiler checks. - Add and apply the Google Java formatter. (Yeah, this should have been a separate commit...
This commit is contained in:
@@ -279,6 +279,20 @@
|
||||
</rules>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.theoryinpractise</groupId>
|
||||
<artifactId>googleformatter-maven-plugin</artifactId>
|
||||
<version>1.5.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>format-sources</id>
|
||||
<phase>process-sources</phase>
|
||||
<goals>
|
||||
<goal>format</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>de.thetaphi</groupId>
|
||||
<artifactId>forbiddenapis</artifactId>
|
||||
@@ -665,59 +679,24 @@
|
||||
<configuration>
|
||||
<compilerArgs combine.children="append">
|
||||
<arg>-Werror</arg>
|
||||
<arg>-Xdoclint:reference</arg>
|
||||
<arg>-Xlint:all</arg>
|
||||
<!-- Some generated jOOQ code performs a
|
||||
redundant cast. Until that is resolved we
|
||||
suppress this warning. See
|
||||
https://github.com/jOOQ/jOOQ/issues/6705. -->
|
||||
<arg>-Xlint:-cast</arg>
|
||||
<!-- Some of the dependencies we pull in
|
||||
declare additional non-existent dependencies in
|
||||
their MANIFEST.MF file. Since we're using Maven
|
||||
this doesn't impact us. As such the resultant
|
||||
compiler warnings are irrelevant. -->
|
||||
<arg>-Xlint:-path</arg>
|
||||
<!-- The Immutables.org annotation processor
|
||||
does not handle all annotations present on the
|
||||
classpath, and javac complains about this. That
|
||||
doesn't make a lot of sense. From time to time
|
||||
we should review whether this issue has been
|
||||
<!-- Not all annotations present on the
|
||||
classpath are handled by annotation processors,
|
||||
and javac complains about this. That doesn't
|
||||
make a lot of sense. From time to time we
|
||||
should review whether this issue has been
|
||||
resolved. -->
|
||||
<arg>-Xlint:-processing</arg>
|
||||
<!-- The JAXB-generated classes implement
|
||||
java.io.Serializable, yet do not declare the
|
||||
serialVersionUID field. Until that's solved, we
|
||||
cannot enable "serial" warnings. -->
|
||||
<arg>-Xlint:-serial</arg>
|
||||
<!-- We want to enable almost all error-prone
|
||||
bug pattern checkers, so we enable all and then
|
||||
selectively deactivate some. -->
|
||||
<arg>-XepAllDisabledChecksAsWarnings</arg>
|
||||
<!-- The JAXB-generated classes exhibit some
|
||||
error-prone bug patterns. Nothing we can do
|
||||
about that, so we simply tell error-prone not
|
||||
to warn about generated code. -->
|
||||
<arg>-XepDisableWarningsInGeneratedCode</arg>
|
||||
<!-- XXX: A whole bunch of JPA entities violate
|
||||
this pattern. Revisit this once we phased out
|
||||
Hibernate. See
|
||||
https://picnic.atlassian.net/browse/PRP-6035. -->
|
||||
<arg>-Xep:ConstructorInvokesOverridable:OFF</arg>
|
||||
<!-- See https://github.com/google/error-prone/issues/655. -->
|
||||
<arg>-Xep:ConstructorLeaksThis:OFF</arg>
|
||||
<!-- See https://github.com/google/error-prone/issues/708. -->
|
||||
<arg>-Xep:FieldMissingNullable:OFF</arg>
|
||||
<!-- Disabled for now, but TBD. -->
|
||||
<arg>-Xep:MethodCanBeStatic:OFF</arg>
|
||||
<!-- See https://github.com/google/error-prone/issues/723. -->
|
||||
<arg>-Xep:ReturnMissingNullable:OFF</arg>
|
||||
<!-- Deals with an Android-specific limitation
|
||||
not applicable to us. See also
|
||||
https://github.com/google/error-prone/issues/488. -->
|
||||
<arg>-Xep:StaticOrDefaultInterfaceMethod:OFF</arg>
|
||||
<!-- Interesting idea, but way too much work for now. -->
|
||||
<arg>-Xep:Var:OFF</arg>
|
||||
<!-- The following bug patterns by default have
|
||||
"SUGGESTION" severity. We raise them to the
|
||||
"WARNING" level. See also
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.picnicinternational.errorprone.bugpatterns;
|
||||
|
||||
import com.google.common.collect.HashMultimap;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableSetMultimap;
|
||||
import com.google.common.collect.SetMultimap;
|
||||
import java.io.Serializable;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
// XXX: Redefine using auto-value
|
||||
// XXX: Document design decision that the project stays as close as possible to Error Prone.
|
||||
// ^ ... and *therefore* uses Google Auto Value rather than Immutables.org.
|
||||
/**
|
||||
* A matcher of (annotation, attribute) pairs.
|
||||
*
|
||||
* <p>This class allows one to define a whitelist or blacklist of annotations or their attributes.
|
||||
* Annotations are identified by their fully qualified name.
|
||||
*/
|
||||
final class AnnotationAttributeMatcher implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final boolean complement;
|
||||
private final ImmutableSet<String> wholeTypes;
|
||||
private final ImmutableSetMultimap<String, String> includedAttributes;
|
||||
private final ImmutableSetMultimap<String, String> excludedAttributes;
|
||||
|
||||
private AnnotationAttributeMatcher(
|
||||
boolean complement,
|
||||
ImmutableSet<String> wholeTypes,
|
||||
ImmutableSetMultimap<String, String> includedAttributes,
|
||||
ImmutableSetMultimap<String, String> excludedAttributes) {
|
||||
this.complement = complement;
|
||||
this.wholeTypes = wholeTypes;
|
||||
this.includedAttributes = includedAttributes;
|
||||
this.excludedAttributes = excludedAttributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link AnnotationAttributeMatcher}.
|
||||
*
|
||||
* <p>Each string provided to this method must be of the form {@code
|
||||
* "some.fully.qualified.AnnotationType"} or {@code
|
||||
* "some.fully.qualified.AnnotationType#attribute"}.
|
||||
*
|
||||
* @param inclusions If specified, only the listed annotations or annotation attributes are
|
||||
* matched.
|
||||
* @param exclusions If specified, the listed annotations or annotation attributes are not
|
||||
* matched.
|
||||
* @return A non-{@code null} {@link AnnotationAttributeMatcher}.
|
||||
*/
|
||||
static AnnotationAttributeMatcher create(
|
||||
Optional<ImmutableList<String>> inclusions, Optional<ImmutableList<String>> exclusions) {
|
||||
Set<String> includedWholeTypes = new HashSet<>();
|
||||
Set<String> excludedWholeTypes = new HashSet<>();
|
||||
SetMultimap<String, String> includedAttributes = HashMultimap.create();
|
||||
SetMultimap<String, String> excludedAttributes = HashMultimap.create();
|
||||
|
||||
inclusions.ifPresent(incl -> update(incl, includedWholeTypes, includedAttributes));
|
||||
exclusions.ifPresent(excl -> update(excl, excludedWholeTypes, excludedAttributes));
|
||||
includedWholeTypes.removeAll(excludedWholeTypes);
|
||||
includedAttributes.keySet().removeAll(includedWholeTypes);
|
||||
includedAttributes.keySet().removeAll(excludedWholeTypes);
|
||||
excludedAttributes.forEach(includedAttributes::remove);
|
||||
excludedAttributes.keySet().removeAll(excludedWholeTypes);
|
||||
|
||||
return new AnnotationAttributeMatcher(
|
||||
!inclusions.isPresent(),
|
||||
ImmutableSet.copyOf(inclusions.isPresent() ? includedWholeTypes : excludedWholeTypes),
|
||||
ImmutableSetMultimap.copyOf(includedAttributes),
|
||||
ImmutableSetMultimap.copyOf(excludedAttributes));
|
||||
}
|
||||
|
||||
private static void update(
|
||||
ImmutableList<String> enumeration,
|
||||
Set<String> wholeTypes,
|
||||
SetMultimap<String, String> attributeRestrictions) {
|
||||
for (String entry : enumeration) {
|
||||
int hash = entry.indexOf('#');
|
||||
if (hash < 0) {
|
||||
wholeTypes.add(entry);
|
||||
|
||||
} else {
|
||||
String annotationType = entry.substring(0, hash);
|
||||
String attribute = entry.substring(hash + 1);
|
||||
attributeRestrictions.put(annotationType, attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean matches(String annotationType, String attribute) {
|
||||
if (this.complement) {
|
||||
return !this.wholeTypes.contains(annotationType)
|
||||
&& !this.excludedAttributes.containsEntry(annotationType, attribute);
|
||||
}
|
||||
|
||||
return (this.wholeTypes.contains(annotationType)
|
||||
&& !this.excludedAttributes.containsEntry(annotationType, attribute))
|
||||
|| this.includedAttributes.containsEntry(annotationType, attribute);
|
||||
}
|
||||
}
|
||||
@@ -35,134 +35,132 @@ import java.util.function.ToLongFunction;
|
||||
import java.util.stream.Stream;
|
||||
import javax.annotation.CheckForNull;
|
||||
|
||||
// XXX: Add more documentation. Explain how this is useful in the face of refactoring to more specific types.
|
||||
// XXX: Add more documentation. Explain how this is useful in the face of refactoring to more
|
||||
// specific types.
|
||||
// XXX: Change this checker's name?
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "PrimitiveComparison",
|
||||
summary =
|
||||
"Ensure invocations of `Comparator#comparing{,Double,Int,Long}` match the return type"
|
||||
+ " of the provided function",
|
||||
linkType = LinkType.NONE,
|
||||
severity = SeverityLevel.WARNING,
|
||||
tags = StandardTags.LIKELY_ERROR,
|
||||
providesFix = ProvidesFix.REQUIRES_HUMAN_ATTENTION
|
||||
name = "PrimitiveComparison",
|
||||
summary =
|
||||
"Ensure invocations of `Comparator#comparing{,Double,Int,Long}` match the return type"
|
||||
+ " of the provided function",
|
||||
linkType = LinkType.NONE,
|
||||
severity = SeverityLevel.WARNING,
|
||||
tags = StandardTags.LIKELY_ERROR,
|
||||
providesFix = ProvidesFix.REQUIRES_HUMAN_ATTENTION
|
||||
)
|
||||
public class BoxingComparisonCheck extends BugChecker implements MethodInvocationTreeMatcher {
|
||||
private static final Matcher<ExpressionTree> STATIC_MATCH = getStaticTargetMatcher();
|
||||
private static final Matcher<ExpressionTree> INSTANCE_MATCH = getInstanceTargetMatcher();
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Matcher<ExpressionTree> STATIC_MATCH = getStaticTargetMatcher();
|
||||
private static final Matcher<ExpressionTree> INSTANCE_MATCH = getInstanceTargetMatcher();
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
boolean isStatic = STATIC_MATCH.matches(tree, state);
|
||||
if (!isStatic && !INSTANCE_MATCH.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
Type potentiallyBoxedType = getPotentiallyBoxedReturnType(tree.getArguments().get(0));
|
||||
if (potentiallyBoxedType == null) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
String actualMethodName = ASTHelpers.getSymbol(tree).getSimpleName().toString();
|
||||
String preferredMethodName = getPreferredMethod(state, potentiallyBoxedType, isStatic);
|
||||
if (actualMethodName.equals(preferredMethodName)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
Description.Builder description = buildDescription(tree);
|
||||
tryFix(description, tree, preferredMethodName);
|
||||
return description.build();
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
boolean isStatic = STATIC_MATCH.matches(tree, state);
|
||||
if (!isStatic && !INSTANCE_MATCH.matches(tree, state)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
private String getPreferredMethod(VisitorState state, Type cmpType, boolean isStatic) {
|
||||
Types types = state.getTypes();
|
||||
Symtab symtab = state.getSymtab();
|
||||
|
||||
if (types.isSubtype(cmpType, symtab.intType)) {
|
||||
return isStatic ? "comparingInt" : "thenComparingInt";
|
||||
}
|
||||
|
||||
if (types.isSubtype(cmpType, symtab.longType)) {
|
||||
return isStatic ? "comparingLong" : "thenComparingLong";
|
||||
}
|
||||
|
||||
if (types.isSubtype(cmpType, symtab.doubleType)) {
|
||||
return isStatic ? "comparingDouble" : "thenComparingDouble";
|
||||
}
|
||||
|
||||
return isStatic ? "comparing" : "thenComparing";
|
||||
Type potentiallyBoxedType = getPotentiallyBoxedReturnType(tree.getArguments().get(0));
|
||||
if (potentiallyBoxedType == null) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
@CheckForNull
|
||||
private Type getPotentiallyBoxedReturnType(ExpressionTree tree) {
|
||||
switch (tree.getKind()) {
|
||||
case LAMBDA_EXPRESSION:
|
||||
/* Return the lambda expression's actual return type. */
|
||||
return ASTHelpers.getType(((LambdaExpressionTree) tree).getBody());
|
||||
case MEMBER_REFERENCE:
|
||||
/* Return the method's declared return type. */
|
||||
// XXX: Very fragile. Do better.
|
||||
Type subType2 = ((JCMemberReference) tree).referentType;
|
||||
return ((MethodType) subType2).getReturnType();
|
||||
default:
|
||||
/* This appears to be a genuine `{,ToInt,ToLong,ToDouble}Function`. */
|
||||
return null;
|
||||
}
|
||||
String actualMethodName = ASTHelpers.getSymbol(tree).getSimpleName().toString();
|
||||
String preferredMethodName = getPreferredMethod(state, potentiallyBoxedType, isStatic);
|
||||
if (actualMethodName.equals(preferredMethodName)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
private void tryFix(
|
||||
Description.Builder description,
|
||||
MethodInvocationTree tree,
|
||||
String preferredMethodName) {
|
||||
ExpressionTree expr = tree.getMethodSelect();
|
||||
switch (expr.getKind()) {
|
||||
case IDENTIFIER:
|
||||
SuggestedFix.Builder fix = SuggestedFix.builder();
|
||||
fix.addStaticImport(
|
||||
java.util.Comparator.class.getName() + '.' + preferredMethodName);
|
||||
fix.replace(expr, preferredMethodName);
|
||||
description.addFix(fix.build());
|
||||
return;
|
||||
case MEMBER_SELECT:
|
||||
MemberSelectTree ms = (MemberSelectTree) tree.getMethodSelect();
|
||||
description.addFix(
|
||||
SuggestedFix.replace(ms, ms.getExpression() + "." + preferredMethodName));
|
||||
return;
|
||||
default:
|
||||
throw new VerifyException("Unexpected type of expression: " + expr.getKind());
|
||||
}
|
||||
Description.Builder description = buildDescription(tree);
|
||||
tryFix(description, tree, preferredMethodName);
|
||||
return description.build();
|
||||
}
|
||||
|
||||
private static String getPreferredMethod(VisitorState state, Type cmpType, boolean isStatic) {
|
||||
Types types = state.getTypes();
|
||||
Symtab symtab = state.getSymtab();
|
||||
|
||||
if (types.isSubtype(cmpType, symtab.intType)) {
|
||||
return isStatic ? "comparingInt" : "thenComparingInt";
|
||||
}
|
||||
|
||||
private static Matcher<ExpressionTree> getStaticTargetMatcher() {
|
||||
MethodClassMatcher clazz = staticMethod().onClass(Comparator.class.getName());
|
||||
|
||||
return anyMatch(
|
||||
unaryMethod(clazz, "comparingInt", ToIntFunction.class),
|
||||
unaryMethod(clazz, "comparingLong", ToLongFunction.class),
|
||||
unaryMethod(clazz, "comparingDouble", ToDoubleFunction.class),
|
||||
unaryMethod(clazz, "comparing", Function.class));
|
||||
if (types.isSubtype(cmpType, symtab.longType)) {
|
||||
return isStatic ? "comparingLong" : "thenComparingLong";
|
||||
}
|
||||
|
||||
private static Matcher<ExpressionTree> getInstanceTargetMatcher() {
|
||||
MethodClassMatcher instance = instanceMethod().onDescendantOf(Comparator.class.getName());
|
||||
|
||||
return anyMatch(
|
||||
unaryMethod(instance, "thenComparingInt", ToIntFunction.class),
|
||||
unaryMethod(instance, "thenComparingLong", ToLongFunction.class),
|
||||
unaryMethod(instance, "thenComparingDouble", ToDoubleFunction.class),
|
||||
unaryMethod(instance, "thenComparing", Function.class));
|
||||
if (types.isSubtype(cmpType, symtab.doubleType)) {
|
||||
return isStatic ? "comparingDouble" : "thenComparingDouble";
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
@SuppressWarnings("varargs")
|
||||
private static Matcher<ExpressionTree> anyMatch(Matcher<ExpressionTree>... matchers) {
|
||||
return (ExpressionTree t, VisitorState s) ->
|
||||
Stream.of(matchers).anyMatch(m -> m.matches(t, s));
|
||||
}
|
||||
return isStatic ? "comparing" : "thenComparing";
|
||||
}
|
||||
|
||||
private static Matcher<ExpressionTree> unaryMethod(
|
||||
MethodClassMatcher classMatcher, String name, Class<?> paramType) {
|
||||
return classMatcher.named(name).withParameters(paramType.getName());
|
||||
@CheckForNull
|
||||
private static Type getPotentiallyBoxedReturnType(ExpressionTree tree) {
|
||||
switch (tree.getKind()) {
|
||||
case LAMBDA_EXPRESSION:
|
||||
/* Return the lambda expression's actual return type. */
|
||||
return ASTHelpers.getType(((LambdaExpressionTree) tree).getBody());
|
||||
case MEMBER_REFERENCE:
|
||||
/* Return the method's declared return type. */
|
||||
// XXX: Very fragile. Do better.
|
||||
Type subType2 = ((JCMemberReference) tree).referentType;
|
||||
return ((MethodType) subType2).getReturnType();
|
||||
default:
|
||||
/* This appears to be a genuine `{,ToInt,ToLong,ToDouble}Function`. */
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void tryFix(
|
||||
Description.Builder description, MethodInvocationTree tree, String preferredMethodName) {
|
||||
ExpressionTree expr = tree.getMethodSelect();
|
||||
switch (expr.getKind()) {
|
||||
case IDENTIFIER:
|
||||
SuggestedFix.Builder fix = SuggestedFix.builder();
|
||||
fix.addStaticImport(java.util.Comparator.class.getName() + '.' + preferredMethodName);
|
||||
fix.replace(expr, preferredMethodName);
|
||||
description.addFix(fix.build());
|
||||
return;
|
||||
case MEMBER_SELECT:
|
||||
MemberSelectTree ms = (MemberSelectTree) tree.getMethodSelect();
|
||||
description.addFix(
|
||||
SuggestedFix.replace(ms, ms.getExpression() + "." + preferredMethodName));
|
||||
return;
|
||||
default:
|
||||
throw new VerifyException("Unexpected type of expression: " + expr.getKind());
|
||||
}
|
||||
}
|
||||
|
||||
private static Matcher<ExpressionTree> getStaticTargetMatcher() {
|
||||
MethodClassMatcher clazz = staticMethod().onClass(Comparator.class.getName());
|
||||
|
||||
return anyMatch(
|
||||
unaryMethod(clazz, "comparingInt", ToIntFunction.class),
|
||||
unaryMethod(clazz, "comparingLong", ToLongFunction.class),
|
||||
unaryMethod(clazz, "comparingDouble", ToDoubleFunction.class),
|
||||
unaryMethod(clazz, "comparing", Function.class));
|
||||
}
|
||||
|
||||
private static Matcher<ExpressionTree> getInstanceTargetMatcher() {
|
||||
MethodClassMatcher instance = instanceMethod().onDescendantOf(Comparator.class.getName());
|
||||
|
||||
return anyMatch(
|
||||
unaryMethod(instance, "thenComparingInt", ToIntFunction.class),
|
||||
unaryMethod(instance, "thenComparingLong", ToLongFunction.class),
|
||||
unaryMethod(instance, "thenComparingDouble", ToDoubleFunction.class),
|
||||
unaryMethod(instance, "thenComparing", Function.class));
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
@SuppressWarnings("varargs")
|
||||
private static Matcher<ExpressionTree> anyMatch(Matcher<ExpressionTree>... matchers) {
|
||||
return (ExpressionTree t, VisitorState s) -> Stream.of(matchers).anyMatch(m -> m.matches(t, s));
|
||||
}
|
||||
|
||||
private static Matcher<ExpressionTree> unaryMethod(
|
||||
MethodClassMatcher classMatcher, String name, Class<?> paramType) {
|
||||
return classMatcher.named(name).withParameters(paramType.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,117 +27,116 @@ import java.util.function.BiFunction;
|
||||
// XXX: Also flag/drop trailing commas?
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "CanonicalAnnotationSyntax",
|
||||
summary = "Omit redundant syntax from annotation declarations",
|
||||
linkType = LinkType.NONE,
|
||||
severity = SeverityLevel.SUGGESTION,
|
||||
tags = StandardTags.STYLE,
|
||||
providesFix = ProvidesFix.REQUIRES_HUMAN_ATTENTION
|
||||
name = "CanonicalAnnotationSyntax",
|
||||
summary = "Omit redundant syntax from annotation declarations",
|
||||
linkType = LinkType.NONE,
|
||||
severity = SeverityLevel.SUGGESTION,
|
||||
tags = StandardTags.STYLE,
|
||||
providesFix = ProvidesFix.REQUIRES_HUMAN_ATTENTION
|
||||
)
|
||||
public final class CanonicalAnnotationSyntaxCheck extends BugChecker
|
||||
implements AnnotationTreeMatcher {
|
||||
private static final ImmutableSet<BiFunction<AnnotationTree, VisitorState, Optional<Fix>>>
|
||||
FIX_FACTORIES =
|
||||
ImmutableSet.of(
|
||||
CanonicalAnnotationSyntaxCheck::dropRedundantParentheses,
|
||||
CanonicalAnnotationSyntaxCheck::dropRedundantValueAttribute,
|
||||
CanonicalAnnotationSyntaxCheck::dropRedundantCurlies);
|
||||
implements AnnotationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final ImmutableSet<BiFunction<AnnotationTree, VisitorState, Optional<Fix>>>
|
||||
FIX_FACTORIES =
|
||||
ImmutableSet.of(
|
||||
CanonicalAnnotationSyntaxCheck::dropRedundantParentheses,
|
||||
CanonicalAnnotationSyntaxCheck::dropRedundantValueAttribute,
|
||||
CanonicalAnnotationSyntaxCheck::dropRedundantCurlies);
|
||||
|
||||
@Override
|
||||
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
|
||||
return FIX_FACTORIES
|
||||
.stream()
|
||||
.map(op -> op.apply(tree, state))
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.findFirst()
|
||||
.map(fix -> buildDescription(tree).addFix(fix).build())
|
||||
.orElse(Description.NO_MATCH);
|
||||
@Override
|
||||
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
|
||||
return FIX_FACTORIES
|
||||
.stream()
|
||||
.map(op -> op.apply(tree, state))
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.findFirst()
|
||||
.map(fix -> buildDescription(tree).addFix(fix).build())
|
||||
.orElse(Description.NO_MATCH);
|
||||
}
|
||||
|
||||
private static Optional<Fix> dropRedundantParentheses(AnnotationTree tree, VisitorState state) {
|
||||
if (!tree.getArguments().isEmpty()) {
|
||||
/* Parentheses are necessary. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Optional<Fix> dropRedundantParentheses(AnnotationTree tree, VisitorState state) {
|
||||
if (!tree.getArguments().isEmpty()) {
|
||||
/* Parentheses are necessary. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
String src = state.getSourceForNode(tree);
|
||||
if (src == null) {
|
||||
/* Without the source code there's not much we can do. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
int parenIndex = src.indexOf('(');
|
||||
if (parenIndex < 0) {
|
||||
/* There are no redundant parentheses. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(SuggestedFix.replace(tree, src.substring(0, parenIndex)));
|
||||
String src = state.getSourceForNode(tree);
|
||||
if (src == null) {
|
||||
/* Without the source code there's not much we can do. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Optional<Fix> dropRedundantValueAttribute(
|
||||
AnnotationTree tree, VisitorState state) {
|
||||
List<? extends ExpressionTree> args = tree.getArguments();
|
||||
if (args.size() != 1) {
|
||||
/* The `value` attribute, if specified, cannot be dropped. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
ExpressionTree arg = args.get(0);
|
||||
if (arg.getKind() != Kind.ASSIGNMENT) {
|
||||
/* Evidently `value` isn't assigned to explicitly. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
AssignmentTree assignment = (AssignmentTree) arg;
|
||||
ExpressionTree variable = assignment.getVariable();
|
||||
if (variable.getKind() != Kind.IDENTIFIER
|
||||
|| !((IdentifierTree) variable).getName().contentEquals("value")
|
||||
|| state.getSourceForNode(variable) == null) {
|
||||
/* This is not an explicit assignment to the `value` attribute. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/* Replace the assignment with (the simplified representation of) just its value. */
|
||||
ExpressionTree expr = assignment.getExpression();
|
||||
return Optional.of(
|
||||
SuggestedFix.replace(arg, simplifyAttributeValue(expr).orElseGet(expr::toString)));
|
||||
int parenIndex = src.indexOf('(');
|
||||
if (parenIndex < 0) {
|
||||
/* There are no redundant parentheses. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Optional<Fix> dropRedundantCurlies(AnnotationTree tree, VisitorState state) {
|
||||
List<SuggestedFix.Builder> fixes = new ArrayList<>();
|
||||
for (ExpressionTree arg : tree.getArguments()) {
|
||||
/*
|
||||
* We'll try to simplify each assignment's RHS; for non-assignment we'll try to simplify
|
||||
* the expression as a whole.
|
||||
*/
|
||||
ExpressionTree value =
|
||||
(arg.getKind() == Kind.ASSIGNMENT)
|
||||
? ((AssignmentTree) arg).getExpression()
|
||||
: arg;
|
||||
return Optional.of(SuggestedFix.replace(tree, src.substring(0, parenIndex)));
|
||||
}
|
||||
|
||||
/* Store a fix for each expression that was successfully simplified. */
|
||||
simplifyAttributeValue(value)
|
||||
.ifPresent(expr -> fixes.add(SuggestedFix.builder().replace(value, expr)));
|
||||
}
|
||||
|
||||
return fixes.stream().reduce(SuggestedFix.Builder::merge).map(SuggestedFix.Builder::build);
|
||||
private static Optional<Fix> dropRedundantValueAttribute(
|
||||
AnnotationTree tree, VisitorState state) {
|
||||
List<? extends ExpressionTree> args = tree.getArguments();
|
||||
if (args.size() != 1) {
|
||||
/* The `value` attribute, if specified, cannot be dropped. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private static Optional<String> simplifyAttributeValue(ExpressionTree expr) {
|
||||
if (expr.getKind() != Kind.NEW_ARRAY) {
|
||||
/* There are no curly braces to be dropped here. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
NewArrayTree newArray = (NewArrayTree) expr;
|
||||
if (newArray.getInitializers().size() != 1) {
|
||||
/* Only singleton arrays can be simplified. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/* Return the expression describing the array's sole element. */
|
||||
return Optional.of(newArray.getInitializers().get(0).toString());
|
||||
ExpressionTree arg = args.get(0);
|
||||
if (arg.getKind() != Kind.ASSIGNMENT) {
|
||||
/* Evidently `value` isn't assigned to explicitly. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
AssignmentTree assignment = (AssignmentTree) arg;
|
||||
ExpressionTree variable = assignment.getVariable();
|
||||
if (variable.getKind() != Kind.IDENTIFIER
|
||||
|| !((IdentifierTree) variable).getName().contentEquals("value")
|
||||
|| state.getSourceForNode(variable) == null) {
|
||||
/* This is not an explicit assignment to the `value` attribute. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/* Replace the assignment with (the simplified representation of) just its value. */
|
||||
ExpressionTree expr = assignment.getExpression();
|
||||
return Optional.of(
|
||||
SuggestedFix.replace(arg, simplifyAttributeValue(expr).orElseGet(expr::toString)));
|
||||
}
|
||||
|
||||
private static Optional<Fix> dropRedundantCurlies(AnnotationTree tree, VisitorState state) {
|
||||
List<SuggestedFix.Builder> fixes = new ArrayList<>();
|
||||
for (ExpressionTree arg : tree.getArguments()) {
|
||||
/*
|
||||
* We'll try to simplify each assignment's RHS; for non-assignment we'll try to simplify
|
||||
* the expression as a whole.
|
||||
*/
|
||||
ExpressionTree value =
|
||||
(arg.getKind() == Kind.ASSIGNMENT) ? ((AssignmentTree) arg).getExpression() : arg;
|
||||
|
||||
/* Store a fix for each expression that was successfully simplified. */
|
||||
simplifyAttributeValue(value)
|
||||
.ifPresent(expr -> fixes.add(SuggestedFix.builder().replace(value, expr)));
|
||||
}
|
||||
|
||||
return fixes.stream().reduce(SuggestedFix.Builder::merge).map(SuggestedFix.Builder::build);
|
||||
}
|
||||
|
||||
private static Optional<String> simplifyAttributeValue(ExpressionTree expr) {
|
||||
if (expr.getKind() != Kind.NEW_ARRAY) {
|
||||
/* There are no curly braces to be dropped here. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
NewArrayTree newArray = (NewArrayTree) expr;
|
||||
if (newArray.getInitializers().size() != 1) {
|
||||
/* Only singleton arrays can be simplified. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/* Return the expression describing the array's sole element. */
|
||||
return Optional.of(newArray.getInitializers().get(0).toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,25 +11,27 @@ import com.google.errorprone.matchers.Description;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
|
||||
// XXX: Disable until fixed.
|
||||
//@AutoService(BugChecker.class)
|
||||
// @AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "EmptyMethod",
|
||||
summary = "Empty method can likely be deleted",
|
||||
linkType = LinkType.NONE,
|
||||
severity = SeverityLevel.WARNING,
|
||||
tags = StandardTags.LIKELY_ERROR
|
||||
name = "EmptyMethod",
|
||||
summary = "Empty method can likely be deleted",
|
||||
linkType = LinkType.NONE,
|
||||
severity = SeverityLevel.WARNING,
|
||||
tags = StandardTags.LIKELY_ERROR
|
||||
)
|
||||
public final class EmptyMethodCheck extends BugChecker implements MethodTreeMatcher {
|
||||
// XXX: Restrict: only warn about:
|
||||
// - static methods
|
||||
// - methods that are provably not in an inheritance hierarchy.
|
||||
// - public methods in a Mockito test class.
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
if (tree.getBody() == null || !tree.getBody().getStatements().isEmpty()) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
return buildDescription(tree).build();
|
||||
// XXX: Restrict: only warn about:
|
||||
// - static methods
|
||||
// - methods that are provably not in an inheritance hierarchy.
|
||||
// - public methods in a Mockito test class.
|
||||
@Override
|
||||
public Description matchMethod(MethodTree tree, VisitorState state) {
|
||||
if (tree.getBody() == null || !tree.getBody().getStatements().isEmpty()) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
return buildDescription(tree).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.BugPattern.LinkType;
|
||||
import com.google.errorprone.BugPattern.SeverityLevel;
|
||||
import com.google.errorprone.BugPattern.StandardTags;
|
||||
import com.google.errorprone.ErrorProneFlags;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher;
|
||||
@@ -28,88 +29,114 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
// XXX: Add documentation. Explain that sorting reduces chances of conflicts and simplifies their resolution when they do happen.
|
||||
// XXX: Add support for inclusions and exclusions.
|
||||
// XXX: Add documentation. Explain that sorting reduces chances of conflicts and simplifies their
|
||||
// resolution when they do happen.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "LexicographicalAnnotationAttributeListing",
|
||||
summary = "Where possible, sort annotation array attributes lexicographically",
|
||||
linkType = LinkType.NONE,
|
||||
severity = SeverityLevel.SUGGESTION,
|
||||
tags = StandardTags.STYLE
|
||||
name = "LexicographicalAnnotationAttributeListing",
|
||||
summary = "Where possible, sort annotation array attributes lexicographically",
|
||||
linkType = LinkType.NONE,
|
||||
severity = SeverityLevel.SUGGESTION,
|
||||
tags = StandardTags.STYLE
|
||||
)
|
||||
public final class LexicographicalAnnotationAttributeListingCheck extends BugChecker
|
||||
implements AnnotationTreeMatcher {
|
||||
@Override
|
||||
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
|
||||
return sortArrayElements(tree, state)
|
||||
.map(fix -> buildDescription(tree).addFix(fix).build())
|
||||
.orElse(Description.NO_MATCH);
|
||||
implements AnnotationTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String FLAG_PREFIX = "LexicographicalAnnotationAttributeListingCheck:";
|
||||
private static final String INCLUDED_ANNOTATIONS_FLAG = FLAG_PREFIX + "Includes";
|
||||
private static final String EXCLUDED_ANNOTATIONS_FLAG = FLAG_PREFIX + "Excludes";
|
||||
|
||||
private final AnnotationAttributeMatcher matcher;
|
||||
|
||||
public LexicographicalAnnotationAttributeListingCheck() {
|
||||
this(ErrorProneFlags.empty());
|
||||
}
|
||||
|
||||
public LexicographicalAnnotationAttributeListingCheck(ErrorProneFlags flags) {
|
||||
this.matcher = getAnnotationAttributeMatcher(flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Description matchAnnotation(AnnotationTree tree, VisitorState state) {
|
||||
return sortArrayElements(tree, state)
|
||||
.map(fix -> buildDescription(tree).addFix(fix).build())
|
||||
.orElse(Description.NO_MATCH);
|
||||
}
|
||||
|
||||
private Optional<Fix> sortArrayElements(AnnotationTree tree, VisitorState state) {
|
||||
/**
|
||||
* We loop over the array's attributes, trying to sort each array associated with a
|
||||
* non-blacklisted attribute. A single compound fix, if any, is returned.
|
||||
*/
|
||||
String annotationType = ASTHelpers.getType(tree.getAnnotationType()).toString();
|
||||
return tree.getArguments()
|
||||
.stream()
|
||||
.filter(a -> this.matcher.matches(annotationType, extractAttributeName(a)))
|
||||
.map(expr -> extractArray(expr).flatMap(arr -> suggestSorting(arr, state)))
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.reduce(SuggestedFix.Builder::merge)
|
||||
.map(SuggestedFix.Builder::build);
|
||||
}
|
||||
|
||||
private static String extractAttributeName(ExpressionTree expr) {
|
||||
return (expr.getKind() == Kind.ASSIGNMENT)
|
||||
? ASTHelpers.getSymbol(((AssignmentTree) expr).getVariable()).getSimpleName().toString()
|
||||
: "value";
|
||||
}
|
||||
|
||||
private static Optional<NewArrayTree> extractArray(ExpressionTree expr) {
|
||||
if (expr.getKind() == Kind.ASSIGNMENT) {
|
||||
return extractArray(((AssignmentTree) expr).getExpression());
|
||||
}
|
||||
|
||||
private Optional<Fix> sortArrayElements(AnnotationTree tree, VisitorState state) {
|
||||
List<SuggestedFix.Builder> fixes = new ArrayList<>();
|
||||
return Optional.of(expr)
|
||||
.filter(e -> e.getKind() == Kind.NEW_ARRAY)
|
||||
.map(NewArrayTree.class::cast);
|
||||
}
|
||||
|
||||
/*
|
||||
* For each argument, see whether it contains an unsorted array and emit a suggested fix if
|
||||
* so.
|
||||
*/
|
||||
for (ExpressionTree arg : tree.getArguments()) {
|
||||
extractArray(arg).flatMap(arr -> suggestSorting(arr, state)).ifPresent(fixes::add);
|
||||
}
|
||||
|
||||
/* Return a single compound fix, if any. */
|
||||
return fixes.stream().reduce(SuggestedFix.Builder::merge).map(SuggestedFix.Builder::build);
|
||||
private static Optional<SuggestedFix.Builder> suggestSorting(
|
||||
NewArrayTree array, VisitorState state) {
|
||||
if (array.getInitializers().size() < 2 || !canSort(array, state)) {
|
||||
/* There's nothing to sort, or we don't want to sort. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Optional<NewArrayTree> extractArray(ExpressionTree expr) {
|
||||
if (expr.getKind() == Kind.ASSIGNMENT) {
|
||||
return extractArray(((AssignmentTree) expr).getExpression());
|
||||
}
|
||||
|
||||
return Optional.of(expr)
|
||||
.filter(e -> e.getKind() == Kind.NEW_ARRAY)
|
||||
.map(NewArrayTree.class::cast);
|
||||
/* We're sorting based on each expression's string representation. */
|
||||
List<String> expressions = new ArrayList<>();
|
||||
for (ExpressionTree expr : array.getInitializers()) {
|
||||
expressions.add(expr.toString());
|
||||
}
|
||||
|
||||
private Optional<SuggestedFix.Builder> suggestSorting(NewArrayTree array, VisitorState state) {
|
||||
if (array.getInitializers().size() < 2 || !canSort(array, state)) {
|
||||
/* There's nothing to sort, or we don't want to sort. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/* We're sorting based on each expression's string representation. */
|
||||
List<String> expressions = new ArrayList<>();
|
||||
for (ExpressionTree expr : array.getInitializers()) {
|
||||
expressions.add(expr.toString());
|
||||
}
|
||||
|
||||
if (Comparators.isInOrder(expressions, naturalOrder())) {
|
||||
/* In the (presumably) common case the elements are already sorted. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/* The elements aren't sorted yet. Do so now. */
|
||||
expressions.sort(naturalOrder());
|
||||
|
||||
/* Re-assemble the expressions into an array. */
|
||||
StringBuilder sb = new StringBuilder("{");
|
||||
Joiner.on(", ").appendTo(sb, expressions);
|
||||
sb.append("}");
|
||||
|
||||
return Optional.of(SuggestedFix.builder().replace(array, sb.toString()));
|
||||
if (Comparators.isInOrder(expressions, naturalOrder())) {
|
||||
/* In the (presumably) common case the elements are already sorted. */
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private boolean canSort(NewArrayTree array, VisitorState state) {
|
||||
Type elemType = state.getTypes().elemtype(ASTHelpers.getType(array));
|
||||
Symtab symtab = state.getSymtab();
|
||||
/* The elements aren't sorted yet. Do so now. */
|
||||
expressions.sort(naturalOrder());
|
||||
|
||||
/* For now we don't force sorting on numeric types. */
|
||||
return Stream.of(
|
||||
symtab.annotationType,
|
||||
symtab.classType,
|
||||
symtab.enumSym.type,
|
||||
symtab.stringType)
|
||||
.anyMatch(t -> ASTHelpers.isSubtype(elemType, t, state));
|
||||
}
|
||||
/* Re-assemble the expressions into an array. */
|
||||
StringBuilder sb = new StringBuilder("{");
|
||||
Joiner.on(", ").appendTo(sb, expressions);
|
||||
sb.append("}");
|
||||
|
||||
return Optional.of(SuggestedFix.builder().replace(array, sb.toString()));
|
||||
}
|
||||
|
||||
private static boolean canSort(NewArrayTree array, VisitorState state) {
|
||||
Type elemType = state.getTypes().elemtype(ASTHelpers.getType(array));
|
||||
Symtab symtab = state.getSymtab();
|
||||
|
||||
/* For now we don't force sorting on numeric types. */
|
||||
return Stream.of(
|
||||
symtab.annotationType, symtab.classType, symtab.enumSym.type, symtab.stringType)
|
||||
.anyMatch(t -> ASTHelpers.isSubtype(elemType, t, state));
|
||||
}
|
||||
|
||||
private static AnnotationAttributeMatcher getAnnotationAttributeMatcher(ErrorProneFlags flags) {
|
||||
return AnnotationAttributeMatcher.create(
|
||||
flags.getList(INCLUDED_ANNOTATIONS_FLAG), flags.getList(EXCLUDED_ANNOTATIONS_FLAG));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.picnicinternational.errorprone.bugpatterns;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.Optional;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public final class AnnotationAttributeMatcherTest {
|
||||
@Test
|
||||
public void testWithoutListings() {
|
||||
AnnotationAttributeMatcher matcher =
|
||||
AnnotationAttributeMatcher.create(Optional.empty(), Optional.empty());
|
||||
assertTrue(matcher.matches("foo", "bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithSingleFullAnnotationWhitelist() {
|
||||
AnnotationAttributeMatcher matcher =
|
||||
AnnotationAttributeMatcher.create(Optional.of(ImmutableList.of("foo")), Optional.empty());
|
||||
assertTrue(matcher.matches("foo", "bar"));
|
||||
assertTrue(matcher.matches("foo", "baz"));
|
||||
assertFalse(matcher.matches("quux", "bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithSingleAnnotationAttributeWhitelist() {
|
||||
AnnotationAttributeMatcher matcher =
|
||||
AnnotationAttributeMatcher.create(
|
||||
Optional.of(ImmutableList.of("foo#bar")), Optional.empty());
|
||||
assertTrue(matcher.matches("foo", "bar"));
|
||||
assertFalse(matcher.matches("foo", "baz"));
|
||||
assertFalse(matcher.matches("quux", "bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithSingleFullAnnotationBlacklist() {
|
||||
AnnotationAttributeMatcher matcher =
|
||||
AnnotationAttributeMatcher.create(Optional.empty(), Optional.of(ImmutableList.of("foo")));
|
||||
assertFalse(matcher.matches("foo", "bar"));
|
||||
assertFalse(matcher.matches("foo", "baz"));
|
||||
assertTrue(matcher.matches("quux", "bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithSingleAnnotationAttributeBlacklist() {
|
||||
AnnotationAttributeMatcher matcher =
|
||||
AnnotationAttributeMatcher.create(
|
||||
Optional.empty(), Optional.of(ImmutableList.of("foo#bar")));
|
||||
assertFalse(matcher.matches("foo", "bar"));
|
||||
assertTrue(matcher.matches("foo", "baz"));
|
||||
assertTrue(matcher.matches("quux", "bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithComplicatedConfiguration() {
|
||||
AnnotationAttributeMatcher matcher =
|
||||
AnnotationAttributeMatcher.create(
|
||||
Optional.of(ImmutableList.of("foo", "bar", "baz", "baz#1", "baz#2", "quux#1")),
|
||||
Optional.of(ImmutableList.of("foo", "baz#2")));
|
||||
assertFalse(matcher.matches("foo", "1"));
|
||||
assertFalse(matcher.matches("foo", "2"));
|
||||
assertTrue(matcher.matches("bar", "1"));
|
||||
assertTrue(matcher.matches("bar", "2"));
|
||||
assertTrue(matcher.matches("baz", "1"));
|
||||
assertFalse(matcher.matches("baz", "2"));
|
||||
assertTrue(matcher.matches("quux", "1"));
|
||||
assertFalse(matcher.matches("quux", "2"));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,137 +9,136 @@ import org.junit.runners.JUnit4;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public final class CanonicalAnnotationSyntaxCheckTest {
|
||||
private final CompilationTestHelper compilationTestHelper =
|
||||
CompilationTestHelper.newInstance(CanonicalAnnotationSyntaxCheck.class, getClass());
|
||||
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
|
||||
BugCheckerRefactoringTestHelper.newInstance(
|
||||
new CanonicalAnnotationSyntaxCheck(), getClass());
|
||||
private final CompilationTestHelper compilationTestHelper =
|
||||
CompilationTestHelper.newInstance(CanonicalAnnotationSyntaxCheck.class, getClass());
|
||||
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
|
||||
BugCheckerRefactoringTestHelper.newInstance(new CanonicalAnnotationSyntaxCheck(), getClass());
|
||||
|
||||
@Test
|
||||
public void testIdentification() {
|
||||
compilationTestHelper
|
||||
.addSourceLines(
|
||||
"pkg/A.java",
|
||||
"package pkg;",
|
||||
"",
|
||||
"import pkg.A.Foo;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @interface Foo {",
|
||||
" int[] value() default {};",
|
||||
" int[] value2() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @pkg.A.Foo A minimal1();",
|
||||
" @A.Foo A minimal2();",
|
||||
" @Foo A minimal3();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @pkg.A.Foo() A functional1();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @A.Foo() A functional2();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo() A functional3();",
|
||||
"",
|
||||
" @pkg.A.Foo(1) A simple1();",
|
||||
" @A.Foo(1) A simple2();",
|
||||
" @Foo(1) A simple3();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @pkg.A.Foo({1}) A singleton1();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @A.Foo({1}) A singleton2();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo({1}) A singleton3();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @pkg.A.Foo(value = 1) A verbose1();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @A.Foo(value = 1) A verbose2();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo(value = 1) A verbose3();",
|
||||
"",
|
||||
" @pkg.A.Foo(value2 = 2) A custom1();",
|
||||
" @A.Foo(value2 = 2) A custom2();",
|
||||
" @Foo(value2 = 2) A custom3();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @pkg.A.Foo(value2 = {2}) A customSingleton1();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @A.Foo(value2 = {2}) A customSingleton2();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo(value2 = {2}) A customSingleton3();",
|
||||
"",
|
||||
" @pkg.A.Foo(value2 = {2, 2}) A customPair1();",
|
||||
" @A.Foo(value2 = {2, 2}) A customPair2();",
|
||||
" @Foo(value2 = {2, 2}) A customPair3();",
|
||||
"",
|
||||
" @pkg.A.Foo(value = 1, value2 = 2) A extended1();",
|
||||
" @A.Foo(value = 1, value2 = 2) A extended2();",
|
||||
" @Foo(value = 1, value2 = 2) A extended3();",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
@Test
|
||||
public void testIdentification() {
|
||||
compilationTestHelper
|
||||
.addSourceLines(
|
||||
"pkg/A.java",
|
||||
"package pkg;",
|
||||
"",
|
||||
"import pkg.A.Foo;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @interface Foo {",
|
||||
" int[] value() default {};",
|
||||
" int[] value2() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @pkg.A.Foo A minimal1();",
|
||||
" @A.Foo A minimal2();",
|
||||
" @Foo A minimal3();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @pkg.A.Foo() A functional1();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @A.Foo() A functional2();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo() A functional3();",
|
||||
"",
|
||||
" @pkg.A.Foo(1) A simple1();",
|
||||
" @A.Foo(1) A simple2();",
|
||||
" @Foo(1) A simple3();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @pkg.A.Foo({1}) A singleton1();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @A.Foo({1}) A singleton2();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo({1}) A singleton3();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @pkg.A.Foo(value = 1) A verbose1();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @A.Foo(value = 1) A verbose2();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo(value = 1) A verbose3();",
|
||||
"",
|
||||
" @pkg.A.Foo(value2 = 2) A custom1();",
|
||||
" @A.Foo(value2 = 2) A custom2();",
|
||||
" @Foo(value2 = 2) A custom3();",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @pkg.A.Foo(value2 = {2}) A customSingleton1();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @A.Foo(value2 = {2}) A customSingleton2();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo(value2 = {2}) A customSingleton3();",
|
||||
"",
|
||||
" @pkg.A.Foo(value2 = {2, 2}) A customPair1();",
|
||||
" @A.Foo(value2 = {2, 2}) A customPair2();",
|
||||
" @Foo(value2 = {2, 2}) A customPair3();",
|
||||
"",
|
||||
" @pkg.A.Foo(value = 1, value2 = 2) A extended1();",
|
||||
" @A.Foo(value = 1, value2 = 2) A extended2();",
|
||||
" @Foo(value = 1, value2 = 2) A extended3();",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplacement() throws IOException {
|
||||
refactoringTestHelper
|
||||
.addInputLines(
|
||||
"in/pkg/A.java",
|
||||
"package pkg;",
|
||||
"",
|
||||
"import pkg.A.Foo;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @interface Foo {",
|
||||
" int[] value() default {};",
|
||||
" int[] value2() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @pkg.A.Foo() A functional1();",
|
||||
" @A.Foo() A functional2();",
|
||||
" @Foo() A functional3();",
|
||||
"",
|
||||
" @pkg.A.Foo(value = 1) A verbose1();",
|
||||
" @A.Foo(value = 1) A verbose2();",
|
||||
" @Foo(value = 1) A verbose3();",
|
||||
"",
|
||||
" @pkg.A.Foo(value = {1}) A moreVerbose1();",
|
||||
" @A.Foo(value = {1}) A moreVerbose2();",
|
||||
" @Foo(value = {1}) A moreVerbose3();",
|
||||
"",
|
||||
" @pkg.A.Foo(value = {1, 1}, value2 = {2}) A extended1();",
|
||||
" @A.Foo(value = {1, 1}, value2 = {2}) A extended2();",
|
||||
" @Foo(value = {1, 1}, value2 = {2}) A extended3();",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"out/pkg/A.java",
|
||||
"package pkg;",
|
||||
"",
|
||||
"import pkg.A.Foo;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @interface Foo {",
|
||||
" int[] value() default {};",
|
||||
" int[] value2() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @pkg.A.Foo A functional1();",
|
||||
" @A.Foo A functional2();",
|
||||
" @Foo A functional3();",
|
||||
"",
|
||||
" @pkg.A.Foo(1) A verbose1();",
|
||||
" @A.Foo(1) A verbose2();",
|
||||
" @Foo(1) A verbose3();",
|
||||
"",
|
||||
" @pkg.A.Foo(1) A moreVerbose1();",
|
||||
" @A.Foo(1) A moreVerbose2();",
|
||||
" @Foo(1) A moreVerbose3();",
|
||||
"",
|
||||
" @pkg.A.Foo(value = {1, 1}, value2 = 2) A extended1();",
|
||||
" @A.Foo(value = {1, 1}, value2 = 2) A extended2();",
|
||||
" @Foo(value = {1, 1}, value2 = 2) A extended3();",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
@Test
|
||||
public void testReplacement() throws IOException {
|
||||
refactoringTestHelper
|
||||
.addInputLines(
|
||||
"in/pkg/A.java",
|
||||
"package pkg;",
|
||||
"",
|
||||
"import pkg.A.Foo;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @interface Foo {",
|
||||
" int[] value() default {};",
|
||||
" int[] value2() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @pkg.A.Foo() A functional1();",
|
||||
" @A.Foo() A functional2();",
|
||||
" @Foo() A functional3();",
|
||||
"",
|
||||
" @pkg.A.Foo(value = 1) A verbose1();",
|
||||
" @A.Foo(value = 1) A verbose2();",
|
||||
" @Foo(value = 1) A verbose3();",
|
||||
"",
|
||||
" @pkg.A.Foo(value = {1}) A moreVerbose1();",
|
||||
" @A.Foo(value = {1}) A moreVerbose2();",
|
||||
" @Foo(value = {1}) A moreVerbose3();",
|
||||
"",
|
||||
" @pkg.A.Foo(value = {1, 1}, value2 = {2}) A extended1();",
|
||||
" @A.Foo(value = {1, 1}, value2 = {2}) A extended2();",
|
||||
" @Foo(value = {1, 1}, value2 = {2}) A extended3();",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"out/pkg/A.java",
|
||||
"package pkg;",
|
||||
"",
|
||||
"import pkg.A.Foo;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @interface Foo {",
|
||||
" int[] value() default {};",
|
||||
" int[] value2() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @pkg.A.Foo A functional1();",
|
||||
" @A.Foo A functional2();",
|
||||
" @Foo A functional3();",
|
||||
"",
|
||||
" @pkg.A.Foo(1) A verbose1();",
|
||||
" @A.Foo(1) A verbose2();",
|
||||
" @Foo(1) A verbose3();",
|
||||
"",
|
||||
" @pkg.A.Foo(1) A moreVerbose1();",
|
||||
" @A.Foo(1) A moreVerbose2();",
|
||||
" @Foo(1) A moreVerbose3();",
|
||||
"",
|
||||
" @pkg.A.Foo(value = {1, 1}, value2 = 2) A extended1();",
|
||||
" @A.Foo(value = {1, 1}, value2 = 2) A extended2();",
|
||||
" @Foo(value = {1, 1}, value2 = 2) A extended3();",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,42 +7,42 @@ import org.junit.runners.JUnit4;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public final class EmptyMethodCheckTest {
|
||||
private final CompilationTestHelper compilationTestHelper =
|
||||
CompilationTestHelper.newInstance(EmptyMethodCheck.class, getClass());
|
||||
private final CompilationTestHelper compilationTestHelper =
|
||||
CompilationTestHelper.newInstance(EmptyMethodCheck.class, getClass());
|
||||
|
||||
@Test
|
||||
public void testNegative() {
|
||||
compilationTestHelper
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"class A {",
|
||||
" Object m() {",
|
||||
" return null;",
|
||||
" }",
|
||||
"",
|
||||
" void m2() {",
|
||||
" System.out.println(42);",
|
||||
" }",
|
||||
"",
|
||||
" interface F {",
|
||||
" void fun();",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
@Test
|
||||
public void testNegative() {
|
||||
compilationTestHelper
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"class A {",
|
||||
" Object m() {",
|
||||
" return null;",
|
||||
" }",
|
||||
"",
|
||||
" void m2() {",
|
||||
" System.out.println(42);",
|
||||
" }",
|
||||
"",
|
||||
" interface F {",
|
||||
" void fun();",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPositive() {
|
||||
compilationTestHelper
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"class A {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" void m() {}",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" static void m2() {}",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
@Test
|
||||
public void testPositive() {
|
||||
compilationTestHelper
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"class A {",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" void m() {}",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" static void m2() {}",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.picnicinternational.errorprone.bugpatterns;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import java.io.IOException;
|
||||
@@ -9,120 +10,166 @@ import org.junit.runners.JUnit4;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public final class LexicographicalAnnotationAttributeListingCheckTest {
|
||||
private final CompilationTestHelper compilationTestHelper =
|
||||
CompilationTestHelper.newInstance(
|
||||
LexicographicalAnnotationAttributeListingCheck.class, getClass());
|
||||
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
|
||||
BugCheckerRefactoringTestHelper.newInstance(
|
||||
new LexicographicalAnnotationAttributeListingCheck(), getClass());
|
||||
private final CompilationTestHelper compilationTestHelper =
|
||||
CompilationTestHelper.newInstance(
|
||||
LexicographicalAnnotationAttributeListingCheck.class, getClass());
|
||||
private final CompilationTestHelper restrictedCompilationTestHelper =
|
||||
CompilationTestHelper.newInstance(
|
||||
LexicographicalAnnotationAttributeListingCheck.class, getClass())
|
||||
.setArgs(
|
||||
ImmutableList.of(
|
||||
"-XepOpt:LexicographicalAnnotationAttributeListingCheck:Includes=pkg.A.Foo,pkg.A.Bar",
|
||||
"-XepOpt:LexicographicalAnnotationAttributeListingCheck:Excludes=pkg.A.Bar#value"));
|
||||
private final BugCheckerRefactoringTestHelper refactoringTestHelper =
|
||||
BugCheckerRefactoringTestHelper.newInstance(
|
||||
new LexicographicalAnnotationAttributeListingCheck(), getClass());
|
||||
|
||||
@Test
|
||||
public void testIdentification() {
|
||||
compilationTestHelper
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import static java.math.RoundingMode.UP;",
|
||||
"import static java.math.RoundingMode.DOWN;",
|
||||
"",
|
||||
"import java.math.RoundingMode;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @interface Foo {",
|
||||
" String[] value() default {};",
|
||||
" Class<?>[] cls() default {};",
|
||||
" RoundingMode[] enums() default {};",
|
||||
" Bar[] anns() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @interface Bar {",
|
||||
" String[] value() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @Foo({}) A noString();",
|
||||
" @Foo({\"a\"}) A oneString();",
|
||||
" @Foo({\"a\", \"b\"}) A sortedStrings();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo({\"b\", \"a\"}) A unsortedString();",
|
||||
"",
|
||||
" @Foo(cls = {}) A noClasses();",
|
||||
" @Foo(cls = {int.class}) A oneClass();",
|
||||
" @Foo(cls = {int.class, long.class}) A sortedClasses();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo(cls = {long.class, int.class}) A unsortedClasses();",
|
||||
"",
|
||||
" @Foo(enums = {}) A noEnums();",
|
||||
" @Foo(enums = {DOWN}) A oneEnum();",
|
||||
" @Foo(enums = {DOWN, UP}) A sortedEnums();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo(enums = {UP, DOWN}) A unsortedEnums();",
|
||||
"",
|
||||
" @Foo(anns = {}) A noAnns();",
|
||||
" @Foo(anns = {@Bar(\"a\")}) A oneAnnd();",
|
||||
" @Foo(anns = {@Bar(\"a\"), @Bar(\"b\")}) A sortedAnns();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")}) A unsortedAnns();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo(anns = {@Bar(\"a\"), @Bar({\"b\", \"a\"})}) A unsortedInnderAnns();",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
@Test
|
||||
public void testIdentification() {
|
||||
compilationTestHelper
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import static java.math.RoundingMode.UP;",
|
||||
"import static java.math.RoundingMode.DOWN;",
|
||||
"",
|
||||
"import java.math.RoundingMode;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @interface Foo {",
|
||||
" String[] value() default {};",
|
||||
" Class<?>[] cls() default {};",
|
||||
" RoundingMode[] enums() default {};",
|
||||
" Bar[] anns() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @interface Bar {",
|
||||
" String[] value() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @Foo({}) A noString();",
|
||||
" @Foo({\"a\"}) A oneString();",
|
||||
" @Foo({\"a\", \"b\"}) A sortedStrings();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo({\"b\", \"a\"}) A unsortedString();",
|
||||
"",
|
||||
" @Foo(cls = {}) A noClasses();",
|
||||
" @Foo(cls = {int.class}) A oneClass();",
|
||||
" @Foo(cls = {int.class, long.class}) A sortedClasses();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo(cls = {long.class, int.class}) A unsortedClasses();",
|
||||
"",
|
||||
" @Foo(enums = {}) A noEnums();",
|
||||
" @Foo(enums = {DOWN}) A oneEnum();",
|
||||
" @Foo(enums = {DOWN, UP}) A sortedEnums();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo(enums = {UP, DOWN}) A unsortedEnums();",
|
||||
"",
|
||||
" @Foo(anns = {}) A noAnns();",
|
||||
" @Foo(anns = {@Bar(\"a\")}) A oneAnnd();",
|
||||
" @Foo(anns = {@Bar(\"a\"), @Bar(\"b\")}) A sortedAnns();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")}) A unsortedAnns();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo(anns = {@Bar(\"a\"), @Bar({\"b\", \"a\"})}) A unsortedInnderAnns();",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
// XXX: Note that in the output below in one instance redundant `value = ` assignments are introduced.
|
||||
// Avoiding that might make the code too complex. Instead, users can have the `CanonicalAnnotationSyntaxCheck`
|
||||
// correct the situation in a subsequent run.
|
||||
@Test
|
||||
public void testReplacement() throws IOException {
|
||||
refactoringTestHelper
|
||||
.addInputLines(
|
||||
"in/A.java",
|
||||
"import static java.math.RoundingMode.UP;",
|
||||
"import static java.math.RoundingMode.DOWN;",
|
||||
"",
|
||||
"import java.math.RoundingMode;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @interface Foo {",
|
||||
" String[] value() default {};",
|
||||
" Class<?>[] cls() default {};",
|
||||
" RoundingMode[] enums() default {};",
|
||||
" Bar[] anns() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @interface Bar {",
|
||||
" String[] value() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @Foo({\"b\", \"a\"}) A unsortedString();",
|
||||
" @Foo(cls = {long.class, int.class}) A unsortedClasses();",
|
||||
" @Foo(enums = {UP, DOWN}) A unsortedEnums();",
|
||||
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")}) A unsortedAnns();",
|
||||
" @Foo(anns = {@Bar(\"a\"), @Bar({\"b\", \"a\"})}) A unsortedInnderAnns();",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"out/A.java",
|
||||
"import static java.math.RoundingMode.UP;",
|
||||
"import static java.math.RoundingMode.DOWN;",
|
||||
"",
|
||||
"import java.math.RoundingMode;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @interface Foo {",
|
||||
" String[] value() default {};",
|
||||
" Class<?>[] cls() default {};",
|
||||
" RoundingMode[] enums() default {};",
|
||||
" Bar[] anns() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @interface Bar {",
|
||||
" String[] value() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @Foo({\"a\", \"b\"}) A unsortedString();",
|
||||
" @Foo(cls = {int.class, long.class}) A unsortedClasses();",
|
||||
" @Foo(enums = {DOWN, UP}) A unsortedEnums();",
|
||||
" @Foo(anns = {@Bar(value = \"a\"), @Bar(value = \"b\")}) A unsortedAnns();",
|
||||
" @Foo(anns = {@Bar(\"a\"), @Bar({\"a\", \"b\"})}) A unsortedInnderAnns();",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
// XXX: Note that in the output below in one instance redundant `value = ` assignments are
|
||||
// introduced.
|
||||
// Avoiding that might make the code too complex. Instead, users can have the
|
||||
// `CanonicalAnnotationSyntaxCheck`
|
||||
// correct the situation in a subsequent run.
|
||||
@Test
|
||||
public void testReplacement() throws IOException {
|
||||
refactoringTestHelper
|
||||
.addInputLines(
|
||||
"in/A.java",
|
||||
"import static java.math.RoundingMode.UP;",
|
||||
"import static java.math.RoundingMode.DOWN;",
|
||||
"",
|
||||
"import java.math.RoundingMode;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @interface Foo {",
|
||||
" String[] value() default {};",
|
||||
" Class<?>[] cls() default {};",
|
||||
" RoundingMode[] enums() default {};",
|
||||
" Bar[] anns() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @interface Bar {",
|
||||
" String[] value() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @Foo({\"b\", \"a\"}) A unsortedString();",
|
||||
" @Foo(cls = {long.class, int.class}) A unsortedClasses();",
|
||||
" @Foo(enums = {UP, DOWN}) A unsortedEnums();",
|
||||
" @Foo(anns = {@Bar(\"b\"), @Bar(\"a\")}) A unsortedAnns();",
|
||||
" @Foo(anns = {@Bar(\"a\"), @Bar({\"b\", \"a\"})}) A unsortedInnderAnns();",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"out/A.java",
|
||||
"import static java.math.RoundingMode.UP;",
|
||||
"import static java.math.RoundingMode.DOWN;",
|
||||
"",
|
||||
"import java.math.RoundingMode;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @interface Foo {",
|
||||
" String[] value() default {};",
|
||||
" Class<?>[] cls() default {};",
|
||||
" RoundingMode[] enums() default {};",
|
||||
" Bar[] anns() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @interface Bar {",
|
||||
" String[] value() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @Foo({\"a\", \"b\"}) A unsortedString();",
|
||||
" @Foo(cls = {int.class, long.class}) A unsortedClasses();",
|
||||
" @Foo(enums = {DOWN, UP}) A unsortedEnums();",
|
||||
" @Foo(anns = {@Bar(value = \"a\"), @Bar(value = \"b\")}) A unsortedAnns();",
|
||||
" @Foo(anns = {@Bar(\"a\"), @Bar({\"a\", \"b\"})}) A unsortedInnderAnns();",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFiltering() {
|
||||
/* Some violations are not flagged because they are not in- or excluded. */
|
||||
restrictedCompilationTestHelper
|
||||
.addSourceLines(
|
||||
"pkg/A.java",
|
||||
"package pkg;",
|
||||
"",
|
||||
"interface A {",
|
||||
" @interface Foo {",
|
||||
" String[] value() default {};",
|
||||
" String[] value2() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @interface Bar {",
|
||||
" String[] value() default {};",
|
||||
" String[] value2() default {};",
|
||||
" }",
|
||||
"",
|
||||
" @interface Baz {",
|
||||
" String[] value() default {};",
|
||||
" String[] value2() default {};",
|
||||
" }",
|
||||
"",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo({\"b\", \"a\"}) A fooValue();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Foo(value2 = {\"b\", \"a\"}) A fooValue2();",
|
||||
" @Bar({\"b\", \"a\"}) A barValue();",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" @Bar(value2 = {\"b\", \"a\"}) A barValue2();",
|
||||
" @Baz({\"b\", \"a\"}) A bazValue();",
|
||||
" @Baz(value2 = {\"b\", \"a\"}) A bazValue2();",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user