| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * 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.plugins.groovy.annotator; |
| |
| import com.intellij.lang.ASTNode; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.colors.TextAttributesKey; |
| import com.intellij.openapi.project.DumbService; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.psi.*; |
| import com.intellij.psi.search.PsiElementProcessor; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.util.CachedValueProvider; |
| import com.intellij.psi.util.CachedValuesManager; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.util.containers.ContainerUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.plugins.groovy.highlighter.DefaultHighlighter; |
| import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; |
| import org.jetbrains.plugins.groovy.lang.lexer.TokenSets; |
| import org.jetbrains.plugins.groovy.lang.psi.GrReferenceElement; |
| import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierList; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrClassInitializer; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrLabeledStatement; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression; |
| 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.GrUnaryExpression; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrExtendsClause; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrImplementsClause; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrAccessorMethod; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMember; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass; |
| import org.jetbrains.plugins.groovy.lang.psi.util.GroovyPropertyUtils; |
| import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; |
| import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil; |
| |
| import java.util.Set; |
| |
| /** |
| * @author Max Medvedev |
| */ |
| public class GrHighlightUtil { |
| private static final Logger LOG = Logger.getInstance(GrHighlightUtil.class); |
| |
| private static Set<String> getReassignedNames(final PsiElement scope) { |
| return CachedValuesManager.getCachedValue(scope, new CachedValueProvider<Set<String>>() { |
| @Nullable |
| @Override |
| public Result<Set<String>> compute() { |
| return Result.create(collectReassignedNames(scope), scope); |
| } |
| }); |
| } |
| |
| private static Set<String> collectReassignedNames(PsiElement scope) { |
| final Set<String> result = ContainerUtil.newHashSet(); |
| PsiTreeUtil.processElements(scope, new PsiElementProcessor() { |
| @Override |
| public boolean execute(@NotNull PsiElement element) { |
| if (!(element instanceof GrReferenceExpression) || !((GrReferenceExpression)element).isQualified()) { |
| return true; |
| } |
| |
| GrReferenceExpression ref = (GrReferenceExpression)element; |
| if (isWriteAccess(ref)) { |
| String varName = ref.getReferenceName(); |
| if (!result.contains(varName)) { |
| PsiElement target = ref.resolve(); |
| if (target instanceof GrVariable && ((GrVariable)target).getInitializerGroovy() != null || |
| target instanceof GrParameter) { |
| result.add(varName); |
| } |
| } |
| } |
| return true; |
| } |
| }); |
| return result; |
| } |
| |
| private static boolean isWriteAccess(GrReferenceExpression element) { |
| return PsiUtil.isLValue(element) || |
| element.getParent() instanceof GrUnaryExpression && ((GrUnaryExpression)element.getParent()).isPostfix(); |
| } |
| |
| static boolean isReassigned(final GrVariable var) { |
| LOG.assertTrue(!DumbService.getInstance(var.getProject()).isDumb()); |
| |
| PsiMethod method = PsiTreeUtil.getParentOfType(var, PsiMethod.class); |
| PsiNamedElement scope = method == null ? var.getContainingFile() : method; |
| return scope != null && getReassignedNames(scope).contains(var.getName()); |
| } |
| |
| /** |
| * |
| * @param resolved declaration element |
| * @param refElement reference to highlight. if null, 'resolved' is highlighted and no resolve is allowed. |
| * @return |
| */ |
| @Nullable |
| static TextAttributesKey getDeclarationHighlightingAttribute(PsiElement resolved, @Nullable PsiElement refElement) { |
| if (refElement != null && isReferenceWithLiteralName(refElement)) return null; //don't highlight literal references |
| |
| if (resolved instanceof PsiField || resolved instanceof GrVariable && ResolveUtil.isScriptField((GrVariable)resolved)) { |
| boolean isStatic = ((PsiVariable)resolved).hasModifierProperty(PsiModifier.STATIC); |
| return isStatic ? DefaultHighlighter.STATIC_FIELD : DefaultHighlighter.INSTANCE_FIELD; |
| } |
| else if (resolved instanceof GrAccessorMethod) { |
| boolean isStatic = ((GrAccessorMethod)resolved).hasModifierProperty(PsiModifier.STATIC); |
| return isStatic ? DefaultHighlighter.STATIC_PROPERTY_REFERENCE : DefaultHighlighter.INSTANCE_PROPERTY_REFERENCE; |
| } |
| else if (resolved instanceof PsiMethod) { |
| |
| if (isMethodWithLiteralName((PsiMethod)resolved)) return null; //don't highlight method with literal name |
| |
| if (((PsiMethod)resolved).isConstructor()) { |
| if (refElement != null) { |
| if (refElement.getNode().getElementType() == GroovyTokenTypes.kTHIS || //don't highlight this() or super() |
| refElement.getNode().getElementType() == GroovyTokenTypes.kSUPER) { |
| return null; |
| } |
| else { |
| return DefaultHighlighter.CONSTRUCTOR_CALL; |
| } |
| } |
| else { |
| return DefaultHighlighter.CONSTRUCTOR_DECLARATION; |
| } |
| } |
| else { |
| boolean isStatic = ((PsiMethod)resolved).hasModifierProperty(PsiModifier.STATIC); |
| if (GroovyPropertyUtils.isSimplePropertyAccessor((PsiMethod)resolved)) { |
| return isStatic ? DefaultHighlighter.STATIC_PROPERTY_REFERENCE : DefaultHighlighter.INSTANCE_PROPERTY_REFERENCE; |
| } |
| else { |
| if (refElement != null) { |
| return isStatic ? DefaultHighlighter.STATIC_METHOD_ACCESS : DefaultHighlighter.METHOD_CALL; |
| } |
| else { |
| return DefaultHighlighter.METHOD_DECLARATION; |
| } |
| } |
| } |
| } |
| else if (resolved instanceof PsiTypeParameter) { |
| return DefaultHighlighter.TYPE_PARAMETER; |
| } |
| else if (resolved instanceof PsiClass) { |
| if (((PsiClass)resolved).isAnnotationType()) { |
| return DefaultHighlighter.ANNOTATION; |
| } |
| else { |
| return DefaultHighlighter.CLASS_REFERENCE; |
| } |
| } |
| else if (resolved instanceof GrParameter) { |
| boolean reassigned = isReassigned((GrParameter)resolved); |
| return reassigned ? DefaultHighlighter.REASSIGNED_PARAMETER : DefaultHighlighter.PARAMETER; |
| } |
| else if (resolved instanceof GrVariable) { |
| boolean reassigned = isReassigned((GrVariable)resolved); |
| return reassigned ? DefaultHighlighter.REASSIGNED_LOCAL_VARIABLE : DefaultHighlighter.LOCAL_VARIABLE; |
| } |
| else if (resolved instanceof GrLabeledStatement) { |
| return DefaultHighlighter.LABEL; |
| } |
| return null; |
| } |
| |
| private static boolean isMethodWithLiteralName(@Nullable PsiMethod method) { |
| if (method instanceof GrMethod) { |
| final PsiElement nameIdentifier = ((GrMethod)method).getNameIdentifierGroovy(); |
| if (isStringNameElement(nameIdentifier)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static boolean isReferenceWithLiteralName(@Nullable PsiElement ref) { |
| if (ref instanceof GrReferenceExpression) { |
| final PsiElement nameIdentifier = ((GrReferenceExpression)ref).getReferenceNameElement(); |
| if (nameIdentifier != null && isStringNameElement(nameIdentifier)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static boolean isStringNameElement(@NotNull PsiElement nameIdentifier) { |
| final ASTNode node = nameIdentifier.getNode(); |
| if (node == null) return false; |
| |
| final IElementType nameElementType = node.getElementType(); |
| return TokenSets.STRING_LITERAL_SET.contains(nameElementType); |
| } |
| |
| public static boolean isDeclarationAssignment(GrReferenceExpression refExpr) { |
| return isAssignmentLhs(refExpr) && isScriptPropertyAccess(refExpr); |
| } |
| |
| private static boolean isAssignmentLhs(GrReferenceExpression refExpr) { |
| return refExpr.getParent() instanceof GrAssignmentExpression && |
| refExpr.equals(((GrAssignmentExpression)refExpr.getParent()).getLValue()); |
| } |
| |
| private static boolean isScriptPropertyAccess(GrReferenceExpression refExpr) { |
| final GrExpression qualifier = refExpr.getQualifierExpression(); |
| if (qualifier == null) { |
| final PsiClass clazz = PsiTreeUtil.getParentOfType(refExpr, PsiClass.class); |
| if (clazz == null) { //script |
| return true; |
| } |
| return false; //in class, a property should normally be defined, so it's not a declaration |
| } |
| |
| final PsiType type = qualifier.getType(); |
| if (type instanceof PsiClassType && |
| !(qualifier instanceof GrReferenceExpression && ((GrReferenceExpression)qualifier).resolve() instanceof GroovyScriptClass)) { |
| final PsiClassType classType = (PsiClassType)type; |
| final PsiClass psiClass = classType.resolve(); |
| if (psiClass instanceof GroovyScriptClass) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public static TextRange getMethodHeaderTextRange(PsiMethod method) { |
| final PsiModifierList modifierList = method.getModifierList(); |
| final PsiParameterList parameterList = method.getParameterList(); |
| |
| final TextRange textRange = modifierList.getTextRange(); |
| LOG.assertTrue(textRange != null, method.getClass() + ":" + method.getText()); |
| int startOffset = textRange.getStartOffset(); |
| int endOffset = parameterList.getTextRange().getEndOffset() + 1; |
| |
| return new TextRange(startOffset, endOffset); |
| } |
| |
| @NotNull |
| public static PsiElement getElementToHighlight(@NotNull GrReferenceElement refElement) { |
| final PsiElement refNameElement = refElement.getReferenceNameElement(); |
| return refNameElement != null ? refNameElement : refElement; |
| } |
| |
| public static TextRange getClassHeaderTextRange(GrTypeDefinition clazz) { |
| final GrModifierList modifierList = clazz.getModifierList(); |
| final int startOffset = modifierList != null ? modifierList.getTextOffset() : clazz.getTextOffset(); |
| final GrImplementsClause implementsClause = clazz.getImplementsClause(); |
| |
| final int endOffset; |
| if (implementsClause != null) { |
| endOffset = implementsClause.getTextRange().getEndOffset(); |
| } |
| else { |
| final GrExtendsClause extendsClause = clazz.getExtendsClause(); |
| if (extendsClause != null) { |
| endOffset = extendsClause.getTextRange().getEndOffset(); |
| } |
| else { |
| endOffset = clazz.getNameIdentifierGroovy().getTextRange().getEndOffset(); |
| } |
| } |
| return new TextRange(startOffset, endOffset); |
| } |
| |
| public static TextRange getInitializerHeaderTextRange(GrClassInitializer initializer) { |
| final PsiModifierList modifierList = initializer.getModifierList(); |
| final GrOpenBlock block = initializer.getBlock(); |
| |
| final TextRange textRange = modifierList.getTextRange(); |
| LOG.assertTrue(textRange != null, initializer.getClass() + ":" + initializer.getText()); |
| int startOffset = textRange.getStartOffset(); |
| int endOffset = block.getLBrace().getTextRange().getEndOffset() + 1; |
| |
| return new TextRange(startOffset, endOffset); |
| |
| } |
| |
| @Nullable |
| public static GrMember findClassMemberContainer(@NotNull GrReferenceExpression ref, @NotNull PsiClass aClass) { |
| for (PsiElement parent = ref.getParent(); parent != null && parent != aClass; parent = parent.getParent()) { |
| if (parent instanceof GrMember && ((GrMember)parent).getContainingClass() == aClass) { |
| return (GrMember)parent; |
| } |
| } |
| return null; |
| } |
| } |