mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-115118 Support for 'order' attribute in extensions in plugin.xml files
This commit is contained in:
@@ -43,12 +43,14 @@ import java.util.*;
|
||||
* @author Alexander Kireyev
|
||||
*/
|
||||
public class LoadingOrder {
|
||||
@NonNls private static final String FIRST_STR = "FIRST";
|
||||
@NonNls private static final String LAST_STR = "LAST";
|
||||
@NonNls private static final String BEFORE_STR = "BEFORE ";
|
||||
@NonNls private static final String BEFORE_STR_OLD = "BEFORE:";
|
||||
@NonNls private static final String AFTER_STR = "AFTER ";
|
||||
@NonNls private static final String AFTER_STR_OLD = "AFTER:";
|
||||
@NonNls public static final String FIRST_STR = "first";
|
||||
@NonNls public static final String LAST_STR = "last";
|
||||
@NonNls public static final String BEFORE_STR = "before ";
|
||||
@NonNls public static final String BEFORE_STR_OLD = "before:";
|
||||
@NonNls public static final String AFTER_STR = "after ";
|
||||
@NonNls public static final String AFTER_STR_OLD = "after:";
|
||||
|
||||
@NonNls public static final String ORDER_RULE_SEPARATOR = ",";
|
||||
|
||||
public static final LoadingOrder ANY = new LoadingOrder();
|
||||
public static final LoadingOrder FIRST = new LoadingOrder(FIRST_STR);
|
||||
@@ -70,7 +72,7 @@ public class LoadingOrder {
|
||||
myName = text;
|
||||
boolean last = false;
|
||||
boolean first = false;
|
||||
for (final String string : StringUtil.split(text, ",")) {
|
||||
for (final String string : StringUtil.split(text, ORDER_RULE_SEPARATOR)) {
|
||||
String trimmed = string.trim();
|
||||
if (trimmed.equalsIgnoreCase(FIRST_STR)) first = true;
|
||||
else if (trimmed.equalsIgnoreCase(LAST_STR)) last = true;
|
||||
|
||||
@@ -1389,11 +1389,23 @@ public class StringUtil extends StringUtilRt {
|
||||
@NotNull
|
||||
@Contract(pure = true)
|
||||
public static List<TextRange> getWordIndicesIn(@NotNull String text) {
|
||||
return getWordIndicesIn(text, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param text text to get word ranges in.
|
||||
* @param separatorsSet if not null, only these characters will be considered as separators (i.e. not a part of word).
|
||||
* Otherwise {@link Character#isJavaIdentifierPart(char)} will be used to determine whether a symbol is part of word.
|
||||
* @return ranges ranges of words in passed text.
|
||||
*/
|
||||
@NotNull
|
||||
@Contract(pure = true)
|
||||
public static List<TextRange> getWordIndicesIn(@NotNull String text, @Nullable Set<Character> separatorsSet) {
|
||||
List<TextRange> result = new SmartList<TextRange>();
|
||||
int start = -1;
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
boolean isIdentifierPart = Character.isJavaIdentifierPart(c);
|
||||
boolean isIdentifierPart = separatorsSet == null ? Character.isJavaIdentifierPart(c) : !separatorsSet.contains(c);
|
||||
if (isIdentifierPart && start == -1) {
|
||||
start = i;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package com.intellij.util.text;
|
||||
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.NaturalComparator;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.LineSeparator;
|
||||
@@ -616,4 +617,16 @@ public class StringUtilTest {
|
||||
assertNull(StringUtil.substringAfterLast("abc", "1"));
|
||||
assertNull(StringUtil.substringAfterLast("", "1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetWordIndicesIn() {
|
||||
assertEquals(ContainerUtil.list(new TextRange(0, 5), new TextRange(6, 12)), StringUtil.getWordIndicesIn("first second"));
|
||||
assertEquals(ContainerUtil.list(new TextRange(1, 6), new TextRange(7, 13)), StringUtil.getWordIndicesIn(" first second"));
|
||||
assertEquals(ContainerUtil.list(new TextRange(1, 6), new TextRange(7, 13)), StringUtil.getWordIndicesIn(" first second "));
|
||||
assertEquals(ContainerUtil.list(new TextRange(0, 5), new TextRange(6, 12)), StringUtil.getWordIndicesIn("first:second"));
|
||||
assertEquals(ContainerUtil.list(new TextRange(0, 5), new TextRange(6, 12)), StringUtil.getWordIndicesIn("first-second"));
|
||||
assertEquals(ContainerUtil.list(new TextRange(0, 12)), StringUtil.getWordIndicesIn("first-second", ContainerUtil.set(' ', '_', '.')));
|
||||
assertEquals(ContainerUtil.list(new TextRange(0, 5), new TextRange(6, 12)),
|
||||
StringUtil.getWordIndicesIn("first-second", ContainerUtil.set('-')));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +62,8 @@
|
||||
<lang.documentationProvider language="XML"
|
||||
implementationClass="org.jetbrains.idea.devkit.references.extensions.ExtensionPointDocumentationProvider"/>
|
||||
|
||||
<completion.contributor implementationClass="org.jetbrains.idea.devkit.completion.ExtensionOrderKeywordCompletionContributor" language="XML" />
|
||||
|
||||
<localInspection language="XML" shortName="PluginXmlValidity" displayName="Plugin.xml Validity" applyToDialects="false"
|
||||
groupKey="inspections.group.name" enabledByDefault="true" level="ERROR"
|
||||
implementationClass="org.jetbrains.idea.devkit.inspections.PluginXmlDomInspection"/>
|
||||
@@ -174,7 +176,7 @@
|
||||
<implicitUsageProvider implementation="org.jetbrains.idea.devkit.inspections.DevKitImplicitUsageProvider"/>
|
||||
<psi.referenceContributor language="XML" implementation="org.jetbrains.idea.devkit.dom.impl.I18nReferenceContributor"/>
|
||||
<psi.referenceContributor implementation="org.jetbrains.idea.devkit.references.IconsReferencesContributor"/>
|
||||
<psi.referenceContributor implementation="org.jetbrains.idea.devkit.references.TestDataFilesReferencesContributor" />
|
||||
<psi.referenceContributor implementation="org.jetbrains.idea.devkit.references.TestDataFilesReferencesContributor"/>
|
||||
<referencesSearch implementation="org.jetbrains.idea.devkit.references.IconsReferencesContributor"/>
|
||||
<unusedDeclarationFixProvider implementation="org.jetbrains.idea.devkit.inspections.quickfix.RegisterExtensionFixProvider"/>
|
||||
|
||||
|
||||
@@ -172,6 +172,8 @@ inspections.component.not.registered.quickfix.error=Cannot Register {0}
|
||||
inspections.component.postfix.template.not.found.description.name=Postfix template Description Checker
|
||||
|
||||
inspections.presentation.cannot.resolve.icon=Cannot resolve icon ''{0}''
|
||||
inspections.plugin.xml.invalid.order.attribute=Invalid 'order' attribute value
|
||||
invalid.order.attribute.part=Invalid ''order'' attribute value part: ''{0}'', must be ''first'', ''last'', ''before <id>'' or ''after <id>''
|
||||
|
||||
ant.build.jar.comment=Build archive for plugin ''{0}''
|
||||
ant.build.jar.description=Build plugin archive for module ''{0}''
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright 2000-2017 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.completion;
|
||||
|
||||
import com.intellij.codeInsight.completion.*;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.codeInsight.lookup.LookupElementBuilder;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.extensions.LoadingOrder;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.patterns.*;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.xml.XmlElement;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.util.ProcessingContext;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.xml.DomManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.idea.devkit.dom.Extension;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.intellij.patterns.PlatformPatterns.psiElement;
|
||||
|
||||
public class ExtensionOrderKeywordCompletionContributor extends CompletionContributor {
|
||||
private static final List<LookupElement> KEYWORD_COMPLETION_VARIANTS = ContainerUtil.list(
|
||||
LookupElementBuilder.create(LoadingOrder.FIRST_STR), LookupElementBuilder.create(LoadingOrder.LAST_STR),
|
||||
LookupElementBuilder.create(LoadingOrder.BEFORE_STR.trim()).withInsertHandler(new AddSpaceInsertHandler(true)),
|
||||
LookupElementBuilder.create(LoadingOrder.AFTER_STR.trim()).withInsertHandler(new AddSpaceInsertHandler(true))
|
||||
);
|
||||
|
||||
public ExtensionOrderKeywordCompletionContributor() {
|
||||
extend(CompletionType.BASIC, getCapture(), new CompletionProvider<CompletionParameters>() {
|
||||
@Override
|
||||
protected void addCompletions(@NotNull CompletionParameters parameters,
|
||||
ProcessingContext context,
|
||||
@NotNull CompletionResultSet result) {
|
||||
String prefix = getCompletionPrefix(parameters);
|
||||
if (shouldProposeKeywordsAfterPrefix(prefix)) {
|
||||
result.addAllElements(KEYWORD_COMPLETION_VARIANTS);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static PsiElementPattern.Capture<PsiElement> getCapture() {
|
||||
//TODO write a method for attribute value in XmlPatterns
|
||||
return psiElement().inside(
|
||||
XmlPatterns.xmlAttributeValue("order").inside(
|
||||
XmlPatterns.xmlTag().with(new PatternCondition<XmlTag>("extension tag") {
|
||||
@Override
|
||||
public boolean accepts(@NotNull XmlTag tag, ProcessingContext context) {
|
||||
Project project = tag.getProject();
|
||||
DomManager domManager = DomManager.getDomManager(project);
|
||||
return domManager.getDomElement(tag) instanceof Extension;
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getCompletionPrefix(@NotNull CompletionParameters parameters) {
|
||||
XmlElement position = (XmlElement)parameters.getPosition();
|
||||
int startOffset = position.getTextOffset();
|
||||
int endOffset = parameters.getOffset();
|
||||
Document document = parameters.getEditor().getDocument();
|
||||
return document.getText(new TextRange(startOffset, endOffset));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String getPrefixLastPart(String prefix) {
|
||||
String lastPart = StringUtil.substringAfterLast(prefix, LoadingOrder.ORDER_RULE_SEPARATOR);
|
||||
if (lastPart == null) {
|
||||
lastPart = prefix;
|
||||
}
|
||||
lastPart = StringUtil.trimLeading(lastPart);
|
||||
return lastPart;
|
||||
}
|
||||
|
||||
private static boolean shouldProposeKeywordsAfterPrefix(String prefix) {
|
||||
return !getPrefixLastPart(prefix).contains(" "); // propose keywords if there's only a single word (or empty prefix)
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillCompletionVariants(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result) {
|
||||
String prefix = result.getPrefixMatcher().getPrefix();
|
||||
if (prefix.endsWith(LoadingOrder.ORDER_RULE_SEPARATOR)) {
|
||||
result = result.withPrefixMatcher(""); // keywords should be proposed after comma even without space
|
||||
}
|
||||
else {
|
||||
result = result.withPrefixMatcher(getPrefixLastPart(prefix));
|
||||
}
|
||||
|
||||
super.fillCompletionVariants(parameters, result);
|
||||
}
|
||||
}
|
||||
@@ -15,21 +15,18 @@
|
||||
*/
|
||||
package org.jetbrains.idea.devkit.dom;
|
||||
|
||||
import com.intellij.util.xml.DomElement;
|
||||
import com.intellij.util.xml.GenericAttributeValue;
|
||||
import com.intellij.util.xml.NameValue;
|
||||
import com.intellij.util.xml.Required;
|
||||
import com.intellij.ide.presentation.Presentation;
|
||||
import com.intellij.util.xml.*;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author mike
|
||||
*/
|
||||
@Presentation(typeName = "Extension")
|
||||
public interface Extension extends DomElement {
|
||||
|
||||
@NameValue
|
||||
@Required(value = false)
|
||||
GenericAttributeValue<String> getId();
|
||||
|
||||
@Referencing(ExtensionOrderConverter.class)
|
||||
@Required(value = false)
|
||||
GenericAttributeValue<String> getOrder();
|
||||
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
// Copyright 2000-2017 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.completion.CompletionUtil;
|
||||
import com.intellij.codeInsight.daemon.EmptyResolveMessageProvider;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.codeInsight.lookup.LookupElementBuilder;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.extensions.LoadingOrder;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.pom.references.PomService;
|
||||
import com.intellij.psi.ElementManipulators;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiReference;
|
||||
import com.intellij.psi.PsiReferenceBase;
|
||||
import com.intellij.psi.util.ReferenceSetBase;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.xml.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.idea.devkit.DevKitBundle;
|
||||
import org.jetbrains.idea.devkit.util.ExtensionCandidate;
|
||||
import org.jetbrains.idea.devkit.util.ExtensionLocator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ExtensionOrderConverter implements CustomReferenceConverter<String> {
|
||||
private static final Logger LOG = Logger.getInstance(ExtensionOrderConverter.class);
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiReference[] createReferences(GenericDomValue<String> value, PsiElement element, ConvertContext context) {
|
||||
PsiElement originalElement = CompletionUtil.getOriginalOrSelf(element); // avoid 'IntellijIdeaRulezzz' placeholder
|
||||
String orderValue = ElementManipulators.getValueText(originalElement);
|
||||
if (StringUtil.isEmpty(orderValue)) {
|
||||
return PsiReference.EMPTY_ARRAY;
|
||||
}
|
||||
Extension extension = value.getParentOfType(Extension.class, false);
|
||||
if (extension == null) {
|
||||
return PsiReference.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
return new ReferenceSetBase<PsiReference>(orderValue, element, 0, ',') {
|
||||
@Override
|
||||
protected List<PsiReference> createReferences(TextRange range, int index) {
|
||||
String orderPart = range.substring(orderValue);
|
||||
|
||||
// reference range and attribute text range are not equal
|
||||
range = new TextRange(range.getStartOffset() + 1, range.getEndOffset() + 1);
|
||||
|
||||
List<String> subParts = splitOrderPart(orderPart);
|
||||
if (subParts.isEmpty()) {
|
||||
// last symbol is ','
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String idSubPart = null; // second one, after keyword subpart
|
||||
if (subParts.size() == 2) {
|
||||
idSubPart = subParts.get(1);
|
||||
}
|
||||
else if (isBeforeOrAfterKeyword(StringUtil.trimLeading(orderPart), false)) {
|
||||
// This order part is, for instance, 'after ' or 'before:'. In such cases IDs reference should be provided.
|
||||
idSubPart = "";
|
||||
}
|
||||
|
||||
if (subParts.size() > 2 || (idSubPart != null && !isBeforeOrAfterKeyword(subParts.get(0)))) {
|
||||
// Order value can't contain more than 3 subparts. If there are two subparts, first one must be 'before' or 'after'.
|
||||
return Collections.singletonList(new InvalidOrderPartPsiReference(getElement(), range, orderPart));
|
||||
}
|
||||
|
||||
List<TextRange> wordIndices = getWordIndicesInOrderPart(orderPart);
|
||||
if (wordIndices.isEmpty()) {
|
||||
LOG.error("Unexpected empty word indices list for 'order' part: " + orderPart);
|
||||
return Collections.singletonList(new InvalidOrderPartPsiReference(getElement(), range, orderPart));
|
||||
}
|
||||
if (idSubPart != null && idSubPart.isEmpty()) { // right after the before/after keyword
|
||||
wordIndices.add(new TextRange(orderPart.length(), orderPart.length()));
|
||||
}
|
||||
|
||||
if (idSubPart == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
assert wordIndices.size() == 2 : wordIndices.toString();
|
||||
TextRange idSubPartRange = wordIndices.get(1).shiftRight(range.getStartOffset());
|
||||
return ContainerUtil.list(new OrderReferencedIdPsiReference(getElement(), idSubPartRange, idSubPart, extension));
|
||||
}
|
||||
}.getPsiReferences();
|
||||
}
|
||||
|
||||
private static List<String> splitOrderPart(String orderPart) {
|
||||
List<String> result = new ArrayList<>();
|
||||
List<String> subParts = StringUtil.split(orderPart, " ");
|
||||
subParts.forEach(s -> {
|
||||
if (":".equals(s)) {
|
||||
result.add(s);
|
||||
}
|
||||
else {
|
||||
Collections.addAll(result, s.split(":"));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<TextRange> getWordIndicesInOrderPart(String orderPart) {
|
||||
return StringUtil.getWordIndicesIn(orderPart, ContainerUtil.set(' ', ':'));
|
||||
}
|
||||
|
||||
private static boolean isBeforeOrAfterKeyword(String str) {
|
||||
return isBeforeOrAfterKeyword(str, true);
|
||||
}
|
||||
|
||||
private static boolean isBeforeOrAfterKeyword(String str, boolean trimKeyword) {
|
||||
return (trimKeyword ? LoadingOrder.BEFORE_STR.trim() : LoadingOrder.BEFORE_STR).equalsIgnoreCase(str) ||
|
||||
(trimKeyword ? LoadingOrder.AFTER_STR.trim(): LoadingOrder.AFTER_STR).equalsIgnoreCase(str) ||
|
||||
LoadingOrder.BEFORE_STR_OLD.equalsIgnoreCase(str) ||
|
||||
LoadingOrder.BEFORE_STR_OLD.equalsIgnoreCase(str);
|
||||
}
|
||||
|
||||
|
||||
private static class InvalidOrderPartPsiReference extends PsiReferenceBase<PsiElement> implements EmptyResolveMessageProvider {
|
||||
private final String myOrderPart;
|
||||
|
||||
public InvalidOrderPartPsiReference(@NotNull PsiElement element, @NotNull TextRange rangeInElement, String orderPart) {
|
||||
super(element, rangeInElement);
|
||||
myOrderPart = orderPart;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PsiElement resolve() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Object[] getVariants() {
|
||||
return ArrayUtil.EMPTY_OBJECT_ARRAY;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getUnresolvedMessagePattern() {
|
||||
return DevKitBundle.message("invalid.order.attribute.part", myOrderPart.trim());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSoft() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static class OrderReferencedIdPsiReference extends PsiReferenceBase<PsiElement> implements EmptyResolveMessageProvider {
|
||||
private final String myReferencedId;
|
||||
private final Extension myExtension;
|
||||
|
||||
public OrderReferencedIdPsiReference(@NotNull PsiElement element, @NotNull TextRange rangeInElement,
|
||||
@NotNull String referencedId, @NotNull Extension extension) {
|
||||
super(element, rangeInElement);
|
||||
myReferencedId = referencedId;
|
||||
myExtension = extension;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public PsiElement resolve() {
|
||||
ExtensionPoint extensionPoint = myExtension.getExtensionPoint();
|
||||
if (extensionPoint == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ExtensionLocator epAndIdLocator = ExtensionLocator.byExtensionPointAndId(extensionPoint, myReferencedId);
|
||||
List<ExtensionCandidate> candidates = epAndIdLocator.findCandidates();
|
||||
if (candidates.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
XmlTag referencedElement = candidates.iterator().next().pointer.getElement();
|
||||
if (referencedElement == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// return DOM target PSI for "Find Usages" to work
|
||||
DomManager domManager = DomManager.getDomManager(referencedElement.getProject());
|
||||
DomElement domElement = domManager.getDomElement(referencedElement);
|
||||
if (domElement == null) {
|
||||
return referencedElement; // fallback
|
||||
}
|
||||
DomTarget target = DomTarget.getTarget(domElement);
|
||||
if (target == null) {
|
||||
return referencedElement; // fallback
|
||||
}
|
||||
return PomService.convertToPsi(target);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Object[] getVariants() {
|
||||
ExtensionPoint extensionPoint = myExtension.getExtensionPoint();
|
||||
if (extensionPoint == null) {
|
||||
return ArrayUtil.EMPTY_OBJECT_ARRAY;
|
||||
}
|
||||
|
||||
ExtensionLocator epLocator = ExtensionLocator.byExtensionPoint(extensionPoint);
|
||||
List<ExtensionCandidate> candidates = epLocator.findCandidates();
|
||||
DomManager domManager = DomManager.getDomManager(getElement().getProject());
|
||||
|
||||
List<Extension> extensionsForThisEp = new ArrayList<>();
|
||||
for (ExtensionCandidate candidate : candidates) {
|
||||
XmlTag tag = candidate.pointer.getElement();
|
||||
DomElement domElement = domManager.getDomElement(tag);
|
||||
if (domElement instanceof Extension) {
|
||||
extensionsForThisEp.add((Extension)domElement);
|
||||
}
|
||||
}
|
||||
|
||||
String currentExtensionId = myExtension.getId().getStringValue();
|
||||
List<LookupElement> idCompletionVariantsList = new ArrayList<>();
|
||||
for (Extension e : extensionsForThisEp) {
|
||||
if (Objects.equals(currentExtensionId, e.getId().getStringValue())) {
|
||||
continue; // do not suggest the same extension id
|
||||
}
|
||||
String id = e.getId().getStringValue();
|
||||
if (StringUtil.isNotEmpty(id)) {
|
||||
DomTarget extensionTarget = DomTarget.getTarget(e);
|
||||
if (extensionTarget != null) {
|
||||
PsiElement extensionPsi = PomService.convertToPsi(extensionTarget);
|
||||
idCompletionVariantsList.add(LookupElementBuilder.create(extensionPsi, id));
|
||||
}
|
||||
else {
|
||||
// shouldn't happen, fallback for additional safety
|
||||
idCompletionVariantsList.add(LookupElementBuilder.create(e.getXmlTag(), id));
|
||||
}
|
||||
}
|
||||
}
|
||||
return idCompletionVariantsList.toArray(new LookupElement[idCompletionVariantsList.size()]);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getUnresolvedMessagePattern() {
|
||||
ExtensionPoint ep = myExtension.getExtensionPoint();
|
||||
return "Cannot resolve ''{0}'' " + (ep != null ? ep.getEffectiveName() + " " : "") + "extension";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSoft() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,9 @@ import com.intellij.diagnostic.ITNReporter;
|
||||
import com.intellij.ide.plugins.IdeaPluginDescriptorImpl;
|
||||
import com.intellij.ide.plugins.PluginManagerCore;
|
||||
import com.intellij.ide.plugins.PluginManagerMain;
|
||||
import com.intellij.lang.annotation.HighlightSeverity;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.extensions.LoadingOrder;
|
||||
import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.roots.ModuleRootManager;
|
||||
@@ -269,6 +271,9 @@ public class PluginXmlDomInspection extends BasicDomElementsInspection<IdeaPlugi
|
||||
if ("icon".equals(attributeDescription.getXmlElementName())) {
|
||||
annotateResolveProblems(holder, attributeValue);
|
||||
}
|
||||
else if ("order".equals(attributeDescription.getXmlElementName())) {
|
||||
annotateOrderAttributeProblems(holder, attributeValue);
|
||||
}
|
||||
|
||||
final PsiElement declaration = attributeDescription.getDeclaration(extension.getManager().getProject());
|
||||
if (declaration instanceof PsiField) {
|
||||
@@ -336,6 +341,22 @@ public class PluginXmlDomInspection extends BasicDomElementsInspection<IdeaPlugi
|
||||
}
|
||||
}
|
||||
|
||||
private static void annotateOrderAttributeProblems(DomElementAnnotationHolder holder, GenericAttributeValue attributeValue) {
|
||||
String orderValue = attributeValue.getStringValue();
|
||||
if (StringUtil.isEmpty(orderValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
LoadingOrder.readOrder(orderValue);
|
||||
} catch (AssertionError ignore) {
|
||||
holder.createProblem(attributeValue, HighlightSeverity.ERROR, DevKitBundle.message("inspections.plugin.xml.invalid.order.attribute"));
|
||||
return; // no need to resolve references for invalid attribute value
|
||||
}
|
||||
|
||||
annotateResolveProblems(holder, attributeValue);
|
||||
}
|
||||
|
||||
private static void annotateResolveProblems(DomElementAnnotationHolder holder, GenericAttributeValue attributeValue) {
|
||||
final XmlAttributeValue value = attributeValue.getXmlAttributeValue();
|
||||
if (value != null) {
|
||||
|
||||
@@ -44,7 +44,7 @@ public class StatefulEpInspection extends DevKitUastInspectionBase {
|
||||
if (fields.length == 0) return super.checkClass(psiClass, manager, isOnTheFly);
|
||||
|
||||
final boolean isQuickFix = InheritanceUtil.isInheritor(psiClass, LocalQuickFix.class.getCanonicalName());
|
||||
ExtensionLocator locator = new ExtensionLocator(psiClass);
|
||||
ExtensionLocator locator = ExtensionLocator.byPsiClass(psiClass);
|
||||
List<ExtensionCandidate> targets = locator.findCandidates();
|
||||
if (isQuickFix || !targets.isEmpty()) {
|
||||
boolean isProjectComponent = InheritanceUtil.isInheritor(psiClass, ProjectComponent.class.getCanonicalName());
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ public class ExtensionDeclarationRelatedItemLineMarkerProvider extends DevkitRel
|
||||
return;
|
||||
}
|
||||
|
||||
ExtensionLocator locator = new ExtensionLocator(psiClass);
|
||||
ExtensionLocator locator = ExtensionLocator.byPsiClass(psiClass);
|
||||
List<ExtensionCandidate> targets = locator.findCandidates();
|
||||
if (targets.isEmpty()) {
|
||||
return;
|
||||
|
||||
@@ -53,7 +53,6 @@ import com.intellij.util.QueryExecutor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.idea.devkit.DevKitBundle;
|
||||
import org.jetbrains.idea.devkit.util.DescriptorUtil;
|
||||
import org.jetbrains.idea.devkit.util.PsiUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -122,8 +121,7 @@ public class IconsReferencesContributor extends PsiReferenceContributor
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiReference[] getReferencesByElement(@NotNull final PsiElement element, @NotNull ProcessingContext context) {
|
||||
if (!PsiUtil.isPluginProject(element.getProject()) ||
|
||||
!DescriptorUtil.isPluginXml(element.getContainingFile())) {
|
||||
if (!PsiUtil.isPluginXmlPsiElement(element)) {
|
||||
return PsiReference.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ public final class DescriptorUtil {
|
||||
return ideaPlugin.getRootElement().getPluginId();
|
||||
}
|
||||
|
||||
public static boolean isPluginXml(PsiFile file) {
|
||||
public static boolean isPluginXml(@Nullable PsiFile file) {
|
||||
if (!(file instanceof XmlFile)) return false;
|
||||
return getIdeaPlugin((XmlFile)file) != null;
|
||||
}
|
||||
|
||||
@@ -27,83 +27,135 @@ import com.intellij.psi.search.PsiSearchHelper;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.xml.XmlTag;
|
||||
import com.intellij.psi.xml.XmlToken;
|
||||
import com.intellij.util.CommonProcessors;
|
||||
import com.intellij.util.Processor;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.xml.DomElement;
|
||||
import com.intellij.util.xml.DomManager;
|
||||
import com.intellij.util.xml.DomUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.idea.devkit.dom.Extension;
|
||||
import org.jetbrains.idea.devkit.dom.ExtensionPoint;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ExtensionLocator {
|
||||
private final PsiClass myPsiClass;
|
||||
|
||||
public ExtensionLocator(PsiClass aClass) {
|
||||
myPsiClass = aClass;
|
||||
}
|
||||
|
||||
public abstract class ExtensionLocator {
|
||||
@NotNull
|
||||
public List<ExtensionCandidate> findCandidates() {
|
||||
String name = myPsiClass.getQualifiedName();
|
||||
if (name == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
public abstract List<ExtensionCandidate> findCandidates();
|
||||
|
||||
List<ExtensionCandidate> result = new SmartList<>();
|
||||
processExtensionDeclarations(myPsiClass, new ReferenceProcessor(name, tag -> {
|
||||
result.add(new ExtensionCandidate(SmartPointerManager.getInstance(tag.getProject()).createSmartPsiElementPointer(tag)));
|
||||
return true; // continue processing
|
||||
}));
|
||||
return result;
|
||||
public static ExtensionLocator byPsiClass(PsiClass psiClass) {
|
||||
return new ExtensionByPsiClassLocator(psiClass);
|
||||
}
|
||||
|
||||
public static boolean isRegisteredExtension(@NotNull PsiClass psiClass) {
|
||||
String name = psiClass.getQualifiedName();
|
||||
if (name == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CommonProcessors.FindFirstProcessor<XmlTag> processor = new CommonProcessors.FindFirstProcessor<>();
|
||||
processExtensionDeclarations(psiClass, new ReferenceProcessor(name, processor));
|
||||
return processor.isFound();
|
||||
public static ExtensionLocator byExtensionPoint(@NotNull ExtensionPoint extensionPoint) {
|
||||
return new ExtensionByExtensionPointLocator(extensionPoint, null);
|
||||
}
|
||||
|
||||
private static void processExtensionDeclarations(PsiClass psiClass, PsiNonJavaFileReferenceProcessor referenceProcessor) {
|
||||
String name = psiClass.getQualifiedName();
|
||||
public static ExtensionLocator byExtensionPointAndId(@NotNull ExtensionPoint extensionPoint, @NotNull String extensionId) {
|
||||
return new ExtensionByExtensionPointLocator(extensionPoint, extensionId);
|
||||
}
|
||||
|
||||
|
||||
private static class ExtensionByPsiClassLocator extends ExtensionLocator {
|
||||
private final PsiClass myPsiClass;
|
||||
|
||||
ExtensionByPsiClassLocator(PsiClass psiClass) {
|
||||
myPsiClass = psiClass;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<ExtensionCandidate> findCandidates() {
|
||||
String name = myPsiClass.getQualifiedName();
|
||||
if (name == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<ExtensionCandidate> result = new SmartList<>();
|
||||
processExtensionDeclarations(myPsiClass.getQualifiedName(), myPsiClass.getProject(), (file, startOffset, endOffset) -> {
|
||||
XmlTag tag = getXmlTagOfTokenElement(file, startOffset, name, true);
|
||||
DomElement dom = DomUtil.getDomElement(tag);
|
||||
if (dom instanceof Extension && ((Extension)dom).getExtensionPoint() != null) {
|
||||
result.add(new ExtensionCandidate(SmartPointerManager.getInstance(tag.getProject()).createSmartPsiElementPointer(tag)));
|
||||
}
|
||||
return true; // continue processing
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ExtensionByExtensionPointLocator extends ExtensionLocator {
|
||||
private final ExtensionPoint myExtensionPoint;
|
||||
private final String myExtensionId;
|
||||
|
||||
private ExtensionByExtensionPointLocator(@NotNull ExtensionPoint extensionPoint, @Nullable String extensionId) {
|
||||
myExtensionPoint = extensionPoint;
|
||||
myExtensionId = extensionId;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<ExtensionCandidate> findCandidates() {
|
||||
XmlTag epTag = myExtensionPoint.getXmlTag();
|
||||
if (epTag == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
Project project = epTag.getProject();
|
||||
DomManager domManager = DomManager.getDomManager(project);
|
||||
String epName = myExtensionPoint.getEffectiveName();
|
||||
|
||||
List<ExtensionCandidate> result = new SmartList<>();
|
||||
processExtensionDeclarations(myExtensionPoint.getEffectiveName(), project, (file, startOffset, endOffset) -> {
|
||||
XmlTag tag = getXmlTagOfTokenElement(file, startOffset, epName, false);
|
||||
if (tag == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
DomElement domElement = domManager.getDomElement(tag);
|
||||
if (!(domElement instanceof Extension)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Extension extension = (Extension)domElement;
|
||||
ExtensionPoint ep = extension.getExtensionPoint();
|
||||
if (ep == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (StringUtil.equals(ep.getEffectiveQualifiedName(), myExtensionPoint.getEffectiveQualifiedName())
|
||||
&& (myExtensionId == null || myExtensionId.equals(extension.getId().getStringValue()))) {
|
||||
result.add(new ExtensionCandidate(SmartPointerManager.getInstance(tag.getProject()).createSmartPsiElementPointer(tag)));
|
||||
return myExtensionId == null; // stop after the first found candidate if ID is specified
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void processExtensionDeclarations(String name, Project project, PsiNonJavaFileReferenceProcessor referenceProcessor) {
|
||||
if (name == null) return;
|
||||
|
||||
Project project = psiClass.getProject();
|
||||
GlobalSearchScope scope = PluginRelatedLocatorsUtils.getCandidatesScope(project);
|
||||
|
||||
PsiSearchHelper.SERVICE.getInstance(project).processUsagesInNonJavaFiles(name, referenceProcessor, scope);
|
||||
}
|
||||
|
||||
private static class ReferenceProcessor implements PsiNonJavaFileReferenceProcessor {
|
||||
private final String myExtensionClassName;
|
||||
private final Processor<XmlTag> myExtensionTagHandler;
|
||||
|
||||
private ReferenceProcessor(String name, Processor<XmlTag> extensionTagHandler) {
|
||||
myExtensionClassName = name;
|
||||
myExtensionTagHandler = extensionTagHandler;
|
||||
@Nullable
|
||||
private static XmlTag getXmlTagOfTokenElement(PsiFile file, int startOffset, String matchStr, boolean strictMatch) {
|
||||
PsiElement element = file.findElementAt(startOffset);
|
||||
String tokenText = element instanceof XmlToken ? element.getText() : null;
|
||||
if (tokenText == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(PsiFile file, int startOffset, int endOffset) {
|
||||
PsiElement element = file.findElementAt(startOffset);
|
||||
String tokenText = element instanceof XmlToken ? element.getText() : null;
|
||||
if (!StringUtil.equals(myExtensionClassName, tokenText)) return true;
|
||||
|
||||
XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class);
|
||||
if (tag == null) return true;
|
||||
|
||||
DomElement dom = DomUtil.getDomElement(tag);
|
||||
if (dom instanceof Extension && ((Extension)dom).getExtensionPoint() != null) {
|
||||
return myExtensionTagHandler.process(tag);
|
||||
}
|
||||
return true;
|
||||
if (!strictMatch && !StringUtil.contains(tokenText, matchStr)) {
|
||||
return null;
|
||||
}
|
||||
if (strictMatch && !StringUtil.equals(tokenText, matchStr)) {
|
||||
return null;
|
||||
}
|
||||
return PsiTreeUtil.getParentOfType(element, XmlTag.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,4 +194,8 @@ public class PsiUtil {
|
||||
public static <E extends PsiElement> SmartPsiElementPointer<E> createPointer(@NotNull E e) {
|
||||
return SmartPointerManager.getInstance(e.getProject()).createSmartPsiElementPointer(e);
|
||||
}
|
||||
|
||||
public static boolean isPluginXmlPsiElement(@NotNull PsiElement element) {
|
||||
return isPluginProject(element.getProject()) && DescriptorUtil.isPluginXml(element.getContainingFile());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<idea-plugin>
|
||||
<id>com.intellij</id>
|
||||
|
||||
<extensionPoints>
|
||||
<!--suppress PluginXmlValidity -->
|
||||
<extensionPoint name="myEp2"
|
||||
beanClass="java.lang.String"/>
|
||||
</extensionPoints>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<myEp2 id="id1" order="b<caret>"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
@@ -0,0 +1,13 @@
|
||||
<idea-plugin>
|
||||
<id>com.intellij</id>
|
||||
|
||||
<extensionPoints>
|
||||
<!--suppress PluginXmlValidity -->
|
||||
<extensionPoint name="myEp2"
|
||||
beanClass="java.lang.String"/>
|
||||
</extensionPoints>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<myEp2 id="id1" order="before "/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
@@ -0,0 +1,14 @@
|
||||
<idea-plugin>
|
||||
<id>com.intellij</id>
|
||||
|
||||
<extensionPoints>
|
||||
<!--suppress PluginXmlValidity -->
|
||||
<extensionPoint name="myEp6"
|
||||
beanClass="java.lang.String"/>
|
||||
</extensionPoints>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<myEp6 id="id1"/>
|
||||
<myEp6 id="id2" order="before id1, fi<caret>"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<idea-plugin>
|
||||
<id>com.intellij</id>
|
||||
|
||||
<extensionPoints>
|
||||
<!--suppress PluginXmlValidity -->
|
||||
<extensionPoint name="myEp6"
|
||||
beanClass="java.lang.String"/>
|
||||
</extensionPoints>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<myEp6 id="id1"/>
|
||||
<myEp6 id="id2" order="before id1, first"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
@@ -0,0 +1,16 @@
|
||||
<idea-plugin>
|
||||
<id>com.intellij</id>
|
||||
|
||||
<extensionPoints>
|
||||
<!--suppress PluginXmlValidity -->
|
||||
<extensionPoint name="myEp4"
|
||||
beanClass="java.lang.String"/>
|
||||
</extensionPoints>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<myEp4 id="id1"/>
|
||||
<myEp4 id="id2"/>
|
||||
<myEp4 id="id3"/>
|
||||
<myEp4 id="id4" order="before <caret>"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
@@ -0,0 +1,16 @@
|
||||
<idea-plugin>
|
||||
<id>com.intellij</id>
|
||||
|
||||
<extensionPoints>
|
||||
<!--suppress PluginXmlValidity -->
|
||||
<extensionPoint name="myEp5"
|
||||
beanClass="java.lang.String"/>
|
||||
</extensionPoints>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<myEp5 id="id1"/>
|
||||
<myEp5 id="id2"/>
|
||||
<myEp5 id="id3"/>
|
||||
<myEp5 id="id4" order="first, after <caret>"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
@@ -0,0 +1,13 @@
|
||||
<idea-plugin>
|
||||
<id>com.intellij</id>
|
||||
|
||||
<extensionPoints>
|
||||
<!--suppress PluginXmlValidity -->
|
||||
<extensionPoint name="myEp3"
|
||||
beanClass="java.lang.String"/>
|
||||
</extensionPoints>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<myEp3 id="id1" order="first, <caret>"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
@@ -0,0 +1,13 @@
|
||||
<idea-plugin>
|
||||
<id>com.intellij</id>
|
||||
|
||||
<extensionPoints>
|
||||
<!--suppress PluginXmlValidity -->
|
||||
<extensionPoint name="myEp1"
|
||||
beanClass="java.lang.String"/>
|
||||
</extensionPoints>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<myEp1 id="id1" order="<caret>"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
@@ -0,0 +1,18 @@
|
||||
<idea-plugin>
|
||||
<id>com.intellij</id>
|
||||
|
||||
<extensionPoints>
|
||||
<!--suppress PluginXmlValidity -->
|
||||
<extensionPoint name="myEp"
|
||||
beanClass="java.lang.String"/>
|
||||
</extensionPoints>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<myEp id="id1" order="first"/>
|
||||
<myEp id="id2" order="last"/>
|
||||
<myEp id="id3" order="after id1, before <error descr="Cannot resolve 'wrongId' myEp extension">wrongId</error>"/>
|
||||
<myEp id="id4" order="before id2, after id1"/>
|
||||
<myEp id="id5" order="before id2, after <error descr="Cannot resolve 'wrongId' myEp extension">wrongId</error>"/>
|
||||
<myEp id="id6" order="before id6, before id5, after id1, before <error descr="Cannot resolve 'wrongId' myEp extension">wrongId</error>"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
@@ -23,6 +23,7 @@ import com.intellij.codeInspection.xml.DeprecatedClassUsageInspection
|
||||
import com.intellij.diagnostic.ITNReporter
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.PluginPathManager
|
||||
import com.intellij.openapi.extensions.LoadingOrder
|
||||
import com.intellij.psi.ElementDescriptionUtil
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.testFramework.PsiTestUtil
|
||||
@@ -361,6 +362,40 @@ class PluginXmlFunctionalTest extends JavaCodeInsightFixtureTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
void testOrderAttributeHighlighting() {
|
||||
myFixture.testHighlighting("orderAttributeHighlighting.xml")
|
||||
}
|
||||
|
||||
// separate tests for 'order' attribute completion because cannot test all cases with completeBasicAllCarets
|
||||
|
||||
void testOrderAttributeCompletionKeywordsInEmptyValue() {
|
||||
myFixture.testCompletionVariants(getTestName(true) + ".xml",
|
||||
LoadingOrder.FIRST_STR, LoadingOrder.LAST_STR,
|
||||
LoadingOrder.BEFORE_STR.trim(), LoadingOrder.AFTER_STR.trim())
|
||||
}
|
||||
|
||||
void testOrderAttributeCompletionBeforeKeyword() {
|
||||
myFixture.testCompletion(getTestName(true) + ".xml", getTestName(true) + "_after.xml")
|
||||
}
|
||||
|
||||
void testOrderAttributeCompletionKeywords() {
|
||||
myFixture.testCompletionVariants(getTestName(true) + ".xml",
|
||||
LoadingOrder.FIRST_STR, LoadingOrder.LAST_STR,
|
||||
LoadingOrder.BEFORE_STR.trim(), LoadingOrder.AFTER_STR.trim())
|
||||
}
|
||||
|
||||
void testOrderAttributeCompletionIds() {
|
||||
myFixture.testCompletionVariants(getTestName(true) + ".xml", "id1", "id2", "id3")
|
||||
}
|
||||
|
||||
void testOrderAttributeCompletionIdsWithFirst() {
|
||||
myFixture.testCompletionVariants(getTestName(true) + ".xml", "id1", "id2", "id3")
|
||||
}
|
||||
|
||||
void testOrderAttributeCompletionFirstKeywordWithId() {
|
||||
myFixture.testCompletion(getTestName(true) + ".xml", getTestName(true) + "_after.xml")
|
||||
}
|
||||
|
||||
private void testHighlightingInIdeaProject(String path) {
|
||||
myFixture.enableInspections(PluginXmlDomInspection.class)
|
||||
PsiUtil.markAsIdeaProject(project, true)
|
||||
|
||||
Reference in New Issue
Block a user