blob: 020970d7699661cd85a12a724a86e6a2baa4f580 [file] [log] [blame]
/*
* Copyright (C) 2015 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.idea.gradle.editor.parser;
import com.intellij.lang.ASTNode;
import com.intellij.lang.LanguageParserDefinitions;
import com.intellij.lang.ParserDefinition;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.GroovyLanguage;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementVisitor;
import org.jetbrains.plugins.groovy.lang.psi.GroovyRecursiveElementVisitor;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrString;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrStringContent;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrStringInjection;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression;
/**
* Allows to fill {@link GradleEditorModelParseContext target context's} cached
* {@link GradleEditorModelParseContext#addCachedValue(GradleEditorModelParseContext.Value) value} and/or
* {@link GradleEditorModelParseContext#addCachedVariable(GradleEditorModelParseContext.Variable, TextRange) variable} by the data
* from the target PSI element.
*/
public class GradleEditorValueExtractor extends GroovyRecursiveElementVisitor {
@NotNull private final GradleEditorModelParseContext myContext;
@NotNull private final PsiElementVisitor myVisitor;
private boolean myInterestedInReferencesOnly;
public GradleEditorValueExtractor(@NotNull GradleEditorModelParseContext context) {
myContext = context;
myVisitor = new GroovyPsiElementVisitor(this);
}
@Nullable
public static Pair<String, TextRange> extractMethodName(@NotNull PsiElement methodCall) {
GrReferenceExpression methodNameRef = PsiTreeUtil.findChildOfType(methodCall, GrReferenceExpression.class);
if (methodNameRef == null || methodNameRef.getParent() != methodCall) {
return null;
}
String methodName = methodNameRef.getReferenceName();
if (methodName == null) {
return null;
}
return Pair.create(methodName, methodNameRef.getTextRange());
}
public void extractValueOrVariable(@NotNull PsiElement element) {
element.accept(myVisitor);
}
@Override
public void visitLiteralExpression(GrLiteral literal) {
if (myInterestedInReferencesOnly) {
return;
}
Object value = literal.getValue();
if (value != null) {
String stringValue = String.valueOf(value);
int i = literal.getText().indexOf(stringValue);
if (i >= 0) {
TextRange range = TextRange.create(literal.getTextOffset() + i, literal.getTextOffset() + i + stringValue.length());
myContext.addCachedValue(stringValue, range);
}
}
}
@Override
public void visitGStringExpression(GrString expression) {
GroovyPsiElement[] parts = expression.getAllContentParts();
boolean registeredAssignment = false;
for (GroovyPsiElement part : parts) {
if (part instanceof GrStringContent) {
if (!myInterestedInReferencesOnly && !registeredAssignment && !part.getTextRange().isEmpty()) {
registeredAssignment = true;
String text = expression.getText();
TextRange range = expression.getTextRange();
if (text.startsWith("'") || text.startsWith("\"")) {
text = text.substring(1);
range = TextRange.create(range.getStartOffset() + 1, range.getEndOffset());
}
if (text.endsWith("'") || text.endsWith("\"")) {
text = text.substring(0, text.length() - 1);
range = TextRange.create(range.getStartOffset(), range.getEndOffset() - 1);
}
myContext.addCachedValue(text, range);
}
}
else if (part instanceof GrStringInjection) {
GrExpression injectedExpression = ((GrStringInjection)part).getExpression();
if (injectedExpression != null) {
injectedExpression.accept(myVisitor);
}
}
}
}
@Override
public void visitReferenceExpression(GrReferenceExpression expression) {
if (expression.getParent() instanceof GrMethodCallExpression) {
// This is a reference expression which points to the method name. We're not interested in it, so, just return.
return;
}
GrExpression qualifier = expression.getQualifierExpression();
if (qualifier == null) {
myContext.addCachedVariable(expression.getText(), expression.getTextRange());
return;
}
myContext.rememberVariableQualifier(qualifier.getText());
PsiElement dotToken = expression.getDotToken();
if (dotToken == null) {
return;
}
ParserDefinition parserDefinition = LanguageParserDefinitions.INSTANCE.findSingle(GroovyLanguage.INSTANCE);
for (PsiElement e = dotToken.getNextSibling(); e != null; e = e.getNextSibling()) {
ASTNode node = e.getNode();
if (node == null) {
if (e instanceof PsiWhiteSpace) {
continue;
}
e.accept(myVisitor);
return;
}
IElementType type = node.getElementType();
if (type == GroovyTokenTypes.mIDENT) {
myContext.addCachedVariable(e.getText(), e.getTextRange());
}
else if (parserDefinition.getWhitespaceTokens().contains(type) || parserDefinition.getCommentTokens().contains(type)) {
continue;
}
else {
e.accept(myVisitor);
}
return;
}
}
@Override
public void visitElement(GroovyPsiElement element) {
if (!myInterestedInReferencesOnly) {
// Consider every expression over than those we explicitly prepared for (e.g. literal expression, reference expression etc)
// to be registered as a definition source binding within the context. However, we want to correctly handle a situation like
// a method call, e.g. 'System.getenv("VAR")' - here the whole method call expression is given to this method first and
// we register a variable at the context for it. However, later on expression '("VAR")' is given to this method - we don't
// want to register is because it's already registered for the outer scope.
myInterestedInReferencesOnly = true;
myContext.addCachedValue("", element.getTextRange());
}
super.visitElement(element);
}
}