All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.plugins.groovy.annotator.GroovyAnnotator Maven / Gradle / Ivy

Go to download

A packaging of the IntelliJ Community Edition groovy-psi library. This is release number 1 of trunk branch 142.

The newest version!
/*
 * 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.GroovySyntaxHighlighter;
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 IS_INTERFACE = new Condition() {
    @Override
    public boolean value(PsiClass aClass) {
      return aClass.isInterface();
    }
  };
  private static final Condition IS_NOT_INTERFACE = new Condition() {
    @Override
    public boolean value(PsiClass aClass) {
      return !aClass.isInterface();
    }
  };
  public static final Condition IS_TRAIT = new Condition() {
    @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 usedExceptions = new ArrayList();

    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 usedInsideDisjunction = new ArrayList();
        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 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(GroovySyntaxHighlighter.MAP_KEY);
    }
    else if (ResolveUtil.isClassReference(referenceExpression)) {
      PsiElement nameElement = referenceExpression.getReferenceNameElement();
      LOG.assertTrue(nameElement != null);
      myHolder.createInfoAnnotation(nameElement, null).setTextAttributes(GroovySyntaxHighlighter.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 map = MultiMap.create();
    for (GrMethod method : methods) {
      if (!method.isConstructor()) {
        map.putValue(method.getName(), method);
      }
    }

    for (Map.Entry> entry : map.entrySet()) {
      Collection 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 collection) {
    Iterator 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 nodes = new HashMap(constructors.length);

    Set 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 checked = new HashSet();

    Set current;
    for (PsiMethod constructor : constructors) {
      if (!checked.add(constructor)) continue;

      current = new HashSet();
      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 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(GroovySyntaxHighlighter.LITERAL_CONVERSION);
      }
      final PsiElement endToken = listOrMap.getLastChild();
      if (endToken != null && endToken.getNode().getElementType() == GroovyTokenTypes.mRBRACK) {
        myHolder.createInfoAnnotation(endToken, null).setTextAttributes(GroovySyntaxHighlighter.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(GroovySyntaxHighlighter.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 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 "";
  }


  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(GroovySyntaxHighlighter.MAP_KEY);
      }
    }
  }

  private void checkNamedArgs(GrNamedArgument[] namedArguments, boolean forArgList) {
    highlightNamedArgs(namedArguments);

    MultiMap map = new MultiMap();
    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 arguments = (List)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 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() {
            @Override
            public boolean value(PsiClassType type) {
              return ref.getManager().areElementsEquivalent(type.resolve(), resolved);
            }
          }) != null) {
            holder.createInfoAnnotation(nameElement, null).setTextAttributes(GroovySyntaxHighlighter.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(GroovySyntaxHighlighter.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(GroovySyntaxHighlighter.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 set = new THashSet(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 map = GrClosureSignatureUtil.findRawMethodSignatures(clazz.getMethods(), clazz);
    processMethodDuplicates(map, holder);
  }

  protected static void processMethodDuplicates(MultiMap map, AnnotationHolder holder) {
    for (MethodSignature signature : map.keySet()) {
      Collection methods = map.get(signature);
      if (methods.size() > 1) {
        for (Iterator 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());
    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 = "";
    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);
  }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy