@nudojs/service 5.0.0-beta.0 → 5.0.0-beta.1
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/README.md +7 -12
- package/dist/{analysis-CSXrOVlg.d.ts → analysis-_DlXLC-L.d.ts} +17 -3
- package/dist/analysis.d.ts +3 -2
- package/dist/analysis.js +9 -5
- package/dist/{analyzer-types-JyJiCt8w.d.ts → analyzer-types-CfBrPK9L.d.ts} +1 -1
- package/dist/{chunk-LFZRUXGK.js → chunk-EG6FYQLI.js} +1 -1
- package/dist/chunk-J2PUE4NV.js +235 -0
- package/dist/{chunk-QAFMPM2E.js → chunk-NQXAF4LJ.js} +2277 -2225
- package/dist/{chunk-YLALZXTZ.js → chunk-ZXVAX7DE.js} +255 -11
- package/dist/{interface-BGlP7aIk.d.ts → emit/index.d.ts} +258 -18
- package/dist/{chunk-HEU4WON5.js → emit/index.js} +1627 -299
- package/dist/evaluator/evaluator-api.d.ts +1 -1
- package/dist/evaluator/evaluator-api.js +5 -5
- package/dist/index.d.ts +17 -19
- package/dist/index.js +188 -197
- package/dist/what-if-mIo7I5Ii.d.ts +21 -0
- package/package.json +9 -25
- package/dist/case.d.ts +0 -124
- package/dist/case.js +0 -18
- package/dist/chunk-6IZR4ZYJ.js +0 -1107
- package/dist/chunk-E4N4A4JV.js +0 -651
- package/dist/chunk-K3ZPOTP2.js +0 -199
- package/dist/chunk-PVJIMQRI.js +0 -246
- package/dist/chunk-QYRH7Z2Q.js +0 -89
- package/dist/chunk-U7JYTRIB.js +0 -888
- package/dist/chunk-YLVZMHWC.js +0 -364
- package/dist/dts.d.ts +0 -142
- package/dist/dts.js +0 -30
- package/dist/harvest.d.ts +0 -254
- package/dist/harvest.js +0 -75
- package/dist/interface.d.ts +0 -3
- package/dist/interface.js +0 -43
- package/dist/lsp.d.ts +0 -103
- package/dist/lsp.js +0 -36
|
@@ -1,159 +1,25 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
DiskCache,
|
|
3
|
+
collectLoadDepContents,
|
|
4
|
+
ifaceCacheKey,
|
|
5
|
+
injectBindings,
|
|
6
|
+
typeExprToDirective
|
|
7
|
+
} from "../chunk-J2PUE4NV.js";
|
|
4
8
|
import {
|
|
5
9
|
analyzeFileAsync,
|
|
6
10
|
defaultLoadModule,
|
|
7
11
|
evalAbsModuleGraph
|
|
8
|
-
} from "
|
|
12
|
+
} from "../chunk-NQXAF4LJ.js";
|
|
9
13
|
import {
|
|
10
14
|
diskCacheRoot,
|
|
11
15
|
findProjectConfig,
|
|
12
16
|
interfaceConfig,
|
|
13
17
|
matchesEmitAllowlist
|
|
14
|
-
} from "
|
|
15
|
-
|
|
16
|
-
// src/dep-contents.ts
|
|
17
|
-
import { loadModuleDepsFingerprint } from "@nudojs/core/internal";
|
|
18
|
-
function collectLoadDepContents(filePath, source, loadModule) {
|
|
19
|
-
const fp = loadModuleDepsFingerprint(source, loadModule, filePath);
|
|
20
|
-
const depContents = fp.contents.length > 0 ? fp.contents : fp.paths.map((path) => ({ path, content: null }));
|
|
21
|
-
return { depContents, truncated: fp.truncated };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// src/disk-cache.ts
|
|
25
|
-
import { createHash } from "crypto";
|
|
26
|
-
import { createRequire } from "module";
|
|
27
|
-
import { mkdirSync, readFileSync, writeFileSync, existsSync, rmSync } from "fs";
|
|
28
|
-
import { join, dirname, relative, sep, isAbsolute } from "path";
|
|
29
|
-
function readServiceVersion() {
|
|
30
|
-
try {
|
|
31
|
-
const require2 = createRequire(import.meta.url);
|
|
32
|
-
for (const p of ["../package.json", "./package.json", "../../package.json"]) {
|
|
33
|
-
try {
|
|
34
|
-
const pkg = require2(p);
|
|
35
|
-
if (pkg?.name === "@nudojs/service" && pkg.version) return pkg.version;
|
|
36
|
-
if (pkg?.version && p.includes("service")) return pkg.version;
|
|
37
|
-
} catch {
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
} catch {
|
|
41
|
-
}
|
|
42
|
-
return "0";
|
|
43
|
-
}
|
|
44
|
-
var ANALYSIS_ABI = `nudo-check-cache-v3+${readServiceVersion()}`;
|
|
45
|
-
function sha256Hex(data) {
|
|
46
|
-
return createHash("sha256").update(data).digest("hex");
|
|
47
|
-
}
|
|
48
|
-
function relativizePath(p, root) {
|
|
49
|
-
const norm = p.split(sep).join("/");
|
|
50
|
-
if (!root) return norm;
|
|
51
|
-
const r = relative(root, p).split(sep).join("/");
|
|
52
|
-
if (!r.startsWith("..") && !isAbsolute(r)) return r;
|
|
53
|
-
return `ext:${createHash("sha256").update(norm).digest("hex").slice(0, 16)}`;
|
|
54
|
-
}
|
|
55
|
-
function sanitizeCacheNamespace(ns) {
|
|
56
|
-
return ns.replace(/[^a-z0-9_-]/gi, "_").replace(/^_+|_+$/g, "") || "cache";
|
|
57
|
-
}
|
|
58
|
-
var DiskCache = class {
|
|
59
|
-
root;
|
|
60
|
-
ns;
|
|
61
|
-
enabled = false;
|
|
62
|
-
constructor(opts) {
|
|
63
|
-
this.root = opts.root;
|
|
64
|
-
this.ns = sanitizeCacheNamespace(opts.namespace);
|
|
65
|
-
this.enabled = !!opts.root;
|
|
66
|
-
}
|
|
67
|
-
pathFor(key) {
|
|
68
|
-
if (!/^[a-f0-9]{16,128}$/i.test(key)) {
|
|
69
|
-
throw new Error("DiskCache key must be a hex digest");
|
|
70
|
-
}
|
|
71
|
-
return join(this.root, this.ns, key.slice(0, 2), `${key}.json`);
|
|
72
|
-
}
|
|
73
|
-
get(key) {
|
|
74
|
-
if (!this.enabled || !this.root) return void 0;
|
|
75
|
-
try {
|
|
76
|
-
const p = this.pathFor(key);
|
|
77
|
-
if (!existsSync(p)) return void 0;
|
|
78
|
-
const raw = readFileSync(p, "utf8");
|
|
79
|
-
const parsed = JSON.parse(raw);
|
|
80
|
-
if (parsed?.abi !== ANALYSIS_ABI) return void 0;
|
|
81
|
-
return parsed.value;
|
|
82
|
-
} catch {
|
|
83
|
-
return void 0;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
set(key, value) {
|
|
87
|
-
if (!this.enabled || !this.root) return;
|
|
88
|
-
try {
|
|
89
|
-
const p = this.pathFor(key);
|
|
90
|
-
mkdirSync(dirname(p), { recursive: true });
|
|
91
|
-
writeFileSync(p, JSON.stringify({ abi: ANALYSIS_ABI, value }), "utf8");
|
|
92
|
-
} catch {
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
clearNamespace() {
|
|
96
|
-
if (!this.enabled || !this.root) return;
|
|
97
|
-
try {
|
|
98
|
-
rmSync(join(this.root, this.ns), { recursive: true, force: true });
|
|
99
|
-
} catch {
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
};
|
|
103
|
-
function checkCacheKey(filePath, source, opts) {
|
|
104
|
-
const rel = relativizePath(filePath, opts.projectDir);
|
|
105
|
-
const sidecarSha = opts.sidecarContent != null ? sha256Hex(opts.sidecarContent) : "nosidecar";
|
|
106
|
-
const depSeg = (opts.depContents ?? []).map(
|
|
107
|
-
(d) => `${relativizePath(d.path, opts.projectDir)}\0${d.content != null ? sha256Hex(d.content) : "miss"}`
|
|
108
|
-
).join("\n");
|
|
109
|
-
const envSeg = (opts.projectEnvNames ?? []).length > 0 ? [...opts.projectEnvNames ?? []].sort().join(",") : "-";
|
|
110
|
-
const cfgSeg = opts.analysisCfg ? `${opts.analysisCfg.mode ?? "-"}|${opts.analysisCfg.evalMissingSlot ?? "-"}|${opts.analysisCfg.callSiteBudget ?? "-"}|${opts.analysisCfg.entryThrows ?? "-"}|${opts.analysisCfg.ignoreThrows ?? "-"}` : "-";
|
|
111
|
-
return sha256Hex(
|
|
112
|
-
[
|
|
113
|
-
ANALYSIS_ABI,
|
|
114
|
-
rel,
|
|
115
|
-
opts.autoBind ? "ab1" : "ab0",
|
|
116
|
-
sha256Hex(source),
|
|
117
|
-
sidecarSha,
|
|
118
|
-
depSeg,
|
|
119
|
-
envSeg,
|
|
120
|
-
cfgSeg
|
|
121
|
-
].join("\0")
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
function ifaceCacheKey(filePath, source, opts) {
|
|
125
|
-
const rel = relativizePath(filePath, opts.projectDir);
|
|
126
|
-
const sidecarSeg = opts.autoBind && opts.sidecarSource !== void 0 ? `sc:${sha256Hex(opts.sidecarSource)}` : "sc0";
|
|
127
|
-
const depSeg = (opts.depContents ?? []).map(
|
|
128
|
-
(d) => `${relativizePath(d.path, opts.projectDir)}\0${d.content != null ? sha256Hex(d.content) : "miss"}`
|
|
129
|
-
).join("\n");
|
|
130
|
-
const envSeg = (opts.projectEnvNames ?? []).length > 0 ? [...opts.projectEnvNames ?? []].sort().join(",") : "-";
|
|
131
|
-
return sha256Hex(
|
|
132
|
-
[
|
|
133
|
-
ANALYSIS_ABI,
|
|
134
|
-
"iface",
|
|
135
|
-
rel,
|
|
136
|
-
opts.autoBind ? "ab1" : "ab0",
|
|
137
|
-
sha256Hex(source),
|
|
138
|
-
sidecarSeg,
|
|
139
|
-
depSeg,
|
|
140
|
-
envSeg
|
|
141
|
-
].join("\0")
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
|
-
function extractNudoImportSpecs(source) {
|
|
145
|
-
const specs = /* @__PURE__ */ new Set();
|
|
146
|
-
const named = /@nudo:import\s*\{[^}]*\}\s*from\s*["']([^"']+)["']/g;
|
|
147
|
-
const ns = /@nudo:import\s+\*\s+as\s+\w+\s+from\s*["']([^"']+)["']/g;
|
|
148
|
-
let m;
|
|
149
|
-
while (m = named.exec(source)) specs.add(m[1]);
|
|
150
|
-
while (m = ns.exec(source)) specs.add(m[1]);
|
|
151
|
-
return [...specs];
|
|
152
|
-
}
|
|
18
|
+
} from "../chunk-ZXVAX7DE.js";
|
|
153
19
|
|
|
154
|
-
// src/interface-surface.ts
|
|
155
|
-
import { readFileSync
|
|
156
|
-
import { dirname
|
|
20
|
+
// src/emit/interface-surface.ts
|
|
21
|
+
import { readFileSync, existsSync } from "fs";
|
|
22
|
+
import { dirname, resolve } from "path";
|
|
157
23
|
import {
|
|
158
24
|
effectiveInterface,
|
|
159
25
|
formatConstraint,
|
|
@@ -199,11 +65,11 @@ function cachedToEffective(e) {
|
|
|
199
65
|
}
|
|
200
66
|
async function interfaceSurface(filePath, opts = {}) {
|
|
201
67
|
const abs = resolve(filePath);
|
|
202
|
-
const source = opts.source ??
|
|
68
|
+
const source = opts.source ?? readFileSync(abs, "utf-8");
|
|
203
69
|
const fromBuffer = opts.source !== void 0;
|
|
204
70
|
const ifaceSince = interfaceDiagCount();
|
|
205
71
|
const refineSince = refineDiagCount();
|
|
206
|
-
const proj = findProjectConfig(
|
|
72
|
+
const proj = findProjectConfig(dirname(abs));
|
|
207
73
|
const autoBind = opts.autoBind ?? interfaceConfig(proj?.config).autoBind;
|
|
208
74
|
const loadModule = opts.loadModule ?? defaultLoadModule;
|
|
209
75
|
const exported = localNamedExports(source);
|
|
@@ -222,7 +88,7 @@ async function interfaceSurface(filePath, opts = {}) {
|
|
|
222
88
|
if (autoBind !== false) {
|
|
223
89
|
const openSc = loadModule(`./${sc.slice(sc.lastIndexOf("/") + 1)}`, abs);
|
|
224
90
|
if (openSc !== void 0) sidecarSource = openSc;
|
|
225
|
-
else if (
|
|
91
|
+
else if (existsSync(sc)) sidecarSource = readFileSync(sc, "utf-8");
|
|
226
92
|
}
|
|
227
93
|
} catch {
|
|
228
94
|
sidecarSource = void 0;
|
|
@@ -314,9 +180,9 @@ async function interfaceSurface(filePath, opts = {}) {
|
|
|
314
180
|
return entries;
|
|
315
181
|
}
|
|
316
182
|
|
|
317
|
-
// src/interface-emitter.ts
|
|
318
|
-
import { existsSync as
|
|
319
|
-
import { basename, dirname as
|
|
183
|
+
// src/emit/interface-emitter.ts
|
|
184
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync, renameSync, unlinkSync } from "fs";
|
|
185
|
+
import { basename, dirname as dirname2, relative, resolve as resolve2 } from "path";
|
|
320
186
|
import {
|
|
321
187
|
execNudoModule,
|
|
322
188
|
formatConstraint as formatConstraint2,
|
|
@@ -332,6 +198,365 @@ import {
|
|
|
332
198
|
takeRefineDiagsSince as takeRefineDiagsSince2
|
|
333
199
|
} from "@nudojs/core";
|
|
334
200
|
import { randomBytes } from "crypto";
|
|
201
|
+
|
|
202
|
+
// src/emit/case-emitter.ts
|
|
203
|
+
import { parse, extractDirectives } from "@nudojs/parser";
|
|
204
|
+
var GENERATED_PREFIX = "call@";
|
|
205
|
+
var GENERATED_CASE_LINE_REGEX = /^\s*\*?\s*@nudo:case\s+"(call@[^"]*)"/;
|
|
206
|
+
var IDENT_KEY_REGEX = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
207
|
+
var NUMBER_LITERAL_REGEX = /^-?\d+(\.\d+)?$/;
|
|
208
|
+
var UNSAFE_STRING_CHARS = /[,()[\]{}]/;
|
|
209
|
+
var UNSAFE_KEY_CHARS = /[,()[\]{}:]/;
|
|
210
|
+
var CONTROL_CHARS = /[\x00-\x1f\x7f]/;
|
|
211
|
+
function serializeStringLiteral(value) {
|
|
212
|
+
if (CONTROL_CHARS.test(value)) return null;
|
|
213
|
+
if (value.includes("*/")) return null;
|
|
214
|
+
if (UNSAFE_STRING_CHARS.test(value)) return null;
|
|
215
|
+
const hasDbl = value.includes('"');
|
|
216
|
+
const hasSgl = value.includes("'");
|
|
217
|
+
if (hasDbl && hasSgl) return null;
|
|
218
|
+
return hasDbl ? `'${value}'` : `"${value}"`;
|
|
219
|
+
}
|
|
220
|
+
function serializeObjectKey(key) {
|
|
221
|
+
if (IDENT_KEY_REGEX.test(key)) return key;
|
|
222
|
+
if (CONTROL_CHARS.test(key)) return null;
|
|
223
|
+
if (key.includes('"')) return null;
|
|
224
|
+
if (UNSAFE_KEY_CHARS.test(key) || key.includes("*/")) return null;
|
|
225
|
+
if (/^['"]|['"]$/.test(key)) return null;
|
|
226
|
+
return `"${key}"`;
|
|
227
|
+
}
|
|
228
|
+
function serializeCaseArg(a) {
|
|
229
|
+
const s = a.shape;
|
|
230
|
+
if (a.term?.op === "lit") {
|
|
231
|
+
const v = a.term.value;
|
|
232
|
+
if (typeof v === "number") {
|
|
233
|
+
if (!Number.isFinite(v)) return null;
|
|
234
|
+
const n = String(v);
|
|
235
|
+
return NUMBER_LITERAL_REGEX.test(n) ? n : null;
|
|
236
|
+
}
|
|
237
|
+
if (typeof v === "boolean") return v ? "true" : "false";
|
|
238
|
+
if (v === null) return "null";
|
|
239
|
+
if (v === void 0) return "undefined";
|
|
240
|
+
if (typeof v === "bigint") return null;
|
|
241
|
+
if (typeof v === "string") return serializeStringLiteral(v);
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
switch (s.k) {
|
|
245
|
+
case "prim":
|
|
246
|
+
if (s.type === "number") return "number()";
|
|
247
|
+
if (s.type === "string") return "string()";
|
|
248
|
+
if (s.type === "boolean") return "boolean()";
|
|
249
|
+
return null;
|
|
250
|
+
case "unknown":
|
|
251
|
+
case "any":
|
|
252
|
+
return "any()";
|
|
253
|
+
case "never":
|
|
254
|
+
return "never";
|
|
255
|
+
case "sum": {
|
|
256
|
+
const parts = [];
|
|
257
|
+
for (const member of s.members) {
|
|
258
|
+
const ser = serializeCaseArg(member);
|
|
259
|
+
if (ser === null) return null;
|
|
260
|
+
parts.push(ser);
|
|
261
|
+
}
|
|
262
|
+
return `union(${parts.join(", ")})`;
|
|
263
|
+
}
|
|
264
|
+
case "arr": {
|
|
265
|
+
const el = serializeCaseArg(s.element);
|
|
266
|
+
return el === null ? null : `array(${el})`;
|
|
267
|
+
}
|
|
268
|
+
case "tuple": {
|
|
269
|
+
const parts = [];
|
|
270
|
+
for (const el of s.elements) {
|
|
271
|
+
const ser = serializeCaseArg(el);
|
|
272
|
+
if (ser === null) return null;
|
|
273
|
+
parts.push(ser);
|
|
274
|
+
}
|
|
275
|
+
return `[${parts.join(", ")}]`;
|
|
276
|
+
}
|
|
277
|
+
case "obj": {
|
|
278
|
+
const parts = [];
|
|
279
|
+
for (const [key, slot] of Object.entries(s.slots)) {
|
|
280
|
+
const vs = serializeCaseArg(slot.value);
|
|
281
|
+
if (vs === null) return null;
|
|
282
|
+
const ks = serializeObjectKey(key);
|
|
283
|
+
if (ks === null) return null;
|
|
284
|
+
parts.push(`${ks}: ${vs}`);
|
|
285
|
+
}
|
|
286
|
+
return parts.length === 0 ? "{}" : `{ ${parts.join(", ")} }`;
|
|
287
|
+
}
|
|
288
|
+
default:
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function buildCaseDirective(name, argsAbs) {
|
|
293
|
+
if (name.includes('"') || /[\r\n]/.test(name)) return null;
|
|
294
|
+
const parts = [];
|
|
295
|
+
for (const arg of argsAbs) {
|
|
296
|
+
const s = serializeCaseArg(arg);
|
|
297
|
+
if (s === null) return null;
|
|
298
|
+
parts.push(s);
|
|
299
|
+
}
|
|
300
|
+
return ` * @nudo:case "${name}" (${parts.join(", ")})`;
|
|
301
|
+
}
|
|
302
|
+
function stripGeneratedCaseDirectives(source) {
|
|
303
|
+
const lines = source.split("\n");
|
|
304
|
+
const removed = [];
|
|
305
|
+
const drop = new Array(lines.length).fill(false);
|
|
306
|
+
for (let i = 0; i < lines.length; i++) {
|
|
307
|
+
const m = lines[i].match(GENERATED_CASE_LINE_REGEX);
|
|
308
|
+
if (m) {
|
|
309
|
+
drop[i] = true;
|
|
310
|
+
removed.push(m[1]);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
for (let i = 0; i < lines.length; i++) {
|
|
314
|
+
if (!/^\s*\/\*\*\s*$/.test(lines[i])) continue;
|
|
315
|
+
let j = i + 1;
|
|
316
|
+
let clean = true;
|
|
317
|
+
while (j < lines.length) {
|
|
318
|
+
if (/\*\//.test(lines[j])) break;
|
|
319
|
+
if (/\/\*/.test(lines[j])) {
|
|
320
|
+
clean = false;
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
j++;
|
|
324
|
+
}
|
|
325
|
+
if (!clean || j >= lines.length) continue;
|
|
326
|
+
if (!/^\s*\*\/\s*$/.test(lines[j])) continue;
|
|
327
|
+
let onlyFiller = true;
|
|
328
|
+
for (let k = i + 1; k < j; k++) {
|
|
329
|
+
if (drop[k]) continue;
|
|
330
|
+
const t = lines[k].trim();
|
|
331
|
+
if (t !== "" && !/^\*{1,2}$/.test(t)) {
|
|
332
|
+
onlyFiller = false;
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (onlyFiller) {
|
|
337
|
+
for (let k = i; k <= j; k++) drop[k] = true;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
source: lines.filter((_, i) => !drop[i]).join("\n"),
|
|
342
|
+
removed
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function collectExistingCases(source) {
|
|
346
|
+
const byName = /* @__PURE__ */ new Map();
|
|
347
|
+
let ast;
|
|
348
|
+
try {
|
|
349
|
+
ast = parse(source, { errorRecovery: true });
|
|
350
|
+
} catch {
|
|
351
|
+
return byName;
|
|
352
|
+
}
|
|
353
|
+
for (const { name, directives } of extractDirectives(ast)) {
|
|
354
|
+
for (const d of directives) {
|
|
355
|
+
if (d.kind !== "case") continue;
|
|
356
|
+
const list = byName.get(name);
|
|
357
|
+
if (list) list.push(d);
|
|
358
|
+
else byName.set(name, [d]);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return byName;
|
|
362
|
+
}
|
|
363
|
+
function insertGeneratedCaseDirectives(source, analysis) {
|
|
364
|
+
const written = [];
|
|
365
|
+
const skipped = [];
|
|
366
|
+
const existing = collectExistingCases(source);
|
|
367
|
+
const edits = [];
|
|
368
|
+
for (const fn of analysis.functions) {
|
|
369
|
+
const existingCases = existing.get(fn.name) ?? [];
|
|
370
|
+
if (existingCases.some((c) => !c.name.startsWith(GENERATED_PREFIX))) {
|
|
371
|
+
skipped.push({ fn: fn.name, reason: "hand-written" });
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
if (existingCases.length > 0) {
|
|
375
|
+
skipped.push({ fn: fn.name, reason: "already-generated" });
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
if (fn.skipped) {
|
|
379
|
+
skipped.push({ fn: fn.name, reason: "skipped" });
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
if (fn.noDeclaration) {
|
|
383
|
+
skipped.push({ fn: fn.name, reason: "no-declaration" });
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (fn.entryOnly) {
|
|
387
|
+
skipped.push({ fn: fn.name, reason: "entry-only" });
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
const callsiteCases = fn.cases.filter((c) => c.source === "callsite");
|
|
391
|
+
if (callsiteCases.length === 0) {
|
|
392
|
+
skipped.push({ fn: fn.name, reason: "no-serializable-cases", detail: "no callsite cases" });
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
const built = [];
|
|
396
|
+
const names = [];
|
|
397
|
+
for (const c of callsiteCases) {
|
|
398
|
+
const directive = buildCaseDirective(c.name, c.argAbs ?? []);
|
|
399
|
+
if (directive === null) {
|
|
400
|
+
skipped.push({ fn: fn.name, reason: "no-serializable-cases", detail: `case ${c.name} not serializable` });
|
|
401
|
+
} else {
|
|
402
|
+
built.push(directive);
|
|
403
|
+
names.push(c.name);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (built.length === 0) continue;
|
|
407
|
+
edits.push({ line: fn.loc.start.line, column: fn.loc.start.column, directives: built, fn: fn.name });
|
|
408
|
+
written.push({ fn: fn.name, cases: names });
|
|
409
|
+
}
|
|
410
|
+
edits.sort((a, b) => b.line - a.line);
|
|
411
|
+
const lines = source.split("\n");
|
|
412
|
+
for (const edit of edits) {
|
|
413
|
+
const declIdx = edit.line - 1;
|
|
414
|
+
const indent = " ".repeat(edit.column);
|
|
415
|
+
let insertIdx = -1;
|
|
416
|
+
let blockIndent = "";
|
|
417
|
+
const aboveIdx = declIdx - 1;
|
|
418
|
+
if (aboveIdx >= 0 && /^\s*\*\/\s*$/.test(lines[aboveIdx])) {
|
|
419
|
+
let s = aboveIdx - 1;
|
|
420
|
+
while (s >= 0) {
|
|
421
|
+
const t = lines[s];
|
|
422
|
+
if (/^\s*\/\*\*/.test(t)) {
|
|
423
|
+
insertIdx = s + 1;
|
|
424
|
+
blockIndent = t.match(/^\s*/)?.[0] ?? "";
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
if (/^\s*\*/.test(t) || t.trim() === "") {
|
|
428
|
+
s--;
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (insertIdx >= 0) {
|
|
435
|
+
lines.splice(insertIdx, 0, ...edit.directives.map((d) => blockIndent + d));
|
|
436
|
+
} else {
|
|
437
|
+
lines.splice(declIdx, 0, `${indent}/**`, ...edit.directives.map((d) => indent + d), `${indent} */`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return {
|
|
441
|
+
source: lines.join("\n"),
|
|
442
|
+
changed: written.length > 0,
|
|
443
|
+
written,
|
|
444
|
+
skipped
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
function diffOps(a, b) {
|
|
448
|
+
let start = 0;
|
|
449
|
+
while (start < a.length && start < b.length && a[start] === b[start]) start++;
|
|
450
|
+
let endA = a.length;
|
|
451
|
+
let endB = b.length;
|
|
452
|
+
while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {
|
|
453
|
+
endA--;
|
|
454
|
+
endB--;
|
|
455
|
+
}
|
|
456
|
+
const midA = a.slice(start, endA);
|
|
457
|
+
const midB = b.slice(start, endB);
|
|
458
|
+
const n = midA.length;
|
|
459
|
+
const m = midB.length;
|
|
460
|
+
const dp = new Int32Array((n + 1) * (m + 1));
|
|
461
|
+
for (let i2 = n - 1; i2 >= 0; i2--) {
|
|
462
|
+
for (let j2 = m - 1; j2 >= 0; j2--) {
|
|
463
|
+
dp[i2 * (m + 1) + j2] = midA[i2] === midB[j2] ? dp[(i2 + 1) * (m + 1) + (j2 + 1)] + 1 : Math.max(dp[(i2 + 1) * (m + 1) + j2], dp[i2 * (m + 1) + (j2 + 1)]);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
const ops = [];
|
|
467
|
+
for (let k = 0; k < start; k++) ops.push({ type: "equal", text: a[k] });
|
|
468
|
+
let i = 0;
|
|
469
|
+
let j = 0;
|
|
470
|
+
while (i < n && j < m) {
|
|
471
|
+
if (midA[i] === midB[j]) {
|
|
472
|
+
ops.push({ type: "equal", text: midA[i] });
|
|
473
|
+
i++;
|
|
474
|
+
j++;
|
|
475
|
+
} else if (dp[(i + 1) * (m + 1) + j] >= dp[i * (m + 1) + (j + 1)]) {
|
|
476
|
+
ops.push({ type: "delete", text: midA[i] });
|
|
477
|
+
i++;
|
|
478
|
+
} else {
|
|
479
|
+
ops.push({ type: "insert", text: midB[j] });
|
|
480
|
+
j++;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
while (i < n) ops.push({ type: "delete", text: midA[i++] });
|
|
484
|
+
while (j < m) ops.push({ type: "insert", text: midB[j++] });
|
|
485
|
+
for (let k = endA; k < a.length; k++) ops.push({ type: "equal", text: a[k] });
|
|
486
|
+
return ops;
|
|
487
|
+
}
|
|
488
|
+
var DIFF_CONTEXT = 3;
|
|
489
|
+
function formatRange(start, count) {
|
|
490
|
+
const s = count === 0 ? start - 1 : start;
|
|
491
|
+
return count === 1 ? `${s}` : `${s},${count}`;
|
|
492
|
+
}
|
|
493
|
+
function unifiedDiff(a, b, path) {
|
|
494
|
+
if (a === b) return "";
|
|
495
|
+
const ops = diffOps(a.split("\n"), b.split("\n"));
|
|
496
|
+
const hunks = [];
|
|
497
|
+
let idx = 0;
|
|
498
|
+
while (idx < ops.length) {
|
|
499
|
+
if (ops[idx].type === "equal") {
|
|
500
|
+
idx++;
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
let start = idx;
|
|
504
|
+
let ctx = 0;
|
|
505
|
+
while (start > 0 && ops[start - 1].type === "equal" && ctx < DIFF_CONTEXT) {
|
|
506
|
+
start--;
|
|
507
|
+
ctx++;
|
|
508
|
+
}
|
|
509
|
+
let j = idx;
|
|
510
|
+
let equalRun = 0;
|
|
511
|
+
let lastChange = idx;
|
|
512
|
+
while (j < ops.length) {
|
|
513
|
+
if (ops[j].type === "equal") {
|
|
514
|
+
equalRun++;
|
|
515
|
+
if (equalRun > DIFF_CONTEXT * 2) break;
|
|
516
|
+
} else {
|
|
517
|
+
equalRun = 0;
|
|
518
|
+
lastChange = j;
|
|
519
|
+
}
|
|
520
|
+
j++;
|
|
521
|
+
}
|
|
522
|
+
let end = lastChange + 1;
|
|
523
|
+
ctx = 0;
|
|
524
|
+
while (end < ops.length && ops[end].type === "equal" && ctx < DIFF_CONTEXT) {
|
|
525
|
+
end++;
|
|
526
|
+
ctx++;
|
|
527
|
+
}
|
|
528
|
+
hunks.push({ start, ops: ops.slice(start, end) });
|
|
529
|
+
idx = end;
|
|
530
|
+
}
|
|
531
|
+
const out = [`--- a/${path}`, `+++ b/${path}`];
|
|
532
|
+
for (let h = 0; h < hunks.length; h++) {
|
|
533
|
+
const { start, ops: hunkOps } = hunks[h];
|
|
534
|
+
let oldCount = 0;
|
|
535
|
+
let newCount = 0;
|
|
536
|
+
for (let k = 0; k < start; k++) {
|
|
537
|
+
if (ops[k].type !== "insert") oldCount++;
|
|
538
|
+
if (ops[k].type !== "delete") newCount++;
|
|
539
|
+
}
|
|
540
|
+
let dels = 0;
|
|
541
|
+
let ins = 0;
|
|
542
|
+
for (const op of hunkOps) {
|
|
543
|
+
if (op.type === "delete") dels++;
|
|
544
|
+
else if (op.type === "insert") ins++;
|
|
545
|
+
else {
|
|
546
|
+
dels++;
|
|
547
|
+
ins++;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
out.push(`@@ -${formatRange(oldCount + 1, dels)} +${formatRange(newCount + 1, ins)} @@`);
|
|
551
|
+
for (const op of hunkOps) {
|
|
552
|
+
const prefix = op.type === "equal" ? " " : op.type === "delete" ? "-" : "+";
|
|
553
|
+
out.push(prefix + op.text);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
return out.join("\n") + "\n";
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// src/emit/interface-emitter.ts
|
|
335
560
|
var GENERATED_HEADER = "// @generated by nudo \u2014 do not edit; regenerate with `nudo contract --emit`";
|
|
336
561
|
async function emitInterface(filePath, opts) {
|
|
337
562
|
const abs = resolve2(filePath);
|
|
@@ -340,7 +565,7 @@ async function emitInterface(filePath, opts) {
|
|
|
340
565
|
`emit target '${abs}' is inside node_modules; contract sidecars are never written there`
|
|
341
566
|
);
|
|
342
567
|
}
|
|
343
|
-
const proj = findProjectConfig(
|
|
568
|
+
const proj = findProjectConfig(dirname2(abs));
|
|
344
569
|
const allow = interfaceConfig(proj?.config).emit;
|
|
345
570
|
if (!matchesEmitAllowlist(abs, proj?.projectDir, allow)) {
|
|
346
571
|
return {
|
|
@@ -351,24 +576,24 @@ async function emitInterface(filePath, opts) {
|
|
|
351
576
|
{
|
|
352
577
|
code: "nudo:interface-emit-denied",
|
|
353
578
|
severity: "warning",
|
|
354
|
-
message: `emit target '${
|
|
579
|
+
message: `emit target '${relative(process.cwd(), abs) || abs}' is outside package.json#nudo.contract.emit allowlist`
|
|
355
580
|
}
|
|
356
581
|
],
|
|
357
582
|
sidecarPath: sidecarPathOf2(abs)
|
|
358
583
|
};
|
|
359
584
|
}
|
|
360
|
-
const source = opts.source ??
|
|
585
|
+
const source = opts.source ?? readFileSync2(abs, "utf-8");
|
|
361
586
|
const ifaceSince = interfaceDiagCount2();
|
|
362
587
|
const refineSince = refineDiagCount2();
|
|
363
588
|
const sidecarPath = sidecarPathOf2(abs);
|
|
364
|
-
const sidecarSrc =
|
|
365
|
-
const srcRel =
|
|
589
|
+
const sidecarSrc = existsSync2(sidecarPath) ? readFileSync2(sidecarPath, "utf-8") : "";
|
|
590
|
+
const srcRel = relative(dirname2(sidecarPath), abs) || basename(abs);
|
|
366
591
|
const sections = collectGeneratedSections(sidecarSrc);
|
|
367
592
|
const generatedNames = new Set(sections.flatMap((s) => s.names));
|
|
368
593
|
const declared = topLevelDeclaredNames(sidecarSrc);
|
|
369
594
|
if (declared === void 0) {
|
|
370
595
|
throw new Error(
|
|
371
|
-
`sidecar '${
|
|
596
|
+
`sidecar '${relative(process.cwd(), sidecarPath) || sidecarPath}' is not parseable; fix it before emitting`
|
|
372
597
|
);
|
|
373
598
|
}
|
|
374
599
|
for (const n of generatedNames) declared.delete(n);
|
|
@@ -481,15 +706,15 @@ async function emitInterface(filePath, opts) {
|
|
|
481
706
|
finalContent = joinSections(base, [...preserved, ...accepted.map((a) => a.text)]);
|
|
482
707
|
}
|
|
483
708
|
const changed = finalContent !== sidecarSrc;
|
|
484
|
-
const diff = changed ? unifiedDiff(sidecarSrc, finalContent,
|
|
709
|
+
const diff = changed ? unifiedDiff(sidecarSrc, finalContent, relative(process.cwd(), sidecarPath) || sidecarPath) : void 0;
|
|
485
710
|
if (changed && !opts.dryRun) {
|
|
486
711
|
const tmp = `${sidecarPath}.tmp-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
487
712
|
try {
|
|
488
|
-
|
|
713
|
+
writeFileSync(tmp, finalContent, "utf-8");
|
|
489
714
|
renameSync(tmp, sidecarPath);
|
|
490
715
|
} catch (e) {
|
|
491
716
|
try {
|
|
492
|
-
if (
|
|
717
|
+
if (existsSync2(tmp)) unlinkSync(tmp);
|
|
493
718
|
} catch {
|
|
494
719
|
}
|
|
495
720
|
throw e;
|
|
@@ -679,9 +904,9 @@ function joinSections(base, sectionTexts) {
|
|
|
679
904
|
return lead + normalized.join("\n");
|
|
680
905
|
}
|
|
681
906
|
|
|
682
|
-
// src/interface-draft.ts
|
|
683
|
-
import { existsSync as
|
|
684
|
-
import { basename as basename2, dirname as
|
|
907
|
+
// src/emit/interface-draft.ts
|
|
908
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2, realpathSync } from "fs";
|
|
909
|
+
import { basename as basename2, dirname as dirname3, isAbsolute, join, relative as relative2 } from "path";
|
|
685
910
|
import {
|
|
686
911
|
effectiveInterface as effectiveInterface2,
|
|
687
912
|
formatConstraint as formatConstraint3,
|
|
@@ -692,12 +917,12 @@ import {
|
|
|
692
917
|
localNamedExports as localNamedExports3,
|
|
693
918
|
sidecarPathOf as sidecarPathOf3
|
|
694
919
|
} from "@nudojs/core";
|
|
695
|
-
import { parse } from "@nudojs/parser";
|
|
920
|
+
import { parse as parse2 } from "@nudojs/parser";
|
|
696
921
|
function collectParamBodyAccesses(source) {
|
|
697
922
|
const out = /* @__PURE__ */ new Map();
|
|
698
923
|
let ast;
|
|
699
924
|
try {
|
|
700
|
-
ast =
|
|
925
|
+
ast = parse2(source);
|
|
701
926
|
} catch {
|
|
702
927
|
return out;
|
|
703
928
|
}
|
|
@@ -1059,7 +1284,7 @@ function suggestedBodyDsl(fnName, params) {
|
|
|
1059
1284
|
return `// suggested (body-read, not a contract): ${fnName} = fn({ ${parts.join(", ")} })`;
|
|
1060
1285
|
}
|
|
1061
1286
|
async function draftInterface(filePath, opts = {}) {
|
|
1062
|
-
const source = opts.source ??
|
|
1287
|
+
const source = opts.source ?? readFileSync3(filePath, "utf-8");
|
|
1063
1288
|
const loadModule = opts.loadModule ?? defaultLoadModule;
|
|
1064
1289
|
const sidecarPath = sidecarPathOf3(filePath);
|
|
1065
1290
|
const wantBody = opts.bodyAccesses !== false;
|
|
@@ -1209,7 +1434,7 @@ function safeRealpath(p) {
|
|
|
1209
1434
|
return realpathSync(p);
|
|
1210
1435
|
} catch {
|
|
1211
1436
|
try {
|
|
1212
|
-
return
|
|
1437
|
+
return join(realpathSync(dirname3(p)), basename2(p));
|
|
1213
1438
|
} catch {
|
|
1214
1439
|
return p;
|
|
1215
1440
|
}
|
|
@@ -1232,17 +1457,17 @@ function writeInterfaceDraft(filePath, draftSource, opts = {}) {
|
|
|
1232
1457
|
if (opts.projectDir) {
|
|
1233
1458
|
const rootReal = safeRealpath(opts.projectDir);
|
|
1234
1459
|
const draftReal = safeRealpath(draftPath);
|
|
1235
|
-
const rel =
|
|
1236
|
-
if (rel.startsWith("..") ||
|
|
1460
|
+
const rel = relative2(rootReal, draftReal);
|
|
1461
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
1237
1462
|
throw new Error(`draft write refused: outside project root ${opts.projectDir}`);
|
|
1238
1463
|
}
|
|
1239
1464
|
}
|
|
1240
1465
|
const draftable = opts.draftable !== void 0 ? opts.draftable : opts.entries !== void 0 ? isDraftableEntry(opts.entries) : /export\s+const\s+[\p{ID_Start}$_][\p{ID_Continue}$]*\s*=/u.test(draftSource);
|
|
1241
|
-
const prev =
|
|
1466
|
+
const prev = existsSync3(draftPath) ? readFileSync3(draftPath, "utf-8") : void 0;
|
|
1242
1467
|
const changed = prev !== draftSource;
|
|
1243
1468
|
const written = !opts.dryRun && changed && draftable;
|
|
1244
1469
|
if (written) {
|
|
1245
|
-
|
|
1470
|
+
writeFileSync2(draftPath, draftSource, "utf-8");
|
|
1246
1471
|
}
|
|
1247
1472
|
return {
|
|
1248
1473
|
draftPath,
|
|
@@ -1288,10 +1513,10 @@ function formatDraftSummary(sourceRel, draftRel, result, write) {
|
|
|
1288
1513
|
return lines;
|
|
1289
1514
|
}
|
|
1290
1515
|
|
|
1291
|
-
// src/interface-derivation-derive.ts
|
|
1292
|
-
import { existsSync as
|
|
1293
|
-
import { basename as basename3, dirname as
|
|
1294
|
-
import { parse as
|
|
1516
|
+
// src/emit/interface-derivation-derive.ts
|
|
1517
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
1518
|
+
import { basename as basename3, dirname as dirname4, relative as relative3, resolve as resolve3 } from "path";
|
|
1519
|
+
import { parse as parse3 } from "@nudojs/parser";
|
|
1295
1520
|
import {
|
|
1296
1521
|
constraintToEntryAbs,
|
|
1297
1522
|
effectiveInterface as effectiveInterface3,
|
|
@@ -1313,7 +1538,7 @@ import {
|
|
|
1313
1538
|
tagDerivationRoot
|
|
1314
1539
|
} from "@nudojs/core/internal";
|
|
1315
1540
|
|
|
1316
|
-
// src/interface-derivation-project.ts
|
|
1541
|
+
// src/emit/interface-derivation-project.ts
|
|
1317
1542
|
import { formatConstraint as formatConstraint4, joinThenProject as joinThenProject3 } from "@nudojs/core";
|
|
1318
1543
|
import { derivationChain, getDerivation, projectDerivationDsl } from "@nudojs/core/internal";
|
|
1319
1544
|
function projectParamSlot(absList, paramName) {
|
|
@@ -1448,12 +1673,12 @@ function projectReturnRelativeToParams(node, params, paramNames) {
|
|
|
1448
1673
|
return void 0;
|
|
1449
1674
|
}
|
|
1450
1675
|
|
|
1451
|
-
// src/interface-derivation-derive.ts
|
|
1676
|
+
// src/emit/interface-derivation-derive.ts
|
|
1452
1677
|
function extractFnConstraintSources(sidecarSrc, fnName) {
|
|
1453
1678
|
const out = { params: {} };
|
|
1454
1679
|
let ast;
|
|
1455
1680
|
try {
|
|
1456
|
-
ast =
|
|
1681
|
+
ast = parse3(sidecarSrc);
|
|
1457
1682
|
} catch {
|
|
1458
1683
|
return out;
|
|
1459
1684
|
}
|
|
@@ -1524,7 +1749,7 @@ function extractFnConstraintSources(sidecarSrc, fnName) {
|
|
|
1524
1749
|
}
|
|
1525
1750
|
function functionParamNames(source, fnName) {
|
|
1526
1751
|
try {
|
|
1527
|
-
const ast =
|
|
1752
|
+
const ast = parse3(source);
|
|
1528
1753
|
if (fnName.includes(".")) {
|
|
1529
1754
|
const [clsName, methodName] = fnName.split(".", 2);
|
|
1530
1755
|
for (const stmt of ast.program.body) {
|
|
@@ -1585,8 +1810,8 @@ function topLevelFnNames(source) {
|
|
|
1585
1810
|
function importLocalMap(source, fromFile) {
|
|
1586
1811
|
const out = /* @__PURE__ */ new Map();
|
|
1587
1812
|
try {
|
|
1588
|
-
const ast =
|
|
1589
|
-
const base =
|
|
1813
|
+
const ast = parse3(source);
|
|
1814
|
+
const base = dirname4(resolve3(fromFile));
|
|
1590
1815
|
for (const stmt of ast.program.body) {
|
|
1591
1816
|
if (stmt.type !== "ImportDeclaration") continue;
|
|
1592
1817
|
const spec = stmt.source.value;
|
|
@@ -1594,7 +1819,7 @@ function importLocalMap(source, fromFile) {
|
|
|
1594
1819
|
const raw = resolve3(base, spec);
|
|
1595
1820
|
let modulePath = null;
|
|
1596
1821
|
for (const cand of [raw, `${raw}.js`, `${raw}.ts`, `${raw}.mjs`]) {
|
|
1597
|
-
if (
|
|
1822
|
+
if (existsSync4(cand)) {
|
|
1598
1823
|
modulePath = cand;
|
|
1599
1824
|
break;
|
|
1600
1825
|
}
|
|
@@ -1613,12 +1838,12 @@ function importLocalMap(source, fromFile) {
|
|
|
1613
1838
|
}
|
|
1614
1839
|
function deriveFromRoot(filePath, opts = {}) {
|
|
1615
1840
|
const abs = resolve3(filePath);
|
|
1616
|
-
const source =
|
|
1841
|
+
const source = readFileSync4(abs, "utf-8");
|
|
1617
1842
|
const since = interfaceDiagCount3();
|
|
1618
|
-
const autoBind = opts.autoBind ?? interfaceConfig(findProjectConfig(
|
|
1843
|
+
const autoBind = opts.autoBind ?? interfaceConfig(findProjectConfig(dirname4(abs))?.config).autoBind;
|
|
1619
1844
|
const loadModule = opts.loadModule ?? defaultLoadModule;
|
|
1620
1845
|
const sidecarPath = sidecarPathOf4(abs);
|
|
1621
|
-
const sidecarSrc =
|
|
1846
|
+
const sidecarSrc = existsSync4(sidecarPath) && !isNodeModulesPath2(sidecarPath) ? readFileSync4(sidecarPath, "utf-8") : "";
|
|
1622
1847
|
const fnNames = topLevelFnNames(source);
|
|
1623
1848
|
const roots = [];
|
|
1624
1849
|
const plans = [];
|
|
@@ -1659,7 +1884,7 @@ function deriveFromRoot(filePath, opts = {}) {
|
|
|
1659
1884
|
const wanted = opts.fnNames && opts.fnNames.length > 0 ? new Set(opts.fnNames) : void 0;
|
|
1660
1885
|
const derived = [];
|
|
1661
1886
|
for (const plan of plans) {
|
|
1662
|
-
const relRoot =
|
|
1887
|
+
const relRoot = relative3(process.cwd(), abs);
|
|
1663
1888
|
const label = `${relRoot === "" || relRoot.startsWith("..") ? basename3(abs) : relRoot}:${plan.fnName}`;
|
|
1664
1889
|
const rows = deriveOneRoot(plan, source, abs, modules, importLocals, label);
|
|
1665
1890
|
for (const row of rows) {
|
|
@@ -1729,7 +1954,7 @@ function deriveOneRoot(plan, source, file, modules, importLocals, label) {
|
|
|
1729
1954
|
targetFile = file;
|
|
1730
1955
|
}
|
|
1731
1956
|
if (isNodeModulesPath2(targetFile)) continue;
|
|
1732
|
-
if (!
|
|
1957
|
+
if (!existsSync4(targetFile)) continue;
|
|
1733
1958
|
const key = `${targetFile}::${targetExport}`;
|
|
1734
1959
|
let agg = byCallee.get(key);
|
|
1735
1960
|
if (!agg) {
|
|
@@ -1743,7 +1968,7 @@ function deriveOneRoot(plan, source, file, modules, importLocals, label) {
|
|
|
1743
1968
|
for (const agg of byCallee.values()) {
|
|
1744
1969
|
let targetSource;
|
|
1745
1970
|
try {
|
|
1746
|
-
targetSource =
|
|
1971
|
+
targetSource = readFileSync4(agg.targetFile, "utf-8");
|
|
1747
1972
|
} catch {
|
|
1748
1973
|
continue;
|
|
1749
1974
|
}
|
|
@@ -1822,11 +2047,11 @@ function deriveOneRoot(plan, source, file, modules, importLocals, label) {
|
|
|
1822
2047
|
}
|
|
1823
2048
|
}
|
|
1824
2049
|
|
|
1825
|
-
// src/interface-derivation-rewrite.ts
|
|
1826
|
-
import { relative as
|
|
2050
|
+
// src/emit/interface-derivation-rewrite.ts
|
|
2051
|
+
import { relative as relative4, resolve as resolve4 } from "path";
|
|
1827
2052
|
function resolveRelImport(fromSpec, fromDir, targetDir) {
|
|
1828
2053
|
const abs = resolve4(fromDir, fromSpec);
|
|
1829
|
-
let rel =
|
|
2054
|
+
let rel = relative4(targetDir, abs);
|
|
1830
2055
|
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
1831
2056
|
return rel.split("\\").join("/");
|
|
1832
2057
|
}
|
|
@@ -1943,9 +2168,9 @@ function formatDerivedSection(row, opts) {
|
|
|
1943
2168
|
return { text: lines.join("\n"), usedNames: [...usedNames] };
|
|
1944
2169
|
}
|
|
1945
2170
|
|
|
1946
|
-
// src/interface-derivation-emit.ts
|
|
1947
|
-
import { existsSync as
|
|
1948
|
-
import { basename as basename4, dirname as
|
|
2171
|
+
// src/emit/interface-derivation-emit.ts
|
|
2172
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
2173
|
+
import { basename as basename4, dirname as dirname5, relative as relative5, resolve as resolve5 } from "path";
|
|
1949
2174
|
import {
|
|
1950
2175
|
execNudoModule as execNudoModule2,
|
|
1951
2176
|
isNodeModulesPath as isNodeModulesPath3,
|
|
@@ -2011,10 +2236,10 @@ function emitDerivedFromRoot(rootFile, opts) {
|
|
|
2011
2236
|
if (!derive.hasRoot) {
|
|
2012
2237
|
return { sidecars: [], hasRoot: false, roots: [], entryOnly: true };
|
|
2013
2238
|
}
|
|
2014
|
-
const proj = findProjectConfig(
|
|
2239
|
+
const proj = findProjectConfig(dirname5(abs));
|
|
2015
2240
|
const allow = interfaceConfig(proj?.config).emit;
|
|
2016
2241
|
const projectDir = proj?.projectDir;
|
|
2017
|
-
const rootSidecarDir =
|
|
2242
|
+
const rootSidecarDir = dirname5(sidecarPathOf5(abs));
|
|
2018
2243
|
const wanted = opts.fnNames && opts.fnNames.length > 0 ? new Set(opts.fnNames) : void 0;
|
|
2019
2244
|
const result = {
|
|
2020
2245
|
sidecars: [],
|
|
@@ -2033,8 +2258,8 @@ function emitDerivedFromRoot(rootFile, opts) {
|
|
|
2033
2258
|
}
|
|
2034
2259
|
for (const [sidecarPath, rows0] of bySidecar) {
|
|
2035
2260
|
const targetFile = rows0[0].file;
|
|
2036
|
-
const targetSidecarDir =
|
|
2037
|
-
const prevSrc =
|
|
2261
|
+
const targetSidecarDir = dirname5(sidecarPath);
|
|
2262
|
+
const prevSrc = existsSync5(sidecarPath) ? readFileSync5(sidecarPath, "utf-8") : "";
|
|
2038
2263
|
const rows = opts.refreshExistingOnly ? rows0.filter((r) => findGeneratedSectionText(prevSrc, r.fn) !== void 0) : rows0;
|
|
2039
2264
|
if (rows.length === 0) continue;
|
|
2040
2265
|
const handwritten = handwrittenNames(prevSrc);
|
|
@@ -2053,7 +2278,7 @@ function emitDerivedFromRoot(rootFile, opts) {
|
|
|
2053
2278
|
issues.push({
|
|
2054
2279
|
code: "nudo:interface-name-clash",
|
|
2055
2280
|
severity: "error",
|
|
2056
|
-
message: `sidecar already has a handwritten binding '${row.fn}' (${
|
|
2281
|
+
message: `sidecar already has a handwritten binding '${row.fn}' (${relative5(process.cwd(), sidecarPath) || sidecarPath}); handwritten wins \u2014 skipping emit`
|
|
2057
2282
|
});
|
|
2058
2283
|
anySkip = "name-clash";
|
|
2059
2284
|
continue;
|
|
@@ -2081,7 +2306,7 @@ function emitDerivedFromRoot(rootFile, opts) {
|
|
|
2081
2306
|
continue;
|
|
2082
2307
|
}
|
|
2083
2308
|
for (const n of body.usedNames) taken.add(n);
|
|
2084
|
-
const srcRel =
|
|
2309
|
+
const srcRel = relative5(targetSidecarDir, targetFile) || basename4(targetFile);
|
|
2085
2310
|
const section = [
|
|
2086
2311
|
DERIVED_HEADER,
|
|
2087
2312
|
`// source: ${srcRel}:${row.fn}`,
|
|
@@ -2131,7 +2356,7 @@ function emitDerivedFromRoot(rootFile, opts) {
|
|
|
2131
2356
|
issues.push({
|
|
2132
2357
|
code: "nudo:interface-not-projectable",
|
|
2133
2358
|
severity: "error",
|
|
2134
|
-
message: `assembled sidecar failed round-trip (${
|
|
2359
|
+
message: `assembled sidecar failed round-trip (${relative5(process.cwd(), sidecarPath) || sidecarPath}); refusing to write`
|
|
2135
2360
|
});
|
|
2136
2361
|
anySkip = "not-projectable";
|
|
2137
2362
|
result.sidecars.push({
|
|
@@ -2146,15 +2371,15 @@ function emitDerivedFromRoot(rootFile, opts) {
|
|
|
2146
2371
|
continue;
|
|
2147
2372
|
}
|
|
2148
2373
|
const changed = finalContent !== prevSrc;
|
|
2149
|
-
const diff = changed ? unifiedDiff(prevSrc, finalContent,
|
|
2374
|
+
const diff = changed ? unifiedDiff(prevSrc, finalContent, relative5(process.cwd(), sidecarPath) || sidecarPath) : void 0;
|
|
2150
2375
|
if (changed && !opts.dryRun) {
|
|
2151
2376
|
const tmp = `${sidecarPath}.tmp-${process.pid}-${Math.random().toString(16).slice(2, 10)}`;
|
|
2152
2377
|
try {
|
|
2153
|
-
|
|
2378
|
+
writeFileSync3(tmp, finalContent, "utf-8");
|
|
2154
2379
|
renameSync2(tmp, sidecarPath);
|
|
2155
2380
|
} catch (e) {
|
|
2156
2381
|
try {
|
|
2157
|
-
if (
|
|
2382
|
+
if (existsSync5(tmp)) unlinkSync2(tmp);
|
|
2158
2383
|
} catch {
|
|
2159
2384
|
}
|
|
2160
2385
|
throw e;
|
|
@@ -2305,115 +2530,1218 @@ function derivedRoundTrips(text, fn, fromFile, loadModule) {
|
|
|
2305
2530
|
}
|
|
2306
2531
|
}
|
|
2307
2532
|
|
|
2308
|
-
// src/
|
|
2309
|
-
import {
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
let
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2533
|
+
// src/emit/dts-generator.ts
|
|
2534
|
+
import { joinAbs, litValue, abs as makeAbs, collectAbsFreeVars } from "@nudojs/core";
|
|
2535
|
+
import { isTemplateLike, templatePartsOf } from "@nudojs/core/internal";
|
|
2536
|
+
function tsTypeParamName(id) {
|
|
2537
|
+
let n = id.replace(/[^A-Za-z0-9_$]/g, "_");
|
|
2538
|
+
if (!/^[A-Za-z_$]/.test(n)) n = `T_${n}`;
|
|
2539
|
+
if (n.length === 0) n = "T";
|
|
2540
|
+
return n;
|
|
2541
|
+
}
|
|
2542
|
+
function wrapComplexAbs(a, typeVars) {
|
|
2543
|
+
const ts = absToTSType(a, typeVars);
|
|
2544
|
+
if (a.shape.k === "sum" || a.shape.k === "fn") return `(${ts})`;
|
|
2545
|
+
return ts;
|
|
2546
|
+
}
|
|
2547
|
+
function wrapUnionMember(a, typeVars) {
|
|
2548
|
+
const ts = absToTSType(a, typeVars);
|
|
2549
|
+
if (a.shape.k === "fn") return `(${ts})`;
|
|
2550
|
+
return ts;
|
|
2551
|
+
}
|
|
2552
|
+
var TS_PARAM_RESERVED = /* @__PURE__ */ new Set([
|
|
2553
|
+
"break",
|
|
2554
|
+
"case",
|
|
2555
|
+
"catch",
|
|
2556
|
+
"class",
|
|
2557
|
+
"const",
|
|
2558
|
+
"continue",
|
|
2559
|
+
"debugger",
|
|
2560
|
+
"default",
|
|
2561
|
+
"delete",
|
|
2562
|
+
"do",
|
|
2563
|
+
"else",
|
|
2564
|
+
"enum",
|
|
2565
|
+
"export",
|
|
2566
|
+
"extends",
|
|
2567
|
+
"false",
|
|
2568
|
+
"finally",
|
|
2569
|
+
"for",
|
|
2570
|
+
"function",
|
|
2571
|
+
"if",
|
|
2572
|
+
"import",
|
|
2573
|
+
"in",
|
|
2574
|
+
"instanceof",
|
|
2575
|
+
"new",
|
|
2576
|
+
"null",
|
|
2577
|
+
"return",
|
|
2578
|
+
"super",
|
|
2579
|
+
"switch",
|
|
2580
|
+
"this",
|
|
2581
|
+
"throw",
|
|
2582
|
+
"true",
|
|
2583
|
+
"try",
|
|
2584
|
+
"typeof",
|
|
2585
|
+
"var",
|
|
2586
|
+
"void",
|
|
2587
|
+
"while",
|
|
2588
|
+
"with",
|
|
2589
|
+
"yield",
|
|
2590
|
+
"let",
|
|
2591
|
+
"static",
|
|
2592
|
+
"await",
|
|
2593
|
+
"implements",
|
|
2594
|
+
"interface",
|
|
2595
|
+
"package",
|
|
2596
|
+
"private",
|
|
2597
|
+
"protected",
|
|
2598
|
+
"public",
|
|
2599
|
+
"arguments",
|
|
2600
|
+
"eval",
|
|
2601
|
+
"constructor"
|
|
2602
|
+
]);
|
|
2603
|
+
function isTsIdent(name) {
|
|
2604
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) && !TS_PARAM_RESERVED.has(name);
|
|
2605
|
+
}
|
|
2606
|
+
function sanitizeParamName(name, index) {
|
|
2607
|
+
if (name.startsWith("...")) {
|
|
2608
|
+
const rest = name.slice(3);
|
|
2609
|
+
if (isTsIdent(rest)) return name;
|
|
2610
|
+
return `...arg${index}`;
|
|
2611
|
+
}
|
|
2612
|
+
if (isTsIdent(name)) return name;
|
|
2613
|
+
return `arg${index}`;
|
|
2614
|
+
}
|
|
2615
|
+
function formatPropKey(k) {
|
|
2616
|
+
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(k)) return k;
|
|
2617
|
+
if (/^\d+$/.test(k)) return k;
|
|
2618
|
+
return JSON.stringify(k);
|
|
2619
|
+
}
|
|
2620
|
+
function absToTSType(a, typeVars) {
|
|
2621
|
+
if (a.shape.k === "any" && a.term?.op === "var" && typeVars) {
|
|
2622
|
+
const mapped = typeVars.get(a.term.id);
|
|
2623
|
+
if (mapped) return mapped;
|
|
2624
|
+
}
|
|
2625
|
+
if (a.shape.k === "arr" && a.shape.element.shape.k === "any" && a.shape.element.term?.op === "var" && typeVars) {
|
|
2626
|
+
const mapped = typeVars.get(a.shape.element.term.id);
|
|
2627
|
+
if (mapped) return `${mapped}[]`;
|
|
2628
|
+
}
|
|
2629
|
+
if (a.term?.op === "lit") {
|
|
2630
|
+
const v = a.term.value;
|
|
2631
|
+
if (v === null) return "null";
|
|
2632
|
+
if (v === void 0) return "undefined";
|
|
2633
|
+
if (a.shape.k === "prim") {
|
|
2634
|
+
if (typeof v === "string") return JSON.stringify(v);
|
|
2635
|
+
if (typeof v === "boolean") return String(v);
|
|
2636
|
+
if (typeof v === "number") return String(v);
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
if (a.shape.k === "prim" && a.shape.type === "string" && isTemplateLike(a)) {
|
|
2640
|
+
const parts = templatePartsOf(a);
|
|
2641
|
+
const inner = parts.map((p) => {
|
|
2642
|
+
const lv = litValue(p);
|
|
2643
|
+
if (typeof lv === "string") return lv;
|
|
2644
|
+
return `\${${absToTSType(p, typeVars)}}`;
|
|
2645
|
+
}).join("");
|
|
2646
|
+
return `\`${inner}\``;
|
|
2647
|
+
}
|
|
2648
|
+
switch (a.shape.k) {
|
|
2649
|
+
case "never":
|
|
2650
|
+
return "never";
|
|
2651
|
+
case "unknown":
|
|
2652
|
+
case "any":
|
|
2653
|
+
return "unknown";
|
|
2654
|
+
case "prim":
|
|
2655
|
+
return a.shape.type;
|
|
2656
|
+
case "obj": {
|
|
2657
|
+
const entries = Object.entries(a.shape.slots).map(([k, slot]) => {
|
|
2658
|
+
const inner = absToTSType(slot.value, typeVars);
|
|
2659
|
+
if (slot.optional) {
|
|
2660
|
+
const t = wrapComplexAbs(slot.value, typeVars);
|
|
2661
|
+
return `${formatPropKey(k)}: ${t} | undefined`;
|
|
2662
|
+
}
|
|
2663
|
+
return `${formatPropKey(k)}: ${inner}`;
|
|
2664
|
+
});
|
|
2665
|
+
if (entries.length === 0) return "{}";
|
|
2666
|
+
return `{ ${entries.join("; ")} }`;
|
|
2667
|
+
}
|
|
2668
|
+
case "arr":
|
|
2669
|
+
return `${wrapComplexAbs(a.shape.element, typeVars)}[]`;
|
|
2670
|
+
case "tuple": {
|
|
2671
|
+
const parts = a.shape.elements.map((e) => absToTSType(e, typeVars));
|
|
2672
|
+
if (a.shape.rest) {
|
|
2673
|
+
const rest = a.shape.rest;
|
|
2674
|
+
const restTs = rest.shape.k === "arr" ? absToTSType(rest, typeVars) : `${absToTSType(rest, typeVars)}[]`;
|
|
2675
|
+
parts.push(`...${restTs}`);
|
|
2676
|
+
}
|
|
2677
|
+
return `[${parts.join(", ")}]`;
|
|
2678
|
+
}
|
|
2679
|
+
case "fn": {
|
|
2680
|
+
const paramTypes = a.shape.paramTypes;
|
|
2681
|
+
const params = a.shape.params.map((p, i) => {
|
|
2682
|
+
const isRest = p.startsWith("...");
|
|
2683
|
+
const name = sanitizeParamName(p, i);
|
|
2684
|
+
const pt = paramTypes?.[i];
|
|
2685
|
+
let typeStr;
|
|
2686
|
+
if (pt) typeStr = absToTSType(pt, typeVars);
|
|
2687
|
+
else if (isRest) typeStr = "unknown[]";
|
|
2688
|
+
else typeStr = "unknown";
|
|
2689
|
+
return `${name}: ${typeStr}`;
|
|
2690
|
+
}).join(", ");
|
|
2691
|
+
const ret = a.shape.returnType ? absToTSType(a.shape.returnType, typeVars) : "unknown";
|
|
2692
|
+
return `(${params}) => ${ret}`;
|
|
2693
|
+
}
|
|
2694
|
+
case "brand":
|
|
2695
|
+
return isTsIdent(a.shape.name) || /^[A-Z][A-Za-z0-9_$]*$/.test(a.shape.name) ? a.shape.name : "unknown";
|
|
2696
|
+
case "eff":
|
|
2697
|
+
if (a.shape.eff === "promise")
|
|
2698
|
+
return `Promise<${absToTSType(a.shape.inner, typeVars)}>`;
|
|
2699
|
+
return absToTSType(a.shape.inner, typeVars);
|
|
2700
|
+
case "sum": {
|
|
2701
|
+
const parts = a.shape.members.map((m) => wrapUnionMember(m, typeVars)).filter((p) => p !== "never");
|
|
2702
|
+
const uniq = [...new Set(parts)];
|
|
2703
|
+
if (uniq.length === 0) return "never";
|
|
2704
|
+
if (uniq.length === 1) return uniq[0];
|
|
2705
|
+
return uniq.join(" | ");
|
|
2706
|
+
}
|
|
2707
|
+
default:
|
|
2708
|
+
return "unknown";
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
function caseArgAbs(c, i) {
|
|
2712
|
+
return c.argAbs[i];
|
|
2713
|
+
}
|
|
2714
|
+
function caseResultAbs(c) {
|
|
2715
|
+
return c.abs;
|
|
2716
|
+
}
|
|
2717
|
+
function widenParamAbs(a) {
|
|
2718
|
+
const s = a.shape;
|
|
2719
|
+
switch (s.k) {
|
|
2720
|
+
case "prim":
|
|
2721
|
+
return makeAbs(s, void 0, void 0, "exact");
|
|
2722
|
+
case "tuple": {
|
|
2723
|
+
const widened = s.elements.map(widenParamAbs);
|
|
2724
|
+
const first = widened[0];
|
|
2725
|
+
if (first && widened.length > 0 && widened.every((el) => absToTSType(el) === absToTSType(first))) {
|
|
2726
|
+
return makeAbs({ k: "arr", element: first }, void 0, void 0, "exact");
|
|
2727
|
+
}
|
|
2728
|
+
return makeAbs({ k: "tuple", elements: widened }, void 0, void 0, "exact");
|
|
2729
|
+
}
|
|
2730
|
+
case "arr":
|
|
2731
|
+
return makeAbs({ k: "arr", element: widenParamAbs(s.element) }, void 0, void 0, "exact");
|
|
2732
|
+
case "obj": {
|
|
2733
|
+
const slots = {};
|
|
2734
|
+
for (const [k, slot] of Object.entries(s.slots)) {
|
|
2735
|
+
slots[k] = {
|
|
2736
|
+
value: widenParamAbs(slot.value),
|
|
2737
|
+
...slot.optional ? { optional: true } : {}
|
|
2738
|
+
};
|
|
2739
|
+
}
|
|
2740
|
+
return makeAbs({ k: "obj", slots }, void 0, void 0, "exact");
|
|
2741
|
+
}
|
|
2742
|
+
case "eff":
|
|
2743
|
+
return makeAbs(
|
|
2744
|
+
{ k: "eff", eff: s.eff, inner: widenParamAbs(s.inner) },
|
|
2745
|
+
void 0,
|
|
2746
|
+
void 0,
|
|
2747
|
+
"exact"
|
|
2748
|
+
);
|
|
2749
|
+
case "brand":
|
|
2750
|
+
return makeAbs(
|
|
2751
|
+
{ k: "brand", name: s.name, shape: widenParamAbs(s.shape) },
|
|
2752
|
+
void 0,
|
|
2753
|
+
void 0,
|
|
2754
|
+
"exact"
|
|
2755
|
+
);
|
|
2756
|
+
case "sum":
|
|
2757
|
+
return makeAbs(
|
|
2758
|
+
{ k: "sum", members: s.members.map(widenParamAbs) },
|
|
2759
|
+
void 0,
|
|
2760
|
+
void 0,
|
|
2761
|
+
"exact"
|
|
2762
|
+
);
|
|
2763
|
+
default:
|
|
2764
|
+
return a;
|
|
2765
|
+
}
|
|
2341
2766
|
}
|
|
2342
|
-
function
|
|
2343
|
-
if (
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2767
|
+
function widenTopLevelAbs(a) {
|
|
2768
|
+
if (a.shape.k === "sum") {
|
|
2769
|
+
return makeAbs(
|
|
2770
|
+
{ k: "sum", members: a.shape.members.map(widenTopLevelAbs) },
|
|
2771
|
+
void 0,
|
|
2772
|
+
void 0,
|
|
2773
|
+
a.conf
|
|
2774
|
+
);
|
|
2775
|
+
}
|
|
2776
|
+
return widenLiteralToPrimAbs(a);
|
|
2777
|
+
}
|
|
2778
|
+
function widenLiteralToPrimAbs(a) {
|
|
2779
|
+
if (a.term?.op === "lit" && a.term.value === null) return a;
|
|
2780
|
+
if (a.term?.op === "lit" && a.term.value === void 0) return a;
|
|
2781
|
+
if (a.term?.op === "lit" && a.shape.k === "prim") {
|
|
2782
|
+
const v = a.term.value;
|
|
2783
|
+
if (typeof v === "number" || typeof v === "string" || typeof v === "boolean" || typeof v === "bigint") {
|
|
2784
|
+
return makeAbs(a.shape, void 0, void 0, a.conf);
|
|
2348
2785
|
}
|
|
2349
|
-
} else if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
|
|
2350
|
-
declaredNames(stmt.declaration, out);
|
|
2351
2786
|
}
|
|
2787
|
+
return a;
|
|
2352
2788
|
}
|
|
2353
|
-
function
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2789
|
+
function paramTypeFromAbs(members) {
|
|
2790
|
+
if (members.length === 0) return "unknown";
|
|
2791
|
+
let joined;
|
|
2792
|
+
try {
|
|
2793
|
+
joined = members.reduce((a, b) => joinAbs(a, b));
|
|
2794
|
+
} catch {
|
|
2795
|
+
joined = members[0];
|
|
2796
|
+
}
|
|
2797
|
+
return absToTSType(widenParamAbs(joined));
|
|
2798
|
+
}
|
|
2799
|
+
function returnAbsOf(fn) {
|
|
2800
|
+
if (fn.combinedAbs) return fn.combinedAbs;
|
|
2801
|
+
const results = fn.cases.map(caseResultAbs);
|
|
2802
|
+
if (results.length > 0) {
|
|
2803
|
+
try {
|
|
2804
|
+
return results.reduce((a, b) => joinAbs(a, b));
|
|
2805
|
+
} catch {
|
|
2806
|
+
}
|
|
2807
|
+
return results[0];
|
|
2808
|
+
}
|
|
2809
|
+
return void 0;
|
|
2810
|
+
}
|
|
2811
|
+
function getParamName(fn, index) {
|
|
2812
|
+
if (fn.paramNames && fn.paramNames[index]) {
|
|
2813
|
+
return fn.paramNames[index];
|
|
2814
|
+
}
|
|
2815
|
+
return `arg${index}`;
|
|
2816
|
+
}
|
|
2817
|
+
function computeMainSignature(fn) {
|
|
2818
|
+
const arity = Math.max(...fn.cases.map((c) => c.argAbs.length));
|
|
2819
|
+
const minArity = Math.min(...fn.cases.map((c) => c.argAbs.length));
|
|
2820
|
+
const params = [];
|
|
2821
|
+
const paramNames = [];
|
|
2822
|
+
const paramTypes = [];
|
|
2823
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
2824
|
+
for (let i = 0; i < arity; i++) {
|
|
2825
|
+
const members = [];
|
|
2826
|
+
for (const c of fn.cases) {
|
|
2827
|
+
if (i >= c.argAbs.length) continue;
|
|
2828
|
+
const a = caseArgAbs(c, i);
|
|
2829
|
+
if (a) members.push(a);
|
|
2830
|
+
}
|
|
2831
|
+
const typeStr = paramTypeFromAbs(members);
|
|
2832
|
+
let name = getParamName(fn, i);
|
|
2833
|
+
const isRest = name.startsWith("...");
|
|
2834
|
+
const bare = isRest ? name.slice(3) : name;
|
|
2835
|
+
if (!isTsIdent(bare)) {
|
|
2836
|
+
name = isRest ? `...arg${i}` : `arg${i}`;
|
|
2837
|
+
}
|
|
2838
|
+
if (usedNames.has(name)) {
|
|
2839
|
+
let n = 2;
|
|
2840
|
+
while (usedNames.has(`${name}${n}`)) n++;
|
|
2841
|
+
name = `${name}${n}`;
|
|
2842
|
+
}
|
|
2843
|
+
usedNames.add(name);
|
|
2844
|
+
const optional = i >= minArity && !isRest;
|
|
2845
|
+
params.push(optional ? `${name}?: ${typeStr}` : `${name}: ${typeStr}`);
|
|
2846
|
+
paramNames.push(name);
|
|
2847
|
+
paramTypes.push(typeStr);
|
|
2848
|
+
}
|
|
2849
|
+
const retAbs = returnAbsOf(fn);
|
|
2850
|
+
const returnType = retAbs ? absToTSType(widenTopLevelAbs(retAbs)) : "unknown";
|
|
2851
|
+
return { params, paramNames, paramTypes, returnType };
|
|
2852
|
+
}
|
|
2853
|
+
function generateJSDoc(fn, sig) {
|
|
2854
|
+
if (fn.cases.length === 0) return "";
|
|
2855
|
+
const lines = ["/**"];
|
|
2856
|
+
for (const c of fn.cases) {
|
|
2857
|
+
const preciseDiffers = c.argAbs.length !== sig.paramTypes.length || c.argAbs.some((a, i) => absToTSType(a) !== sig.paramTypes[i]) || absToTSType(c.abs) !== sig.returnType;
|
|
2858
|
+
if (!preciseDiffers) continue;
|
|
2859
|
+
const argsStr = c.argAbs.map((a) => absToTSType(a)).join(", ");
|
|
2860
|
+
lines.push(` * Case: ${c.name} (${argsStr}) => ${absToTSType(c.abs)}`);
|
|
2861
|
+
}
|
|
2862
|
+
for (let i = 0; i < sig.paramTypes.length; i++) {
|
|
2863
|
+
lines.push(` * @param ${sig.paramNames[i]} - ${sig.paramTypes[i]}`);
|
|
2864
|
+
}
|
|
2865
|
+
lines.push(` * @returns ${sig.returnType}`);
|
|
2866
|
+
lines.push(" */");
|
|
2867
|
+
return lines.join("\n");
|
|
2868
|
+
}
|
|
2869
|
+
function computeHofSignature(fn) {
|
|
2870
|
+
const hof = fn.hof;
|
|
2871
|
+
if (!hof) return void 0;
|
|
2872
|
+
const byParam = /* @__PURE__ */ new Map();
|
|
2873
|
+
for (const s of hof.entryShapes ?? []) byParam.set(s.param, s.abs);
|
|
2874
|
+
for (const r of hof.fnRels ?? []) byParam.set(r.param, r.abs);
|
|
2875
|
+
if (byParam.size === 0 && !hof.symbolic) return void 0;
|
|
2876
|
+
if (fn.cases.length > 0) {
|
|
2877
|
+
for (const [param] of byParam) {
|
|
2878
|
+
const idx = fn.paramNames.indexOf(param);
|
|
2879
|
+
if (idx < 0) continue;
|
|
2880
|
+
const caseType = paramTypeFromAbs(
|
|
2881
|
+
fn.cases.map((c) => c.argAbs[idx]).filter((a) => !!a)
|
|
2882
|
+
);
|
|
2883
|
+
if (caseType !== "unknown" && caseType !== "unknown[]") {
|
|
2884
|
+
return void 0;
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
const arity = Math.max(
|
|
2889
|
+
fn.paramNames.length,
|
|
2890
|
+
...[...byParam.keys()].map((p) => fn.paramNames.indexOf(p) + 1),
|
|
2891
|
+
0
|
|
2892
|
+
);
|
|
2893
|
+
if (arity === 0 && !hof.symbolic) return void 0;
|
|
2894
|
+
const free = /* @__PURE__ */ new Set();
|
|
2895
|
+
for (const a of byParam.values()) {
|
|
2896
|
+
for (const id of collectAbsFreeVars(a)) free.add(id);
|
|
2897
|
+
}
|
|
2898
|
+
const retAbs = hof.symbolic ?? fn.combinedAbs;
|
|
2899
|
+
if (retAbs) {
|
|
2900
|
+
for (const id of collectAbsFreeVars(retAbs)) free.add(id);
|
|
2901
|
+
}
|
|
2902
|
+
if (free.size === 0) return void 0;
|
|
2903
|
+
const typeVars = /* @__PURE__ */ new Map();
|
|
2904
|
+
const used = /* @__PURE__ */ new Set();
|
|
2905
|
+
const typeParams = [];
|
|
2906
|
+
for (const id of [...free].sort()) {
|
|
2907
|
+
let n = tsTypeParamName(id);
|
|
2908
|
+
if (used.has(n) || TS_PARAM_RESERVED.has(n)) {
|
|
2909
|
+
let i = 2;
|
|
2910
|
+
while (used.has(`${n}${i}`)) i++;
|
|
2911
|
+
n = `${n}${i}`;
|
|
2912
|
+
}
|
|
2913
|
+
used.add(n);
|
|
2914
|
+
typeVars.set(id, n);
|
|
2915
|
+
typeParams.push(n);
|
|
2916
|
+
}
|
|
2917
|
+
const params = [];
|
|
2918
|
+
const paramNames = [];
|
|
2919
|
+
const paramTypes = [];
|
|
2920
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
2921
|
+
for (let i = 0; i < arity; i++) {
|
|
2922
|
+
const rawName = getParamName(fn, i);
|
|
2923
|
+
const isRest = rawName.startsWith("...");
|
|
2924
|
+
let name = sanitizeParamName(rawName, i);
|
|
2925
|
+
if (usedNames.has(name)) {
|
|
2926
|
+
let n = 2;
|
|
2927
|
+
while (usedNames.has(`${name}${n}`)) n++;
|
|
2928
|
+
name = isRest && name.startsWith("...") ? `...${name.slice(3)}${n}` : `${name}${n}`;
|
|
2929
|
+
}
|
|
2930
|
+
usedNames.add(name);
|
|
2931
|
+
const promoted = byParam.get(fn.paramNames[i] ?? rawName) ?? byParam.get(rawName);
|
|
2932
|
+
const typeStr = promoted ? absToTSType(promoted, typeVars) : isRest ? "unknown[]" : "unknown";
|
|
2933
|
+
params.push(`${name}: ${typeStr}`);
|
|
2934
|
+
paramNames.push(name);
|
|
2935
|
+
paramTypes.push(typeStr);
|
|
2936
|
+
}
|
|
2937
|
+
const returnType = retAbs ? absToTSType(retAbs, typeVars) : "unknown";
|
|
2938
|
+
return { typeParams, params, paramNames, paramTypes, returnType };
|
|
2939
|
+
}
|
|
2940
|
+
function generateFunctionDtsLines(fn) {
|
|
2941
|
+
let emitFn = fn;
|
|
2942
|
+
if (fn.noDeclaration) {
|
|
2943
|
+
const m = /^([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)$/.exec(fn.name);
|
|
2944
|
+
if (!m) return [];
|
|
2945
|
+
emitFn = { ...fn, name: `${m[1]}_${m[2]}`, noDeclaration: false };
|
|
2946
|
+
}
|
|
2947
|
+
fn = emitFn;
|
|
2948
|
+
const hofSig = computeHofSignature(fn);
|
|
2949
|
+
if (hofSig) {
|
|
2950
|
+
const lines2 = [];
|
|
2951
|
+
if (fn.cases.length > 0) {
|
|
2952
|
+
const jsdoc2 = generateJSDoc(fn, {
|
|
2953
|
+
params: hofSig.params,
|
|
2954
|
+
paramNames: hofSig.paramNames,
|
|
2955
|
+
paramTypes: hofSig.paramTypes,
|
|
2956
|
+
returnType: hofSig.returnType
|
|
2957
|
+
});
|
|
2958
|
+
if (jsdoc2) lines2.push(jsdoc2);
|
|
2959
|
+
}
|
|
2960
|
+
const tparams = hofSig.typeParams.length > 0 ? `<${hofSig.typeParams.join(", ")}>` : "";
|
|
2961
|
+
lines2.push(
|
|
2962
|
+
`export declare function ${fn.name}${tparams}(${hofSig.params.join(", ")}): ${hofSig.returnType};`
|
|
2963
|
+
);
|
|
2964
|
+
return lines2;
|
|
2965
|
+
}
|
|
2966
|
+
if (fn.cases.length === 0) {
|
|
2967
|
+
const retAbs = fn.combinedAbs;
|
|
2968
|
+
if (retAbs) {
|
|
2969
|
+
return [
|
|
2970
|
+
`export declare function ${fn.name}(...args: unknown[]): ${absToTSType(retAbs)};`
|
|
2971
|
+
];
|
|
2972
|
+
}
|
|
2973
|
+
return [];
|
|
2974
|
+
}
|
|
2975
|
+
const sig = computeMainSignature(fn);
|
|
2976
|
+
const jsdoc = generateJSDoc(fn, sig);
|
|
2977
|
+
const lines = [];
|
|
2978
|
+
if (jsdoc) lines.push(jsdoc);
|
|
2979
|
+
lines.push(`export declare function ${fn.name}(${sig.params.join(", ")}): ${sig.returnType};`);
|
|
2980
|
+
return lines;
|
|
2981
|
+
}
|
|
2982
|
+
function generateDts(result) {
|
|
2983
|
+
const lines = [];
|
|
2984
|
+
for (const fn of result.functions) {
|
|
2985
|
+
lines.push(...generateFunctionDtsLines(fn));
|
|
2986
|
+
}
|
|
2987
|
+
return lines.join("\n") + "\n";
|
|
2988
|
+
}
|
|
2989
|
+
|
|
2990
|
+
// src/emit/schema-generator.ts
|
|
2991
|
+
import { absToConstraint, isIntFlag as isIntFlag2, predToString } from "@nudojs/core";
|
|
2992
|
+
function predLeaves(p) {
|
|
2993
|
+
if (!p || p.op === "true") return [];
|
|
2994
|
+
if (p.op === "and") {
|
|
2995
|
+
const out = [];
|
|
2996
|
+
for (const x of p.args) {
|
|
2997
|
+
const sub = predLeaves(x);
|
|
2998
|
+
if (sub === "unexpressible") return "unexpressible";
|
|
2999
|
+
out.push(...sub);
|
|
3000
|
+
}
|
|
3001
|
+
return out;
|
|
3002
|
+
}
|
|
3003
|
+
if (p.op === "or" || p.op === "not" || p.op === "false") return "unexpressible";
|
|
3004
|
+
return [p];
|
|
3005
|
+
}
|
|
3006
|
+
function isSelfVar(t) {
|
|
3007
|
+
return !!t && t.op === "var";
|
|
3008
|
+
}
|
|
3009
|
+
function litOf(t) {
|
|
3010
|
+
return t && t.op === "lit" ? t.value : void 0;
|
|
3011
|
+
}
|
|
3012
|
+
function eqLitValue(p) {
|
|
3013
|
+
if (p.op !== "eq") return "unanchored";
|
|
3014
|
+
const aLit = litOf(p.a);
|
|
3015
|
+
const bLit = litOf(p.b);
|
|
3016
|
+
if (aLit !== void 0 && (isSelfVar(p.b) || p.b.op === "app")) return aLit;
|
|
3017
|
+
if (bLit !== void 0 && (isSelfVar(p.a) || p.a.op === "app")) return bLit;
|
|
3018
|
+
if (aLit !== void 0 && bLit !== void 0) return aLit === bLit ? aLit : "unanchored";
|
|
3019
|
+
return "unanchored";
|
|
3020
|
+
}
|
|
3021
|
+
function isIntModOne(p) {
|
|
3022
|
+
if (p.op !== "eq") return false;
|
|
3023
|
+
const zero = (t) => !!t && t.op === "lit" && t.value === 0;
|
|
3024
|
+
const isModOne = (t) => !!t && t.op === "app" && t.fn === "%" && t.args.length === 2 && t.args[1]?.op === "lit" && t.args[1].value === 1 && (isSelfVar(t.args[0]) || t.args[0].op === "app");
|
|
3025
|
+
return isModOne(p.a) && zero(p.b) || isModOne(p.b) && zero(p.a);
|
|
3026
|
+
}
|
|
3027
|
+
function numericBound(p, allowSelfVar) {
|
|
3028
|
+
if (p.op !== "gt" && p.op !== "ge" && p.op !== "lt" && p.op !== "le") return "drop";
|
|
3029
|
+
const n = litOf(p.b);
|
|
3030
|
+
if (typeof n !== "number") return "drop";
|
|
3031
|
+
if (p.a.op === "app" && p.a.fn === "length") return "skip";
|
|
3032
|
+
if (allowSelfVar && isSelfVar(p.a)) return { op: p.op, n };
|
|
3033
|
+
if (p.a.op === "app" && (p.a.fn === "get" || p.a.fn === "length")) return "skip";
|
|
3034
|
+
return "drop";
|
|
3035
|
+
}
|
|
3036
|
+
function lengthBound(p) {
|
|
3037
|
+
if (p.op !== "gt" && p.op !== "ge" && p.op !== "lt" && p.op !== "le") return void 0;
|
|
3038
|
+
if (p.a.op !== "app" || p.a.fn !== "length") return void 0;
|
|
3039
|
+
const n = litOf(p.b);
|
|
3040
|
+
if (typeof n !== "number") return void 0;
|
|
3041
|
+
if (p.op === "ge") return { dir: "min", n: Math.ceil(n) };
|
|
3042
|
+
if (p.op === "gt") return { dir: "min", n: Math.floor(n) + 1 };
|
|
3043
|
+
if (p.op === "le") return { dir: "max", n: Math.floor(n) };
|
|
3044
|
+
return { dir: "max", n: Math.ceil(n) - 1 };
|
|
3045
|
+
}
|
|
3046
|
+
function refinementsFromPreds(preds, opts) {
|
|
3047
|
+
const refinements = [];
|
|
3048
|
+
const dropped = [];
|
|
3049
|
+
let eqLit;
|
|
3050
|
+
for (const p of preds) {
|
|
3051
|
+
if (p.op === "typeof") continue;
|
|
3052
|
+
if (p.op === "eq") {
|
|
3053
|
+
if (opts.kind === "number" && isIntModOne(p)) {
|
|
3054
|
+
if (!refinements.some((r) => r.kind === "int")) refinements.push({ kind: "int" });
|
|
3055
|
+
continue;
|
|
3056
|
+
}
|
|
3057
|
+
const v = eqLitValue(p);
|
|
3058
|
+
if (v === "unanchored") {
|
|
3059
|
+
dropped.push(`pred not projected: ${predToString(p)}`);
|
|
3060
|
+
continue;
|
|
3061
|
+
}
|
|
3062
|
+
if (typeof v === "number" && Number.isNaN(v)) {
|
|
3063
|
+
dropped.push(`pred not projected (NaN): ${predToString(p)}`);
|
|
3064
|
+
continue;
|
|
3065
|
+
}
|
|
3066
|
+
if (eqLit !== void 0 && eqLit !== v) {
|
|
3067
|
+
dropped.push(`conflicting eq preds: ${predToString(p)}`);
|
|
3068
|
+
continue;
|
|
3069
|
+
}
|
|
3070
|
+
eqLit = v;
|
|
2373
3071
|
continue;
|
|
2374
3072
|
}
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
3073
|
+
if (p.op === "gt" || p.op === "ge" || p.op === "lt" || p.op === "le") {
|
|
3074
|
+
if (opts.kind === "string") {
|
|
3075
|
+
const lb = lengthBound(p);
|
|
3076
|
+
if (lb) {
|
|
3077
|
+
refinements.push(lb.dir === "min" ? { kind: "strMin", n: lb.n } : { kind: "strMax", n: lb.n });
|
|
3078
|
+
continue;
|
|
3079
|
+
}
|
|
3080
|
+
dropped.push(`pred not projected: ${predToString(p)}`);
|
|
3081
|
+
continue;
|
|
3082
|
+
}
|
|
3083
|
+
const b = numericBound(p, opts.kind === "number" || opts.kind === "other");
|
|
3084
|
+
if (b === "skip") continue;
|
|
3085
|
+
if (b === "drop") {
|
|
3086
|
+
dropped.push(`pred not projected: ${predToString(p)}`);
|
|
3087
|
+
continue;
|
|
3088
|
+
}
|
|
3089
|
+
if (opts.kind === "number" || opts.kind === "other") {
|
|
3090
|
+
refinements.push({ kind: "numBound", op: b.op, n: b.n });
|
|
3091
|
+
continue;
|
|
3092
|
+
}
|
|
3093
|
+
dropped.push(`pred not projected: ${predToString(p)}`);
|
|
3094
|
+
continue;
|
|
3095
|
+
}
|
|
3096
|
+
dropped.push(`pred not projected: ${predToString(p)}`);
|
|
2379
3097
|
}
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
}
|
|
3098
|
+
return { refinements, ...eqLit !== void 0 ? { eqLit } : {}, dropped };
|
|
3099
|
+
}
|
|
3100
|
+
function constraintToSchemaNode(c) {
|
|
3101
|
+
if (c.fn) return { k: "fn" };
|
|
3102
|
+
if (c.members) {
|
|
3103
|
+
return { k: "union", members: c.members.map(constraintToSchemaNode) };
|
|
2386
3104
|
}
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
3105
|
+
if (c.fields) {
|
|
3106
|
+
return {
|
|
3107
|
+
k: "obj",
|
|
3108
|
+
slots: Object.entries(c.fields).map(([key, field]) => ({
|
|
3109
|
+
key,
|
|
3110
|
+
node: constraintToSchemaNode(field.constraint),
|
|
3111
|
+
...field.optional || field.constraint.isOptional ? { optional: true } : {}
|
|
3112
|
+
}))
|
|
3113
|
+
};
|
|
3114
|
+
}
|
|
3115
|
+
if (c.element) {
|
|
3116
|
+
return { k: "arr", element: constraintToSchemaNode(c.element) };
|
|
3117
|
+
}
|
|
3118
|
+
const preds = c.preds ?? [];
|
|
3119
|
+
const kind = c.prim === "number" ? "number" : c.prim === "string" ? "string" : c.prim === "boolean" ? "boolean" : c.prim ? "other" : "other";
|
|
3120
|
+
const { refinements, eqLit, dropped } = refinementsFromPreds(preds, { kind });
|
|
3121
|
+
if (eqLit !== void 0 && (kind === "number" || kind === "string" || kind === "boolean" || !c.prim)) {
|
|
3122
|
+
return { k: "lit", value: eqLit };
|
|
3123
|
+
}
|
|
3124
|
+
if (isIntFlag2(c) && !refinements.some((r) => r.kind === "int")) {
|
|
3125
|
+
refinements.push({ kind: "int" });
|
|
3126
|
+
}
|
|
3127
|
+
void dropped;
|
|
3128
|
+
if (!c.prim) {
|
|
3129
|
+
if (Object.keys(c.fields ?? {}).length === 0 && !c.element && !c.members) {
|
|
3130
|
+
return { k: "unknown" };
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
const type = c.prim === "number" || c.prim === "string" || c.prim === "boolean" || c.prim === "bigint" || c.prim === "symbol" ? c.prim : "unknown";
|
|
3134
|
+
if (type === "unknown") return { k: "unknown" };
|
|
3135
|
+
return { k: "prim", type, refinements };
|
|
3136
|
+
}
|
|
3137
|
+
function constraintDropped(c, prefix = "") {
|
|
3138
|
+
const out = [];
|
|
3139
|
+
const visit = (n, path) => {
|
|
3140
|
+
if (n.fields) {
|
|
3141
|
+
for (const [k, f] of Object.entries(n.fields)) visit(f.constraint, path ? `${path}.${k}` : k);
|
|
3142
|
+
return;
|
|
3143
|
+
}
|
|
3144
|
+
if (n.members) {
|
|
3145
|
+
n.members.forEach((m, i) => visit(m, `${path}[${i}]`));
|
|
3146
|
+
return;
|
|
3147
|
+
}
|
|
3148
|
+
if (n.element) {
|
|
3149
|
+
visit(n.element, `${path}[]`);
|
|
3150
|
+
return;
|
|
3151
|
+
}
|
|
3152
|
+
if (n.fn) return;
|
|
3153
|
+
const kind = n.prim === "number" ? "number" : n.prim === "string" ? "string" : n.prim === "boolean" ? "boolean" : "other";
|
|
3154
|
+
const { eqLit, dropped } = refinementsFromPreds(n.preds ?? [], { kind });
|
|
3155
|
+
for (const d of dropped) out.push(path ? `${path}: ${d}` : d);
|
|
3156
|
+
if (eqLit === void 0 && n.preds?.some((p) => p.op === "eq") && !isIntFlag2(n)) {
|
|
3157
|
+
}
|
|
3158
|
+
};
|
|
3159
|
+
visit(c, prefix);
|
|
3160
|
+
return out;
|
|
3161
|
+
}
|
|
3162
|
+
function absToSchemaNode(a) {
|
|
3163
|
+
const dropped = [];
|
|
3164
|
+
if (a.term?.op === "lit") {
|
|
3165
|
+
const v = a.term.value;
|
|
3166
|
+
if (typeof v === "number" && Number.isNaN(v)) {
|
|
3167
|
+
dropped.push("lit NaN not projected");
|
|
3168
|
+
} else if (v === null || v === void 0 || typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
3169
|
+
return { node: { k: "lit", value: v }, dropped };
|
|
3170
|
+
} else if (typeof v === "bigint") {
|
|
3171
|
+
dropped.push(`lit bigint not projected: ${String(v)}`);
|
|
3172
|
+
return { node: { k: "unknown" }, dropped };
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
const conf = a.conf;
|
|
3176
|
+
const lowConf = conf !== "exact" && conf !== "path";
|
|
3177
|
+
const projected = absToConstraint(a);
|
|
3178
|
+
if (projected) {
|
|
3179
|
+
const node = constraintToSchemaNode(projected);
|
|
3180
|
+
dropped.push(...constraintDropped(projected));
|
|
3181
|
+
return { node, dropped };
|
|
3182
|
+
}
|
|
3183
|
+
if (lowConf) {
|
|
3184
|
+
dropped.push(`conf=${conf}: contract projection skipped; shape-only fallback`);
|
|
3185
|
+
}
|
|
3186
|
+
const s = a.shape;
|
|
3187
|
+
switch (s.k) {
|
|
3188
|
+
case "never":
|
|
3189
|
+
return { node: { k: "never" }, dropped };
|
|
3190
|
+
case "any":
|
|
3191
|
+
case "unknown":
|
|
3192
|
+
return { node: { k: "unknown" }, dropped };
|
|
3193
|
+
case "prim": {
|
|
3194
|
+
const kind = s.type === "number" ? "number" : s.type === "string" ? "string" : s.type === "boolean" ? "boolean" : "other";
|
|
3195
|
+
const leaves = predLeaves(a.pred);
|
|
3196
|
+
if (leaves === "unexpressible") {
|
|
3197
|
+
if (a.pred && a.pred.op !== "true") {
|
|
3198
|
+
dropped.push(`pred not projected: ${predToString(a.pred)}`);
|
|
3199
|
+
}
|
|
3200
|
+
return { node: { k: "prim", type: s.type, refinements: [] }, dropped };
|
|
3201
|
+
}
|
|
3202
|
+
const { refinements, eqLit, dropped: d } = refinementsFromPreds(leaves, { kind });
|
|
3203
|
+
dropped.push(...d);
|
|
3204
|
+
if (eqLit !== void 0 && !Number.isNaN(eqLit)) {
|
|
3205
|
+
return { node: { k: "lit", value: eqLit }, dropped };
|
|
3206
|
+
}
|
|
3207
|
+
return { node: { k: "prim", type: s.type, refinements }, dropped };
|
|
3208
|
+
}
|
|
3209
|
+
case "obj": {
|
|
3210
|
+
if (s.open || s.index) {
|
|
3211
|
+
dropped.push("open/index object not fully projected (known slots only)");
|
|
3212
|
+
}
|
|
3213
|
+
if (a.pred && a.pred.op !== "true") {
|
|
3214
|
+
dropped.push(`obj pred not projected: ${predToString(a.pred)}`);
|
|
3215
|
+
}
|
|
3216
|
+
const slots = [];
|
|
3217
|
+
for (const [key, slot] of Object.entries(s.slots)) {
|
|
3218
|
+
const sub = absToSchemaNode(slot.value);
|
|
3219
|
+
dropped.push(...sub.dropped.map((n) => `${key}: ${n}`));
|
|
3220
|
+
slots.push({ key, node: sub.node, ...slot.optional ? { optional: true } : {} });
|
|
3221
|
+
}
|
|
3222
|
+
return { node: { k: "obj", slots }, dropped };
|
|
3223
|
+
}
|
|
3224
|
+
case "arr": {
|
|
3225
|
+
if (a.pred && a.pred.op !== "true") {
|
|
3226
|
+
dropped.push(`arr pred not projected: ${predToString(a.pred)}`);
|
|
3227
|
+
}
|
|
3228
|
+
const sub = absToSchemaNode(s.element);
|
|
3229
|
+
dropped.push(...sub.dropped);
|
|
3230
|
+
return { node: { k: "arr", element: sub.node }, dropped };
|
|
3231
|
+
}
|
|
3232
|
+
case "tuple": {
|
|
3233
|
+
const elements = s.elements.map((e) => {
|
|
3234
|
+
const sub = absToSchemaNode(e);
|
|
3235
|
+
dropped.push(...sub.dropped);
|
|
3236
|
+
return sub.node;
|
|
3237
|
+
});
|
|
3238
|
+
return { node: { k: "tuple", elements }, dropped };
|
|
3239
|
+
}
|
|
3240
|
+
case "sum": {
|
|
3241
|
+
const members = s.members.map((m) => {
|
|
3242
|
+
const sub = absToSchemaNode(m);
|
|
3243
|
+
dropped.push(...sub.dropped);
|
|
3244
|
+
return sub.node;
|
|
3245
|
+
});
|
|
3246
|
+
return { node: { k: "union", members }, dropped };
|
|
3247
|
+
}
|
|
3248
|
+
case "fn":
|
|
3249
|
+
return { node: { k: "fn" }, dropped };
|
|
3250
|
+
case "eff": {
|
|
3251
|
+
const inner = absToSchemaNode(s.inner);
|
|
3252
|
+
dropped.push(...inner.dropped);
|
|
3253
|
+
return {
|
|
3254
|
+
node: s.eff === "promise" ? { k: "promise", inner: inner.node } : inner.node,
|
|
3255
|
+
dropped
|
|
3256
|
+
};
|
|
3257
|
+
}
|
|
3258
|
+
case "brand":
|
|
3259
|
+
return { node: { k: "brand", name: s.name }, dropped };
|
|
3260
|
+
default:
|
|
3261
|
+
return { node: { k: "unknown" }, dropped };
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
function zodApplyRefinements(base, refinements) {
|
|
3265
|
+
let out = base;
|
|
3266
|
+
if (refinements.some((r) => r.kind === "int")) out += ".int()";
|
|
3267
|
+
for (const r of refinements) {
|
|
3268
|
+
switch (r.kind) {
|
|
3269
|
+
case "numBound":
|
|
3270
|
+
if (r.op === "gt") out += `.gt(${r.n})`;
|
|
3271
|
+
else if (r.op === "ge") out += `.gte(${r.n})`;
|
|
3272
|
+
else if (r.op === "lt") out += `.lt(${r.n})`;
|
|
3273
|
+
else out += `.lte(${r.n})`;
|
|
3274
|
+
break;
|
|
3275
|
+
case "strMin":
|
|
3276
|
+
out += `.min(${r.n})`;
|
|
3277
|
+
break;
|
|
3278
|
+
case "strMax":
|
|
3279
|
+
out += `.max(${r.n})`;
|
|
3280
|
+
break;
|
|
3281
|
+
default:
|
|
3282
|
+
break;
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
return out;
|
|
3286
|
+
}
|
|
3287
|
+
function schemaNodeToZod(node) {
|
|
3288
|
+
switch (node.k) {
|
|
3289
|
+
case "lit": {
|
|
3290
|
+
const v = node.value;
|
|
3291
|
+
if (v === null) return "z.null()";
|
|
3292
|
+
if (v === void 0) return "z.undefined()";
|
|
3293
|
+
if (typeof v === "string") return `z.literal(${JSON.stringify(v)})`;
|
|
3294
|
+
if (typeof v === "boolean") return `z.literal(${v})`;
|
|
3295
|
+
if (typeof v === "number") return `z.literal(${v})`;
|
|
3296
|
+
return "z.unknown()";
|
|
3297
|
+
}
|
|
3298
|
+
case "prim":
|
|
3299
|
+
return zodApplyRefinements(`z.${node.type}()`, node.refinements);
|
|
3300
|
+
case "obj": {
|
|
3301
|
+
const entries = node.slots.map((slot) => {
|
|
3302
|
+
const inner = schemaNodeToZod(slot.node);
|
|
3303
|
+
return `${slot.key}: ${slot.optional ? `${inner}.optional()` : inner}`;
|
|
3304
|
+
}).join(", ");
|
|
3305
|
+
return `z.object({ ${entries} })`;
|
|
3306
|
+
}
|
|
3307
|
+
case "arr":
|
|
3308
|
+
return `z.array(${schemaNodeToZod(node.element)})`;
|
|
3309
|
+
case "tuple":
|
|
3310
|
+
return `z.tuple([${node.elements.map(schemaNodeToZod).join(", ")}])`;
|
|
3311
|
+
case "union":
|
|
3312
|
+
return `z.union([${node.members.map(schemaNodeToZod).join(", ")}])`;
|
|
3313
|
+
case "fn":
|
|
3314
|
+
return "z.function()";
|
|
3315
|
+
case "promise":
|
|
3316
|
+
return `z.promise(${schemaNodeToZod(node.inner)})`;
|
|
3317
|
+
case "brand":
|
|
3318
|
+
return `z.instanceof(${node.name})`;
|
|
3319
|
+
case "never":
|
|
3320
|
+
return "z.never()";
|
|
3321
|
+
case "unknown":
|
|
3322
|
+
default:
|
|
3323
|
+
return "z.unknown()";
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
var DIALECT_RENDERERS = {
|
|
3327
|
+
zod: schemaNodeToZod
|
|
3328
|
+
};
|
|
3329
|
+
function projectAbsToSchema(a, opts) {
|
|
3330
|
+
const dialect = opts?.dialect ?? "zod";
|
|
3331
|
+
const { node, dropped } = absToSchemaNode(a);
|
|
3332
|
+
const render = DIALECT_RENDERERS[dialect] ?? schemaNodeToZod;
|
|
3333
|
+
return { source: render(node), dialect, dropped };
|
|
3334
|
+
}
|
|
3335
|
+
function absToSchemaSource(a, opts) {
|
|
3336
|
+
return projectAbsToSchema(a, opts).source;
|
|
2391
3337
|
}
|
|
2392
3338
|
|
|
3339
|
+
// src/emit/standard-schema.ts
|
|
3340
|
+
function pushIssue(issues, path, message) {
|
|
3341
|
+
issues.push(path.length > 0 ? { message, path: [...path] } : { message });
|
|
3342
|
+
}
|
|
3343
|
+
function typeOf(v) {
|
|
3344
|
+
if (v === null) return "null";
|
|
3345
|
+
if (Array.isArray(v)) return "array";
|
|
3346
|
+
return typeof v;
|
|
3347
|
+
}
|
|
3348
|
+
function checkPrimRefinements(refinements, value, path, issues) {
|
|
3349
|
+
if (typeof value !== "number" && typeof value !== "string") return;
|
|
3350
|
+
for (const r of refinements) {
|
|
3351
|
+
if (r.kind === "int") {
|
|
3352
|
+
if (typeof value === "number" && !Number.isInteger(value)) {
|
|
3353
|
+
pushIssue(issues, path, "expected integer");
|
|
3354
|
+
}
|
|
3355
|
+
continue;
|
|
3356
|
+
}
|
|
3357
|
+
if (r.kind === "numBound" && typeof value === "number" && typeof r.n === "number") {
|
|
3358
|
+
const n = r.n;
|
|
3359
|
+
const op = r.op;
|
|
3360
|
+
let ok = true;
|
|
3361
|
+
if (op === "gt") ok = value > n;
|
|
3362
|
+
else if (op === "ge") ok = value >= n;
|
|
3363
|
+
else if (op === "lt") ok = value < n;
|
|
3364
|
+
else if (op === "le") ok = value <= n;
|
|
3365
|
+
if (!ok) pushIssue(issues, path, `expected number ${op} ${n}, got ${value}`);
|
|
3366
|
+
continue;
|
|
3367
|
+
}
|
|
3368
|
+
if (r.kind === "strMin" && typeof value === "string" && typeof r.n === "number") {
|
|
3369
|
+
if (value.length < r.n) {
|
|
3370
|
+
pushIssue(issues, path, `expected string length >= ${r.n}, got ${value.length}`);
|
|
3371
|
+
}
|
|
3372
|
+
continue;
|
|
3373
|
+
}
|
|
3374
|
+
if (r.kind === "strMax" && typeof value === "string" && typeof r.n === "number") {
|
|
3375
|
+
if (value.length > r.n) {
|
|
3376
|
+
pushIssue(issues, path, `expected string length <= ${r.n}, got ${value.length}`);
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
function checkNode(node, value, path, issues) {
|
|
3382
|
+
switch (node.k) {
|
|
3383
|
+
case "unknown":
|
|
3384
|
+
case "fn":
|
|
3385
|
+
case "brand":
|
|
3386
|
+
case "promise":
|
|
3387
|
+
return;
|
|
3388
|
+
case "never":
|
|
3389
|
+
pushIssue(issues, path, "expected never");
|
|
3390
|
+
return;
|
|
3391
|
+
case "lit": {
|
|
3392
|
+
const expected = node.value;
|
|
3393
|
+
const same = expected === value || typeof expected === "number" && typeof value === "number" && Number.isNaN(expected) && Number.isNaN(value);
|
|
3394
|
+
if (!same) {
|
|
3395
|
+
pushIssue(issues, path, `expected ${JSON.stringify(expected)}, got ${JSON.stringify(value)}`);
|
|
3396
|
+
}
|
|
3397
|
+
return;
|
|
3398
|
+
}
|
|
3399
|
+
case "prim": {
|
|
3400
|
+
const t = node.type;
|
|
3401
|
+
const actual = typeOf(value);
|
|
3402
|
+
const ok = t === "number" && actual === "number" || t === "string" && actual === "string" || t === "boolean" && actual === "boolean" || t === "bigint" && actual === "bigint" || t === "symbol" && actual === "symbol";
|
|
3403
|
+
if (!ok) {
|
|
3404
|
+
pushIssue(issues, path, `expected ${t}, got ${actual}`);
|
|
3405
|
+
return;
|
|
3406
|
+
}
|
|
3407
|
+
checkPrimRefinements(node.refinements, value, path, issues);
|
|
3408
|
+
return;
|
|
3409
|
+
}
|
|
3410
|
+
case "obj": {
|
|
3411
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
3412
|
+
pushIssue(issues, path, `expected object, got ${typeOf(value)}`);
|
|
3413
|
+
return;
|
|
3414
|
+
}
|
|
3415
|
+
const rec = value;
|
|
3416
|
+
for (const slot of node.slots) {
|
|
3417
|
+
const key = slot.key;
|
|
3418
|
+
if (!(key in rec) || rec[key] === void 0) {
|
|
3419
|
+
if (!slot.optional) pushIssue(issues, [...path, key], "required");
|
|
3420
|
+
continue;
|
|
3421
|
+
}
|
|
3422
|
+
checkNode(slot.node, rec[key], [...path, key], issues);
|
|
3423
|
+
}
|
|
3424
|
+
return;
|
|
3425
|
+
}
|
|
3426
|
+
case "arr": {
|
|
3427
|
+
if (!Array.isArray(value)) {
|
|
3428
|
+
pushIssue(issues, path, `expected array, got ${typeOf(value)}`);
|
|
3429
|
+
return;
|
|
3430
|
+
}
|
|
3431
|
+
value.forEach((item, i) => {
|
|
3432
|
+
checkNode(node.element, item, [...path, i], issues);
|
|
3433
|
+
});
|
|
3434
|
+
return;
|
|
3435
|
+
}
|
|
3436
|
+
case "tuple": {
|
|
3437
|
+
if (!Array.isArray(value)) {
|
|
3438
|
+
pushIssue(issues, path, `expected tuple, got ${typeOf(value)}`);
|
|
3439
|
+
return;
|
|
3440
|
+
}
|
|
3441
|
+
node.elements.forEach((el, i) => {
|
|
3442
|
+
checkNode(el, value[i], [...path, i], issues);
|
|
3443
|
+
});
|
|
3444
|
+
return;
|
|
3445
|
+
}
|
|
3446
|
+
case "union": {
|
|
3447
|
+
for (const m of node.members) {
|
|
3448
|
+
const local = [];
|
|
3449
|
+
checkNode(m, value, path, local);
|
|
3450
|
+
if (local.length === 0) return;
|
|
3451
|
+
}
|
|
3452
|
+
pushIssue(
|
|
3453
|
+
issues,
|
|
3454
|
+
path,
|
|
3455
|
+
`expected one of ${node.members.length} union members, got ${typeOf(value)}`
|
|
3456
|
+
);
|
|
3457
|
+
return;
|
|
3458
|
+
}
|
|
3459
|
+
case "promise": {
|
|
3460
|
+
return;
|
|
3461
|
+
}
|
|
3462
|
+
default:
|
|
3463
|
+
return;
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
function validateSchemaNode(node, value) {
|
|
3467
|
+
const issues = [];
|
|
3468
|
+
checkNode(node, value, [], issues);
|
|
3469
|
+
return issues.length === 0 ? { value } : { issues };
|
|
3470
|
+
}
|
|
3471
|
+
var CHECK_FN_SOURCE = `
|
|
3472
|
+
function __nudoTypeOf(v) {
|
|
3473
|
+
if (v === null) return "null";
|
|
3474
|
+
if (Array.isArray(v)) return "array";
|
|
3475
|
+
return typeof v;
|
|
3476
|
+
}
|
|
3477
|
+
function __nudoCheck(node, value, path, issues) {
|
|
3478
|
+
function push(msg) {
|
|
3479
|
+
issues.push(path.length > 0 ? { message: msg, path: path.slice() } : { message: msg });
|
|
3480
|
+
}
|
|
3481
|
+
switch (node.k) {
|
|
3482
|
+
case "unknown":
|
|
3483
|
+
case "summarized":
|
|
3484
|
+
case "fn":
|
|
3485
|
+
case "brand":
|
|
3486
|
+
case "promise":
|
|
3487
|
+
return;
|
|
3488
|
+
case "never":
|
|
3489
|
+
push("expected never");
|
|
3490
|
+
return;
|
|
3491
|
+
case "lit": {
|
|
3492
|
+
const expected = node.value;
|
|
3493
|
+
const same = expected === value ||
|
|
3494
|
+
(typeof expected === "number" && typeof value === "number" &&
|
|
3495
|
+
Number.isNaN(expected) && Number.isNaN(value));
|
|
3496
|
+
if (!same) push("expected " + JSON.stringify(expected) + ", got " + JSON.stringify(value));
|
|
3497
|
+
return;
|
|
3498
|
+
}
|
|
3499
|
+
case "prim": {
|
|
3500
|
+
const t = node.type;
|
|
3501
|
+
const actual = __nudoTypeOf(value);
|
|
3502
|
+
const ok =
|
|
3503
|
+
(t === "number" && actual === "number") ||
|
|
3504
|
+
(t === "string" && actual === "string") ||
|
|
3505
|
+
(t === "boolean" && actual === "boolean") ||
|
|
3506
|
+
(t === "bigint" && actual === "bigint") ||
|
|
3507
|
+
(t === "symbol" && actual === "symbol");
|
|
3508
|
+
if (!ok) { push("expected " + t + ", got " + actual); return; }
|
|
3509
|
+
for (const r of node.refinements || []) {
|
|
3510
|
+
if (r.kind === "int" && typeof value === "number" && !Number.isInteger(value)) {
|
|
3511
|
+
push("expected integer");
|
|
3512
|
+
} else if (r.kind === "numBound" && typeof value === "number" && typeof r.n === "number") {
|
|
3513
|
+
var okB = true;
|
|
3514
|
+
if (r.op === "gt") okB = value > r.n;
|
|
3515
|
+
else if (r.op === "ge") okB = value >= r.n;
|
|
3516
|
+
else if (r.op === "lt") okB = value < r.n;
|
|
3517
|
+
else if (r.op === "le") okB = value <= r.n;
|
|
3518
|
+
if (!okB) push("expected number " + r.op + " " + r.n + ", got " + value);
|
|
3519
|
+
} else if (r.kind === "strMin" && typeof value === "string" && typeof r.n === "number") {
|
|
3520
|
+
if (value.length < r.n) push("expected string length >= " + r.n + ", got " + value.length);
|
|
3521
|
+
} else if (r.kind === "strMax" && typeof value === "string" && typeof r.n === "number") {
|
|
3522
|
+
if (value.length > r.n) push("expected string length <= " + r.n + ", got " + value.length);
|
|
3523
|
+
}
|
|
3524
|
+
}
|
|
3525
|
+
return;
|
|
3526
|
+
}
|
|
3527
|
+
case "obj": {
|
|
3528
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
3529
|
+
push("expected object, got " + __nudoTypeOf(value));
|
|
3530
|
+
return;
|
|
3531
|
+
}
|
|
3532
|
+
for (const slot of node.slots) {
|
|
3533
|
+
const key = slot.key;
|
|
3534
|
+
if (!(key in value) || value[key] === undefined) {
|
|
3535
|
+
if (!slot.optional) { path.push(key); push("required"); path.pop(); }
|
|
3536
|
+
continue;
|
|
3537
|
+
}
|
|
3538
|
+
path.push(key);
|
|
3539
|
+
__nudoCheck(slot.node, value[key], path, issues);
|
|
3540
|
+
path.pop();
|
|
3541
|
+
}
|
|
3542
|
+
return;
|
|
3543
|
+
}
|
|
3544
|
+
case "arr": {
|
|
3545
|
+
if (!Array.isArray(value)) { push("expected array, got " + __nudoTypeOf(value)); return; }
|
|
3546
|
+
for (let i = 0; i < value.length; i++) {
|
|
3547
|
+
path.push(i);
|
|
3548
|
+
__nudoCheck(node.element, value[i], path, issues);
|
|
3549
|
+
path.pop();
|
|
3550
|
+
}
|
|
3551
|
+
return;
|
|
3552
|
+
}
|
|
3553
|
+
case "tuple": {
|
|
3554
|
+
if (!Array.isArray(value)) { push("expected tuple, got " + __nudoTypeOf(value)); return; }
|
|
3555
|
+
for (let i = 0; i < node.elements.length; i++) {
|
|
3556
|
+
path.push(i);
|
|
3557
|
+
__nudoCheck(node.elements[i], value[i], path, issues);
|
|
3558
|
+
path.pop();
|
|
3559
|
+
}
|
|
3560
|
+
return;
|
|
3561
|
+
}
|
|
3562
|
+
case "union": {
|
|
3563
|
+
for (const m of node.members) {
|
|
3564
|
+
const local = [];
|
|
3565
|
+
__nudoCheck(m, value, path, local);
|
|
3566
|
+
if (local.length === 0) return;
|
|
3567
|
+
}
|
|
3568
|
+
push("expected one of " + node.members.length + " union members, got " + __nudoTypeOf(value));
|
|
3569
|
+
return;
|
|
3570
|
+
}
|
|
3571
|
+
default:
|
|
3572
|
+
return;
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
`.trim();
|
|
3576
|
+
function makeStandardSchemaSource(exportName, node, dropped) {
|
|
3577
|
+
const json = JSON.stringify(node);
|
|
3578
|
+
const notes = dropped.length > 0 ? dropped.map((d) => `// ${d}`).join("\n") + "\n" : "";
|
|
3579
|
+
return `${notes}export const ${exportName} = {
|
|
3580
|
+
"~standard": {
|
|
3581
|
+
version: 1,
|
|
3582
|
+
vendor: "nudo",
|
|
3583
|
+
validate(value) {
|
|
3584
|
+
const issues = [];
|
|
3585
|
+
__nudoCheck(${json}, value, [], issues);
|
|
3586
|
+
return issues.length ? { issues } : { value };
|
|
3587
|
+
},
|
|
3588
|
+
},
|
|
3589
|
+
} as const;`;
|
|
3590
|
+
}
|
|
3591
|
+
function absToStandardSchemaModule(exports, opts) {
|
|
3592
|
+
const dropped = [];
|
|
3593
|
+
const bodies = [];
|
|
3594
|
+
for (const [name, abs] of Object.entries(exports)) {
|
|
3595
|
+
const { node, dropped: d } = absToSchemaNode(abs);
|
|
3596
|
+
dropped.push(...d.map((n) => `${name}: ${n}`));
|
|
3597
|
+
bodies.push(makeStandardSchemaSource(name, node, d.map((n) => `${name}: ${n}`)));
|
|
3598
|
+
}
|
|
3599
|
+
const banner = opts?.banner ?? `// @generated by nudo export --format standard \u2014 Standard Schema v1 (vendor: nudo)
|
|
3600
|
+
// One-way lossy projection of Abs; do not edit. nudo check remains the gate.`;
|
|
3601
|
+
const source = `${banner}
|
|
3602
|
+
|
|
3603
|
+
${CHECK_FN_SOURCE}
|
|
3604
|
+
|
|
3605
|
+
${bodies.join("\n\n")}
|
|
3606
|
+
`;
|
|
3607
|
+
return { source, dropped };
|
|
3608
|
+
}
|
|
3609
|
+
function absToStandardSchema(a, opts) {
|
|
3610
|
+
return absToStandardSchemaModule({ [opts?.name ?? "schema"]: a });
|
|
3611
|
+
}
|
|
3612
|
+
|
|
3613
|
+
// src/emit/guard-generator.ts
|
|
3614
|
+
import { denoteGuard } from "@nudojs/core/internal";
|
|
3615
|
+
function generateGuardFunctionFromAbs(name, abs) {
|
|
3616
|
+
const body = denoteGuard(abs, "data");
|
|
3617
|
+
return `export function ${name}(data) {
|
|
3618
|
+
return ${body};
|
|
3619
|
+
}`;
|
|
3620
|
+
}
|
|
3621
|
+
function generateGuardFunction(name, abs) {
|
|
3622
|
+
return generateGuardFunctionFromAbs(name, abs);
|
|
3623
|
+
}
|
|
3624
|
+
|
|
3625
|
+
// src/emit/case-json.ts
|
|
3626
|
+
import { formatAbs, formatShape as formatShape3 } from "@nudojs/core";
|
|
3627
|
+
function mapCase(c) {
|
|
3628
|
+
const out = {
|
|
3629
|
+
name: c.name,
|
|
3630
|
+
args: c.argAbs.map((a) => formatShape3(a)),
|
|
3631
|
+
result: formatShape3(c.abs),
|
|
3632
|
+
throws: c.throwsAbs.shape.k !== "never" ? formatShape3(c.throwsAbs) : null,
|
|
3633
|
+
source: c.source ?? null
|
|
3634
|
+
};
|
|
3635
|
+
if (c.aggregatedFrom !== void 0) out.aggregatedFrom = c.aggregatedFrom;
|
|
3636
|
+
if (c.argAbs.length > 0) {
|
|
3637
|
+
out.argsAbs = c.argAbs.map((a) => {
|
|
3638
|
+
try {
|
|
3639
|
+
return formatAbs(a);
|
|
3640
|
+
} catch {
|
|
3641
|
+
return "unknown";
|
|
3642
|
+
}
|
|
3643
|
+
});
|
|
3644
|
+
}
|
|
3645
|
+
try {
|
|
3646
|
+
out.resultAbs = formatAbs(c.abs);
|
|
3647
|
+
} catch {
|
|
3648
|
+
}
|
|
3649
|
+
if (c.intension) {
|
|
3650
|
+
const i = c.intension;
|
|
3651
|
+
out.intension = {
|
|
3652
|
+
...i.display !== void 0 ? { display: i.display } : {},
|
|
3653
|
+
...i.abs !== void 0 ? { abs: i.abs } : {},
|
|
3654
|
+
...i.absMultiline !== void 0 ? { absMultiline: i.absMultiline } : {},
|
|
3655
|
+
...i.term !== void 0 ? { term: i.term } : {},
|
|
3656
|
+
...i.pred !== void 0 ? { pred: i.pred } : {},
|
|
3657
|
+
...i.conf !== void 0 ? { conf: i.conf } : {}
|
|
3658
|
+
};
|
|
3659
|
+
}
|
|
3660
|
+
return out;
|
|
3661
|
+
}
|
|
3662
|
+
function mapFunction(f) {
|
|
3663
|
+
const out = {
|
|
3664
|
+
name: f.name,
|
|
3665
|
+
loc: f.loc,
|
|
3666
|
+
entryOnly: f.entryOnly ?? false,
|
|
3667
|
+
cases: f.cases.map(mapCase)
|
|
3668
|
+
};
|
|
3669
|
+
if (f.noDeclaration) out.noDeclaration = true;
|
|
3670
|
+
if (f.combinedAbs) {
|
|
3671
|
+
out.combined = formatShape3(f.combinedAbs);
|
|
3672
|
+
try {
|
|
3673
|
+
out.combinedAbs = formatAbs(f.combinedAbs);
|
|
3674
|
+
} catch {
|
|
3675
|
+
}
|
|
3676
|
+
}
|
|
3677
|
+
return out;
|
|
3678
|
+
}
|
|
3679
|
+
function serializeCaseJson(result, file) {
|
|
3680
|
+
const functions = result.functions.map(mapFunction);
|
|
3681
|
+
const external = result.externalFunctions?.map((f) => ({
|
|
3682
|
+
name: f.name,
|
|
3683
|
+
fromModule: f.fromModule,
|
|
3684
|
+
cases: f.cases.map(mapCase)
|
|
3685
|
+
}));
|
|
3686
|
+
const caseCount = functions.reduce((n, f) => n + f.cases.length, 0) + (external ?? []).reduce((n, f) => n + f.cases.length, 0);
|
|
3687
|
+
const json = {
|
|
3688
|
+
version: 1,
|
|
3689
|
+
file,
|
|
3690
|
+
summary: {
|
|
3691
|
+
functions: functions.length,
|
|
3692
|
+
externalFunctions: external?.length ?? 0,
|
|
3693
|
+
cases: caseCount,
|
|
3694
|
+
diagnostics: result.diagnostics.length
|
|
3695
|
+
},
|
|
3696
|
+
functions,
|
|
3697
|
+
diagnostics: result.diagnostics.map((d) => ({
|
|
3698
|
+
range: d.range,
|
|
3699
|
+
severity: d.severity,
|
|
3700
|
+
message: d.message,
|
|
3701
|
+
...d.code !== void 0 ? { code: d.code } : {},
|
|
3702
|
+
...d.suggestions ? { suggestions: d.suggestions } : {},
|
|
3703
|
+
...d.tags ? { tags: d.tags } : {},
|
|
3704
|
+
...d.origin ? { origin: d.origin } : {}
|
|
3705
|
+
}))
|
|
3706
|
+
};
|
|
3707
|
+
if (external) json.externalFunctions = external;
|
|
3708
|
+
return json;
|
|
3709
|
+
}
|
|
2393
3710
|
export {
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
ifaceCacheKey,
|
|
2401
|
-
extractNudoImportSpecs,
|
|
2402
|
-
formatInterfaceSurfaceLine,
|
|
2403
|
-
interfaceSurface,
|
|
2404
|
-
emitInterface,
|
|
2405
|
-
formatEmitSummary,
|
|
3711
|
+
absToSchemaNode,
|
|
3712
|
+
absToSchemaSource,
|
|
3713
|
+
absToStandardSchema,
|
|
3714
|
+
absToStandardSchemaModule,
|
|
3715
|
+
absToTSType,
|
|
3716
|
+
buildCaseDirective,
|
|
2406
3717
|
collectParamBodyAccesses,
|
|
3718
|
+
constraintToSchemaNode,
|
|
3719
|
+
deriveFromRoot,
|
|
2407
3720
|
draftInterface,
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
isDraftableEntry,
|
|
2411
|
-
writeInterfaceDraft,
|
|
2412
|
-
formatDraftSummary,
|
|
3721
|
+
emitDerivedFromRoot,
|
|
3722
|
+
emitInterface,
|
|
2413
3723
|
extractFnConstraintSources,
|
|
2414
|
-
deriveFromRoot,
|
|
2415
3724
|
formatDerivedSection,
|
|
2416
|
-
|
|
3725
|
+
formatDraftModule,
|
|
3726
|
+
formatDraftSummary,
|
|
3727
|
+
formatEmitSummary,
|
|
3728
|
+
formatInterfaceSurfaceLine,
|
|
3729
|
+
generateDts,
|
|
3730
|
+
generateFunctionDtsLines,
|
|
3731
|
+
generateGuardFunction,
|
|
3732
|
+
generateGuardFunctionFromAbs,
|
|
3733
|
+
injectBindings,
|
|
3734
|
+
insertGeneratedCaseDirectives,
|
|
3735
|
+
interfaceSurface,
|
|
3736
|
+
isDraftableEntry,
|
|
3737
|
+
projectAbsToSchema,
|
|
3738
|
+
schemaNodeToZod,
|
|
3739
|
+
serializeCaseArg,
|
|
3740
|
+
serializeCaseJson,
|
|
3741
|
+
sidecarDraftPath,
|
|
3742
|
+
stripGeneratedCaseDirectives,
|
|
2417
3743
|
typeExprToDirective,
|
|
2418
|
-
|
|
3744
|
+
unifiedDiff,
|
|
3745
|
+
validateSchemaNode,
|
|
3746
|
+
writeInterfaceDraft
|
|
2419
3747
|
};
|