Skip to content

Commit ed16439

Browse files
committed
Add detector for needless nullness annotations
1 parent b99cbd7 commit ed16439

File tree

4 files changed

+420
-2
lines changed

4 files changed

+420
-2
lines changed

pom.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
</distributionManagement>
6767

6868
<properties>
69-
<android-version>24.4.0-beta1</android-version>
69+
<android-version>25.1.0</android-version>
7070
<fingbugs-annotations-version>3.0.0</fingbugs-annotations-version>
7171
<asm-all-version>5.0.3</asm-all-version>
7272
<guava-version>17.0</guava-version>
@@ -170,6 +170,12 @@
170170
<artifactId>lint-api</artifactId>
171171
<version>${android-version}</version>
172172
</dependency>
173+
<dependency>
174+
<groupId>com.android.tools.external.lombok</groupId>
175+
<artifactId>lombok-ast</artifactId>
176+
<version>0.2.3</version>
177+
<scope>provided</scope>
178+
</dependency>
173179
<dependency>
174180
<groupId>com.android.tools</groupId>
175181
<artifactId>common</artifactId>

src/main/java/com/monits/linters/MonitsIssueRegistry.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import com.android.tools.lint.client.api.IssueRegistry;
2222
import com.android.tools.lint.detector.api.Issue;
23+
import com.monits.linters.ast.NeedlessNullnessAnnotationDetector;
2324
import com.monits.linters.parcelable.ParcelDetector;
2425

2526
public class MonitsIssueRegistry extends IssueRegistry {
@@ -31,6 +32,7 @@ public List<Issue> getIssues() {
3132
ParcelDetector.MISSING_OR_OUT_OF_ORDER,
3233
ParcelDetector.INCOMPATIBLE_READ_WRITE_TYPE,
3334
FactoryMethodDetector.USE_FACTORY_METHOD_INSTEAD_NEW_FRAGMENT,
34-
InstanceStateDetector.MISSING_SAVED_INSTANCE_STATES);
35+
InstanceStateDetector.MISSING_SAVED_INSTANCE_STATES,
36+
NeedlessNullnessAnnotationDetector.NEEDLESS_NULLNESS_ANNOTATION);
3537
}
3638
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/**
2+
* Copyright 2010 - 2016 - Monits
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this
5+
* file except in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under
10+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11+
* ANY KIND, either express or implied. See the License for the specific language governing
12+
* permissions and limitations under the License.
13+
*/
14+
package com.monits.linters.ast;
15+
16+
import java.io.File;
17+
import java.util.EnumSet;
18+
import java.util.List;
19+
20+
import lombok.ast.Annotation;
21+
import lombok.ast.AstVisitor;
22+
import lombok.ast.ForwardingAstVisitor;
23+
import lombok.ast.Identifier;
24+
import lombok.ast.MethodDeclaration;
25+
import lombok.ast.Modifiers;
26+
import lombok.ast.Node;
27+
import lombok.ast.StrictListAccessor;
28+
import lombok.ast.TypeReference;
29+
import lombok.ast.VariableDefinition;
30+
import lombok.ast.VariableDefinitionEntry;
31+
32+
import com.android.tools.lint.detector.api.Category;
33+
import com.android.tools.lint.detector.api.Context;
34+
import com.android.tools.lint.detector.api.Detector;
35+
import com.android.tools.lint.detector.api.Detector.JavaScanner;
36+
import com.android.tools.lint.detector.api.Implementation;
37+
import com.android.tools.lint.detector.api.Issue;
38+
import com.android.tools.lint.detector.api.JavaContext;
39+
import com.android.tools.lint.detector.api.Scope;
40+
import com.android.tools.lint.detector.api.Severity;
41+
import com.google.common.collect.ImmutableList;
42+
43+
public class NeedlessNullnessAnnotationDetector extends Detector implements JavaScanner {
44+
45+
/* default */ static final String VOID_RETURN_TYPE_MSG = "Method %s returns void and can therefore never be null.";
46+
/* default */ static final String PRIMITIVE_RETURN_TYPE_MSG = "Method %s has a primitive return type and can therefore never be null.";
47+
/* default */ static final String VOID_VAR_MSG = "Variable %s is void and can therefore never be null.";
48+
/* default */ static final String PRIMITIVE_VAR_MSG = "Variable %s has a primitive data type and can therefore never be null.";
49+
50+
public static final Issue NEEDLESS_NULLNESS_ANNOTATION = Issue.create("NeedlessNullnessAnnotation",
51+
"Checks that no nullness annotations are present on variables that simply can't be null",
52+
"Adding extra annotations jsut clutters code, and may cause confusion when they are contradictory",
53+
Category.CORRECTNESS, 6, Severity.WARNING,
54+
new Implementation(NeedlessNullnessAnnotationDetector.class, Scope.JAVA_FILE_SCOPE));
55+
56+
@Override
57+
public boolean appliesTo(final Context context, final File file) {
58+
return true;
59+
}
60+
61+
@Override
62+
public EnumSet<Scope> getApplicableFiles() {
63+
return Scope.JAVA_FILE_SCOPE;
64+
}
65+
66+
@Override
67+
public List<Class<? extends Node>> getApplicableNodeTypes() {
68+
return ImmutableList.<Class<? extends Node>>of(Annotation.class);
69+
}
70+
71+
@Override
72+
public AstVisitor createJavaVisitor(final JavaContext context) {
73+
return new NeedlessNullnessAnnotationChecker(context);
74+
}
75+
76+
private static class NeedlessNullnessAnnotationChecker extends ForwardingAstVisitor {
77+
private static final String NULLABLE = "Nullable";
78+
private static final String FQCN_NULLABLE = "com.android.annotations." + NULLABLE;
79+
80+
private static final String NONNULL = "NonNull";
81+
private static final String FQCN_NONNULL = "com.android.annotations." + NONNULL;
82+
83+
private final JavaContext context;
84+
85+
public NeedlessNullnessAnnotationChecker(final JavaContext context) {
86+
this.context = context;
87+
}
88+
89+
@Override
90+
public boolean visitAnnotation(final Annotation node) {
91+
final String type = node.astAnnotationTypeReference().getTypeName();
92+
93+
if (!NULLABLE.equals(type) && !FQCN_NULLABLE.equals(type)
94+
&& !NONNULL.equals(type) && !FQCN_NONNULL.equals(type)) {
95+
return super.visitAnnotation(node);
96+
}
97+
98+
// What's the annotation's target?
99+
final Node parentNode = node.getParent();
100+
if (parentNode instanceof Modifiers) {
101+
final Node targetNode = parentNode.getParent();
102+
if (targetNode instanceof VariableDefinition) {
103+
// We are on a variable definition (most probably a parameter)
104+
final TypeReference typeReference = ((VariableDefinition) targetNode).astTypeReference();
105+
final StrictListAccessor<VariableDefinitionEntry, VariableDefinition> variables = ((VariableDefinition) targetNode).astVariables();
106+
107+
for (final VariableDefinitionEntry varDef : variables) {
108+
if (typeReference.isPrimitive()) {
109+
context.report(NEEDLESS_NULLNESS_ANNOTATION, node, context.getLocation(node),
110+
String.format(PRIMITIVE_VAR_MSG, varDef.astName().astValue()));
111+
} else if (typeReference.isVoid()) {
112+
context.report(NEEDLESS_NULLNESS_ANNOTATION, node, context.getLocation(node),
113+
String.format(VOID_VAR_MSG, varDef.astName().astValue()));
114+
}
115+
}
116+
} else if (targetNode instanceof MethodDeclaration) {
117+
// We are on a method definition, check the return type
118+
final TypeReference typeReference = ((MethodDeclaration) targetNode).astReturnTypeReference();
119+
final Identifier methodName = ((MethodDeclaration) targetNode).astMethodName();
120+
121+
if (typeReference.isPrimitive()) {
122+
context.report(NEEDLESS_NULLNESS_ANNOTATION, node, context.getLocation(node),
123+
String.format(PRIMITIVE_RETURN_TYPE_MSG, methodName.astValue()));
124+
} else if (typeReference.isVoid()) {
125+
context.report(NEEDLESS_NULLNESS_ANNOTATION, node, context.getLocation(node),
126+
String.format(VOID_RETURN_TYPE_MSG, methodName.astValue()));
127+
}
128+
}
129+
}
130+
131+
return super.visitAnnotation(node);
132+
}
133+
}
134+
}

0 commit comments

Comments
 (0)