@defold-typescript/library-types 0.23.0 → 0.25.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/NOTICE +2 -3
- package/api-doc/boom.json +3872 -0
- package/api-doc/bridge.json +10 -5
- package/api-doc/bzAnim.json +338 -0
- package/api-doc/checkpoint.json +127 -0
- package/api-doc/decore.json +1 -1
- package/api-doc/defcon.json +150 -0
- package/api-doc/{defmath.defmath.json → defmath.json} +6 -46
- package/api-doc/defsave.json +518 -0
- package/api-doc/deftest.json +745 -0
- package/api-doc/{dicebag.dicebag.json → dicebag.json} +19 -1
- package/api-doc/druid.json +90 -92
- package/api-doc/event.json +16 -16
- package/api-doc/gooey.json +2050 -0
- package/api-doc/in.accelerometer.json +75 -1
- package/api-doc/in.button.json +17 -10
- package/api-doc/in.cursor.json +26 -0
- package/api-doc/in.gesture.json +401 -6
- package/api-doc/in.keyboard.json +2 -0
- package/api-doc/in.mapper.json +12 -8
- package/api-doc/in.onscreen.json +61 -5
- package/api-doc/in.state.json +61 -13
- package/api-doc/in.textbox.json +23 -12
- package/api-doc/in.triggers.json +336 -0
- package/api-doc/lang.json +5 -5
- package/api-doc/log.json +1 -1
- package/api-doc/metrics.fps.json +61 -2
- package/api-doc/metrics.mem.json +61 -2
- package/api-doc/monarch.monarch.json +249 -7
- package/api-doc/monarch.transitions.easings.json +19 -26
- package/api-doc/monarch.transitions.gui.json +782 -79
- package/api-doc/nakama.engine.defold.json +31 -18
- package/api-doc/{nakama.nakama.json → nakama.json} +4752 -2554
- package/api-doc/nakama.session.json +315 -0
- package/api-doc/nakama.socket.json +3356 -0
- package/api-doc/nakama.util.log.json +32 -4
- package/api-doc/narrator.json +1 -1
- package/api-doc/openapi/nakama.nakama.json +7680 -0
- package/api-doc/orthographic.camera.json +96 -98
- package/api-doc/orthographic.json +602 -0
- package/api-doc/panthera.json +1183 -0
- package/api-doc/{persist.persist.json → persist.json} +26 -1
- package/api-doc/{platypus.platypus.json → platypus.json} +68 -14
- package/api-doc/{rendy.rendy.json → rendy.json} +46 -99
- package/api-doc/richtext.color.json +77 -0
- package/api-doc/richtext.richtext.json +375 -5
- package/api-doc/saver.saver.json +2 -1
- package/api-doc/saver.storage.json +2 -1
- package/api-doc/shutter.json +551 -0
- package/api-doc/{yagames.yagames.json → yagames.json} +784 -189
- package/api-doc/{zzfx.api.json → zzfx.json} +9 -9
- package/authored-targets.json +433 -0
- package/generated/{boom.boom.d.ts → boom.d.ts} +14 -0
- package/generated/bridge.d.ts +5 -5
- package/generated/{bzAnim.bzLibrary.d.ts → bzAnim.d.ts} +6 -0
- package/generated/checkpoint.d.ts +62 -0
- package/generated/decore.d.ts +1 -1
- package/generated/defcon.d.ts +43 -0
- package/generated/{defmath.defmath.d.ts → defmath.d.ts} +3 -2
- package/generated/defsave.d.ts +144 -0
- package/generated/{dicebag.dicebag.d.ts → dicebag.d.ts} +25 -0
- package/generated/druid.d.ts +81 -79
- package/generated/event.d.ts +15 -15
- package/generated/{gooey.gooey.d.ts → gooey.d.ts} +61 -6
- package/generated/in.onscreen.d.ts +5 -0
- package/generated/in.textbox.d.ts +4 -0
- package/generated/lang.d.ts +5 -5
- package/generated/log.d.ts +1 -1
- package/generated/metrics.fps.d.ts +19 -1
- package/generated/metrics.mem.d.ts +19 -1
- package/generated/monarch.monarch.d.ts +44 -1
- package/generated/monarch.transitions.easings.d.ts +0 -1
- package/generated/monarch.transitions.gui.d.ts +96 -12
- package/generated/nakama.d.ts +1158 -0
- package/generated/nakama.engine.defold.d.ts +3 -1
- package/generated/nakama.session.d.ts +95 -0
- package/generated/nakama.socket.d.ts +706 -0
- package/generated/nakama.util.log.d.ts +6 -0
- package/generated/narrator.d.ts +1 -1
- package/generated/openapi/nakama.nakama.d.ts +884 -0
- package/generated/orthographic.camera.d.ts +24 -38
- package/generated/orthographic.d.ts +155 -0
- package/generated/panthera.d.ts +170 -0
- package/generated/{persist.persist.d.ts → persist.d.ts} +3 -0
- package/generated/{platypus.platypus.d.ts → platypus.d.ts} +9 -0
- package/generated/proto.d.ts +4 -4
- package/generated/{rendy.rendy.d.ts → rendy.d.ts} +33 -80
- package/generated/richtext.color.d.ts +15 -0
- package/generated/saver.saver.d.ts +2 -0
- package/generated/saver.storage.d.ts +2 -0
- package/generated/shutter.d.ts +183 -0
- package/generated/{yagames.yagames.d.ts → yagames.d.ts} +151 -29
- package/generated/{zzfx.api.d.ts → zzfx.d.ts} +7 -0
- package/library-classification.json +0 -156
- package/library-targets.json +1 -218
- package/luals-targets.json +56 -3
- package/openapi-targets.json +17 -0
- package/package.json +7 -57
- package/scripts/__snapshots__/parse-luals.test.ts.snap +164 -10
- package/scripts/apply-luals-overrides.ts +39 -7
- package/scripts/authored-parity.ts +718 -0
- package/scripts/emit-library-dts.ts +94 -17
- package/scripts/extract-api-doc.ts +214 -42
- package/scripts/fidelity-floor.ts +178 -0
- package/scripts/import-upstream-docs.ts +125 -0
- package/scripts/lower-api-doc.ts +21 -3
- package/scripts/luals-fidelity.ts +16 -11
- package/scripts/map-luals-types.ts +52 -21
- package/scripts/parse-lua-surface.ts +187 -0
- package/scripts/parse-luals.ts +50 -4
- package/scripts/parse-markdown-api.ts +247 -0
- package/scripts/parse-openapi-api.ts +313 -0
- package/scripts/sync-authored-types.ts +249 -0
- package/scripts/sync-library-types.ts +99 -3
- package/scripts/sync-luals-types.ts +14 -1
- package/scripts/sync-markdown-types.ts +761 -0
- package/scripts/sync-openapi-types.ts +308 -0
- package/scripts/sync-script-api-types.ts +16 -17
- package/api-doc/boom.boom.json +0 -26
- package/api-doc/bzAnim.bzLibrary.json +0 -156
- package/api-doc/defcon.console.json +0 -109
- package/api-doc/defsave.defsave.json +0 -175
- package/api-doc/deftest.deftest.json +0 -71
- package/api-doc/gooey.gooey.json +0 -963
- package/api-doc/starly.starly.json +0 -488
- package/generated/defcon.console.d.ts +0 -24
- package/generated/defsave.defsave.d.ts +0 -31
- package/generated/nakama.nakama.d.ts +0 -594
- package/generated/starly.starly.d.ts +0 -148
- /package/generated/{deftest.deftest.d.ts → deftest.d.ts} +0 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A one-sided coverage ratchet over the committed `fidelity/*.json` reports.
|
|
3
|
+
*
|
|
4
|
+
* Each emitting lane already round-trips its committed report against a freshly
|
|
5
|
+
* built one, which proves the report matches what the pipeline builds *today* —
|
|
6
|
+
* but a regeneration that lowers coverage rewrites the report too, so the drop
|
|
7
|
+
* lands silently. `fidelity-floor.json` pins each report's coverage from the
|
|
8
|
+
* outside: `regen` never touches it, so the only way a floor moves is the
|
|
9
|
+
* explicit, monotone `--raise` below — which also makes it the one hand-editable
|
|
10
|
+
* input here, so it is parsed under a validating contract (`parseFloors`).
|
|
11
|
+
*
|
|
12
|
+
* Reports are keyed by package-root-relative POSIX path, not by namespace:
|
|
13
|
+
* `fidelity/openapi/nakama.nakama.json` reports `namespace: "nakama"`, and two
|
|
14
|
+
* lanes emitting the same namespace would collide on a namespace key.
|
|
15
|
+
*
|
|
16
|
+
* The walk is universal over `fidelity/` with one carve-out: `fidelity/authored/`
|
|
17
|
+
* holds *surface* parity, not *type-token* coverage. Those reports have no
|
|
18
|
+
* `totalTypeTokens` to be a fraction of, and their coverage means something else —
|
|
19
|
+
* upstream members declared at the right arity, from `authored-parity.ts`. They get
|
|
20
|
+
* their own ratchet in `authored-parity-floor.json`, so folding them in here would
|
|
21
|
+
* only mean two incompatible denominators under one floor manifest. That manifest
|
|
22
|
+
* also ratchets *two* axes per key (`callableCoverage` and `fieldCoverage`, never
|
|
23
|
+
* averaged), which a flat one-ratio-per-key manifest cannot express — a second,
|
|
24
|
+
* independent reason the two do not merge. Its entries are parsed by
|
|
25
|
+
* `parseAuthoredFloors`, not by `parseFloors` below.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
29
|
+
import { join, resolve } from "node:path";
|
|
30
|
+
|
|
31
|
+
export const FIDELITY_DIR = "fidelity";
|
|
32
|
+
/** Directory name under `fidelity/` the token-coverage walk skips — see the
|
|
33
|
+
* module note; ratcheted by `authored-parity-floor.json` instead. */
|
|
34
|
+
export const AUTHORED_PARITY_DIRNAME = "authored";
|
|
35
|
+
export const FLOOR_MANIFEST_FILE = "fidelity-floor.json";
|
|
36
|
+
export const FLOOR_RAISE_COMMAND = "bun run --cwd packages/library-types fidelity:floor";
|
|
37
|
+
|
|
38
|
+
export interface FidelityFloorReport {
|
|
39
|
+
coverage: number;
|
|
40
|
+
totalMembers: number;
|
|
41
|
+
totalTypeTokens: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function numberField(raw: Record<string, unknown>, field: string, path: string): number {
|
|
45
|
+
const value = raw[field];
|
|
46
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
47
|
+
throw new Error(`${path}: expected a finite numeric "${field}", got ${JSON.stringify(value)}`);
|
|
48
|
+
}
|
|
49
|
+
return value;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function walkJsonFiles(dir: string, prefix: string, out: string[]): void {
|
|
53
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
54
|
+
const rel = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
55
|
+
if (entry.isDirectory()) {
|
|
56
|
+
if (prefix === "" && entry.name === AUTHORED_PARITY_DIRNAME) continue;
|
|
57
|
+
walkJsonFiles(join(dir, entry.name), rel, out);
|
|
58
|
+
} else if (entry.name.endsWith(".json")) out.push(rel);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Every committed fidelity report under `fidelity/`, at any depth, keyed by its
|
|
64
|
+
* package-root-relative POSIX path and sorted by that key. Reads only — the walk
|
|
65
|
+
* is the enumeration the gate trusts, so a lane that emits into a nested
|
|
66
|
+
* directory (`fidelity/openapi/`) is picked up without registration.
|
|
67
|
+
*/
|
|
68
|
+
export function collectFidelityReports(root: string): Record<string, FidelityFloorReport> {
|
|
69
|
+
const dir = join(root, FIDELITY_DIR);
|
|
70
|
+
if (!existsSync(dir)) return {};
|
|
71
|
+
const relatives: string[] = [];
|
|
72
|
+
walkJsonFiles(dir, "", relatives);
|
|
73
|
+
const reports: Record<string, FidelityFloorReport> = {};
|
|
74
|
+
for (const rel of relatives.sort()) {
|
|
75
|
+
const key = `${FIDELITY_DIR}/${rel}`;
|
|
76
|
+
const raw = JSON.parse(readFileSync(join(dir, rel), "utf8")) as Record<string, unknown>;
|
|
77
|
+
reports[key] = {
|
|
78
|
+
coverage: numberField(raw, "coverage", key),
|
|
79
|
+
totalMembers: numberField(raw, "totalMembers", key),
|
|
80
|
+
totalTypeTokens: numberField(raw, "totalTypeTokens", key),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return reports;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// `JSON.stringify` renders NaN and Infinity as `null` and returns `undefined` for
|
|
87
|
+
// `undefined`, both of which would misreport the value the manifest actually holds.
|
|
88
|
+
function describeFloor(value: unknown): string {
|
|
89
|
+
if (typeof value === "number") return String(value);
|
|
90
|
+
return JSON.stringify(value) ?? String(value);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The floor manifest's parse-time contract: a plain object of finite numbers in
|
|
95
|
+
* `[0, 1]`. Every consuming assertion compares coercively, so an unvalidated
|
|
96
|
+
* `null` or `false` floor would pass `floor !== undefined` and fail every
|
|
97
|
+
* `coverage < floor` comparison — switching that report's ratchet off silently.
|
|
98
|
+
* Key order is the writer's contract and is preserved here, not sorted.
|
|
99
|
+
*/
|
|
100
|
+
export function parseFloors(raw: unknown, path: string): Record<string, number> {
|
|
101
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
102
|
+
throw new Error(`${path}: expected a JSON object of floors, got ${describeFloor(raw)}`);
|
|
103
|
+
}
|
|
104
|
+
const floors: Record<string, number> = {};
|
|
105
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
106
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`${path}: floor "${key}" must be a finite number in [0, 1], got ${describeFloor(value)}`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
floors[key] = value;
|
|
112
|
+
}
|
|
113
|
+
return floors;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** The committed floor manifest, or an empty manifest when it does not exist yet. */
|
|
117
|
+
export function readFloors(root: string): Record<string, number> {
|
|
118
|
+
const path = join(root, FLOOR_MANIFEST_FILE);
|
|
119
|
+
if (!existsSync(path)) return {};
|
|
120
|
+
return parseFloors(JSON.parse(readFileSync(path, "utf8")), FLOOR_MANIFEST_FILE);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The next floor manifest: each report's floor rises to its current coverage and
|
|
125
|
+
* never falls, a report with no floor gains one, and a floor whose report is gone
|
|
126
|
+
* is dropped. Monotone by construction — no code path here writes a smaller
|
|
127
|
+
* number, so a regression can only ever be reported by the gate, never absorbed.
|
|
128
|
+
*/
|
|
129
|
+
export function raiseFloors(
|
|
130
|
+
floors: Record<string, number>,
|
|
131
|
+
reports: Record<string, FidelityFloorReport>,
|
|
132
|
+
): Record<string, number> {
|
|
133
|
+
const next: Record<string, number> = {};
|
|
134
|
+
for (const key of Object.keys(reports).sort()) {
|
|
135
|
+
const existing = floors[key];
|
|
136
|
+
const { coverage } = reports[key] as FidelityFloorReport;
|
|
137
|
+
next[key] = existing === undefined ? coverage : Math.max(existing, coverage);
|
|
138
|
+
}
|
|
139
|
+
return next;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// `fidelity-floor.json` sits at the package root, so unlike the reports under
|
|
143
|
+
// `fidelity/` it is not biome-excluded and must match Biome's formatting.
|
|
144
|
+
function biomeFormatJson(raw: string): string {
|
|
145
|
+
const out = Bun.spawnSync(
|
|
146
|
+
["bunx", "biome", "format", `--stdin-file-path=${FLOOR_MANIFEST_FILE}`],
|
|
147
|
+
{
|
|
148
|
+
stdin: Buffer.from(raw),
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
if (out.exitCode !== 0) {
|
|
152
|
+
throw new Error(`biome format failed: ${out.stderr.toString()}`);
|
|
153
|
+
}
|
|
154
|
+
return out.stdout.toString();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (import.meta.main) {
|
|
158
|
+
const root = resolve(import.meta.dir, "..");
|
|
159
|
+
const reports = collectFidelityReports(root);
|
|
160
|
+
const current = readFloors(root);
|
|
161
|
+
const next = raiseFloors(current, reports);
|
|
162
|
+
|
|
163
|
+
if (process.argv.includes("--raise")) {
|
|
164
|
+
for (const key of Object.keys(next)) {
|
|
165
|
+
const before = current[key];
|
|
166
|
+
if (before === undefined) console.log(`${key}: (new) -> ${next[key]}`);
|
|
167
|
+
else if (before !== next[key]) console.log(`${key}: ${before} -> ${next[key]}`);
|
|
168
|
+
}
|
|
169
|
+
for (const key of Object.keys(current)) {
|
|
170
|
+
if (next[key] === undefined) console.log(`${key}: dropped (no such report)`);
|
|
171
|
+
}
|
|
172
|
+
const path = join(root, FLOOR_MANIFEST_FILE);
|
|
173
|
+
writeFileSync(path, biomeFormatJson(JSON.stringify(next)));
|
|
174
|
+
console.log(`wrote ${path}`);
|
|
175
|
+
} else {
|
|
176
|
+
console.log(JSON.stringify(next, null, 2));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Carrying upstream's own LuaDoc into the authored lane's api-doc, for members the
|
|
3
|
+
* fork documents nowhere.
|
|
4
|
+
*
|
|
5
|
+
* `parse-lua-surface.ts` already reads each upstream member's `---` block and
|
|
6
|
+
* `authored-parity.ts` already counts how many of them the fork drops, so the text
|
|
7
|
+
* exists on one side of the pipeline and was thrown away before the docs-site saw
|
|
8
|
+
* it. This module is the merge, and it runs at api-doc lowering rather than in the
|
|
9
|
+
* vendored `.d.ts`: writing upstream prose into `fixtures/authored/*.d.ts` would
|
|
10
|
+
* still pass the forked-vs-generated identity diff while destroying what that diff
|
|
11
|
+
* exists to prove — that the emitted surface *is* the vendored fork — and imported
|
|
12
|
+
* prose would be indistinguishable from fork prose forever after.
|
|
13
|
+
*
|
|
14
|
+
* Three rules carry the whole merge:
|
|
15
|
+
*
|
|
16
|
+
* - **Fork prose always wins.** An element the fork gave either a `brief` or a
|
|
17
|
+
* `description` is returned untouched and gains no `docSource`, so authoring the
|
|
18
|
+
* fork's own doc-comment is how a member opts out. There is no exclusion list:
|
|
19
|
+
* the only thing one would add is "show no prose at all for a member upstream
|
|
20
|
+
* documented", which is worse than writing the correct brief.
|
|
21
|
+
* - **Tags never come with the summary.** Every block in this corpus is a summary
|
|
22
|
+
* followed by `@param`/`@return` lines, and a tag's continuation lines carry no
|
|
23
|
+
* marker of their own, so truncating at the *first* `@` line is what keeps
|
|
24
|
+
* LuaDoc-derived types and parameter names out of the api-doc.
|
|
25
|
+
* - **A brief never restates its own symbol.** A leading line that is exactly the
|
|
26
|
+
* element's name is upstream's heading, not prose, and is dropped before the brief is
|
|
27
|
+
* taken. That is the importer's knowledge, not `summarizeLuaDoc`'s, which stays a pure
|
|
28
|
+
* block-to-prose function.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import type { LuaMember } from "./parse-lua-surface";
|
|
32
|
+
|
|
33
|
+
/** One `api-doc/<namespace>.json` element, open at the edges: this module reads the
|
|
34
|
+
* four keys the merge turns on and passes every other key through in place. */
|
|
35
|
+
export interface ApiDocElement {
|
|
36
|
+
type: string;
|
|
37
|
+
name: string;
|
|
38
|
+
global?: boolean;
|
|
39
|
+
brief?: string;
|
|
40
|
+
description?: string;
|
|
41
|
+
docSource?: string;
|
|
42
|
+
[key: string]: unknown;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The prose half of a LuaDoc block: its lines up to the first whose trimmed text
|
|
47
|
+
* starts with `@`, rejoined and trimmed. Empty for a block that is only tags.
|
|
48
|
+
*
|
|
49
|
+
* Interior newlines and blank lines survive. Markdown collapses a soft break at
|
|
50
|
+
* render time, so no unwrapping is done and the imported description stays
|
|
51
|
+
* upstream's own text.
|
|
52
|
+
*/
|
|
53
|
+
export function summarizeLuaDoc(doc: string): string {
|
|
54
|
+
const summary: string[] = [];
|
|
55
|
+
for (const line of doc.split("\n")) {
|
|
56
|
+
if (line.trim().startsWith("@")) break;
|
|
57
|
+
summary.push(line);
|
|
58
|
+
}
|
|
59
|
+
return summary.join("\n").trim();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The summary without a leading line that is only the element's own name. nakama writes
|
|
63
|
+
* `--- <member_name>` above the real summary, and `briefOf` takes the first line, so
|
|
64
|
+
* leaving it in ships a brief that restates the symbol it sits under. The comparison is
|
|
65
|
+
* exact: a first line that merely mentions the name is prose and survives. */
|
|
66
|
+
function withoutNameLine(summary: string, name: string): string {
|
|
67
|
+
const [first, ...rest] = summary.split("\n");
|
|
68
|
+
return (first as string).trim() === name ? rest.join("\n").trim() : summary;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The element with upstream's summary in place, `docSource` inserted immediately
|
|
72
|
+
* after `description` by copying the element's own entries in order rather than
|
|
73
|
+
* assigning onto it — the key order is what the committed api-doc golden diffs on. */
|
|
74
|
+
function withImportedDoc(element: ApiDocElement, summary: string): ApiDocElement {
|
|
75
|
+
const brief = summary.split("\n")[0]?.trim() ?? "";
|
|
76
|
+
const imported: ApiDocElement = { type: element.type, name: element.name };
|
|
77
|
+
let placedBrief = false;
|
|
78
|
+
let placedDescription = false;
|
|
79
|
+
for (const [key, value] of Object.entries(element)) {
|
|
80
|
+
if (key === "type" || key === "name") continue;
|
|
81
|
+
if (key === "brief") {
|
|
82
|
+
imported.brief = brief;
|
|
83
|
+
placedBrief = true;
|
|
84
|
+
} else if (key === "description") {
|
|
85
|
+
imported.description = summary;
|
|
86
|
+
imported.docSource = "upstream";
|
|
87
|
+
placedDescription = true;
|
|
88
|
+
} else imported[key] = value;
|
|
89
|
+
}
|
|
90
|
+
if (!placedDescription) {
|
|
91
|
+
imported.description = summary;
|
|
92
|
+
imported.docSource = "upstream";
|
|
93
|
+
}
|
|
94
|
+
if (!placedBrief) imported.brief = brief;
|
|
95
|
+
return imported;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The api-doc element list with upstream prose merged in, as a new list.
|
|
100
|
+
*
|
|
101
|
+
* An element is imported when it is a `FUNCTION` or a `VARIABLE` — the two kinds
|
|
102
|
+
* `authored-parity.ts` compares, a `TYPEDEF` being a type rather than a runtime
|
|
103
|
+
* member — is not `global: true`, has an empty `brief` *and* an empty
|
|
104
|
+
* `description`, matches an upstream member by exact name, and that member's
|
|
105
|
+
* summary survives tag stripping. Every other element is returned unchanged.
|
|
106
|
+
*
|
|
107
|
+
* The name match is exact on purpose: a namespace-qualified api-doc name
|
|
108
|
+
* (`bridge.bridge`) never matches a bare upstream name, which is the same non-match
|
|
109
|
+
* the parity pass makes.
|
|
110
|
+
*/
|
|
111
|
+
export function importUpstreamDocs(
|
|
112
|
+
elements: readonly ApiDocElement[],
|
|
113
|
+
members: ReadonlyMap<string, LuaMember>,
|
|
114
|
+
): ApiDocElement[] {
|
|
115
|
+
return elements.map((element) => {
|
|
116
|
+
if (element.type !== "FUNCTION" && element.type !== "VARIABLE") return element;
|
|
117
|
+
if (element.global === true) return element;
|
|
118
|
+
if ((element.brief ?? "") !== "" || (element.description ?? "") !== "") return element;
|
|
119
|
+
const member = members.get(element.name);
|
|
120
|
+
if (member === undefined) return element;
|
|
121
|
+
const summary = withoutNameLine(summarizeLuaDoc(member.doc), element.name);
|
|
122
|
+
if (summary === "") return element;
|
|
123
|
+
return withImportedDoc(element, summary);
|
|
124
|
+
});
|
|
125
|
+
}
|
package/scripts/lower-api-doc.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import {
|
|
17
17
|
buildModelContext,
|
|
18
|
+
type ExternalTypeRef,
|
|
18
19
|
isPublicField,
|
|
19
20
|
isPublicMethod,
|
|
20
21
|
mapTypes,
|
|
@@ -67,9 +68,13 @@ function functionElement(method: LibraryMethod, ctx: MapContext): Record<string,
|
|
|
67
68
|
parameterElement(param, optionalFlags[index] ?? false, fnCtx),
|
|
68
69
|
),
|
|
69
70
|
returnvalues: method.returns.map((ret) => returnElement(ret, fnCtx)),
|
|
71
|
+
...(method.deprecated !== undefined ? { deprecated: method.deprecated } : {}),
|
|
70
72
|
};
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
// No `deprecated` spread here or on `variableElement`: both take a `LibraryField`,
|
|
76
|
+
// and a one-line `---@field` has no slot for a block tag in this dialect, so a
|
|
77
|
+
// property can never carry one.
|
|
73
78
|
function propertyElement(field: LibraryField, ctx: MapContext): Record<string, unknown> {
|
|
74
79
|
return {
|
|
75
80
|
name: field.name,
|
|
@@ -93,9 +98,17 @@ function variableElement(field: LibraryField, ctx: MapContext): Record<string, u
|
|
|
93
98
|
|
|
94
99
|
export function lowerLibraryModel(
|
|
95
100
|
model: LibraryModel,
|
|
96
|
-
{
|
|
101
|
+
{
|
|
102
|
+
namespace,
|
|
103
|
+
typeRenames,
|
|
104
|
+
externalTypes,
|
|
105
|
+
}: {
|
|
106
|
+
namespace: string;
|
|
107
|
+
typeRenames?: Record<string, string>;
|
|
108
|
+
externalTypes?: Record<string, ExternalTypeRef> | undefined;
|
|
109
|
+
},
|
|
97
110
|
): unknown {
|
|
98
|
-
const ctx = buildModelContext(model, typeRenames);
|
|
111
|
+
const ctx = buildModelContext(model, typeRenames, externalTypes);
|
|
99
112
|
const elements: Record<string, unknown>[] = [];
|
|
100
113
|
|
|
101
114
|
for (const fn of model.moduleFunctions) {
|
|
@@ -125,11 +138,16 @@ export function lowerLibraryModel(
|
|
|
125
138
|
name: sanitizeTypeName(iface.name),
|
|
126
139
|
...(functions.length > 0 ? { functions } : {}),
|
|
127
140
|
...(properties.length > 0 ? { properties } : {}),
|
|
141
|
+
...(iface.deprecated !== undefined ? { deprecated: iface.deprecated } : {}),
|
|
128
142
|
});
|
|
129
143
|
}
|
|
130
144
|
|
|
131
145
|
for (const alias of model.aliases) {
|
|
132
|
-
elements.push({
|
|
146
|
+
elements.push({
|
|
147
|
+
type: "TYPEDEF",
|
|
148
|
+
name: sanitizeTypeName(alias.name),
|
|
149
|
+
...(alias.deprecated !== undefined ? { deprecated: alias.deprecated } : {}),
|
|
150
|
+
});
|
|
133
151
|
}
|
|
134
152
|
|
|
135
153
|
// The module's own `@class` carries the library's summary; use it as the page
|
|
@@ -10,7 +10,12 @@
|
|
|
10
10
|
* sorted-unique token list so the gap is visible instead of silent.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
buildModelContext,
|
|
15
|
+
type ExternalTypeRef,
|
|
16
|
+
isPublicField,
|
|
17
|
+
isPublicMethod,
|
|
18
|
+
} from "./emit-library-dts";
|
|
14
19
|
import { type MapContext, mapLualsType, scopeGenerics } from "./map-luals-types";
|
|
15
20
|
import type { LibraryGeneric, LibraryModel } from "./parse-luals";
|
|
16
21
|
|
|
@@ -29,22 +34,22 @@ function round3(value: number): number {
|
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
/**
|
|
32
|
-
* Build the fidelity report for one namespace.
|
|
33
|
-
*
|
|
34
|
-
* resolves
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
37
|
+
* Build the fidelity report for one namespace. Tokens map through the same
|
|
38
|
+
* `buildModelContext` the emitter and the api-doc lowering use, so all three agree
|
|
39
|
+
* on what resolves — the model's own interface and alias names, plus the
|
|
40
|
+
* `externalTypes` tokens reached through a cross-module import — and all three
|
|
41
|
+
* reject an external alias that collides with a declared name. Every field, every
|
|
42
|
+
* param and return of every method and module function, and every alias expression
|
|
43
|
+
* is mapped; `undocumentedMembers` counts fields/methods/moduleFunctions whose doc
|
|
44
|
+
* or brief is empty. Deterministic; no I/O.
|
|
38
45
|
*/
|
|
39
46
|
export function buildFidelityReport(
|
|
40
47
|
namespace: string,
|
|
41
48
|
model: LibraryModel,
|
|
42
49
|
typeRenames: Record<string, string>,
|
|
50
|
+
externalTypes?: Record<string, ExternalTypeRef>,
|
|
43
51
|
): FidelityReport {
|
|
44
|
-
const
|
|
45
|
-
for (const iface of model.interfaces) knownNames.add(iface.name);
|
|
46
|
-
for (const alias of model.aliases) knownNames.add(alias.name);
|
|
47
|
-
const ctx: MapContext = { knownNames, typeRenames };
|
|
52
|
+
const ctx = buildModelContext(model, typeRenames, externalTypes);
|
|
48
53
|
|
|
49
54
|
let totalMembers = 0;
|
|
50
55
|
let totalTypeTokens = 0;
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* verbatim and the emitter sanitizes it later.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
+
import { luaMultiReturn } from "@defold-typescript/types";
|
|
16
17
|
import { CORE_TYPE_RENAMES } from "./sync-library-types";
|
|
17
18
|
|
|
18
19
|
export interface MapContext {
|
|
@@ -52,6 +53,24 @@ const SCALARS: Readonly<Record<string, string>> = {
|
|
|
52
53
|
any: "unknown",
|
|
53
54
|
};
|
|
54
55
|
|
|
56
|
+
/**
|
|
57
|
+
* LuaLS's bare `function` — any callable, signature unspecified. Params are `any[]`
|
|
58
|
+
* because under `strictFunctionTypes` an `unknown[]` rest would reject every concrete
|
|
59
|
+
* callback a consumer passes; the return stays `unknown` because return position is
|
|
60
|
+
* covariant, so `any` there would only leak unchecked values into call sites.
|
|
61
|
+
*/
|
|
62
|
+
const CALLABLE_UNSPECIFIED = "(...args: any[]) => unknown";
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* LuaLS's two placeholders: the throwaway param name `_`, and a bare `...` in vararg
|
|
66
|
+
* or return position. Neither declares a type at all, so lowering it to `unknown`
|
|
67
|
+
* loses nothing an author wrote — the same reasoning that already exempts `any` from
|
|
68
|
+
* the fallback count. The boundary is deliberate: an untyped `self` or `ctx` *is* an
|
|
69
|
+
* upstream omission and stays recorded.
|
|
70
|
+
*/
|
|
71
|
+
const LUALS_THROWAWAY_PARAM = "_";
|
|
72
|
+
export const LUALS_VARARG_TOKEN = "...";
|
|
73
|
+
|
|
55
74
|
/**
|
|
56
75
|
* Split `s` on every top-level occurrence of the single-character `sep`, honoring
|
|
57
76
|
* bracket depth and double-quoted string literals so a separator nested inside
|
|
@@ -146,21 +165,18 @@ function functionParts(
|
|
|
146
165
|
const paramList = params
|
|
147
166
|
.map((raw) => raw.trim())
|
|
148
167
|
.map((part) => {
|
|
149
|
-
if (part.startsWith(
|
|
168
|
+
if (part.startsWith(LUALS_VARARG_TOKEN)) {
|
|
150
169
|
const after = part.slice(3).trim();
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
} else {
|
|
155
|
-
element = "unknown";
|
|
156
|
-
unknowns.push("...");
|
|
157
|
-
}
|
|
170
|
+
const element = after.startsWith(":")
|
|
171
|
+
? mapToken(after.slice(1).trim(), ctx, unknowns)
|
|
172
|
+
: "unknown";
|
|
158
173
|
return `...args: ${needsArrayParens(element) ? `(${element})[]` : `${element}[]`}`;
|
|
159
174
|
}
|
|
160
175
|
const colon = splitTopLevel(part, ":");
|
|
161
176
|
if (colon.length < 2) {
|
|
162
|
-
// Untyped param (`self`, `
|
|
163
|
-
|
|
177
|
+
// Untyped param (`self`, `ctx`): a recorded gap, not a silent `any`. A bare `_`
|
|
178
|
+
// is LuaLS's deliberate throwaway and records nothing.
|
|
179
|
+
if (part !== LUALS_THROWAWAY_PARAM) unknowns.push(part);
|
|
164
180
|
return `${part}: unknown`;
|
|
165
181
|
}
|
|
166
182
|
const name = colon[0]?.trim() ?? "";
|
|
@@ -177,8 +193,11 @@ function functionParts(
|
|
|
177
193
|
if (retTokens.length === 1) {
|
|
178
194
|
ret = mapToken(retTokens[0] as string, ctx, unknowns);
|
|
179
195
|
} else if (retTokens.length > 1) {
|
|
180
|
-
const
|
|
181
|
-
ret =
|
|
196
|
+
const restTail = retTokens.at(-1) === LUALS_VARARG_TOKEN;
|
|
197
|
+
ret = luaMultiReturn(
|
|
198
|
+
retTokens.map((r) => mapToken(r, ctx, unknowns)),
|
|
199
|
+
restTail,
|
|
200
|
+
);
|
|
182
201
|
}
|
|
183
202
|
}
|
|
184
203
|
return { paramList, ret };
|
|
@@ -215,15 +234,11 @@ function mapToken(raw: string, ctx: MapContext, unknowns: string[]): string {
|
|
|
215
234
|
|
|
216
235
|
if (token === "") return "unknown";
|
|
217
236
|
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// A `fun(...)` whose return follows the `)` keeps its return-type `|` inside the
|
|
226
|
-
// function; splitting the union first would cut `fun(): a|b` into `(fun) | b`.
|
|
237
|
+
// A `fun(...)` whose return follows the `)` keeps its return-type `|` and `?` inside
|
|
238
|
+
// the function; splitting the union first would cut `fun(): a|b` into `(fun) | b`, and
|
|
239
|
+
// peeling the optional suffix first would turn `fun(): number?` — a function with an
|
|
240
|
+
// optional *return* — into an optional function. The whole-function optional is spelled
|
|
241
|
+
// with explicit parentheses, `(fun(): number)?`, which does not match here.
|
|
227
242
|
// `fun()|nil` (a `|` right after the `)`) falls through to the union split.
|
|
228
243
|
if (/^fun\s*\(/.test(token)) {
|
|
229
244
|
const close = matchBracket(token, token.indexOf("("));
|
|
@@ -233,6 +248,13 @@ function mapToken(raw: string, ctx: MapContext, unknowns: string[]): string {
|
|
|
233
248
|
}
|
|
234
249
|
}
|
|
235
250
|
|
|
251
|
+
// Optional suffix.
|
|
252
|
+
if (token.length > 1 && token.endsWith("?")) {
|
|
253
|
+
const base = mapToken(token.slice(0, -1), ctx, unknowns);
|
|
254
|
+
const members = splitTopLevel(base, "|").map((m) => m.trim());
|
|
255
|
+
return members.includes("undefined") ? base : `${wrapForUnion(base)} | undefined`;
|
|
256
|
+
}
|
|
257
|
+
|
|
236
258
|
// Top-level union.
|
|
237
259
|
const unionParts = splitTopLevel(token, "|");
|
|
238
260
|
if (unionParts.length > 1) {
|
|
@@ -263,6 +285,15 @@ function mapToken(raw: string, ctx: MapContext, unknowns: string[]): string {
|
|
|
263
285
|
// String literal — passthrough.
|
|
264
286
|
if (token.startsWith('"') && token.endsWith('"')) return token;
|
|
265
287
|
|
|
288
|
+
// Signature-less callable. Placed after every composite branch so `function[]`,
|
|
289
|
+
// `function|nil`, and `fun(cb: function)` route through those first.
|
|
290
|
+
if (token === "function") return CALLABLE_UNSPECIFIED;
|
|
291
|
+
|
|
292
|
+
// Placeholder vararg in return position — not a `SCALARS` entry, which maps real Lua
|
|
293
|
+
// type names. Sits beside the callable branch so `...[]` and `...|nil` reach their
|
|
294
|
+
// structural handlers first.
|
|
295
|
+
if (token === LUALS_VARARG_TOKEN) return "unknown";
|
|
296
|
+
|
|
266
297
|
// Scalars.
|
|
267
298
|
const scalar = SCALARS[token];
|
|
268
299
|
if (scalar !== undefined) return scalar;
|