From 504a7ef9e0ae48e3e8b1386e65f72c9dfc64e5aa Mon Sep 17 00:00:00 2001 From: Pavel Dolgov Date: Tue, 31 Jan 2017 15:43:23 +0300 Subject: [PATCH] Java: Implemented enhanced deprecation inspection (JEP 277) for Java 9 (IDEA-159124) --- .../deprecation/DeprecationInspection.java | 233 +------------- .../DeprecationInspectionBase.java | 286 ++++++++++++++++++ .../MarkedForRemovalInspection.java | 68 +++++ .../src/messages/JavaErrorMessages.properties | 4 + .../forRemoval/forRemovalClass/expected.xml | 51 ++++ .../forRemoval/forRemovalClass/src/Test.java | 36 +++ .../expected.xml | 21 ++ .../src/Test.java | 22 ++ .../forRemoval/forRemovalField/expected.xml | 26 ++ .../forRemoval/forRemovalField/src/Test.java | 28 ++ .../forRemoval/forRemovalMethod/expected.xml | 26 ++ .../forRemoval/forRemovalMethod/src/Test.java | 28 ++ .../forRemovalOverride/expected.xml | 14 + .../forRemovalOverride/src/Test.java | 17 ++ .../daemon/ModuleHighlightingTest.kt | 9 +- .../MarkedForRemovalInspectionTest.java | 72 +++++ .../codeInspection/DeprecationUtil.java | 4 + .../src/messages/InspectionsBundle.properties | 1 + .../MarkedForRemoval.html | 5 + resources/src/META-INF/IdeaPlugin.xml | 8 +- 20 files changed, 735 insertions(+), 224 deletions(-) create mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/DeprecationInspectionBase.java create mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/MarkedForRemovalInspection.java create mode 100644 java/java-tests/testData/inspection/forRemoval/forRemovalClass/expected.xml create mode 100644 java/java-tests/testData/inspection/forRemoval/forRemovalClass/src/Test.java create mode 100644 java/java-tests/testData/inspection/forRemoval/forRemovalDefaultConstructorInSuper/expected.xml create mode 100644 java/java-tests/testData/inspection/forRemoval/forRemovalDefaultConstructorInSuper/src/Test.java create mode 100644 java/java-tests/testData/inspection/forRemoval/forRemovalField/expected.xml create mode 100644 java/java-tests/testData/inspection/forRemoval/forRemovalField/src/Test.java create mode 100644 java/java-tests/testData/inspection/forRemoval/forRemovalMethod/expected.xml create mode 100644 java/java-tests/testData/inspection/forRemoval/forRemovalMethod/src/Test.java create mode 100644 java/java-tests/testData/inspection/forRemoval/forRemovalOverride/expected.xml create mode 100644 java/java-tests/testData/inspection/forRemoval/forRemovalOverride/src/Test.java create mode 100644 java/java-tests/testSrc/com/intellij/java/codeInspection/MarkedForRemovalInspectionTest.java create mode 100644 resources-en/src/inspectionDescriptions/MarkedForRemoval.html diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/DeprecationInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/DeprecationInspection.java index 41a6140e9333..6c2fe0d743af 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/DeprecationInspection.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/DeprecationInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2017 JetBrains s.r.o. + * Copyright 2000-2016 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. @@ -15,30 +15,23 @@ */ 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.impl.PsiImplUtil; -import com.intellij.psi.infos.MethodCandidateInfo; -import com.intellij.psi.util.MethodSignatureBackedByPsiMethod; -import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiElementVisitor; +import org.intellij.lang.annotations.Pattern; +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 { +public class DeprecationInspection extends DeprecationInspectionBase { public static final String SHORT_NAME = DeprecationUtil.DEPRECATION_SHORT_NAME; public static final String ID = DeprecationUtil.DEPRECATION_ID; public static final String DISPLAY_NAME = DeprecationUtil.DEPRECATION_DISPLAY_NAME; @@ -51,9 +44,10 @@ public class DeprecationInspection extends BaseJavaBatchLocalInspectionTool { @Override @NotNull - public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + 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); + IGNORE_IMPORT_STATEMENTS, IGNORE_METHODS_OF_DEPRECATED, + false); } @Override @@ -81,11 +75,6 @@ public class DeprecationInspection extends BaseJavaBatchLocalInspectionTool { return ID; } - @Override - public boolean isEnabledByDefault() { - return true; - } - @Override public JComponent createOptionsPanel() { final MultipleCheckboxOptionsPanel panel = new MultipleCheckboxOptionsPanel(this); @@ -96,210 +85,10 @@ public class DeprecationInspection extends BaseJavaBatchLocalInspectionTool { 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; - - private DeprecationElementVisitor(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) { - PsiElement resolved = reference.advancedResolve(true).getElement(); - PsiElement refName = reference.getReferenceNameElement(); - checkDeprecated(resolved, refName, null, myIgnoreInsideDeprecated, myIgnoreImportStatements, myIgnoreMethodsOfDeprecated, myHolder); - } - - @Override - public void visitImportStaticStatement(PsiImportStaticStatement statement) { - PsiJavaCodeReferenceElement importReference = statement.getImportReference(); - if (importReference != null) { - PsiElement refName = importReference.getReferenceNameElement(); - checkDeprecated(importReference.resolve(), refName, null, myIgnoreInsideDeprecated, false, true, myHolder); - } - } - - @Override - public void visitReferenceExpression(PsiReferenceExpression expression) { - visitReferenceElement(expression); - } - - @Override - public void visitNewExpression(PsiNewExpression expression) { - PsiClass aClass = null; - PsiType type = expression.getType(); - if (type instanceof PsiClassType) { - aClass = ((PsiClassType)type).resolveGenerics().getElement(); - if (aClass instanceof PsiAnonymousClass) { - type = ((PsiAnonymousClass)aClass).getBaseClassType(); - aClass = ((PsiClassType)type).resolveGenerics().getElement(); - } - } - if (aClass == null) return; - PsiExpressionList list = expression.getArgumentList(); - PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(expression.getProject()).getResolveHelper(); - if (list != null && aClass.getConstructors().length > 0) { - 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(); - PsiJavaCodeReferenceElement ref; - if (constructor != null && (ref = expression.getClassOrAnonymousClassReference()) != null) { - if (expression.getClassReference() == null && constructor.getParameterList().getParametersCount() == 0) return; - checkDeprecated(constructor, ref, null, myIgnoreInsideDeprecated, myIgnoreImportStatements, true, myHolder); - } - } - } - - @Override - public void visitMethod(PsiMethod method) { - MethodSignatureBackedByPsiMethod methodSignature = MethodSignatureBackedByPsiMethod.create(method, PsiSubstitutor.EMPTY); - if (!method.isConstructor()) { - List 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) { - ProblemHighlightType type = asDeprecated ? ProblemHighlightType.LIKE_DEPRECATED : ProblemHighlightType.GENERIC_ERROR_OR_WARNING; - myHolder.registerProblem(nameIdentifier, "Default constructor in " + superClass.getQualifiedName() + " is deprecated", type); - } - - @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; - } - PsiElement identifier = isAnonymous ? ((PsiAnonymousClass)aClass).getBaseClassReference() : aClass.getNameIdentifier(); - registerDefaultConstructorProblem(superClass, identifier, isAnonymous); - } - } - } - - @Override - public void visitRequiresStatement(PsiRequiresStatement statement) { - PsiJavaModuleReferenceElement refElement = statement.getReferenceElement(); - if (refElement != null) { - PsiPolyVariantReference ref = refElement.getReference(); - PsiElement target = ref != null ? ref.resolve() : null; - if (target instanceof PsiJavaModule && PsiImplUtil.isDeprecatedByAnnotation((PsiJavaModule)target)) { - String message = JavaErrorMessages.message("deprecated.symbol", HighlightMessageUtil.getSymbolName(target)); - myHolder.registerProblem(refElement, message, ProblemHighlightType.LIKE_DEPRECATED); - } - } - } - } - - 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; - } - - private static void checkMethodOverridesDeprecated(MethodSignatureBackedByPsiMethod methodSignature, - List superMethodSignatures, - boolean ignoreAbstractDeprecatedOverrides, - ProblemsHolder holder) { - PsiMethod method = methodSignature.getMethod(); - PsiElement methodName = method.getNameIdentifier(); - if (methodName == null) return; - 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)); - 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); + checkDeprecated(refElement, elementToHighlight, rangeInElement, false, false, true, holder, false); } - - private 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)); - holder.registerProblem(elementToHighlight, description, ProblemHighlightType.LIKE_DEPRECATED, rangeInElement); - } -} \ No newline at end of file +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/DeprecationInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/DeprecationInspectionBase.java new file mode 100644 index 000000000000..87360fbdf4f4 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/DeprecationInspectionBase.java @@ -0,0 +1,286 @@ +/* + * Copyright 2000-2017 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.AnnotationUtil; +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.ProblemHighlightType; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.openapi.util.TextRange; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.*; +import com.intellij.psi.impl.JavaConstantExpressionEvaluator; +import com.intellij.psi.impl.PsiImplUtil; +import com.intellij.psi.infos.MethodCandidateInfo; +import com.intellij.psi.util.MethodSignatureBackedByPsiMethod; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.List; + +abstract class DeprecationInspectionBase extends BaseJavaBatchLocalInspectionTool { + + @Override + public boolean isEnabledByDefault() { + return true; + } + + protected 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; + private final boolean myForRemoval; + + DeprecationElementVisitor(@NotNull ProblemsHolder holder, + boolean ignoreInsideDeprecated, + boolean ignoreAbstractDeprecatedOverrides, + boolean ignoreImportStatements, + boolean ignoreMethodsOfDeprecated, + boolean forRemoval) { + myHolder = holder; + myIgnoreInsideDeprecated = ignoreInsideDeprecated; + myIgnoreAbstractDeprecatedOverrides = ignoreAbstractDeprecatedOverrides; + myIgnoreImportStatements = ignoreImportStatements; + myIgnoreMethodsOfDeprecated = ignoreMethodsOfDeprecated; + myForRemoval = forRemoval; + } + + @Override + public void visitReferenceElement(PsiJavaCodeReferenceElement reference) { + JavaResolveResult result = reference.advancedResolve(true); + PsiElement resolved = result.getElement(); + checkDeprecated(resolved, reference.getReferenceNameElement(), null, myIgnoreInsideDeprecated, myIgnoreImportStatements, + myIgnoreMethodsOfDeprecated, myHolder, myForRemoval); + } + + @Override + public void visitImportStaticStatement(PsiImportStaticStatement statement) { + PsiFile file = statement.getContainingFile(); + if (file instanceof PsiJavaFile && ((PsiJavaFile)file).getLanguageLevel().isAtLeast(LanguageLevel.JDK_1_9)) return; + final PsiJavaCodeReferenceElement importReference = statement.getImportReference(); + if (importReference != null) { + PsiElement resolved = importReference.resolve(); + checkDeprecated(resolved, importReference.getReferenceNameElement(), null, myIgnoreInsideDeprecated, + false, true, myHolder, myForRemoval); + } + } + + @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, myForRemoval); + } + } + } + + @Override + public void visitMethod(PsiMethod method) { + MethodSignatureBackedByPsiMethod methodSignature = MethodSignatureBackedByPsiMethod.create(method, PsiSubstitutor.EMPTY); + if (!method.isConstructor()) { + List superMethodSignatures = method.findSuperMethodSignaturesIncludingStatic(true); + checkMethodOverridesDeprecated(methodSignature, superMethodSignatures, myIgnoreAbstractDeprecatedOverrides, myHolder, myForRemoval); + } + else { + checkImplicitCallToSuper(method); + } + } + + private void checkImplicitCallToSuper(PsiMethod method) { + final PsiClass containingClass = method.getContainingClass(); + assert containingClass != null; + final PsiClass superClass = containingClass.getSuperClass(); + if (hasDefaultDeprecatedConstructor(superClass, myForRemoval)) { + 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) { + String description = + JavaErrorMessages.message(myForRemoval ? "marked.for.removal.default.constructor" : "deprecated.default.constructor", + superClass.getQualifiedName()); + ProblemHighlightType type = + asDeprecated && !myForRemoval ? ProblemHighlightType.LIKE_DEPRECATED : ProblemHighlightType.GENERIC_ERROR_OR_WARNING; + myHolder.registerProblem(nameIdentifier, description, type); + } + + @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, myForRemoval)) { + 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); + } + } + } + + @Override + public void visitRequiresStatement(PsiRequiresStatement statement) { + PsiJavaModuleReferenceElement refElement = statement.getReferenceElement(); + if (refElement != null) { + PsiPolyVariantReference ref = refElement.getReference(); + PsiElement target = ref != null ? ref.resolve() : null; + if (target instanceof PsiJavaModule && + isMarkedForRemoval((PsiJavaModule)target, myForRemoval) && + PsiImplUtil.isDeprecatedByAnnotation((PsiJavaModule)target)) { + String description = JavaErrorMessages.message(myForRemoval ? "marked.for.removal.symbol" : "deprecated.symbol", + HighlightMessageUtil.getSymbolName(target)); + ProblemHighlightType type = myForRemoval ? ProblemHighlightType.GENERIC_ERROR_OR_WARNING : ProblemHighlightType.LIKE_DEPRECATED; + myHolder.registerProblem(refElement, description, type); + } + } + } + } + + private static boolean hasDefaultDeprecatedConstructor(PsiClass superClass, boolean forRemoval) { + return superClass != null && Arrays.stream(superClass.getConstructors()) + .anyMatch(constructor -> constructor.getParameterList().getParametersCount() == 0 && + constructor.isDeprecated() && + isMarkedForRemoval(constructor, forRemoval)); + } + + //@top + static void checkMethodOverridesDeprecated(MethodSignatureBackedByPsiMethod methodSignature, + List superMethodSignatures, + boolean ignoreAbstractDeprecatedOverrides, ProblemsHolder holder, boolean forRemoval) { + PsiMethod method = methodSignature.getMethod(); + PsiElement methodName = method.getNameIdentifier(); + if (methodName == null) return; + 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() && isMarkedForRemoval(superMethod, forRemoval)) { + String description = JavaErrorMessages.message(forRemoval ? "overrides.marked.for.removal.method" : "overrides.deprecated.method", + HighlightMessageUtil.getSymbolName(aClass, PsiSubstitutor.EMPTY)); + ProblemHighlightType type = forRemoval ? ProblemHighlightType.GENERIC_ERROR_OR_WARNING : ProblemHighlightType.LIKE_DEPRECATED; + holder.registerProblem(methodName, description, type); + } + } + } + + static void checkDeprecated(PsiElement refElement, + PsiElement elementToHighlight, + @Nullable TextRange rangeInElement, + boolean ignoreInsideDeprecated, + boolean ignoreImportStatements, + boolean ignoreMethodsOfDeprecated, + ProblemsHolder holder, + boolean forRemoval) { + if (!(refElement instanceof PsiDocCommentOwner) || !isMarkedForRemoval((PsiDocCommentOwner)refElement, forRemoval)) { + return; + } + + if (!((PsiDocCommentOwner)refElement).isDeprecated()) { + if (!ignoreMethodsOfDeprecated) { + checkDeprecated(((PsiDocCommentOwner)refElement).getContainingClass(), elementToHighlight, rangeInElement, + ignoreInsideDeprecated, ignoreImportStatements, false, holder, forRemoval); + } + 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(forRemoval ? "marked.for.removal.symbol" : "deprecated.symbol", + HighlightMessageUtil.getSymbolName(refElement, PsiSubstitutor.EMPTY)); + + ProblemHighlightType type = forRemoval ? ProblemHighlightType.GENERIC_ERROR_OR_WARNING : ProblemHighlightType.LIKE_DEPRECATED; + holder.registerProblem(elementToHighlight, description, type, rangeInElement); + } + + private static boolean isMarkedForRemoval(PsiModifierListOwner superMethod, boolean forRemoval) { + return isMarkedForRemoval(superMethod) == forRemoval; + } + + private static boolean isMarkedForRemoval(@Nullable PsiModifierListOwner element) { + PsiAnnotation annotation = AnnotationUtil.findAnnotation(element, CommonClassNames.JAVA_LANG_DEPRECATED); + if (annotation == null) { + return false; + } + PsiAnnotationMemberValue value = annotation.findAttributeValue("forRemoval"); + Object result = null; + if (value instanceof PsiLiteral) { + result = ((PsiLiteral)value).getValue(); + } + else if (value instanceof PsiExpression) { + result = JavaConstantExpressionEvaluator.computeConstantExpression((PsiExpression)value, false); + } + return result instanceof Boolean && ((Boolean)result); + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/MarkedForRemovalInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/MarkedForRemovalInspection.java new file mode 100644 index 000000000000..2855651ae8e6 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/MarkedForRemovalInspection.java @@ -0,0 +1,68 @@ +/* + * Copyright 2000-2017 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.codeInspection.DeprecationUtil; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.PsiElementVisitor; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiJavaFile; +import org.intellij.lang.annotations.Pattern; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +public class MarkedForRemovalInspection extends DeprecationInspectionBase { + + + @Override + @NotNull + public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) { + PsiFile file = holder.getFile(); + if (file instanceof PsiJavaFile && ((PsiJavaFile)file).getLanguageLevel().isAtLeast(LanguageLevel.JDK_1_9)) { + return new DeprecationElementVisitor(holder, false, false, + false, false, + true); + } + return PsiElementVisitor.EMPTY_VISITOR; + } + + @Override + @NotNull + public String getDisplayName() { + return DeprecationUtil.FOR_REMOVAL_DISPLAY_NAME; + } + + @Override + @NotNull + public String getGroupDisplayName() { + return ""; + } + + @Override + @NotNull + public String getShortName() { + return DeprecationUtil.FOR_REMOVAL_SHORT_NAME; + } + + @Pattern(VALID_ID_PATTERN) + @Override + @NotNull + @NonNls + public String getID() { + return DeprecationUtil.FOR_REMOVAL_ID; + } +} diff --git a/java/java-psi-impl/src/messages/JavaErrorMessages.properties b/java/java-psi-impl/src/messages/JavaErrorMessages.properties index 4e17654c03f6..697d2d8db176 100644 --- a/java/java-psi-impl/src/messages/JavaErrorMessages.properties +++ b/java/java-psi-impl/src/messages/JavaErrorMessages.properties @@ -167,6 +167,9 @@ constructor.call.must.be.first.statement=Call to ''{0}'' must be first statement direct.abstract.method.access=Abstract method ''{0}'' cannot be accessed directly unrelated.overriding.methods.return.types=methods have unrelated return types overrides.deprecated.method=Overrides deprecated method in ''{0}'' +overrides.marked.for.removal.method=Overrides method marked for removal in ''{0}'' +deprecated.default.constructor=Default constructor in ''{0}'' is deprecated +marked.for.removal.default.constructor=Default constructor in ''{0}'' is deprecated and marked for removal recursive.constructor.invocation=Recursive constructor invocation wrong.constructor.arguments=''{0}'' cannot be applied to ''{1}'' cannot.resolve.constructor=Cannot resolve constructor ''{0}'' @@ -275,6 +278,7 @@ numeric.overflow.in.expression=Numeric overflow in expression static.member.accessed.via.instance.reference=Static member ''{0}.{1}'' accessed via instance reference unresolved.label=Undefined label: ''{0}'' deprecated.symbol=''{0}'' is deprecated +marked.for.removal.symbol=''{0}'' is deprecated and marked for removal qualifier.must.be.expression=Qualifier must be an expression cannot.resolve.symbol=Cannot resolve symbol ''{0}'' class.is.already.defined.in.single.static.import=Class ''{0}'' is already defined in a single static import diff --git a/java/java-tests/testData/inspection/forRemoval/forRemovalClass/expected.xml b/java/java-tests/testData/inspection/forRemoval/forRemovalClass/expected.xml new file mode 100644 index 000000000000..8c6568a99b7f --- /dev/null +++ b/java/java-tests/testData/inspection/forRemoval/forRemovalClass/expected.xml @@ -0,0 +1,51 @@ + + + + + Test.java + 9 + 'Test' is deprecated and marked for removal + + + + Test.java + 10 + 'Test' is deprecated and marked for removal + + + + Test.java + 15 + 'Test' is deprecated and marked for removal + + + + Test.java + 16 + 'Test' is deprecated and marked for removal + + + + Test.java + 21 + 'Test' is deprecated and marked for removal + + + + Test.java + 22 + 'Test' is deprecated and marked for removal + + + + Test.java + 27 + 'Test' is deprecated and marked for removal + + + + Test.java + 28 + 'Test' is deprecated and marked for removal + + diff --git a/java/java-tests/testData/inspection/forRemoval/forRemovalClass/src/Test.java b/java/java-tests/testData/inspection/forRemoval/forRemovalClass/src/Test.java new file mode 100644 index 000000000000..2226534c2e5b --- /dev/null +++ b/java/java-tests/testData/inspection/forRemoval/forRemovalClass/src/Test.java @@ -0,0 +1,36 @@ +@Deprecated(forRemoval = true) +class Test { + public Test() { + } +} + +class Usages { + void normal() { + Test t = + new Test(); + } + + @Deprecated() + void normallyDeprecated() { + Test t = + new Test(); + } + + @Deprecated(forRemoval = 1 + 1 == 2) + void deprecatedForRemoval() { + Test t = + new Test(); + } + + @SuppressWarnings("deprecation") + void suppressDeprecation() { + Test t = + new Test(); + } + + @SuppressWarnings("removal") + void suppressRemoval() { + Test t = + new Test(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/forRemoval/forRemovalDefaultConstructorInSuper/expected.xml b/java/java-tests/testData/inspection/forRemoval/forRemovalDefaultConstructorInSuper/expected.xml new file mode 100644 index 000000000000..d5af04c66f68 --- /dev/null +++ b/java/java-tests/testData/inspection/forRemoval/forRemovalDefaultConstructorInSuper/expected.xml @@ -0,0 +1,21 @@ + + + + Test.java + 5 + Default constructor in C is deprecated and marked for removal + + + + Test.java + 10 + Default constructor in C is deprecated and marked for removal + + + + Test.java + 15 + Default constructor in C is deprecated and marked for removal + + + diff --git a/java/java-tests/testData/inspection/forRemoval/forRemovalDefaultConstructorInSuper/src/Test.java b/java/java-tests/testData/inspection/forRemoval/forRemovalDefaultConstructorInSuper/src/Test.java new file mode 100644 index 000000000000..a60e5ca075e5 --- /dev/null +++ b/java/java-tests/testData/inspection/forRemoval/forRemovalDefaultConstructorInSuper/src/Test.java @@ -0,0 +1,22 @@ +class C { + @Deprecated(forRemoval=true) C() { } +} + +class D extends C { +} + +class P { + void normal() { + new C(){}; + } + + @SuppressWarnings("deprecation") + void suppressDeprecation() { + new C(){}; + } + + @SuppressWarnings("removal") + void suppressRemoval() { + new C(){}; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/forRemoval/forRemovalField/expected.xml b/java/java-tests/testData/inspection/forRemoval/forRemovalField/expected.xml new file mode 100644 index 000000000000..81232b7f2ef4 --- /dev/null +++ b/java/java-tests/testData/inspection/forRemoval/forRemovalField/expected.xml @@ -0,0 +1,26 @@ + + + + Test.java + 6 + 'foo' is deprecated and marked for removal + + + + Test.java + 11 + 'foo' is deprecated and marked for removal + + + + Test.java + 16 + 'foo' is deprecated and marked for removal + + + + Test.java + 21 + 'foo' is deprecated and marked for removal + + diff --git a/java/java-tests/testData/inspection/forRemoval/forRemovalField/src/Test.java b/java/java-tests/testData/inspection/forRemoval/forRemovalField/src/Test.java new file mode 100644 index 000000000000..fba5d28672c9 --- /dev/null +++ b/java/java-tests/testData/inspection/forRemoval/forRemovalField/src/Test.java @@ -0,0 +1,28 @@ +public class Test { + @Deprecated(forRemoval = true) + int foo; + + int normal() { + return foo; + } + + @Deprecated() + int normallyDeprecated() { + return foo; + } + + @Deprecated(forRemoval = 1 + 1 == 2) + int deprecatedForRemoval() { + return foo; + } + + @SuppressWarnings("deprecation") + int suppressDeprecation() { + return foo; + } + + @SuppressWarnings("removal") + int suppressRemoval() { + return foo; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/forRemoval/forRemovalMethod/expected.xml b/java/java-tests/testData/inspection/forRemoval/forRemovalMethod/expected.xml new file mode 100644 index 000000000000..57fc028a09e2 --- /dev/null +++ b/java/java-tests/testData/inspection/forRemoval/forRemovalMethod/expected.xml @@ -0,0 +1,26 @@ + + + + Test.java + 6 + 'foo()' is deprecated and marked for removal + + + + Test.java + 11 + 'foo()' is deprecated and marked for removal + + + + Test.java + 16 + 'foo()' is deprecated and marked for removal + + + + Test.java + 21 + 'foo()' is deprecated and marked for removal + + diff --git a/java/java-tests/testData/inspection/forRemoval/forRemovalMethod/src/Test.java b/java/java-tests/testData/inspection/forRemoval/forRemovalMethod/src/Test.java new file mode 100644 index 000000000000..6dbfef815411 --- /dev/null +++ b/java/java-tests/testData/inspection/forRemoval/forRemovalMethod/src/Test.java @@ -0,0 +1,28 @@ +public class Test { + @Deprecated(forRemoval = true) + void foo() {} + + void normal() { + foo(); + } + + @Deprecated() + void normallyDeprecated() { + foo(); + } + + @Deprecated(forRemoval = 1 + 1 == 2) + void deprecatedForRemoval() { + foo(); + } + + @SuppressWarnings("deprecation") + void suppressDeprecation() { + foo(); + } + + @SuppressWarnings("removal") + void suppressRemoval() { + foo(); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/forRemoval/forRemovalOverride/expected.xml b/java/java-tests/testData/inspection/forRemoval/forRemovalOverride/expected.xml new file mode 100644 index 000000000000..4643be89131b --- /dev/null +++ b/java/java-tests/testData/inspection/forRemoval/forRemovalOverride/expected.xml @@ -0,0 +1,14 @@ + + + + Test.java + 7 + Overrides method marked for removal in 'Test' + + + + Test.java + 8 + 'foo()' is deprecated and marked for removal + + diff --git a/java/java-tests/testData/inspection/forRemoval/forRemovalOverride/src/Test.java b/java/java-tests/testData/inspection/forRemoval/forRemovalOverride/src/Test.java new file mode 100644 index 000000000000..5240691b84c8 --- /dev/null +++ b/java/java-tests/testData/inspection/forRemoval/forRemovalOverride/src/Test.java @@ -0,0 +1,17 @@ +public class Test { + @Deprecated(forRemoval = true) + void foo() {} +} + +class Overrides extends Test { + void foo() { + super.foo(); + } +} + +@SuppressWarnings("removal") +class Suppressed extends Test { + void foo() { + super.foo(); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ModuleHighlightingTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ModuleHighlightingTest.kt index 82e4c78ab718..fd12d00eb018 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ModuleHighlightingTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/daemon/ModuleHighlightingTest.kt @@ -17,6 +17,7 @@ package com.intellij.java.codeInsight.daemon import com.intellij.codeInsight.daemon.impl.JavaHighlightInfoTypes import com.intellij.codeInspection.deprecation.DeprecationInspection +import com.intellij.codeInspection.deprecation.MarkedForRemovalInspection import com.intellij.java.testFramework.fixtures.LightJava9ModulesCodeInsightFixtureTestCase import com.intellij.java.testFramework.fixtures.MultiModuleJava9ProjectDescriptor.ModuleDescriptor.* import com.intellij.openapi.util.TextRange @@ -296,11 +297,17 @@ class ModuleHighlightingTest : LightJava9ModulesCodeInsightFixtureTestCase() { } fun testDeprecations() { - myFixture.enableInspections(DeprecationInspection()) + myFixture.enableInspections(DeprecationInspection(), MarkedForRemovalInspection()) addFile("module-info.java", "@Deprecated module M2 { }", M2) highlight("""module M { requires M2; }""") } + fun testMarkedForRemoval() { + myFixture.enableInspections(DeprecationInspection(), MarkedForRemovalInspection()) + addFile("module-info.java", "@Deprecated(forRemoval=true) module M2 { }", M2) + highlight("""module M { requires M2; }""") + } + fun testPackageConflicts() { addFile("pkg/collision2/C2.java", "package pkg.collision2;\npublic class C2 { }", M2) addFile("pkg/collision4/C4.java", "package pkg.collision4;\npublic class C4 { }", M4) diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/MarkedForRemovalInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/MarkedForRemovalInspectionTest.java new file mode 100644 index 000000000000..e00f1a0df826 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/MarkedForRemovalInspectionTest.java @@ -0,0 +1,72 @@ +/* + * Copyright 2000-2017 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.java.codeInspection; + +import com.intellij.JavaTestUtil; +import com.intellij.codeInspection.deprecation.MarkedForRemovalInspection; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.roots.LanguageLevelProjectExtension; +import com.intellij.openapi.roots.ModuleRootModificationUtil; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.testFramework.IdeaTestUtil; +import com.intellij.testFramework.InspectionTestCase; + +/** + * @author max + */ +public class MarkedForRemovalInspectionTest extends InspectionTestCase { + @Override + protected String getTestDataPath() { + return JavaTestUtil.getJavaTestDataPath() + "/inspection"; + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + LanguageLevelProjectExtension.getInstance(myJavaFacade.getProject()).setLanguageLevel(LanguageLevel.JDK_1_9); + ModuleRootModificationUtil.setModuleSdk(getModule(), getTestProjectSdk()); + } + + @Override + protected Sdk getTestProjectSdk() { + return IdeaTestUtil.getMockJdk9(); + } + + private void doTest() { + doTest("forRemoval/" + getTestName(true), new MarkedForRemovalInspection()); + } + + public void testForRemovalClass() { + doTest(); + } + + public void testForRemovalField() { + doTest(); + } + + public void testForRemovalMethod() { + doTest(); + } + + public void testForRemovalOverride() { + doTest(); + } + + public void testForRemovalDefaultConstructorInSuper() { + doTest(); + } +} diff --git a/platform/analysis-impl/src/com/intellij/codeInspection/DeprecationUtil.java b/platform/analysis-impl/src/com/intellij/codeInspection/DeprecationUtil.java index 9b07c6829983..8ba60a4258a7 100644 --- a/platform/analysis-impl/src/com/intellij/codeInspection/DeprecationUtil.java +++ b/platform/analysis-impl/src/com/intellij/codeInspection/DeprecationUtil.java @@ -21,4 +21,8 @@ public class DeprecationUtil { @NonNls public static final String DEPRECATION_SHORT_NAME = "Deprecation"; @NonNls public static final String DEPRECATION_DISPLAY_NAME = InspectionsBundle.message("inspection.deprecated.display.name"); @NonNls public static final String DEPRECATION_ID = "deprecation"; + + @NonNls public static final String FOR_REMOVAL_SHORT_NAME = "MarkedForRemoval"; + @NonNls public static final String FOR_REMOVAL_DISPLAY_NAME = InspectionsBundle.message("inspection.marked.for.removal.display.name"); + @NonNls public static final String FOR_REMOVAL_ID = "removal"; } diff --git a/platform/platform-resources-en/src/messages/InspectionsBundle.properties b/platform/platform-resources-en/src/messages/InspectionsBundle.properties index 75dafabeb317..793322c920ea 100644 --- a/platform/platform-resources-en/src/messages/InspectionsBundle.properties +++ b/platform/platform-resources-en/src/messages/InspectionsBundle.properties @@ -100,6 +100,7 @@ dataflow.message.array.index.out.of.bounds=Array index is out of bounds #deprecated inspection.deprecated.display.name=Deprecated API usage +inspection.marked.for.removal.display.name=Usage of API marked for removal #empty method inspection.empty.method.display.name=Empty method inspection.empty.method.delete.quickfix=Delete unnecessary method(s) diff --git a/resources-en/src/inspectionDescriptions/MarkedForRemoval.html b/resources-en/src/inspectionDescriptions/MarkedForRemoval.html new file mode 100644 index 000000000000..3a3daac15719 --- /dev/null +++ b/resources-en/src/inspectionDescriptions/MarkedForRemoval.html @@ -0,0 +1,5 @@ + + +This inspection reports usages of deprecated classes, fields, and methods which are marked for removal (Java 9+). + + diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 41d84b6a89ed..498bc702db54 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -704,9 +704,15 @@ implementationClass="com.intellij.codeInspection.javaDoc.JavadocHtmlLintInspection"/> - +