diff --git a/plugins/lint/lint-api/lint-api.iml b/plugins/lint/lint-api/lint-api.iml
index 148f696c544..b353d109428 100644
--- a/plugins/lint/lint-api/lint-api.iml
+++ b/plugins/lint/lint-api/lint-api.iml
@@ -8,8 +8,8 @@
-
-
+
+
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiDetector.java
index e5f49be9664..298c8d307e6 100644
--- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiDetector.java
+++ b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ApiDetector.java
@@ -18,10 +18,15 @@ package com.android.tools.klint.checks;
import static com.android.SdkConstants.ANDROID_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_FULL_BACKUP_CONTENT;
+import static com.android.SdkConstants.ATTR_ID;
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_PADDING_START;
import static com.android.SdkConstants.ATTR_PARENT;
import static com.android.SdkConstants.ATTR_TARGET_API;
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.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.skipParentheses;
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.NEAREST;
import static com.android.utils.SdkUtils.getResourceFieldName;
+import com.android.SdkConstants;
import com.android.annotations.NonNull;
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.ResourceType;
import com.android.sdklib.AndroidVersion;
+import com.android.sdklib.BuildToolInfo;
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.LintDriver;
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.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.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.PsiAnnotationMemberValue;
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.IntInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
-import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.LocalVariableNode;
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 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$
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));
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 ORDINAL_METHOD = "ordinal"; //$NON-NLS-1$
public static final String ENUM_SWITCH_PREFIX = "$SwitchMap$"; //$NON-NLS-1$
@@ -312,6 +339,7 @@ public class ApiDetector extends ResourceXmlDetector
@Override
public void beforeCheckProject(@NonNull Context context) {
if (mApiDatabase == null) {
+ //TODO
//mApiDatabase = ApiLookup.get(context.getClient());
//// 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.
@@ -321,7 +349,7 @@ public class ApiDetector extends ResourceXmlDetector
//if (mApiDatabase == null && !mWarnedMissingDb) {
// mWarnedMissingDb = true;
// context.report(IssueRegistry.LINT_ERROR, Location.create(context.file),
- // "Can't find API database; API check not performed");
+ // "Can't find API database; API check not performed");
//} else {
// // See if you don't have at least version 23.0.1 of platform tools installed
// AndroidSdkHandler sdk = context.getClient().getSdk();
@@ -329,7 +357,7 @@ public class ApiDetector extends ResourceXmlDetector
// return;
// }
// LocalPackage pkgInfo = sdk.getLocalPackage(SdkConstants.FD_PLATFORM_TOOLS,
- // context.getClient().getRepositoryLogger());
+ // context.getClient().getRepositoryLogger());
// if (pkgInfo == null) {
// return;
// }
@@ -342,7 +370,7 @@ public class ApiDetector extends ResourceXmlDetector
// int compileSdkVersion = context.getProject().getBuildSdk();
// if (compileSdkVersion == 23) {
// if (revision.getMajor() > 23 || revision.getMajor() == 23
- // && (revision.getMinor() > 0 || revision.getMicro() > 0)) {
+ // && (revision.getMinor() > 0 || revision.getMicro() > 0)) {
// return;
// }
// } else if (compileSdkVersion <= revision.getMajor()) {
@@ -361,17 +389,17 @@ public class ApiDetector extends ResourceXmlDetector
// firstLineEnd = contents.length();
// }
// location = Location.create(file,
- // new DefaultPosition(0, 0, 0), new
- // DefaultPosition(0, firstLineEnd, firstLineEnd));
+ // new DefaultPosition(0, 0, 0), new
+ // DefaultPosition(0, firstLineEnd, firstLineEnd));
// } else {
// location = Location.create(context.file);
// }
// context.report(UNSUPPORTED,
- // location,
- // String.format("The SDK platform-tools version (%1$s) is too old "
- // + " to check APIs compiled with API %2$d; please update",
- // revision.toShortString(),
- // compileSdkVersion));
+ // location,
+ // String.format("The SDK platform-tools version (%1$s) is too old "
+ // + " to check APIs compiled with API %2$d; please update",
+ // revision.toShortString(),
+ // compileSdkVersion));
//}
}
}
@@ -400,76 +428,76 @@ public class ApiDetector extends ResourceXmlDetector
}
int attributeApiLevel = -1;
- //if (ANDROID_URI.equals(attribute.getNamespaceURI())) {
- // String name = attribute.getLocalName();
- // if (!(name.equals(ATTR_LAYOUT_WIDTH) && !(name.equals(ATTR_LAYOUT_HEIGHT)) &&
- // !(name.equals(ATTR_ID)))) {
- // String owner = "android/R$attr"; //$NON-NLS-1$
- // attributeApiLevel = mApiDatabase.getFieldVersion(owner, name);
- // int minSdk = getMinSdk(context);
- // if (attributeApiLevel > minSdk && attributeApiLevel > context.getFolderVersion()
- // && attributeApiLevel > getLocalMinSdk(attribute.getOwnerElement())
- // && !isBenignUnusedAttribute(name)
- // && !isAlreadyWarnedDrawableFile(context, attribute, attributeApiLevel)) {
- // if (RtlDetector.isRtlAttributeName(name) || ATTR_SUPPORTS_RTL.equals(name)) {
- // // No need to warn for example that
- // // "layout_alignParentEnd will only be used in API level 17 and higher"
- // // 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, as of build tools 23.0.1 aapt works around this by packaging
- // // the resources differently.
- // if (name.equals(ATTR_PADDING_START)) {
- // BuildToolInfo buildToolInfo = context.getProject().getBuildTools();
- // Revision buildTools = buildToolInfo != null
- // ? buildToolInfo.getRevision() : null;
- // boolean isOldBuildTools = buildTools != null &&
- // (buildTools.getMajor() < 23 || buildTools.getMajor() == 23
- // && buildTools.getMinor() == 0 && buildTools.getMicro() == 0);
- // if ((buildTools == null || isOldBuildTools) &&
- // viewMayExtendTextView(attribute.getOwnerElement())) {
- // Location location = context.getLocation(attribute);
- // String message = String.format(
- // "Attribute `%1$s` referenced here can result in a crash on "
- // + "some specific devices older than API %2$d "
- // + "(current min is %3$d)",
- // attribute.getLocalName(), attributeApiLevel, minSdk);
- // //noinspection VariableNotUsedInsideIf
- // if (buildTools != null) {
- // message = String.format("Upgrade `buildToolsVersion` from "
- // + "`%1$s` to at least `23.0.1`; if not, ",
- // buildTools.toShortString())
- // + Character.toLowerCase(message.charAt(0))
- // + message.substring(1);
- // }
- // context.report(UNSUPPORTED, attribute, location, message);
- // }
- // }
- // } else {
- // 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);
- // }
- // }
- // }
- //
- // // Special case:
- // // 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;
- // }
- //}
+ if (ANDROID_URI.equals(attribute.getNamespaceURI())) {
+ String name = attribute.getLocalName();
+ if (!(name.equals(ATTR_LAYOUT_WIDTH) && !(name.equals(ATTR_LAYOUT_HEIGHT)) &&
+ !(name.equals(ATTR_ID)))) {
+ String owner = "android/R$attr"; //$NON-NLS-1$
+ attributeApiLevel = mApiDatabase.getFieldVersion(owner, name);
+ int minSdk = getMinSdk(context);
+ if (attributeApiLevel > minSdk && attributeApiLevel > context.getFolderVersion()
+ && attributeApiLevel > getLocalMinSdk(attribute.getOwnerElement())
+ && !isBenignUnusedAttribute(name)
+ && !isAlreadyWarnedDrawableFile(context, attribute, attributeApiLevel)) {
+ if (RtlDetector.isRtlAttributeName(name) || ATTR_SUPPORTS_RTL.equals(name)) {
+ // No need to warn for example that
+ // "layout_alignParentEnd will only be used in API level 17 and higher"
+ // 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, as of build tools 23.0.1 aapt works around this by packaging
+ // the resources differently.
+ if (name.equals(ATTR_PADDING_START)) {
+ BuildToolInfo buildToolInfo = context.getProject().getBuildTools();
+ Revision buildTools = buildToolInfo != null
+ ? buildToolInfo.getRevision() : null;
+ boolean isOldBuildTools = buildTools != null &&
+ (buildTools.getMajor() < 23 || buildTools.getMajor() == 23
+ && buildTools.getMinor() == 0 && buildTools.getMicro() == 0);
+ if ((buildTools == null || isOldBuildTools) &&
+ viewMayExtendTextView(attribute.getOwnerElement())) {
+ Location location = context.getLocation(attribute);
+ String message = String.format(
+ "Attribute `%1$s` referenced here can result in a crash on "
+ + "some specific devices older than API %2$d "
+ + "(current min is %3$d)",
+ attribute.getLocalName(), attributeApiLevel, minSdk);
+ //noinspection VariableNotUsedInsideIf
+ if (buildTools != null) {
+ message = String.format("Upgrade `buildToolsVersion` from "
+ + "`%1$s` to at least `23.0.1`; if not, ",
+ buildTools.toShortString())
+ + Character.toLowerCase(message.charAt(0))
+ + message.substring(1);
+ }
+ context.report(UNSUPPORTED, attribute, location, message);
+ }
+ }
+ } else {
+ 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);
+ }
+ }
+ }
+
+ // Special case:
+ // 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 owner = null;
@@ -483,12 +511,12 @@ public class ApiDetector extends ResourceXmlDetector
int api = 21;
int minSdk = getMinSdk(context);
if (api > minSdk && api > context.getFolderVersion()
- && api > getLocalMinSdk(attribute.getOwnerElement())) {
+ && api > getLocalMinSdk(attribute.getOwnerElement())) {
Location location = context.getLocation(attribute);
String message;
message = String.format(
"Using theme references in XML drawables requires API level %1$d "
- + "(current min is %2$d)", api,
+ + "(current min is %2$d)", api,
minSdk);
context.report(UNSUPPORTED, attribute, location, message);
// Don't flag individual theme attribute requirements here, e.g. once
@@ -499,14 +527,14 @@ public class ApiDetector extends ResourceXmlDetector
}
}
} else if (value.startsWith(PREFIX_ANDROID) && ATTR_NAME.equals(attribute.getName())
- && TAG_ITEM.equals(attribute.getOwnerElement().getTagName())
- && attribute.getOwnerElement().getParentNode() != null
- && TAG_STYLE.equals(attribute.getOwnerElement().getParentNode().getNodeName())) {
+ && TAG_ITEM.equals(attribute.getOwnerElement().getTagName())
+ && attribute.getOwnerElement().getParentNode() != null
+ && TAG_STYLE.equals(attribute.getOwnerElement().getParentNode().getNodeName())) {
owner = "android/R$attr"; //$NON-NLS-1$
name = value.substring(PREFIX_ANDROID.length());
prefix = null;
} else if (value.startsWith(PREFIX_ANDROID) && ATTR_PARENT.equals(attribute.getName())
- && TAG_STYLE.equals(attribute.getOwnerElement().getTagName())) {
+ && TAG_STYLE.equals(attribute.getOwnerElement().getTagName())) {
owner = "android/R$style"; //$NON-NLS-1$
name = getResourceFieldName(value.substring(PREFIX_ANDROID.length()));
prefix = null;
@@ -531,7 +559,7 @@ public class ApiDetector extends ResourceXmlDetector
int api = mApiDatabase.getFieldVersion(owner, name);
int minSdk = getMinSdk(context);
if (api > minSdk && api > context.getFolderVersion()
- && api > getLocalMinSdk(attribute.getOwnerElement())) {
+ && api > getLocalMinSdk(attribute.getOwnerElement())) {
// Don't complain about resource references in the tools namespace,
// such as for example "tools:layout="@android:layout/list_content",
// used only for designtime previews
@@ -549,8 +577,8 @@ public class ApiDetector extends ResourceXmlDetector
Location location = context.getLocation(attribute);
String message = String.format(
"`%1$s` requires API level %2$d (current min is %3$d), but note "
- + "that attribute `%4$s` is only used in API level %5$d "
- + "and higher",
+ + "that attribute `%4$s` is only used in API level %5$d "
+ + "and higher",
name, api, minSdk, attributeName, attributeApiLevel);
context.report(UNSUPPORTED, attribute, location, message);
} else {
@@ -585,11 +613,11 @@ public class ApiDetector extends ResourceXmlDetector
}
return tag.contains("Text") // TextView, EditText, etc
- || tag.contains(BUTTON) // Button, ToggleButton, etc
- || tag.equals("DigitalClock")
- || tag.equals("Chronometer")
- || tag.equals(CHECK_BOX)
- || tag.equals(SWITCH);
+ || tag.contains(BUTTON) // Button, ToggleButton, etc
+ || tag.equals("DigitalClock")
+ || tag.equals("Chronometer")
+ || tag.equals(CHECK_BOX)
+ || tag.equals(SWITCH);
}
/**
@@ -601,12 +629,12 @@ public class ApiDetector extends ResourceXmlDetector
// Don't complain if it's in a drawable file where we've already
// flagged the root drawable type as being unsupported
if (context.getResourceFolderType() == ResourceFolderType.DRAWABLE
- && attributeApiLevel == 21) {
+ && attributeApiLevel == 21) {
String root = attribute.getOwnerDocument().getDocumentElement().getTagName();
if (TAG_RIPPLE.equals(root)
- || TAG_VECTOR.equals(root)
- || TAG_ANIMATED_VECTOR.equals(root)
- || TAG_ANIMATED_SELECTOR.equals(root)) {
+ || TAG_VECTOR.equals(root)
+ || TAG_ANIMATED_VECTOR.equals(root)
+ || TAG_ANIMATED_SELECTOR.equals(root)) {
return true;
}
}
@@ -624,6 +652,7 @@ public class ApiDetector extends ResourceXmlDetector
public static boolean isBenignUnusedAttribute(@NonNull String name) {
return ATTR_LABEL_FOR.equals(name)
|| ATTR_TEXT_IS_SELECTABLE.equals(name)
+ || "textAlignment".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_ANIMATED_SELECTOR, 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) {
// Root node
@@ -664,7 +699,7 @@ public class ApiDetector extends ResourceXmlDetector
int api = mApiDatabase.getFieldVersion(owner, name);
int minSdk = getMinSdk(context);
if (api > minSdk && api > context.getFolderVersion()
- && api > getLocalMinSdk(element)) {
+ && api > getLocalMinSdk(element)) {
Location location = context.getLocation(textNode);
String message = String.format(
"`%1$s` requires API level %2$d (current min is %3$d)",
@@ -700,7 +735,7 @@ public class ApiDetector extends ResourceXmlDetector
int api = mApiDatabase.getClassVersion(fqn);
int minSdk = getMinSdk(context);
if (api > minSdk && api > context.getFolderVersion()
- && api > getLocalMinSdk(element)) {
+ && api > getLocalMinSdk(element)) {
Location location = context.getLocation(element);
String message = String.format(
"View requires API level %1$d (current min is %2$d): `<%3$s>`",
@@ -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
* the minimum version that the given tag is supported in */
private void checkElement(@NonNull XmlContext context, @NonNull Element element,
@@ -717,9 +786,21 @@ public class ApiDetector extends ResourceXmlDetector
if (tag.equals(element.getTagName())) {
int minSdk = getMinSdk(context);
if (api > minSdk
- && api > context.getFolderVersion()
- && api > getLocalMinSdk(element)) {
+ && api > context.getFolderVersion()
+ && api > getLocalMinSdk(element)
+ && !featureProvidedByGradle(context, gradleVersion)) {
Location location = context.getLocation(element);
+
+ // For the 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;
if (issue == UNSUPPORTED) {
message = String.format(
@@ -729,12 +810,16 @@ public class ApiDetector extends ResourceXmlDetector
message += String.format(
" or building with Android Gradle plugin %1$s or higher",
gradleVersion);
+ } else if (tag.contains(".")) {
+ message = String.format(
+ "Custom drawables requires API level %1$d (current min is %2$d)",
+ api, minSdk);
}
} else {
assert issue == UNUSED : issue;
message = String.format(
"`<%1$s>` is only used in API level %2$d and higher "
- + "(current min is %3$d)", tag, api, minSdk);
+ + "(current min is %3$d)", tag, api, minSdk);
}
context.report(issue, element, location, message);
}
@@ -779,7 +864,7 @@ public class ApiDetector extends ResourceXmlDetector
boolean checkCalls = context.isEnabled(UNSUPPORTED)
|| context.isEnabled(INLINED);
boolean checkMethods = context.isEnabled(OVERRIDE)
- && context.getMainProject().getBuildSdk() >= 1;
+ && context.getMainProject().getBuildSdk() >= 1;
String frameworkParent = null;
if (checkMethods) {
LintDriver driver = context.getDriver();
@@ -788,9 +873,9 @@ public class ApiDetector extends ResourceXmlDetector
// For virtual dispatch, walk up the inheritance chain checking
// each inherited method
if ((owner.startsWith("android/") //$NON-NLS-1$
- && !owner.startsWith("android/support/")) //$NON-NLS-1$
- || owner.startsWith("java/") //$NON-NLS-1$
- || owner.startsWith("javax/")) { //$NON-NLS-1$
+ && !owner.startsWith("android/support/")) //$NON-NLS-1$
+ || owner.startsWith("java/") //$NON-NLS-1$
+ || owner.startsWith("javax/")) { //$NON-NLS-1$
frameworkParent = owner;
break;
}
@@ -808,7 +893,7 @@ public class ApiDetector extends ResourceXmlDetector
}
if (classNode.interfaces != null) {
@SuppressWarnings("unchecked") // ASM API
- List interfaceList = classNode.interfaces;
+ List interfaceList = classNode.interfaces;
for (String signature : interfaceList) {
checkExtendsClass(context, classNode, classMinSdk, signature);
}
@@ -868,11 +953,11 @@ public class ApiDetector extends ResourceXmlDetector
if (api > minSdk) {
String fqcn = getFqcn(className);
String message = String.format(
- "Class requires API level %1$d (current min is %2$d): `%3$s`",
- api, minSdk, fqcn);
+ "Class requires API level %1$d (current min is %2$d): `%3$s`",
+ api, minSdk, fqcn);
report(context, message, var.start, method,
- className.substring(className.lastIndexOf('/') + 1), null,
- SearchHints.create(NEAREST).matchJavaSymbol());
+ className.substring(className.lastIndexOf('/') + 1), null,
+ SearchHints.create(NEAREST).matchJavaSymbol());
}
}
}
@@ -891,11 +976,11 @@ public class ApiDetector extends ResourceXmlDetector
if (api > minSdk) {
String fqcn = getFqcn(type);
String message = String.format(
- "Class requires API level %1$d (current min is %2$d): `%3$s`",
- api, minSdk, fqcn);
+ "Class requires API level %1$d (current min is %2$d): `%3$s`",
+ api, minSdk, fqcn);
AbstractInsnNode first = nodes.size() > 0 ? nodes.get(0) : null;
report(context, message, first, method, method.name, null,
- SearchHints.create(BACKWARD).matchJavaSymbol());
+ SearchHints.create(BACKWARD).matchJavaSymbol());
}
}
}
@@ -913,7 +998,7 @@ public class ApiDetector extends ResourceXmlDetector
// No need to check methods in this local class; we know they
// won't be an API match
if (node.getOpcode() == Opcodes.INVOKEVIRTUAL
- && owner.equals(classNode.name)) {
+ && owner.equals(classNode.name)) {
owner = classNode.superName;
}
@@ -932,8 +1017,8 @@ public class ApiDetector extends ResourceXmlDetector
}
if (!checkingSuperClass
- && node.getOpcode() == Opcodes.INVOKEVIRTUAL
- && methodDefinedLocally(classNode, name, desc)) {
+ && node.getOpcode() == Opcodes.INVOKEVIRTUAL
+ && methodDefinedLocally(classNode, name, desc)) {
break;
}
@@ -948,15 +1033,15 @@ public class ApiDetector extends ResourceXmlDetector
api, minSdk, fqcn);
if (name.equals(ORDINAL_METHOD)
- && instruction.getNext() != null
- && instruction.getNext().getNext() != null
- && instruction.getNext().getOpcode() == Opcodes.IALOAD
- && instruction.getNext().getNext().getOpcode()
- == Opcodes.TABLESWITCH) {
+ && instruction.getNext() != null
+ && instruction.getNext().getNext() != null
+ && instruction.getNext().getOpcode() == Opcodes.IALOAD
+ && instruction.getNext().getNext().getOpcode()
+ == Opcodes.TABLESWITCH) {
message = String.format(
- "Enum for switch requires API level %1$d " +
- "(current min is %2$d): `%3$s`",
- api, minSdk, getFqcn(owner));
+ "Enum for switch requires API level %1$d " +
+ "(current min is %2$d): `%3$s`",
+ api, minSdk, getFqcn(owner));
}
// If you're simply calling super.X from method X, even if method X
@@ -965,24 +1050,24 @@ public class ApiDetector extends ResourceXmlDetector
// API levels. (There is a danger of somebody calling that method
// locally in other contexts, but this is hopefully unlikely.)
if (instruction.getOpcode() == Opcodes.INVOKESPECIAL &&
- name.equals(method.name) && desc.equals(method.desc) &&
- // We specifically exclude constructors from this check,
- // because we do want to flag constructors requiring the
- // new API level; it's highly likely that the constructor
- // is called by local code so you should specifically
- // investigate this as a developer
- !name.equals(CONSTRUCTOR_NAME)) {
+ name.equals(method.name) && desc.equals(method.desc) &&
+ // We specifically exclude constructors from this check,
+ // because we do want to flag constructors requiring the
+ // new API level; it's highly likely that the constructor
+ // is called by local code so you should specifically
+ // investigate this as a developer
+ !name.equals(CONSTRUCTOR_NAME)) {
break;
}
if (isWithinSdkConditional(context, classNode, method, instruction,
- api)) {
+ api)) {
break;
}
if (api == 19
- && owner.equals("java/lang/ReflectiveOperationException")
- && !method.tryCatchBlocks.isEmpty()) {
+ && owner.equals("java/lang/ReflectiveOperationException")
+ && !method.tryCatchBlocks.isEmpty()) {
boolean direct = false;
for (Object o : method.tryCatchBlocks) {
if (((TryCatchBlockNode)o).type.equals("java/lang/ReflectiveOperationException")) {
@@ -992,24 +1077,32 @@ public class ApiDetector extends ResourceXmlDetector
}
if (!direct) {
message = String.format("Multi-catch with these reflection "
- + "exceptions requires API level 19 (current min is"
- + " %2$d) because they get compiled to the common but "
- + "new super type `ReflectiveOperationException`. "
- + "As a workaround either create individual catch "
- + "statements, or catch `Exception`.",
- api, minSdk);
+ + "exceptions requires API level 19 (current min is"
+ + " %2$d) because they get compiled to the common but "
+ + "new super type `ReflectiveOperationException`. "
+ + "As a workaround either create individual catch "
+ + "statements, or catch `Exception`.",
+ api, minSdk);
}
}
+ 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,
- SearchHints.create(FORWARD).matchJavaSymbol());
+ SearchHints.create(FORWARD).matchJavaSymbol());
break;
}
// For virtual dispatch, walk up the inheritance chain checking
// each inherited method
if (owner.startsWith("android/") //$NON-NLS-1$
- || owner.startsWith("javax/")) { //$NON-NLS-1$
+ || owner.startsWith("javax/")) { //$NON-NLS-1$
// The API map has already inlined all inherited methods
// so no need to keep checking up the chain
// -- unless it's the support library which is also in
@@ -1044,7 +1137,7 @@ public class ApiDetector extends ResourceXmlDetector
if (api > minSdk) {
if (method.name.startsWith(SWITCH_TABLE_PREFIX)) {
checkSwitchBlock(context, classNode, node, method, name, owner,
- api, minSdk);
+ api, minSdk);
continue;
}
@@ -1061,7 +1154,7 @@ public class ApiDetector extends ResourceXmlDetector
"Field requires API level %1$d (current min is %2$d): `%3$s`",
api, minSdk, fqcn);
report(context, message, node, method, name, null,
- SearchHints.create(FORWARD).matchJavaSymbol());
+ SearchHints.create(FORWARD).matchJavaSymbol());
}
} else if (type == AbstractInsnNode.LDC_INSN) {
LdcInsnNode node = (LdcInsnNode) instruction;
@@ -1076,8 +1169,8 @@ public class ApiDetector extends ResourceXmlDetector
"Class requires API level %1$d (current min is %2$d): `%3$s`",
api, minSdk, fqcn);
report(context, message, node, method,
- className.substring(className.lastIndexOf('/') + 1), null,
- SearchHints.create(FORWARD).matchJavaSymbol());
+ className.substring(className.lastIndexOf('/') + 1), null,
+ SearchHints.create(FORWARD).matchJavaSymbol());
}
}
}
@@ -1099,7 +1192,7 @@ public class ApiDetector extends ResourceXmlDetector
SearchHints hints = SearchHints.create(BACKWARD).matchJavaSymbol();
int lineNumber = ClassContext.findLineNumber(classNode);
Location location = context.getLocationForLine(lineNumber, name, null,
- hints);
+ hints);
context.report(UNSUPPORTED, location, message);
}
}
@@ -1131,7 +1224,7 @@ public class ApiDetector extends ResourceXmlDetector
"The pattern character '%1$c' requires API level 9 (current " +
"min is %2$d) : \"`%3$s`\"", c, minSdk, pattern);
report(context, message, node, method, pattern, null,
- SearchHints.create(FORWARD));
+ SearchHints.create(FORWARD));
return;
}
}
@@ -1246,15 +1339,15 @@ public class ApiDetector extends ResourceXmlDetector
}
MethodInsnNode node = (MethodInsnNode) instruction;
if (node.name.equals(method.name)
- && node.desc.equals(method.desc)
- && node.owner.equals(classNode.name)) {
+ && node.desc.equals(method.desc)
+ && node.owner.equals(classNode.name)) {
// Find lookup switch
AbstractInsnNode target = getNextInstruction(node);
while (target != null) {
if (target.getOpcode() == Opcodes.LOOKUPSWITCH) {
LookupSwitchInsnNode lookup = (LookupSwitchInsnNode) target;
@SuppressWarnings("unchecked") // ASM API
- List keys = lookup.keys;
+ List keys = lookup.keys;
if (keys != null && keys.contains(ordinal)) {
String fqcn = getFqcn(owner) + '#' + name;
String message = String.format(
@@ -1262,7 +1355,7 @@ public class ApiDetector extends ResourceXmlDetector
"(current min is %2$d): `%3$s`",
api, minSdk, fqcn);
report(context, message, lookup, (MethodNode) m, name, null,
- SearchHints.create(FORWARD).matchJavaSymbol());
+ SearchHints.create(FORWARD).matchJavaSymbol());
// Break out of the inner target search only; the switch
// statement could be used in other places in this class as
@@ -1279,7 +1372,7 @@ public class ApiDetector extends ResourceXmlDetector
private static boolean isEnumSwitchInitializer(ClassNode classNode) {
@SuppressWarnings("rawtypes") // ASM API
- List fieldList = classNode.fields;
+ List fieldList = classNode.fields;
for (Object f : fieldList) {
FieldNode field = (FieldNode) f;
if (field.name.startsWith(ENUM_SWITCH_PREFIX)) {
@@ -1292,7 +1385,7 @@ public class ApiDetector extends ResourceXmlDetector
private static MethodNode findEnumSwitchUsage(ClassNode classNode, String owner) {
String target = ENUM_SWITCH_PREFIX + owner.replace('/', '$');
@SuppressWarnings("rawtypes") // ASM API
- List methodList = classNode.methods;
+ List methodList = classNode.methods;
for (Object f : methodList) {
MethodNode method = (MethodNode) f;
InsnList nodes = method.instructions;
@@ -1319,10 +1412,10 @@ public class ApiDetector extends ResourceXmlDetector
// chain
AbstractInsnNode next = getNextInstruction(node);
if (next != null && next.getOpcode() == Opcodes.INVOKEVIRTUAL
- && CLASS_CONSTRUCTOR.equals(method.name)
- && ORDINAL_METHOD.equals(((MethodInsnNode) next).name)
- && classNode.outerClass != null
- && isEnumSwitchInitializer(classNode)) {
+ && CLASS_CONSTRUCTOR.equals(method.name)
+ && ORDINAL_METHOD.equals(((MethodInsnNode) next).name)
+ && classNode.outerClass != null
+ && isEnumSwitchInitializer(classNode)) {
LintDriver driver = context.getDriver();
ClassNode outer = driver.getOuterClassNode(classNode);
if (outer != null) {
@@ -1330,13 +1423,13 @@ public class ApiDetector extends ResourceXmlDetector
if (switchUser != null) {
// Is the API check suppressed at the call site?
if (driver.isSuppressed(UNSUPPORTED, outer, switchUser,
- null)) {
+ null)) {
return true;
}
// Is there a @TargetAPI annotation on the method or
// class referencing this switch map class?
if (getLocalMinSdk(switchUser.invisibleAnnotations) >= api
- || getLocalMinSdk(outer.invisibleAnnotations) >= api) {
+ || getLocalMinSdk(outer.invisibleAnnotations) >= api) {
return true;
}
}
@@ -1365,11 +1458,11 @@ public class ApiDetector extends ResourceXmlDetector
// TODO: Should this be "curr" instead?
if (prev.outerMethod != null) {
@SuppressWarnings("rawtypes") // ASM API
- List methods = classNode.methods;
+ List methods = classNode.methods;
for (Object m : methods) {
MethodNode method = (MethodNode) m;
if (method.name.equals(prev.outerMethod)
- && method.desc.equals(prev.outerMethodDesc)) {
+ && method.desc.equals(prev.outerMethodDesc)) {
// Found the outer method for this anonymous class; check method
// annotations on it, then continue up the class hierarchy
int methodMinSdk = getLocalMinSdk(method.invisibleAnnotations);
@@ -1408,7 +1501,7 @@ public class ApiDetector extends ResourceXmlDetector
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("value")) { //$NON-NLS-1$
+ if (key.equals(ATTR_VALUE)) { //$NON-NLS-1$
Object value = annotation.values.get(i + 1);
if (value instanceof Integer) {
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;
}
+ /**
+ * 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,
MethodNode method, String patternStart, String patternEnd, SearchHints hints) {
int lineNumber = node != null ? ClassContext.findLineNumber(node) : -1;
@@ -1464,7 +1597,7 @@ public class ApiDetector extends ResourceXmlDetector
// If looking for a constructor, the string we'll see in the source is not the
// method name () but the class name
if (patternStart != null && patternStart.equals(CONSTRUCTOR_NAME)
- && node instanceof MethodInsnNode) {
+ && node instanceof MethodInsnNode) {
if (hints != null) {
hints = hints.matchConstructor();
}
@@ -1483,7 +1616,7 @@ public class ApiDetector extends ResourceXmlDetector
}
Location location = context.getLocationForLine(lineNumber, patternStart, patternEnd,
- hints);
+ hints);
context.report(UNSUPPORTED, method, node, location, message);
}
@@ -1542,13 +1675,13 @@ public class ApiDetector extends ResourceXmlDetector
return true;
}
if (owner.equals("android/view/ViewGroup$LayoutParams") //$NON-NLS-1$
- && name.equals("MATCH_PARENT")) { //$NON-NLS-1$
+ && name.equals("MATCH_PARENT")) { //$NON-NLS-1$
return true;
}
if (owner.equals("android/widget/AbsListView") //$NON-NLS-1$
- && ((name.equals("CHOICE_MODE_NONE") //$NON-NLS-1$
- || name.equals("CHOICE_MODE_MULTIPLE") //$NON-NLS-1$
- || name.equals("CHOICE_MODE_SINGLE")))) { //$NON-NLS-1$
+ && ((name.equals("CHOICE_MODE_NONE") //$NON-NLS-1$
+ || name.equals("CHOICE_MODE_MULTIPLE") //$NON-NLS-1$
+ || name.equals("CHOICE_MODE_SINGLE")))) { //$NON-NLS-1$
// android.widget.ListView#CHOICE_MODE_MULTIPLE and friends have API=1,
// but in API 11 it was moved up to the parent class AbsListView.
// Referencing AbsListView#CHOICE_MODE_MULTIPLE technically requires API 11,
@@ -1561,7 +1694,7 @@ public class ApiDetector extends ResourceXmlDetector
// be backwards compatible (by using the same lower bits for START as LEFT and
// for END as RIGHT)
if ("android/view/Gravity".equals(owner) //$NON-NLS-1$
- && ("START".equals(name) || "END".equals(name))) { //$NON-NLS-1$ //$NON-NLS-2$
+ && ("START".equals(name) || "END".equals(name))) { //$NON-NLS-1$ //$NON-NLS-2$
return true;
}
@@ -1681,8 +1814,8 @@ public class ApiDetector extends ResourceXmlDetector
Location location = mContext.getUastLocation(node);
String message = String.format("Cast from %1$s to %2$s requires API level %3$d (current min is %4$d)",
- UastLintUtils.getClassName(classType),
- UastLintUtils.getClassName(interfaceType), api, minSdk);
+ UastLintUtils.getClassName(classType),
+ UastLintUtils.getClassName(interfaceType), api, minSdk);
mContext.report(UNSUPPORTED, location, message);
}
@@ -1696,7 +1829,7 @@ public class ApiDetector extends ResourceXmlDetector
if (!isSuppressed(api, method, minSdk, mContext)) {
Location location = mContext.getLocation((PsiElement) method);
String message = String.format("Default method requires API level %1$d "
- + "(current min is %2$d)", api, minSdk);
+ + "(current min is %2$d)", api, minSdk);
mContext.reportUast(UNSUPPORTED, method, location, message);
}
}
@@ -1718,7 +1851,7 @@ public class ApiDetector extends ResourceXmlDetector
if (!isSuppressed(api, aClass, minSdk, mContext)) {
Location location = mContext.getLocation(annotation);
String message = String.format("Repeatable annotation requires "
- + "API level %1$d (current min is %2$d)", api, minSdk);
+ + "API level %1$d (current min is %2$d)", api, minSdk);
mContext.report(UNSUPPORTED, annotation, location, message);
}
} else if ("java.lang.annotation.Target".equals(name)) {
@@ -1750,14 +1883,14 @@ public class ApiDetector extends ResourceXmlDetector
UReferenceExpression ref = (UReferenceExpression) element;
String referenceName = UastLintUtils.getReferenceName(ref);
if ("TYPE_PARAMETER".equals(referenceName)
- || "TYPE_USE".equals(referenceName)) {
+ || "TYPE_USE".equals(referenceName)) {
PsiAnnotation retention = modifierList
.findAnnotation("java.lang.annotation.Retention");
if (retention == null ||
- retention.getText().contains("RUNTIME")) {
+ retention.getText().contains("RUNTIME")) {
Location location = mContext.getLocation(element);
String message = String.format("Type annotations are not "
- + "supported in Android: %1$s", referenceName);
+ + "supported in Android: %1$s", referenceName);
mContext.report(UNSUPPORTED, element, location, message);
}
}
@@ -1783,11 +1916,11 @@ public class ApiDetector extends ResourceXmlDetector
UExpression argument = arguments.get(i);
PsiType argumentType = argument.getExpressionType();
if (argumentType == null || parameterType.equals(argumentType)
- || !(argumentType instanceof PsiClassType)) {
+ || !(argumentType instanceof PsiClassType)) {
continue;
}
checkCast(argument, (PsiClassType) argumentType,
- (PsiClassType) parameterType);
+ (PsiClassType) parameterType);
}
}
}
@@ -1809,21 +1942,33 @@ public class ApiDetector extends ResourceXmlDetector
// Look for @RequiresApi in modifier lists
private boolean checkRequiresApi(UCallExpression expression, PsiMethod method,
- PsiModifierList modifierList) {
+ PsiModifierList modifierList) {
for (PsiAnnotation annotation : modifierList.getAnnotations()) {
if (REQUIRES_API_ANNOTATION.equals(annotation.getQualifiedName())) {
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);
if (api > minSdk) {
int target = getTargetApi(expression);
if (target == -1 || api > target) {
+ if (ApiDetector.isWithinVersionCheckConditional(expression, api, mContext)) {
+ return true;
+ }
+ if (ApiDetector.isPrecededByVersionCheckExit(expression, api, mContext)) {
+ return true;
+ }
+
Location location;
location = mContext.getUastLocation(expression);
String fqcn = method.getName();
String message = String.format(
- "Call requires API level %1$d (current min is %2$d): `%3$s`",
- api, minSdk, fqcn);
+ "Call requires API level %1$d (current min is %2$d): `%3$s`",
+ api, minSdk, fqcn);
mContext.report(UNSUPPORTED, location, message);
}
}
@@ -1905,7 +2050,7 @@ public class ApiDetector extends ResourceXmlDetector
if (api > minSdk && api > getTargetApi(statement)) {
Location location = mContext.getUastLocation(statement);
String message = String.format("Try-with-resources requires "
- + "API level %1$d (current min is %2$d)", api, minSdk);
+ + "API level %1$d (current min is %2$d)", api, minSdk);
LintDriver driver = mContext.getDriver();
if (!driver.isSuppressed(mContext, UNSUPPORTED, statement)) {
mContext.report(UNSUPPORTED, statement, location, message);
@@ -2033,7 +2178,7 @@ public class ApiDetector extends ResourceXmlDetector
if (api != -1) {
int minSdk = getMinSdk(mContext);
if (api > minSdk
- && api > getTargetApi(node)) {
+ && api > getTargetApi(node)) {
if (isBenignConstantUsage(node, name, owner)) {
return true;
}
@@ -2087,19 +2232,19 @@ public class ApiDetector extends ResourceXmlDetector
return true;
}
//if (mySeenTargetApi) {
- int target = getTargetApi(element);
- if (target != -1) {
- if (api <= target) {
- return true;
- }
+ int target = getTargetApi(element);
+ if (target != -1) {
+ if (api <= target) {
+ return true;
}
+ }
//}
-// TODO: This MUST BE RESTORED
-// if (context.getDriver().isSuppressed(UNSUPPORTED, element))
-// if (/*mySeenSuppress &&*/
-// (IntellijLintUtils.isSuppressed(element, myFile, UNSUPPORTED) || IntellijLintUtils.isSuppressed(element, myFile, INLINED))) {
-// return true;
-// }
+ // TODO: This MUST BE RESTORED
+ // if (context.getDriver().isSuppressed(UNSUPPORTED, element))
+ // if (/*mySeenSuppress &&*/
+ // (IntellijLintUtils.isSuppressed(element, myFile, UNSUPPORTED) || IntellijLintUtils.isSuppressed(element, myFile, INLINED))) {
+ // return true;
+ // }
if (isWithinVersionCheckConditional(element, api, context)) {
return true;
@@ -2143,8 +2288,10 @@ public class ApiDetector extends ResourceXmlDetector
for (PsiAnnotation annotation : modifierList.getAnnotations()) {
String fqcn = annotation.getQualifiedName();
- if (fqcn != null && (fqcn.equals(FQCN_TARGET_API) || fqcn.equals(REQUIRES_API_ANNOTATION)
- || fqcn.equals(TARGET_API))) { // when missing imports
+ if (fqcn != null &&
+ (fqcn.equals(FQCN_TARGET_API)
+ || fqcn.equals(REQUIRES_API_ANNOTATION)
+ || fqcn.equals(TARGET_API))) { // when missing imports
PsiAnnotationParameterList parameterList = annotation.getParameterList();
for (PsiNameValuePair pair : parameterList.getAttributes()) {
PsiAnnotationMemberValue v = pair.getValue();
@@ -2332,8 +2479,8 @@ public class ApiDetector extends ResourceXmlDetector
@Override
protected void add(@NonNull AbstractInsnNode from, @NonNull AbstractInsnNode to) {
if (from.getType() == AbstractInsnNode.JUMP_INSN &&
- from.getPrevious() != null &&
- from.getPrevious().getType() == AbstractInsnNode.INT_INSN) {
+ from.getPrevious() != null &&
+ from.getPrevious().getType() == AbstractInsnNode.INT_INSN) {
IntInsnNode intNode = (IntInsnNode) from.getPrevious();
if (intNode.getPrevious() != null && isSdkVersionLookup(intNode.getPrevious())) {
JumpInsnNode jumpNode = (JumpInsnNode) from;
@@ -2372,6 +2519,7 @@ public class ApiDetector extends ResourceXmlDetector
}
private static class VersionCheckWithExitFinder extends AbstractUastVisitor {
+
private final UExpression mExpression;
private final UElement mEndElement;
private final int mApi;
@@ -2529,7 +2677,7 @@ public class ApiDetector extends ResourceXmlDetector
UExpression returnValue = returnStatement.getReturnExpression();
if (returnValue instanceof UBinaryExpression) {
Boolean isConditional = isVersionCheckConditional(api, null, null,
- (UBinaryExpression) returnValue);
+ (UBinaryExpression) returnValue);
if (isConditional != null) {
return isConditional;
}
@@ -2548,8 +2696,8 @@ public class ApiDetector extends ResourceXmlDetector
@NonNull UBinaryExpression binary) {
UastBinaryOperator tokenType = binary.getOperator();
if (tokenType == UastBinaryOperator.GREATER || tokenType == UastBinaryOperator.GREATER_OR_EQUAL ||
- tokenType == UastBinaryOperator.LESS_OR_EQUAL || tokenType == UastBinaryOperator.LESS ||
- tokenType == UastBinaryOperator.EQUALS || tokenType == UastBinaryOperator.IDENTITY_EQUALS) {
+ tokenType == UastBinaryOperator.LESS_OR_EQUAL || tokenType == UastBinaryOperator.LESS ||
+ tokenType == UastBinaryOperator.EQUALS || tokenType == UastBinaryOperator.IDENTITY_EQUALS) {
UExpression left = binary.getLeftOperand();
if (left instanceof UReferenceExpression) {
UReferenceExpression ref = (UReferenceExpression)left;
@@ -2591,7 +2739,7 @@ public class ApiDetector extends ResourceXmlDetector
return level >= api && fromElse;
}
else if (tokenType == UastBinaryOperator.EQUALS
- || tokenType == UastBinaryOperator.IDENTITY_EQUALS) {
+ || tokenType == UastBinaryOperator.IDENTITY_EQUALS) {
// if (SDK_INT == ICE_CREAM_SANDWICH) { } else { }
return level >= api && fromThen;
} else {
@@ -2601,7 +2749,7 @@ public class ApiDetector extends ResourceXmlDetector
}
}
} else if (tokenType == UastBinaryOperator.LOGICAL_AND
- && (ifStatement != null && prev == ifStatement.getThenExpression())) {
+ && (ifStatement != null && prev == ifStatement.getThenExpression())) {
if (isAndedWithConditional(ifStatement.getCondition(), api, prev)) {
return true;
}
@@ -2614,9 +2762,9 @@ public class ApiDetector extends ResourceXmlDetector
UBinaryExpression inner = (UBinaryExpression) element;
if (inner.getOperator() == UastBinaryOperator.LOGICAL_AND) {
return isAndedWithConditional(inner.getLeftOperand(), api, before) ||
- inner.getRightOperand() != before && isAndedWithConditional(inner.getRightOperand(), api, before);
+ inner.getRightOperand() != before && isAndedWithConditional(inner.getRightOperand(), api, before);
} else if (inner.getLeftOperand() instanceof UReferenceExpression &&
- SDK_INT.equals(((UReferenceExpression)inner.getLeftOperand()).getResolvedName())) {
+ SDK_INT.equals(((UReferenceExpression)inner.getLeftOperand()).getResolvedName())) {
int level = -1;
UastOperator tokenType = inner.getOperator();
UExpression right = inner.getRightOperand();
@@ -2644,7 +2792,7 @@ public class ApiDetector extends ResourceXmlDetector
return level >= api - 1;
}
else if (tokenType == UastBinaryOperator.EQUALS
- || tokenType == UastBinaryOperator.IDENTITY_EQUALS) {
+ || tokenType == UastBinaryOperator.IDENTITY_EQUALS) {
// if (SDK_INT == ICE_CREAM_SANDWICH) &&
return level >= api;
}
diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ToastDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ToastDetector.java
index b68628be1cf..366e17a0ee3 100644
--- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ToastDetector.java
+++ b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/ToastDetector.java
@@ -28,12 +28,7 @@ import com.android.tools.klint.detector.api.Scope;
import com.android.tools.klint.detector.api.Severity;
import com.intellij.psi.PsiMethod;
-import org.jetbrains.uast.UCallExpression;
-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.*;
import org.jetbrains.uast.visitor.AbstractUastVisitor;
import org.jetbrains.uast.visitor.UastVisitor;
@@ -90,32 +85,34 @@ public class ToastDetector extends Detector implements Detector.UastScanner {
"duration value is not supported");
}
}
-
- UMethod surroundingMethod = UastUtils.getContainingUMethod(call);
- if (surroundingMethod == null) {
+ UElement surroundingDeclaration = UastUtils.getParentOfType(
+ call, true,
+ UMethod.class, UBlockExpression.class, ULambdaExpression.class);
+
+ if (surroundingDeclaration == null) {
return;
}
ShowFinder finder = new ShowFinder(call);
- surroundingMethod.getUastBody().accept(finder);
+ surroundingDeclaration.accept(finder);
if (!finder.isShowCalled()) {
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()` ?");
}
}
private static class ShowFinder extends AbstractUastVisitor {
/** The target makeText call */
- private final UExpression mTarget;
+ private final UCallExpression mTarget;
/** Whether we've found the show method */
private boolean mFound;
/** Whether we've seen the target makeText node yet */
private boolean mSeenTarget;
private ShowFinder(UCallExpression target) {
- mTarget = UastUtils.getQualifiedParentOrThis(target);
+ mTarget = target;
}
@Override
@@ -123,14 +120,14 @@ public class ToastDetector extends Detector implements Detector.UastScanner {
if (node.equals(mTarget)) {
mSeenTarget = true;
} else {
- if ((mTarget.equals(node.getReceiver()))
- && "show".equals(node.getMethodName())) {
+ if ((mSeenTarget || mTarget.equals(node.getReceiver()))
+ && "show".equals(node.getMethodName())) {
// TODO: Do more flow analysis to see whether we're really calling show
// on the right type of object?
mFound = true;
}
}
-
+
return super.visitCallExpression(node);
}
@@ -140,7 +137,7 @@ public class ToastDetector extends Detector implements Detector.UastScanner {
// If you just do "return Toast.makeText(...) don't warn
mFound = true;
}
-
+
return super.visitReturnExpression(node);
}
diff --git a/plugins/uast-kotlin/test/org.jetbrains.kotlin.uast/AbstractKotlinLintTest.kt b/plugins/uast-kotlin/test/org.jetbrains.kotlin.uast/AbstractKotlinLintTest.kt
index ab7cd30fd24..25750cb366a 100644
--- a/plugins/uast-kotlin/test/org.jetbrains.kotlin.uast/AbstractKotlinLintTest.kt
+++ b/plugins/uast-kotlin/test/org.jetbrains.kotlin.uast/AbstractKotlinLintTest.kt
@@ -16,6 +16,7 @@
package org.jetbrains.kotlin.uast
+import org.jetbrains.android.inspections.klint.AndroidLintInspectionBase
import org.jetbrains.kotlin.android.KotlinAndroidTestCase
import org.jetbrains.kotlin.idea.test.ConfigLibraryUtil
import org.jetbrains.kotlin.test.InTextDirectivesUtils.findStringWithPrefixes
@@ -27,7 +28,7 @@ abstract class AbstractKotlinLintTest : KotlinAndroidTestCase() {
override fun setUp() {
super.setUp()
ConfigLibraryUtil.configureKotlinRuntime(myModule)
- // AndroidLintInspectionBase.invalidateInspectionShortName2IssueMap()
+ AndroidLintInspectionBase.invalidateInspectionShortName2IssueMap()
}
override fun tearDown() {
@@ -45,29 +46,28 @@ abstract class AbstractKotlinLintTest : KotlinAndroidTestCase() {
inspectionClassNames += className
}
- //TODO
-// myFixture.enableInspections(*inspectionClassNames.map { className ->
-// val inspectionClass = Class.forName(className)
-// inspectionClass.newInstance() as AndroidLintInspectionBase
-// }.toTypedArray())
+ myFixture.enableInspections(*inspectionClassNames.map { className ->
+ val inspectionClass = Class.forName(className)
+ inspectionClass.newInstance() as AndroidLintInspectionBase
+ }.toTypedArray())
-// val additionalResourcesDir = File(ktFile.parentFile, getTestName(true))
-// if (additionalResourcesDir.exists()) {
-// for (file in additionalResourcesDir.listFiles()) {
-// if (file.isFile) {
-// myFixture.copyFileToProject(file.absolutePath, file.name)
-// }
-// else if (file.isDirectory) {
-// myFixture.copyDirectoryToProject(file.absolutePath, file.name)
-// }
-// }
-// }
-//
-// val virtualFile = myFixture.copyFileToProject(ktFile.absolutePath, "src/" + getTestName(true) + ".kt");
-// myFixture.configureFromExistingVirtualFile(virtualFile)
-//
-// myFixture.doHighlighting()
-// myFixture.checkHighlighting(true, false, false)
+ val additionalResourcesDir = File(ktFile.parentFile, getTestName(true))
+ if (additionalResourcesDir.exists()) {
+ for (file in additionalResourcesDir.listFiles()) {
+ if (file.isFile) {
+ myFixture.copyFileToProject(file.absolutePath, file.name)
+ }
+ else if (file.isDirectory) {
+ myFixture.copyDirectoryToProject(file.absolutePath, file.name)
+ }
+ }
+ }
+
+ val virtualFile = myFixture.copyFileToProject(ktFile.absolutePath, "src/" + getTestName(true) + ".kt");
+ myFixture.configureFromExistingVirtualFile(virtualFile)
+
+ myFixture.doHighlighting()
+ myFixture.checkHighlighting(true, false, false)
}
override fun getTestDataPath() = KotlinTestUtils.getHomeDirectory() + "/plugins/uast-kotlin/testData/lint/"
diff --git a/plugins/uast-kotlin/testData/lint/apiCheck.kt b/plugins/uast-kotlin/testData/lint/apiCheck.kt
index 2eef109c83d..65017f80a28 100644
--- a/plugins/uast-kotlin/testData/lint/apiCheck.kt
+++ b/plugins/uast-kotlin/testData/lint/apiCheck.kt
@@ -1,4 +1,4 @@
-// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
+// INSPECTION_CLASS: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintNewApiInspection
// INSPECTION_CLASS2: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintInlinedApiInspection
// INSPECTION_CLASS3: org.jetbrains.android.inspections.klint.AndroidLintInspectionToolProvider$AndroidKLintOverrideInspection
@@ -31,52 +31,51 @@ import android.widget.TextView
class ApiCallTest: Activity() {
fun method(chronometer: Chronometer, locator: DOMLocator) {
- chronometer.setBackground(null)
+ chronometer.setBackground(null)
// Ok
Bundle().getInt("")
// Ok, this constant is inlined
- View.SYSTEM_UI_FLAG_FULLSCREEN
-
+ View.SYSTEM_UI_FLAG_FULLSCREEN
+
// Virtual call
- getActionBar() // API 11
- actionBar // API 11
+ getActionBar() // API 11
+ actionBar // API 11
// Class references (no call or field access)
val error: DOMError? = null // API 8
val clz = DOMErrorHandler::class // API 8
// Method call
- chronometer.onChronometerTickListener // API 3
+ chronometer.onChronometerTickListener // API 3
// Inherited method call (from TextView
- chronometer.setTextIsSelectable(true) // API 11
+ chronometer.setTextIsSelectable(true) // API 11
- // TODO: fix UClassLiteralExpression and uncomment, must be: error descr="Class requires API level 14 (current min is 1): `GridLayout`"
GridLayout::class
// Field access
- val field = OpcodeInfo.MAXIMUM_VALUE // API 11
+ val field = OpcodeInfo.MAXIMUM_VALUE // API 11
val fillParent = LayoutParams.FILL_PARENT // API 1
// This is a final int, which means it gets inlined
val matchParent = LayoutParams.MATCH_PARENT // API 8
// Field access: non final
- val batteryInfo = report!!.batteryInfo
+ val batteryInfo = report!!.batteryInfo
// Enum access
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
- val mode = PorterDuff.Mode.OVERLAY // API 11
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
+ val mode = PorterDuff.Mode.OVERLAY // API 11
}
}
fun test(rect: Rect) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
RectEvaluator(rect); // OK
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (rect != null) {
RectEvaluator(rect); // OK
}
@@ -84,183 +83,183 @@ class ApiCallTest: Activity() {
}
fun test2(rect: Rect) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
RectEvaluator(rect); // OK
}
}
fun test3(rect: Rect) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
- RectEvaluator(); // ERROR
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
+ RectEvaluator(); // ERROR
}
}
fun test4(rect: Rect) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
System.out.println("Something");
RectEvaluator(rect); // OK
} else {
- RectEvaluator(rect); // ERROR
+ RectEvaluator(rect); // ERROR
}
}
fun test5(rect: Rect) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) {
- RectEvaluator(rect); // ERROR
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) {
+ RectEvaluator(rect); // ERROR
} else {
- RectEvaluator(rect); // ERROR
+ RectEvaluator(rect); // ERROR
}
}
fun test(priority: Boolean, layout: ViewGroup) {
if (layout is LinearLayout) {}
layout as? LinearLayout
-
- if (layout is GridLayout) {}
- layout as? GridLayout
-
- if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+
+ if (layout is GridLayout) {}
+ layout as? GridLayout
+
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
GridLayout(null).getOrientation(); // Not flagged
} else {
- GridLayout(null).getOrientation(); // Flagged
+ GridLayout(null).getOrientation(); // Flagged
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
GridLayout(null).getOrientation(); // Not flagged
} else {
- GridLayout(null).getOrientation(); // Flagged
+ GridLayout(null).getOrientation(); // Flagged
}
- if (SDK_INT >= ICE_CREAM_SANDWICH) {
+ if (SDK_INT >= ICE_CREAM_SANDWICH) {
GridLayout(null).getOrientation(); // Not flagged
} else {
- GridLayout(null).getOrientation(); // Flagged
+ GridLayout(null).getOrientation(); // Flagged
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
GridLayout(null).getOrientation(); // Not flagged
} else {
- GridLayout(null).getOrientation(); // Flagged
+ GridLayout(null).getOrientation(); // Flagged
}
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- GridLayout(null).getOrientation(); // Flagged
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ GridLayout(null).getOrientation(); // Flagged
} else {
GridLayout(null).getOrientation(); // Not flagged
}
- if (Build.VERSION.SDK_INT >= 14) {
+ if (Build.VERSION.SDK_INT >= 14) {
GridLayout(null).getOrientation(); // Not flagged
} else {
- GridLayout(null).getOrientation(); // Flagged
+ GridLayout(null).getOrientation(); // Flagged
}
- if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH) {
+ if (VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH) {
GridLayout(null).getOrientation(); // Not flagged
} else {
- GridLayout(null).getOrientation(); // Flagged
+ GridLayout(null).getOrientation(); // Flagged
}
// Nested conditionals
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
if (priority) {
- GridLayout(null).getOrientation(); // Flagged
+ GridLayout(null).getOrientation(); // Flagged
} else {
- GridLayout(null).getOrientation(); // Flagged
+ GridLayout(null).getOrientation(); // Flagged
}
} else {
- GridLayout(null).getOrientation(); // Flagged
+ GridLayout(null).getOrientation(); // Flagged
}
// Nested conditionals 2
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
if (priority) {
- GridLayout(null).getOrientation(); // Not flagged
+ GridLayout(null).getOrientation(); // Not flagged
} else {
- GridLayout(null).getOrientation(); // Not flagged
+ GridLayout(null).getOrientation(); // Not flagged
}
} else {
- GridLayout(null); // Flagged
+ GridLayout(null); // Flagged
}
}
fun test2(priority: Boolean) {
- if (android.os.Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
+ if (android.os.Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
GridLayout(null).getOrientation(); // Not flagged
} else {
- GridLayout(null); // Flagged
+ GridLayout(null); // Flagged
}
- if (android.os.Build.VERSION.SDK_INT >= 16) {
+ if (android.os.Build.VERSION.SDK_INT >= 16) {
GridLayout(null).getOrientation(); // Not flagged
} else {
- GridLayout(null); // Flagged
+ GridLayout(null); // Flagged
}
- if (android.os.Build.VERSION.SDK_INT >= 13) {
- GridLayout(null).getOrientation(); // Flagged
+ if (android.os.Build.VERSION.SDK_INT >= 13) {
+ GridLayout(null).getOrientation(); // Flagged
} else {
- GridLayout(null); // Flagged
+ GridLayout(null); // Flagged
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
GridLayout(null).getOrientation(); // Not flagged
} else {
- GridLayout(null); // Flagged
+ GridLayout(null); // Flagged
}
- if (SDK_INT >= JELLY_BEAN) {
+ if (SDK_INT >= JELLY_BEAN) {
GridLayout(null).getOrientation(); // Not flagged
} else {
- GridLayout(null); // Flagged
+ GridLayout(null); // Flagged
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
GridLayout(null).getOrientation(); // Not flagged
} else {
- GridLayout(null); // Flagged
+ GridLayout(null); // Flagged
}
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
- GridLayout(null); // Flagged
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
+ GridLayout(null); // Flagged
} else {
GridLayout(null).getOrientation(); // Not flagged
}
- if (Build.VERSION.SDK_INT >= 16) {
+ if (Build.VERSION.SDK_INT >= 16) {
GridLayout(null).getOrientation(); // Not flagged
} else {
- GridLayout(null); // Flagged
+ GridLayout(null); // Flagged
}
- if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
+ if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) {
GridLayout(null).getOrientation(); // Not flagged
} else {
- GridLayout(null); // Flagged
+ GridLayout(null); // Flagged
}
}
fun test(textView: TextView) {
- if (textView.isSuggestionsEnabled()) {
+ if (textView.isSuggestionsEnabled()) {
//ERROR
}
- if (textView.isSuggestionsEnabled) {
+ if (textView.isSuggestionsEnabled) {
//ERROR
}
- if (SDK_INT >= JELLY_BEAN && textView.isSuggestionsEnabled()) {
+ if (SDK_INT >= JELLY_BEAN && textView.isSuggestionsEnabled()) {
//NO ERROR
}
- if (SDK_INT >= JELLY_BEAN && textView.isSuggestionsEnabled) {
+ if (SDK_INT >= JELLY_BEAN && textView.isSuggestionsEnabled) {
//NO ERROR
}
- if (SDK_INT < JELLY_BEAN && textView.isSuggestionsEnabled()) {
+ if (SDK_INT < JELLY_BEAN && textView.isSuggestionsEnabled()) {
//ERROR
}
- if (SDK_INT < JELLY_BEAN && textView.isSuggestionsEnabled) {
+ if (SDK_INT < JELLY_BEAN && textView.isSuggestionsEnabled) {
//ERROR
}
}
diff --git a/plugins/uast-kotlin/testData/lint/javaPerformance.kt b/plugins/uast-kotlin/testData/lint/javaPerformance.kt
index 2b7e32cc01c..5cd562d3f62 100644
--- a/plugins/uast-kotlin/testData/lint/javaPerformance.kt
+++ b/plugins/uast-kotlin/testData/lint/javaPerformance.kt
@@ -73,7 +73,7 @@ class JavaPerformanceTest(context: Context, attrs: AttributeSet, defStyle: Int)
// This one should not be reported:
@SuppressLint("UseSparseArrays")
- val myOtherMap = HashMap()
+ val myOtherMap = HashMap()
}
protected fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int,
@@ -120,8 +120,8 @@ class JavaPerformanceTest(context: Context, attrs: AttributeSet, defStyle: Int)
val layoutHeight = height
if (mAllowCrop && (mOverlay == null || mOverlay!!.width != layoutWidth ||
mOverlay!!.height != layoutHeight)) {
- mOverlay = Bitmap.createBitmap(layoutWidth, layoutHeight, Bitmap.Config.ARGB_8888)
- mOverlayCanvas = Canvas(mOverlay!!)
+ mOverlay = Bitmap.createBitmap(layoutWidth, layoutHeight, Bitmap.Config.ARGB_8888)
+ mOverlayCanvas = Canvas(mOverlay!!)
}
if (widthMeasureSpec == 42) {
diff --git a/plugins/uast-kotlin/testData/lint/log.kt b/plugins/uast-kotlin/testData/lint/log.kt
index e17ec748260..ebf4d97adba 100644
--- a/plugins/uast-kotlin/testData/lint/log.kt
+++ b/plugins/uast-kotlin/testData/lint/log.kt
@@ -15,8 +15,8 @@ class LogTest {
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, Constants.MY_MESSAGE) // ok: unconditional, but constant string
- Log.i(TAG1, "message" + m) // error: unconditional w/ computation
- Log.i(TAG1, toString()) // error: unconditional w/ computation
+ Log.i(TAG1, "message" + m) // error: unconditional w/ computation
+ Log.i(TAG1, toString()) // error: unconditional w/ computation
Log.e(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
@@ -26,11 +26,11 @@ class LogTest {
}
fun checkWrongTag(tag: String) {
- if (Log.isLoggable(TAG1, Log.DEBUG)) {
- Log.d(TAG2, "message") // warn: mismatched tags!
+ if (Log.isLoggable(TAG1, Log.DEBUG)) {
+ Log.d(TAG2, "message") // warn: mismatched tags!
}
- if (Log.isLoggable("my_tag", Log.DEBUG)) {
- Log.d("other_tag", "message") // warn: mismatched tags!
+ if (Log.isLoggable("my_tag", Log.DEBUG)) {
+ Log.d("other_tag", "message") // warn: mismatched tags!
}
if (Log.isLoggable("my_tag", Log.DEBUG)) {
Log.d("my_tag", "message") // ok: strings equal
@@ -73,9 +73,9 @@ class LogTest {
if (Log.isLoggable(TAG1, Log.DEBUG)) {
Log.v(TAG1, "message") // warn: wrong level
}
- if (Log.isLoggable(TAG1, DEBUG)) {
+ if (Log.isLoggable(TAG1, DEBUG)) {
// static import of level
- Log.v(TAG1, "message") // warn: wrong level
+ Log.v(TAG1, "message") // warn: wrong level
}
if (Log.isLoggable(TAG1, Log.VERBOSE)) {
Log.d(TAG1, "message") // warn? verbose is a lower logging level, which includes debug
diff --git a/plugins/uast-kotlin/testData/lint/parcel.kt b/plugins/uast-kotlin/testData/lint/parcel.kt
index 4ba5a4eda46..600130a0df3 100644
--- a/plugins/uast-kotlin/testData/lint/parcel.kt
+++ b/plugins/uast-kotlin/testData/lint/parcel.kt
@@ -55,7 +55,7 @@ class RecyclerViewScrollPosition(val position: Int, val topOffset: Int): Parcela
}
}
-class RecyclerViewScrollPositionWithoutJvmF(val position: Int, val topOffset: Int): Parcelable {
+class RecyclerViewScrollPositionWithoutJvmF(val position: Int, val topOffset: Int): Parcelable {
override fun describeContents(): Int = 0
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeInt(position)
diff --git a/plugins/uast-kotlin/testData/lint/showDiagnosticsWhenFileIsRed.kt b/plugins/uast-kotlin/testData/lint/showDiagnosticsWhenFileIsRed.kt
index e750c6bd63a..5623005ea68 100644
--- a/plugins/uast-kotlin/testData/lint/showDiagnosticsWhenFileIsRed.kt
+++ b/plugins/uast-kotlin/testData/lint/showDiagnosticsWhenFileIsRed.kt
@@ -5,5 +5,5 @@ import android.database.sqlite.SQLiteDatabase
fun test(db: SQLiteDatabase) {
val a: String = 1
- db.execSQL("CREATE TABLE COMPANY(NAME STRING)")
+ db.execSQL("CREATE TABLE COMPANY(NAME STRING)")
}
\ No newline at end of file
diff --git a/plugins/uast-kotlin/testData/lint/sqlite.kt b/plugins/uast-kotlin/testData/lint/sqlite.kt
index 95da6d3f5f4..93eab539d4e 100644
--- a/plugins/uast-kotlin/testData/lint/sqlite.kt
+++ b/plugins/uast-kotlin/testData/lint/sqlite.kt
@@ -3,5 +3,5 @@
import android.database.sqlite.SQLiteDatabase
fun test(db: SQLiteDatabase) {
- db.execSQL("CREATE TABLE COMPANY(NAME STRING)")
+ db.execSQL("CREATE TABLE COMPANY(NAME STRING)")
}
\ No newline at end of file
diff --git a/plugins/uast-kotlin/testData/lint/toast.kt b/plugins/uast-kotlin/testData/lint/toast.kt
index 6df0c2a896c..9cb25102a04 100644
--- a/plugins/uast-kotlin/testData/lint/toast.kt
+++ b/plugins/uast-kotlin/testData/lint/toast.kt
@@ -24,7 +24,7 @@ class ToastTest(context: Context) : Activity() {
}
Runnable {
- Toast.makeText(context, "foo", Toast.LENGTH_LONG)
+ Toast.makeText(context, "foo", Toast.LENGTH_LONG)
}
}
@@ -45,13 +45,13 @@ class ToastTest(context: Context) : Activity() {
private fun broken(context: Context) {
// Errors
- Toast.makeText(context, "foo", Toast.LENGTH_LONG)
- val toast = Toast.makeText(context, R.string.app_name, 5000)
+ Toast.makeText(context, "foo", Toast.LENGTH_LONG)
+ val toast = Toast.makeText(context, R.string.app_name, 5000)
toast.duration
}
init {
- Toast.makeText(context, "foo", Toast.LENGTH_LONG)
+ Toast.makeText(context, "foo", Toast.LENGTH_LONG)
}
@android.annotation.SuppressLint("ShowToast")
@@ -61,7 +61,7 @@ class ToastTest(context: Context) : Activity() {
private fun checkSuppress2(context: Context) {
@android.annotation.SuppressLint("ShowToast")
- val toast = Toast.makeText(this, "MyToast", Toast.LENGTH_LONG)
+ val toast = Toast.makeText(this, "MyToast", Toast.LENGTH_LONG)
}
class R {
diff --git a/plugins/uast-kotlin/testData/lint/wrongAnnotation.kt b/plugins/uast-kotlin/testData/lint/wrongAnnotation.kt
index 327362eeff4..62cb9f47bec 100644
--- a/plugins/uast-kotlin/testData/lint/wrongAnnotation.kt
+++ b/plugins/uast-kotlin/testData/lint/wrongAnnotation.kt
@@ -13,13 +13,13 @@ class WrongAnnotation2 {
companion object {
@SuppressLint("NewApi") // Valid: class-file check on method
- fun foobar(view: View, @SuppressLint("NewApi") foo: Int) {
+ fun foobar(view: View, @SuppressLint("NewApi") foo: Int) {
// Invalid: class-file check
- @SuppressLint("NewApi") // Invalid
+ @SuppressLint("NewApi") // Invalid
val a: Boolean
- @SuppressLint("SdCardPath", "NewApi") // Invalid: class-file based check on local variable
+ @SuppressLint("SdCardPath", "NewApi") // Invalid: class-file based check on local variable
val b: Boolean
- @android.annotation.SuppressLint("SdCardPath", "NewApi") // Invalid (FQN)
+ @android.annotation.SuppressLint("SdCardPath", "NewApi") // Invalid (FQN)
val c: Boolean
@SuppressLint("SdCardPath") // Valid: AST-based check
val d: Boolean
@@ -27,7 +27,7 @@ class WrongAnnotation2 {
init {
// Local variable outside method: invalid
- @SuppressLint("NewApi")
+ @SuppressLint("NewApi")
val localvar = 5
}
diff --git a/plugins/uast-kotlin/uast-kotlin.iml b/plugins/uast-kotlin/uast-kotlin.iml
index c5f99284c4d..be432270e36 100644
--- a/plugins/uast-kotlin/uast-kotlin.iml
+++ b/plugins/uast-kotlin/uast-kotlin.iml
@@ -22,5 +22,6 @@
+
\ No newline at end of file