@defold-typescript/types 0.40.0 → 0.41.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-availability.json +3963 -772
- package/api-migrations.json +27 -0
- package/api-signatures.json +18069 -10240
- package/api-targets.json +448 -11
- package/examples/translations.json +560 -89
- package/fixtures/messages_doc.json +96 -0
- package/generated/b2d.d.ts +680 -4
- package/generated/b2d_body.d.ts +29 -103
- package/generated/b2d_chain.d.ts +5 -6
- package/generated/b2d_fixture.d.ts +34 -38
- package/generated/b2d_joint.d.ts +503 -25
- package/generated/b2d_shape.d.ts +183 -20
- package/generated/b2d_world.d.ts +57 -63
- package/generated/buffer.d.ts +16 -15
- package/generated/builtin-messages.d.ts +7 -1
- package/generated/bullet3d.d.ts +91 -0
- package/generated/bullet3d_collision_object.d.ts +433 -0
- package/generated/bullet3d_constraint.d.ts +818 -0
- package/generated/bullet3d_rigid_body.d.ts +429 -0
- package/generated/bullet3d_shape.d.ts +123 -0
- package/generated/bullet3d_world.d.ts +492 -0
- package/generated/camera.d.ts +29 -32
- package/generated/collectionfactory.d.ts +15 -18
- package/generated/collectionproxy.d.ts +22 -6
- package/generated/compute.d.ts +12 -238
- package/generated/crash.d.ts +24 -10
- package/generated/editor-vm/http.d.ts +90 -7
- package/generated/editor-vm/image.d.ts +7 -7
- package/generated/editor-vm/json.d.ts +11 -0
- package/generated/editor-vm/localization.d.ts +7 -7
- package/generated/editor-vm/tilemap_tiles.d.ts +56 -10
- package/generated/editor-vm/zip.d.ts +28 -0
- package/generated/editor.d.ts +1496 -430
- package/generated/factory.d.ts +21 -25
- package/generated/font.d.ts +64 -22
- package/generated/go.d.ts +235 -182
- package/generated/graphics.d.ts +335 -97
- package/generated/gui.d.ts +240 -471
- package/generated/html5.d.ts +2 -2
- package/generated/http.d.ts +28 -35
- package/generated/image.d.ts +105 -72
- package/generated/json.d.ts +34 -16
- package/generated/kinds/gui-script.d.ts +7 -0
- package/generated/kinds/render-script.d.ts +7 -0
- package/generated/kinds/script.d.ts +7 -0
- package/generated/label.d.ts +77 -7
- package/generated/liveupdate.d.ts +13 -10
- package/generated/material.d.ts +312 -360
- package/generated/model.d.ts +40 -39
- package/generated/particlefx.d.ts +19 -28
- package/generated/physics.d.ts +365 -133
- package/generated/profiler.d.ts +13 -23
- package/generated/render.d.ts +237 -297
- package/generated/resource.d.ts +664 -583
- package/generated/socket.d.ts +120 -133
- package/generated/sound.d.ts +62 -31
- package/generated/sprite.d.ts +31 -17
- package/generated/sys.d.ts +234 -190
- package/generated/tilemap.d.ts +21 -16
- package/generated/timer.d.ts +34 -21
- package/generated/versions/defold-1.12.4/b2d_body.d.ts +0 -7
- package/generated/versions/defold-1.12.4/buffer.d.ts +1 -3
- package/generated/versions/defold-1.12.4/camera.d.ts +21 -21
- package/generated/versions/defold-1.12.4/collectionfactory.d.ts +9 -9
- package/generated/versions/defold-1.12.4/collectionproxy.d.ts +2 -2
- package/generated/versions/defold-1.12.4/crash.d.ts +2 -2
- package/generated/versions/defold-1.12.4/factory.d.ts +15 -16
- package/generated/versions/defold-1.12.4/go.d.ts +6 -12
- package/generated/versions/defold-1.12.4/gui.d.ts +77 -175
- package/generated/versions/defold-1.12.4/html5.d.ts +1 -1
- package/generated/versions/defold-1.12.4/http.d.ts +0 -13
- package/generated/versions/defold-1.12.4/image.d.ts +16 -33
- package/generated/versions/defold-1.12.4/index.d.ts +1 -0
- package/generated/versions/defold-1.12.4/json.d.ts +10 -6
- package/generated/versions/defold-1.12.4/label.d.ts +2 -3
- package/generated/versions/defold-1.12.4/liveupdate.d.ts +5 -3
- package/generated/versions/defold-1.12.4/model.d.ts +2 -10
- package/generated/versions/defold-1.12.4/particlefx.d.ts +2 -5
- package/generated/versions/defold-1.12.4/physics.d.ts +69 -67
- package/generated/versions/defold-1.12.4/profiler.d.ts +2 -5
- package/generated/versions/defold-1.12.4/render.d.ts +99 -128
- package/generated/versions/defold-1.12.4/resource.d.ts +120 -148
- package/generated/versions/defold-1.12.4/socket.d.ts +34 -60
- package/generated/versions/defold-1.12.4/sound.d.ts +8 -8
- package/generated/versions/defold-1.12.4/sprite.d.ts +15 -5
- package/generated/versions/defold-1.12.4/sys.d.ts +37 -71
- package/generated/versions/defold-1.12.4/tilemap.d.ts +18 -11
- package/generated/versions/defold-1.12.4/timer.d.ts +5 -3
- package/generated/versions/defold-1.12.4/vmath.d.ts +0 -21
- package/generated/versions/defold-1.12.4/window.d.ts +1 -16
- package/generated/versions/defold-1.13.0/b2d_body.d.ts +10 -55
- package/generated/versions/defold-1.13.0/b2d_fixture.d.ts +25 -25
- package/generated/versions/defold-1.13.0/b2d_shape.d.ts +169 -8
- package/generated/versions/defold-1.13.0/b2d_world.d.ts +16 -10
- package/generated/versions/defold-1.13.0/buffer.d.ts +1 -3
- package/generated/versions/defold-1.13.0/camera.d.ts +25 -25
- package/generated/versions/defold-1.13.0/collectionfactory.d.ts +9 -9
- package/generated/versions/defold-1.13.0/collectionproxy.d.ts +2 -2
- package/generated/versions/defold-1.13.0/compute.d.ts +0 -69
- package/generated/versions/defold-1.13.0/crash.d.ts +2 -2
- package/generated/versions/defold-1.13.0/editor-vm/tilemap_tiles.d.ts +3 -3
- package/generated/versions/defold-1.13.0/editor.d.ts +34 -24
- package/generated/versions/defold-1.13.0/factory.d.ts +15 -16
- package/generated/versions/defold-1.13.0/go.d.ts +6 -12
- package/generated/versions/defold-1.13.0/graphics.d.ts +73 -37
- package/generated/versions/defold-1.13.0/gui.d.ts +77 -175
- package/generated/versions/defold-1.13.0/html5.d.ts +1 -1
- package/generated/versions/defold-1.13.0/http.d.ts +0 -13
- package/generated/versions/defold-1.13.0/image.d.ts +16 -33
- package/generated/versions/defold-1.13.0/index.d.ts +1 -0
- package/generated/versions/defold-1.13.0/json.d.ts +10 -6
- package/generated/versions/defold-1.13.0/label.d.ts +2 -3
- package/generated/versions/defold-1.13.0/liveupdate.d.ts +7 -5
- package/generated/versions/defold-1.13.0/material.d.ts +68 -106
- package/generated/versions/defold-1.13.0/model.d.ts +2 -10
- package/generated/versions/defold-1.13.0/particlefx.d.ts +2 -5
- package/generated/versions/defold-1.13.0/physics.d.ts +69 -67
- package/generated/versions/defold-1.13.0/profiler.d.ts +2 -5
- package/generated/versions/defold-1.13.0/render.d.ts +99 -128
- package/generated/versions/defold-1.13.0/resource.d.ts +126 -150
- package/generated/versions/defold-1.13.0/socket.d.ts +34 -60
- package/generated/versions/defold-1.13.0/sound.d.ts +8 -8
- package/generated/versions/defold-1.13.0/sprite.d.ts +15 -5
- package/generated/versions/defold-1.13.0/sys.d.ts +37 -71
- package/generated/versions/defold-1.13.0/tilemap.d.ts +18 -11
- package/generated/versions/defold-1.13.0/timer.d.ts +5 -3
- package/generated/versions/defold-1.13.0/vmath.d.ts +0 -21
- package/generated/versions/defold-1.13.0/window.d.ts +1 -16
- package/generated/versions/defold-1.13.1/b2d.d.ts +33 -0
- package/generated/versions/defold-1.13.1/b2d_body.d.ts +636 -0
- package/generated/versions/defold-1.13.1/b2d_chain.d.ts +106 -0
- package/generated/versions/defold-1.13.1/b2d_fixture.d.ts +157 -0
- package/generated/versions/defold-1.13.1/b2d_joint.d.ts +823 -0
- package/generated/versions/defold-1.13.1/b2d_shape.d.ts +400 -0
- package/generated/versions/defold-1.13.1/b2d_world.d.ts +320 -0
- package/generated/versions/defold-1.13.1/buffer.d.ts +189 -0
- package/generated/versions/defold-1.13.1/camera.d.ts +303 -0
- package/generated/versions/defold-1.13.1/collectionfactory.d.ts +151 -0
- package/generated/versions/defold-1.13.1/collectionproxy.d.ts +93 -0
- package/generated/versions/defold-1.13.1/compute.d.ts +237 -0
- package/generated/versions/defold-1.13.1/crash.d.ts +143 -0
- package/generated/versions/defold-1.13.1/editor-vm/http.d.ts +56 -0
- package/generated/versions/defold-1.13.1/editor-vm/image.d.ts +48 -0
- package/generated/versions/defold-1.13.1/editor-vm/json.d.ts +10 -0
- package/generated/versions/defold-1.13.1/editor-vm/localization.d.ts +42 -0
- package/generated/versions/defold-1.13.1/editor-vm/tilemap_tiles.d.ts +80 -0
- package/generated/versions/defold-1.13.1/editor-vm/zip.d.ts +10 -0
- package/generated/versions/defold-1.13.1/editor-vm/zlib.d.ts +24 -0
- package/generated/versions/defold-1.13.1/editor.d.ts +1121 -0
- package/generated/versions/defold-1.13.1/factory.d.ts +124 -0
- package/generated/versions/defold-1.13.1/font.d.ts +81 -0
- package/generated/versions/defold-1.13.1/go.d.ts +1107 -0
- package/generated/versions/defold-1.13.1/graphics.d.ts +411 -0
- package/generated/versions/defold-1.13.1/gui.d.ts +2353 -0
- package/generated/versions/defold-1.13.1/html5.d.ts +55 -0
- package/generated/versions/defold-1.13.1/http.d.ts +77 -0
- package/generated/versions/defold-1.13.1/image.d.ts +131 -0
- package/generated/versions/defold-1.13.1/index.d.ts +57 -0
- package/generated/versions/defold-1.13.1/json.d.ts +80 -0
- package/generated/versions/defold-1.13.1/kinds/editor-script.d.ts +14 -0
- package/generated/versions/defold-1.13.1/label.d.ts +93 -0
- package/generated/versions/defold-1.13.1/liveupdate.d.ts +142 -0
- package/generated/versions/defold-1.13.1/material.d.ts +408 -0
- package/generated/versions/defold-1.13.1/model.d.ts +272 -0
- package/generated/versions/defold-1.13.1/msg.d.ts +10 -0
- package/generated/versions/defold-1.13.1/particlefx.d.ts +163 -0
- package/generated/versions/defold-1.13.1/physics.d.ts +559 -0
- package/generated/versions/defold-1.13.1/profiler.d.ts +235 -0
- package/generated/versions/defold-1.13.1/render.d.ts +1190 -0
- package/generated/versions/defold-1.13.1/resource.d.ts +1523 -0
- package/generated/versions/defold-1.13.1/socket.d.ts +784 -0
- package/generated/versions/defold-1.13.1/sound.d.ts +292 -0
- package/generated/versions/defold-1.13.1/sprite.d.ts +175 -0
- package/generated/versions/defold-1.13.1/sys.d.ts +678 -0
- package/generated/versions/defold-1.13.1/tilemap.d.ts +208 -0
- package/generated/versions/defold-1.13.1/timer.d.ts +125 -0
- package/generated/versions/defold-1.13.1/types.d.ts +61 -0
- package/generated/versions/defold-1.13.1/vmath.d.ts +774 -0
- package/generated/versions/defold-1.13.1/window.d.ts +183 -0
- package/generated/versions/defold-1.13.1/zlib.d.ts +41 -0
- package/generated/vmath.d.ts +1 -21
- package/generated/window.d.ts +71 -71
- package/identity-vocabulary.json +561 -0
- package/index.d.ts +7 -0
- package/package.json +13 -2
- package/scripts/example-store-io.ts +12 -1
- package/scripts/extension-goldens.ts +1 -0
- package/scripts/fidelity-baseline.json +146 -46
- package/scripts/generate-api-availability.ts +90 -12
- package/scripts/generate-api-signatures.ts +12 -2
- package/scripts/import-defold-release.ts +85 -24
- package/scripts/prose-constants.ts +77 -16
- package/scripts/regen.ts +391 -77
- package/scripts/shared-handle-methods.ts +53 -0
- package/scripts/signature-store-fs.ts +1 -0
- package/scripts/sync-api-docs.ts +16 -5
- package/src/api-availability.ts +0 -0
- package/src/api-doc.ts +201 -3
- package/src/api-identity.ts +252 -0
- package/src/core-types.ts +26 -3
- package/src/correction-pins.ts +49 -0
- package/src/doc-comment.ts +104 -11
- package/src/doc-corrections.ts +108 -0
- package/src/editor-vm-globals.d.ts +5 -2
- package/src/emit-dts.ts +2064 -281
- package/src/emit-messages.ts +52 -0
- package/src/example-store.ts +125 -14
- package/src/field-correction-coverage.ts +204 -0
- package/src/fnv1a.ts +16 -0
- package/src/go-overloads.d.ts +25 -6
- package/src/index-base-table.ts +145 -0
- package/src/index-slot-classifications.ts +588 -0
- package/src/index.ts +38 -5
- package/src/library-index-slot-classifications.ts +332 -0
- package/src/lifecycle.ts +61 -199
- package/src/luals-type-expr.ts +354 -0
- package/src/msg-overloads.d.ts +0 -1
- package/src/param-correction-provenance.ts +8 -11
- package/src/render-overloads.d.ts +0 -15
- package/src/return-correction-provenance.ts +58 -16
- package/src/sys-overloads.d.ts +48 -0
- package/src/vmath-overloads.d.ts +27 -1
- package/src/window-event-guard.d.ts +6 -9
- package/url-parameters.json +1 -2
package/src/emit-dts.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import messagesDoc from "../fixtures/messages_doc.json" with { type: "json" };
|
|
1
2
|
import { type ApiSymbolIdentity, normalizedFunctionSignature } from "./api-availability";
|
|
2
3
|
import type {
|
|
3
4
|
ApiConstant,
|
|
@@ -5,22 +6,41 @@ import type {
|
|
|
5
6
|
ApiModule,
|
|
6
7
|
ApiParameter,
|
|
7
8
|
ApiProperty,
|
|
9
|
+
ApiStruct,
|
|
10
|
+
ApiStructMember,
|
|
11
|
+
ApiTypedef,
|
|
8
12
|
ApiVariable,
|
|
9
13
|
} from "./api-doc";
|
|
10
14
|
import { DEFOLD_TYPE_MAP } from "./core-types";
|
|
15
|
+
import { correctionPins, pinMatches, propertyCorrectionPins } from "./correction-pins";
|
|
11
16
|
import {
|
|
12
17
|
type DocCommentParts,
|
|
13
18
|
htmlToCodeText,
|
|
14
19
|
htmlToDocText,
|
|
15
20
|
renderDocComment,
|
|
16
21
|
splitExampleSources,
|
|
22
|
+
summaryFor,
|
|
17
23
|
} from "./doc-comment";
|
|
18
24
|
import type { TranslationStore } from "./example-store";
|
|
19
|
-
import {
|
|
25
|
+
import { lookupExampleSegments, translateProseFences } from "./example-store";
|
|
26
|
+
import {
|
|
27
|
+
appendNotes,
|
|
28
|
+
type IndexBaseNoteResolver,
|
|
29
|
+
type IndexBaseSource,
|
|
30
|
+
indexBaseNoteResolver,
|
|
31
|
+
OVERRIDE_RETURN_SLOT,
|
|
32
|
+
} from "./index-slot-classifications";
|
|
33
|
+
import { type LualsPosition, mapLualsExpression, needsArrayParens } from "./luals-type-expr";
|
|
20
34
|
import { classifyUrlParameter, type UrlParameterTable } from "./url-parameters";
|
|
21
35
|
|
|
36
|
+
export { summaryFor };
|
|
37
|
+
|
|
38
|
+
// Maps one ref-doc type token. `position` says whether the caller supplies the
|
|
39
|
+
// value (a parameter) or the engine hands it back; see `LualsPosition`.
|
|
40
|
+
export type DefoldTypeMapper = (token: string, position?: LualsPosition) => string;
|
|
41
|
+
|
|
22
42
|
export interface EmitOptions {
|
|
23
|
-
mapType?:
|
|
43
|
+
mapType?: DefoldTypeMapper;
|
|
24
44
|
// Constant FQNs from *other* modules, so a foreign token like
|
|
25
45
|
// `graphics.BUFFER_TYPE_COLOR0_BIT` used as a param type inside `render`
|
|
26
46
|
// brands to the same FQN-keyed type its owning module's `const` emits,
|
|
@@ -38,6 +58,23 @@ export interface EmitOptions {
|
|
|
38
58
|
// emit. `regen` supplies the committed `url-parameters.json`; the table
|
|
39
59
|
// arrives as data so this module stays free of `node:fs` (bug-88).
|
|
40
60
|
urlParameters?: UrlParameterTable;
|
|
61
|
+
// Which classification map the index-base notes read. Defaults to the engine
|
|
62
|
+
// map; a library or extension declaration passes its page keys (`[]` for one
|
|
63
|
+
// no page documents), so it never names Defold as the receiver.
|
|
64
|
+
indexBaseSource?: IndexBaseSource;
|
|
65
|
+
// The TS path of each `ENUM`, `STRUCT` and `TYPEDEF` another module declares
|
|
66
|
+
// (`declaredTypePaths`), so a token naming one resolves instead of widening to
|
|
67
|
+
// `unknown`. The module's own declarations resolve without it. Read only by the
|
|
68
|
+
// default engine mapper; a caller-supplied `mapType` keeps its own vocabulary.
|
|
69
|
+
declaredTypes?: ReadonlyMap<string, string>;
|
|
70
|
+
// Tokens a surface names that the engine vocabulary does not (the editor's
|
|
71
|
+
// handles), resolved ahead of it both as whole tokens and as leaves inside a
|
|
72
|
+
// composite. Read by the default mapper together with the declared types.
|
|
73
|
+
typeLeaves?: Readonly<Record<string, string>>;
|
|
74
|
+
// Constant FQNs the target's own engine bindings register, which gate the
|
|
75
|
+
// `UNDOCUMENTED_CONSTANTS` back-fill. Absent for a surface with no vendored
|
|
76
|
+
// bindings (a ref-doc target), where every entry the ref-doc omits is emitted.
|
|
77
|
+
registeredConstants?: ReadonlySet<string>;
|
|
41
78
|
}
|
|
42
79
|
|
|
43
80
|
export const TS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
@@ -174,11 +211,6 @@ export const ARBITRARY_TABLE_SLOTS = new Set([
|
|
|
174
211
|
// not a loss. The audit skips counting these; the emitter already leaves an
|
|
175
212
|
// uncurated slot as `Record`. Add deliberately, one rationale per line.
|
|
176
213
|
export const ARBITRARY_TABLE_SLOT_KEYS = new Set([
|
|
177
|
-
// "the chain definition" / "the shape definition" / "explosion definition" —
|
|
178
|
-
// create-side option tables with no documented field list at this slot.
|
|
179
|
-
"b2d.body.create_chain:param:definition",
|
|
180
|
-
"b2d.body.create_shape:param:definition",
|
|
181
|
-
"b2d.world.explode:param:definition",
|
|
182
214
|
"b2d.joint.create_filter:param:definition", // "optional definition table"
|
|
183
215
|
// opaque Box2D result structs: the doc names no fields.
|
|
184
216
|
"b2d.chain.get_geometry:return:geometry", // "chain geometry table"
|
|
@@ -196,6 +228,18 @@ export const ARBITRARY_TABLE_SLOT_KEYS = new Set([
|
|
|
196
228
|
// set to keep the `droppedMembers` count honest; an uncovered skip (e.g. a new
|
|
197
229
|
// `skipFunctions` entry with no matching overload) still counts, so the gate
|
|
198
230
|
// stays a real signal. Mirrors the ARBITRARY_TABLE_SLOTS audit-honesty pattern.
|
|
231
|
+
// A slot the engine treats as omissible on a function OVERLOAD_COVERED_SKIPS
|
|
232
|
+
// hands to a `*-overloads.d.ts` form without it, where no generated declaration
|
|
233
|
+
// exists for OPTIONAL_SLOT_CORRECTIONS to reach. Availability identity reads it
|
|
234
|
+
// as optional, so a ref-doc that starts marking it is no change. The value is
|
|
235
|
+
// the evidence, as in OPTIONAL_SLOT_CORRECTIONS.
|
|
236
|
+
export const HAND_DECLARED_OPTIONAL_SLOTS: ReadonlyMap<string, string> = new Map(
|
|
237
|
+
(["y", "z"] as const).map((slot) => [
|
|
238
|
+
`vmath.euler_to_quat:param:${slot}`,
|
|
239
|
+
"example `vmath.euler_to_quat(v)` passes a vector3 alone; vmath-overloads.d.ts declares that form",
|
|
240
|
+
]),
|
|
241
|
+
);
|
|
242
|
+
|
|
199
243
|
export const OVERLOAD_COVERED_SKIPS = new Set([
|
|
200
244
|
// go-overloads.d.ts supplies the typed go.get / go.set / go.property shapes.
|
|
201
245
|
"go.get",
|
|
@@ -210,14 +254,55 @@ export const OVERLOAD_COVERED_SKIPS = new Set([
|
|
|
210
254
|
"render.render_target",
|
|
211
255
|
// vmath-overloads.d.ts supplies the vector3 and three-number euler_to_quat arities.
|
|
212
256
|
"vmath.euler_to_quat",
|
|
257
|
+
// vmath-overloads.d.ts supplies the empty form and the table form; the engine
|
|
258
|
+
// raises on an explicit nil, so no optional parameter admits undefined.
|
|
259
|
+
"vmath.vector",
|
|
213
260
|
// vmath-overloads.d.ts supplies the generic covariant signatures.
|
|
214
261
|
"vmath.clamp",
|
|
215
262
|
"vmath.lerp",
|
|
216
263
|
"vmath.slerp",
|
|
217
264
|
"vmath.mul_per_elem",
|
|
218
265
|
"vmath.normalize",
|
|
266
|
+
// sys-overloads.d.ts supplies the disabling form and the form that takes the
|
|
267
|
+
// cooldown the engine requires when enabling.
|
|
268
|
+
"sys.set_engine_throttle",
|
|
269
|
+
// sys-overloads.d.ts supplies the form without a default, which returns nil
|
|
270
|
+
// for a missing key, and the form with one, which always returns a string.
|
|
271
|
+
"sys.get_config_string",
|
|
272
|
+
]);
|
|
273
|
+
|
|
274
|
+
// A declaration upstream files under another function's name: its return slot
|
|
275
|
+
// and prose are the named sibling's, so the element keeps only its own
|
|
276
|
+
// declarations. Keyed `<fqn>:<first return name>`, which tells the misfiled
|
|
277
|
+
// declaration from the real one. `return-correction-provenance.test.ts` reds
|
|
278
|
+
// once no retained target still files it, and the entry is then deleted.
|
|
279
|
+
export const MISFILED_DECLARATIONS: ReadonlyMap<string, string> = new Map([
|
|
280
|
+
// b2d_body_doc.json repeats `get_angle`'s `angle` return under `get_world_center`.
|
|
281
|
+
["b2d.body.get_world_center:angle", "b2d.body.get_angle"],
|
|
282
|
+
]);
|
|
283
|
+
|
|
284
|
+
function isMisfiled(fn: ApiFunction): boolean {
|
|
285
|
+
return MISFILED_DECLARATIONS.has(`${fn.name}:${fn.returnValues[0]?.name ?? ""}`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// skipFunction FQNs withheld because no engine binding registers them in the
|
|
289
|
+
// target that documents them, so the declared call would raise. Like
|
|
290
|
+
// OVERLOAD_COVERED_SKIPS, the audit leaves them out of `droppedMembers`: the
|
|
291
|
+
// surface loses nothing a script could call. The engine-binding diff reports a
|
|
292
|
+
// missing binding if one is declared again.
|
|
293
|
+
export const UNBOUND_SKIPS = new Set([
|
|
294
|
+
"b2d.body.get_contact_list",
|
|
295
|
+
"b2d.body.get_user_data",
|
|
296
|
+
"b2d.body.set_user_data",
|
|
297
|
+
"b2d.body.reset_mass_data",
|
|
219
298
|
]);
|
|
220
299
|
|
|
300
|
+
// skipFunction FQNs withheld because another lane's declaration takes the same
|
|
301
|
+
// global name: the runtime `http.response` table beside the editor VM's
|
|
302
|
+
// `http.response` handle. The audit leaves them out of `droppedMembers`, since
|
|
303
|
+
// a slot that needs the shape types it inline.
|
|
304
|
+
export const NAME_CLASH_SKIPS = new Set(["http.response"]);
|
|
305
|
+
|
|
221
306
|
// Element names whose `table` slot is a prose-only `a table mapping X to Y`
|
|
222
307
|
// shape the field-list parser cannot read, but whose key/value a human curated
|
|
223
308
|
// from the doc. Emitted as `LuaMap<K, V>` because the key is a branded `Hash`,
|
|
@@ -246,6 +331,17 @@ export const RETURN_TYPE_OVERRIDES: ReadonlyMap<string, string> = new Map([
|
|
|
246
331
|
// `returnvalues` is empty while every sibling `editor.tx.*` builder declares
|
|
247
332
|
// one, and `editor.transact()` takes exactly that.
|
|
248
333
|
["editor.tx.add", 'Opaque<"transaction_step">'],
|
|
334
|
+
// The engine binding pushes a value the ref-doc never documents:
|
|
335
|
+
// script_sys.cpp:Sys_Save pushes whether the write succeeded,
|
|
336
|
+
// script_tilemap.cpp:TileMap_SetTile pushes false for an unknown layer or an
|
|
337
|
+
// out-of-range cell, and script_box2d_body_v3.cpp:Body_CreateShape pushes the
|
|
338
|
+
// created shape's info table through PushShapeInfo.
|
|
339
|
+
["sys.save", "boolean"],
|
|
340
|
+
["tilemap.set_tile", "boolean"],
|
|
341
|
+
[
|
|
342
|
+
"b2d.body.create_shape",
|
|
343
|
+
'{ index: number; shape_id: Opaque<"b2Shape">; type: number; sensor: boolean; density: number; friction: number; restitution: number; material: number; child_count: number; is_chain_segment: boolean }',
|
|
344
|
+
],
|
|
249
345
|
]);
|
|
250
346
|
|
|
251
347
|
// A function whose `returnvalues` declare a concrete type while the same slot's
|
|
@@ -269,7 +365,12 @@ export const RETURN_TYPE_OVERRIDES: ReadonlyMap<string, string> = new Map([
|
|
|
269
365
|
export interface ReturnTypeCorrection {
|
|
270
366
|
readonly ts: string;
|
|
271
367
|
readonly upstream: readonly string[];
|
|
368
|
+
// A later release's spelling of the same defect; see `correction-pins.ts`.
|
|
369
|
+
readonly retypedUpstream?: readonly (readonly string[])[];
|
|
272
370
|
readonly reason: string;
|
|
371
|
+
// The named value of a multi-return the correction rewrites; `ts` is then
|
|
372
|
+
// that value's type. Absent, the correction rewrites a single return.
|
|
373
|
+
readonly slot?: string;
|
|
273
374
|
}
|
|
274
375
|
|
|
275
376
|
// Keyed by FQN, mirroring PROPERTY_TYPE_CORRECTIONS.
|
|
@@ -282,11 +383,50 @@ export const RETURN_TYPE_CORRECTIONS: ReadonlyMap<string, ReturnTypeCorrection>
|
|
|
282
383
|
reason: "the body if successful. Otherwise nil.",
|
|
283
384
|
},
|
|
284
385
|
],
|
|
386
|
+
[
|
|
387
|
+
"b2d.shape.ray_cast",
|
|
388
|
+
{
|
|
389
|
+
ts: "{ point: Vector3; normal: Vector3; fraction: number; iterations: number } | undefined",
|
|
390
|
+
upstream: ["table"],
|
|
391
|
+
reason: "and iterations, or nil",
|
|
392
|
+
},
|
|
393
|
+
],
|
|
394
|
+
[
|
|
395
|
+
"b2d.world.cast_ray_closest",
|
|
396
|
+
{
|
|
397
|
+
ts: "{ fixture: number; shape: number; point: Vector3; normal: Vector3; fraction: number; node_visits: number; leaf_visits: number } | undefined",
|
|
398
|
+
upstream: ["table"],
|
|
399
|
+
reason: "and leaf_visits, or nil",
|
|
400
|
+
},
|
|
401
|
+
],
|
|
402
|
+
[
|
|
403
|
+
"gui.new_texture",
|
|
404
|
+
{
|
|
405
|
+
ts: "number | undefined",
|
|
406
|
+
upstream: ["number"],
|
|
407
|
+
reason: "codes if unsuccessful",
|
|
408
|
+
slot: "code",
|
|
409
|
+
},
|
|
410
|
+
],
|
|
411
|
+
// 1.13.2 declares `render_predicate`, whose TYPEDEF aliases `userdata`, and
|
|
412
|
+
// that still emits `number`.
|
|
413
|
+
[
|
|
414
|
+
"render.predicate",
|
|
415
|
+
{
|
|
416
|
+
ts: 'Opaque<"render_predicate">',
|
|
417
|
+
upstream: ["number"],
|
|
418
|
+
retypedUpstream: [["render_predicate"]],
|
|
419
|
+
reason: "new predicate",
|
|
420
|
+
},
|
|
421
|
+
],
|
|
422
|
+
// 1.13.2 declares both as `fun(...:any):any`, which emits
|
|
423
|
+
// `(...args: unknown[]) => unknown` and still drops the contract.
|
|
285
424
|
[
|
|
286
425
|
"socket.protect",
|
|
287
426
|
{
|
|
288
427
|
ts: "(...args: unknown[]) => LuaMultiReturn<[unknown, string | undefined]>",
|
|
289
428
|
upstream: ["function(function())"],
|
|
429
|
+
retypedUpstream: [["fun(...:any):any"]],
|
|
290
430
|
reason: "returns nil followed by an error message.",
|
|
291
431
|
},
|
|
292
432
|
],
|
|
@@ -295,38 +435,172 @@ export const RETURN_TYPE_CORRECTIONS: ReadonlyMap<string, ReturnTypeCorrection>
|
|
|
295
435
|
{
|
|
296
436
|
ts: "<T>(value: T | undefined, ...rest: T extends { readonly __tstlMultiReturn: unknown } ? [boxedMultiReturn: never] : unknown[]) => T",
|
|
297
437
|
upstream: ["function"],
|
|
438
|
+
retypedUpstream: [["fun(...:any):any"]],
|
|
298
439
|
reason: "the customized try function.",
|
|
299
440
|
},
|
|
300
441
|
],
|
|
301
442
|
]);
|
|
302
443
|
|
|
303
|
-
// A parameter whose declared `types`
|
|
304
|
-
// an upstream example, demonstrably
|
|
305
|
-
// a texture unit, a Lua sequence where `table` is
|
|
306
|
-
// a
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
//
|
|
313
|
-
//
|
|
444
|
+
// A parameter whose declared `types` disagree with what the same element's
|
|
445
|
+
// prose, an upstream example, or the engine binding demonstrably takes — a render
|
|
446
|
+
// target by resource name, a texture unit, a Lua sequence where `table` is
|
|
447
|
+
// declared, a hash the binding refuses as a string. Admission rule: only a slot
|
|
448
|
+
// that evidence contradicts; a slot that is merely imprecise is not a candidate.
|
|
449
|
+
// The correction edits the mapped union rather than replacing it: `adds` appends
|
|
450
|
+
// what the slot also takes and `removes` drops members the binding raises on, so
|
|
451
|
+
// the URL address alias, array curation and constant mapping stay live, and
|
|
452
|
+
// optionality stays with `isDocOptional`. Each entry records the upstream tokens
|
|
453
|
+
// it contradicts, plus any later spelling of the same defect in
|
|
454
|
+
// `retypedUpstream`, and applies only to a slot still declaring one of them;
|
|
455
|
+
// `param-correction-provenance.test.ts` pins them against every retained
|
|
456
|
+
// vendored ref-doc. Once no retained target still declares any of them the
|
|
457
|
+
// entry is deleted, never re-pinned.
|
|
314
458
|
export interface ParamTypeCorrection {
|
|
315
|
-
readonly adds
|
|
459
|
+
readonly adds?: string;
|
|
460
|
+
readonly removes?: readonly string[];
|
|
316
461
|
readonly upstream: readonly string[];
|
|
462
|
+
readonly retypedUpstream?: readonly (readonly string[])[];
|
|
317
463
|
readonly reason: string;
|
|
318
464
|
}
|
|
319
465
|
|
|
466
|
+
const CAMERA_ADDRESS_REASON =
|
|
467
|
+
"render_script_camera.cpp:CheckRenderCamera resolves anything but a number handle through ResolveURL, which takes a string or a hash";
|
|
468
|
+
|
|
469
|
+
const CAMERA_SLOT_FUNCTIONS = [
|
|
470
|
+
"get_aspect_ratio",
|
|
471
|
+
"get_auto_aspect_ratio",
|
|
472
|
+
"get_enabled",
|
|
473
|
+
"get_far_z",
|
|
474
|
+
"get_fov",
|
|
475
|
+
"get_near_z",
|
|
476
|
+
"get_orthographic_auto_zoom",
|
|
477
|
+
"get_orthographic_mode",
|
|
478
|
+
"get_orthographic_zoom",
|
|
479
|
+
"get_projection",
|
|
480
|
+
"get_view",
|
|
481
|
+
"screen_to_world",
|
|
482
|
+
"screen_xy_to_world",
|
|
483
|
+
"set_aspect_ratio",
|
|
484
|
+
"set_auto_aspect_ratio",
|
|
485
|
+
"set_far_z",
|
|
486
|
+
"set_fov",
|
|
487
|
+
"set_near_z",
|
|
488
|
+
"set_orthographic_mode",
|
|
489
|
+
"set_orthographic_zoom",
|
|
490
|
+
"world_to_screen",
|
|
491
|
+
] as const;
|
|
492
|
+
|
|
320
493
|
// Keyed `<element>:param:<slot>` like OPTIONAL_SLOT_CORRECTIONS, so a
|
|
321
494
|
// same-named slot on another element is untouched.
|
|
322
|
-
export const PARAM_TYPE_CORRECTIONS: ReadonlyMap<string, ParamTypeCorrection> = new Map
|
|
495
|
+
export const PARAM_TYPE_CORRECTIONS: ReadonlyMap<string, ParamTypeCorrection> = new Map<
|
|
496
|
+
string,
|
|
497
|
+
ParamTypeCorrection
|
|
498
|
+
>([
|
|
499
|
+
...CAMERA_SLOT_FUNCTIONS.map(
|
|
500
|
+
(name) =>
|
|
501
|
+
[
|
|
502
|
+
`camera.${name}:param:camera`,
|
|
503
|
+
{
|
|
504
|
+
adds: "string | Hash",
|
|
505
|
+
upstream: ["url", "number", "nil"],
|
|
506
|
+
reason: CAMERA_ADDRESS_REASON,
|
|
507
|
+
},
|
|
508
|
+
] as const,
|
|
509
|
+
),
|
|
510
|
+
...(["set_group", "get_maskbit", "set_maskbit"] as const).map(
|
|
511
|
+
(name) =>
|
|
512
|
+
[
|
|
513
|
+
`physics.${name}:param:group`,
|
|
514
|
+
{
|
|
515
|
+
adds: "Hash",
|
|
516
|
+
upstream: ["string"],
|
|
517
|
+
reason: `script_physics.cpp reads the ${name} group with CheckHashOrString(L, 2)`,
|
|
518
|
+
},
|
|
519
|
+
] as const,
|
|
520
|
+
),
|
|
521
|
+
[
|
|
522
|
+
"resource.get_text_metrics:param:url",
|
|
523
|
+
{
|
|
524
|
+
adds: "string",
|
|
525
|
+
upstream: ["hash"],
|
|
526
|
+
reason: "script_resource.cpp:GetTextMetrics reads the font path with CheckHashOrString(L, 1)",
|
|
527
|
+
},
|
|
528
|
+
],
|
|
529
|
+
[
|
|
530
|
+
"collectionproxy.get_resources:param:collectionproxy",
|
|
531
|
+
{
|
|
532
|
+
adds: "string | Hash",
|
|
533
|
+
upstream: ["url"],
|
|
534
|
+
reason:
|
|
535
|
+
"script_collectionproxy.cpp:CollectionProxy_GetResourcesInternal resolves the proxy through GetComponentFromLua, which takes a string or a hash",
|
|
536
|
+
},
|
|
537
|
+
],
|
|
538
|
+
...(["screen_to_local", "set_screen_position"] as const).map(
|
|
539
|
+
(name) =>
|
|
540
|
+
[
|
|
541
|
+
`gui.${name}:param:screen_position`,
|
|
542
|
+
{
|
|
543
|
+
adds: "Vector4",
|
|
544
|
+
upstream: ["vector3"],
|
|
545
|
+
reason: "gui_script.cpp:GetPositionFromArgumentIndex takes a vector3 or a vector4",
|
|
546
|
+
},
|
|
547
|
+
] as const,
|
|
548
|
+
),
|
|
549
|
+
[
|
|
550
|
+
"particlefx.set_constant:param:value",
|
|
551
|
+
{
|
|
552
|
+
adds: "Matrix4",
|
|
553
|
+
upstream: ["vector4"],
|
|
554
|
+
reason: "script_particlefx.cpp:ParticleFX_SetConstant takes a vector4 or a matrix4 value",
|
|
555
|
+
},
|
|
556
|
+
],
|
|
557
|
+
...(["animate", "cancel_animations"] as const).map(
|
|
558
|
+
(name) =>
|
|
559
|
+
[
|
|
560
|
+
`gui.${name}:param:property`,
|
|
561
|
+
{
|
|
562
|
+
adds: "Hash",
|
|
563
|
+
upstream: name === "animate" ? ["string", "constant"] : ["nil", "string", "constant"],
|
|
564
|
+
reason: `gui_script.cpp reads the ${name} property with CheckHashOrString(L, 2)`,
|
|
565
|
+
},
|
|
566
|
+
] as const,
|
|
567
|
+
),
|
|
568
|
+
[
|
|
569
|
+
"sound.get_group_name:param:group",
|
|
570
|
+
{
|
|
571
|
+
removes: ["string"],
|
|
572
|
+
upstream: ["string", "hash"],
|
|
573
|
+
reason: "script_sound.cpp:Sound_GetGroupName reads CheckHash(L, 1), which refuses a string",
|
|
574
|
+
},
|
|
575
|
+
],
|
|
576
|
+
...(["get_mesh_enabled", "set_mesh_enabled"] as const).map(
|
|
577
|
+
(name) =>
|
|
578
|
+
[
|
|
579
|
+
`model.${name}:param:mesh_id`,
|
|
580
|
+
{
|
|
581
|
+
removes: ["Url"],
|
|
582
|
+
upstream: ["string", "hash", "url"],
|
|
583
|
+
reason: `script_model.cpp reads the ${name} mesh id with CheckHashOrString(L, 2), which refuses a url`,
|
|
584
|
+
},
|
|
585
|
+
] as const,
|
|
586
|
+
),
|
|
587
|
+
[
|
|
588
|
+
"resource.create_texture_async:param:path",
|
|
589
|
+
{
|
|
590
|
+
removes: ["Hash"],
|
|
591
|
+
upstream: ["string", "hash"],
|
|
592
|
+
reason:
|
|
593
|
+
"script_resource.cpp:CheckCreateTextureResourceParams reads the path with luaL_checkstring(L, 1), which refuses a hash",
|
|
594
|
+
},
|
|
595
|
+
],
|
|
323
596
|
[
|
|
324
597
|
"render.set_render_target:param:render_target",
|
|
325
598
|
{
|
|
326
599
|
adds: "string | Hash | typeof render.RENDER_TARGET_DEFAULT",
|
|
327
600
|
upstream: ["render_target"],
|
|
601
|
+
retypedUpstream: [["render_target", "string", "hash", "nil"]],
|
|
328
602
|
reason:
|
|
329
|
-
"\"render.RENDER_TARGET_DEFAULT to set the default render target\"; example `render.set_render_target('my_rt_resource')
|
|
603
|
+
"\"render.RENDER_TARGET_DEFAULT to set the default render target\"; example `render.set_render_target('my_rt_resource')`; 1.13.2 adds the names but still omits the constant",
|
|
330
604
|
},
|
|
331
605
|
],
|
|
332
606
|
[
|
|
@@ -377,9 +651,21 @@ export const PARAM_TYPE_CORRECTIONS: ReadonlyMap<string, ParamTypeCorrection> =
|
|
|
377
651
|
[
|
|
378
652
|
"render.set_camera:param:camera",
|
|
379
653
|
{
|
|
380
|
-
adds: "string",
|
|
654
|
+
adds: "string | Hash",
|
|
381
655
|
upstream: ["url", "number", "nil"],
|
|
382
|
-
reason:
|
|
656
|
+
reason:
|
|
657
|
+
'example `render.set_camera("main:/my_go#camera")`; render_script.cpp:RenderScript_SetCamera resolves a non-number camera through ResolveURL, which also takes a hash',
|
|
658
|
+
},
|
|
659
|
+
],
|
|
660
|
+
[
|
|
661
|
+
"render.draw:param:predicate",
|
|
662
|
+
{
|
|
663
|
+
adds: 'Opaque<"render_predicate">',
|
|
664
|
+
removes: ["number"],
|
|
665
|
+
upstream: ["number"],
|
|
666
|
+
retypedUpstream: [["render_predicate"]],
|
|
667
|
+
reason:
|
|
668
|
+
"render_script.cpp:RenderScript_Draw reads the predicate with RenderScriptPredicate_Check, the userdata render.predicate returns; 1.13.2's `render_predicate` still emits `number`",
|
|
383
669
|
},
|
|
384
670
|
],
|
|
385
671
|
[
|
|
@@ -387,7 +673,9 @@ export const PARAM_TYPE_CORRECTIONS: ReadonlyMap<string, ParamTypeCorrection> =
|
|
|
387
673
|
{
|
|
388
674
|
adds: "boolean",
|
|
389
675
|
upstream: ["string", "hash", "url", "table"],
|
|
390
|
-
|
|
676
|
+
retypedUpstream: [["string", "hash", "url", "(string|hash|url)[]"]],
|
|
677
|
+
reason:
|
|
678
|
+
"example `go.delete(true)` deletes the script game object and its children; 1.13.2 types the id list but still omits `boolean`",
|
|
391
679
|
},
|
|
392
680
|
],
|
|
393
681
|
[
|
|
@@ -420,8 +708,9 @@ export const PARAM_TYPE_CORRECTIONS: ReadonlyMap<string, ParamTypeCorrection> =
|
|
|
420
708
|
{
|
|
421
709
|
adds: "readonly unknown[]",
|
|
422
710
|
upstream: ["table"],
|
|
711
|
+
retypedUpstream: [["table<any, any>"]],
|
|
423
712
|
reason:
|
|
424
|
-
'example builds `my_table` with `table.insert(my_table, "my_value")` and passes it to `sys.save`',
|
|
713
|
+
'example builds `my_table` with `table.insert(my_table, "my_value")` and passes it to `sys.save`; 1.13.2\'s `table<any, any>` emits a `LuaTable`, which refuses an array',
|
|
425
714
|
},
|
|
426
715
|
],
|
|
427
716
|
[
|
|
@@ -429,46 +718,41 @@ export const PARAM_TYPE_CORRECTIONS: ReadonlyMap<string, ParamTypeCorrection> =
|
|
|
429
718
|
{
|
|
430
719
|
adds: "readonly unknown[]",
|
|
431
720
|
upstream: ["table"],
|
|
721
|
+
retypedUpstream: [["table<any, any>"]],
|
|
432
722
|
reason:
|
|
433
|
-
'example builds `my_table` with `table.insert(my_table, "my_value")` and passes it to `sys.serialize`',
|
|
723
|
+
'example builds `my_table` with `table.insert(my_table, "my_value")` and passes it to `sys.serialize`; 1.13.2\'s `table<any, any>` emits a `LuaTable`, which refuses an array',
|
|
724
|
+
},
|
|
725
|
+
],
|
|
726
|
+
[
|
|
727
|
+
"window.set_dim_mode:param:mode",
|
|
728
|
+
{
|
|
729
|
+
removes: ["window.DIMMING"],
|
|
730
|
+
adds: "window.DimModeStateSettable",
|
|
731
|
+
upstream: ["window.DIMMING"],
|
|
732
|
+
reason:
|
|
733
|
+
'script_window.cpp:SetDimMode raises "The dim mode specified is not supported." for anything but DIMMING_ON and DIMMING_OFF',
|
|
734
|
+
},
|
|
735
|
+
],
|
|
736
|
+
...(["texture", "render_target"] as const).map((handle): [string, ParamTypeCorrection] => [
|
|
737
|
+
`resource.get_${handle}_info:param:path`,
|
|
738
|
+
{
|
|
739
|
+
adds: "number",
|
|
740
|
+
upstream: ["hash", "string", handle],
|
|
741
|
+
reason:
|
|
742
|
+
"script_resource.cpp reads any number in slot 1 as the handle, which `info.handle` returns and older targets declared as `number`",
|
|
743
|
+
},
|
|
744
|
+
]),
|
|
745
|
+
[
|
|
746
|
+
"tilemap.set_tile:param:transform_bitmask",
|
|
747
|
+
{
|
|
748
|
+
adds: "number",
|
|
749
|
+
upstream: ["tilemap.TRANSFORM"],
|
|
750
|
+
reason:
|
|
751
|
+
"a bitmask: the ref-doc example passes tilemap.H_FLIP + tilemap.V_FLIP + tilemap.ROTATE_90, a sum no single TRANSFORM member names",
|
|
434
752
|
},
|
|
435
753
|
],
|
|
436
754
|
]);
|
|
437
755
|
|
|
438
|
-
// Parameter doc text whose upstream wording describes Lua rather than the
|
|
439
|
-
// TypeScript a user writes. The transpiler adds one to a literal `options.index`
|
|
440
|
-
// at these calls, so the 1-based wording would tell the author to be off by one.
|
|
441
|
-
// The `.d.ts` emit and the API reference both apply it, and applying it throws
|
|
442
|
-
// when `from` is absent: an upstream rewording fails the build instead of
|
|
443
|
-
// shipping the stale text alongside the lowering.
|
|
444
|
-
export interface ParamDocRewrite {
|
|
445
|
-
readonly from: string;
|
|
446
|
-
readonly to: string;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const ZERO_BASED_INDEX_OPTION_DOC: ParamDocRewrite = {
|
|
450
|
-
from: "index into array property (1 based)",
|
|
451
|
-
to: "zero-based index into array property; the transpiler emits Defold's 1-based index",
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
// Keyed `<element>:param:<slot>` like PARAM_TYPE_CORRECTIONS.
|
|
455
|
-
export const PARAM_DOC_REWRITES: ReadonlyMap<string, ParamDocRewrite> = new Map([
|
|
456
|
-
["go.get:param:options", ZERO_BASED_INDEX_OPTION_DOC],
|
|
457
|
-
["go.set:param:options", ZERO_BASED_INDEX_OPTION_DOC],
|
|
458
|
-
["gui.get:param:options", ZERO_BASED_INDEX_OPTION_DOC],
|
|
459
|
-
["gui.set:param:options", ZERO_BASED_INDEX_OPTION_DOC],
|
|
460
|
-
]);
|
|
461
|
-
|
|
462
|
-
export function rewriteParamDoc(elementName: string, slotName: string, doc: string): string {
|
|
463
|
-
const key = tableSlotKey(elementName, "param", slotName);
|
|
464
|
-
const rewrite = PARAM_DOC_REWRITES.get(key);
|
|
465
|
-
if (rewrite === undefined) return doc;
|
|
466
|
-
if (!doc.includes(rewrite.from)) {
|
|
467
|
-
throw new Error(`PARAM_DOC_REWRITES ${key}: upstream doc no longer contains "${rewrite.from}"`);
|
|
468
|
-
}
|
|
469
|
-
return doc.replaceAll(rewrite.from, rewrite.to);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
756
|
// A property whose upstream `<span class="type">` states a type the engine does
|
|
473
757
|
// not use. Unlike every other override here, which fills a gap upstream left
|
|
474
758
|
// empty, these contradict a token upstream explicitly declares — so each entry
|
|
@@ -482,6 +766,8 @@ export function rewriteParamDoc(elementName: string, slotName: string, doc: stri
|
|
|
482
766
|
export interface PropertyTypeCorrection {
|
|
483
767
|
readonly ts: string;
|
|
484
768
|
readonly upstream: string;
|
|
769
|
+
// A later release's span for the same defect; see `correction-pins.ts`.
|
|
770
|
+
readonly retypedUpstream?: readonly string[];
|
|
485
771
|
readonly reason: string;
|
|
486
772
|
}
|
|
487
773
|
|
|
@@ -497,6 +783,24 @@ export const PROPERTY_TYPE_CORRECTIONS: ReadonlyMap<string, PropertyTypeCorrecti
|
|
|
497
783
|
"a count of animation frames, which the prose confirms; `hash` is a copy-paste of the sibling animation-id spans",
|
|
498
784
|
},
|
|
499
785
|
],
|
|
786
|
+
[
|
|
787
|
+
"go.scale",
|
|
788
|
+
{
|
|
789
|
+
ts: "number | Vector3",
|
|
790
|
+
upstream: "number",
|
|
791
|
+
reason:
|
|
792
|
+
"the engine reads a vector3 back from `go.get`, and `go.set` also takes a number, which it applies as a uniform scale",
|
|
793
|
+
},
|
|
794
|
+
],
|
|
795
|
+
[
|
|
796
|
+
"label.scale",
|
|
797
|
+
{
|
|
798
|
+
ts: "Vector3",
|
|
799
|
+
upstream: "number | vector3",
|
|
800
|
+
reason:
|
|
801
|
+
'the engine reads a vector3 back from `go.get`, and `go.set` raises "must be a vmath.vector3" when given a number',
|
|
802
|
+
},
|
|
803
|
+
],
|
|
500
804
|
[
|
|
501
805
|
"camera.projection",
|
|
502
806
|
{
|
|
@@ -543,9 +847,10 @@ export const PROPERTY_KEY_RANGES: ReadonlyMap<string, PropertyKeyRange> = new Ma
|
|
|
543
847
|
// examples skip — so the emitted declaration rejects the call the docs teach.
|
|
544
848
|
//
|
|
545
849
|
// Keyed `<element>:param:<slot>`, the shape `tableSlotKey` builds, so a
|
|
546
|
-
// same-named slot on another element is untouched. The value is the
|
|
547
|
-
//
|
|
548
|
-
//
|
|
850
|
+
// same-named slot on another element is untouched. The value is the evidence for
|
|
851
|
+
// that one entry: the example call that omits it, the doc phrase that names it
|
|
852
|
+
// optional, or the engine binding that reads it as omissible
|
|
853
|
+
// (`<file>:<cFunction>`). Both emitted shapes follow from one predicate — a
|
|
549
854
|
// trailing slot becomes `slot?:`, an interior one `slot: T | undefined`.
|
|
550
855
|
//
|
|
551
856
|
// Demonstrated omissions only. `resource.set_texture`'s buffer and
|
|
@@ -557,10 +862,6 @@ export const OPTIONAL_SLOT_CORRECTIONS: ReadonlyMap<string, string> = new Map([
|
|
|
557
862
|
'example `resource.create_texture("/my_custom_texture.texturec", tparams)`; "optional buffer of precreated pixel data"',
|
|
558
863
|
],
|
|
559
864
|
["resource.create_texture_async:param:buffer", '"optional buffer of precreated pixel data"'],
|
|
560
|
-
[
|
|
561
|
-
"resource.create_texture_async:param:callback",
|
|
562
|
-
'example `resource.create_texture_async("/my_texture.texturec", tparams, tbuffer)`',
|
|
563
|
-
],
|
|
564
865
|
[
|
|
565
866
|
"gui.new_texture:param:flip",
|
|
566
867
|
'example `gui.new_texture("orange_tx", w, h, "rgb", string.rep(orange, w * h))`',
|
|
@@ -569,7 +870,6 @@ export const OPTIONAL_SLOT_CORRECTIONS: ReadonlyMap<string, string> = new Map([
|
|
|
569
870
|
"gui.set_texture_data:param:flip",
|
|
570
871
|
'example `gui.set_texture_data("dynamic_tx", w, h, "rgb", string.rep(orange, w * h))`',
|
|
571
872
|
],
|
|
572
|
-
["sys.set_engine_throttle:param:cooldown", "example `sys.set_engine_throttle(false)`"],
|
|
573
873
|
[
|
|
574
874
|
"b2d.fixture.set_shape:param:update_mass",
|
|
575
875
|
'2 of 3 examples omit it; "The body mass is not updated unless update_mass is true"',
|
|
@@ -583,6 +883,26 @@ export const OPTIONAL_SLOT_CORRECTIONS: ReadonlyMap<string, string> = new Map([
|
|
|
583
883
|
'"optional maximum translation fraction, defaults to 1"',
|
|
584
884
|
],
|
|
585
885
|
["iap.buy:param:options", '"optional parameters as properties"'],
|
|
886
|
+
[
|
|
887
|
+
"go.world_to_local_position:param:url",
|
|
888
|
+
"gameobject_script.cpp:Script_WorldToLocalPosition resolves argument 2 with ResolveInstance, which takes the calling instance when it is absent",
|
|
889
|
+
],
|
|
890
|
+
[
|
|
891
|
+
"go.world_to_local_transform:param:url",
|
|
892
|
+
"gameobject_script.cpp:Script_WorldToLocalTransfrom resolves argument 2 with ResolveInstance, which takes the calling instance when it is absent",
|
|
893
|
+
],
|
|
894
|
+
[
|
|
895
|
+
"go.cancel_animations:param:url",
|
|
896
|
+
"gameobject_script.cpp:Script_CancelAnimations resolves argument 1 with ResolveURL, which takes the calling instance when it is absent",
|
|
897
|
+
],
|
|
898
|
+
[
|
|
899
|
+
"b2d.fixture.set_density:param:update_mass",
|
|
900
|
+
"script_box2d_fixture_v2.cpp:Fixture_SetDensity reads argument 4 only when lua_gettop(L) >= 4",
|
|
901
|
+
],
|
|
902
|
+
[
|
|
903
|
+
"render.set_render_target:param:render_target",
|
|
904
|
+
"render_script.cpp:RenderScript_SetRenderTarget sets the default target when lua_gettop(L) == 0",
|
|
905
|
+
],
|
|
586
906
|
["socket.newtry:param:finalizer", "socket.protect's example `local try = socket.newtry()`"],
|
|
587
907
|
...(
|
|
588
908
|
[
|
|
@@ -605,6 +925,159 @@ export const OPTIONAL_SLOT_CORRECTIONS: ReadonlyMap<string, string> = new Map([
|
|
|
605
925
|
),
|
|
606
926
|
]);
|
|
607
927
|
|
|
928
|
+
// A parameter the ref-doc marks omissible — `[name]` or a `nil` type — that the
|
|
929
|
+
// engine binding refuses to go without, so the emitted declaration accepts a call
|
|
930
|
+
// that raises. It applies only where a ref-doc marks the slot, so it follows each
|
|
931
|
+
// target's own markup, and it overrides OPTIONAL_SLOT_CORRECTIONS: the slot is
|
|
932
|
+
// never emitted with `?`. `nil` records whether the binding still takes an
|
|
933
|
+
// explicit nil there — `lua_isnil` answers false past the stack top, so such a
|
|
934
|
+
// slot must be passed yet may be nil — which emits `slot: T | undefined`.
|
|
935
|
+
//
|
|
936
|
+
// Keyed `<element>:param:<slot>` like OPTIONAL_SLOT_CORRECTIONS. `evidence` is the
|
|
937
|
+
// C++ binding, `<file>:<cFunction>`, and what it does with the slot;
|
|
938
|
+
// `required-correction-provenance.test.ts` deletes an entry once no retained
|
|
939
|
+
// target marks the slot omissible.
|
|
940
|
+
export interface RequiredSlotCorrection {
|
|
941
|
+
readonly nil: boolean;
|
|
942
|
+
readonly evidence: string;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
const COMPONENT_URL_EVIDENCE =
|
|
946
|
+
"resolves the url through GetComponentFromLua, which refuses the calling script's own url";
|
|
947
|
+
|
|
948
|
+
export const REQUIRED_SLOT_CORRECTIONS: ReadonlyMap<string, RequiredSlotCorrection> = new Map<
|
|
949
|
+
string,
|
|
950
|
+
RequiredSlotCorrection
|
|
951
|
+
>([
|
|
952
|
+
[
|
|
953
|
+
"resource.create_texture_async:param:callback",
|
|
954
|
+
{
|
|
955
|
+
nil: false,
|
|
956
|
+
evidence:
|
|
957
|
+
"script_resource.cpp:CreateTextureAsync hands argument 4 to dmScript::CreateCallback, which raises through luaL_checktype(L, 4, LUA_TFUNCTION)",
|
|
958
|
+
},
|
|
959
|
+
],
|
|
960
|
+
...(["factory", "collectionfactory"] as const).flatMap((namespace) => {
|
|
961
|
+
const file = namespace === "factory" ? "script_factory.cpp" : "script_collection_factory.cpp";
|
|
962
|
+
const prefix = namespace === "factory" ? "FactoryComp" : "CollectionFactoryComp";
|
|
963
|
+
return [
|
|
964
|
+
...(
|
|
965
|
+
[
|
|
966
|
+
["get_status", "GetStatus"],
|
|
967
|
+
["unload", "Unload"],
|
|
968
|
+
["set_prototype", "SetPrototype"],
|
|
969
|
+
["load", "Load"],
|
|
970
|
+
] as const
|
|
971
|
+
).map(
|
|
972
|
+
([name, cFunction]) =>
|
|
973
|
+
[
|
|
974
|
+
`${namespace}.${name}:param:url`,
|
|
975
|
+
{ nil: false, evidence: `${file}:${prefix}_${cFunction} ${COMPONENT_URL_EVIDENCE}` },
|
|
976
|
+
] as const,
|
|
977
|
+
),
|
|
978
|
+
[
|
|
979
|
+
`${namespace}.load:param:complete_function`,
|
|
980
|
+
{
|
|
981
|
+
nil: false,
|
|
982
|
+
evidence: `${file}:${prefix}_Load raises unless argument 2 is a function`,
|
|
983
|
+
},
|
|
984
|
+
] as const,
|
|
985
|
+
[
|
|
986
|
+
`${namespace}.set_prototype:param:prototype`,
|
|
987
|
+
{
|
|
988
|
+
nil: true,
|
|
989
|
+
evidence: `${file}:${prefix}_SetPrototype reads luaL_checkstring(L, 2) unless lua_isnil(L, 2)`,
|
|
990
|
+
},
|
|
991
|
+
] as const,
|
|
992
|
+
];
|
|
993
|
+
}),
|
|
994
|
+
[
|
|
995
|
+
"collectionproxy.set_collection:param:url",
|
|
996
|
+
{
|
|
997
|
+
nil: false,
|
|
998
|
+
evidence: `script_collectionproxy.cpp:CollectionProxy_SetCollection ${COMPONENT_URL_EVIDENCE}`,
|
|
999
|
+
},
|
|
1000
|
+
],
|
|
1001
|
+
[
|
|
1002
|
+
"collectionproxy.set_collection:param:prototype",
|
|
1003
|
+
{
|
|
1004
|
+
nil: true,
|
|
1005
|
+
evidence:
|
|
1006
|
+
"script_collectionproxy.cpp:CollectionProxy_SetCollection reads luaL_checkstring(L, 2) unless lua_isnil(L, 2)",
|
|
1007
|
+
},
|
|
1008
|
+
],
|
|
1009
|
+
[
|
|
1010
|
+
"sound.set_gain:param:gain",
|
|
1011
|
+
{ nil: false, evidence: "script_sound.cpp:Sound_SetGain reads luaL_checknumber(L, 2)" },
|
|
1012
|
+
],
|
|
1013
|
+
[
|
|
1014
|
+
"sound.set_pan:param:pan",
|
|
1015
|
+
{ nil: false, evidence: "script_sound.cpp:Sound_SetPan reads luaL_checknumber(L, 2)" },
|
|
1016
|
+
],
|
|
1017
|
+
[
|
|
1018
|
+
"resource.create_buffer:param:table",
|
|
1019
|
+
{
|
|
1020
|
+
nil: false,
|
|
1021
|
+
evidence: "script_resource.cpp:CreateBuffer checks luaL_checktype(L, 2, LUA_TTABLE)",
|
|
1022
|
+
},
|
|
1023
|
+
],
|
|
1024
|
+
[
|
|
1025
|
+
"resource.create_sound_data:param:options",
|
|
1026
|
+
{
|
|
1027
|
+
nil: false,
|
|
1028
|
+
evidence: "script_resource.cpp:CreateSoundData checks luaL_checktype(L, 2, LUA_TTABLE)",
|
|
1029
|
+
},
|
|
1030
|
+
],
|
|
1031
|
+
...(["move_above", "move_below"] as const).map(
|
|
1032
|
+
(name) =>
|
|
1033
|
+
[
|
|
1034
|
+
`gui.${name}:param:reference`,
|
|
1035
|
+
{
|
|
1036
|
+
nil: true,
|
|
1037
|
+
evidence: `gui_script.cpp:Lua${name === "move_above" ? "MoveAbove" : "MoveBelow"} reads a node from argument 2 unless lua_isnil(L, 2)`,
|
|
1038
|
+
},
|
|
1039
|
+
] as const,
|
|
1040
|
+
),
|
|
1041
|
+
[
|
|
1042
|
+
"gui.set_parent:param:parent",
|
|
1043
|
+
{
|
|
1044
|
+
nil: true,
|
|
1045
|
+
evidence: "gui_script.cpp:LuaSetParent reads a node from argument 2 unless lua_isnil(L, 2)",
|
|
1046
|
+
},
|
|
1047
|
+
],
|
|
1048
|
+
[
|
|
1049
|
+
"gui.set:param:value",
|
|
1050
|
+
{
|
|
1051
|
+
nil: true,
|
|
1052
|
+
evidence: "gui_script.cpp:LuaSet converts argument 3 with LuaToVar unless lua_isnil(L, 3)",
|
|
1053
|
+
},
|
|
1054
|
+
],
|
|
1055
|
+
[
|
|
1056
|
+
"window.set_listener:param:callback",
|
|
1057
|
+
{
|
|
1058
|
+
nil: true,
|
|
1059
|
+
evidence: "script_window.cpp:SetListener checks luaL_checkany(L, 1); nil clears the listener",
|
|
1060
|
+
},
|
|
1061
|
+
],
|
|
1062
|
+
[
|
|
1063
|
+
"html5.set_interaction_listener:param:callback",
|
|
1064
|
+
{
|
|
1065
|
+
nil: true,
|
|
1066
|
+
evidence:
|
|
1067
|
+
"script_html5_js.cpp:Html5_SetInteractionListener checks luaL_checkany(L, 1); nil clears the listener",
|
|
1068
|
+
},
|
|
1069
|
+
],
|
|
1070
|
+
]);
|
|
1071
|
+
|
|
1072
|
+
function requiredSlotCorrection(
|
|
1073
|
+
p: ApiParameter,
|
|
1074
|
+
elementName: string,
|
|
1075
|
+
): RequiredSlotCorrection | undefined {
|
|
1076
|
+
return isMarkedOptional(p)
|
|
1077
|
+
? REQUIRED_SLOT_CORRECTIONS.get(tableSlotKey(elementName, "param", p.name))
|
|
1078
|
+
: undefined;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
608
1081
|
// A first slot upstream documents as standing in for a second argument list:
|
|
609
1082
|
// every `b2d.shape` function takes a shape handle "or pass `body, shape_index`".
|
|
610
1083
|
// The element keeps its declared signature and gains an overload with
|
|
@@ -633,7 +1106,7 @@ export const FIRST_SLOT_ALTERNATIVES: ReadonlyMap<string, FirstSlotAlternative>
|
|
|
633
1106
|
},
|
|
634
1107
|
{
|
|
635
1108
|
name: "shape_index",
|
|
636
|
-
doc: "The shape's
|
|
1109
|
+
doc: "The shape's position on that body, in the order <code>b2d.body.get_shapes</code> lists them.",
|
|
637
1110
|
types: ["number"],
|
|
638
1111
|
isOptional: false,
|
|
639
1112
|
},
|
|
@@ -1067,9 +1540,17 @@ export function constantUnionAlias(fqns: readonly string[]): ConstantUnionAlias
|
|
|
1067
1540
|
export function constantUnionAliasDeclarations(namespace: string): string[] {
|
|
1068
1541
|
return CONSTANT_UNION_ALIASES.filter((row) => row.home === namespace)
|
|
1069
1542
|
.sort((a, b) => a.name.localeCompare(b.name))
|
|
1070
|
-
.map(
|
|
1071
|
-
|
|
1072
|
-
|
|
1543
|
+
.map((row) => unionAliasDeclaration(row.name, row.members));
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
function unionAliasDeclaration(
|
|
1547
|
+
name: string,
|
|
1548
|
+
members: readonly string[],
|
|
1549
|
+
nilable: ReadonlySet<string> = new Set(),
|
|
1550
|
+
): string {
|
|
1551
|
+
const arm = (member: string) =>
|
|
1552
|
+
nilable.has(member) ? `NonNullable<typeof ${member}>` : `typeof ${member}`;
|
|
1553
|
+
return `type ${name} = ${members.map(arm).join(" | ")};`;
|
|
1073
1554
|
}
|
|
1074
1555
|
|
|
1075
1556
|
const DOC_CONSTANT_TOKEN =
|
|
@@ -1219,7 +1700,11 @@ export const HOMOGENEOUS_ARRAY_SLOTS: ReadonlyMap<string, string | readonly stri
|
|
|
1219
1700
|
// (`tilemap.get_tiles` → `tiles[row][col]`).
|
|
1220
1701
|
export type NestedMapping = { key: string; value: string };
|
|
1221
1702
|
|
|
1222
|
-
|
|
1703
|
+
// A curation applies where the slot declares the prose `table` token, or a later
|
|
1704
|
+
// spelling a newer release retypes the same slot to without fixing it
|
|
1705
|
+
// (`retypedUpstream`, matched whole): 1.13.2's `table<hash, table<string|hash, any>>`
|
|
1706
|
+
// for `collectionfactory.create`'s properties.
|
|
1707
|
+
export type TableSlotCuration = (
|
|
1223
1708
|
| { kind: "mapping"; key: string; value: string | readonly TableField[] | NestedMapping }
|
|
1224
1709
|
| { kind: "array"; element: string | readonly string[] }
|
|
1225
1710
|
| { kind: "object"; fields: readonly TableField[] }
|
|
@@ -1228,7 +1713,11 @@ export type TableSlotCuration =
|
|
|
1228
1713
|
// keyed by an arbitrary name (constant/sampler/attribute). The parser recovers
|
|
1229
1714
|
// the inner args fields; emit wraps them as `Record<string, { <fields> }>`,
|
|
1230
1715
|
// restoring the keyed-by-name layer the flat object curation drops.
|
|
1231
|
-
| { kind: "keyed-object" }
|
|
1716
|
+
| { kind: "keyed-object" }
|
|
1717
|
+
// A slot whose faithful shape no field list can carry, such as a union
|
|
1718
|
+
// discriminated on `type`. `ts` is emitted verbatim, already mapped.
|
|
1719
|
+
| { kind: "verbatim"; ts: string }
|
|
1720
|
+
) & { readonly retypedUpstream?: readonly string[] };
|
|
1232
1721
|
|
|
1233
1722
|
export const SOCKET_HANDLE_TOKENS = ["client", "master", "unconnected"] as const;
|
|
1234
1723
|
|
|
@@ -1237,8 +1726,10 @@ export const SOCKET_HANDLE_TOKENS = ["client", "master", "unconnected"] as const
|
|
|
1237
1726
|
// b2d submodule curations below so a single shape is defined once.
|
|
1238
1727
|
//
|
|
1239
1728
|
// The shape table is a discriminated union of circle/edge/polygon/box/chain
|
|
1240
|
-
// forms keyed by `type` (a b2d.shape.SHAPE_TYPE_*
|
|
1241
|
-
// field is optional because only one form's
|
|
1729
|
+
// forms, plus the Box2D v3 capsule, keyed by `type` (a b2d.shape.SHAPE_TYPE_*
|
|
1730
|
+
// constant); every kind-specific field is optional because only one form's
|
|
1731
|
+
// fields are present per value. The capsule's `center1`/`center2` come from
|
|
1732
|
+
// script_box2d_shape_v3.cpp, which reads them; the ref-doc prose never names them.
|
|
1242
1733
|
const B2D_SHAPE_TABLE_FIELDS: readonly TableField[] = [
|
|
1243
1734
|
{ name: "type", types: ["number"] },
|
|
1244
1735
|
{ name: "radius", types: ["number"], optional: true },
|
|
@@ -1254,7 +1745,56 @@ const B2D_SHAPE_TABLE_FIELDS: readonly TableField[] = [
|
|
|
1254
1745
|
{ name: "loop", types: ["boolean"], optional: true },
|
|
1255
1746
|
{ name: "prev_vertex", types: ["vector3"], optional: true },
|
|
1256
1747
|
{ name: "next_vertex", types: ["vector3"], optional: true },
|
|
1748
|
+
{ name: "center1", types: ["vector3"], optional: true },
|
|
1749
|
+
{ name: "center2", types: ["vector3"], optional: true },
|
|
1257
1750
|
];
|
|
1751
|
+
// The shape table a Box2D binding reads, one arm per `type`. CheckShapeDef
|
|
1752
|
+
// (v2/script_box2d_shape_v2.cpp, v3/script_box2d_shape_v3.cpp) raises without
|
|
1753
|
+
// `type` and without the fields that type reads with luaL_checknumber or
|
|
1754
|
+
// CheckVec2; the rest go through TryGet*Field. A polygon takes `hx`/`hy` or
|
|
1755
|
+
// `vertices`. `SHAPE_TYPE_BOX` is the polygon value and `SHAPE_TYPE_EDGE` the v3
|
|
1756
|
+
// segment value, so each shares its arm. Each backend raises "Unsupported shape
|
|
1757
|
+
// type" on the kinds it does not build: `create_fixture` and `fixture.set_shape`
|
|
1758
|
+
// are registered only by v2/script_box2d_*_v2.cpp and take B2D_V2_SHAPE_TS (no
|
|
1759
|
+
// capsule, and an edge typed `SHAPE_TYPE_EDGE` with optional ghost vertices);
|
|
1760
|
+
// `create_shape` and `shape.set_shape` are registered only by
|
|
1761
|
+
// v3/script_box2d_*_v3.cpp and take B2D_V3_SHAPE_TS (no chain, and a segment
|
|
1762
|
+
// that reads no ghost vertices). `world.cast_shape` and `world.overlap_shape`
|
|
1763
|
+
// are registered by both, so they keep the full union.
|
|
1764
|
+
// The box arm leads: a v2 fixture keeps its shape type, and the runtime probe
|
|
1765
|
+
// witnesses a union by its first arm against a box collision shape.
|
|
1766
|
+
const B2D_SHAPE = "typeof b2d.shape";
|
|
1767
|
+
const B2D_BOX_ARM = `{ type: ${B2D_SHAPE}.SHAPE_TYPE_POLYGON | ${B2D_SHAPE}.SHAPE_TYPE_BOX; hx: number; hy: number; center?: Vector3; angle?: number }`;
|
|
1768
|
+
const B2D_POLYGON_ARM = `{ type: ${B2D_SHAPE}.SHAPE_TYPE_POLYGON | ${B2D_SHAPE}.SHAPE_TYPE_BOX; vertices: Vector3[] }`;
|
|
1769
|
+
const B2D_CIRCLE_ARM = `{ type: ${B2D_SHAPE}.SHAPE_TYPE_CIRCLE; radius: number; center?: Vector3 }`;
|
|
1770
|
+
const B2D_CAPSULE_ARM = `{ type: ${B2D_SHAPE}.SHAPE_TYPE_CAPSULE; radius: number; center1: Vector3; center2: Vector3 }`;
|
|
1771
|
+
const B2D_EDGE_ARM = `{ type: ${B2D_SHAPE}.SHAPE_TYPE_EDGE | ${B2D_SHAPE}.SHAPE_TYPE_SEGMENT; v1: Vector3; v2: Vector3; v0?: Vector3; v3?: Vector3 }`;
|
|
1772
|
+
const B2D_V2_EDGE_ARM = `{ type: ${B2D_SHAPE}.SHAPE_TYPE_EDGE; v1: Vector3; v2: Vector3; v0?: Vector3; v3?: Vector3 }`;
|
|
1773
|
+
const B2D_V3_SEGMENT_ARM = `{ type: ${B2D_SHAPE}.SHAPE_TYPE_EDGE | ${B2D_SHAPE}.SHAPE_TYPE_SEGMENT; v1: Vector3; v2: Vector3 }`;
|
|
1774
|
+
const B2D_CHAIN_ARM = `{ type: ${B2D_SHAPE}.SHAPE_TYPE_CHAIN; vertices: Vector3[]; loop?: boolean; prev_vertex?: Vector3; next_vertex?: Vector3 }`;
|
|
1775
|
+
const B2D_SHAPE_DEFINITION_TS = [
|
|
1776
|
+
B2D_BOX_ARM,
|
|
1777
|
+
B2D_POLYGON_ARM,
|
|
1778
|
+
B2D_CIRCLE_ARM,
|
|
1779
|
+
B2D_CAPSULE_ARM,
|
|
1780
|
+
B2D_EDGE_ARM,
|
|
1781
|
+
B2D_CHAIN_ARM,
|
|
1782
|
+
].join(" | ");
|
|
1783
|
+
const B2D_V2_SHAPE_TS = [
|
|
1784
|
+
B2D_BOX_ARM,
|
|
1785
|
+
B2D_POLYGON_ARM,
|
|
1786
|
+
B2D_CIRCLE_ARM,
|
|
1787
|
+
B2D_V2_EDGE_ARM,
|
|
1788
|
+
B2D_CHAIN_ARM,
|
|
1789
|
+
].join(" | ");
|
|
1790
|
+
const B2D_V3_SHAPE_TS = [
|
|
1791
|
+
B2D_BOX_ARM,
|
|
1792
|
+
B2D_POLYGON_ARM,
|
|
1793
|
+
B2D_CIRCLE_ARM,
|
|
1794
|
+
B2D_CAPSULE_ARM,
|
|
1795
|
+
B2D_V3_SEGMENT_ARM,
|
|
1796
|
+
].join(" | ");
|
|
1797
|
+
const B2D_SHAPE_DEFINITION: TableSlotCuration = { kind: "verbatim", ts: B2D_SHAPE_DEFINITION_TS };
|
|
1258
1798
|
// A fixture info entry (index/type/sensor/density/friction/restitution/child_count).
|
|
1259
1799
|
const B2D_FIXTURE_INFO_FIELDS: readonly TableField[] = [
|
|
1260
1800
|
{ name: "index", types: ["number"] },
|
|
@@ -1330,8 +1870,38 @@ const PHYSICS_SHAPE_TABLE_FIELDS: readonly TableField[] = [
|
|
|
1330
1870
|
{ name: "dimensions", types: ["vector3"], optional: true },
|
|
1331
1871
|
{ name: "height", types: ["number"], optional: true },
|
|
1332
1872
|
];
|
|
1333
|
-
|
|
1334
|
-
|
|
1873
|
+
// The same record as `physics.set_shape` takes it: script_physics.cpp:
|
|
1874
|
+
// Physics_SetShape reads `type` and then each field its kind needs with
|
|
1875
|
+
// luaL_checknumber or CheckVector3, so each kind is its own arm.
|
|
1876
|
+
const PHYSICS_SHAPE_DEFINITION_TS = [
|
|
1877
|
+
"{ type: typeof physics.SHAPE_TYPE_SPHERE; diameter: number }",
|
|
1878
|
+
"{ type: typeof physics.SHAPE_TYPE_BOX; dimensions: Vector3 }",
|
|
1879
|
+
"{ type: typeof physics.SHAPE_TYPE_CAPSULE; diameter: number; height: number }",
|
|
1880
|
+
].join(" | ");
|
|
1881
|
+
|
|
1882
|
+
export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new Map<
|
|
1883
|
+
string,
|
|
1884
|
+
TableSlotCuration
|
|
1885
|
+
>([
|
|
1886
|
+
// gui_script.cpp:LuaGet and LuaSet read the `key` and `keys` options. On a
|
|
1887
|
+
// prose `table` slot TABLE_SLOT_FIELD_ADDITIONS supplies them; 1.13.2 types the
|
|
1888
|
+
// slots as an inline record and a struct that omit them.
|
|
1889
|
+
...(
|
|
1890
|
+
[
|
|
1891
|
+
["gui.get:param:options", "{ index?:integer }"],
|
|
1892
|
+
["gui.set:param:options", "gui.set_options"],
|
|
1893
|
+
] as const
|
|
1894
|
+
).map(
|
|
1895
|
+
([key, retyped]) =>
|
|
1896
|
+
[
|
|
1897
|
+
key,
|
|
1898
|
+
{
|
|
1899
|
+
kind: "verbatim",
|
|
1900
|
+
ts: "{ index?: number; key?: string | Hash; keys?: (Hash | string)[] }",
|
|
1901
|
+
retypedUpstream: [retyped],
|
|
1902
|
+
},
|
|
1903
|
+
] as const,
|
|
1904
|
+
),
|
|
1335
1905
|
// buffer.create's `declaration` is "a table where each entry (table) describes
|
|
1336
1906
|
// a stream", and upstream's example passes a list of stream records whose
|
|
1337
1907
|
// `type` is a buffer.VALUE_TYPE_* constant.
|
|
@@ -1351,7 +1921,12 @@ export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new
|
|
|
1351
1921
|
// the object-literal form this slot accepted as a bare `table`.
|
|
1352
1922
|
[
|
|
1353
1923
|
"collectionfactory.create:param:properties",
|
|
1354
|
-
{
|
|
1924
|
+
{
|
|
1925
|
+
kind: "mapping",
|
|
1926
|
+
key: "hash | string",
|
|
1927
|
+
value: "table",
|
|
1928
|
+
retypedUpstream: ["table<hash, table<string|hash, any>>"],
|
|
1929
|
+
},
|
|
1355
1930
|
],
|
|
1356
1931
|
["collectionfactory.create:return:ids", { kind: "mapping", key: "hash", value: "hash" }],
|
|
1357
1932
|
// font.get_info's `info` return is a `<dl>` with `path: hash` and a nested
|
|
@@ -1482,8 +2057,22 @@ export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new
|
|
|
1482
2057
|
],
|
|
1483
2058
|
["socket.select:param:recvt", { kind: "array", element: SOCKET_HANDLE_TOKENS }],
|
|
1484
2059
|
["socket.select:param:sendt", { kind: "array", element: SOCKET_HANDLE_TOKENS }],
|
|
1485
|
-
[
|
|
1486
|
-
|
|
2060
|
+
[
|
|
2061
|
+
"socket.select:return:sockets_r",
|
|
2062
|
+
{
|
|
2063
|
+
kind: "array",
|
|
2064
|
+
element: SOCKET_HANDLE_TOKENS,
|
|
2065
|
+
retypedUpstream: ["table<integer|socket_selectable, socket_selectable|integer>"],
|
|
2066
|
+
},
|
|
2067
|
+
],
|
|
2068
|
+
[
|
|
2069
|
+
"socket.select:return:sockets_w",
|
|
2070
|
+
{
|
|
2071
|
+
kind: "array",
|
|
2072
|
+
element: SOCKET_HANDLE_TOKENS,
|
|
2073
|
+
retypedUpstream: ["table<integer|socket_selectable, socket_selectable|integer>"],
|
|
2074
|
+
},
|
|
2075
|
+
],
|
|
1487
2076
|
[
|
|
1488
2077
|
"tilemap.get_tile_info:return:tile_info",
|
|
1489
2078
|
{
|
|
@@ -1575,8 +2164,10 @@ export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new
|
|
|
1575
2164
|
// `headers`; the sibling `options` table stays parser-recovered.
|
|
1576
2165
|
["http.request:param:headers", { kind: "mapping", key: "string", value: "string" }],
|
|
1577
2166
|
// collectionproxy.get_resources returns "the resources, or an empty list", a
|
|
1578
|
-
// homogeneous list of resource
|
|
1579
|
-
|
|
2167
|
+
// homogeneous list of resource paths (prose only, no field list), each a
|
|
2168
|
+
// hexadecimal string: script_collectionproxy.cpp:GetResourceHashCallback pushes
|
|
2169
|
+
// every entry with lua_pushlstring.
|
|
2170
|
+
["collectionproxy.get_resources:return:resources", { kind: "array", element: "string" }],
|
|
1580
2171
|
// render.predicate's `tags` is "table of tags ... can be of either hash or
|
|
1581
2172
|
// string type", a homogeneous array whose element is the string|hash union.
|
|
1582
2173
|
["render.predicate:param:tags", { kind: "array", element: ["string", "hash"] }],
|
|
@@ -1589,6 +2180,7 @@ export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new
|
|
|
1589
2180
|
[
|
|
1590
2181
|
"render.clear:param:buffers",
|
|
1591
2182
|
{
|
|
2183
|
+
retypedUpstream: ["table<graphics.BUFFER_TYPE, number|vector4>"],
|
|
1592
2184
|
kind: "mapping",
|
|
1593
2185
|
key: "graphics.BUFFER_TYPE_COLOR0_BIT | graphics.BUFFER_TYPE_DEPTH_BIT | graphics.BUFFER_TYPE_STENCIL_BIT",
|
|
1594
2186
|
value: "number | vector4",
|
|
@@ -1780,14 +2372,63 @@ export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new
|
|
|
1780
2372
|
[
|
|
1781
2373
|
"b2d.body.create_fixture:param:definition",
|
|
1782
2374
|
{
|
|
2375
|
+
retypedUpstream: ["b2d.fixture_definition"],
|
|
1783
2376
|
kind: "object",
|
|
1784
2377
|
fields: [
|
|
1785
|
-
{ name: "shape", types: ["table"],
|
|
2378
|
+
{ name: "shape", types: ["table"], tsType: B2D_V2_SHAPE_TS },
|
|
1786
2379
|
{ name: "friction", types: ["number"] },
|
|
1787
2380
|
{ name: "restitution", types: ["number"] },
|
|
1788
2381
|
{ name: "density", types: ["number"] },
|
|
1789
2382
|
{ name: "sensor", types: ["boolean"] },
|
|
2383
|
+
// v2/script_box2d_fixture_v2.cpp:CheckFixtureDef reads is_sensor when
|
|
2384
|
+
// sensor is absent.
|
|
2385
|
+
{ name: "is_sensor", types: ["boolean"] },
|
|
2386
|
+
{ name: "filter", types: ["table"], fields: [...B2D_FILTER_DATA_FIELDS] },
|
|
2387
|
+
],
|
|
2388
|
+
},
|
|
2389
|
+
],
|
|
2390
|
+
// The creation-time definitions the v3 bindings document in their C++ doc
|
|
2391
|
+
// comments rather than the ref-doc: Body_CreateChain
|
|
2392
|
+
// (v3/script_box2d_chain_v3.cpp), CheckShapeCreateDef
|
|
2393
|
+
// (v3/script_box2d_shape_v3.cpp) and World_Explode
|
|
2394
|
+
// (v3/script_box2d_world_v3.cpp).
|
|
2395
|
+
[
|
|
2396
|
+
"b2d.body.create_chain:param:definition",
|
|
2397
|
+
{
|
|
2398
|
+
kind: "object",
|
|
2399
|
+
fields: [
|
|
2400
|
+
{ name: "vertices", types: ["table"], tsType: "Vector3[]" },
|
|
2401
|
+
{ name: "loop", types: ["boolean"] },
|
|
2402
|
+
{ name: "prev_vertex", types: ["vector3"] },
|
|
2403
|
+
{ name: "next_vertex", types: ["vector3"] },
|
|
2404
|
+
{ name: "friction", types: ["number"] },
|
|
2405
|
+
{ name: "restitution", types: ["number"] },
|
|
2406
|
+
{ name: "material", types: ["number"] },
|
|
1790
2407
|
{ name: "filter", types: ["table"], fields: [...B2D_FILTER_DATA_FIELDS] },
|
|
2408
|
+
{ name: "enable_sensor_events", types: ["boolean"] },
|
|
2409
|
+
],
|
|
2410
|
+
},
|
|
2411
|
+
],
|
|
2412
|
+
// The shape table sits in `definition.shape` or is the definition itself, and
|
|
2413
|
+
// a filter, when given, needs all three members CheckFilterData reads.
|
|
2414
|
+
[
|
|
2415
|
+
"b2d.body.create_shape:param:definition",
|
|
2416
|
+
{
|
|
2417
|
+
retypedUpstream: ["b2d.shape_create_definition"],
|
|
2418
|
+
kind: "verbatim",
|
|
2419
|
+
ts: `({ shape: ${B2D_V3_SHAPE_TS} } | ${B2D_V3_SHAPE_TS}) & { density?: number; friction?: number; restitution?: number; material?: number; sensor?: boolean; is_sensor?: boolean; filter?: { category_bits: number; mask_bits: number; group_index: number } }`,
|
|
2420
|
+
},
|
|
2421
|
+
],
|
|
2422
|
+
[
|
|
2423
|
+
"b2d.world.explode:param:definition",
|
|
2424
|
+
{
|
|
2425
|
+
kind: "object",
|
|
2426
|
+
fields: [
|
|
2427
|
+
{ name: "position", types: ["vector3"] },
|
|
2428
|
+
{ name: "radius", types: ["number"] },
|
|
2429
|
+
{ name: "falloff", types: ["number"] },
|
|
2430
|
+
{ name: "impulse_per_length", types: ["number"] },
|
|
2431
|
+
{ name: "mask_bits", types: ["number"] },
|
|
1791
2432
|
],
|
|
1792
2433
|
},
|
|
1793
2434
|
],
|
|
@@ -1825,10 +2466,16 @@ export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new
|
|
|
1825
2466
|
["b2d.fixture.get_filter_data:return:filter", { kind: "object", fields: B2D_FILTER_DATA_FIELDS }],
|
|
1826
2467
|
["b2d.fixture.set_filter_data:param:filter", { kind: "object", fields: B2D_FILTER_DATA_FIELDS }],
|
|
1827
2468
|
["b2d.fixture.get_shape:return:shape", { kind: "object", fields: B2D_SHAPE_TABLE_FIELDS }],
|
|
1828
|
-
[
|
|
2469
|
+
[
|
|
2470
|
+
"b2d.fixture.set_shape:param:shape",
|
|
2471
|
+
{ kind: "verbatim", ts: B2D_V2_SHAPE_TS, retypedUpstream: ["b2d.shape.definition"] },
|
|
2472
|
+
],
|
|
1829
2473
|
// b2d.shape
|
|
1830
2474
|
["b2d.shape.get_shape:return:shape", { kind: "object", fields: B2D_SHAPE_TABLE_FIELDS }],
|
|
1831
|
-
[
|
|
2475
|
+
[
|
|
2476
|
+
"b2d.shape.set_shape:param:definition",
|
|
2477
|
+
{ kind: "verbatim", ts: B2D_V3_SHAPE_TS, retypedUpstream: ["b2d.shape.definition"] },
|
|
2478
|
+
],
|
|
1832
2479
|
["b2d.shape.get_mass_data:return:data", { kind: "object", fields: B2D_MASS_DATA_FIELDS }],
|
|
1833
2480
|
[
|
|
1834
2481
|
"b2d.shape.get_sensor_overlaps:return:overlaps",
|
|
@@ -2011,7 +2658,10 @@ export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new
|
|
|
2011
2658
|
],
|
|
2012
2659
|
["b2d.world.overlap_aabb:return:hits", { kind: "array-object", fields: B2D_SHAPE_INFO_FIELDS }],
|
|
2013
2660
|
["b2d.world.overlap_aabb:return:stats", { kind: "object", fields: B2D_QUERY_STATS_FIELDS }],
|
|
2014
|
-
[
|
|
2661
|
+
[
|
|
2662
|
+
"b2d.world.overlap_shape:param:shape",
|
|
2663
|
+
{ ...B2D_SHAPE_DEFINITION, retypedUpstream: ["b2d.shape.definition"] },
|
|
2664
|
+
],
|
|
2015
2665
|
["b2d.world.overlap_shape:param:filter", { kind: "object", fields: B2D_QUERY_FILTER_FIELDS }],
|
|
2016
2666
|
[
|
|
2017
2667
|
"b2d.world.overlap_shape:return:fixtures",
|
|
@@ -2038,7 +2688,10 @@ export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new
|
|
|
2038
2688
|
],
|
|
2039
2689
|
},
|
|
2040
2690
|
],
|
|
2041
|
-
[
|
|
2691
|
+
[
|
|
2692
|
+
"b2d.world.cast_shape:param:shape",
|
|
2693
|
+
{ ...B2D_SHAPE_DEFINITION, retypedUpstream: ["b2d.shape.definition"] },
|
|
2694
|
+
],
|
|
2042
2695
|
["b2d.world.cast_shape:param:filter", { kind: "object", fields: B2D_QUERY_FILTER_FIELDS }],
|
|
2043
2696
|
["b2d.world.cast_shape:return:hits", { kind: "array-object", fields: B2D_CAST_HIT_FIELDS }],
|
|
2044
2697
|
["b2d.world.cast_shape:return:stats", { kind: "object", fields: B2D_QUERY_STATS_FIELDS }],
|
|
@@ -2047,14 +2700,15 @@ export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new
|
|
|
2047
2700
|
["b2d.world.collide_mover:param:capsule", { kind: "object", fields: B2D_CAPSULE_FIELDS }],
|
|
2048
2701
|
["b2d.world.collide_mover:param:filter", { kind: "object", fields: B2D_MOVER_FILTER_FIELDS }],
|
|
2049
2702
|
["physics.get_shape:return:table", { kind: "object", fields: PHYSICS_SHAPE_TABLE_FIELDS }],
|
|
2050
|
-
["physics.set_shape:param:table", { kind: "
|
|
2703
|
+
["physics.set_shape:param:table", { kind: "verbatim", ts: PHYSICS_SHAPE_DEFINITION_TS }],
|
|
2051
2704
|
]);
|
|
2052
2705
|
|
|
2053
2706
|
// Slot-keyed (`element:param:name`, mirroring TABLE_SLOT_CURATIONS) replacements
|
|
2054
2707
|
// for a callback parameter's recovered signature, used where the generic
|
|
2055
2708
|
// `recoverCallbackSignature` `unknown`-everywhere form leaves real engine type
|
|
2056
2709
|
// information on the table. The value is the full emitted function type. Honest
|
|
2057
|
-
// only: `window.set_listener` discriminates `event` by
|
|
2710
|
+
// only: `window.set_listener` discriminates `event` by the union of the
|
|
2711
|
+
// `WINDOW_EVENT_*` constants the surface's own `window` namespace declares, and
|
|
2058
2712
|
// its `data` is a bare record (only resize carries fields — `isWindowEvent` is the
|
|
2059
2713
|
// path to typed `data`), mirroring the `msg.post` (send, typed) / `isMessage`
|
|
2060
2714
|
// (receive, narrow) split.
|
|
@@ -2071,7 +2725,8 @@ export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new
|
|
|
2071
2725
|
export const CALLBACK_SIGNATURE_CURATIONS: ReadonlyMap<string, string> = new Map([
|
|
2072
2726
|
[
|
|
2073
2727
|
"window.set_listener:param:callback",
|
|
2074
|
-
|
|
2728
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: emitted verbatim as a TS template literal type
|
|
2729
|
+
"(self: unknown, event: (typeof window)[Extract<keyof typeof window, `WINDOW_EVENT_${string}`>], data: Record<string | number, unknown>) => void",
|
|
2075
2730
|
],
|
|
2076
2731
|
[
|
|
2077
2732
|
"http.request:param:callback",
|
|
@@ -2096,9 +2751,11 @@ export const CALLBACK_SIGNATURE_CURATIONS: ReadonlyMap<string, string> = new Map
|
|
|
2096
2751
|
// hand-curated. Each geometry is the triangle data (the create-input form
|
|
2097
2752
|
// get_atlas mirrors via its cross-ref), and `vertices`/`uvs`/`indices` are
|
|
2098
2753
|
// brace-form number-lists, hence `numberList: true` (emitted `number[]`).
|
|
2099
|
-
//
|
|
2100
|
-
//
|
|
2101
|
-
//
|
|
2754
|
+
// resource.create_atlas documents a geometry's `id`/`width`/`height`/pivot
|
|
2755
|
+
// members, so the three arrays join those: script_resource.cpp reads them from
|
|
2756
|
+
// each `geometries` entry. Injected onto the already-parsed field list (every
|
|
2757
|
+
// sibling stays parser-authoritative), so this is a nested-field curation, not a
|
|
2758
|
+
// whole-slot object curation.
|
|
2102
2759
|
const ATLAS_GEOMETRY_MEMBERS: readonly TableField[] = [
|
|
2103
2760
|
{ name: "vertices", types: ["table"], numberList: true },
|
|
2104
2761
|
{ name: "uvs", types: ["table"], numberList: true },
|
|
@@ -2107,6 +2764,7 @@ const ATLAS_GEOMETRY_MEMBERS: readonly TableField[] = [
|
|
|
2107
2764
|
|
|
2108
2765
|
export const NESTED_FIELD_CURATIONS: ReadonlyMap<string, readonly TableField[]> = new Map([
|
|
2109
2766
|
["resource.set_atlas:param:table:geometries", ATLAS_GEOMETRY_MEMBERS],
|
|
2767
|
+
["resource.create_atlas:param:table:geometries", ATLAS_GEOMETRY_MEMBERS],
|
|
2110
2768
|
["resource.get_atlas:return:data:geometries", ATLAS_GEOMETRY_MEMBERS],
|
|
2111
2769
|
]);
|
|
2112
2770
|
|
|
@@ -2130,6 +2788,9 @@ export const NESTED_FIELD_CURATIONS: ReadonlyMap<string, readonly TableField[]>
|
|
|
2130
2788
|
// `render.set_render_target`'s `options.transient` is documented as `table`
|
|
2131
2789
|
// with the prose "a list of buffer types", and upstream's example passes
|
|
2132
2790
|
// graphics.BUFFER_TYPE_* constants.
|
|
2791
|
+
//
|
|
2792
|
+
// `resource.create_sound_data`'s `options.data` is documented as `string`, while
|
|
2793
|
+
// script_resource.cpp:CreateSoundData reads it with CheckBufferOrString.
|
|
2133
2794
|
const CONSTANT_VALUE_TS = "Vector4 | Vector3 | Matrix4 | number | (Vector4 | Matrix4)[]";
|
|
2134
2795
|
const ATTRIBUTE_VALUE_TS = "Vector4 | Vector3 | Matrix4 | number | number[]";
|
|
2135
2796
|
export const TABLE_FIELD_TYPE_OVERRIDES: ReadonlyMap<string, string> = new Map([
|
|
@@ -2142,6 +2803,23 @@ export const TABLE_FIELD_TYPE_OVERRIDES: ReadonlyMap<string, string> = new Map([
|
|
|
2142
2803
|
["resource.get_atlas:return:data:animations[].playback", "go.Playback"],
|
|
2143
2804
|
["gui.set:param:options:key", "string | Hash"],
|
|
2144
2805
|
["render.set_render_target:param:options:transient", "graphics.BufferType[]"],
|
|
2806
|
+
["resource.create_sound_data:param:options:data", 'string | Opaque<"buffer">'],
|
|
2807
|
+
// The same two slots where 1.13.2 types the option bag as a STRUCT, whose
|
|
2808
|
+
// members still carry the narrower upstream types.
|
|
2809
|
+
["gui.set_options:member:key", "string | Hash"],
|
|
2810
|
+
["resource.sound_data_options:member:data", 'string | Opaque<"buffer">'],
|
|
2811
|
+
// Bitmasks typed as their flag enum: the create_texture example passes
|
|
2812
|
+
// TEXTURE_USAGE_FLAG_STORAGE + TEXTURE_USAGE_FLAG_SAMPLE, a sum no single member
|
|
2813
|
+
// names, and a reported value combines them the same way.
|
|
2814
|
+
...(
|
|
2815
|
+
[
|
|
2816
|
+
"resource.texture_creation_params",
|
|
2817
|
+
"resource.texture_info",
|
|
2818
|
+
"resource.render_target_attachment_info",
|
|
2819
|
+
"material.texture_info",
|
|
2820
|
+
] as const
|
|
2821
|
+
).map((struct) => [`${struct}:member:flags`, "graphics.TEXTURE_USAGE_FLAG | number"] as const),
|
|
2822
|
+
["render.render_target_buffer_params:member:flags", "render.RENDER_TARGET_FLAG | number"],
|
|
2145
2823
|
]);
|
|
2146
2824
|
|
|
2147
2825
|
// A field the engine accepts on an option bag whose `<dl>` upstream otherwise
|
|
@@ -2150,27 +2828,56 @@ export const TABLE_FIELD_TYPE_OVERRIDES: ReadonlyMap<string, string> = new Map([
|
|
|
2150
2828
|
// upstream omits, leaving the documented fields parser-authoritative so an
|
|
2151
2829
|
// upstream edit to any of them lands without a hand-maintained restatement.
|
|
2152
2830
|
//
|
|
2153
|
-
// `evidence` pins where upstream itself shows
|
|
2154
|
-
// throws once the parse recovers
|
|
2155
|
-
// here and the entry is deleted rather than
|
|
2831
|
+
// `evidence` pins where upstream itself shows each field, or the engine binding
|
|
2832
|
+
// that reads it. applyFieldAdditions throws once the parse recovers one, so
|
|
2833
|
+
// upstream documenting a field reds here and the entry is deleted rather than
|
|
2834
|
+
// re-pinned.
|
|
2156
2835
|
//
|
|
2157
2836
|
// Keyed `<element>:<kind>:<slot>`, the TABLE_SLOT_CURATIONS shape.
|
|
2158
2837
|
export interface TableFieldAddition {
|
|
2159
|
-
readonly
|
|
2838
|
+
readonly fields: readonly TableField[];
|
|
2160
2839
|
readonly evidence: readonly string[];
|
|
2161
2840
|
}
|
|
2162
2841
|
|
|
2163
|
-
|
|
2842
|
+
const PROPERTY_OPTION_KEYS: TableField = {
|
|
2843
|
+
name: "keys",
|
|
2844
|
+
types: ["table"],
|
|
2845
|
+
optional: true,
|
|
2846
|
+
tsType: "(Hash | string)[]",
|
|
2847
|
+
};
|
|
2848
|
+
|
|
2849
|
+
export const TABLE_SLOT_FIELD_ADDITIONS: ReadonlyMap<string, TableFieldAddition> = new Map<
|
|
2850
|
+
string,
|
|
2851
|
+
TableFieldAddition
|
|
2852
|
+
>([
|
|
2164
2853
|
[
|
|
2165
2854
|
"resource.create_texture:param:table",
|
|
2166
2855
|
{
|
|
2167
|
-
|
|
2856
|
+
fields: [{ name: "page_count", types: ["number"], optional: true }],
|
|
2168
2857
|
evidence: [
|
|
2169
2858
|
"the array-texture example on this element creates with `page_count = 5`",
|
|
2170
2859
|
"resource.get_texture_info documents `page_count` as the texture array's page count",
|
|
2171
2860
|
],
|
|
2172
2861
|
},
|
|
2173
2862
|
],
|
|
2863
|
+
[
|
|
2864
|
+
"gui.get:param:options",
|
|
2865
|
+
{
|
|
2866
|
+
fields: [{ name: "key", types: ["string", "hash"], optional: true }, PROPERTY_OPTION_KEYS],
|
|
2867
|
+
evidence: [
|
|
2868
|
+
"gui_script.cpp:LuaGet reads its options through dmGameObject::LuaToPropertyOptions, which reads index, key and keys",
|
|
2869
|
+
],
|
|
2870
|
+
},
|
|
2871
|
+
],
|
|
2872
|
+
[
|
|
2873
|
+
"gui.set:param:options",
|
|
2874
|
+
{
|
|
2875
|
+
fields: [PROPERTY_OPTION_KEYS],
|
|
2876
|
+
evidence: [
|
|
2877
|
+
"gui_script.cpp:LuaSet reads its options through dmGameObject::LuaToPropertyOptions, which reads index, key and keys",
|
|
2878
|
+
],
|
|
2879
|
+
},
|
|
2880
|
+
],
|
|
2174
2881
|
]);
|
|
2175
2882
|
|
|
2176
2883
|
// A return-slot field upstream documents as platform-gated or nil-valued while
|
|
@@ -2262,6 +2969,247 @@ export function applyFieldOptionalityCorrections(
|
|
|
2262
2969
|
return corrected;
|
|
2263
2970
|
}
|
|
2264
2971
|
|
|
2972
|
+
// A param-side table field the engine binding refuses to go without. A `<dl>`
|
|
2973
|
+
// carries no per-field optionality, so every field of an input option bag is
|
|
2974
|
+
// emitted `?`; an entry here keeps one field required. Keyed
|
|
2975
|
+
// `<element>:param:<slot>:<field>`, the TABLE_FIELD_TYPE_OVERRIDES shape, with
|
|
2976
|
+
// `<field>.<member>` or `<field>[].<member>` one level down. A `STRUCT` member
|
|
2977
|
+
// is keyed `<struct>:member:<field>`, for a target whose slot names that struct
|
|
2978
|
+
// (`field-correction-coverage.ts` pairs the two). The value is the evidence: the
|
|
2979
|
+
// C++ binding, `<file>:<cFunction>`, and how it reads the field.
|
|
2980
|
+
const CHECK_SHAPE_DEF = "CheckShapeDef reads the shape table with luaL_checkinteger(type)";
|
|
2981
|
+
const V2_FILTER_DATA =
|
|
2982
|
+
"v2/script_box2d_fixture_v2.cpp:CheckFilterData reads each with luaL_checkinteger";
|
|
2983
|
+
const TEXTURE_PARAMS =
|
|
2984
|
+
"script_resource.cpp:CheckCreateTextureResourceParams reads CheckTableInteger";
|
|
2985
|
+
const ATLAS_ARGUMENTS =
|
|
2986
|
+
"script_resource.cpp:CheckAtlasArguments raises unless the atlas has at least one geometry and one animation";
|
|
2987
|
+
|
|
2988
|
+
const filterDataFields = (slot: string, evidence: string) =>
|
|
2989
|
+
(["category_bits", "mask_bits", "group_index"] as const).map(
|
|
2990
|
+
(field) => [`${slot}:filter.${field}`, evidence] as const,
|
|
2991
|
+
);
|
|
2992
|
+
|
|
2993
|
+
// A `STRUCT` member upstream marks required while the engine binding reads it
|
|
2994
|
+
// only when present, keyed `<struct>:member:<field>` like REQUIRED_FIELD_CORRECTIONS
|
|
2995
|
+
// and valued with the same binding evidence.
|
|
2996
|
+
export const OPTIONAL_MEMBER_CORRECTIONS: ReadonlyMap<string, string> = new Map([
|
|
2997
|
+
[
|
|
2998
|
+
"sound.stop_properties:member:play_id",
|
|
2999
|
+
"script_sound.cpp:Sound_Stop reads play_id with luaL_checknumber only when it is not nil",
|
|
3000
|
+
],
|
|
3001
|
+
]);
|
|
3002
|
+
|
|
3003
|
+
export const REQUIRED_FIELD_CORRECTIONS: ReadonlyMap<string, string> = new Map<string, string>([
|
|
3004
|
+
...(["mass", "center", "inertia"] as const).map(
|
|
3005
|
+
(field) =>
|
|
3006
|
+
[
|
|
3007
|
+
`b2d.body.set_mass_data:param:data:${field}`,
|
|
3008
|
+
"script_box2d_body_v2/v3.cpp:CheckMassData reads mass and inertia with luaL_checknumber and center with CheckVec2",
|
|
3009
|
+
] as const,
|
|
3010
|
+
),
|
|
3011
|
+
...(["category_bits", "mask_bits", "group_index"] as const).map(
|
|
3012
|
+
(field) => [`b2d.fixture.set_filter_data:param:filter:${field}`, V2_FILTER_DATA] as const,
|
|
3013
|
+
),
|
|
3014
|
+
[
|
|
3015
|
+
"b2d.body.create_fixture:param:definition:shape",
|
|
3016
|
+
`v2/script_box2d_fixture_v2.cpp:CheckFixtureDef passes definition.shape to ${CHECK_SHAPE_DEF}`,
|
|
3017
|
+
],
|
|
3018
|
+
...filterDataFields("b2d.body.create_fixture:param:definition", V2_FILTER_DATA),
|
|
3019
|
+
[
|
|
3020
|
+
"b2d.body.create_chain:param:definition:vertices",
|
|
3021
|
+
"v3/script_box2d_chain_v3.cpp:Body_CreateChain reads vertices with CheckVerticesTable, at least 2 (4 for a loop)",
|
|
3022
|
+
],
|
|
3023
|
+
...(["cast_mover", "collide_mover"] as const).flatMap((name) =>
|
|
3024
|
+
(["center1", "center2", "radius"] as const).map(
|
|
3025
|
+
(field) =>
|
|
3026
|
+
[
|
|
3027
|
+
`b2d.world.${name}:param:capsule:${field}`,
|
|
3028
|
+
"v3/script_box2d_world_v3.cpp:CheckCapsule reads center1 and center2 with CheckVec2 and radius with luaL_checknumber",
|
|
3029
|
+
] as const,
|
|
3030
|
+
),
|
|
3031
|
+
),
|
|
3032
|
+
...(["lower", "upper"] as const).map(
|
|
3033
|
+
(field) =>
|
|
3034
|
+
[
|
|
3035
|
+
`b2d.world.overlap_aabb:param:aabb:${field}`,
|
|
3036
|
+
"script_box2d_world_v2/v3.cpp:CheckAABB reads lower and upper with CheckVec2",
|
|
3037
|
+
] as const,
|
|
3038
|
+
),
|
|
3039
|
+
...(["position", "radius", "falloff", "impulse_per_length"] as const).map(
|
|
3040
|
+
(field) =>
|
|
3041
|
+
[
|
|
3042
|
+
`b2d.world.explode:param:definition:${field}`,
|
|
3043
|
+
"v3/script_box2d_world_v3.cpp:World_Explode reads position with CheckVec2 and the rest with luaL_checknumber",
|
|
3044
|
+
] as const,
|
|
3045
|
+
),
|
|
3046
|
+
[
|
|
3047
|
+
"buffer.create:param:declaration:type",
|
|
3048
|
+
"script_buffer.cpp:ParseStreamDeclaration raises on a stream with no value type",
|
|
3049
|
+
],
|
|
3050
|
+
...(["create_texture", "create_texture_async"] as const).flatMap((name) =>
|
|
3051
|
+
(["type", "format", "width", "height"] as const).map(
|
|
3052
|
+
(field) => [`resource.${name}:param:table:${field}`, TEXTURE_PARAMS] as const,
|
|
3053
|
+
),
|
|
3054
|
+
),
|
|
3055
|
+
...(["type", "format", "width", "height"] as const).map(
|
|
3056
|
+
(field) =>
|
|
3057
|
+
[
|
|
3058
|
+
`resource.set_texture:param:table:${field}`,
|
|
3059
|
+
"script_resource.cpp:SetTexture reads CheckTableInteger",
|
|
3060
|
+
] as const,
|
|
3061
|
+
),
|
|
3062
|
+
...(["create_atlas", "set_atlas"] as const).flatMap((name) => [
|
|
3063
|
+
[
|
|
3064
|
+
`resource.${name}:param:table:texture`,
|
|
3065
|
+
"script_resource.cpp:CheckTextureResource reads the texture with CheckHashOrString",
|
|
3066
|
+
] as const,
|
|
3067
|
+
[`resource.${name}:param:table:geometries`, ATLAS_ARGUMENTS] as const,
|
|
3068
|
+
[`resource.${name}:param:table:animations`, ATLAS_ARGUMENTS] as const,
|
|
3069
|
+
...(["vertices", "uvs", "indices"] as const).map(
|
|
3070
|
+
(field) =>
|
|
3071
|
+
[
|
|
3072
|
+
`resource.${name}:param:table:geometries[].${field}`,
|
|
3073
|
+
"script_resource.cpp:CheckAtlasArguments checks each geometry stream with luaL_checktype(LUA_TTABLE)",
|
|
3074
|
+
] as const,
|
|
3075
|
+
),
|
|
3076
|
+
...(["id", "width", "height"] as const).map(
|
|
3077
|
+
(field) =>
|
|
3078
|
+
[
|
|
3079
|
+
`resource.${name}:param:table:animations[].${field}`,
|
|
3080
|
+
"script_resource.cpp:CheckAtlasArguments raises on an animation with no id, width or height",
|
|
3081
|
+
] as const,
|
|
3082
|
+
),
|
|
3083
|
+
]),
|
|
3084
|
+
[
|
|
3085
|
+
"resource.create_buffer:param:table:buffer",
|
|
3086
|
+
"script_resource.cpp:CreateBuffer reads the buffer with dmScript::CheckBuffer",
|
|
3087
|
+
],
|
|
3088
|
+
[
|
|
3089
|
+
"resource.create_sound_data:param:options:data",
|
|
3090
|
+
"script_resource.cpp:CreateSoundData reads the data with CheckBufferOrString",
|
|
3091
|
+
],
|
|
3092
|
+
[
|
|
3093
|
+
"resource.sound_data_options:member:data",
|
|
3094
|
+
"script_resource.cpp:CreateSoundData reads the data with CheckBufferOrString",
|
|
3095
|
+
],
|
|
3096
|
+
]);
|
|
3097
|
+
|
|
3098
|
+
// Mark the fields REQUIRED_FIELD_CORRECTIONS names for this slot required,
|
|
3099
|
+
// leaving every other field and every type untouched. An entry keyed to this
|
|
3100
|
+
// slot that names no field throws, so a renamed field cannot leave a stale
|
|
3101
|
+
// correction looking applied. Returns a new array; never mutates its input.
|
|
3102
|
+
export function applyRequiredFieldCorrections(
|
|
3103
|
+
elementName: string,
|
|
3104
|
+
slotKind: "param" | "return" | undefined,
|
|
3105
|
+
slotName: string | undefined,
|
|
3106
|
+
fields: readonly TableField[],
|
|
3107
|
+
): TableField[] {
|
|
3108
|
+
if (slotKind !== "param" || slotName === undefined) return [...fields];
|
|
3109
|
+
const prefix = `${tableSlotKey(elementName, slotKind, slotName)}:`;
|
|
3110
|
+
const applied = new Set<string>();
|
|
3111
|
+
const mark = (field: TableField, path: string): TableField => {
|
|
3112
|
+
if (!REQUIRED_FIELD_CORRECTIONS.has(`${prefix}${path}`)) return field;
|
|
3113
|
+
applied.add(path);
|
|
3114
|
+
return { ...field, required: true };
|
|
3115
|
+
};
|
|
3116
|
+
const corrected = fields.map((field) => {
|
|
3117
|
+
const own = mark(field, field.name);
|
|
3118
|
+
if (own.fields === undefined) return own;
|
|
3119
|
+
const parent = `${field.name}${field.isList === true ? "[]" : ""}`;
|
|
3120
|
+
return { ...own, fields: own.fields.map((child) => mark(child, `${parent}.${child.name}`)) };
|
|
3121
|
+
});
|
|
3122
|
+
const stale = [...REQUIRED_FIELD_CORRECTIONS.keys()].filter(
|
|
3123
|
+
(key) => key.startsWith(prefix) && !applied.has(key.slice(prefix.length)),
|
|
3124
|
+
);
|
|
3125
|
+
if (stale.length > 0) {
|
|
3126
|
+
throw new Error(
|
|
3127
|
+
`required-field correction names no recovered field: ${stale.sort().join(", ")}`,
|
|
3128
|
+
);
|
|
3129
|
+
}
|
|
3130
|
+
return corrected;
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
// A param-side list field the engine binding refuses empty. Every entry emits
|
|
3134
|
+
// the list as `[E, ...E[]]`; `replaces` drops members the binding reads only as
|
|
3135
|
+
// one of several alternatives, and `alternatives` restores them as a union the
|
|
3136
|
+
// element is intersected with. Keyed `<element>:param:<slot>:<field>` or
|
|
3137
|
+
// `<struct>:member:<field>`, the REQUIRED_FIELD_CORRECTIONS shapes.
|
|
3138
|
+
export interface ListFieldCorrection {
|
|
3139
|
+
readonly evidence: string;
|
|
3140
|
+
readonly replaces?: readonly string[];
|
|
3141
|
+
readonly alternatives?: string;
|
|
3142
|
+
}
|
|
3143
|
+
|
|
3144
|
+
const ATLAS_FRAMES =
|
|
3145
|
+
"script_resource.cpp:CheckAtlasArguments reads a non-empty frames table, else both frame_start and frame_end with CheckFieldValue<int>";
|
|
3146
|
+
|
|
3147
|
+
const ATLAS_GEOMETRIES: ListFieldCorrection = { evidence: ATLAS_ARGUMENTS };
|
|
3148
|
+
const ATLAS_ANIMATIONS: ListFieldCorrection = {
|
|
3149
|
+
evidence: `${ATLAS_ARGUMENTS}; ${ATLAS_FRAMES}`,
|
|
3150
|
+
replaces: ["frame_start", "frame_end"],
|
|
3151
|
+
alternatives: "{ frames: [number, ...number[]] } | { frame_start: number; frame_end: number }",
|
|
3152
|
+
};
|
|
3153
|
+
|
|
3154
|
+
export const LIST_FIELD_CORRECTIONS: ReadonlyMap<string, ListFieldCorrection> = new Map<
|
|
3155
|
+
string,
|
|
3156
|
+
ListFieldCorrection
|
|
3157
|
+
>([
|
|
3158
|
+
...(["create_atlas", "set_atlas"] as const).flatMap((name) => [
|
|
3159
|
+
[`resource.${name}:param:table:geometries`, ATLAS_GEOMETRIES] as const,
|
|
3160
|
+
[`resource.${name}:param:table:animations`, ATLAS_ANIMATIONS] as const,
|
|
3161
|
+
]),
|
|
3162
|
+
["resource.atlas:member:geometries", ATLAS_GEOMETRIES],
|
|
3163
|
+
["resource.atlas:member:animations", ATLAS_ANIMATIONS],
|
|
3164
|
+
]);
|
|
3165
|
+
|
|
3166
|
+
// Apply LIST_FIELD_CORRECTIONS to this slot's fields. An entry keyed to this
|
|
3167
|
+
// slot that names no field, names a field that is not a list, or replaces a
|
|
3168
|
+
// member the field does not carry throws, so a renamed field or member cannot
|
|
3169
|
+
// leave a stale correction looking applied. Returns a new array; never mutates
|
|
3170
|
+
// its input.
|
|
3171
|
+
export function applyListFieldCorrections(
|
|
3172
|
+
elementName: string,
|
|
3173
|
+
slotKind: "param" | "return" | undefined,
|
|
3174
|
+
slotName: string | undefined,
|
|
3175
|
+
fields: readonly TableField[],
|
|
3176
|
+
): TableField[] {
|
|
3177
|
+
if (slotKind !== "param" || slotName === undefined) return [...fields];
|
|
3178
|
+
const prefix = `${tableSlotKey(elementName, slotKind, slotName)}:`;
|
|
3179
|
+
const applied = new Set<string>();
|
|
3180
|
+
const corrected = fields.map((field): TableField => {
|
|
3181
|
+
const key = `${prefix}${field.name}`;
|
|
3182
|
+
const correction = LIST_FIELD_CORRECTIONS.get(key);
|
|
3183
|
+
if (correction === undefined) return field;
|
|
3184
|
+
applied.add(field.name);
|
|
3185
|
+
if (field.isList !== true) {
|
|
3186
|
+
throw new Error(`list-field correction names a field that is not a list: ${key}`);
|
|
3187
|
+
}
|
|
3188
|
+
const replaces = correction.replaces ?? [];
|
|
3189
|
+
const missing = replaces.filter(
|
|
3190
|
+
(name) => !(field.fields ?? []).some((member) => member.name === name),
|
|
3191
|
+
);
|
|
3192
|
+
if (missing.length > 0) {
|
|
3193
|
+
throw new Error(`list-field correction ${key} replaces no member: ${missing.join(", ")}`);
|
|
3194
|
+
}
|
|
3195
|
+
return {
|
|
3196
|
+
...field,
|
|
3197
|
+
nonEmpty: true,
|
|
3198
|
+
...(field.fields === undefined
|
|
3199
|
+
? {}
|
|
3200
|
+
: { fields: field.fields.filter((member) => !replaces.includes(member.name)) }),
|
|
3201
|
+
...(correction.alternatives === undefined ? {} : { alternatives: correction.alternatives }),
|
|
3202
|
+
};
|
|
3203
|
+
});
|
|
3204
|
+
const stale = [...LIST_FIELD_CORRECTIONS.keys()].filter(
|
|
3205
|
+
(key) => key.startsWith(prefix) && !applied.has(key.slice(prefix.length)),
|
|
3206
|
+
);
|
|
3207
|
+
if (stale.length > 0) {
|
|
3208
|
+
throw new Error(`list-field correction names no recovered field: ${stale.sort().join(", ")}`);
|
|
3209
|
+
}
|
|
3210
|
+
return corrected;
|
|
3211
|
+
}
|
|
3212
|
+
|
|
2265
3213
|
// Append the member TABLE_SLOT_FIELD_ADDITIONS supplies for this slot, after
|
|
2266
3214
|
// every field the parser recovered. Throws on an addition whose field the parse
|
|
2267
3215
|
// already produced: upstream has documented it, and the entry is stale.
|
|
@@ -2276,10 +3224,12 @@ export function applyFieldAdditions(
|
|
|
2276
3224
|
const key = tableSlotKey(elementName, slotKind, slotName);
|
|
2277
3225
|
const addition = TABLE_SLOT_FIELD_ADDITIONS.get(key);
|
|
2278
3226
|
if (addition === undefined) return [...fields];
|
|
2279
|
-
|
|
2280
|
-
|
|
3227
|
+
for (const added of addition.fields) {
|
|
3228
|
+
if (fields.some((field) => field.name === added.name)) {
|
|
3229
|
+
throw new Error(`field addition names a recovered field: ${key}:${added.name}`);
|
|
3230
|
+
}
|
|
2281
3231
|
}
|
|
2282
|
-
return [...fields, addition.
|
|
3232
|
+
return [...fields, ...addition.fields];
|
|
2283
3233
|
}
|
|
2284
3234
|
|
|
2285
3235
|
// Pin a parser-recovered field's TS type from TABLE_FIELD_TYPE_OVERRIDES,
|
|
@@ -2448,6 +3398,14 @@ export interface TableField {
|
|
|
2448
3398
|
isList?: boolean;
|
|
2449
3399
|
numberList?: boolean;
|
|
2450
3400
|
optional?: boolean;
|
|
3401
|
+
// Set only by REQUIRED_FIELD_CORRECTIONS: the engine raises without the field,
|
|
3402
|
+
// so it keeps no `?` even on a param-side option bag.
|
|
3403
|
+
required?: boolean;
|
|
3404
|
+
// Set only by LIST_FIELD_CORRECTIONS: the engine raises on an empty list, so
|
|
3405
|
+
// the list emits as `[E, ...E[]]`, and its element is intersected with the
|
|
3406
|
+
// alternative field sets the binding accepts.
|
|
3407
|
+
nonEmpty?: boolean;
|
|
3408
|
+
alternatives?: string;
|
|
2451
3409
|
// A hand-curated field whose faithful TS shape is not a plain token union
|
|
2452
3410
|
// (e.g. a nested vector array, or a documented value union with an array
|
|
2453
3411
|
// branch): inlineTableType emits this verbatim and the audit treats it as
|
|
@@ -2639,27 +3597,349 @@ function groupFlattenedTableFields(fields: readonly TableField[]): TableField[]
|
|
|
2639
3597
|
// A handle the type map widens (`constant_buffer` takes named constants) keeps
|
|
2640
3598
|
// that shape, so annotating with the alias loses nothing the handle-returning
|
|
2641
3599
|
// function yields.
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
3600
|
+
//
|
|
3601
|
+
// An engine typedef that names its aliased type: a `userdata`/`number` handle the
|
|
3602
|
+
// type map resolves (`vector`, `socket_client`) aliases that mapping, an
|
|
3603
|
+
// unresolved one keeps the brand, and any other aliased type (a union, literals, a
|
|
3604
|
+
// record) aliases the type itself.
|
|
3605
|
+
function typedefAlias(
|
|
3606
|
+
typedef: ApiTypedef,
|
|
3607
|
+
local: string,
|
|
3608
|
+
rawMapType: (token: string) => string,
|
|
3609
|
+
mapType: DefoldTypeMapper,
|
|
3610
|
+
): string {
|
|
3611
|
+
const brand = `Opaque<"${typedef.name}">`;
|
|
3612
|
+
const aliasOf = typedef.aliasOf ?? [];
|
|
3613
|
+
if (aliasOf.length > 0 && !isHandleAlias(aliasOf)) {
|
|
3614
|
+
return `type ${local} = ${unionFromTokens(aliasOf, mapType)};`;
|
|
3615
|
+
}
|
|
3616
|
+
const mapped = rawMapType(typedef.name);
|
|
3617
|
+
if (mapped.startsWith(brand)) return `type ${local} = ${mapped};`;
|
|
3618
|
+
const resolved = aliasOf.length > 0 && mapped !== "unknown" && mapped !== local;
|
|
3619
|
+
return `type ${local} = ${resolved ? mapped : brand};`;
|
|
3620
|
+
}
|
|
3621
|
+
|
|
3622
|
+
function isHandleAlias(aliasOf: readonly string[]): boolean {
|
|
3623
|
+
return aliasOf.length === 1 && (aliasOf[0] === "userdata" || aliasOf[0] === "number");
|
|
2646
3624
|
}
|
|
2647
3625
|
|
|
2648
|
-
|
|
3626
|
+
// A `STRUCT` or aliasing `TYPEDEF` placed at its local path (`shape_data`,
|
|
3627
|
+
// `on_input.action`) under the module that emits it.
|
|
3628
|
+
interface TypeDeclaration {
|
|
3629
|
+
name: string;
|
|
3630
|
+
path: string;
|
|
3631
|
+
// The declaration's lines at `indent`, its keyword line led by `decl`.
|
|
3632
|
+
lines: (indent: string, decl: string) => string[];
|
|
3633
|
+
signature: string;
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3636
|
+
// A typedef with no aliased type keeps the flat-name rule it always had; one that
|
|
3637
|
+
// names its aliased type, and every struct, sits at its name less the module
|
|
3638
|
+
// prefix, nested when that local is dotted. Typedefs come before structs, each in
|
|
3639
|
+
// document order.
|
|
3640
|
+
function collectTypeDeclarations(
|
|
3641
|
+
module: ApiModule,
|
|
3642
|
+
skip: ReadonlySet<string>,
|
|
3643
|
+
rawMapType: (token: string) => string,
|
|
3644
|
+
mapType: DefoldTypeMapper,
|
|
3645
|
+
): TypeDeclaration[] {
|
|
2649
3646
|
const prefix = `${module.namespace}.`;
|
|
3647
|
+
const out: TypeDeclaration[] = [];
|
|
3648
|
+
const localOf = (name: string): string | null => {
|
|
3649
|
+
const local = stripPrefix(name, prefix);
|
|
3650
|
+
return local.split(".").every((segment) => TS_IDENTIFIER.test(segment)) ? local : null;
|
|
3651
|
+
};
|
|
3652
|
+
for (const t of module.typedefs) {
|
|
3653
|
+
const local =
|
|
3654
|
+
t.aliasOf === undefined ? (TS_IDENTIFIER.test(t.name) ? t.name : null) : localOf(t.name);
|
|
3655
|
+
if (local === null || skip.has(local)) continue;
|
|
3656
|
+
const name = local.slice(local.lastIndexOf(".") + 1);
|
|
3657
|
+
const signature = typedefAlias(t, name, rawMapType, mapType);
|
|
3658
|
+
out.push({
|
|
3659
|
+
name,
|
|
3660
|
+
path: local,
|
|
3661
|
+
lines: (indent, decl) => [`${indent}${decl}${signature}`],
|
|
3662
|
+
signature,
|
|
3663
|
+
});
|
|
3664
|
+
}
|
|
3665
|
+
for (const struct of module.structs ?? []) {
|
|
3666
|
+
const local = localOf(struct.name);
|
|
3667
|
+
if (local === null || skip.has(local)) continue;
|
|
3668
|
+
out.push(structDeclaration(struct, local, mapType, module.structs ?? []));
|
|
3669
|
+
}
|
|
3670
|
+
return out;
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3673
|
+
export function memberCorrectionKey(struct: string, field: string): string {
|
|
3674
|
+
return `${struct}:member:${field}`;
|
|
3675
|
+
}
|
|
2650
3676
|
|
|
2651
|
-
|
|
3677
|
+
// A member-keyed LIST_FIELD_CORRECTIONS entry over a `T[]` member: `T` with the
|
|
3678
|
+
// replaced members omitted, rendered by the prose-field list renderer.
|
|
3679
|
+
function correctedListMemberType(
|
|
3680
|
+
struct: ApiStruct,
|
|
3681
|
+
member: ApiStructMember,
|
|
3682
|
+
correction: ListFieldCorrection,
|
|
3683
|
+
structs: readonly ApiStruct[],
|
|
3684
|
+
mapType: DefoldTypeMapper,
|
|
3685
|
+
): string {
|
|
3686
|
+
const key = memberCorrectionKey(struct.name, member.name);
|
|
3687
|
+
const element = member.type.endsWith("[]") ? member.type.slice(0, -2) : undefined;
|
|
3688
|
+
if (element === undefined || element.length === 0) {
|
|
3689
|
+
throw new Error(`list-field correction names a member that is not a list: ${key}`);
|
|
3690
|
+
}
|
|
3691
|
+
const replaces = correction.replaces ?? [];
|
|
3692
|
+
const elementStruct = structs.find((candidate) => candidate.name === element);
|
|
3693
|
+
const missing = replaces.filter(
|
|
3694
|
+
(name) => !(elementStruct?.members ?? []).some((candidate) => candidate.name === name),
|
|
3695
|
+
);
|
|
3696
|
+
if (missing.length > 0) {
|
|
3697
|
+
throw new Error(`list-field correction ${key} replaces no member: ${missing.join(", ")}`);
|
|
3698
|
+
}
|
|
3699
|
+
const mapped = mapType(element);
|
|
3700
|
+
const object =
|
|
3701
|
+
replaces.length === 0
|
|
3702
|
+
? mapped
|
|
3703
|
+
: `Omit<${mapped}, ${replaces.map((name) => JSON.stringify(name)).join(" | ")}>`;
|
|
3704
|
+
return nestedFieldType(
|
|
3705
|
+
{
|
|
3706
|
+
name: member.name,
|
|
3707
|
+
types: [],
|
|
3708
|
+
isList: true,
|
|
3709
|
+
nonEmpty: true,
|
|
3710
|
+
...(correction.alternatives === undefined ? {} : { alternatives: correction.alternatives }),
|
|
3711
|
+
},
|
|
3712
|
+
object,
|
|
3713
|
+
);
|
|
3714
|
+
}
|
|
3715
|
+
|
|
3716
|
+
function structDeclaration(
|
|
3717
|
+
struct: ApiStruct,
|
|
3718
|
+
path: string,
|
|
3719
|
+
mapType: DefoldTypeMapper,
|
|
3720
|
+
structs: readonly ApiStruct[],
|
|
3721
|
+
): TypeDeclaration {
|
|
3722
|
+
const name = path.slice(path.lastIndexOf(".") + 1);
|
|
3723
|
+
const memberPrefix = memberCorrectionKey(struct.name, "");
|
|
3724
|
+
const declared = new Set(struct.members.map((member) => member.name));
|
|
3725
|
+
const stale = [
|
|
3726
|
+
...REQUIRED_FIELD_CORRECTIONS.keys(),
|
|
3727
|
+
...LIST_FIELD_CORRECTIONS.keys(),
|
|
3728
|
+
...TABLE_FIELD_TYPE_OVERRIDES.keys(),
|
|
3729
|
+
...OPTIONAL_MEMBER_CORRECTIONS.keys(),
|
|
3730
|
+
].filter((key) => key.startsWith(memberPrefix) && !declared.has(key.slice(memberPrefix.length)));
|
|
3731
|
+
if (stale.length > 0) {
|
|
3732
|
+
throw new Error(`member-keyed field correction names no member: ${stale.sort().join(", ")}`);
|
|
3733
|
+
}
|
|
3734
|
+
const members = struct.members.map((member) => {
|
|
3735
|
+
const key = memberCorrectionKey(struct.name, member.name);
|
|
3736
|
+
const list = LIST_FIELD_CORRECTIONS.get(key);
|
|
3737
|
+
const retyped = TABLE_FIELD_TYPE_OVERRIDES.get(key);
|
|
3738
|
+
const optional =
|
|
3739
|
+
(member.isOptional || OPTIONAL_MEMBER_CORRECTIONS.has(key)) &&
|
|
3740
|
+
!REQUIRED_FIELD_CORRECTIONS.has(key);
|
|
3741
|
+
const type =
|
|
3742
|
+
list !== undefined
|
|
3743
|
+
? correctedListMemberType(struct, member, list, structs, mapType)
|
|
3744
|
+
: retyped !== undefined
|
|
3745
|
+
? retyped
|
|
3746
|
+
: member.type === ""
|
|
3747
|
+
? "unknown"
|
|
3748
|
+
: mapType(member.type);
|
|
3749
|
+
return {
|
|
3750
|
+
doc: member.doc,
|
|
3751
|
+
line: `${TS_IDENTIFIER.test(member.name) ? member.name : JSON.stringify(member.name)}${optional ? "?" : ""}: ${type};`,
|
|
3752
|
+
};
|
|
3753
|
+
});
|
|
3754
|
+
return {
|
|
3755
|
+
name,
|
|
3756
|
+
path,
|
|
3757
|
+
lines: (indent, decl) => {
|
|
3758
|
+
const memberIndent = `${indent}${INDENT}`;
|
|
3759
|
+
return [
|
|
3760
|
+
...summaryDocLines(struct.brief, struct.description, indent),
|
|
3761
|
+
`${indent}${decl}interface ${name} {`,
|
|
3762
|
+
...members.flatMap((member) => [
|
|
3763
|
+
...summaryDocLines(member.doc, "", memberIndent),
|
|
3764
|
+
`${memberIndent}${member.line}`,
|
|
3765
|
+
]),
|
|
3766
|
+
`${indent}}`,
|
|
3767
|
+
];
|
|
3768
|
+
},
|
|
3769
|
+
signature: `interface ${name} { ${members.map((member) => `${member.line} `).join("")}}`,
|
|
3770
|
+
};
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3773
|
+
// Nested declarations join the group at their path, which is created when no
|
|
3774
|
+
// member lives there; the flat ones are returned in name order.
|
|
3775
|
+
function placeTypeDeclarations(
|
|
3776
|
+
declarations: readonly TypeDeclaration[],
|
|
3777
|
+
nestedRoot: Map<string, NestedGroup>,
|
|
3778
|
+
): TypeDeclaration[] {
|
|
3779
|
+
const byName = (a: TypeDeclaration, b: TypeDeclaration) => a.name.localeCompare(b.name);
|
|
3780
|
+
const topLevel: TypeDeclaration[] = [];
|
|
3781
|
+
for (const declaration of declarations) {
|
|
3782
|
+
if (declaration.path === declaration.name) {
|
|
3783
|
+
topLevel.push(declaration);
|
|
3784
|
+
continue;
|
|
3785
|
+
}
|
|
3786
|
+
const group = nestedGroupAt(nestedRoot, declaration.path.split(".").slice(0, -1));
|
|
3787
|
+
group.typeDeclarations.push(declaration);
|
|
3788
|
+
group.typeDeclarations.sort(byName);
|
|
3789
|
+
}
|
|
3790
|
+
return topLevel.sort(byName);
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
// The mappers both emit surfaces read. `mapType` is the engine vocabulary with
|
|
3794
|
+
// the surface's leaves, the module's own declared types, those `declaredTypes`
|
|
3795
|
+
// names from other modules, and constant tokens branded. `rawMapType`, read only
|
|
3796
|
+
// for a typedef's own name, leaves the declared paths out: a handle typedef
|
|
3797
|
+
// (`editor.schema`) would otherwise map to its own path and alias itself.
|
|
3798
|
+
function moduleTypeMappers(
|
|
3799
|
+
module: ApiModule,
|
|
3800
|
+
constantsByFqn: ReadonlyMap<string, ApiConstant>,
|
|
3801
|
+
options: EmitOptions | undefined,
|
|
3802
|
+
): { rawMapType: (token: string) => string; mapType: DefoldTypeMapper } {
|
|
3803
|
+
const leaves = options?.typeLeaves;
|
|
3804
|
+
const rawMapType = options?.mapType ?? mapTypeWithVocabulary({ leaves });
|
|
3805
|
+
const baseMapType =
|
|
3806
|
+
options?.mapType ??
|
|
3807
|
+
mapTypeWithVocabulary({
|
|
3808
|
+
leaves,
|
|
3809
|
+
declared: new Map([...declaredTypePaths([module]), ...(options?.declaredTypes ?? [])]),
|
|
3810
|
+
});
|
|
3811
|
+
const knownConstantFqns = options?.knownConstantFqns;
|
|
3812
|
+
const mapType: DefoldTypeMapper = (token, position) =>
|
|
3813
|
+
constantsByFqn.has(token) || knownConstantFqns?.has(token)
|
|
3814
|
+
? brandType(token, constantsByFqn.get(token))
|
|
3815
|
+
: baseMapType(token, position);
|
|
3816
|
+
return { rawMapType, mapType };
|
|
3817
|
+
}
|
|
3818
|
+
|
|
3819
|
+
/**
|
|
3820
|
+
* Move each `STRUCT` and aliasing `TYPEDEF` to the module whose namespace is the
|
|
3821
|
+
* longest prefix of its name (`b2d.joint.revolute_definition`, declared in the
|
|
3822
|
+
* `b2d` doc, moves to `b2d.joint`). A name no module prefixes stays with the
|
|
3823
|
+
* module that declares it. A declaration the home already declares under the
|
|
3824
|
+
* same kind and name, or that an earlier module already routed there, is
|
|
3825
|
+
* dropped, so the home's own copy wins; a type sharing a value's name
|
|
3826
|
+
* (`resource.atlas`) is untouched. A function moves the same way (1.13.2 files
|
|
3827
|
+
* `bullet3d.collision_object.get_shapes` in the shape doc), since the emitter
|
|
3828
|
+
* declares only the functions under a module's own namespace. Modules with
|
|
3829
|
+
* nothing moved in or out are returned as-is.
|
|
3830
|
+
*/
|
|
3831
|
+
export function routeTypeDeclarations(modules: readonly ApiModule[]): ApiModule[] {
|
|
3832
|
+
const homeOf = (name: string, declaring: string): string => {
|
|
3833
|
+
let home = declaring;
|
|
3834
|
+
let longest = -1;
|
|
3835
|
+
for (const { namespace } of modules) {
|
|
3836
|
+
if (name.startsWith(`${namespace}.`) && namespace.length > longest) {
|
|
3837
|
+
home = namespace;
|
|
3838
|
+
longest = namespace.length;
|
|
3839
|
+
}
|
|
3840
|
+
}
|
|
3841
|
+
return home;
|
|
3842
|
+
};
|
|
3843
|
+
const incoming = new Map<
|
|
3844
|
+
string,
|
|
3845
|
+
{ structs: ApiStruct[]; typedefs: ApiTypedef[]; functions: ApiFunction[] }
|
|
3846
|
+
>();
|
|
3847
|
+
const into = (namespace: string) => {
|
|
3848
|
+
let bucket = incoming.get(namespace);
|
|
3849
|
+
if (bucket === undefined) {
|
|
3850
|
+
bucket = { structs: [], typedefs: [], functions: [] };
|
|
3851
|
+
incoming.set(namespace, bucket);
|
|
3852
|
+
}
|
|
3853
|
+
return bucket;
|
|
3854
|
+
};
|
|
3855
|
+
const kept = modules.map((module) => {
|
|
3856
|
+
const structs = (module.structs ?? []).filter((struct) => {
|
|
3857
|
+
const home = homeOf(struct.name, module.namespace);
|
|
3858
|
+
if (home === module.namespace) return true;
|
|
3859
|
+
into(home).structs.push(struct);
|
|
3860
|
+
return false;
|
|
3861
|
+
});
|
|
3862
|
+
const typedefs = module.typedefs.filter((typedef) => {
|
|
3863
|
+
if (typedef.aliasOf === undefined) return true;
|
|
3864
|
+
const home = homeOf(typedef.name, module.namespace);
|
|
3865
|
+
if (home === module.namespace) return true;
|
|
3866
|
+
into(home).typedefs.push(typedef);
|
|
3867
|
+
return false;
|
|
3868
|
+
});
|
|
3869
|
+
const functions = module.functions.filter((fn) => {
|
|
3870
|
+
const home = homeOf(fn.name, module.namespace);
|
|
3871
|
+
if (home === module.namespace) return true;
|
|
3872
|
+
into(home).functions.push(fn);
|
|
3873
|
+
return false;
|
|
3874
|
+
});
|
|
3875
|
+
return structs.length === (module.structs ?? []).length &&
|
|
3876
|
+
typedefs.length === module.typedefs.length &&
|
|
3877
|
+
functions.length === module.functions.length
|
|
3878
|
+
? module
|
|
3879
|
+
: { ...module, structs, typedefs, functions };
|
|
3880
|
+
});
|
|
3881
|
+
return kept.map((module) => {
|
|
3882
|
+
const bucket = incoming.get(module.namespace);
|
|
3883
|
+
if (bucket === undefined) return module;
|
|
3884
|
+
const unclaimed = (names: Set<string>) => (declaration: { name: string }) => {
|
|
3885
|
+
if (names.has(declaration.name)) return false;
|
|
3886
|
+
names.add(declaration.name);
|
|
3887
|
+
return true;
|
|
3888
|
+
};
|
|
3889
|
+
const structNames = new Set((module.structs ?? []).map((struct) => struct.name));
|
|
3890
|
+
const typedefNames = new Set(module.typedefs.map((typedef) => typedef.name));
|
|
3891
|
+
// A function keeps every overload one doc files, so the home's own name wins
|
|
3892
|
+
// over another doc's copy of it without collapsing the incoming overloads.
|
|
3893
|
+
const functionNames = new Set(module.functions.map((fn) => fn.name));
|
|
3894
|
+
return {
|
|
3895
|
+
...module,
|
|
3896
|
+
structs: [...(module.structs ?? []), ...bucket.structs.filter(unclaimed(structNames))],
|
|
3897
|
+
typedefs: [...module.typedefs, ...bucket.typedefs.filter(unclaimed(typedefNames))],
|
|
3898
|
+
functions: [
|
|
3899
|
+
...module.functions,
|
|
3900
|
+
...bucket.functions.filter((fn) => !functionNames.has(fn.name)),
|
|
3901
|
+
],
|
|
3902
|
+
};
|
|
3903
|
+
});
|
|
3904
|
+
}
|
|
3905
|
+
|
|
3906
|
+
/**
|
|
3907
|
+
* The TS path of every `ENUM`, `STRUCT` and aliasing `TYPEDEF` the (routed)
|
|
3908
|
+
* modules declare, keyed by its Lua name: the name itself when it sits under its
|
|
3909
|
+
* module's namespace, else that name nested under the module
|
|
3910
|
+
* (`on_input.action` in `go` is `go.on_input.action`).
|
|
3911
|
+
*/
|
|
3912
|
+
export function declaredTypePaths(modules: readonly ApiModule[]): Map<string, string> {
|
|
3913
|
+
const paths = new Map<string, string>();
|
|
3914
|
+
for (const module of modules) {
|
|
3915
|
+
const names = [
|
|
3916
|
+
...(module.enums ?? []).map((e) => e.name),
|
|
3917
|
+
...(module.structs ?? []).map((struct) => struct.name),
|
|
3918
|
+
...module.typedefs.filter((t) => t.aliasOf !== undefined).map((t) => t.name),
|
|
3919
|
+
];
|
|
3920
|
+
for (const name of names) {
|
|
3921
|
+
paths.set(
|
|
3922
|
+
name,
|
|
3923
|
+
name.startsWith(`${module.namespace}.`) ? name : `${module.namespace}.${name}`,
|
|
3924
|
+
);
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
return paths;
|
|
3928
|
+
}
|
|
3929
|
+
|
|
3930
|
+
export function emitDeclarations(documented: ApiModule, options?: EmitOptions): string {
|
|
3931
|
+
const module = withUndocumentedConstants(documented, options?.registeredConstants);
|
|
3932
|
+
const prefix = `${module.namespace}.`;
|
|
3933
|
+
|
|
3934
|
+
const constantsByFqn = new Map(module.constants.map((c) => [c.name, c]));
|
|
2652
3935
|
const knownConstantFqns = options?.knownConstantFqns;
|
|
2653
3936
|
const translations = options?.translations ?? {};
|
|
2654
3937
|
const urlParameters = options?.urlParameters ?? [];
|
|
2655
|
-
const
|
|
2656
|
-
const mapType = (
|
|
2657
|
-
constantFqns.has(token) || knownConstantFqns?.has(token)
|
|
2658
|
-
? brandType(token)
|
|
2659
|
-
: baseMapType(token);
|
|
3938
|
+
const baseNotes = indexBaseNoteResolver(options?.indexBaseSource ?? "engine");
|
|
3939
|
+
const { rawMapType, mapType } = moduleTypeMappers(module, constantsByFqn, options);
|
|
2660
3940
|
const constantTokens = constantSlotTokenResolver(
|
|
2661
3941
|
module,
|
|
2662
|
-
new Set([...
|
|
3942
|
+
new Set([...constantsByFqn.keys(), ...(knownConstantFqns ?? [])]),
|
|
2663
3943
|
);
|
|
2664
3944
|
|
|
2665
3945
|
const constants = module.constants
|
|
@@ -2673,6 +3953,7 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
|
|
|
2673
3953
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
2674
3954
|
|
|
2675
3955
|
const functions = module.functions
|
|
3956
|
+
.filter((fn) => !isMisfiled(fn))
|
|
2676
3957
|
.map((fn) => prepareFunction(fn, prefix))
|
|
2677
3958
|
.filter((entry): entry is PreparedFunction => entry !== null)
|
|
2678
3959
|
.sort((a, b) =>
|
|
@@ -2689,6 +3970,7 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
|
|
|
2689
3970
|
// segment. One or two leading identifier segments qualify; anything deeper,
|
|
2690
3971
|
// and any non-identifier segment, stays dropped.
|
|
2691
3972
|
const nestedRoot = collectNestedGroups(module, prefix);
|
|
3973
|
+
const enumAliases = collectEnumAliases(module, constants, nestedRoot);
|
|
2692
3974
|
|
|
2693
3975
|
// Colon methods (`client:send`) are FUNCTION elements named `<receiver>:<method>`
|
|
2694
3976
|
// and are NOT namespace-prefixed, so they fail the flat-identifier test in
|
|
@@ -2706,9 +3988,12 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
|
|
|
2706
3988
|
})),
|
|
2707
3989
|
);
|
|
2708
3990
|
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
3991
|
+
// A typedef that also has colon methods is emitted as a method-bearing
|
|
3992
|
+
// interface below, not an opaque brand alias.
|
|
3993
|
+
const typeDeclarations = placeTypeDeclarations(
|
|
3994
|
+
collectTypeDeclarations(module, new Set(handleGroups.keys()), rawMapType, mapType),
|
|
3995
|
+
nestedRoot,
|
|
3996
|
+
);
|
|
2712
3997
|
|
|
2713
3998
|
// A re-export alias (`export { _x as x }`) switches the ambient namespace out
|
|
2714
3999
|
// of its implicit-export mode, so once any alias is present every sibling
|
|
@@ -2716,28 +4001,29 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
|
|
|
2716
4001
|
// keyword is otherwise omitted to keep every alias-free module byte-identical.
|
|
2717
4002
|
const hasAliases =
|
|
2718
4003
|
variables.some((v) => TS_RESERVED_NAMES.has(v.name)) ||
|
|
2719
|
-
functions.some((fn) => TS_RESERVED_NAMES.has(fn.name))
|
|
4004
|
+
functions.some((fn) => TS_RESERVED_NAMES.has(fn.name)) ||
|
|
4005
|
+
[...nestedRoot.keys()].some((segment) => TS_RESERVED_NAMES.has(segment));
|
|
2720
4006
|
const decl = hasAliases ? "export " : "";
|
|
2721
4007
|
|
|
2722
4008
|
const lines: string[] = [];
|
|
2723
|
-
for (const docLine of namespaceDocLines(module)) lines.push(docLine);
|
|
4009
|
+
for (const docLine of namespaceDocLines(module, translations)) lines.push(docLine);
|
|
2724
4010
|
lines.push(`declare namespace ${module.namespace} {`);
|
|
2725
4011
|
|
|
2726
|
-
for (const
|
|
2727
|
-
// A typedef that also has colon methods is emitted as a method-bearing
|
|
2728
|
-
// interface below, not an opaque brand alias.
|
|
2729
|
-
if (handleGroups.has(t.name)) continue;
|
|
2730
|
-
lines.push(`${INDENT}${decl}${typedefAlias(t.name, baseMapType)}`);
|
|
2731
|
-
}
|
|
4012
|
+
for (const declaration of typeDeclarations) lines.push(...declaration.lines(INDENT, decl));
|
|
2732
4013
|
for (const aliasDecl of constantUnionAliasDeclarations(module.namespace)) {
|
|
2733
4014
|
lines.push(`${INDENT}${decl}${aliasDecl}`);
|
|
2734
4015
|
}
|
|
4016
|
+
for (const alias of enumAliases.topLevel) {
|
|
4017
|
+
lines.push(
|
|
4018
|
+
`${INDENT}${decl}${unionAliasDeclaration(alias.name, alias.members, alias.nilable)}`,
|
|
4019
|
+
);
|
|
4020
|
+
}
|
|
2735
4021
|
const handleIndent = `${INDENT}${INDENT}`;
|
|
2736
4022
|
for (const receiver of handleReceivers) {
|
|
2737
4023
|
const group = handleGroups.get(receiver) ?? [];
|
|
2738
4024
|
lines.push(`${INDENT}${decl}interface ${receiver} {`);
|
|
2739
4025
|
for (const fn of group) {
|
|
2740
|
-
for (const docLine of functionDocLines(fn.original, translations, handleIndent)) {
|
|
4026
|
+
for (const docLine of functionDocLines(fn.original, translations, baseNotes, handleIndent)) {
|
|
2741
4027
|
lines.push(docLine);
|
|
2742
4028
|
}
|
|
2743
4029
|
lines.push(
|
|
@@ -2750,7 +4036,7 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
|
|
|
2750
4036
|
for (const docLine of summaryDocLines(c.original.brief, c.original.description, INDENT)) {
|
|
2751
4037
|
lines.push(docLine);
|
|
2752
4038
|
}
|
|
2753
|
-
lines.push(`${INDENT}${decl}const ${c.name}: ${
|
|
4039
|
+
lines.push(`${INDENT}${decl}const ${c.name}: ${constantType(c)};`);
|
|
2754
4040
|
}
|
|
2755
4041
|
const aliases: { internal: string; public: string }[] = [];
|
|
2756
4042
|
for (const v of variables) {
|
|
@@ -2770,11 +4056,17 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
|
|
|
2770
4056
|
for (const fn of functions) {
|
|
2771
4057
|
const reserved = TS_RESERVED_NAMES.has(fn.name);
|
|
2772
4058
|
const emitName = aliasName(fn.name, aliases);
|
|
2773
|
-
for (const docLine of functionDocLines(fn.original, translations))
|
|
4059
|
+
for (const docLine of functionDocLines(fn.original, translations, baseNotes))
|
|
4060
|
+
lines.push(docLine);
|
|
2774
4061
|
const line = emitFunction(fn, emitName, mapType, resolver, constantTokens, urlParameters);
|
|
2775
4062
|
lines.push(`${INDENT}${reserved ? "" : decl}${line}`);
|
|
2776
4063
|
const alternative = firstSlotAlternative(fn, module.namespace);
|
|
2777
4064
|
if (alternative !== null) {
|
|
4065
|
+
// The overload documents its own parameters, so its hover names the base
|
|
4066
|
+
// of the index it takes. Its examples are the primary signature's.
|
|
4067
|
+
const { examples: _examples, ...overloadDoc } = alternative.original;
|
|
4068
|
+
for (const docLine of functionDocLines(overloadDoc, translations, baseNotes))
|
|
4069
|
+
lines.push(docLine);
|
|
2778
4070
|
const overload = emitFunction(
|
|
2779
4071
|
alternative,
|
|
2780
4072
|
emitName,
|
|
@@ -2787,6 +4079,7 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
|
|
|
2787
4079
|
}
|
|
2788
4080
|
}
|
|
2789
4081
|
|
|
4082
|
+
aliasReservedSegments(nestedRoot.keys(), aliases);
|
|
2790
4083
|
for (const alias of [...aliases].sort((a, b) => a.public.localeCompare(b.public))) {
|
|
2791
4084
|
lines.push(`${INDENT}export { ${alias.internal} as ${alias.public} };`);
|
|
2792
4085
|
}
|
|
@@ -2802,14 +4095,38 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
|
|
|
2802
4095
|
// A reserved-name member inside a nested namespace gets the same recovery as
|
|
2803
4096
|
// a top-level one: emitted un-exported as `_<name>` and re-exported under the
|
|
2804
4097
|
// reserved name. The alias switches this namespace out of implicit-export mode,
|
|
2805
|
-
// so its siblings then need an explicit `export` to stay reachable.
|
|
4098
|
+
// so its siblings then need an explicit `export` to stay reachable. A
|
|
4099
|
+
// reserved segment is itself declared as `_<segment>`, merging with a
|
|
4100
|
+
// same-named function, and reached through the parent's alias.
|
|
2806
4101
|
const segmentAliases: { internal: string; public: string }[] = [];
|
|
2807
4102
|
const segmentDecl =
|
|
2808
4103
|
group.variables.some((v) => TS_RESERVED_NAMES.has(v.name)) ||
|
|
2809
|
-
group.functions.some((fn) => TS_RESERVED_NAMES.has(fn.name))
|
|
4104
|
+
group.functions.some((fn) => TS_RESERVED_NAMES.has(fn.name)) ||
|
|
4105
|
+
[...group.children.keys()].some((child) => TS_RESERVED_NAMES.has(child))
|
|
2810
4106
|
? "export "
|
|
2811
4107
|
: "";
|
|
2812
|
-
|
|
4108
|
+
const reservedSegment = TS_RESERVED_NAMES.has(segment);
|
|
4109
|
+
lines.push(
|
|
4110
|
+
`${indent}${reservedSegment ? "" : outerDecl}namespace ${reservedSegment ? `_${segment}` : segment} {`,
|
|
4111
|
+
);
|
|
4112
|
+
for (const declaration of group.typeDeclarations) {
|
|
4113
|
+
lines.push(...declaration.lines(bodyIndent, segmentDecl));
|
|
4114
|
+
}
|
|
4115
|
+
for (const alias of group.enumAliases) {
|
|
4116
|
+
lines.push(
|
|
4117
|
+
`${bodyIndent}${segmentDecl}${unionAliasDeclaration(alias.name, alias.members, alias.nilable)}`,
|
|
4118
|
+
);
|
|
4119
|
+
}
|
|
4120
|
+
for (const c of group.constants) {
|
|
4121
|
+
for (const docLine of summaryDocLines(
|
|
4122
|
+
c.original.brief,
|
|
4123
|
+
c.original.description,
|
|
4124
|
+
bodyIndent,
|
|
4125
|
+
)) {
|
|
4126
|
+
lines.push(docLine);
|
|
4127
|
+
}
|
|
4128
|
+
lines.push(`${bodyIndent}${segmentDecl}const ${c.name}: ${constantType(c)};`);
|
|
4129
|
+
}
|
|
2813
4130
|
for (const v of group.variables) {
|
|
2814
4131
|
const reserved = TS_RESERVED_NAMES.has(v.name);
|
|
2815
4132
|
const emitName = aliasName(v.name, segmentAliases);
|
|
@@ -2827,13 +4144,14 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
|
|
|
2827
4144
|
for (const fn of group.functions) {
|
|
2828
4145
|
const reserved = TS_RESERVED_NAMES.has(fn.name);
|
|
2829
4146
|
const emitName = aliasName(fn.name, segmentAliases);
|
|
2830
|
-
for (const docLine of functionDocLines(fn.original, translations, bodyIndent)) {
|
|
4147
|
+
for (const docLine of functionDocLines(fn.original, translations, baseNotes, bodyIndent)) {
|
|
2831
4148
|
lines.push(docLine);
|
|
2832
4149
|
}
|
|
2833
4150
|
lines.push(
|
|
2834
4151
|
`${bodyIndent}${reserved ? "" : segmentDecl}${emitFunction(fn, emitName, mapType, resolver, constantTokens, urlParameters)}`,
|
|
2835
4152
|
);
|
|
2836
4153
|
}
|
|
4154
|
+
aliasReservedSegments(group.children.keys(), segmentAliases);
|
|
2837
4155
|
for (const alias of [...segmentAliases].sort((a, b) => a.public.localeCompare(b.public))) {
|
|
2838
4156
|
lines.push(`${bodyIndent}export { ${alias.internal} as ${alias.public} };`);
|
|
2839
4157
|
}
|
|
@@ -2862,14 +4180,22 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
|
|
|
2862
4180
|
}
|
|
2863
4181
|
|
|
2864
4182
|
/**
|
|
2865
|
-
* Every documented slot's rendered TS type, keyed
|
|
2866
|
-
* ref-doc name>`. The position is part of the key
|
|
2867
|
-
* carry several unnamed slots (`push.schedule`
|
|
2868
|
-
* key would collapse into one. Each
|
|
2869
|
-
* own `tsSignature
|
|
2870
|
-
*
|
|
4183
|
+
* Every documented slot's rendered TS type and optionality, keyed
|
|
4184
|
+
* `param|return:<position>:<raw ref-doc name>`. The position is part of the key
|
|
4185
|
+
* because a multi-return can carry several unnamed slots (`push.schedule`
|
|
4186
|
+
* renders two), which a name-only key would collapse into one. Each `type`
|
|
4187
|
+
* appears verbatim inside the symbol's own `tsSignature` as the slot's printed
|
|
4188
|
+
* operand (an interior omissible slot keeps its `| undefined` arm), and
|
|
4189
|
+
* `optional` is whether the name carries `?` there, so a consumer rendering a
|
|
4190
|
+
* slot beside that signature cannot contradict it. Returns and slots folded into
|
|
4191
|
+
* a rest element are never `optional`.
|
|
2871
4192
|
*/
|
|
2872
|
-
export
|
|
4193
|
+
export interface SlotType {
|
|
4194
|
+
readonly type: string;
|
|
4195
|
+
readonly optional: boolean;
|
|
4196
|
+
}
|
|
4197
|
+
|
|
4198
|
+
export type SlotTypes = Readonly<Record<string, SlotType>>;
|
|
2873
4199
|
|
|
2874
4200
|
function slotKey(kind: "param" | "return", position: number, name: string): string {
|
|
2875
4201
|
return `${kind}:${position}:${name}`;
|
|
@@ -2897,19 +4223,19 @@ export interface SymbolSignature {
|
|
|
2897
4223
|
* line, primary first, the same convention as the authored fold; its
|
|
2898
4224
|
* `slotTypes` describe the primary arm only.
|
|
2899
4225
|
*/
|
|
2900
|
-
export function emitSymbolSignatures(
|
|
4226
|
+
export function emitSymbolSignatures(
|
|
4227
|
+
documented: ApiModule,
|
|
4228
|
+
options?: EmitOptions,
|
|
4229
|
+
): SymbolSignature[] {
|
|
4230
|
+
const module = withUndocumentedConstants(documented, options?.registeredConstants);
|
|
2901
4231
|
const prefix = `${module.namespace}.`;
|
|
2902
|
-
const
|
|
4232
|
+
const constantsByFqn = new Map(module.constants.map((c) => [c.name, c]));
|
|
2903
4233
|
const knownConstantFqns = options?.knownConstantFqns;
|
|
2904
4234
|
const urlParameters = options?.urlParameters ?? [];
|
|
2905
|
-
const
|
|
2906
|
-
const mapType = (token: string): string =>
|
|
2907
|
-
constantFqns.has(token) || knownConstantFqns?.has(token)
|
|
2908
|
-
? brandType(token)
|
|
2909
|
-
: baseMapType(token);
|
|
4235
|
+
const { rawMapType, mapType } = moduleTypeMappers(module, constantsByFqn, options);
|
|
2910
4236
|
const constantTokens = constantSlotTokenResolver(
|
|
2911
4237
|
module,
|
|
2912
|
-
new Set([...
|
|
4238
|
+
new Set([...constantsByFqn.keys(), ...(knownConstantFqns ?? [])]),
|
|
2913
4239
|
);
|
|
2914
4240
|
const resolver = buildTableDocResolver(
|
|
2915
4241
|
module.functions.map((fn) => ({
|
|
@@ -2932,9 +4258,10 @@ export function emitSymbolSignatures(module: ApiModule, options?: EmitOptions):
|
|
|
2932
4258
|
const NO_SLOTS: SlotTypes = {};
|
|
2933
4259
|
|
|
2934
4260
|
for (const fn of module.functions) {
|
|
4261
|
+
if (isMisfiled(fn)) continue;
|
|
2935
4262
|
const prepared = prepareFunction(fn, prefix);
|
|
2936
4263
|
if (prepared === null) continue;
|
|
2937
|
-
const slotTypes: Record<string,
|
|
4264
|
+
const slotTypes: Record<string, SlotType> = {};
|
|
2938
4265
|
const primary = emitFunction(
|
|
2939
4266
|
prepared,
|
|
2940
4267
|
emitName(prepared.name),
|
|
@@ -2959,7 +4286,20 @@ export function emitSymbolSignatures(module: ApiModule, options?: EmitOptions):
|
|
|
2959
4286
|
out.push({ identity: fnIdentity(fn), tsSignature, slotTypes });
|
|
2960
4287
|
}
|
|
2961
4288
|
|
|
2962
|
-
const
|
|
4289
|
+
const nestedRoot = collectNestedGroups(module, prefix);
|
|
4290
|
+
const nested = flattenNestedGroups(nestedRoot);
|
|
4291
|
+
for (const c of nested.constants) {
|
|
4292
|
+
out.push({
|
|
4293
|
+
identity: {
|
|
4294
|
+
namespace: module.namespace,
|
|
4295
|
+
kind: "CONSTANT",
|
|
4296
|
+
name: c.fqn,
|
|
4297
|
+
signature: "",
|
|
4298
|
+
},
|
|
4299
|
+
tsSignature: `const ${c.name}: ${constantType(c)};`,
|
|
4300
|
+
slotTypes: NO_SLOTS,
|
|
4301
|
+
});
|
|
4302
|
+
}
|
|
2963
4303
|
for (const v of nested.variables) {
|
|
2964
4304
|
out.push({
|
|
2965
4305
|
identity: {
|
|
@@ -2973,7 +4313,7 @@ export function emitSymbolSignatures(module: ApiModule, options?: EmitOptions):
|
|
|
2973
4313
|
});
|
|
2974
4314
|
}
|
|
2975
4315
|
for (const fn of nested.functions) {
|
|
2976
|
-
const slotTypes: Record<string,
|
|
4316
|
+
const slotTypes: Record<string, SlotType> = {};
|
|
2977
4317
|
out.push({
|
|
2978
4318
|
identity: fnIdentity(fn.original),
|
|
2979
4319
|
tsSignature: emitFunction(
|
|
@@ -2992,7 +4332,7 @@ export function emitSymbolSignatures(module: ApiModule, options?: EmitOptions):
|
|
|
2992
4332
|
const handleGroups = collectHandleMethodGroups(module);
|
|
2993
4333
|
for (const group of handleGroups.values()) {
|
|
2994
4334
|
for (const prepared of group) {
|
|
2995
|
-
const slotTypes: Record<string,
|
|
4335
|
+
const slotTypes: Record<string, SlotType> = {};
|
|
2996
4336
|
out.push({
|
|
2997
4337
|
identity: fnIdentity(prepared.original),
|
|
2998
4338
|
tsSignature: emitMethod(
|
|
@@ -3008,12 +4348,14 @@ export function emitSymbolSignatures(module: ApiModule, options?: EmitOptions):
|
|
|
3008
4348
|
}
|
|
3009
4349
|
}
|
|
3010
4350
|
|
|
4351
|
+
const constants: PreparedConstant[] = [];
|
|
3011
4352
|
for (const c of module.constants) {
|
|
3012
4353
|
const prepared = prepareConstant(c, prefix);
|
|
3013
4354
|
if (prepared === null) continue;
|
|
4355
|
+
constants.push(prepared);
|
|
3014
4356
|
out.push({
|
|
3015
4357
|
identity: { namespace: module.namespace, kind: "CONSTANT", name: c.name, signature: "" },
|
|
3016
|
-
tsSignature: `const ${prepared.name}: ${
|
|
4358
|
+
tsSignature: `const ${prepared.name}: ${constantType(prepared)};`,
|
|
3017
4359
|
slotTypes: NO_SLOTS,
|
|
3018
4360
|
});
|
|
3019
4361
|
}
|
|
@@ -3040,11 +4382,22 @@ export function emitSymbolSignatures(module: ApiModule, options?: EmitOptions):
|
|
|
3040
4382
|
});
|
|
3041
4383
|
}
|
|
3042
4384
|
|
|
3043
|
-
|
|
3044
|
-
|
|
4385
|
+
// A struct signs as its interface on one line; each member line is verbatim in
|
|
4386
|
+
// the declarations.
|
|
4387
|
+
for (const declaration of collectTypeDeclarations(
|
|
4388
|
+
module,
|
|
4389
|
+
new Set(handleGroups.keys()),
|
|
4390
|
+
rawMapType,
|
|
4391
|
+
mapType,
|
|
4392
|
+
)) {
|
|
3045
4393
|
out.push({
|
|
3046
|
-
identity: {
|
|
3047
|
-
|
|
4394
|
+
identity: {
|
|
4395
|
+
namespace: module.namespace,
|
|
4396
|
+
kind: "TYPEDEF",
|
|
4397
|
+
name: declaration.path,
|
|
4398
|
+
signature: "",
|
|
4399
|
+
},
|
|
4400
|
+
tsSignature: declaration.signature,
|
|
3048
4401
|
slotTypes: NO_SLOTS,
|
|
3049
4402
|
});
|
|
3050
4403
|
}
|
|
@@ -3055,9 +4408,18 @@ export function emitSymbolSignatures(module: ApiModule, options?: EmitOptions):
|
|
|
3055
4408
|
for (const row of CONSTANT_UNION_ALIASES.filter((r) => r.home === module.namespace)) {
|
|
3056
4409
|
out.push({
|
|
3057
4410
|
identity: { namespace: module.namespace, kind: "TYPEDEF", name: row.name, signature: "" },
|
|
3058
|
-
tsSignature:
|
|
3059
|
-
|
|
3060
|
-
|
|
4411
|
+
tsSignature: unionAliasDeclaration(row.name, row.members),
|
|
4412
|
+
slotTypes: NO_SLOTS,
|
|
4413
|
+
});
|
|
4414
|
+
}
|
|
4415
|
+
|
|
4416
|
+
// An enum alias is keyed by its local path (`STATUS`, `ui.COLOR`), as the
|
|
4417
|
+
// constant-union aliases above are keyed by their local name.
|
|
4418
|
+
const enumAliases = collectEnumAliases(module, constants, nestedRoot);
|
|
4419
|
+
for (const alias of enumAliases.all) {
|
|
4420
|
+
out.push({
|
|
4421
|
+
identity: { namespace: module.namespace, kind: "TYPEDEF", name: alias.path, signature: "" },
|
|
4422
|
+
tsSignature: unionAliasDeclaration(alias.name, alias.members, alias.nilable),
|
|
3061
4423
|
slotTypes: NO_SLOTS,
|
|
3062
4424
|
});
|
|
3063
4425
|
}
|
|
@@ -3084,35 +4446,67 @@ interface PreparedVariable {
|
|
|
3084
4446
|
// One level of the nested-member tree: the members declared directly under a
|
|
3085
4447
|
// segment, plus the segments declared beneath it.
|
|
3086
4448
|
interface NestedGroup {
|
|
4449
|
+
constants: PreparedConstant[];
|
|
3087
4450
|
variables: PreparedVariable[];
|
|
3088
4451
|
functions: PreparedFunction[];
|
|
4452
|
+
enumAliases: EnumAlias[];
|
|
4453
|
+
typeDeclarations: TypeDeclaration[];
|
|
3089
4454
|
children: Map<string, NestedGroup>;
|
|
3090
4455
|
}
|
|
3091
4456
|
|
|
4457
|
+
// One `ENUM`'s union alias: `name` is the declared identifier, `path` the local
|
|
4458
|
+
// path under the module namespace (`ui.COLOR`), `members` the member FQNs.
|
|
4459
|
+
interface EnumAlias {
|
|
4460
|
+
name: string;
|
|
4461
|
+
path: string;
|
|
4462
|
+
members: readonly string[];
|
|
4463
|
+
// Members typed `<ENUM>|nil`, whose arm drops the `undefined` their `const`
|
|
4464
|
+
// admits, as a slot naming one does.
|
|
4465
|
+
nilable: ReadonlySet<string>;
|
|
4466
|
+
}
|
|
4467
|
+
|
|
3092
4468
|
// A stripped local carrying one or two leading identifier segments before the
|
|
3093
4469
|
// member name (`dns.toip`, `COLOR.TEXT`, `schema.integer`). Three levels deep is
|
|
3094
4470
|
// beyond anything the vendored documents describe, so it stays dropped rather
|
|
3095
4471
|
// than half-emitted.
|
|
3096
4472
|
const NESTED_MEMBER_LOCAL = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*){1,2}$/;
|
|
3097
4473
|
|
|
4474
|
+
// The group at `segments` under `root`, creating each missing level.
|
|
4475
|
+
function nestedGroupAt(root: Map<string, NestedGroup>, segments: readonly string[]): NestedGroup {
|
|
4476
|
+
let level = root;
|
|
4477
|
+
let group: NestedGroup | undefined;
|
|
4478
|
+
for (const segment of segments) {
|
|
4479
|
+
let next = level.get(segment);
|
|
4480
|
+
if (next === undefined) {
|
|
4481
|
+
next = {
|
|
4482
|
+
constants: [],
|
|
4483
|
+
variables: [],
|
|
4484
|
+
functions: [],
|
|
4485
|
+
enumAliases: [],
|
|
4486
|
+
typeDeclarations: [],
|
|
4487
|
+
children: new Map(),
|
|
4488
|
+
};
|
|
4489
|
+
level.set(segment, next);
|
|
4490
|
+
}
|
|
4491
|
+
group = next;
|
|
4492
|
+
level = next.children;
|
|
4493
|
+
}
|
|
4494
|
+
return group as NestedGroup;
|
|
4495
|
+
}
|
|
4496
|
+
|
|
3098
4497
|
function collectNestedGroups(module: ApiModule, prefix: string): Map<string, NestedGroup> {
|
|
3099
4498
|
const root = new Map<string, NestedGroup>();
|
|
3100
|
-
const groupAt = (segments: readonly string[]): NestedGroup =>
|
|
3101
|
-
let level = root;
|
|
3102
|
-
let group: NestedGroup | undefined;
|
|
3103
|
-
for (const segment of segments) {
|
|
3104
|
-
let next = level.get(segment);
|
|
3105
|
-
if (next === undefined) {
|
|
3106
|
-
next = { variables: [], functions: [], children: new Map() };
|
|
3107
|
-
level.set(segment, next);
|
|
3108
|
-
}
|
|
3109
|
-
group = next;
|
|
3110
|
-
level = next.children;
|
|
3111
|
-
}
|
|
3112
|
-
return group as NestedGroup;
|
|
3113
|
-
};
|
|
4499
|
+
const groupAt = (segments: readonly string[]): NestedGroup => nestedGroupAt(root, segments);
|
|
3114
4500
|
const pathOf = (local: string): string[] => local.split(".").slice(0, -1);
|
|
3115
4501
|
|
|
4502
|
+
for (const c of module.constants) {
|
|
4503
|
+
const local = stripPrefix(c.name, prefix);
|
|
4504
|
+
if (!NESTED_MEMBER_LOCAL.test(local)) continue;
|
|
4505
|
+
const segments = pathOf(local);
|
|
4506
|
+
const prepared = prepareConstant(c, `${module.namespace}.${segments.join(".")}.`);
|
|
4507
|
+
if (prepared === null) continue;
|
|
4508
|
+
groupAt(segments).constants.push(prepared);
|
|
4509
|
+
}
|
|
3116
4510
|
for (const v of module.variables) {
|
|
3117
4511
|
const local = stripPrefix(v.name, prefix);
|
|
3118
4512
|
if (!NESTED_MEMBER_LOCAL.test(local)) continue;
|
|
@@ -3132,6 +4526,7 @@ function collectNestedGroups(module: ApiModule, prefix: string): Map<string, Nes
|
|
|
3132
4526
|
|
|
3133
4527
|
const sortLevel = (level: Map<string, NestedGroup>): void => {
|
|
3134
4528
|
for (const group of level.values()) {
|
|
4529
|
+
group.constants.sort((a, b) => a.name.localeCompare(b.name));
|
|
3135
4530
|
group.variables.sort((a, b) => a.name.localeCompare(b.name));
|
|
3136
4531
|
group.functions.sort((a, b) =>
|
|
3137
4532
|
a.name === b.name
|
|
@@ -3150,21 +4545,71 @@ function collectNestedGroups(module: ApiModule, prefix: string): Map<string, Nes
|
|
|
3150
4545
|
// children). Callers that need no per-segment container read the tree through
|
|
3151
4546
|
// this rather than re-deriving which members are nested and how deep.
|
|
3152
4547
|
function flattenNestedGroups(level: ReadonlyMap<string, NestedGroup>): {
|
|
4548
|
+
constants: PreparedConstant[];
|
|
3153
4549
|
variables: PreparedVariable[];
|
|
3154
4550
|
functions: PreparedFunction[];
|
|
3155
4551
|
} {
|
|
4552
|
+
const constants: PreparedConstant[] = [];
|
|
3156
4553
|
const variables: PreparedVariable[] = [];
|
|
3157
4554
|
const functions: PreparedFunction[] = [];
|
|
3158
4555
|
const walk = (current: ReadonlyMap<string, NestedGroup>): void => {
|
|
3159
4556
|
for (const segment of [...current.keys()].sort((a, b) => a.localeCompare(b))) {
|
|
3160
4557
|
const group = current.get(segment) as NestedGroup;
|
|
4558
|
+
constants.push(...group.constants);
|
|
3161
4559
|
variables.push(...group.variables);
|
|
3162
4560
|
functions.push(...group.functions);
|
|
3163
4561
|
walk(group.children);
|
|
3164
4562
|
}
|
|
3165
4563
|
};
|
|
3166
4564
|
walk(level);
|
|
3167
|
-
return { variables, functions };
|
|
4565
|
+
return { constants, variables, functions };
|
|
4566
|
+
}
|
|
4567
|
+
|
|
4568
|
+
// Each `ENUM`'s union alias, placed where its name lands: a flat local
|
|
4569
|
+
// (`STATUS`) at the top of the namespace, a nested one (`ui.COLOR`) in the
|
|
4570
|
+
// nested group `ui`, which the tree already holds when the members are nested
|
|
4571
|
+
// beside it. An enum is aliased only when every member is declared, so the
|
|
4572
|
+
// alias never names a constant the module does not emit. The nested groups
|
|
4573
|
+
// gain their aliases in place; `topLevel` and `all` are in name order.
|
|
4574
|
+
function collectEnumAliases(
|
|
4575
|
+
module: ApiModule,
|
|
4576
|
+
topLevelConstants: readonly PreparedConstant[],
|
|
4577
|
+
nestedRoot: Map<string, NestedGroup>,
|
|
4578
|
+
): { topLevel: EnumAlias[]; all: EnumAlias[] } {
|
|
4579
|
+
const prefix = `${module.namespace}.`;
|
|
4580
|
+
const declared = new Set([
|
|
4581
|
+
...topLevelConstants.map((c) => c.fqn),
|
|
4582
|
+
...flattenNestedGroups(nestedRoot).constants.map((c) => c.fqn),
|
|
4583
|
+
]);
|
|
4584
|
+
const nilable = new Set(module.constants.filter((c) => c.nilable).map((c) => c.name));
|
|
4585
|
+
const topLevel: EnumAlias[] = [];
|
|
4586
|
+
const all: EnumAlias[] = [];
|
|
4587
|
+
const enums = [...(module.enums ?? [])].sort((a, b) => a.name.localeCompare(b.name));
|
|
4588
|
+
for (const e of enums) {
|
|
4589
|
+
if (e.members.length === 0 || !e.members.every((m) => declared.has(m))) continue;
|
|
4590
|
+
const path = stripPrefix(e.name, prefix);
|
|
4591
|
+
if (TS_IDENTIFIER.test(path)) {
|
|
4592
|
+
const alias = { name: path, path, members: e.members, nilable };
|
|
4593
|
+
topLevel.push(alias);
|
|
4594
|
+
all.push(alias);
|
|
4595
|
+
continue;
|
|
4596
|
+
}
|
|
4597
|
+
if (!NESTED_MEMBER_LOCAL.test(path)) continue;
|
|
4598
|
+
const segments = path.split(".");
|
|
4599
|
+
const name = segments.pop() as string;
|
|
4600
|
+
let level = nestedRoot;
|
|
4601
|
+
let group: NestedGroup | undefined;
|
|
4602
|
+
for (const segment of segments) {
|
|
4603
|
+
group = level.get(segment);
|
|
4604
|
+
if (group === undefined) break;
|
|
4605
|
+
level = group.children;
|
|
4606
|
+
}
|
|
4607
|
+
if (group === undefined) continue;
|
|
4608
|
+
const alias = { name, path, members: e.members, nilable };
|
|
4609
|
+
group.enumAliases.push(alias);
|
|
4610
|
+
all.push(alias);
|
|
4611
|
+
}
|
|
4612
|
+
return { topLevel, all };
|
|
3168
4613
|
}
|
|
3169
4614
|
|
|
3170
4615
|
function prepareFunction(fn: ApiFunction, prefix: string): PreparedFunction | null {
|
|
@@ -3210,8 +4655,137 @@ function prepareConstant(c: ApiConstant, prefix: string): PreparedConstant | nul
|
|
|
3210
4655
|
return { name: stripped, fqn: c.name, original: c };
|
|
3211
4656
|
}
|
|
3212
4657
|
|
|
3213
|
-
|
|
3214
|
-
|
|
4658
|
+
// Where the engine registers a constant as a string: the vendored binding file
|
|
4659
|
+
// and the call there whose macro pushes the literal.
|
|
4660
|
+
export interface StringConstantSource {
|
|
4661
|
+
readonly binding: string;
|
|
4662
|
+
readonly macro: string;
|
|
4663
|
+
readonly call: string;
|
|
4664
|
+
}
|
|
4665
|
+
|
|
4666
|
+
const GUI_SCRIPT = "engine/gui/src/gui_script.cpp";
|
|
4667
|
+
const IMAGE_SCRIPT = "engine/gamesys/src/gamesys/scripts/script_image.cpp";
|
|
4668
|
+
|
|
4669
|
+
// Constants the engine registers with `lua_pushliteral`, keyed by FQN. The
|
|
4670
|
+
// ref-docs give constants no type, and every other constant is a number;
|
|
4671
|
+
// `string-constant-provenance.test.ts` reds when a vendored binding stops
|
|
4672
|
+
// pushing one as a string.
|
|
4673
|
+
export const STRING_CONSTANTS: ReadonlyMap<string, StringConstantSource> = new Map([
|
|
4674
|
+
...[
|
|
4675
|
+
"position",
|
|
4676
|
+
"rotation",
|
|
4677
|
+
"euler",
|
|
4678
|
+
"scale",
|
|
4679
|
+
"color",
|
|
4680
|
+
"outline",
|
|
4681
|
+
"shadow",
|
|
4682
|
+
"size",
|
|
4683
|
+
"fill_angle",
|
|
4684
|
+
"inner_radius",
|
|
4685
|
+
"leading",
|
|
4686
|
+
"tracking",
|
|
4687
|
+
"slice9",
|
|
4688
|
+
].map((prop): [string, StringConstantSource] => {
|
|
4689
|
+
const name = prop.toUpperCase();
|
|
4690
|
+
const call = `SETPROP(${prop}, ${name})`;
|
|
4691
|
+
return [`gui.PROP_${name}`, { binding: GUI_SCRIPT, macro: "SETPROP", call }];
|
|
4692
|
+
}),
|
|
4693
|
+
...(
|
|
4694
|
+
[
|
|
4695
|
+
["TYPE_RGB", "rgb"],
|
|
4696
|
+
["TYPE_RGBA", "rgba"],
|
|
4697
|
+
["TYPE_LUMINANCE", "l"],
|
|
4698
|
+
["TYPE_LUMINANCE_ALPHA", "la"],
|
|
4699
|
+
] as const
|
|
4700
|
+
).map(([name, value]): [string, StringConstantSource] => [
|
|
4701
|
+
`image.${name}`,
|
|
4702
|
+
{ binding: IMAGE_SCRIPT, macro: "SETCONSTANT", call: `SETCONSTANT(${name}, "${value}")` },
|
|
4703
|
+
]),
|
|
4704
|
+
]);
|
|
4705
|
+
|
|
4706
|
+
// Documented constants the engine never registers, keyed by FQN with the
|
|
4707
|
+
// reason, so Lua reads each as nil. `nil-constant-provenance.test.ts` pins the
|
|
4708
|
+
// keys to the accepted `constant-missing-binding` binding verdicts.
|
|
4709
|
+
export const NIL_CONSTANTS: ReadonlyMap<string, string> = new Map([
|
|
4710
|
+
[
|
|
4711
|
+
"render.RENDER_TARGET_DEFAULT",
|
|
4712
|
+
"render_script.cpp registers no such field; render.set_render_target reads the nil as the default target",
|
|
4713
|
+
],
|
|
4714
|
+
]);
|
|
4715
|
+
|
|
4716
|
+
// Constants the engine registers but some release's ref-doc omits, keyed by FQN
|
|
4717
|
+
// with the doc the emit carries. A surface gains an entry only where its ref-doc
|
|
4718
|
+
// leaves the constant out and its engine bindings register it.
|
|
4719
|
+
// `undocumented-constant-provenance.test.ts` reds an entry no committed target
|
|
4720
|
+
// still needs. `sameValueAs` names the documented constant the engine registers
|
|
4721
|
+
// with the same value; where the surface declares it, the back-fill takes its
|
|
4722
|
+
// type, so a value compares equal to either spelling.
|
|
4723
|
+
export const UNDOCUMENTED_CONSTANTS: ReadonlyMap<
|
|
4724
|
+
string,
|
|
4725
|
+
{ readonly doc: string; readonly sameValueAs?: string }
|
|
4726
|
+
> = new Map([
|
|
4727
|
+
[
|
|
4728
|
+
"render.CONTEXT_EVENT_CONTEXT_LOST",
|
|
4729
|
+
{
|
|
4730
|
+
doc: "The rendering context was lost. Rendering is paused and all graphics resources become invalid. Passed to the `render.set_listener` callback.",
|
|
4731
|
+
},
|
|
4732
|
+
],
|
|
4733
|
+
[
|
|
4734
|
+
"render.CONTEXT_EVENT_CONTEXT_RESTORED",
|
|
4735
|
+
{
|
|
4736
|
+
doc: "The rendering context was restored. Rendering is still paused and graphics resources are still invalid, but can be reloaded. Passed to the `render.set_listener` callback.",
|
|
4737
|
+
},
|
|
4738
|
+
],
|
|
4739
|
+
[
|
|
4740
|
+
"window.WINDOW_EVENT_ICONFIED",
|
|
4741
|
+
{
|
|
4742
|
+
doc: "Deprecated misspelling of `window.WINDOW_EVENT_ICONIFIED`, with the same value. The engine still registers it.",
|
|
4743
|
+
sameValueAs: "window.WINDOW_EVENT_ICONIFIED",
|
|
4744
|
+
},
|
|
4745
|
+
],
|
|
4746
|
+
]);
|
|
4747
|
+
|
|
4748
|
+
function withUndocumentedConstants(
|
|
4749
|
+
module: ApiModule,
|
|
4750
|
+
registered: ReadonlySet<string> | undefined,
|
|
4751
|
+
): ApiModule {
|
|
4752
|
+
const prefix = `${module.namespace}.`;
|
|
4753
|
+
const documented = new Set(module.constants.map((c) => c.name));
|
|
4754
|
+
const backfill: ApiConstant[] = [];
|
|
4755
|
+
for (const [fqn, { doc, sameValueAs }] of UNDOCUMENTED_CONSTANTS) {
|
|
4756
|
+
if (!fqn.startsWith(prefix) || fqn.slice(prefix.length).includes(".")) continue;
|
|
4757
|
+
if (documented.has(fqn)) continue;
|
|
4758
|
+
if (registered !== undefined && !registered.has(fqn)) continue;
|
|
4759
|
+
const aliasOf =
|
|
4760
|
+
sameValueAs !== undefined && documented.has(sameValueAs) ? sameValueAs : undefined;
|
|
4761
|
+
backfill.push({
|
|
4762
|
+
name: fqn,
|
|
4763
|
+
brief: doc,
|
|
4764
|
+
description: "",
|
|
4765
|
+
...(aliasOf === undefined ? {} : { aliasOf }),
|
|
4766
|
+
});
|
|
4767
|
+
}
|
|
4768
|
+
return backfill.length === 0
|
|
4769
|
+
? module
|
|
4770
|
+
: { ...module, constants: [...module.constants, ...backfill] };
|
|
4771
|
+
}
|
|
4772
|
+
|
|
4773
|
+
// `constant` is the module's own element for `fqn`, absent for a constant another
|
|
4774
|
+
// module declares; an `ENUM` member's value type rides on it.
|
|
4775
|
+
function brandType(fqn: string, constant?: ApiConstant): string {
|
|
4776
|
+
// `undefined & { __brand }` collapses to `never`, so a nil constant takes no brand.
|
|
4777
|
+
if (NIL_CONSTANTS.has(fqn)) return "undefined";
|
|
4778
|
+
const base = constant?.valueType === "string" || STRING_CONSTANTS.has(fqn) ? "string" : "number";
|
|
4779
|
+
return `${base} & { readonly __brand: "${fqn}" }`;
|
|
4780
|
+
}
|
|
4781
|
+
|
|
4782
|
+
// A declared constant's type. A nilable `ENUM` member admits `undefined` here
|
|
4783
|
+
// only: a slot naming it still takes the brand, as the value it is documented to
|
|
4784
|
+
// accept.
|
|
4785
|
+
function constantType(c: PreparedConstant): string {
|
|
4786
|
+
if (c.original.aliasOf !== undefined) return `typeof ${c.original.aliasOf}`;
|
|
4787
|
+
const brand = brandType(c.fqn, c.original);
|
|
4788
|
+
return c.original.nilable === true && brand !== "undefined" ? `${brand} | undefined` : brand;
|
|
3215
4789
|
}
|
|
3216
4790
|
|
|
3217
4791
|
function prepareVariable(v: ApiVariable, prefix: string): PreparedVariable | null {
|
|
@@ -3235,26 +4809,44 @@ function aliasName(name: string, aliases: { internal: string; public: string }[]
|
|
|
3235
4809
|
return internal;
|
|
3236
4810
|
}
|
|
3237
4811
|
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
4812
|
+
// A nested namespace named by a reserved word is emitted as `_<segment>`; its
|
|
4813
|
+
// parent re-exports it under the reserved name, sharing the alias a same-named
|
|
4814
|
+
// function already recorded.
|
|
4815
|
+
function aliasReservedSegments(
|
|
4816
|
+
segments: Iterable<string>,
|
|
4817
|
+
aliases: { internal: string; public: string }[],
|
|
4818
|
+
): void {
|
|
4819
|
+
for (const segment of segments) {
|
|
4820
|
+
if (!TS_RESERVED_NAMES.has(segment)) continue;
|
|
4821
|
+
if (aliases.some((alias) => alias.public === segment)) continue;
|
|
4822
|
+
aliases.push({ internal: `_${segment}`, public: segment });
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4825
|
+
|
|
4826
|
+
// A variable whose ref-doc states its type only in prose, keyed by FQN. Each
|
|
4827
|
+
// entry fills a slot upstream left empty; `emit-dts.test.ts` reds once the
|
|
4828
|
+
// default target's ref-doc types it.
|
|
4829
|
+
export const VARIABLE_TYPE_OVERRIDES: ReadonlyMap<string, string> = new Map([
|
|
4830
|
+
// "A `string`, either:" followed by the four platform ids.
|
|
4831
|
+
["editor.platform", '"x86_64-win32" | "x86_64-macos" | "arm64-macos" | "x86_64-linux"'],
|
|
4832
|
+
]);
|
|
4833
|
+
|
|
4834
|
+
function emitVariable(prepared: PreparedVariable, name: string, mapType: DefoldTypeMapper): string {
|
|
3243
4835
|
const ts =
|
|
3244
4836
|
prepared.original.types.length > 0
|
|
3245
4837
|
? unionFromTokens(prepared.original.types, mapType)
|
|
3246
|
-
: "unknown";
|
|
4838
|
+
: (VARIABLE_TYPE_OVERRIDES.get(prepared.original.name) ?? "unknown");
|
|
3247
4839
|
return `const ${name}: ${ts};`;
|
|
3248
4840
|
}
|
|
3249
4841
|
|
|
3250
4842
|
function memberSignature(
|
|
3251
4843
|
prepared: PreparedFunction,
|
|
3252
4844
|
name: string,
|
|
3253
|
-
mapType:
|
|
4845
|
+
mapType: DefoldTypeMapper,
|
|
3254
4846
|
resolver: TableDocResolver,
|
|
3255
4847
|
constantTokens: ConstantSlotTokens,
|
|
3256
4848
|
urlParameters: UrlParameterTable,
|
|
3257
|
-
slotSink?: Record<string,
|
|
4849
|
+
slotSink?: Record<string, SlotType>,
|
|
3258
4850
|
): string {
|
|
3259
4851
|
const original = prepared.original.parameters;
|
|
3260
4852
|
const elementName = prepared.original.name;
|
|
@@ -3293,16 +4885,16 @@ function memberSignature(
|
|
|
3293
4885
|
if (slotSink !== undefined) {
|
|
3294
4886
|
for (const [i, p] of positional.entries()) {
|
|
3295
4887
|
const raw = original[i];
|
|
3296
|
-
if (raw) slotSink[slotKey("param", i, raw.name)] = p.
|
|
4888
|
+
if (raw) slotSink[slotKey("param", i, raw.name)] = { type: p.type, optional: p.optional };
|
|
3297
4889
|
}
|
|
3298
4890
|
for (const slot of rest?.slots ?? []) {
|
|
3299
|
-
slotSink[slotKey("param", slot.position, slot.name)] = slot.ts;
|
|
4891
|
+
slotSink[slotKey("param", slot.position, slot.name)] = { type: slot.ts, optional: false };
|
|
3300
4892
|
}
|
|
3301
4893
|
// A predicate renders `arg is T` in place of the mapped `boolean`, so its
|
|
3302
4894
|
// return slot has no text in the signature to stand on and is not reported.
|
|
3303
4895
|
if (!isPredicate) {
|
|
3304
4896
|
for (const slot of ret.slots) {
|
|
3305
|
-
slotSink[slotKey("return", slot.position, slot.name)] = slot.ts;
|
|
4897
|
+
slotSink[slotKey("return", slot.position, slot.name)] = { type: slot.ts, optional: false };
|
|
3306
4898
|
}
|
|
3307
4899
|
}
|
|
3308
4900
|
}
|
|
@@ -3315,11 +4907,11 @@ function memberSignature(
|
|
|
3315
4907
|
function emitFunction(
|
|
3316
4908
|
prepared: PreparedFunction,
|
|
3317
4909
|
name: string,
|
|
3318
|
-
mapType:
|
|
4910
|
+
mapType: DefoldTypeMapper,
|
|
3319
4911
|
resolver: TableDocResolver,
|
|
3320
4912
|
constantTokens: ConstantSlotTokens,
|
|
3321
4913
|
urlParameters: UrlParameterTable,
|
|
3322
|
-
slotSink?: Record<string,
|
|
4914
|
+
slotSink?: Record<string, SlotType>,
|
|
3323
4915
|
): string {
|
|
3324
4916
|
return `function ${memberSignature(prepared, name, mapType, resolver, constantTokens, urlParameters, slotSink)}`;
|
|
3325
4917
|
}
|
|
@@ -3329,11 +4921,11 @@ function emitFunction(
|
|
|
3329
4921
|
// member carries no `self` parameter.
|
|
3330
4922
|
function emitMethod(
|
|
3331
4923
|
prepared: PreparedFunction,
|
|
3332
|
-
mapType:
|
|
4924
|
+
mapType: DefoldTypeMapper,
|
|
3333
4925
|
resolver: TableDocResolver,
|
|
3334
4926
|
constantTokens: ConstantSlotTokens,
|
|
3335
4927
|
urlParameters: UrlParameterTable,
|
|
3336
|
-
slotSink?: Record<string,
|
|
4928
|
+
slotSink?: Record<string, SlotType>,
|
|
3337
4929
|
): string {
|
|
3338
4930
|
return memberSignature(
|
|
3339
4931
|
prepared,
|
|
@@ -3352,57 +4944,103 @@ function emitMethod(
|
|
|
3352
4944
|
// fallback applies to non-identifier names and a vararg drops its dots, matching
|
|
3353
4945
|
// `emitParameter` and `emitRestParameter`) so the tag resolves on hover; a
|
|
3354
4946
|
// parameter folded into a rest element union keeps its own tag, so the prose
|
|
3355
|
-
// documenting it survives. A single documented return becomes `@returns
|
|
3356
|
-
//
|
|
4947
|
+
// documenting it survives. A single documented return becomes `@returns`; a
|
|
4948
|
+
// multi-return function gains one only when a slot carries a classified index
|
|
4949
|
+
// (see `multiReturnDoc`). A return authored in `RETURN_TYPE_OVERRIDES` has no
|
|
4950
|
+
// ref-doc slot, so it gains `@returns` only for the base notes of its classified
|
|
4951
|
+
// fields (see `overrideReturnDoc`). Returns `[]` for a fully-undocumented
|
|
4952
|
+
// function, leaving its emission byte-identical.
|
|
3357
4953
|
function functionDocLines(
|
|
3358
4954
|
fn: ApiFunction,
|
|
3359
4955
|
translations: TranslationStore,
|
|
4956
|
+
baseNotes: IndexBaseNoteResolver,
|
|
3360
4957
|
indent: string = INDENT,
|
|
3361
4958
|
): string[] {
|
|
3362
4959
|
const params = fn.parameters.map((p, index) => ({
|
|
3363
4960
|
name: emittedParamName(p, index),
|
|
3364
|
-
doc:
|
|
4961
|
+
doc: appendNotes(
|
|
4962
|
+
translateProseFences(htmlToDocText(p.doc), fn.name, translations),
|
|
4963
|
+
baseNotes(fn.name, "param", p.name),
|
|
4964
|
+
),
|
|
3365
4965
|
}));
|
|
3366
4966
|
const onlyReturn = fn.returnValues.length === 1 ? fn.returnValues[0] : undefined;
|
|
4967
|
+
const notedReturns =
|
|
4968
|
+
multiReturnDoc(fn, translations, baseNotes) ?? overrideReturnDoc(fn, baseNotes);
|
|
3367
4969
|
const lua = htmlToCodeText(fn.examples ?? "");
|
|
3368
|
-
//
|
|
3369
|
-
//
|
|
3370
|
-
//
|
|
3371
|
-
//
|
|
3372
|
-
// absent translation) keeps the Lua fallback.
|
|
4970
|
+
// Each example resolves by its own segment hash, a single-example element's
|
|
4971
|
+
// one segment being the whole blob with no prose. The blob documents its
|
|
4972
|
+
// examples as TypeScript only when every segment has an authored body: a
|
|
4973
|
+
// partial resolve would drop the rest, so any miss keeps the Lua fallback.
|
|
3373
4974
|
const segments = splitExampleSources(fn.examples ?? "");
|
|
3374
|
-
const
|
|
3375
|
-
segments.length > 1
|
|
3376
|
-
? lookupExampleTranslations(
|
|
3377
|
-
translations,
|
|
3378
|
-
fn.name,
|
|
3379
|
-
segments.map((segment) => hashExampleSource(segment.code)),
|
|
3380
|
-
)
|
|
3381
|
-
: null;
|
|
3382
|
-
const ts = lua === "" ? null : lookupTranslation(translations, fn.name, hashExampleSource(lua));
|
|
4975
|
+
const resolved = lookupExampleSegments(translations, fn.name, segments);
|
|
3383
4976
|
const exampleParts: Pick<DocCommentParts, "examples"> =
|
|
3384
|
-
|
|
4977
|
+
resolved !== null
|
|
3385
4978
|
? {
|
|
3386
|
-
examples:
|
|
4979
|
+
examples: resolved.map(({ ts: text, prose }) => ({
|
|
3387
4980
|
text,
|
|
3388
4981
|
lang: "ts" as const,
|
|
3389
|
-
...(
|
|
4982
|
+
...(prose ? { prose } : {}),
|
|
3390
4983
|
})),
|
|
3391
4984
|
}
|
|
3392
|
-
:
|
|
3393
|
-
? { examples: [{ text:
|
|
3394
|
-
:
|
|
3395
|
-
? { examples: [{ text: lua, lang: "lua" }] }
|
|
3396
|
-
: {};
|
|
4985
|
+
: lua !== ""
|
|
4986
|
+
? { examples: [{ text: lua, lang: "lua" }] }
|
|
4987
|
+
: {};
|
|
3397
4988
|
const parts: DocCommentParts = {
|
|
3398
|
-
summary:
|
|
4989
|
+
summary: translateProseFences(
|
|
4990
|
+
htmlToDocText(summaryFor(fn.brief, fn.description)),
|
|
4991
|
+
fn.name,
|
|
4992
|
+
translations,
|
|
4993
|
+
),
|
|
3399
4994
|
params,
|
|
3400
|
-
...(onlyReturn
|
|
4995
|
+
...(onlyReturn
|
|
4996
|
+
? {
|
|
4997
|
+
returns: appendNotes(
|
|
4998
|
+
translateProseFences(htmlToDocText(onlyReturn.doc), fn.name, translations),
|
|
4999
|
+
baseNotes(fn.name, "return", onlyReturn.name),
|
|
5000
|
+
),
|
|
5001
|
+
}
|
|
5002
|
+
: notedReturns !== undefined
|
|
5003
|
+
? { returns: notedReturns }
|
|
5004
|
+
: {}),
|
|
3401
5005
|
...exampleParts,
|
|
3402
5006
|
};
|
|
3403
5007
|
return indentDocLines(parts, indent);
|
|
3404
5008
|
}
|
|
3405
5009
|
|
|
5010
|
+
// A multi-return function documents its tuple slot by slot, in tuple order, so
|
|
5011
|
+
// the hover can name each classified index's base. Every other multi-return
|
|
5012
|
+
// function keeps no `@returns`, leaving its emission byte-identical.
|
|
5013
|
+
function multiReturnDoc(
|
|
5014
|
+
fn: ApiFunction,
|
|
5015
|
+
translations: TranslationStore,
|
|
5016
|
+
baseNotes: IndexBaseNoteResolver,
|
|
5017
|
+
): string | undefined {
|
|
5018
|
+
if (fn.returnValues.length < 2) return undefined;
|
|
5019
|
+
const classified = fn.returnValues.some(
|
|
5020
|
+
(slot) => baseNotes(fn.name, "return", slot.name).length > 0,
|
|
5021
|
+
);
|
|
5022
|
+
if (!classified) return undefined;
|
|
5023
|
+
const bullets = fn.returnValues.map((slot) => {
|
|
5024
|
+
const doc = appendNotes(
|
|
5025
|
+
translateProseFences(htmlToDocText(slot.doc), fn.name, translations),
|
|
5026
|
+
baseNotes(fn.name, "return", slot.name),
|
|
5027
|
+
);
|
|
5028
|
+
const [first = "", ...rest] = doc.split("\n");
|
|
5029
|
+
return [
|
|
5030
|
+
`- \`${slot.name}\` — ${first}`,
|
|
5031
|
+
...rest.map((line) => (line === "" ? "" : ` ${line}`)),
|
|
5032
|
+
];
|
|
5033
|
+
});
|
|
5034
|
+
const names = fn.returnValues.map((slot) => slot.name).join(", ");
|
|
5035
|
+
return [`\`[${names}]\`:`, ...bullets.flat()].join("\n");
|
|
5036
|
+
}
|
|
5037
|
+
|
|
5038
|
+
function overrideReturnDoc(fn: ApiFunction, baseNotes: IndexBaseNoteResolver): string | undefined {
|
|
5039
|
+
if (fn.returnValues.length > 0 || !RETURN_TYPE_OVERRIDES.has(fn.name)) return undefined;
|
|
5040
|
+
const notes = baseNotes(fn.name, "return", OVERRIDE_RETURN_SLOT);
|
|
5041
|
+
return notes.length > 0 ? notes.join(" ") : undefined;
|
|
5042
|
+
}
|
|
5043
|
+
|
|
3406
5044
|
// Summary-only doc lines for a member that carries no params or returns
|
|
3407
5045
|
// (constants, variables, `properties` members). Reuses the same renderer and
|
|
3408
5046
|
// summary precedence as functions; returns `[]` for an undocumented member so
|
|
@@ -3412,11 +5050,11 @@ function summaryDocLines(brief: string, description: string, indent: string): st
|
|
|
3412
5050
|
return indentDocLines({ summary: htmlToDocText(summaryFor(brief, description)) }, indent);
|
|
3413
5051
|
}
|
|
3414
5052
|
|
|
3415
|
-
function namespaceDocLines(module: ApiModule): string[] {
|
|
5053
|
+
function namespaceDocLines(module: ApiModule, translations: TranslationStore): string[] {
|
|
3416
5054
|
const official = summaryFor(module.brief, module.description).trim();
|
|
3417
5055
|
const summary =
|
|
3418
5056
|
official.length > 0
|
|
3419
|
-
? htmlToDocText(official)
|
|
5057
|
+
? translateProseFences(htmlToDocText(official), module.namespace, translations)
|
|
3420
5058
|
: `(synthesized)\nDefold \`${module.namespace}\` API namespace.`;
|
|
3421
5059
|
return indentDocLines({ summary }, "");
|
|
3422
5060
|
}
|
|
@@ -3425,13 +5063,6 @@ function indentDocLines(parts: DocCommentParts, indent: string): string[] {
|
|
|
3425
5063
|
return renderDocComment(parts).map((line) => `${indent}${line}`);
|
|
3426
5064
|
}
|
|
3427
5065
|
|
|
3428
|
-
// Prefer the full `description`; fall back to the one-line `brief` when prose is
|
|
3429
|
-
// absent. Shared by every documented member kind so the summary source is
|
|
3430
|
-
// consistent across functions, constants, variables, and properties.
|
|
3431
|
-
export function summaryFor(brief: string, description: string): string {
|
|
3432
|
-
return description.trim() !== "" ? description : brief;
|
|
3433
|
-
}
|
|
3434
|
-
|
|
3435
5066
|
export function isMarkedOptional(p: ApiParameter): boolean {
|
|
3436
5067
|
return p.isOptional || p.types.includes("nil");
|
|
3437
5068
|
}
|
|
@@ -3445,6 +5076,7 @@ export function isMarkedOptional(p: ApiParameter): boolean {
|
|
|
3445
5076
|
// second copy of the rule. The fidelity audit reads it too, to tell which slots
|
|
3446
5077
|
// the emitted surface still makes required.
|
|
3447
5078
|
export function isDocOptional(p: ApiParameter, elementName: string): boolean {
|
|
5079
|
+
if (requiredSlotCorrection(p, elementName)) return false;
|
|
3448
5080
|
if (isMarkedOptional(p)) return true;
|
|
3449
5081
|
return OPTIONAL_SLOT_CORRECTIONS.has(tableSlotKey(elementName, "param", p.name));
|
|
3450
5082
|
}
|
|
@@ -3472,7 +5104,7 @@ const SCENE_ADDRESS_ALIASES: Readonly<Record<string, string>> = {
|
|
|
3472
5104
|
// becomes the matching scene-derived alias. Wrapping `mapType` rather than
|
|
3473
5105
|
// rewriting the finished union leaves `mapSlotUnion`'s member ordering and
|
|
3474
5106
|
// de-duplication in charge, so only the one token moves.
|
|
3475
|
-
function addressMapType(mapType:
|
|
5107
|
+
function addressMapType(mapType: DefoldTypeMapper, alias: string): (t: string) => string {
|
|
3476
5108
|
return (token) => (token === "string" ? alias : mapType(token));
|
|
3477
5109
|
}
|
|
3478
5110
|
|
|
@@ -3481,17 +5113,20 @@ function addressMapType(mapType: (t: string) => string, alias: string): (t: stri
|
|
|
3481
5113
|
// type alone.
|
|
3482
5114
|
function parameterType(
|
|
3483
5115
|
p: ApiParameter,
|
|
3484
|
-
mapType:
|
|
5116
|
+
mapType: DefoldTypeMapper,
|
|
3485
5117
|
resolver: TableDocResolver,
|
|
3486
5118
|
constantTokens: ConstantSlotTokens,
|
|
3487
5119
|
elementName: string,
|
|
3488
5120
|
urlParameters: UrlParameterTable,
|
|
3489
5121
|
): string {
|
|
3490
5122
|
const concrete = p.types.filter((t) => t !== "nil");
|
|
5123
|
+
// The caller supplies a parameter, so a string-keyed table in it also takes an
|
|
5124
|
+
// object literal.
|
|
5125
|
+
const inputMapType = (token: string): string => mapType(token, "input");
|
|
3491
5126
|
// The table is keyed by the *raw* ref-doc parameter name, not the emitted
|
|
3492
5127
|
// `safeParamName` fallback.
|
|
3493
5128
|
const alias = SCENE_ADDRESS_ALIASES[classifyUrlParameter(urlParameters, elementName, p.name)];
|
|
3494
|
-
const slotMapType = alias === undefined ?
|
|
5129
|
+
const slotMapType = alias === undefined ? inputMapType : addressMapType(inputMapType, alias);
|
|
3495
5130
|
const mapped =
|
|
3496
5131
|
concrete.length > 0
|
|
3497
5132
|
? mapSlotUnion(
|
|
@@ -3507,7 +5142,55 @@ function parameterType(
|
|
|
3507
5142
|
)
|
|
3508
5143
|
: "unknown";
|
|
3509
5144
|
const correction = PARAM_TYPE_CORRECTIONS.get(tableSlotKey(elementName, "param", p.name));
|
|
3510
|
-
|
|
5145
|
+
if (correction === undefined || !pinMatches(p.types, correctionPins(correction))) return mapped;
|
|
5146
|
+
const kept =
|
|
5147
|
+
correction.removes === undefined ? mapped : withoutUnionMembers(mapped, correction.removes);
|
|
5148
|
+
if (correction.adds === undefined) return kept;
|
|
5149
|
+
if (kept === "") return correction.adds;
|
|
5150
|
+
const present = new Set(unionMembers(kept));
|
|
5151
|
+
const added = unionMembers(correction.adds).filter((member) => !present.has(member));
|
|
5152
|
+
return added.length === 0 ? kept : `${kept} | ${added.join(" | ")}`;
|
|
5153
|
+
}
|
|
5154
|
+
|
|
5155
|
+
function isFunctionTypeText(ts: string): boolean {
|
|
5156
|
+
let depth = 0;
|
|
5157
|
+
for (let i = 0; i < ts.length - 1; i++) {
|
|
5158
|
+
const c = ts[i];
|
|
5159
|
+
if (c === "(" || c === "[" || c === "{" || c === "<") depth++;
|
|
5160
|
+
else if (c === ")" || c === "]" || c === "}" || (c === ">" && ts[i - 1] !== "=")) depth--;
|
|
5161
|
+
else if (c === "=" && ts[i + 1] === ">" && depth === 0) return true;
|
|
5162
|
+
}
|
|
5163
|
+
return false;
|
|
5164
|
+
}
|
|
5165
|
+
|
|
5166
|
+
// The top-level members of a rendered union, leaving nested unions inside
|
|
5167
|
+
// brackets, braces or generics whole.
|
|
5168
|
+
function unionMembers(union: string): string[] {
|
|
5169
|
+
const members: string[] = [];
|
|
5170
|
+
let depth = 0;
|
|
5171
|
+
let start = 0;
|
|
5172
|
+
for (let i = 0; i < union.length; i++) {
|
|
5173
|
+
const c = union[i];
|
|
5174
|
+
if (c === "(" || c === "[" || c === "{" || c === "<") depth++;
|
|
5175
|
+
else if (c === ")" || c === "]" || c === "}" || (c === ">" && union[i - 1] !== "=")) depth--;
|
|
5176
|
+
else if (c === "|" && depth === 0) {
|
|
5177
|
+
members.push(union.slice(start, i).trim());
|
|
5178
|
+
start = i + 1;
|
|
5179
|
+
}
|
|
5180
|
+
}
|
|
5181
|
+
members.push(union.slice(start).trim());
|
|
5182
|
+
return members;
|
|
5183
|
+
}
|
|
5184
|
+
|
|
5185
|
+
// Drops whole top-level members from a rendered union. Called only once the
|
|
5186
|
+
// slot's pin matched, so a removal that finds nothing is a real defect.
|
|
5187
|
+
function withoutUnionMembers(union: string, removed: readonly string[]): string {
|
|
5188
|
+
const members = unionMembers(union);
|
|
5189
|
+
const kept = members.filter((member) => !removed.includes(member));
|
|
5190
|
+
if (kept.length === members.length) {
|
|
5191
|
+
throw new Error(`withoutUnionMembers: none of ${removed.join(", ")} is a member of ${union}`);
|
|
5192
|
+
}
|
|
5193
|
+
return kept.join(" | ");
|
|
3511
5194
|
}
|
|
3512
5195
|
|
|
3513
5196
|
const VARARG_PREFIX = "...";
|
|
@@ -3536,7 +5219,7 @@ function emittedParamName(p: ApiParameter, index: number): string {
|
|
|
3536
5219
|
function emitRestParameter(
|
|
3537
5220
|
params: readonly ApiParameter[],
|
|
3538
5221
|
varargIndex: number,
|
|
3539
|
-
mapType:
|
|
5222
|
+
mapType: DefoldTypeMapper,
|
|
3540
5223
|
resolver: TableDocResolver,
|
|
3541
5224
|
constantTokens: ConstantSlotTokens,
|
|
3542
5225
|
elementName: string,
|
|
@@ -3570,25 +5253,29 @@ function emitParameter(
|
|
|
3570
5253
|
p: ApiParameter,
|
|
3571
5254
|
index: number,
|
|
3572
5255
|
optional: boolean,
|
|
3573
|
-
mapType:
|
|
5256
|
+
mapType: DefoldTypeMapper,
|
|
3574
5257
|
resolver: TableDocResolver,
|
|
3575
5258
|
constantTokens: ConstantSlotTokens,
|
|
3576
5259
|
elementName: string,
|
|
3577
5260
|
urlParameters: UrlParameterTable,
|
|
3578
|
-
): { text: string;
|
|
5261
|
+
): { text: string; type: string; optional: boolean } {
|
|
3579
5262
|
const name = safeParamName(p.name, index);
|
|
3580
5263
|
const ts = parameterType(p, mapType, resolver, constantTokens, elementName, urlParameters);
|
|
3581
5264
|
// An interior doc-optional param (a required param follows, so the trailing-`?`
|
|
3582
5265
|
// projection cannot mark it) keeps its optionality as `| undefined` — TSTL
|
|
3583
5266
|
// lowers `undefined` to `nil`, the faithful call. Trailing optionals keep the
|
|
3584
5267
|
// `?` form; required params are untouched.
|
|
3585
|
-
const
|
|
3586
|
-
|
|
5268
|
+
const nilRequired = requiredSlotCorrection(p, elementName)?.nil === true;
|
|
5269
|
+
const interiorOptional = !optional && (isDocOptional(p, elementName) || nilRequired);
|
|
5270
|
+
// `(a) => void | undefined` reads as a function returning `void | undefined`.
|
|
5271
|
+
const operand = interiorOptional && isFunctionTypeText(ts) ? `(${ts})` : ts;
|
|
5272
|
+
const type = `${operand}${interiorOptional ? " | undefined" : ""}`;
|
|
5273
|
+
return { text: `${name}${optional ? "?" : ""}: ${type}`, type, optional };
|
|
3587
5274
|
}
|
|
3588
5275
|
|
|
3589
5276
|
function emitReturn(
|
|
3590
5277
|
returnValues: ApiParameter[],
|
|
3591
|
-
mapType:
|
|
5278
|
+
mapType: DefoldTypeMapper,
|
|
3592
5279
|
resolver: TableDocResolver,
|
|
3593
5280
|
constantTokens: ConstantSlotTokens,
|
|
3594
5281
|
elementName: string,
|
|
@@ -3610,19 +5297,23 @@ function emitReturn(
|
|
|
3610
5297
|
position: index,
|
|
3611
5298
|
name: rv.name,
|
|
3612
5299
|
ts:
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
5300
|
+
correction?.slot !== undefined &&
|
|
5301
|
+
correction.slot === rv.name &&
|
|
5302
|
+
pinMatches(rv.types, correctionPins(correction))
|
|
5303
|
+
? correction.ts
|
|
5304
|
+
: rv.types.length > 0
|
|
5305
|
+
? mapSlotUnion(
|
|
5306
|
+
rv.types,
|
|
5307
|
+
rv.doc,
|
|
5308
|
+
mapType,
|
|
5309
|
+
false,
|
|
5310
|
+
resolver,
|
|
5311
|
+
constantTokens,
|
|
5312
|
+
elementName,
|
|
5313
|
+
"return",
|
|
5314
|
+
rv.name,
|
|
5315
|
+
)
|
|
5316
|
+
: "unknown",
|
|
3626
5317
|
}));
|
|
3627
5318
|
return {
|
|
3628
5319
|
type: `LuaMultiReturn<[${emitted.map((slot) => slot.ts).join(", ")}]>`,
|
|
@@ -3632,7 +5323,11 @@ function emitReturn(
|
|
|
3632
5323
|
}
|
|
3633
5324
|
const first = returnValues[0];
|
|
3634
5325
|
if (!first) return { type: "void", trailing: "", slots: [] };
|
|
3635
|
-
if (
|
|
5326
|
+
if (
|
|
5327
|
+
correction !== undefined &&
|
|
5328
|
+
correction.slot === undefined &&
|
|
5329
|
+
pinMatches(first.types, correctionPins(correction))
|
|
5330
|
+
) {
|
|
3636
5331
|
return {
|
|
3637
5332
|
type: correction.ts,
|
|
3638
5333
|
trailing: "",
|
|
@@ -3653,12 +5348,22 @@ function emitReturn(
|
|
|
3653
5348
|
first.name,
|
|
3654
5349
|
)
|
|
3655
5350
|
: "unknown";
|
|
5351
|
+
// A LuaLS vararg return (`...`) is every value the call returns, however many.
|
|
5352
|
+
if (first.name === "...") {
|
|
5353
|
+
const type = `LuaMultiReturn<${needsArrayParens(ts) ? `(${ts})` : ts}[]>`;
|
|
5354
|
+
return { type, trailing: "", slots: [{ position: 0, name: first.name, ts }] };
|
|
5355
|
+
}
|
|
3656
5356
|
return { type: ts, trailing: "", slots: [{ position: 0, name: first.name, ts }] };
|
|
3657
5357
|
}
|
|
3658
5358
|
|
|
5359
|
+
// The mark upstream opens a property's description with when the engine refuses
|
|
5360
|
+
// to write it: `go.set` raises "Unable to set the property ... since it is read
|
|
5361
|
+
// only".
|
|
5362
|
+
const READ_ONLY_MARK = /^\s*<span class="mark">READ ONLY<\/span>/;
|
|
5363
|
+
|
|
3659
5364
|
function emitPropertyMembers(
|
|
3660
5365
|
p: ApiProperty,
|
|
3661
|
-
mapType:
|
|
5366
|
+
mapType: DefoldTypeMapper,
|
|
3662
5367
|
namespace: string,
|
|
3663
5368
|
): readonly string[] {
|
|
3664
5369
|
const keyRange = PROPERTY_KEY_RANGES.get(`${namespace}.${p.name}`);
|
|
@@ -3671,12 +5376,13 @@ function emitPropertyMembers(
|
|
|
3671
5376
|
}
|
|
3672
5377
|
const correction = PROPERTY_TYPE_CORRECTIONS.get(`${namespace}.${p.name}`);
|
|
3673
5378
|
const ts =
|
|
3674
|
-
correction !== undefined
|
|
5379
|
+
correction !== undefined && pinMatches(p.types, propertyCorrectionPins(correction))
|
|
3675
5380
|
? correction.ts
|
|
3676
5381
|
: p.types.length > 0
|
|
3677
5382
|
? unionFromTokens(p.types, mapType)
|
|
3678
5383
|
: "unknown";
|
|
3679
|
-
|
|
5384
|
+
const modifier = READ_ONLY_MARK.test(p.description) ? "readonly " : "";
|
|
5385
|
+
return keys.map((key) => `${modifier}${key}: ${ts};`);
|
|
3680
5386
|
}
|
|
3681
5387
|
|
|
3682
5388
|
// Like `unionFromTokens`, but a `table` token whose slot doc carries a parseable
|
|
@@ -3689,7 +5395,7 @@ function emitPropertyMembers(
|
|
|
3689
5395
|
function mapSlotUnion(
|
|
3690
5396
|
types: readonly string[],
|
|
3691
5397
|
doc: string,
|
|
3692
|
-
mapType:
|
|
5398
|
+
mapType: DefoldTypeMapper,
|
|
3693
5399
|
optionalFields: boolean,
|
|
3694
5400
|
resolver: TableDocResolver,
|
|
3695
5401
|
constantTokens: ConstantSlotTokens,
|
|
@@ -3713,11 +5419,12 @@ function mapSlotUnion(
|
|
|
3713
5419
|
}
|
|
3714
5420
|
}
|
|
3715
5421
|
let ts: string;
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
5422
|
+
const slotCuration =
|
|
5423
|
+
slotKind !== undefined && slotName !== undefined
|
|
5424
|
+
? TABLE_SLOT_CURATIONS.get(tableSlotKey(elementName, slotKind, slotName))
|
|
5425
|
+
: undefined;
|
|
5426
|
+
if (token === "table" || slotCuration?.retypedUpstream?.includes(token) === true) {
|
|
5427
|
+
const curation = slotCuration;
|
|
3721
5428
|
const mapping =
|
|
3722
5429
|
curation?.kind === "mapping" ? curation : MAPPING_TABLE_SLOTS.get(elementName);
|
|
3723
5430
|
const element =
|
|
@@ -3758,8 +5465,16 @@ function mapSlotUnion(
|
|
|
3758
5465
|
}
|
|
3759
5466
|
} else if (element !== undefined) {
|
|
3760
5467
|
ts = arrayTypeFromTokens(element, mapType);
|
|
5468
|
+
} else if (curation?.kind === "verbatim") {
|
|
5469
|
+
ts = curation.ts;
|
|
3761
5470
|
} else if (curation?.kind === "object" || curation?.kind === "array-object") {
|
|
3762
|
-
const
|
|
5471
|
+
const fields = applyRequiredFieldCorrections(
|
|
5472
|
+
elementName,
|
|
5473
|
+
slotKind,
|
|
5474
|
+
slotName,
|
|
5475
|
+
curation.fields,
|
|
5476
|
+
);
|
|
5477
|
+
const object = inlineTableType(fields, mapType, optionalFields);
|
|
3763
5478
|
ts = curation.kind === "array-object" ? `${object}[]` : object;
|
|
3764
5479
|
} else if (curation?.kind === "keyed-object") {
|
|
3765
5480
|
// The doc's `<dl>` describes the value shape; parse it and re-key by an
|
|
@@ -3785,7 +5500,13 @@ function mapSlotUnion(
|
|
|
3785
5500
|
const added = applyFieldAdditions(elementName, slotKind, slotName, parsed);
|
|
3786
5501
|
const nested = applyNestedFieldCurations(elementName, slotKind, slotName, added);
|
|
3787
5502
|
const typed = applyFieldTypeOverrides(elementName, slotKind, slotName, nested);
|
|
3788
|
-
const
|
|
5503
|
+
const required = applyRequiredFieldCorrections(
|
|
5504
|
+
elementName,
|
|
5505
|
+
slotKind,
|
|
5506
|
+
slotName,
|
|
5507
|
+
applyFieldOptionalityCorrections(elementName, slotKind, slotName, typed),
|
|
5508
|
+
);
|
|
5509
|
+
const fields = applyListFieldCorrections(elementName, slotKind, slotName, required);
|
|
3789
5510
|
const object = inlineTableType(fields, mapType, optionalFields);
|
|
3790
5511
|
ts = isSlotLevelList(doc) ? `${object}[]` : object;
|
|
3791
5512
|
} else {
|
|
@@ -3840,18 +5561,21 @@ export function applyNestedFieldCurations(
|
|
|
3840
5561
|
): TableField[] {
|
|
3841
5562
|
if (slotKind === undefined || slotName === undefined) return [...fields];
|
|
3842
5563
|
return fields.map((field) => {
|
|
3843
|
-
if (field.fields !== undefined) return field;
|
|
3844
5564
|
const curated = NESTED_FIELD_CURATIONS.get(
|
|
3845
5565
|
nestedFieldKey(elementName, slotKind, slotName, field.name),
|
|
3846
5566
|
);
|
|
3847
5567
|
if (curated === undefined) return field;
|
|
3848
|
-
|
|
5568
|
+
// A parsed member list keeps its members; the curation supplies the ones
|
|
5569
|
+
// upstream flattened to the slot's top level.
|
|
5570
|
+
const parsed = field.fields ?? [];
|
|
5571
|
+
const missing = curated.filter((member) => !parsed.some((p) => p.name === member.name));
|
|
5572
|
+
return { ...field, fields: [...parsed, ...missing], isList: true };
|
|
3849
5573
|
});
|
|
3850
5574
|
}
|
|
3851
5575
|
|
|
3852
5576
|
function arrayTypeFromTokens(
|
|
3853
5577
|
element: string | readonly string[],
|
|
3854
|
-
mapType:
|
|
5578
|
+
mapType: DefoldTypeMapper,
|
|
3855
5579
|
): string {
|
|
3856
5580
|
const tokens = typeof element === "string" ? [element] : element;
|
|
3857
5581
|
return tokens.length > 1
|
|
@@ -3861,7 +5585,7 @@ function arrayTypeFromTokens(
|
|
|
3861
5585
|
|
|
3862
5586
|
export function inlineTableType(
|
|
3863
5587
|
fields: readonly TableField[],
|
|
3864
|
-
mapType:
|
|
5588
|
+
mapType: DefoldTypeMapper,
|
|
3865
5589
|
optionalFields: boolean,
|
|
3866
5590
|
): string {
|
|
3867
5591
|
const members = fields.map((field) => {
|
|
@@ -3879,7 +5603,7 @@ export function inlineTableType(
|
|
|
3879
5603
|
field.tsType !== undefined
|
|
3880
5604
|
? field.tsType
|
|
3881
5605
|
: field.fields !== undefined
|
|
3882
|
-
?
|
|
5606
|
+
? nestedFieldType(field, inlineTableType(field.fields, mapType, optionalFields))
|
|
3883
5607
|
: field.numberList === true
|
|
3884
5608
|
? "number[]"
|
|
3885
5609
|
: field.types.length > 0
|
|
@@ -3888,12 +5612,21 @@ export function inlineTableType(
|
|
|
3888
5612
|
// A field is optional when the whole slot is param-side (optionalFields) or
|
|
3889
5613
|
// when the curation marks that individual field optional (a return-side
|
|
3890
5614
|
// variant field present only for some shape kinds, e.g. a b2d shape table).
|
|
3891
|
-
|
|
5615
|
+
// A param-side field the engine requires stays required.
|
|
5616
|
+
const optional = (optionalFields && field.required !== true) || field.optional === true;
|
|
5617
|
+
return `${key}${optional ? "?" : ""}: ${ts}`;
|
|
3892
5618
|
});
|
|
3893
5619
|
return `{ ${members.join("; ")} }`;
|
|
3894
5620
|
}
|
|
3895
5621
|
|
|
3896
|
-
function
|
|
5622
|
+
function nestedFieldType(field: TableField, object: string): string {
|
|
5623
|
+
const element = field.alternatives === undefined ? object : `${object} & (${field.alternatives})`;
|
|
5624
|
+
if (field.isList !== true) return element;
|
|
5625
|
+
const rest = field.alternatives === undefined ? element : `(${element})`;
|
|
5626
|
+
return field.nonEmpty === true ? `[${element}, ...${rest}[]]` : `${rest}[]`;
|
|
5627
|
+
}
|
|
5628
|
+
|
|
5629
|
+
function unionFromTokens(tokens: readonly string[], mapType: DefoldTypeMapper): string {
|
|
3897
5630
|
const mapped: string[] = [];
|
|
3898
5631
|
const seen = new Set<string>();
|
|
3899
5632
|
for (const token of tokens) {
|
|
@@ -3905,7 +5638,7 @@ function unionFromTokens(tokens: readonly string[], mapType: (t: string) => stri
|
|
|
3905
5638
|
return joinUnionArms(mapped);
|
|
3906
5639
|
}
|
|
3907
5640
|
|
|
3908
|
-
const BRAND_ARM = /^number & \{ readonly __brand: "([^"]+)" \}$/;
|
|
5641
|
+
const BRAND_ARM = /^(?:number|string) & \{ readonly __brand: "([^"]+)" \}$/;
|
|
3909
5642
|
|
|
3910
5643
|
// Joins already-mapped arms, collapsing the branded ones to their alias when
|
|
3911
5644
|
// they are exactly a `CONSTANT_UNION_ALIASES` member set. The alias takes the
|
|
@@ -3934,20 +5667,70 @@ function joinUnionArms(mapped: readonly string[]): string {
|
|
|
3934
5667
|
return out.join(" | ");
|
|
3935
5668
|
}
|
|
3936
5669
|
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
5670
|
+
const BUILTIN_MESSAGE_NAMES: ReadonlySet<string> = new Set(
|
|
5671
|
+
messagesDoc.messages.map((message) => message.name),
|
|
5672
|
+
);
|
|
5673
|
+
const MESSAGE_REFERENCE = /^message\.[a-z_]+\.([a-z_]+)$/;
|
|
5674
|
+
|
|
5675
|
+
// The leaf vocabulary the LuaLS grammar resolves an engine type expression
|
|
5676
|
+
// against. `message.<ns>.<name>` names a documented message payload, typed only
|
|
5677
|
+
// when `<name>` is one `BuiltinMessages` declares.
|
|
5678
|
+
function resolveDefoldLeaf(leaf: string): string | undefined {
|
|
5679
|
+
if (leaf === "nil") return "undefined";
|
|
5680
|
+
if (Object.hasOwn(DEFOLD_TYPE_MAP, leaf)) return DEFOLD_TYPE_MAP[leaf];
|
|
5681
|
+
const message = MESSAGE_REFERENCE.exec(leaf);
|
|
5682
|
+
const name = message?.[1];
|
|
5683
|
+
if (name !== undefined && BUILTIN_MESSAGE_NAMES.has(name)) return `BuiltinMessages["${name}"]`;
|
|
5684
|
+
return undefined;
|
|
3943
5685
|
}
|
|
3944
5686
|
|
|
3945
|
-
|
|
5687
|
+
// Whole-token lookup and callback recovery run first, so the grammar sees only
|
|
5688
|
+
// what they miss; every pre-1.13.2 token resolves before it is reached.
|
|
5689
|
+
function wholeTokenMapping(token: string): string | undefined {
|
|
3946
5690
|
if (Object.hasOwn(DEFOLD_TYPE_MAP, token)) {
|
|
3947
5691
|
const mapped = DEFOLD_TYPE_MAP[token];
|
|
3948
5692
|
if (typeof mapped === "string") return mapped;
|
|
3949
5693
|
}
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
5694
|
+
return recoverCallbackSignature(token) ?? undefined;
|
|
5695
|
+
}
|
|
5696
|
+
|
|
5697
|
+
/** The leaves of `token` that resolve to nothing; empty when the whole token maps. */
|
|
5698
|
+
export function unknownDefoldTypeLeaves(token: string): string[] {
|
|
5699
|
+
if (token === "nil" || wholeTokenMapping(token) !== undefined) return [];
|
|
5700
|
+
if (token.trim() === "") return [token];
|
|
5701
|
+
return mapLualsExpression(token, resolveDefoldLeaf).unknowns;
|
|
5702
|
+
}
|
|
5703
|
+
|
|
5704
|
+
export function isKnownDefoldTypeToken(token: string): boolean {
|
|
5705
|
+
return unknownDefoldTypeLeaves(token).length === 0;
|
|
5706
|
+
}
|
|
5707
|
+
|
|
5708
|
+
export function defaultMapType(token: string, position?: LualsPosition): string {
|
|
5709
|
+
return wholeTokenMapping(token) ?? mapLualsExpression(token, resolveDefoldLeaf, position).ts;
|
|
5710
|
+
}
|
|
5711
|
+
|
|
5712
|
+
/**
|
|
5713
|
+
* The engine mapper extended with `leaves`, resolved ahead of the engine
|
|
5714
|
+
* vocabulary, and `declared` (Lua name -> TS path), consulted after it. A whole
|
|
5715
|
+
* token checks `leaves`, the engine's whole-token mapping, then `declared`; a
|
|
5716
|
+
* leaf inside a composite checks `leaves`, the engine leaves, then `declared`.
|
|
5717
|
+
* So a surface's handle brand (`editor.message`) outranks any declared path.
|
|
5718
|
+
*/
|
|
5719
|
+
export function mapTypeWithVocabulary(vocabulary: {
|
|
5720
|
+
leaves?: Readonly<Record<string, string>> | undefined;
|
|
5721
|
+
declared?: ReadonlyMap<string, string> | undefined;
|
|
5722
|
+
}): DefoldTypeMapper {
|
|
5723
|
+
const leaves = vocabulary.leaves ?? {};
|
|
5724
|
+
const declared = vocabulary.declared ?? new Map<string, string>();
|
|
5725
|
+
if (Object.keys(leaves).length === 0 && declared.size === 0) return defaultMapType;
|
|
5726
|
+
const resolveLeaf = (leaf: string): string | undefined =>
|
|
5727
|
+
Object.hasOwn(leaves, leaf) ? leaves[leaf] : (resolveDefoldLeaf(leaf) ?? declared.get(leaf));
|
|
5728
|
+
return (token, position) => {
|
|
5729
|
+
if (Object.hasOwn(leaves, token)) return leaves[token] as string;
|
|
5730
|
+
return (
|
|
5731
|
+
wholeTokenMapping(token) ??
|
|
5732
|
+
declared.get(token) ??
|
|
5733
|
+
mapLualsExpression(token, resolveLeaf, position).ts
|
|
5734
|
+
);
|
|
5735
|
+
};
|
|
3953
5736
|
}
|