@defold-typescript/types 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/regen.ts +6 -6
- package/scripts/signature-store-fs.ts +2 -0
- package/src/api-doc.ts +58 -0
- package/src/core-types.ts +3 -0
- package/src/doc-comment.ts +21 -3
- package/src/emit-dts.ts +14 -1
- package/src/index.ts +6 -1
- package/src/library-signature.ts +11 -3
- package/src/publish-dts.ts +32 -4
- package/src/script-api.ts +36 -10
package/package.json
CHANGED
package/scripts/regen.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
parseMessagesDoc,
|
|
10
10
|
} from "../src/emit-messages";
|
|
11
11
|
import type { TranslationStore } from "../src/example-store";
|
|
12
|
-
import { wrapAsAmbientGlobal } from "../src/publish-dts";
|
|
12
|
+
import { wrapAsAmbientGlobal, wrapAsModule } from "../src/publish-dts";
|
|
13
13
|
import {
|
|
14
14
|
type DocSourceProvenance,
|
|
15
15
|
type DownloadRefDoc,
|
|
@@ -90,6 +90,7 @@ export interface ModuleManifestEntry {
|
|
|
90
90
|
readonly outFile: string;
|
|
91
91
|
readonly skipFunctions?: readonly string[];
|
|
92
92
|
readonly importsFrom?: string;
|
|
93
|
+
readonly moduleId?: string;
|
|
93
94
|
readonly sourceProvenance?: DocSourceProvenance;
|
|
94
95
|
}
|
|
95
96
|
|
|
@@ -239,11 +240,10 @@ export function generateModuleDeclaration(
|
|
|
239
240
|
options,
|
|
240
241
|
);
|
|
241
242
|
const emitted = emitDeclarations(module, { knownConstantFqns, translations });
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
emitted,
|
|
245
|
-
|
|
246
|
-
});
|
|
243
|
+
const importsFrom = entry.importsFrom ?? "../src/core-types";
|
|
244
|
+
const contents = entry.moduleId
|
|
245
|
+
? wrapAsModule({ namespace: module.namespace, emitted, importsFrom, moduleId: entry.moduleId })
|
|
246
|
+
: wrapAsAmbientGlobal({ namespace: module.namespace, emitted, importsFrom });
|
|
247
247
|
return { contents, dropped };
|
|
248
248
|
}
|
|
249
249
|
|
|
@@ -22,6 +22,8 @@ export const PACKAGE_SIGNATURES_PATH = resolve(import.meta.dir, "..", "signature
|
|
|
22
22
|
export const BASE_SIGNATURES_PATH = resolve(import.meta.dir, "..", "signatures", "base.json");
|
|
23
23
|
export const SOCKET_SIGNATURES_PATH = resolve(import.meta.dir, "..", "signatures", "socket.json");
|
|
24
24
|
export const VMATH_SIGNATURES_PATH = resolve(import.meta.dir, "..", "signatures", "vmath.json");
|
|
25
|
+
export const GO_SIGNATURES_PATH = resolve(import.meta.dir, "..", "signatures", "go.json");
|
|
26
|
+
export const MSG_SIGNATURES_PATH = resolve(import.meta.dir, "..", "signatures", "msg.json");
|
|
25
27
|
|
|
26
28
|
export function loadSignatureFile(path: string): SignatureStore {
|
|
27
29
|
let raw: string;
|
package/src/api-doc.ts
CHANGED
|
@@ -20,12 +20,16 @@ export interface ApiTypedef {
|
|
|
20
20
|
name: string;
|
|
21
21
|
functions?: ApiFunction[];
|
|
22
22
|
properties?: ApiVariable[];
|
|
23
|
+
/** See {@link ApiFunction.global}. */
|
|
24
|
+
global?: true;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
export interface ApiConstant {
|
|
26
28
|
name: string;
|
|
27
29
|
brief: string;
|
|
28
30
|
description: string;
|
|
31
|
+
/** See {@link ApiFunction.global}. */
|
|
32
|
+
global?: true;
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
export interface ApiFunction {
|
|
@@ -41,6 +45,27 @@ export interface ApiFunction {
|
|
|
41
45
|
* library functions; engine ref-docs carry no `generics`, so it stays absent.
|
|
42
46
|
*/
|
|
43
47
|
generics?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Present exactly when the source carried a `@deprecated` tag; `""` for a bare
|
|
50
|
+
* tag. Absence is the only encoding of "not deprecated", so a bare tag stays
|
|
51
|
+
* distinguishable from an untagged symbol.
|
|
52
|
+
*/
|
|
53
|
+
deprecated?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Present exactly when the source declared the symbol as an ambient global —
|
|
56
|
+
* outside the library's `declare module` block — so it is reachable without
|
|
57
|
+
* the module import. Absence is the only encoding of "module member"; the key
|
|
58
|
+
* is never written as `false`.
|
|
59
|
+
*/
|
|
60
|
+
global?: true;
|
|
61
|
+
/**
|
|
62
|
+
* Present exactly when this symbol's prose was imported from the upstream
|
|
63
|
+
* source rather than written in the declaration — the authored/forked library
|
|
64
|
+
* lane lowers upstream's own LuaDoc summary for a member its fork documents
|
|
65
|
+
* nowhere. Absence is the only encoding of first-party prose, so every engine
|
|
66
|
+
* and hand-authored symbol reads as before.
|
|
67
|
+
*/
|
|
68
|
+
docSource?: "upstream";
|
|
44
69
|
}
|
|
45
70
|
|
|
46
71
|
export interface ApiParameter {
|
|
@@ -67,6 +92,30 @@ export interface ApiVariable {
|
|
|
67
92
|
brief: string;
|
|
68
93
|
description: string;
|
|
69
94
|
types: string[];
|
|
95
|
+
/**
|
|
96
|
+
* True for an optional member of a typedef shape (`clear?: boolean`). Set only
|
|
97
|
+
* when the element carries `is_optional: "True"`, so a module-level engine
|
|
98
|
+
* ref-doc VARIABLE — which never carries the key — leaves it absent.
|
|
99
|
+
*/
|
|
100
|
+
isOptional?: boolean;
|
|
101
|
+
/** See {@link ApiFunction.deprecated}. */
|
|
102
|
+
deprecated?: string;
|
|
103
|
+
/** See {@link ApiFunction.global}. */
|
|
104
|
+
global?: true;
|
|
105
|
+
/** See {@link ApiFunction.docSource}. */
|
|
106
|
+
docSource?: "upstream";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The `{ global }` key to spread onto a parsed element, empty for a module member. */
|
|
110
|
+
function globalKey(element: Record<string, unknown>): { global?: true } {
|
|
111
|
+
return element.global === true ? { global: true } : {};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** The `{ docSource }` key to spread onto a parsed element, empty for first-party
|
|
115
|
+
* prose. Only the one recognised value yields a key: an unknown provenance would
|
|
116
|
+
* otherwise reach a page with no marker the render layer knows how to draw. */
|
|
117
|
+
function docSourceKey(element: Record<string, unknown>): { docSource?: "upstream" } {
|
|
118
|
+
return element.docSource === "upstream" ? { docSource: "upstream" } : {};
|
|
70
119
|
}
|
|
71
120
|
|
|
72
121
|
export function parseDefoldApiDoc(input: unknown): ApiModule {
|
|
@@ -119,6 +168,7 @@ function parseTypedef(element: Record<string, unknown>): ApiTypedef {
|
|
|
119
168
|
name: stringOr(element.name, ""),
|
|
120
169
|
...(functions.length > 0 ? { functions } : {}),
|
|
121
170
|
...(properties.length > 0 ? { properties } : {}),
|
|
171
|
+
...globalKey(element),
|
|
122
172
|
};
|
|
123
173
|
}
|
|
124
174
|
|
|
@@ -145,6 +195,7 @@ function parseConstant(element: Record<string, unknown>): ApiConstant {
|
|
|
145
195
|
name: stringOr(element.name, ""),
|
|
146
196
|
brief: stringOr(element.brief, ""),
|
|
147
197
|
description: stringOr(element.description, ""),
|
|
198
|
+
...globalKey(element),
|
|
148
199
|
};
|
|
149
200
|
}
|
|
150
201
|
|
|
@@ -157,6 +208,9 @@ function parseFunction(element: Record<string, unknown>): ApiFunction {
|
|
|
157
208
|
returnValues: parseParameterList(element.returnvalues),
|
|
158
209
|
examples: stringOr(element.examples, ""),
|
|
159
210
|
...(typeof element.generics === "string" ? { generics: element.generics } : {}),
|
|
211
|
+
...(typeof element.deprecated === "string" ? { deprecated: element.deprecated } : {}),
|
|
212
|
+
...globalKey(element),
|
|
213
|
+
...docSourceKey(element),
|
|
160
214
|
};
|
|
161
215
|
}
|
|
162
216
|
|
|
@@ -166,6 +220,10 @@ function parseVariable(element: Record<string, unknown>): ApiVariable {
|
|
|
166
220
|
brief: stringOr(element.brief, ""),
|
|
167
221
|
description: stringOr(element.description, ""),
|
|
168
222
|
types: parseStringArray(element.types),
|
|
223
|
+
...(element.is_optional === "True" ? { isOptional: true } : {}),
|
|
224
|
+
...(typeof element.deprecated === "string" ? { deprecated: element.deprecated } : {}),
|
|
225
|
+
...globalKey(element),
|
|
226
|
+
...docSourceKey(element),
|
|
169
227
|
};
|
|
170
228
|
}
|
|
171
229
|
|
package/src/core-types.ts
CHANGED
|
@@ -188,6 +188,9 @@ export const DEFOLD_TYPE_MAP: Readonly<Record<string, string>> = {
|
|
|
188
188
|
vector4: "Vector4",
|
|
189
189
|
quaternion: "Quaternion",
|
|
190
190
|
matrix4: "Matrix4",
|
|
191
|
+
// Authored-README shorthand for `vmath.matrix4`; absent from every engine
|
|
192
|
+
// ref-doc, which a core-types.test.ts guard keeps true as releases import.
|
|
193
|
+
matrix: "Matrix4",
|
|
191
194
|
"vmath.vector3": "Vector3",
|
|
192
195
|
"vmath.vector4": "Vector4",
|
|
193
196
|
"vmath.matrix4": "Matrix4",
|
package/src/doc-comment.ts
CHANGED
|
@@ -107,6 +107,10 @@ export function examplesHtmlToMarkdown(html: string): string {
|
|
|
107
107
|
|
|
108
108
|
export interface DocCommentParts {
|
|
109
109
|
summary: string;
|
|
110
|
+
// Present exactly when the source carried a deprecation tag; `""` is the bare
|
|
111
|
+
// form and still renders, so this is tested against `undefined` rather than for
|
|
112
|
+
// truthiness the way the other optional parts are.
|
|
113
|
+
deprecated?: string;
|
|
110
114
|
params?: { name: string; doc: string }[];
|
|
111
115
|
returns?: string;
|
|
112
116
|
example?: string;
|
|
@@ -122,8 +126,15 @@ export function renderDocComment(parts: DocCommentParts): string[] {
|
|
|
122
126
|
const params = (parts.params ?? []).filter((p) => p.doc.trim() !== "");
|
|
123
127
|
const returns = parts.returns?.trim() ? parts.returns : "";
|
|
124
128
|
const example = parts.example?.trim() ? parts.example : "";
|
|
125
|
-
|
|
126
|
-
|
|
129
|
+
const deprecated = parts.deprecated;
|
|
130
|
+
|
|
131
|
+
if (
|
|
132
|
+
summaryLines.length === 0 &&
|
|
133
|
+
params.length === 0 &&
|
|
134
|
+
returns === "" &&
|
|
135
|
+
example === "" &&
|
|
136
|
+
deprecated === undefined
|
|
137
|
+
) {
|
|
127
138
|
return [];
|
|
128
139
|
}
|
|
129
140
|
|
|
@@ -132,11 +143,18 @@ export function renderDocComment(parts: DocCommentParts): string[] {
|
|
|
132
143
|
lines.push(line === "" ? " *" : ` * ${line}`);
|
|
133
144
|
}
|
|
134
145
|
|
|
135
|
-
const hasTags = params.length > 0 || returns !== "" || example !== "";
|
|
146
|
+
const hasTags = deprecated !== undefined || params.length > 0 || returns !== "" || example !== "";
|
|
136
147
|
if (summaryLines.length > 0 && hasTags) {
|
|
137
148
|
lines.push(" *");
|
|
138
149
|
}
|
|
139
150
|
|
|
151
|
+
if (deprecated !== undefined) {
|
|
152
|
+
const [first, ...rest] = deprecated.split("\n");
|
|
153
|
+
lines.push(first === "" ? " * @deprecated" : ` * @deprecated ${first}`);
|
|
154
|
+
for (const line of rest) {
|
|
155
|
+
lines.push(line === "" ? " *" : ` * ${line}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
140
158
|
for (const param of params) {
|
|
141
159
|
const [first, ...rest] = param.doc.split("\n");
|
|
142
160
|
lines.push(` * @param ${param.name} - ${first}`);
|
package/src/emit-dts.ts
CHANGED
|
@@ -1565,12 +1565,25 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
|
|
|
1565
1565
|
const nestedIndent = `${INDENT}${INDENT}`;
|
|
1566
1566
|
for (const segment of nestedSegments) {
|
|
1567
1567
|
const group = nestedGroups.get(segment) ?? [];
|
|
1568
|
+
// A reserved-name function inside a nested namespace gets the same recovery as
|
|
1569
|
+
// a top-level one: emitted un-exported as `_<name>` and re-exported under the
|
|
1570
|
+
// reserved name. The alias switches this namespace out of implicit-export mode,
|
|
1571
|
+
// so its siblings then need an explicit `export` to stay reachable.
|
|
1572
|
+
const segmentAliases: { internal: string; public: string }[] = [];
|
|
1573
|
+
const segmentDecl = group.some((fn) => TS_RESERVED_NAMES.has(fn.name)) ? "export " : "";
|
|
1568
1574
|
lines.push(`${INDENT}${decl}namespace ${segment} {`);
|
|
1569
1575
|
for (const fn of group) {
|
|
1576
|
+
const reserved = TS_RESERVED_NAMES.has(fn.name);
|
|
1577
|
+
const emitName = aliasName(fn.name, segmentAliases);
|
|
1570
1578
|
for (const docLine of functionDocLines(fn.original, translations, nestedIndent)) {
|
|
1571
1579
|
lines.push(docLine);
|
|
1572
1580
|
}
|
|
1573
|
-
lines.push(
|
|
1581
|
+
lines.push(
|
|
1582
|
+
`${nestedIndent}${reserved ? "" : segmentDecl}${emitFunction(fn, emitName, mapType, resolver)}`,
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1585
|
+
for (const alias of [...segmentAliases].sort((a, b) => a.public.localeCompare(b.public))) {
|
|
1586
|
+
lines.push(`${nestedIndent}export { ${alias.internal} as ${alias.public} };`);
|
|
1574
1587
|
}
|
|
1575
1588
|
lines.push(`${INDENT}}`);
|
|
1576
1589
|
}
|
package/src/index.ts
CHANGED
|
@@ -80,7 +80,12 @@ export {
|
|
|
80
80
|
type ScriptPropertiesOf,
|
|
81
81
|
type ScriptProperty,
|
|
82
82
|
} from "./lifecycle";
|
|
83
|
-
export {
|
|
83
|
+
export {
|
|
84
|
+
type ModuleWrapOptions,
|
|
85
|
+
type WrapOptions,
|
|
86
|
+
wrapAsAmbientGlobal,
|
|
87
|
+
wrapAsModule,
|
|
88
|
+
} from "./publish-dts";
|
|
84
89
|
export {
|
|
85
90
|
lookupSignature,
|
|
86
91
|
type SignatureOverride,
|
package/src/library-signature.ts
CHANGED
|
@@ -29,7 +29,15 @@ export function varargElementType(mapped: string): string {
|
|
|
29
29
|
return needsArrayParens(mapped) ? `(${mapped})[]` : `${mapped}[]`;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Wrap `>1` mapped return tokens in the `LuaMultiReturn<[...]>` tuple form. With
|
|
34
|
+
* `restTail`, the last element renders as a rest element — the shape a LuaLS
|
|
35
|
+
* multi-return whose final value is a bare vararg (`fun(): T, ...`) calls for.
|
|
36
|
+
*/
|
|
37
|
+
export function luaMultiReturn(mapped: readonly string[], restTail = false): string {
|
|
38
|
+
const elements =
|
|
39
|
+
restTail && mapped.length > 0
|
|
40
|
+
? [...mapped.slice(0, -1), `...${varargElementType(mapped[mapped.length - 1] as string)}`]
|
|
41
|
+
: mapped;
|
|
42
|
+
return `LuaMultiReturn<[${elements.join(", ")}]>`;
|
|
35
43
|
}
|
package/src/publish-dts.ts
CHANGED
|
@@ -16,18 +16,46 @@ export interface WrapOptions {
|
|
|
16
16
|
importsFrom: string;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export
|
|
20
|
-
|
|
19
|
+
export interface ModuleWrapOptions extends WrapOptions {
|
|
20
|
+
moduleId: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// The engine-type import line plus the emitted body with its leading
|
|
24
|
+
// `declare namespace` rewritten to `topKeyword` and every line indented two
|
|
25
|
+
// spaces — the shared shape both the ambient-global and importable-module wraps
|
|
26
|
+
// place inside their envelope.
|
|
27
|
+
function prepareWrapBody(
|
|
28
|
+
emitted: string,
|
|
29
|
+
importsFrom: string,
|
|
30
|
+
topKeyword: string,
|
|
31
|
+
): { importLine: string; indented: string } {
|
|
32
|
+
const used = collectEngineTypes(emitted);
|
|
21
33
|
const importLine =
|
|
22
|
-
used.length === 0 ? "" : `import type { ${used.join(", ")} } from "${
|
|
23
|
-
const inner =
|
|
34
|
+
used.length === 0 ? "" : `import type { ${used.join(", ")} } from "${importsFrom}";\n\n`;
|
|
35
|
+
const inner = emitted.replace(/(^|\n)declare\s+namespace\s+/, `$1${topKeyword} `).trimEnd();
|
|
24
36
|
const indented = inner
|
|
25
37
|
.split("\n")
|
|
26
38
|
.map((l) => (l.length === 0 ? l : ` ${l}`))
|
|
27
39
|
.join("\n");
|
|
40
|
+
return { importLine, indented };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function wrapAsAmbientGlobal(opts: WrapOptions): string {
|
|
44
|
+
const { importLine, indented } = prepareWrapBody(opts.emitted, opts.importsFrom, "namespace");
|
|
28
45
|
return `/** @noSelfInFile */\n${importLine}declare global {\n${indented}\n}\n\nexport {};\n`;
|
|
29
46
|
}
|
|
30
47
|
|
|
48
|
+
export function wrapAsModule(opts: ModuleWrapOptions): string {
|
|
49
|
+
// ambient-globals-only: the module form references engine handles (`Hash`,
|
|
50
|
+
// `Vector3`, ...) as ambient globals and emits no top-level import. A top-level
|
|
51
|
+
// `import type` would make the `.d.ts` a module, demoting `declare module
|
|
52
|
+
// '<id>'` to an augmentation of an unresolvable specifier that a consumer
|
|
53
|
+
// `import` cannot resolve (`TS2307`). `importsFrom` stays in the signature for
|
|
54
|
+
// caller symmetry but is unused here; `wrapAsAmbientGlobal` still imports it.
|
|
55
|
+
const { indented } = prepareWrapBody(opts.emitted, opts.importsFrom, "export namespace");
|
|
56
|
+
return `/** @noSelfInFile */\n/** @noResolution */\ndeclare module '${opts.moduleId}' {\n${indented}\n}\n`;
|
|
57
|
+
}
|
|
58
|
+
|
|
31
59
|
function collectEngineTypes(emitted: string): EngineType[] {
|
|
32
60
|
return ENGINE_TYPES.filter((t) => new RegExp(`\\b${t}\\b`).test(emitted));
|
|
33
61
|
}
|
package/src/script-api.ts
CHANGED
|
@@ -27,6 +27,17 @@ function stringOr(value: unknown, fallback: string): string {
|
|
|
27
27
|
return typeof value === "string" ? value : fallback;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
// A `.script_api` `type:` may spell a union inline (`string | nil`), where the
|
|
31
|
+
// core ref-doc format carries one token per alternative. Splitting here keeps the
|
|
32
|
+
// downstream emitter and fidelity resolver working in single tokens.
|
|
33
|
+
function splitTypeTokens(type: unknown): string[] {
|
|
34
|
+
if (typeof type !== "string") return [];
|
|
35
|
+
return type
|
|
36
|
+
.split("|")
|
|
37
|
+
.map((token) => token.trim())
|
|
38
|
+
.filter((token) => token.length > 0);
|
|
39
|
+
}
|
|
40
|
+
|
|
30
41
|
function mapParameters(raw: unknown): RefDocParameter[] {
|
|
31
42
|
if (!Array.isArray(raw)) return [];
|
|
32
43
|
const out: RefDocParameter[] = [];
|
|
@@ -36,11 +47,10 @@ function mapParameters(raw: unknown): RefDocParameter[] {
|
|
|
36
47
|
// The script_api lists the implicit `self` the engine passes; the emitter
|
|
37
48
|
// stamps @noSelfInFile, so generated signatures must not declare it.
|
|
38
49
|
if (name === "self") continue;
|
|
39
|
-
const type = item.type;
|
|
40
50
|
out.push({
|
|
41
51
|
name,
|
|
42
52
|
doc: stringOr(item.desc, ""),
|
|
43
|
-
types:
|
|
53
|
+
types: splitTypeTokens(item.type),
|
|
44
54
|
});
|
|
45
55
|
}
|
|
46
56
|
return out;
|
|
@@ -73,15 +83,31 @@ export function scriptApiToRefDoc(parsed: unknown): RefDoc {
|
|
|
73
83
|
}
|
|
74
84
|
const members = Array.isArray(table.members) ? table.members : [];
|
|
75
85
|
const elements: RefDocElement[] = [];
|
|
86
|
+
const fnElement = (name: string, member: Record<string, unknown>): RefDocElement => ({
|
|
87
|
+
type: "FUNCTION",
|
|
88
|
+
name,
|
|
89
|
+
description: stringOr(member.desc, ""),
|
|
90
|
+
parameters: mapParameters(member.parameters),
|
|
91
|
+
returnvalues: mapParameters(member.returns),
|
|
92
|
+
});
|
|
76
93
|
for (const member of members) {
|
|
77
|
-
if (!isRecord(member)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
94
|
+
if (!isRecord(member)) continue;
|
|
95
|
+
if (member.type === "function") {
|
|
96
|
+
elements.push(fnElement(`${namespace}.${stringOr(member.name, "")}`, member));
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (member.type === "table") {
|
|
100
|
+
const sub = stringOr(member.name, "");
|
|
101
|
+
// A nameless sub-namespace can't form a valid dotted name; skip it.
|
|
102
|
+
if (sub.length === 0) continue;
|
|
103
|
+
const subMembers = Array.isArray(member.members) ? member.members : [];
|
|
104
|
+
for (const subMember of subMembers) {
|
|
105
|
+
// Recurse exactly one level: a `type: table` nested here is 2nd-level,
|
|
106
|
+
// whose functions the emitter's one-dot pass would silently drop.
|
|
107
|
+
if (!isRecord(subMember) || subMember.type !== "function") continue;
|
|
108
|
+
elements.push(fnElement(`${namespace}.${sub}.${stringOr(subMember.name, "")}`, subMember));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
85
111
|
}
|
|
86
112
|
const doc: RefDoc = {
|
|
87
113
|
info: { namespace, brief: stringOr(table.desc, ""), description: stringOr(table.desc, "") },
|