fix inherit from
This commit is contained in:
+26
-14
@@ -8,7 +8,7 @@ import {
|
||||
} from "../language/context";
|
||||
import { resolveElementType } from "../language/typeContext";
|
||||
import * as model from "../model/schemaModel";
|
||||
import type { AttributeInfo, SimpleTypeInfo } from "../model/schemaModel";
|
||||
import type { AttributeInfo, ContentTypeInfo } from "../model/schemaModel";
|
||||
import { isLocalReferenceAttribute } from "../indexer/refs";
|
||||
import {
|
||||
findContainingGameObject,
|
||||
@@ -84,6 +84,13 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
item.documentation = docText ? new vscode.MarkdownString(docText) : undefined;
|
||||
item.detail = type ? t("RA3 XML · {0}", type) : t("RA3 XML");
|
||||
item.insertText = this.elementSnippet(child.name, type, ctx.element == null);
|
||||
const contentInfo = type ? model.contentInfoOfType(type) : undefined;
|
||||
if (contentInfo && this.simpleContentValueKind(contentInfo)) {
|
||||
item.command = {
|
||||
command: "editor.action.triggerSuggest",
|
||||
title: t("Suggest content value"),
|
||||
};
|
||||
}
|
||||
items.push(item);
|
||||
}
|
||||
return items;
|
||||
@@ -120,13 +127,14 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
if (model.isTopLevelElement(name)) {
|
||||
return new vscode.SnippetString(`${open}${name} id="$1">\n\t$0\n</${name}>`);
|
||||
}
|
||||
const info = type ? model.typeInfo(type) : undefined;
|
||||
// Simple types hold text content (asset id / enum / define / string), so
|
||||
// they need an explicit closing tag and a value placeholder instead of a
|
||||
// self-closing tag that can never contain a value.
|
||||
if (info?.kind === "simple") {
|
||||
// Simple types and simpleContent complex types hold text content (asset
|
||||
// id / enum / define / string), so they need an explicit closing tag and
|
||||
// a value placeholder instead of a self-closing tag that can never
|
||||
// contain a value.
|
||||
if (type && model.contentInfoOfType(type)) {
|
||||
return new vscode.SnippetString(`${open}${name}>$1</${name}>`);
|
||||
}
|
||||
const info = type ? model.typeInfo(type) : undefined;
|
||||
const hasChildren = info?.kind === "complex" && info.children.length > 0;
|
||||
if (hasChildren) {
|
||||
return new vscode.SnippetString(`${open}${name}>\n\t$0\n</${name}>`);
|
||||
@@ -338,6 +346,7 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
|
||||
// inheritFrom: same element type first, then everything.
|
||||
if (attrName === "inheritfrom") {
|
||||
if (!model.isAssetType(elType)) return [];
|
||||
if (!idx) return [];
|
||||
return this.assetIdItems(idx, el.name, null, prefix, make);
|
||||
}
|
||||
@@ -653,12 +662,14 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
const el = ctx.element;
|
||||
if (!el) return [];
|
||||
const elType = resolveElementType(el);
|
||||
const info = elType ? model.typeInfo(elType) : undefined;
|
||||
const info = elType ? model.contentInfoOfType(elType) : undefined;
|
||||
|
||||
// Simple-content element: the text between the tags is the value itself
|
||||
// (e.g. <CreateObject>CrateDebris_01</CreateObject>), so offer value
|
||||
// completions (asset ids / enums / defines) instead of child elements.
|
||||
if (info?.kind === "simple") {
|
||||
// Simple-content element (simple type or simpleContent complex type):
|
||||
// the text between the tags is the value itself (e.g.
|
||||
// <CreateObject>CrateDebris_01</CreateObject> or
|
||||
// <Sound>AudioFile</Sound>), so offer value completions (asset ids /
|
||||
// enums / defines) instead of child elements.
|
||||
if (info) {
|
||||
return this.simpleContentItems(el, elType, info, document, position, idx);
|
||||
}
|
||||
|
||||
@@ -668,7 +679,7 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
private simpleContentItems(
|
||||
el: XmlElement,
|
||||
elType: string | null,
|
||||
info: SimpleTypeInfo,
|
||||
info: ContentTypeInfo,
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position,
|
||||
idx: ModIndex | null,
|
||||
@@ -756,7 +767,8 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
item.detail = type ? t("RA3 XML · {0}", type) : t("RA3 XML");
|
||||
const doc = child.doc || (info?.kind === "complex" ? info.doc : "");
|
||||
if (doc) item.documentation = new vscode.MarkdownString(doc);
|
||||
if (info?.kind === "simple" && this.simpleContentValueKind(info)) {
|
||||
const contentInfo = type ? model.contentInfoOfType(type) : undefined;
|
||||
if (contentInfo && this.simpleContentValueKind(contentInfo)) {
|
||||
item.command = {
|
||||
command: "editor.action.triggerSuggest",
|
||||
title: t("Suggest content value"),
|
||||
@@ -767,7 +779,7 @@ export class Ra3CompletionProvider implements vscode.CompletionItemProvider {
|
||||
return items;
|
||||
}
|
||||
|
||||
private simpleContentValueKind(info: SimpleTypeInfo): boolean {
|
||||
private simpleContentValueKind(info: ContentTypeInfo): boolean {
|
||||
return (
|
||||
info.refType != null ||
|
||||
info.enumValues.length > 0 ||
|
||||
|
||||
@@ -451,10 +451,11 @@ export class Ra3Diagnostics {
|
||||
diags: vscode.Diagnostic[],
|
||||
provisional: boolean,
|
||||
): void {
|
||||
// Only simple-content elements carry a text value; complex elements'
|
||||
// "content" is child markup and must not be scanned for value refs.
|
||||
const info = elType ? model.typeInfo(elType) : undefined;
|
||||
if (info?.kind !== "simple") return;
|
||||
// Only simple-content elements carry a text value (simple types and
|
||||
// simpleContent complex types); ordinary complex elements' "content" is
|
||||
// child markup and must not be scanned for value refs.
|
||||
const info = elType ? model.contentInfoOfType(elType) : undefined;
|
||||
if (!info) return;
|
||||
if (el.selfClosing || el.closeTagStart < 0) return;
|
||||
const text = document.getText();
|
||||
const raw = text.slice(el.startTagEnd, el.closeTagStart);
|
||||
|
||||
+11
-6
@@ -59,12 +59,15 @@ export class Ra3HoverProvider implements vscode.HoverProvider {
|
||||
// Element name.
|
||||
const nameStart = el.start + 1;
|
||||
if (offset >= nameStart && offset <= nameStart + el.name.length) {
|
||||
return this.elementHover(el.name);
|
||||
return this.elementHover(el.name, elType);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private elementHover(name: string): vscode.Hover | null {
|
||||
private elementHover(
|
||||
name: string,
|
||||
resolvedType: string | null = null,
|
||||
): vscode.Hover | null {
|
||||
if (name.startsWith("xi:")) {
|
||||
const md = new vscode.MarkdownString();
|
||||
md.appendCodeblock(`<${name}>`, "xml");
|
||||
@@ -75,7 +78,9 @@ export class Ra3HoverProvider implements vscode.HoverProvider {
|
||||
);
|
||||
return new vscode.Hover(md);
|
||||
}
|
||||
const type = model.elementTypeName(name);
|
||||
const type =
|
||||
resolvedType ??
|
||||
(model.topLevelElementType(name) ?? model.elementTypeName(name));
|
||||
const info = type ? model.typeInfo(type) : undefined;
|
||||
const md = new vscode.MarkdownString();
|
||||
md.appendCodeblock(`<${name}>`, "xml");
|
||||
@@ -87,7 +92,7 @@ export class Ra3HoverProvider implements vscode.HoverProvider {
|
||||
md.appendMarkdown(
|
||||
`${t(
|
||||
"Attributes: {0} · Children: {1}",
|
||||
info.attributes.length,
|
||||
model.attributesOfType(type).length,
|
||||
info.children.length,
|
||||
)} \n`,
|
||||
);
|
||||
@@ -267,8 +272,8 @@ export class Ra3HoverProvider implements vscode.HoverProvider {
|
||||
}
|
||||
const targets = resolveContentReferenceTargets(idx, elType, value);
|
||||
if (targets.length) return this.definitionsHover(targets, document);
|
||||
const info = elType ? model.typeInfo(elType) : undefined;
|
||||
const refType = info?.kind === "simple" ? info.refType : null;
|
||||
const info = elType ? model.contentInfoOfType(elType) : undefined;
|
||||
const refType = info?.refType ?? null;
|
||||
return this.noDefinitionHover(refType ? "typed" : "untyped", refType ?? undefined);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
textContentTokenAt,
|
||||
} from "../language/xmlParser";
|
||||
import { resolveElementType } from "../language/typeContext";
|
||||
import { attributesOfType, typeInfo } from "../model/schemaModel";
|
||||
import { attributesOfType, contentInfoOfType } from "../model/schemaModel";
|
||||
import {
|
||||
filterAndScoreDefs,
|
||||
isReferenceAttributeOfType,
|
||||
@@ -76,10 +76,10 @@ export function referenceContextAt(
|
||||
if (elType && isReferenceContentType(elType)) {
|
||||
const token = textContentTokenAt(text, el, offset);
|
||||
if (token && !token.value.startsWith("$") && !token.value.startsWith("=")) {
|
||||
const info = typeInfo(elType);
|
||||
const info = contentInfoOfType(elType);
|
||||
return {
|
||||
id: token.value,
|
||||
refType: info?.kind === "simple" ? info.refType : null,
|
||||
refType: info?.refType ?? null,
|
||||
selfType: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
import type { LineMap, XmlDocument } from "../language/xmlParser";
|
||||
import type { ShallowDocument } from "./shallowScan";
|
||||
import { attributesOfType, typeInfo } from "../model/schemaModel";
|
||||
import { attributesOfType, contentInfoOfType } from "../model/schemaModel";
|
||||
import { resolveElementType } from "../language/typeContext";
|
||||
import {
|
||||
isReferenceAttributeOfType,
|
||||
@@ -246,10 +246,10 @@ function collectReferenceRecords(
|
||||
continue;
|
||||
}
|
||||
const start = el.startTagEnd + raw.indexOf(value);
|
||||
const info = typeInfo(elType);
|
||||
const info = contentInfoOfType(elType);
|
||||
out.push({
|
||||
kind: "content",
|
||||
refType: info?.kind === "simple" ? info.refType : null,
|
||||
refType: info?.refType ?? null,
|
||||
selfType: null,
|
||||
value,
|
||||
line: lineOf(lineMap, start),
|
||||
|
||||
+33
-11
@@ -2,7 +2,9 @@ import {
|
||||
allTypeNames,
|
||||
attributesOfType,
|
||||
canonicalTypeName,
|
||||
contentInfoOfType,
|
||||
elementTypeName,
|
||||
isAssetType,
|
||||
isAssignableTo,
|
||||
typeChain,
|
||||
typeInfo,
|
||||
@@ -74,7 +76,7 @@ export function isReferenceAttributeOfType(
|
||||
typeName: string | null,
|
||||
attrName: string,
|
||||
): boolean {
|
||||
if (attrName.toLowerCase() === "inheritfrom") return true;
|
||||
if (attrName.toLowerCase() === "inheritfrom") return isAssetType(typeName);
|
||||
const attr = attributesOfType(typeName).find((a) => a.name === attrName);
|
||||
if (attr == null || !(attr.refType != null || attr.isRef)) return false;
|
||||
// Definitions (id) and pipeline-local references (Poid) are not references
|
||||
@@ -125,6 +127,7 @@ export function resolveReferenceTargetsForType(
|
||||
let selfType: string | null = null;
|
||||
|
||||
if (nameLower === "inheritfrom") {
|
||||
if (!isAssetType(typeName)) return [];
|
||||
selfType = typeName;
|
||||
} else {
|
||||
const attr = attributesOfType(typeName).find((a) => a.name === attrName);
|
||||
@@ -141,7 +144,9 @@ export function resolveReferenceTargetsForType(
|
||||
/**
|
||||
* True when an element's text content is a typed reference to a global
|
||||
* asset: the element's resolved XSD type is a simple type carrying an
|
||||
* `xas:refType` (e.g. `<CreateObject>` with `GameObjectWeakRef`).
|
||||
* `xas:refType`, or a simpleContent complex type carrying `xas:refType`
|
||||
* (e.g. `<CreateObject>` with `GameObjectWeakRef`, `<Sound>` with
|
||||
* `AudioFileRefWithWeight`).
|
||||
*
|
||||
* Only *typed* refs are treated as content references. Generic untyped
|
||||
* `AssetReference` content is used by real data for shader constants,
|
||||
@@ -152,8 +157,8 @@ export function resolveReferenceTargetsForType(
|
||||
*/
|
||||
export function isReferenceContentType(typeName: string | null): boolean {
|
||||
if (!typeName) return false;
|
||||
const info = typeInfo(typeName);
|
||||
if (info?.kind !== "simple") return false;
|
||||
const info = contentInfoOfType(typeName);
|
||||
if (!info) return false;
|
||||
if (typeChain(typeName).includes("Poid")) return false;
|
||||
return info.refType != null;
|
||||
}
|
||||
@@ -175,8 +180,8 @@ export function resolveContentReferenceTargets(
|
||||
idx.assetsById.get(id.toLowerCase()),
|
||||
);
|
||||
if (!defs.length) return [];
|
||||
const info = typeInfo(typeName);
|
||||
const refType = info?.kind === "simple" ? info.refType : null;
|
||||
const info = contentInfoOfType(typeName);
|
||||
const refType = info?.refType ?? null;
|
||||
return filterAndScoreDefs(defs, refType, null);
|
||||
}
|
||||
|
||||
@@ -223,13 +228,29 @@ export function mergeLocalAndGlobalDefs(
|
||||
|
||||
let referenceTargetTypeSet: Set<string> | null = null;
|
||||
|
||||
/**
|
||||
* True when the XSD itself declares `inheritFrom` for the type. This is the
|
||||
* narrower "designed reference target" signal used by CodeLens / unreferenced
|
||||
* reports; the universal BAB `inheritFrom` attribute must not widen it to
|
||||
* every BaseAssetType descendant.
|
||||
*/
|
||||
function xsdDeclaresInheritFrom(typeName: string): boolean {
|
||||
const info = typeInfo(typeName);
|
||||
return (
|
||||
info?.kind === "complex" &&
|
||||
info.attributes.some((a) => a.name.toLowerCase() === "inheritfrom")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The set of XSD types that are "reference targets by design": at least one
|
||||
* typed reference attribute / simple-content reference points at them, or
|
||||
* they are inheritable (`inheritFrom`). Types outside this set are
|
||||
* auto-registered / structural (settings, map metadata, w3x sub-assets...),
|
||||
* so a zero reference count is their normal state and counts would only be
|
||||
* noise.
|
||||
* the XSD explicitly declares them inheritable (`inheritFrom`). The universal
|
||||
* BAB `inheritFrom` attribute on every BaseAssetType descendant is a separate
|
||||
* legality concern and intentionally does NOT widen this set. Types outside
|
||||
* this set are auto-registered / structural (settings, map metadata, w3x
|
||||
* sub-assets...), so a zero reference count is their normal state and counts
|
||||
* would only be noise.
|
||||
*/
|
||||
export function referenceTargetTypes(): ReadonlySet<string> {
|
||||
if (referenceTargetTypeSet) return referenceTargetTypeSet;
|
||||
@@ -246,7 +267,8 @@ export function referenceTargetTypes(): ReadonlySet<string> {
|
||||
if (isLocalReferenceAttribute(typeName, attr.name)) continue;
|
||||
if (attr.refType) add(attr.refType);
|
||||
}
|
||||
if (info.attributes.some((a) => a.name.toLowerCase() === "inheritfrom")) {
|
||||
if (info.content?.refType) add(info.content.refType);
|
||||
if (xsdDeclaresInheritFrom(typeName)) {
|
||||
add(typeName);
|
||||
}
|
||||
} else if (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { childTypeOf, elementTypeName } from "../model/schemaModel";
|
||||
import { childTypeOf, elementTypeName, topLevelElementType } from "../model/schemaModel";
|
||||
import type { XmlElement } from "./xmlParser";
|
||||
|
||||
/**
|
||||
@@ -9,7 +9,12 @@ import type { XmlElement } from "./xmlParser";
|
||||
*/
|
||||
export function resolveElementType(el: XmlElement): string | null {
|
||||
if (!el.parent) {
|
||||
return elementTypeName(el.name);
|
||||
// A document root (fragment or full AssetDeclaration) has no parent to
|
||||
// provide context. When the root is a top-level asset whose name also
|
||||
// appears as a nested child type (EvaEvent, UpgradeTemplate, ...),
|
||||
// prefer the AssetDeclaration declaration over the global single-map
|
||||
// fallback.
|
||||
return topLevelElementType(el.name) ?? elementTypeName(el.name);
|
||||
}
|
||||
const parentType = resolveElementType(el.parent);
|
||||
return childTypeOf(parentType, el.name) ?? elementTypeName(el.name);
|
||||
|
||||
File diff suppressed because one or more lines are too long
+125
-1
@@ -33,6 +33,21 @@ export interface ComplexTypeInfo {
|
||||
attributes: AttributeInfo[];
|
||||
base: string | null;
|
||||
doc: string;
|
||||
/**
|
||||
* Present only for complexType + simpleContent types (e.g.
|
||||
* AudioFileRefWithWeight / MultisoundSubsoundRef). Describes the text
|
||||
* between the tags just like a simple type's value semantics.
|
||||
*/
|
||||
content?: SimpleContentInfo | null;
|
||||
}
|
||||
|
||||
export interface SimpleContentInfo {
|
||||
refType: string | null;
|
||||
isRef: boolean;
|
||||
enumValues: string[];
|
||||
isList: boolean;
|
||||
allowsDefine: boolean;
|
||||
base: string | null;
|
||||
}
|
||||
|
||||
export interface SimpleTypeInfo {
|
||||
@@ -48,6 +63,24 @@ export interface SimpleTypeInfo {
|
||||
|
||||
export type TypeInfo = ComplexTypeInfo | SimpleTypeInfo;
|
||||
|
||||
/**
|
||||
* Unified value semantics for element text content. Both simple types
|
||||
* (`<CreateObject>` -> GameObjectWeakRef) and simpleContent complex types
|
||||
* (`<Sound>` -> AudioFileRefWithWeight) share this shape so the completion /
|
||||
* hover / navigation / diagnostics / indexer pipelines do not have to know
|
||||
* which XSD construct produced the content.
|
||||
*/
|
||||
export interface ContentTypeInfo {
|
||||
kind: "simple" | "simpleContent";
|
||||
refType: string | null;
|
||||
isRef: boolean;
|
||||
enumValues: string[];
|
||||
isList: boolean;
|
||||
allowsDefine: boolean;
|
||||
base: string | null;
|
||||
doc: string;
|
||||
}
|
||||
|
||||
interface RawModel {
|
||||
version: number;
|
||||
rootXsd: string;
|
||||
@@ -59,6 +92,33 @@ interface RawModel {
|
||||
|
||||
const model = schemaModel as unknown as RawModel;
|
||||
|
||||
/**
|
||||
* `inheritFrom` is accepted by BAB / real RA3 data on BaseAssetType-derived
|
||||
* assets even though the XSD only declares it on BaseInheritableAsset
|
||||
* (vanilla SageXml uses it on FXList, AIMicroManagerData,
|
||||
* AITargetingHeuristic, ObjectCreationList, ...). It is therefore exposed as
|
||||
* a universal attribute for every asset type.
|
||||
*
|
||||
* This is deliberately separate from `referenceTargetTypes()` in refs.ts:
|
||||
* "may legally appear in the document" and "is a designed CodeLens / FAR
|
||||
* reference target" are different decisions.
|
||||
*/
|
||||
const UNIVERSAL_INHERIT_FROM: AttributeInfo = {
|
||||
name: "inheritFrom",
|
||||
required: false,
|
||||
default: null,
|
||||
doc: "Inherits another asset of the same type.",
|
||||
kind: "simple",
|
||||
type: "@attr:inheritFrom",
|
||||
refType: null,
|
||||
enumValues: [],
|
||||
isList: false,
|
||||
allowsDefine: false,
|
||||
isRef: false,
|
||||
isBoolean: false,
|
||||
base: "string",
|
||||
};
|
||||
|
||||
/** Lowercase type name -> canonical (XSD) type name. */
|
||||
const typeNameIndex = new Map<string, string>();
|
||||
for (const name of Object.keys(model.types)) {
|
||||
@@ -109,6 +169,43 @@ export function typeInfo(name: string): TypeInfo | undefined {
|
||||
return model.types[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns content-value semantics for a type, or null when the element is a
|
||||
* normal complex element (children, not text).
|
||||
*/
|
||||
export function contentInfoOfType(
|
||||
typeName: string | null,
|
||||
): ContentTypeInfo | null {
|
||||
if (!typeName) return null;
|
||||
const info = model.types[canonicalTypeName(typeName) ?? typeName];
|
||||
if (!info) return null;
|
||||
if (info.kind === "simple") {
|
||||
return {
|
||||
kind: "simple",
|
||||
refType: info.refType,
|
||||
isRef: info.isRef,
|
||||
enumValues: info.enumValues,
|
||||
isList: info.isList,
|
||||
allowsDefine: info.allowsDefine,
|
||||
base: info.base,
|
||||
doc: info.doc,
|
||||
};
|
||||
}
|
||||
if (info.kind === "complex" && info.content) {
|
||||
return {
|
||||
kind: "simpleContent",
|
||||
refType: info.content.refType,
|
||||
isRef: info.content.isRef,
|
||||
enumValues: info.content.enumValues,
|
||||
isList: info.content.isList,
|
||||
allowsDefine: info.content.allowsDefine,
|
||||
base: info.content.base,
|
||||
doc: info.doc,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function elementTypeName(name: string): string | null {
|
||||
const t = elementToType.get(name);
|
||||
return t ? t : null;
|
||||
@@ -135,7 +232,23 @@ export function attributesOfElement(name: string): AttributeInfo[] {
|
||||
export function attributesOfType(typeName: string | null): AttributeInfo[] {
|
||||
if (!typeName) return [];
|
||||
const info = model.types[canonicalTypeName(typeName) ?? typeName];
|
||||
return info && info.kind === "complex" ? info.attributes : [];
|
||||
if (!info || info.kind !== "complex") return [];
|
||||
if (
|
||||
isAssetType(typeName) &&
|
||||
!info.attributes.some((a) => a.name === "inheritFrom")
|
||||
) {
|
||||
return [...info.attributes, UNIVERSAL_INHERIT_FROM];
|
||||
}
|
||||
return info.attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* True for types in the asset hierarchy (BaseAssetType and its descendants).
|
||||
* These are the types on which BAB accepts the universal `inheritFrom`
|
||||
* attribute even when the XSD does not declare it.
|
||||
*/
|
||||
export function isAssetType(typeName: string | null): boolean {
|
||||
return !!typeName && typeChain(typeName).includes("BaseAssetType");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,6 +283,17 @@ export function elementTypeIn(
|
||||
return elementTypeName(childName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a top-level asset element name to the type declared inside
|
||||
* AssetDeclaration. This is the type a fragment/standalone document root
|
||||
* should use when its name collides with a nested child type (e.g. EvaEvent
|
||||
* is both a top-level asset and an FXNugget child).
|
||||
*/
|
||||
export function topLevelElementType(name: string): string | null {
|
||||
const declType = elementTypeName("AssetDeclaration");
|
||||
return declType ? childTypeOf(declType, name) : null;
|
||||
}
|
||||
|
||||
export function typeDoc(name: string): string {
|
||||
const info = model.types[name];
|
||||
return info?.doc ?? "";
|
||||
|
||||
Reference in New Issue
Block a user