Files
openide/java/java-impl/src/com/intellij/codeInspection/uncheckedWarnings/UncheckedWarningLocalInspection.java
T

471 lines
20 KiB
Java

/*
* Copyright 2000-2009 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.GenericsHighlightUtil;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil;
import com.intellij.codeInsight.daemon.impl.quickfix.GenerifyFileFix;
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.psi.*;
import com.intellij.psi.util.*;
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.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
/**
* User: anna
* Date: 17-Feb-2006
*/
public class UncheckedWarningLocalInspection extends BaseJavaLocalInspectionTool {
@NonNls public static final String SHORT_NAME = "UNCHECKED_WARNING";
public static final String DISPLAY_NAME = InspectionsBundle.message("unchecked.warning");
@NonNls public static final String ID = "unchecked";
private static final Logger LOG = Logger.getInstance("#" + UncheckedWarningLocalInspection.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;
@NotNull
public String getGroupDisplayName() {
return "";
}
@NotNull
public String getDisplayName() {
return DISPLAY_NAME;
}
@NotNull
@NonNls
public String getShortName() {
return SHORT_NAME;
}
@NotNull
@NonNls
public String getID() {
return ID;
}
public boolean isEnabledByDefault() {
return true;
}
@Override
public void writeSettings(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);
}
}
@Override
public JComponent createOptionsPanel() {
final JPanel panel = new JPanel(new GridBagLayout());
final GridBagConstraints gc = new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0,0,0,0), 0,0);
panel.add(createSetting("Ignore unchecked assignment", IGNORE_UNCHECKED_ASSIGNMENT, new Pass<JCheckBox>() {
@Override
public void pass(JCheckBox cb) {
IGNORE_UNCHECKED_ASSIGNMENT = cb.isSelected();
}
}), gc);
panel.add(createSetting("Ignore unchecked generics array creation for vararg parameter", IGNORE_UNCHECKED_GENERICS_ARRAY_CREATION, new Pass<JCheckBox>() {
@Override
public void pass(JCheckBox cb) {
IGNORE_UNCHECKED_GENERICS_ARRAY_CREATION = cb.isSelected();
}
}), gc);
panel.add(createSetting("Ignore unchecked call as member of raw type", IGNORE_UNCHECKED_CALL, new Pass<JCheckBox>() {
@Override
public void pass(JCheckBox cb) {
IGNORE_UNCHECKED_CALL = cb.isSelected();
}
}), gc);
panel.add(createSetting("Ignore unchecked cast", IGNORE_UNCHECKED_CAST, new Pass<JCheckBox>() {
@Override
public void pass(JCheckBox cb) {
IGNORE_UNCHECKED_CAST = cb.isSelected();
}
}), gc);
panel.add(createSetting("Ignore unchecked overriding", IGNORE_UNCHECKED_OVERRIDING, new Pass<JCheckBox>() {
@Override
public void pass(JCheckBox cb) {
IGNORE_UNCHECKED_OVERRIDING = cb.isSelected();
}
}), gc);
gc.fill = GridBagConstraints.BOTH;
gc.weighty = 1;
panel.add(Box.createVerticalBox(), gc);
return panel;
}
private 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;
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
return new UncheckedWarningsVisitor(isOnTheFly){
@Override
protected void registerProblem(String message, PsiElement psiElement, LocalQuickFix... quickFix) {
holder.registerProblem(psiElement, message, quickFix);
}
};
}
public abstract class UncheckedWarningsVisitor extends JavaElementVisitor {
private final boolean myOnTheFly;
public UncheckedWarningsVisitor(boolean onTheFly) {
myOnTheFly = onTheFly;
}
protected abstract void registerProblem(String message, PsiElement psiElement, LocalQuickFix... quickFix);
@Override
public void visitReferenceExpression(PsiReferenceExpression expression) {
if (IGNORE_UNCHECKED_GENERICS_ARRAY_CREATION) return;
if (!PsiUtil.isLanguageLevel5OrHigher(expression)) return;
if (GenericsHighlightUtil.isUncheckedWarning(expression, expression.resolve())) {
registerProblem("Unchecked generics array creation for varargs parameter", expression, null);
}
}
@Override
public void visitNewExpression(PsiNewExpression expression) {
super.visitNewExpression(expression);
if (IGNORE_UNCHECKED_GENERICS_ARRAY_CREATION) return;
if (!PsiUtil.isLanguageLevel5OrHigher(expression)) return;
final PsiJavaCodeReferenceElement classReference = expression.getClassOrAnonymousClassReference();
if (GenericsHighlightUtil.isUncheckedWarning(classReference, expression.resolveConstructor())) {
registerProblem("Unchecked generics array creation for varargs parameter", classReference, null);
}
}
@Override
public void visitTypeCastExpression(PsiTypeCastExpression expression) {
super.visitTypeCastExpression(expression);
if (IGNORE_UNCHECKED_CAST) return;
if (!PsiUtil.isLanguageLevel5OrHigher(expression)) 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 (GenericsHighlightUtil.isUncheckedCast(castType, exprType)) {
final String description =
JavaErrorMessages.message("generics.unchecked.cast", HighlightUtil.formatType(exprType), HighlightUtil.formatType(castType));
registerProblem(description, expression, myOnTheFly ? new GenerifyFileFix(operand.getContainingFile().getName()) : null);
}
}
@Override
public void visitCallExpression(PsiCallExpression callExpression) {
super.visitCallExpression(callExpression);
if (!PsiUtil.isLanguageLevel5OrHigher(callExpression)) return;
final JavaResolveResult result = callExpression.resolveMethodGenerics();
final String description = getUncheckedCallDescription(result);
if (description != null) {
if (IGNORE_UNCHECKED_CALL) return;
registerProblem(description, callExpression instanceof PsiMethodCallExpression
? ((PsiMethodCallExpression)callExpression).getMethodExpression()
: callExpression, myOnTheFly ? new GenerifyFileFix(callExpression.getContainingFile().getName()) : null);
}
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 = substitutor.substitute(expression.getType());
if (expressionType != null) {
checkRawToGenericsAssignment(expression, parameterType, expressionType, true, myOnTheFly ? new GenerifyFileFix(expression.getContainingFile().getName()) : null);
}
}
}
}
}
}
}
@Override
public void visitVariable(PsiVariable variable) {
super.visitVariable(variable);
if (IGNORE_UNCHECKED_ASSIGNMENT) return;
if (!PsiUtil.isLanguageLevel5OrHigher(variable)) 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) : null);
}
@Override
public void visitForeachStatement(PsiForeachStatement statement) {
super.visitForeachStatement(statement);
if (IGNORE_UNCHECKED_ASSIGNMENT) return;
if (!PsiUtil.isLanguageLevel5OrHigher(statement)) return;
final PsiParameter parameter = statement.getIterationParameter();
final PsiType parameterType = parameter.getType();
final PsiExpression iteratedValue = statement.getIteratedValue();
if (iteratedValue == null) return;
final PsiType itemType = GenericsHighlightUtil.getCollectionItemType(iteratedValue);
if (!PsiUtil.isLanguageLevel5OrHigher(statement)) return;
checkRawToGenericsAssignment(parameter, parameterType, itemType, true, myOnTheFly ? getChangeVariableTypeFixes(parameter, itemType) : null);
}
@Override
public void visitAssignmentExpression(PsiAssignmentExpression expression) {
super.visitAssignmentExpression(expression);
if (IGNORE_UNCHECKED_ASSIGNMENT) return;
if (!PsiUtil.isLanguageLevel5OrHigher(expression)) 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) : null);
}
@Override
public void visitArrayInitializerExpression(PsiArrayInitializerExpression arrayInitializer) {
super.visitArrayInitializerExpression(arrayInitializer);
if (IGNORE_UNCHECKED_ASSIGNMENT) return;
if (!PsiUtil.isLanguageLevel5OrHigher(arrayInitializer)) 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 (GenericsHighlightUtil.isRawToGeneric(componentType, itemType)) {
String description = JavaErrorMessages.message("generics.unchecked.assignment",
HighlightUtil.formatType(itemType),
HighlightUtil.formatType(componentType));
if (!arrayTypeFixChecked) {
final PsiType checkResult = HighlightUtil.sameType(initializers);
fix = checkResult != null ? new VariableArrayTypeFix(arrayInitializer, checkResult) : null;
arrayTypeFixChecked = true;
}
if (fix != null) {
registerProblem(description, expression, (LocalQuickFix)fix);
}
}
}
}
private void checkRawToGenericsAssignment(PsiElement parameter,
PsiType parameterType,
PsiType itemType,
boolean checkAssignability,
final LocalQuickFix... quickFix) {
if (parameterType == null || itemType == null) return;
if (checkAssignability && !TypeConversionUtil.isAssignable(parameterType, itemType)) return;
if (GenericsHighlightUtil.isRawToGeneric(parameterType, itemType)) {
String description = JavaErrorMessages.message("generics.unchecked.assignment",
HighlightUtil.formatType(itemType),
HighlightUtil.formatType(parameterType));
registerProblem(description, parameter, quickFix);
}
}
@Override
public void visitMethod(PsiMethod method) {
super.visitMethod(method);
if (IGNORE_UNCHECKED_OVERRIDING) return;
if (!PsiUtil.isLanguageLevel5OrHigher(method)) 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 (GenericsHighlightUtil.isRawToGeneric(baseReturnType, overriderReturnType)) {
final String message = JavaErrorMessages.message("unchecked.overriding.incompatible.return.type",
HighlightUtil.formatType(overriderReturnType),
HighlightUtil.formatType(baseReturnType));
final PsiTypeElement returnTypeElement = method.getReturnTypeElement();
LOG.assertTrue(returnTypeElement != null);
registerProblem(message, returnTypeElement);
}
}
}
}
}
@Override
public void visitReturnStatement(PsiReturnStatement statement) {
super.visitReturnStatement(statement);
if (IGNORE_UNCHECKED_ASSIGNMENT) return;
if (!PsiUtil.isLanguageLevel5OrHigher(statement)) 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,
(LocalQuickFix)QuickFixFactory.getInstance().createMethodReturnFix(method, valueType, true));
}
}
}
}
}
@Nullable
public String getUncheckedCallDescription(JavaResolveResult resolveResult) {
final PsiMethod method = (PsiMethod)resolveResult.getElement();
if (method == null) return null;
final PsiSubstitutor substitutor = resolveResult.getSubstitutor();
final PsiParameter[] parameters = method.getParameterList().getParameters();
for (final PsiParameter parameter : parameters) {
final PsiType parameterType = parameter.getType();
if (parameterType.accept(new PsiTypeVisitor<Boolean>() {
public Boolean visitPrimitiveType(PsiPrimitiveType primitiveType) {
return Boolean.FALSE;
}
public Boolean visitArrayType(PsiArrayType arrayType) {
return arrayType.getComponentType().accept(this);
}
public Boolean visitClassType(PsiClassType classType) {
PsiClass psiClass = classType.resolve();
if (psiClass instanceof PsiTypeParameter) {
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;
}
public Boolean visitWildcardType(PsiWildcardType wildcardType) {
PsiType bound = wildcardType.getBound();
if (bound != null) return bound.accept(this);
return Boolean.FALSE;
}
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",
HighlightUtil.formatMethod(method),
HighlightUtil.formatType(type));
}
}
return null;
}
}
public static LocalQuickFix[] getChangeVariableTypeFixes(PsiVariable parameter, PsiType itemType) {
final List<LocalQuickFix> result = new ArrayList<LocalQuickFix>();
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()]);
}
}