diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/BuiltinIssueRegistry.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/BuiltinIssueRegistry.java index 3d6e16e9cc0..518229c1867 100644 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/BuiltinIssueRegistry.java +++ b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/BuiltinIssueRegistry.java @@ -42,7 +42,6 @@ public class BuiltinIssueRegistry extends IssueRegistry { issues.add(ApiDetector.INLINED); issues.add(ApiDetector.OVERRIDE); issues.add(ApiDetector.UNSUPPORTED); - issues.add(ApiDetector.UNUSED); issues.add(AppCompatCallDetector.ISSUE); issues.add(AppCompatResourceDetector.ISSUE); issues.add(AssertDetector.ISSUE); diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintInspectionToolProvider.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintInspectionToolProvider.java index 069203c0135..6ec61151641 100644 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintInspectionToolProvider.java +++ b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/AndroidLintInspectionToolProvider.java @@ -121,12 +121,6 @@ public class AndroidLintInspectionToolProvider { } } - public static class AndroidKLintUnusedAttributeInspection extends AndroidLintInspectionBase { - public AndroidKLintUnusedAttributeInspection() { - super(AndroidBundle.message("android.lint.inspections.unused.attribute"), ApiDetector.UNUSED); - } - } - public static class AndroidKLintUnusedIdsInspection extends AndroidLintInspectionBase { public AndroidKLintUnusedIdsInspection() { super(AndroidBundle.message("android.lint.inspections.unused.ids"), UnusedResourceDetector.ISSUE_IDS); diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijApiDetector.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijApiDetector.java deleted file mode 100755 index 5dec5140022..00000000000 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijApiDetector.java +++ /dev/null @@ -1,817 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.jetbrains.android.inspections.klint; - -import com.android.annotations.NonNull; -import com.android.annotations.Nullable; -import com.android.sdklib.SdkVersionInfo; -import com.android.tools.klint.checks.ApiDetector; -import com.android.tools.klint.checks.ApiLookup; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.psi.*; -import com.intellij.psi.impl.source.PsiClassReferenceType; -import com.intellij.psi.tree.IElementType; -import com.intellij.psi.util.MethodSignatureUtil; -import com.intellij.psi.util.PsiTreeUtil; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.uast.UFile; -import org.jetbrains.uast.check.UastAndroidContext; -import org.jetbrains.uast.visitor.UastVisitor; - -import java.util.EnumSet; - -import static org.jetbrains.android.inspections.klint.IntellijLintUtils.SUPPRESS_LINT_FQCN; -import static org.jetbrains.android.inspections.klint.IntellijLintUtils.SUPPRESS_WARNINGS_FQCN; - -/** - * Intellij-specific version of the {@link ApiDetector} which uses the PSI structure - * to check accesses - *

- * TODO: - *

- */ -public class IntellijApiDetector extends ApiDetector { - @SuppressWarnings("unchecked") - static final com.android.tools.klint.detector.api.Implementation IMPLEMENTATION = new com.android.tools.klint.detector.api.Implementation( - IntellijApiDetector.class, - EnumSet.of(com.android.tools.klint.detector.api.Scope.RESOURCE_FILE, com.android.tools.klint.detector.api.Scope.MANIFEST, com.android.tools.klint.detector.api.Scope.SOURCE_FILE), - com.android.tools.klint.detector.api.Scope.MANIFEST_SCOPE, - com.android.tools.klint.detector.api.Scope.RESOURCE_FILE_SCOPE, - com.android.tools.klint.detector.api.Scope.JAVA_FILE_SCOPE - ); - - @NonNls - private static final String TARGET_API_FQCN = "android.annotation.TargetApi"; - private static final String SDK_INT = "SDK_INT"; - - @Override - public UastVisitor createUastVisitor(final UastAndroidContext context) { - return new UastVisitor() { - @Override - public boolean visitFile(@NotNull UFile node) { - check(context); - return true; - } - }; - } - - private void check(final UastAndroidContext uastContext) { - if (mApiDatabase == null) { - return; - } - - final com.android.tools.klint.detector.api.JavaContext context = uastContext.getLintContext(); - - ApplicationManager.getApplication().runReadAction(new Runnable() { - @Override - public void run() { - final PsiFile psiFile = IntellijLintUtils.getPsiFile(context); - if (!(psiFile instanceof PsiJavaFile)) { - return; - } - PsiJavaFile javaFile = (PsiJavaFile)psiFile; - for (PsiClass clz : javaFile.getClasses()) { - PsiElementVisitor visitor = new ApiCheckVisitor(context, clz, psiFile); - - javaFile.accept(visitor); - } - } - }); - } - - private static int getTargetApi(@NonNull PsiElement e, @NonNull PsiElement file) { - PsiElement element = e; - // Search upwards for target api annotations - while (element != null && element != file) { // otherwise it will keep going into directories! - if (element instanceof PsiModifierListOwner) { - PsiModifierListOwner owner = (PsiModifierListOwner)element; - PsiModifierList modifierList = owner.getModifierList(); - PsiAnnotation annotation = null; - if (modifierList != null) { - annotation = modifierList.findAnnotation(TARGET_API_FQCN); - } - if (annotation != null) { - for (PsiNameValuePair pair : annotation.getParameterList().getAttributes()) { - PsiAnnotationMemberValue v = pair.getValue(); - - if (v instanceof PsiLiteral) { - PsiLiteral literal = (PsiLiteral)v; - Object value = literal.getValue(); - if (value instanceof Integer) { - return (Integer) value; - } else if (value instanceof String) { - return codeNameToApi((String) value); - } - } else if (v instanceof PsiArrayInitializerMemberValue) { - PsiArrayInitializerMemberValue mv = (PsiArrayInitializerMemberValue)v; - for (PsiAnnotationMemberValue mmv : mv.getInitializers()) { - if (mmv instanceof PsiLiteral) { - PsiLiteral literal = (PsiLiteral)mmv; - Object value = literal.getValue(); - if (value instanceof Integer) { - return (Integer) value; - } else if (value instanceof String) { - return codeNameToApi((String) value); - } - } - } - } else if (v instanceof PsiExpression) { - if (v instanceof PsiReferenceExpression) { - String fqcn = ((PsiReferenceExpression)v).getQualifiedName(); - return codeNameToApi(fqcn); - } else { - return codeNameToApi(v.getText()); - } - } - } - } - } - element = element.getParent(); - } - - return -1; - } - - private static int codeNameToApi(String text) { - int dotIndex = text.lastIndexOf('.'); - if (dotIndex != -1) { - text = text.substring(dotIndex + 1); - } - - return SdkVersionInfo.getApiByBuildCode(text, true); - } - - private class ApiCheckVisitor extends JavaRecursiveElementVisitor { - private final com.android.tools.klint.detector.api.Context myContext; - private boolean mySeenSuppress; - private boolean mySeenTargetApi; - private final PsiClass myClass; - private final PsiFile myFile; - private final boolean myCheckAccess; - private boolean myCheckOverride; - private String myFrameworkParent; - - public ApiCheckVisitor(com.android.tools.klint.detector.api.Context context, PsiClass clz, PsiFile file) { - myContext = context; - myClass = clz; - myFile = file; - - myCheckAccess = context.isEnabled(UNSUPPORTED) || context.isEnabled(INLINED); - myCheckOverride = context.isEnabled(OVERRIDE) - && context.getMainProject().getBuildSdk() >= 1; - if (myCheckOverride) { - myFrameworkParent = null; - PsiClass superClass = myClass.getSuperClass(); - while (superClass != null) { - String fqcn = superClass.getQualifiedName(); - if (fqcn == null) { - myCheckOverride = false; - } else if (fqcn.startsWith("android.") //$NON-NLS-1$ - || fqcn.startsWith("java.") //$NON-NLS-1$ - || fqcn.startsWith("javax.")) { //$NON-NLS-1$ - if (!fqcn.equals(CommonClassNames.JAVA_LANG_OBJECT)) { - myFrameworkParent = com.android.tools.klint.detector.api.ClassContext.getInternalName(fqcn); - } - break; - } - superClass = superClass.getSuperClass(); - } - if (myFrameworkParent == null) { - myCheckOverride = false; - } - } - } - - @Override - public void visitAnnotation(PsiAnnotation annotation) { - super.visitAnnotation(annotation); - - String fqcn = annotation.getQualifiedName(); - if (TARGET_API_FQCN.equals(fqcn)) { - mySeenTargetApi = true; - } - else if (SUPPRESS_LINT_FQCN.equals(fqcn) || SUPPRESS_WARNINGS_FQCN.equals(fqcn)) { - mySeenSuppress = true; - } - } - - @Override - public void visitMethod(PsiMethod method) { - super.visitMethod(method); - - if (!myCheckOverride) { - return; - } - - int buildSdk = myContext.getMainProject().getBuildSdk(); - String name = method.getName(); - assert myFrameworkParent != null; - String desc = IntellijLintUtils.getInternalDescription(method, false, false); - if (desc == null) { - // Couldn't compute description of method for some reason; probably - // failure to resolve parameter types - return; - } - int api = mApiDatabase.getCallVersion(myFrameworkParent, name, desc); - if (api > buildSdk && buildSdk != -1) { - if (mySeenSuppress && - IntellijLintUtils.isSuppressed(method, myFile, OVERRIDE)) { - return; - } - - // TODO: Don't complain if it's annotated with @Override; that means - // somehow the build target isn't correct. - - String fqcn; - PsiClass containingClass = method.getContainingClass(); - if (containingClass != null) { - String className = containingClass.getName(); - String fullClassName = containingClass.getQualifiedName(); - if (fullClassName != null) { - className = fullClassName; - } - fqcn = className + '#' + name; - } else { - fqcn = name; - } - - String message = String.format( - "This method is not overriding anything with the current build " + - "target, but will in API level %1$d (current target is %2$d): %3$s", - api, buildSdk, fqcn); - - PsiElement locationNode = method.getNameIdentifier(); - if (locationNode == null) { - locationNode = method; - } - com.android.tools.klint.detector.api.Location location = IntellijLintUtils.getLocation(myContext.file, locationNode); - myContext.report(OVERRIDE, location, message); - } - } - - @Override - public void visitClass(PsiClass aClass) { - super.visitClass(aClass); - - if (!myCheckAccess) { - return; - } - - for (PsiClassType type : aClass.getSuperTypes()) { - String signature = IntellijLintUtils.getInternalName(type); - if (signature == null) { - continue; - } - - int api = mApiDatabase.getClassVersion(signature); - if (api == -1) { - continue; - } - int minSdk = getMinSdk(myContext); - if (api <= minSdk) { - continue; - } - if (mySeenTargetApi) { - int target = getTargetApi(aClass, myFile); - if (target != -1) { - if (api <= target) { - continue; - } - } - } - if (mySeenSuppress && IntellijLintUtils.isSuppressed(aClass, myFile, UNSUPPORTED)) { - continue; - } - - com.android.tools.klint.detector.api.Location location; - if (type instanceof PsiClassReferenceType) { - PsiReference reference = ((PsiClassReferenceType)type).getReference(); - PsiElement element = reference.getElement(); - if (isWithinVersionCheckConditional(element, api)) { - continue; - } - location = IntellijLintUtils.getLocation(myContext.file, element); - } else { - location = IntellijLintUtils.getLocation(myContext.file, aClass); - } - String fqcn = type.getClassName(); - String message = String.format("Class requires API level %1$d (current min is %2$d): %3$s", api, minSdk, fqcn); - myContext.report(UNSUPPORTED, location, message); - } - } - - @Override - public void visitReferenceExpression(PsiReferenceExpression expression) { - super.visitReferenceExpression(expression); - - if (!myCheckAccess) { - return; - } - - PsiReference reference = expression.getReference(); - if (reference == null) { - return; - } - PsiElement resolved = reference.resolve(); - if (resolved != null) { - if (resolved instanceof PsiField) { - PsiField field = (PsiField)resolved; - PsiClass containingClass = field.getContainingClass(); - if (containingClass == null) { - return; - } - String owner = IntellijLintUtils.getInternalName(containingClass); - if (owner == null) { - return; // Couldn't resolve type - } - String name = field.getName(); - - int api = mApiDatabase.getFieldVersion(owner, name); - if (api == -1) { - return; - } - int minSdk = getMinSdk(myContext); - if (isSuppressed(api, expression, minSdk)) { - return; - } - - com.android.tools.klint.detector.api.Location location = IntellijLintUtils.getLocation(myContext.file, expression); - String fqcn = containingClass.getQualifiedName(); - String message = String.format( - "Field requires API level %1$d (current min is %2$d): %3$s", - api, minSdk, fqcn + '#' + name); - - com.android.tools.klint.detector.api.Issue issue = UNSUPPORTED; - // When accessing primitive types or Strings, the values get copied into - // the class files (e.g. get inlined) which has a separate issue type: - // INLINED. - PsiType type = field.getType(); - if (PsiType.INT.equals(type) || PsiType.CHAR.equals(type) || PsiType.BOOLEAN.equals(type) - || PsiType.DOUBLE.equals(type) || PsiType.FLOAT.equals(type) || PsiType.BYTE.equals(type) - || type.equalsToText(CommonClassNames.JAVA_LANG_STRING)) { - issue = INLINED; - - // Some usages of inlined constants are okay: - if (isBenignConstantUsage(expression, name, owner)) { - return; - } - } - - myContext.report(issue, location, message); - } - } - } - - @Override - public void visitTryStatement(PsiTryStatement statement) { - super.visitTryStatement(statement); - - PsiResourceList resourceList = statement.getResourceList(); - if (resourceList != null) { - int api = 19; // minSdk for try with resources - int minSdk = getMinSdk(myContext); - - if (isSuppressed(api, statement, minSdk)) { - return; - } - com.android.tools.klint.detector.api.Location location = IntellijLintUtils.getLocation(myContext.file, resourceList); - String message = String.format("Try-with-resources requires API level %1$d (current min is %2$d)", api, minSdk); - myContext.report(UNSUPPORTED, location, message); - } - - for (PsiParameter parameter : statement.getCatchBlockParameters()) { - PsiTypeElement typeElement = parameter.getTypeElement(); - if (typeElement != null) { - PsiType type = typeElement.getType(); - PsiClass resolved = null; - PsiElement reference = parameter; - if (type instanceof PsiDisjunctionType) { - type = ((PsiDisjunctionType)type).getLeastUpperBound(); - if (type instanceof PsiClassType) { - resolved = ((PsiClassType)type).resolve(); - } - } else if (type instanceof PsiClassReferenceType) { - PsiClassReferenceType referenceType = (PsiClassReferenceType)type; - resolved = referenceType.resolve(); - reference = referenceType.getReference().getElement(); - } else if (type instanceof PsiClassType) { - resolved = ((PsiClassType)type).resolve(); - } - if (resolved != null) { - String signature = IntellijLintUtils.getInternalName(resolved); - if (signature == null) { - continue; - } - - int api = mApiDatabase.getClassVersion(signature); - if (api == -1) { - continue; - } - int minSdk = getMinSdk(myContext); - if (api <= minSdk) { - continue; - } - if (mySeenTargetApi) { - int target = getTargetApi(statement, myFile); - if (target != -1) { - if (api <= target) { - continue; - } - } - } - if (mySeenSuppress && IntellijLintUtils.isSuppressed(statement, myFile, UNSUPPORTED)) { - continue; - } - - com.android.tools.klint.detector.api.Location location; - location = IntellijLintUtils.getLocation(myContext.file, reference); - String fqcn = resolved.getName(); - String message = String.format("Class requires API level %1$d (current min is %2$d): %3$s", api, minSdk, fqcn); - - // Special case reflective operation exception which can be implicitly used - // with multi-catches: see issue 153406 - if (api == 19 && fqcn.equals("ReflectiveOperationException")) { - 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); - } - myContext.report(UNSUPPORTED, location, message); - } - } - } - } - - private boolean isSuppressed(int api, PsiElement element, int minSdk) { - if (api <= minSdk) { - return true; - } - if (mySeenTargetApi) { - int target = getTargetApi(element, myFile); - if (target != -1) { - if (api <= target) { - return true; - } - } - } - if (mySeenSuppress && - (IntellijLintUtils.isSuppressed(element, myFile, UNSUPPORTED) || IntellijLintUtils.isSuppressed(element, myFile, INLINED))) { - return true; - } - - if (isWithinVersionCheckConditional(element, api)) { - return true; - } - - return false; - } - - public boolean isBenignConstantUsage( - @NonNull PsiElement node, - @NonNull String name, - @NonNull String owner) { - if (ApiDetector.isBenignConstantUsage(null, name, owner)) { - return true; - } - - // It's okay to reference the constant as a case constant (since that - // code path won't be taken) or in a condition of an if statement - // or as a case value - PsiElement curr = node.getParent(); - while (curr != null) { - if (curr instanceof PsiSwitchLabelStatement) { - PsiSwitchLabelStatement caseStatement = (PsiSwitchLabelStatement)curr; - PsiExpression condition = caseStatement.getCaseValue(); - return condition != null && PsiTreeUtil.isAncestor(condition, node, false); - } else if (curr instanceof PsiIfStatement) { - PsiIfStatement ifStatement = (PsiIfStatement)curr; - PsiExpression condition = ifStatement.getCondition(); - return condition != null && PsiTreeUtil.isAncestor(condition, node, false); - } else if (curr instanceof PsiConditionalExpression) { - // ?:-statement - PsiConditionalExpression ifStatement = (PsiConditionalExpression)curr; - PsiExpression condition = ifStatement.getCondition(); - return PsiTreeUtil.isAncestor(condition, node, false); - } - curr = curr.getParent(); - } - - return false; - } - - @Override - public void visitCallExpression(PsiCallExpression expression) { - super.visitCallExpression(expression); - - if (!myCheckAccess) { - return; - } - - // TODO: How does this differ from visitMethodCallExpression? - // Inferred super perhaps? No, I think it refers to constructor invocations! - PsiMethod method = expression.resolveMethod(); - if (method != null) { - PsiClass containingClass = method.getContainingClass(); - if (containingClass == null) { - return; - } - String fqcn = containingClass.getQualifiedName(); - String owner = IntellijLintUtils.getInternalName(containingClass); - if (owner == null) { - return; // Couldn't resolve type - } - String name = IntellijLintUtils.getInternalMethodName(method); - String desc = IntellijLintUtils.getInternalDescription(method, false, false); - if (desc == null) { - // Couldn't compute description of method for some reason; probably - // failure to resolve parameter types - return; - } - - int api = mApiDatabase.getCallVersion(owner, name, desc); - if (api == -1) { - return; - } - int minSdk = getMinSdk(myContext); - if (api <= minSdk) { - return; - } - - // The lint API database contains two optimizations: - // First, all members that were available in API 1 are omitted from the database, since that saves - // about half of the size of the database, and for API check purposes, we don't need to distinguish - // between "doesn't exist" and "available in all versions". - // Second, all inherited members were inlined into each class, so that it doesn't have to do a - // repeated search up the inheritance chain. - // - // Unfortunately, in this custom PSI detector, we look up the real resolved method, which can sometimes - // have a different minimum API. - // - // For example, SQLiteDatabase had a close() method from API 1. Therefore, calling SQLiteDatabase is supported - // in all versions. However, it extends SQLiteClosable, which in API 16 added "implements Closable". In - // this detector, if we have the following code: - // void test(SQLiteDatabase db) { db.close } - // here the call expression will be the close method on type SQLiteClosable. And that will result in an API - // requirement of API 16, since the close method it now resolves to is in API 16. - // - // To work around this, we can now look up the type of the call expression ("db" in the above, but it could - // have been more complicated), and if that's a different type than the type of the method, we look up - // *that* method from lint's database instead. Furthermore, it's possible for that method to return "-1" - // and we can't tell if that means "doesn't exist" or "present in API 1", we then check the package prefix - // to see whether we know it's an API method whose members should all have been inlined. - if (expression instanceof PsiMethodCallExpression) { - PsiExpression qualifier = ((PsiMethodCallExpression)expression).getMethodExpression().getQualifierExpression(); - if (qualifier != null && !(qualifier instanceof PsiThisExpression) && !(qualifier instanceof PsiSuperExpression)) { - PsiType type = qualifier.getType(); - if (type != null && type instanceof PsiClassType) { - String expressionOwner = IntellijLintUtils.getInternalName((PsiClassType)type); - if (expressionOwner != null && !expressionOwner.equals(owner)) { - int specificApi = mApiDatabase.getCallVersion(expressionOwner, name, desc); - if (specificApi == -1) { - if (ApiLookup.isRelevantOwner(expressionOwner)) { - return; - } - } else if (specificApi <= minSdk) { - return; - } - } - } - } else { - // Unqualified call; need to search in our super hierarchy - PsiClass cls = PsiTreeUtil.getParentOfType(expression, PsiClass.class); - - //noinspection ConstantConditions - if (qualifier instanceof PsiThisExpression || qualifier instanceof PsiSuperExpression) { - PsiQualifiedExpression pte = (PsiQualifiedExpression)qualifier; - PsiJavaCodeReferenceElement operand = pte.getQualifier(); - if (operand != null) { - PsiElement resolved = operand.resolve(); - if (resolved instanceof PsiClass) { - cls = (PsiClass)resolved; - } - } - } - - while (cls != null) { - if (cls instanceof PsiAnonymousClass) { - // If it's an unqualified call in an anonymous class, we need to rely on the - // resolve method to find out whether the method is picked up from the anonymous - // class chain or any outer classes - break; - } - String expressionOwner = IntellijLintUtils.getInternalName(cls); - if (expressionOwner == null) { - break; - } - int specificApi = mApiDatabase.getCallVersion(expressionOwner, name, desc); - if (specificApi == -1) { - if (ApiLookup.isRelevantOwner(expressionOwner)) { - return; - } - } else if (specificApi <= minSdk) { - return; - } else { - break; - } - cls = cls.getSuperClass(); - } - } - } - - if (isSuppressed(api, expression, minSdk)) { - return; - } - - // If you're simply calling super.X from method X, even if method X is in a higher API level than the minSdk, we're - // generally safe; that method should only be called by the framework on the right API levels. (There is a danger of - // somebody calling that method locally in other contexts, but this is hopefully unlikely.) - if (expression instanceof PsiMethodCallExpression) { - PsiMethodCallExpression call = (PsiMethodCallExpression)expression; - PsiReferenceExpression methodExpression = call.getMethodExpression(); - if (methodExpression.getQualifierExpression() instanceof PsiSuperExpression) { - PsiMethod containingMethod = PsiTreeUtil.getParentOfType(expression, PsiMethod.class, true); - if (containingMethod != null && name.equals(containingMethod.getName()) - && MethodSignatureUtil.areSignaturesEqual(method, containingMethod) - // 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 - && !method.isConstructor()) { - return; - } - } - } - - PsiElement locationNode = IntellijLintUtils.getCallName(expression); - if (locationNode == null) { - locationNode = expression; - } - com.android.tools.klint.detector.api.Location location = IntellijLintUtils.getLocation(myContext.file, locationNode); - String message = String.format("Call requires API level %1$d (current min is %2$d): %3$s", api, minSdk, - fqcn + '#' + method.getName()); - - myContext.report(UNSUPPORTED, location, message); - } - } - } - - private static boolean isWithinVersionCheckConditional(PsiElement element, int api) { - PsiElement current = element.getParent(); - PsiElement prev = element; - while (current != null) { - if (current instanceof PsiIfStatement) { - PsiIfStatement ifStatement = (PsiIfStatement)current; - PsiExpression condition = ifStatement.getCondition(); - if (condition != prev && condition instanceof PsiBinaryExpression) { - PsiBinaryExpression binary = (PsiBinaryExpression)condition; - IElementType tokenType = binary.getOperationTokenType(); - if (tokenType == JavaTokenType.GT || tokenType == JavaTokenType.GE || - tokenType == JavaTokenType.LE || tokenType == JavaTokenType.LT || - tokenType == JavaTokenType.EQEQ) { - PsiExpression left = binary.getLOperand(); - if (left instanceof PsiReferenceExpression) { - PsiReferenceExpression ref = (PsiReferenceExpression)left; - if (SDK_INT.equals(ref.getReferenceName())) { - PsiExpression right = binary.getROperand(); - int level = -1; - if (right instanceof PsiReferenceExpression) { - PsiReferenceExpression ref2 = (PsiReferenceExpression)right; - String codeName = ref2.getReferenceName(); - if (codeName == null) { - return false; - } - level = SdkVersionInfo.getApiByBuildCode(codeName, true); - } else if (right instanceof PsiLiteralExpression) { - PsiLiteralExpression lit = (PsiLiteralExpression)right; - Object value = lit.getValue(); - if (value instanceof Integer) { - level = ((Integer)value).intValue(); - } - } - if (level != -1) { - boolean fromThen = prev == ifStatement.getThenBranch(); - boolean fromElse = prev == ifStatement.getElseBranch(); - assert fromThen == !fromElse; - if (tokenType == JavaTokenType.GE) { - // if (SDK_INT >= ICE_CREAM_SANDWICH) { } else { ... } - return level >= api && fromThen; - } - else if (tokenType == JavaTokenType.GT) { - // if (SDK_INT > ICE_CREAM_SANDWICH) { } else { ... } - return level >= api - 1 && fromThen; - } - else if (tokenType == JavaTokenType.LE) { - // if (SDK_INT <= ICE_CREAM_SANDWICH) { ... } else { } - return level >= api - 1 && fromElse; - } - else if (tokenType == JavaTokenType.LT) { - // if (SDK_INT < ICE_CREAM_SANDWICH) { ... } else { } - return level >= api && fromElse; - } - else if (tokenType == JavaTokenType.EQEQ) { - // if (SDK_INT == ICE_CREAM_SANDWICH) { } else { } - return level >= api && fromThen; - } else { - assert false : tokenType; - } - } - } - } - } else if (tokenType == JavaTokenType.ANDAND && (prev == ifStatement.getThenBranch())) { - if (isAndedWithConditional(ifStatement.getCondition(), api, prev)) { - return true; - } - } - } else if (condition instanceof PsiPolyadicExpression) { - PsiPolyadicExpression ppe = (PsiPolyadicExpression)condition; - if (ppe.getOperationTokenType() == JavaTokenType.ANDAND && (prev == ifStatement.getThenBranch())) { - if (isAndedWithConditional(ppe, api, prev)) { - return true; - } - } - } - } else if (current instanceof PsiPolyadicExpression && isAndedWithConditional(current, api, prev)) { - return true; - } else if (current instanceof PsiMethod || current instanceof PsiFile) { - return false; - } - prev = current; - current = current.getParent(); - } - - return false; - } - - private static boolean isAndedWithConditional(PsiElement element, int api, @Nullable PsiElement before) { - if (element instanceof PsiBinaryExpression) { - PsiBinaryExpression inner = (PsiBinaryExpression)element; - if (inner.getOperationTokenType() == JavaTokenType.ANDAND) { - return isAndedWithConditional(inner.getLOperand(), api, before) || - inner.getROperand() != before && isAndedWithConditional(inner.getROperand(), api, before); - } else if (inner.getLOperand() instanceof PsiReferenceExpression && - SDK_INT.equals(((PsiReferenceExpression)inner.getLOperand()).getReferenceName())) { - int level = -1; - IElementType tokenType = inner.getOperationTokenType(); - PsiExpression right = inner.getROperand(); - if (right instanceof PsiReferenceExpression) { - PsiReferenceExpression ref2 = (PsiReferenceExpression)right; - String codeName = ref2.getReferenceName(); - if (codeName == null) { - return false; - } - level = SdkVersionInfo.getApiByBuildCode(codeName, true); - } else if (right instanceof PsiLiteralExpression) { - PsiLiteralExpression lit = (PsiLiteralExpression)right; - Object value = lit.getValue(); - if (value instanceof Integer) { - level = ((Integer)value).intValue(); - } - } - if (level != -1) { - if (tokenType == JavaTokenType.GE) { - // if (SDK_INT >= ICE_CREAM_SANDWICH && - return level >= api; - } - else if (tokenType == JavaTokenType.GT) { - // if (SDK_INT > ICE_CREAM_SANDWICH) && - return level >= api - 1; - } - else if (tokenType == JavaTokenType.EQEQ) { - // if (SDK_INT == ICE_CREAM_SANDWICH) && - return level >= api; - } - } - } - } - else if (element instanceof PsiPolyadicExpression) { - PsiPolyadicExpression ppe = (PsiPolyadicExpression)element; - if (ppe.getOperationTokenType() == JavaTokenType.ANDAND) { - for (PsiExpression operand : ppe.getOperands()) { - if (operand == before) { - break; - } - else if (isAndedWithConditional(operand, api, before)) { - return true; - } - } - } - } - - return false; - } -} diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintIssueRegistry.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintIssueRegistry.java index 636a6928db9..943ce94b445 100644 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintIssueRegistry.java +++ b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintIssueRegistry.java @@ -49,9 +49,7 @@ public class IntellijLintIssueRegistry extends BuiltinIssueRegistry { Implementation implementation = issue.getImplementation(); EnumSet scope = implementation.getScope(); Class detectorClass = implementation.getDetectorClass(); - if (detectorClass == ApiDetector.class) { - issue.setImplementation(IntellijApiDetector.IMPLEMENTATION); - } else if (detectorClass == RegistrationDetector.class) { + if (detectorClass == RegistrationDetector.class) { issue.setImplementation(IntellijRegistrationDetector.IMPLEMENTATION); } else if (detectorClass == ViewTypeDetector.class) { issue.setImplementation(IntellijViewTypeDetector.IMPLEMENTATION);