• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 Google LLC
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.auto.value.processor;
17 
18 import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION;
19 import static com.google.common.truth.OptionalSubject.optionals;
20 import static com.google.common.truth.Truth.assertThat;
21 import static com.google.common.truth.TruthJUnit.assume;
22 import static com.google.testing.compile.CompilationSubject.assertThat;
23 import static java.util.stream.Collectors.partitioningBy;
24 
25 import com.google.auto.common.MoreTypes;
26 import com.google.common.collect.ImmutableList;
27 import com.google.common.truth.Expect;
28 import com.google.testing.compile.Compilation;
29 import com.google.testing.compile.Compiler;
30 import com.google.testing.compile.JavaFileObjects;
31 import java.lang.annotation.ElementType;
32 import java.lang.annotation.Retention;
33 import java.lang.annotation.RetentionPolicy;
34 import java.lang.annotation.Target;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Set;
38 import javax.annotation.processing.AbstractProcessor;
39 import javax.annotation.processing.RoundEnvironment;
40 import javax.annotation.processing.SupportedAnnotationTypes;
41 import javax.lang.model.SourceVersion;
42 import javax.lang.model.element.AnnotationMirror;
43 import javax.lang.model.element.ExecutableElement;
44 import javax.lang.model.element.TypeElement;
45 import javax.lang.model.type.DeclaredType;
46 import javax.lang.model.util.ElementFilter;
47 import org.junit.Rule;
48 import org.junit.Test;
49 import org.junit.runner.RunWith;
50 import org.junit.runners.JUnit4;
51 
52 @RunWith(JUnit4.class)
53 public class NullablesTest {
54   @Rule public Expect expect = Expect.create();
55 
56   @Target(ElementType.TYPE_USE)
57   @Retention(RetentionPolicy.RUNTIME)
58   public @interface Nullable {}
59 
60   @Target(ElementType.TYPE_USE)
61   @Retention(RetentionPolicy.RUNTIME)
62   public @interface Irrelevant {}
63 
64   // The class here has various methods that we will examine with
65   // Nullables.nullableMentionedInMethods to ensure that we do indeed detect @Nullable annotations
66   // in various contexts.
67   // This test is a lot more complicated than it should be. Ideally we would just have this Methods
68   // class be an actual class nested inside this test, and we would use CompilationRule to get
69   // the corresponding TypeElement so we could check the various methods. Unfortunately, if we
70   // do that then we get a TypeElement where all type annotations have disappeared because of
71   // https://bugs.openjdk.java.net/browse/JDK-8225377. So instead we have to use Compiler to compile
72   // the code here with a special annotation processor that will check the annotations of the
73   // just-compiled class. Since the processor is running as part of the same compilation, we don't
74   // lose the type annotations.
75 
76   private static final ImmutableList<String> METHOD_LINES =
77       ImmutableList.of(
78           // Methods in this class whose names begin with "no" do not mention @Nullable anywhere.",
79           // All other methods do.",
80           "package foo.bar;",
81           "",
82           "import " + Irrelevant.class.getCanonicalName() + ";",
83           "import " + Nullable.class.getCanonicalName() + ";",
84           "import java.util.List;",
85           "",
86           "abstract class Methods {",
87           "  void noAnnotations() {}",
88           "  abstract int noAnnotationsEither(int x);",
89           "  abstract @Irrelevant String noRelevantAnnotations(@Irrelevant int x);",
90           "  abstract @Nullable String nullableString();",
91           "  abstract String @Nullable [] nullableArrayOfString();",
92           "  abstract @Nullable String[] arrayOfNullableString();",
93           "  abstract @Nullable String @Nullable [] nullableArrayOfNullableString();",
94           "  abstract List<@Nullable String> listOfNullableString();",
95           "  abstract List<? extends @Nullable Object> listOfExtendsNullable();",
96           "  abstract List<? super @Nullable Number> listOfSuperNullable();",
97           "  abstract <T extends @Nullable Object> T nullableTypeParamBound();",
98           "  abstract <T> @Nullable T nullableTypeParamRef();",
99           "  void nullableParam(@Nullable String x) {}",
100           "  void nullableParamBound(List<? extends @Nullable String> x) {}",
101           "}");
102 
103   @Test
nullableMentionedInMethods()104   public void nullableMentionedInMethods() {
105     // Sadly we can't rely on JDK 8 to handle type annotations correctly.
106     // Some versions do, some don't. So skip the test unless we are on at least JDK 9.
107     double javaVersion = Double.parseDouble(JAVA_SPECIFICATION_VERSION.value());
108     assume().that(javaVersion).isAtLeast(9.0);
109     NullableProcessor processor = new NullableProcessor(expect);
110     Compilation compilation =
111         Compiler.javac()
112             .withProcessors(processor)
113             .compile(JavaFileObjects.forSourceLines("foo.bar.Methods", METHOD_LINES));
114     assertThat(compilation).succeededWithoutWarnings();
115     assertThat(processor.ran).isTrue();
116     // If any `expect` calls failed then the test will fail now because of the Expect rule.
117   }
118 
119   @SupportedAnnotationTypes("*")
120   private static class NullableProcessor extends AbstractProcessor {
121 
122     private final Expect expect;
123     boolean ran;
124 
NullableProcessor(Expect expect)125     NullableProcessor(Expect expect) {
126       this.expect = expect;
127     }
128 
129     @Override
getSupportedSourceVersion()130     public SourceVersion getSupportedSourceVersion() {
131       return SourceVersion.latestSupported();
132     }
133 
134     @Override
process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)135     public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
136       if (roundEnv.processingOver()) {
137         TypeElement methodsElement =
138             processingEnv.getElementUtils().getTypeElement("foo.bar.Methods");
139         expect.that(methodsElement).isNotNull();
140 
141         List<ExecutableElement> methods =
142             ElementFilter.methodsIn(methodsElement.getEnclosedElements());
143         Map<Boolean, List<ExecutableElement>> partitionedMethods =
144             methods.stream()
145                 .collect(partitioningBy(p -> !p.getSimpleName().toString().startsWith("no")));
146         List<ExecutableElement> nullableMethods = partitionedMethods.get(true);
147         List<ExecutableElement> notNullableMethods = partitionedMethods.get(false);
148 
149         expect
150             .about(optionals())
151             .that(Nullables.nullableMentionedInMethods(notNullableMethods))
152             .isEmpty();
153 
154         TypeElement nullableElement =
155             processingEnv.getElementUtils().getTypeElement(Nullable.class.getCanonicalName());
156         expect.that(nullableElement).isNotNull();
157         DeclaredType nullableType = MoreTypes.asDeclared(nullableElement.asType());
158 
159         for (ExecutableElement nullableMethod : nullableMethods) {
160           // Make a list with all the methods that don't have @Nullable plus one method that does.
161           ImmutableList<ExecutableElement> notNullablePlusNullable =
162               ImmutableList.<ExecutableElement>builder()
163                   .addAll(notNullableMethods)
164                   .add(nullableMethod)
165                   .build();
166           expect
167               .withMessage("method %s should have @Nullable", nullableMethod)
168               .about(optionals())
169               .that(
170                   Nullables.nullableMentionedInMethods(notNullablePlusNullable)
171                       .map(AnnotationMirror::getAnnotationType))
172               .hasValue(nullableType);
173         }
174         ran = true;
175       }
176       return false;
177     }
178   }
179 }
180