Files
openide/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/OverrideOnlyInspection.kt
Johannes Spangenberg 6c7da7b25a IDEA-367007 Exclude static methods from @OverrideOnly
GitOrigin-RevId: 0f67f91edf0558fcab0be98927e0828872ec21d4
2025-02-11 00:47:19 +00:00

81 lines
3.7 KiB
Kotlin

// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection
import com.intellij.analysis.JvmAnalysisBundle
import com.intellij.codeInsight.daemon.impl.analysis.HighlightMessageUtil
import com.intellij.codeInspection.apiUsage.ApiUsageProcessor
import com.intellij.codeInspection.apiUsage.ApiUsageUastVisitor
import com.intellij.openapi.roots.ProjectFileIndex
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiModifier
import com.intellij.psi.PsiModifierListOwner
import com.intellij.psi.util.MethodSignatureUtil
import com.intellij.psi.util.PsiUtilCore
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.VisibleForTesting
import org.jetbrains.uast.UElement
import org.jetbrains.uast.UExpression
import org.jetbrains.uast.USuperExpression
import org.jetbrains.uast.getContainingUMethod
private inline val ANNOTATION_NAME get() = ApiStatus.OverrideOnly::class.java.canonicalName!!
/**
* UAST-based inspection checking that no API method, which is marked with [ApiStatus.OverrideOnly] annotation,
* is referenced or invoked in client code.
*/
@VisibleForTesting
class OverrideOnlyInspection : LocalInspectionTool() {
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor =
if (AnnotatedApiUsageUtil.canAnnotationBeUsedInFile(ANNOTATION_NAME, holder.file)) {
ApiUsageUastVisitor.createPsiElementVisitor(OverrideOnlyProcessor(holder))
}
else {
PsiElementVisitor.EMPTY_VISITOR
}
private class OverrideOnlyProcessor(private val problemsHolder: ProblemsHolder) : ApiUsageProcessor {
private fun isLibraryElement(element: PsiElement): Boolean {
val containingVirtualFile = PsiUtilCore.getVirtualFile(element)
return containingVirtualFile != null && ProjectFileIndex.getInstance(element.project).isInLibraryClasses(containingVirtualFile)
}
private fun isOverrideOnlyMethod(method: PsiMethod) =
method.hasAnnotation(ANNOTATION_NAME) ||
!method.hasModifierProperty(PsiModifier.STATIC) && method.containingClass?.hasAnnotation(ANNOTATION_NAME) == true
private fun isInsideOverridenOnlyMethod(sourceNode: UElement, target: PsiMethod): Boolean = sourceNode.getContainingUMethod()?.let {
val psiMethod = it.javaPsi as? PsiMethod ?: return false
MethodSignatureUtil.areSignaturesEqual(psiMethod, target)
} ?: false
private fun isSuperCall(qualifier: UExpression?) = qualifier is USuperExpression
private fun isDelegateCall(qualifier: UExpression?, target: PsiMethod) = qualifier?.let {
val methodClassName = target.containingClass?.qualifiedName ?: return@let false
it.getExpressionType()?.isInheritorOf(methodClassName)
} ?: false
private fun isSuperOrDelegateCall(sourceNode: UElement, target: PsiMethod, qualifier: UExpression?): Boolean {
if (!isInsideOverridenOnlyMethod(sourceNode, target)) return false
return isSuperCall(qualifier) || isDelegateCall(qualifier, target)
}
override fun processReference(sourceNode: UElement, target: PsiModifierListOwner, qualifier: UExpression?) {
if (target is PsiMethod && isOverrideOnlyMethod(target) && isLibraryElement(target)
&& !isSuperOrDelegateCall(sourceNode, target, qualifier)) {
val elementToHighlight = sourceNode.sourcePsi ?: return
val methodName = HighlightMessageUtil.getSymbolName(target) ?: return
val description = JvmAnalysisBundle.message("jvm.inspections.api.override.only.description", methodName)
problemsHolder.registerProblem(elementToHighlight, description, ProblemHighlightType.GENERIC_ERROR_OR_WARNING)
}
}
}
}