From f3e97446ec0c165b3ac844194e10b5610527a2ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yann=20C=C3=A9bron?= Date: Thu, 17 Oct 2024 15:52:22 +0200 Subject: [PATCH] [devkit] new inspection: "Can be DumbAware" (IJPL-115445), initial GitOrigin-RevId: bf9dc5ce980b721fece8d1fde9d4f9b726fa92ca --- .../CanBeDumbAware.html | 21 +++++ .../resources/intellij.devkit.core.xml | 7 ++ .../messages/DevKitBundle.properties | 6 ++ .../inspections/CanBeDumbAwareInspection.kt | 85 +++++++++++++++++++ ...AbstractClassNotImplementingDumbAware.java | 5 ++ .../canBeDumbAware/ImplementingDumbAware.java | 10 +++ .../ImplementingDumbAwareByParent.java | 8 ++ .../NotImplementingDumbAware.java | 4 + .../NotImplementingDumbAwareByParent.java | 6 ++ .../NotImplementingPossiblyDumbAware.java | 5 ++ .../canBeDumbAware/OverridingIsDumbAware.java | 9 ++ .../OverridingIsDumbAwareByInterface.java | 14 +++ .../OverridingIsDumbAwareByParent.java | 14 +++ .../CanBeDumbAwareInspectionTest.kt | 72 ++++++++++++++++ ...evkitInspectionsRegistrationCheckTest.java | 3 +- 15 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 plugins/devkit/devkit-core/resources/inspectionDescriptions/CanBeDumbAware.html create mode 100644 plugins/devkit/devkit-core/src/inspections/CanBeDumbAwareInspection.kt create mode 100644 plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/AbstractClassNotImplementingDumbAware.java create mode 100644 plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/ImplementingDumbAware.java create mode 100644 plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/ImplementingDumbAwareByParent.java create mode 100644 plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/NotImplementingDumbAware.java create mode 100644 plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/NotImplementingDumbAwareByParent.java create mode 100644 plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/NotImplementingPossiblyDumbAware.java create mode 100644 plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/OverridingIsDumbAware.java create mode 100644 plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/OverridingIsDumbAwareByInterface.java create mode 100644 plugins/devkit/devkit-java-tests/testData/inspections/canBeDumbAware/OverridingIsDumbAwareByParent.java create mode 100644 plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/CanBeDumbAwareInspectionTest.kt 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);