diff --git a/nop-dev-tools/nop-idea-plugin/.gitignore b/nop-dev-tools/nop-idea-plugin/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..138c50ca664dcdb06ea30b71b4e25e7be6feb7b5 --- /dev/null +++ b/nop-dev-tools/nop-idea-plugin/.gitignore @@ -0,0 +1 @@ +.intellijPlatform/ diff --git a/nop-dev-tools/nop-idea-plugin/build.gradle.kts b/nop-dev-tools/nop-idea-plugin/build.gradle.kts index 8853aac6c2ad0d6827ac3ad6383e168e0253305b..b79326be884a1bff920a52e84f44e5d50b683958 100644 --- a/nop-dev-tools/nop-idea-plugin/build.gradle.kts +++ b/nop-dev-tools/nop-idea-plugin/build.gradle.kts @@ -1,7 +1,10 @@ plugins { id("java") id("org.jetbrains.kotlin.jvm") version "1.9.25" + id("org.jetbrains.intellij") version "1.17.4" + // for idea 2025+ +// id("org.jetbrains.intellij.platform") version "2.10.0" } @@ -13,6 +16,11 @@ version = "1.0-SNAPSHOT" repositories { mavenLocal() mavenCentral() + + // for idea 2025+ +// intellijPlatform { +// defaultRepositories() +// } } // Configure Gradle IntelliJ Plugin @@ -23,10 +31,16 @@ intellij { type.set("IC") // Target IDE Platform plugins.set(listOf("java", "gradle", "org.jetbrains.plugins.yaml")) - } dependencies { + // for idea 2025+ +// intellijPlatform { +// intellijIdeaCommunity("2025.2.2") +// +// bundledPlugins("com.intellij.java", "com.intellij.gradle", "org.jetbrains.plugins.yaml") +// } + // ANTLR 适配器:https://github.com/antlr/antlr4-intellij-adaptor implementation("org.antlr:antlr4-intellij-adaptor:0.1") diff --git a/nop-dev-tools/nop-idea-plugin/gradle/wrapper/gradle-wrapper.properties b/nop-dev-tools/nop-idea-plugin/gradle/wrapper/gradle-wrapper.properties index f8bedccfbf1801a9ebd6a01374497f100a798515..342a59647c19a00d213fbbc3f2c7016a6196ad67 100644 --- a/nop-dev-tools/nop-idea-plugin/gradle/wrapper/gradle-wrapper.properties +++ b/nop-dev-tools/nop-idea-plugin/gradle/wrapper/gradle-wrapper.properties @@ -2,5 +2,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists #distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.10.2-bin.zip +# for idea 2025+ +#distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.13-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/annotator/XLangAnnotator.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/annotator/XLangAnnotator.java index bc86d2d0dc6e60248fda74e8e5ec317f6b989e62..61099e22cd349a98a507c79b3ed6758af2e44a8f 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/annotator/XLangAnnotator.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/annotator/XLangAnnotator.java @@ -7,6 +7,8 @@ */ package io.nop.idea.plugin.annotator; +import java.util.Objects; + import com.intellij.codeInsight.daemon.impl.HighlightRangeExtension; import com.intellij.codeInspection.ProblemHighlightType; import com.intellij.lang.annotation.AnnotationHolder; @@ -28,6 +30,7 @@ import io.nop.core.exceptions.ErrorMessageManager; import io.nop.idea.plugin.lang.psi.XLangAttribute; import io.nop.idea.plugin.lang.psi.XLangAttributeValue; import io.nop.idea.plugin.lang.psi.XLangTag; +import io.nop.idea.plugin.lang.psi.XLangTagMeta; import io.nop.idea.plugin.lang.psi.XLangText; import io.nop.idea.plugin.lang.psi.XLangTextToken; import io.nop.idea.plugin.lang.reference.XLangReference; @@ -130,37 +133,36 @@ public class XLangAnnotator implements Annotator { } private void checkTag(@NotNull AnnotationHolder holder, @NotNull XLangTag tag) { - if (tag.getSchemaDefNode() != null) { - checkTagValue(holder, tag); - return; - } - - XLangTag parentTag = tag.getParentTag(); - if ((parentTag != null && parentTag.isXdefValueSupportBody()) // - || tag.isAllowedUnknownTag() // - ) { + XLangTagMeta tagMeta = tag.getTagMeta(); + if (!tagMeta.hasError()) { + checkTagBySchemaDefNode(holder, tag); return; } - XmlToken startTagName = XmlTagUtil.getStartTagNameElement(tag); - if (startTagName != null) { - errorAnnotation(holder, - startTagName.getTextRange(), - "xlang.annotation.tag.not-defined", - startTagName.getText()); - } + tagErrorAnnotation(holder, tag, tagMeta.getErrorMsg()); + } - XmlToken endTagName = XmlTagUtil.getEndTagNameElement(tag); - if (endTagName != null) { - errorAnnotation(holder, - endTagName.getTextRange(), - "xlang.annotation.tag.not-defined", - endTagName.getText()); + private void checkTagBySchemaDefNode(@NotNull AnnotationHolder holder, @NotNull XLangTag tag) { + XLangTagMeta tagMeta = tag.getTagMeta(); + // 检查标签可重复性 + if (!tagMeta.canBeMultipleTag() && tag.getParentTag() != null) { + for (PsiElement child : tag.getParentTag().getChildren()) { + // 检查在其之前的重复节点 + if (child == tag) { + break; + } + + if (child instanceof XLangTag t // + && Objects.equals(t.getName(), tag.getName()) // + ) { + tagErrorAnnotation(holder, tag, "xlang.annotation.tag.multiple-tag-not-allowed", tag.getName()); + return; + } + } } - } - private void checkTagValue(@NotNull AnnotationHolder holder, @NotNull XLangTag tag) { - XDefTypeDecl xdefValue = tag.getSchemaDefNodeXdefValue(); + // 检查节点内容 + XDefTypeDecl xdefValue = tagMeta.getXdefValue(); TextRange textRange = tag.getValue().getTextRange(); String bodyText = tag.hasChildTag() ? null : tag.getBodyText(); boolean blankBodyText = StringHelper.isBlank(bodyText); @@ -172,16 +174,8 @@ public class XLangAnnotator implements Annotator { return; } - if (!tag.isAllowedChildTag() && tag.hasChildTag()) { - errorAnnotation(holder, textRange, "xlang.annotation.tag.child-not-allowed", tag.getName()); - return; - } - if (xdefValue.isMandatory() && blankBodyText) { - errorAnnotation(holder, - getStartTagName(tag).getTextRange(), - "xlang.annotation.tag.body-required", - tag.getName()); + tagErrorAnnotation(holder, tag, "xlang.annotation.tag.body-required", tag.getName()); return; } @@ -189,7 +183,7 @@ public class XLangAnnotator implements Annotator { SourceLocation loc = XmlPsiHelper.getValueLocation(tag); String stdDomain = xdefValue.getStdDomain(); - if (tag.isXlibSourceNode()) { + if (tagMeta.isXlibSourceNode()) { stdDomain = "xpl"; } @@ -206,18 +200,22 @@ public class XLangAnnotator implements Annotator { return; } - if (attr.getDefAttr() == null) { + IXDefAttribute defAttr = attr.getDefAttr(); + if (defAttr == null) { errorAnnotation(holder, attrNameElement.getTextRange(), "xlang.annotation.attr.not-defined", attr.getName()); + } // + else if (defAttr instanceof XLangAttribute.XDefAttributeWithError error) { + _errorAnnotation(holder, attrNameElement.getTextRange(), error.getErrorMsg()); } } private void checkAttrValue(@NotNull AnnotationHolder holder, @NotNull XLangAttributeValue attrValue) { XLangAttribute attr = attrValue.getParentAttr(); IXDefAttribute defAttr = attr != null ? attr.getDefAttr() : null; - if (defAttr == null) { + if (XLangAttribute.isNullOrErrorDefAttr(defAttr)) { return; } @@ -259,10 +257,21 @@ public class XLangAnnotator implements Annotator { } } - private XmlElement getStartTagName(XmlTag tag) { - XmlElement element = XmlTagUtil.getStartTagNameElement(tag); + private void tagErrorAnnotation(AnnotationHolder holder, XmlTag tag, String msgKey, Object... msgParams) { + String msg = NopPluginBundle.message(msgKey, msgParams); + + tagErrorAnnotation(holder, tag, msg); + } - return element != null ? element : tag; + private void tagErrorAnnotation(AnnotationHolder holder, XmlTag tag, String msg) { + XmlToken[] tokens = new XmlToken[] { + XmlTagUtil.getStartTagNameElement(tag), XmlTagUtil.getEndTagNameElement(tag) + }; + for (XmlToken token : tokens) { + if (token != null) { + _errorAnnotation(holder, token.getTextRange(), msg); + } + } } private void errorAnnotation(AnnotationHolder holder, TextRange textRange, String msgKey, Object... msgParams) { diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/completion/XLangCompletionContributor.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/completion/XLangCompletionContributor.java index 1a90680c632e2d2007f550d1b22629decae40412..74481a00439af3fbcdf102b8f50b194e4a2f542f 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/completion/XLangCompletionContributor.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/completion/XLangCompletionContributor.java @@ -37,6 +37,7 @@ import io.nop.core.dict.DictProvider; import io.nop.idea.plugin.lang.psi.XLangAttribute; import io.nop.idea.plugin.lang.psi.XLangAttributeValue; import io.nop.idea.plugin.lang.psi.XLangTag; +import io.nop.idea.plugin.lang.psi.XLangTagMeta; import io.nop.idea.plugin.resource.ProjectEnv; import io.nop.idea.plugin.utils.XmlPsiHelper; import io.nop.xlang.xdef.IXDefAttribute; @@ -95,8 +96,7 @@ public class XLangCompletionContributor extends CompletionContributor implements // 情况比较简单,因此没有使用extend来注册CompletionProvider,而是直接实现此方法 @Override public void fillCompletionVariants( - @NotNull final CompletionParameters parameters, @NotNull CompletionResultSet result - ) { + @NotNull final CompletionParameters parameters, @NotNull CompletionResultSet result) { PsiElement element = parameters.getPosition().getParent(); ASTNode node = element.getNode(); if (node == null) { @@ -120,13 +120,14 @@ public class XLangCompletionContributor extends CompletionContributor implements if (elType == XmlElementType.XML_TAG) { XLangTag tag = (XLangTag) element; - XLangTag parentTag = tag.getParentTag(); - IXDefNode parentTagDefNode = parentTag != null ? parentTag.getSchemaDefNode() : null; if (parentTag == null) { return; } + XLangTagMeta parentTagMeta = parentTag.getTagMeta(); + IXDefNode parentTagDefNode = parentTagMeta.getDefNodeInSchema(); + Set existChildTagNames = XmlPsiHelper.getChildTagNames(parentTag); for (IXDefNode childNode : parentTagDefNode.getChildren().values()) { if (childNode.isInternal()) { @@ -148,10 +149,12 @@ public class XLangCompletionContributor extends CompletionContributor implements return; } + XLangTagMeta tagMeta = tag.getTagMeta(); String prefix = result.getPrefixMatcher().getPrefix(); - IXDefNode defNode = tag.getSchemaDefNode(); + + IXDefNode defNode = tagMeta.getDefNodeInSchema(); if (prefix.startsWith(XDslKeys.DEFAULT.X_NS_PREFIX)) { - defNode = tag.getXDslDefNode(); + defNode = tagMeta.getXDslDefNode(); } if (defNode == null) { @@ -177,8 +180,8 @@ public class XLangCompletionContributor extends CompletionContributor implements return; } - String attrName = attr.getName(); - IXDefAttribute defAttr = tag.getSchemaDefNodeAttr(attrName); + XLangTagMeta tagMeta = tag.getTagMeta(); + IXDefAttribute defAttr = tagMeta.getDefAttr(attr); if (defAttr != null) { completeAttrValue(result, defAttr); } diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/doc/XLangDocumentationProvider.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/doc/XLangDocumentationProvider.java index 02d895b1aa1e2113febf5fed72a01c8905e8312f..006b07a45b3333674c51d68d205d103c5c6a8476 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/doc/XLangDocumentationProvider.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/doc/XLangDocumentationProvider.java @@ -20,6 +20,7 @@ import io.nop.api.core.beans.DictOptionBean; import io.nop.idea.plugin.lang.XLangDocumentation; import io.nop.idea.plugin.lang.psi.XLangAttribute; import io.nop.idea.plugin.lang.psi.XLangTag; +import io.nop.idea.plugin.lang.psi.XLangTagMeta; import io.nop.idea.plugin.utils.ProjectFileHelper; import io.nop.xlang.xdef.IXDefAttribute; import io.nop.xlang.xdef.XDefConstants; @@ -32,7 +33,7 @@ import static com.intellij.psi.xml.XmlTokenType.XML_NAME; public class XLangDocumentationProvider extends AbstractDocumentationProvider { /** - * 文档生成函数 + * 文档生成函数:对于没有可识别的引用的元素,将不会调用该接口 *

* 默认鼠标移动时的文档也由该函数生成 {@link #generateHoverDoc} * @@ -77,13 +78,18 @@ public class XLangDocumentationProvider extends AbstractDocumentationProvider { XLangDocumentation doc = null; if (parent instanceof XLangTag tag) { - doc = tag.getTagDocumentation(); + XLangTagMeta tagMeta = tag.getTagMeta(); + + doc = tagMeta.getTagDocumentation(); } // else if (parent instanceof XLangAttribute attr) { - String attrName = attr.getName(); XLangTag tag = attr.getParentTag(); - doc = tag != null ? tag.getAttrDocumentation(attrName) : null; + if (tag != null) { + XLangTagMeta tagMeta = tag.getTagMeta(); + + doc = tagMeta.getAttrDocumentation(attr); + } } return doc; @@ -97,11 +103,11 @@ public class XLangDocumentationProvider extends AbstractDocumentationProvider { } IXDefAttribute defAttr = attr.getDefAttr(); - XDefTypeDecl defAttrType = defAttr != null ? defAttr.getType() : null; - if (defAttrType == null) { + if (XLangAttribute.isNullOrErrorDefAttr(defAttr)) { return null; } + XDefTypeDecl defAttrType = defAttr.getType(); if (!XDefConstants.STD_DOMAIN_DICT.equals(defAttrType.getStdDomain())) { return null; } diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangDocumentation.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangDocumentation.java index f40eae04027ccfab5b32fe5f03c48a068be2955b..a01b40b6841c8de81f3dc2bb7dfe1482002bfa13 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangDocumentation.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangDocumentation.java @@ -48,6 +48,7 @@ public class XLangDocumentation { String stdDomain; String defaultValue; String[] modifiers; + boolean additional; String desc; String path; @@ -105,6 +106,10 @@ public class XLangDocumentation { this.desc = desc; } + public void setAdditional(boolean additional) { + this.additional = additional; + } + public String genDoc() { StringBuilder sb = new StringBuilder(); sb.append(""); @@ -129,6 +134,12 @@ public class XLangDocumentation { resolveAttributes(JavaHighlightingColors.LINE_COMMENT), " (=" + defaultValue + ')'); } + if (additional) { + sb.append(' '); + appendStyledSpan(sb, + resolveAttributes(JavaHighlightingColors.LINE_COMMENT), + NopPluginBundle.message("xlang.doc.flag.additional")); + } if (StringHelper.isNotBlank(subTitle)) { sb.append(' '); appendStyledSpan(sb, resolveAttributes(JavaHighlightingColors.CLASS_NAME_ATTRIBUTES), subTitle); @@ -202,14 +213,12 @@ public class XLangDocumentation { } private static void appendStyledSpan( - @NotNull StringBuilder sb, @Nullable String value, String @NotNull ... properties - ) { + @NotNull StringBuilder sb, @Nullable String value, String @NotNull ... properties) { HtmlSyntaxInfoUtil.appendStyledSpan(sb, StringHelper.escapeXml(value), properties); } private static void appendStyledSpan( - @NotNull StringBuilder sb, @NotNull TextAttributes attributes, @Nullable String value - ) { + @NotNull StringBuilder sb, @NotNull TextAttributes attributes, @Nullable String value) { HtmlSyntaxInfoUtil.appendStyledSpan(sb, attributes, StringHelper.escapeXml(value), diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangScriptLanguageInjector.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangScriptLanguageInjector.java index 1d6867721cf8396d7b0c88fc9a8d669acedbc723..e4b02a21dbe68cc96125954606ceccf17fe51be0 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangScriptLanguageInjector.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangScriptLanguageInjector.java @@ -15,6 +15,7 @@ import com.intellij.psi.LanguageInjector; import com.intellij.psi.PsiLanguageInjectionHost; import io.nop.commons.util.StringHelper; import io.nop.idea.plugin.lang.psi.XLangTag; +import io.nop.idea.plugin.lang.psi.XLangTagMeta; import io.nop.idea.plugin.lang.psi.XLangText; import io.nop.idea.plugin.lang.script.XLangScriptLanguage; import io.nop.xlang.xpl.XplConstants; @@ -35,16 +36,22 @@ public class XLangScriptLanguageInjector implements LanguageInjector { @Override public void getLanguagesToInject( - @NotNull PsiLanguageInjectionHost host, @NotNull InjectedLanguagePlaces registrar + @NotNull PsiLanguageInjectionHost host, + @NotNull InjectedLanguagePlaces registrar ) { // 针对仅包含文本内容的 Xpl 类型节点(xdef:value=xpl*) if (!(host instanceof XLangText) // || !(host.getParent() instanceof XLangTag tag) // - || !tag.isXplDefNode() // + || tag.hasChildTag() // + ) { + return; + } + + XLangTagMeta tagMeta = tag.getTagMeta(); + if (!tagMeta.isXplNode() // || (!XplConstants.TAG_C_SCRIPT.equals(tag.getName()) // - && !tag.isXlibSourceNode() // TODO 暂时仅针对 c:script/source 标签做内嵌代码解析 + && !tagMeta.isXlibSourceNode() // TODO 暂时仅针对 c:script/source 标签做内嵌代码解析 ) // - || tag.hasChildTag() // ) { return; } diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangAttribute.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangAttribute.java index 2c7e35444b74e91f22e23427b8fb4c222602c50d..0b915ef1f78423d8a94743eae1921ff760548b25 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangAttribute.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangAttribute.java @@ -17,6 +17,9 @@ import io.nop.idea.plugin.lang.reference.XLangAttributeReference; import io.nop.idea.plugin.lang.reference.XLangXlibTagAttrReference; import io.nop.idea.plugin.lang.xlib.XlibXDefAttribute; import io.nop.xlang.xdef.IXDefAttribute; +import io.nop.xlang.xdef.XDefTypeDecl; +import io.nop.xlang.xdef.impl.XDefAttribute; +import io.nop.xlang.xdef.parse.XDefTypeDeclParser; import org.jetbrains.annotations.NotNull; /** @@ -32,6 +35,12 @@ public class XLangAttribute extends XmlAttributeImpl { return getClass().getSimpleName() + ':' + getElementType() + "('" + getName() + "')"; } + @Override + public boolean skipValidation() { + // Note: 禁用 xml 的校验 + return true; + } + public XLangTag getParentTag() { return (XLangTag) getParent(); } @@ -72,19 +81,35 @@ public class XLangAttribute extends XmlAttributeImpl { return null; } - String ns = getNamespacePrefix(); - String attrName = getName(); - boolean hasXDslNs = !ns.isEmpty() && ns.equals(tag.getXDslKeys().NS); - - IXDefAttribute defAttr; - // 取 xdsl.xdef 中声明的属性 - if (hasXDslNs) { - defAttr = tag.getXDslDefNodeAttr(attrName); - } // - else { - defAttr = tag.getSchemaDefNodeAttr(attrName); + XLangTagMeta tagMeta = tag.getTagMeta(); + return tagMeta.getDefAttr(this); + } + + public static boolean isNullOrErrorDefAttr(IXDefAttribute defAttr) { + return defAttr == null || defAttr instanceof XDefAttributeWithError; + } + + /** 类型为 {@code any} 的属性定义 */ + public static class XDefAttributeWithTypeAny extends XDefAttribute { + private static final XDefTypeDecl STD_DOMAIN_ANY = new XDefTypeDeclParser().parseFromText(null, "any"); + + public XDefAttributeWithTypeAny(String name) { + setName(name); + setType(STD_DOMAIN_ANY); } + } + + /** 含错误信息的属性定义 */ + public static class XDefAttributeWithError extends XDefAttribute { + final String errorMsg; - return defAttr; + public XDefAttributeWithError(String name, String errorMsg) { + this.errorMsg = errorMsg; + setName(name); + } + + public String getErrorMsg() { + return this.errorMsg; + } } } diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangAttributeValue.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangAttributeValue.java index 99b4ba6555170347f726ff97cd61b5546664d4b5..cc13b52669a5cdaed593e4f7cad6952c40d623be 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangAttributeValue.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangAttributeValue.java @@ -45,6 +45,12 @@ public class XLangAttributeValue extends XmlAttributeValueImpl { return getClass().getSimpleName(); } + @Override + public boolean skipValidation() { + // Note: 禁用 xml 的校验 + return true; + } + public XLangAttribute getParentAttr() { return getParent() instanceof XLangAttribute p ? p : null; } @@ -63,7 +69,11 @@ public class XLangAttributeValue extends XmlAttributeValueImpl { IXDefAttribute defAttr = attr.getDefAttr(); // 对于未定义属性,不做引用识别 - if (defAttr == null || (defAttr.isUnknownAttr() && defAttr.getType().getStdDomain().equals("any"))) { + if (XLangAttribute.isNullOrErrorDefAttr(defAttr) // + || (defAttr.isUnknownAttr() // + && defAttr.getType().getStdDomain().equals("any") // + ) // + ) { //return PsiReference.EMPTY_ARRAY; // Note: 临时支持对 xpl 内置函数的 vfs 引用识别 return XLangReferenceHelper.getReferencesFromText(this, attrValue); @@ -89,41 +99,48 @@ public class XLangAttributeValue extends XmlAttributeValueImpl { private PsiReference[] getReferencesByAttrName(XLangAttribute attr, String attrValue) { XLangTag tag = attr.getParentTag(); - XDslKeys xdslKeys = tag.getXDslKeys(); - XDefKeys xdefKeys = tag.getXDefKeys(); + XLangTagMeta tagMeta = tag.getTagMeta(); String attrName = attr.getName(); // Note: XmlAttributeValue 的文本范围是包含引号的 TextRange attrValueTextRange = getValueTextRange().shiftLeft(getStartOffset()); - if (xdslKeys.PROTOTYPE.equals(attrName)) { - return new PsiReference[] { - new XLangXPrototypeReference(this, attrValueTextRange, attrValue) - }; - } // - else if (xdefKeys.KEY_ATTR.equals(attrName)) { - return new PsiReference[] { - new XLangXdefKeyAttrReference(this, attrValueTextRange, attrValue) - }; - } // - else if (xdefKeys.UNIQUE_ATTR.equals(attrName) // - || xdefKeys.ORDER_ATTR.equals(attrName) // - ) { - return new PsiReference[] { - new XLangParentTagAttrReference(this, attrValueTextRange, attrValue) - }; - } // - else if (xdefKeys.NAME.equals(attrName)) { - // 与根节点上的 xdef:bean-package 组成 class - XLangTag rootTag = tag.getRootTag(); - - String pkgName = rootTag.getAttributeValue(rootTag.getXDefKeys().BEAN_PACKAGE); - if (StringHelper.isEmpty(pkgName)) { - return PsiReference.EMPTY_ARRAY; + + XDslKeys xdslKeys = tagMeta.getXdslKeys(); + if (xdslKeys != null) { + if (xdslKeys.PROTOTYPE.equals(attrName)) { + return new PsiReference[] { + new XLangXPrototypeReference(this, attrValueTextRange, attrValue) + }; } + } - return new PsiReference[] { - new XLangXdefNameReference(this, attrValueTextRange, pkgName, attrValue) - }; + XDefKeys xdefKeys = tagMeta.getXdefKeys(); + if (xdefKeys != null) { + if (xdefKeys.KEY_ATTR.equals(attrName)) { + return new PsiReference[] { + new XLangXdefKeyAttrReference(this, attrValueTextRange, attrValue) + }; + } // + else if (xdefKeys.UNIQUE_ATTR.equals(attrName) // + || xdefKeys.ORDER_ATTR.equals(attrName) // + ) { + return new PsiReference[] { + new XLangParentTagAttrReference(this, attrValueTextRange, attrValue) + }; + } // + else if (xdefKeys.NAME.equals(attrName)) { + // 与根节点上的 xdef:bean-package 组成 class + XLangTag rootTag = tag.getRootTag(); + + String pkgName = rootTag.getAttributeValue(xdefKeys.BEAN_PACKAGE); + if (StringHelper.isEmpty(pkgName)) { + return PsiReference.EMPTY_ARRAY; + } + + return new PsiReference[] { + new XLangXdefNameReference(this, attrValueTextRange, pkgName, attrValue) + }; + } } return null; diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangTag.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangTag.java index c82227ab3cdbb7c4cbed6a40706dec4a73f3e16d..bdc2c5d65e6172431c8cb825e85ffe5b0c17d3c3 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangTag.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangTag.java @@ -10,10 +10,9 @@ package io.nop.idea.plugin.lang.psi; import java.util.ArrayList; import java.util.List; -import java.util.function.Supplier; +import java.util.Objects; import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; @@ -26,34 +25,16 @@ import com.intellij.psi.xml.XmlElement; import com.intellij.psi.xml.XmlToken; import com.intellij.util.IncorrectOperationException; import com.intellij.xml.util.XmlTagUtil; -import io.nop.api.core.util.SourceLocation; import io.nop.commons.util.StringHelper; import io.nop.core.lang.xml.XNode; -import io.nop.idea.plugin.lang.XLangDocumentation; import io.nop.idea.plugin.lang.reference.XLangTagReference; import io.nop.idea.plugin.lang.reference.XLangXlibTagNsReference; import io.nop.idea.plugin.lang.reference.XLangXlibTagReference; import io.nop.idea.plugin.lang.xlib.XlibTagMeta; import io.nop.idea.plugin.resource.ProjectEnv; -import io.nop.idea.plugin.utils.XDefPsiHelper; import io.nop.idea.plugin.utils.XmlPsiHelper; -import io.nop.xlang.xdef.IXDefAttribute; -import io.nop.xlang.xdef.IXDefComment; -import io.nop.xlang.xdef.IXDefNode; -import io.nop.xlang.xdef.IXDefSubComment; -import io.nop.xlang.xdef.IXDefinition; -import io.nop.xlang.xdef.XDefConstants; -import io.nop.xlang.xdef.XDefKeys; -import io.nop.xlang.xdef.XDefTypeDecl; -import io.nop.xlang.xdef.domain.StdDomainRegistry; -import io.nop.xlang.xdef.impl.XDefAttribute; -import io.nop.xlang.xdef.parse.XDefTypeDeclParser; -import io.nop.xlang.xdsl.XDslConstants; -import io.nop.xlang.xdsl.XDslKeys; import io.nop.xlang.xpl.XplConstants; -import io.nop.xlang.xpl.xlib.XlibConstants; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import static com.intellij.psi.xml.XmlElementType.XML_TAG; import static com.intellij.psi.xml.XmlElementType.XML_TEXT; @@ -67,30 +48,61 @@ import static com.intellij.psi.xml.XmlElementType.XML_TEXT; * @date 2025-07-09 */ public class XLangTag extends XmlTagImpl { - private static final SchemaMeta UNKNOWN_SCHEMA_META = new UnknownSchemaMeta(); - - private static final XDefTypeDecl STD_DOMAIN_XDEF_REF = new XDefTypeDeclParser().parseFromText(null, - XDefConstants.STD_DOMAIN_XDEF_REF); - - private SchemaMeta schemaMeta; + private XLangTagMeta tagMeta; @Override public String toString() { return getClass().getSimpleName() + ':' + getElementType() + "('" + getName() + "')"; } + public synchronized XLangTagMeta getTagMeta() { + String tagName = getName(); + + if (tagMeta == null || !isValid() /* 文件已无效 */) { + tagMeta = createTagMeta(); + } + // 根节点发生了 schema 相关的更新(包括 schema 的依赖的变更),或者标签名发生了变化 + else if (tagMeta != null // + && (!tagName.equals(tagMeta.getTagName()) || isRootTag()) // + ) { + XLangTagMeta newTagMeta = createTagMeta(); + + if (!Objects.equals(tagMeta, newTagMeta)) { + clearTagMeta(); + tagMeta = newTagMeta; + } + } + + // Note: 避免后续访问出现 NPE 问题 + return Objects.requireNonNullElseGet(tagMeta, + () -> XLangTagMeta.errorTag(this, + "xlang.parser.tag-meta.creating-failed", + tagName)); + } + + /** 标签存在被复用的可能,因此,需显式清理与之绑定的数据 */ @Override public void clearCaches() { - this.schemaMeta = null; + clearTagMeta(); super.clearCaches(); } + @Override + public boolean skipValidation() { + // Note: 禁用 xml 的校验 + return true; + } + @Override public XLangTag getParentTag() { return (XLangTag) super.getParentTag(); } + private boolean isRootTag() { + return getParentTag() == null; + } + public XLangTag getRootTag() { XLangTag tag = this; @@ -117,8 +129,8 @@ public class XLangTag extends XmlTagImpl { @Override public PsiElement setName(@NotNull String name) throws IncorrectOperationException { - String tagName = getName(); - if (getXDefKeys().UNKNOWN_TAG.equals(tagName)) { + XLangTagMeta tagMeta = getTagMeta(); + if (tagMeta.isXDefUnknownTag()) { return this; } @@ -187,138 +199,6 @@ public class XLangTag extends XmlTagImpl { return refs.toArray(PsiReference.EMPTY_ARRAY); } - /** @see SchemaMeta#getSchemaDef() */ - private IXDefinition getSchemaDef() { - return getSchemaMeta().getSchemaDef(); - } - - /** @see SchemaMeta#getSchemaDefNode() */ - public IXDefNode getSchemaDefNode() { - return getSchemaMeta().getSchemaDefNode(); - } - - /** - * 获取当前标签上指定属性在元模型中的定义 - *

- * 在元元模型 xdef.xdef 中,名字空间 meta 的属性均由同名的以 - * xdef 为名字空间的属性定义,而以 xdef - * 为名字空间的属性,则均由 meta:unknown-attr 定义。 - * 即,二者形成交叉定义 - */ - public IXDefAttribute getSchemaDefNodeAttr(String attrName) { - // 在元元模型中,以 xdef 为名字空间的属性,需以 meta:unknown-attr 作为其属性定义 - if (isInXDefXDef() && attrName.startsWith(XDefKeys.DEFAULT.NS + ':')) { - attrName = "*"; - } - // xdef.xdef 的属性在固定的名字空间 xdef 中声明 - else { - attrName = changeNamespace(attrName, getXDefKeys().NS, XDefKeys.DEFAULT.NS); - } - - IXDefAttribute attr = getXDefNodeAttr(getSchemaDefNode(), attrName); - - if (attr == null || attr.isUnknownAttr()) { - XlibTagMeta xlibTag = getXlibTagMeta(); - - if (xlibTag != null) { - return xlibTag.getAttribute(attrName); - } - } - return attr; - } - - /** - * 获取 {@link #getSchemaDefNode()} 节点的 {@link XDefKeys#VALUE xdef:value}, - * 即,其子节点(包括文本节点)对应的{@link XDefTypeDecl 类型} - */ - public XDefTypeDecl getSchemaDefNodeXdefValue() { - IXDefNode defNode = getSchemaDefNode(); - - return defNode != null ? defNode.getXdefValue() : null; - } - - /** @see SchemaMeta#getXDslDefNode() */ - public IXDefNode getXDslDefNode() { - return getSchemaMeta().getXDslDefNode(); - } - - /** 获取当前标签上指定属性在 xdsl.xdef 中的定义 */ - public IXDefAttribute getXDslDefNodeAttr(String attrName) { - // Note: xdsl.xdef 的属性在固定的名字空间 x 中声明 - attrName = changeNamespace(attrName, getXDslKeys().NS, XDslKeys.DEFAULT.NS); - - return getXDefNodeAttr(getXDslDefNode(), attrName); - } - - /** @see SchemaMeta#getSelfDefNode() */ - public IXDefNode getSelfDefNode() { - return getSchemaMeta().getSelfDefNode(); - } - - /** @see SchemaMeta#getXDefKeys() */ - public XDefKeys getXDefKeys() { - return getSchemaMeta().getXDefKeys(); - } - - /** @see SchemaMeta#getXDslKeys() */ - public XDslKeys getXDslKeys() { - return getSchemaMeta().getXDslKeys(); - } - - /** @see SchemaMeta#isInXDefXDef() */ - private boolean isInXDefXDef() { - return getSchemaMeta().isInXDefXDef(); - } - - /** @see SchemaMeta#isXplDefNode() */ - public boolean isXplDefNode() { - return getSchemaMeta().isXplDefNode(); - } - - /** @see SchemaMeta#isXlibSourceNode() */ - public boolean isXlibSourceNode() { - return getSchemaMeta().isXlibSourceNode(); - } - - /** 当前标签是否允许拥有子标签 */ - public boolean isAllowedChildTag() { - IXDefNode defNode = getSchemaDefNode(); - if (defNode == null) { - return false; - } else if (defNode.hasChild()) { - return true; - } - - return isXdefValueSupportBody(); - } - - /** 当前标签的 {@link #getSchemaDefNodeXdefValue() xdef:value} 类型是否支持内嵌节点 */ - public boolean isXdefValueSupportBody() { - XDefTypeDecl xdefValue = getSchemaDefNodeXdefValue(); - - return xdefValue != null && xdefValue.isSupportBody(StdDomainRegistry.instance()); - } - - /** - * 当前标签是否为可被允许的未知标签 - *

- * 仅当前标签包含自定义名字空间,且 {@link #getSchemaDef()} 的 - * {@link IXDefinition#getXdefCheckNs() xdef:check-ns} - * 不包含该名字空间时,该标签才是被许可的 - */ - public boolean isAllowedUnknownTag() { - String name = getName(); - IXDefinition def = getSchemaDef(); - String ns = StringHelper.getNamespace(name); - - if (def == null || ns == null) { - return false; - } - - return def.getXdefCheckNs() == null // - || !def.getXdefCheckNs().contains(ns); - } - /** 若当前标签对应的是 xlib 的函数节点,则返回该函数节点信息 */ public XlibTagMeta getXlibTagMeta() { String tagNs = getNamespacePrefix(); @@ -327,7 +207,7 @@ public class XLangTag extends XmlTagImpl { } XLangTag parentTag = getParentTag(); - if (parentTag == null || !parentTag.isXplDefNode()) { + if (parentTag == null || !parentTag.getTagMeta().isXplNode()) { return null; } @@ -376,553 +256,40 @@ public class XLangTag extends XmlTagImpl { return new XlibTagMeta(ref, tagNs, tagName, lib); } - /** 获取当前标签的说明文档 */ - public XLangDocumentation getTagDocumentation() { - String tagNs = getNamespacePrefix(); - String tagName = getName(); - - IXDefNode defNode; - // xdef:define 没有实体节点,故而,不显示其文档 - if (getXDefKeys().DEFINE.equals(tagName)) { - defNode = null; - } - // x 名字空间的节点,显示其在 xdsl 中的文档 - else if (XDslKeys.DEFAULT.NS.equals(tagNs)) { - defNode = getXDslDefNode(); - } - // 在非 xdef.xdef 中的以 xdef 为名字空间的节点(不含 xdef:unknown-tag),显示其定义文档 - else if (!isInXDefXDef() && XDefKeys.DEFAULT.NS.equals(tagNs) // - && !XDefKeys.DEFAULT.UNKNOWN_TAG.equals(tagName) // - ) { - defNode = getSchemaDefNode(); - } - // *.xdef 中的自定义节点,显示自己的文档 - else if (getSelfDefNode() != null) { - defNode = getSelfDefNode(); - } - // 其余 dsl 均显示节点的定义文档 - else { - defNode = getSchemaDefNode(); - } - - if (defNode == null) { - return null; - } - - XlibTagMeta xlibTag = getXlibTagMeta(); - if (xlibTag != null) { - return xlibTag.getDocumentation(); - } - - XLangDocumentation doc = new XLangDocumentation(defNode); - doc.setMainTitle(tagName); - - IXDefComment comment = defNode.getComment(); - if (comment != null) { - doc.setSubTitle(comment.getMainDisplayName()); - doc.setDesc(comment.getMainDescription()); - } - - return doc; - } - - /** 获取当前标签指定属性的说明文档 */ - public XLangDocumentation getAttrDocumentation(String attrName) { - String attrNs = StringHelper.getNamespace(attrName); - if ("xmlns".equals(attrNs)) { - return null; - } - - String mainTitle = attrName; - IXDefNode defNode; - if (isInXDefXDef() && XDefKeys.DEFAULT.NS.equals(attrNs)) { - defNode = getSelfDefNode(); - } // - else if (XDslKeys.DEFAULT.NS.equals(attrNs) || getXDslKeys().NS.equals(attrNs)) { - attrName = changeNamespace(attrName, getXDslKeys().NS, XDslKeys.DEFAULT.NS); - defNode = getXDslDefNode(); - } // - else { - attrName = changeNamespace(attrName, getXDefKeys().NS, XDefKeys.DEFAULT.NS); - - defNode = getSelfDefNode(); - // 对于 *.xdef,优先取其自身定义节点上的属性文档 - if (defNode == null || defNode.getAttribute(attrName) == null) { - defNode = getSchemaDefNode(); - } - } - - IXDefAttribute defAttr = getXDefNodeAttr(defNode, attrName); - if (defAttr == null) { - return null; - } - - if (defAttr.isUnknownAttr()) { - XlibTagMeta xlibTag = getXlibTagMeta(); - - if (xlibTag != null) { - return xlibTag.getAttrDocumentation(attrName); - } - } - - XLangDocumentation doc = new XLangDocumentation(defAttr); - doc.setMainTitle(mainTitle); - - IXDefComment nodeComment = defNode.getComment(); - if (nodeComment != null) { - IXDefSubComment attrComment = nodeComment.getSubComments().get(attrName); - if (attrComment == null && defAttr.isUnknownAttr()) { - attrComment = nodeComment.getSubComments().get(XDefKeys.DEFAULT.UNKNOWN_ATTR); - } - - if (attrComment != null) { - doc.setSubTitle(attrComment.getDisplayName()); - doc.setDesc(attrComment.getDescription()); - } - } - - return doc; - } - - /** 获取 {@link IXDefNode} 上指定属性的 xdef 定义 */ - private IXDefAttribute getXDefNodeAttr(IXDefNode xdefNode, String attrName) { - String xmlnsPrefix = "xmlns:"; - if (attrName.startsWith(xmlnsPrefix)) { - String attrValue = getAttributeValue(attrName); - // 忽略 xmlns:biz="biz" 形式的属性 - if (attrName.equals(xmlnsPrefix + attrValue)) { - return null; - } - - XDefAttribute at = new XDefAttribute(); - at.setName(attrName); - at.setType(STD_DOMAIN_XDEF_REF); - - return at; - } + private XLangTagMeta createTagMeta() { + Project project = getProject(); - if (xdefNode == null) { + try { + return ProjectEnv.withProject(project, () -> XLangTagMeta.create(this)); + } catch (ProcessCanceledException e) { + // Note: 若处理被中断,则保持节点定义信息为空,以便于后续再重新初始化 return null; } - - IXDefAttribute attr = xdefNode.getAttribute(attrName); - if (attr != null) { - return attr; - } - - // Note: 在 IXDefNode 中,对 xdef:unknown-attr 只记录了类型,并没有 IXDefAttribute 实体, - // 其处理逻辑见 XDefinitionParser#parseNode - XDefTypeDecl xdefUnknownAttrType = xdefNode.getXdefUnknownAttr(); - if (xdefUnknownAttrType != null) { - XDefAttribute at = new XDefAttribute() { - @Override - public boolean isUnknownAttr() { - return true; - } - }; - - at.setName(getXDefKeys().UNKNOWN_ATTR); - at.setType(xdefUnknownAttrType); - - // Note: 在需要时,通过节点位置再定位具体的属性位置 - SourceLocation loc = null; - // 在存在节点继承的情况下,选择最上层定义的同类型的 unknown-attr 属性 - IXDefNode refNode = xdefNode; - while (refNode != null) { - if (refNode.getXdefUnknownAttr() != xdefUnknownAttrType) { - break; - } - - loc = refNode.getLocation(); - refNode = refNode.getRefNode(); - } - at.setLocation(loc); - - return at; - } - - return null; - } - - private static IXDefNode getXDefNodeChild(IXDefNode xdefNode, String tagName) { - return xdefNode != null ? xdefNode.getChild(tagName) : null; - } - - public static String changeNamespace(String name, String fromNs, String toNs) { - if (fromNs == null || toNs == null || fromNs.equals(toNs)) { - return name; - } - - if (StringHelper.startsWithNamespace(name, fromNs)) { - return toNs + ':' + name.substring(fromNs.length() + 1); - } - return name; } - private synchronized SchemaMeta getSchemaMeta() { - if (schemaMeta == null) { - Project project = getProject(); - schemaMeta = ProjectEnv.withProject(project, this::createSchemaMeta); - - try { - ProgressManager.checkCanceled(); - } catch (ProcessCanceledException e) { - // Note: 若处理被中断,则保持元模型信息为空,以便于后续再重新初始化 - schemaMeta = null; + private void clearTagMeta() { + this.tagMeta = null; - // Note: 避免后续访问成员变量出现 NPE 问题 - return UNKNOWN_SCHEMA_META; + // 子节点同时失效 + for (PsiElement child : getChildren()) { + if (child instanceof XLangTag tag) { + tag.clearTagMeta(); } } - return schemaMeta; - } - - private SchemaMeta createSchemaMeta() { - XLangTag parentTag = getParentTag(); - if (parentTag == null) { - return createSchemaMetaForRootTag(this); - } - - String tagName = getName(); - String tagNs = getNamespacePrefix(); - - Project project = getProject(); - SchemaMeta parentSchemaMeta = parentTag.getSchemaMeta(); - - return new ChildTagSchemaMeta(project, parentSchemaMeta, tagName, tagNs); } - private static SchemaMeta createSchemaMetaForRootTag(XLangTag rootTag) { - String schemaUrl = XDefPsiHelper.getSchemaPath(rootTag); - if (schemaUrl == null) { - return UNKNOWN_SCHEMA_META; - } - - String xdefNs = XmlPsiHelper.getXmlnsForUrl(rootTag, XDslConstants.XDSL_SCHEMA_XDEF); - String xdslNs = XmlPsiHelper.getXmlnsForUrl(rootTag, XDslConstants.XDSL_SCHEMA_XDSL); - XDefKeys xdefKeys = XDefKeys.of(xdefNs); - XDslKeys xdslKeys = XDslKeys.of(xdslNs); - - Supplier selfDef = () -> null; - // x:schema 为 /nop/schema/xdef.xdef 时,其自身也为元模型 - if (XDslConstants.XDSL_SCHEMA_XDEF.equals(schemaUrl)) { - String vfsPath = XmlPsiHelper.getNopVfsPath(rootTag); - - if (vfsPath != null) { - selfDef = () -> XDefPsiHelper.loadSchema(vfsPath); - } else { - // 适配单元测试环境:待测试资源可能不是标准的 vfs 资源 - IXDefinition def = XDefPsiHelper.loadSchema(rootTag.getContainingFile()); - selfDef = () -> def; - } - } - - Project project = rootTag.getProject(); - - return new RootTagSchemaMeta(project, schemaUrl, xdefKeys, xdslKeys, selfDef); - } - - private static class RootTagSchemaMeta extends SchemaMeta { - protected final String schemaUrl; - protected final XDefKeys xdefKeys; - protected final XDslKeys xdslKeys; - - private final Supplier selfDef; - - RootTagSchemaMeta( - Project project, String schemaUrl, // - XDefKeys xdefKeys, XDslKeys xdslKeys, // - Supplier selfDef + public static String replaceXmlNs(String name, String fromNs, String toNs) { + if (fromNs == null || toNs == null // + || fromNs.equals(toNs) // + || !StringHelper.startsWithNamespace(name, fromNs) // ) { - super(project, null); - this.schemaUrl = schemaUrl; - this.xdefKeys = xdefKeys; - this.xdslKeys = xdslKeys; - this.selfDef = selfDef; - } - - // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - @Override - public IXDefinition getSchemaDef() { - return ProjectEnv.withProject(project, () -> XDefPsiHelper.loadSchema(schemaUrl)); - } - - @Override - public IXDefNode getSchemaDefNode() { - IXDefinition def = getSchemaDef(); - - return def != null ? def.getRootNode() : null; - } - - @Override - public @Nullable IXDefNode getXDslDefNode() { - return ProjectEnv.withProject(project, () -> XDefPsiHelper.getXDslDef().getRootNode()); - } - - @Override - public @Nullable IXDefinition getSelfDef() { - return ProjectEnv.withProject(project, selfDef); - } - - @Override - public IXDefNode getSelfDefNode() { - IXDefinition def = getSelfDef(); - - return def != null ? def.getRootNode() : null; - } - // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - @Override - public @NotNull XDefKeys getXDefKeys() { - return xdefKeys; - } - - @Override - public @NotNull XDslKeys getXDslKeys() { - return xdslKeys; - } - } - - private static class ChildTagSchemaMeta extends SchemaMeta { - protected final SchemaMeta parent; - protected final String tagNs; - - ChildTagSchemaMeta(Project project, SchemaMeta parent, String tagName, String tagNs) { - super(project, tagName); - this.parent = parent; - this.tagNs = tagNs; - } - - // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - @Override - public @Nullable IXDefinition getSchemaDef() { - return parent.getSchemaDef(); - } - - @Override - public @Nullable IXDefNode getSchemaDefNode() { - if (XDslKeys.DEFAULT.NS.equals(tagNs) && !parent.isInXDslXDef()) { - return getXDslDefNode(); - } - - IXDefNode parentDefNode = parent.getSchemaDefNode(); - if (parent.isXplDefNode()) { - // Xpl 子节点均为 xdef:unknown-tag - parentDefNode = ProjectEnv.withProject(project, - () -> XDefPsiHelper.getXplDef() - .getRootNode() - .getXdefUnknownTag()); - } - - if (parentDefNode == null) { - return null; - } - - IXDefNode defNode; - // 在元元模型中,以 xdef 为名字空间的标签, - // 需以 meta:unknown-tag 作为其节点定义,即,交叉定义 - if (parent.isInXDefXDef() && XDefKeys.DEFAULT.NS.equals(tagNs)) { - defNode = parentDefNode.getXdefUnknownTag(); - } - // 其余的,则将标签的 xdef 名字空间固定为名字 xdef - else { - XDefKeys xdefKeys = parent.getXDefKeys(); - String newTagName = changeNamespace(tagName, xdefKeys.NS, XDefKeys.DEFAULT.NS); - - defNode = getXDefNodeChild(parentDefNode, newTagName); - } - - return defNode; - } - - @Override - public @Nullable IXDefNode getXDslDefNode() { - IXDefNode parentDefNode = parent.getXDslDefNode(); - - return getXDefNodeChild(parentDefNode, tagName); - } - - @Override - public @Nullable IXDefinition getSelfDef() { - return parent.getSelfDef(); - } - - @Override - public @Nullable IXDefNode getSelfDefNode() { - // 在非 xdsl.xdef 中的 x 名字空间的节点,始终不视为自定义节点 - if (XDslKeys.DEFAULT.NS.equals(tagNs) && !parent.isInXDslXDef()) { - return null; - } - - IXDefNode parentDefNode = parent.getSelfDefNode(); - - return getXDefNodeChild(parentDefNode, tagName); - } - // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - @Override - public @NotNull XDefKeys getXDefKeys() { - return parent.getXDefKeys(); - } - - @Override - public @NotNull XDslKeys getXDslKeys() { - return parent.getXDslKeys(); - } - } - - private static class UnknownSchemaMeta extends SchemaMeta { - - UnknownSchemaMeta() { - super(null, null); - } - - // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - @Override - public @Nullable IXDefinition getSchemaDef() { - return null; - } - - @Override - public @Nullable IXDefNode getSchemaDefNode() { - return null; - } - - @Override - public @Nullable IXDefNode getXDslDefNode() { - return null; - } - - @Override - public @Nullable IXDefinition getSelfDef() { - return null; - } - - @Override - public @Nullable IXDefNode getSelfDefNode() { - return null; - } - // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - @Override - public @NotNull XDefKeys getXDefKeys() { - return XDefKeys.DEFAULT; + return name; } - @Override - public @NotNull XDslKeys getXDslKeys() { - return XDslKeys.DEFAULT; - } + return replaceXmlNs(name, toNs); } - /** - * 注意,由于涉及对 *.xdef 的修改,因此,需采用实时加载方式获取 {@link IXDefinition} - * 和 {@link IXDefNode},不能缓存其实体对象。{@link XDefPsiHelper#loadSchema(String)} - * 是有缓存和失效机制的,不会明显影响性能 - */ - private static abstract class SchemaMeta { - protected final Project project; - protected final String tagName; - - SchemaMeta(Project project, String tagName) { - this.project = project; - this.tagName = tagName; - } - - /** - * 当前标签所在的元模型(在 *.xdef 中定义) - *

- * 允许元模型不存在,以支持检查 xdsl.xdef 对应的节点 - */ - public abstract @Nullable IXDefinition getSchemaDef(); - - /** 当前标签在 {@link #getSchemaDef()} 中所对应的节点 */ - public abstract @Nullable IXDefNode getSchemaDefNode(); - - /** - * 当前标签在 xdsl 模型(xdsl.xdef)中所对应的节点。 - * 注:所有 DSL 模型的节点均与 xdsl.xdef 的节点存在对应 - */ - public abstract @Nullable IXDefNode getXDslDefNode(); - - /** 当当前标签定义在 *.xdef 文件中时,需记录该元模型 */ - public abstract @Nullable IXDefinition getSelfDef(); - - /** 当前标签定义在 {@link #getSelfDef()} 中所对应的节点 */ - public abstract @Nullable IXDefNode getSelfDefNode(); - - /** - * /nop/schema/xdef.xdef 对应的 {@link XDefKeys}。 - * 仅在元模型中设置,如 xmlns:xdef="/nop/schema/xdef.xdef" - */ - public abstract @NotNull XDefKeys getXDefKeys(); - - /** - * /nop/schema/xdsl.xdef 对应的 {@link XDslKeys}。 - * 在 DSL 模型(含元模型)中均有设置,如 xmlns:x="/nop/schema/xdsl.xdef" - */ - public abstract @NotNull XDslKeys getXDslKeys(); - - /** 当前标签是否在元元模型 xdef.xdef 中 */ - public boolean isInXDefXDef() { - IXDefinition def = getSelfDef(); - - // Note: 在单元测试中只能基于内容做判断,而不是 vfs 路径 - return def != null // - && def.getXdefCheckNs().contains(XDefKeys.DEFAULT.NS) // - && !XDefKeys.DEFAULT.equals(getXDefKeys()); - } - - /** 当前标签是否在 DSL 元模型 xdsl.xdef 中 */ - public boolean isInXDslXDef() { - IXDefinition def = getSelfDef(); - - // Note: 在单元测试中只能基于内容做判断,而不是 vfs 路径 - return def != null // - && def.getXdefCheckNs().contains(XDslKeys.DEFAULT.NS) // - && !XDslKeys.DEFAULT.equals(getXDslKeys()); - } - - /** 当前标签是否对应 Xlib 的 source 节点 */ - public boolean isXlibSourceNode() { - IXDefNode defNode = getSchemaDefNode(); - if (defNode == null) { - return false; - } - - String defPath = XmlPsiHelper.getNopVfsPath(defNode); - - return isXlibSourceNode(defNode, defPath); - } - - /** 当前标签是否对应 Xpl 节点 */ - public boolean isXplDefNode() { - IXDefNode defNode = getSchemaDefNode(); - if (defNode == null) { - return false; - } - - String defPath = XmlPsiHelper.getNopVfsPath(defNode); - - if (XDefPsiHelper.isXplDefNode(defNode) // - || XDslConstants.XDSL_SCHEMA_XPL.equals(defPath) // - ) { - return true; - } - - return isXlibSourceNode(defNode, defPath); - } - - private boolean isXlibSourceNode(IXDefNode defNode, String defPath) { - // xlib.xdef 中的 source 标签设置为 xml 类型,是因为在获取 XplLib 模型的时候会根据 xlib.xdef 来解析, - // 但此时这个 source 段无法自动进行编译,必须结合它的 outputMode 和 attrs 配置等才能决定。 - // 因此,将其子节点同样视为 xpl 节点处理 - return XDslConstants.XDSL_SCHEMA_XLIB.equals(defPath) - && XDefConstants.STD_DOMAIN_XML.equals(XDefPsiHelper.getDefNodeType(defNode)) - && XlibConstants.SOURCE_NAME.equals(tagName); - } + public static String replaceXmlNs(String name, String ns) { + return ns + ':' + name.substring(name.indexOf(':') + 1); } } diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangTagMeta.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangTagMeta.java new file mode 100644 index 0000000000000000000000000000000000000000..fa80eb1a04ecb5d5f2159c013f46789f741d21ca --- /dev/null +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangTagMeta.java @@ -0,0 +1,769 @@ +/* + * Copyright (c) 2017-2025 Nop Platform. All rights reserved. + * Author: canonical_entropy@163.com + * Blog: https://www.zhihu.com/people/canonical-entropy + * Gitee: https://gitee.com/canonical-entropy/nop-entropy + * Github: https://github.com/entropy-cloud/nop-entropy + */ + +package io.nop.idea.plugin.lang.psi; + +import java.util.Objects; +import java.util.Set; + +import io.nop.api.core.util.SourceLocation; +import io.nop.commons.util.StringHelper; +import io.nop.idea.plugin.lang.XLangDocumentation; +import io.nop.idea.plugin.lang.xlib.XlibTagMeta; +import io.nop.idea.plugin.messages.NopPluginBundle; +import io.nop.idea.plugin.utils.XDefPsiHelper; +import io.nop.idea.plugin.utils.XmlPsiHelper; +import io.nop.xlang.xdef.IXDefAttribute; +import io.nop.xlang.xdef.IXDefComment; +import io.nop.xlang.xdef.IXDefNode; +import io.nop.xlang.xdef.IXDefSubComment; +import io.nop.xlang.xdef.IXDefinition; +import io.nop.xlang.xdef.XDefConstants; +import io.nop.xlang.xdef.XDefKeys; +import io.nop.xlang.xdef.XDefTypeDecl; +import io.nop.xlang.xdef.domain.StdDomainRegistry; +import io.nop.xlang.xdef.impl.XDefAttribute; +import io.nop.xlang.xdef.impl.XDefinition; +import io.nop.xlang.xdef.parse.XDefTypeDeclParser; +import io.nop.xlang.xdsl.XDslConstants; +import io.nop.xlang.xdsl.XDslKeys; +import io.nop.xlang.xpl.XplConstants; +import io.nop.xlang.xpl.xlib.XlibConstants; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.PropertyKey; + +import static io.nop.idea.plugin.messages.NopPluginBundle.BUNDLE; + +/** + * 标签的{@link IXDefNode 定义节点}信息 + * + * @author flytreeleft + * @date 2025-10-21 + */ +public class XLangTagMeta { + private static final XDefTypeDecl STD_DOMAIN_XDEF_REF = // + new XDefTypeDeclParser().parseFromText(null, XDefConstants.STD_DOMAIN_XDEF_REF); + + final XLangTag tag; + final String errorMsg; + + XLangTagMeta parent; + + /** + * 当前标签在元模型中所对应的定义节点: + * 元模型一般由根节点上的 {@code x:schema} 指定, + * 但 Xpl 类型节点的元模型始终为 {@code /nop/schema/xpl.xdef} + */ + IXDefNode defNodeInSchema; + /** + * 当前标签所在的元模型中通过 {@code xdef:check-ns} 所设置的待校验的名字空间列表: + * 节点或属性的名字空间若在该列表中,则其必须在元模型中显式定义 + */ + Set checkNsInSchema; + + /** + * 若当前标签在元模型文件({@code *.xdef})中, + * 则用于记录当前标签在该元模型中的定义 + */ + IXDefNode defNodeInSelfSchema; + /** + * 若当前标签在元模型文件({@code *.xdef})中, + * 则用于记录该元模型 {@code xdef:check-ns} 属性的值 + */ + Set checkNsInSelfSchema; + + /** + * 当前标签所对应的元模型 {@code /nop/schema/xdsl.xdef} 中的定义节点: + * 所有的 DSL (包括元模型自身)均由该模型定义 + */ + IXDefNode xdslDefNode; + + XDefKeys xdefKeys; + XDslKeys xdslKeys; + + XLangTagMeta(@NotNull XLangTag tag) { + this(tag, null); + } + + XLangTagMeta(@NotNull XLangTag tag, String errorMsg) { + this.tag = tag; + this.errorMsg = errorMsg; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + + XLangTagMeta that = (XLangTagMeta) o; + return tag.equals(that.tag) + && Objects.equals(errorMsg, that.errorMsg) + && Objects.equals(parent, that.parent) + && Objects.equals(defNodeInSchema, that.defNodeInSchema) + && Objects.equals(checkNsInSchema, that.checkNsInSchema) + && Objects.equals(defNodeInSelfSchema, that.defNodeInSelfSchema) + && Objects.equals(checkNsInSelfSchema, that.checkNsInSelfSchema) + && Objects.equals(xdslDefNode, that.xdslDefNode) + && Objects.equals(xdefKeys, that.xdefKeys) + && Objects.equals(xdslKeys, that.xdslKeys); + } + + public String getTagName() { + return tag.getName(); + } + + public String getErrorMsg() { + return this.errorMsg; + } + + public IXDefNode getDefNodeInSchema() { + return defNodeInSchema; + } + + public String getDefNodeSchemaPath() { + return XmlPsiHelper.getNopVfsPath(defNodeInSchema); + } + + public IXDefNode getDefNodeInSelfSchema() { + return defNodeInSelfSchema; + } + + public IXDefNode getXDslDefNode() { + return this.xdslDefNode; + } + + public XDefKeys getXdefKeys() { + return this.xdefKeys; + } + + public XDslKeys getXdslKeys() { + return this.xdslKeys; + } + + /** 当前标签是否存在解析异常 */ + public boolean hasError() { + return errorMsg != null; + } + + /** 当前标签是否在元模型 {@code *.xdef} 中 */ + public boolean isInAnySchema() { + return xdefKeys != null; + } + + /** 当前标签是否在元模型 {@code /nop/schema/xdef.xdef} 中 */ + public boolean isInXdefSchema() { + return checkNsInSelfSchema != null // + && checkNsInSelfSchema.contains(XDefKeys.DEFAULT.NS) // + && !XDefKeys.DEFAULT.equals(xdefKeys); + } + + /** 当前标签是否为 Xpl 类型节点,或者其对应元模型 {@code /nop/schema/xpl.xdef} 中的定义节点 */ + public boolean isXplNode() { + if (defNodeInSchema == null) { + return false; + } else if (XDefPsiHelper.isXplTypeNode(defNodeInSchema)) { + return true; + } + + String vfsPath = getDefNodeSchemaPath(); + if (isXplXdefFile(vfsPath)) { + return true; + } + + return isXlibSourceNode(); + } + + /** 当前标签是否为 xlib 的 {@code } 节点 */ + public boolean isXlibSourceNode() { + if (defNodeInSchema == null) { + return false; + } + + String vfsPath = getDefNodeSchemaPath(); + // xlib.xdef 中的 source 标签被设置为 xml 类型,是因为在获取 XplLib 模型的时候会根据 xlib.xdef 来解析, + // 但此时这个 source 段无法自动进行编译,必须结合它的 outputMode 和 attrs 配置等才能决定。 + // 因此,需将其子节点同样视为 Xpl 节点处理 + return XDslConstants.XDSL_SCHEMA_XLIB.equals(vfsPath) + && XlibConstants.SOURCE_NAME.equals(getTagName()) + && XDefConstants.STD_DOMAIN_XML.equals(XDefPsiHelper.getDefNodeType(defNodeInSchema)); + } + + /** 当前标签是否为 {@code } */ + public boolean isXDefUnknownTag() { + return xdefKeys.UNKNOWN_TAG.equals(getTagName()); + } + + /** + * 获取当前标签 {@link #getDefNodeInSchema()} 的 {@link XDefKeys#VALUE xdef:value} 定义, + * 即,其子节点(包括文本节点)对应的{@link XDefTypeDecl 类型} + */ + public XDefTypeDecl getXdefValue() { + IXDefNode defNode = getDefNodeInSchema(); + + return defNode != null ? defNode.getXdefValue() : null; + } + + /** 当前标签是否允许重复 */ + public boolean canBeMultipleTag() { + IXDefNode defNode = getDefNodeInSchema(); + + return (defNode != null && defNode.isAllowMultiple()) || isXplNode(); + } + + /** + * 获取当前标签上指定属性的{@link IXDefAttribute 属性定义} + *

+ * 其可识别 {@code xdef}、{@code x}、{@code xpl} 名字空间下已定义的属性 + */ + public IXDefAttribute getDefAttr(XLangAttribute attr) { + String attrName = attr.getName(); + if (hasError()) { + return getDefAttrOnNode(attr, null, attrName, null, xdefKeys, null); + } + + String ns = StringHelper.getNamespace(attrName); + boolean hasXDslNs = xdslKeys.NS.equals(ns); + + IXDefAttribute defAttr; + // 取 xdsl.xdef 中声明的属性 + if (hasXDslNs) { + defAttr = getXDslDefNodeAttr(attr); + } else { + defAttr = getDefNodeAttr(attr); + } + return defAttr; + } + + /** 获取当前标签的 {@link #getDefNodeInSchema()} 上指定属性的定义 */ + private IXDefAttribute getDefNodeAttr(XLangAttribute attr) { + String attrName = attr.getName(); + + // 在元元模型 /nop/schema/xdef.xdef 中,名字空间为 meta 的属性均由同名但以 xdef 为名字空间的属性定义, + // 而以 xdef 为名字空间的属性,则均由 meta:unknown-attr 定义。也即,二者形成交叉定义 + if (isInXdefSchema() && attrName.startsWith(XDefKeys.DEFAULT.NS + ':')) { + attrName = "*"; + } else if (isInAnySchema()) { + attrName = XLangTag.replaceXmlNs(attrName, xdefKeys.NS, XDefKeys.DEFAULT.NS); + } + + IXDefAttribute defAttr = getDefAttrOnNode(attr, + defNodeInSchema, + attrName, + attr.getValue(), + xdefKeys, + checkNsInSchema); + + if (defAttr == null || defAttr.isUnknownAttr()) { + XlibTagMeta xlibTag = tag.getXlibTagMeta(); + + if (xlibTag != null) { + return xlibTag.getAttribute(attrName); + } + } + return defAttr; + } + + /** 获取当前标签对应的 {@link #getXDslDefNode()} 上指定属性的定义 */ + private IXDefAttribute getXDslDefNodeAttr(XLangAttribute attr) { + String attrName = attr.getName(); + // Note: xdsl.xdef 的属性在固定的名字空间 x 中声明 + attrName = XLangTag.replaceXmlNs(attrName, xdslKeys.NS, XDslKeys.DEFAULT.NS); + + return getDefAttrOnNode(attr, xdslDefNode, attrName, null, xdefKeys, Set.of(XDslKeys.DEFAULT.NS)); + } + + /** 获取当前标签的说明文档 */ + public XLangDocumentation getTagDocumentation() { + if (hasError()) { + return null; + } + + IXDefNode defNode = null; + String tagName = tag.getName(); + String tagNs = StringHelper.getNamespace(tagName); + + // 单独处理元模型中的 xdef:define 节点:其由父节点记录定义信息 + if (xdefKeys != null && xdefKeys.DEFINE.equals(tagName)) { + String xdefName = tag.getAttributeValue(xdefKeys.NAME); + + if (xdefName != null && parent.defNodeInSelfSchema != null) { + defNode = ((XDefinition) parent.defNodeInSelfSchema).getXdefDefine(xdefName); + } + } + // 不在元元模型中的以 xdef 为名字空间的节点(不含 xdef:unknown-tag),显示其定义文档 + else if (!isInXdefSchema() // + && xdefKeys != null && xdefKeys.NS.equals(tagNs) // + && !xdefKeys.UNKNOWN_TAG.equals(tagName) // + ) { + defNode = defNodeInSchema; + } + // 元模型中的自定义节点,显示元模型中的文档 + else if (isInAnySchema() // + && !xdslKeys.NS.equals(tagNs) // + && !isXplNode() // + ) { + // Note: 可能会因元模型自身解析异常而无法得到节点定义 + defNode = defNodeInSelfSchema; + } + // 其余 dsl 均显示节点的定义文档 + else { + defNode = defNodeInSchema; + } + + if (defNode == null) { + return null; + } + + XlibTagMeta xlibTag = tag.getXlibTagMeta(); + if (xlibTag != null) { + return xlibTag.getDocumentation(); + } + + XLangDocumentation doc = new XLangDocumentation(defNode); + doc.setMainTitle(tagName); + + IXDefComment comment = defNode.getComment(); + if (comment != null) { + doc.setSubTitle(comment.getMainDisplayName()); + doc.setDesc(comment.getMainDescription()); + } + + return doc; + } + + /** 获取当前标签指定属性的说明文档 */ + public XLangDocumentation getAttrDocumentation(XLangAttribute attr) { + String attrName = attr.getName(); + String attrNs = StringHelper.getNamespace(attrName); + if ("xmlns".equals(attrNs) || hasError()) { + return null; + } + + String mainTitle = attrName; + String tagName = tag.getName(); + + IXDefNode defNode = null; + Set checkNsSet = checkNsInSchema; + // 单独处理元模型中的 xdef:define 节点:其由父节点记录定义信息 + if (xdefKeys != null && xdefKeys.DEFINE.equals(tagName) // + && !xdefKeys.NS.equals(attrNs) && !xdslKeys.NS.equals(attrNs) // + ) { + String xdefName = tag.getAttributeValue(xdefKeys.NAME); + + if (xdefName != null && parent.defNodeInSelfSchema != null) { + attrName = XLangTag.replaceXmlNs(attrName, xdefKeys.NS, XDefKeys.DEFAULT.NS); + defNode = ((XDefinition) parent.defNodeInSelfSchema).getXdefDefine(xdefName); + } + } + // 元元模型中 xdef 名字空间下的属性 + else if (isInXdefSchema() && XDefKeys.DEFAULT.NS.equals(attrNs)) { + defNode = defNodeInSelfSchema; + } + // x 名字空间下的属性 + else if (XDslKeys.DEFAULT.NS.equals(attrNs) || xdslKeys.NS.equals(attrNs)) { + attrName = XLangTag.replaceXmlNs(attrName, xdslKeys.NS, XDslKeys.DEFAULT.NS); + defNode = xdslDefNode; + checkNsSet = Set.of(XDslKeys.DEFAULT.NS); + } // + else { + attrName = XLangTag.replaceXmlNs(attrName, xdefKeys != null ? xdefKeys.NS : null, XDefKeys.DEFAULT.NS); + + // 对于元模型,优先取其自身定义节点上的属性文档 + defNode = defNodeInSelfSchema; + if (defNode == null || defNode.getAttribute(attrName) == null) { + defNode = defNodeInSchema; + } + } + + if (defNode == null) { + return null; + } + + IXDefAttribute defAttr = getDefAttrOnNode(attr, defNode, attrName, null, xdefKeys, checkNsSet); + if (XLangAttribute.isNullOrErrorDefAttr(defAttr)) { + return null; + } + + if (defAttr.isUnknownAttr()) { + XlibTagMeta xlibTag = tag.getXlibTagMeta(); + + if (xlibTag != null) { + return xlibTag.getAttrDocumentation(attrName); + } + } + + XLangDocumentation doc = new XLangDocumentation(defAttr); + doc.setMainTitle(mainTitle); + doc.setAdditional(defAttr instanceof XLangAttribute.XDefAttributeWithTypeAny); + + IXDefComment nodeComment = defNode.getComment(); + if (nodeComment != null) { + IXDefSubComment attrComment = nodeComment.getSubComments().get(attrName); + if (attrComment == null && defAttr.isUnknownAttr()) { + attrComment = nodeComment.getSubComments().get(XDefKeys.DEFAULT.UNKNOWN_ATTR); + } + + if (attrComment != null) { + doc.setSubTitle(attrComment.getDisplayName()); + doc.setDesc(attrComment.getDescription()); + } + } + + return doc; + } + + // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + + /** + * Note: 需要在 {@link io.nop.idea.plugin.resource.ProjectEnv#withProject ProjectEnv#withProject} + * 中调用该函数 + */ + public static XLangTagMeta create(XLangTag tag) { + XLangTag parentTag = tag.getParentTag(); + + // 根节点 + if (parentTag == null) { + return createForRootTag(tag); + } + return createForChildTag(tag, parentTag); + } + + protected static XLangTagMeta errorTag( + @NotNull XLangTag tag, @NotNull @PropertyKey(resourceBundle = BUNDLE) String msgKey, + Object @NotNull ... msgParams + ) { + return new XLangTagMeta(tag, NopPluginBundle.message(msgKey, msgParams)); + } + + private static XDefAttribute errorAttr( + @NotNull XLangAttribute attr, @NotNull @PropertyKey(resourceBundle = BUNDLE) String msgKey, + Object @NotNull ... msgParams + ) { + return new XLangAttribute.XDefAttributeWithError(attr.getName(), NopPluginBundle.message(msgKey, msgParams)); + } + + private static XLangTagMeta createForRootTag(XLangTag tag) { + String tagName = tag.getName(); + String tagNs = StringHelper.getNamespace(tagName); + + String xdslNs = XmlPsiHelper.getXmlnsForUrl(tag, XDslConstants.XDSL_SCHEMA_XDSL); + XDslKeys xdslKeys = XDslKeys.of(xdslNs); + + // Note: 若标签在 Xpl 脚本中,则将返回 /nop/schema/xpl.xdef + String schemaPath = XDefPsiHelper.getSchemaPath(tag); + if (schemaPath == null) { + return errorTag(tag, "xlang.parser.tag-meta.schema-not-specified", xdslKeys.SCHEMA, tagName); + } + + IXDefinition schema = XDefPsiHelper.loadSchema(schemaPath); + IXDefinition xdslSchema = XDefPsiHelper.getXDslDef(); + + // 若其元模型为 /nop/schema/xdef.xdef,则其自身也为元模型(*.xdef) + boolean inSchema = XDslConstants.XDSL_SCHEMA_XDEF.equals(schemaPath) // + || isXdefFile(tag.getContainingFile().getName()); + + // 非 xdef/xpl 模型,全部视为 xdsl + if (schema == null && !inSchema && !isXplXdefFile(schemaPath)) { + schema = xdslSchema; + } + // 若元模型加载失败,则不做识别 + if (schema == null) { + return errorTag(tag, "xlang.parser.tag-meta.schema-loading-failed", schemaPath); + } + + String xdefNs = XmlPsiHelper.getXmlnsForUrl(tag, XDslConstants.XDSL_SCHEMA_XDEF); + XDefKeys xdefKeys = XDefKeys.of(xdefNs); + + // Note: xdef 名字空间的根节点必须为 xdef.xdef 中已定义的节点 + if (inSchema && xdefKeys.NS.equals(tagNs)) { + if (getChildXdefDefNode(schema, tagName, xdefKeys) == null) { + return errorTag(tag, + "xlang.parser.tag-meta.ns-tag-not-defined-in-schema", + tagName, + XDefKeys.DEFAULT.NS, + XDslConstants.XDSL_SCHEMA_XDEF); + } + } + + IXDefNode defNodeInSchema = schema.getRootNode(); + // 如果其不在元模型中,则其根节点标签名必须与其 x:schema 所定义的根节点标签名保持一致, + // 除非根节点被定义为 xdef:unknown-tag + if (!inSchema && !defNodeInSchema.isUnknownTag() // + && !defNodeInSchema.getTagName().equals(tagName) // + ) { + return errorTag(tag, + "xlang.parser.tag-meta.root-tag-name-not-match-schema-root", + tagName, + defNodeInSchema.getTagName(), + schemaPath); + } + + // Note: 正在编辑中的 xdef 有可能是不完整的,此时将无法解析出 IXDefinition + IXDefinition selfSchema = null; + if (inSchema) { + String vfsPath = XmlPsiHelper.getNopVfsPath(tag); + if (vfsPath != null) { + selfSchema = XDefPsiHelper.loadSchema(vfsPath); + } + // 支持非标准的 vfs 资源,以适应单元测试等环境 + else { + selfSchema = XDefPsiHelper.loadSchema(tag.getContainingFile()); + } + } + + XLangTagMeta tagMeta = new XLangTagMeta(tag); + tagMeta.defNodeInSchema = defNodeInSchema; + tagMeta.checkNsInSchema = schema.getXdefCheckNs(); + + tagMeta.defNodeInSelfSchema = selfSchema != null ? selfSchema.getRootNode() : null; + tagMeta.checkNsInSelfSchema = inSchema + ? StringHelper.parseCsvSet(tag.getAttributeValue(xdefKeys.CHECK_NS)) + : null; + + tagMeta.xdslDefNode = xdslSchema.getRootNode(); + tagMeta.xdefKeys = inSchema ? xdefKeys : null; + tagMeta.xdslKeys = xdslKeys; + + return tagMeta; + } + + private static XLangTagMeta createForChildTag(XLangTag tag, XLangTag parentTag) { + String tagName = tag.getName(); + String tagNs = StringHelper.getNamespace(tagName); + + XLangTagMeta parentTagMeta = parentTag.getTagMeta(); + // 无定义节点 + if (parentTagMeta.hasError()) { + return errorTag(tag, "xlang.parser.tag-meta.parent-not-defined", parentTagMeta.getTagName()); + } + + XDefKeys xdefKeys = parentTagMeta.xdefKeys; + XDslKeys xdslKeys = parentTagMeta.xdslKeys; + + IXDefNode defNodeInSchema = null; + // 1. 获取确定的 xdsl 节点,如 等 + if (xdslKeys.NS.equals(tagNs)) { + String xdslTagName = XLangTag.replaceXmlNs(tagName, XDslKeys.DEFAULT.NS); + defNodeInSchema = getChildDefNode(parentTagMeta.xdslDefNode, xdslTagName); + + if (defNodeInSchema.isUnknownTag()) { + return errorTag(tag, + "xlang.parser.tag-meta.ns-tag-not-defined-in-schema", + tagName, + XDslKeys.DEFAULT.NS, + XDslConstants.XDSL_SCHEMA_XDSL); + } + } + + // 2. 若父节点为 Xpl 类型节点,则直接从 xpl.xdef 中获取节点(含带 xpl 名字空间的节点) + if (defNodeInSchema == null && parentTagMeta.isXplNode()) { + defNodeInSchema = getChildDefNode(XDefPsiHelper.getXplDef().getXdefUnknownTag(), tagName); + + if (XplConstants.XPL_NS.equals(tagNs) && defNodeInSchema.isUnknownTag()) { + return errorTag(tag, + "xlang.parser.tag-meta.ns-tag-not-defined-in-schema", + tagName, + XplConstants.XPL_NS, + XDslConstants.XDSL_SCHEMA_XPL); + } + } + + // 3. 在元元模型中,以 xdef 为名字空间的标签, 需以 meta:unknown-tag 作为其节点定义,即,交叉定义 + if (defNodeInSchema == null && parentTagMeta.isInXdefSchema() // + && XDefKeys.DEFAULT.NS.equals(tagNs) // + ) { + defNodeInSchema = parentTagMeta.defNodeInSchema.getXdefUnknownTag(); + } + + // 4. 获取确定的 xdef 节点,如 等 + // - *.xdef 必然始终以 /nop/schema/xdef.xdef 为元模型 + if (defNodeInSchema == null && parentTagMeta.isInAnySchema() // + && xdefKeys.NS.equals(tagNs) // + ) { + defNodeInSchema = getChildXdefDefNode(parentTagMeta.defNodeInSchema, tagName, xdefKeys); + if (defNodeInSchema == null) { + return errorTag(tag, + "xlang.parser.tag-meta.ns-tag-not-defined-in-schema", + tagName, + XDefKeys.DEFAULT.NS, + XDslConstants.XDSL_SCHEMA_XDEF); + } + } + + // 5. 从当前标签的父标签的定义节点中获取其定义节点 + if (defNodeInSchema == null) { + defNodeInSchema = getChildDefNode(parentTagMeta.defNodeInSchema, tagName); + } + + // 6. 带名字空间的节点需满足:其在元模型中已显式定义,或者其为无约束节点 + if (tagNs != null) { + // 需要校验的名字空间下的节点必须在其元模型中显式定义 + if (!parentTagMeta.isInAnySchema() // + && parentTagMeta.checkNsInSchema != null // + && parentTagMeta.checkNsInSchema.contains(tagNs) // + ) { + if (defNodeInSchema == null || defNodeInSchema.isUnknownTag()) { + String schemaPath = parentTagMeta.getDefNodeSchemaPath(); + return errorTag(tag, + "xlang.parser.tag-meta.ns-tag-should-be-defined-in-schema", + tagName, + tagNs, + schemaPath); + } + } + // 否则,其定义节点必然为 xdsl 的 xdef:unknown-tag 节点 + else if (defNodeInSchema == null) { + defNodeInSchema = parentTagMeta.xdslDefNode.getXdefUnknownTag(); + } + } + + if (defNodeInSchema == null) { + XDefTypeDecl xdefValue = parentTagMeta.getXdefValue(); + // Note: 对于 xjson、xml 等支持 xml 类型的节点,其子节点均未在元模型中定义, + // 故而,将其视为 xdsl 的 xdef:unknown-tag 节点 + if (xdefValue != null) { + if (xdefValue.isSupportBody(StdDomainRegistry.instance())) { + defNodeInSchema = parentTagMeta.xdslDefNode.getXdefUnknownTag(); + } else { + return errorTag(tag, + "xlang.parser.tag-meta.parent-not-allowed-to-have-child", + parentTagMeta.getTagName()); + } + } + } + + if (defNodeInSchema == null) { + return errorTag(tag, "xlang.parser.tag-meta.tag-not-defined", tagName); + } + + XLangTagMeta tagMeta = new XLangTagMeta(tag); + tagMeta.parent = parentTagMeta; + tagMeta.defNodeInSchema = defNodeInSchema; + tagMeta.checkNsInSchema = parentTagMeta.checkNsInSchema; + + tagMeta.defNodeInSelfSchema = getChildDefNode(parentTagMeta.defNodeInSelfSchema, tagName); + tagMeta.checkNsInSelfSchema = parentTagMeta.checkNsInSelfSchema; + + tagMeta.xdslDefNode = getChildDefNode(parentTagMeta.xdslDefNode, tagName); + tagMeta.xdefKeys = xdefKeys; + tagMeta.xdslKeys = xdslKeys; + + return tagMeta; + } + + /** 获取 {@link IXDefNode} 上指定属性的定义 */ + private static IXDefAttribute getDefAttrOnNode( + XLangAttribute attr, IXDefNode defNode, // + String attrName, String attrValue, // + XDefKeys xdefKeys, Set checkNsInSchema // + ) { + String xmlnsPrefix = "xmlns:"; + if (attrName.startsWith(xmlnsPrefix)) { + // 忽略 xmlns:biz="biz" 形式的属性 + if (attrName.equals(xmlnsPrefix + attrValue)) { + return null; + } + + XDefAttribute at = new XDefAttribute(); + at.setName(attrName); + at.setType(STD_DOMAIN_XDEF_REF); + + return at; + } + + if (defNode == null) { + return errorAttr(attr, "xlang.parser.tag-meta.attr-on-undefined-tag", attrName); + } + + IXDefAttribute defAttr = defNode.getAttribute(attrName); + if (defAttr != null) { + return defAttr; + } + + // 属性未显式定义 + String attrNameNs = StringHelper.getNamespace(attrName); + if (attrNameNs != null) { + if (checkNsInSchema == null || !checkNsInSchema.contains(attrNameNs)) { + // 对于 xdef:check-ns 中不做校验的名字空间,且不在元模型中的,其下的属性均为任意类型 + if (xdefKeys == null) { + return new XLangAttribute.XDefAttributeWithTypeAny(attrName); + } + } + // 对于 xdef:check-ns 中需校验的名字空间,其下属性必须显式定义 + else { + String schemaPath = XmlPsiHelper.getNopVfsPath(defNode); + return errorAttr(attr, + "xlang.parser.tag-meta.check-ns-attr-not-defined", + attrName, + attrNameNs, + schemaPath); + } + } + + // 在节点上定义了 xdef:unknown-attr + XDefTypeDecl xdefUnknownAttrType = defNode.getXdefUnknownAttr(); + if (xdefUnknownAttrType == null) { + return errorAttr(attr, "xlang.parser.tag-meta.attr-not-defined", attrName); + } + + // Note: 在 IXDefNode 中,对 xdef:unknown-attr 只记录了类型, + // 并没有 IXDefAttribute 实体(详见 XDefinitionParser#parseNode), + // 因此,需要单独构造 + XDefAttribute xdefUnknownAttr = new XDefAttribute() { + @Override + public boolean isUnknownAttr() { + return true; + } + }; + + String xdefUnknownAttrName = Objects.requireNonNullElse(xdefKeys, XDefKeys.DEFAULT).UNKNOWN_ATTR; + xdefUnknownAttr.setName(xdefUnknownAttrName); + xdefUnknownAttr.setType(xdefUnknownAttrType); + + // Note: 在需要时,通过节点位置再定位具体的属性位置 + SourceLocation loc = null; + // 在存在节点继承的情况下,选择最上层定义的同类型的 xdef:unknown-attr 属性 + IXDefNode refNode = defNode; + while (refNode != null) { + if (refNode.getXdefUnknownAttr() != xdefUnknownAttrType) { + break; + } + + loc = refNode.getLocation(); + refNode = refNode.getRefNode(); + } + xdefUnknownAttr.setLocation(loc); + + return xdefUnknownAttr; + } + + private static boolean isXdefFile(String filename) { + return filename.endsWith(".xdef"); + } + + private static boolean isXplXdefFile(String filename) { + return XDslConstants.XDSL_SCHEMA_XPL.equals(filename); + } + + /** 获得指定标签名的子定义节点,或者 {@code xdef:unknown-tag} 定义节点 */ + private static IXDefNode getChildDefNode(IXDefNode defNode, String tagName) { + return defNode != null ? defNode.getChild(tagName) : null; + } + + private static IXDefNode getChildXdefDefNode(IXDefNode defNode, String tagName, XDefKeys xdefKeys) { + String xdefTagName = XLangTag.replaceXmlNs(tagName, XDefKeys.DEFAULT.NS); + IXDefNode childDefNode = getChildDefNode(defNode, xdefTagName); + + if (childDefNode.isUnknownTag() && !xdefKeys.UNKNOWN_TAG.equals(tagName)) { + // 在名字空间 xx 对应的元模型 xdef.xdef 中未定义 xx:xx + childDefNode = null; + } + return childDefNode; + } +} diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangText.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangText.java index 69cef1d05c31b61cfdc0da91b62501cc507fbb06..d2fb060269bbbd3d59a7f6550b7623a3a1fbf36f 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangText.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangText.java @@ -59,6 +59,12 @@ public class XLangText extends XmlTextImpl { return getClass().getSimpleName() + ':' + getElementType(); } + @Override + public boolean skipValidation() { + // Note: 禁用 xml 的校验 + return true; + } + /** 获取文本内容(特殊符号已转义) */ public @NotNull String getTextChars() { PsiElement cdata = findPsiChildByType(XML_CDATA); diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangTextToken.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangTextToken.java index d8f0881f8b2c82f07f48e1a68ecf7b257cd347ad..9b887f2137e58776cb32422146b6d639343a0440 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangTextToken.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangTextToken.java @@ -51,6 +51,12 @@ public class XLangTextToken extends XmlTokenImpl { return getClass().getSimpleName() + ':' + getTokenType(); } + @Override + public boolean skipValidation() { + // Note: 禁用 xml 的校验 + return true; + } + public XLangTag getParentTag() { return PsiTreeUtil.getParentOfType(this, XLangTag.class); } @@ -63,7 +69,9 @@ public class XLangTextToken extends XmlTokenImpl { } String text = getText(); - XDefTypeDecl xdefValue = tag.getSchemaDefNodeXdefValue(); + XLangTagMeta tagMeta = tag.getTagMeta(); + + XDefTypeDecl xdefValue = tagMeta.getXdefValue(); if (xdefValue == null || StringHelper.isBlank(text)) { return PsiReference.EMPTY_ARRAY; } diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangValueToken.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangValueToken.java index 8ed940d13e14562a5eafcf9059082c4dfa8eba41..dfab68e7755ed36d208e88b9d8c3ab13384d95aa 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangValueToken.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangValueToken.java @@ -34,4 +34,10 @@ public class XLangValueToken extends XmlTokenImpl { public String toString() { return getClass().getSimpleName() + ':' + getTokenType(); } + + @Override + public boolean skipValidation() { + // Note: 禁用 xml 的校验 + return true; + } } diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangAttributeReference.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangAttributeReference.java index 67b1dba7f147af64185ccf69b08e72f62d049888..828c03f32f7f9e7c64e7e428ad7fb355da2dc73b 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangAttributeReference.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangAttributeReference.java @@ -23,6 +23,7 @@ import com.intellij.util.PlatformIcons; import io.nop.api.core.util.SourceLocation; import io.nop.idea.plugin.lang.psi.XLangAttribute; import io.nop.idea.plugin.lang.psi.XLangTag; +import io.nop.idea.plugin.lang.psi.XLangTagMeta; import io.nop.idea.plugin.lang.xlib.XlibTagMeta; import io.nop.idea.plugin.lang.xlib.XlibXDefAttribute; import io.nop.idea.plugin.utils.XmlPsiHelper; @@ -35,6 +36,8 @@ import io.nop.xlang.xdsl.XDslKeys; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import static io.nop.idea.plugin.lang.reference.XLangReferenceHelper.XLANG_NAME_COMPARATOR; + /** * 对 {@link XLangAttributeReference} 的引用识别:指向属性的定义位置 * @@ -51,10 +54,17 @@ public class XLangAttributeReference extends XLangReferenceBase { @Override public @Nullable PsiElement resolveInner() { - if (defAttr == null) { + if (XLangAttribute.isNullOrErrorDefAttr(defAttr)) { return null; } + // 对于带名字空间的附加属性,其定义直接引用其自身 + if (defAttr instanceof XLangAttribute.XDefAttributeWithTypeAny) { + String path = XmlPsiHelper.getNopVfsPath(myElement); + + return path == null ? null : new NopVirtualFile(myElement, path, (file) -> myElement); + } + String path = XmlPsiHelper.getNopVfsPath(defAttr); if (path == null) { return null; @@ -88,14 +98,16 @@ public class XLangAttributeReference extends XLangReferenceBase { } String attrNs = attr.getNamespacePrefix(); - XDefKeys xdefKeys = tag.getXDefKeys(); - XDslKeys xdslKeys = tag.getXDslKeys(); + XLangTagMeta tagMeta = tag.getTagMeta(); + + XDefKeys xdefKeys = tagMeta.getXdefKeys(); + XDslKeys xdslKeys = tagMeta.getXdslKeys(); // Note: 需支持处理 x/xdef 的名字空间非默认的情况 - boolean usedXDefNs = xdefKeys.NS.equals(attrNs); - boolean usedXDslNs = xdslKeys.NS.equals(attrNs); + boolean usedXDefNs = xdefKeys != null && xdefKeys.NS.equals(attrNs); + boolean usedXDslNs = xdslKeys != null && xdslKeys.NS.equals(attrNs); - IXDefNode xdslDefNode = tag.getXDslDefNode(); - IXDefNode tagDefNode = tag.getSchemaDefNode(); + IXDefNode xdslDefNode = tagMeta.getXDslDefNode(); + IXDefNode tagDefNode = tagMeta.getDefNodeInSchema(); if (usedXDslNs) { tagDefNode = xdslDefNode; } @@ -119,14 +131,18 @@ public class XLangAttributeReference extends XLangReferenceBase { addDefAttr(result, tag.getXlibTagMeta(), existAttrNames); return result.stream() // - .sorted((a, b) -> XLangReferenceHelper.XLANG_NAME_COMPARATOR.compare(a.name, b.name)) // + .sorted((a, b) -> XLANG_NAME_COMPARATOR.compare(a.name, b.name)) // .map((defAttr) -> { boolean trimNs = !attrNs.isEmpty(); String attrName = defAttr.name; if (!trimNs) { - attrName = XLangTag.changeNamespace(attrName, XDefKeys.DEFAULT.NS, xdefKeys.NS); - attrName = XLangTag.changeNamespace(attrName, XDslKeys.DEFAULT.NS, xdslKeys.NS); + attrName = XLangTag.replaceXmlNs(attrName, + XDefKeys.DEFAULT.NS, + xdefKeys != null ? xdefKeys.NS : null); + attrName = XLangTag.replaceXmlNs(attrName, + XDslKeys.DEFAULT.NS, + xdslKeys != null ? xdslKeys.NS : null); } return lookupAttr(attrName, defAttr.label, trimNs); @@ -135,8 +151,7 @@ public class XLangAttributeReference extends XLangReferenceBase { } private static void addDefAttr( - List list, IXDefNode defNode, String onlyNs, Set excludeNames - ) { + List list, IXDefNode defNode, String onlyNs, Set excludeNames) { for (IXDefAttribute defAttr : defNode.getAttributes().values()) { String attrName = defAttr.getName(); diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangDictOptionReference.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangDictOptionReference.java index 979ac28d6c7fa57dc398ea0305c09d1ecc1b1ef9..50ef7fb35b60cba82b34d9d150604d529f35b4cc 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangDictOptionReference.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangDictOptionReference.java @@ -22,6 +22,8 @@ import io.nop.idea.plugin.vfs.NopVirtualFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import static io.nop.idea.plugin.lang.reference.XLangReferenceHelper.XLANG_NAME_COMPARATOR; + /** * 对字典选项的引用 * @@ -101,8 +103,7 @@ public class XLangDictOptionReference extends XLangReferenceBase { .stream() .filter((opt) -> !opt.isInternal() && !opt.isDeprecated()) .map(LookupElementHelper::lookupDictOpt) - .sorted((a, b) -> XLangReferenceHelper.XLANG_NAME_COMPARATOR.compare(a.getLookupString(), - b.getLookupString())) + .sorted((a, b) -> XLANG_NAME_COMPARATOR.compare(a.getLookupString(), b.getLookupString())) .toArray(); } diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangParentTagAttrReference.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangParentTagAttrReference.java index 6cd8968f4b9d730fa7624532d8e78a0e9ea0fc8b..0aae2bd27525b7ebeca7d0d99e4512be82b859a5 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangParentTagAttrReference.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangParentTagAttrReference.java @@ -14,11 +14,14 @@ import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.xml.XmlElement; import io.nop.idea.plugin.lang.psi.XLangAttribute; import io.nop.idea.plugin.lang.psi.XLangTag; +import io.nop.idea.plugin.lang.psi.XLangTagMeta; import io.nop.idea.plugin.messages.NopPluginBundle; import io.nop.idea.plugin.utils.XmlPsiHelper; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import static io.nop.idea.plugin.lang.reference.XLangReferenceHelper.XLANG_NAME_COMPARATOR; + /** * 对父标签上的属性的引用 * @@ -56,7 +59,8 @@ public class XLangParentTagAttrReference extends XLangReferenceBase { } // 不能引用属性自身 else if (target == getParentAttr()) { - String msg = NopPluginBundle.message("xlang.annotation.reference.parent-tag-attr-self-referenced", attrName); + String msg = NopPluginBundle.message("xlang.annotation.reference.parent-tag-attr-self-referenced", + attrName); setUnresolvedMessage(msg); return null; @@ -65,6 +69,7 @@ public class XLangParentTagAttrReference extends XLangReferenceBase { return target; } + /** @return {@link #attrName} 所在标签上的可引用的属性名 */ @Override public Object @NotNull [] getVariants() { // Note: 在自动补全阶段,DSL 结构很可能是不完整的,只能从 xml 角度做分析 @@ -76,11 +81,12 @@ public class XLangParentTagAttrReference extends XLangReferenceBase { XLangAttribute attr = getParentAttr(); String attrName = attr != null ? attr.getName() : null; + XLangTagMeta tagMeta = tag.getTagMeta(); return XmlPsiHelper.getTagAttrNames(tag) // .stream() // - .filter(new XLangXdefKeyAttrReference.TagAttrNameFilter(tag)) // + .filter(new XLangXdefKeyAttrReference.TagAttrNameFilter(tagMeta)) // .filter((name) -> !name.equals(attrName)) // - .sorted(XLangReferenceHelper.XLANG_NAME_COMPARATOR) // + .sorted(XLANG_NAME_COMPARATOR) // .toArray(); } } diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangReferenceHelper.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangReferenceHelper.java index e0227a04b3e279ab7763fe1b6d06fca471465824..7f4c9dcf5399b68ed0f6314794662041fb32efe1 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangReferenceHelper.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangReferenceHelper.java @@ -84,8 +84,7 @@ public class XLangReferenceHelper { * @return 若返回 null,则表示未支持对指定类型的处理 */ public static PsiReference[] getReferencesByDefType( - XmlElement refElement, String refValue, XDefTypeDecl refDefType - ) { + XmlElement refElement, String refValue, XDefTypeDecl refDefType) { // Note: 计算引用源文本(XmlAttributeValue#getText 的结果包含引号)与引用值文本之间的文本偏移量, // 从而精确匹配与引用相关的文本内容 int textRangeOffset = refElement.getText().indexOf(refValue); @@ -97,7 +96,11 @@ public class XLangReferenceHelper { new PsiReference[] { new XLangStdDomainXdefRefReference(refElement, textRange, refValue) }; - case STD_DOMAIN_V_PATH, STD_DOMAIN_NAME_OR_V_PATH -> // + case STD_DOMAIN_NAME_OR_V_PATH -> // + refValue.indexOf('.') > 0 + ? getReferencesByVfsPath(refElement, refValue, textRange) + : PsiReference.EMPTY_ARRAY; + case STD_DOMAIN_V_PATH -> // getReferencesByVfsPath(refElement, refValue, textRange); case STD_DOMAIN_V_PATH_LIST -> // getReferencesFromVfsPathCsv(refElement, refValue, textRangeOffset); @@ -125,8 +128,7 @@ public class XLangReferenceHelper { /** 根据属性的类型定义识别引用 */ public static PsiReference[] getReferencesFromDefType( - XmlElement refElement, String refValue, String refDefTypeText - ) { + XmlElement refElement, String refValue, String refDefTypeText) { XDefTypeDecl refDefType; try { refDefType = XDslParseHelper.parseDefType(null, null, refDefTypeText); @@ -209,8 +211,7 @@ public class XLangReferenceHelper { /** 从 csv 文本中识别对 vfs 资源路径的引用 */ public static PsiReference[] getReferencesFromVfsPathCsv( - XmlElement refElement, String refValue, int textRangeOffset - ) { + XmlElement refElement, String refValue, int textRangeOffset) { Map rangeMap = extractValuesFromCsv(refValue); List list = new ArrayList<>(rangeMap.size()); @@ -240,15 +241,14 @@ public class XLangReferenceHelper { int textRangeOffset = refElement.getText().indexOf(refValue); TextRange textRange = new TextRange(0, refValue.length()).shiftRight(textRangeOffset); + if (!StringHelper.isValidFilePath(refValue) || refValue.lastIndexOf('.') <= 0) { + return PsiReference.EMPTY_ARRAY; + } return getReferencesByVfsPath(refElement, refValue, textRange); } /** 识别对 vfs 资源路径的引用 */ public static PsiReference[] getReferencesByVfsPath(XmlElement refElement, String path, TextRange textRange) { - if (!StringHelper.isValidFilePath(path) || path.lastIndexOf('.') <= 0) { - return PsiReference.EMPTY_ARRAY; - } - return new PsiReference[] { new NopVirtualFileReference(refElement, textRange, path) }; @@ -256,8 +256,7 @@ public class XLangReferenceHelper { /** 从 csv 文本中识别对 {@link IGenericType} 的引用 */ public static PsiReference[] getReferencesFromGenericTypeCsv( - XmlElement refElement, String refValue, int textRangeOffset - ) { + XmlElement refElement, String refValue, int textRangeOffset) { Map rangeMap = extractValuesFromCsv(refValue); List list = new ArrayList<>(rangeMap.size()); @@ -271,9 +270,7 @@ public class XLangReferenceHelper { return list.toArray(PsiReference[]::new); } - public static NopVirtualFile createNopVfsForDict( - PsiElement refElement, String dictName, Object dictOptionValue - ) { + public static NopVirtualFile createNopVfsForDict(PsiElement refElement, String dictName, Object dictOptionValue) { Function targetResolver = // (file) -> XmlPsiHelper.findFirstElement(file, (element) -> { if (element instanceof LeafPsiElement value // diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangStdDomainDictReference.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangStdDomainDictReference.java index ce36f37fa1d881b99325a0686972d950dba60ec7..fcf31fc15f061ac6587cfee616f372e5fc69a171 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangStdDomainDictReference.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangStdDomainDictReference.java @@ -45,14 +45,11 @@ public class XLangStdDomainDictReference extends XLangReferenceBase { return target; } + /** @return 项目内所有可访问的字典名 */ @Override public Object @NotNull [] getVariants() { Project project = myElement.getProject(); - return ProjectFileHelper.findAllDictNopVfsPaths(project) - .stream() - .map(ProjectFileHelper::getDictNameFromVfsPath) - .sorted(XLangReferenceHelper.XLANG_NAME_COMPARATOR) - .toArray(); + return ProjectFileHelper.getCachedNopDictNames(project).toArray(); } } diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangStdDomainReference.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangStdDomainReference.java index 4096b1ac83a9bec52c392ab58786643c95e8397b..bd39422ca2b11d1f75b3d9590913c44b749796b5 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangStdDomainReference.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangStdDomainReference.java @@ -24,6 +24,8 @@ import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import static io.nop.idea.plugin.lang.reference.XLangReferenceHelper.XLANG_NAME_COMPARATOR; + /** * 对标准数据域的引用 * @@ -84,7 +86,7 @@ public class XLangStdDomainReference extends XLangReferenceBase { return StreamEx.of(modifiers) // .filter((modifier) -> !text.contains(modifier.getStringValue())) // .append(XLangReferenceHelper.getRegisteredStdDomains().stream() // - .sorted(XLangReferenceHelper.XLANG_NAME_COMPARATOR) // + .sorted(XLANG_NAME_COMPARATOR) // .map((value) -> { DictBean dict = getDict(); DictOptionBean opt = dict != null diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangStdDomainXdefRefReference.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangStdDomainXdefRefReference.java index 121c6a91f85b78edef1da51871847848ecfd83eb..cafd182b8fe5d8a03a29f7751d8e3de48ac08ce9 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangStdDomainXdefRefReference.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangStdDomainXdefRefReference.java @@ -9,6 +9,7 @@ package io.nop.idea.plugin.lang.reference; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.function.Function; @@ -24,10 +25,11 @@ import io.nop.idea.plugin.messages.NopPluginBundle; import io.nop.idea.plugin.utils.ProjectFileHelper; import io.nop.idea.plugin.utils.XmlPsiHelper; import io.nop.idea.plugin.vfs.NopVirtualFile; -import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import static io.nop.idea.plugin.lang.reference.XLangReferenceHelper.XLANG_NAME_COMPARATOR; + /** * {@link io.nop.xlang.xdef.XDefConstants#STD_DOMAIN_XDEF_REF xdef-ref} 类型的值引用 * @@ -101,6 +103,7 @@ public class XLangStdDomainXdefRefReference extends XLangReferenceBase { return target; } + /** @return 当前 xdef 文件中可引用的 xdef:name 名字,或者项目内所有可访问的 *.xdef 资源路径 */ @Override public Object @NotNull [] getVariants() { // Note: 在自动补全阶段,DSL 结构很可能是不完整的,只能从 xml 角度做分析 @@ -120,14 +123,12 @@ public class XLangStdDomainXdefRefReference extends XLangReferenceBase { return true; }); - return StreamEx.of( // - names.stream().sorted(XLangReferenceHelper.XLANG_NAME_COMPARATOR) // - ) // - .append(ProjectFileHelper.findAllXdefNopVfsPaths(project) - .stream() - .sorted(XLangReferenceHelper.XLANG_NAME_COMPARATOR) // - ) // - .toArray(); + Collection xdefVfsPaths = ProjectFileHelper.getCachedNopXDefVfsPaths(project); + + names.sort(XLANG_NAME_COMPARATOR); + names.addAll(xdefVfsPaths); + + return names.toArray(); } private XmlAttribute getXdefNameAttr(XLangTag tag) { diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangTagReference.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangTagReference.java index 0f6b20aed3c60a74229882951bfad2359a047533..ce900c0132a1e6ce96ee199d2fd7b045ab0e041e 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangTagReference.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangTagReference.java @@ -19,15 +19,19 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import io.nop.api.core.util.SourceLocation; import io.nop.idea.plugin.lang.psi.XLangTag; +import io.nop.idea.plugin.lang.psi.XLangTagMeta; import io.nop.idea.plugin.utils.LookupElementHelper; import io.nop.idea.plugin.utils.XmlPsiHelper; import io.nop.idea.plugin.vfs.NopVirtualFile; import io.nop.xlang.xdef.IXDefComment; import io.nop.xlang.xdef.IXDefNode; +import io.nop.xlang.xdef.XDefKeys; import io.nop.xlang.xdsl.XDslKeys; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import static io.nop.idea.plugin.lang.reference.XLangReferenceHelper.XLANG_NAME_COMPARATOR; + /** * 对 {@link XLangTag} 的引用识别:指向节点的定义位置 * @@ -43,13 +47,14 @@ public class XLangTagReference extends XLangReferenceBase { @Override public @Nullable PsiElement resolveInner() { XLangTag tag = (XLangTag) myElement; - IXDefNode defNode = tag.getSchemaDefNode(); - String path = XmlPsiHelper.getNopVfsPath(defNode); + XLangTagMeta tagMeta = tag.getTagMeta(); + String path = tagMeta.getDefNodeSchemaPath(); if (path == null) { return null; } + IXDefNode defNode = tagMeta.getDefNodeInSchema(); SourceLocation loc = defNode.getLocation(); Function targetResolver = (file) -> XmlPsiHelper.getPsiElementAt(file, loc, @@ -58,20 +63,49 @@ public class XLangTagReference extends XLangReferenceBase { return new NopVirtualFile(myElement, path, targetResolver); } + /** @return 可替换 {@link #myElement} 的标签名(即,在元模型中其父标签所定义的子标签)列表 */ @Override public Object @NotNull [] getVariants() { // Note: 在自动补全阶段,DSL 结构很可能是不完整的,只能从 xml 角度做分析 XLangTag tag = (XLangTag) myElement; + String tagNs = tag.getNamespacePrefix(); + XLangTagMeta tagMeta = tag.getTagMeta(); + + XDefKeys tagXdefKeys = tagMeta.getXdefKeys(); + boolean trimLookupTagNs = !tagNs.isEmpty(); + XLangTag parentTag = tag.getParentTag(); if (parentTag == null) { - return LookupElement.EMPTY_ARRAY; + String fileName = tag.getContainingFile().getName(); + String tagName = fileName.contains(".") ? fileName.substring(0, fileName.indexOf('.')) : fileName; + + if (!tagMeta.isInAnySchema()) { + IXDefNode rootDefNode = tag.getRootTag().getTagMeta().getDefNodeInSchema(); + + return new LookupElement[] { + // 对于非元模型的根节点,其标签名只能为其 xdef 中的根节点名 + rootDefNode != null && !rootDefNode.isUnknownTag() // + ? lookupTag(rootDefNode, trimLookupTagNs) + // 若根节点被定义为 xdef:unknown-tag,则以文件名作为其标签名 + : lookupTag(tagName, null, trimLookupTagNs), + }; + } else { + return new LookupElement[] { + // 对于元模型的根节点,则一般以文件名作为其标签名 + lookupTag(tagName, null, trimLookupTagNs), + // 或者为 xdef:unknown-tag + lookupTag(tagXdefKeys != null ? tagXdefKeys.UNKNOWN_TAG : XDefKeys.DEFAULT.UNKNOWN_TAG, + null, + trimLookupTagNs), + }; + } } - String tagNs = tag.getNamespacePrefix(); boolean usedXDslNs = XDslKeys.DEFAULT.NS.equals(tagNs); - IXDefNode xdslDefNode = parentTag.getXDslDefNode(); - IXDefNode parentTagDefNode = parentTag.getSchemaDefNode(); + XLangTagMeta parentTagMeta = parentTag.getTagMeta(); + IXDefNode xdslDefNode = parentTagMeta.getXDslDefNode(); + IXDefNode parentTagDefNode = parentTagMeta.getDefNodeInSchema(); if (usedXDslNs) { parentTagDefNode = xdslDefNode; } @@ -90,15 +124,13 @@ public class XLangTagReference extends XLangReferenceBase { } return result.stream() - .sorted((a, b) -> XLangReferenceHelper.XLANG_NAME_COMPARATOR.compare(a.getTagName(), - b.getTagName())) - .map((defNode) -> lookupTag(defNode, !tagNs.isEmpty())) + .sorted((a, b) -> XLANG_NAME_COMPARATOR.compare(a.getTagName(), b.getTagName())) + .map((defNode) -> lookupTag(defNode, trimLookupTagNs)) .toArray(LookupElement[]::new); } private static void addChildDefNode( - List list, IXDefNode parentDefNode, String onlyNs, Set excludeNames - ) { + List list, IXDefNode parentDefNode, String onlyNs, Set excludeNames) { for (IXDefNode defNode : parentDefNode.getChildren().values()) { String tagName = defNode.getTagName(); @@ -125,10 +157,15 @@ public class XLangTagReference extends XLangReferenceBase { } String tagName = defNode.getTagName(); + + return lookupTag(tagName, label, trimNs); + } + + /** 注意,若当前标签已经包含完整的名字空间,则补全项必须移除其名字空间,否则,补全项的插入位置将会发生偏移 */ + private static LookupElement lookupTag(String tagName, String label, boolean trimNs) { if (trimNs) { tagName = tagName.substring(tagName.indexOf(':') + 1); } - return LookupElementHelper.lookupXmlTag(tagName, label); } } diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangXPrototypeReference.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangXPrototypeReference.java index 4f749e60ef5da4e775dd5002066f8cf8a1ca8c47..20db348965fd619428cc94932226f91e0c647b6c 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangXPrototypeReference.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangXPrototypeReference.java @@ -18,12 +18,15 @@ import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.xml.XmlElement; import io.nop.idea.plugin.lang.psi.XLangAttribute; import io.nop.idea.plugin.lang.psi.XLangTag; +import io.nop.idea.plugin.lang.psi.XLangTagMeta; import io.nop.idea.plugin.messages.NopPluginBundle; import io.nop.idea.plugin.utils.XmlPsiHelper; import io.nop.xlang.xdef.IXDefNode; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import static io.nop.idea.plugin.lang.reference.XLangReferenceHelper.XLANG_NAME_COMPARATOR; + /** * {@link io.nop.xlang.xdsl.XDslKeys#PROTOTYPE x:prototype} 的值引用 * @@ -88,6 +91,7 @@ public class XLangXPrototypeReference extends XLangReferenceBase { return getKeyAttrElement(protoTag, keyAttr); } + /** @return {@link io.nop.xlang.xdsl.XDslKeys#PROTOTYPE x:prototype} 可引用的值(即,在兄弟节点上指定的唯一属性值) */ @Override public Object @NotNull [] getVariants() { // Note: 在自动补全阶段,DSL 结构很可能是不完整的,只能从 xml 角度做分析 @@ -106,15 +110,18 @@ public class XLangXPrototypeReference extends XLangReferenceBase { .filter(Objects::nonNull) .map((child) -> child instanceof XLangAttribute attr ? attr.getValue() : child.getName()) .filter(Objects::nonNull) - .sorted(XLangReferenceHelper.XLANG_NAME_COMPARATOR) + .sorted(XLANG_NAME_COMPARATOR) .toArray(); } private String getKeyAttrName(XLangTag tag, XLangTag parentTag) { + XLangTagMeta tagMeta = tag.getTagMeta(); + XLangTagMeta parentTagMeta = parentTag.getTagMeta(); + // 仅从父节点中取引用到的子节点 // io.nop.xlang.delta.DeltaMerger#mergePrototype - IXDefNode defNode = tag.getSchemaDefNode(); - IXDefNode parentDefNode = parentTag.getSchemaDefNode(); + IXDefNode defNode = tagMeta.getDefNodeInSchema(); + IXDefNode parentDefNode = parentTagMeta.getDefNodeInSchema(); String keyAttr = parentDefNode.getXdefKeyAttr(); diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangXdefKeyAttrReference.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangXdefKeyAttrReference.java index 8294506e14e1f49dfb178663fd5a4449e0da1284..7e23af8f3f62326f5773dd6c79d8bab0cbf3e00a 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangXdefKeyAttrReference.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangXdefKeyAttrReference.java @@ -19,6 +19,7 @@ import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.xml.XmlElement; import io.nop.commons.util.StringHelper; import io.nop.idea.plugin.lang.psi.XLangTag; +import io.nop.idea.plugin.lang.psi.XLangTagMeta; import io.nop.idea.plugin.messages.NopPluginBundle; import io.nop.idea.plugin.utils.XmlPsiHelper; import io.nop.xlang.xdef.XDefKeys; @@ -26,6 +27,8 @@ import io.nop.xlang.xdsl.XDslKeys; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import static io.nop.idea.plugin.lang.reference.XLangReferenceHelper.XLANG_NAME_COMPARATOR; + /** * {@link io.nop.xlang.xdef.XDefKeys#KEY_ATTR xdef:key-attr} 的值引用 * @@ -68,6 +71,7 @@ public class XLangXdefKeyAttrReference extends XLangReferenceBase implements Psi .toArray(ResolveResult[]::new); } + /** 在子节点中均存在的可被 {@link io.nop.xlang.xdef.XDefKeys#KEY_ATTR xdef:key-attr} 引用的属性名列表 */ @Override public Object @NotNull [] getVariants() { // Note: 在自动补全阶段,DSL 结构很可能是不完整的,只能从 xml 角度做分析 @@ -76,28 +80,30 @@ public class XLangXdefKeyAttrReference extends XLangReferenceBase implements Psi return new Object[0]; } + XLangTagMeta tagMeta = tag.getTagMeta(); return XmlPsiHelper.getCommonAttrNamesFromChildTag(tag) // .stream() // - .filter(new TagAttrNameFilter(tag)) // - .sorted(XLangReferenceHelper.XLANG_NAME_COMPARATOR) // + .filter(new TagAttrNameFilter(tagMeta)) // + .sorted(XLANG_NAME_COMPARATOR) // .toArray(); } static class TagAttrNameFilter implements Predicate { - private final XLangTag refTag; + private final XLangTagMeta refTagMeta; - TagAttrNameFilter(XLangTag refTag) { - this.refTag = refTag; + TagAttrNameFilter(XLangTagMeta refTagMeta) { + this.refTagMeta = refTagMeta; } @Override public boolean test(String name) { - XDefKeys xdefKeys = refTag.getXDefKeys(); - XDslKeys xdslKeys = refTag.getXDslKeys(); + XDefKeys xdefKeys = refTagMeta.getXdefKeys(); + XDslKeys xdslKeys = refTagMeta.getXdslKeys(); String ns = StringHelper.getNamespace(name); - return !xdefKeys.NS.equals(ns) && !xdslKeys.NS.equals(ns); + return (xdefKeys == null || !xdefKeys.NS.equals(ns)) // + && (xdslKeys == null || !xdslKeys.NS.equals(ns)); } } } diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangXlibTagReference.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangXlibTagReference.java index 57ef83882d78d297bf15fdbce7f3e09b0aeba1a1..393b3c97254f70cb59becc04c2385b53ab837f08 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangXlibTagReference.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangXlibTagReference.java @@ -75,6 +75,7 @@ public class XLangXlibTagReference extends XLangReferenceBase { return target; } + /** @return {@link #xlibPath} 中定义的标签函数 */ @Override public Object @NotNull [] getVariants() { return XlibTagMeta.withLoadedXlib(myElement, xlibPath, (xlib) -> { diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/xlib/XlibTagMeta.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/xlib/XlibTagMeta.java index 952bc0b9815d2041852d79ba41b2e6f34bcfb405..141b7de3780b2a0664ea04d547f7566419f21533 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/xlib/XlibTagMeta.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/xlib/XlibTagMeta.java @@ -143,7 +143,7 @@ public class XlibTagMeta { public static XLangTag findXlibImportTag(XLangTag tag, String alias) { XLangTag parentTag = tag != null ? tag.getParentTag() : null; - if (parentTag == null || !parentTag.isXplDefNode()) { + if (parentTag == null || !parentTag.getTagMeta().isXplNode()) { return null; } diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/resource/ProjectDictProvider.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/resource/ProjectDictProvider.java index b39282fce436e7362005a140f732f89f19f708e7..317e864e74818f355af3c11b086185269aaeda22 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/resource/ProjectDictProvider.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/resource/ProjectDictProvider.java @@ -14,7 +14,6 @@ import com.intellij.lang.jvm.JvmEnumField; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiField; -import com.intellij.psi.search.GlobalSearchScope; import io.nop.api.core.annotations.core.Description; import io.nop.api.core.annotations.core.Label; import io.nop.api.core.annotations.core.Option; @@ -73,9 +72,7 @@ public class ProjectDictProvider implements IDictProvider { } // 从枚举类中得到字典信息 else if (dictName.indexOf('.') > 0 && StringHelper.isValidClassName(dictName)) { - GlobalSearchScope scope = GlobalSearchScope.allScope(project); - - PsiClass clazz = PsiClassHelper.findClass(project, dictName, scope); + PsiClass clazz = PsiClassHelper.findClass(project, dictName); if (clazz == null) { return null; } diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/resource/ProjectVirtualFileSystem.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/resource/ProjectVirtualFileSystem.java index 08f1eb6ffe44e0136a4f33400dea18e4d0b5a1fc..73d2baf2d0b5243b594a9a4c55b42eaedd49818b 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/resource/ProjectVirtualFileSystem.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/resource/ProjectVirtualFileSystem.java @@ -7,10 +7,16 @@ */ package io.nop.idea.plugin.resource; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + import com.intellij.openapi.project.Project; -import com.intellij.psi.PsiFile; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.search.FilenameIndex; -import com.intellij.psi.search.GlobalSearchScope; import io.nop.api.core.util.progress.IStepProgressListener; import io.nop.commons.util.StringHelper; import io.nop.core.resource.IResource; @@ -22,40 +28,32 @@ import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; - public class ProjectVirtualFileSystem implements IVirtualFileSystem { static final Logger LOG = LoggerFactory.getLogger(ProjectVirtualFileSystem.class); @Override public IResource getResource(String path, boolean returnNull) { Project project = ProjectEnv.currentProject(); - if (project == null) + if (project == null) { return returnNull ? null : new UnknownResource(path); + } String fileName = StringHelper.fileFullName(path); - PsiFile[] files = FilenameIndex.getFilesByName(project, fileName, GlobalSearchScope.allScope(project)); - if (files.length == 0) { + Collection files = // + FilenameIndex.getVirtualFilesByName(fileName, ProjectFileHelper.getSearchScope(project)); + if (files.isEmpty()) { LOG.debug("nop.project.file-index-not-found:{}", fileName); return returnNull ? null : new UnknownResource(path); } - if (path.startsWith("/dict/")) { - // 在模块中查找 - for (PsiFile file : files) { - String matchPath = ProjectFileHelper.getNopVfsPath(file.getVirtualFile()); - if (matchPath != null && matchPath.startsWith(path)) - return new VirtualFileResource(path, file.getVirtualFile()); - } - } else { - for (PsiFile file : files) { - String matchPath = ProjectFileHelper.getNopVfsPath(file.getVirtualFile()); - if (Objects.equals(path, matchPath)) - return new VirtualFileResource(path, file.getVirtualFile()); + boolean isDictPath = path.startsWith("/dict/"); + for (VirtualFile file : files) { + String matchPath = ProjectFileHelper.getNopVfsPath(file); + + if ((isDictPath && matchPath != null && matchPath.startsWith(path)) // + || (!isDictPath && Objects.equals(path, matchPath)) // + ) { + return new VirtualFileResource(path, file); } } @@ -98,7 +96,10 @@ public class ProjectVirtualFileSystem implements IVirtualFileSystem { } @Override - public String saveResource(String path, IResource iResource, IStepProgressListener iStepProgressListener, Map map) { + public String saveResource( + String path, IResource iResource, IStepProgressListener iStepProgressListener, + Map map + ) { return null; } } diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/XLangRootTagNameMacro.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/XLangRootTagNameMacro.java index 84c5b6b27eae600aefaadbe91b470668f45b4d78..c7d34410f8f5b53f033da93849ce750139381ccf 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/XLangRootTagNameMacro.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/XLangRootTagNameMacro.java @@ -32,6 +32,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** + * 根据 x:schema 或文件名得到 DSL 的根节点标签名 + * * @author flytreeleft * @date 2025-08-06 */ diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/XLangSchemaPathMacro.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/XLangSchemaPathMacro.java index ca9613f8c025011a1e4c388c2e6cde9c62297dcb..4b13a35f24a0fa7fecfa54a3140d7e2389a00d5b 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/XLangSchemaPathMacro.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/XLangSchemaPathMacro.java @@ -14,7 +14,6 @@ import com.intellij.codeInsight.template.ExpressionContext; import com.intellij.codeInsight.template.Macro; import com.intellij.codeInsight.template.Result; import com.intellij.codeInsight.template.TemplateContextType; -import com.intellij.openapi.project.Project; import io.nop.idea.plugin.utils.LookupElementHelper; import io.nop.idea.plugin.utils.ProjectFileHelper; import org.jetbrains.annotations.NonNls; @@ -22,6 +21,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** + * 获取项目内所有可访问的 xdef 资源路径 + * * @author flytreeleft * @date 2025-08-06 */ @@ -39,16 +40,10 @@ public class XLangSchemaPathMacro extends Macro { @Override public LookupElement @Nullable [] calculateLookupItems(Expression @NotNull [] params, ExpressionContext context) { - // Note: TODO 全项目搜索的性能极差,暂时不启用补全 -// Project project = context.getProject(); -// -// return ProjectFileHelper.findAllNopVfsPaths(project) -// .stream() -// .sorted() -// .filter(path -> path.endsWith(".xdef")) -// .map(LookupElementHelper::lookupString) -// .toArray(LookupElement[]::new); - return LookupElement.EMPTY_ARRAY; + return ProjectFileHelper.getCachedNopXDefVfsPaths(context.getProject()) + .stream() + .map(LookupElementHelper::lookupString) + .toArray(LookupElement[]::new); } @Override diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/ProjectFileHelper.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/ProjectFileHelper.java index d738e455502bb51d44dfcd7fa68361e4fd744a01..99b83d549892d08f3e737cc9c4712b408ecbf30a 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/ProjectFileHelper.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/ProjectFileHelper.java @@ -5,15 +5,17 @@ package io.nop.idea.plugin.utils; import java.io.File; import java.net.URL; import java.util.Collection; -import java.util.Collections; -import java.util.Objects; +import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import com.intellij.java.library.JavaLibraryModificationTracker; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.application.PluginPathManager; import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.vfs.JarFileSystem; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; @@ -23,6 +25,10 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.search.FilenameIndex; import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.ProjectAndLibrariesScope; +import com.intellij.psi.util.CachedValue; +import com.intellij.psi.util.CachedValueProvider; +import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.xml.XmlElement; import com.intellij.util.containers.CollectionFactory; @@ -35,9 +41,11 @@ import io.nop.commons.util.StringHelper; import io.nop.core.dict.DictProvider; import io.nop.core.resource.ResourceHelper; import io.nop.idea.plugin.resource.ProjectEnv; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class ProjectFileHelper { + private static final Map>> nopVfsPathCaches = new ConcurrentHashMap<>(); /** * 与FileHelper.getFileUrl格式保持一致 @@ -98,23 +106,21 @@ public class ProjectFileHelper { return ResourceHelper.getStdPath(path); } - /** 查找所有的 *.xdef 资源路径 */ - public static Collection findAllXdefNopVfsPaths(Project project) { - return FilenameIndex.getAllFilesByExt(project, "xdef") - .stream() - .map(ProjectFileHelper::getNopVfsStdPath) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); + public static @NotNull GlobalSearchScope getSearchScope(@NotNull Project project) { + return new ProjectAndLibrariesScope(project); } - /** 查找所有的 Nop 字典资源路径 */ - public static Collection findAllDictNopVfsPaths(Project project) { - return FilenameIndex.getAllFilesByExt(project, "dict.yaml") - .stream() - .map(ProjectFileHelper::getNopVfsStdPath) - .filter(Objects::nonNull) - .filter((path) -> path.startsWith("/dict/")) - .collect(Collectors.toSet()); + /** 获取已缓存的项目内所有可访问的 *.xdef 资源路径列表(已排序) */ + public static Collection getCachedNopXDefVfsPaths(Project project) { + return getCachedNopVfsPaths(project).stream().filter((path) -> path.endsWith(".xdef")).toList(); + } + + /** 获取已缓存的项目内所有可访问的字典名列表(已排序) */ + public static Collection getCachedNopDictNames(Project project) { + return getCachedNopVfsPaths(project).stream() + .filter((path) -> path.startsWith("/dict/") && path.endsWith(".dict.yaml")) + .map(ProjectFileHelper::getDictNameFromVfsPath) + .toList(); } /** 从 vfs 路径中获取字典名字 */ @@ -125,18 +131,28 @@ public class ProjectFileHelper { return name; } + /** 获取已缓存的项目内所有可访问的 vfs 资源路径列表(已排序) */ + public static Collection getCachedNopVfsPaths(Project project) { + return getCachedSearchResult(nopVfsPathCaches, + project, + (p) -> findAllNopVfsPaths(p).stream().sorted().toList()); + } + /** 查找所有 vfs 资源路径 */ public static Collection findAllNopVfsPaths(Project project) { + GlobalSearchScope scope = getSearchScope(project); + Set names = CollectionFactory.createSmallMemoryFootprintSet(); - Collections.addAll(names, FilenameIndex.getAllFilenames(project)); + FilenameIndex.processAllFileNames((name) -> { + names.add(name); + return true; + }, scope, null); Set vfsPaths = CollectionFactory.createSmallMemoryFootprintSet(); - - GlobalSearchScope scope = GlobalSearchScope.allScope(project); FilenameIndex.processFilesByNames(names, true, scope, null, (file) -> { - String vfsPath = getNopVfsPath(file); + String vfsPath = getNopVfsStdPath(file); - if (!file.isDirectory() && vfsPath != null) { + if (!file.isDirectory() && vfsPath != null && !vfsPath.equals("/")) { vfsPaths.add(vfsPath); } return true; @@ -145,6 +161,26 @@ public class ProjectFileHelper { return vfsPaths; } + /** 获得与 {@link Project} 相关的已缓存的查询结果 */ + public static T getCachedSearchResult( + Map> caches, // + Project project, Function searcher // + ) { + return caches.computeIfAbsent(project, (p) -> { + return CachedValuesManager.getManager(p) // + .createCachedValue(() -> { + Object[] deps = new Object[] { + JavaLibraryModificationTracker.getInstance(p), + ProjectRootManager.getInstance(p), + }; + + T result = searcher.apply(p); + + return CachedValueProvider.Result.create(result, deps); + }); + }).getValue(); + } + public static DictBean loadDict(PsiElement refElement, String dictName) { return ProjectEnv.withProject(refElement.getProject(), () -> DictProvider.instance().getDict(null, dictName, null, null)); @@ -192,8 +228,7 @@ public class ProjectFileHelper { return null; } - CharSequence str = document.getCharsSequence().subSequence(beginOffset, endOffset); - return str; + return document.getCharsSequence().subSequence(beginOffset, endOffset); } // copy from XsltBreakpointHandler diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/PsiClassHelper.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/PsiClassHelper.java index 246ba14deab60295be0f6764a8605ba6c5c77b6d..9c9f86d639f8ef57b782ea43453901d0d49daffe 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/PsiClassHelper.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/PsiClassHelper.java @@ -64,8 +64,7 @@ public class PsiClassHelper { // scope = module == null // ? GlobalSearchScope.allScope(project) // : module.getModuleWithDependenciesAndLibrariesScope(true); - // Note: DSL 可能定义在独立的项目中,因此,其可能引入非其所在模块依赖包中的 class - return GlobalSearchScope.allScope(project); + return getSearchScope(project); } }; @@ -96,12 +95,16 @@ public class PsiClassHelper { public static @NotNull GlobalSearchScope getSearchScope(@NotNull PsiElement element) { Project project = element.getProject(); - return javaClassRefProvider.getScope(project); + return getSearchScope(project); + } + + public static @NotNull GlobalSearchScope getSearchScope(@NotNull Project project) { + // Note: DSL 可能定义在独立的项目中,因此,其可能引入非其所在模块依赖包中的 class + return GlobalSearchScope.allScope(project); } public static PsiReference @NotNull [] createJavaClassReferences( - PsiElement element, String qualifiedName, int startInElement - ) { + PsiElement element, String qualifiedName, int startInElement) { JavaClassReferenceSet refSet = new JavaClassReferenceSet(qualifiedName, element, startInElement, @@ -112,8 +115,7 @@ public class PsiClassHelper { } public static PsiReference @NotNull [] createPackageReferences( - PsiElement element, String qualifiedName, int startInElement - ) { + PsiElement element, String qualifiedName, int startInElement) { PackageReferenceSet refSet = new PackageReferenceSet(qualifiedName, element, startInElement); return refSet.getPsiReferences(); @@ -223,15 +225,15 @@ public class PsiClassHelper { public static PsiClass findClass(PsiElement context, String className) { Project project = context.getProject(); - GlobalSearchScope scope = PsiClassHelper.getSearchScope(context); + GlobalSearchScope scope = getSearchScope(context); return findClass(project, className, scope); } public static PsiClass findClass(Project project, String className) { - GlobalSearchScope scope = GlobalSearchScope.allScope(project); + GlobalSearchScope scope = getSearchScope(project); - return JavaPsiFacade.getInstance(project).findClass(className, scope); + return findClass(project, className, scope); } public static PsiClass findClass(Project project, String className, GlobalSearchScope scope) { @@ -247,7 +249,7 @@ public class PsiClassHelper { /** 查找指定类的继承类 */ public static @NotNull Query findInheritors(Project project, String className) { - GlobalSearchScope scope = GlobalSearchScope.allScope(project); + GlobalSearchScope scope = getSearchScope(project); return findInheritors(project, className, scope); } @@ -255,7 +257,7 @@ public class PsiClassHelper { /** 查找指定类的继承类 */ public static @NotNull Query findInheritors(Project project, String className, GlobalSearchScope scope) { // 确保可找到基类 - GlobalSearchScope baseScope = GlobalSearchScope.allScope(project); + GlobalSearchScope baseScope = getSearchScope(project); PsiClass clazz = findClass(project, className, baseScope); if (clazz == null) { diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/XDefPsiHelper.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/XDefPsiHelper.java index 2ccea4ffe5c09765d25f84c1b15a344589e9acbc..d58f9015cf8cc01fa18498d932fb474db89f6df3 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/XDefPsiHelper.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/XDefPsiHelper.java @@ -11,7 +11,6 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.xml.XmlTag; import io.nop.commons.util.StringHelper; import io.nop.core.resource.IResource; -import io.nop.core.resource.impl.ClassPathResource; import io.nop.core.resource.impl.InMemoryTextResource; import io.nop.xlang.xdef.IXDefNode; import io.nop.xlang.xdef.IXDefinition; @@ -25,46 +24,23 @@ import org.slf4j.LoggerFactory; public class XDefPsiHelper { static final Logger LOG = LoggerFactory.getLogger(XDefPsiHelper.class); - private static IXDefinition xdefDef; - private static IXDefinition xdslDef; - private static IXDefinition xplDef; - - private static IXDefinition loadDef(String path) { - return new XDefinitionParser().parseFromResource(new ClassPathResource("classpath:/_vfs" + path)); - } - - public static synchronized IXDefinition getXdefDef() { - if (xdefDef == null) { - xdefDef = loadDef(XDslConstants.XDSL_SCHEMA_XDEF); - } - return xdefDef; - } - - public static synchronized IXDefinition getXDslDef() { - if (xdslDef == null) { - xdslDef = loadDef(XDslConstants.XDSL_SCHEMA_XDSL); - } - return xdslDef; + public static IXDefinition getXdefDef() { + return loadSchema(XDslConstants.XDSL_SCHEMA_XDEF); } - public static synchronized IXDefinition getXplDef() { - if (xplDef == null) { - xplDef = loadDef(XDslConstants.XDSL_SCHEMA_XPL); - } - return xplDef; + public static IXDefinition getXDslDef() { + return loadSchema(XDslConstants.XDSL_SCHEMA_XDSL); } - public static String getXDslNamespace(XmlTag tag) { - XmlTag rootTag = XmlPsiHelper.getRoot(tag); - String ns = XmlPsiHelper.getXmlnsForUrl(rootTag, XDslConstants.XDSL_SCHEMA_XDSL); - - if (ns == null) { - ns = XDslKeys.DEFAULT.NS; - } - return ns; + public static IXDefinition getXplDef() { + return loadSchema(XDslConstants.XDSL_SCHEMA_XPL); } - /** 从根节点获取 dsl 的元模型的 vfs 路径 */ + /** + * 从根节点获取 dsl 的元模型的 vfs 路径: + * 若节点所在文件为 *.xpl/*.xrun/*.xgen, + * 则其元模型为 Xpl,返回 {@link XDslConstants#XDSL_SCHEMA_XPL} + */ public static String getSchemaPath(XmlTag rootTag) { PsiFile file = rootTag.getContainingFile(); String fileExt = StringHelper.fileExt(file.getName()); @@ -72,14 +48,19 @@ public class XDefPsiHelper { return XDslConstants.XDSL_SCHEMA_XPL; } - String ns = getXDslNamespace(rootTag); - String key = ns + ":schema"; + String ns = XmlPsiHelper.getXmlnsForUrl(rootTag, XDslConstants.XDSL_SCHEMA_XDSL); + if (ns == null) { + ns = XDslKeys.DEFAULT.NS; + } + String key = ns + ":schema"; String schemaUrl = rootTag.getAttributeValue(key); + // Note: schema 可能为相对路径 return XmlPsiHelper.getNopVfsAbsolutePath(schemaUrl, rootTag); } + /** Note: 已加载且自身及其依赖未变更的模型,将被缓存起来,不会重复解析 */ public static IXDefinition loadSchema(String schemaUrl) { try { return SchemaLoader.loadXDefinition(schemaUrl); @@ -89,6 +70,7 @@ public class XDefPsiHelper { } } + /** Note: 直接解析内容,且不缓存解析结果 */ public static IXDefinition loadSchema(PsiFile file) { String content = file.getText(); // Note: 解析过程中,会检查路径的有效性,需保证以 / 开头,并添加 .xdef 后缀 @@ -102,7 +84,7 @@ public class XDefPsiHelper { } } - public static boolean isXplDefNode(IXDefNode defNode) { + public static boolean isXplTypeNode(IXDefNode defNode) { String stdDomain = getDefNodeType(defNode); return stdDomain != null && (stdDomain.equals("xpl") || stdDomain.startsWith("xpl-")); diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/XmlPsiHelper.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/XmlPsiHelper.java index e2719c7fe59515362f197a1a736605a3cabacd2a..f83decc9c70cb8026b5ddfcb574b4bbc0a4fe215 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/XmlPsiHelper.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/XmlPsiHelper.java @@ -88,9 +88,10 @@ public class XmlPsiHelper { } public static List findPsiFileList(Project project, String path) { + GlobalSearchScope scope = ProjectFileHelper.getSearchScope(project); String fileName = StringHelper.fileFullName(path); - Collection vfList = FilenameIndex.getVirtualFilesByName(fileName, - GlobalSearchScope.allScope(project)); + + Collection vfList = FilenameIndex.getVirtualFilesByName(fileName, scope); if (vfList.isEmpty()) { return Collections.emptyList(); } @@ -127,7 +128,14 @@ public class XmlPsiHelper { return null; } - int offset = document.getLineStartOffset(line - 1) + column - 1; + int offset; + try { + offset = document.getLineStartOffset(line - 1) + column - 1; + } catch (IndexOutOfBoundsException e) { + // Note: 对于 _delta 文件,其可能不包含目标元素 + return null; + } + return psiFile.findElementAt(offset); } @@ -260,8 +268,7 @@ public class XmlPsiHelper { /** 找到第一个符合条件的 {@link PsiElement 元素} */ public static T findFirstElement( - PsiElement element, Predicate condition - ) { + PsiElement element, Predicate condition) { PsiElement[] result = new PsiElement[] { null }; PsiTreeUtil.processElements(element, el -> { @@ -284,8 +291,8 @@ public class XmlPsiHelper { } while (true); } - public static String getXmlnsForUrl(XmlTag tag, String url) { - for (XmlAttribute attr : tag.getAttributes()) { + public static String getXmlnsForUrl(XmlTag rootTag, String url) { + for (XmlAttribute attr : rootTag.getAttributes()) { if (url.equals(attr.getValue())) { String name = attr.getName(); if (name.startsWith("xmlns:")) { diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/vfs/NopVirtualFile.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/vfs/NopVirtualFile.java index 12b8cafab902445a37cf1903e5535b11871145a1..1fdd06433e34950fb953c1ad95101109f79de10c 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/vfs/NopVirtualFile.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/vfs/NopVirtualFile.java @@ -1,12 +1,12 @@ package io.nop.idea.plugin.vfs; -import java.util.Arrays; import java.util.Map; import java.util.Objects; import java.util.function.Function; import javax.swing.*; import com.intellij.codeInsight.navigation.PsiTargetNavigator; +import com.intellij.ide.util.EditSourceUtil; import com.intellij.lang.ASTNode; import com.intellij.lang.Language; import com.intellij.navigation.ItemPresentation; @@ -15,6 +15,7 @@ import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileTypes.PlainTextLanguage; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; +import com.intellij.pom.Navigatable; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiNamedElement; @@ -69,6 +70,7 @@ public class NopVirtualFile extends PsiElementBase implements PsiNamedElement { return true; } + /** 支持 ctrl + 点击 方式跳转到具体文件 */ @Override public void navigate(boolean requestFocus) { Editor editor = FileEditorManager.getInstance(getProject()).getSelectedTextEditor(); @@ -76,9 +78,20 @@ public class NopVirtualFile extends PsiElementBase implements PsiNamedElement { return; } - // 支持 ctrl + 点击 方式跳转到具体文件 - PsiTargetNavigator navigator = new PsiTargetNavigator<>(() -> Arrays.asList(getChildren())); - navigator.navigate(editor, null); + PsiElement[] children = getChildren(); + // Note: 在 2025 版本中,PsiTargetNavigator 不能对单个元素做跳转,原因未知, + // 故而,单独通过 Navigatable 做元素跳转 + if (children.length == 1) { + PsiElement child = children[0]; + Navigatable nav = EditSourceUtil.getDescriptor(child); + + if (nav != null) { + nav.navigate(true); + } + } else if (children.length > 1) { + PsiTargetNavigator navigator = new PsiTargetNavigator<>(children); + navigator.navigate(editor, null); + } } /** ctrl + 鼠标移动 所显示的元素信息 */ diff --git a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/vfs/NopVirtualFileReference.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/vfs/NopVirtualFileReference.java index f485bcbde5c9d9d185f39b7034a65b02c9296e91..5c1775cca2d8a82b1151b4368a33492036dd3541 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/vfs/NopVirtualFileReference.java +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/vfs/NopVirtualFileReference.java @@ -44,10 +44,11 @@ public class NopVirtualFileReference extends XLangReferenceBase { return target; } + /** @return 项目内所有可访问的 vfs 资源路径 */ @Override public Object @NotNull [] getVariants() { Project project = myElement.getProject(); - return ProjectFileHelper.findAllNopVfsPaths(project).stream().sorted().toArray(); + return ProjectFileHelper.getCachedNopVfsPaths(project).toArray(); } } diff --git a/nop-dev-tools/nop-idea-plugin/src/main/resources/messages/NopPluginBundle.properties b/nop-dev-tools/nop-idea-plugin/src/main/resources/messages/NopPluginBundle.properties index 943976c3849f5674ed117a83fa8b9cced8ae802c..d97793de2caf8e67755d8aed05cc87ddbba31aa1 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/resources/messages/NopPluginBundle.properties +++ b/nop-dev-tools/nop-idea-plugin/src/main/resources/messages/NopPluginBundle.properties @@ -5,10 +5,9 @@ action.error.no-xlang-file-extension = XLang DSL file must have extension name, xlang.annotation.attr.not-defined = Undefined attribute ''{0}'' xlang.annotation.attr.value-required = Attribute ''{0}'' can''t have empty value -xlang.annotation.tag.not-defined = Undefined tag ''{0}'' xlang.annotation.tag.value-not-allowed = Tag ''{0}'' can''t have any value -xlang.annotation.tag.child-not-allowed = Tag ''{0}'' can''t have any child xlang.annotation.tag.body-required = Tag ''{0}'' body can''t be empty +xlang.annotation.tag.multiple-tag-not-allowed = Tag ''{0}'' can''t be multiple xlang.annotation.reference.vfs-file-not-found = Vfs file ''{0}'' doesn''t exist xlang.annotation.reference.dict-not-found = Dict or enum ''{0}'' doesn''t exist xlang.annotation.reference.dict-yaml-not-found = Dict yaml file ''{0}'' doesn''t exist @@ -24,7 +23,6 @@ xlang.annotation.reference.x-prototype-tag-not-found = No sibling node <{0}/> ex xlang.annotation.reference.x-prototype-attr-not-found = No sibling node [{0}="{1}"] exists xlang.annotation.reference.x-prototype-tag-self-referenced = Node <{0}/> is self-referenced xlang.annotation.reference.x-prototype-attr-self-referenced = Node [{0}="{1}"] is self-referenced -xlang.annotation.reference.attr-xdef-not-defined = Undefined attribute ''{0}'' xlang.annotation.reference.std-domain-not-registered = Std-domain ''{0}'' isn''t listed in {1} xlang.annotation.reference.xdef-name-class-not-found = Java class ''{0}'' isn''t created or generated xlang.annotation.reference.enum-not-found = Java class ''{0}'' doesn''t exist @@ -37,9 +35,23 @@ xlang.doc.flag.required = [Required] xlang.doc.flag.option = [Option] xlang.doc.flag.internal = [Internal] xlang.doc.flag.allow-cp-expr = [#{var}] +xlang.doc.flag.additional = (Additional Attribute) xlang.doc.markdown.link-title = '{'Link'}'{0} xlang.doc.markdown.image-title = '{'Image'}'{0} xlang.debugger.line.breakpoints.tab.title = XLang Line Breakpoints xlang.debugger.not-exist = Current module doesn't depend on 'nop-xlang-debugger', please add dependency 'io.github.entropy-cloud:nop-xlang-debugger' to pom.xml or build.gradle.kts with 'test' scope xlang.debugger.connect-fail = Can''t connect to XLangDebugger on port {0} + +xlang.parser.tag-meta.schema-not-specified = No schema path is specified in the attribute ''{0}'' of the root tag ''{1}'' +xlang.parser.tag-meta.schema-loading-failed = The schema ''{0}'' is loading failed +xlang.parser.tag-meta.ns-tag-not-defined-in-schema = The tag ''{0}'' isn''t defined in schema ''{2}'' (corresponding namespace ''{1}'') +xlang.parser.tag-meta.root-tag-name-not-match-schema-root = The root tag ''{0}'' doesn''t match with the root tag ''{1}'' in schema ''{2}'' +xlang.parser.tag-meta.ns-tag-should-be-defined-in-schema = The tag ''{0}'' with namespace ''{1}'' should be defined in schema ''{2}'' +xlang.parser.tag-meta.parent-not-allowed-to-have-child = The parent tag ''{0}'' can''t have any child +xlang.parser.tag-meta.parent-not-defined = The parent tag ''{0}'' isn''t defined +xlang.parser.tag-meta.tag-not-defined = The tag ''{0}'' isn''t defined +xlang.parser.tag-meta.attr-on-undefined-tag = The attribute ''{0}'' is defined on undefined tag +xlang.parser.tag-meta.attr-not-defined = The attribute ''{0}'' isn''t defined +xlang.parser.tag-meta.check-ns-attr-not-defined = The attribute ''{0}'' with namespace ''{1}'' should be defined in schema ''{2}'' +xlang.parser.tag-meta.creating-failed = Failed to create tag meta for tag ''{0}'' diff --git a/nop-dev-tools/nop-idea-plugin/src/main/resources/messages/NopPluginBundle_zh.properties b/nop-dev-tools/nop-idea-plugin/src/main/resources/messages/NopPluginBundle_zh.properties index 306feadc2d43b288f17d8ce9c61b95365d6d04bf..934b42da3e469b1350a8282ae74685570cae5d0f 100644 --- a/nop-dev-tools/nop-idea-plugin/src/main/resources/messages/NopPluginBundle_zh.properties +++ b/nop-dev-tools/nop-idea-plugin/src/main/resources/messages/NopPluginBundle_zh.properties @@ -5,10 +5,9 @@ action.error.no-xlang-file-extension = XLang DSL \u6587\u4EF6\u5FC5\u987B\u6307\ xlang.annotation.attr.not-defined = \u5C5E\u6027 ''{0}'' \u672A\u5B9A\u4E49 xlang.annotation.attr.value-required = \u5C5E\u6027 ''{0}'' \u7684\u503C\u4E0D\u5141\u8BB8\u4E3A\u7A7A -xlang.annotation.tag.not-defined = \u6807\u7B7E ''{0}'' \u672A\u5B9A\u4E49 xlang.annotation.tag.value-not-allowed = \u6807\u7B7E ''{0}'' \u4E0D\u5141\u8BB8\u5305\u542B\u5185\u5BB9 -xlang.annotation.tag.child-not-allowed = \u6807\u7B7E ''{0}'' \u4E0D\u5141\u8BB8\u5305\u542B\u5B50\u8282\u70B9 xlang.annotation.tag.body-required = \u6807\u7B7E ''{0}'' \u4E0D\u5141\u8BB8\u4E3A\u7A7A +xlang.annotation.tag.multiple-tag-not-allowed = \u6807\u7B7E ''{0}'' \u4E0D\u5141\u8BB8\u91CD\u590D xlang.annotation.reference.vfs-file-not-found = vfs \u6587\u4EF6 ''{0}'' \u4E0D\u5B58\u5728 xlang.annotation.reference.dict-not-found = \u5B57\u5178\u6216\u679A\u4E3E\u7C7B ''{0}'' \u4E0D\u5B58\u5728 xlang.annotation.reference.dict-yaml-not-found = \u5B57\u5178\u6587\u4EF6 ''{0}'' \u4E0D\u5B58\u5728 @@ -24,7 +23,6 @@ xlang.annotation.reference.x-prototype-tag-not-found = \u4E0D\u5B58\u5728\u5144\ xlang.annotation.reference.x-prototype-attr-not-found = \u4E0D\u5B58\u5728\u5144\u5F1F\u8282\u70B9 [{0}="{1}"] xlang.annotation.reference.x-prototype-tag-self-referenced = \u5F53\u524D\u8282\u70B9 <{0}/> \u51FA\u73B0\u81EA\u5F15\u7528 xlang.annotation.reference.x-prototype-attr-self-referenced = \u5F53\u524D\u8282\u70B9 [{0}="{1}"] \u51FA\u73B0\u81EA\u5F15\u7528 -xlang.annotation.reference.attr-xdef-not-defined = \u5C5E\u6027 ''{0}'' \u672A\u5B9A\u4E49 xlang.annotation.reference.std-domain-not-registered = \u6570\u636E\u57DF ''{0}'' \u4E0D\u5728\u53EF\u9009\u5217\u8868 {1} \u4E2D xlang.annotation.reference.xdef-name-class-not-found = \u8FD8\u672A\u521B\u5EFA\u6216\u751F\u6210 Java \u7C7B ''{0}'' xlang.annotation.reference.enum-not-found = Java \u7C7B ''{0}'' \u4E0D\u5B58\u5728 @@ -37,9 +35,23 @@ xlang.doc.flag.required = [\u5FC5\u586B] xlang.doc.flag.option = [\u53EF\u9009] xlang.doc.flag.internal = [\u5185\u90E8] xlang.doc.flag.allow-cp-expr = [#{var}] +xlang.doc.flag.additional = (\u9644\u52A0\u5C5E\u6027) xlang.doc.markdown.link-title = '{'\u94FE\u63A5'}'{0} xlang.doc.markdown.image-title = '{'\u56FE\u7247'}'{0} xlang.debugger.line.breakpoints.tab.title = XLang \u884C\u65AD\u70B9 xlang.debugger.not-exist = \u5F53\u524D\u6A21\u5757\u672A\u4F9D\u8D56 'nop-xlang-debugger'\uFF0C\u8BF7\u6DFB\u52A0\u4F9D\u8D56 'io.github.entropy-cloud:nop-xlang-debugger' \u5230 pom.xml \u6216 build.gradle.kts \u6587\u4EF6\u4E2D\uFF0C\u5E76\u5C06 scope \u8BBE\u7F6E\u4E3A test xlang.debugger.connect-fail = \u65E0\u6CD5\u4E0E XLangDebugger \u7684\u670D\u52A1\u7AEF\u53E3 {0} \u5EFA\u7ACB\u8FDE\u63A5 + +xlang.parser.tag-meta.schema-not-specified = \u672A\u5728\u6839\u6807\u7B7E ''{1}'' \u7684\u5C5E\u6027 ''{0}'' \u4E0A\u6307\u5B9A\u5143\u6A21\u578B\u8DEF\u5F84 +xlang.parser.tag-meta.schema-loading-failed = \u5143\u6A21\u578B ''{0}'' \u52A0\u8F7D\u5931\u8D25 +xlang.parser.tag-meta.ns-tag-not-defined-in-schema = \u6807\u7B7E ''{0}'' \u672A\u5728\u5143\u6A21\u578B ''{2}'' \u4E2D\u5B9A\u4E49\uFF08\u5BF9\u5E94\u7684\u540D\u5B57\u7A7A\u95F4\u4E3A ''{1}''\uFF09 +xlang.parser.tag-meta.root-tag-name-not-match-schema-root = \u6839\u6807\u7B7E ''{0}'' \u4E0E\u5143\u6A21\u578B ''{2}'' \u7684\u6839\u6807\u7B7E ''{1}'' \u540D\u5B57\u4E0D\u4E00\u81F4 +xlang.parser.tag-meta.ns-tag-should-be-defined-in-schema = \u540D\u5B57\u7A7A\u95F4 ''{1}'' \u4E0B\u7684\u6807\u7B7E ''{0}'' \u9700\u5728\u5143\u6A21\u578B ''{2}'' \u4E2D\u663E\u5F0F\u5B9A\u4E49 +xlang.parser.tag-meta.parent-not-allowed-to-have-child = \u7236\u6807\u7B7E ''{0}'' \u4E0D\u5141\u8BB8\u5B58\u5728\u5B50\u6807\u7B7E +xlang.parser.tag-meta.parent-not-defined = \u7236\u6807\u7B7E ''{0}'' \u672A\u5B9A\u4E49 +xlang.parser.tag-meta.tag-not-defined = \u6807\u7B7E ''{0}'' \u672A\u5B9A\u4E49 +xlang.parser.tag-meta.attr-on-undefined-tag = \u5C5E\u6027 ''{0}'' \u6240\u5728\u6807\u7B7E\u672A\u5B9A\u4E49 +xlang.parser.tag-meta.attr-not-defined = \u5C5E\u6027 ''{0}'' \u672A\u5B9A\u4E49 +xlang.parser.tag-meta.check-ns-attr-not-defined = \u540D\u5B57\u7A7A\u95F4 ''{1}'' \u4E0B\u7684\u5C5E\u6027 ''{0}'' \u9700\u5728\u5143\u6A21\u578B ''{2}'' \u4E2D\u663E\u5F0F\u5B9A\u4E49 +xlang.parser.tag-meta.creating-failed = \u6807\u7B7E ''{0}'' \u7684\u8282\u70B9\u5B9A\u4E49\u4FE1\u606F\u521B\u5EFA\u5931\u8D25 diff --git a/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/BaseXLangPluginTestCase.java b/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/BaseXLangPluginTestCase.java index 7144f58c40c4725e5329d19060d665e25f13489a..eea9bac0a8786ca32dd14774dda61bd5c1b32396 100644 --- a/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/BaseXLangPluginTestCase.java +++ b/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/BaseXLangPluginTestCase.java @@ -138,7 +138,8 @@ public abstract class BaseXLangPluginTestCase extends LightJavaCodeInsightFixtur } protected PsiFile configureByXLangText(String text) { - return myFixture.configureByText("unit." + XLANG_EXT, text); + String fileName = "unit-" + StringHelper.randomDigits(8) + '.' + XLANG_EXT; + return myFixture.configureByText(fileName, text); } protected void addAllNopXDefsToProject() { @@ -218,7 +219,7 @@ public abstract class BaseXLangPluginTestCase extends LightJavaCodeInsightFixtur * 得到的是原始元素的引用元素 */ protected PsiElement getOriginalElementAtCaret() { - doAssertCaretExists(); + assertCaretExists(); return myFixture.getFile().findElementAt(myFixture.getCaretOffset()); } @@ -229,7 +230,7 @@ public abstract class BaseXLangPluginTestCase extends LightJavaCodeInsightFixtur /** 找到光标位置的 {@link XLangReference} 或者其他类型的唯一引用 */ protected PsiReference findReferenceAtCaret() { - doAssertCaretExists(); + assertCaretExists(); // 实际有多个引用时,将构造返回 PsiMultiReference, // 其会按 PsiMultiReference#COMPARATOR 对引用排序得到优先引用, @@ -252,7 +253,13 @@ public abstract class BaseXLangPluginTestCase extends LightJavaCodeInsightFixtur // 消除异常 "Read access is allowed from inside read-action" PsiElement originalElement = getOriginalElementAtCaret(); - PsiElement resolvedElement = getResolvedElementAtCaret(); + PsiElement resolvedElement; + // Note: 当 位置的元素没有引用元素时,会直接抛出异常,这里需要屏蔽该异常 + try { + resolvedElement = getResolvedElementAtCaret(); + } catch (Throwable ignore) { + resolvedElement = originalElement; + } Language lang = originalElement.getContainingFile().getLanguage(); DocumentationProvider docProvider = LanguageDocumentation.INSTANCE.forLanguage(lang); @@ -260,7 +267,7 @@ public abstract class BaseXLangPluginTestCase extends LightJavaCodeInsightFixtur return docProvider.generateDoc(resolvedElement, originalElement); } - private void doAssertCaretExists() { + protected void assertCaretExists() { assertTrue("No '' found in current text", myFixture.getCaretOffset() > 0); } @@ -269,12 +276,15 @@ public abstract class BaseXLangPluginTestCase extends LightJavaCodeInsightFixtur doAssertCompletion(null, expectedText); } - /** 检查选中指定的补全项之后的文本是否与预期相符 */ + /** + * 检查选中指定的补全项之后的文本是否与预期相符 + *

+ * 注意:

+ */ protected void doAssertCompletion(String selectedItem, String expectedText) { - // Note: - // - 在仅有唯一的补全元素时,将自动完成补全,且不能再获取到补全列表 - // - 只有在调用 `myFixture.completeBasic()` 后,才能完成补全,获得补全列表 - // 获取当前查找元素 LookupImpl lookup = (LookupImpl) LookupManager.getActiveLookup(myFixture.getEditor()); assertNotNull("Lookup not active", lookup); diff --git a/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/doc/TestXLangDocumentationProvider.java b/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/doc/TestXLangDocumentationProvider.java index 62047e4bbb6e1fb0a70125f00645a084ed6e2d05..91f16e34ebc076611f73c20ac21e251e45487785 100644 --- a/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/doc/TestXLangDocumentationProvider.java +++ b/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/doc/TestXLangDocumentationProvider.java @@ -41,7 +41,11 @@ public class TestXLangDocumentationProvider extends BaseXLangPluginTestCase { assertDoc(insertCaretIntoVfs("/nop/schema/xdef.xdef", // "ine meta:name"), // - TestCase::assertNull // + (doc) -> { + assertTrue(doc.contains("meta:define")); + assertTrue(doc.contains("def-type")); + assertFalse(doc.contains("/nop/schema/xdef.xdef")); + } // ); assertDoc(insertCaretIntoVfs("/nop/schema/xdef.xdef", // " { assertTrue(doc.contains("Any child node")); assertFalse(doc.contains("/test/doc/example.xdef")); + assertFalse(doc.contains("/nop/schema/xdef.xdef")); + } // + ); + assertDoc(insertCaretIntoVfs("/test/doc/example.xdef", // + "ef:define"), // + (doc) -> { + assertTrue(doc.contains("Common Node Definition")); + assertTrue(doc.contains("string")); + assertFalse(doc.contains("/test/doc/example.xdef")); + assertFalse(doc.contains("/nop/schema/xdef.xdef")); } // ); assertDoc(insertCaretIntoVfs("/test/doc/example.xdef", // @@ -171,6 +186,46 @@ public class TestXLangDocumentationProvider extends BaseXLangPluginTestCase { assertTrue(doc.contains("/test/reference/a.xlib")); } // ); + + // xpl 节点 + assertDoc(""" + + + ipt xpl:dump="true"/> + + + """, // + (doc) -> { + assertTrue(doc.contains("XPL标签")); + assertTrue(doc.contains("/nop/schema/xpl.xdef")); + } // + ); + + // 在 xdef 中的 x/xpl 节点 + assertDoc(""" + + tends> + + + + """, // + (doc) -> { + assertTrue(doc.contains("通过x:gen-extends来动态生成父模型")); + assertTrue(doc.contains("/nop/schema/xdsl.xdef")); + } // + ); + assertDoc(""" + + + ipt xpl:dump="true"/> + + + """, // + (doc) -> { + assertTrue(doc.contains("XPL标签")); + assertTrue(doc.contains("/nop/schema/xpl.xdef")); + } // + ); } public void testGenerateDocForAttribute() { @@ -229,6 +284,14 @@ public class TestXLangDocumentationProvider extends BaseXLangPluginTestCase { assertTrue(doc.contains("/nop/schema/xdef.xdef")); } // ); + assertDoc(insertCaretIntoVfs("/nop/schema/xdef.xdef", // + "meta:value=\"def-type\"", // + "meta:value=\"def-type\""), // + (doc) -> { + assertTrue(doc.contains("body的数据类型")); + assertFalse(doc.contains("/nop/schema/xdef.xdef")); + } // + ); // *.xdef 中,xdef/x/xpl 名字空间的属性,始终显示其定义的文档 assertDoc(insertCaretIntoVfs("/nop/schema/xdef.xdef", // @@ -300,6 +363,31 @@ public class TestXLangDocumentationProvider extends BaseXLangPluginTestCase { } // ); + assertDoc(insertCaretIntoVfs("/test/doc/example.xdef", // + "me=\"!string\""), // + (doc) -> { + assertTrue(doc.contains("This is node name")); + assertFalse(doc.contains("/test/doc/example.xdef")); + assertFalse(doc.contains("/nop/schema/xdef.xdef")); + } // + ); + assertDoc(insertCaretIntoVfs("/test/doc/example.xdef", // + "xdef:name=\"Common\"", // + "xdef:name=\"Common\""), // + (doc) -> { + assertTrue(doc.contains("注册为xdef片段")); + assertTrue(doc.contains("/nop/schema/xdef.xdef")); + } // + ); + assertDoc(insertCaretIntoVfs("/test/doc/example.xdef", // + "x:prototype-override=\"merge\"", // + "x:prototype-override=\"merge\""), // + (doc) -> { + assertTrue(doc.contains("对应的合并算子")); + assertTrue(doc.contains("/nop/schema/xdsl.xdef")); + } // + ); assertDoc(insertCaretIntoVfs("/test/doc/example.xdef", // "", // "-attr=\"any\"/>"), // @@ -447,6 +535,22 @@ public class TestXLangDocumentationProvider extends BaseXLangPluginTestCase { assertTrue(doc.contains("/test/reference/a.xlib")); } // ); + + // 未定义 xdef/x 名字空间下的属性 + assertDoc(""" + + e="int"/> + + """, // + TestCase::assertNull // + ); + assertDoc(""" + + e="22"/> + + """, // + TestCase::assertNull // + ); } public void testGenerateDocForXmlAttributeValue() { diff --git a/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangCompletions.java b/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangCompletions.java index 46a7f8a71c03f847d031d0b3bdd58d029d13b71e..4a4c5bf144a6f17f1edd05045afc556b0cf1507c 100644 --- a/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangCompletions.java +++ b/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangCompletions.java @@ -10,7 +10,8 @@ package io.nop.idea.plugin.lang; import java.util.Arrays; import io.nop.idea.plugin.BaseXLangPluginTestCase; -import io.nop.idea.plugin.lang.reference.XLangReferenceHelper; + +import static io.nop.idea.plugin.lang.reference.XLangReferenceHelper.XLANG_NAME_COMPARATOR; public class TestXLangCompletions extends BaseXLangPluginTestCase { @@ -26,7 +27,7 @@ public class TestXLangCompletions extends BaseXLangPluginTestCase { "value", // }; - Arrays.sort(names, XLangReferenceHelper.XLANG_NAME_COMPARATOR); + Arrays.sort(names, XLANG_NAME_COMPARATOR); assertEquals(String.join(", ", new String[] { "name", // @@ -600,29 +601,29 @@ public class TestXLangCompletions extends BaseXLangPluginTestCase { """); -// // 对 vfs 的补全:TODO 暂不支持对文本节点的补全 -// assertCompletion("/nop/schema/xdsl.xdef", // -// """ -// -// /nop/schema/ -// -// """, // -// """ -// -// /nop/schema/xdsl.xdef -// -// """); -// assertCompletion("/test/doc/example.xdef", // -// """ -// -// /nop/schema/xdsl.xdef,/test/doc -// -// """, // -// """ -// -// /nop/schema/xdsl.xdef,/test/doc/example.xdef -// -// """); + // 对 vfs 的补全 + assertCompletion("/nop/schema/xdsl.xdef", // + """ + + /nop/schema/ + + """, // + """ + + /nop/schema/xdsl.xdef + + """); + assertCompletion("/nop/schema/xui/xview.xdef", // + """ + + /nop/schema/xdsl.xdef,/nop/schema/ + + """, // + """ + + /nop/schema/xdsl.xdef,/nop/schema/xui/xview.xdef + + """); // 对 boolean 的补全 assertCompletion("false", // diff --git a/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangParser.java b/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangParser.java index b83363c42ddd6c13593f5f45a77864b01c519842..eb94deda90d9173b5cc964cec29d13717395c8db 100644 --- a/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangParser.java +++ b/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangParser.java @@ -41,6 +41,6 @@ public class TestXLangParser extends BaseXLangPluginTestCase { protected void assertASTTree(String code, String expectedAstFile) { PsiFile testFile = configureByXLangText(code); - doAssertASTTree(testFile, expectedAstFile); + doAssertASTTree(testFile.getFirstChild()/* 跳过文件名 */, expectedAstFile); } } diff --git a/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangReferences.java b/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangReferences.java index c97cde4361977fcbb48e2c26518f99ed24e02bb4..b13d16fd078c36d705dbea65c69cdd4f1bd9b563 100644 --- a/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangReferences.java +++ b/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangReferences.java @@ -748,7 +748,7 @@ public class TestXLangReferences extends BaseXLangPluginTestCase { assertReference(""" - + """, // @@ -816,10 +816,10 @@ public class TestXLangReferences extends BaseXLangPluginTestCase { "meta:unique-attr=\"xdef:name\""), // "xdef:define#xdef:name=!var-name" // ); - assertReference(insertCaretIntoVfs("/nop/schema/xmeta.xdef", // - "xdef:key-attr=\"id\"", // - "xdef:key-attr=\"id\""), // - "selection#id=!var-name" // + assertReference(insertCaretIntoVfs("/nop/schema/conf.xdef", // + "xdef:key-attr=\"name\"", // + "xdef:key-attr=\"name\""), // + "var#name=!conf-name" // ); // - 引用不存在 assertReference(""" diff --git a/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangRename.java b/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangRename.java index 92ce2698969e538de846dd48ae9b2197ceb85328..9575c776d793fb08a97cdbc36be708f96207a435 100644 --- a/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangRename.java +++ b/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangRename.java @@ -10,6 +10,8 @@ package io.nop.idea.plugin.lang; import io.nop.idea.plugin.BaseXLangPluginTestCase; /** + * TODO 对 xlib 函数及参数名的重命名支持 + * * @author flytreeleft * @date 2025-07-23 */ @@ -21,7 +23,7 @@ public class TestXLangRename extends BaseXLangPluginTestCase { public void testRenameTag() { } - public void testRenameXlibTag() { + public void _testRenameXlibTag() { // 从定义侧更名 assertRename("NewCall", // """ @@ -79,7 +81,7 @@ public class TestXLangRename extends BaseXLangPluginTestCase { ); } - public void testRenameXlibAttr() { + public void _testRenameXlibAttr() { // 从定义侧更名 assertRename("newArg", // """ diff --git a/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangTagMeta.java b/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangTagMeta.java new file mode 100644 index 0000000000000000000000000000000000000000..20d9ff61c99f17b185b61a87a34fe97472bb00f7 --- /dev/null +++ b/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangTagMeta.java @@ -0,0 +1,1073 @@ +/* + * Copyright (c) 2017-2025 Nop Platform. All rights reserved. + * Author: canonical_entropy@163.com + * Blog: https://www.zhihu.com/people/canonical-entropy + * Gitee: https://gitee.com/canonical-entropy/nop-entropy + * Github: https://github.com/entropy-cloud/nop-entropy + */ + +package io.nop.idea.plugin.lang; + +import java.util.function.BiConsumer; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import io.nop.idea.plugin.BaseXLangPluginTestCase; +import io.nop.idea.plugin.lang.psi.XLangAttribute; +import io.nop.idea.plugin.lang.psi.XLangTag; +import io.nop.idea.plugin.lang.psi.XLangTagMeta; +import io.nop.idea.plugin.utils.XmlPsiHelper; +import io.nop.xlang.xdef.IXDefAttribute; +import io.nop.xlang.xdef.IXDefNode; + +/** + * + * @author flytreeleft + * @date 2025-10-22 + */ +public class TestXLangTagMeta extends BaseXLangPluginTestCase { + + public void testCreateTagMeta() { + // xdef.xdef + assertTagMeta(""" + known-tag + xmlns:x="/nop/schema/xdsl.xdef" xmlns:meta="/nop/schema/xdef.xdef" + x:schema="/nop/schema/xdef.xdef" meta:check-ns="xdef" + > + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertTrue(tagMeta.isInXdefSchema()); + + assertEquals("meta:unknown-tag", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + + assertNotNull(tagMeta.getDefNodeInSelfSchema()); + assertTrue(tagMeta.getDefNodeInSelfSchema().isUnknownTag()); + assertNull(selfDefNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + known-tag + xmlns:x="/nop/schema/xdsl.xdef" + x:schema="/nop/schema/xdef.xdef" + > + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("meta:unknown-tag", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + + assertNotNull(tagMeta.getDefNodeInSelfSchema()); + assertEquals(tagMeta.getTagName(), tagMeta.getDefNodeInSelfSchema().getTagName()); + assertNull(selfDefNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + fine> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertTrue(tagMeta.isInXdefSchema()); + + assertEquals("meta:define", tagMeta.getTagName()); + assertEquals("xdef:define", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + + assertNull(tagMeta.getDefNodeInSelfSchema()); + } // + ); + assertTagMeta(""" + + -parse meta:value="xpl"/> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertTrue(tagMeta.isInXdefSchema()); + + assertEquals("xdef:pre-parse", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + + assertNotNull(tagMeta.getDefNodeInSelfSchema()); + assertEquals(tagMeta.getTagName(), tagMeta.getDefNodeInSelfSchema().getTagName()); + assertNull(selfDefNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + -parse/> + + """, // + (tag, tagMeta) -> { + assertTrue(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertTrue(tagMeta.isInXdefSchema()); + + assertEquals("meta:pre-parse", tagMeta.getTagName()); + assertEquals("xdef:pre-parse", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + + assertNull(tagMeta.getDefNodeInSelfSchema()); + } // + ); + assertTagMeta(""" + + fine/> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertTrue(tagMeta.isInXdefSchema()); + + assertEquals("meta:define", tagMeta.getTagName()); + assertEquals("xdef:define", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + + assertNull(tagMeta.getDefNodeInSelfSchema()); + } // + ); + assertTagMeta(""" + + + + n-tag meta:ref="XDefNode"/> + + + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertTrue(tagMeta.isInXdefSchema()); + + assertEquals("xdef:unknown-tag", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + + assertNotNull(tagMeta.getDefNodeInSelfSchema()); + assertEquals(tagMeta.getTagName(), tagMeta.getDefNodeInSelfSchema().getTagName()); + assertNull(selfDefNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + en-extends/> + + """, // + (tag, tagMeta) -> { + assertTrue(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertTrue(tagMeta.isInXdefSchema()); + + assertEquals("x:gen-extends", tagMeta.getTagName()); + assertEquals("x:gen-extends", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/nop/schema/xdsl.xdef", defNodeVfsPath(tagMeta)); + + assertNull(tagMeta.getDefNodeInSelfSchema()); + } // + ); + // - xpl node + assertTagMeta(""" + + + ipt /> + + + """, // + (tag, tagMeta) -> { + assertTrue(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertTrue(tagMeta.isInXdefSchema()); + + assertEquals("c:script", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xpl.xdef", defNodeVfsPath(tagMeta)); + + assertNull(tagMeta.getDefNodeInSelfSchema()); + } // + ); + assertTagMeta(""" + + + ipt /> + + + """, // + (tag, tagMeta) -> { + assertTrue(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertTrue(tagMeta.isInXdefSchema()); + + assertEquals("c:script", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xpl.xdef", defNodeVfsPath(tagMeta)); + + assertNull(tagMeta.getDefNodeInSelfSchema()); + } // + ); + + // xdsl.xdef + assertTagMeta(""" + wn-tag + xmlns:xdef="/nop/schema/xdef.xdef" xmlns:xdsl="/nop/schema/xdsl.xdef" + xdsl:schema="/nop/schema/xdef.xdef" + > + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("xdef:unknown-tag", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + + assertNotNull(tagMeta.getDefNodeInSelfSchema()); + assertTrue(tagMeta.getDefNodeInSelfSchema().isUnknownTag()); + assertNull(selfDefNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + tends xdef:value="xpl-node"/> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("x:gen-extends", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + + assertNotNull(tagMeta.getDefNodeInSelfSchema()); + assertEquals(tagMeta.getTagName(), tagMeta.getDefNodeInSelfSchema().getTagName()); + assertNull(selfDefNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + tends/> + + """, // + (tag, tagMeta) -> { + assertTrue(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("xdsl:gen-extends", tagMeta.getTagName()); + assertEquals("x:gen-extends", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/nop/schema/xdsl.xdef", defNodeVfsPath(tagMeta)); + + assertNull(tagMeta.getDefNodeInSelfSchema()); + } // + ); + assertTagMeta(""" + + + per xdef:internal="true"/> + + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("x:super", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + + assertNotNull(tagMeta.getDefNodeInSelfSchema()); + assertEquals(tagMeta.getTagName(), tagMeta.getDefNodeInSelfSchema().getTagName()); + assertNull(selfDefNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + + per/> + + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("xdsl:super", tagMeta.getTagName()); + assertEquals("x:super", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/nop/schema/xdsl.xdef", defNodeVfsPath(tagMeta)); + + assertNull(tagMeta.getDefNodeInSelfSchema()); + } // + ); + // - xpl node + assertTagMeta(""" + + + ipt /> + + + """, // + (tag, tagMeta) -> { + assertTrue(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("c:script", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xpl.xdef", defNodeVfsPath(tagMeta)); + + assertNull(tagMeta.getDefNodeInSelfSchema()); + } // + ); + + // Normal xdef + assertTagMeta(""" + mple + xmlns:x="/nop/schema/xdsl.xdef" xmlns:xdef="/nop/schema/xdef.xdef" + x:schema="/nop/schema/xdef.xdef" + > + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("example", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + + assertNotNull(tagMeta.getDefNodeInSelfSchema()); + assertEquals(tagMeta.getTagName(), tagMeta.getDefNodeInSelfSchema().getTagName()); + assertNull(selfDefNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + st-parse> + + + """, // + (tag, tagMeta) -> { + assertTrue(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("xdef:post-parse", tagMeta.getTagName()); + assertEquals("xdef:post-parse", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + + assertNull(tagMeta.getDefNodeInSelfSchema()); + } // + ); + assertTagMeta(""" + + n-extends> + + + """, // + (tag, tagMeta) -> { + assertTrue(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("x:gen-extends", tagMeta.getTagName()); + assertEquals("x:gen-extends", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/nop/schema/xdsl.xdef", defNodeVfsPath(tagMeta)); + + assertNull(tagMeta.getDefNodeInSelfSchema()); + } // + ); + assertTagMeta(""" + + fs xdef:value="v-path-list"/> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("refs", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + + assertNotNull(tagMeta.getDefNodeInSelfSchema()); + assertEquals(tagMeta.getTagName(), tagMeta.getDefNodeInSelfSchema().getTagName()); + assertNull(selfDefNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + nown-tag xdef:unknown-attr="any"/> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("xdef:unknown-tag", tagMeta.getTagName()); + assertEquals("xdef:unknown-tag", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + + assertNotNull(tagMeta.getDefNodeInSelfSchema()); + assertTrue(tagMeta.getDefNodeInSelfSchema().isUnknownTag()); + assertNull(selfDefNodeVfsPath(tagMeta)); + } // + ); + // - xpl node + assertTagMeta(""" + + + ipt /> + + + """, // + (tag, tagMeta) -> { + assertTrue(tagMeta.isXplNode()); + assertTrue(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("c:script", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xpl.xdef", defNodeVfsPath(tagMeta)); + + assertNull(tagMeta.getDefNodeInSelfSchema()); + } // + ); + + // Normal DSL + assertTagMeta(""" + mple + xmlns:x="/nop/schema/xdsl.xdef" + x:schema="/test/lang/lang.xdef" + > + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("example", tagMeta.getTagName()); + assertEquals("example", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/test/lang/lang.xdef", defNodeVfsPath(tagMeta)); + + assertNull(tagMeta.getDefNodeInSelfSchema()); + } // + ); + assertTagMeta(""" + + ld name="Child" type="leaf" abc="abc"/> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("child", tagMeta.getTagName()); + assertEquals("child", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/test/lang/lang.xdef", defNodeVfsPath(tagMeta)); + + assertNull(tagMeta.getDefNodeInSelfSchema()); + } // + ); + assertTagMeta(""" + + known name="abc"/> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("unknown", tagMeta.getTagName()); + assertNull(tagMeta.getDefNodeInSchema()); + + assertNull(tagMeta.getDefNodeInSelfSchema()); + } // + ); + // - xdsl node + assertTagMeta(""" + + n-extends/> + + """, // + (tag, tagMeta) -> { + assertTrue(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("x:gen-extends", tagMeta.getTagName()); + assertEquals("x:gen-extends", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/nop/schema/xdsl.xdef", defNodeVfsPath(tagMeta)); + + assertNull(tagMeta.getDefNodeInSelfSchema()); + } // + ); + // - xpl node + assertTagMeta(""" + + + ipt /> + + + """, // + (tag, tagMeta) -> { + assertTrue(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("c:script", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xpl.xdef", defNodeVfsPath(tagMeta)); + + assertNull(tagMeta.getDefNodeInSelfSchema()); + } // + ); + // - undefined namespace node + assertTagMeta(""" + + yle/> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("yui:style", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xdsl.xdef", defNodeVfsPath(tagMeta)); + + assertNull(tagMeta.getDefNodeInSelfSchema()); + } // + ); + assertTagMeta(""" + + yle/> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("xui:style", tagMeta.getTagName()); + assertNull(tagMeta.getDefNodeInSchema()); + + assertNull(tagMeta.getDefNodeInSelfSchema()); + } // + ); + + assertTagMeta(""" + + + ame>Tom + 23 + + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("name", tagMeta.getTagName()); + assertNotNull(tagMeta.getDefNodeInSchema()); + // 实际为 xdsl.xdef 的 xdef:unknown-tag 节点 + assertEquals(tagMeta.getDefNodeInSchema(), tagMeta.getXDslDefNode()); + + assertNull(tagMeta.getDefNodeInSelfSchema()); + } // + ); + } + + public void testCreateUnknownTagMeta() { + assertTagMeta(""" + known-tag> + + """, // + (tag, tagMeta) -> { + // 元模型未指定 + assertTrue(tagMeta.hasError()); + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("meta:unknown-tag", tagMeta.getTagName()); + assertTrue(tagMeta.getErrorMsg().contains("No schema path is specified")); + } // + ); + assertTagMeta(""" + ple + xmlns:x="/nop/schema/xdsl.xdef" + x:schema="/xx/xx/example.xdef" + > + + """, // + (tag, tagMeta) -> { + // 标签由 xdsl.xdef 定义 + assertFalse(tagMeta.hasError()); + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("example", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xdsl.xdef", defNodeVfsPath(tagMeta)); + } // + ); + + assertTagMeta(""" + known + xmlns:x="/nop/schema/xdsl.xdef" xmlns:meta="/nop/schema/xdef.xdef" + x:schema="/nop/schema/xdef.xdef" meta:check-ns="xdef" + > + + """, // + (tag, tagMeta) -> { + // meta 名字空间的标签未定义 + assertTrue(tagMeta.hasError()); + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("meta:unknown", tagMeta.getTagName()); + assertTrue(tagMeta.getErrorMsg().contains("corresponding namespace 'xdef'")); + } // + ); + assertTagMeta(""" + + cd/> + + """, // + (tag, tagMeta) -> { + // meta 名字空间的标签未定义 + assertTrue(tagMeta.hasError()); + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("meta:abcd", tagMeta.getTagName()); + assertTrue(tagMeta.getErrorMsg().contains("corresponding namespace 'xdef'")); + } // + ); + assertTagMeta(""" + + ething> + + + """, // + (tag, tagMeta) -> { + // xdef 名字空间的标签未定义 + assertTrue(tagMeta.hasError()); + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("xdef:something", tagMeta.getTagName()); + assertTrue(tagMeta.getErrorMsg().contains("isn't defined in schema '/nop/schema/xdef.xdef'")); + } // + ); + assertTagMeta(""" + + ny/> + + """, // + (tag, tagMeta) -> { + // x 名字空间的标签未定义 + assertTrue(tagMeta.hasError()); + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("x:any", tagMeta.getTagName()); + assertTrue(tagMeta.getErrorMsg().contains("corresponding namespace 'x'")); + } // + ); + assertTagMeta(""" + + + c/> + + + """, // + (tag, tagMeta) -> { + // xpl 名字空间的标签未定义 + assertTrue(tagMeta.hasError()); + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("xpl:abc", tagMeta.getTagName()); + assertTrue(tagMeta.getErrorMsg().contains("corresponding namespace 'xpl'")); + } // + ); + + assertTagMeta(""" + ng + xmlns:x="/nop/schema/xdsl.xdef" + x:schema="/test/lang/lang.xdef" + > + + """, // + (tag, tagMeta) -> { + // 根节点标签与定义的不一致 + assertTrue(tagMeta.hasError()); + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("lang", tagMeta.getTagName()); + assertTrue(tagMeta.getErrorMsg().contains("doesn't match with the root tag")); + } // + ); + assertTagMeta(""" + + ent> + + + """, // + (tag, tagMeta) -> { + // xui 名字空间的标签未显式定义 + assertTrue(tagMeta.hasError()); + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("xui:parent", tagMeta.getTagName()); + assertTrue(tagMeta.getErrorMsg().contains("should be defined in schema")); + } // + ); + assertTagMeta(""" + + + ild/> + + + """, // + (tag, tagMeta) -> { + // 父标签未定义 + assertTrue(tagMeta.hasError()); + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("xui:child", tagMeta.getTagName()); + assertTrue(tagMeta.getErrorMsg().contains("'xui:parent' isn't defined")); + } // + ); + } + + public void testDefAttrByTagMeta() { + assertDefAttr(""" + + tle="Refs" /> + + """, // + (attr, defAttr) -> { + // 名字空间未要求校验,则其下属性可任意指定 + assertEquals("i18n:title", attr.getName()); + + assertNotNull(defAttr); + assertEquals("any", defAttr.getType().getStdDomain()); + } // + ); + + assertDefAttr(""" + + le="Some" /> + + """, // + (attr, defAttr) -> { + // 无名字空间的属性必须显式定义 + assertEquals("title", attr.getName()); + + assertNotNull(defAttr); + assertInstanceOf(defAttr, XLangAttribute.XDefAttributeWithError.class); + assertTrue(((XLangAttribute.XDefAttributeWithError) defAttr).getErrorMsg() + .contains( + "is defined on undefined tag")); + } // + ); + assertDefAttr(""" + + le="Some" /> + + """, // + (attr, defAttr) -> { + // 在未定义标签上的属性也为未定义 + assertEquals("i18n:title", attr.getName()); + + assertNotNull(defAttr); + assertInstanceOf(defAttr, XLangAttribute.XDefAttributeWithError.class); + assertTrue(((XLangAttribute.XDefAttributeWithError) defAttr).getErrorMsg() + .contains( + "is defined on undefined tag")); + } // + ); + + assertDefAttr(""" + + ss="w-full" /> + + """, // + (attr, defAttr) -> { + // 需校验的名字空间,其下属性必须显式定义 + assertEquals("xui:class", attr.getName()); + + assertNotNull(defAttr); + assertInstanceOf(defAttr, XLangAttribute.XDefAttributeWithError.class); + assertTrue(((XLangAttribute.XDefAttributeWithError) defAttr).getErrorMsg() + .contains( + "namespace 'xui' should be defined in schema '/test/lang/lang.xdef'")); + } // + ); + assertDefAttr(""" + + ss="w-full" /> + + """, // + (attr, defAttr) -> { + // x 名字空间下的属性必须为 xdsl.xdef 中已定义的 + assertEquals("x:class", attr.getName()); + + assertNotNull(defAttr); + assertInstanceOf(defAttr, XLangAttribute.XDefAttributeWithError.class); + assertTrue(((XLangAttribute.XDefAttributeWithError) defAttr).getErrorMsg() + .contains( + "namespace 'x' should be defined in schema '/nop/schema/xdsl.xdef'")); + } // + ); + assertDefAttr(""" + + fined="string" /> + + """, // + (attr, defAttr) -> { + // xdef 名字空间下的属性必须为 xdef.xdef 中已定义的 + assertEquals("xdef:undefined", attr.getName()); + + assertNotNull(defAttr); + assertInstanceOf(defAttr, XLangAttribute.XDefAttributeWithError.class); + assertTrue(((XLangAttribute.XDefAttributeWithError) defAttr).getErrorMsg() + .contains( + "namespace 'xdef' should be defined in schema '/nop/schema/xdef.xdef'")); + } // + ); + assertDefAttr(""" + + fined="string" /> + + """, // + (attr, defAttr) -> { + // x 名字空间下的属性必须为 xdsl.xdef 中已定义的 + assertEquals("x:undefined", attr.getName()); + + assertNotNull(defAttr); + assertInstanceOf(defAttr, XLangAttribute.XDefAttributeWithError.class); + assertTrue(((XLangAttribute.XDefAttributeWithError) defAttr).getErrorMsg() + .contains( + "namespace 'x' should be defined in schema '/nop/schema/xdsl.xdef'")); + } // + ); + assertDefAttr(""" + + ass="string" /> + + """, // + (attr, defAttr) -> { + // 在元模型中的带名字空间的属性可任意定义 + assertEquals("xui:class", attr.getName()); + + assertNotNull(defAttr); + assertNotNull(defAttr.getType()); + assertEquals("xdef-attr", defAttr.getType().getStdDomain()); + } // + ); + } + + private void assertTagMeta(String text, BiConsumer consumer) { + configureByXLangText(text); + assertCaretExists(); + + PsiElement target = getOriginalElementAtCaret(); + XLangTag tag = PsiTreeUtil.getParentOfType(target, XLangTag.class); + assertNotNull(tag); + + XLangTagMeta tagMeta = tag.getTagMeta(); + assertNotNull(tagMeta); + + consumer.accept(tag, tagMeta); + } + + private void assertDefAttr(String text, BiConsumer consumer) { + configureByXLangText(text); + assertCaretExists(); + + PsiElement target = getOriginalElementAtCaret(); + XLangAttribute attr = PsiTreeUtil.getParentOfType(target, XLangAttribute.class); + assertNotNull(attr); + + XLangTagMeta tagMeta = attr.getParentTag().getTagMeta(); + assertNotNull(tagMeta); + + consumer.accept(attr, tagMeta.getDefAttr(attr)); + } + + private String defNodeVfsPath(XLangTagMeta tagMeta) { + return defNodeVfsPath(tagMeta.getDefNodeInSchema()); + } + + private String selfDefNodeVfsPath(XLangTagMeta tagMeta) { + return defNodeVfsPath(tagMeta.getDefNodeInSelfSchema()); + } + + private String defNodeVfsPath(IXDefNode defNode) { + String vfsPath = XmlPsiHelper.getNopVfsPath(defNode); + + return vfsPath != null && vfsPath.lastIndexOf('/') == 0 ? null : vfsPath; + } +} diff --git a/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/utils/TestXDefPsiHelper.java b/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/utils/TestXDefPsiHelper.java index c8e5fc4093c1374bd300eb77d4644832f33e6104..9f1e2ce86f3faf38c5a5615cc5963994d1bde46d 100644 --- a/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/utils/TestXDefPsiHelper.java +++ b/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/utils/TestXDefPsiHelper.java @@ -9,6 +9,7 @@ package io.nop.idea.plugin.utils; import io.nop.idea.plugin.BaseXLangPluginTestCase; +import io.nop.idea.plugin.resource.ProjectEnv; import io.nop.xlang.xdef.IXDefinition; /** @@ -18,7 +19,7 @@ import io.nop.xlang.xdef.IXDefinition; public class TestXDefPsiHelper extends BaseXLangPluginTestCase { public void testGetXdefDef() { - IXDefinition xdef = XDefPsiHelper.getXdefDef(); + IXDefinition xdef = ProjectEnv.withProject(getProject(), XDefPsiHelper::getXdefDef); assertNotNull(xdef); } diff --git a/nop-dev-tools/nop-idea-plugin/src/test/resources/_vfs/test/ast/xlang-1.ast b/nop-dev-tools/nop-idea-plugin/src/test/resources/_vfs/test/ast/xlang-1.ast index 08a6aa6f8b0bb4df7cb9cd2f6b078d1bfd2fe3f3..01b7e6ed878dc57de62a054093b7898a5689fdb9 100644 --- a/nop-dev-tools/nop-idea-plugin/src/test/resources/_vfs/test/ast/xlang-1.ast +++ b/nop-dev-tools/nop-idea-plugin/src/test/resources/_vfs/test/ast/xlang-1.ast @@ -1,145 +1,144 @@ -XmlFile:unit.xtest - XLangDocument - PsiElement(XML_PROLOG) - - XLangTag:XML_TAG('example') +XLangDocument + PsiElement(XML_PROLOG) + + XLangTag:XML_TAG('example') + XmlToken:XML_START_TAG_START('<') + XmlToken:XML_NAME('example') + PsiWhiteSpace(' ') + XLangAttribute:XML_ATTRIBUTE('xmlns:x') + XmlToken:XML_NAME('xmlns:x') + XmlToken:XML_EQ('=') + XLangAttributeValue + XmlToken:XML_ATTRIBUTE_VALUE_START_DELIMITER('"') + XLangValueToken:XML_ATTRIBUTE_VALUE_TOKEN('/nop/schema/xdsl.xdef') + XmlToken:XML_ATTRIBUTE_VALUE_END_DELIMITER('"') + PsiWhiteSpace('\n ') + XLangAttribute:XML_ATTRIBUTE('x:schema') + XmlToken:XML_NAME('x:schema') + XmlToken:XML_EQ('=') + XLangAttributeValue + XmlToken:XML_ATTRIBUTE_VALUE_START_DELIMITER('"') + XLangValueToken:XML_ATTRIBUTE_VALUE_TOKEN('/path/to/example.xdef') + XmlToken:XML_ATTRIBUTE_VALUE_END_DELIMITER('"') + PsiWhiteSpace('\n') + XmlToken:XML_TAG_END('>') + XLangText:XML_TEXT + PsiWhiteSpace('\n ') + XLangTag:XML_TAG('tag') XmlToken:XML_START_TAG_START('<') - XmlToken:XML_NAME('example') + XmlToken:XML_NAME('tag') PsiWhiteSpace(' ') - XLangAttribute:XML_ATTRIBUTE('xmlns:x') - XmlToken:XML_NAME('xmlns:x') - XmlToken:XML_EQ('=') - XLangAttributeValue - XmlToken:XML_ATTRIBUTE_VALUE_START_DELIMITER('"') - XLangValueToken:XML_ATTRIBUTE_VALUE_TOKEN('/nop/schema/xdsl.xdef') - XmlToken:XML_ATTRIBUTE_VALUE_END_DELIMITER('"') - PsiWhiteSpace('\n ') - XLangAttribute:XML_ATTRIBUTE('x:schema') - XmlToken:XML_NAME('x:schema') + XLangAttribute:XML_ATTRIBUTE('name') + XmlToken:XML_NAME('name') XmlToken:XML_EQ('=') XLangAttributeValue XmlToken:XML_ATTRIBUTE_VALUE_START_DELIMITER('"') - XLangValueToken:XML_ATTRIBUTE_VALUE_TOKEN('/path/to/example.xdef') + XLangValueToken:XML_ATTRIBUTE_VALUE_TOKEN('Child ') + XmlToken:XML_CHAR_ENTITY_REF('&') + XLangValueToken:XML_ATTRIBUTE_VALUE_TOKEN(' Tag') XmlToken:XML_ATTRIBUTE_VALUE_END_DELIMITER('"') - PsiWhiteSpace('\n') + XmlToken:XML_EMPTY_ELEMENT_END('/>') + XLangText:XML_TEXT + PsiWhiteSpace('\n ') + XLangTag:XML_TAG('refs') + XmlToken:XML_START_TAG_START('<') + XmlToken:XML_NAME('refs') XmlToken:XML_TAG_END('>') XLangText:XML_TEXT - PsiWhiteSpace('\n ') - XLangTag:XML_TAG('tag') - XmlToken:XML_START_TAG_START('<') - XmlToken:XML_NAME('tag') - PsiWhiteSpace(' ') - XLangAttribute:XML_ATTRIBUTE('name') - XmlToken:XML_NAME('name') - XmlToken:XML_EQ('=') - XLangAttributeValue - XmlToken:XML_ATTRIBUTE_VALUE_START_DELIMITER('"') - XLangValueToken:XML_ATTRIBUTE_VALUE_TOKEN('Child ') - XmlToken:XML_CHAR_ENTITY_REF('&') - XLangValueToken:XML_ATTRIBUTE_VALUE_TOKEN(' Tag') - XmlToken:XML_ATTRIBUTE_VALUE_END_DELIMITER('"') - XmlToken:XML_EMPTY_ELEMENT_END('/>') + XLangTextToken:XML_DATA_CHARACTERS('/nop/schema/xdef.xdef,/nop/schema/xdsl.xdef') + XmlToken:XML_END_TAG_START('') + XLangText:XML_TEXT + PsiWhiteSpace('\n ') + XLangTag:XML_TAG('refs') + XmlToken:XML_START_TAG_START('<') + XmlToken:XML_NAME('refs') + XmlToken:XML_TAG_END('>') XLangText:XML_TEXT + PsiWhiteSpace('\n ') + XLangTextToken:XML_DATA_CHARACTERS('/nop/schema/xdef.xdef,') + PsiWhiteSpace('\n ') + XLangTextToken:XML_DATA_CHARACTERS('/nop/schema/xdsl.xdef') PsiWhiteSpace('\n ') - XLangTag:XML_TAG('refs') - XmlToken:XML_START_TAG_START('<') - XmlToken:XML_NAME('refs') - XmlToken:XML_TAG_END('>') - XLangText:XML_TEXT - XLangTextToken:XML_DATA_CHARACTERS('/nop/schema/xdef.xdef,/nop/schema/xdsl.xdef') - XmlToken:XML_END_TAG_START('') + XmlToken:XML_END_TAG_START('') + XLangText:XML_TEXT + PsiWhiteSpace('\n ') + XLangTag:XML_TAG('text') + XmlToken:XML_START_TAG_START('<') + XmlToken:XML_NAME('text') + XmlToken:XML_TAG_END('>') XLangText:XML_TEXT - PsiWhiteSpace('\n ') - XLangTag:XML_TAG('refs') + PsiElement(XML_CDATA) + XmlToken:XML_CDATA_START('') + XmlToken:XML_END_TAG_START('') + XLangText:XML_TEXT + PsiWhiteSpace('\n ') + XLangTag:XML_TAG('mix') + XmlToken:XML_START_TAG_START('<') + XmlToken:XML_NAME('mix') + XmlToken:XML_TAG_END('>') + XLangText:XML_TEXT + PsiWhiteSpace('\n ') + XLangTextToken:XML_DATA_CHARACTERS('This') + PsiWhiteSpace(' ') + XLangTextToken:XML_DATA_CHARACTERS('is') + PsiWhiteSpace(' ') + XLangTextToken:XML_DATA_CHARACTERS('a') + PsiWhiteSpace(' ') + XmlToken:XML_CHAR_ENTITY_REF('<') + XLangTextToken:XML_DATA_CHARACTERS('text/') + XmlToken:XML_CHAR_ENTITY_REF('>') + PsiWhiteSpace(' ') + XLangTextToken:XML_DATA_CHARACTERS('node.') + PsiWhiteSpace('\n ') + XLangTag:XML_TAG('tag') XmlToken:XML_START_TAG_START('<') - XmlToken:XML_NAME('refs') + XmlToken:XML_NAME('tag') XmlToken:XML_TAG_END('>') XLangText:XML_TEXT + PsiWhiteSpace('\n ') + XLangTextToken:XML_DATA_CHARACTERS('This') + PsiWhiteSpace(' ') + XLangTextToken:XML_DATA_CHARACTERS('is') + PsiWhiteSpace(' ') + XLangTextToken:XML_DATA_CHARACTERS('a') + PsiWhiteSpace(' ') + XLangTextToken:XML_DATA_CHARACTERS('child') + PsiWhiteSpace(' ') + XLangTextToken:XML_DATA_CHARACTERS('tag.') PsiWhiteSpace('\n ') - XLangTextToken:XML_DATA_CHARACTERS('/nop/schema/xdef.xdef,') - PsiWhiteSpace('\n ') - XLangTextToken:XML_DATA_CHARACTERS('/nop/schema/xdsl.xdef') - PsiWhiteSpace('\n ') XmlToken:XML_END_TAG_START('') XLangText:XML_TEXT - PsiWhiteSpace('\n ') - XLangTag:XML_TAG('text') + PsiWhiteSpace('\n ') + XLangTag:XML_TAG('tag') XmlToken:XML_START_TAG_START('<') - XmlToken:XML_NAME('text') + XmlToken:XML_NAME('tag') XmlToken:XML_TAG_END('>') XLangText:XML_TEXT PsiElement(XML_CDATA) XmlToken:XML_CDATA_START('') XmlToken:XML_END_TAG_START('') XLangText:XML_TEXT PsiWhiteSpace('\n ') - XLangTag:XML_TAG('mix') - XmlToken:XML_START_TAG_START('<') - XmlToken:XML_NAME('mix') - XmlToken:XML_TAG_END('>') - XLangText:XML_TEXT - PsiWhiteSpace('\n ') - XLangTextToken:XML_DATA_CHARACTERS('This') - PsiWhiteSpace(' ') - XLangTextToken:XML_DATA_CHARACTERS('is') - PsiWhiteSpace(' ') - XLangTextToken:XML_DATA_CHARACTERS('a') - PsiWhiteSpace(' ') - XmlToken:XML_CHAR_ENTITY_REF('<') - XLangTextToken:XML_DATA_CHARACTERS('text/') - XmlToken:XML_CHAR_ENTITY_REF('>') - PsiWhiteSpace(' ') - XLangTextToken:XML_DATA_CHARACTERS('node.') - PsiWhiteSpace('\n ') - XLangTag:XML_TAG('tag') - XmlToken:XML_START_TAG_START('<') - XmlToken:XML_NAME('tag') - XmlToken:XML_TAG_END('>') - XLangText:XML_TEXT - PsiWhiteSpace('\n ') - XLangTextToken:XML_DATA_CHARACTERS('This') - PsiWhiteSpace(' ') - XLangTextToken:XML_DATA_CHARACTERS('is') - PsiWhiteSpace(' ') - XLangTextToken:XML_DATA_CHARACTERS('a') - PsiWhiteSpace(' ') - XLangTextToken:XML_DATA_CHARACTERS('child') - PsiWhiteSpace(' ') - XLangTextToken:XML_DATA_CHARACTERS('tag.') - PsiWhiteSpace('\n ') - XmlToken:XML_END_TAG_START('') - XLangText:XML_TEXT - PsiWhiteSpace('\n ') - XLangTag:XML_TAG('tag') - XmlToken:XML_START_TAG_START('<') - XmlToken:XML_NAME('tag') - XmlToken:XML_TAG_END('>') - XLangText:XML_TEXT - PsiElement(XML_CDATA) - XmlToken:XML_CDATA_START('') - XmlToken:XML_END_TAG_START('') - XLangText:XML_TEXT - PsiWhiteSpace('\n ') - XmlToken:XML_END_TAG_START('') - XLangText:XML_TEXT - PsiWhiteSpace('\n') XmlToken:XML_END_TAG_START('') - PsiWhiteSpace('\n') + XLangText:XML_TEXT + PsiWhiteSpace('\n') + XmlToken:XML_END_TAG_START('') + PsiWhiteSpace('\n') diff --git a/nop-dev-tools/nop-idea-plugin/src/test/resources/_vfs/test/doc/example.xdef b/nop-dev-tools/nop-idea-plugin/src/test/resources/_vfs/test/doc/example.xdef index 264794eedfa16d545de389943d99a4493f26068c..5a8d378d1f9565188cd003285a5b92c76b4ddf15 100644 --- a/nop-dev-tools/nop-idea-plugin/src/test/resources/_vfs/test/doc/example.xdef +++ b/nop-dev-tools/nop-idea-plugin/src/test/resources/_vfs/test/doc/example.xdef @@ -13,6 +13,11 @@ + + +