diff --git a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/UastLintUtils.java b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/UastLintUtils.java index 629c88d7d98..b30b72bab88 100644 --- a/plugins/lint/lint-api/src/com/android/tools/klint/client/api/UastLintUtils.java +++ b/plugins/lint/lint-api/src/com/android/tools/klint/client/api/UastLintUtils.java @@ -23,16 +23,7 @@ import com.android.resources.ResourceType; import com.android.tools.klint.detector.api.ConstantEvaluator; import com.android.tools.klint.detector.api.JavaContext; import com.google.common.base.Joiner; -import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiClassType; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiField; -import com.intellij.psi.PsiJavaFile; -import com.intellij.psi.PsiLocalVariable; -import com.intellij.psi.PsiModifier; -import com.intellij.psi.PsiParameter; -import com.intellij.psi.PsiType; -import com.intellij.psi.PsiVariable; +import com.intellij.psi.*; import org.jetbrains.uast.*; import org.jetbrains.uast.expressions.UReferenceExpression; @@ -43,6 +34,35 @@ import java.util.Collections; import java.util.List; public class UastLintUtils { + @Nullable + public static String getQualifiedName(PsiElement element) { + if (element instanceof PsiClass) { + return ((PsiClass) element).getQualifiedName(); + } else if (element instanceof PsiMethod) { + PsiClass containingClass = ((PsiMethod) element).getContainingClass(); + if (containingClass == null) { + return null; + } + String containingClassFqName = getQualifiedName(containingClass); + if (containingClassFqName == null) { + return null; + } + return containingClassFqName + "." + ((PsiMethod) element).getName(); + } else if (element instanceof PsiField) { + PsiClass containingClass = ((PsiField) element).getContainingClass(); + if (containingClass == null) { + return null; + } + String containingClassFqName = getQualifiedName(containingClass); + if (containingClassFqName == null) { + return null; + } + return containingClassFqName + "." + ((PsiField) element).getName(); + } else { + return null; + } + } + @Nullable public static PsiElement resolve(ExternalReferenceExpression expression, UElement context) { UDeclaration declaration = UastUtils.getParentOfType(context, UDeclaration.class); diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AnnotationDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AnnotationDetector.java index 68e50450d8b..05ee0463ee5 100644 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AnnotationDetector.java +++ b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/AnnotationDetector.java @@ -97,19 +97,11 @@ import com.intellij.psi.PsiVariable; import com.intellij.psi.util.InheritanceUtil; import com.intellij.psi.util.PsiTreeUtil; -import org.jetbrains.uast.UAnnotation; -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UClass; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UIfExpression; -import org.jetbrains.uast.ULiteralExpression; -import org.jetbrains.uast.UParenthesizedExpression; -import org.jetbrains.uast.USwitchClauseExpression; -import org.jetbrains.uast.USwitchExpression; -import org.jetbrains.uast.UastUtils; +import org.jetbrains.uast.*; import org.jetbrains.uast.expressions.UReferenceExpression; +import org.jetbrains.uast.java.JavaUAnnotation; import org.jetbrains.uast.java.JavaUTypeCastExpression; +import org.jetbrains.uast.util.UastExpressionUtils; import org.jetbrains.uast.visitor.AbstractUastVisitor; import org.jetbrains.uast.visitor.UastVisitor; @@ -249,34 +241,29 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner } @Override - public boolean visitAnnotation(UAnnotation annotation) { + public boolean visitAnnotation(@NonNull UAnnotation annotation) { String type = annotation.getQualifiedName(); if (type == null || type.startsWith("java.lang.")) { return false; } if (FQCN_SUPPRESS_LINT.equals(type)) { - PsiAnnotationOwner owner = annotation.getOwner(); - if (owner == null) { + UElement parent = annotation.getContainingElement(); + if (parent == null) { return false; } - if (owner instanceof PsiModifierList) { - PsiElement parent = ((PsiModifierList) owner).getParent(); - // Only flag local variables and parameters (not classes, fields and methods) - if (!(parent instanceof PsiDeclarationStatement - || parent instanceof PsiLocalVariable - || parent instanceof PsiParameter)) { - return false; - } - } else { + // Only flag local variables and parameters (not classes, fields and methods) + if (!(parent instanceof UVariableDeclarationsExpression + || parent instanceof ULocalVariable + || parent instanceof UParameter)) { return false; } - PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes(); - if (attributes.length == 1) { - PsiNameValuePair attribute = attributes[0]; - PsiAnnotationMemberValue value = attribute.getValue(); - if (value instanceof PsiLiteral) { - Object v = ((PsiLiteral) value).getValue(); + List attributes = annotation.getAttributeValues(); + if (attributes.size() == 1) { + UNamedExpression attribute = attributes.get(0); + UExpression value = attribute.getExpression(); + if (value instanceof ULiteralExpression) { + Object v = ((ULiteralExpression) value).getValue(); if (v instanceof String) { String id = (String) v; checkSuppressLint(annotation, id); @@ -300,9 +287,8 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner } else if (type.startsWith(SUPPORT_ANNOTATIONS_PREFIX)) { if (CHECK_RESULT_ANNOTATION.equals(type)) { // Check that the return type of this method is not void! - if (annotation.getParent() instanceof PsiModifierList - && annotation.getParent().getParent() instanceof PsiMethod) { - PsiMethod method = (PsiMethod) annotation.getParent().getParent(); + if (annotation.getContainingElement() instanceof UMethod) { + UMethod method = (UMethod) annotation.getContainingElement(); if (!method.isConstructor() && PsiType.VOID.equals(method.getReturnType())) { mContext.report(ANNOTATION_USAGE, annotation.getPsi(), @@ -367,8 +353,7 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner PERMISSION_ANNOTATION_WRITE.equals(type)) { // Check that if there are no arguments, this is specified on a parameter, // and conversely, on methods and fields there is a valid argument. - if (annotation.getParent() instanceof PsiModifierList - && annotation.getParent().getParent() instanceof PsiMethod) { + if (annotation.getContainingElement() instanceof UMethod) { String value = PermissionRequirement.getAnnotationStringValue(annotation, ATTR_VALUE); String[] anyOf = PermissionRequirement.getAnnotationStringValues(annotation, ATTR_ANY_OF); String[] allOf = PermissionRequirement.getAnnotationStringValues(annotation, ATTR_ALL_OF); @@ -405,20 +390,16 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner } } else { // Look for typedefs (and make sure they're specified on the right type) - PsiJavaCodeReferenceElement referenceElement = annotation - .getNameReferenceElement(); - if (referenceElement != null) { - PsiElement resolved = referenceElement.resolve(); - if (resolved instanceof PsiClass) { - PsiClass cls = (PsiClass) resolved; - if (cls.isAnnotationType() && cls.getModifierList() != null) { - for (PsiAnnotation a : cls.getModifierList().getAnnotations()) { - String name = a.getQualifiedName(); - if (INT_DEF_ANNOTATION.equals(name)) { - checkTargetType(annotation, TYPE_INT, TYPE_LONG, true); - } else if (STRING_DEF_ANNOTATION.equals(type)) { - checkTargetType(annotation, TYPE_STRING, null, true); - } + PsiElement resolved = annotation.resolve(); + if (resolved != null) { + PsiClass cls = (PsiClass) resolved; + if (cls.isAnnotationType() && cls.getModifierList() != null) { + for (PsiAnnotation a : cls.getModifierList().getAnnotations()) { + String name = a.getQualifiedName(); + if (INT_DEF_ANNOTATION.equals(name)) { + checkTargetType(annotation, TYPE_INT, TYPE_LONG, true); + } else if (STRING_DEF_ANNOTATION.equals(type)) { + checkTargetType(annotation, TYPE_STRING, null, true); } } } @@ -428,75 +409,73 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner return false; } - private void checkTargetType(@NonNull PsiAnnotation node, @NonNull String type1, + private void checkTargetType(@NonNull UAnnotation node, @NonNull String type1, @Nullable String type2, boolean allowCollection) { - PsiAnnotationOwner owner = node.getOwner(); - if (owner instanceof PsiModifierList) { - PsiElement parent = ((PsiModifierList) owner).getParent(); - PsiType type; - if (parent instanceof PsiDeclarationStatement) { - PsiElement[] elements = ((PsiDeclarationStatement) parent).getDeclaredElements(); - if (elements.length > 0) { - PsiElement element = elements[0]; - if (element instanceof PsiLocalVariable) { - type = ((PsiLocalVariable)element).getType(); - } else { - return; - } + UElement parent = node.getContainingElement(); + PsiType type; + + if (parent instanceof UVariableDeclarationsExpression) { + List elements = ((UVariableDeclarationsExpression) parent).getVariables(); + if (!elements.isEmpty()) { + UVariable element = elements.get(0); + if (element instanceof ULocalVariable) { + type = element.getType(); } else { return; } - } else if (parent instanceof PsiMethod) { - PsiMethod method = (PsiMethod) parent; - type = method.isConstructor() - ? mContext.getEvaluator().getClassType(method.getContainingClass()) - : method.getReturnType(); - } else if (parent instanceof PsiVariable) { - // Field or local variable or parameter - type = ((PsiVariable)parent).getType(); } else { return; } - if (type == null) { - return; - } + } else if (parent instanceof UMethod) { + UMethod method = (UMethod) parent; + type = method.isConstructor() + ? mContext.getEvaluator().getClassType(method.getContainingClass()) + : method.getReturnType(); + } else if (parent instanceof UVariable) { + // Field or local variable or parameter + type = ((UVariable) parent).getType(); + } else { + return; + } + if (type == null) { + return; + } - if (allowCollection) { - if (type instanceof PsiArrayType) { - // For example, int[] - type = type.getDeepComponentType(); - } else if (type instanceof PsiClassType) { - // For example, List - PsiClassType classType = (PsiClassType)type; - if (classType.getParameters().length == 1) { - PsiClass resolved = classType.resolve(); - if (resolved != null && - InheritanceUtil.isInheritor(resolved, false, "java.util.Collection")) { - type = classType.getParameters()[0]; - } + if (allowCollection) { + if (type instanceof PsiArrayType) { + // For example, int[] + type = type.getDeepComponentType(); + } else if (type instanceof PsiClassType) { + // For example, List + PsiClassType classType = (PsiClassType)type; + if (classType.getParameters().length == 1) { + PsiClass resolved = classType.resolve(); + if (resolved != null && + InheritanceUtil.isInheritor(resolved, false, "java.util.Collection")) { + type = classType.getParameters()[0]; } } } + } - String typeName = type.getCanonicalText(); - if (!typeName.equals(type1) - && (type2 == null || !typeName.equals(type2))) { - // Autoboxing? You can put @DrawableRes on a java.lang.Integer for example - if (typeName.equals(getAutoBoxedType(type1)) - || type2 != null && typeName.equals(getAutoBoxedType(type2))) { - return; - } - - String expectedTypes = type2 == null ? type1 : type1 + " or " + type2; - if (typeName.equals(TYPE_STRING)) { - typeName = "String"; - } - String message = String.format( - "This annotation does not apply for type %1$s; expected %2$s", - typeName, expectedTypes); - Location location = mContext.getLocation(node); - mContext.report(ANNOTATION_USAGE, node, location, message); + String typeName = type.getCanonicalText(); + if (!typeName.equals(type1) + && (type2 == null || !typeName.equals(type2))) { + // Autoboxing? You can put @DrawableRes on a java.lang.Integer for example + if (typeName.equals(getAutoBoxedType(type1)) + || type2 != null && typeName.equals(getAutoBoxedType(type2))) { + return; } + + String expectedTypes = type2 == null ? type1 : type1 + " or " + type2; + if (typeName.equals(TYPE_STRING)) { + typeName = "String"; + } + String message = String.format( + "This annotation does not apply for type %1$s; expected %2$s", + typeName, expectedTypes); + Location location = mContext.getUastLocation(node); + mContext.report(ANNOTATION_USAGE, node, location, message); } } @@ -504,17 +483,19 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner public boolean visitSwitchExpression(USwitchExpression switchExpression) { UExpression condition = switchExpression.getExpression(); if (condition != null && PsiType.INT.equals(condition.getExpressionType())) { - PsiAnnotation annotation = findIntDefAnnotation(condition); + UAnnotation annotation = findIntDefAnnotation(condition); if (annotation != null) { - PsiAnnotationMemberValue value = + UNamedExpression namedValue = annotation.findDeclaredAttributeValue(ATTR_VALUE); - if (value == null) { - value = annotation.findDeclaredAttributeValue(null); + if (namedValue == null) { + namedValue = annotation.findDeclaredAttributeValue(null); } - if (value instanceof PsiArrayInitializerMemberValue) { - PsiAnnotationMemberValue[] allowedValues = - ((PsiArrayInitializerMemberValue)value).getInitializers(); + UExpression value = (namedValue != null) ? namedValue.getExpression() : null; + + if (UastExpressionUtils.isArrayInitializer(value)) { + List allowedValues = + ((UCallExpression) value).getValueArguments(); switchExpression.accept(new SwitchChecker(switchExpression, allowedValues)); } } @@ -537,15 +518,17 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner * with a given node */ @Nullable - private PsiAnnotation findIntDefAnnotation(@NonNull UExpression expression) { + private UAnnotation findIntDefAnnotation(@NonNull UExpression expression) { if (expression instanceof UReferenceExpression) { PsiElement resolved = ((UReferenceExpression) expression).resolve(); if (resolved instanceof PsiModifierListOwner) { PsiAnnotation[] annotations = mContext.getEvaluator().getAllAnnotations( (PsiModifierListOwner)resolved); - PsiAnnotation annotation = SupportAnnotationDetector.findIntDef( - filterRelevantAnnotations(mContext.getEvaluator(), annotations)); + PsiAnnotation[] relevantAnnotations = + filterRelevantAnnotations(mContext.getEvaluator(), annotations); + UAnnotation annotation = SupportAnnotationDetector.findIntDef( + JavaUAnnotation.wrap(relevantAnnotations)); if (annotation != null) { return annotation; } @@ -565,10 +548,12 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner } else if (expression instanceof UCallExpression) { PsiMethod method = ((UCallExpression) expression).resolve(); if (method != null) { - PsiAnnotation[] annotations = mContext.getEvaluator() - .getAllAnnotations(method); - PsiAnnotation annotation = SupportAnnotationDetector.findIntDef( - filterRelevantAnnotations(mContext.getEvaluator(), annotations)); + PsiAnnotation[] annotations = + mContext.getEvaluator().getAllAnnotations(method); + PsiAnnotation[] relevantAnnotations = + filterRelevantAnnotations(mContext.getEvaluator(), annotations); + List uAnnotations = JavaUAnnotation.wrap(relevantAnnotations); + UAnnotation annotation = SupportAnnotationDetector.findIntDef(uAnnotations); if (annotation != null) { return annotation; } @@ -576,13 +561,13 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner } else if (expression instanceof UIfExpression) { UIfExpression ifExpression = (UIfExpression) expression; if (ifExpression.getThenExpression() != null) { - PsiAnnotation result = findIntDefAnnotation(ifExpression.getThenExpression()); + UAnnotation result = findIntDefAnnotation(ifExpression.getThenExpression()); if (result != null) { return result; } } if (ifExpression.getElseExpression() != null) { - PsiAnnotation result = findIntDefAnnotation(ifExpression.getElseExpression()); + UAnnotation result = findIntDefAnnotation(ifExpression.getElseExpression()); if (result != null) { return result; } @@ -597,16 +582,17 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner return null; } - private void ensureUniqueValues(@NonNull PsiAnnotation node) { - PsiAnnotationMemberValue value = node.findAttributeValue(ATTR_VALUE); - if (value == null) { - value = node.findAttributeValue(null); + private void ensureUniqueValues(@NonNull UAnnotation node) { + UNamedExpression namedValue = node.findAttributeValue(ATTR_VALUE); + if (namedValue == null) { + namedValue = node.findAttributeValue(null); } - if (value == null) { + if (namedValue == null) { return; } + UExpression value = namedValue.getExpression(); - if (!(value instanceof PsiArrayInitializerMemberValue)) { + if (!(UastExpressionUtils.isArrayInitializer(value))) { return; } @@ -644,8 +630,8 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner Location secondary = mContext.getLocation(prevConstant); secondary.setMessage("Previous same value"); location.setSecondary(secondary); - PsiElement scope = getAnnotationScope(node); - mContext.report(UNIQUE, scope, location, message); + UElement scope = getAnnotationScope(node); + mContext.reportUast(UNIQUE, scope, location, message); break; } valueToIndex.put(number, index); @@ -697,7 +683,7 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner } } - private boolean checkSuppressLint(@NonNull PsiAnnotation node, @NonNull String id) { + private boolean checkSuppressLint(@NonNull UAnnotation node, @NonNull String id) { IssueRegistry registry = mContext.getDriver().getRegistry(); Issue issue = registry.getIssue(id); // Special-case the ApiDetector issue, since it does both source file analysis @@ -707,8 +693,8 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner || issue == ApiDetector.UNSUPPORTED) { // This issue doesn't have AST access: annotations are not // available for local variables or parameters - PsiElement scope = getAnnotationScope(node); - mContext.report(INSIDE_METHOD, scope, mContext.getLocation(node), String.format( + UElement scope = getAnnotationScope(node); + mContext.report(INSIDE_METHOD, scope, mContext.getUastLocation(node), String.format( "The `@SuppressLint` annotation cannot be used on a local " + "variable with the lint check '%1$s': move out to the " + "surrounding method", id)); @@ -721,19 +707,19 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner private class SwitchChecker extends AbstractUastVisitor { private final USwitchExpression mSwitchExpression; - private final PsiAnnotationMemberValue[] mAllowedValues; - private final List mFields; + private final List mAllowedValues; + private final List mFields; private final List mSeenValues; private boolean mReported = false; private SwitchChecker(USwitchExpression switchExpression, - PsiAnnotationMemberValue[] allowedValues) { + List allowedValues) { mSwitchExpression = switchExpression; mAllowedValues = allowedValues; - mFields = Lists.newArrayListWithCapacity(allowedValues.length); - for (PsiAnnotationMemberValue allowedValue : allowedValues) { + mFields = Lists.newArrayListWithCapacity(allowedValues.size()); + for (UExpression allowedValue : allowedValues) { if (allowedValue instanceof ExternalReferenceExpression) { ExternalReferenceExpression externalRef = (ExternalReferenceExpression) allowedValue; @@ -743,17 +729,17 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner if (resolved instanceof PsiField) { mFields.add(resolved); } - } else if (allowedValue instanceof PsiReferenceExpression) { - PsiElement resolved = ((PsiReferenceExpression) allowedValue).resolve(); + } else if (allowedValue instanceof UReferenceExpression) { + PsiElement resolved = ((UReferenceExpression) allowedValue).resolve(); if (resolved != null) { mFields.add(resolved); } - } else if (allowedValue instanceof PsiLiteral) { + } else if (allowedValue instanceof ULiteralExpression) { mFields.add(allowedValue); } } - mSeenValues = Lists.newArrayListWithCapacity(allowedValues.length); + mSeenValues = Lists.newArrayListWithCapacity(allowedValues.size()); } @Override @@ -807,9 +793,9 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner // So instead, manually check for equals. These lists tend to // be very short anyway. boolean found = false; - ListIterator iterator = mFields.listIterator(); + ListIterator iterator = mFields.listIterator(); while (iterator.hasNext()) { - PsiElement field = iterator.next(); + Object field = iterator.next(); if (field.equals(resolved)) { iterator.remove(); found = true; @@ -825,7 +811,7 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner if (resolved instanceof PsiField) { iterator = mFields.listIterator(); while (iterator.hasNext()) { - PsiElement field = iterator.next(); + Object field = iterator.next(); if (field.equals(resolved)) { iterator.remove(); found = true; @@ -842,8 +828,8 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner mSeenValues.add(cv); } } else { - List list = computeFieldNames(mSwitchExpression, - Arrays.asList(mAllowedValues)); + List list = computeFieldNames( + mSwitchExpression, Collections.singletonList(mAllowedValues)); // Keep error message in sync with {@link #getMissingCases} String message = "Unexpected constant; expected one of: " + Joiner .on(", ").join(list); @@ -874,9 +860,9 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner // Any missing switch constants? Before we flag them, look to see if any // of them have the same values: those can be omitted if (!mFields.isEmpty()) { - ListIterator iterator = mFields.listIterator(); + ListIterator iterator = mFields.listIterator(); while (iterator.hasNext()) { - PsiElement next = iterator.next(); + Object next = iterator.next(); if (next instanceof PsiField) { Integer cv = getConstantValue((PsiField)next); if (mSeenValues.contains(cv)) { @@ -937,30 +923,6 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner return list; } - /** - * Given an error message produced by this lint detector for the {@link #SWITCH_TYPE_DEF} issue - * type, returns the list of missing enum cases.

Intended for IDE quickfix implementations. - * - * @param errorMessage the error message associated with the error - * @param format the format of the error message - * @return the list of enum cases, or null if not recognized - */ - @Nullable - public static List getMissingCases(@NonNull String errorMessage, - @NonNull TextFormat format) { - errorMessage = format.toText(errorMessage); - - String substring = findSubstring(errorMessage, " missing case ", null); - if (substring == null) { - substring = findSubstring(errorMessage, "expected one of: ", null); - } - if (substring != null) { - return Splitter.on(",").trimResults().splitToList(substring); - } - - return null; - } - /** * Returns the node to use as the scope for the given annotation node. * You can't annotate an annotation itself (with {@code @SuppressLint}), but @@ -968,8 +930,8 @@ public class AnnotationDetector extends Detector implements Detector.UastScanner * suppress the error on this annotated element, not the whole surrounding class. */ @NonNull - private static PsiElement getAnnotationScope(@NonNull PsiAnnotation node) { - PsiElement scope = PsiTreeUtil.getParentOfType(node, PsiAnnotation.class, true); + private static UElement getAnnotationScope(@NonNull UAnnotation node) { + UElement scope = UastUtils.getParentOfType(node, UAnnotation.class, true); if (scope == null) { scope = node; } 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 b6c0e6b0354..7e05949bf4a 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 @@ -106,30 +106,10 @@ import com.intellij.psi.PsiReferenceExpression; import com.intellij.psi.PsiResourceListElement; import com.intellij.psi.PsiType; -import org.jetbrains.uast.UBinaryExpression; -import org.jetbrains.uast.UBinaryExpressionWithType; -import org.jetbrains.uast.UBlockExpression; -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UCatchClause; -import org.jetbrains.uast.UClass; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UFile; -import org.jetbrains.uast.UIfExpression; -import org.jetbrains.uast.UImportStatement; -import org.jetbrains.uast.ULiteralExpression; -import org.jetbrains.uast.ULocalVariable; -import org.jetbrains.uast.UMethod; -import org.jetbrains.uast.UReturnExpression; -import org.jetbrains.uast.USimpleNameReferenceExpression; -import org.jetbrains.uast.USwitchClauseExpression; -import org.jetbrains.uast.UTryExpression; -import org.jetbrains.uast.UVariable; -import org.jetbrains.uast.UastBinaryOperator; -import org.jetbrains.uast.UastOperator; -import org.jetbrains.uast.UastUtils; +import org.jetbrains.uast.*; import org.jetbrains.uast.expressions.UReferenceExpression; import org.jetbrains.uast.expressions.UTypeReferenceExpression; +import org.jetbrains.uast.java.JavaUAnnotation; import org.jetbrains.uast.util.UastExpressionUtils; import org.jetbrains.uast.visitor.AbstractUastVisitor; import org.jetbrains.uast.visitor.UastVisitor; @@ -1923,12 +1903,13 @@ public class ApiDetector extends ResourceXmlDetector } PsiModifierList modifierList = method.getModifierList(); - if (!checkRequiresApi(expression, method, modifierList)) { + List annotations = JavaUAnnotation.wrap(modifierList.getAnnotations()); + if (!checkRequiresApi(expression, method, annotations)) { PsiClass containingClass = method.getContainingClass(); if (containingClass != null) { modifierList = containingClass.getModifierList(); if (modifierList != null) { - checkRequiresApi(expression, method, modifierList); + checkRequiresApi(expression, method, annotations); } } } @@ -1938,9 +1919,11 @@ public class ApiDetector extends ResourceXmlDetector } // Look for @RequiresApi in modifier lists - private boolean checkRequiresApi(UCallExpression expression, PsiMethod method, - PsiModifierList modifierList) { - for (PsiAnnotation annotation : modifierList.getAnnotations()) { + private boolean checkRequiresApi( + UCallExpression expression, + PsiMethod method, + List annotations) { + for (UAnnotation annotation : annotations) { if (REQUIRES_API_ANNOTATION.equals(annotation.getQualifiedName())) { int api = (int) SupportAnnotationDetector.getLongAttribute(annotation, ATTR_VALUE, -1); @@ -1953,10 +1936,10 @@ public class ApiDetector extends ResourceXmlDetector if (api > minSdk) { int target = getTargetApi(expression); if (target == -1 || api > target) { - if (ApiDetector.isWithinVersionCheckConditional(expression, api, mContext)) { + if (isWithinVersionCheckConditional(expression, api, mContext)) { return true; } - if (ApiDetector.isPrecededByVersionCheckExit(expression, api, mContext)) { + if (isPrecededByVersionCheckExit(expression, api, mContext)) { return true; } @@ -2588,7 +2571,7 @@ public class ApiDetector extends ResourceXmlDetector } } - private static boolean isPrecededByVersionCheckExit(UElement element, int api, + protected static boolean isPrecededByVersionCheckExit(UElement element, int api, JavaContext context) { //noinspection unchecked UExpression currentExpression = UastUtils.getParentOfType(element, UExpression.class, diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionFinder.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionFinder.java index 7a8cfbc49f4..1f1357ac9c3 100644 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionFinder.java +++ b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionFinder.java @@ -19,45 +19,17 @@ import static com.android.SdkConstants.CLASS_INTENT; import static com.android.tools.klint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION; import static com.android.tools.klint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION_READ; import static com.android.tools.klint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION_WRITE; -import static org.jetbrains.uast.UastBinaryExpressionWithTypeKind.TYPE_CAST; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.tools.klint.client.api.UastLintUtils; import com.android.tools.klint.detector.api.JavaContext; -import com.intellij.psi.PsiAnnotation; -import com.intellij.psi.PsiAssignmentExpression; import com.intellij.psi.PsiClass; -import com.intellij.psi.PsiConditionalExpression; -import com.intellij.psi.PsiDeclarationStatement; import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiExpression; -import com.intellij.psi.PsiExpressionList; -import com.intellij.psi.PsiExpressionStatement; import com.intellij.psi.PsiField; -import com.intellij.psi.PsiJavaCodeReferenceElement; -import com.intellij.psi.PsiLiteral; -import com.intellij.psi.PsiLocalVariable; -import com.intellij.psi.PsiModifierList; -import com.intellij.psi.PsiNameValuePair; -import com.intellij.psi.PsiNewExpression; -import com.intellij.psi.PsiParenthesizedExpression; -import com.intellij.psi.PsiReferenceExpression; -import com.intellij.psi.PsiStatement; -import com.intellij.psi.PsiTypeCastExpression; import com.intellij.psi.PsiVariable; -import com.intellij.psi.util.PsiTreeUtil; -import org.jetbrains.uast.UBinaryExpressionWithType; -import org.jetbrains.uast.UCallExpression; -import org.jetbrains.uast.UElement; -import org.jetbrains.uast.UExpression; -import org.jetbrains.uast.UIfExpression; -import org.jetbrains.uast.ULiteralExpression; -import org.jetbrains.uast.UParenthesizedExpression; -import org.jetbrains.uast.UastCallKind; -import org.jetbrains.uast.UastLiteralUtils; -import org.jetbrains.uast.UastUtils; +import org.jetbrains.uast.*; import org.jetbrains.uast.expressions.UReferenceExpression; import org.jetbrains.uast.util.UastExpressionUtils; @@ -188,25 +160,24 @@ public class PermissionFinder { } else if (node instanceof UReferenceExpression) { PsiElement resolved = ((UReferenceExpression) node).resolve(); if (resolved instanceof PsiField) { - PsiField field = (PsiField) resolved; + UField field = (UField) mContext.getUastContext().convertElementWithParent(resolved, UField.class); + if (field == null) { + return null; + } if (mOperation == Operation.ACTION) { - PsiModifierList modifierList = field.getModifierList(); - PsiAnnotation annotation = modifierList != null - ? modifierList.findAnnotation(PERMISSION_ANNOTATION) : null; + UAnnotation annotation = field.findAnnotation(PERMISSION_ANNOTATION); if (annotation != null) { return getPermissionRequirement(field, annotation); } } else if (mOperation == Operation.READ || mOperation == Operation.WRITE) { String fqn = mOperation == Operation.READ ? PERMISSION_ANNOTATION_READ : PERMISSION_ANNOTATION_WRITE; - PsiModifierList modifierList = field.getModifierList(); - PsiAnnotation annotation = modifierList != null - ? modifierList.findAnnotation(fqn) : null; + UAnnotation annotation = field.findAnnotation(fqn); if (annotation != null) { - PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes(); - PsiNameValuePair o = attributes.length == 1 ? attributes[0] : null; - if (o != null && o.getValue() instanceof PsiAnnotation) { - annotation = (PsiAnnotation) o.getValue(); + List attributes = annotation.getAttributeValues(); + UNamedExpression o = attributes.size() == 1 ? attributes.get(0) : null; + if (o != null && o.getExpression() instanceof UAnnotation) { + annotation = (UAnnotation) o.getExpression(); if (PERMISSION_ANNOTATION.equals(annotation.getQualifiedName())) { return getPermissionRequirement(field, annotation); } @@ -242,8 +213,8 @@ public class PermissionFinder { @NonNull private Result getPermissionRequirement( @NonNull PsiField field, - @NonNull PsiAnnotation annotation) { - PermissionRequirement requirement = PermissionRequirement.create(mContext, annotation); + @NonNull UAnnotation annotation) { + PermissionRequirement requirement = PermissionRequirement.create(annotation); PsiClass containingClass = field.getContainingClass(); String name = containingClass != null ? containingClass.getName() + "." + field.getName() diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionRequirement.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionRequirement.java index b7317d6bf05..8a5309b279e 100644 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionRequirement.java +++ b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/PermissionRequirement.java @@ -34,6 +34,8 @@ import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiAnnotationMemberValue; import com.intellij.psi.PsiArrayInitializerMemberValue; import com.intellij.psi.tree.IElementType; +import org.jetbrains.uast.*; +import org.jetbrains.uast.util.UastExpressionUtils; import java.util.Arrays; import java.util.Collections; @@ -48,7 +50,7 @@ public abstract class PermissionRequirement { public static final String ATTR_PROTECTION_LEVEL = "protectionLevel"; //$NON-NLS-1$ public static final String VALUE_DANGEROUS = "dangerous"; //$NON-NLS-1$ - protected final PsiAnnotation annotation; + protected final UAnnotation annotation; private int firstApi; private int lastApi; @@ -102,15 +104,12 @@ public abstract class PermissionRequirement { } }; - private PermissionRequirement(@NonNull PsiAnnotation annotation) { + private PermissionRequirement(@NonNull UAnnotation annotation) { this.annotation = annotation; } @NonNull - public static PermissionRequirement create( - @NonNull JavaContext context, - @NonNull PsiAnnotation annotation) { - + public static PermissionRequirement create(@NonNull UAnnotation annotation) { String value = getAnnotationStringValue(annotation, ATTR_VALUE); if (value != null && !value.isEmpty()) { return new Single(annotation, value); @@ -138,10 +137,10 @@ public abstract class PermissionRequirement { } @Nullable - public static Boolean getAnnotationBooleanValue(@Nullable PsiAnnotation annotation, + public static Boolean getAnnotationBooleanValue(@Nullable UAnnotation annotation, @NonNull String name) { if (annotation != null) { - PsiAnnotationMemberValue attributeValue = annotation.findDeclaredAttributeValue(name); + UNamedExpression attributeValue = annotation.findDeclaredAttributeValue(name); if (attributeValue == null && ATTR_VALUE.equals(name)) { attributeValue = annotation.findDeclaredAttributeValue(null); } @@ -158,10 +157,10 @@ public abstract class PermissionRequirement { } @Nullable - public static Long getAnnotationLongValue(@Nullable PsiAnnotation annotation, + public static Long getAnnotationLongValue(@Nullable UAnnotation annotation, @NonNull String name) { if (annotation != null) { - PsiAnnotationMemberValue attributeValue = annotation.findDeclaredAttributeValue(name); + UNamedExpression attributeValue = annotation.findDeclaredAttributeValue(name); if (attributeValue == null && ATTR_VALUE.equals(name)) { attributeValue = annotation.findDeclaredAttributeValue(null); } @@ -178,10 +177,10 @@ public abstract class PermissionRequirement { } @Nullable - public static Double getAnnotationDoubleValue(@Nullable PsiAnnotation annotation, + public static Double getAnnotationDoubleValue(@Nullable UAnnotation annotation, @NonNull String name) { if (annotation != null) { - PsiAnnotationMemberValue attributeValue = annotation.findDeclaredAttributeValue(name); + UNamedExpression attributeValue = annotation.findDeclaredAttributeValue(name); if (attributeValue == null && ATTR_VALUE.equals(name)) { attributeValue = annotation.findDeclaredAttributeValue(null); } @@ -198,10 +197,10 @@ public abstract class PermissionRequirement { } @Nullable - public static String getAnnotationStringValue(@Nullable PsiAnnotation annotation, + public static String getAnnotationStringValue(@Nullable UAnnotation annotation, @NonNull String name) { if (annotation != null) { - PsiAnnotationMemberValue attributeValue = annotation.findDeclaredAttributeValue(name); + UNamedExpression attributeValue = annotation.findDeclaredAttributeValue(name); if (attributeValue == null && ATTR_VALUE.equals(name)) { attributeValue = annotation.findDeclaredAttributeValue(null); } @@ -218,19 +217,22 @@ public abstract class PermissionRequirement { } @Nullable - public static String[] getAnnotationStringValues(@Nullable PsiAnnotation annotation, + public static String[] getAnnotationStringValues(@Nullable UAnnotation annotation, @NonNull String name) { if (annotation != null) { - PsiAnnotationMemberValue attributeValue = annotation.findDeclaredAttributeValue(name); + UNamedExpression attributeValue = annotation.findDeclaredAttributeValue(name); if (attributeValue == null && ATTR_VALUE.equals(name)) { attributeValue = annotation.findDeclaredAttributeValue(null); } - if (attributeValue instanceof PsiArrayInitializerMemberValue) { - PsiAnnotationMemberValue[] initializers = - ((PsiArrayInitializerMemberValue) attributeValue).getInitializers(); - List result = Lists.newArrayListWithCapacity(initializers.length); + if (attributeValue == null) { + return null; + } + if (UastExpressionUtils.isArrayInitializer(attributeValue.getExpression())) { + List initializers = + ((UCallExpression) attributeValue.getExpression()).getValueArguments(); + List result = Lists.newArrayListWithCapacity(initializers.size()); ConstantEvaluator constantEvaluator = new ConstantEvaluator(null); - for (PsiAnnotationMemberValue element : initializers) { + for (UExpression element : initializers) { Object o = constantEvaluator.evaluate(element); if (o instanceof String) { result.add((String)o); @@ -243,22 +245,20 @@ public abstract class PermissionRequirement { } } else { // Use constant evaluator since we want to resolve field references as well - if (attributeValue != null) { - Object o = ConstantEvaluator.evaluate(null, attributeValue); - if (o instanceof String) { - return new String[]{(String) o}; - } else if (o instanceof String[]) { - return (String[])o; - } else if (o instanceof Object[]) { - Object[] array = (Object[]) o; - List strings = Lists.newArrayListWithCapacity(array.length); - for (Object element : array) { - if (element instanceof String) { - strings.add((String) element); - } + Object o = ConstantEvaluator.evaluate(null, attributeValue.getExpression()); + if (o instanceof String) { + return new String[]{(String) o}; + } else if (o instanceof String[]) { + return (String[])o; + } else if (o instanceof Object[]) { + Object[] array = (Object[]) o; + List strings = Lists.newArrayListWithCapacity(array.length); + for (Object element : array) { + if (element instanceof String) { + strings.add((String) element); } - return strings.toArray(new String[0]); } + return strings.toArray(new String[0]); } } } @@ -416,7 +416,7 @@ public abstract class PermissionRequirement { private static class Single extends PermissionRequirement { public final String name; - public Single(@NonNull PsiAnnotation annotation, @NonNull String name) { + public Single(@NonNull UAnnotation annotation, @NonNull String name) { super(annotation); this.name = name; } @@ -496,7 +496,7 @@ public abstract class PermissionRequirement { public final List permissions; public Many( - @NonNull PsiAnnotation annotation, + @NonNull UAnnotation annotation, IElementType operator, String[] names) { super(annotation); diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/StringFormatDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/StringFormatDetector.java index c1d194d94c5..8cfe511c687 100644 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/StringFormatDetector.java +++ b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/StringFormatDetector.java @@ -1167,7 +1167,7 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto (PsiVariable) resolved); if (initializer != null && (UastExpressionUtils.isNewArray(initializer) || - UastExpressionUtils.isNestedArrayInitializer(initializer))) { + UastExpressionUtils.isArrayInitializer(initializer))) { argWasReference = true; // Now handled by check below lastArg = initializer; @@ -1176,11 +1176,11 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto } if (UastExpressionUtils.isNewArray(lastArg) || - UastExpressionUtils.isNestedArrayInitializer(lastArg)) { + UastExpressionUtils.isArrayInitializer(lastArg)) { UCallExpression arrayInitializer = (UCallExpression) lastArg; if (UastExpressionUtils.isNewArrayWithInitializer(lastArg) || - UastExpressionUtils.isNestedArrayInitializer(lastArg)) { + UastExpressionUtils.isArrayInitializer(lastArg)) { callCount = arrayInitializer.getValueArgumentCount(); knownArity = true; } else if (UastExpressionUtils.isNewArrayWithDimensions(lastArg)) { diff --git a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SupportAnnotationDetector.java b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SupportAnnotationDetector.java index acf4623c45b..28bb1c10e71 100644 --- a/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SupportAnnotationDetector.java +++ b/plugins/lint/lint-checks/src/com/android/tools/klint/checks/SupportAnnotationDetector.java @@ -42,7 +42,6 @@ import static com.android.tools.klint.checks.PermissionRequirement.getAnnotation import static com.android.tools.klint.checks.PermissionRequirement.getAnnotationDoubleValue; import static com.android.tools.klint.checks.PermissionRequirement.getAnnotationLongValue; import static com.android.tools.klint.checks.PermissionRequirement.getAnnotationStringValue; -import static com.android.tools.klint.detector.api.LintUtils.skipParentheses; import static com.android.tools.klint.detector.api.ResourceEvaluator.COLOR_INT_ANNOTATION; import static com.android.tools.klint.detector.api.ResourceEvaluator.PX_ANNOTATION; import static com.android.tools.klint.detector.api.ResourceEvaluator.RES_SUFFIX; @@ -74,27 +73,23 @@ import com.android.utils.XmlUtils; import com.google.common.base.Joiner; import com.google.common.collect.Sets; import com.intellij.psi.PsiAnnotation; -import com.intellij.psi.PsiAnnotationMemberValue; -import com.intellij.psi.PsiArrayInitializerMemberValue; import com.intellij.psi.PsiArrayType; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiClassType; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiField; import com.intellij.psi.PsiJavaCodeReferenceElement; -import com.intellij.psi.PsiLiteral; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiModifier; import com.intellij.psi.PsiModifierList; -import com.intellij.psi.PsiNameValuePair; import com.intellij.psi.PsiParameter; import com.intellij.psi.PsiParameterList; -import com.intellij.psi.PsiReference; import com.intellij.psi.PsiType; import com.intellij.psi.PsiVariable; import org.jetbrains.uast.*; import org.jetbrains.uast.expressions.UReferenceExpression; +import org.jetbrains.uast.java.JavaUAnnotation; import org.jetbrains.uast.util.UastExpressionUtils; import org.jetbrains.uast.visitor.AbstractUastVisitor; import org.jetbrains.uast.visitor.UastVisitor; @@ -284,9 +279,9 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast @NonNull JavaContext context, @NonNull PsiMethod method, @NonNull UCallExpression call, - @NonNull PsiAnnotation annotation, - @NonNull PsiAnnotation[] allMethodAnnotations, - @NonNull PsiAnnotation[] allClassAnnotations) { + @NonNull UAnnotation annotation, + @NonNull List allMethodAnnotations, + @NonNull List allClassAnnotations) { String signature = annotation.getQualifiedName(); if (signature == null) { return; @@ -296,7 +291,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast || signature.endsWith(".CheckReturnValue")) { checkResult(context, call, method, annotation); } else if (signature.equals(PERMISSION_ANNOTATION)) { - PermissionRequirement requirement = PermissionRequirement.create(context, annotation); + PermissionRequirement requirement = PermissionRequirement.create(annotation); checkPermission(context, call, method, null, requirement); } else if (signature.endsWith(THREAD_SUFFIX) && signature.startsWith(SUPPORT_ANNOTATIONS_PREFIX)) { @@ -310,9 +305,9 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast @NonNull UExpression argument, @NonNull UCallExpression call, @NonNull PsiMethod method, - @NonNull PsiAnnotation[] annotations) { + @NonNull List annotations) { boolean handledResourceTypes = false; - for (PsiAnnotation annotation : annotations) { + for (UAnnotation annotation : annotations) { String signature = annotation.getQualifiedName(); if (signature == null) { continue; @@ -356,7 +351,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast // Handle all resource type annotations in one go: there could be multiple // resource type annotations specified on the same element; we need to // know about them all up front. - for (PsiAnnotation a : annotations) { + for (UAnnotation a : annotations) { String s = a.getQualifiedName(); if (s != null && s.endsWith(RES_SUFFIX)) { String typeString = s.substring(SUPPORT_ANNOTATIONS_PREFIX.length(), @@ -485,7 +480,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast if (!requirement.isSatisfied(permissions)) { // See if it looks like we're holding the permission implicitly by @RequirePermission // annotations in the surrounding context - permissions = addLocalPermissions(context, permissions, node); + permissions = addLocalPermissions(permissions, node); if (!requirement.isSatisfied(permissions)) { if (isIgnoredInIde(MISSING_PERMISSION, context, node)) { return; @@ -567,35 +562,32 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast @NonNull private static PermissionHolder addLocalPermissions( - @NonNull JavaContext context, @NonNull PermissionHolder permissions, - @NonNull UElement node) { + @NonNull UElement node + ) { // Accumulate @RequirePermissions available in the local context UMethod method = UastUtils.getParentOfType(node, UMethod.class, true); if (method == null) { return permissions; } - PsiAnnotation annotation = method.getModifierList().findAnnotation(PERMISSION_ANNOTATION); - permissions = mergeAnnotationPermissions(context, permissions, annotation); + UAnnotation annotation = method.findAnnotation(PERMISSION_ANNOTATION); + permissions = mergeAnnotationPermissions(permissions, annotation); - PsiClass containingClass = method.getContainingClass(); + UClass containingClass = UastUtils.getContainingUClass(method); if (containingClass != null) { - PsiModifierList modifierList = containingClass.getModifierList(); - if (modifierList != null) { - annotation = modifierList.findAnnotation(PERMISSION_ANNOTATION); - permissions = mergeAnnotationPermissions(context, permissions, annotation); - } + annotation = containingClass.findAnnotation(PERMISSION_ANNOTATION); + permissions = mergeAnnotationPermissions(permissions, annotation); } return permissions; } @NonNull private static PermissionHolder mergeAnnotationPermissions( - @NonNull JavaContext context, @NonNull PermissionHolder permissions, - @Nullable PsiAnnotation annotation) { + @Nullable UAnnotation annotation + ) { if (annotation != null) { - PermissionRequirement requirement = PermissionRequirement.create(context, annotation); + PermissionRequirement requirement = PermissionRequirement.create(annotation); permissions = SetPermissionLookup.join(permissions, requirement); } @@ -759,7 +751,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast } private static void checkResult(@NonNull JavaContext context, @NonNull UCallExpression node, - @NonNull PsiMethod method, @NonNull PsiAnnotation annotation) { + @NonNull PsiMethod method, @NonNull UAnnotation annotation) { if (isExpressionValueUnused(node)) { String methodName = JavaContext.getMethodName(node); String suggested = getAnnotationStringValue(annotation, ATTR_SUGGEST); @@ -806,9 +798,9 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast @NonNull UElement node, @NonNull PsiMethod method, @NonNull String signature, - @NonNull PsiAnnotation annotation, - @NonNull PsiAnnotation[] allMethodAnnotations, - @NonNull PsiAnnotation[] allClassAnnotations) { + @NonNull UAnnotation annotation, + @NonNull List allMethodAnnotations, + @NonNull List allClassAnnotations) { List threadContext = getThreadContext(context, node); if (threadContext != null && !isCompatibleThread(threadContext, signature) && !isIgnoredInIde(THREAD, context, node)) { @@ -823,7 +815,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast assert containsAnnotation(allMethodAnnotations, annotation); // See if any of the *other* annotations are compatible. Boolean isFirst = null; - for (PsiAnnotation other : allMethodAnnotations) { + for (UAnnotation other : allMethodAnnotations) { if (other == annotation) { if (isFirst == null) { isFirst = true; @@ -870,9 +862,9 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast } public static boolean containsAnnotation( - @NonNull PsiAnnotation[] array, - @NonNull PsiAnnotation annotation) { - for (PsiAnnotation a : array) { + @NonNull List array, + @NonNull UAnnotation annotation) { + for (UAnnotation a : array) { if (a == annotation) { return true; } @@ -881,8 +873,8 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast return false; } - public static boolean containsThreadingAnnotation(@NonNull PsiAnnotation[] array) { - for (PsiAnnotation annotation : array) { + public static boolean containsThreadingAnnotation(@NonNull List array) { + for (UAnnotation annotation : array) { if (isThreadingAnnotation(annotation)) { return true; } @@ -891,7 +883,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast return false; } - public static boolean isThreadingAnnotation(@NonNull PsiAnnotation annotation) { + public static boolean isThreadingAnnotation(@NonNull UAnnotation annotation) { String signature = annotation.getQualifiedName(); return signature != null && signature.endsWith(THREAD_SUFFIX) @@ -1225,9 +1217,9 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast private static void checkIntRange( @NonNull JavaContext context, - @NonNull PsiAnnotation annotation, + @NonNull UAnnotation annotation, @NonNull UElement argument, - @NonNull PsiAnnotation[] allAnnotations) { + @NonNull List allAnnotations) { String message = getIntRangeError(context, annotation, argument); if (message != null) { if (findIntDef(allAnnotations) != null) { @@ -1248,7 +1240,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast @Nullable private static String getIntRangeError( @NonNull JavaContext context, - @NonNull PsiAnnotation annotation, + @NonNull UAnnotation annotation, @NonNull UElement argument) { if (UastExpressionUtils.isNewArrayWithInitializer(argument)) { UCallExpression newExpression = (UCallExpression) argument; @@ -1295,7 +1287,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast private static void checkFloatRange( @NonNull JavaContext context, - @NonNull PsiAnnotation annotation, + @NonNull UAnnotation annotation, @NonNull UElement argument) { Object object = ConstantEvaluator.evaluate(context, argument); if (!(object instanceof Number)) { @@ -1387,7 +1379,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast private static void checkSize( @NonNull JavaContext context, - @NonNull PsiAnnotation annotation, + @NonNull UAnnotation annotation, @NonNull UElement argument) { int actual; boolean isString = false; @@ -1459,9 +1451,9 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast } @Nullable - private static PsiAnnotation findIntRange( - @NonNull PsiAnnotation[] annotations) { - for (PsiAnnotation annotation : annotations) { + private static UAnnotation findIntRange( + @NonNull List annotations) { + for (UAnnotation annotation : annotations) { if (INT_RANGE_ANNOTATION.equals(annotation.getQualifiedName())) { return annotation; } @@ -1471,8 +1463,8 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast } @Nullable - static PsiAnnotation findIntDef(@NonNull PsiAnnotation[] annotations) { - for (PsiAnnotation annotation : annotations) { + static UAnnotation findIntDef(@NonNull List annotations) { + for (UAnnotation annotation : annotations) { if (INT_DEF_ANNOTATION.equals(annotation.getQualifiedName())) { return annotation; } @@ -1483,11 +1475,11 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast private static void checkTypeDefConstant( @NonNull JavaContext context, - @NonNull PsiAnnotation annotation, + @NonNull UAnnotation annotation, @Nullable UElement argument, @Nullable UElement errorNode, boolean flag, - @NonNull PsiAnnotation[] allAnnotations) { + @NonNull List allAnnotations) { if (argument == null) { return; } @@ -1615,10 +1607,10 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast } private static void checkTypeDefConstant(@NonNull JavaContext context, - @NonNull PsiAnnotation annotation, @NonNull UElement argument, + @NonNull UAnnotation annotation, @NonNull UElement argument, @Nullable UElement errorNode, boolean flag, Object value, - @NonNull PsiAnnotation[] allAnnotations) { - PsiAnnotation rangeAnnotation = findIntRange(allAnnotations); + @NonNull List allAnnotations) { + UAnnotation rangeAnnotation = findIntRange(allAnnotations); if (rangeAnnotation != null) { // Allow @IntRange on this number if (getIntRangeError(context, rangeAnnotation, argument) == null) { @@ -1626,18 +1618,17 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast } } - PsiAnnotationMemberValue allowed = getAnnotationValue(annotation); + UExpression allowed = getAnnotationValue(annotation); if (allowed == null) { return; } - if (allowed instanceof PsiArrayInitializerMemberValue) { - PsiArrayInitializerMemberValue initializerExpression = - (PsiArrayInitializerMemberValue) allowed; - PsiAnnotationMemberValue[] initializers = initializerExpression.getInitializers(); - for (PsiAnnotationMemberValue expression : initializers) { - if (expression instanceof PsiLiteral) { - if (value.equals(((PsiLiteral)expression).getValue())) { + if (UastExpressionUtils.isArrayInitializer(allowed)) { + UCallExpression initializerExpression = (UCallExpression) allowed; + List initializers = initializerExpression.getValueArguments(); + for (UExpression expression : initializers) { + if (expression instanceof ULiteralExpression) { + if (value.equals(((ULiteralExpression)expression).getValue())) { return; } } else if (expression instanceof ExternalReferenceExpression) { @@ -1646,8 +1637,8 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast if (resolved != null && resolved.equals(value)) { return; } - } else if (expression instanceof PsiReference) { - PsiElement resolved = ((PsiReference) expression).resolve(); + } else if (expression instanceof UReferenceExpression) { + PsiElement resolved = ((UReferenceExpression) expression).resolve(); if (resolved != null && resolved.equals(value)) { return; } @@ -1669,23 +1660,27 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast } } - private static void reportTypeDef(@NonNull JavaContext context, - @NonNull PsiAnnotation annotation, @NonNull UElement argument, - @Nullable UElement errorNode, @NonNull PsiAnnotation[] allAnnotations) { + private static void reportTypeDef( + @NonNull JavaContext context, + @NonNull UAnnotation annotation, + @NonNull UElement argument, + @Nullable UElement errorNode, + @NonNull List allAnnotations + ) { // reportTypeDef(context, argument, errorNode, false, allowedValues, allAnnotations); - PsiAnnotationMemberValue allowed = getAnnotationValue(annotation); - if (allowed instanceof PsiArrayInitializerMemberValue) { - PsiArrayInitializerMemberValue initializerExpression = - (PsiArrayInitializerMemberValue) allowed; - PsiAnnotationMemberValue[] initializers = initializerExpression.getInitializers(); + UExpression allowed = getAnnotationValue(annotation); + if (UastExpressionUtils.isArrayInitializer(allowed)) { + UCallExpression initializerExpression = + (UCallExpression) allowed; + List initializers = initializerExpression.getValueArguments(); reportTypeDef(context, argument, errorNode, false, initializers, allAnnotations); } } private static void reportTypeDef(@NonNull JavaContext context, @NonNull UElement node, @Nullable UElement errorNode, boolean flag, - @NonNull PsiAnnotationMemberValue[] allowedValues, - @NonNull PsiAnnotation[] allAnnotations) { + @NonNull List allowedValues, + @NonNull List allAnnotations) { if (errorNode == null) { errorNode = node; } @@ -1701,7 +1696,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast message = "Must be one of: " + values; } - PsiAnnotation rangeAnnotation = findIntRange(allAnnotations); + UAnnotation rangeAnnotation = findIntRange(allAnnotations); if (rangeAnnotation != null) { // Allow @IntRange on this number String rangeError = getIntRangeError(context, rangeAnnotation, node); @@ -1715,27 +1710,29 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast } @Nullable - private static PsiAnnotationMemberValue getAnnotationValue(@NonNull PsiAnnotation annotation) { - PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes(); - for (PsiNameValuePair pair : attributes) { - if (pair.getName() == null || pair.getName().equals(ATTR_VALUE)) { - return pair.getValue(); - } + private static UExpression getAnnotationValue(@NonNull UAnnotation annotation) { + UNamedExpression value = annotation.findDeclaredAttributeValue(ATTR_VALUE); + if (value == null) { + value = annotation.findDeclaredAttributeValue(null); } - return null; + if (value == null) { + return null; + } + + return value.getExpression(); } private static String listAllowedValues(@NonNull UElement context, - @NonNull PsiAnnotationMemberValue[] allowedValues) { + @NonNull List allowedValues) { StringBuilder sb = new StringBuilder(); - for (PsiAnnotationMemberValue allowedValue : allowedValues) { + for (UExpression allowedValue : allowedValues) { String s = null; PsiElement resolved = null; if (allowedValue instanceof ExternalReferenceExpression) { resolved = UastLintUtils.resolve( (ExternalReferenceExpression) allowedValue, context); - } else if (allowedValue instanceof PsiReference) { - resolved = ((PsiReference) allowedValue).resolve(); + } else if (allowedValue instanceof UReferenceExpression) { + resolved = ((UReferenceExpression) allowedValue).resolve(); } if (resolved instanceof PsiField) { @@ -1749,7 +1746,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast } if (s == null) { - s = allowedValue.getText(); + s = allowedValue.asSourceString(); } if (sb.length() > 0) { sb.append(", "); @@ -1759,7 +1756,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast return sb.toString(); } - static double getDoubleAttribute(@NonNull PsiAnnotation annotation, + static double getDoubleAttribute(@NonNull UAnnotation annotation, @NonNull String name, double defaultValue) { Double value = getAnnotationDoubleValue(annotation, name); if (value != null) { @@ -1769,7 +1766,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast return defaultValue; } - static long getLongAttribute(@NonNull PsiAnnotation annotation, + static long getLongAttribute(@NonNull UAnnotation annotation, @NonNull String name, long defaultValue) { Long value = getAnnotationLongValue(annotation, name); if (value != null) { @@ -1779,7 +1776,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast return defaultValue; } - static boolean getBoolean(@NonNull PsiAnnotation annotation, + static boolean getBoolean(@NonNull UAnnotation annotation, @NonNull String name, boolean defaultValue) { Boolean value = getAnnotationBooleanValue(annotation, name); if (value != null) { @@ -1909,27 +1906,30 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast public void checkCall(PsiMethod method, UCallExpression call) { JavaEvaluator evaluator = mContext.getEvaluator(); - PsiAnnotation[] methodAnnotations = evaluator.getAllAnnotations(method); - methodAnnotations = filterRelevantAnnotations(evaluator, methodAnnotations); + List methodAnnotations; + { + PsiAnnotation[] annotations = evaluator.getAllAnnotations(method); + methodAnnotations = JavaUAnnotation.wrap(filterRelevantAnnotations(evaluator, annotations)); + } // Look for annotations on the class as well: these trickle // down to all the methods in the class PsiClass containingClass = method.getContainingClass(); - PsiAnnotation[] classAnnotations; + List classAnnotations; if (containingClass != null) { - classAnnotations = evaluator.getAllAnnotations(containingClass); - classAnnotations = filterRelevantAnnotations(evaluator, classAnnotations); + PsiAnnotation[] annotations = evaluator.getAllAnnotations(containingClass); + classAnnotations = JavaUAnnotation.wrap(filterRelevantAnnotations(evaluator, annotations)); } else { - classAnnotations = PsiAnnotation.EMPTY_ARRAY; + classAnnotations = Collections.emptyList(); } - for (PsiAnnotation annotation : methodAnnotations) { + for (UAnnotation annotation : methodAnnotations) { checkMethodAnnotation(mContext, method, call, annotation, methodAnnotations, classAnnotations); } - if (classAnnotations.length > 0) { - for (PsiAnnotation annotation : classAnnotations) { + if (!classAnnotations.isEmpty()) { + for (UAnnotation annotation : classAnnotations) { checkMethodAnnotation(mContext, method, call, annotation, methodAnnotations, classAnnotations); } @@ -1938,14 +1938,14 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast List arguments = call.getValueArguments(); PsiParameterList parameterList = method.getParameterList(); PsiParameter[] parameters = parameterList.getParameters(); - PsiAnnotation[] annotations = null; + List annotations = null; for (int i = 0, n = Math.min(parameters.length, arguments.size()); i < n; i++) { UExpression argument = arguments.get(i); PsiParameter parameter = parameters[i]; - annotations = evaluator.getAllAnnotations(parameter); - annotations = filterRelevantAnnotations(evaluator, annotations); + annotations = JavaUAnnotation.wrap( + filterRelevantAnnotations(evaluator, evaluator.getAllAnnotations(parameter))); checkParameterAnnotations(mContext, argument, call, method, annotations); } if (annotations != null) { 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 new file mode 100644 index 00000000000..8a1201258c4 --- /dev/null +++ b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijApiDetector.java @@ -0,0 +1,913 @@ +/* + * 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.tools.klint.checks.ApiDetector; +import com.android.tools.klint.checks.ApiLookup; +import com.android.tools.klint.client.api.UastLintUtils; +import com.android.tools.klint.detector.api.*; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.PsiClassReferenceType; +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.*; +import org.jetbrains.uast.expressions.UInstanceExpression; +import org.jetbrains.uast.util.UastExpressionUtils; +import org.jetbrains.uast.visitor.AbstractUastVisitor; +import org.jetbrains.uast.visitor.UastVisitor; + +import java.util.Collections; +import java.util.EnumSet; +import java.util.List; + +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: + *

    + *
  • Port this part from the bytecode based check: + * if (owner.equals("java/text/SimpleDateFormat")) { + * checkSimpleDateFormat(context, method, node, minSdk); + * } + *
  • + *
  • Compare to the bytecode based results
  • + *
+ */ +public class IntellijApiDetector extends ApiDetector { + @SuppressWarnings("unchecked") + public static final Implementation IMPLEMENTATION = new Implementation( + IntellijApiDetector.class, + EnumSet.of(Scope.RESOURCE_FILE, Scope.MANIFEST, Scope.JAVA_FILE), + Scope.MANIFEST_SCOPE, + Scope.RESOURCE_FILE_SCOPE, + Scope.JAVA_FILE_SCOPE + ); + + @NonNls + private static final String TARGET_API_FQCN = "android.annotation.TargetApi"; + + @Override + public List> getApplicableUastTypes() { + return Collections.>singletonList(UFile.class); + } + + // TODO: Reuse the parent ApiVisitor to share more code between these two! + @Override + public UastVisitor createUastVisitor(@NonNull final JavaContext context) { + return new AbstractUastVisitor() { + @Override + public boolean visitFile(@NotNull UFile file) { + List classes = file.getClasses(); + if (!classes.isEmpty()) { + // TODO: This is weird; I should just perform the per class checks as part of visitClass!! + file.accept(new ApiCheckVisitor(context, classes.get(0), file)); + } + return super.visitFile(file); + } + }; + } + + private static int getTargetApi(@NonNull UElement e, @NonNull UFile file) { + UElement element = e; + // Search upwards for target api annotations + while (element != null && element != file) { + if (element instanceof UAnnotated) { + UAnnotated owner = (UAnnotated)element; + UAnnotation annotation = owner.findAnnotation(TARGET_API_FQCN); + if (annotation == null) { + annotation = owner.findAnnotation(REQUIRES_API_ANNOTATION); + } + if (annotation != null) { + for (UNamedExpression pair : annotation.getAttributeValues()) { + UExpression v = pair.getExpression(); + + if (v instanceof ULiteralExpression) { + ULiteralExpression literal = (ULiteralExpression)v; + Object value = literal.getValue(); + if (value instanceof Integer) { + return (Integer) value; + } else if (value instanceof String) { + return codeNameToApi((String) value); + } + } else if (UastExpressionUtils.isArrayInitializer(v)) { + UCallExpression mv = (UCallExpression)v; + for (UExpression mmv : mv.getValueArguments()) { + if (mmv instanceof ULiteralExpression) { + ULiteralExpression literal = (ULiteralExpression)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 UResolvable) { + PsiElement resolved = ((UResolvable) v).resolve(); + if (resolved != null) { + String fqcn = UastLintUtils.getQualifiedName(resolved); + return codeNameToApi(fqcn); + } else { + return codeNameToApi(v.asRenderString()); + } + } + } + } + } + element = element.getContainingElement(); + } + + return -1; + } + + private class ApiCheckVisitor extends AbstractUastVisitor { + private final JavaContext myContext; + private boolean mySeenSuppress; + private boolean mySeenTargetApi; + private final UClass myClass; + private final UFile myFile; + private final boolean myCheckAccess; + private boolean myCheckOverride; + private String myFrameworkParent; + + public ApiCheckVisitor(JavaContext context, UClass clz, UFile file) { + myContext = context; + myClass = clz; + myFile = file; + + myCheckAccess = context.isEnabled(UNSUPPORTED) || context.isEnabled(INLINED); + myCheckOverride = context.isEnabled(OVERRIDE) + && context.getMainProject().getBuildSdk() >= 1; + int depth = 0; + if (myCheckOverride) { + myFrameworkParent = null; + UClass superClass = myClass.getUastSuperClass(); + 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 = ClassContext.getInternalName(fqcn); + } + break; + } + superClass = superClass.getUastSuperClass(); + depth++; + if (depth == 500) { + // Shouldn't happen in practice; this prevents the IDE from + // hanging if the user has accidentally typed in an incorrect + // super class which creates a cycle. + break; + } + } + if (myFrameworkParent == null) { + myCheckOverride = false; + } + } + } + + @Override + public boolean visitAnnotation(@NotNull UAnnotation annotation) { + String fqcn = annotation.getQualifiedName(); + if (TARGET_API_FQCN.equals(fqcn) || REQUIRES_API_ANNOTATION.equals(fqcn)) { + mySeenTargetApi = true; + } + else if (SUPPRESS_LINT_FQCN.equals(fqcn) || SUPPRESS_WARNINGS_FQCN.equals(fqcn)) { + mySeenSuppress = true; + } + + return super.visitAnnotation(annotation); + } + + @Override + public boolean visitMethod(@NotNull UMethod method) { + + // API check for default methods + if (method.getModifierList().hasExplicitModifier(PsiModifier.DEFAULT)) { + int api = 24; // minSdk for default methods + int minSdk = getMinSdk(myContext); + + if (!isSuppressed(api, method, minSdk)) { + Location location = IntellijLintUtils.getUastLocation(myContext.file, method); + String message = String.format("Default method requires API level %1$d (current min is %2$d)", api, minSdk); + myContext.report(UNSUPPORTED, location, message); + } + } + + if (!myCheckOverride) { + return super.visitMethod(method); + } + + 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 super.visitMethod(method); + } + int api = mApiDatabase.getCallVersion(myFrameworkParent, name, desc); + if (api > buildSdk && buildSdk != -1) { + if (mySeenSuppress && + IntellijLintUtils.isSuppressed(method, myFile.getPsi(), OVERRIDE)) { + return super.visitMethod(method); + } + + // 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; + } + Location location = IntellijLintUtils.getLocation(myContext.file, locationNode); + myContext.report(OVERRIDE, location, message); + } + + return super.visitMethod(method); + } + + @Override + public boolean visitClass(@NotNull UClass aClass) { + if (!myCheckAccess) { + return super.visitClass(aClass); + } + + if (aClass.isAnnotationType()) { + PsiModifierList modifierList = aClass.getModifierList(); + if (modifierList != null) { + for (PsiAnnotation annotation : modifierList.getAnnotations()) { + String name = annotation.getQualifiedName(); + if ("java.lang.annotation.Repeatable".equals(name)) { + int api = 24; // minSdk for repeatable annotations + int minSdk = getMinSdk(myContext); + if (!isSuppressed(api, aClass, minSdk)) { + Location location = IntellijLintUtils.getLocation(myContext.file, annotation); + String message = String.format("Repeatable annotation requires API level %1$d (current min is %2$d)", api, minSdk); + myContext.report(UNSUPPORTED, location, message); + } + } + } + } + } + + 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.getPsi(), UNSUPPORTED)) { + continue; + } + + Location location; + if (type instanceof PsiClassReferenceType) { + PsiReference reference = ((PsiClassReferenceType)type).getReference(); + PsiElement element = reference.getElement(); + if (isWithinVersionCheckConditional(aClass, api, myContext)) { + continue; + } + if (isPrecededByVersionCheckExit(aClass, api, myContext)) { + continue; + } + location = IntellijLintUtils.getLocation(myContext.file, element); + } else { + location = IntellijLintUtils.getLocation(myContext.file, aClass); //TODO to super type reference + } + 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); + } + + return super.visitClass(aClass); + } + + @Override + public boolean visitSimpleNameReferenceExpression(@NotNull USimpleNameReferenceExpression expression) { + if (!myCheckAccess) { + return super.visitSimpleNameReferenceExpression(expression); + } + + PsiElement resolved = expression.resolve(); + if (resolved != null) { + if (resolved instanceof PsiField) { + PsiField field = (PsiField)resolved; + PsiClass containingClass = field.getContainingClass(); + if (containingClass == null) { + return super.visitSimpleNameReferenceExpression(expression); + } + String owner = IntellijLintUtils.getInternalName(containingClass); + if (owner == null) { + return super.visitSimpleNameReferenceExpression(expression); // Couldn't resolve type + } + String name = field.getName(); + if (name == null) { + return super.visitSimpleNameReferenceExpression(expression); + } + + int api = mApiDatabase.getFieldVersion(owner, name); + if (api == -1) { + return super.visitSimpleNameReferenceExpression(expression); + } + int minSdk = getMinSdk(myContext); + if (isSuppressed(api, expression, minSdk)) { + return super.visitSimpleNameReferenceExpression(expression); + } + + Location location = IntellijLintUtils.getUastLocation(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); + + 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 super.visitSimpleNameReferenceExpression(expression); + } + } + + myContext.report(issue, location, message); + } + } + + return super.visitSimpleNameReferenceExpression(expression); + } + + @Override + public boolean visitTryExpression(@NotNull UTryExpression statement) { + List resourceList = statement.getResources(); + if (resourceList != null) { + int api = 19; // minSdk for try with resources + int minSdk = getMinSdk(myContext); + + if (isSuppressed(api, statement, minSdk)) { + return super.visitTryExpression(statement); + } + Location location = IntellijLintUtils.getUastLocation(myContext.file, statement.getTryClause()); + 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 (UCatchClause catchClause : statement.getCatchClauses()) { + for (PsiParameter parameter : catchClause.getParameters()) { + PsiTypeElement typeElement = parameter.getTypeElement(); + if (typeElement != null) { + checkCatchTypeElement(statement, typeElement, typeElement.getType()); + } + } + } + + return super.visitTryExpression(statement); + } + + private void checkCatchTypeElement(@NonNull UTryExpression statement, + @NotNull PsiTypeElement typeElement, + @Nullable PsiType type) { + PsiClass resolved = null; + if (type instanceof PsiDisjunctionType) { + PsiDisjunctionType disjunctionType = (PsiDisjunctionType)type; + type = disjunctionType.getLeastUpperBound(); + if (type instanceof PsiClassType) { + resolved = ((PsiClassType)type).resolve(); + } + for (PsiElement child : typeElement.getChildren()) { + if (child instanceof PsiTypeElement) { + PsiTypeElement childTypeElement = (PsiTypeElement)child; + PsiType childType = childTypeElement.getType(); + if (!type.equals(childType)) { + checkCatchTypeElement(statement, childTypeElement, childType); + } + } + } + } else if (type instanceof PsiClassReferenceType) { + PsiClassReferenceType referenceType = (PsiClassReferenceType)type; + resolved = referenceType.resolve(); + } else if (type instanceof PsiClassType) { + resolved = ((PsiClassType)type).resolve(); + } + if (resolved != null) { + String signature = IntellijLintUtils.getInternalName(resolved); + if (signature == null) { + return; + } + + int api = mApiDatabase.getClassVersion(signature); + if (api == -1) { + return; + } + int minSdk = getMinSdk(myContext); + if (api <= minSdk) { + return; + } + if (mySeenTargetApi) { + int target = getTargetApi(statement, myFile); + if (target != -1) { + if (api <= target) { + return; + } + } + } + if (mySeenSuppress && IntellijLintUtils.isSuppressed(statement, myFile, UNSUPPORTED)) { + return; + } + + Location location; + location = IntellijLintUtils.getLocation(myContext.file, typeElement); + 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 && "ReflectiveOperationException".equals(fqcn)) { + 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, UElement 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, myContext)) { + return true; + } + if (isPrecededByVersionCheckExit(element, api, myContext)) { + return true; + } + + return false; + } + + @Override + public boolean visitBinaryExpressionWithType(@NotNull UBinaryExpressionWithType expression) { + if (myCheckAccess) { + UExpression operand = expression.getOperand(); + PsiType operandType = operand.getExpressionType(); + PsiType castType = expression.getType(); + if (castType.equals(operandType)) { + return super.visitBinaryExpressionWithType(expression); + } + if (!(operandType instanceof PsiClassType)) { + return super.visitBinaryExpressionWithType(expression); + } + if (!(castType instanceof PsiClassType)) { + return super.visitBinaryExpressionWithType(expression); + } + PsiClassType classType = (PsiClassType)operandType; + PsiClassType interfaceType = (PsiClassType)castType; + checkCast(expression, classType, interfaceType); + } + + return super.visitBinaryExpressionWithType(expression); + } + + private void checkCast(@NotNull UElement node, @NotNull PsiClassType classType, @NotNull PsiClassType interfaceType) { + if (classType.equals(interfaceType)) { + return; + } + String classTypeInternal = IntellijLintUtils.getInternalName(classType); + String interfaceTypeInternal = IntellijLintUtils.getInternalName(interfaceType); + if (classTypeInternal == null || interfaceTypeInternal == null || "java/lang/Object".equals(interfaceTypeInternal)) { + return; // Couldn't resolve type + } + + int api = mApiDatabase.getValidCastVersion(classTypeInternal, interfaceTypeInternal); + if (api == -1) { + return; + } + + int minSdk = getMinSdk(myContext); + if (api <= minSdk) { + return; + } + + if (isSuppressed(api, node, minSdk)) { + return; + } + + Location location = IntellijLintUtils.getUastLocation(myContext.file, node); + String message = String.format("Cast from %1$s to %2$s requires API level %3$d (current min is %4$d)", + classType.getClassName(), interfaceType.getClassName(), api, minSdk); + myContext.report(UNSUPPORTED, location, message); + } + + @Override + public boolean visitVariable(@NotNull UVariable variable) { + if (variable instanceof ULocalVariable) { + if (!myCheckAccess) { + return super.visitVariable(variable); + } + + UExpression initializer = variable.getUastInitializer(); + if (initializer == null) { + return super.visitVariable(variable); + } + + PsiType initializerType = initializer.getExpressionType(); + if (initializerType == null || !(initializerType instanceof PsiClassType)) { + return super.visitVariable(variable); + } + + PsiType interfaceType = variable.getType(); + if (initializerType.equals(interfaceType)) { + return super.visitVariable(variable); + } + + if (!(interfaceType instanceof PsiClassType)) { + return super.visitVariable(variable); + } + + checkCast(initializer, (PsiClassType)initializerType, (PsiClassType)interfaceType); + } + + return super.visitVariable(variable); + } + + @Override + public boolean visitBinaryExpression(@NotNull UBinaryExpression expression) { + if (expression.getOperator() instanceof UastBinaryOperator.AssignOperator) { + if (!myCheckAccess) { + return super.visitBinaryExpression(expression); + } + + UExpression rExpression = expression.getRightOperand(); + PsiType rhsType = rExpression.getExpressionType(); + if (rhsType == null || !(rhsType instanceof PsiClassType)) { + return super.visitBinaryExpression(expression); + } + + PsiType interfaceType = expression.getLeftOperand().getExpressionType(); + if (rhsType.equals(interfaceType)) { + return super.visitBinaryExpression(expression); + } + + if (!(interfaceType instanceof PsiClassType)) { + return super.visitBinaryExpression(expression); + } + + checkCast(rExpression, (PsiClassType)rhsType, (PsiClassType)interfaceType); + } + + return super.visitBinaryExpression(expression); + } + + @Override + public boolean visitForEachExpression(@NotNull UForEachExpression statement) { + // The for each method will implicitly call iterator() on the + // Iterable that is used in the for each loop; make sure that + // the API level for that + if (!myCheckAccess) { + return super.visitForEachExpression(statement); + } + + UExpression value = statement.getIteratedValue(); + PsiType type = value.getExpressionType(); + + if (type instanceof PsiClassType) { + String expressionOwner = IntellijLintUtils.getInternalName((PsiClassType)type); + if (expressionOwner != null) { + int api = mApiDatabase.getClassVersion(expressionOwner); + if (api == -1) { + return super.visitForEachExpression(statement); + } + int minSdk = getMinSdk(myContext); + if (api <= minSdk) { + return super.visitForEachExpression(statement); + } + if (mySeenTargetApi) { + int target = getTargetApi(statement, myFile); + if (target != -1) { + if (api <= target) { + return super.visitForEachExpression(statement); + } + } + } + if (mySeenSuppress && IntellijLintUtils.isSuppressed(statement, myFile, UNSUPPORTED)) { + return super.visitForEachExpression(statement); + } + + if (isWithinVersionCheckConditional(statement, api, myContext)) { + return super.visitForEachExpression(statement); + } + if (isPrecededByVersionCheckExit(statement, api, myContext)) { + return super.visitForEachExpression(statement); + } + + Location location = IntellijLintUtils.getUastLocation(myContext.file, value); + String message = String.format("The type of the for loop iterated value is %1$s, which requires API level %2$d" + + " (current min is %3$d)", type.getCanonicalText(), api, minSdk); + + // Add specific check ConcurrentHashMap#keySet and add workaround text. + // This was an unfortunate incompatible API change in Open JDK 8, which is + // not an issue for the Android SDK but is relevant if you're using a + // Java library. + if (value instanceof PsiMethodCallExpression) { + PsiMethodCallExpression valueCall = (PsiMethodCallExpression)value; + if ("keySet".equals(valueCall.getMethodExpression().getReferenceName())) { + PsiMethod keySet = valueCall.resolveMethod(); + if (keySet != null && keySet.getContainingClass() != null && + "java.util.concurrent.ConcurrentHashMap".equals(keySet.getContainingClass().getQualifiedName())) { + message += "; to work around this, add an explicit cast to (Map) before the `keySet` call."; + } + } + } + myContext.report(UNSUPPORTED, location, message); + } + } + + return super.visitForEachExpression(statement); + } + + @Override + public boolean visitCallExpression(@NotNull UCallExpression node) { + checkMethodCallExpression(node); + return super.visitCallExpression(node); + } + + private void checkMethodCallExpression(@NotNull UCallExpression expression) { + super.visitCallExpression(expression); + + if (!myCheckAccess) { + return; + } + + PsiMethod method = expression.resolve(); + if (method != null) { + PsiClass containingClass = method.getContainingClass(); + if (containingClass == null) { + return; + } + + PsiParameterList parameterList = method.getParameterList(); + if (parameterList.getParametersCount() > 0) { + PsiParameter[] parameters = parameterList.getParameters(); + + List arguments = expression.getValueArguments(); + for (int i = 0; i < parameters.length; i++) { + PsiType parameterType = parameters[i].getType(); + if (parameterType instanceof PsiClassType) { + if (i >= arguments.size()) { + // We can end up with more arguments than parameters when + // there is a varargs call. + break; + } + UExpression argument = arguments.get(i); + PsiType argumentType = argument.getExpressionType(); + if (argumentType == null || parameterType.equals(argumentType) || !(argumentType instanceof PsiClassType)) { + continue; + } + checkCast(argument, (PsiClassType)argumentType, (PsiClassType)parameterType); + } + } + } + + 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 (UastExpressionUtils.isMethodCall(expression)) { + UExpression qualifier = expression.getReceiver(); + if (qualifier != null && !(qualifier instanceof UThisExpression) && !(qualifier instanceof USuperExpression)) { + PsiType type = qualifier.getExpressionType(); + 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 { + // For example, for Bundle#getString(String,String) the API level is 12, whereas for + // BaseBundle#getString(String,String) the API level is 21. If the code specified a Bundle instead of + // a BaseBundle, reported the Bundle level in the error message instead. + if (specificApi < api) { + api = specificApi; + fqcn = expressionOwner.replace('/', '.'); + } + api = Math.min(specificApi, api); + } + } + } + } else { + // Unqualified call; need to search in our super hierarchy + PsiClass cls = UastUtils.getContainingClass(expression); + + //noinspection ConstantConditions + if (qualifier instanceof UThisExpression || qualifier instanceof USuperExpression) { + UInstanceExpression pte = (UInstanceExpression) qualifier; + PsiElement resolved = pte.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 + boolean found = false; + PsiClassType anonymousBaseType = ((PsiAnonymousClass)cls).getBaseClassType(); + PsiClass anonymousBase = anonymousBaseType.resolve(); + if (anonymousBase != null && anonymousBase.isInheritor(containingClass, true)) { + cls = anonymousBase; + found = true; + } else { + PsiClass surroundingBaseType = PsiTreeUtil.getParentOfType(cls, PsiClass.class, true); + if (surroundingBaseType != null && surroundingBaseType.isInheritor(containingClass, true)) { + cls = surroundingBaseType; + found = true; + } + } + if (!found) { + 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 { + if (specificApi < api) { + api = specificApi; + fqcn = expressionOwner.replace('/', '.'); + } + api = Math.min(specificApi, api); + 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 (UastExpressionUtils.isMethodCall(expression)) { + if (expression.getReceiver() instanceof USuperExpression) { + PsiMethod containingMethod = UastUtils.getContainingMethod(expression); + 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; + } + } + } + + UElement locationNode = expression.getMethodIdentifier(); + if (locationNode == null) { + locationNode = expression; + } + Location location = IntellijLintUtils.getUastLocation(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); + } + } + } +} 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 934104f1fe8..c3ae8d19029 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 @@ -68,6 +68,11 @@ 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 == ViewTypeDetector.class) { + issue.setImplementation(IntellijViewTypeDetector.IMPLEMENTATION); + } if (detectorClass == SupportAnnotationDetector.class) { // Handled by the ResourceTypeInspection continue; diff --git a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintUtils.java b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintUtils.java index 398e834a9bf..724e5c7b1ef 100644 --- a/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintUtils.java +++ b/plugins/lint/lint-idea/src/org/jetbrains/android/inspections/klint/IntellijLintUtils.java @@ -39,6 +39,10 @@ import com.intellij.psi.util.TypeConversionUtil; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.uast.*; +import org.jetbrains.uast.psi.PsiElementBacked; +import org.jetbrains.uast.psi.UElementWithLocation; +import org.jetbrains.uast.util.UastExpressionUtils; import java.io.File; import java.util.ArrayList; @@ -88,6 +92,51 @@ public class IntellijLintUtils { return Location.create(file, start, end); } + /** + * Gets the location of the given element + * + * @param file the file containing the location + * @param element the element to look up the location for + * @return the location of the given element + */ + @NonNull + public static Location getUastLocation(@NonNull File file, @NonNull UElement element) { + //noinspection ConstantConditions + PsiFile containingPsiFile = UastUtils.getContainingFile(element).getPsi(); + assert containingPsiFile.getVirtualFile() == null + || FileUtil.filesEqual(VfsUtilCore.virtualToIoFile(containingPsiFile.getVirtualFile()), file); + + if (element instanceof UClass) { + // Point to the name rather than the beginning of the javadoc + UClass clz = (UClass)element; + UElement nameIdentifier = clz.getUastAnchor(); + if (nameIdentifier != null) { + element = nameIdentifier; + } + } + + TextRange textRange = null; + if (element instanceof PsiElementBacked) { + PsiElement psi = ((PsiElementBacked) element).getPsi(); + if (psi != null) { + textRange = psi.getTextRange(); + } + } else if (element instanceof UElementWithLocation) { + UElementWithLocation elementWithLocation = (UElementWithLocation) element; + textRange = new TextRange( + elementWithLocation.getStartOffset(), + elementWithLocation.getEndOffset()); + } + + if (textRange == null) { + return Location.NONE; + } + + Position start = new DefaultPosition(-1, -1, textRange.getStartOffset()); + Position end = new DefaultPosition(-1, -1, textRange.getEndOffset()); + return Location.create(file, start, end); + } + /** * Returns the {@link PsiFile} associated with a given lint {@link Context} * @@ -169,6 +218,58 @@ public class IntellijLintUtils { return false; } + /** + * Returns true if the given issue is suppressed at the given element within the given file + * + * @param element the element to check + * @param file the file containing the element + * @param issue the issue to check + * @return true if the given issue is suppressed + */ + public static boolean isSuppressed(@NonNull UElement element, @NonNull UFile file, @NonNull Issue issue) { + // Search upwards for suppress lint and suppress warnings annotations + //noinspection ConstantConditions + while (element != null && element != file) { // otherwise it will keep going into directories! + if (element instanceof UAnnotated) { + UAnnotated annotated = (UAnnotated)element; + for (UAnnotation annotation : annotated.getAnnotations()) { + String fqcn = annotation.getQualifiedName(); + if (fqcn != null && (fqcn.equals(SUPPRESS_LINT_FQCN) || fqcn.equals(SUPPRESS_WARNINGS_FQCN))) { + List parameterList = annotation.getAttributeValues(); + for (UNamedExpression pair : parameterList) { + UExpression v = pair.getExpression(); + if (v instanceof ULiteralExpression) { + ULiteralExpression literal = (ULiteralExpression)v; + Object value = literal.getValue(); + if (value instanceof String) { + if (isSuppressed(issue, (String) value)) { + return true; + } + } + } else if (UastExpressionUtils.isArrayInitializer(v)) { + UCallExpression mv = (UCallExpression)v; + for (UExpression mmv : mv.getValueArguments()) { + if (mmv instanceof ULiteralExpression) { + ULiteralExpression literal = (ULiteralExpression) mmv; + Object value = literal.getValue(); + if (value instanceof String) { + if (isSuppressed(issue, (String) value)) { + return true; + } + } + } + } + } + } + } + } + } + element = element.getContainingElement(); + } + + return false; + } + /** * Returns true if the given issue is suppressed by the given suppress string; this * is typically the same as the issue id, but is allowed to not match case sensitively,