diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/apiUsage/ApiUsageDetector.java b/java/java-analysis-impl/src/com/intellij/codeInspection/apiUsage/ApiUsageDetector.java deleted file mode 100644 index 19ee2d2d9211..000000000000 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/apiUsage/ApiUsageDetector.java +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -package com.intellij.codeInspection.apiUsage; - -import com.intellij.psi.*; -import org.jetbrains.annotations.NotNull; - -/** - * Interface containing all types of API usages events emitted by {@link ApiUsageVisitorBase}. - * - * The most common event is {@link #processReference(PsiReference)}, - * which reports a reference to a class, method, field, or any other API. - * Implicit usages of APIs, which are not present in source code by any reference, are handled by remaining methods. - */ -interface ApiUsageDetector { - - /** - * Checks whether references from this PSI element to other PSI elements must be processed. - *
- * For example, it may be overridden to ignore references of elements residing in import statements. - */ - default boolean shouldProcessReferences(@NotNull PsiElement element) { - return true; - } - - /** - * Reference to API is found. - */ - void processReference(@NotNull PsiReference reference); - - /** - * Invocation of a constructor is found in {@code new} expression. - * {@code instantiatedClass} is a reference to a class being instantiated. - *
- * When anonymous class is instantiated, the {@code instantiatedClass} is the base class. - */ - default void processConstructorInvocation(@NotNull PsiJavaCodeReferenceElement instantiatedClass, @NotNull PsiMethod constructor) { - processReference(instantiatedClass); - } - - /** - * Invocation of the default constructor of a class is found in a {@code new} expression. - *
{@code
-   * class Test {
-   *   //The default implicit constructor
-   * }
-   *
-   * void foo() {
-   *   Object o = new Test(); //The default constructor invocation
-   * }}
- * {@code instantiatedClass} is a reference to a class being instantiated. - *
- * When anonymous class is instantiated, the {@code instantiatedClass} is the base class. - */ - default void processDefaultConstructorInvocation(@NotNull PsiJavaCodeReferenceElement instantiatedClass) {} - - /** - * Invocation of the empty constructor of a super class is found in the default constructor of a subclass, which is not an anonymous class. - *
-   * {@code
-   * class Subclass extends Super {
-   *   
-   * }}
-   * 
- */ - default void processEmptyConstructorOfSuperClassImplicitInvocationAtSubclassDeclaration( - @NotNull PsiClass subclass, - @NotNull PsiClass superClass - ) { } - - /** - * Implicit invocation of an empty constructor of a super class is found in a constructor of a subclass. - * The empty constructor is either a constructor with no arguments, or the default constructor. - * - *
-   * {@code
-   * class Subclass extends Super {
-   *    Subclass() {
-   *       
-   *    }
-   * }}
-   * 
- */ - default void processEmptyConstructorOfSuperClassImplicitInvocationAtSubclassConstructor( - @NotNull PsiClass superClass, - @NotNull PsiMethod subclassConstructor - ) {} - - /** - * Method of a super class {@code overriddenMethod} is overridden by a subclass' {@code method}. - * - * The super class is {@code overriddenMethod.containingClass}, and the subclass is {@code method.containingClass}. - */ - default void processMethodOverriding(@NotNull PsiMethod method, @NotNull PsiMethod overriddenMethod) {} - -} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/apiUsage/ApiUsageProcessor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/apiUsage/ApiUsageProcessor.java new file mode 100644 index 000000000000..5d64e85f68ff --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/apiUsage/ApiUsageProcessor.java @@ -0,0 +1,68 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.codeInspection.apiUsage; + +import com.intellij.psi.*; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.uast.UClass; +import org.jetbrains.uast.UElement; +import org.jetbrains.uast.UExpression; +import org.jetbrains.uast.UMethod; + +/** + * Processes usages of APIs in source code of UAST-supporting languages, which are detected by {@link ApiUsageUastVisitor}. + */ +@ApiStatus.Experimental +public interface ApiUsageProcessor { + + /** + * Process reference to a class, method (not constructor), field or any other API element found in source code. + * + * @param sourceNode can be used to get actual PSI element to highlight in inspections via {@code sourceNode.sourcePsi} + * @param target resolved API element + * @param qualifier is optionally a qualified expression of the reference. + */ + default void processReference(@NotNull UElement sourceNode, @NotNull PsiModifierListOwner target, @Nullable UExpression qualifier) { } + + /** + * Process reference to an imported API element. + * + * @param sourceNode can be used to get actual PSI element to highlight in inspections via {@code sourceNode.sourcePsi} + * @param target resolved API element being imported + */ + default void processImportReference(@NotNull UElement sourceNode, @NotNull PsiModifierListOwner target) { } + + /** + * Process constructor invocation of a class.
+ * The invoked constructor may be the default constructor, which is not declared in source code.
+ * The constructor invocation may be implicit: in declaration of a subclass with no constructors defined, + * in declaration of an anonymous class, as implicit {@code super()} invocation in subclass' constructor, etc. + * + * @param sourceNode can be used to get actual PSI element to highlight in inspections via {@code sourceNode.sourcePsi} + * @param instantiatedClass class being instantiated + * @param constructor PSI constructor defined in source code, or {@code null} if the default constructor is being invoked. + * @param subclassDeclaration declaration of a subclass or anonymous subclass where the constructor invocation's happens, + * or {@code null} if the constructor is being invoked explicitly. + */ + default void processConstructorInvocation(@NotNull UElement sourceNode, + @NotNull PsiClass instantiatedClass, + @Nullable PsiMethod constructor, + @Nullable UClass subclassDeclaration) {} + + /** + * Process overriding of a super class' method. + * + * @param method method that overrides the parent's method. {@code method.uastAnchor.sourcePsi} can be be used to highlight name declaration. + * @param overriddenMethod super class' method being overridden + */ + default void processMethodOverriding(@NotNull UMethod method, @NotNull PsiMethod overriddenMethod) {} + + /** + * Process reference to a Java module found in {@code module-info.java} file. + * + * @param javaModuleReference Java module reference + * @param target resolved Java module + */ + default void processJavaModuleReference(@NotNull PsiJavaModuleReference javaModuleReference, @NotNull PsiJavaModule target) {} +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/apiUsage/ApiUsageUastVisitor.kt b/java/java-analysis-impl/src/com/intellij/codeInspection/apiUsage/ApiUsageUastVisitor.kt new file mode 100644 index 000000000000..66ba2c5a65b3 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/apiUsage/ApiUsageUastVisitor.kt @@ -0,0 +1,323 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.codeInspection.apiUsage + +import com.intellij.psi.* +import org.jetbrains.uast.* +import org.jetbrains.uast.visitor.AbstractUastNonRecursiveVisitor + +/** + * Non-recursive UAST visitor that detects usages of APIs in source code of UAST-supporting languages + * and reports them via [ApiUsageProcessor] interface. + */ +class ApiUsageUastVisitor(private val apiUsageProcessor: ApiUsageProcessor) : AbstractUastNonRecursiveVisitor() { + + override fun visitSimpleNameReferenceExpression(node: USimpleNameReferenceExpression): Boolean { + if (maybeProcessReferenceInsideImportStatement(node)) { + return true + } + if (maybeProcessJavaModuleReference(node)) { + return true + } + if (isMethodReferenceOfCallExpression(node) + || isSelectorOfQualifiedReference(node) + || isKotlinConstructorCalleeInObjectDeclarationReference(node) + ) { + return true + } + val resolved = node.resolve() + if (resolved is PsiModifierListOwner) { + apiUsageProcessor.processReference(node, resolved, null) + return true + } + if (resolved == null) { + /* + * KT-30522 UAST for Kotlin: reference to annotation parameter resolves to null. + */ + val psiReferences = node.sourcePsi?.references.orEmpty() + for (psiReference in psiReferences) { + val target = psiReference.resolve()?.toUElement()?.javaPsi as? PsiAnnotationMethod + if (target != null) { + apiUsageProcessor.processReference(node, target, null) + return true + } + } + } + return true + } + + override fun visitQualifiedReferenceExpression(node: UQualifiedReferenceExpression): Boolean { + if (maybeProcessReferenceInsideImportStatement(node)) { + return true + } + if (node.sourcePsi is PsiMethodCallExpression || node.selector is UCallExpression) { + //UAST for Java produces UQualifiedReferenceExpression for both PsiMethodCallExpression and PsiReferenceExpression inside it + //UAST for Kotlin produces UQualifiedReferenceExpression with UCallExpression as selector + return true + } + val uastParent = node.uastParent + if (uastParent is UCallExpression && uastParent.kind == UastCallKind.CONSTRUCTOR_CALL) { + //Constructor call will be handled in visitCallExpression(). + return true + } + val resolved = node.resolve() + if (resolved is PsiMember) { + apiUsageProcessor.processReference(node.selector, resolved, node.receiver) + } + return true + } + + override fun visitCallableReferenceExpression(node: UCallableReferenceExpression): Boolean { + val resolve = node.resolve() + if (resolve is PsiMember) { + val sourceNode = node.referenceNameElement ?: node + apiUsageProcessor.processReference(sourceNode, resolve, node.qualifierExpression) + } + return true + } + + override fun visitCallExpression(node: UCallExpression): Boolean { + if (node.sourcePsi is PsiExpressionStatement) { + //UAST for Java generates UCallExpression for PsiExpressionStatement and PsiMethodCallExpression inside it. + return true + } + + val psiMethod = node.resolve() + val sourceNode = node.methodIdentifier ?: node.classReference?.referenceNameElement ?: node.classReference ?: node + if (psiMethod != null) { + val containingClass = psiMethod.containingClass + if (psiMethod.isConstructor) { + if (containingClass != null) { + apiUsageProcessor.processConstructorInvocation(sourceNode, containingClass, psiMethod, null) + } + } + else { + apiUsageProcessor.processReference(sourceNode, psiMethod, node.receiver) + } + return true + } + + if (node.methodName == "super" && node.valueArgumentCount == 0) { + //Java does not resolve constructor for subclass constructor's "super()" statement + // if the superclass has the default constructor, which is not declared in source code and lacks PsiMethod. + val superClass = node.getContainingUClass()?.javaPsi?.superClass ?: return true + apiUsageProcessor.processConstructorInvocation(sourceNode, superClass, null, null) + return true + } + + val classReference = node.classReference + if (classReference != null) { + val resolvedClass = classReference.resolve() as? PsiClass + if (resolvedClass != null) { + if (node.kind == UastCallKind.CONSTRUCTOR_CALL) { + val emptyConstructor = resolvedClass.constructors.find { it.parameterList.isEmpty } + apiUsageProcessor.processConstructorInvocation(sourceNode, resolvedClass, emptyConstructor, null) + } + else { + apiUsageProcessor.processReference(sourceNode, resolvedClass, node.receiver) + } + } + return true + } + return true + } + + override fun visitObjectLiteralExpression(node: UObjectLiteralExpression): Boolean { + val psiMethod = node.resolve() + val sourceNode = node.methodIdentifier ?: node.classReference ?: node.declaration.uastSuperTypes.firstOrNull() ?: node + if (psiMethod != null) { + val containingClass = psiMethod.containingClass + if (psiMethod.isConstructor) { + if (containingClass != null) { + apiUsageProcessor.processConstructorInvocation(sourceNode, containingClass, psiMethod, node.declaration) + } + } + else { + apiUsageProcessor.processReference(sourceNode, psiMethod, node.receiver) + } + } + else { + maybeProcessImplicitConstructorInvocationAtSubclassDeclaration(sourceNode, node.declaration) + } + return true + } + + override fun visitElement(node: UElement): Boolean { + if (node is UNamedExpression) { + //IDEA-209279: UAstVisitor lacks a hook for UNamedExpression + //KT-30522: Kotlin does not generate UNamedExpression for annotation's parameters. + processNamedExpression(node) + return true + } + return super.visitElement(node) + } + + override fun visitClass(node: UClass): Boolean { + val uastAnchor = node.uastAnchor + if (uastAnchor == null || node is UAnonymousClass || node.javaPsi is PsiTypeParameter) { + return true + } + maybeProcessImplicitConstructorInvocationAtSubclassDeclaration(uastAnchor, node) + return true + } + + override fun visitMethod(node: UMethod): Boolean { + if (node.isConstructor) { + checkImplicitCallOfSuperEmptyConstructor(node) + } + else { + checkMethodOverriding(node) + } + return true + } + + private fun maybeProcessJavaModuleReference(node: UElement): Boolean { + val sourcePsi = node.sourcePsi + val psiParent = sourcePsi?.parent + if (sourcePsi is PsiIdentifier && psiParent is PsiJavaModuleReferenceElement && sourcePsi == psiParent.lastChild) { + val reference = psiParent.reference + val target = reference?.resolve() + if (target != null) { + apiUsageProcessor.processJavaModuleReference(reference, target) + } + return true + } + return false + } + + private fun maybeProcessReferenceInsideImportStatement(node: UReferenceExpression): Boolean { + if (isInsideImportStatement(node)) { + fun isKotlin(node: UElement): Boolean { + val sourcePsi = node.sourcePsi ?: return false + return sourcePsi.language.id.contains("kotlin", true) + } + + if (isKotlin(node)) { + /* + UAST for Kotlin 1.3.30 import statements have bugs. + + KT-30546: some references resolve to nulls. + KT-30957: simple references for members resolve incorrectly to class declaration, not to the member declaration + + Therefore, we have to fallback to base PSI for Kotlin references. + */ + val resolved = node.sourcePsi?.reference?.resolve() + val target = (resolved?.toUElement()?.javaPsi ?: resolved) as? PsiModifierListOwner + if (target != null) { + apiUsageProcessor.processImportReference(node, target) + } + } + else { + val resolved = node.resolve() as? PsiModifierListOwner + if (resolved != null) { + apiUsageProcessor.processImportReference(node.referenceNameElement ?: node, resolved) + } + } + return true + } + return false + } + + private fun isInsideImportStatement(node: UElement) = + node.skipParentOfType(true, UQualifiedReferenceExpression::class.java) is UImportStatement + + private fun maybeProcessImplicitConstructorInvocationAtSubclassDeclaration(sourceNode: UElement, subclassDeclaration: UClass) { + val hasExplicitConstructor = subclassDeclaration.methods.any { it.isConstructor } + if (!hasExplicitConstructor) { + val instantiatedClass = subclassDeclaration.javaPsi.superClass ?: return + val constructor = instantiatedClass.constructors.find { it.parameterList.isEmpty } + apiUsageProcessor.processConstructorInvocation(sourceNode, instantiatedClass, constructor, subclassDeclaration) + } + } + + private fun processNamedExpression(node: UNamedExpression) { + val sourcePsi = node.sourcePsi + val annotationMethod = sourcePsi?.reference?.resolve() as? PsiMember + if (annotationMethod != null) { + val sourceNode = (sourcePsi as? PsiNameValuePair)?.nameIdentifier?.toUElement() ?: node + apiUsageProcessor.processReference(sourceNode, annotationMethod, null) + } + } + + private fun checkImplicitCallOfSuperEmptyConstructor(constructor: UMethod) { + val containingUClass = constructor.getContainingUClass() ?: return + val superClass = containingUClass.javaPsi.superClass ?: return + val uastBody = constructor.uastBody + val uastAnchor = constructor.uastAnchor + if (uastAnchor != null && isImplicitCallOfSuperConstructorFromSubclassConstructorBody(uastBody)) { + val emptyConstructor = superClass.constructors.find { it.parameterList.isEmpty } + apiUsageProcessor.processConstructorInvocation(uastAnchor, superClass, emptyConstructor, null) + } + } + + private fun isImplicitCallOfSuperConstructorFromSubclassConstructorBody(constructorBody: UExpression?): Boolean { + if (constructorBody == null || constructorBody is UBlockExpression && constructorBody.expressions.isEmpty()) { + //Empty constructor body => implicit super() call. + return true + } + val firstExpression = (constructorBody as? UBlockExpression)?.expressions?.firstOrNull() ?: constructorBody + if (firstExpression !is UCallExpression) { + //First expression is not super() => the super() is implicit. + return true + } + return firstExpression.methodName != "super" + } + + private fun checkMethodOverriding(node: UMethod) { + val method = node.javaPsi + val superMethods = method.findSuperMethods(true) + for (superMethod in superMethods) { + apiUsageProcessor.processMethodOverriding(node, superMethod) + } + } + + /** + * UAST for Kotlin generates UAST tree with UnknownKotlinExpression element, for expression "object : BaseClass() { ... }". + * + * ``` + * UObjectLiteralExpression + * UnknownKotlinExpression (CONSTRUCTOR_CALLEE) + * UTypeReferenceExpression (BaseClass) + * USimpleNameReferenceExpression (BaseClass) + * ``` + * + * This method checks that [expression] is a simple reference from the super class' constructor invocation. + * If so, we have to ignore its processing in "visitSimpleNameReferenceExpression" because it will be processed in "visitObjectLiteralExpression". + */ + private fun isKotlinConstructorCalleeInObjectDeclarationReference(expression: USimpleNameReferenceExpression): Boolean { + val parent1 = expression.uastParent + val parent2 = parent1?.uastParent + val parent3 = parent2?.uastParent + return parent3 is UObjectLiteralExpression + && parent1 is UTypeReferenceExpression + && parent2.asLogString().contains("CONSTRUCTOR_CALLEE") + } + + private fun isSelectorOfQualifiedReference(expression: USimpleNameReferenceExpression): Boolean { + val qualifiedReference = expression.uastParent as? UQualifiedReferenceExpression ?: return false + return haveSameSourceElement(expression, qualifiedReference.selector) + } + + private fun isMethodReferenceOfCallExpression(expression: USimpleNameReferenceExpression): Boolean { + val callExpression = expression.uastParent as? UCallExpression ?: return false + val expressionNameElement = expression.referenceNameElement + if (expressionNameElement == null && expression.identifier == "super") { + //UAST for Java returns null for "referenceNameElement" of "super()" statement : IDEA-210418 + return true + } + val methodIdentifier = callExpression.methodIdentifier + val classReference = callExpression.classReference + + return if (methodIdentifier != null) { + haveSameSourceElement(expressionNameElement, methodIdentifier) + } + else { + haveSameSourceElement(expressionNameElement, classReference?.referenceNameElement ?: classReference) + } + } + + private fun haveSameSourceElement(element1: UElement?, element2: UElement?): Boolean { + if (element1 == null || element2 == null) return false + val sourcePsi1 = element1.sourcePsi + return sourcePsi1 != null && sourcePsi1 == element2.sourcePsi + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/apiUsage/ApiUsageVisitorBase.kt b/java/java-analysis-impl/src/com/intellij/codeInspection/apiUsage/ApiUsageVisitorBase.kt deleted file mode 100644 index a8d04f5d28a0..000000000000 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/apiUsage/ApiUsageVisitorBase.kt +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -package com.intellij.codeInspection.apiUsage - -import com.intellij.psi.* -import com.intellij.psi.impl.source.tree.LeafPsiElement -import com.intellij.psi.infos.MethodCandidateInfo - -/** - * Non-recursive PSI visitor that detects usages of APIs and reports them via [ApiUsageDetector] interface. - * This visitor is mainly designed for Java, but may basically work with any language, including Kotlin. - * Inheritors should provide at least implementation of [processReference]. - */ -abstract class ApiUsageVisitorBase : PsiElementVisitor(), ApiUsageDetector { - - final override fun visitElement(element: PsiElement) { - super.visitElement(element) - if (element is PsiLanguageInjectionHost || element is LeafPsiElement) { - //Better performance. - return - } - - when (element) { - is PsiClass -> visitClass(element) - is PsiMethod -> visitMethod(element) - is PsiNewExpression -> visitNewExpression(element) - else -> processReferences(element) - } - } - - private fun processReferences(element: PsiElement) { - if (shouldProcessReferences(element)) { - for (reference in element.references) { - processReference(reference) - } - } - } - - private fun visitClass(aClass: PsiClass) { - if (aClass is PsiTypeParameter || aClass is PsiAnonymousClass) return - if (aClass.constructors.isEmpty()) { - val superClass = aClass.superClass ?: return - val superConstructors = superClass.constructors - if (superConstructors.isEmpty() || superConstructors.any { it.parameterList.isEmpty }) { - processEmptyConstructorOfSuperClassImplicitInvocationAtSubclassDeclaration(aClass, superClass) - } - } - } - - private fun visitMethod(method: PsiMethod) { - if (method.isConstructor) { - checkCallToSuperEmptyConstructor(method) - } - else { - checkMethodOverriding(method) - } - } - - private fun visitNewExpression(expression: PsiNewExpression) { - var classType = expression.type as? PsiClassType ?: return - val argumentList = expression.argumentList ?: return - val classReference = expression.classOrAnonymousClassReference ?: return - - var typeResult = classType.resolveGenerics() - var aClass = typeResult.element ?: return - if (aClass is PsiAnonymousClass) { - classType = aClass.baseClassType - typeResult = classType.resolveGenerics() - aClass = typeResult.element ?: return - } - - if (aClass.constructors.isEmpty()) { - processDefaultConstructorInvocation(classReference) - } else { - val results = JavaPsiFacade - .getInstance(expression.project) - .resolveHelper - .multiResolveConstructor(classType, argumentList, argumentList) - val result = results.singleOrNull() as? MethodCandidateInfo ?: return - val constructor = result.element - processConstructorInvocation(classReference, constructor) - } - } - - private fun checkCallToSuperEmptyConstructor(constructor: PsiMethod) { - val superClass = constructor.containingClass?.superClass ?: return - val statements = constructor.body?.statements ?: return - if (isCallToEmptySuper(statements)) { - processEmptyConstructorOfSuperClassImplicitInvocationAtSubclassConstructor(superClass, constructor) - } - } - - private fun isCallToEmptySuper(statements: Array): Boolean { - if (statements.isEmpty()) { - //Implicit call to super() - return true - } - val expressionStatement = statements.first() as? PsiExpressionStatement - val methodCallExpression = expressionStatement?.expression as? PsiMethodCallExpression - val methodExpression = methodCallExpression?.methodExpression - if (methodExpression == null) { - //First statement is not "this" nor "super" => there is implicit super(). - return true - } - return methodExpression.text == "super" && methodCallExpression.argumentList.isEmpty - } - - private fun checkMethodOverriding(method: PsiMethod) { - val superSignatures = method.findSuperMethodSignaturesIncludingStatic(true) - for (superSignature in superSignatures) { - val superMethod = superSignature.method - processMethodOverriding(method, superMethod) - } - } - -} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/DeprecatedApiUsageProcessor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/DeprecatedApiUsageProcessor.java new file mode 100644 index 000000000000..f71b183b3f1e --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/DeprecatedApiUsageProcessor.java @@ -0,0 +1,141 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.codeInspection.deprecation; + +import com.intellij.codeInsight.ExternalAnnotationsManager; +import com.intellij.codeInsight.daemon.JavaErrorMessages; +import com.intellij.codeInspection.ProblemHighlightType; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.codeInspection.apiUsage.ApiUsageProcessor; +import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.psi.*; +import com.intellij.psi.impl.PsiImplUtil; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.uast.*; + +import java.util.List; + +import static com.intellij.codeInspection.deprecation.DeprecationInspectionBase.*; + +/** + * PSI visitor that detects usages of deprecated APIs, which are deprecated + * with {@code @Deprecated, @ScheduledForRemoval} annotations or {@code @deprecated} Javadoc tag. + */ +public final class DeprecatedApiUsageProcessor implements ApiUsageProcessor { + private final ProblemsHolder myHolder; + private final boolean myIgnoreInsideDeprecated; + private final boolean myIgnoreAbstractDeprecatedOverrides; + private final boolean myIgnoreImportStatements; + private final boolean myIgnoreMethodsOfDeprecated; + private final boolean myIgnoreInSameOutermostClass; + private final boolean myForRemoval; + private final ProblemHighlightType myHighlightType; + + public DeprecatedApiUsageProcessor(@NotNull ProblemsHolder holder, + boolean ignoreInsideDeprecated, + boolean ignoreAbstractDeprecatedOverrides, + boolean ignoreImportStatements, + boolean ignoreMethodsOfDeprecated, + boolean ignoreInSameOutermostClass, + boolean forRemoval, + @Nullable HighlightSeverity severity) { + myHolder = holder; + myIgnoreInsideDeprecated = ignoreInsideDeprecated; + myIgnoreAbstractDeprecatedOverrides = ignoreAbstractDeprecatedOverrides; + myIgnoreImportStatements = ignoreImportStatements; + myIgnoreMethodsOfDeprecated = ignoreMethodsOfDeprecated; + myIgnoreInSameOutermostClass = ignoreInSameOutermostClass; + myForRemoval = forRemoval; + myHighlightType = forRemoval && severity == HighlightSeverity.ERROR + ? ProblemHighlightType.LIKE_MARKED_FOR_REMOVAL + : ProblemHighlightType.LIKE_DEPRECATED; + } + + @Override + public void processReference(@NotNull UElement sourceNode, @NotNull PsiModifierListOwner target, @Nullable UExpression qualifier) { + checkTargetDeprecated(sourceNode, target); + } + + @Override + public void processImportReference(@NotNull UElement sourceNode, @NotNull PsiModifierListOwner target) { + checkTargetDeprecated(sourceNode, target); + } + + private void checkTargetDeprecated(@NotNull UElement sourceNode, @NotNull PsiModifierListOwner target) { + PsiElement elementToHighlight = sourceNode.getSourcePsi(); + if (elementToHighlight != null) { + checkTargetDeprecated(elementToHighlight, target); + } + } + + private void checkTargetDeprecated(@NotNull PsiElement elementToHighlight, @NotNull PsiModifierListOwner target) { + checkDeprecated(target, elementToHighlight, null, myIgnoreInsideDeprecated, myIgnoreImportStatements, + myIgnoreMethodsOfDeprecated, myIgnoreInSameOutermostClass, myHolder, myForRemoval, myHighlightType); + } + + @Override + public void processConstructorInvocation(@NotNull UElement sourceNode, + @NotNull PsiClass instantiatedClass, + @Nullable PsiMethod constructor, + @Nullable UClass subclassDeclaration) { + if (constructor != null) { + if (PsiImplUtil.isDeprecated(constructor) && myForRemoval == isForRemovalAttributeSet(constructor)) { + checkTargetDeprecated(sourceNode, constructor); + return; + } + } + + if (isDefaultConstructorDeprecated(instantiatedClass)) { + PsiElement elementToHighlight = sourceNode.getSourcePsi(); + if (elementToHighlight == null) { + return; + } + String description = JavaErrorMessages.message(myForRemoval + ? "marked.for.removal.default.constructor" + : "deprecated.default.constructor", + instantiatedClass.getQualifiedName()); + myHolder.registerProblem(elementToHighlight, getDescription(description, myForRemoval, myHighlightType), myHighlightType); + } + else { + checkTargetDeprecated(sourceNode, instantiatedClass); + } + } + + @Override + public void processMethodOverriding(@NotNull UMethod method, @NotNull PsiMethod overriddenMethod) { + PsiClass aClass = overriddenMethod.getContainingClass(); + if (aClass == null) return; + + PsiElement methodNameElement = UElementKt.getSourcePsiElement(method.getUastAnchor()); + if (methodNameElement == null) return; + + //Do not show deprecated warning for class implementing deprecated methods + if (myIgnoreAbstractDeprecatedOverrides && !aClass.isDeprecated() && overriddenMethod.hasModifierProperty(PsiModifier.ABSTRACT)) { + return; + } + + if (overriddenMethod.isDeprecated() && myForRemoval == isForRemovalAttributeSet(overriddenMethod)) { + String description = JavaErrorMessages.message(myForRemoval ? "overrides.marked.for.removal.method" : "overrides.deprecated.method", + getPresentableName(aClass)); + myHolder.registerProblem(methodNameElement, getDescription(description, myForRemoval, myHighlightType), myHighlightType); + } + } + + @Override + public void processJavaModuleReference(@NotNull PsiJavaModuleReference javaModuleReference, @NotNull PsiJavaModule target) { + checkTargetDeprecated(javaModuleReference.getElement(), target); + } + + /** + * The default constructor of a class can be externally annotated (IDEA-200832). + */ + private boolean isDefaultConstructorDeprecated(@NotNull PsiClass aClass) { + List externalDeprecated = ExternalAnnotationsManager + .getInstance(aClass.getProject()) + .findDefaultConstructorExternalAnnotations(aClass, CommonClassNames.JAVA_LANG_DEPRECATED); + + return externalDeprecated != null && + ContainerUtil.exists(externalDeprecated, annotation -> isForRemovalAttributeSet(annotation) == myForRemoval); + } +} \ No newline at end of file diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/DeprecationElementVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/DeprecationElementVisitor.java deleted file mode 100644 index 4e051d6e5066..000000000000 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/DeprecationElementVisitor.java +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -package com.intellij.codeInspection.deprecation; - -import com.intellij.codeInsight.ExternalAnnotationsManager; -import com.intellij.codeInsight.daemon.JavaErrorMessages; -import com.intellij.codeInspection.ProblemHighlightType; -import com.intellij.codeInspection.ProblemsHolder; -import com.intellij.codeInspection.apiUsage.ApiUsageVisitorBase; -import com.intellij.lang.annotation.HighlightSeverity; -import com.intellij.psi.*; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Arrays; -import java.util.List; - -import static com.intellij.codeInspection.deprecation.DeprecationInspectionBase.*; - -/** - * PSI visitor that detects usages of deprecated APIs, which are deprecated - * with {@code @Deprecated, @ScheduledForRemoval} annotations or {@code @deprecated} Javadoc tag. - */ -public class DeprecationElementVisitor extends ApiUsageVisitorBase { - private final ProblemsHolder myHolder; - private final boolean myIgnoreInsideDeprecated; - private final boolean myIgnoreAbstractDeprecatedOverrides; - private final boolean myIgnoreImportStatements; - private final boolean myIgnoreMethodsOfDeprecated; - private final boolean myIgnoreInSameOutermostClass; - private final boolean myForRemoval; - private final ProblemHighlightType myHighlightType; - - DeprecationElementVisitor(@NotNull ProblemsHolder holder, - boolean ignoreInsideDeprecated, - boolean ignoreAbstractDeprecatedOverrides, - boolean ignoreImportStatements, - boolean ignoreMethodsOfDeprecated, - boolean ignoreInSameOutermostClass, - boolean forRemoval, - @Nullable HighlightSeverity severity) { - myHolder = holder; - myIgnoreInsideDeprecated = ignoreInsideDeprecated; - myIgnoreAbstractDeprecatedOverrides = ignoreAbstractDeprecatedOverrides; - myIgnoreImportStatements = ignoreImportStatements; - myIgnoreMethodsOfDeprecated = ignoreMethodsOfDeprecated; - myIgnoreInSameOutermostClass = ignoreInSameOutermostClass; - myForRemoval = forRemoval; - myHighlightType = forRemoval && severity == HighlightSeverity.ERROR - ? ProblemHighlightType.LIKE_MARKED_FOR_REMOVAL - : ProblemHighlightType.LIKE_DEPRECATED; - } - - @Override - public void processReference(@NotNull PsiReference reference) { - if (reference instanceof ResolvingHint && !((ResolvingHint)reference).canResolveTo(PsiModifierListOwner.class)) { - return; - } - PsiElement resolved = reference.resolve(); - if (resolved != null) { - PsiElement elementToHighlight = getElementToHighlight(reference); - checkDeprecated(resolved, elementToHighlight, null, myIgnoreInsideDeprecated, myIgnoreImportStatements, - myIgnoreMethodsOfDeprecated, myIgnoreInSameOutermostClass, myHolder, myForRemoval, myHighlightType); - } - } - - @NotNull - private static PsiElement getElementToHighlight(@NotNull PsiReference reference) { - if (reference instanceof PsiJavaCodeReferenceElement) { - PsiElement referenceNameElement = ((PsiJavaCodeReferenceElement)reference).getReferenceNameElement(); - if (referenceNameElement != null) { - return referenceNameElement; - } - } - return reference.getElement(); - } - - @Override - public void processConstructorInvocation(@NotNull PsiJavaCodeReferenceElement instantiatedClass, @NotNull PsiMethod constructor) { - checkDeprecated(constructor, instantiatedClass, null, myIgnoreInsideDeprecated, myIgnoreImportStatements, - true, myIgnoreInSameOutermostClass, myHolder, myForRemoval, myHighlightType); - } - - @Override - public void processDefaultConstructorInvocation(@NotNull PsiJavaCodeReferenceElement instantiatedClass) { - PsiElement createdClass = instantiatedClass.resolve(); - if (createdClass instanceof PsiClass && hasEmptyDeprecatedConstructor((PsiClass) createdClass, myForRemoval)) { - registerDefaultConstructorProblem((PsiClass) createdClass, instantiatedClass); - } - } - - @Override - public void processMethodOverriding(@NotNull PsiMethod method, @NotNull PsiMethod overriddenMethod) { - PsiClass aClass = overriddenMethod.getContainingClass(); - if (aClass == null) return; - - PsiIdentifier nameIdentifier = method.getNameIdentifier(); - if (nameIdentifier == null) return; - - //Do not show deprecated warning for class implementing deprecated methods - if (myIgnoreAbstractDeprecatedOverrides && !aClass.isDeprecated() && overriddenMethod.hasModifierProperty(PsiModifier.ABSTRACT)) { - return; - } - - if (overriddenMethod.isDeprecated() && myForRemoval == isForRemovalAttributeSet(overriddenMethod)) { - String description = JavaErrorMessages.message(myForRemoval ? "overrides.marked.for.removal.method" : "overrides.deprecated.method", - getPresentableName(aClass)); - myHolder.registerProblem(nameIdentifier, getDescription(description, myForRemoval, myHighlightType), myHighlightType); - } - } - - @Override - public void processEmptyConstructorOfSuperClassImplicitInvocationAtSubclassConstructor(@NotNull PsiClass superClass, - @NotNull PsiMethod subclassConstructor) { - if (hasEmptyDeprecatedConstructor(superClass, myForRemoval)) { - PsiIdentifier nameIdentifier = subclassConstructor.getNameIdentifier(); - if (nameIdentifier != null) { - registerDefaultConstructorProblem(superClass, nameIdentifier); - } - } - } - - @Override - public void processEmptyConstructorOfSuperClassImplicitInvocationAtSubclassDeclaration(@NotNull PsiClass subclass, - @NotNull PsiClass superClass) { - if (hasEmptyDeprecatedConstructor(superClass, myForRemoval)) { - final boolean isAnonymous = subclass instanceof PsiAnonymousClass; - if (isAnonymous) { - final PsiExpressionList argumentList = ((PsiAnonymousClass)subclass).getArgumentList(); - if (argumentList != null && !argumentList.isEmpty()) return; - } - PsiElement elementToHighlight = isAnonymous ? ((PsiAnonymousClass) subclass).getBaseClassReference() : subclass.getNameIdentifier(); - if (elementToHighlight != null) { - registerDefaultConstructorProblem(superClass, elementToHighlight); - } - } - } - - private static boolean hasEmptyDeprecatedConstructor(@NotNull PsiClass aClass, boolean forRemoval) { - PsiMethod[] constructors = aClass.getConstructors(); - if (constructors.length == 0) { - /* - The default constructor of a class can be externally annotated (IDEA-200832). - There cannot be inferred annotations for a default constructor, - so here is no need to check all annotations returned - by `AnnotationUtil.findAnnotations()`, but only external ones. - */ - List externalDeprecated = ExternalAnnotationsManager - .getInstance(aClass.getProject()) - .findDefaultConstructorExternalAnnotations(aClass, CommonClassNames.JAVA_LANG_DEPRECATED); - - return externalDeprecated != null - && !externalDeprecated.isEmpty() - && ContainerUtil.exists(externalDeprecated, annotation -> isForRemovalAttributeSet(annotation) == forRemoval); - } - return Arrays.stream(constructors) - .anyMatch(constructor -> constructor.getParameterList().isEmpty() && - constructor.isDeprecated() && - forRemoval == isForRemovalAttributeSet(constructor)); - } - - private void registerDefaultConstructorProblem(@NotNull PsiClass constructorOwner, - @NotNull PsiElement elementToHighlight) { - String description = - JavaErrorMessages.message(myForRemoval ? "marked.for.removal.default.constructor" : "deprecated.default.constructor", - constructorOwner.getQualifiedName()); - myHolder.registerProblem(elementToHighlight, getDescription(description, myForRemoval, myHighlightType), myHighlightType); - } -} \ No newline at end of file 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 a5a942c19472..b0f154d14dff 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 @@ -3,8 +3,10 @@ package com.intellij.codeInspection.deprecation; import com.intellij.codeInspection.DeprecationUtil; import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.codeInspection.apiUsage.ApiUsageUastVisitor; import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel; import com.intellij.psi.PsiElementVisitor; +import com.intellij.uast.UastVisitorAdapter; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -26,9 +28,15 @@ public class DeprecationInspection extends DeprecationInspectionBase { @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, - IGNORE_IN_SAME_OUTERMOST_CLASS, false, null); + return new UastVisitorAdapter( + new ApiUsageUastVisitor( + new DeprecatedApiUsageProcessor(holder, IGNORE_INSIDE_DEPRECATED, IGNORE_ABSTRACT_DEPRECATED_OVERRIDES, + IGNORE_IMPORT_STATEMENTS, IGNORE_METHODS_OF_DEPRECATED, + IGNORE_IN_SAME_OUTERMOST_CLASS, false, null) + ) + , + true + ); } @Override 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 index 6a5c6fd45b99..93a3b7c04ece 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/DeprecationInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/DeprecationInspectionBase.java @@ -38,7 +38,7 @@ public abstract class DeprecationInspectionBase extends LocalInspectionTool { return true; } - public static void checkDeprecated(@NotNull PsiElement element, + public static void checkDeprecated(@NotNull PsiModifierListOwner element, @NotNull PsiElement elementToHighlight, @Nullable TextRange rangeInElement, boolean ignoreInsideDeprecated, @@ -48,13 +48,8 @@ public abstract class DeprecationInspectionBase extends LocalInspectionTool { @NotNull ProblemsHolder holder, boolean forRemoval, @NotNull ProblemHighlightType highlightType) { - if (!(element instanceof PsiModifierListOwner)) { - return; - } - - PsiModifierListOwner modifierListOwner = (PsiModifierListOwner)element; - if (PsiImplUtil.isDeprecated(modifierListOwner)) { - if (forRemoval != isForRemovalAttributeSet(modifierListOwner)) { + if (PsiImplUtil.isDeprecated(element)) { + if (forRemoval != isForRemovalAttributeSet(element)) { return; } } @@ -108,7 +103,7 @@ public abstract class DeprecationInspectionBase extends LocalInspectionTool { } } if (refElement instanceof PsiField) { - PsiReferenceExpression referenceExpression = ObjectUtils.tryCast(elementToHighlight.getParent(), PsiReferenceExpression.class); + PsiReferenceExpression referenceExpression = getFieldReferenceExpression(elementToHighlight); if (referenceExpression != null) { PsiField replacement = findReplacementInJavaDoc((PsiField)refElement, referenceExpression); if (replacement != null) { @@ -269,11 +264,22 @@ public abstract class DeprecationInspectionBase extends LocalInspectionTool { return PsiUtil.resolveClassInType(expression.getType()); } + @Nullable + private static PsiReferenceExpression getFieldReferenceExpression(@NotNull PsiElement element) { + if (element instanceof PsiReferenceExpression) { + return (PsiReferenceExpression) element; + } + return ObjectUtils.tryCast(element.getParent(), PsiReferenceExpression.class); + } + @Nullable private static PsiMethodCallExpression getMethodCall(@NotNull PsiElement element) { - if (!(element instanceof PsiIdentifier)) return null; - PsiElement parent = element.getParent(); - if (!(parent instanceof PsiReferenceExpression)) return null; - return ObjectUtils.tryCast(parent.getParent(), PsiMethodCallExpression.class); + if (element instanceof PsiReferenceExpression) { + return ObjectUtils.tryCast(element.getParent(), PsiMethodCallExpression.class); + } + if (element instanceof PsiIdentifier) { + return getMethodCall(element.getParent()); + } + return null; } } 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 index 38f073e8e872..700f6f5b0451 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/MarkedForRemovalInspection.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/deprecation/MarkedForRemovalInspection.java @@ -6,6 +6,7 @@ import com.intellij.codeInsight.daemon.HighlightDisplayKey; import com.intellij.codeInspection.DeprecationUtil; import com.intellij.codeInspection.InspectionProfile; import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.codeInspection.apiUsage.ApiUsageUastVisitor; import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel; import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.pom.java.LanguageLevel; @@ -13,6 +14,7 @@ import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.psi.PsiElementVisitor; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiJavaFile; +import com.intellij.uast.UastVisitorAdapter; import org.intellij.lang.annotations.Pattern; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -27,7 +29,12 @@ public class MarkedForRemovalInspection extends DeprecationInspectionBase { PsiFile file = holder.getFile(); if (file instanceof PsiJavaFile && ((PsiJavaFile)file).getLanguageLevel().isAtLeast(LanguageLevel.JDK_1_9)) { HighlightSeverity severity = getCurrentSeverity(file); - return new DeprecationElementVisitor(holder, false, false, false, false, IGNORE_IN_SAME_OUTERMOST_CLASS, true, severity); + return new UastVisitorAdapter( + new ApiUsageUastVisitor( + new DeprecatedApiUsageProcessor(holder, false, false, false, false, IGNORE_IN_SAME_OUTERMOST_CLASS, true, severity) + ), + true + ); } return PsiElementVisitor.EMPTY_VISITOR; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/xml/DeprecatedClassUsageInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/xml/DeprecatedClassUsageInspection.java index 084fe75d0b2b..f8055707ee0a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/xml/DeprecatedClassUsageInspection.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/xml/DeprecatedClassUsageInspection.java @@ -43,8 +43,8 @@ public class DeprecatedClassUsageInspection extends XmlSuppressableInspectionToo PsiReference last = ArrayUtil.getLastElement(references); if (last != null && (!(last instanceof ResolvingHint) || ((ResolvingHint)last).canResolveTo(PsiDocCommentOwner.class))) { PsiElement resolved = last.resolve(); - if (resolved != null) { - DeprecationInspectionBase.checkDeprecated(resolved, psiElement, last.getRangeInElement(), false, false, true, false, + if (resolved instanceof PsiModifierListOwner) { + DeprecationInspectionBase.checkDeprecated((PsiModifierListOwner)resolved, psiElement, last.getRangeInElement(), false, false, true, false, holder, false, ProblemHighlightType.LIKE_DEPRECATED); } } diff --git a/java/java-tests/testData/inspection/deprecation/deprecatedDefaultConstructorInSuper/expected.xml b/java/java-tests/testData/inspection/deprecation/deprecatedDefaultConstructorInSuper/expected.xml index dd14b20ddb1b..04616138a1e8 100644 --- a/java/java-tests/testData/inspection/deprecation/deprecatedDefaultConstructorInSuper/expected.xml +++ b/java/java-tests/testData/inspection/deprecation/deprecatedDefaultConstructorInSuper/expected.xml @@ -3,7 +3,7 @@ Test.java 5 - Default constructor in C is deprecated + 'C()' is deprecated Test.java diff --git a/java/java-tests/testData/inspection/deprecation/deprecatedDefaultConstructorInSuperNotCalled/expected.xml b/java/java-tests/testData/inspection/deprecation/deprecatedDefaultConstructorInSuperNotCalled/expected.xml index fbfc5a0037f4..e5483e38400f 100644 --- a/java/java-tests/testData/inspection/deprecation/deprecatedDefaultConstructorInSuperNotCalled/expected.xml +++ b/java/java-tests/testData/inspection/deprecation/deprecatedDefaultConstructorInSuperNotCalled/expected.xml @@ -3,7 +3,7 @@ Test.java 6 - Default constructor in C is deprecated + 'C()' is deprecated diff --git a/java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/expected.xml b/java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/expected.xml index 66dbc21a8224..58c57d3e4be2 100644 --- a/java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/expected.xml +++ b/java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/expected.xml @@ -12,7 +12,7 @@ Test.java - 16 + 17 Default constructor in 'a.A' is deprecated diff --git a/java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/src/Test.java b/java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/src/Test.java index e20149695856..c41d55ae4813 100644 --- a/java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/src/Test.java +++ b/java/java-tests/testData/inspection/deprecation/externallyDeprecatedDefaultConstructor/src/Test.java @@ -12,8 +12,8 @@ class Test2 { } class Test3 extends A { - //Here "Default constructor in A is deprecated" warning must be reported. public Test3() { + //Here "Default constructor in A is deprecated" warning must be reported. super(); } } \ No newline at end of file diff --git a/java/java-tests/testData/inspection/forRemoval/DefaultConstructorInSuper.java b/java/java-tests/testData/inspection/forRemoval/DefaultConstructorInSuper.java index d754c7b080f9..320a18b1240c 100644 --- a/java/java-tests/testData/inspection/forRemoval/DefaultConstructorInSuper.java +++ b/java/java-tests/testData/inspection/forRemoval/DefaultConstructorInSuper.java @@ -2,7 +2,7 @@ class C { @Deprecated(forRemoval=true) C() { } } -class D extends C { +class D extends C { } class P { diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/AnnotatedApiUsageProcessor.java b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/AnnotatedApiUsageProcessor.java new file mode 100644 index 000000000000..7c6c108ec09f --- /dev/null +++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/AnnotatedApiUsageProcessor.java @@ -0,0 +1,20 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.codeInspection; + +import com.intellij.psi.PsiAnnotation; +import com.intellij.psi.PsiModifierListOwner; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.uast.UElement; + +import java.util.List; + +/** + * Processor of usages of APIs marked with specified annotations, which are detected by {@link AnnotatedElementInspectionBase}. + */ +public interface AnnotatedApiUsageProcessor { + void processAnnotatedTarget( + @NotNull UElement sourceNode, + @NotNull PsiModifierListOwner annotatedTarget, + @NotNull List annotations + ); +} diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/AnnotatedElementInspectionBase.java b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/AnnotatedElementInspectionBase.java index 9fab49fcb36b..7eba9cb29460 100644 --- a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/AnnotatedElementInspectionBase.java +++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/AnnotatedElementInspectionBase.java @@ -2,6 +2,9 @@ package com.intellij.codeInspection; import com.intellij.analysis.JvmAnalysisBundle; +import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.codeInspection.apiUsage.ApiUsageProcessor; +import com.intellij.codeInspection.apiUsage.ApiUsageUastVisitor; import com.intellij.codeInspection.deprecation.DeprecationInspectionBase; import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel; import com.intellij.openapi.application.ApplicationManager; @@ -11,8 +14,12 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiUtilCore; +import com.intellij.uast.UastVisitorAdapter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.uast.UClass; +import org.jetbrains.uast.UElement; +import org.jetbrains.uast.UExpression; import javax.swing.*; import java.util.List; @@ -27,15 +34,6 @@ public abstract class AnnotatedElementInspectionBase extends LocalInspectionTool @NotNull protected abstract List getAnnotations(); - protected abstract void createProblem(@NotNull PsiReference reference, - @NotNull PsiModifierListOwner annotatedTarget, - @NotNull List annotations, - @NotNull ProblemsHolder holder); - - protected boolean shouldProcessElement(@NotNull PsiModifierListOwner element) { - return isLibraryElement(element); - } - @NotNull @Override public JPanel createOptionsPanel() { @@ -43,6 +41,9 @@ public abstract class AnnotatedElementInspectionBase extends LocalInspectionTool JvmAnalysisBundle.message("jvm.inspections.unstable.api.usage.ignore.inside.imports"), this, "myIgnoreInsideImports"); } + @NotNull + protected abstract AnnotatedApiUsageProcessor buildAnnotatedApiUsageProcessor(@NotNull ProblemsHolder holder); + @NotNull @Override public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { @@ -50,16 +51,11 @@ public abstract class AnnotatedElementInspectionBase extends LocalInspectionTool return PsiElementVisitor.EMPTY_VISITOR; } - return new AnnotatedElementVisitorBase(myIgnoreInsideImports, getAnnotations()) { - @Override - public void processAnnotatedTarget(@NotNull PsiReference reference, - @NotNull PsiModifierListOwner annotatedTarget, - @NotNull List annotations) { - if (AnnotatedElementInspectionBase.this.shouldProcessElement(annotatedTarget)) { - createProblem(reference, annotatedTarget, annotations, holder); - } - } - }; + AnnotatedApiUsageProcessor annotatedApiProcessor = buildAnnotatedApiUsageProcessor(holder); + AnnotatedApiUsageProcessorBridge processorBridge = new AnnotatedApiUsageProcessorBridge( + myIgnoreInsideImports, getAnnotations(), annotatedApiProcessor + ); + return new UastVisitorAdapter(new ApiUsageUastVisitor(processorBridge), true); } private boolean isApplicable(@Nullable PsiFile file, @Nullable Project project) { @@ -82,11 +78,56 @@ public abstract class AnnotatedElementInspectionBase extends LocalInspectionTool return DeprecationInspectionBase.getPresentableName(psiElement); } - private static boolean isLibraryElement(@NotNull PsiElement element) { + protected static boolean isLibraryElement(@NotNull PsiElement element) { if (ApplicationManager.getApplication().isUnitTestMode()) { return true; } VirtualFile containingVirtualFile = PsiUtilCore.getVirtualFile(element); return containingVirtualFile != null && ProjectFileIndex.getInstance(element.getProject()).isInLibraryClasses(containingVirtualFile); } + + private static final class AnnotatedApiUsageProcessorBridge implements ApiUsageProcessor { + private final boolean myIgnoreInsideImports; + private final List myAnnotations; + private final AnnotatedApiUsageProcessor myAnnotatedApiProcessor; + + private AnnotatedApiUsageProcessorBridge(boolean ignoreInsideImports, + @NotNull List annotations, + @NotNull AnnotatedApiUsageProcessor annotatedApiProcessor) { + myIgnoreInsideImports = ignoreInsideImports; + myAnnotations = annotations; + myAnnotatedApiProcessor = annotatedApiProcessor; + } + + @Override + public void processImportReference(@NotNull UElement sourceNode, @NotNull PsiModifierListOwner target) { + if (!myIgnoreInsideImports) { + maybeProcessAnnotatedTarget(sourceNode, target); + } + } + + @Override + public void processReference(@NotNull UElement sourceNode, @NotNull PsiModifierListOwner target, @Nullable UExpression qualifier) { + maybeProcessAnnotatedTarget(sourceNode, target); + } + + @Override + public void processConstructorInvocation(@NotNull UElement sourceNode, + @NotNull PsiClass instantiatedClass, + @Nullable PsiMethod constructor, + @Nullable UClass subclassDeclaration) { + if (constructor == null || !maybeProcessAnnotatedTarget(sourceNode, constructor)) { + maybeProcessAnnotatedTarget(sourceNode, instantiatedClass); + } + } + + private boolean maybeProcessAnnotatedTarget(@NotNull UElement sourceNode, @NotNull PsiModifierListOwner target) { + List annotations = AnnotationUtil.findAllAnnotations(target, myAnnotations, false); + if (annotations.isEmpty()) { + return false; + } + myAnnotatedApiProcessor.processAnnotatedTarget(sourceNode, target, annotations); + return true; + } + } } \ No newline at end of file diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/AnnotatedElementVisitorBase.java b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/AnnotatedElementVisitorBase.java deleted file mode 100644 index e90be2689586..000000000000 --- a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/AnnotatedElementVisitorBase.java +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -package com.intellij.codeInspection; - -import com.intellij.codeInsight.AnnotationUtil; -import com.intellij.codeInspection.apiUsage.ApiUsageVisitorBase; -import com.intellij.psi.*; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.uast.UImportStatement; -import org.jetbrains.uast.UastContextKt; - -import java.util.List; - -/** - * PSI visitor for any language that detects references to API annotated with one of {@code annotations}. - */ -public abstract class AnnotatedElementVisitorBase extends ApiUsageVisitorBase { - private final boolean myIgnoreInsideImports; - private final List myAnnotations; - - public AnnotatedElementVisitorBase(boolean ignoreInsideImports, @NotNull List annotations) { - myIgnoreInsideImports = ignoreInsideImports; - myAnnotations = annotations; - } - - /** - * Reference {@code reference} is found in source code, which resolves to {@code annotatedTarget} that has the - * following {@code annotations}, which are subset of sought-for annotations passed to constructor. - */ - public abstract void processAnnotatedTarget(@NotNull PsiReference reference, - @NotNull PsiModifierListOwner annotatedTarget, - @NotNull List annotations); - - @Override - public boolean shouldProcessReferences(@NotNull PsiElement element) { - return !myIgnoreInsideImports || !isInsideImportStatement(element); - } - - private static boolean isInsideImportStatement(@NotNull PsiElement element) { - return UastContextKt.getUastParentOfType(element, UImportStatement.class) != null; - } - - @Override - public void processReference(@NotNull PsiReference reference) { - PsiModifierListOwner annotationsOwner = resolveModifierListOwner(reference); - if (annotationsOwner != null) { - List annotations = AnnotationUtil.findAllAnnotations(annotationsOwner, myAnnotations, false); - if (!annotations.isEmpty()) { - processAnnotatedTarget(reference, annotationsOwner, annotations); - } - } - } - - @Override - public void processConstructorInvocation(@NotNull PsiJavaCodeReferenceElement instantiatedClass, @NotNull PsiMethod constructor) { - List annotations = AnnotationUtil.findAllAnnotations(constructor, myAnnotations, false); - if (!annotations.isEmpty()) { - processAnnotatedTarget(instantiatedClass, constructor, annotations); - } - } - - @Nullable - private static PsiModifierListOwner resolveModifierListOwner(@NotNull PsiReference reference) { - if (reference instanceof ResolvingHint && !((ResolvingHint)reference).canResolveTo(PsiModifierListOwner.class)) { - return null; - } - - PsiElement resolvedElement = reference.resolve(); - if (resolvedElement instanceof PsiModifierListOwner) { - return (PsiModifierListOwner)resolvedElement; - } - return null; - } -} diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/ScheduledForRemovalInspection.java b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/ScheduledForRemovalInspection.java index bb28cf1c892d..061ad63fe04e 100644 --- a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/ScheduledForRemovalInspection.java +++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/ScheduledForRemovalInspection.java @@ -4,11 +4,12 @@ package com.intellij.codeInspection; import com.intellij.analysis.JvmAnalysisBundle; import com.intellij.codeInsight.AnnotationUtil; import com.intellij.psi.PsiAnnotation; +import com.intellij.psi.PsiElement; import com.intellij.psi.PsiModifierListOwner; -import com.intellij.psi.PsiReference; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.uast.UElement; import java.util.Collections; import java.util.List; @@ -24,23 +25,35 @@ public class ScheduledForRemovalInspection extends AnnotatedElementInspectionBas return Collections.singletonList(ANNOTATION_NAME); } + @NotNull @Override - protected void createProblem(@NotNull PsiReference reference, - @NotNull PsiModifierListOwner annotatedTarget, - @NotNull List annotations, - @NotNull ProblemsHolder holder) { - //TODO determine highlight severity like in MarkedForRemovalInspection? - PsiAnnotation scheduledForRemoval = ContainerUtil.find(annotations, psiAnnotation -> psiAnnotation.hasQualifiedName(ANNOTATION_NAME)); - if (scheduledForRemoval != null) { - String inVersion = AnnotationUtil.getDeclaredStringAttributeValue(scheduledForRemoval, "inVersion"); - String targetText = getPresentableText(annotatedTarget); - String message; - if (inVersion == null || inVersion.isEmpty()) { - message = JvmAnalysisBundle.message("jvm.inspections.scheduled.for.removal.description.no.version", targetText); - } else { - message = JvmAnalysisBundle.message("jvm.inspections.scheduled.for.removal.description.with.version", targetText, inVersion); + protected AnnotatedApiUsageProcessor buildAnnotatedApiUsageProcessor(@NotNull ProblemsHolder holder) { + return new AnnotatedApiUsageProcessor() { + @Override + public void processAnnotatedTarget(@NotNull UElement sourceNode, + @NotNull PsiModifierListOwner annotatedTarget, + @NotNull List annotations) { + if (!AnnotatedElementInspectionBase.isLibraryElement(annotatedTarget)) { + return; + } + //TODO determine highlight severity like in MarkedForRemovalInspection? + PsiElement elementToHighlight = sourceNode.getSourcePsi(); + PsiAnnotation scheduledForRemoval = ContainerUtil.find( + annotations, psiAnnotation -> psiAnnotation.hasQualifiedName(ANNOTATION_NAME) + ); + if (elementToHighlight != null && scheduledForRemoval != null) { + String inVersion = AnnotationUtil.getDeclaredStringAttributeValue(scheduledForRemoval, "inVersion"); + String targetText = getPresentableText(annotatedTarget); + String message; + if (inVersion == null || inVersion.isEmpty()) { + message = JvmAnalysisBundle.message("jvm.inspections.scheduled.for.removal.description.no.version", targetText); + } + else { + message = JvmAnalysisBundle.message("jvm.inspections.scheduled.for.removal.description.with.version", targetText, inVersion); + } + holder.registerProblem(elementToHighlight, message, ProblemHighlightType.LIKE_MARKED_FOR_REMOVAL); + } } - holder.registerProblem(reference, message, ProblemHighlightType.LIKE_MARKED_FOR_REMOVAL); - } + }; } } diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/UnstableApiUsageInspection.java b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/UnstableApiUsageInspection.java index e3c7091ceed7..054cfadba732 100644 --- a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/UnstableApiUsageInspection.java +++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/UnstableApiUsageInspection.java @@ -4,10 +4,11 @@ package com.intellij.codeInspection; import com.intellij.analysis.JvmAnalysisBundle; import com.intellij.codeInspection.util.SpecialAnnotationsUtil; import com.intellij.psi.PsiAnnotation; +import com.intellij.psi.PsiElement; import com.intellij.psi.PsiModifierListOwner; -import com.intellij.psi.PsiReference; import com.siyeh.ig.ui.ExternalizableStringSet; import org.jetbrains.annotations.NotNull; +import org.jetbrains.uast.UElement; import javax.swing.*; import java.awt.*; @@ -34,13 +35,24 @@ public class UnstableApiUsageInspection extends AnnotatedElementInspectionBase { return unstableApiAnnotations; } + @NotNull @Override - protected void createProblem(@NotNull PsiReference reference, - @NotNull PsiModifierListOwner annotatedTarget, - @NotNull List annotations, - @NotNull ProblemsHolder holder) { - String message = JvmAnalysisBundle.message("jvm.inspections.unstable.api.usage.description", getPresentableName(annotatedTarget)); - holder.registerProblem(reference, message, ProblemHighlightType.GENERIC_ERROR_OR_WARNING); + protected AnnotatedApiUsageProcessor buildAnnotatedApiUsageProcessor(@NotNull ProblemsHolder holder) { + return new AnnotatedApiUsageProcessor() { + @Override + public void processAnnotatedTarget(@NotNull UElement sourceNode, + @NotNull PsiModifierListOwner annotatedTarget, + @NotNull List annotations) { + if (!AnnotatedElementInspectionBase.isLibraryElement(annotatedTarget)) { + return; + } + String message = JvmAnalysisBundle.message("jvm.inspections.unstable.api.usage.description", getPresentableName(annotatedTarget)); + PsiElement elementToHighlight = sourceNode.getSourcePsi(); + if (elementToHighlight != null) { + holder.registerProblem(elementToHighlight, message, ProblemHighlightType.GENERIC_ERROR_OR_WARNING); + } + } + }; } @NotNull diff --git a/jvm/jvm-analysis-java-tests/testData/codeInspection/scheduledForRemoval/ScheduledForRemovalElementsIgnoreImportsTest.java b/jvm/jvm-analysis-java-tests/testData/codeInspection/scheduledForRemoval/ScheduledForRemovalElementsIgnoreImportsTest.java index f930ad6ec6a9..c2f578822d45 100644 --- a/jvm/jvm-analysis-java-tests/testData/codeInspection/scheduledForRemoval/ScheduledForRemovalElementsIgnoreImportsTest.java +++ b/jvm/jvm-analysis-java-tests/testData/codeInspection/scheduledForRemoval/ScheduledForRemovalElementsIgnoreImportsTest.java @@ -35,7 +35,7 @@ public class ScheduledForRemovalElementsIgnoreImportsTest { s = AnnotatedClass.ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS; AnnotatedClass.staticAnnotatedMethodInAnnotatedClass(); - AnnotatedClass annotatedClassInstanceViaAnnotatedConstructor = new AnnotatedClass(""); + AnnotatedClass annotatedClassInstanceViaAnnotatedConstructor = new AnnotatedClass(""); s = annotatedClassInstanceViaAnnotatedConstructor.annotatedFieldInAnnotatedClass; annotatedClassInstanceViaAnnotatedConstructor.annotatedMethodInAnnotatedClass(); s = ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS; diff --git a/jvm/jvm-analysis-java-tests/testData/codeInspection/scheduledForRemoval/ScheduledForRemovalElementsTest.java b/jvm/jvm-analysis-java-tests/testData/codeInspection/scheduledForRemoval/ScheduledForRemovalElementsTest.java index 4c1166828841..c3f1dd6ec717 100644 --- a/jvm/jvm-analysis-java-tests/testData/codeInspection/scheduledForRemoval/ScheduledForRemovalElementsTest.java +++ b/jvm/jvm-analysis-java-tests/testData/codeInspection/scheduledForRemoval/ScheduledForRemovalElementsTest.java @@ -35,7 +35,7 @@ public class ScheduledForRemovalElementsTest { s = AnnotatedClass.ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS; AnnotatedClass.staticAnnotatedMethodInAnnotatedClass(); - AnnotatedClass annotatedClassInstanceViaAnnotatedConstructor = new AnnotatedClass(""); + AnnotatedClass annotatedClassInstanceViaAnnotatedConstructor = new AnnotatedClass(""); s = annotatedClassInstanceViaAnnotatedConstructor.annotatedFieldInAnnotatedClass; annotatedClassInstanceViaAnnotatedConstructor.annotatedMethodInAnnotatedClass(); s = ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS; diff --git a/jvm/jvm-analysis-java-tests/testData/codeInspection/unstableApiUsage/UnstableElementsIgnoreImportsTest.java b/jvm/jvm-analysis-java-tests/testData/codeInspection/unstableApiUsage/UnstableElementsIgnoreImportsTest.java index ee0110bd8843..853f2f10f427 100644 --- a/jvm/jvm-analysis-java-tests/testData/codeInspection/unstableApiUsage/UnstableElementsIgnoreImportsTest.java +++ b/jvm/jvm-analysis-java-tests/testData/codeInspection/unstableApiUsage/UnstableElementsIgnoreImportsTest.java @@ -35,7 +35,7 @@ public class UnstableElementsIgnoreImportsTest { s = AnnotatedClass.ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS; AnnotatedClass.staticAnnotatedMethodInAnnotatedClass(); - AnnotatedClass annotatedClassInstanceViaAnnotatedConstructor = new AnnotatedClass(""); + AnnotatedClass annotatedClassInstanceViaAnnotatedConstructor = new AnnotatedClass(""); s = annotatedClassInstanceViaAnnotatedConstructor.annotatedFieldInAnnotatedClass; annotatedClassInstanceViaAnnotatedConstructor.annotatedMethodInAnnotatedClass(); s = ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS; diff --git a/jvm/jvm-analysis-java-tests/testData/codeInspection/unstableApiUsage/UnstableElementsTest.java b/jvm/jvm-analysis-java-tests/testData/codeInspection/unstableApiUsage/UnstableElementsTest.java index 68fe87ad8811..7717fe31ad2f 100644 --- a/jvm/jvm-analysis-java-tests/testData/codeInspection/unstableApiUsage/UnstableElementsTest.java +++ b/jvm/jvm-analysis-java-tests/testData/codeInspection/unstableApiUsage/UnstableElementsTest.java @@ -35,7 +35,7 @@ public class UnstableElementsTest { s = AnnotatedClass.ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS; AnnotatedClass.staticAnnotatedMethodInAnnotatedClass(); - AnnotatedClass annotatedClassInstanceViaAnnotatedConstructor = new AnnotatedClass(""); + AnnotatedClass annotatedClassInstanceViaAnnotatedConstructor = new AnnotatedClass(""); s = annotatedClassInstanceViaAnnotatedConstructor.annotatedFieldInAnnotatedClass; annotatedClassInstanceViaAnnotatedConstructor.annotatedMethodInAnnotatedClass(); s = ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS; diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/scheduledForRemoval/ScheduledForRemovalElementsIgnoreImportsTest.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/scheduledForRemoval/ScheduledForRemovalElementsIgnoreImportsTest.kt index 4eb9f40c7f12..0e78da694b4e 100644 --- a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/scheduledForRemoval/ScheduledForRemovalElementsIgnoreImportsTest.kt +++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/scheduledForRemoval/ScheduledForRemovalElementsIgnoreImportsTest.kt @@ -27,7 +27,7 @@ class ScheduledForRemovalElementsIgnoreImportsTest { fun test() { var s = AnnotatedClass.NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS AnnotatedClass.staticNonAnnotatedMethodInAnnotatedClass() - val annotatedClassInstanceViaNonAnnotatedConstructor : AnnotatedClass = AnnotatedClass() + val annotatedClassInstanceViaNonAnnotatedConstructor : AnnotatedClass = AnnotatedClass() s = annotatedClassInstanceViaNonAnnotatedConstructor.nonAnnotatedFieldInAnnotatedClass annotatedClassInstanceViaNonAnnotatedConstructor.nonAnnotatedMethodInAnnotatedClass() s = NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/scheduledForRemoval/ScheduledForRemovalElementsTest.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/scheduledForRemoval/ScheduledForRemovalElementsTest.kt index a683e322c619..2236a9c1ef21 100644 --- a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/scheduledForRemoval/ScheduledForRemovalElementsTest.kt +++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/scheduledForRemoval/ScheduledForRemovalElementsTest.kt @@ -27,7 +27,7 @@ class ScheduledForRemovalElementsTest { fun test() { var s = AnnotatedClass.NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS AnnotatedClass.staticNonAnnotatedMethodInAnnotatedClass() - val annotatedClassInstanceViaNonAnnotatedConstructor : AnnotatedClass = AnnotatedClass() + val annotatedClassInstanceViaNonAnnotatedConstructor : AnnotatedClass = AnnotatedClass() s = annotatedClassInstanceViaNonAnnotatedConstructor.nonAnnotatedFieldInAnnotatedClass annotatedClassInstanceViaNonAnnotatedConstructor.nonAnnotatedMethodInAnnotatedClass() s = NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/suspiciousPackagePrivateAccess/src/AccessingPackagePrivateMembers.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/suspiciousPackagePrivateAccess/src/AccessingPackagePrivateMembers.kt index 2ce2dddbd4b8..0ab132fb5588 100644 --- a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/suspiciousPackagePrivateAccess/src/AccessingPackagePrivateMembers.kt +++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/suspiciousPackagePrivateAccess/src/AccessingPackagePrivateMembers.kt @@ -8,17 +8,17 @@ import xxx.StaticMembers.* */ @Suppress("UNUSED_VARIABLE") class AccessingPackagePrivateMembers { - private val property = PackagePrivateClass() + private val property = PackagePrivateClass() fun main() { - PackagePrivateClass() + PackagePrivateClass() var variable: PackagePrivateClass val sam = PackagePrivateInterface { "" } val aClass: PublicClass = PublicClass(1); val aClass2: PublicClassWithDefaultConstructor = PublicClassWithDefaultConstructor(); - PublicClass() - PublicClass(true) + PublicClass() + PublicClass(true) System.out.println(aClass.publicField) System.out.println(aClass.packagePrivateField) @@ -26,17 +26,17 @@ class AccessingPackagePrivateMembers { System.out.println(PublicClass.PACKAGE_PRIVATE_STATIC_FIELD) aClass.publicMethod() - aClass.packagePrivateMethod() + aClass.packagePrivateMethod() System.out.println(IMPORTED_FIELD) importedMethod() - InnerClasses.PackagePrivateInnerClass() - InnerClasses.PackagePrivateInnerClassWithConstructor() - InnerClasses.ClassWithPackagePrivateConstructor() + InnerClasses.PackagePrivateInnerClass() + InnerClasses.PackagePrivateInnerClassWithConstructor() + InnerClasses.ClassWithPackagePrivateConstructor() } companion object { - private val staticProperty = PackagePrivateClass() + private val staticProperty = PackagePrivateClass() } } \ No newline at end of file diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/suspiciousPackagePrivateAccess/src/AccessingProtectedMembers.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/suspiciousPackagePrivateAccess/src/AccessingProtectedMembers.kt index e910cc31e1cc..5fb0bcf48d25 100644 --- a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/suspiciousPackagePrivateAccess/src/AccessingProtectedMembers.kt +++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/suspiciousPackagePrivateAccess/src/AccessingProtectedMembers.kt @@ -3,10 +3,10 @@ package xxx class AccessingProtectedMembersNotFromSubclass { fun foo() { val aClass: ProtectedMembers = ProtectedMembers() - aClass.method() - ProtectedMembers.staticMethod() - ProtectedConstructors() - ProtectedConstructors(1) + aClass.method() + ProtectedMembers.staticMethod() + ProtectedConstructors() + ProtectedConstructors(1) } } @@ -18,7 +18,7 @@ class AccessingProtectedMembersFromSubclass : ProtectedMembers() { ProtectedMembers.staticMethod() val aClass = ProtectedMembers() - aClass.method() + aClass.method() val myInstance = AccessingProtectedMembersFromSubclass() myInstance.method() diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/unstableApiUsage/UnstableElementsIgnoreImportsTest.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/unstableApiUsage/UnstableElementsIgnoreImportsTest.kt index b93fc7e1f0a3..197d59f7ff88 100644 --- a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/unstableApiUsage/UnstableElementsIgnoreImportsTest.kt +++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/unstableApiUsage/UnstableElementsIgnoreImportsTest.kt @@ -27,7 +27,7 @@ class UnstableElementsIgnoreImportsTest { fun test() { var s = AnnotatedClass.NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS AnnotatedClass.staticNonAnnotatedMethodInAnnotatedClass() - val annotatedClassInstanceViaNonAnnotatedConstructor : AnnotatedClass = AnnotatedClass() + val annotatedClassInstanceViaNonAnnotatedConstructor : AnnotatedClass = AnnotatedClass() s = annotatedClassInstanceViaNonAnnotatedConstructor.nonAnnotatedFieldInAnnotatedClass annotatedClassInstanceViaNonAnnotatedConstructor.nonAnnotatedMethodInAnnotatedClass() s = NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS diff --git a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/unstableApiUsage/UnstableElementsTest.kt b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/unstableApiUsage/UnstableElementsTest.kt index 8b35661b6252..4d76dd9ee079 100644 --- a/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/unstableApiUsage/UnstableElementsTest.kt +++ b/jvm/jvm-analysis-kotlin-tests/testData/codeInspection/unstableApiUsage/UnstableElementsTest.kt @@ -27,7 +27,7 @@ class UnstableElementsTest { fun test() { var s = AnnotatedClass.NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS AnnotatedClass.staticNonAnnotatedMethodInAnnotatedClass() - val annotatedClassInstanceViaNonAnnotatedConstructor : AnnotatedClass = AnnotatedClass() + val annotatedClassInstanceViaNonAnnotatedConstructor : AnnotatedClass = AnnotatedClass() s = annotatedClassInstanceViaNonAnnotatedConstructor.nonAnnotatedFieldInAnnotatedClass annotatedClassInstanceViaNonAnnotatedConstructor.nonAnnotatedMethodInAnnotatedClass() s = NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS diff --git a/plugins/InspectionGadgets/src/com/siyeh/ig/dependency/SuspiciousPackagePrivateAccessInspection.java b/plugins/InspectionGadgets/src/com/siyeh/ig/dependency/SuspiciousPackagePrivateAccessInspection.java index eb2d24de5a7e..1fc9b8db25b5 100644 --- a/plugins/InspectionGadgets/src/com/siyeh/ig/dependency/SuspiciousPackagePrivateAccessInspection.java +++ b/plugins/InspectionGadgets/src/com/siyeh/ig/dependency/SuspiciousPackagePrivateAccessInspection.java @@ -3,6 +3,8 @@ package com.siyeh.ig.dependency; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.codeInspection.*; +import com.intellij.codeInspection.apiUsage.ApiUsageProcessor; +import com.intellij.codeInspection.apiUsage.ApiUsageUastVisitor; import com.intellij.codeInspection.ex.InspectionProfileImpl; import com.intellij.lang.jvm.JvmModifier; import com.intellij.lang.jvm.actions.JvmElementActionFactories; @@ -25,6 +27,7 @@ import com.intellij.ui.components.JBLabel; import com.intellij.ui.components.JBScrollPane; import com.intellij.ui.components.JBTextArea; import com.intellij.util.ArrayUtil; +import com.intellij.util.ObjectUtils; import com.intellij.util.xmlb.annotations.Property; import com.intellij.util.xmlb.annotations.Tag; import com.intellij.util.xmlb.annotations.XCollection; @@ -34,7 +37,6 @@ import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.uast.*; -import org.jetbrains.uast.visitor.AbstractUastNonRecursiveVisitor; import javax.swing.*; import javax.swing.event.DocumentEvent; @@ -60,150 +62,98 @@ public class SuspiciousPackagePrivateAccessInspection extends AbstractBaseUastLo @NotNull @Override public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { - return new UastVisitorAdapter(new AbstractUastNonRecursiveVisitor() { - @Override - public boolean visitQualifiedReferenceExpression(@NotNull UQualifiedReferenceExpression node) { - UExpression receiver = node.getReceiver(); - if (node.getSourcePsi() instanceof PsiMethodCallExpression) { - //JavaUastLanguagePlugin produces UQualifiedReferenceExpression for the both PsiMethodCallExpression and PsiReferenceExpression inside it, so we need to ignore them - return true; - } - PsiElement resolved = node.resolve(); - if (resolved instanceof PsiMember) { - checkAccess(node.getSelector(), (PsiMember)resolved, getAccessObjectType(receiver)); - } - return true; - } - - @Override - public boolean visitSimpleNameReferenceExpression(@NotNull USimpleNameReferenceExpression node) { - UElement uastParent = node.getUastParent(); - //we should skip 'checkAccess' here if node is part of UQualifiedReferenceExpression or UCallExpression node, - // otherwise the same problem will be reported twice - if (!isSelectorOfQualifiedReference(node) && !isReferenceToConstructorOrQualifiedMethodReference(node, uastParent)) { - PsiElement resolved = node.resolve(); - if (resolved instanceof PsiMember) { - checkAccess(node, (PsiMember)resolved, null); - } - } - return true; - } - - private boolean isReferenceToConstructorOrQualifiedMethodReference(@NotNull USimpleNameReferenceExpression node, - @Nullable UElement uastParent) { - return uastParent instanceof UCallExpression && isMethodReferenceOfCallExpression(node, (UCallExpression)uastParent) - && (((UCallExpression)uastParent).getKind() == UastCallKind.CONSTRUCTOR_CALL || isSelectorOfQualifiedReference((UExpression)uastParent)); - } - - private boolean isSelectorOfQualifiedReference(@Nullable UExpression expression) { - if (expression == null) return false; - UElement parent = expression.getUastParent(); - return parent instanceof UQualifiedReferenceExpression - && referToSameSourceElement(expression, ((UQualifiedReferenceExpression)parent).getSelector()); - } - - private boolean isMethodReferenceOfCallExpression(@NotNull USimpleNameReferenceExpression expression, @NotNull UCallExpression parent) { - UElement methodIdentifier = parent.getMethodIdentifier(); - UReferenceExpression classReference = parent.getClassReference(); - if (methodIdentifier == null && classReference != null) { - methodIdentifier = classReference.getReferenceNameElement(); - } - return referToSameSourceElement(expression.getReferenceNameElement(), methodIdentifier); - } - - private boolean referToSameSourceElement(@Nullable UElement element1, @Nullable UElement element2) { - if (element1 == null || element2 == null) return false; - PsiElement sourcePsi1 = element1.getSourcePsi(); - return sourcePsi1 != null && sourcePsi1.equals(element2.getSourcePsi()); - } - - @Override - public boolean visitCallableReferenceExpression(@NotNull UCallableReferenceExpression node) { - PsiElement resolve = node.resolve(); - if (resolve instanceof PsiMember) { - PsiMember member = (PsiMember)resolve; - UElement sourceNode = getReferenceNameElement(node); - if (sourceNode != null) { - checkAccess(sourceNode, member, getAccessObjectType(node.getQualifierExpression())); - } - } - return true; - } - - @Override - public boolean visitCallExpression(@NotNull UCallExpression node) { - //regular method calls are handled by visitSimpleNameReferenceExpression or visitQualifiedReferenceExpression, but we need to handle - // constructor calls in a special way because they may refer to classes - if (!isSelectorOfQualifiedReference(node) && node.getKind() == UastCallKind.CONSTRUCTOR_CALL) { - PsiMethod resolved = node.resolve(); - if (resolved != null) { - checkAccess(node, resolved, null); - } - else { - UReferenceExpression classReference = node.getClassReference(); - PsiElement resolvedClass = classReference != null ? classReference.resolve() : null; - if (resolvedClass instanceof PsiClass) { - checkAccess(node, (PsiClass)resolvedClass, null); - } - } - } - return true; - } - - private void checkAccess(@NotNull UElement sourceNode, @NotNull PsiMember target, @Nullable PsiClass accessObjectType) { - if (target.hasModifier(JvmModifier.PACKAGE_LOCAL)) { - checkPackageLocalAccess(sourceNode, target, "package-private"); - } - else if (target.hasModifier(JvmModifier.PROTECTED) && !canAccessProtectedMember(sourceNode, target, accessObjectType)) { - checkPackageLocalAccess(sourceNode, target, "protected and used not through a subclass here"); - } - } - - private void checkPackageLocalAccess(@NotNull UElement sourceNode, PsiMember targetElement, final String accessType) { - PsiElement sourcePsi = sourceNode.getSourcePsi(); - if (sourcePsi != null) { - Module targetModule = ModuleUtilCore.findModuleForPsiElement(targetElement); - Module sourceModule = ModuleUtilCore.findModuleForPsiElement(sourcePsi); - if (isPackageLocalAccessSuspicious(sourceModule, targetModule) && PsiTreeUtil.getParentOfType(sourcePsi, PsiComment.class) == null) { - List fixes = - JvmElementActionFactories.createModifierActions(targetElement, MemberRequestsKt.modifierRequest(JvmModifier.PUBLIC, true)); - String elementDescription = StringUtil.removeHtmlTags(StringUtil.capitalize(RefactoringUIUtil.getDescription(targetElement, true))); - LocalQuickFix[] quickFixes = IntentionWrapper.wrapToQuickFixes(fixes.toArray(IntentionAction.EMPTY_ARRAY), targetElement.getContainingFile()); - holder.registerProblem(sourcePsi, elementDescription + " is " + accessType + ", but declared in a different module '" - + targetModule.getName() + "'", - ArrayUtil.append(quickFixes, new MarkModulesAsLoadedTogetherFix(sourceModule.getName(), targetModule.getName()))); - } - } - } - }, true); + return new UastVisitorAdapter(new ApiUsageUastVisitor( + new SuspiciousApiUsageProcessor(holder, myModuleSetByModuleName.getValue()) + ), true); } - @Nullable - private static UElement getReferenceNameElement(UCallableReferenceExpression node) { - PsiElement psi = node.getSourcePsi(); - if (psi instanceof PsiReferenceExpression) { - PsiElement nameElement = ((PsiReferenceExpression)psi).getReferenceNameElement(); - if (nameElement != null) { - return UastContextKt.toUElement(nameElement); + private static class SuspiciousApiUsageProcessor implements ApiUsageProcessor { + + private final ProblemsHolder myProblemsHolder; + private final Map myModuleNameToModulesSet; + + private SuspiciousApiUsageProcessor(ProblemsHolder problemsHolder, Map moduleNameToModulesSet) { + myProblemsHolder = problemsHolder; + myModuleNameToModulesSet = moduleNameToModulesSet; + } + + @Override + public void processReference(@NotNull UElement sourceNode, @NotNull PsiModifierListOwner target, @Nullable UExpression qualifier) { + PsiClass accessObjectType = getAccessObjectType(qualifier); + if (target instanceof PsiMember) { + checkAccess(sourceNode, (PsiMember)target, accessObjectType); } } - return node; - } - @Nullable - private static PsiClass getAccessObjectType(@Nullable UExpression receiver) { - if (receiver == null || receiver instanceof UThisExpression || receiver instanceof USuperExpression) { + @Override + public void processConstructorInvocation(@NotNull UElement sourceNode, + @NotNull PsiClass instantiatedClass, + @Nullable PsiMethod constructor, + @Nullable UClass subclassDeclaration) { + if (subclassDeclaration == null) { + if (constructor != null) { + checkAccess(sourceNode, constructor, null); + } + else { + checkAccess(sourceNode, instantiatedClass, null); + } + } + } + + @Nullable + private static PsiClass getAccessObjectType(@Nullable UExpression qualifier) { + if (qualifier == null || qualifier instanceof UThisExpression || qualifier instanceof USuperExpression) { + return null; + } + + PsiType type = qualifier.getExpressionType(); + if (type instanceof PsiClassType) { + return ((PsiClassType)type).resolve(); + } + if (qualifier instanceof UReferenceExpression) { + return ObjectUtils.tryCast(((UReferenceExpression)qualifier).resolve(), PsiClass.class); + } return null; } - PsiType type = receiver.getExpressionType(); - if (type != null) { - if (!(type instanceof PsiClassType)) return null; - return ((PsiClassType)type).resolve(); + private void checkAccess(@NotNull UElement sourceNode, @NotNull PsiMember target, @Nullable PsiClass accessObjectType) { + if (target.hasModifier(JvmModifier.PACKAGE_LOCAL)) { + checkPackageLocalAccess(sourceNode, target, "package-private"); + } + else if (target.hasModifier(JvmModifier.PROTECTED) && !canAccessProtectedMember(sourceNode, target, accessObjectType)) { + checkPackageLocalAccess(sourceNode, target, "protected and used not through a subclass here"); + } } - else { - PsiElement element = ((UReferenceExpression)receiver).resolve(); - return element instanceof PsiClass ? (PsiClass)element : null; + + private void checkPackageLocalAccess(@NotNull UElement sourceNode, PsiMember targetElement, final String accessType) { + PsiElement sourcePsi = sourceNode.getSourcePsi(); + if (sourcePsi != null) { + Module targetModule = ModuleUtilCore.findModuleForPsiElement(targetElement); + Module sourceModule = ModuleUtilCore.findModuleForPsiElement(sourcePsi); + if (isPackageLocalAccessSuspicious(sourceModule, targetModule) && + PsiTreeUtil.getParentOfType(sourcePsi, PsiComment.class) == null) { + List fixes = + JvmElementActionFactories.createModifierActions(targetElement, MemberRequestsKt.modifierRequest(JvmModifier.PUBLIC, true)); + String elementDescription = + StringUtil.removeHtmlTags(StringUtil.capitalize(RefactoringUIUtil.getDescription(targetElement, true))); + LocalQuickFix[] quickFixes = + IntentionWrapper.wrapToQuickFixes(fixes.toArray(IntentionAction.EMPTY_ARRAY), targetElement.getContainingFile()); + myProblemsHolder.registerProblem(sourcePsi, elementDescription + " is " + accessType + ", but declared in a different module '" + + targetModule.getName() + "'", + ArrayUtil.append(quickFixes, + new MarkModulesAsLoadedTogetherFix(sourceModule.getName(), + targetModule.getName()))); + } + } + } + + private boolean isPackageLocalAccessSuspicious(Module sourceModule, Module targetModule) { + if (targetModule == null || sourceModule == null || targetModule.equals(sourceModule)) { + return false; + } + ModulesSet sourceGroup = myModuleNameToModulesSet.get(sourceModule.getName()); + ModulesSet targetGroup = myModuleNameToModulesSet.get(targetModule.getName()); + return sourceGroup == null || sourceGroup != targetGroup; } } @@ -250,15 +200,6 @@ public class SuspiciousPackagePrivateAccessInspection extends AbstractBaseUastLo return UastUtils.findContaining(sourcePsi, UClass.class); } - private boolean isPackageLocalAccessSuspicious(Module sourceModule, Module targetModule) { - if (targetModule == null || sourceModule == null || targetModule.equals(sourceModule)) { - return false; - } - ModulesSet sourceGroup = myModuleSetByModuleName.getValue().get(sourceModule.getName()); - ModulesSet targetGroup = myModuleSetByModuleName.getValue().get(targetModule.getName()); - return sourceGroup == null || sourceGroup != targetGroup; - } - @Tag("modules-set") public static class ModulesSet { @XCollection(elementName = "module", valueAttributeName = "name") diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/dependency/suspicious_package_private_access/src/AccessingPackagePrivateMembers.java b/plugins/InspectionGadgets/test/com/siyeh/igtest/dependency/suspicious_package_private_access/src/AccessingPackagePrivateMembers.java index 5c609bca49dd..5fc7666648df 100644 --- a/plugins/InspectionGadgets/test/com/siyeh/igtest/dependency/suspicious_package_private_access/src/AccessingPackagePrivateMembers.java +++ b/plugins/InspectionGadgets/test/com/siyeh/igtest/dependency/suspicious_package_private_access/src/AccessingPackagePrivateMembers.java @@ -7,23 +7,23 @@ import static xxx.StaticMembers.*; * @see PublicClass#packagePrivateField */ public class AccessingPackagePrivateMembers { - static Object staticField = new PackagePrivateClass(); - Object field = new PackagePrivateClass(); + static Object staticField = new PackagePrivateClass(); + Object field = new PackagePrivateClass(); { - new PackagePrivateClass(); + new PackagePrivateClass(); } static { - new PackagePrivateClass(); + new PackagePrivateClass(); } public void main() { - new PackagePrivateClass(); + new PackagePrivateClass(); PackagePrivateClass variable; PublicClass aClass = new PublicClass(1); PublicClassWithDefaultConstructor aClass2 = new PublicClassWithDefaultConstructor(); - new PublicClass(); - new PublicClass(true); + new PublicClass(); + new PublicClass(true); System.out.println(aClass.publicField); System.out.println(aClass.packagePrivateField); @@ -37,8 +37,8 @@ public class AccessingPackagePrivateMembers { System.out.println(IMPORTED_FIELD); importedMethod(); - new InnerClasses.PackagePrivateInnerClass(); - new InnerClasses.PackagePrivateInnerClassWithConstructor(); - new InnerClasses.ClassWithPackagePrivateConstructor(); + new InnerClasses.PackagePrivateInnerClass(); + new InnerClasses.PackagePrivateInnerClassWithConstructor(); + new InnerClasses.ClassWithPackagePrivateConstructor(); } } \ No newline at end of file diff --git a/plugins/InspectionGadgets/test/com/siyeh/igtest/dependency/suspicious_package_private_access/src/AccessingProtectedMembers.java b/plugins/InspectionGadgets/test/com/siyeh/igtest/dependency/suspicious_package_private_access/src/AccessingProtectedMembers.java index 1950836066f6..db8d6a6863aa 100644 --- a/plugins/InspectionGadgets/test/com/siyeh/igtest/dependency/suspicious_package_private_access/src/AccessingProtectedMembers.java +++ b/plugins/InspectionGadgets/test/com/siyeh/igtest/dependency/suspicious_package_private_access/src/AccessingProtectedMembers.java @@ -5,8 +5,8 @@ class AccessingProtectedMembersNotFromSubclass { ProtectedMembers aClass = new ProtectedMembers(); aClass.method(); ProtectedMembers.staticMethod(); - new ProtectedConstructors(); - new ProtectedConstructors(1); + new ProtectedConstructors(); + new ProtectedConstructors(1); new ProtectedConstructors() {}; new ProtectedConstructors(1) {}; } diff --git a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/dependency/SuspiciousPackagePrivateAccessInspectionTestCase.java b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/dependency/SuspiciousPackagePrivateAccessInspectionTestCase.java index 53b9164c761c..f2babd23d719 100644 --- a/plugins/InspectionGadgets/testsrc/com/siyeh/ig/dependency/SuspiciousPackagePrivateAccessInspectionTestCase.java +++ b/plugins/InspectionGadgets/testsrc/com/siyeh/ig/dependency/SuspiciousPackagePrivateAccessInspectionTestCase.java @@ -20,7 +20,7 @@ import org.jetbrains.jps.model.java.JavaSourceRootType; import java.io.File; import java.io.IOException; -public class SuspiciousPackagePrivateAccessInspectionTestCase extends LightInspectionTestCase { +public abstract class SuspiciousPackagePrivateAccessInspectionTestCase extends LightInspectionTestCase { private final ProjectWithDepModuleDescriptor myProjectDescriptor = new ProjectWithDepModuleDescriptor(LanguageLevel.HIGHEST); private final String myExtension; diff --git a/plugins/devkit/devkit-core/src/inspections/missingApi/MissingRecentApiInspection.kt b/plugins/devkit/devkit-core/src/inspections/missingApi/MissingRecentApiInspection.kt index e8d7952be0b0..b08980fd3837 100644 --- a/plugins/devkit/devkit-core/src/inspections/missingApi/MissingRecentApiInspection.kt +++ b/plugins/devkit/devkit-core/src/inspections/missingApi/MissingRecentApiInspection.kt @@ -5,11 +5,13 @@ import com.intellij.codeInspection.InspectionProfileEntry import com.intellij.codeInspection.LocalInspectionTool import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.ProblemsHolder +import com.intellij.codeInspection.apiUsage.ApiUsageUastVisitor import com.intellij.openapi.module.Module import com.intellij.openapi.module.ModuleUtil import com.intellij.openapi.util.BuildNumber import com.intellij.psi.PsiElementVisitor import com.intellij.psi.xml.XmlFile +import com.intellij.uast.UastVisitorAdapter import com.intellij.ui.components.JBLabel import com.intellij.util.ui.FormBuilder import org.jetbrains.idea.devkit.actions.DevkitActionsUtil @@ -70,7 +72,12 @@ class MissingRecentApiInspection : LocalInspectionTool() { if (targetedSinceUntilRanges.isEmpty()) { return PsiElementVisitor.EMPTY_VISITOR } - return MissingRecentApiVisitor(holder, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, targetedSinceUntilRanges) + return UastVisitorAdapter( + ApiUsageUastVisitor( + MissingRecentApiUsageProcessor(holder, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, targetedSinceUntilRanges) + ), + true + ) } override fun createOptionsPanel(): JComponent { diff --git a/plugins/devkit/devkit-core/src/inspections/missingApi/MissingRecentApiUsageProcessor.kt b/plugins/devkit/devkit-core/src/inspections/missingApi/MissingRecentApiUsageProcessor.kt new file mode 100644 index 000000000000..6754a8066194 --- /dev/null +++ b/plugins/devkit/devkit-core/src/inspections/missingApi/MissingRecentApiUsageProcessor.kt @@ -0,0 +1,139 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.idea.devkit.inspections.missingApi + +import com.intellij.codeInsight.AnnotationUtil +import com.intellij.codeInsight.ExternalAnnotationsManager +import com.intellij.codeInsight.daemon.impl.analysis.HighlightMessageUtil +import com.intellij.codeInspection.ProblemHighlightType +import com.intellij.codeInspection.ProblemsHolder +import com.intellij.codeInspection.apiUsage.ApiUsageProcessor +import com.intellij.openapi.util.BuildNumber +import com.intellij.psi.* +import com.intellij.psi.util.PsiUtil +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.idea.devkit.DevKitBundle +import org.jetbrains.uast.* + +/** + * PSI visitor containing implementation of [MissingRecentApiInspection], + * which reports usages of APIs that are not available in old IDE builds matching the + * "since" constraint of the plugin. + */ +class MissingRecentApiUsageProcessor( + private val holder: ProblemsHolder, + private val highlightType: ProblemHighlightType, + private val targetedSinceUntilRanges: List +) : ApiUsageProcessor { + + companion object { + val AVAILABLE_SINCE_ANNOTATION: String = ApiStatus.AvailableSince::class.java.canonicalName + } + + override fun processReference(sourceNode: UElement, target: PsiModifierListOwner, qualifier: UExpression?) { + val elementToHighlight = sourceNode.sourcePsi ?: return + checkApiIsRecent(target, elementToHighlight) + } + + override fun processConstructorInvocation( + sourceNode: UElement, + instantiatedClass: PsiClass, + constructor: PsiMethod?, + subclassDeclaration: UClass? + ) { + val elementToHighlight = sourceNode.sourcePsi ?: return + if (constructor != null) { + checkApiIsRecent(constructor, elementToHighlight) + } + else { + val availableSince = findEmptyConstructorAnnotations(instantiatedClass)?.getAvailableSinceBuildNumber() ?: return + val brokenRanges = targetedSinceUntilRanges.filter { it.someBuildsAreNotCovered(availableSince) } + if (brokenRanges.isNotEmpty()) { + registerDefaultConstructorProblem(instantiatedClass, elementToHighlight, availableSince, brokenRanges) + } + } + } + + override fun processMethodOverriding(method: UMethod, overriddenMethod: PsiMethod) { + val availableSince = overriddenMethod.getApiSinceBuildNumber() ?: return + val brokenRanges = targetedSinceUntilRanges.filter { it.someBuildsAreNotCovered(availableSince) } + if (brokenRanges.isNotEmpty()) { + val aClass = overriddenMethod.containingClass ?: return + val methodNameElement = method.uastAnchor.sourcePsiElement ?: return + val description = DevKitBundle.message( + "inspections.api.overrides.method.available.only.since", + aClass.getPresentableName(), + availableSince.asString(), + brokenRanges.joinToString { it.asString() } + ) + holder.registerProblem(methodNameElement, description, highlightType) + } + } + + private fun registerDefaultConstructorProblem( + constructorOwner: PsiClass, + elementToHighlight: PsiElement, + apiSinceBuildNumber: BuildNumber, + brokenRanges: List + ) { + val description = DevKitBundle.message( + "inspections.api.constructor.only.since", + constructorOwner.qualifiedName, + apiSinceBuildNumber.asString(), + brokenRanges.joinToString { it.asString() } + ) + holder.registerProblem(elementToHighlight, description, highlightType) + } + + private fun findEmptyConstructorAnnotations(psiClass: PsiClass): List? { + // Default constructor of a class, which is not represented in PSI, can be externally annotated (IDEA-200832). + return ExternalAnnotationsManager.getInstance(psiClass.project) + .findDefaultConstructorExternalAnnotations(psiClass, AVAILABLE_SINCE_ANNOTATION) + } + + private fun checkApiIsRecent(modifierListOwner: PsiModifierListOwner, elementToHighlight: PsiElement) { + val presentableName = modifierListOwner.getPresentableName() + val availableSince = modifierListOwner.getApiSinceBuildNumber() ?: return + val brokenRanges = targetedSinceUntilRanges.filter { it.someBuildsAreNotCovered(availableSince) } + if (brokenRanges.isNotEmpty()) { + val description = DevKitBundle.message( + "inspections.api.available.only.since", + presentableName, + availableSince.asString(), + brokenRanges.joinToString { it.asString() } + ) + holder.registerProblem(elementToHighlight, description, highlightType) + } + } + + /** + * Returns the first build number when `this` API element was added. + */ + private fun PsiModifierListOwner.getApiSinceBuildNumber(): BuildNumber? { + val externalAnnotations = AnnotationUtil.findAllAnnotations(this, listOf(AVAILABLE_SINCE_ANNOTATION), false) + if (externalAnnotations.isEmpty()) return null + return externalAnnotations.getAvailableSinceBuildNumber() + } + + private fun List.getAvailableSinceBuildNumber(): BuildNumber? = + asSequence() + .mapNotNull { annotation -> + AnnotationUtil.getDeclaredStringAttributeValue(annotation, "value")?.let { + BuildNumber.fromStringOrNull(it) + } + } + .min() + + private fun SinceUntilRange.someBuildsAreNotCovered(apiSinceBuildNumber: BuildNumber) = + sinceBuild == null || sinceBuild < apiSinceBuildNumber + + private fun PsiElement.getPresentableName(): String? { + //Annotation attribute methods don't have parameters. + return if (this is PsiMethod && PsiUtil.isAnnotationMethod(this)) { + name + } + else { + HighlightMessageUtil.getSymbolName(this) + } + } + +} \ No newline at end of file diff --git a/plugins/devkit/devkit-core/src/inspections/missingApi/MissingRecentApiVisitor.kt b/plugins/devkit/devkit-core/src/inspections/missingApi/MissingRecentApiVisitor.kt deleted file mode 100644 index 5428d812ea93..000000000000 --- a/plugins/devkit/devkit-core/src/inspections/missingApi/MissingRecentApiVisitor.kt +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -package org.jetbrains.idea.devkit.inspections.missingApi - -import com.intellij.codeInsight.AnnotationUtil -import com.intellij.codeInsight.ExternalAnnotationsManager -import com.intellij.codeInsight.daemon.impl.analysis.HighlightMessageUtil -import com.intellij.codeInspection.ProblemHighlightType -import com.intellij.codeInspection.ProblemsHolder -import com.intellij.codeInspection.apiUsage.ApiUsageVisitorBase -import com.intellij.openapi.util.BuildNumber -import com.intellij.psi.* -import com.intellij.psi.util.PsiUtil -import org.jetbrains.annotations.ApiStatus -import org.jetbrains.idea.devkit.DevKitBundle -import org.jetbrains.uast.UImportStatement -import org.jetbrains.uast.getUastParentOfType - -/** - * PSI visitor containing implementation of [MissingRecentApiInspection], - * which reports usages of APIs that are not available in old IDE builds matching the - * "since" constraint of the plugin. - */ -class MissingRecentApiVisitor( - private val holder: ProblemsHolder, - private val highlightType: ProblemHighlightType, - private val targetedSinceUntilRanges: List -) : ApiUsageVisitorBase() { - - companion object { - val AVAILABLE_SINCE_ANNOTATION: String = ApiStatus.AvailableSince::class.java.canonicalName - } - - override fun shouldProcessReferences(element: PsiElement) = !element.isInsideImportStatement() - - private fun PsiElement.isInsideImportStatement() = getUastParentOfType() != null - - override fun processReference(reference: PsiReference) { - if (reference is ResolvingHint && !(reference as ResolvingHint).canResolveTo(PsiModifierListOwner::class.java)) { - return - } - val resolved = reference.resolve() - if (resolved != null) { - val elementToHighlight = getElementToHighlight(reference) - checkMissingApi(resolved, elementToHighlight) - } - } - - private fun getElementToHighlight(reference: PsiReference): PsiElement { - if (reference is PsiJavaCodeReferenceElement) { - val referenceNameElement = reference.referenceNameElement - if (referenceNameElement != null) { - return referenceNameElement - } - } - return reference.element - } - - override fun processConstructorInvocation(instantiatedClass: PsiJavaCodeReferenceElement, constructor: PsiMethod) { - checkMissingApi(constructor, instantiatedClass) - } - - override fun processDefaultConstructorInvocation(instantiatedClass: PsiJavaCodeReferenceElement) { - val createdClass = instantiatedClass.resolve() as? PsiClass ?: return - checkClassDefaultConstructorApi(createdClass, instantiatedClass) - } - - private fun checkClassDefaultConstructorApi(psiClass: PsiClass, elementToHighlight: PsiElement) { - val availableSince = findEmptyConstructorAnnotations(psiClass)?.getAvailableSinceBuildNumber() ?: return - val brokenRanges = targetedSinceUntilRanges.filter { it.someBuildsAreNotCovered(availableSince) } - if (brokenRanges.isNotEmpty()) { - registerDefaultConstructorProblem(psiClass, elementToHighlight, availableSince, brokenRanges) - } - } - - override fun processEmptyConstructorOfSuperClassImplicitInvocationAtSubclassDeclaration( - subclass: PsiClass, - superClass: PsiClass - ) { - val availableSince = findEmptyConstructorAnnotations(superClass)?.getAvailableSinceBuildNumber() ?: return - val brokenRanges = targetedSinceUntilRanges.filter { it.someBuildsAreNotCovered(availableSince) } - if (brokenRanges.isNotEmpty()) { - val asAnonymous = subclass as? PsiAnonymousClass - if (asAnonymous != null) { - val argumentList = asAnonymous.argumentList - if (argumentList != null && !argumentList.isEmpty) return - } - val elementToHighlight = asAnonymous?.baseClassReference ?: subclass.nameIdentifier - if (elementToHighlight != null) { - registerDefaultConstructorProblem(superClass, elementToHighlight, availableSince, brokenRanges) - } - } - } - - override fun processEmptyConstructorOfSuperClassImplicitInvocationAtSubclassConstructor( - superClass: PsiClass, - subclassConstructor: PsiMethod - ) { - val nameIdentifier = subclassConstructor.nameIdentifier ?: return - checkClassDefaultConstructorApi(superClass, nameIdentifier) - } - - override fun processMethodOverriding(method: PsiMethod, overriddenMethod: PsiMethod) { - val availableSince = overriddenMethod.getApiSinceBuildNumber() ?: return - val brokenRanges = targetedSinceUntilRanges.filter { it.someBuildsAreNotCovered(availableSince) } - if (brokenRanges.isNotEmpty()) { - val aClass = overriddenMethod.containingClass ?: return - val nameIdentifier = method.nameIdentifier ?: return - val description = DevKitBundle.message( - "inspections.api.overrides.method.available.only.since", - aClass.getPresentableName(), - availableSince.asString(), - brokenRanges.joinToString { it.asString() } - ) - holder.registerProblem(nameIdentifier, description, highlightType) - } - } - - private fun registerDefaultConstructorProblem( - constructorOwner: PsiClass, - elementToHighlight: PsiElement, - apiSinceBuildNumber: BuildNumber, - brokenRanges: List - ) { - val description = DevKitBundle.message( - "inspections.api.constructor.only.since", - constructorOwner.qualifiedName, - apiSinceBuildNumber.asString(), - brokenRanges.joinToString { it.asString() } - ) - holder.registerProblem(elementToHighlight, description, highlightType) - } - - private fun findEmptyConstructorAnnotations(psiClass: PsiClass): List? { - val constructors = psiClass.constructors - if (constructors.isEmpty()) { - /* - Default constructor of a class, which is not present in source code, - can be externally annotated (IDEA-200832). - */ - return ExternalAnnotationsManager.getInstance(psiClass.project) - .findDefaultConstructorExternalAnnotations(psiClass, AVAILABLE_SINCE_ANNOTATION) - } else { - val emptyConstructor = constructors.find { it.parameterList.isEmpty } - if (emptyConstructor != null) { - return AnnotationUtil.findAllAnnotations(emptyConstructor, listOf(AVAILABLE_SINCE_ANNOTATION), false) - } - return null - } - } - - /** - * Checks if the API element [refElement] is annotated - * with [org.jetbrains.annotations.ApiStatus.AvailableSince]. - * If so, it checks plugin's [since, until] compatibility range - * and registers a problem if the API was first introduced later - * than plugin's `since` build. - */ - private fun checkMissingApi(refElement: PsiElement, elementToHighlight: PsiElement) { - if (refElement !is PsiModifierListOwner) return - - val availableSince = refElement.getApiSinceBuildNumber() ?: return - val brokenRanges = targetedSinceUntilRanges.filter { it.someBuildsAreNotCovered(availableSince) } - if (brokenRanges.isNotEmpty()) { - val description = DevKitBundle.message( - "inspections.api.available.only.since", - refElement.getPresentableName(), - availableSince.asString(), - brokenRanges.joinToString { it.asString() } - ) - holder.registerProblem(elementToHighlight, description, highlightType) - } - } - - /** - * Returns the first build number when `this` API element was added. - */ - private fun PsiModifierListOwner.getApiSinceBuildNumber(): BuildNumber? { - val externalAnnotations = AnnotationUtil.findAllAnnotations(this, listOf(AVAILABLE_SINCE_ANNOTATION), false) - if (externalAnnotations.isEmpty()) return null - return externalAnnotations.getAvailableSinceBuildNumber() - } - - private fun List.getAvailableSinceBuildNumber(): BuildNumber? = - asSequence() - .mapNotNull { annotation -> - AnnotationUtil.getDeclaredStringAttributeValue(annotation, "value")?.let { - BuildNumber.fromStringOrNull(it) - } - } - .min() - - private fun SinceUntilRange.someBuildsAreNotCovered(apiSinceBuildNumber: BuildNumber) = - sinceBuild == null || sinceBuild < apiSinceBuildNumber - - private fun PsiElement.getPresentableName(): String? { - //Annotation attribute methods don't have parameters. - return if (this is PsiMethod && PsiUtil.isAnnotationMethod(this)) { - name - } else HighlightMessageUtil.getSymbolName(this) - } - -} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/missingApi/library/RecentInterface.java b/plugins/devkit/devkit-java-tests/testData/inspections/missingApi/library/RecentInterface.java new file mode 100644 index 000000000000..24d6bad9d6e8 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/missingApi/library/RecentInterface.java @@ -0,0 +1,4 @@ +package library; + +public interface RecentInterface { +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/missingApi/library/RecentKotlinInterface.kt b/plugins/devkit/devkit-java-tests/testData/inspections/missingApi/library/RecentKotlinInterface.kt new file mode 100644 index 000000000000..6a0182781d82 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/missingApi/library/RecentKotlinInterface.kt @@ -0,0 +1,4 @@ +package library + +interface RecentKotlinInterface { +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/missingApi/library/RecentSamInterface.java b/plugins/devkit/devkit-java-tests/testData/inspections/missingApi/library/RecentSamInterface.java new file mode 100644 index 000000000000..b99f4daad6ab --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/inspections/missingApi/library/RecentSamInterface.java @@ -0,0 +1,5 @@ +package library; + +public interface RecentSamInterface { + int singleMethod(); +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/missingApi/plugin/missingApiUsages.java b/plugins/devkit/devkit-java-tests/testData/inspections/missingApi/plugin/missingApiUsages.java index 45ffa454aafc..885aa43338e0 100644 --- a/plugins/devkit/devkit-java-tests/testData/inspections/missingApi/plugin/missingApiUsages.java +++ b/plugins/devkit/devkit-java-tests/testData/inspections/missingApi/plugin/missingApiUsages.java @@ -1,5 +1,6 @@ -package client; +package plugin; +import java.util.function.Consumer; import java.util.function.Supplier; import library.RecentClass; @@ -25,6 +26,22 @@ class A { public RecentClass r = null; public RecentKotlinClass r2 = null; + public void parameters( + RecentClass rc, + RecentKotlinClass rkc + ) { + } + + public void array() { + RecentClass[] a1 = new RecentClass[0]; + RecentKotlinClass[] a2 = new RecentKotlinClass[0]; + } + + public void classAccess() { + Class<RecentClass> o = RecentClass.class; + Class<RecentKotlinClass> o2 = RecentKotlinClass.class; + } + public void m1(OldClass oc, OldKotlinClass okc) { String s = oc.recentField; String s2 = okc.recentField; @@ -66,14 +83,18 @@ class A { } }); } + + public void m8() { + Consumer methodReference = OldClass::recentMethod; + } } -class B extends OldClass { - //implicit call to default "recent" constructor available in source code. +class B extends OldClass { + //implicit call to empty "recent" constructor available in source code. } -class BK extends OldKotlinClass { - //implicit call to default "recent" constructor available in source code. +class BK extends OldKotlinClass { + //implicit call to empty "recent" constructor available in source code. } class C extends OldClass { @@ -104,7 +125,7 @@ class DK extends OldKotlinClassWithDefaultConstructor { +class DK extends OldKotlinClassWithDefaultConstructor { //implicit call to default "recent" constructor that is NOT available in source code. } @@ -112,16 +133,16 @@ class E() {} - public E(int x) { - super(); + public E(int x) { + super(); } } //Class with constructors delegating to default "recent" constructor. class EK extends OldKotlinClassWithDefaultConstructor { - public EK() {} + public EK() {} - public EK(int x) { + public EK(int x) { super(); } } diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/missingApi/plugin/missingApiUsages.kt b/plugins/devkit/devkit-java-tests/testData/inspections/missingApi/plugin/missingApiUsages.kt index 921a7167d2af..e3a7f9b59c20 100644 --- a/plugins/devkit/devkit-java-tests/testData/inspections/missingApi/plugin/missingApiUsages.kt +++ b/plugins/devkit/devkit-java-tests/testData/inspections/missingApi/plugin/missingApiUsages.kt @@ -3,55 +3,124 @@ package client import java.util.function.Supplier import library.RecentClass +import library.RecentKotlinClass + +import library.RecentInterface +import library.RecentKotlinInterface + +import library.RecentSamInterface + import library.OldClass +import library.OldKotlinClass + import library.OldClassWithDefaultConstructor +import library.OldKotlinClassWithDefaultConstructor import library.RecentAnnotation +import library.RecentKotlinAnnotation + import library.OldAnnotation +import library.OldKotlinAnnotation import library.RecentClass.* +import library.* class A { var r: RecentClass? = null + var kr: RecentKotlinClass? = null - fun m1(oc: OldClass): Any { - return oc.recentField + @Suppress("UNUSED_PARAMETER") + fun parameters( + rc: RecentClass, + krc: RecentKotlinClass + ) { } - fun m2(oc: OldClass) { + fun arrays() { + arrayOfNulls<RecentClass>(0) + arrayOfNulls<RecentKotlinClass>(0) + } + + fun classAccess() { + RecentClass::class.java + RecentKotlinClass::class.java + } + + fun m1(oc: OldClass, okc: OldKotlinClass) { + oc.recentField + okc.recentField + } + + fun m2(oc: OldClass, okc: OldKotlinClass) { oc.recentMethod() + okc.recentMethod() } fun m3() { OldClass.recentStaticMethod() + OldKotlinClass.recentStaticMethod() } - fun m4(): Any { - return OldClass("") + fun m4() { + OldClass("") + OldKotlinClass("") } - fun m5(): Any { - //anonymous class - return object : OldClass() { + fun anonymousClasses() { + object : OldClass(), RecentInterface { + } + object : OldKotlinClass(), RecentKotlinInterface { } } - fun m6(): Any { - return Supplier { OldClass() } + fun m6() { + Supplier { OldClass() } + Supplier { OldKotlinClass() } } + + fun topLevelFunction(s: String) { + recentTopLevelFunction() + s.recentExtensionFunction() + s.recentInlineExtensionFunction { "" } + } + + fun singleAbstractMethod() { + RecentSamInterface { 42 } + } +} + +class Overrider : OldClass(42) { + //overrides "recent" method. + override fun recentMethod() { } +} + +class KOverrider : OldKotlinClass(42) { + //overrides "recent" method. + override fun recentMethod() { } } class B { @RecentAnnotation - fun m1() { + fun markedWithAnnotation() { + } + + @RecentKotlinAnnotation + fun markedWithKotlinAnnotation() { } @OldAnnotation( oldParam = 0, recentParam = 1 ) - fun m2() { + fun recentAnnotationParam() { + } + + @OldKotlinAnnotation( + oldParam = 0, + recentParam = 1 + ) + fun recentKotlinAnnotationParam() { } } \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/missingApi/since-2.0/library/annotations.xml b/plugins/devkit/devkit-java-tests/testData/inspections/missingApi/since-2.0/library/annotations.xml index 4fd3a329b7fe..c7488239601f 100644 --- a/plugins/devkit/devkit-java-tests/testData/inspections/missingApi/since-2.0/library/annotations.xml +++ b/plugins/devkit/devkit-java-tests/testData/inspections/missingApi/since-2.0/library/annotations.xml @@ -5,6 +5,16 @@ + + + + + + + + + + @@ -52,6 +62,11 @@ + + + + + @@ -87,6 +102,11 @@ + + + + + diff --git a/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/inspections/missingApi/MissingRecentApiInspectionTestBase.kt b/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/inspections/missingApi/MissingRecentApiInspectionTestBase.kt index 5f2a5432775b..49776f4e963d 100644 --- a/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/inspections/missingApi/MissingRecentApiInspectionTestBase.kt +++ b/plugins/devkit/devkit-kotlin-tests/testSrc/org/jetbrains/idea/devkit/kotlin/inspections/missingApi/MissingRecentApiInspectionTestBase.kt @@ -10,7 +10,7 @@ import com.intellij.testFramework.TestDataPath import org.jetbrains.idea.devkit.DevkitJavaTestsUtil import org.jetbrains.idea.devkit.inspections.PluginModuleTestCase import org.jetbrains.idea.devkit.inspections.missingApi.MissingRecentApiInspection -import org.jetbrains.idea.devkit.inspections.missingApi.MissingRecentApiVisitor +import org.jetbrains.idea.devkit.inspections.missingApi.MissingRecentApiUsageProcessor import org.jetbrains.idea.devkit.kotlin.inspections.missingApi.project.PluginProjectWithIdeaJdkDescriptor import org.jetbrains.idea.devkit.kotlin.inspections.missingApi.project.PluginProjectWithIdeaLibraryDescriptor @@ -60,12 +60,15 @@ abstract class MissingRecentApiInspectionTestBase : PluginModuleTestCase() { private fun configureLibraryFiles() { myFixture.configureByFiles( "library/RecentClass.java", + "library/RecentInterface.java", + "library/RecentSamInterface.java", "library/RecentAnnotation.java", "library/OldClass.java", "library/OldClassWithDefaultConstructor.java", "library/OldAnnotation.java", "library/RecentKotlinClass.kt", + "library/RecentKotlinInterface.kt", "library/RecentKotlinUtils.kt", "library/RecentKotlinAnnotation.kt", @@ -90,7 +93,7 @@ abstract class MissingRecentApiInspectionTestBase : PluginModuleTestCase() { private fun assertAnnotationsFoundForClass(className: String) { val psiClass = JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project))!! - val annotations = AnnotationUtil.findAllAnnotations(psiClass, listOf(MissingRecentApiVisitor.AVAILABLE_SINCE_ANNOTATION), false) + val annotations = AnnotationUtil.findAllAnnotations(psiClass, listOf(MissingRecentApiUsageProcessor.AVAILABLE_SINCE_ANNOTATION), false) assertNotEmpty(annotations) }