@defold-typescript/types 0.5.5 → 0.6.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-targets.json +1 -1
- package/generated/b2d.d.ts +3 -0
- package/generated/buffer.d.ts +44 -38
- package/generated/builtin-messages.d.ts +1 -1
- package/generated/camera.d.ts +3 -0
- package/generated/collectionfactory.d.ts +47 -40
- package/generated/collectionproxy.d.ts +23 -18
- package/generated/crash.d.ts +3 -0
- package/generated/factory.d.ts +32 -24
- package/generated/go.d.ts +123 -124
- package/generated/graphics.d.ts +3 -0
- package/generated/gui.d.ts +303 -283
- package/generated/http.d.ts +26 -16
- package/generated/iac.d.ts +3 -0
- package/generated/iap.d.ts +6 -3
- package/generated/image.d.ts +30 -26
- package/generated/json.d.ts +36 -32
- package/generated/kinds/gui-script.d.ts +7 -5
- package/generated/kinds/render-script.d.ts +7 -5
- package/generated/kinds/script.d.ts +7 -5
- package/generated/label.d.ts +16 -9
- package/generated/liveupdate.d.ts +29 -26
- package/generated/model.d.ts +57 -45
- package/generated/msg.d.ts +3 -0
- package/generated/particlefx.d.ts +50 -34
- package/generated/physics.d.ts +153 -133
- package/generated/profiler.d.ts +45 -41
- package/generated/push.d.ts +5 -2
- package/generated/render.d.ts +410 -349
- package/generated/resource.d.ts +619 -572
- package/generated/socket.d.ts +49 -33
- package/generated/sound.d.ts +83 -72
- package/generated/sprite.d.ts +3 -0
- package/generated/sys.d.ts +198 -189
- package/generated/tilemap.d.ts +43 -39
- package/generated/timer.d.ts +42 -36
- package/generated/vmath.d.ts +22 -0
- package/generated/webview.d.ts +3 -0
- package/generated/window.d.ts +23 -17
- package/generated/zlib.d.ts +15 -12
- package/index.d.ts +3 -1
- package/package.json +6 -2
- package/scripts/fidelity-audit.ts +61 -1
- package/scripts/fidelity-baseline.json +10 -10
- package/scripts/ref-doc-delta.ts +143 -0
- package/scripts/regen.ts +18 -9
- package/src/core-types.ts +14 -0
- package/src/emit-dts.ts +219 -13
- package/src/engine-globals.d.ts +2 -0
- package/src/go-overloads.d.ts +43 -0
- package/src/index.ts +5 -0
- package/src/lifecycle.ts +157 -16
- package/src/publish-dts.ts +1 -1
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { parseDefoldApiDoc } from "../src/api-doc";
|
|
2
|
+
import {
|
|
3
|
+
type ApiTarget,
|
|
4
|
+
loadApiTargets,
|
|
5
|
+
type ResolveTargetOptions,
|
|
6
|
+
resolveTargetModules,
|
|
7
|
+
} from "./regen";
|
|
8
|
+
|
|
9
|
+
export interface RefDocDeltaArgs {
|
|
10
|
+
target: string;
|
|
11
|
+
namespace: string;
|
|
12
|
+
present: string[];
|
|
13
|
+
absent: string[];
|
|
14
|
+
json: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface RefDocDeltaReport {
|
|
18
|
+
ok: boolean;
|
|
19
|
+
targetId: string;
|
|
20
|
+
version: string;
|
|
21
|
+
namespace: string;
|
|
22
|
+
provenance: string | null;
|
|
23
|
+
missingPresent: string[];
|
|
24
|
+
unexpectedPresent: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface VerifyRefDocDeltaInput {
|
|
28
|
+
target: ApiTarget;
|
|
29
|
+
namespace: string;
|
|
30
|
+
present: readonly string[];
|
|
31
|
+
absent: readonly string[];
|
|
32
|
+
resolveOpts?: ResolveTargetOptions;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function collectElementNames(doc: unknown): Set<string> {
|
|
36
|
+
const module = parseDefoldApiDoc(doc);
|
|
37
|
+
const names = new Set<string>([module.namespace]);
|
|
38
|
+
for (const collection of [
|
|
39
|
+
module.functions,
|
|
40
|
+
module.constants,
|
|
41
|
+
module.variables,
|
|
42
|
+
module.properties,
|
|
43
|
+
]) {
|
|
44
|
+
for (const item of collection) {
|
|
45
|
+
if (item.name.length > 0) names.add(item.name);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return names;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function parseRefDocDeltaArgs(argv: readonly string[]): RefDocDeltaArgs {
|
|
52
|
+
const args: RefDocDeltaArgs = { target: "", namespace: "", present: [], absent: [], json: false };
|
|
53
|
+
for (let i = 0; i < argv.length; i++) {
|
|
54
|
+
const arg = argv[i];
|
|
55
|
+
if (arg === "--json") {
|
|
56
|
+
args.json = true;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (arg === "--target" || arg === "--namespace" || arg === "--present" || arg === "--absent") {
|
|
60
|
+
const value = argv[++i];
|
|
61
|
+
if (!value) throw new Error(`${arg} requires a value`);
|
|
62
|
+
if (arg === "--target") args.target = value;
|
|
63
|
+
else if (arg === "--namespace") args.namespace = value;
|
|
64
|
+
else if (arg === "--present") args.present.push(value);
|
|
65
|
+
else args.absent.push(value);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
throw new Error(`unknown argument: ${arg}`);
|
|
69
|
+
}
|
|
70
|
+
if (!args.target) throw new Error("--target is required");
|
|
71
|
+
if (!args.namespace) throw new Error("--namespace is required");
|
|
72
|
+
if (args.present.length === 0 && args.absent.length === 0) {
|
|
73
|
+
throw new Error("at least one --present or --absent assertion is required");
|
|
74
|
+
}
|
|
75
|
+
return args;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function selectRefDocDeltaTarget(
|
|
79
|
+
targets: readonly ApiTarget[],
|
|
80
|
+
targetId: string,
|
|
81
|
+
): ApiTarget {
|
|
82
|
+
const target = targets.find((item) => item.id === targetId);
|
|
83
|
+
if (!target) throw new Error(`target "${targetId}" not found`);
|
|
84
|
+
if ((target.source ?? null)?.kind !== "ref-doc") {
|
|
85
|
+
throw new Error(`target "${targetId}" is not ref-doc sourced`);
|
|
86
|
+
}
|
|
87
|
+
return target;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function verifyRefDocDelta(input: VerifyRefDocDeltaInput): Promise<RefDocDeltaReport> {
|
|
91
|
+
const source = input.target.source ?? null;
|
|
92
|
+
if (source?.kind !== "ref-doc") {
|
|
93
|
+
throw new Error(`target "${input.target.id}" is not ref-doc sourced`);
|
|
94
|
+
}
|
|
95
|
+
const modules = await resolveTargetModules(input.target, input.resolveOpts);
|
|
96
|
+
const module = modules.find((entry) => entry.namespace === input.namespace);
|
|
97
|
+
if (!module) {
|
|
98
|
+
throw new Error(`target "${input.target.id}" has no namespace "${input.namespace}"`);
|
|
99
|
+
}
|
|
100
|
+
const names = collectElementNames(module.doc);
|
|
101
|
+
const missingPresent = input.present.filter((name) => !names.has(name));
|
|
102
|
+
const unexpectedPresent = input.absent.filter((name) => names.has(name));
|
|
103
|
+
return {
|
|
104
|
+
ok: missingPresent.length === 0 && unexpectedPresent.length === 0,
|
|
105
|
+
targetId: input.target.id,
|
|
106
|
+
version: source.version,
|
|
107
|
+
namespace: input.namespace,
|
|
108
|
+
provenance: module.sourceProvenance ?? null,
|
|
109
|
+
missingPresent,
|
|
110
|
+
unexpectedPresent,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function renderPlainReport(report: RefDocDeltaReport): string {
|
|
115
|
+
const lines = [
|
|
116
|
+
`${report.ok ? "ok" : "drift"}: ${report.targetId}/${report.namespace} (${report.version}, ${report.provenance ?? "unknown"})`,
|
|
117
|
+
];
|
|
118
|
+
if (report.missingPresent.length > 0) {
|
|
119
|
+
lines.push(`missing expected present: ${report.missingPresent.join(", ")}`);
|
|
120
|
+
}
|
|
121
|
+
if (report.unexpectedPresent.length > 0) {
|
|
122
|
+
lines.push(`unexpected expected absent: ${report.unexpectedPresent.join(", ")}`);
|
|
123
|
+
}
|
|
124
|
+
return lines.join("\n");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (import.meta.main) {
|
|
128
|
+
try {
|
|
129
|
+
const args = parseRefDocDeltaArgs(process.argv.slice(2));
|
|
130
|
+
const target = selectRefDocDeltaTarget(loadApiTargets(), args.target);
|
|
131
|
+
const report = await verifyRefDocDelta({
|
|
132
|
+
target,
|
|
133
|
+
namespace: args.namespace,
|
|
134
|
+
present: args.present,
|
|
135
|
+
absent: args.absent,
|
|
136
|
+
});
|
|
137
|
+
console.log(args.json ? JSON.stringify(report, null, 2) : renderPlainReport(report));
|
|
138
|
+
if (!report.ok) process.exitCode = 1;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
141
|
+
process.exitCode = 1;
|
|
142
|
+
}
|
|
143
|
+
}
|
package/scripts/regen.ts
CHANGED
|
@@ -6,7 +6,12 @@ import { emitDeclarations } from "../src/emit-dts";
|
|
|
6
6
|
import { emitBuiltinMessages, parseMessagesDoc } from "../src/emit-messages";
|
|
7
7
|
import type { TranslationStore } from "../src/example-store";
|
|
8
8
|
import { wrapAsAmbientGlobal } from "../src/publish-dts";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
type DocSourceProvenance,
|
|
11
|
+
type DownloadRefDoc,
|
|
12
|
+
refDocCacheDir,
|
|
13
|
+
resolveRefDoc,
|
|
14
|
+
} from "./doc-source";
|
|
10
15
|
import { loadTranslations } from "./example-store-io";
|
|
11
16
|
import { type readZip, SYNC_MANIFEST, type SyncManifestEntry } from "./sync-api-docs";
|
|
12
17
|
|
|
@@ -75,6 +80,7 @@ export interface ModuleManifestEntry {
|
|
|
75
80
|
readonly outFile: string;
|
|
76
81
|
readonly skipFunctions?: readonly string[];
|
|
77
82
|
readonly importsFrom?: string;
|
|
83
|
+
readonly sourceProvenance?: DocSourceProvenance;
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
export interface ResolveTargetOptions {
|
|
@@ -97,7 +103,7 @@ export async function resolveTargetModules(
|
|
|
97
103
|
if (source == null) {
|
|
98
104
|
return loadTargetModules(target, opts.packageRoot);
|
|
99
105
|
}
|
|
100
|
-
const { zip } = await resolveRefDoc({
|
|
106
|
+
const { zip, provenance } = await resolveRefDoc({
|
|
101
107
|
version: source.version,
|
|
102
108
|
cacheDir: opts.cacheDir ?? refDocCacheDir(),
|
|
103
109
|
...(opts.download ? { download: opts.download } : {}),
|
|
@@ -116,6 +122,7 @@ export async function resolveTargetModules(
|
|
|
116
122
|
doc: JSON.parse(zip.read(sync.zipEntry)),
|
|
117
123
|
outFile: module.outFile,
|
|
118
124
|
importsFrom: target.coreTypesImport,
|
|
125
|
+
sourceProvenance: provenance,
|
|
119
126
|
};
|
|
120
127
|
return module.skipFunctions ? { ...entry, skipFunctions: module.skipFunctions } : entry;
|
|
121
128
|
});
|
|
@@ -212,6 +219,7 @@ export const RESTRICTED_NAMESPACES: Readonly<Record<string, string>> = {
|
|
|
212
219
|
|
|
213
220
|
const UNIVERSAL_EXTRA_IMPORTS: readonly string[] = [
|
|
214
221
|
"../builtin-messages",
|
|
222
|
+
"../../src/engine-globals",
|
|
215
223
|
"../../src/msg-overloads",
|
|
216
224
|
"../../src/message-guard",
|
|
217
225
|
"../../src/go-overloads",
|
|
@@ -220,12 +228,13 @@ const UNIVERSAL_EXTRA_IMPORTS: readonly string[] = [
|
|
|
220
228
|
export interface KindManifestEntry {
|
|
221
229
|
readonly kind: string;
|
|
222
230
|
readonly restricted?: string;
|
|
231
|
+
readonly factory: string;
|
|
223
232
|
}
|
|
224
233
|
|
|
225
234
|
export const KIND_MODULE_MANIFEST: readonly KindManifestEntry[] = [
|
|
226
|
-
{ kind: "script" },
|
|
227
|
-
{ kind: "gui-script", restricted: "gui" },
|
|
228
|
-
{ kind: "render-script", restricted: "render" },
|
|
235
|
+
{ kind: "script", factory: "defineScript" },
|
|
236
|
+
{ kind: "gui-script", restricted: "gui", factory: "defineGuiScript" },
|
|
237
|
+
{ kind: "render-script", restricted: "render", factory: "defineRenderScript" },
|
|
229
238
|
];
|
|
230
239
|
|
|
231
240
|
export function generateKindIndex(kind: string): string {
|
|
@@ -234,11 +243,11 @@ export function generateKindIndex(kind: string): string {
|
|
|
234
243
|
const universalNamespaces = MODULE_MANIFEST.filter(
|
|
235
244
|
(m) => !Object.hasOwn(RESTRICTED_NAMESPACES, m.namespace),
|
|
236
245
|
).map((m) => `../${m.outFile.replace(/\.d\.ts$/, "")}`);
|
|
237
|
-
const lines = [
|
|
238
|
-
.sort()
|
|
239
|
-
|
|
246
|
+
const lines = [
|
|
247
|
+
...new Set([...universalNamespaces.sort(), ...[...UNIVERSAL_EXTRA_IMPORTS].sort()]),
|
|
248
|
+
].map((path) => `import "${path}";`);
|
|
240
249
|
if (entry.restricted) lines.push(`import "../${entry.restricted}";`);
|
|
241
|
-
return `${lines.join("\n")}\n\nexport {};\n`;
|
|
250
|
+
return `${lines.join("\n")}\n\nexport { ${entry.factory} } from "../../src/lifecycle";\nexport type { ScriptProperties, ScriptProperty } from "../../src/lifecycle";\n`;
|
|
242
251
|
}
|
|
243
252
|
|
|
244
253
|
export function generateVersionIndex(
|
package/src/core-types.ts
CHANGED
|
@@ -78,6 +78,20 @@ export interface Hash {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
declare const OpaqueBrand: unique symbol;
|
|
81
|
+
/**
|
|
82
|
+
* A nominal handle to an engine value you may hold and pass back to the API but
|
|
83
|
+
* must never inspect or construct — a Defold `node`, `texture`, `render_target`,
|
|
84
|
+
* `userdata`, etc. The `Name` parameter mints a distinct, mutually-incompatible
|
|
85
|
+
* brand per kind, so TypeScript's structural typing can't silently swap a
|
|
86
|
+
* `render_target` for a `constant`, and a plain object can't stand in for either.
|
|
87
|
+
*
|
|
88
|
+
* @remarks
|
|
89
|
+
* The brand is a phantom `unique symbol` property: it exists only in the type
|
|
90
|
+
* system (erased at transpile, never present at runtime). Because the symbol is
|
|
91
|
+
* not exported, consumer code cannot fabricate an `Opaque` — the engine API is
|
|
92
|
+
* the only source. Contrast with a `LuaTable` alias, which says the opposite:
|
|
93
|
+
* "inspect freely, the shape just isn't modeled."
|
|
94
|
+
*/
|
|
81
95
|
export interface Opaque<Name extends string> {
|
|
82
96
|
readonly [OpaqueBrand]: Name;
|
|
83
97
|
}
|
package/src/emit-dts.ts
CHANGED
|
@@ -82,8 +82,11 @@ export const TS_RESERVED_NAMES = new Set([
|
|
|
82
82
|
"extends",
|
|
83
83
|
]);
|
|
84
84
|
|
|
85
|
-
// Element names whose `table` slot is a genuinely-arbitrary lua table by design
|
|
86
|
-
//
|
|
85
|
+
// Element names whose `table` slot is a genuinely-arbitrary lua table by design.
|
|
86
|
+
// Two kinds: the serialization/JSON passthrough functions (Defold-internal — the
|
|
87
|
+
// engine round-trips an arbitrary lua value), and the platform/OS-sourced opaque
|
|
88
|
+
// blobs (external — the shape is set by the host OS or invoking app, not by
|
|
89
|
+
// Defold, so there is no documented field list). Their emitted
|
|
87
90
|
// `Record<string | number, unknown>` is the faithful "any lua table" type, not a
|
|
88
91
|
// `recordTables` fidelity loss, so the audit consults this set to avoid counting
|
|
89
92
|
// them. A new ref-doc function with an opaque table must be added here
|
|
@@ -95,6 +98,9 @@ export const ARBITRARY_TABLE_SLOTS = new Set([
|
|
|
95
98
|
"sys.load",
|
|
96
99
|
"sys.serialize",
|
|
97
100
|
"sys.deserialize",
|
|
101
|
+
"iac.set_listener",
|
|
102
|
+
"push.get_scheduled",
|
|
103
|
+
"push.get_all_scheduled",
|
|
98
104
|
]);
|
|
99
105
|
|
|
100
106
|
// Element names whose `table` slot is a prose-only `a table mapping X to Y`
|
|
@@ -131,6 +137,157 @@ export const HOMOGENEOUS_ARRAY_SLOTS: ReadonlyMap<string, string | readonly stri
|
|
|
131
137
|
["sound.get_groups", "hash"],
|
|
132
138
|
["iap.list", "string"],
|
|
133
139
|
["go.delete", ["string", "hash", "url"]],
|
|
140
|
+
// push.register's `notifications` is a prose-only array of push.NOTIFICATION_*
|
|
141
|
+
// bitmask constants (the ref example sums NOTIFICATION_BADGE/SOUND/ALERT). The
|
|
142
|
+
// vendored push_doc.json fixture declares no NOTIFICATION_* constant elements, so
|
|
143
|
+
// no brand exists to reference; `number` is the faithful element token for these
|
|
144
|
+
// numeric constants, mirroring the vmath.vector/buffer.* number entries.
|
|
145
|
+
["push.register", "number"],
|
|
146
|
+
]);
|
|
147
|
+
|
|
148
|
+
// A `mapping` curation whose value is itself a single-level mapping, emitted
|
|
149
|
+
// `LuaMap<Kouter, LuaMap<Kinner, Vinner>>` — the nested-row-map shape
|
|
150
|
+
// (`tilemap.get_tiles` → `tiles[row][col]`).
|
|
151
|
+
export type NestedMapping = { key: string; value: string };
|
|
152
|
+
|
|
153
|
+
export type TableSlotCuration =
|
|
154
|
+
| { kind: "mapping"; key: string; value: string | readonly TableField[] | NestedMapping }
|
|
155
|
+
| { kind: "array"; element: string | readonly string[] }
|
|
156
|
+
| { kind: "object"; fields: readonly TableField[] }
|
|
157
|
+
| { kind: "array-object"; fields: readonly TableField[] };
|
|
158
|
+
|
|
159
|
+
const SOCKET_HANDLE_TOKENS = ["client", "master", "unconnected"] as const;
|
|
160
|
+
|
|
161
|
+
export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new Map([
|
|
162
|
+
["collectionfactory.create:return:ids", { kind: "mapping", key: "hash", value: "hash" }],
|
|
163
|
+
// iap.finish and iap.acknowledge take the same Defold IAP transaction object —
|
|
164
|
+
// the table handed to the iap.set_listener callback. The ref-doc fixture
|
|
165
|
+
// describes it in prose only (no field list), so the shape is curated from the
|
|
166
|
+
// Defold iap reference. As a param-side object curation every field emits `?`,
|
|
167
|
+
// which is faithful: original_trans/signature/user_id are platform-specific.
|
|
168
|
+
[
|
|
169
|
+
"iap.finish:param:transaction",
|
|
170
|
+
{
|
|
171
|
+
kind: "object",
|
|
172
|
+
fields: [
|
|
173
|
+
{ name: "ident", types: ["string"] },
|
|
174
|
+
{ name: "state", types: ["number"] },
|
|
175
|
+
{ name: "trans_ident", types: ["string"] },
|
|
176
|
+
{ name: "date", types: ["string"] },
|
|
177
|
+
{ name: "original_trans", types: ["string"] },
|
|
178
|
+
{ name: "receipt", types: ["string"] },
|
|
179
|
+
{ name: "signature", types: ["string"] },
|
|
180
|
+
{ name: "user_id", types: ["string"] },
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
[
|
|
185
|
+
"iap.acknowledge:param:transaction",
|
|
186
|
+
{
|
|
187
|
+
kind: "object",
|
|
188
|
+
fields: [
|
|
189
|
+
{ name: "ident", types: ["string"] },
|
|
190
|
+
{ name: "state", types: ["number"] },
|
|
191
|
+
{ name: "trans_ident", types: ["string"] },
|
|
192
|
+
{ name: "date", types: ["string"] },
|
|
193
|
+
{ name: "original_trans", types: ["string"] },
|
|
194
|
+
{ name: "receipt", types: ["string"] },
|
|
195
|
+
{ name: "signature", types: ["string"] },
|
|
196
|
+
{ name: "user_id", types: ["string"] },
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
// iap.buy's `options` is documented in the fixture as prose only ("optional
|
|
201
|
+
// parameters as properties"), so the field set is curated from the Defold iap
|
|
202
|
+
// reference: a Facebook-only custom `request_id` and a Google-Play-only
|
|
203
|
+
// subscription-offer `token`. Both are platform-specific, hence param-side
|
|
204
|
+
// optional fields.
|
|
205
|
+
[
|
|
206
|
+
"iap.buy:param:options",
|
|
207
|
+
{
|
|
208
|
+
kind: "object",
|
|
209
|
+
fields: [
|
|
210
|
+
{ name: "request_id", types: ["string"] },
|
|
211
|
+
{ name: "token", types: ["string"] },
|
|
212
|
+
],
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
[
|
|
216
|
+
"liveupdate.get_mounts:return:mounts",
|
|
217
|
+
{
|
|
218
|
+
kind: "array-object",
|
|
219
|
+
fields: [
|
|
220
|
+
{ name: "name", types: ["string"] },
|
|
221
|
+
{ name: "uri", types: ["string"] },
|
|
222
|
+
{ name: "priority", types: ["number"] },
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
],
|
|
226
|
+
[
|
|
227
|
+
"model.get_aabb:return:aabb",
|
|
228
|
+
{
|
|
229
|
+
kind: "object",
|
|
230
|
+
fields: [
|
|
231
|
+
{ name: "min", types: ["vector3"] },
|
|
232
|
+
{ name: "max", types: ["vector3"] },
|
|
233
|
+
],
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
[
|
|
237
|
+
"model.get_mesh_aabb:return:aabb",
|
|
238
|
+
{
|
|
239
|
+
kind: "mapping",
|
|
240
|
+
key: "hash",
|
|
241
|
+
value: [
|
|
242
|
+
{ name: "min", types: ["vector3"] },
|
|
243
|
+
{ name: "max", types: ["vector3"] },
|
|
244
|
+
],
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
["physics.raycast:param:groups", { kind: "array", element: "hash" }],
|
|
248
|
+
["physics.raycast_async:param:groups", { kind: "array", element: "hash" }],
|
|
249
|
+
// push.schedule's `notification_settings` is documented in the fixture as prose
|
|
250
|
+
// only ("Table with notification and platform specific fields"), so the field
|
|
251
|
+
// set is curated from the Defold push reference: an iOS `action`, an iOS
|
|
252
|
+
// `badge_count`, and an Android `priority`. Each is platform-specific, hence
|
|
253
|
+
// param-side optional fields.
|
|
254
|
+
[
|
|
255
|
+
"push.schedule:param:notification_settings",
|
|
256
|
+
{
|
|
257
|
+
kind: "object",
|
|
258
|
+
fields: [
|
|
259
|
+
{ name: "action", types: ["string"] },
|
|
260
|
+
{ name: "badge_count", types: ["number"] },
|
|
261
|
+
{ name: "priority", types: ["number"] },
|
|
262
|
+
],
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
["socket.select:param:recvt", { kind: "array", element: SOCKET_HANDLE_TOKENS }],
|
|
266
|
+
["socket.select:param:sendt", { kind: "array", element: SOCKET_HANDLE_TOKENS }],
|
|
267
|
+
["socket.select:return:sockets_r", { kind: "array", element: SOCKET_HANDLE_TOKENS }],
|
|
268
|
+
["socket.select:return:sockets_w", { kind: "array", element: SOCKET_HANDLE_TOKENS }],
|
|
269
|
+
[
|
|
270
|
+
"tilemap.get_tile_info:return:tile_info",
|
|
271
|
+
{
|
|
272
|
+
kind: "object",
|
|
273
|
+
fields: [
|
|
274
|
+
{ name: "index", types: ["number"] },
|
|
275
|
+
{ name: "h_flip", types: ["boolean"] },
|
|
276
|
+
{ name: "v_flip", types: ["boolean"] },
|
|
277
|
+
{ name: "rotate_90", types: ["boolean"] },
|
|
278
|
+
],
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
// tilemap.get_tiles returns a sparse table of rows iterated `tiles[row][col]`,
|
|
282
|
+
// the keys being tile positions offset by tilemap.get_bounds() (not a dense
|
|
283
|
+
// 0..n run). The faithful shape is the nested LuaMap idiom
|
|
284
|
+
// `LuaMap<number, LuaMap<number, number>>` — the keys are plain `number` tile
|
|
285
|
+
// positions, not a branded `Hash`, and a non-string-keyed Lua table is `LuaMap`,
|
|
286
|
+
// never a `Record` (which would imply a dense index object).
|
|
287
|
+
[
|
|
288
|
+
"tilemap.get_tiles:return:tiles",
|
|
289
|
+
{ kind: "mapping", key: "number", value: { key: "number", value: "number" } },
|
|
290
|
+
],
|
|
134
291
|
]);
|
|
135
292
|
|
|
136
293
|
/**
|
|
@@ -482,6 +639,7 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
|
|
|
482
639
|
const decl = hasAliases ? "export " : "";
|
|
483
640
|
|
|
484
641
|
const lines: string[] = [];
|
|
642
|
+
for (const docLine of namespaceDocLines(module)) lines.push(docLine);
|
|
485
643
|
lines.push(`declare namespace ${module.namespace} {`);
|
|
486
644
|
|
|
487
645
|
for (const t of typedefs) {
|
|
@@ -653,6 +811,15 @@ function summaryDocLines(brief: string, description: string, indent: string): st
|
|
|
653
811
|
return indentDocLines({ summary: htmlToDocText(summaryFor(brief, description)) }, indent);
|
|
654
812
|
}
|
|
655
813
|
|
|
814
|
+
function namespaceDocLines(module: ApiModule): string[] {
|
|
815
|
+
const official = summaryFor(module.brief, module.description).trim();
|
|
816
|
+
const summary =
|
|
817
|
+
official.length > 0
|
|
818
|
+
? htmlToDocText(official)
|
|
819
|
+
: `(synthesized)\nDefold \`${module.namespace}\` API namespace.`;
|
|
820
|
+
return indentDocLines({ summary }, "");
|
|
821
|
+
}
|
|
822
|
+
|
|
656
823
|
function indentDocLines(parts: DocCommentParts, indent: string): string[] {
|
|
657
824
|
return renderDocComment(parts).map((line) => `${indent}${line}`);
|
|
658
825
|
}
|
|
@@ -690,7 +857,7 @@ function emitParameter(
|
|
|
690
857
|
const concrete = p.types.filter((t) => t !== "nil");
|
|
691
858
|
const ts =
|
|
692
859
|
concrete.length > 0
|
|
693
|
-
? mapSlotUnion(concrete, p.doc, mapType, true, resolver, elementName)
|
|
860
|
+
? mapSlotUnion(concrete, p.doc, mapType, true, resolver, elementName, "param", p.name)
|
|
694
861
|
: "unknown";
|
|
695
862
|
return `${name}${optional ? "?" : ""}: ${ts}`;
|
|
696
863
|
}
|
|
@@ -708,7 +875,7 @@ function emitReturn(
|
|
|
708
875
|
// typescript-to-lua erases to `local a, b = fn()`.
|
|
709
876
|
const slots = returnValues.map((rv) =>
|
|
710
877
|
rv.types.length > 0
|
|
711
|
-
? mapSlotUnion(rv.types, rv.doc, mapType, false, resolver, elementName)
|
|
878
|
+
? mapSlotUnion(rv.types, rv.doc, mapType, false, resolver, elementName, "return", rv.name)
|
|
712
879
|
: "unknown",
|
|
713
880
|
);
|
|
714
881
|
return { type: `LuaMultiReturn<[${slots.join(", ")}]>`, trailing: "" };
|
|
@@ -717,7 +884,16 @@ function emitReturn(
|
|
|
717
884
|
if (!first) return { type: "void", trailing: "" };
|
|
718
885
|
const ts =
|
|
719
886
|
first.types.length > 0
|
|
720
|
-
? mapSlotUnion(
|
|
887
|
+
? mapSlotUnion(
|
|
888
|
+
first.types,
|
|
889
|
+
first.doc,
|
|
890
|
+
mapType,
|
|
891
|
+
false,
|
|
892
|
+
resolver,
|
|
893
|
+
elementName,
|
|
894
|
+
"return",
|
|
895
|
+
first.name,
|
|
896
|
+
)
|
|
721
897
|
: "unknown";
|
|
722
898
|
return { type: ts, trailing: "" };
|
|
723
899
|
}
|
|
@@ -742,22 +918,38 @@ function mapSlotUnion(
|
|
|
742
918
|
optionalFields: boolean,
|
|
743
919
|
resolver: TableDocResolver,
|
|
744
920
|
elementName: string,
|
|
921
|
+
slotKind?: "param" | "return",
|
|
922
|
+
slotName?: string,
|
|
745
923
|
): string {
|
|
746
924
|
const mapped: string[] = [];
|
|
747
925
|
const seen = new Set<string>();
|
|
748
926
|
for (const token of types) {
|
|
749
927
|
let ts: string;
|
|
750
928
|
if (token === "table") {
|
|
751
|
-
const
|
|
752
|
-
|
|
929
|
+
const curation =
|
|
930
|
+
slotKind !== undefined && slotName !== undefined
|
|
931
|
+
? TABLE_SLOT_CURATIONS.get(tableSlotKey(elementName, slotKind, slotName))
|
|
932
|
+
: undefined;
|
|
933
|
+
const mapping =
|
|
934
|
+
curation?.kind === "mapping" ? curation : MAPPING_TABLE_SLOTS.get(elementName);
|
|
935
|
+
const element =
|
|
936
|
+
curation?.kind === "array" ? curation.element : HOMOGENEOUS_ARRAY_SLOTS.get(elementName);
|
|
753
937
|
if (mapping !== undefined) {
|
|
754
|
-
|
|
938
|
+
let value: string;
|
|
939
|
+
if (typeof mapping.value === "string") {
|
|
940
|
+
value = mapType(mapping.value);
|
|
941
|
+
} else if (Array.isArray(mapping.value)) {
|
|
942
|
+
value = inlineTableType(mapping.value, mapType, optionalFields);
|
|
943
|
+
} else {
|
|
944
|
+
const nested = mapping.value as NestedMapping;
|
|
945
|
+
value = `LuaMap<${mapType(nested.key)}, ${mapType(nested.value)}>`;
|
|
946
|
+
}
|
|
947
|
+
ts = `LuaMap<${mapType(mapping.key)}, ${value}>`;
|
|
755
948
|
} else if (element !== undefined) {
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
: `${mapType(tokens[0] as string)}[]`;
|
|
949
|
+
ts = arrayTypeFromTokens(element, mapType);
|
|
950
|
+
} else if (curation?.kind === "object" || curation?.kind === "array-object") {
|
|
951
|
+
const object = inlineTableType(curation.fields, mapType, optionalFields);
|
|
952
|
+
ts = curation.kind === "array-object" ? `${object}[]` : object;
|
|
761
953
|
} else {
|
|
762
954
|
const fields = parseTableFields(doc, resolver);
|
|
763
955
|
if (fields !== null) {
|
|
@@ -777,6 +969,20 @@ function mapSlotUnion(
|
|
|
777
969
|
return mapped.join(" | ");
|
|
778
970
|
}
|
|
779
971
|
|
|
972
|
+
function tableSlotKey(elementName: string, slotKind: "param" | "return", slotName: string): string {
|
|
973
|
+
return `${elementName}:${slotKind}:${slotName}`;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
function arrayTypeFromTokens(
|
|
977
|
+
element: string | readonly string[],
|
|
978
|
+
mapType: (t: string) => string,
|
|
979
|
+
): string {
|
|
980
|
+
const tokens = typeof element === "string" ? [element] : element;
|
|
981
|
+
return tokens.length > 1
|
|
982
|
+
? `(${unionFromTokens(tokens, mapType)})[]`
|
|
983
|
+
: `${mapType(tokens[0] as string)}[]`;
|
|
984
|
+
}
|
|
985
|
+
|
|
780
986
|
export function inlineTableType(
|
|
781
987
|
fields: readonly TableField[],
|
|
782
988
|
mapType: (t: string) => string,
|
package/src/engine-globals.d.ts
CHANGED
package/src/go-overloads.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
/** @noSelfInFile */
|
|
2
|
+
|
|
2
3
|
import type { Hash, Opaque, Quaternion, Url, Vector3, Vector4 } from "./core-types";
|
|
3
4
|
|
|
5
|
+
interface ScriptProperty<TValue> {
|
|
6
|
+
readonly __defoldScriptProperty: TValue;
|
|
7
|
+
}
|
|
8
|
+
|
|
4
9
|
declare global {
|
|
5
10
|
namespace go {
|
|
6
11
|
interface GoPropertyOptions {
|
|
@@ -61,5 +66,43 @@ declare global {
|
|
|
61
66
|
value: number | boolean | Hash | Url | Vector3 | Vector4 | Quaternion | Opaque<"resource">,
|
|
62
67
|
options?: GoPropertyOptions,
|
|
63
68
|
): void;
|
|
69
|
+
/**
|
|
70
|
+
* Registers a Defold editor script property (deprecated escape hatch).
|
|
71
|
+
*
|
|
72
|
+
* @deprecated Don't call `go.property` yourself. Declare the property in
|
|
73
|
+
* `defineScript({ properties })` — that is the only form that types it onto
|
|
74
|
+
* `self`; the transpiler emits the `go.property(...)` registration for you.
|
|
75
|
+
* A direct call still registers, but `self.<name>` stays untyped.
|
|
76
|
+
*
|
|
77
|
+
* @param name - editor property id to register.
|
|
78
|
+
* @param value - default value for the registered property.
|
|
79
|
+
* @returns a phantom descriptor used only for TypeScript self typing.
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* // Declare it as a field — the key is the name, the value is the default:
|
|
83
|
+
* export default defineScript({
|
|
84
|
+
* properties: { speed: 450 },
|
|
85
|
+
* update(self) {
|
|
86
|
+
* self.speed; // number
|
|
87
|
+
* },
|
|
88
|
+
* });
|
|
89
|
+
* // The transpiler emits, at chunk scope: go.property("speed", 450)
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
function property(name: string, value: number): ScriptProperty<number>;
|
|
93
|
+
/** @deprecated Declare booleans via the `defineScript({ properties })` field. */
|
|
94
|
+
function property(name: string, value: boolean): ScriptProperty<boolean>;
|
|
95
|
+
/** @deprecated Declare hashes via the `defineScript({ properties })` field. */
|
|
96
|
+
function property(name: string, value: Hash): ScriptProperty<Hash>;
|
|
97
|
+
/** @deprecated Declare URLs via the `defineScript({ properties })` field. */
|
|
98
|
+
function property(name: string, value: Url): ScriptProperty<Url>;
|
|
99
|
+
/** @deprecated Declare vector3s via the `defineScript({ properties })` field. */
|
|
100
|
+
function property(name: string, value: Vector3): ScriptProperty<Vector3>;
|
|
101
|
+
/** @deprecated Declare vector4s via the `defineScript({ properties })` field. */
|
|
102
|
+
function property(name: string, value: Vector4): ScriptProperty<Vector4>;
|
|
103
|
+
/** @deprecated Declare quaternions via the `defineScript({ properties })` field. */
|
|
104
|
+
function property(name: string, value: Quaternion): ScriptProperty<Quaternion>;
|
|
105
|
+
/** @deprecated Declare resources via the `defineScript({ properties })` field. */
|
|
106
|
+
function property(name: string, value: Opaque<"resource">): ScriptProperty<Opaque<"resource">>;
|
|
64
107
|
}
|
|
65
108
|
}
|
package/src/index.ts
CHANGED
|
@@ -17,11 +17,16 @@ export {
|
|
|
17
17
|
defineRenderScript,
|
|
18
18
|
defineScript,
|
|
19
19
|
type GuiScriptHooks,
|
|
20
|
+
type GuiScriptHooksWithProperties,
|
|
20
21
|
type InputAction,
|
|
21
22
|
type InputTouch,
|
|
22
23
|
type RenderScriptHooks,
|
|
24
|
+
type RenderScriptHooksWithProperties,
|
|
23
25
|
SCRIPT_HOOK_NAMES,
|
|
24
26
|
type ScriptHookName,
|
|
25
27
|
type ScriptHooks,
|
|
28
|
+
type ScriptHooksWithProperties,
|
|
29
|
+
type ScriptProperties,
|
|
30
|
+
type ScriptProperty,
|
|
26
31
|
} from "./lifecycle";
|
|
27
32
|
export { type WrapOptions, wrapAsAmbientGlobal } from "./publish-dts";
|