@defold-typescript/types 0.8.4 → 0.10.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/README.md +1 -1
- package/api-targets.json +18 -0
- package/generated/b2d_body.d.ts +348 -0
- package/generated/buffer.d.ts +5 -2
- package/generated/camera.d.ts +236 -1
- package/generated/collectionfactory.d.ts +4 -0
- package/generated/factory.d.ts +4 -0
- package/generated/font.d.ts +81 -0
- package/generated/go.d.ts +53 -0
- package/generated/gui.d.ts +265 -50
- package/generated/html5.d.ts +17 -13
- package/generated/http.d.ts +16 -0
- package/generated/image.d.ts +24 -0
- package/generated/json.d.ts +2 -0
- package/generated/kinds/gui-script.d.ts +2 -0
- package/generated/kinds/render-script.d.ts +2 -0
- package/generated/kinds/script.d.ts +2 -0
- package/generated/model.d.ts +14 -0
- package/generated/msg.d.ts +17 -11
- package/generated/particlefx.d.ts +6 -0
- package/generated/physics.d.ts +32 -0
- package/generated/profiler.d.ts +14 -0
- package/generated/render.d.ts +109 -0
- package/generated/resource.d.ts +223 -0
- package/generated/socket.d.ts +640 -11
- package/generated/sound.d.ts +6 -0
- package/generated/sprite.d.ts +5 -0
- package/generated/sys.d.ts +136 -0
- package/generated/tilemap.d.ts +2 -0
- package/generated/timer.d.ts +3 -0
- package/generated/vmath.d.ts +109 -93
- package/generated/window.d.ts +23 -0
- package/index.d.ts +8 -0
- package/package.json +4 -1
- package/scripts/fidelity-audit.ts +26 -3
- package/scripts/fidelity-baseline.json +18 -2
- package/scripts/materialize-version.ts +23 -0
- package/scripts/regen.ts +5 -1
- package/scripts/signature-store-io.ts +18 -0
- package/scripts/sync-api-docs.ts +208 -12
- package/src/core-types.ts +13 -6
- package/src/doc-comment.ts +42 -5
- package/src/emit-dts.ts +134 -5
- package/src/engine-globals.d.ts +2 -0
- package/src/example-store.ts +11 -7
- package/src/index.ts +18 -1
- package/src/lifecycle.ts +383 -4
- package/src/msg-overloads.d.ts +3 -0
- package/src/signature-store.ts +20 -0
- package/src/socket-types.d.ts +48 -0
package/generated/window.d.ts
CHANGED
|
@@ -51,8 +51,11 @@ declare global {
|
|
|
51
51
|
* On platforms that does not support dimming, `window.DIMMING_UNKNOWN` is always returned.
|
|
52
52
|
*
|
|
53
53
|
* @returns The mode for screen dimming
|
|
54
|
+
*
|
|
54
55
|
* - `window.DIMMING_UNKNOWN`
|
|
56
|
+
*
|
|
55
57
|
* - `window.DIMMING_ON`
|
|
58
|
+
*
|
|
56
59
|
* - `window.DIMMING_OFF`
|
|
57
60
|
*/
|
|
58
61
|
function get_dim_mode(): Opaque<"constant">;
|
|
@@ -74,15 +77,24 @@ declare global {
|
|
|
74
77
|
* this returns the full window size and zero insets.
|
|
75
78
|
*
|
|
76
79
|
* @returns safe area data
|
|
80
|
+
*
|
|
77
81
|
* `safe_area`
|
|
78
82
|
* table table containing these keys:
|
|
83
|
+
*
|
|
79
84
|
* - number `x`
|
|
85
|
+
*
|
|
80
86
|
* - number `y`
|
|
87
|
+
*
|
|
81
88
|
* - number `width`
|
|
89
|
+
*
|
|
82
90
|
* - number `height`
|
|
91
|
+
*
|
|
83
92
|
* - number `inset_left`
|
|
93
|
+
*
|
|
84
94
|
* - number `inset_top`
|
|
95
|
+
*
|
|
85
96
|
* - number `inset_right`
|
|
97
|
+
*
|
|
86
98
|
* - number `inset_bottom`
|
|
87
99
|
*/
|
|
88
100
|
function get_safe_area(): { safe_area: { x: number; y: number; width: number; height: number; inset_left: number; inset_top: number; inset_right: number; inset_bottom: number } };
|
|
@@ -96,7 +108,9 @@ declare global {
|
|
|
96
108
|
* This function has no effect on platforms that does not support dimming.
|
|
97
109
|
*
|
|
98
110
|
* @param mode - The mode for screen dimming
|
|
111
|
+
*
|
|
99
112
|
* - `window.DIMMING_ON`
|
|
113
|
+
*
|
|
100
114
|
* - `window.DIMMING_OFF`
|
|
101
115
|
*/
|
|
102
116
|
function set_dim_mode(mode: Opaque<"constant">): void;
|
|
@@ -104,18 +118,27 @@ declare global {
|
|
|
104
118
|
* Sets a window event listener. Only one window event listener can be set at a time.
|
|
105
119
|
*
|
|
106
120
|
* @param callback - A callback which receives info about window events. Pass an empty function or `nil` if you no longer wish to receive callbacks.
|
|
121
|
+
*
|
|
107
122
|
* `self`
|
|
108
123
|
* object The calling script
|
|
109
124
|
* `event`
|
|
110
125
|
* constant The type of event. Can be one of these:
|
|
126
|
+
*
|
|
111
127
|
* - `window.WINDOW_EVENT_FOCUS_LOST`
|
|
128
|
+
*
|
|
112
129
|
* - `window.WINDOW_EVENT_FOCUS_GAINED`
|
|
130
|
+
*
|
|
113
131
|
* - `window.WINDOW_EVENT_RESIZED`
|
|
132
|
+
*
|
|
114
133
|
* - `window.WINDOW_EVENT_ICONIFIED`
|
|
134
|
+
*
|
|
115
135
|
* - `window.WINDOW_EVENT_DEICONIFIED`
|
|
136
|
+
*
|
|
116
137
|
* `data`
|
|
117
138
|
* table The callback value `data` is a table which currently holds these values
|
|
139
|
+
*
|
|
118
140
|
* - number `width`: The width of a resize event. nil otherwise.
|
|
141
|
+
*
|
|
119
142
|
* - number `height`: The height of a resize event. nil otherwise.
|
|
120
143
|
* @example
|
|
121
144
|
* ```ts
|
package/index.d.ts
CHANGED
|
@@ -34,6 +34,7 @@ import "./generated/push";
|
|
|
34
34
|
import "./generated/render";
|
|
35
35
|
import "./generated/resource";
|
|
36
36
|
import "./generated/socket";
|
|
37
|
+
import "./src/socket-types";
|
|
37
38
|
import "./generated/sound";
|
|
38
39
|
import "./generated/sprite";
|
|
39
40
|
import "./generated/sys";
|
|
@@ -57,7 +58,14 @@ export {
|
|
|
57
58
|
type Vector3,
|
|
58
59
|
type Vector4,
|
|
59
60
|
} from "./src/core-types";
|
|
61
|
+
export { examplesHtmlToMarkdown, htmlToCodeText, htmlToDocText } from "./src/doc-comment";
|
|
60
62
|
export { type EmitOptions, emitDeclarations } from "./src/emit-dts";
|
|
63
|
+
export {
|
|
64
|
+
hashExampleSource,
|
|
65
|
+
lookupTranslation,
|
|
66
|
+
type Translation,
|
|
67
|
+
type TranslationStore,
|
|
68
|
+
} from "./src/example-store";
|
|
61
69
|
export {
|
|
62
70
|
defineGuiScript,
|
|
63
71
|
defineRenderScript,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defold-typescript/types",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "TypeScript types for the Defold engine's Lua APIs.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -30,6 +30,9 @@
|
|
|
30
30
|
"./core-types": {
|
|
31
31
|
"types": "./src/core-types.ts"
|
|
32
32
|
},
|
|
33
|
+
"./lifecycle": {
|
|
34
|
+
"types": "./src/lifecycle.ts"
|
|
35
|
+
},
|
|
33
36
|
"./timers": {
|
|
34
37
|
"types": "./src/timers.d.ts"
|
|
35
38
|
},
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
ARBITRARY_TABLE_SLOTS,
|
|
5
5
|
applyNestedFieldCurations,
|
|
6
6
|
buildTableDocResolver,
|
|
7
|
+
HANDLE_METHOD_LOCAL,
|
|
7
8
|
HOMOGENEOUS_ARRAY_SLOTS,
|
|
8
9
|
MAPPING_TABLE_SLOTS,
|
|
9
10
|
type NestedMapping,
|
|
@@ -76,6 +77,26 @@ function trailingOptionalCutoff(params: readonly Record<string, unknown>[]): num
|
|
|
76
77
|
return cutoff;
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
// Colon `<receiver>:<method>` FUNCTION elements are emitted as method-bearing
|
|
81
|
+
// interfaces (see emit-dts collectHandleMethodGroups). Before that recovery they
|
|
82
|
+
// emitted nothing yet were never counted as a droppedMembers loss — a blind spot
|
|
83
|
+
// that let `socket` read `droppedMembers: 0` over an incomplete surface. Count
|
|
84
|
+
// them honestly: with emission on, only a malformed colon name (non-identifier
|
|
85
|
+
// segment) is still a loss; with emission off, every colon method is dropped.
|
|
86
|
+
export function countDroppedHandleMethods(doc: unknown, namespace: string, emit = true): number {
|
|
87
|
+
const prefix = `${namespace}.`;
|
|
88
|
+
let dropped = 0;
|
|
89
|
+
for (const element of elementsOf(doc)) {
|
|
90
|
+
if (element.type !== "FUNCTION" || typeof element.name !== "string") continue;
|
|
91
|
+
const local = element.name.startsWith(prefix)
|
|
92
|
+
? element.name.slice(prefix.length)
|
|
93
|
+
: element.name;
|
|
94
|
+
if (!local.includes(":")) continue;
|
|
95
|
+
if (!emit || !HANDLE_METHOD_LOCAL.test(local)) dropped += 1;
|
|
96
|
+
}
|
|
97
|
+
return dropped;
|
|
98
|
+
}
|
|
99
|
+
|
|
79
100
|
function auditEntry(
|
|
80
101
|
entry: ModuleManifestEntry,
|
|
81
102
|
knownConstantFqns: ReadonlySet<string>,
|
|
@@ -356,9 +377,11 @@ function auditEntry(
|
|
|
356
377
|
unknownTokens: [...unknown].sort(),
|
|
357
378
|
recordTables,
|
|
358
379
|
multiReturn,
|
|
359
|
-
droppedMembers:
|
|
360
|
-
|
|
361
|
-
|
|
380
|
+
droppedMembers:
|
|
381
|
+
generateModuleDeclaration(entry, {
|
|
382
|
+
knownConstantFqns: NO_KNOWN_CONSTANTS,
|
|
383
|
+
}).dropped.filter((name) => !OVERLOAD_COVERED_SKIPS.has(name)).length +
|
|
384
|
+
countDroppedHandleMethods(entry.doc, entry.namespace),
|
|
362
385
|
optionalAsRequired,
|
|
363
386
|
};
|
|
364
387
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"droppedMembers": 0,
|
|
8
8
|
"optionalAsRequired": 0
|
|
9
9
|
},
|
|
10
|
-
"
|
|
10
|
+
"b2d.body": {
|
|
11
11
|
"droppedElements": 0,
|
|
12
12
|
"unknownTokens": [],
|
|
13
13
|
"recordTables": 0,
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"droppedMembers": 0,
|
|
16
16
|
"optionalAsRequired": 0
|
|
17
17
|
},
|
|
18
|
-
"
|
|
18
|
+
"buffer": {
|
|
19
19
|
"droppedElements": 0,
|
|
20
20
|
"unknownTokens": [],
|
|
21
21
|
"recordTables": 0,
|
|
@@ -23,6 +23,14 @@
|
|
|
23
23
|
"droppedMembers": 0,
|
|
24
24
|
"optionalAsRequired": 0
|
|
25
25
|
},
|
|
26
|
+
"camera": {
|
|
27
|
+
"droppedElements": 0,
|
|
28
|
+
"unknownTokens": [],
|
|
29
|
+
"recordTables": 1,
|
|
30
|
+
"multiReturn": 0,
|
|
31
|
+
"droppedMembers": 0,
|
|
32
|
+
"optionalAsRequired": 7
|
|
33
|
+
},
|
|
26
34
|
"collectionfactory": {
|
|
27
35
|
"droppedElements": 0,
|
|
28
36
|
"unknownTokens": [],
|
|
@@ -55,6 +63,14 @@
|
|
|
55
63
|
"droppedMembers": 0,
|
|
56
64
|
"optionalAsRequired": 0
|
|
57
65
|
},
|
|
66
|
+
"font": {
|
|
67
|
+
"droppedElements": 0,
|
|
68
|
+
"unknownTokens": [],
|
|
69
|
+
"recordTables": 1,
|
|
70
|
+
"multiReturn": 0,
|
|
71
|
+
"droppedMembers": 0,
|
|
72
|
+
"optionalAsRequired": 0
|
|
73
|
+
},
|
|
58
74
|
"go": {
|
|
59
75
|
"droppedElements": 0,
|
|
60
76
|
"unknownTokens": [],
|
|
@@ -4,10 +4,33 @@ import {
|
|
|
4
4
|
type ApiTarget,
|
|
5
5
|
generateModuleDeclaration,
|
|
6
6
|
generateVersionIndex,
|
|
7
|
+
KIND_MODULE_MANIFEST,
|
|
8
|
+
LUA_STDLIB_REFERENCES,
|
|
7
9
|
type ResolveTargetOptions,
|
|
8
10
|
resolveTargetModules,
|
|
9
11
|
} from "./regen";
|
|
10
12
|
|
|
13
|
+
export { KIND_MODULE_MANIFEST } from "./regen";
|
|
14
|
+
|
|
15
|
+
export interface RenderMaterializedKindIndexOptions {
|
|
16
|
+
readonly kind: string;
|
|
17
|
+
readonly universalModules: readonly string[];
|
|
18
|
+
readonly restrictedModule: string | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Render one per-kind subpath for the materialized surface, mirroring
|
|
22
|
+
// `generateKindIndex` but re-exporting the factory from the installed
|
|
23
|
+
// `@defold-typescript/types/lifecycle` subpath (the materialized surface has no
|
|
24
|
+
// relative `src/lifecycle` to reach). Pure: returns a string, no FS.
|
|
25
|
+
export function renderMaterializedKindIndex(opts: RenderMaterializedKindIndexOptions): string {
|
|
26
|
+
const entry = KIND_MODULE_MANIFEST.find((e) => e.kind === opts.kind);
|
|
27
|
+
if (!entry) throw new Error(`unknown script kind: ${opts.kind}`);
|
|
28
|
+
const universal = [...new Set(["engine-globals", ...opts.universalModules])].sort();
|
|
29
|
+
const lines = universal.map((mod) => `import "../${mod}";`);
|
|
30
|
+
if (opts.restrictedModule) lines.push(`import "../${opts.restrictedModule}";`);
|
|
31
|
+
return `${LUA_STDLIB_REFERENCES}${lines.join("\n")}\n\nexport { ${entry.factory} } from "@defold-typescript/types/lifecycle";\nexport type { ScriptProperties, ScriptProperty } from "@defold-typescript/types/lifecycle";\n`;
|
|
32
|
+
}
|
|
33
|
+
|
|
11
34
|
export interface MaterializeVersionedSurfaceOptions {
|
|
12
35
|
readonly destDir: string;
|
|
13
36
|
readonly resolveOpts?: ResolveTargetOptions;
|
package/scripts/regen.ts
CHANGED
|
@@ -36,6 +36,10 @@ export interface ApiTarget {
|
|
|
36
36
|
readonly coreTypesImport: string;
|
|
37
37
|
readonly source?: ApiTargetSource;
|
|
38
38
|
readonly modules: readonly ApiTargetModule[];
|
|
39
|
+
// Docs-only Lua stdlib pages (no generated `.d.ts`): vendored fixtures the
|
|
40
|
+
// docs-site pages under the "Lua standard library" category. Never read by
|
|
41
|
+
// regen/MODULE_MANIFEST; surfaced here so the registry stays type-honest.
|
|
42
|
+
readonly luaStdlib?: readonly { readonly namespace: string; readonly fixture: string }[];
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
const REGISTRY_PATH = resolve(import.meta.dir, "..", "api-targets.json");
|
|
@@ -226,7 +230,7 @@ export const RESTRICTED_NAMESPACES: Readonly<Record<string, string>> = {
|
|
|
226
230
|
// The Lua standard library rides every per-kind subpath the same as the full
|
|
227
231
|
// entrypoint. Triple-slash directives must precede the first statement, so they
|
|
228
232
|
// lead the generated kind index.
|
|
229
|
-
const LUA_STDLIB_REFERENCES =
|
|
233
|
+
export const LUA_STDLIB_REFERENCES =
|
|
230
234
|
'/// <reference types="lua-types/5.1" />\n/// <reference types="lua-types/special/jit-only" />\n';
|
|
231
235
|
|
|
232
236
|
const UNIVERSAL_EXTRA_IMPORTS: readonly string[] = [
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import type { SignatureStore } from "../src/signature-store";
|
|
4
|
+
|
|
5
|
+
// Build-time only: lives under `scripts/` (never reachable from the shipped
|
|
6
|
+
// `src/index.ts` graph) so its `node:fs` import cannot leak into a consumer's
|
|
7
|
+
// typecheck. `src/signature-store.ts` stays pure for exactly that reason.
|
|
8
|
+
const IO_SIGNATURES_PATH = resolve(import.meta.dir, "..", "signatures", "io.json");
|
|
9
|
+
|
|
10
|
+
export function loadSignatures(path: string = IO_SIGNATURES_PATH): SignatureStore {
|
|
11
|
+
let raw: string;
|
|
12
|
+
try {
|
|
13
|
+
raw = readFileSync(path, "utf8");
|
|
14
|
+
} catch {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
return JSON.parse(raw) as SignatureStore;
|
|
18
|
+
}
|
package/scripts/sync-api-docs.ts
CHANGED
|
@@ -14,6 +14,11 @@ export interface SyncManifestEntry {
|
|
|
14
14
|
readonly namespace: string;
|
|
15
15
|
readonly zipEntry: string;
|
|
16
16
|
readonly fixture: string;
|
|
17
|
+
// Extra ref-doc.zip entries whose elements are merged into this namespace at
|
|
18
|
+
// sync time. A namespace whose Lua surface is split across several upstream
|
|
19
|
+
// docs (e.g. `sys`) is reassembled before parse, so the emitter still sees one
|
|
20
|
+
// fixture, one namespace.
|
|
21
|
+
readonly mergeEntries?: readonly string[];
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
// namespace -> ref-doc.zip entry. Entry paths do not match the namespace (gui ->
|
|
@@ -21,12 +26,24 @@ export interface SyncManifestEntry {
|
|
|
21
26
|
// so each path is confirmed against the pinned release artifact, not derived.
|
|
22
27
|
export const SYNC_MANIFEST: readonly SyncManifestEntry[] = [
|
|
23
28
|
entry("b2d", "doc/scripts-box2d-script_box2d.cpp_doc.json"),
|
|
29
|
+
// On-disk name uses underscores to avoid a double-dotted filename; the namespace
|
|
30
|
+
// string stays `b2d.body` so /api/b2d.body routes correctly and the emitter
|
|
31
|
+
// produces `declare global { namespace b2d.body { ... } }`. Use the non-`_defold`
|
|
32
|
+
// variant — the `_defold` variant has duplicate `get_world_center` elements.
|
|
33
|
+
entry(
|
|
34
|
+
"b2d.body",
|
|
35
|
+
"doc/scripts-box2d-script_box2d_body.cpp_doc.json",
|
|
36
|
+
"fixtures/b2d_body_doc.json",
|
|
37
|
+
),
|
|
24
38
|
entry("buffer", "doc/scripts-script_buffer.cpp_doc.json"),
|
|
25
|
-
|
|
39
|
+
// camera's Lua surface lives in the render-script doc, not the scripts-script
|
|
40
|
+
// one (`doc/scripts-script_camera.cpp_doc.json` is empty upstream in 1.12.4).
|
|
41
|
+
entry("camera", "doc/render-render_script_camera.cpp_doc.json"),
|
|
26
42
|
entry("collectionfactory", "doc/scripts-script_collection_factory.cpp_doc.json"),
|
|
27
43
|
entry("collectionproxy", "doc/scripts-script_collectionproxy.cpp_doc.json"),
|
|
28
44
|
entry("crash", "doc/script_crash.cpp_doc.json"),
|
|
29
45
|
entry("factory", "doc/scripts-script_factory.cpp_doc.json"),
|
|
46
|
+
entry("font", "doc/scripts-script_font.cpp_doc.json"),
|
|
30
47
|
entry("go", "doc/gameobject_script.cpp_doc.json"),
|
|
31
48
|
entry("graphics", "doc/src-script_graphics.cpp_doc.json"),
|
|
32
49
|
entry("gui", "doc/gui_script.cpp_doc.json"),
|
|
@@ -46,9 +63,16 @@ export const SYNC_MANIFEST: readonly SyncManifestEntry[] = [
|
|
|
46
63
|
entry("socket", "doc/luasocket-luasocket.doc_h_doc.json"),
|
|
47
64
|
entry("sound", "doc/scripts-script_sound.cpp_doc.json"),
|
|
48
65
|
entry("sprite", "doc/scripts-script_sprite.cpp_doc.json"),
|
|
49
|
-
// The `sys` surface is split across
|
|
50
|
-
// (src-script_sys)
|
|
51
|
-
|
|
66
|
+
// The `sys` Lua surface is split across three docs in ref-doc.zip: the core
|
|
67
|
+
// (src-script_sys), the gamesys subset (`load_buffer`/`load_buffer_async` plus
|
|
68
|
+
// the `REQUEST_STATUS_*` constants), and the engine doc (`set_engine_throttle`/
|
|
69
|
+
// `set_render_enable`). `mergeEntries` folds all three into one fixture at sync
|
|
70
|
+
// time. The `*_ddf.proto` doc is deliberately excluded — it is message-shaped
|
|
71
|
+
// and owned by the builtin-messages catalog.
|
|
72
|
+
entry("sys", "doc/src-script_sys.cpp_doc.json", "fixtures/sys_doc.json", [
|
|
73
|
+
"doc/scripts-script_sys_gamesys.cpp_doc.json",
|
|
74
|
+
"doc/script-script_engine.cpp_doc.json",
|
|
75
|
+
]),
|
|
52
76
|
entry("tilemap", "doc/scripts-script_tilemap.cpp_doc.json"),
|
|
53
77
|
entry("timer", "doc/src-script_timer.cpp_doc.json"),
|
|
54
78
|
entry("types", "doc/src-script_types.cpp_doc.json"),
|
|
@@ -62,8 +86,61 @@ export const SYNC_MANIFEST: readonly SyncManifestEntry[] = [
|
|
|
62
86
|
// four extension-only surfaces are wired via EXTENSION_MANIFEST below.
|
|
63
87
|
export const UNMAPPED: ReadonlyMap<string, string> = new Map();
|
|
64
88
|
|
|
65
|
-
|
|
66
|
-
|
|
89
|
+
// A Lua script namespace is a lowercase, dot-separated identifier (`sys`,
|
|
90
|
+
// `b2d.body`). The native C/C++ SDK docs (`dmGraphics`, …), the C# bindings
|
|
91
|
+
// (`cs-*`), and the struct-only docs (empty `info.namespace`) fail this shape,
|
|
92
|
+
// so the upstream-coverage guard skips them structurally rather than listing
|
|
93
|
+
// each by hand — that keeps the guard self-maintaining as the SDK grows.
|
|
94
|
+
const LUA_NAMESPACE = /^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)*$/;
|
|
95
|
+
|
|
96
|
+
// Lowercase-identifier namespaces that pass LUA_NAMESPACE but are intentionally
|
|
97
|
+
// not wired through SYNC_MANIFEST, each with a sourced reason (mirrors the
|
|
98
|
+
// `EMPTY_BY_UPSTREAM` allowlist shape). The upstream-coverage guard treats these
|
|
99
|
+
// as covered. Adding a namespace here is a deliberate, reviewed act.
|
|
100
|
+
export const IGNORED_UPSTREAM: ReadonlyMap<string, string> = new Map([
|
|
101
|
+
["editor", "editor-scripting API (editor.apidoc), not a runtime game namespace"],
|
|
102
|
+
["engine", "CLI/engine env doc, not a runtime Lua namespace"],
|
|
103
|
+
[
|
|
104
|
+
"builtins",
|
|
105
|
+
"global Defold builtins (hash/pprint/…) typed as ambient globals, not a `builtins.*` namespace",
|
|
106
|
+
],
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
// Pure-Lua / LuaJIT surfaces Defold documents (https://defold.com/ref/stable/base-lua/,
|
|
110
|
+
// `bit-lua`) whose TypeScript types are owned by the `lua-types` dependency the
|
|
111
|
+
// `lua-stdlib-globals` goal adopted — `lua-types/special/jit-only.d.ts` declares
|
|
112
|
+
// `declare namespace bit { ... }` and `lua-types/core/global.d.ts` carries the
|
|
113
|
+
// base globals. Re-emitting them as generated Defold namespaces would collide
|
|
114
|
+
// (`declare namespace bit` duplicates; base globals are top-level globals, not
|
|
115
|
+
// a `base.*` namespace). LUA_STDLIB_MANIFEST vendors the same ref-doc JSON
|
|
116
|
+
// `SYNC_MANIFEST` carries, but the docs-site is the only consumer — `regen.ts`
|
|
117
|
+
// / `MODULE_MANIFEST` never read it, so no `generated/<ns>.d.ts` is produced.
|
|
118
|
+
export const LUA_STDLIB_MANIFEST: readonly SyncManifestEntry[] = [
|
|
119
|
+
entry("base", "doc/lua_base.doc_h_doc.json"),
|
|
120
|
+
entry("bit", "doc/src-script_bitop.cpp_doc.json"),
|
|
121
|
+
// Core Lua stdlib docs. The 1.12.4 release names these `doc/lua_<ns>.doc_h_doc.json`
|
|
122
|
+
// (confirmed against the pinned ref-doc.zip, not derived — the naming drifted from
|
|
123
|
+
// the 1.9.8 `doc/<ns>_doc.json` form). Typed by the lua-types dependency, so docs-only.
|
|
124
|
+
entry("math", "doc/lua_math.doc_h_doc.json"),
|
|
125
|
+
entry("os", "doc/lua_os.doc_h_doc.json"),
|
|
126
|
+
entry("string", "doc/lua_string.doc_h_doc.json"),
|
|
127
|
+
entry("table", "doc/lua_table.doc_h_doc.json"),
|
|
128
|
+
entry("coroutine", "doc/lua_coroutine.doc_h_doc.json"),
|
|
129
|
+
// Sandboxed-runtime stdlib surfaces. Defold sandboxes the Lua VM, but the pinned
|
|
130
|
+
// 1.12.4 ref-doc.zip documents all three with real function elements; types stay
|
|
131
|
+
// owned by lua-types (core/{debug,io}.d.ts + core/modules.d.ts for `package`).
|
|
132
|
+
entry("debug", "doc/lua_debug.doc_h_doc.json"),
|
|
133
|
+
entry("io", "doc/lua_io.doc_h_doc.json"),
|
|
134
|
+
entry("package", "doc/lua_package.doc_h_doc.json"),
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
function entry(
|
|
138
|
+
namespace: string,
|
|
139
|
+
zipEntry: string,
|
|
140
|
+
fixture: string = `fixtures/${namespace}_doc.json`,
|
|
141
|
+
mergeEntries?: readonly string[],
|
|
142
|
+
): SyncManifestEntry {
|
|
143
|
+
return { namespace, zipEntry, fixture, ...(mergeEntries ? { mergeEntries } : {}) };
|
|
67
144
|
}
|
|
68
145
|
|
|
69
146
|
export interface ExtensionManifestEntry {
|
|
@@ -115,7 +192,7 @@ export function parseChecklistNamespaces(visionMarkdown: string): string[] {
|
|
|
115
192
|
const seen = new Set<string>();
|
|
116
193
|
for (const match of segment.matchAll(/`([^`]+)`/g)) {
|
|
117
194
|
const token = match[1] as string;
|
|
118
|
-
if (!/^[a-z][a-z0-9]*$/.test(token) || seen.has(token)) continue;
|
|
195
|
+
if (!/^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)*$/.test(token) || seen.has(token)) continue;
|
|
119
196
|
seen.add(token);
|
|
120
197
|
namespaces.push(token);
|
|
121
198
|
}
|
|
@@ -168,15 +245,52 @@ export interface ExtractedFixture {
|
|
|
168
245
|
contents: string;
|
|
169
246
|
}
|
|
170
247
|
|
|
248
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
249
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Concatenate the `elements` of every doc into the first doc, deduping by
|
|
253
|
+
// (name, type) with first occurrence winning, and keep the first doc's `info`.
|
|
254
|
+
// First-wins guards the `b2d.body` `_defold` duplicate-`get_world_center`
|
|
255
|
+
// hazard. Operates on the raw ref-doc JSON shape (`{ info, elements }`), so the
|
|
256
|
+
// result is still parseable by `parseDefoldApiDoc`.
|
|
257
|
+
export function mergeApiDocs(docs: readonly unknown[]): unknown {
|
|
258
|
+
const first = docs[0];
|
|
259
|
+
const seen = new Set<string>();
|
|
260
|
+
const elements: unknown[] = [];
|
|
261
|
+
for (const doc of docs) {
|
|
262
|
+
if (!isRecord(doc) || !Array.isArray(doc.elements)) continue;
|
|
263
|
+
for (const element of doc.elements) {
|
|
264
|
+
const key =
|
|
265
|
+
isRecord(element) && typeof element.name === "string" && typeof element.type === "string"
|
|
266
|
+
? `${element.type}${element.name}`
|
|
267
|
+
: null;
|
|
268
|
+
if (key !== null) {
|
|
269
|
+
if (seen.has(key)) continue;
|
|
270
|
+
seen.add(key);
|
|
271
|
+
}
|
|
272
|
+
elements.push(element);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return isRecord(first) ? { ...first, elements } : { elements };
|
|
276
|
+
}
|
|
277
|
+
|
|
171
278
|
export function extractFixtures(
|
|
172
279
|
zip: ZipAccessor,
|
|
173
280
|
manifest: readonly SyncManifestEntry[] = SYNC_MANIFEST,
|
|
174
281
|
): ExtractedFixture[] {
|
|
175
282
|
return manifest.map((item) => {
|
|
176
|
-
|
|
177
|
-
|
|
283
|
+
const sources = [item.zipEntry, ...(item.mergeEntries ?? [])];
|
|
284
|
+
for (const source of sources) {
|
|
285
|
+
if (!zip.has(source)) {
|
|
286
|
+
throw new Error(`zip is missing entry ${source} for namespace ${item.namespace}`);
|
|
287
|
+
}
|
|
178
288
|
}
|
|
179
|
-
|
|
289
|
+
const contents =
|
|
290
|
+
item.mergeEntries === undefined
|
|
291
|
+
? zip.read(item.zipEntry)
|
|
292
|
+
: JSON.stringify(mergeApiDocs(sources.map((source) => JSON.parse(zip.read(source)))));
|
|
293
|
+
return { namespace: item.namespace, fixture: item.fixture, contents };
|
|
180
294
|
});
|
|
181
295
|
}
|
|
182
296
|
|
|
@@ -276,11 +390,26 @@ export interface SyncedDoc {
|
|
|
276
390
|
doc: unknown;
|
|
277
391
|
}
|
|
278
392
|
|
|
393
|
+
export interface UpstreamNamespace {
|
|
394
|
+
namespace: string;
|
|
395
|
+
functionCount: number;
|
|
396
|
+
zipEntry: string;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export interface UnmappedUpstreamNamespace {
|
|
400
|
+
namespace: string;
|
|
401
|
+
zipEntry: string;
|
|
402
|
+
}
|
|
403
|
+
|
|
279
404
|
export interface CoverageReport {
|
|
280
405
|
wired: string[];
|
|
281
406
|
fixtureOnly: string[];
|
|
282
407
|
missingMapping: string[];
|
|
283
408
|
unknownTypeTokens: { namespace: string; tokens: string[] }[];
|
|
409
|
+
// Function-bearing Lua namespaces present in the zip but neither mapped nor
|
|
410
|
+
// allowlisted — the next `font`-style miss. The `--check` path fails when this
|
|
411
|
+
// is non-empty.
|
|
412
|
+
unmappedUpstream: UnmappedUpstreamNamespace[];
|
|
284
413
|
}
|
|
285
414
|
|
|
286
415
|
export interface CoverageInput {
|
|
@@ -288,6 +417,38 @@ export interface CoverageInput {
|
|
|
288
417
|
moduleManifest: readonly { namespace: string }[];
|
|
289
418
|
unmapped: ReadonlyMap<string, string>;
|
|
290
419
|
syncedDocs: readonly SyncedDoc[];
|
|
420
|
+
// Every function-bearing namespace the zip itself exposes (from
|
|
421
|
+
// collectUpstreamNamespaces). Absent in pure-fixture callers that don't audit
|
|
422
|
+
// upstream coverage, in which case unmappedUpstream is empty.
|
|
423
|
+
upstream?: readonly UpstreamNamespace[];
|
|
424
|
+
upstreamMapped?: ReadonlySet<string>;
|
|
425
|
+
ignoredUpstream?: ReadonlyMap<string, string>;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Enumerate every `doc/*.json` zip entry, reading its `info.namespace` and the
|
|
429
|
+
// count of FUNCTION elements. Only function-bearing docs are returned, so
|
|
430
|
+
// message-only / struct-only docs never reach the coverage guard.
|
|
431
|
+
export function collectUpstreamNamespaces(zip: ZipAccessor): UpstreamNamespace[] {
|
|
432
|
+
const out: UpstreamNamespace[] = [];
|
|
433
|
+
for (const zipEntry of zip.entries()) {
|
|
434
|
+
if (!/^doc\/.*\.json$/.test(zipEntry)) continue;
|
|
435
|
+
let doc: unknown;
|
|
436
|
+
try {
|
|
437
|
+
doc = JSON.parse(zip.read(zipEntry));
|
|
438
|
+
} catch {
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
if (!isRecord(doc)) continue;
|
|
442
|
+
const info = doc.info;
|
|
443
|
+
const namespace = isRecord(info) && typeof info.namespace === "string" ? info.namespace : "";
|
|
444
|
+
const elements = Array.isArray(doc.elements) ? doc.elements : [];
|
|
445
|
+
let functionCount = 0;
|
|
446
|
+
for (const element of elements) {
|
|
447
|
+
if (isRecord(element) && element.type === "FUNCTION") functionCount += 1;
|
|
448
|
+
}
|
|
449
|
+
if (functionCount > 0) out.push({ namespace, functionCount, zipEntry });
|
|
450
|
+
}
|
|
451
|
+
return out;
|
|
291
452
|
}
|
|
292
453
|
|
|
293
454
|
export function buildCoverageReport(input: CoverageInput): CoverageReport {
|
|
@@ -307,7 +468,26 @@ export function buildCoverageReport(input: CoverageInput): CoverageReport {
|
|
|
307
468
|
unknownTypeTokens.push({ namespace: synced.namespace, tokens });
|
|
308
469
|
}
|
|
309
470
|
}
|
|
310
|
-
|
|
471
|
+
|
|
472
|
+
const mapped = input.upstreamMapped ?? new Set<string>();
|
|
473
|
+
const ignored = input.ignoredUpstream ?? new Map<string, string>();
|
|
474
|
+
const flagged = new Map<string, string>();
|
|
475
|
+
for (const item of input.upstream ?? []) {
|
|
476
|
+
if (
|
|
477
|
+
!LUA_NAMESPACE.test(item.namespace) ||
|
|
478
|
+
mapped.has(item.namespace) ||
|
|
479
|
+
ignored.has(item.namespace) ||
|
|
480
|
+
flagged.has(item.namespace)
|
|
481
|
+
) {
|
|
482
|
+
continue;
|
|
483
|
+
}
|
|
484
|
+
flagged.set(item.namespace, item.zipEntry);
|
|
485
|
+
}
|
|
486
|
+
const unmappedUpstream = [...flagged]
|
|
487
|
+
.map(([namespace, zipEntry]) => ({ namespace, zipEntry }))
|
|
488
|
+
.sort((a, b) => a.namespace.localeCompare(b.namespace));
|
|
489
|
+
|
|
490
|
+
return { wired, fixtureOnly, missingMapping, unknownTypeTokens, unmappedUpstream };
|
|
311
491
|
}
|
|
312
492
|
|
|
313
493
|
function collectUnknownTokens(module: ApiModule): string[] {
|
|
@@ -349,6 +529,15 @@ function printReport(results: FixtureSyncResult[], report: CoverageReport, check
|
|
|
349
529
|
console.log(` ${namespace}: ${tokens.join(", ")}`);
|
|
350
530
|
}
|
|
351
531
|
}
|
|
532
|
+
|
|
533
|
+
if (report.unmappedUpstream.length > 0) {
|
|
534
|
+
console.log(
|
|
535
|
+
"\nunmapped upstream namespaces (function-bearing, neither mapped nor allowlisted)",
|
|
536
|
+
);
|
|
537
|
+
for (const { namespace, zipEntry } of report.unmappedUpstream) {
|
|
538
|
+
console.log(` ${namespace}: ${zipEntry}`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
352
541
|
}
|
|
353
542
|
|
|
354
543
|
if (import.meta.main) {
|
|
@@ -359,9 +548,11 @@ if (import.meta.main) {
|
|
|
359
548
|
|
|
360
549
|
const coreFixtures = extractFixtures(zip);
|
|
361
550
|
const extensionFixtures = await downloadExtensionFixtures();
|
|
551
|
+
const luaStdlibFixtures = extractFixtures(zip, LUA_STDLIB_MANIFEST);
|
|
362
552
|
const results = [
|
|
363
553
|
...syncExtractedFixtures(coreFixtures, { check }),
|
|
364
554
|
...syncExtractedFixtures(extensionFixtures, { check }),
|
|
555
|
+
...syncExtractedFixtures(luaStdlibFixtures, { check }),
|
|
365
556
|
];
|
|
366
557
|
const syncedDocs = [...coreFixtures, ...extensionFixtures].map((f) => ({
|
|
367
558
|
namespace: f.namespace,
|
|
@@ -372,10 +563,15 @@ if (import.meta.main) {
|
|
|
372
563
|
moduleManifest: MODULE_MANIFEST,
|
|
373
564
|
unmapped: UNMAPPED,
|
|
374
565
|
syncedDocs,
|
|
566
|
+
upstream: collectUpstreamNamespaces(zip),
|
|
567
|
+
upstreamMapped: new Set(
|
|
568
|
+
[...SYNC_MANIFEST, ...EXTENSION_MANIFEST, ...LUA_STDLIB_MANIFEST].map((e) => e.namespace),
|
|
569
|
+
),
|
|
570
|
+
ignoredUpstream: IGNORED_UPSTREAM,
|
|
375
571
|
});
|
|
376
572
|
printReport(results, report, check);
|
|
377
573
|
|
|
378
|
-
if (check && results.some((r) => r.status === "drift")) {
|
|
574
|
+
if (check && (results.some((r) => r.status === "drift") || report.unmappedUpstream.length > 0)) {
|
|
379
575
|
process.exitCode = 1;
|
|
380
576
|
}
|
|
381
577
|
}
|
package/src/core-types.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface Vector3 {
|
|
|
17
17
|
* @remarks
|
|
18
18
|
* Prefer `v.unm()` over `-v` — TypeScript does not flag unary `-` on object
|
|
19
19
|
* types and silently produces `number`. See
|
|
20
|
-
* `docs/guide/typescript-gotchas.md` for the full story.
|
|
20
|
+
* `packages/docs/guide/typescript-gotchas.md` for the full story.
|
|
21
21
|
*/
|
|
22
22
|
unm: LuaNegationMethod<Vector3>;
|
|
23
23
|
}
|
|
@@ -35,7 +35,7 @@ export interface Vector4 {
|
|
|
35
35
|
* @remarks
|
|
36
36
|
* Prefer `v.unm()` over `-v` — TypeScript does not flag unary `-` on object
|
|
37
37
|
* types and silently produces `number`. See
|
|
38
|
-
* `docs/guide/typescript-gotchas.md` for the full story.
|
|
38
|
+
* `packages/docs/guide/typescript-gotchas.md` for the full story.
|
|
39
39
|
*/
|
|
40
40
|
unm: LuaNegationMethod<Vector4>;
|
|
41
41
|
}
|
|
@@ -123,13 +123,20 @@ export const DEFOLD_TYPE_MAP: Readonly<Record<string, string>> = {
|
|
|
123
123
|
constant: 'Opaque<"constant">',
|
|
124
124
|
constant_buffer: 'Opaque<"constant_buffer">',
|
|
125
125
|
buffer: 'Opaque<"buffer">',
|
|
126
|
-
bufferstream: 'Opaque<"bufferstream">',
|
|
126
|
+
bufferstream: 'Opaque<"bufferstream"> & { [index: number]: number }',
|
|
127
127
|
userdata: 'Opaque<"userdata">',
|
|
128
128
|
resource: 'Opaque<"resource">',
|
|
129
129
|
b2World: 'Opaque<"b2World">',
|
|
130
130
|
b2Body: 'Opaque<"b2Body">',
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
b2BodyType:
|
|
132
|
+
'(number & { readonly __brand: "b2d.body.B2_DYNAMIC_BODY" }) | (number & { readonly __brand: "b2d.body.B2_KINEMATIC_BODY" }) | (number & { readonly __brand: "b2d.body.B2_STATIC_BODY" })',
|
|
133
|
+
// socket handle types resolve to the method-bearing `interface <receiver>`
|
|
134
|
+
// emitted inside `namespace socket`, not opaque brands — the documented
|
|
135
|
+
// colon methods (`client:send`, …) make each handle structurally distinct.
|
|
136
|
+
client: "client",
|
|
137
|
+
connected: "connected",
|
|
138
|
+
master: "master",
|
|
139
|
+
server: "server",
|
|
140
|
+
unconnected: "unconnected",
|
|
134
141
|
any: "unknown",
|
|
135
142
|
} as const;
|