in lint/src/com/android/tools/idea/lint/common/LintIdeGradleVisitor.java [176:477]
public void visitBuildScript(@NotNull GradleContext context, @NotNull List<? extends GradleScanner> detectors) {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
final PsiFile psiFile = LintIdeUtilsKt.getPsiFile(context);
if (!(psiFile instanceof GroovyFile)) {
return;
}
GroovyFile groovyFile = (GroovyFile)psiFile;
groovyFile.accept(new GroovyRecursiveElementVisitor() {
private @Nullable GrClosableBlock getParentClosure(PsiElement element) {
return PsiTreeUtil.getParentOfType(element, GrClosableBlock.class, true);
}
@Override
public void visitVariable(@NotNull GrVariable variable) {
String name = variable.getName();
GrExpression initial = variable.getInitializerGroovy();
PsiIdentifier identifier = variable.getNameIdentifier();
if (initial != null && identifier != null) {
String parentName = "";
String parentParentName = null;
GrClosableBlock closure = getParentClosure(variable);
if (closure != null) {
List<String> names = getClosureNames(closure);
int size = names.size();
if (size >= 1) {
parentName = names.get(0);
if (size >= 2) {
parentParentName = names.get(1);
}
}
}
String value = initial.getText();
for (GradleScanner detector : detectors) {
detector.checkDslPropertyAssignment(context, name, value, parentName,
parentParentName, identifier, initial, variable);
}
}
super.visitVariable(variable);
}
@Override
public void visitMethodCall(@NotNull GrMethodCall call) {
GroovyMethodCallReference reference = call.getExplicitCallReference();
if (reference != null) {
String methodName = reference.getMethodName();
// Produce the parent and grand parent names.
// When you have something like this:
// android {
// defaultConfig {
// compileOptions.java.level 1.7
// ...
// this is equivalent to android.defaultConfig.compileOptions.java.level.
// In our case, we're at the method call for "level", so methodName is level,
// and we want the two closes ancestors; parent="java" and
// parentParent="compileOptions".
//
// To do this we'll look at the method call; if it has a receiver which is a fully
// qualified name (here it's "compileOptions.java.level", so we can fetch both
// parents directly from the qualified name. If it was shorter, we may need to
// also look at the surrounding closures to get the closure names (e.g. "android"
// and "defaultConfig" above).
// We then grab up to two elements from these.
String qualifier = null;
PsiElement firstChild = call.getFirstChild();
if (firstChild instanceof GrReferenceExpression) {
String s = ((GrReferenceExpression)firstChild).getQualifiedReferenceName();
if (s != null) {
int index = s.lastIndexOf('.');
if (index != -1) {
qualifier = s.substring(0, index);
}
}
else {
// if we don't understand our operator, don't proceed.
super.visitMethodCall(call);
return;
}
}
String parent = null;
String parentParent = null;
if (qualifier != null) {
int index = qualifier.lastIndexOf('.');
if (index != -1) {
parent = qualifier.substring(index + 1);
parentParent = qualifier.substring(0, index);
index = parentParent.lastIndexOf('.');
if (index != -1) {
parentParent = parentParent.substring(index + 1);
}
} else {
parent = qualifier;
}
}
if (parentParent == null) {
// Didn't get both parent and parentParent from qualified name in call; look at surrounding closures
// for one or two names.
GrClosableBlock closure = getParentClosure(call);
if (closure != null) {
List<String> names = getClosureNames(closure);
int size = names.size();
if (parent != null) {
if (size >= 1) {
parentParent = names.get(0);
}
} else if (size >= 1) {
parent = names.get(0);
if (size >= 2) {
parentParent = names.get(1);
}
}
}
}
Map<String, String> namedArguments = Maps.newHashMap();
List<String> unnamedArguments = new ArrayList<>();
extractMethodCallArguments(call, unnamedArguments, namedArguments);
// PSI Groovy isn't treating the closure as a parameter like it is in Kotlin
PsiElement child = call.getLastChild();
if (child instanceof GrClosableBlock) {
unnamedArguments.add(child.getText());
}
for (GradleScanner detector : detectors) {
detector.checkMethodCall(context, methodName, parent, parentParent, namedArguments,
unnamedArguments, call);
}
}
super.visitMethodCall(call);
}
private void handleApplicationOrMethodCallInClosure(List<String> closureNames, PsiElement element) {
GrReferenceExpression propertyRef;
GroovyPsiElement valueCookie;
String value;
if (element instanceof GrApplicationStatement) {
GrApplicationStatement call = (GrApplicationStatement)element;
GrExpression propertyExpression = call.getInvokedExpression();
if (!(propertyExpression instanceof GrReferenceExpression)) return;
propertyRef = (GrReferenceExpression)propertyExpression;
GrCommandArgumentList argumentList = call.getArgumentList();
//noinspection ConstantConditions
if (argumentList == null) return;
valueCookie = argumentList;
value = argumentList.getText();
}
else if (element instanceof GrMethodCallExpression) {
GrMethodCallExpression call = (GrMethodCallExpression)element;
GrExpression propertyExpression = call.getInvokedExpression();
if (!(propertyExpression instanceof GrReferenceExpression)) return;
propertyRef = (GrReferenceExpression)propertyExpression;
GrExpression[] argumentList = call.getArgumentList().getExpressionArguments();
if (argumentList.length != 1) return;
valueCookie = argumentList[0];
value = argumentList[0].getText();
}
else {
return;
}
List<String> names = getReferenceExpressionNamesOrNull(propertyRef);
if (names == null || propertyRef.getQualifierExpression() instanceof GrCallExpression) {
handleApplicationOrMethodCallInClosure(closureNames, propertyRef.getQualifierExpression());
}
if (names == null) return;
names.addAll(closureNames);
String property = names.get(0);
String parentName = names.size() > 1 ? names.get(1) : null;
String parentParentName = names.size() > 2 ? names.get(2) : null;
if (property != null && parentName != null) {
for (GradleScanner detector : detectors) {
detector.checkDslPropertyAssignment(context, property, value, parentName, parentParentName, propertyRef, valueCookie,
element);
}
}
}
@Override
public void visitClosure(@NotNull GrClosableBlock closure) {
List<String> closureNames = getClosureNames(closure);
if (!closureNames.isEmpty()) {
for (PsiElement element : closure.getChildren()) {
if (element instanceof GrApplicationStatement || element instanceof GrMethodCallExpression) {
handleApplicationOrMethodCallInClosure(closureNames, element);
}
else if (element instanceof GrAssignmentExpression) {
GrAssignmentExpression assignment = (GrAssignmentExpression)element;
GrExpression lValue = assignment.getLValue();
if (lValue instanceof GrReferenceExpression) {
GrReferenceExpression propertyRef = (GrReferenceExpression)lValue;
List<String> names = getReferenceExpressionNames(propertyRef);
names.addAll(closureNames);
String property = names.get(0);
String parentName = names.size() > 1 ? names.get(1) : null;
String parentParentName = names.size() > 2 ? names.get(2) : null;
if (property != null && parentName != null) {
GrExpression rValue = assignment.getRValue();
if (rValue != null) {
String value = rValue.getText();
for (GradleScanner detector : detectors) {
detector
.checkDslPropertyAssignment(context, property, value, parentName, parentParentName, lValue, rValue, assignment);
}
// As of 0.11 you can't use assignment for these two properties. This is handled here rather
// than up in GradleDetector for a couple of reasons: The project won't compile with that
// error, so gradle from the command line won't get invoked. Second, we want to do some unusual
// things with the positions here (map between two nodes), and the property abstraction we
// pass to GradleDetector doesn't distinguish between assignments and DSL method calls, so just
// handle it here.
if (!parentName.equals("ext") &&
(property.equals(ATTR_MIN_SDK_VERSION) || property.equals(ATTR_TARGET_SDK_VERSION))) {
int lValueEnd = lValue.getTextRange().getEndOffset();
int rValueStart = rValue.getTextRange().getStartOffset();
assert lValueEnd <= rValueStart;
DefaultPosition startPosition = new DefaultPosition(-1, -1, lValueEnd);
DefaultPosition endPosition = new DefaultPosition(-1, -1, rValueStart);
Location location = Location.create(context.file, startPosition, endPosition);
String message = String.format("Do not use assignment with the %1$s property (remove the '=')", property);
context.report(GradleDetector.IDE_SUPPORT, location, message, null);
}
}
}
}
}
}
}
super.visitClosure(closure);
}
@Override
public void visitApplicationStatement(@NotNull GrApplicationStatement applicationStatement) {
GrClosableBlock block = PsiTreeUtil.getParentOfType(applicationStatement, GrClosableBlock.class, true);
List<String> parentNames = block != null ? getClosureNames(block) : new ArrayList<>(0);
Map<String, String> namedArguments = Maps.newHashMap();
List<String> unnamedArguments = new ArrayList<>();
extractMethodCallArguments(applicationStatement, unnamedArguments, namedArguments);
if (parentNames.isEmpty() && unnamedArguments.size() == 1 && namedArguments.isEmpty()) {
// This might be a top-level application statement for Dsl property assignment with embedded hierarchy
GrExpression invokedExpression = applicationStatement.getInvokedExpression();
if (invokedExpression instanceof GrReferenceExpression) {
GrReferenceExpression referenceExpression = (GrReferenceExpression) invokedExpression;
List<String> names = getReferenceExpressionNames(referenceExpression);
String name = !names.isEmpty() ? names.get(0) : null;
String parentName = names.size() > 1 ? names.get(1) : ""; // empty-string parent convention for top-level properties
String parentParentName = names.size() > 2 ? names.get(2) : null;
if (name != null) {
String value = unnamedArguments.get(0);
GrCommandArgumentList argumentList = applicationStatement.getArgumentList();
for (GradleScanner detector : detectors) {
detector.checkDslPropertyAssignment(context, name, value, parentName, parentParentName,
invokedExpression, argumentList, applicationStatement);
}
}
}
} else if (parentNames.isEmpty() && namedArguments.size() == 1 && unnamedArguments.isEmpty()) {
GrExpression invokedExpression = applicationStatement.getInvokedExpression();
if (invokedExpression instanceof GrReferenceExpression) {
GrReferenceExpression referenceExpression = (GrReferenceExpression) invokedExpression;
List<String> names = getReferenceExpressionNames(referenceExpression);
if (names.size() == 1 && names.get(0).equals("apply")) {
String relative = namedArguments.get("from");
addIncludedScript(context, relative);
}
}
}
super.visitApplicationStatement(applicationStatement);
}
@Override
public void visitAssignmentExpression(@NotNull GrAssignmentExpression expression) {
GrClosableBlock block = PsiTreeUtil.getParentOfType(expression, GrClosableBlock.class, true);
// if block is not null, we will handle assignments in visitClosure()
if (block == null) {
GrExpression lvalue = expression.getLValue();
if (lvalue instanceof GrReferenceExpression) {
GrReferenceExpression lvalueRef = (GrReferenceExpression) lvalue;
List<String> names = getReferenceExpressionNames(lvalueRef);
String name = !names.isEmpty() ? names.get(0) : null;
String parentName = names.size() > 1 ? names.get(1) : ""; // empty-string parent convention for top-level properties
String parentParentName = names.size() > 2 ? names.get(2) : null;
GrExpression rvalue = expression.getRValue();
if (name != null && rvalue != null) {
String value = rvalue.getText();
for (GradleScanner detector : detectors) {
detector.checkDslPropertyAssignment(context, name, value, parentName,
parentParentName, lvalue, rvalue, expression);
}
}
}
}
super.visitAssignmentExpression(expression);
}
});
}
});
}