| /* |
| * Copyright 2000-2013 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 com.intellij.codeInsight.daemon.impl.analysis; |
| |
| import com.intellij.codeInsight.ContainerProvider; |
| import com.intellij.codeInsight.ExceptionUtil; |
| import com.intellij.codeInsight.daemon.JavaErrorMessages; |
| import com.intellij.codeInsight.daemon.impl.HighlightInfo; |
| import com.intellij.codeInsight.daemon.impl.HighlightInfoType; |
| import com.intellij.codeInsight.daemon.impl.quickfix.*; |
| import com.intellij.codeInsight.highlighting.HighlightUsagesDescriptionLocation; |
| import com.intellij.codeInsight.intention.IntentionAction; |
| import com.intellij.codeInsight.intention.QuickFixFactory; |
| import com.intellij.codeInsight.quickfix.ChangeVariableTypeQuickFixProvider; |
| import com.intellij.codeInsight.quickfix.UnresolvedReferenceQuickFixProvider; |
| import com.intellij.codeInspection.LocalQuickFixOnPsiElementAsIntentionAdapter; |
| import com.intellij.lang.findUsages.LanguageFindUsages; |
| import com.intellij.lang.java.JavaLanguage; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.extensions.Extensions; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.projectRoots.JavaSdkVersion; |
| import com.intellij.openapi.roots.ProjectFileIndex; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.Condition; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.pom.java.LanguageLevel; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.source.resolve.JavaResolveUtil; |
| import com.intellij.psi.impl.source.tree.java.PsiLiteralExpressionImpl; |
| import com.intellij.psi.impl.source.tree.java.PsiReferenceExpressionImpl; |
| import com.intellij.psi.javadoc.PsiDocComment; |
| import com.intellij.psi.scope.processor.VariablesNotProcessor; |
| import com.intellij.psi.scope.util.PsiScopesUtil; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.templateLanguages.OuterLanguageElement; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.util.*; |
| import com.intellij.refactoring.util.RefactoringChangeUtil; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.ArrayUtilRt; |
| import com.intellij.util.Function; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.ui.UIUtil; |
| import com.intellij.xml.util.XmlStringUtil; |
| import gnu.trove.THashMap; |
| import org.intellij.lang.annotations.Language; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.annotations.PropertyKey; |
| |
| import java.util.*; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * @author cdr |
| * @since Jul 30, 2002 |
| */ |
| public class HighlightUtil extends HighlightUtilBase { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil"); |
| |
| @NotNull private static final Map<String, Set<String>> ourInterfaceIncompatibleModifiers; |
| @NotNull private static final Map<String, Set<String>> ourMethodIncompatibleModifiers; |
| @NotNull private static final Map<String, Set<String>> ourFieldIncompatibleModifiers; |
| @NotNull private static final Map<String, Set<String>> ourClassIncompatibleModifiers; |
| @NotNull private static final Map<String, Set<String>> ourClassInitializerIncompatibleModifiers; |
| @NotNull private static final Set<String> ourConstructorNotAllowedModifiers; |
| |
| @NonNls private static final String SERIAL_PERSISTENT_FIELDS_FIELD_NAME = "serialPersistentFields"; |
| private static final QuickFixFactory QUICK_FIX_FACTORY = QuickFixFactory.getInstance(); |
| |
| private HighlightUtil() { } |
| |
| static { |
| ourClassIncompatibleModifiers = new THashMap<String, Set<String>>(8); |
| ourClassIncompatibleModifiers.put(PsiModifier.ABSTRACT, ContainerUtil.newTroveSet(PsiModifier.FINAL)); |
| ourClassIncompatibleModifiers.put(PsiModifier.FINAL, ContainerUtil.newTroveSet(PsiModifier.ABSTRACT)); |
| ourClassIncompatibleModifiers.put(PsiModifier.PACKAGE_LOCAL, ContainerUtil.newTroveSet(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); |
| ourClassIncompatibleModifiers.put(PsiModifier.PRIVATE, ContainerUtil.newTroveSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); |
| ourClassIncompatibleModifiers.put(PsiModifier.PUBLIC, ContainerUtil.newTroveSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED)); |
| ourClassIncompatibleModifiers.put(PsiModifier.PROTECTED, ContainerUtil.newTroveSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE)); |
| ourClassIncompatibleModifiers.put(PsiModifier.STRICTFP, Collections.<String>emptySet()); |
| ourClassIncompatibleModifiers.put(PsiModifier.STATIC, Collections.<String>emptySet()); |
| |
| ourInterfaceIncompatibleModifiers = new THashMap<String, Set<String>>(7); |
| ourInterfaceIncompatibleModifiers.put(PsiModifier.ABSTRACT, Collections.<String>emptySet()); |
| ourInterfaceIncompatibleModifiers.put(PsiModifier.PACKAGE_LOCAL, ContainerUtil.newTroveSet(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); |
| ourInterfaceIncompatibleModifiers.put(PsiModifier.PRIVATE, ContainerUtil.newTroveSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); |
| ourInterfaceIncompatibleModifiers.put(PsiModifier.PUBLIC, ContainerUtil.newTroveSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED)); |
| ourInterfaceIncompatibleModifiers.put(PsiModifier.PROTECTED, ContainerUtil.newTroveSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE)); |
| ourInterfaceIncompatibleModifiers.put(PsiModifier.STRICTFP, Collections.<String>emptySet()); |
| ourInterfaceIncompatibleModifiers.put(PsiModifier.STATIC, Collections.<String>emptySet()); |
| |
| ourMethodIncompatibleModifiers = new THashMap<String, Set<String>>(11); |
| ourMethodIncompatibleModifiers.put(PsiModifier.ABSTRACT, ContainerUtil.newTroveSet(PsiModifier.NATIVE, PsiModifier.STATIC, PsiModifier.FINAL, PsiModifier.PRIVATE, PsiModifier.STRICTFP, PsiModifier.SYNCHRONIZED, PsiModifier.DEFAULT)); |
| ourMethodIncompatibleModifiers.put(PsiModifier.NATIVE, ContainerUtil.newTroveSet(PsiModifier.ABSTRACT, PsiModifier.STRICTFP)); |
| ourMethodIncompatibleModifiers.put(PsiModifier.PACKAGE_LOCAL, ContainerUtil.newTroveSet(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); |
| ourMethodIncompatibleModifiers.put(PsiModifier.PRIVATE, ContainerUtil.newTroveSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); |
| ourMethodIncompatibleModifiers.put(PsiModifier.PUBLIC, ContainerUtil.newTroveSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED)); |
| ourMethodIncompatibleModifiers.put(PsiModifier.PROTECTED, ContainerUtil.newTroveSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE)); |
| ourMethodIncompatibleModifiers.put(PsiModifier.STATIC, ContainerUtil.newTroveSet(PsiModifier.ABSTRACT, PsiModifier.DEFAULT)); |
| ourMethodIncompatibleModifiers.put(PsiModifier.DEFAULT, ContainerUtil.newTroveSet(PsiModifier.ABSTRACT, PsiModifier.STATIC)); |
| ourMethodIncompatibleModifiers.put(PsiModifier.SYNCHRONIZED, ContainerUtil.newTroveSet(PsiModifier.ABSTRACT)); |
| ourMethodIncompatibleModifiers.put(PsiModifier.STRICTFP, ContainerUtil.newTroveSet(PsiModifier.ABSTRACT)); |
| ourMethodIncompatibleModifiers.put(PsiModifier.FINAL, ContainerUtil.newTroveSet(PsiModifier.ABSTRACT)); |
| |
| ourFieldIncompatibleModifiers = new THashMap<String, Set<String>>(8); |
| ourFieldIncompatibleModifiers.put(PsiModifier.FINAL, ContainerUtil.newTroveSet(PsiModifier.VOLATILE)); |
| ourFieldIncompatibleModifiers.put(PsiModifier.PACKAGE_LOCAL, ContainerUtil.newTroveSet(PsiModifier.PRIVATE, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); |
| ourFieldIncompatibleModifiers.put(PsiModifier.PRIVATE, ContainerUtil.newTroveSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PROTECTED)); |
| ourFieldIncompatibleModifiers.put(PsiModifier.PUBLIC, ContainerUtil.newTroveSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PRIVATE, PsiModifier.PROTECTED)); |
| ourFieldIncompatibleModifiers.put(PsiModifier.PROTECTED, ContainerUtil.newTroveSet(PsiModifier.PACKAGE_LOCAL, PsiModifier.PUBLIC, PsiModifier.PRIVATE)); |
| ourFieldIncompatibleModifiers.put(PsiModifier.STATIC, Collections.<String>emptySet()); |
| ourFieldIncompatibleModifiers.put(PsiModifier.TRANSIENT, Collections.<String>emptySet()); |
| ourFieldIncompatibleModifiers.put(PsiModifier.VOLATILE, ContainerUtil.newTroveSet(PsiModifier.FINAL)); |
| |
| ourClassInitializerIncompatibleModifiers = new THashMap<String, Set<String>>(1); |
| ourClassInitializerIncompatibleModifiers.put(PsiModifier.STATIC, Collections.<String>emptySet()); |
| |
| ourConstructorNotAllowedModifiers = ContainerUtil.newTroveSet(PsiModifier.ABSTRACT, PsiModifier.STATIC, PsiModifier.NATIVE, PsiModifier.FINAL, PsiModifier.STRICTFP, PsiModifier.SYNCHRONIZED); |
| } |
| |
| @Nullable |
| public static String getIncompatibleModifier(String modifier, |
| @Nullable PsiModifierList modifierList, |
| @NotNull Map<String, Set<String>> incompatibleModifiersHash) { |
| if (modifierList == null) return null; |
| |
| // modifier is always incompatible with itself |
| PsiElement[] modifiers = modifierList.getChildren(); |
| int modifierCount = 0; |
| for (PsiElement otherModifier : modifiers) { |
| if (Comparing.equal(modifier, otherModifier.getText(), true)) modifierCount++; |
| } |
| if (modifierCount > 1) { |
| return modifier; |
| } |
| |
| Set<String> incompatibles = incompatibleModifiersHash.get(modifier); |
| if (incompatibles == null) return null; |
| final boolean level8OrHigher = PsiUtil.isLanguageLevel8OrHigher(modifierList); |
| for (@PsiModifier.ModifierConstant String incompatible : incompatibles) { |
| if (level8OrHigher) { |
| if (modifier.equals(PsiModifier.STATIC) && incompatible.equals(PsiModifier.ABSTRACT)){ |
| continue; |
| } |
| } |
| if (modifierList.hasModifierProperty(incompatible)) { |
| return incompatible; |
| } |
| else if (PsiModifier.ABSTRACT.equals(incompatible) && modifierList.hasExplicitModifier(incompatible)) { |
| return incompatible; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * make element protected/package local/public suggestion |
| */ |
| static void registerAccessQuickFixAction(@NotNull PsiMember refElement, |
| @NotNull PsiJavaCodeReferenceElement place, |
| @Nullable HighlightInfo errorResult, |
| final PsiElement fileResolveScope) { |
| if (errorResult == null) return; |
| PsiClass accessObjectClass = null; |
| PsiElement qualifier = place.getQualifier(); |
| if (qualifier instanceof PsiExpression) { |
| accessObjectClass = (PsiClass)PsiUtil.getAccessObjectClass((PsiExpression)qualifier).getElement(); |
| } |
| registerReplaceInaccessibleFieldWithGetterSetterFix(refElement, place, accessObjectClass, |
| errorResult); |
| |
| if (refElement instanceof PsiCompiledElement) return; |
| PsiModifierList modifierList = refElement.getModifierList(); |
| if (modifierList == null) return; |
| |
| PsiClass packageLocalClassInTheMiddle = getPackageLocalClassInTheMiddle(place); |
| if (packageLocalClassInTheMiddle != null) { |
| IntentionAction fix = |
| QUICK_FIX_FACTORY.createModifierListFix(packageLocalClassInTheMiddle, PsiModifier.PUBLIC, true, true); |
| QuickFixAction.registerQuickFixAction(errorResult, fix); |
| return; |
| } |
| |
| try { |
| Project project = refElement.getProject(); |
| JavaPsiFacade facade = JavaPsiFacade.getInstance(project); |
| PsiModifierList modifierListCopy = facade.getElementFactory().createFieldFromText("int a;", null).getModifierList(); |
| modifierListCopy.setModifierProperty(PsiModifier.STATIC, modifierList.hasModifierProperty(PsiModifier.STATIC)); |
| String minModifier = PsiModifier.PACKAGE_LOCAL; |
| if (refElement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL)) { |
| minModifier = PsiModifier.PROTECTED; |
| } |
| if (refElement.hasModifierProperty(PsiModifier.PROTECTED)) { |
| minModifier = PsiModifier.PUBLIC; |
| } |
| String[] modifiers = {PsiModifier.PACKAGE_LOCAL, PsiModifier.PROTECTED, PsiModifier.PUBLIC,}; |
| for (int i = ArrayUtil.indexOf(modifiers, minModifier); i < modifiers.length; i++) { |
| @PsiModifier.ModifierConstant String modifier = modifiers[i]; |
| modifierListCopy.setModifierProperty(modifier, true); |
| if (facade.getResolveHelper().isAccessible(refElement, modifierListCopy, place, accessObjectClass, fileResolveScope)) { |
| IntentionAction fix = QUICK_FIX_FACTORY.createModifierListFix(refElement, modifier, true, true); |
| TextRange fixRange = new TextRange(errorResult.startOffset, errorResult.endOffset); |
| PsiElement ref = place.getReferenceNameElement(); |
| if (ref != null) { |
| fixRange = fixRange.union(ref.getTextRange()); |
| } |
| QuickFixAction.registerQuickFixAction(errorResult, fixRange, fix); |
| } |
| } |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| } |
| |
| @Nullable |
| private static PsiClass getPackageLocalClassInTheMiddle(@NotNull PsiElement place) { |
| if (place instanceof PsiReferenceExpression) { |
| // check for package local classes in the middle |
| PsiReferenceExpression expression = (PsiReferenceExpression)place; |
| while (true) { |
| PsiElement resolved = expression.resolve(); |
| if (resolved instanceof PsiField) { |
| PsiField field = (PsiField)resolved; |
| PsiClass aClass = field.getContainingClass(); |
| if (aClass != null && aClass.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) && |
| !JavaPsiFacade.getInstance(aClass.getProject()).arePackagesTheSame(aClass, place)) { |
| |
| return aClass; |
| } |
| } |
| PsiExpression qualifier = expression.getQualifierExpression(); |
| if (!(qualifier instanceof PsiReferenceExpression)) break; |
| expression = (PsiReferenceExpression)qualifier; |
| } |
| } |
| return null; |
| } |
| |
| |
| @Nullable |
| static HighlightInfo checkInstanceOfApplicable(@NotNull PsiInstanceOfExpression expression) { |
| PsiExpression operand = expression.getOperand(); |
| PsiTypeElement typeElement = expression.getCheckType(); |
| if (typeElement == null) return null; |
| PsiType checkType = typeElement.getType(); |
| PsiType operandType = operand.getType(); |
| if (operandType == null) return null; |
| if (operandType instanceof PsiLambdaExpressionType) { |
| return HighlightInfo |
| .newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip("Lambda expression is not expected here").create(); |
| } |
| if (operandType instanceof PsiMethodReferenceType) { |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip( |
| "Method reference expression is not expected here").create(); |
| } |
| if (TypeConversionUtil.isPrimitiveAndNotNull(operandType) |
| || TypeConversionUtil.isPrimitiveAndNotNull(checkType) |
| || !TypeConversionUtil.areTypesConvertible(operandType, checkType)) { |
| String message = JavaErrorMessages.message("inconvertible.type.cast", JavaHighlightUtil.formatType(operandType), JavaHighlightUtil |
| .formatType(checkType)); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| return null; |
| } |
| |
| |
| @Nullable |
| static HighlightInfo checkInconvertibleTypeCast(@NotNull PsiTypeCastExpression expression) { |
| PsiTypeElement castTypeElement = expression.getCastType(); |
| if (castTypeElement == null) return null; |
| PsiType castType = castTypeElement.getType(); |
| |
| PsiExpression operand = expression.getOperand(); |
| if (operand == null) return null; |
| PsiType operandType = operand.getType(); |
| |
| if (operandType != null && |
| !TypeConversionUtil.areTypesConvertible(operandType, castType) && |
| !RedundantCastUtil.isInPolymorphicCall(expression)) { |
| String message = JavaErrorMessages.message("inconvertible.type.cast", JavaHighlightUtil.formatType(operandType), JavaHighlightUtil |
| .formatType(castType)); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| static HighlightInfo checkVariableExpected(@NotNull PsiExpression expression) { |
| PsiExpression lValue; |
| if (expression instanceof PsiAssignmentExpression) { |
| PsiAssignmentExpression assignment = (PsiAssignmentExpression)expression; |
| lValue = assignment.getLExpression(); |
| } |
| else if (PsiUtil.isIncrementDecrementOperation(expression)) { |
| lValue = expression instanceof PsiPostfixExpression |
| ? ((PsiPostfixExpression)expression).getOperand() |
| : ((PsiPrefixExpression)expression).getOperand(); |
| } |
| else { |
| lValue = null; |
| } |
| HighlightInfo errorResult = null; |
| if (lValue != null && !TypeConversionUtil.isLValue(lValue)) { |
| String description = JavaErrorMessages.message("variable.expected"); |
| errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(lValue).descriptionAndTooltip(description).create(); |
| } |
| |
| return errorResult; |
| } |
| |
| |
| @Nullable |
| static HighlightInfo checkAssignmentOperatorApplicable(@NotNull PsiAssignmentExpression assignment,@NotNull PsiFile containingFile) { |
| PsiJavaToken operationSign = assignment.getOperationSign(); |
| IElementType eqOpSign = operationSign.getTokenType(); |
| IElementType opSign = TypeConversionUtil.convertEQtoOperation(eqOpSign); |
| if (opSign == null) return null; |
| final PsiType lType = assignment.getLExpression().getType(); |
| final PsiExpression rExpression = assignment.getRExpression(); |
| if (rExpression == null) return null; |
| final PsiType rType = rExpression.getType(); |
| HighlightInfo errorResult = null; |
| if (!TypeConversionUtil.isBinaryOperatorApplicable(opSign, lType, rType, true) || |
| PsiType.getJavaLangObject(containingFile.getManager(), assignment.getResolveScope()).equals(lType)) { |
| String operatorText = operationSign.getText().substring(0, operationSign.getText().length() - 1); |
| String message = JavaErrorMessages.message("binary.operator.not.applicable", operatorText, |
| JavaHighlightUtil.formatType(lType), |
| JavaHighlightUtil.formatType(rType)); |
| |
| errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(assignment).descriptionAndTooltip(message).create(); |
| } |
| return errorResult; |
| } |
| |
| |
| @Nullable |
| static HighlightInfo checkAssignmentCompatibleTypes(@NotNull PsiAssignmentExpression assignment) { |
| PsiExpression lExpr = assignment.getLExpression(); |
| PsiExpression rExpr = assignment.getRExpression(); |
| if (rExpr == null) return null; |
| PsiType lType = lExpr.getType(); |
| PsiType rType = rExpr.getType(); |
| if (rType == null) return null; |
| |
| final IElementType sign = assignment.getOperationTokenType(); |
| HighlightInfo highlightInfo; |
| if (JavaTokenType.EQ.equals(sign)) { |
| highlightInfo = checkAssignability(lType, rType, rExpr, assignment); |
| } |
| else { |
| // 15.26.2. Compound Assignment Operators |
| final IElementType opSign = TypeConversionUtil.convertEQtoOperation(sign); |
| final PsiType type = TypeConversionUtil.calcTypeForBinaryExpression(lType, rType, opSign, true); |
| if (type == null || lType == null || TypeConversionUtil.areTypesConvertible(type, lType)) { |
| return null; |
| } |
| highlightInfo = createIncompatibleTypeHighlightInfo(lType, type, assignment.getTextRange(), 0); |
| QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createChangeToAppendFix(sign, lType, assignment)); |
| } |
| if (highlightInfo == null) { |
| return null; |
| } |
| registerChangeVariableTypeFixes(lExpr, rType, highlightInfo); |
| if (lType != null) { |
| registerChangeVariableTypeFixes(rExpr, lType, highlightInfo); |
| } |
| return highlightInfo; |
| } |
| |
| private static void registerChangeVariableTypeFixes(@NotNull PsiExpression expression, |
| @NotNull PsiType type, |
| @Nullable HighlightInfo highlightInfo) { |
| if (highlightInfo == null || !(expression instanceof PsiReferenceExpression)) return; |
| |
| final PsiElement element = ((PsiReferenceExpression)expression).resolve(); |
| if (element == null || !(element instanceof PsiVariable)) return; |
| |
| registerChangeVariableTypeFixes((PsiVariable)element, type, highlightInfo); |
| } |
| |
| private static boolean isCastIntentionApplicable(@NotNull PsiExpression expression, @Nullable PsiType toType) { |
| while (expression instanceof PsiTypeCastExpression || expression instanceof PsiParenthesizedExpression) { |
| if (expression instanceof PsiTypeCastExpression) { |
| expression = ((PsiTypeCastExpression)expression).getOperand(); |
| } |
| if (expression instanceof PsiParenthesizedExpression) { |
| expression = ((PsiParenthesizedExpression)expression).getExpression(); |
| } |
| } |
| if (expression == null) return false; |
| PsiType rType = expression.getType(); |
| return rType != null && toType != null && TypeConversionUtil.areTypesConvertible(rType, toType); |
| } |
| |
| |
| @Nullable |
| static HighlightInfo checkVariableInitializerType(@NotNull PsiVariable variable) { |
| PsiExpression initializer = variable.getInitializer(); |
| // array initializer checked in checkArrayInitializerApplicable |
| if (initializer == null || initializer instanceof PsiArrayInitializerExpression) return null; |
| PsiType lType = variable.getType(); |
| PsiType rType = initializer.getType(); |
| int start = variable.getTypeElement().getTextRange().getStartOffset(); |
| int end = variable.getTextRange().getEndOffset(); |
| HighlightInfo highlightInfo = checkAssignability(lType, rType, initializer, new TextRange(start, end), 0); |
| if (highlightInfo != null) { |
| registerChangeVariableTypeFixes(variable, rType, highlightInfo); |
| } |
| return highlightInfo; |
| } |
| |
| @Nullable |
| static HighlightInfo checkAssignability(PsiType lType, @Nullable PsiType rType, PsiExpression expression, @NotNull PsiElement elementToHighlight) { |
| TextRange textRange = elementToHighlight.getTextRange(); |
| return checkAssignability(lType, rType, expression, textRange, 0); |
| } |
| |
| @Nullable |
| public static HighlightInfo checkAssignability(@Nullable PsiType lType, |
| @Nullable PsiType rType, |
| @Nullable PsiExpression expression, |
| @NotNull TextRange textRange, |
| int navigationShift) { |
| if (lType == rType) return null; |
| if (expression == null) { |
| if (rType == null || lType == null || TypeConversionUtil.isAssignable(lType, rType)) return null; |
| } |
| else if (TypeConversionUtil.areTypesAssignmentCompatible(lType, expression)) { |
| return null; |
| } |
| if (rType == null) { |
| rType = expression.getType(); |
| } |
| HighlightInfo highlightInfo = createIncompatibleTypeHighlightInfo(lType, rType, textRange, navigationShift); |
| if (rType != null && expression != null && isCastIntentionApplicable(expression, lType)) { |
| QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createAddTypeCastFix(lType, expression)); |
| } |
| if (expression != null && lType != null) { |
| QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createWrapExpressionFix(lType, expression)); |
| AddTypeArgumentsConditionalFix.register(highlightInfo, expression, lType); |
| } |
| ChangeNewOperatorTypeFix.register(highlightInfo, expression, lType); |
| return highlightInfo; |
| } |
| |
| |
| @Nullable |
| static HighlightInfo checkReturnStatementType(@NotNull PsiReturnStatement statement) { |
| PsiMethod method = null; |
| PsiLambdaExpression lambda = null; |
| PsiElement parent = statement.getParent(); |
| while (true) { |
| if (parent instanceof PsiFile) break; |
| if (parent instanceof PsiClassInitializer) break; |
| if (parent instanceof PsiLambdaExpression){ |
| lambda = (PsiLambdaExpression)parent; |
| break; |
| } |
| if (parent instanceof PsiMethod) { |
| method = (PsiMethod)parent; |
| break; |
| } |
| parent = parent.getParent(); |
| } |
| String description; |
| HighlightInfo errorResult = null; |
| if (method == null && lambda != null) { |
| //todo check return statements type inside lambda |
| } |
| else if (method == null && !(parent instanceof ServerPageFile)) { |
| description = JavaErrorMessages.message("return.outside.method"); |
| errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); |
| } |
| else { |
| PsiType returnType = method != null ? method.getReturnType() : null/*JSP page returns void*/; |
| boolean isMethodVoid = returnType == null || PsiType.VOID.equals(returnType); |
| final PsiExpression returnValue = statement.getReturnValue(); |
| if (returnValue != null) { |
| PsiType valueType = returnValue.getType(); |
| if (isMethodVoid) { |
| description = JavaErrorMessages.message("return.from.void.method"); |
| errorResult = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); |
| if (valueType != null) { |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createMethodReturnFix(method, valueType, true)); |
| } |
| } |
| else { |
| TextRange textRange = statement.getTextRange(); |
| errorResult = checkAssignability(returnType, valueType, returnValue, textRange, returnValue.getStartOffsetInParent()); |
| if (errorResult != null && valueType != null) { |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createMethodReturnFix(method, valueType, true)); |
| registerChangeParameterClassFix(returnType, valueType, errorResult); |
| if (returnType instanceof PsiArrayType && TypeConversionUtil.isAssignable(((PsiArrayType)returnType).getComponentType(), valueType)) { |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createSurroundWithArrayFix(null, returnValue)); |
| } |
| } |
| } |
| } |
| else { |
| if (!isMethodVoid) { |
| description = JavaErrorMessages.message("missing.return.value"); |
| errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).navigationShift(PsiKeyword.RETURN.length()).create(); |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createMethodReturnFix(method, PsiType.VOID, true)); |
| } |
| } |
| } |
| return errorResult; |
| } |
| |
| @NotNull |
| public static String getUnhandledExceptionsDescriptor(@NotNull final Collection<PsiClassType> unhandled) { |
| return getUnhandledExceptionsDescriptor(unhandled, null); |
| } |
| |
| @NotNull |
| private static String getUnhandledExceptionsDescriptor(@NotNull final Collection<PsiClassType> unhandled, @Nullable final String source) { |
| final String exceptions = formatTypes(unhandled); |
| return source != null ? JavaErrorMessages.message("unhandled.close.exceptions", exceptions, unhandled.size(), source) |
| : JavaErrorMessages.message("unhandled.exceptions", exceptions, unhandled.size()); |
| } |
| |
| @NotNull |
| private static String formatTypes(@NotNull Collection<PsiClassType> unhandled) { |
| return StringUtil.join(unhandled, new Function<PsiClassType, String>() { |
| @NotNull |
| @Override |
| public String fun(PsiClassType type) { |
| return JavaHighlightUtil.formatType(type); |
| } |
| }, ", "); |
| } |
| |
| @Nullable |
| static HighlightInfo checkVariableAlreadyDefined(@NotNull PsiVariable variable) { |
| if (variable instanceof ExternallyDefinedPsiElement) return null; |
| boolean isIncorrect = false; |
| PsiElement declarationScope; |
| if (variable instanceof PsiLocalVariable || |
| variable instanceof PsiParameter && |
| ((declarationScope = ((PsiParameter)variable).getDeclarationScope()) instanceof PsiCatchSection || |
| declarationScope instanceof PsiForeachStatement || |
| declarationScope instanceof PsiLambdaExpression)) { |
| @SuppressWarnings("unchecked") |
| PsiElement scope = PsiTreeUtil.getParentOfType(variable, PsiFile.class, PsiMethod.class, PsiClassInitializer.class, PsiResourceList.class); |
| VariablesNotProcessor proc = new VariablesNotProcessor(variable, false) { |
| @Override |
| protected boolean check(final PsiVariable var, final ResolveState state) { |
| return (var instanceof PsiLocalVariable || var instanceof PsiParameter) && super.check(var, state); |
| } |
| }; |
| PsiIdentifier identifier = variable.getNameIdentifier(); |
| assert identifier != null : variable; |
| PsiScopesUtil.treeWalkUp(proc, identifier, scope); |
| if (scope instanceof PsiResourceList && proc.size() == 0) { |
| scope = PsiTreeUtil.getParentOfType(variable, PsiFile.class, PsiMethod.class, PsiClassInitializer.class); |
| PsiScopesUtil.treeWalkUp(proc, identifier, scope); |
| } |
| if (proc.size() > 0) { |
| isIncorrect = true; |
| } |
| } |
| else if (variable instanceof PsiField) { |
| PsiField field = (PsiField)variable; |
| PsiClass aClass = field.getContainingClass(); |
| if (aClass == null) return null; |
| PsiField fieldByName = aClass.findFieldByName(variable.getName(), false); |
| if (fieldByName != null && fieldByName != field) { |
| isIncorrect = true; |
| } |
| } |
| else { |
| PsiElement scope = variable.getParent(); |
| PsiElement[] children = scope.getChildren(); |
| for (PsiElement child : children) { |
| if (child instanceof PsiVariable) { |
| if (child.equals(variable)) continue; |
| if (variable.getName().equals(((PsiVariable)child).getName())) { |
| isIncorrect = true; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (isIncorrect) { |
| String description = JavaErrorMessages.message("variable.already.defined", variable.getName()); |
| PsiIdentifier identifier = variable.getNameIdentifier(); |
| assert identifier != null : variable; |
| HighlightInfo highlightInfo = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(description).create(); |
| if (variable instanceof PsiLocalVariable) { |
| QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createReuseVariableDeclarationFix((PsiLocalVariable)variable)); |
| } |
| return highlightInfo; |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkUnderscore(@NotNull PsiIdentifier identifier, @NotNull PsiVariable variable) { |
| if ("_".equals(variable.getName()) && PsiUtil.isLanguageLevel8OrHigher(variable)) { |
| if (variable instanceof PsiParameter && ((PsiParameter)variable).getDeclarationScope() instanceof PsiLambdaExpression) { |
| String message = JavaErrorMessages.message("underscore.lambda.identifier"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(message).create(); |
| } |
| else { |
| String message = JavaErrorMessages.message("underscore.identifier"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range(identifier).descriptionAndTooltip(message).create(); |
| } |
| } |
| |
| return null; |
| } |
| |
| @NotNull |
| public static String formatClass(@NotNull PsiClass aClass) { |
| return formatClass(aClass, true); |
| } |
| |
| @NotNull |
| public static String formatClass(@NotNull PsiClass aClass, boolean fqn) { |
| return PsiFormatUtil.formatClass(aClass, PsiFormatUtilBase.SHOW_NAME | |
| PsiFormatUtilBase.SHOW_ANONYMOUS_CLASS_VERBOSE | (fqn ? PsiFormatUtilBase.SHOW_FQ_NAME : 0)); |
| } |
| |
| @NotNull |
| public static String formatField(@NotNull PsiField field) { |
| return PsiFormatUtil.formatVariable(field, PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, PsiSubstitutor.EMPTY); |
| } |
| |
| @Nullable |
| public static HighlightInfo checkUnhandledExceptions(@NotNull final PsiElement element, @Nullable TextRange textRange) { |
| final List<PsiClassType> unhandledExceptions = ExceptionUtil.getUnhandledExceptions(element); |
| if (unhandledExceptions.isEmpty()) return null; |
| |
| final HighlightInfoType highlightType = getUnhandledExceptionHighlightType(element); |
| if (highlightType == null) return null; |
| |
| if (textRange == null) textRange = element.getTextRange(); |
| final String description = getUnhandledExceptionsDescriptor(unhandledExceptions); |
| HighlightInfo errorResult = HighlightInfo.newHighlightInfo(highlightType).range(textRange).descriptionAndTooltip(description).create(); |
| registerUnhandledExceptionFixes(element, errorResult, unhandledExceptions); |
| return errorResult; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkUnhandledCloserExceptions(@NotNull final PsiResourceVariable resource) { |
| final List<PsiClassType> unhandled = ExceptionUtil.getUnhandledCloserExceptions(resource, null); |
| if (unhandled.isEmpty()) return null; |
| |
| final HighlightInfoType highlightType = getUnhandledExceptionHighlightType(resource); |
| if (highlightType == null) return null; |
| |
| final String description = getUnhandledExceptionsDescriptor(unhandled, "auto-closeable resource"); |
| final HighlightInfo highlight = |
| HighlightInfo.newHighlightInfo(highlightType).range(resource).descriptionAndTooltip(description).create(); |
| registerUnhandledExceptionFixes(resource, highlight, unhandled); |
| return highlight; |
| } |
| |
| private static void registerUnhandledExceptionFixes(@NotNull final PsiElement element, |
| final HighlightInfo errorResult, |
| @NotNull final List<PsiClassType> unhandled) { |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createAddExceptionToCatchFix()); |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createAddExceptionToThrowsFix(element)); |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createSurroundWithTryCatchFix(element)); |
| if (unhandled.size() == 1) { |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createGeneralizeCatchFix(element, unhandled.get(0))); |
| } |
| } |
| |
| @Nullable |
| private static HighlightInfoType getUnhandledExceptionHighlightType(final PsiElement element) { |
| if (!FileTypeUtils.isInServerPageFile(element)) { |
| return HighlightInfoType.UNHANDLED_EXCEPTION; |
| } |
| PsiMethod targetMethod = PsiTreeUtil.getParentOfType(element, PsiMethod.class); |
| if (!(targetMethod instanceof SyntheticElement)) return HighlightInfoType.UNHANDLED_EXCEPTION; |
| // ignore JSP top level errors - it handled by UnhandledExceptionInJSP inspection |
| return null; |
| } |
| |
| |
| @Nullable |
| static HighlightInfo checkBreakOutsideLoop(@NotNull PsiBreakStatement statement) { |
| if (statement.getLabelIdentifier() == null) { |
| if (new PsiMatcherImpl(statement).ancestor(EnclosingLoopOrSwitchMatcherExpression.INSTANCE).getElement() == null) { |
| String description = JavaErrorMessages.message("break.outside.switch.or.loop"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); |
| } |
| } |
| else { |
| // todo labeled |
| } |
| return null; |
| } |
| |
| |
| @Nullable |
| static HighlightInfo checkContinueOutsideLoop(@NotNull PsiContinueStatement statement) { |
| if (statement.getLabelIdentifier() == null) { |
| if (new PsiMatcherImpl(statement).ancestor(EnclosingLoopMatcherExpression.INSTANCE).getElement() == null) { |
| String description = JavaErrorMessages.message("continue.outside.loop"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); |
| } |
| } |
| else { |
| PsiStatement exitedStatement = statement.findContinuedStatement(); |
| if (exitedStatement == null) return null; |
| if (!(exitedStatement instanceof PsiForStatement) && !(exitedStatement instanceof PsiWhileStatement) && |
| !(exitedStatement instanceof PsiDoWhileStatement) && !(exitedStatement instanceof PsiForeachStatement)) { |
| String description = JavaErrorMessages.message("not.loop.label", statement.getLabelIdentifier().getText()); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| static HighlightInfo checkIllegalModifierCombination(@NotNull PsiKeyword keyword, @NotNull PsiModifierList modifierList) { |
| @PsiModifier.ModifierConstant String modifier = keyword.getText(); |
| String incompatible = getIncompatibleModifier(modifier, modifierList); |
| if (incompatible != null) { |
| String message = JavaErrorMessages.message("incompatible.modifiers", modifier, incompatible); |
| HighlightInfo highlightInfo = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(keyword).descriptionAndTooltip(message).create(); |
| QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createModifierListFix(modifierList, modifier, false, false)); |
| return highlightInfo; |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| private static Map<String, Set<String>> getIncompatibleModifierMap(@NotNull PsiModifierList modifierList) { |
| PsiElement parent = modifierList.getParent(); |
| if (parent == null || PsiUtilCore.hasErrorElementChild(parent)) return null; |
| return parent instanceof PsiClass |
| ? ((PsiClass)parent).isInterface() ? ourInterfaceIncompatibleModifiers : ourClassIncompatibleModifiers |
| : parent instanceof PsiMethod |
| ? ourMethodIncompatibleModifiers |
| : parent instanceof PsiVariable |
| ? ourFieldIncompatibleModifiers |
| : parent instanceof PsiClassInitializer ? ourClassInitializerIncompatibleModifiers : null; |
| } |
| |
| @Nullable |
| public static String getIncompatibleModifier(String modifier, @NotNull PsiModifierList modifierList) { |
| PsiElement parent = modifierList.getParent(); |
| if (parent == null || PsiUtilCore.hasErrorElementChild(parent)) return null; |
| final Map<String, Set<String>> incompatibleModifierMap = getIncompatibleModifierMap(modifierList); |
| if (incompatibleModifierMap == null) return null; |
| return getIncompatibleModifier(modifier, modifierList, incompatibleModifierMap); |
| } |
| |
| |
| @Nullable |
| public static HighlightInfo checkNotAllowedModifier(@NotNull PsiKeyword keyword, @NotNull PsiModifierList modifierList) { |
| PsiElement modifierOwner = modifierList.getParent(); |
| if (modifierOwner == null) return null; |
| if (PsiUtilCore.hasErrorElementChild(modifierOwner)) return null; |
| |
| @PsiModifier.ModifierConstant String modifier = keyword.getText(); |
| final Map<String, Set<String>> incompatibleModifierMap = getIncompatibleModifierMap(modifierList); |
| if (incompatibleModifierMap == null) return null; |
| |
| Set<String> incompatibles = incompatibleModifierMap.get(modifier); |
| PsiElement modifierOwnerParent = modifierOwner instanceof PsiMember ? ((PsiMember)modifierOwner).getContainingClass() : modifierOwner.getParent(); |
| if (modifierOwnerParent == null) modifierOwnerParent = modifierOwner.getParent(); |
| boolean isAllowed = true; |
| if (modifierOwner instanceof PsiClass) { |
| PsiClass aClass = (PsiClass)modifierOwner; |
| if (aClass.isInterface()) { |
| if (PsiModifier.STATIC.equals(modifier) || PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || |
| PsiModifier.PACKAGE_LOCAL.equals(modifier)) { |
| isAllowed = modifierOwnerParent instanceof PsiClass; |
| } |
| } |
| else { |
| if (PsiModifier.PUBLIC.equals(modifier)) { |
| isAllowed = modifierOwnerParent instanceof PsiJavaFile || |
| modifierOwnerParent instanceof PsiClass && |
| (modifierOwnerParent instanceof PsiSyntheticClass || ((PsiClass)modifierOwnerParent).getQualifiedName() != null); |
| } |
| else if (PsiModifier.STATIC.equals(modifier) || PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || |
| PsiModifier.PACKAGE_LOCAL.equals(modifier)) { |
| isAllowed = modifierOwnerParent instanceof PsiClass && |
| ((PsiClass)modifierOwnerParent).getQualifiedName() != null || FileTypeUtils.isInServerPageFile(modifierOwnerParent); |
| } |
| |
| if (aClass.isEnum()) { |
| isAllowed &= !(PsiModifier.FINAL.equals(modifier) || PsiModifier.ABSTRACT.equals(modifier)); |
| } |
| |
| if (aClass.getContainingClass() instanceof PsiAnonymousClass) { |
| isAllowed &= !(PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier)); |
| } |
| } |
| } |
| else if (modifierOwner instanceof PsiMethod) { |
| PsiMethod method = (PsiMethod)modifierOwner; |
| isAllowed = !(method.isConstructor() && ourConstructorNotAllowedModifiers.contains(modifier)); |
| PsiClass containingClass = method.getContainingClass(); |
| if ((method.hasModifierProperty(PsiModifier.PUBLIC) || method.hasModifierProperty(PsiModifier.PROTECTED)) && method.isConstructor() && |
| containingClass != null && containingClass.isEnum()) { |
| isAllowed = false; |
| } |
| |
| if (PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || PsiModifier.TRANSIENT.equals(modifier) || |
| PsiModifier.STRICTFP.equals(modifier) || PsiModifier.SYNCHRONIZED.equals(modifier)) { |
| isAllowed &= modifierOwnerParent instanceof PsiClass && !((PsiClass)modifierOwnerParent).isInterface(); |
| } |
| } |
| else if (modifierOwner instanceof PsiField) { |
| if (PsiModifier.PRIVATE.equals(modifier) || PsiModifier.PROTECTED.equals(modifier) || PsiModifier.TRANSIENT.equals(modifier) || |
| PsiModifier.STRICTFP.equals(modifier) || PsiModifier.SYNCHRONIZED.equals(modifier)) { |
| isAllowed = modifierOwnerParent instanceof PsiClass && !((PsiClass)modifierOwnerParent).isInterface(); |
| } |
| } |
| else if (modifierOwner instanceof PsiClassInitializer) { |
| isAllowed = PsiModifier.STATIC.equals(modifier); |
| } |
| else if (modifierOwner instanceof PsiLocalVariable || modifierOwner instanceof PsiParameter) { |
| isAllowed = PsiModifier.FINAL.equals(modifier); |
| } |
| |
| isAllowed &= incompatibles != null; |
| if (!isAllowed) { |
| String message = JavaErrorMessages.message("modifier.not.allowed", modifier); |
| |
| HighlightInfo highlightInfo = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(keyword).descriptionAndTooltip(message).create(); |
| QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createModifierListFix(modifierList, modifier, false, false)); |
| return highlightInfo; |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkLiteralExpressionParsingError(@NotNull final PsiLiteralExpression expression, |
| @NotNull LanguageLevel languageLevel, @NotNull PsiFile containingFile) { |
| PsiElement literal = expression.getFirstChild(); |
| assert literal instanceof PsiJavaToken : literal; |
| IElementType type = ((PsiJavaToken)literal).getTokenType(); |
| if (type == JavaTokenType.TRUE_KEYWORD || type == JavaTokenType.FALSE_KEYWORD || type == JavaTokenType.NULL_KEYWORD) { |
| return null; |
| } |
| |
| boolean isInt = PsiLiteralExpressionImpl.INTEGER_LITERALS.contains(type); |
| boolean isFP = PsiLiteralExpressionImpl.REAL_LITERALS.contains(type); |
| String text = isInt || isFP ? literal.getText().toLowerCase() : literal.getText(); |
| Object value = expression.getValue(); |
| |
| if (isFP) { |
| if (text.startsWith(PsiLiteralExpressionImpl.HEX_PREFIX)) { |
| final HighlightInfo info = checkFeature(expression, Feature.HEX_FP_LITERALS, languageLevel, containingFile); |
| if (info != null) return info; |
| } |
| } |
| if (isInt) { |
| if (text.startsWith(PsiLiteralExpressionImpl.BIN_PREFIX)) { |
| final HighlightInfo info = checkFeature(expression, Feature.BIN_LITERALS, languageLevel, containingFile); |
| if (info != null) return info; |
| } |
| } |
| if (isInt || isFP) { |
| if (text.contains("_")) { |
| HighlightInfo info = checkFeature(expression, Feature.UNDERSCORES, languageLevel, containingFile); |
| if (info != null) return info; |
| info = checkUnderscores(expression, text, isInt); |
| if (info != null) return info; |
| } |
| } |
| |
| final PsiElement parent = expression.getParent(); |
| if (type == JavaTokenType.INTEGER_LITERAL) { |
| //literal 2147483648 may appear only as the operand of the unary negation operator -. |
| if (!(text.equals(PsiLiteralExpressionImpl._2_IN_31) && |
| parent instanceof PsiPrefixExpression && |
| ((PsiPrefixExpression)parent).getOperationTokenType() == JavaTokenType.MINUS)) { |
| if (text.equals(PsiLiteralExpressionImpl.HEX_PREFIX)) { |
| final String message = JavaErrorMessages.message("hexadecimal.numbers.must.contain.at.least.one.hexadecimal.digit"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| if (text.equals(PsiLiteralExpressionImpl.BIN_PREFIX)) { |
| final String message = JavaErrorMessages.message("binary.numbers.must.contain.at.least.one.hexadecimal.digit"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| if (value == null || text.equals(PsiLiteralExpressionImpl._2_IN_31)) { |
| final String message = JavaErrorMessages.message("integer.number.too.large"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| } |
| } |
| else if (type == JavaTokenType.LONG_LITERAL) { |
| final String mText = text.endsWith("l") ? text.substring(0, text.length() - 1) : text; |
| //literal 9223372036854775808L may appear only as the operand of the unary negation operator -. |
| if (!(mText.equals(PsiLiteralExpressionImpl._2_IN_63) && |
| parent instanceof PsiPrefixExpression && |
| ((PsiPrefixExpression)parent).getOperationTokenType() == JavaTokenType.MINUS)) { |
| if (mText.equals(PsiLiteralExpressionImpl.HEX_PREFIX)) { |
| final String message = JavaErrorMessages.message("hexadecimal.numbers.must.contain.at.least.one.hexadecimal.digit"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| if (mText.equals(PsiLiteralExpressionImpl.BIN_PREFIX)) { |
| final String message = JavaErrorMessages.message("binary.numbers.must.contain.at.least.one.hexadecimal.digit"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| if (value == null || mText.equals(PsiLiteralExpressionImpl._2_IN_63)) { |
| final String message = JavaErrorMessages.message("long.number.too.large"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| } |
| } |
| else if (isFP) { |
| if (value == null) { |
| final String message = JavaErrorMessages.message("malformed.floating.point.literal"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| } |
| else if (type == JavaTokenType.CHARACTER_LITERAL) { |
| // todo[r.sh] clean this mess up |
| if (value != null) { |
| if (!StringUtil.endsWithChar(text, '\'')) { |
| final String message = JavaErrorMessages.message("unclosed.char.literal"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| } |
| else { |
| if (!StringUtil.startsWithChar(text, '\'')) return null; |
| if (StringUtil.endsWithChar(text, '\'')) { |
| if (text.length() == 1) { |
| final String message = JavaErrorMessages.message("illegal.line.end.in.character.literal"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| text = text.substring(1, text.length() - 1); |
| } |
| else { |
| final String message = JavaErrorMessages.message("illegal.line.end.in.character.literal"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| StringBuilder chars = new StringBuilder(); |
| final boolean success = PsiLiteralExpressionImpl.parseStringCharacters(text, chars, null); |
| if (!success) { |
| final String message = JavaErrorMessages.message("illegal.escape.character.in.character.literal"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| int length = chars.length(); |
| if (length > 1) { |
| final String message = JavaErrorMessages.message("too.many.characters.in.character.literal"); |
| final HighlightInfo info = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createConvertToStringLiteralAction()); |
| return info; |
| } |
| else if (length == 0) { |
| final String message = JavaErrorMessages.message("empty.character.literal"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| } |
| } |
| else if (type == JavaTokenType.STRING_LITERAL) { |
| if (value == null) { |
| for (final PsiElement element : expression.getChildren()) { |
| if (element instanceof OuterLanguageElement) { |
| return null; |
| } |
| } |
| |
| if (!StringUtil.startsWithChar(text, '\"')) return null; |
| if (StringUtil.endsWithChar(text, '\"')) { |
| if (text.length() == 1) { |
| final String message = JavaErrorMessages.message("illegal.line.end.in.string.literal"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| text = text.substring(1, text.length() - 1); |
| } |
| else { |
| final String message = JavaErrorMessages.message("illegal.line.end.in.string.literal"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| StringBuilder chars = new StringBuilder(); |
| boolean success = PsiLiteralExpressionImpl.parseStringCharacters(text, chars, null); |
| if (!success) { |
| final String message = JavaErrorMessages.message("illegal.escape.character.in.string.literal"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| } |
| } |
| |
| if (value instanceof Float) { |
| final Float number = (Float)value; |
| if (number.isInfinite()) { |
| final String message = JavaErrorMessages.message("floating.point.number.too.large"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| if (number.floatValue() == 0 && !TypeConversionUtil.isFPZero(text)) { |
| final String message = JavaErrorMessages.message("floating.point.number.too.small"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| } |
| else if (value instanceof Double) { |
| final Double number = (Double)value; |
| if (number.isInfinite()) { |
| final String message = JavaErrorMessages.message("floating.point.number.too.large"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| if (number.doubleValue() == 0 && !TypeConversionUtil.isFPZero(text)) { |
| final String message = JavaErrorMessages.message("floating.point.number.too.small"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| } |
| |
| return null; |
| } |
| |
| private static final Pattern FP_LITERAL_PARTS = |
| Pattern.compile("(?:" + |
| "(?:0x([_\\p{XDigit}]*)\\.?([_\\p{XDigit}]*)p[+-]?([_\\d]*))" + |
| "|" + |
| "(?:([_\\d]*)\\.?([_\\d]*)e?[+-]?([_\\d]*))" + |
| ")[fd]?"); |
| |
| @Nullable |
| private static HighlightInfo checkUnderscores(PsiElement expression, String text, boolean isInt) { |
| String[] parts = ArrayUtil.EMPTY_STRING_ARRAY; |
| |
| if (isInt) { |
| int start = 0; |
| int end = text.length(); |
| if (text.startsWith(PsiLiteralExpressionImpl.HEX_PREFIX) || text.startsWith(PsiLiteralExpressionImpl.BIN_PREFIX)) start += 2; |
| if (StringUtil.endsWithChar(text, 'l')) --end; |
| parts = new String[]{text.substring(start, end)}; |
| } |
| else { |
| Matcher matcher = FP_LITERAL_PARTS.matcher(text); |
| if (matcher.matches()) { |
| parts = new String[matcher.groupCount()]; |
| for (int i = 0; i < matcher.groupCount(); i++) { |
| parts[i] = matcher.group(i + 1); |
| } |
| } |
| } |
| |
| for (String part : parts) { |
| if (part != null && (StringUtil.startsWithChar(part, '_') || StringUtil.endsWithChar(part, '_'))) { |
| String message = JavaErrorMessages.message("illegal.underscore"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| static HighlightInfo checkMustBeBoolean(@NotNull PsiExpression expr, PsiType type) { |
| PsiElement parent = expr.getParent(); |
| if (parent instanceof PsiIfStatement || parent instanceof PsiWhileStatement || |
| parent instanceof PsiForStatement && expr.equals(((PsiForStatement)parent).getCondition()) || |
| parent instanceof PsiDoWhileStatement && expr.equals(((PsiDoWhileStatement)parent).getCondition())) { |
| if (expr.getNextSibling() instanceof PsiErrorElement) return null; |
| |
| if (!TypeConversionUtil.isBooleanType(type)) { |
| final HighlightInfo info = createIncompatibleTypeHighlightInfo(PsiType.BOOLEAN, type, expr.getTextRange(), 0); |
| if (expr instanceof PsiMethodCallExpression) { |
| final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expr; |
| final PsiMethod method = methodCall.resolveMethod(); |
| if (method != null && PsiType.VOID.equals(method.getReturnType())) { |
| QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createMethodReturnFix(method, PsiType.BOOLEAN, true)); |
| } |
| } |
| return info; |
| } |
| } |
| return null; |
| } |
| |
| |
| @NotNull |
| static Set<PsiClassType> collectUnhandledExceptions(@NotNull final PsiTryStatement statement) { |
| final Set<PsiClassType> thrownTypes = ContainerUtil.newHashSet(); |
| |
| final PsiCodeBlock tryBlock = statement.getTryBlock(); |
| if (tryBlock != null) { |
| thrownTypes.addAll(ExceptionUtil.collectUnhandledExceptions(tryBlock, tryBlock)); |
| } |
| |
| final PsiResourceList resources = statement.getResourceList(); |
| if (resources != null) { |
| thrownTypes.addAll(ExceptionUtil.collectUnhandledExceptions(resources, resources)); |
| } |
| |
| return thrownTypes; |
| } |
| |
| @Nullable |
| static Collection<HighlightInfo> checkExceptionThrownInTry(@NotNull final PsiParameter parameter, @NotNull final Set<PsiClassType> thrownTypes) { |
| final PsiElement declarationScope = parameter.getDeclarationScope(); |
| if (!(declarationScope instanceof PsiCatchSection)) return null; |
| |
| final PsiType caughtType = parameter.getType(); |
| if (caughtType instanceof PsiClassType) { |
| return checkSimpleCatchParameter(parameter, thrownTypes, (PsiClassType)caughtType); |
| } |
| if (caughtType instanceof PsiDisjunctionType) { |
| return checkMultiCatchParameter(parameter, thrownTypes); |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| private static Collection<HighlightInfo> checkSimpleCatchParameter(@NotNull final PsiParameter parameter, |
| @NotNull final Collection<PsiClassType> thrownTypes, |
| @NotNull final PsiClassType caughtType) { |
| if (ExceptionUtil.isUncheckedExceptionOrSuperclass(caughtType)) return null; |
| |
| for (PsiClassType exceptionType : thrownTypes) { |
| if (exceptionType.isAssignableFrom(caughtType) || caughtType.isAssignableFrom(exceptionType)) return null; |
| } |
| |
| final String description = JavaErrorMessages.message("exception.never.thrown.try", JavaHighlightUtil.formatType(caughtType)); |
| final HighlightInfo errorResult = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(parameter).descriptionAndTooltip(description).create(); |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createDeleteCatchFix(parameter)); |
| return Collections.singleton(errorResult); |
| } |
| |
| @Nullable |
| private static Collection<HighlightInfo> checkMultiCatchParameter(@NotNull final PsiParameter parameter, |
| @NotNull final Collection<PsiClassType> thrownTypes) { |
| final List<PsiTypeElement> typeElements = PsiUtil.getParameterTypeElements(parameter); |
| final Collection<HighlightInfo> highlights = ContainerUtil.newArrayListWithCapacity(typeElements.size()); |
| |
| for (final PsiTypeElement typeElement : typeElements) { |
| final PsiType catchType = typeElement.getType(); |
| if (catchType instanceof PsiClassType && ExceptionUtil.isUncheckedExceptionOrSuperclass((PsiClassType)catchType)) continue; |
| |
| boolean used = false; |
| for (PsiClassType exceptionType : thrownTypes) { |
| if (exceptionType.isAssignableFrom(catchType) || catchType.isAssignableFrom(exceptionType)) { |
| used = true; |
| break; |
| } |
| } |
| if (!used) { |
| final String description = JavaErrorMessages.message("exception.never.thrown.try", JavaHighlightUtil.formatType(catchType)); |
| final HighlightInfo highlight = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create(); |
| QuickFixAction.registerQuickFixAction(highlight, QUICK_FIX_FACTORY.createDeleteMultiCatchFix(typeElement)); |
| highlights.add(highlight); |
| } |
| } |
| |
| return highlights; |
| } |
| |
| |
| @Nullable |
| static Collection<HighlightInfo> checkWithImprovedCatchAnalysis(@NotNull PsiParameter parameter, |
| @NotNull Collection<PsiClassType> thrownInTryStatement, |
| @NotNull PsiFile containingFile) { |
| final PsiElement scope = parameter.getDeclarationScope(); |
| if (!(scope instanceof PsiCatchSection)) return null; |
| |
| final PsiCatchSection catchSection = (PsiCatchSection)scope; |
| final PsiCatchSection[] allCatchSections = catchSection.getTryStatement().getCatchSections(); |
| final int idx = ArrayUtilRt.find(allCatchSections, catchSection); |
| if (idx <= 0) return null; |
| |
| final Collection<PsiClassType> thrownTypes = ContainerUtil.newHashSet(thrownInTryStatement); |
| final PsiManager manager = containingFile.getManager(); |
| final GlobalSearchScope parameterResolveScope = parameter.getResolveScope(); |
| thrownTypes.add(PsiType.getJavaLangError(manager, parameterResolveScope)); |
| thrownTypes.add(PsiType.getJavaLangRuntimeException(manager, parameterResolveScope)); |
| final Collection<HighlightInfo> result = ContainerUtil.newArrayList(); |
| |
| final List<PsiTypeElement> parameterTypeElements = PsiUtil.getParameterTypeElements(parameter); |
| final boolean isMultiCatch = parameterTypeElements.size() > 1; |
| for (PsiTypeElement catchTypeElement : parameterTypeElements) { |
| final PsiType catchType = catchTypeElement.getType(); |
| if (ExceptionUtil.isGeneralExceptionType(catchType)) continue; |
| |
| // collect exceptions which are caught by this type |
| Collection<PsiClassType> caught = ContainerUtil.findAll(thrownTypes, new Condition<PsiClassType>() { |
| @Override |
| public boolean value(@NotNull PsiClassType type) { |
| return catchType.isAssignableFrom(type); |
| } |
| }); |
| if (caught.isEmpty()) continue; |
| final Collection<PsiClassType> caughtCopy = ContainerUtil.newHashSet(caught); |
| |
| // exclude all which are caught by previous catch sections |
| for (int i = 0; i < idx; i++) { |
| final PsiParameter prevCatchParameter = allCatchSections[i].getParameter(); |
| if (prevCatchParameter == null) continue; |
| for (PsiTypeElement prevCatchTypeElement : PsiUtil.getParameterTypeElements(prevCatchParameter)) { |
| final PsiType prevCatchType = prevCatchTypeElement.getType(); |
| for (Iterator<PsiClassType> iterator = caught.iterator(); iterator.hasNext(); ) { |
| if (prevCatchType.isAssignableFrom(iterator.next())) iterator.remove(); |
| } |
| if (caught.isEmpty()) break; |
| } |
| } |
| |
| // check & warn |
| if (caught.isEmpty()) { |
| final String message = JavaErrorMessages.message("exception.already.caught.warn", formatTypes(caughtCopy), caughtCopy.size()); |
| final HighlightInfo highlightInfo = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range(catchSection).descriptionAndTooltip(message).create(); |
| if (isMultiCatch) { |
| QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createDeleteMultiCatchFix(catchTypeElement)); |
| } |
| else { |
| QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createDeleteCatchFix(parameter)); |
| } |
| result.add(highlightInfo); |
| } |
| } |
| |
| return result; |
| } |
| |
| |
| @Nullable |
| static HighlightInfo checkNotAStatement(@NotNull PsiStatement statement) { |
| if (!PsiUtil.isStatement(statement) && !PsiUtilCore.hasErrorElementChild(statement)) { |
| String description = JavaErrorMessages.message("not.a.statement"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); |
| } |
| return null; |
| } |
| |
| |
| @Nullable |
| public static HighlightInfo checkSwitchSelectorType(@NotNull PsiSwitchStatement statement) { |
| final PsiExpression expression = statement.getExpression(); |
| PsiType type = expression == null ? null : expression.getType(); |
| if (type == null) { |
| return null; |
| } |
| HighlightInfo errorResult = null; |
| if (!isValidTypeForSwitchSelector(type, PsiUtil.isLanguageLevel7OrHigher(expression))) { |
| String message = |
| JavaErrorMessages.message("incompatible.types", JavaErrorMessages.message("valid.switch.selector.types"), JavaHighlightUtil.formatType( |
| type)); |
| errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createConvertSwitchToIfIntention(statement)); |
| if (PsiType.LONG.equals(type) || PsiType.FLOAT.equals(type) || PsiType.DOUBLE.equals(type)) { |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createAddTypeCastFix(PsiType.INT, expression)); |
| } |
| } |
| else { |
| final PsiClass member = PsiUtil.resolveClassInClassTypeOnly(type); |
| if (member != null && !PsiUtil.isAccessible(member.getProject(), member, expression, null)) { |
| String message = PsiFormatUtil.formatClass(member, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME) + " is inaccessible here"; |
| errorResult = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| } |
| return errorResult; |
| } |
| |
| public static boolean isValidTypeForSwitchSelector(@NotNull PsiType type, final boolean languageLevel7OrHigher) { |
| if (TypeConversionUtil.getTypeRank(type) <= TypeConversionUtil.INT_RANK) return true; |
| if (type instanceof PsiClassType) { |
| PsiClass psiClass = ((PsiClassType)type).resolve(); |
| if (psiClass == null) return false; |
| if (psiClass.isEnum()) { |
| return true; |
| } |
| if (languageLevel7OrHigher) { |
| return Comparing.strEqual(psiClass.getQualifiedName(), CommonClassNames.JAVA_LANG_STRING); |
| } |
| } |
| return false; |
| } |
| |
| @Nullable |
| static HighlightInfo checkPolyadicOperatorApplicable(@NotNull PsiPolyadicExpression expression) { |
| PsiExpression[] operands = expression.getOperands(); |
| |
| PsiType lType = operands[0].getType(); |
| IElementType operationSign = expression.getOperationTokenType(); |
| for (int i = 1; i < operands.length; i++) { |
| PsiExpression operand = operands[i]; |
| PsiType rType = operand.getType(); |
| if (!TypeConversionUtil.isBinaryOperatorApplicable(operationSign, lType, rType, false)) { |
| PsiJavaToken token = expression.getTokenBeforeOperand(operand); |
| String message = JavaErrorMessages.message("binary.operator.not.applicable", token.getText(), |
| JavaHighlightUtil.formatType(lType), |
| JavaHighlightUtil.formatType(rType)); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(message).create(); |
| } |
| lType = TypeConversionUtil.calcTypeForBinaryExpression(lType, rType, operationSign, true); |
| } |
| |
| return null; |
| } |
| |
| |
| @Nullable |
| static HighlightInfo checkUnaryOperatorApplicable(@Nullable PsiJavaToken token, @Nullable PsiExpression expression) { |
| if (token != null && expression != null && !TypeConversionUtil.isUnaryOperatorApplicable(token, expression)) { |
| PsiType type = expression.getType(); |
| if (type == null) return null; |
| String message = JavaErrorMessages.message("unary.operator.not.applicable", token.getText(), JavaHighlightUtil.formatType(type)); |
| |
| PsiElement parentExpr = token.getParent(); |
| HighlightInfo highlightInfo = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(parentExpr).descriptionAndTooltip(message).create(); |
| if (parentExpr instanceof PsiPrefixExpression && token.getTokenType() == JavaTokenType.EXCL) { |
| QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createNegationBroadScopeFix((PsiPrefixExpression)parentExpr)); |
| } |
| return highlightInfo; |
| } |
| return null; |
| } |
| |
| @Nullable |
| static HighlightInfo checkThisOrSuperExpressionInIllegalContext(@NotNull PsiExpression expr, |
| @Nullable PsiJavaCodeReferenceElement qualifier) { |
| if (expr instanceof PsiSuperExpression) { |
| final PsiElement parent = expr.getParent(); |
| if (!(parent instanceof PsiReferenceExpression)) { |
| // like in 'Object o = super;' |
| final int o = expr.getTextRange().getEndOffset(); |
| String description = JavaErrorMessages.message("dot.expected.after.super.or.this"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(o, o + 1).descriptionAndTooltip(description).create(); |
| } |
| |
| if (PsiUtil.isLanguageLevel8OrHigher(expr)) { |
| final PsiMethod method = PsiTreeUtil.getParentOfType(expr, PsiMethod.class); |
| if (method != null && method.hasModifierProperty(PsiModifier.DEFAULT) && qualifier == null) { |
| //todo[r.sh] "Add qualifier" quick fix |
| String description = JavaErrorMessages.message("unqualified.super.disallowed"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(parent).descriptionAndTooltip(description).create(); |
| } |
| } |
| } |
| |
| PsiClass aClass; |
| if (qualifier != null) { |
| PsiElement resolved = qualifier.advancedResolve(true).getElement(); |
| if (resolved != null && !(resolved instanceof PsiClass)) { |
| String description = JavaErrorMessages.message("class.expected"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(qualifier).descriptionAndTooltip(description).create(); |
| } |
| aClass = (PsiClass)resolved; |
| } |
| else { |
| aClass = PsiTreeUtil.getParentOfType(expr, PsiClass.class); |
| } |
| if (aClass == null) return null; |
| |
| if (!InheritanceUtil.hasEnclosingInstanceInScope(aClass, expr, false, false) && |
| !resolvesToImmediateSuperInterface(expr, qualifier, aClass)) { |
| return HighlightClassUtil.reportIllegalEnclosingUsage(expr, null, aClass, expr); |
| } |
| |
| if (expr instanceof PsiThisExpression) { |
| final PsiMethod psiMethod = PsiTreeUtil.getParentOfType(expr, PsiMethod.class); |
| if (psiMethod == null || psiMethod.getContainingClass() != aClass && !isInsideDefaultMethod(psiMethod, aClass)) { |
| if (aClass.isInterface()) { |
| return thisNotFoundInInterfaceInfo(expr); |
| } |
| |
| if (aClass instanceof PsiAnonymousClass && PsiTreeUtil.isAncestor(((PsiAnonymousClass)aClass).getArgumentList(), expr, true)) { |
| final PsiClass parentClass = PsiTreeUtil.getParentOfType(aClass, PsiClass.class, true); |
| if (parentClass != null && parentClass.isInterface()) { |
| return thisNotFoundInInterfaceInfo(expr); |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| private static boolean isInsideDefaultMethod(PsiMethod method, PsiClass aClass) { |
| while (method != null && method.getContainingClass() != aClass) { |
| method = PsiTreeUtil.getParentOfType(method, PsiMethod.class, true); |
| } |
| return method != null && method.hasModifierProperty(PsiModifier.DEFAULT); |
| } |
| |
| private static HighlightInfo thisNotFoundInInterfaceInfo(@NotNull PsiExpression expr) { |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expr).descriptionAndTooltip("Cannot find symbol variable this").create(); |
| } |
| |
| private static boolean resolvesToImmediateSuperInterface(@NotNull PsiExpression expr, |
| @Nullable PsiJavaCodeReferenceElement qualifier, |
| @NotNull PsiClass aClass) { |
| if (!(expr instanceof PsiSuperExpression) || qualifier == null || !PsiUtil.isLanguageLevel8OrHigher(expr)) return false; |
| final PsiType superType = expr.getType(); |
| if (!(superType instanceof PsiClassType)) return false; |
| final PsiClass superClass = ((PsiClassType)superType).resolve(); |
| return superClass != null && aClass.equals(superClass); |
| } |
| |
| @NotNull |
| static String buildProblemWithStaticDescription(@NotNull PsiElement refElement) { |
| String type = LanguageFindUsages.INSTANCE.forLanguage(JavaLanguage.INSTANCE).getType(refElement); |
| String name = HighlightMessageUtil.getSymbolName(refElement, PsiSubstitutor.EMPTY); |
| return JavaErrorMessages.message("non.static.symbol.referenced.from.static.context", type, name); |
| } |
| |
| static void registerStaticProblemQuickFixAction(@NotNull PsiElement refElement, HighlightInfo errorResult, @NotNull PsiJavaCodeReferenceElement place) { |
| if (refElement instanceof PsiModifierListOwner) { |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createModifierListFix((PsiModifierListOwner)refElement, PsiModifier.STATIC, true, false)); |
| } |
| // make context non static |
| PsiModifierListOwner staticParent = PsiUtil.getEnclosingStaticElement(place, null); |
| if (staticParent != null && isInstanceReference(place)) { |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createModifierListFix(staticParent, PsiModifier.STATIC, false, false)); |
| } |
| if (place instanceof PsiReferenceExpression && refElement instanceof PsiField) { |
| QuickFixAction.registerQuickFixAction(errorResult, QUICK_FIX_FACTORY.createCreateFieldFromUsageFix((PsiReferenceExpression)place)); |
| } |
| } |
| |
| private static boolean isInstanceReference(@NotNull PsiJavaCodeReferenceElement place) { |
| PsiElement qualifier = place.getQualifier(); |
| if (qualifier == null) return true; |
| if (!(qualifier instanceof PsiJavaCodeReferenceElement)) return false; |
| PsiElement q = ((PsiReference)qualifier).resolve(); |
| if (q instanceof PsiClass) return false; |
| if (q != null) return true; |
| String qname = ((PsiJavaCodeReferenceElement)qualifier).getQualifiedName(); |
| return qname == null || !Character.isLowerCase(qname.charAt(0)); |
| } |
| |
| @NotNull |
| static String buildProblemWithAccessDescription(@NotNull final PsiElement reference, @NotNull final JavaResolveResult result) { |
| return buildProblemWithAccessDescription(reference, result, result.getElement()); |
| } |
| |
| @NotNull |
| static String buildProblemWithAccessDescription(@NotNull final PsiElement reference, |
| @NotNull final JavaResolveResult result, |
| @NotNull final PsiElement resolved) { |
| assert resolved instanceof PsiModifierListOwner : resolved; |
| PsiModifierListOwner refElement = (PsiModifierListOwner)resolved; |
| String symbolName = HighlightMessageUtil.getSymbolName(refElement, result.getSubstitutor()); |
| |
| if (refElement.hasModifierProperty(PsiModifier.PRIVATE)) { |
| String containerName = getContainerName(refElement, result.getSubstitutor()); |
| return JavaErrorMessages.message("private.symbol", symbolName, containerName); |
| } |
| else if (refElement.hasModifierProperty(PsiModifier.PROTECTED)) { |
| String containerName = getContainerName(refElement, result.getSubstitutor()); |
| return JavaErrorMessages.message("protected.symbol", symbolName, containerName); |
| } |
| else { |
| PsiClass packageLocalClass = getPackageLocalClassInTheMiddle(reference); |
| if (packageLocalClass != null) { |
| refElement = packageLocalClass; |
| symbolName = HighlightMessageUtil.getSymbolName(refElement, result.getSubstitutor()); |
| } |
| if (refElement.hasModifierProperty(PsiModifier.PACKAGE_LOCAL) || packageLocalClass != null) { |
| String containerName = getContainerName(refElement, result.getSubstitutor()); |
| return JavaErrorMessages.message("package.local.symbol", symbolName, containerName); |
| } |
| else { |
| String containerName = getContainerName(refElement, result.getSubstitutor()); |
| return JavaErrorMessages.message("visibility.access.problem", symbolName, containerName); |
| } |
| } |
| } |
| |
| private static PsiElement getContainer(PsiModifierListOwner refElement) { |
| for (ContainerProvider provider : ContainerProvider.EP_NAME.getExtensions()) { |
| final PsiElement container = provider.getContainer(refElement); |
| if (container != null) return container; |
| } |
| return refElement.getParent(); |
| } |
| |
| private static String getContainerName(PsiModifierListOwner refElement, final PsiSubstitutor substitutor) { |
| final PsiElement container = getContainer(refElement); |
| return container == null ? "?" : HighlightMessageUtil.getSymbolName(container, substitutor); |
| } |
| |
| @Nullable |
| static HighlightInfo checkValidArrayAccessExpression(@NotNull PsiArrayAccessExpression arrayAccessExpression) { |
| final PsiExpression arrayExpression = arrayAccessExpression.getArrayExpression(); |
| final PsiType arrayExpressionType = arrayExpression.getType(); |
| |
| if (arrayExpressionType != null && !(arrayExpressionType instanceof PsiArrayType)) { |
| final String description = JavaErrorMessages.message("array.type.expected", JavaHighlightUtil.formatType(arrayExpressionType)); |
| final HighlightInfo info = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(arrayExpression).descriptionAndTooltip(description).create(); |
| QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createReplaceWithListAccessFix(arrayAccessExpression)); |
| return info; |
| } |
| |
| final PsiExpression indexExpression = arrayAccessExpression.getIndexExpression(); |
| return indexExpression != null ? checkAssignability(PsiType.INT, indexExpression.getType(), indexExpression, indexExpression) : null; |
| } |
| |
| |
| @Nullable |
| public static HighlightInfo checkCatchParameterIsThrowable(@NotNull final PsiParameter parameter) { |
| if (parameter.getDeclarationScope() instanceof PsiCatchSection) { |
| final PsiType type = parameter.getType(); |
| return checkMustBeThrowable(type, parameter, true); |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkTryResourceIsAutoCloseable(@NotNull final PsiResourceVariable resource) { |
| final PsiType type = resource.getType(); |
| final PsiElementFactory factory = JavaPsiFacade.getInstance(resource.getProject()).getElementFactory(); |
| final PsiClassType autoCloseable = factory.createTypeByFQClassName(CommonClassNames.JAVA_LANG_AUTO_CLOSEABLE, resource.getResolveScope()); |
| if (TypeConversionUtil.isAssignable(autoCloseable, type)) return null; |
| |
| return createIncompatibleTypeHighlightInfo(autoCloseable, type, resource.getTextRange(), 0); |
| } |
| |
| @Nullable |
| public static Collection<HighlightInfo> checkArrayInitializer(final PsiExpression initializer, PsiType type) { |
| if (!(initializer instanceof PsiArrayInitializerExpression)) return null; |
| if (!(type instanceof PsiArrayType)) return null; |
| |
| final PsiType componentType = ((PsiArrayType)type).getComponentType(); |
| final PsiArrayInitializerExpression arrayInitializer = (PsiArrayInitializerExpression)initializer; |
| |
| boolean arrayTypeFixChecked = false; |
| VariableArrayTypeFix fix = null; |
| |
| final Collection<HighlightInfo> result = ContainerUtil.newArrayList(); |
| final PsiExpression[] initializers = arrayInitializer.getInitializers(); |
| for (PsiExpression expression : initializers) { |
| final HighlightInfo info = checkArrayInitializerCompatibleTypes(expression, componentType); |
| if (info != null) { |
| result.add(info); |
| |
| if (!arrayTypeFixChecked) { |
| final PsiType checkResult = JavaHighlightUtil.sameType(initializers); |
| fix = checkResult != null ? new VariableArrayTypeFix(arrayInitializer, checkResult) : null; |
| arrayTypeFixChecked = true; |
| } |
| if (fix != null) { |
| QuickFixAction.registerQuickFixAction(info, new LocalQuickFixOnPsiElementAsIntentionAdapter(fix)); |
| } |
| } |
| } |
| return result; |
| } |
| |
| @Nullable |
| private static HighlightInfo checkArrayInitializerCompatibleTypes(@NotNull PsiExpression initializer, final PsiType componentType) { |
| PsiType initializerType = initializer.getType(); |
| if (initializerType == null) { |
| String description = JavaErrorMessages.message("illegal.initializer", JavaHighlightUtil.formatType(componentType)); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(initializer).descriptionAndTooltip(description).create(); |
| } |
| PsiExpression expression = initializer instanceof PsiArrayInitializerExpression ? null : initializer; |
| return checkAssignability(componentType, initializerType, expression, initializer); |
| } |
| |
| @Nullable |
| public static HighlightInfo checkExpressionRequired(@NotNull PsiReferenceExpression expression, @NotNull JavaResolveResult resultForIncompleteCode) { |
| if (expression.getNextSibling() instanceof PsiErrorElement) return null; |
| PsiElement resolved = resultForIncompleteCode.getElement(); |
| if (resolved == null) return null; |
| PsiElement parent = expression.getParent(); |
| // String.class or String() are both correct |
| if (parent instanceof PsiReferenceExpression || parent instanceof PsiMethodCallExpression) return null; |
| if (resolved instanceof PsiVariable) return null; |
| String description = JavaErrorMessages.message("expression.expected"); |
| final HighlightInfo info = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); |
| UnresolvedReferenceQuickFixProvider.registerReferenceFixes(expression, new QuickFixActionRegistrarImpl(info)); |
| return info; |
| } |
| |
| |
| @Nullable |
| public static HighlightInfo checkArrayInitializerApplicable(@NotNull PsiArrayInitializerExpression expression) { |
| /* |
| JLS 10.6 Array Initializers |
| An array initializer may be specified in a declaration, or as part of an array creation expression |
| */ |
| PsiElement parent = expression.getParent(); |
| if (parent instanceof PsiVariable) { |
| PsiVariable variable = (PsiVariable)parent; |
| if (variable.getType() instanceof PsiArrayType) return null; |
| } |
| else if (parent instanceof PsiNewExpression || parent instanceof PsiArrayInitializerExpression) { |
| return null; |
| } |
| |
| String description = JavaErrorMessages.message("array.initializer.not.allowed"); |
| HighlightInfo info = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); |
| QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createAddNewArrayExpressionFix(expression)); |
| return info; |
| } |
| |
| |
| @Nullable |
| public static HighlightInfo checkCaseStatement(@NotNull PsiSwitchLabelStatement statement) { |
| PsiSwitchStatement switchStatement = statement.getEnclosingSwitchStatement(); |
| if (switchStatement == null) { |
| String description = JavaErrorMessages.message("case.statement.outside.switch"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); |
| } |
| if (switchStatement.getBody() == null) return null; |
| PsiExpression switchExpression = switchStatement.getExpression(); |
| PsiType switchType = switchExpression == null ? PsiType.INT : switchExpression.getType(); |
| // check constant expression |
| PsiExpression caseValue = statement.getCaseValue(); |
| |
| // Every case constant expression associated with a switch statement must be assignable ($5.2) to the type of the switch Expression. |
| if (caseValue != null && switchExpression != null) { |
| HighlightInfo highlightInfo = checkAssignability(switchType, caseValue.getType(), caseValue, caseValue); |
| if (highlightInfo != null) return highlightInfo; |
| } |
| Object value = null; |
| |
| boolean isEnumSwitch = false; |
| if (!statement.isDefaultCase() && caseValue != null) { |
| if (caseValue instanceof PsiReferenceExpression) { |
| PsiElement element = ((PsiReferenceExpression)caseValue).resolve(); |
| if (element instanceof PsiEnumConstant) { |
| isEnumSwitch = true; |
| value = ((PsiEnumConstant)element).getName(); |
| if (!(((PsiReferenceExpression)caseValue).getQualifier() == null)) { |
| String message = JavaErrorMessages.message("qualified.enum.constant.in.switch"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(caseValue).descriptionAndTooltip(message).create(); |
| } |
| } |
| } |
| if (!isEnumSwitch) { |
| value = ConstantExpressionUtil.computeCastTo(caseValue, switchType); |
| } |
| if (value == null) { |
| String description = JavaErrorMessages.message("constant.expression.required"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(caseValue).descriptionAndTooltip(description).create(); |
| } |
| } |
| |
| // check duplicate |
| PsiStatement[] statements = switchStatement.getBody().getStatements(); |
| for (PsiStatement st : statements) { |
| if (st == statement) continue; |
| if (!(st instanceof PsiSwitchLabelStatement)) continue; |
| PsiSwitchLabelStatement labelStatement = (PsiSwitchLabelStatement)st; |
| if (labelStatement.isDefaultCase() != statement.isDefaultCase()) continue; |
| PsiExpression caseExpr = labelStatement.getCaseValue(); |
| if (isEnumSwitch && caseExpr instanceof PsiReferenceExpression) { |
| PsiElement element = ((PsiReferenceExpression)caseExpr).resolve(); |
| if (!(element instanceof PsiEnumConstant && Comparing.equal(((PsiEnumConstant)element).getName(), value))) continue; |
| } |
| else { |
| // not assignable error already caught |
| if (!TypeConversionUtil.areTypesAssignmentCompatible(switchType, caseExpr)) continue; |
| if (!Comparing.equal(ConstantExpressionUtil.computeCastTo(caseExpr, switchType), value)) continue; |
| } |
| String description = statement.isDefaultCase() |
| ? JavaErrorMessages.message("duplicate.default.switch.label") |
| : JavaErrorMessages.message("duplicate.switch.label", value); |
| PsiElement element = value == null ? statement : caseValue; |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(description).create(); |
| } |
| |
| // must be followed with colon |
| PsiElement lastChild = statement.getLastChild(); |
| while (lastChild instanceof PsiComment || lastChild instanceof PsiWhiteSpace) { |
| lastChild = lastChild.getPrevSibling(); |
| } |
| if (!(lastChild instanceof PsiJavaToken && ((PsiJavaToken)lastChild).getTokenType() == JavaTokenType.COLON)) { |
| int start = statement.getTextRange().getEndOffset(); |
| int end = statement.getTextRange().getEndOffset() + 1; |
| String description = JavaErrorMessages.message("switch.colon.expected.after.case.label"); |
| CharSequence chars = statement.getContainingFile().getViewProvider().getContents(); |
| boolean isAfterEndOfLine = end >= chars.length() || chars.charAt(start) == '\n' || chars.charAt(start) == '\r'; |
| HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(start, end).descriptionAndTooltip(description); |
| if (isAfterEndOfLine) { |
| builder.endOfLine(); |
| } |
| return builder.create(); |
| } |
| return null; |
| } |
| |
| |
| /** |
| * see JLS 8.3.2.3 |
| */ |
| @Nullable |
| public static HighlightInfo checkIllegalForwardReferenceToField(@NotNull PsiReferenceExpression expression, @NotNull PsiField referencedField) { |
| PsiClass containingClass = referencedField.getContainingClass(); |
| if (containingClass == null) return null; |
| if (expression.getContainingFile() != referencedField.getContainingFile()) return null; |
| if (expression.getTextRange().getStartOffset() >= referencedField.getTextRange().getEndOffset()) return null; |
| // only simple reference can be illegal |
| if (expression.getQualifierExpression() != null) return null; |
| PsiField initField = findEnclosingFieldInitializer(expression); |
| PsiClassInitializer classInitializer = findParentClassInitializer(expression); |
| if (initField == null && classInitializer == null) return null; |
| // instance initializers may access static fields |
| boolean isStaticClassInitializer = classInitializer != null && classInitializer.hasModifierProperty(PsiModifier.STATIC); |
| boolean isStaticInitField = initField != null && initField.hasModifierProperty(PsiModifier.STATIC); |
| boolean inStaticContext = isStaticInitField || isStaticClassInitializer; |
| if (!inStaticContext && referencedField.hasModifierProperty(PsiModifier.STATIC)) return null; |
| if (PsiUtil.isOnAssignmentLeftHand(expression) && !PsiUtil.isAccessedForReading(expression)) return null; |
| if (!containingClass.getManager().areElementsEquivalent(containingClass, PsiTreeUtil.getParentOfType(expression, PsiClass.class))) { |
| return null; |
| } |
| String description = JavaErrorMessages.message("illegal.forward.reference"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); |
| } |
| |
| /** |
| * @return field that has initializer with this element as subexpression or null if not found |
| */ |
| @Nullable |
| static PsiField findEnclosingFieldInitializer(@Nullable PsiElement element) { |
| while (element != null) { |
| PsiElement parent = element.getParent(); |
| if (parent instanceof PsiField) { |
| PsiField field = (PsiField)parent; |
| if (element == field.getInitializer()) return field; |
| if (field instanceof PsiEnumConstant && element == ((PsiEnumConstant)field).getArgumentList()) return field; |
| } |
| if (element instanceof PsiClass || element instanceof PsiMethod || parent instanceof PsiLambdaExpression) return null; |
| element = parent; |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static PsiClassInitializer findParentClassInitializer(@Nullable PsiElement element) { |
| while (element != null) { |
| if (element instanceof PsiClassInitializer) return (PsiClassInitializer)element; |
| if (element instanceof PsiClass || element instanceof PsiMethod) return null; |
| element = element.getParent(); |
| } |
| return null; |
| } |
| |
| |
| @Nullable |
| public static HighlightInfo checkIllegalType(@Nullable PsiTypeElement typeElement) { |
| if (typeElement == null || typeElement.getParent() instanceof PsiTypeElement) return null; |
| |
| if (PsiUtil.isInsideJavadocComment(typeElement)) return null; |
| |
| PsiType type = typeElement.getType(); |
| PsiType componentType = type.getDeepComponentType(); |
| if (componentType instanceof PsiClassType) { |
| PsiClass aClass = PsiUtil.resolveClassInType(componentType); |
| if (aClass == null) { |
| String canonicalText = type.getCanonicalText(); |
| String description = JavaErrorMessages.message("unknown.class", canonicalText); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create(); |
| } |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkIllegalVoidType(@NotNull PsiKeyword type) { |
| if (!PsiKeyword.VOID.equals(type.getText())) return null; |
| |
| PsiElement parent = type.getParent(); |
| if (parent instanceof PsiTypeElement) { |
| PsiElement typeOwner = parent.getParent(); |
| if (typeOwner != null) { |
| // do not highlight incomplete declarations |
| if (PsiUtilCore.hasErrorElementChild(typeOwner)) return null; |
| } |
| |
| if (typeOwner instanceof PsiMethod) { |
| PsiMethod method = (PsiMethod)typeOwner; |
| if (method.getReturnTypeElement() == parent && PsiType.VOID.equals(method.getReturnType())) return null; |
| } |
| else if (typeOwner instanceof PsiClassObjectAccessExpression) { |
| if (TypeConversionUtil.isVoidType(((PsiClassObjectAccessExpression)typeOwner).getOperand().getType())) return null; |
| } |
| else if (typeOwner instanceof JavaCodeFragment) { |
| if (typeOwner.getUserData(PsiUtil.VALID_VOID_TYPE_IN_CODE_FRAGMENT) != null) return null; |
| } |
| } |
| |
| String description = JavaErrorMessages.message("illegal.type.void"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(type).descriptionAndTooltip(description).create(); |
| } |
| |
| @Nullable |
| public static HighlightInfo checkMemberReferencedBeforeConstructorCalled(@NotNull PsiElement expression, |
| PsiElement resolved, |
| @NotNull PsiFile containingFile) { |
| PsiClass referencedClass; |
| @NonNls String resolvedName; |
| PsiType type; |
| if (expression instanceof PsiJavaCodeReferenceElement) { |
| // redirected ctr |
| if (PsiKeyword.THIS.equals(((PsiJavaCodeReferenceElement)expression).getReferenceName()) |
| && resolved instanceof PsiMethod |
| && ((PsiMethod)resolved).isConstructor()) { |
| return null; |
| } |
| PsiElement qualifier = ((PsiJavaCodeReferenceElement)expression).getQualifier(); |
| type = qualifier instanceof PsiExpression ? ((PsiExpression)qualifier).getType() : null; |
| referencedClass = PsiUtil.resolveClassInType(type); |
| |
| boolean isSuperCall = RefactoringChangeUtil.isSuperMethodCall(expression.getParent()); |
| if (resolved == null && isSuperCall) { |
| if (qualifier instanceof PsiReferenceExpression) { |
| resolved = ((PsiReferenceExpression)qualifier).resolve(); |
| expression = qualifier; |
| type = ((PsiReferenceExpression)qualifier).getType(); |
| referencedClass = PsiUtil.resolveClassInType(type); |
| } |
| else if (qualifier == null) { |
| resolved = PsiTreeUtil.getParentOfType(expression, PsiMethod.class, true, PsiMember.class); |
| if (resolved != null) { |
| referencedClass = ((PsiMethod)resolved).getContainingClass(); |
| } |
| } else if (qualifier instanceof PsiThisExpression) { |
| referencedClass = PsiUtil.resolveClassInType(((PsiThisExpression)qualifier).getType()); |
| } |
| } |
| if (resolved instanceof PsiField) { |
| PsiField referencedField = (PsiField)resolved; |
| if (referencedField.hasModifierProperty(PsiModifier.STATIC)) return null; |
| resolvedName = PsiFormatUtil.formatVariable(referencedField, PsiFormatUtilBase.SHOW_CONTAINING_CLASS | PsiFormatUtilBase.SHOW_NAME, PsiSubstitutor.EMPTY); |
| referencedClass = referencedField.getContainingClass(); |
| } |
| else if (resolved instanceof PsiMethod) { |
| PsiMethod method = (PsiMethod)resolved; |
| if (method.hasModifierProperty(PsiModifier.STATIC)) return null; |
| PsiElement nameElement = expression instanceof PsiThisExpression ? expression : ((PsiJavaCodeReferenceElement)expression).getReferenceNameElement(); |
| String name = nameElement == null ? null : nameElement.getText(); |
| if (isSuperCall) { |
| if (referencedClass == null) return null; |
| if (qualifier == null) { |
| PsiClass superClass = referencedClass.getSuperClass(); |
| if (superClass != null |
| && PsiUtil.isInnerClass(superClass) |
| && InheritanceUtil.isInheritorOrSelf(referencedClass, superClass.getContainingClass(), true)) { |
| // by default super() is considered this. - qualified |
| resolvedName = PsiKeyword.THIS; |
| } |
| else { |
| return null; |
| } |
| } |
| else { |
| resolvedName = qualifier.getText(); |
| } |
| } |
| else if (PsiKeyword.THIS.equals(name)) { |
| resolvedName = PsiKeyword.THIS; |
| } |
| else { |
| resolvedName = PsiFormatUtil.formatMethod(method, PsiSubstitutor.EMPTY, PsiFormatUtilBase.SHOW_CONTAINING_CLASS | |
| PsiFormatUtilBase.SHOW_NAME, 0); |
| if (referencedClass == null) referencedClass = method.getContainingClass(); |
| } |
| } |
| else if (resolved instanceof PsiClass) { |
| PsiClass aClass = (PsiClass)resolved; |
| if (aClass.hasModifierProperty(PsiModifier.STATIC)) return null; |
| referencedClass = aClass.getContainingClass(); |
| if (referencedClass == null) return null; |
| resolvedName = PsiFormatUtil.formatClass(aClass, PsiFormatUtilBase.SHOW_NAME); |
| } |
| else { |
| return null; |
| } |
| } |
| else if (expression instanceof PsiThisExpression) { |
| PsiThisExpression thisExpression = (PsiThisExpression)expression; |
| type = thisExpression.getType(); |
| referencedClass = PsiUtil.resolveClassInType(type); |
| if (thisExpression.getQualifier() != null) { |
| resolvedName = referencedClass == null |
| ? null |
| : PsiFormatUtil.formatClass(referencedClass, PsiFormatUtilBase.SHOW_NAME) + ".this"; |
| } |
| else { |
| resolvedName = "this"; |
| } |
| } |
| else { |
| return null; |
| } |
| if (referencedClass == null) return null; |
| return checkReferenceToOurInstanceInsideThisOrSuper(expression, referencedClass, resolvedName, containingFile); |
| } |
| |
| @Nullable |
| private static HighlightInfo checkReferenceToOurInstanceInsideThisOrSuper(@NotNull final PsiElement expression, |
| @NotNull PsiClass referencedClass, |
| final String resolvedName, |
| @NotNull PsiFile containingFile) { |
| if (PsiTreeUtil.getParentOfType(expression, PsiReferenceParameterList.class) != null) return null; |
| PsiElement element = expression.getParent(); |
| while (element != null) { |
| // check if expression inside super()/this() call |
| if (RefactoringChangeUtil.isSuperOrThisMethodCall(element)) { |
| PsiElement parentClass = new PsiMatcherImpl(element) |
| .parent(PsiMatchers.hasClass(PsiExpressionStatement.class)) |
| .parent(PsiMatchers.hasClass(PsiCodeBlock.class)) |
| .parent(PsiMatchers.hasClass(PsiMethod.class)) |
| .dot(JavaMatchers.isConstructor(true)) |
| .parent(PsiMatchers.hasClass(PsiClass.class)) |
| .getElement(); |
| if (parentClass == null) { |
| return null; |
| } |
| |
| // only this class/superclasses instance methods are not allowed to call |
| PsiClass aClass = (PsiClass)parentClass; |
| if (PsiUtil.isInnerClass(aClass) && referencedClass == aClass.getContainingClass()) return null; |
| // field or method should be declared in this class or super |
| if (!InheritanceUtil.isInheritorOrSelf(aClass, referencedClass, true)) return null; |
| // and point to our instance |
| if (expression instanceof PsiReferenceExpression && |
| !thisOrSuperReference(((PsiReferenceExpression)expression).getQualifierExpression(), aClass)) { |
| return null; |
| } |
| |
| if (expression instanceof PsiJavaCodeReferenceElement && |
| !aClass.equals(PsiTreeUtil.getParentOfType(expression, PsiClass.class)) && |
| PsiTreeUtil.getParentOfType(expression, PsiTypeElement.class) != null) { |
| return null; |
| } |
| |
| final HighlightInfo highlightInfo = createMemberReferencedError(resolvedName, expression.getTextRange()); |
| if (expression instanceof PsiReferenceExpression && PsiUtil.isInnerClass(aClass)) { |
| final String referenceName = ((PsiReferenceExpression)expression).getReferenceName(); |
| final PsiClass containingClass = aClass.getContainingClass(); |
| LOG.assertTrue(containingClass != null); |
| final PsiField fieldInContainingClass = containingClass.findFieldByName(referenceName, true); |
| if (fieldInContainingClass != null && ((PsiReferenceExpression)expression).getQualifierExpression() == null) { |
| QuickFixAction.registerQuickFixAction(highlightInfo, new QualifyWithThisFix(containingClass, expression)); |
| } |
| } |
| |
| return highlightInfo; |
| } |
| |
| if (element instanceof PsiReferenceExpression) { |
| final PsiElement resolve; |
| if (element instanceof PsiReferenceExpressionImpl) { |
| PsiReferenceExpressionImpl referenceExpression = (PsiReferenceExpressionImpl)element; |
| JavaResolveResult[] results = JavaResolveUtil |
| .resolveWithContainingFile(referenceExpression, PsiReferenceExpressionImpl.OurGenericsResolver.INSTANCE, true, false, containingFile); |
| resolve = results.length == 1 ? results[0].getElement() : null; |
| } |
| else { |
| resolve = ((PsiReferenceExpression)element).resolve(); |
| } |
| if (resolve instanceof PsiField && ((PsiField)resolve).hasModifierProperty(PsiModifier.STATIC)) { |
| return null; |
| } |
| } |
| |
| element = element.getParent(); |
| if (element instanceof PsiClass && InheritanceUtil.isInheritorOrSelf((PsiClass)element, referencedClass, true)) return null; |
| } |
| return null; |
| } |
| |
| private static HighlightInfo createMemberReferencedError(@NonNls final String resolvedName, @NotNull TextRange textRange) { |
| String description = JavaErrorMessages.message("member.referenced.before.constructor.called", resolvedName); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).descriptionAndTooltip(description).create(); |
| } |
| |
| @Nullable |
| public static HighlightInfo checkImplicitThisReferenceBeforeSuper(@NotNull PsiClass aClass, @NotNull JavaSdkVersion javaSdkVersion) { |
| if (javaSdkVersion.isAtLeast(JavaSdkVersion.JDK_1_7)) return null; |
| if (aClass instanceof PsiAnonymousClass || aClass instanceof PsiTypeParameter) return null; |
| PsiClass superClass = aClass.getSuperClass(); |
| if (superClass == null || !PsiUtil.isInnerClass(superClass)) return null; |
| PsiClass outerClass = superClass.getContainingClass(); |
| if (!InheritanceUtil.isInheritorOrSelf(aClass, outerClass, true)) { |
| return null; |
| } |
| // 'this' can be used as an (implicit) super() qualifier |
| PsiMethod[] constructors = aClass.getConstructors(); |
| if (constructors.length == 0) { |
| TextRange range = HighlightNamesUtil.getClassDeclarationTextRange(aClass); |
| return createMemberReferencedError(aClass.getName() + ".this", range); |
| } |
| for (PsiMethod constructor : constructors) { |
| if (!isSuperCalledInConstructor(constructor)) { |
| return createMemberReferencedError(aClass.getName() + ".this", HighlightNamesUtil.getMethodDeclarationTextRange(constructor)); |
| } |
| } |
| return null; |
| } |
| |
| private static boolean isSuperCalledInConstructor(@NotNull final PsiMethod constructor) { |
| final PsiCodeBlock body = constructor.getBody(); |
| if (body == null) return false; |
| final PsiStatement[] statements = body.getStatements(); |
| if (statements.length == 0) return false; |
| final PsiStatement statement = statements[0]; |
| final PsiElement element = new PsiMatcherImpl(statement) |
| .dot(PsiMatchers.hasClass(PsiExpressionStatement.class)) |
| .firstChild(PsiMatchers.hasClass(PsiMethodCallExpression.class)) |
| .firstChild(PsiMatchers.hasClass(PsiReferenceExpression.class)) |
| .firstChild(PsiMatchers.hasClass(PsiKeyword.class)) |
| .dot(PsiMatchers.hasText(PsiKeyword.SUPER)) |
| .getElement(); |
| return element != null; |
| } |
| |
| private static boolean thisOrSuperReference(@Nullable PsiExpression qualifierExpression, PsiClass aClass) { |
| if (qualifierExpression == null) return true; |
| PsiJavaCodeReferenceElement qualifier; |
| if (qualifierExpression instanceof PsiThisExpression) { |
| qualifier = ((PsiThisExpression)qualifierExpression).getQualifier(); |
| } |
| else if (qualifierExpression instanceof PsiSuperExpression) { |
| qualifier = ((PsiSuperExpression)qualifierExpression).getQualifier(); |
| } |
| else { |
| return false; |
| } |
| if (qualifier == null) return true; |
| PsiElement resolved = qualifier.resolve(); |
| return resolved instanceof PsiClass && InheritanceUtil.isInheritorOrSelf(aClass, (PsiClass)resolved, true); |
| } |
| |
| |
| @Nullable |
| public static HighlightInfo checkLabelWithoutStatement(@NotNull PsiLabeledStatement statement) { |
| if (statement.getStatement() == null) { |
| String description = JavaErrorMessages.message("label.without.statement"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); |
| } |
| return null; |
| } |
| |
| |
| @Nullable |
| public static HighlightInfo checkLabelAlreadyInUse(@NotNull PsiLabeledStatement statement) { |
| PsiIdentifier identifier = statement.getLabelIdentifier(); |
| String text = identifier.getText(); |
| PsiElement element = statement; |
| while (element != null) { |
| if (element instanceof PsiMethod || element instanceof PsiClass) break; |
| if (element instanceof PsiLabeledStatement && element != statement && |
| Comparing.equal(((PsiLabeledStatement)element).getLabelIdentifier().getText(), text)) { |
| String description = JavaErrorMessages.message("duplicate.label", text); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(identifier).descriptionAndTooltip(description).create(); |
| } |
| element = element.getParent(); |
| } |
| return null; |
| } |
| |
| |
| @Nullable |
| public static HighlightInfo checkUnclosedComment(@NotNull PsiComment comment) { |
| if (!(comment instanceof PsiDocComment) && !(comment.getTokenType() == JavaTokenType.C_STYLE_COMMENT)) return null; |
| if (!comment.getText().endsWith("*/")) { |
| int start = comment.getTextRange().getEndOffset() - 1; |
| int end = start + 1; |
| String description = JavaErrorMessages.message("unclosed.comment"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(start, end).descriptionAndTooltip(description).create(); |
| } |
| return null; |
| } |
| |
| |
| @Nullable |
| static Collection<HighlightInfo> checkCatchTypeIsDisjoint(@NotNull final PsiParameter parameter) { |
| if (!(parameter.getType() instanceof PsiDisjunctionType)) return null; |
| |
| final Collection<HighlightInfo> result = ContainerUtil.newArrayList(); |
| final List<PsiTypeElement> typeElements = PsiUtil.getParameterTypeElements(parameter); |
| for (int i = 0, size = typeElements.size(); i < size; i++) { |
| final PsiClass class1 = PsiUtil.resolveClassInClassTypeOnly(typeElements.get(i).getType()); |
| if (class1 == null) continue; |
| for (int j = i + 1; j < size; j++) { |
| final PsiClass class2 = PsiUtil.resolveClassInClassTypeOnly(typeElements.get(j).getType()); |
| if (class2 == null) continue; |
| final boolean sub = InheritanceUtil.isInheritorOrSelf(class1, class2, true); |
| final boolean sup = InheritanceUtil.isInheritorOrSelf(class2, class1, true); |
| if (sub || sup) { |
| final String name1 = PsiFormatUtil.formatClass(class1, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME); |
| final String name2 = PsiFormatUtil.formatClass(class2, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME); |
| final String message = JavaErrorMessages.message("exception.must.be.disjoint", sub ? name1 : name2, sub ? name2 : name1); |
| PsiElement element = typeElements.get(sub ? i : j); |
| result.add(HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(message).create()); |
| break; |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| |
| @Nullable |
| static Collection<HighlightInfo> checkExceptionAlreadyCaught(@NotNull final PsiParameter parameter) { |
| final PsiElement scope = parameter.getDeclarationScope(); |
| if (!(scope instanceof PsiCatchSection)) return null; |
| |
| final PsiCatchSection catchSection = (PsiCatchSection)scope; |
| final PsiCatchSection[] allCatchSections = catchSection.getTryStatement().getCatchSections(); |
| final int startFrom = ArrayUtilRt.find(allCatchSections, catchSection) - 1; |
| if (startFrom < 0) return null; |
| |
| final List<PsiTypeElement> typeElements = PsiUtil.getParameterTypeElements(parameter); |
| final boolean isInMultiCatch = typeElements.size() > 1; |
| final Collection<HighlightInfo> result = ContainerUtil.newArrayList(); |
| |
| for (PsiTypeElement typeElement : typeElements) { |
| final PsiClass catchClass = PsiUtil.resolveClassInClassTypeOnly(typeElement.getType()); |
| if (catchClass == null) continue; |
| |
| for (int i = startFrom; i >= 0; i--) { |
| final PsiCatchSection upperCatchSection = allCatchSections[i]; |
| final PsiType upperCatchType = upperCatchSection.getCatchType(); |
| |
| final boolean highlight = upperCatchType instanceof PsiDisjunctionType |
| ? checkMultipleTypes(catchClass, ((PsiDisjunctionType)upperCatchType).getDisjunctions()) |
| : checkSingleType(catchClass, upperCatchType); |
| if (highlight) { |
| final String className = PsiFormatUtil.formatClass(catchClass, PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_FQ_NAME); |
| final String description = JavaErrorMessages.message("exception.already.caught", className); |
| final HighlightInfo highlightInfo = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(typeElement).descriptionAndTooltip(description).create(); |
| result.add(highlightInfo); |
| |
| QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createMoveCatchUpFix(catchSection, upperCatchSection)); |
| if (isInMultiCatch) { |
| QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createDeleteMultiCatchFix(typeElement)); |
| } |
| else { |
| QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createDeleteCatchFix(parameter)); |
| } |
| } |
| } |
| } |
| |
| return result.isEmpty() ? null : result; |
| } |
| |
| private static boolean checkMultipleTypes(final PsiClass catchClass, @NotNull final List<PsiType> upperCatchTypes) { |
| for (int i = upperCatchTypes.size() - 1; i >= 0; i--) { |
| if (checkSingleType(catchClass, upperCatchTypes.get(i))) return true; |
| } |
| return false; |
| } |
| |
| private static boolean checkSingleType(final PsiClass catchClass, final PsiType upperCatchType) { |
| final PsiClass upperCatchClass = PsiUtil.resolveClassInType(upperCatchType); |
| return upperCatchClass != null && InheritanceUtil.isInheritorOrSelf(catchClass, upperCatchClass, true); |
| } |
| |
| |
| @Nullable |
| public static HighlightInfo checkTernaryOperatorConditionIsBoolean(@NotNull PsiExpression expression, PsiType type) { |
| if (expression.getParent() instanceof PsiConditionalExpression && |
| ((PsiConditionalExpression)expression.getParent()).getCondition() == expression && !TypeConversionUtil.isBooleanType(type)) { |
| return createIncompatibleTypeHighlightInfo(PsiType.BOOLEAN, type, expression.getTextRange(), 0); |
| } |
| return null; |
| } |
| |
| |
| @Nullable |
| public static HighlightInfo checkStatementPrependedWithCaseInsideSwitch(@NotNull PsiStatement statement) { |
| if (!(statement instanceof PsiSwitchLabelStatement) && statement.getParent() instanceof PsiCodeBlock && |
| statement.getParent().getParent() instanceof PsiSwitchStatement && |
| ((PsiCodeBlock)statement.getParent()).getStatements().length != 0 && |
| statement == ((PsiCodeBlock)statement.getParent()).getStatements()[0]) { |
| String description = JavaErrorMessages.message("statement.must.be.prepended.with.case.label"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); |
| } |
| return null; |
| } |
| |
| |
| @Nullable |
| public static HighlightInfo checkAssertOperatorTypes(@NotNull PsiExpression expression, @Nullable PsiType type) { |
| if (type == null) return null; |
| if (!(expression.getParent() instanceof PsiAssertStatement)) { |
| return null; |
| } |
| PsiAssertStatement assertStatement = (PsiAssertStatement)expression.getParent(); |
| if (expression == assertStatement.getAssertCondition() && !TypeConversionUtil.isBooleanType(type)) { |
| // addTypeCast quickfix is not applicable here since no type can be cast to boolean |
| return createIncompatibleTypeHighlightInfo(PsiType.BOOLEAN, type, expression.getTextRange(), 0); |
| } |
| if (expression == assertStatement.getAssertDescription() && TypeConversionUtil.isVoidType(type)) { |
| String description = JavaErrorMessages.message("void.type.is.not.allowed"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(expression).descriptionAndTooltip(description).create(); |
| } |
| return null; |
| } |
| |
| |
| @Nullable |
| public static HighlightInfo checkSynchronizedExpressionType(@NotNull PsiExpression expression, @Nullable PsiType type,@NotNull PsiFile containingFile) { |
| if (type == null) return null; |
| if (expression.getParent() instanceof PsiSynchronizedStatement) { |
| PsiSynchronizedStatement synchronizedStatement = (PsiSynchronizedStatement)expression.getParent(); |
| if (expression == synchronizedStatement.getLockExpression() && |
| (type instanceof PsiPrimitiveType || TypeConversionUtil.isNullType(type))) { |
| PsiClassType objectType = PsiType.getJavaLangObject(containingFile.getManager(), expression.getResolveScope()); |
| return createIncompatibleTypeHighlightInfo(objectType, type, expression.getTextRange(), 0); |
| } |
| } |
| return null; |
| } |
| |
| |
| @Nullable |
| public static HighlightInfo checkConditionalExpressionBranchTypesMatch(@NotNull final PsiExpression expression, PsiType type) { |
| PsiElement parent = expression.getParent(); |
| if (!(parent instanceof PsiConditionalExpression)) { |
| return null; |
| } |
| PsiConditionalExpression conditionalExpression = (PsiConditionalExpression)parent; |
| // check else branches only |
| if (conditionalExpression.getElseExpression() != expression) return null; |
| final PsiExpression thenExpression = conditionalExpression.getThenExpression(); |
| assert thenExpression != null; |
| PsiType thenType = thenExpression.getType(); |
| PsiType elseType = type; |
| if (thenType == null || elseType == null) return null; |
| if (conditionalExpression.getType() == null) { |
| // cannot derive type of conditional expression |
| // elseType will never be castable to thenType, so no quick fix here |
| return createIncompatibleTypeHighlightInfo(thenType, elseType, expression.getTextRange(), 0); |
| } |
| return null; |
| } |
| |
| private static HighlightInfo createIncompatibleTypeHighlightInfo(final PsiType lType, |
| final PsiType rType, |
| @NotNull final TextRange textRange, |
| int navigationShift) { |
| PsiType lType1 = lType; |
| PsiType rType1 = rType; |
| PsiTypeParameter[] lTypeParams = PsiTypeParameter.EMPTY_ARRAY; |
| PsiSubstitutor lTypeSubstitutor = PsiSubstitutor.EMPTY; |
| if (lType1 instanceof PsiClassType) { |
| PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)lType1).resolveGenerics(); |
| lTypeSubstitutor = resolveResult.getSubstitutor(); |
| PsiClass psiClass = resolveResult.getElement(); |
| if (psiClass instanceof PsiAnonymousClass) { |
| lType1 = ((PsiAnonymousClass)psiClass).getBaseClassType(); |
| resolveResult = ((PsiClassType)lType1).resolveGenerics(); |
| lTypeSubstitutor = resolveResult.getSubstitutor(); |
| psiClass = resolveResult.getElement(); |
| } |
| lTypeParams = psiClass == null ? PsiTypeParameter.EMPTY_ARRAY : psiClass.getTypeParameters(); |
| } |
| PsiTypeParameter[] rTypeParams = PsiTypeParameter.EMPTY_ARRAY; |
| PsiSubstitutor rTypeSubstitutor = PsiSubstitutor.EMPTY; |
| if (rType1 instanceof PsiClassType) { |
| PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)rType1).resolveGenerics(); |
| rTypeSubstitutor = resolveResult.getSubstitutor(); |
| PsiClass psiClass = resolveResult.getElement(); |
| if (psiClass instanceof PsiAnonymousClass) { |
| rType1 = ((PsiAnonymousClass)psiClass).getBaseClassType(); |
| resolveResult = ((PsiClassType)rType1).resolveGenerics(); |
| rTypeSubstitutor = resolveResult.getSubstitutor(); |
| psiClass = resolveResult.getElement(); |
| } |
| rTypeParams = psiClass == null ? PsiTypeParameter.EMPTY_ARRAY : psiClass.getTypeParameters(); |
| } |
| |
| int typeParamColumns = Math.max(lTypeParams.length, rTypeParams.length); |
| @Language("HTML") @NonNls String requiredRow = ""; |
| @Language("HTML") @NonNls String foundRow = ""; |
| for (int i = 0; i < typeParamColumns; i++) { |
| PsiTypeParameter lTypeParameter = i >= lTypeParams.length ? null : lTypeParams[i]; |
| PsiTypeParameter rTypeParameter = i >= rTypeParams.length ? null : rTypeParams[i]; |
| PsiType lSubstitutedType = lTypeParameter == null ? null : lTypeSubstitutor.substitute(lTypeParameter); |
| PsiType rSubstitutedType = rTypeParameter == null ? null : rTypeSubstitutor.substitute(rTypeParameter); |
| boolean matches = Comparing.equal(lSubstitutedType, rSubstitutedType); |
| @NonNls String openBrace = i == 0 ? "<" : ""; |
| @NonNls String closeBrace = i == typeParamColumns - 1 ? ">" : ","; |
| requiredRow += "<td>" + (lTypeParams.length == 0 ? "" : openBrace) + redIfNotMatch(lSubstitutedType, matches) + |
| (i < lTypeParams.length ? closeBrace : "") + "</td>"; |
| foundRow += "<td>" + (rTypeParams.length == 0 ? "" : openBrace) + redIfNotMatch(rSubstitutedType, matches) + |
| (i < rTypeParams.length ? closeBrace : "") + "</td>"; |
| } |
| PsiType lRawType = lType1 instanceof PsiClassType ? ((PsiClassType)lType1).rawType() : lType1; |
| PsiType rRawType = rType1 instanceof PsiClassType ? ((PsiClassType)rType1).rawType() : rType1; |
| boolean assignable = lRawType == null || rRawType == null || TypeConversionUtil.isAssignable(lRawType, rRawType); |
| |
| String toolTip = JavaErrorMessages.message("incompatible.types.html.tooltip", |
| redIfNotMatch(lRawType, assignable), requiredRow, |
| redIfNotMatch(rRawType, assignable), foundRow); |
| |
| String description = JavaErrorMessages.message("incompatible.types", JavaHighlightUtil.formatType(lType1), JavaHighlightUtil |
| .formatType(rType1)); |
| |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(textRange).description(description).escapedToolTip(toolTip).navigationShift(navigationShift).create(); |
| } |
| |
| @Nullable |
| public static HighlightInfo checkSingleImportClassConflict(@NotNull PsiImportStatement statement, |
| @NotNull Map<String, Pair<PsiImportStaticReferenceElement, PsiClass>> importedClasses,@NotNull PsiFile containingFile) { |
| if (statement.isOnDemand()) return null; |
| PsiElement element = statement.resolve(); |
| if (element instanceof PsiClass) { |
| String name = ((PsiClass)element).getName(); |
| Pair<PsiImportStaticReferenceElement, PsiClass> imported = importedClasses.get(name); |
| PsiClass importedClass = imported == null ? null : imported.getSecond(); |
| if (importedClass != null && !containingFile.getManager().areElementsEquivalent(importedClass, element)) { |
| String description = JavaErrorMessages.message("single.import.class.conflict", formatClass(importedClass)); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(statement).descriptionAndTooltip(description).create(); |
| } |
| importedClasses.put(name, Pair.create((PsiImportStaticReferenceElement)null, (PsiClass)element)); |
| } |
| return null; |
| } |
| |
| |
| @NonNls |
| private static String redIfNotMatch(PsiType type, boolean matches) { |
| if (matches) return getFQName(type, false); |
| String color = UIUtil.isUnderDarcula() ? "FF6B68" : "red"; |
| return "<font color='" + color +"'><b>" + getFQName(type, true) + "</b></font>"; |
| } |
| |
| private static String getFQName(@Nullable PsiType type, boolean longName) { |
| if (type == null) return ""; |
| return XmlStringUtil.escapeString(longName ? type.getInternalCanonicalText() : type.getPresentableText()); |
| } |
| |
| |
| @Nullable |
| static HighlightInfo checkMustBeThrowable(@Nullable PsiType type, @NotNull PsiElement context, boolean addCastIntention) { |
| if (type == null) return null; |
| PsiElementFactory factory = JavaPsiFacade.getInstance(context.getProject()).getElementFactory(); |
| PsiClassType throwable = factory.createTypeByFQClassName("java.lang.Throwable", context.getResolveScope()); |
| if (!TypeConversionUtil.isAssignable(throwable, type)) { |
| HighlightInfo highlightInfo = createIncompatibleTypeHighlightInfo(throwable, type, context.getTextRange(), 0); |
| if (addCastIntention && TypeConversionUtil.areTypesConvertible(type, throwable)) { |
| if (context instanceof PsiExpression) { |
| QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createAddTypeCastFix(throwable, (PsiExpression)context)); |
| } |
| } |
| return highlightInfo; |
| } |
| return null; |
| } |
| |
| |
| @Nullable |
| private static HighlightInfo checkMustBeThrowable(@Nullable PsiClass aClass, @NotNull PsiElement context) { |
| if (aClass == null) return null; |
| PsiClassType type = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createType(aClass); |
| return checkMustBeThrowable(type, context, false); |
| } |
| |
| |
| @Nullable |
| static HighlightInfo checkLabelDefined(@Nullable PsiIdentifier labelIdentifier, @Nullable PsiStatement exitedStatement) { |
| if (labelIdentifier == null) return null; |
| String label = labelIdentifier.getText(); |
| if (label == null) return null; |
| if (exitedStatement == null) { |
| String message = JavaErrorMessages.message("unresolved.label", label); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(labelIdentifier).descriptionAndTooltip(message).create(); |
| } |
| return null; |
| } |
| |
| |
| @Nullable |
| static HighlightInfo checkReference(@NotNull final PsiJavaCodeReferenceElement ref, |
| @NotNull final JavaResolveResult result, |
| @NotNull PsiFile containingFile, |
| @NotNull LanguageLevel languageLevel) { |
| final PsiElement refName = ref.getReferenceNameElement(); |
| |
| if (!(refName instanceof PsiIdentifier) && !(refName instanceof PsiKeyword)) return null; |
| final PsiElement resolved = result.getElement(); |
| |
| HighlightInfo highlightInfo = checkMemberReferencedBeforeConstructorCalled(ref, resolved, containingFile); |
| if (highlightInfo != null) return highlightInfo; |
| |
| PsiElement refParent = ref.getParent(); |
| PsiElement granny; |
| if (refParent instanceof PsiReferenceExpression && (granny = refParent.getParent()) instanceof PsiMethodCallExpression) { |
| PsiReferenceExpression referenceToMethod = ((PsiMethodCallExpression)granny).getMethodExpression(); |
| PsiExpression qualifierExpression = referenceToMethod.getQualifierExpression(); |
| if (qualifierExpression == ref) { |
| @SuppressWarnings("UnnecessaryLocalVariable") PsiElement qualifier = resolved; |
| if (qualifier != null && !(qualifier instanceof PsiClass) && !(qualifier instanceof PsiVariable)) { |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(qualifierExpression).descriptionAndTooltip( |
| "Qualifier must be an expression").create(); |
| } |
| } |
| } |
| else if (refParent instanceof PsiMethodCallExpression) { |
| return null; // methods checked elsewhere |
| } |
| if (resolved == null) { |
| // do not highlight unknown packages - javac does not care about illegal package names |
| if (isInsidePackageStatement(refName)) return null; |
| if (result.isPackagePrefixPackageReference()) return null; |
| JavaResolveResult[] results = ref.multiResolve(true); |
| String description; |
| if (results.length > 1) { |
| String t1 = format(results[0].getElement()); |
| String t2 = format(results[1].getElement()); |
| description = JavaErrorMessages.message("ambiguous.reference", refName.getText(), t1, t2); |
| } |
| else { |
| description = JavaErrorMessages.message("cannot.resolve.symbol", refName.getText()); |
| } |
| |
| HighlightInfoType type = HighlightInfoType.WRONG_REF; |
| if (PsiUtil.isInsideJavadocComment(ref)) return null; |
| |
| HighlightInfo info = HighlightInfo.newHighlightInfo(type).range(refName).descriptionAndTooltip(description).create(); |
| |
| UnresolvedReferenceQuickFixProvider.registerReferenceFixes(ref, new QuickFixActionRegistrarImpl(info)); |
| return info; |
| } |
| |
| if (!result.isValidResult() && !PsiUtil.isInsideJavadocComment(ref)) { |
| if (!result.isAccessible()) { |
| String description = buildProblemWithAccessDescription(ref, result, resolved); |
| HighlightInfo info = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(refName).descriptionAndTooltip(description).create(); |
| if (result.isStaticsScopeCorrect()) { |
| registerAccessQuickFixAction((PsiMember)resolved, ref, info, result.getCurrentFileResolveScope()); |
| if (ref instanceof PsiReferenceExpression) { |
| QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createRenameWrongRefFix((PsiReferenceExpression)ref)); |
| } |
| } |
| UnresolvedReferenceQuickFixProvider.registerReferenceFixes(ref, new QuickFixActionRegistrarImpl(info)); |
| return info; |
| } |
| |
| if (!result.isStaticsScopeCorrect()) { |
| String description = buildProblemWithStaticDescription(resolved); |
| HighlightInfo info = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(refName).descriptionAndTooltip(description).create(); |
| registerStaticProblemQuickFixAction(resolved, info, ref); |
| if (ref instanceof PsiReferenceExpression) { |
| QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createRenameWrongRefFix((PsiReferenceExpression)ref)); |
| } |
| return info; |
| } |
| } |
| if ((resolved instanceof PsiLocalVariable || resolved instanceof PsiParameter) && !(resolved instanceof ImplicitVariable)) { |
| highlightInfo = HighlightControlFlowUtil.checkVariableMustBeFinal((PsiVariable)resolved, ref, |
| languageLevel); |
| } |
| else if (resolved instanceof PsiClass) { |
| if (Comparing.strEqual(((PsiClass)resolved).getQualifiedName(), ((PsiClass)resolved).getName())) { |
| final PsiElement parent = ref.getParent(); |
| if (parent instanceof PsiImportStaticReferenceElement || parent instanceof PsiImportStatementBase) { |
| String description = JavaErrorMessages.message("cannot.resolve.symbol", refName.getText()); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.WRONG_REF).range(refName).descriptionAndTooltip(description).create(); |
| } |
| } |
| } |
| return highlightInfo; |
| } |
| |
| @NotNull |
| private static String format(@NotNull PsiElement element) { |
| if (element instanceof PsiClass) return formatClass((PsiClass)element); |
| if (element instanceof PsiMethod) return JavaHighlightUtil.formatMethod((PsiMethod)element); |
| if (element instanceof PsiField) return formatField((PsiField)element); |
| return ElementDescriptionUtil.getElementDescription(element, HighlightUsagesDescriptionLocation.INSTANCE); |
| } |
| |
| private static boolean isInsidePackageStatement(@Nullable PsiElement element) { |
| while (element != null) { |
| if (element instanceof PsiPackageStatement) return true; |
| if (!(element instanceof PsiIdentifier) && !(element instanceof PsiJavaCodeReferenceElement)) return false; |
| element = element.getParent(); |
| } |
| return false; |
| } |
| |
| @Nullable |
| static HighlightInfo checkPackageAndClassConflict(@NotNull PsiJavaCodeReferenceElement ref) { |
| if (ref.isQualified() && isInsidePackageStatement(ref)) { |
| VirtualFile file = ref.getContainingFile().getVirtualFile(); |
| if (file != null) { |
| Module module = ProjectFileIndex.SERVICE.getInstance(ref.getProject()).getModuleForFile(file); |
| if (module != null) { |
| GlobalSearchScope scope = module.getModuleWithDependenciesAndLibrariesScope(false); |
| PsiClass aClass = JavaPsiFacade.getInstance(ref.getProject()).findClass(ref.getCanonicalText(), scope); |
| if (aClass != null) { |
| String message = JavaErrorMessages.message("package.clashes.with.class", ref.getText()); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(ref).descriptionAndTooltip(message).create(); |
| } |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| static HighlightInfo checkElementInReferenceList(@NotNull PsiJavaCodeReferenceElement ref, |
| @NotNull PsiReferenceList referenceList, |
| @NotNull JavaResolveResult resolveResult, |
| @NotNull LanguageLevel languageLevel) { |
| PsiElement resolved = resolveResult.getElement(); |
| HighlightInfo highlightInfo = null; |
| PsiElement refGrandParent = referenceList.getParent(); |
| if (resolved instanceof PsiClass) { |
| PsiClass aClass = (PsiClass)resolved; |
| if (refGrandParent instanceof PsiClass) { |
| if (refGrandParent instanceof PsiTypeParameter) { |
| highlightInfo = GenericsHighlightUtil.checkElementInTypeParameterExtendsList(referenceList, (PsiClass)refGrandParent, resolveResult, ref, languageLevel); |
| } |
| else { |
| highlightInfo = HighlightClassUtil.checkExtendsClassAndImplementsInterface(referenceList, resolveResult, ref); |
| if (highlightInfo == null) { |
| highlightInfo = HighlightClassUtil.checkCannotInheritFromFinal(aClass, ref); |
| } |
| if (highlightInfo == null) { |
| highlightInfo = GenericsHighlightUtil.checkCannotInheritFromEnum(aClass, ref); |
| } |
| if (highlightInfo == null) { |
| highlightInfo = GenericsHighlightUtil.checkCannotInheritFromTypeParameter(aClass, ref); |
| } |
| } |
| } |
| else if (refGrandParent instanceof PsiMethod && ((PsiMethod)refGrandParent).getThrowsList() == referenceList) { |
| highlightInfo = checkMustBeThrowable(aClass, ref); |
| } |
| } |
| else if (refGrandParent instanceof PsiMethod && referenceList == ((PsiMethod)refGrandParent).getThrowsList()) { |
| String description = JavaErrorMessages.message("class.name.expected"); |
| highlightInfo = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(ref).descriptionAndTooltip(description).create(); |
| } |
| return highlightInfo; |
| } |
| |
| |
| public static boolean isSerializationImplicitlyUsedField(@NotNull PsiField field) { |
| final String name = field.getName(); |
| if (!SERIAL_VERSION_UID_FIELD_NAME.equals(name) && !SERIAL_PERSISTENT_FIELDS_FIELD_NAME.equals(name)) return false; |
| if (!field.hasModifierProperty(PsiModifier.STATIC)) return false; |
| PsiClass aClass = field.getContainingClass(); |
| return aClass == null || JavaHighlightUtil.isSerializable(aClass); |
| } |
| |
| @Nullable |
| public static HighlightInfo checkClassReferenceAfterQualifier(@NotNull final PsiReferenceExpression expression, final PsiElement resolved) { |
| if (!(resolved instanceof PsiClass)) return null; |
| final PsiExpression qualifier = expression.getQualifierExpression(); |
| if (qualifier == null) return null; |
| if (qualifier instanceof PsiReferenceExpression) { |
| PsiElement qualifierResolved = ((PsiReferenceExpression)qualifier).resolve(); |
| if (qualifierResolved instanceof PsiClass || qualifierResolved instanceof PsiPackage) return null; |
| } |
| String description = JavaErrorMessages.message("expected.class.or.package"); |
| HighlightInfo info = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(qualifier).descriptionAndTooltip(description).create(); |
| QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createRemoveQualifierFix(qualifier, expression, (PsiClass)resolved)); |
| return info; |
| } |
| |
| public static void registerChangeVariableTypeFixes(@NotNull PsiVariable parameter, PsiType itemType, @NotNull HighlightInfo highlightInfo) { |
| for (IntentionAction action : getChangeVariableTypeFixes(parameter, itemType)) { |
| QuickFixAction.registerQuickFixAction(highlightInfo, action); |
| } |
| } |
| |
| @NotNull |
| public static List<IntentionAction> getChangeVariableTypeFixes(@NotNull PsiVariable parameter, PsiType itemType) { |
| if (itemType instanceof PsiMethodReferenceType) return Collections.emptyList(); |
| List<IntentionAction> result = new ArrayList<IntentionAction>(); |
| if (itemType != null) { |
| for (ChangeVariableTypeQuickFixProvider fixProvider : Extensions.getExtensions(ChangeVariableTypeQuickFixProvider.EP_NAME)) { |
| Collections.addAll(result, fixProvider.getFixes(parameter, itemType)); |
| } |
| } |
| IntentionAction changeFix = getChangeParameterClassFix(parameter.getType(), itemType); |
| if (changeFix != null) result.add(changeFix); |
| return result; |
| } |
| |
| @Nullable |
| static HighlightInfo checkAnnotationMethodParameters(@NotNull PsiParameterList list) { |
| final PsiElement parent = list.getParent(); |
| if (PsiUtil.isAnnotationMethod(parent) && list.getParametersCount() > 0) { |
| final String message = JavaErrorMessages.message("annotation.interface.members.may.not.have.parameters"); |
| final HighlightInfo highlightInfo = |
| HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(list).descriptionAndTooltip(message).create(); |
| QuickFixAction.registerQuickFixAction(highlightInfo, QUICK_FIX_FACTORY.createRemoveParameterListFix((PsiMethod)parent)); |
| return highlightInfo; |
| } |
| return null; |
| } |
| |
| @Nullable |
| static HighlightInfo checkForStatement(@NotNull PsiForStatement statement) { |
| PsiStatement init = statement.getInitialization(); |
| if (!(init == null || init instanceof PsiEmptyStatement || |
| init instanceof PsiDeclarationStatement || |
| init instanceof PsiExpressionStatement || init instanceof PsiExpressionListStatement)) { |
| String message = JavaErrorMessages.message("invalid.statement"); |
| return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(init).descriptionAndTooltip(message).create(); |
| } |
| |
| return null; |
| } |
| |
| private static void registerChangeParameterClassFix(PsiType lType, PsiType rType, HighlightInfo info) { |
| QuickFixAction.registerQuickFixAction(info, getChangeParameterClassFix(lType, rType)); |
| } |
| @Nullable |
| private static IntentionAction getChangeParameterClassFix(PsiType lType, PsiType rType) { |
| final PsiClass lClass = PsiUtil.resolveClassInClassTypeOnly(lType); |
| final PsiClass rClass = PsiUtil.resolveClassInClassTypeOnly(rType); |
| |
| if (rClass == null || lClass == null) return null; |
| if (rClass instanceof PsiAnonymousClass) return null; |
| if (rClass.isInheritor(lClass, true)) return null; |
| if (lClass.isInheritor(rClass, true)) return null; |
| if (lClass == rClass) return null; |
| |
| return QUICK_FIX_FACTORY.createChangeParameterClassFix(rClass, (PsiClassType)lType); |
| } |
| |
| private static void registerReplaceInaccessibleFieldWithGetterSetterFix(PsiMember refElement, |
| PsiJavaCodeReferenceElement place, |
| PsiClass accessObjectClass, |
| HighlightInfo error) { |
| if (refElement instanceof PsiField && place instanceof PsiReferenceExpression) { |
| final PsiField psiField = (PsiField)refElement; |
| final PsiClass containingClass = psiField.getContainingClass(); |
| if (containingClass != null) { |
| if (PsiUtil.isOnAssignmentLeftHand((PsiExpression)place)) { |
| final PsiMethod setterPrototype = PropertyUtil.generateSetterPrototype(psiField); |
| final PsiMethod setter = containingClass.findMethodBySignature(setterPrototype, true); |
| if (setter != null && PsiUtil.isAccessible(setter, place, accessObjectClass)) { |
| QuickFixAction.registerQuickFixAction(error, QUICK_FIX_FACTORY.createReplaceInaccessibleFieldWithGetterSetterFix(place, setter, true)); |
| } |
| } |
| else if (PsiUtil.isAccessedForReading((PsiExpression)place)) { |
| final PsiMethod getterPrototype = PropertyUtil.generateGetterPrototype(psiField); |
| final PsiMethod getter = containingClass.findMethodBySignature(getterPrototype, true); |
| if (getter != null && PsiUtil.isAccessible(getter, place, accessObjectClass)) { |
| QuickFixAction.registerQuickFixAction(error, QUICK_FIX_FACTORY.createReplaceInaccessibleFieldWithGetterSetterFix(place, getter, false)); |
| } |
| } |
| } |
| } |
| } |
| |
| private enum Feature { |
| GENERICS(LanguageLevel.JDK_1_5, "feature.generics"), |
| ANNOTATIONS(LanguageLevel.JDK_1_5, "feature.annotations"), |
| STATIC_IMPORTS(LanguageLevel.JDK_1_5, "feature.static.imports"), |
| FOR_EACH(LanguageLevel.JDK_1_5, "feature.for.each"), |
| VARARGS(LanguageLevel.JDK_1_5, "feature.varargs"), |
| HEX_FP_LITERALS(LanguageLevel.JDK_1_5, "feature.hex.fp.literals"), |
| DIAMOND_TYPES(LanguageLevel.JDK_1_7, "feature.diamond.types"), |
| MULTI_CATCH(LanguageLevel.JDK_1_7, "feature.multi.catch"), |
| TRY_WITH_RESOURCES(LanguageLevel.JDK_1_7, "feature.try.with.resources"), |
| BIN_LITERALS(LanguageLevel.JDK_1_7, "feature.binary.literals"), |
| UNDERSCORES(LanguageLevel.JDK_1_7, "feature.underscores.in.literals"), |
| EXTENSION_METHODS(LanguageLevel.JDK_1_8, "feature.extension.methods"), |
| METHOD_REFERENCES(LanguageLevel.JDK_1_8, "feature.method.references"), |
| LAMBDA_EXPRESSIONS(LanguageLevel.JDK_1_8, "feature.lambda.expressions"), |
| TYPE_ANNOTATIONS(LanguageLevel.JDK_1_8, "feature.type.annotations"); |
| |
| @NotNull |
| private final LanguageLevel level; |
| @NotNull |
| private final String key; |
| |
| Feature(@NotNull LanguageLevel level, @NotNull @PropertyKey(resourceBundle = JavaErrorMessages.BUNDLE) final String key) { |
| this.level = level; |
| this.key = key; |
| } |
| } |
| |
| @Nullable |
| private static HighlightInfo checkFeature(@NotNull final PsiElement element, |
| @NotNull Feature feature, |
| @NotNull LanguageLevel languageLevel, |
| @NotNull PsiFile containingFile) { |
| if (containingFile.getManager().isInProject(containingFile) && !languageLevel.isAtLeast(feature.level)) { |
| String message = JavaErrorMessages.message("insufficient.language.level", JavaErrorMessages.message(feature.key)); |
| HighlightInfo info = HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(element).descriptionAndTooltip(message).create(); |
| QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createIncreaseLanguageLevelFix(feature.level)); |
| QuickFixAction.registerQuickFixAction(info, QUICK_FIX_FACTORY.createShowModulePropertiesFix(element)); |
| return info; |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkGenericsFeature(@NotNull PsiElement parameterList, |
| int listSize, |
| @NotNull LanguageLevel languageLevel, |
| @NotNull PsiFile containingFile) { |
| return listSize > 0 ? checkFeature(parameterList, Feature.GENERICS, languageLevel, containingFile) : null; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkAnnotationFeature(@NotNull PsiElement element, |
| @NotNull LanguageLevel languageLevel, |
| @NotNull PsiFile containingFile) { |
| return checkFeature(element, Feature.ANNOTATIONS, languageLevel, containingFile); |
| } |
| |
| @Nullable |
| public static HighlightInfo checkForEachFeature(@NotNull PsiForeachStatement statement, @NotNull LanguageLevel languageLevel,@NotNull PsiFile containingFile) { |
| return checkFeature(statement, Feature.FOR_EACH, languageLevel, containingFile); |
| } |
| |
| @Nullable |
| public static HighlightInfo checkStaticImportFeature(@NotNull PsiImportStaticStatement statement, @NotNull LanguageLevel languageLevel,@NotNull PsiFile containingFile) { |
| return checkFeature(statement, Feature.STATIC_IMPORTS, languageLevel, containingFile); |
| } |
| |
| @Nullable |
| public static HighlightInfo checkVarargFeature(@NotNull PsiParameter parameter, @NotNull LanguageLevel languageLevel,@NotNull PsiFile containingFile) { |
| return checkFeature(parameter, Feature.VARARGS, languageLevel, containingFile); |
| } |
| |
| @Nullable |
| public static HighlightInfo checkDiamondFeature(@NotNull PsiTypeElement typeElement, @NotNull LanguageLevel languageLevel,@NotNull PsiFile containingFile) { |
| return typeElement.getType() instanceof PsiDiamondType ? checkFeature(typeElement.getParent(), Feature.DIAMOND_TYPES, |
| languageLevel, containingFile) : null; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkMultiCatchFeature(@NotNull PsiParameter parameter, @NotNull LanguageLevel languageLevel,@NotNull PsiFile containingFile) { |
| return parameter.getType() instanceof PsiDisjunctionType ? checkFeature(parameter, Feature.MULTI_CATCH, |
| languageLevel, containingFile) : null; |
| } |
| |
| @Nullable |
| public static HighlightInfo checkTryWithResourcesFeature(@NotNull PsiResourceVariable resourceVariable, @NotNull LanguageLevel languageLevel,@NotNull PsiFile containingFile) { |
| return checkFeature(resourceVariable.getParent(), Feature.TRY_WITH_RESOURCES, languageLevel, containingFile); |
| } |
| |
| @Nullable |
| public static HighlightInfo checkExtensionMethodsFeature(@NotNull PsiMethod method, @NotNull LanguageLevel languageLevel,@NotNull PsiFile containingFile) { |
| return checkFeature(method, Feature.EXTENSION_METHODS, languageLevel, containingFile); |
| } |
| |
| @Nullable |
| public static HighlightInfo checkMethodReferencesFeature(@NotNull PsiMethodReferenceExpression expression, @NotNull LanguageLevel languageLevel,@NotNull PsiFile containingFile) { |
| return checkFeature(expression, Feature.METHOD_REFERENCES, languageLevel, containingFile); |
| } |
| |
| @Nullable |
| public static HighlightInfo checkLambdaFeature(@NotNull PsiLambdaExpression expression, @NotNull LanguageLevel languageLevel,@NotNull PsiFile containingFile) { |
| return checkFeature(expression, Feature.LAMBDA_EXPRESSIONS, languageLevel, containingFile); |
| } |
| |
| @Nullable |
| public static HighlightInfo checkTypeAnnotationFeature(@NotNull PsiAnnotation annotation, @NotNull LanguageLevel languageLevel,@NotNull PsiFile containingFile) { |
| return checkFeature(annotation, Feature.TYPE_ANNOTATIONS, languageLevel, containingFile); |
| } |
| } |