Lint: Fix diagnostic tests

This commit is contained in:
Yan Zhulanow
2016-10-13 21:38:14 +03:00
committed by Yan Zhulanow
parent 565ca0f7a3
commit 7404b91cb3
13 changed files with 500 additions and 355 deletions
+2 -2
View File
@@ -8,8 +8,8 @@
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="guava" level="project" /> <orderEntry type="library" name="guava" level="project" />
<orderEntry type="module" module-name="uast-common" /> <orderEntry type="module" module-name="uast-common" exported="" />
<orderEntry type="module" module-name="uast-java" /> <orderEntry type="module" module-name="uast-java" exported="" />
<orderEntry type="library" name="android-plugin" level="project" /> <orderEntry type="library" name="android-plugin" level="project" />
<orderEntry type="library" name="intellij-core" level="project" /> <orderEntry type="library" name="intellij-core" level="project" />
<orderEntry type="module" module-name="android-annotations" /> <orderEntry type="module" module-name="android-annotations" />
@@ -18,10 +18,15 @@ package com.android.tools.klint.checks;
import static com.android.SdkConstants.ANDROID_PREFIX; import static com.android.SdkConstants.ANDROID_PREFIX;
import static com.android.SdkConstants.ANDROID_THEME_PREFIX; import static com.android.SdkConstants.ANDROID_THEME_PREFIX;
import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_CLASS; import static com.android.SdkConstants.ATTR_CLASS;
import static com.android.SdkConstants.ATTR_FULL_BACKUP_CONTENT; import static com.android.SdkConstants.ATTR_FULL_BACKUP_CONTENT;
import static com.android.SdkConstants.ATTR_ID;
import static com.android.SdkConstants.ATTR_LABEL_FOR; import static com.android.SdkConstants.ATTR_LABEL_FOR;
import static com.android.SdkConstants.ATTR_LAYOUT_HEIGHT;
import static com.android.SdkConstants.ATTR_LAYOUT_WIDTH;
import static com.android.SdkConstants.ATTR_NAME; import static com.android.SdkConstants.ATTR_NAME;
import static com.android.SdkConstants.ATTR_PADDING_START;
import static com.android.SdkConstants.ATTR_PARENT; import static com.android.SdkConstants.ATTR_PARENT;
import static com.android.SdkConstants.ATTR_TARGET_API; import static com.android.SdkConstants.ATTR_TARGET_API;
import static com.android.SdkConstants.ATTR_TEXT_IS_SELECTABLE; import static com.android.SdkConstants.ATTR_TEXT_IS_SELECTABLE;
@@ -42,26 +47,44 @@ import static com.android.SdkConstants.TOOLS_URI;
import static com.android.SdkConstants.VIEW_TAG; import static com.android.SdkConstants.VIEW_TAG;
import static com.android.tools.klint.detector.api.ClassContext.getFqcn; import static com.android.tools.klint.detector.api.ClassContext.getFqcn;
import static com.android.tools.klint.detector.api.LintUtils.getNextInstruction; import static com.android.tools.klint.detector.api.LintUtils.getNextInstruction;
import static com.android.tools.klint.detector.api.LintUtils.skipParentheses;
import static com.android.tools.klint.detector.api.Location.SearchDirection.BACKWARD; import static com.android.tools.klint.detector.api.Location.SearchDirection.BACKWARD;
import static com.android.tools.klint.detector.api.Location.SearchDirection.EOL_NEAREST;
import static com.android.tools.klint.detector.api.Location.SearchDirection.FORWARD; import static com.android.tools.klint.detector.api.Location.SearchDirection.FORWARD;
import static com.android.tools.klint.detector.api.Location.SearchDirection.NEAREST; import static com.android.tools.klint.detector.api.Location.SearchDirection.NEAREST;
import static com.android.utils.SdkUtils.getResourceFieldName; import static com.android.utils.SdkUtils.getResourceFieldName;
import com.android.SdkConstants;
import com.android.annotations.NonNull; import com.android.annotations.NonNull;
import com.android.annotations.Nullable; import com.android.annotations.Nullable;
import com.android.ide.common.repository.GradleVersion;
import com.android.repository.Revision;
import com.android.repository.api.LocalPackage;
import com.android.resources.ResourceFolderType; import com.android.resources.ResourceFolderType;
import com.android.resources.ResourceType; import com.android.resources.ResourceType;
import com.android.sdklib.AndroidVersion; import com.android.sdklib.AndroidVersion;
import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.SdkVersionInfo; import com.android.sdklib.SdkVersionInfo;
import com.android.tools.klint.client.api.IssueRegistry;
import com.android.tools.klint.client.api.JavaEvaluator; import com.android.tools.klint.client.api.JavaEvaluator;
import com.android.tools.klint.client.api.LintDriver; import com.android.tools.klint.client.api.LintDriver;
import com.android.tools.klint.client.api.UastLintUtils; import com.android.tools.klint.client.api.UastLintUtils;
import com.android.tools.klint.detector.api.*; import com.android.tools.klint.detector.api.Category;
import com.android.tools.klint.detector.api.ClassContext;
import com.android.tools.klint.detector.api.Context;
import com.android.tools.klint.detector.api.DefaultPosition;
import com.android.tools.klint.detector.api.Detector;
import com.android.tools.klint.detector.api.Detector.ClassScanner; import com.android.tools.klint.detector.api.Detector.ClassScanner;
import com.android.tools.klint.detector.api.Implementation;
import com.android.tools.klint.detector.api.Issue;
import com.android.tools.klint.detector.api.JavaContext;
import com.android.tools.klint.detector.api.LintUtils;
import com.android.tools.klint.detector.api.Location;
import com.android.tools.klint.detector.api.Location.SearchHints; import com.android.tools.klint.detector.api.Location.SearchHints;
import com.google.common.collect.Lists; import com.android.tools.klint.detector.api.Position;
import com.android.tools.klint.detector.api.ResourceXmlDetector;
import com.android.tools.klint.detector.api.Scope;
import com.android.tools.klint.detector.api.Severity;
import com.android.tools.klint.detector.api.TextFormat;
import com.android.tools.klint.detector.api.XmlContext;
import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiAnnotationMemberValue; import com.intellij.psi.PsiAnnotationMemberValue;
import com.intellij.psi.PsiAnnotationParameterList; import com.intellij.psi.PsiAnnotationParameterList;
@@ -122,7 +145,6 @@ import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.IntInsnNode; import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.JumpInsnNode; import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.LookupSwitchInsnNode; import org.objectweb.asm.tree.LookupSwitchInsnNode;
@@ -161,6 +183,10 @@ public class ApiDetector extends ResourceXmlDetector
*/ */
private static final boolean CHECK_DECLARATIONS = false; private static final boolean CHECK_DECLARATIONS = false;
private static final String ATTR_WIDTH = "width";
private static final String ATTR_HEIGHT = "height";
private static final String ATTR_SUPPORTS_RTL = "supportsRtl";
private static final boolean AOSP_BUILD = System.getenv("ANDROID_BUILD_TOP") != null; //$NON-NLS-1$ private static final boolean AOSP_BUILD = System.getenv("ANDROID_BUILD_TOP") != null; //$NON-NLS-1$
public static final String REQUIRES_API_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "RequiresApi"; //$NON-NLS-1$ public static final String REQUIRES_API_ANNOTATION = SUPPORT_ANNOTATIONS_PREFIX + "RequiresApi"; //$NON-NLS-1$
@@ -287,6 +313,7 @@ public class ApiDetector extends ResourceXmlDetector
Scope.RESOURCE_FILE_SCOPE)); Scope.RESOURCE_FILE_SCOPE));
private static final String TARGET_API_VMSIG = '/' + TARGET_API + ';'; private static final String TARGET_API_VMSIG = '/' + TARGET_API + ';';
private static final String REQ_API_VMSIG = "/RequiresApi;";
private static final String SWITCH_TABLE_PREFIX = "$SWITCH_TABLE$"; //$NON-NLS-1$ private static final String SWITCH_TABLE_PREFIX = "$SWITCH_TABLE$"; //$NON-NLS-1$
private static final String ORDINAL_METHOD = "ordinal"; //$NON-NLS-1$ private static final String ORDINAL_METHOD = "ordinal"; //$NON-NLS-1$
public static final String ENUM_SWITCH_PREFIX = "$SwitchMap$"; //$NON-NLS-1$ public static final String ENUM_SWITCH_PREFIX = "$SwitchMap$"; //$NON-NLS-1$
@@ -312,6 +339,7 @@ public class ApiDetector extends ResourceXmlDetector
@Override @Override
public void beforeCheckProject(@NonNull Context context) { public void beforeCheckProject(@NonNull Context context) {
if (mApiDatabase == null) { if (mApiDatabase == null) {
//TODO
//mApiDatabase = ApiLookup.get(context.getClient()); //mApiDatabase = ApiLookup.get(context.getClient());
//// We can't look up the minimum API required by the project here: //// We can't look up the minimum API required by the project here:
//// The manifest file hasn't been processed yet in the -before- project hook. //// The manifest file hasn't been processed yet in the -before- project hook.
@@ -400,76 +428,76 @@ public class ApiDetector extends ResourceXmlDetector
} }
int attributeApiLevel = -1; int attributeApiLevel = -1;
//if (ANDROID_URI.equals(attribute.getNamespaceURI())) { if (ANDROID_URI.equals(attribute.getNamespaceURI())) {
// String name = attribute.getLocalName(); String name = attribute.getLocalName();
// if (!(name.equals(ATTR_LAYOUT_WIDTH) && !(name.equals(ATTR_LAYOUT_HEIGHT)) && if (!(name.equals(ATTR_LAYOUT_WIDTH) && !(name.equals(ATTR_LAYOUT_HEIGHT)) &&
// !(name.equals(ATTR_ID)))) { !(name.equals(ATTR_ID)))) {
// String owner = "android/R$attr"; //$NON-NLS-1$ String owner = "android/R$attr"; //$NON-NLS-1$
// attributeApiLevel = mApiDatabase.getFieldVersion(owner, name); attributeApiLevel = mApiDatabase.getFieldVersion(owner, name);
// int minSdk = getMinSdk(context); int minSdk = getMinSdk(context);
// if (attributeApiLevel > minSdk && attributeApiLevel > context.getFolderVersion() if (attributeApiLevel > minSdk && attributeApiLevel > context.getFolderVersion()
// && attributeApiLevel > getLocalMinSdk(attribute.getOwnerElement()) && attributeApiLevel > getLocalMinSdk(attribute.getOwnerElement())
// && !isBenignUnusedAttribute(name) && !isBenignUnusedAttribute(name)
// && !isAlreadyWarnedDrawableFile(context, attribute, attributeApiLevel)) { && !isAlreadyWarnedDrawableFile(context, attribute, attributeApiLevel)) {
// if (RtlDetector.isRtlAttributeName(name) || ATTR_SUPPORTS_RTL.equals(name)) { if (RtlDetector.isRtlAttributeName(name) || ATTR_SUPPORTS_RTL.equals(name)) {
// // No need to warn for example that // No need to warn for example that
// // "layout_alignParentEnd will only be used in API level 17 and higher" // "layout_alignParentEnd will only be used in API level 17 and higher"
// // since we have a dedicated RTL lint rule dealing with those attributes // since we have a dedicated RTL lint rule dealing with those attributes
// However, paddingStart in particular is known to cause crashes
// when used on TextViews (and subclasses of TextViews), on some
// devices, because vendor specific attributes conflict with the
// later-added framework resources, and these are apparently read
// by the text views.
// //
// // However, paddingStart in particular is known to cause crashes // However, as of build tools 23.0.1 aapt works around this by packaging
// // when used on TextViews (and subclasses of TextViews), on some // the resources differently.
// // devices, because vendor specific attributes conflict with the if (name.equals(ATTR_PADDING_START)) {
// // later-added framework resources, and these are apparently read BuildToolInfo buildToolInfo = context.getProject().getBuildTools();
// // by the text views. Revision buildTools = buildToolInfo != null
// // ? buildToolInfo.getRevision() : null;
// // However, as of build tools 23.0.1 aapt works around this by packaging boolean isOldBuildTools = buildTools != null &&
// // the resources differently. (buildTools.getMajor() < 23 || buildTools.getMajor() == 23
// if (name.equals(ATTR_PADDING_START)) { && buildTools.getMinor() == 0 && buildTools.getMicro() == 0);
// BuildToolInfo buildToolInfo = context.getProject().getBuildTools(); if ((buildTools == null || isOldBuildTools) &&
// Revision buildTools = buildToolInfo != null viewMayExtendTextView(attribute.getOwnerElement())) {
// ? buildToolInfo.getRevision() : null; Location location = context.getLocation(attribute);
// boolean isOldBuildTools = buildTools != null && String message = String.format(
// (buildTools.getMajor() < 23 || buildTools.getMajor() == 23 "Attribute `%1$s` referenced here can result in a crash on "
// && buildTools.getMinor() == 0 && buildTools.getMicro() == 0); + "some specific devices older than API %2$d "
// if ((buildTools == null || isOldBuildTools) && + "(current min is %3$d)",
// viewMayExtendTextView(attribute.getOwnerElement())) { attribute.getLocalName(), attributeApiLevel, minSdk);
// Location location = context.getLocation(attribute); //noinspection VariableNotUsedInsideIf
// String message = String.format( if (buildTools != null) {
// "Attribute `%1$s` referenced here can result in a crash on " message = String.format("Upgrade `buildToolsVersion` from "
// + "some specific devices older than API %2$d " + "`%1$s` to at least `23.0.1`; if not, ",
// + "(current min is %3$d)", buildTools.toShortString())
// attribute.getLocalName(), attributeApiLevel, minSdk); + Character.toLowerCase(message.charAt(0))
// //noinspection VariableNotUsedInsideIf + message.substring(1);
// if (buildTools != null) { }
// message = String.format("Upgrade `buildToolsVersion` from " context.report(UNSUPPORTED, attribute, location, message);
// + "`%1$s` to at least `23.0.1`; if not, ", }
// buildTools.toShortString()) }
// + Character.toLowerCase(message.charAt(0)) } else {
// + message.substring(1); Location location = context.getLocation(attribute);
// } String message = String.format(
// context.report(UNSUPPORTED, attribute, location, message); "Attribute `%1$s` is only used in API level %2$d and higher "
// } + "(current min is %3$d)",
// } attribute.getLocalName(), attributeApiLevel, minSdk);
// } else { context.report(UNUSED, attribute, location, message);
// Location location = context.getLocation(attribute); }
// String message = String.format( }
// "Attribute `%1$s` is only used in API level %2$d and higher " }
// + "(current min is %3$d)",
// attribute.getLocalName(), attributeApiLevel, minSdk); // Special case:
// context.report(UNUSED, attribute, location, message); // the dividers attribute is present in API 1, but it won't be read on older
// } // versions, so don't flag the common pattern
// } // android:divider="?android:attr/dividerHorizontal"
// } // since this will work just fine. See issue 67440 for more.
// if (name.equals("divider")) {
// // Special case: return;
// // the dividers attribute is present in API 1, but it won't be read on older }
// // versions, so don't flag the common pattern }
// // android:divider="?android:attr/dividerHorizontal"
// // since this will work just fine. See issue 67440 for more.
// if (name.equals("divider")) {
// return;
// }
//}
String value = attribute.getValue(); String value = attribute.getValue();
String owner = null; String owner = null;
@@ -624,6 +652,7 @@ public class ApiDetector extends ResourceXmlDetector
public static boolean isBenignUnusedAttribute(@NonNull String name) { public static boolean isBenignUnusedAttribute(@NonNull String name) {
return ATTR_LABEL_FOR.equals(name) return ATTR_LABEL_FOR.equals(name)
|| ATTR_TEXT_IS_SELECTABLE.equals(name) || ATTR_TEXT_IS_SELECTABLE.equals(name)
|| "textAlignment".equals(name)
|| ATTR_FULL_BACKUP_CONTENT.equals(name); || ATTR_FULL_BACKUP_CONTENT.equals(name);
} }
@@ -642,6 +671,12 @@ public class ApiDetector extends ResourceXmlDetector
checkElement(context, element, TAG_RIPPLE, 21, null, UNSUPPORTED); checkElement(context, element, TAG_RIPPLE, 21, null, UNSUPPORTED);
checkElement(context, element, TAG_ANIMATED_SELECTOR, 21, null, UNSUPPORTED); checkElement(context, element, TAG_ANIMATED_SELECTOR, 21, null, UNSUPPORTED);
checkElement(context, element, TAG_ANIMATED_VECTOR, 21, null, UNSUPPORTED); checkElement(context, element, TAG_ANIMATED_VECTOR, 21, null, UNSUPPORTED);
checkElement(context, element, "drawable", 24, null, UNSUPPORTED);
if ("layer-list".equals(tag)) {
checkLevelList(context, element);
} else if (tag.contains(".")) {
checkElement(context, element, tag, 24, null, UNSUPPORTED);
}
} }
if (element.getParentNode().getNodeType() != Node.ELEMENT_NODE) { if (element.getParentNode().getNodeType() != Node.ELEMENT_NODE) {
// Root node // Root node
@@ -710,6 +745,40 @@ public class ApiDetector extends ResourceXmlDetector
} }
} }
/** Checks whether the given element is the given tag, and if so, whether it satisfied
* the minimum version that the given tag is supported in */
private void checkLevelList(@NonNull XmlContext context, @NonNull Element element) {
Node curr = element.getFirstChild();
while (curr != null) {
if (curr.getNodeType() == Node.ELEMENT_NODE
&& TAG_ITEM.equals(curr.getNodeName())) {
Element e = (Element) curr;
if (e.hasAttributeNS(ANDROID_URI, ATTR_WIDTH)
|| e.hasAttributeNS(ANDROID_URI, ATTR_HEIGHT)) {
int attributeApiLevel = 23; // Using width and height on layer-list children requires M
int minSdk = getMinSdk(context);
if (attributeApiLevel > minSdk
&& attributeApiLevel > context.getFolderVersion()
&& attributeApiLevel > getLocalMinSdk(element)) {
for (String attributeName : new String[] { ATTR_WIDTH, ATTR_HEIGHT}) {
Attr attribute = e.getAttributeNodeNS(ANDROID_URI, attributeName);
if (attribute == null) {
continue;
}
Location location = context.getLocation(attribute);
String message = String.format(
"Attribute `%1$s` is only used in API level %2$d and higher "
+ "(current min is %3$d)",
attribute.getLocalName(), attributeApiLevel, minSdk);
context.report(UNUSED, attribute, location, message);
}
}
}
}
curr = curr.getNextSibling();
}
}
/** Checks whether the given element is the given tag, and if so, whether it satisfied /** Checks whether the given element is the given tag, and if so, whether it satisfied
* the minimum version that the given tag is supported in */ * the minimum version that the given tag is supported in */
private void checkElement(@NonNull XmlContext context, @NonNull Element element, private void checkElement(@NonNull XmlContext context, @NonNull Element element,
@@ -718,8 +787,20 @@ public class ApiDetector extends ResourceXmlDetector
int minSdk = getMinSdk(context); int minSdk = getMinSdk(context);
if (api > minSdk if (api > minSdk
&& api > context.getFolderVersion() && api > context.getFolderVersion()
&& api > getLocalMinSdk(element)) { && api > getLocalMinSdk(element)
&& !featureProvidedByGradle(context, gradleVersion)) {
Location location = context.getLocation(element); Location location = context.getLocation(element);
// For the <drawable> tag we report it against the class= attribute
if ("drawable".equals(tag)) {
Attr attribute = element.getAttributeNode(ATTR_CLASS);
if (attribute == null) {
return;
}
location = context.getLocation(attribute);
tag = ATTR_CLASS;
}
String message; String message;
if (issue == UNSUPPORTED) { if (issue == UNSUPPORTED) {
message = String.format( message = String.format(
@@ -729,6 +810,10 @@ public class ApiDetector extends ResourceXmlDetector
message += String.format( message += String.format(
" or building with Android Gradle plugin %1$s or higher", " or building with Android Gradle plugin %1$s or higher",
gradleVersion); gradleVersion);
} else if (tag.contains(".")) {
message = String.format(
"Custom drawables requires API level %1$d (current min is %2$d)",
api, minSdk);
} }
} else { } else {
assert issue == UNUSED : issue; assert issue == UNUSED : issue;
@@ -1001,6 +1086,14 @@ public class ApiDetector extends ResourceXmlDetector
} }
} }
if (api == 24
&& "java.util.concurrent.ConcurrentHashMap.KeySetView#iterator".equals(fqcn)) {
message += ". The `keySet()` method in `ConcurrentHashMap` "
+ "changed in a backwards incompatible way in Java 8; "
+ "to work around this issue, add an explicit cast to "
+ "`(Map)` before the `keySet()` call.";
}
report(context, message, node, method, name, null, report(context, message, node, method, name, null,
SearchHints.create(FORWARD).matchJavaSymbol()); SearchHints.create(FORWARD).matchJavaSymbol());
break; break;
@@ -1408,7 +1501,7 @@ public class ApiDetector extends ResourceXmlDetector
if (annotation.values != null) { if (annotation.values != null) {
for (int i = 0, n = annotation.values.size(); i < n; i += 2) { for (int i = 0, n = annotation.values.size(); i < n; i += 2) {
String key = (String) annotation.values.get(i); String key = (String) annotation.values.get(i);
if (key.equals("value")) { //$NON-NLS-1$ if (key.equals(ATTR_VALUE)) { //$NON-NLS-1$
Object value = annotation.values.get(i + 1); Object value = annotation.values.get(i + 1);
if (value instanceof Integer) { if (value instanceof Integer) {
return (Integer) value; return (Integer) value;
@@ -1416,6 +1509,21 @@ public class ApiDetector extends ResourceXmlDetector
} }
} }
} }
} else if (desc.endsWith(REQ_API_VMSIG)) {
if (annotation.values != null) {
for (int i = 0, n = annotation.values.size(); i < n; i += 2) {
String key = (String) annotation.values.get(i);
if (key.equals(ATTR_VALUE) || key.equals("api")) {
Object value = annotation.values.get(i + 1);
if (value instanceof Integer) {
int api = (Integer) value;
if (api > 1) {
return api;
}
}
}
}
}
} }
} }
} }
@@ -1457,6 +1565,31 @@ public class ApiDetector extends ResourceXmlDetector
return -1; return -1;
} }
/**
* Checks if the current project supports features added in {@code minGradleVersion} version of
* the Android gradle plugin.
*
* @param context Current context.
* @param minGradleVersionString Version in which support for a given feature was added, or null
* if it's not supported at build time.
*/
private static boolean featureProvidedByGradle(@NonNull XmlContext context,
@Nullable String minGradleVersionString) {
if (minGradleVersionString == null) {
return false;
}
GradleVersion gradleModelVersion = context.getProject().getGradleModelVersion();
if (gradleModelVersion != null) {
GradleVersion minVersion = GradleVersion.tryParse(minGradleVersionString);
if (minVersion != null
&& gradleModelVersion.compareIgnoringQualifiers(minVersion) >= 0) {
return true;
}
}
return false;
}
private static void report(final ClassContext context, String message, AbstractInsnNode node, private static void report(final ClassContext context, String message, AbstractInsnNode node,
MethodNode method, String patternStart, String patternEnd, SearchHints hints) { MethodNode method, String patternStart, String patternEnd, SearchHints hints) {
int lineNumber = node != null ? ClassContext.findLineNumber(node) : -1; int lineNumber = node != null ? ClassContext.findLineNumber(node) : -1;
@@ -1814,10 +1947,22 @@ public class ApiDetector extends ResourceXmlDetector
if (REQUIRES_API_ANNOTATION.equals(annotation.getQualifiedName())) { if (REQUIRES_API_ANNOTATION.equals(annotation.getQualifiedName())) {
int api = (int) SupportAnnotationDetector.getLongAttribute(annotation, int api = (int) SupportAnnotationDetector.getLongAttribute(annotation,
ATTR_VALUE, -1); ATTR_VALUE, -1);
if (api <= 1) {
// @RequiresApi has two aliasing attributes: api and value
api = (int) SupportAnnotationDetector.getLongAttribute(annotation,
"api", -1);
}
int minSdk = getMinSdk(mContext); int minSdk = getMinSdk(mContext);
if (api > minSdk) { if (api > minSdk) {
int target = getTargetApi(expression); int target = getTargetApi(expression);
if (target == -1 || api > target) { if (target == -1 || api > target) {
if (ApiDetector.isWithinVersionCheckConditional(expression, api, mContext)) {
return true;
}
if (ApiDetector.isPrecededByVersionCheckExit(expression, api, mContext)) {
return true;
}
Location location; Location location;
location = mContext.getUastLocation(expression); location = mContext.getUastLocation(expression);
String fqcn = method.getName(); String fqcn = method.getName();
@@ -2094,12 +2239,12 @@ public class ApiDetector extends ResourceXmlDetector
} }
} }
//} //}
// TODO: This MUST BE RESTORED // TODO: This MUST BE RESTORED
// if (context.getDriver().isSuppressed(UNSUPPORTED, element)) // if (context.getDriver().isSuppressed(UNSUPPORTED, element))
// if (/*mySeenSuppress &&*/ // if (/*mySeenSuppress &&*/
// (IntellijLintUtils.isSuppressed(element, myFile, UNSUPPORTED) || IntellijLintUtils.isSuppressed(element, myFile, INLINED))) { // (IntellijLintUtils.isSuppressed(element, myFile, UNSUPPORTED) || IntellijLintUtils.isSuppressed(element, myFile, INLINED))) {
// return true; // return true;
// } // }
if (isWithinVersionCheckConditional(element, api, context)) { if (isWithinVersionCheckConditional(element, api, context)) {
return true; return true;
@@ -2143,7 +2288,9 @@ public class ApiDetector extends ResourceXmlDetector
for (PsiAnnotation annotation : modifierList.getAnnotations()) { for (PsiAnnotation annotation : modifierList.getAnnotations()) {
String fqcn = annotation.getQualifiedName(); String fqcn = annotation.getQualifiedName();
if (fqcn != null && (fqcn.equals(FQCN_TARGET_API) || fqcn.equals(REQUIRES_API_ANNOTATION) if (fqcn != null &&
(fqcn.equals(FQCN_TARGET_API)
|| fqcn.equals(REQUIRES_API_ANNOTATION)
|| fqcn.equals(TARGET_API))) { // when missing imports || fqcn.equals(TARGET_API))) { // when missing imports
PsiAnnotationParameterList parameterList = annotation.getParameterList(); PsiAnnotationParameterList parameterList = annotation.getParameterList();
for (PsiNameValuePair pair : parameterList.getAttributes()) { for (PsiNameValuePair pair : parameterList.getAttributes()) {
@@ -2372,6 +2519,7 @@ public class ApiDetector extends ResourceXmlDetector
} }
private static class VersionCheckWithExitFinder extends AbstractUastVisitor { private static class VersionCheckWithExitFinder extends AbstractUastVisitor {
private final UExpression mExpression; private final UExpression mExpression;
private final UElement mEndElement; private final UElement mEndElement;
private final int mApi; private final int mApi;
@@ -28,12 +28,7 @@ import com.android.tools.klint.detector.api.Scope;
import com.android.tools.klint.detector.api.Severity; import com.android.tools.klint.detector.api.Severity;
import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiMethod;
import org.jetbrains.uast.UCallExpression; import org.jetbrains.uast.*;
import org.jetbrains.uast.UExpression;
import org.jetbrains.uast.ULiteralExpression;
import org.jetbrains.uast.UMethod;
import org.jetbrains.uast.UReturnExpression;
import org.jetbrains.uast.UastUtils;
import org.jetbrains.uast.visitor.AbstractUastVisitor; import org.jetbrains.uast.visitor.AbstractUastVisitor;
import org.jetbrains.uast.visitor.UastVisitor; import org.jetbrains.uast.visitor.UastVisitor;
@@ -91,14 +86,16 @@ public class ToastDetector extends Detector implements Detector.UastScanner {
} }
} }
UElement surroundingDeclaration = UastUtils.getParentOfType(
call, true,
UMethod.class, UBlockExpression.class, ULambdaExpression.class);
UMethod surroundingMethod = UastUtils.getContainingUMethod(call); if (surroundingDeclaration == null) {
if (surroundingMethod == null) {
return; return;
} }
ShowFinder finder = new ShowFinder(call); ShowFinder finder = new ShowFinder(call);
surroundingMethod.getUastBody().accept(finder); surroundingDeclaration.accept(finder);
if (!finder.isShowCalled()) { if (!finder.isShowCalled()) {
context.report(ISSUE, call, context.getUastNameLocation(call), context.report(ISSUE, call, context.getUastNameLocation(call),
"Toast created but not shown: did you forget to call `show()` ?"); "Toast created but not shown: did you forget to call `show()` ?");
@@ -108,14 +105,14 @@ public class ToastDetector extends Detector implements Detector.UastScanner {
private static class ShowFinder extends AbstractUastVisitor { private static class ShowFinder extends AbstractUastVisitor {
/** The target makeText call */ /** The target makeText call */
private final UExpression mTarget; private final UCallExpression mTarget;
/** Whether we've found the show method */ /** Whether we've found the show method */
private boolean mFound; private boolean mFound;
/** Whether we've seen the target makeText node yet */ /** Whether we've seen the target makeText node yet */
private boolean mSeenTarget; private boolean mSeenTarget;
private ShowFinder(UCallExpression target) { private ShowFinder(UCallExpression target) {
mTarget = UastUtils.getQualifiedParentOrThis(target); mTarget = target;
} }
@Override @Override
@@ -123,7 +120,7 @@ public class ToastDetector extends Detector implements Detector.UastScanner {
if (node.equals(mTarget)) { if (node.equals(mTarget)) {
mSeenTarget = true; mSeenTarget = true;
} else { } else {
if ((mTarget.equals(node.getReceiver())) if ((mSeenTarget || mTarget.equals(node.getReceiver()))
&& "show".equals(node.getMethodName())) { && "show".equals(node.getMethodName())) {
// TODO: Do more flow analysis to see whether we're really calling show // TODO: Do more flow analysis to see whether we're really calling show
// on the right type of object? // on the right type of object?
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.uast package org.jetbrains.kotlin.uast
import org.jetbrains.android.inspections.klint.AndroidLintInspectionBase
import org.jetbrains.kotlin.android.KotlinAndroidTestCase import org.jetbrains.kotlin.android.KotlinAndroidTestCase
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
import org.jetbrains.kotlin.test.InTextDirectivesUtils.findStringWithPrefixes import org.jetbrains.kotlin.test.InTextDirectivesUtils.findStringWithPrefixes
@@ -27,7 +28,7 @@ abstract class AbstractKotlinLintTest : KotlinAndroidTestCase() {
override fun setUp() { override fun setUp() {
super.setUp() super.setUp()
ConfigLibraryUtil.configureKotlinRuntime(myModule) ConfigLibraryUtil.configureKotlinRuntime(myModule)
// AndroidLintInspectionBase.invalidateInspectionShortName2IssueMap() AndroidLintInspectionBase.invalidateInspectionShortName2IssueMap()
} }
override fun tearDown() { override fun tearDown() {
@@ -45,29 +46,28 @@ abstract class AbstractKotlinLintTest : KotlinAndroidTestCase() {
inspectionClassNames += className inspectionClassNames += className
} }
//TODO myFixture.enableInspections(*inspectionClassNames.map { className ->
// myFixture.enableInspections(*inspectionClassNames.map { className -> val inspectionClass = Class.forName(className)
// val inspectionClass = Class.forName(className) inspectionClass.newInstance() as AndroidLintInspectionBase
// inspectionClass.newInstance() as AndroidLintInspectionBase }.toTypedArray())
// }.toTypedArray())
// val additionalResourcesDir = File(ktFile.parentFile, getTestName(true)) val additionalResourcesDir = File(ktFile.parentFile, getTestName(true))
// if (additionalResourcesDir.exists()) { if (additionalResourcesDir.exists()) {
// for (file in additionalResourcesDir.listFiles()) { for (file in additionalResourcesDir.listFiles()) {
// if (file.isFile) { if (file.isFile) {
// myFixture.copyFileToProject(file.absolutePath, file.name) myFixture.copyFileToProject(file.absolutePath, file.name)
// } }
// else if (file.isDirectory) { else if (file.isDirectory) {
// myFixture.copyDirectoryToProject(file.absolutePath, file.name) myFixture.copyDirectoryToProject(file.absolutePath, file.name)
// } }
// } }
// } }
//
// val virtualFile = myFixture.copyFileToProject(ktFile.absolutePath, "src/" + getTestName(true) + ".kt"); val virtualFile = myFixture.copyFileToProject(ktFile.absolutePath, "src/" + getTestName(true) + ".kt");
// myFixture.configureFromExistingVirtualFile(virtualFile) myFixture.configureFromExistingVirtualFile(virtualFile)
//
// myFixture.doHighlighting() myFixture.doHighlighting()
// myFixture.checkHighlighting(true, false, false) myFixture.checkHighlighting(true, false, false)
} }
override fun getTestDataPath() = KotlinTestUtils.getHomeDirectory() + "/plugins/uast-kotlin/testData/lint/" override fun getTestDataPath() = KotlinTestUtils.getHomeDirectory() + "/plugins/uast-kotlin/testData/lint/"
+70 -71
View File
@@ -1,4 +1,4 @@
// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection <error descr="The SDK platform-tools version (23) is too old to check APIs compiled with API 23; please update">// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection</error>
// INSPECTION_CLASS2: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintInlinedApiInspection // INSPECTION_CLASS2: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintInlinedApiInspection
// INSPECTION_CLASS3: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintOverrideInspection // INSPECTION_CLASS3: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintOverrideInspection
@@ -31,52 +31,51 @@ import android.widget.TextView
class ApiCallTest: Activity() { class ApiCallTest: Activity() {
fun method(chronometer: Chronometer, locator: DOMLocator) { fun method(chronometer: Chronometer, locator: DOMLocator) {
chronometer.<error descr="Call requires API level 16 (current min is 1): `setBackground`">setBackground(null)</error> chronometer.<error descr="Call requires API level 16 (current min is 1): android.view.View#setBackground">setBackground</error>(null)
// Ok // Ok
Bundle().getInt("") Bundle().getInt("")
// Ok, this constant is inlined // Ok, this constant is inlined
View.SYSTEM_UI_FLAG_FULLSCREEN View.<warning descr="Field requires API level 16 (current min is 1): android.view.View#SYSTEM_UI_FLAG_FULLSCREEN">SYSTEM_UI_FLAG_FULLSCREEN</warning>
// Virtual call // Virtual call
<error descr="Call requires API level 11 (current min is 1): `getActionBar`">getActionBar()</error> // API 11 <error descr="Call requires API level 11 (current min is 1): android.app.Activity#getActionBar">getActionBar</error>() // API 11
<error descr="Call requires API level 11 (current min is 1): `getActionBar`">actionBar</error> // API 11 <error descr="Call requires API level 11 (current min is 1): android.app.Activity#getActionBar">actionBar</error> // API 11
// Class references (no call or field access) // Class references (no call or field access)
val error: DOMError? = null // API 8 val error: DOMError? = null // API 8
val clz = DOMErrorHandler::class // API 8 val clz = DOMErrorHandler::class // API 8
// Method call // Method call
chronometer.<error descr="Call requires API level 3 (current min is 1): `getOnChronometerTickListener`">onChronometerTickListener</error> // API 3 chronometer.<error descr="Call requires API level 3 (current min is 1): android.widget.Chronometer#getOnChronometerTickListener">onChronometerTickListener</error> // API 3
// Inherited method call (from TextView // Inherited method call (from TextView
chronometer.<error descr="Call requires API level 11 (current min is 1): `setTextIsSelectable`">setTextIsSelectable(true)</error> // API 11 chronometer.<error descr="Call requires API level 11 (current min is 1): android.widget.TextView#setTextIsSelectable">setTextIsSelectable</error>(true) // API 11
// TODO: fix UClassLiteralExpression and uncomment, must be: error descr="Class requires API level 14 (current min is 1): `GridLayout`"
GridLayout::class GridLayout::class
// Field access // Field access
val field = OpcodeInfo.<error descr="Field requires API level 11 (current min is 1): `MAXIMUM_VALUE`">MAXIMUM_VALUE</error> // API 11 val field = OpcodeInfo.<warning descr="Field requires API level 11 (current min is 1): dalvik.bytecode.OpcodeInfo#MAXIMUM_VALUE">MAXIMUM_VALUE</warning> // API 11
val fillParent = LayoutParams.FILL_PARENT // API 1 val fillParent = LayoutParams.FILL_PARENT // API 1
// This is a final int, which means it gets inlined // This is a final int, which means it gets inlined
val matchParent = LayoutParams.MATCH_PARENT // API 8 val matchParent = LayoutParams.MATCH_PARENT // API 8
// Field access: non final // Field access: non final
val batteryInfo = report!!.<error descr="Field requires API level 14 (current min is 1): `batteryInfo`">batteryInfo</error> val batteryInfo = report!!.<error descr="Field requires API level 14 (current min is 1): android.app.ApplicationErrorReport#batteryInfo">batteryInfo</error>
// Enum access // Enum access
if (Build.VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> <= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
val mode = PorterDuff.Mode.<error descr="Field requires API level 11 (current min is 1): `OVERLAY`">OVERLAY</error> // API 11 val mode = PorterDuff.Mode.<error descr="Field requires API level 11 (current min is 1): android.graphics.PorterDuff.Mode#OVERLAY">OVERLAY</error> // API 11
} }
} }
fun test(rect: Rect) { fun test(rect: Rect) {
if (Build.VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
RectEvaluator(rect); // OK RectEvaluator(rect); // OK
} }
if (Build.VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (rect != null) { if (rect != null) {
RectEvaluator(rect); // OK RectEvaluator(rect); // OK
} }
@@ -84,31 +83,31 @@ class ApiCallTest: Activity() {
} }
fun test2(rect: Rect) { fun test2(rect: Rect) {
if (Build.VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
RectEvaluator(rect); // OK RectEvaluator(rect); // OK
} }
} }
fun test3(rect: Rect) { fun test3(rect: Rect) {
if (Build.VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= Build.VERSION_CODES.GINGERBREAD) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
<error descr="Class requires API level 18 (current min is 1): `RectEvaluator`">RectEvaluator()</error>; // ERROR <error descr="Call requires API level 18 (current min is 1): android.animation.RectEvaluator#RectEvaluator">RectEvaluator</error>(); // ERROR
} }
} }
fun test4(rect: Rect) { fun test4(rect: Rect) {
if (Build.VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
System.out.println("Something"); System.out.println("Something");
RectEvaluator(rect); // OK RectEvaluator(rect); // OK
} else { } else {
<error descr="Class requires API level 18 (current min is 1): `RectEvaluator`">RectEvaluator(rect)</error>; // ERROR <error descr="Call requires API level 21 (current min is 1): android.animation.RectEvaluator#RectEvaluator">RectEvaluator</error>(rect); // ERROR
} }
} }
fun test5(rect: Rect) { fun test5(rect: Rect) {
if (Build.VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= Build.VERSION_CODES.CUPCAKE) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) {
<error descr="Class requires API level 18 (current min is 1): `RectEvaluator`">RectEvaluator(rect)</error>; // ERROR <error descr="Call requires API level 21 (current min is 1): android.animation.RectEvaluator#RectEvaluator">RectEvaluator</error>(rect); // ERROR
} else { } else {
RectEvaluator(rect); // ERROR <error descr="Call requires API level 21 (current min is 1): android.animation.RectEvaluator#RectEvaluator">RectEvaluator</error>(rect); // ERROR
} }
} }
@@ -116,151 +115,151 @@ class ApiCallTest: Activity() {
if (layout is LinearLayout) {} if (layout is LinearLayout) {}
layout as? LinearLayout layout as? LinearLayout
if (layout is <error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout</error>) {} if (layout is GridLayout) {}
layout as? <error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout</error> layout as? GridLayout
if (android.os.Build.VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
GridLayout(null).getOrientation(); // Not flagged GridLayout(null).getOrientation(); // Not flagged
} else { } else {
<error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout(null)</error>.<error descr="Call requires API level 14 (current min is 1): `getOrientation`">getOrientation()</error>; // Flagged <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
} }
if (Build.VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
GridLayout(null).getOrientation(); // Not flagged GridLayout(null).getOrientation(); // Not flagged
} else { } else {
<error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout(null)</error>.<error descr="Call requires API level 14 (current min is 1): `getOrientation`">getOrientation()</error>; // Flagged <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
} }
if (<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= ICE_CREAM_SANDWICH) { if (SDK_INT >= ICE_CREAM_SANDWICH) {
GridLayout(null).getOrientation(); // Not flagged GridLayout(null).getOrientation(); // Not flagged
} else { } else {
<error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout(null)</error>.<error descr="Call requires API level 14 (current min is 1): `getOrientation`">getOrientation()</error>; // Flagged <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
} }
if (Build.VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
GridLayout(null).getOrientation(); // Not flagged GridLayout(null).getOrientation(); // Not flagged
} else { } else {
<error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout(null)</error>.<error descr="Call requires API level 14 (current min is 1): `getOrientation`">getOrientation()</error>; // Flagged <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
} }
if (Build.VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
<error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout(null)</error>.<error descr="Call requires API level 14 (current min is 1): `getOrientation`">getOrientation()</error>; // Flagged <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
} else { } else {
GridLayout(null).getOrientation(); // Not flagged GridLayout(null).getOrientation(); // Not flagged
} }
if (Build.VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= 14) { if (Build.VERSION.SDK_INT >= 14) {
GridLayout(null).getOrientation(); // Not flagged GridLayout(null).getOrientation(); // Not flagged
} else { } else {
<error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout(null)</error>.<error descr="Call requires API level 14 (current min is 1): `getOrientation`">getOrientation()</error>; // Flagged <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
} }
if (VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= VERSION_CODES.ICE_CREAM_SANDWICH) { if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH) {
GridLayout(null).getOrientation(); // Not flagged GridLayout(null).getOrientation(); // Not flagged
} else { } else {
<error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout(null)</error>.<error descr="Call requires API level 14 (current min is 1): `getOrientation`">getOrientation()</error>; // Flagged <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
} }
// Nested conditionals // Nested conditionals
if (Build.VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= Build.VERSION_CODES.HONEYCOMB) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (priority) { if (priority) {
<error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout(null)</error>.<error descr="Call requires API level 14 (current min is 1): `getOrientation`">getOrientation()</error>; // Flagged <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
} else { } else {
<error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout(null)</error>.<error descr="Call requires API level 14 (current min is 1): `getOrientation`">getOrientation()</error>; // Flagged <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
} }
} else { } else {
GridLayout(null).getOrientation(); // Flagged <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
} }
// Nested conditionals 2 // Nested conditionals 2
if (Build.VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= Build.VERSION_CODES.JELLY_BEAN) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
if (priority) { if (priority) {
<error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout(null)</error>.<error descr="Call requires API level 14 (current min is 1): `getOrientation`">getOrientation()</error>; // Not flagged GridLayout(null).getOrientation(); // Not flagged
} else { } else {
<error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout(null)</error>.<error descr="Call requires API level 14 (current min is 1): `getOrientation`">getOrientation()</error>; // Not flagged GridLayout(null).getOrientation(); // Not flagged
} }
} else { } else {
<error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout(null)</error>; // Flagged <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null); // Flagged
} }
} }
fun test2(priority: Boolean) { fun test2(priority: Boolean) {
if (android.os.Build.VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= VERSION_CODES.JELLY_BEAN) { if (android.os.Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
GridLayout(null).getOrientation(); // Not flagged GridLayout(null).getOrientation(); // Not flagged
} else { } else {
<error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout(null)</error>; // Flagged <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null); // Flagged
} }
if (android.os.Build.VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= 16) { if (android.os.Build.VERSION.SDK_INT >= 16) {
GridLayout(null).getOrientation(); // Not flagged GridLayout(null).getOrientation(); // Not flagged
} else { } else {
<error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout(null)</error>; // Flagged <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null); // Flagged
} }
if (android.os.Build.VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= 13) { if (android.os.Build.VERSION.SDK_INT >= 13) {
<error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout(null)</error>.<error descr="Call requires API level 14 (current min is 1): `getOrientation`">getOrientation()</error>; // Flagged <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null).<error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#getOrientation">getOrientation</error>(); // Flagged
} else { } else {
GridLayout(null); // Flagged <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null); // Flagged
} }
if (Build.VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= Build.VERSION_CODES.JELLY_BEAN) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
GridLayout(null).getOrientation(); // Not flagged GridLayout(null).getOrientation(); // Not flagged
} else { } else {
<error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout(null)</error>; // Flagged <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null); // Flagged
} }
if (<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= JELLY_BEAN) { if (SDK_INT >= JELLY_BEAN) {
GridLayout(null).getOrientation(); // Not flagged GridLayout(null).getOrientation(); // Not flagged
} else { } else {
<error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout(null)</error>; // Flagged <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null); // Flagged
} }
if (Build.VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= Build.VERSION_CODES.JELLY_BEAN) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
GridLayout(null).getOrientation(); // Not flagged GridLayout(null).getOrientation(); // Not flagged
} else { } else {
<error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout(null)</error>; // Flagged <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null); // Flagged
} }
if (Build.VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> < Build.VERSION_CODES.JELLY_BEAN) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
<error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout(null)</error>; // Flagged <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null); // Flagged
} else { } else {
GridLayout(null).getOrientation(); // Not flagged GridLayout(null).getOrientation(); // Not flagged
} }
if (Build.VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= 16) { if (Build.VERSION.SDK_INT >= 16) {
GridLayout(null).getOrientation(); // Not flagged GridLayout(null).getOrientation(); // Not flagged
} else { } else {
<error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout(null)</error>; // Flagged <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null); // Flagged
} }
if (VERSION.<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= VERSION_CODES.JELLY_BEAN) { if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
GridLayout(null).getOrientation(); // Not flagged GridLayout(null).getOrientation(); // Not flagged
} else { } else {
<error descr="Class requires API level 14 (current min is 1): `GridLayout`">GridLayout(null)</error>; // Flagged <error descr="Call requires API level 14 (current min is 1): android.widget.GridLayout#GridLayout">GridLayout</error>(null); // Flagged
} }
} }
fun test(textView: TextView) { fun test(textView: TextView) {
if (textView.<error descr="Call requires API level 14 (current min is 1): `isSuggestionsEnabled`">isSuggestionsEnabled()</error>) { if (textView.<error descr="Call requires API level 14 (current min is 1): android.widget.TextView#isSuggestionsEnabled">isSuggestionsEnabled</error>()) {
//ERROR //ERROR
} }
if (textView.<error descr="Call requires API level 14 (current min is 1): `isSuggestionsEnabled`">isSuggestionsEnabled</error>) { if (textView.<error descr="Call requires API level 14 (current min is 1): android.widget.TextView#isSuggestionsEnabled">isSuggestionsEnabled</error>) {
//ERROR //ERROR
} }
if (<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= JELLY_BEAN && textView.isSuggestionsEnabled()) { if (SDK_INT >= JELLY_BEAN && textView.<error descr="Call requires API level 14 (current min is 1): android.widget.TextView#isSuggestionsEnabled">isSuggestionsEnabled</error>()) {
//NO ERROR //NO ERROR
} }
if (<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> >= JELLY_BEAN && textView.isSuggestionsEnabled) { if (SDK_INT >= JELLY_BEAN && textView.<error descr="Call requires API level 14 (current min is 1): android.widget.TextView#isSuggestionsEnabled">isSuggestionsEnabled</error>) {
//NO ERROR //NO ERROR
} }
if (<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> < JELLY_BEAN && textView.<error descr="Call requires API level 14 (current min is 1): `isSuggestionsEnabled`">isSuggestionsEnabled()</error>) { if (SDK_INT < JELLY_BEAN && textView.<error descr="Call requires API level 14 (current min is 1): android.widget.TextView#isSuggestionsEnabled">isSuggestionsEnabled</error>()) {
//ERROR //ERROR
} }
if (<error descr="Field requires API level 4 (current min is 1): `SDK_INT`">SDK_INT</error> < JELLY_BEAN && textView.<error descr="Call requires API level 14 (current min is 1): `isSuggestionsEnabled`">isSuggestionsEnabled</error>) { if (SDK_INT < JELLY_BEAN && textView.<error descr="Call requires API level 14 (current min is 1): android.widget.TextView#isSuggestionsEnabled">isSuggestionsEnabled</error>) {
//ERROR //ERROR
} }
} }
+3 -3
View File
@@ -73,7 +73,7 @@ class JavaPerformanceTest(context: Context, attrs: AttributeSet, defStyle: Int)
// This one should not be reported: // This one should not be reported:
@SuppressLint("UseSparseArrays") @SuppressLint("UseSparseArrays")
val myOtherMap = HashMap<Int, Any>() val myOtherMap = <warning descr="Use `new SparseArray<Object>(...)` instead for better performance">HashMap<Int, Any>()</warning>
} }
protected fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int, protected fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int,
@@ -120,8 +120,8 @@ class JavaPerformanceTest(context: Context, attrs: AttributeSet, defStyle: Int)
val layoutHeight = height val layoutHeight = height
if (mAllowCrop && (mOverlay == null || mOverlay!!.width != layoutWidth || if (mAllowCrop && (mOverlay == null || mOverlay!!.width != layoutWidth ||
mOverlay!!.height != layoutHeight)) { mOverlay!!.height != layoutHeight)) {
mOverlay = Bitmap.<warning descr="Avoid object allocations during draw/layout operations (preallocate and reuse instead)">createBitmap(layoutWidth, layoutHeight, Bitmap.Config.ARGB_8888)</warning> mOverlay = Bitmap.createBitmap(layoutWidth, layoutHeight, Bitmap.Config.ARGB_8888)
mOverlayCanvas = <warning descr="Avoid object allocations during draw/layout operations (preallocate and reuse instead)">Canvas(mOverlay!!)</warning> mOverlayCanvas = Canvas(mOverlay!!)
} }
if (widthMeasureSpec == 42) { if (widthMeasureSpec == 42) {
+8 -8
View File
@@ -15,8 +15,8 @@ class LogTest {
Log.d(TAG1, m) // ok: unconditional, but not performing computation Log.d(TAG1, m) // ok: unconditional, but not performing computation
Log.d(TAG1, "a" + "b") // ok: unconditional, but not performing non-constant computation Log.d(TAG1, "a" + "b") // ok: unconditional, but not performing non-constant computation
Log.d(TAG1, Constants.MY_MESSAGE) // ok: unconditional, but constant string Log.d(TAG1, Constants.MY_MESSAGE) // ok: unconditional, but constant string
Log.i(TAG1, "message" + m) // error: unconditional w/ computation Log.<warning descr="The log call Log.i(...) should be conditional: surround with `if (Log.isLoggable(...))` or `if (BuildConfig.DEBUG) { ... }`">i(TAG1, "message" + m)</warning> // error: unconditional w/ computation
Log.i(TAG1, toString()) // error: unconditional w/ computation Log.<warning descr="The log call Log.i(...) should be conditional: surround with `if (Log.isLoggable(...))` or `if (BuildConfig.DEBUG) { ... }`">i(TAG1, toString())</warning> // error: unconditional w/ computation
Log.e(TAG1, toString()) // ok: only flagging debug/info messages Log.e(TAG1, toString()) // ok: only flagging debug/info messages
Log.w(TAG1, toString()) // ok: only flagging debug/info messages Log.w(TAG1, toString()) // ok: only flagging debug/info messages
Log.wtf(TAG1, toString()) // ok: only flagging debug/info messages Log.wtf(TAG1, toString()) // ok: only flagging debug/info messages
@@ -26,11 +26,11 @@ class LogTest {
} }
fun checkWrongTag(tag: String) { fun checkWrongTag(tag: String) {
if (Log.isLoggable(<error descr="Mismatched tags: the `d()` and `isLoggable()` calls typically should pass the same tag: `MyTag1` versus `MyTag2` (Conflicting tag)">TAG1</error>, Log.DEBUG)) { if (Log.isLoggable(<error descr="Mismatched tags: the `d()` and `isLoggable()` calls typically should pass the same tag: `getTAG1` versus `getTAG2` (Conflicting tag)">TAG1</error>, Log.DEBUG)) {
Log.d(<error descr="Mismatched tags: the `d()` and `isLoggable()` calls typically should pass the same tag: `MyTag1` versus `MyTag2`">TAG2</error>, "message") // warn: mismatched tags! Log.d(<error descr="Mismatched tags: the `d()` and `isLoggable()` calls typically should pass the same tag: `getTAG1` versus `getTAG2`">TAG2</error>, "message") // warn: mismatched tags!
} }
if (Log.isLoggable("<error descr="Mismatched tags: the `d()` and `isLoggable()` calls typically should pass the same tag: `my_tag` versus `other_tag` (Conflicting tag)">my_tag</error>", Log.DEBUG)) { if (Log.isLoggable("<error descr="Mismatched tags: the `d()` and `isLoggable()` calls typically should pass the same tag: `\"my_tag\"` versus `\"other_tag\"` (Conflicting tag)">my_tag</error>", Log.DEBUG)) {
Log.d("<error descr="Mismatched tags: the `d()` and `isLoggable()` calls typically should pass the same tag: `my_tag` versus `other_tag`">other_tag</error>", "message") // warn: mismatched tags! Log.d("<error descr="Mismatched tags: the `d()` and `isLoggable()` calls typically should pass the same tag: `\"my_tag\"` versus `\"other_tag\"`">other_tag</error>", "message") // warn: mismatched tags!
} }
if (Log.isLoggable("my_tag", Log.DEBUG)) { if (Log.isLoggable("my_tag", Log.DEBUG)) {
Log.d("my_tag", "message") // ok: strings equal Log.d("my_tag", "message") // ok: strings equal
@@ -73,9 +73,9 @@ class LogTest {
if (Log.isLoggable(TAG1, <error descr="Mismatched logging levels: when checking `isLoggable` level `DEBUG`, the corresponding log call should be `Log.d`, not `Log.v` (Conflicting tag)">Log.DEBUG</error>)) { if (Log.isLoggable(TAG1, <error descr="Mismatched logging levels: when checking `isLoggable` level `DEBUG`, the corresponding log call should be `Log.d`, not `Log.v` (Conflicting tag)">Log.DEBUG</error>)) {
Log.<error descr="Mismatched logging levels: when checking `isLoggable` level `DEBUG`, the corresponding log call should be `Log.d`, not `Log.v`">v</error>(TAG1, "message") // warn: wrong level Log.<error descr="Mismatched logging levels: when checking `isLoggable` level `DEBUG`, the corresponding log call should be `Log.d`, not `Log.v`">v</error>(TAG1, "message") // warn: wrong level
} }
if (Log.isLoggable(TAG1, DEBUG)) { if (Log.isLoggable(TAG1, <error descr="Mismatched logging levels: when checking `isLoggable` level `DEBUG`, the corresponding log call should be `Log.d`, not `Log.v` (Conflicting tag)">DEBUG</error>)) {
// static import of level // static import of level
Log.v(TAG1, "message") // warn: wrong level Log.<error descr="Mismatched logging levels: when checking `isLoggable` level `DEBUG`, the corresponding log call should be `Log.d`, not `Log.v`">v</error>(TAG1, "message") // warn: wrong level
} }
if (Log.isLoggable(TAG1, <error descr="Mismatched logging levels: when checking `isLoggable` level `VERBOSE`, the corresponding log call should be `Log.v`, not `Log.d` (Conflicting tag)">Log.VERBOSE</error>)) { if (Log.isLoggable(TAG1, <error descr="Mismatched logging levels: when checking `isLoggable` level `VERBOSE`, the corresponding log call should be `Log.v`, not `Log.d` (Conflicting tag)">Log.VERBOSE</error>)) {
Log.<error descr="Mismatched logging levels: when checking `isLoggable` level `VERBOSE`, the corresponding log call should be `Log.v`, not `Log.d`">d</error>(TAG1, "message") // warn? verbose is a lower logging level, which includes debug Log.<error descr="Mismatched logging levels: when checking `isLoggable` level `VERBOSE`, the corresponding log call should be `Log.v`, not `Log.d`">d</error>(TAG1, "message") // warn? verbose is a lower logging level, which includes debug
+1 -1
View File
@@ -55,7 +55,7 @@ class RecyclerViewScrollPosition(val position: Int, val topOffset: Int): Parcela
} }
} }
class <error>RecyclerViewScrollPositionWithoutJvmF</error>(val position: Int, val topOffset: Int): Parcelable { class RecyclerViewScrollPositionWithoutJvmF(val position: Int, val topOffset: Int): Parcelable {
override fun describeContents(): Int = 0 override fun describeContents(): Int = 0
override fun writeToParcel(dest: Parcel, flags: Int) { override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeInt(position) dest.writeInt(position)
@@ -5,5 +5,5 @@ import android.database.sqlite.SQLiteDatabase
fun test(db: SQLiteDatabase) { fun test(db: SQLiteDatabase) {
val <warning descr="[UNUSED_VARIABLE] Variable 'a' is never used">a</warning>: String = <error descr="[CONSTANT_EXPECTED_TYPE_MISMATCH] The integer literal does not conform to the expected type String">1</error> val <warning descr="[UNUSED_VARIABLE] Variable 'a' is never used">a</warning>: String = <error descr="[CONSTANT_EXPECTED_TYPE_MISMATCH] The integer literal does not conform to the expected type String">1</error>
db.<warning descr="Using column type STRING; did you mean to use TEXT? (STRING is a numeric type and its value can be adjusted; for example,strings that look like integers can drop leading zeroes. See issue explanation for details.)">execSQL("CREATE TABLE COMPANY(NAME STRING)")</warning> db.<warning descr="Using column type STRING; did you mean to use TEXT? (STRING is a numeric type and its value can be adjusted; for example, strings that look like integers can drop leading zeroes. See issue explanation for details.)">execSQL("CREATE TABLE COMPANY(NAME STRING)")</warning>
} }
+1 -1
View File
@@ -3,5 +3,5 @@
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
fun test(db: SQLiteDatabase) { fun test(db: SQLiteDatabase) {
db.<warning descr="Using column type STRING; did you mean to use TEXT? (STRING is a numeric type and its value can be adjusted; for example,strings that look like integers can drop leading zeroes. See issue explanation for details.)">execSQL("CREATE TABLE COMPANY(NAME STRING)")</warning> db.<warning descr="Using column type STRING; did you mean to use TEXT? (STRING is a numeric type and its value can be adjusted; for example, strings that look like integers can drop leading zeroes. See issue explanation for details.)">execSQL("CREATE TABLE COMPANY(NAME STRING)")</warning>
} }
+5 -5
View File
@@ -24,7 +24,7 @@ class ToastTest(context: Context) : Activity() {
} }
Runnable { Runnable {
Toast.<warning descr="Toast created but not shown: did you forget to call `show()` ?">makeText(context, "foo", Toast.LENGTH_LONG)</warning> Toast.<warning descr="Toast created but not shown: did you forget to call `show()` ?">makeText</warning>(context, "foo", Toast.LENGTH_LONG)
} }
} }
@@ -45,13 +45,13 @@ class ToastTest(context: Context) : Activity() {
private fun broken(context: Context) { private fun broken(context: Context) {
// Errors // Errors
Toast.<warning descr="Toast created but not shown: did you forget to call `show()` ?">makeText(context, "foo", Toast.LENGTH_LONG)</warning> Toast.<warning descr="Toast created but not shown: did you forget to call `show()` ?">makeText</warning>(context, "foo", Toast.LENGTH_LONG)
val toast = Toast.<warning descr="Toast created but not shown: did you forget to call `show()` ?">makeText(context, R.string.app_name, <warning descr="Expected duration `Toast.LENGTH_SHORT` or `Toast.LENGTH_LONG`, a custom duration value is not supported">5000</warning>)</warning> val toast = Toast.<warning descr="Toast created but not shown: did you forget to call `show()` ?">makeText</warning>(context, R.string.app_name, <warning descr="Expected duration `Toast.LENGTH_SHORT` or `Toast.LENGTH_LONG`, a custom duration value is not supported">5000</warning>)
toast.duration toast.duration
} }
init { init {
Toast.<warning descr="Toast created but not shown: did you forget to call `show()` ?"><warning descr="Toast created but not shown: did you forget to call `show()` ?">makeText(context, "foo", Toast.LENGTH_LONG)</warning></warning> Toast.<warning descr="Toast created but not shown: did you forget to call `show()` ?">makeText</warning>(context, "foo", Toast.LENGTH_LONG)
} }
@android.annotation.SuppressLint("ShowToast") @android.annotation.SuppressLint("ShowToast")
@@ -61,7 +61,7 @@ class ToastTest(context: Context) : Activity() {
private fun checkSuppress2(context: Context) { private fun checkSuppress2(context: Context) {
@android.annotation.SuppressLint("ShowToast") @android.annotation.SuppressLint("ShowToast")
val toast = Toast.makeText(this, "MyToast", Toast.LENGTH_LONG) val toast = Toast.<warning descr="Toast created but not shown: did you forget to call `show()` ?">makeText</warning>(this, "MyToast", Toast.LENGTH_LONG)
} }
class R { class R {
+5 -5
View File
@@ -13,13 +13,13 @@ class WrongAnnotation2 {
companion object { companion object {
@SuppressLint("NewApi") // Valid: class-file check on method @SuppressLint("NewApi") // Valid: class-file check on method
fun foobar(view: View, <error descr="The `@SuppressLint` annotation cannot be used on a local variable with the lint check 'NewApi': move out to the surrounding method">@SuppressLint("NewApi")</error> foo: Int) { fun foobar(view: View, @SuppressLint("NewApi") foo: Int) {
// Invalid: class-file check // Invalid: class-file check
<error descr="The `@SuppressLint` annotation cannot be used on a local variable with the lint check 'NewApi': move out to the surrounding method">@SuppressLint("NewApi")</error> // Invalid @SuppressLint("NewApi") // Invalid
val a: Boolean val a: Boolean
<error descr="The `@SuppressLint` annotation cannot be used on a local variable with the lint check 'NewApi': move out to the surrounding method">@SuppressLint("SdCardPath", "NewApi")</error> // Invalid: class-file based check on local variable @SuppressLint("SdCardPath", "NewApi") // Invalid: class-file based check on local variable
val b: Boolean val b: Boolean
<error descr="The `@SuppressLint` annotation cannot be used on a local variable with the lint check 'NewApi': move out to the surrounding method">@android.annotation.SuppressLint("SdCardPath", "NewApi")</error> // Invalid (FQN) @android.annotation.SuppressLint("SdCardPath", "NewApi") // Invalid (FQN)
val c: Boolean val c: Boolean
@SuppressLint("SdCardPath") // Valid: AST-based check @SuppressLint("SdCardPath") // Valid: AST-based check
val d: Boolean val d: Boolean
@@ -27,7 +27,7 @@ class WrongAnnotation2 {
init { init {
// Local variable outside method: invalid // Local variable outside method: invalid
<error descr="The `@SuppressLint` annotation cannot be used on a local variable with the lint check 'NewApi': move out to the surrounding method"><error descr="The `@SuppressLint` annotation cannot be used on a local variable with the lint check 'NewApi': move out to the surrounding method">@SuppressLint("NewApi")</error></error> @SuppressLint("NewApi")
val localvar = 5 val localvar = 5
} }
+1
View File
@@ -22,5 +22,6 @@
<orderEntry type="module" module-name="android-extensions-idea" scope="TEST" /> <orderEntry type="module" module-name="android-extensions-idea" scope="TEST" />
<orderEntry type="module" module-name="light-classes" /> <orderEntry type="module" module-name="light-classes" />
<orderEntry type="module" module-name="util.runtime" /> <orderEntry type="module" module-name="util.runtime" />
<orderEntry type="module" module-name="lint-idea" scope="TEST" />
</component> </component>
</module> </module>