blob: 9cf3afd95dc6b7f65b39cfde8fd362fb8a11e053 [file] [log] [blame]
/*
* 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();
//noinspection ConstantConditions
if (property != null && isInterestingProperty(property, parentName, parentParentName)
&& argumentList != null) {
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));
}
}