@defold-typescript/library-types 0.18.1

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.
Files changed (101) hide show
  1. package/NOTICE +52 -0
  2. package/README.md +78 -0
  3. package/api-doc/boom.boom.json +26 -0
  4. package/api-doc/bridge.bridge.json +1653 -0
  5. package/api-doc/bzAnim.bzLibrary.json +156 -0
  6. package/api-doc/defcon.console.json +109 -0
  7. package/api-doc/defmath.defmath.json +1381 -0
  8. package/api-doc/defsave.defsave.json +175 -0
  9. package/api-doc/deftest.deftest.json +71 -0
  10. package/api-doc/dicebag.dicebag.json +323 -0
  11. package/api-doc/event.event.json +238 -0
  12. package/api-doc/gooey.gooey.json +963 -0
  13. package/api-doc/immutable.immutable.json +63 -0
  14. package/api-doc/in.accelerometer.json +281 -0
  15. package/api-doc/in.button.json +148 -0
  16. package/api-doc/in.cursor.json +203 -0
  17. package/api-doc/in.gesture.json +206 -0
  18. package/api-doc/in.keyboard.json +45 -0
  19. package/api-doc/in.mapper.json +141 -0
  20. package/api-doc/in.onscreen.json +265 -0
  21. package/api-doc/in.state.json +195 -0
  22. package/api-doc/in.textbox.json +213 -0
  23. package/api-doc/in.triggers.json +1260 -0
  24. package/api-doc/lang.lang.json +411 -0
  25. package/api-doc/log.log.json +50 -0
  26. package/api-doc/metrics.fps.json +88 -0
  27. package/api-doc/metrics.mem.json +80 -0
  28. package/api-doc/monarch.monarch.json +1065 -0
  29. package/api-doc/monarch.transitions.easings.json +206 -0
  30. package/api-doc/monarch.transitions.gui.json +445 -0
  31. package/api-doc/nakama.engine.defold.json +165 -0
  32. package/api-doc/nakama.nakama.json +6574 -0
  33. package/api-doc/nakama.util.log.json +25 -0
  34. package/api-doc/narrator.narrator.json +150 -0
  35. package/api-doc/orthographic.camera.json +1019 -0
  36. package/api-doc/persist.persist.json +142 -0
  37. package/api-doc/platypus.platypus.json +631 -0
  38. package/api-doc/proto.proto.json +355 -0
  39. package/api-doc/rendy.rendy.json +408 -0
  40. package/api-doc/richtext.color.json +219 -0
  41. package/api-doc/richtext.richtext.json +382 -0
  42. package/api-doc/richtext.tags.json +67 -0
  43. package/api-doc/saver.saver.json +553 -0
  44. package/api-doc/saver.storage.json +174 -0
  45. package/api-doc/squid.squid.json +660 -0
  46. package/api-doc/starly.starly.json +488 -0
  47. package/api-doc/tweener.tweener.json +419 -0
  48. package/api-doc/yagames.yagames.json +1465 -0
  49. package/api-doc/zzfx.api.json +85 -0
  50. package/generated/boom.boom.d.ts +1585 -0
  51. package/generated/bridge.bridge.d.ts +533 -0
  52. package/generated/bzAnim.bzLibrary.d.ts +93 -0
  53. package/generated/defcon.console.d.ts +24 -0
  54. package/generated/defmath.defmath.d.ts +194 -0
  55. package/generated/defsave.defsave.d.ts +31 -0
  56. package/generated/deftest.deftest.d.ts +47 -0
  57. package/generated/dicebag.dicebag.d.ts +90 -0
  58. package/generated/event.event.d.ts +54 -0
  59. package/generated/gooey.gooey.d.ts +261 -0
  60. package/generated/immutable.immutable.d.ts +13 -0
  61. package/generated/in.accelerometer.d.ts +37 -0
  62. package/generated/in.button.d.ts +20 -0
  63. package/generated/in.cursor.d.ts +33 -0
  64. package/generated/in.gesture.d.ts +64 -0
  65. package/generated/in.keyboard.d.ts +18 -0
  66. package/generated/in.mapper.d.ts +23 -0
  67. package/generated/in.onscreen.d.ts +58 -0
  68. package/generated/in.state.d.ts +34 -0
  69. package/generated/in.textbox.d.ts +26 -0
  70. package/generated/in.triggers.d.ts +180 -0
  71. package/generated/lang.lang.d.ts +33 -0
  72. package/generated/log.log.d.ts +40 -0
  73. package/generated/metrics.fps.d.ts +16 -0
  74. package/generated/metrics.mem.d.ts +16 -0
  75. package/generated/monarch.monarch.d.ts +412 -0
  76. package/generated/monarch.transitions.easings.d.ts +65 -0
  77. package/generated/monarch.transitions.gui.d.ts +197 -0
  78. package/generated/nakama.engine.defold.d.ts +24 -0
  79. package/generated/nakama.nakama.d.ts +594 -0
  80. package/generated/nakama.util.log.d.ts +10 -0
  81. package/generated/narrator.narrator.d.ts +66 -0
  82. package/generated/orthographic.camera.d.ts +308 -0
  83. package/generated/persist.persist.d.ts +34 -0
  84. package/generated/platypus.platypus.d.ts +76 -0
  85. package/generated/proto.proto.d.ts +36 -0
  86. package/generated/rendy.rendy.d.ts +184 -0
  87. package/generated/richtext.color.d.ts +33 -0
  88. package/generated/richtext.richtext.d.ts +126 -0
  89. package/generated/richtext.tags.d.ts +12 -0
  90. package/generated/saver.saver.d.ts +44 -0
  91. package/generated/saver.storage.d.ts +16 -0
  92. package/generated/squid.squid.d.ts +106 -0
  93. package/generated/starly.starly.d.ts +148 -0
  94. package/generated/tweener.tweener.d.ts +151 -0
  95. package/generated/yagames.yagames.d.ts +279 -0
  96. package/generated/zzfx.api.d.ts +20 -0
  97. package/library-classification.json +726 -0
  98. package/library-targets.json +291 -0
  99. package/package.json +180 -0
  100. package/scripts/extract-api-doc.ts +418 -0
  101. package/scripts/sync-library-types.ts +542 -0
@@ -0,0 +1,418 @@
1
+ import ts from "typescript";
2
+
3
+ const printer = ts.createPrinter({ removeComments: true });
4
+
5
+ /**
6
+ * A type node's text as a single comment-free line. The printer re-emits the
7
+ * AST (so a `//`/`/*` inside a string-literal type is never mistaken for a
8
+ * comment), then interior whitespace is collapsed so multi-line object literals
9
+ * and wrapped unions no longer leak newlines or member JSDoc into `/api`.
10
+ */
11
+ function typeText(node: ts.TypeNode, sf: ts.SourceFile): string {
12
+ return printer.printNode(ts.EmitHint.Unspecified, node, sf).replace(/\s+/g, " ").trim();
13
+ }
14
+
15
+ interface Field {
16
+ name: string;
17
+ doc: string;
18
+ types: string[];
19
+ is_optional: string;
20
+ fields?: Field[];
21
+ }
22
+
23
+ /**
24
+ * Recursively walk an object-literal type's property members into a `fields`
25
+ * tree, preserving each member's JSDoc alongside the flat `typeText` token (see
26
+ * bug-39). Returns `undefined` for a non-object-literal node or a literal with
27
+ * no property members (e.g. an index-signature-only map), so plain-typed
28
+ * parameters emit no `fields` key.
29
+ */
30
+ function objectFields(node: ts.TypeNode | undefined, sf: ts.SourceFile): Field[] | undefined {
31
+ if (!node || !ts.isTypeLiteralNode(node)) return undefined;
32
+ const fields: Field[] = [];
33
+ for (const member of node.members) {
34
+ if (!ts.isPropertySignature(member) || !member.type) continue;
35
+ const nested = objectFields(member.type, sf);
36
+ fields.push({
37
+ name: member.name.getText(sf),
38
+ doc: jsDocSummary(member),
39
+ types: [typeText(member.type, sf)],
40
+ is_optional: member.questionToken ? "True" : "False",
41
+ ...(nested ? { fields: nested } : {}),
42
+ });
43
+ }
44
+ return fields.length > 0 ? fields : undefined;
45
+ }
46
+
47
+ /**
48
+ * Turn a vendored `declare module '<name>'` ambient `.d.ts` into the
49
+ * `{ info, elements }` JSON shape that `@defold-typescript/types`'
50
+ * `parseDefoldApiDoc` accepts, so the docs-site consumes ref-doc JSON and never
51
+ * parses `.d.ts`. Pure and node-free (string in, object out) so it is unit
52
+ * testable and reusable by `regen`; walks the TS AST rather than regexing the
53
+ * source.
54
+ *
55
+ * The output carries module content only. Per-library provenance
56
+ * (repo/commit/import string) is joined at load time from
57
+ * `library-classification.json` + `NOTICE`, not written here.
58
+ */
59
+ export function extractApiDoc(source: string, moduleName: string): unknown {
60
+ const sf = ts.createSourceFile(
61
+ "module.d.ts",
62
+ source,
63
+ ts.ScriptTarget.Latest,
64
+ /* setParentNodes */ true,
65
+ ts.ScriptKind.TS,
66
+ );
67
+
68
+ const moduleBlock = findModuleBlock(sf);
69
+ const rawSummary = moduleBlock ? jsDocSummary(moduleBlock.parent) : "";
70
+ // ts-defold marks a hand-written stub module with a fixed "definition stub"
71
+ // JSDoc that carries no real docs. Emitting it as `info.description` would
72
+ // shadow the docs-site fallback to the GitHub-sourced `library-descriptions`
73
+ // text, so treat the sentinel as no summary and let that fallback fill in.
74
+ const summary = isStubSummary(rawSummary) ? "" : rawSummary;
75
+
76
+ const statements = moduleBlock?.statements ?? [];
77
+ // A string-named ambient module's public surface is the module block itself.
78
+ // When it has no `export =` re-export, every top-level (and nested-namespace)
79
+ // declaration is API regardless of a missing `export` keyword — ts-defold
80
+ // vendors both `export function` and bare `function` for the same intent
81
+ // (`rendy.rendy`, `in.onscreen`). With an `export =`, the bare declarations
82
+ // are internal plumbing behind the re-exported value, so they stay unemitted
83
+ // and the value's interface drives the surface instead (`squid`, `starly`).
84
+ const emitBare = !statements.some(ts.isExportAssignment);
85
+
86
+ const elements: Record<string, unknown>[] = [];
87
+ const emittedNames = new Set<string>();
88
+ const referencedTypeNodes: ts.TypeNode[] = [];
89
+
90
+ // Members nested in an `export namespace` (`bridge.bridge`) keep their
91
+ // namespace path so same-named members across namespaces (e.g. `is_supported`)
92
+ // stay distinct instead of colliding in `emittedNames`.
93
+ const collect = (nodes: readonly ts.Statement[], prefix: string): void => {
94
+ const qualify = (name: string): string => (prefix ? `${prefix}.${name}` : name);
95
+ for (const stmt of nodes) {
96
+ if (ts.isFunctionDeclaration(stmt) && stmt.name && (isExported(stmt) || emitBare)) {
97
+ collectFunctionReferenceTypes(stmt, referencedTypeNodes);
98
+ const name = qualify(stmt.name.text);
99
+ elements.push(functionElement(stmt, name, sf));
100
+ emittedNames.add(name);
101
+ } else if (ts.isVariableStatement(stmt) && (isExported(stmt) || emitBare)) {
102
+ for (const decl of stmt.declarationList.declarations) {
103
+ if (decl.type) referencedTypeNodes.push(decl.type);
104
+ const fields = objectFields(decl.type, sf);
105
+ const name = qualify(decl.name.getText(sf));
106
+ elements.push({
107
+ type: "VARIABLE",
108
+ name,
109
+ types: decl.type ? [typeText(decl.type, sf)] : [],
110
+ ...(fields ? { fields } : {}),
111
+ });
112
+ emittedNames.add(name);
113
+ }
114
+ } else if (ts.isTypeAliasDeclaration(stmt)) {
115
+ const name = qualify(stmt.name.text);
116
+ elements.push({ type: "TYPEDEF", name });
117
+ emittedNames.add(name);
118
+ } else if (
119
+ ts.isModuleDeclaration(stmt) &&
120
+ stmt.body &&
121
+ ts.isModuleBlock(stmt.body) &&
122
+ (isExported(stmt) || emitBare)
123
+ ) {
124
+ collect(stmt.body.statements, qualify(stmt.name.getText(sf)));
125
+ }
126
+ }
127
+ };
128
+ collect(statements, "");
129
+
130
+ const moduleValueInterfaces = exportedValueInterfaces(moduleBlock);
131
+ for (const iface of moduleValueInterfaces) {
132
+ for (const member of iface.members) {
133
+ if (!member.name) continue;
134
+ const name = memberName(member.name, sf);
135
+ if (emittedNames.has(name)) continue;
136
+ if (ts.isMethodSignature(member)) {
137
+ collectFunctionReferenceTypes(member, referencedTypeNodes);
138
+ elements.push(functionElement(member, name, sf));
139
+ emittedNames.add(name);
140
+ } else if (ts.isPropertySignature(member)) {
141
+ if (member.type) referencedTypeNodes.push(member.type);
142
+ const fields = objectFields(member.type, sf);
143
+ elements.push({
144
+ type: "VARIABLE",
145
+ name,
146
+ types: member.type ? [typeText(member.type, sf)] : [],
147
+ ...(fields ? { fields } : {}),
148
+ });
149
+ emittedNames.add(name);
150
+ }
151
+ }
152
+ }
153
+
154
+ const moduleValueInterfaceNames = new Set(moduleValueInterfaces.map((iface) => iface.name.text));
155
+ for (const iface of referencedInterfaces(
156
+ moduleBlock,
157
+ referencedTypeNodes,
158
+ moduleValueInterfaceNames,
159
+ )) {
160
+ if (emittedNames.has(iface.name.text)) continue;
161
+ const typedef = typedefElement(iface, sf);
162
+ if (!typedef) continue;
163
+ elements.push(typedef);
164
+ emittedNames.add(iface.name.text);
165
+ }
166
+
167
+ return {
168
+ info: { namespace: moduleName, brief: briefOf(summary), description: summary },
169
+ elements,
170
+ };
171
+ }
172
+
173
+ function functionElement(
174
+ decl: ts.FunctionDeclaration | ts.MethodSignature,
175
+ name: string,
176
+ sf: ts.SourceFile,
177
+ ): Record<string, unknown> {
178
+ const summary = jsDocSummary(decl);
179
+ const paramDocs = paramDocMap(decl, sf);
180
+ const parameters = decl.parameters.map((p) => {
181
+ const pname = p.name.getText(sf);
182
+ const fields = objectFields(p.type, sf);
183
+ return {
184
+ name: pname,
185
+ doc: paramDocs.get(pname) ?? "",
186
+ types: p.type ? [typeText(p.type, sf)] : [],
187
+ is_optional: p.questionToken || p.initializer ? "True" : "False",
188
+ ...(fields ? { fields } : {}),
189
+ };
190
+ });
191
+
192
+ const returnText = decl.type ? typeText(decl.type, sf) : "";
193
+ const returnFields = objectFields(decl.type, sf);
194
+ const returnvalues =
195
+ returnText === "" || returnText === "void"
196
+ ? []
197
+ : [
198
+ {
199
+ name: "",
200
+ doc: returnDoc(decl),
201
+ types: [returnText],
202
+ ...(returnFields ? { fields: returnFields } : {}),
203
+ },
204
+ ];
205
+
206
+ const example = exampleText(decl);
207
+ return {
208
+ type: "FUNCTION",
209
+ name,
210
+ brief: briefOf(summary),
211
+ description: summary,
212
+ parameters,
213
+ returnvalues,
214
+ ...(example === "" ? {} : { examples: example }),
215
+ };
216
+ }
217
+
218
+ function collectFunctionReferenceTypes(
219
+ decl: ts.FunctionDeclaration | ts.MethodSignature,
220
+ out: ts.TypeNode[],
221
+ ): void {
222
+ for (const param of decl.parameters) {
223
+ if (param.type) out.push(param.type);
224
+ }
225
+ if (decl.type) out.push(decl.type);
226
+ }
227
+
228
+ function typedefElement(
229
+ iface: ts.InterfaceDeclaration,
230
+ sf: ts.SourceFile,
231
+ ): Record<string, unknown> | undefined {
232
+ const functions: Record<string, unknown>[] = [];
233
+ const properties: Record<string, unknown>[] = [];
234
+ for (const member of iface.members) {
235
+ if (!member.name) continue;
236
+ const name = memberName(member.name, sf);
237
+ if (ts.isMethodSignature(member)) {
238
+ functions.push(functionElement(member, name, sf));
239
+ } else if (ts.isPropertySignature(member)) {
240
+ const summary = jsDocSummary(member);
241
+ const fields = objectFields(member.type, sf);
242
+ properties.push({
243
+ name,
244
+ brief: briefOf(summary),
245
+ description: summary,
246
+ types: member.type ? [typeText(member.type, sf)] : [],
247
+ ...(fields ? { fields } : {}),
248
+ });
249
+ }
250
+ }
251
+ if (functions.length === 0 && properties.length === 0) return undefined;
252
+ return {
253
+ type: "TYPEDEF",
254
+ name: iface.name.text,
255
+ ...(functions.length > 0 ? { functions } : {}),
256
+ ...(properties.length > 0 ? { properties } : {}),
257
+ };
258
+ }
259
+
260
+ function referencedInterfaces(
261
+ moduleBlock: ts.ModuleBlock | undefined,
262
+ typeNodes: ts.TypeNode[],
263
+ excludedNames: ReadonlySet<string>,
264
+ ): ts.InterfaceDeclaration[] {
265
+ if (!moduleBlock) return [];
266
+ const aliases = new Map<string, ts.TypeAliasDeclaration>();
267
+ const interfaces = new Map<string, ts.InterfaceDeclaration>();
268
+ for (const stmt of moduleBlock.statements) {
269
+ if (ts.isTypeAliasDeclaration(stmt)) aliases.set(stmt.name.text, stmt);
270
+ if (ts.isInterfaceDeclaration(stmt)) interfaces.set(stmt.name.text, stmt);
271
+ }
272
+
273
+ const found = new Map<string, ts.InterfaceDeclaration>();
274
+ const seenAliases = new Set<string>();
275
+ const visit = (node: ts.Node): void => {
276
+ if (ts.isTypeReferenceNode(node) && ts.isIdentifier(node.typeName)) {
277
+ const name = node.typeName.text;
278
+ const iface = interfaces.get(name);
279
+ if (iface && !excludedNames.has(name)) found.set(name, iface);
280
+ const alias = aliases.get(name);
281
+ if (alias && !seenAliases.has(name)) {
282
+ seenAliases.add(name);
283
+ visit(alias.type);
284
+ }
285
+ }
286
+ ts.forEachChild(node, visit);
287
+ };
288
+
289
+ for (const node of typeNodes) visit(node);
290
+ return [...found.values()];
291
+ }
292
+
293
+ function exportedValueInterfaces(
294
+ moduleBlock: ts.ModuleBlock | undefined,
295
+ ): ts.InterfaceDeclaration[] {
296
+ if (!moduleBlock) return [];
297
+ const localVars = new Map<string, ts.TypeNode>();
298
+ const aliases = new Map<string, ts.TypeAliasDeclaration>();
299
+ const interfaces = new Map<string, ts.InterfaceDeclaration>();
300
+ let exportedName = "";
301
+
302
+ for (const stmt of moduleBlock.statements) {
303
+ if (ts.isVariableStatement(stmt)) {
304
+ for (const decl of stmt.declarationList.declarations) {
305
+ if (ts.isIdentifier(decl.name) && decl.type) localVars.set(decl.name.text, decl.type);
306
+ }
307
+ } else if (ts.isTypeAliasDeclaration(stmt)) {
308
+ aliases.set(stmt.name.text, stmt);
309
+ } else if (ts.isInterfaceDeclaration(stmt)) {
310
+ interfaces.set(stmt.name.text, stmt);
311
+ } else if (ts.isExportAssignment(stmt) && ts.isIdentifier(stmt.expression)) {
312
+ exportedName = stmt.expression.text;
313
+ }
314
+ }
315
+
316
+ const rootType = localVars.get(exportedName);
317
+ if (!rootType) return [];
318
+ const seen = new Set<string>();
319
+ const resolved = resolveInterfaces(rootType, aliases, interfaces, seen);
320
+ return [...new Set(resolved)];
321
+ }
322
+
323
+ function resolveInterfaces(
324
+ node: ts.TypeNode,
325
+ aliases: Map<string, ts.TypeAliasDeclaration>,
326
+ interfaces: Map<string, ts.InterfaceDeclaration>,
327
+ seen: Set<string>,
328
+ ): ts.InterfaceDeclaration[] {
329
+ if (ts.isIntersectionTypeNode(node)) {
330
+ return node.types.flatMap((type) => resolveInterfaces(type, aliases, interfaces, seen));
331
+ }
332
+ if (!ts.isTypeReferenceNode(node) || !ts.isIdentifier(node.typeName)) return [];
333
+ const name = node.typeName.text;
334
+ if (name === "Readonly" && node.typeArguments?.[0]) {
335
+ return resolveInterfaces(node.typeArguments[0], aliases, interfaces, seen);
336
+ }
337
+ const iface = interfaces.get(name);
338
+ if (iface) return [iface];
339
+ const alias = aliases.get(name);
340
+ if (!alias || seen.has(name)) return [];
341
+ seen.add(name);
342
+ return resolveInterfaces(alias.type, aliases, interfaces, seen);
343
+ }
344
+
345
+ function memberName(name: ts.PropertyName, sf: ts.SourceFile): string {
346
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name))
347
+ return name.text;
348
+ if (ts.isComputedPropertyName(name) && ts.isStringLiteral(name.expression))
349
+ return name.expression.text;
350
+ return name.getText(sf);
351
+ }
352
+
353
+ function findModuleBlock(sf: ts.SourceFile): ts.ModuleBlock | undefined {
354
+ for (const stmt of sf.statements) {
355
+ if (ts.isModuleDeclaration(stmt) && stmt.body && ts.isModuleBlock(stmt.body)) {
356
+ return stmt.body;
357
+ }
358
+ }
359
+ return undefined;
360
+ }
361
+
362
+ function isExported(node: ts.HasModifiers): boolean {
363
+ return (ts.getCombinedModifierFlags(node as ts.Declaration) & ts.ModifierFlags.Export) !== 0;
364
+ }
365
+
366
+ /** The closest non-empty JSDoc summary text attached to a node, tags stripped. */
367
+ function jsDocSummary(node: ts.Node): string {
368
+ const comments = ts
369
+ .getJSDocCommentsAndTags(node)
370
+ .filter(ts.isJSDoc)
371
+ .map((d) => (ts.getTextOfJSDocComment(d.comment) ?? "").trim())
372
+ .filter((s) => s.length > 0);
373
+ return comments.at(-1) ?? "";
374
+ }
375
+
376
+ function briefOf(summary: string): string {
377
+ return summary.split("\n")[0]?.trim() ?? "";
378
+ }
379
+
380
+ // The verbatim first line of ts-defold's "definition stub" JSDoc marker.
381
+ const STUB_SUMMARY_MARKER = "This is a definition stub with incomplete or untested signatures.";
382
+
383
+ /** Whether a module summary is ts-defold's placeholder stub marker (no real docs). */
384
+ function isStubSummary(summary: string): boolean {
385
+ return summary.startsWith(STUB_SUMMARY_MARKER);
386
+ }
387
+
388
+ function paramDocMap(
389
+ decl: ts.FunctionDeclaration | ts.MethodSignature,
390
+ sf: ts.SourceFile,
391
+ ): Map<string, string> {
392
+ const out = new Map<string, string>();
393
+ for (const tag of ts.getAllJSDocTagsOfKind(decl, ts.SyntaxKind.JSDocParameterTag)) {
394
+ const paramTag = tag as ts.JSDocParameterTag;
395
+ const name = ts.isIdentifier(paramTag.name) ? paramTag.name.text : paramTag.name.getText(sf);
396
+ out.set(name, cleanDoc(ts.getTextOfJSDocComment(paramTag.comment)));
397
+ }
398
+ return out;
399
+ }
400
+
401
+ function returnDoc(decl: ts.FunctionDeclaration | ts.MethodSignature): string {
402
+ const [tag] = ts.getAllJSDocTagsOfKind(decl, ts.SyntaxKind.JSDocReturnTag);
403
+ return tag ? cleanDoc(ts.getTextOfJSDocComment(tag.comment)) : "";
404
+ }
405
+
406
+ function exampleText(decl: ts.FunctionDeclaration | ts.MethodSignature): string {
407
+ for (const tag of ts.getJSDocTags(decl)) {
408
+ if (tag.tagName.text === "example") {
409
+ return (ts.getTextOfJSDocComment(tag.comment) ?? "").trim();
410
+ }
411
+ }
412
+ return "";
413
+ }
414
+
415
+ /** Trim a JSDoc `@param`/`@returns` comment and drop a leading `-` delimiter. */
416
+ function cleanDoc(comment: string | undefined): string {
417
+ return (comment ?? "").trim().replace(/^-\s*/, "");
418
+ }