@defold-typescript/types 0.8.4 → 0.9.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 +3 -0
- 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 +264 -49
- 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 +4 -0
- 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 +7 -0
- package/package.json +1 -1
- package/scripts/fidelity-baseline.json +18 -2
- package/scripts/regen.ts +4 -0
- package/scripts/signature-store-io.ts +18 -0
- package/scripts/sync-api-docs.ts +208 -12
- package/src/core-types.ts +4 -2
- package/src/doc-comment.ts +42 -5
- package/src/engine-globals.d.ts +2 -0
- package/src/example-store.ts +11 -7
- package/src/index.ts +18 -1
- package/src/msg-overloads.d.ts +3 -0
- package/src/signature-store.ts +20 -0
package/index.d.ts
CHANGED
|
@@ -57,7 +57,14 @@ export {
|
|
|
57
57
|
type Vector3,
|
|
58
58
|
type Vector4,
|
|
59
59
|
} from "./src/core-types";
|
|
60
|
+
export { examplesHtmlToMarkdown, htmlToCodeText, htmlToDocText } from "./src/doc-comment";
|
|
60
61
|
export { type EmitOptions, emitDeclarations } from "./src/emit-dts";
|
|
62
|
+
export {
|
|
63
|
+
hashExampleSource,
|
|
64
|
+
lookupTranslation,
|
|
65
|
+
type Translation,
|
|
66
|
+
type TranslationStore,
|
|
67
|
+
} from "./src/example-store";
|
|
61
68
|
export {
|
|
62
69
|
defineGuiScript,
|
|
63
70
|
defineRenderScript,
|
package/package.json
CHANGED
|
@@ -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": [],
|
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");
|
|
@@ -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
|
}
|
|
@@ -128,6 +128,8 @@ export const DEFOLD_TYPE_MAP: Readonly<Record<string, string>> = {
|
|
|
128
128
|
resource: 'Opaque<"resource">',
|
|
129
129
|
b2World: 'Opaque<"b2World">',
|
|
130
130
|
b2Body: 'Opaque<"b2Body">',
|
|
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" })',
|
|
131
133
|
client: 'Opaque<"client">',
|
|
132
134
|
master: 'Opaque<"master">',
|
|
133
135
|
unconnected: 'Opaque<"unconnected">',
|
package/src/doc-comment.ts
CHANGED
|
@@ -29,18 +29,20 @@ export function htmlToDocText(html: string): string {
|
|
|
29
29
|
.replace(/<li>/gi, "\n- ")
|
|
30
30
|
.replace(/<\/li>/gi, "")
|
|
31
31
|
.replace(/<br\s*\/?>/gi, "\n")
|
|
32
|
+
.replace(/<\/p>/gi, "\n\n")
|
|
32
33
|
.replace(/<\/?pre>/gi, "\n")
|
|
33
34
|
.replace(/<[^>]+>/g, "");
|
|
34
35
|
|
|
35
36
|
text = decodeEntities(text);
|
|
36
37
|
|
|
37
|
-
// Collapse horizontal whitespace runs, trim around newlines,
|
|
38
|
-
// runs, then trim the whole string — preserving
|
|
39
|
-
// and `<br
|
|
38
|
+
// Collapse horizontal whitespace runs, trim around newlines, fold runaway
|
|
39
|
+
// blank runs to one blank line, then trim the whole string — preserving
|
|
40
|
+
// single newlines from lists and `<br>`, and the paragraph-break blank line
|
|
41
|
+
// from `</p>`.
|
|
40
42
|
text = text
|
|
41
43
|
.replace(/[ \t]+/g, " ")
|
|
42
44
|
.replace(/ *\n */g, "\n")
|
|
43
|
-
.replace(/\n{
|
|
45
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
44
46
|
.trim();
|
|
45
47
|
|
|
46
48
|
// A literal `*/` would close the JSDoc comment early; escape it.
|
|
@@ -68,6 +70,41 @@ export function htmlToCodeText(html: string): string {
|
|
|
68
70
|
return collapsed.split("*/").join("*\\/");
|
|
69
71
|
}
|
|
70
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Convert a ref-doc `examples` HTML fragment — prose interleaved with one or
|
|
75
|
+
* more `<div class="codehilite">…</div>` syntax-highlight blocks — into Markdown:
|
|
76
|
+
* prose runs become `htmlToDocText`, each highlight block becomes a ` ```lua `
|
|
77
|
+
* fence via `htmlToCodeText`. A fragment with no `codehilite` block is wrapped
|
|
78
|
+
* whole as a single ` ```lua ` fence (back-compat for plain-code examples).
|
|
79
|
+
* Returns `""` for empty / whitespace-only input.
|
|
80
|
+
*/
|
|
81
|
+
export function examplesHtmlToMarkdown(html: string): string {
|
|
82
|
+
if (html.trim() === "") return "";
|
|
83
|
+
|
|
84
|
+
const parts: string[] = [];
|
|
85
|
+
const blocks = /<div class="codehilite">([\s\S]*?)<\/div>/gi;
|
|
86
|
+
let lastIndex = 0;
|
|
87
|
+
let matched = false;
|
|
88
|
+
for (let match = blocks.exec(html); match !== null; match = blocks.exec(html)) {
|
|
89
|
+
matched = true;
|
|
90
|
+
const prose = htmlToDocText(html.slice(lastIndex, match.index));
|
|
91
|
+
if (prose !== "") parts.push(prose);
|
|
92
|
+
const code = htmlToCodeText(match[1] ?? "");
|
|
93
|
+
if (code !== "") parts.push(`\`\`\`lua\n${code}\n\`\`\``);
|
|
94
|
+
lastIndex = match.index + match[0].length;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!matched) {
|
|
98
|
+
const code = htmlToCodeText(html);
|
|
99
|
+
return code === "" ? "" : `\`\`\`lua\n${code}\n\`\`\``;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const trailing = htmlToDocText(html.slice(lastIndex));
|
|
103
|
+
if (trailing !== "") parts.push(trailing);
|
|
104
|
+
|
|
105
|
+
return parts.join("\n\n");
|
|
106
|
+
}
|
|
107
|
+
|
|
71
108
|
export interface DocCommentParts {
|
|
72
109
|
summary: string;
|
|
73
110
|
params?: { name: string; doc: string }[];
|
|
@@ -92,7 +129,7 @@ export function renderDocComment(parts: DocCommentParts): string[] {
|
|
|
92
129
|
|
|
93
130
|
const lines = ["/**"];
|
|
94
131
|
for (const line of summaryLines) {
|
|
95
|
-
lines.push(` * ${line}`);
|
|
132
|
+
lines.push(line === "" ? " *" : ` * ${line}`);
|
|
96
133
|
}
|
|
97
134
|
|
|
98
135
|
const hasTags = params.length > 0 || returns !== "" || example !== "";
|
package/src/engine-globals.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type * as Core from "./core-types";
|
|
|
4
4
|
declare global {
|
|
5
5
|
type Hash = Core.Hash;
|
|
6
6
|
function hash(s: string): Core.Hash;
|
|
7
|
+
function hash_to_hex(h: Core.Hash): string;
|
|
8
|
+
function pprint(v: unknown): void;
|
|
7
9
|
type Opaque<Name extends string> = Core.Opaque<Name>;
|
|
8
10
|
type Url = Core.Url;
|
|
9
11
|
type Vector = Core.Vector;
|
package/src/example-store.ts
CHANGED
|
@@ -7,7 +7,10 @@ export interface Translation {
|
|
|
7
7
|
ts: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
// An FQN maps to one translation per distinct example body it carries: an
|
|
11
|
+
// overloaded element (same name, differing `@example` source) contributes one
|
|
12
|
+
// array entry per body, each pinned by its own `sourceHash`.
|
|
13
|
+
export type TranslationStore = Record<string, Translation[]>;
|
|
11
14
|
|
|
12
15
|
const FNV_OFFSET_BASIS = 0xcbf29ce484222325n;
|
|
13
16
|
const FNV_PRIME = 0x100000001b3n;
|
|
@@ -30,15 +33,16 @@ export function hashExampleSource(source: string): string {
|
|
|
30
33
|
return hash.toString(16).padStart(16, "0");
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
// Return the stored TypeScript body only when the FQN exists and its
|
|
34
|
-
// `sourceHash` matches the source we are about to emit; any mismatch
|
|
35
|
-
// `null` so the caller keeps the Lua fallback.
|
|
36
|
+
// Return the stored TypeScript body only when the FQN exists and one of its
|
|
37
|
+
// pinned `sourceHash`es matches the source we are about to emit; any mismatch
|
|
38
|
+
// returns `null` so the caller keeps the Lua fallback.
|
|
36
39
|
export function lookupTranslation(
|
|
37
40
|
store: TranslationStore,
|
|
38
41
|
fqn: string,
|
|
39
42
|
sourceHash: string,
|
|
40
43
|
): string | null {
|
|
41
|
-
const
|
|
42
|
-
if (!
|
|
43
|
-
|
|
44
|
+
const entries = store[fqn];
|
|
45
|
+
if (!entries) return null;
|
|
46
|
+
const match = entries.find((entry) => entry.sourceHash === sourceHash);
|
|
47
|
+
return match ? match.ts : null;
|
|
44
48
|
}
|
package/src/index.ts
CHANGED
|
@@ -10,8 +10,20 @@ export {
|
|
|
10
10
|
type Vector3,
|
|
11
11
|
type Vector4,
|
|
12
12
|
} from "./core-types";
|
|
13
|
-
export {
|
|
13
|
+
export {
|
|
14
|
+
type DocCommentParts,
|
|
15
|
+
examplesHtmlToMarkdown,
|
|
16
|
+
htmlToCodeText,
|
|
17
|
+
htmlToDocText,
|
|
18
|
+
renderDocComment,
|
|
19
|
+
} from "./doc-comment";
|
|
14
20
|
export { type EmitOptions, emitDeclarations } from "./emit-dts";
|
|
21
|
+
export {
|
|
22
|
+
hashExampleSource,
|
|
23
|
+
lookupTranslation,
|
|
24
|
+
type Translation,
|
|
25
|
+
type TranslationStore,
|
|
26
|
+
} from "./example-store";
|
|
15
27
|
export {
|
|
16
28
|
defineGuiScript,
|
|
17
29
|
defineRenderScript,
|
|
@@ -30,3 +42,8 @@ export {
|
|
|
30
42
|
type ScriptProperty,
|
|
31
43
|
} from "./lifecycle";
|
|
32
44
|
export { type WrapOptions, wrapAsAmbientGlobal } from "./publish-dts";
|
|
45
|
+
export {
|
|
46
|
+
lookupSignature,
|
|
47
|
+
type SignatureOverride,
|
|
48
|
+
type SignatureStore,
|
|
49
|
+
} from "./signature-store";
|
package/src/msg-overloads.d.ts
CHANGED
|
@@ -12,8 +12,11 @@ declare global {
|
|
|
12
12
|
* to a component. If the component part of the receiver is omitted, the message
|
|
13
13
|
* is broadcast to all components in the game object.
|
|
14
14
|
* The following receiver shorthands are available:
|
|
15
|
+
*
|
|
15
16
|
* - `"."` the current game object
|
|
17
|
+
*
|
|
16
18
|
* - `"#"` the current component
|
|
19
|
+
*
|
|
17
20
|
* There is a 2 kilobyte limit to the message parameter table size.
|
|
18
21
|
*
|
|
19
22
|
* @param receiver - The receiver must be a string in URL-format, a URL object or a hashed string.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// A hand-authored override for one ref-doc element's signature, sourced from
|
|
2
|
+
// the `lua-types` declarations the compiler actually enforces. The ref-doc's
|
|
3
|
+
// own signatures are far weaker (`...` params show `unknown`, no return types),
|
|
4
|
+
// so these strengthen the rendered signature while the ref-doc prose stays.
|
|
5
|
+
export interface SignatureOverride {
|
|
6
|
+
signatures: string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// An FQN (`io.open`, `file:read`, …) maps to one override. An element with
|
|
10
|
+
// several overloads carries more than one entry in `signatures`, in authored
|
|
11
|
+
// order (the order the docs should render them).
|
|
12
|
+
export type SignatureStore = Record<string, SignatureOverride>;
|
|
13
|
+
|
|
14
|
+
// Return the override for an FQN, or `null` when the store has no entry. Pure
|
|
15
|
+
// and dependency-free like `example-store.ts`: this module is reachable from
|
|
16
|
+
// `index.ts`, so a `node:fs`/ambient-`Bun` reference here would fail type-checking
|
|
17
|
+
// in every downstream consumer that compiles the shipped `src/` graph.
|
|
18
|
+
export function lookupSignature(store: SignatureStore, fqn: string): SignatureOverride | null {
|
|
19
|
+
return store[fqn] ?? null;
|
|
20
|
+
}
|