Lint: Android Lint diagnostics fixes
This commit is contained in:
committed by
Yan Zhulanow
parent
c2ddd943f9
commit
2be9a083ad
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 com.android.tools.klint.client.api;
|
||||
|
||||
import com.android.annotations.Nullable;
|
||||
import com.intellij.psi.PsiElement;
|
||||
|
||||
public interface ExternalReferenceExpression {
|
||||
@Nullable
|
||||
PsiElement resolve(PsiElement context);
|
||||
}
|
||||
@@ -248,8 +248,7 @@ public abstract class JavaEvaluator {
|
||||
public abstract PsiClassType getClassType(@Nullable PsiClass psiClass);
|
||||
|
||||
@NonNull
|
||||
public abstract PsiAnnotation[] getAllAnnotations(@NonNull PsiModifierListOwner owner,
|
||||
boolean inHierarchy);
|
||||
public abstract PsiAnnotation[] getAllAnnotations(@NonNull PsiModifierListOwner owner);
|
||||
|
||||
@Nullable
|
||||
public abstract PsiAnnotation findAnnotationInHierarchy(
|
||||
|
||||
@@ -504,6 +504,17 @@ public class UElementVisitor {
|
||||
}
|
||||
|
||||
private class DispatchPsiVisitor extends AbstractUastVisitor {
|
||||
|
||||
@Override
|
||||
public boolean visitAnnotation(UAnnotation node) {
|
||||
List<VisitingDetector> list = mNodePsiTypeDetectors.get(UAnnotation.class);
|
||||
if (list != null) {
|
||||
for (VisitingDetector v : list) {
|
||||
v.getVisitor().visitAnnotation(node);
|
||||
}
|
||||
}
|
||||
return super.visitAnnotation(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visitCatchClause(UCatchClause node) {
|
||||
|
||||
@@ -34,15 +34,7 @@ import com.intellij.psi.PsiParameter;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.PsiVariable;
|
||||
|
||||
import org.jetbrains.uast.UElement;
|
||||
import org.jetbrains.uast.UExpression;
|
||||
import org.jetbrains.uast.ULiteralExpression;
|
||||
import org.jetbrains.uast.UMethod;
|
||||
import org.jetbrains.uast.UQualifiedReferenceExpression;
|
||||
import org.jetbrains.uast.UResolvable;
|
||||
import org.jetbrains.uast.USimpleNameReferenceExpression;
|
||||
import org.jetbrains.uast.UVariable;
|
||||
import org.jetbrains.uast.UastUtils;
|
||||
import org.jetbrains.uast.*;
|
||||
import org.jetbrains.uast.expressions.UReferenceExpression;
|
||||
import org.jetbrains.uast.java.JavaAbstractUExpression;
|
||||
import org.jetbrains.uast.java.JavaUVariableDeclarationsExpression;
|
||||
@@ -51,6 +43,16 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class UastLintUtils {
|
||||
@Nullable
|
||||
public static PsiElement resolve(ExternalReferenceExpression expression, UElement context) {
|
||||
UDeclaration declaration = UastUtils.getParentOfType(context, UDeclaration.class);
|
||||
if (declaration == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return expression.resolve(declaration.getPsi());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getClassName(PsiClassType type) {
|
||||
PsiClass psiClass = type.resolve();
|
||||
@@ -88,10 +90,8 @@ public class UastLintUtils {
|
||||
(variable instanceof PsiLocalVariable || variable instanceof PsiParameter)) {
|
||||
UMethod containingFunction = UastUtils.getContainingUMethod(call);
|
||||
if (containingFunction != null) {
|
||||
ConstantEvaluator.LastAssignmentFinder
|
||||
finder = new ConstantEvaluator.LastAssignmentFinder(
|
||||
variable, call, context, null,
|
||||
(variable instanceof PsiParameter) ? 1 : 0);
|
||||
ConstantEvaluator.LastAssignmentFinder finder =
|
||||
new ConstantEvaluator.LastAssignmentFinder(variable, call, context, null, -1);
|
||||
containingFunction.accept(finder);
|
||||
lastAssignment = finder.getLastAssignment();
|
||||
}
|
||||
|
||||
+24
-41
@@ -63,21 +63,7 @@ import com.intellij.psi.PsiVariable;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
|
||||
import org.jetbrains.uast.UBinaryExpression;
|
||||
import org.jetbrains.uast.UBinaryExpressionWithType;
|
||||
import org.jetbrains.uast.UBlockExpression;
|
||||
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.UPrefixExpression;
|
||||
import org.jetbrains.uast.UResolvable;
|
||||
import org.jetbrains.uast.UVariable;
|
||||
import org.jetbrains.uast.UastBinaryOperator;
|
||||
import org.jetbrains.uast.UastContext;
|
||||
import org.jetbrains.uast.UastPrefixOperator;
|
||||
import org.jetbrains.uast.*;
|
||||
import org.jetbrains.uast.expressions.UReferenceExpression;
|
||||
import org.jetbrains.uast.util.UastExpressionUtils;
|
||||
import org.jetbrains.uast.visitor.AbstractUastVisitor;
|
||||
@@ -1073,7 +1059,6 @@ public class ConstantEvaluator {
|
||||
private final PsiVariable mVariable;
|
||||
private final UElement mEndAt;
|
||||
|
||||
private final JavaContext mContext;
|
||||
private final ConstantEvaluator mConstantEvaluator;
|
||||
|
||||
private boolean mDone = false;
|
||||
@@ -1092,7 +1077,6 @@ public class ConstantEvaluator {
|
||||
mEndAt = endAt;
|
||||
UExpression initializer = context.getUastContext().getInitializerBody(variable);
|
||||
mLastAssignment = initializer;
|
||||
mContext = context;
|
||||
mConstantEvaluator = constantEvaluator;
|
||||
if (initializer != null && constantEvaluator != null) {
|
||||
mCurrentValue = constantEvaluator.evaluate(initializer);
|
||||
@@ -1112,7 +1096,7 @@ public class ConstantEvaluator {
|
||||
|
||||
@Override
|
||||
public boolean visitElement(UElement node) {
|
||||
if (!(node instanceof UBlockExpression)) {
|
||||
if (elementHasLevel(node)) {
|
||||
mCurrentLevel++;
|
||||
}
|
||||
if (node.equals(mEndAt)) {
|
||||
@@ -1123,23 +1107,23 @@ public class ConstantEvaluator {
|
||||
|
||||
@Override
|
||||
public boolean visitVariable(UVariable node) {
|
||||
if (mVariableLevel < 0 && node.equals(mVariable)) {
|
||||
if (mVariableLevel < 0 && node.getPsi().isEquivalentTo(mVariable)) {
|
||||
mVariableLevel = mCurrentLevel;
|
||||
}
|
||||
|
||||
|
||||
return super.visitVariable(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterVisitBinaryExpression(UBinaryExpression node) {
|
||||
if (!mDone
|
||||
&& node.getOperator() instanceof UastBinaryOperator.AssignOperator
|
||||
&& mVariableLevel >= 0) {
|
||||
if (!mDone
|
||||
&& node.getOperator() instanceof UastBinaryOperator.AssignOperator
|
||||
&& mVariableLevel >= 0) {
|
||||
UExpression leftOperand = node.getLeftOperand();
|
||||
UastBinaryOperator operator = node.getOperator();
|
||||
|
||||
if (!(operator instanceof UastBinaryOperator.AssignOperator)
|
||||
|| !(leftOperand instanceof UResolvable)) {
|
||||
|| !(leftOperand instanceof UResolvable)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1148,39 +1132,38 @@ public class ConstantEvaluator {
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop search if we see an assignment inside some conditional or loop statement.
|
||||
if (mCurrentLevel > mVariableLevel) {
|
||||
// Last assignment is unknown if we see an assignment inside
|
||||
// some conditional or loop statement.
|
||||
if (mCurrentLevel > mVariableLevel + 1) {
|
||||
mLastAssignment = null;
|
||||
mCurrentValue = null;
|
||||
mDone = true;
|
||||
return;
|
||||
}
|
||||
|
||||
UExpression rightOperand = node.getRightOperand();
|
||||
ConstantEvaluator constantEvaluator = mConstantEvaluator;
|
||||
|
||||
Object newExpression = (constantEvaluator != null)
|
||||
? constantEvaluator.evaluate(rightOperand)
|
||||
: null;
|
||||
|
||||
//TODO implement other assign operators
|
||||
if (node.getOperator() == UastBinaryOperator.ASSIGN) {
|
||||
mCurrentValue = newExpression;
|
||||
mLastAssignment = rightOperand;
|
||||
} else {
|
||||
mCurrentValue = newExpression;
|
||||
// Technically wrong, just reflect the old behaviour for now
|
||||
mLastAssignment = rightOperand;
|
||||
}
|
||||
mCurrentValue = (constantEvaluator != null)
|
||||
? constantEvaluator.evaluate(rightOperand)
|
||||
: null;
|
||||
mLastAssignment = rightOperand;
|
||||
}
|
||||
|
||||
super.afterVisitBinaryExpression(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterVisitElement(UElement node) {
|
||||
if (!(node instanceof UBlockExpression)) {
|
||||
if (elementHasLevel(node)) {
|
||||
mCurrentLevel--;
|
||||
}
|
||||
super.afterVisitElement(node);
|
||||
}
|
||||
|
||||
private static boolean elementHasLevel(UElement node) {
|
||||
return !(node instanceof UBlockExpression
|
||||
|| node instanceof UVariableDeclarationsExpression);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -478,7 +478,12 @@ public class JavaContext extends Context {
|
||||
}
|
||||
|
||||
public boolean isSuppressedWithComment(@NonNull UElement scope, @NonNull Issue issue) {
|
||||
return false;
|
||||
if (!(scope instanceof PsiElementBacked)) {
|
||||
return false;
|
||||
}
|
||||
PsiElement psi = ((PsiElementBacked) scope).getPsi();
|
||||
return psi != null && isSuppressedWithComment(psi, issue);
|
||||
|
||||
}
|
||||
|
||||
public boolean isSuppressedWithComment(@NonNull PsiElement scope, @NonNull Issue issue) {
|
||||
|
||||
+1
-1
@@ -589,7 +589,7 @@ public class ResourceEvaluator {
|
||||
if (mEvaluator == null) {
|
||||
return null;
|
||||
}
|
||||
for (PsiAnnotation annotation : mEvaluator.getAllAnnotations(owner, true)) {
|
||||
for (PsiAnnotation annotation : mEvaluator.getAllAnnotations(owner)) {
|
||||
String signature = annotation.getQualifiedName();
|
||||
if (signature == null) {
|
||||
continue;
|
||||
|
||||
+320
-275
@@ -53,11 +53,13 @@ import static com.android.tools.klint.detector.api.ResourceEvaluator.RES_SUFFIX;
|
||||
|
||||
import com.android.annotations.NonNull;
|
||||
import com.android.annotations.Nullable;
|
||||
import com.android.tools.klint.client.api.ExternalReferenceExpression;
|
||||
import com.android.tools.klint.client.api.IssueRegistry;
|
||||
import com.android.tools.klint.client.api.JavaEvaluator;
|
||||
import com.android.tools.klint.client.api.UastLintUtils;
|
||||
import com.android.tools.klint.detector.api.Category;
|
||||
import com.android.tools.klint.detector.api.ConstantEvaluator;
|
||||
import com.android.tools.klint.detector.api.Detector;
|
||||
import com.android.tools.klint.detector.api.Detector.JavaPsiScanner;
|
||||
import com.android.tools.klint.detector.api.Implementation;
|
||||
import com.android.tools.klint.detector.api.Issue;
|
||||
import com.android.tools.klint.detector.api.JavaContext;
|
||||
@@ -70,42 +72,47 @@ import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.intellij.psi.JavaElementVisitor;
|
||||
import com.intellij.psi.PsiAnnotation;
|
||||
import com.intellij.psi.PsiAnnotationMemberValue;
|
||||
import com.intellij.psi.PsiAnnotationOwner;
|
||||
import com.intellij.psi.PsiArrayInitializerMemberValue;
|
||||
import com.intellij.psi.PsiArrayType;
|
||||
import com.intellij.psi.PsiAssignmentExpression;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiClassType;
|
||||
import com.intellij.psi.PsiCodeBlock;
|
||||
import com.intellij.psi.PsiConditionalExpression;
|
||||
import com.intellij.psi.PsiDeclarationStatement;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
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.PsiMethod;
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import com.intellij.psi.PsiModifierList;
|
||||
import com.intellij.psi.PsiModifierListOwner;
|
||||
import com.intellij.psi.PsiNameValuePair;
|
||||
import com.intellij.psi.PsiParameter;
|
||||
import com.intellij.psi.PsiParenthesizedExpression;
|
||||
import com.intellij.psi.PsiReference;
|
||||
import com.intellij.psi.PsiReferenceExpression;
|
||||
import com.intellij.psi.PsiStatement;
|
||||
import com.intellij.psi.PsiSwitchLabelStatement;
|
||||
import com.intellij.psi.PsiSwitchStatement;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.PsiTypeCastExpression;
|
||||
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.expressions.UReferenceExpression;
|
||||
import org.jetbrains.uast.java.JavaUTypeCastExpression;
|
||||
import org.jetbrains.uast.visitor.AbstractUastVisitor;
|
||||
import org.jetbrains.uast.visitor.UastVisitor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
@@ -117,11 +124,11 @@ import java.util.Set;
|
||||
/**
|
||||
* Checks annotations to make sure they are valid
|
||||
*/
|
||||
public class AnnotationDetector extends Detector implements JavaPsiScanner {
|
||||
public class AnnotationDetector extends Detector implements Detector.UastScanner {
|
||||
|
||||
public static final Implementation IMPLEMENTATION = new Implementation(
|
||||
AnnotationDetector.class,
|
||||
Scope.JAVA_FILE_SCOPE);
|
||||
AnnotationDetector.class,
|
||||
Scope.JAVA_FILE_SCOPE);
|
||||
|
||||
/** Placing SuppressLint on a local variable doesn't work for class-file based checks */
|
||||
public static final Issue INSIDE_METHOD = Issue.create(
|
||||
@@ -217,38 +224,41 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
|
||||
*/
|
||||
private Set<PsiElement> mWarnedFlags;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<Class<? extends PsiElement>> getApplicablePsiTypes() {
|
||||
List<Class<? extends PsiElement>> types = new ArrayList<Class<? extends PsiElement>>(2);
|
||||
types.add(PsiAnnotation.class);
|
||||
types.add(PsiSwitchStatement.class);
|
||||
public List<Class<? extends UElement>> getApplicableUastTypes() {
|
||||
List<Class<? extends UElement>> types = new ArrayList<Class<? extends UElement>>(2);
|
||||
types.add(UAnnotation.class);
|
||||
types.add(USwitchExpression.class);
|
||||
return types;
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public JavaElementVisitor createPsiVisitor(@NonNull JavaContext context) {
|
||||
public UastVisitor createUastVisitor(@NonNull JavaContext context) {
|
||||
return new AnnotationChecker(context);
|
||||
}
|
||||
|
||||
private class AnnotationChecker extends JavaElementVisitor {
|
||||
private class AnnotationChecker extends AbstractUastVisitor {
|
||||
|
||||
private final JavaContext mContext;
|
||||
|
||||
public AnnotationChecker(JavaContext context) {
|
||||
private AnnotationChecker(JavaContext context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitAnnotation(PsiAnnotation annotation) {
|
||||
public boolean visitAnnotation(UAnnotation annotation) {
|
||||
String type = annotation.getQualifiedName();
|
||||
if (type == null || type.startsWith("java.lang.")) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (FQCN_SUPPRESS_LINT.equals(type)) {
|
||||
PsiAnnotationOwner owner = annotation.getOwner();
|
||||
if (owner == null) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
if (owner instanceof PsiModifierList) {
|
||||
PsiElement parent = ((PsiModifierList) owner).getParent();
|
||||
@@ -256,10 +266,10 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
|
||||
if (!(parent instanceof PsiDeclarationStatement
|
||||
|| parent instanceof PsiLocalVariable
|
||||
|| parent instanceof PsiParameter)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
PsiNameValuePair[] attributes = annotation.getParameterList().getAttributes();
|
||||
if (attributes.length == 1) {
|
||||
@@ -280,7 +290,7 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
|
||||
if (v instanceof String) {
|
||||
String id = (String) v;
|
||||
if (!checkSuppressLint(annotation, id)) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,17 +301,17 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
|
||||
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) {
|
||||
&& annotation.getParent().getParent() instanceof PsiMethod) {
|
||||
PsiMethod method = (PsiMethod) annotation.getParent().getParent();
|
||||
if (!method.isConstructor()
|
||||
&& PsiType.VOID.equals(method.getReturnType())) {
|
||||
mContext.report(ANNOTATION_USAGE, annotation,
|
||||
mContext.getLocation(annotation),
|
||||
"@CheckResult should not be specified on `void` methods");
|
||||
&& PsiType.VOID.equals(method.getReturnType())) {
|
||||
mContext.report(ANNOTATION_USAGE, annotation.getPsi(),
|
||||
mContext.getLocation(annotation.getPsi()),
|
||||
"@CheckResult should not be specified on `void` methods");
|
||||
}
|
||||
}
|
||||
} else if (INT_RANGE_ANNOTATION.equals(type)
|
||||
|| FLOAT_RANGE_ANNOTATION.equals(type)) {
|
||||
|| FLOAT_RANGE_ANNOTATION.equals(type)) {
|
||||
// Check that the annotated element's type is int or long.
|
||||
// Also make sure that from <= to.
|
||||
boolean invalid;
|
||||
@@ -315,14 +325,14 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
|
||||
checkTargetType(annotation, TYPE_FLOAT, TYPE_DOUBLE, true);
|
||||
|
||||
double from = getDoubleAttribute(annotation, ATTR_FROM,
|
||||
Double.NEGATIVE_INFINITY);
|
||||
Double.NEGATIVE_INFINITY);
|
||||
double to = getDoubleAttribute(annotation, ATTR_TO,
|
||||
Double.POSITIVE_INFINITY);
|
||||
Double.POSITIVE_INFINITY);
|
||||
invalid = from > to;
|
||||
}
|
||||
if (invalid) {
|
||||
mContext.report(ANNOTATION_USAGE, annotation, mContext.getLocation(annotation),
|
||||
"Invalid range: the `from` attribute must be less than "
|
||||
mContext.report(ANNOTATION_USAGE, annotation.getPsi(), mContext.getLocation(annotation.getPsi()),
|
||||
"Invalid range: the `from` attribute must be less than "
|
||||
+ "the `to` attribute");
|
||||
}
|
||||
} else if (SIZE_ANNOTATION.equals(type)) {
|
||||
@@ -335,16 +345,16 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
|
||||
long max = getLongAttribute(annotation, ATTR_MAX, Long.MAX_VALUE);
|
||||
long multiple = getLongAttribute(annotation, ATTR_MULTIPLE, 1);
|
||||
if (min > max) {
|
||||
mContext.report(ANNOTATION_USAGE, annotation, mContext.getLocation(annotation),
|
||||
"Invalid size range: the `min` attribute must be less than "
|
||||
mContext.report(ANNOTATION_USAGE, annotation.getPsi(), mContext.getLocation(annotation.getPsi()),
|
||||
"Invalid size range: the `min` attribute must be less than "
|
||||
+ "the `max` attribute");
|
||||
} else if (multiple < 1) {
|
||||
mContext.report(ANNOTATION_USAGE, annotation, mContext.getLocation(annotation),
|
||||
"The size multiple must be at least 1");
|
||||
mContext.report(ANNOTATION_USAGE, annotation.getPsi(), mContext.getLocation(annotation.getPsi()),
|
||||
"The size multiple must be at least 1");
|
||||
|
||||
} else if (exact < 0 && exact != unset || min < 0 && min != Long.MIN_VALUE) {
|
||||
mContext.report(ANNOTATION_USAGE, annotation, mContext.getLocation(annotation),
|
||||
"The size can't be negative");
|
||||
mContext.report(ANNOTATION_USAGE, annotation.getPsi(), mContext.getLocation(annotation.getPsi()),
|
||||
"The size can't be negative");
|
||||
}
|
||||
} else if (COLOR_INT_ANNOTATION.equals(type) || (PX_ANNOTATION.equals(type))) {
|
||||
// Check that ColorInt applies to the right type
|
||||
@@ -353,8 +363,8 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
|
||||
// Make sure IntDef constants are unique
|
||||
ensureUniqueValues(annotation);
|
||||
} else if (PERMISSION_ANNOTATION.equals(type) ||
|
||||
PERMISSION_ANNOTATION_READ.equals(type) ||
|
||||
PERMISSION_ANNOTATION_WRITE.equals(type)) {
|
||||
PERMISSION_ANNOTATION_READ.equals(type) ||
|
||||
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
|
||||
@@ -378,14 +388,14 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
|
||||
}
|
||||
|
||||
if (set == 0) {
|
||||
mContext.report(ANNOTATION_USAGE, annotation,
|
||||
mContext.getLocation(annotation),
|
||||
"For methods, permission annotation should specify one "
|
||||
mContext.report(ANNOTATION_USAGE, annotation.getPsi(),
|
||||
mContext.getLocation(annotation.getPsi()),
|
||||
"For methods, permission annotation should specify one "
|
||||
+ "of `value`, `anyOf` or `allOf`");
|
||||
} else if (set > 1) {
|
||||
mContext.report(ANNOTATION_USAGE, annotation,
|
||||
mContext.getLocation(annotation),
|
||||
"Only specify one of `value`, `anyOf` or `allOf`");
|
||||
mContext.report(ANNOTATION_USAGE, annotation.getPsi(),
|
||||
mContext.getLocation(annotation.getPsi()),
|
||||
"Only specify one of `value`, `anyOf` or `allOf`");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,6 +424,8 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void checkTargetType(@NonNull PsiAnnotation node, @NonNull String type1,
|
||||
@@ -437,8 +449,8 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
|
||||
} else if (parent instanceof PsiMethod) {
|
||||
PsiMethod method = (PsiMethod) parent;
|
||||
type = method.isConstructor()
|
||||
? mContext.getEvaluator().getClassType(method.getContainingClass())
|
||||
: method.getReturnType();
|
||||
? mContext.getEvaluator().getClassType(method.getContainingClass())
|
||||
: method.getReturnType();
|
||||
} else if (parent instanceof PsiVariable) {
|
||||
// Field or local variable or parameter
|
||||
type = ((PsiVariable)parent).getType();
|
||||
@@ -459,8 +471,7 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
|
||||
if (classType.getParameters().length == 1) {
|
||||
PsiClass resolved = classType.resolve();
|
||||
if (resolved != null &&
|
||||
mContext.getEvaluator().implementsInterface(resolved,
|
||||
"java.util.Collection", false)) {
|
||||
InheritanceUtil.isInheritor(resolved, false, "java.util.Collection")) {
|
||||
type = classType.getParameters()[0];
|
||||
}
|
||||
}
|
||||
@@ -469,10 +480,10 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
|
||||
|
||||
String typeName = type.getCanonicalText();
|
||||
if (!typeName.equals(type1)
|
||||
&& (type2 == null || !typeName.equals(type2))) {
|
||||
&& (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))) {
|
||||
|| type2 != null && typeName.equals(getAutoBoxedType(type2))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -490,14 +501,35 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitSwitchStatement(PsiSwitchStatement statement) {
|
||||
PsiExpression condition = statement.getExpression();
|
||||
if (condition != null && PsiType.INT.equals(condition.getType())) {
|
||||
PsiAnnotation annotation = findIntDef(condition);
|
||||
public boolean visitSwitchExpression(USwitchExpression switchExpression) {
|
||||
UExpression condition = switchExpression.getExpression();
|
||||
if (condition != null && PsiType.INT.equals(condition.getExpressionType())) {
|
||||
PsiAnnotation annotation = findIntDefAnnotation(condition);
|
||||
if (annotation != null) {
|
||||
checkSwitch(statement, annotation);
|
||||
PsiAnnotationMemberValue value =
|
||||
annotation.findDeclaredAttributeValue(ATTR_VALUE);
|
||||
if (value == null) {
|
||||
value = annotation.findDeclaredAttributeValue(null);
|
||||
}
|
||||
|
||||
if (value instanceof PsiArrayInitializerMemberValue) {
|
||||
PsiAnnotationMemberValue[] allowedValues =
|
||||
((PsiArrayInitializerMemberValue)value).getInitializers();
|
||||
switchExpression.accept(new SwitchChecker(switchExpression, allowedValues));
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Integer getConstantValue(@NonNull PsiField intDefConstantRef) {
|
||||
Object constant = intDefConstantRef.computeConstantValue();
|
||||
if (constant instanceof Number) {
|
||||
return ((Number)constant).intValue();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -505,12 +537,13 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
|
||||
* with a given node
|
||||
*/
|
||||
@Nullable
|
||||
private PsiAnnotation findIntDef(@NonNull PsiElement node) {
|
||||
if (node instanceof PsiReferenceExpression) {
|
||||
PsiElement resolved = ((PsiReference) node).resolve();
|
||||
private PsiAnnotation findIntDefAnnotation(@NonNull UExpression expression) {
|
||||
if (expression instanceof UReferenceExpression) {
|
||||
PsiElement resolved = ((UReferenceExpression) expression).resolve();
|
||||
|
||||
if (resolved instanceof PsiModifierListOwner) {
|
||||
PsiAnnotation[] annotations = mContext.getEvaluator().getAllAnnotations(
|
||||
(PsiModifierListOwner)resolved, true);
|
||||
(PsiModifierListOwner)resolved);
|
||||
PsiAnnotation annotation = SupportAnnotationDetector.findIntDef(
|
||||
filterRelevantAnnotations(annotations));
|
||||
if (annotation != null) {
|
||||
@@ -520,217 +553,50 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
|
||||
|
||||
if (resolved instanceof PsiLocalVariable) {
|
||||
PsiLocalVariable variable = (PsiLocalVariable) resolved;
|
||||
PsiStatement statement = PsiTreeUtil.getParentOfType(node, PsiStatement.class,
|
||||
false);
|
||||
if (statement != null) {
|
||||
PsiStatement prev = PsiTreeUtil.getPrevSiblingOfType(statement,
|
||||
PsiStatement.class);
|
||||
String targetName = variable.getName();
|
||||
if (targetName == null) {
|
||||
return null;
|
||||
}
|
||||
while (prev != null) {
|
||||
if (prev instanceof PsiDeclarationStatement) {
|
||||
for (PsiElement element : ((PsiDeclarationStatement) prev)
|
||||
.getDeclaredElements()) {
|
||||
if (variable.equals(element)) {
|
||||
PsiExpression initializer = variable.getInitializer();
|
||||
if (initializer != null) {
|
||||
return findIntDef(initializer);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (prev instanceof PsiExpressionStatement) {
|
||||
PsiExpression expression = ((PsiExpressionStatement) prev)
|
||||
.getExpression();
|
||||
if (expression instanceof PsiAssignmentExpression) {
|
||||
PsiAssignmentExpression assign
|
||||
= (PsiAssignmentExpression) expression;
|
||||
PsiExpression lhs = assign.getLExpression();
|
||||
if (lhs instanceof PsiReferenceExpression) {
|
||||
PsiReferenceExpression reference = (PsiReferenceExpression) lhs;
|
||||
if (targetName.equals(reference.getReferenceName()) &&
|
||||
reference.getQualifier() == null) {
|
||||
PsiExpression rExpression = assign.getRExpression();
|
||||
if (rExpression != null) {
|
||||
return findIntDef(rExpression);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
prev = PsiTreeUtil.getPrevSiblingOfType(prev,
|
||||
PsiStatement.class);
|
||||
}
|
||||
UExpression lastAssignment = UastLintUtils.findLastAssignment(variable,
|
||||
expression, mContext);
|
||||
|
||||
if(lastAssignment != null) {
|
||||
return findIntDefAnnotation(lastAssignment);
|
||||
}
|
||||
|
||||
}
|
||||
} else if (node instanceof PsiMethodCallExpression) {
|
||||
PsiMethod method = ((PsiMethodCallExpression) node).resolveMethod();
|
||||
|
||||
} else if (expression instanceof UCallExpression) {
|
||||
PsiMethod method = ((UCallExpression) expression).resolve();
|
||||
if (method != null) {
|
||||
PsiAnnotation[] annotations = mContext.getEvaluator().getAllAnnotations(method, true);
|
||||
PsiAnnotation[] annotations = mContext.getEvaluator()
|
||||
.getAllAnnotations(method);
|
||||
PsiAnnotation annotation = SupportAnnotationDetector.findIntDef(
|
||||
filterRelevantAnnotations(annotations));
|
||||
if (annotation != null) {
|
||||
return annotation;
|
||||
}
|
||||
}
|
||||
} else if (node instanceof PsiConditionalExpression) {
|
||||
PsiConditionalExpression expression = (PsiConditionalExpression) node;
|
||||
if (expression.getThenExpression() != null) {
|
||||
PsiAnnotation result = findIntDef(expression.getThenExpression());
|
||||
} else if (expression instanceof UIfExpression) {
|
||||
UIfExpression ifExpression = (UIfExpression) expression;
|
||||
if (ifExpression.getThenExpression() != null) {
|
||||
PsiAnnotation result = findIntDefAnnotation(ifExpression.getThenExpression());
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
if (expression.getElseExpression() != null) {
|
||||
PsiAnnotation result = findIntDef(expression.getElseExpression());
|
||||
if (ifExpression.getElseExpression() != null) {
|
||||
PsiAnnotation result = findIntDefAnnotation(ifExpression.getElseExpression());
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} else if (node instanceof PsiTypeCastExpression) {
|
||||
PsiTypeCastExpression cast = (PsiTypeCastExpression) node;
|
||||
if (cast.getOperand() != null) {
|
||||
return findIntDef(cast.getOperand());
|
||||
}
|
||||
} else if (node instanceof PsiParenthesizedExpression) {
|
||||
PsiParenthesizedExpression expression = (PsiParenthesizedExpression) node;
|
||||
if (expression.getExpression() != null) {
|
||||
return findIntDef(expression.getExpression());
|
||||
}
|
||||
} else if (expression instanceof JavaUTypeCastExpression) {
|
||||
return findIntDefAnnotation(((JavaUTypeCastExpression)expression).getOperand());
|
||||
|
||||
} else if (expression instanceof UParenthesizedExpression) {
|
||||
return findIntDefAnnotation(((UParenthesizedExpression) expression).getExpression());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void checkSwitch(@NonNull PsiSwitchStatement node, @NonNull PsiAnnotation annotation) {
|
||||
PsiCodeBlock block = node.getBody();
|
||||
if (block == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PsiAnnotationMemberValue value = annotation.findDeclaredAttributeValue(ATTR_VALUE);
|
||||
if (value == null) {
|
||||
value = annotation.findDeclaredAttributeValue(null);
|
||||
}
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(value instanceof PsiArrayInitializerMemberValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
PsiArrayInitializerMemberValue array = (PsiArrayInitializerMemberValue)value;
|
||||
PsiAnnotationMemberValue[] allowedValues = array.getInitializers();
|
||||
|
||||
List<PsiElement> fields = Lists.newArrayListWithCapacity(allowedValues.length);
|
||||
for (PsiAnnotationMemberValue allowedValue : allowedValues) {
|
||||
if (allowedValue instanceof PsiReferenceExpression) {
|
||||
PsiElement resolved = ((PsiReferenceExpression) allowedValue).resolve();
|
||||
if (resolved != null) {
|
||||
fields.add(resolved);
|
||||
}
|
||||
} else if (allowedValue instanceof PsiLiteral) {
|
||||
fields.add(allowedValue);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Empty switch: arguably we could skip these (since the IDE already warns about
|
||||
// empty switches) but it's useful since the quickfix will kick in and offer all
|
||||
// the missing ones when you're editing.
|
||||
// if (block.getStatements().length == 0) { return; }
|
||||
|
||||
for (PsiStatement statement : block.getStatements()) {
|
||||
if (statement instanceof PsiSwitchLabelStatement) {
|
||||
PsiSwitchLabelStatement caseStatement = (PsiSwitchLabelStatement) statement;
|
||||
PsiExpression expression = caseStatement.getCaseValue();
|
||||
if (expression instanceof PsiLiteral) {
|
||||
// Report warnings if you specify hardcoded constants.
|
||||
// It's the wrong thing to do.
|
||||
List<String> list = computeFieldNames(node, Arrays.asList(allowedValues));
|
||||
// Keep error message in sync with {@link #getMissingCases}
|
||||
String message = "Don't use a constant here; expected one of: " + Joiner
|
||||
.on(", ").join(list);
|
||||
mContext.report(SWITCH_TYPE_DEF, expression,
|
||||
mContext.getLocation(expression), message);
|
||||
return; // Don't look for other missing typedef constants since you might
|
||||
// have aliased with value
|
||||
} else if (expression instanceof PsiReferenceExpression) { // default case can have null expression
|
||||
PsiElement resolved = ((PsiReferenceExpression) expression).resolve();
|
||||
if (resolved == null) {
|
||||
// If there are compilation issues (e.g. user is editing code) we
|
||||
// can't be certain, so don't flag anything.
|
||||
return;
|
||||
}
|
||||
if (resolved instanceof PsiField) {
|
||||
// We can't just do
|
||||
// fields.remove(resolved);
|
||||
// since the fields list contains instances of potentially
|
||||
// different types with different hash codes (due to the
|
||||
// external annotations, which are not of the same type as
|
||||
// for example the ECJ based ones.
|
||||
//
|
||||
// The equals method on external field class deliberately handles
|
||||
// this (but it can't make its hash code match what
|
||||
// the ECJ fields do, which is tied to the ECJ binding hash code.)
|
||||
// So instead, manually check for equals. These lists tend to
|
||||
// be very short anyway.
|
||||
boolean found = false;
|
||||
ListIterator<PsiElement> iterator = fields.listIterator();
|
||||
while (iterator.hasNext()) {
|
||||
PsiElement field = iterator.next();
|
||||
if (field.isEquivalentTo(resolved)) {
|
||||
iterator.remove();
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// Look for local alias
|
||||
PsiExpression initializer = ((PsiField) resolved).getInitializer();
|
||||
if (initializer instanceof PsiReferenceExpression) {
|
||||
resolved = ((PsiReferenceExpression) expression).resolve();
|
||||
if (resolved instanceof PsiField) {
|
||||
iterator = fields.listIterator();
|
||||
while (iterator.hasNext()) {
|
||||
PsiElement field = iterator.next();
|
||||
if (field.equals(initializer)) {
|
||||
iterator.remove();
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
List<String> list = computeFieldNames(node, Arrays.asList(allowedValues));
|
||||
// Keep error message in sync with {@link #getMissingCases}
|
||||
String message = "Unexpected constant; expected one of: " + Joiner
|
||||
.on(", ").join(list);
|
||||
Location location = mContext.getNameLocation(expression);
|
||||
mContext.report(SWITCH_TYPE_DEF, expression, location, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!fields.isEmpty()) {
|
||||
List<String> list = computeFieldNames(node, fields);
|
||||
// Keep error message in sync with {@link #getMissingCases}
|
||||
String message = "Switch statement on an `int` with known associated constant "
|
||||
+ "missing case " + Joiner.on(", ").join(list);
|
||||
Location location = mContext.getNameLocation(node);
|
||||
mContext.report(SWITCH_TYPE_DEF, node, location, message);
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureUniqueValues(@NonNull PsiAnnotation node) {
|
||||
PsiAnnotationMemberValue value = node.findAttributeValue(ATTR_VALUE);
|
||||
if (value == null) {
|
||||
@@ -770,8 +636,8 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
|
||||
PsiElement prevConstant = initializers[prevIndex];
|
||||
message = String.format(
|
||||
"Constants `%1$s` and `%2$s` specify the same exact "
|
||||
+ "value (%3$s); this is usually a cut & paste or "
|
||||
+ "merge error",
|
||||
+ "value (%3$s); this is usually a cut & paste or "
|
||||
+ "merge error",
|
||||
expression.getText(), prevConstant.getText(),
|
||||
repeatedValue.toString());
|
||||
location = mContext.getLocation(expression);
|
||||
@@ -838,27 +704,212 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
|
||||
// only on field references, and class file analysis on the rest, so we allow
|
||||
// annotations outside of methods only on fields
|
||||
if (issue != null && !issue.getImplementation().getScope().contains(Scope.JAVA_FILE)
|
||||
|| issue == ApiDetector.UNSUPPORTED) {
|
||||
|| 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(
|
||||
"The `@SuppressLint` annotation cannot be used on a local " +
|
||||
"variable with the lint check '%1$s': move out to the " +
|
||||
"surrounding method", id));
|
||||
"The `@SuppressLint` annotation cannot be used on a local " +
|
||||
"variable with the lint check '%1$s': move out to the " +
|
||||
"surrounding method", id));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private class SwitchChecker extends AbstractUastVisitor {
|
||||
|
||||
private final USwitchExpression mSwitchExpression;
|
||||
private final PsiAnnotationMemberValue[] mAllowedValues;
|
||||
private final List<PsiElement> mFields;
|
||||
private final List<Integer> mSeenValues;
|
||||
|
||||
private boolean mReported = false;
|
||||
|
||||
private SwitchChecker(USwitchExpression switchExpression,
|
||||
PsiAnnotationMemberValue[] allowedValues) {
|
||||
mSwitchExpression = switchExpression;
|
||||
mAllowedValues = allowedValues;
|
||||
|
||||
mFields = Lists.newArrayListWithCapacity(allowedValues.length);
|
||||
for (PsiAnnotationMemberValue allowedValue : allowedValues) {
|
||||
if (allowedValue instanceof ExternalReferenceExpression) {
|
||||
ExternalReferenceExpression externalRef =
|
||||
(ExternalReferenceExpression) allowedValue;
|
||||
|
||||
PsiElement resolved = UastLintUtils.resolve(externalRef, switchExpression);
|
||||
|
||||
if (resolved instanceof PsiField) {
|
||||
mFields.add(resolved);
|
||||
}
|
||||
} else if (allowedValue instanceof PsiReferenceExpression) {
|
||||
PsiElement resolved = ((PsiReferenceExpression) allowedValue).resolve();
|
||||
if (resolved != null) {
|
||||
mFields.add(resolved);
|
||||
}
|
||||
} else if (allowedValue instanceof PsiLiteral) {
|
||||
mFields.add(allowedValue);
|
||||
}
|
||||
}
|
||||
|
||||
mSeenValues = Lists.newArrayListWithCapacity(allowedValues.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visitSwitchClauseExpression(USwitchClauseExpression node) {
|
||||
if (mReported) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mAllowedValues == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
List<UExpression> caseValues = node.getCaseValues();
|
||||
if (caseValues == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (UExpression caseValue : caseValues) {
|
||||
if (caseValue instanceof ULiteralExpression) {
|
||||
// Report warnings if you specify hardcoded constants.
|
||||
// It's the wrong thing to do.
|
||||
List<String> list = computeFieldNames(mSwitchExpression,
|
||||
Arrays.asList(mAllowedValues));
|
||||
// Keep error message in sync with {@link #getMissingCases}
|
||||
String message = "Don't use a constant here; expected one of: " + Joiner
|
||||
.on(", ").join(list);
|
||||
mContext.report(SWITCH_TYPE_DEF, caseValue,
|
||||
mContext.getUastLocation(caseValue), message);
|
||||
// Don't look for other missing typedef constants since you might
|
||||
// have aliased with value
|
||||
mReported = true;
|
||||
|
||||
} else if (caseValue instanceof UReferenceExpression) { // default case can have null expression
|
||||
PsiElement resolved = ((UReferenceExpression) caseValue).resolve();
|
||||
if (resolved == null) {
|
||||
// If there are compilation issues (e.g. user is editing code) we
|
||||
// can't be certain, so don't flag anything.
|
||||
return true;
|
||||
}
|
||||
if (resolved instanceof PsiField) {
|
||||
// We can't just do
|
||||
// fields.remove(resolved);
|
||||
// since the fields list contains instances of potentially
|
||||
// different types with different hash codes (due to the
|
||||
// external annotations, which are not of the same type as
|
||||
// for example the ECJ based ones.
|
||||
//
|
||||
// The equals method on external field class deliberately handles
|
||||
// this (but it can't make its hash code match what
|
||||
// the ECJ fields do, which is tied to the ECJ binding hash code.)
|
||||
// So instead, manually check for equals. These lists tend to
|
||||
// be very short anyway.
|
||||
boolean found = false;
|
||||
ListIterator<PsiElement> iterator = mFields.listIterator();
|
||||
while (iterator.hasNext()) {
|
||||
PsiElement field = iterator.next();
|
||||
if (field.equals(resolved)) {
|
||||
iterator.remove();
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// Look for local alias
|
||||
UExpression initializer = mContext.getUastContext()
|
||||
.getInitializerBody(((PsiField) resolved));
|
||||
if (initializer instanceof UReferenceExpression) {
|
||||
resolved = ((UReferenceExpression) initializer).resolve();
|
||||
if (resolved instanceof PsiField) {
|
||||
iterator = mFields.listIterator();
|
||||
while (iterator.hasNext()) {
|
||||
PsiElement field = iterator.next();
|
||||
if (field.equals(resolved)) {
|
||||
iterator.remove();
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found) {
|
||||
Integer cv = getConstantValue((PsiField) resolved);
|
||||
if (cv != null) {
|
||||
mSeenValues.add(cv);
|
||||
}
|
||||
} else {
|
||||
List<String> list = computeFieldNames(mSwitchExpression,
|
||||
Arrays.asList(mAllowedValues));
|
||||
// Keep error message in sync with {@link #getMissingCases}
|
||||
String message = "Unexpected constant; expected one of: " + Joiner
|
||||
.on(", ").join(list);
|
||||
Location location = mContext.getUastNameLocation(caseValue);
|
||||
mContext.report(SWITCH_TYPE_DEF, caseValue, location, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterVisitSwitchExpression(USwitchExpression node) {
|
||||
reportMissingSwitchCases();
|
||||
super.afterVisitSwitchExpression(node);
|
||||
}
|
||||
|
||||
private void reportMissingSwitchCases() {
|
||||
if (mReported) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mAllowedValues == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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();
|
||||
while (iterator.hasNext()) {
|
||||
PsiElement next = iterator.next();
|
||||
if (next instanceof PsiField) {
|
||||
Integer cv = getConstantValue((PsiField)next);
|
||||
if (mSeenValues.contains(cv)) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!mFields.isEmpty()) {
|
||||
List<String> list = computeFieldNames(mSwitchExpression, mFields);
|
||||
// Keep error message in sync with {@link #getMissingCases}
|
||||
String message = "Switch statement on an `int` with known associated constant "
|
||||
+ "missing case " + Joiner.on(", ").join(list);
|
||||
Location location = mContext.getUastLocation(mSwitchExpression.getSwitchIdentifier());
|
||||
mContext.report(SWITCH_TYPE_DEF, mSwitchExpression, location, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static List<String> computeFieldNames(@NonNull PsiSwitchStatement node,
|
||||
Iterable<?> allowedValues) {
|
||||
private static List<String> computeFieldNames(
|
||||
@NonNull USwitchExpression node, Iterable<?> allowedValues) {
|
||||
|
||||
List<String> list = Lists.newArrayList();
|
||||
for (Object o : allowedValues) {
|
||||
if (o instanceof PsiReferenceExpression) {
|
||||
if (o instanceof ExternalReferenceExpression) {
|
||||
ExternalReferenceExpression externalRef = (ExternalReferenceExpression) o;
|
||||
PsiElement resolved = UastLintUtils.resolve(externalRef, node);
|
||||
if (resolved != null) {
|
||||
o = resolved;
|
||||
}
|
||||
} else if (o instanceof PsiReferenceExpression) {
|
||||
PsiElement resolved = ((PsiReferenceExpression) o).resolve();
|
||||
if (resolved != null) {
|
||||
o = resolved;
|
||||
@@ -872,17 +923,11 @@ public class AnnotationDetector extends Detector implements JavaPsiScanner {
|
||||
PsiField field = (PsiField) o;
|
||||
// Only include class name if necessary
|
||||
String name = field.getName();
|
||||
PsiClass clz = PsiTreeUtil.getParentOfType(node, PsiClass.class, true);
|
||||
UClass clz = UastUtils.getParentOfType(node, UClass.class, true);
|
||||
if (clz != null) {
|
||||
PsiClass containingClass = field.getContainingClass();
|
||||
if (containingClass != null && !containingClass.equals(clz)) {
|
||||
|
||||
//if (Objects.equal(containingClass.getPackage(),
|
||||
// ((ResolvedClass) resolved).getPackage())) {
|
||||
// name = containingClass.getSimpleName() + '.' + field.getName();
|
||||
//} else {
|
||||
name = containingClass.getName() + '.' + field.getName();
|
||||
//}
|
||||
if (containingClass != null && !containingClass.equals(clz.getPsi())) {
|
||||
name = containingClass.getName() + '.' + field.getName();
|
||||
}
|
||||
}
|
||||
list.add('`' + name + '`');
|
||||
|
||||
@@ -58,22 +58,9 @@ import com.android.sdklib.SdkVersionInfo;
|
||||
import com.android.tools.klint.client.api.JavaEvaluator;
|
||||
import com.android.tools.klint.client.api.LintDriver;
|
||||
import com.android.tools.klint.client.api.UastLintUtils;
|
||||
import com.android.tools.klint.detector.api.Category;
|
||||
import com.android.tools.klint.detector.api.ClassContext;
|
||||
import com.android.tools.klint.detector.api.Context;
|
||||
import com.android.tools.klint.detector.api.Detector;
|
||||
import com.android.tools.klint.detector.api.*;
|
||||
import com.android.tools.klint.detector.api.Detector.ClassScanner;
|
||||
import com.android.tools.klint.detector.api.Implementation;
|
||||
import com.android.tools.klint.detector.api.Issue;
|
||||
import com.android.tools.klint.detector.api.JavaContext;
|
||||
import com.android.tools.klint.detector.api.LintUtils;
|
||||
import com.android.tools.klint.detector.api.Location;
|
||||
import com.android.tools.klint.detector.api.Location.SearchHints;
|
||||
import com.android.tools.klint.detector.api.ResourceXmlDetector;
|
||||
import com.android.tools.klint.detector.api.Scope;
|
||||
import com.android.tools.klint.detector.api.Severity;
|
||||
import com.android.tools.klint.detector.api.TextFormat;
|
||||
import com.android.tools.klint.detector.api.XmlContext;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.intellij.psi.PsiAnnotation;
|
||||
import com.intellij.psi.PsiAnnotationMemberValue;
|
||||
@@ -148,6 +135,7 @@ import org.w3c.dom.Element;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
@@ -310,6 +298,8 @@ public class ApiDetector extends ResourceXmlDetector
|
||||
|
||||
private static final String SDK_INT = "SDK_INT";
|
||||
private static final String ANDROID_OS_BUILD_VERSION = "android/os/Build$VERSION";
|
||||
private static final String REFLECTIVE_OPERATION_EXCEPTION
|
||||
= "java.lang.ReflectiveOperationException";
|
||||
|
||||
protected ApiLookup mApiDatabase;
|
||||
private boolean mWarnedMissingDb;
|
||||
@@ -864,33 +854,6 @@ public class ApiDetector extends ResourceXmlDetector
|
||||
continue;
|
||||
}
|
||||
|
||||
List tryCatchBlocks = method.tryCatchBlocks;
|
||||
// single-catch blocks are already handled by an AST level check in ApiVisitor
|
||||
if (tryCatchBlocks.size() > 1) {
|
||||
List<String> checked = Lists.newArrayList();
|
||||
for (Object o : tryCatchBlocks) {
|
||||
TryCatchBlockNode tryCatchBlock = (TryCatchBlockNode) o;
|
||||
String className = tryCatchBlock.type;
|
||||
if (className == null || checked.contains(className)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int api = mApiDatabase.getClassVersion(className);
|
||||
if (api > minSdk) {
|
||||
// Find instruction node
|
||||
LabelNode label = tryCatchBlock.handler;
|
||||
String fqcn = getFqcn(className);
|
||||
String message = String.format(
|
||||
"Class requires API level %1$d (current min is %2$d): `%3$s`",
|
||||
api, minSdk, fqcn);
|
||||
report(context, message, label, method,
|
||||
className.substring(className.lastIndexOf('/') + 1), null,
|
||||
SearchHints.create(EOL_NEAREST).matchJavaSymbol());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (CHECK_DECLARATIONS) {
|
||||
// Check types in parameter list and types of local variables
|
||||
List localVariables = method.localVariables;
|
||||
@@ -1493,7 +1456,7 @@ public class ApiDetector extends ResourceXmlDetector
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
private static void report(final ClassContext context, String message, AbstractInsnNode node,
|
||||
MethodNode method, String patternStart, String patternEnd, SearchHints hints) {
|
||||
int lineNumber = node != null ? ClassContext.findLineNumber(node) : -1;
|
||||
@@ -1647,7 +1610,7 @@ public class ApiDetector extends ResourceXmlDetector
|
||||
checkField(statement, (PsiField)resolved);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return super.visitImportStatement(statement);
|
||||
}
|
||||
|
||||
@@ -1657,7 +1620,7 @@ public class ApiDetector extends ResourceXmlDetector
|
||||
if (resolved instanceof PsiField) {
|
||||
checkField(node, (PsiField)resolved);
|
||||
}
|
||||
|
||||
|
||||
return super.visitSimpleNameReferenceExpression(node);
|
||||
}
|
||||
|
||||
@@ -1666,16 +1629,16 @@ public class ApiDetector extends ResourceXmlDetector
|
||||
if (UastExpressionUtils.isTypeCast(node)) {
|
||||
visitTypeCastExpression(node);
|
||||
}
|
||||
|
||||
|
||||
return super.visitBinaryExpressionWithType(node);
|
||||
}
|
||||
|
||||
private void visitTypeCastExpression(UBinaryExpressionWithType expression) {
|
||||
UExpression operand = expression.getOperand();
|
||||
|
||||
|
||||
PsiType operandType = operand.getExpressionType();
|
||||
PsiType castType = expression.getType();
|
||||
|
||||
|
||||
if (castType.equals(operandType)) {
|
||||
return;
|
||||
}
|
||||
@@ -1690,7 +1653,7 @@ public class ApiDetector extends ResourceXmlDetector
|
||||
checkCast(expression, classType, interfaceType);
|
||||
}
|
||||
|
||||
private void checkCast(@NonNull UElement node, @NonNull PsiClassType classType,
|
||||
private void checkCast(@NonNull UElement node, @NonNull PsiClassType classType,
|
||||
@NonNull PsiClassType interfaceType) {
|
||||
if (classType.equals(interfaceType)) {
|
||||
return;
|
||||
@@ -1737,7 +1700,7 @@ public class ApiDetector extends ResourceXmlDetector
|
||||
mContext.reportUast(UNSUPPORTED, method, location, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return super.visitMethod(method);
|
||||
}
|
||||
|
||||
@@ -1777,7 +1740,7 @@ public class ApiDetector extends ResourceXmlDetector
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return super.visitClass(aClass);
|
||||
}
|
||||
|
||||
@@ -1840,7 +1803,7 @@ public class ApiDetector extends ResourceXmlDetector
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return super.visitCallExpression(expression);
|
||||
}
|
||||
|
||||
@@ -1908,7 +1871,7 @@ public class ApiDetector extends ResourceXmlDetector
|
||||
if (UastExpressionUtils.isAssignment(node)) {
|
||||
visitAssignmentExpression(node);
|
||||
}
|
||||
|
||||
|
||||
return super.visitBinaryExpression(node);
|
||||
}
|
||||
|
||||
@@ -1951,14 +1914,75 @@ public class ApiDetector extends ResourceXmlDetector
|
||||
}
|
||||
|
||||
for (UCatchClause catchClause : statement.getCatchClauses()) {
|
||||
|
||||
// Special case reflective operation exception which can be implicitly used
|
||||
// with multi-catches: see issue 153406
|
||||
int minSdk = getMinSdk(mContext);
|
||||
if(minSdk < 19 && isMultiCatchReflectiveOperationException(catchClause)) {
|
||||
String message = String.format("Multi-catch with these reflection exceptions requires API level 19 (current min is %d) " +
|
||||
"because they get compiled to the common but new super type `ReflectiveOperationException`. " +
|
||||
"As a workaround either create individual catch statements, or catch `Exception`.",
|
||||
minSdk);
|
||||
|
||||
mContext.report(UNSUPPORTED, getCatchParametersLocation(mContext, catchClause), message);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (UTypeReferenceExpression typeReference : catchClause.getTypeReferences()) {
|
||||
checkCatchTypeElement(statement, typeReference, typeReference.getType());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return super.visitTryExpression(statement);
|
||||
}
|
||||
|
||||
private Location getCatchParametersLocation(JavaContext context, UCatchClause catchClause) {
|
||||
List<UTypeReferenceExpression> types = catchClause.getTypeReferences();
|
||||
if (types.isEmpty()) {
|
||||
return Location.NONE;
|
||||
}
|
||||
|
||||
Location first = context.getUastLocation(types.get(0));
|
||||
if (types.size() < 2) {
|
||||
return first;
|
||||
}
|
||||
|
||||
Location last = context.getUastLocation(types.get(types.size() - 1));
|
||||
File file = first.getFile();
|
||||
Position start = first.getStart();
|
||||
Position end = last.getEnd();
|
||||
|
||||
if (start == null) {
|
||||
return Location.create(file);
|
||||
}
|
||||
|
||||
return Location.create(file, start, end);
|
||||
}
|
||||
|
||||
private boolean isMultiCatchReflectiveOperationException(UCatchClause catchClause) {
|
||||
List<PsiType> types = catchClause.getTypes();
|
||||
if (types.size() < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (PsiType t : types) {
|
||||
if(!isSubclassOfReflectiveOperationException(t)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isSubclassOfReflectiveOperationException(PsiType type) {
|
||||
for (PsiType t : type.getSuperTypes()) {
|
||||
if (REFLECTIVE_OPERATION_EXCEPTION.equals(t.getCanonicalText())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void checkCatchTypeElement(@NonNull UTryExpression statement,
|
||||
@NonNull UTypeReferenceExpression typeReference,
|
||||
@Nullable PsiType type) {
|
||||
@@ -1981,19 +2005,10 @@ public class ApiDetector extends ResourceXmlDetector
|
||||
return;
|
||||
}
|
||||
|
||||
Location location;
|
||||
location = mContext.getUastLocation(typeReference);
|
||||
Location location = mContext.getUastLocation(typeReference);
|
||||
String fqcn = resolved.getQualifiedName();
|
||||
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);
|
||||
}
|
||||
String message = String.format("Class requires API level %1$d (current min is %2$d): %3$s",
|
||||
api, minSdk, fqcn);
|
||||
mContext.report(UNSUPPORTED, location, message);
|
||||
}
|
||||
}
|
||||
@@ -2356,55 +2371,97 @@ public class ApiDetector extends ResourceXmlDetector
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isPrecededByVersionCheckExit(
|
||||
UElement element, int api, JavaContext context) {
|
||||
UElement current = UastUtils.getParentOfType(element, UExpression.class);
|
||||
if (current != null) {
|
||||
UElement prev = getPreviousStatement(current);
|
||||
if (prev == null) {
|
||||
//noinspection unchecked
|
||||
current = UastUtils.getParentOfType(current, UExpression.class, true,
|
||||
UMethod.class, UClass.class);
|
||||
} else {
|
||||
current = prev;
|
||||
}
|
||||
private static class VersionCheckWithExitFinder extends AbstractUastVisitor {
|
||||
private final UExpression mExpression;
|
||||
private final UElement mEndElement;
|
||||
private final int mApi;
|
||||
private final JavaContext mContext;
|
||||
|
||||
private boolean mFound = false;
|
||||
private boolean mDone = false;
|
||||
|
||||
public VersionCheckWithExitFinder(UExpression expression, UElement endElement,
|
||||
int api, JavaContext context) {
|
||||
mExpression = expression;
|
||||
|
||||
mEndElement = endElement;
|
||||
mApi = api;
|
||||
mContext = context;
|
||||
}
|
||||
while (current != null) {
|
||||
if (current instanceof UIfExpression) {
|
||||
UIfExpression ifStatement = (UIfExpression)current;
|
||||
UExpression thenBranch = ifStatement.getThenExpression();
|
||||
UExpression elseBranch = ifStatement.getElseExpression();
|
||||
if (thenBranch != null) {
|
||||
Boolean level = isVersionCheckConditional(api, thenBranch, ifStatement, context);
|
||||
//noinspection VariableNotUsedInsideIf
|
||||
if (level != null) {
|
||||
// See if the body does an immediate return
|
||||
if (isUnconditionalReturn(thenBranch)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (elseBranch != null) {
|
||||
Boolean level = isVersionCheckConditional(api, elseBranch, ifStatement, context);
|
||||
//noinspection VariableNotUsedInsideIf
|
||||
if (level != null) {
|
||||
if (isUnconditionalReturn(elseBranch)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visitElement(UElement node) {
|
||||
if (mDone) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (node.equals(mEndElement)) {
|
||||
mDone = true;
|
||||
}
|
||||
|
||||
return mDone || !mExpression.equals(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean visitIfExpression(UIfExpression ifStatement) {
|
||||
|
||||
if (mDone) {
|
||||
return true;
|
||||
}
|
||||
|
||||
UExpression thenBranch = ifStatement.getThenExpression();
|
||||
UExpression elseBranch = ifStatement.getElseExpression();
|
||||
|
||||
if (thenBranch != null) {
|
||||
Boolean level = isVersionCheckConditional(mApi, thenBranch, ifStatement, mContext);
|
||||
//noinspection VariableNotUsedInsideIf
|
||||
if (level != null) {
|
||||
// See if the body does an immediate return
|
||||
if (isUnconditionalReturn(thenBranch)) {
|
||||
mFound = true;
|
||||
mDone = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
UElement prev = getPreviousStatement(current);
|
||||
if (prev == null) {
|
||||
//noinspection unchecked
|
||||
current = UastUtils.getParentOfType(current, UExpression.class, true,
|
||||
UMethod.class, UClass.class);
|
||||
if (current == null) {
|
||||
return false;
|
||||
|
||||
if (elseBranch != null) {
|
||||
Boolean level = isVersionCheckConditional(mApi, elseBranch, ifStatement, mContext);
|
||||
//noinspection VariableNotUsedInsideIf
|
||||
if (level != null) {
|
||||
if (isUnconditionalReturn(elseBranch)) {
|
||||
mFound = true;
|
||||
mDone = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
current = prev;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean found() {
|
||||
return mFound;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isPrecededByVersionCheckExit(UElement element, int api,
|
||||
JavaContext context) {
|
||||
//noinspection unchecked
|
||||
UExpression currentExpression = UastUtils.getParentOfType(element, UExpression.class,
|
||||
true, UMethod.class, UClass.class);
|
||||
|
||||
while(currentExpression != null) {
|
||||
VersionCheckWithExitFinder visitor = new VersionCheckWithExitFinder(
|
||||
currentExpression, element, api, context);
|
||||
currentExpression.accept(visitor);
|
||||
|
||||
if (visitor.found()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
element = currentExpression;
|
||||
//noinspection unchecked
|
||||
currentExpression = UastUtils.getParentOfType(currentExpression, UExpression.class,
|
||||
true, UMethod.class, UClass.class);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -2420,16 +2477,6 @@ public class ApiDetector extends ResourceXmlDetector
|
||||
return statement instanceof UReturnExpression;
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
public static UElement getPreviousStatement(UElement element) {
|
||||
//TODO
|
||||
return null;
|
||||
//final PsiElement prevStatement = PsiTreeUtil.skipSiblingsBackward(element,
|
||||
// PsiWhiteSpace.class, PsiComment.class);
|
||||
//return prevStatement instanceof PsiStatement ? (PsiStatement)prevStatement : null;
|
||||
}
|
||||
|
||||
public static boolean isWithinVersionCheckConditional(
|
||||
UElement element, int api, JavaContext context) {
|
||||
UElement current = element.getContainingElement();
|
||||
@@ -2453,7 +2500,7 @@ public class ApiDetector extends ResourceXmlDetector
|
||||
|
||||
@Nullable
|
||||
private static Boolean isVersionCheckConditional(
|
||||
int api,
|
||||
int api,
|
||||
UElement prev,
|
||||
UIfExpression ifStatement,
|
||||
@NonNull JavaContext context) {
|
||||
@@ -2543,7 +2590,7 @@ public class ApiDetector extends ResourceXmlDetector
|
||||
// if (SDK_INT < ICE_CREAM_SANDWICH) { ... } else { <call> }
|
||||
return level >= api && fromElse;
|
||||
}
|
||||
else if (tokenType == UastBinaryOperator.EQUALS
|
||||
else if (tokenType == UastBinaryOperator.EQUALS
|
||||
|| tokenType == UastBinaryOperator.IDENTITY_EQUALS) {
|
||||
// if (SDK_INT == ICE_CREAM_SANDWICH) { <call> } else { }
|
||||
return level >= api && fromThen;
|
||||
@@ -2553,7 +2600,7 @@ public class ApiDetector extends ResourceXmlDetector
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (tokenType == UastBinaryOperator.LOGICAL_AND
|
||||
} else if (tokenType == UastBinaryOperator.LOGICAL_AND
|
||||
&& (ifStatement != null && prev == ifStatement.getThenExpression())) {
|
||||
if (isAndedWithConditional(ifStatement.getCondition(), api, prev)) {
|
||||
return true;
|
||||
|
||||
@@ -346,8 +346,7 @@ public class CleanupDetector extends Detector implements Detector.UastScanner {
|
||||
recycleName);
|
||||
}
|
||||
|
||||
UElement locationNode = node instanceof UCallExpression ?
|
||||
((UCallExpression) node).getMethodIdentifier() : node;
|
||||
UElement locationNode = node.getMethodIdentifier();
|
||||
if (locationNode == null) {
|
||||
locationNode = node;
|
||||
}
|
||||
@@ -507,7 +506,12 @@ public class CleanupDetector extends Detector implements Detector.UastScanner {
|
||||
protected boolean isCleanupCall(@NonNull UCallExpression call) {
|
||||
if (isEditorApplyMethodCall(mContext, call)
|
||||
|| isEditorCommitMethodCall(mContext, call)) {
|
||||
UExpression operand = call.getReceiver();
|
||||
List<UExpression> chain = getQualifiedChain(getOutermostQualified(call));
|
||||
if (chain.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UExpression operand = chain.get(0);
|
||||
if (operand != null) {
|
||||
PsiElement resolved = UastUtils.tryResolve(operand);
|
||||
//noinspection SuspiciousMethodCalls
|
||||
@@ -515,7 +519,7 @@ public class CleanupDetector extends Detector implements Detector.UastScanner {
|
||||
return true;
|
||||
} else if (resolved instanceof PsiMethod
|
||||
&& operand instanceof UCallExpression
|
||||
&& isCommittedInChainedCalls(mContext,
|
||||
&& isEditorCommittedInChainedCalls(mContext,
|
||||
(UCallExpression) operand)) {
|
||||
// Check that the target of the committed chains is the
|
||||
// right variable!
|
||||
|
||||
@@ -20,17 +20,18 @@ import com.android.annotations.NonNull;
|
||||
import com.android.annotations.Nullable;
|
||||
import com.android.tools.klint.detector.api.Category;
|
||||
import com.android.tools.klint.detector.api.Detector;
|
||||
import com.android.tools.klint.detector.api.Detector.JavaPsiScanner;
|
||||
import com.android.tools.klint.detector.api.Implementation;
|
||||
import com.android.tools.klint.detector.api.Issue;
|
||||
import com.android.tools.klint.detector.api.JavaContext;
|
||||
import com.android.tools.klint.detector.api.Location;
|
||||
import com.android.tools.klint.detector.api.Scope;
|
||||
import com.android.tools.klint.detector.api.Severity;
|
||||
import com.intellij.psi.JavaElementVisitor;
|
||||
import com.intellij.psi.PsiComment;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiLiteralExpression;
|
||||
|
||||
import org.jetbrains.uast.UElement;
|
||||
import org.jetbrains.uast.UComment;
|
||||
import org.jetbrains.uast.UFile;
|
||||
import org.jetbrains.uast.visitor.AbstractUastVisitor;
|
||||
import org.jetbrains.uast.visitor.UastVisitor;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -38,7 +39,7 @@ import java.util.List;
|
||||
/**
|
||||
* Looks for issues in Java comments
|
||||
*/
|
||||
public class CommentDetector extends Detector implements JavaPsiScanner {
|
||||
public class CommentDetector extends Detector implements Detector.UastScanner {
|
||||
private static final String STOPSHIP_COMMENT = "STOPSHIP"; //$NON-NLS-1$
|
||||
|
||||
private static final Implementation IMPLEMENTATION = new Implementation(
|
||||
@@ -75,7 +76,7 @@ public class CommentDetector extends Detector implements JavaPsiScanner {
|
||||
private static final String ESCAPE_STRING = "\\u002a\\u002f"; //$NON-NLS-1$
|
||||
|
||||
/** The current AST only passes comment nodes for Javadoc so I need to do manual token scanning
|
||||
instead */
|
||||
instead */
|
||||
private static final boolean USE_AST = false;
|
||||
|
||||
|
||||
@@ -83,20 +84,20 @@ public class CommentDetector extends Detector implements JavaPsiScanner {
|
||||
public CommentDetector() {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<Class<? extends PsiElement>> getApplicablePsiTypes() {
|
||||
public List<Class<? extends UElement>> getApplicableUastTypes() {
|
||||
if (USE_AST) {
|
||||
return Collections.<Class<? extends PsiElement>>singletonList(
|
||||
PsiLiteralExpression.class);
|
||||
return Collections.<Class<? extends UElement>>singletonList(
|
||||
UFile.class);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public JavaElementVisitor createPsiVisitor(@NonNull JavaContext context) {
|
||||
// Lombok does not generate comment nodes for block and line comments, only for
|
||||
// javadoc comments!
|
||||
public UastVisitor createUastVisitor(@NonNull JavaContext context) {
|
||||
if (USE_AST) {
|
||||
return new CommentChecker(context);
|
||||
} else {
|
||||
@@ -135,7 +136,7 @@ public class CommentDetector extends Detector implements JavaPsiScanner {
|
||||
}
|
||||
}
|
||||
|
||||
private static class CommentChecker extends JavaElementVisitor {
|
||||
private static class CommentChecker extends AbstractUastVisitor {
|
||||
private final JavaContext mContext;
|
||||
|
||||
public CommentChecker(JavaContext context) {
|
||||
@@ -143,16 +144,19 @@ public class CommentDetector extends Detector implements JavaPsiScanner {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitComment(PsiComment comment) {
|
||||
String contents = comment.getText();
|
||||
checkComment(mContext, comment, contents, comment.getTextRange().getStartOffset(), 0,
|
||||
contents.length());
|
||||
public boolean visitFile(UFile node) {
|
||||
for (UComment comment : node.getAllCommentsInFile()) {
|
||||
String contents = comment.getText();
|
||||
checkComment(mContext, comment, contents,
|
||||
comment.getPsi().getTextRange().getStartOffset(), 0, contents.length());
|
||||
}
|
||||
return super.visitFile(node);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkComment(
|
||||
@NonNull JavaContext context,
|
||||
@Nullable PsiComment node,
|
||||
@Nullable UComment node,
|
||||
@NonNull String source,
|
||||
int offset,
|
||||
int start,
|
||||
@@ -164,24 +168,31 @@ public class CommentDetector extends Detector implements JavaPsiScanner {
|
||||
if (prev == '\\') {
|
||||
if (c == 'u' || c == 'U') {
|
||||
if (source.regionMatches(true, i - 1, ESCAPE_STRING,
|
||||
0, ESCAPE_STRING.length())) {
|
||||
0, ESCAPE_STRING.length())) {
|
||||
Location location = Location.create(context.file, source,
|
||||
offset + i - 1, offset + i - 1 + ESCAPE_STRING.length());
|
||||
offset + i - 1, offset + i - 1 + ESCAPE_STRING.length());
|
||||
context.report(EASTER_EGG, node, location,
|
||||
"Code might be hidden here; found unicode escape sequence " +
|
||||
"which is interpreted as comment end, compiled code follows");
|
||||
"Code might be hidden here; found unicode escape sequence " +
|
||||
"which is interpreted as comment end, compiled code follows");
|
||||
}
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
} else if (prev == 'S' && c == 'T' &&
|
||||
source.regionMatches(i - 1, STOPSHIP_COMMENT, 0, STOPSHIP_COMMENT.length())) {
|
||||
source.regionMatches(i - 1, STOPSHIP_COMMENT, 0, STOPSHIP_COMMENT.length())) {
|
||||
|
||||
// TODO: Only flag this issue in release mode??
|
||||
Location location = Location.create(context.file, source,
|
||||
offset + i - 1, offset + i - 1 + STOPSHIP_COMMENT.length());
|
||||
Location location;
|
||||
if (node != null) {
|
||||
location = context.getLocation(node);
|
||||
} else {
|
||||
location = Location.create(context.file, source,
|
||||
offset + i - 1, offset + i - 1 + STOPSHIP_COMMENT.length());
|
||||
}
|
||||
|
||||
context.report(STOP_SHIP, node, location,
|
||||
"`STOPSHIP` comment found; points to code which must be fixed prior " +
|
||||
"to release");
|
||||
"`STOPSHIP` comment found; points to code which must be fixed prior " +
|
||||
"to release");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+180
-115
@@ -42,7 +42,7 @@ import com.intellij.psi.PsiStatement;
|
||||
import com.intellij.psi.PsiWhiteSpace;
|
||||
|
||||
import org.jetbrains.uast.UArrayAccessExpression;
|
||||
import org.jetbrains.uast.UBinaryExpression;
|
||||
import org.jetbrains.uast.*;
|
||||
import org.jetbrains.uast.UCallExpression;
|
||||
import org.jetbrains.uast.UElement;
|
||||
import org.jetbrains.uast.UExpression;
|
||||
@@ -52,6 +52,7 @@ import org.jetbrains.uast.UQualifiedReferenceExpression;
|
||||
import org.jetbrains.uast.UastUtils;
|
||||
import org.jetbrains.uast.expressions.UReferenceExpression;
|
||||
import org.jetbrains.uast.util.UastExpressionUtils;
|
||||
import org.jetbrains.uast.visitor.AbstractUastVisitor;
|
||||
import org.jetbrains.uast.visitor.UastVisitor;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -165,137 +166,201 @@ public class CutPasteDetector extends Detector implements Detector.UastScanner {
|
||||
|
||||
@Nullable
|
||||
private static String getLhs(@NonNull UCallExpression call) {
|
||||
UElement parent = skipParentheses(call.getContainingElement());
|
||||
if (parent != null && UastExpressionUtils.isTypeCast(parent)) {
|
||||
UElement parent = call.getContainingElement();
|
||||
while (parent != null && !(parent instanceof UBlockExpression)) {
|
||||
if (parent instanceof ULocalVariable) {
|
||||
return ((ULocalVariable) parent).getName();
|
||||
} else if (UastExpressionUtils.isAssignment(parent)) {
|
||||
UExpression left = ((UBinaryExpression) parent).getLeftOperand();
|
||||
if (left instanceof UReferenceExpression) {
|
||||
return left.asSourceString();
|
||||
} else if (left instanceof UArrayAccessExpression) {
|
||||
UArrayAccessExpression aa = (UArrayAccessExpression) left;
|
||||
return aa.getReceiver().asSourceString();
|
||||
}
|
||||
}
|
||||
parent = parent.getContainingElement();
|
||||
}
|
||||
|
||||
if (parent instanceof ULocalVariable) {
|
||||
return ((ULocalVariable) parent).getName();
|
||||
} else if (parent instanceof UBinaryExpression) {
|
||||
UBinaryExpression be = (UBinaryExpression) parent;
|
||||
UExpression left = be.getLeftOperand();
|
||||
if (left instanceof UReferenceExpression) {
|
||||
return left.asRenderString();
|
||||
} else if (left instanceof UArrayAccessExpression) {
|
||||
UArrayAccessExpression aa = (UArrayAccessExpression) left;
|
||||
return aa.getReceiver().asSourceString();
|
||||
}
|
||||
} else if (UastExpressionUtils.isAssignment(parent)) {
|
||||
//noinspection ConstantConditions
|
||||
UExpression left = ((UBinaryExpression) parent).getLeftOperand();
|
||||
if (left instanceof UReferenceExpression) {
|
||||
return left.asSourceString();
|
||||
} else if (left instanceof UArrayAccessExpression) {
|
||||
UArrayAccessExpression aa = (UArrayAccessExpression) left;
|
||||
return aa.getReceiver().asSourceString();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static boolean isReachableFrom(
|
||||
@NonNull PsiMethod method,
|
||||
@NonNull PsiElement from,
|
||||
@NonNull PsiElement to) {
|
||||
PsiElement prev = from;
|
||||
PsiElement curr = next(method, from, to, null);
|
||||
//noinspection ConstantConditions
|
||||
while (curr != null) {
|
||||
if (containsElement(method, curr, to)) {
|
||||
return true;
|
||||
}
|
||||
curr = next(method, curr, to, prev);
|
||||
prev = curr;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static boolean isReachableFrom(
|
||||
@NonNull PsiMethod method,
|
||||
@NonNull UMethod method,
|
||||
@NonNull UElement from,
|
||||
@NonNull UElement to) {
|
||||
//TODO
|
||||
return false;
|
||||
ReachabilityVisitor visitor = new ReachabilityVisitor(from, to);
|
||||
method.accept(visitor);
|
||||
return visitor.isReachable();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
static PsiElement next(
|
||||
@NonNull PsiMethod method,
|
||||
@NonNull PsiElement curr,
|
||||
@NonNull PsiElement target,
|
||||
@Nullable PsiElement prev) {
|
||||
private static class ReachabilityVisitor extends AbstractUastVisitor {
|
||||
|
||||
if (curr instanceof PsiMethod) {
|
||||
return null;
|
||||
private final UElement mFrom;
|
||||
private final UElement mTarget;
|
||||
|
||||
private boolean mIsFromReached;
|
||||
private boolean mIsTargetReachable;
|
||||
private boolean mIsFinished;
|
||||
|
||||
private UExpression mBreakedExpression;
|
||||
private UExpression mContinuedExpression;
|
||||
|
||||
ReachabilityVisitor(UElement from, UElement target) {
|
||||
mFrom = from;
|
||||
mTarget = target;
|
||||
}
|
||||
|
||||
PsiElement parent = curr.getParent();
|
||||
if (curr instanceof PsiContinueStatement) {
|
||||
PsiStatement continuedStatement = ((PsiContinueStatement) curr)
|
||||
.findContinuedStatement();
|
||||
if (continuedStatement != null) {
|
||||
if (containsElement(method, continuedStatement, target)) {
|
||||
return target;
|
||||
}
|
||||
return next(method, continuedStatement, target, curr);
|
||||
} else {
|
||||
return next(method, parent, target, curr);
|
||||
}
|
||||
} else if (curr instanceof PsiBreakStatement) {
|
||||
PsiStatement exitedStatement = ((PsiBreakStatement) curr).findExitedStatement();
|
||||
if (exitedStatement != null) {
|
||||
return next(method, exitedStatement, target, curr);
|
||||
} else {
|
||||
return next(method, parent, target, curr);
|
||||
}
|
||||
} else if (curr instanceof PsiReturnStatement) {
|
||||
return null;
|
||||
} else if (curr instanceof PsiLoopStatement && prev != null &&
|
||||
containsElement(method, curr, prev)) {
|
||||
// If we stepped *up* (from a last child nested in the loop) up to the loop
|
||||
// itself, mark all children in the loop as reachable since we're iterating
|
||||
if (containsElement(method, curr, target)) {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
PsiElement sibling = curr.getNextSibling();
|
||||
while (sibling instanceof PsiWhiteSpace || sibling instanceof PsiJavaToken) {
|
||||
// Skip whitespaces and tokens such as PsiJavaToken.SEMICOLON etc
|
||||
sibling = sibling.getNextSibling();
|
||||
}
|
||||
if (sibling == null) {
|
||||
return next(method, parent, target, curr);
|
||||
}
|
||||
|
||||
if (parent instanceof PsiIfStatement &&
|
||||
curr == ((PsiIfStatement)parent).getThenBranch()) {
|
||||
return next(method, parent, target, curr);
|
||||
} else if (parent instanceof PsiLoopStatement) {
|
||||
if (containsElement(method, parent, target)) {
|
||||
return target;
|
||||
}
|
||||
}
|
||||
|
||||
return sibling;
|
||||
}
|
||||
|
||||
private static boolean containsElement(
|
||||
@NonNull PsiMethod method,
|
||||
@NonNull PsiElement root,
|
||||
@NonNull PsiElement element) {
|
||||
//noinspection ConstantConditions
|
||||
while (element != null && element != method) {
|
||||
if (root.equals(element)) {
|
||||
@Override
|
||||
public boolean visitElement(UElement node) {
|
||||
if (mIsFinished || mBreakedExpression != null || mContinuedExpression != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
element = element.getParent();
|
||||
if (node.equals(mFrom)) {
|
||||
mIsFromReached = true;
|
||||
}
|
||||
|
||||
if (node.equals(mTarget)) {
|
||||
mIsFinished = true;
|
||||
if (mIsFromReached) {
|
||||
mIsTargetReachable = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mIsFromReached) {
|
||||
if (node instanceof UReturnExpression) {
|
||||
mIsFinished = true;
|
||||
} else if (node instanceof UBreakExpression) {
|
||||
mBreakedExpression = getBreakedExpression((UBreakExpression) node);
|
||||
} else if (node instanceof UContinueExpression) {
|
||||
UExpression expression = getContinuedExpression((UContinueExpression) node);
|
||||
if (expression != null && UastUtils.isChildOf(mTarget, expression, false)) {
|
||||
mIsTargetReachable = true;
|
||||
mIsFinished = true;
|
||||
} else {
|
||||
mContinuedExpression = expression;
|
||||
}
|
||||
} else if (UastUtils.isChildOf(mTarget, node, false)) {
|
||||
mIsTargetReachable = true;
|
||||
mIsFinished = true;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (node instanceof UIfExpression) {
|
||||
UIfExpression ifExpression = (UIfExpression) node;
|
||||
|
||||
ifExpression.getCondition().accept(this);
|
||||
|
||||
boolean isFromReached = mIsFromReached;
|
||||
|
||||
UExpression thenExpression = ifExpression.getThenExpression();
|
||||
if (thenExpression != null) {
|
||||
thenExpression.accept(this);
|
||||
}
|
||||
|
||||
UExpression elseExpression = ifExpression.getElseExpression();
|
||||
if (elseExpression != null && isFromReached == mIsFromReached) {
|
||||
elseExpression.accept(this);
|
||||
}
|
||||
return true;
|
||||
} else if (node instanceof ULoopExpression) {
|
||||
visitLoopExpressionHeader(node);
|
||||
boolean isFromReached = mIsFromReached;
|
||||
|
||||
((ULoopExpression) node).getBody().accept(this);
|
||||
|
||||
if (isFromReached != mIsFromReached
|
||||
&& UastUtils.isChildOf(mTarget, node, false)) {
|
||||
mIsTargetReachable = true;
|
||||
mIsFinished = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
@Override
|
||||
public void afterVisitElement(UElement node) {
|
||||
if (node.equals(mBreakedExpression)) {
|
||||
mBreakedExpression = null;
|
||||
} else if (node.equals(mContinuedExpression)) {
|
||||
mContinuedExpression = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void visitLoopExpressionHeader(UElement node) {
|
||||
if (node instanceof UWhileExpression) {
|
||||
((UWhileExpression) node).getCondition().accept(this);
|
||||
} else if (node instanceof UDoWhileExpression) {
|
||||
((UDoWhileExpression) node).getCondition().accept(this);
|
||||
} else if (node instanceof UForExpression) {
|
||||
UForExpression forExpression = (UForExpression) node;
|
||||
|
||||
if (forExpression.getDeclaration() != null) {
|
||||
forExpression.getDeclaration().accept(this);
|
||||
}
|
||||
|
||||
if (forExpression.getCondition() != null) {
|
||||
forExpression.getCondition().accept(this);
|
||||
}
|
||||
|
||||
if (forExpression.getUpdate() != null) {
|
||||
forExpression.getUpdate().accept(this);
|
||||
}
|
||||
} else if (node instanceof UForEachExpression) {
|
||||
UForEachExpression forEachExpression = (UForEachExpression) node;
|
||||
forEachExpression.getForIdentifier().accept(this);
|
||||
forEachExpression.getIteratedValue().accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
private static UExpression getBreakedExpression(UBreakExpression node) {
|
||||
UElement parent = node.getContainingElement();
|
||||
String label = node.getLabel();
|
||||
while (parent != null) {
|
||||
if (label != null) {
|
||||
if (parent instanceof ULabeledExpression) {
|
||||
ULabeledExpression labeledExpression = (ULabeledExpression) parent;
|
||||
if (labeledExpression.getLabel().equals(label)) {
|
||||
return labeledExpression.getExpression();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (parent instanceof ULoopExpression || parent instanceof USwitchExpression) {
|
||||
return (UExpression) parent;
|
||||
}
|
||||
}
|
||||
parent = parent.getContainingElement();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static UExpression getContinuedExpression(UContinueExpression node) {
|
||||
UElement parent = node.getContainingElement();
|
||||
String label = node.getLabel();
|
||||
while (parent != null) {
|
||||
if (label != null) {
|
||||
if (parent instanceof ULabeledExpression) {
|
||||
ULabeledExpression labeledExpression = (ULabeledExpression) parent;
|
||||
if (labeledExpression.getLabel().equals(label)) {
|
||||
return labeledExpression.getExpression();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (parent instanceof ULoopExpression) {
|
||||
return (UExpression) parent;
|
||||
}
|
||||
}
|
||||
parent = parent.getContainingElement();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean isReachable() {
|
||||
return mIsTargetReachable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1972,9 +1972,10 @@ public class IconDetector extends ResourceXmlDetector implements Detector.UastSc
|
||||
|
||||
// ---- Implements UastScanner ----
|
||||
|
||||
private static final String NOTIFICATION_CLASS = "Notification";
|
||||
private static final String NOTIFICATION_BUILDER_CLASS = "Notification.Builder";
|
||||
private static final String NOTIFICATION_COMPAT_BUILDER_CLASS = "NotificationCompat.Builder";
|
||||
private static final String NOTIFICATION_CLASS = "android.app.Notification";
|
||||
private static final String NOTIFICATION_BUILDER_CLASS = "android.app.Notification.Builder";
|
||||
private static final String NOTIFICATION_COMPAT_BUILDER_CLASS =
|
||||
"android.support.v4.app.NotificationCompat.Builder";
|
||||
private static final String SET_SMALL_ICON = "setSmallIcon";
|
||||
private static final String ON_CREATE_OPTIONS_MENU = "onCreateOptionsMenu";
|
||||
|
||||
@@ -2026,7 +2027,7 @@ public class IconDetector extends ResourceXmlDetector implements Detector.UastSc
|
||||
if (!(resolved instanceof PsiClass)) {
|
||||
return;
|
||||
}
|
||||
String typeName = ((PsiClass) resolved).getName();
|
||||
String typeName = ((PsiClass) resolved).getQualifiedName();
|
||||
if (NOTIFICATION_CLASS.equals(typeName)) {
|
||||
List<UExpression> args = node.getValueArguments();
|
||||
if (args.size() == 3) {
|
||||
|
||||
@@ -41,6 +41,7 @@ import com.intellij.psi.PsiType;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.uast.UClass;
|
||||
import org.jetbrains.uast.UElement;
|
||||
import org.jetbrains.uast.UField;
|
||||
import org.jetbrains.uast.UVariable;
|
||||
import org.jetbrains.uast.visitor.AbstractUastVisitor;
|
||||
@@ -127,7 +128,7 @@ public class LeakDetector extends Detector implements Detector.UastScanner {
|
||||
if (isLeakCandidate(cls, mContext.getEvaluator())) {
|
||||
String message = "Do not place Android context classes in static fields; "
|
||||
+ "this is a memory leak (and also breaks Instant Run)";
|
||||
report(field, modifierList, message);
|
||||
report(field, message);
|
||||
}
|
||||
} else {
|
||||
// User application object -- look to see if that one itself has
|
||||
@@ -162,7 +163,7 @@ public class LeakDetector extends Detector implements Detector.UastScanner {
|
||||
+ "`" + referenced.getName() + "` pointing to `"
|
||||
+ innerCls.getName() + "`); "
|
||||
+ "this is a memory leak (and also breaks Instant Run)";
|
||||
report(field, modifierList, message);
|
||||
report(field, message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -170,11 +171,8 @@ public class LeakDetector extends Detector implements Detector.UastScanner {
|
||||
}
|
||||
}
|
||||
|
||||
private void report(@NonNull PsiField field, @NonNull PsiModifierList modifierList,
|
||||
@NonNull String message) {
|
||||
Location location = mContext.getLocation(
|
||||
modifierList.getTextRange().getLength() > 0 ? modifierList : field);
|
||||
mContext.report(ISSUE, field, location, message);
|
||||
private void report(@NonNull UElement element, @NonNull String message) {
|
||||
mContext.report(ISSUE, element, mContext.getUastLocation(element), message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,19 +37,7 @@ import com.intellij.psi.PsiNamedElement;
|
||||
import com.intellij.psi.PsiParameterList;
|
||||
import com.intellij.psi.PsiVariable;
|
||||
|
||||
import org.jetbrains.uast.UBinaryExpressionWithType;
|
||||
import org.jetbrains.uast.UCallExpression;
|
||||
import org.jetbrains.uast.UClass;
|
||||
import org.jetbrains.uast.UClassInitializer;
|
||||
import org.jetbrains.uast.UElement;
|
||||
import org.jetbrains.uast.UExpression;
|
||||
import org.jetbrains.uast.UField;
|
||||
import org.jetbrains.uast.UIfExpression;
|
||||
import org.jetbrains.uast.ULiteralExpression;
|
||||
import org.jetbrains.uast.UMethod;
|
||||
import org.jetbrains.uast.UQualifiedReferenceExpression;
|
||||
import org.jetbrains.uast.USimpleNameReferenceExpression;
|
||||
import org.jetbrains.uast.UastUtils;
|
||||
import org.jetbrains.uast.*;
|
||||
import org.jetbrains.uast.visitor.UastVisitor;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -165,7 +153,7 @@ public class LogDetector extends Detector implements Detector.UastScanner {
|
||||
String message = String.format("The log call Log.%1$s(...) should be " +
|
||||
"conditional: surround with `if (Log.isLoggable(...))` or " +
|
||||
"`if (BuildConfig.DEBUG) { ... }`",
|
||||
node.getMethodIdentifier());
|
||||
name);
|
||||
context.report(CONDITIONAL, node, context.getUastLocation(node), message);
|
||||
}
|
||||
|
||||
@@ -205,7 +193,7 @@ public class LogDetector extends Detector implements Detector.UastScanner {
|
||||
if (argument instanceof ULiteralExpression) {
|
||||
return false;
|
||||
}
|
||||
if (argument instanceof UBinaryExpressionWithType) {
|
||||
if (argument instanceof UBinaryExpression) {
|
||||
String string = UastUtils.evaluateString(argument);
|
||||
//noinspection VariableNotUsedInsideIf
|
||||
if (string != null) { // does it resolve to a constant?
|
||||
@@ -233,17 +221,21 @@ public class LogDetector extends Detector implements Detector.UastScanner {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private static boolean checkWithinConditional(
|
||||
@NonNull JavaContext context,
|
||||
@Nullable UElement curr,
|
||||
@NonNull UCallExpression logCall) {
|
||||
while (curr != null) {
|
||||
if (curr instanceof UIfExpression) {
|
||||
UIfExpression ifNode = (UIfExpression) curr;
|
||||
List<UExpression> chain = UastUtils.getQualifiedChain(ifNode.getCondition());
|
||||
if (!chain.isEmpty() && chain.get(chain.size() - 1) instanceof UCallExpression) {
|
||||
UCallExpression call = (UCallExpression) chain.get(chain.size() - 1);
|
||||
|
||||
UExpression condition = ((UIfExpression) curr).getCondition();
|
||||
if (condition instanceof UQualifiedReferenceExpression) {
|
||||
condition = getLastInQualifiedChain((UQualifiedReferenceExpression) condition);
|
||||
}
|
||||
|
||||
if (condition instanceof UCallExpression) {
|
||||
UCallExpression call = (UCallExpression) condition;
|
||||
if (IS_LOGGABLE.equals(call.getMethodName())) {
|
||||
checkTagConsistent(context, logCall, call);
|
||||
}
|
||||
@@ -251,10 +243,10 @@ public class LogDetector extends Detector implements Detector.UastScanner {
|
||||
|
||||
return true;
|
||||
} else if (curr instanceof UCallExpression
|
||||
|| curr instanceof UMethod
|
||||
|| curr instanceof UClassInitializer
|
||||
|| curr instanceof UField
|
||||
|| curr instanceof UClass) { // static block
|
||||
|| curr instanceof UMethod
|
||||
|| curr instanceof UClassInitializer
|
||||
|| curr instanceof UField
|
||||
|| curr instanceof UClass) { // static block
|
||||
break;
|
||||
}
|
||||
curr = curr.getContainingElement();
|
||||
@@ -283,7 +275,8 @@ public class LogDetector extends Detector implements Detector.UastScanner {
|
||||
}
|
||||
|
||||
if (logTag != null) {
|
||||
if (!UastLintUtils.areIdentifiersEqual(isLoggableTag, logTag)) {
|
||||
if (!areLiteralsEqual(isLoggableTag, logTag) &&
|
||||
!UastLintUtils.areIdentifiersEqual(isLoggableTag, logTag)) {
|
||||
PsiNamedElement resolved1 = UastUtils.tryResolveNamed(isLoggableTag);
|
||||
PsiNamedElement resolved2 = UastUtils.tryResolveNamed(logTag);
|
||||
if ((resolved1 == null || resolved2 == null || !resolved1.equals(resolved2))
|
||||
@@ -348,4 +341,32 @@ public class LogDetector extends Detector implements Detector.UastScanner {
|
||||
context.report(WRONG_TAG, isLoggableCall, location, message);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static UExpression getLastInQualifiedChain(@NonNull UQualifiedReferenceExpression node) {
|
||||
UExpression last = node.getSelector();
|
||||
while (last instanceof UQualifiedReferenceExpression) {
|
||||
last = ((UQualifiedReferenceExpression) last).getSelector();
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
private static boolean areLiteralsEqual(UExpression first, UExpression second) {
|
||||
if (!(first instanceof ULiteralExpression)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(second instanceof ULiteralExpression)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Object firstValue = ((ULiteralExpression) first).getValue();
|
||||
Object secondValue = ((ULiteralExpression) second).getValue();
|
||||
|
||||
if (firstValue == null) {
|
||||
return secondValue == null;
|
||||
}
|
||||
|
||||
return firstValue.equals(secondValue);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-13
@@ -39,16 +39,7 @@ import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiParameter;
|
||||
import com.intellij.psi.PsiVariable;
|
||||
|
||||
import org.jetbrains.uast.UAnonymousClass;
|
||||
import org.jetbrains.uast.UBinaryExpression;
|
||||
import org.jetbrains.uast.UCallExpression;
|
||||
import org.jetbrains.uast.UClass;
|
||||
import org.jetbrains.uast.UElement;
|
||||
import org.jetbrains.uast.UExpression;
|
||||
import org.jetbrains.uast.USimpleNameReferenceExpression;
|
||||
import org.jetbrains.uast.UVariable;
|
||||
import org.jetbrains.uast.UastBinaryOperator;
|
||||
import org.jetbrains.uast.UastUtils;
|
||||
import org.jetbrains.uast.*;
|
||||
import org.jetbrains.uast.expressions.UReferenceExpression;
|
||||
import org.jetbrains.uast.util.UastExpressionUtils;
|
||||
import org.jetbrains.uast.visitor.AbstractUastVisitor;
|
||||
@@ -128,14 +119,15 @@ public class RecyclerViewDetector extends Detector implements Detector.UastScann
|
||||
PsiParameter parameter = parameters[1];
|
||||
|
||||
ParameterEscapesVisitor visitor = new ParameterEscapesVisitor(context, cls, parameter);
|
||||
context.getUastContext().getMethodBody(declaration).accept(visitor);
|
||||
UMethod method = context.getUastContext().getMethod(declaration);
|
||||
method.accept(visitor);
|
||||
if (visitor.variableEscapes()) {
|
||||
reportError(context, viewHolder, parameter);
|
||||
}
|
||||
|
||||
// Look for pending data binder calls that aren't executed before the method finishes
|
||||
List<UCallExpression> dataBinderReferences = visitor.getDataBinders();
|
||||
checkDataBinders(context, declaration, dataBinderReferences);
|
||||
checkDataBinders(context, method, dataBinderReferences);
|
||||
}
|
||||
|
||||
private static void reportError(@NonNull JavaContext context, PsiParameter viewHolder,
|
||||
@@ -152,7 +144,7 @@ public class RecyclerViewDetector extends Detector implements Detector.UastScann
|
||||
}
|
||||
|
||||
private static void checkDataBinders(@NonNull JavaContext context,
|
||||
@NonNull PsiMethod declaration, List<UCallExpression> references) {
|
||||
@NonNull UMethod declaration, List<UCallExpression> references) {
|
||||
if (references != null && !references.isEmpty()) {
|
||||
List<UCallExpression> targets = Lists.newArrayList();
|
||||
List<UCallExpression> sources = Lists.newArrayList();
|
||||
|
||||
+10
-8
@@ -1162,12 +1162,12 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto
|
||||
boolean argWasReference = false;
|
||||
if (lastArg instanceof UReferenceExpression) {
|
||||
PsiElement resolved = ((UReferenceExpression) lastArg).resolve();
|
||||
|
||||
|
||||
if (resolved instanceof PsiVariable) {
|
||||
UExpression initializer = context.getUastContext().getInitializerBody (
|
||||
(PsiVariable) resolved);
|
||||
if (UastExpressionUtils.isConstructorCall(initializer)) {
|
||||
if (initializer != null &&
|
||||
(UastExpressionUtils.isNewArray(initializer) ||
|
||||
UastExpressionUtils.isNestedArrayInitializer(initializer))) {
|
||||
argWasReference = true;
|
||||
// Now handled by check below
|
||||
lastArg = initializer;
|
||||
@@ -1175,14 +1175,16 @@ public class StringFormatDetector extends ResourceXmlDetector implements Detecto
|
||||
}
|
||||
}
|
||||
|
||||
if (UastExpressionUtils.isNewArray(lastArg)) {
|
||||
UCallExpression callExpression = (UCallExpression) lastArg;
|
||||
if (UastExpressionUtils.isNewArray(lastArg) ||
|
||||
UastExpressionUtils.isNestedArrayInitializer(lastArg)) {
|
||||
UCallExpression arrayInitializer = (UCallExpression) lastArg;
|
||||
|
||||
if (UastExpressionUtils.isNewArrayWithInitializer(lastArg)) {
|
||||
callCount = callExpression.getValueArgumentCount();
|
||||
if (UastExpressionUtils.isNewArrayWithInitializer(lastArg) ||
|
||||
UastExpressionUtils.isNestedArrayInitializer(lastArg)) {
|
||||
callCount = arrayInitializer.getValueArgumentCount();
|
||||
knownArity = true;
|
||||
} else if (UastExpressionUtils.isNewArrayWithDimensions(lastArg)) {
|
||||
List<UExpression> arrayDimensions = callExpression.getValueArguments();
|
||||
List<UExpression> arrayDimensions = arrayInitializer.getValueArguments();
|
||||
if (arrayDimensions.size() == 1) {
|
||||
UExpression first = arrayDimensions.get(0);
|
||||
if (first instanceof ULiteralExpression) {
|
||||
|
||||
+95
-76
@@ -46,6 +46,7 @@ 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;
|
||||
import static org.jetbrains.uast.UastUtils.getQualifiedParentOrThis;
|
||||
|
||||
import com.android.annotations.NonNull;
|
||||
import com.android.annotations.Nullable;
|
||||
@@ -54,6 +55,7 @@ import com.android.sdklib.AndroidVersion;
|
||||
import com.android.tools.klint.checks.PermissionFinder.Operation;
|
||||
import com.android.tools.klint.checks.PermissionFinder.Result;
|
||||
import com.android.tools.klint.checks.PermissionHolder.SetPermissionLookup;
|
||||
import com.android.tools.klint.client.api.ExternalReferenceExpression;
|
||||
import com.android.tools.klint.client.api.JavaEvaluator;
|
||||
import com.android.tools.klint.client.api.LintClient;
|
||||
import com.android.tools.klint.client.api.UastLintUtils;
|
||||
@@ -91,26 +93,7 @@ import com.intellij.psi.PsiReference;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.PsiVariable;
|
||||
|
||||
import org.jetbrains.uast.UAnonymousClass;
|
||||
import org.jetbrains.uast.UBinaryExpression;
|
||||
import org.jetbrains.uast.UBinaryExpressionWithType;
|
||||
import org.jetbrains.uast.UCallExpression;
|
||||
import org.jetbrains.uast.UCatchClause;
|
||||
import org.jetbrains.uast.UElement;
|
||||
import org.jetbrains.uast.UEnumConstant;
|
||||
import org.jetbrains.uast.UExpression;
|
||||
import org.jetbrains.uast.UField;
|
||||
import org.jetbrains.uast.UIfExpression;
|
||||
import org.jetbrains.uast.ULiteralExpression;
|
||||
import org.jetbrains.uast.UMethod;
|
||||
import org.jetbrains.uast.UParenthesizedExpression;
|
||||
import org.jetbrains.uast.UPrefixExpression;
|
||||
import org.jetbrains.uast.UTryExpression;
|
||||
import org.jetbrains.uast.UVariable;
|
||||
import org.jetbrains.uast.UastBinaryOperator;
|
||||
import org.jetbrains.uast.UastOperator;
|
||||
import org.jetbrains.uast.UastPrefixOperator;
|
||||
import org.jetbrains.uast.UastUtils;
|
||||
import org.jetbrains.uast.*;
|
||||
import org.jetbrains.uast.expressions.UReferenceExpression;
|
||||
import org.jetbrains.uast.util.UastExpressionUtils;
|
||||
import org.jetbrains.uast.visitor.AbstractUastVisitor;
|
||||
@@ -289,6 +272,8 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
|
||||
public static final String ATTR_ANY_OF = "anyOf";
|
||||
public static final String ATTR_CONDITIONAL = "conditional";
|
||||
|
||||
public static final String SECURITY_EXCEPTION = "java.lang.SecurityException";
|
||||
|
||||
/**
|
||||
* Constructs a new {@link SupportAnnotationDetector} check
|
||||
*/
|
||||
@@ -557,8 +542,8 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
|
||||
if (tryCatch == null) {
|
||||
break;
|
||||
} else {
|
||||
for (UCatchClause psiCatchSection : tryCatch.getCatchClauses()) {
|
||||
if (isSecurityException(psiCatchSection.getTypes())) {
|
||||
for (UCatchClause catchClause : tryCatch.getCatchClauses()) {
|
||||
if (containsSecurityException(catchClause.getTypes())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -572,7 +557,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
|
||||
UMethod declaration = UastUtils.getParentOfType(parent, UMethod.class, false);
|
||||
if (declaration != null) {
|
||||
PsiClassType[] thrownTypes = declaration.getThrowsList().getReferencedTypes();
|
||||
if (isSecurityException(Arrays.<PsiType>asList(thrownTypes))) {
|
||||
if (containsSecurityException(Arrays.asList(thrownTypes))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -685,8 +670,8 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isSecurityException(
|
||||
@NonNull List<PsiType> types) {
|
||||
private static boolean containsSecurityException(
|
||||
@NonNull List<? extends PsiType> types) {
|
||||
for (PsiType type : types) {
|
||||
if (type instanceof PsiClassType) {
|
||||
PsiClass cls = ((PsiClassType) type).resolve();
|
||||
@@ -694,7 +679,9 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
|
||||
// any super type as well, however that probably hides warnings in cases where
|
||||
// users don't want that; see http://b.android.com/182165
|
||||
//return context.getEvaluator().extendsClass(cls, "java.lang.SecurityException", false);
|
||||
return cls != null && "java.lang.SecurityException".equals(cls.getQualifiedName());
|
||||
if (cls != null && SECURITY_EXCEPTION.equals(cls.getQualifiedName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -773,7 +760,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) {
|
||||
if (context.getUastContext().isExpressionValueUsed(UastUtils.getQualifiedParentOrThis(node))) {
|
||||
if (isExpressionValueUnused(node)) {
|
||||
String methodName = JavaContext.getMethodName(node);
|
||||
String suggested = getAnnotationStringValue(annotation, ATTR_SUGGEST);
|
||||
|
||||
@@ -809,6 +796,11 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isExpressionValueUnused(UExpression expression) {
|
||||
return getQualifiedParentOrThis(expression).getContainingElement()
|
||||
instanceof UBlockExpression;
|
||||
}
|
||||
|
||||
private static void checkThreading(
|
||||
@NonNull JavaContext context,
|
||||
@NonNull UElement node,
|
||||
@@ -1150,7 +1142,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
|
||||
*/
|
||||
public static boolean typeArrayFromArrayLiteral(
|
||||
@Nullable UElement node, @NonNull JavaContext context) {
|
||||
if (UastExpressionUtils.isMethodCall(node)) {
|
||||
if (isMethodCall(node)) {
|
||||
UCallExpression expression = (UCallExpression) node;
|
||||
assert expression != null;
|
||||
String name = expression.getMethodName();
|
||||
@@ -1213,6 +1205,24 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isMethodCall(UElement node) {
|
||||
if (node instanceof UQualifiedReferenceExpression) {
|
||||
UExpression last = getLastInQualifiedChain((UQualifiedReferenceExpression) node);
|
||||
return UastExpressionUtils.isMethodCall(last);
|
||||
}
|
||||
|
||||
return UastExpressionUtils.isMethodCall(node);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static UExpression getLastInQualifiedChain(@NonNull UQualifiedReferenceExpression node) {
|
||||
UExpression last = node.getSelector();
|
||||
while (last instanceof UQualifiedReferenceExpression) {
|
||||
last = ((UQualifiedReferenceExpression) last).getSelector();
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
private static void checkIntRange(
|
||||
@NonNull JavaContext context,
|
||||
@NonNull PsiAnnotation annotation,
|
||||
@@ -1630,6 +1640,12 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
|
||||
if (value.equals(((PsiLiteral)expression).getValue())) {
|
||||
return;
|
||||
}
|
||||
} else if (expression instanceof ExternalReferenceExpression) {
|
||||
PsiElement resolved = UastLintUtils.resolve(
|
||||
(ExternalReferenceExpression) expression, argument);
|
||||
if (resolved != null && resolved.equals(value)) {
|
||||
return;
|
||||
}
|
||||
} else if (expression instanceof PsiReference) {
|
||||
PsiElement resolved = ((PsiReference) expression).resolve();
|
||||
if (resolved != null && resolved.equals(value)) {
|
||||
@@ -1677,7 +1693,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
|
||||
return;
|
||||
}
|
||||
|
||||
String values = listAllowedValues(allowedValues);
|
||||
String values = listAllowedValues(node, allowedValues);
|
||||
String message;
|
||||
if (flag) {
|
||||
message = "Must be one or more of: " + values;
|
||||
@@ -1709,22 +1725,29 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String listAllowedValues(@NonNull PsiAnnotationMemberValue[] allowedValues) {
|
||||
private static String listAllowedValues(@NonNull UElement context,
|
||||
@NonNull PsiAnnotationMemberValue[] allowedValues) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (PsiAnnotationMemberValue allowedValue : allowedValues) {
|
||||
String s = null;
|
||||
if (allowedValue instanceof PsiReference) {
|
||||
PsiElement resolved = ((PsiReference) allowedValue).resolve();
|
||||
if (resolved instanceof PsiField) {
|
||||
PsiField field = (PsiField) resolved;
|
||||
String containingClassName = field.getContainingClass() != null
|
||||
? field.getContainingClass().getName() : null;
|
||||
if (containingClassName == null) {
|
||||
continue;
|
||||
}
|
||||
s = containingClassName + "." + field.getName();
|
||||
}
|
||||
PsiElement resolved = null;
|
||||
if (allowedValue instanceof ExternalReferenceExpression) {
|
||||
resolved = UastLintUtils.resolve(
|
||||
(ExternalReferenceExpression) allowedValue, context);
|
||||
} else if (allowedValue instanceof PsiReference) {
|
||||
resolved = ((PsiReference) allowedValue).resolve();
|
||||
}
|
||||
|
||||
if (resolved instanceof PsiField) {
|
||||
PsiField field = (PsiField) resolved;
|
||||
String containingClassName = field.getContainingClass() != null
|
||||
? field.getContainingClass().getName() : null;
|
||||
if (containingClassName == null) {
|
||||
continue;
|
||||
}
|
||||
s = containingClassName + "." + field.getName();
|
||||
}
|
||||
|
||||
if (s == null) {
|
||||
s = allowedValue.getText();
|
||||
}
|
||||
@@ -1768,7 +1791,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
|
||||
|
||||
@NonNull
|
||||
static PsiAnnotation[] filterRelevantAnnotations(
|
||||
@NonNull PsiAnnotation[] annotations) {
|
||||
@NonNull JavaEvaluator evaluator, @NonNull PsiAnnotation[] annotations) {
|
||||
List<PsiAnnotation> result = null;
|
||||
int length = annotations.length;
|
||||
if (length == 0) {
|
||||
@@ -1812,33 +1835,31 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
|
||||
continue;
|
||||
}
|
||||
PsiClass cls = (PsiClass)resolved;
|
||||
PsiModifierList modifierList = cls.getModifierList();
|
||||
if (modifierList != null) {
|
||||
PsiAnnotation[] innerAnnotations = modifierList.getAnnotations();
|
||||
for (int j = 0; j < innerAnnotations.length; j++) {
|
||||
PsiAnnotation inner = innerAnnotations[j];
|
||||
String a = inner.getQualifiedName();
|
||||
if (a == null) {
|
||||
continue;
|
||||
PsiAnnotation[] innerAnnotations = evaluator.getAllAnnotations(cls);
|
||||
for (int j = 0; j < innerAnnotations.length; j++) {
|
||||
PsiAnnotation inner = innerAnnotations[j];
|
||||
String a = inner.getQualifiedName();
|
||||
if (a == null || a.startsWith("java.")) {
|
||||
// @Override, @SuppressWarnings etc. Ignore
|
||||
continue;
|
||||
}
|
||||
if (a.equals(INT_DEF_ANNOTATION)
|
||||
|| a.equals(PERMISSION_ANNOTATION)
|
||||
|| a.equals(INT_RANGE_ANNOTATION)
|
||||
|| a.equals(STRING_DEF_ANNOTATION)) {
|
||||
if (length == 1 && j == innerAnnotations.length - 1 && result == null) {
|
||||
return innerAnnotations;
|
||||
}
|
||||
if (a.equals(INT_DEF_ANNOTATION)
|
||||
|| a.equals(PERMISSION_ANNOTATION)
|
||||
|| a.equals(INT_RANGE_ANNOTATION)
|
||||
|| a.equals(STRING_DEF_ANNOTATION)) {
|
||||
if (length == 1 && j == innerAnnotations.length - 1) {
|
||||
return innerAnnotations;
|
||||
}
|
||||
if (result == null) {
|
||||
result = new ArrayList<PsiAnnotation>(2);
|
||||
}
|
||||
result.add(inner);
|
||||
if (result == null) {
|
||||
result = new ArrayList<PsiAnnotation>(2);
|
||||
}
|
||||
result.add(inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result != null
|
||||
? result.toArray(PsiAnnotation.EMPTY_ARRAY) : PsiAnnotation.EMPTY_ARRAY;
|
||||
? result.toArray(PsiAnnotation.EMPTY_ARRAY) : PsiAnnotation.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
// ---- Implements UastScanner ----
|
||||
@@ -1849,9 +1870,7 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
|
||||
public List<Class<? extends UElement>> getApplicableUastTypes() {
|
||||
List<Class<? extends UElement>> types = new ArrayList<Class<? extends UElement>>(3);
|
||||
types.add(UCallExpression.class);
|
||||
//types.add(PsiMethodCallExpression.class);
|
||||
//types.add(PsiNewExpression.class);
|
||||
types.add(UField.class);
|
||||
types.add(UVariable.class);
|
||||
return types;
|
||||
}
|
||||
|
||||
@@ -1886,33 +1905,33 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
|
||||
}
|
||||
return super.visitVariable(node);
|
||||
}
|
||||
|
||||
|
||||
public void checkCall(PsiMethod method, UCallExpression call) {
|
||||
JavaEvaluator evaluator = mContext.getEvaluator();
|
||||
|
||||
PsiAnnotation[] methodAnnotations = evaluator.getAllAnnotations(method, true);
|
||||
methodAnnotations = filterRelevantAnnotations(methodAnnotations);
|
||||
PsiAnnotation[] methodAnnotations = evaluator.getAllAnnotations(method);
|
||||
methodAnnotations = filterRelevantAnnotations(evaluator, methodAnnotations);
|
||||
|
||||
// Look for annotations on the class as well: these trickle
|
||||
// down to all the methods in the class
|
||||
PsiClass containingClass = method.getContainingClass();
|
||||
PsiAnnotation[] classAnnotations;
|
||||
if (containingClass != null) {
|
||||
classAnnotations = evaluator.getAllAnnotations(containingClass, true);
|
||||
classAnnotations = filterRelevantAnnotations(classAnnotations);
|
||||
classAnnotations = evaluator.getAllAnnotations(containingClass);
|
||||
classAnnotations = filterRelevantAnnotations(evaluator, classAnnotations);
|
||||
} else {
|
||||
classAnnotations = PsiAnnotation.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
for (PsiAnnotation annotation : methodAnnotations) {
|
||||
checkMethodAnnotation(mContext, method, call, annotation, methodAnnotations,
|
||||
classAnnotations);
|
||||
classAnnotations);
|
||||
}
|
||||
|
||||
if (classAnnotations.length > 0) {
|
||||
for (PsiAnnotation annotation : classAnnotations) {
|
||||
checkMethodAnnotation(mContext, method, call, annotation, methodAnnotations,
|
||||
classAnnotations);
|
||||
classAnnotations);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1921,12 +1940,12 @@ public class SupportAnnotationDetector extends Detector implements Detector.Uast
|
||||
PsiParameter[] parameters = parameterList.getParameters();
|
||||
PsiAnnotation[] annotations = null;
|
||||
for (int i = 0, n = Math.min(parameters.length, arguments.size());
|
||||
i < n;
|
||||
i++) {
|
||||
i < n;
|
||||
i++) {
|
||||
UExpression argument = arguments.get(i);
|
||||
PsiParameter parameter = parameters[i];
|
||||
annotations = evaluator.getAllAnnotations(parameter, true);
|
||||
annotations = filterRelevantAnnotations(annotations);
|
||||
annotations = evaluator.getAllAnnotations(parameter);
|
||||
annotations = filterRelevantAnnotations(evaluator, annotations);
|
||||
checkParameterAnnotations(mContext, argument, call, method, annotations);
|
||||
}
|
||||
if (annotations != null) {
|
||||
|
||||
+1
-1
@@ -183,7 +183,7 @@ public class IdeaJavaParser extends JavaParser {
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public PsiAnnotation[] getAllAnnotations(@NonNull PsiModifierListOwner owner, boolean inHierarchy) {
|
||||
public PsiAnnotation[] getAllAnnotations(@NonNull PsiModifierListOwner owner) {
|
||||
return AnnotationUtil.getAllAnnotations(owner, inHierarchy, null, true);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user