@defold-typescript/library-types 0.22.0 → 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.
- package/api-doc/{bridge.bridge.json → bridge.json} +749 -844
- package/api-doc/decore.json +22 -22
- package/api-doc/druid.json +195 -1790
- package/api-doc/event.json +1074 -0
- package/api-doc/immutable.json +65 -0
- package/api-doc/lang.json +528 -0
- package/api-doc/{event.event.json → log.json} +96 -101
- package/api-doc/narrator.json +647 -0
- package/api-doc/proto.json +995 -0
- package/api-doc/saver.saver.json +713 -263
- package/api-doc/saver.storage.json +282 -52
- package/api-doc/squid.json +846 -0
- package/api-doc/tweener.json +277 -0
- package/generated/bridge.d.ts +468 -0
- package/generated/decore.d.ts +36 -36
- package/generated/druid.d.ts +137 -443
- package/generated/event.d.ts +318 -0
- package/generated/immutable.d.ts +13 -0
- package/generated/lang.d.ts +101 -0
- package/generated/log.d.ts +36 -0
- package/generated/narrator.d.ts +121 -0
- package/generated/proto.d.ts +146 -0
- package/generated/saver.saver.d.ts +287 -42
- package/generated/saver.storage.d.ts +77 -14
- package/generated/squid.d.ts +127 -0
- package/generated/tweener.d.ts +42 -0
- package/library-classification.json +0 -71
- package/library-targets.json +0 -66
- package/luals-targets.json +114 -0
- package/package.json +4 -33
- package/script-api-targets.json +15 -0
- package/scripts/__snapshots__/parse-luals.test.ts.snap +340 -75
- package/scripts/apply-luals-overrides.ts +63 -0
- package/scripts/emit-library-dts.ts +84 -3
- package/scripts/lower-api-doc.ts +48 -16
- package/scripts/luals-fidelity.ts +7 -0
- package/scripts/map-luals-types.ts +34 -1
- package/scripts/parse-luals.ts +423 -18
- package/scripts/sync-luals-types.ts +15 -1
- package/scripts/sync-script-api-types.ts +368 -0
- package/api-doc/immutable.immutable.json +0 -63
- package/api-doc/lang.lang.json +0 -411
- package/api-doc/log.log.json +0 -50
- package/api-doc/narrator.narrator.json +0 -150
- package/api-doc/proto.proto.json +0 -355
- package/api-doc/squid.squid.json +0 -660
- package/api-doc/tweener.tweener.json +0 -419
- package/generated/bridge.bridge.d.ts +0 -533
- package/generated/event.event.d.ts +0 -54
- package/generated/immutable.immutable.d.ts +0 -13
- package/generated/lang.lang.d.ts +0 -33
- package/generated/log.log.d.ts +0 -40
- package/generated/narrator.narrator.d.ts +0 -66
- package/generated/proto.proto.d.ts +0 -36
- package/generated/squid.squid.d.ts +0 -106
- 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 =
|
|
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)
|
|
249
|
-
|
|
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
|
}
|
package/scripts/lower-api-doc.ts
CHANGED
|
@@ -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(
|
|
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:
|
|
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) =>
|
|
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
|
-
|
|
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`
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
|
|
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
|
-
|
|
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
|