diff --git a/plugins/devkit/devkit-core/resources/inspectionDescriptions/CanBeDumbAware.html b/plugins/devkit/devkit-core/resources/inspectionDescriptions/CanBeDumbAware.html
new file mode 100644
index 000000000000..7270ee5777c0
--- /dev/null
+++ b/plugins/devkit/devkit-core/resources/inspectionDescriptions/CanBeDumbAware.html
@@ -0,0 +1,21 @@
+
+
+Reports API that can be made DumbAware.
+
+ If the implementation does not access indexes, it can be marked as working during dumb mode.
+
+
+ See DumbAware API
+ for more information.
+
+
+
+New in 2025.1
+
+
+ Add classes that have been verified not 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.
+
+
+
\ No newline at end of file
diff --git a/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml b/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml
index ee571be5d810..5a903f0a00d7 100644
--- a/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml
+++ b/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml
@@ -468,6 +468,13 @@
level="ERROR"
implementationClass="org.jetbrains.idea.devkit.inspections.PotentialDeadlockInServiceInitializationInspection"/>
+
+
diff --git a/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties b/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties
index a8c45b5fcbc7..08f8779c62cb 100644
--- a/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties
+++ b/plugins/devkit/devkit-core/resources/messages/DevKitBundle.properties
@@ -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}''
\ No newline at end of file
diff --git a/plugins/devkit/devkit-core/src/inspections/CanBeDumbAwareInspection.kt b/plugins/devkit/devkit-core/src/inspections/CanBeDumbAwareInspection.kt
new file mode 100644
index 000000000000..2827190494dd
--- /dev/null
+++ b/plugins/devkit/devkit-core/src/inspections/CanBeDumbAwareInspection.kt
@@ -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 = 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)
+ }
+}
\ No newline at end of file
diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/AbstractClassNotImplementingDumbAware.java b/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/AbstractClassNotImplementingDumbAware.java
new file mode 100644
index 000000000000..0276c281f3c4
--- /dev/null
+++ b/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/AbstractClassNotImplementingDumbAware.java
@@ -0,0 +1,5 @@
+import com.intellij.openapi.project.PossiblyDumbAware;
+
+public abstract class AbstractClassNotImplementingDumbAware implements PossiblyDumbAware {
+
+}
\ No newline at end of file
diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/ImplementingDumbAware.java b/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/ImplementingDumbAware.java
new file mode 100644
index 000000000000..33c44bbacf77
--- /dev/null
+++ b/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/ImplementingDumbAware.java
@@ -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();
+ }
+}
\ No newline at end of file
diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/ImplementingDumbAwareByParent.java b/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/ImplementingDumbAwareByParent.java
new file mode 100644
index 000000000000..404b396f379e
--- /dev/null
+++ b/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/ImplementingDumbAwareByParent.java
@@ -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 {}
\ No newline at end of file
diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/NotImplementingDumbAware.java b/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/NotImplementingDumbAware.java
new file mode 100644
index 000000000000..6f24b41142a6
--- /dev/null
+++ b/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/NotImplementingDumbAware.java
@@ -0,0 +1,4 @@
+import com.intellij.openapi.project.PossiblyDumbAware;
+
+public class NotImplementingDumbAware implements PossiblyDumbAware {
+}
\ No newline at end of file
diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/NotImplementingDumbAwareByParent.java b/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/NotImplementingDumbAwareByParent.java
new file mode 100644
index 000000000000..f392b0dbe112
--- /dev/null
+++ b/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/NotImplementingDumbAwareByParent.java
@@ -0,0 +1,6 @@
+import com.intellij.openapi.project.PossiblyDumbAware;
+
+public class NotImplementingDumbAwareByParent extends ByParent {
+}
+
+abstract class ByParent implements PossiblyDumbAware {}
\ No newline at end of file
diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/NotImplementingPossiblyDumbAware.java b/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/NotImplementingPossiblyDumbAware.java
new file mode 100644
index 000000000000..173a9651dd7a
--- /dev/null
+++ b/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/NotImplementingPossiblyDumbAware.java
@@ -0,0 +1,5 @@
+public class NotImplementingPossiblyDumbAware {
+ public boolean isDumbAware() {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/OverridingIsDumbAware.java b/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/OverridingIsDumbAware.java
new file mode 100644
index 000000000000..c38f8778ff8c
--- /dev/null
+++ b/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/OverridingIsDumbAware.java
@@ -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();
+ }
+}
\ No newline at end of file
diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/OverridingIsDumbAwareByInterface.java b/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/OverridingIsDumbAwareByInterface.java
new file mode 100644
index 000000000000..6e8e43c36ad0
--- /dev/null
+++ b/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/OverridingIsDumbAwareByInterface.java
@@ -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;
+ }
+
+}
\ No newline at end of file
diff --git a/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/OverridingIsDumbAwareByParent.java b/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/OverridingIsDumbAwareByParent.java
new file mode 100644
index 000000000000..898f2856f293
--- /dev/null
+++ b/plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/OverridingIsDumbAwareByParent.java
@@ -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;
+ }
+
+}
\ No newline at end of file
diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/CanBeDumbAwareInspectionTest.kt b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/CanBeDumbAwareInspectionTest.kt
new file mode 100644
index 000000000000..4c6ce9487d6f
--- /dev/null
+++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/CanBeDumbAwareInspectionTest.kt
@@ -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()
+ }
+}
\ No newline at end of file
diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/DevkitInspectionsRegistrationCheckTest.java b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/DevkitInspectionsRegistrationCheckTest.java
index 3d6a9811592b..7c064f14556e 100644
--- a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/DevkitInspectionsRegistrationCheckTest.java
+++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/DevkitInspectionsRegistrationCheckTest.java
@@ -27,6 +27,7 @@ public class DevkitInspectionsRegistrationCheckTest extends BasePlatformTestCase
*/
private static final List WIP_INSPECTIONS =
Stream.of("ExtensionClassShouldBeFinalAndNonPublic",
+ "CanBeDumbAware",
"CancellationCheckInLoops",
"ThreadingConcurrency",
"CallingMethodShouldBeRequiresBlockingContext",
@@ -41,7 +42,7 @@ public class DevkitInspectionsRegistrationCheckTest extends BasePlatformTestCase
List 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 disabledInspections = ContainerUtil.filter(devkitInspections, ep -> !ep.enabledByDefault);