diff options
| author | Tor Norbye <tnorbye@google.com> | 2015-08-03 16:47:20 +0000 |
|---|---|---|
| committer | Android Git Automerger <android-git-automerger@android.com> | 2015-08-03 16:47:20 +0000 |
| commit | 02c79dc3042cdce78f303e63fff56280652e34f1 (patch) | |
| tree | 9c52a13cbf09d37abc9a8643d06beff73eb81462 | |
| parent | b59c8a98a338b1db5283c12e7a22f0c70cce8a00 (diff) | |
| parent | 46711741799f9c5ae8a7d0934227d8f216ebebfb (diff) | |
| download | platform_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
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; |
