diff --git a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/OverrideOnlyInspection.kt b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/OverrideOnlyInspection.kt index 34308179d769..624c1971eb8a 100644 --- a/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/OverrideOnlyInspection.kt +++ b/jvm/jvm-analysis-impl/src/com/intellij/codeInspection/OverrideOnlyInspection.kt @@ -1,15 +1,14 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2025 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.lang.jvm.JvmModifier 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 @@ -28,7 +27,6 @@ private inline val ANNOTATION_NAME get() = ApiStatus.OverrideOnly::class.java.ca */ @VisibleForTesting class OverrideOnlyInspection : LocalInspectionTool() { - override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor = if (AnnotatedApiUsageUtil.canAnnotationBeUsedInFile(ANNOTATION_NAME, holder.file)) { ApiUsageUastVisitor.createPsiElementVisitor(OverrideOnlyProcessor(holder)) @@ -38,19 +36,21 @@ class OverrideOnlyInspection : LocalInspectionTool() { } 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 PsiMethod.isOverridable(): Boolean { + return !hasModifier(JvmModifier.PRIVATE) && !hasModifier(JvmModifier.FINAL) && !hasModifier(JvmModifier.STATIC) } - private fun isOverrideOnlyMethod(method: PsiMethod) = - method.hasAnnotation(ANNOTATION_NAME) || - !method.hasModifierProperty(PsiModifier.STATIC) && method.containingClass?.hasAnnotation(ANNOTATION_NAME) == true + private fun isLibraryElement(method: PsiMethod): Boolean { + val containingVirtualFile = PsiUtilCore.getVirtualFile(method) + return containingVirtualFile != null && ProjectFileIndex.getInstance(method.project).isInLibraryClasses(containingVirtualFile) + } + + private fun isOverrideOnlyMethod(method: PsiMethod): Boolean { + return method.hasAnnotation(ANNOTATION_NAME) || 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) + MethodSignatureUtil.areSignaturesEqual(it.javaPsi, target) } ?: false private fun isSuperCall(qualifier: UExpression?) = qualifier is USuperExpression @@ -67,7 +67,7 @@ class OverrideOnlyInspection : LocalInspectionTool() { } override fun processReference(sourceNode: UElement, target: PsiModifierListOwner, qualifier: UExpression?) { - if (target is PsiMethod && isOverrideOnlyMethod(target) && isLibraryElement(target) + if (target is PsiMethod && target.isOverridable() && isLibraryElement(target) && isOverrideOnlyMethod(target) && !isSuperOrDelegateCall(sourceNode, target, qualifier)) { val elementToHighlight = sourceNode.sourcePsi ?: return val methodName = HighlightMessageUtil.getSymbolName(target) ?: return diff --git a/jvm/jvm-analysis-kotlin-tests-shared/testData/codeInspection/overrideOnly/library/JavaClassOverrideOnly.class b/jvm/jvm-analysis-kotlin-tests-shared/testData/codeInspection/overrideOnly/library/JavaClassOverrideOnly.class index 174e7095abaf..6d1c3afc0272 100644 Binary files a/jvm/jvm-analysis-kotlin-tests-shared/testData/codeInspection/overrideOnly/library/JavaClassOverrideOnly.class and b/jvm/jvm-analysis-kotlin-tests-shared/testData/codeInspection/overrideOnly/library/JavaClassOverrideOnly.class differ diff --git a/jvm/jvm-analysis-kotlin-tests-shared/testData/codeInspection/overrideOnly/library/JavaMembersOverrideOnly.class b/jvm/jvm-analysis-kotlin-tests-shared/testData/codeInspection/overrideOnly/library/JavaMembersOverrideOnly.class new file mode 100644 index 000000000000..12582f528ea1 Binary files /dev/null and b/jvm/jvm-analysis-kotlin-tests-shared/testData/codeInspection/overrideOnly/library/JavaMembersOverrideOnly.class differ diff --git a/jvm/jvm-analysis-kotlin-tests-shared/testData/codeInspection/overrideOnly/plugin/JavaCode.java b/jvm/jvm-analysis-kotlin-tests-shared/testData/codeInspection/overrideOnly/plugin/JavaCode.java index 19ea325b8da7..6b9b508a6fe0 100644 --- a/jvm/jvm-analysis-kotlin-tests-shared/testData/codeInspection/overrideOnly/plugin/JavaCode.java +++ b/jvm/jvm-analysis-kotlin-tests-shared/testData/codeInspection/overrideOnly/plugin/JavaCode.java @@ -8,6 +8,7 @@ import library.KotlinInterface; import library.JavaClassOverrideOnly; import library.JavaInterfaceOverrideOnly; +import library.JavaMembersOverrideOnly; import library.KotlinClassOverrideOnly; import library.KotlinInterfaceOverrideOnly; @@ -20,6 +21,7 @@ class Invoker { JavaClassOverrideOnly javaClassOverrideOnly, JavaInterfaceOverrideOnly javaInterfaceOverrideOnly, + JavaMembersOverrideOnly javeMembersOverrideOnly, KotlinClassOverrideOnly kotlinClassOverrideOnly, KotlinInterfaceOverrideOnly kotlinInterfaceOverrideOnly ) { @@ -29,13 +31,17 @@ class Invoker { kotlinInterface.implementOnlyMethod(); javaClassOverrideOnly.overrideOnlyMethod(); + javaClassOverrideOnly.finalMethod(); // no warning because it's a final method javaInterfaceOverrideOnly.implementOnlyMethod(); + javeMembersOverrideOnly.implementOnlyMethod(); + javeMembersOverrideOnly.finalMethod(); // no warning because it's a final method kotlinClassOverrideOnly.overrideOnlyMethod(); kotlinInterfaceOverrideOnly.implementOnlyMethod(); //No warning JavaClassOverrideOnly.staticMethod(); JavaInterfaceOverrideOnly.staticMethod(); + JavaMembersOverrideOnly.staticMethod(); KotlinClassOverrideOnly.staticMethod(); KotlinInterfaceOverrideOnly.staticMethod(); } @@ -47,15 +53,19 @@ class Invoker { Consumer d = KotlinInterface::implementOnlyMethod; Consumer a1 = JavaClassOverrideOnly::overrideOnlyMethod; + Consumer a2 = JavaClassOverrideOnly::finalMethod; // no warning because it's a final method Consumer b1 = JavaInterfaceOverrideOnly::implementOnlyMethod; - Consumer c1 = KotlinClassOverrideOnly::overrideOnlyMethod; - Consumer d1 = KotlinInterfaceOverrideOnly::implementOnlyMethod; + Consumer c1 = JavaMembersOverrideOnly::implementOnlyMethod; + Consumer c2 = JavaMembersOverrideOnly::finalMethod; // no warning because it's a final method + Consumer d1 = KotlinClassOverrideOnly::overrideOnlyMethod; + Consumer e1 = KotlinInterfaceOverrideOnly::implementOnlyMethod; //No warning - Runnable a2 = JavaClassOverrideOnly::staticMethod; + Runnable a3 = JavaClassOverrideOnly::staticMethod; Runnable b2 = JavaInterfaceOverrideOnly::staticMethod; - Runnable c2 = KotlinClassOverrideOnly::staticMethod; - Runnable d2 = KotlinInterfaceOverrideOnly::staticMethod; + Runnable c3 = JavaMembersOverrideOnly::staticMethod; + Runnable d2 = KotlinClassOverrideOnly::staticMethod; + Runnable e2 = KotlinInterfaceOverrideOnly::staticMethod; } } diff --git a/jvm/jvm-analysis-kotlin-tests-shared/testData/codeInspection/overrideOnly/src/library/JavaClassOverrideOnly.java b/jvm/jvm-analysis-kotlin-tests-shared/testData/codeInspection/overrideOnly/src/library/JavaClassOverrideOnly.java index 29715f4bb0fc..112cc648ddde 100644 --- a/jvm/jvm-analysis-kotlin-tests-shared/testData/codeInspection/overrideOnly/src/library/JavaClassOverrideOnly.java +++ b/jvm/jvm-analysis-kotlin-tests-shared/testData/codeInspection/overrideOnly/src/library/JavaClassOverrideOnly.java @@ -7,5 +7,7 @@ public abstract class JavaClassOverrideOnly { public abstract void overrideOnlyMethod(); - public static void staticMethod() {} + public static void staticMethod() { } + + public final void finalMethod() { } } diff --git a/jvm/jvm-analysis-kotlin-tests-shared/testData/codeInspection/overrideOnly/src/library/JavaMembersOverrideOnly.java b/jvm/jvm-analysis-kotlin-tests-shared/testData/codeInspection/overrideOnly/src/library/JavaMembersOverrideOnly.java new file mode 100644 index 000000000000..b7110993ccaf --- /dev/null +++ b/jvm/jvm-analysis-kotlin-tests-shared/testData/codeInspection/overrideOnly/src/library/JavaMembersOverrideOnly.java @@ -0,0 +1,14 @@ +package library; + +import org.jetbrains.annotations.ApiStatus.OverrideOnly; + +public abstract class JavaMembersOverrideOnly { + @OverrideOnly + public abstract void implementOnlyMethod(); + + @OverrideOnly + public static void staticMethod() { } + + @OverrideOnly + public final void finalMethod() { } +}