@defold-typescript/types 0.21.1 → 0.23.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/index.d.ts +5 -0
- package/package.json +1 -1
- package/scripts/regen.ts +6 -6
- package/src/editor.ts +75 -0
- package/src/emit-dts.ts +14 -1
- package/src/index.ts +11 -1
- package/src/publish-dts.ts +32 -4
- package/src/script-api.ts +24 -8
package/index.d.ts
CHANGED
package/package.json
CHANGED
package/scripts/regen.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
parseMessagesDoc,
|
|
10
10
|
} from "../src/emit-messages";
|
|
11
11
|
import type { TranslationStore } from "../src/example-store";
|
|
12
|
-
import { wrapAsAmbientGlobal } from "../src/publish-dts";
|
|
12
|
+
import { wrapAsAmbientGlobal, wrapAsModule } from "../src/publish-dts";
|
|
13
13
|
import {
|
|
14
14
|
type DocSourceProvenance,
|
|
15
15
|
type DownloadRefDoc,
|
|
@@ -90,6 +90,7 @@ export interface ModuleManifestEntry {
|
|
|
90
90
|
readonly outFile: string;
|
|
91
91
|
readonly skipFunctions?: readonly string[];
|
|
92
92
|
readonly importsFrom?: string;
|
|
93
|
+
readonly moduleId?: string;
|
|
93
94
|
readonly sourceProvenance?: DocSourceProvenance;
|
|
94
95
|
}
|
|
95
96
|
|
|
@@ -239,11 +240,10 @@ export function generateModuleDeclaration(
|
|
|
239
240
|
options,
|
|
240
241
|
);
|
|
241
242
|
const emitted = emitDeclarations(module, { knownConstantFqns, translations });
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
emitted,
|
|
245
|
-
|
|
246
|
-
});
|
|
243
|
+
const importsFrom = entry.importsFrom ?? "../src/core-types";
|
|
244
|
+
const contents = entry.moduleId
|
|
245
|
+
? wrapAsModule({ namespace: module.namespace, emitted, importsFrom, moduleId: entry.moduleId })
|
|
246
|
+
: wrapAsAmbientGlobal({ namespace: module.namespace, emitted, importsFrom });
|
|
247
247
|
return { contents, dropped };
|
|
248
248
|
}
|
|
249
249
|
|
package/src/editor.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// Editor scripts are loaded by the Defold *editor*, not the runtime engine: the
|
|
2
|
+
// editor `require`s the emitted chunk and reads the hooks table it returns. That
|
|
3
|
+
// makes them a fourth, disjoint script kind — lowered to a chunk-level
|
|
4
|
+
// `return <hooks table>` rather than the runtime kinds' flat top-level globals.
|
|
5
|
+
//
|
|
6
|
+
// This is the keystone surface only. The full typed `editor.*` global
|
|
7
|
+
// (`get`/`transact`/`command` + the editor-VM `http`/`json`/`zip`) and the
|
|
8
|
+
// per-kind API walls are a later slice, so a command's `run`/`active` receive a
|
|
9
|
+
// loosely-typed opts bag for now.
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A single command an editor script contributes: a label, the editor UI
|
|
13
|
+
* locations it appears in (e.g. `"Edit"`, `"Assets"`, `"Outline"`, `"View"`),
|
|
14
|
+
* and optional `active`/`run` hooks the editor calls with a command-context bag.
|
|
15
|
+
*/
|
|
16
|
+
export interface EditorCommand {
|
|
17
|
+
/** Menu/label text shown for the command. */
|
|
18
|
+
label: string;
|
|
19
|
+
/** Editor UI locations the command is offered in. */
|
|
20
|
+
locations: string[];
|
|
21
|
+
/**
|
|
22
|
+
* Declares the command's context arguments; the editor passes the resolved
|
|
23
|
+
* values to `active`/`run`. Loosely typed until the `editor.*` slice lands.
|
|
24
|
+
*/
|
|
25
|
+
query?: Record<string, unknown>;
|
|
26
|
+
/**
|
|
27
|
+
* Called to decide whether the command is currently enabled. Omit to always
|
|
28
|
+
* enable. The opts bag is loosely typed until the `editor.*` slice lands.
|
|
29
|
+
*/
|
|
30
|
+
active?: (opts: Record<string, unknown>) => boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Called when the command is invoked. The opts bag is loosely typed until the
|
|
33
|
+
* `editor.*` slice lands.
|
|
34
|
+
*/
|
|
35
|
+
run?: (opts: Record<string, unknown>) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The hooks table an editor script returns. Every hook is optional; the editor
|
|
40
|
+
* calls the ones present. Only the keystone hooks are typed here.
|
|
41
|
+
*/
|
|
42
|
+
export interface EditorScriptModule {
|
|
43
|
+
/** Returns the commands this script contributes to the editor. */
|
|
44
|
+
get_commands?: () => EditorCommand[];
|
|
45
|
+
/** Returns language-server descriptors this script contributes. */
|
|
46
|
+
get_language_servers?: () => unknown[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Type an editor script's hooks table. At runtime this is an identity function —
|
|
51
|
+
* it returns `module` unchanged; its only job is typing. The transpiler's
|
|
52
|
+
* `editor-script-erasure` pass rewrites the top-level `export default
|
|
53
|
+
* defineEditorScript({...})` into a chunk-level `return { ... }` (the shape the
|
|
54
|
+
* editor loads) and erases this import — zero runtime cost.
|
|
55
|
+
*
|
|
56
|
+
* @param module - the editor-script hooks table to type and return.
|
|
57
|
+
* @returns the same `module` object, now typed (identity at runtime).
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* export default defineEditorScript({
|
|
61
|
+
* get_commands: () => [
|
|
62
|
+
* { label: "Say Hi", locations: ["Edit"], run: () => print("hi") },
|
|
63
|
+
* ],
|
|
64
|
+
* });
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export function defineEditorScript<T extends EditorScriptModule>(
|
|
68
|
+
// Intersecting the non-module keys with `never` rejects an unknown hook key on
|
|
69
|
+
// a fresh object literal, while the `T` return keeps the call an identity over
|
|
70
|
+
// its exact argument type (a bare `<T extends ...>` would silently absorb the
|
|
71
|
+
// extra key into `T` and accept it).
|
|
72
|
+
module: T & Record<Exclude<keyof T, keyof EditorScriptModule>, never>,
|
|
73
|
+
): T {
|
|
74
|
+
return module;
|
|
75
|
+
}
|
package/src/emit-dts.ts
CHANGED
|
@@ -1565,12 +1565,25 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
|
|
|
1565
1565
|
const nestedIndent = `${INDENT}${INDENT}`;
|
|
1566
1566
|
for (const segment of nestedSegments) {
|
|
1567
1567
|
const group = nestedGroups.get(segment) ?? [];
|
|
1568
|
+
// A reserved-name function inside a nested namespace gets the same recovery as
|
|
1569
|
+
// a top-level one: emitted un-exported as `_<name>` and re-exported under the
|
|
1570
|
+
// reserved name. The alias switches this namespace out of implicit-export mode,
|
|
1571
|
+
// so its siblings then need an explicit `export` to stay reachable.
|
|
1572
|
+
const segmentAliases: { internal: string; public: string }[] = [];
|
|
1573
|
+
const segmentDecl = group.some((fn) => TS_RESERVED_NAMES.has(fn.name)) ? "export " : "";
|
|
1568
1574
|
lines.push(`${INDENT}${decl}namespace ${segment} {`);
|
|
1569
1575
|
for (const fn of group) {
|
|
1576
|
+
const reserved = TS_RESERVED_NAMES.has(fn.name);
|
|
1577
|
+
const emitName = aliasName(fn.name, segmentAliases);
|
|
1570
1578
|
for (const docLine of functionDocLines(fn.original, translations, nestedIndent)) {
|
|
1571
1579
|
lines.push(docLine);
|
|
1572
1580
|
}
|
|
1573
|
-
lines.push(
|
|
1581
|
+
lines.push(
|
|
1582
|
+
`${nestedIndent}${reserved ? "" : segmentDecl}${emitFunction(fn, emitName, mapType, resolver)}`,
|
|
1583
|
+
);
|
|
1584
|
+
}
|
|
1585
|
+
for (const alias of [...segmentAliases].sort((a, b) => a.public.localeCompare(b.public))) {
|
|
1586
|
+
lines.push(`${nestedIndent}export { ${alias.internal} as ${alias.public} };`);
|
|
1574
1587
|
}
|
|
1575
1588
|
lines.push(`${INDENT}}`);
|
|
1576
1589
|
}
|
package/src/index.ts
CHANGED
|
@@ -37,6 +37,11 @@ export {
|
|
|
37
37
|
htmlToDocText,
|
|
38
38
|
renderDocComment,
|
|
39
39
|
} from "./doc-comment";
|
|
40
|
+
export {
|
|
41
|
+
defineEditorScript,
|
|
42
|
+
type EditorCommand,
|
|
43
|
+
type EditorScriptModule,
|
|
44
|
+
} from "./editor";
|
|
40
45
|
export {
|
|
41
46
|
type EmitOptions,
|
|
42
47
|
emitDeclarations,
|
|
@@ -75,7 +80,12 @@ export {
|
|
|
75
80
|
type ScriptPropertiesOf,
|
|
76
81
|
type ScriptProperty,
|
|
77
82
|
} from "./lifecycle";
|
|
78
|
-
export {
|
|
83
|
+
export {
|
|
84
|
+
type ModuleWrapOptions,
|
|
85
|
+
type WrapOptions,
|
|
86
|
+
wrapAsAmbientGlobal,
|
|
87
|
+
wrapAsModule,
|
|
88
|
+
} from "./publish-dts";
|
|
79
89
|
export {
|
|
80
90
|
lookupSignature,
|
|
81
91
|
type SignatureOverride,
|
package/src/publish-dts.ts
CHANGED
|
@@ -16,18 +16,46 @@ export interface WrapOptions {
|
|
|
16
16
|
importsFrom: string;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export
|
|
20
|
-
|
|
19
|
+
export interface ModuleWrapOptions extends WrapOptions {
|
|
20
|
+
moduleId: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// The engine-type import line plus the emitted body with its leading
|
|
24
|
+
// `declare namespace` rewritten to `topKeyword` and every line indented two
|
|
25
|
+
// spaces — the shared shape both the ambient-global and importable-module wraps
|
|
26
|
+
// place inside their envelope.
|
|
27
|
+
function prepareWrapBody(
|
|
28
|
+
emitted: string,
|
|
29
|
+
importsFrom: string,
|
|
30
|
+
topKeyword: string,
|
|
31
|
+
): { importLine: string; indented: string } {
|
|
32
|
+
const used = collectEngineTypes(emitted);
|
|
21
33
|
const importLine =
|
|
22
|
-
used.length === 0 ? "" : `import type { ${used.join(", ")} } from "${
|
|
23
|
-
const inner =
|
|
34
|
+
used.length === 0 ? "" : `import type { ${used.join(", ")} } from "${importsFrom}";\n\n`;
|
|
35
|
+
const inner = emitted.replace(/(^|\n)declare\s+namespace\s+/, `$1${topKeyword} `).trimEnd();
|
|
24
36
|
const indented = inner
|
|
25
37
|
.split("\n")
|
|
26
38
|
.map((l) => (l.length === 0 ? l : ` ${l}`))
|
|
27
39
|
.join("\n");
|
|
40
|
+
return { importLine, indented };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function wrapAsAmbientGlobal(opts: WrapOptions): string {
|
|
44
|
+
const { importLine, indented } = prepareWrapBody(opts.emitted, opts.importsFrom, "namespace");
|
|
28
45
|
return `/** @noSelfInFile */\n${importLine}declare global {\n${indented}\n}\n\nexport {};\n`;
|
|
29
46
|
}
|
|
30
47
|
|
|
48
|
+
export function wrapAsModule(opts: ModuleWrapOptions): string {
|
|
49
|
+
// ambient-globals-only: the module form references engine handles (`Hash`,
|
|
50
|
+
// `Vector3`, ...) as ambient globals and emits no top-level import. A top-level
|
|
51
|
+
// `import type` would make the `.d.ts` a module, demoting `declare module
|
|
52
|
+
// '<id>'` to an augmentation of an unresolvable specifier that a consumer
|
|
53
|
+
// `import` cannot resolve (`TS2307`). `importsFrom` stays in the signature for
|
|
54
|
+
// caller symmetry but is unused here; `wrapAsAmbientGlobal` still imports it.
|
|
55
|
+
const { indented } = prepareWrapBody(opts.emitted, opts.importsFrom, "export namespace");
|
|
56
|
+
return `/** @noSelfInFile */\n/** @noResolution */\ndeclare module '${opts.moduleId}' {\n${indented}\n}\n`;
|
|
57
|
+
}
|
|
58
|
+
|
|
31
59
|
function collectEngineTypes(emitted: string): EngineType[] {
|
|
32
60
|
return ENGINE_TYPES.filter((t) => new RegExp(`\\b${t}\\b`).test(emitted));
|
|
33
61
|
}
|
package/src/script-api.ts
CHANGED
|
@@ -73,15 +73,31 @@ export function scriptApiToRefDoc(parsed: unknown): RefDoc {
|
|
|
73
73
|
}
|
|
74
74
|
const members = Array.isArray(table.members) ? table.members : [];
|
|
75
75
|
const elements: RefDocElement[] = [];
|
|
76
|
+
const fnElement = (name: string, member: Record<string, unknown>): RefDocElement => ({
|
|
77
|
+
type: "FUNCTION",
|
|
78
|
+
name,
|
|
79
|
+
description: stringOr(member.desc, ""),
|
|
80
|
+
parameters: mapParameters(member.parameters),
|
|
81
|
+
returnvalues: mapParameters(member.returns),
|
|
82
|
+
});
|
|
76
83
|
for (const member of members) {
|
|
77
|
-
if (!isRecord(member)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
if (!isRecord(member)) continue;
|
|
85
|
+
if (member.type === "function") {
|
|
86
|
+
elements.push(fnElement(`${namespace}.${stringOr(member.name, "")}`, member));
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (member.type === "table") {
|
|
90
|
+
const sub = stringOr(member.name, "");
|
|
91
|
+
// A nameless sub-namespace can't form a valid dotted name; skip it.
|
|
92
|
+
if (sub.length === 0) continue;
|
|
93
|
+
const subMembers = Array.isArray(member.members) ? member.members : [];
|
|
94
|
+
for (const subMember of subMembers) {
|
|
95
|
+
// Recurse exactly one level: a `type: table` nested here is 2nd-level,
|
|
96
|
+
// whose functions the emitter's one-dot pass would silently drop.
|
|
97
|
+
if (!isRecord(subMember) || subMember.type !== "function") continue;
|
|
98
|
+
elements.push(fnElement(`${namespace}.${sub}.${stringOr(subMember.name, "")}`, subMember));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
85
101
|
}
|
|
86
102
|
const doc: RefDoc = {
|
|
87
103
|
info: { namespace, brief: stringOr(table.desc, ""), description: stringOr(table.desc, "") },
|