@nudojs/service 5.0.0-beta.0 → 5.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,23 +1,73 @@
1
1
  import {
2
- bareSpecToAbsModules
3
- } from "./chunk-U7JYTRIB.js";
4
- import {
2
+ BoundedLruMap,
5
3
  analysisConfig,
6
4
  findProjectConfig,
7
5
  getSessionCacheLimits,
8
6
  interfaceConfig,
9
- resolveNpmJsEntry
10
- } from "./chunk-YLALZXTZ.js";
11
- import {
12
- BoundedLruMap,
13
7
  loadEnvs,
14
- preloadPathEnvs
15
- } from "./chunk-PVJIMQRI.js";
8
+ preloadPathEnvs,
9
+ resolveNpmJsEntry,
10
+ resolveNpmNudo
11
+ } from "./chunk-ZXVAX7DE.js";
16
12
 
17
13
  // src/analyzer-module-load.ts
18
14
  import { readFileSync, existsSync, statSync } from "fs";
19
15
  import { resolve, dirname } from "path";
20
16
  import { parse } from "@nudojs/parser";
17
+ function resolveModule(source, fromDir) {
18
+ const extensions = [".js", ".ts", ".mjs"];
19
+ const nudoPath = resolveNpmNudo(source, fromDir);
20
+ if (nudoPath) {
21
+ const src = readFileSync(nudoPath, "utf-8");
22
+ return { ast: parse(src), filePath: nudoPath };
23
+ }
24
+ const basePath = resolve(fromDir, source);
25
+ for (const ext of ["", ...extensions]) {
26
+ const candidate = basePath + ext;
27
+ if (!existsSync(candidate)) continue;
28
+ if (statSync(candidate).isDirectory()) {
29
+ let entry = null;
30
+ const pkgPath = resolve(candidate, "package.json");
31
+ if (existsSync(pkgPath)) {
32
+ try {
33
+ const main = JSON.parse(readFileSync(pkgPath, "utf-8")).main;
34
+ if (typeof main === "string") {
35
+ for (const e of ["", ...extensions]) {
36
+ const p = resolve(candidate, main + e);
37
+ if (existsSync(p) && statSync(p).isFile()) {
38
+ entry = p;
39
+ break;
40
+ }
41
+ }
42
+ }
43
+ } catch {
44
+ }
45
+ }
46
+ if (!entry) {
47
+ for (const e of ["", ...extensions]) {
48
+ const p = resolve(candidate, "index" + e);
49
+ if (existsSync(p) && statSync(p).isFile()) {
50
+ entry = p;
51
+ break;
52
+ }
53
+ }
54
+ }
55
+ if (!entry) return null;
56
+ const src2 = readFileSync(entry, "utf-8");
57
+ return { ast: parse(src2), filePath: entry };
58
+ }
59
+ if (candidate.endsWith(".json")) {
60
+ try {
61
+ return { ast: parse("module.exports = undefined;"), filePath: candidate, json: JSON.parse(readFileSync(candidate, "utf-8")) };
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+ const src = readFileSync(candidate, "utf-8");
67
+ return { ast: parse(src), filePath: candidate };
68
+ }
69
+ return null;
70
+ }
21
71
  function resolveImportPath(specifier, fromDir) {
22
72
  const basePath = resolve(fromDir, specifier);
23
73
  for (const ext of ["", ".js", ".ts", ".mjs"]) {
@@ -121,2511 +171,2512 @@ function topoSortDirty(imports, dirty) {
121
171
  return ordered;
122
172
  }
123
173
 
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
- }
174
+ // src/analyzer-ast.ts
175
+ import traverse from "@babel/traverse";
176
+ import {
177
+ formalParamsFromNodes,
178
+ formalParamDisplayNames,
179
+ unknown as absUnknown,
180
+ abs as makeAbsVal,
181
+ numLit,
182
+ strLit,
183
+ boolLit
184
+ } from "@nudojs/core";
185
+ import {
186
+ programBody,
187
+ getDeclarations,
188
+ getDeclaratorInit,
189
+ getFirstDeclaratorId,
190
+ getExpressionStatementExpression,
191
+ unwrapExport,
192
+ asIdentifier,
193
+ asAssignmentExpression,
194
+ classIdName,
195
+ fnOrClassIdLoc,
196
+ isModuleExportsMember,
197
+ memberPropertyKey,
198
+ classInstanceMethods,
199
+ exportSpecifierLocalName,
200
+ exportSpecifierExportedName,
201
+ unwrapDefaultExport
202
+ } from "@nudojs/parser";
203
+ function locFromNode(node) {
204
+ return {
205
+ start: { line: node.loc?.start.line ?? 1, column: node.loc?.start.column ?? 0 },
206
+ end: { line: node.loc?.end.line ?? 1, column: node.loc?.end.column ?? 0 }
207
+ };
149
208
  }
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}`;
209
+ function extractParamNames(node) {
210
+ const fn = node.type === "ExportDefaultDeclaration" ? node.declaration : node;
211
+ if (fn.type === "FunctionDeclaration" || fn.type === "FunctionExpression" || fn.type === "ArrowFunctionExpression" || fn.type === "ClassMethod" || fn.type === "ObjectMethod" || fn.type === "TSDeclareMethod") {
212
+ return formalParamDisplayNames(formalParamsFromNodes(fn.params));
168
213
  }
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);
214
+ if (fn.type === "VariableDeclaration") {
215
+ const init = getDeclaratorInit(getDeclarations(fn)[0]);
216
+ if (init && (init.type === "FunctionExpression" || init.type === "ArrowFunctionExpression")) {
217
+ return formalParamDisplayNames(formalParamsFromNodes(init.params));
183
218
  }
184
- } catch {
185
- noCache = true;
186
- depSeg = "fperr";
187
219
  }
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
- };
220
+ return [];
195
221
  }
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
- };
222
+ function resolveFunctionNode(node) {
223
+ if (node.type === "ExportNamedDeclaration" && node.declaration) return resolveFunctionNode(node.declaration);
224
+ if (node.type === "ExportDefaultDeclaration") return resolveFunctionNode(node.declaration);
225
+ if (node.type === "VariableDeclaration") {
226
+ const init = getDeclaratorInit(getDeclarations(node)[0]);
227
+ if (init && (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression")) return init;
228
+ }
229
+ return node;
206
230
  }
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
- };
231
+ function fnNameLoc(node, fallback) {
232
+ const { declaration: d } = unwrapExport(node);
233
+ if (d.type === "VariableDeclaration") {
234
+ const id = getFirstDeclaratorId(d);
235
+ if (id?.loc) return id.loc;
236
+ }
237
+ const idLoc = fnOrClassIdLoc(d);
238
+ if (idLoc) return idLoc;
239
+ return fallback;
226
240
  }
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
- };
241
+ function isFnExprValue(node) {
242
+ return !!node && (node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression");
232
243
  }
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;
244
+ function namedFnExprId(node) {
245
+ return node.type === "FunctionExpression" && node.id ? node.id.name : null;
240
246
  }
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
+ function isModuleExportsTarget(node) {
248
+ return node.type === "MemberExpression" && !node.computed && isModuleExportsMember(node);
247
249
  }
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
- });
250
+ function deepestAssignValue(expr) {
251
+ let cur = expr;
252
+ while (cur) {
253
+ const assign = asAssignmentExpression(cur);
254
+ if (!assign) break;
255
+ cur = assign.right;
269
256
  }
270
- return out;
257
+ return cur ?? null;
271
258
  }
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 {
259
+ function assignmentChainName(expr) {
260
+ let cur = expr;
261
+ while (cur) {
262
+ const assign = asAssignmentExpression(cur);
263
+ if (!assign) break;
264
+ const target = assign.left;
265
+ if (target.type === "Identifier") return target.name;
266
+ if (target.type === "MemberExpression") {
267
+ if (isModuleExportsTarget(target)) {
268
+ cur = assign.right;
269
+ continue;
280
270
  }
271
+ return memberPropertyKey(target);
281
272
  }
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
- }
273
+ return null;
299
274
  }
300
275
  return null;
301
276
  }
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);
277
+ function collectTopLevelFunctions(ast) {
278
+ const results = [];
279
+ if (ast.type !== "File") return results;
280
+ const body = programBody(ast);
281
+ const classDecls = /* @__PURE__ */ new Map();
282
+ const exportedClassNames = /* @__PURE__ */ new Set();
283
+ const collectClassMethods = (cname, decl, stmt) => {
284
+ for (const { name: keyName, node: methodNode } of classInstanceMethods(decl)) {
285
+ results.push({
286
+ name: `${cname}.${keyName}`,
287
+ node: methodNode,
288
+ stmt,
289
+ noDeclaration: true
290
+ });
339
291
  }
340
292
  };
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));
293
+ for (const stmt of body) {
294
+ const className = classIdName(stmt);
295
+ if (stmt.type === "ClassDeclaration" && className) {
296
+ classDecls.set(className, { node: stmt, stmt });
297
+ continue;
298
+ }
299
+ if (stmt.type === "ExportNamedDeclaration") {
300
+ const d = stmt.declaration;
301
+ const exportedClassName = classIdName(d);
302
+ if (d?.type === "ClassDeclaration" && exportedClassName) {
303
+ classDecls.set(exportedClassName, { node: d, stmt });
304
+ exportedClassNames.add(exportedClassName);
350
305
  continue;
351
306
  }
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;
307
+ if (!d && stmt.specifiers) {
308
+ for (const spec of stmt.specifiers) {
309
+ if (spec.type !== "ExportSpecifier") continue;
310
+ const local = exportSpecifierLocalName(spec);
311
+ const exported = exportSpecifierExportedName(spec);
312
+ if (!local) continue;
313
+ if (classDecls.has(local) || body.some((s) => classIdName(s) === local)) {
314
+ exportedClassNames.add(local);
315
+ }
316
+ if (exported === "default") exportedClassNames.add(local);
360
317
  }
318
+ continue;
361
319
  }
362
- const bare = bareSpecToAbsModules(spec, fromFile);
363
- if (bare) modules[spec] = bare;
320
+ }
321
+ if (stmt.type === "ExportDefaultDeclaration") {
322
+ const d = stmt.declaration;
323
+ const defaultClassName = classIdName(d);
324
+ if (d?.type === "ClassDeclaration" && defaultClassName) {
325
+ classDecls.set(defaultClassName, { node: d, stmt });
326
+ exportedClassNames.add(defaultClassName);
327
+ } else {
328
+ const idName = asIdentifier(d)?.name;
329
+ if (idName) exportedClassNames.add(idName);
330
+ }
331
+ continue;
364
332
  }
365
333
  }
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
334
  for (const stmt of body) {
398
- if (stmt.type === "FunctionDeclaration" && stmt.id && stmt.params) {
399
- out.set(stmt.id.name, stmt.params.map(paramNameOf));
335
+ const decl = resolveFunctionNode(stmt);
336
+ if (decl.type === "FunctionDeclaration" && decl.id) {
337
+ results.push({ name: decl.id.name, node: decl, stmt, noDeclaration: false });
338
+ continue;
339
+ }
340
+ if (decl.type === "ClassDeclaration" && decl.id?.name) {
341
+ continue;
342
+ }
343
+ if (stmt.type === "ExportNamedDeclaration") {
344
+ const d = resolveFunctionNode(stmt);
345
+ if (d.type === "FunctionDeclaration" && d.id) {
346
+ results.push({ name: d.id.name, node: d, stmt, noDeclaration: false });
347
+ }
348
+ if (d.type === "ClassDeclaration" && d.id?.name) {
349
+ }
350
+ continue;
400
351
  }
401
352
  if (stmt.type === "ExportDefaultDeclaration") {
402
353
  const d = stmt.declaration;
403
- if (d && (d.type === "FunctionDeclaration" || d.type === "ArrowFunctionExpression") && d.params) {
404
- out.set("default", d.params.map(paramNameOf));
354
+ if (d.type === "FunctionDeclaration") {
355
+ if (d.id) {
356
+ results.push({ name: d.id.name, node: d, stmt, noDeclaration: false });
357
+ } else {
358
+ results.push({ name: "default", node: d, stmt, noDeclaration: true });
359
+ }
360
+ } else if (isFnExprValue(d)) {
361
+ results.push({ name: "default", node: d, stmt, noDeclaration: true });
362
+ }
363
+ continue;
364
+ }
365
+ if (stmt.type === "VariableDeclaration") {
366
+ for (const declarator of getDeclarations(stmt)) {
367
+ const id = asIdentifier(declarator.id);
368
+ if (id && isFnExprValue(declarator.init)) {
369
+ results.push({ name: id.name, node: declarator.init, stmt, noDeclaration: true });
370
+ }
371
+ }
372
+ continue;
373
+ }
374
+ const expr = getExpressionStatementExpression(stmt);
375
+ if (expr?.type === "AssignmentExpression") {
376
+ const fn = deepestAssignValue(expr);
377
+ if (isFnExprValue(fn)) {
378
+ const name = namedFnExprId(fn) ?? assignmentChainName(expr) ?? "default";
379
+ const chain = assignmentChainName(expr);
380
+ const assignedName = namedFnExprId(fn) && chain && chain !== name ? chain : void 0;
381
+ results.push({ name, node: fn, stmt, noDeclaration: true, ...assignedName ? { assignedName } : {} });
405
382
  }
406
383
  }
407
384
  }
408
- return out;
385
+ for (const cname of exportedClassNames) {
386
+ const hit = classDecls.get(cname);
387
+ if (!hit) continue;
388
+ collectClassMethods(cname, hit.node, hit.stmt);
389
+ }
390
+ return results;
409
391
  }
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;
392
+ function findSingleModuleExportsFunction(ast) {
393
+ if (ast.type !== "File") return null;
394
+ let found = null;
395
+ for (const stmt of programBody(ast)) {
396
+ const expr = getExpressionStatementExpression(stmt);
397
+ if (expr?.type !== "AssignmentExpression") continue;
398
+ const fn = deepestAssignValue(expr);
399
+ if (!isFnExprValue(fn)) continue;
400
+ let targetsModuleExports = false;
401
+ let cur = expr;
402
+ while (cur) {
403
+ const assign = asAssignmentExpression(cur);
404
+ if (!assign) break;
405
+ if (isModuleExportsTarget(assign.left)) {
406
+ targetsModuleExports = true;
407
+ break;
408
+ }
409
+ cur = assign.right;
431
410
  }
432
- if (k === "default") def = absVal;
433
- else named[k] = absVal;
411
+ if (!targetsModuleExports) continue;
412
+ if (found) return null;
413
+ found = fn;
434
414
  }
435
- const out = { named };
436
- if (def) out.default = def;
437
- return out;
415
+ return found;
438
416
  }
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 });
417
+ function collectBindings(ast, env, bindings, absBinds) {
418
+ if (ast.type !== "File") return;
419
+ const setBinding = (name, loc) => {
420
+ const absVal = absBinds?.get(name) ?? absUnknown;
421
+ bindings.set(name, {
422
+ abs: absVal,
423
+ loc
424
+ });
451
425
  };
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;
426
+ const walk = (body) => {
427
+ for (const stmt of body) {
428
+ if (stmt.type === "FunctionDeclaration" && stmt.id) {
429
+ setBinding(stmt.id.name, locFromNode(stmt));
430
+ }
431
+ if (stmt.type === "VariableDeclaration") {
432
+ for (const decl of stmt.declarations) {
433
+ if (decl.id.type === "Identifier") {
434
+ setBinding(decl.id.name, locFromNode(decl));
435
+ }
471
436
  }
472
- } catch {
473
437
  }
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;
438
+ if (stmt.type === "ClassDeclaration" && stmt.id) {
439
+ setBinding(stmt.id.name, locFromNode(stmt));
495
440
  }
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
- );
441
+ if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
442
+ walk([stmt.declaration]);
522
443
  }
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
444
  }
562
- );
563
- return { modules, byPath: cache, issues };
445
+ };
446
+ walk(programBody(ast));
564
447
  }
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;
448
+ function nullLitAbs() {
449
+ return makeAbsVal({ k: "unknown" }, { op: "lit", value: null }, void 0, "exact");
597
450
  }
598
- function collectAbsBindingsFromGraph(source, filePath, opts = {}) {
599
- const out = /* @__PURE__ */ new Map();
451
+ function buildNodeTypeMap(ast, env, nodeAbsMap) {
452
+ const traverseFn = unwrapDefaultExport(traverse);
600
453
  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);
454
+ traverseFn(ast, {
455
+ enter(path) {
456
+ const node = path.node;
457
+ try {
458
+ const setAbs = (a) => {
459
+ nodeAbsMap.set(node, a);
460
+ };
461
+ if (node.type === "Identifier" && node.name !== "undefined" && path.parentPath?.node.type !== "FunctionDeclaration" && path.parentPath?.node.type !== "VariableDeclarator") {
462
+ if (env.has(node.name)) {
463
+ setAbs(env.lookup(node.name));
464
+ }
465
+ }
466
+ if (node.type === "NumericLiteral") {
467
+ setAbs(numLit(node.value));
468
+ }
469
+ if (node.type === "StringLiteral") {
470
+ setAbs(strLit(node.value));
471
+ }
472
+ if (node.type === "BooleanLiteral") {
473
+ setAbs(boolLit(node.value));
474
+ }
475
+ if (node.type === "NullLiteral") {
476
+ setAbs(nullLitAbs());
477
+ }
478
+ } catch {
479
+ }
613
480
  }
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
- }
481
+ });
622
482
  } catch {
623
483
  }
624
- return out;
625
484
  }
626
485
 
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) {
486
+ // src/load-module.ts
487
+ import { existsSync as existsSync2, statSync as statSync2, readFileSync as readFileSync2 } from "fs";
488
+ import { dirname as dirname2, resolve as resolve2, join } from "path";
489
+ function defaultLoadModule(spec, fromFile) {
641
490
  if (!spec.startsWith(".") && !spec.startsWith("/")) return void 0;
642
491
  try {
643
- const base = dirname4(resolve4(fromFile));
644
- const p = resolve4(base, spec);
492
+ const base = dirname2(resolve2(fromFile));
493
+ const p = resolve2(base, spec);
645
494
  for (const cand of [
646
495
  p,
647
496
  `${p}.js`,
648
497
  `${p}.mjs`,
649
498
  `${p}.ts`,
650
- join2(p, "index.js"),
651
- join2(p, "index.mjs"),
652
- join2(p, "index.ts")
499
+ join(p, "index.js"),
500
+ join(p, "index.mjs"),
501
+ join(p, "index.ts")
653
502
  ]) {
654
- if (existsSync3(cand) && !statSync4(cand).isDirectory()) return cand;
655
- }
656
- return void 0;
503
+ if (existsSync2(cand) && !statSync2(cand).isDirectory()) {
504
+ return readFileSync2(cand, "utf-8");
505
+ }
506
+ }
507
+ return void 0;
657
508
  } catch {
658
509
  return void 0;
659
510
  }
660
511
  }
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;
512
+
513
+ // src/analyzer-cache.ts
514
+ import { loadModuleDepsFingerprint, hashSource, stableAnalyzeKeySource } from "@nudojs/core/internal";
515
+ var externalRecordIds = /* @__PURE__ */ new WeakMap();
516
+ var nextExternalRecordId = 1;
517
+ function analysisFileCacheKey(filePath, source, activeCases, externalCallRecords, analysisCfg, loadModule, projectEnvNames, autoBind, caseMode = "all") {
518
+ let cases = "-";
519
+ if (activeCases && activeCases.size > 0) {
520
+ cases = [...activeCases.entries()].sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0).map(([k, v]) => `${k}=${v}`).join(",");
669
521
  }
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);
522
+ let ext = "-";
523
+ if (externalCallRecords && externalCallRecords.length > 0) {
524
+ let id = externalRecordIds.get(externalCallRecords);
525
+ if (id === void 0) {
526
+ id = nextExternalRecordId++;
527
+ externalRecordIds.set(externalCallRecords, id);
679
528
  }
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);
529
+ ext = `n${externalCallRecords.length}#id${id}`;
530
+ }
531
+ const cfg = analysisCfg ? `m=${analysisCfg.mode}|e=${analysisCfg.evalMissingSlot}|b=${analysisCfg.callSiteBudget}|d=${analysisCfg.diagnostics}` : "-";
532
+ const lm = loadModule !== void 0 && loadModule !== defaultLoadModule ? "lm1" : "lm0";
533
+ const envSeg = projectEnvNames && projectEnvNames.length > 0 ? projectEnvNames.join(",") : "-";
534
+ const abSeg = autoBind === false ? "ab0" : autoBind === true ? "ab1" : "ab?";
535
+ const effectiveLoader = loadModule ?? defaultLoadModule;
536
+ let depSeg = "-";
537
+ let noCache = false;
538
+ try {
539
+ const fp = loadModuleDepsFingerprint(source, effectiveLoader, filePath);
540
+ if (fp.truncated) {
541
+ noCache = true;
542
+ depSeg = `trunc:${fp.paths.length}`;
543
+ } else {
544
+ depSeg = hashSource(fp.fp);
683
545
  }
684
- set.add(src);
546
+ } catch {
547
+ noCache = true;
548
+ depSeg = "fperr";
685
549
  }
550
+ return {
551
+ filePath,
552
+ // 尾部无 @nudo 注释/空行不进键:comment-only 编辑命中 AnalysisResult
553
+ source: stableAnalyzeKeySource(source),
554
+ auxKey: `${cases}\0${ext}\0${cfg}\0${lm}\0${envSeg}\0${abSeg}\0${depSeg}\0cm=${caseMode}`,
555
+ noCache
556
+ };
686
557
  }
687
- function envPathDependents(envPath) {
688
- return [...envDependents.peek(norm(envPath)) ?? []];
558
+ function cloneAnalysisResult(r) {
559
+ return {
560
+ functions: r.functions.map(cloneFunctionAnalysis),
561
+ diagnostics: r.diagnostics.map((d) => ({ ...d })),
562
+ bindings: new Map(r.bindings),
563
+ // Node 键与 AST LRU 共享身份;Map 浅拷贝即可
564
+ nodeAbsMap: new Map(r.nodeAbsMap),
565
+ caseHints: r.caseHints.map((h) => ({ ...h })),
566
+ ...r.externalFunctions ? { externalFunctions: r.externalFunctions.map(cloneFunctionAnalysis) } : {}
567
+ };
689
568
  }
690
- function clearEnvPathDeps() {
691
- envDependents.clear();
569
+ function cloneFunctionAnalysis(a) {
570
+ return {
571
+ ...a,
572
+ paramNames: [...a.paramNames],
573
+ ...a.formals ? { formals: a.formals.map((f) => ({ ...f })) } : {},
574
+ cases: a.cases.map((c) => ({
575
+ ...c,
576
+ argAbs: [...c.argAbs],
577
+ ...c.intension ? { intension: { ...c.intension } } : {}
578
+ })),
579
+ loc: { start: { ...a.loc.start }, end: { ...a.loc.end } },
580
+ ...a.hof ? {
581
+ hof: {
582
+ ...a.hof.fnRels ? { fnRels: a.hof.fnRels.map((r) => ({ ...r })) } : {},
583
+ ...a.hof.entryShapes ? { entryShapes: a.hof.entryShapes.map((s) => ({ ...s })) } : {},
584
+ ...a.hof.symbolic ? { symbolic: a.hof.symbolic } : {}
585
+ }
586
+ } : {}
587
+ };
692
588
  }
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);
589
+ function shiftSourceLoc(loc, lineDelta) {
590
+ return {
591
+ start: { line: loc.start.line + lineDelta, column: loc.start.column },
592
+ end: { line: loc.end.line + lineDelta, column: loc.end.column }
593
+ };
696
594
  }
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` };
595
+ function shiftDiagnosticLines(d, lineDelta) {
596
+ if (lineDelta === 0) return { ...d };
597
+ const out = { ...d, range: shiftSourceLoc(d.range, lineDelta) };
598
+ if (d.origin) {
599
+ out.origin = { line: d.origin.line + lineDelta, column: d.origin.column };
733
600
  }
601
+ return out;
602
+ }
603
+ function shiftCallRecordLines(r, lineDelta) {
604
+ if (lineDelta === 0 || !r.callLoc) return { ...r };
734
605
  return {
735
- ok: true,
736
- run,
737
- absPath: entryFile,
738
- source,
739
- modules
606
+ ...r,
607
+ callLoc: { line: r.callLoc.line + lineDelta, column: r.callLoc.column }
740
608
  };
741
609
  }
742
610
 
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
- };
611
+ // src/abs-modules-graph.ts
612
+ import { readFileSync as readFileSync3, statSync as statSync3 } from "fs";
613
+ import { dirname as dirname3, resolve as resolve3 } from "path";
614
+ import { parse as parse2 } from "@nudojs/parser";
615
+ import {
616
+ abs as makeAbs,
617
+ absFunction,
618
+ bindingsOf,
619
+ tryRunTranspiled,
620
+ callTranspiledExportFull,
621
+ foldStaticStringExpr,
622
+ unknown
623
+ } from "@nudojs/core";
624
+ import { bareSpecToAbsModules } from "@nudojs/harvester";
625
+ function seedFnsToMocks(seedVars, seedFns) {
626
+ const out = { ...seedVars };
627
+ for (const [name, fn] of Object.entries(seedFns)) {
628
+ out[name] = absFunction(fn.params, {
629
+ body: fn.body,
630
+ async: fn.async ?? false
631
+ });
750
632
  }
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);
633
+ return out;
634
+ }
635
+ function defaultAbsLoadModule(spec, fromFile) {
636
+ if (!spec.startsWith(".") && !spec.startsWith("/")) return void 0;
637
+ try {
638
+ const p = resolve3(dirname3(resolve3(fromFile)), spec);
639
+ for (const cand of [p, `${p}.js`, `${p}.mjs`, `${p}.ts`, resolve3(p, "index.js")]) {
640
+ try {
641
+ return readFileSync3(cand, "utf-8");
642
+ } catch {
643
+ }
644
+ }
645
+ return void 0;
646
+ } catch {
647
+ return void 0;
756
648
  }
757
- return { exports };
758
649
  }
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;
650
+ function relCandidates(spec, fromFile) {
651
+ const p = resolve3(dirname3(resolve3(fromFile)), spec);
652
+ return [p, `${p}.js`, `${p}.mjs`, `${p}.ts`, resolve3(p, "index.js")];
653
+ }
654
+ function resolveRel(spec, fromFile) {
655
+ if (!spec.startsWith(".") && !spec.startsWith("/")) return null;
656
+ for (const cand of relCandidates(spec, fromFile)) {
657
+ try {
658
+ readFileSync3(cand, "utf-8");
659
+ return cand;
660
+ } catch {
766
661
  }
767
- const v = mock.named[n];
768
- if (v !== void 0) named[n] = v;
769
662
  }
770
- return def !== void 0 ? { named, default: def } : { named };
663
+ return null;
771
664
  }
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 };
665
+ function importSpecs(source) {
666
+ try {
667
+ const file = parse2(source);
668
+ const out = [];
669
+ for (const stmt of file.program.body) {
670
+ if (stmt.type === "ImportDeclaration" || stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportAllDeclaration") {
671
+ const src = stmt.source;
672
+ if (src && typeof src.value === "string") {
673
+ out.push(src.value);
674
+ }
675
+ }
676
+ collectRequireSpecs(stmt, out);
677
+ }
678
+ return out;
679
+ } catch {
680
+ return [];
778
681
  }
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;
682
+ }
683
+ function collectRequireSpecs(node, out) {
684
+ const visit = (n) => {
685
+ if (!n || typeof n !== "object") return;
686
+ const o = n;
687
+ if (o.type === "CallExpression" && o.callee) {
688
+ const c = o.callee;
689
+ let spec;
690
+ if (c.type === "Identifier" && c.name === "require") {
691
+ spec = foldStaticStringExpr(o.arguments?.[0]);
692
+ } else if (c.type === "MemberExpression" && !c.computed && c.object?.type === "Identifier" && c.object.name === "require" && c.property?.type === "Identifier" && c.property.name === "resolve") {
693
+ spec = foldStaticStringExpr(o.arguments?.[0]);
694
+ }
695
+ if (spec !== void 0) out.push(spec);
797
696
  }
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;
697
+ for (const key of Object.keys(o)) {
698
+ if (key === "loc" || key === "start" || key === "end") continue;
699
+ const v = o[key];
700
+ if (Array.isArray(v)) v.forEach(visit);
701
+ else if (v && typeof v === "object") visit(v);
807
702
  }
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 });
703
+ };
704
+ visit(node);
856
705
  }
857
- function evictAnalysisFileCacheForFiles(files) {
858
- let n = 0;
859
- for (const f of files) {
860
- if (analysisByFile.delete(f)) n++;
706
+ function buildModulesForFile(source, fromFile, load, evalDep, depth, maxDepth, onMissing) {
707
+ const modules = {};
708
+ for (const spec of importSpecs(source)) {
709
+ if (spec.startsWith(".") || spec.startsWith("/")) {
710
+ const childPath = resolveRel(spec, fromFile);
711
+ if (!childPath) {
712
+ onMissing(spec, fromFile, relCandidates(spec, fromFile));
713
+ continue;
714
+ }
715
+ modules[spec] = evalDep(childPath, spec, fromFile, depth + 1);
716
+ } else if (!spec.startsWith("node:")) {
717
+ const entry = resolveNpmJsEntry(spec, dirname3(fromFile));
718
+ if (entry) {
719
+ const executed = evalDep(entry, spec, fromFile, depth + 1);
720
+ if (executed && (executed.default !== void 0 || Object.keys(executed.named ?? {}).length > 0)) {
721
+ modules[spec] = executed;
722
+ continue;
723
+ }
724
+ }
725
+ const bare = bareSpecToAbsModules(spec, fromFile);
726
+ if (bare) modules[spec] = bare;
727
+ }
861
728
  }
862
- return n;
729
+ void maxDepth;
730
+ return modules;
863
731
  }
864
-
865
- // src/fn-analysis-cache.ts
866
- var fnAnalysisCache = /* @__PURE__ */ new Map();
867
- function getFnAnalysisCacheSize() {
868
- return fnAnalysisCache.size;
732
+ var ABS_MODULE_CACHE_MAX = 512;
733
+ var absModuleCache = new BoundedLruMap(ABS_MODULE_CACHE_MAX);
734
+ function getAbsModuleCacheSize() {
735
+ return absModuleCache.size;
869
736
  }
870
- function clearFnAnalysisCache() {
871
- fnAnalysisCache.clear();
737
+ function clearAbsModuleCache() {
738
+ absModuleCache.clear();
872
739
  }
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
- }
740
+ function evictAbsModuleCacheFiles(paths) {
741
+ for (const p of paths) absModuleCache.delete(p);
880
742
  }
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;
743
+ function moduleLabel(p) {
744
+ const parts = p.split(/[/\\]/);
745
+ return parts[parts.length - 1] || p;
888
746
  }
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);
747
+ function isAbsVal(v) {
748
+ return !!v && typeof v === "object" && "shape" in v && "conf" in v;
898
749
  }
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
- }
750
+ function paramNameOf(p, i) {
751
+ if (p.type === "Identifier" && p.name) return p.name;
752
+ if (p.type === "RestElement" && p.argument?.type === "Identifier" && p.argument.name) {
753
+ return `...${p.argument.name}`;
908
754
  }
909
- return n;
755
+ return `arg${i}`;
910
756
  }
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}`;
757
+ function topLevelFnParams(file) {
758
+ const out = /* @__PURE__ */ new Map();
759
+ const body = file.program?.body ?? [];
760
+ for (const stmt of body) {
761
+ if (stmt.type === "FunctionDeclaration" && stmt.id && stmt.params) {
762
+ out.set(stmt.id.name, stmt.params.map(paramNameOf));
916
763
  }
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=?";
764
+ if (stmt.type === "ExportDefaultDeclaration") {
765
+ const d = stmt.declaration;
766
+ if (d && (d.type === "FunctionDeclaration" || d.type === "ArrowFunctionExpression") && d.params) {
767
+ out.set("default", d.params.map(paramNameOf));
935
768
  }
936
769
  }
937
- if (impl?.apply) return "apply";
938
- }
939
- try {
940
- return formatAbs(a);
941
- } catch {
942
- return "?";
943
770
  }
771
+ return out;
944
772
  }
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])}`);
773
+ function bPathExportsToModuleExports(run, file, fingerprintPrefix) {
774
+ const named = {};
775
+ let def;
776
+ const paramTable = topLevelFnParams(file);
777
+ for (const [k, v] of Object.entries(run)) {
778
+ if (v === void 0 || v === null) continue;
779
+ let absVal;
780
+ if (isAbsVal(v)) {
781
+ absVal = v;
782
+ } else if (typeof v === "function") {
783
+ const params = paramTable.get(k) ?? Array.from({ length: v.length ?? 0 }, (_, i) => `arg${i}`);
784
+ absVal = absFunction(params, {
785
+ apply: (args) => callTranspiledExportFull(run, k, args).result,
786
+ kind: "bpath-export",
787
+ // 无 body 的桥接 fn 预算键 = fingerprint ?? anon#N——缺省会让所有
788
+ // 桥接导出共享 anon#1,嵌套跨模块调用(a 调 b 调 a')撞
789
+ // _activeCallKeys 递归守卫被误截断为 opaque。按 模块#导出 唯一化。
790
+ fingerprint: `${fingerprintPrefix}#${k}`
791
+ });
792
+ } else {
793
+ absVal = unknown;
794
+ }
795
+ if (k === "default") def = absVal;
796
+ else named[k] = absVal;
956
797
  }
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(";"));
798
+ const out = { named };
799
+ if (def) out.default = def;
800
+ return out;
963
801
  }
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 {};
802
+ function evalAbsModuleGraph(entrySource, entryFile, opts = {}) {
803
+ const load = opts.loadModule ?? defaultAbsLoadModule;
804
+ const maxDepth = opts.maxDepth ?? 16;
805
+ const cache = /* @__PURE__ */ new Map();
806
+ const loading = [];
807
+ const issues = [];
808
+ const seenIssue = /* @__PURE__ */ new Set();
809
+ const pushIssue = (kind, label, reason) => {
810
+ const key = `${kind}:${label}`;
811
+ if (seenIssue.has(key)) return;
812
+ seenIssue.add(key);
813
+ issues.push({ kind, label, reason });
814
+ };
815
+ function evalDep(absPath, spec, fromFile, depth) {
816
+ const cycleIndex = loading.indexOf(absPath);
817
+ if (cycleIndex !== -1) {
818
+ const chain = [...loading.slice(cycleIndex), absPath];
819
+ pushIssue(
820
+ "cycle",
821
+ moduleLabel(absPath),
822
+ `Circular module load: ${chain.join(" -> ")} (bindings inside the cycle resolve to their partially evaluated types)`
823
+ );
824
+ return cache.get(absPath) ?? { named: {} };
825
+ }
826
+ const shared = absModuleCache.get(absPath);
827
+ if (shared) {
828
+ try {
829
+ const st = statSync3(absPath);
830
+ if (st.mtimeMs === shared.mtimeMs && st.size === shared.size) {
831
+ for (const iss of shared.issues) pushIssue(iss.kind, iss.label, iss.reason);
832
+ cache.set(absPath, shared.exports);
833
+ return shared.exports;
834
+ }
835
+ } catch {
836
+ }
837
+ absModuleCache.delete(absPath);
838
+ }
839
+ if (cache.has(absPath)) return cache.get(absPath);
840
+ if (depth > maxDepth) {
841
+ const chain = [...loading, absPath];
842
+ pushIssue(
843
+ "depth",
844
+ moduleLabel(absPath),
845
+ `Module load chain too deep (depth ${chain.length} > ${maxDepth} max): ${chain.join(" -> ")} (loading truncated, deeper modules typed as unknown)`
846
+ );
847
+ const empty = { named: {} };
848
+ cache.set(absPath, empty);
849
+ return empty;
850
+ }
851
+ cache.set(absPath, { named: {} });
852
+ loading.push(absPath);
853
+ const source = load(spec, fromFile) ?? (() => {
854
+ try {
855
+ return readFileSync3(absPath, "utf-8");
856
+ } catch {
857
+ return void 0;
858
+ }
859
+ })();
860
+ if (source === void 0) {
861
+ pushIssue(
862
+ "missing",
863
+ spec,
864
+ `Module file not found for '${spec}' (from ${moduleLabel(fromFile)}); tried: ${absPath}`
865
+ );
866
+ const empty = { named: {} };
867
+ cache.set(absPath, empty);
868
+ loading.pop();
869
+ return empty;
870
+ }
871
+ const issueStart = issues.length;
872
+ const modules2 = buildModulesForFile(
873
+ source,
874
+ absPath,
875
+ load,
876
+ evalDep,
877
+ depth,
878
+ maxDepth,
879
+ (spec2, fromFile2, tried) => {
880
+ pushIssue(
881
+ "missing",
882
+ spec2,
883
+ `Module file not found for '${spec2}' (from ${moduleLabel(fromFile2)}); tried: ${tried.join(", ")}`
884
+ );
885
+ }
886
+ );
887
+ let exports;
888
+ const bRun = tryRunTranspiled(source, { mode: "analyze", modules: modules2 });
889
+ if (bRun) {
890
+ exports = bPathExportsToModuleExports(bRun, parse2(source), `bpath:${absPath}`);
891
+ } else {
892
+ exports = { named: {} };
893
+ }
894
+ loading.pop();
895
+ cache.set(absPath, exports);
896
+ let fingerprint;
897
+ try {
898
+ const st = statSync3(absPath);
899
+ fingerprint = { mtimeMs: st.mtimeMs, size: st.size };
900
+ } catch {
901
+ }
902
+ if (fingerprint) {
903
+ absModuleCache.set(absPath, {
904
+ ...fingerprint,
905
+ exports,
906
+ issues: issues.slice(issueStart)
907
+ });
908
+ }
909
+ return exports;
971
910
  }
911
+ const modules = buildModulesForFile(
912
+ entrySource,
913
+ entryFile,
914
+ load,
915
+ evalDep,
916
+ 0,
917
+ maxDepth,
918
+ (spec, fromFile, tried) => {
919
+ pushIssue(
920
+ "missing",
921
+ spec,
922
+ `Module file not found for '${spec}' (from ${moduleLabel(fromFile)}); tried: ${tried.join(", ")}`
923
+ );
924
+ }
925
+ );
926
+ return { modules, byPath: cache, issues };
972
927
  }
973
- function collectEnvModules(envNames) {
974
- if (envNames.length === 0) return {};
975
- const env = createEnvironment();
976
- let mods = {};
928
+ function importLocalBindings(source, modules) {
929
+ const out = /* @__PURE__ */ new Map();
930
+ let file;
977
931
  try {
978
- mods = loadEnvs(envNames, env).modules;
932
+ file = parse2(source);
979
933
  } catch {
980
- return {};
934
+ return out;
981
935
  }
982
- const out = {};
983
- for (const [spec, named] of Object.entries(mods)) {
984
- out[spec] = { named: { ...named } };
936
+ const body = file.program?.body ?? [];
937
+ for (const stmt of body) {
938
+ if (stmt.type !== "ImportDeclaration" || typeof stmt.source?.value !== "string") continue;
939
+ const mod = modules[stmt.source.value];
940
+ if (!mod) continue;
941
+ for (const sp of stmt.specifiers ?? []) {
942
+ const local = sp.local?.name;
943
+ if (!local) continue;
944
+ if (sp.type === "ImportNamespaceSpecifier") {
945
+ const slots = {};
946
+ for (const [k, v] of Object.entries(mod.named)) slots[k] = { value: v };
947
+ if (mod.default) slots["default"] = { value: mod.default };
948
+ out.set(local, makeAbs({ k: "obj", slots, open: true }, void 0, void 0, "path"));
949
+ } else if (sp.type === "ImportDefaultSpecifier") {
950
+ if (mod.default) out.set(local, mod.default);
951
+ } else if (sp.type === "ImportSpecifier") {
952
+ const imported = sp.imported?.type === "StringLiteral" ? sp.imported.value : sp.imported?.name;
953
+ if (imported === void 0) continue;
954
+ const absVal = imported === "default" ? mod.default : mod.named[imported];
955
+ if (absVal) out.set(local, absVal);
956
+ }
957
+ }
985
958
  }
986
959
  return out;
987
960
  }
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;
961
+ function collectAbsBindingsFromGraph(source, filePath, opts = {}) {
962
+ const out = /* @__PURE__ */ new Map();
963
+ try {
964
+ const { modules } = evalAbsModuleGraph(source, filePath, opts);
965
+ const mocks = seedFnsToMocks(opts.seedVars ?? {}, opts.seedFns ?? {});
966
+ const run = tryRunTranspiled(source, {
967
+ mode: "analyze",
968
+ modules,
969
+ envGlobals: Object.keys(mocks).length ? mocks : void 0
970
+ });
971
+ if (!run) return out;
972
+ const binds = bindingsOf(run);
973
+ if (binds) {
974
+ for (const [name, v] of binds) {
975
+ if (isAbsVal(v)) out.set(name, v);
976
+ }
1010
977
  }
1011
- const overwritten = [];
1012
- for (const key of Object.keys(envExports.named)) {
1013
- if (existing.named[key] !== void 0) overwritten.push(key);
978
+ const exports = bPathExportsToModuleExports(run, parse2(source), `bpath:bindings:${filePath}`);
979
+ for (const [name, v] of Object.entries(exports.named)) {
980
+ if (!out.has(name)) out.set(name, v);
1014
981
  }
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
- }
982
+ for (const [local, absVal] of importLocalBindings(source, modules)) {
983
+ if (!out.has(local)) out.set(local, absVal);
1025
984
  }
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 };
985
+ } catch {
1029
986
  }
1030
987
  return out;
1031
988
  }
1032
- function collectBPathReplacements(source) {
1033
- const targets = [];
1034
- const values = {};
1035
- const asTargets = [];
1036
- const asValues = {};
1037
- let i = 0;
989
+
990
+ // src/env-path-deps.ts
991
+ import { existsSync as existsSync3, statSync as statSync4 } from "fs";
992
+ import { dirname as dirname4, resolve as resolve4, join as join2 } from "path";
993
+ import { extractAllLoadSpecs } from "@nudojs/core/internal";
994
+ var ENV_DEPENDENTS_MAX = 256;
995
+ var ENV_DEPENDENTS_PER_KEY_MAX = 512;
996
+ var envDependents = new BoundedLruMap(ENV_DEPENDENTS_MAX);
997
+ function getEnvPathDepsSize() {
998
+ return envDependents.size;
999
+ }
1000
+ function norm(p) {
1001
+ return p.replace(/\\/g, "/");
1002
+ }
1003
+ function resolveLoadSpecPath(spec, fromFile) {
1004
+ if (!spec.startsWith(".") && !spec.startsWith("/")) return void 0;
1038
1005
  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);
1006
+ const base = dirname4(resolve4(fromFile));
1007
+ const p = resolve4(base, spec);
1008
+ for (const cand of [
1009
+ p,
1010
+ `${p}.js`,
1011
+ `${p}.mjs`,
1012
+ `${p}.ts`,
1013
+ join2(p, "index.js"),
1014
+ join2(p, "index.mjs"),
1015
+ join2(p, "index.ts")
1016
+ ]) {
1017
+ if (existsSync3(cand) && !statSync4(cand).isDirectory()) return cand;
1018
+ }
1019
+ return void 0;
1080
1020
  } catch {
1021
+ return void 0;
1081
1022
  }
1082
- return { targets, values, asTargets, asValues };
1083
- }
1084
- function isBPathCapable(source, envNames = []) {
1085
- void envNames;
1086
- void source;
1087
- return true;
1088
1023
  }
1089
- var bRunByFile = /* @__PURE__ */ new Map();
1090
- function bPathDepKey(source, filePath) {
1024
+ function noteEnvPathDeps(sourcePath, source) {
1025
+ const src = norm(sourcePath);
1026
+ for (const set of envDependents.values()) set.delete(src);
1027
+ let specs = [];
1091
1028
  try {
1092
- const fp = loadModuleDepsFingerprint2(source, defaultLoadModule, filePath);
1093
- if (fp.truncated) return null;
1094
- return hashSource2(fp.fp);
1029
+ specs = extractAllLoadSpecs(source);
1095
1030
  } catch {
1096
- return null;
1031
+ return;
1032
+ }
1033
+ for (const spec of specs) {
1034
+ if (!spec.startsWith(".") && !spec.startsWith("/")) continue;
1035
+ const abs = resolveLoadSpecPath(spec, sourcePath);
1036
+ if (!abs) continue;
1037
+ const key = norm(abs);
1038
+ let set = envDependents.get(key);
1039
+ if (!set) {
1040
+ set = /* @__PURE__ */ new Set();
1041
+ envDependents.set(key, set);
1042
+ }
1043
+ if (!set.has(src) && set.size >= ENV_DEPENDENTS_PER_KEY_MAX) {
1044
+ const oldest = set.values().next().value;
1045
+ if (oldest !== void 0) set.delete(oldest);
1046
+ }
1047
+ set.add(src);
1097
1048
  }
1098
1049
  }
1099
- function clearBPathCache() {
1100
- bRunByFile.clear();
1101
- clearAnalysisFileCache();
1102
- clearFnAnalysisCache();
1050
+ function envPathDependents(envPath) {
1051
+ return [...envDependents.peek(norm(envPath)) ?? []];
1103
1052
  }
1104
- function getBPathCacheSize() {
1105
- return bRunByFile.size;
1053
+ function clearEnvPathDeps() {
1054
+ envDependents.clear();
1106
1055
  }
1107
- function evictBPathCacheForFiles(files) {
1108
- let n = 0;
1109
- for (const f of files) {
1110
- if (bRunByFile.delete(f)) n++;
1111
- }
1112
- return n;
1056
+ function isEnvTemplatePath(path) {
1057
+ const lower = norm(path).toLowerCase();
1058
+ 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);
1113
1059
  }
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);
1060
+
1061
+ // src/mock-module.ts
1062
+ import { extractFileDirectives, parse as parse3 } from "@nudojs/parser";
1063
+ import { obj as absObj } from "@nudojs/core";
1064
+
1065
+ // src/mock-file.ts
1066
+ import { tryRunTranspiled as tryRunTranspiled2 } from "@nudojs/core";
1067
+ function evalMockFileWithDeps(fromPath, fromFile, loadModule) {
1068
+ const load = loadModule ?? defaultLoadModule;
1069
+ let source;
1070
+ try {
1071
+ source = load(fromPath, fromFile);
1072
+ } catch {
1073
+ source = void 0;
1121
1074
  }
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);
1075
+ if (source === void 0) {
1076
+ return { ok: false, error: `Mock file not found (from "${fromPath}")` };
1130
1077
  }
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
- }
1078
+ const entryFile = resolveLoadSpecPath(fromPath, fromFile) ?? fromPath;
1079
+ let modules = {};
1080
+ try {
1081
+ modules = evalAbsModuleGraph(source, entryFile, { loadModule }).modules;
1082
+ } catch {
1083
+ modules = {};
1147
1084
  }
1148
- let out = null;
1085
+ let run;
1149
1086
  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
- }
1087
+ run = tryRunTranspiled2(source, {
1088
+ mode: "exec",
1089
+ modules
1090
+ });
1191
1091
  } catch {
1192
- out = null;
1092
+ run = void 0;
1193
1093
  }
1194
- if (canCache && depKey !== null) {
1195
- bPathCacheSet(filePath, stable, mode, envKey, mockKey, depKey, out);
1094
+ if (!run) {
1095
+ return { ok: false, error: `Mock file "${fromPath}" failed to evaluate` };
1196
1096
  }
1197
- return out ?? void 0;
1097
+ return {
1098
+ ok: true,
1099
+ run,
1100
+ absPath: entryFile,
1101
+ source,
1102
+ modules
1103
+ };
1198
1104
  }
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];
1105
+
1106
+ // src/mock-module.ts
1107
+ function loadMockModuleExports(fromPath, fromFile, loadModule) {
1108
+ const evaled = evalMockFileWithDeps(fromPath, fromFile, loadModule);
1109
+ if (!evaled.ok) {
1211
1110
  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
1111
+ error: evaled.error.includes("not found") ? `Mock module not found (from "${fromPath}")` : evaled.error
1217
1112
  };
1218
- } finally {
1219
- if (opts.collectCalls) setBCallCollector(prevCall ?? null);
1220
- if (wantMember) setMemberDiagCollector(prevMember ?? null);
1221
1113
  }
1114
+ const exports = bPathExportsToModuleExports(evaled.run, parse3(evaled.source), `mock-module:${fromPath}`);
1115
+ if (exports.default === void 0 && Object.keys(exports.named).length > 0) {
1116
+ const slots = {};
1117
+ for (const [k, v] of Object.entries(exports.named)) slots[k] = { value: v };
1118
+ exports.default = absObj(slots);
1119
+ }
1120
+ return { exports };
1222
1121
  }
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") {
1122
+ function pickNamed(mock, names) {
1123
+ const named = {};
1124
+ let def;
1125
+ for (const n of names) {
1126
+ if (n === "default") {
1127
+ if (mock.default) def = mock.default;
1267
1128
  continue;
1268
1129
  }
1269
- const v = obj[key];
1270
- if (v && typeof v === "object") parts.push(`${key}:{${astContentKey(v, depth + 1)}}`);
1130
+ const v = mock.named[n];
1131
+ if (v !== void 0) named[n] = v;
1271
1132
  }
1272
- return parts.join("|");
1273
- }
1274
- function stampFingerprint(a, fp) {
1275
- const impl = getFnImpl2(a);
1276
- if (impl) impl.fingerprint = fp;
1277
- return a;
1133
+ return def !== void 0 ? { named, default: def } : { named };
1278
1134
  }
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}`
1135
+ function applyMockModuleDirectives(base, fileDirectives, opts) {
1136
+ const mods = fileDirectives.filter(
1137
+ (d) => d.kind === "mock-module"
1302
1138
  );
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);
1139
+ if (mods.length === 0) {
1140
+ return { modules: base, errors: [], applied: false };
1310
1141
  }
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";
1142
+ const out = { ...base };
1143
+ const errors = [];
1144
+ let applied = false;
1145
+ for (const d of mods) {
1146
+ const loaded = loadMockModuleExports(d.fromPath, opts.fromFile, opts.loadModule);
1147
+ if (!loaded.exports) {
1148
+ errors.push({
1149
+ name: d.source,
1150
+ fromPath: d.fromPath,
1151
+ message: loaded.error ?? `Mock module "${d.fromPath}" could not be loaded`
1152
+ });
1153
+ continue;
1154
+ }
1155
+ const mock = loaded.exports;
1156
+ if (!d.names || d.names.length === 0) {
1157
+ out[d.source] = mock;
1158
+ applied = true;
1159
+ continue;
1160
+ }
1161
+ const orig = out[d.source] ?? { named: {} };
1162
+ const overlay = pickNamed(mock, d.names);
1163
+ const named = { ...orig.named, ...overlay.named };
1164
+ const merged = { named };
1165
+ const wantDefault = d.names.includes("default");
1166
+ if (wantDefault && overlay.default !== void 0) {
1167
+ merged.default = overlay.default;
1168
+ } else if (orig.default !== void 0) {
1169
+ merged.default = orig.default;
1316
1170
  }
1171
+ out[d.source] = merged;
1172
+ applied = true;
1317
1173
  }
1318
- return false;
1174
+ return { modules: out, errors, applied };
1319
1175
  }
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;
1176
+ function applyMockModuleDirectivesFromSource(source, base, opts) {
1177
+ let fileDirectives = [];
1332
1178
  try {
1333
- defaultKey = formatAbs2(markedDefault);
1179
+ fileDirectives = extractFileDirectives(parse3(source));
1334
1180
  } catch {
1335
- defaultKey = "?";
1181
+ fileDirectives = [];
1336
1182
  }
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
- );
1183
+ return applyMockModuleDirectives(base, fileDirectives, opts);
1357
1184
  }
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);
1185
+
1186
+ // src/analysis-file-cache.ts
1187
+ var analysisByFile = /* @__PURE__ */ new Map();
1188
+ function clearAnalysisFileCache() {
1189
+ analysisByFile.clear();
1384
1190
  }
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
- );
1191
+ function getAnalysisFileCacheSize() {
1192
+ return analysisByFile.size;
1193
+ }
1194
+ function trimAnalysisFileCache() {
1195
+ const max = getSessionCacheLimits().maxFiles;
1196
+ while (analysisByFile.size > max) {
1197
+ const oldest = analysisByFile.keys().next().value;
1198
+ if (oldest === void 0) break;
1199
+ analysisByFile.delete(oldest);
1395
1200
  }
1396
- if (sinonExpr.rejectedValue) {
1397
- return constantMockFn(makeAbs2({ k: "never" }, void 0, void 0, "exact"));
1201
+ }
1202
+ function analysisCacheGet(filePath, source, auxKey) {
1203
+ const e = analysisByFile.get(filePath);
1204
+ if (!e) return void 0;
1205
+ if (e.source !== source || e.auxKey !== auxKey) return void 0;
1206
+ analysisByFile.delete(filePath);
1207
+ analysisByFile.set(filePath, e);
1208
+ return e.value;
1209
+ }
1210
+ function analysisCacheSet(filePath, source, auxKey, value) {
1211
+ const max = getSessionCacheLimits().maxFiles;
1212
+ if (max <= 0) return;
1213
+ while (analysisByFile.size >= max && !analysisByFile.has(filePath)) {
1214
+ const oldest = analysisByFile.keys().next().value;
1215
+ if (oldest === void 0) break;
1216
+ analysisByFile.delete(oldest);
1398
1217
  }
1399
- if (sinonExpr.returnValue) {
1400
- return constantMockFn(sinonExpr.returnValue);
1218
+ analysisByFile.set(filePath, { source, auxKey, value });
1219
+ }
1220
+ function evictAnalysisFileCacheForFiles(files) {
1221
+ let n = 0;
1222
+ for (const f of files) {
1223
+ if (analysisByFile.delete(f)) n++;
1401
1224
  }
1402
- return constantMockFn(absUnknown);
1225
+ return n;
1403
1226
  }
1404
- function isAbsVal2(v) {
1405
- return !!v && typeof v === "object" && "shape" in v && "conf" in v;
1227
+
1228
+ // src/fn-analysis-cache.ts
1229
+ var fnAnalysisCache = /* @__PURE__ */ new Map();
1230
+ function getFnAnalysisCacheSize() {
1231
+ return fnAnalysisCache.size;
1406
1232
  }
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
- };
1233
+ function clearFnAnalysisCache() {
1234
+ fnAnalysisCache.clear();
1235
+ }
1236
+ function trimFnAnalysisCache() {
1237
+ const max = getSessionCacheLimits().maxFns;
1238
+ while (fnAnalysisCache.size > max) {
1239
+ const oldest = fnAnalysisCache.keys().next().value;
1240
+ if (oldest === void 0) break;
1241
+ fnAnalysisCache.delete(oldest);
1425
1242
  }
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;
1243
+ }
1244
+ function fnAnalysisCacheGet(key) {
1245
+ const hit = fnAnalysisCache.get(key);
1246
+ if (hit !== void 0) {
1247
+ fnAnalysisCache.delete(key);
1248
+ fnAnalysisCache.set(key, hit);
1439
1249
  }
1440
- return { abs: markMockConf(absVal) };
1250
+ return hit;
1441
1251
  }
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 });
1252
+ function fnAnalysisCacheSet(key, value) {
1253
+ const max = getSessionCacheLimits().maxFns;
1254
+ if (max <= 0) return;
1255
+ while (fnAnalysisCache.size >= max && !fnAnalysisCache.has(key)) {
1256
+ const oldest = fnAnalysisCache.keys().next().value;
1257
+ if (oldest === void 0) break;
1258
+ fnAnalysisCache.delete(oldest);
1446
1259
  }
1447
- return out;
1260
+ fnAnalysisCache.set(key, value);
1448
1261
  }
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
- }
1262
+ function evictFnAnalysisCacheForFiles(files) {
1263
+ if (files.length === 0 || fnAnalysisCache.size === 0) return 0;
1264
+ const prefixes = files.map((f) => `${f}\0`);
1265
+ let n = 0;
1266
+ for (const key of [...fnAnalysisCache.keys()]) {
1267
+ if (prefixes.some((p) => key.startsWith(p))) {
1268
+ fnAnalysisCache.delete(key);
1269
+ n++;
1488
1270
  }
1489
1271
  }
1490
- return { seedVars, seedFns, ...fromErrors.length > 0 ? { fromErrors } : {} };
1272
+ return n;
1491
1273
  }
1492
- function mockSeedsForSource(source, opts) {
1493
- const fns = extractDirectives(parse5(source));
1494
- return mockSeedsToAbsMocks(mockDirectivesToAbsSeeds(fns, opts));
1274
+ function caseDirectiveKey(directives, formatAbs6) {
1275
+ return directives.map((d) => {
1276
+ if (d.kind === "case") {
1277
+ const args = (d.argsAbs ?? []).map((a) => formatAbs6(a)).join(",");
1278
+ return `c:${d.name ?? ""}:${args}`;
1279
+ }
1280
+ return d.kind;
1281
+ }).join("|");
1495
1282
  }
1496
1283
 
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)}`;
1284
+ // src/bpath-run.ts
1285
+ import { runTranspiled, callTranspiledExport, callTranspiledExportFull as callTranspiledExportFull2, setBCallCollector, createEnvironment, formatAbs, getFnImpl } from "@nudojs/core";
1286
+ import { setMemberDiagCollector, setAbsTruncationCollector, stableAnalyzeKeySource as stableAnalyzeKeySource2, hashSource as hashSource2, loadModuleDepsFingerprint as loadModuleDepsFingerprint2 } from "@nudojs/core/internal";
1287
+ import { parse as parse4, extractInlineDirectives } from "@nudojs/parser";
1288
+ function absSeedPart(a) {
1289
+ const impl = getFnImpl(a);
1290
+ if (impl?.fingerprint) return impl.fingerprint;
1291
+ if (a.shape.k === "fn") {
1292
+ const ret = impl?.env?.vars?.get("__nudo_mock_ret");
1293
+ if (ret) {
1294
+ try {
1295
+ return `ret=${formatAbs(ret)}`;
1296
+ } catch {
1297
+ return "ret=?";
1298
+ }
1299
+ }
1300
+ if (impl?.apply) return "apply";
1301
+ }
1534
1302
  try {
1535
- return formatAbs3(a);
1303
+ return formatAbs(a);
1536
1304
  } 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;
1305
+ return "?";
1569
1306
  }
1570
1307
  }
1571
- var MAX_RECORD_TYPE_NODES = 2e3;
1572
- function absNodeCount(a, seen = /* @__PURE__ */ new Set()) {
1573
- return shapeNodeCount(a, seen);
1308
+ function seedFnPart(name, fn) {
1309
+ if (fn.fingerprint) return `${name}:${fn.fingerprint}`;
1310
+ const body = fn.body;
1311
+ const bodyKey = body && typeof body.start === "number" && typeof body.end === "number" ? `${body.start}:${body.end}:${body.type ?? ""}` : String(body?.type ?? "?");
1312
+ return `${name}(${fn.params.join(",")})@${bodyKey}`;
1574
1313
  }
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;
1314
+ function mockSeedFingerprint(mocks, seedFns) {
1315
+ const parts = [];
1316
+ if (mocks) {
1317
+ const names = Object.keys(mocks).sort();
1318
+ for (const n of names) parts.push(`${n}=${absSeedPart(mocks[n])}`);
1579
1319
  }
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";
1320
+ if (seedFns) {
1321
+ const names = Object.keys(seedFns).sort();
1322
+ for (const n of names) parts.push(`fn:${seedFnPart(n, seedFns[n])}`);
1323
+ }
1324
+ if (parts.length === 0) return "-";
1325
+ return hashSource2(parts.join(";"));
1591
1326
  }
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 };
1327
+ function collectEnvGlobals(envNames) {
1328
+ if (envNames.length === 0) return {};
1329
+ const env = createEnvironment();
1330
+ try {
1331
+ return { ...loadEnvs(envNames, env).globals };
1332
+ } catch {
1333
+ return {};
1595
1334
  }
1596
- return a;
1597
1335
  }
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
- }
1336
+ function collectEnvModules(envNames) {
1337
+ if (envNames.length === 0) return {};
1338
+ const env = createEnvironment();
1339
+ let mods = {};
1340
+ try {
1341
+ mods = loadEnvs(envNames, env).modules;
1342
+ } catch {
1343
+ return {};
1623
1344
  }
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
- }
1345
+ const out = {};
1346
+ for (const [spec, named] of Object.entries(mods)) {
1347
+ out[spec] = { named: { ...named } };
1629
1348
  }
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;
1349
+ return out;
1647
1350
  }
1648
- function isSelfContainedSource(source, envNames) {
1649
- if (envNames.length > 0) return false;
1650
- return !/\brequire\s*\(|\bimport\s*[{'"*]/.test(source);
1351
+ var envHarvestConflictCollector = null;
1352
+ function setEnvHarvestConflictCollector(collector) {
1353
+ const prev = envHarvestConflictCollector;
1354
+ envHarvestConflictCollector = collector;
1355
+ return prev;
1651
1356
  }
1652
- function absModulesOk(source, envNames) {
1653
- void source;
1654
- void envNames;
1655
- return true;
1357
+ function getEnvHarvestConflictCollector() {
1358
+ return envHarvestConflictCollector;
1656
1359
  }
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
- }
1360
+ function mergeHarvestUnderEnv(harvestModules, envModules, opts) {
1361
+ const out = {};
1362
+ for (const [mod, exports] of Object.entries(harvestModules)) {
1363
+ const named = { ...exports.named };
1364
+ out[mod] = exports.default !== void 0 ? { named, default: exports.default } : { named };
1365
+ }
1366
+ const notify = opts?.onConflict ?? envHarvestConflictCollector;
1367
+ for (const [mod, envExports] of Object.entries(envModules)) {
1368
+ const existing = out[mod];
1369
+ if (!existing) {
1370
+ const named2 = { ...envExports.named };
1371
+ out[mod] = envExports.default !== void 0 ? { named: named2, default: envExports.default } : { named: named2 };
1372
+ continue;
1373
+ }
1374
+ const overwritten = [];
1375
+ for (const key of Object.keys(envExports.named)) {
1376
+ if (existing.named[key] !== void 0) overwritten.push(key);
1377
+ }
1378
+ const defaultOverwritten = envExports.default !== void 0 && existing.default !== void 0;
1379
+ if (notify && (overwritten.length > 0 || defaultOverwritten)) {
1380
+ try {
1381
+ notify({
1382
+ module: mod,
1383
+ exports: overwritten,
1384
+ defaultOverwritten
1385
+ });
1386
+ } catch {
1708
1387
  }
1709
1388
  }
1710
- } catch {
1389
+ const named = { ...existing.named, ...envExports.named };
1390
+ const defaultAbs = envExports.default !== void 0 ? envExports.default : existing.default;
1391
+ out[mod] = defaultAbs !== void 0 ? { named, default: defaultAbs } : { named };
1711
1392
  }
1712
1393
  return out;
1713
1394
  }
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;
1395
+ function collectBPathReplacements(source) {
1396
+ const targets = [];
1397
+ const values = {};
1398
+ const asTargets = [];
1399
+ const asValues = {};
1400
+ let i = 0;
1727
1401
  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 };
1402
+ const file = parse4(source);
1403
+ const visitStmts = (stmts) => {
1404
+ for (const stmt of stmts) {
1405
+ if (!stmt || typeof stmt !== "object") continue;
1406
+ const loc = stmt.loc;
1407
+ const dirs = extractInlineDirectives(stmt);
1408
+ for (const d of dirs) {
1409
+ if (d.kind === "replace") {
1410
+ const varName = `__rep${i++}`;
1411
+ targets.push({
1412
+ target: d.targetSource,
1413
+ varName,
1414
+ stmtStart: loc?.start.line,
1415
+ stmtEnd: loc?.end.line
1416
+ });
1417
+ values[varName] = d.typeAbs;
1418
+ } else if (d.kind === "as" && loc) {
1419
+ const varName = `__as${i++}`;
1420
+ asTargets.push({
1421
+ varName,
1422
+ stmtStart: loc.start.line,
1423
+ stmtEnd: loc.end.line
1424
+ });
1425
+ asValues[varName] = d.typeAbs;
1426
+ }
1427
+ }
1428
+ const s = stmt;
1429
+ if (s.type === "BlockStatement" && Array.isArray(s.body)) visitStmts(s.body);
1430
+ if (s.type === "FunctionDeclaration" || s.type === "FunctionExpression") {
1431
+ if (s.body) visitStmts([s.body]);
1432
+ }
1433
+ if (s.type === "ExportNamedDeclaration" && s.declaration) {
1434
+ visitStmts([s.declaration]);
1435
+ }
1436
+ if (s.type === "IfStatement") {
1437
+ if (s.consequent) visitStmts([s.consequent]);
1438
+ if (s.alternate) visitStmts([s.alternate]);
1735
1439
  }
1736
1440
  }
1737
- }
1738
- return void 0;
1441
+ };
1442
+ visitStmts(file.program.body);
1739
1443
  } catch {
1740
- return void 0;
1741
1444
  }
1445
+ return { targets, values, asTargets, asValues };
1742
1446
  }
1743
- function tryEvalEntryAbs(source, fnName, args, filePath, mocks, assignedName) {
1744
- return tryEvalAbsRaw(source, fnName, args, filePath, mocks, assignedName);
1447
+ function isBPathCapable(source, envNames = []) {
1448
+ void envNames;
1449
+ void source;
1450
+ return true;
1745
1451
  }
1746
- function tryAttachIntension(caseResult, source, fnName) {
1452
+ var bRunByFile = /* @__PURE__ */ new Map();
1453
+ function bPathDepKey(source, filePath) {
1747
1454
  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;
1455
+ const fp = loadModuleDepsFingerprint2(source, defaultLoadModule, filePath);
1456
+ if (fp.truncated) return null;
1457
+ return hashSource2(fp.fp);
1764
1458
  } catch {
1459
+ return null;
1765
1460
  }
1766
1461
  }
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
- }
1462
+ function clearBPathCache() {
1463
+ bRunByFile.clear();
1464
+ clearAnalysisFileCache();
1465
+ clearFnAnalysisCache();
1782
1466
  }
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
- };
1467
+ function getBPathCacheSize() {
1468
+ return bRunByFile.size;
1792
1469
  }
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
- });
1470
+ function evictBPathCacheForFiles(files) {
1471
+ let n = 0;
1472
+ for (const f of files) {
1473
+ if (bRunByFile.delete(f)) n++;
1813
1474
  }
1814
- return absUnknown2;
1475
+ return n;
1815
1476
  }
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;
1477
+ function bPathCacheSet(filePath, stableSource, mode, envKey, mockKey, depKey, value) {
1478
+ const max = getSessionCacheLimits().maxBRuns;
1479
+ if (max <= 0) return;
1480
+ while (bRunByFile.size >= max && !bRunByFile.has(filePath)) {
1481
+ const oldest = bRunByFile.keys().next().value;
1482
+ if (oldest === void 0) break;
1483
+ bRunByFile.delete(oldest);
1841
1484
  }
1842
- return rec;
1485
+ bRunByFile.set(filePath, { stableSource, mode, envKey, mockKey, depKey, value });
1843
1486
  }
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])];
1487
+ function trimBPathCache() {
1488
+ const max = getSessionCacheLimits().maxBRuns;
1489
+ while (bRunByFile.size > max) {
1490
+ const oldest = bRunByFile.keys().next().value;
1491
+ if (oldest === void 0) break;
1492
+ bRunByFile.delete(oldest);
1493
+ }
1854
1494
  }
1855
- function collectCallRecords(filePath, source) {
1856
- if (filePath) {
1495
+ function tryRunBPath(source, filePath, opts = {}) {
1496
+ if (!isBPathCapable(source, opts.envNames ?? [])) return void 0;
1497
+ const mode = opts.mode ?? "analyze";
1498
+ const envKey = (opts.envNames ?? []).join(",");
1499
+ const mockKey = mockSeedFingerprint(opts.mocks);
1500
+ const stable = stableAnalyzeKeySource2(source);
1501
+ const depKey = bPathDepKey(source, filePath);
1502
+ const canCache = depKey !== null;
1503
+ if (canCache) {
1504
+ const cached = bRunByFile.get(filePath);
1505
+ if (cached && cached.stableSource === stable && cached.mode === mode && cached.envKey === envKey && cached.mockKey === mockKey && cached.depKey === depKey) {
1506
+ bRunByFile.delete(filePath);
1507
+ bRunByFile.set(filePath, cached);
1508
+ return cached.value ?? void 0;
1509
+ }
1510
+ }
1511
+ let out = null;
1512
+ try {
1513
+ const memberDiags = [];
1514
+ const truncated = /* @__PURE__ */ new Set();
1515
+ const topCalls = [];
1516
+ const prevMember = setMemberDiagCollector((d) => memberDiags.push(d));
1517
+ const prevTrunc = setAbsTruncationCollector((label) => truncated.add(label));
1518
+ const prevCall = setBCallCollector((r) => topCalls.push(r));
1857
1519
  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 } : {}
1520
+ const { modules: graphMods, issues } = evalAbsModuleGraph(source, filePath);
1521
+ const envMods = collectEnvModules(opts.envNames ?? []);
1522
+ let modules = mergeHarvestUnderEnv(graphMods, envMods);
1523
+ const mm = applyMockModuleDirectivesFromSource(source, modules, { fromFile: filePath });
1524
+ modules = mm.modules;
1525
+ const { targets, values, asTargets, asValues } = collectBPathReplacements(source);
1526
+ const envGlobals = {
1527
+ ...collectEnvGlobals(opts.envNames ?? []),
1528
+ ...opts.mocks ?? {}
1529
+ };
1530
+ const exports = runTranspiled(source, {
1531
+ modules,
1532
+ maxLoopIters: opts.maxLoopIters,
1533
+ mode: opts.mode ?? "analyze",
1534
+ replacementTargets: targets.length ? targets : void 0,
1535
+ replacements: targets.length ? values : void 0,
1536
+ asOverrideTargets: asTargets.length ? asTargets : void 0,
1537
+ asOverrides: asTargets.length ? asValues : void 0,
1538
+ envGlobals: Object.keys(envGlobals).length ? envGlobals : void 0,
1539
+ lenientGlobals: opts.lenientGlobals
1865
1540
  });
1866
- if (run?.calls?.length) {
1867
- const importLocals = buildAbsImportLocalMap(source, filePath);
1868
- return expandTestCallbacks(run.calls).map((r) => callRecordFromAbsCall(r, importLocals));
1869
- }
1870
- } catch {
1541
+ out = {
1542
+ exports,
1543
+ modules,
1544
+ memberDiags: memberDiags.length ? memberDiags : void 0,
1545
+ moduleIssues: issues.length ? issues : void 0,
1546
+ truncatedFns: truncated.size ? [...truncated] : void 0,
1547
+ calls: topCalls.length ? topCalls : void 0
1548
+ };
1549
+ } finally {
1550
+ setMemberDiagCollector(prevMember);
1551
+ setAbsTruncationCollector(prevTrunc);
1552
+ setBCallCollector(prevCall);
1871
1553
  }
1554
+ } catch {
1555
+ out = null;
1872
1556
  }
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
- }
1557
+ if (canCache && depKey !== null) {
1558
+ bPathCacheSet(filePath, stable, mode, envKey, mockKey, depKey, out);
1905
1559
  }
1906
- return out;
1560
+ return out ?? void 0;
1907
1561
  }
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
- };
1562
+ function tryBPathCallFull(source, filePath, fnName, args, opts = {}) {
1563
+ const run = tryRunBPath(source, filePath, { envNames: opts.envNames, mocks: opts.mocks });
1564
+ if (!run) return void 0;
1565
+ if (!(fnName in run.exports)) return void 0;
1566
+ const collected = [];
1567
+ const memberDiags = [];
1568
+ const prevCall = opts.collectCalls ? setBCallCollector((r) => collected.push(r)) : void 0;
1569
+ const wantMember = opts.collectMemberDiags ?? true;
1570
+ const prevMember = wantMember ? setMemberDiagCollector((d) => memberDiags.push(d)) : void 0;
1571
+ try {
1572
+ const full = callTranspiledExportFull2(run.exports, fnName, args, opts.phi ? { phi: opts.phi } : void 0);
1573
+ const all = [...run.memberDiags ?? [], ...memberDiags];
1574
+ return {
1575
+ ...full,
1576
+ calls: opts.collectCalls ? collected : void 0,
1577
+ memberDiags: all.length ? all : void 0,
1578
+ moduleIssues: run.moduleIssues,
1579
+ truncatedFns: run.truncatedFns
1580
+ };
1581
+ } finally {
1582
+ if (opts.collectCalls) setBCallCollector(prevCall ?? null);
1583
+ if (wantMember) setMemberDiagCollector(prevMember ?? null);
1584
+ }
1917
1585
  }
1918
- function isExit(stmt) {
1919
- return stmt.type === "ReturnStatement" || stmt.type === "ThrowStatement";
1586
+ function tryBPathCall(source, filePath, fnName, args, opts = {}) {
1587
+ const full = tryBPathCallFull(source, filePath, fnName, args, opts);
1588
+ if (!full) return void 0;
1589
+ const r = full.result;
1590
+ if (!r) return void 0;
1591
+ if (r.shape.k === "never" && full.throws.shape.k !== "never") {
1592
+ return void 0;
1593
+ }
1594
+ if (r.shape.k === "unknown" && !r.term) return void 0;
1595
+ return r;
1920
1596
  }
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;
1597
+
1598
+ // src/mock-abs.ts
1599
+ import { extractDirectives } from "@nudojs/parser";
1600
+ import { parse as parse5, parseCaseArgExpr } from "@nudojs/parser";
1601
+ import {
1602
+ absFunction as absFunction2,
1603
+ emptyEnv,
1604
+ abs as makeAbs2,
1605
+ confJoin,
1606
+ unknown as absUnknown2,
1607
+ litValue,
1608
+ formatAbs as formatAbs2,
1609
+ getFnImpl as getFnImpl2,
1610
+ bindingsOf as bindingsOf2,
1611
+ callTranspiledExportFull as callTranspiledExportFull3
1612
+ } from "@nudojs/core";
1613
+ function markMockConf(a) {
1614
+ a.conf = confJoin(a.conf, "mock");
1615
+ return a;
1616
+ }
1617
+ function astContentKey(n, depth = 0) {
1618
+ if (n == null || depth > 24) return "";
1619
+ if (typeof n !== "object") return String(n);
1620
+ if (Array.isArray(n)) return n.map((x) => astContentKey(x, depth + 1)).join(",");
1621
+ const obj = n;
1622
+ const type = typeof obj.type === "string" ? obj.type : "";
1623
+ const parts = [type];
1624
+ for (const key of ["name", "value", "raw", "operator", "computed"]) {
1625
+ const v = obj[key];
1626
+ if (v !== void 0 && (typeof v !== "object" || v === null)) parts.push(`${key}=${String(v)}`);
1627
+ }
1628
+ for (const key of Object.keys(obj)) {
1629
+ 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") {
1630
+ continue;
1974
1631
  }
1975
- default:
1976
- break;
1632
+ const v = obj[key];
1633
+ if (v && typeof v === "object") parts.push(`${key}:{${astContentKey(v, depth + 1)}}`);
1977
1634
  }
1635
+ return parts.join("|");
1978
1636
  }
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);
1637
+ function stampFingerprint(a, fp) {
1638
+ const impl = getFnImpl2(a);
1639
+ if (impl) impl.fingerprint = fp;
1640
+ return a;
1641
+ }
1642
+ function constantMockFn(result) {
1643
+ const marked = markMockConf(result);
1644
+ const mockEnv = emptyEnv();
1645
+ const retName = "__nudo_mock_ret";
1646
+ mockEnv.vars.set(retName, marked);
1647
+ const body = {
1648
+ type: "BlockStatement",
1649
+ body: [
1650
+ {
1651
+ type: "ReturnStatement",
1652
+ argument: { type: "Identifier", name: retName }
1987
1653
  }
1988
- }
1989
- if (id.type === "ArrayPattern") {
1990
- for (const el of id.elements) addId(el);
1991
- }
1654
+ ]
1992
1655
  };
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);
1656
+ let retKey;
1657
+ try {
1658
+ retKey = formatAbs2(marked);
1659
+ } catch {
1660
+ retKey = "?";
1661
+ }
1662
+ return stampFingerprint(
1663
+ markMockConf(absFunction2(["...args"], { body, env: mockEnv })),
1664
+ `ret=${retKey}`
1665
+ );
1666
+ }
1667
+ function absArgMatches(declared, actual) {
1668
+ if (!actual) return false;
1669
+ const av = litValue(actual);
1670
+ const dv = litValue(declared);
1671
+ if (dv !== void 0) {
1672
+ return av !== void 0 && Object.is(av, dv);
1673
+ }
1674
+ if (declared.shape.k === "prim") {
1675
+ if (actual.shape.k === "prim") return actual.shape.type === declared.shape.type;
1676
+ if (av !== void 0) {
1677
+ const t = typeof av;
1678
+ 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";
2023
1679
  }
1680
+ }
1681
+ return false;
1682
+ }
1683
+ function dispatchMockFn(defaultReturn, cases) {
1684
+ if (!cases?.length) return constantMockFn(defaultReturn);
1685
+ const caseAbs = cases.map((c) => ({
1686
+ declared: c.args,
1687
+ result: markMockConf(c.returnValue)
1688
+ }));
1689
+ const dummyBody = {
1690
+ type: "BlockStatement",
1691
+ body: [{ type: "ReturnStatement", argument: null }]
2024
1692
  };
2025
- visit(file);
2026
- return names;
1693
+ const markedDefault = markMockConf(defaultReturn);
1694
+ let defaultKey;
1695
+ try {
1696
+ defaultKey = formatAbs2(markedDefault);
1697
+ } catch {
1698
+ defaultKey = "?";
1699
+ }
1700
+ const caseKey = cases.map(
1701
+ (c) => `${c.args.map((a) => formatAbs2(a)).join(",")}->${formatAbs2(c.returnValue)}`
1702
+ ).join("|");
1703
+ return stampFingerprint(
1704
+ markMockConf(
1705
+ absFunction2(["...args"], {
1706
+ body: dummyBody,
1707
+ env: emptyEnv(),
1708
+ apply: (args) => {
1709
+ for (const c of caseAbs) {
1710
+ if (c.declared.every((d, i) => absArgMatches(d, args[i]))) {
1711
+ return c.result;
1712
+ }
1713
+ }
1714
+ return markedDefault;
1715
+ }
1716
+ })
1717
+ ),
1718
+ `dispatch=default=${defaultKey};cases=${caseKey}`
1719
+ );
2027
1720
  }
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));
1721
+ function absFromMockHelper(h) {
1722
+ if (h.callsFakeImpl) {
1723
+ const { params, body, async } = h.callsFakeImpl;
1724
+ return stampFingerprint(
1725
+ absFunction2(params, { body, async: async ?? false }),
1726
+ `fake=${params.join(",")}:${astContentKey(body)}`
1727
+ );
1728
+ }
1729
+ let defaultReturn;
1730
+ if (h.resolvedValue) {
1731
+ defaultReturn = makeAbs2(
1732
+ { k: "eff", eff: "promise", inner: h.resolvedValue },
1733
+ void 0,
1734
+ void 0,
1735
+ "path"
1736
+ );
1737
+ } else if (h.rejectedValue) {
1738
+ defaultReturn = makeAbs2({ k: "never" }, void 0, void 0, "exact");
1739
+ } else if (h.returnValue) {
1740
+ defaultReturn = h.returnValue;
1741
+ } else if (h.onFirstCallValue) {
1742
+ defaultReturn = h.onFirstCallValue;
1743
+ } else {
1744
+ defaultReturn = absUnknown2;
1745
+ }
1746
+ return dispatchMockFn(defaultReturn, h.withArgsCases);
1747
+ }
1748
+ function absFromSinon(sinonExpr) {
1749
+ if (sinonExpr.resolvedValue) {
1750
+ return constantMockFn(
1751
+ makeAbs2(
1752
+ { k: "eff", eff: "promise", inner: sinonExpr.resolvedValue },
1753
+ void 0,
1754
+ void 0,
1755
+ "path"
1756
+ )
1757
+ );
1758
+ }
1759
+ if (sinonExpr.rejectedValue) {
1760
+ return constantMockFn(makeAbs2({ k: "never" }, void 0, void 0, "exact"));
1761
+ }
1762
+ if (sinonExpr.returnValue) {
1763
+ return constantMockFn(sinonExpr.returnValue);
1764
+ }
1765
+ return constantMockFn(absUnknown2);
1766
+ }
1767
+ function isAbsVal2(v) {
1768
+ return !!v && typeof v === "object" && "shape" in v && "conf" in v;
1769
+ }
1770
+ function loadFromMockBinding(name, fromPath, fromFile, loadModule) {
1771
+ const base = fromFile ?? `${process.cwd()}/<mock-from>`;
1772
+ const evaled = evalMockFileWithDeps(fromPath, base, loadModule);
1773
+ if (!evaled.ok) {
1774
+ return {
1775
+ error: evaled.error.includes("not found") ? `Mock file not found for '${name}' (from "${fromPath}")` : evaled.error
1776
+ };
1777
+ }
1778
+ const run = evaled.run;
1779
+ let val = run[name];
1780
+ if (val === void 0) {
1781
+ const binds = bindingsOf2(run);
1782
+ val = binds?.get(name);
1783
+ }
1784
+ if (val === void 0) {
1785
+ return {
1786
+ error: `Mock file "${fromPath}" does not define a binding named '${name}'`
1787
+ };
2064
1788
  }
2065
- if (n.type === "BinaryExpression" && n.operator === "+") {
2066
- return isStaticStringExpr(n.left) && isStaticStringExpr(n.right);
1789
+ let absVal;
1790
+ if (isAbsVal2(val)) {
1791
+ absVal = val;
1792
+ } else if (typeof val === "function") {
1793
+ const fn = val;
1794
+ const params = Array.from({ length: fn.length ?? 0 }, (_, i) => `arg${i}`);
1795
+ absVal = absFunction2(params, {
1796
+ apply: (args) => callTranspiledExportFull3(run, name, args).result,
1797
+ kind: "bpath-export",
1798
+ fingerprint: `from-mock=${fromPath}#${name}`
1799
+ });
1800
+ } else {
1801
+ absVal = absUnknown2;
2067
1802
  }
2068
- return false;
1803
+ return { abs: markMockConf(absVal) };
2069
1804
  }
2070
- function isFreeUnknown(name, declared, seen, known) {
2071
- return !declared.has(name) && !known.has(name) && !name.startsWith("$") && !seen.has(name);
1805
+ function mockSeedsToAbsMocks(seeds) {
1806
+ const out = { ...seeds.seedVars };
1807
+ for (const [name, fn] of Object.entries(seeds.seedFns)) {
1808
+ out[name] = absFunction2(fn.params, { body: fn.body, async: fn.async ?? false });
1809
+ }
1810
+ return out;
2072
1811
  }
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");
1812
+ function mockDirectivesToAbsSeeds(functions, opts) {
1813
+ const seedVars = {};
1814
+ const seedFns = {};
1815
+ const fromErrors = [];
1816
+ for (const fn of functions) {
1817
+ for (const d of fn.directives) {
1818
+ if (d.kind !== "mock") continue;
1819
+ if (d.fromPath) {
1820
+ const r = loadFromMockBinding(d.name, d.fromPath, opts?.fromFile, opts?.loadModule);
1821
+ if (r.abs) {
1822
+ seedVars[d.name] = r.abs;
1823
+ } else {
1824
+ fromErrors.push({
1825
+ name: d.name,
1826
+ fromPath: d.fromPath,
1827
+ message: r.error ?? `Mock file "${d.fromPath}" could not be loaded`
1828
+ });
1829
+ }
1830
+ continue;
2091
1831
  }
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);
1832
+ if (d.arrowFn) {
1833
+ const body = d.arrowFn.body;
1834
+ seedFns[d.name] = {
1835
+ params: d.arrowFn.params,
1836
+ body,
1837
+ async: false,
1838
+ fingerprint: `fake=${d.arrowFn.params.join(",")}:${astContentKey(body)}`
1839
+ };
1840
+ } else if (d.nudoMock) {
1841
+ seedVars[d.name] = markMockConf(absFromMockHelper(d.nudoMock));
1842
+ } else if (d.sinonExpr) {
1843
+ seedVars[d.name] = markMockConf(absFromSinon(d.sinonExpr));
1844
+ } else if (d.expression) {
1845
+ try {
1846
+ seedVars[d.name] = parseCaseArgExpr(d.expression);
1847
+ } catch {
1848
+ seedVars[d.name] = absUnknown2;
2105
1849
  }
2106
- });
2107
- } else if (v && typeof v === "object" && "type" in v) {
2108
- walkBuiltinUnknown(v, declared, out, seen, known, key);
1850
+ }
2109
1851
  }
2110
1852
  }
1853
+ return { seedVars, seedFns, ...fromErrors.length > 0 ? { fromErrors } : {} };
2111
1854
  }
2112
- function collectBPathDiagnostics(source, extraKnown) {
2113
- let file;
1855
+ function mockSeedsForSource(source, opts) {
1856
+ const fns = extractDirectives(parse5(source));
1857
+ return mockSeedsToAbsMocks(mockDirectivesToAbsSeeds(fns, opts));
1858
+ }
1859
+
1860
+ // src/analyzer-orchestrate-calls.ts
1861
+ import { dirname as dirname6 } from "path";
1862
+ import {
1863
+ getBCallCollector,
1864
+ getFnImpl as getFnImpl4,
1865
+ setBCallCollector as setBCallCollector2,
1866
+ $call as $call2,
1867
+ unknown as absUnknown4
1868
+ } from "@nudojs/core";
1869
+ import { parse as parse7, extractFileDirectives as extractFileDirectives2 } from "@nudojs/parser";
1870
+
1871
+ // src/analyzer-abs-eval.ts
1872
+ import { existsSync as existsSync4, statSync as statSync5 } from "fs";
1873
+ import { resolve as resolve5, dirname as dirname5 } from "path";
1874
+ import {
1875
+ generalizeFromAst,
1876
+ termToString,
1877
+ predToString,
1878
+ unknown as absUnknown3,
1879
+ absFunction as absFunction3,
1880
+ formatAbs as formatAbs4,
1881
+ formatAbsMultiline
1882
+ } from "@nudojs/core";
1883
+ import { parse as parse6 } from "@nudojs/parser";
1884
+
1885
+ // src/evaluator/call-record.ts
1886
+ import { formatAbs as formatAbs3, joinAbs, litValue as litValue2 } from "@nudojs/core";
1887
+ var neverAbs = { shape: { k: "never" }, conf: "exact" };
1888
+ var undefAbs = {
1889
+ shape: { k: "unknown" },
1890
+ term: { op: "lit", value: void 0 },
1891
+ conf: "exact"
1892
+ };
1893
+ function absStructureKey(a) {
1894
+ const v = litValue2(a);
1895
+ if (v !== void 0) return `L:${typeof v}:${String(v)}`;
1896
+ if (a.term?.op === "lit") return `L:${typeof a.term.value}:${String(a.term.value)}`;
2114
1897
  try {
2115
- file = parseSource(source);
1898
+ return formatAbs3(a);
2116
1899
  } 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);
1900
+ return a.shape.k;
2126
1901
  }
2127
- const builtinUnknown = [];
2128
- walkBuiltinUnknown(file, declared, builtinUnknown, /* @__PURE__ */ new Set(), known);
2129
- return { unreachable, builtinUnknown };
2130
1902
  }
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;
1903
+ function shapeNodeCount(a, seen) {
1904
+ if (!a || typeof a !== "object") return 1;
1905
+ if (seen.has(a)) return 0;
1906
+ seen.add(a);
1907
+ const s = a.shape;
1908
+ switch (s.k) {
1909
+ case "arr":
1910
+ return 1 + shapeNodeCount(s.element, seen);
1911
+ case "tuple":
1912
+ return 1 + s.elements.reduce((acc, e) => acc + shapeNodeCount(e, seen), 0);
1913
+ case "obj": {
1914
+ let n = 1;
1915
+ for (const slot of Object.values(s.slots)) n += shapeNodeCount(slot.value, seen);
1916
+ return n;
2150
1917
  }
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
- }
1918
+ case "eff":
1919
+ return 1 + shapeNodeCount(s.inner, seen);
1920
+ case "sum":
1921
+ return 1 + s.members.reduce((acc, e) => acc + shapeNodeCount(e, seen), 0);
1922
+ case "brand":
1923
+ return 1 + shapeNodeCount(s.shape, seen);
1924
+ case "fn": {
1925
+ let n = 1;
1926
+ for (const p of s.paramTypes ?? []) n += shapeNodeCount(p, seen);
1927
+ if (s.returnType) n += shapeNodeCount(s.returnType, seen);
1928
+ return n;
2160
1929
  }
2161
- };
2162
- walk(exportsField, false);
1930
+ default:
1931
+ return 1;
1932
+ }
2163
1933
  }
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);
1934
+ var MAX_RECORD_TYPE_NODES = 2e3;
1935
+ function absNodeCount(a, seen = /* @__PURE__ */ new Set()) {
1936
+ return shapeNodeCount(a, seen);
1937
+ }
1938
+ function isOversizedCallRecord(rec) {
1939
+ const seen = /* @__PURE__ */ new Set();
1940
+ for (const a of rec.argAbs) {
1941
+ if (absNodeCount(a, seen) > MAX_RECORD_TYPE_NODES) return true;
1942
+ }
1943
+ return absNodeCount(rec.resultAbs, seen) > MAX_RECORD_TYPE_NODES;
1944
+ }
1945
+ function isLeakedCallRecord(rec) {
1946
+ return rec.resultAbs.shape.k === "never" && rec.throwsAbs.shape.k === "never";
1947
+ }
1948
+ function joinAllAbs(members) {
1949
+ if (members.length === 0) return neverAbs;
1950
+ return members.reduce((a, b) => joinAbs(a, b));
1951
+ }
1952
+ function isPrimLit(a) {
1953
+ return a.shape.k === "prim" && a.term?.op === "lit";
1954
+ }
1955
+ function widenAbsPrim(a) {
1956
+ if (a.shape.k === "prim" && a.term?.op === "lit") {
1957
+ return { shape: a.shape, conf: a.conf === "exact" ? "path" : a.conf };
1958
+ }
1959
+ return a;
1960
+ }
1961
+ function collapseAbsLits(members, maxLits) {
1962
+ const uniq = [];
1963
+ const seen = /* @__PURE__ */ new Set();
1964
+ for (const m of members) {
1965
+ if (m.shape.k === "never") continue;
1966
+ const k = absStructureKey(m);
1967
+ if (seen.has(k)) continue;
1968
+ seen.add(k);
1969
+ uniq.push(m);
1970
+ }
1971
+ if (uniq.length === 0) return neverAbs;
1972
+ if (uniq.length === 1) return uniq[0];
1973
+ const nonLitPrims = /* @__PURE__ */ new Set();
1974
+ for (const m of uniq) {
1975
+ if (m.shape.k === "prim" && m.term?.op !== "lit") {
1976
+ nonLitPrims.add(m.shape.type);
2175
1977
  }
2176
- for (const k of Object.keys(bField)) {
2177
- if (k.startsWith(".")) node.push(k);
1978
+ }
1979
+ if (nonLitPrims.size > 0) {
1980
+ const absorbed = uniq.filter(
1981
+ (m) => !(m.shape.k === "prim" && m.term?.op === "lit" && nonLitPrims.has(m.shape.type))
1982
+ );
1983
+ if (absorbed.length !== uniq.length) {
1984
+ return collapseAbsLits(absorbed, maxLits);
2178
1985
  }
2179
1986
  }
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;
1987
+ if (uniq.length > maxLits && uniq.every(isPrimLit)) {
1988
+ const t = uniq[0].shape.type;
1989
+ if (uniq.every((m) => m.shape.k === "prim" && m.shape.type === t)) {
1990
+ return { shape: { k: "prim", type: t }, conf: "path" };
1991
+ }
2189
1992
  }
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)] };
1993
+ return { shape: { k: "sum", members: uniq }, conf: "exact" };
2192
1994
  }
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);
1995
+ function widenJoinAbs(members) {
1996
+ return widenAbsPrim(joinAllAbs(members));
2204
1997
  }
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];
1998
+
1999
+ // src/analyzer-abs-eval.ts
2000
+ function absIsBetter(next, prev) {
2001
+ const isBareUnknown = (a) => a.shape.k === "unknown" && !a.term;
2002
+ const isNever = (a) => a.shape.k === "never";
2003
+ 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";
2004
+ const hasPred = (a) => !!a.pred && a.pred.op !== "true";
2005
+ if (isBareUnknown(prev)) return !isBareUnknown(next);
2006
+ if (isBareUnknown(next) || isNever(next)) return false;
2007
+ if (isStructured(prev)) return false;
2008
+ if (hasPred(next) && prev.shape.k === "prim" && !hasPred(prev)) return true;
2009
+ return false;
2212
2010
  }
2213
- function normalizeTarget(t) {
2214
- return t.replace(/^\.\//, "");
2011
+ function isSelfContainedSource(source, envNames) {
2012
+ if (envNames.length > 0) return false;
2013
+ return !/\brequire\s*\(|\bimport\s*[{'"*]/.test(source);
2215
2014
  }
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
- }
2015
+ function absModulesOk(source, envNames) {
2016
+ void source;
2017
+ void envNames;
2018
+ return true;
2234
2019
  }
2235
- function dualEntryForFile(filePath) {
2236
- let owning = null;
2020
+ function buildAbsImportLocalMap(source, fromFile) {
2021
+ const out = /* @__PURE__ */ new Map();
2237
2022
  try {
2238
- owning = findOwningPackage(filePath);
2023
+ const file = parse6(source);
2024
+ for (const stmt of file.program.body) {
2025
+ if (stmt.type === "ImportDeclaration") {
2026
+ const spec = stmt.source.value;
2027
+ let modulePath = null;
2028
+ if (spec.startsWith(".") || spec.startsWith("/")) {
2029
+ modulePath = resolveImportAbs(spec, fromFile);
2030
+ } else if (!spec.startsWith("node:")) {
2031
+ modulePath = spec;
2032
+ }
2033
+ if (!modulePath) continue;
2034
+ for (const s of stmt.specifiers) {
2035
+ if (s.type === "ImportSpecifier") {
2036
+ const imported = s.imported.type === "Identifier" ? s.imported.name : String(s.imported);
2037
+ out.set(s.local.name, { modulePath, exportName: imported });
2038
+ } else if (s.type === "ImportDefaultSpecifier") {
2039
+ out.set(s.local.name, { modulePath, exportName: "default" });
2040
+ } else if (s.type === "ImportNamespaceSpecifier") {
2041
+ out.set(s.local.name, { modulePath, exportName: "*" });
2042
+ }
2043
+ }
2044
+ continue;
2045
+ }
2046
+ if (stmt.type === "VariableDeclaration") {
2047
+ for (const d of stmt.declarations) {
2048
+ if (d.init?.type !== "CallExpression") continue;
2049
+ const callee = d.init.callee;
2050
+ if (callee.type !== "Identifier" || callee.name !== "require") continue;
2051
+ const arg0 = d.init.arguments[0];
2052
+ if (!arg0 || arg0.type !== "StringLiteral") continue;
2053
+ const spec = arg0.value;
2054
+ let modulePath = null;
2055
+ if (spec.startsWith(".") || spec.startsWith("/")) {
2056
+ modulePath = resolveImportAbs(spec, fromFile);
2057
+ } else if (!spec.startsWith("node:")) {
2058
+ modulePath = spec;
2059
+ }
2060
+ if (!modulePath) continue;
2061
+ if (d.id.type === "ObjectPattern") {
2062
+ for (const p of d.id.properties) {
2063
+ if (p.type !== "ObjectProperty" || p.computed) continue;
2064
+ if (p.key.type !== "Identifier" || p.value.type !== "Identifier") continue;
2065
+ out.set(p.value.name, { modulePath, exportName: p.key.name });
2066
+ }
2067
+ } else if (d.id.type === "Identifier") {
2068
+ out.set(d.id.name, { modulePath, exportName: "default" });
2069
+ }
2070
+ }
2071
+ }
2072
+ }
2239
2073
  } catch {
2240
- return null;
2241
2074
  }
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
- };
2075
+ return out;
2284
2076
  }
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
- };
2077
+ function resolveImportAbs(spec, fromFile) {
2078
+ const base = dirname5(resolve5(fromFile));
2079
+ const p = resolve5(base, spec);
2080
+ for (const cand of [p, `${p}.js`, `${p}.mjs`, `${p}.ts`, resolve5(p, "index.js")]) {
2081
+ if (existsSync4(cand) && !statSync5(cand).isDirectory()) return cand;
2082
+ }
2083
+ return null;
2351
2084
  }
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));
2085
+ function tryEvalAbsRaw(source, fnName, args, filePath, mocks, assignedName) {
2086
+ return tryEvalAbsFull(source, fnName, args, filePath, mocks, assignedName)?.result;
2087
+ }
2088
+ function tryEvalAbsFull(source, fnName, args, filePath, mocks, assignedName) {
2089
+ if (/\brequire\s*\(/.test(source)) return void 0;
2090
+ try {
2091
+ if (filePath) {
2092
+ const viaB = tryBPathCallFull(source, filePath, fnName, args, { mocks }) ?? (assignedName ? tryBPathCallFull(source, filePath, assignedName, args, { mocks }) : void 0);
2093
+ if (viaB) {
2094
+ const r = viaB.result;
2095
+ if (r && !(r.shape.k === "unknown" && !r.term)) {
2096
+ const throws = viaB.throws ?? { shape: { k: "never" }, conf: "exact" };
2097
+ return { result: r, throws };
2098
+ }
2099
+ }
2100
+ }
2101
+ return void 0;
2102
+ } catch {
2103
+ return void 0;
2356
2104
  }
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));
2105
+ }
2106
+ function tryEvalEntryAbs(source, fnName, args, filePath, mocks, assignedName) {
2107
+ return tryEvalAbsRaw(source, fnName, args, filePath, mocks, assignedName);
2108
+ }
2109
+ function tryAttachIntension(caseResult, source, fnName) {
2110
+ try {
2111
+ const g = generalizeFromAst(fnName, source);
2112
+ if (!g) return;
2113
+ const sym = g.symbolic;
2114
+ const intension = {
2115
+ display: g.display,
2116
+ abs: formatAbs4(sym),
2117
+ absMultiline: formatAbsMultiline(sym, fnName)
2118
+ };
2119
+ if (sym.term && sym.term.op !== "lit") {
2120
+ intension.term = termToString(sym.term);
2361
2121
  }
2122
+ if (sym.pred && sym.pred.op !== "true") {
2123
+ intension.pred = predToString(sym.pred);
2124
+ }
2125
+ intension.conf = sym.conf;
2126
+ caseResult.intension = intension;
2127
+ } catch {
2362
2128
  }
2363
- return [];
2364
2129
  }
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;
2130
+ function attachHofSnapshot(analysis, source) {
2131
+ if (analysis.hof) return;
2132
+ try {
2133
+ const g = generalizeFromAst(analysis.name, source);
2134
+ if (!g) return;
2135
+ const fnRels = g.fnRels ? [...g.fnRels.entries()].map(([param, rec]) => ({ param, abs: rec.abs })) : void 0;
2136
+ const entryShapes = g.entryShapes ? [...g.entryShapes.entries()].map(([param, rec]) => ({ param, abs: rec.abs })) : void 0;
2137
+ if (!fnRels?.length && !entryShapes?.length) return;
2138
+ analysis.hof = {
2139
+ ...fnRels?.length ? { fnRels } : {},
2140
+ ...entryShapes?.length ? { entryShapes } : {},
2141
+ symbolic: g.symbolic
2142
+ };
2143
+ } catch {
2371
2144
  }
2372
- return node;
2373
2145
  }
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;
2146
+ function attachAbsToIntension(caseResult, absVal, label) {
2147
+ const prev = caseResult.intension ?? {};
2148
+ caseResult.abs = absVal;
2149
+ caseResult.intension = {
2150
+ ...prev,
2151
+ abs: formatAbs4(absVal),
2152
+ absMultiline: formatAbsMultiline(absVal, label ?? caseResult.name),
2153
+ conf: absVal.conf
2154
+ };
2155
+ }
2156
+ function safeAbsOrUnknown(a) {
2157
+ if (a && typeof a === "object" && "shape" in a && "conf" in a) {
2158
+ return a;
2379
2159
  }
2380
- const idLoc = fnOrClassIdLoc(d);
2381
- if (idLoc) return idLoc;
2382
- return fallback;
2160
+ if (typeof a === "function") {
2161
+ const fn = a;
2162
+ const n = Math.max(0, fn.length);
2163
+ const params = Array.from({ length: n }, (_, i) => `arg${i}`);
2164
+ return absFunction3(params, {
2165
+ body: { type: "BlockStatement", body: [], directives: [] },
2166
+ apply: (args) => {
2167
+ try {
2168
+ const r = fn(...args);
2169
+ if (r && typeof r === "object" && "shape" in r) return r;
2170
+ return absUnknown3;
2171
+ } catch {
2172
+ return absUnknown3;
2173
+ }
2174
+ }
2175
+ });
2176
+ }
2177
+ return absUnknown3;
2383
2178
  }
2384
- function isFnExprValue(node) {
2385
- return !!node && (node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression");
2179
+ function callRecordFromAbsCall(r, impMap) {
2180
+ const argAbs = r.args.map(safeAbsOrUnknown);
2181
+ const threw = !!r.threw;
2182
+ const thrownOrResult = safeAbsOrUnknown(r.result);
2183
+ const rec = {
2184
+ fnName: r.fnName,
2185
+ argAbs,
2186
+ resultAbs: threw ? neverAbs : thrownOrResult,
2187
+ throwsAbs: threw ? thrownOrResult : neverAbs,
2188
+ callLoc: r.callLoc
2189
+ };
2190
+ const imp = impMap?.get(r.fnName) ?? // `ns.helper` / `Class.method`:先整名,再按首段点号拆命名空间成员
2191
+ (() => {
2192
+ const dot = r.fnName.indexOf(".");
2193
+ if (dot <= 0) return void 0;
2194
+ const base = impMap?.get(r.fnName.slice(0, dot));
2195
+ if (!base || base.exportName !== "*") return void 0;
2196
+ return {
2197
+ modulePath: base.modulePath,
2198
+ exportName: r.fnName.slice(dot + 1)
2199
+ };
2200
+ })();
2201
+ if (imp) {
2202
+ rec.targetModule = imp.modulePath;
2203
+ rec.targetExport = imp.exportName;
2204
+ }
2205
+ return rec;
2386
2206
  }
2387
- function namedFnExprId(node) {
2388
- return node.type === "FunctionExpression" && node.id ? node.id.name : null;
2207
+
2208
+ // src/analyzer-orchestrate-calls.ts
2209
+ function collectEnvNames(filePath, source, includeProject) {
2210
+ const ast = parse7(source);
2211
+ const fileDirectives = extractFileDirectives2(ast);
2212
+ const fileEnvNames = fileDirectives.filter((d) => d.kind === "env").flatMap((d) => d.envs);
2213
+ if (!includeProject) return fileEnvNames;
2214
+ const projectConfig = findProjectConfig(dirname6(filePath));
2215
+ const projectEnvNames = projectConfig?.config.env ?? [];
2216
+ return [.../* @__PURE__ */ new Set([...projectEnvNames, ...fileEnvNames])];
2389
2217
  }
2390
- function isModuleExportsTarget(node) {
2391
- return node.type === "MemberExpression" && !node.computed && isModuleExportsMember(node);
2218
+ function collectCallRecords(filePath, source) {
2219
+ if (filePath) {
2220
+ try {
2221
+ const mocks = mockSeedsForSource(source, {
2222
+ fromFile: filePath || void 0
2223
+ });
2224
+ const run = tryRunBPath(source, filePath, {
2225
+ mode: "exec",
2226
+ lenientGlobals: true,
2227
+ ...Object.keys(mocks).length > 0 ? { mocks } : {}
2228
+ });
2229
+ if (run?.calls?.length) {
2230
+ const importLocals = buildAbsImportLocalMap(source, filePath);
2231
+ return expandTestCallbacks(run.calls).map((r) => callRecordFromAbsCall(r, importLocals));
2232
+ }
2233
+ } catch {
2234
+ }
2235
+ }
2236
+ return [];
2392
2237
  }
2393
- function deepestAssignValue(expr) {
2394
- let cur = expr;
2395
- while (cur) {
2396
- const assign = asAssignmentExpression(cur);
2397
- if (!assign) break;
2398
- cur = assign.right;
2238
+ function expandTestCallbacks(calls) {
2239
+ const out = [...calls];
2240
+ const seenDescribe = /* @__PURE__ */ new Set();
2241
+ let cursor = 0;
2242
+ let expanded = 0;
2243
+ while (cursor < out.length && expanded < 500) {
2244
+ const rec = out[cursor++];
2245
+ if (rec.fnName !== "it" && rec.fnName !== "test" && rec.fnName !== "describe") continue;
2246
+ for (const a of rec.args) {
2247
+ if (!a || typeof a !== "object" || !("shape" in a)) continue;
2248
+ const abs = a;
2249
+ if (abs.shape.k !== "fn") continue;
2250
+ if (rec.fnName === "describe") {
2251
+ if (seenDescribe.has(abs)) continue;
2252
+ seenDescribe.add(abs);
2253
+ }
2254
+ const impl = getFnImpl4(abs);
2255
+ const params = impl?.params ?? [];
2256
+ const collected = [];
2257
+ const prev = getBCallCollector();
2258
+ setBCallCollector2((r) => collected.push(r));
2259
+ try {
2260
+ $call2(abs, params.map(() => absUnknown4));
2261
+ } catch {
2262
+ } finally {
2263
+ setBCallCollector2(prev);
2264
+ }
2265
+ out.push(...collected);
2266
+ expanded++;
2267
+ }
2399
2268
  }
2400
- return cur ?? null;
2269
+ return out;
2401
2270
  }
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;
2271
+
2272
+ // src/bpath-diagnostics.ts
2273
+ import { parseSource } from "@nudojs/core";
2274
+ function locOf(node) {
2275
+ if (!node.loc) return null;
2276
+ return {
2277
+ start: { line: node.loc.start.line, column: node.loc.start.column },
2278
+ end: { line: node.loc.end.line, column: node.loc.end.column }
2279
+ };
2280
+ }
2281
+ function isExit(stmt) {
2282
+ return stmt.type === "ReturnStatement" || stmt.type === "ThrowStatement";
2283
+ }
2284
+ function walkUnreachable(node, out) {
2285
+ const n = node;
2286
+ const checkBlock = (block) => {
2287
+ if (!block) return;
2288
+ const body = block.body ?? (Array.isArray(block) ? block : null);
2289
+ if (!Array.isArray(body)) return;
2290
+ for (let i = 0; i < body.length; i++) {
2291
+ walkUnreachable(body[i], out);
2292
+ if (isExit(body[i])) {
2293
+ for (let j = i + 1; j < body.length; j++) {
2294
+ const r = locOf(body[j]);
2295
+ if (r) out.push({ range: r });
2296
+ }
2297
+ break;
2298
+ }
2299
+ }
2300
+ };
2301
+ switch (n.type) {
2302
+ case "Program":
2303
+ case "BlockStatement":
2304
+ checkBlock(n.body);
2305
+ break;
2306
+ case "FunctionDeclaration":
2307
+ case "FunctionExpression":
2308
+ case "ArrowFunctionExpression":
2309
+ if (n.body) walkUnreachable(n.body, out);
2310
+ break;
2311
+ case "IfStatement":
2312
+ if (n.consequent) walkUnreachable(n.consequent, out);
2313
+ if (n.alternate) walkUnreachable(n.alternate, out);
2314
+ break;
2315
+ case "ForStatement":
2316
+ case "ForInStatement":
2317
+ case "ForOfStatement":
2318
+ case "WhileStatement":
2319
+ case "DoWhileStatement":
2320
+ if (n.body) walkUnreachable(n.body, out);
2321
+ break;
2322
+ case "TryStatement":
2323
+ if (n.block) walkUnreachable(n.block, out);
2324
+ if (n.handler?.body) walkUnreachable(n.handler.body, out);
2325
+ if (n.finalizer) walkUnreachable(n.finalizer, out);
2326
+ break;
2327
+ case "SwitchStatement":
2328
+ for (const c of n.cases ?? []) {
2329
+ checkBlock(c.consequent);
2413
2330
  }
2414
- return memberPropertyKey(target);
2331
+ break;
2332
+ case "ExportNamedDeclaration":
2333
+ case "ExportDefaultDeclaration": {
2334
+ const decl = n.declaration;
2335
+ if (decl) walkUnreachable(decl, out);
2336
+ break;
2415
2337
  }
2416
- return null;
2338
+ default:
2339
+ break;
2417
2340
  }
2418
- return null;
2419
2341
  }
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
- });
2342
+ function collectDeclared(file) {
2343
+ const names = /* @__PURE__ */ new Set();
2344
+ const addId = (id) => {
2345
+ if (!id) return;
2346
+ if (id.type === "Identifier") names.add(id.name);
2347
+ if (id.type === "ObjectPattern") {
2348
+ for (const p of id.properties) {
2349
+ addId(p.value);
2350
+ }
2351
+ }
2352
+ if (id.type === "ArrayPattern") {
2353
+ for (const el of id.elements) addId(el);
2434
2354
  }
2435
2355
  };
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;
2356
+ const visit = (node) => {
2357
+ if (!node || typeof node !== "object") return;
2358
+ const o = node;
2359
+ switch (o.type) {
2360
+ case "FunctionDeclaration":
2361
+ case "ClassDeclaration":
2362
+ addId(o.id);
2363
+ break;
2364
+ case "VariableDeclarator":
2365
+ addId(o.id);
2366
+ break;
2367
+ case "ImportSpecifier":
2368
+ case "ImportDefaultSpecifier":
2369
+ case "ImportNamespaceSpecifier":
2370
+ addId(o.local);
2371
+ break;
2372
+ case "CatchClause":
2373
+ addId(o.param);
2374
+ break;
2375
+ default:
2376
+ break;
2441
2377
  }
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
- }
2378
+ if (o.params && Array.isArray(o.params)) {
2379
+ for (const p of o.params) addId(p);
2463
2380
  }
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);
2381
+ for (const key of Object.keys(o)) {
2382
+ if (key === "loc" || key === "start" || key === "end") continue;
2383
+ const v = o[key];
2384
+ if (Array.isArray(v)) v.forEach(visit);
2385
+ else if (v && typeof v === "object") visit(v);
2386
+ }
2387
+ };
2388
+ visit(file);
2389
+ return names;
2390
+ }
2391
+ var KNOWN_GLOBALS = /* @__PURE__ */ new Set([
2392
+ "Object",
2393
+ "Array",
2394
+ "String",
2395
+ "Number",
2396
+ "Boolean",
2397
+ "Symbol",
2398
+ "Math",
2399
+ "JSON",
2400
+ "Promise",
2401
+ "Error",
2402
+ "TypeError",
2403
+ "RangeError",
2404
+ "SyntaxError",
2405
+ "RegExp",
2406
+ "Date",
2407
+ "Map",
2408
+ "Set",
2409
+ "WeakMap",
2410
+ "WeakSet",
2411
+ "console",
2412
+ "undefined",
2413
+ "NaN",
2414
+ "Infinity",
2415
+ "globalThis",
2416
+ "parseInt",
2417
+ "parseFloat",
2418
+ "isNaN",
2419
+ "isFinite"
2420
+ ]);
2421
+ function isStaticStringExpr(node) {
2422
+ if (!node || typeof node !== "object") return false;
2423
+ const n = node;
2424
+ if (n.type === "StringLiteral" || n.type === "NumericLiteral") return true;
2425
+ if (n.type === "TemplateLiteral") {
2426
+ return (n.expressions ?? []).every((e) => isStaticStringExpr(e));
2427
+ }
2428
+ if (n.type === "BinaryExpression" && n.operator === "+") {
2429
+ return isStaticStringExpr(n.left) && isStaticStringExpr(n.right);
2430
+ }
2431
+ return false;
2432
+ }
2433
+ function isFreeUnknown(name, declared, seen, known) {
2434
+ return !declared.has(name) && !known.has(name) && !name.startsWith("$") && !seen.has(name);
2435
+ }
2436
+ function walkBuiltinUnknown(node, declared, out, seen, known, parentKey) {
2437
+ const n = node;
2438
+ const flag = (idNode, name) => {
2439
+ if (!isFreeUnknown(name, declared, seen, known)) return;
2440
+ const range = locOf(idNode) ?? locOf(node);
2441
+ if (range) {
2442
+ out.push({ name, range });
2443
+ seen.add(name);
2444
+ }
2445
+ };
2446
+ if (n.type === "CallExpression") {
2447
+ const callee = n.callee;
2448
+ const args = n.arguments ?? [];
2449
+ if (callee?.type === "Identifier" && callee.name === "require") {
2450
+ if (!isStaticStringExpr(args[0])) flag(n.callee, "require");
2451
+ } else if (callee?.type === "MemberExpression" && callee.object?.type === "Identifier" && callee.object.name === "require") {
2452
+ if (callee.property?.name === "resolve" && !isStaticStringExpr(args[0])) {
2453
+ flag(callee.object, "require");
2473
2454
  }
2474
- continue;
2455
+ } else if (callee?.type === "Identifier") {
2456
+ flag(n.callee, callee.name);
2475
2457
  }
2458
+ } else if (n.type === "Identifier" && typeof n.name === "string" && n.name !== "require" && parentKey !== "key" && parentKey !== "property" && parentKey !== "local" && parentKey !== "id") {
2459
+ flag(node, n.name);
2476
2460
  }
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;
2461
+ for (const key of Object.keys(n)) {
2462
+ if (key === "loc" || key === "start" || key === "end") continue;
2463
+ const v = n[key];
2464
+ if (Array.isArray(v)) {
2465
+ v.forEach((x) => {
2466
+ if (x && typeof x === "object" && "type" in x) {
2467
+ walkBuiltinUnknown(x, declared, out, seen, known, key);
2468
+ }
2469
+ });
2470
+ } else if (v && typeof v === "object" && "type" in v) {
2471
+ walkBuiltinUnknown(v, declared, out, seen, known, key);
2482
2472
  }
2483
- if (decl.type === "ClassDeclaration" && decl.id?.name) {
2484
- continue;
2473
+ }
2474
+ }
2475
+ function collectBPathDiagnostics(source, extraKnown) {
2476
+ let file;
2477
+ try {
2478
+ file = parseSource(source);
2479
+ } catch {
2480
+ return { unreachable: [], builtinUnknown: [] };
2481
+ }
2482
+ const unreachable = [];
2483
+ const root = file.program ?? file;
2484
+ walkUnreachable(root, unreachable);
2485
+ const declared = collectDeclared(file);
2486
+ const known = new Set(KNOWN_GLOBALS);
2487
+ if (extraKnown) {
2488
+ for (const n of extraKnown) known.add(n);
2489
+ }
2490
+ const builtinUnknown = [];
2491
+ walkBuiltinUnknown(file, declared, builtinUnknown, /* @__PURE__ */ new Set(), known);
2492
+ return { unreachable, builtinUnknown };
2493
+ }
2494
+
2495
+ // src/entry-variants.ts
2496
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
2497
+ import { dirname as dirname7, join as join3, resolve as resolve6 } from "path";
2498
+ function collectExportFaces(exportsField, browser, node) {
2499
+ if (exportsField == null) return;
2500
+ if (typeof exportsField === "string") {
2501
+ node.push(exportsField);
2502
+ return;
2503
+ }
2504
+ const walk = (val, inBrowser) => {
2505
+ if (typeof val === "string") {
2506
+ if (inBrowser) browser.push(val);
2507
+ else node.push(val);
2508
+ return;
2485
2509
  }
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;
2510
+ if (Array.isArray(val)) {
2511
+ for (const v of val) walk(v, inBrowser);
2512
+ return;
2494
2513
  }
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 });
2514
+ if (!val || typeof val !== "object") return;
2515
+ for (const [k, v] of Object.entries(val)) {
2516
+ if (k.startsWith(".")) {
2517
+ walk(v, inBrowser);
2518
+ } else if (k === "browser" || k === "web") {
2519
+ walk(v, true);
2520
+ } else {
2521
+ walk(v, inBrowser);
2505
2522
  }
2506
- continue;
2507
2523
  }
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;
2524
+ };
2525
+ walk(exportsField, false);
2526
+ }
2527
+ function detectEntryVariantsFromPackageJson(pkg) {
2528
+ if (!pkg || typeof pkg !== "object") return null;
2529
+ const p = pkg;
2530
+ const browser = [];
2531
+ const node = [];
2532
+ collectExportFaces(p.exports, browser, node);
2533
+ const bField = p.browser;
2534
+ if (typeof bField === "string") browser.push(bField);
2535
+ else if (bField && typeof bField === "object") {
2536
+ for (const v of Object.values(bField)) {
2537
+ if (typeof v === "string") browser.push(v);
2516
2538
  }
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
- }
2539
+ for (const k of Object.keys(bField)) {
2540
+ if (k.startsWith(".")) node.push(k);
2526
2541
  }
2527
2542
  }
2528
- for (const cname of exportedClassNames) {
2529
- const hit = classDecls.get(cname);
2530
- if (!hit) continue;
2531
- collectClassMethods(cname, hit.node, hit.stmt);
2543
+ if (typeof p.main === "string") node.push(p.main);
2544
+ if (typeof p.module === "string") node.push(p.module);
2545
+ const browserPaths = uniqueResolved(browser, null);
2546
+ const nodePaths = uniqueResolved(node, null);
2547
+ if (browserPaths.length === 0 || nodePaths.length === 0) return null;
2548
+ const browserSet = new Set(browserPaths);
2549
+ const nodeSet = new Set(nodePaths);
2550
+ if (browserSet.size === nodeSet.size && [...browserSet].every((p2) => nodeSet.has(p2))) {
2551
+ return null;
2532
2552
  }
2533
- return results;
2553
+ const kind = typeof p.exports !== "undefined" && collectExportHasBrowser(p.exports) ? "exports-conditions" : "browser-field";
2554
+ return { kind, browser: [...new Set(browser)], node: [...new Set(node)] };
2534
2555
  }
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;
2556
+ function collectExportHasBrowser(exportsField) {
2557
+ if (!exportsField || typeof exportsField !== "object") return false;
2558
+ const walk = (val) => {
2559
+ if (!val || typeof val !== "object") return false;
2560
+ for (const [k, v] of Object.entries(val)) {
2561
+ if (k === "browser" || k === "web") return true;
2562
+ if (walk(v)) return true;
2553
2563
  }
2554
- if (!targetsModuleExports) continue;
2555
- if (found) return null;
2556
- found = fn;
2564
+ return false;
2565
+ };
2566
+ return walk(exportsField);
2567
+ }
2568
+ function uniqueResolved(targets, pkgDir) {
2569
+ const out = /* @__PURE__ */ new Set();
2570
+ for (const t of targets) {
2571
+ if (typeof t !== "string" || t.length === 0) continue;
2572
+ out.add(pkgDir === null ? normalizeTarget(t) : resolve6(pkgDir, t));
2557
2573
  }
2558
- return found;
2574
+ return [...out];
2559
2575
  }
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]);
2576
+ function normalizeTarget(t) {
2577
+ return t.replace(/^\.\//, "");
2578
+ }
2579
+ function findOwningPackage(fromFile) {
2580
+ let dir = dirname7(resolve6(fromFile));
2581
+ const root = resolve6("/");
2582
+ for (; ; ) {
2583
+ const pkgPath = join3(dir, "package.json");
2584
+ if (existsSync5(pkgPath)) {
2585
+ try {
2586
+ const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
2587
+ return { path: pkgPath, dir, pkg };
2588
+ } catch {
2589
+ return null;
2586
2590
  }
2587
2591
  }
2588
- };
2589
- walk(programBody(ast));
2590
- }
2591
- function nullLitAbs() {
2592
- return makeAbsVal({ k: "unknown" }, { op: "lit", value: null }, void 0, "exact");
2592
+ if (dir === root) return null;
2593
+ const parent = dirname7(dir);
2594
+ if (parent === dir) return null;
2595
+ dir = parent;
2596
+ }
2593
2597
  }
2594
- function buildNodeTypeMap(ast, env, nodeAbsMap) {
2595
- const traverseFn = unwrapDefaultExport(traverse);
2598
+ function entryVariantForFile(filePath) {
2599
+ let owning = null;
2596
2600
  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
- });
2601
+ owning = findOwningPackage(filePath);
2625
2602
  } catch {
2603
+ return null;
2626
2604
  }
2605
+ if (!owning) return null;
2606
+ const faces = detectEntryVariantsFromPackageJson(owning.pkg);
2607
+ if (!faces) return null;
2608
+ const abs = resolve6(filePath);
2609
+ const browserPaths = uniqueResolved(faces.browser, owning.dir);
2610
+ const nodePaths = uniqueResolved(faces.node, owning.dir);
2611
+ const inBrowser = browserPaths.includes(abs);
2612
+ const inNode = nodePaths.includes(abs);
2613
+ if (!inBrowser && !inNode) return null;
2614
+ if (inBrowser && inNode) return null;
2615
+ return {
2616
+ pkgPath: owning.path,
2617
+ pkgDir: owning.dir,
2618
+ ...typeof owning.pkg.name === "string" ? { pkgName: owning.pkg.name } : {},
2619
+ kind: faces.kind,
2620
+ browserPaths,
2621
+ nodePaths,
2622
+ role: inBrowser ? "browser" : "node",
2623
+ browserTargets: [...new Set(faces.browser.map(normalizeTarget))],
2624
+ nodeTargets: [...new Set(faces.node.map(normalizeTarget))]
2625
+ };
2626
+ }
2627
+ function entryVariantMessage(info) {
2628
+ const name = info.pkgName ? `"${info.pkgName}"` : info.pkgPath;
2629
+ const b = info.browserTargets.join(", ") || "(browser)";
2630
+ const n = info.nodeTargets.join(", ") || "(node)";
2631
+ 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`;
2632
+ }
2633
+ function entryVariantSuggestion() {
2634
+ return "analyze the entry you ship and mock or skip the other variant; browser/node records stay file-scoped (limits: dual package entrypoints)";
2635
+ }
2636
+ function entryVariantIssueForFile(filePath) {
2637
+ const info = entryVariantForFile(filePath);
2638
+ if (!info) return null;
2639
+ return {
2640
+ severity: "info",
2641
+ code: "nudo:dual-entry",
2642
+ message: entryVariantMessage(info),
2643
+ suggestion: entryVariantSuggestion(),
2644
+ line: 0,
2645
+ column: 0
2646
+ };
2627
2647
  }
2628
2648
 
2649
+ // src/analyzer-orchestrate.ts
2650
+ import { dirname as dirname9 } from "path";
2651
+
2652
+ // src/analyzer-orchestrate-uncached.ts
2653
+ import { realpathSync } from "fs";
2654
+ import { resolve as resolve7, dirname as dirname8 } from "path";
2655
+ import {
2656
+ createEnvironment as createEnvironment2,
2657
+ markPureFn,
2658
+ unknown as absUnknown5,
2659
+ anyAbs,
2660
+ formatAbs as formatAbs5,
2661
+ formatShape,
2662
+ leqAbs,
2663
+ localNamedExports,
2664
+ effectiveInterface,
2665
+ abs as makeAbsVal2,
2666
+ formalParamsFromNodes as formalParamsFromNodes2
2667
+ } from "@nudojs/core";
2668
+ import {
2669
+ setMayThrowCollector,
2670
+ mayThrowEffectsToAbs,
2671
+ formatThrowsAbs,
2672
+ checkInjectedDomainEvidence,
2673
+ runWithEvalMissingSlot,
2674
+ fnFingerprints,
2675
+ loadModuleDepsFingerprint as loadModuleDepsFingerprint3,
2676
+ hashSource as hashSource3
2677
+ } from "@nudojs/core/internal";
2678
+ import { parse as parse8, extractDirectives as extractDirectives2, extractFileDirectives as extractFileDirectives3 } from "@nudojs/parser";
2679
+
2629
2680
  // src/analyzer-diagnose.ts
2630
2681
  function validateMockDirectives(directives, diagnostics) {
2631
2682
  for (const d of directives) {
@@ -3563,14 +3614,14 @@ function analyzeFileUncachedInner(filePath, source, activeCases, externalCallRec
3563
3614
  });
3564
3615
  }
3565
3616
  try {
3566
- const dual = dualEntryForFile(filePath);
3617
+ const dual = entryVariantForFile(filePath);
3567
3618
  if (dual) {
3568
3619
  diagnostics.push({
3569
3620
  range: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
3570
3621
  severity: "info",
3571
- message: dualEntryMessage(dual),
3622
+ message: entryVariantMessage(dual),
3572
3623
  code: "nudo:dual-entry",
3573
- suggestions: [dualEntrySuggestion()]
3624
+ suggestions: [entryVariantSuggestion()]
3574
3625
  });
3575
3626
  }
3576
3627
  } catch {
@@ -3631,6 +3682,7 @@ function analyzeFile(filePath, source, activeCases, externalCallRecords, loadMod
3631
3682
  }
3632
3683
 
3633
3684
  export {
3685
+ resolveModule,
3634
3686
  buildModuleGraph,
3635
3687
  computeDirtySet,
3636
3688
  topoSortDirty,
@@ -3678,10 +3730,10 @@ export {
3678
3730
  collectEnvNames,
3679
3731
  collectCallRecords,
3680
3732
  collectBPathDiagnostics,
3681
- detectDualEntryFromPackageJson,
3733
+ detectEntryVariantsFromPackageJson,
3682
3734
  findOwningPackage,
3683
- dualEntryForFile,
3684
- dualEntryIssueForFile,
3735
+ entryVariantForFile,
3736
+ entryVariantIssueForFile,
3685
3737
  analyzeFileAsync,
3686
3738
  analyzeFile
3687
3739
  };