| /* |
| * 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.codeInspection.assignment; |
| |
| import com.intellij.codeInsight.intention.IntentionAction; |
| import com.intellij.codeInspection.*; |
| import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel; |
| import com.intellij.lang.annotation.Annotation; |
| import com.intellij.lang.annotation.AnnotationHolder; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Condition; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.profile.codeInspection.InspectionProjectProfileManager; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.PsiSubstitutorImpl; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.util.InheritanceUtil; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.util.Function; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.containers.ContainerUtil; |
| import org.jetbrains.annotations.Nls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.plugins.groovy.GroovyBundle; |
| import org.jetbrains.plugins.groovy.annotator.GrHighlightUtil; |
| import org.jetbrains.plugins.groovy.codeInspection.BaseInspection; |
| import org.jetbrains.plugins.groovy.codeInspection.BaseInspectionVisitor; |
| import org.jetbrains.plugins.groovy.codeInspection.GroovyInspectionBundle; |
| import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils; |
| import org.jetbrains.plugins.groovy.config.GroovyConfigUtils; |
| import org.jetbrains.plugins.groovy.extensions.GroovyNamedArgumentProvider; |
| import org.jetbrains.plugins.groovy.extensions.NamedArgumentDescriptor; |
| import org.jetbrains.plugins.groovy.findUsages.LiteralConstructorReference; |
| import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; |
| import org.jetbrains.plugins.groovy.lang.psi.GrControlFlowOwner; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; |
| 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.signatures.GrClosureSignature; |
| import org.jetbrains.plugins.groovy.lang.psi.api.signatures.GrSignature; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrConstructorInvocation; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration; |
| 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.arguments.GrSpreadArgument; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrReturnStatement; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrThrowStatement; |
| 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.GrLiteral; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrString; |
| 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.params.GrParameterList; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.*; |
| import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement; |
| import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement; |
| import org.jetbrains.plugins.groovy.lang.psi.dataFlow.types.TypeInferenceHelper; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.GrClosureType; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.GroovyPsiManager; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil; |
| 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.typeEnhancers.ClosureParameterEnhancer; |
| import org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.ClosureParamsEnhancer; |
| import org.jetbrains.plugins.groovy.lang.psi.util.*; |
| import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil; |
| import org.jetbrains.plugins.groovy.spock.SpockUtils; |
| |
| import javax.swing.*; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * @author Maxim.Medvedev |
| */ |
| public class GroovyAssignabilityCheckInspection extends BaseInspection { |
| private static final Logger LOG = Logger.getInstance(GroovyAssignabilityCheckInspection.class); |
| |
| private static final String SHORT_NAME = "GroovyAssignabilityCheck"; |
| |
| public boolean myHighlightAssignmentsFromVoid = true; |
| |
| @Nls |
| @NotNull |
| @Override |
| public String getGroupDisplayName() { |
| return ASSIGNMENT_ISSUES; |
| } |
| |
| @Override |
| public boolean isEnabledByDefault() { |
| return true; |
| } |
| |
| @Nullable |
| @Override |
| public JComponent createOptionsPanel() { |
| final MultipleCheckboxOptionsPanel optionsPanel = new MultipleCheckboxOptionsPanel(this); |
| optionsPanel.addCheckbox(GroovyInspectionBundle.message("highlight.assignments.from.void"), "myHighlightAssignmentsFromVoid"); |
| return optionsPanel; |
| } |
| |
| @Nls |
| @NotNull |
| @Override |
| public String getDisplayName() { |
| return "Incompatible type assignments"; |
| } |
| |
| @Override |
| protected String buildErrorString(Object... args) { |
| return (String)args[0]; |
| } |
| |
| @NotNull |
| @Override |
| protected BaseInspectionVisitor buildVisitor() { |
| return new MyVisitor(); |
| } |
| |
| private static class MyVisitor extends BaseInspectionVisitor { |
| private void checkAssignability(@NotNull PsiType expectedType, @NotNull GrExpression expression, PsiElement toHighlight) { |
| if (PsiUtil.isRawClassMemberAccess(expression)) return; |
| if (checkForImplicitEnumAssigning(expectedType, expression, expression)) return; |
| final PsiType rType = expression.getType(); |
| if (rType == null) return; |
| |
| if (PsiUtil.isVoidMethodCall(expression)) { |
| if (isHighlightAssignmentsFromVoid(expression)) { |
| registerError(toHighlight, GroovyBundle.message("cannot.assign", PsiType.VOID.getPresentableText(), |
| expectedType.getPresentableText())); |
| } |
| return; |
| } |
| |
| if (!TypesUtil.isAssignable(expectedType, rType, expression)) { |
| final List<LocalQuickFix> fixes = ContainerUtil.newArrayList(); |
| fixes.add(new GrCastFix(expectedType, expression)); |
| |
| String varName = getLValueVarName(toHighlight); |
| if (varName != null) { |
| fixes.add(new GrChangeVariableType(rType, varName)); |
| } |
| |
| final String message = GroovyBundle.message("cannot.assign", rType.getPresentableText(), expectedType.getPresentableText()); |
| registerError(toHighlight, message, fixes.toArray(new LocalQuickFix[fixes.size()]), ProblemHighlightType.GENERIC_ERROR_OR_WARNING); |
| } |
| } |
| |
| private static boolean isHighlightAssignmentsFromVoid(PsiElement place) { |
| final GroovyAssignabilityCheckInspection instance = getInspectionInstance(place.getContainingFile(), place.getProject()); |
| if (instance != null) { |
| return instance.myHighlightAssignmentsFromVoid; |
| } |
| |
| return false; |
| } |
| |
| private static GroovyAssignabilityCheckInspection getInspectionInstance(PsiFile file, Project project) { |
| final InspectionProfile profile = InspectionProjectProfileManager.getInstance(project).getInspectionProfile(); |
| return (GroovyAssignabilityCheckInspection)profile.getUnwrappedTool(SHORT_NAME, file); |
| } |
| |
| |
| @Nullable |
| private static String getLValueVarName(PsiElement highlight) { |
| final PsiElement parent = highlight.getParent(); |
| if (parent instanceof GrVariable) { |
| return ((GrVariable)parent).getName(); |
| } |
| else if (highlight instanceof GrReferenceExpression && |
| parent instanceof GrAssignmentExpression && |
| ((GrAssignmentExpression)parent).getLValue() == highlight) { |
| final PsiElement resolved = ((GrReferenceExpression)highlight).resolve(); |
| if (resolved instanceof GrVariable && PsiUtil.isLocalVariable(resolved)) { |
| return ((GrVariable)resolved).getName(); |
| } |
| } |
| |
| return null; |
| } |
| |
| private void checkAssignability(@NotNull PsiType lType, |
| @Nullable PsiType rType, |
| @NotNull GroovyPsiElement context, |
| @NotNull final PsiElement elementToHighlight) { |
| if (rType == null) return; |
| if (!TypesUtil.isAssignable(lType, rType, context)) { |
| final String message = GroovyBundle.message("cannot.assign", rType.getPresentableText(), lType.getPresentableText()); |
| registerError(elementToHighlight, message, LocalQuickFix.EMPTY_ARRAY, ProblemHighlightType.GENERIC_ERROR_OR_WARNING); |
| } |
| } |
| |
| |
| private boolean checkForImplicitEnumAssigning(PsiType expectedType, GrExpression expression, GroovyPsiElement element) { |
| if (!(expectedType instanceof PsiClassType)) return false; |
| |
| if (!GroovyConfigUtils.getInstance().isVersionAtLeast(element, GroovyConfigUtils.GROOVY1_8)) return false; |
| |
| final PsiClass resolved = ((PsiClassType)expectedType).resolve(); |
| if (resolved == null || !resolved.isEnum()) return false; |
| |
| final PsiType type = expression.getType(); |
| if (type == null) return false; |
| |
| if (!type.equalsToText(GroovyCommonClassNames.GROOVY_LANG_GSTRING) && !type.equalsToText(CommonClassNames.JAVA_LANG_STRING)) { |
| return false; |
| } |
| |
| final Object result = GroovyConstantExpressionEvaluator.evaluate(expression); |
| if (result == null || !(result instanceof String)) { |
| registerError(element, ProblemHighlightType.WEAK_WARNING, |
| GroovyBundle.message("cannot.assign.string.to.enum.0", expectedType.getPresentableText())); |
| } |
| else { |
| final PsiField field = resolved.findFieldByName((String)result, true); |
| if (!(field instanceof PsiEnumConstant)) { |
| registerError(element, GroovyBundle.message("cannot.find.enum.constant.0.in.enum.1", result, expectedType.getPresentableText())); |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public void visitReturnStatement(GrReturnStatement returnStatement) { |
| final GrExpression value = returnStatement.getReturnValue(); |
| if (value == null || isNewInstanceInitialingByTuple(value)) return; |
| |
| final PsiType returnType = PsiImplUtil.inferReturnType(returnStatement); |
| if (returnType != null) { |
| checkAssignability(returnType, value, returnStatement.getReturnWord()); |
| } |
| } |
| |
| @Override |
| public void visitExpression(GrExpression expression) { |
| if (PsiUtil.isExpressionStatement(expression)) { |
| final PsiType returnType = PsiImplUtil.inferReturnType(expression); |
| final GrControlFlowOwner flowOwner = ControlFlowUtils.findControlFlowOwner(expression); |
| if (flowOwner != null && returnType != null && returnType != PsiType.VOID) { |
| if (ControlFlowUtils.isReturnValue(expression, flowOwner) && |
| !isNewInstanceInitialingByTuple(expression) && |
| !PsiUtil.isVoidMethodCall(expression)) { |
| checkAssignability(returnType, expression, getExpressionPartToHighlight(expression)); |
| } |
| } |
| } |
| } |
| |
| protected boolean shouldProcess(GrMember member) { |
| return !GroovyPsiManager.getInstance(member.getProject()).isCompileStatic(member); |
| } |
| |
| @Override |
| public void visitAssignmentExpression(GrAssignmentExpression assignment) { |
| GrExpression lValue = assignment.getLValue(); |
| if (lValue instanceof GrIndexProperty) return; |
| if (!PsiUtil.mightBeLValue(lValue)) return; |
| |
| IElementType opToken = assignment.getOperationTokenType(); |
| if (opToken != GroovyTokenTypes.mASSIGN) return; |
| |
| GrExpression rValue = assignment.getRValue(); |
| if (rValue == null) return; |
| |
| if (lValue instanceof GrTupleExpression) { |
| checkTupleAssignment(((GrTupleExpression)lValue), rValue); |
| } |
| else { |
| checkAssignment(lValue, rValue); |
| } |
| } |
| |
| private void checkTupleAssignment(GrTupleExpression tupleExpression, GrExpression initializer) { |
| GrExpression[] lValues = tupleExpression.getExpressions(); |
| if (initializer instanceof GrListOrMap) { |
| GrExpression[] initializers = ((GrListOrMap)initializer).getInitializers(); |
| for (int i = 0; i < lValues.length; i++) { |
| GrExpression lValue = lValues[i]; |
| if (initializers.length >= i) break; |
| GrExpression rValue = initializers[i]; |
| checkAssignment(lValue, rValue); |
| } |
| } |
| else { |
| PsiType type = initializer.getType(); |
| PsiType rType = com.intellij.psi.util.PsiUtil.extractIterableTypeParameter(type, false); |
| |
| for (GrExpression lValue : lValues) { |
| PsiType lType = lValue.getNominalType(); |
| // For assignments with spread dot |
| if (PsiImplUtil.isSpreadAssignment(lValue)) { |
| final PsiType argType = extractIterableArg(lType); |
| if (argType != null && rType != null) { |
| checkAssignability(argType, rType, tupleExpression, getExpressionPartToHighlight(lValue)); |
| } |
| return; |
| } |
| if (lValue instanceof GrReferenceExpression && ((GrReferenceExpression)lValue).resolve() instanceof GrReferenceExpression) { |
| //lvalue is not-declared variable |
| return; |
| } |
| |
| if (lType != null && rType != null) { |
| checkAssignability(lType, rType, tupleExpression, getExpressionPartToHighlight(lValue)); |
| } |
| } |
| } |
| } |
| |
| private void checkAssignment(GrExpression lValue, GrExpression rValue) { |
| PsiType lType = lValue.getNominalType(); |
| PsiType rType = rValue.getType(); |
| if (PsiImplUtil.isSpreadAssignment(lValue)) { |
| final PsiType argType = extractIterableArg(lType); |
| if (argType != null && rValue != null) { |
| checkAssignability(argType, rValue, getExpressionPartToHighlight(lValue)); |
| } |
| return; |
| } |
| if (lValue instanceof GrReferenceExpression && ((GrReferenceExpression)lValue).resolve() instanceof GrReferenceExpression) { |
| //lvalue is not-declared variable |
| return; |
| } |
| |
| if (isNewInstanceInitialingByTuple(rValue)) { |
| // new instance initializing e.g.: X x; x = [1, 2] |
| return; |
| } |
| |
| if (lType != null && rType != null) { |
| checkAssignability(lType, rValue, getExpressionPartToHighlight(lValue)); |
| } |
| } |
| |
| @Nullable |
| private static PsiType extractIterableArg(@Nullable PsiType type) { |
| return com.intellij.psi.util.PsiUtil.extractIterableTypeParameter(type, false); |
| } |
| |
| @Override |
| public void visitVariable(GrVariable variable) { |
| PsiType varType = variable.getType(); |
| |
| PsiElement parent = variable.getParent(); |
| //check tuple assignment: def (int x, int y) = foo() |
| if (parent instanceof GrVariableDeclaration && ((GrVariableDeclaration)parent).isTuple()) { |
| GrVariableDeclaration tuple = (GrVariableDeclaration)parent; |
| GrExpression initializer = tuple.getTupleInitializer(); |
| if (initializer == null) return; |
| if (!(initializer instanceof GrListOrMap)) { |
| PsiType type = initializer.getType(); |
| if (type == null) return; |
| PsiType valueType = com.intellij.psi.util.PsiUtil.extractIterableTypeParameter(type, false); |
| checkAssignability(varType, valueType, tuple, variable.getNameIdentifierGroovy()); |
| return; |
| } |
| } |
| else if (parent instanceof GrForInClause) { |
| GrExpression iterated = ((GrForInClause)parent).getIteratedExpression(); |
| if (iterated == null) return; |
| |
| PsiType iteratedType = ClosureParameterEnhancer.findTypeForIteration(iterated, parent); |
| if (iteratedType == null) return; |
| |
| checkAssignability(varType, iteratedType, iterated, variable.getNameIdentifierGroovy()); |
| return; |
| } |
| |
| GrExpression initializer = variable.getInitializerGroovy(); |
| if (initializer == null) return; |
| |
| PsiType rType = initializer.getType(); |
| if (rType == null) return; |
| if (isNewInstanceInitialingByTuple(initializer)) { |
| return; |
| } |
| |
| if (variable instanceof GrParameter && ((GrParameter)variable).getDeclarationScope() instanceof GrMethod) { |
| final GrMethod method = (GrMethod)((GrParameter)variable).getDeclarationScope(); |
| final PsiTypeParameter[] parameters = method.getTypeParameters(); |
| |
| Map<PsiTypeParameter, PsiType> map = ContainerUtil.newHashMap(); |
| for (PsiTypeParameter parameter : parameters) { |
| final PsiClassType[] types = parameter.getSuperTypes(); |
| |
| if (types.length == 1) { |
| map.put(parameter, PsiWildcardType.createExtends(variable.getManager(), types[0])); |
| } |
| else { |
| map.put(parameter, PsiWildcardType.createExtends(variable.getManager(), PsiIntersectionType.createIntersection(types))); |
| } |
| } |
| PsiSubstitutor substitutor = PsiSubstitutorImpl.createSubstitutor(map); |
| checkAssignability(substitutor.substitute(varType), initializer, variable.getNameIdentifierGroovy()); |
| return; |
| } |
| |
| checkAssignability(varType, initializer, variable.getNameIdentifierGroovy()); |
| } |
| |
| private static boolean isNewInstanceInitialingByTuple(GrExpression initializer) { |
| return initializer instanceof GrListOrMap && |
| initializer.getReference() instanceof LiteralConstructorReference && |
| ((LiteralConstructorReference)initializer.getReference()).getConstructedClassType() != null; |
| } |
| |
| @Override |
| public void visitNewExpression(GrNewExpression newExpression) { |
| if (newExpression.getArrayCount() > 0) return; |
| |
| GrCodeReferenceElement refElement = newExpression.getReferenceElement(); |
| if (refElement == null) return; |
| |
| GrNewExpressionInfo info = new GrNewExpressionInfo(newExpression); |
| checkConstructorCall(info); |
| } |
| |
| private void checkConstructorCall(ConstructorCallInfo<?> info) { |
| if (hasErrorElements(info.getArgumentList())) return; |
| |
| if (!checkCannotInferArgumentTypes(info)) return; |
| final GroovyResolveResult constructorResolveResult = info.advancedResolve(); |
| final PsiElement constructor = constructorResolveResult.getElement(); |
| |
| if (constructor != null) { |
| if (!checkConstructorApplicability(constructorResolveResult, info, true)) return; |
| } |
| else { |
| final GroovyResolveResult[] results = info.multiResolve(); |
| if (results.length > 0) { |
| for (GroovyResolveResult result : results) { |
| PsiElement resolved = result.getElement(); |
| if (resolved instanceof PsiMethod) { |
| if (!checkConstructorApplicability(result, info, false)) return; |
| } |
| } |
| registerError(info.getElementToHighlight(), GroovyBundle.message("constructor.call.is.ambiguous")); |
| } |
| else { |
| final GrExpression[] expressionArguments = info.getExpressionArguments(); |
| final boolean hasClosureArgs = info.getClosureArguments().length > 0; |
| final boolean hasNamedArgs = info.getNamedArguments().length > 0; |
| if (hasClosureArgs || |
| hasNamedArgs && expressionArguments.length > 0 || |
| !hasNamedArgs && expressionArguments.length > 0 && !isOnlyOneMapParam(expressionArguments)) { |
| final GroovyResolveResult[] resolveResults = info.multiResolveClass(); |
| if (resolveResults.length == 1) { |
| final PsiElement element = resolveResults[0].getElement(); |
| if (element instanceof PsiClass) { |
| registerError(info.getElementToHighlight(), |
| GroovyBundle.message("cannot.apply.default.constructor", ((PsiClass)element).getName())); |
| return; |
| } |
| } |
| } |
| } |
| } |
| |
| checkNamedArgumentsType(info); |
| } |
| |
| private static boolean isOnlyOneMapParam(GrExpression[] exprs) { |
| if (!(exprs.length == 1)) return false; |
| |
| final GrExpression e = exprs[0]; |
| return TypesUtil.isAssignableByMethodCallConversion(TypesUtil.createTypeByFQClassName(CommonClassNames.JAVA_UTIL_MAP, e), e.getType(), e); |
| } |
| |
| @NotNull |
| private static PsiElement getExpressionPartToHighlight(@NotNull GrExpression expr) { |
| if (expr instanceof GrClosableBlock) { |
| return ((GrClosableBlock)expr).getLBrace(); |
| } |
| |
| return expr; |
| } |
| |
| @Override |
| public void visitListOrMap(GrListOrMap listOrMap) { |
| |
| final PsiReference reference = listOrMap.getReference(); |
| if (!(reference instanceof LiteralConstructorReference)) return; |
| |
| final GroovyResolveResult[] results = ((LiteralConstructorReference)reference).multiResolve(false); |
| if (results.length == 0) return; |
| |
| checkConstructorCall(new GrListOrMapInfo(listOrMap)); |
| } |
| |
| @Override |
| public void visitThrowStatement(GrThrowStatement throwStatement) { |
| |
| final GrExpression exception = throwStatement.getException(); |
| if (exception != null) { |
| final PsiElement throwWord = throwStatement.getFirstChild(); |
| checkAssignability(PsiType.getJavaLangThrowable(throwStatement.getManager(), throwStatement.getResolveScope()), exception, throwWord); |
| } |
| } |
| |
| private boolean checkConstructorApplicability(GroovyResolveResult constructorResolveResult, |
| CallInfo<?> info, |
| boolean checkUnknownArgs) { |
| final PsiElement element = constructorResolveResult.getElement(); |
| LOG.assertTrue(element instanceof PsiMethod && ((PsiMethod)element).isConstructor(), element); |
| final PsiMethod constructor = (PsiMethod)element; |
| |
| final GrArgumentList argList = info.getArgumentList(); |
| if (argList != null) { |
| final GrExpression[] exprArgs = argList.getExpressionArguments(); |
| if (exprArgs.length == 0 && !PsiUtil.isConstructorHasRequiredParameters(constructor)) return true; |
| } |
| |
| PsiType[] types = info.getArgumentTypes(); |
| PsiClass containingClass = constructor.getContainingClass(); |
| if (types != null && containingClass != null) { |
| final PsiType[] newTypes = GrInnerClassConstructorUtil.addEnclosingArgIfNeeded(types, info.getCall(), containingClass); |
| if (newTypes.length != types.length) { |
| return checkMethodApplicability(constructorResolveResult, checkUnknownArgs, new DelegatingCallInfo(info) { |
| @Nullable |
| @Override |
| public PsiType[] getArgumentTypes() { |
| return newTypes; |
| } |
| }); |
| } |
| } |
| |
| return checkMethodApplicability(constructorResolveResult, checkUnknownArgs, info); |
| } |
| |
| @Override |
| public void visitConstructorInvocation(GrConstructorInvocation invocation) { |
| GrConstructorInvocationInfo info = new GrConstructorInvocationInfo(invocation); |
| checkConstructorCall(info); |
| checkNamedArgumentsType(info); |
| } |
| |
| @Override |
| public void visitIndexProperty(GrIndexProperty expression) { |
| checkIndexProperty(new GrIndexPropertyInfo(expression)); |
| } |
| |
| private void checkIndexProperty(CallInfo<? extends GrIndexProperty> info) { |
| if (hasErrorElements(info.getArgumentList())) return; |
| |
| if (!checkCannotInferArgumentTypes(info)) return; |
| |
| final PsiType type = info.getQualifierInstanceType(); |
| final PsiType[] types = info.getArgumentTypes(); |
| |
| if (checkSimpleArrayAccess(info, type, types)) return; |
| |
| final GroovyResolveResult[] results = info.multiResolve(); |
| final GroovyResolveResult resolveResult = info.advancedResolve(); |
| |
| if (resolveResult.getElement() != null) { |
| PsiElement resolved = resolveResult.getElement(); |
| |
| if (resolved instanceof PsiMethod && !resolveResult.isInvokedOnProperty()) { |
| checkMethodApplicability(resolveResult, true, info); |
| } |
| else if (resolved instanceof GrField) { |
| checkCallApplicability(((GrField)resolved).getTypeGroovy(), true, info); |
| } |
| else if (resolved instanceof PsiField) { |
| checkCallApplicability(((PsiField)resolved).getType(), true, info); |
| } |
| } |
| else if (results.length > 0) { |
| for (GroovyResolveResult result : results) { |
| PsiElement resolved = result.getElement(); |
| if (resolved instanceof PsiMethod && !result.isInvokedOnProperty()) { |
| if (!checkMethodApplicability(result, false, info)) return; |
| } |
| else if (resolved instanceof GrField) { |
| if (!checkCallApplicability(((GrField)resolved).getTypeGroovy(), false, info)) return; |
| } |
| else if (resolved instanceof PsiField) { |
| if (!checkCallApplicability(((PsiField)resolved).getType(), false, info)) return; |
| } |
| } |
| |
| registerError(info.getElementToHighlight(), GroovyBundle.message("method.call.is.ambiguous")); |
| } |
| else { |
| final String typesString = buildArgTypesList(types); |
| registerError(info.getElementToHighlight(), GroovyBundle.message("cannot.find.operator.overload.method", typesString)); |
| } |
| } |
| |
| private static boolean checkSimpleArrayAccess(CallInfo<? extends GrIndexProperty> info, PsiType type, PsiType[] types) { |
| if (!(type instanceof PsiArrayType)) return false; |
| |
| assert types != null; |
| |
| if (PsiUtil.isLValue(info.getCall())) { |
| if (types.length == 2 && |
| TypesUtil.isAssignable(PsiType.INT, types[0], info.getCall()) && |
| TypesUtil.isAssignable(((PsiArrayType)type).getComponentType(), types[1], info.getCall())) { |
| return true; |
| } |
| } |
| else { |
| if (types.length == 1 && TypesUtil.isAssignable(PsiType.INT, types[0], info.getCall())) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| private boolean checkCannotInferArgumentTypes(CallInfo info) { |
| if (info.getArgumentTypes() != null) { |
| return true; |
| } |
| else { |
| highlightUnknownArgs(info); |
| return false; |
| } |
| } |
| |
| @Override |
| public void visitMethodCallExpression(GrMethodCallExpression methodCallExpression) { |
| checkMethodCall(new GrMethodCallInfo(methodCallExpression)); |
| } |
| |
| @Override |
| public void visitApplicationStatement(GrApplicationStatement applicationStatement) { |
| checkMethodCall(new GrMethodCallInfo(applicationStatement)); |
| } |
| |
| @Override |
| public void visitBinaryExpression(GrBinaryExpression binary) { |
| checkOperator(new GrBinaryExprInfo(binary)); |
| } |
| |
| @Override |
| public void visitEnumConstant(GrEnumConstant enumConstant) { |
| GrEnumConstantInfo info = new GrEnumConstantInfo(enumConstant); |
| checkConstructorCall(info); |
| checkNamedArgumentsType(info); |
| } |
| |
| private void checkNamedArgumentsType(CallInfo<?> info) { |
| GroovyPsiElement rawCall = info.getCall(); |
| if (!(rawCall instanceof GrCall)) return; |
| GrCall call = (GrCall)rawCall; |
| |
| GrNamedArgument[] namedArguments = PsiUtil.getFirstMapNamedArguments(call); |
| |
| if (namedArguments.length == 0) return; |
| |
| Map<String, NamedArgumentDescriptor> map = GroovyNamedArgumentProvider.getNamedArgumentsFromAllProviders(call, null, false); |
| if (map == null) return; |
| |
| for (GrNamedArgument namedArgument : namedArguments) { |
| String labelName = namedArgument.getLabelName(); |
| |
| NamedArgumentDescriptor descriptor = map.get(labelName); |
| |
| if (descriptor == null) continue; |
| |
| GrExpression namedArgumentExpression = namedArgument.getExpression(); |
| if (namedArgumentExpression == null) continue; |
| |
| if (isNewInstanceInitialingByTuple(namedArgumentExpression)) continue; |
| |
| if (PsiUtil.isRawClassMemberAccess(namedArgumentExpression)) continue; |
| |
| PsiType expressionType = TypesUtil.boxPrimitiveType(namedArgumentExpression.getType(), call.getManager(), call.getResolveScope()); |
| if (expressionType == null) continue; |
| |
| if (!descriptor.checkType(expressionType, call)) { |
| registerError(namedArgumentExpression, "Type of argument '" + labelName + "' can not be '" + expressionType.getPresentableText() + "'"); |
| } |
| } |
| } |
| |
| @Override |
| public void visitParameterList(final GrParameterList parameterList) { |
| PsiElement parent = parameterList.getParent(); |
| if (parent instanceof GrClosableBlock) { |
| |
| GrParameter[] parameters = parameterList.getParameters(); |
| if (parameters.length > 0) { |
| List<PsiType[]> signatures = ClosureParamsEnhancer.findFittingSignatures((GrClosableBlock)parent); |
| final List<PsiType> paramTypes = ContainerUtil.map(parameters, new Function<GrParameter, PsiType>() { |
| @Override |
| public PsiType fun(GrParameter parameter) { |
| return parameter.getType(); |
| } |
| }); |
| |
| if (signatures.size() > 1) { |
| PsiType[] fittingSignature = ContainerUtil.find(signatures, new Condition<PsiType[]>() { |
| @Override |
| public boolean value(PsiType[] types) { |
| for (int i = 0; i < types.length; i++) { |
| if (!typesAreEqual(types[i], paramTypes.get(i), parameterList)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| }); |
| |
| if (fittingSignature == null) { |
| registerError(parameterList, GroovyInspectionBundle.message("no.applicable.signature.found")); |
| } |
| } |
| else if (signatures.size() == 1) { |
| PsiType[] types = signatures.get(0); |
| for (int i = 0; i < types.length; i++) { |
| GrTypeElement typeElement = parameters[i].getTypeElementGroovy(); |
| if (typeElement == null) continue; |
| PsiType expected = types[i]; |
| PsiType actual = paramTypes.get(i); |
| if (!typesAreEqual(expected, actual, parameterList)) { |
| registerError(typeElement, GroovyInspectionBundle.message("expected.type.0", expected.getPresentableText())); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private static boolean typesAreEqual(@NotNull PsiType expected, @NotNull PsiType actual, @NotNull PsiElement context) { |
| return TypesUtil.isAssignableByMethodCallConversion(expected, actual, context) && |
| TypesUtil.isAssignableByMethodCallConversion(actual, expected, context); |
| } |
| |
| |
| /** |
| * checks only children of e |
| */ |
| private static boolean hasErrorElements(@Nullable PsiElement e) { |
| if (e == null) return false; |
| |
| for (PsiElement child = e.getFirstChild(); child != null; child = child.getNextSibling()) { |
| if (child instanceof PsiErrorElement) return true; |
| } |
| return false; |
| } |
| |
| private void checkOperator(CallInfo<? extends GrBinaryExpression> info) { |
| if (hasErrorElements(info.getCall())) return; |
| |
| if (isSpockTimesOperator(info.getCall())) return; |
| |
| GroovyResolveResult[] results = info.multiResolve(); |
| GroovyResolveResult resolveResult = info.advancedResolve(); |
| |
| if (isOperatorWithSimpleTypes(info.getCall(), resolveResult)) return; |
| |
| if (!checkCannotInferArgumentTypes(info)) return; |
| |
| if (resolveResult.getElement() != null) { |
| checkMethodApplicability(resolveResult, true, info); |
| } |
| else if (results.length > 0) { |
| for (GroovyResolveResult result : results) { |
| if (!checkMethodApplicability(result, false, info)) return; |
| } |
| |
| registerError(info.getElementToHighlight(), GroovyBundle.message("method.call.is.ambiguous")); |
| } |
| } |
| |
| private static boolean isSpockTimesOperator(GrBinaryExpression call) { |
| if (call.getOperationTokenType() == GroovyTokenTypes.mSTAR && PsiUtil.isExpressionStatement(call)) { |
| GrExpression operand = call.getLeftOperand(); |
| if (operand instanceof GrLiteral && TypesUtil.isNumericType(operand.getType())) { |
| PsiClass aClass = PsiUtil.getContextClass(call); |
| if (InheritanceUtil.isInheritor(aClass, false, SpockUtils.SPEC_CLASS_NAME)) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| private static boolean isOperatorWithSimpleTypes(GrBinaryExpression binary, GroovyResolveResult result) { |
| if (result.getElement() != null && result.isApplicable()) { |
| return false; |
| } |
| |
| GrExpression left = binary.getLeftOperand(); |
| GrExpression right = binary.getRightOperand(); |
| |
| PsiType ltype = left.getType(); |
| PsiType rtype = right != null ? right.getType() : null; |
| |
| return TypesUtil.isNumericType(ltype) && (rtype == null || TypesUtil.isNumericType(rtype)); |
| } |
| |
| private void checkMethodCall(CallInfo<? extends GrMethodCall> info) { |
| if (hasErrorElements(info.getArgumentList())) return; |
| |
| if (info.getInvokedExpression() instanceof GrReferenceExpression) { |
| final GrReferenceExpression referenceExpression = (GrReferenceExpression)info.getInvokedExpression(); |
| GroovyResolveResult resolveResult = info.advancedResolve(); |
| GroovyResolveResult[] results = info.multiResolve(); |
| |
| PsiElement resolved = resolveResult.getElement(); |
| if (resolved == null) { |
| GrExpression qualifier = referenceExpression.getQualifierExpression(); |
| if (qualifier == null && GrHighlightUtil.isDeclarationAssignment(referenceExpression)) return; |
| } |
| |
| if (!checkCannotInferArgumentTypes(info)) return; |
| |
| final PsiType type = referenceExpression.getType(); |
| if (resolved != null) { |
| if (resolved instanceof PsiMethod && !resolveResult.isInvokedOnProperty()) { |
| checkMethodApplicability(resolveResult, true, info); |
| } |
| else { |
| checkCallApplicability(type, true, info); |
| } |
| } |
| else if (results.length > 0) { |
| for (GroovyResolveResult result : results) { |
| PsiElement current = result.getElement(); |
| if (current instanceof PsiMethod && !result.isInvokedOnProperty()) { |
| if (!checkMethodApplicability(result, false, info)) return; |
| } |
| else { |
| if (!checkCallApplicability(type, false, info)) return; |
| } |
| } |
| |
| registerError(info.getElementToHighlight(), GroovyBundle.message("method.call.is.ambiguous")); |
| } |
| } |
| else if (info.getInvokedExpression() != null) { //it checks in visitRefExpr(...) |
| final PsiType type = info.getInvokedExpression().getType(); |
| checkCallApplicability(type, true, info); |
| } |
| |
| checkNamedArgumentsType(info); |
| } |
| |
| private void highlightInapplicableMethodUsage(@NotNull GroovyResolveResult methodResolveResult, |
| @NotNull CallInfo info, |
| @NotNull PsiMethod method) { |
| final PsiClass containingClass = |
| method instanceof GrGdkMethod ? ((GrGdkMethod)method).getStaticMethod().getContainingClass() : method.getContainingClass(); |
| |
| PsiType[] argumentTypes = info.getArgumentTypes(); |
| if (containingClass == null) { |
| registerCannotApplyError(method.getName(), info); |
| return; |
| } |
| final String typesString = buildArgTypesList(argumentTypes); |
| final PsiElementFactory factory = JavaPsiFacade.getElementFactory(method.getProject()); |
| final PsiClassType containingType = factory.createType(containingClass, methodResolveResult.getSubstitutor()); |
| final String canonicalText = containingType.getInternalCanonicalText(); |
| String message = method.isConstructor() ? GroovyBundle.message("cannot.apply.constructor", method.getName(), canonicalText, typesString) |
| : GroovyBundle.message("cannot.apply.method1", method.getName(), canonicalText, typesString); |
| |
| registerError(info.getElementToHighlight(), message, |
| genCastFixes(GrClosureSignatureUtil.createSignature(methodResolveResult), argumentTypes, info.getArgumentList()), |
| ProblemHighlightType.GENERIC_ERROR_OR_WARNING); |
| } |
| |
| private static LocalQuickFix[] genCastFixes(GrSignature signature, PsiType[] argumentTypes, @Nullable GrArgumentList argumentList) { |
| if (argumentList == null) return LocalQuickFix.EMPTY_ARRAY; |
| final List<GrExpression> args = getExpressionArgumentsOfCall(argumentList); |
| |
| if (args == null) { |
| return LocalQuickFix.EMPTY_ARRAY; |
| } |
| |
| final List<GrClosureSignature> signatures = GrClosureSignatureUtil.generateSimpleSignatures(signature); |
| |
| List<Pair<Integer, PsiType>> allErrors = new ArrayList<Pair<Integer, PsiType>>(); |
| for (GrClosureSignature closureSignature : signatures) { |
| final GrClosureSignatureUtil.MapResultWithError<PsiType> map = |
| GrClosureSignatureUtil.mapSimpleSignatureWithErrors(closureSignature, argumentTypes, Function.ID, argumentList, 1); |
| if (map != null) { |
| final List<Pair<Integer, PsiType>> errors = map.getErrors(); |
| for (Pair<Integer, PsiType> error : errors) { |
| if (!(error.first == 0 && PsiImplUtil.hasNamedArguments(argumentList))) { |
| allErrors.add(error); |
| } |
| } |
| } |
| } |
| |
| final ArrayList<LocalQuickFix> fixes = new ArrayList<LocalQuickFix>(); |
| for (Pair<Integer, PsiType> error : allErrors) { |
| fixes.add(new ParameterCastFix(error.first, error.second, args.get(error.first))); |
| } |
| |
| return fixes.toArray(new LocalQuickFix[fixes.size()]); |
| } |
| |
| private boolean checkCallApplicability(PsiType type, boolean checkUnknownArgs, CallInfo info) { |
| |
| PsiType[] argumentTypes = info.getArgumentTypes(); |
| GrExpression invoked = info.getInvokedExpression(); |
| if (invoked == null) return true; |
| |
| if (type instanceof GrClosureType) { |
| if (argumentTypes == null) return true; |
| |
| GrClosureSignatureUtil.ApplicabilityResult result = PsiUtil.isApplicableConcrete(argumentTypes, (GrClosureType)type, info.getCall()); |
| switch (result) { |
| case inapplicable: |
| registerCannotApplyError(invoked.getText(), info); |
| return false; |
| case canBeApplicable: |
| if (checkUnknownArgs) { |
| highlightUnknownArgs(info); |
| } |
| return !checkUnknownArgs; |
| default: |
| return true; |
| } |
| } |
| else if (type != null) { |
| final GroovyResolveResult[] calls = ResolveUtil.getMethodCandidates(type, "call", invoked, argumentTypes); |
| for (GroovyResolveResult result : calls) { |
| PsiElement resolved = result.getElement(); |
| if (resolved instanceof PsiMethod && !result.isInvokedOnProperty()) { |
| if (!checkMethodApplicability(result, checkUnknownArgs, info)) return false; |
| } |
| else if (resolved instanceof PsiField) { |
| if (!checkCallApplicability(((PsiField)resolved).getType(), checkUnknownArgs && calls.length == 1, info)) return false; |
| } |
| } |
| if (calls.length == 0 && !(invoked instanceof GrString)) { |
| registerCannotApplyError(invoked.getText(), info); |
| } |
| return true; |
| } |
| return true; |
| } |
| |
| private void registerCannotApplyError(String invokedText, CallInfo info) { |
| final String typesString = buildArgTypesList(info.getArgumentTypes()); |
| registerError(info.getElementToHighlight(), GroovyBundle.message("cannot.apply.method.or.closure", invokedText, typesString)); |
| } |
| |
| private static String buildArgTypesList(PsiType[] argTypes) { |
| StringBuilder builder = new StringBuilder(); |
| builder.append("("); |
| for (int i = 0; i < argTypes.length; i++) { |
| if (i > 0) { |
| builder.append(", "); |
| } |
| PsiType argType = argTypes[i]; |
| builder.append(argType != null ? argType.getInternalCanonicalText() : "?"); |
| } |
| builder.append(")"); |
| return builder.toString(); |
| } |
| |
| private boolean checkMethodApplicability(@NotNull GroovyResolveResult methodResolveResult, |
| boolean checkUnknownArgs, |
| @NotNull CallInfo info) { |
| final PsiElement element = methodResolveResult.getElement(); |
| if (!(element instanceof PsiMethod)) return true; |
| if (element instanceof GrBuilderMethod) return true; |
| |
| final PsiMethod method = (PsiMethod)element; |
| if ("call".equals(method.getName()) && info.getInvokedExpression() instanceof GrReferenceExpression) { |
| final GrExpression qualifierExpression = ((GrReferenceExpression)info.getInvokedExpression()).getQualifierExpression(); |
| if (qualifierExpression != null) { |
| final PsiType type = qualifierExpression.getType(); |
| if (type instanceof GrClosureType) { |
| GrClosureSignatureUtil.ApplicabilityResult result = PsiUtil.isApplicableConcrete(info.getArgumentTypes(), (GrClosureType)type, info.getInvokedExpression()); |
| switch (result) { |
| case inapplicable: |
| highlightInapplicableMethodUsage(methodResolveResult, info, method); |
| return false; |
| case canBeApplicable: |
| if (checkUnknownArgs) { |
| highlightUnknownArgs(info); |
| } |
| return !checkUnknownArgs; |
| default: |
| return true; |
| } |
| } |
| } |
| } |
| |
| if (method instanceof GrGdkMethod && info.getInvokedExpression() instanceof GrReferenceExpression) { |
| final PsiMethod staticMethod = ((GrGdkMethod)method).getStaticMethod(); |
| final PsiType qualifierType = info.getQualifierInstanceType(); |
| |
| GrReferenceExpression invoked = (GrReferenceExpression)info.getInvokedExpression(); |
| final GrExpression qualifier = PsiImplUtil.getRuntimeQualifier(invoked); |
| |
| //check methods processed by @Category(ClassWhichProcessMethod) annotation |
| if (qualifierType != null && |
| !GdkMethodUtil.isCategoryMethod(staticMethod, qualifierType, qualifier, methodResolveResult.getSubstitutor()) && |
| !checkCategoryQualifier(invoked, qualifier, staticMethod, methodResolveResult.getSubstitutor())) { |
| registerError(info.getHighlightElementForCategoryQualifier(), GroovyInspectionBundle |
| .message("category.method.0.cannot.be.applied.to.1", method.getName(), qualifierType.getCanonicalText())); |
| return false; |
| } |
| } |
| |
| if (info.getArgumentTypes() == null) return true; |
| |
| GrClosureSignatureUtil.ApplicabilityResult applicable = PsiUtil.isApplicableConcrete(info.getArgumentTypes(), method, methodResolveResult.getSubstitutor(), info.getCall(), false); |
| switch (applicable) { |
| case inapplicable: |
| highlightInapplicableMethodUsage(methodResolveResult, info, method); |
| return false; |
| case canBeApplicable: |
| if (checkUnknownArgs) { |
| highlightUnknownArgs(info); |
| } |
| return !checkUnknownArgs; |
| default: |
| return true; |
| } |
| } |
| |
| private static boolean checkCategoryQualifier(GrReferenceExpression place, |
| GrExpression qualifier, |
| PsiMethod gdkMethod, |
| PsiSubstitutor substitutor) { |
| PsiClass categoryAnnotationOwner = inferCategoryAnnotationOwner(place, qualifier); |
| |
| if (categoryAnnotationOwner != null) { |
| PsiClassType categoryType = GdkMethodUtil.getCategoryType(categoryAnnotationOwner); |
| if (categoryType != null) { |
| return GdkMethodUtil.isCategoryMethod(gdkMethod, categoryType, qualifier, substitutor); |
| } |
| } |
| |
| return false; |
| } |
| |
| private static PsiClass inferCategoryAnnotationOwner(GrReferenceExpression place, GrExpression qualifier) { |
| if (qualifier == null) { |
| GrMethod container = PsiTreeUtil.getParentOfType(place, GrMethod.class, true, GrMember.class); |
| if (container != null && !container.hasModifierProperty(PsiModifier.STATIC)) { //only instance methods can be qualified by category class |
| return container.getContainingClass(); |
| } |
| } |
| else if (PsiUtil.isThisReference(qualifier)) { |
| PsiElement resolved = ((GrReferenceExpression)qualifier).resolve(); |
| if (resolved instanceof PsiClass) { |
| return (PsiClass)resolved; |
| } |
| } |
| return null; |
| } |
| |
| private void highlightUnknownArgs(@NotNull CallInfo info) { |
| registerError(info.getElementToHighlight(), GroovyBundle.message("cannot.infer.argument.types"), LocalQuickFix.EMPTY_ARRAY, ProblemHighlightType.WEAK_WARNING); |
| } |
| |
| @Override |
| public void visitElement(GroovyPsiElement element) { |
| //do nothing |
| } |
| } |
| |
| @Nullable |
| private static List<GrExpression> getExpressionArgumentsOfCall(@NotNull GrArgumentList argumentList) { |
| final GrExpression[] argArray = argumentList.getExpressionArguments(); |
| final ArrayList<GrExpression> args = ContainerUtil.newArrayList(); |
| |
| for (GrExpression arg : argArray) { |
| if (arg instanceof GrSpreadArgument) { |
| GrExpression spreaded = ((GrSpreadArgument)arg).getArgument(); |
| if (spreaded instanceof GrListOrMap && !((GrListOrMap)spreaded).isMap()) { |
| Collections.addAll(args, ((GrListOrMap)spreaded).getInitializers()); |
| } |
| else { |
| return null; |
| } |
| } |
| else { |
| args.add(arg); |
| } |
| } |
| |
| final PsiElement parent = argumentList.getParent(); |
| if (parent instanceof GrIndexProperty && PsiUtil.isLValue((GroovyPsiElement)parent)) { |
| args.add(TypeInferenceHelper.getInitializerFor((GrExpression)parent)); |
| } |
| else if (parent instanceof GrMethodCallExpression) { |
| ContainerUtil.addAll(args, ((GrMethodCallExpression)parent).getClosureArguments()); |
| } |
| return args; |
| } |
| |
| |
| private static class AnnotatingVisitor extends MyVisitor { |
| private AnnotationHolder myHolder; |
| |
| @Override |
| protected boolean shouldProcess(GrMember member) { |
| return true; |
| } |
| |
| @Override |
| protected void registerError(@NotNull final PsiElement location, |
| @NotNull final String description, |
| @Nullable final LocalQuickFix[] fixes, |
| final ProblemHighlightType highlightType) { |
| Annotation annotation = myHolder.createErrorAnnotation(location, description); |
| if (fixes != null) { |
| for (final LocalQuickFix fix : fixes) { |
| annotation.registerFix(new IntentionAction() { |
| @NotNull |
| @Override |
| public String getText() { |
| return fix.getName(); |
| } |
| |
| @NotNull |
| @Override |
| public String getFamilyName() { |
| return fix.getFamilyName(); |
| } |
| |
| @Override |
| public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { |
| return true; |
| } |
| |
| @Override |
| public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { |
| InspectionManager manager = InspectionManager.getInstance(project); |
| ProblemDescriptor descriptor = manager.createProblemDescriptor(location, description, fixes, highlightType, fixes.length == 1, false); |
| fix.applyFix(project, descriptor); |
| } |
| |
| @Override |
| public boolean startInWriteAction() { |
| return true; |
| } |
| }); |
| } |
| } |
| } |
| |
| @Override |
| protected void registerError(@NotNull PsiElement location, |
| ProblemHighlightType highlightType, |
| Object... args) { |
| registerError(location, (String)args[0], LocalQuickFix.EMPTY_ARRAY, highlightType); |
| } |
| } |
| |
| private static final ThreadLocal<AnnotatingVisitor> visitor = new ThreadLocal<AnnotatingVisitor>() { |
| @Override |
| protected AnnotatingVisitor initialValue() { |
| return new AnnotatingVisitor(); |
| } |
| }; |
| |
| public static void checkElement(GroovyPsiElement e, AnnotationHolder holder) { |
| AnnotatingVisitor annotatingVisitor = visitor.get(); |
| |
| AnnotationHolder oldHolder = annotatingVisitor.myHolder; |
| try { |
| annotatingVisitor.myHolder = holder; |
| e.accept(annotatingVisitor); |
| } |
| finally { |
| annotatingVisitor.myHolder = oldHolder; |
| } |
| } |
| |
| @Override |
| @Nullable |
| public ProblemDescriptor[] checkFile(@NotNull PsiFile psiFile, @NotNull InspectionManager inspectionManager, boolean isOnTheFly) { |
| if (!(psiFile instanceof GroovyFileBase)) { |
| return super.checkFile(psiFile, inspectionManager, isOnTheFly); |
| } |
| |
| |
| final GroovyFileBase groovyFile = (GroovyFileBase)psiFile; |
| final ProblemsHolder problemsHolder = new ProblemsHolder(inspectionManager, psiFile, isOnTheFly); |
| final MyVisitor visitor = (MyVisitor)buildGroovyVisitor(problemsHolder, isOnTheFly); |
| |
| processElement(groovyFile, visitor); |
| |
| return problemsHolder.getResultsArray(); |
| } |
| |
| private static void processElement(@NotNull GroovyPsiElement element, @NotNull MyVisitor visitor) { |
| if (element instanceof GrMember && !visitor.shouldProcess((GrMember)element)) { |
| return; |
| } |
| |
| final int count = visitor.getErrorCount(); |
| |
| PsiElement child = element.getFirstChild(); |
| while (child != null) { |
| if (child instanceof GroovyPsiElement) { |
| processElement((GroovyPsiElement)child, visitor); |
| } |
| child = child.getNextSibling(); |
| } |
| |
| if (count == visitor.getErrorCount()) { |
| element.accept(visitor); |
| } |
| } |
| |
| } |