@nudojs/service 4.0.0 → 5.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3687 @@
1
+ import {
2
+ bareSpecToAbsModules
3
+ } from "./chunk-U7JYTRIB.js";
4
+ import {
5
+ analysisConfig,
6
+ findProjectConfig,
7
+ getSessionCacheLimits,
8
+ interfaceConfig,
9
+ resolveNpmJsEntry
10
+ } from "./chunk-YLALZXTZ.js";
11
+ import {
12
+ BoundedLruMap,
13
+ loadEnvs,
14
+ preloadPathEnvs
15
+ } from "./chunk-PVJIMQRI.js";
16
+
17
+ // src/analyzer-module-load.ts
18
+ import { readFileSync, existsSync, statSync } from "fs";
19
+ import { resolve, dirname } from "path";
20
+ import { parse } from "@nudojs/parser";
21
+ function resolveImportPath(specifier, fromDir) {
22
+ const basePath = resolve(fromDir, specifier);
23
+ for (const ext of ["", ".js", ".ts", ".mjs"]) {
24
+ const candidate = basePath + ext;
25
+ if (existsSync(candidate)) return candidate;
26
+ }
27
+ return null;
28
+ }
29
+ function buildModuleGraph(files, cache) {
30
+ const imports = /* @__PURE__ */ new Map();
31
+ const dependents = /* @__PURE__ */ new Map();
32
+ const edge = (from, to) => {
33
+ if (!imports.has(from)) imports.set(from, /* @__PURE__ */ new Set());
34
+ if (!dependents.has(to)) dependents.set(to, /* @__PURE__ */ new Set());
35
+ imports.get(from).add(to);
36
+ dependents.get(to).add(from);
37
+ };
38
+ for (const file of files) {
39
+ if (!imports.has(file)) imports.set(file, /* @__PURE__ */ new Set());
40
+ if (!dependents.has(file)) dependents.set(file, /* @__PURE__ */ new Set());
41
+ let edges;
42
+ if (cache) {
43
+ let stat = null;
44
+ try {
45
+ stat = statSync(file);
46
+ } catch {
47
+ }
48
+ const cached = stat ? cache.get(file) : void 0;
49
+ if (stat && cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
50
+ edges = cached.edges;
51
+ } else {
52
+ edges = extractImportEdges(file);
53
+ if (stat) cache.set(file, { mtimeMs: stat.mtimeMs, size: stat.size, edges });
54
+ }
55
+ } else {
56
+ edges = extractImportEdges(file);
57
+ }
58
+ for (const to of edges) edge(file, to);
59
+ }
60
+ return { imports, dependents };
61
+ }
62
+ function extractImportEdges(file) {
63
+ const edges = [];
64
+ let ast;
65
+ try {
66
+ ast = parse(readFileSync(file, "utf-8"));
67
+ } catch {
68
+ return edges;
69
+ }
70
+ for (const stmt of ast.program.body) {
71
+ if (stmt.type !== "ImportDeclaration") continue;
72
+ const specifier = stmt.source.value;
73
+ if (!specifier.startsWith(".") && !specifier.startsWith("/")) continue;
74
+ const resolved = resolveImportPath(specifier, dirname(file));
75
+ if (resolved) edges.push(resolved);
76
+ }
77
+ return edges;
78
+ }
79
+ function computeDirtySet(dependents, changedFile) {
80
+ const dirty = [];
81
+ const visited = /* @__PURE__ */ new Set();
82
+ const queue = [changedFile];
83
+ while (queue.length > 0) {
84
+ const file = queue.shift();
85
+ if (visited.has(file)) continue;
86
+ visited.add(file);
87
+ dirty.push(file);
88
+ for (const dep of dependents.get(file) ?? []) {
89
+ if (!visited.has(dep)) queue.push(dep);
90
+ }
91
+ }
92
+ return dirty;
93
+ }
94
+ function topoSortDirty(imports, dirty) {
95
+ const inSet = new Set(dirty);
96
+ const pending = /* @__PURE__ */ new Map();
97
+ for (const file of dirty) {
98
+ let count = 0;
99
+ for (const dep of imports.get(file) ?? []) {
100
+ if (inSet.has(dep)) count++;
101
+ }
102
+ pending.set(file, count);
103
+ }
104
+ const ordered = [];
105
+ const ready = dirty.filter((f) => pending.get(f) === 0);
106
+ while (ready.length > 0) {
107
+ const file = ready.shift();
108
+ ordered.push(file);
109
+ for (const other of dirty) {
110
+ if (pending.get(other) === void 0) continue;
111
+ if ((imports.get(other) ?? /* @__PURE__ */ new Set()).has(file)) {
112
+ const next = pending.get(other) - 1;
113
+ pending.set(other, next);
114
+ if (next === 0) ready.push(other);
115
+ }
116
+ }
117
+ }
118
+ for (const file of dirty) {
119
+ if (pending.has(file) && pending.get(file) > 0) ordered.push(file);
120
+ }
121
+ return ordered;
122
+ }
123
+
124
+ // src/load-module.ts
125
+ import { existsSync as existsSync2, statSync as statSync2, readFileSync as readFileSync2 } from "fs";
126
+ import { dirname as dirname2, resolve as resolve2, join } from "path";
127
+ function defaultLoadModule(spec, fromFile) {
128
+ if (!spec.startsWith(".") && !spec.startsWith("/")) return void 0;
129
+ try {
130
+ const base = dirname2(resolve2(fromFile));
131
+ const p = resolve2(base, spec);
132
+ for (const cand of [
133
+ p,
134
+ `${p}.js`,
135
+ `${p}.mjs`,
136
+ `${p}.ts`,
137
+ join(p, "index.js"),
138
+ join(p, "index.mjs"),
139
+ join(p, "index.ts")
140
+ ]) {
141
+ if (existsSync2(cand) && !statSync2(cand).isDirectory()) {
142
+ return readFileSync2(cand, "utf-8");
143
+ }
144
+ }
145
+ return void 0;
146
+ } catch {
147
+ return void 0;
148
+ }
149
+ }
150
+
151
+ // src/analyzer-cache.ts
152
+ import { loadModuleDepsFingerprint, hashSource, stableAnalyzeKeySource } from "@nudojs/core/internal";
153
+ var externalRecordIds = /* @__PURE__ */ new WeakMap();
154
+ var nextExternalRecordId = 1;
155
+ function analysisFileCacheKey(filePath, source, activeCases, externalCallRecords, analysisCfg, loadModule, projectEnvNames, autoBind, caseMode = "all") {
156
+ let cases = "-";
157
+ if (activeCases && activeCases.size > 0) {
158
+ cases = [...activeCases.entries()].sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0).map(([k, v]) => `${k}=${v}`).join(",");
159
+ }
160
+ let ext = "-";
161
+ if (externalCallRecords && externalCallRecords.length > 0) {
162
+ let id = externalRecordIds.get(externalCallRecords);
163
+ if (id === void 0) {
164
+ id = nextExternalRecordId++;
165
+ externalRecordIds.set(externalCallRecords, id);
166
+ }
167
+ ext = `n${externalCallRecords.length}#id${id}`;
168
+ }
169
+ const cfg = analysisCfg ? `m=${analysisCfg.mode}|e=${analysisCfg.evalMissingSlot}|b=${analysisCfg.callSiteBudget}|d=${analysisCfg.diagnostics}` : "-";
170
+ const lm = loadModule !== void 0 && loadModule !== defaultLoadModule ? "lm1" : "lm0";
171
+ const envSeg = projectEnvNames && projectEnvNames.length > 0 ? projectEnvNames.join(",") : "-";
172
+ const abSeg = autoBind === false ? "ab0" : autoBind === true ? "ab1" : "ab?";
173
+ const effectiveLoader = loadModule ?? defaultLoadModule;
174
+ let depSeg = "-";
175
+ let noCache = false;
176
+ try {
177
+ const fp = loadModuleDepsFingerprint(source, effectiveLoader, filePath);
178
+ if (fp.truncated) {
179
+ noCache = true;
180
+ depSeg = `trunc:${fp.paths.length}`;
181
+ } else {
182
+ depSeg = hashSource(fp.fp);
183
+ }
184
+ } catch {
185
+ noCache = true;
186
+ depSeg = "fperr";
187
+ }
188
+ return {
189
+ filePath,
190
+ // 尾部无 @nudo 注释/空行不进键:comment-only 编辑命中 AnalysisResult
191
+ source: stableAnalyzeKeySource(source),
192
+ auxKey: `${cases}\0${ext}\0${cfg}\0${lm}\0${envSeg}\0${abSeg}\0${depSeg}\0cm=${caseMode}`,
193
+ noCache
194
+ };
195
+ }
196
+ function cloneAnalysisResult(r) {
197
+ return {
198
+ functions: r.functions.map(cloneFunctionAnalysis),
199
+ diagnostics: r.diagnostics.map((d) => ({ ...d })),
200
+ bindings: new Map(r.bindings),
201
+ // Node 键与 AST LRU 共享身份;Map 浅拷贝即可
202
+ nodeAbsMap: new Map(r.nodeAbsMap),
203
+ caseHints: r.caseHints.map((h) => ({ ...h })),
204
+ ...r.externalFunctions ? { externalFunctions: r.externalFunctions.map(cloneFunctionAnalysis) } : {}
205
+ };
206
+ }
207
+ function cloneFunctionAnalysis(a) {
208
+ return {
209
+ ...a,
210
+ paramNames: [...a.paramNames],
211
+ ...a.formals ? { formals: a.formals.map((f) => ({ ...f })) } : {},
212
+ cases: a.cases.map((c) => ({
213
+ ...c,
214
+ argAbs: [...c.argAbs],
215
+ ...c.intension ? { intension: { ...c.intension } } : {}
216
+ })),
217
+ loc: { start: { ...a.loc.start }, end: { ...a.loc.end } },
218
+ ...a.hof ? {
219
+ hof: {
220
+ ...a.hof.fnRels ? { fnRels: a.hof.fnRels.map((r) => ({ ...r })) } : {},
221
+ ...a.hof.entryShapes ? { entryShapes: a.hof.entryShapes.map((s) => ({ ...s })) } : {},
222
+ ...a.hof.symbolic ? { symbolic: a.hof.symbolic } : {}
223
+ }
224
+ } : {}
225
+ };
226
+ }
227
+ function shiftSourceLoc(loc, lineDelta) {
228
+ return {
229
+ start: { line: loc.start.line + lineDelta, column: loc.start.column },
230
+ end: { line: loc.end.line + lineDelta, column: loc.end.column }
231
+ };
232
+ }
233
+ function shiftDiagnosticLines(d, lineDelta) {
234
+ if (lineDelta === 0) return { ...d };
235
+ const out = { ...d, range: shiftSourceLoc(d.range, lineDelta) };
236
+ if (d.origin) {
237
+ out.origin = { line: d.origin.line + lineDelta, column: d.origin.column };
238
+ }
239
+ return out;
240
+ }
241
+ function shiftCallRecordLines(r, lineDelta) {
242
+ if (lineDelta === 0 || !r.callLoc) return { ...r };
243
+ return {
244
+ ...r,
245
+ callLoc: { line: r.callLoc.line + lineDelta, column: r.callLoc.column }
246
+ };
247
+ }
248
+
249
+ // src/abs-modules-graph.ts
250
+ import { readFileSync as readFileSync3, statSync as statSync3 } from "fs";
251
+ import { dirname as dirname3, resolve as resolve3 } from "path";
252
+ import { parse as parse2 } from "@nudojs/parser";
253
+ import {
254
+ abs as makeAbs,
255
+ absFunction,
256
+ bindingsOf,
257
+ tryRunTranspiled,
258
+ callTranspiledExportFull,
259
+ foldStaticStringExpr,
260
+ unknown
261
+ } from "@nudojs/core";
262
+ function seedFnsToMocks(seedVars, seedFns) {
263
+ const out = { ...seedVars };
264
+ for (const [name, fn] of Object.entries(seedFns)) {
265
+ out[name] = absFunction(fn.params, {
266
+ body: fn.body,
267
+ async: fn.async ?? false
268
+ });
269
+ }
270
+ return out;
271
+ }
272
+ function defaultAbsLoadModule(spec, fromFile) {
273
+ if (!spec.startsWith(".") && !spec.startsWith("/")) return void 0;
274
+ try {
275
+ const p = resolve3(dirname3(resolve3(fromFile)), spec);
276
+ for (const cand of [p, `${p}.js`, `${p}.mjs`, `${p}.ts`, resolve3(p, "index.js")]) {
277
+ try {
278
+ return readFileSync3(cand, "utf-8");
279
+ } catch {
280
+ }
281
+ }
282
+ return void 0;
283
+ } catch {
284
+ return void 0;
285
+ }
286
+ }
287
+ function relCandidates(spec, fromFile) {
288
+ const p = resolve3(dirname3(resolve3(fromFile)), spec);
289
+ return [p, `${p}.js`, `${p}.mjs`, `${p}.ts`, resolve3(p, "index.js")];
290
+ }
291
+ function resolveRel(spec, fromFile) {
292
+ if (!spec.startsWith(".") && !spec.startsWith("/")) return null;
293
+ for (const cand of relCandidates(spec, fromFile)) {
294
+ try {
295
+ readFileSync3(cand, "utf-8");
296
+ return cand;
297
+ } catch {
298
+ }
299
+ }
300
+ return null;
301
+ }
302
+ function importSpecs(source) {
303
+ try {
304
+ const file = parse2(source);
305
+ const out = [];
306
+ for (const stmt of file.program.body) {
307
+ if (stmt.type === "ImportDeclaration" || stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportAllDeclaration") {
308
+ const src = stmt.source;
309
+ if (src && typeof src.value === "string") {
310
+ out.push(src.value);
311
+ }
312
+ }
313
+ collectRequireSpecs(stmt, out);
314
+ }
315
+ return out;
316
+ } catch {
317
+ return [];
318
+ }
319
+ }
320
+ function collectRequireSpecs(node, out) {
321
+ const visit = (n) => {
322
+ if (!n || typeof n !== "object") return;
323
+ const o = n;
324
+ if (o.type === "CallExpression" && o.callee) {
325
+ const c = o.callee;
326
+ let spec;
327
+ if (c.type === "Identifier" && c.name === "require") {
328
+ spec = foldStaticStringExpr(o.arguments?.[0]);
329
+ } else if (c.type === "MemberExpression" && !c.computed && c.object?.type === "Identifier" && c.object.name === "require" && c.property?.type === "Identifier" && c.property.name === "resolve") {
330
+ spec = foldStaticStringExpr(o.arguments?.[0]);
331
+ }
332
+ if (spec !== void 0) out.push(spec);
333
+ }
334
+ for (const key of Object.keys(o)) {
335
+ if (key === "loc" || key === "start" || key === "end") continue;
336
+ const v = o[key];
337
+ if (Array.isArray(v)) v.forEach(visit);
338
+ else if (v && typeof v === "object") visit(v);
339
+ }
340
+ };
341
+ visit(node);
342
+ }
343
+ function buildModulesForFile(source, fromFile, load, evalDep, depth, maxDepth, onMissing) {
344
+ const modules = {};
345
+ for (const spec of importSpecs(source)) {
346
+ if (spec.startsWith(".") || spec.startsWith("/")) {
347
+ const childPath = resolveRel(spec, fromFile);
348
+ if (!childPath) {
349
+ onMissing(spec, fromFile, relCandidates(spec, fromFile));
350
+ continue;
351
+ }
352
+ modules[spec] = evalDep(childPath, spec, fromFile, depth + 1);
353
+ } else if (!spec.startsWith("node:")) {
354
+ const entry = resolveNpmJsEntry(spec, dirname3(fromFile));
355
+ if (entry) {
356
+ const executed = evalDep(entry, spec, fromFile, depth + 1);
357
+ if (executed && (executed.default !== void 0 || Object.keys(executed.named ?? {}).length > 0)) {
358
+ modules[spec] = executed;
359
+ continue;
360
+ }
361
+ }
362
+ const bare = bareSpecToAbsModules(spec, fromFile);
363
+ if (bare) modules[spec] = bare;
364
+ }
365
+ }
366
+ void maxDepth;
367
+ return modules;
368
+ }
369
+ var ABS_MODULE_CACHE_MAX = 512;
370
+ var absModuleCache = new BoundedLruMap(ABS_MODULE_CACHE_MAX);
371
+ function getAbsModuleCacheSize() {
372
+ return absModuleCache.size;
373
+ }
374
+ function clearAbsModuleCache() {
375
+ absModuleCache.clear();
376
+ }
377
+ function evictAbsModuleCacheFiles(paths) {
378
+ for (const p of paths) absModuleCache.delete(p);
379
+ }
380
+ function moduleLabel(p) {
381
+ const parts = p.split(/[/\\]/);
382
+ return parts[parts.length - 1] || p;
383
+ }
384
+ function isAbsVal(v) {
385
+ return !!v && typeof v === "object" && "shape" in v && "conf" in v;
386
+ }
387
+ function paramNameOf(p, i) {
388
+ if (p.type === "Identifier" && p.name) return p.name;
389
+ if (p.type === "RestElement" && p.argument?.type === "Identifier" && p.argument.name) {
390
+ return `...${p.argument.name}`;
391
+ }
392
+ return `arg${i}`;
393
+ }
394
+ function topLevelFnParams(file) {
395
+ const out = /* @__PURE__ */ new Map();
396
+ const body = file.program?.body ?? [];
397
+ for (const stmt of body) {
398
+ if (stmt.type === "FunctionDeclaration" && stmt.id && stmt.params) {
399
+ out.set(stmt.id.name, stmt.params.map(paramNameOf));
400
+ }
401
+ if (stmt.type === "ExportDefaultDeclaration") {
402
+ const d = stmt.declaration;
403
+ if (d && (d.type === "FunctionDeclaration" || d.type === "ArrowFunctionExpression") && d.params) {
404
+ out.set("default", d.params.map(paramNameOf));
405
+ }
406
+ }
407
+ }
408
+ return out;
409
+ }
410
+ function bPathExportsToModuleExports(run, file, fingerprintPrefix) {
411
+ const named = {};
412
+ let def;
413
+ const paramTable = topLevelFnParams(file);
414
+ for (const [k, v] of Object.entries(run)) {
415
+ if (v === void 0 || v === null) continue;
416
+ let absVal;
417
+ if (isAbsVal(v)) {
418
+ absVal = v;
419
+ } else if (typeof v === "function") {
420
+ const params = paramTable.get(k) ?? Array.from({ length: v.length ?? 0 }, (_, i) => `arg${i}`);
421
+ absVal = absFunction(params, {
422
+ apply: (args) => callTranspiledExportFull(run, k, args).result,
423
+ kind: "bpath-export",
424
+ // 无 body 的桥接 fn 预算键 = fingerprint ?? anon#N——缺省会让所有
425
+ // 桥接导出共享 anon#1,嵌套跨模块调用(a 调 b 调 a')撞
426
+ // _activeCallKeys 递归守卫被误截断为 opaque。按 模块#导出 唯一化。
427
+ fingerprint: `${fingerprintPrefix}#${k}`
428
+ });
429
+ } else {
430
+ absVal = unknown;
431
+ }
432
+ if (k === "default") def = absVal;
433
+ else named[k] = absVal;
434
+ }
435
+ const out = { named };
436
+ if (def) out.default = def;
437
+ return out;
438
+ }
439
+ function evalAbsModuleGraph(entrySource, entryFile, opts = {}) {
440
+ const load = opts.loadModule ?? defaultAbsLoadModule;
441
+ const maxDepth = opts.maxDepth ?? 16;
442
+ const cache = /* @__PURE__ */ new Map();
443
+ const loading = [];
444
+ const issues = [];
445
+ const seenIssue = /* @__PURE__ */ new Set();
446
+ const pushIssue = (kind, label, reason) => {
447
+ const key = `${kind}:${label}`;
448
+ if (seenIssue.has(key)) return;
449
+ seenIssue.add(key);
450
+ issues.push({ kind, label, reason });
451
+ };
452
+ function evalDep(absPath, spec, fromFile, depth) {
453
+ const cycleIndex = loading.indexOf(absPath);
454
+ if (cycleIndex !== -1) {
455
+ const chain = [...loading.slice(cycleIndex), absPath];
456
+ pushIssue(
457
+ "cycle",
458
+ moduleLabel(absPath),
459
+ `Circular module load: ${chain.join(" -> ")} (bindings inside the cycle resolve to their partially evaluated types)`
460
+ );
461
+ return cache.get(absPath) ?? { named: {} };
462
+ }
463
+ const shared = absModuleCache.get(absPath);
464
+ if (shared) {
465
+ try {
466
+ const st = statSync3(absPath);
467
+ if (st.mtimeMs === shared.mtimeMs && st.size === shared.size) {
468
+ for (const iss of shared.issues) pushIssue(iss.kind, iss.label, iss.reason);
469
+ cache.set(absPath, shared.exports);
470
+ return shared.exports;
471
+ }
472
+ } catch {
473
+ }
474
+ absModuleCache.delete(absPath);
475
+ }
476
+ if (cache.has(absPath)) return cache.get(absPath);
477
+ if (depth > maxDepth) {
478
+ const chain = [...loading, absPath];
479
+ pushIssue(
480
+ "depth",
481
+ moduleLabel(absPath),
482
+ `Module load chain too deep (depth ${chain.length} > ${maxDepth} max): ${chain.join(" -> ")} (loading truncated, deeper modules typed as unknown)`
483
+ );
484
+ const empty = { named: {} };
485
+ cache.set(absPath, empty);
486
+ return empty;
487
+ }
488
+ cache.set(absPath, { named: {} });
489
+ loading.push(absPath);
490
+ const source = load(spec, fromFile) ?? (() => {
491
+ try {
492
+ return readFileSync3(absPath, "utf-8");
493
+ } catch {
494
+ return void 0;
495
+ }
496
+ })();
497
+ if (source === void 0) {
498
+ pushIssue(
499
+ "missing",
500
+ spec,
501
+ `Module file not found for '${spec}' (from ${moduleLabel(fromFile)}); tried: ${absPath}`
502
+ );
503
+ const empty = { named: {} };
504
+ cache.set(absPath, empty);
505
+ loading.pop();
506
+ return empty;
507
+ }
508
+ const issueStart = issues.length;
509
+ const modules2 = buildModulesForFile(
510
+ source,
511
+ absPath,
512
+ load,
513
+ evalDep,
514
+ depth,
515
+ maxDepth,
516
+ (spec2, fromFile2, tried) => {
517
+ pushIssue(
518
+ "missing",
519
+ spec2,
520
+ `Module file not found for '${spec2}' (from ${moduleLabel(fromFile2)}); tried: ${tried.join(", ")}`
521
+ );
522
+ }
523
+ );
524
+ let exports;
525
+ const bRun = tryRunTranspiled(source, { mode: "analyze", modules: modules2 });
526
+ if (bRun) {
527
+ exports = bPathExportsToModuleExports(bRun, parse2(source), `bpath:${absPath}`);
528
+ } else {
529
+ exports = { named: {} };
530
+ }
531
+ loading.pop();
532
+ cache.set(absPath, exports);
533
+ let fingerprint;
534
+ try {
535
+ const st = statSync3(absPath);
536
+ fingerprint = { mtimeMs: st.mtimeMs, size: st.size };
537
+ } catch {
538
+ }
539
+ if (fingerprint) {
540
+ absModuleCache.set(absPath, {
541
+ ...fingerprint,
542
+ exports,
543
+ issues: issues.slice(issueStart)
544
+ });
545
+ }
546
+ return exports;
547
+ }
548
+ const modules = buildModulesForFile(
549
+ entrySource,
550
+ entryFile,
551
+ load,
552
+ evalDep,
553
+ 0,
554
+ maxDepth,
555
+ (spec, fromFile, tried) => {
556
+ pushIssue(
557
+ "missing",
558
+ spec,
559
+ `Module file not found for '${spec}' (from ${moduleLabel(fromFile)}); tried: ${tried.join(", ")}`
560
+ );
561
+ }
562
+ );
563
+ return { modules, byPath: cache, issues };
564
+ }
565
+ function importLocalBindings(source, modules) {
566
+ const out = /* @__PURE__ */ new Map();
567
+ let file;
568
+ try {
569
+ file = parse2(source);
570
+ } catch {
571
+ return out;
572
+ }
573
+ const body = file.program?.body ?? [];
574
+ for (const stmt of body) {
575
+ if (stmt.type !== "ImportDeclaration" || typeof stmt.source?.value !== "string") continue;
576
+ const mod = modules[stmt.source.value];
577
+ if (!mod) continue;
578
+ for (const sp of stmt.specifiers ?? []) {
579
+ const local = sp.local?.name;
580
+ if (!local) continue;
581
+ if (sp.type === "ImportNamespaceSpecifier") {
582
+ const slots = {};
583
+ for (const [k, v] of Object.entries(mod.named)) slots[k] = { value: v };
584
+ if (mod.default) slots["default"] = { value: mod.default };
585
+ out.set(local, makeAbs({ k: "obj", slots, open: true }, void 0, void 0, "path"));
586
+ } else if (sp.type === "ImportDefaultSpecifier") {
587
+ if (mod.default) out.set(local, mod.default);
588
+ } else if (sp.type === "ImportSpecifier") {
589
+ const imported = sp.imported?.type === "StringLiteral" ? sp.imported.value : sp.imported?.name;
590
+ if (imported === void 0) continue;
591
+ const absVal = imported === "default" ? mod.default : mod.named[imported];
592
+ if (absVal) out.set(local, absVal);
593
+ }
594
+ }
595
+ }
596
+ return out;
597
+ }
598
+ function collectAbsBindingsFromGraph(source, filePath, opts = {}) {
599
+ const out = /* @__PURE__ */ new Map();
600
+ try {
601
+ const { modules } = evalAbsModuleGraph(source, filePath, opts);
602
+ const mocks = seedFnsToMocks(opts.seedVars ?? {}, opts.seedFns ?? {});
603
+ const run = tryRunTranspiled(source, {
604
+ mode: "analyze",
605
+ modules,
606
+ envGlobals: Object.keys(mocks).length ? mocks : void 0
607
+ });
608
+ if (!run) return out;
609
+ const binds = bindingsOf(run);
610
+ if (binds) {
611
+ for (const [name, v] of binds) {
612
+ if (isAbsVal(v)) out.set(name, v);
613
+ }
614
+ }
615
+ const exports = bPathExportsToModuleExports(run, parse2(source), `bpath:bindings:${filePath}`);
616
+ for (const [name, v] of Object.entries(exports.named)) {
617
+ if (!out.has(name)) out.set(name, v);
618
+ }
619
+ for (const [local, absVal] of importLocalBindings(source, modules)) {
620
+ if (!out.has(local)) out.set(local, absVal);
621
+ }
622
+ } catch {
623
+ }
624
+ return out;
625
+ }
626
+
627
+ // src/env-path-deps.ts
628
+ import { existsSync as existsSync3, statSync as statSync4 } from "fs";
629
+ import { dirname as dirname4, resolve as resolve4, join as join2 } from "path";
630
+ import { extractAllLoadSpecs } from "@nudojs/core/internal";
631
+ var ENV_DEPENDENTS_MAX = 256;
632
+ var ENV_DEPENDENTS_PER_KEY_MAX = 512;
633
+ var envDependents = new BoundedLruMap(ENV_DEPENDENTS_MAX);
634
+ function getEnvPathDepsSize() {
635
+ return envDependents.size;
636
+ }
637
+ function norm(p) {
638
+ return p.replace(/\\/g, "/");
639
+ }
640
+ function resolveLoadSpecPath(spec, fromFile) {
641
+ if (!spec.startsWith(".") && !spec.startsWith("/")) return void 0;
642
+ try {
643
+ const base = dirname4(resolve4(fromFile));
644
+ const p = resolve4(base, spec);
645
+ for (const cand of [
646
+ p,
647
+ `${p}.js`,
648
+ `${p}.mjs`,
649
+ `${p}.ts`,
650
+ join2(p, "index.js"),
651
+ join2(p, "index.mjs"),
652
+ join2(p, "index.ts")
653
+ ]) {
654
+ if (existsSync3(cand) && !statSync4(cand).isDirectory()) return cand;
655
+ }
656
+ return void 0;
657
+ } catch {
658
+ return void 0;
659
+ }
660
+ }
661
+ function noteEnvPathDeps(sourcePath, source) {
662
+ const src = norm(sourcePath);
663
+ for (const set of envDependents.values()) set.delete(src);
664
+ let specs = [];
665
+ try {
666
+ specs = extractAllLoadSpecs(source);
667
+ } catch {
668
+ return;
669
+ }
670
+ for (const spec of specs) {
671
+ if (!spec.startsWith(".") && !spec.startsWith("/")) continue;
672
+ const abs = resolveLoadSpecPath(spec, sourcePath);
673
+ if (!abs) continue;
674
+ const key = norm(abs);
675
+ let set = envDependents.get(key);
676
+ if (!set) {
677
+ set = /* @__PURE__ */ new Set();
678
+ envDependents.set(key, set);
679
+ }
680
+ if (!set.has(src) && set.size >= ENV_DEPENDENTS_PER_KEY_MAX) {
681
+ const oldest = set.values().next().value;
682
+ if (oldest !== void 0) set.delete(oldest);
683
+ }
684
+ set.add(src);
685
+ }
686
+ }
687
+ function envPathDependents(envPath) {
688
+ return [...envDependents.peek(norm(envPath)) ?? []];
689
+ }
690
+ function clearEnvPathDeps() {
691
+ envDependents.clear();
692
+ }
693
+ function isEnvTemplatePath(path) {
694
+ const lower = norm(path).toLowerCase();
695
+ return lower.endsWith(".env.js") || lower.endsWith(".env.mjs") || lower.endsWith(".env.ts") || lower.endsWith(".env.cjs") || lower.endsWith(".env.cts") || lower.endsWith(".env.mts") || /\/mock-[^/]+\.(js|mjs|ts)$/.test(lower) || /\.mock\.(js|mjs|ts)$/.test(lower);
696
+ }
697
+
698
+ // src/mock-module.ts
699
+ import { extractFileDirectives, parse as parse3 } from "@nudojs/parser";
700
+ import { obj as absObj } from "@nudojs/core";
701
+
702
+ // src/mock-file.ts
703
+ import { tryRunTranspiled as tryRunTranspiled2 } from "@nudojs/core";
704
+ function evalMockFileWithDeps(fromPath, fromFile, loadModule) {
705
+ const load = loadModule ?? defaultLoadModule;
706
+ let source;
707
+ try {
708
+ source = load(fromPath, fromFile);
709
+ } catch {
710
+ source = void 0;
711
+ }
712
+ if (source === void 0) {
713
+ return { ok: false, error: `Mock file not found (from "${fromPath}")` };
714
+ }
715
+ const entryFile = resolveLoadSpecPath(fromPath, fromFile) ?? fromPath;
716
+ let modules = {};
717
+ try {
718
+ modules = evalAbsModuleGraph(source, entryFile, { loadModule }).modules;
719
+ } catch {
720
+ modules = {};
721
+ }
722
+ let run;
723
+ try {
724
+ run = tryRunTranspiled2(source, {
725
+ mode: "exec",
726
+ modules
727
+ });
728
+ } catch {
729
+ run = void 0;
730
+ }
731
+ if (!run) {
732
+ return { ok: false, error: `Mock file "${fromPath}" failed to evaluate` };
733
+ }
734
+ return {
735
+ ok: true,
736
+ run,
737
+ absPath: entryFile,
738
+ source,
739
+ modules
740
+ };
741
+ }
742
+
743
+ // src/mock-module.ts
744
+ function loadMockModuleExports(fromPath, fromFile, loadModule) {
745
+ const evaled = evalMockFileWithDeps(fromPath, fromFile, loadModule);
746
+ if (!evaled.ok) {
747
+ return {
748
+ error: evaled.error.includes("not found") ? `Mock module not found (from "${fromPath}")` : evaled.error
749
+ };
750
+ }
751
+ const exports = bPathExportsToModuleExports(evaled.run, parse3(evaled.source), `mock-module:${fromPath}`);
752
+ if (exports.default === void 0 && Object.keys(exports.named).length > 0) {
753
+ const slots = {};
754
+ for (const [k, v] of Object.entries(exports.named)) slots[k] = { value: v };
755
+ exports.default = absObj(slots);
756
+ }
757
+ return { exports };
758
+ }
759
+ function pickNamed(mock, names) {
760
+ const named = {};
761
+ let def;
762
+ for (const n of names) {
763
+ if (n === "default") {
764
+ if (mock.default) def = mock.default;
765
+ continue;
766
+ }
767
+ const v = mock.named[n];
768
+ if (v !== void 0) named[n] = v;
769
+ }
770
+ return def !== void 0 ? { named, default: def } : { named };
771
+ }
772
+ function applyMockModuleDirectives(base, fileDirectives, opts) {
773
+ const mods = fileDirectives.filter(
774
+ (d) => d.kind === "mock-module"
775
+ );
776
+ if (mods.length === 0) {
777
+ return { modules: base, errors: [], applied: false };
778
+ }
779
+ const out = { ...base };
780
+ const errors = [];
781
+ let applied = false;
782
+ for (const d of mods) {
783
+ const loaded = loadMockModuleExports(d.fromPath, opts.fromFile, opts.loadModule);
784
+ if (!loaded.exports) {
785
+ errors.push({
786
+ name: d.source,
787
+ fromPath: d.fromPath,
788
+ message: loaded.error ?? `Mock module "${d.fromPath}" could not be loaded`
789
+ });
790
+ continue;
791
+ }
792
+ const mock = loaded.exports;
793
+ if (!d.names || d.names.length === 0) {
794
+ out[d.source] = mock;
795
+ applied = true;
796
+ continue;
797
+ }
798
+ const orig = out[d.source] ?? { named: {} };
799
+ const overlay = pickNamed(mock, d.names);
800
+ const named = { ...orig.named, ...overlay.named };
801
+ const merged = { named };
802
+ const wantDefault = d.names.includes("default");
803
+ if (wantDefault && overlay.default !== void 0) {
804
+ merged.default = overlay.default;
805
+ } else if (orig.default !== void 0) {
806
+ merged.default = orig.default;
807
+ }
808
+ out[d.source] = merged;
809
+ applied = true;
810
+ }
811
+ return { modules: out, errors, applied };
812
+ }
813
+ function applyMockModuleDirectivesFromSource(source, base, opts) {
814
+ let fileDirectives = [];
815
+ try {
816
+ fileDirectives = extractFileDirectives(parse3(source));
817
+ } catch {
818
+ fileDirectives = [];
819
+ }
820
+ return applyMockModuleDirectives(base, fileDirectives, opts);
821
+ }
822
+
823
+ // src/analysis-file-cache.ts
824
+ var analysisByFile = /* @__PURE__ */ new Map();
825
+ function clearAnalysisFileCache() {
826
+ analysisByFile.clear();
827
+ }
828
+ function getAnalysisFileCacheSize() {
829
+ return analysisByFile.size;
830
+ }
831
+ function trimAnalysisFileCache() {
832
+ const max = getSessionCacheLimits().maxFiles;
833
+ while (analysisByFile.size > max) {
834
+ const oldest = analysisByFile.keys().next().value;
835
+ if (oldest === void 0) break;
836
+ analysisByFile.delete(oldest);
837
+ }
838
+ }
839
+ function analysisCacheGet(filePath, source, auxKey) {
840
+ const e = analysisByFile.get(filePath);
841
+ if (!e) return void 0;
842
+ if (e.source !== source || e.auxKey !== auxKey) return void 0;
843
+ analysisByFile.delete(filePath);
844
+ analysisByFile.set(filePath, e);
845
+ return e.value;
846
+ }
847
+ function analysisCacheSet(filePath, source, auxKey, value) {
848
+ const max = getSessionCacheLimits().maxFiles;
849
+ if (max <= 0) return;
850
+ while (analysisByFile.size >= max && !analysisByFile.has(filePath)) {
851
+ const oldest = analysisByFile.keys().next().value;
852
+ if (oldest === void 0) break;
853
+ analysisByFile.delete(oldest);
854
+ }
855
+ analysisByFile.set(filePath, { source, auxKey, value });
856
+ }
857
+ function evictAnalysisFileCacheForFiles(files) {
858
+ let n = 0;
859
+ for (const f of files) {
860
+ if (analysisByFile.delete(f)) n++;
861
+ }
862
+ return n;
863
+ }
864
+
865
+ // src/fn-analysis-cache.ts
866
+ var fnAnalysisCache = /* @__PURE__ */ new Map();
867
+ function getFnAnalysisCacheSize() {
868
+ return fnAnalysisCache.size;
869
+ }
870
+ function clearFnAnalysisCache() {
871
+ fnAnalysisCache.clear();
872
+ }
873
+ function trimFnAnalysisCache() {
874
+ const max = getSessionCacheLimits().maxFns;
875
+ while (fnAnalysisCache.size > max) {
876
+ const oldest = fnAnalysisCache.keys().next().value;
877
+ if (oldest === void 0) break;
878
+ fnAnalysisCache.delete(oldest);
879
+ }
880
+ }
881
+ function fnAnalysisCacheGet(key) {
882
+ const hit = fnAnalysisCache.get(key);
883
+ if (hit !== void 0) {
884
+ fnAnalysisCache.delete(key);
885
+ fnAnalysisCache.set(key, hit);
886
+ }
887
+ return hit;
888
+ }
889
+ function fnAnalysisCacheSet(key, value) {
890
+ const max = getSessionCacheLimits().maxFns;
891
+ if (max <= 0) return;
892
+ while (fnAnalysisCache.size >= max && !fnAnalysisCache.has(key)) {
893
+ const oldest = fnAnalysisCache.keys().next().value;
894
+ if (oldest === void 0) break;
895
+ fnAnalysisCache.delete(oldest);
896
+ }
897
+ fnAnalysisCache.set(key, value);
898
+ }
899
+ function evictFnAnalysisCacheForFiles(files) {
900
+ if (files.length === 0 || fnAnalysisCache.size === 0) return 0;
901
+ const prefixes = files.map((f) => `${f}\0`);
902
+ let n = 0;
903
+ for (const key of [...fnAnalysisCache.keys()]) {
904
+ if (prefixes.some((p) => key.startsWith(p))) {
905
+ fnAnalysisCache.delete(key);
906
+ n++;
907
+ }
908
+ }
909
+ return n;
910
+ }
911
+ function caseDirectiveKey(directives, formatAbs6) {
912
+ return directives.map((d) => {
913
+ if (d.kind === "case") {
914
+ const args = (d.argsAbs ?? []).map((a) => formatAbs6(a)).join(",");
915
+ return `c:${d.name ?? ""}:${args}`;
916
+ }
917
+ return d.kind;
918
+ }).join("|");
919
+ }
920
+
921
+ // src/bpath-run.ts
922
+ import { runTranspiled, callTranspiledExport, callTranspiledExportFull as callTranspiledExportFull2, setBCallCollector, createEnvironment, formatAbs, getFnImpl } from "@nudojs/core";
923
+ import { setMemberDiagCollector, setAbsTruncationCollector, stableAnalyzeKeySource as stableAnalyzeKeySource2, hashSource as hashSource2, loadModuleDepsFingerprint as loadModuleDepsFingerprint2 } from "@nudojs/core/internal";
924
+ import { parse as parse4, extractInlineDirectives } from "@nudojs/parser";
925
+ function absSeedPart(a) {
926
+ const impl = getFnImpl(a);
927
+ if (impl?.fingerprint) return impl.fingerprint;
928
+ if (a.shape.k === "fn") {
929
+ const ret = impl?.env?.vars?.get("__nudo_mock_ret");
930
+ if (ret) {
931
+ try {
932
+ return `ret=${formatAbs(ret)}`;
933
+ } catch {
934
+ return "ret=?";
935
+ }
936
+ }
937
+ if (impl?.apply) return "apply";
938
+ }
939
+ try {
940
+ return formatAbs(a);
941
+ } catch {
942
+ return "?";
943
+ }
944
+ }
945
+ function seedFnPart(name, fn) {
946
+ if (fn.fingerprint) return `${name}:${fn.fingerprint}`;
947
+ const body = fn.body;
948
+ const bodyKey = body && typeof body.start === "number" && typeof body.end === "number" ? `${body.start}:${body.end}:${body.type ?? ""}` : String(body?.type ?? "?");
949
+ return `${name}(${fn.params.join(",")})@${bodyKey}`;
950
+ }
951
+ function mockSeedFingerprint(mocks, seedFns) {
952
+ const parts = [];
953
+ if (mocks) {
954
+ const names = Object.keys(mocks).sort();
955
+ for (const n of names) parts.push(`${n}=${absSeedPart(mocks[n])}`);
956
+ }
957
+ if (seedFns) {
958
+ const names = Object.keys(seedFns).sort();
959
+ for (const n of names) parts.push(`fn:${seedFnPart(n, seedFns[n])}`);
960
+ }
961
+ if (parts.length === 0) return "-";
962
+ return hashSource2(parts.join(";"));
963
+ }
964
+ function collectEnvGlobals(envNames) {
965
+ if (envNames.length === 0) return {};
966
+ const env = createEnvironment();
967
+ try {
968
+ return { ...loadEnvs(envNames, env).globals };
969
+ } catch {
970
+ return {};
971
+ }
972
+ }
973
+ function collectEnvModules(envNames) {
974
+ if (envNames.length === 0) return {};
975
+ const env = createEnvironment();
976
+ let mods = {};
977
+ try {
978
+ mods = loadEnvs(envNames, env).modules;
979
+ } catch {
980
+ return {};
981
+ }
982
+ const out = {};
983
+ for (const [spec, named] of Object.entries(mods)) {
984
+ out[spec] = { named: { ...named } };
985
+ }
986
+ return out;
987
+ }
988
+ var envHarvestConflictCollector = null;
989
+ function setEnvHarvestConflictCollector(collector) {
990
+ const prev = envHarvestConflictCollector;
991
+ envHarvestConflictCollector = collector;
992
+ return prev;
993
+ }
994
+ function getEnvHarvestConflictCollector() {
995
+ return envHarvestConflictCollector;
996
+ }
997
+ function mergeHarvestUnderEnv(harvestModules, envModules, opts) {
998
+ const out = {};
999
+ for (const [mod, exports] of Object.entries(harvestModules)) {
1000
+ const named = { ...exports.named };
1001
+ out[mod] = exports.default !== void 0 ? { named, default: exports.default } : { named };
1002
+ }
1003
+ const notify = opts?.onConflict ?? envHarvestConflictCollector;
1004
+ for (const [mod, envExports] of Object.entries(envModules)) {
1005
+ const existing = out[mod];
1006
+ if (!existing) {
1007
+ const named2 = { ...envExports.named };
1008
+ out[mod] = envExports.default !== void 0 ? { named: named2, default: envExports.default } : { named: named2 };
1009
+ continue;
1010
+ }
1011
+ const overwritten = [];
1012
+ for (const key of Object.keys(envExports.named)) {
1013
+ if (existing.named[key] !== void 0) overwritten.push(key);
1014
+ }
1015
+ const defaultOverwritten = envExports.default !== void 0 && existing.default !== void 0;
1016
+ if (notify && (overwritten.length > 0 || defaultOverwritten)) {
1017
+ try {
1018
+ notify({
1019
+ module: mod,
1020
+ exports: overwritten,
1021
+ defaultOverwritten
1022
+ });
1023
+ } catch {
1024
+ }
1025
+ }
1026
+ const named = { ...existing.named, ...envExports.named };
1027
+ const defaultAbs = envExports.default !== void 0 ? envExports.default : existing.default;
1028
+ out[mod] = defaultAbs !== void 0 ? { named, default: defaultAbs } : { named };
1029
+ }
1030
+ return out;
1031
+ }
1032
+ function collectBPathReplacements(source) {
1033
+ const targets = [];
1034
+ const values = {};
1035
+ const asTargets = [];
1036
+ const asValues = {};
1037
+ let i = 0;
1038
+ try {
1039
+ const file = parse4(source);
1040
+ const visitStmts = (stmts) => {
1041
+ for (const stmt of stmts) {
1042
+ if (!stmt || typeof stmt !== "object") continue;
1043
+ const loc = stmt.loc;
1044
+ const dirs = extractInlineDirectives(stmt);
1045
+ for (const d of dirs) {
1046
+ if (d.kind === "replace") {
1047
+ const varName = `__rep${i++}`;
1048
+ targets.push({
1049
+ target: d.targetSource,
1050
+ varName,
1051
+ stmtStart: loc?.start.line,
1052
+ stmtEnd: loc?.end.line
1053
+ });
1054
+ values[varName] = d.typeAbs;
1055
+ } else if (d.kind === "as" && loc) {
1056
+ const varName = `__as${i++}`;
1057
+ asTargets.push({
1058
+ varName,
1059
+ stmtStart: loc.start.line,
1060
+ stmtEnd: loc.end.line
1061
+ });
1062
+ asValues[varName] = d.typeAbs;
1063
+ }
1064
+ }
1065
+ const s = stmt;
1066
+ if (s.type === "BlockStatement" && Array.isArray(s.body)) visitStmts(s.body);
1067
+ if (s.type === "FunctionDeclaration" || s.type === "FunctionExpression") {
1068
+ if (s.body) visitStmts([s.body]);
1069
+ }
1070
+ if (s.type === "ExportNamedDeclaration" && s.declaration) {
1071
+ visitStmts([s.declaration]);
1072
+ }
1073
+ if (s.type === "IfStatement") {
1074
+ if (s.consequent) visitStmts([s.consequent]);
1075
+ if (s.alternate) visitStmts([s.alternate]);
1076
+ }
1077
+ }
1078
+ };
1079
+ visitStmts(file.program.body);
1080
+ } catch {
1081
+ }
1082
+ return { targets, values, asTargets, asValues };
1083
+ }
1084
+ function isBPathCapable(source, envNames = []) {
1085
+ void envNames;
1086
+ void source;
1087
+ return true;
1088
+ }
1089
+ var bRunByFile = /* @__PURE__ */ new Map();
1090
+ function bPathDepKey(source, filePath) {
1091
+ try {
1092
+ const fp = loadModuleDepsFingerprint2(source, defaultLoadModule, filePath);
1093
+ if (fp.truncated) return null;
1094
+ return hashSource2(fp.fp);
1095
+ } catch {
1096
+ return null;
1097
+ }
1098
+ }
1099
+ function clearBPathCache() {
1100
+ bRunByFile.clear();
1101
+ clearAnalysisFileCache();
1102
+ clearFnAnalysisCache();
1103
+ }
1104
+ function getBPathCacheSize() {
1105
+ return bRunByFile.size;
1106
+ }
1107
+ function evictBPathCacheForFiles(files) {
1108
+ let n = 0;
1109
+ for (const f of files) {
1110
+ if (bRunByFile.delete(f)) n++;
1111
+ }
1112
+ return n;
1113
+ }
1114
+ function bPathCacheSet(filePath, stableSource, mode, envKey, mockKey, depKey, value) {
1115
+ const max = getSessionCacheLimits().maxBRuns;
1116
+ if (max <= 0) return;
1117
+ while (bRunByFile.size >= max && !bRunByFile.has(filePath)) {
1118
+ const oldest = bRunByFile.keys().next().value;
1119
+ if (oldest === void 0) break;
1120
+ bRunByFile.delete(oldest);
1121
+ }
1122
+ bRunByFile.set(filePath, { stableSource, mode, envKey, mockKey, depKey, value });
1123
+ }
1124
+ function trimBPathCache() {
1125
+ const max = getSessionCacheLimits().maxBRuns;
1126
+ while (bRunByFile.size > max) {
1127
+ const oldest = bRunByFile.keys().next().value;
1128
+ if (oldest === void 0) break;
1129
+ bRunByFile.delete(oldest);
1130
+ }
1131
+ }
1132
+ function tryRunBPath(source, filePath, opts = {}) {
1133
+ if (!isBPathCapable(source, opts.envNames ?? [])) return void 0;
1134
+ const mode = opts.mode ?? "analyze";
1135
+ const envKey = (opts.envNames ?? []).join(",");
1136
+ const mockKey = mockSeedFingerprint(opts.mocks);
1137
+ const stable = stableAnalyzeKeySource2(source);
1138
+ const depKey = bPathDepKey(source, filePath);
1139
+ const canCache = depKey !== null;
1140
+ if (canCache) {
1141
+ const cached = bRunByFile.get(filePath);
1142
+ if (cached && cached.stableSource === stable && cached.mode === mode && cached.envKey === envKey && cached.mockKey === mockKey && cached.depKey === depKey) {
1143
+ bRunByFile.delete(filePath);
1144
+ bRunByFile.set(filePath, cached);
1145
+ return cached.value ?? void 0;
1146
+ }
1147
+ }
1148
+ let out = null;
1149
+ try {
1150
+ const memberDiags = [];
1151
+ const truncated = /* @__PURE__ */ new Set();
1152
+ const topCalls = [];
1153
+ const prevMember = setMemberDiagCollector((d) => memberDiags.push(d));
1154
+ const prevTrunc = setAbsTruncationCollector((label) => truncated.add(label));
1155
+ const prevCall = setBCallCollector((r) => topCalls.push(r));
1156
+ try {
1157
+ const { modules: graphMods, issues } = evalAbsModuleGraph(source, filePath);
1158
+ const envMods = collectEnvModules(opts.envNames ?? []);
1159
+ let modules = mergeHarvestUnderEnv(graphMods, envMods);
1160
+ const mm = applyMockModuleDirectivesFromSource(source, modules, { fromFile: filePath });
1161
+ modules = mm.modules;
1162
+ const { targets, values, asTargets, asValues } = collectBPathReplacements(source);
1163
+ const envGlobals = {
1164
+ ...collectEnvGlobals(opts.envNames ?? []),
1165
+ ...opts.mocks ?? {}
1166
+ };
1167
+ const exports = runTranspiled(source, {
1168
+ modules,
1169
+ maxLoopIters: opts.maxLoopIters,
1170
+ mode: opts.mode ?? "analyze",
1171
+ replacementTargets: targets.length ? targets : void 0,
1172
+ replacements: targets.length ? values : void 0,
1173
+ asOverrideTargets: asTargets.length ? asTargets : void 0,
1174
+ asOverrides: asTargets.length ? asValues : void 0,
1175
+ envGlobals: Object.keys(envGlobals).length ? envGlobals : void 0,
1176
+ lenientGlobals: opts.lenientGlobals
1177
+ });
1178
+ out = {
1179
+ exports,
1180
+ modules,
1181
+ memberDiags: memberDiags.length ? memberDiags : void 0,
1182
+ moduleIssues: issues.length ? issues : void 0,
1183
+ truncatedFns: truncated.size ? [...truncated] : void 0,
1184
+ calls: topCalls.length ? topCalls : void 0
1185
+ };
1186
+ } finally {
1187
+ setMemberDiagCollector(prevMember);
1188
+ setAbsTruncationCollector(prevTrunc);
1189
+ setBCallCollector(prevCall);
1190
+ }
1191
+ } catch {
1192
+ out = null;
1193
+ }
1194
+ if (canCache && depKey !== null) {
1195
+ bPathCacheSet(filePath, stable, mode, envKey, mockKey, depKey, out);
1196
+ }
1197
+ return out ?? void 0;
1198
+ }
1199
+ function tryBPathCallFull(source, filePath, fnName, args, opts = {}) {
1200
+ const run = tryRunBPath(source, filePath, { envNames: opts.envNames, mocks: opts.mocks });
1201
+ if (!run) return void 0;
1202
+ if (!(fnName in run.exports)) return void 0;
1203
+ const collected = [];
1204
+ const memberDiags = [];
1205
+ const prevCall = opts.collectCalls ? setBCallCollector((r) => collected.push(r)) : void 0;
1206
+ const wantMember = opts.collectMemberDiags ?? true;
1207
+ const prevMember = wantMember ? setMemberDiagCollector((d) => memberDiags.push(d)) : void 0;
1208
+ try {
1209
+ const full = callTranspiledExportFull2(run.exports, fnName, args, opts.phi ? { phi: opts.phi } : void 0);
1210
+ const all = [...run.memberDiags ?? [], ...memberDiags];
1211
+ return {
1212
+ ...full,
1213
+ calls: opts.collectCalls ? collected : void 0,
1214
+ memberDiags: all.length ? all : void 0,
1215
+ moduleIssues: run.moduleIssues,
1216
+ truncatedFns: run.truncatedFns
1217
+ };
1218
+ } finally {
1219
+ if (opts.collectCalls) setBCallCollector(prevCall ?? null);
1220
+ if (wantMember) setMemberDiagCollector(prevMember ?? null);
1221
+ }
1222
+ }
1223
+ function tryBPathCall(source, filePath, fnName, args, opts = {}) {
1224
+ const full = tryBPathCallFull(source, filePath, fnName, args, opts);
1225
+ if (!full) return void 0;
1226
+ const r = full.result;
1227
+ if (!r) return void 0;
1228
+ if (r.shape.k === "never" && full.throws.shape.k !== "never") {
1229
+ return void 0;
1230
+ }
1231
+ if (r.shape.k === "unknown" && !r.term) return void 0;
1232
+ return r;
1233
+ }
1234
+
1235
+ // src/mock-abs.ts
1236
+ import { extractDirectives } from "@nudojs/parser";
1237
+ import { parse as parse5, parseCaseArgExpr } from "@nudojs/parser";
1238
+ import {
1239
+ absFunction as absFunction2,
1240
+ emptyEnv,
1241
+ abs as makeAbs2,
1242
+ confJoin,
1243
+ unknown as absUnknown,
1244
+ litValue,
1245
+ formatAbs as formatAbs2,
1246
+ getFnImpl as getFnImpl2,
1247
+ bindingsOf as bindingsOf2,
1248
+ callTranspiledExportFull as callTranspiledExportFull3
1249
+ } from "@nudojs/core";
1250
+ function markMockConf(a) {
1251
+ a.conf = confJoin(a.conf, "mock");
1252
+ return a;
1253
+ }
1254
+ function astContentKey(n, depth = 0) {
1255
+ if (n == null || depth > 24) return "";
1256
+ if (typeof n !== "object") return String(n);
1257
+ if (Array.isArray(n)) return n.map((x) => astContentKey(x, depth + 1)).join(",");
1258
+ const obj = n;
1259
+ const type = typeof obj.type === "string" ? obj.type : "";
1260
+ const parts = [type];
1261
+ for (const key of ["name", "value", "raw", "operator", "computed"]) {
1262
+ const v = obj[key];
1263
+ if (v !== void 0 && (typeof v !== "object" || v === null)) parts.push(`${key}=${String(v)}`);
1264
+ }
1265
+ for (const key of Object.keys(obj)) {
1266
+ if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments" || key === "innerComments" || key === "type" || key === "name" || key === "value" || key === "raw" || key === "operator" || key === "computed") {
1267
+ continue;
1268
+ }
1269
+ const v = obj[key];
1270
+ if (v && typeof v === "object") parts.push(`${key}:{${astContentKey(v, depth + 1)}}`);
1271
+ }
1272
+ return parts.join("|");
1273
+ }
1274
+ function stampFingerprint(a, fp) {
1275
+ const impl = getFnImpl2(a);
1276
+ if (impl) impl.fingerprint = fp;
1277
+ return a;
1278
+ }
1279
+ function constantMockFn(result) {
1280
+ const marked = markMockConf(result);
1281
+ const mockEnv = emptyEnv();
1282
+ const retName = "__nudo_mock_ret";
1283
+ mockEnv.vars.set(retName, marked);
1284
+ const body = {
1285
+ type: "BlockStatement",
1286
+ body: [
1287
+ {
1288
+ type: "ReturnStatement",
1289
+ argument: { type: "Identifier", name: retName }
1290
+ }
1291
+ ]
1292
+ };
1293
+ let retKey;
1294
+ try {
1295
+ retKey = formatAbs2(marked);
1296
+ } catch {
1297
+ retKey = "?";
1298
+ }
1299
+ return stampFingerprint(
1300
+ markMockConf(absFunction2(["...args"], { body, env: mockEnv })),
1301
+ `ret=${retKey}`
1302
+ );
1303
+ }
1304
+ function absArgMatches(declared, actual) {
1305
+ if (!actual) return false;
1306
+ const av = litValue(actual);
1307
+ const dv = litValue(declared);
1308
+ if (dv !== void 0) {
1309
+ return av !== void 0 && Object.is(av, dv);
1310
+ }
1311
+ if (declared.shape.k === "prim") {
1312
+ if (actual.shape.k === "prim") return actual.shape.type === declared.shape.type;
1313
+ if (av !== void 0) {
1314
+ const t = typeof av;
1315
+ return declared.shape.type === "number" && t === "number" || declared.shape.type === "string" && t === "string" || declared.shape.type === "boolean" && t === "boolean" || declared.shape.type === "bigint" && t === "bigint";
1316
+ }
1317
+ }
1318
+ return false;
1319
+ }
1320
+ function dispatchMockFn(defaultReturn, cases) {
1321
+ if (!cases?.length) return constantMockFn(defaultReturn);
1322
+ const caseAbs = cases.map((c) => ({
1323
+ declared: c.args,
1324
+ result: markMockConf(c.returnValue)
1325
+ }));
1326
+ const dummyBody = {
1327
+ type: "BlockStatement",
1328
+ body: [{ type: "ReturnStatement", argument: null }]
1329
+ };
1330
+ const markedDefault = markMockConf(defaultReturn);
1331
+ let defaultKey;
1332
+ try {
1333
+ defaultKey = formatAbs2(markedDefault);
1334
+ } catch {
1335
+ defaultKey = "?";
1336
+ }
1337
+ const caseKey = cases.map(
1338
+ (c) => `${c.args.map((a) => formatAbs2(a)).join(",")}->${formatAbs2(c.returnValue)}`
1339
+ ).join("|");
1340
+ return stampFingerprint(
1341
+ markMockConf(
1342
+ absFunction2(["...args"], {
1343
+ body: dummyBody,
1344
+ env: emptyEnv(),
1345
+ apply: (args) => {
1346
+ for (const c of caseAbs) {
1347
+ if (c.declared.every((d, i) => absArgMatches(d, args[i]))) {
1348
+ return c.result;
1349
+ }
1350
+ }
1351
+ return markedDefault;
1352
+ }
1353
+ })
1354
+ ),
1355
+ `dispatch=default=${defaultKey};cases=${caseKey}`
1356
+ );
1357
+ }
1358
+ function absFromMockHelper(h) {
1359
+ if (h.callsFakeImpl) {
1360
+ const { params, body, async } = h.callsFakeImpl;
1361
+ return stampFingerprint(
1362
+ absFunction2(params, { body, async: async ?? false }),
1363
+ `fake=${params.join(",")}:${astContentKey(body)}`
1364
+ );
1365
+ }
1366
+ let defaultReturn;
1367
+ if (h.resolvedValue) {
1368
+ defaultReturn = makeAbs2(
1369
+ { k: "eff", eff: "promise", inner: h.resolvedValue },
1370
+ void 0,
1371
+ void 0,
1372
+ "path"
1373
+ );
1374
+ } else if (h.rejectedValue) {
1375
+ defaultReturn = makeAbs2({ k: "never" }, void 0, void 0, "exact");
1376
+ } else if (h.returnValue) {
1377
+ defaultReturn = h.returnValue;
1378
+ } else if (h.onFirstCallValue) {
1379
+ defaultReturn = h.onFirstCallValue;
1380
+ } else {
1381
+ defaultReturn = absUnknown;
1382
+ }
1383
+ return dispatchMockFn(defaultReturn, h.withArgsCases);
1384
+ }
1385
+ function absFromSinon(sinonExpr) {
1386
+ if (sinonExpr.resolvedValue) {
1387
+ return constantMockFn(
1388
+ makeAbs2(
1389
+ { k: "eff", eff: "promise", inner: sinonExpr.resolvedValue },
1390
+ void 0,
1391
+ void 0,
1392
+ "path"
1393
+ )
1394
+ );
1395
+ }
1396
+ if (sinonExpr.rejectedValue) {
1397
+ return constantMockFn(makeAbs2({ k: "never" }, void 0, void 0, "exact"));
1398
+ }
1399
+ if (sinonExpr.returnValue) {
1400
+ return constantMockFn(sinonExpr.returnValue);
1401
+ }
1402
+ return constantMockFn(absUnknown);
1403
+ }
1404
+ function isAbsVal2(v) {
1405
+ return !!v && typeof v === "object" && "shape" in v && "conf" in v;
1406
+ }
1407
+ function loadFromMockBinding(name, fromPath, fromFile, loadModule) {
1408
+ const base = fromFile ?? `${process.cwd()}/<mock-from>`;
1409
+ const evaled = evalMockFileWithDeps(fromPath, base, loadModule);
1410
+ if (!evaled.ok) {
1411
+ return {
1412
+ error: evaled.error.includes("not found") ? `Mock file not found for '${name}' (from "${fromPath}")` : evaled.error
1413
+ };
1414
+ }
1415
+ const run = evaled.run;
1416
+ let val = run[name];
1417
+ if (val === void 0) {
1418
+ const binds = bindingsOf2(run);
1419
+ val = binds?.get(name);
1420
+ }
1421
+ if (val === void 0) {
1422
+ return {
1423
+ error: `Mock file "${fromPath}" does not define a binding named '${name}'`
1424
+ };
1425
+ }
1426
+ let absVal;
1427
+ if (isAbsVal2(val)) {
1428
+ absVal = val;
1429
+ } else if (typeof val === "function") {
1430
+ const fn = val;
1431
+ const params = Array.from({ length: fn.length ?? 0 }, (_, i) => `arg${i}`);
1432
+ absVal = absFunction2(params, {
1433
+ apply: (args) => callTranspiledExportFull3(run, name, args).result,
1434
+ kind: "bpath-export",
1435
+ fingerprint: `from-mock=${fromPath}#${name}`
1436
+ });
1437
+ } else {
1438
+ absVal = absUnknown;
1439
+ }
1440
+ return { abs: markMockConf(absVal) };
1441
+ }
1442
+ function mockSeedsToAbsMocks(seeds) {
1443
+ const out = { ...seeds.seedVars };
1444
+ for (const [name, fn] of Object.entries(seeds.seedFns)) {
1445
+ out[name] = absFunction2(fn.params, { body: fn.body, async: fn.async ?? false });
1446
+ }
1447
+ return out;
1448
+ }
1449
+ function mockDirectivesToAbsSeeds(functions, opts) {
1450
+ const seedVars = {};
1451
+ const seedFns = {};
1452
+ const fromErrors = [];
1453
+ for (const fn of functions) {
1454
+ for (const d of fn.directives) {
1455
+ if (d.kind !== "mock") continue;
1456
+ if (d.fromPath) {
1457
+ const r = loadFromMockBinding(d.name, d.fromPath, opts?.fromFile, opts?.loadModule);
1458
+ if (r.abs) {
1459
+ seedVars[d.name] = r.abs;
1460
+ } else {
1461
+ fromErrors.push({
1462
+ name: d.name,
1463
+ fromPath: d.fromPath,
1464
+ message: r.error ?? `Mock file "${d.fromPath}" could not be loaded`
1465
+ });
1466
+ }
1467
+ continue;
1468
+ }
1469
+ if (d.arrowFn) {
1470
+ const body = d.arrowFn.body;
1471
+ seedFns[d.name] = {
1472
+ params: d.arrowFn.params,
1473
+ body,
1474
+ async: false,
1475
+ fingerprint: `fake=${d.arrowFn.params.join(",")}:${astContentKey(body)}`
1476
+ };
1477
+ } else if (d.nudoMock) {
1478
+ seedVars[d.name] = markMockConf(absFromMockHelper(d.nudoMock));
1479
+ } else if (d.sinonExpr) {
1480
+ seedVars[d.name] = markMockConf(absFromSinon(d.sinonExpr));
1481
+ } else if (d.expression) {
1482
+ try {
1483
+ seedVars[d.name] = parseCaseArgExpr(d.expression);
1484
+ } catch {
1485
+ seedVars[d.name] = absUnknown;
1486
+ }
1487
+ }
1488
+ }
1489
+ }
1490
+ return { seedVars, seedFns, ...fromErrors.length > 0 ? { fromErrors } : {} };
1491
+ }
1492
+ function mockSeedsForSource(source, opts) {
1493
+ const fns = extractDirectives(parse5(source));
1494
+ return mockSeedsToAbsMocks(mockDirectivesToAbsSeeds(fns, opts));
1495
+ }
1496
+
1497
+ // src/analyzer-orchestrate-calls.ts
1498
+ import { dirname as dirname6 } from "path";
1499
+ import {
1500
+ getBCallCollector,
1501
+ getFnImpl as getFnImpl4,
1502
+ setBCallCollector as setBCallCollector2,
1503
+ $call as $call2,
1504
+ unknown as absUnknown3
1505
+ } from "@nudojs/core";
1506
+ import { parse as parse7, extractFileDirectives as extractFileDirectives2 } from "@nudojs/parser";
1507
+
1508
+ // src/analyzer-abs-eval.ts
1509
+ import { existsSync as existsSync4, statSync as statSync5 } from "fs";
1510
+ import { resolve as resolve5, dirname as dirname5 } from "path";
1511
+ import {
1512
+ generalizeFromAst,
1513
+ termToString,
1514
+ predToString,
1515
+ unknown as absUnknown2,
1516
+ absFunction as absFunction3,
1517
+ formatAbs as formatAbs4,
1518
+ formatAbsMultiline
1519
+ } from "@nudojs/core";
1520
+ import { parse as parse6 } from "@nudojs/parser";
1521
+
1522
+ // src/evaluator/call-record.ts
1523
+ import { formatAbs as formatAbs3, joinAbs, litValue as litValue2 } from "@nudojs/core";
1524
+ var neverAbs = { shape: { k: "never" }, conf: "exact" };
1525
+ var undefAbs = {
1526
+ shape: { k: "unknown" },
1527
+ term: { op: "lit", value: void 0 },
1528
+ conf: "exact"
1529
+ };
1530
+ function absStructureKey(a) {
1531
+ const v = litValue2(a);
1532
+ if (v !== void 0) return `L:${typeof v}:${String(v)}`;
1533
+ if (a.term?.op === "lit") return `L:${typeof a.term.value}:${String(a.term.value)}`;
1534
+ try {
1535
+ return formatAbs3(a);
1536
+ } catch {
1537
+ return a.shape.k;
1538
+ }
1539
+ }
1540
+ function shapeNodeCount(a, seen) {
1541
+ if (!a || typeof a !== "object") return 1;
1542
+ if (seen.has(a)) return 0;
1543
+ seen.add(a);
1544
+ const s = a.shape;
1545
+ switch (s.k) {
1546
+ case "arr":
1547
+ return 1 + shapeNodeCount(s.element, seen);
1548
+ case "tuple":
1549
+ return 1 + s.elements.reduce((acc, e) => acc + shapeNodeCount(e, seen), 0);
1550
+ case "obj": {
1551
+ let n = 1;
1552
+ for (const slot of Object.values(s.slots)) n += shapeNodeCount(slot.value, seen);
1553
+ return n;
1554
+ }
1555
+ case "eff":
1556
+ return 1 + shapeNodeCount(s.inner, seen);
1557
+ case "sum":
1558
+ return 1 + s.members.reduce((acc, e) => acc + shapeNodeCount(e, seen), 0);
1559
+ case "brand":
1560
+ return 1 + shapeNodeCount(s.shape, seen);
1561
+ case "fn": {
1562
+ let n = 1;
1563
+ for (const p of s.paramTypes ?? []) n += shapeNodeCount(p, seen);
1564
+ if (s.returnType) n += shapeNodeCount(s.returnType, seen);
1565
+ return n;
1566
+ }
1567
+ default:
1568
+ return 1;
1569
+ }
1570
+ }
1571
+ var MAX_RECORD_TYPE_NODES = 2e3;
1572
+ function absNodeCount(a, seen = /* @__PURE__ */ new Set()) {
1573
+ return shapeNodeCount(a, seen);
1574
+ }
1575
+ function isOversizedCallRecord(rec) {
1576
+ const seen = /* @__PURE__ */ new Set();
1577
+ for (const a of rec.argAbs) {
1578
+ if (absNodeCount(a, seen) > MAX_RECORD_TYPE_NODES) return true;
1579
+ }
1580
+ return absNodeCount(rec.resultAbs, seen) > MAX_RECORD_TYPE_NODES;
1581
+ }
1582
+ function isLeakedCallRecord(rec) {
1583
+ return rec.resultAbs.shape.k === "never" && rec.throwsAbs.shape.k === "never";
1584
+ }
1585
+ function joinAllAbs(members) {
1586
+ if (members.length === 0) return neverAbs;
1587
+ return members.reduce((a, b) => joinAbs(a, b));
1588
+ }
1589
+ function isPrimLit(a) {
1590
+ return a.shape.k === "prim" && a.term?.op === "lit";
1591
+ }
1592
+ function widenAbsPrim(a) {
1593
+ if (a.shape.k === "prim" && a.term?.op === "lit") {
1594
+ return { shape: a.shape, conf: a.conf === "exact" ? "path" : a.conf };
1595
+ }
1596
+ return a;
1597
+ }
1598
+ function collapseAbsLits(members, maxLits) {
1599
+ const uniq = [];
1600
+ const seen = /* @__PURE__ */ new Set();
1601
+ for (const m of members) {
1602
+ if (m.shape.k === "never") continue;
1603
+ const k = absStructureKey(m);
1604
+ if (seen.has(k)) continue;
1605
+ seen.add(k);
1606
+ uniq.push(m);
1607
+ }
1608
+ if (uniq.length === 0) return neverAbs;
1609
+ if (uniq.length === 1) return uniq[0];
1610
+ const nonLitPrims = /* @__PURE__ */ new Set();
1611
+ for (const m of uniq) {
1612
+ if (m.shape.k === "prim" && m.term?.op !== "lit") {
1613
+ nonLitPrims.add(m.shape.type);
1614
+ }
1615
+ }
1616
+ if (nonLitPrims.size > 0) {
1617
+ const absorbed = uniq.filter(
1618
+ (m) => !(m.shape.k === "prim" && m.term?.op === "lit" && nonLitPrims.has(m.shape.type))
1619
+ );
1620
+ if (absorbed.length !== uniq.length) {
1621
+ return collapseAbsLits(absorbed, maxLits);
1622
+ }
1623
+ }
1624
+ if (uniq.length > maxLits && uniq.every(isPrimLit)) {
1625
+ const t = uniq[0].shape.type;
1626
+ if (uniq.every((m) => m.shape.k === "prim" && m.shape.type === t)) {
1627
+ return { shape: { k: "prim", type: t }, conf: "path" };
1628
+ }
1629
+ }
1630
+ return { shape: { k: "sum", members: uniq }, conf: "exact" };
1631
+ }
1632
+ function widenJoinAbs(members) {
1633
+ return widenAbsPrim(joinAllAbs(members));
1634
+ }
1635
+
1636
+ // src/analyzer-abs-eval.ts
1637
+ function absIsBetter(next, prev) {
1638
+ const isBareUnknown = (a) => a.shape.k === "unknown" && !a.term;
1639
+ const isNever = (a) => a.shape.k === "never";
1640
+ const isStructured = (a) => a.term?.op === "lit" || a.shape.k === "obj" || a.shape.k === "arr" || a.shape.k === "tuple" || a.shape.k === "sum" || a.shape.k === "fn" || a.shape.k === "brand" || a.shape.k === "eff";
1641
+ const hasPred = (a) => !!a.pred && a.pred.op !== "true";
1642
+ if (isBareUnknown(prev)) return !isBareUnknown(next);
1643
+ if (isBareUnknown(next) || isNever(next)) return false;
1644
+ if (isStructured(prev)) return false;
1645
+ if (hasPred(next) && prev.shape.k === "prim" && !hasPred(prev)) return true;
1646
+ return false;
1647
+ }
1648
+ function isSelfContainedSource(source, envNames) {
1649
+ if (envNames.length > 0) return false;
1650
+ return !/\brequire\s*\(|\bimport\s*[{'"*]/.test(source);
1651
+ }
1652
+ function absModulesOk(source, envNames) {
1653
+ void source;
1654
+ void envNames;
1655
+ return true;
1656
+ }
1657
+ function buildAbsImportLocalMap(source, fromFile) {
1658
+ const out = /* @__PURE__ */ new Map();
1659
+ try {
1660
+ const file = parse6(source);
1661
+ for (const stmt of file.program.body) {
1662
+ if (stmt.type === "ImportDeclaration") {
1663
+ const spec = stmt.source.value;
1664
+ let modulePath = null;
1665
+ if (spec.startsWith(".") || spec.startsWith("/")) {
1666
+ modulePath = resolveImportAbs(spec, fromFile);
1667
+ } else if (!spec.startsWith("node:")) {
1668
+ modulePath = spec;
1669
+ }
1670
+ if (!modulePath) continue;
1671
+ for (const s of stmt.specifiers) {
1672
+ if (s.type === "ImportSpecifier") {
1673
+ const imported = s.imported.type === "Identifier" ? s.imported.name : String(s.imported);
1674
+ out.set(s.local.name, { modulePath, exportName: imported });
1675
+ } else if (s.type === "ImportDefaultSpecifier") {
1676
+ out.set(s.local.name, { modulePath, exportName: "default" });
1677
+ } else if (s.type === "ImportNamespaceSpecifier") {
1678
+ out.set(s.local.name, { modulePath, exportName: "*" });
1679
+ }
1680
+ }
1681
+ continue;
1682
+ }
1683
+ if (stmt.type === "VariableDeclaration") {
1684
+ for (const d of stmt.declarations) {
1685
+ if (d.init?.type !== "CallExpression") continue;
1686
+ const callee = d.init.callee;
1687
+ if (callee.type !== "Identifier" || callee.name !== "require") continue;
1688
+ const arg0 = d.init.arguments[0];
1689
+ if (!arg0 || arg0.type !== "StringLiteral") continue;
1690
+ const spec = arg0.value;
1691
+ let modulePath = null;
1692
+ if (spec.startsWith(".") || spec.startsWith("/")) {
1693
+ modulePath = resolveImportAbs(spec, fromFile);
1694
+ } else if (!spec.startsWith("node:")) {
1695
+ modulePath = spec;
1696
+ }
1697
+ if (!modulePath) continue;
1698
+ if (d.id.type === "ObjectPattern") {
1699
+ for (const p of d.id.properties) {
1700
+ if (p.type !== "ObjectProperty" || p.computed) continue;
1701
+ if (p.key.type !== "Identifier" || p.value.type !== "Identifier") continue;
1702
+ out.set(p.value.name, { modulePath, exportName: p.key.name });
1703
+ }
1704
+ } else if (d.id.type === "Identifier") {
1705
+ out.set(d.id.name, { modulePath, exportName: "default" });
1706
+ }
1707
+ }
1708
+ }
1709
+ }
1710
+ } catch {
1711
+ }
1712
+ return out;
1713
+ }
1714
+ function resolveImportAbs(spec, fromFile) {
1715
+ const base = dirname5(resolve5(fromFile));
1716
+ const p = resolve5(base, spec);
1717
+ for (const cand of [p, `${p}.js`, `${p}.mjs`, `${p}.ts`, resolve5(p, "index.js")]) {
1718
+ if (existsSync4(cand) && !statSync5(cand).isDirectory()) return cand;
1719
+ }
1720
+ return null;
1721
+ }
1722
+ function tryEvalAbsRaw(source, fnName, args, filePath, mocks, assignedName) {
1723
+ return tryEvalAbsFull(source, fnName, args, filePath, mocks, assignedName)?.result;
1724
+ }
1725
+ function tryEvalAbsFull(source, fnName, args, filePath, mocks, assignedName) {
1726
+ if (/\brequire\s*\(/.test(source)) return void 0;
1727
+ try {
1728
+ if (filePath) {
1729
+ const viaB = tryBPathCallFull(source, filePath, fnName, args, { mocks }) ?? (assignedName ? tryBPathCallFull(source, filePath, assignedName, args, { mocks }) : void 0);
1730
+ if (viaB) {
1731
+ const r = viaB.result;
1732
+ if (r && !(r.shape.k === "unknown" && !r.term)) {
1733
+ const throws = viaB.throws ?? { shape: { k: "never" }, conf: "exact" };
1734
+ return { result: r, throws };
1735
+ }
1736
+ }
1737
+ }
1738
+ return void 0;
1739
+ } catch {
1740
+ return void 0;
1741
+ }
1742
+ }
1743
+ function tryEvalEntryAbs(source, fnName, args, filePath, mocks, assignedName) {
1744
+ return tryEvalAbsRaw(source, fnName, args, filePath, mocks, assignedName);
1745
+ }
1746
+ function tryAttachIntension(caseResult, source, fnName) {
1747
+ try {
1748
+ const g = generalizeFromAst(fnName, source);
1749
+ if (!g) return;
1750
+ const sym = g.symbolic;
1751
+ const intension = {
1752
+ display: g.display,
1753
+ abs: formatAbs4(sym),
1754
+ absMultiline: formatAbsMultiline(sym, fnName)
1755
+ };
1756
+ if (sym.term && sym.term.op !== "lit") {
1757
+ intension.term = termToString(sym.term);
1758
+ }
1759
+ if (sym.pred && sym.pred.op !== "true") {
1760
+ intension.pred = predToString(sym.pred);
1761
+ }
1762
+ intension.conf = sym.conf;
1763
+ caseResult.intension = intension;
1764
+ } catch {
1765
+ }
1766
+ }
1767
+ function attachHofSnapshot(analysis, source) {
1768
+ if (analysis.hof) return;
1769
+ try {
1770
+ const g = generalizeFromAst(analysis.name, source);
1771
+ if (!g) return;
1772
+ const fnRels = g.fnRels ? [...g.fnRels.entries()].map(([param, rec]) => ({ param, abs: rec.abs })) : void 0;
1773
+ const entryShapes = g.entryShapes ? [...g.entryShapes.entries()].map(([param, rec]) => ({ param, abs: rec.abs })) : void 0;
1774
+ if (!fnRels?.length && !entryShapes?.length) return;
1775
+ analysis.hof = {
1776
+ ...fnRels?.length ? { fnRels } : {},
1777
+ ...entryShapes?.length ? { entryShapes } : {},
1778
+ symbolic: g.symbolic
1779
+ };
1780
+ } catch {
1781
+ }
1782
+ }
1783
+ function attachAbsToIntension(caseResult, absVal, label) {
1784
+ const prev = caseResult.intension ?? {};
1785
+ caseResult.abs = absVal;
1786
+ caseResult.intension = {
1787
+ ...prev,
1788
+ abs: formatAbs4(absVal),
1789
+ absMultiline: formatAbsMultiline(absVal, label ?? caseResult.name),
1790
+ conf: absVal.conf
1791
+ };
1792
+ }
1793
+ function safeAbsOrUnknown(a) {
1794
+ if (a && typeof a === "object" && "shape" in a && "conf" in a) {
1795
+ return a;
1796
+ }
1797
+ if (typeof a === "function") {
1798
+ const fn = a;
1799
+ const n = Math.max(0, fn.length);
1800
+ const params = Array.from({ length: n }, (_, i) => `arg${i}`);
1801
+ return absFunction3(params, {
1802
+ body: { type: "BlockStatement", body: [], directives: [] },
1803
+ apply: (args) => {
1804
+ try {
1805
+ const r = fn(...args);
1806
+ if (r && typeof r === "object" && "shape" in r) return r;
1807
+ return absUnknown2;
1808
+ } catch {
1809
+ return absUnknown2;
1810
+ }
1811
+ }
1812
+ });
1813
+ }
1814
+ return absUnknown2;
1815
+ }
1816
+ function callRecordFromAbsCall(r, impMap) {
1817
+ const argAbs = r.args.map(safeAbsOrUnknown);
1818
+ const threw = !!r.threw;
1819
+ const thrownOrResult = safeAbsOrUnknown(r.result);
1820
+ const rec = {
1821
+ fnName: r.fnName,
1822
+ argAbs,
1823
+ resultAbs: threw ? neverAbs : thrownOrResult,
1824
+ throwsAbs: threw ? thrownOrResult : neverAbs,
1825
+ callLoc: r.callLoc
1826
+ };
1827
+ const imp = impMap?.get(r.fnName) ?? // `ns.helper` / `Class.method`:先整名,再按首段点号拆命名空间成员
1828
+ (() => {
1829
+ const dot = r.fnName.indexOf(".");
1830
+ if (dot <= 0) return void 0;
1831
+ const base = impMap?.get(r.fnName.slice(0, dot));
1832
+ if (!base || base.exportName !== "*") return void 0;
1833
+ return {
1834
+ modulePath: base.modulePath,
1835
+ exportName: r.fnName.slice(dot + 1)
1836
+ };
1837
+ })();
1838
+ if (imp) {
1839
+ rec.targetModule = imp.modulePath;
1840
+ rec.targetExport = imp.exportName;
1841
+ }
1842
+ return rec;
1843
+ }
1844
+
1845
+ // src/analyzer-orchestrate-calls.ts
1846
+ function collectEnvNames(filePath, source, includeProject) {
1847
+ const ast = parse7(source);
1848
+ const fileDirectives = extractFileDirectives2(ast);
1849
+ const fileEnvNames = fileDirectives.filter((d) => d.kind === "env").flatMap((d) => d.envs);
1850
+ if (!includeProject) return fileEnvNames;
1851
+ const projectConfig = findProjectConfig(dirname6(filePath));
1852
+ const projectEnvNames = projectConfig?.config.env ?? [];
1853
+ return [.../* @__PURE__ */ new Set([...projectEnvNames, ...fileEnvNames])];
1854
+ }
1855
+ function collectCallRecords(filePath, source) {
1856
+ if (filePath) {
1857
+ try {
1858
+ const mocks = mockSeedsForSource(source, {
1859
+ fromFile: filePath || void 0
1860
+ });
1861
+ const run = tryRunBPath(source, filePath, {
1862
+ mode: "exec",
1863
+ lenientGlobals: true,
1864
+ ...Object.keys(mocks).length > 0 ? { mocks } : {}
1865
+ });
1866
+ if (run?.calls?.length) {
1867
+ const importLocals = buildAbsImportLocalMap(source, filePath);
1868
+ return expandTestCallbacks(run.calls).map((r) => callRecordFromAbsCall(r, importLocals));
1869
+ }
1870
+ } catch {
1871
+ }
1872
+ }
1873
+ return [];
1874
+ }
1875
+ function expandTestCallbacks(calls) {
1876
+ const out = [...calls];
1877
+ const seenDescribe = /* @__PURE__ */ new Set();
1878
+ let cursor = 0;
1879
+ let expanded = 0;
1880
+ while (cursor < out.length && expanded < 500) {
1881
+ const rec = out[cursor++];
1882
+ if (rec.fnName !== "it" && rec.fnName !== "test" && rec.fnName !== "describe") continue;
1883
+ for (const a of rec.args) {
1884
+ if (!a || typeof a !== "object" || !("shape" in a)) continue;
1885
+ const abs = a;
1886
+ if (abs.shape.k !== "fn") continue;
1887
+ if (rec.fnName === "describe") {
1888
+ if (seenDescribe.has(abs)) continue;
1889
+ seenDescribe.add(abs);
1890
+ }
1891
+ const impl = getFnImpl4(abs);
1892
+ const params = impl?.params ?? [];
1893
+ const collected = [];
1894
+ const prev = getBCallCollector();
1895
+ setBCallCollector2((r) => collected.push(r));
1896
+ try {
1897
+ $call2(abs, params.map(() => absUnknown3));
1898
+ } catch {
1899
+ } finally {
1900
+ setBCallCollector2(prev);
1901
+ }
1902
+ out.push(...collected);
1903
+ expanded++;
1904
+ }
1905
+ }
1906
+ return out;
1907
+ }
1908
+
1909
+ // src/bpath-diagnostics.ts
1910
+ import { parseSource } from "@nudojs/core";
1911
+ function locOf(node) {
1912
+ if (!node.loc) return null;
1913
+ return {
1914
+ start: { line: node.loc.start.line, column: node.loc.start.column },
1915
+ end: { line: node.loc.end.line, column: node.loc.end.column }
1916
+ };
1917
+ }
1918
+ function isExit(stmt) {
1919
+ return stmt.type === "ReturnStatement" || stmt.type === "ThrowStatement";
1920
+ }
1921
+ function walkUnreachable(node, out) {
1922
+ const n = node;
1923
+ const checkBlock = (block) => {
1924
+ if (!block) return;
1925
+ const body = block.body ?? (Array.isArray(block) ? block : null);
1926
+ if (!Array.isArray(body)) return;
1927
+ for (let i = 0; i < body.length; i++) {
1928
+ walkUnreachable(body[i], out);
1929
+ if (isExit(body[i])) {
1930
+ for (let j = i + 1; j < body.length; j++) {
1931
+ const r = locOf(body[j]);
1932
+ if (r) out.push({ range: r });
1933
+ }
1934
+ break;
1935
+ }
1936
+ }
1937
+ };
1938
+ switch (n.type) {
1939
+ case "Program":
1940
+ case "BlockStatement":
1941
+ checkBlock(n.body);
1942
+ break;
1943
+ case "FunctionDeclaration":
1944
+ case "FunctionExpression":
1945
+ case "ArrowFunctionExpression":
1946
+ if (n.body) walkUnreachable(n.body, out);
1947
+ break;
1948
+ case "IfStatement":
1949
+ if (n.consequent) walkUnreachable(n.consequent, out);
1950
+ if (n.alternate) walkUnreachable(n.alternate, out);
1951
+ break;
1952
+ case "ForStatement":
1953
+ case "ForInStatement":
1954
+ case "ForOfStatement":
1955
+ case "WhileStatement":
1956
+ case "DoWhileStatement":
1957
+ if (n.body) walkUnreachable(n.body, out);
1958
+ break;
1959
+ case "TryStatement":
1960
+ if (n.block) walkUnreachable(n.block, out);
1961
+ if (n.handler?.body) walkUnreachable(n.handler.body, out);
1962
+ if (n.finalizer) walkUnreachable(n.finalizer, out);
1963
+ break;
1964
+ case "SwitchStatement":
1965
+ for (const c of n.cases ?? []) {
1966
+ checkBlock(c.consequent);
1967
+ }
1968
+ break;
1969
+ case "ExportNamedDeclaration":
1970
+ case "ExportDefaultDeclaration": {
1971
+ const decl = n.declaration;
1972
+ if (decl) walkUnreachable(decl, out);
1973
+ break;
1974
+ }
1975
+ default:
1976
+ break;
1977
+ }
1978
+ }
1979
+ function collectDeclared(file) {
1980
+ const names = /* @__PURE__ */ new Set();
1981
+ const addId = (id) => {
1982
+ if (!id) return;
1983
+ if (id.type === "Identifier") names.add(id.name);
1984
+ if (id.type === "ObjectPattern") {
1985
+ for (const p of id.properties) {
1986
+ addId(p.value);
1987
+ }
1988
+ }
1989
+ if (id.type === "ArrayPattern") {
1990
+ for (const el of id.elements) addId(el);
1991
+ }
1992
+ };
1993
+ const visit = (node) => {
1994
+ if (!node || typeof node !== "object") return;
1995
+ const o = node;
1996
+ switch (o.type) {
1997
+ case "FunctionDeclaration":
1998
+ case "ClassDeclaration":
1999
+ addId(o.id);
2000
+ break;
2001
+ case "VariableDeclarator":
2002
+ addId(o.id);
2003
+ break;
2004
+ case "ImportSpecifier":
2005
+ case "ImportDefaultSpecifier":
2006
+ case "ImportNamespaceSpecifier":
2007
+ addId(o.local);
2008
+ break;
2009
+ case "CatchClause":
2010
+ addId(o.param);
2011
+ break;
2012
+ default:
2013
+ break;
2014
+ }
2015
+ if (o.params && Array.isArray(o.params)) {
2016
+ for (const p of o.params) addId(p);
2017
+ }
2018
+ for (const key of Object.keys(o)) {
2019
+ if (key === "loc" || key === "start" || key === "end") continue;
2020
+ const v = o[key];
2021
+ if (Array.isArray(v)) v.forEach(visit);
2022
+ else if (v && typeof v === "object") visit(v);
2023
+ }
2024
+ };
2025
+ visit(file);
2026
+ return names;
2027
+ }
2028
+ var KNOWN_GLOBALS = /* @__PURE__ */ new Set([
2029
+ "Object",
2030
+ "Array",
2031
+ "String",
2032
+ "Number",
2033
+ "Boolean",
2034
+ "Symbol",
2035
+ "Math",
2036
+ "JSON",
2037
+ "Promise",
2038
+ "Error",
2039
+ "TypeError",
2040
+ "RangeError",
2041
+ "SyntaxError",
2042
+ "RegExp",
2043
+ "Date",
2044
+ "Map",
2045
+ "Set",
2046
+ "WeakMap",
2047
+ "WeakSet",
2048
+ "console",
2049
+ "undefined",
2050
+ "NaN",
2051
+ "Infinity",
2052
+ "globalThis",
2053
+ "parseInt",
2054
+ "parseFloat",
2055
+ "isNaN",
2056
+ "isFinite"
2057
+ ]);
2058
+ function isStaticStringExpr(node) {
2059
+ if (!node || typeof node !== "object") return false;
2060
+ const n = node;
2061
+ if (n.type === "StringLiteral" || n.type === "NumericLiteral") return true;
2062
+ if (n.type === "TemplateLiteral") {
2063
+ return (n.expressions ?? []).every((e) => isStaticStringExpr(e));
2064
+ }
2065
+ if (n.type === "BinaryExpression" && n.operator === "+") {
2066
+ return isStaticStringExpr(n.left) && isStaticStringExpr(n.right);
2067
+ }
2068
+ return false;
2069
+ }
2070
+ function isFreeUnknown(name, declared, seen, known) {
2071
+ return !declared.has(name) && !known.has(name) && !name.startsWith("$") && !seen.has(name);
2072
+ }
2073
+ function walkBuiltinUnknown(node, declared, out, seen, known, parentKey) {
2074
+ const n = node;
2075
+ const flag = (idNode, name) => {
2076
+ if (!isFreeUnknown(name, declared, seen, known)) return;
2077
+ const range = locOf(idNode) ?? locOf(node);
2078
+ if (range) {
2079
+ out.push({ name, range });
2080
+ seen.add(name);
2081
+ }
2082
+ };
2083
+ if (n.type === "CallExpression") {
2084
+ const callee = n.callee;
2085
+ const args = n.arguments ?? [];
2086
+ if (callee?.type === "Identifier" && callee.name === "require") {
2087
+ if (!isStaticStringExpr(args[0])) flag(n.callee, "require");
2088
+ } else if (callee?.type === "MemberExpression" && callee.object?.type === "Identifier" && callee.object.name === "require") {
2089
+ if (callee.property?.name === "resolve" && !isStaticStringExpr(args[0])) {
2090
+ flag(callee.object, "require");
2091
+ }
2092
+ } else if (callee?.type === "Identifier") {
2093
+ flag(n.callee, callee.name);
2094
+ }
2095
+ } else if (n.type === "Identifier" && typeof n.name === "string" && n.name !== "require" && parentKey !== "key" && parentKey !== "property" && parentKey !== "local" && parentKey !== "id") {
2096
+ flag(node, n.name);
2097
+ }
2098
+ for (const key of Object.keys(n)) {
2099
+ if (key === "loc" || key === "start" || key === "end") continue;
2100
+ const v = n[key];
2101
+ if (Array.isArray(v)) {
2102
+ v.forEach((x) => {
2103
+ if (x && typeof x === "object" && "type" in x) {
2104
+ walkBuiltinUnknown(x, declared, out, seen, known, key);
2105
+ }
2106
+ });
2107
+ } else if (v && typeof v === "object" && "type" in v) {
2108
+ walkBuiltinUnknown(v, declared, out, seen, known, key);
2109
+ }
2110
+ }
2111
+ }
2112
+ function collectBPathDiagnostics(source, extraKnown) {
2113
+ let file;
2114
+ try {
2115
+ file = parseSource(source);
2116
+ } catch {
2117
+ return { unreachable: [], builtinUnknown: [] };
2118
+ }
2119
+ const unreachable = [];
2120
+ const root = file.program ?? file;
2121
+ walkUnreachable(root, unreachable);
2122
+ const declared = collectDeclared(file);
2123
+ const known = new Set(KNOWN_GLOBALS);
2124
+ if (extraKnown) {
2125
+ for (const n of extraKnown) known.add(n);
2126
+ }
2127
+ const builtinUnknown = [];
2128
+ walkBuiltinUnknown(file, declared, builtinUnknown, /* @__PURE__ */ new Set(), known);
2129
+ return { unreachable, builtinUnknown };
2130
+ }
2131
+
2132
+ // src/dual-entry.ts
2133
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
2134
+ import { dirname as dirname7, join as join3, resolve as resolve6 } from "path";
2135
+ function collectExportFaces(exportsField, browser, node) {
2136
+ if (exportsField == null) return;
2137
+ if (typeof exportsField === "string") {
2138
+ node.push(exportsField);
2139
+ return;
2140
+ }
2141
+ const walk = (val, inBrowser) => {
2142
+ if (typeof val === "string") {
2143
+ if (inBrowser) browser.push(val);
2144
+ else node.push(val);
2145
+ return;
2146
+ }
2147
+ if (Array.isArray(val)) {
2148
+ for (const v of val) walk(v, inBrowser);
2149
+ return;
2150
+ }
2151
+ if (!val || typeof val !== "object") return;
2152
+ for (const [k, v] of Object.entries(val)) {
2153
+ if (k.startsWith(".")) {
2154
+ walk(v, inBrowser);
2155
+ } else if (k === "browser" || k === "web") {
2156
+ walk(v, true);
2157
+ } else {
2158
+ walk(v, inBrowser);
2159
+ }
2160
+ }
2161
+ };
2162
+ walk(exportsField, false);
2163
+ }
2164
+ function detectDualEntryFromPackageJson(pkg) {
2165
+ if (!pkg || typeof pkg !== "object") return null;
2166
+ const p = pkg;
2167
+ const browser = [];
2168
+ const node = [];
2169
+ collectExportFaces(p.exports, browser, node);
2170
+ const bField = p.browser;
2171
+ if (typeof bField === "string") browser.push(bField);
2172
+ else if (bField && typeof bField === "object") {
2173
+ for (const v of Object.values(bField)) {
2174
+ if (typeof v === "string") browser.push(v);
2175
+ }
2176
+ for (const k of Object.keys(bField)) {
2177
+ if (k.startsWith(".")) node.push(k);
2178
+ }
2179
+ }
2180
+ if (typeof p.main === "string") node.push(p.main);
2181
+ if (typeof p.module === "string") node.push(p.module);
2182
+ const browserPaths = uniqueResolved(browser, null);
2183
+ const nodePaths = uniqueResolved(node, null);
2184
+ if (browserPaths.length === 0 || nodePaths.length === 0) return null;
2185
+ const browserSet = new Set(browserPaths);
2186
+ const nodeSet = new Set(nodePaths);
2187
+ if (browserSet.size === nodeSet.size && [...browserSet].every((p2) => nodeSet.has(p2))) {
2188
+ return null;
2189
+ }
2190
+ const kind = typeof p.exports !== "undefined" && collectExportHasBrowser(p.exports) ? "exports-conditions" : "browser-field";
2191
+ return { kind, browser: [...new Set(browser)], node: [...new Set(node)] };
2192
+ }
2193
+ function collectExportHasBrowser(exportsField) {
2194
+ if (!exportsField || typeof exportsField !== "object") return false;
2195
+ const walk = (val) => {
2196
+ if (!val || typeof val !== "object") return false;
2197
+ for (const [k, v] of Object.entries(val)) {
2198
+ if (k === "browser" || k === "web") return true;
2199
+ if (walk(v)) return true;
2200
+ }
2201
+ return false;
2202
+ };
2203
+ return walk(exportsField);
2204
+ }
2205
+ function uniqueResolved(targets, pkgDir) {
2206
+ const out = /* @__PURE__ */ new Set();
2207
+ for (const t of targets) {
2208
+ if (typeof t !== "string" || t.length === 0) continue;
2209
+ out.add(pkgDir === null ? normalizeTarget(t) : resolve6(pkgDir, t));
2210
+ }
2211
+ return [...out];
2212
+ }
2213
+ function normalizeTarget(t) {
2214
+ return t.replace(/^\.\//, "");
2215
+ }
2216
+ function findOwningPackage(fromFile) {
2217
+ let dir = dirname7(resolve6(fromFile));
2218
+ const root = resolve6("/");
2219
+ for (; ; ) {
2220
+ const pkgPath = join3(dir, "package.json");
2221
+ if (existsSync5(pkgPath)) {
2222
+ try {
2223
+ const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
2224
+ return { path: pkgPath, dir, pkg };
2225
+ } catch {
2226
+ return null;
2227
+ }
2228
+ }
2229
+ if (dir === root) return null;
2230
+ const parent = dirname7(dir);
2231
+ if (parent === dir) return null;
2232
+ dir = parent;
2233
+ }
2234
+ }
2235
+ function dualEntryForFile(filePath) {
2236
+ let owning = null;
2237
+ try {
2238
+ owning = findOwningPackage(filePath);
2239
+ } catch {
2240
+ return null;
2241
+ }
2242
+ if (!owning) return null;
2243
+ const faces = detectDualEntryFromPackageJson(owning.pkg);
2244
+ if (!faces) return null;
2245
+ const abs = resolve6(filePath);
2246
+ const browserPaths = uniqueResolved(faces.browser, owning.dir);
2247
+ const nodePaths = uniqueResolved(faces.node, owning.dir);
2248
+ const inBrowser = browserPaths.includes(abs);
2249
+ const inNode = nodePaths.includes(abs);
2250
+ if (!inBrowser && !inNode) return null;
2251
+ if (inBrowser && inNode) return null;
2252
+ return {
2253
+ pkgPath: owning.path,
2254
+ pkgDir: owning.dir,
2255
+ ...typeof owning.pkg.name === "string" ? { pkgName: owning.pkg.name } : {},
2256
+ kind: faces.kind,
2257
+ browserPaths,
2258
+ nodePaths,
2259
+ role: inBrowser ? "browser" : "node",
2260
+ browserTargets: [...new Set(faces.browser.map(normalizeTarget))],
2261
+ nodeTargets: [...new Set(faces.node.map(normalizeTarget))]
2262
+ };
2263
+ }
2264
+ function dualEntryMessage(info) {
2265
+ const name = info.pkgName ? `"${info.pkgName}"` : info.pkgPath;
2266
+ const b = info.browserTargets.join(", ") || "(browser)";
2267
+ const n = info.nodeTargets.join(", ") || "(node)";
2268
+ return `dual-entry package ${name}: browser (${b}) and node (${n}) call-site records do not cross files \u2014 analysis observes only the ${info.role} entry variant`;
2269
+ }
2270
+ function dualEntrySuggestion() {
2271
+ return "analyze the entry you ship and mock or skip the other variant; browser/node records stay file-scoped (limits: dual package entrypoints)";
2272
+ }
2273
+ function dualEntryIssueForFile(filePath) {
2274
+ const info = dualEntryForFile(filePath);
2275
+ if (!info) return null;
2276
+ return {
2277
+ severity: "info",
2278
+ code: "nudo:dual-entry",
2279
+ message: dualEntryMessage(info),
2280
+ suggestion: dualEntrySuggestion(),
2281
+ line: 0,
2282
+ column: 0
2283
+ };
2284
+ }
2285
+
2286
+ // src/analyzer-orchestrate.ts
2287
+ import { dirname as dirname9 } from "path";
2288
+
2289
+ // src/analyzer-orchestrate-uncached.ts
2290
+ import { realpathSync } from "fs";
2291
+ import { resolve as resolve7, dirname as dirname8 } from "path";
2292
+ import {
2293
+ createEnvironment as createEnvironment2,
2294
+ markPureFn,
2295
+ unknown as absUnknown5,
2296
+ anyAbs,
2297
+ formatAbs as formatAbs5,
2298
+ formatShape,
2299
+ leqAbs,
2300
+ localNamedExports,
2301
+ effectiveInterface,
2302
+ abs as makeAbsVal2,
2303
+ formalParamsFromNodes as formalParamsFromNodes2
2304
+ } from "@nudojs/core";
2305
+ import {
2306
+ setMayThrowCollector,
2307
+ mayThrowEffectsToAbs,
2308
+ formatThrowsAbs,
2309
+ checkInjectedDomainEvidence,
2310
+ runWithEvalMissingSlot,
2311
+ fnFingerprints,
2312
+ loadModuleDepsFingerprint as loadModuleDepsFingerprint3,
2313
+ hashSource as hashSource3
2314
+ } from "@nudojs/core/internal";
2315
+ import { parse as parse8, extractDirectives as extractDirectives2, extractFileDirectives as extractFileDirectives3 } from "@nudojs/parser";
2316
+
2317
+ // src/analyzer-ast.ts
2318
+ import traverse from "@babel/traverse";
2319
+ import {
2320
+ formalParamsFromNodes,
2321
+ formalParamDisplayNames,
2322
+ unknown as absUnknown4,
2323
+ abs as makeAbsVal,
2324
+ numLit,
2325
+ strLit,
2326
+ boolLit
2327
+ } from "@nudojs/core";
2328
+ import {
2329
+ programBody,
2330
+ getDeclarations,
2331
+ getDeclaratorInit,
2332
+ getFirstDeclaratorId,
2333
+ getExpressionStatementExpression,
2334
+ unwrapExport,
2335
+ asIdentifier,
2336
+ asAssignmentExpression,
2337
+ classIdName,
2338
+ fnOrClassIdLoc,
2339
+ isModuleExportsMember,
2340
+ memberPropertyKey,
2341
+ classInstanceMethods,
2342
+ exportSpecifierLocalName,
2343
+ exportSpecifierExportedName,
2344
+ unwrapDefaultExport
2345
+ } from "@nudojs/parser";
2346
+ function locFromNode(node) {
2347
+ return {
2348
+ start: { line: node.loc?.start.line ?? 1, column: node.loc?.start.column ?? 0 },
2349
+ end: { line: node.loc?.end.line ?? 1, column: node.loc?.end.column ?? 0 }
2350
+ };
2351
+ }
2352
+ function extractParamNames(node) {
2353
+ const fn = node.type === "ExportDefaultDeclaration" ? node.declaration : node;
2354
+ if (fn.type === "FunctionDeclaration" || fn.type === "FunctionExpression" || fn.type === "ArrowFunctionExpression" || fn.type === "ClassMethod" || fn.type === "ObjectMethod" || fn.type === "TSDeclareMethod") {
2355
+ return formalParamDisplayNames(formalParamsFromNodes(fn.params));
2356
+ }
2357
+ if (fn.type === "VariableDeclaration") {
2358
+ const init = getDeclaratorInit(getDeclarations(fn)[0]);
2359
+ if (init && (init.type === "FunctionExpression" || init.type === "ArrowFunctionExpression")) {
2360
+ return formalParamDisplayNames(formalParamsFromNodes(init.params));
2361
+ }
2362
+ }
2363
+ return [];
2364
+ }
2365
+ function resolveFunctionNode(node) {
2366
+ if (node.type === "ExportNamedDeclaration" && node.declaration) return resolveFunctionNode(node.declaration);
2367
+ if (node.type === "ExportDefaultDeclaration") return resolveFunctionNode(node.declaration);
2368
+ if (node.type === "VariableDeclaration") {
2369
+ const init = getDeclaratorInit(getDeclarations(node)[0]);
2370
+ if (init && (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression")) return init;
2371
+ }
2372
+ return node;
2373
+ }
2374
+ function fnNameLoc(node, fallback) {
2375
+ const { declaration: d } = unwrapExport(node);
2376
+ if (d.type === "VariableDeclaration") {
2377
+ const id = getFirstDeclaratorId(d);
2378
+ if (id?.loc) return id.loc;
2379
+ }
2380
+ const idLoc = fnOrClassIdLoc(d);
2381
+ if (idLoc) return idLoc;
2382
+ return fallback;
2383
+ }
2384
+ function isFnExprValue(node) {
2385
+ return !!node && (node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression");
2386
+ }
2387
+ function namedFnExprId(node) {
2388
+ return node.type === "FunctionExpression" && node.id ? node.id.name : null;
2389
+ }
2390
+ function isModuleExportsTarget(node) {
2391
+ return node.type === "MemberExpression" && !node.computed && isModuleExportsMember(node);
2392
+ }
2393
+ function deepestAssignValue(expr) {
2394
+ let cur = expr;
2395
+ while (cur) {
2396
+ const assign = asAssignmentExpression(cur);
2397
+ if (!assign) break;
2398
+ cur = assign.right;
2399
+ }
2400
+ return cur ?? null;
2401
+ }
2402
+ function assignmentChainName(expr) {
2403
+ let cur = expr;
2404
+ while (cur) {
2405
+ const assign = asAssignmentExpression(cur);
2406
+ if (!assign) break;
2407
+ const target = assign.left;
2408
+ if (target.type === "Identifier") return target.name;
2409
+ if (target.type === "MemberExpression") {
2410
+ if (isModuleExportsTarget(target)) {
2411
+ cur = assign.right;
2412
+ continue;
2413
+ }
2414
+ return memberPropertyKey(target);
2415
+ }
2416
+ return null;
2417
+ }
2418
+ return null;
2419
+ }
2420
+ function collectTopLevelFunctions(ast) {
2421
+ const results = [];
2422
+ if (ast.type !== "File") return results;
2423
+ const body = programBody(ast);
2424
+ const classDecls = /* @__PURE__ */ new Map();
2425
+ const exportedClassNames = /* @__PURE__ */ new Set();
2426
+ const collectClassMethods = (cname, decl, stmt) => {
2427
+ for (const { name: keyName, node: methodNode } of classInstanceMethods(decl)) {
2428
+ results.push({
2429
+ name: `${cname}.${keyName}`,
2430
+ node: methodNode,
2431
+ stmt,
2432
+ noDeclaration: true
2433
+ });
2434
+ }
2435
+ };
2436
+ for (const stmt of body) {
2437
+ const className = classIdName(stmt);
2438
+ if (stmt.type === "ClassDeclaration" && className) {
2439
+ classDecls.set(className, { node: stmt, stmt });
2440
+ continue;
2441
+ }
2442
+ if (stmt.type === "ExportNamedDeclaration") {
2443
+ const d = stmt.declaration;
2444
+ const exportedClassName = classIdName(d);
2445
+ if (d?.type === "ClassDeclaration" && exportedClassName) {
2446
+ classDecls.set(exportedClassName, { node: d, stmt });
2447
+ exportedClassNames.add(exportedClassName);
2448
+ continue;
2449
+ }
2450
+ if (!d && stmt.specifiers) {
2451
+ for (const spec of stmt.specifiers) {
2452
+ if (spec.type !== "ExportSpecifier") continue;
2453
+ const local = exportSpecifierLocalName(spec);
2454
+ const exported = exportSpecifierExportedName(spec);
2455
+ if (!local) continue;
2456
+ if (classDecls.has(local) || body.some((s) => classIdName(s) === local)) {
2457
+ exportedClassNames.add(local);
2458
+ }
2459
+ if (exported === "default") exportedClassNames.add(local);
2460
+ }
2461
+ continue;
2462
+ }
2463
+ }
2464
+ if (stmt.type === "ExportDefaultDeclaration") {
2465
+ const d = stmt.declaration;
2466
+ const defaultClassName = classIdName(d);
2467
+ if (d?.type === "ClassDeclaration" && defaultClassName) {
2468
+ classDecls.set(defaultClassName, { node: d, stmt });
2469
+ exportedClassNames.add(defaultClassName);
2470
+ } else {
2471
+ const idName = asIdentifier(d)?.name;
2472
+ if (idName) exportedClassNames.add(idName);
2473
+ }
2474
+ continue;
2475
+ }
2476
+ }
2477
+ for (const stmt of body) {
2478
+ const decl = resolveFunctionNode(stmt);
2479
+ if (decl.type === "FunctionDeclaration" && decl.id) {
2480
+ results.push({ name: decl.id.name, node: decl, stmt, noDeclaration: false });
2481
+ continue;
2482
+ }
2483
+ if (decl.type === "ClassDeclaration" && decl.id?.name) {
2484
+ continue;
2485
+ }
2486
+ if (stmt.type === "ExportNamedDeclaration") {
2487
+ const d = resolveFunctionNode(stmt);
2488
+ if (d.type === "FunctionDeclaration" && d.id) {
2489
+ results.push({ name: d.id.name, node: d, stmt, noDeclaration: false });
2490
+ }
2491
+ if (d.type === "ClassDeclaration" && d.id?.name) {
2492
+ }
2493
+ continue;
2494
+ }
2495
+ if (stmt.type === "ExportDefaultDeclaration") {
2496
+ const d = stmt.declaration;
2497
+ if (d.type === "FunctionDeclaration") {
2498
+ if (d.id) {
2499
+ results.push({ name: d.id.name, node: d, stmt, noDeclaration: false });
2500
+ } else {
2501
+ results.push({ name: "default", node: d, stmt, noDeclaration: true });
2502
+ }
2503
+ } else if (isFnExprValue(d)) {
2504
+ results.push({ name: "default", node: d, stmt, noDeclaration: true });
2505
+ }
2506
+ continue;
2507
+ }
2508
+ if (stmt.type === "VariableDeclaration") {
2509
+ for (const declarator of getDeclarations(stmt)) {
2510
+ const id = asIdentifier(declarator.id);
2511
+ if (id && isFnExprValue(declarator.init)) {
2512
+ results.push({ name: id.name, node: declarator.init, stmt, noDeclaration: true });
2513
+ }
2514
+ }
2515
+ continue;
2516
+ }
2517
+ const expr = getExpressionStatementExpression(stmt);
2518
+ if (expr?.type === "AssignmentExpression") {
2519
+ const fn = deepestAssignValue(expr);
2520
+ if (isFnExprValue(fn)) {
2521
+ const name = namedFnExprId(fn) ?? assignmentChainName(expr) ?? "default";
2522
+ const chain = assignmentChainName(expr);
2523
+ const assignedName = namedFnExprId(fn) && chain && chain !== name ? chain : void 0;
2524
+ results.push({ name, node: fn, stmt, noDeclaration: true, ...assignedName ? { assignedName } : {} });
2525
+ }
2526
+ }
2527
+ }
2528
+ for (const cname of exportedClassNames) {
2529
+ const hit = classDecls.get(cname);
2530
+ if (!hit) continue;
2531
+ collectClassMethods(cname, hit.node, hit.stmt);
2532
+ }
2533
+ return results;
2534
+ }
2535
+ function findSingleModuleExportsFunction(ast) {
2536
+ if (ast.type !== "File") return null;
2537
+ let found = null;
2538
+ for (const stmt of programBody(ast)) {
2539
+ const expr = getExpressionStatementExpression(stmt);
2540
+ if (expr?.type !== "AssignmentExpression") continue;
2541
+ const fn = deepestAssignValue(expr);
2542
+ if (!isFnExprValue(fn)) continue;
2543
+ let targetsModuleExports = false;
2544
+ let cur = expr;
2545
+ while (cur) {
2546
+ const assign = asAssignmentExpression(cur);
2547
+ if (!assign) break;
2548
+ if (isModuleExportsTarget(assign.left)) {
2549
+ targetsModuleExports = true;
2550
+ break;
2551
+ }
2552
+ cur = assign.right;
2553
+ }
2554
+ if (!targetsModuleExports) continue;
2555
+ if (found) return null;
2556
+ found = fn;
2557
+ }
2558
+ return found;
2559
+ }
2560
+ function collectBindings(ast, env, bindings, absBinds) {
2561
+ if (ast.type !== "File") return;
2562
+ const setBinding = (name, loc) => {
2563
+ const absVal = absBinds?.get(name) ?? absUnknown4;
2564
+ bindings.set(name, {
2565
+ abs: absVal,
2566
+ loc
2567
+ });
2568
+ };
2569
+ const walk = (body) => {
2570
+ for (const stmt of body) {
2571
+ if (stmt.type === "FunctionDeclaration" && stmt.id) {
2572
+ setBinding(stmt.id.name, locFromNode(stmt));
2573
+ }
2574
+ if (stmt.type === "VariableDeclaration") {
2575
+ for (const decl of stmt.declarations) {
2576
+ if (decl.id.type === "Identifier") {
2577
+ setBinding(decl.id.name, locFromNode(decl));
2578
+ }
2579
+ }
2580
+ }
2581
+ if (stmt.type === "ClassDeclaration" && stmt.id) {
2582
+ setBinding(stmt.id.name, locFromNode(stmt));
2583
+ }
2584
+ if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
2585
+ walk([stmt.declaration]);
2586
+ }
2587
+ }
2588
+ };
2589
+ walk(programBody(ast));
2590
+ }
2591
+ function nullLitAbs() {
2592
+ return makeAbsVal({ k: "unknown" }, { op: "lit", value: null }, void 0, "exact");
2593
+ }
2594
+ function buildNodeTypeMap(ast, env, nodeAbsMap) {
2595
+ const traverseFn = unwrapDefaultExport(traverse);
2596
+ try {
2597
+ traverseFn(ast, {
2598
+ enter(path) {
2599
+ const node = path.node;
2600
+ try {
2601
+ const setAbs = (a) => {
2602
+ nodeAbsMap.set(node, a);
2603
+ };
2604
+ if (node.type === "Identifier" && node.name !== "undefined" && path.parentPath?.node.type !== "FunctionDeclaration" && path.parentPath?.node.type !== "VariableDeclarator") {
2605
+ if (env.has(node.name)) {
2606
+ setAbs(env.lookup(node.name));
2607
+ }
2608
+ }
2609
+ if (node.type === "NumericLiteral") {
2610
+ setAbs(numLit(node.value));
2611
+ }
2612
+ if (node.type === "StringLiteral") {
2613
+ setAbs(strLit(node.value));
2614
+ }
2615
+ if (node.type === "BooleanLiteral") {
2616
+ setAbs(boolLit(node.value));
2617
+ }
2618
+ if (node.type === "NullLiteral") {
2619
+ setAbs(nullLitAbs());
2620
+ }
2621
+ } catch {
2622
+ }
2623
+ }
2624
+ });
2625
+ } catch {
2626
+ }
2627
+ }
2628
+
2629
+ // src/analyzer-diagnose.ts
2630
+ function validateMockDirectives(directives, diagnostics) {
2631
+ for (const d of directives) {
2632
+ if (d.kind !== "mock" || !d.expression) continue;
2633
+ if (d.arrowFn || d.sinonExpr || d.nudoMock) continue;
2634
+ const expr = d.expression.trim();
2635
+ const isTypeExpr = /^(number|string|boolean|any|array|shape|lit|union|fn|partial|pick|omit|record|required|readonly|nonNullable|and)\s*\(/.test(expr) || expr === "unknown" || expr === "any" || expr === "never" || expr === "true" || expr === "false" || expr === "null" || expr === "undefined" || /^-?\d+(\.\d+)?$/.test(expr) || (expr.startsWith('"') && expr.endsWith('"') || expr.startsWith("'") && expr.endsWith("'")) || expr.startsWith("{") || expr.startsWith("[");
2636
+ const looksLikeCall = expr.includes("(") && expr.includes(")") || expr.includes("=>");
2637
+ if (looksLikeCall && !isTypeExpr) {
2638
+ diagnostics.push({
2639
+ range: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
2640
+ severity: "warning",
2641
+ message: `Mock expression "${expr}" could not be parsed as a known pattern`,
2642
+ code: "nudo:mock-invalid",
2643
+ suggestions: [
2644
+ "Supported formats: stub(), stub().returns(value), spy(), mock()",
2645
+ "Arrow functions: (args) => expression or (args) => { statements; return value; }",
2646
+ "Type expressions: number(), string(), shape({...}), union(...), or concrete literals"
2647
+ ]
2648
+ });
2649
+ }
2650
+ }
2651
+ }
2652
+ var DEFAULT_CALLSITE_BUDGET = 3;
2653
+ var COLLAPSE_LITERAL_THRESHOLD = 4;
2654
+ function dedupeCallRecords(records) {
2655
+ const seen = /* @__PURE__ */ new Set();
2656
+ const out = [];
2657
+ for (const rec of records) {
2658
+ if (isOversizedCallRecord(rec)) continue;
2659
+ const key = rec.argAbs.map(absStructureKey).join(",") + "=>" + absStructureKey(rec.resultAbs) + "!" + absStructureKey(rec.throwsAbs);
2660
+ if (seen.has(key)) continue;
2661
+ seen.add(key);
2662
+ out.push(rec);
2663
+ }
2664
+ return out;
2665
+ }
2666
+ function locFromCallLoc(loc) {
2667
+ const p = loc ?? { line: 0, column: 0 };
2668
+ return { start: { line: p.line, column: p.column }, end: { line: p.line, column: p.column } };
2669
+ }
2670
+ function synthesizeExternalFunctions(records, currentFile, callSiteBudget = DEFAULT_CALLSITE_BUDGET) {
2671
+ const groups = /* @__PURE__ */ new Map();
2672
+ for (const rec of records) {
2673
+ if (!rec.targetModule || !rec.targetExport) continue;
2674
+ if (rec.targetModule === currentFile) continue;
2675
+ const key = `${rec.targetModule}\0${rec.targetExport}`;
2676
+ let group = groups.get(key);
2677
+ if (!group) {
2678
+ group = { module: rec.targetModule, exportName: rec.targetExport, records: [] };
2679
+ groups.set(key, group);
2680
+ }
2681
+ group.records.push(rec);
2682
+ }
2683
+ const out = [];
2684
+ for (const { module, exportName, records: records2 } of groups.values()) {
2685
+ const deduped = dedupeCallRecords(records2);
2686
+ const arity = Math.max(...deduped.map((r) => r.argAbs.length));
2687
+ const analysis = {
2688
+ name: exportName,
2689
+ loc: locFromCallLoc(deduped[0].callLoc),
2690
+ paramNames: Array.from({ length: arity }, (_, i) => `arg${i}`),
2691
+ cases: [],
2692
+ fromModule: module
2693
+ };
2694
+ const precise = deduped.slice(0, callSiteBudget);
2695
+ for (const rec of precise) {
2696
+ analysis.cases.push({
2697
+ name: `call@L${rec.callLoc?.line ?? 0}`,
2698
+ argAbs: [...rec.argAbs],
2699
+ abs: rec.resultAbs,
2700
+ throwsAbs: rec.throwsAbs,
2701
+ source: "callsite"
2702
+ });
2703
+ }
2704
+ const remaining = deduped.slice(callSiteBudget);
2705
+ if (remaining.length > 0) {
2706
+ const symArgsAbs = Array.from(
2707
+ { length: arity },
2708
+ (_, i) => (
2709
+ // 缺参按真实 JS 语义 widen 成 undefined 而非 unknown——可选参守卫
2710
+ // (target || [] 等)对 unknown 全塌,对 undefined 正常走默认分支
2711
+ widenJoinAbs(remaining.map((rec) => rec.argAbs[i] ?? undefAbs))
2712
+ )
2713
+ );
2714
+ const symResultAbs = collapseAbsLits(
2715
+ remaining.map((r) => r.resultAbs),
2716
+ COLLAPSE_LITERAL_THRESHOLD
2717
+ );
2718
+ const symThrowsAbs = joinAllAbs(remaining.map((r) => r.throwsAbs));
2719
+ analysis.cases.push({
2720
+ name: "call@symbolic",
2721
+ argAbs: symArgsAbs,
2722
+ abs: symResultAbs,
2723
+ throwsAbs: symThrowsAbs,
2724
+ source: "callsite",
2725
+ aggregatedFrom: remaining.length
2726
+ });
2727
+ }
2728
+ if (deduped.length > 1) {
2729
+ analysis.combinedAbs = collapseAbsLits(
2730
+ deduped.map((r) => r.resultAbs),
2731
+ COLLAPSE_LITERAL_THRESHOLD
2732
+ );
2733
+ } else {
2734
+ analysis.combinedAbs = deduped[0].resultAbs;
2735
+ }
2736
+ out.push(analysis);
2737
+ }
2738
+ return out;
2739
+ }
2740
+ function findModuleImportLoc(source, module) {
2741
+ const bare = module.startsWith("node:") ? module.slice("node:".length) : module;
2742
+ const alts = [.../* @__PURE__ */ new Set([module, bare, `node:${bare}`])];
2743
+ const lines = source.split(/\r?\n/);
2744
+ for (let i = 0; i < lines.length; i++) {
2745
+ const line = lines[i];
2746
+ for (const alt of alts) {
2747
+ const re = new RegExp(
2748
+ `["'\`]${alt.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}["'\`]`
2749
+ );
2750
+ const m = re.exec(line);
2751
+ if (m && m.index !== void 0) {
2752
+ return { line: i, column: m.index, length: m[0].length };
2753
+ }
2754
+ }
2755
+ }
2756
+ return null;
2757
+ }
2758
+
2759
+ // src/analyzer-orchestrate-uncached.ts
2760
+ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords, loadModule, caseMode = "all") {
2761
+ const projectConfig = findProjectConfig(dirname8(filePath));
2762
+ const missingSlotOn = analysisConfig(projectConfig?.config).evalMissingSlot === "warning";
2763
+ return runWithEvalMissingSlot(
2764
+ missingSlotOn,
2765
+ () => analyzeFileUncachedInner(
2766
+ filePath,
2767
+ source,
2768
+ activeCases,
2769
+ externalCallRecords,
2770
+ loadModule,
2771
+ caseMode
2772
+ )
2773
+ );
2774
+ }
2775
+ function analyzeFileUncachedInner(filePath, source, activeCases, externalCallRecords, loadModule, caseMode = "all") {
2776
+ const ast = parse8(source);
2777
+ const functions = extractDirectives2(ast);
2778
+ const diagnostics = [];
2779
+ const bindings = /* @__PURE__ */ new Map();
2780
+ const nodeAbsMap = /* @__PURE__ */ new Map();
2781
+ const functionResults = [];
2782
+ const caseHints = [];
2783
+ const envHarvestConflicts = [];
2784
+ const prevHarvestConflictCollector = setEnvHarvestConflictCollector((c) => {
2785
+ if (!envHarvestConflicts.some((x) => x.module === c.module)) {
2786
+ envHarvestConflicts.push(c);
2787
+ } else {
2788
+ const prev = envHarvestConflicts.find((x) => x.module === c.module);
2789
+ for (const e of c.exports) {
2790
+ if (!prev.exports.includes(e)) prev.exports.push(e);
2791
+ }
2792
+ prev.defaultOverwritten ||= c.defaultOverwritten;
2793
+ }
2794
+ });
2795
+ const fileDirectives = extractFileDirectives3(ast);
2796
+ const fileEnvNames = fileDirectives.filter((d) => d.kind === "env").flatMap((d) => d.envs);
2797
+ const projectConfig = findProjectConfig(dirname8(filePath));
2798
+ const projectEnvNames = projectConfig?.config.env ?? [];
2799
+ const envNames = [.../* @__PURE__ */ new Set([...projectEnvNames, ...fileEnvNames])];
2800
+ const analysisCfg = analysisConfig(projectConfig?.config);
2801
+ const callSiteBudget = analysisCfg.callSiteBudget;
2802
+ const callRecords = [];
2803
+ const globalEnv = createEnvironment2();
2804
+ const bMemberDiagNames = /* @__PURE__ */ new Set();
2805
+ const bMemberDiagSeen = /* @__PURE__ */ new Set();
2806
+ const pushBMemberDiag = (d, fallbackLine) => {
2807
+ bMemberDiagNames.add(d.name);
2808
+ const key = `${d.kind}:${d.name}:${d.receiver}:${d.line ?? fallbackLine}:${d.column ?? 0}:${d.code ?? ""}`;
2809
+ if (bMemberDiagSeen.has(key)) return;
2810
+ bMemberDiagSeen.add(key);
2811
+ if (d.code === "nudo:missing-slot") {
2812
+ diagnostics.push({
2813
+ range: {
2814
+ start: { line: d.line ?? fallbackLine, column: d.column ?? 0 },
2815
+ end: { line: d.line ?? fallbackLine, column: (d.column ?? 0) + d.name.length }
2816
+ },
2817
+ severity: "warning",
2818
+ message: `Field '${d.name}' is missing on the evaluated object shape`,
2819
+ code: "nudo:missing-slot",
2820
+ ...d.origin ? { origin: d.origin } : {}
2821
+ });
2822
+ return;
2823
+ }
2824
+ if (d.receiver === "unknown") {
2825
+ diagnostics.push({
2826
+ range: {
2827
+ start: { line: d.line ?? fallbackLine, column: d.column ?? 0 },
2828
+ end: { line: d.line ?? fallbackLine, column: (d.column ?? 0) + d.name.length }
2829
+ },
2830
+ severity: "warning",
2831
+ message: `Cannot resolve '${d.name}' on unknown value`,
2832
+ code: "nudo:unknown-recv",
2833
+ ...d.origin ? { origin: d.origin } : {}
2834
+ });
2835
+ return;
2836
+ }
2837
+ diagnostics.push({
2838
+ range: {
2839
+ start: { line: d.line ?? fallbackLine, column: d.column ?? 0 },
2840
+ end: { line: d.line ?? fallbackLine, column: (d.column ?? 0) + d.name.length }
2841
+ },
2842
+ severity: d.receiver === "number" || d.receiver === "boolean" || d.receiver === "bigint" || d.receiver === "symbol" ? "error" : "warning",
2843
+ message: d.kind === "method" ? `Method '${d.name}' does not exist on type '${d.receiver}'` : `Property '${d.name}' does not exist on type '${d.receiver}'`,
2844
+ code: "nudo:no-method",
2845
+ ...d.origin ? { origin: d.origin } : {}
2846
+ });
2847
+ };
2848
+ for (const fn of functions) {
2849
+ validateMockDirectives(fn.directives, diagnostics);
2850
+ }
2851
+ const selfContained = isSelfContainedSource(source, envNames);
2852
+ const canAbsModules = absModulesOk(source, envNames);
2853
+ const bCapable = isBPathCapable(source, envNames);
2854
+ const bModuleIssueKinds = /* @__PURE__ */ new Set();
2855
+ const bTruncatedFns = /* @__PURE__ */ new Set();
2856
+ const bBuiltinUnknownNames = /* @__PURE__ */ new Set();
2857
+ let bHostedEval = false;
2858
+ const pushBModuleIssues = (issues) => {
2859
+ if (!issues) return;
2860
+ for (const iss of issues) {
2861
+ bModuleIssueKinds.add(iss.kind);
2862
+ const code = iss.kind === "cycle" ? "nudo:module-cycle" : iss.kind === "depth" ? "nudo:module-depth" : "nudo:module-missing";
2863
+ diagnostics.push({
2864
+ range: { start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
2865
+ severity: iss.kind === "missing" ? "error" : "warning",
2866
+ message: iss.reason,
2867
+ code
2868
+ });
2869
+ }
2870
+ };
2871
+ const seeds = mockDirectivesToAbsSeeds(functions, {
2872
+ fromFile: filePath || void 0,
2873
+ ...loadModule ? { loadModule } : {}
2874
+ });
2875
+ for (const fe of seeds.fromErrors ?? []) {
2876
+ diagnostics.push({
2877
+ range: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
2878
+ severity: "error",
2879
+ message: fe.message,
2880
+ code: "nudo:module-missing",
2881
+ suggestions: [
2882
+ `Create the mock file or fix the path in @nudo:mock ${fe.name} from "${fe.fromPath}"`,
2883
+ "The mock module must define a binding with the same name as the mock"
2884
+ ]
2885
+ });
2886
+ }
2887
+ let absCallRecords = [];
2888
+ let bTopCallRecords = [];
2889
+ let absGraphModules;
2890
+ let absBindsShared;
2891
+ let absNodesShared;
2892
+ if (bCapable && filePath) {
2893
+ try {
2894
+ const g = evalAbsModuleGraph(source, filePath, {
2895
+ seedVars: seeds.seedVars,
2896
+ seedFns: seeds.seedFns,
2897
+ ...loadModule ? { loadModule } : {}
2898
+ });
2899
+ absGraphModules = mergeHarvestUnderEnv(g.modules, collectEnvModules(envNames));
2900
+ const mm = applyMockModuleDirectives(absGraphModules, fileDirectives, {
2901
+ fromFile: filePath,
2902
+ ...loadModule ? { loadModule } : {}
2903
+ });
2904
+ absGraphModules = mm.modules;
2905
+ for (const fe of mm.errors) {
2906
+ diagnostics.push({
2907
+ range: { start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
2908
+ severity: "error",
2909
+ message: fe.message,
2910
+ code: "nudo:module-missing"
2911
+ });
2912
+ }
2913
+ pushBModuleIssues(g.issues);
2914
+ } catch {
2915
+ }
2916
+ const bRun = tryRunBPath(source, filePath, {
2917
+ envNames,
2918
+ mocks: mockSeedsToAbsMocks(seeds)
2919
+ });
2920
+ if (bRun) {
2921
+ bHostedEval = true;
2922
+ if (bRun.memberDiags?.length) {
2923
+ for (const d of bRun.memberDiags) {
2924
+ pushBMemberDiag(d, 1);
2925
+ }
2926
+ }
2927
+ if (bRun.truncatedFns) {
2928
+ for (const fn of bRun.truncatedFns) bTruncatedFns.add(fn);
2929
+ }
2930
+ if (bRun.calls?.length) {
2931
+ const impMap = buildAbsImportLocalMap(source, filePath);
2932
+ for (const c of bRun.calls) {
2933
+ bTopCallRecords.push(callRecordFromAbsCall(c, impMap));
2934
+ }
2935
+ }
2936
+ }
2937
+ }
2938
+ if (bHostedEval && bTopCallRecords.length > 0) {
2939
+ callRecords.length = 0;
2940
+ callRecords.push(...bTopCallRecords);
2941
+ } else if ((selfContained || canAbsModules) && absCallRecords.length > 0) {
2942
+ callRecords.length = 0;
2943
+ callRecords.push(...absCallRecords);
2944
+ } else if (bTopCallRecords.length > 0) {
2945
+ callRecords.length = 0;
2946
+ callRecords.push(...bTopCallRecords);
2947
+ }
2948
+ const unreachableRanges = [];
2949
+ if (bCapable) {
2950
+ const bKnownGlobals = /* @__PURE__ */ new Set([
2951
+ ...Object.keys(collectEnvGlobals(envNames)),
2952
+ ...Object.keys(mockSeedsToAbsMocks(seeds))
2953
+ ]);
2954
+ const bDiag = collectBPathDiagnostics(source, bKnownGlobals);
2955
+ for (const ur of bDiag.unreachable) {
2956
+ diagnostics.push({
2957
+ range: ur.range,
2958
+ severity: "info",
2959
+ message: "Code after return/throw statement is unreachable",
2960
+ tags: ["unnecessary"],
2961
+ code: "nudo-unreachable",
2962
+ suggestions: ["Remove the unreachable code after the return/throw statement"]
2963
+ });
2964
+ }
2965
+ for (const b of bDiag.builtinUnknown) {
2966
+ bBuiltinUnknownNames.add(b.name);
2967
+ diagnostics.push({
2968
+ range: b.range,
2969
+ severity: "warning",
2970
+ message: `Built-in API "${b.name}" is not covered by Nudo's type inference`,
2971
+ code: "nudo:builtin-unknown",
2972
+ suggestions: [
2973
+ `Use @nudo:mock to define the type: @nudo:mock ${b.name} = stub().returns(...)`,
2974
+ `Or use @nudo:contract return <constraint> to declare the return contract`
2975
+ ]
2976
+ });
2977
+ }
2978
+ } else {
2979
+ for (const ur of unreachableRanges) {
2980
+ diagnostics.push({
2981
+ range: ur,
2982
+ severity: "info",
2983
+ message: "Code after return/throw statement is unreachable",
2984
+ tags: ["unnecessary"],
2985
+ code: "nudo-unreachable",
2986
+ suggestions: ["Remove the unreachable code after the return/throw statement"]
2987
+ });
2988
+ }
2989
+ }
2990
+ if (!absNodesShared && !bHostedEval && (selfContained || canAbsModules)) {
2991
+ try {
2992
+ const collected = { binds: /* @__PURE__ */ new Map(), nodes: /* @__PURE__ */ new Map() };
2993
+ if (!absBindsShared) absBindsShared = collected.binds;
2994
+ absNodesShared = collected.nodes;
2995
+ } catch {
2996
+ }
2997
+ }
2998
+ collectBindings(ast, globalEnv, bindings, absBindsShared);
2999
+ if (isBPathCapable(source, envNames)) {
3000
+ try {
3001
+ const absBinds = absBindsShared ?? collectAbsBindingsFromGraph(source, filePath, {
3002
+ seedVars: seeds.seedVars,
3003
+ seedFns: seeds.seedFns
3004
+ });
3005
+ if (!absBinds) {
3006
+ } else for (const [name, absVal0] of absBinds) {
3007
+ const absVal = absVal0;
3008
+ const prev = bindings.get(name);
3009
+ bindings.set(name, {
3010
+ abs: absVal,
3011
+ loc: prev?.loc
3012
+ });
3013
+ }
3014
+ } catch {
3015
+ }
3016
+ }
3017
+ const synthCandidates = [];
3018
+ let fnFpMap;
3019
+ try {
3020
+ fnFpMap = fnFingerprints(source, ast);
3021
+ } catch {
3022
+ fnFpMap = void 0;
3023
+ }
3024
+ const envKeyFn = envNames.join(",");
3025
+ const mockKeyFn = mockSeedFingerprint(seeds.seedVars, seeds.seedFns);
3026
+ for (const fn of functions) {
3027
+ const isPure = fn.directives.some((d) => d.kind === "pure");
3028
+ const skipDirective = fn.directives.find((d) => d.kind === "skip");
3029
+ const fnLoc = locFromNode(fn.node);
3030
+ const paramNames = extractParamNames(fn.node);
3031
+ const formals = formalParamsFromNodes2(
3032
+ fn.node.params ?? []
3033
+ );
3034
+ const analysis = {
3035
+ name: fn.name,
3036
+ loc: fnLoc,
3037
+ paramNames,
3038
+ ...formals.length > 0 ? { formals } : {},
3039
+ cases: []
3040
+ };
3041
+ if (skipDirective && skipDirective.kind === "skip") {
3042
+ analysis.skipped = true;
3043
+ if (skipDirective.returns) {
3044
+ analysis.combinedAbs = skipDirective.returns;
3045
+ }
3046
+ functionResults.push(analysis);
3047
+ continue;
3048
+ }
3049
+ const caseDirectives = fn.directives.filter((d) => d.kind === "case");
3050
+ const activeCaseIdx = activeCases?.get(fn.name) ?? 0;
3051
+ const caseIdxsToRun = caseMode === "none" || caseDirectives.length === 0 ? [] : caseMode === "all" ? caseDirectives.map((_, i) => i) : activeCases?.has(fn.name) ? [Math.min(activeCaseIdx, caseDirectives.length - 1)] : [];
3052
+ const fp = fnFpMap?.get(fn.name);
3053
+ const analysisFnKey = `m=${analysisCfg.mode}|e=${analysisCfg.evalMissingSlot}|b=${analysisCfg.callSiteBudget}`;
3054
+ let fnDepSeg = "-";
3055
+ let fnDepFailClosed = false;
3056
+ try {
3057
+ const dfp = loadModuleDepsFingerprint3(source, loadModule ?? defaultLoadModule, filePath);
3058
+ if (dfp.truncated) {
3059
+ fnDepFailClosed = true;
3060
+ fnDepSeg = null;
3061
+ } else {
3062
+ fnDepSeg = hashSource3(dfp.fp);
3063
+ }
3064
+ } catch {
3065
+ fnDepFailClosed = true;
3066
+ fnDepSeg = null;
3067
+ }
3068
+ const fnCacheKey = !fnDepFailClosed && fp && caseDirectives.length > 0 ? [
3069
+ filePath,
3070
+ fp.own,
3071
+ fp.deps,
3072
+ String(activeCaseIdx),
3073
+ caseDirectiveKey(caseDirectives, formatAbs5),
3074
+ envKeyFn,
3075
+ mockKeyFn,
3076
+ analysisFnKey,
3077
+ fnDepSeg ?? "-",
3078
+ `cm=${caseMode}`
3079
+ ].join("\0") : void 0;
3080
+ const dLen0 = diagnostics.length;
3081
+ const hLen0 = caseHints.length;
3082
+ const cLen0 = callRecords.length;
3083
+ if (fnCacheKey) {
3084
+ const hitFn = fnAnalysisCacheGet(fnCacheKey);
3085
+ if (hitFn) {
3086
+ const cached = cloneFunctionAnalysis(hitFn.analysis);
3087
+ const lineDelta = fnLoc.start.line - cached.loc.start.line;
3088
+ cached.loc = fnLoc;
3089
+ if (lineDelta !== 0) {
3090
+ for (const c of cached.cases) {
3091
+ if (c.throwLoc) c.throwLoc = shiftSourceLoc(c.throwLoc, lineDelta);
3092
+ }
3093
+ }
3094
+ if (lineDelta === 0) {
3095
+ diagnostics.push(...hitFn.diagnostics);
3096
+ caseHints.push(...hitFn.caseHints);
3097
+ callRecords.push(...hitFn.callRecords);
3098
+ } else {
3099
+ for (const d of hitFn.diagnostics) {
3100
+ diagnostics.push(shiftDiagnosticLines(d, lineDelta));
3101
+ }
3102
+ for (const h of hitFn.caseHints) {
3103
+ caseHints.push({ ...h, line: h.line + lineDelta });
3104
+ }
3105
+ for (const r of hitFn.callRecords) {
3106
+ callRecords.push(shiftCallRecordLines(r, lineDelta));
3107
+ }
3108
+ }
3109
+ functionResults.push(cached);
3110
+ continue;
3111
+ }
3112
+ }
3113
+ if (isPure) {
3114
+ const fnVal = globalEnv.has(fn.name) ? globalEnv.lookup(fn.name) : null;
3115
+ if (fnVal && typeof fnVal === "object") {
3116
+ markPureFn(fnVal, fn.name);
3117
+ }
3118
+ }
3119
+ const sampleDirective = fn.directives.find((d) => d.kind === "sample");
3120
+ void sampleDirective;
3121
+ if (caseIdxsToRun.length === 0) {
3122
+ synthCandidates.push({ name: fn.name, node: fn.node, analysis });
3123
+ }
3124
+ for (const ci of caseIdxsToRun) {
3125
+ const directive = caseDirectives[ci];
3126
+ let caseAbs;
3127
+ let caseThrowsAbs;
3128
+ let caseThrowLoc;
3129
+ let caseUnreachable = [];
3130
+ const bCapable2 = isBPathCapable(source, envNames);
3131
+ const bPrimary = bCapable2;
3132
+ if (bCapable2 && filePath) {
3133
+ const caseArgsAbs = directive.argsAbs;
3134
+ const bFull = tryBPathCallFull(
3135
+ source,
3136
+ filePath,
3137
+ fn.name,
3138
+ caseArgsAbs,
3139
+ { collectCalls: true, envNames, mocks: mockSeedsToAbsMocks(seeds) }
3140
+ );
3141
+ const res = bFull?.result;
3142
+ const weakUnknown = !!res && res.shape.k === "unknown" && (!res.term || res.term.op === "lit" && res.term.value === void 0);
3143
+ const bOk = !!res && (bHostedEval || !weakUnknown && res.conf !== "opaque");
3144
+ if (bOk && bFull && bPrimary) {
3145
+ caseAbs = bFull.result;
3146
+ caseThrowsAbs = bFull.throws;
3147
+ for (const d of bFull.memberDiags ?? []) {
3148
+ pushBMemberDiag(d, fnLoc.start.line);
3149
+ }
3150
+ if (bFull.calls?.length) {
3151
+ const impMap = buildAbsImportLocalMap(source, filePath);
3152
+ for (const c of bFull.calls) {
3153
+ callRecords.push(callRecordFromAbsCall(c, impMap));
3154
+ }
3155
+ }
3156
+ }
3157
+ }
3158
+ if (!caseAbs) {
3159
+ if (bHostedEval) {
3160
+ caseAbs = absUnknown5;
3161
+ caseThrowsAbs = neverAbs;
3162
+ } else {
3163
+ const caseArgsAbs = directive.argsAbs;
3164
+ const absFull = selfContained || canAbsModules ? tryEvalAbsFull(
3165
+ source,
3166
+ fn.name,
3167
+ caseArgsAbs,
3168
+ filePath,
3169
+ mockSeedsToAbsMocks(seeds)
3170
+ ) : void 0;
3171
+ const weak = !!absFull && absFull.result.shape.k === "unknown" && (!absFull.result.term || absFull.result.term.op === "lit" && absFull.result.term.value === void 0);
3172
+ const absOk = !!absFull && !weak && absFull.result.conf !== "opaque" && absFull.throws.shape.k === "never";
3173
+ const absThrew = !!absFull && !weak && absFull.result.shape.k === "never" && absFull.throws.shape.k !== "never";
3174
+ if (absOk) {
3175
+ caseAbs = absFull.result;
3176
+ caseThrowsAbs = neverAbs;
3177
+ } else if (absThrew) {
3178
+ caseAbs = absFull.result;
3179
+ caseThrowsAbs = absFull.throws;
3180
+ const tl = absFull.throwLoc;
3181
+ if (tl) caseThrowLoc = { start: { ...tl }, end: { ...tl } };
3182
+ } else {
3183
+ caseAbs = absUnknown5;
3184
+ caseThrowsAbs = neverAbs;
3185
+ }
3186
+ }
3187
+ }
3188
+ if (!caseThrowsAbs) caseThrowsAbs = neverAbs;
3189
+ const caseEntry = {
3190
+ name: directive.name,
3191
+ argAbs: directive.argsAbs,
3192
+ abs: caseAbs,
3193
+ throwsAbs: caseThrowsAbs,
3194
+ throwLoc: caseThrowLoc,
3195
+ expected: directive.expected,
3196
+ source: "directive"
3197
+ };
3198
+ tryAttachIntension(caseEntry, source, fn.name);
3199
+ attachAbsToIntension(caseEntry, caseAbs, fn.name);
3200
+ analysis.cases.push(caseEntry);
3201
+ if (directive.commentLine) {
3202
+ const hasThrow = caseThrowsAbs.shape.k !== "never";
3203
+ const resultStr = caseAbs.shape.k !== "never" ? formatShape(caseAbs) : "";
3204
+ const throwStr = hasThrow ? `throws ${formatShape(caseThrowsAbs)}` : "";
3205
+ const label = [resultStr, throwStr].filter(Boolean).join(" ");
3206
+ const hintLabel = `=> ${label}`;
3207
+ let ok = true;
3208
+ if (directive.expected) {
3209
+ ok = leqAbs(caseAbs, directive.expected).ok;
3210
+ if (!ok) {
3211
+ diagnostics.push({
3212
+ range: { start: { line: directive.commentLine, column: 0 }, end: { line: directive.commentLine, column: 999 } },
3213
+ severity: "error",
3214
+ message: `debug "${directive.name}": expected ${formatShape(directive.expected)}, got ${formatShape(caseAbs)}. The inferred return type does not match the expected type declared in the @nudo:case witness`,
3215
+ code: "nudo:case-expected"
3216
+ });
3217
+ }
3218
+ }
3219
+ caseHints.push({ line: directive.commentLine, label: hintLabel, ok });
3220
+ }
3221
+ const isActive = ci === Math.min(activeCaseIdx, caseDirectives.length - 1);
3222
+ if (isActive) {
3223
+ if (caseThrowsAbs.shape.k !== "never") {
3224
+ const throwRange = caseThrowLoc ?? fnLoc;
3225
+ diagnostics.push({
3226
+ range: throwRange,
3227
+ severity: "warning",
3228
+ message: `Function "${fn.name}" case "${directive.name}" may throw: ${formatShape(caseThrowsAbs)}. Consider adding a try-catch block or using @nudo:contract return <constraint>`,
3229
+ code: "nudo:may-throw"
3230
+ });
3231
+ }
3232
+ if (!isBPathCapable(source, envNames)) {
3233
+ for (const ur of caseUnreachable) {
3234
+ diagnostics.push({
3235
+ range: ur,
3236
+ severity: "info",
3237
+ message: "Code after return/throw statement is unreachable",
3238
+ tags: ["unnecessary"],
3239
+ code: "nudo-unreachable",
3240
+ suggestions: ["Remove the unreachable code after the return/throw statement"]
3241
+ });
3242
+ }
3243
+ }
3244
+ }
3245
+ }
3246
+ if (analysis.cases.length > 0) {
3247
+ analysis.combinedAbs = collapseAbsLits(
3248
+ analysis.cases.map((c) => c.abs),
3249
+ COLLAPSE_LITERAL_THRESHOLD
3250
+ );
3251
+ }
3252
+ attachHofSnapshot(analysis, source);
3253
+ if (fnCacheKey) {
3254
+ fnAnalysisCacheSet(fnCacheKey, {
3255
+ analysis: cloneFunctionAnalysis(analysis),
3256
+ diagnostics: diagnostics.slice(dLen0),
3257
+ caseHints: caseHints.slice(hLen0),
3258
+ callRecords: callRecords.slice(cLen0)
3259
+ });
3260
+ }
3261
+ functionResults.push(analysis);
3262
+ }
3263
+ const directiveFnNames = new Set(functions.map((f) => f.name));
3264
+ const directiveFnStmts = new Set(functions.map((f) => f.node));
3265
+ for (const { name, node, stmt, noDeclaration, assignedName } of collectTopLevelFunctions(ast)) {
3266
+ if (directiveFnNames.has(name)) continue;
3267
+ if (directiveFnStmts.has(stmt)) continue;
3268
+ const analysis = { name, loc: locFromNode(node), paramNames: extractParamNames(node), cases: [] };
3269
+ if (noDeclaration) analysis.noDeclaration = true;
3270
+ functionResults.push(analysis);
3271
+ synthCandidates.push({ name, node, analysis, assignedName });
3272
+ }
3273
+ const modulePathCache = /* @__PURE__ */ new Map();
3274
+ const normalizeModulePath = (p) => {
3275
+ let n = modulePathCache.get(p);
3276
+ if (n === void 0) {
3277
+ try {
3278
+ n = realpathSync(p);
3279
+ } catch {
3280
+ n = p;
3281
+ }
3282
+ modulePathCache.set(p, n);
3283
+ }
3284
+ return n;
3285
+ };
3286
+ const currentModulePath = normalizeModulePath(resolve7(filePath));
3287
+ const singleExportFn = findSingleModuleExportsFunction(ast);
3288
+ const reportInjectedDomainExceeds = (name, node, fallbackLoc) => {
3289
+ if (!externalCallRecords || externalCallRecords.length === 0) return;
3290
+ const singleExportHit = singleExportFn !== null && node === singleExportFn;
3291
+ const nameRoutes = (r) => r.fnName === name || r.targetExport === name || (r.targetAliases?.includes(name) ?? false) || singleExportHit && (r.fnModule !== void 0 || r.targetModule !== void 0);
3292
+ const matchingExternal = (r) => {
3293
+ const attributed = r.fnModule !== void 0 && normalizeModulePath(r.fnModule) === currentModulePath || r.targetModule !== void 0 && normalizeModulePath(r.targetModule) === currentModulePath;
3294
+ if (!attributed) return false;
3295
+ return nameRoutes(r);
3296
+ };
3297
+ const injected = externalCallRecords.filter(matchingExternal);
3298
+ if (injected.length === 0) return;
3299
+ const nameLoc = fnNameLoc(node, fallbackLoc);
3300
+ const fnNode = resolveFunctionNode(node);
3301
+ const autoBind = interfaceConfig(findProjectConfig(dirname8(filePath))?.config).autoBind;
3302
+ const domainIssues = checkInjectedDomainEvidence(name, source, injected, {
3303
+ paramNames: extractParamNames(fnNode),
3304
+ loadModule: loadModule ?? defaultLoadModule,
3305
+ fromFile: filePath,
3306
+ loc: { line: nameLoc.start.line, column: nameLoc.start.column },
3307
+ ...autoBind === false ? { autoBind: false } : {}
3308
+ });
3309
+ for (const issue of domainIssues) {
3310
+ const line = issue.line ?? nameLoc.start.line;
3311
+ const column = issue.column ?? nameLoc.start.column;
3312
+ diagnostics.push({
3313
+ range: { start: { line, column }, end: { line, column: column + name.length } },
3314
+ severity: issue.severity,
3315
+ message: issue.message,
3316
+ code: issue.code,
3317
+ suggestions: issue.suggestion ? [issue.suggestion] : void 0,
3318
+ data: { actual: issue.actual, expected: issue.expected }
3319
+ });
3320
+ }
3321
+ };
3322
+ for (const fn of functions) {
3323
+ if (fn.directives.some((d) => d.kind === "case")) {
3324
+ reportInjectedDomainExceeds(fn.name, fn.node, locFromNode(fn.node));
3325
+ }
3326
+ }
3327
+ for (const candidate of synthCandidates) {
3328
+ const singleExportHit = singleExportFn !== null && candidate.node === singleExportFn;
3329
+ const nameRoutes = (r) => r.fnName === candidate.name || r.targetExport === candidate.name || (r.targetAliases?.includes(candidate.name) ?? false) || singleExportHit && (r.fnModule !== void 0 || r.targetModule !== void 0);
3330
+ const matchingLocal = (r) => {
3331
+ const targetsThisFile = r.targetModule === void 0 || normalizeModulePath(r.targetModule) === currentModulePath;
3332
+ if (!targetsThisFile) return false;
3333
+ return nameRoutes(r);
3334
+ };
3335
+ const matchingExternal = (r) => {
3336
+ const attributed = r.fnModule !== void 0 && normalizeModulePath(r.fnModule) === currentModulePath || r.targetModule !== void 0 && normalizeModulePath(r.targetModule) === currentModulePath;
3337
+ if (!attributed) return false;
3338
+ return nameRoutes(r);
3339
+ };
3340
+ const records = dedupeCallRecords(
3341
+ [
3342
+ ...callRecords.filter(matchingLocal),
3343
+ ...(externalCallRecords ?? []).filter(matchingExternal)
3344
+ ].filter((r) => !isLeakedCallRecord(r))
3345
+ );
3346
+ reportInjectedDomainExceeds(candidate.name, candidate.node, candidate.analysis.loc);
3347
+ if (records.length > 0) {
3348
+ const informativeness = (r) => {
3349
+ if (r.resultAbs.shape.k === "unknown") return 2;
3350
+ if (r.resultAbs.shape.k === "never") return 1;
3351
+ return 0;
3352
+ };
3353
+ const ordered = records.map((r, i) => ({ r, i })).sort((a, b) => informativeness(a.r) - informativeness(b.r) || a.i - b.i).map(({ r }) => r);
3354
+ const precise = ordered.slice(0, callSiteBudget);
3355
+ for (const rec of precise) {
3356
+ let absRaw;
3357
+ let absResult;
3358
+ if (rec.resultAbs.shape.k !== "never" && rec.argAbs.length > 0) {
3359
+ absRaw = tryEvalAbsRaw(source, candidate.name, rec.argAbs, filePath, mockSeedsToAbsMocks(seeds));
3360
+ if (absRaw) {
3361
+ if (absIsBetter(absRaw, rec.resultAbs)) {
3362
+ absResult = absRaw;
3363
+ }
3364
+ }
3365
+ }
3366
+ if (!absRaw && rec.resultAbs.shape.k !== "never") {
3367
+ absRaw = rec.resultAbs;
3368
+ }
3369
+ const caseAbs = absResult ?? rec.resultAbs;
3370
+ const caseResult2 = {
3371
+ name: `call@L${rec.callLoc?.line ?? candidate.analysis.loc.start.line}`,
3372
+ argAbs: [...rec.argAbs],
3373
+ abs: caseAbs,
3374
+ throwsAbs: rec.throwsAbs,
3375
+ source: "callsite"
3376
+ };
3377
+ tryAttachIntension(caseResult2, source, candidate.name);
3378
+ if (absRaw) attachAbsToIntension(caseResult2, absRaw, candidate.name);
3379
+ candidate.analysis.cases.push(caseResult2);
3380
+ }
3381
+ const remaining = ordered.slice(callSiteBudget).filter((rec) => !rec.argAbs.some((a) => a.shape.k === "unknown" && !a.term));
3382
+ if (remaining.length > 0) {
3383
+ const fnNode2 = resolveFunctionNode(candidate.node);
3384
+ const paramCount = extractParamNames(fnNode2).length;
3385
+ const widenedArgsAbs = Array.from(
3386
+ { length: paramCount },
3387
+ (_, i) => (
3388
+ // 缺参按真实 JS 语义 widen 成 undefined 而非 unknown——可选参守卫
3389
+ // (target || [] 等)对 unknown 全塌,对 undefined 正常走默认分支
3390
+ widenJoinAbs(remaining.map((rec) => rec.argAbs[i] ?? undefAbs))
3391
+ )
3392
+ );
3393
+ let symAbs;
3394
+ if (isBPathCapable(source, envNames) && filePath) {
3395
+ const bSym = tryBPathCall(
3396
+ source,
3397
+ filePath,
3398
+ candidate.name,
3399
+ widenedArgsAbs,
3400
+ { envNames, mocks: mockSeedsToAbsMocks(seeds) }
3401
+ );
3402
+ if (bSym && (bHostedEval || !(bSym.shape.k === "unknown" && !bSym.term))) {
3403
+ symAbs = bSym;
3404
+ }
3405
+ }
3406
+ if (!symAbs && !bHostedEval) {
3407
+ const absTry = tryEvalAbsRaw(
3408
+ source,
3409
+ candidate.name,
3410
+ widenedArgsAbs,
3411
+ filePath,
3412
+ mockSeedsToAbsMocks(seeds)
3413
+ );
3414
+ const weak = !!absTry && absTry.shape.k === "unknown" && (!absTry.term || absTry.term.op === "lit" && absTry.term.value === void 0);
3415
+ if (absTry && !weak && absTry.shape.k !== "never" && absTry.conf !== "opaque") {
3416
+ symAbs = absTry;
3417
+ }
3418
+ }
3419
+ const symCase = {
3420
+ name: "call@symbolic",
3421
+ argAbs: widenedArgsAbs,
3422
+ // B4:超预算聚合必须 #widened(可解释降级)
3423
+ abs: symAbs ? { ...symAbs, conf: symAbs.conf === "exact" ? "widened" : symAbs.conf } : absUnknown5,
3424
+ throwsAbs: neverAbs,
3425
+ source: "callsite",
3426
+ aggregatedFrom: remaining.length
3427
+ };
3428
+ tryAttachIntension(symCase, source, candidate.name);
3429
+ if (symAbs) attachAbsToIntension(symCase, symAbs, candidate.name);
3430
+ candidate.analysis.cases.push(symCase);
3431
+ }
3432
+ candidate.analysis.combinedAbs = collapseAbsLits(
3433
+ records.map((r) => r.resultAbs),
3434
+ COLLAPSE_LITERAL_THRESHOLD
3435
+ );
3436
+ continue;
3437
+ }
3438
+ const fnNode = resolveFunctionNode(candidate.node);
3439
+ const argAbsEntry = extractParamNames(fnNode).map(() => anyAbs);
3440
+ let entryAbs;
3441
+ let entryThrowsAbs = makeAbsVal2({ k: "never" }, void 0, void 0, "exact");
3442
+ const entryEffects = [];
3443
+ setMayThrowCollector((e) => entryEffects.push(e));
3444
+ try {
3445
+ if (isBPathCapable(source, envNames) && filePath) {
3446
+ const bEntryFull = tryBPathCallFull(
3447
+ source,
3448
+ filePath,
3449
+ candidate.analysis.name,
3450
+ argAbsEntry,
3451
+ { envNames, mocks: mockSeedsToAbsMocks(seeds), collectMemberDiags: true }
3452
+ ) ?? (candidate.assignedName ? tryBPathCallFull(source, filePath, candidate.assignedName, argAbsEntry, {
3453
+ envNames,
3454
+ mocks: mockSeedsToAbsMocks(seeds)
3455
+ }) : void 0);
3456
+ if (bEntryFull?.memberDiags?.length) {
3457
+ for (const d of bEntryFull.memberDiags) {
3458
+ pushBMemberDiag(d, candidate.analysis.loc.start.line);
3459
+ }
3460
+ }
3461
+ const bEntry = bEntryFull?.result;
3462
+ if (bEntry && (bHostedEval || !(bEntry.shape.k === "unknown" && !bEntry.term))) {
3463
+ entryAbs = bEntry;
3464
+ if (bEntryFull?.throws) entryThrowsAbs = bEntryFull.throws;
3465
+ }
3466
+ }
3467
+ if (!entryAbs) {
3468
+ if (bHostedEval) {
3469
+ entryAbs = absUnknown5;
3470
+ entryThrowsAbs = makeAbsVal2({ k: "never" }, void 0, void 0, "exact");
3471
+ } else {
3472
+ const absEntry = tryEvalEntryAbs(source, candidate.analysis.name, argAbsEntry, filePath, seeds.seedVars, candidate.assignedName);
3473
+ if (absEntry) {
3474
+ entryAbs = absEntry;
3475
+ entryThrowsAbs = makeAbsVal2({ k: "never" }, void 0, void 0, "exact");
3476
+ } else {
3477
+ entryAbs = absUnknown5;
3478
+ entryThrowsAbs = makeAbsVal2({ k: "never" }, void 0, void 0, "exact");
3479
+ }
3480
+ }
3481
+ }
3482
+ } finally {
3483
+ setMayThrowCollector(null);
3484
+ }
3485
+ const softThrows = mayThrowEffectsToAbs(entryEffects);
3486
+ if (entryThrowsAbs.shape.k === "never" && softThrows.shape.k !== "never") {
3487
+ entryThrowsAbs = softThrows;
3488
+ } else if (entryThrowsAbs.shape.k !== "never" && softThrows.shape.k !== "never") {
3489
+ const hard = formatThrowsAbs(entryThrowsAbs);
3490
+ const soft = formatThrowsAbs(softThrows);
3491
+ if (hard && soft && hard !== soft) {
3492
+ entryThrowsAbs = makeAbsVal2(
3493
+ { k: "sum", members: [entryThrowsAbs, softThrows] },
3494
+ void 0,
3495
+ void 0,
3496
+ "exact"
3497
+ );
3498
+ }
3499
+ }
3500
+ const caseResult = {
3501
+ name: `entry@L${candidate.analysis.loc.start.line}`,
3502
+ argAbs: argAbsEntry,
3503
+ abs: entryAbs,
3504
+ throwsAbs: entryThrowsAbs
3505
+ };
3506
+ tryAttachIntension(caseResult, source, candidate.analysis.name);
3507
+ attachAbsToIntension(caseResult, entryAbs, candidate.analysis.name);
3508
+ candidate.analysis.cases.push(caseResult);
3509
+ candidate.analysis.entryOnly = true;
3510
+ candidate.analysis.combinedAbs = entryAbs;
3511
+ attachHofSnapshot(candidate.analysis, source);
3512
+ try {
3513
+ const exportNames = localNamedExports(source);
3514
+ const isEntry = exportNames.has(candidate.analysis.name) || candidate.assignedName !== void 0 && exportNames.has(candidate.assignedName);
3515
+ if (isEntry) {
3516
+ const autoBind = interfaceConfig(
3517
+ findProjectConfig(dirname8(filePath))?.config
3518
+ ).autoBind;
3519
+ const eff = effectiveInterface(source, candidate.analysis.name, {
3520
+ loadModule: loadModule ?? defaultLoadModule,
3521
+ fromFile: filePath,
3522
+ ...autoBind === false ? { autoBind: false } : {}
3523
+ });
3524
+ if (!eff) {
3525
+ diagnostics.push({
3526
+ range: {
3527
+ start: candidate.analysis.loc.start,
3528
+ end: candidate.analysis.loc.start
3529
+ },
3530
+ severity: "info",
3531
+ message: `export '${candidate.analysis.name}' has no contract root and no call-site domain (entry-only)`,
3532
+ code: "nudo:interface-entry-only"
3533
+ });
3534
+ }
3535
+ }
3536
+ } catch {
3537
+ }
3538
+ }
3539
+ if (!bHostedEval) {
3540
+ buildNodeTypeMap(ast, globalEnv, nodeAbsMap);
3541
+ }
3542
+ const externalFunctions = synthesizeExternalFunctions(
3543
+ callRecords,
3544
+ filePath,
3545
+ analysisConfig(projectConfig?.config).callSiteBudget
3546
+ );
3547
+ setEnvHarvestConflictCollector(prevHarvestConflictCollector);
3548
+ for (const c of envHarvestConflicts) {
3549
+ const parts = [];
3550
+ if (c.exports.length > 0) parts.push(`export(s) ${c.exports.join(", ")}`);
3551
+ if (c.defaultOverwritten) parts.push("default");
3552
+ const detail = parts.length > 0 ? ` \u2014 ${parts.join("; ")}` : "";
3553
+ const loc = findModuleImportLoc(source, c.module);
3554
+ const range = loc ? {
3555
+ start: { line: loc.line, column: loc.column },
3556
+ end: { line: loc.line, column: loc.column + loc.length }
3557
+ } : { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } };
3558
+ diagnostics.push({
3559
+ range,
3560
+ severity: "warning",
3561
+ message: `handwritten @nudo:env wins over harvest on module "${c.module}"${detail}; harvest only fills missing slots (B8). code=nudo:env-harvest-conflict`,
3562
+ code: "nudo:env-harvest-conflict"
3563
+ });
3564
+ }
3565
+ try {
3566
+ const dual = dualEntryForFile(filePath);
3567
+ if (dual) {
3568
+ diagnostics.push({
3569
+ range: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
3570
+ severity: "info",
3571
+ message: dualEntryMessage(dual),
3572
+ code: "nudo:dual-entry",
3573
+ suggestions: [dualEntrySuggestion()]
3574
+ });
3575
+ }
3576
+ } catch {
3577
+ }
3578
+ return {
3579
+ functions: functionResults,
3580
+ diagnostics,
3581
+ bindings,
3582
+ nodeAbsMap,
3583
+ caseHints,
3584
+ ...externalFunctions.length > 0 ? { externalFunctions } : {}
3585
+ };
3586
+ }
3587
+
3588
+ // src/analyzer-orchestrate.ts
3589
+ async function analyzeFileAsync(filePath, source, activeCases, externalCallRecords, loadModule, caseMode = "all") {
3590
+ const envNames = collectEnvNames(filePath, source, true);
3591
+ noteEnvPathDeps(filePath, source);
3592
+ if (envNames.length > 0) {
3593
+ await preloadPathEnvs(envNames, dirname9(filePath));
3594
+ }
3595
+ return analyzeFile(filePath, source, activeCases, externalCallRecords, loadModule, caseMode);
3596
+ }
3597
+ function analyzeFile(filePath, source, activeCases, externalCallRecords, loadModule, caseMode = "all") {
3598
+ const projectConfig = findProjectConfig(dirname9(filePath));
3599
+ const cfg = analysisConfig(projectConfig?.config);
3600
+ const projectEnvNames = projectConfig?.config.env ?? [];
3601
+ const autoBind = interfaceConfig(projectConfig?.config).autoBind;
3602
+ const k = analysisFileCacheKey(
3603
+ filePath,
3604
+ source,
3605
+ activeCases,
3606
+ externalCallRecords,
3607
+ cfg,
3608
+ loadModule,
3609
+ projectEnvNames,
3610
+ autoBind !== false,
3611
+ caseMode
3612
+ );
3613
+ if (!k.noCache) {
3614
+ const hit = analysisCacheGet(k.filePath, k.source, k.auxKey);
3615
+ if (hit !== void 0) {
3616
+ return cloneAnalysisResult(hit);
3617
+ }
3618
+ }
3619
+ const result = analyzeFileUncached(
3620
+ filePath,
3621
+ source,
3622
+ activeCases,
3623
+ externalCallRecords,
3624
+ loadModule,
3625
+ caseMode
3626
+ );
3627
+ if (!k.noCache) {
3628
+ analysisCacheSet(k.filePath, k.source, k.auxKey, result);
3629
+ }
3630
+ return cloneAnalysisResult(result);
3631
+ }
3632
+
3633
+ export {
3634
+ buildModuleGraph,
3635
+ computeDirtySet,
3636
+ topoSortDirty,
3637
+ locFromNode,
3638
+ defaultLoadModule,
3639
+ analysisFileCacheKey,
3640
+ defaultAbsLoadModule,
3641
+ getAbsModuleCacheSize,
3642
+ clearAbsModuleCache,
3643
+ evictAbsModuleCacheFiles,
3644
+ evalAbsModuleGraph,
3645
+ collectAbsBindingsFromGraph,
3646
+ getEnvPathDepsSize,
3647
+ noteEnvPathDeps,
3648
+ envPathDependents,
3649
+ clearEnvPathDeps,
3650
+ isEnvTemplatePath,
3651
+ applyMockModuleDirectives,
3652
+ applyMockModuleDirectivesFromSource,
3653
+ clearAnalysisFileCache,
3654
+ getAnalysisFileCacheSize,
3655
+ trimAnalysisFileCache,
3656
+ evictAnalysisFileCacheForFiles,
3657
+ getFnAnalysisCacheSize,
3658
+ clearFnAnalysisCache,
3659
+ trimFnAnalysisCache,
3660
+ evictFnAnalysisCacheForFiles,
3661
+ collectEnvGlobals,
3662
+ collectEnvModules,
3663
+ setEnvHarvestConflictCollector,
3664
+ getEnvHarvestConflictCollector,
3665
+ mergeHarvestUnderEnv,
3666
+ collectBPathReplacements,
3667
+ isBPathCapable,
3668
+ clearBPathCache,
3669
+ getBPathCacheSize,
3670
+ evictBPathCacheForFiles,
3671
+ trimBPathCache,
3672
+ tryRunBPath,
3673
+ tryBPathCallFull,
3674
+ tryBPathCall,
3675
+ mockSeedsToAbsMocks,
3676
+ mockDirectivesToAbsSeeds,
3677
+ mockSeedsForSource,
3678
+ collectEnvNames,
3679
+ collectCallRecords,
3680
+ collectBPathDiagnostics,
3681
+ detectDualEntryFromPackageJson,
3682
+ findOwningPackage,
3683
+ dualEntryForFile,
3684
+ dualEntryIssueForFile,
3685
+ analyzeFileAsync,
3686
+ analyzeFile
3687
+ };