@defold-typescript/types 0.1.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-targets.json +84 -0
- package/generated/b2d.d.ts +13 -0
- package/generated/buffer.d.ts +25 -0
- package/generated/builtin-messages.d.ts +42 -0
- package/generated/camera.d.ts +7 -0
- package/generated/collectionfactory.d.ts +17 -0
- package/generated/collectionproxy.d.ts +14 -0
- package/generated/crash.d.ts +31 -0
- package/generated/factory.d.ts +17 -0
- package/generated/go.d.ts +96 -0
- package/generated/graphics.d.ts +115 -0
- package/generated/gui.d.ts +245 -0
- package/generated/http.d.ts +8 -0
- package/generated/iac.d.ts +8 -0
- package/generated/iap.d.ts +16 -0
- package/generated/image.d.ts +16 -0
- package/generated/json.d.ts +11 -0
- package/generated/kinds/gui-script.d.ts +39 -0
- package/generated/kinds/render-script.d.ts +39 -0
- package/generated/kinds/script.d.ts +38 -0
- package/generated/label.d.ts +23 -0
- package/generated/liveupdate.d.ts +23 -0
- package/generated/model.d.ts +23 -0
- package/generated/msg.d.ts +12 -0
- package/generated/particlefx.d.ts +22 -0
- package/generated/physics.d.ts +47 -0
- package/generated/profiler.d.ts +28 -0
- package/generated/push.d.ts +15 -0
- package/generated/render.d.ts +55 -0
- package/generated/resource.d.ts +33 -0
- package/generated/socket.d.ts +25 -0
- package/generated/sound.d.ts +28 -0
- package/generated/sprite.d.ts +23 -0
- package/generated/sys.d.ts +37 -0
- package/generated/tilemap.d.ts +24 -0
- package/generated/timer.d.ts +12 -0
- package/generated/vmath.d.ts +63 -0
- package/generated/webview.d.ts +15 -0
- package/generated/window.d.ts +28 -0
- package/generated/zlib.d.ts +9 -0
- package/index.d.ts +63 -0
- package/package.json +46 -0
- package/scripts/doc-source.ts +100 -0
- package/scripts/fidelity-audit.ts +311 -0
- package/scripts/fidelity-baseline.json +282 -0
- package/scripts/materialize-version.ts +51 -0
- package/scripts/regen.ts +294 -0
- package/scripts/sync-api-docs.ts +375 -0
- package/src/api-doc.ts +168 -0
- package/src/core-types.ts +121 -0
- package/src/emit-dts.ts +754 -0
- package/src/emit-messages.ts +148 -0
- package/src/go-overloads.d.ts +35 -0
- package/src/index.ts +24 -0
- package/src/lifecycle.ts +81 -0
- package/src/msg-overloads.d.ts +21 -0
- package/src/publish-dts.ts +33 -0
- package/src/script-api.ts +95 -0
package/src/emit-dts.ts
ADDED
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ApiConstant,
|
|
3
|
+
ApiFunction,
|
|
4
|
+
ApiModule,
|
|
5
|
+
ApiParameter,
|
|
6
|
+
ApiProperty,
|
|
7
|
+
ApiVariable,
|
|
8
|
+
} from "./api-doc";
|
|
9
|
+
import { DEFOLD_TYPE_MAP } from "./core-types";
|
|
10
|
+
|
|
11
|
+
export interface EmitOptions {
|
|
12
|
+
mapType?: (defoldType: string) => string;
|
|
13
|
+
// Constant FQNs from *other* modules, so a foreign token like
|
|
14
|
+
// `graphics.BUFFER_TYPE_COLOR0_BIT` used as a param type inside `render`
|
|
15
|
+
// brands to the same FQN-keyed type its owning module's `const` emits,
|
|
16
|
+
// instead of widening to `unknown`.
|
|
17
|
+
knownConstantFqns?: ReadonlySet<string>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const TS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
21
|
+
const INDENT = " ";
|
|
22
|
+
|
|
23
|
+
// TS reserved words that pass api-doc's identifier check but cannot be emitted
|
|
24
|
+
// directly inside `declare namespace { function/const <name> }` — `function
|
|
25
|
+
// delete(...)` / `const null:` are hard syntax errors. They are recovered as an
|
|
26
|
+
// internal `_<name>` declaration re-exported under the reserved name via
|
|
27
|
+
// `export { _<name> as <name> }`, the only form that is both TS-legal and keeps
|
|
28
|
+
// the engine's real call name. `regen` imports this set and lets these members
|
|
29
|
+
// flow through to the emitter instead of dropping them.
|
|
30
|
+
export const TS_RESERVED_NAMES = new Set([
|
|
31
|
+
"delete",
|
|
32
|
+
"new",
|
|
33
|
+
"class",
|
|
34
|
+
"function",
|
|
35
|
+
"return",
|
|
36
|
+
"if",
|
|
37
|
+
"else",
|
|
38
|
+
"for",
|
|
39
|
+
"while",
|
|
40
|
+
"do",
|
|
41
|
+
"switch",
|
|
42
|
+
"case",
|
|
43
|
+
"break",
|
|
44
|
+
"continue",
|
|
45
|
+
"var",
|
|
46
|
+
"let",
|
|
47
|
+
"const",
|
|
48
|
+
"try",
|
|
49
|
+
"catch",
|
|
50
|
+
"finally",
|
|
51
|
+
"throw",
|
|
52
|
+
"typeof",
|
|
53
|
+
"instanceof",
|
|
54
|
+
"in",
|
|
55
|
+
"void",
|
|
56
|
+
"yield",
|
|
57
|
+
"await",
|
|
58
|
+
"null",
|
|
59
|
+
"true",
|
|
60
|
+
"false",
|
|
61
|
+
"super",
|
|
62
|
+
"this",
|
|
63
|
+
"import",
|
|
64
|
+
"export",
|
|
65
|
+
"default",
|
|
66
|
+
"with",
|
|
67
|
+
"debugger",
|
|
68
|
+
"extends",
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
// Element names whose `table` slot is a genuinely-arbitrary lua table by design
|
|
72
|
+
// — the serialization/JSON passthrough functions. Their emitted
|
|
73
|
+
// `Record<string | number, unknown>` is the faithful "any lua table" type, not a
|
|
74
|
+
// `recordTables` fidelity loss, so the audit consults this set to avoid counting
|
|
75
|
+
// them. A new ref-doc function with an opaque table must be added here
|
|
76
|
+
// deliberately; until then it surfaces under `recordTables` as a visible signal.
|
|
77
|
+
export const ARBITRARY_TABLE_SLOTS = new Set([
|
|
78
|
+
"json.encode",
|
|
79
|
+
"json.decode",
|
|
80
|
+
"sys.save",
|
|
81
|
+
"sys.load",
|
|
82
|
+
"sys.serialize",
|
|
83
|
+
"sys.deserialize",
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
// Element names whose `table` slot is a prose-only `a table mapping X to Y`
|
|
87
|
+
// shape the field-list parser cannot read, but whose key/value a human curated
|
|
88
|
+
// from the doc. Emitted as `LuaMap<K, V>` because the key is a branded `Hash`,
|
|
89
|
+
// illegal in a TS index signature (`string | number | symbol` only); `LuaMap`
|
|
90
|
+
// is the TSTL non-string-key Lua-table idiom and resolves the same way the
|
|
91
|
+
// already-emitted `LuaMultiReturn` does. The audit consults this map too, so the
|
|
92
|
+
// gate and the emitted surface stay coupled; a slot not curated here still
|
|
93
|
+
// surfaces under `recordTables` until a human adds it.
|
|
94
|
+
export const MAPPING_TABLE_SLOTS: ReadonlyMap<string, { key: string; value: string }> = new Map([
|
|
95
|
+
["gui.clone_tree", { key: "hash", value: "node" }],
|
|
96
|
+
["gui.get_tree", { key: "hash", value: "node" }],
|
|
97
|
+
["gui.get_layouts", { key: "hash", value: "vector3" }],
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
// Element names whose `table` slot is a prose-only `array/list/table of <T>` shape
|
|
101
|
+
// the field-list parser cannot read, but whose element type a human curated from
|
|
102
|
+
// the doc. The value is a single element token (`T[]`) or a token list when the
|
|
103
|
+
// element is itself a union — `go.delete`'s "table of id's" is the id union
|
|
104
|
+
// `string | hash | url`, emitted `(string | Hash | Url)[]`. Emitted as a plain
|
|
105
|
+
// array, never `LuaMap`: an array element carries no illegal-index-key problem so
|
|
106
|
+
// no TSTL wiring is needed. The audit consults this map too, so the gate and the
|
|
107
|
+
// emitted surface stay coupled; a slot not curated here still surfaces under
|
|
108
|
+
// `recordTables` until a human adds it. Each function here has exactly one `table`
|
|
109
|
+
// slot, so the element name keys it unambiguously.
|
|
110
|
+
export const HOMOGENEOUS_ARRAY_SLOTS: ReadonlyMap<string, string | readonly string[]> = new Map<
|
|
111
|
+
string,
|
|
112
|
+
string | readonly string[]
|
|
113
|
+
>([
|
|
114
|
+
["buffer.set_metadata", "number"],
|
|
115
|
+
["buffer.get_metadata", "number"],
|
|
116
|
+
["vmath.vector", "number"],
|
|
117
|
+
["sound.get_groups", "hash"],
|
|
118
|
+
["iap.list", "string"],
|
|
119
|
+
["go.delete", ["string", "hash", "url"]],
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Recover a Defold `function(...)` callback-signature token into a TypeScript
|
|
124
|
+
* function type. The token carries parameter names but no inner types, so each
|
|
125
|
+
* param is typed `unknown`; arity and names are preserved so the callback stays
|
|
126
|
+
* self-documenting on hover. Return is `void` — Defold callbacks are
|
|
127
|
+
* side-effecting and the engine ignores any returned value. A param that is not
|
|
128
|
+
* a valid TS identifier (e.g. the nested `function(function())`) is named
|
|
129
|
+
* positionally `argN`. Returns `null` for any non-`function(...)` token, which
|
|
130
|
+
* is the scope boundary both the emitter and the audit key off.
|
|
131
|
+
*
|
|
132
|
+
* Lives here rather than in `core-types.ts` because that module is fed verbatim
|
|
133
|
+
* to typescript-to-lua as ambient source, and TSTL rejects regex literals.
|
|
134
|
+
*/
|
|
135
|
+
export function recoverCallbackSignature(token: string): string | null {
|
|
136
|
+
const match = /^function\((.*)\)$/.exec(token);
|
|
137
|
+
if (match === null) return null;
|
|
138
|
+
const body = match[1] ?? "";
|
|
139
|
+
const raw = body.trim() === "" ? [] : body.split(",");
|
|
140
|
+
const named = raw.map((param, index) => {
|
|
141
|
+
const trimmed = param.trim();
|
|
142
|
+
return TS_IDENTIFIER.test(trimmed) ? trimmed : `arg${index}`;
|
|
143
|
+
});
|
|
144
|
+
return `(${named.map((n) => `${n}: unknown`).join(", ")}) => void`;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// The trailing `([^<]*)` captures the plain prose immediately after the type
|
|
148
|
+
// span up to the next tag. The "is this a list?" signal ("a list of …") lives in
|
|
149
|
+
// that prose, never in the field name or type token.
|
|
150
|
+
const TABLE_FIELD =
|
|
151
|
+
/<dt>\s*<code>([^<]+)<\/code>\s*<\/dt>\s*<dd>\s*<span class="type">([^<]+)<\/span>([^<]*)/g;
|
|
152
|
+
const LIST_PROSE = /\ba list of\b/i;
|
|
153
|
+
// The slot-level array signal ("an array of …" / "a list of …") lives in the
|
|
154
|
+
// prose preceding the field list, never in a field's own `<dd>` (that is the
|
|
155
|
+
// per-field `LIST_PROSE` path). `isSlotLevelList` therefore tests only the
|
|
156
|
+
// substring before the first `<dl>`/`<ul>`/`<li>`, so a field-internal list
|
|
157
|
+
// marker can never wrap the whole slot.
|
|
158
|
+
export const SLOT_LEVEL_LIST_PROSE = /\b(an?\s+array of|a\s+list of)\b/i;
|
|
159
|
+
function isSlotLevelList(doc: string): boolean {
|
|
160
|
+
const prefix = doc.split(/<dl>|<ul>|<li>/i)[0] ?? "";
|
|
161
|
+
return SLOT_LEVEL_LIST_PROSE.test(prefix);
|
|
162
|
+
}
|
|
163
|
+
const FLATTENED_TABLE = /<li>\s*<dl>/;
|
|
164
|
+
// A number-list slot's element type is read from the brace form a "a list of …"
|
|
165
|
+
// `<dd>` ends with: `in the form {px0, py0, ..., pxn, pyn}` / `{i0, i1, ..., in}`.
|
|
166
|
+
// Every comma-separated token must be a numeric placeholder — an optional letter
|
|
167
|
+
// axis prefix then a digit (`px0`, `i2`, `0`) or the symbolic nth-index `pxn`/`un`
|
|
168
|
+
// — or an ellipsis; at least one must carry a digit. A brace body with quotes,
|
|
169
|
+
// nested braces, or plain word identifiers is not a number list and stays `Record`.
|
|
170
|
+
const NUMBER_LIST_BRACE = /\bin the form (?:of\s+)?\{([^}]*)\}/i;
|
|
171
|
+
const NUMBER_LIST_TOKEN = /^(?:[a-z]*\d+|[a-z]+n|\.\.\.|…)$/i;
|
|
172
|
+
function isNumberListForm(prose: string): boolean {
|
|
173
|
+
const brace = NUMBER_LIST_BRACE.exec(prose);
|
|
174
|
+
if (brace === null) return false;
|
|
175
|
+
const body = brace[1] ?? "";
|
|
176
|
+
if (/["<{]/.test(body)) return false;
|
|
177
|
+
const tokens = body
|
|
178
|
+
.split(",")
|
|
179
|
+
.map((t) => t.trim())
|
|
180
|
+
.filter((t) => t.length > 0);
|
|
181
|
+
if (tokens.length === 0) return false;
|
|
182
|
+
let numeric = 0;
|
|
183
|
+
for (const token of tokens) {
|
|
184
|
+
if (!NUMBER_LIST_TOKEN.test(token)) return false;
|
|
185
|
+
if (/\d/.test(token)) numeric += 1;
|
|
186
|
+
}
|
|
187
|
+
return numeric > 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// The `<ul>` typed-field shape lists a field as `<span class="type">T</span>
|
|
191
|
+
// <code>name</code>` — type first, name second, the reverse of the `<dl>` form.
|
|
192
|
+
const UL_TABLE_FIELD = /<li>\s*<span class="type">([^<]+)<\/span>\s*<code>([^<]+)<\/code>/g;
|
|
193
|
+
const DASH_TABLE_FIELD =
|
|
194
|
+
/^\s*-\s*(?:<code>([^<]+)<\/code>|([A-Za-z_$][A-Za-z0-9_$]*))\s*<span class="type">([^<]+)<\/span>/gm;
|
|
195
|
+
// The code-dash shape names the field first inside a `<code>` and puts the dash
|
|
196
|
+
// between the name and its type span: `<code>NAME</code> - <span
|
|
197
|
+
// class="type">T</span>` (`sys.open_url`'s `target`). Untyped value lists in the
|
|
198
|
+
// same doc (`<code>_self</code> - prose`) carry no type span and never match.
|
|
199
|
+
const CODE_DASH_TABLE_FIELD = /<code>([^<]+)<\/code>\s*-\s*<span class="type">([^<]+)<\/span>/g;
|
|
200
|
+
// A cross-reference table doc carries no inline fields, only a pointer to a
|
|
201
|
+
// sibling element: `See <a href="…#<element.name>">…`. The capture is the
|
|
202
|
+
// fragment after `#` — the referenced element's full name. Non-global: a single
|
|
203
|
+
// match suffices and keeps `.exec` stateless.
|
|
204
|
+
const CROSS_REF_TABLE = /See\s+<a href="[^"]*#([^"]+)">/;
|
|
205
|
+
// A doc enumerating its own *typed* fields inline (`<dt>` or typed `<li>` items)
|
|
206
|
+
// owns its field list; a `See` anchor beside it is supplementary detail, not the
|
|
207
|
+
// slot's source of truth. The pure-pointer cross-reference branch fires only for
|
|
208
|
+
// a doc with no inline list of its own. When the inline list is an *untyped
|
|
209
|
+
// name-only* `<ul>` (`<li>NAME</li>`, no `<span class="type">`) the names alone
|
|
210
|
+
// recover nothing, so the supplementary branch adopts the referenced sibling's
|
|
211
|
+
// fields filtered to those names (`resource.get_atlas` lists
|
|
212
|
+
// `texture`/`geometries`/`animations` then points at `resource.set_atlas`).
|
|
213
|
+
const OWN_FIELD_LIST_MARKUP = /<dt>|<li>/;
|
|
214
|
+
// Plain `<li>NAME</li>` items — a bare identifier with no `<span class="type">`,
|
|
215
|
+
// `<code>`, or nested markup. Used to read the field *names* a supplementary
|
|
216
|
+
// cross-reference slot enumerates; the sibling supplies the types. Does not match
|
|
217
|
+
// the typed `<li>` forms `parseUlFields` handles, the flattened `<li><dl>` form,
|
|
218
|
+
// or multi-word prose `<li>` items.
|
|
219
|
+
const UL_NAME_FIELD = /<li>\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*<\/li>/g;
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Recover a `table`-typed slot's field structure from its doc HTML. Three
|
|
223
|
+
* regular field shapes are recognised: the `<dl>` definition list
|
|
224
|
+
* (`<dt><code>NAME</code></dt><dd><span class="type">T</span>…`), the `<ul>`
|
|
225
|
+
* typed list (`<li><span class="type">T</span> <code>NAME</code>…`,
|
|
226
|
+
* type-before-name), and dash-list option fields (`- NAME <span
|
|
227
|
+
* class="type">T</span>` or `- <code>NAME</code> <span class="type">T</span>`,
|
|
228
|
+
* name-before-type), and the code-dash form (`<code>NAME</code> - <span
|
|
229
|
+
* class="type">T</span>`, name-in-code then dash then type). They all use the
|
|
230
|
+
* same `<span class="type">` form `parseProperty` reads from a PROPERTY brief.
|
|
231
|
+
* Returns the ordered `{ name, types }[]` (types split on `|`) when at least one
|
|
232
|
+
* field matches, `null` otherwise — the boundary both the emitter and the
|
|
233
|
+
* fidelity audit key off so the gate and the emitted surface cannot drift. The
|
|
234
|
+
* `<dl>` form takes precedence; the `<ul>`, dash-list, and code-dash fallbacks
|
|
235
|
+
* fire only when the earlier forms yield nothing.
|
|
236
|
+
*
|
|
237
|
+
* Lives here beside `recoverCallbackSignature` rather than in `core-types.ts`
|
|
238
|
+
* because that module is fed verbatim to typescript-to-lua, which rejects regex
|
|
239
|
+
* literals.
|
|
240
|
+
*/
|
|
241
|
+
export interface TableField {
|
|
242
|
+
name: string;
|
|
243
|
+
types: string[];
|
|
244
|
+
fields?: TableField[];
|
|
245
|
+
isList?: boolean;
|
|
246
|
+
numberList?: boolean;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function parseUlFields(doc: string): TableField[] {
|
|
250
|
+
const fields: TableField[] = [];
|
|
251
|
+
for (const match of doc.matchAll(UL_TABLE_FIELD)) {
|
|
252
|
+
const name = (match[2] ?? "").trim();
|
|
253
|
+
const types = splitTypeTokens(match[1] ?? "");
|
|
254
|
+
if (name.length > 0) fields.push({ name, types });
|
|
255
|
+
}
|
|
256
|
+
return fields;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function parseUlNames(doc: string): string[] {
|
|
260
|
+
const names: string[] = [];
|
|
261
|
+
for (const match of doc.matchAll(UL_NAME_FIELD)) {
|
|
262
|
+
const name = (match[1] ?? "").trim();
|
|
263
|
+
if (name.length > 0) names.push(name);
|
|
264
|
+
}
|
|
265
|
+
return names;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function parseDashListFields(doc: string): TableField[] {
|
|
269
|
+
const fields: TableField[] = [];
|
|
270
|
+
for (const match of doc.matchAll(DASH_TABLE_FIELD)) {
|
|
271
|
+
const name = (match[1] ?? match[2] ?? "").trim();
|
|
272
|
+
const types = splitTypeTokens(match[3] ?? "");
|
|
273
|
+
if (name.length > 0) fields.push({ name, types });
|
|
274
|
+
}
|
|
275
|
+
return fields;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function parseCodeDashFields(doc: string): TableField[] {
|
|
279
|
+
const fields: TableField[] = [];
|
|
280
|
+
for (const match of doc.matchAll(CODE_DASH_TABLE_FIELD)) {
|
|
281
|
+
const name = (match[1] ?? "").trim();
|
|
282
|
+
const types = splitTypeTokens(match[2] ?? "");
|
|
283
|
+
if (name.length > 0) fields.push({ name, types });
|
|
284
|
+
}
|
|
285
|
+
return fields;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function splitTypeTokens(raw: string): string[] {
|
|
289
|
+
return raw
|
|
290
|
+
.split("|")
|
|
291
|
+
.map((t) => t.trim())
|
|
292
|
+
.filter((t) => t.length > 0);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Resolver from a referenced element's name to that element's first
|
|
296
|
+
// `table`-typed param-or-return doc. Threaded into `parseTableFields` so a
|
|
297
|
+
// cross-reference slot can adopt the referenced sibling's already-recovered
|
|
298
|
+
// fields. The resolver is module-scoped, so a cross-module anchor never
|
|
299
|
+
// resolves (left for a later slice).
|
|
300
|
+
export type TableDocResolver = (elementName: string) => string | undefined;
|
|
301
|
+
|
|
302
|
+
interface TableDocSource {
|
|
303
|
+
name: string;
|
|
304
|
+
slots: readonly { types: readonly string[]; doc: string }[];
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function buildTableDocResolver(sources: readonly TableDocSource[]): TableDocResolver {
|
|
308
|
+
const docByName = new Map<string, string>();
|
|
309
|
+
for (const source of sources) {
|
|
310
|
+
if (docByName.has(source.name)) continue;
|
|
311
|
+
const tableSlot = source.slots.find((slot) => slot.types.includes("table"));
|
|
312
|
+
if (tableSlot !== undefined) docByName.set(source.name, tableSlot.doc);
|
|
313
|
+
}
|
|
314
|
+
return (elementName) => docByName.get(elementName);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function parseTableFields(doc: string, resolver?: TableDocResolver): TableField[] | null {
|
|
318
|
+
const fields: TableField[] = [];
|
|
319
|
+
for (const match of doc.matchAll(TABLE_FIELD)) {
|
|
320
|
+
const name = (match[1] ?? "").trim();
|
|
321
|
+
const types = splitTypeTokens(match[2] ?? "");
|
|
322
|
+
if (name.length === 0) continue;
|
|
323
|
+
const field: TableField = { name, types };
|
|
324
|
+
const prose = match[3] ?? "";
|
|
325
|
+
if (types.includes("table") && LIST_PROSE.test(prose)) {
|
|
326
|
+
field.isList = true;
|
|
327
|
+
if (isNumberListForm(prose)) field.numberList = true;
|
|
328
|
+
}
|
|
329
|
+
fields.push(field);
|
|
330
|
+
}
|
|
331
|
+
if (FLATTENED_TABLE.test(doc) && fields.some((field) => isTableField(field))) {
|
|
332
|
+
return groupFlattenedTableFields(fields);
|
|
333
|
+
}
|
|
334
|
+
// Nested recovery, scoped to the one unambiguous shape: a `<dl>` declaring a
|
|
335
|
+
// single `table`-typed field whose keys sit in an immediately-following
|
|
336
|
+
// top-level `<ul>` typed-field list (`window.get_safe_area`). Attach the
|
|
337
|
+
// `<ul>` fields as that field's nested shape so the slot recovers as a nested
|
|
338
|
+
// object instead of a `Record`.
|
|
339
|
+
if (fields.length === 1) {
|
|
340
|
+
const only = fields[0];
|
|
341
|
+
if (only && only.types.length === 1 && only.types[0] === "table") {
|
|
342
|
+
const nested = parseUlFields(doc);
|
|
343
|
+
if (nested.length > 0) {
|
|
344
|
+
only.fields = nested;
|
|
345
|
+
return [only];
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
if (fields.length === 0) {
|
|
350
|
+
for (const field of parseUlFields(doc)) fields.push(field);
|
|
351
|
+
}
|
|
352
|
+
if (fields.length === 0) {
|
|
353
|
+
for (const field of parseDashListFields(doc)) fields.push(field);
|
|
354
|
+
}
|
|
355
|
+
if (fields.length === 0) {
|
|
356
|
+
for (const field of parseCodeDashFields(doc)) fields.push(field);
|
|
357
|
+
}
|
|
358
|
+
if (fields.length > 0) return fields;
|
|
359
|
+
// The direct parsers recovered nothing. If a resolver is present and the doc
|
|
360
|
+
// is a cross-reference pointer, adopt the referenced element's fields. Recurse
|
|
361
|
+
// without the resolver — the depth-1 / cycle guard: a referenced doc that is
|
|
362
|
+
// itself only another cross-ref anchor recovers nothing.
|
|
363
|
+
if (resolver !== undefined && !OWN_FIELD_LIST_MARKUP.test(doc)) {
|
|
364
|
+
const ref = CROSS_REF_TABLE.exec(doc);
|
|
365
|
+
const target = ref?.[1];
|
|
366
|
+
if (target !== undefined) {
|
|
367
|
+
const resolved = resolver(target);
|
|
368
|
+
if (resolved !== undefined) return parseTableFields(resolved);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// Supplementary cross-reference: an untyped name-only `<ul>` (recovered nothing
|
|
372
|
+
// above) beside a `See <sibling>` pointer. Adopt the referenced sibling's
|
|
373
|
+
// recovered fields, filtered to the names the own `<ul>` enumerates — the
|
|
374
|
+
// filter is load-bearing: it excludes sibling fields this slot does not list,
|
|
375
|
+
// so the recovery cannot raise the table-granularity loss count. One hop: the
|
|
376
|
+
// sibling is resolved without the resolver (depth-1 / cycle guard).
|
|
377
|
+
if (resolver !== undefined && OWN_FIELD_LIST_MARKUP.test(doc)) {
|
|
378
|
+
const names = parseUlNames(doc);
|
|
379
|
+
const ref = CROSS_REF_TABLE.exec(doc);
|
|
380
|
+
const target = ref?.[1];
|
|
381
|
+
if (names.length > 0 && target !== undefined) {
|
|
382
|
+
const resolved = resolver(target);
|
|
383
|
+
if (resolved !== undefined) {
|
|
384
|
+
const resolvedFields = parseTableFields(resolved);
|
|
385
|
+
if (resolvedFields !== null) {
|
|
386
|
+
const wanted = new Set(names);
|
|
387
|
+
const filtered = resolvedFields.filter((field) => wanted.has(field.name));
|
|
388
|
+
if (filtered.length > 0) return filtered;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function isTableField(field: TableField): boolean {
|
|
397
|
+
return field.types.length === 1 && field.types[0] === "table";
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function groupFlattenedTableFields(fields: readonly TableField[]): TableField[] {
|
|
401
|
+
const grouped: TableField[] = [];
|
|
402
|
+
let open: TableField | undefined;
|
|
403
|
+
for (const field of fields) {
|
|
404
|
+
if (isTableField(field)) {
|
|
405
|
+
grouped.push(field);
|
|
406
|
+
open = field;
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
if (open === undefined) {
|
|
410
|
+
grouped.push(field);
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
open.fields ??= [];
|
|
414
|
+
open.fields.push(field);
|
|
415
|
+
}
|
|
416
|
+
return grouped;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export function emitDeclarations(module: ApiModule, options?: EmitOptions): string {
|
|
420
|
+
const prefix = `${module.namespace}.`;
|
|
421
|
+
|
|
422
|
+
const constantFqns = new Set(module.constants.map((c) => c.name));
|
|
423
|
+
const knownConstantFqns = options?.knownConstantFqns;
|
|
424
|
+
const baseMapType = options?.mapType ?? defaultMapType;
|
|
425
|
+
const mapType = (token: string): string =>
|
|
426
|
+
constantFqns.has(token) || knownConstantFqns?.has(token)
|
|
427
|
+
? brandType(token)
|
|
428
|
+
: baseMapType(token);
|
|
429
|
+
|
|
430
|
+
const constants = module.constants
|
|
431
|
+
.map((c) => prepareConstant(c, prefix))
|
|
432
|
+
.filter((entry): entry is PreparedConstant => entry !== null)
|
|
433
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
434
|
+
|
|
435
|
+
const variables = module.variables
|
|
436
|
+
.map((v) => prepareVariable(v, prefix))
|
|
437
|
+
.filter((entry): entry is PreparedVariable => entry !== null)
|
|
438
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
439
|
+
|
|
440
|
+
const functions = module.functions
|
|
441
|
+
.map((fn) => prepareFunction(fn, prefix))
|
|
442
|
+
.filter((entry): entry is PreparedFunction => entry !== null)
|
|
443
|
+
.sort((a, b) =>
|
|
444
|
+
a.name === b.name
|
|
445
|
+
? a.original.parameters.length - b.original.parameters.length
|
|
446
|
+
: a.name.localeCompare(b.name),
|
|
447
|
+
);
|
|
448
|
+
|
|
449
|
+
const resolver = buildTableDocResolver(
|
|
450
|
+
module.functions.map((fn) => ({
|
|
451
|
+
name: fn.name,
|
|
452
|
+
slots: [...fn.parameters, ...fn.returnValues],
|
|
453
|
+
})),
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
const typedefs = module.typedefs
|
|
457
|
+
.filter((t) => TS_IDENTIFIER.test(t.name))
|
|
458
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
459
|
+
|
|
460
|
+
// A re-export alias (`export { _x as x }`) switches the ambient namespace out
|
|
461
|
+
// of its implicit-export mode, so once any alias is present every sibling
|
|
462
|
+
// declaration must carry an explicit `export` keyword to stay visible. The
|
|
463
|
+
// keyword is otherwise omitted to keep every alias-free module byte-identical.
|
|
464
|
+
const hasAliases =
|
|
465
|
+
variables.some((v) => TS_RESERVED_NAMES.has(v.name)) ||
|
|
466
|
+
functions.some((fn) => TS_RESERVED_NAMES.has(fn.name));
|
|
467
|
+
const decl = hasAliases ? "export " : "";
|
|
468
|
+
|
|
469
|
+
const lines: string[] = [];
|
|
470
|
+
lines.push(`declare namespace ${module.namespace} {`);
|
|
471
|
+
|
|
472
|
+
for (const t of typedefs) {
|
|
473
|
+
lines.push(`${INDENT}${decl}type ${t.name} = Opaque<"${t.name}">;`);
|
|
474
|
+
}
|
|
475
|
+
for (const c of constants) {
|
|
476
|
+
lines.push(`${INDENT}${decl}const ${c.name}: ${brandType(c.fqn)};`);
|
|
477
|
+
}
|
|
478
|
+
const aliases: { internal: string; public: string }[] = [];
|
|
479
|
+
for (const v of variables) {
|
|
480
|
+
// A reserved-name member's `_`-prefixed declaration stays un-exported so it
|
|
481
|
+
// is local-only (not reachable as `ns._x`); the alias below re-exports it
|
|
482
|
+
// under the public reserved name.
|
|
483
|
+
const reserved = TS_RESERVED_NAMES.has(v.name);
|
|
484
|
+
const emitName = aliasName(v.name, aliases);
|
|
485
|
+
const line = emitVariable(v, emitName, mapType);
|
|
486
|
+
if (line !== null) lines.push(`${INDENT}${reserved ? "" : decl}${line}`);
|
|
487
|
+
}
|
|
488
|
+
for (const fn of functions) {
|
|
489
|
+
const reserved = TS_RESERVED_NAMES.has(fn.name);
|
|
490
|
+
const emitName = aliasName(fn.name, aliases);
|
|
491
|
+
const line = emitFunction(fn, emitName, mapType, resolver);
|
|
492
|
+
lines.push(`${INDENT}${reserved ? "" : decl}${line}`);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
for (const alias of [...aliases].sort((a, b) => a.public.localeCompare(b.public))) {
|
|
496
|
+
lines.push(`${INDENT}export { ${alias.internal} as ${alias.public} };`);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (module.properties.length > 0) {
|
|
500
|
+
const members = [...module.properties].sort((a, b) => a.name.localeCompare(b.name));
|
|
501
|
+
lines.push(`${INDENT}${decl}interface properties {`);
|
|
502
|
+
for (const p of members) {
|
|
503
|
+
lines.push(`${INDENT}${INDENT}${emitPropertyMember(p, mapType)}`);
|
|
504
|
+
}
|
|
505
|
+
lines.push(`${INDENT}}`);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
lines.push("}");
|
|
509
|
+
return `${lines.join("\n")}\n`;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
interface PreparedConstant {
|
|
513
|
+
name: string;
|
|
514
|
+
fqn: string;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
interface PreparedFunction {
|
|
518
|
+
name: string;
|
|
519
|
+
original: ApiFunction;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
interface PreparedVariable {
|
|
523
|
+
name: string;
|
|
524
|
+
original: ApiVariable;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function prepareFunction(fn: ApiFunction, prefix: string): PreparedFunction | null {
|
|
528
|
+
const stripped = stripPrefix(fn.name, prefix);
|
|
529
|
+
if (!TS_IDENTIFIER.test(stripped)) return null;
|
|
530
|
+
return { name: stripped, original: fn };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function prepareConstant(c: ApiConstant, prefix: string): PreparedConstant | null {
|
|
534
|
+
const stripped = stripPrefix(c.name, prefix);
|
|
535
|
+
if (!TS_IDENTIFIER.test(stripped)) return null;
|
|
536
|
+
return { name: stripped, fqn: c.name };
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function brandType(fqn: string): string {
|
|
540
|
+
return `number & { readonly __brand: "${fqn}" }`;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function prepareVariable(v: ApiVariable, prefix: string): PreparedVariable | null {
|
|
544
|
+
const stripped = stripPrefix(v.name, prefix);
|
|
545
|
+
if (!TS_IDENTIFIER.test(stripped)) return null;
|
|
546
|
+
return { name: stripped, original: v };
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function stripPrefix(name: string, prefix: string): string {
|
|
550
|
+
return name.startsWith(prefix) ? name.slice(prefix.length) : name;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// When a prepared member's name is a TS reserved word it cannot be emitted
|
|
554
|
+
// directly, so it is declared under an `_`-prefixed internal name and the
|
|
555
|
+
// `{ internal, public }` pair is recorded for a trailing `export { _x as x }`
|
|
556
|
+
// alias. Non-reserved names pass through unchanged.
|
|
557
|
+
function aliasName(name: string, aliases: { internal: string; public: string }[]): string {
|
|
558
|
+
if (!TS_RESERVED_NAMES.has(name)) return name;
|
|
559
|
+
const internal = `_${name}`;
|
|
560
|
+
aliases.push({ internal, public: name });
|
|
561
|
+
return internal;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function emitVariable(
|
|
565
|
+
prepared: PreparedVariable,
|
|
566
|
+
name: string,
|
|
567
|
+
mapType: (t: string) => string,
|
|
568
|
+
): string {
|
|
569
|
+
const ts =
|
|
570
|
+
prepared.original.types.length > 0
|
|
571
|
+
? unionFromTokens(prepared.original.types, mapType)
|
|
572
|
+
: "unknown";
|
|
573
|
+
return `const ${name}: ${ts};`;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function emitFunction(
|
|
577
|
+
prepared: PreparedFunction,
|
|
578
|
+
name: string,
|
|
579
|
+
mapType: (t: string) => string,
|
|
580
|
+
resolver: TableDocResolver,
|
|
581
|
+
): string {
|
|
582
|
+
const original = prepared.original.parameters;
|
|
583
|
+
const elementName = prepared.original.name;
|
|
584
|
+
const cutoff = trailingOptionalCutoff(original);
|
|
585
|
+
const params = original
|
|
586
|
+
.map((p, i) => emitParameter(p, i, i >= cutoff, mapType, resolver, elementName))
|
|
587
|
+
.join(", ");
|
|
588
|
+
const ret = emitReturn(prepared.original.returnValues, mapType, resolver, elementName);
|
|
589
|
+
return `function ${name}(${params}): ${ret.type};${ret.trailing}`;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
function isDocOptional(p: ApiParameter): boolean {
|
|
593
|
+
return p.isOptional || p.types.includes("nil");
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function trailingOptionalCutoff(params: readonly ApiParameter[]): number {
|
|
597
|
+
let cutoff = params.length;
|
|
598
|
+
for (let i = params.length - 1; i >= 0; i -= 1) {
|
|
599
|
+
const p = params[i];
|
|
600
|
+
if (p && isDocOptional(p)) cutoff = i;
|
|
601
|
+
else break;
|
|
602
|
+
}
|
|
603
|
+
return cutoff;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function emitParameter(
|
|
607
|
+
p: ApiParameter,
|
|
608
|
+
index: number,
|
|
609
|
+
optional: boolean,
|
|
610
|
+
mapType: (t: string) => string,
|
|
611
|
+
resolver: TableDocResolver,
|
|
612
|
+
elementName: string,
|
|
613
|
+
): string {
|
|
614
|
+
const name = TS_IDENTIFIER.test(p.name) ? p.name : `arg${index}`;
|
|
615
|
+
const concrete = p.types.filter((t) => t !== "nil");
|
|
616
|
+
const ts =
|
|
617
|
+
concrete.length > 0
|
|
618
|
+
? mapSlotUnion(concrete, p.doc, mapType, true, resolver, elementName)
|
|
619
|
+
: "unknown";
|
|
620
|
+
return `${name}${optional ? "?" : ""}: ${ts}`;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function emitReturn(
|
|
624
|
+
returnValues: ApiParameter[],
|
|
625
|
+
mapType: (t: string) => string,
|
|
626
|
+
resolver: TableDocResolver,
|
|
627
|
+
elementName: string,
|
|
628
|
+
): { type: string; trailing: string } {
|
|
629
|
+
if (returnValues.length === 0) return { type: "void", trailing: "" };
|
|
630
|
+
if (returnValues.length > 1) {
|
|
631
|
+
// Defold multi-returns are positional and always present; each slot maps
|
|
632
|
+
// straight through (unknown when the doc lists no type) into a tuple that
|
|
633
|
+
// typescript-to-lua erases to `local a, b = fn()`.
|
|
634
|
+
const slots = returnValues.map((rv) =>
|
|
635
|
+
rv.types.length > 0
|
|
636
|
+
? mapSlotUnion(rv.types, rv.doc, mapType, false, resolver, elementName)
|
|
637
|
+
: "unknown",
|
|
638
|
+
);
|
|
639
|
+
return { type: `LuaMultiReturn<[${slots.join(", ")}]>`, trailing: "" };
|
|
640
|
+
}
|
|
641
|
+
const first = returnValues[0];
|
|
642
|
+
if (!first) return { type: "void", trailing: "" };
|
|
643
|
+
const ts =
|
|
644
|
+
first.types.length > 0
|
|
645
|
+
? mapSlotUnion(first.types, first.doc, mapType, false, resolver, elementName)
|
|
646
|
+
: "unknown";
|
|
647
|
+
return { type: ts, trailing: "" };
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function emitPropertyMember(p: ApiProperty, mapType: (t: string) => string): string {
|
|
651
|
+
const key = TS_IDENTIFIER.test(p.name) ? p.name : JSON.stringify(p.name);
|
|
652
|
+
const ts = p.types.length > 0 ? unionFromTokens(p.types, mapType) : "unknown";
|
|
653
|
+
return `${key}: ${ts};`;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Like `unionFromTokens`, but a `table` token whose slot doc carries a parseable
|
|
657
|
+
// `<dl>` field list emits an inline object type instead of the opaque `Record`
|
|
658
|
+
// fallback. Other tokens in the union map unchanged. `optionalFields` marks the
|
|
659
|
+
// recovered fields `?` for input params — a `<dl>` does not encode per-field
|
|
660
|
+
// optionality and an input option-bag's fields are individually omittable (the
|
|
661
|
+
// old `Record` accepted partial/empty objects), so a parameter's fields are
|
|
662
|
+
// optional while a return's fields (engine-populated, all present) stay required.
|
|
663
|
+
function mapSlotUnion(
|
|
664
|
+
types: readonly string[],
|
|
665
|
+
doc: string,
|
|
666
|
+
mapType: (t: string) => string,
|
|
667
|
+
optionalFields: boolean,
|
|
668
|
+
resolver: TableDocResolver,
|
|
669
|
+
elementName: string,
|
|
670
|
+
): string {
|
|
671
|
+
const mapped: string[] = [];
|
|
672
|
+
const seen = new Set<string>();
|
|
673
|
+
for (const token of types) {
|
|
674
|
+
let ts: string;
|
|
675
|
+
if (token === "table") {
|
|
676
|
+
const mapping = MAPPING_TABLE_SLOTS.get(elementName);
|
|
677
|
+
const element = HOMOGENEOUS_ARRAY_SLOTS.get(elementName);
|
|
678
|
+
if (mapping !== undefined) {
|
|
679
|
+
ts = `LuaMap<${mapType(mapping.key)}, ${mapType(mapping.value)}>`;
|
|
680
|
+
} else if (element !== undefined) {
|
|
681
|
+
const tokens = typeof element === "string" ? [element] : element;
|
|
682
|
+
ts =
|
|
683
|
+
tokens.length > 1
|
|
684
|
+
? `(${unionFromTokens(tokens, mapType)})[]`
|
|
685
|
+
: `${mapType(tokens[0] as string)}[]`;
|
|
686
|
+
} else {
|
|
687
|
+
const fields = parseTableFields(doc, resolver);
|
|
688
|
+
if (fields !== null) {
|
|
689
|
+
const object = inlineTableType(fields, mapType, optionalFields);
|
|
690
|
+
ts = isSlotLevelList(doc) ? `${object}[]` : object;
|
|
691
|
+
} else {
|
|
692
|
+
ts = mapType(token);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
} else {
|
|
696
|
+
ts = mapType(token);
|
|
697
|
+
}
|
|
698
|
+
if (seen.has(ts)) continue;
|
|
699
|
+
seen.add(ts);
|
|
700
|
+
mapped.push(ts);
|
|
701
|
+
}
|
|
702
|
+
return mapped.join(" | ");
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
export function inlineTableType(
|
|
706
|
+
fields: readonly TableField[],
|
|
707
|
+
mapType: (t: string) => string,
|
|
708
|
+
optionalFields: boolean,
|
|
709
|
+
): string {
|
|
710
|
+
const members = fields.map((field) => {
|
|
711
|
+
const key = TS_IDENTIFIER.test(field.name) ? field.name : JSON.stringify(field.name);
|
|
712
|
+
// A field carrying recovered nested fields (the mixed `<dl>`+`<ul>` shape)
|
|
713
|
+
// emits a one-level-nested inline object; every other field token maps
|
|
714
|
+
// through the same machinery as a top-level slot. Deeper nesting is not
|
|
715
|
+
// recovered — a nested `table` field with no nested fields stays `Record`.
|
|
716
|
+
// A field whose doc reads "a list of …" emits an array of that recovered
|
|
717
|
+
// element shape, but only when members were recovered — a bare `Record` with
|
|
718
|
+
// no machine-readable element type is left unwrapped (no stray `[]`). A
|
|
719
|
+
// number-list field carries no member shape but a machine-readable numeric
|
|
720
|
+
// element type ("in the form {px0, …}"), so it emits `number[]`.
|
|
721
|
+
const ts =
|
|
722
|
+
field.fields !== undefined
|
|
723
|
+
? `${inlineTableType(field.fields, mapType, optionalFields)}${field.isList ? "[]" : ""}`
|
|
724
|
+
: field.numberList === true
|
|
725
|
+
? "number[]"
|
|
726
|
+
: field.types.length > 0
|
|
727
|
+
? unionFromTokens(field.types, mapType)
|
|
728
|
+
: "unknown";
|
|
729
|
+
return `${key}${optionalFields ? "?" : ""}: ${ts}`;
|
|
730
|
+
});
|
|
731
|
+
return `{ ${members.join("; ")} }`;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function unionFromTokens(tokens: readonly string[], mapType: (t: string) => string): string {
|
|
735
|
+
const mapped: string[] = [];
|
|
736
|
+
const seen = new Set<string>();
|
|
737
|
+
for (const token of tokens) {
|
|
738
|
+
const ts = mapType(token);
|
|
739
|
+
if (seen.has(ts)) continue;
|
|
740
|
+
seen.add(ts);
|
|
741
|
+
mapped.push(ts);
|
|
742
|
+
}
|
|
743
|
+
return mapped.join(" | ");
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function defaultMapType(token: string): string {
|
|
747
|
+
if (Object.hasOwn(DEFOLD_TYPE_MAP, token)) {
|
|
748
|
+
const mapped = DEFOLD_TYPE_MAP[token];
|
|
749
|
+
if (typeof mapped === "string") return mapped;
|
|
750
|
+
}
|
|
751
|
+
const callback = recoverCallbackSignature(token);
|
|
752
|
+
if (callback !== null) return callback;
|
|
753
|
+
return "unknown";
|
|
754
|
+
}
|