@nudojs/service 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,19 +1,37 @@
1
1
  // src/analyzer.ts
2
- import { readFileSync, existsSync, statSync, realpathSync } from "fs";
3
- import { resolve, dirname } from "path";
2
+ import { readFileSync as readFileSync4, existsSync as existsSync3, statSync as statSync3, realpathSync } from "fs";
3
+ import { resolve as resolve4, dirname as dirname5 } from "path";
4
4
  import traverse from "@babel/traverse";
5
5
  import {
6
- T,
7
- typeValueToString,
6
+ T as T2,
7
+ typeValueToString as typeValueToString3,
8
8
  simplifyUnion,
9
9
  widenLiteral,
10
10
  collapseLiteralUnion,
11
- createEnvironment,
11
+ createEnvironment as createEnvironment2,
12
12
  isSubtypeOf,
13
- mockHelperToTypeValue
13
+ mockHelperToTypeValue,
14
+ getFnSig as getFnSig3,
15
+ generalizeFromAst as generalizeFromAst2,
16
+ termToString,
17
+ predToString,
18
+ analyzeFn,
19
+ evalProgramAbs as evalProgramAbs2,
20
+ setAbsCallCollector,
21
+ setAbsNodeCollector,
22
+ absFunction as absFunction5,
23
+ collectAbsNodeTypes,
24
+ findAbsAtPosition,
25
+ typeValueToAbs as typeValueToAbs5,
26
+ absToTypeValue as absToTypeValue2,
27
+ formatAbs as formatAbs3,
28
+ formatAbsMultiline,
29
+ stableAnalyzeKeySource as stableAnalyzeKeySource2,
30
+ fnFingerprints
14
31
  } from "@nudojs/core";
15
- import { parse, extractDirectives, extractFileDirectives, parseTypeValueExpr } from "@nudojs/parser";
32
+ import { parse as parse5, extractDirectives, extractFileDirectives, parseTypeValueExpr as parseTypeValueExpr2 } from "@nudojs/parser";
16
33
  import {
34
+ evaluate,
17
35
  evaluateFunction,
18
36
  evaluateFunctionFull,
19
37
  memberMayExistOn,
@@ -36,32 +54,1545 @@ import {
36
54
  setMockModules,
37
55
  resetMockModules,
38
56
  setCurrentSource,
39
- loadEnvs,
57
+ loadEnvs as loadEnvs2,
40
58
  preloadPathEnvs,
41
59
  findProjectConfig,
42
- resolveNpmNudo
60
+ resolveNpmNudo,
61
+ BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS
43
62
  } from "@nudojs/cli/evaluator";
63
+
64
+ // src/mock-abs.ts
65
+ import { parseTypeValueExpr } from "@nudojs/parser";
66
+ import {
67
+ absFunction,
68
+ emptyEnv,
69
+ typeValueToAbs,
70
+ abs as makeAbs,
71
+ confJoin,
72
+ unknown as absUnknown,
73
+ litValue,
74
+ formatAbs,
75
+ getFnImpl,
76
+ typeValueToString
77
+ } from "@nudojs/core";
78
+ function markMockConf(a) {
79
+ a.conf = confJoin(a.conf, "mock");
80
+ return a;
81
+ }
82
+ function astContentKey(n, depth = 0) {
83
+ if (n == null || depth > 24) return "";
84
+ if (typeof n !== "object") return String(n);
85
+ if (Array.isArray(n)) return n.map((x) => astContentKey(x, depth + 1)).join(",");
86
+ const obj = n;
87
+ const type = typeof obj.type === "string" ? obj.type : "";
88
+ const parts = [type];
89
+ for (const key of ["name", "value", "raw", "operator", "computed"]) {
90
+ const v = obj[key];
91
+ if (v !== void 0 && (typeof v !== "object" || v === null)) parts.push(`${key}=${String(v)}`);
92
+ }
93
+ for (const key of Object.keys(obj)) {
94
+ 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") {
95
+ continue;
96
+ }
97
+ const v = obj[key];
98
+ if (v && typeof v === "object") parts.push(`${key}:{${astContentKey(v, depth + 1)}}`);
99
+ }
100
+ return parts.join("|");
101
+ }
102
+ function stampFingerprint(a, fp) {
103
+ const impl = getFnImpl(a);
104
+ if (impl) impl.fingerprint = fp;
105
+ return a;
106
+ }
107
+ function constantMockFn(result) {
108
+ const marked = markMockConf(result);
109
+ const mockEnv = emptyEnv();
110
+ const retName = "__nudo_mock_ret";
111
+ mockEnv.vars.set(retName, marked);
112
+ const body = {
113
+ type: "BlockStatement",
114
+ body: [
115
+ {
116
+ type: "ReturnStatement",
117
+ argument: { type: "Identifier", name: retName }
118
+ }
119
+ ]
120
+ };
121
+ let retKey;
122
+ try {
123
+ retKey = formatAbs(marked);
124
+ } catch {
125
+ retKey = "?";
126
+ }
127
+ return stampFingerprint(
128
+ markMockConf(absFunction(["...args"], { body, env: mockEnv })),
129
+ `ret=${retKey}`
130
+ );
131
+ }
132
+ function absArgMatches(declared, actual) {
133
+ if (!actual) return false;
134
+ const declaredAbs = typeValueToAbs(declared);
135
+ const av = litValue(actual);
136
+ const dv = litValue(declaredAbs);
137
+ if (dv !== void 0) {
138
+ return av !== void 0 && Object.is(av, dv);
139
+ }
140
+ if (declared.kind === "primitive") {
141
+ if (actual.shape.k === "prim") return actual.shape.type === declared.type;
142
+ if (av !== void 0) {
143
+ const t = typeof av;
144
+ return declared.type === "number" && t === "number" || declared.type === "string" && t === "string" || declared.type === "boolean" && t === "boolean" || declared.type === "bigint" && t === "bigint";
145
+ }
146
+ }
147
+ return false;
148
+ }
149
+ function dispatchMockFn(defaultReturn, cases) {
150
+ if (!cases?.length) return constantMockFn(defaultReturn);
151
+ const caseAbs = cases.map((c) => ({
152
+ declared: c.args,
153
+ result: markMockConf(typeValueToAbs(c.returnValue))
154
+ }));
155
+ const dummyBody = {
156
+ type: "BlockStatement",
157
+ body: [{ type: "ReturnStatement", argument: null }]
158
+ };
159
+ const markedDefault = markMockConf(defaultReturn);
160
+ let defaultKey;
161
+ try {
162
+ defaultKey = formatAbs(markedDefault);
163
+ } catch {
164
+ defaultKey = "?";
165
+ }
166
+ const caseKey = cases.map(
167
+ (c) => `${c.args.map((a) => typeValueToString(a)).join(",")}->${typeValueToString(c.returnValue)}`
168
+ ).join("|");
169
+ return stampFingerprint(
170
+ markMockConf(
171
+ absFunction(["...args"], {
172
+ body: dummyBody,
173
+ env: emptyEnv(),
174
+ apply: (args) => {
175
+ for (const c of caseAbs) {
176
+ if (c.declared.every((d, i) => absArgMatches(d, args[i]))) {
177
+ return c.result;
178
+ }
179
+ }
180
+ return markedDefault;
181
+ }
182
+ })
183
+ ),
184
+ `dispatch=default=${defaultKey};cases=${caseKey}`
185
+ );
186
+ }
187
+ function absFromMockHelper(h) {
188
+ if (h.callsFakeImpl && h.callsFakeImpl.kind === "function") {
189
+ const fn = h.callsFakeImpl;
190
+ const body = fn.body;
191
+ return stampFingerprint(
192
+ absFunction(fn.params, { body, async: false }),
193
+ `fake=${fn.params.join(",")}:${astContentKey(body)}`
194
+ );
195
+ }
196
+ let defaultReturn;
197
+ if (h.resolvedValue) {
198
+ defaultReturn = makeAbs(
199
+ { k: "eff", eff: "promise", inner: typeValueToAbs(h.resolvedValue) },
200
+ void 0,
201
+ void 0,
202
+ "path"
203
+ );
204
+ } else if (h.rejectedValue) {
205
+ defaultReturn = makeAbs({ k: "never" }, void 0, void 0, "exact");
206
+ } else if (h.returnValue) {
207
+ defaultReturn = typeValueToAbs(h.returnValue);
208
+ } else if (h.onFirstCallValue) {
209
+ defaultReturn = typeValueToAbs(h.onFirstCallValue);
210
+ } else {
211
+ defaultReturn = absUnknown;
212
+ }
213
+ return dispatchMockFn(defaultReturn, h.withArgsCases);
214
+ }
215
+ function absFromSinon(sinonExpr) {
216
+ if (sinonExpr.resolvedValue) {
217
+ return constantMockFn(
218
+ makeAbs(
219
+ { k: "eff", eff: "promise", inner: typeValueToAbs(sinonExpr.resolvedValue) },
220
+ void 0,
221
+ void 0,
222
+ "path"
223
+ )
224
+ );
225
+ }
226
+ if (sinonExpr.rejectedValue) {
227
+ return constantMockFn(makeAbs({ k: "never" }, void 0, void 0, "exact"));
228
+ }
229
+ if (sinonExpr.returnValue) {
230
+ return constantMockFn(typeValueToAbs(sinonExpr.returnValue));
231
+ }
232
+ return constantMockFn(absUnknown);
233
+ }
234
+ function mockSeedsToAbsMocks(seeds) {
235
+ const out = { ...seeds.seedVars };
236
+ for (const [name, fn] of Object.entries(seeds.seedFns)) {
237
+ out[name] = absFunction(fn.params, { body: fn.body, async: fn.async ?? false });
238
+ }
239
+ return out;
240
+ }
241
+ function mockDirectivesToAbsSeeds(functions) {
242
+ const seedVars = {};
243
+ const seedFns = {};
244
+ for (const fn of functions) {
245
+ for (const d of fn.directives) {
246
+ if (d.kind !== "mock") continue;
247
+ if (d.arrowFn) {
248
+ const body = d.arrowFn.body;
249
+ seedFns[d.name] = {
250
+ params: d.arrowFn.params,
251
+ body,
252
+ async: false,
253
+ fingerprint: `fake=${d.arrowFn.params.join(",")}:${astContentKey(body)}`
254
+ };
255
+ } else if (d.nudoMock) {
256
+ seedVars[d.name] = markMockConf(absFromMockHelper(d.nudoMock));
257
+ } else if (d.sinonExpr) {
258
+ seedVars[d.name] = markMockConf(absFromSinon(d.sinonExpr));
259
+ } else if (d.expression) {
260
+ try {
261
+ seedVars[d.name] = typeValueToAbs(parseTypeValueExpr(d.expression));
262
+ } catch {
263
+ seedVars[d.name] = absUnknown;
264
+ }
265
+ }
266
+ }
267
+ }
268
+ return { seedVars, seedFns };
269
+ }
270
+
271
+ // src/harvest-auto.ts
272
+ import { parse as parse2 } from "@nudojs/parser";
273
+
274
+ // src/static-imports.ts
275
+ import { parse } from "@nudojs/parser";
276
+ import { readFileSync, existsSync } from "fs";
277
+ import { resolve, dirname, join } from "path";
278
+ import { generalizeFromAst } from "@nudojs/core";
279
+ function resolveRelative(fromFile, spec) {
280
+ if (!spec.startsWith(".") && !spec.startsWith("/")) return void 0;
281
+ const base = resolve(dirname(fromFile), spec);
282
+ for (const cand of [
283
+ base,
284
+ base + ".js",
285
+ base + ".cjs",
286
+ base + ".mjs",
287
+ base + ".ts",
288
+ base + ".mts",
289
+ base + ".cts",
290
+ join(base, "index.js"),
291
+ join(base, "index.cjs"),
292
+ join(base, "index.mjs"),
293
+ join(base, "index.ts")
294
+ ]) {
295
+ if (existsSync(cand)) return cand;
296
+ }
297
+ return void 0;
298
+ }
299
+ function collectDependencySpecs(ast) {
300
+ const specs = [];
301
+ for (const stmt of ast.program.body) {
302
+ if (stmt.type === "ImportDeclaration") {
303
+ specs.push(stmt.source.value);
304
+ }
305
+ if (stmt.type === "ExportNamedDeclaration" && stmt.source) {
306
+ specs.push(stmt.source.value);
307
+ }
308
+ if (stmt.type === "ExportAllDeclaration" && stmt.source) {
309
+ specs.push(stmt.source.value);
310
+ }
311
+ collectRequires(stmt, specs);
312
+ }
313
+ return specs;
314
+ }
315
+ function collectRequires(node, out) {
316
+ const visit = (n) => {
317
+ if (!n || typeof n !== "object") return;
318
+ const obj = n;
319
+ if (obj.type === "CallExpression" && obj.callee?.type === "Identifier" && obj.callee.name === "require" && obj.arguments?.[0]?.type === "StringLiteral") {
320
+ const spec = obj.arguments[0].value;
321
+ if (typeof spec === "string") out.push(spec);
322
+ }
323
+ for (const key of Object.keys(obj)) {
324
+ if (key === "loc" || key === "start" || key === "end") continue;
325
+ const v = obj[key];
326
+ if (Array.isArray(v)) v.forEach(visit);
327
+ else if (v && typeof v === "object") visit(v);
328
+ }
329
+ };
330
+ visit(node);
331
+ }
332
+ function analyzeExportsFromSource(filePath, source) {
333
+ const file = parse(source);
334
+ const named = /* @__PURE__ */ new Map();
335
+ let defaultExport;
336
+ const poly = /* @__PURE__ */ new Map();
337
+ for (const stmt of file.program.body) {
338
+ let decl = stmt;
339
+ if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
340
+ decl = stmt.declaration;
341
+ }
342
+ if (decl.type === "FunctionDeclaration" && decl.id) {
343
+ named.set(decl.id.name, decl.id.name);
344
+ }
345
+ if (decl.type === "VariableDeclaration") {
346
+ for (const d of decl.declarations) {
347
+ if (d.id.type === "Identifier") named.set(d.id.name, d.id.name);
348
+ }
349
+ }
350
+ if (decl.type === "ClassDeclaration" && decl.id) {
351
+ named.set(decl.id.name, decl.id.name);
352
+ }
353
+ if (stmt.type === "ExportDefaultDeclaration") {
354
+ const d = stmt.declaration;
355
+ if (d.type === "FunctionDeclaration" && d.id) defaultExport = d.id.name;
356
+ if (d.type === "Identifier") defaultExport = d.name;
357
+ }
358
+ collectCjsExports(stmt, named, (n) => {
359
+ defaultExport = n;
360
+ });
361
+ }
362
+ for (const name of named.values()) {
363
+ const g = generalizeFromAst(name, source);
364
+ if (g) poly.set(name, g);
365
+ }
366
+ if (defaultExport) {
367
+ const g = generalizeFromAst(defaultExport, source);
368
+ if (g) poly.set("default", g);
369
+ }
370
+ return { path: filePath, named, defaultExport, source, poly };
371
+ }
372
+ function collectCjsExports(stmt, named, setDefault) {
373
+ const visit = (n) => {
374
+ if (!n || typeof n !== "object") return;
375
+ const obj = n;
376
+ if (obj.type !== "AssignmentExpression") {
377
+ for (const key of Object.keys(obj)) {
378
+ if (key === "loc" || key === "start" || key === "end") continue;
379
+ const v = obj[key];
380
+ if (Array.isArray(v)) v.forEach(visit);
381
+ else if (v && typeof v === "object") visit(v);
382
+ }
383
+ return;
384
+ }
385
+ const left = obj.left;
386
+ const right = obj.right;
387
+ if (left?.type === "MemberExpression" && left.object?.type === "Identifier" && left.object.name === "exports" && left.property?.type === "Identifier" && left.property.name) {
388
+ named.set(left.property.name, left.property.name);
389
+ }
390
+ if (left?.type === "MemberExpression" && left.object?.type === "Identifier" && left.object.name === "module" && left.property?.type === "Identifier" && left.property.name === "exports") {
391
+ if (right?.type === "Identifier" && typeof right.name === "string") {
392
+ named.set(right.name, right.name);
393
+ setDefault(right.name);
394
+ }
395
+ if (right?.type === "ObjectExpression") {
396
+ const props = right.properties;
397
+ for (const p of props) {
398
+ if (p.type !== "ObjectProperty") continue;
399
+ const key = p.key;
400
+ const val = p.value;
401
+ const exportName = key?.type === "Identifier" ? key.name : key?.type === "StringLiteral" ? key.value : void 0;
402
+ if (exportName) named.set(exportName, exportName);
403
+ if (val?.type === "Identifier" && val.name && exportName) {
404
+ }
405
+ }
406
+ }
407
+ }
408
+ };
409
+ visit(stmt);
410
+ }
411
+ function collectStaticImports(entryFile, maxDepth = 8) {
412
+ const graph = /* @__PURE__ */ new Map();
413
+ const queue = [
414
+ { file: resolve(entryFile), depth: 0 }
415
+ ];
416
+ const seen = /* @__PURE__ */ new Set();
417
+ while (queue.length > 0) {
418
+ const { file, depth } = queue.shift();
419
+ if (seen.has(file) || depth > maxDepth) continue;
420
+ seen.add(file);
421
+ if (!existsSync(file)) continue;
422
+ const source = readFileSync(file, "utf8");
423
+ const mod = analyzeExportsFromSource(file, source);
424
+ graph.set(file, mod);
425
+ const ast = parse(source);
426
+ for (const spec of collectDependencySpecs(ast)) {
427
+ const resolved = resolveRelative(file, spec);
428
+ if (resolved) queue.push({ file: resolved, depth: depth + 1 });
429
+ }
430
+ }
431
+ return graph;
432
+ }
433
+
434
+ // src/harvest-package.ts
435
+ import { existsSync as existsSync2, readdirSync, statSync, readFileSync as readFileSync2 } from "fs";
436
+ import { join as join2, resolve as resolve2, dirname as dirname2, basename } from "path";
437
+ import { harvestDts } from "@nudojs/harvester";
438
+ import { typeValueToString as typeValueToString2 } from "@nudojs/core";
439
+ function resolvePackageRoot(pkg, fromDir = process.cwd()) {
440
+ let dir = resolve2(fromDir);
441
+ const bare = pkg.replace(/^@/, "").replace(/\//g, "__");
442
+ for (let i = 0; i < 8; i++) {
443
+ const typesPath = join2(dir, "node_modules", "@types", bare);
444
+ if (existsSync2(typesPath)) return typesPath;
445
+ const pkgPath = join2(dir, "node_modules", pkg);
446
+ if (existsSync2(pkgPath)) return pkgPath;
447
+ const parent = resolve2(dir, "..");
448
+ if (parent === dir) break;
449
+ dir = parent;
450
+ }
451
+ return void 0;
452
+ }
453
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "test", "tests", "docs", "doc", "examples", "__tests__"]);
454
+ var MAX_DTS_BYTES = 15e5;
455
+ function dtsPriority(p, root) {
456
+ const base = basename(p);
457
+ const rel = p.slice(root.length);
458
+ if (base === "index.d.ts") return 0;
459
+ if (base === "index.d.mts") return 1;
460
+ if (rel.includes("/types/") && base === "index.d.ts") return 2;
461
+ if (base.endsWith(".d.ts") && !base.includes(".")) return 3;
462
+ return 10;
463
+ }
464
+ function collectDtsFiles(root, maxFiles = 8) {
465
+ const out = [];
466
+ const walk = (dir) => {
467
+ if (out.length >= maxFiles * 3) return;
468
+ let entries;
469
+ try {
470
+ entries = readdirSync(dir);
471
+ } catch {
472
+ return;
473
+ }
474
+ for (const name of entries) {
475
+ if (out.length >= maxFiles * 3) return;
476
+ if (SKIP_DIRS.has(name)) continue;
477
+ const p = join2(dir, name);
478
+ let st;
479
+ try {
480
+ st = statSync(p);
481
+ } catch {
482
+ continue;
483
+ }
484
+ if (st.isDirectory()) walk(p);
485
+ else if (name.endsWith(".d.ts") && !name.endsWith(".d.ts.map")) {
486
+ if (st.size > MAX_DTS_BYTES) continue;
487
+ out.push(p);
488
+ }
489
+ }
490
+ };
491
+ walk(root);
492
+ out.sort((a, b) => dtsPriority(a, root) - dtsPriority(b, root) || a.length - b.length);
493
+ return out.slice(0, maxFiles);
494
+ }
495
+ function entryDtsFromPackageJson(root) {
496
+ const pj = join2(root, "package.json");
497
+ if (!existsSync2(pj)) return void 0;
498
+ try {
499
+ const raw = JSON.parse(readFileSync2(pj, "utf8"));
500
+ const entry = raw.types ?? raw.typings;
501
+ if (typeof entry === "string" && entry.endsWith(".d.ts")) {
502
+ const p = resolve2(root, entry);
503
+ if (existsSync2(p)) {
504
+ const st = statSync(p);
505
+ if (st.size <= MAX_DTS_BYTES) return p;
506
+ }
507
+ }
508
+ } catch {
509
+ }
510
+ return void 0;
511
+ }
512
+ function collectDtsFromEntry(entry, maxFiles = 200) {
513
+ const files = [];
514
+ const seen = /* @__PURE__ */ new Set();
515
+ const queue = [entry];
516
+ const REFERENCE_PATH_REGEX = /<reference\s+path=["']([^"']+)["']\s*\/>/g;
517
+ const RELATIVE_FROM_REGEX = /\bfrom\s+["'](\.[^"']+)["']/g;
518
+ while (queue.length > 0 && files.length < maxFiles) {
519
+ const current = queue.shift();
520
+ if (seen.has(current) || !existsSync2(current)) continue;
521
+ seen.add(current);
522
+ if (!current.endsWith(".d.ts")) continue;
523
+ files.push(current);
524
+ let text;
525
+ try {
526
+ text = readFileSync2(current, "utf-8");
527
+ } catch {
528
+ continue;
529
+ }
530
+ const dir = dirname2(current);
531
+ for (const match of text.matchAll(REFERENCE_PATH_REGEX)) {
532
+ queue.push(resolve2(dir, match[1]));
533
+ }
534
+ for (const match of text.matchAll(RELATIVE_FROM_REGEX)) {
535
+ const base = resolve2(dir, match[1]);
536
+ for (const candidate of [`${base}.d.ts`, join2(base, "index.d.ts")]) {
537
+ if (existsSync2(candidate)) {
538
+ queue.push(candidate);
539
+ break;
540
+ }
541
+ }
542
+ }
543
+ }
544
+ return files;
545
+ }
546
+ function harvestPackage(pkg, fromDir, maxFiles = 8) {
547
+ const root = resolvePackageRoot(pkg, fromDir);
548
+ if (!root) return { error: `package not found: ${pkg}` };
549
+ const entry = entryDtsFromPackageJson(root) ?? join2(root, "index.d.ts");
550
+ let dtsFiles = existsSync2(entry) ? collectDtsFromEntry(entry, maxFiles) : [];
551
+ if (dtsFiles.length === 0) {
552
+ const collected = collectDtsFiles(root, maxFiles);
553
+ dtsFiles = entry && existsSync2(entry) && !collected.includes(entry) ? [entry, ...collected.filter((f) => f !== entry)].slice(0, maxFiles) : collected;
554
+ }
555
+ if (dtsFiles.length === 0) {
556
+ return { error: `no .d.ts under ${root}` };
557
+ }
558
+ const env = harvestDts(dtsFiles);
559
+ return { pkg, root, dtsFiles, env };
560
+ }
561
+ function formatHarvestSummary(h) {
562
+ const lines = [];
563
+ lines.push(`${h.pkg}: ${h.dtsFiles.length} d.ts, ${h.env.stats.symbols} symbols`);
564
+ const mods = Object.keys(h.env.modules);
565
+ for (const m of mods.slice(0, 5)) {
566
+ const exports = h.env.modules[m];
567
+ const names = Object.keys(exports).slice(0, 12);
568
+ lines.push(` module "${m}": ${names.join(", ")}${Object.keys(exports).length > 12 ? "\u2026" : ""}`);
569
+ }
570
+ if (mods.length > 5) lines.push(` \u2026 ${mods.length - 5} more modules`);
571
+ return lines.join("\n");
572
+ }
573
+ function lookupHarvested(h, moduleName, exportName) {
574
+ const mod = h.env.modules[moduleName];
575
+ const tv = mod?.[exportName] ?? h.env.globals[exportName];
576
+ if (!tv) return void 0;
577
+ return typeValueToString2(tv);
578
+ }
579
+
580
+ // src/harvest-auto.ts
581
+ function barePackageName(spec) {
582
+ if (!spec) return void 0;
583
+ if (spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("node:")) return void 0;
584
+ const parts = spec.split("/");
585
+ if (spec.startsWith("@")) {
586
+ return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : void 0;
587
+ }
588
+ return parts[0];
589
+ }
590
+ function collectBarePackages(source) {
591
+ try {
592
+ const ast = parse2(source);
593
+ const names = /* @__PURE__ */ new Set();
594
+ for (const spec of collectDependencySpecs(ast)) {
595
+ const pkg = barePackageName(spec);
596
+ if (pkg) names.add(pkg);
597
+ }
598
+ return [...names];
599
+ } catch {
600
+ return [];
601
+ }
602
+ }
603
+ var harvestCache = /* @__PURE__ */ new Map();
604
+ function harvestPackageCached(pkg, fromDir) {
605
+ const key = `${fromDir}::${pkg}`;
606
+ if (harvestCache.has(key)) return harvestCache.get(key);
607
+ let result = null;
608
+ try {
609
+ const h = harvestPackage(pkg, fromDir);
610
+ if (!("error" in h)) result = h;
611
+ } catch {
612
+ result = null;
613
+ }
614
+ harvestCache.set(key, result);
615
+ return result;
616
+ }
617
+ function clearHarvestCache() {
618
+ harvestCache.clear();
619
+ }
620
+ function autoHarvestModules(source, fromDir) {
621
+ const packages = collectBarePackages(source);
622
+ if (packages.length === 0) return {};
623
+ const modules = {};
624
+ for (const pkg of packages) {
625
+ const h = harvestPackageCached(pkg, fromDir);
626
+ if (!h) continue;
627
+ for (const [mod, exports] of Object.entries(h.env.modules)) {
628
+ modules[mod] = exports;
629
+ if (!modules[pkg]) modules[pkg] = exports;
630
+ const base = mod.replace(/^@types\//, "").replace(/\\/g, "/");
631
+ if (base && !modules[base]) modules[base] = exports;
632
+ }
633
+ for (const [k, v] of Object.entries(h.env.globals)) {
634
+ if (!modules[pkg]) modules[pkg] = {};
635
+ if (modules[pkg][k] === void 0) modules[pkg][k] = v;
636
+ }
637
+ }
638
+ return modules;
639
+ }
640
+
641
+ // src/abs-modules-graph.ts
642
+ import { readFileSync as readFileSync3, statSync as statSync2 } from "fs";
643
+ import { dirname as dirname4, resolve as resolve3 } from "path";
644
+ import { parse as parse3 } from "@nudojs/parser";
645
+ import {
646
+ evalProgramAbs,
647
+ collectAbsExports,
648
+ absFunction as absFunction3
649
+ } from "@nudojs/core";
650
+
651
+ // src/harvest-to-abs.ts
652
+ import {
653
+ T,
654
+ typeValueToAbs as typeValueToAbs2,
655
+ confJoin as confJoin2,
656
+ getFnSig,
657
+ absFunction as absFunction2
658
+ } from "@nudojs/core";
659
+ import { dirname as dirname3 } from "path";
660
+ function markMockConf2(a) {
661
+ a.conf = confJoin2(a.conf, "mock");
662
+ return a;
663
+ }
664
+ function harvestedValueToAbs(tv) {
665
+ if (!tv) return markMockConf2(typeValueToAbs2(T.unknown));
666
+ const sig = getFnSig(tv);
667
+ if (sig) {
668
+ const ret = markMockConf2(typeValueToAbs2(sig.returnType));
669
+ const params = tv.kind === "function" && tv.params?.length ? tv.params : sig.paramTypes.map((_, i) => `_arg${i}`);
670
+ const dummyBody = {
671
+ type: "BlockStatement",
672
+ body: [],
673
+ directives: []
674
+ };
675
+ return markMockConf2(
676
+ absFunction2(params, {
677
+ body: dummyBody,
678
+ apply: () => ret
679
+ })
680
+ );
681
+ }
682
+ return markMockConf2(typeValueToAbs2(tv));
683
+ }
684
+ function harvestToAbsModules(pkg, fromDir) {
685
+ const h = harvestPackageCached(pkg, fromDir);
686
+ if (!h) return {};
687
+ return packageHarvestToAbsModules(pkg, h);
688
+ }
689
+ function packageHarvestToAbsModules(pkg, h) {
690
+ const out = {};
691
+ const convertRecord = (rec) => {
692
+ const named = {};
693
+ for (const [k, v] of Object.entries(rec)) {
694
+ named[k] = harvestedValueToAbs(v);
695
+ }
696
+ return named;
697
+ };
698
+ const register = (key, exports) => {
699
+ if (!out[key]) out[key] = exports;
700
+ };
701
+ for (const [mod, rec] of Object.entries(h.env.modules)) {
702
+ const named = convertRecord(rec);
703
+ const exports = { named };
704
+ const def = rec["default"];
705
+ if (def) exports.default = harvestedValueToAbs(def);
706
+ register(mod, exports);
707
+ register(pkg, exports);
708
+ const stripped = mod.replace(/^@types\//, "").replace(/\\/g, "/");
709
+ if (stripped) register(stripped, exports);
710
+ const base = stripped.split("/").filter(Boolean).pop();
711
+ if (base && base !== pkg) register(base, exports);
712
+ }
713
+ const globalNamed = convertRecord(h.env.globals);
714
+ if (Object.keys(globalNamed).length > 0) {
715
+ const existing = out[pkg];
716
+ if (existing) {
717
+ out[pkg] = { named: { ...globalNamed, ...existing.named }, default: existing.default };
718
+ } else {
719
+ out[pkg] = { named: globalNamed };
720
+ }
721
+ }
722
+ return out;
723
+ }
724
+ function bareSpecToAbsModules(spec, fromFile) {
725
+ if (!spec || spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("node:")) {
726
+ return void 0;
727
+ }
728
+ const parts = spec.split("/");
729
+ const pkg = spec.startsWith("@") ? parts.length >= 2 ? `${parts[0]}/${parts[1]}` : void 0 : parts[0];
730
+ if (!pkg) return void 0;
731
+ const table = harvestToAbsModules(pkg, dirname3(fromFile));
732
+ return table[spec] ?? table[pkg];
733
+ }
734
+
735
+ // src/abs-modules-graph.ts
736
+ function defaultAbsLoadModule(spec, fromFile) {
737
+ if (!spec.startsWith(".") && !spec.startsWith("/")) return void 0;
738
+ try {
739
+ const p = resolve3(dirname4(resolve3(fromFile)), spec);
740
+ for (const cand of [p, `${p}.js`, `${p}.mjs`, `${p}.ts`, resolve3(p, "index.js")]) {
741
+ try {
742
+ return readFileSync3(cand, "utf-8");
743
+ } catch {
744
+ }
745
+ }
746
+ return void 0;
747
+ } catch {
748
+ return void 0;
749
+ }
750
+ }
751
+ function relCandidates(spec, fromFile) {
752
+ const p = resolve3(dirname4(resolve3(fromFile)), spec);
753
+ return [p, `${p}.js`, `${p}.mjs`, `${p}.ts`, resolve3(p, "index.js")];
754
+ }
755
+ function resolveRel(spec, fromFile) {
756
+ if (!spec.startsWith(".") && !spec.startsWith("/")) return null;
757
+ for (const cand of relCandidates(spec, fromFile)) {
758
+ try {
759
+ readFileSync3(cand, "utf-8");
760
+ return cand;
761
+ } catch {
762
+ }
763
+ }
764
+ return null;
765
+ }
766
+ function importSpecs(source) {
767
+ try {
768
+ const file = parse3(source);
769
+ const out = [];
770
+ for (const stmt of file.program.body) {
771
+ if (stmt.type === "ImportDeclaration" || stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportAllDeclaration") {
772
+ const src = stmt.source;
773
+ if (src && typeof src.value === "string") {
774
+ out.push(src.value);
775
+ }
776
+ }
777
+ collectRequireSpecs(stmt, out);
778
+ }
779
+ return out;
780
+ } catch {
781
+ return [];
782
+ }
783
+ }
784
+ function collectRequireSpecs(node, out) {
785
+ const visit = (n) => {
786
+ if (!n || typeof n !== "object") return;
787
+ const o = n;
788
+ if (o.type === "CallExpression" && o.callee?.type === "Identifier" && o.callee.name === "require" && o.arguments?.[0]?.type === "StringLiteral") {
789
+ const spec = o.arguments[0].value;
790
+ if (typeof spec === "string") out.push(spec);
791
+ }
792
+ for (const key of Object.keys(o)) {
793
+ if (key === "loc" || key === "start" || key === "end") continue;
794
+ const v = o[key];
795
+ if (Array.isArray(v)) v.forEach(visit);
796
+ else if (v && typeof v === "object") visit(v);
797
+ }
798
+ };
799
+ visit(node);
800
+ }
801
+ function buildModulesForFile(source, fromFile, load, evalDep, depth, maxDepth, onMissing) {
802
+ const modules = {};
803
+ for (const spec of importSpecs(source)) {
804
+ if (spec.startsWith(".") || spec.startsWith("/")) {
805
+ const childPath = resolveRel(spec, fromFile);
806
+ if (!childPath) {
807
+ onMissing(spec, fromFile, relCandidates(spec, fromFile));
808
+ continue;
809
+ }
810
+ modules[spec] = evalDep(childPath, spec, fromFile, depth + 1);
811
+ } else if (!spec.startsWith("node:")) {
812
+ const bare = bareSpecToAbsModules(spec, fromFile);
813
+ if (bare) modules[spec] = bare;
814
+ }
815
+ }
816
+ void maxDepth;
817
+ return modules;
818
+ }
819
+ var absModuleCache = /* @__PURE__ */ new Map();
820
+ function clearAbsModuleCache() {
821
+ absModuleCache.clear();
822
+ }
823
+ function evictAbsModuleCacheFiles(paths) {
824
+ for (const p of paths) absModuleCache.delete(p);
825
+ }
826
+ function moduleLabel(p) {
827
+ const parts = p.split(/[/\\]/);
828
+ return parts[parts.length - 1] || p;
829
+ }
830
+ function evalAbsModuleGraph(entrySource, entryFile, opts = {}) {
831
+ const load = opts.loadModule ?? defaultAbsLoadModule;
832
+ const maxDepth = opts.maxDepth ?? 16;
833
+ const cache = /* @__PURE__ */ new Map();
834
+ const loading = [];
835
+ const issues = [];
836
+ const seenIssue = /* @__PURE__ */ new Set();
837
+ const pushIssue = (kind, label, reason) => {
838
+ const key = `${kind}:${label}`;
839
+ if (seenIssue.has(key)) return;
840
+ seenIssue.add(key);
841
+ issues.push({ kind, label, reason });
842
+ };
843
+ function evalDep(absPath, spec, fromFile, depth) {
844
+ const cycleIndex = loading.indexOf(absPath);
845
+ if (cycleIndex !== -1) {
846
+ const chain = [...loading.slice(cycleIndex), absPath];
847
+ pushIssue(
848
+ "cycle",
849
+ moduleLabel(absPath),
850
+ `Circular module load: ${chain.join(" -> ")} (bindings inside the cycle resolve to their partially evaluated types)`
851
+ );
852
+ return cache.get(absPath) ?? { named: {} };
853
+ }
854
+ const shared = absModuleCache.get(absPath);
855
+ if (shared) {
856
+ try {
857
+ const st = statSync2(absPath);
858
+ if (st.mtimeMs === shared.mtimeMs && st.size === shared.size) {
859
+ for (const iss of shared.issues) pushIssue(iss.kind, iss.label, iss.reason);
860
+ cache.set(absPath, shared.exports);
861
+ return shared.exports;
862
+ }
863
+ } catch {
864
+ }
865
+ absModuleCache.delete(absPath);
866
+ }
867
+ if (cache.has(absPath)) return cache.get(absPath);
868
+ if (depth > maxDepth) {
869
+ const chain = [...loading, absPath];
870
+ pushIssue(
871
+ "depth",
872
+ moduleLabel(absPath),
873
+ `Module load chain too deep (depth ${chain.length} > ${maxDepth} max): ${chain.join(" -> ")} (loading truncated, deeper modules typed as unknown)`
874
+ );
875
+ const empty = { named: {} };
876
+ cache.set(absPath, empty);
877
+ return empty;
878
+ }
879
+ cache.set(absPath, { named: {} });
880
+ loading.push(absPath);
881
+ const source = load(spec, fromFile);
882
+ if (source === void 0) {
883
+ pushIssue(
884
+ "missing",
885
+ spec,
886
+ `Module file not found for '${spec}' (from ${moduleLabel(fromFile)}); tried: ${absPath}`
887
+ );
888
+ const empty = { named: {} };
889
+ cache.set(absPath, empty);
890
+ loading.pop();
891
+ return empty;
892
+ }
893
+ const issueStart = issues.length;
894
+ const modules2 = buildModulesForFile(
895
+ source,
896
+ absPath,
897
+ load,
898
+ evalDep,
899
+ depth,
900
+ maxDepth,
901
+ (spec2, fromFile2, tried) => {
902
+ pushIssue(
903
+ "missing",
904
+ spec2,
905
+ `Module file not found for '${spec2}' (from ${moduleLabel(fromFile2)}); tried: ${tried.join(", ")}`
906
+ );
907
+ }
908
+ );
909
+ let exports;
910
+ try {
911
+ const file = parse3(source);
912
+ const { env } = evalProgramAbs(source, { file, modules: modules2 });
913
+ exports = collectAbsExports(file, env, modules2);
914
+ } catch {
915
+ exports = { named: {} };
916
+ } finally {
917
+ loading.pop();
918
+ }
919
+ cache.set(absPath, exports);
920
+ let fingerprint;
921
+ try {
922
+ const st = statSync2(absPath);
923
+ fingerprint = { mtimeMs: st.mtimeMs, size: st.size };
924
+ } catch {
925
+ }
926
+ if (fingerprint) {
927
+ absModuleCache.set(absPath, {
928
+ ...fingerprint,
929
+ exports,
930
+ issues: issues.slice(issueStart)
931
+ });
932
+ }
933
+ return exports;
934
+ }
935
+ const modules = buildModulesForFile(
936
+ entrySource,
937
+ entryFile,
938
+ load,
939
+ evalDep,
940
+ 0,
941
+ maxDepth,
942
+ (spec, fromFile, tried) => {
943
+ pushIssue(
944
+ "missing",
945
+ spec,
946
+ `Module file not found for '${spec}' (from ${moduleLabel(fromFile)}); tried: ${tried.join(", ")}`
947
+ );
948
+ }
949
+ );
950
+ return { modules, byPath: cache, issues };
951
+ }
952
+ function evalProgramAbsWithModules(source, entryFile, opts = {}) {
953
+ const { modules } = evalAbsModuleGraph(source, entryFile, opts);
954
+ return evalProgramAbs(source, {
955
+ file: opts.file,
956
+ modules,
957
+ seedVars: opts.seedVars,
958
+ seedFns: opts.seedFns
959
+ });
960
+ }
961
+ function collectAbsBindingsFromGraph(source, filePath, opts = {}) {
962
+ const out = /* @__PURE__ */ new Map();
963
+ try {
964
+ const { env } = evalProgramAbsWithModules(source, filePath, opts);
965
+ for (const [k, v] of env.vars) {
966
+ out.set(k, v);
967
+ }
968
+ for (const [name, impl] of env.fns) {
969
+ if (!out.has(name)) {
970
+ out.set(
971
+ name,
972
+ absFunction3(impl.params, { body: impl.body, async: impl.async, env })
973
+ );
974
+ }
975
+ }
976
+ } catch {
977
+ }
978
+ return out;
979
+ }
980
+
981
+ // src/bpath-run.ts
982
+ import {
983
+ runTranspiled,
984
+ callTranspiledExport,
985
+ callTranspiledExportFull,
986
+ setBCallCollector,
987
+ setMemberDiagCollector,
988
+ setAbsTruncationCollector,
989
+ typeValueToAbs as typeValueToAbs4,
990
+ createEnvironment,
991
+ stableAnalyzeKeySource,
992
+ formatAbs as formatAbs2,
993
+ hashSource,
994
+ getFnImpl as getFnImpl2
995
+ } from "@nudojs/core";
996
+ import { parse as parse4, extractInlineDirectives } from "@nudojs/parser";
997
+ import { loadEnvs } from "@nudojs/cli/evaluator";
998
+
999
+ // src/env-to-abs.ts
1000
+ import {
1001
+ typeValueToAbs as typeValueToAbs3,
1002
+ absToTypeValue,
1003
+ getFnSig as getFnSig2,
1004
+ absFunction as absFunction4,
1005
+ relationFn,
1006
+ objOf
1007
+ } from "@nudojs/core";
1008
+ function envValueToAbs(tv) {
1009
+ if (!tv) return typeValueToAbs3({ kind: "unknown" });
1010
+ if (tv.kind === "function") {
1011
+ const sig = getFnSig2(tv);
1012
+ if (sig) {
1013
+ const paramTypes = sig.paramTypes.map(typeValueToAbs3);
1014
+ const returnType = typeValueToAbs3(sig.returnType);
1015
+ if (sig.impl) {
1016
+ const params = tv.params?.length ? tv.params : sig.paramTypes.map((_, i) => `_arg${i}`);
1017
+ const dummyBody = {
1018
+ type: "BlockStatement",
1019
+ body: [],
1020
+ directives: []
1021
+ };
1022
+ const impl = sig.impl;
1023
+ return absFunction4(params, {
1024
+ body: dummyBody,
1025
+ apply: (args) => {
1026
+ const tvArgs = args.map((a) => absToTypeValue(a));
1027
+ try {
1028
+ const r = impl(tvArgs);
1029
+ if (!r) return returnType;
1030
+ return envValueToAbs(r);
1031
+ } catch {
1032
+ return returnType;
1033
+ }
1034
+ }
1035
+ });
1036
+ }
1037
+ return relationFn(paramTypes, returnType, {
1038
+ params: tv.params?.length ? tv.params : void 0
1039
+ });
1040
+ }
1041
+ }
1042
+ if (tv.kind === "object") {
1043
+ const slots = {};
1044
+ for (const [k, v] of Object.entries(tv.properties)) {
1045
+ slots[k] = { value: envValueToAbs(v) };
1046
+ }
1047
+ return objOf(slots);
1048
+ }
1049
+ return typeValueToAbs3(tv);
1050
+ }
1051
+
1052
+ // src/analysis-file-cache.ts
1053
+ var analysisByFile = /* @__PURE__ */ new Map();
1054
+ var MAX_ANALYSIS_FILE_CACHE = 64;
1055
+ function clearAnalysisFileCache() {
1056
+ analysisByFile.clear();
1057
+ }
1058
+ function getAnalysisFileCacheSize() {
1059
+ return analysisByFile.size;
1060
+ }
1061
+ function analysisCacheGet(filePath, source, auxKey) {
1062
+ const e = analysisByFile.get(filePath);
1063
+ if (!e) return void 0;
1064
+ if (e.source !== source || e.auxKey !== auxKey) return void 0;
1065
+ analysisByFile.delete(filePath);
1066
+ analysisByFile.set(filePath, e);
1067
+ return e.value;
1068
+ }
1069
+ function analysisCacheSet(filePath, source, auxKey, value) {
1070
+ if (analysisByFile.size >= MAX_ANALYSIS_FILE_CACHE && !analysisByFile.has(filePath)) {
1071
+ const oldest = analysisByFile.keys().next().value;
1072
+ if (oldest !== void 0) analysisByFile.delete(oldest);
1073
+ }
1074
+ analysisByFile.set(filePath, { source, auxKey, value });
1075
+ }
1076
+ function evictAnalysisFileCacheForFiles(files) {
1077
+ let n = 0;
1078
+ for (const f of files) {
1079
+ if (analysisByFile.delete(f)) n++;
1080
+ }
1081
+ return n;
1082
+ }
1083
+
1084
+ // src/fn-analysis-cache.ts
1085
+ var fnAnalysisCache = /* @__PURE__ */ new Map();
1086
+ var MAX_FN_ANALYSIS_CACHE = 1024;
1087
+ function clearFnAnalysisCache() {
1088
+ fnAnalysisCache.clear();
1089
+ }
1090
+ function fnAnalysisCacheGet(key) {
1091
+ const hit = fnAnalysisCache.get(key);
1092
+ if (hit !== void 0) {
1093
+ fnAnalysisCache.delete(key);
1094
+ fnAnalysisCache.set(key, hit);
1095
+ }
1096
+ return hit;
1097
+ }
1098
+ function fnAnalysisCacheSet(key, value) {
1099
+ if (fnAnalysisCache.size >= MAX_FN_ANALYSIS_CACHE && !fnAnalysisCache.has(key)) {
1100
+ const oldest = fnAnalysisCache.keys().next().value;
1101
+ if (oldest !== void 0) fnAnalysisCache.delete(oldest);
1102
+ }
1103
+ fnAnalysisCache.set(key, value);
1104
+ }
1105
+ function evictFnAnalysisCacheForFiles(files) {
1106
+ if (files.length === 0 || fnAnalysisCache.size === 0) return 0;
1107
+ const prefixes = files.map((f) => `${f}\0`);
1108
+ let n = 0;
1109
+ for (const key of [...fnAnalysisCache.keys()]) {
1110
+ if (prefixes.some((p) => key.startsWith(p))) {
1111
+ fnAnalysisCache.delete(key);
1112
+ n++;
1113
+ }
1114
+ }
1115
+ return n;
1116
+ }
1117
+ function caseDirectiveKey(directives, typeValueToString5) {
1118
+ return directives.map((d) => {
1119
+ if (d.kind === "case") {
1120
+ const args = (d.args ?? []).map((a) => typeValueToString5(a)).join(",");
1121
+ return `c:${d.name ?? ""}:${args}`;
1122
+ }
1123
+ return d.kind;
1124
+ }).join("|");
1125
+ }
1126
+
1127
+ // src/bpath-run.ts
1128
+ function absSeedPart(a) {
1129
+ const impl = getFnImpl2(a);
1130
+ if (impl?.fingerprint) return impl.fingerprint;
1131
+ if (a.shape.k === "fn") {
1132
+ const ret = impl?.env?.vars?.get("__nudo_mock_ret");
1133
+ if (ret) {
1134
+ try {
1135
+ return `ret=${formatAbs2(ret)}`;
1136
+ } catch {
1137
+ return "ret=?";
1138
+ }
1139
+ }
1140
+ if (impl?.apply) return "apply";
1141
+ }
1142
+ try {
1143
+ return formatAbs2(a);
1144
+ } catch {
1145
+ return "?";
1146
+ }
1147
+ }
1148
+ function seedFnPart(name, fn) {
1149
+ if (fn.fingerprint) return `${name}:${fn.fingerprint}`;
1150
+ const body = fn.body;
1151
+ const bodyKey = body && typeof body.start === "number" && typeof body.end === "number" ? `${body.start}:${body.end}:${body.type ?? ""}` : String(body?.type ?? "?");
1152
+ return `${name}(${fn.params.join(",")})@${bodyKey}`;
1153
+ }
1154
+ function mockSeedFingerprint(mocks, seedFns) {
1155
+ const parts = [];
1156
+ if (mocks) {
1157
+ const names = Object.keys(mocks).sort();
1158
+ for (const n of names) parts.push(`${n}=${absSeedPart(mocks[n])}`);
1159
+ }
1160
+ if (seedFns) {
1161
+ const names = Object.keys(seedFns).sort();
1162
+ for (const n of names) parts.push(`fn:${seedFnPart(n, seedFns[n])}`);
1163
+ }
1164
+ if (parts.length === 0) return "-";
1165
+ return hashSource(parts.join(";"));
1166
+ }
1167
+ function collectEnvGlobals(envNames) {
1168
+ if (envNames.length === 0) return {};
1169
+ const env = createEnvironment();
1170
+ try {
1171
+ loadEnvs(envNames, env);
1172
+ } catch {
1173
+ return {};
1174
+ }
1175
+ const out = {};
1176
+ for (const [k, v] of Object.entries(env.getOwnBindings())) {
1177
+ out[k] = envValueToAbs(v);
1178
+ }
1179
+ return out;
1180
+ }
1181
+ function collectEnvModules(envNames) {
1182
+ if (envNames.length === 0) return {};
1183
+ const env = createEnvironment();
1184
+ let mods = {};
1185
+ try {
1186
+ mods = loadEnvs(envNames, env).modules;
1187
+ } catch {
1188
+ return {};
1189
+ }
1190
+ const out = {};
1191
+ for (const [spec, exports] of Object.entries(mods)) {
1192
+ const named = {};
1193
+ for (const [k, v] of Object.entries(exports)) {
1194
+ named[k] = envValueToAbs(v);
1195
+ }
1196
+ out[spec] = { named };
1197
+ }
1198
+ return out;
1199
+ }
1200
+ function collectBPathReplacements(source) {
1201
+ const targets = [];
1202
+ const values = {};
1203
+ const asTargets = [];
1204
+ const asValues = {};
1205
+ let i = 0;
1206
+ try {
1207
+ const file = parse4(source);
1208
+ const visitStmts = (stmts) => {
1209
+ for (const stmt of stmts) {
1210
+ if (!stmt || typeof stmt !== "object") continue;
1211
+ const loc = stmt.loc;
1212
+ const dirs = extractInlineDirectives(stmt);
1213
+ for (const d of dirs) {
1214
+ if (d.kind === "replace") {
1215
+ const varName = `__rep${i++}`;
1216
+ targets.push({
1217
+ target: d.targetSource,
1218
+ varName,
1219
+ stmtStart: loc?.start.line,
1220
+ stmtEnd: loc?.end.line
1221
+ });
1222
+ values[varName] = typeValueToAbs4(d.typeExpr);
1223
+ } else if (d.kind === "as" && loc) {
1224
+ const varName = `__as${i++}`;
1225
+ asTargets.push({
1226
+ varName,
1227
+ stmtStart: loc.start.line,
1228
+ stmtEnd: loc.end.line
1229
+ });
1230
+ asValues[varName] = typeValueToAbs4(d.typeExpr);
1231
+ }
1232
+ }
1233
+ const s = stmt;
1234
+ if (s.type === "BlockStatement" && Array.isArray(s.body)) visitStmts(s.body);
1235
+ if (s.type === "FunctionDeclaration" || s.type === "FunctionExpression") {
1236
+ if (s.body) visitStmts([s.body]);
1237
+ }
1238
+ if (s.type === "ExportNamedDeclaration" && s.declaration) {
1239
+ visitStmts([s.declaration]);
1240
+ }
1241
+ if (s.type === "IfStatement") {
1242
+ if (s.consequent) visitStmts([s.consequent]);
1243
+ if (s.alternate) visitStmts([s.alternate]);
1244
+ }
1245
+ }
1246
+ };
1247
+ visitStmts(file.program.body);
1248
+ } catch {
1249
+ }
1250
+ return { targets, values, asTargets, asValues };
1251
+ }
1252
+ function isBPathCapable(source, envNames = []) {
1253
+ void envNames;
1254
+ if (/(^|[^.\w$])this\s*\./.test(source) && !/\bclass\s+/.test(source)) return false;
1255
+ return true;
1256
+ }
1257
+ var bRunByFile = /* @__PURE__ */ new Map();
1258
+ var MAX_B_RUN_CACHE = 32;
1259
+ function clearBPathCache() {
1260
+ bRunByFile.clear();
1261
+ clearAnalysisFileCache();
1262
+ clearFnAnalysisCache();
1263
+ }
1264
+ function evictBPathCacheForFiles(files) {
1265
+ let n = 0;
1266
+ for (const f of files) {
1267
+ if (bRunByFile.delete(f)) n++;
1268
+ }
1269
+ return n;
1270
+ }
1271
+ function bPathCacheSet(filePath, stableSource, mode, envKey, mockKey, value) {
1272
+ if (bRunByFile.size >= MAX_B_RUN_CACHE && !bRunByFile.has(filePath)) {
1273
+ const oldest = bRunByFile.keys().next().value;
1274
+ if (oldest !== void 0) bRunByFile.delete(oldest);
1275
+ }
1276
+ bRunByFile.set(filePath, { stableSource, mode, envKey, mockKey, value });
1277
+ }
1278
+ function tryRunBPath(source, filePath, opts = {}) {
1279
+ if (!isBPathCapable(source, opts.envNames ?? [])) return void 0;
1280
+ const mode = opts.mode ?? "analyze";
1281
+ const envKey = (opts.envNames ?? []).join(",");
1282
+ const mockKey = mockSeedFingerprint(opts.mocks);
1283
+ const stable = stableAnalyzeKeySource(source);
1284
+ const cached = bRunByFile.get(filePath);
1285
+ if (cached && cached.stableSource === stable && cached.mode === mode && cached.envKey === envKey && cached.mockKey === mockKey) {
1286
+ bRunByFile.delete(filePath);
1287
+ bRunByFile.set(filePath, cached);
1288
+ return cached.value ?? void 0;
1289
+ }
1290
+ let out = null;
1291
+ try {
1292
+ const memberDiags = [];
1293
+ const truncated = /* @__PURE__ */ new Set();
1294
+ const topCalls = [];
1295
+ setMemberDiagCollector((d) => memberDiags.push(d));
1296
+ setAbsTruncationCollector((label) => truncated.add(label));
1297
+ setBCallCollector((r) => topCalls.push(r));
1298
+ try {
1299
+ const { modules: graphMods, issues } = evalAbsModuleGraph(source, filePath);
1300
+ const envMods = collectEnvModules(opts.envNames ?? []);
1301
+ const modules = { ...envMods, ...graphMods };
1302
+ const { targets, values, asTargets, asValues } = collectBPathReplacements(source);
1303
+ const envGlobals = {
1304
+ ...collectEnvGlobals(opts.envNames ?? []),
1305
+ ...opts.mocks ?? {}
1306
+ };
1307
+ const exports = runTranspiled(source, {
1308
+ modules,
1309
+ maxLoopIters: opts.maxLoopIters,
1310
+ mode: opts.mode ?? "analyze",
1311
+ replacementTargets: targets.length ? targets : void 0,
1312
+ replacements: targets.length ? values : void 0,
1313
+ asOverrideTargets: asTargets.length ? asTargets : void 0,
1314
+ asOverrides: asTargets.length ? asValues : void 0,
1315
+ envGlobals: Object.keys(envGlobals).length ? envGlobals : void 0
1316
+ });
1317
+ out = {
1318
+ exports,
1319
+ modules,
1320
+ memberDiags: memberDiags.length ? memberDiags : void 0,
1321
+ moduleIssues: issues.length ? issues : void 0,
1322
+ truncatedFns: truncated.size ? [...truncated] : void 0,
1323
+ calls: topCalls.length ? topCalls : void 0
1324
+ };
1325
+ } finally {
1326
+ setMemberDiagCollector(null);
1327
+ setAbsTruncationCollector(null);
1328
+ setBCallCollector(null);
1329
+ }
1330
+ } catch {
1331
+ out = null;
1332
+ }
1333
+ bPathCacheSet(filePath, stable, mode, envKey, mockKey, out);
1334
+ return out ?? void 0;
1335
+ }
1336
+ function tryBPathCallFull(source, filePath, fnName, args, opts = {}) {
1337
+ const run = tryRunBPath(source, filePath, { envNames: opts.envNames, mocks: opts.mocks });
1338
+ if (!run) return void 0;
1339
+ if (!(fnName in run.exports)) return void 0;
1340
+ const collected = [];
1341
+ const memberDiags = [];
1342
+ if (opts.collectCalls) {
1343
+ setBCallCollector((r) => collected.push(r));
1344
+ }
1345
+ if (opts.collectMemberDiags ?? true) {
1346
+ setMemberDiagCollector((d) => memberDiags.push(d));
1347
+ }
1348
+ try {
1349
+ const full = callTranspiledExportFull(run.exports, fnName, args);
1350
+ const all = [...run.memberDiags ?? [], ...memberDiags];
1351
+ return {
1352
+ ...full,
1353
+ calls: opts.collectCalls ? collected : void 0,
1354
+ memberDiags: all.length ? all : void 0,
1355
+ moduleIssues: run.moduleIssues,
1356
+ truncatedFns: run.truncatedFns
1357
+ };
1358
+ } finally {
1359
+ if (opts.collectCalls) setBCallCollector(null);
1360
+ setMemberDiagCollector(null);
1361
+ }
1362
+ }
1363
+ function tryBPathCall(source, filePath, fnName, args, opts = {}) {
1364
+ const full = tryBPathCallFull(source, filePath, fnName, args, opts);
1365
+ if (!full) return void 0;
1366
+ const r = full.result;
1367
+ if (!r) return void 0;
1368
+ if (r.shape.k === "never" && full.throws.shape.k !== "never") {
1369
+ return void 0;
1370
+ }
1371
+ if (r.shape.k === "unknown" && !r.term) return void 0;
1372
+ return r;
1373
+ }
1374
+
1375
+ // src/bpath-diagnostics.ts
1376
+ import { parseSource } from "@nudojs/core";
1377
+ function locOf(node) {
1378
+ if (!node.loc) return null;
1379
+ return {
1380
+ start: { line: node.loc.start.line, column: node.loc.start.column },
1381
+ end: { line: node.loc.end.line, column: node.loc.end.column }
1382
+ };
1383
+ }
1384
+ function isExit(stmt) {
1385
+ return stmt.type === "ReturnStatement" || stmt.type === "ThrowStatement";
1386
+ }
1387
+ function walkUnreachable(node, out) {
1388
+ const n = node;
1389
+ const checkBlock = (block) => {
1390
+ if (!block) return;
1391
+ const body = block.body ?? (Array.isArray(block) ? block : null);
1392
+ if (!Array.isArray(body)) return;
1393
+ for (let i = 0; i < body.length; i++) {
1394
+ walkUnreachable(body[i], out);
1395
+ if (isExit(body[i])) {
1396
+ for (let j = i + 1; j < body.length; j++) {
1397
+ const r = locOf(body[j]);
1398
+ if (r) out.push({ range: r });
1399
+ }
1400
+ break;
1401
+ }
1402
+ }
1403
+ };
1404
+ switch (n.type) {
1405
+ case "Program":
1406
+ case "BlockStatement":
1407
+ checkBlock(n.body);
1408
+ break;
1409
+ case "FunctionDeclaration":
1410
+ case "FunctionExpression":
1411
+ case "ArrowFunctionExpression":
1412
+ if (n.body) walkUnreachable(n.body, out);
1413
+ break;
1414
+ case "IfStatement":
1415
+ if (n.consequent) walkUnreachable(n.consequent, out);
1416
+ if (n.alternate) walkUnreachable(n.alternate, out);
1417
+ break;
1418
+ case "ForStatement":
1419
+ case "ForInStatement":
1420
+ case "ForOfStatement":
1421
+ case "WhileStatement":
1422
+ case "DoWhileStatement":
1423
+ if (n.body) walkUnreachable(n.body, out);
1424
+ break;
1425
+ case "TryStatement":
1426
+ if (n.block) walkUnreachable(n.block, out);
1427
+ if (n.handler?.body) walkUnreachable(n.handler.body, out);
1428
+ if (n.finalizer) walkUnreachable(n.finalizer, out);
1429
+ break;
1430
+ case "SwitchStatement":
1431
+ for (const c of n.cases ?? []) {
1432
+ checkBlock(c.consequent);
1433
+ }
1434
+ break;
1435
+ case "ExportNamedDeclaration":
1436
+ case "ExportDefaultDeclaration": {
1437
+ const decl = n.declaration;
1438
+ if (decl) walkUnreachable(decl, out);
1439
+ break;
1440
+ }
1441
+ default:
1442
+ break;
1443
+ }
1444
+ }
1445
+ function collectDeclared(file) {
1446
+ const names = /* @__PURE__ */ new Set();
1447
+ const addId = (id) => {
1448
+ if (!id) return;
1449
+ if (id.type === "Identifier") names.add(id.name);
1450
+ if (id.type === "ObjectPattern") {
1451
+ for (const p of id.properties) {
1452
+ addId(p.value);
1453
+ }
1454
+ }
1455
+ if (id.type === "ArrayPattern") {
1456
+ for (const el of id.elements) addId(el);
1457
+ }
1458
+ };
1459
+ const visit = (node) => {
1460
+ if (!node || typeof node !== "object") return;
1461
+ const o = node;
1462
+ switch (o.type) {
1463
+ case "FunctionDeclaration":
1464
+ case "ClassDeclaration":
1465
+ addId(o.id);
1466
+ break;
1467
+ case "VariableDeclarator":
1468
+ addId(o.id);
1469
+ break;
1470
+ case "ImportSpecifier":
1471
+ case "ImportDefaultSpecifier":
1472
+ case "ImportNamespaceSpecifier":
1473
+ addId(o.local);
1474
+ break;
1475
+ default:
1476
+ break;
1477
+ }
1478
+ if (o.params && Array.isArray(o.params)) {
1479
+ for (const p of o.params) addId(p);
1480
+ }
1481
+ for (const key of Object.keys(o)) {
1482
+ if (key === "loc" || key === "start" || key === "end") continue;
1483
+ const v = o[key];
1484
+ if (Array.isArray(v)) v.forEach(visit);
1485
+ else if (v && typeof v === "object") visit(v);
1486
+ }
1487
+ };
1488
+ visit(file);
1489
+ return names;
1490
+ }
1491
+ var KNOWN_GLOBALS = /* @__PURE__ */ new Set([
1492
+ "Object",
1493
+ "Array",
1494
+ "String",
1495
+ "Number",
1496
+ "Boolean",
1497
+ "Symbol",
1498
+ "Math",
1499
+ "JSON",
1500
+ "Promise",
1501
+ "Error",
1502
+ "TypeError",
1503
+ "RangeError",
1504
+ "SyntaxError",
1505
+ "RegExp",
1506
+ "Date",
1507
+ "Map",
1508
+ "Set",
1509
+ "WeakMap",
1510
+ "WeakSet",
1511
+ "console",
1512
+ "undefined",
1513
+ "NaN",
1514
+ "Infinity",
1515
+ "globalThis",
1516
+ "parseInt",
1517
+ "parseFloat",
1518
+ "isNaN",
1519
+ "isFinite"
1520
+ ]);
1521
+ function isFreeUnknown(name, declared, seen, known) {
1522
+ return !declared.has(name) && !known.has(name) && !name.startsWith("$") && !seen.has(name);
1523
+ }
1524
+ function walkBuiltinUnknown(node, declared, out, seen, known, parentKey) {
1525
+ const n = node;
1526
+ const flag = (idNode, name) => {
1527
+ if (!isFreeUnknown(name, declared, seen, known)) return;
1528
+ const range = locOf(idNode) ?? locOf(node);
1529
+ if (range) {
1530
+ out.push({ name, range });
1531
+ seen.add(name);
1532
+ }
1533
+ };
1534
+ if (n.type === "CallExpression" && n.callee?.type === "Identifier") {
1535
+ flag(n.callee, n.callee.name);
1536
+ } else if (n.type === "Identifier" && typeof n.name === "string" && parentKey !== "key" && parentKey !== "property" && parentKey !== "local" && parentKey !== "id") {
1537
+ flag(node, n.name);
1538
+ }
1539
+ for (const key of Object.keys(n)) {
1540
+ if (key === "loc" || key === "start" || key === "end") continue;
1541
+ const v = n[key];
1542
+ if (Array.isArray(v)) {
1543
+ v.forEach((x) => {
1544
+ if (x && typeof x === "object" && "type" in x) {
1545
+ walkBuiltinUnknown(x, declared, out, seen, known, key);
1546
+ }
1547
+ });
1548
+ } else if (v && typeof v === "object" && "type" in v) {
1549
+ walkBuiltinUnknown(v, declared, out, seen, known, key);
1550
+ }
1551
+ }
1552
+ }
1553
+ function collectBPathDiagnostics(source, extraKnown) {
1554
+ let file;
1555
+ try {
1556
+ file = parseSource(source);
1557
+ } catch {
1558
+ return { unreachable: [], builtinUnknown: [] };
1559
+ }
1560
+ const unreachable = [];
1561
+ const root = file.program ?? file;
1562
+ walkUnreachable(root, unreachable);
1563
+ const declared = collectDeclared(file);
1564
+ const known = new Set(KNOWN_GLOBALS);
1565
+ if (extraKnown) {
1566
+ for (const n of extraKnown) known.add(n);
1567
+ }
1568
+ const builtinUnknown = [];
1569
+ walkBuiltinUnknown(file, declared, builtinUnknown, /* @__PURE__ */ new Set(), known);
1570
+ return { unreachable, builtinUnknown };
1571
+ }
1572
+
1573
+ // src/analyzer.ts
1574
+ import { setAbsTruncationCollector as setAbsTruncationCollector2 } from "@nudojs/core";
44
1575
  function resolveModule(source, fromDir) {
45
1576
  const extensions = [".js", ".ts", ".mjs"];
46
1577
  const nudoPath = resolveNpmNudo(source, fromDir);
47
1578
  if (nudoPath) {
48
- const src = readFileSync(nudoPath, "utf-8");
49
- return { ast: parse(src), filePath: nudoPath };
1579
+ const src = readFileSync4(nudoPath, "utf-8");
1580
+ return { ast: parse5(src), filePath: nudoPath };
50
1581
  }
51
- const basePath = resolve(fromDir, source);
1582
+ const basePath = resolve4(fromDir, source);
52
1583
  for (const ext of ["", ...extensions]) {
53
1584
  const candidate = basePath + ext;
54
- if (!existsSync(candidate)) continue;
55
- if (statSync(candidate).isDirectory()) {
1585
+ if (!existsSync3(candidate)) continue;
1586
+ if (statSync3(candidate).isDirectory()) {
56
1587
  let entry = null;
57
- const pkgPath = resolve(candidate, "package.json");
58
- if (existsSync(pkgPath)) {
1588
+ const pkgPath = resolve4(candidate, "package.json");
1589
+ if (existsSync3(pkgPath)) {
59
1590
  try {
60
- const main = JSON.parse(readFileSync(pkgPath, "utf-8")).main;
1591
+ const main = JSON.parse(readFileSync4(pkgPath, "utf-8")).main;
61
1592
  if (typeof main === "string") {
62
1593
  for (const e of ["", ...extensions]) {
63
- const p = resolve(candidate, main + e);
64
- if (existsSync(p) && statSync(p).isFile()) {
1594
+ const p = resolve4(candidate, main + e);
1595
+ if (existsSync3(p) && statSync3(p).isFile()) {
65
1596
  entry = p;
66
1597
  break;
67
1598
  }
@@ -72,34 +1603,34 @@ function resolveModule(source, fromDir) {
72
1603
  }
73
1604
  if (!entry) {
74
1605
  for (const e of ["", ...extensions]) {
75
- const p = resolve(candidate, "index" + e);
76
- if (existsSync(p) && statSync(p).isFile()) {
1606
+ const p = resolve4(candidate, "index" + e);
1607
+ if (existsSync3(p) && statSync3(p).isFile()) {
77
1608
  entry = p;
78
1609
  break;
79
1610
  }
80
1611
  }
81
1612
  }
82
1613
  if (!entry) return null;
83
- const src2 = readFileSync(entry, "utf-8");
84
- return { ast: parse(src2), filePath: entry };
1614
+ const src2 = readFileSync4(entry, "utf-8");
1615
+ return { ast: parse5(src2), filePath: entry };
85
1616
  }
86
1617
  if (candidate.endsWith(".json")) {
87
1618
  try {
88
- return { ast: parse("module.exports = undefined;"), filePath: candidate, json: JSON.parse(readFileSync(candidate, "utf-8")) };
1619
+ return { ast: parse5("module.exports = undefined;"), filePath: candidate, json: JSON.parse(readFileSync4(candidate, "utf-8")) };
89
1620
  } catch {
90
1621
  return null;
91
1622
  }
92
1623
  }
93
- const src = readFileSync(candidate, "utf-8");
94
- return { ast: parse(src), filePath: candidate };
1624
+ const src = readFileSync4(candidate, "utf-8");
1625
+ return { ast: parse5(src), filePath: candidate };
95
1626
  }
96
1627
  return null;
97
1628
  }
98
1629
  function resolveImportPath(specifier, fromDir) {
99
- const basePath = resolve(fromDir, specifier);
1630
+ const basePath = resolve4(fromDir, specifier);
100
1631
  for (const ext of ["", ".js", ".ts", ".mjs"]) {
101
1632
  const candidate = basePath + ext;
102
- if (existsSync(candidate)) return candidate;
1633
+ if (existsSync3(candidate)) return candidate;
103
1634
  }
104
1635
  return null;
105
1636
  }
@@ -119,7 +1650,7 @@ function buildModuleGraph(files, cache) {
119
1650
  if (cache) {
120
1651
  let stat = null;
121
1652
  try {
122
- stat = statSync(file);
1653
+ stat = statSync3(file);
123
1654
  } catch {
124
1655
  }
125
1656
  const cached = stat ? cache.get(file) : void 0;
@@ -140,7 +1671,7 @@ function extractImportEdges(file) {
140
1671
  const edges = [];
141
1672
  let ast;
142
1673
  try {
143
- ast = parse(readFileSync(file, "utf-8"));
1674
+ ast = parse5(readFileSync4(file, "utf-8"));
144
1675
  } catch {
145
1676
  return edges;
146
1677
  }
@@ -148,7 +1679,7 @@ function extractImportEdges(file) {
148
1679
  if (stmt.type !== "ImportDeclaration") continue;
149
1680
  const specifier = stmt.source.value;
150
1681
  if (!specifier.startsWith(".") && !specifier.startsWith("/")) continue;
151
- const resolved = resolveImportPath(specifier, dirname(file));
1682
+ const resolved = resolveImportPath(specifier, dirname5(file));
152
1683
  if (resolved) edges.push(resolved);
153
1684
  }
154
1685
  return edges;
@@ -201,7 +1732,7 @@ function applyMocks(directives, env, filePath, diagnostics) {
201
1732
  for (const d of directives) {
202
1733
  if (d.kind !== "mock") continue;
203
1734
  if (d.arrowFn) {
204
- const fnType = T.fn(d.arrowFn.params, d.arrowFn.body, env);
1735
+ const fnType = T2.fn(d.arrowFn.params, d.arrowFn.body, env);
205
1736
  fnType._paramPatterns = d.arrowFn.paramPatterns;
206
1737
  env.bind(d.name, fnType);
207
1738
  } else if (d.nudoMock) {
@@ -224,12 +1755,12 @@ function applyMocks(directives, env, filePath, diagnostics) {
224
1755
  ]
225
1756
  });
226
1757
  }
227
- env.bind(d.name, parseTypeValueExpr(d.expression));
1758
+ env.bind(d.name, parseTypeValueExpr2(d.expression));
228
1759
  } else if (d.fromPath) {
229
- const mockPath = resolve(dirname(filePath), d.fromPath);
230
- const mockSource = readFileSync(mockPath, "utf-8");
231
- const mockAst = parse(mockSource);
232
- const mockEnv = createEnvironment();
1760
+ const mockPath = resolve4(dirname5(filePath), d.fromPath);
1761
+ const mockSource = readFileSync4(mockPath, "utf-8");
1762
+ const mockAst = parse5(mockSource);
1763
+ const mockEnv = createEnvironment2();
233
1764
  evaluateProgram(mockAst, mockEnv);
234
1765
  const mockVal = mockEnv.lookup(d.name);
235
1766
  env.bind(d.name, mockVal);
@@ -239,19 +1770,19 @@ function applyMocks(directives, env, filePath, diagnostics) {
239
1770
  function createSinonTypeValue(sinonExpr, env) {
240
1771
  if (sinonExpr.type === "stub" || sinonExpr.type === "spy") {
241
1772
  const body = { type: "BlockStatement", body: [] };
242
- const fn = T.fn(["...args"], body, env);
1773
+ const fn = T2.fn(["...args"], body, env);
243
1774
  if (sinonExpr.returnValue) {
244
1775
  fn._directReturn = sinonExpr.returnValue;
245
1776
  } else if (sinonExpr.resolvedValue) {
246
- fn._directReturn = T.promise(sinonExpr.resolvedValue);
1777
+ fn._directReturn = T2.promise(sinonExpr.resolvedValue);
247
1778
  } else if (sinonExpr.rejectedValue) {
248
- fn._directReturn = T.never;
1779
+ fn._directReturn = T2.never;
249
1780
  } else {
250
- fn._directReturn = T.unknown;
1781
+ fn._directReturn = T2.unknown;
251
1782
  }
252
1783
  return fn;
253
1784
  }
254
- return T.unknown;
1785
+ return T2.unknown;
255
1786
  }
256
1787
  function locFromNode(node) {
257
1788
  return {
@@ -516,7 +2047,7 @@ function synthesizeExternalFunctions(records, currentFile) {
516
2047
  (_, i) => (
517
2048
  // 缺参按真实 JS 语义 widen 成 undefined 而非 unknown——可选参守卫
518
2049
  // (target || [] 等)对 unknown 全塌,对 undefined 正常走默认分支
519
- widenType(simplifyUnion(remaining.map((rec) => rec.argTypes[i] ?? T.undefined)))
2050
+ widenType(simplifyUnion(remaining.map((rec) => rec.argTypes[i] ?? T2.undefined)))
520
2051
  )
521
2052
  ),
522
2053
  result: collapseLiteralUnion(simplifyUnion(remaining.map((r) => r.resultType)), COLLAPSE_LITERAL_THRESHOLD),
@@ -548,7 +2079,7 @@ function receiverTypeToDisplay(tv) {
548
2079
  case "union":
549
2080
  return tv.members.map(receiverTypeToDisplay).join(" | ");
550
2081
  default:
551
- return typeValueToString(tv);
2082
+ return typeValueToString3(tv);
552
2083
  }
553
2084
  }
554
2085
  function receiverIsConcrete(tv) {
@@ -645,34 +2176,34 @@ function unknownRecordsToDiagnostics(records) {
645
2176
  return out;
646
2177
  }
647
2178
  function collectEnvNames(filePath, source, includeProject) {
648
- const ast = parse(source);
2179
+ const ast = parse5(source);
649
2180
  const fileDirectives = extractFileDirectives(ast);
650
2181
  const fileEnvNames = fileDirectives.filter((d) => d.kind === "env").flatMap((d) => d.envs);
651
2182
  if (!includeProject) return fileEnvNames;
652
- const projectConfig = findProjectConfig(dirname(filePath));
2183
+ const projectConfig = findProjectConfig(dirname5(filePath));
653
2184
  const projectEnvNames = projectConfig?.config.env ?? [];
654
2185
  return [.../* @__PURE__ */ new Set([...projectEnvNames, ...fileEnvNames])];
655
2186
  }
656
2187
  async function analyzeFileAsync(filePath, source, activeCases, externalCallRecords) {
657
2188
  const envNames = collectEnvNames(filePath, source, true);
658
2189
  if (envNames.length > 0) {
659
- await preloadPathEnvs(envNames, dirname(filePath));
2190
+ await preloadPathEnvs(envNames, dirname5(filePath));
660
2191
  }
661
2192
  return analyzeFile(filePath, source, activeCases, externalCallRecords);
662
2193
  }
663
2194
  function collectCallRecords(filePath, source) {
664
2195
  const records = [];
665
- const ast = parse(source, { errorRecovery: true });
2196
+ const ast = parse5(source, { errorRecovery: true });
666
2197
  resetMemo();
667
2198
  resetUnreachableRanges();
668
2199
  resetEnvModules();
669
2200
  resetMockModules();
670
2201
  setModuleResolver(resolveModule);
671
- setCurrentFileDir(dirname(filePath));
2202
+ setCurrentFileDir(dirname5(filePath));
672
2203
  setCurrentSource(source);
673
2204
  setCallCollector((record) => records.push(record));
674
2205
  try {
675
- const env = createEnvironment();
2206
+ const env = createEnvironment2();
676
2207
  evaluateProgram(ast, env);
677
2208
  const runCallbacks = (statements) => {
678
2209
  for (const stmt of statements) {
@@ -688,7 +2219,7 @@ function collectCallRecords(filePath, source) {
688
2219
  if (!cb) continue;
689
2220
  try {
690
2221
  const params = cb.params ?? [];
691
- evaluateFunction(cb, params.map(() => T.unknown), env);
2222
+ evaluateFunction(cb, params.map(() => T2.unknown), env);
692
2223
  } catch {
693
2224
  }
694
2225
  if (name === "describe" && cb.body.type === "BlockStatement") {
@@ -706,11 +2237,88 @@ function collectCallRecords(filePath, source) {
706
2237
  return records;
707
2238
  }
708
2239
  var TEST_CALLBACK_NAMES = /* @__PURE__ */ new Set(["it", "test", "describe"]);
2240
+ var externalRecordIds = /* @__PURE__ */ new WeakMap();
2241
+ var nextExternalRecordId = 1;
2242
+ function analysisFileCacheKey(filePath, source, activeCases, externalCallRecords) {
2243
+ let cases = "-";
2244
+ if (activeCases && activeCases.size > 0) {
2245
+ cases = [...activeCases.entries()].sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0).map(([k, v]) => `${k}=${v}`).join(",");
2246
+ }
2247
+ let ext = "-";
2248
+ if (externalCallRecords && externalCallRecords.length > 0) {
2249
+ let id = externalRecordIds.get(externalCallRecords);
2250
+ if (id === void 0) {
2251
+ id = nextExternalRecordId++;
2252
+ externalRecordIds.set(externalCallRecords, id);
2253
+ }
2254
+ ext = `n${externalCallRecords.length}#id${id}`;
2255
+ }
2256
+ return {
2257
+ filePath,
2258
+ // 尾部无 @nudo 注释/空行不进键:comment-only 编辑命中 AnalysisResult
2259
+ source: stableAnalyzeKeySource2(source),
2260
+ auxKey: `${cases}\0${ext}`
2261
+ };
2262
+ }
2263
+ function cloneAnalysisResult(r) {
2264
+ return {
2265
+ functions: r.functions.map(cloneFunctionAnalysis),
2266
+ diagnostics: r.diagnostics.map((d) => ({ ...d })),
2267
+ bindings: new Map(r.bindings),
2268
+ // Node 键与 AST LRU 共享身份;Map 浅拷贝即可
2269
+ nodeTypeMap: new Map(r.nodeTypeMap),
2270
+ caseHints: r.caseHints.map((h) => ({ ...h })),
2271
+ ...r.externalFunctions ? { externalFunctions: r.externalFunctions.map(cloneFunctionAnalysis) } : {}
2272
+ };
2273
+ }
2274
+ function cloneFunctionAnalysis(a) {
2275
+ return {
2276
+ ...a,
2277
+ paramNames: [...a.paramNames],
2278
+ cases: a.cases.map((c) => ({
2279
+ ...c,
2280
+ args: [...c.args],
2281
+ ...c.intension ? { intension: { ...c.intension } } : {}
2282
+ })),
2283
+ loc: { start: { ...a.loc.start }, end: { ...a.loc.end } }
2284
+ };
2285
+ }
2286
+ function shiftSourceLoc(loc, lineDelta) {
2287
+ return {
2288
+ start: { line: loc.start.line + lineDelta, column: loc.start.column },
2289
+ end: { line: loc.end.line + lineDelta, column: loc.end.column }
2290
+ };
2291
+ }
2292
+ function shiftDiagnosticLines(d, lineDelta) {
2293
+ if (lineDelta === 0) return { ...d };
2294
+ const out = { ...d, range: shiftSourceLoc(d.range, lineDelta) };
2295
+ if (d.origin) {
2296
+ out.origin = { line: d.origin.line + lineDelta, column: d.origin.column };
2297
+ }
2298
+ return out;
2299
+ }
2300
+ function shiftCallRecordLines(r, lineDelta) {
2301
+ if (lineDelta === 0 || !r.callLoc) return { ...r };
2302
+ return {
2303
+ ...r,
2304
+ callLoc: { line: r.callLoc.line + lineDelta, column: r.callLoc.column }
2305
+ };
2306
+ }
709
2307
  function analyzeFile(filePath, source, activeCases, externalCallRecords) {
2308
+ const k = analysisFileCacheKey(filePath, source, activeCases, externalCallRecords);
2309
+ const hit = analysisCacheGet(k.filePath, k.source, k.auxKey);
2310
+ if (hit !== void 0) {
2311
+ return cloneAnalysisResult(hit);
2312
+ }
2313
+ const result = analyzeFileUncached(filePath, source, activeCases, externalCallRecords);
2314
+ analysisCacheSet(k.filePath, k.source, k.auxKey, result);
2315
+ return cloneAnalysisResult(result);
2316
+ }
2317
+ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords) {
710
2318
  for (const rec of externalCallRecords ?? []) {
711
2319
  for (const a of rec.argTypes) setUsageSiteTag(a);
712
2320
  }
713
- const ast = parse(source);
2321
+ const ast = parse5(source);
714
2322
  const functions = extractDirectives(ast);
715
2323
  const diagnostics = [];
716
2324
  const bindings = /* @__PURE__ */ new Map();
@@ -722,14 +2330,15 @@ function analyzeFile(filePath, source, activeCases, externalCallRecords) {
722
2330
  resetEnvModules();
723
2331
  resetMockModules();
724
2332
  setModuleResolver(resolveModule);
725
- setCurrentFileDir(dirname(filePath));
2333
+ setCurrentFileDir(dirname5(filePath));
726
2334
  setCurrentSource(source);
727
2335
  const fileDirectives = extractFileDirectives(ast);
728
2336
  const fileEnvNames = fileDirectives.filter((d) => d.kind === "env").flatMap((d) => d.envs);
729
- const projectConfig = findProjectConfig(dirname(filePath));
2337
+ const projectConfig = findProjectConfig(dirname5(filePath));
730
2338
  const projectEnvNames = projectConfig?.config.env ?? [];
731
2339
  const envNames = [.../* @__PURE__ */ new Set([...projectEnvNames, ...fileEnvNames])];
732
2340
  setUnknownBuiltinHandler((name, loc) => {
2341
+ if (isBPathCapable(source, envNames)) return;
733
2342
  diagnostics.push({
734
2343
  range: loc ? { start: loc.start, end: loc.end } : { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
735
2344
  severity: "warning",
@@ -737,19 +2346,21 @@ function analyzeFile(filePath, source, activeCases, externalCallRecords) {
737
2346
  code: "nudo:builtin-unknown",
738
2347
  suggestions: [
739
2348
  `Use @nudo:mock to define the type: @nudo:mock ${name} = stub().returns(...)`,
740
- `Or use @nudo:returns to declare the expected return type`
2349
+ `Or use @nudo:refine return <constraint> to declare the return contract`
741
2350
  ]
742
2351
  });
743
2352
  });
744
- const globalEnv = createEnvironment();
745
- if (envNames.length > 0) {
746
- const loaded = loadEnvs(envNames, globalEnv);
747
- setEnvModules(loaded.modules);
2353
+ const globalEnv = createEnvironment2();
2354
+ {
2355
+ const loaded = envNames.length > 0 ? loadEnvs2(envNames, globalEnv) : { modules: {} };
2356
+ const auto = autoHarvestModules(source, dirname5(filePath));
2357
+ const modules = { ...loaded.modules, ...auto };
2358
+ if (Object.keys(modules).length > 0) setEnvModules(modules);
748
2359
  }
749
2360
  const mocks = /* @__PURE__ */ new Map();
750
2361
  if (projectConfig?.config.mocks) {
751
2362
  for (const [source2, mockPath] of Object.entries(projectConfig.config.mocks)) {
752
- mocks.set(source2, { fromPath: resolve(projectConfig.projectDir, mockPath) });
2363
+ mocks.set(source2, { fromPath: resolve4(projectConfig.projectDir, mockPath) });
753
2364
  }
754
2365
  }
755
2366
  const mockModuleDirectives = fileDirectives.filter((d) => d.kind === "mock-module");
@@ -764,27 +2375,225 @@ function analyzeFile(filePath, source, activeCases, externalCallRecords) {
764
2375
  const unknownRecords = [];
765
2376
  setUnknownCollector((r) => unknownRecords.push(r));
766
2377
  setProvenanceTracking(true);
2378
+ const bMemberDiagNames = /* @__PURE__ */ new Set();
2379
+ const bMemberDiagSeen = /* @__PURE__ */ new Set();
2380
+ const pushBMemberDiag = (d, fallbackLine) => {
2381
+ bMemberDiagNames.add(d.name);
2382
+ const key = `${d.kind}:${d.name}:${d.receiver}:${d.line ?? fallbackLine}:${d.column ?? 0}`;
2383
+ if (bMemberDiagSeen.has(key)) return;
2384
+ bMemberDiagSeen.add(key);
2385
+ if (d.receiver === "unknown") {
2386
+ diagnostics.push({
2387
+ range: {
2388
+ start: { line: d.line ?? fallbackLine, column: d.column ?? 0 },
2389
+ end: { line: d.line ?? fallbackLine, column: (d.column ?? 0) + d.name.length }
2390
+ },
2391
+ severity: "warning",
2392
+ message: `Cannot resolve '${d.name}' on unknown value`,
2393
+ code: "nudo:unknown-recv",
2394
+ ...d.origin ? { origin: d.origin } : {}
2395
+ });
2396
+ return;
2397
+ }
2398
+ diagnostics.push({
2399
+ range: {
2400
+ start: { line: d.line ?? fallbackLine, column: d.column ?? 0 },
2401
+ end: { line: d.line ?? fallbackLine, column: (d.column ?? 0) + d.name.length }
2402
+ },
2403
+ severity: d.receiver === "number" || d.receiver === "boolean" || d.receiver === "bigint" || d.receiver === "symbol" ? "error" : "warning",
2404
+ message: d.kind === "method" ? `Method '${d.name}' does not exist on type '${d.receiver}'` : `Property '${d.name}' does not exist on type '${d.receiver}'`,
2405
+ code: "nudo:no-method",
2406
+ ...d.origin ? { origin: d.origin } : {}
2407
+ });
2408
+ };
767
2409
  for (const fn of functions) {
768
2410
  applyMocks(fn.directives, globalEnv, filePath, diagnostics);
769
2411
  }
770
- evaluateProgram(ast, globalEnv);
771
- const unreachableRanges = getUnreachableRanges();
772
- for (const ur of unreachableRanges) {
773
- diagnostics.push({
774
- range: ur,
775
- severity: "info",
776
- message: "Code after return/throw statement is unreachable",
777
- tags: ["unnecessary"],
778
- code: "nudo-unreachable",
779
- suggestions: ["Remove the unreachable code after the return/throw statement"]
2412
+ const selfContained = isSelfContainedSource(source, envNames);
2413
+ const canAbsModules = absModulesOk(source, envNames);
2414
+ const bCapable = isBPathCapable(source, envNames);
2415
+ const bModuleIssueKinds = /* @__PURE__ */ new Set();
2416
+ const bTruncatedFns = /* @__PURE__ */ new Set();
2417
+ const bBuiltinUnknownNames = /* @__PURE__ */ new Set();
2418
+ let bHostedEval = false;
2419
+ const pushBModuleIssues = (issues) => {
2420
+ if (!issues) return;
2421
+ for (const iss of issues) {
2422
+ bModuleIssueKinds.add(iss.kind);
2423
+ const code = iss.kind === "cycle" ? "nudo:module-cycle" : iss.kind === "depth" ? "nudo:module-depth" : "nudo:module-missing";
2424
+ diagnostics.push({
2425
+ range: { start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
2426
+ severity: iss.kind === "missing" ? "error" : "warning",
2427
+ message: iss.reason,
2428
+ code
2429
+ });
2430
+ }
2431
+ };
2432
+ const seeds = mockDirectivesToAbsSeeds(functions);
2433
+ let absCallRecords = [];
2434
+ let bTopCallRecords = [];
2435
+ let absGraphModules;
2436
+ let absBindsShared;
2437
+ let absNodesShared;
2438
+ if (bCapable && filePath) {
2439
+ try {
2440
+ const g = evalAbsModuleGraph(source, filePath, {
2441
+ seedVars: seeds.seedVars,
2442
+ seedFns: seeds.seedFns
2443
+ });
2444
+ absGraphModules = { ...collectEnvModules(envNames), ...g.modules };
2445
+ pushBModuleIssues(g.issues);
2446
+ } catch {
2447
+ }
2448
+ const bRun = tryRunBPath(source, filePath, {
2449
+ envNames,
2450
+ mocks: mockSeedsToAbsMocks(seeds)
780
2451
  });
2452
+ if (bRun) {
2453
+ bHostedEval = true;
2454
+ if (bRun.memberDiags?.length) {
2455
+ for (const d of bRun.memberDiags) {
2456
+ pushBMemberDiag(d, 1);
2457
+ }
2458
+ }
2459
+ if (bRun.truncatedFns) {
2460
+ for (const fn of bRun.truncatedFns) bTruncatedFns.add(fn);
2461
+ }
2462
+ if (bRun.calls?.length) {
2463
+ const impMap = buildAbsImportLocalMap(source, filePath);
2464
+ for (const c of bRun.calls) {
2465
+ const safeAbs = (a) => {
2466
+ if (!a || typeof a !== "object" || !("shape" in a) || !a.shape) {
2467
+ return T2.unknown;
2468
+ }
2469
+ try {
2470
+ return absToTypeValue2(a);
2471
+ } catch {
2472
+ return T2.unknown;
2473
+ }
2474
+ };
2475
+ const rec = {
2476
+ fnName: c.fnName,
2477
+ argTypes: c.args.map(safeAbs),
2478
+ resultType: c.threw ? T2.never : safeAbs(c.result),
2479
+ throws: c.threw ? safeAbs(c.result) : T2.never,
2480
+ callLoc: c.callLoc
2481
+ };
2482
+ const imp = impMap.get(c.fnName);
2483
+ if (imp) {
2484
+ rec.targetModule = imp.modulePath;
2485
+ rec.targetExport = imp.exportName;
2486
+ }
2487
+ bTopCallRecords.push(rec);
2488
+ }
2489
+ }
2490
+ }
2491
+ }
2492
+ if (selfContained || canAbsModules) {
2493
+ setAbsTruncationCollector2((label) => bTruncatedFns.add(label));
2494
+ try {
2495
+ absCallRecords = collectAbsCallRecords(source, seeds, filePath, absGraphModules, envNames);
2496
+ } finally {
2497
+ setAbsTruncationCollector2(null);
2498
+ }
2499
+ }
2500
+ if (bHostedEval) {
2501
+ try {
2502
+ const collected = collectAbsBindsAndNodes(source, seeds, absGraphModules);
2503
+ absBindsShared = collected.binds;
2504
+ absNodesShared = collected.nodes;
2505
+ for (const [name, absVal] of absBindsShared) {
2506
+ if (absVal?.shape?.k === "fn") continue;
2507
+ if (!globalEnv.has(name)) {
2508
+ globalEnv.bind(name, absToTypeValue2(absVal));
2509
+ }
2510
+ }
2511
+ for (const [node, absVal] of absNodesShared) {
2512
+ nodeTypeMap.set(node, absToTypeValue2(absVal));
2513
+ }
2514
+ } catch {
2515
+ }
2516
+ } else {
2517
+ evaluateProgram(ast, globalEnv);
2518
+ }
2519
+ if ((selfContained || canAbsModules) && absCallRecords.length > 0) {
2520
+ callRecords.length = 0;
2521
+ callRecords.push(...absCallRecords);
2522
+ } else if (bTopCallRecords.length > 0) {
2523
+ callRecords.length = 0;
2524
+ callRecords.push(...bTopCallRecords);
2525
+ }
2526
+ const unreachableRanges = bCapable ? [] : getUnreachableRanges();
2527
+ if (bCapable) {
2528
+ const bKnownGlobals = /* @__PURE__ */ new Set([
2529
+ ...Object.keys(collectEnvGlobals(envNames)),
2530
+ ...Object.keys(mockSeedsToAbsMocks(seeds))
2531
+ ]);
2532
+ const bDiag = collectBPathDiagnostics(source, bKnownGlobals);
2533
+ for (const ur of bDiag.unreachable) {
2534
+ diagnostics.push({
2535
+ range: ur.range,
2536
+ severity: "info",
2537
+ message: "Code after return/throw statement is unreachable",
2538
+ tags: ["unnecessary"],
2539
+ code: "nudo-unreachable",
2540
+ suggestions: ["Remove the unreachable code after the return/throw statement"]
2541
+ });
2542
+ }
2543
+ for (const b of bDiag.builtinUnknown) {
2544
+ bBuiltinUnknownNames.add(b.name);
2545
+ diagnostics.push({
2546
+ range: b.range,
2547
+ severity: "warning",
2548
+ message: `Built-in API "${b.name}" is not covered by Nudo's type inference`,
2549
+ code: "nudo:builtin-unknown",
2550
+ suggestions: [
2551
+ `Use @nudo:mock to define the type: @nudo:mock ${b.name} = stub().returns(...)`,
2552
+ `Or use @nudo:refine return <constraint> to declare the return contract`
2553
+ ]
2554
+ });
2555
+ }
2556
+ } else {
2557
+ for (const ur of unreachableRanges) {
2558
+ diagnostics.push({
2559
+ range: ur,
2560
+ severity: "info",
2561
+ message: "Code after return/throw statement is unreachable",
2562
+ tags: ["unnecessary"],
2563
+ code: "nudo-unreachable",
2564
+ suggestions: ["Remove the unreachable code after the return/throw statement"]
2565
+ });
2566
+ }
781
2567
  }
782
2568
  collectBindings(ast, globalEnv, bindings);
2569
+ if (isBPathCapable(source, envNames)) {
2570
+ try {
2571
+ const absBinds = absBindsShared ?? collectAbsBindingsFromGraph(source, filePath, {
2572
+ seedVars: seeds.seedVars,
2573
+ seedFns: seeds.seedFns
2574
+ });
2575
+ for (const [name, absVal] of absBinds) {
2576
+ const prev = bindings.get(name);
2577
+ bindings.set(name, {
2578
+ type: absToTypeValue2(absVal),
2579
+ loc: prev?.loc
2580
+ });
2581
+ }
2582
+ } catch {
2583
+ }
2584
+ }
783
2585
  const synthCandidates = [];
2586
+ let fnFpMap;
2587
+ try {
2588
+ fnFpMap = fnFingerprints(source, ast);
2589
+ } catch {
2590
+ fnFpMap = void 0;
2591
+ }
2592
+ const envKeyFn = envNames.join(",");
2593
+ const mockKeyFn = mockSeedFingerprint(seeds.seedVars, seeds.seedFns);
784
2594
  for (const fn of functions) {
785
2595
  const isPure = fn.directives.some((d) => d.kind === "pure");
786
2596
  const skipDirective = fn.directives.find((d) => d.kind === "skip");
787
- const returnsDirective = fn.directives.find((d) => d.kind === "returns");
788
2597
  const fnLoc = locFromNode(fn.node);
789
2598
  const paramNames = extractParamNames(fn.node);
790
2599
  const analysis = { name: fn.name, loc: fnLoc, paramNames, cases: [] };
@@ -796,6 +2605,51 @@ function analyzeFile(filePath, source, activeCases, externalCallRecords) {
796
2605
  functionResults.push(analysis);
797
2606
  continue;
798
2607
  }
2608
+ const caseDirectives = fn.directives.filter((d) => d.kind === "case");
2609
+ const activeCaseIdx = activeCases?.get(fn.name) ?? 0;
2610
+ const fp = fnFpMap?.get(fn.name);
2611
+ const fnCacheKey = fp && caseDirectives.length > 0 ? [
2612
+ filePath,
2613
+ fp.own,
2614
+ fp.deps,
2615
+ String(activeCaseIdx),
2616
+ caseDirectiveKey(caseDirectives, typeValueToString3),
2617
+ envKeyFn,
2618
+ mockKeyFn
2619
+ ].join("\0") : void 0;
2620
+ const dLen0 = diagnostics.length;
2621
+ const hLen0 = caseHints.length;
2622
+ const cLen0 = callRecords.length;
2623
+ if (fnCacheKey) {
2624
+ const hitFn = fnAnalysisCacheGet(fnCacheKey);
2625
+ if (hitFn) {
2626
+ const cached = cloneFunctionAnalysis(hitFn.analysis);
2627
+ const lineDelta = fnLoc.start.line - cached.loc.start.line;
2628
+ cached.loc = fnLoc;
2629
+ if (lineDelta !== 0) {
2630
+ for (const c of cached.cases) {
2631
+ if (c.throwLoc) c.throwLoc = shiftSourceLoc(c.throwLoc, lineDelta);
2632
+ }
2633
+ }
2634
+ if (lineDelta === 0) {
2635
+ diagnostics.push(...hitFn.diagnostics);
2636
+ caseHints.push(...hitFn.caseHints);
2637
+ callRecords.push(...hitFn.callRecords);
2638
+ } else {
2639
+ for (const d of hitFn.diagnostics) {
2640
+ diagnostics.push(shiftDiagnosticLines(d, lineDelta));
2641
+ }
2642
+ for (const h of hitFn.caseHints) {
2643
+ caseHints.push({ ...h, line: h.line + lineDelta });
2644
+ }
2645
+ for (const r of hitFn.callRecords) {
2646
+ callRecords.push(shiftCallRecordLines(r, lineDelta));
2647
+ }
2648
+ }
2649
+ functionResults.push(cached);
2650
+ continue;
2651
+ }
2652
+ }
799
2653
  if (isPure) {
800
2654
  const fnVal = globalEnv.has(fn.name) ? globalEnv.lookup(fn.name) : null;
801
2655
  if (fnVal && fnVal.kind === "function") {
@@ -808,37 +2662,114 @@ function analyzeFile(filePath, source, activeCases, externalCallRecords) {
808
2662
  } else {
809
2663
  setSampleCount(3);
810
2664
  }
811
- const caseDirectives = fn.directives.filter((d) => d.kind === "case");
812
- const activeCaseIdx = activeCases?.get(fn.name) ?? 0;
813
2665
  if (caseDirectives.length === 0) {
814
2666
  synthCandidates.push({ name: fn.name, node: fn.node, analysis });
815
2667
  }
816
2668
  for (let ci = 0; ci < caseDirectives.length; ci++) {
817
2669
  const directive = caseDirectives[ci];
818
2670
  resetUnreachableRanges();
819
- const fullResult = evaluateFunctionFull(fn.node, directive.args, globalEnv);
820
- const caseUnreachable = [...getUnreachableRanges()];
821
- analysis.cases.push({
2671
+ let caseValue;
2672
+ let caseAbs;
2673
+ let caseThrows;
2674
+ let fullResult = null;
2675
+ let caseUnreachable = [];
2676
+ const bCapable2 = isBPathCapable(source, envNames);
2677
+ const bPrimary = bCapable2;
2678
+ if (bCapable2 && filePath) {
2679
+ const bFull = tryBPathCallFull(
2680
+ source,
2681
+ filePath,
2682
+ fn.name,
2683
+ directive.args.map((a) => typeValueToAbs5(a)),
2684
+ { collectCalls: true, envNames, mocks: mockSeedsToAbsMocks(seeds) }
2685
+ );
2686
+ const res = bFull?.result;
2687
+ const weakUnknown = !!res && res.shape.k === "unknown" && (!res.term || res.term.op === "lit" && res.term.value === void 0);
2688
+ const bOk = !!res && (bHostedEval || !weakUnknown && res.conf !== "opaque");
2689
+ if (bOk && bFull && bPrimary) {
2690
+ caseAbs = bFull.result;
2691
+ caseValue = absToTypeValue2(bFull.result);
2692
+ caseThrows = bFull.throws.shape.k !== "never" ? absToTypeValue2(bFull.throws) : T2.never;
2693
+ fullResult = { value: caseValue, throws: caseThrows };
2694
+ for (const d of bFull.memberDiags ?? []) {
2695
+ pushBMemberDiag(d, fnLoc.start.line);
2696
+ }
2697
+ if (bFull.calls?.length) {
2698
+ const impMap = buildAbsImportLocalMap(source, filePath);
2699
+ const safeAbs = (a) => {
2700
+ if (!a || typeof a !== "object" || !("shape" in a) || !a.shape) {
2701
+ return T2.unknown;
2702
+ }
2703
+ try {
2704
+ return absToTypeValue2(a);
2705
+ } catch {
2706
+ return T2.unknown;
2707
+ }
2708
+ };
2709
+ for (const c of bFull.calls) {
2710
+ const rec = {
2711
+ fnName: c.fnName,
2712
+ argTypes: c.args.map(safeAbs),
2713
+ resultType: c.threw ? T2.never : safeAbs(c.result),
2714
+ throws: c.threw ? safeAbs(c.result) : T2.never,
2715
+ callLoc: c.callLoc
2716
+ };
2717
+ const imp = impMap.get(c.fnName);
2718
+ if (imp) {
2719
+ rec.targetModule = imp.modulePath;
2720
+ rec.targetExport = imp.exportName;
2721
+ }
2722
+ callRecords.push(rec);
2723
+ }
2724
+ }
2725
+ }
2726
+ }
2727
+ if (!fullResult) {
2728
+ if (bHostedEval) {
2729
+ fullResult = { value: T2.unknown, throws: T2.never };
2730
+ caseValue = T2.unknown;
2731
+ } else {
2732
+ fullResult = evaluateFunctionFull(fn.node, directive.args, globalEnv);
2733
+ caseUnreachable = [...getUnreachableRanges()];
2734
+ caseValue = fullResult.value;
2735
+ if ((selfContained || canAbsModules) && fullResult.value.kind !== "never") {
2736
+ caseAbs = tryEvalAbsRaw(source, fn.name, directive.args, filePath, mockSeedsToAbsMocks(seeds));
2737
+ if (caseAbs) {
2738
+ const projected = absToTypeValue2(caseAbs);
2739
+ if (absIsBetter(projected, fullResult.value)) caseValue = projected;
2740
+ }
2741
+ }
2742
+ }
2743
+ }
2744
+ const tv = caseValue;
2745
+ const throwsTv = caseThrows ?? fullResult.throws;
2746
+ const caseEntry = {
822
2747
  name: directive.name,
823
2748
  args: directive.args,
824
- result: fullResult.value,
825
- throws: fullResult.throws,
826
- throwLoc: fullResult.throwLoc
827
- });
2749
+ result: tv,
2750
+ throws: throwsTv,
2751
+ throwLoc: fullResult.throwLoc,
2752
+ expected: directive.expected,
2753
+ source: "directive"
2754
+ };
2755
+ tryAttachIntension(caseEntry, source, fn.name);
2756
+ if (caseAbs) attachAbsToIntension(caseEntry, caseAbs, fn.name);
2757
+ analysis.cases.push(caseEntry);
828
2758
  if (directive.commentLine) {
829
- const hasThrow = fullResult.throws.kind !== "never";
830
- const resultStr = fullResult.value.kind !== "never" ? typeValueToString(fullResult.value) : "";
831
- const throwStr = hasThrow ? `throws ${typeValueToString(fullResult.throws)}` : "";
2759
+ const hasThrow = throwsTv.kind !== "never";
2760
+ const resultStr = tv.kind !== "never" ? typeValueToString3(tv) : "";
2761
+ const throwStr = hasThrow ? `throws ${typeValueToString3(throwsTv)}` : "";
832
2762
  const label = [resultStr, throwStr].filter(Boolean).join(" ");
833
2763
  const hintLabel = `=> ${label}`;
834
2764
  let ok = true;
835
2765
  if (directive.expected) {
836
- ok = isSubtypeOf(fullResult.value, directive.expected);
2766
+ ok = isSubtypeOf(tv, directive.expected);
837
2767
  if (!ok) {
838
2768
  diagnostics.push({
839
2769
  range: { start: { line: directive.commentLine, column: 0 }, end: { line: directive.commentLine, column: 999 } },
840
2770
  severity: "error",
841
- message: `Case "${directive.name}": expected ${typeValueToString(directive.expected)}, got ${typeValueToString(fullResult.value)}. The inferred return type does not match the expected type declared in the @nudo:case directive`
2771
+ message: `Case "${directive.name}": expected ${typeValueToString3(directive.expected)}, got ${typeValueToString3(tv)}. The inferred return type does not match the expected type declared in the @nudo:case directive`,
2772
+ code: "nudo:case-expected"
842
2773
  });
843
2774
  }
844
2775
  }
@@ -846,24 +2777,26 @@ function analyzeFile(filePath, source, activeCases, externalCallRecords) {
846
2777
  }
847
2778
  const isActive = ci === Math.min(activeCaseIdx, caseDirectives.length - 1);
848
2779
  if (isActive) {
849
- if (fullResult.throws.kind !== "never") {
850
- const throwRange = fullResult.throwLoc ?? fnLoc;
2780
+ if (throwsTv.kind !== "never") {
2781
+ const throwRange = fullResult?.throwLoc ?? fnLoc;
851
2782
  diagnostics.push({
852
2783
  range: throwRange,
853
2784
  severity: "warning",
854
- message: `Function "${fn.name}" case "${directive.name}" may throw: ${typeValueToString(fullResult.throws)}. Consider adding a try-catch block or using @nudo:returns to declare expected behavior`,
2785
+ message: `Function "${fn.name}" case "${directive.name}" may throw: ${typeValueToString3(throwsTv)}. Consider adding a try-catch block or using @nudo:refine return <constraint>`,
855
2786
  code: "nudo-may-throw"
856
2787
  });
857
2788
  }
858
- for (const ur of caseUnreachable) {
859
- diagnostics.push({
860
- range: ur,
861
- severity: "info",
862
- message: "Code after return/throw statement is unreachable",
863
- tags: ["unnecessary"],
864
- code: "nudo-unreachable",
865
- suggestions: ["Remove the unreachable code after the return/throw statement"]
866
- });
2789
+ if (!isBPathCapable(source, envNames)) {
2790
+ for (const ur of caseUnreachable) {
2791
+ diagnostics.push({
2792
+ range: ur,
2793
+ severity: "info",
2794
+ message: "Code after return/throw statement is unreachable",
2795
+ tags: ["unnecessary"],
2796
+ code: "nudo-unreachable",
2797
+ suggestions: ["Remove the unreachable code after the return/throw statement"]
2798
+ });
2799
+ }
867
2800
  }
868
2801
  }
869
2802
  }
@@ -872,26 +2805,13 @@ function analyzeFile(filePath, source, activeCases, externalCallRecords) {
872
2805
  } else if (analysis.cases.length === 1) {
873
2806
  analysis.combined = analysis.cases[0].result;
874
2807
  }
875
- if (returnsDirective && returnsDirective.kind === "returns") {
876
- analysis.assertionErrors = [];
877
- for (const directive of caseDirectives) {
878
- const result = evaluateFunction(fn.node, directive.args, globalEnv);
879
- const matches = isSubtypeOf(result, returnsDirective.expected);
880
- if (!matches) {
881
- const msg = `@nudo:returns assertion failed for case "${directive.name}": expected ${typeValueToString(returnsDirective.expected)}, got ${typeValueToString(result)}. Update the @nudo:returns directive to match the inferred type, or fix the function implementation`;
882
- analysis.assertionErrors.push(msg);
883
- diagnostics.push({
884
- range: fnLoc,
885
- severity: "error",
886
- message: msg,
887
- code: "nudo-assertion-failed",
888
- suggestions: [
889
- "Update @nudo:returns to match the inferred type",
890
- "Fix the function to return the expected type"
891
- ]
892
- });
893
- }
894
- }
2808
+ if (fnCacheKey) {
2809
+ fnAnalysisCacheSet(fnCacheKey, {
2810
+ analysis: cloneFunctionAnalysis(analysis),
2811
+ diagnostics: diagnostics.slice(dLen0),
2812
+ caseHints: caseHints.slice(hLen0),
2813
+ callRecords: callRecords.slice(cLen0)
2814
+ });
895
2815
  }
896
2816
  functionResults.push(analysis);
897
2817
  }
@@ -918,7 +2838,7 @@ function analyzeFile(filePath, source, activeCases, externalCallRecords) {
918
2838
  }
919
2839
  return n;
920
2840
  };
921
- const currentModulePath = normalizeModulePath(resolve(filePath));
2841
+ const currentModulePath = normalizeModulePath(resolve4(filePath));
922
2842
  const singleExportFn = findSingleModuleExportsFunction(ast);
923
2843
  for (const candidate of synthCandidates) {
924
2844
  const singleExportHit = singleExportFn !== null && candidate.node === singleExportFn;
@@ -948,13 +2868,25 @@ function analyzeFile(filePath, source, activeCases, externalCallRecords) {
948
2868
  const ordered = records.map((r, i) => ({ r, i })).sort((a, b) => informativeness(a.r) - informativeness(b.r) || a.i - b.i).map(({ r }) => r);
949
2869
  const precise = ordered.slice(0, MAX_PRECISE_CALLSITE_CASES);
950
2870
  for (const rec of precise) {
951
- candidate.analysis.cases.push({
2871
+ let absRaw;
2872
+ let absResult;
2873
+ if (rec.resultType.kind !== "never" && rec.argTypes.length > 0) {
2874
+ absRaw = tryEvalAbsRaw(source, candidate.name, rec.argTypes, filePath, mockSeedsToAbsMocks(seeds));
2875
+ if (absRaw) {
2876
+ const projected = absToTypeValue2(absRaw);
2877
+ if (absIsBetter(projected, rec.resultType)) absResult = projected;
2878
+ }
2879
+ }
2880
+ const caseResult2 = {
952
2881
  name: `call@L${rec.callLoc?.line ?? candidate.analysis.loc.start.line}`,
953
2882
  args: rec.argTypes,
954
- result: rec.resultType,
2883
+ result: absResult ?? rec.resultType,
955
2884
  throws: rec.throws,
956
2885
  source: "callsite"
957
- });
2886
+ };
2887
+ tryAttachIntension(caseResult2, source, candidate.name);
2888
+ if (absRaw) attachAbsToIntension(caseResult2, absRaw, candidate.name);
2889
+ candidate.analysis.cases.push(caseResult2);
958
2890
  }
959
2891
  const remaining = ordered.slice(MAX_PRECISE_CALLSITE_CASES).filter((rec) => !rec.argTypes.some((a) => a.kind === "unknown"));
960
2892
  if (remaining.length > 0) {
@@ -965,19 +2897,50 @@ function analyzeFile(filePath, source, activeCases, externalCallRecords) {
965
2897
  (_, i) => (
966
2898
  // 缺参按真实 JS 语义 widen 成 undefined 而非 unknown——可选参守卫
967
2899
  // (target || [] 等)对 unknown 全塌,对 undefined 正常走默认分支
968
- widenType(simplifyUnion(remaining.map((rec) => rec.argTypes[i] ?? T.undefined)))
2900
+ widenType(simplifyUnion(remaining.map((rec) => rec.argTypes[i] ?? T2.undefined)))
969
2901
  )
970
2902
  );
971
- const full2 = evaluateFunctionFull(fnNode2, widenedArgs, globalEnv);
972
- candidate.analysis.cases.push({
2903
+ const absSymRaw = tryEvalAbs(source, candidate.name, widenedArgs, filePath, mockSeedsToAbsMocks(seeds));
2904
+ const absSym = absSymRaw && absIsBetter(
2905
+ absSymRaw,
2906
+ /* 无先验:仅 unknown 时 */
2907
+ { kind: "unknown" }
2908
+ ) ? absSymRaw : void 0;
2909
+ let symValue;
2910
+ let symThrows = T2.never;
2911
+ let symLoc;
2912
+ let symAbs;
2913
+ if (isBPathCapable(source, envNames) && filePath) {
2914
+ const bSym = tryBPathCall(
2915
+ source,
2916
+ filePath,
2917
+ candidate.name,
2918
+ widenedArgs.map((a) => typeValueToAbs5(a)),
2919
+ { envNames, mocks: mockSeedsToAbsMocks(seeds) }
2920
+ );
2921
+ if (bSym && (bHostedEval || !(bSym.shape.k === "unknown" && !bSym.term))) {
2922
+ symAbs = bSym;
2923
+ symValue = absToTypeValue2(bSym);
2924
+ }
2925
+ }
2926
+ if (!symAbs && !bHostedEval) {
2927
+ const full = evaluateFunctionFull(fnNode2, widenedArgs, globalEnv);
2928
+ symValue = full.value.kind === "unknown" && absSym ? absSym : full.value;
2929
+ symThrows = full.throws;
2930
+ symLoc = full.throwLoc;
2931
+ }
2932
+ const symCase = {
973
2933
  name: "call@symbolic",
974
2934
  args: widenedArgs,
975
- result: full2.value,
976
- throws: full2.throws,
977
- throwLoc: full2.throwLoc,
2935
+ result: symValue ?? T2.unknown,
2936
+ throws: symThrows,
2937
+ throwLoc: symLoc,
978
2938
  source: "callsite",
979
2939
  aggregatedFrom: remaining.length
980
- });
2940
+ };
2941
+ tryAttachIntension(symCase, source, candidate.name);
2942
+ if (symAbs) attachAbsToIntension(symCase, symAbs, candidate.name);
2943
+ candidate.analysis.cases.push(symCase);
981
2944
  }
982
2945
  candidate.analysis.combined = collapseLiteralUnion(
983
2946
  simplifyUnion(records.map((r) => r.resultType)),
@@ -985,20 +2948,59 @@ function analyzeFile(filePath, source, activeCases, externalCallRecords) {
985
2948
  );
986
2949
  continue;
987
2950
  }
988
- const fnNode = resolveFunctionNode(candidate.node);
989
- const args = extractParamNames(fnNode).map(() => T.unknown);
990
- const full = evaluateFunctionFull(fnNode, args, globalEnv);
991
- candidate.analysis.cases.push({
2951
+ const fnNode = resolveFunctionNode(candidate.node);
2952
+ const args = extractParamNames(fnNode).map(() => T2.unknown);
2953
+ let entryValue;
2954
+ let entryThrows = T2.never;
2955
+ let entryLoc;
2956
+ let entryAbs;
2957
+ if (isBPathCapable(source, envNames) && filePath) {
2958
+ const bEntryFull = tryBPathCallFull(
2959
+ source,
2960
+ filePath,
2961
+ candidate.analysis.name,
2962
+ args.map((a) => typeValueToAbs5(a)),
2963
+ { envNames, mocks: mockSeedsToAbsMocks(seeds), collectMemberDiags: true }
2964
+ );
2965
+ if (bEntryFull?.memberDiags?.length) {
2966
+ for (const d of bEntryFull.memberDiags) {
2967
+ pushBMemberDiag(d, candidate.analysis.loc.start.line);
2968
+ }
2969
+ }
2970
+ const bEntry = bEntryFull?.result;
2971
+ if (bEntry && (bHostedEval || !(bEntry.shape.k === "unknown" && !bEntry.term))) {
2972
+ entryAbs = bEntry;
2973
+ entryValue = absToTypeValue2(bEntry);
2974
+ }
2975
+ }
2976
+ if (!entryValue) {
2977
+ if (bHostedEval) {
2978
+ entryValue = T2.unknown;
2979
+ entryThrows = T2.never;
2980
+ } else {
2981
+ const absEntry = tryEvalEntryAbs(source, candidate.analysis.name, args, filePath, seeds.seedVars);
2982
+ const full = absEntry ? { value: absEntry, throws: T2.never, throwLoc: void 0 } : evaluateFunctionFull(fnNode, args, globalEnv);
2983
+ entryValue = full.value;
2984
+ entryThrows = full.throws;
2985
+ entryLoc = full.throwLoc;
2986
+ }
2987
+ }
2988
+ const caseResult = {
992
2989
  name: `entry@L${candidate.analysis.loc.start.line}`,
993
2990
  args,
994
- result: full.value,
995
- throws: full.throws,
996
- throwLoc: full.throwLoc
997
- });
2991
+ result: entryValue,
2992
+ throws: entryThrows,
2993
+ throwLoc: entryLoc
2994
+ };
2995
+ tryAttachIntension(caseResult, source, candidate.analysis.name);
2996
+ if (entryAbs) attachAbsToIntension(caseResult, entryAbs, candidate.analysis.name);
2997
+ candidate.analysis.cases.push(caseResult);
998
2998
  candidate.analysis.entryOnly = true;
999
- candidate.analysis.combined = collapseLiteralUnion(full.value, COLLAPSE_LITERAL_THRESHOLD);
2999
+ candidate.analysis.combined = collapseLiteralUnion(entryValue, COLLAPSE_LITERAL_THRESHOLD);
3000
+ }
3001
+ if (!bHostedEval) {
3002
+ buildNodeTypeMap(ast, globalEnv, nodeTypeMap);
1000
3003
  }
1001
- buildNodeTypeMap(ast, globalEnv, nodeTypeMap);
1002
3004
  setUnknownBuiltinHandler(null);
1003
3005
  setCallCollector(null);
1004
3006
  setUnknownCollector(null);
@@ -1009,9 +3011,29 @@ function analyzeFile(filePath, source, activeCases, externalCallRecords) {
1009
3011
  const maxLine = source.split("\n").length;
1010
3012
  diagnostics.push(
1011
3013
  ...unknownRecordsToDiagnostics(
1012
- unknownRecords.filter(
1013
- (r) => (r.loc?.line ?? 0) <= maxLine && r.originModule !== USAGE_SITE_MODULE
1014
- )
3014
+ unknownRecords.filter((r) => {
3015
+ if ((r.loc?.line ?? 0) > maxLine) return false;
3016
+ if (r.originModule === USAGE_SITE_MODULE) return false;
3017
+ if (bHostedEval && (r.kind === "method" || r.kind === "property")) {
3018
+ return false;
3019
+ }
3020
+ if ((r.kind === "method" || r.kind === "property") && bMemberDiagNames.has(r.name)) {
3021
+ return false;
3022
+ }
3023
+ if (r.kind === "global") {
3024
+ if (r.name.startsWith("module-cycle:") && bModuleIssueKinds.has("cycle")) return false;
3025
+ if (r.name.startsWith("module-depth:") && bModuleIssueKinds.has("depth")) return false;
3026
+ if (r.name.startsWith("module-missing:") && bModuleIssueKinds.has("missing")) return false;
3027
+ if (r.name.startsWith("recursion:")) {
3028
+ const fn = r.name.slice("recursion:".length);
3029
+ if (bTruncatedFns.has(fn) || bTruncatedFns.has(fn.split(".").pop() ?? fn)) {
3030
+ return false;
3031
+ }
3032
+ }
3033
+ if (bBuiltinUnknownNames.has(r.name)) return false;
3034
+ }
3035
+ return true;
3036
+ })
1015
3037
  )
1016
3038
  );
1017
3039
  const externalFunctions = synthesizeExternalFunctions(callRecords, filePath);
@@ -1028,19 +3050,19 @@ function collectBindings(ast, env, bindings) {
1028
3050
  if (ast.type !== "File") return;
1029
3051
  for (const stmt of ast.program.body) {
1030
3052
  if (stmt.type === "FunctionDeclaration" && stmt.id) {
1031
- const val = env.has(stmt.id.name) ? env.lookup(stmt.id.name) : T.unknown;
3053
+ const val = env.has(stmt.id.name) ? env.lookup(stmt.id.name) : T2.unknown;
1032
3054
  bindings.set(stmt.id.name, { type: val, loc: locFromNode(stmt) });
1033
3055
  }
1034
3056
  if (stmt.type === "VariableDeclaration") {
1035
3057
  for (const decl of stmt.declarations) {
1036
3058
  if (decl.id.type === "Identifier") {
1037
- const val = env.has(decl.id.name) ? env.lookup(decl.id.name) : T.unknown;
3059
+ const val = env.has(decl.id.name) ? env.lookup(decl.id.name) : T2.unknown;
1038
3060
  bindings.set(decl.id.name, { type: val, loc: locFromNode(decl) });
1039
3061
  }
1040
3062
  }
1041
3063
  }
1042
3064
  if (stmt.type === "ClassDeclaration" && stmt.id) {
1043
- const val = env.has(stmt.id.name) ? env.lookup(stmt.id.name) : T.unknown;
3065
+ const val = env.has(stmt.id.name) ? env.lookup(stmt.id.name) : T2.unknown;
1044
3066
  bindings.set(stmt.id.name, { type: val, loc: locFromNode(stmt) });
1045
3067
  }
1046
3068
  if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
@@ -1061,16 +3083,16 @@ function buildNodeTypeMap(ast, env, nodeTypeMap) {
1061
3083
  }
1062
3084
  }
1063
3085
  if (node.type === "NumericLiteral") {
1064
- nodeTypeMap.set(node, T.literal(node.value));
3086
+ nodeTypeMap.set(node, T2.literal(node.value));
1065
3087
  }
1066
3088
  if (node.type === "StringLiteral") {
1067
- nodeTypeMap.set(node, T.literal(node.value));
3089
+ nodeTypeMap.set(node, T2.literal(node.value));
1068
3090
  }
1069
3091
  if (node.type === "BooleanLiteral") {
1070
- nodeTypeMap.set(node, T.literal(node.value));
3092
+ nodeTypeMap.set(node, T2.literal(node.value));
1071
3093
  }
1072
3094
  if (node.type === "NullLiteral") {
1073
- nodeTypeMap.set(node, T.null);
3095
+ nodeTypeMap.set(node, T2.null);
1074
3096
  }
1075
3097
  } catch {
1076
3098
  }
@@ -1080,7 +3102,7 @@ function buildNodeTypeMap(ast, env, nodeTypeMap) {
1080
3102
  }
1081
3103
  }
1082
3104
  function getCasesForFile(filePath, source) {
1083
- const ast = parse(source);
3105
+ const ast = parse5(source);
1084
3106
  const functions = extractDirectives(ast);
1085
3107
  return functions.map((fn) => {
1086
3108
  const cases = fn.directives.filter((d) => d.kind === "case").map((d, i) => ({ name: d.name, index: i }));
@@ -1090,23 +3112,48 @@ function getCasesForFile(filePath, source) {
1090
3112
  async function getTypeAtPositionAsync(filePath, source, line, column, activeCases) {
1091
3113
  const envNames = collectEnvNames(filePath, source, false);
1092
3114
  if (envNames.length > 0) {
1093
- await preloadPathEnvs(envNames, dirname(filePath));
3115
+ await preloadPathEnvs(envNames, dirname5(filePath));
1094
3116
  }
1095
3117
  return getTypeAtPosition(filePath, source, line, column, activeCases);
1096
3118
  }
1097
3119
  function getTypeAtPosition(filePath, source, line, column, activeCases) {
1098
- const ast = parse(source);
3120
+ const ast = parse5(source);
3121
+ const fileDirectives = extractFileDirectives(ast);
3122
+ const envNames = fileDirectives.filter((d) => d.kind === "env").flatMap((d) => d.envs);
3123
+ if (isBPathCapable(source, envNames)) {
3124
+ try {
3125
+ const seeds = mockDirectivesToAbsSeeds(extractDirectives(ast));
3126
+ const { modules } = evalAbsModuleGraph(source, filePath);
3127
+ const absNodes = collectAbsNodeTypes(source, {
3128
+ ...seeds,
3129
+ modules,
3130
+ file: ast
3131
+ });
3132
+ const absAt = findAbsAtPosition(absNodes, line, column);
3133
+ if (absAt) return absToTypeValue2(absAt);
3134
+ const ident = findIdentNameAtPosition(source, line, column, ast);
3135
+ if (ident) {
3136
+ const binds = collectAbsBindingsFromGraph(source, filePath, {
3137
+ seedVars: seeds.seedVars,
3138
+ seedFns: seeds.seedFns
3139
+ });
3140
+ const bound = binds.get(ident);
3141
+ if (bound) return absToTypeValue2(bound);
3142
+ }
3143
+ } catch {
3144
+ }
3145
+ }
1099
3146
  resetMemo();
1100
3147
  resetEnvModules();
1101
3148
  setModuleResolver(resolveModule);
1102
- setCurrentFileDir(dirname(filePath));
3149
+ setCurrentFileDir(dirname5(filePath));
1103
3150
  setCurrentSource(source);
1104
- const fileDirectives = extractFileDirectives(ast);
1105
- const envNames = fileDirectives.filter((d) => d.kind === "env").flatMap((d) => d.envs);
1106
- const globalEnv = createEnvironment();
1107
- if (envNames.length > 0) {
1108
- const loaded = loadEnvs(envNames, globalEnv);
1109
- setEnvModules(loaded.modules);
3151
+ const globalEnv = createEnvironment2();
3152
+ {
3153
+ const loaded = envNames.length > 0 ? loadEnvs2(envNames, globalEnv) : { modules: {} };
3154
+ const auto = autoHarvestModules(source, dirname5(filePath));
3155
+ const modules = { ...loaded.modules, ...auto };
3156
+ if (Object.keys(modules).length > 0) setEnvModules(modules);
1110
3157
  }
1111
3158
  evaluateProgram(ast, globalEnv);
1112
3159
  const nodeTypeMap = /* @__PURE__ */ new Map();
@@ -1132,6 +3179,167 @@ function getTypeAtPosition(filePath, source, line, column, activeCases) {
1132
3179
  resetMockModules();
1133
3180
  return findBestTypeAtPosition(nodeTypeMap, globalEnv, ast, line, column);
1134
3181
  }
3182
+ function getHoverAtPosition(filePath, source, line, column, activeCases) {
3183
+ let file;
3184
+ try {
3185
+ file = parse5(source);
3186
+ } catch {
3187
+ file = void 0;
3188
+ }
3189
+ const envNames = collectEnvNames(filePath, source, false);
3190
+ const fnName = findFunctionNameAtPosition(source, line, column, file);
3191
+ let gDisplay;
3192
+ let gAbs;
3193
+ let gMulti;
3194
+ if (fnName) {
3195
+ try {
3196
+ const g = generalizeFromAst2(fnName, source, file ? { file } : {});
3197
+ if (g) {
3198
+ gDisplay = g.display;
3199
+ gAbs = formatAbs3(g.symbolic);
3200
+ gMulti = formatAbsMultiline(g.symbolic, fnName);
3201
+ }
3202
+ } catch {
3203
+ }
3204
+ }
3205
+ const attachIntension = (info2) => {
3206
+ if (!gDisplay) return info2;
3207
+ if (!info2) {
3208
+ return { typeText: gDisplay, intension: gDisplay, abs: gAbs, absMultiline: gMulti };
3209
+ }
3210
+ return {
3211
+ ...info2,
3212
+ intension: gDisplay,
3213
+ // 外延侧已有更准 Abs 时保留;否则用 symbolic 兜底
3214
+ abs: info2.abs ?? gAbs,
3215
+ absMultiline: info2.absMultiline ?? gMulti
3216
+ };
3217
+ };
3218
+ if (isBPathCapable(source, envNames)) {
3219
+ try {
3220
+ const seeds = mockDirectivesToAbsSeeds(extractDirectives(file ?? parse5(source)));
3221
+ const { modules } = evalAbsModuleGraph(source, filePath);
3222
+ const absNodes = collectAbsNodeTypes(source, {
3223
+ ...seeds,
3224
+ modules,
3225
+ ...file ? { file } : {}
3226
+ });
3227
+ const absAt = findAbsAtPosition(absNodes, line, column);
3228
+ const ident2 = findIdentNameAtPosition(source, line, column, file);
3229
+ if (ident2 && !fnName) {
3230
+ const binds = collectAbsBindingsFromGraph(source, filePath, {
3231
+ seedVars: seeds.seedVars,
3232
+ seedFns: seeds.seedFns
3233
+ });
3234
+ const bound = binds.get(ident2);
3235
+ if (bound) {
3236
+ const absLine = formatAbs3(bound);
3237
+ const absMulti = formatAbsMultiline(bound, ident2);
3238
+ return attachIntension({ typeText: absLine, abs: absLine, absMultiline: absMulti });
3239
+ }
3240
+ }
3241
+ if (absAt) {
3242
+ const absLine = formatAbs3(absAt);
3243
+ const absMulti = formatAbsMultiline(absAt, void 0);
3244
+ return attachIntension({ typeText: absLine, abs: absLine, absMultiline: absMulti });
3245
+ }
3246
+ } catch {
3247
+ }
3248
+ }
3249
+ const tv = getTypeAtPosition(filePath, source, line, column, activeCases);
3250
+ const info = tv ? { typeText: typeValueToString3(tv) } : null;
3251
+ const ident = findIdentNameAtPosition(source, line, column, file);
3252
+ if (ident && !fnName) {
3253
+ try {
3254
+ if (isBPathCapable(source, []) || !/\brequire\s*\(/.test(source)) {
3255
+ const seeds = mockDirectivesToAbsSeeds(extractDirectives(file ?? parse5(source)));
3256
+ const absBinds = collectAbsBindingsFromGraph(source, filePath, {
3257
+ seedVars: seeds.seedVars,
3258
+ seedFns: seeds.seedFns
3259
+ });
3260
+ const absBound = absBinds.get(ident);
3261
+ if (absBound) {
3262
+ const absLine = formatAbs3(absBound);
3263
+ const absMulti = formatAbsMultiline(absBound, ident);
3264
+ if (info) {
3265
+ info.abs = absLine;
3266
+ info.absMultiline = absMulti;
3267
+ return attachIntension(info);
3268
+ }
3269
+ return attachIntension({ typeText: absLine, abs: absLine, absMultiline: absMulti });
3270
+ }
3271
+ }
3272
+ } catch {
3273
+ }
3274
+ }
3275
+ try {
3276
+ if (isBPathCapable(source, []) || !/\brequire\s*\(|\bimport\s*[{'"*]/.test(source)) {
3277
+ const seeds = mockDirectivesToAbsSeeds(
3278
+ extractDirectives(file ?? parse5(source))
3279
+ );
3280
+ const { modules } = evalAbsModuleGraph(source, filePath);
3281
+ const nodeTypes = collectAbsNodeTypes(source, {
3282
+ ...seeds,
3283
+ modules,
3284
+ ...file ? { file } : {}
3285
+ });
3286
+ const absAt = findAbsAtPosition(nodeTypes, line, column);
3287
+ if (absAt) {
3288
+ const absLine = formatAbs3(absAt);
3289
+ const absMulti = formatAbsMultiline(absAt, void 0);
3290
+ if (info) {
3291
+ info.abs = absLine;
3292
+ info.absMultiline = absMulti;
3293
+ } else {
3294
+ return attachIntension({ typeText: absLine, abs: absLine, absMultiline: absMulti });
3295
+ }
3296
+ }
3297
+ }
3298
+ } catch {
3299
+ }
3300
+ return attachIntension(info);
3301
+ }
3302
+ function findIdentNameAtPosition(source, line, column, fileAst) {
3303
+ try {
3304
+ const ast = fileAst ?? parse5(source);
3305
+ let found;
3306
+ traverse(ast, {
3307
+ Identifier(path) {
3308
+ const loc = path.node.loc;
3309
+ if (!loc) return;
3310
+ if (loc.start.line !== line) return;
3311
+ if (column < loc.start.column || column > loc.end.column) return;
3312
+ found = path.node.name;
3313
+ }
3314
+ });
3315
+ return found;
3316
+ } catch {
3317
+ return void 0;
3318
+ }
3319
+ }
3320
+ function findFunctionNameAtPosition(source, line, column, fileAst) {
3321
+ try {
3322
+ const ast = fileAst ?? parse5(source);
3323
+ let found;
3324
+ traverse(ast, {
3325
+ Identifier(path) {
3326
+ const loc = path.node.loc;
3327
+ if (!loc) return;
3328
+ if (loc.start.line !== line) return;
3329
+ if (column < loc.start.column || column > loc.end.column) return;
3330
+ const parent = path.parent;
3331
+ if (parent.type === "FunctionDeclaration" && parent.id === path.node) {
3332
+ found = path.node.name;
3333
+ } else if (parent.type === "CallExpression" && parent.callee === path.node) {
3334
+ found = path.node.name;
3335
+ }
3336
+ }
3337
+ });
3338
+ return found;
3339
+ } catch {
3340
+ return void 0;
3341
+ }
3342
+ }
1135
3343
  function findEnclosingFunction(functions, line) {
1136
3344
  for (const fn of functions) {
1137
3345
  const loc = fn.node.loc;
@@ -1190,18 +3398,18 @@ function getCompletionsAtPosition(filePath, source, line, column) {
1190
3398
  const safeSource = sanitizeSourceForParsing(source);
1191
3399
  let ast;
1192
3400
  try {
1193
- ast = parse(safeSource);
3401
+ ast = parse5(safeSource);
1194
3402
  } catch {
1195
3403
  try {
1196
- ast = parse(source);
3404
+ ast = parse5(source);
1197
3405
  } catch {
1198
3406
  return [];
1199
3407
  }
1200
3408
  }
1201
3409
  resetMemo();
1202
3410
  setModuleResolver(resolveModule);
1203
- setCurrentFileDir(dirname(filePath));
1204
- const globalEnv = createEnvironment();
3411
+ setCurrentFileDir(dirname5(filePath));
3412
+ const globalEnv = createEnvironment2();
1205
3413
  evaluateProgram(ast, globalEnv);
1206
3414
  if (!globalEnv.has(objName)) {
1207
3415
  setModuleResolver(null);
@@ -1221,11 +3429,11 @@ function getTextBeforePosition(source, line, column) {
1221
3429
  return lines[line - 1].slice(0, column);
1222
3430
  }
1223
3431
  function getVariableCompletions(filePath, source) {
1224
- const ast = parse(source);
3432
+ const ast = parse5(source);
1225
3433
  resetMemo();
1226
3434
  setModuleResolver(resolveModule);
1227
- setCurrentFileDir(dirname(filePath));
1228
- const globalEnv = createEnvironment();
3435
+ setCurrentFileDir(dirname5(filePath));
3436
+ const globalEnv = createEnvironment2();
1229
3437
  evaluateProgram(ast, globalEnv);
1230
3438
  const ownBindings = globalEnv.getOwnBindings();
1231
3439
  const completions = [];
@@ -1234,12 +3442,97 @@ function getVariableCompletions(filePath, source) {
1234
3442
  completions.push({
1235
3443
  label: name,
1236
3444
  kind: tv.kind === "function" ? "method" : "variable",
1237
- detail: typeValueToString(tv)
3445
+ detail: typeValueToString3(tv)
1238
3446
  });
1239
3447
  }
1240
3448
  setModuleResolver(null);
1241
3449
  return completions;
1242
3450
  }
3451
+ function builtinMemberType(memberExpr) {
3452
+ try {
3453
+ const result = evaluate(parse5(`${memberExpr};`), createEnvironment2());
3454
+ if (!result || typeof result !== "object" || !("kind" in result)) return null;
3455
+ return result;
3456
+ } catch {
3457
+ return null;
3458
+ }
3459
+ }
3460
+ function builtinProtoMembers(className) {
3461
+ const table = BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS[className];
3462
+ return table ? Object.keys(table) : [];
3463
+ }
3464
+ function describeMember(label, tv, fallbackClass) {
3465
+ if (tv) {
3466
+ const sig = getFnSig3(tv);
3467
+ if (sig) {
3468
+ const paramNames = tv.kind === "function" ? tv.params : [];
3469
+ const params = sig.paramTypes.map((p, i) => `${paramNames[i] ?? `arg${i}`}: ${typeValueToString3(p)}`).join(", ");
3470
+ return `(${params}) => ${typeValueToString3(sig.returnType)}`;
3471
+ }
3472
+ if (tv.kind !== "function") return typeValueToString3(tv);
3473
+ }
3474
+ return `${label}(\u2026)@${fallbackClass}`;
3475
+ }
3476
+ function getArrayCompletions(tv) {
3477
+ const completions = [];
3478
+ for (const m of builtinProtoMembers("Array")) {
3479
+ const detail = describeMember(m, builtinMemberType(`Array.prototype.${m}`), "Array");
3480
+ completions.push({ label: m, kind: "method", detail });
3481
+ }
3482
+ completions.push({
3483
+ label: "length",
3484
+ kind: "property",
3485
+ // tuple 长度是精确字面量;array 是 number(保持原展示语义)
3486
+ detail: tv.kind === "tuple" ? `${tv.elements.length}` : "number"
3487
+ });
3488
+ return completions;
3489
+ }
3490
+ function getPromiseCompletions() {
3491
+ return builtinProtoMembers("Promise").map((m) => ({
3492
+ label: m,
3493
+ kind: "method",
3494
+ detail: describeMember(m, builtinMemberType(`Promise.prototype.${m}`), "Promise")
3495
+ }));
3496
+ }
3497
+ function getStringCompletions() {
3498
+ const completions = [];
3499
+ for (const m of builtinProtoMembers("String")) {
3500
+ completions.push({
3501
+ label: m,
3502
+ kind: "method",
3503
+ detail: describeMember(m, builtinMemberType(`"s".${m}`), "String")
3504
+ });
3505
+ }
3506
+ completions.push({ label: "length", kind: "property", detail: "number" });
3507
+ return completions;
3508
+ }
3509
+ function getUnionCompletions(tv) {
3510
+ const members = tv.members;
3511
+ if (members.length === 0) return [];
3512
+ const labelsByMember = members.map((m) => getCompletionsForType(m));
3513
+ const baseIdx = labelsByMember.findIndex((labels) => labels.length > 0);
3514
+ if (baseIdx === -1) return [];
3515
+ const common = [];
3516
+ for (const base of labelsByMember[baseIdx]) {
3517
+ let allPresent = true;
3518
+ const memberTypes = [base.detail ?? base.label];
3519
+ for (let i = 0; i < members.length; i++) {
3520
+ if (i === baseIdx) continue;
3521
+ const labels = labelsByMember[i];
3522
+ if (labels.length === 0) continue;
3523
+ const hit = labels.find((l) => l.label === base.label);
3524
+ if (!hit) {
3525
+ allPresent = false;
3526
+ break;
3527
+ }
3528
+ memberTypes.push(hit.detail ?? hit.label);
3529
+ }
3530
+ if (allPresent) {
3531
+ common.push({ ...base, detail: memberTypes.join(" | ") });
3532
+ }
3533
+ }
3534
+ return common;
3535
+ }
1243
3536
  function getCompletionsForType(tv) {
1244
3537
  const completions = [];
1245
3538
  if (tv.kind === "object") {
@@ -1247,7 +3540,7 @@ function getCompletionsForType(tv) {
1247
3540
  completions.push({
1248
3541
  label: key,
1249
3542
  kind: val.kind === "function" ? "method" : "property",
1250
- detail: typeValueToString(val)
3543
+ detail: typeValueToString3(val)
1251
3544
  });
1252
3545
  }
1253
3546
  return completions;
@@ -1257,52 +3550,526 @@ function getCompletionsForType(tv) {
1257
3550
  completions.push({
1258
3551
  label: key,
1259
3552
  kind: val.kind === "function" ? "method" : "property",
1260
- detail: typeValueToString(val)
3553
+ detail: typeValueToString3(val)
1261
3554
  });
1262
3555
  }
1263
3556
  return completions;
1264
3557
  }
3558
+ if (tv.kind === "union") {
3559
+ return getUnionCompletions(tv);
3560
+ }
1265
3561
  if (tv.kind === "array" || tv.kind === "tuple") {
1266
- const arrayMethods = [
1267
- { label: "map", detail: "map(callback)" },
1268
- { label: "filter", detail: "filter(callback)" },
1269
- { label: "reduce", detail: "reduce(callback, init)" },
1270
- { label: "find", detail: "find(callback)" },
1271
- { label: "some", detail: "some(callback)" },
1272
- { label: "every", detail: "every(callback)" },
1273
- { label: "forEach", detail: "forEach(callback)" },
1274
- { label: "flatMap", detail: "flatMap(callback)" },
1275
- { label: "includes", detail: "includes(value)" },
1276
- { label: "indexOf", detail: "indexOf(value)" },
1277
- { label: "join", detail: "join(separator)" },
1278
- { label: "slice", detail: "slice(start, end)" },
1279
- { label: "concat", detail: "concat(other)" },
1280
- { label: "push", detail: "push(value)" },
1281
- { label: "length", detail: tv.kind === "tuple" ? `${tv.elements.length}` : "number" }
1282
- ];
1283
- for (const m of arrayMethods) {
1284
- completions.push({ label: m.label, kind: "method", detail: m.detail });
1285
- }
1286
- return completions;
3562
+ return getArrayCompletions(tv);
1287
3563
  }
1288
3564
  if (tv.kind === "promise") {
1289
- completions.push({ label: "then", kind: "method", detail: "then(callback)" });
1290
- completions.push({ label: "catch", kind: "method", detail: "catch(callback)" });
1291
- completions.push({ label: "finally", kind: "method", detail: "finally(callback)" });
1292
- return completions;
3565
+ return getPromiseCompletions();
1293
3566
  }
1294
3567
  if (tv.kind === "primitive" && tv.type === "string") {
1295
- const stringMethods = ["toUpperCase", "toLowerCase", "trim", "split", "slice", "includes", "indexOf", "replace", "startsWith", "endsWith", "charAt", "length"];
1296
- for (const m of stringMethods) {
1297
- completions.push({ label: m, kind: "method", detail: `string.${m}` });
1298
- }
1299
- return completions;
3568
+ return getStringCompletions();
1300
3569
  }
1301
3570
  return completions;
1302
3571
  }
3572
+ function absIsBetter(absTv, prev) {
3573
+ if (prev.kind === "unknown") return absTv.kind !== "unknown";
3574
+ if (absTv.kind === "unknown" || absTv.kind === "never") return false;
3575
+ if (prev.kind === "literal" || prev.kind === "object" || prev.kind === "array" || prev.kind === "tuple" || prev.kind === "union" || prev.kind === "function" || prev.kind === "instance" || prev.kind === "promise") {
3576
+ return false;
3577
+ }
3578
+ if (absTv.kind === "refined" && prev.kind === "primitive") return true;
3579
+ return false;
3580
+ }
3581
+ function isSelfContainedSource(source, envNames) {
3582
+ if (envNames.length > 0) return false;
3583
+ return !/\brequire\s*\(|\bimport\s*[{'"*]/.test(source);
3584
+ }
3585
+ function absModulesOk(source, envNames) {
3586
+ void source;
3587
+ void envNames;
3588
+ return true;
3589
+ }
3590
+ function collectAbsCallRecords(source, seeds, filePath, precomputedModules, envNames = []) {
3591
+ const absCalls = [];
3592
+ let modules = precomputedModules;
3593
+ let importLocals = /* @__PURE__ */ new Map();
3594
+ if (filePath && absModulesOk(source, envNames)) {
3595
+ if (!modules) {
3596
+ try {
3597
+ const graph = evalAbsModuleGraph(source, filePath, {
3598
+ seedVars: seeds?.seedVars,
3599
+ seedFns: seeds?.seedFns
3600
+ });
3601
+ modules = graph.modules;
3602
+ } catch {
3603
+ modules = void 0;
3604
+ }
3605
+ }
3606
+ if (envNames.length > 0) {
3607
+ modules = { ...collectEnvModules(envNames), ...modules ?? {} };
3608
+ }
3609
+ try {
3610
+ importLocals = buildAbsImportLocalMap(source, filePath);
3611
+ } catch {
3612
+ importLocals = /* @__PURE__ */ new Map();
3613
+ }
3614
+ }
3615
+ setAbsCallCollector((r) => absCalls.push(r));
3616
+ try {
3617
+ evalProgramAbs2(source, { ...seeds, modules });
3618
+ } catch {
3619
+ } finally {
3620
+ setAbsCallCollector(null);
3621
+ }
3622
+ return absCalls.map((r) => {
3623
+ const rec = {
3624
+ fnName: r.fnName,
3625
+ argTypes: r.args.map((a) => absToTypeValue2(a)),
3626
+ resultType: r.threw ? T2.never : absToTypeValue2(r.result),
3627
+ throws: r.threw ? absToTypeValue2(r.result) : T2.never,
3628
+ callLoc: r.callLoc
3629
+ };
3630
+ const imp = importLocals.get(r.fnName);
3631
+ if (imp) {
3632
+ rec.targetModule = imp.modulePath;
3633
+ rec.targetExport = imp.exportName;
3634
+ }
3635
+ return rec;
3636
+ });
3637
+ }
3638
+ function collectAbsBindsAndNodes(source, seeds, modules) {
3639
+ const binds = /* @__PURE__ */ new Map();
3640
+ const nodes = /* @__PURE__ */ new Map();
3641
+ setAbsNodeCollector((node, value) => {
3642
+ nodes.set(node, value);
3643
+ });
3644
+ try {
3645
+ const { env } = evalProgramAbs2(source, {
3646
+ ...seeds,
3647
+ modules
3648
+ });
3649
+ for (const [k, v] of env.vars) binds.set(k, v);
3650
+ for (const [name, impl] of env.fns) {
3651
+ if (!binds.has(name)) {
3652
+ binds.set(
3653
+ name,
3654
+ absFunction5(impl.params, { body: impl.body, async: impl.async, env })
3655
+ );
3656
+ }
3657
+ }
3658
+ } catch {
3659
+ } finally {
3660
+ setAbsNodeCollector(null);
3661
+ }
3662
+ return { binds, nodes };
3663
+ }
3664
+ function buildAbsImportLocalMap(source, fromFile) {
3665
+ const out = /* @__PURE__ */ new Map();
3666
+ try {
3667
+ const file = parse5(source);
3668
+ for (const stmt of file.program.body) {
3669
+ if (stmt.type !== "ImportDeclaration") continue;
3670
+ const spec = stmt.source.value;
3671
+ let modulePath = null;
3672
+ if (spec.startsWith(".") || spec.startsWith("/")) {
3673
+ modulePath = resolveImportAbs(spec, fromFile);
3674
+ } else if (!spec.startsWith("node:")) {
3675
+ modulePath = spec;
3676
+ }
3677
+ if (!modulePath) continue;
3678
+ for (const s of stmt.specifiers) {
3679
+ if (s.type === "ImportSpecifier") {
3680
+ const imported = s.imported.type === "Identifier" ? s.imported.name : String(s.imported);
3681
+ out.set(s.local.name, { modulePath, exportName: imported });
3682
+ } else if (s.type === "ImportDefaultSpecifier") {
3683
+ out.set(s.local.name, { modulePath, exportName: "default" });
3684
+ }
3685
+ }
3686
+ }
3687
+ } catch {
3688
+ }
3689
+ return out;
3690
+ }
3691
+ function resolveImportAbs(spec, fromFile) {
3692
+ const base = dirname5(resolve4(fromFile));
3693
+ const p = resolve4(base, spec);
3694
+ for (const cand of [p, `${p}.js`, `${p}.mjs`, `${p}.ts`, resolve4(p, "index.js")]) {
3695
+ if (existsSync3(cand) && !statSync3(cand).isDirectory()) return cand;
3696
+ }
3697
+ return null;
3698
+ }
3699
+ function tryEvalAbs(source, fnName, args, filePath, mocks) {
3700
+ const raw = tryEvalAbsRaw(source, fnName, args, filePath, mocks);
3701
+ return raw ? absToTypeValue2(raw) : void 0;
3702
+ }
3703
+ function tryEvalAbsRaw(source, fnName, args, filePath, mocks) {
3704
+ if (/\brequire\s*\(/.test(source)) return void 0;
3705
+ try {
3706
+ const absArgs = args.map((a) => typeValueToAbs5(a));
3707
+ if (filePath) {
3708
+ const viaB = tryBPathCall(source, filePath, fnName, absArgs, { mocks });
3709
+ if (viaB) return viaB;
3710
+ }
3711
+ let modules;
3712
+ if (filePath && /\bimport\s*[{'"*]/.test(source)) {
3713
+ modules = evalAbsModuleGraph(source, filePath).modules;
3714
+ }
3715
+ const result = analyzeFn(source, fnName, absArgs, void 0, void 0, void 0, modules);
3716
+ if (result.shape.k === "unknown" && !result.term) {
3717
+ return void 0;
3718
+ }
3719
+ return result;
3720
+ } catch {
3721
+ return void 0;
3722
+ }
3723
+ }
3724
+ function tryEvalEntryAbs(source, fnName, args, filePath, mocks) {
3725
+ return tryEvalAbs(source, fnName, args, filePath, mocks);
3726
+ }
3727
+ function tryAttachIntension(caseResult, source, fnName) {
3728
+ try {
3729
+ const g = generalizeFromAst2(fnName, source);
3730
+ if (!g) return;
3731
+ const sym = g.symbolic;
3732
+ const intension = {
3733
+ display: g.display,
3734
+ abs: formatAbs3(sym),
3735
+ absMultiline: formatAbsMultiline(sym, fnName)
3736
+ };
3737
+ if (sym.term && sym.term.op !== "lit") {
3738
+ intension.term = termToString(sym.term);
3739
+ }
3740
+ if (sym.pred && sym.pred.op !== "true") {
3741
+ intension.pred = predToString(sym.pred);
3742
+ }
3743
+ intension.conf = sym.conf;
3744
+ caseResult.intension = intension;
3745
+ } catch {
3746
+ }
3747
+ }
3748
+ function attachAbsToIntension(caseResult, absVal, label) {
3749
+ const prev = caseResult.intension ?? {};
3750
+ caseResult.abs = absVal;
3751
+ caseResult.intension = {
3752
+ ...prev,
3753
+ abs: formatAbs3(absVal),
3754
+ absMultiline: formatAbsMultiline(absVal, label ?? caseResult.name),
3755
+ conf: absVal.conf
3756
+ };
3757
+ }
3758
+
3759
+ // src/index.ts
3760
+ import { collectAbsInlays } from "@nudojs/core";
3761
+
3762
+ // src/infer-json.ts
3763
+ import { typeValueToString as typeValueToString4 } from "@nudojs/core";
3764
+ function mapCase(c) {
3765
+ const out = {
3766
+ name: c.name,
3767
+ args: c.args.map((a) => typeValueToString4(a)),
3768
+ result: typeValueToString4(c.result),
3769
+ throws: c.throws.kind !== "never" ? typeValueToString4(c.throws) : null,
3770
+ source: c.source ?? null
3771
+ };
3772
+ if (c.aggregatedFrom !== void 0) out.aggregatedFrom = c.aggregatedFrom;
3773
+ if (c.intension) {
3774
+ const i = c.intension;
3775
+ out.intension = {
3776
+ ...i.display !== void 0 ? { display: i.display } : {},
3777
+ ...i.abs !== void 0 ? { abs: i.abs } : {},
3778
+ ...i.absMultiline !== void 0 ? { absMultiline: i.absMultiline } : {},
3779
+ ...i.term !== void 0 ? { term: i.term } : {},
3780
+ ...i.pred !== void 0 ? { pred: i.pred } : {},
3781
+ ...i.conf !== void 0 ? { conf: i.conf } : {}
3782
+ };
3783
+ }
3784
+ return out;
3785
+ }
3786
+ function mapFunction(f) {
3787
+ const out = {
3788
+ name: f.name,
3789
+ loc: f.loc,
3790
+ entryOnly: f.entryOnly ?? false,
3791
+ cases: f.cases.map(mapCase)
3792
+ };
3793
+ if (f.noDeclaration) out.noDeclaration = true;
3794
+ if (f.combined) out.combined = typeValueToString4(f.combined);
3795
+ return out;
3796
+ }
3797
+ function serializeInferJson(result, file) {
3798
+ const functions = result.functions.map(mapFunction);
3799
+ const external = result.externalFunctions?.map((f) => ({
3800
+ name: f.name,
3801
+ fromModule: f.fromModule,
3802
+ cases: f.cases.map(mapCase)
3803
+ }));
3804
+ const caseCount = functions.reduce((n, f) => n + f.cases.length, 0) + (external ?? []).reduce((n, f) => n + f.cases.length, 0);
3805
+ const json = {
3806
+ version: 1,
3807
+ file,
3808
+ summary: {
3809
+ functions: functions.length,
3810
+ externalFunctions: external?.length ?? 0,
3811
+ cases: caseCount,
3812
+ diagnostics: result.diagnostics.length
3813
+ },
3814
+ functions,
3815
+ diagnostics: result.diagnostics.map((d) => ({
3816
+ range: d.range,
3817
+ severity: d.severity,
3818
+ message: d.message,
3819
+ ...d.code !== void 0 ? { code: d.code } : {},
3820
+ ...d.suggestions ? { suggestions: d.suggestions } : {},
3821
+ ...d.tags ? { tags: d.tags } : {},
3822
+ ...d.origin ? { origin: d.origin } : {}
3823
+ }))
3824
+ };
3825
+ if (external) json.externalFunctions = external;
3826
+ return json;
3827
+ }
3828
+
3829
+ // src/target-path.ts
3830
+ function isNudoTargetPath(path) {
3831
+ const lower = path.toLowerCase();
3832
+ if (lower.endsWith(".d.ts")) return false;
3833
+ return lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".ts");
3834
+ }
3835
+
3836
+ // src/load-module.ts
3837
+ import { existsSync as existsSync4, statSync as statSync4, readFileSync as readFileSync5 } from "fs";
3838
+ import { dirname as dirname6, resolve as resolve5, join as join3 } from "path";
3839
+ function defaultLoadModule(spec, fromFile) {
3840
+ if (!spec.startsWith(".") && !spec.startsWith("/")) return void 0;
3841
+ try {
3842
+ const base = dirname6(resolve5(fromFile));
3843
+ const p = resolve5(base, spec);
3844
+ for (const cand of [
3845
+ p,
3846
+ `${p}.js`,
3847
+ `${p}.mjs`,
3848
+ `${p}.ts`,
3849
+ join3(p, "index.js"),
3850
+ join3(p, "index.mjs"),
3851
+ join3(p, "index.ts")
3852
+ ]) {
3853
+ if (existsSync4(cand) && !statSync4(cand).isDirectory()) {
3854
+ return readFileSync5(cand, "utf-8");
3855
+ }
3856
+ }
3857
+ return void 0;
3858
+ } catch {
3859
+ return void 0;
3860
+ }
3861
+ }
3862
+
3863
+ // src/session-cache.ts
3864
+ import {
3865
+ resetGeneralizeMemo,
3866
+ resetCheckSourceMemo,
3867
+ resetNudoModuleExecCache,
3868
+ resetParseSourceCache
3869
+ } from "@nudojs/core";
3870
+ function evictAnalysisCachesForFiles(files) {
3871
+ if (files.length === 0) return;
3872
+ evictBPathCacheForFiles(files);
3873
+ evictAnalysisFileCacheForFiles(files);
3874
+ evictFnAnalysisCacheForFiles(files);
3875
+ evictAbsModuleCacheFiles(files);
3876
+ }
3877
+ function clearAnalysisSessionCaches() {
3878
+ clearBPathCache();
3879
+ clearAbsModuleCache();
3880
+ resetGeneralizeMemo();
3881
+ resetCheckSourceMemo();
3882
+ resetNudoModuleExecCache();
3883
+ }
3884
+ function resetAllAnalysisCaches() {
3885
+ clearAnalysisSessionCaches();
3886
+ resetParseSourceCache();
3887
+ }
3888
+
3889
+ // src/harvest-node.ts
3890
+ import { existsSync as existsSync5 } from "fs";
3891
+ import { harvestDts as harvestDts2 } from "@nudojs/harvester";
3892
+ function harvestNodeTypes(fromDir, maxFiles = 12, maxMs = 2500) {
3893
+ const root = resolvePackageRoot("@types/node", fromDir) ?? resolvePackageRoot("node", fromDir);
3894
+ if (!root || !existsSync5(root)) {
3895
+ return { ok: false, error: "@types/node not found" };
3896
+ }
3897
+ const dts = collectDtsFiles(root, maxFiles);
3898
+ if (dts.length === 0) {
3899
+ return { ok: false, error: `no .d.ts under ${root}` };
3900
+ }
3901
+ const env = harvestDts2(dts, { maxMs });
3902
+ return { ok: true, env, root, files: env.stats.files };
3903
+ }
3904
+ function summarizeNodeEnv(env) {
3905
+ return {
3906
+ modules: Object.keys(env.modules).sort(),
3907
+ globals: Object.keys(env.globals).sort().slice(0, 50),
3908
+ symbolCount: env.stats.symbols
3909
+ };
3910
+ }
3911
+
3912
+ // src/semantic-tokens.ts
3913
+ import { dirname as dirname7 } from "path";
3914
+ import { createEnvironment as createEnvironment3 } from "@nudojs/core";
3915
+ import { parse as parse6 } from "@nudojs/parser";
3916
+ import {
3917
+ evaluateProgram as evaluateProgram2,
3918
+ setModuleResolver as setModuleResolver2,
3919
+ setCurrentFileDir as setCurrentFileDir2,
3920
+ resetMemo as resetMemo2
3921
+ } from "@nudojs/cli/evaluator";
3922
+ var SEMANTIC_TOKEN_TYPES = [
3923
+ "function",
3924
+ "variable",
3925
+ "parameter",
3926
+ "property",
3927
+ "type",
3928
+ "keyword",
3929
+ "string",
3930
+ "number",
3931
+ "comment",
3932
+ "decorator",
3933
+ "method"
3934
+ ];
3935
+ var SEMANTIC_TOKEN_MODIFIERS = [
3936
+ "declaration",
3937
+ "readonly",
3938
+ "deprecated",
3939
+ "unreachable"
3940
+ ];
3941
+ function encodeSemanticTokens(tokens) {
3942
+ const result = [];
3943
+ let prevLine = 0;
3944
+ let prevChar = 0;
3945
+ for (const token of tokens) {
3946
+ const deltaLine = token.line - prevLine;
3947
+ const deltaChar = deltaLine === 0 ? token.char - prevChar : token.char;
3948
+ result.push(deltaLine, deltaChar, token.length, token.typeIndex, token.modifierBitmask);
3949
+ prevLine = token.line;
3950
+ prevChar = token.char;
3951
+ }
3952
+ return result;
3953
+ }
3954
+ var TYPE_FUNCTION = SEMANTIC_TOKEN_TYPES.indexOf("function");
3955
+ var TYPE_VARIABLE = SEMANTIC_TOKEN_TYPES.indexOf("variable");
3956
+ var TYPE_PARAMETER = SEMANTIC_TOKEN_TYPES.indexOf("parameter");
3957
+ var TYPE_PROPERTY = SEMANTIC_TOKEN_TYPES.indexOf("property");
3958
+ var TYPE_METHOD = SEMANTIC_TOKEN_TYPES.indexOf("method");
3959
+ var MOD_DECLARATION = 1 << SEMANTIC_TOKEN_MODIFIERS.indexOf("declaration");
3960
+ function buildSemanticTokens(filePath, source) {
3961
+ let ast;
3962
+ try {
3963
+ ast = parse6(source);
3964
+ } catch {
3965
+ return [];
3966
+ }
3967
+ const env = createEnvironment3();
3968
+ resetMemo2();
3969
+ setModuleResolver2(resolveModule);
3970
+ setCurrentFileDir2(dirname7(filePath));
3971
+ try {
3972
+ evaluateProgram2(ast, env);
3973
+ } catch {
3974
+ } finally {
3975
+ setModuleResolver2(null);
3976
+ }
3977
+ const program = ast.program ?? ast;
3978
+ const ownBindings = env.getOwnBindings();
3979
+ const topLevelDeclarators = /* @__PURE__ */ new Set();
3980
+ for (const stmt of program.body ?? []) {
3981
+ const decl = stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportDefaultDeclaration" ? stmt.declaration ?? null : stmt;
3982
+ if (decl && decl.type === "VariableDeclaration") {
3983
+ for (const d of decl.declarations) {
3984
+ topLevelDeclarators.add(d);
3985
+ }
3986
+ }
3987
+ }
3988
+ const tokens = [];
3989
+ const pushIdentifier = (id, typeIndex) => {
3990
+ const loc = id.loc;
3991
+ const name = id.name;
3992
+ if (!loc || typeof name !== "string") return;
3993
+ tokens.push({
3994
+ line: loc.start.line - 1,
3995
+ char: loc.start.column,
3996
+ length: name.length,
3997
+ typeIndex,
3998
+ modifierBitmask: MOD_DECLARATION
3999
+ });
4000
+ };
4001
+ const collectParams = (params) => {
4002
+ for (const p of params) {
4003
+ if (!p) continue;
4004
+ if (p.type === "Identifier") pushIdentifier(p, TYPE_PARAMETER);
4005
+ else if (p.type === "AssignmentPattern" && p.left?.type === "Identifier") {
4006
+ pushIdentifier(p.left, TYPE_PARAMETER);
4007
+ } else if (p.type === "RestElement" && p.argument?.type === "Identifier") {
4008
+ pushIdentifier(p.argument, TYPE_PARAMETER);
4009
+ }
4010
+ }
4011
+ };
4012
+ const isFunctionValue = (name) => ownBindings[name]?.kind === "function";
4013
+ const visit = (node) => {
4014
+ if (!node || typeof node !== "object") return;
4015
+ const n = node;
4016
+ switch (n.type) {
4017
+ case "VariableDeclarator": {
4018
+ const id = n.id;
4019
+ if (id?.type === "Identifier") {
4020
+ const isTopLevel = topLevelDeclarators.has(node);
4021
+ const typeIndex = isTopLevel && isFunctionValue(id.name) ? TYPE_FUNCTION : TYPE_VARIABLE;
4022
+ pushIdentifier(id, typeIndex);
4023
+ }
4024
+ break;
4025
+ }
4026
+ case "FunctionDeclaration":
4027
+ case "FunctionExpression": {
4028
+ const id = n.id;
4029
+ if (id?.type === "Identifier") pushIdentifier(id, TYPE_FUNCTION);
4030
+ collectParams(n.params ?? []);
4031
+ break;
4032
+ }
4033
+ case "ArrowFunctionExpression":
4034
+ collectParams(n.params ?? []);
4035
+ break;
4036
+ case "ObjectMethod": {
4037
+ const key = n.key;
4038
+ if (key?.type === "Identifier" && !n.computed) pushIdentifier(key, TYPE_METHOD);
4039
+ collectParams(n.params ?? []);
4040
+ break;
4041
+ }
4042
+ case "ObjectProperty": {
4043
+ const key = n.key;
4044
+ const value = n.value;
4045
+ if (key?.type === "Identifier" && !n.computed) {
4046
+ const isFn = value?.type === "FunctionExpression" || value?.type === "ArrowFunctionExpression";
4047
+ pushIdentifier(key, isFn ? TYPE_METHOD : TYPE_PROPERTY);
4048
+ }
4049
+ break;
4050
+ }
4051
+ }
4052
+ for (const key of Object.keys(n)) {
4053
+ if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments" || key === "innerComments") continue;
4054
+ const child = n[key];
4055
+ if (Array.isArray(child)) {
4056
+ for (const item of child) {
4057
+ if (item && typeof item === "object" && typeof item.type === "string") {
4058
+ visit(item);
4059
+ }
4060
+ }
4061
+ } else if (child && typeof child === "object" && typeof child.type === "string") {
4062
+ visit(child);
4063
+ }
4064
+ }
4065
+ };
4066
+ visit(program);
4067
+ tokens.sort((a, b) => a.line - b.line || a.char - b.char);
4068
+ return encodeSemanticTokens(tokens);
4069
+ }
1303
4070
 
1304
4071
  // src/dts-generator.ts
1305
- import { T as T2, isTemplate, getTemplateParts, simplifyUnion as simplifyUnion2, typeValueEquals } from "@nudojs/core";
4072
+ import { T as T3, isTemplate, getTemplateParts, simplifyUnion as simplifyUnion2, typeValueEquals, getFnSig as getFnSig4 } from "@nudojs/core";
1306
4073
  function typeValueToTSType(tv) {
1307
4074
  switch (tv.kind) {
1308
4075
  case "literal": {
@@ -1337,8 +4104,8 @@ function typeValueToTSType(tv) {
1337
4104
  }
1338
4105
  case "function": {
1339
4106
  const params = tv.params.map((p) => `${p}: unknown`).join(", ");
1340
- const returnType = tv._returnType;
1341
- const retStr = returnType ? typeValueToTSType(returnType) : "unknown";
4107
+ const sig = getFnSig4(tv);
4108
+ const retStr = sig?.returnType ? typeValueToTSType(sig.returnType) : "unknown";
1342
4109
  return `(${params}) => ${retStr}`;
1343
4110
  }
1344
4111
  case "promise":
@@ -1367,10 +4134,10 @@ function getParamName(fn, index) {
1367
4134
  function widenLiteralToBase(tv) {
1368
4135
  if (tv.kind !== "literal") return tv;
1369
4136
  const v = tv.value;
1370
- if (typeof v === "number") return T2.number;
1371
- if (typeof v === "string") return T2.string;
1372
- if (typeof v === "boolean") return T2.boolean;
1373
- if (typeof v === "bigint") return T2.bigint;
4137
+ if (typeof v === "number") return T3.number;
4138
+ if (typeof v === "string") return T3.string;
4139
+ if (typeof v === "boolean") return T3.boolean;
4140
+ if (typeof v === "bigint") return T3.bigint;
1374
4141
  return tv;
1375
4142
  }
1376
4143
  function widenTopLevel(tv) {
@@ -1392,18 +4159,18 @@ function widenParamType(tv) {
1392
4159
  const widened = tv.elements.map(widenParamType);
1393
4160
  const first = widened[0];
1394
4161
  if (first && widened.every((el) => typeValueEquals(el, first))) {
1395
- return T2.array(first);
4162
+ return T3.array(first);
1396
4163
  }
1397
- return T2.tuple(widened);
4164
+ return T3.tuple(widened);
1398
4165
  }
1399
4166
  case "array":
1400
- return T2.array(widenParamType(tv.element));
4167
+ return T3.array(widenParamType(tv.element));
1401
4168
  case "object":
1402
- return T2.object(widenProperties(tv.properties));
4169
+ return T3.object(widenProperties(tv.properties));
1403
4170
  case "promise":
1404
- return T2.promise(widenParamType(tv.value));
4171
+ return T3.promise(widenParamType(tv.value));
1405
4172
  case "instance":
1406
- return T2.instanceOf(tv.className, widenProperties(tv.properties));
4173
+ return T3.instanceOf(tv.className, widenProperties(tv.properties));
1407
4174
  case "refined":
1408
4175
  return widenParamType(tv.base);
1409
4176
  default:
@@ -1539,12 +4306,19 @@ function typeValueToZodSchema(tv) {
1539
4306
  }
1540
4307
 
1541
4308
  // src/guard-generator.ts
4309
+ import { denoteGuard } from "@nudojs/core";
1542
4310
  function generateGuardFunction(name, tv) {
1543
4311
  const body = generateGuardBody(tv, "data");
1544
4312
  return `export function ${name}(data) {
1545
4313
  return ${body};
1546
4314
  }`;
1547
4315
  }
4316
+ function generateGuardFunctionFromAbs(name, abs) {
4317
+ const body = denoteGuard(abs, "data");
4318
+ return `export function ${name}(data) {
4319
+ return ${body};
4320
+ }`;
4321
+ }
1548
4322
  function generateGuardBody(tv, varName) {
1549
4323
  switch (tv.kind) {
1550
4324
  case "literal": {
@@ -1590,7 +4364,7 @@ function generateGuardBody(tv, varName) {
1590
4364
  }
1591
4365
 
1592
4366
  // src/case-emitter.ts
1593
- import { parse as parse2, extractDirectives as extractDirectives2 } from "@nudojs/parser";
4367
+ import { parse as parse7, extractDirectives as extractDirectives2 } from "@nudojs/parser";
1594
4368
  var GENERATED_PREFIX = "call@";
1595
4369
  var GENERATED_CASE_LINE_REGEX = /^\s*\*?\s*@nudo:case\s+"(call@[^"]*)"/;
1596
4370
  var IDENT_KEY_REGEX = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
@@ -1636,6 +4410,7 @@ function serializeCaseArg(tv) {
1636
4410
  if (typeof v === "boolean") return v ? "true" : "false";
1637
4411
  if (v === null) return "null";
1638
4412
  if (v === void 0) return "undefined";
4413
+ if (typeof v === "bigint") return null;
1639
4414
  return serializeStringLiteral(v);
1640
4415
  }
1641
4416
  case "union": {
@@ -1732,7 +4507,7 @@ function collectExistingCases(source) {
1732
4507
  const byName = /* @__PURE__ */ new Map();
1733
4508
  let ast;
1734
4509
  try {
1735
- ast = parse2(source, { errorRecovery: true });
4510
+ ast = parse7(source, { errorRecovery: true });
1736
4511
  } catch {
1737
4512
  return byName;
1738
4513
  }
@@ -1942,23 +4717,78 @@ function unifiedDiff(a, b, path) {
1942
4717
  return out.join("\n") + "\n";
1943
4718
  }
1944
4719
  export {
4720
+ SEMANTIC_TOKEN_MODIFIERS,
4721
+ SEMANTIC_TOKEN_TYPES,
4722
+ analyzeExportsFromSource,
1945
4723
  analyzeFile,
1946
4724
  analyzeFileAsync,
4725
+ autoHarvestModules,
4726
+ barePackageName,
4727
+ bareSpecToAbsModules,
1947
4728
  buildCaseDirective,
1948
4729
  buildModuleGraph,
4730
+ buildSemanticTokens,
4731
+ clearAbsModuleCache,
4732
+ clearAnalysisFileCache,
4733
+ clearAnalysisSessionCaches,
4734
+ clearBPathCache,
4735
+ clearFnAnalysisCache,
4736
+ clearHarvestCache,
4737
+ collectAbsBindingsFromGraph,
4738
+ collectAbsInlays,
4739
+ collectBPathDiagnostics,
4740
+ collectBPathReplacements,
4741
+ collectBarePackages,
1949
4742
  collectCallRecords,
4743
+ collectDependencySpecs,
4744
+ collectDtsFromEntry,
4745
+ collectEnvGlobals,
4746
+ collectEnvModules,
4747
+ collectStaticImports,
1950
4748
  computeDirtySet,
4749
+ defaultAbsLoadModule,
4750
+ defaultLoadModule,
4751
+ encodeSemanticTokens,
4752
+ envValueToAbs,
4753
+ evalAbsModuleGraph,
4754
+ evalProgramAbsWithModules,
4755
+ evictAbsModuleCacheFiles,
4756
+ evictAnalysisCachesForFiles,
4757
+ evictAnalysisFileCacheForFiles,
4758
+ evictBPathCacheForFiles,
4759
+ evictFnAnalysisCacheForFiles,
4760
+ formatHarvestSummary,
1951
4761
  generateDts,
1952
4762
  generateFunctionDtsLines,
1953
4763
  generateGuardFunction,
4764
+ generateGuardFunctionFromAbs,
4765
+ getAnalysisFileCacheSize,
1954
4766
  getCasesForFile,
1955
4767
  getCompletionsAtPosition,
4768
+ getHoverAtPosition,
1956
4769
  getTypeAtPosition,
1957
4770
  getTypeAtPositionAsync,
4771
+ harvestNodeTypes,
4772
+ harvestPackage,
4773
+ harvestPackageCached,
4774
+ harvestToAbsModules,
4775
+ harvestedValueToAbs,
1958
4776
  insertGeneratedCaseDirectives,
4777
+ isBPathCapable,
4778
+ isNudoTargetPath,
4779
+ lookupHarvested,
4780
+ mockDirectivesToAbsSeeds,
4781
+ packageHarvestToAbsModules,
4782
+ resetAllAnalysisCaches,
4783
+ resolvePackageRoot,
1959
4784
  serializeCaseArg,
4785
+ serializeInferJson,
1960
4786
  stripGeneratedCaseDirectives,
4787
+ summarizeNodeEnv,
1961
4788
  topoSortDirty,
4789
+ tryBPathCall,
4790
+ tryBPathCallFull,
4791
+ tryRunBPath,
1962
4792
  typeValueToTSType,
1963
4793
  typeValueToZodSchema,
1964
4794
  unifiedDiff