[devkit] new inspection: "Can be DumbAware" (IJPL-115445), initial

GitOrigin-RevId: bf9dc5ce980b721fece8d1fde9d4f9b726fa92ca
This commit is contained in:
Yann Cébron
2024-10-17 15:40:10 +00:00
committed by intellij-monorepo-bot
parent fb0c01850e
commit f3e97446ec
15 changed files with 268 additions and 1 deletions
@@ -0,0 +1,21 @@
<html>
<body>
Reports API that can be made <code>DumbAware</code>.
<p>
If the implementation does not access indexes, it can be marked as working during dumb mode.
</p>
<p>
See <a href="https://plugins.jetbrains.com/docs/intellij/indexing-and-psi-stubs.html#DumbAwareAPI">DumbAware API</a>
for more information.
</p>
<!-- tooltip end -->
<p><small>New in 2025.1</small>
</p>
<p>
Add classes that have been verified <em>not</em> to be suitable to "Ignore class list".
These will be skipped while avoiding adding a suppression comment/annotation.
A quick-fix is available to add the highlighted class to this list.
</p>
</body>
</html>
@@ -468,6 +468,13 @@
level="ERROR"
implementationClass="org.jetbrains.idea.devkit.inspections.PotentialDeadlockInServiceInitializationInspection"/>
<localInspection language="JVM"
projectType="INTELLIJ_PLUGIN"
groupPathKey="inspections.group.path" groupKey="inspections.group.code"
enabledByDefault="false" level="WEAK WARNING"
implementationClass="org.jetbrains.idea.devkit.inspections.CanBeDumbAwareInspection"
key="inspection.can.be.dumb.aware.name"/>
<moduleConfigurationEditorProvider implementation="org.jetbrains.idea.devkit.module.PluginModuleEditorsProvider"/>
<implicitUsageProvider implementation="org.jetbrains.idea.devkit.inspections.DevKitImplicitUsageProvider"/>
@@ -705,3 +705,9 @@ devkit.unstable.api.usage.ignore.declared.inside.this.project=Ignore unstable AP
inspections.jcomponent.data.provider.display.name=JComponent must use UiDataProvider
inspections.jcomponent.data.provider.use.uidataprovider.instead.of.dataprovider=Use UiDataProvider instead of DataProvider
inspection.can.be.dumb.aware.name=Can be DumbAware
inspection.can.be.dumb.aware.settings.ignore.classes.title=Ignore class list:
inspection.can.be.dumb.aware.settings.ignore.classes.dialog.title=Specify Class to Ignore
inspection.can.be.dumb.aware.message=Can be made DumbAware if it does not access indexes
inspection.can.be.dumb.aware.quickfix.add.to.ignore=Ignore ''{0}''
@@ -0,0 +1,85 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.inspections
import com.intellij.codeInsight.options.JavaClassValidator
import com.intellij.codeInspection.LocalQuickFix
import com.intellij.codeInspection.ProblemDescriptor
import com.intellij.codeInspection.options.OptPane
import com.intellij.codeInspection.options.OptPane.pane
import com.intellij.codeInspection.options.OptPane.stringList
import com.intellij.codeInspection.util.IntentionFamilyName
import com.intellij.lang.jvm.JvmModifier
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.PossiblyDumbAware
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiModifier
import com.intellij.psi.PsiTypes
import com.intellij.psi.impl.light.LightMethodBuilder
import com.intellij.psi.util.InheritanceUtil
import com.siyeh.ig.ui.ExternalizableStringSet
import org.jetbrains.idea.devkit.DevKitBundle
internal class CanBeDumbAwareInspection : DevKitJvmInspection.ForClass() {
@Suppress("MemberVisibilityCanBePrivate")
var ignoreClasses: MutableList<String> = ExternalizableStringSet()
override fun getOptionsPane(): OptPane {
return pane(
stringList("ignoreClasses", DevKitBundle.message("inspection.can.be.dumb.aware.settings.ignore.classes.title"),
JavaClassValidator()
.withTitle(DevKitBundle.message("inspection.can.be.dumb.aware.settings.ignore.classes.dialog.title"))
.withSuperClass(PossiblyDumbAware::class.java.canonicalName))
)
}
override fun checkClass(project: Project, psiClass: PsiClass, sink: HighlightSink) {
if (psiClass.hasModifier(JvmModifier.ABSTRACT)) return
val qualifiedName = psiClass.qualifiedName
if (ignoreClasses.contains(qualifiedName)) return
val possiblyDumbAwarePsiClass = JavaPsiFacade.getInstance(project)
.findClass(PossiblyDumbAware::class.java.canonicalName, psiClass.resolveScope) ?: return
if (!psiClass.isInheritor(possiblyDumbAwarePsiClass, true)) return
if (InheritanceUtil.isInheritor(psiClass, DumbAware::class.java.canonicalName)) return
val isDumbAwareMethod = LightMethodBuilder(psiClass.manager, "isDumbAware")
.setContainingClass(possiblyDumbAwarePsiClass)
.setModifiers(PsiModifier.PUBLIC)
.setMethodReturnType(PsiTypes.booleanType())
val overriddenMethod = psiClass.findMethodBySignature(isDumbAwareMethod, true)
if (overriddenMethod != null) {
if (overriddenMethod.containingClass != possiblyDumbAwarePsiClass) {
return
}
// explicit search for 'default' method in interfaces is necessary
for (clazz in psiClass.interfaces) {
val overriddenInInterface = clazz.findMethodBySignature(isDumbAwareMethod, false)
if (overriddenInInterface != null &&
overriddenInInterface.containingClass != possiblyDumbAwarePsiClass) {
return
}
}
}
val fixes =
when {
qualifiedName != null -> object : LocalQuickFix {
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
ignoreClasses.add(qualifiedName)
}
override fun getFamilyName(): @IntentionFamilyName String {
return DevKitBundle.message("inspection.can.be.dumb.aware.quickfix.add.to.ignore", qualifiedName)
}
}
else -> null
}
sink.highlight(DevKitBundle.message("inspection.can.be.dumb.aware.message"), fixes)
}
}
@@ -0,0 +1,5 @@
import com.intellij.openapi.project.PossiblyDumbAware;
public abstract class AbstractClassNotImplementingDumbAware implements PossiblyDumbAware {
}
@@ -0,0 +1,10 @@
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.PossiblyDumbAware;
public class ImplementingDumbAware implements PossiblyDumbAware, DumbAware {
// explicit override does not matter
@Override
public boolean isDumbAware() {
return PossiblyDumbAware.super.isDumbAware();
}
}
@@ -0,0 +1,8 @@
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.PossiblyDumbAware;
public class ImplementingDumbAwareByParent extends ByParent {
}
interface ParentPossiblyDumbAware extends PossiblyDumbAware {}
abstract class ByParent implements ParentPossiblyDumbAware, DumbAware {}
@@ -0,0 +1,4 @@
import com.intellij.openapi.project.PossiblyDumbAware;
public class <weak_warning descr="Can be made DumbAware if it does not access indexes">NotImplementingDumbAware</weak_warning> implements PossiblyDumbAware {
}
@@ -0,0 +1,6 @@
import com.intellij.openapi.project.PossiblyDumbAware;
public class <weak_warning descr="Can be made DumbAware if it does not access indexes">NotImplementingDumbAwareByParent</weak_warning> extends ByParent {
}
abstract class ByParent implements PossiblyDumbAware {}
@@ -0,0 +1,5 @@
public class NotImplementingPossiblyDumbAware {
public boolean isDumbAware() {
return true;
}
}
@@ -0,0 +1,9 @@
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.PossiblyDumbAware;
public class OverridingIsDumbAware implements PossiblyDumbAware {
@Override
public boolean isDumbAware() {
return PossiblyDumbAware.super.isDumbAware();
}
}
@@ -0,0 +1,14 @@
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.PossiblyDumbAware;
public class OverridingIsDumbAwareByInterface implements ByInterface {
}
interface ByInterface extends PossiblyDumbAware {
@Override
default boolean isDumbAware() {
return true;
}
}
@@ -0,0 +1,14 @@
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.PossiblyDumbAware;
public class OverridingIsDumbAwareByParent extends ByParent {
}
abstract class ByParent implements PossiblyDumbAware {
@Override
public boolean isDumbAware() {
return true;
}
}
@@ -0,0 +1,72 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.devkit.inspections
import com.intellij.testFramework.TestDataPath
import org.jetbrains.idea.devkit.DevkitJavaTestsUtil
import org.jetbrains.idea.devkit.inspections.quickfix.LightDevKitInspectionFixTestBase
@TestDataPath("/inspections/canBeDumbAware")
class CanBeDumbAwareInspectionTest : LightDevKitInspectionFixTestBase() {
override fun getBasePath() = DevkitJavaTestsUtil.TESTDATA_PATH + "inspections/canBeDumbAware"
override fun getFileExtension(): String = "java"
override fun setUp() {
super.setUp()
myFixture.enableInspections(CanBeDumbAwareInspection())
myFixture.addClass("""
package com.intellij.openapi.project;
public interface DumbAware {}
""".trimIndent())
myFixture.addClass("""
package com.intellij.openapi.project;
public interface PossiblyDumbAware {
default boolean isDumbAware() {
//noinspection SSBasedInspection
return this instanceof DumbAware;
}
}
""".trimIndent())
}
fun testNotImplementingPossiblyDumbAware() {
doTest()
}
fun testImplementingDumbAware() {
doTest()
}
fun testNotImplementingDumbAware() {
doTest()
}
fun testAbstractClassNotImplementingDumbAware() {
doTest()
}
fun testNotImplementingDumbAwareByParent() {
doTest()
}
fun testImplementingDumbAwareByParent() {
doTest()
}
fun testOverridingIsDumbAware() {
doTest()
}
fun testOverridingIsDumbAwareByParent() {
doTest()
}
fun testOverridingIsDumbAwareByInterface() {
doTest()
}
}
@@ -27,6 +27,7 @@ public class DevkitInspectionsRegistrationCheckTest extends BasePlatformTestCase
*/
private static final List<String> WIP_INSPECTIONS =
Stream.of("ExtensionClassShouldBeFinalAndNonPublic",
"CanBeDumbAware",
"CancellationCheckInLoops",
"ThreadingConcurrency",
"CallingMethodShouldBeRequiresBlockingContext",
@@ -41,7 +42,7 @@ public class DevkitInspectionsRegistrationCheckTest extends BasePlatformTestCase
List<LocalInspectionEP> devkitInspections = ContainerUtil.filter(LocalInspectionEP.LOCAL_INSPECTION.getExtensionList(), ep -> {
return "DevKit".equals(ep.getPluginDescriptor().getPluginId().getIdString());
});
assertEquals("Mismatch in total inspections, check classpath in test run configuration (intellij.devkit.plugin)", 73,
assertEquals("Mismatch in total inspections, check classpath in test run configuration (intellij.devkit.plugin)", 74,
devkitInspections.size());
List<LocalInspectionEP> disabledInspections = ContainerUtil.filter(devkitInspections, ep -> !ep.enabledByDefault);