@defold-typescript/types 0.24.0 → 0.26.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.
Files changed (41) hide show
  1. package/api-availability.json +333 -306
  2. package/api-signatures.json +47 -46
  3. package/api-targets.json +66 -2
  4. package/generated/collectionproxy.d.ts +22 -0
  5. package/generated/editor-vm/http.d.ts +56 -0
  6. package/generated/editor-vm/image.d.ts +48 -0
  7. package/generated/editor-vm/json.d.ts +10 -0
  8. package/generated/editor-vm/localization.d.ts +42 -0
  9. package/generated/editor-vm/tilemap_tiles.d.ts +80 -0
  10. package/generated/editor-vm/zip.d.ts +10 -0
  11. package/generated/editor-vm/zlib.d.ts +24 -0
  12. package/generated/editor.d.ts +1111 -0
  13. package/generated/go.d.ts +22 -22
  14. package/generated/gui.d.ts +35 -6
  15. package/generated/kinds/editor-script.d.ts +14 -0
  16. package/generated/kinds/gui-script.d.ts +1 -0
  17. package/generated/kinds/render-script.d.ts +1 -0
  18. package/generated/kinds/script.d.ts +1 -0
  19. package/generated/material.d.ts +4 -0
  20. package/generated/versions/defold-1.12.4/go.d.ts +22 -22
  21. package/generated/versions/defold-1.12.4/gui.d.ts +43 -43
  22. package/index.d.ts +9 -0
  23. package/package.json +9 -1
  24. package/scripts/import-defold-release.ts +10 -0
  25. package/scripts/materialize-version.ts +54 -13
  26. package/scripts/regen.ts +425 -42
  27. package/scripts/sync-api-docs.ts +86 -9
  28. package/src/api-availability.ts +0 -0
  29. package/src/core-types.ts +15 -1
  30. package/src/editor-overloads.d.ts +33 -0
  31. package/src/editor-vm-globals.d.ts +200 -0
  32. package/src/editor-vm-types.ts +44 -0
  33. package/src/editor.ts +117 -16
  34. package/src/emit-dts.ts +298 -79
  35. package/src/engine-globals.d.ts +2 -2
  36. package/src/go-overloads.d.ts +6 -6
  37. package/src/index.ts +15 -0
  38. package/src/msg-overloads.d.ts +3 -3
  39. package/src/scene-addresses.d.ts +62 -0
  40. package/src/url-parameters.ts +174 -0
  41. package/url-parameters.json +285 -0
package/src/emit-dts.ts CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  } from "./doc-comment";
17
17
  import type { TranslationStore } from "./example-store";
18
18
  import { hashExampleSource, lookupTranslation } from "./example-store";
19
+ import { classifyUrlParameter, type UrlParameterTable } from "./url-parameters";
19
20
 
20
21
  export interface EmitOptions {
21
22
  mapType?: (defoldType: string) => string;
@@ -30,6 +31,12 @@ export interface EmitOptions {
30
31
  // `examples/translations.json`. Loading lives in `scripts/example-store-io.ts`
31
32
  // so this module stays node-free for downstream consumers.
32
33
  translations?: TranslationStore;
34
+ // Which parameters address the scene graph, so their `string` member emits a
35
+ // scene-derived address alias instead. Defaults to an empty table — every
36
+ // slot classifies `none` and the output is byte-identical to an un-retyped
37
+ // emit. `regen` supplies the committed `url-parameters.json`; the table
38
+ // arrives as data so this module stays free of `node:fs` (bug-88).
39
+ urlParameters?: UrlParameterTable;
33
40
  }
34
41
 
35
42
  export const TS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
@@ -81,6 +88,7 @@ export const TS_RESERVED_NAMES = new Set([
81
88
  "with",
82
89
  "debugger",
83
90
  "extends",
91
+ "enum",
84
92
  ]);
85
93
 
86
94
  // A parameter name that clears `TS_IDENTIFIER` but is a TS reserved word (e.g.
@@ -149,6 +157,12 @@ export const ARBITRARY_TABLE_SLOTS = new Set([
149
157
  // is opaque to the field-list parser and the whole-slot `Record` is faithful.
150
158
  "render.render_target",
151
159
  "render.set_render_target",
160
+ // reserved-but-undocumented options bag: `collectionproxy.load`'s `options`
161
+ // slot is documented verbatim as "options table, currently unused" and accepts
162
+ // `nil`, so upstream defines no field list to emit. The whole-slot `Record` is
163
+ // faithful to what is documented today. If a later release fills the bag in,
164
+ // drop this entry so the missing fields resurface under `recordTables`.
165
+ "collectionproxy.load",
152
166
  ]);
153
167
 
154
168
  // Per-slot companion to ARBITRARY_TABLE_SLOTS (`element:kind:name`), for a
@@ -218,7 +232,13 @@ export const MAPPING_TABLE_SLOTS: ReadonlyMap<string, { key: string; value: stri
218
232
  // honest, ts-defold-matching shape. Per-FQN, not a blanket "empty returnvalues
219
233
  // -> unknown" rule: `gui.set` also has empty returnvalues and `void` is correct
220
234
  // there.
221
- export const RETURN_TYPE_OVERRIDES: ReadonlyMap<string, string> = new Map([["gui.get", "unknown"]]);
235
+ export const RETURN_TYPE_OVERRIDES: ReadonlyMap<string, string> = new Map([
236
+ ["gui.get", "unknown"],
237
+ // Upstream documents the returned transaction step in prose only — its
238
+ // `returnvalues` is empty while every sibling `editor.tx.*` builder declares
239
+ // one, and `editor.transact()` takes exactly that.
240
+ ["editor.tx.add", 'Opaque<"transaction_step">'],
241
+ ]);
222
242
 
223
243
  // FQN-keyed allowlist of the `types.is_*` checks that genuinely narrow their
224
244
  // argument, mapped to the `DEFOLD_TYPE_MAP` token whose interface they prove.
@@ -1426,6 +1446,7 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
1426
1446
  const constantFqns = new Set(module.constants.map((c) => c.name));
1427
1447
  const knownConstantFqns = options?.knownConstantFqns;
1428
1448
  const translations = options?.translations ?? {};
1449
+ const urlParameters = options?.urlParameters ?? [];
1429
1450
  const baseMapType = options?.mapType ?? defaultMapType;
1430
1451
  const mapType = (token: string): string =>
1431
1452
  constantFqns.has(token) || knownConstantFqns?.has(token)
@@ -1451,32 +1472,14 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
1451
1472
  : a.name.localeCompare(b.name),
1452
1473
  );
1453
1474
 
1454
- // One-level nested functions (`socket.dns.toip`) fail the flat-identifier test
1455
- // in `prepareFunction` above (the stripped `dns.toip` has a dot), so they are
1456
- // absent from `functions`. Re-collect them grouped by their single leading
1457
- // segment, re-stripping against `<namespace>.<segment>.` so the emitted
1458
- // identifier is the final segment. Only exactly-one-dot, both-sides-identifier
1459
- // locals qualify; deeper nesting and non-identifier segments stay dropped.
1460
- const nestedFunctionLocal = /^[A-Za-z_$][\w$]*\.[A-Za-z_$][\w$]*$/;
1461
- const nestedGroups = new Map<string, PreparedFunction[]>();
1462
- for (const fn of module.functions) {
1463
- const local = stripPrefix(fn.name, prefix);
1464
- if (!nestedFunctionLocal.test(local)) continue;
1465
- const segment = local.slice(0, local.indexOf("."));
1466
- const prepared = prepareFunction(fn, `${module.namespace}.${segment}.`);
1467
- if (prepared === null) continue;
1468
- const group = nestedGroups.get(segment) ?? [];
1469
- group.push(prepared);
1470
- nestedGroups.set(segment, group);
1471
- }
1472
- for (const group of nestedGroups.values()) {
1473
- group.sort((a, b) =>
1474
- a.name === b.name
1475
- ? a.original.parameters.length - b.original.parameters.length
1476
- : a.name.localeCompare(b.name),
1477
- );
1478
- }
1479
- const nestedSegments = [...nestedGroups.keys()].sort((a, b) => a.localeCompare(b));
1475
+ // Nested members (`socket.dns.toip`, `editor.ui.COLOR.TEXT`) fail the
1476
+ // flat-identifier test in `prepareVariable`/`prepareFunction` above (the
1477
+ // stripped local has a dot), so they are absent from `variables`/`functions`.
1478
+ // Re-collect them into a tree keyed by their full leading path, re-stripping
1479
+ // against `<namespace>.<path>.` so the emitted identifier is the final
1480
+ // segment. One or two leading identifier segments qualify; anything deeper,
1481
+ // and any non-identifier segment, stays dropped.
1482
+ const nestedRoot = collectNestedGroups(module, prefix);
1480
1483
 
1481
1484
  // Colon methods (`client:send`) are FUNCTION elements named `<receiver>:<method>`
1482
1485
  // and are NOT namespace-prefixed, so they fail the flat-identifier test in
@@ -1525,7 +1528,7 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
1525
1528
  for (const docLine of functionDocLines(fn.original, translations, handleIndent)) {
1526
1529
  lines.push(docLine);
1527
1530
  }
1528
- lines.push(`${handleIndent}${emitMethod(fn, mapType, resolver)}`);
1531
+ lines.push(`${handleIndent}${emitMethod(fn, mapType, resolver, urlParameters)}`);
1529
1532
  }
1530
1533
  lines.push(`${INDENT}}`);
1531
1534
  }
@@ -1554,7 +1557,7 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
1554
1557
  const reserved = TS_RESERVED_NAMES.has(fn.name);
1555
1558
  const emitName = aliasName(fn.name, aliases);
1556
1559
  for (const docLine of functionDocLines(fn.original, translations)) lines.push(docLine);
1557
- const line = emitFunction(fn, emitName, mapType, resolver);
1560
+ const line = emitFunction(fn, emitName, mapType, resolver, urlParameters);
1558
1561
  lines.push(`${INDENT}${reserved ? "" : decl}${line}`);
1559
1562
  }
1560
1563
 
@@ -1562,31 +1565,57 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
1562
1565
  lines.push(`${INDENT}export { ${alias.internal} as ${alias.public} };`);
1563
1566
  }
1564
1567
 
1565
- const nestedIndent = `${INDENT}${INDENT}`;
1566
- for (const segment of nestedSegments) {
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 " : "";
1574
- lines.push(`${INDENT}${decl}namespace ${segment} {`);
1575
- for (const fn of group) {
1576
- const reserved = TS_RESERVED_NAMES.has(fn.name);
1577
- const emitName = aliasName(fn.name, segmentAliases);
1578
- for (const docLine of functionDocLines(fn.original, translations, nestedIndent)) {
1579
- lines.push(docLine);
1568
+ const emitNestedLevel = (
1569
+ level: ReadonlyMap<string, NestedGroup>,
1570
+ indent: string,
1571
+ outerDecl: string,
1572
+ ): void => {
1573
+ for (const segment of [...level.keys()].sort((a, b) => a.localeCompare(b))) {
1574
+ const group = level.get(segment) as NestedGroup;
1575
+ const bodyIndent = `${indent}${INDENT}`;
1576
+ // A reserved-name member inside a nested namespace gets the same recovery as
1577
+ // a top-level one: emitted un-exported as `_<name>` and re-exported under the
1578
+ // reserved name. The alias switches this namespace out of implicit-export mode,
1579
+ // so its siblings then need an explicit `export` to stay reachable.
1580
+ const segmentAliases: { internal: string; public: string }[] = [];
1581
+ const segmentDecl =
1582
+ group.variables.some((v) => TS_RESERVED_NAMES.has(v.name)) ||
1583
+ group.functions.some((fn) => TS_RESERVED_NAMES.has(fn.name))
1584
+ ? "export "
1585
+ : "";
1586
+ lines.push(`${indent}${outerDecl}namespace ${segment} {`);
1587
+ for (const v of group.variables) {
1588
+ const reserved = TS_RESERVED_NAMES.has(v.name);
1589
+ const emitName = aliasName(v.name, segmentAliases);
1590
+ for (const docLine of summaryDocLines(
1591
+ v.original.brief,
1592
+ v.original.description,
1593
+ bodyIndent,
1594
+ )) {
1595
+ lines.push(docLine);
1596
+ }
1597
+ lines.push(
1598
+ `${bodyIndent}${reserved ? "" : segmentDecl}${emitVariable(v, emitName, mapType)}`,
1599
+ );
1580
1600
  }
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} };`);
1601
+ for (const fn of group.functions) {
1602
+ const reserved = TS_RESERVED_NAMES.has(fn.name);
1603
+ const emitName = aliasName(fn.name, segmentAliases);
1604
+ for (const docLine of functionDocLines(fn.original, translations, bodyIndent)) {
1605
+ lines.push(docLine);
1606
+ }
1607
+ lines.push(
1608
+ `${bodyIndent}${reserved ? "" : segmentDecl}${emitFunction(fn, emitName, mapType, resolver, urlParameters)}`,
1609
+ );
1610
+ }
1611
+ for (const alias of [...segmentAliases].sort((a, b) => a.public.localeCompare(b.public))) {
1612
+ lines.push(`${bodyIndent}export { ${alias.internal} as ${alias.public} };`);
1613
+ }
1614
+ emitNestedLevel(group.children, bodyIndent, segmentDecl);
1615
+ lines.push(`${indent}}`);
1587
1616
  }
1588
- lines.push(`${INDENT}}`);
1589
- }
1617
+ };
1618
+ emitNestedLevel(nestedRoot, INDENT, decl);
1590
1619
 
1591
1620
  if (module.properties.length > 0) {
1592
1621
  const members = [...module.properties].sort((a, b) => a.name.localeCompare(b.name));
@@ -1616,13 +1645,17 @@ export interface SymbolSignature {
1616
1645
  * availability matrix joins on. This is the single declaration-backed source for
1617
1646
  * the shared `api-signatures.json` artifact: the same `mapType` (constant
1618
1647
  * branding) and table-doc resolver produce text that appears verbatim in the
1619
- * committed `.d.ts`. The caller applies the same `skipFunctions` filter the
1620
- * `.d.ts` generation does, so a dropped member never yields a signature.
1648
+ * committed `.d.ts`, and nested members come from the same
1649
+ * {@link collectNestedGroups} tree the declaration emitter walks, so neither
1650
+ * surface can decide on its own which members are nested or how deep they go.
1651
+ * The caller applies the same `skipFunctions` filter the `.d.ts` generation
1652
+ * does, so a dropped member never yields a signature.
1621
1653
  */
1622
1654
  export function emitSymbolSignatures(module: ApiModule, options?: EmitOptions): SymbolSignature[] {
1623
1655
  const prefix = `${module.namespace}.`;
1624
1656
  const constantFqns = new Set(module.constants.map((c) => c.name));
1625
1657
  const knownConstantFqns = options?.knownConstantFqns;
1658
+ const urlParameters = options?.urlParameters ?? [];
1626
1659
  const baseMapType = options?.mapType ?? defaultMapType;
1627
1660
  const mapType = (token: string): string =>
1628
1661
  constantFqns.has(token) || knownConstantFqns?.has(token)
@@ -1649,20 +1682,32 @@ export function emitSymbolSignatures(module: ApiModule, options?: EmitOptions):
1649
1682
  if (prepared === null) continue;
1650
1683
  out.push({
1651
1684
  identity: fnIdentity(fn),
1652
- tsSignature: emitFunction(prepared, emitName(prepared.name), mapType, resolver),
1685
+ tsSignature: emitFunction(
1686
+ prepared,
1687
+ emitName(prepared.name),
1688
+ mapType,
1689
+ resolver,
1690
+ urlParameters,
1691
+ ),
1653
1692
  });
1654
1693
  }
1655
1694
 
1656
- const nestedFunctionLocal = /^[A-Za-z_$][\w$]*\.[A-Za-z_$][\w$]*$/;
1657
- for (const fn of module.functions) {
1658
- const local = stripPrefix(fn.name, prefix);
1659
- if (!nestedFunctionLocal.test(local)) continue;
1660
- const segment = local.slice(0, local.indexOf("."));
1661
- const prepared = prepareFunction(fn, `${module.namespace}.${segment}.`);
1662
- if (prepared === null) continue;
1695
+ const nested = flattenNestedGroups(collectNestedGroups(module, prefix));
1696
+ for (const v of nested.variables) {
1663
1697
  out.push({
1664
- identity: fnIdentity(fn),
1665
- tsSignature: emitFunction(prepared, prepared.name, mapType, resolver),
1698
+ identity: {
1699
+ namespace: module.namespace,
1700
+ kind: "VARIABLE",
1701
+ name: v.original.name,
1702
+ signature: "",
1703
+ },
1704
+ tsSignature: emitVariable(v, emitName(v.name), mapType),
1705
+ });
1706
+ }
1707
+ for (const fn of nested.functions) {
1708
+ out.push({
1709
+ identity: fnIdentity(fn.original),
1710
+ tsSignature: emitFunction(fn, emitName(fn.name), mapType, resolver, urlParameters),
1666
1711
  });
1667
1712
  }
1668
1713
 
@@ -1671,7 +1716,7 @@ export function emitSymbolSignatures(module: ApiModule, options?: EmitOptions):
1671
1716
  for (const prepared of group) {
1672
1717
  out.push({
1673
1718
  identity: fnIdentity(prepared.original),
1674
- tsSignature: emitMethod(prepared, mapType, resolver),
1719
+ tsSignature: emitMethod(prepared, mapType, resolver, urlParameters),
1675
1720
  });
1676
1721
  }
1677
1722
  }
@@ -1728,6 +1773,92 @@ interface PreparedVariable {
1728
1773
  original: ApiVariable;
1729
1774
  }
1730
1775
 
1776
+ // One level of the nested-member tree: the members declared directly under a
1777
+ // segment, plus the segments declared beneath it.
1778
+ interface NestedGroup {
1779
+ variables: PreparedVariable[];
1780
+ functions: PreparedFunction[];
1781
+ children: Map<string, NestedGroup>;
1782
+ }
1783
+
1784
+ // A stripped local carrying one or two leading identifier segments before the
1785
+ // member name (`dns.toip`, `COLOR.TEXT`, `schema.integer`). Three levels deep is
1786
+ // beyond anything the vendored documents describe, so it stays dropped rather
1787
+ // than half-emitted.
1788
+ const NESTED_MEMBER_LOCAL = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*){1,2}$/;
1789
+
1790
+ function collectNestedGroups(module: ApiModule, prefix: string): Map<string, NestedGroup> {
1791
+ const root = new Map<string, NestedGroup>();
1792
+ const groupAt = (segments: readonly string[]): NestedGroup => {
1793
+ let level = root;
1794
+ let group: NestedGroup | undefined;
1795
+ for (const segment of segments) {
1796
+ let next = level.get(segment);
1797
+ if (next === undefined) {
1798
+ next = { variables: [], functions: [], children: new Map() };
1799
+ level.set(segment, next);
1800
+ }
1801
+ group = next;
1802
+ level = next.children;
1803
+ }
1804
+ return group as NestedGroup;
1805
+ };
1806
+ const pathOf = (local: string): string[] => local.split(".").slice(0, -1);
1807
+
1808
+ for (const v of module.variables) {
1809
+ const local = stripPrefix(v.name, prefix);
1810
+ if (!NESTED_MEMBER_LOCAL.test(local)) continue;
1811
+ const segments = pathOf(local);
1812
+ const prepared = prepareVariable(v, `${module.namespace}.${segments.join(".")}.`);
1813
+ if (prepared === null) continue;
1814
+ groupAt(segments).variables.push(prepared);
1815
+ }
1816
+ for (const fn of module.functions) {
1817
+ const local = stripPrefix(fn.name, prefix);
1818
+ if (!NESTED_MEMBER_LOCAL.test(local)) continue;
1819
+ const segments = pathOf(local);
1820
+ const prepared = prepareFunction(fn, `${module.namespace}.${segments.join(".")}.`);
1821
+ if (prepared === null) continue;
1822
+ groupAt(segments).functions.push(prepared);
1823
+ }
1824
+
1825
+ const sortLevel = (level: Map<string, NestedGroup>): void => {
1826
+ for (const group of level.values()) {
1827
+ group.variables.sort((a, b) => a.name.localeCompare(b.name));
1828
+ group.functions.sort((a, b) =>
1829
+ a.name === b.name
1830
+ ? a.original.parameters.length - b.original.parameters.length
1831
+ : a.name.localeCompare(b.name),
1832
+ );
1833
+ sortLevel(group.children);
1834
+ }
1835
+ };
1836
+ sortLevel(root);
1837
+ return root;
1838
+ }
1839
+
1840
+ // Every member a nested tree holds, at any depth, in the order the declaration
1841
+ // emitter walks it (segments sorted, each group's own members before its
1842
+ // children). Callers that need no per-segment container read the tree through
1843
+ // this rather than re-deriving which members are nested and how deep.
1844
+ function flattenNestedGroups(level: ReadonlyMap<string, NestedGroup>): {
1845
+ variables: PreparedVariable[];
1846
+ functions: PreparedFunction[];
1847
+ } {
1848
+ const variables: PreparedVariable[] = [];
1849
+ const functions: PreparedFunction[] = [];
1850
+ const walk = (current: ReadonlyMap<string, NestedGroup>): void => {
1851
+ for (const segment of [...current.keys()].sort((a, b) => a.localeCompare(b))) {
1852
+ const group = current.get(segment) as NestedGroup;
1853
+ variables.push(...group.variables);
1854
+ functions.push(...group.functions);
1855
+ walk(group.children);
1856
+ }
1857
+ };
1858
+ walk(level);
1859
+ return { variables, functions };
1860
+ }
1861
+
1731
1862
  function prepareFunction(fn: ApiFunction, prefix: string): PreparedFunction | null {
1732
1863
  const stripped = stripPrefix(fn.name, prefix);
1733
1864
  if (!TS_IDENTIFIER.test(stripped)) return null;
@@ -1813,13 +1944,23 @@ function memberSignature(
1813
1944
  name: string,
1814
1945
  mapType: (t: string) => string,
1815
1946
  resolver: TableDocResolver,
1947
+ urlParameters: UrlParameterTable,
1816
1948
  ): string {
1817
1949
  const original = prepared.original.parameters;
1818
1950
  const elementName = prepared.original.name;
1819
1951
  const cutoff = trailingOptionalCutoff(original);
1820
- const params = original
1821
- .map((p, i) => emitParameter(p, i, i >= cutoff, mapType, resolver, elementName))
1822
- .join(", ");
1952
+ const varargIndex = original.findIndex(isVarargParameter);
1953
+ const positional = (varargIndex === -1 ? original : original.slice(0, varargIndex)).map((p, i) =>
1954
+ emitParameter(p, i, i >= cutoff, mapType, resolver, elementName, urlParameters),
1955
+ );
1956
+ const params = (
1957
+ varargIndex === -1
1958
+ ? positional
1959
+ : [
1960
+ ...positional,
1961
+ emitRestParameter(original, varargIndex, mapType, resolver, elementName, urlParameters),
1962
+ ]
1963
+ ).join(", ");
1823
1964
  const ret = emitReturn(prepared.original.returnValues, mapType, resolver, elementName);
1824
1965
  const predicateToken = TYPE_PREDICATES.get(elementName);
1825
1966
  const soleParam = original[0];
@@ -1839,8 +1980,9 @@ function emitFunction(
1839
1980
  name: string,
1840
1981
  mapType: (t: string) => string,
1841
1982
  resolver: TableDocResolver,
1983
+ urlParameters: UrlParameterTable,
1842
1984
  ): string {
1843
- return `function ${memberSignature(prepared, name, mapType, resolver)}`;
1985
+ return `function ${memberSignature(prepared, name, mapType, resolver, urlParameters)}`;
1844
1986
  }
1845
1987
 
1846
1988
  // A colon-method member of a handle interface: identical signature machinery to a
@@ -1850,15 +1992,18 @@ function emitMethod(
1850
1992
  prepared: PreparedFunction,
1851
1993
  mapType: (t: string) => string,
1852
1994
  resolver: TableDocResolver,
1995
+ urlParameters: UrlParameterTable,
1853
1996
  ): string {
1854
- return memberSignature(prepared, prepared.name, mapType, resolver);
1997
+ return memberSignature(prepared, prepared.name, mapType, resolver, urlParameters);
1855
1998
  }
1856
1999
 
1857
2000
  // Build the indented JSDoc lines for a function from its ref-doc prose. The
1858
2001
  // summary prefers the full `description`, falling back to the one-line `brief`;
1859
2002
  // each `@param` name is the parameter's *emitted* name (the `arg<index>`
1860
- // fallback applies to non-identifier names, matching `emitParameter`) so the tag
1861
- // resolves on hover; a single documented return becomes `@returns`. Returns `[]`
2003
+ // fallback applies to non-identifier names and a vararg drops its dots, matching
2004
+ // `emitParameter` and `emitRestParameter`) so the tag resolves on hover; a
2005
+ // parameter folded into a rest element union keeps its own tag, so the prose
2006
+ // documenting it survives. A single documented return becomes `@returns`. Returns `[]`
1862
2007
  // for a fully-undocumented function, leaving its emission byte-identical.
1863
2008
  function functionDocLines(
1864
2009
  fn: ApiFunction,
@@ -1866,7 +2011,7 @@ function functionDocLines(
1866
2011
  indent: string = INDENT,
1867
2012
  ): string[] {
1868
2013
  const params = fn.parameters.map((p, index) => ({
1869
- name: safeParamName(p.name, index),
2014
+ name: emittedParamName(p, index),
1870
2015
  doc: htmlToDocText(p.doc),
1871
2016
  }));
1872
2017
  const onlyReturn = fn.returnValues.length === 1 ? fn.returnValues[0] : undefined;
@@ -1928,6 +2073,83 @@ function trailingOptionalCutoff(params: readonly ApiParameter[]): number {
1928
2073
  return cutoff;
1929
2074
  }
1930
2075
 
2076
+ const SCENE_ADDRESS_ALIASES: Readonly<Record<string, string>> = {
2077
+ "game-object": "SceneGameObjectAddress",
2078
+ component: "SceneComponentAddress",
2079
+ either: "SceneAddress",
2080
+ };
2081
+
2082
+ // A classified address slot keeps every mapped member except `string`, which
2083
+ // becomes the matching scene-derived alias. Wrapping `mapType` rather than
2084
+ // rewriting the finished union leaves `mapSlotUnion`'s member ordering and
2085
+ // de-duplication in charge, so only the one token moves.
2086
+ function addressMapType(mapType: (t: string) => string, alias: string): (t: string) => string {
2087
+ return (token) => (token === "string" ? alias : mapType(token));
2088
+ }
2089
+
2090
+ // The mapped slot type of a parameter, without the name, `?` or `| undefined`
2091
+ // decoration — so a parameter folded into a rest element union contributes its
2092
+ // type alone.
2093
+ function parameterType(
2094
+ p: ApiParameter,
2095
+ mapType: (t: string) => string,
2096
+ resolver: TableDocResolver,
2097
+ elementName: string,
2098
+ urlParameters: UrlParameterTable,
2099
+ ): string {
2100
+ const concrete = p.types.filter((t) => t !== "nil");
2101
+ // The table is keyed by the *raw* ref-doc parameter name, not the emitted
2102
+ // `safeParamName` fallback.
2103
+ const alias = SCENE_ADDRESS_ALIASES[classifyUrlParameter(urlParameters, elementName, p.name)];
2104
+ const slotMapType = alias === undefined ? mapType : addressMapType(mapType, alias);
2105
+ return concrete.length > 0
2106
+ ? mapSlotUnion(concrete, p.doc, slotMapType, true, resolver, elementName, "param", p.name)
2107
+ : "unknown";
2108
+ }
2109
+
2110
+ const VARARG_PREFIX = "...";
2111
+
2112
+ function isVarargParameter(p: ApiParameter): boolean {
2113
+ return p.name.startsWith(VARARG_PREFIX);
2114
+ }
2115
+
2116
+ // A vararg's emitted name is its documented name minus the dots (`...commands`
2117
+ // -> `commands`); a bare `...` carries none, so it becomes `args`.
2118
+ function varargParamName(rawName: string, index: number): string {
2119
+ const named = rawName.slice(VARARG_PREFIX.length);
2120
+ return named === "" ? "args" : safeParamName(named, index);
2121
+ }
2122
+
2123
+ function emittedParamName(p: ApiParameter, index: number): string {
2124
+ return isVarargParameter(p) ? varargParamName(p.name, index) : safeParamName(p.name, index);
2125
+ }
2126
+
2127
+ // TS1266 forbids a positional parameter after a rest one, so every parameter
2128
+ // documented *after* the vararg folds into the rest's element union — which is
2129
+ // the only shape that types `editor.execute("git", "log", { out: "capture" })`,
2130
+ // where upstream documents a trailing options table behind the vararg.
2131
+ function emitRestParameter(
2132
+ params: readonly ApiParameter[],
2133
+ varargIndex: number,
2134
+ mapType: (t: string) => string,
2135
+ resolver: TableDocResolver,
2136
+ elementName: string,
2137
+ urlParameters: UrlParameterTable,
2138
+ ): string {
2139
+ const vararg = params[varargIndex];
2140
+ if (vararg === undefined) return "";
2141
+ const members = [
2142
+ ...new Set(
2143
+ params
2144
+ .slice(varargIndex)
2145
+ .map((p) => parameterType(p, mapType, resolver, elementName, urlParameters)),
2146
+ ),
2147
+ ];
2148
+ const first = members[0] ?? "unknown";
2149
+ const element = members.length > 1 ? `(${members.join(" | ")})` : first;
2150
+ return `...${varargParamName(vararg.name, varargIndex)}: ${element}[]`;
2151
+ }
2152
+
1931
2153
  function emitParameter(
1932
2154
  p: ApiParameter,
1933
2155
  index: number,
@@ -1935,13 +2157,10 @@ function emitParameter(
1935
2157
  mapType: (t: string) => string,
1936
2158
  resolver: TableDocResolver,
1937
2159
  elementName: string,
2160
+ urlParameters: UrlParameterTable,
1938
2161
  ): string {
1939
2162
  const name = safeParamName(p.name, index);
1940
- const concrete = p.types.filter((t) => t !== "nil");
1941
- const ts =
1942
- concrete.length > 0
1943
- ? mapSlotUnion(concrete, p.doc, mapType, true, resolver, elementName, "param", p.name)
1944
- : "unknown";
2163
+ const ts = parameterType(p, mapType, resolver, elementName, urlParameters);
1945
2164
  // An interior doc-optional param (a required param follows, so the trailing-`?`
1946
2165
  // projection cannot mark it) keeps its optionality as `| undefined` — TSTL
1947
2166
  // lowers `undefined` to `nil`, the faithful call. Trailing optionals keep the
@@ -2193,7 +2412,7 @@ export function isKnownDefoldTypeToken(token: string): boolean {
2193
2412
  );
2194
2413
  }
2195
2414
 
2196
- function defaultMapType(token: string): string {
2415
+ export function defaultMapType(token: string): string {
2197
2416
  if (Object.hasOwn(DEFOLD_TYPE_MAP, token)) {
2198
2417
  const mapped = DEFOLD_TYPE_MAP[token];
2199
2418
  if (typeof mapped === "string") return mapped;
@@ -7,9 +7,9 @@ import type * as Core from "./core-types";
7
7
  // engine-globals test fails if the two fall out of sync.
8
8
  declare global {
9
9
  /** An opaque, branded handle to a hashed name; see {@link Core.Hash}. */
10
- type Hash = Core.Hash;
10
+ type Hash<S extends string = string> = Core.Hash<S>;
11
11
  /** Hash a string into the engine's `Hash` handle. */
12
- function hash(s: string): Core.Hash;
12
+ function hash<S extends string>(s: S): Core.Hash<S>;
13
13
  /** Render a `Hash` handle as its hexadecimal string. */
14
14
  function hash_to_hex(h: Core.Hash): string;
15
15
  /** Pretty-print any value to the console for debugging. */
@@ -33,17 +33,17 @@ declare global {
33
33
  * ```
34
34
  */
35
35
  function get<P>(): <K extends keyof P>(
36
- url: string | Hash | Url,
36
+ url: SceneAddress | Hash | Url,
37
37
  property: K,
38
38
  options?: GoPropertyOptions,
39
39
  ) => P[K];
40
40
  function get<K extends keyof go.properties>(
41
- url: string | Hash | Url,
41
+ url: SceneAddress | Hash | Url,
42
42
  property: K,
43
43
  options?: GoPropertyOptions,
44
44
  ): go.properties[K];
45
45
  function get(
46
- url: string | Hash | Url,
46
+ url: SceneAddress | Hash | Url,
47
47
  property: string | Hash,
48
48
  options?: GoPropertyOptions,
49
49
  ): number | boolean | Hash | Url | Vector3 | Vector4 | Quaternion | Opaque<"resource">;
@@ -66,19 +66,19 @@ declare global {
66
66
  * ```
67
67
  */
68
68
  function set<P>(): <K extends keyof P>(
69
- url: string | Hash | Url,
69
+ url: SceneAddress | Hash | Url,
70
70
  property: K,
71
71
  value: P[K],
72
72
  options?: GoPropertyOptions,
73
73
  ) => void;
74
74
  function set<K extends keyof go.properties>(
75
- url: string | Hash | Url,
75
+ url: SceneAddress | Hash | Url,
76
76
  property: K,
77
77
  value: go.properties[K],
78
78
  options?: GoPropertyOptions,
79
79
  ): void;
80
80
  function set(
81
- url: string | Hash | Url,
81
+ url: SceneAddress | Hash | Url,
82
82
  property: string | Hash,
83
83
  value: number | boolean | Hash | Url | Vector3 | Vector4 | Quaternion | Opaque<"resource">,
84
84
  options?: GoPropertyOptions,
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ export {
5
5
  type ApiSymbolIdentity,
6
6
  type AvailabilityLabel,
7
7
  type AvailabilityLabelKind,
8
+ type AvailabilityLabelOptions,
8
9
  applyMigrationOverlay,
9
10
  availabilityLabel,
10
11
  type Box2dBackend,
@@ -14,7 +15,9 @@ export {
14
15
  isSignatureTransition,
15
16
  type LogicalNameGroup,
16
17
  normalizedFunctionSignature,
18
+ signatureTransitionNames,
17
19
  symbolIdentityKey,
20
+ symbolNameKey,
18
21
  type VersionSurface,
19
22
  validateAvailability,
20
23
  } from "./api-availability";
@@ -91,3 +94,15 @@ export {
91
94
  type SignatureOverride,
92
95
  type SignatureStore,
93
96
  } from "./signature-store";
97
+ export {
98
+ classifyUrlParameter,
99
+ collectParameterSlots,
100
+ collectUrlParameterSlots,
101
+ parameterTypesSatisfyClass,
102
+ REQUIRED_TYPES,
103
+ type UrlParameterClass,
104
+ type UrlParameterEntry,
105
+ type UrlParameterSlot,
106
+ type UrlParameterSource,
107
+ type UrlParameterTable,
108
+ } from "./url-parameters";
@@ -31,12 +31,12 @@ declare global {
31
31
  * ```
32
32
  */
33
33
  function post<K extends string>(
34
- receiver: string | Url | Hash,
34
+ receiver: SceneAddress | Url | Hash,
35
35
  message_id: K,
36
36
  message?: MsgPostPayload<K>,
37
37
  ): void;
38
38
  function post(
39
- receiver: string | Url | Hash,
39
+ receiver: SceneAddress | Url | Hash,
40
40
  message_id: Hash,
41
41
  message?: Record<string | number, unknown>,
42
42
  ): void;
@@ -76,7 +76,7 @@ declare global {
76
76
  * ```
77
77
  */
78
78
  function url(): Url;
79
- function url(urlstring: string): Url;
79
+ function url(urlstring: SceneAddress): Url;
80
80
  function url(socket: string | Hash, path: string | Hash, fragment: string | Hash): Url;
81
81
  }
82
82
  }