| /* |
| * 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.codeInspection.uncheckedWarnings; |
| |
| import com.intellij.codeInsight.daemon.JavaErrorMessages; |
| import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil; |
| import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil; |
| import com.intellij.codeInsight.daemon.impl.quickfix.VariableArrayTypeFix; |
| import com.intellij.codeInsight.intention.IntentionAction; |
| import com.intellij.codeInsight.intention.QuickFixFactory; |
| import com.intellij.codeInsight.quickfix.ChangeVariableTypeQuickFixProvider; |
| import com.intellij.codeInspection.*; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.extensions.Extensions; |
| import com.intellij.openapi.util.Pass; |
| import com.intellij.openapi.util.WriteExternalException; |
| import com.intellij.pom.java.LanguageLevel; |
| import com.intellij.psi.*; |
| import com.intellij.psi.util.*; |
| import org.intellij.lang.annotations.Pattern; |
| import org.jdom.Element; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.ActionListener; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| public class UncheckedWarningLocalInspectionBase extends BaseJavaBatchLocalInspectionTool { |
| @NonNls public static final String SHORT_NAME = "UNCHECKED_WARNING"; |
| public static final String DISPLAY_NAME = InspectionsBundle.message("unchecked.warning"); |
| @NonNls private static final String ID = "unchecked"; |
| private static final Logger LOG = Logger.getInstance("#" + UncheckedWarningLocalInspectionBase.class); |
| public boolean IGNORE_UNCHECKED_ASSIGNMENT = false; |
| public boolean IGNORE_UNCHECKED_GENERICS_ARRAY_CREATION = false; |
| public boolean IGNORE_UNCHECKED_CALL = false; |
| public boolean IGNORE_UNCHECKED_CAST = false; |
| public boolean IGNORE_UNCHECKED_OVERRIDING = false; |
| |
| protected static JCheckBox createSetting(final String cbText, |
| final boolean option, |
| final Pass<JCheckBox> pass) { |
| final JCheckBox uncheckedCb = new JCheckBox(cbText, option); |
| uncheckedCb.addActionListener(new ActionListener() { |
| @Override |
| public void actionPerformed(ActionEvent e) { |
| pass.pass(uncheckedCb); |
| } |
| }); |
| return uncheckedCb; |
| } |
| |
| public static LocalQuickFix[] getChangeVariableTypeFixes(@NotNull PsiVariable parameter, PsiType itemType) { |
| if (itemType instanceof PsiMethodReferenceType) return LocalQuickFix.EMPTY_ARRAY; |
| final List<LocalQuickFix> result = new ArrayList<LocalQuickFix>(); |
| LOG.assertTrue(parameter.isValid()); |
| if (itemType != null) { |
| for (ChangeVariableTypeQuickFixProvider fixProvider : Extensions.getExtensions(ChangeVariableTypeQuickFixProvider.EP_NAME)) { |
| for (IntentionAction action : fixProvider.getFixes(parameter, itemType)) { |
| if (action instanceof LocalQuickFix) { |
| result.add((LocalQuickFix)action); |
| } |
| } |
| } |
| } |
| return result.toArray(new LocalQuickFix[result.size()]); |
| } |
| |
| @Override |
| @NotNull |
| public String getGroupDisplayName() { |
| return ""; |
| } |
| |
| @Override |
| @NotNull |
| public String getDisplayName() { |
| return DISPLAY_NAME; |
| } |
| |
| @Override |
| @NotNull |
| @NonNls |
| public String getShortName() { |
| return SHORT_NAME; |
| } |
| |
| @Override |
| @Pattern(VALID_ID_PATTERN) |
| @NotNull |
| @NonNls |
| public String getID() { |
| return ID; |
| } |
| |
| @Override |
| public boolean isEnabledByDefault() { |
| return true; |
| } |
| |
| @Override |
| public void writeSettings(@NotNull Element node) throws WriteExternalException { |
| if (IGNORE_UNCHECKED_ASSIGNMENT || |
| IGNORE_UNCHECKED_CALL || |
| IGNORE_UNCHECKED_CAST || |
| IGNORE_UNCHECKED_OVERRIDING || |
| IGNORE_UNCHECKED_GENERICS_ARRAY_CREATION) { |
| super.writeSettings(node); |
| } |
| } |
| |
| @NotNull |
| @Override |
| public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, |
| boolean isOnTheFly, |
| @NotNull LocalInspectionToolSession session) { |
| LanguageLevel languageLevel = PsiUtil.getLanguageLevel(session.getFile()); |
| if (!languageLevel.isAtLeast(LanguageLevel.JDK_1_5)) return super.buildVisitor(holder, isOnTheFly, session); |
| |
| return new UncheckedWarningsVisitor(isOnTheFly, languageLevel){ |
| @Override |
| protected void registerProblem(@NotNull String message, @NotNull PsiElement psiElement, @NotNull LocalQuickFix[] quickFixes) { |
| holder.registerProblem(psiElement, message, quickFixes); |
| } |
| }; |
| } |
| |
| protected LocalQuickFix[] createFixes() { |
| return null; |
| } |
| |
| private abstract class UncheckedWarningsVisitor extends JavaElementVisitor { |
| private final boolean myOnTheFly; |
| @NotNull private final LanguageLevel myLanguageLevel; |
| private final LocalQuickFix[] myGenerifyFixes; |
| |
| public UncheckedWarningsVisitor(boolean onTheFly, @NotNull LanguageLevel level) { |
| myOnTheFly = onTheFly; |
| myLanguageLevel = level; |
| myGenerifyFixes = onTheFly ? createFixes() : LocalQuickFix.EMPTY_ARRAY; |
| } |
| |
| protected abstract void registerProblem(@NotNull String message, @NotNull PsiElement psiElement, @NotNull LocalQuickFix[] quickFixes); |
| |
| |
| @Override |
| public void visitReferenceExpression(PsiReferenceExpression expression) { |
| if (IGNORE_UNCHECKED_GENERICS_ARRAY_CREATION) return; |
| final JavaResolveResult result = expression.advancedResolve(false); |
| if (JavaGenericsUtil.isUncheckedWarning(expression, result, myLanguageLevel)) { |
| registerProblem("Unchecked generics array creation for varargs parameter", expression, LocalQuickFix.EMPTY_ARRAY); |
| } |
| } |
| |
| @Override |
| public void visitNewExpression(PsiNewExpression expression) { |
| super.visitNewExpression(expression); |
| if (IGNORE_UNCHECKED_GENERICS_ARRAY_CREATION) return; |
| final PsiJavaCodeReferenceElement classReference = expression.getClassOrAnonymousClassReference(); |
| if (classReference != null && JavaGenericsUtil.isUncheckedWarning(classReference, expression.resolveMethodGenerics(), myLanguageLevel)) { |
| registerProblem("Unchecked generics array creation for varargs parameter", classReference, LocalQuickFix.EMPTY_ARRAY); |
| } |
| } |
| |
| @Override |
| public void visitTypeCastExpression(PsiTypeCastExpression expression) { |
| super.visitTypeCastExpression(expression); |
| if (IGNORE_UNCHECKED_CAST) return; |
| final PsiTypeElement typeElement = expression.getCastType(); |
| if (typeElement == null) return; |
| final PsiType castType = typeElement.getType(); |
| final PsiExpression operand = expression.getOperand(); |
| if (operand == null) return; |
| final PsiType exprType = operand.getType(); |
| if (exprType == null) return; |
| if (!TypeConversionUtil.areTypesConvertible(exprType, castType)) return; |
| if (JavaGenericsUtil.isUncheckedCast(castType, exprType)) { |
| final String description = |
| JavaErrorMessages.message("generics.unchecked.cast", JavaHighlightUtil.formatType(exprType), JavaHighlightUtil |
| .formatType(castType)); |
| registerProblem(description, expression, myGenerifyFixes); |
| } |
| } |
| |
| @Override |
| public void visitMethodReferenceExpression(PsiMethodReferenceExpression expression) { |
| super.visitMethodReferenceExpression(expression); |
| if (IGNORE_UNCHECKED_CALL) return; |
| final JavaResolveResult result = expression.advancedResolve(false); |
| final String description = getUncheckedCallDescription(result); |
| if (description != null) { |
| final PsiElement referenceNameElement = expression.getReferenceNameElement(); |
| registerProblem(description, referenceNameElement != null ? referenceNameElement : expression, myGenerifyFixes); |
| } |
| } |
| |
| @Override |
| public void visitCallExpression(PsiCallExpression callExpression) { |
| super.visitCallExpression(callExpression); |
| final JavaResolveResult result = callExpression.resolveMethodGenerics(); |
| final String description = getUncheckedCallDescription(result); |
| if (description != null) { |
| if (IGNORE_UNCHECKED_CALL) return; |
| final PsiExpression element = callExpression instanceof PsiMethodCallExpression |
| ? ((PsiMethodCallExpression)callExpression).getMethodExpression() |
| : callExpression; |
| registerProblem(description, element, myGenerifyFixes); |
| } |
| else { |
| if (IGNORE_UNCHECKED_ASSIGNMENT) return; |
| final PsiSubstitutor substitutor = result.getSubstitutor(); |
| final PsiExpressionList argumentList = callExpression.getArgumentList(); |
| if (argumentList != null) { |
| final PsiMethod method = (PsiMethod)result.getElement(); |
| if (method != null) { |
| final PsiExpression[] expressions = argumentList.getExpressions(); |
| final PsiParameter[] parameters = method.getParameterList().getParameters(); |
| if (parameters.length != 0) { |
| for (int i = 0; i < expressions.length; i++) { |
| PsiParameter parameter = parameters[Math.min(i, parameters.length - 1)]; |
| final PsiExpression expression = expressions[i]; |
| final PsiType parameterType = substitutor.substitute(parameter.getType()); |
| final PsiType expressionType = expression.getType(); |
| if (expressionType != null) { |
| checkRawToGenericsAssignment(expression, parameterType, expressionType, true, myGenerifyFixes); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void visitVariable(PsiVariable variable) { |
| super.visitVariable(variable); |
| if (IGNORE_UNCHECKED_ASSIGNMENT) return; |
| PsiExpression initializer = variable.getInitializer(); |
| if (initializer == null || initializer instanceof PsiArrayInitializerExpression) return; |
| final PsiType initializerType = initializer.getType(); |
| checkRawToGenericsAssignment(initializer, variable.getType(), initializerType, true, |
| myOnTheFly ? getChangeVariableTypeFixes(variable, initializerType) : LocalQuickFix.EMPTY_ARRAY); |
| } |
| |
| @Override |
| public void visitForeachStatement(PsiForeachStatement statement) { |
| super.visitForeachStatement(statement); |
| if (IGNORE_UNCHECKED_ASSIGNMENT) return; |
| final PsiParameter parameter = statement.getIterationParameter(); |
| final PsiType parameterType = parameter.getType(); |
| final PsiExpression iteratedValue = statement.getIteratedValue(); |
| if (iteratedValue == null) return; |
| final PsiType itemType = JavaGenericsUtil.getCollectionItemType(iteratedValue); |
| checkRawToGenericsAssignment(parameter, parameterType, itemType, true, myOnTheFly ? getChangeVariableTypeFixes(parameter, itemType) : LocalQuickFix.EMPTY_ARRAY); |
| } |
| |
| @Override |
| public void visitAssignmentExpression(PsiAssignmentExpression expression) { |
| super.visitAssignmentExpression(expression); |
| if (IGNORE_UNCHECKED_ASSIGNMENT) return; |
| if (!"=".equals(expression.getOperationSign().getText())) return; |
| PsiExpression lExpr = expression.getLExpression(); |
| PsiExpression rExpr = expression.getRExpression(); |
| if (rExpr == null) return; |
| PsiType lType = lExpr.getType(); |
| PsiType rType = rExpr.getType(); |
| if (rType == null) return; |
| PsiVariable leftVar = null; |
| if (lExpr instanceof PsiReferenceExpression) { |
| PsiElement element = ((PsiReferenceExpression)lExpr).resolve(); |
| if (element instanceof PsiVariable) { |
| leftVar = (PsiVariable)element; |
| } |
| } |
| checkRawToGenericsAssignment(rExpr, lType, rType, true, myOnTheFly && leftVar != null ? getChangeVariableTypeFixes(leftVar, rType) : LocalQuickFix.EMPTY_ARRAY); |
| } |
| |
| @Override |
| public void visitArrayInitializerExpression(PsiArrayInitializerExpression arrayInitializer) { |
| super.visitArrayInitializerExpression(arrayInitializer); |
| if (IGNORE_UNCHECKED_ASSIGNMENT) return; |
| final PsiType type = arrayInitializer.getType(); |
| if (!(type instanceof PsiArrayType)) return; |
| final PsiType componentType = ((PsiArrayType)type).getComponentType(); |
| |
| |
| boolean arrayTypeFixChecked = false; |
| VariableArrayTypeFix fix = null; |
| |
| final PsiExpression[] initializers = arrayInitializer.getInitializers(); |
| for (PsiExpression expression : initializers) { |
| final PsiType itemType = expression.getType(); |
| |
| if (itemType == null) continue; |
| if (!TypeConversionUtil.isAssignable(componentType, itemType)) continue; |
| if (JavaGenericsUtil.isRawToGeneric(componentType, itemType)) { |
| String description = JavaErrorMessages.message("generics.unchecked.assignment", |
| JavaHighlightUtil.formatType(itemType), |
| JavaHighlightUtil.formatType(componentType)); |
| if (!arrayTypeFixChecked) { |
| final PsiType checkResult = JavaHighlightUtil.sameType(initializers); |
| fix = checkResult != null ? new VariableArrayTypeFix(arrayInitializer, checkResult) : null; |
| arrayTypeFixChecked = true; |
| } |
| |
| if (fix != null) { |
| registerProblem(description, expression, new LocalQuickFix[]{fix}); |
| } |
| } |
| } |
| } |
| |
| private void checkRawToGenericsAssignment(@NotNull PsiElement parameter, |
| PsiType parameterType, |
| PsiType itemType, |
| boolean checkAssignability, |
| @NotNull LocalQuickFix[] quickFixes) { |
| if (parameterType == null || itemType == null) return; |
| if (checkAssignability && !TypeConversionUtil.isAssignable(parameterType, itemType)) return; |
| if (JavaGenericsUtil.isRawToGeneric(parameterType, itemType)) { |
| String description = JavaErrorMessages.message("generics.unchecked.assignment", |
| JavaHighlightUtil.formatType(itemType), |
| JavaHighlightUtil.formatType(parameterType)); |
| registerProblem(description, parameter, quickFixes); |
| } |
| } |
| |
| @Override |
| public void visitMethod(PsiMethod method) { |
| super.visitMethod(method); |
| if (IGNORE_UNCHECKED_OVERRIDING) return; |
| if (!method.isConstructor()) { |
| List<HierarchicalMethodSignature> superMethodSignatures = method.getHierarchicalMethodSignature().getSuperSignatures(); |
| if (!superMethodSignatures.isEmpty() && !method.hasModifierProperty(PsiModifier.STATIC)) { |
| final MethodSignature signature = method.getSignature(PsiSubstitutor.EMPTY); |
| for (MethodSignatureBackedByPsiMethod superSignature : superMethodSignatures) { |
| PsiMethod baseMethod = superSignature.getMethod(); |
| PsiSubstitutor substitutor = MethodSignatureUtil.getSuperMethodSignatureSubstitutor(signature, superSignature); |
| if (substitutor == null) substitutor = superSignature.getSubstitutor(); |
| if (PsiUtil.isRawSubstitutor(baseMethod, superSignature.getSubstitutor())) continue; |
| final PsiType baseReturnType = substitutor.substitute(baseMethod.getReturnType()); |
| final PsiType overriderReturnType = method.getReturnType(); |
| if (baseReturnType == null || overriderReturnType == null) return; |
| if (JavaGenericsUtil.isRawToGeneric(baseReturnType, overriderReturnType)) { |
| final String message = JavaErrorMessages.message("unchecked.overriding.incompatible.return.type", |
| JavaHighlightUtil.formatType(overriderReturnType), |
| JavaHighlightUtil.formatType(baseReturnType)); |
| |
| final PsiTypeElement returnTypeElement = method.getReturnTypeElement(); |
| LOG.assertTrue(returnTypeElement != null); |
| registerProblem(message, returnTypeElement, LocalQuickFix.EMPTY_ARRAY); |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void visitReturnStatement(PsiReturnStatement statement) { |
| super.visitReturnStatement(statement); |
| if (IGNORE_UNCHECKED_ASSIGNMENT) return; |
| final PsiMethod method = PsiTreeUtil.getParentOfType(statement, PsiMethod.class); |
| if (method != null) { |
| final PsiType returnType = method.getReturnType(); |
| if (returnType != null && returnType != PsiType.VOID) { |
| final PsiExpression returnValue = statement.getReturnValue(); |
| if (returnValue != null) { |
| final PsiType valueType = returnValue.getType(); |
| if (valueType != null) { |
| checkRawToGenericsAssignment(returnValue, returnType, valueType, |
| false, |
| new LocalQuickFix[]{QuickFixFactory.getInstance().createMethodReturnFix(method, valueType, true)}); |
| } |
| } |
| } |
| } |
| } |
| |
| |
| @Nullable |
| private String getUncheckedCallDescription(JavaResolveResult resolveResult) { |
| final PsiElement element = resolveResult.getElement(); |
| if (!(element instanceof PsiMethod)) return null; |
| final PsiMethod method = (PsiMethod)element; |
| final PsiSubstitutor substitutor = resolveResult.getSubstitutor(); |
| if (!PsiUtil.isRawSubstitutor(method, substitutor)) return null; |
| final PsiParameter[] parameters = method.getParameterList().getParameters(); |
| for (final PsiParameter parameter : parameters) { |
| final PsiType parameterType = parameter.getType(); |
| if (parameterType.accept(new PsiTypeVisitor<Boolean>() { |
| @Override |
| public Boolean visitPrimitiveType(PsiPrimitiveType primitiveType) { |
| return Boolean.FALSE; |
| } |
| |
| @Override |
| public Boolean visitArrayType(PsiArrayType arrayType) { |
| return arrayType.getComponentType().accept(this); |
| } |
| |
| @Override |
| public Boolean visitClassType(PsiClassType classType) { |
| PsiClass psiClass = classType.resolve(); |
| if (psiClass instanceof PsiTypeParameter) { |
| if (((PsiTypeParameter)psiClass).getOwner() == method) return Boolean.FALSE; |
| return substitutor.substitute((PsiTypeParameter)psiClass) == null ? Boolean.TRUE : Boolean.FALSE; |
| } |
| PsiType[] parameters = classType.getParameters(); |
| for (PsiType parameter : parameters) { |
| if (parameter.accept(this).booleanValue()) return Boolean.TRUE; |
| } |
| return Boolean.FALSE; |
| } |
| |
| @Override |
| public Boolean visitWildcardType(PsiWildcardType wildcardType) { |
| PsiType bound = wildcardType.getBound(); |
| if (bound != null) return bound.accept(this); |
| return Boolean.TRUE; |
| } |
| |
| @Override |
| public Boolean visitEllipsisType(PsiEllipsisType ellipsisType) { |
| return ellipsisType.getComponentType().accept(this); |
| } |
| }).booleanValue()) { |
| final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(method.getProject()).getElementFactory(); |
| PsiType type = elementFactory.createType(method.getContainingClass(), substitutor); |
| return JavaErrorMessages.message("generics.unchecked.call.to.member.of.raw.type", |
| JavaHighlightUtil.formatMethod(method), |
| JavaHighlightUtil.formatType(type)); |
| } |
| } |
| return null; |
| } |
| } |
| } |