From f8408d7db3a08d93508fb41705c542b2d868f7d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20=C5=9Eener?= Date: Mon, 1 Jun 2020 10:51:40 +0200 Subject: [PATCH] PSM-442 Add `AmbiguousJsonCreatorCheck` --- .../AmbiguousJsonCreatorCheck.java | 73 +++++++++++++ .../AmbiguousJsonCreatorCheckTest.java | 101 ++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/AmbiguousJsonCreatorCheck.java create mode 100644 error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/AmbiguousJsonCreatorCheckTest.java diff --git a/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/AmbiguousJsonCreatorCheck.java b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/AmbiguousJsonCreatorCheck.java new file mode 100644 index 00000000000..f27954eeb59 --- /dev/null +++ b/error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/AmbiguousJsonCreatorCheck.java @@ -0,0 +1,73 @@ +package tech.picnic.errorprone.bugpatterns; + +import com.google.auto.service.AutoService; +import com.google.errorprone.BugPattern; +import com.google.errorprone.BugPattern.LinkType; +import com.google.errorprone.BugPattern.ProvidesFix; +import com.google.errorprone.BugPattern.SeverityLevel; +import com.google.errorprone.BugPattern.StandardTags; +import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker; +import com.google.errorprone.bugpatterns.BugChecker.AnnotationTreeMatcher; +import com.google.errorprone.fixes.SuggestedFix; +import com.google.errorprone.matchers.AnnotationType; +import com.google.errorprone.matchers.Description; +import com.google.errorprone.matchers.Matcher; +import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.AnnotationTree; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.Tree; +import com.sun.tools.javac.code.Symbol; +import java.util.Map; +import java.util.Optional; +import javax.lang.model.element.AnnotationValue; + +/** A {@link BugChecker} which flags ambiguous {@code @JsonCreator}s in enums. */ +@AutoService(BugChecker.class) +@BugPattern( + name = "AmbiguousJsonCreator", + summary = "JsonCreator.Mode should be set for single-argument creators", + linkType = LinkType.NONE, + severity = SeverityLevel.SUGGESTION, + tags = StandardTags.LIKELY_ERROR, + providesFix = ProvidesFix.REQUIRES_HUMAN_ATTENTION) +public final class AmbiguousJsonCreatorCheck extends BugChecker implements AnnotationTreeMatcher { + private static final long serialVersionUID = 1L; + private static final Matcher JSON_CREATOR_ANNOTATION = + new AnnotationType("com.fasterxml.jackson.annotation.JsonCreator"); + + @Override + public Description matchAnnotation(AnnotationTree tree, VisitorState state) { + if (!JSON_CREATOR_ANNOTATION.matches(tree, state)) { + return Description.NO_MATCH; + } + + ClassTree clazz = state.findEnclosing(ClassTree.class); + if (clazz == null || clazz.getKind() != Tree.Kind.ENUM) { + return Description.NO_MATCH; + } + + MethodTree method = state.findEnclosing(MethodTree.class); + if (method == null || method.getParameters().size() != 1) { + return Description.NO_MATCH; + } + + Optional mode = + ASTHelpers.getAnnotationMirror(tree).getElementValues().entrySet().stream() + .filter(entry -> entry.getKey().getSimpleName().contentEquals("mode")) + .map(Map.Entry::getValue) + .map(AnnotationValue::getValue) + .filter(Symbol.VarSymbol.class::isInstance) + .map(o -> (Symbol.VarSymbol) o) + .filter(varSymbol -> !varSymbol.getSimpleName().contentEquals("DEFAULT")) + .findFirst(); + + if (mode.isPresent()) { + return Description.NO_MATCH; + } + + return describeMatch( + tree, SuggestedFix.replace(tree, "@JsonCreator(mode = JsonCreator.Mode.DELEGATING)")); + } +} diff --git a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/AmbiguousJsonCreatorCheckTest.java b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/AmbiguousJsonCreatorCheckTest.java new file mode 100644 index 00000000000..fb7b23bf997 --- /dev/null +++ b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/AmbiguousJsonCreatorCheckTest.java @@ -0,0 +1,101 @@ +package tech.picnic.errorprone.bugpatterns; + +import com.google.common.base.Predicates; +import com.google.errorprone.CompilationTestHelper; +import org.junit.jupiter.api.Test; + +final class AmbiguousJsonCreatorCheckTest { + private final CompilationTestHelper compilationTestHelper = + CompilationTestHelper.newInstance(AmbiguousJsonCreatorCheck.class, getClass()) + .expectErrorMessage( + "X", + Predicates.containsPattern( + "JsonCreator.Mode should be set for single-argument creators")); + + @Test + void testIdentification() { + compilationTestHelper + .addSourceLines( + "Container.java", + "import com.fasterxml.jackson.annotation.JsonCreator;", + "import com.fasterxml.jackson.annotation.JsonValue;", + "", + "interface Container {", + " enum A {", + " FOO(1);", + " private final int i;", + " A(int i) {", + " this.i = i;", + " }", + " // BUG: Diagnostic matches: X", + " @JsonCreator", + " public static A of(int i) {", + " return FOO;", + " }", + " }", + "", + " enum B {", + " FOO(1);", + " private final int i;", + " B(int i) {", + " this.i = i;", + " }", + " @JsonCreator(mode = JsonCreator.Mode.DELEGATING)", + " public static B of(int i) {", + " return FOO;", + " }", + " }", + "", + " enum C {", + " FOO(1, \"s\");", + " @JsonValue private final int i;", + " private final String s;", + " C(int i, String s) {", + " this.i = i;", + " this.s = s;", + " }", + " // BUG: Diagnostic matches: X", + " @JsonCreator", + " public static C of(int i) {", + " return FOO;", + " }", + " }", + "", + " enum D {", + " FOO(1, \"s\");", + " private final int i;", + " private final String s;", + " D(int i, String s) {", + " this.i = i;", + " this.s = s;", + " }", + " @JsonCreator", + " public static D of(int i, String s) {", + " return FOO;", + " }", + " }", + "", + " enum E {", + " FOO;", + " // BUG: Diagnostic matches: X", + " @JsonCreator", + " public static E of(String s) {", + " return FOO;", + " }", + " }", + "", + " class F {", + " private final String s;", + " F(String s) {", + " this.s = s;", + " }", + " @JsonCreator", + " public static F of(String s) {", + " return new F(s);", + " }", + " }", + "", + "}") + .doTest(); + } +}