@defold-typescript/library-types 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api-doc/boom.json +3870 -0
- package/api-doc/{bridge.bridge.json → bridge.json} +754 -844
- package/api-doc/bzAnim.json +338 -0
- package/api-doc/decore.json +23 -23
- package/api-doc/defcon.json +150 -0
- package/api-doc/{defmath.defmath.json → defmath.json} +6 -46
- package/api-doc/defsave.json +518 -0
- package/api-doc/deftest.json +741 -0
- package/api-doc/{dicebag.dicebag.json → dicebag.json} +19 -1
- package/api-doc/druid.json +249 -1846
- package/api-doc/event.json +1074 -0
- package/api-doc/gooey.json +2050 -0
- package/api-doc/immutable.json +65 -0
- package/api-doc/in.accelerometer.json +75 -1
- package/api-doc/in.button.json +17 -10
- package/api-doc/in.cursor.json +26 -0
- package/api-doc/in.gesture.json +401 -6
- package/api-doc/in.keyboard.json +2 -0
- package/api-doc/in.mapper.json +12 -8
- package/api-doc/in.onscreen.json +61 -5
- package/api-doc/in.state.json +61 -13
- package/api-doc/in.textbox.json +23 -12
- package/api-doc/in.triggers.json +336 -0
- package/api-doc/lang.json +528 -0
- package/api-doc/{event.event.json → log.json} +96 -101
- package/api-doc/metrics.fps.json +61 -2
- package/api-doc/metrics.mem.json +61 -2
- package/api-doc/monarch.monarch.json +249 -7
- package/api-doc/monarch.transitions.easings.json +19 -26
- package/api-doc/monarch.transitions.gui.json +782 -79
- package/api-doc/nakama.engine.defold.json +31 -18
- package/api-doc/{nakama.nakama.json → nakama.json} +4752 -2554
- package/api-doc/nakama.util.log.json +32 -4
- package/api-doc/narrator.json +647 -0
- package/api-doc/openapi/nakama.nakama.json +7680 -0
- package/api-doc/orthographic.camera.json +96 -98
- package/api-doc/orthographic.json +602 -0
- package/api-doc/panthera.json +1183 -0
- package/api-doc/{persist.persist.json → persist.json} +26 -1
- package/api-doc/{platypus.platypus.json → platypus.json} +68 -14
- package/api-doc/proto.json +995 -0
- package/api-doc/{rendy.rendy.json → rendy.json} +46 -99
- package/api-doc/richtext.color.json +77 -0
- package/api-doc/richtext.richtext.json +375 -5
- package/api-doc/saver.saver.json +714 -263
- package/api-doc/saver.storage.json +283 -52
- package/api-doc/squid.json +846 -0
- package/api-doc/{starly.starly.json → starly.json} +1 -1
- package/api-doc/tweener.json +277 -0
- package/api-doc/{yagames.yagames.json → yagames.json} +784 -189
- package/api-doc/{zzfx.api.json → zzfx.json} +9 -9
- package/authored-targets.json +403 -0
- package/generated/{boom.boom.d.ts → boom.d.ts} +14 -0
- package/generated/bridge.d.ts +468 -0
- package/generated/{bzAnim.bzLibrary.d.ts → bzAnim.d.ts} +6 -0
- package/generated/decore.d.ts +37 -37
- package/generated/defcon.d.ts +43 -0
- package/generated/{defmath.defmath.d.ts → defmath.d.ts} +3 -2
- package/generated/defsave.d.ts +144 -0
- package/generated/{dicebag.dicebag.d.ts → dicebag.d.ts} +25 -0
- package/generated/druid.d.ts +198 -502
- package/generated/event.d.ts +318 -0
- package/generated/{gooey.gooey.d.ts → gooey.d.ts} +61 -6
- package/generated/immutable.d.ts +13 -0
- package/generated/in.onscreen.d.ts +5 -0
- package/generated/in.textbox.d.ts +4 -0
- package/generated/lang.d.ts +101 -0
- package/generated/log.d.ts +36 -0
- package/generated/metrics.fps.d.ts +19 -1
- package/generated/metrics.mem.d.ts +19 -1
- package/generated/monarch.monarch.d.ts +44 -1
- package/generated/monarch.transitions.easings.d.ts +0 -1
- package/generated/monarch.transitions.gui.d.ts +96 -12
- package/generated/nakama.d.ts +1158 -0
- package/generated/nakama.engine.defold.d.ts +3 -1
- package/generated/nakama.util.log.d.ts +6 -0
- package/generated/narrator.d.ts +121 -0
- package/generated/openapi/nakama.nakama.d.ts +884 -0
- package/generated/orthographic.camera.d.ts +24 -38
- package/generated/orthographic.d.ts +155 -0
- package/generated/panthera.d.ts +170 -0
- package/generated/{persist.persist.d.ts → persist.d.ts} +3 -0
- package/generated/{platypus.platypus.d.ts → platypus.d.ts} +9 -0
- package/generated/proto.d.ts +146 -0
- package/generated/{rendy.rendy.d.ts → rendy.d.ts} +33 -80
- package/generated/richtext.color.d.ts +15 -0
- package/generated/saver.saver.d.ts +289 -42
- package/generated/saver.storage.d.ts +79 -14
- package/generated/squid.d.ts +127 -0
- package/generated/tweener.d.ts +42 -0
- package/generated/{yagames.yagames.d.ts → yagames.d.ts} +151 -29
- package/generated/{zzfx.api.d.ts → zzfx.d.ts} +7 -0
- package/library-classification.json +0 -227
- package/library-targets.json +1 -284
- package/luals-targets.json +168 -1
- package/openapi-targets.json +17 -0
- package/package.json +9 -88
- package/script-api-targets.json +15 -0
- package/scripts/__snapshots__/parse-luals.test.ts.snap +504 -85
- package/scripts/apply-luals-overrides.ts +95 -0
- package/scripts/authored-parity.ts +718 -0
- package/scripts/emit-library-dts.ts +177 -19
- package/scripts/extract-api-doc.ts +189 -38
- package/scripts/fidelity-floor.ts +178 -0
- package/scripts/import-upstream-docs.ts +125 -0
- package/scripts/lower-api-doc.ts +69 -19
- package/scripts/luals-fidelity.ts +22 -10
- package/scripts/map-luals-types.ts +86 -22
- package/scripts/parse-lua-surface.ts +187 -0
- package/scripts/parse-luals.ts +473 -22
- package/scripts/parse-markdown-api.ts +247 -0
- package/scripts/parse-openapi-api.ts +313 -0
- package/scripts/sync-authored-types.ts +249 -0
- package/scripts/sync-library-types.ts +99 -3
- package/scripts/sync-luals-types.ts +29 -2
- package/scripts/sync-markdown-types.ts +761 -0
- package/scripts/sync-openapi-types.ts +308 -0
- package/scripts/sync-script-api-types.ts +367 -0
- package/api-doc/boom.boom.json +0 -26
- package/api-doc/bzAnim.bzLibrary.json +0 -156
- package/api-doc/defcon.console.json +0 -109
- package/api-doc/defsave.defsave.json +0 -175
- package/api-doc/deftest.deftest.json +0 -71
- package/api-doc/gooey.gooey.json +0 -963
- package/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/defcon.console.d.ts +0 -24
- package/generated/defsave.defsave.d.ts +0 -31
- 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/nakama.nakama.d.ts +0 -594
- 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
- /package/generated/{deftest.deftest.d.ts → deftest.d.ts} +0 -0
- /package/generated/{starly.starly.d.ts → starly.d.ts} +0 -0
package/scripts/lower-api-doc.ts
CHANGED
|
@@ -15,34 +15,37 @@
|
|
|
15
15
|
|
|
16
16
|
import {
|
|
17
17
|
buildModelContext,
|
|
18
|
+
type ExternalTypeRef,
|
|
19
|
+
isPublicField,
|
|
20
|
+
isPublicMethod,
|
|
18
21
|
mapTypes,
|
|
22
|
+
paramOptionalFlags,
|
|
19
23
|
renderGenericParams,
|
|
20
24
|
sanitizeTypeName,
|
|
21
25
|
} from "./emit-library-dts";
|
|
22
26
|
import { type MapContext, scopeGenerics } from "./map-luals-types";
|
|
23
27
|
import type { LibraryField, LibraryMethod, LibraryModel, LibraryParam } from "./parse-luals";
|
|
24
28
|
|
|
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
29
|
// Each type token is mapped independently (one mapped TS string per token) so the
|
|
33
30
|
// ref-doc `types` array stays token-per-slot the way engine ref-docs are shaped.
|
|
34
31
|
function mapTokens(tokens: readonly string[], ctx: MapContext): string[] {
|
|
35
32
|
return tokens.map((token) => mapTypes([token], ctx));
|
|
36
33
|
}
|
|
37
34
|
|
|
38
|
-
function parameterElement(
|
|
35
|
+
function parameterElement(
|
|
36
|
+
param: LibraryParam,
|
|
37
|
+
isOptional: boolean,
|
|
38
|
+
ctx: MapContext,
|
|
39
|
+
): Record<string, unknown> {
|
|
39
40
|
// A vararg's element type stays a plain mapped token here; the renderer arrayifies
|
|
40
41
|
// it (`...args: T[]`) from the `is_vararg` flag, keeping the JSON structurally honest.
|
|
42
|
+
// `isOptional` comes from the emitter's trailing-run rule (paramOptionalFlags), not
|
|
43
|
+
// `param.isOptional` alone, so the `/api` signature matches the emitted `.d.ts`.
|
|
41
44
|
return {
|
|
42
45
|
name: param.isVararg ? "...args" : param.name,
|
|
43
46
|
doc: param.doc,
|
|
44
47
|
types: mapTokens(param.types, ctx),
|
|
45
|
-
is_optional:
|
|
48
|
+
is_optional: isOptional ? "True" : "False",
|
|
46
49
|
is_vararg: param.isVararg ? "True" : "False",
|
|
47
50
|
};
|
|
48
51
|
}
|
|
@@ -54,17 +57,24 @@ function returnElement(ret: LibraryParam, ctx: MapContext): Record<string, unkno
|
|
|
54
57
|
function functionElement(method: LibraryMethod, ctx: MapContext): Record<string, unknown> {
|
|
55
58
|
const fnCtx = scopeGenerics(ctx, method.generics);
|
|
56
59
|
const generics = renderGenericParams(method.generics, fnCtx);
|
|
60
|
+
const optionalFlags = paramOptionalFlags(method.params);
|
|
57
61
|
return {
|
|
58
62
|
type: "FUNCTION",
|
|
59
63
|
name: method.name,
|
|
60
64
|
brief: method.brief,
|
|
61
65
|
description: method.brief,
|
|
62
66
|
...(generics !== "" ? { generics } : {}),
|
|
63
|
-
parameters: method.params.map((param) =>
|
|
67
|
+
parameters: method.params.map((param, index) =>
|
|
68
|
+
parameterElement(param, optionalFlags[index] ?? false, fnCtx),
|
|
69
|
+
),
|
|
64
70
|
returnvalues: method.returns.map((ret) => returnElement(ret, fnCtx)),
|
|
71
|
+
...(method.deprecated !== undefined ? { deprecated: method.deprecated } : {}),
|
|
65
72
|
};
|
|
66
73
|
}
|
|
67
74
|
|
|
75
|
+
// No `deprecated` spread here or on `variableElement`: both take a `LibraryField`,
|
|
76
|
+
// and a one-line `---@field` has no slot for a block tag in this dialect, so a
|
|
77
|
+
// property can never carry one.
|
|
68
78
|
function propertyElement(field: LibraryField, ctx: MapContext): Record<string, unknown> {
|
|
69
79
|
return {
|
|
70
80
|
name: field.name,
|
|
@@ -74,20 +84,52 @@ function propertyElement(field: LibraryField, ctx: MapContext): Record<string, u
|
|
|
74
84
|
};
|
|
75
85
|
}
|
|
76
86
|
|
|
87
|
+
// A module object's field lowered as a top-level `VARIABLE` — the ref-doc shape for a
|
|
88
|
+
// module constant (`squid.TRACE`), matching the emitter's module-level `export const`.
|
|
89
|
+
function variableElement(field: LibraryField, ctx: MapContext): Record<string, unknown> {
|
|
90
|
+
return {
|
|
91
|
+
type: "VARIABLE",
|
|
92
|
+
name: field.name,
|
|
93
|
+
brief: field.doc,
|
|
94
|
+
description: field.doc,
|
|
95
|
+
types: mapTokens(field.types, ctx),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
77
99
|
export function lowerLibraryModel(
|
|
78
100
|
model: LibraryModel,
|
|
79
|
-
{
|
|
101
|
+
{
|
|
102
|
+
namespace,
|
|
103
|
+
typeRenames,
|
|
104
|
+
externalTypes,
|
|
105
|
+
}: {
|
|
106
|
+
namespace: string;
|
|
107
|
+
typeRenames?: Record<string, string>;
|
|
108
|
+
externalTypes?: Record<string, ExternalTypeRef> | undefined;
|
|
109
|
+
},
|
|
80
110
|
): unknown {
|
|
81
|
-
const ctx = buildModelContext(model, typeRenames);
|
|
111
|
+
const ctx = buildModelContext(model, typeRenames, externalTypes);
|
|
82
112
|
const elements: Record<string, unknown>[] = [];
|
|
83
113
|
|
|
84
114
|
for (const fn of model.moduleFunctions) {
|
|
115
|
+
if (!isPublicMethod(fn)) continue;
|
|
85
116
|
elements.push(functionElement(fn, ctx));
|
|
86
117
|
}
|
|
87
118
|
|
|
88
119
|
for (const iface of model.interfaces) {
|
|
89
120
|
const ifaceCtx = scopeGenerics(ctx, iface.generics);
|
|
90
|
-
|
|
121
|
+
// The module object's public fields are module-level constants, lowered as top-level
|
|
122
|
+
// VARIABLE elements rather than a TYPEDEF named after the class.
|
|
123
|
+
if (iface.name === model.moduleObject) {
|
|
124
|
+
for (const field of iface.fields) {
|
|
125
|
+
if (!isPublicField(field)) continue;
|
|
126
|
+
elements.push(variableElement(field, ifaceCtx));
|
|
127
|
+
}
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const functions = iface.methods
|
|
131
|
+
.filter(isPublicMethod)
|
|
132
|
+
.map((method) => functionElement(method, ifaceCtx));
|
|
91
133
|
const properties = iface.fields
|
|
92
134
|
.filter(isPublicField)
|
|
93
135
|
.map((field) => propertyElement(field, ifaceCtx));
|
|
@@ -96,18 +138,26 @@ export function lowerLibraryModel(
|
|
|
96
138
|
name: sanitizeTypeName(iface.name),
|
|
97
139
|
...(functions.length > 0 ? { functions } : {}),
|
|
98
140
|
...(properties.length > 0 ? { properties } : {}),
|
|
141
|
+
...(iface.deprecated !== undefined ? { deprecated: iface.deprecated } : {}),
|
|
99
142
|
});
|
|
100
143
|
}
|
|
101
144
|
|
|
102
145
|
for (const alias of model.aliases) {
|
|
103
|
-
elements.push({
|
|
146
|
+
elements.push({
|
|
147
|
+
type: "TYPEDEF",
|
|
148
|
+
name: sanitizeTypeName(alias.name),
|
|
149
|
+
...(alias.deprecated !== undefined ? { deprecated: alias.deprecated } : {}),
|
|
150
|
+
});
|
|
104
151
|
}
|
|
105
152
|
|
|
106
|
-
// The module's own `@class`
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
|
|
153
|
+
// The module's own `@class` carries the library's summary; use it as the page
|
|
154
|
+
// description so a LuaLS-sourced library reads with an intro like every other `/api`
|
|
155
|
+
// page, rather than opening on a bare provenance block. `brief` is its first line.
|
|
156
|
+
// A tracked `moduleObject` names it directly (squid's `Squid` != namespace `squid`);
|
|
157
|
+
// otherwise fall back to the class named for the namespace (`@class druid`).
|
|
158
|
+
const moduleClass = model.interfaces.find(
|
|
159
|
+
(iface) => iface.name === (model.moduleObject ?? namespace),
|
|
160
|
+
);
|
|
111
161
|
const description = moduleClass?.brief ?? "";
|
|
112
162
|
const brief = description.split("\n")[0] ?? "";
|
|
113
163
|
|
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
* sorted-unique token list so the gap is visible instead of silent.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import {
|
|
14
|
+
buildModelContext,
|
|
15
|
+
type ExternalTypeRef,
|
|
16
|
+
isPublicField,
|
|
17
|
+
isPublicMethod,
|
|
18
|
+
} from "./emit-library-dts";
|
|
13
19
|
import { type MapContext, mapLualsType, scopeGenerics } from "./map-luals-types";
|
|
14
20
|
import type { LibraryGeneric, LibraryModel } from "./parse-luals";
|
|
15
21
|
|
|
@@ -28,22 +34,22 @@ function round3(value: number): number {
|
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
/**
|
|
31
|
-
* Build the fidelity report for one namespace.
|
|
32
|
-
*
|
|
33
|
-
* resolves
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
+
* Build the fidelity report for one namespace. Tokens map through the same
|
|
38
|
+
* `buildModelContext` the emitter and the api-doc lowering use, so all three agree
|
|
39
|
+
* on what resolves — the model's own interface and alias names, plus the
|
|
40
|
+
* `externalTypes` tokens reached through a cross-module import — and all three
|
|
41
|
+
* reject an external alias that collides with a declared name. Every field, every
|
|
42
|
+
* param and return of every method and module function, and every alias expression
|
|
43
|
+
* is mapped; `undocumentedMembers` counts fields/methods/moduleFunctions whose doc
|
|
44
|
+
* or brief is empty. Deterministic; no I/O.
|
|
37
45
|
*/
|
|
38
46
|
export function buildFidelityReport(
|
|
39
47
|
namespace: string,
|
|
40
48
|
model: LibraryModel,
|
|
41
49
|
typeRenames: Record<string, string>,
|
|
50
|
+
externalTypes?: Record<string, ExternalTypeRef>,
|
|
42
51
|
): FidelityReport {
|
|
43
|
-
const
|
|
44
|
-
for (const iface of model.interfaces) knownNames.add(iface.name);
|
|
45
|
-
for (const alias of model.aliases) knownNames.add(alias.name);
|
|
46
|
-
const ctx: MapContext = { knownNames, typeRenames };
|
|
52
|
+
const ctx = buildModelContext(model, typeRenames, externalTypes);
|
|
47
53
|
|
|
48
54
|
let totalMembers = 0;
|
|
49
55
|
let totalTypeTokens = 0;
|
|
@@ -80,12 +86,17 @@ export function buildFidelityReport(
|
|
|
80
86
|
);
|
|
81
87
|
}
|
|
82
88
|
mapConstraints(iface.generics, ifaceCtx);
|
|
89
|
+
// A class `@overload fun(...)` is emitted as a call signature, so its type token
|
|
90
|
+
// counts toward coverage exactly like a field or method type.
|
|
91
|
+
for (const overload of iface.overloads ?? []) mapTokens([overload.type], ifaceCtx);
|
|
83
92
|
for (const field of iface.fields) {
|
|
93
|
+
if (!isPublicField(field)) continue;
|
|
84
94
|
totalMembers++;
|
|
85
95
|
if (undocumented(field.doc)) undocumentedMembers++;
|
|
86
96
|
mapTokens(field.types, ifaceCtx);
|
|
87
97
|
}
|
|
88
98
|
for (const method of iface.methods) {
|
|
99
|
+
if (!isPublicMethod(method)) continue;
|
|
89
100
|
totalMembers++;
|
|
90
101
|
if (undocumented(method.brief)) undocumentedMembers++;
|
|
91
102
|
const methodCtx = scopeGenerics(ifaceCtx, method.generics);
|
|
@@ -96,6 +107,7 @@ export function buildFidelityReport(
|
|
|
96
107
|
}
|
|
97
108
|
|
|
98
109
|
for (const fn of model.moduleFunctions) {
|
|
110
|
+
if (!isPublicMethod(fn)) continue;
|
|
99
111
|
totalMembers++;
|
|
100
112
|
if (undocumented(fn.brief)) undocumentedMembers++;
|
|
101
113
|
const fnCtx = scopeGenerics(ctx, fn.generics);
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* verbatim and the emitter sanitizes it later.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { luaMultiReturn } from "@defold-typescript/types";
|
|
16
17
|
import { CORE_TYPE_RENAMES } from "./sync-library-types";
|
|
17
18
|
|
|
18
19
|
export interface MapContext {
|
|
@@ -52,6 +53,24 @@ const SCALARS: Readonly<Record<string, string>> = {
|
|
|
52
53
|
any: "unknown",
|
|
53
54
|
};
|
|
54
55
|
|
|
56
|
+
/**
|
|
57
|
+
* LuaLS's bare `function` — any callable, signature unspecified. Params are `any[]`
|
|
58
|
+
* because under `strictFunctionTypes` an `unknown[]` rest would reject every concrete
|
|
59
|
+
* callback a consumer passes; the return stays `unknown` because return position is
|
|
60
|
+
* covariant, so `any` there would only leak unchecked values into call sites.
|
|
61
|
+
*/
|
|
62
|
+
const CALLABLE_UNSPECIFIED = "(...args: any[]) => unknown";
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* LuaLS's two placeholders: the throwaway param name `_`, and a bare `...` in vararg
|
|
66
|
+
* or return position. Neither declares a type at all, so lowering it to `unknown`
|
|
67
|
+
* loses nothing an author wrote — the same reasoning that already exempts `any` from
|
|
68
|
+
* the fallback count. The boundary is deliberate: an untyped `self` or `ctx` *is* an
|
|
69
|
+
* upstream omission and stays recorded.
|
|
70
|
+
*/
|
|
71
|
+
const LUALS_THROWAWAY_PARAM = "_";
|
|
72
|
+
export const LUALS_VARARG_TOKEN = "...";
|
|
73
|
+
|
|
55
74
|
/**
|
|
56
75
|
* Split `s` on every top-level occurrence of the single-character `sep`, honoring
|
|
57
76
|
* bracket depth and double-quoted string literals so a separator nested inside
|
|
@@ -126,7 +145,17 @@ function needsArrayParens(tsExpr: string): boolean {
|
|
|
126
145
|
);
|
|
127
146
|
}
|
|
128
147
|
|
|
129
|
-
|
|
148
|
+
/**
|
|
149
|
+
* The mapped `(params)` list and `ret` type of a `fun(...)` token, shared by the
|
|
150
|
+
* arrow-form `mapFunction` and the colon-return `mapLualsCallSignature`. The only
|
|
151
|
+
* difference between the two consumers is the separator (`=>` vs `:`), so both the
|
|
152
|
+
* param handling (typed/untyped/vararg) and the single/multi-return logic live here.
|
|
153
|
+
*/
|
|
154
|
+
function functionParts(
|
|
155
|
+
token: string,
|
|
156
|
+
ctx: MapContext,
|
|
157
|
+
unknowns: string[],
|
|
158
|
+
): { paramList: string; ret: string } {
|
|
130
159
|
const open = token.indexOf("(");
|
|
131
160
|
const close = matchBracket(token, open);
|
|
132
161
|
const paramsStr = token.slice(open + 1, close).trim();
|
|
@@ -136,21 +165,18 @@ function mapFunction(token: string, ctx: MapContext, unknowns: string[]): string
|
|
|
136
165
|
const paramList = params
|
|
137
166
|
.map((raw) => raw.trim())
|
|
138
167
|
.map((part) => {
|
|
139
|
-
if (part.startsWith(
|
|
168
|
+
if (part.startsWith(LUALS_VARARG_TOKEN)) {
|
|
140
169
|
const after = part.slice(3).trim();
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
} else {
|
|
145
|
-
element = "unknown";
|
|
146
|
-
unknowns.push("...");
|
|
147
|
-
}
|
|
170
|
+
const element = after.startsWith(":")
|
|
171
|
+
? mapToken(after.slice(1).trim(), ctx, unknowns)
|
|
172
|
+
: "unknown";
|
|
148
173
|
return `...args: ${needsArrayParens(element) ? `(${element})[]` : `${element}[]`}`;
|
|
149
174
|
}
|
|
150
175
|
const colon = splitTopLevel(part, ":");
|
|
151
176
|
if (colon.length < 2) {
|
|
152
|
-
// Untyped param (`self`, `
|
|
153
|
-
|
|
177
|
+
// Untyped param (`self`, `ctx`): a recorded gap, not a silent `any`. A bare `_`
|
|
178
|
+
// is LuaLS's deliberate throwaway and records nothing.
|
|
179
|
+
if (part !== LUALS_THROWAWAY_PARAM) unknowns.push(part);
|
|
154
180
|
return `${part}: unknown`;
|
|
155
181
|
}
|
|
156
182
|
const name = colon[0]?.trim() ?? "";
|
|
@@ -167,10 +193,18 @@ function mapFunction(token: string, ctx: MapContext, unknowns: string[]): string
|
|
|
167
193
|
if (retTokens.length === 1) {
|
|
168
194
|
ret = mapToken(retTokens[0] as string, ctx, unknowns);
|
|
169
195
|
} else if (retTokens.length > 1) {
|
|
170
|
-
const
|
|
171
|
-
ret =
|
|
196
|
+
const restTail = retTokens.at(-1) === LUALS_VARARG_TOKEN;
|
|
197
|
+
ret = luaMultiReturn(
|
|
198
|
+
retTokens.map((r) => mapToken(r, ctx, unknowns)),
|
|
199
|
+
restTail,
|
|
200
|
+
);
|
|
172
201
|
}
|
|
173
202
|
}
|
|
203
|
+
return { paramList, ret };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function mapFunction(token: string, ctx: MapContext, unknowns: string[]): string {
|
|
207
|
+
const { paramList, ret } = functionParts(token, ctx, unknowns);
|
|
174
208
|
return `(${paramList}) => ${ret}`;
|
|
175
209
|
}
|
|
176
210
|
|
|
@@ -200,15 +234,11 @@ function mapToken(raw: string, ctx: MapContext, unknowns: string[]): string {
|
|
|
200
234
|
|
|
201
235
|
if (token === "") return "unknown";
|
|
202
236
|
|
|
203
|
-
//
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// A `fun(...)` whose return follows the `)` keeps its return-type `|` inside the
|
|
211
|
-
// function; splitting the union first would cut `fun(): a|b` into `(fun) | b`.
|
|
237
|
+
// A `fun(...)` whose return follows the `)` keeps its return-type `|` and `?` inside
|
|
238
|
+
// the function; splitting the union first would cut `fun(): a|b` into `(fun) | b`, and
|
|
239
|
+
// peeling the optional suffix first would turn `fun(): number?` — a function with an
|
|
240
|
+
// optional *return* — into an optional function. The whole-function optional is spelled
|
|
241
|
+
// with explicit parentheses, `(fun(): number)?`, which does not match here.
|
|
212
242
|
// `fun()|nil` (a `|` right after the `)`) falls through to the union split.
|
|
213
243
|
if (/^fun\s*\(/.test(token)) {
|
|
214
244
|
const close = matchBracket(token, token.indexOf("("));
|
|
@@ -218,6 +248,13 @@ function mapToken(raw: string, ctx: MapContext, unknowns: string[]): string {
|
|
|
218
248
|
}
|
|
219
249
|
}
|
|
220
250
|
|
|
251
|
+
// Optional suffix.
|
|
252
|
+
if (token.length > 1 && token.endsWith("?")) {
|
|
253
|
+
const base = mapToken(token.slice(0, -1), ctx, unknowns);
|
|
254
|
+
const members = splitTopLevel(base, "|").map((m) => m.trim());
|
|
255
|
+
return members.includes("undefined") ? base : `${wrapForUnion(base)} | undefined`;
|
|
256
|
+
}
|
|
257
|
+
|
|
221
258
|
// Top-level union.
|
|
222
259
|
const unionParts = splitTopLevel(token, "|");
|
|
223
260
|
if (unionParts.length > 1) {
|
|
@@ -248,6 +285,15 @@ function mapToken(raw: string, ctx: MapContext, unknowns: string[]): string {
|
|
|
248
285
|
// String literal — passthrough.
|
|
249
286
|
if (token.startsWith('"') && token.endsWith('"')) return token;
|
|
250
287
|
|
|
288
|
+
// Signature-less callable. Placed after every composite branch so `function[]`,
|
|
289
|
+
// `function|nil`, and `fun(cb: function)` route through those first.
|
|
290
|
+
if (token === "function") return CALLABLE_UNSPECIFIED;
|
|
291
|
+
|
|
292
|
+
// Placeholder vararg in return position — not a `SCALARS` entry, which maps real Lua
|
|
293
|
+
// type names. Sits beside the callable branch so `...[]` and `...|nil` reach their
|
|
294
|
+
// structural handlers first.
|
|
295
|
+
if (token === LUALS_VARARG_TOKEN) return "unknown";
|
|
296
|
+
|
|
251
297
|
// Scalars.
|
|
252
298
|
const scalar = SCALARS[token];
|
|
253
299
|
if (scalar !== undefined) return scalar;
|
|
@@ -275,6 +321,24 @@ export function mapLualsType(token: string, ctx: MapContext): MapResult {
|
|
|
275
321
|
return { ts, unknowns };
|
|
276
322
|
}
|
|
277
323
|
|
|
324
|
+
/**
|
|
325
|
+
* Map a `fun(...)` token (a class `@overload`) to a TypeScript **call signature** —
|
|
326
|
+
* the colon-return form `(params): ret` an interface uses to become callable, not
|
|
327
|
+
* the `=>` arrow a field/param function type takes. Shares the exact param/return
|
|
328
|
+
* computation as `mapFunction`, so nested callback params and multi-returns map
|
|
329
|
+
* identically. Throws on a non-`fun` token; the parser only ever records `fun(...)`
|
|
330
|
+
* overloads, so this guards a programming error rather than user input.
|
|
331
|
+
*/
|
|
332
|
+
export function mapLualsCallSignature(token: string, ctx: MapContext): MapResult {
|
|
333
|
+
const trimmed = token.trim();
|
|
334
|
+
if (!/^fun\s*\(/.test(trimmed)) {
|
|
335
|
+
throw new Error(`mapLualsCallSignature: expected a "fun(...)" token, got "${token}".`);
|
|
336
|
+
}
|
|
337
|
+
const unknowns: string[] = [];
|
|
338
|
+
const { paramList, ret } = functionParts(trimmed, ctx, unknowns);
|
|
339
|
+
return { ts: `(${paramList}): ${ret}`, unknowns };
|
|
340
|
+
}
|
|
341
|
+
|
|
278
342
|
/**
|
|
279
343
|
* When `types` is exactly one `fun(self: <selfTypeName>, ...)` token — optionally
|
|
280
344
|
* unioned with `nil` — whose first parameter is `self` typed as the enclosing
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A line-oriented reader for the *public surface* of a plain-Lua module — the
|
|
3
|
+
* member names and parameter names a `local M = {} … return M` file declares.
|
|
4
|
+
*
|
|
5
|
+
* `sync-authored-types.ts` gates the authored/forked lane on a forked-vs-generated
|
|
6
|
+
* identity diff and records that there is no coverage comparison against a primary
|
|
7
|
+
* source. That holds for *types*: upstream Lua declares none. It does not hold for
|
|
8
|
+
* *surface*: names and arity are right there in the source, so a fork that drops a
|
|
9
|
+
* member or a trailing parameter is measurable. This module reads that side; the
|
|
10
|
+
* comparison lives in `authored-parity.ts`.
|
|
11
|
+
*
|
|
12
|
+
* The corpus is a handful of pinned files, so line scanning is what it needs — a
|
|
13
|
+
* Lua grammar would buy nothing here. Two rules keep it honest:
|
|
14
|
+
*
|
|
15
|
+
* - **Column 0 only.** A definition is a member when it starts the line. This is
|
|
16
|
+
* what keeps `nakama/util/log.lua`'s `M.log = noop` — reassigned inside three
|
|
17
|
+
* different function bodies — out of the surface, and it is why the reader never
|
|
18
|
+
* needs to know where a block ends.
|
|
19
|
+
* - **Loud failure over silent undercount.** A missing `return <name>` or a
|
|
20
|
+
* parameter list that does not close on its own line throws, as does a
|
|
21
|
+
* `return setmetatable(M, …)` whose metatable carries `__index` or is named by a
|
|
22
|
+
* variable whose keys this reader cannot see — either could add members no column-0
|
|
23
|
+
* scan will find. The whole point of the instrument is that a dropped member is
|
|
24
|
+
* visible; a parser that quietly skips what it cannot read would inflate every
|
|
25
|
+
* coverage number it feeds.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export interface LuaMember {
|
|
29
|
+
name: string;
|
|
30
|
+
/** Absent for a non-callable field (`M.SOME_CONSTANT = "X"`), so a field can
|
|
31
|
+
* never be confused with a zero-arity function. */
|
|
32
|
+
params?: string[];
|
|
33
|
+
/** True when the definition ends in `...`, which is not a named parameter. */
|
|
34
|
+
varargs: boolean;
|
|
35
|
+
/** The `---` LuaDoc block immediately above the definition, comment markers
|
|
36
|
+
* stripped, one interior blank line crossed; empty when the block is absent or
|
|
37
|
+
* opens with a plain `--`. */
|
|
38
|
+
doc: string;
|
|
39
|
+
/** True when a comment block *was* there and the `---` rule declined it, which `doc`
|
|
40
|
+
* alone cannot say — an empty `doc` otherwise means upstream documented nothing. */
|
|
41
|
+
refusedDoc: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface LuaSurface {
|
|
45
|
+
moduleLocal: string;
|
|
46
|
+
members: LuaMember[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const RETURN_LINE = /^return\s+([A-Za-z_][A-Za-z0-9_]*)\s*;?\s*$/;
|
|
50
|
+
const RETURN_SETMETATABLE = /^return\s+setmetatable\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*,/;
|
|
51
|
+
const IDENTIFIER = "[A-Za-z_][A-Za-z0-9_]*";
|
|
52
|
+
|
|
53
|
+
/** The metatable argument of a `return setmetatable(<name>, …)`, refused unless its
|
|
54
|
+
* keys are readable and none of them delegates. `text` runs from the comma to the end
|
|
55
|
+
* of the source rather than to the matching brace: over-reading can only refuse a file
|
|
56
|
+
* loudly, where under-reading would accept a delegating module and report its short
|
|
57
|
+
* surface as complete. */
|
|
58
|
+
function assertNoDelegation(text: string): void {
|
|
59
|
+
if (text.trimStart().startsWith("{") === false) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
"lua surface: the returned `setmetatable` is given a metatable this reader cannot see the keys of, so a delegated member would be invisible — inline the table literal or measure the module another way.",
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
if (text.includes("__index")) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
"lua surface: the returned metatable carries `__index`, so members reached through it would be invisible to a column-0 scan and the surface would read as complete while short.",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function resolveModuleLocal(lines: string[]): string {
|
|
72
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
73
|
+
const line = lines[index] as string;
|
|
74
|
+
const bare = RETURN_LINE.exec(line);
|
|
75
|
+
if (bare) return bare[1] as string;
|
|
76
|
+
const wrapped = RETURN_SETMETATABLE.exec(line);
|
|
77
|
+
if (wrapped) {
|
|
78
|
+
assertNoDelegation(
|
|
79
|
+
[line.slice((wrapped[0] as string).length), ...lines.slice(index + 1)].join("\n"),
|
|
80
|
+
);
|
|
81
|
+
return wrapped[1] as string;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
throw new Error(
|
|
85
|
+
"lua surface: the source has no trailing `return <name>`, so the module-local name cannot be derived.",
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The parameter names between the parentheses opened at `open` on `line`, with a
|
|
90
|
+
* `...` tail reported separately. Throws when the list does not close on the same
|
|
91
|
+
* line rather than dropping the member. */
|
|
92
|
+
function readParams(
|
|
93
|
+
line: string,
|
|
94
|
+
open: number,
|
|
95
|
+
lineNumber: number,
|
|
96
|
+
): { params: string[]; varargs: boolean } {
|
|
97
|
+
const close = line.indexOf(")", open);
|
|
98
|
+
if (close === -1) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`lua surface: line ${lineNumber} opens a parameter list that does not close on the same line — ${line.trim()}`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const raw = line
|
|
104
|
+
.slice(open + 1, close)
|
|
105
|
+
.split(",")
|
|
106
|
+
.map((part) => part.trim())
|
|
107
|
+
.filter((part) => part !== "");
|
|
108
|
+
return { params: raw.filter((part) => part !== "..."), varargs: raw.includes("...") };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The contiguous run of comment lines ending at `index - 1`, in source order. */
|
|
112
|
+
function readSegment(lines: string[], index: number): string[] {
|
|
113
|
+
const segment: string[] = [];
|
|
114
|
+
for (let cursor = index - 1; cursor >= 0; cursor -= 1) {
|
|
115
|
+
const line = (lines[cursor] as string).trim();
|
|
116
|
+
if (!line.startsWith("--") || line.startsWith("--[[")) break;
|
|
117
|
+
segment.unshift(line);
|
|
118
|
+
}
|
|
119
|
+
return segment;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The contiguous comment block ending at `index - 1`, kept only when it opens
|
|
123
|
+
* with `---`. LuaDoc in this corpus is a `---` summary followed by plain `--`
|
|
124
|
+
* `@param`/`@return` lines, so the marker on the *first* line is what decides.
|
|
125
|
+
*
|
|
126
|
+
* A single blank line inside the run is crossed, and only when the segment
|
|
127
|
+
* attached to the definition opens no block of its own — upstream wraps a long
|
|
128
|
+
* `@param` list around a blank, which would otherwise truncate the block to its
|
|
129
|
+
* tag half and lose the summary above. A segment carrying its own `---` is never
|
|
130
|
+
* absorbed into the one below it, so a blank-separated section header stays out. */
|
|
131
|
+
function readDoc(lines: string[], index: number): Pick<LuaMember, "doc" | "refusedDoc"> {
|
|
132
|
+
const attached = readSegment(lines, index);
|
|
133
|
+
if (attached.length === 0) return { doc: "", refusedDoc: false };
|
|
134
|
+
const above = index - attached.length - 1;
|
|
135
|
+
const block =
|
|
136
|
+
attached.some((line) => line.startsWith("---")) ||
|
|
137
|
+
above < 0 ||
|
|
138
|
+
(lines[above] as string).trim() !== ""
|
|
139
|
+
? attached
|
|
140
|
+
: [...readSegment(lines, above), ...attached];
|
|
141
|
+
if (!(block[0] as string).startsWith("---")) return { doc: "", refusedDoc: true };
|
|
142
|
+
const doc = block
|
|
143
|
+
.map((line) => line.replace(/^-+\s?/, "").trimEnd())
|
|
144
|
+
.join("\n")
|
|
145
|
+
.trim();
|
|
146
|
+
return { doc, refusedDoc: false };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The public surface of a Lua module source: the module-local name it returns and
|
|
151
|
+
* every member assigned to it at column 0, in source order. A name defined more
|
|
152
|
+
* than once keeps its first position and its last definition, matching Lua.
|
|
153
|
+
*/
|
|
154
|
+
export function parseLuaSurface(source: string): LuaSurface {
|
|
155
|
+
const lines = source.split("\n");
|
|
156
|
+
const moduleLocal = resolveModuleLocal(lines);
|
|
157
|
+
const definition = new RegExp(`^function\\s+${moduleLocal}\\.(${IDENTIFIER})\\s*\\(`);
|
|
158
|
+
const assignment = new RegExp(`^${moduleLocal}\\.(${IDENTIFIER})\\s*=\\s*(.*)$`);
|
|
159
|
+
const assignedFunction = /^function\s*\(/;
|
|
160
|
+
|
|
161
|
+
const members = new Map<string, LuaMember>();
|
|
162
|
+
const record = (member: LuaMember): void => {
|
|
163
|
+
members.set(member.name, member);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
for (const [index, line] of lines.entries()) {
|
|
167
|
+
const defined = definition.exec(line);
|
|
168
|
+
if (defined) {
|
|
169
|
+
const { params, varargs } = readParams(line, defined[0].length - 1, index + 1);
|
|
170
|
+
record({ name: defined[1] as string, params, varargs, ...readDoc(lines, index) });
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const assigned = assignment.exec(line);
|
|
174
|
+
if (!assigned) continue;
|
|
175
|
+
const name = assigned[1] as string;
|
|
176
|
+
const rhs = assigned[2] as string;
|
|
177
|
+
if (!assignedFunction.test(rhs)) {
|
|
178
|
+
record({ name, varargs: false, ...readDoc(lines, index) });
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const open = line.indexOf("(", line.length - rhs.length);
|
|
182
|
+
const { params, varargs } = readParams(line, open, index + 1);
|
|
183
|
+
record({ name, params, varargs, ...readDoc(lines, index) });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return { moduleLocal, members: [...members.values()] };
|
|
187
|
+
}
|