@defold-typescript/types 0.8.4 → 0.10.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 (50) hide show
  1. package/README.md +1 -1
  2. package/api-targets.json +18 -0
  3. package/generated/b2d_body.d.ts +348 -0
  4. package/generated/buffer.d.ts +5 -2
  5. package/generated/camera.d.ts +236 -1
  6. package/generated/collectionfactory.d.ts +4 -0
  7. package/generated/factory.d.ts +4 -0
  8. package/generated/font.d.ts +81 -0
  9. package/generated/go.d.ts +53 -0
  10. package/generated/gui.d.ts +265 -50
  11. package/generated/html5.d.ts +17 -13
  12. package/generated/http.d.ts +16 -0
  13. package/generated/image.d.ts +24 -0
  14. package/generated/json.d.ts +2 -0
  15. package/generated/kinds/gui-script.d.ts +2 -0
  16. package/generated/kinds/render-script.d.ts +2 -0
  17. package/generated/kinds/script.d.ts +2 -0
  18. package/generated/model.d.ts +14 -0
  19. package/generated/msg.d.ts +17 -11
  20. package/generated/particlefx.d.ts +6 -0
  21. package/generated/physics.d.ts +32 -0
  22. package/generated/profiler.d.ts +14 -0
  23. package/generated/render.d.ts +109 -0
  24. package/generated/resource.d.ts +223 -0
  25. package/generated/socket.d.ts +640 -11
  26. package/generated/sound.d.ts +6 -0
  27. package/generated/sprite.d.ts +5 -0
  28. package/generated/sys.d.ts +136 -0
  29. package/generated/tilemap.d.ts +2 -0
  30. package/generated/timer.d.ts +3 -0
  31. package/generated/vmath.d.ts +109 -93
  32. package/generated/window.d.ts +23 -0
  33. package/index.d.ts +8 -0
  34. package/package.json +4 -1
  35. package/scripts/fidelity-audit.ts +26 -3
  36. package/scripts/fidelity-baseline.json +18 -2
  37. package/scripts/materialize-version.ts +23 -0
  38. package/scripts/regen.ts +5 -1
  39. package/scripts/signature-store-io.ts +18 -0
  40. package/scripts/sync-api-docs.ts +208 -12
  41. package/src/core-types.ts +13 -6
  42. package/src/doc-comment.ts +42 -5
  43. package/src/emit-dts.ts +134 -5
  44. package/src/engine-globals.d.ts +2 -0
  45. package/src/example-store.ts +11 -7
  46. package/src/index.ts +18 -1
  47. package/src/lifecycle.ts +383 -4
  48. package/src/msg-overloads.d.ts +3 -0
  49. package/src/signature-store.ts +20 -0
  50. package/src/socket-types.d.ts +48 -0
@@ -29,18 +29,20 @@ export function htmlToDocText(html: string): string {
29
29
  .replace(/<li>/gi, "\n- ")
30
30
  .replace(/<\/li>/gi, "")
31
31
  .replace(/<br\s*\/?>/gi, "\n")
32
+ .replace(/<\/p>/gi, "\n\n")
32
33
  .replace(/<\/?pre>/gi, "\n")
33
34
  .replace(/<[^>]+>/g, "");
34
35
 
35
36
  text = decodeEntities(text);
36
37
 
37
- // Collapse horizontal whitespace runs, trim around newlines, drop blank
38
- // runs, then trim the whole string — preserving single newlines from lists
39
- // and `<br>`.
38
+ // Collapse horizontal whitespace runs, trim around newlines, fold runaway
39
+ // blank runs to one blank line, then trim the whole string — preserving
40
+ // single newlines from lists and `<br>`, and the paragraph-break blank line
41
+ // from `</p>`.
40
42
  text = text
41
43
  .replace(/[ \t]+/g, " ")
42
44
  .replace(/ *\n */g, "\n")
43
- .replace(/\n{2,}/g, "\n")
45
+ .replace(/\n{3,}/g, "\n\n")
44
46
  .trim();
45
47
 
46
48
  // A literal `*/` would close the JSDoc comment early; escape it.
@@ -68,6 +70,41 @@ export function htmlToCodeText(html: string): string {
68
70
  return collapsed.split("*/").join("*\\/");
69
71
  }
70
72
 
73
+ /**
74
+ * Convert a ref-doc `examples` HTML fragment — prose interleaved with one or
75
+ * more `<div class="codehilite">…</div>` syntax-highlight blocks — into Markdown:
76
+ * prose runs become `htmlToDocText`, each highlight block becomes a ` ```lua `
77
+ * fence via `htmlToCodeText`. A fragment with no `codehilite` block is wrapped
78
+ * whole as a single ` ```lua ` fence (back-compat for plain-code examples).
79
+ * Returns `""` for empty / whitespace-only input.
80
+ */
81
+ export function examplesHtmlToMarkdown(html: string): string {
82
+ if (html.trim() === "") return "";
83
+
84
+ const parts: string[] = [];
85
+ const blocks = /<div class="codehilite">([\s\S]*?)<\/div>/gi;
86
+ let lastIndex = 0;
87
+ let matched = false;
88
+ for (let match = blocks.exec(html); match !== null; match = blocks.exec(html)) {
89
+ matched = true;
90
+ const prose = htmlToDocText(html.slice(lastIndex, match.index));
91
+ if (prose !== "") parts.push(prose);
92
+ const code = htmlToCodeText(match[1] ?? "");
93
+ if (code !== "") parts.push(`\`\`\`lua\n${code}\n\`\`\``);
94
+ lastIndex = match.index + match[0].length;
95
+ }
96
+
97
+ if (!matched) {
98
+ const code = htmlToCodeText(html);
99
+ return code === "" ? "" : `\`\`\`lua\n${code}\n\`\`\``;
100
+ }
101
+
102
+ const trailing = htmlToDocText(html.slice(lastIndex));
103
+ if (trailing !== "") parts.push(trailing);
104
+
105
+ return parts.join("\n\n");
106
+ }
107
+
71
108
  export interface DocCommentParts {
72
109
  summary: string;
73
110
  params?: { name: string; doc: string }[];
@@ -92,7 +129,7 @@ export function renderDocComment(parts: DocCommentParts): string[] {
92
129
 
93
130
  const lines = ["/**"];
94
131
  for (const line of summaryLines) {
95
- lines.push(` * ${line}`);
132
+ lines.push(line === "" ? " *" : ` * ${line}`);
96
133
  }
97
134
 
98
135
  const hasTags = params.length > 0 || returns !== "" || example !== "";
package/src/emit-dts.ts CHANGED
@@ -180,6 +180,14 @@ export const MAPPING_TABLE_SLOTS: ReadonlyMap<string, { key: string; value: stri
180
180
  ["gui.get_layouts", { key: "hash", value: "vector3" }],
181
181
  ]);
182
182
 
183
+ // FQN-keyed return-type overrides for functions whose doc `returnvalues` are
184
+ // empty yet the engine returns a value. `gui.get`'s doc lists no return, but it
185
+ // yields the property value (a vmath.vector4 or a number); `unknown` is the
186
+ // honest, ts-defold-matching shape. Per-FQN, not a blanket "empty returnvalues
187
+ // -> unknown" rule: `gui.set` also has empty returnvalues and `void` is correct
188
+ // there.
189
+ export const RETURN_TYPE_OVERRIDES: ReadonlyMap<string, string> = new Map([["gui.get", "unknown"]]);
190
+
183
191
  // Element names whose `table` slot is a prose-only `array/list/table of <T>` shape
184
192
  // the field-list parser cannot read, but whose element type a human curated from
185
193
  // the doc. The value is a single element token (`T[]`) or a token list when the
@@ -219,7 +227,7 @@ export type TableSlotCuration =
219
227
  | { kind: "object"; fields: readonly TableField[] }
220
228
  | { kind: "array-object"; fields: readonly TableField[] };
221
229
 
222
- const SOCKET_HANDLE_TOKENS = ["client", "master", "unconnected"] as const;
230
+ export const SOCKET_HANDLE_TOKENS = ["client", "master", "unconnected"] as const;
223
231
 
224
232
  export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new Map([
225
233
  ["collectionfactory.create:return:ids", { kind: "mapping", key: "hash", value: "hash" }],
@@ -866,6 +874,42 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
866
874
  : a.name.localeCompare(b.name),
867
875
  );
868
876
 
877
+ // One-level nested functions (`socket.dns.toip`) fail the flat-identifier test
878
+ // in `prepareFunction` above (the stripped `dns.toip` has a dot), so they are
879
+ // absent from `functions`. Re-collect them grouped by their single leading
880
+ // segment, re-stripping against `<namespace>.<segment>.` so the emitted
881
+ // identifier is the final segment. Only exactly-one-dot, both-sides-identifier
882
+ // locals qualify; deeper nesting and non-identifier segments stay dropped.
883
+ const nestedFunctionLocal = /^[A-Za-z_$][\w$]*\.[A-Za-z_$][\w$]*$/;
884
+ const nestedGroups = new Map<string, PreparedFunction[]>();
885
+ for (const fn of module.functions) {
886
+ const local = stripPrefix(fn.name, prefix);
887
+ if (!nestedFunctionLocal.test(local)) continue;
888
+ const segment = local.slice(0, local.indexOf("."));
889
+ const prepared = prepareFunction(fn, `${module.namespace}.${segment}.`);
890
+ if (prepared === null) continue;
891
+ const group = nestedGroups.get(segment) ?? [];
892
+ group.push(prepared);
893
+ nestedGroups.set(segment, group);
894
+ }
895
+ for (const group of nestedGroups.values()) {
896
+ group.sort((a, b) =>
897
+ a.name === b.name
898
+ ? a.original.parameters.length - b.original.parameters.length
899
+ : a.name.localeCompare(b.name),
900
+ );
901
+ }
902
+ const nestedSegments = [...nestedGroups.keys()].sort((a, b) => a.localeCompare(b));
903
+
904
+ // Colon methods (`client:send`) are FUNCTION elements named `<receiver>:<method>`
905
+ // and are NOT namespace-prefixed, so they fail the flat-identifier test in
906
+ // prepareFunction (the colon) and are absent from `functions`. A returned handle
907
+ // is defined entirely by these methods; emit each receiver as a method-bearing
908
+ // interface, the same non-flat recovery the dns nested-namespace pass does one
909
+ // container shape shallower.
910
+ const handleGroups = collectHandleMethodGroups(module);
911
+ const handleReceivers = [...handleGroups.keys()].sort((a, b) => a.localeCompare(b));
912
+
869
913
  const resolver = buildTableDocResolver(
870
914
  module.functions.map((fn) => ({
871
915
  name: fn.name,
@@ -891,8 +935,23 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
891
935
  lines.push(`declare namespace ${module.namespace} {`);
892
936
 
893
937
  for (const t of typedefs) {
938
+ // A typedef that also has colon methods is emitted as a method-bearing
939
+ // interface below, not an opaque brand alias.
940
+ if (handleGroups.has(t.name)) continue;
894
941
  lines.push(`${INDENT}${decl}type ${t.name} = Opaque<"${t.name}">;`);
895
942
  }
943
+ const handleIndent = `${INDENT}${INDENT}`;
944
+ for (const receiver of handleReceivers) {
945
+ const group = handleGroups.get(receiver) ?? [];
946
+ lines.push(`${INDENT}${decl}interface ${receiver} {`);
947
+ for (const fn of group) {
948
+ for (const docLine of functionDocLines(fn.original, translations, handleIndent)) {
949
+ lines.push(docLine);
950
+ }
951
+ lines.push(`${handleIndent}${emitMethod(fn, mapType, resolver)}`);
952
+ }
953
+ lines.push(`${INDENT}}`);
954
+ }
896
955
  for (const c of constants) {
897
956
  for (const docLine of summaryDocLines(c.original.brief, c.original.description, INDENT)) {
898
957
  lines.push(docLine);
@@ -926,6 +985,19 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
926
985
  lines.push(`${INDENT}export { ${alias.internal} as ${alias.public} };`);
927
986
  }
928
987
 
988
+ const nestedIndent = `${INDENT}${INDENT}`;
989
+ for (const segment of nestedSegments) {
990
+ const group = nestedGroups.get(segment) ?? [];
991
+ lines.push(`${INDENT}${decl}namespace ${segment} {`);
992
+ for (const fn of group) {
993
+ for (const docLine of functionDocLines(fn.original, translations, nestedIndent)) {
994
+ lines.push(docLine);
995
+ }
996
+ lines.push(`${nestedIndent}${decl}${emitFunction(fn, fn.name, mapType, resolver)}`);
997
+ }
998
+ lines.push(`${INDENT}}`);
999
+ }
1000
+
929
1001
  if (module.properties.length > 0) {
930
1002
  const members = [...module.properties].sort((a, b) => a.name.localeCompare(b.name));
931
1003
  lines.push(`${INDENT}${decl}interface properties {`);
@@ -964,6 +1036,37 @@ function prepareFunction(fn: ApiFunction, prefix: string): PreparedFunction | nu
964
1036
  return { name: stripped, original: fn };
965
1037
  }
966
1038
 
1039
+ // A `<receiver>:<method>` handle method, both segments bare identifiers. Deeper
1040
+ // shapes and non-identifier segments do not match and stay dropped.
1041
+ export const HANDLE_METHOD_LOCAL = /^[A-Za-z_$][\w$]*:[A-Za-z_$][\w$]*$/;
1042
+
1043
+ // Group a module's colon-method FUNCTION elements (`client:send`) by their
1044
+ // receiver segment, each method prepared so its emitted name is the final
1045
+ // segment. Receivers (and the methods within each) are returned unsorted; the
1046
+ // emitter sorts for a stable surface. A non-identifier segment yields no group.
1047
+ export function collectHandleMethodGroups(module: ApiModule): Map<string, PreparedFunction[]> {
1048
+ const prefix = `${module.namespace}.`;
1049
+ const groups = new Map<string, PreparedFunction[]>();
1050
+ for (const fn of module.functions) {
1051
+ const local = stripPrefix(fn.name, prefix);
1052
+ if (!HANDLE_METHOD_LOCAL.test(local)) continue;
1053
+ const receiver = local.slice(0, local.indexOf(":"));
1054
+ const prepared = prepareFunction(fn, fn.name.slice(0, fn.name.lastIndexOf(":") + 1));
1055
+ if (prepared === null) continue;
1056
+ const group = groups.get(receiver) ?? [];
1057
+ group.push(prepared);
1058
+ groups.set(receiver, group);
1059
+ }
1060
+ for (const group of groups.values()) {
1061
+ group.sort((a, b) =>
1062
+ a.name === b.name
1063
+ ? a.original.parameters.length - b.original.parameters.length
1064
+ : a.name.localeCompare(b.name),
1065
+ );
1066
+ }
1067
+ return groups;
1068
+ }
1069
+
967
1070
  function prepareConstant(c: ApiConstant, prefix: string): PreparedConstant | null {
968
1071
  const stripped = stripPrefix(c.name, prefix);
969
1072
  if (!TS_IDENTIFIER.test(stripped)) return null;
@@ -1007,7 +1110,7 @@ function emitVariable(
1007
1110
  return `const ${name}: ${ts};`;
1008
1111
  }
1009
1112
 
1010
- function emitFunction(
1113
+ function memberSignature(
1011
1114
  prepared: PreparedFunction,
1012
1115
  name: string,
1013
1116
  mapType: (t: string) => string,
@@ -1020,7 +1123,27 @@ function emitFunction(
1020
1123
  .map((p, i) => emitParameter(p, i, i >= cutoff, mapType, resolver, elementName))
1021
1124
  .join(", ");
1022
1125
  const ret = emitReturn(prepared.original.returnValues, mapType, resolver, elementName);
1023
- return `function ${name}(${params}): ${ret.type};${ret.trailing}`;
1126
+ return `${name}(${params}): ${ret.type};${ret.trailing}`;
1127
+ }
1128
+
1129
+ function emitFunction(
1130
+ prepared: PreparedFunction,
1131
+ name: string,
1132
+ mapType: (t: string) => string,
1133
+ resolver: TableDocResolver,
1134
+ ): string {
1135
+ return `function ${memberSignature(prepared, name, mapType, resolver)}`;
1136
+ }
1137
+
1138
+ // A colon-method member of a handle interface: identical signature machinery to a
1139
+ // free function, but without the `function` keyword. `@noSelfInFile` means the
1140
+ // member carries no `self` parameter.
1141
+ function emitMethod(
1142
+ prepared: PreparedFunction,
1143
+ mapType: (t: string) => string,
1144
+ resolver: TableDocResolver,
1145
+ ): string {
1146
+ return memberSignature(prepared, prepared.name, mapType, resolver);
1024
1147
  }
1025
1148
 
1026
1149
  // Build the indented JSDoc lines for a function from its ref-doc prose. The
@@ -1029,7 +1152,11 @@ function emitFunction(
1029
1152
  // fallback applies to non-identifier names, matching `emitParameter`) so the tag
1030
1153
  // resolves on hover; a single documented return becomes `@returns`. Returns `[]`
1031
1154
  // for a fully-undocumented function, leaving its emission byte-identical.
1032
- function functionDocLines(fn: ApiFunction, translations: TranslationStore): string[] {
1155
+ function functionDocLines(
1156
+ fn: ApiFunction,
1157
+ translations: TranslationStore,
1158
+ indent: string = INDENT,
1159
+ ): string[] {
1033
1160
  const params = fn.parameters.map((p, index) => ({
1034
1161
  name: safeParamName(p.name, index),
1035
1162
  doc: htmlToDocText(p.doc),
@@ -1047,7 +1174,7 @@ function functionDocLines(fn: ApiFunction, translations: TranslationStore): stri
1047
1174
  ...(onlyReturn ? { returns: htmlToDocText(onlyReturn.doc) } : {}),
1048
1175
  ...exampleParts,
1049
1176
  };
1050
- return indentDocLines(parts, INDENT);
1177
+ return indentDocLines(parts, indent);
1051
1178
  }
1052
1179
 
1053
1180
  // Summary-only doc lines for a member that carries no params or returns
@@ -1116,6 +1243,8 @@ function emitReturn(
1116
1243
  resolver: TableDocResolver,
1117
1244
  elementName: string,
1118
1245
  ): { type: string; trailing: string } {
1246
+ const override = RETURN_TYPE_OVERRIDES.get(elementName);
1247
+ if (override !== undefined) return { type: override, trailing: "" };
1119
1248
  if (returnValues.length === 0) return { type: "void", trailing: "" };
1120
1249
  if (returnValues.length > 1) {
1121
1250
  // Defold multi-returns are positional and always present; each slot maps
@@ -4,6 +4,8 @@ import type * as Core from "./core-types";
4
4
  declare global {
5
5
  type Hash = Core.Hash;
6
6
  function hash(s: string): Core.Hash;
7
+ function hash_to_hex(h: Core.Hash): string;
8
+ function pprint(v: unknown): void;
7
9
  type Opaque<Name extends string> = Core.Opaque<Name>;
8
10
  type Url = Core.Url;
9
11
  type Vector = Core.Vector;
@@ -7,7 +7,10 @@ export interface Translation {
7
7
  ts: string;
8
8
  }
9
9
 
10
- export type TranslationStore = Record<string, Translation>;
10
+ // An FQN maps to one translation per distinct example body it carries: an
11
+ // overloaded element (same name, differing `@example` source) contributes one
12
+ // array entry per body, each pinned by its own `sourceHash`.
13
+ export type TranslationStore = Record<string, Translation[]>;
11
14
 
12
15
  const FNV_OFFSET_BASIS = 0xcbf29ce484222325n;
13
16
  const FNV_PRIME = 0x100000001b3n;
@@ -30,15 +33,16 @@ export function hashExampleSource(source: string): string {
30
33
  return hash.toString(16).padStart(16, "0");
31
34
  }
32
35
 
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
+ // Return the stored TypeScript body only when the FQN exists and one of its
37
+ // pinned `sourceHash`es matches the source we are about to emit; any mismatch
38
+ // returns `null` so the caller keeps the Lua fallback.
36
39
  export function lookupTranslation(
37
40
  store: TranslationStore,
38
41
  fqn: string,
39
42
  sourceHash: string,
40
43
  ): string | null {
41
- const entry = store[fqn];
42
- if (!entry || entry.sourceHash !== sourceHash) return null;
43
- return entry.ts;
44
+ const entries = store[fqn];
45
+ if (!entries) return null;
46
+ const match = entries.find((entry) => entry.sourceHash === sourceHash);
47
+ return match ? match.ts : null;
44
48
  }
package/src/index.ts CHANGED
@@ -10,8 +10,20 @@ export {
10
10
  type Vector3,
11
11
  type Vector4,
12
12
  } from "./core-types";
13
- export { type DocCommentParts, htmlToDocText, renderDocComment } from "./doc-comment";
13
+ export {
14
+ type DocCommentParts,
15
+ examplesHtmlToMarkdown,
16
+ htmlToCodeText,
17
+ htmlToDocText,
18
+ renderDocComment,
19
+ } from "./doc-comment";
14
20
  export { type EmitOptions, emitDeclarations } from "./emit-dts";
21
+ export {
22
+ hashExampleSource,
23
+ lookupTranslation,
24
+ type Translation,
25
+ type TranslationStore,
26
+ } from "./example-store";
15
27
  export {
16
28
  defineGuiScript,
17
29
  defineRenderScript,
@@ -30,3 +42,8 @@ export {
30
42
  type ScriptProperty,
31
43
  } from "./lifecycle";
32
44
  export { type WrapOptions, wrapAsAmbientGlobal } from "./publish-dts";
45
+ export {
46
+ lookupSignature,
47
+ type SignatureOverride,
48
+ type SignatureStore,
49
+ } from "./signature-store";