/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jetbrains.android.inspections.lint; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.tools.lint.checks.GradleDetector; import com.android.tools.lint.detector.api.*; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; import org.jetbrains.plugins.groovy.lang.psi.GroovyRecursiveElementVisitor; import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument; import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrCallExpression; import java.util.List; import java.util.Map; import static com.android.SdkConstants.ATTR_MIN_SDK_VERSION; import static com.android.SdkConstants.ATTR_TARGET_SDK_VERSION; public class IntellijGradleDetector extends GradleDetector { static final Implementation IMPLEMENTATION = new Implementation( IntellijGradleDetector.class, Scope.GRADLE_SCOPE); @Nullable protected String getClosureName(@NonNull GrClosableBlock closure) { if (closure.getParent() instanceof GrMethodCall) { GrMethodCall parent = (GrMethodCall)closure.getParent(); if (parent.getInvokedExpression() instanceof GrReferenceExpression) { GrReferenceExpression invokedExpression = (GrReferenceExpression)(parent.getInvokedExpression()); if (invokedExpression.getDotToken() == null) { return invokedExpression.getReferenceName(); } } } return null; } @Override public void visitBuildScript(@NonNull final Context context, Map<String, Object> sharedData) { ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { final PsiFile psiFile = IntellijLintUtils.getPsiFile(context); if (!(psiFile instanceof GroovyFile)) { return; } GroovyFile groovyFile = (GroovyFile)psiFile; groovyFile.accept(new GroovyRecursiveElementVisitor() { @Override public void visitClosure(GrClosableBlock closure) { String parentName = getClosureName(closure); String parentParentName = null; if (parentName != null) { GrClosableBlock block = PsiTreeUtil.getParentOfType(closure, GrClosableBlock.class, true); if (block != null) { parentParentName = getClosureName(block); } } if (parentName != null && isInterestingBlock(parentName, parentParentName)) { for (PsiElement element : closure.getChildren()) { if (element instanceof GrApplicationStatement) { GrApplicationStatement call = (GrApplicationStatement)element; GrExpression propertyExpression = call.getInvokedExpression(); GrCommandArgumentList argumentList = call.getArgumentList(); if (propertyExpression instanceof GrReferenceExpression) { GrReferenceExpression propertyRef = (GrReferenceExpression)propertyExpression; String property = propertyRef.getReferenceName(); if (property != null && isInterestingProperty(property, parentName, parentParentName)) { String value = argumentList.getText(); checkDslPropertyAssignment(context, property, value, parentName, parentParentName, argumentList, call); } } } else if (element instanceof GrAssignmentExpression) { GrAssignmentExpression assignment = (GrAssignmentExpression)element; GrExpression lValue = assignment.getLValue(); if (lValue instanceof GrReferenceExpression) { GrReferenceExpression propertyRef = (GrReferenceExpression)lValue; String property = propertyRef.getReferenceName(); if (property != null && isInterestingProperty(property, parentName, parentParentName)) { GrExpression rValue = assignment.getRValue(); if (rValue != null) { String value = rValue.getText(); checkDslPropertyAssignment(context, property, value, parentName, parentParentName, 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 (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(GrApplicationStatement applicationStatement) { GrClosableBlock block = PsiTreeUtil.getParentOfType(applicationStatement, GrClosableBlock.class, true); String parentName = block != null ? getClosureName(block) : null; String statementName = applicationStatement.getInvokedExpression().getText(); if (isInterestingStatement(statementName, parentName)) { GrCommandArgumentList argumentList = applicationStatement.getArgumentList(); Map<String, String> namedArguments = Maps.newHashMap(); List<String> unnamedArguments = Lists.newArrayList(); for (GroovyPsiElement groovyPsiElement : argumentList.getAllArguments()) { if (groovyPsiElement instanceof GrNamedArgument) { GrNamedArgument namedArgument = (GrNamedArgument)groovyPsiElement; GrExpression expression = namedArgument.getExpression(); if (expression == null || !(expression instanceof GrLiteral)) { continue; } Object value = ((GrLiteral)expression).getValue(); if (value == null) { continue; } namedArguments.put(namedArgument.getLabelName(), value.toString()); } else if (groovyPsiElement instanceof GrExpression) { unnamedArguments.add(groovyPsiElement.getText()); } } checkMethodCall(context, statementName, parentName, namedArguments, unnamedArguments, applicationStatement); } super.visitApplicationStatement(applicationStatement); } }); } }); } @Override protected int getStartOffset(@NonNull Context context, @NonNull Object cookie) { PsiElement element = (PsiElement)cookie; TextRange textRange = element.getTextRange(); return textRange.getStartOffset(); } @NonNull @Override protected Object getPropertyPairCookie(@NonNull Object cookie) { PsiElement element = (PsiElement)cookie; return element.getParent(); } @NonNull @Override protected Object getPropertyKeyCookie(@NonNull Object cookie) { PsiElement element = (PsiElement)cookie; PsiElement parent = element.getParent(); if (parent instanceof GrApplicationStatement) { GrApplicationStatement call = (GrApplicationStatement)parent; return call.getInvokedExpression(); } else if (parent instanceof GrAssignmentExpression) { GrAssignmentExpression assignment = (GrAssignmentExpression)parent; return assignment.getLValue(); } return super.getPropertyKeyCookie(cookie); } @Override protected Location createLocation(@NonNull Context context, @NonNull Object cookie) { PsiElement element = (PsiElement)cookie; TextRange textRange = element.getTextRange(); int start = textRange.getStartOffset(); int end = textRange.getEndOffset(); return Location.create(context.file, new DefaultPosition(-1, -1, start), new DefaultPosition(-1, -1, end)); } }