From 7404b91cb3cd7efe44f9b2fccfdde4036201c525 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Thu, 13 Oct 2016 21:38:14 +0300 Subject: [PATCH] Lint: Fix diagnostic tests --- plugins/lint/lint-api/lint-api.iml | 4 +- .../tools/klint/checks/ApiDetector.java | 578 +++++++++++------- .../tools/klint/checks/ToastDetector.java | 31 +- .../AbstractKotlinLintTest.kt | 46 +- plugins/uast-kotlin/testData/lint/apiCheck.kt | 147 +++-- .../testData/lint/javaPerformance.kt | 6 +- plugins/uast-kotlin/testData/lint/log.kt | 16 +- plugins/uast-kotlin/testData/lint/parcel.kt | 2 +- .../lint/showDiagnosticsWhenFileIsRed.kt | 2 +- plugins/uast-kotlin/testData/lint/sqlite.kt | 2 +- plugins/uast-kotlin/testData/lint/toast.kt | 10 +- .../testData/lint/wrongAnnotation.kt | 10 +- plugins/uast-kotlin/uast-kotlin.iml | 1 + 13 files changed, 500 insertions(+), 355 deletions(-) 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