IDEA-210618: Reference implementation of PSI visitor for UAST-supporting languages that detects usages of APIs in source code.

This commit is contained in:
Sergey Patrikeev
2019-04-18 19:58:17 +03:00
parent 06e8977358
commit 95cc10cd5e
44 changed files with 1125 additions and 928 deletions
@@ -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.
* <br>
* 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.
* <br>
* 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.
* <pre>{@code
* class Test {
* //The default implicit constructor
* }
*
* void foo() {
* Object o = new Test(); //The default constructor invocation
* }}</pre>
* {@code instantiatedClass} is a reference to a class being instantiated.
* <br>
* 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.
* <pre>
* {@code
* class Subclass extends Super {
* <implicit invocation of Super() in default constructor>
* }}
* </pre>
*/
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.
*
* <pre>
* {@code
* class Subclass extends Super {
* Subclass() {
* <implicit invocation of Super()>
* }
* }}
* </pre>
*/
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) {}
}
@@ -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.<br>
* The invoked constructor may be the default constructor, which is not declared in source code.<br>
* 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) {}
}
@@ -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
}
}
@@ -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<PsiStatement>): 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)
}
}
}
@@ -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<PsiAnnotation> externalDeprecated = ExternalAnnotationsManager
.getInstance(aClass.getProject())
.findDefaultConstructorExternalAnnotations(aClass, CommonClassNames.JAVA_LANG_DEPRECATED);
return externalDeprecated != null &&
ContainerUtil.exists(externalDeprecated, annotation -> isForRemovalAttributeSet(annotation) == myForRemoval);
}
}
@@ -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<PsiAnnotation> 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);
}
}
@@ -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
@@ -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;
}
}
@@ -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;
}
@@ -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);
}
}
@@ -3,7 +3,7 @@
<problem>
<file>Test.java</file>
<line>5</line>
<description>Default constructor in C is deprecated</description>
<description>'C()' is deprecated</description>
</problem>
<problem>
<file>Test.java</file>
@@ -3,7 +3,7 @@
<problem>
<file>Test.java</file>
<line>6</line>
<description>Default constructor in C is deprecated</description>
<description>'C()' is deprecated</description>
</problem>
</problems>
@@ -12,7 +12,7 @@
</problem>
<problem>
<file>Test.java</file>
<line>16</line>
<line>17</line>
<description>Default constructor in 'a.A' is deprecated</description>
</problem>
</problems>
@@ -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();
}
}
@@ -2,7 +2,7 @@ class C {
@Deprecated(forRemoval=true) C() { }
}
class <error descr="Default constructor in 'C' is deprecated and marked for removal">D</error> extends C {
class <error descr="'C()' is deprecated and marked for removal">D</error> extends C {
}
class P {
@@ -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<PsiAnnotation> annotations
);
}
@@ -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<String> getAnnotations();
protected abstract void createProblem(@NotNull PsiReference reference,
@NotNull PsiModifierListOwner annotatedTarget,
@NotNull List<PsiAnnotation> 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<PsiAnnotation> 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<String> myAnnotations;
private final AnnotatedApiUsageProcessor myAnnotatedApiProcessor;
private AnnotatedApiUsageProcessorBridge(boolean ignoreInsideImports,
@NotNull List<String> 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<PsiAnnotation> annotations = AnnotationUtil.findAllAnnotations(target, myAnnotations, false);
if (annotations.isEmpty()) {
return false;
}
myAnnotatedApiProcessor.processAnnotatedTarget(sourceNode, target, annotations);
return true;
}
}
}
@@ -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<String> myAnnotations;
public AnnotatedElementVisitorBase(boolean ignoreInsideImports, @NotNull List<String> 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<PsiAnnotation> 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<PsiAnnotation> annotations = AnnotationUtil.findAllAnnotations(annotationsOwner, myAnnotations, false);
if (!annotations.isEmpty()) {
processAnnotatedTarget(reference, annotationsOwner, annotations);
}
}
}
@Override
public void processConstructorInvocation(@NotNull PsiJavaCodeReferenceElement instantiatedClass, @NotNull PsiMethod constructor) {
List<PsiAnnotation> 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;
}
}
@@ -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<PsiAnnotation> 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<PsiAnnotation> 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);
}
};
}
}
@@ -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<PsiAnnotation> 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<PsiAnnotation> 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
@@ -35,7 +35,7 @@ public class ScheduledForRemovalElementsIgnoreImportsTest {
s = <warning descr="'pkg.AnnotatedClass' is scheduled for removal in 123.456">AnnotatedClass</warning>.<warning descr="'ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS' is scheduled for removal in 123.456">ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS</warning>;
<warning descr="'pkg.AnnotatedClass' is scheduled for removal in 123.456">AnnotatedClass</warning>.<warning descr="'staticAnnotatedMethodInAnnotatedClass()' is scheduled for removal in 123.456">staticAnnotatedMethodInAnnotatedClass</warning>();
<warning descr="'pkg.AnnotatedClass' is scheduled for removal in 123.456">AnnotatedClass</warning> annotatedClassInstanceViaAnnotatedConstructor = new <warning descr="'AnnotatedClass(java.lang.String)' is scheduled for removal in 123.456"><warning descr="'pkg.AnnotatedClass' is scheduled for removal in 123.456">AnnotatedClass</warning></warning>("");
<warning descr="'pkg.AnnotatedClass' is scheduled for removal in 123.456">AnnotatedClass</warning> annotatedClassInstanceViaAnnotatedConstructor = new <warning descr="'AnnotatedClass(java.lang.String)' is scheduled for removal in 123.456">AnnotatedClass</warning>("");
s = annotatedClassInstanceViaAnnotatedConstructor.<warning descr="'annotatedFieldInAnnotatedClass' is scheduled for removal in 123.456">annotatedFieldInAnnotatedClass</warning>;
annotatedClassInstanceViaAnnotatedConstructor.<warning descr="'annotatedMethodInAnnotatedClass()' is scheduled for removal in 123.456">annotatedMethodInAnnotatedClass</warning>();
s = <warning descr="'ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS' is scheduled for removal in 123.456">ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS</warning>;
@@ -35,7 +35,7 @@ public class ScheduledForRemovalElementsTest {
s = <warning descr="'pkg.AnnotatedClass' is scheduled for removal in 123.456">AnnotatedClass</warning>.<warning descr="'ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS' is scheduled for removal in 123.456">ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS</warning>;
<warning descr="'pkg.AnnotatedClass' is scheduled for removal in 123.456">AnnotatedClass</warning>.<warning descr="'staticAnnotatedMethodInAnnotatedClass()' is scheduled for removal in 123.456">staticAnnotatedMethodInAnnotatedClass</warning>();
<warning descr="'pkg.AnnotatedClass' is scheduled for removal in 123.456">AnnotatedClass</warning> annotatedClassInstanceViaAnnotatedConstructor = new <warning descr="'AnnotatedClass(java.lang.String)' is scheduled for removal in 123.456"><warning descr="'pkg.AnnotatedClass' is scheduled for removal in 123.456">AnnotatedClass</warning></warning>("");
<warning descr="'pkg.AnnotatedClass' is scheduled for removal in 123.456">AnnotatedClass</warning> annotatedClassInstanceViaAnnotatedConstructor = new <warning descr="'AnnotatedClass(java.lang.String)' is scheduled for removal in 123.456">AnnotatedClass</warning>("");
s = annotatedClassInstanceViaAnnotatedConstructor.<warning descr="'annotatedFieldInAnnotatedClass' is scheduled for removal in 123.456">annotatedFieldInAnnotatedClass</warning>;
annotatedClassInstanceViaAnnotatedConstructor.<warning descr="'annotatedMethodInAnnotatedClass()' is scheduled for removal in 123.456">annotatedMethodInAnnotatedClass</warning>();
s = <warning descr="'ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS' is scheduled for removal in 123.456">ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS</warning>;
@@ -35,7 +35,7 @@ public class UnstableElementsIgnoreImportsTest {
s = <warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>.<warning descr="'ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS' is marked unstable">ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS</warning>;
<warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>.<warning descr="'staticAnnotatedMethodInAnnotatedClass()' is marked unstable">staticAnnotatedMethodInAnnotatedClass</warning>();
<warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning> annotatedClassInstanceViaAnnotatedConstructor = new <warning descr="'AnnotatedClass(java.lang.String)' is marked unstable"><warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning></warning>("");
<warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning> annotatedClassInstanceViaAnnotatedConstructor = new <warning descr="'AnnotatedClass(java.lang.String)' is marked unstable">AnnotatedClass</warning>("");
s = annotatedClassInstanceViaAnnotatedConstructor.<warning descr="'annotatedFieldInAnnotatedClass' is marked unstable">annotatedFieldInAnnotatedClass</warning>;
annotatedClassInstanceViaAnnotatedConstructor.<warning descr="'annotatedMethodInAnnotatedClass()' is marked unstable">annotatedMethodInAnnotatedClass</warning>();
s = <warning descr="'ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS' is marked unstable">ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS</warning>;
@@ -35,7 +35,7 @@ public class UnstableElementsTest {
s = <warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>.<warning descr="'ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS' is marked unstable">ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS</warning>;
<warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>.<warning descr="'staticAnnotatedMethodInAnnotatedClass()' is marked unstable">staticAnnotatedMethodInAnnotatedClass</warning>();
<warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning> annotatedClassInstanceViaAnnotatedConstructor = new <warning descr="'AnnotatedClass(java.lang.String)' is marked unstable"><warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning></warning>("");
<warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning> annotatedClassInstanceViaAnnotatedConstructor = new <warning descr="'AnnotatedClass(java.lang.String)' is marked unstable">AnnotatedClass</warning>("");
s = annotatedClassInstanceViaAnnotatedConstructor.<warning descr="'annotatedFieldInAnnotatedClass' is marked unstable">annotatedFieldInAnnotatedClass</warning>;
annotatedClassInstanceViaAnnotatedConstructor.<warning descr="'annotatedMethodInAnnotatedClass()' is marked unstable">annotatedMethodInAnnotatedClass</warning>();
s = <warning descr="'ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS' is marked unstable">ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS</warning>;
@@ -27,7 +27,7 @@ class ScheduledForRemovalElementsIgnoreImportsTest {
fun test() {
var s = <warning descr="'pkg.AnnotatedClass' is scheduled for removal in 123.456">AnnotatedClass</warning>.NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS
<warning descr="'pkg.AnnotatedClass' is scheduled for removal in 123.456">AnnotatedClass</warning>.staticNonAnnotatedMethodInAnnotatedClass()
val annotatedClassInstanceViaNonAnnotatedConstructor : <warning descr="'pkg.AnnotatedClass' is scheduled for removal in 123.456">AnnotatedClass</warning> = AnnotatedClass()
val annotatedClassInstanceViaNonAnnotatedConstructor : <warning descr="'pkg.AnnotatedClass' is scheduled for removal in 123.456">AnnotatedClass</warning> = <warning descr="'pkg.AnnotatedClass' is scheduled for removal in 123.456">AnnotatedClass</warning>()
s = annotatedClassInstanceViaNonAnnotatedConstructor.nonAnnotatedFieldInAnnotatedClass
annotatedClassInstanceViaNonAnnotatedConstructor.nonAnnotatedMethodInAnnotatedClass()
s = NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS
@@ -27,7 +27,7 @@ class ScheduledForRemovalElementsTest {
fun test() {
var s = <warning descr="'pkg.AnnotatedClass' is scheduled for removal in 123.456">AnnotatedClass</warning>.NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS
<warning descr="'pkg.AnnotatedClass' is scheduled for removal in 123.456">AnnotatedClass</warning>.staticNonAnnotatedMethodInAnnotatedClass()
val annotatedClassInstanceViaNonAnnotatedConstructor : <warning descr="'pkg.AnnotatedClass' is scheduled for removal in 123.456">AnnotatedClass</warning> = AnnotatedClass()
val annotatedClassInstanceViaNonAnnotatedConstructor : <warning descr="'pkg.AnnotatedClass' is scheduled for removal in 123.456">AnnotatedClass</warning> = <warning descr="'pkg.AnnotatedClass' is scheduled for removal in 123.456">AnnotatedClass</warning>()
s = annotatedClassInstanceViaNonAnnotatedConstructor.nonAnnotatedFieldInAnnotatedClass
annotatedClassInstanceViaNonAnnotatedConstructor.nonAnnotatedMethodInAnnotatedClass()
s = NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS
@@ -8,17 +8,17 @@ import xxx.StaticMembers.*
*/
@Suppress("UNUSED_VARIABLE")
class AccessingPackagePrivateMembers {
private val property = <warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass()</warning>
private val property = <warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass</warning>()
fun main() {
<warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass()</warning>
<warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass</warning>()
var variable: <warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass</warning>
val sam = <warning descr="Interface xxx.PackagePrivateInterface is package-private, but declared in a different module 'dep'">PackagePrivateInterface</warning> { "" }
val aClass: PublicClass = PublicClass(1);
val aClass2: PublicClassWithDefaultConstructor = PublicClassWithDefaultConstructor();
<warning descr="Constructor PublicClass.PublicClass() is package-private, but declared in a different module 'dep'">PublicClass()</warning>
<warning descr="Constructor PublicClass.PublicClass(boolean) is package-private, but declared in a different module 'dep'">PublicClass(true)</warning>
<warning descr="Constructor PublicClass.PublicClass() is package-private, but declared in a different module 'dep'">PublicClass</warning>()
<warning descr="Constructor PublicClass.PublicClass(boolean) is package-private, but declared in a different module 'dep'">PublicClass</warning>(true)
System.out.println(aClass.publicField)
System.out.println(aClass.<warning descr="Field PublicClass.packagePrivateField is package-private, but declared in a different module 'dep'">packagePrivateField</warning>)
@@ -26,17 +26,17 @@ class AccessingPackagePrivateMembers {
System.out.println(PublicClass.<warning descr="Field PublicClass.PACKAGE_PRIVATE_STATIC_FIELD is package-private, but declared in a different module 'dep'">PACKAGE_PRIVATE_STATIC_FIELD</warning>)
aClass.publicMethod()
aClass.<warning descr="Method PublicClass.packagePrivateMethod() is package-private, but declared in a different module 'dep'">packagePrivateMethod()</warning>
aClass.<warning descr="Method PublicClass.packagePrivateMethod() is package-private, but declared in a different module 'dep'">packagePrivateMethod</warning>()
System.out.println(<warning descr="Field StaticMembers.IMPORTED_FIELD is package-private, but declared in a different module 'dep'">IMPORTED_FIELD</warning>)
<warning descr="Method StaticMembers.importedMethod() is package-private, but declared in a different module 'dep'">importedMethod</warning>()
InnerClasses.<warning descr="Class xxx.InnerClasses.PackagePrivateInnerClass is package-private, but declared in a different module 'dep'">PackagePrivateInnerClass()</warning>
InnerClasses.<warning descr="Constructor PackagePrivateInnerClassWithConstructor.PackagePrivateInnerClassWithConstructor() is package-private, but declared in a different module 'dep'">PackagePrivateInnerClassWithConstructor()</warning>
InnerClasses.<warning descr="Constructor ClassWithPackagePrivateConstructor.ClassWithPackagePrivateConstructor() is package-private, but declared in a different module 'dep'">ClassWithPackagePrivateConstructor()</warning>
InnerClasses.<warning descr="Class xxx.InnerClasses.PackagePrivateInnerClass is package-private, but declared in a different module 'dep'">PackagePrivateInnerClass</warning>()
InnerClasses.<warning descr="Constructor PackagePrivateInnerClassWithConstructor.PackagePrivateInnerClassWithConstructor() is package-private, but declared in a different module 'dep'">PackagePrivateInnerClassWithConstructor</warning>()
InnerClasses.<warning descr="Constructor ClassWithPackagePrivateConstructor.ClassWithPackagePrivateConstructor() is package-private, but declared in a different module 'dep'">ClassWithPackagePrivateConstructor</warning>()
}
companion object {
private val staticProperty = <warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass()</warning>
private val staticProperty = <warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass</warning>()
}
}
@@ -3,10 +3,10 @@ package xxx
class AccessingProtectedMembersNotFromSubclass {
fun foo() {
val aClass: ProtectedMembers = ProtectedMembers()
aClass.<warning descr="Method ProtectedMembers.method() is protected and used not through a subclass here, but declared in a different module 'dep'">method()</warning>
ProtectedMembers.<warning descr="Method ProtectedMembers.staticMethod() is protected and used not through a subclass here, but declared in a different module 'dep'">staticMethod()</warning>
<warning descr="Constructor ProtectedConstructors.ProtectedConstructors() is protected and used not through a subclass here, but declared in a different module 'dep'">ProtectedConstructors()</warning>
<warning descr="Constructor ProtectedConstructors.ProtectedConstructors(int) is protected and used not through a subclass here, but declared in a different module 'dep'">ProtectedConstructors(1)</warning>
aClass.<warning descr="Method ProtectedMembers.method() is protected and used not through a subclass here, but declared in a different module 'dep'">method</warning>()
ProtectedMembers.<warning descr="Method ProtectedMembers.staticMethod() is protected and used not through a subclass here, but declared in a different module 'dep'">staticMethod</warning>()
<warning descr="Constructor ProtectedConstructors.ProtectedConstructors() is protected and used not through a subclass here, but declared in a different module 'dep'">ProtectedConstructors</warning>()
<warning descr="Constructor ProtectedConstructors.ProtectedConstructors(int) is protected and used not through a subclass here, but declared in a different module 'dep'">ProtectedConstructors</warning>(1)
}
}
@@ -18,7 +18,7 @@ class AccessingProtectedMembersFromSubclass : ProtectedMembers() {
ProtectedMembers.staticMethod()
val aClass = ProtectedMembers()
aClass.<warning descr="Method ProtectedMembers.method() is protected and used not through a subclass here, but declared in a different module 'dep'">method()</warning>
aClass.<warning descr="Method ProtectedMembers.method() is protected and used not through a subclass here, but declared in a different module 'dep'">method</warning>()
val myInstance = AccessingProtectedMembersFromSubclass()
myInstance.method()
@@ -27,7 +27,7 @@ class UnstableElementsIgnoreImportsTest {
fun test() {
var s = <warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>.NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS
<warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>.staticNonAnnotatedMethodInAnnotatedClass()
val annotatedClassInstanceViaNonAnnotatedConstructor : <warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning> = AnnotatedClass()
val annotatedClassInstanceViaNonAnnotatedConstructor : <warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning> = <warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>()
s = annotatedClassInstanceViaNonAnnotatedConstructor.nonAnnotatedFieldInAnnotatedClass
annotatedClassInstanceViaNonAnnotatedConstructor.nonAnnotatedMethodInAnnotatedClass()
s = NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS
@@ -27,7 +27,7 @@ class UnstableElementsTest {
fun test() {
var s = <warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>.NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS
<warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>.staticNonAnnotatedMethodInAnnotatedClass()
val annotatedClassInstanceViaNonAnnotatedConstructor : <warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning> = AnnotatedClass()
val annotatedClassInstanceViaNonAnnotatedConstructor : <warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning> = <warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>()
s = annotatedClassInstanceViaNonAnnotatedConstructor.nonAnnotatedFieldInAnnotatedClass
annotatedClassInstanceViaNonAnnotatedConstructor.nonAnnotatedMethodInAnnotatedClass()
s = NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS
@@ -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<IntentionAction> 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<String, ModulesSet> myModuleNameToModulesSet;
private SuspiciousApiUsageProcessor(ProblemsHolder problemsHolder, Map<String, ModulesSet> 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<IntentionAction> 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")
@@ -7,23 +7,23 @@ import static xxx.StaticMembers.*;
* @see PublicClass#packagePrivateField
*/
public class AccessingPackagePrivateMembers {
static Object staticField = <warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">new PackagePrivateClass()</warning>;
Object field = <warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">new PackagePrivateClass()</warning>;
static Object staticField = new <warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass</warning>();
Object field = new <warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass</warning>();
{
<warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">new PackagePrivateClass()</warning>;
new <warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass</warning>();
}
static {
<warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">new PackagePrivateClass()</warning>;
new <warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass</warning>();
}
public void main() {
<warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">new PackagePrivateClass()</warning>;
new <warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass</warning>();
<warning descr="Class xxx.PackagePrivateClass is package-private, but declared in a different module 'dep'">PackagePrivateClass</warning> variable;
PublicClass aClass = new PublicClass(1);
PublicClassWithDefaultConstructor aClass2 = new PublicClassWithDefaultConstructor();
<warning descr="Constructor PublicClass.PublicClass() is package-private, but declared in a different module 'dep'">new PublicClass()</warning>;
<warning descr="Constructor PublicClass.PublicClass(boolean) is package-private, but declared in a different module 'dep'">new PublicClass(true)</warning>;
new <warning descr="Constructor PublicClass.PublicClass() is package-private, but declared in a different module 'dep'">PublicClass</warning>();
new <warning descr="Constructor PublicClass.PublicClass(boolean) is package-private, but declared in a different module 'dep'">PublicClass</warning>(true);
System.out.println(aClass.publicField);
System.out.println(aClass.<warning descr="Field PublicClass.packagePrivateField is package-private, but declared in a different module 'dep'">packagePrivateField</warning>);
@@ -37,8 +37,8 @@ public class AccessingPackagePrivateMembers {
System.out.println(<warning descr="Field StaticMembers.IMPORTED_FIELD is package-private, but declared in a different module 'dep'">IMPORTED_FIELD</warning>);
<warning descr="Method StaticMembers.importedMethod() is package-private, but declared in a different module 'dep'">importedMethod</warning>();
<warning descr="Class xxx.InnerClasses.PackagePrivateInnerClass is package-private, but declared in a different module 'dep'">new InnerClasses.<warning descr="Class xxx.InnerClasses.PackagePrivateInnerClass is package-private, but declared in a different module 'dep'">PackagePrivateInnerClass</warning>()</warning>;
<warning descr="Constructor PackagePrivateInnerClassWithConstructor.PackagePrivateInnerClassWithConstructor() is package-private, but declared in a different module 'dep'">new InnerClasses.<warning descr="Class xxx.InnerClasses.PackagePrivateInnerClassWithConstructor is package-private, but declared in a different module 'dep'">PackagePrivateInnerClassWithConstructor</warning>()</warning>;
<warning descr="Constructor ClassWithPackagePrivateConstructor.ClassWithPackagePrivateConstructor() is package-private, but declared in a different module 'dep'">new InnerClasses.ClassWithPackagePrivateConstructor()</warning>;
new InnerClasses.<warning descr="Class xxx.InnerClasses.PackagePrivateInnerClass is package-private, but declared in a different module 'dep'">PackagePrivateInnerClass</warning>();
new InnerClasses.<warning descr="Constructor PackagePrivateInnerClassWithConstructor.PackagePrivateInnerClassWithConstructor() is package-private, but declared in a different module 'dep'">PackagePrivateInnerClassWithConstructor</warning>();
new InnerClasses.<warning descr="Constructor ClassWithPackagePrivateConstructor.ClassWithPackagePrivateConstructor() is package-private, but declared in a different module 'dep'">ClassWithPackagePrivateConstructor</warning>();
}
}
@@ -5,8 +5,8 @@ class AccessingProtectedMembersNotFromSubclass {
ProtectedMembers aClass = new ProtectedMembers();
aClass.<warning descr="Method ProtectedMembers.method() is protected and used not through a subclass here, but declared in a different module 'dep'">method</warning>();
ProtectedMembers.<warning descr="Method ProtectedMembers.staticMethod() is protected and used not through a subclass here, but declared in a different module 'dep'">staticMethod</warning>();
<warning descr="Constructor ProtectedConstructors.ProtectedConstructors() is protected and used not through a subclass here, but declared in a different module 'dep'">new ProtectedConstructors()</warning>;
<warning descr="Constructor ProtectedConstructors.ProtectedConstructors(int) is protected and used not through a subclass here, but declared in a different module 'dep'">new ProtectedConstructors(1)</warning>;
new <warning descr="Constructor ProtectedConstructors.ProtectedConstructors() is protected and used not through a subclass here, but declared in a different module 'dep'">ProtectedConstructors</warning>();
new <warning descr="Constructor ProtectedConstructors.ProtectedConstructors(int) is protected and used not through a subclass here, but declared in a different module 'dep'">ProtectedConstructors</warning>(1);
new ProtectedConstructors() {};
new ProtectedConstructors(1) {};
}
@@ -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;
@@ -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 {
@@ -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<SinceUntilRange>
) : 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<SinceUntilRange>
) {
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<PsiAnnotation>? {
// 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<PsiAnnotation>.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)
}
}
}
@@ -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<SinceUntilRange>
) : 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<UImportStatement>() != 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<SinceUntilRange>
) {
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<PsiAnnotation>? {
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<PsiAnnotation>.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)
}
}
@@ -0,0 +1,4 @@
package library;
public interface RecentInterface {
}
@@ -0,0 +1,4 @@
package library
interface RecentKotlinInterface {
}
@@ -0,0 +1,5 @@
package library;
public interface RecentSamInterface {
int singleMethod();
}
@@ -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 <error descr="'library.RecentClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentClass</error> r = null;
public <error descr="'library.RecentKotlinClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentKotlinClass</error> r2 = null;
public void parameters(
<error descr="'library.RecentClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentClass</error> rc,
<error descr="'library.RecentKotlinClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentKotlinClass</error> rkc
) {
}
public void array() {
<error descr="'library.RecentClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentClass</error>[] a1 = new <error descr="'library.RecentClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentClass</error>[0];
<error descr="'library.RecentKotlinClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentKotlinClass</error>[] a2 = new <error descr="'library.RecentKotlinClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentKotlinClass</error>[0];
}
public void classAccess() {
Class<<error descr="'library.RecentClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentClass</error>> o = <error descr="'library.RecentClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentClass</error>.class;
Class<<error descr="'library.RecentKotlinClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentKotlinClass</error>> o2 = <error descr="'library.RecentKotlinClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentKotlinClass</error>.class;
}
public void m1(OldClass oc, OldKotlinClass okc) {
String s = oc.<error descr="'recentField' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">recentField</error>;
String s2 = okc.<error descr="'recentField' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">recentField</error>;
@@ -66,14 +83,18 @@ class A {
}
});
}
public void m8() {
Consumer<OldClass> methodReference = OldClass::<error descr="'recentMethod()' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">recentMethod</error>;
}
}
class <error descr="Default constructor in 'library.OldClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">B</error> extends OldClass {
//implicit call to default "recent" constructor available in source code.
class <error descr="'OldClass()' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">B</error> extends OldClass {
//implicit call to empty "recent" constructor available in source code.
}
class <error descr="Default constructor in 'library.OldKotlinClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">BK</error> extends OldKotlinClass {
//implicit call to default "recent" constructor available in source code.
class <error descr="'OldKotlinClass()' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">BK</error> extends OldKotlinClass {
//implicit call to empty "recent" constructor available in source code.
}
class C extends OldClass {
@@ -104,7 +125,7 @@ class <error descr="Default constructor in 'library.OldClassWithDefaultConstruct
//implicit call to default "recent" constructor that is NOT available in source code.
}
class <error descr="Default constructor in 'library.OldKotlinClassWithDefaultConstructor' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">DK</error> extends OldKotlinClassWithDefaultConstructor {
class <error descr="'OldKotlinClassWithDefaultConstructor()' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">DK</error> extends OldKotlinClassWithDefaultConstructor {
//implicit call to default "recent" constructor that is NOT available in source code.
}
@@ -112,16 +133,16 @@ class <error descr="Default constructor in 'library.OldKotlinClassWithDefaultCon
class E extends OldClassWithDefaultConstructor {
public <error descr="Default constructor in 'library.OldClassWithDefaultConstructor' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">E</error>() {}
public <error descr="Default constructor in 'library.OldClassWithDefaultConstructor' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">E</error>(int x) {
super();
public E(int x) {
<error descr="Default constructor in 'library.OldClassWithDefaultConstructor' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">super</error>();
}
}
//Class with constructors delegating to default "recent" constructor.
class EK extends OldKotlinClassWithDefaultConstructor {
public <error descr="Default constructor in 'library.OldKotlinClassWithDefaultConstructor' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">EK</error>() {}
public <error descr="'OldKotlinClassWithDefaultConstructor()' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">EK</error>() {}
public <error descr="Default constructor in 'library.OldKotlinClassWithDefaultConstructor' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">EK</error>(int x) {
public EK(int x) {
<error descr="'OldKotlinClassWithDefaultConstructor()' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">super</error>();
}
}
@@ -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: <error descr="'library.RecentClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentClass</error>? = null
var kr: <error descr="'library.RecentKotlinClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentKotlinClass</error>? = null
fun m1(oc: OldClass): Any {
return oc.<error descr="'recentField' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">recentField</error>
@Suppress("UNUSED_PARAMETER")
fun parameters(
rc: <error descr="'library.RecentClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentClass</error>,
krc: <error descr="'library.RecentKotlinClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentKotlinClass</error>
) {
}
fun m2(oc: OldClass) {
fun arrays() {
arrayOfNulls<<error descr="'library.RecentClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentClass</error>>(0)
arrayOfNulls<<error descr="'library.RecentKotlinClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentKotlinClass</error>>(0)
}
fun classAccess() {
<error descr="'library.RecentClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentClass</error>::class.java
<error descr="'library.RecentKotlinClass' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentKotlinClass</error>::class.java
}
fun m1(oc: OldClass, okc: OldKotlinClass) {
oc.<error descr="'recentField' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">recentField</error>
okc.<error descr="'recentField' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">recentField</error>
}
fun m2(oc: OldClass, okc: OldKotlinClass) {
oc.<error descr="'recentMethod()' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">recentMethod</error>()
okc.<error descr="'recentMethod()' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">recentMethod</error>()
}
fun m3() {
OldClass.<error descr="'recentStaticMethod()' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">recentStaticMethod</error>()
OldKotlinClass.<error descr="'recentStaticMethod()' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">recentStaticMethod</error>()
}
fun m4(): Any {
return <error descr="'OldClass(java.lang.String)' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">OldClass</error>("")
fun m4() {
<error descr="'OldClass(java.lang.String)' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">OldClass</error>("")
<error descr="'OldKotlinClass(java.lang.String)' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">OldKotlinClass</error>("")
}
fun m5(): Any {
//anonymous class
return object : <error descr="'OldClass()' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">OldClass</error>() {
fun anonymousClasses() {
object : <error descr="'OldClass()' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">OldClass()</error>, <error descr="'library.RecentInterface' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentInterface</error> {
}
object : <error descr="'OldKotlinClass()' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">OldKotlinClass()</error>, <error descr="'library.RecentKotlinInterface' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentKotlinInterface</error> {
}
}
fun m6(): Any {
return Supplier<OldClass> { <error descr="'OldClass()' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">OldClass</error>() }
fun m6() {
Supplier<OldClass> { <error descr="'OldClass()' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">OldClass</error>() }
Supplier<OldKotlinClass> { <error descr="'OldKotlinClass()' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">OldKotlinClass</error>() }
}
fun topLevelFunction(s: String) {
<error descr="'recentTopLevelFunction()' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">recentTopLevelFunction</error>()
s.<error descr="'recentExtensionFunction(java.lang.String)' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">recentExtensionFunction</error>()
s.<error descr="'recentInlineExtensionFunction(java.lang.String, kotlin.jvm.functions.Function0<java.lang.String>)' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">recentInlineExtensionFunction</error> { "" }
}
fun singleAbstractMethod() {
<error descr="'library.RecentSamInterface' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentSamInterface</error> { 42 }
}
}
class Overrider : OldClass(42) {
//overrides "recent" method.
override fun <error descr="Overrides method in library.OldClass that is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">recentMethod</error>() { }
}
class KOverrider : OldKotlinClass(42) {
//overrides "recent" method.
override fun <error descr="Overrides method in library.OldKotlinClass that is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">recentMethod</error>() { }
}
class B {
@<error descr="'library.RecentAnnotation' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentAnnotation</error>
fun m1() {
fun markedWithAnnotation() {
}
@<error descr="'library.RecentKotlinAnnotation' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">RecentKotlinAnnotation</error>
fun markedWithKotlinAnnotation() {
}
@OldAnnotation(
oldParam = 0,
<error descr="'recentParam' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">recentParam</error> = 1
)
fun m2() {
fun recentAnnotationParam() {
}
@OldKotlinAnnotation(
oldParam = 0,
<error descr="'recentParam' is available only since 2.0 but the module is targeted for 1.0 - 999.0. It may lead to compatibility problems with IDEs prior to 2.0.">recentParam</error> = 1
)
fun recentKotlinAnnotationParam() {
}
}
@@ -5,6 +5,16 @@
<val name="value" val="&quot;2.0&quot;"/>
</annotation>
</item>
<item name='library.RecentInterface'>
<annotation name='org.jetbrains.annotations.ApiStatus.AvailableSince'>
<val name="value" val="&quot;2.0&quot;"/>
</annotation>
</item>
<item name='library.RecentSamInterface'>
<annotation name='org.jetbrains.annotations.ApiStatus.AvailableSince'>
<val name="value" val="&quot;2.0&quot;"/>
</annotation>
</item>
<item name='library.OldClass recentField'>
<annotation name='org.jetbrains.annotations.ApiStatus.AvailableSince'>
<val name="value" val="&quot;2.0&quot;"/>
@@ -52,6 +62,11 @@
<val name="value" val="&quot;2.0&quot;"/>
</annotation>
</item>
<item name='library.RecentKotlinInterface'>
<annotation name='org.jetbrains.annotations.ApiStatus.AvailableSince'>
<val name="value" val="&quot;2.0&quot;"/>
</annotation>
</item>
<item name='library.OldKotlinClass recentField'>
<annotation name='org.jetbrains.annotations.ApiStatus.AvailableSince'>
<val name="value" val="&quot;2.0&quot;"/>
@@ -87,6 +102,11 @@
<val name="value" val="&quot;2.0&quot;"/>
</annotation>
</item>
<item name='library.OldKotlinClass.Companion void recentStaticMethod()'>
<annotation name='org.jetbrains.annotations.ApiStatus.AvailableSince'>
<val name="value" val="&quot;2.0&quot;"/>
</annotation>
</item>
<item name='library.OldKotlinClassWithDefaultConstructor OldKotlinClassWithDefaultConstructor()'>
<annotation name='org.jetbrains.annotations.ApiStatus.AvailableSince'>
<val name="value" val="&quot;2.0&quot;"/>
@@ -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)
}