@defold-typescript/types 0.38.0 → 0.40.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/api-availability.json +117 -0
- package/api-signatures.json +586 -480
- package/examples/translations.json +356 -180
- package/generated/b2d_body.d.ts +7 -4
- package/generated/b2d_shape.d.ts +23 -1
- package/generated/buffer.d.ts +34 -19
- package/generated/camera.d.ts +3 -2
- package/generated/collectionfactory.d.ts +17 -12
- package/generated/collectionproxy.d.ts +2 -2
- package/generated/compute.d.ts +1 -1
- package/generated/factory.d.ts +2 -1
- package/generated/go.d.ts +150 -91
- package/generated/graphics.d.ts +54 -0
- package/generated/gui.d.ts +117 -71
- package/generated/html5.d.ts +4 -2
- package/generated/http.d.ts +8 -2
- package/generated/image.d.ts +24 -15
- package/generated/kinds/gui-script.d.ts +1 -0
- package/generated/kinds/render-script.d.ts +1 -0
- package/generated/kinds/script.d.ts +1 -0
- package/generated/material.d.ts +1 -1
- package/generated/model.d.ts +41 -7
- package/generated/particlefx.d.ts +1 -1
- package/generated/physics.d.ts +29 -20
- package/generated/profiler.d.ts +10 -3
- package/generated/render.d.ts +293 -111
- package/generated/resource.d.ts +243 -226
- package/generated/socket.d.ts +33 -18
- package/generated/sound.d.ts +6 -5
- package/generated/sprite.d.ts +9 -1
- package/generated/sys.d.ts +44 -27
- package/generated/tilemap.d.ts +25 -19
- package/generated/timer.d.ts +33 -24
- package/generated/versions/defold-1.12.4/buffer.d.ts +34 -19
- package/generated/versions/defold-1.12.4/camera.d.ts +3 -2
- package/generated/versions/defold-1.12.4/collectionfactory.d.ts +17 -12
- package/generated/versions/defold-1.12.4/collectionproxy.d.ts +1 -1
- package/generated/versions/defold-1.12.4/factory.d.ts +2 -1
- package/generated/versions/defold-1.12.4/go.d.ts +150 -91
- package/generated/versions/defold-1.12.4/graphics.d.ts +2 -0
- package/generated/versions/defold-1.12.4/gui.d.ts +109 -69
- package/generated/versions/defold-1.12.4/html5.d.ts +4 -2
- package/generated/versions/defold-1.12.4/http.d.ts +8 -2
- package/generated/versions/defold-1.12.4/image.d.ts +24 -15
- package/generated/versions/defold-1.12.4/index.d.ts +1 -0
- package/generated/versions/defold-1.12.4/model.d.ts +41 -7
- package/generated/versions/defold-1.12.4/particlefx.d.ts +1 -1
- package/generated/versions/defold-1.12.4/physics.d.ts +29 -20
- package/generated/versions/defold-1.12.4/profiler.d.ts +10 -3
- package/generated/versions/defold-1.12.4/render.d.ts +292 -110
- package/generated/versions/defold-1.12.4/resource.d.ts +243 -226
- package/generated/versions/defold-1.12.4/socket.d.ts +33 -18
- package/generated/versions/defold-1.12.4/sound.d.ts +6 -5
- package/generated/versions/defold-1.12.4/sprite.d.ts +9 -1
- package/generated/versions/defold-1.12.4/sys.d.ts +44 -27
- package/generated/versions/defold-1.12.4/tilemap.d.ts +25 -19
- package/generated/versions/defold-1.12.4/timer.d.ts +33 -24
- package/generated/versions/defold-1.12.4/vmath.d.ts +18 -10
- package/generated/versions/defold-1.12.4/window.d.ts +5 -3
- package/generated/versions/defold-1.13.0/b2d_body.d.ts +7 -4
- package/generated/versions/defold-1.13.0/b2d_shape.d.ts +23 -1
- package/generated/versions/defold-1.13.0/buffer.d.ts +34 -19
- package/generated/versions/defold-1.13.0/camera.d.ts +3 -2
- package/generated/versions/defold-1.13.0/collectionfactory.d.ts +17 -12
- package/generated/versions/defold-1.13.0/collectionproxy.d.ts +1 -1
- package/generated/versions/defold-1.13.0/compute.d.ts +1 -1
- package/generated/versions/defold-1.13.0/factory.d.ts +2 -1
- package/generated/versions/defold-1.13.0/go.d.ts +150 -91
- package/generated/versions/defold-1.13.0/graphics.d.ts +50 -0
- package/generated/versions/defold-1.13.0/gui.d.ts +109 -69
- package/generated/versions/defold-1.13.0/html5.d.ts +4 -2
- package/generated/versions/defold-1.13.0/http.d.ts +8 -2
- package/generated/versions/defold-1.13.0/image.d.ts +24 -15
- package/generated/versions/defold-1.13.0/index.d.ts +1 -0
- package/generated/versions/defold-1.13.0/material.d.ts +1 -1
- package/generated/versions/defold-1.13.0/model.d.ts +41 -7
- package/generated/versions/defold-1.13.0/particlefx.d.ts +1 -1
- package/generated/versions/defold-1.13.0/physics.d.ts +29 -20
- package/generated/versions/defold-1.13.0/profiler.d.ts +10 -3
- package/generated/versions/defold-1.13.0/render.d.ts +293 -111
- package/generated/versions/defold-1.13.0/resource.d.ts +243 -226
- package/generated/versions/defold-1.13.0/socket.d.ts +33 -18
- package/generated/versions/defold-1.13.0/sound.d.ts +6 -5
- package/generated/versions/defold-1.13.0/sprite.d.ts +9 -1
- package/generated/versions/defold-1.13.0/sys.d.ts +44 -27
- package/generated/versions/defold-1.13.0/tilemap.d.ts +25 -19
- package/generated/versions/defold-1.13.0/timer.d.ts +33 -24
- package/generated/versions/defold-1.13.0/vmath.d.ts +18 -10
- package/generated/versions/defold-1.13.0/window.d.ts +5 -3
- package/generated/vmath.d.ts +18 -10
- package/generated/window.d.ts +5 -3
- package/index.d.ts +1 -0
- package/package.json +2 -1
- package/scripts/generate-api-signatures.ts +2 -1
- package/scripts/prose-constants.ts +96 -0
- package/scripts/regen.ts +6 -2
- package/src/api-doc.ts +8 -0
- package/src/component-properties.d.ts +33 -0
- package/src/core-types.ts +4 -2
- package/src/custom-messages.d.ts +4 -3
- package/src/doc-comment.ts +247 -43
- package/src/emit-dts.ts +958 -57
- package/src/engine-globals.d.ts +2 -2
- package/src/example-store.ts +26 -0
- package/src/go-overloads.d.ts +11 -10
- package/src/index.ts +9 -0
- package/src/param-correction-provenance.ts +67 -0
- package/src/render-overloads.d.ts +15 -17
- package/src/script-api.ts +3 -1
package/generated/window.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ declare global {
|
|
|
5
5
|
* and screen dimming.
|
|
6
6
|
*/
|
|
7
7
|
namespace window {
|
|
8
|
+
type DimModeState = typeof window.DIMMING_UNKNOWN | typeof window.DIMMING_ON | typeof window.DIMMING_OFF;
|
|
9
|
+
type DimModeStateSettable = typeof window.DIMMING_ON | typeof window.DIMMING_OFF;
|
|
8
10
|
/**
|
|
9
11
|
* Dimming mode is used to control whether or not a mobile device should dim the screen after a period without user interaction.
|
|
10
12
|
*/
|
|
@@ -56,7 +58,7 @@ declare global {
|
|
|
56
58
|
*
|
|
57
59
|
* - `window.DIMMING_OFF`
|
|
58
60
|
*/
|
|
59
|
-
function get_dim_mode():
|
|
61
|
+
function get_dim_mode(): window.DimModeState;
|
|
60
62
|
/**
|
|
61
63
|
* This returns the content scale of the current display.
|
|
62
64
|
*
|
|
@@ -111,7 +113,7 @@ declare global {
|
|
|
111
113
|
*
|
|
112
114
|
* - `window.DIMMING_OFF`
|
|
113
115
|
*/
|
|
114
|
-
function set_dim_mode(mode:
|
|
116
|
+
function set_dim_mode(mode: window.DimModeStateSettable): void;
|
|
115
117
|
/**
|
|
116
118
|
* Sets a window event listener. Only one window event listener can be set at a time.
|
|
117
119
|
*
|
|
@@ -140,7 +142,7 @@ declare global {
|
|
|
140
142
|
* - number `height`: The height of a resize event. nil otherwise.
|
|
141
143
|
* @example
|
|
142
144
|
* ```ts
|
|
143
|
-
* function window_callback(self, event, data) {
|
|
145
|
+
* function window_callback(self: unknown, event: unknown, data: Record<string | number, unknown>) {
|
|
144
146
|
* if (event === window.WINDOW_EVENT_FOCUS_LOST) {
|
|
145
147
|
* print("window.WINDOW_EVENT_FOCUS_LOST");
|
|
146
148
|
* } else if (event === window.WINDOW_EVENT_FOCUS_GAINED) {
|
package/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defold-typescript/types",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.0",
|
|
4
4
|
"description": "TypeScript types for the Defold engine's Lua APIs.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
"!scripts/fidelity-audit.ts",
|
|
71
71
|
"!scripts/augmentation-namespaces.ts",
|
|
72
72
|
"!scripts/example-pins.ts",
|
|
73
|
+
"!scripts/example-semantics.ts",
|
|
73
74
|
"!scripts/example-surfaces.ts",
|
|
74
75
|
"!scripts/example-typecheck.ts"
|
|
75
76
|
],
|
|
@@ -14,7 +14,8 @@ const PACKAGE_ROOT = resolve(import.meta.dir, "..");
|
|
|
14
14
|
const SIGNATURES_PATH = resolve(PACKAGE_ROOT, "api-signatures.json");
|
|
15
15
|
|
|
16
16
|
export interface SignaturesArtifact {
|
|
17
|
-
// version -> symbolIdentityKey -> authoritative TS signature text
|
|
17
|
+
// version -> symbolIdentityKey -> authoritative TS signature text; a symbol
|
|
18
|
+
// with several declared arms carries one arm per line, primary first
|
|
18
19
|
readonly versions: Record<string, Record<string, string>>;
|
|
19
20
|
// The same axis and keys, carrying each documented slot's rendered type. Kept
|
|
20
21
|
// parallel rather than folded into `versions` because three consumers
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { ModuleManifestEntry } from "./regen";
|
|
2
|
+
|
|
3
|
+
// A constant family the engine registers and one function's parameter prose
|
|
4
|
+
// lists, but no ref-doc element declares. Each release's own list is the
|
|
5
|
+
// evidence, so a surface declares exactly the members its release documents.
|
|
6
|
+
interface ProseConstantFamily {
|
|
7
|
+
readonly namespace: string;
|
|
8
|
+
readonly prefix: string;
|
|
9
|
+
readonly describes: string;
|
|
10
|
+
readonly evidence: {
|
|
11
|
+
readonly namespace: string;
|
|
12
|
+
readonly function: string;
|
|
13
|
+
readonly parameter: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const PROSE_CONSTANT_FAMILIES: readonly ProseConstantFamily[] = [
|
|
18
|
+
{
|
|
19
|
+
namespace: "graphics",
|
|
20
|
+
prefix: "SEMANTIC_TYPE_",
|
|
21
|
+
describes: "Vertex attribute semantic type",
|
|
22
|
+
evidence: {
|
|
23
|
+
namespace: "material",
|
|
24
|
+
function: "material.set_vertex_attributes",
|
|
25
|
+
parameter: "attributes",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
interface DocElement {
|
|
31
|
+
readonly type: string;
|
|
32
|
+
readonly name: string;
|
|
33
|
+
readonly parameters?: readonly { readonly name: string; readonly doc?: string }[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface ApiDoc {
|
|
37
|
+
readonly elements: readonly DocElement[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function escapeRegExp(text: string): string {
|
|
41
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function evidencedNames(family: ProseConstantFamily, modules: readonly ModuleManifestEntry[]) {
|
|
45
|
+
const source = modules.find((m) => m.namespace === family.evidence.namespace);
|
|
46
|
+
if (!source) return [];
|
|
47
|
+
const fn = (source.doc as ApiDoc).elements.find(
|
|
48
|
+
(e) => e.type === "FUNCTION" && e.name === family.evidence.function,
|
|
49
|
+
);
|
|
50
|
+
const prose = fn?.parameters?.find((p) => p.name === family.evidence.parameter)?.doc ?? "";
|
|
51
|
+
const item = new RegExp(
|
|
52
|
+
`<li><code>(${escapeRegExp(`${family.namespace}.${family.prefix}`)}\\w+)</code></li>`,
|
|
53
|
+
"g",
|
|
54
|
+
);
|
|
55
|
+
return [...new Set(Array.from(prose.matchAll(item), (m) => m[1] as string))];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function constantElement(name: string, brief: string) {
|
|
59
|
+
return {
|
|
60
|
+
type: "CONSTANT",
|
|
61
|
+
name,
|
|
62
|
+
brief,
|
|
63
|
+
description: "",
|
|
64
|
+
returnvalues: [],
|
|
65
|
+
parameters: [],
|
|
66
|
+
examples: "",
|
|
67
|
+
replaces: "",
|
|
68
|
+
error: "",
|
|
69
|
+
tparams: [],
|
|
70
|
+
members: [],
|
|
71
|
+
notes: [],
|
|
72
|
+
language: "",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function synthesizeProseConstants(
|
|
77
|
+
modules: readonly ModuleManifestEntry[],
|
|
78
|
+
): ModuleManifestEntry[] {
|
|
79
|
+
let out = [...modules];
|
|
80
|
+
for (const family of PROSE_CONSTANT_FAMILIES) {
|
|
81
|
+
const names = evidencedNames(family, out);
|
|
82
|
+
if (names.length === 0) continue;
|
|
83
|
+
const index = out.findIndex((m) => m.namespace === family.namespace);
|
|
84
|
+
const target = out[index];
|
|
85
|
+
if (!target) continue;
|
|
86
|
+
const doc = target.doc as ApiDoc;
|
|
87
|
+
const declared = new Set(doc.elements.map((e) => e.name));
|
|
88
|
+
const brief = `${family.describes}, for \`${family.evidence.function}\`.`;
|
|
89
|
+
const added = names.filter((n) => !declared.has(n)).map((n) => constantElement(n, brief));
|
|
90
|
+
if (added.length === 0) continue;
|
|
91
|
+
out = out.map((m, i) =>
|
|
92
|
+
i === index ? { ...m, doc: { ...doc, elements: [...doc.elements, ...added] } } : m,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
package/scripts/regen.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
resolveRefDoc,
|
|
27
27
|
} from "./doc-source";
|
|
28
28
|
import { loadTranslations } from "./example-store-io";
|
|
29
|
+
import { synthesizeProseConstants } from "./prose-constants";
|
|
29
30
|
import { type readZip, SYNC_MANIFEST, type SyncManifestEntry } from "./sync-api-docs";
|
|
30
31
|
|
|
31
32
|
export interface ApiTargetModule {
|
|
@@ -99,7 +100,7 @@ export function loadTargetModules(
|
|
|
99
100
|
target: ApiTarget,
|
|
100
101
|
packageRoot: string = PACKAGE_ROOT,
|
|
101
102
|
): ModuleManifestEntry[] {
|
|
102
|
-
|
|
103
|
+
const modules = target.modules.map((module) => {
|
|
103
104
|
const path = resolve(packageRoot, target.fixturesDir, module.fixture);
|
|
104
105
|
let raw: string;
|
|
105
106
|
try {
|
|
@@ -117,6 +118,7 @@ export function loadTargetModules(
|
|
|
117
118
|
};
|
|
118
119
|
return module.skipFunctions ? { ...entry, skipFunctions: module.skipFunctions } : entry;
|
|
119
120
|
});
|
|
121
|
+
return synthesizeProseConstants(modules);
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
export interface ModuleManifestEntry {
|
|
@@ -168,7 +170,7 @@ export async function resolveTargetModules(
|
|
|
168
170
|
...(opts.fetchChannelInfo ? { fetchChannelInfo: opts.fetchChannelInfo } : {}),
|
|
169
171
|
});
|
|
170
172
|
const syncManifest = opts.syncManifest ?? SYNC_MANIFEST;
|
|
171
|
-
|
|
173
|
+
const modules = target.modules.map((module) => {
|
|
172
174
|
const sync = syncManifest.find((s) => s.namespace === module.namespace);
|
|
173
175
|
if (!sync) {
|
|
174
176
|
throw new Error(
|
|
@@ -184,6 +186,7 @@ export async function resolveTargetModules(
|
|
|
184
186
|
};
|
|
185
187
|
return module.skipFunctions ? { ...entry, skipFunctions: module.skipFunctions } : entry;
|
|
186
188
|
});
|
|
189
|
+
return synthesizeProseConstants(modules);
|
|
187
190
|
}
|
|
188
191
|
|
|
189
192
|
const API_TARGETS = loadApiTargets();
|
|
@@ -556,6 +559,7 @@ const UNIVERSAL_EXTRA_IMPORTS: readonly UniversalExtraImport[] = [
|
|
|
556
559
|
{ specifier: "../../src/window-event-guard" },
|
|
557
560
|
{ specifier: "../../src/scene-addresses" },
|
|
558
561
|
{ specifier: "../../src/go-overloads" },
|
|
562
|
+
{ specifier: "../../src/component-properties" },
|
|
559
563
|
{ specifier: "../../src/render-overloads", restrictedTo: "render" },
|
|
560
564
|
{ specifier: "../../src/vmath-overloads" },
|
|
561
565
|
];
|
package/src/api-doc.ts
CHANGED
|
@@ -20,6 +20,10 @@ export interface ApiTypedef {
|
|
|
20
20
|
name: string;
|
|
21
21
|
functions?: ApiFunction[];
|
|
22
22
|
properties?: ApiVariable[];
|
|
23
|
+
/** A union alias's top-level arms; a shape with members carries none. */
|
|
24
|
+
types?: string[];
|
|
25
|
+
/** The type text of each parent an interface-backed typedef extends. */
|
|
26
|
+
extends?: string[];
|
|
23
27
|
/** See {@link ApiFunction.global}. */
|
|
24
28
|
global?: true;
|
|
25
29
|
}
|
|
@@ -164,10 +168,14 @@ export function parseDefoldApiDoc(input: unknown): ApiModule {
|
|
|
164
168
|
function parseTypedef(element: Record<string, unknown>): ApiTypedef {
|
|
165
169
|
const functions = parseFunctionList(element.functions);
|
|
166
170
|
const properties = parseVariableList(element.properties);
|
|
171
|
+
const types = parseStringArray(element.types);
|
|
172
|
+
const parents = parseStringArray(element.extends);
|
|
167
173
|
return {
|
|
168
174
|
name: stringOr(element.name, ""),
|
|
169
175
|
...(functions.length > 0 ? { functions } : {}),
|
|
170
176
|
...(properties.length > 0 ? { properties } : {}),
|
|
177
|
+
...(types.length > 0 ? { types } : {}),
|
|
178
|
+
...(parents.length > 0 ? { extends: parents } : {}),
|
|
171
179
|
...globalKey(element),
|
|
172
180
|
};
|
|
173
181
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** @noSelfInFile */
|
|
2
|
+
|
|
3
|
+
import type { Hash } from "./core-types";
|
|
4
|
+
|
|
5
|
+
// Component property keys upstream examples read through `go.get` while no
|
|
6
|
+
// ref-doc catalog declares them. Only evidenced keys belong here.
|
|
7
|
+
declare global {
|
|
8
|
+
namespace sprite {
|
|
9
|
+
interface properties {
|
|
10
|
+
/**
|
|
11
|
+
* The texture bound to the sprite's first sampler. Not in the sprite
|
|
12
|
+
* ref-doc; `resource.set`'s example reads it with
|
|
13
|
+
* `go.get("#sprite", "texture0")`.
|
|
14
|
+
*/
|
|
15
|
+
texture0: Hash;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Mesh component properties. Upstream ships no mesh API module, so this
|
|
21
|
+
* catalog holds only the keys upstream examples read.
|
|
22
|
+
*/
|
|
23
|
+
namespace mesh {
|
|
24
|
+
interface properties {
|
|
25
|
+
/**
|
|
26
|
+
* The buffer resource holding the mesh's vertex data.
|
|
27
|
+
* `resource.get_buffer` and `resource.set_buffer` read it with
|
|
28
|
+
* `go.get("#mesh", "vertices")`.
|
|
29
|
+
*/
|
|
30
|
+
vertices: Hash;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/core-types.ts
CHANGED
|
@@ -214,9 +214,11 @@ export const DEFOLD_TYPE_MAP: Readonly<Record<string, string>> = {
|
|
|
214
214
|
texture: 'Opaque<"texture">',
|
|
215
215
|
render_target: 'Opaque<"render_target">',
|
|
216
216
|
constant: 'Opaque<"constant">',
|
|
217
|
-
constant_buffer:
|
|
217
|
+
constant_buffer:
|
|
218
|
+
'Opaque<"constant_buffer"> & { [name: string]: Vector4 | Matrix4 | Vector4[] | Matrix4[] }',
|
|
218
219
|
buffer: 'Opaque<"buffer">',
|
|
219
|
-
bufferstream:
|
|
220
|
+
bufferstream:
|
|
221
|
+
'Opaque<"bufferstream"> & { [index: number]: number; length: LuaLengthMethod<number> }',
|
|
220
222
|
userdata: 'Opaque<"userdata">',
|
|
221
223
|
// Vocabulary, not a shipped brand: no current ref-doc fixture types a slot
|
|
222
224
|
// `resource`, so nothing hand-written may name `Opaque<"resource">`. Kept so a
|
package/src/custom-messages.d.ts
CHANGED
|
@@ -41,14 +41,15 @@ declare global {
|
|
|
41
41
|
// diagnostic at the call site rather than the declaration.
|
|
42
42
|
/**
|
|
43
43
|
* The payload declared for a message id: its `BuiltinMessages` entry, else
|
|
44
|
-
* its `CustomMessages` entry, else the open
|
|
45
|
-
* An id declared in both resolves to the built-in
|
|
44
|
+
* its `CustomMessages` entry, else the open table an undeclared id keeps: a
|
|
45
|
+
* record or a Lua sequence. An id declared in both resolves to the built-in
|
|
46
|
+
* payload.
|
|
46
47
|
*/
|
|
47
48
|
type MessagePayload<K> = K extends BuiltinMessageId
|
|
48
49
|
? BuiltinMessages[K]
|
|
49
50
|
: K extends keyof CustomMessages
|
|
50
51
|
? CustomMessages[K]
|
|
51
|
-
: Record<string | number, unknown
|
|
52
|
+
: Record<string | number, unknown> | readonly unknown[];
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
export {};
|
package/src/doc-comment.ts
CHANGED
|
@@ -1,19 +1,83 @@
|
|
|
1
1
|
const NAMED_ENTITIES: Record<string, string> = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
amp: "&",
|
|
3
|
+
lt: "<",
|
|
4
|
+
gt: ">",
|
|
5
|
+
quot: '"',
|
|
6
|
+
apos: "'",
|
|
7
|
+
// A non-breaking space in JSDoc is invisible noise; a plain space reads the same.
|
|
8
|
+
nbsp: " ",
|
|
9
|
+
times: "×",
|
|
10
|
+
copy: "©",
|
|
11
|
+
mdash: "—",
|
|
12
|
+
ndash: "–",
|
|
13
|
+
hellip: "…",
|
|
14
|
+
middot: "·",
|
|
15
|
+
deg: "°",
|
|
16
|
+
plusmn: "±",
|
|
17
|
+
le: "≤",
|
|
18
|
+
ge: "≥",
|
|
19
|
+
ne: "≠",
|
|
20
|
+
rarr: "→",
|
|
21
|
+
larr: "←",
|
|
22
|
+
lsquo: "‘",
|
|
23
|
+
rsquo: "’",
|
|
24
|
+
ldquo: "“",
|
|
25
|
+
rdquo: "”",
|
|
7
26
|
};
|
|
8
27
|
|
|
28
|
+
// One pass over the text, so a character an entity decodes to (`&` -> `&`)
|
|
29
|
+
// is never read as the start of another entity.
|
|
9
30
|
function decodeEntities(text: string): string {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
31
|
+
return text.replace(/&(?:#[xX]([0-9a-fA-F]+)|#(\d+)|([a-zA-Z]+));/g, (entity, hex, dec, name) => {
|
|
32
|
+
if (hex !== undefined) return codePointOr(Number.parseInt(hex, 16), entity);
|
|
33
|
+
if (dec !== undefined) return codePointOr(Number(dec), entity);
|
|
34
|
+
return Object.hasOwn(NAMED_ENTITIES, name) ? (NAMED_ENTITIES[name] ?? entity) : entity;
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function codePointOr(code: number, fallback: string): string {
|
|
39
|
+
return code <= 0x10ffff ? String.fromCodePoint(code) : fallback;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Join a list marker left alone on its line back onto the item's first content
|
|
44
|
+
* line. Upstream prose puts a newline between `<li>` and the item's first word,
|
|
45
|
+
* which the whitespace pass preserves, dropping the value out of the list. The
|
|
46
|
+
* item's remaining lines stay where they are: Markdown's lazy continuation
|
|
47
|
+
* already keeps an unindented line inside the item's paragraph.
|
|
48
|
+
*/
|
|
49
|
+
function weldStrandedListMarkers(text: string): string {
|
|
50
|
+
const lines = text.split("\n");
|
|
51
|
+
const out: string[] = [];
|
|
52
|
+
let inFence = false;
|
|
53
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
54
|
+
const line = lines[index] ?? "";
|
|
55
|
+
if (line.startsWith("```")) {
|
|
56
|
+
inFence = !inFence;
|
|
57
|
+
out.push(line);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (inFence || line !== "-") {
|
|
61
|
+
out.push(line);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
let ahead = index + 1;
|
|
65
|
+
while (ahead < lines.length && lines[ahead] === "") ahead += 1;
|
|
66
|
+
const content = lines[ahead];
|
|
67
|
+
if (
|
|
68
|
+
content === undefined ||
|
|
69
|
+
content === "-" ||
|
|
70
|
+
content.startsWith("- ") ||
|
|
71
|
+
content.startsWith("```")
|
|
72
|
+
) {
|
|
73
|
+
// No value to weld onto — drop the marker and the blank run behind it.
|
|
74
|
+
index = ahead - 1;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
out.push(`- ${content}`);
|
|
78
|
+
index = ahead;
|
|
13
79
|
}
|
|
14
|
-
|
|
15
|
-
// `&` last so an already-decoded `&` is never re-interpreted.
|
|
16
|
-
return out.split("&").join("&");
|
|
80
|
+
return out.join("\n").trim();
|
|
17
81
|
}
|
|
18
82
|
|
|
19
83
|
/**
|
|
@@ -45,6 +109,12 @@ export function htmlToDocText(html: string): string {
|
|
|
45
109
|
.replace(/\n{3,}/g, "\n\n")
|
|
46
110
|
.trim();
|
|
47
111
|
|
|
112
|
+
text = weldStrandedListMarkers(text);
|
|
113
|
+
|
|
114
|
+
// Upstream prose can open a fence and end mid-body; left open it swallows the
|
|
115
|
+
// rest of the JSDoc block and everything after it on the rendered page.
|
|
116
|
+
if ((text.match(/^```/gm) ?? []).length % 2 === 1) text += "\n```";
|
|
117
|
+
|
|
48
118
|
// A literal `*/` would close the JSDoc comment early; escape it.
|
|
49
119
|
return text.split("*/").join("*\\/");
|
|
50
120
|
}
|
|
@@ -70,45 +140,169 @@ export function htmlToCodeText(html: string): string {
|
|
|
70
140
|
return collapsed.split("*/").join("*\\/");
|
|
71
141
|
}
|
|
72
142
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
* for plain-code examples). Returns `""` for empty / whitespace-only input.
|
|
82
|
-
*/
|
|
83
|
-
export function examplesHtmlToMarkdown(html: string): string {
|
|
84
|
-
if (html.trim() === "") return "";
|
|
143
|
+
// One run of the `examples` fragment: the inner markup of a `<div
|
|
144
|
+
// class="codehilite">` block, or the markup between two of them. `lang` is
|
|
145
|
+
// meaningful on a code region only.
|
|
146
|
+
interface ExampleRegion {
|
|
147
|
+
kind: "code" | "prose";
|
|
148
|
+
html: string;
|
|
149
|
+
lang: string;
|
|
150
|
+
}
|
|
85
151
|
|
|
86
|
-
|
|
152
|
+
// The one `codehilite` walk. Both the markdown lane (`examplesHtmlToMarkdown`)
|
|
153
|
+
// and the store lane (`splitExampleSources`) read their regions from here, so
|
|
154
|
+
// the two cannot disagree about where an example begins. `matched` is false for
|
|
155
|
+
// a fragment carrying no block at all, which both lanes treat as one whole-blob
|
|
156
|
+
// code run.
|
|
157
|
+
function scanExampleRegions(html: string): { matched: boolean; regions: ExampleRegion[] } {
|
|
158
|
+
const regions: ExampleRegion[] = [];
|
|
87
159
|
const blocks = /<div class="codehilite">([\s\S]*?)<\/div>/gi;
|
|
88
160
|
let lastIndex = 0;
|
|
89
161
|
let matched = false;
|
|
90
162
|
for (let match = blocks.exec(html); match !== null; match = blocks.exec(html)) {
|
|
91
163
|
matched = true;
|
|
92
|
-
|
|
93
|
-
if (prose !== "") parts.push(prose);
|
|
164
|
+
regions.push({ kind: "prose", html: html.slice(lastIndex, match.index), lang: "lua" });
|
|
94
165
|
const inner = match[1] ?? "";
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
166
|
+
regions.push({
|
|
167
|
+
kind: "code",
|
|
168
|
+
html: inner,
|
|
169
|
+
lang: /<code\b[^>]*\bclass="language-([^"\s]+)"/i.exec(inner)?.[1] ?? "lua",
|
|
170
|
+
});
|
|
98
171
|
lastIndex = match.index + match[0].length;
|
|
99
172
|
}
|
|
173
|
+
if (!matched) return { matched: false, regions: [{ kind: "code", html, lang: "lua" }] };
|
|
174
|
+
regions.push({ kind: "prose", html: html.slice(lastIndex), lang: "lua" });
|
|
175
|
+
return { matched: true, regions };
|
|
176
|
+
}
|
|
100
177
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
178
|
+
/**
|
|
179
|
+
* Convert a ref-doc `examples` HTML fragment — prose interleaved with one or
|
|
180
|
+
* more `<div class="codehilite">…</div>` syntax-highlight blocks — into Markdown,
|
|
181
|
+
* one fence per example that `segmentExampleRegions` carves out. Each segment
|
|
182
|
+
* emits its leading prose then a fence in its own language: `class="language-X"`
|
|
183
|
+
* on the block's `<code>` (as converted extension `.script_api` examples carry)
|
|
184
|
+
* or an inner ` ```X ` opener, else `lua`, since engine ref-doc blocks carry no
|
|
185
|
+
* language class. Prose trailing the last example is emitted last, and a
|
|
186
|
+
* fragment with no `codehilite` block is wrapped whole as a single ` ```lua `
|
|
187
|
+
* fence (back-compat for plain-code examples). Returns `""` for empty /
|
|
188
|
+
* whitespace-only input.
|
|
189
|
+
*/
|
|
190
|
+
export function examplesHtmlToMarkdown(html: string): string {
|
|
191
|
+
const { segments, trailingProse } = segmentExampleRegions(html);
|
|
105
192
|
|
|
106
|
-
const
|
|
107
|
-
|
|
193
|
+
const parts: string[] = [];
|
|
194
|
+
for (const segment of segments) {
|
|
195
|
+
if (segment.prose !== "") parts.push(segment.prose);
|
|
196
|
+
parts.push(`\`\`\`${segment.lang}\n${segment.code}\n\`\`\``);
|
|
197
|
+
}
|
|
198
|
+
if (trailingProse !== "") parts.push(trailingProse);
|
|
108
199
|
|
|
109
200
|
return parts.join("\n\n");
|
|
110
201
|
}
|
|
111
202
|
|
|
203
|
+
/** One example carved out of an `examples` fragment, with the prose leading it. */
|
|
204
|
+
export interface ExampleSegment {
|
|
205
|
+
prose: string;
|
|
206
|
+
code: string;
|
|
207
|
+
lang: string;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// A line that opens a code run: ``` followed by a bare language token and
|
|
211
|
+
// nothing else. Any other ``` line closes the run, its remainder being prose
|
|
212
|
+
// upstream welded onto the fence.
|
|
213
|
+
const FENCE_OPENER = /^```([A-Za-z0-9_+-]+)$/;
|
|
214
|
+
|
|
215
|
+
/** The examples a fragment carries, plus any prose left over after the last one. */
|
|
216
|
+
export interface ExampleSegmentation {
|
|
217
|
+
segments: ExampleSegment[];
|
|
218
|
+
/** Prose following the final code run, which belongs to no segment. */
|
|
219
|
+
trailingProse: string;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Carve an `examples` HTML fragment into one segment per example it carries.
|
|
224
|
+
*
|
|
225
|
+
* A `<div class="codehilite">` block is a code region and the markup between
|
|
226
|
+
* two of them is a prose region; inside either, a ` ``` ` line switches between
|
|
227
|
+
* code and prose, and an opener reached while already in code ends the running
|
|
228
|
+
* example and starts the next. Upstream writes several examples both ways — as
|
|
229
|
+
* several blocks, and as its own Markdown fences inside one block — so both
|
|
230
|
+
* signals are read.
|
|
231
|
+
*
|
|
232
|
+
* This is the walk alone: no whole-blob fallback, so a caller sees the real
|
|
233
|
+
* segments even where there is only one. `splitExampleSources` adds the
|
|
234
|
+
* stability post-pass that keys stored translations.
|
|
235
|
+
*/
|
|
236
|
+
export function segmentExampleRegions(html: string): ExampleSegmentation {
|
|
237
|
+
if (html.trim() === "") return { segments: [], trailingProse: "" };
|
|
238
|
+
|
|
239
|
+
const { regions } = scanExampleRegions(html);
|
|
240
|
+
const segments: ExampleSegment[] = [];
|
|
241
|
+
let codeLines: string[] = [];
|
|
242
|
+
let proseLines: string[] = [];
|
|
243
|
+
let lang = "lua";
|
|
244
|
+
|
|
245
|
+
const flush = () => {
|
|
246
|
+
const code = trimBlankEdges(codeLines).join("\n");
|
|
247
|
+
codeLines = [];
|
|
248
|
+
if (code === "") return;
|
|
249
|
+
segments.push({ prose: trimBlankEdges(proseLines).join("\n"), code, lang });
|
|
250
|
+
proseLines = [];
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
for (const region of regions) {
|
|
254
|
+
const text = region.kind === "code" ? htmlToCodeText(region.html) : htmlToDocText(region.html);
|
|
255
|
+
let inCode = region.kind === "code";
|
|
256
|
+
if (region.kind === "code") lang = region.lang;
|
|
257
|
+
// Prose surviving an empty code run keeps its paragraph break from the next.
|
|
258
|
+
if (region.kind === "prose" && text !== "" && proseLines.length > 0) proseLines.push("");
|
|
259
|
+
for (const line of text === "" ? [] : text.split("\n")) {
|
|
260
|
+
const trimmed = line.trim();
|
|
261
|
+
if (!trimmed.startsWith("```")) {
|
|
262
|
+
(inCode ? codeLines : proseLines).push(line);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const opener = FENCE_OPENER.exec(trimmed);
|
|
266
|
+
if (opener) {
|
|
267
|
+
if (inCode) flush();
|
|
268
|
+
inCode = true;
|
|
269
|
+
lang = opener[1] ?? lang;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
flush();
|
|
273
|
+
inCode = false;
|
|
274
|
+
const remainder = trimmed.slice(3).trim();
|
|
275
|
+
if (remainder !== "") proseLines.push(remainder);
|
|
276
|
+
}
|
|
277
|
+
flush();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return { segments, trailingProse: trimBlankEdges(proseLines).join("\n") };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* `segmentExampleRegions` plus the stability post-pass: a fragment that yields
|
|
285
|
+
* at most one segment returns the whole-blob `htmlToCodeText` verbatim, prose
|
|
286
|
+
* lines included. That is the string every stored translation is pinned
|
|
287
|
+
* against, so a single-example element keeps its identity and cannot be re-keyed
|
|
288
|
+
* by this walk.
|
|
289
|
+
*/
|
|
290
|
+
export function splitExampleSources(html: string): ExampleSegment[] {
|
|
291
|
+
const { segments } = segmentExampleRegions(html);
|
|
292
|
+
if (segments.length > 1) return segments;
|
|
293
|
+
if (html.trim() === "") return [];
|
|
294
|
+
const whole = htmlToCodeText(html);
|
|
295
|
+
if (whole === "") return [];
|
|
296
|
+
return [{ prose: "", code: whole, lang: segments[0]?.lang ?? "lua" }];
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function trimBlankEdges(lines: readonly string[]): string[] {
|
|
300
|
+
const out = [...lines];
|
|
301
|
+
while (out.length > 0 && out[0]?.trim() === "") out.shift();
|
|
302
|
+
while (out.length > 0 && out[out.length - 1]?.trim() === "") out.pop();
|
|
303
|
+
return out;
|
|
304
|
+
}
|
|
305
|
+
|
|
112
306
|
export interface DocCommentParts {
|
|
113
307
|
summary: string;
|
|
114
308
|
// Present exactly when the source carried a deprecation tag; `""` is the bare
|
|
@@ -117,8 +311,12 @@ export interface DocCommentParts {
|
|
|
117
311
|
deprecated?: string;
|
|
118
312
|
params?: { name: string; doc: string }[];
|
|
119
313
|
returns?: string;
|
|
120
|
-
example
|
|
121
|
-
|
|
314
|
+
// One entry per example the element documents, each rendered as its own
|
|
315
|
+
// `@example` block. An element whose ref-doc blob holds several examples
|
|
316
|
+
// carries several entries; a blank body is dropped. `prose` is the sentence
|
|
317
|
+
// introducing that example, rendered above its fence; blank or absent leaves
|
|
318
|
+
// the block byte-identical to a prose-less one.
|
|
319
|
+
examples?: { text: string; lang: "lua" | "ts"; prose?: string }[];
|
|
122
320
|
}
|
|
123
321
|
|
|
124
322
|
/**
|
|
@@ -129,14 +327,14 @@ export function renderDocComment(parts: DocCommentParts): string[] {
|
|
|
129
327
|
const summaryLines = parts.summary.trim() === "" ? [] : parts.summary.split("\n");
|
|
130
328
|
const params = (parts.params ?? []).filter((p) => p.doc.trim() !== "");
|
|
131
329
|
const returns = parts.returns?.trim() ? parts.returns : "";
|
|
132
|
-
const
|
|
330
|
+
const examples = (parts.examples ?? []).filter((entry) => entry.text.trim() !== "");
|
|
133
331
|
const deprecated = parts.deprecated;
|
|
134
332
|
|
|
135
333
|
if (
|
|
136
334
|
summaryLines.length === 0 &&
|
|
137
335
|
params.length === 0 &&
|
|
138
336
|
returns === "" &&
|
|
139
|
-
|
|
337
|
+
examples.length === 0 &&
|
|
140
338
|
deprecated === undefined
|
|
141
339
|
) {
|
|
142
340
|
return [];
|
|
@@ -147,7 +345,8 @@ export function renderDocComment(parts: DocCommentParts): string[] {
|
|
|
147
345
|
lines.push(line === "" ? " *" : ` * ${line}`);
|
|
148
346
|
}
|
|
149
347
|
|
|
150
|
-
const hasTags =
|
|
348
|
+
const hasTags =
|
|
349
|
+
deprecated !== undefined || params.length > 0 || returns !== "" || examples.length > 0;
|
|
151
350
|
if (summaryLines.length > 0 && hasTags) {
|
|
152
351
|
lines.push(" *");
|
|
153
352
|
}
|
|
@@ -173,10 +372,15 @@ export function renderDocComment(parts: DocCommentParts): string[] {
|
|
|
173
372
|
lines.push(line === "" ? " *" : ` * ${line}`);
|
|
174
373
|
}
|
|
175
374
|
}
|
|
176
|
-
|
|
375
|
+
for (const entry of examples) {
|
|
177
376
|
lines.push(" * @example");
|
|
178
|
-
|
|
179
|
-
|
|
377
|
+
if (entry.prose !== undefined && entry.prose.trim() !== "") {
|
|
378
|
+
for (const line of entry.prose.split("\n")) {
|
|
379
|
+
lines.push(line === "" ? " *" : ` * ${line}`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
lines.push(` * \`\`\`${entry.lang}`);
|
|
383
|
+
for (const line of entry.text.split("\n")) {
|
|
180
384
|
lines.push(line === "" ? " *" : ` * ${line}`);
|
|
181
385
|
}
|
|
182
386
|
lines.push(" * ```");
|