From 801e239dbf8753a0500c5048698eaea25f2fc543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yann=20C=C3=A9bron?= Date: Wed, 19 May 2021 18:07:16 +0200 Subject: [PATCH] IDEA-269636 Devkit: improve 'place' code insight GitOrigin-RevId: 376870875d7bfc74076fe69249b9c8f96dc47514 --- .../devkit-core/src/dom/OverrideText.java | 94 ++++++++++++++----- .../ActionOverrideTextPlaceCompletion.xml | 7 ++ .../ActionOverrideTextPlaceResolve.xml | 7 ++ .../PluginXmlFunctionalTest.groovy | 67 ++++++++----- 4 files changed, 126 insertions(+), 49 deletions(-) create mode 100644 plugins/devkit/devkit-java-tests/testData/codeInsight/ActionOverrideTextPlaceCompletion.xml create mode 100644 plugins/devkit/devkit-java-tests/testData/codeInsight/ActionOverrideTextPlaceResolve.xml diff --git a/plugins/devkit/devkit-core/src/dom/OverrideText.java b/plugins/devkit/devkit-core/src/dom/OverrideText.java index f611c48f68d9..7d9bb263a6e5 100644 --- a/plugins/devkit/devkit-core/src/dom/OverrideText.java +++ b/plugins/devkit/devkit-core/src/dom/OverrideText.java @@ -1,59 +1,105 @@ // Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.idea.devkit.dom; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementBuilder; import com.intellij.codeInspection.dataFlow.StringExpressionHelper; import com.intellij.openapi.util.Pair; -import com.intellij.psi.PsiClass; import com.intellij.psi.PsiExpression; +import com.intellij.psi.PsiField; import com.intellij.psi.PsiModifier; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.searches.AllClassesSearch; +import com.intellij.util.CommonProcessors; +import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.xml.*; +import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Collection; -import java.util.Collections; -import java.util.List; - +import java.util.Objects; public interface OverrideText extends DomElement { @NotNull @Required - @Convert(PlaceConverter.class) - GenericAttributeValue getPlace(); + @Convert(value = PlaceConverter.class, soft = true) + GenericAttributeValue getPlace(); @NotNull @Required(false) - @Convert(PlaceConverter.class) - GenericAttributeValue getUseTextOfPlace(); + @Convert(value = PlaceConverter.class, soft = true) + GenericAttributeValue getUseTextOfPlace(); @NotNull @Required(false) GenericAttributeValue getText(); - class PlaceConverter extends ResolvingConverter.StringConverter { + + class PlaceConverter extends ResolvingConverter { + + @NonNls private static final String PLACES_CLASSNAME_SUFFIX = "Places"; + + @Override + public @Nullable PsiField fromString(@Nullable String s, + ConvertContext context) { + if (s == null) return null; + CommonProcessors.FindProcessor resolve = new CommonProcessors.FindProcessor<>() { + @Override + protected boolean accept(PsiField field) { + return s.equals(getPlaceName(field)); + } + }; + + processAllActionPlaces(context, resolve); + return resolve.getFoundValue(); + } + + + @Override + public @Nullable String toString(@Nullable PsiField field, + ConvertContext context) { + return field != null ? field.getName() : null; + } + + @Override + public @Nullable + LookupElement createLookupElement(PsiField field) { + return LookupElementBuilder.create(field, Objects.requireNonNull(getPlaceName(field))) + .withStrikeoutness(field.isDeprecated()) + .withTailText(" (" + field.getName() + ")") + .withTypeText(Objects.requireNonNull(field.getContainingClass()).getName()); + } @NotNull @Override - public Collection getVariants(ConvertContext context) { - return ContainerUtil.concat(getPlaceNames(context, "com.intellij.openapi.actionSystem.ActionPlaces"), - getPlaceNames(context, "com.intellij.vcs.log.ui.VcsLogActionPlaces")); + public Collection getVariants(ConvertContext context) { + CommonProcessors.CollectProcessor collectProcessor = new CommonProcessors.CollectProcessor<>(); + processAllActionPlaces(context, collectProcessor); + return collectProcessor.getResults(); } - @NotNull - private static List getPlaceNames(ConvertContext context, String className) { - final PsiClass actionPlacesClass = DomJavaUtil.findClass(className, context.getInvocationElement()); - if (actionPlacesClass == null) return Collections.emptyList(); + private static void processAllActionPlaces(ConvertContext context, Processor fieldProcessor) { + final GlobalSearchScope scope = context.getSearchScope(); + if (scope == null) return; - return ContainerUtil.mapNotNull(actionPlacesClass.getFields(), field -> { - if (!field.hasModifierProperty(PsiModifier.PUBLIC)) return null; + AllClassesSearch.search(context.getSearchScope(), context.getProject(), s -> s.endsWith(PLACES_CLASSNAME_SUFFIX)) + .forEach(psiClass -> { + return ContainerUtil.process(psiClass.getFields(), psiField -> { + if (!psiField.hasModifierProperty(PsiModifier.PUBLIC)) return true; + if (getPlaceName(psiField) == null) return true; - final PsiExpression initializer = field.getInitializer(); - if (initializer != null) { - return Pair.getSecond(StringExpressionHelper.evaluateExpression(initializer)); - } - return null; - }); + return fieldProcessor.process(psiField); + }); + }); + } + + @Nullable + private static String getPlaceName(PsiField field) { + final PsiExpression initializer = field.getInitializer(); + return initializer != null ? Pair.getSecond(StringExpressionHelper.evaluateExpression(initializer)) : null; } } } diff --git a/plugins/devkit/devkit-java-tests/testData/codeInsight/ActionOverrideTextPlaceCompletion.xml b/plugins/devkit/devkit-java-tests/testData/codeInsight/ActionOverrideTextPlaceCompletion.xml new file mode 100644 index 000000000000..3a9173256d48 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/codeInsight/ActionOverrideTextPlaceCompletion.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/codeInsight/ActionOverrideTextPlaceResolve.xml b/plugins/devkit/devkit-java-tests/testData/codeInsight/ActionOverrideTextPlaceResolve.xml new file mode 100644 index 000000000000..fbb7e987dcef --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/codeInsight/ActionOverrideTextPlaceResolve.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/codeInsight/PluginXmlFunctionalTest.groovy b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/codeInsight/PluginXmlFunctionalTest.groovy index f54ce16aac3f..1ba46bc8b7ee 100644 --- a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/codeInsight/PluginXmlFunctionalTest.groovy +++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/codeInsight/PluginXmlFunctionalTest.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. + * Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. */ package org.jetbrains.idea.devkit.codeInsight @@ -26,6 +26,8 @@ import com.intellij.openapi.util.registry.Registry import com.intellij.openapi.vfs.VfsUtil import com.intellij.psi.ElementDescriptionUtil import com.intellij.psi.PsiElement +import com.intellij.psi.PsiField +import com.intellij.psi.PsiReference import com.intellij.testFramework.PsiTestUtil import com.intellij.testFramework.TestDataPath import com.intellij.testFramework.builders.JavaModuleFixtureBuilder @@ -406,16 +408,9 @@ class PluginXmlFunctionalTest extends JavaCodeInsightFixtureTestCase { myFixture.configureByFile("languageAttribute.xml") - def lookupElements = myFixture.complete(CompletionType.BASIC).sort { it.lookupString } - assertLookupElement(lookupElements[0], "MyAnonymousLanguageID", "MyLanguage.MySubLanguage") - assertLookupElement(lookupElements[1], "MyLanguageID", "MyLanguage") - } - - private static void assertLookupElement(LookupElement element, String lookupString, String typeText) { - def presentation = new LookupElementPresentation() - element.renderElement(presentation) - assertEquals(lookupString, presentation.itemText) - assertEquals(typeText, presentation.typeText) + def lookupElements = myFixture.complete(CompletionType.BASIC) + assertLookupElement(lookupElements, "MyAnonymousLanguageID", null, "MyLanguage.MySubLanguage") + assertLookupElement(lookupElements, "MyLanguageID", null, "MyLanguage") } @SuppressWarnings("ComponentNotRegistered") @@ -431,7 +426,7 @@ class PluginXmlFunctionalTest extends JavaCodeInsightFixtureTestCase { void testIconAttributeCompletion() { addIconClasses() myFixture.configureByFile("iconAttributeCompletion.xml") - Registry.get("ide.completion.variant.limit").setValue("5000", getTestRootDisposable()); + Registry.get("ide.completion.variant.limit").setValue("5000", getTestRootDisposable()) myFixture.completeBasic() List lookupElementStrings = myFixture.getLookupElementStrings() @@ -664,17 +659,27 @@ public class MyErrorHandler extends ErrorReportSubmitter {} myFixture.completeBasic() LookupElement[] lookupElements = myFixture.getLookupElements() - assertActionLookupElement(lookupElements[0], "actionId", " \"ActionId Text\"", "ActionId description") - assertActionLookupElement(lookupElements[1], "actionId.localized", " \"Action Localized Text\"", "Action localized description") - assertActionLookupElement(lookupElements[2], "actionId.missing.localized", null, null) + assertLookupElement(lookupElements, "actionId", " \"ActionId Text\"", "ActionId description") + assertLookupElement(lookupElements, "actionId.localized", " \"Action Localized Text\"", "Action localized description") + assertLookupElement(lookupElements, "actionId.missing.localized", null, null) } - private static void assertActionLookupElement(LookupElement element, String lookupText, String tailText, String typeText) { - def presentation = new LookupElementPresentation() - element.renderElement(presentation) - assertEquals(lookupText, presentation.itemText) - assertEquals(tailText, presentation.tailText) - assertEquals(typeText, presentation.typeText) + void testActionOverrideTextPlaceCompletion() { + configureByFile() + myFixture.addClass("public interface CustomPlaces { String CUSTOM=\"custom\"; }") + + myFixture.completeBasic() + LookupElement[] lookupElements = myFixture.getLookupElements() + assertLookupElement(lookupElements, "FavoritesPopup", " (FAVORITES_VIEW_POPUP)", "ActionPlaces") + assertLookupElement(lookupElements, "custom", " (CUSTOM)", "CustomPlaces") + } + + void testActionOverrideTextPlaceResolve() { + configureByFile() + + PsiReference reference = myFixture.getReferenceAtCaretPositionWithAssertion() + PsiField resolvedField = assertInstanceOf(reference.resolve(), PsiField.class) + assertEquals("FAVORITES_VIEW_POPUP", resolvedField.getName()) } void testExtensionPointNameValidity() { @@ -777,10 +782,6 @@ public class MyErrorHandler extends ErrorReportSubmitter {} doHighlightingTest("pluginWithSinceBuildGreaterThanUntilBuild.xml") } - private void doHighlightingTest(String... filePaths) { - myFixture.testHighlighting(true, false, false, filePaths) - } - void testProductDescriptor() { doHighlightingTest("productDescriptor.xml") } @@ -813,4 +814,20 @@ public class MyErrorHandler extends ErrorReportSubmitter {} void testRedundantServiceInterfaceClass() { doHighlightingTest("redundantServiceInterfaceClass.xml") } + + private void doHighlightingTest(String... filePaths) { + myFixture.testHighlighting(true, false, false, filePaths) + } + + private static void assertLookupElement(LookupElement[] variants, String lookupText, String tailText, String typeText) { + LookupElement lookupElement = variants.find { it.lookupString == lookupText } + assertNotNull(toString(variants, "\n"), lookupElement) + + def presentation = new LookupElementPresentation() + lookupElement.renderElement(presentation) + + assertEquals(lookupText, presentation.itemText) + assertEquals(tailText, presentation.tailText) + assertEquals(typeText, presentation.typeText) + } }