blob: a0e51171f9d0e5e99ed8a364e1abf9fb72e117b4 [file] [log] [blame]
/*
* 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.codeInsight.ClassUtil;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightClassUtil;
import com.intellij.codeInsight.generation.OverrideImplementExploreUtil;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.codeInsight.intention.QuickFixFactory;
import com.intellij.codeInspection.InspectionManager;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.lang.ASTNode;
import com.intellij.lang.annotation.Annotation;
import com.intellij.lang.annotation.AnnotationHolder;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.ExpressionConverter;
import com.intellij.psi.impl.light.LightElement;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.SuperMethodsSearch;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.*;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.VisibilityUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.MultiMap;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.GroovyBundle;
import org.jetbrains.plugins.groovy.GroovyLanguage;
import org.jetbrains.plugins.groovy.annotator.checkers.AnnotationChecker;
import org.jetbrains.plugins.groovy.annotator.checkers.CustomAnnotationChecker;
import org.jetbrains.plugins.groovy.annotator.intentions.*;
import org.jetbrains.plugins.groovy.codeInspection.bugs.GrModifierFix;
import org.jetbrains.plugins.groovy.config.GroovyConfigUtils;
import org.jetbrains.plugins.groovy.findUsages.LiteralConstructorReference;
import org.jetbrains.plugins.groovy.highlighter.DefaultHighlighter;
import org.jetbrains.plugins.groovy.lang.documentation.GroovyPresentationUtil;
import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocComment;
import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocReferenceElement;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.lexer.TokenSets;
import org.jetbrains.plugins.groovy.lang.psi.*;
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifier;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierList;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentLabel;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
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.blocks.GrOpenBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrBreakStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrContinueStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrFlowInterruptingStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrReturnStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrForInClause;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrIndexProperty;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.*;
import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.packaging.GrPackageDefinition;
import org.jetbrains.plugins.groovy.lang.psi.api.types.*;
import org.jetbrains.plugins.groovy.lang.psi.dataFlow.types.TypeInferenceHelper;
import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.auxiliary.modifiers.GrAnnotationCollector;
import org.jetbrains.plugins.groovy.lang.psi.impl.signatures.GrClosureSignatureUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass;
import org.jetbrains.plugins.groovy.lang.psi.util.GrStringUtil;
import org.jetbrains.plugins.groovy.lang.psi.util.GrTraitUtil;
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 org.jetbrains.plugins.groovy.lang.resolve.ast.GrInheritConstructorContributor;
import java.util.*;
/**
* @author ven
*/
@SuppressWarnings({"unchecked"})
public class GroovyAnnotator extends GroovyElementVisitor {
private static final Logger LOG = Logger.getInstance("org.jetbrains.plugins.groovy.annotator.GroovyAnnotator");
public static final Condition<PsiClass> IS_INTERFACE = new Condition<PsiClass>() {
@Override
public boolean value(PsiClass aClass) {
return aClass.isInterface();
}
};
private static final Condition<PsiClass> IS_NOT_INTERFACE = new Condition<PsiClass>() {
@Override
public boolean value(PsiClass aClass) {
return !aClass.isInterface();
}
};
public static final Condition<PsiClass> IS_TRAIT = new Condition<PsiClass>() {
@Override
public boolean value(PsiClass aClass) {
return GrTraitUtil.isTrait(aClass);
}
};
private final AnnotationHolder myHolder;
public GroovyAnnotator(@NotNull AnnotationHolder holder) {
myHolder = holder;
}
@Override
public void visitTypeArgumentList(GrTypeArgumentList typeArgumentList) {
PsiElement parent = typeArgumentList.getParent();
if (!(parent instanceof GrReferenceElement)) return;
final GroovyResolveResult resolveResult = ((GrReferenceElement)parent).advancedResolve();
final PsiElement resolved = resolveResult.getElement();
final PsiSubstitutor substitutor = resolveResult.getSubstitutor();
if (resolved == null) return;
if (!(resolved instanceof PsiTypeParameterListOwner)) {
myHolder.createWarningAnnotation(typeArgumentList, GroovyBundle.message("type.argument.list.is.not.allowed.here"));
return;
}
if (parent instanceof GrCodeReferenceElement) {
if (!checkDiamonds((GrCodeReferenceElement)parent, myHolder)) return;
}
final PsiTypeParameter[] parameters = ((PsiTypeParameterListOwner)resolved).getTypeParameters();
final GrTypeElement[] arguments = typeArgumentList.getTypeArgumentElements();
if (arguments.length != parameters.length) {
myHolder.createWarningAnnotation(typeArgumentList,
GroovyBundle.message("wrong.number.of.type.arguments", arguments.length, parameters.length));
return;
}
for (int i = 0; i < parameters.length; i++) {
PsiTypeParameter parameter = parameters[i];
final PsiClassType[] superTypes = parameter.getExtendsListTypes();
final PsiType argType = arguments[i].getType();
for (PsiClassType superType : superTypes) {
final PsiType substitutedSuper = substitutor.substitute(superType);
if (substitutedSuper != null && !substitutedSuper.isAssignableFrom(argType)) {
myHolder.createWarningAnnotation(arguments[i], GroovyBundle
.message("type.argument.0.is.not.in.its.bound.should.extend.1", argType.getCanonicalText(), superType.getCanonicalText()));
break;
}
}
}
}
@Override
public void visitNamedArgument(GrNamedArgument argument) {
PsiElement parent = argument.getParent();
if (parent instanceof GrArgumentList) {
final PsiElement pparent = parent.getParent();
if (pparent instanceof GrIndexProperty) {
myHolder.createErrorAnnotation(argument, GroovyBundle.message("named.arguments.are.not.allowed.inside.index.operations"));
}
}
}
@Override
public void visitApplicationStatement(GrApplicationStatement applicationStatement) {
super.visitApplicationStatement(applicationStatement);
checkForCommandExpressionSyntax(applicationStatement);
}
@Override
public void visitMethodCallExpression(GrMethodCallExpression methodCallExpression) {
super.visitMethodCallExpression(methodCallExpression);
checkForCommandExpressionSyntax(methodCallExpression);
}
private void checkForCommandExpressionSyntax(GrMethodCall methodCall) {
final GroovyConfigUtils groovyConfig = GroovyConfigUtils.getInstance();
if (methodCall.isCommandExpression() && !groovyConfig.isVersionAtLeast(methodCall, GroovyConfigUtils.GROOVY1_8)) {
myHolder
.createErrorAnnotation(methodCall, GroovyBundle.message("is.not.supported.in.version", groovyConfig.getSDKVersion(methodCall)));
}
}
@Override
public void visitElement(GroovyPsiElement element) {
if (element.getParent() instanceof GrDocReferenceElement) {
checkGrDocReferenceElement(myHolder, element);
}
}
@Override
public void visitTryStatement(GrTryCatchStatement statement) {
final GrCatchClause[] clauses = statement.getCatchClauses();
List<PsiType> usedExceptions = new ArrayList<PsiType>();
for (GrCatchClause clause : clauses) {
final GrParameter parameter = clause.getParameter();
if (parameter == null) continue;
final GrTypeElement typeElement = parameter.getTypeElementGroovy();
PsiType type = typeElement != null ? typeElement.getType() : TypesUtil.createType(CommonClassNames.JAVA_LANG_EXCEPTION, statement);
if (typeElement instanceof GrDisjunctionTypeElement) {
final GrTypeElement[] elements = ((GrDisjunctionTypeElement)typeElement).getTypeElements();
PsiType[] types = PsiType.createArray(elements.length);
for (int i = 0; i < elements.length; i++) {
types[i] = elements[i].getType();
}
List<PsiType> usedInsideDisjunction = new ArrayList<PsiType>();
for (int i = 0; i < types.length; i++) {
if (checkExceptionUsed(usedExceptions, parameter, elements[i], types[i])) {
usedInsideDisjunction.add(types[i]);
for (int j = 0; j < types.length; j++) {
if (i != j && types[j].isAssignableFrom(types[i])) {
myHolder.createWarningAnnotation(elements[i], GroovyBundle.message("unnecessary.type", types[i].getCanonicalText(),
types[j].getCanonicalText()))
.registerFix(new GrRemoveExceptionFix(true));
}
}
}
}
usedExceptions.addAll(usedInsideDisjunction);
}
else {
if (checkExceptionUsed(usedExceptions, parameter, typeElement, type)) {
usedExceptions.add(type);
}
}
}
}
@Override
public void visitCatchClause(GrCatchClause clause) {
final GrParameter parameter = clause.getParameter();
if (parameter == null) return;
final GrTypeElement typeElement = parameter.getTypeElementGroovy();
if (typeElement != null) {
final PsiType type = typeElement.getType();
if (type instanceof PsiClassType && ((PsiClassType)type).resolve() == null) return; //don't highlight unresolved types
final PsiClassType throwable = TypesUtil.createType(CommonClassNames.JAVA_LANG_THROWABLE, clause);
if (!throwable.isAssignableFrom(type)) {
myHolder.createErrorAnnotation(typeElement, GroovyBundle.message("catch.statement.parameter.type.should.be.a.subclass.of.throwable"));
}
}
}
@Override
public void visitDocComment(GrDocComment comment) {
String text = comment.getText();
if (!text.endsWith("*/")) {
TextRange range = comment.getTextRange();
myHolder.createErrorAnnotation(new TextRange(range.getEndOffset() - 1, range.getEndOffset()), GroovyBundle.message("doc.end.expected"));
}
}
@Override
public void visitVariableDeclaration(GrVariableDeclaration variableDeclaration) {
if (variableDeclaration.isTuple()) {
final GrModifierList list = variableDeclaration.getModifierList();
final PsiElement last = PsiUtil.skipWhitespacesAndComments(list.getLastChild(), false);
if (last != null) {
final IElementType type = last.getNode().getElementType();
if (type != GroovyTokenTypes.kDEF) {
myHolder.createErrorAnnotation(list, GroovyBundle.message("tuple.declaration.should.end.with.def.modifier"));
}
}
else {
myHolder.createErrorAnnotation(list, GroovyBundle.message("tuple.declaration.should.end.with.def.modifier"));
}
}
}
private boolean checkExceptionUsed(List<PsiType> usedExceptions, GrParameter parameter, GrTypeElement typeElement, PsiType type) {
for (PsiType exception : usedExceptions) {
if (exception.isAssignableFrom(type)) {
myHolder.createWarningAnnotation(typeElement != null ? typeElement : parameter.getNameIdentifierGroovy(),
GroovyBundle.message("exception.0.has.already.been.caught", type.getCanonicalText()))
.registerFix(new GrRemoveExceptionFix(parameter.getTypeElementGroovy() instanceof GrDisjunctionTypeElement));
return false;
}
}
return true;
}
@Override
public void visitReferenceExpression(final GrReferenceExpression referenceExpression) {
checkStringNameIdentifier(referenceExpression);
checkThisOrSuperReferenceExpression(referenceExpression, myHolder);
checkFinalFieldAccess(referenceExpression);
checkFinalParameterAccess(referenceExpression);
if (ResolveUtil.isKeyOfMap(referenceExpression)) {
PsiElement nameElement = referenceExpression.getReferenceNameElement();
LOG.assertTrue(nameElement != null);
myHolder.createInfoAnnotation(nameElement, null).setTextAttributes(DefaultHighlighter.MAP_KEY);
}
else if (ResolveUtil.isClassReference(referenceExpression)) {
PsiElement nameElement = referenceExpression.getReferenceNameElement();
LOG.assertTrue(nameElement != null);
myHolder.createInfoAnnotation(nameElement, null).setTextAttributes(DefaultHighlighter.KEYWORD);
}
}
private void checkFinalParameterAccess(GrReferenceExpression ref) {
final PsiElement resolved = ref.resolve();
if (resolved instanceof GrParameter) {
final GrParameter parameter = (GrParameter)resolved;
if (parameter.isPhysical() && parameter.hasModifierProperty(PsiModifier.FINAL) && PsiUtil.isLValue(ref)) {
if (parameter.getDeclarationScope() instanceof PsiMethod) {
myHolder.createErrorAnnotation(ref, GroovyBundle.message("cannot.assign.a.value.to.final.parameter.0", parameter.getName()));
}
}
}
}
private void checkFinalFieldAccess(@NotNull GrReferenceExpression ref) {
final PsiElement resolved = ref.resolve();
if (resolved instanceof GrField && resolved.isPhysical() && ((GrField)resolved).hasModifierProperty(PsiModifier.FINAL) && PsiUtil.isLValue(ref)) {
final GrField field = (GrField)resolved;
final PsiClass containingClass = field.getContainingClass();
if (containingClass != null && PsiTreeUtil.isAncestor(containingClass, ref, true)) {
GrMember container = GrHighlightUtil.findClassMemberContainer(ref, containingClass);
if (field.hasModifierProperty(PsiModifier.STATIC)) {
if (container instanceof GrClassInitializer && ((GrClassInitializer)container).isStatic()) {
return;
}
}
else {
if (container instanceof GrMethod && ((GrMethod)container).isConstructor() ||
container instanceof GrClassInitializer && !((GrClassInitializer)container).isStatic()) {
return;
}
}
myHolder.createErrorAnnotation(ref, GroovyBundle.message("cannot.assign.a.value.to.final.field.0", field.getName()));
}
}
}
private void checkStringNameIdentifier(GrReferenceExpression ref) {
final PsiElement nameElement = ref.getReferenceNameElement();
if (nameElement == null) return;
final IElementType elementType = nameElement.getNode().getElementType();
if (elementType == GroovyTokenTypes.mSTRING_LITERAL || elementType == GroovyTokenTypes.mGSTRING_LITERAL) {
checkStringLiteral(nameElement);
}
else if (elementType == GroovyTokenTypes.mREGEX_LITERAL || elementType == GroovyTokenTypes.mDOLLAR_SLASH_REGEX_LITERAL) {
checkRegexLiteral(nameElement);
}
}
@Override
public void visitTypeDefinitionBody(GrTypeDefinitionBody typeDefinitionBody) {
final PsiElement parent = typeDefinitionBody.getParent();
if (parent instanceof GrAnonymousClassDefinition) {
final PsiElement prev = typeDefinitionBody.getPrevSibling();
if (PsiUtil.isLineFeed(prev)) {
myHolder.createErrorAnnotation(typeDefinitionBody, GroovyBundle.message("ambiguous.code.block"));
}
}
}
@Override
public void visitTypeDefinition(GrTypeDefinition typeDefinition) {
final PsiElement parent = typeDefinition.getParent();
if (!(typeDefinition.isAnonymous() ||
parent instanceof GrTypeDefinitionBody ||
parent instanceof GroovyFile ||
typeDefinition instanceof GrTypeParameter)) {
final TextRange range = GrHighlightUtil.getClassHeaderTextRange(typeDefinition);
final Annotation errorAnnotation =
myHolder.createErrorAnnotation(range, GroovyBundle.message("class.definition.is.not.expected.here"));
errorAnnotation.registerFix(new GrMoveClassToCorrectPlaceFix(typeDefinition));
}
checkTypeDefinition(myHolder, typeDefinition);
checkDuplicateMethod(typeDefinition, myHolder);
checkImplementedMethodsOfClass(myHolder, typeDefinition);
checkConstructors(myHolder, typeDefinition);
checkAnnotationCollector(myHolder, typeDefinition);
checkSameNameMethodsWithDifferentAccessModifiers(myHolder, typeDefinition.getCodeMethods());
}
private static void checkSameNameMethodsWithDifferentAccessModifiers(AnnotationHolder holder, GrMethod[] methods) {
MultiMap<String, GrMethod> map = MultiMap.create();
for (GrMethod method : methods) {
if (!method.isConstructor()) {
map.putValue(method.getName(), method);
}
}
for (Map.Entry<String, Collection<GrMethod>> entry : map.entrySet()) {
Collection<GrMethod> collection = entry.getValue();
if (collection.size() > 1 && !sameAccessModifier(collection)) {
for (GrMethod method : collection) {
holder.createErrorAnnotation(GrHighlightUtil.getMethodHeaderTextRange(method), GroovyBundle.message("mixing.private.and.public.protected.methods.of.the.same.name"));
}
}
}
}
private static boolean sameAccessModifier(Collection<GrMethod> collection) {
Iterator<GrMethod> iterator = collection.iterator();
GrMethod method = iterator.next();
boolean privateAccess = PsiModifier.PRIVATE.equals(VisibilityUtil.getVisibilityModifier(method.getModifierList()));
while (iterator.hasNext()) {
GrMethod next = iterator.next();
if (privateAccess != PsiModifier.PRIVATE.equals(VisibilityUtil.getVisibilityModifier(next.getModifierList()))) {
return false;
}
}
return true;
}
private static void checkAnnotationCollector(AnnotationHolder holder, GrTypeDefinition definition) {
if (definition.isAnnotationType() &&
GrAnnotationCollector.findAnnotationCollector(definition) != null &&
definition.getCodeMethods().length > 0) {
holder.createErrorAnnotation(definition.getNameIdentifierGroovy(), GroovyBundle.message("annotation.collector.cannot.have.attributes"));
}
}
private static void checkConstructors(AnnotationHolder holder, GrTypeDefinition typeDefinition) {
if (typeDefinition.isEnum() || typeDefinition.isInterface() || typeDefinition.isAnonymous() || typeDefinition instanceof GrTypeParameter) return;
final PsiClass superClass = typeDefinition.getSuperClass();
if (superClass == null) return;
if (GrInheritConstructorContributor.hasInheritConstructorsAnnotation(typeDefinition)) return;
PsiMethod defConstructor = getDefaultConstructor(superClass);
boolean hasImplicitDefConstructor = superClass.getConstructors().length == 0;
final PsiMethod[] constructors = typeDefinition.getCodeConstructors();
final String qName = superClass.getQualifiedName();
if (constructors.length == 0) {
if (!hasImplicitDefConstructor && (defConstructor == null || !PsiUtil.isAccessible(typeDefinition, defConstructor))) {
final TextRange range = GrHighlightUtil.getClassHeaderTextRange(typeDefinition);
holder.createErrorAnnotation(range, GroovyBundle.message("there.is.no.default.constructor.available.in.class.0", qName))
.registerFix(QuickFixFactory.getInstance().createCreateConstructorMatchingSuperFix(typeDefinition));
}
return;
}
for (PsiMethod method : constructors) {
if (method instanceof GrMethod) {
final GrOpenBlock block = ((GrMethod)method).getBlock();
if (block == null) continue;
final GrStatement[] statements = block.getStatements();
if (statements.length > 0) {
if (statements[0] instanceof GrConstructorInvocation) continue;
}
if (!hasImplicitDefConstructor && (defConstructor == null || !PsiUtil.isAccessible(typeDefinition, defConstructor))) {
holder.createErrorAnnotation(GrHighlightUtil.getMethodHeaderTextRange(method),
GroovyBundle.message("there.is.no.default.constructor.available.in.class.0", qName));
}
}
}
checkRecursiveConstructors(holder, constructors);
}
@Override
public void visitEnumConstant(GrEnumConstant enumConstant) {
super.visitEnumConstant(enumConstant);
final GrArgumentList argumentList = enumConstant.getArgumentList();
if (argumentList != null && PsiImplUtil.hasNamedArguments(argumentList) && !PsiImplUtil.hasExpressionArguments(argumentList)) {
final PsiMethod constructor = enumConstant.resolveConstructor();
if (constructor != null) {
if (!PsiUtil.isConstructorHasRequiredParameters(constructor)) {
myHolder.createErrorAnnotation(argumentList, GroovyBundle
.message("the.usage.of.a.map.entry.expression.to.initialize.an.enum.is.currently.not.supported"));
}
}
}
}
private static void checkRecursiveConstructors(AnnotationHolder holder, PsiMethod[] constructors) {
Map<PsiMethod, PsiMethod> nodes = new HashMap<PsiMethod, PsiMethod>(constructors.length);
Set<PsiMethod> set = ContainerUtil.set(constructors);
for (PsiMethod constructor : constructors) {
if (!(constructor instanceof GrMethod)) continue;
final GrOpenBlock block = ((GrMethod)constructor).getBlock();
if (block == null) continue;
final GrStatement[] statements = block.getStatements();
if (statements.length <= 0 || !(statements[0] instanceof GrConstructorInvocation)) continue;
final PsiMethod resolved = ((GrConstructorInvocation)statements[0]).resolveMethod();
if (!set.contains(resolved)) continue;
nodes.put(constructor, resolved);
}
Set<PsiMethod> checked = new HashSet<PsiMethod>();
Set<PsiMethod> current;
for (PsiMethod constructor : constructors) {
if (!checked.add(constructor)) continue;
current = new HashSet<PsiMethod>();
current.add(constructor);
for (constructor = nodes.get(constructor); constructor != null && current.add(constructor); constructor = nodes.get(constructor)) {
checked.add(constructor);
}
if (constructor != null) {
PsiMethod circleStart = constructor;
do {
holder.createErrorAnnotation(GrHighlightUtil.getMethodHeaderTextRange(constructor),
GroovyBundle.message("recursive.constructor.invocation"));
constructor = nodes.get(constructor);
}
while (constructor != circleStart);
}
}
}
@Override
public void visitUnaryExpression(GrUnaryExpression expression) {
if (expression.getOperationTokenType() == GroovyTokenTypes.mINC ||
expression.getOperationTokenType() == GroovyTokenTypes.mDEC) {
GrExpression operand = expression.getOperand();
if (operand instanceof GrReferenceExpression && ((GrReferenceExpression)operand).getQualifier() == null) {
GrTraitTypeDefinition trait = PsiTreeUtil.getParentOfType(operand, GrTraitTypeDefinition.class);
if (trait != null) {
PsiElement resolved = ((GrReferenceExpression)operand).resolve();
if (resolved instanceof GrField && ((GrField)resolved).getContainingClass() instanceof GrTraitTypeDefinition) {
myHolder.createErrorAnnotation(expression, GroovyBundle
.message("0.expressions.on.trait.fields.properties.are.not.supported.in.traits", expression.getOperationToken().getText()));
}
}
}
}
}
@Override
public void visitOpenBlock(GrOpenBlock block) {
if (block.getParent() instanceof GrMethod) {
final GrMethod method = (GrMethod)block.getParent();
if (method.getModifierList().hasExplicitModifier(PsiModifier.ABSTRACT) || GrTraitUtil.isInterface(method.getContainingClass())) {
final Annotation annotation = myHolder.createErrorAnnotation(block, GroovyBundle.message("abstract.methods.must.not.have.body"));
registerMakeAbstractMethodNotAbstractFix(annotation, method, true);
}
}
}
@Override
public void visitMethod(GrMethod method) {
checkMethodWithTypeParamsShouldHaveReturnType(myHolder, method);
checkInnerMethod(myHolder, method);
checkOptionalParametersInAbstractMethod(myHolder, method);
checkConstructorOfImmutableClass(myHolder, method);
checkGetterOfImmutable(myHolder, method);
final PsiElement nameIdentifier = method.getNameIdentifierGroovy();
if (nameIdentifier.getNode().getElementType() == GroovyTokenTypes.mSTRING_LITERAL) {
checkStringLiteral(nameIdentifier);
}
GrOpenBlock block = method.getBlock();
if (block != null && TypeInferenceHelper.isTooComplexTooAnalyze(block)) {
myHolder.createWeakWarningAnnotation(nameIdentifier,
GroovyBundle.message("method.0.is.too.complex.too.analyze", method.getName()));
}
final PsiClass containingClass = method.getContainingClass();
if (method.isConstructor()) {
if (containingClass instanceof GrAnonymousClassDefinition) {
myHolder.createErrorAnnotation(nameIdentifier, GroovyBundle.message("constructors.are.not.allowed.in.anonymous.class"));
}
else if (containingClass != null && containingClass.isInterface()) {
myHolder.createErrorAnnotation(nameIdentifier, GroovyBundle.message("constructors.are.not.allowed.in.interface"));
}
}
if (method.getBlock() == null && !method.hasModifierProperty(PsiModifier.NATIVE) && !GrTraitUtil.isMethodAbstract(method)) {
final Annotation annotation =
myHolder.createErrorAnnotation(nameIdentifier, GroovyBundle.message("not.abstract.method.should.have.body"));
//annotation.registerFix(new AddMethodBodyFix(method)); //todo make intentions work
//registerFix(annotation, new GrModifierFix(method, ABSTRACT, false, true, GrModifierFix.MODIFIER_LIST_OWNER), method);
}
checkOverridingMethod(myHolder, method);
}
private static void checkGetterOfImmutable(AnnotationHolder holder, GrMethod method) {
if (!GroovyPropertyUtils.isSimplePropertyGetter(method)) return;
PsiClass aClass = method.getContainingClass();
if (aClass == null) return;
PsiModifierList aClassModifierList = aClass.getModifierList();
if (aClassModifierList == null) return;
if (!PsiImplUtil.hasImmutableAnnotation(aClassModifierList)) return;
PsiField field = GroovyPropertyUtils.findFieldForAccessor(method, false);
if (field == null || !(field instanceof GrField)) return;
GrModifierList fieldModifierList = ((GrField)field).getModifierList();
if (fieldModifierList == null) return;
if (fieldModifierList.hasExplicitVisibilityModifiers()) return;
holder.createErrorAnnotation(method.getNameIdentifierGroovy(), GroovyBundle.message("repetitive.method.name.0", method.getName()));
}
private static void checkConstructorOfImmutableClass(AnnotationHolder holder, GrMethod method) {
if (!method.isConstructor()) return;
PsiClass aClass = method.getContainingClass();
if (aClass == null) return;
PsiModifierList modifierList = aClass.getModifierList();
if (modifierList == null) return;
if (!PsiImplUtil.hasImmutableAnnotation(modifierList)) return;
holder.createErrorAnnotation(method.getNameIdentifierGroovy(), GroovyBundle.message("explicit.constructors.are.not.allowed.in.immutable.class"));
}
private static void checkOverridingMethod(@NotNull AnnotationHolder holder, @NotNull GrMethod method) {
final List<HierarchicalMethodSignature> signatures = method.getHierarchicalMethodSignature().getSuperSignatures();
for (HierarchicalMethodSignature signature : signatures) {
final PsiMethod superMethod = signature.getMethod();
if (superMethod.hasModifierProperty(PsiModifier.FINAL)) {
final String current = GroovyPresentationUtil.getSignaturePresentation(method.getSignature(PsiSubstitutor.EMPTY));
final String superPresentation = GroovyPresentationUtil.getSignaturePresentation(signature);
final String superQName = getQNameOfMember(superMethod);
holder.createErrorAnnotation(
GrHighlightUtil.getMethodHeaderTextRange(method),
GroovyBundle.message("method.0.cannot.override.method.1.in.2.overridden.method.is.final", current, superPresentation, superQName)
);
return;
}
final String currentModifier = VisibilityUtil.getVisibilityModifier(method.getModifierList());
final String superModifier = VisibilityUtil.getVisibilityModifier(superMethod.getModifierList());
if (PsiModifier.PUBLIC.equals(superModifier) && (PsiModifier.PROTECTED.equals(currentModifier) || PsiModifier.PRIVATE
.equals(currentModifier)) ||
PsiModifier.PROTECTED.equals(superModifier) && PsiModifier.PRIVATE.equals(currentModifier)) {
final String currentPresentation = GroovyPresentationUtil.getSignaturePresentation(method.getSignature(PsiSubstitutor.EMPTY));
final String superPresentation = GroovyPresentationUtil.getSignaturePresentation(signature);
final String superQName = getQNameOfMember(superMethod);
final PsiElement modifier = PsiUtil.findModifierInList(method.getModifierList(), currentModifier);
holder.createErrorAnnotation(
modifier != null? modifier : method.getNameIdentifierGroovy(),
GroovyBundle.message("method.0.cannot.have.weaker.access.privileges.1.than.2.in.3.4", currentPresentation, currentModifier, superPresentation, superQName, superModifier)
);
}
}
}
private static void checkMethodWithTypeParamsShouldHaveReturnType(AnnotationHolder holder, GrMethod method) {
final PsiTypeParameterList parameterList = method.getTypeParameterList();
if (parameterList != null) {
final GrTypeElement typeElement = method.getReturnTypeElementGroovy();
if (typeElement == null) {
final TextRange parameterListTextRange = parameterList.getTextRange();
final TextRange range = new TextRange(parameterListTextRange.getEndOffset(), parameterListTextRange.getEndOffset() + 1);
holder.createErrorAnnotation(range, GroovyBundle.message("method.with.type.parameters.should.have.return.type"));
}
}
}
private static void checkOptionalParametersInAbstractMethod(AnnotationHolder holder, GrMethod method) {
if (!method.hasModifierProperty(PsiModifier.ABSTRACT)) return;
for (GrParameter parameter : method.getParameters()) {
GrExpression initializerGroovy = parameter.getInitializerGroovy();
if (initializerGroovy != null) {
PsiElement assignOperator = parameter.getNameIdentifierGroovy();
TextRange textRange =
new TextRange(assignOperator.getTextRange().getEndOffset(), initializerGroovy.getTextRange().getEndOffset());
holder.createErrorAnnotation(textRange, GroovyBundle.message("default.initializers.are.not.allowed.in.abstract.method"));
}
}
}
@Nullable
private static PsiMethod getDefaultConstructor(PsiClass clazz) {
final String className = clazz.getName();
if (className == null) return null;
final PsiMethod[] byName = clazz.findMethodsByName(className, true);
if (byName.length == 0) return null;
Outer:
for (PsiMethod method : byName) {
if (method.getParameterList().getParametersCount() == 0) return method;
if (!(method instanceof GrMethod)) continue;
final GrParameter[] parameters = ((GrMethod)method).getParameterList().getParameters();
for (GrParameter parameter : parameters) {
if (!parameter.isOptional()) continue Outer;
}
return method;
}
return null;
}
@Override
public void visitVariable(GrVariable variable) {
checkName(variable);
PsiElement parent = variable.getParent();
if (parent instanceof GrForInClause) {
PsiElement delimiter = ((GrForInClause)parent).getDelimiter();
if (delimiter.getNode().getElementType() == GroovyTokenTypes.mCOLON) {
GrTypeElement typeElement = variable.getTypeElementGroovy();
GrModifierList modifierList = variable.getModifierList();
if (typeElement == null && StringUtil.isEmptyOrSpaces(modifierList.getText())) {
Annotation annotation = myHolder.createErrorAnnotation(variable.getNameIdentifierGroovy(), GroovyBundle
.message("java.style.for.each.statement.requires.a.type.declaration"));
annotation.registerFix(new ReplaceDelimiterFix());
}
}
}
PsiNamedElement duplicate = ResolveUtil.findDuplicate(variable);
if (duplicate instanceof GrVariable &&
(variable instanceof GrField || ResolveUtil.isScriptField(variable) || !(duplicate instanceof GrField))) {
final String key = duplicate instanceof GrField ? "field.already.defined" : "variable.already.defined";
myHolder.createErrorAnnotation(variable.getNameIdentifierGroovy(), GroovyBundle.message(key, variable.getName()));
}
PsiType type = variable.getDeclaredType();
if (type instanceof PsiEllipsisType && !isLastParameter(variable)) {
TextRange range = getEllipsisRange(variable);
if (range == null) {
range = getTypeRange(variable);
}
if (range != null) {
myHolder.createErrorAnnotation(range, GroovyBundle.message("ellipsis.type.is.not.allowed.here"));
}
}
}
@Nullable
private static TextRange getEllipsisRange(GrVariable variable) {
if (variable instanceof GrParameter) {
final PsiElement dots = ((GrParameter)variable).getEllipsisDots();
if (dots != null) {
return dots.getTextRange();
}
}
return null;
}
@Nullable
private static TextRange getTypeRange(GrVariable variable) {
GrTypeElement typeElement = variable.getTypeElementGroovy();
if (typeElement == null) return null;
PsiElement sibling = typeElement.getNextSibling();
if (sibling != null && sibling.getNode().getElementType() == GroovyTokenTypes.mTRIPLE_DOT) {
return new TextRange(typeElement.getTextRange().getStartOffset(), sibling.getTextRange().getEndOffset());
}
return typeElement.getTextRange();
}
private static boolean isLastParameter(PsiVariable variable) {
if (!(variable instanceof PsiParameter)) return false;
PsiElement parent = variable.getParent();
if (!(parent instanceof PsiParameterList)) return false;
PsiParameter[] parameters = ((PsiParameterList)parent).getParameters();
return parameters.length > 0 && parameters[parameters.length - 1] == variable;
}
private void checkName(GrVariable variable) {
if (!"$".equals(variable.getName())) return;
myHolder.createErrorAnnotation(variable.getNameIdentifierGroovy(), GroovyBundle.message("incorrect.variable.name"));
}
@Override
public void visitAssignmentExpression(GrAssignmentExpression expression) {
GrExpression lValue = expression.getLValue();
if (!PsiUtil.mightBeLValue(lValue)) {
myHolder.createErrorAnnotation(lValue, GroovyBundle.message("invalid.lvalue"));
}
}
@Override
public void visitReturnStatement(GrReturnStatement returnStatement) {
final GrExpression value = returnStatement.getReturnValue();
if (value != null) {
final PsiType type = value.getType();
if (type != null) {
final GrParametersOwner owner = PsiTreeUtil.getParentOfType(returnStatement, GrMethod.class, GrClosableBlock.class);
if (owner instanceof PsiMethod) {
final PsiMethod method = (PsiMethod)owner;
if (method.isConstructor()) {
myHolder.createErrorAnnotation(value, GroovyBundle.message("cannot.return.from.constructor"));
}
else {
final PsiType methodType = method.getReturnType();
if (methodType != null) {
if (PsiType.VOID.equals(methodType)) {
myHolder.createErrorAnnotation(value, GroovyBundle.message("cannot.return.from.void.method"));
}
}
}
}
}
}
}
@Override
public void visitTypeParameterList(GrTypeParameterList list) {
final PsiElement parent = list.getParent();
if (parent instanceof GrMethod && ((GrMethod)parent).isConstructor() ||
parent instanceof GrEnumTypeDefinition ||
parent instanceof GrAnnotationTypeDefinition) {
myHolder.createErrorAnnotation(list, GroovyBundle.message("type.parameters.are.unexpected"));
}
}
@Override
public void visitListOrMap(GrListOrMap listOrMap) {
final PsiReference constructorReference = listOrMap.getReference();
if (constructorReference instanceof LiteralConstructorReference &&
((LiteralConstructorReference)constructorReference).getConstructedClassType() != null) {
final PsiElement startToken = listOrMap.getFirstChild();
if (startToken != null && startToken.getNode().getElementType() == GroovyTokenTypes.mLBRACK) {
myHolder.createInfoAnnotation(startToken, null).setTextAttributes(DefaultHighlighter.LITERAL_CONVERSION);
}
final PsiElement endToken = listOrMap.getLastChild();
if (endToken != null && endToken.getNode().getElementType() == GroovyTokenTypes.mRBRACK) {
myHolder.createInfoAnnotation(endToken, null).setTextAttributes(DefaultHighlighter.LITERAL_CONVERSION);
}
}
final GrNamedArgument[] namedArguments = listOrMap.getNamedArguments();
final GrExpression[] expressionArguments = listOrMap.getInitializers();
if (namedArguments.length != 0 && expressionArguments.length != 0) {
myHolder.createErrorAnnotation(listOrMap, GroovyBundle.message("collection.literal.contains.named.argument.and.expression.items"));
}
checkNamedArgs(namedArguments, false);
}
@Override
public void visitClassTypeElement(GrClassTypeElement typeElement) {
super.visitClassTypeElement(typeElement);
final GrCodeReferenceElement ref = typeElement.getReferenceElement();
final GrTypeArgumentList argList = ref.getTypeArgumentList();
if (argList == null) return;
final GrTypeElement[] elements = argList.getTypeArgumentElements();
for (GrTypeElement element : elements) {
checkTypeArgForPrimitive(element, GroovyBundle.message("primitive.type.parameters.are.not.allowed"));
}
}
@Override
public void visitCodeReferenceElement(GrCodeReferenceElement refElement) {
PsiElement resolved = refElement.resolve();
if (resolved instanceof PsiClass &&
(((PsiClass)resolved).isAnnotationType() ||
GrAnnotationCollector.findAnnotationCollector((PsiClass)resolved) != null &&
refElement.getParent() instanceof GrAnnotation)) {
myHolder.createInfoAnnotation(refElement, null).setTextAttributes(DefaultHighlighter.ANNOTATION);
}
}
@Override
public void visitTypeElement(GrTypeElement typeElement) {
final PsiElement parent = typeElement.getParent();
if (!(parent instanceof GrMethod)) return;
if (parent instanceof GrAnnotationMethod) {
checkAnnotationAttributeType(typeElement, myHolder);
}
else if (((GrMethod)parent).isConstructor()) {
myHolder.createErrorAnnotation(typeElement, GroovyBundle.message("constructors.cannot.have.return.type"));
}
else {
checkMethodReturnType(((GrMethod)parent), typeElement, myHolder);
}
}
@Override
public void visitModifierList(GrModifierList modifierList) {
final PsiElement parent = modifierList.getParent();
if (parent instanceof GrMethod) {
checkMethodDefinitionModifiers(myHolder, (GrMethod)parent);
}
else if (parent instanceof GrTypeDefinition) {
checkTypeDefinitionModifiers(myHolder, (GrTypeDefinition)parent);
}
else if (parent instanceof GrVariableDeclaration && parent.getParent() instanceof GrTypeDefinition) {
checkFieldModifiers(myHolder, (GrVariableDeclaration)parent);
}
else if (parent instanceof GrClassInitializer) {
checkClassInitializerModifiers(myHolder, modifierList);
}
}
private static void checkClassInitializerModifiers(AnnotationHolder holder, GrModifierList modifierList) {
for (GrAnnotation annotation : modifierList.getAnnotations()) {
holder.createErrorAnnotation(annotation, GroovyBundle.message("initializer.cannot.have.annotations"));
}
for (@GrModifier.GrModifierConstant String modifier : GrModifier.GROOVY_MODIFIERS) {
if (PsiModifier.STATIC.equals(modifier)) continue;
checkModifierIsNotAllowed(modifierList, modifier, GroovyBundle.message("initializer.cannot.be.0", modifier), holder);
}
}
@Override
public void visitClassInitializer(GrClassInitializer initializer) {
final PsiClass aClass = initializer.getContainingClass();
if (aClass != null && aClass.isInterface()) {
final TextRange range = GrHighlightUtil.getInitializerHeaderTextRange(initializer);
myHolder.createErrorAnnotation(range, GroovyBundle.message("initializers.are.not.allowed.in.interface"));
}
}
private static void checkFieldModifiers(AnnotationHolder holder, GrVariableDeclaration fieldDeclaration) {
final GrModifierList modifierList = fieldDeclaration.getModifierList();
final GrField member = (GrField)fieldDeclaration.getVariables()[0];
checkAccessModifiers(holder, modifierList, member);
checkDuplicateModifiers(holder, modifierList, member);
if (modifierList.hasExplicitModifier(PsiModifier.VOLATILE) && modifierList.hasExplicitModifier(PsiModifier.FINAL)) {
final Annotation annotation =
holder.createErrorAnnotation(modifierList, GroovyBundle.message("illegal.combination.of.modifiers.volatile.and.final"));
registerFix(annotation, new GrModifierFix(member, PsiModifier.VOLATILE, true, false, GrModifierFix.MODIFIER_LIST), modifierList);
registerFix(annotation, new GrModifierFix(member, PsiModifier.FINAL, true, false, GrModifierFix.MODIFIER_LIST), modifierList);
}
checkModifierIsNotAllowed(modifierList, PsiModifier.NATIVE, GroovyBundle.message("variable.cannot.be.native"), holder);
checkModifierIsNotAllowed(modifierList, PsiModifier.ABSTRACT, GroovyBundle.message("variable.cannot.be.abstract"), holder);
if (member.getContainingClass() instanceof GrInterfaceDefinition) {
checkModifierIsNotAllowed(modifierList,
PsiModifier.PRIVATE, GroovyBundle.message("interface.members.are.not.allowed.to.be", PsiModifier.PRIVATE), holder);
checkModifierIsNotAllowed(modifierList, PsiModifier.PROTECTED, GroovyBundle.message("interface.members.are.not.allowed.to.be",
PsiModifier.PROTECTED),
holder);
}
}
private static void registerFix(Annotation annotation, LocalQuickFix fix, PsiElement place) {
final InspectionManager manager = InspectionManager.getInstance(place.getProject());
assert !place.getTextRange().isEmpty() : place.getContainingFile().getName();
final ProblemDescriptor descriptor = manager.createProblemDescriptor(place, place, annotation.getMessage(),
annotation.getHighlightType(), true, LocalQuickFix.EMPTY_ARRAY);
final TextRange range = TextRange.create(annotation.getStartOffset(), annotation.getEndOffset());
annotation.registerFix(fix, range, null, descriptor);
}
private static void checkModifierIsNotAllowed(@NotNull GrModifierList modifierList,
@NotNull @GrModifier.GrModifierConstant String modifier,
@Nullable String message,
@NotNull AnnotationHolder holder) {
checkModifierIsNotAllowedImpl(modifierList, modifier, message, holder, false);
}
private static void checkModifierIsNotAllowedImpl(@NotNull GrModifierList modifierList,
@NotNull @GrModifier.GrModifierConstant String modifier,
@Nullable String message,
@NotNull AnnotationHolder holder,
final boolean explicit) {
if (explicit ? modifierList.hasModifierProperty(modifier) : modifierList.hasExplicitModifier(modifier)) {
PsiElement modifierOrList = getModifierOrList(modifierList, modifier);
final Annotation annotation = holder.createErrorAnnotation(modifierOrList, message);
registerFix(annotation, new GrModifierFix((PsiMember)modifierList.getParent(), modifier, true, false, GrModifierFix.MODIFIER_LIST), modifierList);
}
}
private static void checkAnnotationAttributeType(GrTypeElement element, AnnotationHolder holder) {
if (element instanceof GrBuiltInTypeElement) return;
if (element instanceof GrArrayTypeElement) {
checkAnnotationAttributeType(((GrArrayTypeElement)element).getComponentTypeElement(), holder);
return;
}
else if (element instanceof GrClassTypeElement) {
final PsiElement resolved = ((GrClassTypeElement)element).getReferenceElement().resolve();
if (resolved instanceof PsiClass) {
if (CommonClassNames.JAVA_LANG_STRING.equals(((PsiClass)resolved).getQualifiedName())) return;
if (CommonClassNames.JAVA_LANG_CLASS.equals(((PsiClass)resolved).getQualifiedName())) return;
if (((PsiClass)resolved).isAnnotationType()) return;
if (((PsiClass)resolved).isEnum()) return;
}
}
holder.createErrorAnnotation(element, GroovyBundle.message("unexpected.attribute.type.0", element.getType()));
}
static void checkMethodReturnType(PsiMethod method, PsiElement toHighlight, AnnotationHolder holder) {
final HierarchicalMethodSignature signature = method.getHierarchicalMethodSignature();
final List<HierarchicalMethodSignature> superSignatures = signature.getSuperSignatures();
PsiType returnType = signature.getSubstitutor().substitute(method.getReturnType());
for (HierarchicalMethodSignature superMethodSignature : superSignatures) {
PsiMethod superMethod = superMethodSignature.getMethod();
PsiType declaredReturnType = superMethod.getReturnType();
PsiType superReturnType = superMethodSignature.getSubstitutor().substitute(declaredReturnType);
if (superReturnType == PsiType.VOID && method instanceof GrMethod && ((GrMethod)method).getReturnTypeElementGroovy() == null) return;
if (superMethodSignature.isRaw()) superReturnType = TypeConversionUtil.erasure(declaredReturnType);
if (returnType == null || superReturnType == null || method == superMethod) continue;
PsiClass superClass = superMethod.getContainingClass();
if (superClass == null) continue;
String highlightInfo = checkSuperMethodSignature(superMethod, superMethodSignature, superReturnType, method, signature, returnType);
if (highlightInfo != null) {
holder.createErrorAnnotation(toHighlight, highlightInfo);
return;
}
}
}
@Nullable
private static String checkSuperMethodSignature(@NotNull PsiMethod superMethod,
@NotNull MethodSignatureBackedByPsiMethod superMethodSignature,
@NotNull PsiType superReturnType,
@NotNull PsiMethod method,
@NotNull MethodSignatureBackedByPsiMethod methodSignature,
@NotNull PsiType returnType) {
PsiType substitutedSuperReturnType = substituteSuperReturnType(superMethodSignature, methodSignature, superReturnType);
if (returnType.equals(substitutedSuperReturnType)) return null;
final PsiType rawReturnType = TypeConversionUtil.erasure(returnType);
final PsiType rawSuperReturnType = TypeConversionUtil.erasure(substitutedSuperReturnType);
if (returnType instanceof PsiClassType && substitutedSuperReturnType instanceof PsiClassType) {
if (TypeConversionUtil.isAssignable(rawSuperReturnType, rawReturnType)) {
return null;
}
}
else if (returnType instanceof PsiArrayType && superReturnType instanceof PsiArrayType) {
if (rawReturnType.equals(rawSuperReturnType)) {
return null;
}
}
String qName = getQNameOfMember(method);
String baseQName = getQNameOfMember(superMethod);
final String presentation = returnType.getCanonicalText() + " " + GroovyPresentationUtil.getSignaturePresentation(methodSignature);
final String basePresentation =
superReturnType.getCanonicalText() + " " + GroovyPresentationUtil.getSignaturePresentation(superMethodSignature);
return GroovyBundle.message("return.type.is.incompatible", presentation, qName, basePresentation, baseQName);
}
@NotNull
private static PsiType substituteSuperReturnType(@NotNull MethodSignatureBackedByPsiMethod superMethodSignature,
@NotNull MethodSignatureBackedByPsiMethod methodSignature,
@NotNull PsiType superReturnType) {
PsiType substitutedSuperReturnType;
if (!superMethodSignature.isRaw() && superMethodSignature.equals(methodSignature)) { //see 8.4.5
PsiSubstitutor unifyingSubstitutor = MethodSignatureUtil.getSuperMethodSignatureSubstitutor(methodSignature,
superMethodSignature);
substitutedSuperReturnType = unifyingSubstitutor == null
? superReturnType
: unifyingSubstitutor.substitute(superMethodSignature.getSubstitutor().substitute(superReturnType));
}
else {
substitutedSuperReturnType = TypeConversionUtil.erasure(superReturnType);
}
return substitutedSuperReturnType;
}
@NotNull
private static String getQNameOfMember(@NotNull PsiMember member) {
final PsiClass aClass = member.getContainingClass();
return getQName(aClass);
}
@NotNull
private static String getQName(@Nullable PsiClass aClass) {
if (aClass instanceof PsiAnonymousClass) {
return GroovyBundle.message("anonymous.class.derived.from.0", ((PsiAnonymousClass)aClass).getBaseClassType().getCanonicalText());
}
if (aClass != null) {
final String qname = aClass.getQualifiedName();
if (qname != null) {
return qname;
}
}
return "<null>";
}
private void checkTypeArgForPrimitive(@Nullable GrTypeElement element, String message) {
if (element == null || !(element.getType() instanceof PsiPrimitiveType)) return;
final Annotation annotation = myHolder.createErrorAnnotation(element, message);
registerFix(annotation, new GrReplacePrimitiveTypeWithWrapperFix(element), element);
}
@Override
public void visitWildcardTypeArgument(GrWildcardTypeArgument wildcardTypeArgument) {
super.visitWildcardTypeArgument(wildcardTypeArgument);
checkTypeArgForPrimitive(wildcardTypeArgument.getBoundTypeElement(), GroovyBundle.message("primitive.bound.types.are.not.allowed"));
}
private void highlightNamedArgs(GrNamedArgument[] namedArguments) {
for (GrNamedArgument namedArgument : namedArguments) {
final GrArgumentLabel label = namedArgument.getLabel();
if (label != null && label.getExpression() == null && label.getNameElement().getNode().getElementType() != GroovyTokenTypes.mSTAR) {
myHolder.createInfoAnnotation(label, null).setTextAttributes(DefaultHighlighter.MAP_KEY);
}
}
}
private void checkNamedArgs(GrNamedArgument[] namedArguments, boolean forArgList) {
highlightNamedArgs(namedArguments);
MultiMap<String, GrArgumentLabel> map = new MultiMap<String, GrArgumentLabel>();
for (GrNamedArgument element : namedArguments) {
final GrArgumentLabel label = element.getLabel();
if (label != null) {
final String name = label.getName();
if (name != null) {
map.putValue(name, label);
}
}
}
for (String key : map.keySet()) {
final List<GrArgumentLabel> arguments = (List<GrArgumentLabel>)map.get(key);
if (arguments.size() > 1) {
for (int i = 1; i < arguments.size(); i++) {
final GrArgumentLabel label = arguments.get(i);
if (forArgList) {
myHolder.createErrorAnnotation(label, GroovyBundle.message("duplicated.named.parameter", key));
}
else {
myHolder.createWarningAnnotation(label, GroovyBundle.message("duplicate.element.in.the.map"));
}
}
}
}
}
@Override
public void visitNewExpression(GrNewExpression newExpression) {
GrTypeArgumentList constructorTypeArguments = newExpression.getConstructorTypeArguments();
if (constructorTypeArguments != null) {
myHolder.createErrorAnnotation(constructorTypeArguments, GroovyBundle.message("groovy.does.not.support.constructor.type.arguments"));
}
final GrTypeElement typeElement = newExpression.getTypeElement();
if (typeElement instanceof GrBuiltInTypeElement) {
if (newExpression.getArrayCount() == 0) {
myHolder.createErrorAnnotation(typeElement, GroovyBundle.message("create.instance.of.built-in.type"));
}
}
if (newExpression.getArrayCount() > 0) return;
GrCodeReferenceElement refElement = newExpression.getReferenceElement();
if (refElement == null) return;
final PsiElement element = refElement.resolve();
if (element instanceof PsiClass) {
PsiClass clazz = (PsiClass)element;
if (clazz.hasModifierProperty(PsiModifier.ABSTRACT)) {
if (newExpression.getAnonymousClassDefinition() == null) {
String message = clazz.isInterface()
? GroovyBundle.message("cannot.instantiate.interface", clazz.getName())
: GroovyBundle.message("cannot.instantiate.abstract.class", clazz.getName());
myHolder.createErrorAnnotation(refElement, message);
}
return;
}
if (newExpression.getQualifier() != null) {
if (clazz.hasModifierProperty(PsiModifier.STATIC)) {
myHolder.createErrorAnnotation(newExpression, GroovyBundle.message("qualified.new.of.static.class"));
}
}
}
}
private static boolean checkDiamonds(GrCodeReferenceElement refElement, AnnotationHolder holder) {
GrTypeArgumentList typeArgumentList = refElement.getTypeArgumentList();
if (typeArgumentList == null) return true;
if (!typeArgumentList.isDiamond()) return true;
final GroovyConfigUtils configUtils = GroovyConfigUtils.getInstance();
if (!configUtils.isVersionAtLeast(refElement, GroovyConfigUtils.GROOVY1_8)) {
final String message = GroovyBundle.message("diamonds.are.not.allowed.in.groovy.0", configUtils.getSDKVersion(refElement));
holder.createErrorAnnotation(typeArgumentList, message);
}
return false;
}
@Override
public void visitArgumentList(GrArgumentList list) {
checkNamedArgs(list.getNamedArguments(), true);
}
@Override
public void visitConstructorInvocation(GrConstructorInvocation invocation) {
final GroovyResolveResult resolveResult = invocation.advancedResolve();
if (resolveResult.getElement() == null) {
final GroovyResolveResult[] results = invocation.multiResolve(false);
final GrArgumentList argList = invocation.getArgumentList();
if (results.length > 0) {
String message = GroovyBundle.message("ambiguous.constructor.call");
myHolder.createWarningAnnotation(argList, message);
}
else {
final PsiClass clazz = invocation.getDelegatedClass();
if (clazz != null) {
//default constructor invocation
PsiType[] argumentTypes = PsiUtil.getArgumentTypes(invocation.getInvokedExpression(), true);
if (argumentTypes != null && argumentTypes.length > 0) {
String message = GroovyBundle.message("cannot.apply.default.constructor", clazz.getName());
myHolder.createWarningAnnotation(argList, message);
}
}
}
}
}
@Override
public void visitBreakStatement(GrBreakStatement breakStatement) {
checkFlowInterruptStatement(breakStatement, myHolder);
}
@Override
public void visitContinueStatement(GrContinueStatement continueStatement) {
checkFlowInterruptStatement(continueStatement, myHolder);
}
@Override
public void visitPackageDefinition(GrPackageDefinition packageDefinition) {
final GrModifierList modifierList = packageDefinition.getAnnotationList();
checkAnnotationList(myHolder, modifierList, GroovyBundle.message("package.definition.cannot.have.modifiers"));
}
@Override
public void visitClosure(GrClosableBlock closure) {
super.visitClosure(closure);
if (!closure.hasParametersSection() && !followsError(closure) && isClosureAmbiguous(closure)) {
myHolder.createErrorAnnotation(closure, GroovyBundle.message("ambiguous.code.block"));
}
if (TypeInferenceHelper.isTooComplexTooAnalyze(closure)) {
int startOffset = closure.getTextRange().getStartOffset();
int endOffset;
PsiElement arrow = closure.getArrow();
if (arrow != null) {
endOffset = arrow.getTextRange().getEndOffset();
}
else {
Document document = PsiDocumentManager.getInstance(closure.getProject()).getDocument(closure.getContainingFile());
if (document == null) return;
String text = document.getText();
endOffset = Math.min(closure.getTextRange().getEndOffset(), text.indexOf('\n', startOffset));
}
myHolder
.createWeakWarningAnnotation(new TextRange(startOffset, endOffset), GroovyBundle.message("closure.is.too.complex.to.analyze"));
}
}
/**
* for example if (!(a inst)) {}
* ^
* we are here
*/
private static boolean followsError(GrClosableBlock closure) {
PsiElement prev = closure.getPrevSibling();
return prev instanceof PsiErrorElement || prev instanceof PsiWhiteSpace && prev.getPrevSibling() instanceof PsiErrorElement;
}
private static boolean isClosureAmbiguous(GrClosableBlock closure) {
PsiElement place = closure;
while (true) {
PsiElement parent = place.getParent();
if (parent == null || parent instanceof GrUnAmbiguousClosureContainer) return false;
if (PsiUtil.isExpressionStatement(place)) return true;
if (parent.getFirstChild() != place) return false;
place = parent;
}
}
@Override
public void visitLiteralExpression(GrLiteral literal) {
final IElementType elementType = literal.getFirstChild().getNode().getElementType();
if (elementType == GroovyTokenTypes.mSTRING_LITERAL || elementType == GroovyTokenTypes.mGSTRING_LITERAL) {
checkStringLiteral(literal);
}
else if (elementType == GroovyTokenTypes.mREGEX_LITERAL || elementType == GroovyTokenTypes.mDOLLAR_SLASH_REGEX_LITERAL) {
checkRegexLiteral(literal.getFirstChild());
}
}
@Override
public void visitRegexExpression(GrRegex regex) {
checkRegexLiteral(regex);
}
private void checkRegexLiteral(PsiElement regex) {
String text = regex.getText();
String quote = GrStringUtil.getStartQuote(text);
final GroovyConfigUtils config = GroovyConfigUtils.getInstance();
if ("$/".equals(quote)) {
if (!config.isVersionAtLeast(regex, GroovyConfigUtils.GROOVY1_8)) {
myHolder
.createErrorAnnotation(regex, GroovyBundle.message("dollar.slash.strings.are.not.allowed.in.0", config.getSDKVersion(regex)));
}
}
String[] parts;
if (regex instanceof GrRegex) {
parts = ((GrRegex)regex).getTextParts();
}
else {
//noinspection ConstantConditions
parts = new String[]{regex.getFirstChild().getNextSibling().getText()};
}
for (String part : parts) {
if (!GrStringUtil.parseRegexCharacters(part, new StringBuilder(part.length()), null, regex.getText().startsWith("/"))) {
myHolder.createErrorAnnotation(regex, GroovyBundle.message("illegal.escape.character.in.string.literal"));
return;
}
}
if ("/".equals(quote)) {
if (!config.isVersionAtLeast(regex, GroovyConfigUtils.GROOVY1_8)) {
if (text.contains("\n") || text.contains("\r")) {
myHolder.createErrorAnnotation(regex, GroovyBundle
.message("multiline.slashy.strings.are.not.allowed.in.groovy.0", config.getSDKVersion(regex)));
return;
}
}
}
}
@Override
public void visitGStringExpression(GrString gstring) {
for (GrStringContent part : gstring.getContents()) {
final String text = part.getText();
if (!GrStringUtil.parseStringCharacters(text, new StringBuilder(text.length()), null)) {
myHolder.createErrorAnnotation(part, GroovyBundle.message("illegal.escape.character.in.string.literal"));
return;
}
}
}
@Override
public void visitGStringInjection(GrStringInjection injection) {
if (((GrString)injection.getParent()).isPlainString()) {
if (StringUtil.indexOf(injection.getText(), '\n') != -1) {
myHolder.createErrorAnnotation(injection, GroovyBundle.message("injection.should.not.contain.line.feeds"));
}
}
}
private void checkStringLiteral(PsiElement literal) {
InjectedLanguageManager injectedLanguageManager = InjectedLanguageManager.getInstance(literal.getProject());
String text;
if (injectedLanguageManager.isInjectedFragment(literal.getContainingFile())) {
text = injectedLanguageManager.getUnescapedText(literal);
}
else {
text = literal.getText();
}
assert text != null;
StringBuilder builder = new StringBuilder(text.length());
String quote = GrStringUtil.getStartQuote(text);
if (quote.isEmpty()) return;
String substring = text.substring(quote.length());
if (!GrStringUtil.parseStringCharacters(substring, new StringBuilder(text.length()), null)) {
myHolder.createErrorAnnotation(literal, GroovyBundle.message("illegal.escape.character.in.string.literal"));
return;
}
int[] offsets = new int[substring.length() + 1];
boolean result = GrStringUtil.parseStringCharacters(substring, builder, offsets);
LOG.assertTrue(result);
if (!builder.toString().endsWith(quote) || substring.charAt(offsets[builder.length() - quote.length()]) == '\\') {
myHolder.createErrorAnnotation(literal, GroovyBundle.message("string.end.expected"));
}
}
@Override
public void visitForInClause(GrForInClause forInClause) {
final GrVariable var = forInClause.getDeclaredVariable();
if (var == null) return;
final GrModifierList modifierList = var.getModifierList();
if (modifierList == null) return;
final PsiElement[] modifiers = modifierList.getModifiers();
for (PsiElement modifier : modifiers) {
if (modifier instanceof PsiAnnotation) continue;
final String modifierText = modifier.getText();
if (PsiModifier.FINAL.equals(modifierText)) continue;
if (GrModifier.DEF.equals(modifierText)) continue;
myHolder.createErrorAnnotation(modifier, GroovyBundle.message("not.allowed.modifier.in.forin", modifierText));
}
}
@Override
public void visitFile(GroovyFileBase file) {
final PsiClass scriptClass = file.getScriptClass();
if (scriptClass != null) {
checkDuplicateMethod(scriptClass, myHolder);
checkSameNameMethodsWithDifferentAccessModifiers(myHolder, file.getCodeMethods());
}
}
@Override
public void visitAnnotation(GrAnnotation annotation) {
new AnnotationChecker(myHolder).checkApplicability(annotation, annotation.getOwner());
}
@Override
public void visitAnnotationArgumentList(GrAnnotationArgumentList annotationArgumentList) {
new AnnotationChecker(myHolder).checkAnnotationArgumentList((GrAnnotation)annotationArgumentList.getParent());
}
@Override
public void visitAnnotationMethod(GrAnnotationMethod annotationMethod) {
super.visitAnnotationMethod(annotationMethod);
final GrAnnotationMemberValue value = annotationMethod.getDefaultValue();
if (value == null) return;
final PsiType type = annotationMethod.getReturnType();
CustomAnnotationChecker.checkAnnotationValueByType(myHolder, value, type, false);
}
@Override
public void visitAnnotationNameValuePair(GrAnnotationNameValuePair nameValuePair) {
final PsiElement identifier = nameValuePair.getNameIdentifierGroovy();
if (identifier == null) {
final PsiElement parent = nameValuePair.getParent();
if (parent instanceof GrAnnotationArgumentList) {
final int count = ((GrAnnotationArgumentList)parent).getAttributes().length;
if (count > 1) {
myHolder.createErrorAnnotation(nameValuePair, GroovyBundle.message("attribute.name.expected"));
}
}
}
final GrAnnotationMemberValue value = nameValuePair.getValue();
if (value != null) {
checkAnnotationAttributeValue(value, value);
}
}
private boolean checkAnnotationAttributeValue(@Nullable GrAnnotationMemberValue value, @NotNull PsiElement toHighlight) {
if (value == null) return false;
if (value instanceof GrLiteral) return false;
if (value instanceof GrClosableBlock) return false;
if (value instanceof GrAnnotation) return false;
if (value instanceof GrReferenceExpression) {
PsiElement resolved = ((GrReferenceExpression)value).resolve();
if (resolved instanceof PsiClass) return false;
if (resolved instanceof PsiEnumConstant) return false;
if (resolved == null && isClassReference(value)) return false;
if (resolved instanceof GrAccessorMethod) resolved = ((GrAccessorMethod)resolved).getProperty();
if (resolved instanceof PsiField) {
GrExpression initializer;
try {
if (resolved instanceof GrField) {
initializer = ((GrField)resolved).getInitializerGroovy();
}
else {
final PsiExpression _initializer = ((PsiField)resolved).getInitializer();
initializer = _initializer != null
? (GrExpression)ExpressionConverter.getExpression(_initializer, GroovyLanguage.INSTANCE, value.getProject())
: null;
}
}
catch (IncorrectOperationException e) {
initializer = null;
}
if (initializer != null) {
return checkAnnotationAttributeValue(initializer, toHighlight);
}
}
}
if (value instanceof GrAnnotationArrayInitializer) {
for (GrAnnotationMemberValue expression : ((GrAnnotationArrayInitializer)value).getInitializers()) {
if (checkAnnotationAttributeValue(expression, toHighlight)) return true;
}
return false;
}
if (value instanceof GrUnaryExpression) {
final IElementType tokenType = ((GrUnaryExpression)value).getOperationTokenType();
if (tokenType == GroovyTokenTypes.mMINUS || tokenType == GroovyTokenTypes.mPLUS) {
return checkAnnotationAttributeValue(((GrUnaryExpression)value).getOperand(), toHighlight);
}
}
myHolder.createErrorAnnotation(toHighlight, GroovyBundle.message("expected.0.to.be.inline.constant", value.getText()));
return true;
}
private static boolean isClassReference(GrAnnotationMemberValue value) {
if (value instanceof GrReferenceExpression) {
final String referenceName = ((GrReferenceExpression)value).getReferenceName();
if ("class".equals(referenceName)) {
final GrExpression qualifier = ((GrReferenceExpression)value).getQualifier();
if (qualifier instanceof GrReferenceExpression) {
final PsiElement resolved = ((GrReferenceExpression)qualifier).resolve();
if (resolved instanceof PsiClass) {
return true;
}
}
}
}
return false;
}
@Override
public void visitImportStatement(GrImportStatement importStatement) {
checkAnnotationList(myHolder, importStatement.getAnnotationList(), GroovyBundle.message("import.statement.cannot.have.modifiers"));
}
@Override
public void visitExtendsClause(GrExtendsClause extendsClause) {
GrTypeDefinition typeDefinition = (GrTypeDefinition)extendsClause.getParent();
if (typeDefinition.isAnnotationType()) {
myHolder.createErrorAnnotation(extendsClause, GroovyBundle.message("annotation.types.may.not.have.extends.clause"));
}
else if (typeDefinition.isTrait()) {
checkReferenceList(myHolder, extendsClause, IS_TRAIT, GroovyBundle.message("only.traits.expected.here"), null);
}
else if (typeDefinition.isInterface()) {
checkReferenceList(myHolder, extendsClause, IS_INTERFACE, GroovyBundle.message("no.class.expected.here"), null);
}
else if (typeDefinition.isEnum()) {
myHolder.createErrorAnnotation(extendsClause, GroovyBundle.message("enums.may.not.have.extends.clause"));
}
else {
checkReferenceList(myHolder, extendsClause, IS_NOT_INTERFACE, GroovyBundle.message("no.interface.expected.here"), new ChangeExtendsImplementsQuickFix(typeDefinition));
checkForWildCards(myHolder, extendsClause);
}
}
@Override
public void visitImplementsClause(GrImplementsClause implementsClause) {
GrTypeDefinition typeDefinition = (GrTypeDefinition)implementsClause.getParent();
if (typeDefinition.isAnnotationType()) {
myHolder.createErrorAnnotation(implementsClause, GroovyBundle.message("annotation.types.may.not.have.implements.clause"));
}
else if (GrTraitUtil.isInterface(typeDefinition)) {
myHolder.createErrorAnnotation(implementsClause, GroovyBundle.message("no.implements.clause.allowed.for.interface"))
.registerFix(new ChangeExtendsImplementsQuickFix(typeDefinition));
}
else {
checkReferenceList(myHolder, implementsClause, IS_INTERFACE, GroovyBundle.message("no.class.expected.here"), new ChangeExtendsImplementsQuickFix(typeDefinition));
checkForWildCards(myHolder, implementsClause);
}
}
private static void checkReferenceList(@NotNull AnnotationHolder holder,
@NotNull GrReferenceList list,
@NotNull Condition<PsiClass> applicabilityCondition,
@NotNull String message,
@Nullable IntentionAction fix) {
for (GrCodeReferenceElement refElement : list.getReferenceElementsGroovy()) {
final PsiElement psiClass = refElement.resolve();
if (psiClass instanceof PsiClass && !applicabilityCondition.value((PsiClass)psiClass)) {
Annotation annotation = holder.createErrorAnnotation(refElement, message);
if (fix != null) {
annotation.registerFix(fix);
}
}
}
}
private static void checkFlowInterruptStatement(GrFlowInterruptingStatement statement, AnnotationHolder holder) {
final PsiElement label = statement.getLabelIdentifier();
if (label != null) {
final GrLabeledStatement resolved = statement.resolveLabel();
if (resolved == null) {
holder.createErrorAnnotation(label, GroovyBundle.message("undefined.label", statement.getLabelName()));
}
}
final GrStatement targetStatement = statement.findTargetStatement();
if (targetStatement == null) {
if (statement instanceof GrContinueStatement && label == null) {
holder.createErrorAnnotation(statement, GroovyBundle.message("continue.outside.loop"));
}
else if (statement instanceof GrBreakStatement && label == null) {
holder.createErrorAnnotation(statement, GroovyBundle.message("break.outside.loop.or.switch"));
}
}
if (statement instanceof GrBreakStatement && label != null && findFirstLoop(statement) == null) {
holder.createErrorAnnotation(statement, GroovyBundle.message("break.outside.loop"));
}
}
@Nullable
private static GrLoopStatement findFirstLoop(GrFlowInterruptingStatement statement) {
return PsiTreeUtil.getParentOfType(statement, GrLoopStatement.class, true, GrClosableBlock.class, GrMember.class, GroovyFile.class);
}
private static void checkThisOrSuperReferenceExpression(final GrReferenceExpression ref, AnnotationHolder holder) {
PsiElement nameElement = ref.getReferenceNameElement();
if (nameElement == null) return;
IElementType elementType = nameElement.getNode().getElementType();
if (!(elementType == GroovyTokenTypes.kSUPER || elementType == GroovyTokenTypes.kTHIS)) return;
final GrExpression qualifier = ref.getQualifier();
if (qualifier instanceof GrReferenceExpression) {
final PsiElement resolved = ((GrReferenceExpression)qualifier).resolve();
if (resolved instanceof PsiClass) {
GrTypeDefinition containingClass = PsiTreeUtil.getParentOfType(ref, GrTypeDefinition.class, true, GroovyFile.class);
if (elementType == GroovyTokenTypes.kSUPER && containingClass != null && GrTraitUtil.isTrait((PsiClass)resolved)) {
PsiClassType[] superTypes = containingClass.getSuperTypes();
if (ContainerUtil.find(superTypes, new Condition<PsiClassType>() {
@Override
public boolean value(PsiClassType type) {
return ref.getManager().areElementsEquivalent(type.resolve(), resolved);
}
}) != null) {
holder.createInfoAnnotation(nameElement, null).setTextAttributes(DefaultHighlighter.KEYWORD);
return; // reference to trait method
}
}
if (containingClass == null || containingClass.getContainingClass() == null && !containingClass.isAnonymous()) {
holder.createErrorAnnotation(ref, GroovyBundle.message("qualified.0.is.allowed.only.in.nested.or.inner.classes",
nameElement.getText()));
return;
}
if (PsiTreeUtil.isAncestor(resolved, ref, true)) {
if (PsiUtil.hasEnclosingInstanceInScope((PsiClass)resolved, ref, true)) {
holder.createInfoAnnotation(nameElement, null).setTextAttributes(DefaultHighlighter.KEYWORD);
}
}
else {
String qname = ((PsiClass)resolved).getQualifiedName();
assert qname != null;
holder.createErrorAnnotation(ref, GroovyBundle.message("is.not.enclosing.class", qname));
}
}
}
else if (qualifier == null) {
if (elementType == GroovyTokenTypes.kSUPER) {
final GrMember container = PsiTreeUtil.getParentOfType(ref, GrMethod.class, GrClassInitializer.class);
if (container != null && container.hasModifierProperty(PsiModifier.STATIC)) {
holder.createErrorAnnotation(ref, GroovyBundle.message("super.cannot.be.used.in.static.context"));
}
}
}
}
private static void checkGrDocReferenceElement(AnnotationHolder holder, PsiElement element) {
ASTNode node = element.getNode();
if (node != null && TokenSets.BUILT_IN_TYPES.contains(node.getElementType())) {
Annotation annotation = holder.createInfoAnnotation(element, null);
annotation.setTextAttributes(DefaultHighlighter.KEYWORD);
}
}
private static void checkAnnotationList(AnnotationHolder holder, @NotNull GrModifierList modifierList, String message) {
final PsiElement[] modifiers = modifierList.getModifiers();
for (PsiElement modifier : modifiers) {
if (!(modifier instanceof PsiAnnotation)) {
holder.createErrorAnnotation(modifier, message);
}
}
}
private static void checkImplementedMethodsOfClass(AnnotationHolder holder, GrTypeDefinition typeDefinition) {
if (typeDefinition.hasModifierProperty(PsiModifier.ABSTRACT)) return;
if (typeDefinition.isAnnotationType()) return;
if (typeDefinition instanceof GrTypeParameter) return;
PsiMethod abstractMethod = ClassUtil.getAnyAbstractMethod(typeDefinition);
if (abstractMethod == null) return;
String notImplementedMethodName = abstractMethod.getName();
final TextRange range = GrHighlightUtil.getClassHeaderTextRange(typeDefinition);
final Annotation annotation = holder.createErrorAnnotation(range,
GroovyBundle.message("method.is.not.implemented", notImplementedMethodName));
registerImplementsMethodsFix(typeDefinition, abstractMethod, annotation);
}
private static void registerImplementsMethodsFix(@NotNull GrTypeDefinition typeDefinition,
@NotNull PsiMethod abstractMethod,
@NotNull Annotation annotation) {
if (!OverrideImplementExploreUtil.getMethodsToOverrideImplement(typeDefinition, true).isEmpty()) {
annotation.registerFix(QuickFixFactory.getInstance().createImplementMethodsFix(typeDefinition));
}
if (!JavaPsiFacade.getInstance(typeDefinition.getProject()).getResolveHelper().isAccessible(abstractMethod, typeDefinition, null)) {
registerFix(annotation, new GrModifierFix(abstractMethod, PsiModifier.PUBLIC, true, true, GrModifierFix.MODIFIER_LIST_OWNER), abstractMethod);
registerFix(annotation, new GrModifierFix(abstractMethod, PsiModifier.PROTECTED, true, true, GrModifierFix.MODIFIER_LIST_OWNER), abstractMethod);
}
if (!(typeDefinition instanceof GrAnnotationTypeDefinition) && typeDefinition.getModifierList() != null) {
registerFix(annotation, new GrModifierFix(typeDefinition, PsiModifier.ABSTRACT, false, true, GrModifierFix.MODIFIER_LIST_OWNER), typeDefinition);
}
}
private static void checkInnerMethod(AnnotationHolder holder, GrMethod grMethod) {
final PsiElement parent = grMethod.getParent();
if (parent instanceof GrOpenBlock || parent instanceof GrClosableBlock) {
holder.createErrorAnnotation(grMethod.getNameIdentifierGroovy(), GroovyBundle.message("Inner.methods.are.not.supported"));
}
}
private static void registerMakeAbstractMethodNotAbstractFix(Annotation annotation, GrMethod method, boolean makeClassAbstract) {
if (method.getBlock() == null) {
annotation.registerFix(QuickFixFactory.getInstance().createAddMethodBodyFix(method));
}
else {
annotation.registerFix(QuickFixFactory.getInstance().createDeleteMethodBodyFix(method));
}
registerFix(annotation, new GrModifierFix(method, PsiModifier.ABSTRACT, false, false, GrModifierFix.MODIFIER_LIST_OWNER), method);
if (makeClassAbstract) {
final PsiClass containingClass = method.getContainingClass();
if (containingClass != null) {
final PsiModifierList list = containingClass.getModifierList();
if (list != null && !list.hasModifierProperty(PsiModifier.ABSTRACT)) {
registerFix(annotation, new GrModifierFix(containingClass, PsiModifier.ABSTRACT, false, true, GrModifierFix.MODIFIER_LIST_OWNER), containingClass);
}
}
}
}
private static void checkMethodDefinitionModifiers(AnnotationHolder holder, GrMethod method) {
final GrModifierList modifiersList = method.getModifierList();
checkAccessModifiers(holder, modifiersList, method);
checkDuplicateModifiers(holder, modifiersList, method);
checkOverrideAnnotation(holder, modifiersList, method);
checkModifierIsNotAllowed(modifiersList, PsiModifier.VOLATILE, GroovyBundle.message("method.has.incorrect.modifier.volatile"), holder);
checkForAbstractAndFinalCombination(holder, method, modifiersList);
//script methods
boolean isMethodAbstract = modifiersList.hasExplicitModifier(PsiModifier.ABSTRACT);
if (method.getParent() instanceof GroovyFileBase) {
if (isMethodAbstract) {
final Annotation annotation = holder.createErrorAnnotation(getModifierOrList(modifiersList, PsiModifier.ABSTRACT),
GroovyBundle.message("script.method.cannot.have.modifier.abstract"));
registerMakeAbstractMethodNotAbstractFix(annotation, method, false);
}
checkModifierIsNotAllowed(modifiersList, PsiModifier.NATIVE, GroovyBundle.message("script.cannot.have.modifier.native"), holder);
}
//type definition methods
else if (method.getParent() != null && method.getParent().getParent() instanceof GrTypeDefinition) {
GrTypeDefinition containingTypeDef = ((GrTypeDefinition)method.getParent().getParent());
if (containingTypeDef.isTrait()) {
checkModifierIsNotAllowed(modifiersList, PsiModifier.PROTECTED, GroovyBundle.message("trait.method.cannot.be.protected"), holder);
}
//interface
else if (containingTypeDef.isInterface()) {
checkModifierIsNotAllowed(modifiersList, PsiModifier.STATIC, GroovyBundle.message("interface.must.have.no.static.method"), holder);
checkModifierIsNotAllowed(modifiersList, PsiModifier.PRIVATE, GroovyBundle.message("interface.members.are.not.allowed.to.be", PsiModifier.PRIVATE), holder);
checkModifierIsNotAllowed(modifiersList, PsiModifier.PROTECTED, GroovyBundle.message("interface.members.are.not.allowed.to.be", PsiModifier.PROTECTED), holder);
}
else if (containingTypeDef.isAnonymous()) {
if (isMethodAbstract) {
final Annotation annotation = holder.createErrorAnnotation(getModifierOrList(modifiersList, PsiModifier.ABSTRACT),
GroovyBundle.message("anonymous.class.cannot.have.abstract.method"));
registerMakeAbstractMethodNotAbstractFix(annotation, method, false);
}
}
//class
else {
PsiModifierList typeDefModifiersList = containingTypeDef.getModifierList();
LOG.assertTrue(typeDefModifiersList != null, "modifiers list must be not null");
if (!typeDefModifiersList.hasModifierProperty(PsiModifier.ABSTRACT) && isMethodAbstract) {
final Annotation annotation =
holder.createErrorAnnotation(modifiersList, GroovyBundle.message("only.abstract.class.can.have.abstract.method"));
registerMakeAbstractMethodNotAbstractFix(annotation, method, true);
}
}
if (method.isConstructor()) {
checkModifierIsNotAllowed(modifiersList, PsiModifier.STATIC, GroovyBundle.message("constructor.cannot.have.static.modifier"), holder);
}
}
if (method.hasModifierProperty(PsiModifier.NATIVE) && method.getBlock() != null) {
final Annotation annotation = holder.createErrorAnnotation(getModifierOrList(modifiersList, PsiModifier.NATIVE),
GroovyBundle.message("native.methods.cannot.have.body"));
registerFix(annotation, new GrModifierFix((PsiMember)modifiersList.getParent(), PsiModifier.NATIVE, true, false, GrModifierFix.MODIFIER_LIST), modifiersList);
annotation.registerFix(QuickFixFactory.getInstance().createDeleteMethodBodyFix(method));
}
}
private static void checkForAbstractAndFinalCombination(AnnotationHolder holder, GrMember member, GrModifierList modifiersList) {
if (member.hasModifierProperty(PsiModifier.FINAL) && member.hasModifierProperty(PsiModifier.ABSTRACT)) {
final Annotation annotation = holder.createErrorAnnotation(modifiersList, GroovyBundle.message("illegal.combination.of.modifiers.abstract.and.final"));
registerFix(annotation, new GrModifierFix(member, PsiModifier.FINAL, false, false, GrModifierFix.MODIFIER_LIST), modifiersList);
registerFix(annotation, new GrModifierFix(member, PsiModifier.ABSTRACT, false, false, GrModifierFix.MODIFIER_LIST), modifiersList);
}
}
@NotNull
private static PsiElement getModifierOrList(@NotNull GrModifierList modifiersList, @GrModifier.GrModifierConstant final String modifier) {
PsiElement m = PsiUtil.findModifierInList(modifiersList, modifier);
return m != null ? m : modifiersList;
}
private static void checkOverrideAnnotation(AnnotationHolder holder, GrModifierList list, GrMethod method) {
final PsiAnnotation overrideAnnotation = list.findAnnotation("java.lang.Override");
if (overrideAnnotation == null) {
return;
}
try {
MethodSignatureBackedByPsiMethod superMethod = SuperMethodsSearch.search(method, null, true, false).findFirst();
if (superMethod == null) {
holder.createWarningAnnotation(overrideAnnotation, GroovyBundle.message("method.doesnot.override.super"));
}
}
catch (IndexNotReadyException ignored) {
//nothing to do
}
}
private static void checkTypeDefinitionModifiers(AnnotationHolder holder, GrTypeDefinition typeDefinition) {
GrModifierList modifiersList = typeDefinition.getModifierList();
if (modifiersList == null) return;
/**** class ****/
checkAccessModifiers(holder, modifiersList, typeDefinition);
checkDuplicateModifiers(holder, modifiersList, typeDefinition);
PsiClassType[] extendsListTypes = typeDefinition.getExtendsListTypes();
for (PsiClassType classType : extendsListTypes) {
PsiClass psiClass = classType.resolve();
if (psiClass != null && psiClass.hasModifierProperty(PsiModifier.FINAL)) {
final Annotation annotation = holder.createErrorAnnotation(typeDefinition.getNameIdentifierGroovy(), GroovyBundle.message("final.class.cannot.be.extended"));
registerFix(annotation, new GrModifierFix(typeDefinition, PsiModifier.FINAL, false, false, GrModifierFix.MODIFIER_LIST_OWNER), typeDefinition);
}
}
if (!typeDefinition.isEnum()) {
checkForAbstractAndFinalCombination(holder, typeDefinition, modifiersList);
}
checkModifierIsNotAllowed(modifiersList, PsiModifier.TRANSIENT, GroovyBundle.message("modifier.transient.not.allowed.here"), holder);
checkModifierIsNotAllowed(modifiersList, PsiModifier.VOLATILE, GroovyBundle.message("modifier.volatile.not.allowed.here"), holder);
/**** interface ****/
if (typeDefinition.isInterface()) {
checkModifierIsNotAllowed(modifiersList, PsiModifier.FINAL, GroovyBundle.message("intarface.cannot.have.modifier.final"), holder);
}
}
private static void checkDuplicateModifiers(AnnotationHolder holder, @NotNull GrModifierList list, PsiMember member) {
final PsiElement[] modifiers = list.getModifiers();
Set<String> set = new THashSet<String>(modifiers.length);
for (PsiElement modifier : modifiers) {
if (modifier instanceof GrAnnotation) continue;
@GrModifier.GrModifierConstant String name = modifier.getText();
if (set.contains(name)) {
final Annotation annotation = holder.createErrorAnnotation(list, GroovyBundle.message("duplicate.modifier", name));
registerFix(annotation, new GrModifierFix(member, name, false, false, GrModifierFix.MODIFIER_LIST), list);
}
else {
set.add(name);
}
}
}
private static void checkAccessModifiers(AnnotationHolder holder, @NotNull GrModifierList modifierList, PsiMember member) {
boolean hasPrivate = modifierList.hasExplicitModifier(PsiModifier.PRIVATE);
boolean hasPublic = modifierList.hasExplicitModifier(PsiModifier.PUBLIC);
boolean hasProtected = modifierList.hasExplicitModifier(PsiModifier.PROTECTED);
if (hasPrivate && hasPublic || hasPrivate && hasProtected || hasPublic && hasProtected) {
final Annotation annotation = holder.createErrorAnnotation(modifierList, GroovyBundle.message("illegal.combination.of.modifiers"));
if (hasPrivate) {
registerFix(annotation, new GrModifierFix(member, PsiModifier.PRIVATE, false, false, GrModifierFix.MODIFIER_LIST), modifierList);
}
if (hasProtected) {
registerFix(annotation, new GrModifierFix(member, PsiModifier.PROTECTED, false, false, GrModifierFix.MODIFIER_LIST), modifierList);
}
if (hasPublic) {
registerFix(annotation, new GrModifierFix(member, PsiModifier.PUBLIC, false, false, GrModifierFix.MODIFIER_LIST), modifierList);
}
}
else if (member instanceof PsiClass &&
member.getContainingClass() == null &&
GroovyConfigUtils.getInstance().isVersionAtLeast(member, GroovyConfigUtils.GROOVY2_0)) {
checkModifierIsNotAllowed(modifierList, PsiModifier.PRIVATE, GroovyBundle.message("top.level.class.maynot.have.private.modifier"), holder);
checkModifierIsNotAllowed(modifierList, PsiModifier.PROTECTED, GroovyBundle.message("top.level.class.maynot.have.protected.modifier"), holder);
}
}
private static void checkDuplicateMethod(PsiClass clazz, AnnotationHolder holder) {
MultiMap<MethodSignature, PsiMethod> map = GrClosureSignatureUtil.findRawMethodSignatures(clazz.getMethods(), clazz);
processMethodDuplicates(map, holder);
}
protected static void processMethodDuplicates(MultiMap<MethodSignature, PsiMethod> map, AnnotationHolder holder) {
for (MethodSignature signature : map.keySet()) {
Collection<PsiMethod> methods = map.get(signature);
if (methods.size() > 1) {
for (Iterator<PsiMethod> iterator = methods.iterator(); iterator.hasNext(); ) {
PsiMethod method = iterator.next();
if (method instanceof LightElement) iterator.remove();
}
if (methods.size() < 2) continue;
String signaturePresentation = GroovyPresentationUtil.getSignaturePresentation(signature);
for (PsiMethod method : methods) {
//noinspection ConstantConditions
holder.createErrorAnnotation(GrHighlightUtil.getMethodHeaderTextRange(method), GroovyBundle
.message("method.duplicate", signaturePresentation, method.getContainingClass().getName()));
}
}
}
}
private static void checkTypeDefinition(AnnotationHolder holder, GrTypeDefinition typeDefinition) {
final GroovyConfigUtils configUtils = GroovyConfigUtils.getInstance();
if (typeDefinition.isAnonymous()) {
if (!configUtils.isVersionAtLeast(typeDefinition, GroovyConfigUtils.GROOVY1_7)) {
holder.createErrorAnnotation(typeDefinition.getNameIdentifierGroovy(),
GroovyBundle.message("anonymous.classes.are.not.supported", configUtils.getSDKVersion(typeDefinition)));
}
PsiElement superClass = ((PsiAnonymousClass)typeDefinition).getBaseClassReference().resolve();
if (superClass instanceof GrTypeDefinition && ((GrTypeDefinition)superClass).isTrait()) {
holder.createErrorAnnotation(typeDefinition.getNameIdentifierGroovy(), GroovyBundle.message("anonymous.classes.cannot.be.created.from.traits"));
}
}
else if (typeDefinition.isTrait()) {
if (!configUtils.isVersionAtLeast(typeDefinition, GroovyConfigUtils.GROOVY2_3)) {
ASTNode keyword = typeDefinition.getNode().findChildByType(GroovyTokenTypes.kTRAIT);
assert keyword != null;
holder.createErrorAnnotation(keyword, GroovyBundle.message("traits.are.not.supported.in.groovy.0", configUtils.getSDKVersion(typeDefinition)));
}
}
else if (typeDefinition.getContainingClass() != null && !(typeDefinition instanceof GrEnumTypeDefinition)) {
if (!configUtils.isVersionAtLeast(typeDefinition, GroovyConfigUtils.GROOVY1_7)) {
holder.createErrorAnnotation(typeDefinition.getNameIdentifierGroovy(),
GroovyBundle.message("inner.classes.are.not.supported", configUtils.getSDKVersion(typeDefinition)));
}
}
if (typeDefinition.isAnnotationType() && typeDefinition.getContainingClass() != null) {
holder.createErrorAnnotation(typeDefinition.getNameIdentifierGroovy(), GroovyBundle.message("annotation.type.cannot.be.inner"));
}
checkDuplicateClass(typeDefinition, holder);
checkCyclicInheritance(holder, typeDefinition);
}
private static void checkCyclicInheritance(AnnotationHolder holder,
GrTypeDefinition typeDefinition) {
final PsiClass psiClass = HighlightClassUtil.getCircularClass(typeDefinition, new HashSet<PsiClass>());
if (psiClass != null) {
String qname = psiClass.getQualifiedName();
assert qname != null;
holder.createErrorAnnotation(GrHighlightUtil.getClassHeaderTextRange(typeDefinition),
GroovyBundle.message("cyclic.inheritance.involving.0", qname));
}
}
private static void checkForWildCards(AnnotationHolder holder, @Nullable GrReferenceList clause) {
if (clause == null) return;
final GrCodeReferenceElement[] elements = clause.getReferenceElementsGroovy();
for (GrCodeReferenceElement element : elements) {
final GrTypeArgumentList list = element.getTypeArgumentList();
if (list != null) {
for (GrTypeElement type : list.getTypeArgumentElements()) {
if (type instanceof GrWildcardTypeArgument) {
holder.createErrorAnnotation(type, GroovyBundle.message("wildcards.are.not.allowed.in.extends.list"));
}
}
}
}
}
private static void checkDuplicateClass(GrTypeDefinition typeDefinition, AnnotationHolder holder) {
final PsiClass containingClass = typeDefinition.getContainingClass();
String name = typeDefinition.getName();
if (containingClass != null) {
final String containingClassName = containingClass.getName();
if (containingClassName != null && containingClassName.equals(name)) {
holder.createErrorAnnotation(typeDefinition.getNameIdentifierGroovy(),
GroovyBundle.message("duplicate.inner.class", name));
}
}
final String qName = typeDefinition.getQualifiedName();
if (qName != null) {
JavaPsiFacade facade = JavaPsiFacade.getInstance(typeDefinition.getProject());
GlobalSearchScope scope = inferClassScopeForSearchingDuplicates(typeDefinition);
final PsiClass[] classes = facade.findClasses(qName, scope);
if (classes.length > 1) {
String packageName = getPackageName(typeDefinition);
if (!isScriptGeneratedClass(classes)) {
holder.createErrorAnnotation(typeDefinition.getNameIdentifierGroovy(),
GroovyBundle.message("duplicate.class", name, packageName));
}
else {
holder.createErrorAnnotation(typeDefinition.getNameIdentifierGroovy(),
GroovyBundle.message("script.generated.with.same.name", qName));
}
}
}
}
private static GlobalSearchScope inferClassScopeForSearchingDuplicates(GrTypeDefinition typeDefinition) {
GlobalSearchScope defaultScope = typeDefinition.getResolveScope();
PsiFile file = typeDefinition.getContainingFile();
if (file instanceof GroovyFile && ((GroovyFile)file).isScript()) {
Module module = ModuleUtilCore.findModuleForPsiElement(file);
if (module != null) {
return defaultScope.intersectWith(module.getModuleScope());
}
}
return defaultScope;
}
private static String getPackageName(GrTypeDefinition typeDefinition) {
final PsiFile file = typeDefinition.getContainingFile();
String packageName = "<default package>";
if (file instanceof GroovyFile) {
final String name = ((GroovyFile)file).getPackageName();
if (!name.isEmpty()) packageName = name;
}
return packageName;
}
private static boolean isScriptGeneratedClass(PsiClass[] allClasses) {
return allClasses.length == 2 && (allClasses[0] instanceof GroovyScriptClass || allClasses[1] instanceof GroovyScriptClass);
}
}