mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-210618: Reference implementation of PSI visitor for UAST-supporting languages that detects usages of APIs in source code.
This commit is contained in:
-95
@@ -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) {}
|
||||
|
||||
}
|
||||
+68
@@ -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) {}
|
||||
}
|
||||
+323
@@ -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
|
||||
}
|
||||
}
|
||||
-115
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+141
@@ -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);
|
||||
}
|
||||
}
|
||||
-169
@@ -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);
|
||||
}
|
||||
}
|
||||
+11
-3
@@ -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
|
||||
|
||||
+19
-13
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+8
-1
@@ -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;
|
||||
}
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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>
|
||||
|
||||
+1
-1
@@ -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>
|
||||
|
||||
|
||||
+1
-1
@@ -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>
|
||||
|
||||
+1
-1
@@ -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
|
||||
);
|
||||
}
|
||||
+61
-20
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
-74
@@ -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;
|
||||
}
|
||||
}
|
||||
+30
-17
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+19
-7
@@ -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
|
||||
|
||||
+1
-1
@@ -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>;
|
||||
|
||||
+1
-1
@@ -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>;
|
||||
|
||||
+1
-1
@@ -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>;
|
||||
|
||||
+1
-1
@@ -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>;
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+9
-9
@@ -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>()
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -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()
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+86
-145
@@ -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")
|
||||
|
||||
+10
-10
@@ -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>();
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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) {};
|
||||
}
|
||||
|
||||
+1
-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 {
|
||||
|
||||
+139
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package library;
|
||||
|
||||
public interface RecentInterface {
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package library
|
||||
|
||||
interface RecentKotlinInterface {
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package library;
|
||||
|
||||
public interface RecentSamInterface {
|
||||
int singleMethod();
|
||||
}
|
||||
+31
-10
@@ -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>();
|
||||
}
|
||||
}
|
||||
|
||||
+81
-12
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
+20
@@ -5,6 +5,16 @@
|
||||
<val name="value" val=""2.0""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='library.RecentInterface'>
|
||||
<annotation name='org.jetbrains.annotations.ApiStatus.AvailableSince'>
|
||||
<val name="value" val=""2.0""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='library.RecentSamInterface'>
|
||||
<annotation name='org.jetbrains.annotations.ApiStatus.AvailableSince'>
|
||||
<val name="value" val=""2.0""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='library.OldClass recentField'>
|
||||
<annotation name='org.jetbrains.annotations.ApiStatus.AvailableSince'>
|
||||
<val name="value" val=""2.0""/>
|
||||
@@ -52,6 +62,11 @@
|
||||
<val name="value" val=""2.0""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='library.RecentKotlinInterface'>
|
||||
<annotation name='org.jetbrains.annotations.ApiStatus.AvailableSince'>
|
||||
<val name="value" val=""2.0""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='library.OldKotlinClass recentField'>
|
||||
<annotation name='org.jetbrains.annotations.ApiStatus.AvailableSince'>
|
||||
<val name="value" val=""2.0""/>
|
||||
@@ -87,6 +102,11 @@
|
||||
<val name="value" val=""2.0""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='library.OldKotlinClass.Companion void recentStaticMethod()'>
|
||||
<annotation name='org.jetbrains.annotations.ApiStatus.AvailableSince'>
|
||||
<val name="value" val=""2.0""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='library.OldKotlinClassWithDefaultConstructor OldKotlinClassWithDefaultConstructor()'>
|
||||
<annotation name='org.jetbrains.annotations.ApiStatus.AvailableSince'>
|
||||
<val name="value" val=""2.0""/>
|
||||
|
||||
+5
-2
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user