mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 14:23:28 +07:00
UnstableApiUsageInspection: check if signature of the referenced API contains an unstable type.
For example, if a method's parameter is marked with @ApiStatus.Experimental annotation, the method is effectively experimental. Invoking such a method from plugin code must be highlighted even if the method itself is not marked with the same annotation. GitOrigin-RevId: 5ebd52b3a525a0d51aa2cbed5cb2603157e7f292
This commit is contained in:
committed by
intellij-monorepo-bot
parent
3d3e5ed2bc
commit
ddc5ccd24c
@@ -7,6 +7,7 @@ jvm.inspections.unstable.api.usage.api.is.marked.unstable.itself=''{0}'' is mark
|
||||
jvm.inspections.unstable.api.usage.api.is.declared.in.unstable.api=''{0}'' is declared in unstable {1} ''{2}''
|
||||
jvm.inspections.unstable.api.usage.overridden.method.is.marked.unstable.itself=Overridden method ''{0}'' is marked unstable
|
||||
jvm.inspections.unstable.api.usage.overridden.method.is.declared.in.unstable.api=Overridden method ''{0}'' is declared in unstable {1} ''{2}''
|
||||
jvm.inspections.unstable.api.usage.unstable.type.is.used.in.signature.of.referenced.api=''{0}'' is unstable because its signature references unstable {1} ''{2}''
|
||||
|
||||
jvm.inspections.scheduled.for.removal.future.version=a future version
|
||||
jvm.inspections.scheduled.for.removal.predefined.version=version {0}
|
||||
@@ -14,6 +15,7 @@ jvm.inspections.scheduled.for.removal.api.is.marked.itself=''{0}'' is scheduled
|
||||
jvm.inspections.scheduled.for.removal.api.is.declared.in.marked.api=''{0}'' is declared in {1} ''{2}'' scheduled for removal in {3}
|
||||
jvm.inspections.scheduled.for.removal.method.overridden.marked.itself=Overridden method ''{0}'' is scheduled for removal in {1}
|
||||
jvm.inspections.scheduled.for.removal.method.overridden.declared.in.marked.api=Overridden method ''{0}'' is declared in {1} ''{2}'' scheduled for removal in {3}
|
||||
jvm.inspections.scheduled.for.removal.scheduled.for.removal.type.is.used.in.signature.of.referenced.api=''{0}'' is scheduled for removal because its signature references {1} ''{2}'' scheduled for removal in {3}
|
||||
|
||||
jvm.inspections.unstable.type.used.in.signature.display.name=Unstable type is used in signature
|
||||
jvm.inspections.unstable.type.used.in.class.signature.description=Class must be marked with ''@{0}'' annotation because its declaration references unstable type ''{1}''
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
// 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.codeInsight.AnnotationUtil
|
||||
import com.intellij.codeInspection.deprecation.DeprecationInspection
|
||||
import com.intellij.lang.findUsages.LanguageFindUsages
|
||||
import com.intellij.psi.*
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.uast.UClass
|
||||
import org.jetbrains.uast.UDeclaration
|
||||
import org.jetbrains.uast.UField
|
||||
import org.jetbrains.uast.UMethod
|
||||
|
||||
/**
|
||||
* Container of several values representing annotation search result.
|
||||
*
|
||||
* - [target] — starting API element of which annotation was searched.
|
||||
* - [containingDeclaration] — annotated declaration containing [target]. May be equal [target] if the [target] is annotated itself.
|
||||
* Otherwise it is the [target]'s containing class or package.
|
||||
* - [psiAnnotation] — annotation with which [containingDeclaration] is marked.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
data class AnnotatedContainingDeclaration(
|
||||
val target: PsiModifierListOwner,
|
||||
val containingDeclaration: PsiModifierListOwner,
|
||||
val psiAnnotation: PsiAnnotation
|
||||
) {
|
||||
val isOwnAnnotation: Boolean
|
||||
get() = target == containingDeclaration
|
||||
|
||||
val targetName: String
|
||||
get() = DeprecationInspection.getPresentableName(target)
|
||||
|
||||
val targetType: String
|
||||
get() = LanguageFindUsages.getType(target)
|
||||
|
||||
val containingDeclarationName: String
|
||||
get() = DeprecationInspection.getPresentableName(containingDeclaration)
|
||||
|
||||
val containingDeclarationType: String
|
||||
get() = LanguageFindUsages.getType(containingDeclaration)
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility functions for [UnstableApiUsageInspection] and [UnstableTypeUsedInSignatureInspection].
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
internal object AnnotatedApiUsageUtil {
|
||||
|
||||
/**
|
||||
* Searches for an annotation on a [target] or its enclosing declaration (containing class or package).
|
||||
*
|
||||
* If a [target] is marked with annotation, it is returned immediately.
|
||||
* If containing class of [target] is marked with annotation, the class and its annotation is returned.
|
||||
* If the package, to which the [target] belongs, is marked with annotation, the package and its annotation is returned.
|
||||
*/
|
||||
fun findAnnotatedContainingDeclaration(
|
||||
target: PsiModifierListOwner,
|
||||
annotationNames: Collection<String>,
|
||||
includeExternalAnnotations: Boolean,
|
||||
containingDeclaration: PsiModifierListOwner = target
|
||||
): AnnotatedContainingDeclaration? {
|
||||
val annotation = AnnotationUtil.findAnnotation(containingDeclaration, annotationNames, !includeExternalAnnotations)
|
||||
if (annotation != null) {
|
||||
return AnnotatedContainingDeclaration(target, containingDeclaration, annotation)
|
||||
}
|
||||
if (containingDeclaration is PsiMember) {
|
||||
val containingClass = containingDeclaration.containingClass
|
||||
if (containingClass != null) {
|
||||
return findAnnotatedContainingDeclaration(target, annotationNames, includeExternalAnnotations, containingClass)
|
||||
}
|
||||
}
|
||||
val packageName = (containingDeclaration.containingFile as? PsiClassOwner)?.packageName ?: return null
|
||||
val psiPackage = JavaPsiFacade.getInstance(containingDeclaration.project).findPackage(packageName) ?: return null
|
||||
return findAnnotatedContainingDeclaration(target, annotationNames, includeExternalAnnotations, psiPackage)
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for an annotated type that is part of the signature of the given declaration.
|
||||
*
|
||||
* * For classes, annotated type parameter is returned:
|
||||
* * `class Foo<T extends Bar>` -> `Bar` is returned if it is annotated.
|
||||
*
|
||||
* * For methods, annotated return type, parameter type or type parameter is returned:
|
||||
* * `public <T extends Baz> Foo method(Bar bar)` -> `Baz`, `Foo` or `Bar` is returned, whichever is annotated.
|
||||
*
|
||||
* * For fields, annotated field type is returned:
|
||||
* * `public Foo field` -> `Foo` is returned if it is annotated.
|
||||
*/
|
||||
fun findAnnotatedTypeUsedInDeclarationSignature(
|
||||
declaration: UDeclaration,
|
||||
annotations: Collection<String>
|
||||
): AnnotatedContainingDeclaration? {
|
||||
when (declaration) {
|
||||
is UClass -> {
|
||||
return findAnnotatedTypeParameter(declaration.javaPsi, annotations)
|
||||
}
|
||||
is UMethod -> {
|
||||
for (uastParameter in declaration.uastParameters) {
|
||||
val annotatedParamType = findAnnotatedTypePart(uastParameter.type.deepComponentType, annotations)
|
||||
if (annotatedParamType != null) {
|
||||
return annotatedParamType
|
||||
}
|
||||
}
|
||||
val returnType = declaration.returnType
|
||||
if (returnType != null) {
|
||||
val annotatedReturnType = findAnnotatedTypePart(returnType.deepComponentType, annotations)
|
||||
if (annotatedReturnType != null) {
|
||||
return annotatedReturnType
|
||||
}
|
||||
}
|
||||
return findAnnotatedTypeParameter(declaration.javaPsi, annotations)
|
||||
}
|
||||
is UField -> {
|
||||
return findAnnotatedTypePart(declaration.type.deepComponentType, annotations)
|
||||
}
|
||||
else -> return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun findAnnotatedTypeParameter(
|
||||
typeParameterListOwner: PsiTypeParameterListOwner,
|
||||
annotations: Collection<String>
|
||||
): AnnotatedContainingDeclaration? {
|
||||
for (typeParameter in typeParameterListOwner.typeParameters) {
|
||||
for (referencedType in typeParameter.extendsList.referencedTypes) {
|
||||
val annotatedContainingDeclaration = findAnnotatedTypePart(referencedType.deepComponentType, annotations)
|
||||
if (annotatedContainingDeclaration != null) {
|
||||
return annotatedContainingDeclaration
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findAnnotatedTypePart(
|
||||
psiType: PsiType,
|
||||
annotations: Collection<String>
|
||||
): AnnotatedContainingDeclaration? {
|
||||
if (psiType is PsiClassType) {
|
||||
val psiClass = psiType.resolve()
|
||||
if (psiClass != null) {
|
||||
val containingDeclaration = findAnnotatedContainingDeclaration(psiClass, annotations, false)
|
||||
if (containingDeclaration != null) {
|
||||
return containingDeclaration
|
||||
}
|
||||
}
|
||||
for (parameterType in psiType.parameters) {
|
||||
val parameterResult = findAnnotatedTypePart(parameterType, annotations)
|
||||
if (parameterResult != null) {
|
||||
return parameterResult
|
||||
}
|
||||
}
|
||||
}
|
||||
if (psiType is PsiWildcardType) {
|
||||
return findAnnotatedTypePart(psiType.extendsBound, annotations) ?: findAnnotatedTypePart(psiType.superBound, annotations)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -3,12 +3,13 @@ package com.intellij.codeInspection
|
||||
|
||||
import com.intellij.analysis.JvmAnalysisBundle
|
||||
import com.intellij.codeInsight.AnnotationUtil
|
||||
import com.intellij.codeInspection.AnnotatedApiUsageUtil.findAnnotatedContainingDeclaration
|
||||
import com.intellij.codeInspection.AnnotatedApiUsageUtil.findAnnotatedTypeUsedInDeclarationSignature
|
||||
import com.intellij.codeInspection.apiUsage.ApiUsageProcessor
|
||||
import com.intellij.codeInspection.apiUsage.ApiUsageUastVisitor
|
||||
import com.intellij.codeInspection.deprecation.DeprecationInspection
|
||||
import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel
|
||||
import com.intellij.codeInspection.util.SpecialAnnotationsUtil
|
||||
import com.intellij.lang.findUsages.LanguageFindUsages
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.roots.ProjectFileIndex
|
||||
import com.intellij.psi.*
|
||||
@@ -119,85 +120,64 @@ private class UnstableApiUsageProcessor(
|
||||
checkUnstableApiUsage(overriddenMethod, method, true)
|
||||
}
|
||||
|
||||
private fun checkUnstableApiUsage(target: PsiModifierListOwner, sourceNode: UElement, isMethodOverriding: Boolean) {
|
||||
private fun getMessageProvider(psiAnnotation: PsiAnnotation): UnstableApiUsageMessageProvider? {
|
||||
val annotationName = psiAnnotation.qualifiedName ?: return null
|
||||
return knownAnnotationMessageProviders[annotationName] ?: DefaultUnstableApiUsageMessageProvider
|
||||
}
|
||||
|
||||
private fun getElementToHighlight(sourceNode: UElement): PsiElement? =
|
||||
(sourceNode as? UDeclaration)?.uastAnchor.sourcePsiElement ?: sourceNode.sourcePsi
|
||||
|
||||
private fun checkUnstableApiUsage(
|
||||
target: PsiModifierListOwner,
|
||||
sourceNode: UElement,
|
||||
isMethodOverriding: Boolean
|
||||
) {
|
||||
if (!isLibraryElement(target)) {
|
||||
return
|
||||
}
|
||||
val annotatedContainingDeclaration = findAnnotatedContainingDeclaration(target, unstableApiAnnotations, true)
|
||||
if (annotatedContainingDeclaration == null) {
|
||||
if (annotatedContainingDeclaration != null) {
|
||||
val messageProvider = getMessageProvider(annotatedContainingDeclaration.psiAnnotation) ?: return
|
||||
val message = if (isMethodOverriding) {
|
||||
messageProvider.buildMessageUnstableMethodOverridden(annotatedContainingDeclaration)
|
||||
}
|
||||
else {
|
||||
messageProvider.buildMessage(annotatedContainingDeclaration)
|
||||
}
|
||||
val elementToHighlight = getElementToHighlight(sourceNode) ?: return
|
||||
problemsHolder.registerProblem(elementToHighlight, message, ProblemHighlightType.GENERIC_ERROR_OR_WARNING)
|
||||
return
|
||||
}
|
||||
val annotationName = annotatedContainingDeclaration.psiAnnotation.qualifiedName ?: return
|
||||
val messageProvider = knownAnnotationMessageProviders[annotationName] ?: DefaultUnstableApiUsageMessageProvider
|
||||
val message = if (isMethodOverriding) {
|
||||
messageProvider.buildUnstableMethodOverriddenMessage(annotatedContainingDeclaration)
|
||||
}
|
||||
else {
|
||||
messageProvider.buildMessage(annotatedContainingDeclaration)
|
||||
}
|
||||
val elementToHighlight = (sourceNode as? UDeclaration)?.uastAnchor.sourcePsiElement ?: sourceNode.sourcePsi
|
||||
if (elementToHighlight != null) {
|
||||
problemsHolder.registerProblem(elementToHighlight, message, ProblemHighlightType.GENERIC_ERROR_OR_WARNING)
|
||||
|
||||
if (!isMethodOverriding) {
|
||||
val declaration = target.toUElement(UDeclaration::class.java) ?: return
|
||||
val unstableTypeUsedInSignature = findAnnotatedTypeUsedInDeclarationSignature(declaration, unstableApiAnnotations)
|
||||
if (unstableTypeUsedInSignature != null) {
|
||||
val messageProvider = getMessageProvider(unstableTypeUsedInSignature.psiAnnotation) ?: return
|
||||
val message = messageProvider.buildMessageUnstableTypeIsUsedInSignatureOfReferencedApi(target, unstableTypeUsedInSignature)
|
||||
val elementToHighlight = getElementToHighlight(sourceNode) ?: return
|
||||
problemsHolder.registerProblem(elementToHighlight, message, ProblemHighlightType.GENERIC_ERROR_OR_WARNING)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class AnnotatedContainingDeclaration(
|
||||
val target: PsiModifierListOwner,
|
||||
val containingDeclaration: PsiModifierListOwner,
|
||||
val psiAnnotation: PsiAnnotation
|
||||
) {
|
||||
val targetName: String
|
||||
get() = DeprecationInspection.getPresentableName(target)
|
||||
|
||||
val containingDeclarationName: String
|
||||
get() = DeprecationInspection.getPresentableName(containingDeclaration)
|
||||
|
||||
val containingDeclarationType: String
|
||||
get() = LanguageFindUsages.getType(containingDeclaration)
|
||||
|
||||
val isOwnAnnotation: Boolean
|
||||
get() = target == containingDeclaration
|
||||
}
|
||||
|
||||
fun findAnnotatedContainingDeclaration(
|
||||
target: PsiModifierListOwner,
|
||||
annotationNames: Collection<String>,
|
||||
includeExternalAnnotations: Boolean
|
||||
): AnnotatedContainingDeclaration? =
|
||||
findAnnotatedContainingDeclaration(target, target, annotationNames, includeExternalAnnotations)
|
||||
|
||||
private fun findAnnotatedContainingDeclaration(
|
||||
target: PsiModifierListOwner,
|
||||
listOwner: PsiModifierListOwner,
|
||||
annotationNames: Collection<String>,
|
||||
includeExternalAnnotations: Boolean
|
||||
): AnnotatedContainingDeclaration? {
|
||||
val annotation = AnnotationUtil.findAnnotation(listOwner, annotationNames, !includeExternalAnnotations)
|
||||
if (annotation != null) {
|
||||
return AnnotatedContainingDeclaration(target, listOwner, annotation)
|
||||
}
|
||||
if (listOwner is PsiMember) {
|
||||
val containingClass = listOwner.containingClass
|
||||
if (containingClass != null) {
|
||||
return findAnnotatedContainingDeclaration(target, containingClass, annotationNames, includeExternalAnnotations)
|
||||
}
|
||||
}
|
||||
val packageName = (listOwner.containingFile as? PsiClassOwner)?.packageName ?: return null
|
||||
val psiPackage = JavaPsiFacade.getInstance(listOwner.project).findPackage(packageName) ?: return null
|
||||
return findAnnotatedContainingDeclaration(target, psiPackage, annotationNames, includeExternalAnnotations)
|
||||
}
|
||||
|
||||
private interface UnstableApiUsageMessageProvider {
|
||||
|
||||
fun buildMessage(annotatedContainingDeclaration: AnnotatedContainingDeclaration): String
|
||||
|
||||
fun buildUnstableMethodOverriddenMessage(annotatedContainingDeclaration: AnnotatedContainingDeclaration): String
|
||||
fun buildMessageUnstableMethodOverridden(annotatedContainingDeclaration: AnnotatedContainingDeclaration): String
|
||||
|
||||
fun buildMessageUnstableTypeIsUsedInSignatureOfReferencedApi(
|
||||
referencedApi: PsiModifierListOwner,
|
||||
annotatedTypeUsedInSignature: AnnotatedContainingDeclaration
|
||||
): String
|
||||
}
|
||||
|
||||
private object DefaultUnstableApiUsageMessageProvider : UnstableApiUsageMessageProvider {
|
||||
override fun buildUnstableMethodOverriddenMessage(annotatedContainingDeclaration: AnnotatedContainingDeclaration): String =
|
||||
override fun buildMessageUnstableMethodOverridden(annotatedContainingDeclaration: AnnotatedContainingDeclaration): String =
|
||||
with(annotatedContainingDeclaration) {
|
||||
if (isOwnAnnotation) {
|
||||
JvmAnalysisBundle.message("jvm.inspections.unstable.api.usage.overridden.method.is.marked.unstable.itself", targetName)
|
||||
@@ -226,10 +206,20 @@ private object DefaultUnstableApiUsageMessageProvider : UnstableApiUsageMessageP
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun buildMessageUnstableTypeIsUsedInSignatureOfReferencedApi(
|
||||
referencedApi: PsiModifierListOwner,
|
||||
annotatedTypeUsedInSignature: AnnotatedContainingDeclaration
|
||||
): String = JvmAnalysisBundle.message(
|
||||
"jvm.inspections.unstable.api.usage.unstable.type.is.used.in.signature.of.referenced.api",
|
||||
DeprecationInspection.getPresentableName(referencedApi),
|
||||
annotatedTypeUsedInSignature.targetType,
|
||||
annotatedTypeUsedInSignature.targetName
|
||||
)
|
||||
}
|
||||
|
||||
private class ScheduledForRemovalMessageProvider : UnstableApiUsageMessageProvider {
|
||||
override fun buildUnstableMethodOverriddenMessage(annotatedContainingDeclaration: AnnotatedContainingDeclaration): String {
|
||||
override fun buildMessageUnstableMethodOverridden(annotatedContainingDeclaration: AnnotatedContainingDeclaration): String {
|
||||
val versionMessage = getVersionMessage(annotatedContainingDeclaration)
|
||||
return with(annotatedContainingDeclaration) {
|
||||
if (isOwnAnnotation) {
|
||||
@@ -271,6 +261,20 @@ private class ScheduledForRemovalMessageProvider : UnstableApiUsageMessageProvid
|
||||
}
|
||||
}
|
||||
|
||||
override fun buildMessageUnstableTypeIsUsedInSignatureOfReferencedApi(
|
||||
referencedApi: PsiModifierListOwner,
|
||||
annotatedTypeUsedInSignature: AnnotatedContainingDeclaration
|
||||
): String {
|
||||
val versionMessage = getVersionMessage(annotatedTypeUsedInSignature)
|
||||
return JvmAnalysisBundle.message(
|
||||
"jvm.inspections.scheduled.for.removal.scheduled.for.removal.type.is.used.in.signature.of.referenced.api",
|
||||
DeprecationInspection.getPresentableName(referencedApi),
|
||||
annotatedTypeUsedInSignature.targetType,
|
||||
annotatedTypeUsedInSignature.targetName,
|
||||
versionMessage
|
||||
)
|
||||
}
|
||||
|
||||
private fun getVersionMessage(annotatedContainingDeclaration: AnnotatedContainingDeclaration): String {
|
||||
val versionValue = AnnotationUtil.getDeclaredStringAttributeValue(annotatedContainingDeclaration.psiAnnotation, "inVersion")
|
||||
return if (versionValue.isNullOrEmpty()) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package com.intellij.codeInspection
|
||||
|
||||
import com.intellij.analysis.JvmAnalysisBundle
|
||||
import com.intellij.codeInspection.AnnotatedApiUsageUtil.findAnnotatedTypeUsedInDeclarationSignature
|
||||
import com.intellij.codeInspection.UnstableApiUsageInspection.Companion.DEFAULT_UNSTABLE_API_ANNOTATIONS
|
||||
import com.intellij.codeInspection.util.SpecialAnnotationsUtil
|
||||
import com.intellij.lang.jvm.JvmModifier
|
||||
@@ -50,50 +51,34 @@ private class UnstableTypeUsedInSignatureVisitor(
|
||||
private val unstableApiAnnotations: List<String>
|
||||
) : AbstractUastNonRecursiveVisitor() {
|
||||
|
||||
override fun visitClass(node: UClass): Boolean {
|
||||
override fun visitDeclaration(node: UDeclaration): Boolean {
|
||||
if (node !is UClass && node !is UMethod && node !is UField) {
|
||||
return false
|
||||
}
|
||||
if (!isAccessibleDeclaration(node) || isInsideUnstableDeclaration(node)) {
|
||||
return true
|
||||
}
|
||||
checkTypeParameters(node.javaPsi, node)
|
||||
val annotatedTypeUsedInSignature = findAnnotatedTypeUsedInDeclarationSignature(node, unstableApiAnnotations) ?: return true
|
||||
|
||||
val elementToHighlight = node.uastAnchor.sourcePsiElement ?: return true
|
||||
val typeName = (annotatedTypeUsedInSignature.target as? PsiClass)?.qualifiedName ?: return true
|
||||
val annotationName = annotatedTypeUsedInSignature.psiAnnotation.qualifiedName ?: return true
|
||||
|
||||
val message = when (node) {
|
||||
is UMethod -> JvmAnalysisBundle.message(
|
||||
"jvm.inspections.unstable.type.used.in.method.signature.description", annotationName, typeName
|
||||
)
|
||||
is UField -> JvmAnalysisBundle.message(
|
||||
"jvm.inspections.unstable.type.used.in.field.signature.description", annotationName, typeName
|
||||
)
|
||||
else -> JvmAnalysisBundle.message(
|
||||
"jvm.inspections.unstable.type.used.in.class.signature.description", annotationName, typeName
|
||||
)
|
||||
}
|
||||
problemsHolder.registerProblem(elementToHighlight, message, ProblemHighlightType.GENERIC_ERROR_OR_WARNING)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun visitMethod(node: UMethod): Boolean {
|
||||
if (!isAccessibleDeclaration(node) || isInsideUnstableDeclaration(node)) {
|
||||
return true
|
||||
}
|
||||
for (uastParameter in node.uastParameters) {
|
||||
if (checkReferencesUnstableType(uastParameter.type.deepComponentType, node)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
val returnType = node.returnType ?: return true
|
||||
checkReferencesUnstableType(returnType, node)
|
||||
|
||||
checkTypeParameters(node.javaPsi, node)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun visitField(node: UField): Boolean {
|
||||
if (!isAccessibleDeclaration(node) || isInsideUnstableDeclaration(node)) {
|
||||
return true
|
||||
}
|
||||
checkReferencesUnstableType(node.type, node)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun checkTypeParameters(typeParameterListOwner: PsiTypeParameterListOwner, declaration: UDeclaration): Boolean {
|
||||
for (typeParameter in typeParameterListOwner.typeParameters) {
|
||||
val referencedTypes = typeParameter.extendsList.referencedTypes
|
||||
for (referencedType in referencedTypes) {
|
||||
if (checkReferencesUnstableType(referencedType, declaration)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun isAccessibleDeclaration(node: UDeclaration): Boolean {
|
||||
if (node.visibility == UastVisibility.PRIVATE) {
|
||||
if (node is UField) {
|
||||
@@ -133,41 +118,4 @@ private class UnstableTypeUsedInSignatureVisitor(
|
||||
return false
|
||||
}
|
||||
|
||||
private fun checkReferencesUnstableType(psiType: PsiType, declaration: UDeclaration): Boolean {
|
||||
val annotatedContainingDeclaration = findReferencedUnstableType(psiType.deepComponentType) ?: return false
|
||||
val unstableClass = annotatedContainingDeclaration.target as? PsiClass ?: return false
|
||||
val className = unstableClass.qualifiedName ?: return false
|
||||
val annotationName = annotatedContainingDeclaration.psiAnnotation.qualifiedName ?: return false
|
||||
val message = when (declaration) {
|
||||
is UMethod -> JvmAnalysisBundle.message("jvm.inspections.unstable.type.used.in.method.signature.description", annotationName, className)
|
||||
is UField -> JvmAnalysisBundle.message("jvm.inspections.unstable.type.used.in.field.signature.description", annotationName, className)
|
||||
else -> JvmAnalysisBundle.message("jvm.inspections.unstable.type.used.in.class.signature.description", annotationName, className)
|
||||
}
|
||||
val elementToHighlight = declaration.uastAnchor.sourcePsiElement ?: return false
|
||||
problemsHolder.registerProblem(elementToHighlight, message, ProblemHighlightType.GENERIC_ERROR_OR_WARNING)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun findReferencedUnstableType(psiType: PsiType): AnnotatedContainingDeclaration? {
|
||||
if (psiType is PsiClassType) {
|
||||
val psiClass = psiType.resolve()
|
||||
if (psiClass != null) {
|
||||
val unstableContainingDeclaration = findAnnotatedContainingDeclaration(psiClass, unstableApiAnnotations, false)
|
||||
if (unstableContainingDeclaration != null) {
|
||||
return unstableContainingDeclaration
|
||||
}
|
||||
}
|
||||
for (parameterType in psiType.parameters) {
|
||||
val parameterResult = findReferencedUnstableType(parameterType)
|
||||
if (parameterResult != null) {
|
||||
return parameterResult
|
||||
}
|
||||
}
|
||||
}
|
||||
if (psiType is PsiWildcardType) {
|
||||
return findReferencedUnstableType(psiType.extendsBound) ?: findReferencedUnstableType(psiType.superBound)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
@@ -91,4 +91,36 @@ class DirectOverrideAnnotatedMethod extends NonAnnotatedClass {
|
||||
class IndirectOverrideAnnotatedMethod extends DirectOverrideAnnotatedMethod {
|
||||
@Override
|
||||
public void annotatedMethodInNonAnnotatedClass() {}
|
||||
}
|
||||
|
||||
class ClassWithExperimentalTypeInSignature<T extends <warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>> { }
|
||||
|
||||
class OwnerOfMembersWithExperimentalTypesInSignature {
|
||||
public <warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning> field;
|
||||
|
||||
public <warning descr="'annotatedPkg.ClassInAnnotatedPkg' is declared in unstable package 'annotatedPkg'">ClassInAnnotatedPkg</warning> fieldPkg;
|
||||
|
||||
public void parameterType(<warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning> param) { }
|
||||
|
||||
public void parameterTypePkg(<warning descr="'annotatedPkg.ClassInAnnotatedPkg' is declared in unstable package 'annotatedPkg'">ClassInAnnotatedPkg</warning> param) { }
|
||||
|
||||
public <warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning> returnType() { return null; }
|
||||
|
||||
public <warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning> returnTypePkg() { return null; }
|
||||
}
|
||||
|
||||
class WarningsOfExperimentalTypesInSignature {
|
||||
public void classUsage() {
|
||||
new <warning descr="'ClassWithExperimentalTypeInSignature' is unstable because its signature references unstable class 'pkg.AnnotatedClass'">ClassWithExperimentalTypeInSignature<<warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>></warning>();
|
||||
}
|
||||
|
||||
public void membersUsages(OwnerOfMembersWithExperimentalTypesInSignature owner) {
|
||||
Object field = owner.<warning descr="'field' is unstable because its signature references unstable class 'pkg.AnnotatedClass'">field</warning>;
|
||||
owner.<warning descr="'parameterType(pkg.AnnotatedClass)' is unstable because its signature references unstable class 'pkg.AnnotatedClass'">parameterType</warning>(null);
|
||||
owner.<warning descr="'returnType()' is unstable because its signature references unstable class 'pkg.AnnotatedClass'">returnType</warning>();
|
||||
|
||||
Object fieldPkg = owner.<warning descr="'field' is unstable because its signature references unstable class 'pkg.AnnotatedClass'">field</warning>;
|
||||
owner.<warning descr="'parameterTypePkg(annotatedPkg.ClassInAnnotatedPkg)' is unstable because its signature references unstable class 'annotatedPkg.ClassInAnnotatedPkg'">parameterTypePkg</warning>(null);
|
||||
owner.<warning descr="'returnTypePkg()' is unstable because its signature references unstable class 'pkg.AnnotatedClass'">returnTypePkg</warning>();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
@file:Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE", "UNUSED_VALUE", "UNUSED_PARAMETER", "UNUSED_VARIABLE")
|
||||
|
||||
import pkg.<warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>
|
||||
import pkg.<warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>.<warning descr="'NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS' is declared in unstable class 'pkg.AnnotatedClass'">NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS</warning>
|
||||
import pkg.<warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>.<warning descr="'staticNonAnnotatedMethodInAnnotatedClass()' is declared in unstable class 'pkg.AnnotatedClass'">staticNonAnnotatedMethodInAnnotatedClass</warning>
|
||||
@@ -22,7 +24,6 @@ import pkg.NonAnnotatedAnnotation
|
||||
|
||||
import <warning descr="'annotatedPkg' is marked unstable">annotatedPkg</warning>.<warning descr="'annotatedPkg.ClassInAnnotatedPkg' is declared in unstable package 'annotatedPkg'">ClassInAnnotatedPkg</warning>
|
||||
|
||||
@Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE", "UNUSED_VALUE")
|
||||
class UnstableElementsTest {
|
||||
fun test() {
|
||||
var s = <warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>.<warning descr="'NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS' is declared in unstable class 'pkg.AnnotatedClass'">NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS</warning>
|
||||
@@ -89,4 +90,40 @@ open class DirectOverrideAnnotatedMethod : NonAnnotatedClass() {
|
||||
//No warning should be produced.
|
||||
class IndirectOverrideAnnotatedMethod : DirectOverrideAnnotatedMethod() {
|
||||
override fun annotatedMethodInNonAnnotatedClass() {}
|
||||
}
|
||||
|
||||
class ClassWithExperimentalTypeInSignature<T : <warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>>
|
||||
|
||||
class OwnerOfMembersWithExperimentalTypesInSignature {
|
||||
var field: <warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>? = null
|
||||
|
||||
var fieldPkg: <warning descr="'annotatedPkg.ClassInAnnotatedPkg' is declared in unstable package 'annotatedPkg'">ClassInAnnotatedPkg</warning>? = null
|
||||
|
||||
fun parameterType(param: <warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>?) {}
|
||||
|
||||
fun parameterTypePkg(param: <warning descr="'annotatedPkg.ClassInAnnotatedPkg' is declared in unstable package 'annotatedPkg'">ClassInAnnotatedPkg</warning>?) {}
|
||||
|
||||
fun returnType(): <warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>? {
|
||||
return null
|
||||
}
|
||||
|
||||
fun returnTypePkg(): <warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
class WarningsOfExperimentalTypesInSignature {
|
||||
fun classUsage() {
|
||||
<warning descr="'ClassWithExperimentalTypeInSignature' is unstable because its signature references unstable class 'pkg.AnnotatedClass'">ClassWithExperimentalTypeInSignature</warning><<warning descr="'pkg.AnnotatedClass' is marked unstable">AnnotatedClass</warning>>()
|
||||
}
|
||||
|
||||
fun membersUsages(owner: OwnerOfMembersWithExperimentalTypesInSignature) {
|
||||
val field = owner.<warning descr="'getField()' is unstable because its signature references unstable class 'pkg.AnnotatedClass'">field</warning>
|
||||
owner.<warning descr="'parameterType(pkg.AnnotatedClass)' is unstable because its signature references unstable class 'pkg.AnnotatedClass'">parameterType</warning>(null)
|
||||
owner.<warning descr="'returnType()' is unstable because its signature references unstable class 'pkg.AnnotatedClass'">returnType</warning>()
|
||||
|
||||
val fieldPkg = owner.<warning descr="'getField()' is unstable because its signature references unstable class 'pkg.AnnotatedClass'">field</warning>
|
||||
owner.<warning descr="'parameterTypePkg(annotatedPkg.ClassInAnnotatedPkg)' is unstable because its signature references unstable class 'annotatedPkg.ClassInAnnotatedPkg'">parameterTypePkg</warning>(null)
|
||||
owner.<warning descr="'returnTypePkg()' is unstable because its signature references unstable class 'pkg.AnnotatedClass'">returnTypePkg</warning>()
|
||||
}
|
||||
}
|
||||
@@ -90,4 +90,36 @@ class DirectOverrideAnnotatedMethod extends NonAnnotatedClass {
|
||||
class IndirectOverrideAnnotatedMethod extends DirectOverrideAnnotatedMethod {
|
||||
@Override
|
||||
public void annotatedMethodInNonAnnotatedClass() {}
|
||||
}
|
||||
|
||||
class ClassWithScheduledForRemovalTypeInSignature<T extends <warning descr="'pkg.AnnotatedClass' is scheduled for removal in version 123.456">AnnotatedClass</warning>> { }
|
||||
|
||||
class OwnerOfMembersWithScheduledForRemovalTypesInSignature {
|
||||
public <warning descr="'pkg.AnnotatedClass' is scheduled for removal in version 123.456">AnnotatedClass</warning> field;
|
||||
|
||||
public <warning descr="'annotatedPkg.ClassInAnnotatedPkg' is declared in package 'annotatedPkg' scheduled for removal in version 123.456">ClassInAnnotatedPkg</warning> fieldPkg;
|
||||
|
||||
public void parameterType(<warning descr="'pkg.AnnotatedClass' is scheduled for removal in version 123.456">AnnotatedClass</warning> param) { }
|
||||
|
||||
public void parameterTypePkg(<warning descr="'annotatedPkg.ClassInAnnotatedPkg' is declared in package 'annotatedPkg' scheduled for removal in version 123.456">ClassInAnnotatedPkg</warning> param) { }
|
||||
|
||||
public <warning descr="'pkg.AnnotatedClass' is scheduled for removal in version 123.456">AnnotatedClass</warning> returnType() { return null; }
|
||||
|
||||
public <warning descr="'pkg.AnnotatedClass' is scheduled for removal in version 123.456">AnnotatedClass</warning> returnTypePkg() { return null; }
|
||||
}
|
||||
|
||||
class WarningsOfScheduledForRemovalTypesInSignature {
|
||||
public void classUsage() {
|
||||
new <warning descr="'ClassWithScheduledForRemovalTypeInSignature' is scheduled for removal because its signature references class 'pkg.AnnotatedClass' scheduled for removal in version 123.456">ClassWithScheduledForRemovalTypeInSignature<<warning descr="'pkg.AnnotatedClass' is scheduled for removal in version 123.456">AnnotatedClass</warning>></warning>();
|
||||
}
|
||||
|
||||
public void membersUsages(OwnerOfMembersWithScheduledForRemovalTypesInSignature owner) {
|
||||
Object field = owner.<warning descr="'field' is scheduled for removal because its signature references class 'pkg.AnnotatedClass' scheduled for removal in version 123.456">field</warning>;
|
||||
owner.<warning descr="'parameterType(pkg.AnnotatedClass)' is scheduled for removal because its signature references class 'pkg.AnnotatedClass' scheduled for removal in version 123.456">parameterType</warning>(null);
|
||||
owner.<warning descr="'returnType()' is scheduled for removal because its signature references class 'pkg.AnnotatedClass' scheduled for removal in version 123.456">returnType</warning>();
|
||||
|
||||
Object fieldPkg = owner.<warning descr="'field' is scheduled for removal because its signature references class 'pkg.AnnotatedClass' scheduled for removal in version 123.456">field</warning>;
|
||||
owner.<warning descr="'parameterTypePkg(annotatedPkg.ClassInAnnotatedPkg)' is scheduled for removal because its signature references class 'annotatedPkg.ClassInAnnotatedPkg' scheduled for removal in version 123.456">parameterTypePkg</warning>(null);
|
||||
owner.<warning descr="'returnTypePkg()' is scheduled for removal because its signature references class 'pkg.AnnotatedClass' scheduled for removal in version 123.456">returnTypePkg</warning>();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
@file:Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE", "UNUSED_VALUE", "UNUSED_PARAMETER", "UNUSED_VARIABLE")
|
||||
|
||||
import pkg.<warning descr="'pkg.AnnotatedClass' is scheduled for removal in version 123.456">AnnotatedClass</warning>
|
||||
import pkg.<warning descr="'pkg.AnnotatedClass' is scheduled for removal in version 123.456">AnnotatedClass</warning>.<warning descr="'NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS' is declared in class 'pkg.AnnotatedClass' scheduled for removal in version 123.456">NON_ANNOTATED_CONSTANT_IN_ANNOTATED_CLASS</warning>
|
||||
import pkg.<warning descr="'pkg.AnnotatedClass' is scheduled for removal in version 123.456">AnnotatedClass</warning>.<warning descr="'staticNonAnnotatedMethodInAnnotatedClass()' is declared in class 'pkg.AnnotatedClass' scheduled for removal in version 123.456">staticNonAnnotatedMethodInAnnotatedClass</warning>
|
||||
@@ -89,4 +91,40 @@ open class DirectOverrideAnnotatedMethod : NonAnnotatedClass() {
|
||||
//No warning should be produced.
|
||||
class IndirectOverrideAnnotatedMethod : DirectOverrideAnnotatedMethod() {
|
||||
override fun annotatedMethodInNonAnnotatedClass() {}
|
||||
}
|
||||
|
||||
class ClassWithScheduledForRemovalTypeInSignature<T : <warning descr="'pkg.AnnotatedClass' is scheduled for removal in version 123.456">AnnotatedClass</warning>>
|
||||
|
||||
class OwnerOfMembersWithScheduledForRemovalTypesInSignature {
|
||||
var field: <warning descr="'pkg.AnnotatedClass' is scheduled for removal in version 123.456">AnnotatedClass</warning>? = null
|
||||
|
||||
var fieldPkg: <warning descr="'annotatedPkg.ClassInAnnotatedPkg' is declared in package 'annotatedPkg' scheduled for removal in version 123.456">ClassInAnnotatedPkg</warning>? = null
|
||||
|
||||
fun parameterType(param: <warning descr="'pkg.AnnotatedClass' is scheduled for removal in version 123.456">AnnotatedClass</warning>?) {}
|
||||
|
||||
fun parameterTypePkg(param: <warning descr="'annotatedPkg.ClassInAnnotatedPkg' is declared in package 'annotatedPkg' scheduled for removal in version 123.456">ClassInAnnotatedPkg</warning>?) {}
|
||||
|
||||
fun returnType(): <warning descr="'pkg.AnnotatedClass' is scheduled for removal in version 123.456">AnnotatedClass</warning>? {
|
||||
return null
|
||||
}
|
||||
|
||||
fun returnTypePkg(): <warning descr="'pkg.AnnotatedClass' is scheduled for removal in version 123.456">AnnotatedClass</warning>? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
class WarningsOfScheduledForRemovalTypesInSignature {
|
||||
fun classUsage() {
|
||||
<warning descr="'ClassWithScheduledForRemovalTypeInSignature' is scheduled for removal because its signature references class 'pkg.AnnotatedClass' scheduled for removal in version 123.456">ClassWithScheduledForRemovalTypeInSignature</warning><<warning descr="'pkg.AnnotatedClass' is scheduled for removal in version 123.456">AnnotatedClass</warning>>()
|
||||
}
|
||||
|
||||
fun membersUsages(owner: OwnerOfMembersWithScheduledForRemovalTypesInSignature) {
|
||||
val field = owner.<warning descr="'getField()' is scheduled for removal because its signature references class 'pkg.AnnotatedClass' scheduled for removal in version 123.456">field</warning>
|
||||
owner.<warning descr="'parameterType(pkg.AnnotatedClass)' is scheduled for removal because its signature references class 'pkg.AnnotatedClass' scheduled for removal in version 123.456">parameterType</warning>(null)
|
||||
owner.<warning descr="'returnType()' is scheduled for removal because its signature references class 'pkg.AnnotatedClass' scheduled for removal in version 123.456">returnType</warning>()
|
||||
|
||||
val fieldPkg = owner.<warning descr="'getField()' is scheduled for removal because its signature references class 'pkg.AnnotatedClass' scheduled for removal in version 123.456">field</warning>
|
||||
owner.<warning descr="'parameterTypePkg(annotatedPkg.ClassInAnnotatedPkg)' is scheduled for removal because its signature references class 'annotatedPkg.ClassInAnnotatedPkg' scheduled for removal in version 123.456">parameterTypePkg</warning>(null)
|
||||
owner.<warning descr="'returnTypePkg()' is scheduled for removal because its signature references class 'pkg.AnnotatedClass' scheduled for removal in version 123.456">returnTypePkg</warning>()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user