public void visitBuildScript()

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);
          }
        });
      }
    });
  }