blob: b02c02657532ffd6b0e770b05d19e5e9c608835b [file] [log] [blame]
/*
* Copyright 2000-2012 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.deprecation;
import com.intellij.codeInsight.daemon.JavaErrorMessages;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightMessageUtil;
import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil;
import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool;
import com.intellij.codeInspection.DeprecationUtil;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.infos.MethodCandidateInfo;
import com.intellij.psi.util.MethodSignatureBackedByPsiMethod;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.List;
/**
* @author max
*/
public class DeprecationInspection extends BaseJavaBatchLocalInspectionTool {
@NonNls public static final String SHORT_NAME = DeprecationUtil.DEPRECATION_SHORT_NAME;
@NonNls public static final String ID = DeprecationUtil.DEPRECATION_ID;
public static final String DISPLAY_NAME = DeprecationUtil.DEPRECATION_DISPLAY_NAME;
public static final String IGNORE_METHODS_OF_DEPRECATED_NAME = "IGNORE_METHODS_OF_DEPRECATED";
public boolean IGNORE_INSIDE_DEPRECATED = false;
public boolean IGNORE_ABSTRACT_DEPRECATED_OVERRIDES = true;
public boolean IGNORE_IMPORT_STATEMENTS = true;
public boolean IGNORE_METHODS_OF_DEPRECATED = true;
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new DeprecationElementVisitor(holder, IGNORE_INSIDE_DEPRECATED, IGNORE_ABSTRACT_DEPRECATED_OVERRIDES,
IGNORE_IMPORT_STATEMENTS, IGNORE_METHODS_OF_DEPRECATED);
}
@Override
@NotNull
public String getDisplayName() {
return DISPLAY_NAME;
}
@Override
@NotNull
public String getGroupDisplayName() {
return "";
}
@Override
@NotNull
public String getShortName() {
return SHORT_NAME;
}
@Override
@NotNull
@NonNls
public String getID() {
return ID;
}
@Override
public boolean isEnabledByDefault() {
return true;
}
@Override
public JComponent createOptionsPanel() {
final MultipleCheckboxOptionsPanel panel = new MultipleCheckboxOptionsPanel(this);
panel.addCheckbox("Ignore inside deprecated members", "IGNORE_INSIDE_DEPRECATED");
panel.addCheckbox("Ignore inside non-static imports", "IGNORE_IMPORT_STATEMENTS");
panel.addCheckbox("<html>Ignore overrides of deprecated abstract methods from non-deprecated supers</html>", "IGNORE_ABSTRACT_DEPRECATED_OVERRIDES");
panel.addCheckbox("Ignore members of deprecated classes", IGNORE_METHODS_OF_DEPRECATED_NAME);
return panel;
}
private static class DeprecationElementVisitor extends JavaElementVisitor {
private final ProblemsHolder myHolder;
private final boolean myIgnoreInsideDeprecated;
private final boolean myIgnoreAbstractDeprecatedOverrides;
private final boolean myIgnoreImportStatements;
private final boolean myIgnoreMethodsOfDeprecated;
public DeprecationElementVisitor(final ProblemsHolder holder,
boolean ignoreInsideDeprecated,
boolean ignoreAbstractDeprecatedOverrides,
boolean ignoreImportStatements,
boolean ignoreMethodsOfDeprecated) {
myHolder = holder;
myIgnoreInsideDeprecated = ignoreInsideDeprecated;
myIgnoreAbstractDeprecatedOverrides = ignoreAbstractDeprecatedOverrides;
myIgnoreImportStatements = ignoreImportStatements;
myIgnoreMethodsOfDeprecated = ignoreMethodsOfDeprecated;
}
@Override
public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
JavaResolveResult result = reference.advancedResolve(true);
PsiElement resolved = result.getElement();
checkDeprecated(resolved, reference.getReferenceNameElement(), null, myIgnoreInsideDeprecated, myIgnoreImportStatements, myIgnoreMethodsOfDeprecated, myHolder);
}
@Override
public void visitImportStaticStatement(PsiImportStaticStatement statement) {
final PsiJavaCodeReferenceElement importReference = statement.getImportReference();
if (importReference != null) {
checkDeprecated(importReference.resolve(), importReference.getReferenceNameElement(), null, myIgnoreInsideDeprecated, false, true, myHolder);
}
}
@Override public void visitReferenceExpression(PsiReferenceExpression expression) {
visitReferenceElement(expression);
}
@Override public void visitNewExpression(PsiNewExpression expression) {
PsiType type = expression.getType();
PsiExpressionList list = expression.getArgumentList();
if (!(type instanceof PsiClassType)) return;
PsiClassType.ClassResolveResult typeResult = ((PsiClassType)type).resolveGenerics();
PsiClass aClass = typeResult.getElement();
if (aClass == null) return;
if (aClass instanceof PsiAnonymousClass) {
type = ((PsiAnonymousClass)aClass).getBaseClassType();
typeResult = ((PsiClassType)type).resolveGenerics();
aClass = typeResult.getElement();
if (aClass == null) return;
}
final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(expression.getProject()).getResolveHelper();
final PsiMethod[] constructors = aClass.getConstructors();
if (constructors.length > 0 && list != null) {
JavaResolveResult[] results = resolveHelper.multiResolveConstructor((PsiClassType)type, list, list);
MethodCandidateInfo result = null;
if (results.length == 1) result = (MethodCandidateInfo)results[0];
PsiMethod constructor = result == null ? null : result.getElement();
if (constructor != null && expression.getClassOrAnonymousClassReference() != null) {
if (expression.getClassReference() == null && constructor.getParameterList().getParametersCount() == 0) return;
checkDeprecated(constructor, expression.getClassOrAnonymousClassReference(), null, myIgnoreInsideDeprecated, myIgnoreImportStatements, true, myHolder);
}
}
}
@Override public void visitMethod(PsiMethod method){
MethodSignatureBackedByPsiMethod methodSignature = MethodSignatureBackedByPsiMethod.create(method, PsiSubstitutor.EMPTY);
if (!method.isConstructor()) {
List<MethodSignatureBackedByPsiMethod> superMethodSignatures = method.findSuperMethodSignaturesIncludingStatic(true);
checkMethodOverridesDeprecated(methodSignature, superMethodSignatures, myIgnoreAbstractDeprecatedOverrides, myHolder);
} else {
checkImplicitCallToSuper(method);
}
}
private void checkImplicitCallToSuper(PsiMethod method) {
final PsiClass containingClass = method.getContainingClass();
assert containingClass != null;
final PsiClass superClass = containingClass.getSuperClass();
if (hasDefaultDeprecatedConstructor(superClass)) {
if (superClass instanceof PsiAnonymousClass) {
final PsiExpressionList argumentList = ((PsiAnonymousClass)superClass).getArgumentList();
if (argumentList != null && argumentList.getExpressions().length > 0) return;
}
final PsiCodeBlock body = method.getBody();
if (body != null) {
final PsiStatement[] statements = body.getStatements();
if (statements.length == 0 || !JavaHighlightUtil.isSuperOrThisCall(statements[0], true, true)) {
registerDefaultConstructorProblem(superClass, method.getNameIdentifier(), false);
}
}
}
}
private void registerDefaultConstructorProblem(PsiClass superClass, PsiElement nameIdentifier, boolean asDeprecated) {
myHolder.registerProblem(nameIdentifier, "Default constructor in " + superClass.getQualifiedName() + " is deprecated",
asDeprecated ? ProblemHighlightType.LIKE_DEPRECATED : ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
}
@Override
public void visitClass(PsiClass aClass) {
if (aClass instanceof PsiTypeParameter) return;
final PsiMethod[] currentConstructors = aClass.getConstructors();
if (currentConstructors.length == 0) {
final PsiClass superClass = aClass.getSuperClass();
if (hasDefaultDeprecatedConstructor(superClass)) {
final boolean isAnonymous = aClass instanceof PsiAnonymousClass;
if (isAnonymous) {
final PsiExpressionList argumentList = ((PsiAnonymousClass)aClass).getArgumentList();
if (argumentList != null && argumentList.getExpressions().length > 0) return;
}
registerDefaultConstructorProblem(superClass, isAnonymous ? ((PsiAnonymousClass)aClass).getBaseClassReference() : aClass.getNameIdentifier(), isAnonymous);
}
}
}
}
private static boolean hasDefaultDeprecatedConstructor(PsiClass superClass) {
if (superClass != null) {
final PsiMethod[] constructors = superClass.getConstructors();
for (PsiMethod constructor : constructors) {
if (constructor.getParameterList().getParametersCount() == 0 && constructor.isDeprecated()) {
return true;
}
}
}
return false;
}
//@top
static void checkMethodOverridesDeprecated(MethodSignatureBackedByPsiMethod methodSignature,
List<MethodSignatureBackedByPsiMethod> superMethodSignatures,
boolean ignoreAbstractDeprecatedOverrides, ProblemsHolder holder) {
PsiMethod method = methodSignature.getMethod();
PsiElement methodName = method.getNameIdentifier();
for (MethodSignatureBackedByPsiMethod superMethodSignature : superMethodSignatures) {
PsiMethod superMethod = superMethodSignature.getMethod();
PsiClass aClass = superMethod.getContainingClass();
if (aClass == null) continue;
// do not show deprecated warning for class implementing deprecated methods
if (ignoreAbstractDeprecatedOverrides && !aClass.isDeprecated() && superMethod.hasModifierProperty(PsiModifier.ABSTRACT)) continue;
if (superMethod.isDeprecated()) {
String description = JavaErrorMessages.message("overrides.deprecated.method",
HighlightMessageUtil.getSymbolName(aClass, PsiSubstitutor.EMPTY));
holder.registerProblem(methodName, description, ProblemHighlightType.LIKE_DEPRECATED);
}
}
}
public static void checkDeprecated(PsiElement refElement,
PsiElement elementToHighlight,
@Nullable TextRange rangeInElement,
ProblemsHolder holder) {
checkDeprecated(refElement, elementToHighlight, rangeInElement, false, false, true, holder);
}
public static void checkDeprecated(PsiElement refElement,
PsiElement elementToHighlight,
@Nullable TextRange rangeInElement,
boolean ignoreInsideDeprecated,
boolean ignoreImportStatements,
boolean ignoreMethodsOfDeprecated,
ProblemsHolder holder) {
if (!(refElement instanceof PsiDocCommentOwner)) return;
if (!((PsiDocCommentOwner)refElement).isDeprecated()) {
if (!ignoreMethodsOfDeprecated) {
checkDeprecated(((PsiDocCommentOwner)refElement).getContainingClass(), elementToHighlight, rangeInElement,
ignoreInsideDeprecated, ignoreImportStatements, false, holder);
}
return;
}
if (ignoreInsideDeprecated) {
PsiElement parent = elementToHighlight;
while ((parent = PsiTreeUtil.getParentOfType(parent, PsiDocCommentOwner.class, true)) != null) {
if (((PsiDocCommentOwner)parent).isDeprecated()) return;
}
}
if (ignoreImportStatements && PsiTreeUtil.getParentOfType(elementToHighlight, PsiImportStatementBase.class) != null) {
return;
}
String description = JavaErrorMessages.message("deprecated.symbol",
HighlightMessageUtil.getSymbolName(refElement, PsiSubstitutor.EMPTY));
holder.registerProblem(elementToHighlight, description, ProblemHighlightType.LIKE_DEPRECATED, rangeInElement);
}
}