@defold-typescript/library-types 0.23.0 → 0.24.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-doc/boom.json +3870 -0
- package/api-doc/bridge.json +10 -5
- package/api-doc/bzAnim.json +338 -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 +741 -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.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/{starly.starly.json → starly.json} +1 -1
- package/api-doc/{yagames.yagames.json → yagames.json} +784 -189
- package/api-doc/{zzfx.api.json → zzfx.json} +9 -9
- package/authored-targets.json +403 -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/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.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/{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/{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 +189 -38
- 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/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/{deftest.deftest.d.ts → deftest.d.ts} +0 -0
- /package/generated/{starly.starly.d.ts → starly.d.ts} +0 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A line-oriented reader for the *public surface* of a plain-Lua module — the
|
|
3
|
+
* member names and parameter names a `local M = {} … return M` file declares.
|
|
4
|
+
*
|
|
5
|
+
* `sync-authored-types.ts` gates the authored/forked lane on a forked-vs-generated
|
|
6
|
+
* identity diff and records that there is no coverage comparison against a primary
|
|
7
|
+
* source. That holds for *types*: upstream Lua declares none. It does not hold for
|
|
8
|
+
* *surface*: names and arity are right there in the source, so a fork that drops a
|
|
9
|
+
* member or a trailing parameter is measurable. This module reads that side; the
|
|
10
|
+
* comparison lives in `authored-parity.ts`.
|
|
11
|
+
*
|
|
12
|
+
* The corpus is a handful of pinned files, so line scanning is what it needs — a
|
|
13
|
+
* Lua grammar would buy nothing here. Two rules keep it honest:
|
|
14
|
+
*
|
|
15
|
+
* - **Column 0 only.** A definition is a member when it starts the line. This is
|
|
16
|
+
* what keeps `nakama/util/log.lua`'s `M.log = noop` — reassigned inside three
|
|
17
|
+
* different function bodies — out of the surface, and it is why the reader never
|
|
18
|
+
* needs to know where a block ends.
|
|
19
|
+
* - **Loud failure over silent undercount.** A missing `return <name>` or a
|
|
20
|
+
* parameter list that does not close on its own line throws, as does a
|
|
21
|
+
* `return setmetatable(M, …)` whose metatable carries `__index` or is named by a
|
|
22
|
+
* variable whose keys this reader cannot see — either could add members no column-0
|
|
23
|
+
* scan will find. The whole point of the instrument is that a dropped member is
|
|
24
|
+
* visible; a parser that quietly skips what it cannot read would inflate every
|
|
25
|
+
* coverage number it feeds.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export interface LuaMember {
|
|
29
|
+
name: string;
|
|
30
|
+
/** Absent for a non-callable field (`M.SOME_CONSTANT = "X"`), so a field can
|
|
31
|
+
* never be confused with a zero-arity function. */
|
|
32
|
+
params?: string[];
|
|
33
|
+
/** True when the definition ends in `...`, which is not a named parameter. */
|
|
34
|
+
varargs: boolean;
|
|
35
|
+
/** The `---` LuaDoc block immediately above the definition, comment markers
|
|
36
|
+
* stripped, one interior blank line crossed; empty when the block is absent or
|
|
37
|
+
* opens with a plain `--`. */
|
|
38
|
+
doc: string;
|
|
39
|
+
/** True when a comment block *was* there and the `---` rule declined it, which `doc`
|
|
40
|
+
* alone cannot say — an empty `doc` otherwise means upstream documented nothing. */
|
|
41
|
+
refusedDoc: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface LuaSurface {
|
|
45
|
+
moduleLocal: string;
|
|
46
|
+
members: LuaMember[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const RETURN_LINE = /^return\s+([A-Za-z_][A-Za-z0-9_]*)\s*;?\s*$/;
|
|
50
|
+
const RETURN_SETMETATABLE = /^return\s+setmetatable\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*,/;
|
|
51
|
+
const IDENTIFIER = "[A-Za-z_][A-Za-z0-9_]*";
|
|
52
|
+
|
|
53
|
+
/** The metatable argument of a `return setmetatable(<name>, …)`, refused unless its
|
|
54
|
+
* keys are readable and none of them delegates. `text` runs from the comma to the end
|
|
55
|
+
* of the source rather than to the matching brace: over-reading can only refuse a file
|
|
56
|
+
* loudly, where under-reading would accept a delegating module and report its short
|
|
57
|
+
* surface as complete. */
|
|
58
|
+
function assertNoDelegation(text: string): void {
|
|
59
|
+
if (text.trimStart().startsWith("{") === false) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
"lua surface: the returned `setmetatable` is given a metatable this reader cannot see the keys of, so a delegated member would be invisible — inline the table literal or measure the module another way.",
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
if (text.includes("__index")) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
"lua surface: the returned metatable carries `__index`, so members reached through it would be invisible to a column-0 scan and the surface would read as complete while short.",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function resolveModuleLocal(lines: string[]): string {
|
|
72
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
73
|
+
const line = lines[index] as string;
|
|
74
|
+
const bare = RETURN_LINE.exec(line);
|
|
75
|
+
if (bare) return bare[1] as string;
|
|
76
|
+
const wrapped = RETURN_SETMETATABLE.exec(line);
|
|
77
|
+
if (wrapped) {
|
|
78
|
+
assertNoDelegation(
|
|
79
|
+
[line.slice((wrapped[0] as string).length), ...lines.slice(index + 1)].join("\n"),
|
|
80
|
+
);
|
|
81
|
+
return wrapped[1] as string;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
throw new Error(
|
|
85
|
+
"lua surface: the source has no trailing `return <name>`, so the module-local name cannot be derived.",
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The parameter names between the parentheses opened at `open` on `line`, with a
|
|
90
|
+
* `...` tail reported separately. Throws when the list does not close on the same
|
|
91
|
+
* line rather than dropping the member. */
|
|
92
|
+
function readParams(
|
|
93
|
+
line: string,
|
|
94
|
+
open: number,
|
|
95
|
+
lineNumber: number,
|
|
96
|
+
): { params: string[]; varargs: boolean } {
|
|
97
|
+
const close = line.indexOf(")", open);
|
|
98
|
+
if (close === -1) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`lua surface: line ${lineNumber} opens a parameter list that does not close on the same line — ${line.trim()}`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
const raw = line
|
|
104
|
+
.slice(open + 1, close)
|
|
105
|
+
.split(",")
|
|
106
|
+
.map((part) => part.trim())
|
|
107
|
+
.filter((part) => part !== "");
|
|
108
|
+
return { params: raw.filter((part) => part !== "..."), varargs: raw.includes("...") };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The contiguous run of comment lines ending at `index - 1`, in source order. */
|
|
112
|
+
function readSegment(lines: string[], index: number): string[] {
|
|
113
|
+
const segment: string[] = [];
|
|
114
|
+
for (let cursor = index - 1; cursor >= 0; cursor -= 1) {
|
|
115
|
+
const line = (lines[cursor] as string).trim();
|
|
116
|
+
if (!line.startsWith("--") || line.startsWith("--[[")) break;
|
|
117
|
+
segment.unshift(line);
|
|
118
|
+
}
|
|
119
|
+
return segment;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The contiguous comment block ending at `index - 1`, kept only when it opens
|
|
123
|
+
* with `---`. LuaDoc in this corpus is a `---` summary followed by plain `--`
|
|
124
|
+
* `@param`/`@return` lines, so the marker on the *first* line is what decides.
|
|
125
|
+
*
|
|
126
|
+
* A single blank line inside the run is crossed, and only when the segment
|
|
127
|
+
* attached to the definition opens no block of its own — upstream wraps a long
|
|
128
|
+
* `@param` list around a blank, which would otherwise truncate the block to its
|
|
129
|
+
* tag half and lose the summary above. A segment carrying its own `---` is never
|
|
130
|
+
* absorbed into the one below it, so a blank-separated section header stays out. */
|
|
131
|
+
function readDoc(lines: string[], index: number): Pick<LuaMember, "doc" | "refusedDoc"> {
|
|
132
|
+
const attached = readSegment(lines, index);
|
|
133
|
+
if (attached.length === 0) return { doc: "", refusedDoc: false };
|
|
134
|
+
const above = index - attached.length - 1;
|
|
135
|
+
const block =
|
|
136
|
+
attached.some((line) => line.startsWith("---")) ||
|
|
137
|
+
above < 0 ||
|
|
138
|
+
(lines[above] as string).trim() !== ""
|
|
139
|
+
? attached
|
|
140
|
+
: [...readSegment(lines, above), ...attached];
|
|
141
|
+
if (!(block[0] as string).startsWith("---")) return { doc: "", refusedDoc: true };
|
|
142
|
+
const doc = block
|
|
143
|
+
.map((line) => line.replace(/^-+\s?/, "").trimEnd())
|
|
144
|
+
.join("\n")
|
|
145
|
+
.trim();
|
|
146
|
+
return { doc, refusedDoc: false };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The public surface of a Lua module source: the module-local name it returns and
|
|
151
|
+
* every member assigned to it at column 0, in source order. A name defined more
|
|
152
|
+
* than once keeps its first position and its last definition, matching Lua.
|
|
153
|
+
*/
|
|
154
|
+
export function parseLuaSurface(source: string): LuaSurface {
|
|
155
|
+
const lines = source.split("\n");
|
|
156
|
+
const moduleLocal = resolveModuleLocal(lines);
|
|
157
|
+
const definition = new RegExp(`^function\\s+${moduleLocal}\\.(${IDENTIFIER})\\s*\\(`);
|
|
158
|
+
const assignment = new RegExp(`^${moduleLocal}\\.(${IDENTIFIER})\\s*=\\s*(.*)$`);
|
|
159
|
+
const assignedFunction = /^function\s*\(/;
|
|
160
|
+
|
|
161
|
+
const members = new Map<string, LuaMember>();
|
|
162
|
+
const record = (member: LuaMember): void => {
|
|
163
|
+
members.set(member.name, member);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
for (const [index, line] of lines.entries()) {
|
|
167
|
+
const defined = definition.exec(line);
|
|
168
|
+
if (defined) {
|
|
169
|
+
const { params, varargs } = readParams(line, defined[0].length - 1, index + 1);
|
|
170
|
+
record({ name: defined[1] as string, params, varargs, ...readDoc(lines, index) });
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
const assigned = assignment.exec(line);
|
|
174
|
+
if (!assigned) continue;
|
|
175
|
+
const name = assigned[1] as string;
|
|
176
|
+
const rhs = assigned[2] as string;
|
|
177
|
+
if (!assignedFunction.test(rhs)) {
|
|
178
|
+
record({ name, varargs: false, ...readDoc(lines, index) });
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const open = line.indexOf("(", line.length - rhs.length);
|
|
182
|
+
const { params, varargs } = readParams(line, open, index + 1);
|
|
183
|
+
record({ name, params, varargs, ...readDoc(lines, index) });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return { moduleLocal, members: [...members.values()] };
|
|
187
|
+
}
|
package/scripts/parse-luals.ts
CHANGED
|
@@ -35,6 +35,7 @@ export interface LibraryInterface {
|
|
|
35
35
|
// so an interface without overloads carries no key. The emitter renders each as an
|
|
36
36
|
// interface call signature; the mapper maps the token to a `(params): ret` form.
|
|
37
37
|
overloads?: LibraryOverload[];
|
|
38
|
+
deprecated?: string;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export interface LibraryOverload {
|
|
@@ -53,6 +54,10 @@ export interface LibraryMethod {
|
|
|
53
54
|
// unmarked method carries no key. `local` has no `@field` analogue (LuaLS field
|
|
54
55
|
// scope has no `local`), so a method's visibility widens the field set with it.
|
|
55
56
|
visibility?: LibraryMethodVisibility;
|
|
57
|
+
// A `---@deprecated` on the pending block. Present exactly when the tag is, with
|
|
58
|
+
// `""` for the bare form — absence of the key is the sole encoding of "not
|
|
59
|
+
// deprecated", so a bare tag must never be collapsed away by a truthiness guard.
|
|
60
|
+
deprecated?: string;
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
export interface LibraryParam {
|
|
@@ -89,6 +94,7 @@ export interface LibraryAlias {
|
|
|
89
94
|
name: string;
|
|
90
95
|
types: string[];
|
|
91
96
|
doc: string;
|
|
97
|
+
deprecated?: string;
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
interface Pending {
|
|
@@ -98,6 +104,7 @@ interface Pending {
|
|
|
98
104
|
generics: LibraryGeneric[];
|
|
99
105
|
overloads: LibraryOverload[];
|
|
100
106
|
visibility?: LibraryMethodVisibility;
|
|
107
|
+
deprecated?: string;
|
|
101
108
|
}
|
|
102
109
|
|
|
103
110
|
const emptyPending = (): Pending => ({
|
|
@@ -316,7 +323,14 @@ function parseParam(rest: string): LibraryParam {
|
|
|
316
323
|
return param;
|
|
317
324
|
}
|
|
318
325
|
|
|
319
|
-
|
|
326
|
+
/**
|
|
327
|
+
* A single `---@return` line may declare several values (`---@return number, number`),
|
|
328
|
+
* which `readTypeToken`'s comma continuation hands back as one token. Split it on
|
|
329
|
+
* top-level commas into one entry per value; LuaLS attaches the optional trailing name
|
|
330
|
+
* and description to the whole line, so only the last segment carries them. The
|
|
331
|
+
* emitter's `renderReturn` already lowers a multi-entry `returns` to `LuaMultiReturn`.
|
|
332
|
+
*/
|
|
333
|
+
function parseReturn(rest: string): LibraryParam[] {
|
|
320
334
|
const { type, rest: afterType } = readTypeToken(rest);
|
|
321
335
|
const spaceAt = afterType.search(/\s/);
|
|
322
336
|
const head = spaceAt === -1 ? afterType : afterType.slice(0, spaceAt);
|
|
@@ -326,7 +340,22 @@ function parseReturn(rest: string): LibraryParam {
|
|
|
326
340
|
name = head;
|
|
327
341
|
doc = spaceAt === -1 ? "" : afterType.slice(spaceAt).trim();
|
|
328
342
|
}
|
|
329
|
-
|
|
343
|
+
const segments = (type ? splitTopLevel(type, ",") : [""])
|
|
344
|
+
.map((segment) => segment.trim())
|
|
345
|
+
.filter((segment) => segment !== "");
|
|
346
|
+
if (segments.length === 0) {
|
|
347
|
+
return [{ name, types: [], doc, isOptional: false, isVararg: false }];
|
|
348
|
+
}
|
|
349
|
+
return segments.map((segment, index) => {
|
|
350
|
+
const isLast = index === segments.length - 1;
|
|
351
|
+
return {
|
|
352
|
+
name: isLast ? name : "",
|
|
353
|
+
types: [segment],
|
|
354
|
+
doc: isLast ? doc : "",
|
|
355
|
+
isOptional: false,
|
|
356
|
+
isVararg: false,
|
|
357
|
+
};
|
|
358
|
+
});
|
|
330
359
|
}
|
|
331
360
|
|
|
332
361
|
const VISIBILITY_KEYWORDS = new Set<LibraryFieldVisibility>([
|
|
@@ -546,6 +575,7 @@ export function parseLualsSource(source: string): LibraryModel {
|
|
|
546
575
|
params: pending.params,
|
|
547
576
|
returns: pending.returns,
|
|
548
577
|
...(pending.visibility ? { visibility: pending.visibility } : {}),
|
|
578
|
+
...(pending.deprecated !== undefined ? { deprecated: pending.deprecated } : {}),
|
|
549
579
|
});
|
|
550
580
|
|
|
551
581
|
// Interpret one indented line while a function-local `---@class` block is open. Only
|
|
@@ -646,6 +676,7 @@ export function parseLualsSource(source: string): LibraryModel {
|
|
|
646
676
|
if (pending.doc.length > 0 && iface.brief === "") iface.brief = pending.doc.join("\n");
|
|
647
677
|
if (pending.generics.length > 0) iface.generics = pending.generics;
|
|
648
678
|
if (pending.overloads.length > 0) iface.overloads = pending.overloads;
|
|
679
|
+
if (pending.deprecated !== undefined) iface.deprecated = pending.deprecated;
|
|
649
680
|
openClass = iface;
|
|
650
681
|
lastOpenedClass = head.name;
|
|
651
682
|
pending = emptyPending();
|
|
@@ -664,7 +695,7 @@ export function parseLualsSource(source: string): LibraryModel {
|
|
|
664
695
|
break;
|
|
665
696
|
}
|
|
666
697
|
case "return": {
|
|
667
|
-
pending.returns.push(parseReturn(rest));
|
|
698
|
+
pending.returns.push(...parseReturn(rest));
|
|
668
699
|
break;
|
|
669
700
|
}
|
|
670
701
|
case "generic": {
|
|
@@ -684,7 +715,12 @@ export function parseLualsSource(source: string): LibraryModel {
|
|
|
684
715
|
const spaceAt = rest.search(/\s/);
|
|
685
716
|
const name = spaceAt === -1 ? rest : rest.slice(0, spaceAt);
|
|
686
717
|
const expr = spaceAt === -1 ? "" : rest.slice(spaceAt).trim();
|
|
687
|
-
aliases.push({
|
|
718
|
+
aliases.push({
|
|
719
|
+
name,
|
|
720
|
+
types: expr ? [expr] : [],
|
|
721
|
+
doc: pending.doc.join("\n"),
|
|
722
|
+
...(pending.deprecated !== undefined ? { deprecated: pending.deprecated } : {}),
|
|
723
|
+
});
|
|
688
724
|
pending = emptyPending();
|
|
689
725
|
break;
|
|
690
726
|
}
|
|
@@ -698,6 +734,12 @@ export function parseLualsSource(source: string): LibraryModel {
|
|
|
698
734
|
pending.visibility = tag as LibraryMethodVisibility;
|
|
699
735
|
break;
|
|
700
736
|
}
|
|
737
|
+
case "deprecated": {
|
|
738
|
+
// `rest` is already trimmed and is `""` for the bare form, which is the
|
|
739
|
+
// only form the fixtures actually ship; the text form is carried too.
|
|
740
|
+
pending.deprecated = rest;
|
|
741
|
+
break;
|
|
742
|
+
}
|
|
701
743
|
default:
|
|
702
744
|
// @cast, @type, @diagnostic, ... — outside the Druid subset; recognized as a
|
|
703
745
|
// tag and skipped, never treated as doc.
|
|
@@ -793,6 +835,7 @@ export function mergeLibraryModels(models: LibraryModel[]): LibraryModel {
|
|
|
793
835
|
...(iface.overloads && iface.overloads.length > 0
|
|
794
836
|
? { overloads: [...iface.overloads] }
|
|
795
837
|
: {}),
|
|
838
|
+
...(iface.deprecated !== undefined ? { deprecated: iface.deprecated } : {}),
|
|
796
839
|
};
|
|
797
840
|
byName.set(iface.name, copy);
|
|
798
841
|
interfaces.push(copy);
|
|
@@ -801,6 +844,9 @@ export function mergeLibraryModels(models: LibraryModel[]): LibraryModel {
|
|
|
801
844
|
existing.fields.push(...iface.fields);
|
|
802
845
|
existing.methods.push(...iface.methods);
|
|
803
846
|
if (!existing.extends && iface.extends) existing.extends = iface.extends;
|
|
847
|
+
if (existing.deprecated === undefined && iface.deprecated !== undefined) {
|
|
848
|
+
existing.deprecated = iface.deprecated;
|
|
849
|
+
}
|
|
804
850
|
if (existing.brief === "" && iface.brief !== "") existing.brief = iface.brief;
|
|
805
851
|
if (existing.generics.length === 0 && iface.generics.length > 0) {
|
|
806
852
|
existing.generics = [...iface.generics];
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The markdown ingestion front-end's parser: a third `library-types` corpus
|
|
3
|
+
* source beside the LuaLS annotations (`parse-luals.ts`) and the typed
|
|
4
|
+
* `.script_api` (`scriptApiToFixtureJson`). It reads a library's README/`.md`
|
|
5
|
+
* prose API — the only machine-readable type source Bucket-C libraries ship —
|
|
6
|
+
* and produces the same ref-doc `doc` shape those two front-ends feed the shared
|
|
7
|
+
* emitter (`generateModuleDeclaration`), so the markdown path reuses the exact
|
|
8
|
+
* emit + fidelity machinery.
|
|
9
|
+
*
|
|
10
|
+
* Scope is a **flat signature surface**, not deep prose (a PRD non-goal): the
|
|
11
|
+
* parser lifts one element per `##`- or `###`-level `<receiver>.<fn>(<args>)` API
|
|
12
|
+
* header and its `**PARAMETERS**` / `**RETURN**` bullet lists. Both levels are
|
|
13
|
+
* accepted because the corpus is split on the convention (defold-orthographic and
|
|
14
|
+
* defold-input write `###`; monarch's `README_API.md` writes `##`); h1 and h4 stay
|
|
15
|
+
* outside the range, since at those levels a dotted-call-shaped line is document
|
|
16
|
+
* structure rather than a signature. At either level the receiver may be preceded
|
|
17
|
+
* by a literal `function ` declaration keyword (rendy writes 9 of its 11 headings
|
|
18
|
+
* that way); no other prefix is accepted, so prose such as `### see mod.fn()` is
|
|
19
|
+
* still not a signature. Header-only message sections (`<verb>` with
|
|
20
|
+
* no dotted receiver or parens) and nested option-table bullets are ignored. A
|
|
21
|
+
* signature row that names a parameter but gives it no `(type)` loud-fails rather
|
|
22
|
+
* than silently emitting an untyped `any`.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** A single ref-doc parameter or return slot. `is_optional` mirrors the
|
|
26
|
+
* consumer contract's string flag (`"True"`); it is present only when set. */
|
|
27
|
+
export interface MarkdownParam {
|
|
28
|
+
name: string;
|
|
29
|
+
doc: string;
|
|
30
|
+
types: string[];
|
|
31
|
+
is_optional?: "True";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface MarkdownElement {
|
|
35
|
+
type: "FUNCTION";
|
|
36
|
+
name: string;
|
|
37
|
+
description: string;
|
|
38
|
+
parameters: MarkdownParam[];
|
|
39
|
+
returnvalues: MarkdownParam[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The ref-doc `doc` shape `generateModuleDeclaration` consumes. `info.namespace`
|
|
43
|
+
* carries the README's own module prefix; the front-end retargets it to the
|
|
44
|
+
* pinned namespace before emitting. */
|
|
45
|
+
export interface MarkdownDoc {
|
|
46
|
+
info: { namespace: string; brief: string; description: string };
|
|
47
|
+
elements: MarkdownElement[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Only the literal `function` keyword is accepted before the receiver — a general
|
|
51
|
+
// `\w+\s+` prefix would make prose like `### see rendy.set(...)` read as a signature.
|
|
52
|
+
const HEADER = /^#{2,3}\s+(?:function\s+)?([A-Za-z_][\w]*)\.([A-Za-z_][\w]*)\((.*)\)\s*$/;
|
|
53
|
+
const PARAM_MARKER = /^\*\*PARAM(?:ETER|ETERS)?\*\*\s*$/;
|
|
54
|
+
const RETURN_MARKER = /^\*\*RETURNS?\*\*\s*$/;
|
|
55
|
+
// A bullet with a backticked name and a required `(type)` group. A named bullet
|
|
56
|
+
// missing the `(type)` group is an unresolvable row (see `parseSlot`).
|
|
57
|
+
const TYPED_BULLET = /^\*\s+`([^`]+)`\s*\(([^)]*)\)\s*-?\s*(.*)$/;
|
|
58
|
+
const NAMED_BULLET = /^\*\s+`([^`]+)`/;
|
|
59
|
+
|
|
60
|
+
/** Bracketed header arguments are optional; collect their bare names. A bracket
|
|
61
|
+
* may be escaped (`duration \[, scaler]` — a README authoring artifact so the
|
|
62
|
+
* upstream renderer does not read `[, scaler]` as a link), may span a comma
|
|
63
|
+
* (`data [, overwrite]`), and may cover several arguments at once (`[b, c]`),
|
|
64
|
+
* in which case every argument inside it is optional. Unlike `splitTypes`, a
|
|
65
|
+
* comma at any depth ends the current argument: in an argument list a comma
|
|
66
|
+
* always separates arguments, whether or not a bracket group spans it. */
|
|
67
|
+
function bracketedArgs(argList: string): Set<string> {
|
|
68
|
+
const optional = new Set<string>();
|
|
69
|
+
let depth = 0;
|
|
70
|
+
let current = "";
|
|
71
|
+
let bracketed = false;
|
|
72
|
+
|
|
73
|
+
const flush = () => {
|
|
74
|
+
const arg = current.trim();
|
|
75
|
+
if (arg.length > 0 && bracketed) optional.add(arg);
|
|
76
|
+
current = "";
|
|
77
|
+
bracketed = false;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
for (const ch of argList.replace(/\\\[/g, "[").replace(/\\\]/g, "]")) {
|
|
81
|
+
if (ch === "[") {
|
|
82
|
+
depth++;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (ch === "]" && depth > 0) {
|
|
86
|
+
depth--;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (ch === ",") {
|
|
90
|
+
flush();
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (depth > 0 && ch.trim().length > 0) bracketed = true;
|
|
94
|
+
current += ch;
|
|
95
|
+
}
|
|
96
|
+
flush();
|
|
97
|
+
return optional;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Split a documented `(type)` group into single tokens. Both `|` and `,` are
|
|
101
|
+
* union separators — a README is as likely to write `a|b|nil` as `a, b, nil` —
|
|
102
|
+
* but only at depth 0, so a comma inside a token's own group (`table[k, v]`,
|
|
103
|
+
* `function(self, dt`) stays part of that token. `<`/`>` are deliberately not
|
|
104
|
+
* tracked: no group in the corpus uses them, and they need the `=>`
|
|
105
|
+
* disambiguation the comparator-side splitter carries. */
|
|
106
|
+
function splitTypes(group: string): string[] {
|
|
107
|
+
const tokens: string[] = [];
|
|
108
|
+
let depth = 0;
|
|
109
|
+
let current = "";
|
|
110
|
+
for (const ch of group) {
|
|
111
|
+
if (ch === "(" || ch === "[" || ch === "{") depth++;
|
|
112
|
+
else if ((ch === ")" || ch === "]" || ch === "}") && depth > 0) depth--;
|
|
113
|
+
if ((ch === "|" || ch === ",") && depth === 0) {
|
|
114
|
+
tokens.push(current);
|
|
115
|
+
current = "";
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
current += ch;
|
|
119
|
+
}
|
|
120
|
+
tokens.push(current);
|
|
121
|
+
return tokens.map((token) => token.trim()).filter((token) => token.length > 0);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Parse one `* `name` (type) doc` bullet into a slot, splitting a `a|b|nil` or
|
|
125
|
+
* `a, b, nil` union into single tokens. Throws naming `fnName` when the bullet
|
|
126
|
+
* names a parameter but carries no `(type)`. */
|
|
127
|
+
function parseSlot(
|
|
128
|
+
label: string,
|
|
129
|
+
fnName: string,
|
|
130
|
+
line: string,
|
|
131
|
+
optionalNames: Set<string>,
|
|
132
|
+
): MarkdownParam {
|
|
133
|
+
const typed = TYPED_BULLET.exec(line);
|
|
134
|
+
if (typed === null) {
|
|
135
|
+
const named = NAMED_BULLET.exec(line);
|
|
136
|
+
const name = named?.[1] ?? line.trim();
|
|
137
|
+
throw new Error(
|
|
138
|
+
`parse-markdown-api: ${label}: ${fnName} row for \`${name}\` has no (type) — cannot resolve to a typed param (row: ${line.trim()})`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
const name = typed[1] as string;
|
|
142
|
+
const types = splitTypes(typed[2] as string);
|
|
143
|
+
const slot: MarkdownParam = { name, doc: (typed[3] as string).trim(), types };
|
|
144
|
+
if (optionalNames.has(name)) slot.is_optional = "True";
|
|
145
|
+
return slot;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Split the README into `##`/`###` `<receiver>.<fn>(...)` sections, ignoring
|
|
149
|
+
* headers that are not dotted API signatures. */
|
|
150
|
+
function sections(lines: string[]): { header: RegExpExecArray; body: string[] }[] {
|
|
151
|
+
const starts: number[] = [];
|
|
152
|
+
lines.forEach((line, index) => {
|
|
153
|
+
if (HEADER.test(line)) starts.push(index);
|
|
154
|
+
});
|
|
155
|
+
return starts.map((start, i) => {
|
|
156
|
+
const end = i + 1 < starts.length ? (starts[i + 1] as number) : lines.length;
|
|
157
|
+
return {
|
|
158
|
+
header: HEADER.exec(lines[start] as string) as RegExpExecArray,
|
|
159
|
+
body: lines.slice(start + 1, end),
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function parseSection(
|
|
165
|
+
label: string,
|
|
166
|
+
fnName: string,
|
|
167
|
+
body: string[],
|
|
168
|
+
optionalNames: Set<string>,
|
|
169
|
+
): {
|
|
170
|
+
description: string;
|
|
171
|
+
parameters: MarkdownParam[];
|
|
172
|
+
returnvalues: MarkdownParam[];
|
|
173
|
+
} {
|
|
174
|
+
const descriptionLines: string[] = [];
|
|
175
|
+
const parameters: MarkdownParam[] = [];
|
|
176
|
+
const returnvalues: MarkdownParam[] = [];
|
|
177
|
+
let mode: "none" | "params" | "returns" = "none";
|
|
178
|
+
let sawMarker = false;
|
|
179
|
+
|
|
180
|
+
for (const line of body) {
|
|
181
|
+
if (PARAM_MARKER.test(line)) {
|
|
182
|
+
mode = "params";
|
|
183
|
+
sawMarker = true;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (RETURN_MARKER.test(line)) {
|
|
187
|
+
mode = "returns";
|
|
188
|
+
sawMarker = true;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const isBullet = line.trimStart().startsWith("* ");
|
|
192
|
+
if (isBullet && mode === "params") {
|
|
193
|
+
parameters.push(parseSlot(label, fnName, line, optionalNames));
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (isBullet && mode === "returns") {
|
|
197
|
+
returnvalues.push(parseSlot(label, fnName, line, optionalNames));
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
// Any non-bullet line closes an open list, so a blank line before an
|
|
201
|
+
// option-table's `Acceptable values:` bullets stops them being captured.
|
|
202
|
+
mode = "none";
|
|
203
|
+
if (!sawMarker && line.trim().length > 0 && !line.startsWith("---")) {
|
|
204
|
+
descriptionLines.push(line.trim());
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return { description: descriptionLines.join(" "), parameters, returnvalues };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* `label` names the offending module in every loud-fail message. A library whose
|
|
213
|
+
* `.md` is usage/tutorial prose (defold-input ships six such modules) yields no
|
|
214
|
+
* signature section at all; emitting that as an empty namespace would silently
|
|
215
|
+
* publish a module with no members, so it throws instead.
|
|
216
|
+
*/
|
|
217
|
+
export function parseMarkdownApi(text: string, label = "markdown document"): MarkdownDoc {
|
|
218
|
+
const elements: MarkdownElement[] = [];
|
|
219
|
+
const prefixes = new Set<string>();
|
|
220
|
+
|
|
221
|
+
for (const { header, body } of sections(text.split("\n"))) {
|
|
222
|
+
const prefix = header[1] as string;
|
|
223
|
+
const fn = header[2] as string;
|
|
224
|
+
const name = `${prefix}.${fn}`;
|
|
225
|
+
prefixes.add(prefix);
|
|
226
|
+
const { description, parameters, returnvalues } = parseSection(
|
|
227
|
+
label,
|
|
228
|
+
name,
|
|
229
|
+
body,
|
|
230
|
+
bracketedArgs(header[3] as string),
|
|
231
|
+
);
|
|
232
|
+
elements.push({ type: "FUNCTION", name, description, parameters, returnvalues });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (prefixes.size > 1) {
|
|
236
|
+
throw new Error(
|
|
237
|
+
`parse-markdown-api: non-uniform module prefix across headers: ${[...prefixes].sort().join(", ")}`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
if (elements.length === 0) {
|
|
241
|
+
throw new Error(
|
|
242
|
+
`parse-markdown-api: ${label} has no \`##\`/\`###\` \`<receiver>.<fn>(...)\` API signature section — refusing to emit an empty namespace`,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
const namespace = [...prefixes][0] ?? "";
|
|
246
|
+
return { info: { namespace, brief: "", description: "" }, elements };
|
|
247
|
+
}
|