From d8dabf046bc7161009bd44c2f32d1bcf598fa960 Mon Sep 17 00:00:00 2001 From: flytreeleft Date: Fri, 17 Oct 2025 21:55:58 +0800 Subject: [PATCH 01/12] =?UTF-8?q?nop-idea-pugin:=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E8=A1=A5=E5=85=A8=E6=8F=90=E7=A4=BA=E7=9A=84?= =?UTF-8?q?=E6=80=A7=E8=83=BD=EF=BC=8C=E9=99=8D=E4=BD=8E=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E6=97=B6=20UI=20=E5=8D=A1=E6=AD=BB=E9=A3=8E=E9=99=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../idea/plugin/annotator/XLangAnnotator.java | 1 + .../idea/plugin/lang/psi/XLangAttribute.java | 6 + .../plugin/lang/psi/XLangAttributeValue.java | 6 + .../io/nop/idea/plugin/lang/psi/XLangTag.java | 361 +++++++++++------- .../nop/idea/plugin/lang/psi/XLangText.java | 6 + .../idea/plugin/lang/psi/XLangTextToken.java | 6 + .../idea/plugin/lang/psi/XLangValueToken.java | 6 + .../reference/XLangAttributeReference.java | 7 +- .../reference/XLangDictOptionReference.java | 5 +- .../XLangParentTagAttrReference.java | 8 +- .../lang/reference/XLangReferenceHelper.java | 23 +- .../XLangStdDomainDictReference.java | 4 +- .../reference/XLangStdDomainReference.java | 4 +- .../XLangStdDomainXdefRefReference.java | 12 +- .../lang/reference/XLangTagReference.java | 10 +- .../reference/XLangXPrototypeReference.java | 5 +- .../reference/XLangXdefKeyAttrReference.java | 4 +- .../lang/reference/XLangXlibTagReference.java | 1 + .../plugin/resource/ProjectDictProvider.java | 5 +- .../resource/ProjectVirtualFileSystem.java | 49 +-- .../idea/plugin/utils/ProjectFileHelper.java | 27 +- .../nop/idea/plugin/utils/PsiClassHelper.java | 26 +- .../nop/idea/plugin/utils/XmlPsiHelper.java | 17 +- .../plugin/lang/TestXLangCompletions.java | 5 +- .../idea/plugin/lang/TestXLangReferences.java | 8 +- .../nop/idea/plugin/lang/TestXLangRename.java | 2 + .../idea/plugin/utils/TestXDefPsiHelper.java | 3 +- 27 files changed, 388 insertions(+), 229 deletions(-) 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 bc86d2d0d..4a1e6d293 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 @@ -160,6 +160,7 @@ public class XLangAnnotator implements Annotator { } private void checkTagValue(@NotNull AnnotationHolder holder, @NotNull XLangTag tag) { + // TODO 标签唯一性检查:未显式声明的,只能有一个,不能重复 XDefTypeDecl xdefValue = tag.getSchemaDefNodeXdefValue(); TextRange textRange = tag.getValue().getTextRange(); String bodyText = tag.hasChildTag() ? null : tag.getBodyText(); 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 2c7e35444..64bb2578c 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 @@ -32,6 +32,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(); } 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 99b4ba655..6af643a6d 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; } 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 c82227ab3..41733ac99 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; @@ -80,10 +79,9 @@ public class XLangTag extends XmlTagImpl { } @Override - public void clearCaches() { - this.schemaMeta = null; - - super.clearCaches(); + public boolean skipValidation() { + // Note: 禁用 xml 的校验 + return true; } @Override @@ -91,6 +89,10 @@ public class XLangTag extends XmlTagImpl { return (XLangTag) super.getParentTag(); } + private boolean isRootTag() { + return getParentTag() == null; + } + public XLangTag getRootTag() { XLangTag tag = this; @@ -560,113 +562,141 @@ public class XLangTag extends XmlTagImpl { } private synchronized SchemaMeta getSchemaMeta() { - if (schemaMeta == null) { - Project project = getProject(); - schemaMeta = ProjectEnv.withProject(project, this::createSchemaMeta); + String tagName = getName(); - try { - ProgressManager.checkCanceled(); - } catch (ProcessCanceledException e) { - // Note: 若处理被中断,则保持元模型信息为空,以便于后续再重新初始化 - schemaMeta = null; + if (schemaMeta == null || !isValid() /* 文件已无效 */) { + schemaMeta = createSchemaMeta(); + } + // 根节点发生了 schema 相关的更新(包括 schema 的依赖的变更),或者标签名发生了变化 + else if (schemaMeta != null // + && (isRootTag() // + || !Objects.equals(schemaMeta.tagName, tagName) // + ) // + ) { + SchemaMeta newSchemaMeta = createSchemaMeta(); - // Note: 避免后续访问成员变量出现 NPE 问题 - return UNKNOWN_SCHEMA_META; + if (!Objects.equals(schemaMeta, newSchemaMeta)) { + clearSchemaMeta(); + + schemaMeta = newSchemaMeta; } } + + if (schemaMeta == null) { + // Note: 避免后续访问成员变量出现 NPE 问题 + return UNKNOWN_SCHEMA_META; + } return schemaMeta; } + private void clearSchemaMeta() { + this.schemaMeta = null; + + // 子节点同时失效 + for (PsiElement child : getChildren()) { + if (child instanceof XLangTag tag) { + tag.clearSchemaMeta(); + } + } + } + private SchemaMeta createSchemaMeta() { + Project project = getProject(); + + try { + return ProjectEnv.withProject(project, this::doCreateSchemaMeta); + } catch (ProcessCanceledException e) { + // Note: 若处理被中断,则保持元模型信息为空,以便于后续再重新初始化 + return null; + } + } + + private SchemaMeta doCreateSchemaMeta() { + if (isRootTag()) { + return doCreateRootTagSchemaMeta(); + } + XLangTag parentTag = getParentTag(); - if (parentTag == null) { - return createSchemaMetaForRootTag(this); + SchemaMeta parentSchemaMeta = parentTag != null ? parentTag.getSchemaMeta() : null; + if (parentSchemaMeta == null) { + return UNKNOWN_SCHEMA_META; } String tagName = getName(); String tagNs = getNamespacePrefix(); - Project project = getProject(); - SchemaMeta parentSchemaMeta = parentTag.getSchemaMeta(); + IXDefNode xplUnknownTagDefNode = XDefPsiHelper.getXplDef().getRootNode().getXdefUnknownTag(); - return new ChildTagSchemaMeta(project, parentSchemaMeta, tagName, tagNs); + return new ChildTagSchemaMeta(tagName, parentSchemaMeta, xplUnknownTagDefNode, tagNs); } - private static SchemaMeta createSchemaMetaForRootTag(XLangTag rootTag) { - String schemaUrl = XDefPsiHelper.getSchemaPath(rootTag); + private SchemaMeta doCreateRootTagSchemaMeta() { + String schemaUrl = XDefPsiHelper.getSchemaPath(this); 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); + String xdefNs = XmlPsiHelper.getXmlnsForUrl(this, XDslConstants.XDSL_SCHEMA_XDEF); + String xdslNs = XmlPsiHelper.getXmlnsForUrl(this, XDslConstants.XDSL_SCHEMA_XDSL); XDefKeys xdefKeys = XDefKeys.of(xdefNs); XDslKeys xdslKeys = XDslKeys.of(xdslNs); - Supplier selfDef = () -> null; + IXDefinition schemaDef = XDefPsiHelper.loadSchema(schemaUrl); + IXDefNode xdslDefNode = XDefPsiHelper.getXDslDef().getRootNode(); + + IXDefinition selfDef = null; // x:schema 为 /nop/schema/xdef.xdef 时,其自身也为元模型 if (XDslConstants.XDSL_SCHEMA_XDEF.equals(schemaUrl)) { - String vfsPath = XmlPsiHelper.getNopVfsPath(rootTag); + String vfsPath = XmlPsiHelper.getNopVfsPath(this); + // Note: 正在编辑中的 xdef 有可能是不完整的,此时将无法解析出 IXDefinition if (vfsPath != null) { - selfDef = () -> XDefPsiHelper.loadSchema(vfsPath); + selfDef = XDefPsiHelper.loadSchema(vfsPath); } else { // 适配单元测试环境:待测试资源可能不是标准的 vfs 资源 - IXDefinition def = XDefPsiHelper.loadSchema(rootTag.getContainingFile()); - selfDef = () -> def; + selfDef = XDefPsiHelper.loadSchema(getContainingFile()); } } - Project project = rootTag.getProject(); - - return new RootTagSchemaMeta(project, schemaUrl, xdefKeys, xdslKeys, selfDef); + String tagName = getName(); + return new RootTagSchemaMeta(tagName, schemaDef, xdslDefNode, selfDef, xdefKeys, xdslKeys); } 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 + String tagName, // + IXDefinition schemaDef, // + IXDefNode xdslDefNode, IXDefinition selfDef, // + XDefKeys xdefKeys, XDslKeys xdslKeys // ) { - super(project, null); - this.schemaUrl = schemaUrl; - this.xdefKeys = xdefKeys; - this.xdslKeys = xdslKeys; - this.selfDef = selfDef; + super(tagName, schemaDef, xdslDefNode, selfDef, xdefKeys, xdslKeys); } // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @Override - public IXDefinition getSchemaDef() { - return ProjectEnv.withProject(project, () -> XDefPsiHelper.loadSchema(schemaUrl)); - } - - @Override - public IXDefNode getSchemaDefNode() { - IXDefinition def = getSchemaDef(); + protected IXDefNode doGetSchemaDefNode() { + IXDefinition schema = getSchemaDef(); + if (schema == null) { + return null; + } - return def != null ? def.getRootNode() : null; - } + String schemaVfsPath = XmlPsiHelper.getNopVfsPath(schema); - @Override - public @Nullable IXDefNode getXDslDefNode() { - return ProjectEnv.withProject(project, () -> XDefPsiHelper.getXDslDef().getRootNode()); - } + IXDefNode defNode = schema.getRootNode(); + // 如果不是 *.xdef,则其根节点名称必须与其 x:schema 所定义的根节点名称保持一致 + if (!XDslConstants.XDSL_SCHEMA_XDEF.equals(schemaVfsPath) // + && !defNode.getTagName().equals(tagName) // + ) { + defNode = null; + } - @Override - public @Nullable IXDefinition getSelfDef() { - return ProjectEnv.withProject(project, selfDef); + return defNode; } @Override - public IXDefNode getSelfDefNode() { + protected IXDefNode doGetSelfDefNode() { IXDefinition def = getSelfDef(); return def != null ? def.getRootNode() : null; @@ -674,35 +704,49 @@ public class XLangTag extends XmlTagImpl { // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @Override - public @NotNull XDefKeys getXDefKeys() { - return xdefKeys; - } + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } - @Override - public @NotNull XDslKeys getXDslKeys() { - return xdslKeys; + RootTagSchemaMeta that = (RootTagSchemaMeta) o; + return Objects.equals(tagName, that.tagName) + && Objects.equals(getSchemaDef(), that.getSchemaDef()) + && Objects.equals(getXDslDefNode(), that.getXDslDefNode()) + && Objects.equals(getSelfDef(), that.getSelfDef()) + && Objects.equals(getXDefKeys(), that.getXDefKeys()) + && Objects.equals(getXDslKeys(), that.getXDslKeys()); } } private static class ChildTagSchemaMeta extends SchemaMeta { - protected final SchemaMeta parent; - protected final String tagNs; + private final SchemaMeta parent; + private final IXDefNode xplUnknownTagDefNode; + + private final String tagNs; + + ChildTagSchemaMeta( + String tagName, SchemaMeta parent, // + IXDefNode xplUnknownTagDefNode, // + String tagNs + ) { + super(tagName); - ChildTagSchemaMeta(Project project, SchemaMeta parent, String tagName, String tagNs) { - super(project, tagName); this.parent = parent; + this.xplUnknownTagDefNode = xplUnknownTagDefNode; + this.tagNs = tagNs; } // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @Override - public @Nullable IXDefinition getSchemaDef() { + protected @Nullable IXDefinition doGetSchemaDef() { return parent.getSchemaDef(); } @Override - public @Nullable IXDefNode getSchemaDefNode() { + protected @Nullable IXDefNode doGetSchemaDefNode() { if (XDslKeys.DEFAULT.NS.equals(tagNs) && !parent.isInXDslXDef()) { return getXDslDefNode(); } @@ -710,10 +754,7 @@ public class XLangTag extends XmlTagImpl { IXDefNode parentDefNode = parent.getSchemaDefNode(); if (parent.isXplDefNode()) { // Xpl 子节点均为 xdef:unknown-tag - parentDefNode = ProjectEnv.withProject(project, - () -> XDefPsiHelper.getXplDef() - .getRootNode() - .getXdefUnknownTag()); + parentDefNode = xplUnknownTagDefNode; } if (parentDefNode == null) { @@ -738,19 +779,19 @@ public class XLangTag extends XmlTagImpl { } @Override - public @Nullable IXDefNode getXDslDefNode() { + protected @Nullable IXDefNode doGetXDslDefNode() { IXDefNode parentDefNode = parent.getXDslDefNode(); return getXDefNodeChild(parentDefNode, tagName); } @Override - public @Nullable IXDefinition getSelfDef() { + protected @Nullable IXDefinition doGetSelfDef() { return parent.getSelfDef(); } @Override - public @Nullable IXDefNode getSelfDefNode() { + protected @Nullable IXDefNode doGetSelfDefNode() { // 在非 xdsl.xdef 中的 x 名字空间的节点,始终不视为自定义节点 if (XDslKeys.DEFAULT.NS.equals(tagNs) && !parent.isInXDslXDef()) { return null; @@ -763,12 +804,12 @@ public class XLangTag extends XmlTagImpl { // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> @Override - public @NotNull XDefKeys getXDefKeys() { + protected @NotNull XDefKeys doGetXDefKeys() { return parent.getXDefKeys(); } @Override - public @NotNull XDslKeys getXDslKeys() { + protected @NotNull XDslKeys doGetXDslKeys() { return parent.getXDslKeys(); } } @@ -776,45 +817,7 @@ public class XLangTag extends XmlTagImpl { 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; - } - - @Override - public @NotNull XDslKeys getXDslKeys() { - return XDslKeys.DEFAULT; + super(null); } } @@ -824,12 +827,32 @@ public class XLangTag extends XmlTagImpl { * 是有缓存和失效机制的,不会明显影响性能 */ private static abstract class SchemaMeta { - protected final Project project; protected final String tagName; - SchemaMeta(Project project, String tagName) { - this.project = project; + private IXDefinition schemaDef; + private IXDefNode schemaDefNode; + private IXDefNode xdslDefNode; + private IXDefinition selfDef; + private IXDefNode selfDefNode; + private XDefKeys xdefKeys; + private XDslKeys xdslKeys; + + SchemaMeta(String tagName) { + this.tagName = tagName; + } + + SchemaMeta( + String tagName, // + IXDefinition schemaDef, // + IXDefNode xdslDefNode, IXDefinition selfDef, // + XDefKeys xdefKeys, XDslKeys xdslKeys + ) { this.tagName = tagName; + this.schemaDef = schemaDef; + this.xdslDefNode = xdslDefNode; + this.selfDef = selfDef; + this.xdefKeys = xdefKeys; + this.xdslKeys = xdslKeys; } /** @@ -837,34 +860,104 @@ public class XLangTag extends XmlTagImpl { *

* 允许元模型不存在,以支持检查 xdsl.xdef 对应的节点 */ - public abstract @Nullable IXDefinition getSchemaDef(); + public @Nullable IXDefinition getSchemaDef() { + if (schemaDef == null) { + schemaDef = doGetSchemaDef(); + } + return schemaDef; + } + + /** @see #getSchemaDef */ + protected @Nullable IXDefinition doGetSchemaDef() { + return null; + } /** 当前标签在 {@link #getSchemaDef()} 中所对应的节点 */ - public abstract @Nullable IXDefNode getSchemaDefNode(); + public @Nullable IXDefNode getSchemaDefNode() { + if (schemaDefNode == null) { + schemaDefNode = doGetSchemaDefNode(); + } + return schemaDefNode; + } + + /** @see #getSchemaDefNode */ + protected @Nullable IXDefNode doGetSchemaDefNode() { + return null; + } /** * 当前标签在 xdsl 模型(xdsl.xdef)中所对应的节点。 * 注:所有 DSL 模型的节点均与 xdsl.xdef 的节点存在对应 */ - public abstract @Nullable IXDefNode getXDslDefNode(); + public @Nullable IXDefNode getXDslDefNode() { + if (xdslDefNode == null) { + xdslDefNode = doGetXDslDefNode(); + } + return xdslDefNode; + } + + /** @see #getXDslDefNode */ + protected @Nullable IXDefNode doGetXDslDefNode() { + return null; + } /** 当当前标签定义在 *.xdef 文件中时,需记录该元模型 */ - public abstract @Nullable IXDefinition getSelfDef(); + public @Nullable IXDefinition getSelfDef() { + if (selfDef == null) { + selfDef = doGetSelfDef(); + } + return selfDef; + } + + /** @see #getSelfDef */ + protected @Nullable IXDefinition doGetSelfDef() { + return null; + } /** 当前标签定义在 {@link #getSelfDef()} 中所对应的节点 */ - public abstract @Nullable IXDefNode getSelfDefNode(); + public @Nullable IXDefNode getSelfDefNode() { + if (selfDefNode == null) { + selfDefNode = doGetSelfDefNode(); + } + return selfDefNode; + } + + /** @see #getSelfDefNode */ + protected @Nullable IXDefNode doGetSelfDefNode() { + return null; + } /** * /nop/schema/xdef.xdef 对应的 {@link XDefKeys}。 * 仅在元模型中设置,如 xmlns:xdef="/nop/schema/xdef.xdef" */ - public abstract @NotNull XDefKeys getXDefKeys(); + public @NotNull XDefKeys getXDefKeys() { + if (xdefKeys == null) { + xdefKeys = doGetXDefKeys(); + } + return xdefKeys; + } + + /** @see #getXDefKeys */ + protected @NotNull XDefKeys doGetXDefKeys() { + return XDefKeys.DEFAULT; + } /** * /nop/schema/xdsl.xdef 对应的 {@link XDslKeys}。 * 在 DSL 模型(含元模型)中均有设置,如 xmlns:x="/nop/schema/xdsl.xdef" */ - public abstract @NotNull XDslKeys getXDslKeys(); + public @NotNull XDslKeys getXDslKeys() { + if (xdslKeys == null) { + xdslKeys = doGetXDslKeys(); + } + return xdslKeys; + } + + /** @see #getXDslKeys */ + protected @NotNull XDslKeys doGetXDslKeys() { + return XDslKeys.DEFAULT; + } /** 当前标签是否在元元模型 xdef.xdef 中 */ public boolean isInXDefXDef() { @@ -916,7 +1009,7 @@ public class XLangTag extends XmlTagImpl { return isXlibSourceNode(defNode, defPath); } - private boolean isXlibSourceNode(IXDefNode defNode, String defPath) { + protected boolean isXlibSourceNode(IXDefNode defNode, String defPath) { // xlib.xdef 中的 source 标签设置为 xml 类型,是因为在获取 XplLib 模型的时候会根据 xlib.xdef 来解析, // 但此时这个 source 段无法自动进行编译,必须结合它的 outputMode 和 attrs 配置等才能决定。 // 因此,将其子节点同样视为 xpl 节点处理 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 69cef1d05..d2fb06026 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 d8f0881f8..d1bfd8642 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); } 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 8ed940d13..dfab68e77 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 67b1dba7f..f86062765 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 @@ -35,6 +35,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} 的引用识别:指向属性的定义位置 * @@ -119,7 +121,7 @@ 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(); @@ -135,8 +137,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 979ac28d6..50ef7fb35 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 6cd8968f4..f76bdbf1e 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 @@ -19,6 +19,8 @@ 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 +58,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 +68,7 @@ public class XLangParentTagAttrReference extends XLangReferenceBase { return target; } + /** @return {@link #attrName} 所在标签上的可引用的属性名 */ @Override public Object @NotNull [] getVariants() { // Note: 在自动补全阶段,DSL 结构很可能是不完整的,只能从 xml 角度做分析 @@ -80,7 +84,7 @@ public class XLangParentTagAttrReference extends XLangReferenceBase { .stream() // .filter(new XLangXdefKeyAttrReference.TagAttrNameFilter(tag)) // .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 e0227a04b..c415b4864 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); @@ -125,8 +124,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 +207,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 +237,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 +252,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 +266,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 ce36f37fa..fc711ebc8 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 @@ -17,6 +17,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; + /** * 对字典的引用 * @@ -52,7 +54,7 @@ public class XLangStdDomainDictReference extends XLangReferenceBase { return ProjectFileHelper.findAllDictNopVfsPaths(project) .stream() .map(ProjectFileHelper::getDictNameFromVfsPath) - .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/XLangStdDomainReference.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangStdDomainReference.java index 4096b1ac8..bd39422ca 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 121c6a91f..a2cd2821b 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 @@ -28,6 +28,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; + /** * {@link io.nop.xlang.xdef.XDefConstants#STD_DOMAIN_XDEF_REF xdef-ref} 类型的值引用 * @@ -121,12 +123,12 @@ public class XLangStdDomainXdefRefReference extends XLangReferenceBase { }); return StreamEx.of( // - names.stream().sorted(XLangReferenceHelper.XLANG_NAME_COMPARATOR) // - ) // - .append(ProjectFileHelper.findAllXdefNopVfsPaths(project) - .stream() - .sorted(XLangReferenceHelper.XLANG_NAME_COMPARATOR) // + names.stream().sorted(XLANG_NAME_COMPARATOR) // ) // + .append( // + ProjectFileHelper.findAllXdefNopVfsPaths(project) + .stream() + .sorted(XLANG_NAME_COMPARATOR)) // .toArray(); } 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 0f6b20aed..7bd29bdcf 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 @@ -28,6 +28,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 XLangTag} 的引用识别:指向节点的定义位置 * @@ -58,8 +60,10 @@ public class XLangTagReference extends XLangReferenceBase { return new NopVirtualFile(myElement, path, targetResolver); } + /** @return 可替换 {@link #myElement} 的标签名(即,在元模型中其父标签所定义的子标签)列表 */ @Override public Object @NotNull [] getVariants() { + // TODO 对于 DSL 的根节点,其标签名只能为其 xdef 中的根节点名 // Note: 在自动补全阶段,DSL 结构很可能是不完整的,只能从 xml 角度做分析 XLangTag tag = (XLangTag) myElement; XLangTag parentTag = tag.getParentTag(); @@ -90,15 +94,13 @@ public class XLangTagReference extends XLangReferenceBase { } return result.stream() - .sorted((a, b) -> XLangReferenceHelper.XLANG_NAME_COMPARATOR.compare(a.getTagName(), - b.getTagName())) + .sorted((a, b) -> XLANG_NAME_COMPARATOR.compare(a.getTagName(), b.getTagName())) .map((defNode) -> lookupTag(defNode, !tagNs.isEmpty())) .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(); 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 4f749e60e..de4b936cf 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 @@ -24,6 +24,8 @@ 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 +90,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,7 +109,7 @@ 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(); } 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 8294506e1..5342605e5 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 @@ -26,6 +26,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} 的值引用 * @@ -79,7 +81,7 @@ public class XLangXdefKeyAttrReference extends XLangReferenceBase implements Psi return XmlPsiHelper.getCommonAttrNamesFromChildTag(tag) // .stream() // .filter(new TagAttrNameFilter(tag)) // - .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/XLangXlibTagReference.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangXlibTagReference.java index 57ef83882..393b3c972 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/resource/ProjectDictProvider.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/resource/ProjectDictProvider.java index b39282fce..317e864e7 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 08f1eb6ff..73d2baf2d 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/utils/ProjectFileHelper.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/ProjectFileHelper.java index d738e4555..28c91cedd 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,7 +5,6 @@ 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.Set; import java.util.stream.Collectors; @@ -23,6 +22,7 @@ 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.PsiTreeUtil; import com.intellij.psi.xml.XmlElement; import com.intellij.util.containers.CollectionFactory; @@ -35,6 +35,7 @@ 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 { @@ -98,9 +99,15 @@ public class ProjectFileHelper { return ResourceHelper.getStdPath(path); } + public static @NotNull GlobalSearchScope getSearchScope(@NotNull Project project) { + return new ProjectAndLibrariesScope(project); + } + /** 查找所有的 *.xdef 资源路径 */ public static Collection findAllXdefNopVfsPaths(Project project) { - return FilenameIndex.getAllFilesByExt(project, "xdef") + GlobalSearchScope scope = getSearchScope(project); + + return FilenameIndex.getAllFilesByExt(project, "xdef", scope) .stream() .map(ProjectFileHelper::getNopVfsStdPath) .filter(Objects::nonNull) @@ -109,7 +116,9 @@ public class ProjectFileHelper { /** 查找所有的 Nop 字典资源路径 */ public static Collection findAllDictNopVfsPaths(Project project) { - return FilenameIndex.getAllFilesByExt(project, "dict.yaml") + GlobalSearchScope scope = getSearchScope(project); + + return FilenameIndex.getAllFilesByExt(project, "dict.yaml", scope) .stream() .map(ProjectFileHelper::getNopVfsStdPath) .filter(Objects::nonNull) @@ -127,12 +136,15 @@ public class ProjectFileHelper { /** 查找所有 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); @@ -192,8 +204,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 246ba14de..9c9f86d63 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/XmlPsiHelper.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/XmlPsiHelper.java index e2719c7fe..148f4aff5 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 -> { 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 46a7f8a71..88190016e 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", // 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 c97cde436..802526477 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 @@ -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 92ce26989..1f67850e2 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 */ 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 c8e5fc409..9f1e2ce86 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); } -- Gitee From 7c0f58c55fd4de53a3525dcdd185739811f8a9f3 Mon Sep 17 00:00:00 2001 From: flytreeleft Date: Sat, 18 Oct 2025 14:38:21 +0800 Subject: [PATCH 02/12] =?UTF-8?q?nop-idea-pugin:=20=E6=8C=89=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E7=BC=93=E5=AD=98=20vfs=20=E8=B5=84=E6=BA=90=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=EF=BC=8C=E4=BB=8E=E8=80=8C=E6=8F=90=E5=8D=87=E4=B8=8E?= =?UTF-8?q?=20vfs=20=E8=B5=84=E6=BA=90=E5=BC=95=E7=94=A8=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=9A=84=E4=BB=A3=E7=A0=81=E8=A1=A5=E5=85=A8=E7=9A=84=E6=80=A7?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/nop/idea/plugin/lang/psi/XLangTag.java | 23 +++--- .../XLangStdDomainDictReference.java | 9 +-- .../XLangStdDomainXdefRefReference.java | 17 +++-- .../template/XLangRootTagNameMacro.java | 2 + .../plugin/template/XLangSchemaPathMacro.java | 17 ++--- .../idea/plugin/utils/ProjectFileHelper.java | 70 +++++++++++++------ .../plugin/vfs/NopVirtualFileReference.java | 3 +- .../idea/plugin/BaseXLangPluginTestCase.java | 13 ++-- .../plugin/lang/TestXLangCompletions.java | 46 ++++++------ 9 files changed, 111 insertions(+), 89 deletions(-) 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 41733ac99..2f61ed32d 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 @@ -646,7 +646,7 @@ public class XLangTag extends XmlTagImpl { IXDefinition selfDef = null; // x:schema 为 /nop/schema/xdef.xdef 时,其自身也为元模型 - if (XDslConstants.XDSL_SCHEMA_XDEF.equals(schemaUrl)) { + if (isXDefDef(schemaDef)) { String vfsPath = XmlPsiHelper.getNopVfsPath(this); // Note: 正在编辑中的 xdef 有可能是不完整的,此时将无法解析出 IXDefinition @@ -662,6 +662,13 @@ public class XLangTag extends XmlTagImpl { return new RootTagSchemaMeta(tagName, schemaDef, xdslDefNode, selfDef, xdefKeys, xdslKeys); } + /** 指定的 def 是否为元元模型 /nop/schema/xdef.xdef */ + private static boolean isXDefDef(IXDefinition def) { + String defVfsPath = XmlPsiHelper.getNopVfsPath(def); + + return XDslConstants.XDSL_SCHEMA_XDEF.equals(defVfsPath); + } + private static class RootTagSchemaMeta extends SchemaMeta { RootTagSchemaMeta( @@ -677,18 +684,14 @@ public class XLangTag extends XmlTagImpl { @Override protected IXDefNode doGetSchemaDefNode() { - IXDefinition schema = getSchemaDef(); - if (schema == null) { + IXDefinition def = getSchemaDef(); + if (def == null) { return null; } - String schemaVfsPath = XmlPsiHelper.getNopVfsPath(schema); - - IXDefNode defNode = schema.getRootNode(); - // 如果不是 *.xdef,则其根节点名称必须与其 x:schema 所定义的根节点名称保持一致 - if (!XDslConstants.XDSL_SCHEMA_XDEF.equals(schemaVfsPath) // - && !defNode.getTagName().equals(tagName) // - ) { + IXDefNode defNode = def.getRootNode(); + // 如果不是元模型(*.xdef),则其根节点名称必须与其 x:schema 所定义的根节点名称保持一致 + if (!isXDefDef(def) && !defNode.getTagName().equals(tagName)) { defNode = null; } 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 fc711ebc8..fcf31fc15 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 @@ -17,8 +17,6 @@ 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; - /** * 对字典的引用 * @@ -47,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(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/XLangStdDomainXdefRefReference.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangStdDomainXdefRefReference.java index a2cd2821b..cafd182b8 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,7 +25,6 @@ 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; @@ -103,6 +103,7 @@ public class XLangStdDomainXdefRefReference extends XLangReferenceBase { return target; } + /** @return 当前 xdef 文件中可引用的 xdef:name 名字,或者项目内所有可访问的 *.xdef 资源路径 */ @Override public Object @NotNull [] getVariants() { // Note: 在自动补全阶段,DSL 结构很可能是不完整的,只能从 xml 角度做分析 @@ -122,14 +123,12 @@ public class XLangStdDomainXdefRefReference extends XLangReferenceBase { return true; }); - return StreamEx.of( // - names.stream().sorted(XLANG_NAME_COMPARATOR) // - ) // - .append( // - ProjectFileHelper.findAllXdefNopVfsPaths(project) - .stream() - .sorted(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/template/XLangRootTagNameMacro.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/template/XLangRootTagNameMacro.java index 84c5b6b27..c7d34410f 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 ca9613f8c..4b13a35f2 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 28c91cedd..99b83d549 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,14 +5,17 @@ package io.nop.idea.plugin.utils; import java.io.File; import java.net.URL; import java.util.Collection; -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 +26,9 @@ 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; @@ -39,6 +45,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class ProjectFileHelper { + private static final Map>> nopVfsPathCaches = new ConcurrentHashMap<>(); /** * 与FileHelper.getFileUrl格式保持一致 @@ -103,27 +110,17 @@ public class ProjectFileHelper { return new ProjectAndLibrariesScope(project); } - /** 查找所有的 *.xdef 资源路径 */ - public static Collection findAllXdefNopVfsPaths(Project project) { - GlobalSearchScope scope = getSearchScope(project); - - return FilenameIndex.getAllFilesByExt(project, "xdef", scope) - .stream() - .map(ProjectFileHelper::getNopVfsStdPath) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); + /** 获取已缓存的项目内所有可访问的 *.xdef 资源路径列表(已排序) */ + public static Collection getCachedNopXDefVfsPaths(Project project) { + return getCachedNopVfsPaths(project).stream().filter((path) -> path.endsWith(".xdef")).toList(); } - /** 查找所有的 Nop 字典资源路径 */ - public static Collection findAllDictNopVfsPaths(Project project) { - GlobalSearchScope scope = getSearchScope(project); - - return FilenameIndex.getAllFilesByExt(project, "dict.yaml", scope) - .stream() - .map(ProjectFileHelper::getNopVfsStdPath) - .filter(Objects::nonNull) - .filter((path) -> path.startsWith("/dict/")) - .collect(Collectors.toSet()); + /** 获取已缓存的项目内所有可访问的字典名列表(已排序) */ + public static Collection getCachedNopDictNames(Project project) { + return getCachedNopVfsPaths(project).stream() + .filter((path) -> path.startsWith("/dict/") && path.endsWith(".dict.yaml")) + .map(ProjectFileHelper::getDictNameFromVfsPath) + .toList(); } /** 从 vfs 路径中获取字典名字 */ @@ -134,6 +131,13 @@ 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); @@ -146,9 +150,9 @@ public class ProjectFileHelper { Set vfsPaths = CollectionFactory.createSmallMemoryFootprintSet(); 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; @@ -157,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)); 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 f485bcbde..5c1775cca 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/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 7144f58c4..4ae43ad24 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 @@ -269,12 +269,15 @@ public abstract class BaseXLangPluginTestCase extends LightJavaCodeInsightFixtur doAssertCompletion(null, expectedText); } - /** 检查选中指定的补全项之后的文本是否与预期相符 */ + /** + * 检查选中指定的补全项之后的文本是否与预期相符 + *

+ * 注意:

    + *
  • 在仅有唯一的补全元素时,将自动完成补全,且不能再获取到补全列表;
  • + *
  • 只有在调用 `myFixture.completeBasic()` 后,才能完成补全,获得补全列表;
  • + *
+ */ 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/lang/TestXLangCompletions.java b/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangCompletions.java index 88190016e..4a4c5bf14 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 @@ -601,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", // -- Gitee From bac42f7f99ad24c26c496991cfdb01344ce501a8 Mon Sep 17 00:00:00 2001 From: flytreeleft Date: Sat, 18 Oct 2025 19:34:25 +0800 Subject: [PATCH 03/12] =?UTF-8?q?nop-idea-pugin:=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=AF=B9=20dsl=20=E6=A0=B9=E8=8A=82=E7=82=B9=E6=A0=87=E7=AD=BE?= =?UTF-8?q?=E5=90=8D=E5=8C=B9=E9=85=8D=E6=A0=A1=E9=AA=8C=EF=BC=8C=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=E6=A0=87=E7=AD=BE=E5=8F=AF=E9=87=8D=E5=A4=8D=E6=80=A7?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../idea/plugin/annotator/XLangAnnotator.java | 59 ++++++++++++++----- .../io/nop/idea/plugin/lang/psi/XLangTag.java | 14 ++++- .../messages/NopPluginBundle.properties | 2 + .../messages/NopPluginBundle_zh.properties | 2 + 4 files changed, 58 insertions(+), 19 deletions(-) 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 4a1e6d293..2c1c17008 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; @@ -37,6 +39,7 @@ import io.nop.idea.plugin.utils.XmlPsiHelper; import io.nop.idea.plugin.vfs.NopVirtualFile; import io.nop.xlang.xdef.IStdDomainHandler; import io.nop.xlang.xdef.IXDefAttribute; +import io.nop.xlang.xdef.IXDefNode; import io.nop.xlang.xdef.XDefTypeDecl; import io.nop.xlang.xdef.domain.StdDomainRegistry; import io.nop.xlang.xpl.utils.XplParseHelper; @@ -131,7 +134,7 @@ public class XLangAnnotator implements Annotator { private void checkTag(@NotNull AnnotationHolder holder, @NotNull XLangTag tag) { if (tag.getSchemaDefNode() != null) { - checkTagValue(holder, tag); + checkTagBySchemaDefNode(holder, tag); return; } @@ -142,25 +145,49 @@ public class XLangAnnotator implements Annotator { return; } - XmlToken startTagName = XmlTagUtil.getStartTagNameElement(tag); - if (startTagName != null) { - errorAnnotation(holder, - startTagName.getTextRange(), - "xlang.annotation.tag.not-defined", - startTagName.getText()); + // Note: 若根节点被定义为 xdef:unknown-tag,则 tag.getSchemaDef() 不会返回 null + String expectedRootTag = parentTag == null && tag.getSchemaDef() != null // + ? tag.getSchemaDef().getRootNode().getTagName() : null; + XmlToken[] tokens = new XmlToken[] { + XmlTagUtil.getStartTagNameElement(tag), XmlTagUtil.getEndTagNameElement(tag) + }; + for (XmlToken token : tokens) { + if (token != null) { + if (expectedRootTag != null) { + errorAnnotation(holder, + token.getTextRange(), + "xlang.annotation.tag.expected-root", + expectedRootTag); + } else { + errorAnnotation(holder, token.getTextRange(), "xlang.annotation.tag.not-defined", token.getText()); + } + } } + } - 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) { + IXDefNode xdefNode = tag.getSchemaDefNode(); + // 检查标签可重复性 + if (!xdefNode.isAllowMultiple() && tag.getParentTag() != null) { + for (PsiElement child : tag.getParentTag().getChildren()) { + // 检查在其之前的重复节点 + if (child == tag) { + break; + } + + if (child instanceof XLangTag t // + && Objects.equals(t.getName(), tag.getName()) // + ) { + errorAnnotation(holder, + getStartTagName(tag).getTextRange(), + "xlang.annotation.tag.multiple-tag-not-allowed", + tag.getName()); + return; + } + } } - } - private void checkTagValue(@NotNull AnnotationHolder holder, @NotNull XLangTag tag) { - // TODO 标签唯一性检查:未显式声明的,只能有一个,不能重复 + // 检查节点内容 XDefTypeDecl xdefValue = tag.getSchemaDefNodeXdefValue(); TextRange textRange = tag.getValue().getTextRange(); String bodyText = tag.hasChildTag() ? null : tag.getBodyText(); 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 2f61ed32d..3f248697e 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 @@ -190,7 +190,7 @@ public class XLangTag extends XmlTagImpl { } /** @see SchemaMeta#getSchemaDef() */ - private IXDefinition getSchemaDef() { + public IXDefinition getSchemaDef() { return getSchemaMeta().getSchemaDef(); } @@ -267,6 +267,11 @@ public class XLangTag extends XmlTagImpl { return getSchemaMeta().getXDslKeys(); } + /** 当前标签是否在元模型 *.xdef 中 */ + public boolean isInXDef() { + return isXDefDef(getSchemaDef()); + } + /** @see SchemaMeta#isInXDefXDef() */ private boolean isInXDefXDef() { return getSchemaMeta().isInXDefXDef(); @@ -690,8 +695,11 @@ public class XLangTag extends XmlTagImpl { } IXDefNode defNode = def.getRootNode(); - // 如果不是元模型(*.xdef),则其根节点名称必须与其 x:schema 所定义的根节点名称保持一致 - if (!isXDefDef(def) && !defNode.getTagName().equals(tagName)) { + // 如果不是元模型(*.xdef),则其根节点名称必须与其 x:schema 所定义的根节点名称保持一致, + // 除非根节点被定义为 xdef:unknown-tag + if (!isXDefDef(def) && !defNode.isUnknownTag() // + && !defNode.getTagName().equals(tagName) // + ) { defNode = null; } 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 943976c38..b8c11f3a1 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,12 @@ 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.expected-root = Root tag should be <{0}/> 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 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 306feadc2..225e50f3d 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,12 @@ 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.expected-root = \u6839\u6807\u7B7E\u5E94\u8BE5\u4E3A <{0}/> 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 -- Gitee From f6b2a77ebdac61e0fa150ddafea076112733607f Mon Sep 17 00:00:00 2001 From: flytreeleft Date: Sat, 18 Oct 2025 19:39:02 +0800 Subject: [PATCH 04/12] =?UTF-8?q?nop-idea-pugin:=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=AF=B9=E6=A0=B9=E8=8A=82=E7=82=B9=E6=A0=87=E7=AD=BE=E5=90=8D?= =?UTF-8?q?=E7=9A=84=E4=BB=A3=E7=A0=81=E8=A1=A5=E5=85=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lang/reference/XLangTagReference.java | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) 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 7bd29bdcf..26e18a09f 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 @@ -24,6 +24,7 @@ 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; @@ -63,15 +64,39 @@ public class XLangTagReference extends XLangReferenceBase { /** @return 可替换 {@link #myElement} 的标签名(即,在元模型中其父标签所定义的子标签)列表 */ @Override public Object @NotNull [] getVariants() { - // TODO 对于 DSL 的根节点,其标签名只能为其 xdef 中的根节点名 // Note: 在自动补全阶段,DSL 结构很可能是不完整的,只能从 xml 角度做分析 XLangTag tag = (XLangTag) myElement; + String tagNs = tag.getNamespacePrefix(); + XDefKeys tagXDefKeys = tag.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 (!tag.isInXDef()) { + IXDefNode rootDefNode = tag.getSchemaDef().getRootNode(); + + return new LookupElement[] { + // 对于非元模型的根节点,其标签名只能为其 xdef 中的根节点名 + !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(); @@ -95,7 +120,7 @@ public class XLangTagReference extends XLangReferenceBase { return result.stream() .sorted((a, b) -> XLANG_NAME_COMPARATOR.compare(a.getTagName(), b.getTagName())) - .map((defNode) -> lookupTag(defNode, !tagNs.isEmpty())) + .map((defNode) -> lookupTag(defNode, trimLookupTagNs)) .toArray(LookupElement[]::new); } @@ -127,10 +152,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); } } -- Gitee From 6f6fb94194cfa19289310031f20fb332ef64651a Mon Sep 17 00:00:00 2001 From: flytreeleft Date: Sun, 19 Oct 2025 15:12:42 +0800 Subject: [PATCH 05/12] =?UTF-8?q?nop-idea-pugin:=20=E4=BF=AE=E5=A4=8D=20id?= =?UTF-8?q?ea=202025=20=E4=B8=AD=E9=80=9A=E8=BF=87=20ctrl=20+=20=E7=82=B9?= =?UTF-8?q?=E5=87=BB=20=E4=B8=8D=E8=83=BD=E8=B7=B3=E8=BD=AC=20vfs=20?= =?UTF-8?q?=E8=B5=84=E6=BA=90=E6=96=87=E4=BB=B6=E7=9A=84=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/nop/idea/plugin/lang/psi/XLangTag.java | 5 +---- .../lang/reference/XLangReferenceHelper.java | 6 +++++- .../nop/idea/plugin/vfs/NopVirtualFile.java | 21 +++++++++++++++---- 3 files changed, 23 insertions(+), 9 deletions(-) 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 3f248697e..8bdc249b4 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 @@ -574,15 +574,12 @@ public class XLangTag extends XmlTagImpl { } // 根节点发生了 schema 相关的更新(包括 schema 的依赖的变更),或者标签名发生了变化 else if (schemaMeta != null // - && (isRootTag() // - || !Objects.equals(schemaMeta.tagName, tagName) // - ) // + && (!tagName.equals(schemaMeta.tagName) || isRootTag()) // ) { SchemaMeta newSchemaMeta = createSchemaMeta(); if (!Objects.equals(schemaMeta, newSchemaMeta)) { clearSchemaMeta(); - schemaMeta = newSchemaMeta; } } 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 c415b4864..7f4c9dcf5 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 @@ -96,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); 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 12b8cafab..1fdd06433 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 + 鼠标移动 所显示的元素信息 */ -- Gitee From 395ea50936b33323f4121934407653deeb029521 Mon Sep 17 00:00:00 2001 From: flytreeleft Date: Sun, 19 Oct 2025 19:25:28 +0800 Subject: [PATCH 06/12] =?UTF-8?q?nop-idea-pugin:=20=E9=A2=84=E7=BD=AE=20id?= =?UTF-8?q?ea=202025+=20=E7=9A=84=E6=9E=84=E5=BB=BA=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nop-dev-tools/nop-idea-plugin/.gitignore | 1 + nop-dev-tools/nop-idea-plugin/build.gradle.kts | 16 +++++++++++++++- .../gradle/wrapper/gradle-wrapper.properties | 2 ++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 nop-dev-tools/nop-idea-plugin/.gitignore diff --git a/nop-dev-tools/nop-idea-plugin/.gitignore b/nop-dev-tools/nop-idea-plugin/.gitignore new file mode 100644 index 000000000..138c50ca6 --- /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 8853aac6c..b79326be8 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 f8bedccfb..342a59647 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 -- Gitee From 84de61df5aa4d62dfd76a116ffe7cec09a34c6f6 Mon Sep 17 00:00:00 2001 From: flytreeleft Date: Sun, 19 Oct 2025 21:06:53 +0800 Subject: [PATCH 07/12] =?UTF-8?q?nop-idea-pugin:=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=AF=B9=E4=B8=8D=E5=81=9A=E5=90=8D=E5=AD=97=E7=A9=BA=E9=97=B4?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=EF=BC=88xdef:check-ns=20=E4=B8=AD=E6=9C=AA?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=EF=BC=89=E7=9A=84=E5=B1=9E=E6=80=A7=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E6=96=87=E6=A1=A3=EF=BC=8C=E4=B8=94=E4=B8=8D=E5=81=9A?= =?UTF-8?q?=E6=9C=89=E6=95=88=E6=80=A7=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../doc/XLangDocumentationProvider.java | 2 +- .../idea/plugin/lang/XLangDocumentation.java | 17 +++++++++--- .../idea/plugin/lang/psi/XLangAttribute.java | 13 +++++++++ .../io/nop/idea/plugin/lang/psi/XLangTag.java | 27 +++++++++++++------ .../reference/XLangAttributeReference.java | 7 +++++ .../messages/NopPluginBundle.properties | 1 + .../messages/NopPluginBundle_zh.properties | 1 + 7 files changed, 55 insertions(+), 13 deletions(-) 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 02d895b1a..3412cf9e2 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 @@ -32,7 +32,7 @@ import static com.intellij.psi.xml.XmlTokenType.XML_NAME; public class XLangDocumentationProvider extends AbstractDocumentationProvider { /** - * 文档生成函数 + * 文档生成函数:对于没有可识别的引用的元素,将不会调用该接口 *

* 默认鼠标移动时的文档也由该函数生成 {@link #generateHoverDoc} * 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 f40eae040..a01b40b68 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/psi/XLangAttribute.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangAttribute.java index 64bb2578c..7cf540169 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; /** @@ -93,4 +96,14 @@ public class XLangAttribute extends XmlAttributeImpl { return defAttr; } + + /** 带名字空间的附加属性(即,不做名字空间校验的属性) */ + public static class XDefAttributeNotInCheckNS extends XDefAttribute { + private static final XDefTypeDecl STD_DOMAIN_ANY = new XDefTypeDeclParser().parseFromText(null, "any"); + + public XDefAttributeNotInCheckNS(String name) { + setName(name); + setType(STD_DOMAIN_ANY); + } + } } 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 8bdc249b4..175ec4c5e 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 @@ -217,7 +217,7 @@ public class XLangTag extends XmlTagImpl { attrName = changeNamespace(attrName, getXDefKeys().NS, XDefKeys.DEFAULT.NS); } - IXDefAttribute attr = getXDefNodeAttr(getSchemaDefNode(), attrName); + IXDefAttribute attr = getXDefNodeAttr(getSchemaDef(), getSchemaDefNode(), attrName); if (attr == null || attr.isUnknownAttr()) { XlibTagMeta xlibTag = getXlibTagMeta(); @@ -249,7 +249,7 @@ public class XLangTag extends XmlTagImpl { // Note: xdsl.xdef 的属性在固定的名字空间 x 中声明 attrName = changeNamespace(attrName, getXDslKeys().NS, XDslKeys.DEFAULT.NS); - return getXDefNodeAttr(getXDslDefNode(), attrName); + return getXDefNodeAttr(null, getXDslDefNode(), attrName); } /** @see SchemaMeta#getSelfDefNode() */ @@ -441,8 +441,10 @@ public class XLangTag extends XmlTagImpl { } String mainTitle = attrName; + IXDefinition def = null; IXDefNode defNode; if (isInXDefXDef() && XDefKeys.DEFAULT.NS.equals(attrNs)) { + def = getSchemaMeta().getSelfDef(); defNode = getSelfDefNode(); } // else if (XDslKeys.DEFAULT.NS.equals(attrNs) || getXDslKeys().NS.equals(attrNs)) { @@ -452,14 +454,16 @@ public class XLangTag extends XmlTagImpl { else { attrName = changeNamespace(attrName, getXDefKeys().NS, XDefKeys.DEFAULT.NS); + def = getSchemaMeta().getSelfDef(); defNode = getSelfDefNode(); // 对于 *.xdef,优先取其自身定义节点上的属性文档 if (defNode == null || defNode.getAttribute(attrName) == null) { + def = getSchemaDef(); defNode = getSchemaDefNode(); } } - IXDefAttribute defAttr = getXDefNodeAttr(defNode, attrName); + IXDefAttribute defAttr = getXDefNodeAttr(def, defNode, attrName); if (defAttr == null) { return null; } @@ -474,6 +478,7 @@ public class XLangTag extends XmlTagImpl { XLangDocumentation doc = new XLangDocumentation(defAttr); doc.setMainTitle(mainTitle); + doc.setAdditional(defAttr instanceof XLangAttribute.XDefAttributeNotInCheckNS); IXDefComment nodeComment = defNode.getComment(); if (nodeComment != null) { @@ -492,7 +497,7 @@ public class XLangTag extends XmlTagImpl { } /** 获取 {@link IXDefNode} 上指定属性的 xdef 定义 */ - private IXDefAttribute getXDefNodeAttr(IXDefNode xdefNode, String attrName) { + private IXDefAttribute getXDefNodeAttr(IXDefinition def, IXDefNode defNode, String attrName) { String xmlnsPrefix = "xmlns:"; if (attrName.startsWith(xmlnsPrefix)) { String attrValue = getAttributeValue(attrName); @@ -508,18 +513,24 @@ public class XLangTag extends XmlTagImpl { return at; } - if (xdefNode == null) { + if (defNode == null) { return null; } - IXDefAttribute attr = xdefNode.getAttribute(attrName); + IXDefAttribute attr = defNode.getAttribute(attrName); if (attr != null) { return attr; } + // 对于 xdef:check-ns 中不做校验的名字空间,该属性为任意类型 + String attrNameNs = StringHelper.getNamespace(attrName); + if (attrNameNs != null && def != null && !def.getXdefCheckNs().contains(attrNameNs)) { + return new XLangAttribute.XDefAttributeNotInCheckNS(attrName); + } + // Note: 在 IXDefNode 中,对 xdef:unknown-attr 只记录了类型,并没有 IXDefAttribute 实体, // 其处理逻辑见 XDefinitionParser#parseNode - XDefTypeDecl xdefUnknownAttrType = xdefNode.getXdefUnknownAttr(); + XDefTypeDecl xdefUnknownAttrType = defNode.getXdefUnknownAttr(); if (xdefUnknownAttrType != null) { XDefAttribute at = new XDefAttribute() { @Override @@ -534,7 +545,7 @@ public class XLangTag extends XmlTagImpl { // Note: 在需要时,通过节点位置再定位具体的属性位置 SourceLocation loc = null; // 在存在节点继承的情况下,选择最上层定义的同类型的 unknown-attr 属性 - IXDefNode refNode = xdefNode; + IXDefNode refNode = defNode; while (refNode != null) { if (refNode.getXdefUnknownAttr() != xdefUnknownAttrType) { break; 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 f86062765..f0f6a374f 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 @@ -57,6 +57,13 @@ public class XLangAttributeReference extends XLangReferenceBase { return null; } + // 对于带名字空间的附加属性,其定义直接引用其自身 + if (defAttr instanceof XLangAttribute.XDefAttributeNotInCheckNS) { + 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; 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 b8c11f3a1..fcc8441ae 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 @@ -39,6 +39,7 @@ 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} 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 225e50f3d..a80c840ed 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 @@ -39,6 +39,7 @@ 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} -- Gitee From 773182eda8aca8aef98d251fc866ce5276a29921 Mon Sep 17 00:00:00 2001 From: flytreeleft Date: Fri, 24 Oct 2025 21:02:38 +0800 Subject: [PATCH 08/12] =?UTF-8?q?nop-idea-pugin:=20=E7=8B=AC=E7=AB=8B?= =?UTF-8?q?=E5=87=BA=20XLangTagMeta=20=E4=BB=A5=E8=B4=9F=E8=B4=A3=E6=9E=84?= =?UTF-8?q?=E9=80=A0=20XLangTag=20=E6=A0=87=E7=AD=BE=E5=AF=B9=E5=BA=94?= =?UTF-8?q?=E7=9A=84=E5=AE=9A=E4=B9=89=E8=8A=82=E7=82=B9=20IXDefNode=20?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/nop/idea/plugin/lang/psi/XLangTag.java | 37 +- .../idea/plugin/lang/psi/XLangTagMeta.java | 351 ++++++++++++ .../nop/idea/plugin/utils/XDefPsiHelper.java | 56 +- .../nop/idea/plugin/utils/XmlPsiHelper.java | 4 +- .../idea/plugin/BaseXLangPluginTestCase.java | 6 +- .../idea/plugin/lang/TestXLangTagMeta.java | 539 ++++++++++++++++++ .../test/resources/_vfs/test/lang/lang.xdef | 10 + 7 files changed, 948 insertions(+), 55 deletions(-) create mode 100644 nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangTagMeta.java create mode 100644 nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangTagMeta.java create mode 100644 nop-idea-plugin/src/test/resources/_vfs/test/lang/lang.xdef 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 175ec4c5e..a72e50513 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 @@ -72,12 +72,27 @@ public class XLangTag extends XmlTagImpl { XDefConstants.STD_DOMAIN_XDEF_REF); private SchemaMeta schemaMeta; + private XLangTagMeta tagMeta; @Override public String toString() { return getClass().getSimpleName() + ':' + getElementType() + "('" + getName() + "')"; } + public synchronized XLangTagMeta getTagMeta() { + if (tagMeta == null) { + tagMeta = ProjectEnv.withProject(getProject(), () -> XLangTagMeta.create(this)); + } + return this.tagMeta; + } + + /** 标签存在被复用的可能,因此,需显式清理与之绑定的数据 */ + @Override + public void clearCaches() { + tagMeta = null; + super.clearCaches(); + } + @Override public boolean skipValidation() { // Note: 禁用 xml 的校验 @@ -217,7 +232,7 @@ public class XLangTag extends XmlTagImpl { attrName = changeNamespace(attrName, getXDefKeys().NS, XDefKeys.DEFAULT.NS); } - IXDefAttribute attr = getXDefNodeAttr(getSchemaDef(), getSchemaDefNode(), attrName); + IXDefAttribute attr = getXDefNodeAttr(getSchemaDefNode(), attrName); if (attr == null || attr.isUnknownAttr()) { XlibTagMeta xlibTag = getXlibTagMeta(); @@ -249,7 +264,7 @@ public class XLangTag extends XmlTagImpl { // Note: xdsl.xdef 的属性在固定的名字空间 x 中声明 attrName = changeNamespace(attrName, getXDslKeys().NS, XDslKeys.DEFAULT.NS); - return getXDefNodeAttr(null, getXDslDefNode(), attrName); + return getXDefNodeAttr(getXDslDefNode(), attrName); } /** @see SchemaMeta#getSelfDefNode() */ @@ -441,10 +456,8 @@ public class XLangTag extends XmlTagImpl { } String mainTitle = attrName; - IXDefinition def = null; IXDefNode defNode; if (isInXDefXDef() && XDefKeys.DEFAULT.NS.equals(attrNs)) { - def = getSchemaMeta().getSelfDef(); defNode = getSelfDefNode(); } // else if (XDslKeys.DEFAULT.NS.equals(attrNs) || getXDslKeys().NS.equals(attrNs)) { @@ -454,16 +467,14 @@ public class XLangTag extends XmlTagImpl { else { attrName = changeNamespace(attrName, getXDefKeys().NS, XDefKeys.DEFAULT.NS); - def = getSchemaMeta().getSelfDef(); defNode = getSelfDefNode(); // 对于 *.xdef,优先取其自身定义节点上的属性文档 if (defNode == null || defNode.getAttribute(attrName) == null) { - def = getSchemaDef(); defNode = getSchemaDefNode(); } } - IXDefAttribute defAttr = getXDefNodeAttr(def, defNode, attrName); + IXDefAttribute defAttr = getXDefNodeAttr(defNode, attrName); if (defAttr == null) { return null; } @@ -497,7 +508,7 @@ public class XLangTag extends XmlTagImpl { } /** 获取 {@link IXDefNode} 上指定属性的 xdef 定义 */ - private IXDefAttribute getXDefNodeAttr(IXDefinition def, IXDefNode defNode, String attrName) { + private IXDefAttribute getXDefNodeAttr(IXDefNode defNode, String attrName) { String xmlnsPrefix = "xmlns:"; if (attrName.startsWith(xmlnsPrefix)) { String attrValue = getAttributeValue(attrName); @@ -522,11 +533,11 @@ public class XLangTag extends XmlTagImpl { return attr; } - // 对于 xdef:check-ns 中不做校验的名字空间,该属性为任意类型 - String attrNameNs = StringHelper.getNamespace(attrName); - if (attrNameNs != null && def != null && !def.getXdefCheckNs().contains(attrNameNs)) { - return new XLangAttribute.XDefAttributeNotInCheckNS(attrName); - } +// // 对于 xdef:check-ns 中不做校验的名字空间,该属性为任意类型 +// String attrNameNs = StringHelper.getNamespace(attrName); +// if (attrNameNs != null && def != null && !def.getXdefCheckNs().contains(attrNameNs)) { +// return new XLangAttribute.XDefAttributeNotInCheckNS(attrName); +// } // Note: 在 IXDefNode 中,对 xdef:unknown-attr 只记录了类型,并没有 IXDefAttribute 实体, // 其处理逻辑见 XDefinitionParser#parseNode 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 000000000..16c3f4a5f --- /dev/null +++ b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangTagMeta.java @@ -0,0 +1,351 @@ +/* + * 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.Set; + +import io.nop.commons.util.StringHelper; +import io.nop.idea.plugin.utils.XDefPsiHelper; +import io.nop.idea.plugin.utils.XmlPsiHelper; +import io.nop.xlang.xdef.IXDefNode; +import io.nop.xlang.xdef.IXDefinition; +import io.nop.xlang.xdef.XDefConstants; +import io.nop.xlang.xdef.XDefKeys; +import io.nop.xlang.xdsl.XDslConstants; +import io.nop.xlang.xdsl.XDslKeys; +import io.nop.xlang.xpl.xlib.XlibConstants; + +/** + * 标签的{@link IXDefNode 定义节点}信息 + * + * @author flytreeleft + * @date 2025-10-21 + */ +public class XLangTagMeta { + private static final XLangTagMeta UNKNOWN_META = new XLangTagMeta(null); + + final String tagName; + XLangTagMeta parent; + + /** + * 当前标签在元模型中所对应的定义节点: + * 元模型一般由根节点上的 x:schema 指定, + * 但 Xpl 类型节点的元模型始终为 /nop/schema/xpl.xdef + */ + IXDefNode defNodeInSchema; + /** + * 当前标签所在的元模型中通过 xdef:check-ns 所设置的待校验的名字空间列表: + * 节点或属性的名字空间若在该列表中,则其必须在元模型中显式定义 + */ + Set checkNsInSchema; + + /** + * 若当前标签在元模型文件(*.xdef)中, + * 则用于记录当前标签在该元模型中的定义 + */ + IXDefNode defNodeInSelfSchema; + + /** + * 在元模型 /nop/schema/xdef.xdef 绑定的名字空间(默认为 xdef)下, + * 当前标签在该元模型中所对应的定义节点: + * 仅当前标签在元模型文件(*.xdef)中且其名字空间与该元模型的名字空间相同时有效 + */ + NsDefNode defNodeInXdefNs; + /** + * 在元模型 /nop/schema/xdsl.xdef 绑定的名字空间(默认为 x)下, + * 当前标签在该元模型中所对应的定义节点:所有标签的节点均由该元模型定义 + */ + NsDefNode defNodeInXdslNs; + + XLangTagMeta(String tagName) { + this.tagName = tagName; + } + + public String getTagName() { + return tagName; + } + + public IXDefNode getDefNodeInSchema() { + return defNodeInSchema; + } + + public IXDefNode getDefNodeInSelfSchema() { + return defNodeInSelfSchema; + } + + /** 是否未定义 */ + public boolean isUnknown() { + return defNodeInSchema == null; + } + + /** 当前标签是否对应元模型(*.xdef)定义节点 */ + public boolean isXdefDefNode() { + return defNodeInXdefNs != null; + } + + /** 当前标签是否对应 Xpl 定义节点 */ + public boolean isXplDefNode() { + if (defNodeInSchema == null) { + return false; + } else if (XDefPsiHelper.isXplDefNode(defNodeInSchema)) { + return true; + } + + String vfsPath = XmlPsiHelper.getNopVfsPath(defNodeInSchema); + if (isXplXdefFile(vfsPath)) { + return true; + } + + // xlib.xdef 中的 source 标签被设置为 xml 类型,是因为在获取 XplLib 模型的时候会根据 xlib.xdef 来解析, + // 但此时这个 source 段无法自动进行编译,必须结合它的 outputMode 和 attrs 配置等才能决定。 + // 因此,需将其子节点同样视为 Xpl 节点处理 + return XDslConstants.XDSL_SCHEMA_XLIB.equals(vfsPath) + && XlibConstants.SOURCE_NAME.equals(tagName) + && XDefConstants.STD_DOMAIN_XML.equals(XDefPsiHelper.getDefNodeType(defNodeInSchema)); + } + + /** + * Note: 需要在 {@link io.nop.idea.plugin.resource.ProjectEnv#withProject ProjectEnv#withProject} + * 中调用该函数 + */ + public static XLangTagMeta create(XLangTag tag) { + String tagName = tag.getName(); + XLangTag parentTag = tag.getParentTag(); + + // 根节点 + if (parentTag == null) { + return createForRootTag(tagName, tag); + } + return createForChildTag(parentTag, tagName); + } + + private static XLangTagMeta createForRootTag(String tagName, XLangTag tag) { + // Note: 若标签在 Xpl 脚本中,则将返回 /nop/schema/xpl.xdef + String schemaUrl = XDefPsiHelper.getSchemaPath(tag); + if (schemaUrl == null) { + return UNKNOWN_META; + } + + // 若其元模型为 /nop/schema/xdef.xdef,则其自身也为元模型(*.xdef) + boolean inSchema = XDslConstants.XDSL_SCHEMA_XDEF.equals(schemaUrl) // + || isXdefFile(tag.getContainingFile().getName()); + + IXDefinition schema = XDefPsiHelper.loadSchema(schemaUrl); + // 非 xdef/xpl 模型,全部视为 xdsl + IXDefinition xdslSchema = XDefPsiHelper.getXDslDef(); + if (schema == null && !inSchema && !isXplXdefFile(schemaUrl)) { + schema = xdslSchema; + } + // 若元模型加载失败,则不做识别 + if (schema == null) { + return UNKNOWN_META; + } + + IXDefNode defNodeInSchema = schema.getRootNode(); + // 如果其不在元模型中,则其根节点标签名必须与其 x:schema 所定义的根节点标签名保持一致, + // 除非根节点被定义为 xdef:unknown-tag + if (!inSchema && !defNodeInSchema.isUnknownTag() && !defNodeInSchema.getTagName().equals(tagName)) { + return UNKNOWN_META; + } + + // 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()); + } + } + + IXDefinition xdefSchema = XDefPsiHelper.getXdefDef(); + String xdefNs = XmlPsiHelper.getXmlnsForUrl(tag, XDslConstants.XDSL_SCHEMA_XDEF); + NsDefNode defNodeInXdefNs = // + inSchema ? NsDefNode.create(xdefNs, XDefKeys.DEFAULT.NS, xdefSchema) : null; + + String xdslNs = XmlPsiHelper.getXmlnsForUrl(tag, XDslConstants.XDSL_SCHEMA_XDSL); + NsDefNode defNodeInXdslNs = NsDefNode.create(xdslNs, XDslKeys.DEFAULT.NS, xdslSchema); + + XLangTagMeta tagMeta = new XLangTagMeta(tagName); + tagMeta.defNodeInSchema = defNodeInSchema; + tagMeta.checkNsInSchema = schema.getXdefCheckNs(); + tagMeta.defNodeInSelfSchema = selfSchema != null ? selfSchema.getRootNode() : null; + tagMeta.defNodeInXdefNs = defNodeInXdefNs; + tagMeta.defNodeInXdslNs = defNodeInXdslNs; + + return tagMeta; + } + + private static XLangTagMeta createForChildTag(XLangTag parentTag, String tagName) { + XLangTagMeta parentTagMeta = parentTag.getTagMeta(); + // 无定义节点 + if (parentTagMeta.isUnknown()) { + return UNKNOWN_META; + } + + IXDefNode defNodeInSchema = null; + // 1. 先按名字空间匹配对应元模型中定义的节点 + // - 带 xpl 名字空间的节点的父节点只能是 Xpl 类型时才有效 + String tagNs = StringHelper.getNamespace(tagName); + NsDefNode[] nsDefNodes = new NsDefNode[] { + parentTagMeta.defNodeInXdefNs, parentTagMeta.defNodeInXdslNs + }; + for (int i = 0; i < nsDefNodes.length; i++) { + NsDefNode nsDefNode = nsDefNodes[i]; + if (nsDefNode == null) { + continue; + } + + NsDefNode childNsDefNode = nsDefNode.getChild(tagName); + // 若当前标签的命名空间与相应元模型的名字空间相同,则其定义节点必须存在且确定的 + if (nsDefNode.xmlNs.equals(tagNs)) { + if (childNsDefNode != null && !childNsDefNode.defNode.isUnknownTag()) { + defNodeInSchema = childNsDefNode.defNode; + } else { + // TODO 在元模型 /xx/xx.xdef 中未定义 xx:xx + return UNKNOWN_META; + } + } + + // 就地更新为子节点 + nsDefNodes[i] = childNsDefNode; + } + NsDefNode defNodeInXdefNs = nsDefNodes[0]; + NsDefNode defNodeInXdslNs = nsDefNodes[1]; + + // 2. 若父节点为 Xpl 类型节点,则直接从 xpl.xdef 中获取节点(含带 xpl 名字空间的节点) + if (parentTagMeta.isXplDefNode()) { + defNodeInXdefNs = null; + defNodeInSchema = getChildDefNode(XDefPsiHelper.getXplDef().getRootNode(), tagName); + } + + // 3. 从当前标签的父标签的定义节点中获取其定义节点 + if (defNodeInSchema == null) { + defNodeInSchema = getChildDefNode(parentTagMeta.defNodeInSchema, tagName); + } + + // 4. 带名字空间的节点:其在元模型中已显式定义,或者为自由增添的无约束节点 + if (tagNs != null) { + // Note: 在 xdsl.xdef 中的定义节点,不属于元模型定义节点 + if (defNodeInXdslNs != null && defNodeInSchema == defNodeInXdslNs.defNode) { + defNodeInXdefNs = null; + } + + // 若为 xdef.xdef 交叉定义(自己定义自己),则取定义节点 xdef:unknown-tag + if (defNodeInXdefNs != null // + && !defNodeInXdefNs.xmlNs.equals(tagNs) // + && defNodeInXdefNs.defNs.equals(tagNs) // + ) { + defNodeInSchema = defNodeInXdefNs.defNode; + } + // 需要校验的名字空间下的节点必须在其元模型中显式定义 + else if (parentTagMeta.checkNsInSchema != null // + && parentTagMeta.checkNsInSchema.contains(tagNs) // + ) { + if (defNodeInSchema != null && defNodeInSchema.isUnknownTag()) { + // TODO 名字空间 xx 下的标签必须在 /xx/xx.xdef 中显式定义 + return UNKNOWN_META; + } + } + // 否则,其定义节点必然为 xdsl 的 xdef:unknown-tag 节点 + else if (defNodeInSchema == null) { + defNodeInSchema = getChildDefNode(XDefPsiHelper.getXDslDef().getRootNode(), tagName); + } + } + + if (defNodeInSchema == null) { + return UNKNOWN_META; + } + + XLangTagMeta tagMeta = new XLangTagMeta(tagName); + tagMeta.parent = parentTagMeta; + tagMeta.defNodeInSchema = defNodeInSchema; + tagMeta.checkNsInSchema = parentTagMeta.checkNsInSchema; + tagMeta.defNodeInSelfSchema = getChildDefNode(parentTagMeta.defNodeInSelfSchema, tagName); + tagMeta.defNodeInXdefNs = defNodeInXdefNs; + tagMeta.defNodeInXdslNs = defNodeInXdslNs; + + return tagMeta; + } + + private static boolean isXdefFile(String filename) { + return filename.endsWith(".xdef"); + } + + private static boolean isXplXdefFile(String filename) { + return XDslConstants.XDSL_SCHEMA_XPL.equals(filename); + } + + private static IXDefNode getChildDefNode(IXDefNode defNode, String tagName) { + return defNode != null ? defNode.getChild(tagName) : null; + } + + /** 通过名字空间引入的元模型中的定义节点 */ + private static class NsDefNode { + /** + * 引入 {@link #defNode} 的元模型时所绑定的名字空间, + * 比如,在元模型 /nop/schema/xdef.xdef 中通过 + * xmlns:meta="/nop/schema/xdef.xdef" 引入 xdef 模型, + * 则 meta 便为该名字空间,所有在该名字空间下的节点和属性均由该 xdef 模型定义 + */ + final String xmlNs; + /** + * 定义 {@link #defNode} 时所限定的名字空间, + * 比如,在元模型 /nop/schema/xdef.xdef 中定义的节点和属性均被限定在 + * xdef 名字空间下,因此,xdef 即为其值 + */ + final String defNs; + + /** 定义节点 */ + final IXDefNode defNode; + + NsDefNode(String xmlNs, String defNs, IXDefNode defNode) { + this.xmlNs = xmlNs; + this.defNs = defNs; + + this.defNode = defNode; + } + + public NsDefNode getChild(String childTagName) { + IXDefNode childDefNode; + if (childTagName.startsWith(xmlNs + ':')) { + if (!xmlNs.equals(defNs)) { + childTagName = defNs + childTagName.substring(xmlNs.length()); + } + childDefNode = getChildDefNode(defNode, childTagName); + } else { + childDefNode = defNode.getXdefUnknownTag(); + } + + if (childDefNode == null) { + return null; + } + // Note: 复用实例,避免反复新建 + if (defNode == childDefNode) { + return this; + } + return new NsDefNode(xmlNs, defNs, childDefNode); + } + + public static NsDefNode create(String xmlNs, String defNs, IXDefinition schema) { + if (schema == null) { + return null; + } + + if (StringHelper.isBlank(xmlNs)) { + xmlNs = defNs; + } + + return new NsDefNode(xmlNs, defNs, schema.getRootNode()); + } + } +} 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 2ccea4ffe..ca57c57fc 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 后缀 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 148f4aff5..f83decc9c 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 @@ -291,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/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 4ae43ad24..56145862b 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 @@ -218,7 +218,7 @@ public abstract class BaseXLangPluginTestCase extends LightJavaCodeInsightFixtur * 得到的是原始元素的引用元素 */ protected PsiElement getOriginalElementAtCaret() { - doAssertCaretExists(); + assertCaretExists(); return myFixture.getFile().findElementAt(myFixture.getCaretOffset()); } @@ -229,7 +229,7 @@ public abstract class BaseXLangPluginTestCase extends LightJavaCodeInsightFixtur /** 找到光标位置的 {@link XLangReference} 或者其他类型的唯一引用 */ protected PsiReference findReferenceAtCaret() { - doAssertCaretExists(); + assertCaretExists(); // 实际有多个引用时,将构造返回 PsiMultiReference, // 其会按 PsiMultiReference#COMPARATOR 对引用排序得到优先引用, @@ -260,7 +260,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); } 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 000000000..7eb959f37 --- /dev/null +++ b/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangTagMeta.java @@ -0,0 +1,539 @@ +/* + * 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.XLangTag; +import io.nop.idea.plugin.lang.psi.XLangTagMeta; +import io.nop.idea.plugin.utils.XmlPsiHelper; + +/** + * + * @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" + > + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertTrue(tagMeta.isXdefDefNode()); + assertEquals("meta:unknown-tag", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + known-tag + xmlns:x="/nop/schema/xdsl.xdef" + x:schema="/nop/schema/xdef.xdef" + > + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertTrue(tagMeta.isXdefDefNode()); + assertEquals("meta:unknown-tag", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + fine> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertTrue(tagMeta.isXdefDefNode()); + assertEquals("meta:define", tagMeta.getTagName()); + assertEquals("xdef:define", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + -parse meta:value="xpl"/> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertTrue(tagMeta.isXdefDefNode()); + assertEquals("xdef:pre-parse", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + -parse/> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertTrue(tagMeta.isXdefDefNode()); + assertEquals("meta:pre-parse", tagMeta.getTagName()); + assertEquals("xdef:pre-parse", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + fine/> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertTrue(tagMeta.isXdefDefNode()); + assertEquals("meta:define", tagMeta.getTagName()); + assertEquals("xdef:define", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + + n-tag meta:ref="XDefNode"/> + + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertTrue(tagMeta.isXdefDefNode()); + assertEquals("xdef:unknown-tag", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + en-extends/> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertFalse(tagMeta.isXdefDefNode()); + assertEquals("x:gen-extends", tagMeta.getTagName()); + assertEquals("x:gen-extends", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/nop/schema/xdsl.xdef", defNodeVfsPath(tagMeta)); + } // + ); + // - xpl node + assertTagMeta(""" + + + ipt /> + + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertFalse(tagMeta.isXdefDefNode()); + assertEquals("c:script", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xpl.xdef", defNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + + ipt /> + + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertFalse(tagMeta.isXdefDefNode()); + assertEquals("c:script", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xpl.xdef", defNodeVfsPath(tagMeta)); + } // + ); + + // 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.isUnknown()); + assertTrue(tagMeta.isXdefDefNode()); + assertEquals("xdef:unknown-tag", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + tends xdef:value="xpl-node"/> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertTrue(tagMeta.isXdefDefNode()); + assertEquals("x:gen-extends", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + tends/> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertFalse(tagMeta.isXdefDefNode()); + assertEquals("xdsl:gen-extends", tagMeta.getTagName()); + assertEquals("x:gen-extends", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/nop/schema/xdsl.xdef", defNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + + per xdef:internal="true"/> + + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertTrue(tagMeta.isXdefDefNode()); + assertEquals("x:super", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + + per/> + + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertFalse(tagMeta.isXdefDefNode()); + assertEquals("xdsl:super", tagMeta.getTagName()); + assertEquals("x:super", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/nop/schema/xdsl.xdef", defNodeVfsPath(tagMeta)); + } // + ); + // - xpl node + assertTagMeta(""" + + + ipt /> + + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertFalse(tagMeta.isXdefDefNode()); + assertEquals("c:script", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xpl.xdef", defNodeVfsPath(tagMeta)); + } // + ); + + // 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.isUnknown()); + assertTrue(tagMeta.isXdefDefNode()); + assertEquals("example", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + st-parse> + + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertTrue(tagMeta.isXdefDefNode()); + assertEquals("xdef:post-parse", tagMeta.getTagName()); + assertEquals("xdef:post-parse", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + n-extends> + + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertFalse(tagMeta.isXdefDefNode()); + assertEquals("x:gen-extends", tagMeta.getTagName()); + assertEquals("x:gen-extends", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/nop/schema/xdsl.xdef", defNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + fs xdef:value="v-path-list"/> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertTrue(tagMeta.isXdefDefNode()); + assertEquals("refs", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + nown-tag xdef:unknown-attr="any"/> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertTrue(tagMeta.isXdefDefNode()); + assertEquals("xdef:unknown-tag", tagMeta.getTagName()); + assertEquals("xdef:unknown-tag", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/nop/schema/xdef.xdef", defNodeVfsPath(tagMeta)); + } // + ); + // - xpl node + assertTagMeta(""" + + + ipt /> + + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertFalse(tagMeta.isXdefDefNode()); + assertEquals("c:script", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xpl.xdef", defNodeVfsPath(tagMeta)); + } // + ); + + // Normal DSL + assertTagMeta(""" + mple + xmlns:x="/nop/schema/xdsl.xdef" + x:schema="/test/lang/lang.xdef" + > + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertFalse(tagMeta.isXdefDefNode()); + assertEquals("example", tagMeta.getTagName()); + assertEquals("example", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/test/lang/lang.xdef", defNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + ld name="Child" type="leaf" abc="abc"/> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertFalse(tagMeta.isXdefDefNode()); + assertEquals("child", tagMeta.getTagName()); + assertEquals("child", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/test/lang/lang.xdef", defNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + known name="abc"/> + + """, // + (tag, tagMeta) -> { + assertTrue(tagMeta.isUnknown()); + assertFalse(tagMeta.isXdefDefNode()); + assertEquals("unknown", tag.getName()); + assertNull(tagMeta.getTagName()); + assertNull(tagMeta.getDefNodeInSchema()); + } // + ); + // - xdsl node + assertTagMeta(""" + + n-extends/> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertFalse(tagMeta.isXdefDefNode()); + assertEquals("x:gen-extends", tagMeta.getTagName()); + assertEquals("x:gen-extends", tagMeta.getDefNodeInSchema().getTagName()); + assertEquals("/nop/schema/xdsl.xdef", defNodeVfsPath(tagMeta)); + } // + ); + // - xpl node + assertTagMeta(""" + + + ipt /> + + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertFalse(tagMeta.isXdefDefNode()); + assertEquals("c:script", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xpl.xdef", defNodeVfsPath(tagMeta)); + } // + ); + // - undefined namespace node + assertTagMeta(""" + + yle/> + + """, // + (tag, tagMeta) -> { + assertFalse(tagMeta.isUnknown()); + assertFalse(tagMeta.isXdefDefNode()); + assertEquals("yui:style", tagMeta.getTagName()); + assertTrue(tagMeta.getDefNodeInSchema().isUnknownTag()); + assertEquals("/nop/schema/xdsl.xdef", defNodeVfsPath(tagMeta)); + } // + ); + assertTagMeta(""" + + yle/> + + """, // + (tag, tagMeta) -> { + assertTrue(tagMeta.isUnknown()); + assertFalse(tagMeta.isXdefDefNode()); + assertEquals("xui:style", tag.getName()); + assertNull(tagMeta.getTagName()); + assertNull(tagMeta.getDefNodeInSchema()); + } // + ); + } + + 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 String defNodeVfsPath(XLangTagMeta tagMeta) { + String vfsPath = XmlPsiHelper.getNopVfsPath(tagMeta.getDefNodeInSchema()); + + return vfsPath != null && vfsPath.lastIndexOf('/') == 0 ? null : vfsPath; + } +} diff --git a/nop-idea-plugin/src/test/resources/_vfs/test/lang/lang.xdef b/nop-idea-plugin/src/test/resources/_vfs/test/lang/lang.xdef new file mode 100644 index 000000000..2e0d5812e --- /dev/null +++ b/nop-idea-plugin/src/test/resources/_vfs/test/lang/lang.xdef @@ -0,0 +1,10 @@ + + + + + + + -- Gitee From 068893457824a76de722f72f936cd90f0b4e721c Mon Sep 17 00:00:00 2001 From: flytreeleft Date: Sun, 26 Oct 2025 21:07:27 +0800 Subject: [PATCH 09/12] =?UTF-8?q?nop-idea-pugin:=20=E6=94=B9=E8=BF=9B=20XL?= =?UTF-8?q?angTagMeta?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../io/nop/idea/plugin/lang/psi/XLangTag.java | 2 +- .../idea/plugin/lang/psi/XLangTagMeta.java | 253 ++++++------- .../nop/idea/plugin/utils/XDefPsiHelper.java | 2 +- .../idea/plugin/lang/TestXLangTagMeta.java | 347 ++++++++++++++++-- 4 files changed, 429 insertions(+), 175 deletions(-) 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 a72e50513..81d9af75d 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 @@ -1030,7 +1030,7 @@ public class XLangTag extends XmlTagImpl { String defPath = XmlPsiHelper.getNopVfsPath(defNode); - if (XDefPsiHelper.isXplDefNode(defNode) // + if (XDefPsiHelper.isXplTypeNode(defNode) // || XDslConstants.XDSL_SCHEMA_XPL.equals(defPath) // ) { return true; 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 index 16c3f4a5f..3f632dcfa 100644 --- 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 @@ -19,6 +19,7 @@ import io.nop.xlang.xdef.XDefConstants; import io.nop.xlang.xdef.XDefKeys; 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; /** @@ -50,18 +51,20 @@ public class XLangTagMeta { * 则用于记录当前标签在该元模型中的定义 */ IXDefNode defNodeInSelfSchema; - /** - * 在元模型 /nop/schema/xdef.xdef 绑定的名字空间(默认为 xdef)下, - * 当前标签在该元模型中所对应的定义节点: - * 仅当前标签在元模型文件(*.xdef)中且其名字空间与该元模型的名字空间相同时有效 + * 若当前标签在元模型文件(*.xdef)中, + * 则用于记录该元模型 xdef:check-ns 属性的值 */ - NsDefNode defNodeInXdefNs; + Set checkNsInSelfSchema; + /** - * 在元模型 /nop/schema/xdsl.xdef 绑定的名字空间(默认为 x)下, - * 当前标签在该元模型中所对应的定义节点:所有标签的节点均由该元模型定义 + * 当前标签所对应的元模型 /nop/schema/xdsl.xdef 中的定义节点: + * 所有的 DSL (包括元模型自身)均由该模型定义 */ - NsDefNode defNodeInXdslNs; + IXDefNode xdslDefNode; + + XDefKeys xdefKeys; + XDslKeys xdslKeys; XLangTagMeta(String tagName) { this.tagName = tagName; @@ -84,16 +87,23 @@ public class XLangTagMeta { return defNodeInSchema == null; } - /** 当前标签是否对应元模型(*.xdef)定义节点 */ - public boolean isXdefDefNode() { - return defNodeInXdefNs != null; + /** 当前标签是否在元模型 *.xdef 中 */ + public boolean isInAnySchema() { + return xdefKeys != null; } - /** 当前标签是否对应 Xpl 定义节点 */ - public boolean isXplDefNode() { + /** 当前标签是否在元模型 /nop/schema/xdef.xdef 中 */ + public boolean isInXdefSchema() { + return checkNsInSelfSchema != null // + && checkNsInSelfSchema.contains(XDefKeys.DEFAULT.NS) // + && !XDefKeys.DEFAULT.equals(xdefKeys); + } + + /** 当前标签是否为 Xpl 类型节点,或者其对应元模型 /nop/schema/xpl.xdef 中的定义节点 */ + public boolean isXplNode() { if (defNodeInSchema == null) { return false; - } else if (XDefPsiHelper.isXplDefNode(defNodeInSchema)) { + } else if (XDefPsiHelper.isXplTypeNode(defNodeInSchema)) { return true; } @@ -116,41 +126,61 @@ public class XLangTagMeta { */ public static XLangTagMeta create(XLangTag tag) { String tagName = tag.getName(); + String tagNs = StringHelper.getNamespace(tagName); XLangTag parentTag = tag.getParentTag(); // 根节点 if (parentTag == null) { - return createForRootTag(tagName, tag); + return createForRootTag(tagNs, tagName, tag); } - return createForChildTag(parentTag, tagName); + return createForChildTag(parentTag, tagNs, tagName); } - private static XLangTagMeta createForRootTag(String tagName, XLangTag tag) { + private static XLangTagMeta createForRootTag(String tagNs, String tagName, XLangTag tag) { // Note: 若标签在 Xpl 脚本中,则将返回 /nop/schema/xpl.xdef String schemaUrl = XDefPsiHelper.getSchemaPath(tag); if (schemaUrl == null) { + // TODO 未在根节点通过 x:schema 指定元模型路径 return UNKNOWN_META; } + IXDefinition schema = XDefPsiHelper.loadSchema(schemaUrl); + IXDefinition xdslSchema = XDefPsiHelper.getXDslDef(); + // 若其元模型为 /nop/schema/xdef.xdef,则其自身也为元模型(*.xdef) boolean inSchema = XDslConstants.XDSL_SCHEMA_XDEF.equals(schemaUrl) // || isXdefFile(tag.getContainingFile().getName()); - IXDefinition schema = XDefPsiHelper.loadSchema(schemaUrl); // 非 xdef/xpl 模型,全部视为 xdsl - IXDefinition xdslSchema = XDefPsiHelper.getXDslDef(); if (schema == null && !inSchema && !isXplXdefFile(schemaUrl)) { schema = xdslSchema; } // 若元模型加载失败,则不做识别 if (schema == null) { + // TODO 元模型 /xx/xx.xdef 加载失败 return UNKNOWN_META; } + String xdefNs = XmlPsiHelper.getXmlnsForUrl(tag, XDslConstants.XDSL_SCHEMA_XDEF); + String xdslNs = XmlPsiHelper.getXmlnsForUrl(tag, XDslConstants.XDSL_SCHEMA_XDSL); + XDefKeys xdefKeys = XDefKeys.of(xdefNs); + XDslKeys xdslKeys = XDslKeys.of(xdslNs); + + // Note: xdef 名字空间的根节点必须为 xdef.xdef 中已定义的节点 + if (inSchema && xdefKeys.NS.equals(tagNs)) { + if (getChildXdefDefNode(xdefKeys, schema, tagName) == null) { + // TODO 在名字空间 xx 对应的元模型 xdef.xdef 中未定义 xx:xx + return UNKNOWN_META; + } + } + IXDefNode defNodeInSchema = schema.getRootNode(); // 如果其不在元模型中,则其根节点标签名必须与其 x:schema 所定义的根节点标签名保持一致, // 除非根节点被定义为 xdef:unknown-tag - if (!inSchema && !defNodeInSchema.isUnknownTag() && !defNodeInSchema.getTagName().equals(tagName)) { + if (!inSchema && !defNodeInSchema.isUnknownTag() // + && !defNodeInSchema.getTagName().equals(tagName) // + ) { + // TODO 根节点标签名 与元模型定义的根节点标签名 不一致 return UNKNOWN_META; } @@ -167,89 +197,85 @@ public class XLangTagMeta { } } - IXDefinition xdefSchema = XDefPsiHelper.getXdefDef(); - String xdefNs = XmlPsiHelper.getXmlnsForUrl(tag, XDslConstants.XDSL_SCHEMA_XDEF); - NsDefNode defNodeInXdefNs = // - inSchema ? NsDefNode.create(xdefNs, XDefKeys.DEFAULT.NS, xdefSchema) : null; - - String xdslNs = XmlPsiHelper.getXmlnsForUrl(tag, XDslConstants.XDSL_SCHEMA_XDSL); - NsDefNode defNodeInXdslNs = NsDefNode.create(xdslNs, XDslKeys.DEFAULT.NS, xdslSchema); - XLangTagMeta tagMeta = new XLangTagMeta(tagName); tagMeta.defNodeInSchema = defNodeInSchema; tagMeta.checkNsInSchema = schema.getXdefCheckNs(); + tagMeta.defNodeInSelfSchema = selfSchema != null ? selfSchema.getRootNode() : null; - tagMeta.defNodeInXdefNs = defNodeInXdefNs; - tagMeta.defNodeInXdslNs = defNodeInXdslNs; + 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 parentTag, String tagName) { + private static XLangTagMeta createForChildTag(XLangTag parentTag, String tagNs, String tagName) { XLangTagMeta parentTagMeta = parentTag.getTagMeta(); // 无定义节点 if (parentTagMeta.isUnknown()) { + // TODO 父节点未定义 return UNKNOWN_META; } + XDefKeys xdefKeys = parentTagMeta.xdefKeys; + XDslKeys xdslKeys = parentTagMeta.xdslKeys; + IXDefNode defNodeInSchema = null; - // 1. 先按名字空间匹配对应元模型中定义的节点 - // - 带 xpl 名字空间的节点的父节点只能是 Xpl 类型时才有效 - String tagNs = StringHelper.getNamespace(tagName); - NsDefNode[] nsDefNodes = new NsDefNode[] { - parentTagMeta.defNodeInXdefNs, parentTagMeta.defNodeInXdslNs - }; - for (int i = 0; i < nsDefNodes.length; i++) { - NsDefNode nsDefNode = nsDefNodes[i]; - if (nsDefNode == null) { - continue; + // 1. 获取确定的 xdsl 节点,如 等 + if (xdslKeys.NS.equals(tagNs)) { + String xdslTagName = replaceTagNs(tagName, XDslKeys.DEFAULT.NS); + defNodeInSchema = getChildDefNode(parentTagMeta.xdslDefNode, xdslTagName); + + if (defNodeInSchema.isUnknownTag()) { + // TODO 在名字空间 xx 对应的元模型 xdsl.xdef 中未定义 xx:xx + return UNKNOWN_META; } + } - NsDefNode childNsDefNode = nsDefNode.getChild(tagName); - // 若当前标签的命名空间与相应元模型的名字空间相同,则其定义节点必须存在且确定的 - if (nsDefNode.xmlNs.equals(tagNs)) { - if (childNsDefNode != null && !childNsDefNode.defNode.isUnknownTag()) { - defNodeInSchema = childNsDefNode.defNode; - } else { - // TODO 在元模型 /xx/xx.xdef 中未定义 xx:xx - return UNKNOWN_META; - } + // 2. 若父节点为 Xpl 类型节点,则直接从 xpl.xdef 中获取节点(含带 xpl 名字空间的节点) + if (defNodeInSchema == null && parentTagMeta.isXplNode()) { + defNodeInSchema = getChildDefNode(XDefPsiHelper.getXplDef().getRootNode(), tagName); + + if (XplConstants.XPL_NS.equals(tagNs) && defNodeInSchema.isUnknownTag()) { + // TODO 在名字空间 xpl 对应的元模型 xpl.xdef 中未定义 xpl:xx + return UNKNOWN_META; } + } - // 就地更新为子节点 - nsDefNodes[i] = childNsDefNode; + // 3. 在元元模型中,以 xdef 为名字空间的标签, 需以 meta:unknown-tag 作为其节点定义,即,交叉定义 + if (defNodeInSchema == null && parentTagMeta.isInXdefSchema() // + && XDefKeys.DEFAULT.NS.equals(tagNs) // + ) { + defNodeInSchema = parentTagMeta.defNodeInSchema.getXdefUnknownTag(); } - NsDefNode defNodeInXdefNs = nsDefNodes[0]; - NsDefNode defNodeInXdslNs = nsDefNodes[1]; - // 2. 若父节点为 Xpl 类型节点,则直接从 xpl.xdef 中获取节点(含带 xpl 名字空间的节点) - if (parentTagMeta.isXplDefNode()) { - defNodeInXdefNs = null; - defNodeInSchema = getChildDefNode(XDefPsiHelper.getXplDef().getRootNode(), tagName); + // 4. 获取确定的 xdef 节点,如 等 + // - *.xdef 必然始终以 /nop/schema/xdef.xdef 为元模型 + if (defNodeInSchema == null && parentTagMeta.isInAnySchema() // + && xdefKeys.NS.equals(tagNs) // + ) { + defNodeInSchema = getChildXdefDefNode(xdefKeys, parentTagMeta.defNodeInSchema, tagName); + if (defNodeInSchema == null) { + // TODO 在名字空间 xx 对应的元模型 xdef.xdef 中未定义 xx:xx + return UNKNOWN_META; + } } - // 3. 从当前标签的父标签的定义节点中获取其定义节点 + // 5. 从当前标签的父标签的定义节点中获取其定义节点 if (defNodeInSchema == null) { defNodeInSchema = getChildDefNode(parentTagMeta.defNodeInSchema, tagName); } - // 4. 带名字空间的节点:其在元模型中已显式定义,或者为自由增添的无约束节点 + // 6. 带名字空间的节点需满足:其在元模型中已显式定义,或者其为无约束节点 if (tagNs != null) { - // Note: 在 xdsl.xdef 中的定义节点,不属于元模型定义节点 - if (defNodeInXdslNs != null && defNodeInSchema == defNodeInXdslNs.defNode) { - defNodeInXdefNs = null; - } - - // 若为 xdef.xdef 交叉定义(自己定义自己),则取定义节点 xdef:unknown-tag - if (defNodeInXdefNs != null // - && !defNodeInXdefNs.xmlNs.equals(tagNs) // - && defNodeInXdefNs.defNs.equals(tagNs) // - ) { - defNodeInSchema = defNodeInXdefNs.defNode; - } // 需要校验的名字空间下的节点必须在其元模型中显式定义 - else if (parentTagMeta.checkNsInSchema != null // - && parentTagMeta.checkNsInSchema.contains(tagNs) // + if (!parentTagMeta.isInAnySchema() // + && parentTagMeta.checkNsInSchema != null // + && parentTagMeta.checkNsInSchema.contains(tagNs) // ) { if (defNodeInSchema != null && defNodeInSchema.isUnknownTag()) { // TODO 名字空间 xx 下的标签必须在 /xx/xx.xdef 中显式定义 @@ -258,11 +284,12 @@ public class XLangTagMeta { } // 否则,其定义节点必然为 xdsl 的 xdef:unknown-tag 节点 else if (defNodeInSchema == null) { - defNodeInSchema = getChildDefNode(XDefPsiHelper.getXDslDef().getRootNode(), tagName); + defNodeInSchema = parentTagMeta.xdslDefNode.getXdefUnknownTag(); } } if (defNodeInSchema == null) { + // TODO 节点未定义 return UNKNOWN_META; } @@ -270,9 +297,13 @@ public class XLangTagMeta { tagMeta.parent = parentTagMeta; tagMeta.defNodeInSchema = defNodeInSchema; tagMeta.checkNsInSchema = parentTagMeta.checkNsInSchema; + tagMeta.defNodeInSelfSchema = getChildDefNode(parentTagMeta.defNodeInSelfSchema, tagName); - tagMeta.defNodeInXdefNs = defNodeInXdefNs; - tagMeta.defNodeInXdslNs = defNodeInXdslNs; + tagMeta.checkNsInSelfSchema = parentTagMeta.checkNsInSelfSchema; + + tagMeta.xdslDefNode = getChildDefNode(parentTagMeta.xdslDefNode, tagName); + tagMeta.xdefKeys = xdefKeys; + tagMeta.xdslKeys = xdslKeys; return tagMeta; } @@ -285,67 +316,23 @@ public class XLangTagMeta { return XDslConstants.XDSL_SCHEMA_XPL.equals(filename); } + private static String replaceTagNs(String tagName, String ns) { + return ns + tagName.substring(tagName.indexOf(':')); + } + + /** 获得指定标签名的子定义节点,或者 xdef:unknown-tag 定义节点 */ private static IXDefNode getChildDefNode(IXDefNode defNode, String tagName) { return defNode != null ? defNode.getChild(tagName) : null; } - /** 通过名字空间引入的元模型中的定义节点 */ - private static class NsDefNode { - /** - * 引入 {@link #defNode} 的元模型时所绑定的名字空间, - * 比如,在元模型 /nop/schema/xdef.xdef 中通过 - * xmlns:meta="/nop/schema/xdef.xdef" 引入 xdef 模型, - * 则 meta 便为该名字空间,所有在该名字空间下的节点和属性均由该 xdef 模型定义 - */ - final String xmlNs; - /** - * 定义 {@link #defNode} 时所限定的名字空间, - * 比如,在元模型 /nop/schema/xdef.xdef 中定义的节点和属性均被限定在 - * xdef 名字空间下,因此,xdef 即为其值 - */ - final String defNs; - - /** 定义节点 */ - final IXDefNode defNode; - - NsDefNode(String xmlNs, String defNs, IXDefNode defNode) { - this.xmlNs = xmlNs; - this.defNs = defNs; - - this.defNode = defNode; - } - - public NsDefNode getChild(String childTagName) { - IXDefNode childDefNode; - if (childTagName.startsWith(xmlNs + ':')) { - if (!xmlNs.equals(defNs)) { - childTagName = defNs + childTagName.substring(xmlNs.length()); - } - childDefNode = getChildDefNode(defNode, childTagName); - } else { - childDefNode = defNode.getXdefUnknownTag(); - } - - if (childDefNode == null) { - return null; - } - // Note: 复用实例,避免反复新建 - if (defNode == childDefNode) { - return this; - } - return new NsDefNode(xmlNs, defNs, childDefNode); - } - - public static NsDefNode create(String xmlNs, String defNs, IXDefinition schema) { - if (schema == null) { - return null; - } - - if (StringHelper.isBlank(xmlNs)) { - xmlNs = defNs; - } + private static IXDefNode getChildXdefDefNode(XDefKeys xdefKeys, IXDefNode defNode, String tagName) { + String xdefTagName = replaceTagNs(tagName, XDefKeys.DEFAULT.NS); + IXDefNode childDefNode = getChildDefNode(defNode, xdefTagName); - return new NsDefNode(xmlNs, defNs, schema.getRootNode()); + 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/utils/XDefPsiHelper.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/utils/XDefPsiHelper.java index ca57c57fc..d58f9015c 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 @@ -84,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/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 index 7eb959f37..1c55c75cb 100644 --- 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 @@ -16,6 +16,7 @@ import io.nop.idea.plugin.BaseXLangPluginTestCase; 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.IXDefNode; /** * @@ -29,16 +30,23 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { assertTagMeta(""" known-tag xmlns:x="/nop/schema/xdsl.xdef" xmlns:meta="/nop/schema/xdef.xdef" - x:schema="/nop/schema/xdef.xdef" + x:schema="/nop/schema/xdef.xdef" meta:check-ns="xdef" > """, // (tag, tagMeta) -> { assertFalse(tagMeta.isUnknown()); - assertTrue(tagMeta.isXdefDefNode()); + 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(""" @@ -50,115 +58,159 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertFalse(tagMeta.isUnknown()); - assertTrue(tagMeta.isXdefDefNode()); + 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.isUnknown()); - assertTrue(tagMeta.isXdefDefNode()); + 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.isUnknown()); - assertTrue(tagMeta.isXdefDefNode()); + 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) -> { assertFalse(tagMeta.isUnknown()); - assertTrue(tagMeta.isXdefDefNode()); + 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.isUnknown()); - assertTrue(tagMeta.isXdefDefNode()); + 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.isUnknown()); - assertTrue(tagMeta.isXdefDefNode()); + 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) -> { assertFalse(tagMeta.isUnknown()); - assertFalse(tagMeta.isXdefDefNode()); + 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 /> @@ -167,16 +219,21 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertFalse(tagMeta.isUnknown()); - assertFalse(tagMeta.isXdefDefNode()); + 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 /> @@ -185,10 +242,15 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertFalse(tagMeta.isUnknown()); - assertFalse(tagMeta.isXdefDefNode()); + 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()); } // ); @@ -202,10 +264,17 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertFalse(tagMeta.isUnknown()); - assertTrue(tagMeta.isXdefDefNode()); + 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(""" @@ -218,10 +287,17 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertFalse(tagMeta.isUnknown()); - assertTrue(tagMeta.isXdefDefNode()); + 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(""" @@ -234,10 +310,15 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertFalse(tagMeta.isUnknown()); - assertFalse(tagMeta.isXdefDefNode()); + 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(""" @@ -252,10 +333,17 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertFalse(tagMeta.isUnknown()); - assertTrue(tagMeta.isXdefDefNode()); + 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(""" @@ -270,10 +358,15 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertFalse(tagMeta.isUnknown()); - assertFalse(tagMeta.isXdefDefNode()); + 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 @@ -289,10 +382,15 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertFalse(tagMeta.isUnknown()); - assertFalse(tagMeta.isXdefDefNode()); + 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()); } // ); @@ -306,10 +404,17 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertFalse(tagMeta.isUnknown()); - assertTrue(tagMeta.isXdefDefNode()); + 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(""" @@ -323,10 +428,15 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertFalse(tagMeta.isUnknown()); - assertTrue(tagMeta.isXdefDefNode()); + 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(""" @@ -340,10 +450,15 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertFalse(tagMeta.isUnknown()); - assertFalse(tagMeta.isXdefDefNode()); + 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(""" @@ -356,10 +471,17 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertFalse(tagMeta.isUnknown()); - assertTrue(tagMeta.isXdefDefNode()); + 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(""" @@ -372,10 +494,17 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertFalse(tagMeta.isUnknown()); - assertTrue(tagMeta.isXdefDefNode()); + 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 @@ -391,10 +520,15 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertFalse(tagMeta.isUnknown()); - assertFalse(tagMeta.isXdefDefNode()); + 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()); } // ); @@ -408,10 +542,15 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertFalse(tagMeta.isUnknown()); - assertFalse(tagMeta.isXdefDefNode()); + 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(""" @@ -424,10 +563,15 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertFalse(tagMeta.isUnknown()); - assertFalse(tagMeta.isXdefDefNode()); + 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(""" @@ -440,10 +584,15 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertTrue(tagMeta.isUnknown()); - assertFalse(tagMeta.isXdefDefNode()); + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + assertEquals("unknown", tag.getName()); assertNull(tagMeta.getTagName()); assertNull(tagMeta.getDefNodeInSchema()); + + assertNull(tagMeta.getDefNodeInSelfSchema()); } // ); // - xdsl node @@ -457,10 +606,15 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertFalse(tagMeta.isUnknown()); - assertFalse(tagMeta.isXdefDefNode()); + 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 @@ -476,10 +630,15 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertFalse(tagMeta.isUnknown()); - assertFalse(tagMeta.isXdefDefNode()); + 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 @@ -493,10 +652,15 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertFalse(tagMeta.isUnknown()); - assertFalse(tagMeta.isXdefDefNode()); + 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(""" @@ -509,10 +673,105 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { assertTrue(tagMeta.isUnknown()); - assertFalse(tagMeta.isXdefDefNode()); + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + assertEquals("xui:style", tag.getName()); assertNull(tagMeta.getTagName()); assertNull(tagMeta.getDefNodeInSchema()); + + assertNull(tagMeta.getDefNodeInSelfSchema()); + } // + ); + } + + public void testCreateUnknownTagMeta() { + assertTagMeta(""" + known-tag> + + """, // + (tag, tagMeta) -> { + // 元模型未指定 + assertTrue(tagMeta.isUnknown()); + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("meta:unknown-tag", tag.getName()); + } // + ); + assertTagMeta(""" + ple + xmlns:x="/nop/schema/xdsl.xdef" + x:schema="/xx/xx/example.xdef" + > + + """, // + (tag, tagMeta) -> { + // 标签由 xdsl.xdef 定义 + assertFalse(tagMeta.isUnknown()); + 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) -> { + // 标签未定义 + assertTrue(tagMeta.isUnknown()); + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("meta:unknown", tag.getName()); + } // + ); + assertTagMeta(""" + + cd/> + + """, // + (tag, tagMeta) -> { + // 标签未定义 + assertTrue(tagMeta.isUnknown()); + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("meta:abcd", tag.getName()); + } // + ); + + assertTagMeta(""" + ng + xmlns:x="/nop/schema/xdsl.xdef" + x:schema="/test/lang/lang.xdef" + > + + """, // + (tag, tagMeta) -> { + // 根节点标签与定义的不一致 + assertTrue(tagMeta.isUnknown()); + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("lang", tag.getName()); } // ); } @@ -532,7 +791,15 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { } private String defNodeVfsPath(XLangTagMeta tagMeta) { - String vfsPath = XmlPsiHelper.getNopVfsPath(tagMeta.getDefNodeInSchema()); + 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; } -- Gitee From bcf7f007657b19e22b5f22d627124846b59cd2ff Mon Sep 17 00:00:00 2001 From: flytreeleft Date: Thu, 6 Nov 2025 11:11:35 +0800 Subject: [PATCH 10/12] =?UTF-8?q?nop-idea-pugin:=20=E5=9C=A8=20XLangTagMet?= =?UTF-8?q?a=20=E4=B8=AD=E8=AE=B0=E5=BD=95=E8=A7=A3=E6=9E=90=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E4=BF=A1=E6=81=AF=E4=BB=A5=E4=BE=BF=E4=BA=8E=E7=BB=99?= =?UTF-8?q?=E5=87=BA=E5=87=86=E7=A1=AE=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../idea/plugin/lang/psi/XLangTagMeta.java | 93 +++++++++++++----- .../messages/NopPluginBundle.properties | 8 ++ .../messages/NopPluginBundle_zh.properties | 8 ++ .../idea/plugin/lang/TestXLangTagMeta.java | 98 ++++++++++++++++++- 4 files changed, 179 insertions(+), 28 deletions(-) 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 index 3f632dcfa..fd81b80a6 100644 --- 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 @@ -11,6 +11,7 @@ package io.nop.idea.plugin.lang.psi; import java.util.Set; import io.nop.commons.util.StringHelper; +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.IXDefNode; @@ -21,6 +22,10 @@ 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 定义节点}信息 @@ -29,9 +34,9 @@ import io.nop.xlang.xpl.xlib.XlibConstants; * @date 2025-10-21 */ public class XLangTagMeta { - private static final XLangTagMeta UNKNOWN_META = new XLangTagMeta(null); - final String tagName; + final String errorMsg; + XLangTagMeta parent; /** @@ -66,14 +71,23 @@ public class XLangTagMeta { XDefKeys xdefKeys; XDslKeys xdslKeys; - XLangTagMeta(String tagName) { + XLangTagMeta(@NotNull String tagName) { + this(tagName, null); + } + + XLangTagMeta(@NotNull String tagName, String errorMsg) { this.tagName = tagName; + this.errorMsg = errorMsg; } public String getTagName() { return tagName; } + public String getErrorMsg() { + return this.errorMsg; + } + public IXDefNode getDefNodeInSchema() { return defNodeInSchema; } @@ -82,6 +96,10 @@ public class XLangTagMeta { return defNodeInSelfSchema; } + public boolean hasError() { + return errorMsg != null; + } + /** 是否未定义 */ public boolean isUnknown() { return defNodeInSchema == null; @@ -136,12 +154,21 @@ public class XLangTagMeta { return createForChildTag(parentTag, tagNs, tagName); } + private static XLangTagMeta error( + @NotNull String tagName, @NotNull @PropertyKey(resourceBundle = BUNDLE) String msgKey, + Object @NotNull ... msgParams + ) { + return new XLangTagMeta(tagName, NopPluginBundle.message(msgKey, msgParams)); + } + private static XLangTagMeta createForRootTag(String tagNs, String tagName, XLangTag tag) { + String xdslNs = XmlPsiHelper.getXmlnsForUrl(tag, XDslConstants.XDSL_SCHEMA_XDSL); + XDslKeys xdslKeys = XDslKeys.of(xdslNs); + // Note: 若标签在 Xpl 脚本中,则将返回 /nop/schema/xpl.xdef String schemaUrl = XDefPsiHelper.getSchemaPath(tag); if (schemaUrl == null) { - // TODO 未在根节点通过 x:schema 指定元模型路径 - return UNKNOWN_META; + return error(tagName, "xlang.parser.tag-meta.schema-not-specified", xdslKeys.SCHEMA, tagName); } IXDefinition schema = XDefPsiHelper.loadSchema(schemaUrl); @@ -157,20 +184,20 @@ public class XLangTagMeta { } // 若元模型加载失败,则不做识别 if (schema == null) { - // TODO 元模型 /xx/xx.xdef 加载失败 - return UNKNOWN_META; + return error(tagName, "xlang.parser.tag-meta.schema-loading-failed", schemaUrl); } String xdefNs = XmlPsiHelper.getXmlnsForUrl(tag, XDslConstants.XDSL_SCHEMA_XDEF); - String xdslNs = XmlPsiHelper.getXmlnsForUrl(tag, XDslConstants.XDSL_SCHEMA_XDSL); XDefKeys xdefKeys = XDefKeys.of(xdefNs); - XDslKeys xdslKeys = XDslKeys.of(xdslNs); // Note: xdef 名字空间的根节点必须为 xdef.xdef 中已定义的节点 if (inSchema && xdefKeys.NS.equals(tagNs)) { if (getChildXdefDefNode(xdefKeys, schema, tagName) == null) { - // TODO 在名字空间 xx 对应的元模型 xdef.xdef 中未定义 xx:xx - return UNKNOWN_META; + return error(tagName, + "xlang.parser.tag-meta.ns-tag-not-defined-in-schema", + tagName, + XDefKeys.DEFAULT.NS, + XDslConstants.XDSL_SCHEMA_XDEF); } } @@ -180,8 +207,11 @@ public class XLangTagMeta { if (!inSchema && !defNodeInSchema.isUnknownTag() // && !defNodeInSchema.getTagName().equals(tagName) // ) { - // TODO 根节点标签名 与元模型定义的根节点标签名 不一致 - return UNKNOWN_META; + return error(tagName, + "xlang.parser.tag-meta.root-tag-name-not-match-schema-root", + tagName, + defNodeInSchema.getTagName(), + schemaUrl); } // Note: 正在编辑中的 xdef 有可能是不完整的,此时将无法解析出 IXDefinition @@ -217,8 +247,7 @@ public class XLangTagMeta { XLangTagMeta parentTagMeta = parentTag.getTagMeta(); // 无定义节点 if (parentTagMeta.isUnknown()) { - // TODO 父节点未定义 - return UNKNOWN_META; + return error(tagName, "xlang.parser.tag-meta.parent-not-defined", parentTagMeta.getTagName()); } XDefKeys xdefKeys = parentTagMeta.xdefKeys; @@ -231,8 +260,11 @@ public class XLangTagMeta { defNodeInSchema = getChildDefNode(parentTagMeta.xdslDefNode, xdslTagName); if (defNodeInSchema.isUnknownTag()) { - // TODO 在名字空间 xx 对应的元模型 xdsl.xdef 中未定义 xx:xx - return UNKNOWN_META; + return error(tagName, + "xlang.parser.tag-meta.ns-tag-not-defined-in-schema", + tagName, + XDslKeys.DEFAULT.NS, + XDslConstants.XDSL_SCHEMA_XDSL); } } @@ -241,8 +273,11 @@ public class XLangTagMeta { defNodeInSchema = getChildDefNode(XDefPsiHelper.getXplDef().getRootNode(), tagName); if (XplConstants.XPL_NS.equals(tagNs) && defNodeInSchema.isUnknownTag()) { - // TODO 在名字空间 xpl 对应的元模型 xpl.xdef 中未定义 xpl:xx - return UNKNOWN_META; + return error(tagName, + "xlang.parser.tag-meta.ns-tag-not-defined-in-schema", + tagName, + XplConstants.XPL_NS, + XDslConstants.XDSL_SCHEMA_XPL); } } @@ -260,8 +295,11 @@ public class XLangTagMeta { ) { defNodeInSchema = getChildXdefDefNode(xdefKeys, parentTagMeta.defNodeInSchema, tagName); if (defNodeInSchema == null) { - // TODO 在名字空间 xx 对应的元模型 xdef.xdef 中未定义 xx:xx - return UNKNOWN_META; + return error(tagName, + "xlang.parser.tag-meta.ns-tag-not-defined-in-schema", + tagName, + XDefKeys.DEFAULT.NS, + XDslConstants.XDSL_SCHEMA_XDEF); } } @@ -277,9 +315,13 @@ public class XLangTagMeta { && parentTagMeta.checkNsInSchema != null // && parentTagMeta.checkNsInSchema.contains(tagNs) // ) { - if (defNodeInSchema != null && defNodeInSchema.isUnknownTag()) { - // TODO 名字空间 xx 下的标签必须在 /xx/xx.xdef 中显式定义 - return UNKNOWN_META; + if (defNodeInSchema == null || defNodeInSchema.isUnknownTag()) { + String schemaUrl = XmlPsiHelper.getNopVfsPath(parentTagMeta.defNodeInSchema); + return error(tagName, + "xlang.parser.tag-meta.ns-tag-should-be-defined-in-schema", + tagName, + tagNs, + schemaUrl); } } // 否则,其定义节点必然为 xdsl 的 xdef:unknown-tag 节点 @@ -289,8 +331,7 @@ public class XLangTagMeta { } if (defNodeInSchema == null) { - // TODO 节点未定义 - return UNKNOWN_META; + return error(tagName, "xlang.parser.tag-meta.tag-not-defined", tagName); } XLangTagMeta tagMeta = new XLangTagMeta(tagName); 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 fcc8441ae..d13ccee2b 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 @@ -46,3 +46,11 @@ 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-defined = The parent tag ''{0}'' isn''t defined +xlang.parser.tag-meta.tag-not-defined = The tag ''{0}'' isn''t defined 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 a80c840ed..2ce54845f 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 @@ -46,3 +46,11 @@ 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-defined = \u7236\u6807\u7B7E ''{0}'' \u672A\u5B9A\u4E49 +xlang.parser.tag-meta.tag-not-defined = \u6807\u7B7E ''{0}'' \u672A\u5B9A\u4E49 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 index 1c55c75cb..05612b3cd 100644 --- 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 @@ -693,12 +693,14 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { // 元模型未指定 + assertTrue(tagMeta.hasError()); assertTrue(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertFalse(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); assertEquals("meta:unknown-tag", tag.getName()); + assertTrue(tagMeta.getErrorMsg().contains("No schema path is specified")); } // ); assertTagMeta(""" @@ -710,6 +712,7 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { // 标签由 xdsl.xdef 定义 + assertFalse(tagMeta.hasError()); assertFalse(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertFalse(tagMeta.isInAnySchema()); @@ -729,13 +732,15 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - // 标签未定义 + // meta 名字空间的标签未定义 + assertTrue(tagMeta.hasError()); assertTrue(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertFalse(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); assertEquals("meta:unknown", tag.getName()); + assertTrue(tagMeta.getErrorMsg().contains("corresponding namespace 'xdef'")); } // ); assertTagMeta(""" @@ -747,13 +752,57 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - // 标签未定义 + // meta 名字空间的标签未定义 + assertTrue(tagMeta.hasError()); assertTrue(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertFalse(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); assertEquals("meta:abcd", tag.getName()); + assertTrue(tagMeta.getErrorMsg().contains("corresponding namespace 'xdef'")); + } // + ); + assertTagMeta(""" + + ny/> + + """, // + (tag, tagMeta) -> { + // x 名字空间的标签未定义 + assertTrue(tagMeta.hasError()); + assertTrue(tagMeta.isUnknown()); + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("x:any", tag.getName()); + assertTrue(tagMeta.getErrorMsg().contains("corresponding namespace 'x'")); + } // + ); + assertTagMeta(""" + + + c/> + + + """, // + (tag, tagMeta) -> { + // xpl 名字空间的标签未定义 + assertTrue(tagMeta.hasError()); + assertTrue(tagMeta.isUnknown()); + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("xpl:abc", tag.getName()); + assertTrue(tagMeta.getErrorMsg().contains("corresponding namespace 'xpl'")); } // ); @@ -766,12 +815,57 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { // 根节点标签与定义的不一致 + assertTrue(tagMeta.hasError()); assertTrue(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertFalse(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); assertEquals("lang", tag.getName()); + assertTrue(tagMeta.getErrorMsg().contains("doesn't match with the root tag")); + } // + ); + assertTagMeta(""" + + ent> + + + """, // + (tag, tagMeta) -> { + // xui 名字空间的标签未显式定义 + assertTrue(tagMeta.hasError()); + assertTrue(tagMeta.isUnknown()); + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("xui:parent", tag.getName()); + assertTrue(tagMeta.getErrorMsg().contains("should be defined in schema")); + } // + ); + assertTagMeta(""" + + + ild/> + + + """, // + (tag, tagMeta) -> { + // 父标签未定义 + assertTrue(tagMeta.hasError()); + assertTrue(tagMeta.isUnknown()); + assertFalse(tagMeta.isXplNode()); + assertFalse(tagMeta.isInAnySchema()); + assertFalse(tagMeta.isInXdefSchema()); + + assertEquals("xui:child", tag.getName()); + assertTrue(tagMeta.getErrorMsg().contains("'xui:parent' isn't defined")); } // ); } -- Gitee From aac53548b9503a828648faf56a382055536d4044 Mon Sep 17 00:00:00 2001 From: flytreeleft Date: Fri, 7 Nov 2025 17:03:24 +0800 Subject: [PATCH 11/12] =?UTF-8?q?nop-idea-pugin:=20=E4=BD=BF=E7=94=A8=20XL?= =?UTF-8?q?angTagMeta=20=E6=9B=BF=E6=8D=A2=20XLangTag.SchemaMeta?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../idea/plugin/annotator/XLangAnnotator.java | 45 +- .../XLangCompletionContributor.java | 19 +- .../doc/XLangDocumentationProvider.java | 16 +- .../lang/XLangScriptLanguageInjector.java | 15 +- .../idea/plugin/lang/psi/XLangAttribute.java | 38 +- .../plugin/lang/psi/XLangAttributeValue.java | 71 +- .../io/nop/idea/plugin/lang/psi/XLangTag.java | 839 +----------------- .../idea/plugin/lang/psi/XLangTagMeta.java | 517 +++++++++-- .../idea/plugin/lang/psi/XLangTextToken.java | 4 +- .../reference/XLangAttributeReference.java | 27 +- .../XLangParentTagAttrReference.java | 4 +- .../lang/reference/XLangTagReference.java | 23 +- .../reference/XLangXPrototypeReference.java | 8 +- .../reference/XLangXdefKeyAttrReference.java | 18 +- .../idea/plugin/lang/xlib/XlibTagMeta.java | 2 +- .../messages/NopPluginBundle.properties | 8 +- .../messages/NopPluginBundle_zh.properties | 8 +- .../idea/plugin/lang/TestXLangTagMeta.java | 62 +- 18 files changed, 658 insertions(+), 1066 deletions(-) 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 2c1c17008..b91007149 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 @@ -30,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; @@ -39,7 +40,6 @@ import io.nop.idea.plugin.utils.XmlPsiHelper; import io.nop.idea.plugin.vfs.NopVirtualFile; import io.nop.xlang.xdef.IStdDomainHandler; import io.nop.xlang.xdef.IXDefAttribute; -import io.nop.xlang.xdef.IXDefNode; import io.nop.xlang.xdef.XDefTypeDecl; import io.nop.xlang.xdef.domain.StdDomainRegistry; import io.nop.xlang.xpl.utils.XplParseHelper; @@ -133,42 +133,26 @@ public class XLangAnnotator implements Annotator { } private void checkTag(@NotNull AnnotationHolder holder, @NotNull XLangTag tag) { - if (tag.getSchemaDefNode() != null) { + XLangTagMeta tagMeta = tag.getTagMeta(); + if (!tagMeta.hasError()) { checkTagBySchemaDefNode(holder, tag); return; } - XLangTag parentTag = tag.getParentTag(); - if ((parentTag != null && parentTag.isXdefValueSupportBody()) // - || tag.isAllowedUnknownTag() // - ) { - return; - } - - // Note: 若根节点被定义为 xdef:unknown-tag,则 tag.getSchemaDef() 不会返回 null - String expectedRootTag = parentTag == null && tag.getSchemaDef() != null // - ? tag.getSchemaDef().getRootNode().getTagName() : null; XmlToken[] tokens = new XmlToken[] { XmlTagUtil.getStartTagNameElement(tag), XmlTagUtil.getEndTagNameElement(tag) }; for (XmlToken token : tokens) { if (token != null) { - if (expectedRootTag != null) { - errorAnnotation(holder, - token.getTextRange(), - "xlang.annotation.tag.expected-root", - expectedRootTag); - } else { - errorAnnotation(holder, token.getTextRange(), "xlang.annotation.tag.not-defined", token.getText()); - } + _errorAnnotation(holder, token.getTextRange(), tagMeta.getErrorMsg()); } } } private void checkTagBySchemaDefNode(@NotNull AnnotationHolder holder, @NotNull XLangTag tag) { - IXDefNode xdefNode = tag.getSchemaDefNode(); + XLangTagMeta tagMeta = tag.getTagMeta(); // 检查标签可重复性 - if (!xdefNode.isAllowMultiple() && tag.getParentTag() != null) { + if (!tagMeta.canBeMultipleTag() && tag.getParentTag() != null) { for (PsiElement child : tag.getParentTag().getChildren()) { // 检查在其之前的重复节点 if (child == tag) { @@ -188,7 +172,7 @@ public class XLangAnnotator implements Annotator { } // 检查节点内容 - 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); @@ -200,11 +184,6 @@ 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(), @@ -217,7 +196,7 @@ public class XLangAnnotator implements Annotator { SourceLocation loc = XmlPsiHelper.getValueLocation(tag); String stdDomain = xdefValue.getStdDomain(); - if (tag.isXlibSourceNode()) { + if (tagMeta.isXlibSourceNode()) { stdDomain = "xpl"; } @@ -234,18 +213,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; } 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 1a90680c6..74481a004 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 3412cf9e2..006b07a45 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; @@ -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/XLangScriptLanguageInjector.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/XLangScriptLanguageInjector.java index 1d6867721..e4b02a21d 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 7cf540169..0b915ef1f 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 @@ -81,29 +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); + } - return defAttr; + public static boolean isNullOrErrorDefAttr(IXDefAttribute defAttr) { + return defAttr == null || defAttr instanceof XDefAttributeWithError; } - /** 带名字空间的附加属性(即,不做名字空间校验的属性) */ - public static class XDefAttributeNotInCheckNS extends XDefAttribute { + /** 类型为 {@code any} 的属性定义 */ + public static class XDefAttributeWithTypeAny extends XDefAttribute { private static final XDefTypeDecl STD_DOMAIN_ANY = new XDefTypeDeclParser().parseFromText(null, "any"); - public XDefAttributeNotInCheckNS(String name) { + public XDefAttributeWithTypeAny(String name) { setName(name); setType(STD_DOMAIN_ANY); } } + + /** 含错误信息的属性定义 */ + public static class XDefAttributeWithError extends XDefAttribute { + final String errorMsg; + + 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 6af643a6d..cc13b5266 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 @@ -69,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); @@ -95,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 81d9af75d..cbc04bfec 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 @@ -25,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; @@ -66,12 +48,6 @@ 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 @@ -80,10 +56,28 @@ public class XLangTag extends XmlTagImpl { } public synchronized XLangTagMeta getTagMeta() { - if (tagMeta == null) { - tagMeta = ProjectEnv.withProject(getProject(), () -> XLangTagMeta.create(this)); + 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; + } } - return this.tagMeta; + + // Note: 避免后续访问出现 NPE 问题 + return Objects.requireNonNullElseGet(tagMeta, + () -> XLangTagMeta.errorTag(this, + "xlang.parser.tag-meta.creating-failed", + tagName)); } /** 标签存在被复用的可能,因此,需显式清理与之绑定的数据 */ @@ -134,8 +128,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; } @@ -204,143 +198,6 @@ public class XLangTag extends XmlTagImpl { return refs.toArray(PsiReference.EMPTY_ARRAY); } - /** @see SchemaMeta#getSchemaDef() */ - public 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(); - } - - /** 当前标签是否在元模型 *.xdef 中 */ - public boolean isInXDef() { - return isXDefDef(getSchemaDef()); - } - - /** @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(); @@ -349,7 +206,7 @@ public class XLangTag extends XmlTagImpl { } XLangTag parentTag = getParentTag(); - if (parentTag == null || !parentTag.isXplDefNode()) { + if (parentTag == null || !parentTag.getTagMeta().isXplNode()) { return null; } @@ -398,654 +255,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); - doc.setAdditional(defAttr instanceof XLangAttribute.XDefAttributeNotInCheckNS); - - 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 defNode, 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; - } - - if (defNode == null) { - return null; - } - - IXDefAttribute attr = defNode.getAttribute(attrName); - if (attr != null) { - return attr; - } - -// // 对于 xdef:check-ns 中不做校验的名字空间,该属性为任意类型 -// String attrNameNs = StringHelper.getNamespace(attrName); -// if (attrNameNs != null && def != null && !def.getXdefCheckNs().contains(attrNameNs)) { -// return new XLangAttribute.XDefAttributeNotInCheckNS(attrName); -// } - - // Note: 在 IXDefNode 中,对 xdef:unknown-attr 只记录了类型,并没有 IXDefAttribute 实体, - // 其处理逻辑见 XDefinitionParser#parseNode - XDefTypeDecl xdefUnknownAttrType = defNode.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 = defNode; - 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() { - String tagName = getName(); - - if (schemaMeta == null || !isValid() /* 文件已无效 */) { - schemaMeta = createSchemaMeta(); - } - // 根节点发生了 schema 相关的更新(包括 schema 的依赖的变更),或者标签名发生了变化 - else if (schemaMeta != null // - && (!tagName.equals(schemaMeta.tagName) || isRootTag()) // - ) { - SchemaMeta newSchemaMeta = createSchemaMeta(); - - if (!Objects.equals(schemaMeta, newSchemaMeta)) { - clearSchemaMeta(); - schemaMeta = newSchemaMeta; - } - } - - if (schemaMeta == null) { - // Note: 避免后续访问成员变量出现 NPE 问题 - return UNKNOWN_SCHEMA_META; - } - return schemaMeta; - } - - private void clearSchemaMeta() { - this.schemaMeta = null; - - // 子节点同时失效 - for (PsiElement child : getChildren()) { - if (child instanceof XLangTag tag) { - tag.clearSchemaMeta(); - } - } - } - - private SchemaMeta createSchemaMeta() { + private XLangTagMeta createTagMeta() { Project project = getProject(); try { - return ProjectEnv.withProject(project, this::doCreateSchemaMeta); + return ProjectEnv.withProject(project, () -> XLangTagMeta.create(this)); } catch (ProcessCanceledException e) { - // Note: 若处理被中断,则保持元模型信息为空,以便于后续再重新初始化 + // Note: 若处理被中断,则保持节点定义信息为空,以便于后续再重新初始化 return null; } } - private SchemaMeta doCreateSchemaMeta() { - if (isRootTag()) { - return doCreateRootTagSchemaMeta(); - } - - XLangTag parentTag = getParentTag(); - SchemaMeta parentSchemaMeta = parentTag != null ? parentTag.getSchemaMeta() : null; - if (parentSchemaMeta == null) { - return UNKNOWN_SCHEMA_META; - } - - String tagName = getName(); - String tagNs = getNamespacePrefix(); - - IXDefNode xplUnknownTagDefNode = XDefPsiHelper.getXplDef().getRootNode().getXdefUnknownTag(); - - return new ChildTagSchemaMeta(tagName, parentSchemaMeta, xplUnknownTagDefNode, tagNs); - } - - private SchemaMeta doCreateRootTagSchemaMeta() { - String schemaUrl = XDefPsiHelper.getSchemaPath(this); - if (schemaUrl == null) { - return UNKNOWN_SCHEMA_META; - } - - String xdefNs = XmlPsiHelper.getXmlnsForUrl(this, XDslConstants.XDSL_SCHEMA_XDEF); - String xdslNs = XmlPsiHelper.getXmlnsForUrl(this, XDslConstants.XDSL_SCHEMA_XDSL); - XDefKeys xdefKeys = XDefKeys.of(xdefNs); - XDslKeys xdslKeys = XDslKeys.of(xdslNs); - - IXDefinition schemaDef = XDefPsiHelper.loadSchema(schemaUrl); - IXDefNode xdslDefNode = XDefPsiHelper.getXDslDef().getRootNode(); - - IXDefinition selfDef = null; - // x:schema 为 /nop/schema/xdef.xdef 时,其自身也为元模型 - if (isXDefDef(schemaDef)) { - String vfsPath = XmlPsiHelper.getNopVfsPath(this); - - // Note: 正在编辑中的 xdef 有可能是不完整的,此时将无法解析出 IXDefinition - if (vfsPath != null) { - selfDef = XDefPsiHelper.loadSchema(vfsPath); - } else { - // 适配单元测试环境:待测试资源可能不是标准的 vfs 资源 - selfDef = XDefPsiHelper.loadSchema(getContainingFile()); - } - } - - String tagName = getName(); - return new RootTagSchemaMeta(tagName, schemaDef, xdslDefNode, selfDef, xdefKeys, xdslKeys); - } - - /** 指定的 def 是否为元元模型 /nop/schema/xdef.xdef */ - private static boolean isXDefDef(IXDefinition def) { - String defVfsPath = XmlPsiHelper.getNopVfsPath(def); - - return XDslConstants.XDSL_SCHEMA_XDEF.equals(defVfsPath); - } - - private static class RootTagSchemaMeta extends SchemaMeta { + private void clearTagMeta() { + this.tagMeta = null; - RootTagSchemaMeta( - String tagName, // - IXDefinition schemaDef, // - IXDefNode xdslDefNode, IXDefinition selfDef, // - XDefKeys xdefKeys, XDslKeys xdslKeys // - ) { - super(tagName, schemaDef, xdslDefNode, selfDef, xdefKeys, xdslKeys); - } - - // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - @Override - protected IXDefNode doGetSchemaDefNode() { - IXDefinition def = getSchemaDef(); - if (def == null) { - return null; - } - - IXDefNode defNode = def.getRootNode(); - // 如果不是元模型(*.xdef),则其根节点名称必须与其 x:schema 所定义的根节点名称保持一致, - // 除非根节点被定义为 xdef:unknown-tag - if (!isXDefDef(def) && !defNode.isUnknownTag() // - && !defNode.getTagName().equals(tagName) // - ) { - defNode = null; - } - - return defNode; - } - - @Override - protected IXDefNode doGetSelfDefNode() { - IXDefinition def = getSelfDef(); - - return def != null ? def.getRootNode() : null; - } - // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) { - return false; + // 子节点同时失效 + for (PsiElement child : getChildren()) { + if (child instanceof XLangTag tag) { + tag.clearTagMeta(); } - - RootTagSchemaMeta that = (RootTagSchemaMeta) o; - return Objects.equals(tagName, that.tagName) - && Objects.equals(getSchemaDef(), that.getSchemaDef()) - && Objects.equals(getXDslDefNode(), that.getXDslDefNode()) - && Objects.equals(getSelfDef(), that.getSelfDef()) - && Objects.equals(getXDefKeys(), that.getXDefKeys()) - && Objects.equals(getXDslKeys(), that.getXDslKeys()); } } - private static class ChildTagSchemaMeta extends SchemaMeta { - private final SchemaMeta parent; - private final IXDefNode xplUnknownTagDefNode; - - private final String tagNs; - - ChildTagSchemaMeta( - String tagName, SchemaMeta parent, // - IXDefNode xplUnknownTagDefNode, // - String tagNs + public static String replaceXmlNs(String name, String fromNs, String toNs) { + if (fromNs == null || toNs == null // + || fromNs.equals(toNs) // + || !StringHelper.startsWithNamespace(name, fromNs) // ) { - super(tagName); - - this.parent = parent; - this.xplUnknownTagDefNode = xplUnknownTagDefNode; - - this.tagNs = tagNs; - } - - // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< - - @Override - protected @Nullable IXDefinition doGetSchemaDef() { - return parent.getSchemaDef(); - } - - @Override - protected @Nullable IXDefNode doGetSchemaDefNode() { - if (XDslKeys.DEFAULT.NS.equals(tagNs) && !parent.isInXDslXDef()) { - return getXDslDefNode(); - } - - IXDefNode parentDefNode = parent.getSchemaDefNode(); - if (parent.isXplDefNode()) { - // Xpl 子节点均为 xdef:unknown-tag - parentDefNode = xplUnknownTagDefNode; - } - - 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 - protected @Nullable IXDefNode doGetXDslDefNode() { - IXDefNode parentDefNode = parent.getXDslDefNode(); - - return getXDefNodeChild(parentDefNode, tagName); - } - - @Override - protected @Nullable IXDefinition doGetSelfDef() { - return parent.getSelfDef(); - } - - @Override - protected @Nullable IXDefNode doGetSelfDefNode() { - // 在非 xdsl.xdef 中的 x 名字空间的节点,始终不视为自定义节点 - if (XDslKeys.DEFAULT.NS.equals(tagNs) && !parent.isInXDslXDef()) { - return null; - } - - IXDefNode parentDefNode = parent.getSelfDefNode(); - - return getXDefNodeChild(parentDefNode, tagName); - } - // >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - - @Override - protected @NotNull XDefKeys doGetXDefKeys() { - return parent.getXDefKeys(); - } - - @Override - protected @NotNull XDslKeys doGetXDslKeys() { - return parent.getXDslKeys(); + return name; } - } - - private static class UnknownSchemaMeta extends SchemaMeta { - UnknownSchemaMeta() { - super(null); - } + return replaceXmlNs(name, toNs); } - /** - * 注意,由于涉及对 *.xdef 的修改,因此,需采用实时加载方式获取 {@link IXDefinition} - * 和 {@link IXDefNode},不能缓存其实体对象。{@link XDefPsiHelper#loadSchema(String)} - * 是有缓存和失效机制的,不会明显影响性能 - */ - private static abstract class SchemaMeta { - protected final String tagName; - - private IXDefinition schemaDef; - private IXDefNode schemaDefNode; - private IXDefNode xdslDefNode; - private IXDefinition selfDef; - private IXDefNode selfDefNode; - private XDefKeys xdefKeys; - private XDslKeys xdslKeys; - - SchemaMeta(String tagName) { - this.tagName = tagName; - } - - SchemaMeta( - String tagName, // - IXDefinition schemaDef, // - IXDefNode xdslDefNode, IXDefinition selfDef, // - XDefKeys xdefKeys, XDslKeys xdslKeys - ) { - this.tagName = tagName; - this.schemaDef = schemaDef; - this.xdslDefNode = xdslDefNode; - this.selfDef = selfDef; - this.xdefKeys = xdefKeys; - this.xdslKeys = xdslKeys; - } - - /** - * 当前标签所在的元模型(在 *.xdef 中定义) - *

- * 允许元模型不存在,以支持检查 xdsl.xdef 对应的节点 - */ - public @Nullable IXDefinition getSchemaDef() { - if (schemaDef == null) { - schemaDef = doGetSchemaDef(); - } - return schemaDef; - } - - /** @see #getSchemaDef */ - protected @Nullable IXDefinition doGetSchemaDef() { - return null; - } - - /** 当前标签在 {@link #getSchemaDef()} 中所对应的节点 */ - public @Nullable IXDefNode getSchemaDefNode() { - if (schemaDefNode == null) { - schemaDefNode = doGetSchemaDefNode(); - } - return schemaDefNode; - } - - /** @see #getSchemaDefNode */ - protected @Nullable IXDefNode doGetSchemaDefNode() { - return null; - } - - /** - * 当前标签在 xdsl 模型(xdsl.xdef)中所对应的节点。 - * 注:所有 DSL 模型的节点均与 xdsl.xdef 的节点存在对应 - */ - public @Nullable IXDefNode getXDslDefNode() { - if (xdslDefNode == null) { - xdslDefNode = doGetXDslDefNode(); - } - return xdslDefNode; - } - - /** @see #getXDslDefNode */ - protected @Nullable IXDefNode doGetXDslDefNode() { - return null; - } - - /** 当当前标签定义在 *.xdef 文件中时,需记录该元模型 */ - public @Nullable IXDefinition getSelfDef() { - if (selfDef == null) { - selfDef = doGetSelfDef(); - } - return selfDef; - } - - /** @see #getSelfDef */ - protected @Nullable IXDefinition doGetSelfDef() { - return null; - } - - /** 当前标签定义在 {@link #getSelfDef()} 中所对应的节点 */ - public @Nullable IXDefNode getSelfDefNode() { - if (selfDefNode == null) { - selfDefNode = doGetSelfDefNode(); - } - return selfDefNode; - } - - /** @see #getSelfDefNode */ - protected @Nullable IXDefNode doGetSelfDefNode() { - return null; - } - - /** - * /nop/schema/xdef.xdef 对应的 {@link XDefKeys}。 - * 仅在元模型中设置,如 xmlns:xdef="/nop/schema/xdef.xdef" - */ - public @NotNull XDefKeys getXDefKeys() { - if (xdefKeys == null) { - xdefKeys = doGetXDefKeys(); - } - return xdefKeys; - } - - /** @see #getXDefKeys */ - protected @NotNull XDefKeys doGetXDefKeys() { - return XDefKeys.DEFAULT; - } - - /** - * /nop/schema/xdsl.xdef 对应的 {@link XDslKeys}。 - * 在 DSL 模型(含元模型)中均有设置,如 xmlns:x="/nop/schema/xdsl.xdef" - */ - public @NotNull XDslKeys getXDslKeys() { - if (xdslKeys == null) { - xdslKeys = doGetXDslKeys(); - } - return xdslKeys; - } - - /** @see #getXDslKeys */ - protected @NotNull XDslKeys doGetXDslKeys() { - return XDslKeys.DEFAULT; - } - - /** 当前标签是否在元元模型 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.isXplTypeNode(defNode) // - || XDslConstants.XDSL_SCHEMA_XPL.equals(defPath) // - ) { - return true; - } - - return isXlibSourceNode(defNode, defPath); - } - - protected 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 index fd81b80a6..026a70563 100644 --- 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 @@ -8,16 +8,27 @@ 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.parse.XDefTypeDeclParser; import io.nop.xlang.xdsl.XDslConstants; import io.nop.xlang.xdsl.XDslKeys; import io.nop.xlang.xpl.XplConstants; @@ -34,36 +45,39 @@ import static io.nop.idea.plugin.messages.NopPluginBundle.BUNDLE; * @date 2025-10-21 */ public class XLangTagMeta { - final String tagName; + 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; /** * 当前标签在元模型中所对应的定义节点: - * 元模型一般由根节点上的 x:schema 指定, - * 但 Xpl 类型节点的元模型始终为 /nop/schema/xpl.xdef + * 元模型一般由根节点上的 {@code x:schema} 指定, + * 但 Xpl 类型节点的元模型始终为 {@code /nop/schema/xpl.xdef} */ IXDefNode defNodeInSchema; /** - * 当前标签所在的元模型中通过 xdef:check-ns 所设置的待校验的名字空间列表: + * 当前标签所在的元模型中通过 {@code xdef:check-ns} 所设置的待校验的名字空间列表: * 节点或属性的名字空间若在该列表中,则其必须在元模型中显式定义 */ Set checkNsInSchema; /** - * 若当前标签在元模型文件(*.xdef)中, + * 若当前标签在元模型文件({@code *.xdef})中, * 则用于记录当前标签在该元模型中的定义 */ IXDefNode defNodeInSelfSchema; /** - * 若当前标签在元模型文件(*.xdef)中, - * 则用于记录该元模型 xdef:check-ns 属性的值 + * 若当前标签在元模型文件({@code *.xdef})中, + * 则用于记录该元模型 {@code xdef:check-ns} 属性的值 */ Set checkNsInSelfSchema; /** - * 当前标签所对应的元模型 /nop/schema/xdsl.xdef 中的定义节点: + * 当前标签所对应的元模型 {@code /nop/schema/xdsl.xdef} 中的定义节点: * 所有的 DSL (包括元模型自身)均由该模型定义 */ IXDefNode xdslDefNode; @@ -71,17 +85,36 @@ public class XLangTagMeta { XDefKeys xdefKeys; XDslKeys xdslKeys; - XLangTagMeta(@NotNull String tagName) { - this(tagName, null); + XLangTagMeta(@NotNull XLangTag tag) { + this(tag, null); } - XLangTagMeta(@NotNull String tagName, String errorMsg) { - this.tagName = tagName; + 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 tagName; + return tag.getName(); } public String getErrorMsg() { @@ -92,32 +125,44 @@ public class XLangTagMeta { return defNodeInSchema; } + public String getDefNodeSchemaPath() { + return XmlPsiHelper.getNopVfsPath(defNodeInSchema); + } + public IXDefNode getDefNodeInSelfSchema() { return defNodeInSelfSchema; } - public boolean hasError() { - return errorMsg != null; + public IXDefNode getXDslDefNode() { + return this.xdslDefNode; + } + + public XDefKeys getXdefKeys() { + return this.xdefKeys; + } + + public XDslKeys getXdslKeys() { + return this.xdslKeys; } - /** 是否未定义 */ - public boolean isUnknown() { - return defNodeInSchema == null; + /** 当前标签是否存在解析异常 */ + public boolean hasError() { + return errorMsg != null; } - /** 当前标签是否在元模型 *.xdef 中 */ + /** 当前标签是否在元模型 {@code *.xdef} 中 */ public boolean isInAnySchema() { return xdefKeys != null; } - /** 当前标签是否在元模型 /nop/schema/xdef.xdef 中 */ + /** 当前标签是否在元模型 {@code /nop/schema/xdef.xdef} 中 */ public boolean isInXdefSchema() { return checkNsInSelfSchema != null // && checkNsInSelfSchema.contains(XDefKeys.DEFAULT.NS) // && !XDefKeys.DEFAULT.equals(xdefKeys); } - /** 当前标签是否为 Xpl 类型节点,或者其对应元模型 /nop/schema/xpl.xdef 中的定义节点 */ + /** 当前标签是否为 Xpl 类型节点,或者其对应元模型 {@code /nop/schema/xpl.xdef} 中的定义节点 */ public boolean isXplNode() { if (defNodeInSchema == null) { return false; @@ -125,66 +170,283 @@ public class XLangTagMeta { return true; } - String vfsPath = XmlPsiHelper.getNopVfsPath(defNodeInSchema); + 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(tagName) + && 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(); + } + + /** + * 获取当前标签上指定属性的{@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, null); + } + + /** 获取当前标签的说明文档 */ + public XLangDocumentation getTagDocumentation() { + if (hasError()) { + return null; + } + + String tagName = tag.getName(); + // xdef:define 没有实体节点,故而,不显示其文档 + if (xdefKeys != null && xdefKeys.DEFINE.equals(tagName)) { + return null; + } + + IXDefNode defNode; + String tagNs = StringHelper.getNamespace(tagName); + // 不在元元模型 xdef.xdef 中的以 xdef 为名字空间的节点(不含 xdef:unknown-tag),显示其定义文档 + if (!isInXdefSchema() // + && xdefKeys != null && xdefKeys.NS.equals(tagNs) // + && !xdefKeys.UNKNOWN_TAG.equals(tagName) // + ) { + defNode = defNodeInSchema; + } + // *.xdef 中的自定义节点,显示自己的文档 + else if (defNodeInSelfSchema != null) { + 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; + IXDefNode defNode; + // xdef.xdef 中 xdef 名字空间下的属性 + 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; + } // + else { + attrName = XLangTag.replaceXmlNs(attrName, xdefKeys != null ? xdefKeys.NS : null, XDefKeys.DEFAULT.NS); + + defNode = defNodeInSelfSchema; + // 对于 *.xdef,优先取其自身定义节点上的属性文档 + if (defNode == null || defNode.getAttribute(attrName) == null) { + defNode = defNodeInSchema; + } + } + + IXDefAttribute defAttr = getDefAttrOnNode(attr, defNode, attrName, null, xdefKeys, null); + 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) { - String tagName = tag.getName(); - String tagNs = StringHelper.getNamespace(tagName); XLangTag parentTag = tag.getParentTag(); // 根节点 if (parentTag == null) { - return createForRootTag(tagNs, tagName, tag); + return createForRootTag(tag); } - return createForChildTag(parentTag, tagNs, tagName); + 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 XLangTagMeta error( - @NotNull String tagName, @NotNull @PropertyKey(resourceBundle = BUNDLE) String msgKey, + private static XDefAttribute errorAttr( + @NotNull XLangAttribute attr, @NotNull @PropertyKey(resourceBundle = BUNDLE) String msgKey, Object @NotNull ... msgParams ) { - return new XLangTagMeta(tagName, NopPluginBundle.message(msgKey, msgParams)); + return new XLangAttribute.XDefAttributeWithError(attr.getName(), NopPluginBundle.message(msgKey, msgParams)); } - private static XLangTagMeta createForRootTag(String tagNs, String tagName, XLangTag tag) { + 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 schemaUrl = XDefPsiHelper.getSchemaPath(tag); - if (schemaUrl == null) { - return error(tagName, "xlang.parser.tag-meta.schema-not-specified", xdslKeys.SCHEMA, tagName); + 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(schemaUrl); + IXDefinition schema = XDefPsiHelper.loadSchema(schemaPath); IXDefinition xdslSchema = XDefPsiHelper.getXDslDef(); // 若其元模型为 /nop/schema/xdef.xdef,则其自身也为元模型(*.xdef) - boolean inSchema = XDslConstants.XDSL_SCHEMA_XDEF.equals(schemaUrl) // + boolean inSchema = XDslConstants.XDSL_SCHEMA_XDEF.equals(schemaPath) // || isXdefFile(tag.getContainingFile().getName()); // 非 xdef/xpl 模型,全部视为 xdsl - if (schema == null && !inSchema && !isXplXdefFile(schemaUrl)) { + if (schema == null && !inSchema && !isXplXdefFile(schemaPath)) { schema = xdslSchema; } // 若元模型加载失败,则不做识别 if (schema == null) { - return error(tagName, "xlang.parser.tag-meta.schema-loading-failed", schemaUrl); + return errorTag(tag, "xlang.parser.tag-meta.schema-loading-failed", schemaPath); } String xdefNs = XmlPsiHelper.getXmlnsForUrl(tag, XDslConstants.XDSL_SCHEMA_XDEF); @@ -192,12 +454,12 @@ public class XLangTagMeta { // Note: xdef 名字空间的根节点必须为 xdef.xdef 中已定义的节点 if (inSchema && xdefKeys.NS.equals(tagNs)) { - if (getChildXdefDefNode(xdefKeys, schema, tagName) == null) { - return error(tagName, - "xlang.parser.tag-meta.ns-tag-not-defined-in-schema", - tagName, - XDefKeys.DEFAULT.NS, - XDslConstants.XDSL_SCHEMA_XDEF); + 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); } } @@ -207,11 +469,11 @@ public class XLangTagMeta { if (!inSchema && !defNodeInSchema.isUnknownTag() // && !defNodeInSchema.getTagName().equals(tagName) // ) { - return error(tagName, - "xlang.parser.tag-meta.root-tag-name-not-match-schema-root", - tagName, - defNodeInSchema.getTagName(), - schemaUrl); + return errorTag(tag, + "xlang.parser.tag-meta.root-tag-name-not-match-schema-root", + tagName, + defNodeInSchema.getTagName(), + schemaPath); } // Note: 正在编辑中的 xdef 有可能是不完整的,此时将无法解析出 IXDefinition @@ -227,7 +489,7 @@ public class XLangTagMeta { } } - XLangTagMeta tagMeta = new XLangTagMeta(tagName); + XLangTagMeta tagMeta = new XLangTagMeta(tag); tagMeta.defNodeInSchema = defNodeInSchema; tagMeta.checkNsInSchema = schema.getXdefCheckNs(); @@ -243,11 +505,14 @@ public class XLangTagMeta { return tagMeta; } - private static XLangTagMeta createForChildTag(XLangTag parentTag, String tagNs, String tagName) { + private static XLangTagMeta createForChildTag(XLangTag tag, XLangTag parentTag) { + String tagName = tag.getName(); + String tagNs = StringHelper.getNamespace(tagName); + XLangTagMeta parentTagMeta = parentTag.getTagMeta(); // 无定义节点 - if (parentTagMeta.isUnknown()) { - return error(tagName, "xlang.parser.tag-meta.parent-not-defined", parentTagMeta.getTagName()); + if (parentTagMeta.hasError()) { + return errorTag(tag, "xlang.parser.tag-meta.parent-not-defined", parentTagMeta.getTagName()); } XDefKeys xdefKeys = parentTagMeta.xdefKeys; @@ -256,15 +521,15 @@ public class XLangTagMeta { IXDefNode defNodeInSchema = null; // 1. 获取确定的 xdsl 节点,如 等 if (xdslKeys.NS.equals(tagNs)) { - String xdslTagName = replaceTagNs(tagName, XDslKeys.DEFAULT.NS); + String xdslTagName = XLangTag.replaceXmlNs(tagName, XDslKeys.DEFAULT.NS); defNodeInSchema = getChildDefNode(parentTagMeta.xdslDefNode, xdslTagName); if (defNodeInSchema.isUnknownTag()) { - return error(tagName, - "xlang.parser.tag-meta.ns-tag-not-defined-in-schema", - tagName, - XDslKeys.DEFAULT.NS, - XDslConstants.XDSL_SCHEMA_XDSL); + return errorTag(tag, + "xlang.parser.tag-meta.ns-tag-not-defined-in-schema", + tagName, + XDslKeys.DEFAULT.NS, + XDslConstants.XDSL_SCHEMA_XDSL); } } @@ -273,11 +538,11 @@ public class XLangTagMeta { defNodeInSchema = getChildDefNode(XDefPsiHelper.getXplDef().getRootNode(), tagName); if (XplConstants.XPL_NS.equals(tagNs) && defNodeInSchema.isUnknownTag()) { - return error(tagName, - "xlang.parser.tag-meta.ns-tag-not-defined-in-schema", - tagName, - XplConstants.XPL_NS, - XDslConstants.XDSL_SCHEMA_XPL); + return errorTag(tag, + "xlang.parser.tag-meta.ns-tag-not-defined-in-schema", + tagName, + XplConstants.XPL_NS, + XDslConstants.XDSL_SCHEMA_XPL); } } @@ -293,13 +558,13 @@ public class XLangTagMeta { if (defNodeInSchema == null && parentTagMeta.isInAnySchema() // && xdefKeys.NS.equals(tagNs) // ) { - defNodeInSchema = getChildXdefDefNode(xdefKeys, parentTagMeta.defNodeInSchema, tagName); + defNodeInSchema = getChildXdefDefNode(parentTagMeta.defNodeInSchema, tagName, xdefKeys); if (defNodeInSchema == null) { - return error(tagName, - "xlang.parser.tag-meta.ns-tag-not-defined-in-schema", - tagName, - XDefKeys.DEFAULT.NS, - XDslConstants.XDSL_SCHEMA_XDEF); + return errorTag(tag, + "xlang.parser.tag-meta.ns-tag-not-defined-in-schema", + tagName, + XDefKeys.DEFAULT.NS, + XDslConstants.XDSL_SCHEMA_XDEF); } } @@ -316,12 +581,12 @@ public class XLangTagMeta { && parentTagMeta.checkNsInSchema.contains(tagNs) // ) { if (defNodeInSchema == null || defNodeInSchema.isUnknownTag()) { - String schemaUrl = XmlPsiHelper.getNopVfsPath(parentTagMeta.defNodeInSchema); - return error(tagName, - "xlang.parser.tag-meta.ns-tag-should-be-defined-in-schema", - tagName, - tagNs, - schemaUrl); + 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 节点 @@ -331,10 +596,25 @@ public class XLangTagMeta { } if (defNodeInSchema == null) { - return error(tagName, "xlang.parser.tag-meta.tag-not-defined", tagName); + 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()); + } + } } - XLangTagMeta tagMeta = new XLangTagMeta(tagName); + 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; @@ -349,6 +629,77 @@ public class XLangTagMeta { 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-defined-tag", attrName); + } + + IXDefAttribute defAttr = defNode.getAttribute(attrName); + if (defAttr != null) { + return defAttr; + } + + // 对于 xdef:check-ns 中不做校验的名字空间,其下的属性均为任意类型 + String attrNameNs = StringHelper.getNamespace(attrName); + if (attrNameNs != null && checkNsInSchema != null && checkNsInSchema.contains(attrNameNs)) { + return new XLangAttribute.XDefAttributeWithTypeAny(attrName); + } + + 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"); } @@ -357,17 +708,13 @@ public class XLangTagMeta { return XDslConstants.XDSL_SCHEMA_XPL.equals(filename); } - private static String replaceTagNs(String tagName, String ns) { - return ns + tagName.substring(tagName.indexOf(':')); - } - - /** 获得指定标签名的子定义节点,或者 xdef:unknown-tag 定义节点 */ + /** 获得指定标签名的子定义节点,或者 {@code xdef:unknown-tag} 定义节点 */ private static IXDefNode getChildDefNode(IXDefNode defNode, String tagName) { return defNode != null ? defNode.getChild(tagName) : null; } - private static IXDefNode getChildXdefDefNode(XDefKeys xdefKeys, IXDefNode defNode, String tagName) { - String xdefTagName = replaceTagNs(tagName, XDefKeys.DEFAULT.NS); + 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)) { 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 d1bfd8642..9b887f213 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 @@ -69,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/reference/XLangAttributeReference.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/reference/XLangAttributeReference.java index f0f6a374f..828c03f32 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; @@ -53,12 +54,12 @@ public class XLangAttributeReference extends XLangReferenceBase { @Override public @Nullable PsiElement resolveInner() { - if (defAttr == null) { + if (XLangAttribute.isNullOrErrorDefAttr(defAttr)) { return null; } // 对于带名字空间的附加属性,其定义直接引用其自身 - if (defAttr instanceof XLangAttribute.XDefAttributeNotInCheckNS) { + if (defAttr instanceof XLangAttribute.XDefAttributeWithTypeAny) { String path = XmlPsiHelper.getNopVfsPath(myElement); return path == null ? null : new NopVirtualFile(myElement, path, (file) -> myElement); @@ -97,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; } @@ -134,8 +137,12 @@ public class XLangAttributeReference extends XLangReferenceBase { 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); 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 f76bdbf1e..0aae2bd27 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,6 +14,7 @@ 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; @@ -80,9 +81,10 @@ 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(XLANG_NAME_COMPARATOR) // .toArray(); 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 26e18a09f..ce900c013 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,6 +19,7 @@ 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; @@ -46,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, @@ -67,7 +69,9 @@ public class XLangTagReference extends XLangReferenceBase { // Note: 在自动补全阶段,DSL 结构很可能是不完整的,只能从 xml 角度做分析 XLangTag tag = (XLangTag) myElement; String tagNs = tag.getNamespacePrefix(); - XDefKeys tagXDefKeys = tag.getXDefKeys(); + XLangTagMeta tagMeta = tag.getTagMeta(); + + XDefKeys tagXdefKeys = tagMeta.getXdefKeys(); boolean trimLookupTagNs = !tagNs.isEmpty(); XLangTag parentTag = tag.getParentTag(); @@ -75,12 +79,12 @@ public class XLangTagReference extends XLangReferenceBase { String fileName = tag.getContainingFile().getName(); String tagName = fileName.contains(".") ? fileName.substring(0, fileName.indexOf('.')) : fileName; - if (!tag.isInXDef()) { - IXDefNode rootDefNode = tag.getSchemaDef().getRootNode(); + if (!tagMeta.isInAnySchema()) { + IXDefNode rootDefNode = tag.getRootTag().getTagMeta().getDefNodeInSchema(); return new LookupElement[] { // 对于非元模型的根节点,其标签名只能为其 xdef 中的根节点名 - !rootDefNode.isUnknownTag() // + rootDefNode != null && !rootDefNode.isUnknownTag() // ? lookupTag(rootDefNode, trimLookupTagNs) // 若根节点被定义为 xdef:unknown-tag,则以文件名作为其标签名 : lookupTag(tagName, null, trimLookupTagNs), @@ -90,7 +94,7 @@ public class XLangTagReference extends XLangReferenceBase { // 对于元模型的根节点,则一般以文件名作为其标签名 lookupTag(tagName, null, trimLookupTagNs), // 或者为 xdef:unknown-tag - lookupTag(tagXDefKeys != null ? tagXDefKeys.UNKNOWN_TAG : XDefKeys.DEFAULT.UNKNOWN_TAG, + lookupTag(tagXdefKeys != null ? tagXdefKeys.UNKNOWN_TAG : XDefKeys.DEFAULT.UNKNOWN_TAG, null, trimLookupTagNs), }; @@ -99,8 +103,9 @@ public class XLangTagReference extends XLangReferenceBase { 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; } 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 de4b936cf..20db34896 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,6 +18,7 @@ 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; @@ -114,10 +115,13 @@ public class XLangXPrototypeReference extends XLangReferenceBase { } 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 5342605e5..7e23af8f3 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; @@ -70,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 角度做分析 @@ -78,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)) // + .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/xlib/XlibTagMeta.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/xlib/XlibTagMeta.java index 952bc0b98..141b7de37 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/resources/messages/NopPluginBundle.properties b/nop-dev-tools/nop-idea-plugin/src/main/resources/messages/NopPluginBundle.properties index d13ccee2b..b040f769b 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,7 @@ 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.expected-root = Root tag should be <{0}/> -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 @@ -26,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 @@ -52,5 +48,9 @@ xlang.parser.tag-meta.schema-loading-failed = The schema ''{0}'' is loading fail 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-defined-tag = The tag which owns attribute ''{0}'' isn''t defined +xlang.parser.tag-meta.attr-not-defined = The attribute ''{0}'' isn''t defined +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 2ce54845f..5249a4102 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,7 @@ 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.expected-root = \u6839\u6807\u7B7E\u5E94\u8BE5\u4E3A <{0}/> -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 @@ -26,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 @@ -52,5 +48,9 @@ xlang.parser.tag-meta.schema-loading-failed = \u5143\u6A21\u578B ''{0}'' \u52A0\ 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-defined-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.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/lang/TestXLangTagMeta.java b/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangTagMeta.java index 05612b3cd..ec457f4c0 100644 --- 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 @@ -35,7 +35,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertTrue(tagMeta.isInXdefSchema()); @@ -57,7 +56,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); @@ -80,7 +78,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertTrue(tagMeta.isInXdefSchema()); @@ -101,7 +98,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertTrue(tagMeta.isInXdefSchema()); @@ -124,7 +120,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertTrue(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertTrue(tagMeta.isInXdefSchema()); @@ -145,7 +140,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertTrue(tagMeta.isInXdefSchema()); @@ -171,7 +165,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertTrue(tagMeta.isInXdefSchema()); @@ -194,7 +187,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertTrue(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertTrue(tagMeta.isInXdefSchema()); @@ -218,7 +210,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertTrue(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertTrue(tagMeta.isInXdefSchema()); @@ -241,7 +232,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertTrue(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertTrue(tagMeta.isInXdefSchema()); @@ -263,7 +253,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); @@ -286,7 +275,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); @@ -309,7 +297,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertTrue(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); @@ -332,7 +319,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); @@ -357,7 +343,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); @@ -381,7 +366,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertTrue(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); @@ -403,7 +387,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); @@ -427,7 +410,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertTrue(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); @@ -449,7 +431,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertTrue(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); @@ -470,7 +451,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); @@ -493,7 +473,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); @@ -519,7 +498,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertTrue(tagMeta.isXplNode()); assertTrue(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); @@ -541,7 +519,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertFalse(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); @@ -562,7 +539,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertFalse(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); @@ -583,13 +559,11 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertTrue(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertFalse(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); - assertEquals("unknown", tag.getName()); - assertNull(tagMeta.getTagName()); + assertEquals("unknown", tagMeta.getTagName()); assertNull(tagMeta.getDefNodeInSchema()); assertNull(tagMeta.getDefNodeInSelfSchema()); @@ -605,7 +579,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertTrue(tagMeta.isXplNode()); assertFalse(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); @@ -629,7 +602,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertTrue(tagMeta.isXplNode()); assertFalse(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); @@ -651,7 +623,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertFalse(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertFalse(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); @@ -672,18 +643,18 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { """, // (tag, tagMeta) -> { - assertTrue(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertFalse(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); - assertEquals("xui:style", tag.getName()); - assertNull(tagMeta.getTagName()); + assertEquals("xui:style", tagMeta.getTagName()); assertNull(tagMeta.getDefNodeInSchema()); assertNull(tagMeta.getDefNodeInSelfSchema()); } // ); + + // TODO xjson type child } public void testCreateUnknownTagMeta() { @@ -694,12 +665,11 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { (tag, tagMeta) -> { // 元模型未指定 assertTrue(tagMeta.hasError()); - assertTrue(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertFalse(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); - assertEquals("meta:unknown-tag", tag.getName()); + assertEquals("meta:unknown-tag", tagMeta.getTagName()); assertTrue(tagMeta.getErrorMsg().contains("No schema path is specified")); } // ); @@ -713,7 +683,6 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { (tag, tagMeta) -> { // 标签由 xdsl.xdef 定义 assertFalse(tagMeta.hasError()); - assertFalse(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertFalse(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); @@ -734,12 +703,11 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { (tag, tagMeta) -> { // meta 名字空间的标签未定义 assertTrue(tagMeta.hasError()); - assertTrue(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertFalse(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); - assertEquals("meta:unknown", tag.getName()); + assertEquals("meta:unknown", tagMeta.getTagName()); assertTrue(tagMeta.getErrorMsg().contains("corresponding namespace 'xdef'")); } // ); @@ -754,12 +722,11 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { (tag, tagMeta) -> { // meta 名字空间的标签未定义 assertTrue(tagMeta.hasError()); - assertTrue(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertFalse(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); - assertEquals("meta:abcd", tag.getName()); + assertEquals("meta:abcd", tagMeta.getTagName()); assertTrue(tagMeta.getErrorMsg().contains("corresponding namespace 'xdef'")); } // ); @@ -774,12 +741,11 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { (tag, tagMeta) -> { // x 名字空间的标签未定义 assertTrue(tagMeta.hasError()); - assertTrue(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertFalse(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); - assertEquals("x:any", tag.getName()); + assertEquals("x:any", tagMeta.getTagName()); assertTrue(tagMeta.getErrorMsg().contains("corresponding namespace 'x'")); } // ); @@ -796,12 +762,11 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { (tag, tagMeta) -> { // xpl 名字空间的标签未定义 assertTrue(tagMeta.hasError()); - assertTrue(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertFalse(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); - assertEquals("xpl:abc", tag.getName()); + assertEquals("xpl:abc", tagMeta.getTagName()); assertTrue(tagMeta.getErrorMsg().contains("corresponding namespace 'xpl'")); } // ); @@ -816,12 +781,11 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { (tag, tagMeta) -> { // 根节点标签与定义的不一致 assertTrue(tagMeta.hasError()); - assertTrue(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertFalse(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); - assertEquals("lang", tag.getName()); + assertEquals("lang", tagMeta.getTagName()); assertTrue(tagMeta.getErrorMsg().contains("doesn't match with the root tag")); } // ); @@ -837,12 +801,11 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { (tag, tagMeta) -> { // xui 名字空间的标签未显式定义 assertTrue(tagMeta.hasError()); - assertTrue(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertFalse(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); - assertEquals("xui:parent", tag.getName()); + assertEquals("xui:parent", tagMeta.getTagName()); assertTrue(tagMeta.getErrorMsg().contains("should be defined in schema")); } // ); @@ -859,12 +822,11 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { (tag, tagMeta) -> { // 父标签未定义 assertTrue(tagMeta.hasError()); - assertTrue(tagMeta.isUnknown()); assertFalse(tagMeta.isXplNode()); assertFalse(tagMeta.isInAnySchema()); assertFalse(tagMeta.isInXdefSchema()); - assertEquals("xui:child", tag.getName()); + assertEquals("xui:child", tagMeta.getTagName()); assertTrue(tagMeta.getErrorMsg().contains("'xui:parent' isn't defined")); } // ); -- Gitee From 7317a187b4d7a74759957d01ff0a9f9d620f3560 Mon Sep 17 00:00:00 2001 From: flytreeleft Date: Sat, 8 Nov 2025 16:27:08 +0800 Subject: [PATCH 12/12] =?UTF-8?q?nop-idea-pugin:=20=E8=A1=A5=E5=85=85?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B=EF=BC=8C=E5=B9=B6=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E5=B7=B2=E7=9F=A5=E7=BC=BA=E9=99=B7=EF=BC=9B=E6=94=B9?= =?UTF-8?q?=E8=BF=9B=E9=94=99=E8=AF=AF=E6=8F=90=E7=A4=BA=E5=92=8C=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E6=98=BE=E7=A4=BA=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../idea/plugin/annotator/XLangAnnotator.java | 36 ++- .../io/nop/idea/plugin/lang/psi/XLangTag.java | 3 +- .../idea/plugin/lang/psi/XLangTagMeta.java | 89 +++++-- .../messages/NopPluginBundle.properties | 3 +- .../messages/NopPluginBundle_zh.properties | 3 +- .../idea/plugin/BaseXLangPluginTestCase.java | 11 +- .../doc/TestXLangDocumentationProvider.java | 106 +++++++- .../nop/idea/plugin/lang/TestXLangParser.java | 2 +- .../idea/plugin/lang/TestXLangReferences.java | 2 +- .../nop/idea/plugin/lang/TestXLangRename.java | 4 +- .../idea/plugin/lang/TestXLangTagMeta.java | 213 +++++++++++++++- .../test/resources/_vfs/test/ast/xlang-1.ast | 229 +++++++++--------- .../test/resources/_vfs/test/doc/example.xdef | 7 + .../test/resources/_vfs/test/doc/example.xdoc | 4 + .../resources/_vfs/test/lang/lang-error.xdef | 15 ++ .../test/resources/_vfs/test/lang/lang.xdef | 2 + 16 files changed, 561 insertions(+), 168 deletions(-) create mode 100644 nop-idea-plugin/src/test/resources/_vfs/test/lang/lang-error.xdef 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 b91007149..61099e22c 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 @@ -139,14 +139,7 @@ public class XLangAnnotator implements Annotator { return; } - XmlToken[] tokens = new XmlToken[] { - XmlTagUtil.getStartTagNameElement(tag), XmlTagUtil.getEndTagNameElement(tag) - }; - for (XmlToken token : tokens) { - if (token != null) { - _errorAnnotation(holder, token.getTextRange(), tagMeta.getErrorMsg()); - } - } + tagErrorAnnotation(holder, tag, tagMeta.getErrorMsg()); } private void checkTagBySchemaDefNode(@NotNull AnnotationHolder holder, @NotNull XLangTag tag) { @@ -162,10 +155,7 @@ public class XLangAnnotator implements Annotator { if (child instanceof XLangTag t // && Objects.equals(t.getName(), tag.getName()) // ) { - errorAnnotation(holder, - getStartTagName(tag).getTextRange(), - "xlang.annotation.tag.multiple-tag-not-allowed", - tag.getName()); + tagErrorAnnotation(holder, tag, "xlang.annotation.tag.multiple-tag-not-allowed", tag.getName()); return; } } @@ -185,10 +175,7 @@ public class XLangAnnotator implements Annotator { } 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; } @@ -270,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/lang/psi/XLangTag.java b/nop-dev-tools/nop-idea-plugin/src/main/java/io/nop/idea/plugin/lang/psi/XLangTag.java index cbc04bfec..bdc2c5d65 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 @@ -83,7 +83,8 @@ public class XLangTag extends XmlTagImpl { /** 标签存在被复用的可能,因此,需显式清理与之绑定的数据 */ @Override public void clearCaches() { - tagMeta = null; + clearTagMeta(); + super.clearCaches(); } 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 index 026a70563..fa80eb1a0 100644 --- 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 @@ -28,6 +28,7 @@ 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; @@ -212,7 +213,7 @@ public class XLangTagMeta { public boolean canBeMultipleTag() { IXDefNode defNode = getDefNodeInSchema(); - return defNode != null && defNode.isAllowMultiple(); + return (defNode != null && defNode.isAllowMultiple()) || isXplNode(); } /** @@ -274,7 +275,7 @@ public class XLangTagMeta { // Note: xdsl.xdef 的属性在固定的名字空间 x 中声明 attrName = XLangTag.replaceXmlNs(attrName, xdslKeys.NS, XDslKeys.DEFAULT.NS); - return getDefAttrOnNode(attr, xdslDefNode, attrName, null, xdefKeys, null); + return getDefAttrOnNode(attr, xdslDefNode, attrName, null, xdefKeys, Set.of(XDslKeys.DEFAULT.NS)); } /** 获取当前标签的说明文档 */ @@ -283,23 +284,31 @@ public class XLangTagMeta { return null; } + IXDefNode defNode = null; String tagName = tag.getName(); - // xdef:define 没有实体节点,故而,不显示其文档 + String tagNs = StringHelper.getNamespace(tagName); + + // 单独处理元模型中的 xdef:define 节点:其由父节点记录定义信息 if (xdefKeys != null && xdefKeys.DEFINE.equals(tagName)) { - return null; - } + String xdefName = tag.getAttributeValue(xdefKeys.NAME); - IXDefNode defNode; - String tagNs = StringHelper.getNamespace(tagName); - // 不在元元模型 xdef.xdef 中的以 xdef 为名字空间的节点(不含 xdef:unknown-tag),显示其定义文档 - if (!isInXdefSchema() // - && xdefKeys != null && xdefKeys.NS.equals(tagNs) // - && !xdefKeys.UNKNOWN_TAG.equals(tagName) // + 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; } - // *.xdef 中的自定义节点,显示自己的文档 - else if (defNodeInSelfSchema != null) { + // 元模型中的自定义节点,显示元模型中的文档 + else if (isInAnySchema() // + && !xdslKeys.NS.equals(tagNs) // + && !isXplNode() // + ) { + // Note: 可能会因元模型自身解析异常而无法得到节点定义 defNode = defNodeInSelfSchema; } // 其余 dsl 均显示节点的定义文档 @@ -337,27 +346,46 @@ public class XLangTagMeta { } String mainTitle = attrName; - IXDefNode defNode; - // xdef.xdef 中 xdef 名字空间下的属性 - if (isInXdefSchema() && XDefKeys.DEFAULT.NS.equals(attrNs)) { + 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; - // 对于 *.xdef,优先取其自身定义节点上的属性文档 if (defNode == null || defNode.getAttribute(attrName) == null) { defNode = defNodeInSchema; } } - IXDefAttribute defAttr = getDefAttrOnNode(attr, defNode, attrName, null, xdefKeys, null); + if (defNode == null) { + return null; + } + + IXDefAttribute defAttr = getDefAttrOnNode(attr, defNode, attrName, null, xdefKeys, checkNsSet); if (XLangAttribute.isNullOrErrorDefAttr(defAttr)) { return null; } @@ -535,7 +563,7 @@ public class XLangTagMeta { // 2. 若父节点为 Xpl 类型节点,则直接从 xpl.xdef 中获取节点(含带 xpl 名字空间的节点) if (defNodeInSchema == null && parentTagMeta.isXplNode()) { - defNodeInSchema = getChildDefNode(XDefPsiHelper.getXplDef().getRootNode(), tagName); + defNodeInSchema = getChildDefNode(XDefPsiHelper.getXplDef().getXdefUnknownTag(), tagName); if (XplConstants.XPL_NS.equals(tagNs) && defNodeInSchema.isUnknownTag()) { return errorTag(tag, @@ -650,7 +678,7 @@ public class XLangTagMeta { } if (defNode == null) { - return errorAttr(attr, "xlang.parser.tag-meta.attr-on-defined-tag", attrName); + return errorAttr(attr, "xlang.parser.tag-meta.attr-on-undefined-tag", attrName); } IXDefAttribute defAttr = defNode.getAttribute(attrName); @@ -658,12 +686,27 @@ public class XLangTagMeta { return defAttr; } - // 对于 xdef:check-ns 中不做校验的名字空间,其下的属性均为任意类型 + // 属性未显式定义 String attrNameNs = StringHelper.getNamespace(attrName); - if (attrNameNs != null && checkNsInSchema != null && checkNsInSchema.contains(attrNameNs)) { - return new XLangAttribute.XDefAttributeWithTypeAny(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); 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 b040f769b..d97793de2 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 @@ -51,6 +51,7 @@ xlang.parser.tag-meta.ns-tag-should-be-defined-in-schema = The tag ''{0}'' with 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-defined-tag = The tag which owns attribute ''{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 5249a4102..934b42da3 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 @@ -51,6 +51,7 @@ xlang.parser.tag-meta.ns-tag-should-be-defined-in-schema = \u540D\u5B57\u7A7A\u9 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-defined-tag = \u5C5E\u6027 ''{0}'' \u6240\u5728\u6807\u7B7E\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 56145862b..eea9bac0a 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() { @@ -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); 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 62047e4bb..91f16e34e 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/TestXLangParser.java b/nop-dev-tools/nop-idea-plugin/src/test/java/io/nop/idea/plugin/lang/TestXLangParser.java index b83363c42..eb94deda9 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 802526477..b13d16fd0 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(""" - + """, // 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 1f67850e2..9575c776d 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 @@ -23,7 +23,7 @@ public class TestXLangRename extends BaseXLangPluginTestCase { public void testRenameTag() { } - public void testRenameXlibTag() { + public void _testRenameXlibTag() { // 从定义侧更名 assertRename("NewCall", // """ @@ -81,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 index ec457f4c0..20d9ff61c 100644 --- 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 @@ -13,9 +13,11 @@ 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; /** @@ -654,7 +656,30 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { } // ); - // TODO xjson type child + 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() { @@ -730,6 +755,26 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { 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(""" + 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(); @@ -846,6 +1043,20 @@ public class TestXLangTagMeta extends BaseXLangPluginTestCase { 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()); } 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 08a6aa6f8..01b7e6ed8 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 264794eed..5a8d378d1 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 @@ + + +