@defold-typescript/library-types 0.21.1 → 0.23.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.
Files changed (56) hide show
  1. package/api-doc/{bridge.bridge.json → bridge.json} +749 -844
  2. package/api-doc/decore.json +22 -22
  3. package/api-doc/druid.json +195 -1790
  4. package/api-doc/event.json +1074 -0
  5. package/api-doc/immutable.json +65 -0
  6. package/api-doc/lang.json +528 -0
  7. package/api-doc/{event.event.json → log.json} +96 -101
  8. package/api-doc/narrator.json +647 -0
  9. package/api-doc/proto.json +995 -0
  10. package/api-doc/saver.saver.json +713 -263
  11. package/api-doc/saver.storage.json +282 -52
  12. package/api-doc/squid.json +846 -0
  13. package/api-doc/tweener.json +277 -0
  14. package/generated/bridge.d.ts +468 -0
  15. package/generated/decore.d.ts +36 -36
  16. package/generated/druid.d.ts +137 -443
  17. package/generated/event.d.ts +318 -0
  18. package/generated/immutable.d.ts +13 -0
  19. package/generated/lang.d.ts +101 -0
  20. package/generated/log.d.ts +36 -0
  21. package/generated/narrator.d.ts +121 -0
  22. package/generated/proto.d.ts +146 -0
  23. package/generated/saver.saver.d.ts +287 -42
  24. package/generated/saver.storage.d.ts +77 -14
  25. package/generated/squid.d.ts +127 -0
  26. package/generated/tweener.d.ts +42 -0
  27. package/library-classification.json +0 -71
  28. package/library-targets.json +0 -66
  29. package/luals-targets.json +114 -0
  30. package/package.json +4 -33
  31. package/script-api-targets.json +15 -0
  32. package/scripts/__snapshots__/parse-luals.test.ts.snap +340 -75
  33. package/scripts/apply-luals-overrides.ts +63 -0
  34. package/scripts/emit-library-dts.ts +84 -3
  35. package/scripts/lower-api-doc.ts +48 -16
  36. package/scripts/luals-fidelity.ts +7 -0
  37. package/scripts/map-luals-types.ts +34 -1
  38. package/scripts/parse-luals.ts +423 -18
  39. package/scripts/sync-luals-types.ts +15 -1
  40. package/scripts/sync-script-api-types.ts +368 -0
  41. package/api-doc/immutable.immutable.json +0 -63
  42. package/api-doc/lang.lang.json +0 -411
  43. package/api-doc/log.log.json +0 -50
  44. package/api-doc/narrator.narrator.json +0 -150
  45. package/api-doc/proto.proto.json +0 -355
  46. package/api-doc/squid.squid.json +0 -660
  47. package/api-doc/tweener.tweener.json +0 -419
  48. package/generated/bridge.bridge.d.ts +0 -533
  49. package/generated/event.event.d.ts +0 -54
  50. package/generated/immutable.immutable.d.ts +0 -13
  51. package/generated/lang.lang.d.ts +0 -33
  52. package/generated/log.log.d.ts +0 -40
  53. package/generated/narrator.narrator.d.ts +0 -66
  54. package/generated/proto.proto.d.ts +0 -36
  55. package/generated/squid.squid.d.ts +0 -106
  56. package/generated/tweener.tweener.d.ts +0 -151
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Per-target corrections applied to a parsed `LibraryModel` after the merge, for the
3
+ * cases where upstream LuaLS annotations diverge from the library's runtime and the
4
+ * fixtures freeze the annotation verbatim (so it is not hand-patchable in the fixture
5
+ * or the emitted `.d.ts`). The two shapes covered are a module function whose trailing
6
+ * parameter is runtime-optional despite a non-`|nil` `@param`, and an interface method
7
+ * whose `@return` omits an alternative arm. Every named target must exist — a missing
8
+ * function, param, interface, or method throws naming the absent key, mirroring
9
+ * `buildTargetModel`'s loud-fail on an absent `ownFile`, so a stale override never
10
+ * degrades into a silent no-op.
11
+ */
12
+
13
+ import type { LibraryModel } from "./parse-luals";
14
+
15
+ export interface AnnotationOverrides {
16
+ moduleFunctions?: Record<string, { params?: Record<string, { optional?: boolean }> }>;
17
+ interfaces?: Record<string, { methods?: Record<string, { return?: string }> }>;
18
+ }
19
+
20
+ export function applyAnnotationOverrides(
21
+ model: LibraryModel,
22
+ overrides: AnnotationOverrides,
23
+ ): LibraryModel {
24
+ for (const [fnName, fnOverride] of Object.entries(overrides.moduleFunctions ?? {})) {
25
+ const fn = model.moduleFunctions.find((f) => f.name === fnName);
26
+ if (!fn) {
27
+ throw new Error(
28
+ `applyAnnotationOverrides: module function "${fnName}" is absent from the model.`,
29
+ );
30
+ }
31
+ for (const [paramName, paramOverride] of Object.entries(fnOverride.params ?? {})) {
32
+ const param = fn.params.find((p) => p.name === paramName);
33
+ if (!param) {
34
+ throw new Error(
35
+ `applyAnnotationOverrides: param "${paramName}" of module function "${fnName}" is absent from the model.`,
36
+ );
37
+ }
38
+ if (paramOverride.optional) param.isOptional = true;
39
+ }
40
+ }
41
+ for (const [ifaceName, ifaceOverride] of Object.entries(overrides.interfaces ?? {})) {
42
+ const iface = model.interfaces.find((i) => i.name === ifaceName);
43
+ if (!iface) {
44
+ throw new Error(
45
+ `applyAnnotationOverrides: interface "${ifaceName}" is absent from the model.`,
46
+ );
47
+ }
48
+ for (const [methodName, methodOverride] of Object.entries(ifaceOverride.methods ?? {})) {
49
+ const method = iface.methods.find((m) => m.name === methodName);
50
+ if (!method) {
51
+ throw new Error(
52
+ `applyAnnotationOverrides: method "${methodName}" of interface "${ifaceName}" is absent from the model.`,
53
+ );
54
+ }
55
+ if (methodOverride.return !== undefined) {
56
+ method.returns = [
57
+ { name: "", types: [methodOverride.return], doc: "", isOptional: false, isVararg: false },
58
+ ];
59
+ }
60
+ }
61
+ }
62
+ return model;
63
+ }
@@ -27,12 +27,14 @@ import {
27
27
  } from "@defold-typescript/types";
28
28
  import {
29
29
  type MapContext,
30
+ mapLualsCallSignature,
30
31
  mapLualsType,
31
32
  matchSelfHookField,
32
33
  scopeGenerics,
33
34
  } from "./map-luals-types";
34
35
  import type {
35
36
  LibraryAlias,
37
+ LibraryField,
36
38
  LibraryGeneric,
37
39
  LibraryInterface,
38
40
  LibraryMethod,
@@ -47,6 +49,20 @@ export interface EmitLibraryOptions {
47
49
 
48
50
  const INDENT = "\t";
49
51
 
52
+ // A member with an explicit non-public visibility is internal surface; keep only
53
+ // members with no visibility or an explicit `public`, mirroring how LuaLS hides
54
+ // `private`/`protected`/`package` (and, for methods, `local`) from a class's public
55
+ // shape. Shared by all three consumers — the emitter, the api-doc lowering, and the
56
+ // fidelity report — so the declaration, documentation, and coverage surfaces
57
+ // describe one identical public member set.
58
+ export function isPublicField(field: LibraryField): boolean {
59
+ return field.visibility === undefined || field.visibility === "public";
60
+ }
61
+
62
+ export function isPublicMethod(method: LibraryMethod): boolean {
63
+ return method.visibility === undefined || method.visibility === "public";
64
+ }
65
+
50
66
  /** A model type name (dotted like `druid.button`) reduced to a legal TS identifier. */
51
67
  export function sanitizeTypeName(name: string): string {
52
68
  const cleaned = name.replace(/[^A-Za-z0-9_$]/g, "_");
@@ -113,14 +129,40 @@ function renderExtends(
113
129
  return parents.length > 0 ? ` extends ${parents.join(", ")}` : "";
114
130
  }
115
131
 
132
+ /**
133
+ * Which non-vararg params may be emitted with a trailing `?`. Walking right-to-left,
134
+ * a param is emitted-optional iff it is itself omittable (`isOptional || isNilable`)
135
+ * and every later non-vararg param is too — a required param blocks every param to
136
+ * its left, since TS forbids a required parameter after an optional one. A trailing
137
+ * vararg (rendered `...args`, never `?`) does not break the preceding run. Exported so
138
+ * the api-doc lowering keys `is_optional` off the identical rule, keeping the rendered
139
+ * `/api` signature byte-consistent with the `.d.ts`.
140
+ */
141
+ export function paramOptionalFlags(params: readonly LibraryParam[]): boolean[] {
142
+ const flags = new Array<boolean>(params.length).fill(false);
143
+ let laterAllOptional = true;
144
+ for (let i = params.length - 1; i >= 0; i--) {
145
+ const param = params[i] as LibraryParam;
146
+ if (param.isVararg) continue;
147
+ const selfOptional = param.isOptional || param.isNilable === true;
148
+ if (laterAllOptional && selfOptional) {
149
+ flags[i] = true;
150
+ } else {
151
+ laterAllOptional = false;
152
+ }
153
+ }
154
+ return flags;
155
+ }
156
+
116
157
  function renderParams(params: readonly LibraryParam[], ctx: MapContext): string {
158
+ const optionalFlags = paramOptionalFlags(params);
117
159
  return params
118
160
  .map((param, index) => {
119
161
  const mapped = mapTypes(param.types, ctx);
120
162
  if (param.isVararg) {
121
163
  return `...args: ${varargElementType(mapped)}`;
122
164
  }
123
- const optional = param.isOptional ? "?" : "";
165
+ const optional = optionalFlags[index] ? "?" : "";
124
166
  return `${safeParamName(param.name, index)}${optional}: ${mapped}`;
125
167
  })
126
168
  .join(", ");
@@ -156,6 +198,7 @@ function renderInterface(
156
198
  lines.push(`${INDENT}interface ${sanitizeTypeName(iface.name)}${params}${extendsClause} {`);
157
199
  const body = INDENT + INDENT;
158
200
  for (const field of iface.fields) {
201
+ if (!isPublicField(field)) continue;
159
202
  // A base's self-receiving lifecycle hook is emitted as a permissive optional
160
203
  // method (`name?(...args: any[]): ret`) so a concrete subinterface's refined
161
204
  // override stays assignable under `extends`; strict function-field variance
@@ -172,6 +215,7 @@ function renderInterface(
172
215
  lines.push(`${body}${memberKey(field.name)}${optional}: ${mapTypes(field.types, ifaceCtx)};`);
173
216
  }
174
217
  for (const method of iface.methods) {
218
+ if (!isPublicMethod(method)) continue;
175
219
  pushDoc(lines, method.brief, body);
176
220
  const methodCtx = scopeGenerics(ifaceCtx, method.generics);
177
221
  const methodParams = renderGenericParams(method.generics, methodCtx);
@@ -182,10 +226,36 @@ function renderInterface(
182
226
  )}): ${renderReturn(method.returns, methodCtx)};`,
183
227
  );
184
228
  }
229
+ // A class `@overload fun(...)` becomes an interface call signature, making the
230
+ // instance callable (`event(...)`, `promise(...)`) the way the metatable's `__call`
231
+ // does at runtime. Rendered after the members with its own doc comment.
232
+ for (const overload of iface.overloads ?? []) {
233
+ pushDoc(lines, overload.doc, body);
234
+ lines.push(`${body}${mapLualsCallSignature(overload.type, ifaceCtx).ts};`);
235
+ }
185
236
  lines.push(`${INDENT}}`);
186
237
  return lines;
187
238
  }
188
239
 
240
+ /**
241
+ * The module object's public fields rendered as module-level `export const`s — the
242
+ * constants a returned module table (`return Squid`) carries. Emitted in place of the
243
+ * standalone interface, since consumers reach them as `squid.TRACE`, not `Squid.TRACE`.
244
+ */
245
+ function renderModuleConstants(iface: LibraryInterface, ctx: MapContext): string[] {
246
+ const lines: string[] = [];
247
+ for (const field of iface.fields) {
248
+ if (!isPublicField(field)) continue;
249
+ pushDoc(lines, field.doc, INDENT);
250
+ const name =
251
+ TS_IDENTIFIER.test(field.name) && !TS_RESERVED_NAMES.has(field.name)
252
+ ? field.name
253
+ : sanitizeTypeName(field.name);
254
+ lines.push(`${INDENT}export const ${name}: ${mapTypes(field.types, ctx)};`);
255
+ }
256
+ return lines;
257
+ }
258
+
189
259
  function renderModuleFunction(fn: LibraryMethod, ctx: MapContext): string[] {
190
260
  const lines: string[] = [];
191
261
  pushDoc(lines, fn.brief, INDENT);
@@ -245,8 +315,19 @@ export function emitLibraryDeclarations(model: LibraryModel, opts: EmitLibraryOp
245
315
  const interfaceNames = new Set(model.interfaces.map((iface) => iface.name));
246
316
  const out: string[] = ["/** @noResolution */", `declare module '${opts.moduleId}' {`];
247
317
  for (const alias of model.aliases) out.push(...renderAlias(alias, ctx));
248
- for (const iface of model.interfaces) out.push(...renderInterface(iface, ctx, interfaceNames));
249
- for (const fn of model.moduleFunctions) out.push(...renderModuleFunction(fn, ctx));
318
+ for (const iface of model.interfaces) {
319
+ // The module object's fields become module-level `export const`s (below), so it is
320
+ // not also rendered as a standalone interface.
321
+ if (iface.name === model.moduleObject) {
322
+ out.push(...renderModuleConstants(iface, ctx));
323
+ continue;
324
+ }
325
+ out.push(...renderInterface(iface, ctx, interfaceNames));
326
+ }
327
+ for (const fn of model.moduleFunctions) {
328
+ if (!isPublicMethod(fn)) continue;
329
+ out.push(...renderModuleFunction(fn, ctx));
330
+ }
250
331
  out.push("}");
251
332
  return `${out.join("\n")}\n`;
252
333
  }
@@ -15,34 +15,36 @@
15
15
 
16
16
  import {
17
17
  buildModelContext,
18
+ isPublicField,
19
+ isPublicMethod,
18
20
  mapTypes,
21
+ paramOptionalFlags,
19
22
  renderGenericParams,
20
23
  sanitizeTypeName,
21
24
  } from "./emit-library-dts";
22
25
  import { type MapContext, scopeGenerics } from "./map-luals-types";
23
26
  import type { LibraryField, LibraryMethod, LibraryModel, LibraryParam } from "./parse-luals";
24
27
 
25
- // A field with an explicit non-public visibility is internal surface; keep only
26
- // fields with no visibility or an explicit `public`, mirroring how LuaLS hides
27
- // `private`/`protected`/`package` members from a class's public shape.
28
- function isPublicField(field: LibraryField): boolean {
29
- return field.visibility === undefined || field.visibility === "public";
30
- }
31
-
32
28
  // Each type token is mapped independently (one mapped TS string per token) so the
33
29
  // ref-doc `types` array stays token-per-slot the way engine ref-docs are shaped.
34
30
  function mapTokens(tokens: readonly string[], ctx: MapContext): string[] {
35
31
  return tokens.map((token) => mapTypes([token], ctx));
36
32
  }
37
33
 
38
- function parameterElement(param: LibraryParam, ctx: MapContext): Record<string, unknown> {
34
+ function parameterElement(
35
+ param: LibraryParam,
36
+ isOptional: boolean,
37
+ ctx: MapContext,
38
+ ): Record<string, unknown> {
39
39
  // A vararg's element type stays a plain mapped token here; the renderer arrayifies
40
40
  // it (`...args: T[]`) from the `is_vararg` flag, keeping the JSON structurally honest.
41
+ // `isOptional` comes from the emitter's trailing-run rule (paramOptionalFlags), not
42
+ // `param.isOptional` alone, so the `/api` signature matches the emitted `.d.ts`.
41
43
  return {
42
44
  name: param.isVararg ? "...args" : param.name,
43
45
  doc: param.doc,
44
46
  types: mapTokens(param.types, ctx),
45
- is_optional: param.isOptional ? "True" : "False",
47
+ is_optional: isOptional ? "True" : "False",
46
48
  is_vararg: param.isVararg ? "True" : "False",
47
49
  };
48
50
  }
@@ -54,13 +56,16 @@ function returnElement(ret: LibraryParam, ctx: MapContext): Record<string, unkno
54
56
  function functionElement(method: LibraryMethod, ctx: MapContext): Record<string, unknown> {
55
57
  const fnCtx = scopeGenerics(ctx, method.generics);
56
58
  const generics = renderGenericParams(method.generics, fnCtx);
59
+ const optionalFlags = paramOptionalFlags(method.params);
57
60
  return {
58
61
  type: "FUNCTION",
59
62
  name: method.name,
60
63
  brief: method.brief,
61
64
  description: method.brief,
62
65
  ...(generics !== "" ? { generics } : {}),
63
- parameters: method.params.map((param) => parameterElement(param, fnCtx)),
66
+ parameters: method.params.map((param, index) =>
67
+ parameterElement(param, optionalFlags[index] ?? false, fnCtx),
68
+ ),
64
69
  returnvalues: method.returns.map((ret) => returnElement(ret, fnCtx)),
65
70
  };
66
71
  }
@@ -74,6 +79,18 @@ function propertyElement(field: LibraryField, ctx: MapContext): Record<string, u
74
79
  };
75
80
  }
76
81
 
82
+ // A module object's field lowered as a top-level `VARIABLE` — the ref-doc shape for a
83
+ // module constant (`squid.TRACE`), matching the emitter's module-level `export const`.
84
+ function variableElement(field: LibraryField, ctx: MapContext): Record<string, unknown> {
85
+ return {
86
+ type: "VARIABLE",
87
+ name: field.name,
88
+ brief: field.doc,
89
+ description: field.doc,
90
+ types: mapTokens(field.types, ctx),
91
+ };
92
+ }
93
+
77
94
  export function lowerLibraryModel(
78
95
  model: LibraryModel,
79
96
  { namespace, typeRenames }: { namespace: string; typeRenames?: Record<string, string> },
@@ -82,12 +99,24 @@ export function lowerLibraryModel(
82
99
  const elements: Record<string, unknown>[] = [];
83
100
 
84
101
  for (const fn of model.moduleFunctions) {
102
+ if (!isPublicMethod(fn)) continue;
85
103
  elements.push(functionElement(fn, ctx));
86
104
  }
87
105
 
88
106
  for (const iface of model.interfaces) {
89
107
  const ifaceCtx = scopeGenerics(ctx, iface.generics);
90
- const functions = iface.methods.map((method) => functionElement(method, ifaceCtx));
108
+ // The module object's public fields are module-level constants, lowered as top-level
109
+ // VARIABLE elements rather than a TYPEDEF named after the class.
110
+ if (iface.name === model.moduleObject) {
111
+ for (const field of iface.fields) {
112
+ if (!isPublicField(field)) continue;
113
+ elements.push(variableElement(field, ifaceCtx));
114
+ }
115
+ continue;
116
+ }
117
+ const functions = iface.methods
118
+ .filter(isPublicMethod)
119
+ .map((method) => functionElement(method, ifaceCtx));
91
120
  const properties = iface.fields
92
121
  .filter(isPublicField)
93
122
  .map((field) => propertyElement(field, ifaceCtx));
@@ -103,11 +132,14 @@ export function lowerLibraryModel(
103
132
  elements.push({ type: "TYPEDEF", name: sanitizeTypeName(alias.name) });
104
133
  }
105
134
 
106
- // The module's own `@class` (named for the namespace, e.g. `@class druid`)
107
- // carries the library's summary; use it as the page description so a
108
- // LuaLS-sourced library reads with an intro like every other `/api` page,
109
- // rather than opening on a bare provenance block. `brief` is its first line.
110
- const moduleClass = model.interfaces.find((iface) => iface.name === namespace);
135
+ // The module's own `@class` carries the library's summary; use it as the page
136
+ // description so a LuaLS-sourced library reads with an intro like every other `/api`
137
+ // page, rather than opening on a bare provenance block. `brief` is its first line.
138
+ // A tracked `moduleObject` names it directly (squid's `Squid` != namespace `squid`);
139
+ // otherwise fall back to the class named for the namespace (`@class druid`).
140
+ const moduleClass = model.interfaces.find(
141
+ (iface) => iface.name === (model.moduleObject ?? namespace),
142
+ );
111
143
  const description = moduleClass?.brief ?? "";
112
144
  const brief = description.split("\n")[0] ?? "";
113
145
 
@@ -10,6 +10,7 @@
10
10
  * sorted-unique token list so the gap is visible instead of silent.
11
11
  */
12
12
 
13
+ import { isPublicField, isPublicMethod } from "./emit-library-dts";
13
14
  import { type MapContext, mapLualsType, scopeGenerics } from "./map-luals-types";
14
15
  import type { LibraryGeneric, LibraryModel } from "./parse-luals";
15
16
 
@@ -80,12 +81,17 @@ export function buildFidelityReport(
80
81
  );
81
82
  }
82
83
  mapConstraints(iface.generics, ifaceCtx);
84
+ // A class `@overload fun(...)` is emitted as a call signature, so its type token
85
+ // counts toward coverage exactly like a field or method type.
86
+ for (const overload of iface.overloads ?? []) mapTokens([overload.type], ifaceCtx);
83
87
  for (const field of iface.fields) {
88
+ if (!isPublicField(field)) continue;
84
89
  totalMembers++;
85
90
  if (undocumented(field.doc)) undocumentedMembers++;
86
91
  mapTokens(field.types, ifaceCtx);
87
92
  }
88
93
  for (const method of iface.methods) {
94
+ if (!isPublicMethod(method)) continue;
89
95
  totalMembers++;
90
96
  if (undocumented(method.brief)) undocumentedMembers++;
91
97
  const methodCtx = scopeGenerics(ifaceCtx, method.generics);
@@ -96,6 +102,7 @@ export function buildFidelityReport(
96
102
  }
97
103
 
98
104
  for (const fn of model.moduleFunctions) {
105
+ if (!isPublicMethod(fn)) continue;
99
106
  totalMembers++;
100
107
  if (undocumented(fn.brief)) undocumentedMembers++;
101
108
  const fnCtx = scopeGenerics(ctx, fn.generics);
@@ -126,7 +126,17 @@ function needsArrayParens(tsExpr: string): boolean {
126
126
  );
127
127
  }
128
128
 
129
- function mapFunction(token: string, ctx: MapContext, unknowns: string[]): string {
129
+ /**
130
+ * The mapped `(params)` list and `ret` type of a `fun(...)` token, shared by the
131
+ * arrow-form `mapFunction` and the colon-return `mapLualsCallSignature`. The only
132
+ * difference between the two consumers is the separator (`=>` vs `:`), so both the
133
+ * param handling (typed/untyped/vararg) and the single/multi-return logic live here.
134
+ */
135
+ function functionParts(
136
+ token: string,
137
+ ctx: MapContext,
138
+ unknowns: string[],
139
+ ): { paramList: string; ret: string } {
130
140
  const open = token.indexOf("(");
131
141
  const close = matchBracket(token, open);
132
142
  const paramsStr = token.slice(open + 1, close).trim();
@@ -171,6 +181,11 @@ function mapFunction(token: string, ctx: MapContext, unknowns: string[]): string
171
181
  ret = `LuaMultiReturn<[${inner}]>`;
172
182
  }
173
183
  }
184
+ return { paramList, ret };
185
+ }
186
+
187
+ function mapFunction(token: string, ctx: MapContext, unknowns: string[]): string {
188
+ const { paramList, ret } = functionParts(token, ctx, unknowns);
174
189
  return `(${paramList}) => ${ret}`;
175
190
  }
176
191
 
@@ -275,6 +290,24 @@ export function mapLualsType(token: string, ctx: MapContext): MapResult {
275
290
  return { ts, unknowns };
276
291
  }
277
292
 
293
+ /**
294
+ * Map a `fun(...)` token (a class `@overload`) to a TypeScript **call signature** —
295
+ * the colon-return form `(params): ret` an interface uses to become callable, not
296
+ * the `=>` arrow a field/param function type takes. Shares the exact param/return
297
+ * computation as `mapFunction`, so nested callback params and multi-returns map
298
+ * identically. Throws on a non-`fun` token; the parser only ever records `fun(...)`
299
+ * overloads, so this guards a programming error rather than user input.
300
+ */
301
+ export function mapLualsCallSignature(token: string, ctx: MapContext): MapResult {
302
+ const trimmed = token.trim();
303
+ if (!/^fun\s*\(/.test(trimmed)) {
304
+ throw new Error(`mapLualsCallSignature: expected a "fun(...)" token, got "${token}".`);
305
+ }
306
+ const unknowns: string[] = [];
307
+ const { paramList, ret } = functionParts(trimmed, ctx, unknowns);
308
+ return { ts: `(${paramList}): ${ret}`, unknowns };
309
+ }
310
+
278
311
  /**
279
312
  * When `types` is exactly one `fun(self: <selfTypeName>, ...)` token — optionally
280
313
  * unioned with `nil` — whose first parameter is `self` typed as the enclosing