mirror of
https://github.com/jlengrand/error-prone-support.git
synced 2026-03-10 08:11:25 +00:00
Introduce ExhaustiveRefasterTypeMigration check (#770)
The new `@TypeMigration` annotation can be placed on Refaster rule collections to indicate that they migrate most or all public methods of an indicated type. The new check validates the claim made by the annotation.
This commit is contained in:
committed by
GitHub
parent
b5ace6e044
commit
1cc792c615
@@ -36,6 +36,10 @@ import tech.picnic.errorprone.utils.SourceCode;
|
||||
* <p>The idea behind this checker is that maintaining a sorted sequence simplifies conflict
|
||||
* resolution, and can even avoid it if two branches add the same annotation.
|
||||
*/
|
||||
// XXX: Currently this checker only flags method-level annotations. It should likely also flag
|
||||
// type-, field- and parameter-level annotations.
|
||||
// XXX: Duplicate entries are often a mistake. Consider introducing a similar `BugChecker` that
|
||||
// flags duplicates.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary = "Sort annotations lexicographically where possible",
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.function.Executable;
|
||||
import org.junit.jupiter.api.function.ThrowingSupplier;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
import tech.picnic.errorprone.refaster.annotation.TypeMigration;
|
||||
|
||||
/**
|
||||
* Refaster rules to replace JUnit assertions with AssertJ equivalents.
|
||||
@@ -40,6 +41,264 @@ import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
// `() -> toString()` match both `ThrowingSupplier` and `ThrowingCallable`, but `() -> "constant"`
|
||||
// is only compatible with the former.
|
||||
@OnlineDocumentation
|
||||
@TypeMigration(
|
||||
of = Assertions.class,
|
||||
unmigratedMethods = {
|
||||
"assertAll(Collection<Executable>)",
|
||||
"assertAll(Executable[])",
|
||||
"assertAll(Stream<Executable>)",
|
||||
"assertAll(String, Collection<Executable>)",
|
||||
"assertAll(String, Executable[])",
|
||||
"assertAll(String, Stream<Executable>)",
|
||||
"assertArrayEquals(boolean[], boolean[])",
|
||||
"assertArrayEquals(boolean[], boolean[], String)",
|
||||
"assertArrayEquals(boolean[], boolean[], Supplier<String>)",
|
||||
"assertArrayEquals(byte[], byte[])",
|
||||
"assertArrayEquals(byte[], byte[], String)",
|
||||
"assertArrayEquals(byte[], byte[], Supplier<String>)",
|
||||
"assertArrayEquals(char[], char[])",
|
||||
"assertArrayEquals(char[], char[], String)",
|
||||
"assertArrayEquals(char[], char[], Supplier<String>)",
|
||||
"assertArrayEquals(double[], double[])",
|
||||
"assertArrayEquals(double[], double[], double)",
|
||||
"assertArrayEquals(double[], double[], double, String)",
|
||||
"assertArrayEquals(double[], double[], double, Supplier<String>)",
|
||||
"assertArrayEquals(double[], double[], String)",
|
||||
"assertArrayEquals(double[], double[], Supplier<String>)",
|
||||
"assertArrayEquals(float[], float[])",
|
||||
"assertArrayEquals(float[], float[], float)",
|
||||
"assertArrayEquals(float[], float[], float, String)",
|
||||
"assertArrayEquals(float[], float[], float, Supplier<String>)",
|
||||
"assertArrayEquals(float[], float[], String)",
|
||||
"assertArrayEquals(float[], float[], Supplier<String>)",
|
||||
"assertArrayEquals(int[], int[])",
|
||||
"assertArrayEquals(int[], int[], String)",
|
||||
"assertArrayEquals(int[], int[], Supplier<String>)",
|
||||
"assertArrayEquals(long[], long[])",
|
||||
"assertArrayEquals(long[], long[], String)",
|
||||
"assertArrayEquals(long[], long[], Supplier<String>)",
|
||||
"assertArrayEquals(Object[], Object[])",
|
||||
"assertArrayEquals(Object[], Object[], String)",
|
||||
"assertArrayEquals(Object[], Object[], Supplier<String>)",
|
||||
"assertArrayEquals(short[], short[])",
|
||||
"assertArrayEquals(short[], short[], String)",
|
||||
"assertArrayEquals(short[], short[], Supplier<String>)",
|
||||
"assertEquals(Byte, Byte)",
|
||||
"assertEquals(Byte, byte)",
|
||||
"assertEquals(byte, Byte)",
|
||||
"assertEquals(byte, byte)",
|
||||
"assertEquals(Byte, Byte, String)",
|
||||
"assertEquals(Byte, byte, String)",
|
||||
"assertEquals(byte, Byte, String)",
|
||||
"assertEquals(byte, byte, String)",
|
||||
"assertEquals(Byte, Byte, Supplier<String>)",
|
||||
"assertEquals(Byte, byte, Supplier<String>)",
|
||||
"assertEquals(byte, Byte, Supplier<String>)",
|
||||
"assertEquals(byte, byte, Supplier<String>)",
|
||||
"assertEquals(char, char)",
|
||||
"assertEquals(char, char, String)",
|
||||
"assertEquals(char, char, Supplier<String>)",
|
||||
"assertEquals(char, Character)",
|
||||
"assertEquals(char, Character, String)",
|
||||
"assertEquals(char, Character, Supplier<String>)",
|
||||
"assertEquals(Character, char)",
|
||||
"assertEquals(Character, char, String)",
|
||||
"assertEquals(Character, char, Supplier<String>)",
|
||||
"assertEquals(Character, Character)",
|
||||
"assertEquals(Character, Character, String)",
|
||||
"assertEquals(Character, Character, Supplier<String>)",
|
||||
"assertEquals(Double, Double)",
|
||||
"assertEquals(Double, double)",
|
||||
"assertEquals(double, Double)",
|
||||
"assertEquals(double, double)",
|
||||
"assertEquals(double, double, double)",
|
||||
"assertEquals(double, double, double, String)",
|
||||
"assertEquals(double, double, double, Supplier<String>)",
|
||||
"assertEquals(Double, Double, String)",
|
||||
"assertEquals(Double, double, String)",
|
||||
"assertEquals(double, Double, String)",
|
||||
"assertEquals(double, double, String)",
|
||||
"assertEquals(Double, Double, Supplier<String>)",
|
||||
"assertEquals(Double, double, Supplier<String>)",
|
||||
"assertEquals(double, Double, Supplier<String>)",
|
||||
"assertEquals(double, double, Supplier<String>)",
|
||||
"assertEquals(Float, Float)",
|
||||
"assertEquals(Float, float)",
|
||||
"assertEquals(float, Float)",
|
||||
"assertEquals(float, float)",
|
||||
"assertEquals(float, float, float)",
|
||||
"assertEquals(float, float, float, String)",
|
||||
"assertEquals(float, float, float, Supplier<String>)",
|
||||
"assertEquals(Float, Float, String)",
|
||||
"assertEquals(Float, float, String)",
|
||||
"assertEquals(float, Float, String)",
|
||||
"assertEquals(float, float, String)",
|
||||
"assertEquals(Float, Float, Supplier<String>)",
|
||||
"assertEquals(Float, float, Supplier<String>)",
|
||||
"assertEquals(float, Float, Supplier<String>)",
|
||||
"assertEquals(float, float, Supplier<String>)",
|
||||
"assertEquals(int, int)",
|
||||
"assertEquals(int, int, String)",
|
||||
"assertEquals(int, int, Supplier<String>)",
|
||||
"assertEquals(int, Integer)",
|
||||
"assertEquals(int, Integer, String)",
|
||||
"assertEquals(int, Integer, Supplier<String>)",
|
||||
"assertEquals(Integer, int)",
|
||||
"assertEquals(Integer, int, String)",
|
||||
"assertEquals(Integer, int, Supplier<String>)",
|
||||
"assertEquals(Integer, Integer)",
|
||||
"assertEquals(Integer, Integer, String)",
|
||||
"assertEquals(Integer, Integer, Supplier<String>)",
|
||||
"assertEquals(Long, Long)",
|
||||
"assertEquals(Long, long)",
|
||||
"assertEquals(long, Long)",
|
||||
"assertEquals(long, long)",
|
||||
"assertEquals(Long, Long, String)",
|
||||
"assertEquals(Long, long, String)",
|
||||
"assertEquals(long, Long, String)",
|
||||
"assertEquals(long, long, String)",
|
||||
"assertEquals(Long, Long, Supplier<String>)",
|
||||
"assertEquals(Long, long, Supplier<String>)",
|
||||
"assertEquals(long, Long, Supplier<String>)",
|
||||
"assertEquals(long, long, Supplier<String>)",
|
||||
"assertEquals(Object, Object)",
|
||||
"assertEquals(Object, Object, String)",
|
||||
"assertEquals(Object, Object, Supplier<String>)",
|
||||
"assertEquals(Short, Short)",
|
||||
"assertEquals(Short, short)",
|
||||
"assertEquals(short, Short)",
|
||||
"assertEquals(short, short)",
|
||||
"assertEquals(Short, Short, String)",
|
||||
"assertEquals(Short, short, String)",
|
||||
"assertEquals(short, Short, String)",
|
||||
"assertEquals(short, short, String)",
|
||||
"assertEquals(Short, Short, Supplier<String>)",
|
||||
"assertEquals(Short, short, Supplier<String>)",
|
||||
"assertEquals(short, Short, Supplier<String>)",
|
||||
"assertEquals(short, short, Supplier<String>)",
|
||||
"assertFalse(BooleanSupplier)",
|
||||
"assertFalse(BooleanSupplier, String)",
|
||||
"assertFalse(BooleanSupplier, Supplier<String>)",
|
||||
"assertIterableEquals(Iterable<?>, Iterable<?>)",
|
||||
"assertIterableEquals(Iterable<?>, Iterable<?>, String)",
|
||||
"assertIterableEquals(Iterable<?>, Iterable<?>, Supplier<String>)",
|
||||
"assertLinesMatch(List<String>, List<String>)",
|
||||
"assertLinesMatch(List<String>, List<String>, String)",
|
||||
"assertLinesMatch(List<String>, List<String>, Supplier<String>)",
|
||||
"assertLinesMatch(Stream<String>, Stream<String>)",
|
||||
"assertLinesMatch(Stream<String>, Stream<String>, String)",
|
||||
"assertLinesMatch(Stream<String>, Stream<String>, Supplier<String>)",
|
||||
"assertNotEquals(Byte, Byte)",
|
||||
"assertNotEquals(Byte, byte)",
|
||||
"assertNotEquals(byte, Byte)",
|
||||
"assertNotEquals(byte, byte)",
|
||||
"assertNotEquals(Byte, Byte, String)",
|
||||
"assertNotEquals(Byte, byte, String)",
|
||||
"assertNotEquals(byte, Byte, String)",
|
||||
"assertNotEquals(byte, byte, String)",
|
||||
"assertNotEquals(Byte, Byte, Supplier<String>)",
|
||||
"assertNotEquals(Byte, byte, Supplier<String>)",
|
||||
"assertNotEquals(byte, Byte, Supplier<String>)",
|
||||
"assertNotEquals(byte, byte, Supplier<String>)",
|
||||
"assertNotEquals(char, char)",
|
||||
"assertNotEquals(char, char, String)",
|
||||
"assertNotEquals(char, char, Supplier<String>)",
|
||||
"assertNotEquals(char, Character)",
|
||||
"assertNotEquals(char, Character, String)",
|
||||
"assertNotEquals(char, Character, Supplier<String>)",
|
||||
"assertNotEquals(Character, char)",
|
||||
"assertNotEquals(Character, char, String)",
|
||||
"assertNotEquals(Character, char, Supplier<String>)",
|
||||
"assertNotEquals(Character, Character)",
|
||||
"assertNotEquals(Character, Character, String)",
|
||||
"assertNotEquals(Character, Character, Supplier<String>)",
|
||||
"assertNotEquals(Double, Double)",
|
||||
"assertNotEquals(Double, double)",
|
||||
"assertNotEquals(double, Double)",
|
||||
"assertNotEquals(double, double)",
|
||||
"assertNotEquals(double, double, double)",
|
||||
"assertNotEquals(double, double, double, String)",
|
||||
"assertNotEquals(double, double, double, Supplier<String>)",
|
||||
"assertNotEquals(Double, Double, String)",
|
||||
"assertNotEquals(Double, double, String)",
|
||||
"assertNotEquals(double, Double, String)",
|
||||
"assertNotEquals(double, double, String)",
|
||||
"assertNotEquals(Double, Double, Supplier<String>)",
|
||||
"assertNotEquals(Double, double, Supplier<String>)",
|
||||
"assertNotEquals(double, Double, Supplier<String>)",
|
||||
"assertNotEquals(double, double, Supplier<String>)",
|
||||
"assertNotEquals(Float, Float)",
|
||||
"assertNotEquals(Float, float)",
|
||||
"assertNotEquals(float, Float)",
|
||||
"assertNotEquals(float, float)",
|
||||
"assertNotEquals(float, float, float)",
|
||||
"assertNotEquals(float, float, float, String)",
|
||||
"assertNotEquals(float, float, float, Supplier<String>)",
|
||||
"assertNotEquals(Float, Float, String)",
|
||||
"assertNotEquals(Float, float, String)",
|
||||
"assertNotEquals(float, Float, String)",
|
||||
"assertNotEquals(float, float, String)",
|
||||
"assertNotEquals(Float, Float, Supplier<String>)",
|
||||
"assertNotEquals(Float, float, Supplier<String>)",
|
||||
"assertNotEquals(float, Float, Supplier<String>)",
|
||||
"assertNotEquals(float, float, Supplier<String>)",
|
||||
"assertNotEquals(int, int)",
|
||||
"assertNotEquals(int, int, String)",
|
||||
"assertNotEquals(int, int, Supplier<String>)",
|
||||
"assertNotEquals(int, Integer)",
|
||||
"assertNotEquals(int, Integer, String)",
|
||||
"assertNotEquals(int, Integer, Supplier<String>)",
|
||||
"assertNotEquals(Integer, int)",
|
||||
"assertNotEquals(Integer, int, String)",
|
||||
"assertNotEquals(Integer, int, Supplier<String>)",
|
||||
"assertNotEquals(Integer, Integer)",
|
||||
"assertNotEquals(Integer, Integer, String)",
|
||||
"assertNotEquals(Integer, Integer, Supplier<String>)",
|
||||
"assertNotEquals(Long, Long)",
|
||||
"assertNotEquals(Long, long)",
|
||||
"assertNotEquals(long, Long)",
|
||||
"assertNotEquals(long, long)",
|
||||
"assertNotEquals(Long, Long, String)",
|
||||
"assertNotEquals(Long, long, String)",
|
||||
"assertNotEquals(long, Long, String)",
|
||||
"assertNotEquals(long, long, String)",
|
||||
"assertNotEquals(Long, Long, Supplier<String>)",
|
||||
"assertNotEquals(Long, long, Supplier<String>)",
|
||||
"assertNotEquals(long, Long, Supplier<String>)",
|
||||
"assertNotEquals(long, long, Supplier<String>)",
|
||||
"assertNotEquals(Object, Object)",
|
||||
"assertNotEquals(Object, Object, String)",
|
||||
"assertNotEquals(Object, Object, Supplier<String>)",
|
||||
"assertNotEquals(Short, Short)",
|
||||
"assertNotEquals(Short, short)",
|
||||
"assertNotEquals(short, Short)",
|
||||
"assertNotEquals(short, short)",
|
||||
"assertNotEquals(Short, Short, String)",
|
||||
"assertNotEquals(Short, short, String)",
|
||||
"assertNotEquals(short, Short, String)",
|
||||
"assertNotEquals(short, short, String)",
|
||||
"assertNotEquals(Short, Short, Supplier<String>)",
|
||||
"assertNotEquals(Short, short, Supplier<String>)",
|
||||
"assertNotEquals(short, Short, Supplier<String>)",
|
||||
"assertNotEquals(short, short, Supplier<String>)",
|
||||
"assertTimeout(Duration, Executable)",
|
||||
"assertTimeout(Duration, Executable, String)",
|
||||
"assertTimeout(Duration, Executable, Supplier<String>)",
|
||||
"assertTimeout(Duration, ThrowingSupplier<T>)",
|
||||
"assertTimeout(Duration, ThrowingSupplier<T>, String)",
|
||||
"assertTimeout(Duration, ThrowingSupplier<T>, Supplier<String>)",
|
||||
"assertTimeoutPreemptively(Duration, Executable)",
|
||||
"assertTimeoutPreemptively(Duration, Executable, String)",
|
||||
"assertTimeoutPreemptively(Duration, Executable, Supplier<String>)",
|
||||
"assertTimeoutPreemptively(Duration, ThrowingSupplier<T>)",
|
||||
"assertTimeoutPreemptively(Duration, ThrowingSupplier<T>, String)",
|
||||
"assertTimeoutPreemptively(Duration, ThrowingSupplier<T>, Supplier<String>)",
|
||||
"assertTimeoutPreemptively(Duration, ThrowingSupplier<T>, Supplier<String>, TimeoutFailureFactory<E>)",
|
||||
"assertTrue(BooleanSupplier)",
|
||||
"assertTrue(BooleanSupplier, String)",
|
||||
"assertTrue(BooleanSupplier, Supplier<String>)",
|
||||
"fail(Supplier<String>)"
|
||||
})
|
||||
final class JUnitToAssertJRules {
|
||||
private JUnitToAssertJRules() {}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
|
||||
import org.testng.Assert;
|
||||
import org.testng.Assert.ThrowingRunnable;
|
||||
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
import tech.picnic.errorprone.refaster.annotation.TypeMigration;
|
||||
|
||||
/**
|
||||
* Refaster rules that replace TestNG assertions with equivalent AssertJ assertions.
|
||||
@@ -48,32 +49,107 @@ import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
|
||||
* List<Map<String, Object>> myMaps = new ArrayList<>();
|
||||
* assertEquals(myMaps, ImmutableList.of(ImmutableMap.of()));
|
||||
* }</pre>
|
||||
*
|
||||
* <p>A few {@link Assert} methods are not rewritten:
|
||||
*
|
||||
* <ul>
|
||||
* <li>These methods cannot (easily) be expressed using AssertJ because they mix regular equality
|
||||
* and array equality:
|
||||
* <ul>
|
||||
* <li>{@link Assert#assertEqualsDeep(Map, Map)}
|
||||
* <li>{@link Assert#assertEqualsDeep(Map, Map, String)}
|
||||
* <li>{@link Assert#assertEqualsDeep(Set, Set, String)}
|
||||
* <li>{@link Assert#assertNotEqualsDeep(Map, Map)}
|
||||
* <li>{@link Assert#assertNotEqualsDeep(Map, Map, String)}
|
||||
* <li>{@link Assert#assertNotEqualsDeep(Set, Set)}
|
||||
* <li>{@link Assert#assertNotEqualsDeep(Set, Set, String)}
|
||||
* </ul>
|
||||
* <li>This method returns the caught exception; there is no direct counterpart for this in
|
||||
* AssertJ:
|
||||
* <ul>
|
||||
* <li>{@link Assert#expectThrows(Class, ThrowingRunnable)}
|
||||
* </ul>
|
||||
* </ul>
|
||||
*/
|
||||
// XXX: As-is these rules do not result in a complete migration:
|
||||
// - Expressions containing comments are skipped due to a limitation of Refaster.
|
||||
// - Assertions inside lambda expressions are also skipped. Unclear why.
|
||||
// XXX: The `assertEquals` tests for this class generally use the same expression for `expected` and
|
||||
// `actual`, which makes the validation weaker than necessary; fix this. (And investigate whether we
|
||||
// can introduce validation for this.)
|
||||
@OnlineDocumentation
|
||||
@TypeMigration(
|
||||
of = Assert.class,
|
||||
unmigratedMethods = {
|
||||
// XXX: Add migrations for the methods below.
|
||||
"assertEquals(Boolean, Boolean)",
|
||||
"assertEquals(Boolean, boolean)",
|
||||
"assertEquals(boolean, Boolean)",
|
||||
"assertEquals(Boolean, Boolean, String)",
|
||||
"assertEquals(Boolean, boolean, String)",
|
||||
"assertEquals(boolean, Boolean, String)",
|
||||
"assertEquals(Byte, Byte)",
|
||||
"assertEquals(Byte, byte)",
|
||||
"assertEquals(byte, Byte)",
|
||||
"assertEquals(Byte, Byte, String)",
|
||||
"assertEquals(Byte, byte, String)",
|
||||
"assertEquals(byte, Byte, String)",
|
||||
"assertEquals(char, Character)",
|
||||
"assertEquals(char, Character, String)",
|
||||
"assertEquals(Character, char)",
|
||||
"assertEquals(Character, char, String)",
|
||||
"assertEquals(Character, Character)",
|
||||
"assertEquals(Character, Character, String)",
|
||||
"assertEquals(Double, Double)",
|
||||
"assertEquals(Double, double)",
|
||||
"assertEquals(double, Double)",
|
||||
"assertEquals(Double, Double, String)",
|
||||
"assertEquals(Double, double, String)",
|
||||
"assertEquals(double, Double, String)",
|
||||
"assertEquals(double[], double[], double)",
|
||||
"assertEquals(double[], double[], double, String)",
|
||||
"assertEquals(Float, Float)",
|
||||
"assertEquals(Float, float)",
|
||||
"assertEquals(float, Float)",
|
||||
"assertEquals(Float, Float, String)",
|
||||
"assertEquals(Float, float, String)",
|
||||
"assertEquals(float, Float, String)",
|
||||
"assertEquals(float[], float[], float)",
|
||||
"assertEquals(float[], float[], float, String)",
|
||||
"assertEquals(int, Integer)",
|
||||
"assertEquals(int, Integer, String)",
|
||||
"assertEquals(Integer, int)",
|
||||
"assertEquals(Integer, int, String)",
|
||||
"assertEquals(Integer, Integer)",
|
||||
"assertEquals(Integer, Integer, String)",
|
||||
"assertEquals(Long, Long)",
|
||||
"assertEquals(Long, long)",
|
||||
"assertEquals(long, Long)",
|
||||
"assertEquals(Long, Long, String)",
|
||||
"assertEquals(Long, long, String)",
|
||||
"assertEquals(Short, Short)",
|
||||
"assertEquals(Short, short)",
|
||||
"assertEquals(short, Short)",
|
||||
"assertEquals(Short, Short, String)",
|
||||
"assertEquals(Short, short, String)",
|
||||
"assertEquals(short, Short, String)",
|
||||
/*
|
||||
* These `assertEqualsDeep` methods cannot (easily) be expressed using AssertJ because they
|
||||
* mix regular equality and array equality:
|
||||
*/
|
||||
"assertEqualsDeep(Map<?, ?>, Map<?, ?>)",
|
||||
"assertEqualsDeep(Map<?, ?>, Map<?, ?>, String)",
|
||||
"assertEqualsDeep(Set<?>, Set<?>, String)",
|
||||
// XXX: Add migrations for the methods below.
|
||||
"assertEqualsNoOrder(Collection<?>, Collection<?>)",
|
||||
"assertEqualsNoOrder(Collection<?>, Collection<?>, String)",
|
||||
"assertEqualsNoOrder(Iterator<?>, Iterator<?>)",
|
||||
"assertEqualsNoOrder(Iterator<?>, Iterator<?>, String)",
|
||||
"assertListContains(List<T>, Predicate<T>, String)",
|
||||
"assertListContainsObject(List<T>, T, String)",
|
||||
"assertListNotContains(List<T>, Predicate<T>, String)",
|
||||
"assertListNotContainsObject(List<T>, T, String)",
|
||||
"assertNotEquals(Collection<?>, Collection<?>)",
|
||||
"assertNotEquals(Collection<?>, Collection<?>, String)",
|
||||
"assertNotEquals(Iterator<?>, Iterator<?>)",
|
||||
"assertNotEquals(Iterator<?>, Iterator<?>, String)",
|
||||
"assertNotEquals(Object[], Object[], String)",
|
||||
/*
|
||||
* These `assertNotEqualsDeep` methods cannot (easily) be expressed using AssertJ because they
|
||||
* mix regular equality and array equality:
|
||||
*/
|
||||
"assertNotEqualsDeep(Map<?, ?>, Map<?, ?>)",
|
||||
"assertNotEqualsDeep(Map<?, ?>, Map<?, ?>, String)",
|
||||
"assertNotEqualsDeep(Set<?>, Set<?>)",
|
||||
"assertNotEqualsDeep(Set<?>, Set<?>, String)",
|
||||
// XXX: Add a migration for this `assertThrows` method.
|
||||
"assertThrows(String, Class<T>, ThrowingRunnable)",
|
||||
/*
|
||||
* These `expectThrows` methods return the caught exception; there is no direct counterpart
|
||||
* for this in AssertJ.
|
||||
*/
|
||||
"expectThrows(Class<T>, ThrowingRunnable)",
|
||||
"expectThrows(String, Class<T>, ThrowingRunnable)"
|
||||
})
|
||||
final class TestNGToAssertJRules {
|
||||
private TestNGToAssertJRules() {}
|
||||
|
||||
|
||||
@@ -53,6 +53,16 @@
|
||||
<artifactId>error-prone-utils</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>refaster-support</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.auto</groupId>
|
||||
<artifactId>auto-common</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.auto.service</groupId>
|
||||
<artifactId>auto-service-annotations</artifactId>
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
package tech.picnic.errorprone.guidelines.bugpatterns;
|
||||
|
||||
import static com.google.common.base.Verify.verify;
|
||||
import static com.google.common.collect.ImmutableList.toImmutableList;
|
||||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.BugPattern.StandardTags.LIKELY_ERROR;
|
||||
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE;
|
||||
import static com.google.errorprone.matchers.Matchers.annotations;
|
||||
import static com.google.errorprone.matchers.Matchers.isType;
|
||||
import static java.util.Comparator.comparing;
|
||||
import static java.util.stream.Collectors.toCollection;
|
||||
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
|
||||
|
||||
import com.google.auto.common.AnnotationMirrors;
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Streams;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
|
||||
import com.google.errorprone.fixes.SuggestedFixes;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.MultiMatcher;
|
||||
import com.google.errorprone.matchers.MultiMatcher.MultiMatchResult;
|
||||
import com.google.errorprone.refaster.annotation.BeforeTemplate;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.google.errorprone.util.Signatures;
|
||||
import com.sun.source.tree.AnnotationTree;
|
||||
import com.sun.source.tree.ClassTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.tree.MethodTree;
|
||||
import com.sun.source.tree.NewClassTree;
|
||||
import com.sun.source.tree.Tree;
|
||||
import com.sun.source.util.TreeScanner;
|
||||
import com.sun.tools.javac.code.Attribute;
|
||||
import com.sun.tools.javac.code.Symbol.ClassSymbol;
|
||||
import com.sun.tools.javac.code.Symbol.MethodSymbol;
|
||||
import com.sun.tools.javac.code.Symbol.TypeSymbol;
|
||||
import com.sun.tools.javac.util.Constants;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.AnnotationValue;
|
||||
import javax.lang.model.element.Modifier;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A {@link BugChecker} that validates the claim made by {@link
|
||||
* tech.picnic.errorprone.refaster.annotation.TypeMigration} annotations.
|
||||
*/
|
||||
// XXX: As-is this checker assumes that a method is fully migrated if it is invoked inside at least
|
||||
// one `@BeforeTemplate` method. A stronger check would be to additionally verify that:
|
||||
// 1. Such invocations are not conditionally matched. That is, there should be no constraint on
|
||||
// their context (i.e. any surrounding code), and their parameters must be `@BeforeTemplate`
|
||||
// method parameters with types that are not more restrictive than those of the method itself.
|
||||
// Additionally, the result of non-void methods should be "returned" by the `@BeforeTemplate`
|
||||
// method, so that Refaster will match any expression, rather than just statements. (One caveat
|
||||
// with this "context-independent migrations only" approach is that APIs often expose methods
|
||||
// that are only useful in combination with other methods of the API; insisting that such methods
|
||||
// are migrated in isolation is unreasonable.)
|
||||
// 2. Where relevant, method references should also be migrated. (TBD what "relevant" means in this
|
||||
// case, and whether in fact method reference matchers can be _derived_ from the associated
|
||||
// method invocation matchers.)
|
||||
// XXX: This checker currently does no concern itself with public fields. Consider adding support
|
||||
// for those.
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
summary =
|
||||
"The set of unmigrated methods listed by the `@TypeMigration` annotation must be minimal "
|
||||
+ "yet exhaustive",
|
||||
link = BUG_PATTERNS_BASE_URL + "ExhaustiveRefasterTypeMigration",
|
||||
linkType = CUSTOM,
|
||||
severity = WARNING,
|
||||
tags = LIKELY_ERROR)
|
||||
public final class ExhaustiveRefasterTypeMigration extends BugChecker implements ClassTreeMatcher {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final MultiMatcher<Tree, AnnotationTree> IS_TYPE_MIGRATION =
|
||||
annotations(AT_LEAST_ONE, isType("tech.picnic.errorprone.refaster.annotation.TypeMigration"));
|
||||
private static final MultiMatcher<Tree, AnnotationTree> HAS_BEFORE_TEMPLATE =
|
||||
annotations(AT_LEAST_ONE, isType(BeforeTemplate.class.getCanonicalName()));
|
||||
private static final String TYPE_MIGRATION_TYPE_ELEMENT = "of";
|
||||
private static final String TYPE_MIGRATION_UNMIGRATED_METHODS_ELEMENT = "unmigratedMethods";
|
||||
|
||||
/** Instantiates a new {@link ExhaustiveRefasterTypeMigration} instance. */
|
||||
public ExhaustiveRefasterTypeMigration() {}
|
||||
|
||||
@Override
|
||||
public Description matchClass(ClassTree tree, VisitorState state) {
|
||||
MultiMatchResult<AnnotationTree> migrationAnnotations =
|
||||
IS_TYPE_MIGRATION.multiMatchResult(tree, state);
|
||||
if (!migrationAnnotations.matches()) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
AnnotationTree migrationAnnotation = migrationAnnotations.onlyMatchingNode();
|
||||
AnnotationMirror annotationMirror = ASTHelpers.getAnnotationMirror(migrationAnnotation);
|
||||
TypeSymbol migratedType = getMigratedType(annotationMirror);
|
||||
if (migratedType.asType().isPrimitive() || !(migratedType instanceof ClassSymbol)) {
|
||||
return buildDescription(migrationAnnotation)
|
||||
.setMessage(String.format("Migration of type '%s' is unsupported", migratedType))
|
||||
.build();
|
||||
}
|
||||
|
||||
ImmutableList<String> methodsClaimedUnmigrated = getMethodsClaimedUnmigrated(annotationMirror);
|
||||
ImmutableList<String> unmigratedMethods =
|
||||
getMethodsDefinitelyUnmigrated(
|
||||
tree, (ClassSymbol) migratedType, signatureOrder(methodsClaimedUnmigrated), state);
|
||||
|
||||
if (unmigratedMethods.equals(methodsClaimedUnmigrated)) {
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
/*
|
||||
* The `@TypeMigration` annotation lists a different set of unmigrated methods than the one
|
||||
* produced by our analysis; suggest a replacement.
|
||||
*/
|
||||
// XXX: `updateAnnotationArgumentValues` will prepend the new attribute argument if it is not
|
||||
// already present. It would be nicer if it _appended_ the new attribute.
|
||||
return describeMatch(
|
||||
migrationAnnotation,
|
||||
SuggestedFixes.updateAnnotationArgumentValues(
|
||||
migrationAnnotation,
|
||||
state,
|
||||
TYPE_MIGRATION_UNMIGRATED_METHODS_ELEMENT,
|
||||
unmigratedMethods.stream().map(Constants::format).collect(toImmutableList()))
|
||||
.build());
|
||||
}
|
||||
|
||||
private static TypeSymbol getMigratedType(AnnotationMirror migrationAnnotation) {
|
||||
AnnotationValue value =
|
||||
AnnotationMirrors.getAnnotationValue(migrationAnnotation, TYPE_MIGRATION_TYPE_ELEMENT);
|
||||
verify(
|
||||
value instanceof Attribute.Class,
|
||||
"Value of annotation element `%s` is '%s' rather than a class",
|
||||
TYPE_MIGRATION_TYPE_ELEMENT,
|
||||
value);
|
||||
return ((Attribute.Class) value).classType.tsym;
|
||||
}
|
||||
|
||||
private static ImmutableList<String> getMethodsClaimedUnmigrated(
|
||||
AnnotationMirror migrationAnnotation) {
|
||||
AnnotationValue value =
|
||||
AnnotationMirrors.getAnnotationValue(
|
||||
migrationAnnotation, TYPE_MIGRATION_UNMIGRATED_METHODS_ELEMENT);
|
||||
verify(
|
||||
value instanceof Attribute.Array,
|
||||
"Value of annotation element `%s` is '%s' rather than an array",
|
||||
TYPE_MIGRATION_UNMIGRATED_METHODS_ELEMENT,
|
||||
value);
|
||||
return ((Attribute.Array) value)
|
||||
.getValue().stream().map(a -> a.getValue().toString()).collect(toImmutableList());
|
||||
}
|
||||
|
||||
// XXX: Once only JDK 14 and above are supported, change the
|
||||
// `m.getModifiers().contains(Modifier.PUBLIC)` check to just `m.isPublic()`.
|
||||
private static ImmutableList<String> getMethodsDefinitelyUnmigrated(
|
||||
ClassTree tree, ClassSymbol migratedType, Comparator<String> comparator, VisitorState state) {
|
||||
Set<MethodSymbol> publicMethods =
|
||||
Streams.stream(
|
||||
ASTHelpers.scope(migratedType.members())
|
||||
.getSymbols(
|
||||
m ->
|
||||
m.getModifiers().contains(Modifier.PUBLIC)
|
||||
&& m instanceof MethodSymbol))
|
||||
.map(MethodSymbol.class::cast)
|
||||
.collect(toCollection(HashSet::new));
|
||||
|
||||
/* Remove methods that *appear* to be migrated. Note that this is an imperfect heuristic. */
|
||||
removeMethodsInvokedInBeforeTemplateMethods(tree, publicMethods, state);
|
||||
|
||||
return publicMethods.stream()
|
||||
.map(m -> Signatures.prettyMethodSignature(migratedType, m))
|
||||
.sorted(comparator)
|
||||
.collect(toImmutableList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link Comparator} that orders method signatures to match the given list of
|
||||
* signatures, with any signatures not listed ordered first, lexicographically.
|
||||
*
|
||||
* @implNote This method does not use {@code comparing(list::indexOf)}, as that would make each
|
||||
* comparison a linear, rather than constant-time operation.
|
||||
*/
|
||||
private static Comparator<String> signatureOrder(ImmutableList<String> existingOrder) {
|
||||
Map<String, Integer> knownEntries = new HashMap<>();
|
||||
for (int i = 0; i < existingOrder.size(); i++) {
|
||||
knownEntries.putIfAbsent(existingOrder.get(i), i);
|
||||
}
|
||||
|
||||
// XXX: The lexicographical order applied to unknown entries aims to match the order applied by
|
||||
// the `LexicographicalAnnotationAttributeListing` check; consider deduplicating this logic.
|
||||
return comparing((String v) -> knownEntries.getOrDefault(v, -1))
|
||||
.thenComparing(String.CASE_INSENSITIVE_ORDER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes from the given set of {@link MethodSymbol}s the ones that refer to a method that is
|
||||
* invoked inside a {@link com.google.errorprone.refaster.annotation.BeforeTemplate} method inside
|
||||
* the specified {@link ClassTree}.
|
||||
*/
|
||||
private static void removeMethodsInvokedInBeforeTemplateMethods(
|
||||
ClassTree tree, Set<MethodSymbol> candidates, VisitorState state) {
|
||||
new TreeScanner<@Nullable Void, Consumer<MethodSymbol>>() {
|
||||
@Override
|
||||
public @Nullable Void visitMethod(MethodTree tree, Consumer<MethodSymbol> sink) {
|
||||
return HAS_BEFORE_TEMPLATE.matches(tree, state)
|
||||
? super.visitMethod(tree, candidates::remove)
|
||||
: null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Void visitNewClass(NewClassTree tree, Consumer<MethodSymbol> sink) {
|
||||
sink.accept(ASTHelpers.getSymbol(tree));
|
||||
return super.visitNewClass(tree, sink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Void visitMethodInvocation(
|
||||
MethodInvocationTree tree, Consumer<MethodSymbol> sink) {
|
||||
sink.accept(ASTHelpers.getSymbol(tree));
|
||||
return super.visitMethodInvocation(tree, sink);
|
||||
}
|
||||
}.scan(tree, s -> {});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
package tech.picnic.errorprone.guidelines.bugpatterns;
|
||||
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper;
|
||||
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
final class ExhaustiveRefasterTypeMigrationTest {
|
||||
@Test
|
||||
void identification() {
|
||||
CompilationTestHelper.newInstance(ExhaustiveRefasterTypeMigration.class, getClass())
|
||||
.addSourceLines(
|
||||
"Util.java",
|
||||
"class Util {",
|
||||
" public static int CONSTANT = 42;",
|
||||
"",
|
||||
" public static void publicStaticVoidMethod() {}",
|
||||
"",
|
||||
" static void packagePrivateStaticVoidMethod() {}",
|
||||
"",
|
||||
" protected static void protectedStaticVoidMethod() {}",
|
||||
"",
|
||||
" private static void privateStaticVoidMethod() {}",
|
||||
"",
|
||||
" public static int publicStaticIntMethod2() {",
|
||||
" return 0;",
|
||||
" }",
|
||||
"",
|
||||
" public String publicStringMethodWithArg(int arg) {",
|
||||
" return String.valueOf(arg);",
|
||||
" }",
|
||||
"}")
|
||||
.addSourceLines(
|
||||
"A.java",
|
||||
"import com.google.errorprone.refaster.annotation.AfterTemplate;",
|
||||
"import com.google.errorprone.refaster.annotation.BeforeTemplate;",
|
||||
"import tech.picnic.errorprone.refaster.annotation.TypeMigration;",
|
||||
"",
|
||||
"class A {",
|
||||
" class UnannotatedEmptyClass {}",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: Migration of type 'int' is unsupported",
|
||||
" @TypeMigration(of = int.class)",
|
||||
" class AnnotatedWithPrimitive {}",
|
||||
"",
|
||||
" @TypeMigration(",
|
||||
" of = Util.class,",
|
||||
" unmigratedMethods = {",
|
||||
" \"publicStaticIntMethod2()\",",
|
||||
" \"publicStringMethodWithArg(int)\",",
|
||||
" \"publicStaticVoidMethod()\"",
|
||||
" })",
|
||||
" class AnnotatedEmptyClass {}",
|
||||
"",
|
||||
" @TypeMigration(",
|
||||
" of = Util.class,",
|
||||
" unmigratedMethods = {",
|
||||
" \"publicStaticVoidMethod()\",",
|
||||
" \"publicStringMethodWithArg(int)\",",
|
||||
" \"publicStaticIntMethod2()\"",
|
||||
" })",
|
||||
" class AnnotatedEmptyClassWithUnsortedMethodListing {}",
|
||||
"",
|
||||
" class UnannotatedTemplate {",
|
||||
" @BeforeTemplate",
|
||||
" void before(int value) {",
|
||||
" Util.publicStaticVoidMethod();",
|
||||
" Util.publicStaticIntMethod2();",
|
||||
" new Util().publicStringMethodWithArg(value);",
|
||||
" }",
|
||||
" }",
|
||||
"",
|
||||
" @TypeMigration(",
|
||||
" of = Util.class,",
|
||||
" unmigratedMethods = {",
|
||||
" \"publicStaticIntMethod2()\",",
|
||||
" \"publicStringMethodWithArg(int)\",",
|
||||
" \"publicStaticVoidMethod()\"",
|
||||
" })",
|
||||
" class AnnotatedWithoutBeforeTemplate {",
|
||||
" {",
|
||||
" Util.publicStaticIntMethod2();",
|
||||
" }",
|
||||
"",
|
||||
" @AfterTemplate",
|
||||
" void after(int value) {",
|
||||
" Util.publicStaticVoidMethod();",
|
||||
" new Util().publicStringMethodWithArg(value);",
|
||||
" }",
|
||||
" }",
|
||||
"",
|
||||
" @TypeMigration(of = Util.class)",
|
||||
" class AnnotatedFullyMigrated {",
|
||||
" @BeforeTemplate",
|
||||
" void before() {",
|
||||
" new Util().publicStringMethodWithArg(Util.publicStaticIntMethod2());",
|
||||
" }",
|
||||
"",
|
||||
" @BeforeTemplate",
|
||||
" void before2() {",
|
||||
" Util.publicStaticVoidMethod();",
|
||||
" }",
|
||||
" }",
|
||||
"",
|
||||
" @TypeMigration(of = Util.class, unmigratedMethods = \"publicStringMethodWithArg(int)\")",
|
||||
" class AnnotatedPartiallyMigrated {",
|
||||
" @BeforeTemplate",
|
||||
" void before() {",
|
||||
" Util.publicStaticVoidMethod();",
|
||||
" Util.publicStaticIntMethod2();",
|
||||
" }",
|
||||
" }",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: The set of unmigrated methods listed by the `@TypeMigration`",
|
||||
" // annotation must be minimal yet exhaustive",
|
||||
" @TypeMigration(of = Util.class, unmigratedMethods = \"publicStringMethodWithArg(int)\")",
|
||||
" class AnnotatedWithIncompleteMethodListing {",
|
||||
" @BeforeTemplate",
|
||||
" void before() {",
|
||||
" Util.publicStaticIntMethod2();",
|
||||
" }",
|
||||
" }",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: The set of unmigrated methods listed by the `@TypeMigration`",
|
||||
" // annotation must be minimal yet exhaustive",
|
||||
" @TypeMigration(",
|
||||
" of = Util.class,",
|
||||
" unmigratedMethods = {\"publicStaticIntMethod2()\", \"publicStringMethodWithArg(int)\"})",
|
||||
" class AnnotatedWithMigratedMethodReference {",
|
||||
" @BeforeTemplate",
|
||||
" void before() {",
|
||||
" Util.publicStaticVoidMethod();",
|
||||
" Util.publicStaticIntMethod2();",
|
||||
" }",
|
||||
" }",
|
||||
"",
|
||||
" // BUG: Diagnostic contains: The set of unmigrated methods listed by the `@TypeMigration`",
|
||||
" // annotation must be minimal yet exhaustive",
|
||||
" @TypeMigration(",
|
||||
" of = Util.class,",
|
||||
" unmigratedMethods = {\"extra\", \"publicStringMethodWithArg(int)\"})",
|
||||
" class AnnotatedWithUnknownMethodReference {",
|
||||
" @BeforeTemplate",
|
||||
" void before() {",
|
||||
" Util.publicStaticVoidMethod();",
|
||||
" Util.publicStaticIntMethod2();",
|
||||
" }",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacement() {
|
||||
BugCheckerRefactoringTestHelper.newInstance(ExhaustiveRefasterTypeMigration.class, getClass())
|
||||
.addInputLines(
|
||||
"Util.java",
|
||||
"public final class Util {",
|
||||
" public static void publicStaticVoidMethod() {}",
|
||||
"",
|
||||
" public static int publicStaticIntMethod2() {",
|
||||
" return 0;",
|
||||
" }",
|
||||
"",
|
||||
" public String publicStringMethodWithArg(int arg) {",
|
||||
" return String.valueOf(arg);",
|
||||
" }",
|
||||
"",
|
||||
" public String publicStringMethodWithArg(String arg) {",
|
||||
" return arg;",
|
||||
" }",
|
||||
"}")
|
||||
.expectUnchanged()
|
||||
.addInputLines(
|
||||
"A.java",
|
||||
"import com.google.errorprone.refaster.annotation.BeforeTemplate;",
|
||||
"import tech.picnic.errorprone.refaster.annotation.TypeMigration;",
|
||||
"",
|
||||
"class A {",
|
||||
" @TypeMigration(of = Util.class)",
|
||||
" class AnnotatedWithoutMethodListing {",
|
||||
" {",
|
||||
" new Util().publicStringMethodWithArg(1);",
|
||||
" }",
|
||||
"",
|
||||
" @BeforeTemplate",
|
||||
" void before() {",
|
||||
" Util.publicStaticIntMethod2();",
|
||||
" }",
|
||||
" }",
|
||||
"",
|
||||
" @TypeMigration(",
|
||||
" of = Util.class,",
|
||||
" unmigratedMethods = {\"publicStaticIntMethod2()\", \"extra\", \"publicStringMethodWithArg(int)\"})",
|
||||
" class AnnotatedWithIncorrectMethodReference {",
|
||||
" @BeforeTemplate",
|
||||
" void before() {",
|
||||
" new Util().publicStringMethodWithArg(\"1\");",
|
||||
" Util.publicStaticVoidMethod();",
|
||||
" Util.publicStaticIntMethod2();",
|
||||
" }",
|
||||
" }",
|
||||
"",
|
||||
" @TypeMigration(",
|
||||
" of = Util.class,",
|
||||
" unmigratedMethods = {\"publicStaticVoidMethod()\", \"publicStaticVoidMethod()\"})",
|
||||
" class AnnotatedWithDuplicateMethodReference {",
|
||||
" @BeforeTemplate",
|
||||
" void before() {",
|
||||
" new Util().publicStringMethodWithArg(1);",
|
||||
" new Util().publicStringMethodWithArg(\"1\");",
|
||||
" Util.publicStaticIntMethod2();",
|
||||
" }",
|
||||
" }",
|
||||
"}")
|
||||
.addOutputLines(
|
||||
"A.java",
|
||||
"import com.google.errorprone.refaster.annotation.BeforeTemplate;",
|
||||
"import tech.picnic.errorprone.refaster.annotation.TypeMigration;",
|
||||
"",
|
||||
"class A {",
|
||||
" @TypeMigration(",
|
||||
" unmigratedMethods = {",
|
||||
" \"publicStaticVoidMethod()\",",
|
||||
" \"publicStringMethodWithArg(int)\",",
|
||||
" \"publicStringMethodWithArg(String)\",",
|
||||
" \"Util()\"",
|
||||
" },",
|
||||
" of = Util.class)",
|
||||
" class AnnotatedWithoutMethodListing {",
|
||||
" {",
|
||||
" new Util().publicStringMethodWithArg(1);",
|
||||
" }",
|
||||
"",
|
||||
" @BeforeTemplate",
|
||||
" void before() {",
|
||||
" Util.publicStaticIntMethod2();",
|
||||
" }",
|
||||
" }",
|
||||
"",
|
||||
" @TypeMigration(of = Util.class, unmigratedMethods = \"publicStringMethodWithArg(int)\")",
|
||||
" class AnnotatedWithIncorrectMethodReference {",
|
||||
" @BeforeTemplate",
|
||||
" void before() {",
|
||||
" new Util().publicStringMethodWithArg(\"1\");",
|
||||
" Util.publicStaticVoidMethod();",
|
||||
" Util.publicStaticIntMethod2();",
|
||||
" }",
|
||||
" }",
|
||||
"",
|
||||
" @TypeMigration(of = Util.class, unmigratedMethods = \"publicStaticVoidMethod()\")",
|
||||
" class AnnotatedWithDuplicateMethodReference {",
|
||||
" @BeforeTemplate",
|
||||
" void before() {",
|
||||
" new Util().publicStringMethodWithArg(1);",
|
||||
" new Util().publicStringMethodWithArg(\"1\");",
|
||||
" Util.publicStaticIntMethod2();",
|
||||
" }",
|
||||
" }",
|
||||
"}")
|
||||
.doTest(TestMode.TEXT_MATCH);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package tech.picnic.errorprone.refaster.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Indicates that a Refaster rule or group of Refaster rules is intended to migrate away from the
|
||||
* indicated type.
|
||||
*/
|
||||
// XXX: Add support for `#unmigratedFields()`.
|
||||
// XXX: Consider making this annotation `@Repeatable`, for cases where a single Refaster rule
|
||||
// collection migrates away from multiple types.
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface TypeMigration {
|
||||
/**
|
||||
* The type migrated away from.
|
||||
*
|
||||
* @return The type generally used in the {@link
|
||||
* com.google.errorprone.refaster.annotation.BeforeTemplate} methods of annotated Refaster
|
||||
* rule(s).
|
||||
*/
|
||||
Class<?> of();
|
||||
|
||||
/**
|
||||
* The signatures of public methods and constructors that are not (yet) migrated by the annotated
|
||||
* Refaster rule(s).
|
||||
*
|
||||
* @return A possibly empty enumeration of method and constructor signatures, formatted according
|
||||
* to {@link
|
||||
* com.google.errorprone.util.Signatures#prettyMethodSignature(com.sun.tools.javac.code.Symbol.ClassSymbol,
|
||||
* com.sun.tools.javac.code.Symbol.MethodSymbol)}.
|
||||
*/
|
||||
String[] unmigratedMethods() default {};
|
||||
}
|
||||
Reference in New Issue
Block a user