Lint: Use IntellijApiDetector in IDE
This commit is contained in:
committed by
Yan Zhulanow
parent
6ff29f473b
commit
65cea7e92c
@@ -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);
|
||||
|
||||
+135
-173
@@ -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<UNamedExpression> 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<UVariable> 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<Integer>
|
||||
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<Integer>
|
||||
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<UExpression> 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<UAnnotation> 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<PsiElement> mFields;
|
||||
private final List<UExpression> mAllowedValues;
|
||||
private final List<Object> mFields;
|
||||
private final List<Integer> mSeenValues;
|
||||
|
||||
private boolean mReported = false;
|
||||
|
||||
private SwitchChecker(USwitchExpression switchExpression,
|
||||
PsiAnnotationMemberValue[] allowedValues) {
|
||||
List<UExpression> 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<PsiElement> iterator = mFields.listIterator();
|
||||
ListIterator<Object> 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<String> list = computeFieldNames(mSwitchExpression,
|
||||
Arrays.asList(mAllowedValues));
|
||||
List<String> 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<PsiElement> iterator = mFields.listIterator();
|
||||
ListIterator<Object> 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. <p> 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<String> 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;
|
||||
}
|
||||
|
||||
@@ -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<UAnnotation> 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<UAnnotation> 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,
|
||||
|
||||
@@ -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<UNamedExpression> 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()
|
||||
|
||||
+37
-37
@@ -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<String> result = Lists.newArrayListWithCapacity(initializers.length);
|
||||
if (attributeValue == null) {
|
||||
return null;
|
||||
}
|
||||
if (UastExpressionUtils.isArrayInitializer(attributeValue.getExpression())) {
|
||||
List<UExpression> initializers =
|
||||
((UCallExpression) attributeValue.getExpression()).getValueArguments();
|
||||
List<String> 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<String> 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<String> 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<PermissionRequirement> permissions;
|
||||
|
||||
public Many(
|
||||
@NonNull PsiAnnotation annotation,
|
||||
@NonNull UAnnotation annotation,
|
||||
IElementType operator,
|
||||
String[] names) {
|
||||
super(annotation);
|
||||
|
||||
+3
-3
@@ -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)) {
|
||||
|
||||
+101
-101
@@ -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<UAnnotation> allMethodAnnotations,
|
||||
@NonNull List<UAnnotation> 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<UAnnotation> 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<UAnnotation> allMethodAnnotations,
|
||||
@NonNull List<UAnnotation> allClassAnnotations) {
|
||||
List<String> 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<UAnnotation> 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<UAnnotation> 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<UAnnotation> 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<UAnnotation> 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<UAnnotation> 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<UAnnotation> 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<UAnnotation> 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<UExpression> 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<UAnnotation> 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<UExpression> 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<UExpression> allowedValues,
|
||||
@NonNull List<UAnnotation> 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<UExpression> 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<UAnnotation> 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<UAnnotation> 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<UExpression> arguments = call.getValueArguments();
|
||||
PsiParameterList parameterList = method.getParameterList();
|
||||
PsiParameter[] parameters = parameterList.getParameters();
|
||||
PsiAnnotation[] annotations = null;
|
||||
List<UAnnotation> 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) {
|
||||
|
||||
+913
@@ -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
|
||||
* <p>
|
||||
* TODO:
|
||||
* <ul>
|
||||
* <li> Port this part from the bytecode based check:
|
||||
* if (owner.equals("java/text/SimpleDateFormat")) {
|
||||
* checkSimpleDateFormat(context, method, node, minSdk);
|
||||
* }
|
||||
* </li>
|
||||
* <li>Compare to the bytecode based results</li>
|
||||
* </ul>
|
||||
*/
|
||||
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<Class<? extends UElement>> getApplicableUastTypes() {
|
||||
return Collections.<Class<? extends UElement>>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<UClass> 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<PsiResourceListElement> 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<UExpression> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
@@ -68,6 +68,11 @@ public class IntellijLintIssueRegistry extends BuiltinIssueRegistry {
|
||||
Implementation implementation = issue.getImplementation();
|
||||
EnumSet<Scope> scope = implementation.getScope();
|
||||
Class<? extends Detector> 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;
|
||||
|
||||
+101
@@ -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<UNamedExpression> 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,
|
||||
|
||||
Reference in New Issue
Block a user