@defold-typescript/library-types 0.23.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/api-doc/boom.json +3870 -0
- package/api-doc/bridge.json +10 -5
- package/api-doc/bzAnim.json +338 -0
- package/api-doc/decore.json +1 -1
- package/api-doc/defcon.json +150 -0
- package/api-doc/{defmath.defmath.json → defmath.json} +6 -46
- package/api-doc/defsave.json +518 -0
- package/api-doc/deftest.json +741 -0
- package/api-doc/{dicebag.dicebag.json → dicebag.json} +19 -1
- package/api-doc/druid.json +90 -92
- package/api-doc/event.json +16 -16
- package/api-doc/gooey.json +2050 -0
- package/api-doc/in.accelerometer.json +75 -1
- package/api-doc/in.button.json +17 -10
- package/api-doc/in.cursor.json +26 -0
- package/api-doc/in.gesture.json +401 -6
- package/api-doc/in.keyboard.json +2 -0
- package/api-doc/in.mapper.json +12 -8
- package/api-doc/in.onscreen.json +61 -5
- package/api-doc/in.state.json +61 -13
- package/api-doc/in.textbox.json +23 -12
- package/api-doc/in.triggers.json +336 -0
- package/api-doc/lang.json +5 -5
- package/api-doc/log.json +1 -1
- package/api-doc/metrics.fps.json +61 -2
- package/api-doc/metrics.mem.json +61 -2
- package/api-doc/monarch.monarch.json +249 -7
- package/api-doc/monarch.transitions.easings.json +19 -26
- package/api-doc/monarch.transitions.gui.json +782 -79
- package/api-doc/nakama.engine.defold.json +31 -18
- package/api-doc/{nakama.nakama.json → nakama.json} +4752 -2554
- package/api-doc/nakama.util.log.json +32 -4
- package/api-doc/narrator.json +1 -1
- package/api-doc/openapi/nakama.nakama.json +7680 -0
- package/api-doc/orthographic.camera.json +96 -98
- package/api-doc/orthographic.json +602 -0
- package/api-doc/panthera.json +1183 -0
- package/api-doc/{persist.persist.json → persist.json} +26 -1
- package/api-doc/{platypus.platypus.json → platypus.json} +68 -14
- package/api-doc/{rendy.rendy.json → rendy.json} +46 -99
- package/api-doc/richtext.color.json +77 -0
- package/api-doc/richtext.richtext.json +375 -5
- package/api-doc/saver.saver.json +2 -1
- package/api-doc/saver.storage.json +2 -1
- package/api-doc/{starly.starly.json → starly.json} +1 -1
- package/api-doc/{yagames.yagames.json → yagames.json} +784 -189
- package/api-doc/{zzfx.api.json → zzfx.json} +9 -9
- package/authored-targets.json +403 -0
- package/generated/{boom.boom.d.ts → boom.d.ts} +14 -0
- package/generated/bridge.d.ts +5 -5
- package/generated/{bzAnim.bzLibrary.d.ts → bzAnim.d.ts} +6 -0
- package/generated/decore.d.ts +1 -1
- package/generated/defcon.d.ts +43 -0
- package/generated/{defmath.defmath.d.ts → defmath.d.ts} +3 -2
- package/generated/defsave.d.ts +144 -0
- package/generated/{dicebag.dicebag.d.ts → dicebag.d.ts} +25 -0
- package/generated/druid.d.ts +81 -79
- package/generated/event.d.ts +15 -15
- package/generated/{gooey.gooey.d.ts → gooey.d.ts} +61 -6
- package/generated/in.onscreen.d.ts +5 -0
- package/generated/in.textbox.d.ts +4 -0
- package/generated/lang.d.ts +5 -5
- package/generated/log.d.ts +1 -1
- package/generated/metrics.fps.d.ts +19 -1
- package/generated/metrics.mem.d.ts +19 -1
- package/generated/monarch.monarch.d.ts +44 -1
- package/generated/monarch.transitions.easings.d.ts +0 -1
- package/generated/monarch.transitions.gui.d.ts +96 -12
- package/generated/nakama.d.ts +1158 -0
- package/generated/nakama.engine.defold.d.ts +3 -1
- package/generated/nakama.util.log.d.ts +6 -0
- package/generated/narrator.d.ts +1 -1
- package/generated/openapi/nakama.nakama.d.ts +884 -0
- package/generated/orthographic.camera.d.ts +24 -38
- package/generated/orthographic.d.ts +155 -0
- package/generated/panthera.d.ts +170 -0
- package/generated/{persist.persist.d.ts → persist.d.ts} +3 -0
- package/generated/{platypus.platypus.d.ts → platypus.d.ts} +9 -0
- package/generated/{rendy.rendy.d.ts → rendy.d.ts} +33 -80
- package/generated/richtext.color.d.ts +15 -0
- package/generated/saver.saver.d.ts +2 -0
- package/generated/saver.storage.d.ts +2 -0
- package/generated/{yagames.yagames.d.ts → yagames.d.ts} +151 -29
- package/generated/{zzfx.api.d.ts → zzfx.d.ts} +7 -0
- package/library-classification.json +0 -156
- package/library-targets.json +1 -218
- package/luals-targets.json +56 -3
- package/openapi-targets.json +17 -0
- package/package.json +7 -57
- package/scripts/__snapshots__/parse-luals.test.ts.snap +164 -10
- package/scripts/apply-luals-overrides.ts +39 -7
- package/scripts/authored-parity.ts +718 -0
- package/scripts/emit-library-dts.ts +94 -17
- package/scripts/extract-api-doc.ts +189 -38
- package/scripts/fidelity-floor.ts +178 -0
- package/scripts/import-upstream-docs.ts +125 -0
- package/scripts/lower-api-doc.ts +21 -3
- package/scripts/luals-fidelity.ts +16 -11
- package/scripts/map-luals-types.ts +52 -21
- package/scripts/parse-lua-surface.ts +187 -0
- package/scripts/parse-luals.ts +50 -4
- package/scripts/parse-markdown-api.ts +247 -0
- package/scripts/parse-openapi-api.ts +313 -0
- package/scripts/sync-authored-types.ts +249 -0
- package/scripts/sync-library-types.ts +99 -3
- package/scripts/sync-luals-types.ts +14 -1
- package/scripts/sync-markdown-types.ts +761 -0
- package/scripts/sync-openapi-types.ts +308 -0
- package/scripts/sync-script-api-types.ts +16 -17
- package/api-doc/boom.boom.json +0 -26
- package/api-doc/bzAnim.bzLibrary.json +0 -156
- package/api-doc/defcon.console.json +0 -109
- package/api-doc/defsave.defsave.json +0 -175
- package/api-doc/deftest.deftest.json +0 -71
- package/api-doc/gooey.gooey.json +0 -963
- package/generated/defcon.console.d.ts +0 -24
- package/generated/defsave.defsave.d.ts +0 -31
- package/generated/nakama.nakama.d.ts +0 -594
- /package/generated/{deftest.deftest.d.ts → deftest.d.ts} +0 -0
- /package/generated/{starly.starly.d.ts → starly.d.ts} +0 -0
|
@@ -15,7 +15,12 @@
|
|
|
15
15
|
* `name` is scoped into a child `MapContext` (an identity rename) so a bare `T`
|
|
16
16
|
* resolves to `T` instead of lowering to `unknown`, constraints and `extends`
|
|
17
17
|
* targets map through the existing rename map, and an `extends` clause is emitted
|
|
18
|
-
* only for parents that resolve to a declared interface.
|
|
18
|
+
* only for parents that resolve to a declared interface or an imported external type.
|
|
19
|
+
*
|
|
20
|
+
* A token owned by another target is declared through `externalTypes`: the emitter
|
|
21
|
+
* writes a real `import { <name> } from '<module>';` into the `declare module` block
|
|
22
|
+
* and resolves the token to that binding, so a cross-library reference carries its
|
|
23
|
+
* real type instead of lowering to `unknown`.
|
|
19
24
|
*/
|
|
20
25
|
|
|
21
26
|
import {
|
|
@@ -26,6 +31,7 @@ import {
|
|
|
26
31
|
varargElementType,
|
|
27
32
|
} from "@defold-typescript/types";
|
|
28
33
|
import {
|
|
34
|
+
LUALS_VARARG_TOKEN,
|
|
29
35
|
type MapContext,
|
|
30
36
|
mapLualsCallSignature,
|
|
31
37
|
mapLualsType,
|
|
@@ -42,9 +48,20 @@ import type {
|
|
|
42
48
|
LibraryParam,
|
|
43
49
|
} from "./parse-luals";
|
|
44
50
|
|
|
51
|
+
/**
|
|
52
|
+
* A type token owned by another target: the module id that declares it and the name
|
|
53
|
+
* it exports. Resolving one emits a real `import` into the `declare module` block
|
|
54
|
+
* rather than lowering the token to `unknown`.
|
|
55
|
+
*/
|
|
56
|
+
export interface ExternalTypeRef {
|
|
57
|
+
module: string;
|
|
58
|
+
name: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
45
61
|
export interface EmitLibraryOptions {
|
|
46
62
|
moduleId: string;
|
|
47
63
|
typeRenames?: Record<string, string>;
|
|
64
|
+
externalTypes?: Record<string, ExternalTypeRef> | undefined;
|
|
48
65
|
}
|
|
49
66
|
|
|
50
67
|
const INDENT = "\t";
|
|
@@ -85,6 +102,42 @@ function safeParamName(name: string, index: number): string {
|
|
|
85
102
|
return TS_RESERVED_NAMES.has(name) ? `${name}_` : name;
|
|
86
103
|
}
|
|
87
104
|
|
|
105
|
+
/**
|
|
106
|
+
* The token -> local-alias rename map an `externalTypes` config contributes, aliasing
|
|
107
|
+
* exactly as a declared name would (dotted `event.foo` -> `event_foo`). Shared by the
|
|
108
|
+
* emitter, the fidelity report, and the api-doc lowering so all three resolve an
|
|
109
|
+
* external reference to the same identifier the import binds.
|
|
110
|
+
*/
|
|
111
|
+
export function externalTypeRenames(
|
|
112
|
+
externalTypes?: Record<string, ExternalTypeRef>,
|
|
113
|
+
): Record<string, string> {
|
|
114
|
+
const renames: Record<string, string> = {};
|
|
115
|
+
for (const token of Object.keys(externalTypes ?? {})) renames[token] = sanitizeTypeName(token);
|
|
116
|
+
return renames;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* `import { ... } from '<module>';` lines for the external tokens, one per source
|
|
121
|
+
* module with its names sorted and the modules sorted by id, so re-rendering is
|
|
122
|
+
* byte-identical. A token whose local alias differs from the export name binds
|
|
123
|
+
* through an `as` clause.
|
|
124
|
+
*/
|
|
125
|
+
function renderExternalImports(externalTypes?: Record<string, ExternalTypeRef>): string[] {
|
|
126
|
+
const byModule = new Map<string, Set<string>>();
|
|
127
|
+
for (const [token, ref] of Object.entries(externalTypes ?? {})) {
|
|
128
|
+
const alias = sanitizeTypeName(token);
|
|
129
|
+
const binding = alias === ref.name ? ref.name : `${ref.name} as ${alias}`;
|
|
130
|
+
const names = byModule.get(ref.module) ?? new Set<string>();
|
|
131
|
+
names.add(binding);
|
|
132
|
+
byModule.set(ref.module, names);
|
|
133
|
+
}
|
|
134
|
+
return [...byModule.entries()]
|
|
135
|
+
.sort(([a], [b]) => (a < b ? -1 : 1))
|
|
136
|
+
.map(
|
|
137
|
+
([module, names]) => `${INDENT}import { ${[...names].sort().join(", ")} } from '${module}';`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
88
141
|
export function mapTypes(types: readonly string[], ctx: MapContext): string {
|
|
89
142
|
if (types.length === 0) return "unknown";
|
|
90
143
|
return types.map((token) => mapLualsType(token, ctx).ts).join(" | ");
|
|
@@ -107,24 +160,29 @@ export function renderGenericParams(generics: readonly LibraryGeneric[], ctx: Ma
|
|
|
107
160
|
function renderHookReturn(returnTokens: readonly string[], ctx: MapContext): string {
|
|
108
161
|
if (returnTokens.length === 0) return "void";
|
|
109
162
|
if (returnTokens.length === 1) return mapTypes([returnTokens[0] as string], ctx);
|
|
110
|
-
return luaMultiReturn(
|
|
163
|
+
return luaMultiReturn(
|
|
164
|
+
returnTokens.map((token) => mapTypes([token], ctx)),
|
|
165
|
+
returnTokens.at(-1) === LUALS_VARARG_TOKEN,
|
|
166
|
+
);
|
|
111
167
|
}
|
|
112
168
|
|
|
113
169
|
/**
|
|
114
170
|
* An ` extends X, Y` clause built from `iface.extends` split on commas, keeping only
|
|
115
|
-
* parents that name a declared interface
|
|
116
|
-
* mapped through the rename map; `""` when none
|
|
171
|
+
* parents that name a declared interface or an imported external type (so no
|
|
172
|
+
* `extends unknown` is ever emitted), mapped through the rename map; `""` when none
|
|
173
|
+
* survive.
|
|
117
174
|
*/
|
|
118
175
|
function renderExtends(
|
|
119
176
|
iface: LibraryInterface,
|
|
120
177
|
ctx: MapContext,
|
|
121
178
|
interfaceNames: ReadonlySet<string>,
|
|
179
|
+
externalTokens: ReadonlySet<string>,
|
|
122
180
|
): string {
|
|
123
181
|
if (!iface.extends) return "";
|
|
124
182
|
const parents = iface.extends
|
|
125
183
|
.split(",")
|
|
126
184
|
.map((name) => name.trim())
|
|
127
|
-
.filter((name) => interfaceNames.has(name))
|
|
185
|
+
.filter((name) => interfaceNames.has(name) || externalTokens.has(name))
|
|
128
186
|
.map((name) => mapTypes([name], ctx));
|
|
129
187
|
return parents.length > 0 ? ` extends ${parents.join(", ")}` : "";
|
|
130
188
|
}
|
|
@@ -171,16 +229,22 @@ function renderParams(params: readonly LibraryParam[], ctx: MapContext): string
|
|
|
171
229
|
function renderReturn(returns: readonly LibraryParam[], ctx: MapContext): string {
|
|
172
230
|
if (returns.length === 0) return "void";
|
|
173
231
|
if (returns.length === 1) return mapTypes((returns[0] as LibraryParam).types, ctx);
|
|
174
|
-
|
|
232
|
+
const last = returns[returns.length - 1] as LibraryParam;
|
|
233
|
+
const restTail = last.types.length === 1 && last.types[0] === LUALS_VARARG_TOKEN;
|
|
234
|
+
return luaMultiReturn(
|
|
235
|
+
returns.map((ret) => mapTypes(ret.types, ctx)),
|
|
236
|
+
restTail,
|
|
237
|
+
);
|
|
175
238
|
}
|
|
176
239
|
|
|
177
|
-
function pushDoc(lines: string[], summary: string, indent: string): void {
|
|
178
|
-
|
|
240
|
+
function pushDoc(lines: string[], summary: string, indent: string, deprecated?: string): void {
|
|
241
|
+
const parts = deprecated !== undefined ? { summary, deprecated } : { summary };
|
|
242
|
+
for (const line of renderDocComment(parts)) lines.push(`${indent}${line}`);
|
|
179
243
|
}
|
|
180
244
|
|
|
181
245
|
function renderAlias(alias: LibraryAlias, ctx: MapContext): string[] {
|
|
182
246
|
const lines: string[] = [];
|
|
183
|
-
pushDoc(lines, alias.doc, INDENT);
|
|
247
|
+
pushDoc(lines, alias.doc, INDENT, alias.deprecated);
|
|
184
248
|
lines.push(`${INDENT}type ${sanitizeTypeName(alias.name)} = ${mapTypes(alias.types, ctx)};`);
|
|
185
249
|
return lines;
|
|
186
250
|
}
|
|
@@ -189,12 +253,13 @@ function renderInterface(
|
|
|
189
253
|
iface: LibraryInterface,
|
|
190
254
|
ctx: MapContext,
|
|
191
255
|
interfaceNames: ReadonlySet<string>,
|
|
256
|
+
externalTokens: ReadonlySet<string>,
|
|
192
257
|
): string[] {
|
|
193
258
|
const lines: string[] = [];
|
|
194
|
-
pushDoc(lines, iface.brief, INDENT);
|
|
259
|
+
pushDoc(lines, iface.brief, INDENT, iface.deprecated);
|
|
195
260
|
const ifaceCtx = scopeGenerics(ctx, iface.generics);
|
|
196
261
|
const params = renderGenericParams(iface.generics, ifaceCtx);
|
|
197
|
-
const extendsClause = renderExtends(iface, ifaceCtx, interfaceNames);
|
|
262
|
+
const extendsClause = renderExtends(iface, ifaceCtx, interfaceNames, externalTokens);
|
|
198
263
|
lines.push(`${INDENT}interface ${sanitizeTypeName(iface.name)}${params}${extendsClause} {`);
|
|
199
264
|
const body = INDENT + INDENT;
|
|
200
265
|
for (const field of iface.fields) {
|
|
@@ -216,7 +281,7 @@ function renderInterface(
|
|
|
216
281
|
}
|
|
217
282
|
for (const method of iface.methods) {
|
|
218
283
|
if (!isPublicMethod(method)) continue;
|
|
219
|
-
pushDoc(lines, method.brief, body);
|
|
284
|
+
pushDoc(lines, method.brief, body, method.deprecated);
|
|
220
285
|
const methodCtx = scopeGenerics(ifaceCtx, method.generics);
|
|
221
286
|
const methodParams = renderGenericParams(method.generics, methodCtx);
|
|
222
287
|
lines.push(
|
|
@@ -258,7 +323,7 @@ function renderModuleConstants(iface: LibraryInterface, ctx: MapContext): string
|
|
|
258
323
|
|
|
259
324
|
function renderModuleFunction(fn: LibraryMethod, ctx: MapContext): string[] {
|
|
260
325
|
const lines: string[] = [];
|
|
261
|
-
pushDoc(lines, fn.brief, INDENT);
|
|
326
|
+
pushDoc(lines, fn.brief, INDENT, fn.deprecated);
|
|
262
327
|
const fnCtx = scopeGenerics(ctx, fn.generics);
|
|
263
328
|
const genericParams = renderGenericParams(fn.generics, fnCtx);
|
|
264
329
|
const params = renderParams(fn.params, fnCtx);
|
|
@@ -290,6 +355,7 @@ function renderModuleFunction(fn: LibraryMethod, ctx: MapContext): string[] {
|
|
|
290
355
|
export function buildModelContext(
|
|
291
356
|
model: LibraryModel,
|
|
292
357
|
typeRenames?: Record<string, string>,
|
|
358
|
+
externalTypes?: Record<string, ExternalTypeRef>,
|
|
293
359
|
): MapContext {
|
|
294
360
|
const declaredNames = [
|
|
295
361
|
...model.interfaces.map((iface) => iface.name),
|
|
@@ -297,9 +363,18 @@ export function buildModelContext(
|
|
|
297
363
|
];
|
|
298
364
|
const nameRenames: Record<string, string> = {};
|
|
299
365
|
for (const name of declaredNames) nameRenames[name] = sanitizeTypeName(name);
|
|
366
|
+
const externalRenames = externalTypeRenames(externalTypes);
|
|
367
|
+
for (const [token, alias] of Object.entries(externalRenames)) {
|
|
368
|
+
const collision = declaredNames.find((name) => sanitizeTypeName(name) === alias);
|
|
369
|
+
if (collision !== undefined) {
|
|
370
|
+
throw new Error(
|
|
371
|
+
`buildModelContext: external type "${token}" resolves to local name "${alias}", which the model already declares as "${collision}".`,
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
300
375
|
return {
|
|
301
|
-
knownNames: new Set(declaredNames),
|
|
302
|
-
typeRenames: { ...(typeRenames ?? {}), ...nameRenames },
|
|
376
|
+
knownNames: new Set([...declaredNames, ...Object.values(externalRenames)]),
|
|
377
|
+
typeRenames: { ...(typeRenames ?? {}), ...externalRenames, ...nameRenames },
|
|
303
378
|
};
|
|
304
379
|
}
|
|
305
380
|
|
|
@@ -310,10 +385,12 @@ export function buildModelContext(
|
|
|
310
385
|
* `unknown` inside `mapLualsType` exactly as the fidelity report records them.
|
|
311
386
|
*/
|
|
312
387
|
export function emitLibraryDeclarations(model: LibraryModel, opts: EmitLibraryOptions): string {
|
|
313
|
-
const ctx = buildModelContext(model, opts.typeRenames);
|
|
388
|
+
const ctx = buildModelContext(model, opts.typeRenames, opts.externalTypes);
|
|
314
389
|
|
|
315
390
|
const interfaceNames = new Set(model.interfaces.map((iface) => iface.name));
|
|
391
|
+
const externalTokens = new Set(Object.keys(opts.externalTypes ?? {}));
|
|
316
392
|
const out: string[] = ["/** @noResolution */", `declare module '${opts.moduleId}' {`];
|
|
393
|
+
out.push(...renderExternalImports(opts.externalTypes));
|
|
317
394
|
for (const alias of model.aliases) out.push(...renderAlias(alias, ctx));
|
|
318
395
|
for (const iface of model.interfaces) {
|
|
319
396
|
// The module object's fields become module-level `export const`s (below), so it is
|
|
@@ -322,7 +399,7 @@ export function emitLibraryDeclarations(model: LibraryModel, opts: EmitLibraryOp
|
|
|
322
399
|
out.push(...renderModuleConstants(iface, ctx));
|
|
323
400
|
continue;
|
|
324
401
|
}
|
|
325
|
-
out.push(...renderInterface(iface, ctx, interfaceNames));
|
|
402
|
+
out.push(...renderInterface(iface, ctx, interfaceNames, externalTokens));
|
|
326
403
|
}
|
|
327
404
|
for (const fn of model.moduleFunctions) {
|
|
328
405
|
if (!isPublicMethod(fn)) continue;
|
|
@@ -86,46 +86,82 @@ export function extractApiDoc(source: string, moduleName: string): unknown {
|
|
|
86
86
|
const elements: Record<string, unknown>[] = [];
|
|
87
87
|
const emittedNames = new Set<string>();
|
|
88
88
|
const referencedTypeNodes: ts.TypeNode[] = [];
|
|
89
|
+
// Type nodes reached from file-scope declarations, kept apart from the module
|
|
90
|
+
// block's so an ambient global can never pull a module-block shape onto `/api`
|
|
91
|
+
// that the module surface alone did not reach.
|
|
92
|
+
const globalTypeNodes: ts.TypeNode[] = [];
|
|
93
|
+
// Each alias's declaration paired with the bare element it already pushed, so
|
|
94
|
+
// a reachable object-literal alias is filled in by merging into that element
|
|
95
|
+
// rather than by appending a second one — element order stays as emitted.
|
|
96
|
+
const aliasElements: { node: ts.TypeAliasDeclaration; element: Record<string, unknown> }[] = [];
|
|
89
97
|
|
|
90
98
|
// Members nested in an `export namespace` (`bridge.bridge`) keep their
|
|
91
99
|
// namespace path so same-named members across namespaces (e.g. `is_supported`)
|
|
92
100
|
// stay distinct instead of colliding in `emittedNames`.
|
|
93
|
-
|
|
101
|
+
//
|
|
102
|
+
// `global` switches the walk to the ambient lane: the file's own statements
|
|
103
|
+
// rather than the module block's. There the gate is the `declare` modifier
|
|
104
|
+
// (there is no `export =` to suppress bare declarations, so `emitBare` does not
|
|
105
|
+
// apply), each element carries the `global` marker, and a type alias is not
|
|
106
|
+
// pushed eagerly — file scope carries far more incidental type machinery than a
|
|
107
|
+
// module block, so a shape publishes only once reachability proves a published
|
|
108
|
+
// signature names it.
|
|
109
|
+
const collect = (nodes: readonly ts.Statement[], prefix: string, global: boolean): void => {
|
|
94
110
|
const qualify = (name: string): string => (prefix ? `${prefix}.${name}` : name);
|
|
111
|
+
const mark = global ? { global: true } : {};
|
|
112
|
+
const typeNodes = global ? globalTypeNodes : referencedTypeNodes;
|
|
113
|
+
// Inside an ambient namespace every member is ambient by containment, so the
|
|
114
|
+
// nested lane emits regardless of an `export` keyword.
|
|
115
|
+
const gate = (stmt: ts.HasModifiers): boolean =>
|
|
116
|
+
global ? prefix !== "" || isAmbient(stmt) || isExported(stmt) : isExported(stmt) || emitBare;
|
|
95
117
|
for (const stmt of nodes) {
|
|
96
|
-
if (ts.isFunctionDeclaration(stmt) && stmt.name && (
|
|
97
|
-
collectFunctionReferenceTypes(stmt,
|
|
118
|
+
if (ts.isFunctionDeclaration(stmt) && stmt.name && gate(stmt)) {
|
|
119
|
+
collectFunctionReferenceTypes(stmt, typeNodes);
|
|
98
120
|
const name = qualify(stmt.name.text);
|
|
99
|
-
elements.push(functionElement(stmt, name, sf));
|
|
121
|
+
elements.push({ ...functionElement(stmt, name, sf), ...mark });
|
|
100
122
|
emittedNames.add(name);
|
|
101
|
-
} else if (ts.isVariableStatement(stmt) && (
|
|
123
|
+
} else if (ts.isVariableStatement(stmt) && gate(stmt)) {
|
|
124
|
+
// The `VariableStatement` carries the JSDoc, not the declaration, so both the
|
|
125
|
+
// summary and the deprecation tag are read once from the statement and shared
|
|
126
|
+
// by every declarator under it.
|
|
127
|
+
const summary = jsDocSummary(stmt);
|
|
102
128
|
for (const decl of stmt.declarationList.declarations) {
|
|
103
|
-
if (decl.type)
|
|
129
|
+
if (decl.type) typeNodes.push(decl.type);
|
|
104
130
|
const fields = objectFields(decl.type, sf);
|
|
105
131
|
const name = qualify(decl.name.getText(sf));
|
|
106
132
|
elements.push({
|
|
107
133
|
type: "VARIABLE",
|
|
108
134
|
name,
|
|
135
|
+
// Unconditional, matching `functionElement`: an absent key and an empty one
|
|
136
|
+
// would be two shapes the docs-site had to tell apart for no gain.
|
|
137
|
+
brief: briefOf(summary),
|
|
138
|
+
description: summary,
|
|
109
139
|
types: decl.type ? [typeText(decl.type, sf)] : [],
|
|
110
140
|
...(fields ? { fields } : {}),
|
|
141
|
+
...deprecatedKey(stmt),
|
|
142
|
+
...mark,
|
|
111
143
|
});
|
|
112
144
|
emittedNames.add(name);
|
|
113
145
|
}
|
|
114
146
|
} else if (ts.isTypeAliasDeclaration(stmt)) {
|
|
147
|
+
if (global) continue;
|
|
115
148
|
const name = qualify(stmt.name.text);
|
|
116
|
-
|
|
149
|
+
const element: Record<string, unknown> = { type: "TYPEDEF", name };
|
|
150
|
+
elements.push(element);
|
|
117
151
|
emittedNames.add(name);
|
|
152
|
+
aliasElements.push({ node: stmt, element });
|
|
118
153
|
} else if (
|
|
119
154
|
ts.isModuleDeclaration(stmt) &&
|
|
155
|
+
!ts.isStringLiteral(stmt.name) &&
|
|
120
156
|
stmt.body &&
|
|
121
157
|
ts.isModuleBlock(stmt.body) &&
|
|
122
|
-
(
|
|
158
|
+
gate(stmt)
|
|
123
159
|
) {
|
|
124
|
-
collect(stmt.body.statements, qualify(stmt.name.getText(sf)));
|
|
160
|
+
collect(stmt.body.statements, qualify(stmt.name.getText(sf)), global);
|
|
125
161
|
}
|
|
126
162
|
}
|
|
127
163
|
};
|
|
128
|
-
collect(statements, "");
|
|
164
|
+
collect(statements, "", false);
|
|
129
165
|
|
|
130
166
|
const moduleValueInterfaces = exportedValueInterfaces(moduleBlock);
|
|
131
167
|
for (const iface of moduleValueInterfaces) {
|
|
@@ -145,6 +181,7 @@ export function extractApiDoc(source: string, moduleName: string): unknown {
|
|
|
145
181
|
name,
|
|
146
182
|
types: member.type ? [typeText(member.type, sf)] : [],
|
|
147
183
|
...(fields ? { fields } : {}),
|
|
184
|
+
...deprecatedKey(member),
|
|
148
185
|
});
|
|
149
186
|
emittedNames.add(name);
|
|
150
187
|
}
|
|
@@ -152,16 +189,59 @@ export function extractApiDoc(source: string, moduleName: string): unknown {
|
|
|
152
189
|
}
|
|
153
190
|
|
|
154
191
|
const moduleValueInterfaceNames = new Set(moduleValueInterfaces.map((iface) => iface.name.text));
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
192
|
+
const declared = moduleBlock ? moduleTypeDeclarations(moduleBlock) : undefined;
|
|
193
|
+
const reachable = referencedTypeNames(declared, referencedTypeNodes);
|
|
194
|
+
|
|
195
|
+
for (const { node, element } of aliasElements) {
|
|
196
|
+
const name = node.name.text;
|
|
197
|
+
// Reachability is resolved against the module block, so identity keeps a
|
|
198
|
+
// same-named alias nested in a namespace from borrowing the outer one's.
|
|
199
|
+
if (declared?.aliases.get(name) !== node) continue;
|
|
200
|
+
if (!reachable.has(name) || !ts.isTypeLiteralNode(node.type)) continue;
|
|
201
|
+
const { functions, properties } = shapeMembers(node.type.members, sf);
|
|
202
|
+
if (functions.length > 0) element.functions = functions;
|
|
203
|
+
if (properties.length > 0) element.properties = properties;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
for (const name of reachable) {
|
|
207
|
+
if (moduleValueInterfaceNames.has(name) || emittedNames.has(name)) continue;
|
|
208
|
+
const iface = declared?.interfaces.get(name);
|
|
209
|
+
if (!iface) continue;
|
|
161
210
|
const typedef = typedefElement(iface, sf);
|
|
162
211
|
if (!typedef) continue;
|
|
163
212
|
elements.push(typedef);
|
|
164
|
-
emittedNames.add(
|
|
213
|
+
emittedNames.add(name);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// The ambient lane runs last so every module-block element keeps the index it
|
|
217
|
+
// has today. Its shapes resolve against file-scope declarations only, by node
|
|
218
|
+
// identity, so a file-scope shape never borrows a same-named module-block one.
|
|
219
|
+
collect(sf.statements, "", true);
|
|
220
|
+
const globalDeclared = typeDeclarationsIn(sf.statements);
|
|
221
|
+
for (const name of referencedTypeNames(globalDeclared, globalTypeNodes)) {
|
|
222
|
+
if (emittedNames.has(name)) continue;
|
|
223
|
+
const alias = globalDeclared.aliases.get(name);
|
|
224
|
+
if (alias) {
|
|
225
|
+
if (!ts.isTypeLiteralNode(alias.type)) continue;
|
|
226
|
+
const { functions, properties } = shapeMembers(alias.type.members, sf);
|
|
227
|
+
if (functions.length === 0 && properties.length === 0) continue;
|
|
228
|
+
elements.push({
|
|
229
|
+
type: "TYPEDEF",
|
|
230
|
+
name,
|
|
231
|
+
...(functions.length > 0 ? { functions } : {}),
|
|
232
|
+
...(properties.length > 0 ? { properties } : {}),
|
|
233
|
+
...deprecatedKey(alias),
|
|
234
|
+
global: true,
|
|
235
|
+
});
|
|
236
|
+
emittedNames.add(name);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const iface = globalDeclared.interfaces.get(name);
|
|
240
|
+
if (!iface) continue;
|
|
241
|
+
const typedef = typedefElement(iface, sf);
|
|
242
|
+
if (!typedef) continue;
|
|
243
|
+
elements.push({ ...typedef, global: true });
|
|
244
|
+
emittedNames.add(name);
|
|
165
245
|
}
|
|
166
246
|
|
|
167
247
|
return {
|
|
@@ -212,6 +292,7 @@ function functionElement(
|
|
|
212
292
|
parameters,
|
|
213
293
|
returnvalues,
|
|
214
294
|
...(example === "" ? {} : { examples: example }),
|
|
295
|
+
...deprecatedKey(decl),
|
|
215
296
|
};
|
|
216
297
|
}
|
|
217
298
|
|
|
@@ -225,13 +306,22 @@ function collectFunctionReferenceTypes(
|
|
|
225
306
|
if (decl.type) out.push(decl.type);
|
|
226
307
|
}
|
|
227
308
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
309
|
+
interface ShapeMembers {
|
|
310
|
+
functions: Record<string, unknown>[];
|
|
311
|
+
properties: Record<string, unknown>[];
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* The one member reader shared by both shape carriers — an `interface`
|
|
316
|
+
* declaration and a `type X = { … }` alias — so the two lanes cannot drift.
|
|
317
|
+
* `is_optional` is written only on an optional member, matching how
|
|
318
|
+
* `deprecatedKey` encodes absence and keeping a regen diff proportional to what
|
|
319
|
+
* actually changed.
|
|
320
|
+
*/
|
|
321
|
+
function shapeMembers(members: readonly ts.TypeElement[], sf: ts.SourceFile): ShapeMembers {
|
|
232
322
|
const functions: Record<string, unknown>[] = [];
|
|
233
323
|
const properties: Record<string, unknown>[] = [];
|
|
234
|
-
for (const member of
|
|
324
|
+
for (const member of members) {
|
|
235
325
|
if (!member.name) continue;
|
|
236
326
|
const name = memberName(member.name, sf);
|
|
237
327
|
if (ts.isMethodSignature(member)) {
|
|
@@ -244,50 +334,80 @@ function typedefElement(
|
|
|
244
334
|
brief: briefOf(summary),
|
|
245
335
|
description: summary,
|
|
246
336
|
types: member.type ? [typeText(member.type, sf)] : [],
|
|
337
|
+
...(member.questionToken ? { is_optional: "True" } : {}),
|
|
247
338
|
...(fields ? { fields } : {}),
|
|
339
|
+
...deprecatedKey(member),
|
|
248
340
|
});
|
|
249
341
|
}
|
|
250
342
|
}
|
|
343
|
+
return { functions, properties };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function typedefElement(
|
|
347
|
+
iface: ts.InterfaceDeclaration,
|
|
348
|
+
sf: ts.SourceFile,
|
|
349
|
+
): Record<string, unknown> | undefined {
|
|
350
|
+
const { functions, properties } = shapeMembers(iface.members, sf);
|
|
251
351
|
if (functions.length === 0 && properties.length === 0) return undefined;
|
|
252
352
|
return {
|
|
253
353
|
type: "TYPEDEF",
|
|
254
354
|
name: iface.name.text,
|
|
255
355
|
...(functions.length > 0 ? { functions } : {}),
|
|
256
356
|
...(properties.length > 0 ? { properties } : {}),
|
|
357
|
+
...deprecatedKey(iface),
|
|
257
358
|
};
|
|
258
359
|
}
|
|
259
360
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
361
|
+
interface TypeDeclarations {
|
|
362
|
+
aliases: Map<string, ts.TypeAliasDeclaration>;
|
|
363
|
+
interfaces: Map<string, ts.InterfaceDeclaration>;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function typeDeclarationsIn(statements: readonly ts.Statement[]): TypeDeclarations {
|
|
266
367
|
const aliases = new Map<string, ts.TypeAliasDeclaration>();
|
|
267
368
|
const interfaces = new Map<string, ts.InterfaceDeclaration>();
|
|
268
|
-
for (const stmt of
|
|
369
|
+
for (const stmt of statements) {
|
|
269
370
|
if (ts.isTypeAliasDeclaration(stmt)) aliases.set(stmt.name.text, stmt);
|
|
270
371
|
if (ts.isInterfaceDeclaration(stmt)) interfaces.set(stmt.name.text, stmt);
|
|
271
372
|
}
|
|
373
|
+
return { aliases, interfaces };
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function moduleTypeDeclarations(moduleBlock: ts.ModuleBlock): TypeDeclarations {
|
|
377
|
+
return typeDeclarationsIn(moduleBlock.statements);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Every alias and interface name declared in `declared` that is reachable from
|
|
382
|
+
* an emitted member's type, in first-reached order. Both shape lanes gate on
|
|
383
|
+
* this single traversal — a reachable interface becomes its own TYPEDEF element,
|
|
384
|
+
* a reachable alias over an object literal has that element's members filled in
|
|
385
|
+
* — so a shape no published member names stays off `/api` either way. The
|
|
386
|
+
* module-block and file-scope lanes each pass their own declarations, so a name
|
|
387
|
+
* declared in both resolves to the lane that named it.
|
|
388
|
+
*/
|
|
389
|
+
function referencedTypeNames(
|
|
390
|
+
declared: TypeDeclarations | undefined,
|
|
391
|
+
typeNodes: ts.TypeNode[],
|
|
392
|
+
): Set<string> {
|
|
393
|
+
const names = new Set<string>();
|
|
394
|
+
if (!declared) return names;
|
|
395
|
+
const { aliases, interfaces } = declared;
|
|
272
396
|
|
|
273
|
-
const found = new Map<string, ts.InterfaceDeclaration>();
|
|
274
|
-
const seenAliases = new Set<string>();
|
|
275
397
|
const visit = (node: ts.Node): void => {
|
|
276
398
|
if (ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName)) {
|
|
277
399
|
const name = node.typeName.text;
|
|
278
|
-
const iface = interfaces.get(name);
|
|
279
|
-
if (iface && !excludedNames.has(name)) found.set(name, iface);
|
|
280
400
|
const alias = aliases.get(name);
|
|
281
|
-
if (alias && !
|
|
282
|
-
|
|
283
|
-
visit(alias.type);
|
|
401
|
+
if ((alias || interfaces.has(name)) && !names.has(name)) {
|
|
402
|
+
names.add(name);
|
|
403
|
+
if (alias) visit(alias.type);
|
|
284
404
|
}
|
|
285
405
|
}
|
|
286
406
|
ts.forEachChild(node, visit);
|
|
287
407
|
};
|
|
288
408
|
|
|
289
409
|
for (const node of typeNodes) visit(node);
|
|
290
|
-
return
|
|
410
|
+
return names;
|
|
291
411
|
}
|
|
292
412
|
|
|
293
413
|
function exportedValueInterfaces(
|
|
@@ -350,9 +470,17 @@ function memberName(name: ts.PropertyName, sf: ts.SourceFile): string {
|
|
|
350
470
|
return name.getText(sf);
|
|
351
471
|
}
|
|
352
472
|
|
|
473
|
+
// string-named only: a file-scope `declare namespace` is a `ModuleDeclaration`
|
|
474
|
+
// too, so an identifier-named block appearing before the real `declare
|
|
475
|
+
// module '<name>'` would otherwise be mistaken for the module surface.
|
|
353
476
|
function findModuleBlock(sf: ts.SourceFile): ts.ModuleBlock | undefined {
|
|
354
477
|
for (const stmt of sf.statements) {
|
|
355
|
-
if (
|
|
478
|
+
if (
|
|
479
|
+
ts.isModuleDeclaration(stmt) &&
|
|
480
|
+
ts.isStringLiteral(stmt.name) &&
|
|
481
|
+
stmt.body &&
|
|
482
|
+
ts.isModuleBlock(stmt.body)
|
|
483
|
+
) {
|
|
356
484
|
return stmt.body;
|
|
357
485
|
}
|
|
358
486
|
}
|
|
@@ -363,6 +491,10 @@ function isExported(node: ts.HasModifiers): boolean {
|
|
|
363
491
|
return (ts.getCombinedModifierFlags(node as ts.Declaration) & ts.ModifierFlags.Export) !== 0;
|
|
364
492
|
}
|
|
365
493
|
|
|
494
|
+
function isAmbient(node: ts.HasModifiers): boolean {
|
|
495
|
+
return (ts.getCombinedModifierFlags(node as ts.Declaration) & ts.ModifierFlags.Ambient) !== 0;
|
|
496
|
+
}
|
|
497
|
+
|
|
366
498
|
/** The closest non-empty JSDoc summary text attached to a node, tags stripped. */
|
|
367
499
|
function jsDocSummary(node: ts.Node): string {
|
|
368
500
|
const comments = ts
|
|
@@ -412,6 +544,25 @@ function exampleText(decl: ts.FunctionDeclaration | ts.MethodSignature): string
|
|
|
412
544
|
return "";
|
|
413
545
|
}
|
|
414
546
|
|
|
547
|
+
// `undefined` when the node carries no `@deprecated`, the trimmed tag text when
|
|
548
|
+
// it does — `""` for a bare tag. The caller spreads the key only on a defined
|
|
549
|
+
// result, so absence of the key is the sole encoding of "not deprecated" and a
|
|
550
|
+
// bare tag stays distinguishable from an untagged symbol.
|
|
551
|
+
function deprecatedText(node: ts.Node): string | undefined {
|
|
552
|
+
for (const tag of ts.getJSDocTags(node)) {
|
|
553
|
+
if (tag.tagName.text === "deprecated") {
|
|
554
|
+
return (ts.getTextOfJSDocComment(tag.comment) ?? "").trim();
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return undefined;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/** The `{ deprecated }` key to spread onto an element, empty when untagged. */
|
|
561
|
+
function deprecatedKey(node: ts.Node): Record<string, string> {
|
|
562
|
+
const text = deprecatedText(node);
|
|
563
|
+
return text === undefined ? {} : { deprecated: text };
|
|
564
|
+
}
|
|
565
|
+
|
|
415
566
|
/** Trim a JSDoc `@param`/`@returns` comment and drop a leading `-` delimiter. */
|
|
416
567
|
function cleanDoc(comment: string | undefined): string {
|
|
417
568
|
return (comment ?? "").trim().replace(/^-\s*/, "");
|