@defold-typescript/types 0.5.3 → 0.5.5
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/generated/go.d.ts +180 -179
- package/generated/msg.d.ts +9 -9
- package/generated/sprite.d.ts +33 -32
- package/generated/vmath.d.ts +232 -229
- package/package.json +1 -1
- package/scripts/example-store-io.ts +18 -0
- package/scripts/regen.ts +5 -1
- package/src/doc-comment.ts +2 -1
- package/src/emit-dts.ts +19 -5
- package/src/example-store.ts +44 -0
- package/src/go-overloads.d.ts +30 -0
- package/src/message-dispatch.d.ts +21 -0
- package/src/message-guard.d.ts +19 -0
- package/src/msg-overloads.d.ts +20 -0
package/package.json
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import type { TranslationStore } from "../src/example-store";
|
|
4
|
+
|
|
5
|
+
// Build-time only: lives under `scripts/` (never reachable from the shipped
|
|
6
|
+
// `src/index.ts` graph) so its `node:fs` import cannot leak into a consumer's
|
|
7
|
+
// typecheck. `src/example-store.ts` stays pure for exactly that reason.
|
|
8
|
+
const TRANSLATIONS_PATH = resolve(import.meta.dir, "..", "examples", "translations.json");
|
|
9
|
+
|
|
10
|
+
export function loadTranslations(path: string = TRANSLATIONS_PATH): TranslationStore {
|
|
11
|
+
let raw: string;
|
|
12
|
+
try {
|
|
13
|
+
raw = readFileSync(path, "utf8");
|
|
14
|
+
} catch {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
return JSON.parse(raw) as TranslationStore;
|
|
18
|
+
}
|
package/scripts/regen.ts
CHANGED
|
@@ -4,8 +4,10 @@ import messagesDoc from "../fixtures/messages_doc.json" with { type: "json" };
|
|
|
4
4
|
import { parseDefoldApiDoc } from "../src/api-doc";
|
|
5
5
|
import { emitDeclarations } from "../src/emit-dts";
|
|
6
6
|
import { emitBuiltinMessages, parseMessagesDoc } from "../src/emit-messages";
|
|
7
|
+
import type { TranslationStore } from "../src/example-store";
|
|
7
8
|
import { wrapAsAmbientGlobal } from "../src/publish-dts";
|
|
8
9
|
import { type DownloadRefDoc, refDocCacheDir, resolveRefDoc } from "./doc-source";
|
|
10
|
+
import { loadTranslations } from "./example-store-io";
|
|
9
11
|
import { type readZip, SYNC_MANIFEST, type SyncManifestEntry } from "./sync-api-docs";
|
|
10
12
|
|
|
11
13
|
export interface ApiTargetModule {
|
|
@@ -159,6 +161,7 @@ export function collectConstantFqns(
|
|
|
159
161
|
|
|
160
162
|
export interface GenerateOptions {
|
|
161
163
|
knownConstantFqns?: ReadonlySet<string>;
|
|
164
|
+
translations?: TranslationStore;
|
|
162
165
|
}
|
|
163
166
|
|
|
164
167
|
export function generateModuleDeclaration(
|
|
@@ -178,7 +181,8 @@ export function generateModuleDeclaration(
|
|
|
178
181
|
return true;
|
|
179
182
|
});
|
|
180
183
|
const knownConstantFqns = options?.knownConstantFqns ?? collectConstantFqns();
|
|
181
|
-
const
|
|
184
|
+
const translations = options?.translations ?? loadTranslations();
|
|
185
|
+
const emitted = emitDeclarations(module, { knownConstantFqns, translations });
|
|
182
186
|
const contents = wrapAsAmbientGlobal({
|
|
183
187
|
namespace: module.namespace,
|
|
184
188
|
emitted,
|
package/src/doc-comment.ts
CHANGED
|
@@ -73,6 +73,7 @@ export interface DocCommentParts {
|
|
|
73
73
|
params?: { name: string; doc: string }[];
|
|
74
74
|
returns?: string;
|
|
75
75
|
example?: string;
|
|
76
|
+
exampleLang?: "lua" | "ts";
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
/**
|
|
@@ -115,7 +116,7 @@ export function renderDocComment(parts: DocCommentParts): string[] {
|
|
|
115
116
|
}
|
|
116
117
|
if (example !== "") {
|
|
117
118
|
lines.push(" * @example");
|
|
118
|
-
lines.push(
|
|
119
|
+
lines.push(` * \`\`\`${parts.exampleLang ?? "lua"}`);
|
|
119
120
|
for (const line of example.split("\n")) {
|
|
120
121
|
lines.push(line === "" ? " *" : ` * ${line}`);
|
|
121
122
|
}
|
package/src/emit-dts.ts
CHANGED
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
htmlToDocText,
|
|
14
14
|
renderDocComment,
|
|
15
15
|
} from "./doc-comment";
|
|
16
|
+
import type { TranslationStore } from "./example-store";
|
|
17
|
+
import { hashExampleSource, lookupTranslation } from "./example-store";
|
|
16
18
|
|
|
17
19
|
export interface EmitOptions {
|
|
18
20
|
mapType?: (defoldType: string) => string;
|
|
@@ -21,6 +23,12 @@ export interface EmitOptions {
|
|
|
21
23
|
// brands to the same FQN-keyed type its owning module's `const` emits,
|
|
22
24
|
// instead of widening to `unknown`.
|
|
23
25
|
knownConstantFqns?: ReadonlySet<string>;
|
|
26
|
+
// Hand-authored TypeScript `@example` translations keyed by element FQN.
|
|
27
|
+
// Defaults to an empty store (every example stays on its Lua fallback,
|
|
28
|
+
// byte-identical output); the build layer (`regen`) passes the loaded
|
|
29
|
+
// `examples/translations.json`. Loading lives in `scripts/example-store-io.ts`
|
|
30
|
+
// so this module stays node-free for downstream consumers.
|
|
31
|
+
translations?: TranslationStore;
|
|
24
32
|
}
|
|
25
33
|
|
|
26
34
|
export const TS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
@@ -427,6 +435,7 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
|
|
|
427
435
|
|
|
428
436
|
const constantFqns = new Set(module.constants.map((c) => c.name));
|
|
429
437
|
const knownConstantFqns = options?.knownConstantFqns;
|
|
438
|
+
const translations = options?.translations ?? {};
|
|
430
439
|
const baseMapType = options?.mapType ?? defaultMapType;
|
|
431
440
|
const mapType = (token: string): string =>
|
|
432
441
|
constantFqns.has(token) || knownConstantFqns?.has(token)
|
|
@@ -502,7 +511,7 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
|
|
|
502
511
|
for (const fn of functions) {
|
|
503
512
|
const reserved = TS_RESERVED_NAMES.has(fn.name);
|
|
504
513
|
const emitName = aliasName(fn.name, aliases);
|
|
505
|
-
for (const docLine of functionDocLines(fn.original)) lines.push(docLine);
|
|
514
|
+
for (const docLine of functionDocLines(fn.original, translations)) lines.push(docLine);
|
|
506
515
|
const line = emitFunction(fn, emitName, mapType, resolver);
|
|
507
516
|
lines.push(`${INDENT}${reserved ? "" : decl}${line}`);
|
|
508
517
|
}
|
|
@@ -614,18 +623,23 @@ function emitFunction(
|
|
|
614
623
|
// fallback applies to non-identifier names, matching `emitParameter`) so the tag
|
|
615
624
|
// resolves on hover; a single documented return becomes `@returns`. Returns `[]`
|
|
616
625
|
// for a fully-undocumented function, leaving its emission byte-identical.
|
|
617
|
-
function functionDocLines(fn: ApiFunction): string[] {
|
|
626
|
+
function functionDocLines(fn: ApiFunction, translations: TranslationStore): string[] {
|
|
618
627
|
const params = fn.parameters.map((p, index) => ({
|
|
619
628
|
name: TS_IDENTIFIER.test(p.name) ? p.name : `arg${index}`,
|
|
620
629
|
doc: htmlToDocText(p.doc),
|
|
621
630
|
}));
|
|
622
631
|
const onlyReturn = fn.returnValues.length === 1 ? fn.returnValues[0] : undefined;
|
|
623
|
-
const
|
|
632
|
+
const lua = htmlToCodeText(fn.examples ?? "");
|
|
633
|
+
// A hand-authored TS translation pinned to this exact Lua flips the fence to
|
|
634
|
+
// ```ts; any hash mismatch (or absent translation) keeps the Lua fallback.
|
|
635
|
+
const ts = lua === "" ? null : lookupTranslation(translations, fn.name, hashExampleSource(lua));
|
|
636
|
+
const exampleParts: Pick<DocCommentParts, "example" | "exampleLang"> =
|
|
637
|
+
ts !== null ? { example: ts, exampleLang: "ts" } : lua !== "" ? { example: lua } : {};
|
|
624
638
|
const parts: DocCommentParts = {
|
|
625
639
|
summary: htmlToDocText(summaryFor(fn.brief, fn.description)),
|
|
626
640
|
params,
|
|
627
641
|
...(onlyReturn ? { returns: htmlToDocText(onlyReturn.doc) } : {}),
|
|
628
|
-
...
|
|
642
|
+
...exampleParts,
|
|
629
643
|
};
|
|
630
644
|
return indentDocLines(parts, INDENT);
|
|
631
645
|
}
|
|
@@ -646,7 +660,7 @@ function indentDocLines(parts: DocCommentParts, indent: string): string[] {
|
|
|
646
660
|
// Prefer the full `description`; fall back to the one-line `brief` when prose is
|
|
647
661
|
// absent. Shared by every documented member kind so the summary source is
|
|
648
662
|
// consistent across functions, constants, variables, and properties.
|
|
649
|
-
function summaryFor(brief: string, description: string): string {
|
|
663
|
+
export function summaryFor(brief: string, description: string): string {
|
|
650
664
|
return description.trim() !== "" ? description : brief;
|
|
651
665
|
}
|
|
652
666
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// A hand-authored TypeScript translation of one element's ref-doc `@example`,
|
|
2
|
+
// pinned by a hash of the exact source Lua it replaces. A ref-doc re-pin that
|
|
3
|
+
// changes the source Lua flips the hash, so a stale translation stops matching
|
|
4
|
+
// (drift guard) and the emit falls back to the Lua body.
|
|
5
|
+
export interface Translation {
|
|
6
|
+
sourceHash: string;
|
|
7
|
+
ts: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type TranslationStore = Record<string, Translation>;
|
|
11
|
+
|
|
12
|
+
const FNV_OFFSET_BASIS = 0xcbf29ce484222325n;
|
|
13
|
+
const FNV_PRIME = 0x100000001b3n;
|
|
14
|
+
const U64_MASK = 0xffffffffffffffffn;
|
|
15
|
+
|
|
16
|
+
// A pure, dependency-free FNV-1a 64-bit hash over the source's UTF-16 code
|
|
17
|
+
// units, returned as zero-padded hex. Deliberately node- and Bun-free: this
|
|
18
|
+
// module is reachable from `index.ts` (via `emit-dts`), so a `node:crypto` or
|
|
19
|
+
// ambient-`Bun` reference here would fail type-checking in every downstream
|
|
20
|
+
// consumer that compiles the shipped `src/` graph.
|
|
21
|
+
//
|
|
22
|
+
// The input is the already-normalized post-`htmlToCodeText` string (per-line
|
|
23
|
+
// trailing whitespace and surrounding blank lines stripped), so the hash is
|
|
24
|
+
// independent of trailing whitespace in the original ref-doc HTML.
|
|
25
|
+
export function hashExampleSource(source: string): string {
|
|
26
|
+
let hash = FNV_OFFSET_BASIS;
|
|
27
|
+
for (let i = 0; i < source.length; i++) {
|
|
28
|
+
hash = ((hash ^ BigInt(source.charCodeAt(i))) * FNV_PRIME) & U64_MASK;
|
|
29
|
+
}
|
|
30
|
+
return hash.toString(16).padStart(16, "0");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Return the stored TypeScript body only when the FQN exists and its pinned
|
|
34
|
+
// `sourceHash` matches the source we are about to emit; any mismatch returns
|
|
35
|
+
// `null` so the caller keeps the Lua fallback.
|
|
36
|
+
export function lookupTranslation(
|
|
37
|
+
store: TranslationStore,
|
|
38
|
+
fqn: string,
|
|
39
|
+
sourceHash: string,
|
|
40
|
+
): string | null {
|
|
41
|
+
const entry = store[fqn];
|
|
42
|
+
if (!entry || entry.sourceHash !== sourceHash) return null;
|
|
43
|
+
return entry.ts;
|
|
44
|
+
}
|
package/src/go-overloads.d.ts
CHANGED
|
@@ -9,6 +9,21 @@ declare global {
|
|
|
9
9
|
keys?: Record<string | number, unknown>;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* gets a named property of the specified game object or component
|
|
14
|
+
*
|
|
15
|
+
* @param url - url of the game object or component having the property
|
|
16
|
+
* @param property - id of the property to retrieve
|
|
17
|
+
* @param options - optional options table
|
|
18
|
+
* - index number index into array property (1 based)
|
|
19
|
+
* - key hash name of internal property
|
|
20
|
+
* - keys table array of internal component resources identified by key (e.g. a particle fx emitter, see examples below)
|
|
21
|
+
* @returns the value of the specified property
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const position = go.get("#sprite", "position");
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
12
27
|
function get<K extends keyof go.properties>(
|
|
13
28
|
url: string | Hash | Url,
|
|
14
29
|
property: K,
|
|
@@ -19,6 +34,21 @@ declare global {
|
|
|
19
34
|
property: string | Hash,
|
|
20
35
|
options?: GoPropertyOptions,
|
|
21
36
|
): number | boolean | Hash | Url | Vector3 | Vector4 | Quaternion | Opaque<"resource">;
|
|
37
|
+
/**
|
|
38
|
+
* sets a named property of the specified game object or component, or a material constant
|
|
39
|
+
*
|
|
40
|
+
* @param url - url of the game object or component having the property
|
|
41
|
+
* @param property - id of the property to set
|
|
42
|
+
* @param value - the value to set
|
|
43
|
+
* @param options - optional options table
|
|
44
|
+
* - index integer index into array property (1 based)
|
|
45
|
+
* - key hash name of internal property
|
|
46
|
+
* - keys table array of internal component resources identified by key (e.g. a particle fx emitter, see examples below)
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* go.set("#sprite", "tint", vmath.vector4(1, 0, 0, 1));
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
22
52
|
function set<K extends keyof go.properties>(
|
|
23
53
|
url: string | Hash | Url,
|
|
24
54
|
property: K,
|
|
@@ -10,6 +10,27 @@ declare global {
|
|
|
10
10
|
// The transpiler lowers the call to a flat `message_id == hash("...")`
|
|
11
11
|
// if/elseif chain (message-dispatch-lowering.ts), keeping this package free of
|
|
12
12
|
// runtime Lua.
|
|
13
|
+
/**
|
|
14
|
+
* Discriminated-union dispatcher for `on_message`: takes a record of per-message
|
|
15
|
+
* handlers keyed by builtin message id, each receiving its `BuiltinMessages`
|
|
16
|
+
* payload already narrowed, and returns the `on_message` handler that routes to
|
|
17
|
+
* them. Reads as `on_message: onMessage<Self>({ ... })`; `self` threads via the
|
|
18
|
+
* explicit type argument, mirroring `defineScript<Self>`. The transpiler lowers
|
|
19
|
+
* it to a flat `if/elseif message_id == hash("...")` chain.
|
|
20
|
+
*
|
|
21
|
+
* @param handlers - a partial map from builtin message id to its handler; each handler's `message` is narrowed to that id's payload.
|
|
22
|
+
* @returns the `on_message` lifecycle handler that dispatches to the matching entry.
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* defineScript({
|
|
26
|
+
* on_message: onMessage({
|
|
27
|
+
* contact_point_response(self, message, sender) {
|
|
28
|
+
* print(message.other_group);
|
|
29
|
+
* },
|
|
30
|
+
* }),
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
13
34
|
function onMessage<TSelf = Record<never, never>>(
|
|
14
35
|
handlers: Partial<{
|
|
15
36
|
[K in BuiltinMessageId]: (self: TSelf, message: BuiltinMessages[K], sender: Url) => void;
|
package/src/message-guard.d.ts
CHANGED
|
@@ -8,6 +8,25 @@ declare global {
|
|
|
8
8
|
// untyped `message` record narrow to its `BuiltinMessages` payload. The
|
|
9
9
|
// transpiler lowers the call to `message_id == hash("...")`
|
|
10
10
|
// (message-guard-lowering.ts), keeping this package free of runtime Lua.
|
|
11
|
+
/**
|
|
12
|
+
* Type guard for an `on_message` handler: narrows the untyped `message` record
|
|
13
|
+
* to its `BuiltinMessages` payload when `message_id` matches a known builtin
|
|
14
|
+
* message id. The engine delivers `message_id` as a pre-hashed `Hash`, so this
|
|
15
|
+
* guard re-introduces the literal a discriminated union would otherwise need.
|
|
16
|
+
*
|
|
17
|
+
* @param message_id - the hashed message id `on_message` received.
|
|
18
|
+
* @param message - the untyped message record `on_message` received.
|
|
19
|
+
* @param expected - the builtin message id to test against (e.g. `"contact_point_response"`).
|
|
20
|
+
* @returns `true` when `message_id` matches `expected`, narrowing `message` to that payload.
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* function on_message(this: void, message_id: Hash, message: object, sender: Url) {
|
|
24
|
+
* if (isMessage(message_id, message, "contact_point_response")) {
|
|
25
|
+
* print(message.other_group);
|
|
26
|
+
* }
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
11
30
|
function isMessage<K extends BuiltinMessageId>(
|
|
12
31
|
message_id: Hash,
|
|
13
32
|
message: Record<string | number, unknown>,
|
package/src/msg-overloads.d.ts
CHANGED
|
@@ -7,6 +7,26 @@ type MsgPostPayload<K> = K extends BuiltinMessageId
|
|
|
7
7
|
|
|
8
8
|
declare global {
|
|
9
9
|
namespace msg {
|
|
10
|
+
/**
|
|
11
|
+
* Post a message to a receiving URL. The most common case is to send messages
|
|
12
|
+
* to a component. If the component part of the receiver is omitted, the message
|
|
13
|
+
* is broadcast to all components in the game object.
|
|
14
|
+
* The following receiver shorthands are available:
|
|
15
|
+
* - `"."` the current game object
|
|
16
|
+
* - `"#"` the current component
|
|
17
|
+
* There is a 2 kilobyte limit to the message parameter table size.
|
|
18
|
+
*
|
|
19
|
+
* @param receiver - The receiver must be a string in URL-format, a URL object or a hashed string.
|
|
20
|
+
* @param message_id - The id must be a string or a hashed string.
|
|
21
|
+
* @param message - a lua table with message parameters to send.
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* msg.post("#collisionobject", "apply_force", {
|
|
25
|
+
* force: vmath.vector3(0, 1000, 0),
|
|
26
|
+
* position: go.get_world_position(),
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
10
30
|
function post<K extends string>(
|
|
11
31
|
receiver: string | Url | Hash,
|
|
12
32
|
message_id: K,
|