aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTor Norbye <tnorbye@google.com>2015-08-03 16:47:20 +0000
committerAndroid Git Automerger <android-git-automerger@android.com>2015-08-03 16:47:20 +0000
commit02c79dc3042cdce78f303e63fff56280652e34f1 (patch)
tree9c52a13cbf09d37abc9a8643d06beff73eb81462
parentb59c8a98a338b1db5283c12e7a22f0c70cce8a00 (diff)
parent46711741799f9c5ae8a7d0934227d8f216ebebfb (diff)
downloadplatform_tools_base-02c79dc3042cdce78f303e63fff56280652e34f1.tar.gz
platform_tools_base-02c79dc3042cdce78f303e63fff56280652e34f1.tar.bz2
platform_tools_base-02c79dc3042cdce78f303e63fff56280652e34f1.zip
am 46711741: am 53343dba: Merge "181789: Incorrect/inconsistent lint + documentation for Snackbar" into studio-1.4-dev automerge: 10720a8 automerge: 1fd1cea
* commit '46711741799f9c5ae8a7d0934227d8f216ebebfb': 181789: Incorrect/inconsistent lint + documentation for Snackbar
-rw-r--r--build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/Extractor.java107
-rw-r--r--build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/TypedefCollector.java24
-rw-r--r--build-system/gradle-core/src/test/groovy/com/android/build/gradle/tasks/annotations/ExtractAnnotationsDriverTest.java34
-rw-r--r--lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CallSuperDetector.java23
-rw-r--r--lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SupportAnnotationDetector.java246
-rw-r--r--lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SupportAnnotationDetectorTest.java156
-rw-r--r--lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/IntRange.java.txt3
-rw-r--r--lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/p1/p2/Flow.java.txt8
-rw-r--r--lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/RangeTest.java.txt2
9 files changed, 418 insertions, 185 deletions
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/Extractor.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/Extractor.java
index 2c14128bfe..8cc6042654 100644
--- a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/Extractor.java
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/Extractor.java
@@ -32,6 +32,7 @@ import static com.android.SdkConstants.SUPPORT_ANNOTATIONS_PREFIX;
import static com.android.SdkConstants.TYPE_DEF_FLAG_ATTRIBUTE;
import static com.android.SdkConstants.TYPE_DEF_VALUE_ATTRIBUTE;
import static com.android.SdkConstants.VALUE_TRUE;
+import static com.android.tools.lint.checks.SupportAnnotationDetector.INT_RANGE_ANNOTATION;
import static com.android.tools.lint.detector.api.LintUtils.assertionsEnabled;
import com.android.annotations.NonNull;
@@ -179,6 +180,7 @@ public class Extractor {
public static final String ANDROID_NOTNULL = "android.annotation.NonNull";
public static final String SUPPORT_NOTNULL = "android.support.annotation.NonNull";
public static final String ANDROID_INT_DEF = "android.annotation.IntDef";
+ public static final String ANDROID_INT_RANGE = "android.annotation.IntRange";
public static final String ANDROID_STRING_DEF = "android.annotation.StringDef";
public static final String REQUIRES_PERMISSION = "android.support.annotation.RequiresPermission";
public static final String ANDROID_REQUIRES_PERMISSION = "android.annotation.RequiresPermission";
@@ -190,7 +192,7 @@ public class Extractor {
public static final String ATTR_VAL = "val";
@NonNull
- private final Map<String, AnnotationData> types = Maps.newHashMap();
+ private final Map<String, List<AnnotationData>> types = Maps.newHashMap();
@NonNull
private final Set<String> irrelevantAnnotations = Sets.newHashSet();
@@ -211,7 +213,7 @@ public class Extractor {
private final Set<CompilationUnitDeclaration> processedFiles = Sets.newHashSetWithExpectedSize(100);
private final Set<String> ignoredAnnotations = Sets.newHashSet();
private boolean listIgnored;
- private Map<String,Annotation> typedefs;
+ private Map<String,List<Annotation>> typedefs;
private List<String> typedefClasses;
private Map<String,Boolean> sourceRetention;
private final List<Item> keepItems = Lists.newArrayList();
@@ -511,43 +513,46 @@ public class Extractor {
if (annotations != null) {
for (Annotation annotation : annotations) {
if (isRelevantAnnotation(annotation)) {
- AnnotationData annotationData = createAnnotation(annotation);
- if (annotationData != null) {
- if (annotationData.name.equals(SUPPORT_KEEP)) {
- // Put keep rules in a different place; we don't want to write
- // these out into the external annotations database, they go
- // into a special proguard file
- keepItems.add(item);
- } else {
- item.annotations.add(annotationData);
- }
+ String fqn = getFqn(annotation);
+ if (SUPPORT_KEEP.equals(fqn)) {
+ // Put keep rules in a different place; we don't want to write
+ // these out into the external annotations database, they go
+ // into a special proguard file
+ keepItems.add(item);
+ } else {
+ addAnnotation(annotation, fqn, item.annotations);
}
}
}
}
}
- @Nullable
- private AnnotationData createAnnotation(@NonNull Annotation annotation) {
- String fqn = getFqn(annotation);
+ private void addAnnotation(@NonNull Annotation annotation, @Nullable String fqn,
+ @NonNull List<AnnotationData> list) {
if (fqn == null) {
- return null;
+ return;
}
+
if (fqn.equals(ANDROID_NULLABLE) || fqn.equals(SUPPORT_NULLABLE)) {
recordStats(fqn);
- return new AnnotationData(SUPPORT_NULLABLE);
+ list.add(new AnnotationData(SUPPORT_NULLABLE));
+ return;
}
if (fqn.equals(ANDROID_NOTNULL) || fqn.equals(SUPPORT_NOTNULL)) {
recordStats(fqn);
- return new AnnotationData(SUPPORT_NOTNULL);
+ list.add(new AnnotationData(SUPPORT_NOTNULL));
+ return;
}
if (fqn.startsWith(SUPPORT_ANNOTATIONS_PREFIX)
&& fqn.endsWith(RESOURCE_TYPE_ANNOTATIONS_SUFFIX)) {
recordStats(fqn);
- return new AnnotationData(fqn);
- } else if (fqn.startsWith(ANDROID_ANNOTATIONS_PREFIX)) {
+ list.add(new AnnotationData(fqn));
+ return;
+ }
+
+ if (fqn.startsWith(ANDROID_ANNOTATIONS_PREFIX)) {
// System annotations: translate to support library annotations
if (fqn.endsWith(RESOURCE_TYPE_ANNOTATIONS_SUFFIX)) {
// Translate e.g. android.annotation.DrawableRes to
@@ -556,10 +561,11 @@ public class Extractor {
fqn.substring(ANDROID_ANNOTATIONS_PREFIX.length());
if (!includeClassRetentionAnnotations
&& !hasSourceRetention(resAnnotation, null)) {
- return null;
+ return;
}
recordStats(resAnnotation);
- return new AnnotationData(resAnnotation);
+ list.add(new AnnotationData(resAnnotation));
+ return;
} else if (isRelevantFrameworkAnnotation(fqn)) {
// Translate other android.annotation annotations into corresponding
// support annotations
@@ -567,23 +573,25 @@ public class Extractor {
fqn.substring(ANDROID_ANNOTATIONS_PREFIX.length());
if (!includeClassRetentionAnnotations
&& !hasSourceRetention(supportAnnotation, null)) {
- return null;
+ return;
}
recordStats(supportAnnotation);
- return createData(supportAnnotation, annotation);
+ list.add(createData(supportAnnotation, annotation));
}
}
if (fqn.startsWith(SUPPORT_ANNOTATIONS_PREFIX)) {
recordStats(fqn);
- return createData(fqn, annotation);
+ list.add(createData(fqn, annotation));
+ return;
}
if (isMagicConstant(fqn)) {
- return types.get(fqn);
+ List<AnnotationData> indirect = types.get(fqn);
+ if (indirect != null) {
+ list.addAll(indirect);
+ }
}
-
- return null;
}
private void recordStats(String fqn) {
@@ -654,28 +662,31 @@ public class Extractor {
if (types.containsKey(typeName) ||
typeName.equals(INT_DEF_ANNOTATION) ||
typeName.equals(STRING_DEF_ANNOTATION) ||
+ typeName.equals(INT_RANGE_ANNOTATION) ||
+ typeName.equals(ANDROID_INT_RANGE) ||
typeName.equals(ANDROID_INT_DEF) ||
typeName.equals(ANDROID_STRING_DEF)) {
return true;
}
- Annotation typeDef = typedefs.get(typeName);
+ List<Annotation> typeDefs = typedefs.get(typeName);
// We only support a single level of IntDef type annotations, not arbitrary nesting
- if (typeDef != null) {
- String fqn = getFqn(typeDef);
- if (fqn != null &&
- (fqn.equals(INT_DEF_ANNOTATION) ||
- fqn.equals(STRING_DEF_ANNOTATION) ||
- fqn.equals(REQUIRES_PERMISSION) ||
- fqn.equals(ANDROID_REQUIRES_PERMISSION) ||
- fqn.equals(ANDROID_INT_DEF) ||
- fqn.equals(ANDROID_STRING_DEF))) {
- AnnotationData a = createAnnotation(typeDef);
- if (a != null) {
- types.put(typeName, a);
- return true;
+ if (typeDefs != null) {
+ boolean match = false;
+ for (Annotation typeDef : typeDefs) {
+ String fqn = getFqn(typeDef);
+ if (isNestedAnnotation(fqn)) {
+ List<AnnotationData> list = types.get(typeName);
+ if (list == null) {
+ list = new ArrayList<AnnotationData>(2);
+ types.put(typeName, list);
+ }
+ addAnnotation(typeDef, fqn, list);
+ match = true;
}
}
+
+ return match;
}
irrelevantAnnotations.add(typeName);
@@ -683,6 +694,18 @@ public class Extractor {
return false;
}
+ static boolean isNestedAnnotation(@Nullable String fqn) {
+ return (fqn != null &&
+ (fqn.equals(INT_DEF_ANNOTATION) ||
+ fqn.equals(STRING_DEF_ANNOTATION) ||
+ fqn.equals(REQUIRES_PERMISSION) ||
+ fqn.equals(ANDROID_REQUIRES_PERMISSION) ||
+ fqn.equals(INT_RANGE_ANNOTATION) ||
+ fqn.equals(ANDROID_INT_RANGE) ||
+ fqn.equals(ANDROID_INT_DEF) ||
+ fqn.equals(ANDROID_STRING_DEF)));
+ }
+
private boolean writeKeepRules(@NonNull File proguardCfg) {
if (!keepItems.isEmpty()) {
try {
diff --git a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/TypedefCollector.java b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/TypedefCollector.java
index 4430d6fa01..5924f4c010 100644
--- a/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/TypedefCollector.java
+++ b/build-system/gradle-core/src/main/groovy/com/android/build/gradle/tasks/annotations/TypedefCollector.java
@@ -16,9 +16,6 @@
package com.android.build.gradle.tasks.annotations;
-import static com.android.SdkConstants.INT_DEF_ANNOTATION;
-import static com.android.SdkConstants.STRING_DEF_ANNOTATION;
-
import com.android.annotations.NonNull;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -34,13 +31,14 @@ import org.eclipse.jdt.internal.compiler.lookup.CompilationUnitScope;
import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
import java.io.File;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/** Gathers information about typedefs (@IntDef and @StringDef */
public class TypedefCollector extends ASTVisitor {
- private Map<String,Annotation> mMap = Maps.newHashMap();
+ private Map<String,List<Annotation>> mMap = Maps.newHashMap();
private final boolean mRequireHide;
private final boolean mRequireSourceRetention;
@@ -65,7 +63,7 @@ public class TypedefCollector extends ASTVisitor {
return mTypedefClasses;
}
- public Map<String,Annotation> getTypedefs() {
+ public Map<String,List<Annotation>> getTypedefs() {
return mMap;
}
@@ -94,14 +92,16 @@ public class TypedefCollector extends ASTVisitor {
continue;
}
- if (typeName.equals(INT_DEF_ANNOTATION) ||
- typeName.equals(STRING_DEF_ANNOTATION) ||
- typeName.equals(Extractor.REQUIRES_PERMISSION) ||
- typeName.equals(Extractor.ANDROID_REQUIRES_PERMISSION) ||
- typeName.equals(Extractor.ANDROID_INT_DEF) ||
- typeName.equals(Extractor.ANDROID_STRING_DEF)) {
+ if (Extractor.isNestedAnnotation(typeName)) {
String fqn = new String(binding.readableName());
- mMap.put(fqn, annotation);
+
+ List<Annotation> list = mMap.get(fqn);
+ if (list == null) {
+ list = new ArrayList<Annotation>(2);
+ mMap.put(fqn, list);
+ }
+ list.add(annotation);
+
if (mRequireHide) {
Javadoc javadoc = declaration.javadoc;
if (javadoc != null) {
diff --git a/build-system/gradle-core/src/test/groovy/com/android/build/gradle/tasks/annotations/ExtractAnnotationsDriverTest.java b/build-system/gradle-core/src/test/groovy/com/android/build/gradle/tasks/annotations/ExtractAnnotationsDriverTest.java
index 242de76ac4..f6340e3b82 100644
--- a/build-system/gradle-core/src/test/groovy/com/android/build/gradle/tasks/annotations/ExtractAnnotationsDriverTest.java
+++ b/build-system/gradle-core/src/test/groovy/com/android/build/gradle/tasks/annotations/ExtractAnnotationsDriverTest.java
@@ -126,6 +126,7 @@ public class ExtractAnnotationsDriverTest extends SdkTestCase {
mManifest,
mKeepAnnotation,
mIntDefAnnotation,
+ mIntRangeAnnotation,
mPermissionAnnotation);
File output = File.createTempFile("annotations", ".zip");
@@ -161,8 +162,8 @@ public class ExtractAnnotationsDriverTest extends SdkTestCase {
Files.toString(proguard, Charsets.UTF_8));
// Check extracted annotations
- checkPackageXml("test.pkg", output, "" +
- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ checkPackageXml("test.pkg", output, ""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<root>\n"
+ " <item name=\"test.pkg.IntDefTest void setFlags(java.lang.Object, int) 1\">\n"
+ " <annotation name=\"android.support.annotation.IntDef\">\n"
@@ -174,6 +175,9 @@ public class ExtractAnnotationsDriverTest extends SdkTestCase {
+ " <annotation name=\"android.support.annotation.IntDef\">\n"
+ " <val name=\"value\" val=\"{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT}\" />\n"
+ " </annotation>\n"
+ + " <annotation name=\"android.support.annotation.IntRange\">\n"
+ + " <val name=\"from\" val=\"20\" />\n"
+ + " </annotation>\n"
+ " </item>\n"
+ " <item name=\"test.pkg.PermissionsTest CONTENT_URI\">\n"
+ " <annotation name=\"android.support.annotation.RequiresPermission.Read\">\n"
@@ -212,6 +216,7 @@ public class ExtractAnnotationsDriverTest extends SdkTestCase {
mManifest,
mKeepAnnotation,
mIntDefAnnotation,
+ mIntRangeAnnotation,
mPermissionAnnotation);
File output = File.createTempFile("annotations", ".zip");
@@ -240,8 +245,8 @@ public class ExtractAnnotationsDriverTest extends SdkTestCase {
new ExtractAnnotationsDriver().run(args);
// Check external annotations
- checkPackageXml("test.pkg", output, "" +
- "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ checkPackageXml("test.pkg", output, ""
+ + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<root>\n"
+ " <item name=\"test.pkg.IntDefTest void setFlags(java.lang.Object, int) 1\">\n"
+ " <annotation name=\"android.support.annotation.IntDef\">\n"
@@ -253,6 +258,9 @@ public class ExtractAnnotationsDriverTest extends SdkTestCase {
+ " <annotation name=\"android.support.annotation.IntDef\">\n"
+ " <val name=\"value\" val=\"{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT}\" />\n"
+ " </annotation>\n"
+ + " <annotation name=\"android.support.annotation.IntRange\">\n"
+ + " <val name=\"from\" val=\"20\" />\n"
+ + " </annotation>\n"
+ " </item>\n"
+ "</root>\n"
+ "\n");
@@ -303,6 +311,22 @@ public class ExtractAnnotationsDriverTest extends SdkTestCase {
+ " boolean flag() default false;\n"
+ "}\n");
+ private final TestFile mIntRangeAnnotation = mksrc("src/android/support/annotation/IntRange.java", ""
+ + "package android.support.annotation;\n"
+ + "\n"
+ + "import java.lang.annotation.Retention;\n"
+ + "import java.lang.annotation.Target;\n"
+ + "\n"
+ + "import static java.lang.annotation.ElementType.*;\n"
+ + "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
+ + "\n"
+ + "@Retention(CLASS)\n"
+ + "@Target({CONSTRUCTOR,METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE})\n"
+ + "public @interface IntRange {\n"
+ + " long from() default Long.MIN_VALUE;\n"
+ + " long to() default Long.MAX_VALUE;\n"
+ + "}\n");
+
private final TestFile mPermissionAnnotation = mksrc(
"src/android/support/annotation/RequiresPermission.java", ""
+ "package android.support.annotation;\n"
@@ -333,6 +357,7 @@ public class ExtractAnnotationsDriverTest extends SdkTestCase {
+ "\n"
+ "import android.content.Context;\n"
+ "import android.support.annotation.IntDef;\n"
+ + "import android.support.annotation.IntRange;\n"
+ "import android.support.annotation.Keep;\n"
+ "import android.view.View;\n"
+ "\n"
@@ -342,6 +367,7 @@ public class ExtractAnnotationsDriverTest extends SdkTestCase {
+ "@SuppressWarnings(\"UnusedDeclaration\")\n"
+ "public class IntDefTest {\n"
+ " @IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})\n"
+ + " @IntRange(from = 20)\n"
+ " @Retention(RetentionPolicy.SOURCE)\n"
+ " private @interface DialogStyle {}\n"
+ "\n"
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CallSuperDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CallSuperDetector.java
index 51d9c325bb..6dc6e678b0 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CallSuperDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/CallSuperDetector.java
@@ -18,10 +18,11 @@ package com.android.tools.lint.checks;
import static com.android.SdkConstants.CLASS_VIEW;
import static com.android.SdkConstants.SUPPORT_ANNOTATIONS_PREFIX;
+import static com.android.tools.lint.checks.SupportAnnotationDetector.filterRelevantAnnotations;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
-import com.android.tools.lint.client.api.JavaParser;
+import com.android.tools.lint.client.api.JavaParser.ResolvedAnnotation;
import com.android.tools.lint.client.api.JavaParser.ResolvedMethod;
import com.android.tools.lint.client.api.JavaParser.ResolvedNode;
import com.android.tools.lint.detector.api.Category;
@@ -160,17 +161,15 @@ public class CallSuperDetector extends Detector implements Detector.JavaScanner
ResolvedMethod directSuper = method.getSuperMethod();
ResolvedMethod superMethod = directSuper;
while (superMethod != null) {
- Iterable<JavaParser.ResolvedAnnotation> annotations = superMethod.getAnnotations();
- for (JavaParser.ResolvedAnnotation annotation : annotations) {
- annotation = SupportAnnotationDetector.getRelevantAnnotation(annotation);
- if (annotation != null) {
- String signature = annotation.getSignature();
- if (CALL_SUPER_ANNOTATION.equals(signature)) {
- return directSuper;
- } else if (signature.endsWith(".OverrideMustInvoke")) {
- // Handle findbugs annotation on the fly too
- return directSuper;
- }
+ Iterable<ResolvedAnnotation> annotations = superMethod.getAnnotations();
+ annotations = filterRelevantAnnotations(annotations);
+ for (ResolvedAnnotation annotation : annotations) {
+ String signature = annotation.getSignature();
+ if (CALL_SUPER_ANNOTATION.equals(signature)) {
+ return directSuper;
+ } else if (signature.endsWith(".OverrideMustInvoke")) {
+ // Handle findbugs annotation on the fly too
+ return directSuper;
}
}
superMethod = superMethod.getSuperMethod();
diff --git a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SupportAnnotationDetector.java b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SupportAnnotationDetector.java
index 7cd63f912a..d34759a2ae 100644
--- a/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SupportAnnotationDetector.java
+++ b/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/SupportAnnotationDetector.java
@@ -70,6 +70,7 @@ import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import java.io.File;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
@@ -305,13 +306,14 @@ public class SupportAnnotationDetector extends Detector implements Detector.Java
@NonNull Node argument,
@NonNull Node call,
@NonNull ResolvedMethod method,
- @NonNull ResolvedAnnotation annotation) {
+ @NonNull ResolvedAnnotation annotation,
+ @NonNull Iterable<ResolvedAnnotation> allAnnotations) {
String signature = annotation.getSignature();
if (COLOR_INT_ANNOTATION.equals(signature)) {
checkColor(context, argument);
} else if (signature.equals(INT_RANGE_ANNOTATION)) {
- checkIntRange(context, annotation, argument);
+ checkIntRange(context, annotation, argument, allAnnotations);
} else if (signature.equals(FLOAT_RANGE_ANNOTATION)) {
checkFloatRange(context, annotation, argument);
} else if (signature.equals(SIZE_ANNOTATION)) {
@@ -330,9 +332,9 @@ public class SupportAnnotationDetector extends Detector implements Detector.Java
// have to
if (signature.equals(INT_DEF_ANNOTATION)) {
boolean flag = annotation.getValue(TYPE_DEF_FLAG_ATTRIBUTE) == Boolean.TRUE;
- checkTypeDefConstant(context, annotation, argument, null, flag);
+ checkTypeDefConstant(context, annotation, argument, null, flag, allAnnotations);
} else if (signature.equals(STRING_DEF_ANNOTATION)) {
- checkTypeDefConstant(context, annotation, argument, null, false);
+ checkTypeDefConstant(context, annotation, argument, null, false, allAnnotations);
} else if (signature.endsWith(RES_SUFFIX)) {
String typeString = signature.substring(SUPPORT_ANNOTATIONS_PREFIX.length(),
signature.length() - RES_SUFFIX.length()).toLowerCase(Locale.US);
@@ -945,19 +947,35 @@ public class SupportAnnotationDetector extends Detector implements Detector.Java
private static void checkIntRange(
@NonNull JavaContext context,
@NonNull ResolvedAnnotation annotation,
+ @NonNull Node argument,
+ @NonNull Iterable<ResolvedAnnotation> allAnnotations) {
+ String message = getIntRangeError(context, annotation, argument);
+ if (message != null) {
+ if (findIntDef(allAnnotations) != null) {
+ // Don't flag int range errors if there is an int def annotation there too;
+ // there could be a valid @IntDef constant. (The @IntDef check will
+ // perform range validation by calling getIntRange.)
+ return;
+ }
+
+ context.report(RANGE, argument, context.getLocation(argument), message);
+ }
+ }
+
+ @Nullable
+ private static String getIntRangeError(
+ @NonNull JavaContext context,
+ @NonNull ResolvedAnnotation annotation,
@NonNull Node argument) {
Object object = ConstantEvaluator.evaluate(context, argument);
if (!(object instanceof Number)) {
- return;
+ return null;
}
long value = ((Number)object).longValue();
long from = getLongAttribute(annotation, ATTR_FROM, Long.MIN_VALUE);
long to = getLongAttribute(annotation, ATTR_TO, Long.MAX_VALUE);
- String message = getIntRangeError(value, from, to);
- if (message != null) {
- context.report(RANGE, argument, context.getLocation(argument), message);
- }
+ return getIntRangeError(value, from, to);
}
/**
@@ -1148,12 +1166,37 @@ public class SupportAnnotationDetector extends Detector implements Detector.Java
return message;
}
+ @Nullable
+ private static ResolvedAnnotation findIntRange(
+ @NonNull Iterable<ResolvedAnnotation> annotations) {
+ for (ResolvedAnnotation annotation : annotations) {
+ if (INT_RANGE_ANNOTATION.equals(annotation.getName())) {
+ return annotation;
+ }
+ }
+
+ return null;
+ }
+
+ @Nullable
+ private static ResolvedAnnotation findIntDef(
+ @NonNull Iterable<ResolvedAnnotation> annotations) {
+ for (ResolvedAnnotation annotation : annotations) {
+ if (INT_DEF_ANNOTATION.equals(annotation.getName())) {
+ return annotation;
+ }
+ }
+
+ return null;
+ }
+
private static void checkTypeDefConstant(
@NonNull JavaContext context,
@NonNull ResolvedAnnotation annotation,
@NonNull Node argument,
@Nullable Node errorNode,
- boolean flag) {
+ boolean flag,
+ @NonNull Iterable<ResolvedAnnotation> allAnnotations) {
if (argument instanceof NullLiteral) {
// Accepted for @StringDef
return;
@@ -1161,7 +1204,8 @@ public class SupportAnnotationDetector extends Detector implements Detector.Java
if (argument instanceof StringLiteral) {
StringLiteral string = (StringLiteral) argument;
- checkTypeDefConstant(context, annotation, argument, errorNode, false, string.astValue());
+ checkTypeDefConstant(context, annotation, argument, errorNode, false, string.astValue(),
+ allAnnotations);
} else if (argument instanceof IntegralLiteral) {
IntegralLiteral literal = (IntegralLiteral) argument;
int value = literal.astIntValue();
@@ -1169,35 +1213,68 @@ public class SupportAnnotationDetector extends Detector implements Detector.Java
// Accepted for a flag @IntDef
return;
}
- checkTypeDefConstant(context, annotation, argument, errorNode, flag, value);
+
+ ResolvedAnnotation rangeAnnotation = findIntRange(allAnnotations);
+ if (rangeAnnotation != null) {
+ // Allow @IntRange on this number
+ if (getIntRangeError(context, rangeAnnotation, literal) == null) {
+ return;
+ }
+ }
+
+ checkTypeDefConstant(context, annotation, argument, errorNode, flag, value,
+ allAnnotations);
} else if (isMinusOne(argument)) {
// -1 is accepted unconditionally for flags
if (!flag) {
- reportTypeDef(context, annotation, argument, errorNode);
+ ResolvedAnnotation rangeAnnotation = findIntRange(allAnnotations);
+ if (rangeAnnotation != null) {
+ // Allow @IntRange on this number
+ if (getIntRangeError(context, rangeAnnotation, argument) == null) {
+ return;
+ }
+ }
+
+ reportTypeDef(context, annotation, argument, errorNode, allAnnotations);
}
} else if (argument instanceof InlineIfExpression) {
InlineIfExpression expression = (InlineIfExpression) argument;
if (expression.astIfTrue() != null) {
- checkTypeDefConstant(context, annotation, expression.astIfTrue(), errorNode, flag);
+ checkTypeDefConstant(context, annotation, expression.astIfTrue(), errorNode, flag,
+ allAnnotations);
}
if (expression.astIfFalse() != null) {
- checkTypeDefConstant(context, annotation, expression.astIfFalse(), errorNode, flag);
+ checkTypeDefConstant(context, annotation, expression.astIfFalse(), errorNode, flag,
+ allAnnotations);
}
} else if (argument instanceof UnaryExpression) {
UnaryExpression expression = (UnaryExpression) argument;
UnaryOperator operator = expression.astOperator();
if (flag) {
- checkTypeDefConstant(context, annotation, expression.astOperand(), errorNode, true);
+ checkTypeDefConstant(context, annotation, expression.astOperand(), errorNode, true,
+ allAnnotations);
} else if (operator == UnaryOperator.BINARY_NOT) {
context.report(TYPE_DEF, expression, context.getLocation(expression),
"Flag not allowed here");
+ } else if (operator == UnaryOperator.UNARY_MINUS) {
+ ResolvedAnnotation rangeAnnotation = findIntRange(allAnnotations);
+ if (rangeAnnotation != null) {
+ // Allow @IntRange on this number
+ if (getIntRangeError(context, rangeAnnotation, argument) == null) {
+ return;
+ }
+ }
+
+ reportTypeDef(context, annotation, argument, errorNode, allAnnotations);
}
} else if (argument instanceof BinaryExpression) {
// If it's ?: then check both the if and else clauses
BinaryExpression expression = (BinaryExpression) argument;
if (flag) {
- checkTypeDefConstant(context, annotation, expression.astLeft(), errorNode, true);
- checkTypeDefConstant(context, annotation, expression.astRight(), errorNode, true);
+ checkTypeDefConstant(context, annotation, expression.astLeft(), errorNode, true,
+ allAnnotations);
+ checkTypeDefConstant(context, annotation, expression.astRight(), errorNode, true,
+ allAnnotations);
} else {
BinaryOperator operator = expression.astOperator();
if (operator == BinaryOperator.BITWISE_AND
@@ -1210,7 +1287,8 @@ public class SupportAnnotationDetector extends Detector implements Detector.Java
} else {
ResolvedNode resolved = context.resolve(argument);
if (resolved instanceof ResolvedField) {
- checkTypeDefConstant(context, annotation, argument, errorNode, flag, resolved);
+ checkTypeDefConstant(context, annotation, argument, errorNode, flag, resolved,
+ allAnnotations);
} else if (argument instanceof VariableReference) {
Statement statement = getParentOfType(argument, Statement.class, false);
if (statement != null) {
@@ -1236,7 +1314,8 @@ public class SupportAnnotationDetector extends Detector implements Detector.Java
&& entry.astName().astValue().equals(targetName)) {
checkTypeDefConstant(context, annotation,
entry.astInitializer(),
- errorNode != null ? errorNode : argument, flag);
+ errorNode != null ? errorNode : argument, flag,
+ allAnnotations);
return;
}
}
@@ -1250,7 +1329,8 @@ public class SupportAnnotationDetector extends Detector implements Detector.Java
if (targetName.equals(binaryExpression.astLeft().toString())) {
checkTypeDefConstant(context, annotation,
binaryExpression.astRight(),
- errorNode != null ? errorNode : argument, flag);
+ errorNode != null ? errorNode : argument, flag,
+ allAnnotations);
return;
}
}
@@ -1263,7 +1343,8 @@ public class SupportAnnotationDetector extends Detector implements Detector.Java
private static void checkTypeDefConstant(@NonNull JavaContext context,
@NonNull ResolvedAnnotation annotation, @NonNull Node argument,
- @Nullable Node errorNode, boolean flag, Object value) {
+ @Nullable Node errorNode, boolean flag, Object value,
+ @NonNull Iterable<ResolvedAnnotation> allAnnotations) {
Object allowed = annotation.getValue();
if (allowed instanceof Object[]) {
Object[] allowedValues = (Object[]) allowed;
@@ -1272,22 +1353,23 @@ public class SupportAnnotationDetector extends Detector implements Detector.Java
return;
}
}
- reportTypeDef(context, argument, errorNode, flag, allowedValues);
+ reportTypeDef(context, argument, errorNode, flag, allowedValues, allAnnotations);
}
}
private static void reportTypeDef(@NonNull JavaContext context,
@NonNull ResolvedAnnotation annotation, @NonNull Node argument,
- @Nullable Node errorNode) {
+ @Nullable Node errorNode, @NonNull Iterable<ResolvedAnnotation> allAnnotations) {
Object allowed = annotation.getValue();
if (allowed instanceof Object[]) {
Object[] allowedValues = (Object[]) allowed;
- reportTypeDef(context, argument, errorNode, false, allowedValues);
+ reportTypeDef(context, argument, errorNode, false, allowedValues, allAnnotations);
}
}
private static void reportTypeDef(@NonNull JavaContext context, @NonNull Node node,
- @Nullable Node errorNode, boolean flag, @NonNull Object[] allowedValues) {
+ @Nullable Node errorNode, boolean flag, @NonNull Object[] allowedValues,
+ @NonNull Iterable<ResolvedAnnotation> allAnnotations) {
String values = listAllowedValues(allowedValues);
String message;
if (flag) {
@@ -1295,6 +1377,17 @@ public class SupportAnnotationDetector extends Detector implements Detector.Java
} else {
message = "Must be one of: " + values;
}
+
+ ResolvedAnnotation rangeAnnotation = findIntRange(allAnnotations);
+ if (rangeAnnotation != null) {
+ // Allow @IntRange on this number
+ String rangeError = getIntRangeError(context, rangeAnnotation, node);
+ if (rangeError != null && !rangeError.isEmpty()) {
+ message += " or " + Character.toLowerCase(rangeError.charAt(0))
+ + rangeError.substring(1);
+ }
+ }
+
if (errorNode == null) {
errorNode = node;
}
@@ -1358,42 +1451,66 @@ public class SupportAnnotationDetector extends Detector implements Detector.Java
return defaultValue;
}
- @Nullable
- static ResolvedAnnotation getRelevantAnnotation(@NonNull ResolvedAnnotation annotation) {
- String signature = annotation.getSignature();
- if (signature.startsWith(SUPPORT_ANNOTATIONS_PREFIX)) {
- // Bail on the nullness annotations early since they're the most commonly
- // defined ones. They're not analyzed in lint yet.
- if (signature.endsWith(".Nullable") || signature.endsWith(".NonNull")) {
- return null;
+ @NonNull
+ static Iterable<ResolvedAnnotation> filterRelevantAnnotations(
+ @NonNull Iterable<ResolvedAnnotation> annotations) {
+ List<ResolvedAnnotation> result = null;
+ Iterator<ResolvedAnnotation> iterator = annotations.iterator();
+ int index = 0;
+ while (iterator.hasNext()) {
+ ResolvedAnnotation annotation = iterator.next();
+ index++;
+
+ String signature = annotation.getSignature();
+ if (signature.startsWith("java.")) {
+ // @Override, @SuppressWarnings etc. Ignore
+ continue;
}
+ if (signature.startsWith(SUPPORT_ANNOTATIONS_PREFIX)) {
+ // Bail on the nullness annotations early since they're the most commonly
+ // defined ones. They're not analyzed in lint yet.
+ if (signature.endsWith(".Nullable") || signature.endsWith(".NonNull")) {
+ continue;
+ }
- return annotation;
- }
-
- if (signature.startsWith("java.")) {
- // @Override, @SuppressWarnings etc. Ignore
- return null;
- }
+ // Common case: there's just one annotation; no need to create a list copy
+ if (!iterator.hasNext() && index == 1) {
+ return annotations;
+ }
+ if (result == null) {
+ result = new ArrayList<ResolvedAnnotation>(2);
+ }
+ result.add(annotation);
+ }
- // Special case @IntDef and @StringDef: These are used on annotations
- // themselves. For example, you create a new annotation named @foo.bar.Baz,
- // annotate it with @IntDef, and then use @foo.bar.Baz in your signatures.
- // Here we want to map from @foo.bar.Baz to the corresponding int def.
- // Don't need to compute this if performing @IntDef or @StringDef lookup
- ResolvedClass type = annotation.getClassType();
- if (type != null) {
- for (ResolvedAnnotation inner : type.getAnnotations()) {
- if (inner.matches(INT_DEF_ANNOTATION)
- || inner.matches(STRING_DEF_ANNOTATION)
- || inner.matches(PERMISSION_ANNOTATION)) {
- return inner;
+ // Special case @IntDef and @StringDef: These are used on annotations
+ // themselves. For example, you create a new annotation named @foo.bar.Baz,
+ // annotate it with @IntDef, and then use @foo.bar.Baz in your signatures.
+ // Here we want to map from @foo.bar.Baz to the corresponding int def.
+ // Don't need to compute this if performing @IntDef or @StringDef lookup
+ ResolvedClass type = annotation.getClassType();
+ if (type != null) {
+ Iterator<ResolvedAnnotation> iterator2 = type.getAnnotations().iterator();
+ while (iterator2.hasNext()) {
+ ResolvedAnnotation inner = iterator2.next();
+ if (inner.matches(INT_DEF_ANNOTATION)
+ || inner.matches(PERMISSION_ANNOTATION)
+ || inner.matches(INT_RANGE_ANNOTATION)
+ || inner.matches(STRING_DEF_ANNOTATION)) {
+ if (!iterator.hasNext() && !iterator2.hasNext() && index == 1) {
+ return annotations;
+ }
+ if (result == null) {
+ result = new ArrayList<ResolvedAnnotation>(2);
+ }
+ result.add(inner);
+ }
}
}
}
- return null;
+ return result != null ? result : Collections.<ResolvedAnnotation>emptyList();
}
// ---- Implements JavaScanner ----
@@ -1445,7 +1562,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Java
@Override
public boolean visitEnumConstant(EnumConstant node) {
- final ResolvedNode resolved = mContext.resolve(node);
+ ResolvedNode resolved = mContext.resolve(node);
if (resolved instanceof ResolvedMethod) {
ResolvedMethod method = (ResolvedMethod) resolved;
checkCall(node, method);
@@ -1456,22 +1573,18 @@ public class SupportAnnotationDetector extends Detector implements Detector.Java
private void checkCall(@NonNull Node call, ResolvedMethod method) {
Iterable<ResolvedAnnotation> annotations = method.getAnnotations();
+ annotations = filterRelevantAnnotations(annotations);
for (ResolvedAnnotation annotation : annotations) {
- annotation = getRelevantAnnotation(annotation);
- if (annotation != null) {
- checkMethodAnnotation(mContext, method, call, annotation);
- }
+ checkMethodAnnotation(mContext, method, call, annotation);
}
// Look for annotations on the class as well: these trickle
// down to all the methods in the class
ResolvedClass containingClass = method.getContainingClass();
annotations = containingClass.getAnnotations();
+ annotations = filterRelevantAnnotations(annotations);
for (ResolvedAnnotation annotation : annotations) {
- annotation = getRelevantAnnotation(annotation);
- if (annotation != null) {
- checkMethodAnnotation(mContext, method, call, annotation);
- }
+ checkMethodAnnotation(mContext, method, call, annotation);
}
Iterator<Expression> arguments = JavaContext.getParameters(call);
@@ -1481,11 +1594,10 @@ public class SupportAnnotationDetector extends Detector implements Detector.Java
Expression argument = arguments.next();
annotations = method.getParameterAnnotations(i);
+ annotations = filterRelevantAnnotations(annotations);
for (ResolvedAnnotation annotation : annotations) {
- annotation = getRelevantAnnotation(annotation);
- if (annotation != null) {
- checkParameterAnnotation(mContext, argument, call, method, annotation);
- }
+ checkParameterAnnotation(mContext, argument, call, method, annotation,
+ annotations);
}
}
}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SupportAnnotationDetectorTest.java b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SupportAnnotationDetectorTest.java
index 04fda15869..518566ac15 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SupportAnnotationDetectorTest.java
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/SupportAnnotationDetectorTest.java
@@ -149,8 +149,8 @@ public class SupportAnnotationDetectorTest extends AbstractCheckTest {
+ " printBetween(-7); // ERROR\n"
+ " ~~\n"
+ "src/test/pkg/RangeTest.java:146: Error: Value must be > 2.5 (was -10.0) [Range]\n"
- + " printAtLeastExclusive(-10.0); // ERROR\n"
- + " ~~~~~\n"
+ + " printAtLeastExclusive(-10.0f); // ERROR\n"
+ + " ~~~~~~\n"
+ "src/test/pkg/RangeTest.java:156: Error: Value must be ≥ -1 (was -2) [Range]\n"
+ " printIndirect(-2); // ERROR\n"
+ " ~~\n"
@@ -268,8 +268,9 @@ public class SupportAnnotationDetectorTest extends AbstractCheckTest {
+ (SDK_ANNOTATIONS_AVAILABLE ? "7 errors, 0 warnings\n" : "2 errors, 0 warnings\n"),
lintProject(
- "src/test/pkg/WrongColor.java.txt=>src/test/pkg/WrongColor.java",
- "src/android/support/annotation/ColorInt.java.txt=>src/android/support/annotation/ColorInt.java"
+ copy("src/test/pkg/WrongColor.java.txt", "src/test/pkg/WrongColor.java"),
+ copy("src/android/support/annotation/ColorInt.java.txt", "src/android/support/annotation/ColorInt.java"),
+ mColorResAnnotation
));
}
@@ -362,8 +363,11 @@ public class SupportAnnotationDetectorTest extends AbstractCheckTest {
+ " resources.getDrawable(R.string.my_string); // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~~\n" : "")
+ "src/p1/p2/Flow.java:22: Error: Expected resource of type drawable [ResourceType]\n"
- + " myMethod(R.string.my_string); // ERROR\n"
+ + " myMethod(R.string.my_string, null); // ERROR\n"
+ " ~~~~~~~~~~~~~~~~~~\n"
+ + "src/p1/p2/Flow.java:26: Error: Expected resource of type drawable [ResourceType]\n"
+ + " resources.getDrawable(R.string.my_string); // ERROR\n"
+ + " ~~~~~~~~~~~~~~~~~~\n"
+ "src/p1/p2/Flow.java:32: Error: Expected resource identifier (R.type.name) [ResourceType]\n"
+ " myAnyResMethod(50); // ERROR\n"
+ " ~~\n"
@@ -371,12 +375,17 @@ public class SupportAnnotationDetectorTest extends AbstractCheckTest {
+ " resources.getDrawable(MimeTypes.getAnnotatedString()); // Error\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" : "")
+ "src/p1/p2/Flow.java:68: Error: Expected resource of type drawable [ResourceType]\n"
- + " myMethod(z); // ERROR\n"
+ + " myMethod(z, null); // ERROR\n"
+ " ~\n"
- + (SDK_ANNOTATIONS_AVAILABLE ? "6 errors, 0 warnings\n" : "3 errors, 0 warnings\n"),
+ + (SDK_ANNOTATIONS_AVAILABLE ? "7 errors, 0 warnings\n" : "4 errors, 0 warnings\n"),
- lintProject("src/p1/p2/Flow.java.txt=>src/p1/p2/Flow.java",
- "src/android/support/annotation/DrawableRes.java.txt=>src/android/support/annotation/DrawableRes.java"));
+ lintProject(
+ copy("src/p1/p2/Flow.java.txt", "src/p1/p2/Flow.java"),
+ copy("src/android/support/annotation/DrawableRes.java.txt", "src/android/support/annotation/DrawableRes.java"),
+ mStringResAnnotation,
+ mStyleResAnnotation,
+ mAnyResAnnotation
+ ));
}
public void testTypes2() throws Exception {
@@ -515,12 +524,12 @@ public class SupportAnnotationDetectorTest extends AbstractCheckTest {
+ "\n"
+ "import static android.Manifest.permission.ACCESS_COARSE_LOCATION;\n"
+ "import static android.Manifest.permission.ACCESS_FINE_LOCATION;\n"
- + "import static android.Manifest.permission.ADD_VOICEMAIL;\n"
- + "import static android.Manifest.permission.AUTHENTICATE_ACCOUNTS;\n"
+ + "import static android.Manifest.permission.BLUETOOTH;\n"
+ + "import static android.Manifest.permission.READ_SMS;\n"
+ "\n"
+ "@SuppressWarnings(\"UnusedDeclaration\")\n"
+ "public abstract class LocationManager {\n"
- + " @RequiresPermission(\"(\" + ACCESS_FINE_LOCATION + \"|| \" + ACCESS_COARSE_LOCATION + \") && (\" + ADD_VOICEMAIL + \" ^ \" + AUTHENTICATE_ACCOUNTS + \")\")\n"
+ + " @RequiresPermission(\"(\" + ACCESS_FINE_LOCATION + \"|| \" + ACCESS_COARSE_LOCATION + \") && (\" + BLUETOOTH + \" ^ \" + READ_SMS + \")\")\n"
+ " public abstract Location myMethod(String provider);\n"
+ " public static class Location {\n"
+ " }\n"
@@ -550,7 +559,7 @@ public class SupportAnnotationDetectorTest extends AbstractCheckTest {
+ "import static java.lang.annotation.ElementType.*;\n"
+ "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
+ "@Retention(CLASS)\n"
- + "@Target({METHOD,CONSTRUCTOR,FIELD,PARAMETER})\n"
+ + "@Target({METHOD,CONSTRUCTOR,FIELD,PARAMETER,ANNOTATION_TYPE})\n"
+ "public @interface RequiresPermission {\n"
+ " String value() default \"\";\n"
+ " String[] allOf() default {};\n"
@@ -615,19 +624,26 @@ public class SupportAnnotationDetectorTest extends AbstractCheckTest {
+ "public @interface WorkerThread {\n"
+ "}\n");
- private final TestFile mColorResAnnotation = java("src/android/support/annotation/ColorRes.java", ""
- + "package android.support.annotation;\n"
- + "\n"
- + "import java.lang.annotation.Retention;\n"
- + "import java.lang.annotation.Target;\n"
- + "\n"
- + "import static java.lang.annotation.ElementType.*;\n"
- + "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
- + "\n"
- + "@Retention(CLASS)\n"
- + "@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})\n"
- + "public @interface ColorRes {\n"
- + "}\n");
+ private TestFile createResAnnotation(String prefix) {
+ return java("src/android/support/annotation/" + prefix + "Res.java", ""
+ + "package android.support.annotation;\n"
+ + "\n"
+ + "import java.lang.annotation.Retention;\n"
+ + "import java.lang.annotation.Target;\n"
+ + "\n"
+ + "import static java.lang.annotation.ElementType.*;\n"
+ + "import static java.lang.annotation.RetentionPolicy.CLASS;\n"
+ + "\n"
+ + "@Retention(CLASS)\n"
+ + "@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})\n"
+ + "public @interface " + prefix + "Res {\n"
+ + "}\n");
+ }
+
+ private final TestFile mColorResAnnotation = createResAnnotation("Color");
+ private final TestFile mStringResAnnotation = createResAnnotation("String");
+ private final TestFile mStyleResAnnotation = createResAnnotation("Style");
+ private final TestFile mAnyResAnnotation = createResAnnotation("Any");
private final TestFile mColorIntAnnotation = java("src/android/support/annotation/ColorInt.java", ""
+ "package android.support.annotation;\n"
@@ -805,7 +821,7 @@ public class SupportAnnotationDetectorTest extends AbstractCheckTest {
// Regression test for
// https://code.google.com/p/android/issues/detail?id=177381
assertEquals(""
- + "src/test/pkg/PermissionTest2.java:11: Error: Missing permissions required by X.method1: my.permission.PERM2 [MissingPermission]\n"
+ + "src/test/pkg/PermissionTest2.java:11: Error: Missing permissions required by PermissionTest2.method1: my.permission.PERM2 [MissingPermission]\n"
+ " method1(); // ERROR\n"
+ " ~~~~~~~~~\n"
+ "1 errors, 0 warnings\n",
@@ -815,7 +831,7 @@ public class SupportAnnotationDetectorTest extends AbstractCheckTest {
+ "package test.pkg;\n"
+ "import android.support.annotation.RequiresPermission;\n"
+ "\n"
- + "public class X {\n"
+ + "public class PermissionTest2 {\n"
+ " @RequiresPermission(allOf = {\"my.permission.PERM1\",\"my.permission.PERM2\"})\n"
+ " public void method1() {\n"
+ " }\n"
@@ -849,7 +865,7 @@ public class SupportAnnotationDetectorTest extends AbstractCheckTest {
public void testComplexPermission1() throws Exception {
assertEquals(""
- + "src/test/pkg/PermissionTest.java:7: Error: Missing permissions required by LocationManager.myMethod: com.android.voicemail.permission.ADD_VOICEMAIL xor android.permission.AUTHENTICATE_ACCOUNTS [MissingPermission]\n"
+ + "src/test/pkg/PermissionTest.java:7: Error: Missing permissions required by LocationManager.myMethod: android.permission.BLUETOOTH xor android.permission.READ_SMS [MissingPermission]\n"
+ " LocationManager.Location location = locationManager.myMethod(provider);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "1 errors, 0 warnings\n",
@@ -866,7 +882,7 @@ public class SupportAnnotationDetectorTest extends AbstractCheckTest {
lintProject(
getManifestWithPermissions(14,
"android.permission.ACCESS_FINE_LOCATION",
- "com.android.voicemail.permission.ADD_VOICEMAIL"),
+ "android.permission.BLUETOOTH"),
mPermissionTest,
mComplexLocationManagerStub,
mRequirePermissionAnnotation));
@@ -874,15 +890,15 @@ public class SupportAnnotationDetectorTest extends AbstractCheckTest {
public void testComplexPermission3() throws Exception {
assertEquals(""
- + "src/test/pkg/PermissionTest.java:7: Error: Missing permissions required by LocationManager.myMethod: com.android.voicemail.permission.ADD_VOICEMAIL xor android.permission.AUTHENTICATE_ACCOUNTS [MissingPermission]\n"
+ + "src/test/pkg/PermissionTest.java:7: Error: Missing permissions required by LocationManager.myMethod: android.permission.BLUETOOTH xor android.permission.READ_SMS [MissingPermission]\n"
+ " LocationManager.Location location = locationManager.myMethod(provider);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "1 errors, 0 warnings\n",
lintProject(
getManifestWithPermissions(14,
"android.permission.ACCESS_FINE_LOCATION",
- "com.android.voicemail.permission.ADD_VOICEMAIL",
- "android.permission.AUTHENTICATE_ACCOUNTS"),
+ "android.permission.BLUETOOTH",
+ "android.permission.READ_SMS"),
mPermissionTest,
mComplexLocationManagerStub,
mRequirePermissionAnnotation));
@@ -1078,9 +1094,6 @@ public class SupportAnnotationDetectorTest extends AbstractCheckTest {
+ "src/test/pkg/ActionTest.java:67: Error: Missing permissions required to write ActionTest.BOOKMARKS_URI: com.android.browser.permission.WRITE_HISTORY_BOOKMARKS [MissingPermission]\n"
+ " resolver.update(BOOKMARKS_URI, null, null, null);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "src/test/pkg/ActionTest.java:70: Error: Missing permissions required to read Browser.BOOKMARKS_URI: com.android.browser.permission.READ_HISTORY_BOOKMARKS [MissingPermission]\n"
- + " resolver.query(android.provider.Browser.BOOKMARKS_URI, null, null, null, null);\n"
- + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
+ "src/test/pkg/ActionTest.java:86: Error: Missing permissions required by intent ActionTest.ACTION_CALL: android.permission.CALL_PHONE [MissingPermission]\n"
+ " myStartActivity(\"\", null, new Intent(ACTION_CALL));\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
@@ -1090,7 +1103,7 @@ public class SupportAnnotationDetectorTest extends AbstractCheckTest {
+ "src/test/pkg/ActionTest.java:88: Error: Missing permissions required to read ActionTest.BOOKMARKS_URI: com.android.browser.permission.READ_HISTORY_BOOKMARKS [MissingPermission]\n"
+ " myWriteResolverMethod(BOOKMARKS_URI);\n"
+ " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"
- + "20 errors, 0 warnings\n",
+ + "19 errors, 0 warnings\n",
lintProject(
getManifestWithPermissions(14, 23),
@@ -1106,11 +1119,13 @@ public class SupportAnnotationDetectorTest extends AbstractCheckTest {
+ "import android.net.Uri;\n"
+ "import android.support.annotation.RequiresPermission;\n"
+ "\n"
- + "import static android.Manifest.permission.READ_HISTORY_BOOKMARKS;\n"
- + "import static android.Manifest.permission.WRITE_HISTORY_BOOKMARKS;\n"
+ //+ "import static android.Manifest.permission.READ_HISTORY_BOOKMARKS;\n"
+ //+ "import static android.Manifest.permission.WRITE_HISTORY_BOOKMARKS;\n"
+ "\n"
+ "@SuppressWarnings({\"deprecation\", \"unused\"})\n"
+ "public class ActionTest {\n"
+ + " public static final String READ_HISTORY_BOOKMARKS=\"com.android.browser.permission.READ_HISTORY_BOOKMARKS\";\n"
+ + " public static final String WRITE_HISTORY_BOOKMARKS=\"com.android.browser.permission.WRITE_HISTORY_BOOKMARKS\";\n"
+ " @RequiresPermission(Manifest.permission.CALL_PHONE)\n"
+ " public static final String ACTION_CALL = \"android.intent.action.CALL\";\n"
+ "\n"
@@ -1164,7 +1179,7 @@ public class SupportAnnotationDetectorTest extends AbstractCheckTest {
+ " resolver.update(BOOKMARKS_URI, null, null, null);\n"
+ "\n"
+ " // Framework (external) annotation\n"
- + " resolver.query(android.provider.Browser.BOOKMARKS_URI, null, null, null, null);\n"
+ + "//REMOVED resolver.query(android.provider.Browser.BOOKMARKS_URI, null, null, null, null);\n"
+ "\n"
+ " // TODO: Look for more complex URI manipulations\n"
+ " }\n"
@@ -1173,10 +1188,10 @@ public class SupportAnnotationDetectorTest extends AbstractCheckTest {
+ " @RequiresPermission Intent intent) {\n"
+ " }\n"
+ "\n"
- + " public static void myReadResolverMethod(String s1, @RequiresPermission.Read Uri uri) {\n"
+ + " public static void myReadResolverMethod(String s1, @RequiresPermission.Read(@RequiresPermission) Uri uri) {\n"
+ " }\n"
+ "\n"
- + " public static void myWriteResolverMethod(@RequiresPermission.Read Uri uri) {\n"
+ + " public static void myWriteResolverMethod(@RequiresPermission.Read(@RequiresPermission) Uri uri) {\n"
+ " }\n"
+ " \n"
+ " public static void testCustomMethods() {\n"
@@ -1188,4 +1203,61 @@ public class SupportAnnotationDetectorTest extends AbstractCheckTest {
mRequirePermissionAnnotation
));
}
+
+ public void testCombinedIntDefAndIntRange() throws Exception {
+ assertEquals(""
+ + "src/test/pkg/X.java:27: Error: Must be one of: X.LENGTH_INDEFINITE, X.LENGTH_SHORT, X.LENGTH_LONG [WrongConstant]\n"
+ + " setDuration(UNRELATED); /// ERROR: Not right intdef, even if it's in the right number range\n"
+ + " ~~~~~~~~~\n"
+ + "src/test/pkg/X.java:28: Error: Must be one of: X.LENGTH_INDEFINITE, X.LENGTH_SHORT, X.LENGTH_LONG or value must be ≥ 10 (was -5) [WrongConstant]\n"
+ + " setDuration(-5); // ERROR (not right int def or value\n"
+ + " ~~\n"
+ + "src/test/pkg/X.java:29: Error: Must be one of: X.LENGTH_INDEFINITE, X.LENGTH_SHORT, X.LENGTH_LONG or value must be ≥ 10 (was 8) [WrongConstant]\n"
+ + " setDuration(8); // ERROR (not matching number range)\n"
+ + " ~\n"
+ + "3 errors, 0 warnings\n",
+ lintProject(
+ getManifestWithPermissions(14, 23),
+ java("src/test/pkg/X.java", ""
+ + "\n"
+ + "package test.pkg;\n"
+ + "\n"
+ + "import android.support.annotation.IntDef;\n"
+ + "import android.support.annotation.IntRange;\n"
+ + "\n"
+ + "import java.lang.annotation.Retention;\n"
+ + "import java.lang.annotation.RetentionPolicy;\n"
+ + "\n"
+ + "@SuppressWarnings({\"UnusedParameters\", \"unused\", \"SpellCheckingInspection\"})\n"
+ + "public class X {\n"
+ + "\n"
+ + " public static final int UNRELATED = 500;\n"
+ + "\n"
+ + " @IntDef({LENGTH_INDEFINITE, LENGTH_SHORT, LENGTH_LONG})\n"
+ + " @IntRange(from = 10)\n"
+ + " @Retention(RetentionPolicy.SOURCE)\n"
+ + " public @interface Duration {}\n"
+ + "\n"
+ + " public static final int LENGTH_INDEFINITE = -2;\n"
+ + " public static final int LENGTH_SHORT = -1;\n"
+ + " public static final int LENGTH_LONG = 0;\n"
+ + " public void setDuration(@Duration int duration) {\n"
+ + " }\n"
+ + "\n"
+ + " public void test() {\n"
+ + " setDuration(UNRELATED); /// ERROR: Not right intdef, even if it's in the right number range\n"
+ + " setDuration(-5); // ERROR (not right int def or value\n"
+ + " setDuration(8); // ERROR (not matching number range)\n"
+ + " setDuration(8000); // OK (@IntRange applies)\n"
+ + " setDuration(LENGTH_INDEFINITE); // OK (@IntDef)\n"
+ + " setDuration(LENGTH_LONG); // OK (@IntDef)\n"
+ + " setDuration(LENGTH_SHORT); // OK (@IntDef)\n"
+ + " }\n"
+ + "}\n"),
+ copy("src/android/support/annotation/IntDef.java.txt", "src/android/support/annotation/IntDef.java"),
+ copy("src/android/support/annotation/IntRange.java.txt", "src/android/support/annotation/IntRange.java")
+ ));
+ }
+
+
}
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/IntRange.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/IntRange.java.txt
index 06d1c6b389..133efa84be 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/IntRange.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/android/support/annotation/IntRange.java.txt
@@ -3,6 +3,7 @@ package android.support.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
@@ -11,7 +12,7 @@ import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.CLASS;
@Retention(CLASS)
-@Target({CONSTRUCTOR,METHOD,PARAMETER,FIELD,LOCAL_VARIABLE})
+@Target({CONSTRUCTOR,METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE})
public @interface IntRange {
long from() default Long.MIN_VALUE;
long to() default Long.MAX_VALUE;
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/p1/p2/Flow.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/p1/p2/Flow.java.txt
index dbd5a4797a..8d7318681b 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/p1/p2/Flow.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/p1/p2/Flow.java.txt
@@ -19,10 +19,10 @@ public class Flow {
}
public void testLocalAnnotation() {
- myMethod(R.string.my_string); // ERROR
+ myMethod(R.string.my_string, null); // ERROR
}
- private void myMethod(@DrawableRes int arg) {
+ private void myMethod(@DrawableRes int arg, Resources resources) {
resources.getDrawable(R.string.my_string); // ERROR
}
@@ -65,10 +65,10 @@ public class Flow {
public void testFlow() {
int x = R.string.my_string;
int z = x;
- myMethod(z); // ERROR
+ myMethod(z, null); // ERROR
int w = MY_RESOURCE;
- myMethod(w); // ERROR
+ myMethod(w, null); // ERROR
}
private static final int MY_RESOURCE = R.string.my_string;
diff --git a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/RangeTest.java.txt b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/RangeTest.java.txt
index d4f83bf3e3..40bf5dfd72 100644
--- a/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/RangeTest.java.txt
+++ b/lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/data/src/test/pkg/RangeTest.java.txt
@@ -143,7 +143,7 @@ public class RangeTest {
public void testNegative() {
printBetween(-7); // ERROR
- printAtLeastExclusive(-10.0); // ERROR
+ printAtLeastExclusive(-10.0f); // ERROR
}
public static final int MINIMUM = -1;