@defold-typescript/types 0.22.0 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defold-typescript/types",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "TypeScript types for the Defold engine's Lua APIs.",
5
5
  "license": "MIT",
6
6
  "repository": {
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 contents = wrapAsAmbientGlobal({
243
- namespace: module.namespace,
244
- emitted,
245
- importsFrom: entry.importsFrom ?? "../src/core-types",
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/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(`${nestedIndent}${decl}${emitFunction(fn, fn.name, mapType, resolver)}`);
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
@@ -80,7 +80,12 @@ export {
80
80
  type ScriptPropertiesOf,
81
81
  type ScriptProperty,
82
82
  } from "./lifecycle";
83
- export { type WrapOptions, wrapAsAmbientGlobal } from "./publish-dts";
83
+ export {
84
+ type ModuleWrapOptions,
85
+ type WrapOptions,
86
+ wrapAsAmbientGlobal,
87
+ wrapAsModule,
88
+ } from "./publish-dts";
84
89
  export {
85
90
  lookupSignature,
86
91
  type SignatureOverride,
@@ -16,18 +16,46 @@ export interface WrapOptions {
16
16
  importsFrom: string;
17
17
  }
18
18
 
19
- export function wrapAsAmbientGlobal(opts: WrapOptions): string {
20
- const used = collectEngineTypes(opts.emitted);
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 "${opts.importsFrom}";\n\n`;
23
- const inner = opts.emitted.replace(/(^|\n)declare\s+namespace\s+/, "$1namespace ").trimEnd();
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) || member.type !== "function") continue;
78
- elements.push({
79
- type: "FUNCTION",
80
- name: `${namespace}.${stringOr(member.name, "")}`,
81
- description: stringOr(member.desc, ""),
82
- parameters: mapParameters(member.parameters),
83
- returnvalues: mapParameters(member.returns),
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, "") },