@nudojs/service 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +27 -0
  3. package/dist/index.d.ts +638 -5
  4. package/dist/index.js +4439 -208
  5. package/package.json +17 -4
  6. package/src/__tests__/abs-bindings.test.ts +53 -0
  7. package/src/__tests__/abs-call-records.test.ts +33 -0
  8. package/src/__tests__/abs-calls.test.ts +59 -0
  9. package/src/__tests__/abs-module-cache.test.ts +121 -0
  10. package/src/__tests__/abs-modules-graph.test.ts +100 -0
  11. package/src/__tests__/abs-typevalue-diff.test.ts +111 -0
  12. package/src/__tests__/analyzer.test.ts +745 -2
  13. package/src/__tests__/as-replace.test.ts +165 -0
  14. package/src/__tests__/bpath-arg-provenance.test.ts +104 -0
  15. package/src/__tests__/bpath-as.test.ts +54 -0
  16. package/src/__tests__/bpath-cache-key.test.ts +33 -0
  17. package/src/__tests__/bpath-calls.test.ts +55 -0
  18. package/src/__tests__/bpath-diagnostics.test.ts +78 -0
  19. package/src/__tests__/bpath-env-modules.test.ts +30 -0
  20. package/src/__tests__/bpath-env.test.ts +80 -0
  21. package/src/__tests__/bpath-hosted-skip.test.ts +71 -0
  22. package/src/__tests__/bpath-import-default-ns.test.ts +106 -0
  23. package/src/__tests__/bpath-method-dedupe.test.ts +65 -0
  24. package/src/__tests__/bpath-method-diag.test.ts +45 -0
  25. package/src/__tests__/bpath-module-diags.test.ts +101 -0
  26. package/src/__tests__/bpath-path-env.test.ts +46 -0
  27. package/src/__tests__/bpath-reexport.test.ts +131 -0
  28. package/src/__tests__/bpath-replace.test.ts +52 -0
  29. package/src/__tests__/bpath-require.test.ts +66 -0
  30. package/src/__tests__/bpath-run.test.ts +109 -0
  31. package/src/__tests__/case-emitter.test.ts +636 -0
  32. package/src/__tests__/cjs-require.test.ts +54 -0
  33. package/src/__tests__/diagnostics.test.ts +60 -0
  34. package/src/__tests__/dts-generator.test.ts +290 -6
  35. package/src/__tests__/fn-cache-evict.test.ts +56 -0
  36. package/src/__tests__/fn-sig-impl.test.ts +247 -0
  37. package/src/__tests__/guard-generator.test.ts +29 -0
  38. package/src/__tests__/harvest-auto.test.ts +43 -0
  39. package/src/__tests__/harvest-node.test.ts +24 -0
  40. package/src/__tests__/harvest-package.test.ts +38 -0
  41. package/src/__tests__/harvest-to-abs.test.ts +46 -0
  42. package/src/__tests__/hover-intension.test.ts +39 -0
  43. package/src/__tests__/infer-json.test.ts +35 -0
  44. package/src/__tests__/integration.test.ts +421 -0
  45. package/src/__tests__/m3-intension.test.ts +28 -0
  46. package/src/__tests__/mock-seed-fingerprint.test.ts +64 -0
  47. package/src/__tests__/schema-generator.test.ts +38 -0
  48. package/src/__tests__/session-cache-evict.test.ts +90 -0
  49. package/src/__tests__/static-imports.test.ts +44 -0
  50. package/src/abs-modules-graph.ts +387 -0
  51. package/src/analysis-file-cache.ts +51 -0
  52. package/src/analyzer.ts +2569 -94
  53. package/src/bpath-diagnostics.ts +283 -0
  54. package/src/bpath-run.ts +409 -0
  55. package/src/case-emitter.ts +477 -0
  56. package/src/dts-generator.ts +220 -24
  57. package/src/env-to-abs.ts +57 -0
  58. package/src/fn-analysis-cache.ts +119 -0
  59. package/src/guard-generator.ts +57 -0
  60. package/src/harvest-auto.ts +86 -0
  61. package/src/harvest-node.ts +48 -0
  62. package/src/harvest-package.ts +204 -0
  63. package/src/harvest-to-abs.ts +129 -0
  64. package/src/index.ts +128 -0
  65. package/src/infer-json.ts +137 -0
  66. package/src/load-module.ts +34 -0
  67. package/src/mock-abs.ts +275 -0
  68. package/src/schema-generator.ts +43 -0
  69. package/src/semantic-tokens.ts +216 -0
  70. package/src/session-cache.ts +50 -0
  71. package/src/static-imports.ts +241 -0
  72. package/src/target-path.ts +17 -0
package/dist/index.js CHANGED
@@ -1,18 +1,42 @@
1
1
  // src/analyzer.ts
2
- import { readFileSync, existsSync } 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
- createEnvironment,
10
- isSubtypeOf
9
+ widenLiteral,
10
+ collapseLiteralUnion,
11
+ createEnvironment as createEnvironment2,
12
+ isSubtypeOf,
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
11
31
  } from "@nudojs/core";
12
- import { parse, extractDirectives, parseTypeValueExpr } from "@nudojs/parser";
32
+ import { parse as parse5, extractDirectives, extractFileDirectives, parseTypeValueExpr } from "@nudojs/parser";
13
33
  import {
34
+ evaluate,
14
35
  evaluateFunction,
15
36
  evaluateFunctionFull,
37
+ memberMayExistOn,
38
+ setUsageSiteTag,
39
+ USAGE_SITE_MODULE,
16
40
  evaluateProgram,
17
41
  setModuleResolver,
18
42
  setCurrentFileDir,
@@ -20,44 +44,2266 @@ import {
20
44
  getUnreachableRanges,
21
45
  resetUnreachableRanges,
22
46
  setNodeTypeCollector,
23
- setSampleCount
47
+ setCallCollector,
48
+ setUnknownCollector,
49
+ setProvenanceTracking,
50
+ setSampleCount,
51
+ setUnknownBuiltinHandler,
52
+ setEnvModules,
53
+ resetEnvModules,
54
+ setMockModules,
55
+ resetMockModules,
56
+ setCurrentSource,
57
+ loadEnvs as loadEnvs2,
58
+ preloadPathEnvs,
59
+ findProjectConfig,
60
+ resolveNpmNudo,
61
+ BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS
24
62
  } from "@nudojs/cli/evaluator";
63
+
64
+ // src/mock-abs.ts
65
+ import {
66
+ absFunction,
67
+ emptyEnv,
68
+ typeValueToAbs,
69
+ abs as makeAbs,
70
+ confJoin,
71
+ unknown as absUnknown,
72
+ litValue,
73
+ formatAbs,
74
+ getFnImpl,
75
+ typeValueToString
76
+ } from "@nudojs/core";
77
+ function markMockConf(a) {
78
+ a.conf = confJoin(a.conf, "mock");
79
+ return a;
80
+ }
81
+ function astContentKey(n, depth = 0) {
82
+ if (n == null || depth > 24) return "";
83
+ if (typeof n !== "object") return String(n);
84
+ if (Array.isArray(n)) return n.map((x) => astContentKey(x, depth + 1)).join(",");
85
+ const obj = n;
86
+ const type = typeof obj.type === "string" ? obj.type : "";
87
+ const parts = [type];
88
+ for (const key of ["name", "value", "raw", "operator", "computed"]) {
89
+ const v = obj[key];
90
+ if (v !== void 0 && (typeof v !== "object" || v === null)) parts.push(`${key}=${String(v)}`);
91
+ }
92
+ for (const key of Object.keys(obj)) {
93
+ 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") {
94
+ continue;
95
+ }
96
+ const v = obj[key];
97
+ if (v && typeof v === "object") parts.push(`${key}:{${astContentKey(v, depth + 1)}}`);
98
+ }
99
+ return parts.join("|");
100
+ }
101
+ function stampFingerprint(a, fp) {
102
+ const impl = getFnImpl(a);
103
+ if (impl) impl.fingerprint = fp;
104
+ return a;
105
+ }
106
+ function constantMockFn(result) {
107
+ const marked = markMockConf(result);
108
+ const mockEnv = emptyEnv();
109
+ const retName = "__nudo_mock_ret";
110
+ mockEnv.vars.set(retName, marked);
111
+ const body = {
112
+ type: "BlockStatement",
113
+ body: [
114
+ {
115
+ type: "ReturnStatement",
116
+ argument: { type: "Identifier", name: retName }
117
+ }
118
+ ]
119
+ };
120
+ let retKey;
121
+ try {
122
+ retKey = formatAbs(marked);
123
+ } catch {
124
+ retKey = "?";
125
+ }
126
+ return stampFingerprint(
127
+ markMockConf(absFunction(["...args"], { body, env: mockEnv })),
128
+ `ret=${retKey}`
129
+ );
130
+ }
131
+ function absArgMatches(declared, actual) {
132
+ if (!actual) return false;
133
+ const declaredAbs = typeValueToAbs(declared);
134
+ const av = litValue(actual);
135
+ const dv = litValue(declaredAbs);
136
+ if (dv !== void 0) {
137
+ return av !== void 0 && Object.is(av, dv);
138
+ }
139
+ if (declared.kind === "primitive") {
140
+ if (actual.shape.k === "prim") return actual.shape.type === declared.type;
141
+ if (av !== void 0) {
142
+ const t = typeof av;
143
+ return declared.type === "number" && t === "number" || declared.type === "string" && t === "string" || declared.type === "boolean" && t === "boolean" || declared.type === "bigint" && t === "bigint";
144
+ }
145
+ }
146
+ return false;
147
+ }
148
+ function dispatchMockFn(defaultReturn, cases) {
149
+ if (!cases?.length) return constantMockFn(defaultReturn);
150
+ const caseAbs = cases.map((c) => ({
151
+ declared: c.args,
152
+ result: markMockConf(typeValueToAbs(c.returnValue))
153
+ }));
154
+ const dummyBody = {
155
+ type: "BlockStatement",
156
+ body: [{ type: "ReturnStatement", argument: null }]
157
+ };
158
+ const markedDefault = markMockConf(defaultReturn);
159
+ let defaultKey;
160
+ try {
161
+ defaultKey = formatAbs(markedDefault);
162
+ } catch {
163
+ defaultKey = "?";
164
+ }
165
+ const caseKey = cases.map(
166
+ (c) => `${c.args.map((a) => typeValueToString(a)).join(",")}->${typeValueToString(c.returnValue)}`
167
+ ).join("|");
168
+ return stampFingerprint(
169
+ markMockConf(
170
+ absFunction(["...args"], {
171
+ body: dummyBody,
172
+ env: emptyEnv(),
173
+ apply: (args) => {
174
+ for (const c of caseAbs) {
175
+ if (c.declared.every((d, i) => absArgMatches(d, args[i]))) {
176
+ return c.result;
177
+ }
178
+ }
179
+ return markedDefault;
180
+ }
181
+ })
182
+ ),
183
+ `dispatch=default=${defaultKey};cases=${caseKey}`
184
+ );
185
+ }
186
+ function absFromMockHelper(h) {
187
+ if (h.callsFakeImpl && h.callsFakeImpl.kind === "function") {
188
+ const fn = h.callsFakeImpl;
189
+ const body = fn.body;
190
+ return stampFingerprint(
191
+ absFunction(fn.params, { body, async: false }),
192
+ `fake=${fn.params.join(",")}:${astContentKey(body)}`
193
+ );
194
+ }
195
+ let defaultReturn;
196
+ if (h.resolvedValue) {
197
+ defaultReturn = makeAbs(
198
+ { k: "eff", eff: "promise", inner: typeValueToAbs(h.resolvedValue) },
199
+ void 0,
200
+ void 0,
201
+ "path"
202
+ );
203
+ } else if (h.rejectedValue) {
204
+ defaultReturn = makeAbs({ k: "never" }, void 0, void 0, "exact");
205
+ } else if (h.returnValue) {
206
+ defaultReturn = typeValueToAbs(h.returnValue);
207
+ } else if (h.onFirstCallValue) {
208
+ defaultReturn = typeValueToAbs(h.onFirstCallValue);
209
+ } else {
210
+ defaultReturn = absUnknown;
211
+ }
212
+ return dispatchMockFn(defaultReturn, h.withArgsCases);
213
+ }
214
+ function absFromSinon(sinonExpr) {
215
+ if (sinonExpr.resolvedValue) {
216
+ return constantMockFn(
217
+ makeAbs(
218
+ { k: "eff", eff: "promise", inner: typeValueToAbs(sinonExpr.resolvedValue) },
219
+ void 0,
220
+ void 0,
221
+ "path"
222
+ )
223
+ );
224
+ }
225
+ if (sinonExpr.rejectedValue) {
226
+ return constantMockFn(makeAbs({ k: "never" }, void 0, void 0, "exact"));
227
+ }
228
+ if (sinonExpr.returnValue) {
229
+ return constantMockFn(typeValueToAbs(sinonExpr.returnValue));
230
+ }
231
+ return constantMockFn(absUnknown);
232
+ }
233
+ function mockSeedsToAbsMocks(seeds) {
234
+ const out = { ...seeds.seedVars };
235
+ for (const [name, fn] of Object.entries(seeds.seedFns)) {
236
+ out[name] = absFunction(fn.params, { body: fn.body, async: fn.async ?? false });
237
+ }
238
+ return out;
239
+ }
240
+ function mockDirectivesToAbsSeeds(functions) {
241
+ const seedVars = {};
242
+ const seedFns = {};
243
+ for (const fn of functions) {
244
+ for (const d of fn.directives) {
245
+ if (d.kind !== "mock") continue;
246
+ if (d.arrowFn) {
247
+ const body = d.arrowFn.body;
248
+ seedFns[d.name] = {
249
+ params: d.arrowFn.params,
250
+ body,
251
+ async: false,
252
+ fingerprint: `fake=${d.arrowFn.params.join(",")}:${astContentKey(body)}`
253
+ };
254
+ } else if (d.nudoMock) {
255
+ seedVars[d.name] = markMockConf(absFromMockHelper(d.nudoMock));
256
+ } else if (d.sinonExpr) {
257
+ seedVars[d.name] = markMockConf(absFromSinon(d.sinonExpr));
258
+ }
259
+ }
260
+ }
261
+ return { seedVars, seedFns };
262
+ }
263
+
264
+ // src/harvest-auto.ts
265
+ import { parse as parse2 } from "@nudojs/parser";
266
+
267
+ // src/static-imports.ts
268
+ import { parse } from "@nudojs/parser";
269
+ import { readFileSync, existsSync } from "fs";
270
+ import { resolve, dirname, join } from "path";
271
+ import { generalizeFromAst } from "@nudojs/core";
272
+ function resolveRelative(fromFile, spec) {
273
+ if (!spec.startsWith(".") && !spec.startsWith("/")) return void 0;
274
+ const base = resolve(dirname(fromFile), spec);
275
+ for (const cand of [
276
+ base,
277
+ base + ".js",
278
+ base + ".cjs",
279
+ base + ".mjs",
280
+ base + ".ts",
281
+ base + ".mts",
282
+ base + ".cts",
283
+ join(base, "index.js"),
284
+ join(base, "index.cjs"),
285
+ join(base, "index.mjs"),
286
+ join(base, "index.ts")
287
+ ]) {
288
+ if (existsSync(cand)) return cand;
289
+ }
290
+ return void 0;
291
+ }
292
+ function collectDependencySpecs(ast) {
293
+ const specs = [];
294
+ for (const stmt of ast.program.body) {
295
+ if (stmt.type === "ImportDeclaration") {
296
+ specs.push(stmt.source.value);
297
+ }
298
+ if (stmt.type === "ExportNamedDeclaration" && stmt.source) {
299
+ specs.push(stmt.source.value);
300
+ }
301
+ if (stmt.type === "ExportAllDeclaration" && stmt.source) {
302
+ specs.push(stmt.source.value);
303
+ }
304
+ collectRequires(stmt, specs);
305
+ }
306
+ return specs;
307
+ }
308
+ function collectRequires(node, out) {
309
+ const visit = (n) => {
310
+ if (!n || typeof n !== "object") return;
311
+ const obj = n;
312
+ if (obj.type === "CallExpression" && obj.callee?.type === "Identifier" && obj.callee.name === "require" && obj.arguments?.[0]?.type === "StringLiteral") {
313
+ const spec = obj.arguments[0].value;
314
+ if (typeof spec === "string") out.push(spec);
315
+ }
316
+ for (const key of Object.keys(obj)) {
317
+ if (key === "loc" || key === "start" || key === "end") continue;
318
+ const v = obj[key];
319
+ if (Array.isArray(v)) v.forEach(visit);
320
+ else if (v && typeof v === "object") visit(v);
321
+ }
322
+ };
323
+ visit(node);
324
+ }
325
+ function analyzeExportsFromSource(filePath, source) {
326
+ const file = parse(source);
327
+ const named = /* @__PURE__ */ new Map();
328
+ let defaultExport;
329
+ const poly = /* @__PURE__ */ new Map();
330
+ for (const stmt of file.program.body) {
331
+ let decl = stmt;
332
+ if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
333
+ decl = stmt.declaration;
334
+ }
335
+ if (decl.type === "FunctionDeclaration" && decl.id) {
336
+ named.set(decl.id.name, decl.id.name);
337
+ }
338
+ if (decl.type === "VariableDeclaration") {
339
+ for (const d of decl.declarations) {
340
+ if (d.id.type === "Identifier") named.set(d.id.name, d.id.name);
341
+ }
342
+ }
343
+ if (decl.type === "ClassDeclaration" && decl.id) {
344
+ named.set(decl.id.name, decl.id.name);
345
+ }
346
+ if (stmt.type === "ExportDefaultDeclaration") {
347
+ const d = stmt.declaration;
348
+ if (d.type === "FunctionDeclaration" && d.id) defaultExport = d.id.name;
349
+ if (d.type === "Identifier") defaultExport = d.name;
350
+ }
351
+ collectCjsExports(stmt, named, (n) => {
352
+ defaultExport = n;
353
+ });
354
+ }
355
+ for (const name of named.values()) {
356
+ const g = generalizeFromAst(name, source);
357
+ if (g) poly.set(name, g);
358
+ }
359
+ if (defaultExport) {
360
+ const g = generalizeFromAst(defaultExport, source);
361
+ if (g) poly.set("default", g);
362
+ }
363
+ return { path: filePath, named, defaultExport, source, poly };
364
+ }
365
+ function collectCjsExports(stmt, named, setDefault) {
366
+ const visit = (n) => {
367
+ if (!n || typeof n !== "object") return;
368
+ const obj = n;
369
+ if (obj.type !== "AssignmentExpression") {
370
+ for (const key of Object.keys(obj)) {
371
+ if (key === "loc" || key === "start" || key === "end") continue;
372
+ const v = obj[key];
373
+ if (Array.isArray(v)) v.forEach(visit);
374
+ else if (v && typeof v === "object") visit(v);
375
+ }
376
+ return;
377
+ }
378
+ const left = obj.left;
379
+ const right = obj.right;
380
+ if (left?.type === "MemberExpression" && left.object?.type === "Identifier" && left.object.name === "exports" && left.property?.type === "Identifier" && left.property.name) {
381
+ named.set(left.property.name, left.property.name);
382
+ }
383
+ if (left?.type === "MemberExpression" && left.object?.type === "Identifier" && left.object.name === "module" && left.property?.type === "Identifier" && left.property.name === "exports") {
384
+ if (right?.type === "Identifier" && typeof right.name === "string") {
385
+ named.set(right.name, right.name);
386
+ setDefault(right.name);
387
+ }
388
+ if (right?.type === "ObjectExpression") {
389
+ const props = right.properties;
390
+ for (const p of props) {
391
+ if (p.type !== "ObjectProperty") continue;
392
+ const key = p.key;
393
+ const val = p.value;
394
+ const exportName = key?.type === "Identifier" ? key.name : key?.type === "StringLiteral" ? key.value : void 0;
395
+ if (exportName) named.set(exportName, exportName);
396
+ if (val?.type === "Identifier" && val.name && exportName) {
397
+ }
398
+ }
399
+ }
400
+ }
401
+ };
402
+ visit(stmt);
403
+ }
404
+ function collectStaticImports(entryFile, maxDepth = 8) {
405
+ const graph = /* @__PURE__ */ new Map();
406
+ const queue = [
407
+ { file: resolve(entryFile), depth: 0 }
408
+ ];
409
+ const seen = /* @__PURE__ */ new Set();
410
+ while (queue.length > 0) {
411
+ const { file, depth } = queue.shift();
412
+ if (seen.has(file) || depth > maxDepth) continue;
413
+ seen.add(file);
414
+ if (!existsSync(file)) continue;
415
+ const source = readFileSync(file, "utf8");
416
+ const mod = analyzeExportsFromSource(file, source);
417
+ graph.set(file, mod);
418
+ const ast = parse(source);
419
+ for (const spec of collectDependencySpecs(ast)) {
420
+ const resolved = resolveRelative(file, spec);
421
+ if (resolved) queue.push({ file: resolved, depth: depth + 1 });
422
+ }
423
+ }
424
+ return graph;
425
+ }
426
+
427
+ // src/harvest-package.ts
428
+ import { existsSync as existsSync2, readdirSync, statSync, readFileSync as readFileSync2 } from "fs";
429
+ import { join as join2, resolve as resolve2, dirname as dirname2, basename } from "path";
430
+ import { harvestDts } from "@nudojs/harvester";
431
+ import { typeValueToString as typeValueToString2 } from "@nudojs/core";
432
+ function resolvePackageRoot(pkg, fromDir = process.cwd()) {
433
+ let dir = resolve2(fromDir);
434
+ const bare = pkg.replace(/^@/, "").replace(/\//g, "__");
435
+ for (let i = 0; i < 8; i++) {
436
+ const typesPath = join2(dir, "node_modules", "@types", bare);
437
+ if (existsSync2(typesPath)) return typesPath;
438
+ const pkgPath = join2(dir, "node_modules", pkg);
439
+ if (existsSync2(pkgPath)) return pkgPath;
440
+ const parent = resolve2(dir, "..");
441
+ if (parent === dir) break;
442
+ dir = parent;
443
+ }
444
+ return void 0;
445
+ }
446
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "test", "tests", "docs", "doc", "examples", "__tests__"]);
447
+ var MAX_DTS_BYTES = 15e5;
448
+ function dtsPriority(p, root) {
449
+ const base = basename(p);
450
+ const rel = p.slice(root.length);
451
+ if (base === "index.d.ts") return 0;
452
+ if (base === "index.d.mts") return 1;
453
+ if (rel.includes("/types/") && base === "index.d.ts") return 2;
454
+ if (base.endsWith(".d.ts") && !base.includes(".")) return 3;
455
+ return 10;
456
+ }
457
+ function collectDtsFiles(root, maxFiles = 8) {
458
+ const out = [];
459
+ const walk = (dir) => {
460
+ if (out.length >= maxFiles * 3) return;
461
+ let entries;
462
+ try {
463
+ entries = readdirSync(dir);
464
+ } catch {
465
+ return;
466
+ }
467
+ for (const name of entries) {
468
+ if (out.length >= maxFiles * 3) return;
469
+ if (SKIP_DIRS.has(name)) continue;
470
+ const p = join2(dir, name);
471
+ let st;
472
+ try {
473
+ st = statSync(p);
474
+ } catch {
475
+ continue;
476
+ }
477
+ if (st.isDirectory()) walk(p);
478
+ else if (name.endsWith(".d.ts") && !name.endsWith(".d.ts.map")) {
479
+ if (st.size > MAX_DTS_BYTES) continue;
480
+ out.push(p);
481
+ }
482
+ }
483
+ };
484
+ walk(root);
485
+ out.sort((a, b) => dtsPriority(a, root) - dtsPriority(b, root) || a.length - b.length);
486
+ return out.slice(0, maxFiles);
487
+ }
488
+ function entryDtsFromPackageJson(root) {
489
+ const pj = join2(root, "package.json");
490
+ if (!existsSync2(pj)) return void 0;
491
+ try {
492
+ const raw = JSON.parse(readFileSync2(pj, "utf8"));
493
+ const entry = raw.types ?? raw.typings;
494
+ if (typeof entry === "string" && entry.endsWith(".d.ts")) {
495
+ const p = resolve2(root, entry);
496
+ if (existsSync2(p)) {
497
+ const st = statSync(p);
498
+ if (st.size <= MAX_DTS_BYTES) return p;
499
+ }
500
+ }
501
+ } catch {
502
+ }
503
+ return void 0;
504
+ }
505
+ function collectDtsFromEntry(entry, maxFiles = 200) {
506
+ const files = [];
507
+ const seen = /* @__PURE__ */ new Set();
508
+ const queue = [entry];
509
+ const REFERENCE_PATH_REGEX = /<reference\s+path=["']([^"']+)["']\s*\/>/g;
510
+ const RELATIVE_FROM_REGEX = /\bfrom\s+["'](\.[^"']+)["']/g;
511
+ while (queue.length > 0 && files.length < maxFiles) {
512
+ const current = queue.shift();
513
+ if (seen.has(current) || !existsSync2(current)) continue;
514
+ seen.add(current);
515
+ if (!current.endsWith(".d.ts")) continue;
516
+ files.push(current);
517
+ let text;
518
+ try {
519
+ text = readFileSync2(current, "utf-8");
520
+ } catch {
521
+ continue;
522
+ }
523
+ const dir = dirname2(current);
524
+ for (const match of text.matchAll(REFERENCE_PATH_REGEX)) {
525
+ queue.push(resolve2(dir, match[1]));
526
+ }
527
+ for (const match of text.matchAll(RELATIVE_FROM_REGEX)) {
528
+ const base = resolve2(dir, match[1]);
529
+ for (const candidate of [`${base}.d.ts`, join2(base, "index.d.ts")]) {
530
+ if (existsSync2(candidate)) {
531
+ queue.push(candidate);
532
+ break;
533
+ }
534
+ }
535
+ }
536
+ }
537
+ return files;
538
+ }
539
+ function harvestPackage(pkg, fromDir, maxFiles = 8) {
540
+ const root = resolvePackageRoot(pkg, fromDir);
541
+ if (!root) return { error: `package not found: ${pkg}` };
542
+ const entry = entryDtsFromPackageJson(root) ?? join2(root, "index.d.ts");
543
+ let dtsFiles = existsSync2(entry) ? collectDtsFromEntry(entry, maxFiles) : [];
544
+ if (dtsFiles.length === 0) {
545
+ const collected = collectDtsFiles(root, maxFiles);
546
+ dtsFiles = entry && existsSync2(entry) && !collected.includes(entry) ? [entry, ...collected.filter((f) => f !== entry)].slice(0, maxFiles) : collected;
547
+ }
548
+ if (dtsFiles.length === 0) {
549
+ return { error: `no .d.ts under ${root}` };
550
+ }
551
+ const env = harvestDts(dtsFiles);
552
+ return { pkg, root, dtsFiles, env };
553
+ }
554
+ function formatHarvestSummary(h) {
555
+ const lines = [];
556
+ lines.push(`${h.pkg}: ${h.dtsFiles.length} d.ts, ${h.env.stats.symbols} symbols`);
557
+ const mods = Object.keys(h.env.modules);
558
+ for (const m of mods.slice(0, 5)) {
559
+ const exports = h.env.modules[m];
560
+ const names = Object.keys(exports).slice(0, 12);
561
+ lines.push(` module "${m}": ${names.join(", ")}${Object.keys(exports).length > 12 ? "\u2026" : ""}`);
562
+ }
563
+ if (mods.length > 5) lines.push(` \u2026 ${mods.length - 5} more modules`);
564
+ return lines.join("\n");
565
+ }
566
+ function lookupHarvested(h, moduleName, exportName) {
567
+ const mod = h.env.modules[moduleName];
568
+ const tv = mod?.[exportName] ?? h.env.globals[exportName];
569
+ if (!tv) return void 0;
570
+ return typeValueToString2(tv);
571
+ }
572
+
573
+ // src/harvest-auto.ts
574
+ function barePackageName(spec) {
575
+ if (!spec) return void 0;
576
+ if (spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("node:")) return void 0;
577
+ const parts = spec.split("/");
578
+ if (spec.startsWith("@")) {
579
+ return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : void 0;
580
+ }
581
+ return parts[0];
582
+ }
583
+ function collectBarePackages(source) {
584
+ try {
585
+ const ast = parse2(source);
586
+ const names = /* @__PURE__ */ new Set();
587
+ for (const spec of collectDependencySpecs(ast)) {
588
+ const pkg = barePackageName(spec);
589
+ if (pkg) names.add(pkg);
590
+ }
591
+ return [...names];
592
+ } catch {
593
+ return [];
594
+ }
595
+ }
596
+ var harvestCache = /* @__PURE__ */ new Map();
597
+ function harvestPackageCached(pkg, fromDir) {
598
+ const key = `${fromDir}::${pkg}`;
599
+ if (harvestCache.has(key)) return harvestCache.get(key);
600
+ let result = null;
601
+ try {
602
+ const h = harvestPackage(pkg, fromDir);
603
+ if (!("error" in h)) result = h;
604
+ } catch {
605
+ result = null;
606
+ }
607
+ harvestCache.set(key, result);
608
+ return result;
609
+ }
610
+ function clearHarvestCache() {
611
+ harvestCache.clear();
612
+ }
613
+ function autoHarvestModules(source, fromDir) {
614
+ const packages = collectBarePackages(source);
615
+ if (packages.length === 0) return {};
616
+ const modules = {};
617
+ for (const pkg of packages) {
618
+ const h = harvestPackageCached(pkg, fromDir);
619
+ if (!h) continue;
620
+ for (const [mod, exports] of Object.entries(h.env.modules)) {
621
+ modules[mod] = exports;
622
+ if (!modules[pkg]) modules[pkg] = exports;
623
+ const base = mod.replace(/^@types\//, "").replace(/\\/g, "/");
624
+ if (base && !modules[base]) modules[base] = exports;
625
+ }
626
+ for (const [k, v] of Object.entries(h.env.globals)) {
627
+ if (!modules[pkg]) modules[pkg] = {};
628
+ if (modules[pkg][k] === void 0) modules[pkg][k] = v;
629
+ }
630
+ }
631
+ return modules;
632
+ }
633
+
634
+ // src/abs-modules-graph.ts
635
+ import { readFileSync as readFileSync3, statSync as statSync2 } from "fs";
636
+ import { dirname as dirname4, resolve as resolve3 } from "path";
637
+ import { parse as parse3 } from "@nudojs/parser";
638
+ import {
639
+ evalProgramAbs,
640
+ collectAbsExports,
641
+ absFunction as absFunction3
642
+ } from "@nudojs/core";
643
+
644
+ // src/harvest-to-abs.ts
645
+ import {
646
+ T,
647
+ typeValueToAbs as typeValueToAbs2,
648
+ confJoin as confJoin2,
649
+ getFnSig,
650
+ absFunction as absFunction2
651
+ } from "@nudojs/core";
652
+ import { dirname as dirname3 } from "path";
653
+ function markMockConf2(a) {
654
+ a.conf = confJoin2(a.conf, "mock");
655
+ return a;
656
+ }
657
+ function harvestedValueToAbs(tv) {
658
+ if (!tv) return markMockConf2(typeValueToAbs2(T.unknown));
659
+ const sig = getFnSig(tv);
660
+ if (sig) {
661
+ const ret = markMockConf2(typeValueToAbs2(sig.returnType));
662
+ const params = tv.kind === "function" && tv.params?.length ? tv.params : sig.paramTypes.map((_, i) => `_arg${i}`);
663
+ const dummyBody = {
664
+ type: "BlockStatement",
665
+ body: [],
666
+ directives: []
667
+ };
668
+ return markMockConf2(
669
+ absFunction2(params, {
670
+ body: dummyBody,
671
+ apply: () => ret
672
+ })
673
+ );
674
+ }
675
+ return markMockConf2(typeValueToAbs2(tv));
676
+ }
677
+ function harvestToAbsModules(pkg, fromDir) {
678
+ const h = harvestPackageCached(pkg, fromDir);
679
+ if (!h) return {};
680
+ return packageHarvestToAbsModules(pkg, h);
681
+ }
682
+ function packageHarvestToAbsModules(pkg, h) {
683
+ const out = {};
684
+ const convertRecord = (rec) => {
685
+ const named = {};
686
+ for (const [k, v] of Object.entries(rec)) {
687
+ named[k] = harvestedValueToAbs(v);
688
+ }
689
+ return named;
690
+ };
691
+ const register = (key, exports) => {
692
+ if (!out[key]) out[key] = exports;
693
+ };
694
+ for (const [mod, rec] of Object.entries(h.env.modules)) {
695
+ const named = convertRecord(rec);
696
+ const exports = { named };
697
+ const def = rec["default"];
698
+ if (def) exports.default = harvestedValueToAbs(def);
699
+ register(mod, exports);
700
+ register(pkg, exports);
701
+ const stripped = mod.replace(/^@types\//, "").replace(/\\/g, "/");
702
+ if (stripped) register(stripped, exports);
703
+ const base = stripped.split("/").filter(Boolean).pop();
704
+ if (base && base !== pkg) register(base, exports);
705
+ }
706
+ const globalNamed = convertRecord(h.env.globals);
707
+ if (Object.keys(globalNamed).length > 0) {
708
+ const existing = out[pkg];
709
+ if (existing) {
710
+ out[pkg] = { named: { ...globalNamed, ...existing.named }, default: existing.default };
711
+ } else {
712
+ out[pkg] = { named: globalNamed };
713
+ }
714
+ }
715
+ return out;
716
+ }
717
+ function bareSpecToAbsModules(spec, fromFile) {
718
+ if (!spec || spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("node:")) {
719
+ return void 0;
720
+ }
721
+ const parts = spec.split("/");
722
+ const pkg = spec.startsWith("@") ? parts.length >= 2 ? `${parts[0]}/${parts[1]}` : void 0 : parts[0];
723
+ if (!pkg) return void 0;
724
+ const table = harvestToAbsModules(pkg, dirname3(fromFile));
725
+ return table[spec] ?? table[pkg];
726
+ }
727
+
728
+ // src/abs-modules-graph.ts
729
+ function defaultAbsLoadModule(spec, fromFile) {
730
+ if (!spec.startsWith(".") && !spec.startsWith("/")) return void 0;
731
+ try {
732
+ const p = resolve3(dirname4(resolve3(fromFile)), spec);
733
+ for (const cand of [p, `${p}.js`, `${p}.mjs`, `${p}.ts`, resolve3(p, "index.js")]) {
734
+ try {
735
+ return readFileSync3(cand, "utf-8");
736
+ } catch {
737
+ }
738
+ }
739
+ return void 0;
740
+ } catch {
741
+ return void 0;
742
+ }
743
+ }
744
+ function relCandidates(spec, fromFile) {
745
+ const p = resolve3(dirname4(resolve3(fromFile)), spec);
746
+ return [p, `${p}.js`, `${p}.mjs`, `${p}.ts`, resolve3(p, "index.js")];
747
+ }
748
+ function resolveRel(spec, fromFile) {
749
+ if (!spec.startsWith(".") && !spec.startsWith("/")) return null;
750
+ for (const cand of relCandidates(spec, fromFile)) {
751
+ try {
752
+ readFileSync3(cand, "utf-8");
753
+ return cand;
754
+ } catch {
755
+ }
756
+ }
757
+ return null;
758
+ }
759
+ function importSpecs(source) {
760
+ try {
761
+ const file = parse3(source);
762
+ const out = [];
763
+ for (const stmt of file.program.body) {
764
+ if (stmt.type === "ImportDeclaration" || stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportAllDeclaration") {
765
+ const src = stmt.source;
766
+ if (src && typeof src.value === "string") {
767
+ out.push(src.value);
768
+ }
769
+ }
770
+ collectRequireSpecs(stmt, out);
771
+ }
772
+ return out;
773
+ } catch {
774
+ return [];
775
+ }
776
+ }
777
+ function collectRequireSpecs(node, out) {
778
+ const visit = (n) => {
779
+ if (!n || typeof n !== "object") return;
780
+ const o = n;
781
+ if (o.type === "CallExpression" && o.callee?.type === "Identifier" && o.callee.name === "require" && o.arguments?.[0]?.type === "StringLiteral") {
782
+ const spec = o.arguments[0].value;
783
+ if (typeof spec === "string") out.push(spec);
784
+ }
785
+ for (const key of Object.keys(o)) {
786
+ if (key === "loc" || key === "start" || key === "end") continue;
787
+ const v = o[key];
788
+ if (Array.isArray(v)) v.forEach(visit);
789
+ else if (v && typeof v === "object") visit(v);
790
+ }
791
+ };
792
+ visit(node);
793
+ }
794
+ function buildModulesForFile(source, fromFile, load, evalDep, depth, maxDepth, onMissing) {
795
+ const modules = {};
796
+ for (const spec of importSpecs(source)) {
797
+ if (spec.startsWith(".") || spec.startsWith("/")) {
798
+ const childPath = resolveRel(spec, fromFile);
799
+ if (!childPath) {
800
+ onMissing(spec, fromFile, relCandidates(spec, fromFile));
801
+ continue;
802
+ }
803
+ modules[spec] = evalDep(childPath, spec, fromFile, depth + 1);
804
+ } else if (!spec.startsWith("node:")) {
805
+ const bare = bareSpecToAbsModules(spec, fromFile);
806
+ if (bare) modules[spec] = bare;
807
+ }
808
+ }
809
+ void maxDepth;
810
+ return modules;
811
+ }
812
+ var absModuleCache = /* @__PURE__ */ new Map();
813
+ function clearAbsModuleCache() {
814
+ absModuleCache.clear();
815
+ }
816
+ function evictAbsModuleCacheFiles(paths) {
817
+ for (const p of paths) absModuleCache.delete(p);
818
+ }
819
+ function moduleLabel(p) {
820
+ const parts = p.split(/[/\\]/);
821
+ return parts[parts.length - 1] || p;
822
+ }
823
+ function evalAbsModuleGraph(entrySource, entryFile, opts = {}) {
824
+ const load = opts.loadModule ?? defaultAbsLoadModule;
825
+ const maxDepth = opts.maxDepth ?? 16;
826
+ const cache = /* @__PURE__ */ new Map();
827
+ const loading = [];
828
+ const issues = [];
829
+ const seenIssue = /* @__PURE__ */ new Set();
830
+ const pushIssue = (kind, label, reason) => {
831
+ const key = `${kind}:${label}`;
832
+ if (seenIssue.has(key)) return;
833
+ seenIssue.add(key);
834
+ issues.push({ kind, label, reason });
835
+ };
836
+ function evalDep(absPath, spec, fromFile, depth) {
837
+ const cycleIndex = loading.indexOf(absPath);
838
+ if (cycleIndex !== -1) {
839
+ const chain = [...loading.slice(cycleIndex), absPath];
840
+ pushIssue(
841
+ "cycle",
842
+ moduleLabel(absPath),
843
+ `Circular module load: ${chain.join(" -> ")} (bindings inside the cycle resolve to their partially evaluated types)`
844
+ );
845
+ return cache.get(absPath) ?? { named: {} };
846
+ }
847
+ const shared = absModuleCache.get(absPath);
848
+ if (shared) {
849
+ try {
850
+ const st = statSync2(absPath);
851
+ if (st.mtimeMs === shared.mtimeMs && st.size === shared.size) {
852
+ for (const iss of shared.issues) pushIssue(iss.kind, iss.label, iss.reason);
853
+ cache.set(absPath, shared.exports);
854
+ return shared.exports;
855
+ }
856
+ } catch {
857
+ }
858
+ absModuleCache.delete(absPath);
859
+ }
860
+ if (cache.has(absPath)) return cache.get(absPath);
861
+ if (depth > maxDepth) {
862
+ const chain = [...loading, absPath];
863
+ pushIssue(
864
+ "depth",
865
+ moduleLabel(absPath),
866
+ `Module load chain too deep (depth ${chain.length} > ${maxDepth} max): ${chain.join(" -> ")} (loading truncated, deeper modules typed as unknown)`
867
+ );
868
+ const empty = { named: {} };
869
+ cache.set(absPath, empty);
870
+ return empty;
871
+ }
872
+ cache.set(absPath, { named: {} });
873
+ loading.push(absPath);
874
+ const source = load(spec, fromFile);
875
+ if (source === void 0) {
876
+ pushIssue(
877
+ "missing",
878
+ spec,
879
+ `Module file not found for '${spec}' (from ${moduleLabel(fromFile)}); tried: ${absPath}`
880
+ );
881
+ const empty = { named: {} };
882
+ cache.set(absPath, empty);
883
+ loading.pop();
884
+ return empty;
885
+ }
886
+ const issueStart = issues.length;
887
+ const modules2 = buildModulesForFile(
888
+ source,
889
+ absPath,
890
+ load,
891
+ evalDep,
892
+ depth,
893
+ maxDepth,
894
+ (spec2, fromFile2, tried) => {
895
+ pushIssue(
896
+ "missing",
897
+ spec2,
898
+ `Module file not found for '${spec2}' (from ${moduleLabel(fromFile2)}); tried: ${tried.join(", ")}`
899
+ );
900
+ }
901
+ );
902
+ let exports;
903
+ try {
904
+ const file = parse3(source);
905
+ const { env } = evalProgramAbs(source, { file, modules: modules2 });
906
+ exports = collectAbsExports(file, env, modules2);
907
+ } catch {
908
+ exports = { named: {} };
909
+ } finally {
910
+ loading.pop();
911
+ }
912
+ cache.set(absPath, exports);
913
+ let fingerprint;
914
+ try {
915
+ const st = statSync2(absPath);
916
+ fingerprint = { mtimeMs: st.mtimeMs, size: st.size };
917
+ } catch {
918
+ }
919
+ if (fingerprint) {
920
+ absModuleCache.set(absPath, {
921
+ ...fingerprint,
922
+ exports,
923
+ issues: issues.slice(issueStart)
924
+ });
925
+ }
926
+ return exports;
927
+ }
928
+ const modules = buildModulesForFile(
929
+ entrySource,
930
+ entryFile,
931
+ load,
932
+ evalDep,
933
+ 0,
934
+ maxDepth,
935
+ (spec, fromFile, tried) => {
936
+ pushIssue(
937
+ "missing",
938
+ spec,
939
+ `Module file not found for '${spec}' (from ${moduleLabel(fromFile)}); tried: ${tried.join(", ")}`
940
+ );
941
+ }
942
+ );
943
+ return { modules, byPath: cache, issues };
944
+ }
945
+ function evalProgramAbsWithModules(source, entryFile, opts = {}) {
946
+ const { modules } = evalAbsModuleGraph(source, entryFile, opts);
947
+ return evalProgramAbs(source, {
948
+ file: opts.file,
949
+ modules,
950
+ seedVars: opts.seedVars,
951
+ seedFns: opts.seedFns
952
+ });
953
+ }
954
+ function collectAbsBindingsFromGraph(source, filePath, opts = {}) {
955
+ const out = /* @__PURE__ */ new Map();
956
+ try {
957
+ const { env } = evalProgramAbsWithModules(source, filePath, opts);
958
+ for (const [k, v] of env.vars) {
959
+ out.set(k, v);
960
+ }
961
+ for (const [name, impl] of env.fns) {
962
+ if (!out.has(name)) {
963
+ out.set(
964
+ name,
965
+ absFunction3(impl.params, { body: impl.body, async: impl.async, env })
966
+ );
967
+ }
968
+ }
969
+ } catch {
970
+ }
971
+ return out;
972
+ }
973
+
974
+ // src/bpath-run.ts
975
+ import {
976
+ runTranspiled,
977
+ callTranspiledExport,
978
+ callTranspiledExportFull,
979
+ setBCallCollector,
980
+ setMemberDiagCollector,
981
+ setAbsTruncationCollector,
982
+ typeValueToAbs as typeValueToAbs4,
983
+ createEnvironment,
984
+ stableAnalyzeKeySource,
985
+ formatAbs as formatAbs2,
986
+ hashSource,
987
+ getFnImpl as getFnImpl2
988
+ } from "@nudojs/core";
989
+ import { parse as parse4, extractInlineDirectives } from "@nudojs/parser";
990
+ import { loadEnvs } from "@nudojs/cli/evaluator";
991
+
992
+ // src/env-to-abs.ts
993
+ import {
994
+ typeValueToAbs as typeValueToAbs3,
995
+ absToTypeValue,
996
+ getFnSig as getFnSig2,
997
+ absFunction as absFunction4,
998
+ objOf
999
+ } from "@nudojs/core";
1000
+ function envValueToAbs(tv) {
1001
+ if (!tv) return typeValueToAbs3({ kind: "unknown" });
1002
+ if (tv.kind === "function") {
1003
+ const sig = getFnSig2(tv);
1004
+ if (sig?.impl) {
1005
+ const params = tv.kind === "function" && tv.params?.length ? tv.params : sig.paramTypes.map((_, i) => `_arg${i}`);
1006
+ const dummyBody = {
1007
+ type: "BlockStatement",
1008
+ body: [],
1009
+ directives: []
1010
+ };
1011
+ const impl = sig.impl;
1012
+ return absFunction4(params, {
1013
+ body: dummyBody,
1014
+ apply: (args) => {
1015
+ const tvArgs = args.map((a) => absToTypeValue(a));
1016
+ try {
1017
+ const r = impl(tvArgs);
1018
+ if (!r) return typeValueToAbs3(sig.returnType);
1019
+ return envValueToAbs(r);
1020
+ } catch {
1021
+ return typeValueToAbs3(sig.returnType);
1022
+ }
1023
+ }
1024
+ });
1025
+ }
1026
+ }
1027
+ if (tv.kind === "object") {
1028
+ const slots = {};
1029
+ for (const [k, v] of Object.entries(tv.properties)) {
1030
+ slots[k] = { value: envValueToAbs(v) };
1031
+ }
1032
+ return objOf(slots);
1033
+ }
1034
+ return typeValueToAbs3(tv);
1035
+ }
1036
+
1037
+ // src/analysis-file-cache.ts
1038
+ var analysisByFile = /* @__PURE__ */ new Map();
1039
+ var MAX_ANALYSIS_FILE_CACHE = 64;
1040
+ function clearAnalysisFileCache() {
1041
+ analysisByFile.clear();
1042
+ }
1043
+ function getAnalysisFileCacheSize() {
1044
+ return analysisByFile.size;
1045
+ }
1046
+ function analysisCacheGet(filePath, source, auxKey) {
1047
+ const e = analysisByFile.get(filePath);
1048
+ if (!e) return void 0;
1049
+ if (e.source !== source || e.auxKey !== auxKey) return void 0;
1050
+ analysisByFile.delete(filePath);
1051
+ analysisByFile.set(filePath, e);
1052
+ return e.value;
1053
+ }
1054
+ function analysisCacheSet(filePath, source, auxKey, value) {
1055
+ if (analysisByFile.size >= MAX_ANALYSIS_FILE_CACHE && !analysisByFile.has(filePath)) {
1056
+ const oldest = analysisByFile.keys().next().value;
1057
+ if (oldest !== void 0) analysisByFile.delete(oldest);
1058
+ }
1059
+ analysisByFile.set(filePath, { source, auxKey, value });
1060
+ }
1061
+ function evictAnalysisFileCacheForFiles(files) {
1062
+ let n = 0;
1063
+ for (const f of files) {
1064
+ if (analysisByFile.delete(f)) n++;
1065
+ }
1066
+ return n;
1067
+ }
1068
+
1069
+ // src/fn-analysis-cache.ts
1070
+ var fnAnalysisCache = /* @__PURE__ */ new Map();
1071
+ var MAX_FN_ANALYSIS_CACHE = 1024;
1072
+ function clearFnAnalysisCache() {
1073
+ fnAnalysisCache.clear();
1074
+ }
1075
+ function fnAnalysisCacheGet(key) {
1076
+ const hit = fnAnalysisCache.get(key);
1077
+ if (hit !== void 0) {
1078
+ fnAnalysisCache.delete(key);
1079
+ fnAnalysisCache.set(key, hit);
1080
+ }
1081
+ return hit;
1082
+ }
1083
+ function fnAnalysisCacheSet(key, value) {
1084
+ if (fnAnalysisCache.size >= MAX_FN_ANALYSIS_CACHE && !fnAnalysisCache.has(key)) {
1085
+ const oldest = fnAnalysisCache.keys().next().value;
1086
+ if (oldest !== void 0) fnAnalysisCache.delete(oldest);
1087
+ }
1088
+ fnAnalysisCache.set(key, value);
1089
+ }
1090
+ function evictFnAnalysisCacheForFiles(files) {
1091
+ if (files.length === 0 || fnAnalysisCache.size === 0) return 0;
1092
+ const prefixes = files.map((f) => `${f}\0`);
1093
+ let n = 0;
1094
+ for (const key of [...fnAnalysisCache.keys()]) {
1095
+ if (prefixes.some((p) => key.startsWith(p))) {
1096
+ fnAnalysisCache.delete(key);
1097
+ n++;
1098
+ }
1099
+ }
1100
+ return n;
1101
+ }
1102
+ function caseDirectiveKey(directives, typeValueToString5) {
1103
+ return directives.map((d) => {
1104
+ if (d.kind === "case") {
1105
+ const args = (d.args ?? []).map((a) => typeValueToString5(a)).join(",");
1106
+ return `c:${d.name ?? ""}:${args}`;
1107
+ }
1108
+ return d.kind;
1109
+ }).join("|");
1110
+ }
1111
+
1112
+ // src/bpath-run.ts
1113
+ function absSeedPart(a) {
1114
+ const impl = getFnImpl2(a);
1115
+ if (impl?.fingerprint) return impl.fingerprint;
1116
+ if (a.shape.k === "fn") {
1117
+ const ret = impl?.env?.vars?.get("__nudo_mock_ret");
1118
+ if (ret) {
1119
+ try {
1120
+ return `ret=${formatAbs2(ret)}`;
1121
+ } catch {
1122
+ return "ret=?";
1123
+ }
1124
+ }
1125
+ if (impl?.apply) return "apply";
1126
+ }
1127
+ try {
1128
+ return formatAbs2(a);
1129
+ } catch {
1130
+ return "?";
1131
+ }
1132
+ }
1133
+ function seedFnPart(name, fn) {
1134
+ if (fn.fingerprint) return `${name}:${fn.fingerprint}`;
1135
+ const body = fn.body;
1136
+ const bodyKey = body && typeof body.start === "number" && typeof body.end === "number" ? `${body.start}:${body.end}:${body.type ?? ""}` : String(body?.type ?? "?");
1137
+ return `${name}(${fn.params.join(",")})@${bodyKey}`;
1138
+ }
1139
+ function mockSeedFingerprint(mocks, seedFns) {
1140
+ const parts = [];
1141
+ if (mocks) {
1142
+ const names = Object.keys(mocks).sort();
1143
+ for (const n of names) parts.push(`${n}=${absSeedPart(mocks[n])}`);
1144
+ }
1145
+ if (seedFns) {
1146
+ const names = Object.keys(seedFns).sort();
1147
+ for (const n of names) parts.push(`fn:${seedFnPart(n, seedFns[n])}`);
1148
+ }
1149
+ if (parts.length === 0) return "-";
1150
+ return hashSource(parts.join(";"));
1151
+ }
1152
+ function collectEnvGlobals(envNames) {
1153
+ if (envNames.length === 0) return {};
1154
+ const env = createEnvironment();
1155
+ try {
1156
+ loadEnvs(envNames, env);
1157
+ } catch {
1158
+ return {};
1159
+ }
1160
+ const out = {};
1161
+ for (const [k, v] of Object.entries(env.getOwnBindings())) {
1162
+ out[k] = envValueToAbs(v);
1163
+ }
1164
+ return out;
1165
+ }
1166
+ function collectEnvModules(envNames) {
1167
+ if (envNames.length === 0) return {};
1168
+ const env = createEnvironment();
1169
+ let mods = {};
1170
+ try {
1171
+ mods = loadEnvs(envNames, env).modules;
1172
+ } catch {
1173
+ return {};
1174
+ }
1175
+ const out = {};
1176
+ for (const [spec, exports] of Object.entries(mods)) {
1177
+ const named = {};
1178
+ for (const [k, v] of Object.entries(exports)) {
1179
+ named[k] = envValueToAbs(v);
1180
+ }
1181
+ out[spec] = { named };
1182
+ }
1183
+ return out;
1184
+ }
1185
+ function collectBPathReplacements(source) {
1186
+ const targets = [];
1187
+ const values = {};
1188
+ const asTargets = [];
1189
+ const asValues = {};
1190
+ let i = 0;
1191
+ try {
1192
+ const file = parse4(source);
1193
+ const visitStmts = (stmts) => {
1194
+ for (const stmt of stmts) {
1195
+ if (!stmt || typeof stmt !== "object") continue;
1196
+ const loc = stmt.loc;
1197
+ const dirs = extractInlineDirectives(stmt);
1198
+ for (const d of dirs) {
1199
+ if (d.kind === "replace") {
1200
+ const varName = `__rep${i++}`;
1201
+ targets.push({
1202
+ target: d.targetSource,
1203
+ varName,
1204
+ stmtStart: loc?.start.line,
1205
+ stmtEnd: loc?.end.line
1206
+ });
1207
+ values[varName] = typeValueToAbs4(d.typeExpr);
1208
+ } else if (d.kind === "as" && loc) {
1209
+ const varName = `__as${i++}`;
1210
+ asTargets.push({
1211
+ varName,
1212
+ stmtStart: loc.start.line,
1213
+ stmtEnd: loc.end.line
1214
+ });
1215
+ asValues[varName] = typeValueToAbs4(d.typeExpr);
1216
+ }
1217
+ }
1218
+ const s = stmt;
1219
+ if (s.type === "BlockStatement" && Array.isArray(s.body)) visitStmts(s.body);
1220
+ if (s.type === "FunctionDeclaration" || s.type === "FunctionExpression") {
1221
+ if (s.body) visitStmts([s.body]);
1222
+ }
1223
+ if (s.type === "ExportNamedDeclaration" && s.declaration) {
1224
+ visitStmts([s.declaration]);
1225
+ }
1226
+ if (s.type === "IfStatement") {
1227
+ if (s.consequent) visitStmts([s.consequent]);
1228
+ if (s.alternate) visitStmts([s.alternate]);
1229
+ }
1230
+ }
1231
+ };
1232
+ visitStmts(file.program.body);
1233
+ } catch {
1234
+ }
1235
+ return { targets, values, asTargets, asValues };
1236
+ }
1237
+ function isBPathCapable(source, envNames = []) {
1238
+ void envNames;
1239
+ if (/(^|[^.\w$])this\s*\./.test(source) && !/\bclass\s+/.test(source)) return false;
1240
+ return true;
1241
+ }
1242
+ var bRunByFile = /* @__PURE__ */ new Map();
1243
+ var MAX_B_RUN_CACHE = 32;
1244
+ function clearBPathCache() {
1245
+ bRunByFile.clear();
1246
+ clearAnalysisFileCache();
1247
+ clearFnAnalysisCache();
1248
+ }
1249
+ function evictBPathCacheForFiles(files) {
1250
+ let n = 0;
1251
+ for (const f of files) {
1252
+ if (bRunByFile.delete(f)) n++;
1253
+ }
1254
+ return n;
1255
+ }
1256
+ function bPathCacheSet(filePath, stableSource, mode, envKey, mockKey, value) {
1257
+ if (bRunByFile.size >= MAX_B_RUN_CACHE && !bRunByFile.has(filePath)) {
1258
+ const oldest = bRunByFile.keys().next().value;
1259
+ if (oldest !== void 0) bRunByFile.delete(oldest);
1260
+ }
1261
+ bRunByFile.set(filePath, { stableSource, mode, envKey, mockKey, value });
1262
+ }
1263
+ function tryRunBPath(source, filePath, opts = {}) {
1264
+ if (!isBPathCapable(source, opts.envNames ?? [])) return void 0;
1265
+ const mode = opts.mode ?? "analyze";
1266
+ const envKey = (opts.envNames ?? []).join(",");
1267
+ const mockKey = mockSeedFingerprint(opts.mocks);
1268
+ const stable = stableAnalyzeKeySource(source);
1269
+ const cached = bRunByFile.get(filePath);
1270
+ if (cached && cached.stableSource === stable && cached.mode === mode && cached.envKey === envKey && cached.mockKey === mockKey) {
1271
+ bRunByFile.delete(filePath);
1272
+ bRunByFile.set(filePath, cached);
1273
+ return cached.value ?? void 0;
1274
+ }
1275
+ let out = null;
1276
+ try {
1277
+ const memberDiags = [];
1278
+ const truncated = /* @__PURE__ */ new Set();
1279
+ const topCalls = [];
1280
+ setMemberDiagCollector((d) => memberDiags.push(d));
1281
+ setAbsTruncationCollector((label) => truncated.add(label));
1282
+ setBCallCollector((r) => topCalls.push(r));
1283
+ try {
1284
+ const { modules: graphMods, issues } = evalAbsModuleGraph(source, filePath);
1285
+ const envMods = collectEnvModules(opts.envNames ?? []);
1286
+ const modules = { ...envMods, ...graphMods };
1287
+ const { targets, values, asTargets, asValues } = collectBPathReplacements(source);
1288
+ const envGlobals = {
1289
+ ...collectEnvGlobals(opts.envNames ?? []),
1290
+ ...opts.mocks ?? {}
1291
+ };
1292
+ const exports = runTranspiled(source, {
1293
+ modules,
1294
+ maxLoopIters: opts.maxLoopIters,
1295
+ mode: opts.mode ?? "analyze",
1296
+ replacementTargets: targets.length ? targets : void 0,
1297
+ replacements: targets.length ? values : void 0,
1298
+ asOverrideTargets: asTargets.length ? asTargets : void 0,
1299
+ asOverrides: asTargets.length ? asValues : void 0,
1300
+ envGlobals: Object.keys(envGlobals).length ? envGlobals : void 0
1301
+ });
1302
+ out = {
1303
+ exports,
1304
+ modules,
1305
+ memberDiags: memberDiags.length ? memberDiags : void 0,
1306
+ moduleIssues: issues.length ? issues : void 0,
1307
+ truncatedFns: truncated.size ? [...truncated] : void 0,
1308
+ calls: topCalls.length ? topCalls : void 0
1309
+ };
1310
+ } finally {
1311
+ setMemberDiagCollector(null);
1312
+ setAbsTruncationCollector(null);
1313
+ setBCallCollector(null);
1314
+ }
1315
+ } catch {
1316
+ out = null;
1317
+ }
1318
+ bPathCacheSet(filePath, stable, mode, envKey, mockKey, out);
1319
+ return out ?? void 0;
1320
+ }
1321
+ function tryBPathCallFull(source, filePath, fnName, args, opts = {}) {
1322
+ const run = tryRunBPath(source, filePath, { envNames: opts.envNames, mocks: opts.mocks });
1323
+ if (!run) return void 0;
1324
+ if (!(fnName in run.exports)) return void 0;
1325
+ const collected = [...run.calls ?? []];
1326
+ const memberDiags = [];
1327
+ if (opts.collectCalls) {
1328
+ setBCallCollector((r) => collected.push(r));
1329
+ }
1330
+ if (opts.collectMemberDiags ?? true) {
1331
+ setMemberDiagCollector((d) => memberDiags.push(d));
1332
+ }
1333
+ try {
1334
+ const full = callTranspiledExportFull(run.exports, fnName, args);
1335
+ const all = [...run.memberDiags ?? [], ...memberDiags];
1336
+ return {
1337
+ ...full,
1338
+ calls: opts.collectCalls || run.calls?.length ? collected : void 0,
1339
+ memberDiags: all.length ? all : void 0,
1340
+ moduleIssues: run.moduleIssues,
1341
+ truncatedFns: run.truncatedFns
1342
+ };
1343
+ } finally {
1344
+ if (opts.collectCalls) setBCallCollector(null);
1345
+ setMemberDiagCollector(null);
1346
+ }
1347
+ }
1348
+ function tryBPathCall(source, filePath, fnName, args, opts = {}) {
1349
+ const full = tryBPathCallFull(source, filePath, fnName, args, opts);
1350
+ if (!full) return void 0;
1351
+ const r = full.result;
1352
+ if (!r) return void 0;
1353
+ if (r.shape.k === "never" && full.throws.shape.k !== "never") {
1354
+ return void 0;
1355
+ }
1356
+ if (r.shape.k === "unknown" && !r.term) return void 0;
1357
+ return r;
1358
+ }
1359
+
1360
+ // src/bpath-diagnostics.ts
1361
+ import { parseSource } from "@nudojs/core";
1362
+ function locOf(node) {
1363
+ if (!node.loc) return null;
1364
+ return {
1365
+ start: { line: node.loc.start.line, column: node.loc.start.column },
1366
+ end: { line: node.loc.end.line, column: node.loc.end.column }
1367
+ };
1368
+ }
1369
+ function isExit(stmt) {
1370
+ return stmt.type === "ReturnStatement" || stmt.type === "ThrowStatement";
1371
+ }
1372
+ function walkUnreachable(node, out) {
1373
+ const n = node;
1374
+ const checkBlock = (block) => {
1375
+ if (!block) return;
1376
+ const body = block.body ?? (Array.isArray(block) ? block : null);
1377
+ if (!Array.isArray(body)) return;
1378
+ for (let i = 0; i < body.length; i++) {
1379
+ walkUnreachable(body[i], out);
1380
+ if (isExit(body[i])) {
1381
+ for (let j = i + 1; j < body.length; j++) {
1382
+ const r = locOf(body[j]);
1383
+ if (r) out.push({ range: r });
1384
+ }
1385
+ break;
1386
+ }
1387
+ }
1388
+ };
1389
+ switch (n.type) {
1390
+ case "Program":
1391
+ case "BlockStatement":
1392
+ checkBlock(n.body);
1393
+ break;
1394
+ case "FunctionDeclaration":
1395
+ case "FunctionExpression":
1396
+ case "ArrowFunctionExpression":
1397
+ if (n.body) walkUnreachable(n.body, out);
1398
+ break;
1399
+ case "IfStatement":
1400
+ if (n.consequent) walkUnreachable(n.consequent, out);
1401
+ if (n.alternate) walkUnreachable(n.alternate, out);
1402
+ break;
1403
+ case "ForStatement":
1404
+ case "ForInStatement":
1405
+ case "ForOfStatement":
1406
+ case "WhileStatement":
1407
+ case "DoWhileStatement":
1408
+ if (n.body) walkUnreachable(n.body, out);
1409
+ break;
1410
+ case "TryStatement":
1411
+ if (n.block) walkUnreachable(n.block, out);
1412
+ if (n.handler?.body) walkUnreachable(n.handler.body, out);
1413
+ if (n.finalizer) walkUnreachable(n.finalizer, out);
1414
+ break;
1415
+ case "SwitchStatement":
1416
+ for (const c of n.cases ?? []) {
1417
+ checkBlock(c.consequent);
1418
+ }
1419
+ break;
1420
+ case "ExportNamedDeclaration":
1421
+ case "ExportDefaultDeclaration": {
1422
+ const decl = n.declaration;
1423
+ if (decl) walkUnreachable(decl, out);
1424
+ break;
1425
+ }
1426
+ default:
1427
+ break;
1428
+ }
1429
+ }
1430
+ function collectDeclared(file) {
1431
+ const names = /* @__PURE__ */ new Set();
1432
+ const addId = (id) => {
1433
+ if (!id) return;
1434
+ if (id.type === "Identifier") names.add(id.name);
1435
+ if (id.type === "ObjectPattern") {
1436
+ for (const p of id.properties) {
1437
+ addId(p.value);
1438
+ }
1439
+ }
1440
+ if (id.type === "ArrayPattern") {
1441
+ for (const el of id.elements) addId(el);
1442
+ }
1443
+ };
1444
+ const visit = (node) => {
1445
+ if (!node || typeof node !== "object") return;
1446
+ const o = node;
1447
+ switch (o.type) {
1448
+ case "FunctionDeclaration":
1449
+ case "ClassDeclaration":
1450
+ addId(o.id);
1451
+ break;
1452
+ case "VariableDeclarator":
1453
+ addId(o.id);
1454
+ break;
1455
+ case "ImportSpecifier":
1456
+ case "ImportDefaultSpecifier":
1457
+ case "ImportNamespaceSpecifier":
1458
+ addId(o.local);
1459
+ break;
1460
+ default:
1461
+ break;
1462
+ }
1463
+ if (o.params && Array.isArray(o.params)) {
1464
+ for (const p of o.params) addId(p);
1465
+ }
1466
+ for (const key of Object.keys(o)) {
1467
+ if (key === "loc" || key === "start" || key === "end") continue;
1468
+ const v = o[key];
1469
+ if (Array.isArray(v)) v.forEach(visit);
1470
+ else if (v && typeof v === "object") visit(v);
1471
+ }
1472
+ };
1473
+ visit(file);
1474
+ return names;
1475
+ }
1476
+ var KNOWN_GLOBALS = /* @__PURE__ */ new Set([
1477
+ "Object",
1478
+ "Array",
1479
+ "String",
1480
+ "Number",
1481
+ "Boolean",
1482
+ "Symbol",
1483
+ "Math",
1484
+ "JSON",
1485
+ "Promise",
1486
+ "Error",
1487
+ "TypeError",
1488
+ "RangeError",
1489
+ "SyntaxError",
1490
+ "RegExp",
1491
+ "Date",
1492
+ "Map",
1493
+ "Set",
1494
+ "WeakMap",
1495
+ "WeakSet",
1496
+ "console",
1497
+ "undefined",
1498
+ "NaN",
1499
+ "Infinity",
1500
+ "globalThis",
1501
+ "parseInt",
1502
+ "parseFloat",
1503
+ "isNaN",
1504
+ "isFinite"
1505
+ ]);
1506
+ function isFreeUnknown(name, declared, seen, known) {
1507
+ return !declared.has(name) && !known.has(name) && !name.startsWith("$") && !seen.has(name);
1508
+ }
1509
+ function walkBuiltinUnknown(node, declared, out, seen, known, parentKey) {
1510
+ const n = node;
1511
+ const flag = (idNode, name) => {
1512
+ if (!isFreeUnknown(name, declared, seen, known)) return;
1513
+ const range = locOf(idNode) ?? locOf(node);
1514
+ if (range) {
1515
+ out.push({ name, range });
1516
+ seen.add(name);
1517
+ }
1518
+ };
1519
+ if (n.type === "CallExpression" && n.callee?.type === "Identifier") {
1520
+ flag(n.callee, n.callee.name);
1521
+ } else if (n.type === "Identifier" && typeof n.name === "string" && parentKey !== "key" && parentKey !== "property" && parentKey !== "local" && parentKey !== "id") {
1522
+ flag(node, n.name);
1523
+ }
1524
+ for (const key of Object.keys(n)) {
1525
+ if (key === "loc" || key === "start" || key === "end") continue;
1526
+ const v = n[key];
1527
+ if (Array.isArray(v)) {
1528
+ v.forEach((x) => {
1529
+ if (x && typeof x === "object" && "type" in x) {
1530
+ walkBuiltinUnknown(x, declared, out, seen, known, key);
1531
+ }
1532
+ });
1533
+ } else if (v && typeof v === "object" && "type" in v) {
1534
+ walkBuiltinUnknown(v, declared, out, seen, known, key);
1535
+ }
1536
+ }
1537
+ }
1538
+ function collectBPathDiagnostics(source, extraKnown) {
1539
+ let file;
1540
+ try {
1541
+ file = parseSource(source);
1542
+ } catch {
1543
+ return { unreachable: [], builtinUnknown: [] };
1544
+ }
1545
+ const unreachable = [];
1546
+ const root = file.program ?? file;
1547
+ walkUnreachable(root, unreachable);
1548
+ const declared = collectDeclared(file);
1549
+ const known = new Set(KNOWN_GLOBALS);
1550
+ if (extraKnown) {
1551
+ for (const n of extraKnown) known.add(n);
1552
+ }
1553
+ const builtinUnknown = [];
1554
+ walkBuiltinUnknown(file, declared, builtinUnknown, /* @__PURE__ */ new Set(), known);
1555
+ return { unreachable, builtinUnknown };
1556
+ }
1557
+
1558
+ // src/analyzer.ts
1559
+ import { setAbsTruncationCollector as setAbsTruncationCollector2 } from "@nudojs/core";
25
1560
  function resolveModule(source, fromDir) {
26
1561
  const extensions = [".js", ".ts", ".mjs"];
27
- const basePath = resolve(fromDir, source);
1562
+ const nudoPath = resolveNpmNudo(source, fromDir);
1563
+ if (nudoPath) {
1564
+ const src = readFileSync4(nudoPath, "utf-8");
1565
+ return { ast: parse5(src), filePath: nudoPath };
1566
+ }
1567
+ const basePath = resolve4(fromDir, source);
28
1568
  for (const ext of ["", ...extensions]) {
29
1569
  const candidate = basePath + ext;
30
- if (existsSync(candidate)) {
31
- const src = readFileSync(candidate, "utf-8");
32
- return { ast: parse(src), filePath: candidate };
1570
+ if (!existsSync3(candidate)) continue;
1571
+ if (statSync3(candidate).isDirectory()) {
1572
+ let entry = null;
1573
+ const pkgPath = resolve4(candidate, "package.json");
1574
+ if (existsSync3(pkgPath)) {
1575
+ try {
1576
+ const main = JSON.parse(readFileSync4(pkgPath, "utf-8")).main;
1577
+ if (typeof main === "string") {
1578
+ for (const e of ["", ...extensions]) {
1579
+ const p = resolve4(candidate, main + e);
1580
+ if (existsSync3(p) && statSync3(p).isFile()) {
1581
+ entry = p;
1582
+ break;
1583
+ }
1584
+ }
1585
+ }
1586
+ } catch {
1587
+ }
1588
+ }
1589
+ if (!entry) {
1590
+ for (const e of ["", ...extensions]) {
1591
+ const p = resolve4(candidate, "index" + e);
1592
+ if (existsSync3(p) && statSync3(p).isFile()) {
1593
+ entry = p;
1594
+ break;
1595
+ }
1596
+ }
1597
+ }
1598
+ if (!entry) return null;
1599
+ const src2 = readFileSync4(entry, "utf-8");
1600
+ return { ast: parse5(src2), filePath: entry };
1601
+ }
1602
+ if (candidate.endsWith(".json")) {
1603
+ try {
1604
+ return { ast: parse5("module.exports = undefined;"), filePath: candidate, json: JSON.parse(readFileSync4(candidate, "utf-8")) };
1605
+ } catch {
1606
+ return null;
1607
+ }
1608
+ }
1609
+ const src = readFileSync4(candidate, "utf-8");
1610
+ return { ast: parse5(src), filePath: candidate };
1611
+ }
1612
+ return null;
1613
+ }
1614
+ function resolveImportPath(specifier, fromDir) {
1615
+ const basePath = resolve4(fromDir, specifier);
1616
+ for (const ext of ["", ".js", ".ts", ".mjs"]) {
1617
+ const candidate = basePath + ext;
1618
+ if (existsSync3(candidate)) return candidate;
1619
+ }
1620
+ return null;
1621
+ }
1622
+ function buildModuleGraph(files, cache) {
1623
+ const imports = /* @__PURE__ */ new Map();
1624
+ const dependents = /* @__PURE__ */ new Map();
1625
+ const edge = (from, to) => {
1626
+ if (!imports.has(from)) imports.set(from, /* @__PURE__ */ new Set());
1627
+ if (!dependents.has(to)) dependents.set(to, /* @__PURE__ */ new Set());
1628
+ imports.get(from).add(to);
1629
+ dependents.get(to).add(from);
1630
+ };
1631
+ for (const file of files) {
1632
+ if (!imports.has(file)) imports.set(file, /* @__PURE__ */ new Set());
1633
+ if (!dependents.has(file)) dependents.set(file, /* @__PURE__ */ new Set());
1634
+ let edges;
1635
+ if (cache) {
1636
+ let stat = null;
1637
+ try {
1638
+ stat = statSync3(file);
1639
+ } catch {
1640
+ }
1641
+ const cached = stat ? cache.get(file) : void 0;
1642
+ if (stat && cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
1643
+ edges = cached.edges;
1644
+ } else {
1645
+ edges = extractImportEdges(file);
1646
+ if (stat) cache.set(file, { mtimeMs: stat.mtimeMs, size: stat.size, edges });
1647
+ }
1648
+ } else {
1649
+ edges = extractImportEdges(file);
1650
+ }
1651
+ for (const to of edges) edge(file, to);
1652
+ }
1653
+ return { imports, dependents };
1654
+ }
1655
+ function extractImportEdges(file) {
1656
+ const edges = [];
1657
+ let ast;
1658
+ try {
1659
+ ast = parse5(readFileSync4(file, "utf-8"));
1660
+ } catch {
1661
+ return edges;
1662
+ }
1663
+ for (const stmt of ast.program.body) {
1664
+ if (stmt.type !== "ImportDeclaration") continue;
1665
+ const specifier = stmt.source.value;
1666
+ if (!specifier.startsWith(".") && !specifier.startsWith("/")) continue;
1667
+ const resolved = resolveImportPath(specifier, dirname5(file));
1668
+ if (resolved) edges.push(resolved);
1669
+ }
1670
+ return edges;
1671
+ }
1672
+ function computeDirtySet(dependents, changedFile) {
1673
+ const dirty = [];
1674
+ const visited = /* @__PURE__ */ new Set();
1675
+ const queue = [changedFile];
1676
+ while (queue.length > 0) {
1677
+ const file = queue.shift();
1678
+ if (visited.has(file)) continue;
1679
+ visited.add(file);
1680
+ dirty.push(file);
1681
+ for (const dep of dependents.get(file) ?? []) {
1682
+ if (!visited.has(dep)) queue.push(dep);
1683
+ }
1684
+ }
1685
+ return dirty;
1686
+ }
1687
+ function topoSortDirty(imports, dirty) {
1688
+ const inSet = new Set(dirty);
1689
+ const pending = /* @__PURE__ */ new Map();
1690
+ for (const file of dirty) {
1691
+ let count = 0;
1692
+ for (const dep of imports.get(file) ?? []) {
1693
+ if (inSet.has(dep)) count++;
33
1694
  }
1695
+ pending.set(file, count);
34
1696
  }
35
- return null;
1697
+ const ordered = [];
1698
+ const ready = dirty.filter((f) => pending.get(f) === 0);
1699
+ while (ready.length > 0) {
1700
+ const file = ready.shift();
1701
+ ordered.push(file);
1702
+ for (const other of dirty) {
1703
+ if (pending.get(other) === void 0) continue;
1704
+ if ((imports.get(other) ?? /* @__PURE__ */ new Set()).has(file)) {
1705
+ const next = pending.get(other) - 1;
1706
+ pending.set(other, next);
1707
+ if (next === 0) ready.push(other);
1708
+ }
1709
+ }
1710
+ }
1711
+ for (const file of dirty) {
1712
+ if (pending.has(file) && pending.get(file) > 0) ordered.push(file);
1713
+ }
1714
+ return ordered;
36
1715
  }
37
- function applyMocks(directives, env, filePath) {
1716
+ function applyMocks(directives, env, filePath, diagnostics) {
38
1717
  for (const d of directives) {
39
1718
  if (d.kind !== "mock") continue;
40
- if (d.expression) {
1719
+ if (d.arrowFn) {
1720
+ const fnType = T2.fn(d.arrowFn.params, d.arrowFn.body, env);
1721
+ fnType._paramPatterns = d.arrowFn.paramPatterns;
1722
+ env.bind(d.name, fnType);
1723
+ } else if (d.nudoMock) {
1724
+ const typeVal = mockHelperToTypeValue(d.nudoMock, env);
1725
+ env.bind(d.name, typeVal);
1726
+ } else if (d.sinonExpr) {
1727
+ const sinonType = createSinonTypeValue(d.sinonExpr, env);
1728
+ env.bind(d.name, sinonType);
1729
+ } else if (d.expression) {
1730
+ const expr = d.expression.trim();
1731
+ if (expr.includes("(") && expr.includes(")") && !expr.startsWith("T.")) {
1732
+ diagnostics.push({
1733
+ range: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
1734
+ severity: "warning",
1735
+ message: `Mock expression "${expr}" could not be parsed as a known pattern`,
1736
+ code: "nudo:mock-invalid",
1737
+ suggestions: [
1738
+ "Supported formats: stub(), stub().returns(value), spy(), mock()",
1739
+ "Arrow functions: (args) => expression or (args) => { statements; return value; }"
1740
+ ]
1741
+ });
1742
+ }
41
1743
  env.bind(d.name, parseTypeValueExpr(d.expression));
42
1744
  } else if (d.fromPath) {
43
- const mockPath = resolve(dirname(filePath), d.fromPath);
44
- const mockSource = readFileSync(mockPath, "utf-8");
45
- const mockAst = parse(mockSource);
46
- const mockEnv = createEnvironment();
1745
+ const mockPath = resolve4(dirname5(filePath), d.fromPath);
1746
+ const mockSource = readFileSync4(mockPath, "utf-8");
1747
+ const mockAst = parse5(mockSource);
1748
+ const mockEnv = createEnvironment2();
47
1749
  evaluateProgram(mockAst, mockEnv);
48
1750
  const mockVal = mockEnv.lookup(d.name);
49
1751
  env.bind(d.name, mockVal);
50
1752
  }
51
1753
  }
52
1754
  }
1755
+ function createSinonTypeValue(sinonExpr, env) {
1756
+ if (sinonExpr.type === "stub" || sinonExpr.type === "spy") {
1757
+ const body = { type: "BlockStatement", body: [] };
1758
+ const fn = T2.fn(["...args"], body, env);
1759
+ if (sinonExpr.returnValue) {
1760
+ fn._directReturn = sinonExpr.returnValue;
1761
+ } else if (sinonExpr.resolvedValue) {
1762
+ fn._directReturn = T2.promise(sinonExpr.resolvedValue);
1763
+ } else if (sinonExpr.rejectedValue) {
1764
+ fn._directReturn = T2.never;
1765
+ } else {
1766
+ fn._directReturn = T2.unknown;
1767
+ }
1768
+ return fn;
1769
+ }
1770
+ return T2.unknown;
1771
+ }
53
1772
  function locFromNode(node) {
54
1773
  return {
55
1774
  start: { line: node.loc?.start.line ?? 1, column: node.loc?.start.column ?? 0 },
56
1775
  end: { line: node.loc?.end.line ?? 1, column: node.loc?.end.column ?? 0 }
57
1776
  };
58
1777
  }
59
- function analyzeFile(filePath, source, activeCases) {
60
- const ast = parse(source);
1778
+ function extractParamNames(node) {
1779
+ const fn = node.type === "ExportDefaultDeclaration" ? node.declaration : node;
1780
+ if (fn.type === "FunctionDeclaration" || fn.type === "FunctionExpression" || fn.type === "ArrowFunctionExpression") {
1781
+ return fn.params.map((p) => {
1782
+ if (p.type === "Identifier") return p.name;
1783
+ if (p.type === "AssignmentPattern" && p.left.type === "Identifier") return p.left.name;
1784
+ if (p.type === "RestElement" && p.argument.type === "Identifier") return `...${p.argument.name}`;
1785
+ return "_";
1786
+ });
1787
+ }
1788
+ if (fn.type === "VariableDeclaration") {
1789
+ const decl = fn.declarations[0];
1790
+ if (decl.init?.type === "FunctionExpression" || decl.init?.type === "ArrowFunctionExpression") {
1791
+ return decl.init.params.map((p) => {
1792
+ if (p.type === "Identifier") return p.name;
1793
+ if (p.type === "AssignmentPattern" && p.left.type === "Identifier") return p.left.name;
1794
+ if (p.type === "RestElement" && p.argument.type === "Identifier") return `...${p.argument.name}`;
1795
+ return "_";
1796
+ });
1797
+ }
1798
+ }
1799
+ return [];
1800
+ }
1801
+ function resolveFunctionNode(node) {
1802
+ if (node.type === "ExportNamedDeclaration" && node.declaration) return resolveFunctionNode(node.declaration);
1803
+ if (node.type === "ExportDefaultDeclaration") return resolveFunctionNode(node.declaration);
1804
+ if (node.type === "VariableDeclaration") {
1805
+ const init = node.declarations[0]?.init;
1806
+ if (init && (init.type === "ArrowFunctionExpression" || init.type === "FunctionExpression")) return init;
1807
+ }
1808
+ return node;
1809
+ }
1810
+ function isFnExprValue(node) {
1811
+ return !!node && (node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression");
1812
+ }
1813
+ function namedFnExprId(node) {
1814
+ return node.type === "FunctionExpression" && node.id ? node.id.name : null;
1815
+ }
1816
+ function isModuleExportsTarget(node) {
1817
+ return node.type === "MemberExpression" && !node.computed && node.object?.type === "Identifier" && node.object.name === "module" && node.property?.type === "Identifier" && node.property.name === "exports";
1818
+ }
1819
+ function memberPropertyKey(member) {
1820
+ if (member.type !== "MemberExpression" || member.computed) return null;
1821
+ const prop = member.property;
1822
+ if (prop?.type === "Identifier") return prop.name;
1823
+ if (prop?.type === "StringLiteral") return String(prop.value);
1824
+ return null;
1825
+ }
1826
+ function deepestAssignValue(expr) {
1827
+ let cur = expr;
1828
+ while (cur?.type === "AssignmentExpression") cur = cur.right;
1829
+ return cur ?? null;
1830
+ }
1831
+ function assignmentChainName(expr) {
1832
+ let cur = expr;
1833
+ while (cur?.type === "AssignmentExpression") {
1834
+ const target = cur.left;
1835
+ if (target.type === "Identifier") return target.name;
1836
+ if (target.type === "MemberExpression") {
1837
+ if (isModuleExportsTarget(target)) {
1838
+ cur = cur.right;
1839
+ continue;
1840
+ }
1841
+ return memberPropertyKey(target);
1842
+ }
1843
+ return null;
1844
+ }
1845
+ return null;
1846
+ }
1847
+ function collectTopLevelFunctions(ast) {
1848
+ const results = [];
1849
+ if (ast.type !== "File") return results;
1850
+ for (const stmt of ast.program.body) {
1851
+ const decl = resolveFunctionNode(stmt);
1852
+ if (decl.type === "FunctionDeclaration" && decl.id) {
1853
+ results.push({ name: decl.id.name, node: decl, stmt, noDeclaration: false });
1854
+ continue;
1855
+ }
1856
+ if (stmt.type === "VariableDeclaration") {
1857
+ for (const declarator of stmt.declarations) {
1858
+ if (declarator.id?.type === "Identifier" && isFnExprValue(declarator.init)) {
1859
+ results.push({ name: declarator.id.name, node: declarator.init, stmt, noDeclaration: true });
1860
+ }
1861
+ }
1862
+ continue;
1863
+ }
1864
+ if (stmt.type === "ExpressionStatement" && stmt.expression?.type === "AssignmentExpression") {
1865
+ const expr = stmt.expression;
1866
+ const fn = deepestAssignValue(expr);
1867
+ if (isFnExprValue(fn)) {
1868
+ const name = namedFnExprId(fn) ?? assignmentChainName(expr) ?? "default";
1869
+ results.push({ name, node: fn, stmt, noDeclaration: true });
1870
+ }
1871
+ }
1872
+ }
1873
+ return results;
1874
+ }
1875
+ function findSingleModuleExportsFunction(ast) {
1876
+ if (ast.type !== "File") return null;
1877
+ let found = null;
1878
+ for (const stmt of ast.program.body) {
1879
+ if (stmt.type !== "ExpressionStatement") continue;
1880
+ const expr = stmt.expression;
1881
+ if (expr?.type !== "AssignmentExpression") continue;
1882
+ const fn = deepestAssignValue(expr);
1883
+ if (!isFnExprValue(fn)) continue;
1884
+ let targetsModuleExports = false;
1885
+ let cur = expr;
1886
+ while (cur?.type === "AssignmentExpression") {
1887
+ if (isModuleExportsTarget(cur.left)) {
1888
+ targetsModuleExports = true;
1889
+ break;
1890
+ }
1891
+ cur = cur.right;
1892
+ }
1893
+ if (!targetsModuleExports) continue;
1894
+ if (found) return null;
1895
+ found = fn;
1896
+ }
1897
+ return found;
1898
+ }
1899
+ function typeStructureKey(tv) {
1900
+ return typeStructureKeyUncached(tv, /* @__PURE__ */ new Set());
1901
+ }
1902
+ function typeStructureKeyUncached(tv, seen) {
1903
+ const enter = (inner) => {
1904
+ if (inner && typeof inner === "object") {
1905
+ if (seen.has(inner)) return "\xABcycle\xBB";
1906
+ seen.add(inner);
1907
+ const out = typeStructureKeyUncached(inner, seen);
1908
+ seen.delete(inner);
1909
+ return out;
1910
+ }
1911
+ return typeStructureKeyUncached(inner, seen);
1912
+ };
1913
+ switch (tv.kind) {
1914
+ case "literal":
1915
+ return `lit(${typeof tv.value}:${String(tv.value)})`;
1916
+ case "primitive":
1917
+ return `prim(${tv.type})`;
1918
+ case "array":
1919
+ return `arr(${enter(tv.element)})`;
1920
+ case "tuple":
1921
+ return `tup(${tv.elements.map(enter).join(",")})`;
1922
+ case "object":
1923
+ return `obj(${Object.keys(tv.properties).sort().map((k) => `${k}:${enter(tv.properties[k])}`).join(",")})`;
1924
+ case "function":
1925
+ return `fn(${tv.params.join(",")})`;
1926
+ case "promise":
1927
+ return `prom(${enter(tv.value)})`;
1928
+ case "instance":
1929
+ return `inst(${tv.className})`;
1930
+ case "refined":
1931
+ return `ref(${enter(tv.base)})`;
1932
+ case "union":
1933
+ return `uni(${tv.members.map(enter).sort().join("|")})`;
1934
+ default:
1935
+ return tv.kind;
1936
+ }
1937
+ }
1938
+ var MAX_PRECISE_CALLSITE_CASES = 3;
1939
+ var COLLAPSE_LITERAL_THRESHOLD = 4;
1940
+ function widenType(tv) {
1941
+ if (tv.kind === "union") return simplifyUnion(tv.members.map(widenLiteral));
1942
+ return widenLiteral(tv);
1943
+ }
1944
+ function dedupeCallRecords(records) {
1945
+ const seen = /* @__PURE__ */ new Set();
1946
+ const out = [];
1947
+ for (const rec of records) {
1948
+ if (isOversizedRecord(rec)) continue;
1949
+ const key = rec.argTypes.map(typeStructureKey).join(",") + "=>" + typeStructureKey(rec.resultType) + "!" + typeStructureKey(rec.throws);
1950
+ if (seen.has(key)) continue;
1951
+ seen.add(key);
1952
+ out.push(rec);
1953
+ }
1954
+ return out;
1955
+ }
1956
+ var MAX_RECORD_TYPE_NODES = 2e3;
1957
+ function typeNodeCount(tv, seen) {
1958
+ if (tv === null || typeof tv !== "object") return 1;
1959
+ if (seen.has(tv)) return 0;
1960
+ seen.add(tv);
1961
+ switch (tv.kind) {
1962
+ case "array":
1963
+ return 1 + typeNodeCount(tv.element, seen);
1964
+ case "tuple":
1965
+ return 1 + tv.elements.reduce((acc, e) => acc + typeNodeCount(e, seen), 0);
1966
+ case "object": {
1967
+ let n = 1;
1968
+ for (const v of Object.values(tv.properties)) n += typeNodeCount(v, seen);
1969
+ return n;
1970
+ }
1971
+ case "promise":
1972
+ return 1 + typeNodeCount(tv.value, seen);
1973
+ case "union":
1974
+ return 1 + tv.members.reduce((acc, e) => acc + typeNodeCount(e, seen), 0);
1975
+ case "refined":
1976
+ return 1 + typeNodeCount(tv.base, seen);
1977
+ default:
1978
+ return 1;
1979
+ }
1980
+ }
1981
+ function isOversizedRecord(rec) {
1982
+ const seen = /* @__PURE__ */ new Set();
1983
+ for (const a of rec.argTypes) {
1984
+ if (typeNodeCount(a, seen) > MAX_RECORD_TYPE_NODES) return true;
1985
+ }
1986
+ return typeNodeCount(rec.resultType, seen) > MAX_RECORD_TYPE_NODES;
1987
+ }
1988
+ function locFromCallLoc(loc) {
1989
+ const p = loc ?? { line: 0, column: 0 };
1990
+ return { start: { line: p.line, column: p.column }, end: { line: p.line, column: p.column } };
1991
+ }
1992
+ function synthesizeExternalFunctions(records, currentFile) {
1993
+ const groups = /* @__PURE__ */ new Map();
1994
+ for (const rec of records) {
1995
+ if (!rec.targetModule || !rec.targetExport) continue;
1996
+ if (rec.targetModule === currentFile) continue;
1997
+ const key = `${rec.targetModule}\0${rec.targetExport}`;
1998
+ let group = groups.get(key);
1999
+ if (!group) {
2000
+ group = { module: rec.targetModule, exportName: rec.targetExport, records: [] };
2001
+ groups.set(key, group);
2002
+ }
2003
+ group.records.push(rec);
2004
+ }
2005
+ const out = [];
2006
+ for (const { module, exportName, records: records2 } of groups.values()) {
2007
+ const deduped = dedupeCallRecords(records2);
2008
+ const arity = Math.max(...deduped.map((r) => r.argTypes.length));
2009
+ const analysis = {
2010
+ name: exportName,
2011
+ loc: locFromCallLoc(deduped[0].callLoc),
2012
+ paramNames: Array.from({ length: arity }, (_, i) => `arg${i}`),
2013
+ cases: [],
2014
+ fromModule: module
2015
+ };
2016
+ const precise = deduped.slice(0, MAX_PRECISE_CALLSITE_CASES);
2017
+ for (const rec of precise) {
2018
+ analysis.cases.push({
2019
+ name: `call@L${rec.callLoc?.line ?? 0}`,
2020
+ args: rec.argTypes,
2021
+ result: rec.resultType,
2022
+ throws: rec.throws,
2023
+ source: "callsite"
2024
+ });
2025
+ }
2026
+ const remaining = deduped.slice(MAX_PRECISE_CALLSITE_CASES);
2027
+ if (remaining.length > 0) {
2028
+ analysis.cases.push({
2029
+ name: "call@symbolic",
2030
+ args: Array.from(
2031
+ { length: arity },
2032
+ (_, i) => (
2033
+ // 缺参按真实 JS 语义 widen 成 undefined 而非 unknown——可选参守卫
2034
+ // (target || [] 等)对 unknown 全塌,对 undefined 正常走默认分支
2035
+ widenType(simplifyUnion(remaining.map((rec) => rec.argTypes[i] ?? T2.undefined)))
2036
+ )
2037
+ ),
2038
+ result: collapseLiteralUnion(simplifyUnion(remaining.map((r) => r.resultType)), COLLAPSE_LITERAL_THRESHOLD),
2039
+ throws: simplifyUnion(remaining.map((r) => r.throws)),
2040
+ source: "callsite",
2041
+ aggregatedFrom: remaining.length
2042
+ });
2043
+ }
2044
+ if (deduped.length > 1) {
2045
+ analysis.combined = collapseLiteralUnion(
2046
+ simplifyUnion(deduped.map((r) => r.resultType)),
2047
+ COLLAPSE_LITERAL_THRESHOLD
2048
+ );
2049
+ } else {
2050
+ analysis.combined = deduped[0].resultType;
2051
+ }
2052
+ out.push(analysis);
2053
+ }
2054
+ return out;
2055
+ }
2056
+ function receiverTypeToDisplay(tv) {
2057
+ switch (tv.kind) {
2058
+ case "literal": {
2059
+ const v = tv.value;
2060
+ if (v === null) return "null";
2061
+ if (v === void 0) return "undefined";
2062
+ return typeof v;
2063
+ }
2064
+ case "union":
2065
+ return tv.members.map(receiverTypeToDisplay).join(" | ");
2066
+ default:
2067
+ return typeValueToString3(tv);
2068
+ }
2069
+ }
2070
+ function receiverIsConcrete(tv) {
2071
+ if (tv.kind === "unknown") return false;
2072
+ if (tv.kind === "union") return tv.members.every((m) => m.kind !== "unknown");
2073
+ return true;
2074
+ }
2075
+ function unknownRecordsToDiagnostics(records) {
2076
+ const out = [];
2077
+ const seen = /* @__PURE__ */ new Set();
2078
+ for (const r of records) {
2079
+ const line = r.loc?.line ?? 0;
2080
+ const column = r.loc?.column ?? 0;
2081
+ const key = `${line}:${column}:${r.name}`;
2082
+ if (seen.has(key)) continue;
2083
+ seen.add(key);
2084
+ const range = {
2085
+ start: { line, column },
2086
+ end: { line, column: column + r.name.length }
2087
+ };
2088
+ if (r.kind === "global") {
2089
+ if (r.name.startsWith("recursion:")) {
2090
+ out.push({
2091
+ range,
2092
+ severity: "warning",
2093
+ message: `Recursive evaluation of '${r.name.slice("recursion:".length)}' was truncated (depth/size budget); result widened to unknown`,
2094
+ code: "nudo:recursion-truncated"
2095
+ });
2096
+ continue;
2097
+ }
2098
+ if (r.name.startsWith("module-cycle:")) {
2099
+ out.push({
2100
+ range,
2101
+ severity: "warning",
2102
+ message: r.reason ?? "Circular module load detected",
2103
+ code: "nudo:module-cycle"
2104
+ });
2105
+ continue;
2106
+ }
2107
+ if (r.name.startsWith("module-depth:")) {
2108
+ out.push({
2109
+ range,
2110
+ severity: "warning",
2111
+ message: r.reason ?? "Module load chain too deep",
2112
+ code: "nudo:module-depth"
2113
+ });
2114
+ continue;
2115
+ }
2116
+ if (r.name.startsWith("module-missing:")) {
2117
+ out.push({
2118
+ range,
2119
+ severity: "error",
2120
+ message: r.reason ?? "Module file not found",
2121
+ code: "nudo:module-missing"
2122
+ });
2123
+ continue;
2124
+ }
2125
+ out.push({
2126
+ range,
2127
+ severity: "warning",
2128
+ message: `Unknown global identifier '${r.name}'`,
2129
+ code: "nudo:unknown-global"
2130
+ });
2131
+ continue;
2132
+ }
2133
+ const receiver = r.receiverType;
2134
+ if (receiver && receiverIsConcrete(receiver)) {
2135
+ const isApprox = (() => {
2136
+ const members = receiver.kind === "union" ? receiver.members : [receiver];
2137
+ return members.some(
2138
+ (m) => m.kind === "instance" || m.kind === "function" || m.kind === "tuple" || m.kind === "array" || m.kind === "object" || m.kind === "refined" || // wrapper/instance 近似表上可能持有该成员('x'.charCodeAt 于
2139
+ // number|string:string 侧存在,number 侧缺失 → 不确定 → warning)
2140
+ memberMayExistOn(m, r.name)
2141
+ );
2142
+ })();
2143
+ const kindLabel = r.kind === "method" ? "Method" : "Property";
2144
+ out.push({
2145
+ range,
2146
+ severity: isApprox ? "warning" : "error",
2147
+ message: `${kindLabel} '${r.name}' does not exist on type '${receiverTypeToDisplay(receiver)}'`,
2148
+ code: "nudo:no-method",
2149
+ ...r.origin ? { origin: r.origin } : {}
2150
+ });
2151
+ } else {
2152
+ out.push({
2153
+ range,
2154
+ severity: "warning",
2155
+ message: `Cannot resolve '${r.name}' on unknown value`,
2156
+ code: "nudo:unknown-recv",
2157
+ ...r.origin ? { origin: r.origin } : {}
2158
+ });
2159
+ }
2160
+ }
2161
+ return out;
2162
+ }
2163
+ function collectEnvNames(filePath, source, includeProject) {
2164
+ const ast = parse5(source);
2165
+ const fileDirectives = extractFileDirectives(ast);
2166
+ const fileEnvNames = fileDirectives.filter((d) => d.kind === "env").flatMap((d) => d.envs);
2167
+ if (!includeProject) return fileEnvNames;
2168
+ const projectConfig = findProjectConfig(dirname5(filePath));
2169
+ const projectEnvNames = projectConfig?.config.env ?? [];
2170
+ return [.../* @__PURE__ */ new Set([...projectEnvNames, ...fileEnvNames])];
2171
+ }
2172
+ async function analyzeFileAsync(filePath, source, activeCases, externalCallRecords) {
2173
+ const envNames = collectEnvNames(filePath, source, true);
2174
+ if (envNames.length > 0) {
2175
+ await preloadPathEnvs(envNames, dirname5(filePath));
2176
+ }
2177
+ return analyzeFile(filePath, source, activeCases, externalCallRecords);
2178
+ }
2179
+ function collectCallRecords(filePath, source) {
2180
+ const records = [];
2181
+ const ast = parse5(source, { errorRecovery: true });
2182
+ resetMemo();
2183
+ resetUnreachableRanges();
2184
+ resetEnvModules();
2185
+ resetMockModules();
2186
+ setModuleResolver(resolveModule);
2187
+ setCurrentFileDir(dirname5(filePath));
2188
+ setCurrentSource(source);
2189
+ setCallCollector((record) => records.push(record));
2190
+ try {
2191
+ const env = createEnvironment2();
2192
+ evaluateProgram(ast, env);
2193
+ const runCallbacks = (statements) => {
2194
+ for (const stmt of statements) {
2195
+ if (stmt.type !== "ExpressionStatement" || !("expression" in stmt)) continue;
2196
+ const expr = stmt.expression;
2197
+ if (expr.type !== "CallExpression") continue;
2198
+ const callee = expr.callee;
2199
+ if (callee.type !== "MemberExpression" && callee.type !== "Identifier") continue;
2200
+ const name = callee.type === "Identifier" ? callee.name : callee.property.type === "Identifier" ? callee.property.name : null;
2201
+ if (!name || !TEST_CALLBACK_NAMES.has(name)) continue;
2202
+ const args = expr.arguments;
2203
+ const cb = args.find((a) => a.type === "ArrowFunctionExpression" || a.type === "FunctionExpression");
2204
+ if (!cb) continue;
2205
+ try {
2206
+ const params = cb.params ?? [];
2207
+ evaluateFunction(cb, params.map(() => T2.unknown), env);
2208
+ } catch {
2209
+ }
2210
+ if (name === "describe" && cb.body.type === "BlockStatement") {
2211
+ runCallbacks(cb.body.body);
2212
+ }
2213
+ }
2214
+ };
2215
+ runCallbacks(ast.program.body);
2216
+ } catch {
2217
+ } finally {
2218
+ setCallCollector(null);
2219
+ setModuleResolver(null);
2220
+ setUnknownBuiltinHandler(null);
2221
+ }
2222
+ return records;
2223
+ }
2224
+ var TEST_CALLBACK_NAMES = /* @__PURE__ */ new Set(["it", "test", "describe"]);
2225
+ var externalRecordIds = /* @__PURE__ */ new WeakMap();
2226
+ var nextExternalRecordId = 1;
2227
+ function analysisFileCacheKey(filePath, source, activeCases, externalCallRecords) {
2228
+ let cases = "-";
2229
+ if (activeCases && activeCases.size > 0) {
2230
+ cases = [...activeCases.entries()].sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0).map(([k, v]) => `${k}=${v}`).join(",");
2231
+ }
2232
+ let ext = "-";
2233
+ if (externalCallRecords && externalCallRecords.length > 0) {
2234
+ let id = externalRecordIds.get(externalCallRecords);
2235
+ if (id === void 0) {
2236
+ id = nextExternalRecordId++;
2237
+ externalRecordIds.set(externalCallRecords, id);
2238
+ }
2239
+ ext = `n${externalCallRecords.length}#id${id}`;
2240
+ }
2241
+ return {
2242
+ filePath,
2243
+ // 尾部无 @nudo 注释/空行不进键:comment-only 编辑命中 AnalysisResult
2244
+ source: stableAnalyzeKeySource2(source),
2245
+ auxKey: `${cases}\0${ext}`
2246
+ };
2247
+ }
2248
+ function cloneAnalysisResult(r) {
2249
+ return {
2250
+ functions: r.functions.map(cloneFunctionAnalysis),
2251
+ diagnostics: r.diagnostics.map((d) => ({ ...d })),
2252
+ bindings: new Map(r.bindings),
2253
+ // Node 键与 AST LRU 共享身份;Map 浅拷贝即可
2254
+ nodeTypeMap: new Map(r.nodeTypeMap),
2255
+ caseHints: r.caseHints.map((h) => ({ ...h })),
2256
+ ...r.externalFunctions ? { externalFunctions: r.externalFunctions.map(cloneFunctionAnalysis) } : {}
2257
+ };
2258
+ }
2259
+ function cloneFunctionAnalysis(a) {
2260
+ return {
2261
+ ...a,
2262
+ paramNames: [...a.paramNames],
2263
+ cases: a.cases.map((c) => ({
2264
+ ...c,
2265
+ args: [...c.args],
2266
+ ...c.intension ? { intension: { ...c.intension } } : {}
2267
+ })),
2268
+ loc: { start: { ...a.loc.start }, end: { ...a.loc.end } }
2269
+ };
2270
+ }
2271
+ function shiftSourceLoc(loc, lineDelta) {
2272
+ return {
2273
+ start: { line: loc.start.line + lineDelta, column: loc.start.column },
2274
+ end: { line: loc.end.line + lineDelta, column: loc.end.column }
2275
+ };
2276
+ }
2277
+ function shiftDiagnosticLines(d, lineDelta) {
2278
+ if (lineDelta === 0) return { ...d };
2279
+ const out = { ...d, range: shiftSourceLoc(d.range, lineDelta) };
2280
+ if (d.origin) {
2281
+ out.origin = { line: d.origin.line + lineDelta, column: d.origin.column };
2282
+ }
2283
+ return out;
2284
+ }
2285
+ function shiftCallRecordLines(r, lineDelta) {
2286
+ if (lineDelta === 0 || !r.callLoc) return { ...r };
2287
+ return {
2288
+ ...r,
2289
+ callLoc: { line: r.callLoc.line + lineDelta, column: r.callLoc.column }
2290
+ };
2291
+ }
2292
+ function analyzeFile(filePath, source, activeCases, externalCallRecords) {
2293
+ const k = analysisFileCacheKey(filePath, source, activeCases, externalCallRecords);
2294
+ const hit = analysisCacheGet(k.filePath, k.source, k.auxKey);
2295
+ if (hit !== void 0) {
2296
+ return cloneAnalysisResult(hit);
2297
+ }
2298
+ const result = analyzeFileUncached(filePath, source, activeCases, externalCallRecords);
2299
+ analysisCacheSet(k.filePath, k.source, k.auxKey, result);
2300
+ return cloneAnalysisResult(result);
2301
+ }
2302
+ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords) {
2303
+ for (const rec of externalCallRecords ?? []) {
2304
+ for (const a of rec.argTypes) setUsageSiteTag(a);
2305
+ }
2306
+ const ast = parse5(source);
61
2307
  const functions = extractDirectives(ast);
62
2308
  const diagnostics = [];
63
2309
  const bindings = /* @__PURE__ */ new Map();
@@ -66,34 +2312,329 @@ function analyzeFile(filePath, source, activeCases) {
66
2312
  const caseHints = [];
67
2313
  resetMemo();
68
2314
  resetUnreachableRanges();
2315
+ resetEnvModules();
2316
+ resetMockModules();
69
2317
  setModuleResolver(resolveModule);
70
- setCurrentFileDir(dirname(filePath));
71
- const globalEnv = createEnvironment();
72
- evaluateProgram(ast, globalEnv);
73
- const unreachableRanges = getUnreachableRanges();
74
- for (const ur of unreachableRanges) {
2318
+ setCurrentFileDir(dirname5(filePath));
2319
+ setCurrentSource(source);
2320
+ const fileDirectives = extractFileDirectives(ast);
2321
+ const fileEnvNames = fileDirectives.filter((d) => d.kind === "env").flatMap((d) => d.envs);
2322
+ const projectConfig = findProjectConfig(dirname5(filePath));
2323
+ const projectEnvNames = projectConfig?.config.env ?? [];
2324
+ const envNames = [.../* @__PURE__ */ new Set([...projectEnvNames, ...fileEnvNames])];
2325
+ setUnknownBuiltinHandler((name, loc) => {
2326
+ if (isBPathCapable(source, envNames)) return;
2327
+ diagnostics.push({
2328
+ range: loc ? { start: loc.start, end: loc.end } : { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
2329
+ severity: "warning",
2330
+ message: `Built-in API "${name}" is not covered by Nudo's type inference`,
2331
+ code: "nudo:builtin-unknown",
2332
+ suggestions: [
2333
+ `Use @nudo:mock to define the type: @nudo:mock ${name} = stub().returns(...)`,
2334
+ `Or use @nudo:refine return <constraint> to declare the return contract`
2335
+ ]
2336
+ });
2337
+ });
2338
+ const globalEnv = createEnvironment2();
2339
+ {
2340
+ const loaded = envNames.length > 0 ? loadEnvs2(envNames, globalEnv) : { modules: {} };
2341
+ const auto = autoHarvestModules(source, dirname5(filePath));
2342
+ const modules = { ...loaded.modules, ...auto };
2343
+ if (Object.keys(modules).length > 0) setEnvModules(modules);
2344
+ }
2345
+ const mocks = /* @__PURE__ */ new Map();
2346
+ if (projectConfig?.config.mocks) {
2347
+ for (const [source2, mockPath] of Object.entries(projectConfig.config.mocks)) {
2348
+ mocks.set(source2, { fromPath: resolve4(projectConfig.projectDir, mockPath) });
2349
+ }
2350
+ }
2351
+ const mockModuleDirectives = fileDirectives.filter((d) => d.kind === "mock-module");
2352
+ for (const d of mockModuleDirectives) {
2353
+ mocks.set(d.source, { fromPath: d.fromPath, names: d.names });
2354
+ }
2355
+ if (mocks.size > 0) {
2356
+ setMockModules(mocks);
2357
+ }
2358
+ const callRecords = [];
2359
+ setCallCollector((record) => callRecords.push(record));
2360
+ const unknownRecords = [];
2361
+ setUnknownCollector((r) => unknownRecords.push(r));
2362
+ setProvenanceTracking(true);
2363
+ const bMemberDiagNames = /* @__PURE__ */ new Set();
2364
+ const bMemberDiagSeen = /* @__PURE__ */ new Set();
2365
+ const pushBMemberDiag = (d, fallbackLine) => {
2366
+ bMemberDiagNames.add(d.name);
2367
+ const key = `${d.kind}:${d.name}:${d.receiver}:${d.line ?? fallbackLine}:${d.column ?? 0}`;
2368
+ if (bMemberDiagSeen.has(key)) return;
2369
+ bMemberDiagSeen.add(key);
2370
+ if (d.receiver === "unknown") {
2371
+ diagnostics.push({
2372
+ range: {
2373
+ start: { line: d.line ?? fallbackLine, column: d.column ?? 0 },
2374
+ end: { line: d.line ?? fallbackLine, column: (d.column ?? 0) + d.name.length }
2375
+ },
2376
+ severity: "warning",
2377
+ message: `Cannot resolve '${d.name}' on unknown value`,
2378
+ code: "nudo:unknown-recv",
2379
+ ...d.origin ? { origin: d.origin } : {}
2380
+ });
2381
+ return;
2382
+ }
75
2383
  diagnostics.push({
76
- range: ur,
77
- severity: "info",
78
- message: "Unreachable code",
79
- tags: ["unnecessary"]
2384
+ range: {
2385
+ start: { line: d.line ?? fallbackLine, column: d.column ?? 0 },
2386
+ end: { line: d.line ?? fallbackLine, column: (d.column ?? 0) + d.name.length }
2387
+ },
2388
+ severity: d.receiver === "number" || d.receiver === "boolean" || d.receiver === "bigint" || d.receiver === "symbol" ? "error" : "warning",
2389
+ message: d.kind === "method" ? `Method '${d.name}' does not exist on type '${d.receiver}'` : `Property '${d.name}' does not exist on type '${d.receiver}'`,
2390
+ code: "nudo:no-method",
2391
+ ...d.origin ? { origin: d.origin } : {}
2392
+ });
2393
+ };
2394
+ for (const fn of functions) {
2395
+ applyMocks(fn.directives, globalEnv, filePath, diagnostics);
2396
+ }
2397
+ const selfContained = isSelfContainedSource(source, envNames);
2398
+ const canAbsModules = absModulesOk(source, envNames);
2399
+ const bCapable = isBPathCapable(source, envNames);
2400
+ const bModuleIssueKinds = /* @__PURE__ */ new Set();
2401
+ const bTruncatedFns = /* @__PURE__ */ new Set();
2402
+ const bBuiltinUnknownNames = /* @__PURE__ */ new Set();
2403
+ let bHostedEval = false;
2404
+ const pushBModuleIssues = (issues) => {
2405
+ if (!issues) return;
2406
+ for (const iss of issues) {
2407
+ bModuleIssueKinds.add(iss.kind);
2408
+ const code = iss.kind === "cycle" ? "nudo:module-cycle" : iss.kind === "depth" ? "nudo:module-depth" : "nudo:module-missing";
2409
+ diagnostics.push({
2410
+ range: { start: { line: 1, column: 0 }, end: { line: 1, column: 0 } },
2411
+ severity: iss.kind === "missing" ? "error" : "warning",
2412
+ message: iss.reason,
2413
+ code
2414
+ });
2415
+ }
2416
+ };
2417
+ const seeds = mockDirectivesToAbsSeeds(functions);
2418
+ let absCallRecords = [];
2419
+ let bTopCallRecords = [];
2420
+ let absGraphModules;
2421
+ let absBindsShared;
2422
+ let absNodesShared;
2423
+ if (bCapable && filePath) {
2424
+ try {
2425
+ const g = evalAbsModuleGraph(source, filePath, {
2426
+ seedVars: seeds.seedVars,
2427
+ seedFns: seeds.seedFns
2428
+ });
2429
+ absGraphModules = g.modules;
2430
+ pushBModuleIssues(g.issues);
2431
+ } catch {
2432
+ }
2433
+ const bRun = tryRunBPath(source, filePath, {
2434
+ envNames,
2435
+ mocks: mockSeedsToAbsMocks(seeds)
80
2436
  });
2437
+ if (bRun) {
2438
+ bHostedEval = true;
2439
+ if (bRun.memberDiags?.length) {
2440
+ for (const d of bRun.memberDiags) {
2441
+ pushBMemberDiag(d, 1);
2442
+ }
2443
+ }
2444
+ if (bRun.truncatedFns) {
2445
+ for (const fn of bRun.truncatedFns) bTruncatedFns.add(fn);
2446
+ }
2447
+ if (bRun.calls?.length) {
2448
+ const impMap = buildAbsImportLocalMap(source, filePath);
2449
+ for (const c of bRun.calls) {
2450
+ const safeAbs = (a) => {
2451
+ if (!a || typeof a !== "object" || !("shape" in a) || !a.shape) {
2452
+ return T2.unknown;
2453
+ }
2454
+ try {
2455
+ return absToTypeValue2(a);
2456
+ } catch {
2457
+ return T2.unknown;
2458
+ }
2459
+ };
2460
+ const rec = {
2461
+ fnName: c.fnName,
2462
+ argTypes: c.args.map(safeAbs),
2463
+ resultType: c.threw ? T2.never : safeAbs(c.result),
2464
+ throws: c.threw ? safeAbs(c.result) : T2.never,
2465
+ callLoc: c.callLoc
2466
+ };
2467
+ const imp = impMap.get(c.fnName);
2468
+ if (imp) {
2469
+ rec.targetModule = imp.modulePath;
2470
+ rec.targetExport = imp.exportName;
2471
+ }
2472
+ bTopCallRecords.push(rec);
2473
+ }
2474
+ }
2475
+ }
2476
+ }
2477
+ if (selfContained || canAbsModules) {
2478
+ setAbsTruncationCollector2((label) => bTruncatedFns.add(label));
2479
+ try {
2480
+ absCallRecords = collectAbsCallRecords(source, seeds, filePath, absGraphModules);
2481
+ } finally {
2482
+ setAbsTruncationCollector2(null);
2483
+ }
2484
+ }
2485
+ if (bHostedEval) {
2486
+ try {
2487
+ const collected = collectAbsBindsAndNodes(source, seeds, absGraphModules);
2488
+ absBindsShared = collected.binds;
2489
+ absNodesShared = collected.nodes;
2490
+ for (const [name, absVal] of absBindsShared) {
2491
+ if (absVal?.shape?.k === "fn") continue;
2492
+ if (!globalEnv.has(name)) {
2493
+ globalEnv.bind(name, absToTypeValue2(absVal));
2494
+ }
2495
+ }
2496
+ for (const [node, absVal] of absNodesShared) {
2497
+ nodeTypeMap.set(node, absToTypeValue2(absVal));
2498
+ }
2499
+ } catch {
2500
+ }
2501
+ } else {
2502
+ evaluateProgram(ast, globalEnv);
2503
+ }
2504
+ if ((selfContained || canAbsModules) && absCallRecords.length > 0) {
2505
+ callRecords.length = 0;
2506
+ callRecords.push(...absCallRecords);
2507
+ } else if (bTopCallRecords.length > 0) {
2508
+ callRecords.length = 0;
2509
+ callRecords.push(...bTopCallRecords);
2510
+ }
2511
+ const unreachableRanges = bCapable ? [] : getUnreachableRanges();
2512
+ if (bCapable) {
2513
+ const bKnownGlobals = /* @__PURE__ */ new Set([
2514
+ ...Object.keys(collectEnvGlobals(envNames)),
2515
+ ...Object.keys(mockSeedsToAbsMocks(seeds))
2516
+ ]);
2517
+ const bDiag = collectBPathDiagnostics(source, bKnownGlobals);
2518
+ for (const ur of bDiag.unreachable) {
2519
+ diagnostics.push({
2520
+ range: ur.range,
2521
+ severity: "info",
2522
+ message: "Code after return/throw statement is unreachable",
2523
+ tags: ["unnecessary"],
2524
+ code: "nudo-unreachable",
2525
+ suggestions: ["Remove the unreachable code after the return/throw statement"]
2526
+ });
2527
+ }
2528
+ for (const b of bDiag.builtinUnknown) {
2529
+ bBuiltinUnknownNames.add(b.name);
2530
+ diagnostics.push({
2531
+ range: b.range,
2532
+ severity: "warning",
2533
+ message: `Built-in API "${b.name}" is not covered by Nudo's type inference`,
2534
+ code: "nudo:builtin-unknown",
2535
+ suggestions: [
2536
+ `Use @nudo:mock to define the type: @nudo:mock ${b.name} = stub().returns(...)`,
2537
+ `Or use @nudo:refine return <constraint> to declare the return contract`
2538
+ ]
2539
+ });
2540
+ }
2541
+ } else {
2542
+ for (const ur of unreachableRanges) {
2543
+ diagnostics.push({
2544
+ range: ur,
2545
+ severity: "info",
2546
+ message: "Code after return/throw statement is unreachable",
2547
+ tags: ["unnecessary"],
2548
+ code: "nudo-unreachable",
2549
+ suggestions: ["Remove the unreachable code after the return/throw statement"]
2550
+ });
2551
+ }
81
2552
  }
82
2553
  collectBindings(ast, globalEnv, bindings);
2554
+ if (isBPathCapable(source, envNames)) {
2555
+ try {
2556
+ const absBinds = absBindsShared ?? collectAbsBindingsFromGraph(source, filePath, {
2557
+ seedVars: seeds.seedVars,
2558
+ seedFns: seeds.seedFns
2559
+ });
2560
+ for (const [name, absVal] of absBinds) {
2561
+ const prev = bindings.get(name);
2562
+ bindings.set(name, {
2563
+ type: absToTypeValue2(absVal),
2564
+ loc: prev?.loc
2565
+ });
2566
+ }
2567
+ } catch {
2568
+ }
2569
+ }
2570
+ const synthCandidates = [];
2571
+ let fnFpMap;
2572
+ try {
2573
+ fnFpMap = fnFingerprints(source, ast);
2574
+ } catch {
2575
+ fnFpMap = void 0;
2576
+ }
2577
+ const envKeyFn = envNames.join(",");
2578
+ const mockKeyFn = mockSeedFingerprint(seeds.seedVars, seeds.seedFns);
83
2579
  for (const fn of functions) {
84
- applyMocks(fn.directives, globalEnv, filePath);
85
2580
  const isPure = fn.directives.some((d) => d.kind === "pure");
86
2581
  const skipDirective = fn.directives.find((d) => d.kind === "skip");
87
- const returnsDirective = fn.directives.find((d) => d.kind === "returns");
88
2582
  const fnLoc = locFromNode(fn.node);
89
- const analysis = { name: fn.name, loc: fnLoc, cases: [] };
2583
+ const paramNames = extractParamNames(fn.node);
2584
+ const analysis = { name: fn.name, loc: fnLoc, paramNames, cases: [] };
90
2585
  if (skipDirective && skipDirective.kind === "skip") {
2586
+ analysis.skipped = true;
91
2587
  if (skipDirective.returns) {
92
2588
  analysis.combined = skipDirective.returns;
93
2589
  }
94
2590
  functionResults.push(analysis);
95
2591
  continue;
96
2592
  }
2593
+ const caseDirectives = fn.directives.filter((d) => d.kind === "case");
2594
+ const activeCaseIdx = activeCases?.get(fn.name) ?? 0;
2595
+ const fp = fnFpMap?.get(fn.name);
2596
+ const fnCacheKey = fp && caseDirectives.length > 0 ? [
2597
+ filePath,
2598
+ fp.own,
2599
+ fp.deps,
2600
+ String(activeCaseIdx),
2601
+ caseDirectiveKey(caseDirectives, typeValueToString3),
2602
+ envKeyFn,
2603
+ mockKeyFn
2604
+ ].join("\0") : void 0;
2605
+ const dLen0 = diagnostics.length;
2606
+ const hLen0 = caseHints.length;
2607
+ const cLen0 = callRecords.length;
2608
+ if (fnCacheKey) {
2609
+ const hitFn = fnAnalysisCacheGet(fnCacheKey);
2610
+ if (hitFn) {
2611
+ const cached = cloneFunctionAnalysis(hitFn.analysis);
2612
+ const lineDelta = fnLoc.start.line - cached.loc.start.line;
2613
+ cached.loc = fnLoc;
2614
+ if (lineDelta !== 0) {
2615
+ for (const c of cached.cases) {
2616
+ if (c.throwLoc) c.throwLoc = shiftSourceLoc(c.throwLoc, lineDelta);
2617
+ }
2618
+ }
2619
+ if (lineDelta === 0) {
2620
+ diagnostics.push(...hitFn.diagnostics);
2621
+ caseHints.push(...hitFn.caseHints);
2622
+ callRecords.push(...hitFn.callRecords);
2623
+ } else {
2624
+ for (const d of hitFn.diagnostics) {
2625
+ diagnostics.push(shiftDiagnosticLines(d, lineDelta));
2626
+ }
2627
+ for (const h of hitFn.caseHints) {
2628
+ caseHints.push({ ...h, line: h.line + lineDelta });
2629
+ }
2630
+ for (const r of hitFn.callRecords) {
2631
+ callRecords.push(shiftCallRecordLines(r, lineDelta));
2632
+ }
2633
+ }
2634
+ functionResults.push(cached);
2635
+ continue;
2636
+ }
2637
+ }
97
2638
  if (isPure) {
98
2639
  const fnVal = globalEnv.has(fn.name) ? globalEnv.lookup(fn.name) : null;
99
2640
  if (fnVal && fnVal.kind === "function") {
@@ -106,34 +2647,114 @@ function analyzeFile(filePath, source, activeCases) {
106
2647
  } else {
107
2648
  setSampleCount(3);
108
2649
  }
109
- const caseDirectives = fn.directives.filter((d) => d.kind === "case");
110
- const activeCaseIdx = activeCases?.get(fn.name) ?? 0;
2650
+ if (caseDirectives.length === 0) {
2651
+ synthCandidates.push({ name: fn.name, node: fn.node, analysis });
2652
+ }
111
2653
  for (let ci = 0; ci < caseDirectives.length; ci++) {
112
2654
  const directive = caseDirectives[ci];
113
2655
  resetUnreachableRanges();
114
- const fullResult = evaluateFunctionFull(fn.node, directive.args, globalEnv);
115
- const caseUnreachable = [...getUnreachableRanges()];
116
- analysis.cases.push({
2656
+ let caseValue;
2657
+ let caseAbs;
2658
+ let caseThrows;
2659
+ let fullResult = null;
2660
+ let caseUnreachable = [];
2661
+ const bCapable2 = isBPathCapable(source, envNames);
2662
+ const bPrimary = bCapable2;
2663
+ if (bCapable2 && filePath) {
2664
+ const bFull = tryBPathCallFull(
2665
+ source,
2666
+ filePath,
2667
+ fn.name,
2668
+ directive.args.map((a) => typeValueToAbs5(a)),
2669
+ { collectCalls: true, envNames, mocks: mockSeedsToAbsMocks(seeds) }
2670
+ );
2671
+ const res = bFull?.result;
2672
+ const weakUnknown = !!res && res.shape.k === "unknown" && (!res.term || res.term.op === "lit" && res.term.value === void 0);
2673
+ const bOk = !!res && (bHostedEval || !weakUnknown && res.conf !== "opaque");
2674
+ if (bOk && bFull && bPrimary) {
2675
+ caseAbs = bFull.result;
2676
+ caseValue = absToTypeValue2(bFull.result);
2677
+ caseThrows = bFull.throws.shape.k !== "never" ? absToTypeValue2(bFull.throws) : T2.never;
2678
+ fullResult = { value: caseValue, throws: caseThrows };
2679
+ for (const d of bFull.memberDiags ?? []) {
2680
+ pushBMemberDiag(d, fnLoc.start.line);
2681
+ }
2682
+ if (bFull.calls?.length) {
2683
+ const impMap = buildAbsImportLocalMap(source, filePath);
2684
+ const safeAbs = (a) => {
2685
+ if (!a || typeof a !== "object" || !("shape" in a) || !a.shape) {
2686
+ return T2.unknown;
2687
+ }
2688
+ try {
2689
+ return absToTypeValue2(a);
2690
+ } catch {
2691
+ return T2.unknown;
2692
+ }
2693
+ };
2694
+ for (const c of bFull.calls) {
2695
+ const rec = {
2696
+ fnName: c.fnName,
2697
+ argTypes: c.args.map(safeAbs),
2698
+ resultType: c.threw ? T2.never : safeAbs(c.result),
2699
+ throws: c.threw ? safeAbs(c.result) : T2.never,
2700
+ callLoc: c.callLoc
2701
+ };
2702
+ const imp = impMap.get(c.fnName);
2703
+ if (imp) {
2704
+ rec.targetModule = imp.modulePath;
2705
+ rec.targetExport = imp.exportName;
2706
+ }
2707
+ callRecords.push(rec);
2708
+ }
2709
+ }
2710
+ }
2711
+ }
2712
+ if (!fullResult) {
2713
+ if (bHostedEval) {
2714
+ fullResult = { value: T2.unknown, throws: T2.never };
2715
+ caseValue = T2.unknown;
2716
+ } else {
2717
+ fullResult = evaluateFunctionFull(fn.node, directive.args, globalEnv);
2718
+ caseUnreachable = [...getUnreachableRanges()];
2719
+ caseValue = fullResult.value;
2720
+ if ((selfContained || canAbsModules) && fullResult.value.kind !== "never") {
2721
+ caseAbs = tryEvalAbsRaw(source, fn.name, directive.args, filePath, mockSeedsToAbsMocks(seeds));
2722
+ if (caseAbs) {
2723
+ const projected = absToTypeValue2(caseAbs);
2724
+ if (absIsBetter(projected, fullResult.value)) caseValue = projected;
2725
+ }
2726
+ }
2727
+ }
2728
+ }
2729
+ const tv = caseValue;
2730
+ const throwsTv = caseThrows ?? fullResult.throws;
2731
+ const caseEntry = {
117
2732
  name: directive.name,
118
2733
  args: directive.args,
119
- result: fullResult.value,
120
- throws: fullResult.throws,
121
- throwLoc: fullResult.throwLoc
122
- });
2734
+ result: tv,
2735
+ throws: throwsTv,
2736
+ throwLoc: fullResult.throwLoc,
2737
+ expected: directive.expected,
2738
+ source: "directive"
2739
+ };
2740
+ tryAttachIntension(caseEntry, source, fn.name);
2741
+ if (caseAbs) attachAbsToIntension(caseEntry, caseAbs, fn.name);
2742
+ analysis.cases.push(caseEntry);
123
2743
  if (directive.commentLine) {
124
- const hasThrow = fullResult.throws.kind !== "never";
125
- const resultStr = fullResult.value.kind !== "never" ? typeValueToString(fullResult.value) : "";
126
- const throwStr = hasThrow ? `throws ${typeValueToString(fullResult.throws)}` : "";
2744
+ const hasThrow = throwsTv.kind !== "never";
2745
+ const resultStr = tv.kind !== "never" ? typeValueToString3(tv) : "";
2746
+ const throwStr = hasThrow ? `throws ${typeValueToString3(throwsTv)}` : "";
127
2747
  const label = [resultStr, throwStr].filter(Boolean).join(" ");
128
2748
  const hintLabel = `=> ${label}`;
129
2749
  let ok = true;
130
2750
  if (directive.expected) {
131
- ok = isSubtypeOf(fullResult.value, directive.expected);
2751
+ ok = isSubtypeOf(tv, directive.expected);
132
2752
  if (!ok) {
133
2753
  diagnostics.push({
134
2754
  range: { start: { line: directive.commentLine, column: 0 }, end: { line: directive.commentLine, column: 999 } },
135
2755
  severity: "error",
136
- message: `Case "${directive.name}": expected ${typeValueToString(directive.expected)}, got ${typeValueToString(fullResult.value)}`
2756
+ 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`,
2757
+ code: "nudo:case-expected"
137
2758
  });
138
2759
  }
139
2760
  }
@@ -141,74 +2762,292 @@ function analyzeFile(filePath, source, activeCases) {
141
2762
  }
142
2763
  const isActive = ci === Math.min(activeCaseIdx, caseDirectives.length - 1);
143
2764
  if (isActive) {
144
- if (fullResult.throws.kind !== "never") {
145
- const throwRange = fullResult.throwLoc ?? fnLoc;
2765
+ if (throwsTv.kind !== "never") {
2766
+ const throwRange = fullResult?.throwLoc ?? fnLoc;
146
2767
  diagnostics.push({
147
2768
  range: throwRange,
148
2769
  severity: "warning",
149
- message: `Function "${fn.name}" case "${directive.name}" may throw: ${typeValueToString(fullResult.throws)}`
2770
+ message: `Function "${fn.name}" case "${directive.name}" may throw: ${typeValueToString3(throwsTv)}. Consider adding a try-catch block or using @nudo:refine return <constraint>`,
2771
+ code: "nudo-may-throw"
150
2772
  });
151
2773
  }
152
- for (const ur of caseUnreachable) {
153
- diagnostics.push({
154
- range: ur,
155
- severity: "info",
156
- message: "Unreachable code",
157
- tags: ["unnecessary"]
158
- });
2774
+ if (!isBPathCapable(source, envNames)) {
2775
+ for (const ur of caseUnreachable) {
2776
+ diagnostics.push({
2777
+ range: ur,
2778
+ severity: "info",
2779
+ message: "Code after return/throw statement is unreachable",
2780
+ tags: ["unnecessary"],
2781
+ code: "nudo-unreachable",
2782
+ suggestions: ["Remove the unreachable code after the return/throw statement"]
2783
+ });
2784
+ }
159
2785
  }
160
2786
  }
161
2787
  }
162
2788
  if (analysis.cases.length > 1) {
163
- analysis.combined = simplifyUnion(analysis.cases.map((c) => c.result));
2789
+ analysis.combined = collapseLiteralUnion(simplifyUnion(analysis.cases.map((c) => c.result)), COLLAPSE_LITERAL_THRESHOLD);
164
2790
  } else if (analysis.cases.length === 1) {
165
2791
  analysis.combined = analysis.cases[0].result;
166
2792
  }
167
- if (returnsDirective && returnsDirective.kind === "returns") {
168
- analysis.assertionErrors = [];
169
- for (const directive of caseDirectives) {
170
- const result = evaluateFunction(fn.node, directive.args, globalEnv);
171
- const matches = isSubtypeOf(result, returnsDirective.expected);
172
- if (!matches) {
173
- const msg = `@nudo:returns assertion failed for case "${directive.name}": expected ${typeValueToString(returnsDirective.expected)}, got ${typeValueToString(result)}`;
174
- analysis.assertionErrors.push(msg);
175
- diagnostics.push({
176
- range: fnLoc,
177
- severity: "error",
178
- message: msg
179
- });
2793
+ if (fnCacheKey) {
2794
+ fnAnalysisCacheSet(fnCacheKey, {
2795
+ analysis: cloneFunctionAnalysis(analysis),
2796
+ diagnostics: diagnostics.slice(dLen0),
2797
+ caseHints: caseHints.slice(hLen0),
2798
+ callRecords: callRecords.slice(cLen0)
2799
+ });
2800
+ }
2801
+ functionResults.push(analysis);
2802
+ }
2803
+ const directiveFnNames = new Set(functions.map((f) => f.name));
2804
+ const directiveFnStmts = new Set(functions.map((f) => f.node));
2805
+ for (const { name, node, stmt, noDeclaration } of collectTopLevelFunctions(ast)) {
2806
+ if (directiveFnNames.has(name)) continue;
2807
+ if (directiveFnStmts.has(stmt)) continue;
2808
+ const analysis = { name, loc: locFromNode(node), paramNames: extractParamNames(node), cases: [] };
2809
+ if (noDeclaration) analysis.noDeclaration = true;
2810
+ functionResults.push(analysis);
2811
+ synthCandidates.push({ name, node, analysis });
2812
+ }
2813
+ const modulePathCache = /* @__PURE__ */ new Map();
2814
+ const normalizeModulePath = (p) => {
2815
+ let n = modulePathCache.get(p);
2816
+ if (n === void 0) {
2817
+ try {
2818
+ n = realpathSync(p);
2819
+ } catch {
2820
+ n = p;
2821
+ }
2822
+ modulePathCache.set(p, n);
2823
+ }
2824
+ return n;
2825
+ };
2826
+ const currentModulePath = normalizeModulePath(resolve4(filePath));
2827
+ const singleExportFn = findSingleModuleExportsFunction(ast);
2828
+ for (const candidate of synthCandidates) {
2829
+ const singleExportHit = singleExportFn !== null && candidate.node === singleExportFn;
2830
+ const nameRoutes = (r) => r.fnName === candidate.name || r.targetExport === candidate.name || (r.targetAliases?.includes(candidate.name) ?? false) || singleExportHit && (r.fnModule !== void 0 || r.targetModule !== void 0);
2831
+ const matchingLocal = (r) => {
2832
+ const targetsThisFile = r.targetModule === void 0 || normalizeModulePath(r.targetModule) === currentModulePath;
2833
+ if (!targetsThisFile) return false;
2834
+ return nameRoutes(r);
2835
+ };
2836
+ const matchingExternal = (r) => {
2837
+ const attributed = r.fnModule !== void 0 && normalizeModulePath(r.fnModule) === currentModulePath || r.targetModule !== void 0 && normalizeModulePath(r.targetModule) === currentModulePath;
2838
+ if (!attributed) return false;
2839
+ return nameRoutes(r);
2840
+ };
2841
+ const records = dedupeCallRecords(
2842
+ [
2843
+ ...callRecords.filter(matchingLocal),
2844
+ ...(externalCallRecords ?? []).filter(matchingExternal)
2845
+ ].filter((r) => !(r.resultType.kind === "never" && r.throws.kind === "never"))
2846
+ );
2847
+ if (records.length > 0) {
2848
+ const informativeness = (r) => {
2849
+ if (r.resultType.kind === "unknown") return 2;
2850
+ if (r.resultType.kind === "never") return 1;
2851
+ return 0;
2852
+ };
2853
+ const ordered = records.map((r, i) => ({ r, i })).sort((a, b) => informativeness(a.r) - informativeness(b.r) || a.i - b.i).map(({ r }) => r);
2854
+ const precise = ordered.slice(0, MAX_PRECISE_CALLSITE_CASES);
2855
+ for (const rec of precise) {
2856
+ let absRaw;
2857
+ let absResult;
2858
+ if (rec.resultType.kind !== "never" && rec.argTypes.length > 0) {
2859
+ absRaw = tryEvalAbsRaw(source, candidate.name, rec.argTypes, filePath, mockSeedsToAbsMocks(seeds));
2860
+ if (absRaw) {
2861
+ const projected = absToTypeValue2(absRaw);
2862
+ if (absIsBetter(projected, rec.resultType)) absResult = projected;
2863
+ }
2864
+ }
2865
+ const caseResult2 = {
2866
+ name: `call@L${rec.callLoc?.line ?? candidate.analysis.loc.start.line}`,
2867
+ args: rec.argTypes,
2868
+ result: absResult ?? rec.resultType,
2869
+ throws: rec.throws,
2870
+ source: "callsite"
2871
+ };
2872
+ tryAttachIntension(caseResult2, source, candidate.name);
2873
+ if (absRaw) attachAbsToIntension(caseResult2, absRaw, candidate.name);
2874
+ candidate.analysis.cases.push(caseResult2);
2875
+ }
2876
+ const remaining = ordered.slice(MAX_PRECISE_CALLSITE_CASES).filter((rec) => !rec.argTypes.some((a) => a.kind === "unknown"));
2877
+ if (remaining.length > 0) {
2878
+ const fnNode2 = resolveFunctionNode(candidate.node);
2879
+ const paramCount = extractParamNames(fnNode2).length;
2880
+ const widenedArgs = Array.from(
2881
+ { length: paramCount },
2882
+ (_, i) => (
2883
+ // 缺参按真实 JS 语义 widen 成 undefined 而非 unknown——可选参守卫
2884
+ // (target || [] 等)对 unknown 全塌,对 undefined 正常走默认分支
2885
+ widenType(simplifyUnion(remaining.map((rec) => rec.argTypes[i] ?? T2.undefined)))
2886
+ )
2887
+ );
2888
+ const absSymRaw = tryEvalAbs(source, candidate.name, widenedArgs, filePath, mockSeedsToAbsMocks(seeds));
2889
+ const absSym = absSymRaw && absIsBetter(
2890
+ absSymRaw,
2891
+ /* 无先验:仅 unknown 时 */
2892
+ { kind: "unknown" }
2893
+ ) ? absSymRaw : void 0;
2894
+ let symValue;
2895
+ let symThrows = T2.never;
2896
+ let symLoc;
2897
+ let symAbs;
2898
+ if (isBPathCapable(source, envNames) && filePath) {
2899
+ const bSym = tryBPathCall(
2900
+ source,
2901
+ filePath,
2902
+ candidate.name,
2903
+ widenedArgs.map((a) => typeValueToAbs5(a)),
2904
+ { envNames, mocks: mockSeedsToAbsMocks(seeds) }
2905
+ );
2906
+ if (bSym && (bHostedEval || !(bSym.shape.k === "unknown" && !bSym.term))) {
2907
+ symAbs = bSym;
2908
+ symValue = absToTypeValue2(bSym);
2909
+ }
2910
+ }
2911
+ if (!symAbs && !bHostedEval) {
2912
+ const full = evaluateFunctionFull(fnNode2, widenedArgs, globalEnv);
2913
+ symValue = full.value.kind === "unknown" && absSym ? absSym : full.value;
2914
+ symThrows = full.throws;
2915
+ symLoc = full.throwLoc;
2916
+ }
2917
+ const symCase = {
2918
+ name: "call@symbolic",
2919
+ args: widenedArgs,
2920
+ result: symValue ?? T2.unknown,
2921
+ throws: symThrows,
2922
+ throwLoc: symLoc,
2923
+ source: "callsite",
2924
+ aggregatedFrom: remaining.length
2925
+ };
2926
+ tryAttachIntension(symCase, source, candidate.name);
2927
+ if (symAbs) attachAbsToIntension(symCase, symAbs, candidate.name);
2928
+ candidate.analysis.cases.push(symCase);
2929
+ }
2930
+ candidate.analysis.combined = collapseLiteralUnion(
2931
+ simplifyUnion(records.map((r) => r.resultType)),
2932
+ COLLAPSE_LITERAL_THRESHOLD
2933
+ );
2934
+ continue;
2935
+ }
2936
+ const fnNode = resolveFunctionNode(candidate.node);
2937
+ const args = extractParamNames(fnNode).map(() => T2.unknown);
2938
+ let entryValue;
2939
+ let entryThrows = T2.never;
2940
+ let entryLoc;
2941
+ let entryAbs;
2942
+ if (isBPathCapable(source, envNames) && filePath) {
2943
+ const bEntryFull = tryBPathCallFull(
2944
+ source,
2945
+ filePath,
2946
+ candidate.analysis.name,
2947
+ args.map((a) => typeValueToAbs5(a)),
2948
+ { envNames, mocks: mockSeedsToAbsMocks(seeds), collectMemberDiags: true }
2949
+ );
2950
+ if (bEntryFull?.memberDiags?.length) {
2951
+ for (const d of bEntryFull.memberDiags) {
2952
+ pushBMemberDiag(d, candidate.analysis.loc.start.line);
180
2953
  }
181
2954
  }
2955
+ const bEntry = bEntryFull?.result;
2956
+ if (bEntry && (bHostedEval || !(bEntry.shape.k === "unknown" && !bEntry.term))) {
2957
+ entryAbs = bEntry;
2958
+ entryValue = absToTypeValue2(bEntry);
2959
+ }
182
2960
  }
183
- functionResults.push(analysis);
2961
+ if (!entryValue) {
2962
+ if (bHostedEval) {
2963
+ entryValue = T2.unknown;
2964
+ entryThrows = T2.never;
2965
+ } else {
2966
+ const absEntry = tryEvalEntryAbs(source, candidate.analysis.name, args, filePath, seeds.seedVars);
2967
+ const full = absEntry ? { value: absEntry, throws: T2.never, throwLoc: void 0 } : evaluateFunctionFull(fnNode, args, globalEnv);
2968
+ entryValue = full.value;
2969
+ entryThrows = full.throws;
2970
+ entryLoc = full.throwLoc;
2971
+ }
2972
+ }
2973
+ const caseResult = {
2974
+ name: `entry@L${candidate.analysis.loc.start.line}`,
2975
+ args,
2976
+ result: entryValue,
2977
+ throws: entryThrows,
2978
+ throwLoc: entryLoc
2979
+ };
2980
+ tryAttachIntension(caseResult, source, candidate.analysis.name);
2981
+ if (entryAbs) attachAbsToIntension(caseResult, entryAbs, candidate.analysis.name);
2982
+ candidate.analysis.cases.push(caseResult);
2983
+ candidate.analysis.entryOnly = true;
2984
+ candidate.analysis.combined = collapseLiteralUnion(entryValue, COLLAPSE_LITERAL_THRESHOLD);
184
2985
  }
185
- buildNodeTypeMap(ast, globalEnv, nodeTypeMap);
2986
+ if (!bHostedEval) {
2987
+ buildNodeTypeMap(ast, globalEnv, nodeTypeMap);
2988
+ }
2989
+ setUnknownBuiltinHandler(null);
2990
+ setCallCollector(null);
2991
+ setUnknownCollector(null);
2992
+ setProvenanceTracking(false);
186
2993
  setModuleResolver(null);
2994
+ resetEnvModules();
2995
+ resetMockModules();
2996
+ const maxLine = source.split("\n").length;
2997
+ diagnostics.push(
2998
+ ...unknownRecordsToDiagnostics(
2999
+ unknownRecords.filter((r) => {
3000
+ if ((r.loc?.line ?? 0) > maxLine) return false;
3001
+ if (r.originModule === USAGE_SITE_MODULE) return false;
3002
+ if (bHostedEval && (r.kind === "method" || r.kind === "property")) {
3003
+ return false;
3004
+ }
3005
+ if ((r.kind === "method" || r.kind === "property") && bMemberDiagNames.has(r.name)) {
3006
+ return false;
3007
+ }
3008
+ if (r.kind === "global") {
3009
+ if (r.name.startsWith("module-cycle:") && bModuleIssueKinds.has("cycle")) return false;
3010
+ if (r.name.startsWith("module-depth:") && bModuleIssueKinds.has("depth")) return false;
3011
+ if (r.name.startsWith("module-missing:") && bModuleIssueKinds.has("missing")) return false;
3012
+ if (r.name.startsWith("recursion:")) {
3013
+ const fn = r.name.slice("recursion:".length);
3014
+ if (bTruncatedFns.has(fn) || bTruncatedFns.has(fn.split(".").pop() ?? fn)) {
3015
+ return false;
3016
+ }
3017
+ }
3018
+ if (bBuiltinUnknownNames.has(r.name)) return false;
3019
+ }
3020
+ return true;
3021
+ })
3022
+ )
3023
+ );
3024
+ const externalFunctions = synthesizeExternalFunctions(callRecords, filePath);
187
3025
  return {
188
3026
  functions: functionResults,
189
3027
  diagnostics,
190
3028
  bindings,
191
3029
  nodeTypeMap,
192
- caseHints
3030
+ caseHints,
3031
+ ...externalFunctions.length > 0 ? { externalFunctions } : {}
193
3032
  };
194
3033
  }
195
3034
  function collectBindings(ast, env, bindings) {
196
3035
  if (ast.type !== "File") return;
197
3036
  for (const stmt of ast.program.body) {
198
3037
  if (stmt.type === "FunctionDeclaration" && stmt.id) {
199
- const val = env.has(stmt.id.name) ? env.lookup(stmt.id.name) : T.unknown;
3038
+ const val = env.has(stmt.id.name) ? env.lookup(stmt.id.name) : T2.unknown;
200
3039
  bindings.set(stmt.id.name, { type: val, loc: locFromNode(stmt) });
201
3040
  }
202
3041
  if (stmt.type === "VariableDeclaration") {
203
3042
  for (const decl of stmt.declarations) {
204
3043
  if (decl.id.type === "Identifier") {
205
- const val = env.has(decl.id.name) ? env.lookup(decl.id.name) : T.unknown;
3044
+ const val = env.has(decl.id.name) ? env.lookup(decl.id.name) : T2.unknown;
206
3045
  bindings.set(decl.id.name, { type: val, loc: locFromNode(decl) });
207
3046
  }
208
3047
  }
209
3048
  }
210
3049
  if (stmt.type === "ClassDeclaration" && stmt.id) {
211
- const val = env.has(stmt.id.name) ? env.lookup(stmt.id.name) : T.unknown;
3050
+ const val = env.has(stmt.id.name) ? env.lookup(stmt.id.name) : T2.unknown;
212
3051
  bindings.set(stmt.id.name, { type: val, loc: locFromNode(stmt) });
213
3052
  }
214
3053
  if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
@@ -229,16 +3068,16 @@ function buildNodeTypeMap(ast, env, nodeTypeMap) {
229
3068
  }
230
3069
  }
231
3070
  if (node.type === "NumericLiteral") {
232
- nodeTypeMap.set(node, T.literal(node.value));
3071
+ nodeTypeMap.set(node, T2.literal(node.value));
233
3072
  }
234
3073
  if (node.type === "StringLiteral") {
235
- nodeTypeMap.set(node, T.literal(node.value));
3074
+ nodeTypeMap.set(node, T2.literal(node.value));
236
3075
  }
237
3076
  if (node.type === "BooleanLiteral") {
238
- nodeTypeMap.set(node, T.literal(node.value));
3077
+ nodeTypeMap.set(node, T2.literal(node.value));
239
3078
  }
240
3079
  if (node.type === "NullLiteral") {
241
- nodeTypeMap.set(node, T.null);
3080
+ nodeTypeMap.set(node, T2.null);
242
3081
  }
243
3082
  } catch {
244
3083
  }
@@ -248,19 +3087,59 @@ function buildNodeTypeMap(ast, env, nodeTypeMap) {
248
3087
  }
249
3088
  }
250
3089
  function getCasesForFile(filePath, source) {
251
- const ast = parse(source);
3090
+ const ast = parse5(source);
252
3091
  const functions = extractDirectives(ast);
253
3092
  return functions.map((fn) => {
254
3093
  const cases = fn.directives.filter((d) => d.kind === "case").map((d, i) => ({ name: d.name, index: i }));
255
3094
  return { functionName: fn.name, cases, loc: locFromNode(fn.node) };
256
3095
  });
257
3096
  }
3097
+ async function getTypeAtPositionAsync(filePath, source, line, column, activeCases) {
3098
+ const envNames = collectEnvNames(filePath, source, false);
3099
+ if (envNames.length > 0) {
3100
+ await preloadPathEnvs(envNames, dirname5(filePath));
3101
+ }
3102
+ return getTypeAtPosition(filePath, source, line, column, activeCases);
3103
+ }
258
3104
  function getTypeAtPosition(filePath, source, line, column, activeCases) {
259
- const ast = parse(source);
3105
+ const ast = parse5(source);
3106
+ const fileDirectives = extractFileDirectives(ast);
3107
+ const envNames = fileDirectives.filter((d) => d.kind === "env").flatMap((d) => d.envs);
3108
+ if (isBPathCapable(source, envNames)) {
3109
+ try {
3110
+ const seeds = mockDirectivesToAbsSeeds(extractDirectives(ast));
3111
+ const { modules } = evalAbsModuleGraph(source, filePath);
3112
+ const absNodes = collectAbsNodeTypes(source, {
3113
+ ...seeds,
3114
+ modules,
3115
+ file: ast
3116
+ });
3117
+ const absAt = findAbsAtPosition(absNodes, line, column);
3118
+ if (absAt) return absToTypeValue2(absAt);
3119
+ const ident = findIdentNameAtPosition(source, line, column, ast);
3120
+ if (ident) {
3121
+ const binds = collectAbsBindingsFromGraph(source, filePath, {
3122
+ seedVars: seeds.seedVars,
3123
+ seedFns: seeds.seedFns
3124
+ });
3125
+ const bound = binds.get(ident);
3126
+ if (bound) return absToTypeValue2(bound);
3127
+ }
3128
+ } catch {
3129
+ }
3130
+ }
260
3131
  resetMemo();
3132
+ resetEnvModules();
261
3133
  setModuleResolver(resolveModule);
262
- setCurrentFileDir(dirname(filePath));
263
- const globalEnv = createEnvironment();
3134
+ setCurrentFileDir(dirname5(filePath));
3135
+ setCurrentSource(source);
3136
+ const globalEnv = createEnvironment2();
3137
+ {
3138
+ const loaded = envNames.length > 0 ? loadEnvs2(envNames, globalEnv) : { modules: {} };
3139
+ const auto = autoHarvestModules(source, dirname5(filePath));
3140
+ const modules = { ...loaded.modules, ...auto };
3141
+ if (Object.keys(modules).length > 0) setEnvModules(modules);
3142
+ }
264
3143
  evaluateProgram(ast, globalEnv);
265
3144
  const nodeTypeMap = /* @__PURE__ */ new Map();
266
3145
  buildNodeTypeMap(ast, globalEnv, nodeTypeMap);
@@ -281,8 +3160,164 @@ function getTypeAtPosition(filePath, source, line, column, activeCases) {
281
3160
  }
282
3161
  }
283
3162
  setModuleResolver(null);
3163
+ resetEnvModules();
3164
+ resetMockModules();
284
3165
  return findBestTypeAtPosition(nodeTypeMap, globalEnv, ast, line, column);
285
3166
  }
3167
+ function getHoverAtPosition(filePath, source, line, column, activeCases) {
3168
+ let file;
3169
+ try {
3170
+ file = parse5(source);
3171
+ } catch {
3172
+ file = void 0;
3173
+ }
3174
+ const envNames = collectEnvNames(filePath, source, false);
3175
+ if (isBPathCapable(source, envNames)) {
3176
+ try {
3177
+ const seeds = mockDirectivesToAbsSeeds(extractDirectives(file ?? parse5(source)));
3178
+ const { modules } = evalAbsModuleGraph(source, filePath);
3179
+ const absNodes = collectAbsNodeTypes(source, {
3180
+ ...seeds,
3181
+ modules,
3182
+ ...file ? { file } : {}
3183
+ });
3184
+ const absAt = findAbsAtPosition(absNodes, line, column);
3185
+ const ident2 = findIdentNameAtPosition(source, line, column, file);
3186
+ if (ident2 && !findFunctionNameAtPosition(source, line, column, file)) {
3187
+ const binds = collectAbsBindingsFromGraph(source, filePath, {
3188
+ seedVars: seeds.seedVars,
3189
+ seedFns: seeds.seedFns
3190
+ });
3191
+ const bound = binds.get(ident2);
3192
+ if (bound) {
3193
+ const absLine = formatAbs3(bound);
3194
+ const absMulti = formatAbsMultiline(bound, ident2);
3195
+ return { typeText: absLine, abs: absLine, absMultiline: absMulti };
3196
+ }
3197
+ }
3198
+ if (absAt) {
3199
+ const absLine = formatAbs3(absAt);
3200
+ const absMulti = formatAbsMultiline(absAt, void 0);
3201
+ return { typeText: absLine, abs: absLine, absMultiline: absMulti };
3202
+ }
3203
+ } catch {
3204
+ }
3205
+ }
3206
+ const tv = getTypeAtPosition(filePath, source, line, column, activeCases);
3207
+ const info = tv ? { typeText: typeValueToString3(tv) } : null;
3208
+ const fnName = findFunctionNameAtPosition(source, line, column, file);
3209
+ if (fnName) {
3210
+ try {
3211
+ const g = generalizeFromAst2(fnName, source, file ? { file } : {});
3212
+ if (g) {
3213
+ if (info) {
3214
+ info.intension = g.display;
3215
+ info.abs = formatAbs3(g.symbolic);
3216
+ info.absMultiline = formatAbsMultiline(g.symbolic, fnName);
3217
+ } else {
3218
+ return {
3219
+ typeText: g.display,
3220
+ intension: g.display,
3221
+ abs: formatAbs3(g.symbolic),
3222
+ absMultiline: formatAbsMultiline(g.symbolic, fnName)
3223
+ };
3224
+ }
3225
+ }
3226
+ } catch {
3227
+ }
3228
+ }
3229
+ const ident = findIdentNameAtPosition(source, line, column, file);
3230
+ if (ident && !fnName) {
3231
+ try {
3232
+ if (isBPathCapable(source, []) || !/\brequire\s*\(/.test(source)) {
3233
+ const seeds = mockDirectivesToAbsSeeds(extractDirectives(file ?? parse5(source)));
3234
+ const absBinds = collectAbsBindingsFromGraph(source, filePath, {
3235
+ seedVars: seeds.seedVars,
3236
+ seedFns: seeds.seedFns
3237
+ });
3238
+ const absBound = absBinds.get(ident);
3239
+ if (absBound) {
3240
+ const absLine = formatAbs3(absBound);
3241
+ const absMulti = formatAbsMultiline(absBound, ident);
3242
+ if (info) {
3243
+ info.abs = absLine;
3244
+ info.absMultiline = absMulti;
3245
+ return info;
3246
+ }
3247
+ return { typeText: absLine, abs: absLine, absMultiline: absMulti };
3248
+ }
3249
+ }
3250
+ } catch {
3251
+ }
3252
+ }
3253
+ try {
3254
+ if (isBPathCapable(source, []) || !/\brequire\s*\(|\bimport\s*[{'"*]/.test(source)) {
3255
+ const seeds = mockDirectivesToAbsSeeds(
3256
+ extractDirectives(file ?? parse5(source))
3257
+ );
3258
+ const { modules } = evalAbsModuleGraph(source, filePath);
3259
+ const nodeTypes = collectAbsNodeTypes(source, {
3260
+ ...seeds,
3261
+ modules,
3262
+ ...file ? { file } : {}
3263
+ });
3264
+ const absAt = findAbsAtPosition(nodeTypes, line, column);
3265
+ if (absAt) {
3266
+ const absLine = formatAbs3(absAt);
3267
+ const absMulti = formatAbsMultiline(absAt, void 0);
3268
+ if (info) {
3269
+ info.abs = absLine;
3270
+ info.absMultiline = absMulti;
3271
+ } else {
3272
+ return { typeText: absLine, abs: absLine, absMultiline: absMulti };
3273
+ }
3274
+ }
3275
+ }
3276
+ } catch {
3277
+ }
3278
+ return info;
3279
+ }
3280
+ function findIdentNameAtPosition(source, line, column, fileAst) {
3281
+ try {
3282
+ const ast = fileAst ?? parse5(source);
3283
+ let found;
3284
+ traverse(ast, {
3285
+ Identifier(path) {
3286
+ const loc = path.node.loc;
3287
+ if (!loc) return;
3288
+ if (loc.start.line !== line) return;
3289
+ if (column < loc.start.column || column > loc.end.column) return;
3290
+ found = path.node.name;
3291
+ }
3292
+ });
3293
+ return found;
3294
+ } catch {
3295
+ return void 0;
3296
+ }
3297
+ }
3298
+ function findFunctionNameAtPosition(source, line, column, fileAst) {
3299
+ try {
3300
+ const ast = fileAst ?? parse5(source);
3301
+ let found;
3302
+ traverse(ast, {
3303
+ Identifier(path) {
3304
+ const loc = path.node.loc;
3305
+ if (!loc) return;
3306
+ if (loc.start.line !== line) return;
3307
+ if (column < loc.start.column || column > loc.end.column) return;
3308
+ const parent = path.parent;
3309
+ if (parent.type === "FunctionDeclaration" && parent.id === path.node) {
3310
+ found = path.node.name;
3311
+ } else if (parent.type === "CallExpression" && parent.callee === path.node) {
3312
+ found = path.node.name;
3313
+ }
3314
+ }
3315
+ });
3316
+ return found;
3317
+ } catch {
3318
+ return void 0;
3319
+ }
3320
+ }
286
3321
  function findEnclosingFunction(functions, line) {
287
3322
  for (const fn of functions) {
288
3323
  const loc = fn.node.loc;
@@ -331,129 +3366,685 @@ function findIdentifierAtPosition(ast, line, column) {
331
3366
  });
332
3367
  } catch {
333
3368
  }
334
- return found;
3369
+ return found;
3370
+ }
3371
+ function getCompletionsAtPosition(filePath, source, line, column) {
3372
+ const textBefore = getTextBeforePosition(source, line, column);
3373
+ const dotMatch = textBefore.match(/(\w+)\.\s*\w*$/);
3374
+ if (!dotMatch) return getVariableCompletions(filePath, source);
3375
+ const objName = dotMatch[1];
3376
+ const safeSource = sanitizeSourceForParsing(source);
3377
+ let ast;
3378
+ try {
3379
+ ast = parse5(safeSource);
3380
+ } catch {
3381
+ try {
3382
+ ast = parse5(source);
3383
+ } catch {
3384
+ return [];
3385
+ }
3386
+ }
3387
+ resetMemo();
3388
+ setModuleResolver(resolveModule);
3389
+ setCurrentFileDir(dirname5(filePath));
3390
+ const globalEnv = createEnvironment2();
3391
+ evaluateProgram(ast, globalEnv);
3392
+ if (!globalEnv.has(objName)) {
3393
+ setModuleResolver(null);
3394
+ return [];
3395
+ }
3396
+ const objType = globalEnv.lookup(objName);
3397
+ const completions = getCompletionsForType(objType);
3398
+ setModuleResolver(null);
3399
+ return completions;
3400
+ }
3401
+ function sanitizeSourceForParsing(source) {
3402
+ return source.replace(/(\w+)\.\s*$/gm, "$1._ ");
3403
+ }
3404
+ function getTextBeforePosition(source, line, column) {
3405
+ const lines = source.split("\n");
3406
+ if (line < 1 || line > lines.length) return "";
3407
+ return lines[line - 1].slice(0, column);
3408
+ }
3409
+ function getVariableCompletions(filePath, source) {
3410
+ const ast = parse5(source);
3411
+ resetMemo();
3412
+ setModuleResolver(resolveModule);
3413
+ setCurrentFileDir(dirname5(filePath));
3414
+ const globalEnv = createEnvironment2();
3415
+ evaluateProgram(ast, globalEnv);
3416
+ const ownBindings = globalEnv.getOwnBindings();
3417
+ const completions = [];
3418
+ for (const [name, tv] of Object.entries(ownBindings)) {
3419
+ if (name.startsWith("__export_")) continue;
3420
+ completions.push({
3421
+ label: name,
3422
+ kind: tv.kind === "function" ? "method" : "variable",
3423
+ detail: typeValueToString3(tv)
3424
+ });
3425
+ }
3426
+ setModuleResolver(null);
3427
+ return completions;
3428
+ }
3429
+ function builtinMemberType(memberExpr) {
3430
+ try {
3431
+ const result = evaluate(parse5(`${memberExpr};`), createEnvironment2());
3432
+ if (!result || typeof result !== "object" || !("kind" in result)) return null;
3433
+ return result;
3434
+ } catch {
3435
+ return null;
3436
+ }
3437
+ }
3438
+ function builtinProtoMembers(className) {
3439
+ const table = BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS[className];
3440
+ return table ? Object.keys(table) : [];
3441
+ }
3442
+ function describeMember(label, tv, fallbackClass) {
3443
+ if (tv) {
3444
+ const sig = getFnSig3(tv);
3445
+ if (sig) {
3446
+ const paramNames = tv.kind === "function" ? tv.params : [];
3447
+ const params = sig.paramTypes.map((p, i) => `${paramNames[i] ?? `arg${i}`}: ${typeValueToString3(p)}`).join(", ");
3448
+ return `(${params}) => ${typeValueToString3(sig.returnType)}`;
3449
+ }
3450
+ if (tv.kind !== "function") return typeValueToString3(tv);
3451
+ }
3452
+ return `${label}(\u2026)@${fallbackClass}`;
3453
+ }
3454
+ function getArrayCompletions(tv) {
3455
+ const completions = [];
3456
+ for (const m of builtinProtoMembers("Array")) {
3457
+ const detail = describeMember(m, builtinMemberType(`Array.prototype.${m}`), "Array");
3458
+ completions.push({ label: m, kind: "method", detail });
3459
+ }
3460
+ completions.push({
3461
+ label: "length",
3462
+ kind: "property",
3463
+ // tuple 长度是精确字面量;array 是 number(保持原展示语义)
3464
+ detail: tv.kind === "tuple" ? `${tv.elements.length}` : "number"
3465
+ });
3466
+ return completions;
3467
+ }
3468
+ function getPromiseCompletions() {
3469
+ return builtinProtoMembers("Promise").map((m) => ({
3470
+ label: m,
3471
+ kind: "method",
3472
+ detail: describeMember(m, builtinMemberType(`Promise.prototype.${m}`), "Promise")
3473
+ }));
3474
+ }
3475
+ function getStringCompletions() {
3476
+ const completions = [];
3477
+ for (const m of builtinProtoMembers("String")) {
3478
+ completions.push({
3479
+ label: m,
3480
+ kind: "method",
3481
+ detail: describeMember(m, builtinMemberType(`"s".${m}`), "String")
3482
+ });
3483
+ }
3484
+ completions.push({ label: "length", kind: "property", detail: "number" });
3485
+ return completions;
3486
+ }
3487
+ function getUnionCompletions(tv) {
3488
+ const members = tv.members;
3489
+ if (members.length === 0) return [];
3490
+ const labelsByMember = members.map((m) => getCompletionsForType(m));
3491
+ const baseIdx = labelsByMember.findIndex((labels) => labels.length > 0);
3492
+ if (baseIdx === -1) return [];
3493
+ const common = [];
3494
+ for (const base of labelsByMember[baseIdx]) {
3495
+ let allPresent = true;
3496
+ const memberTypes = [base.detail ?? base.label];
3497
+ for (let i = 0; i < members.length; i++) {
3498
+ if (i === baseIdx) continue;
3499
+ const labels = labelsByMember[i];
3500
+ if (labels.length === 0) continue;
3501
+ const hit = labels.find((l) => l.label === base.label);
3502
+ if (!hit) {
3503
+ allPresent = false;
3504
+ break;
3505
+ }
3506
+ memberTypes.push(hit.detail ?? hit.label);
3507
+ }
3508
+ if (allPresent) {
3509
+ common.push({ ...base, detail: memberTypes.join(" | ") });
3510
+ }
3511
+ }
3512
+ return common;
3513
+ }
3514
+ function getCompletionsForType(tv) {
3515
+ const completions = [];
3516
+ if (tv.kind === "object") {
3517
+ for (const [key, val] of Object.entries(tv.properties)) {
3518
+ completions.push({
3519
+ label: key,
3520
+ kind: val.kind === "function" ? "method" : "property",
3521
+ detail: typeValueToString3(val)
3522
+ });
3523
+ }
3524
+ return completions;
3525
+ }
3526
+ if (tv.kind === "instance") {
3527
+ for (const [key, val] of Object.entries(tv.properties)) {
3528
+ completions.push({
3529
+ label: key,
3530
+ kind: val.kind === "function" ? "method" : "property",
3531
+ detail: typeValueToString3(val)
3532
+ });
3533
+ }
3534
+ return completions;
3535
+ }
3536
+ if (tv.kind === "union") {
3537
+ return getUnionCompletions(tv);
3538
+ }
3539
+ if (tv.kind === "array" || tv.kind === "tuple") {
3540
+ return getArrayCompletions(tv);
3541
+ }
3542
+ if (tv.kind === "promise") {
3543
+ return getPromiseCompletions();
3544
+ }
3545
+ if (tv.kind === "primitive" && tv.type === "string") {
3546
+ return getStringCompletions();
3547
+ }
3548
+ return completions;
3549
+ }
3550
+ function absIsBetter(absTv, prev) {
3551
+ if (prev.kind === "unknown") return absTv.kind !== "unknown";
3552
+ if (absTv.kind === "unknown" || absTv.kind === "never") return false;
3553
+ 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") {
3554
+ return false;
3555
+ }
3556
+ if (absTv.kind === "refined" && prev.kind === "primitive") return true;
3557
+ return false;
3558
+ }
3559
+ function isSelfContainedSource(source, envNames) {
3560
+ if (envNames.length > 0) return false;
3561
+ return !/\brequire\s*\(|\bimport\s*[{'"*]/.test(source);
3562
+ }
3563
+ function absModulesOk(source, envNames) {
3564
+ void source;
3565
+ void envNames;
3566
+ return true;
3567
+ }
3568
+ function collectAbsCallRecords(source, seeds, filePath, precomputedModules) {
3569
+ const absCalls = [];
3570
+ let modules = precomputedModules;
3571
+ let importLocals = /* @__PURE__ */ new Map();
3572
+ if (filePath && absModulesOk(source, [])) {
3573
+ if (!modules) {
3574
+ try {
3575
+ const graph = evalAbsModuleGraph(source, filePath, {
3576
+ seedVars: seeds?.seedVars,
3577
+ seedFns: seeds?.seedFns
3578
+ });
3579
+ modules = graph.modules;
3580
+ } catch {
3581
+ modules = void 0;
3582
+ }
3583
+ }
3584
+ try {
3585
+ importLocals = buildAbsImportLocalMap(source, filePath);
3586
+ } catch {
3587
+ importLocals = /* @__PURE__ */ new Map();
3588
+ }
3589
+ }
3590
+ setAbsCallCollector((r) => absCalls.push(r));
3591
+ try {
3592
+ evalProgramAbs2(source, { ...seeds, modules });
3593
+ } catch {
3594
+ } finally {
3595
+ setAbsCallCollector(null);
3596
+ }
3597
+ return absCalls.map((r) => {
3598
+ const rec = {
3599
+ fnName: r.fnName,
3600
+ argTypes: r.args.map((a) => absToTypeValue2(a)),
3601
+ resultType: r.threw ? T2.never : absToTypeValue2(r.result),
3602
+ throws: r.threw ? absToTypeValue2(r.result) : T2.never,
3603
+ callLoc: r.callLoc
3604
+ };
3605
+ const imp = importLocals.get(r.fnName);
3606
+ if (imp) {
3607
+ rec.targetModule = imp.modulePath;
3608
+ rec.targetExport = imp.exportName;
3609
+ }
3610
+ return rec;
3611
+ });
3612
+ }
3613
+ function collectAbsBindsAndNodes(source, seeds, modules) {
3614
+ const binds = /* @__PURE__ */ new Map();
3615
+ const nodes = /* @__PURE__ */ new Map();
3616
+ setAbsNodeCollector((node, value) => {
3617
+ nodes.set(node, value);
3618
+ });
3619
+ try {
3620
+ const { env } = evalProgramAbs2(source, {
3621
+ ...seeds,
3622
+ modules
3623
+ });
3624
+ for (const [k, v] of env.vars) binds.set(k, v);
3625
+ for (const [name, impl] of env.fns) {
3626
+ if (!binds.has(name)) {
3627
+ binds.set(
3628
+ name,
3629
+ absFunction5(impl.params, { body: impl.body, async: impl.async, env })
3630
+ );
3631
+ }
3632
+ }
3633
+ } catch {
3634
+ } finally {
3635
+ setAbsNodeCollector(null);
3636
+ }
3637
+ return { binds, nodes };
3638
+ }
3639
+ function buildAbsImportLocalMap(source, fromFile) {
3640
+ const out = /* @__PURE__ */ new Map();
3641
+ try {
3642
+ const file = parse5(source);
3643
+ for (const stmt of file.program.body) {
3644
+ if (stmt.type !== "ImportDeclaration") continue;
3645
+ const spec = stmt.source.value;
3646
+ let modulePath = null;
3647
+ if (spec.startsWith(".") || spec.startsWith("/")) {
3648
+ modulePath = resolveImportAbs(spec, fromFile);
3649
+ } else if (!spec.startsWith("node:")) {
3650
+ modulePath = spec;
3651
+ }
3652
+ if (!modulePath) continue;
3653
+ for (const s of stmt.specifiers) {
3654
+ if (s.type === "ImportSpecifier") {
3655
+ const imported = s.imported.type === "Identifier" ? s.imported.name : String(s.imported);
3656
+ out.set(s.local.name, { modulePath, exportName: imported });
3657
+ } else if (s.type === "ImportDefaultSpecifier") {
3658
+ out.set(s.local.name, { modulePath, exportName: "default" });
3659
+ }
3660
+ }
3661
+ }
3662
+ } catch {
3663
+ }
3664
+ return out;
3665
+ }
3666
+ function resolveImportAbs(spec, fromFile) {
3667
+ const base = dirname5(resolve4(fromFile));
3668
+ const p = resolve4(base, spec);
3669
+ for (const cand of [p, `${p}.js`, `${p}.mjs`, `${p}.ts`, resolve4(p, "index.js")]) {
3670
+ if (existsSync3(cand) && !statSync3(cand).isDirectory()) return cand;
3671
+ }
3672
+ return null;
3673
+ }
3674
+ function tryEvalAbs(source, fnName, args, filePath, mocks) {
3675
+ const raw = tryEvalAbsRaw(source, fnName, args, filePath, mocks);
3676
+ return raw ? absToTypeValue2(raw) : void 0;
3677
+ }
3678
+ function tryEvalAbsRaw(source, fnName, args, filePath, mocks) {
3679
+ if (/\brequire\s*\(/.test(source)) return void 0;
3680
+ try {
3681
+ const absArgs = args.map((a) => typeValueToAbs5(a));
3682
+ if (filePath) {
3683
+ const viaB = tryBPathCall(source, filePath, fnName, absArgs, { mocks });
3684
+ if (viaB) return viaB;
3685
+ }
3686
+ let modules;
3687
+ if (filePath && /\bimport\s*[{'"*]/.test(source)) {
3688
+ modules = evalAbsModuleGraph(source, filePath).modules;
3689
+ }
3690
+ const result = analyzeFn(source, fnName, absArgs, void 0, void 0, void 0, modules);
3691
+ if (result.shape.k === "unknown" && !result.term) {
3692
+ return void 0;
3693
+ }
3694
+ return result;
3695
+ } catch {
3696
+ return void 0;
3697
+ }
3698
+ }
3699
+ function tryEvalEntryAbs(source, fnName, args, filePath, mocks) {
3700
+ return tryEvalAbs(source, fnName, args, filePath, mocks);
3701
+ }
3702
+ function tryAttachIntension(caseResult, source, fnName) {
3703
+ try {
3704
+ const g = generalizeFromAst2(fnName, source);
3705
+ if (!g) return;
3706
+ const sym = g.symbolic;
3707
+ const intension = {
3708
+ display: g.display,
3709
+ abs: formatAbs3(sym),
3710
+ absMultiline: formatAbsMultiline(sym, fnName)
3711
+ };
3712
+ if (sym.term && sym.term.op !== "lit") {
3713
+ intension.term = termToString(sym.term);
3714
+ }
3715
+ if (sym.pred && sym.pred.op !== "true") {
3716
+ intension.pred = predToString(sym.pred);
3717
+ }
3718
+ intension.conf = sym.conf;
3719
+ caseResult.intension = intension;
3720
+ } catch {
3721
+ }
3722
+ }
3723
+ function attachAbsToIntension(caseResult, absVal, label) {
3724
+ const prev = caseResult.intension ?? {};
3725
+ caseResult.abs = absVal;
3726
+ caseResult.intension = {
3727
+ ...prev,
3728
+ abs: formatAbs3(absVal),
3729
+ absMultiline: formatAbsMultiline(absVal, label ?? caseResult.name),
3730
+ conf: absVal.conf
3731
+ };
3732
+ }
3733
+
3734
+ // src/index.ts
3735
+ import { collectAbsInlays } from "@nudojs/core";
3736
+
3737
+ // src/infer-json.ts
3738
+ import { typeValueToString as typeValueToString4 } from "@nudojs/core";
3739
+ function mapCase(c) {
3740
+ const out = {
3741
+ name: c.name,
3742
+ args: c.args.map((a) => typeValueToString4(a)),
3743
+ result: typeValueToString4(c.result),
3744
+ throws: c.throws.kind !== "never" ? typeValueToString4(c.throws) : null,
3745
+ source: c.source ?? null
3746
+ };
3747
+ if (c.aggregatedFrom !== void 0) out.aggregatedFrom = c.aggregatedFrom;
3748
+ if (c.intension) {
3749
+ const i = c.intension;
3750
+ out.intension = {
3751
+ ...i.display !== void 0 ? { display: i.display } : {},
3752
+ ...i.abs !== void 0 ? { abs: i.abs } : {},
3753
+ ...i.absMultiline !== void 0 ? { absMultiline: i.absMultiline } : {},
3754
+ ...i.term !== void 0 ? { term: i.term } : {},
3755
+ ...i.pred !== void 0 ? { pred: i.pred } : {},
3756
+ ...i.conf !== void 0 ? { conf: i.conf } : {}
3757
+ };
3758
+ }
3759
+ return out;
3760
+ }
3761
+ function mapFunction(f) {
3762
+ const out = {
3763
+ name: f.name,
3764
+ loc: f.loc,
3765
+ entryOnly: f.entryOnly ?? false,
3766
+ cases: f.cases.map(mapCase)
3767
+ };
3768
+ if (f.noDeclaration) out.noDeclaration = true;
3769
+ if (f.combined) out.combined = typeValueToString4(f.combined);
3770
+ return out;
3771
+ }
3772
+ function serializeInferJson(result, file) {
3773
+ const functions = result.functions.map(mapFunction);
3774
+ const external = result.externalFunctions?.map((f) => ({
3775
+ name: f.name,
3776
+ fromModule: f.fromModule,
3777
+ cases: f.cases.map(mapCase)
3778
+ }));
3779
+ const caseCount = functions.reduce((n, f) => n + f.cases.length, 0) + (external ?? []).reduce((n, f) => n + f.cases.length, 0);
3780
+ const json = {
3781
+ version: 1,
3782
+ file,
3783
+ summary: {
3784
+ functions: functions.length,
3785
+ externalFunctions: external?.length ?? 0,
3786
+ cases: caseCount,
3787
+ diagnostics: result.diagnostics.length
3788
+ },
3789
+ functions,
3790
+ diagnostics: result.diagnostics.map((d) => ({
3791
+ range: d.range,
3792
+ severity: d.severity,
3793
+ message: d.message,
3794
+ ...d.code !== void 0 ? { code: d.code } : {},
3795
+ ...d.suggestions ? { suggestions: d.suggestions } : {},
3796
+ ...d.tags ? { tags: d.tags } : {},
3797
+ ...d.origin ? { origin: d.origin } : {}
3798
+ }))
3799
+ };
3800
+ if (external) json.externalFunctions = external;
3801
+ return json;
3802
+ }
3803
+
3804
+ // src/target-path.ts
3805
+ function isNudoTargetPath(path) {
3806
+ const lower = path.toLowerCase();
3807
+ if (lower.endsWith(".d.ts")) return false;
3808
+ return lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".ts");
3809
+ }
3810
+
3811
+ // src/load-module.ts
3812
+ import { existsSync as existsSync4, statSync as statSync4, readFileSync as readFileSync5 } from "fs";
3813
+ import { dirname as dirname6, resolve as resolve5, join as join3 } from "path";
3814
+ function defaultLoadModule(spec, fromFile) {
3815
+ if (!spec.startsWith(".") && !spec.startsWith("/")) return void 0;
3816
+ try {
3817
+ const base = dirname6(resolve5(fromFile));
3818
+ const p = resolve5(base, spec);
3819
+ for (const cand of [
3820
+ p,
3821
+ `${p}.js`,
3822
+ `${p}.mjs`,
3823
+ `${p}.ts`,
3824
+ join3(p, "index.js"),
3825
+ join3(p, "index.mjs"),
3826
+ join3(p, "index.ts")
3827
+ ]) {
3828
+ if (existsSync4(cand) && !statSync4(cand).isDirectory()) {
3829
+ return readFileSync5(cand, "utf-8");
3830
+ }
3831
+ }
3832
+ return void 0;
3833
+ } catch {
3834
+ return void 0;
3835
+ }
3836
+ }
3837
+
3838
+ // src/session-cache.ts
3839
+ import {
3840
+ resetGeneralizeMemo,
3841
+ resetCheckSourceMemo,
3842
+ resetNudoModuleExecCache,
3843
+ resetParseSourceCache
3844
+ } from "@nudojs/core";
3845
+ function evictAnalysisCachesForFiles(files) {
3846
+ if (files.length === 0) return;
3847
+ evictBPathCacheForFiles(files);
3848
+ evictAnalysisFileCacheForFiles(files);
3849
+ evictFnAnalysisCacheForFiles(files);
3850
+ evictAbsModuleCacheFiles(files);
3851
+ }
3852
+ function clearAnalysisSessionCaches() {
3853
+ clearBPathCache();
3854
+ clearAbsModuleCache();
3855
+ resetGeneralizeMemo();
3856
+ resetCheckSourceMemo();
3857
+ resetNudoModuleExecCache();
3858
+ }
3859
+ function resetAllAnalysisCaches() {
3860
+ clearAnalysisSessionCaches();
3861
+ resetParseSourceCache();
3862
+ }
3863
+
3864
+ // src/harvest-node.ts
3865
+ import { existsSync as existsSync5 } from "fs";
3866
+ import { harvestDts as harvestDts2 } from "@nudojs/harvester";
3867
+ function harvestNodeTypes(fromDir, maxFiles = 12, maxMs = 2500) {
3868
+ const root = resolvePackageRoot("@types/node", fromDir) ?? resolvePackageRoot("node", fromDir);
3869
+ if (!root || !existsSync5(root)) {
3870
+ return { ok: false, error: "@types/node not found" };
3871
+ }
3872
+ const dts = collectDtsFiles(root, maxFiles);
3873
+ if (dts.length === 0) {
3874
+ return { ok: false, error: `no .d.ts under ${root}` };
3875
+ }
3876
+ const env = harvestDts2(dts, { maxMs });
3877
+ return { ok: true, env, root, files: env.stats.files };
335
3878
  }
336
- function getCompletionsAtPosition(filePath, source, line, column) {
337
- const textBefore = getTextBeforePosition(source, line, column);
338
- const dotMatch = textBefore.match(/(\w+)\.\s*\w*$/);
339
- if (!dotMatch) return getVariableCompletions(filePath, source);
340
- const objName = dotMatch[1];
341
- const safeSource = sanitizeSourceForParsing(source);
3879
+ function summarizeNodeEnv(env) {
3880
+ return {
3881
+ modules: Object.keys(env.modules).sort(),
3882
+ globals: Object.keys(env.globals).sort().slice(0, 50),
3883
+ symbolCount: env.stats.symbols
3884
+ };
3885
+ }
3886
+
3887
+ // src/semantic-tokens.ts
3888
+ import { dirname as dirname7 } from "path";
3889
+ import { createEnvironment as createEnvironment3 } from "@nudojs/core";
3890
+ import { parse as parse6 } from "@nudojs/parser";
3891
+ import {
3892
+ evaluateProgram as evaluateProgram2,
3893
+ setModuleResolver as setModuleResolver2,
3894
+ setCurrentFileDir as setCurrentFileDir2,
3895
+ resetMemo as resetMemo2
3896
+ } from "@nudojs/cli/evaluator";
3897
+ var SEMANTIC_TOKEN_TYPES = [
3898
+ "function",
3899
+ "variable",
3900
+ "parameter",
3901
+ "property",
3902
+ "type",
3903
+ "keyword",
3904
+ "string",
3905
+ "number",
3906
+ "comment",
3907
+ "decorator",
3908
+ "method"
3909
+ ];
3910
+ var SEMANTIC_TOKEN_MODIFIERS = [
3911
+ "declaration",
3912
+ "readonly",
3913
+ "deprecated",
3914
+ "unreachable"
3915
+ ];
3916
+ function encodeSemanticTokens(tokens) {
3917
+ const result = [];
3918
+ let prevLine = 0;
3919
+ let prevChar = 0;
3920
+ for (const token of tokens) {
3921
+ const deltaLine = token.line - prevLine;
3922
+ const deltaChar = deltaLine === 0 ? token.char - prevChar : token.char;
3923
+ result.push(deltaLine, deltaChar, token.length, token.typeIndex, token.modifierBitmask);
3924
+ prevLine = token.line;
3925
+ prevChar = token.char;
3926
+ }
3927
+ return result;
3928
+ }
3929
+ var TYPE_FUNCTION = SEMANTIC_TOKEN_TYPES.indexOf("function");
3930
+ var TYPE_VARIABLE = SEMANTIC_TOKEN_TYPES.indexOf("variable");
3931
+ var TYPE_PARAMETER = SEMANTIC_TOKEN_TYPES.indexOf("parameter");
3932
+ var TYPE_PROPERTY = SEMANTIC_TOKEN_TYPES.indexOf("property");
3933
+ var TYPE_METHOD = SEMANTIC_TOKEN_TYPES.indexOf("method");
3934
+ var MOD_DECLARATION = 1 << SEMANTIC_TOKEN_MODIFIERS.indexOf("declaration");
3935
+ function buildSemanticTokens(filePath, source) {
342
3936
  let ast;
343
3937
  try {
344
- ast = parse(safeSource);
3938
+ ast = parse6(source);
345
3939
  } catch {
346
- try {
347
- ast = parse(source);
348
- } catch {
349
- return [];
350
- }
351
- }
352
- resetMemo();
353
- setModuleResolver(resolveModule);
354
- setCurrentFileDir(dirname(filePath));
355
- const globalEnv = createEnvironment();
356
- evaluateProgram(ast, globalEnv);
357
- if (!globalEnv.has(objName)) {
358
- setModuleResolver(null);
359
3940
  return [];
360
3941
  }
361
- const objType = globalEnv.lookup(objName);
362
- const completions = getCompletionsForType(objType);
363
- setModuleResolver(null);
364
- return completions;
365
- }
366
- function sanitizeSourceForParsing(source) {
367
- return source.replace(/(\w+)\.\s*$/gm, "$1._ ");
368
- }
369
- function getTextBeforePosition(source, line, column) {
370
- const lines = source.split("\n");
371
- if (line < 1 || line > lines.length) return "";
372
- return lines[line - 1].slice(0, column);
373
- }
374
- function getVariableCompletions(filePath, source) {
375
- const ast = parse(source);
376
- resetMemo();
377
- setModuleResolver(resolveModule);
378
- setCurrentFileDir(dirname(filePath));
379
- const globalEnv = createEnvironment();
380
- evaluateProgram(ast, globalEnv);
381
- const ownBindings = globalEnv.getOwnBindings();
382
- const completions = [];
383
- for (const [name, tv] of Object.entries(ownBindings)) {
384
- if (name.startsWith("__export_")) continue;
385
- completions.push({
386
- label: name,
387
- kind: tv.kind === "function" ? "method" : "variable",
388
- detail: typeValueToString(tv)
389
- });
3942
+ const env = createEnvironment3();
3943
+ resetMemo2();
3944
+ setModuleResolver2(resolveModule);
3945
+ setCurrentFileDir2(dirname7(filePath));
3946
+ try {
3947
+ evaluateProgram2(ast, env);
3948
+ } catch {
3949
+ } finally {
3950
+ setModuleResolver2(null);
390
3951
  }
391
- setModuleResolver(null);
392
- return completions;
393
- }
394
- function getCompletionsForType(tv) {
395
- const completions = [];
396
- if (tv.kind === "object") {
397
- for (const [key, val] of Object.entries(tv.properties)) {
398
- completions.push({
399
- label: key,
400
- kind: val.kind === "function" ? "method" : "property",
401
- detail: typeValueToString(val)
402
- });
3952
+ const program = ast.program ?? ast;
3953
+ const ownBindings = env.getOwnBindings();
3954
+ const topLevelDeclarators = /* @__PURE__ */ new Set();
3955
+ for (const stmt of program.body ?? []) {
3956
+ const decl = stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportDefaultDeclaration" ? stmt.declaration ?? null : stmt;
3957
+ if (decl && decl.type === "VariableDeclaration") {
3958
+ for (const d of decl.declarations) {
3959
+ topLevelDeclarators.add(d);
3960
+ }
403
3961
  }
404
- return completions;
405
3962
  }
406
- if (tv.kind === "instance") {
407
- for (const [key, val] of Object.entries(tv.properties)) {
408
- completions.push({
409
- label: key,
410
- kind: val.kind === "function" ? "method" : "property",
411
- detail: typeValueToString(val)
412
- });
3963
+ const tokens = [];
3964
+ const pushIdentifier = (id, typeIndex) => {
3965
+ const loc = id.loc;
3966
+ const name = id.name;
3967
+ if (!loc || typeof name !== "string") return;
3968
+ tokens.push({
3969
+ line: loc.start.line - 1,
3970
+ char: loc.start.column,
3971
+ length: name.length,
3972
+ typeIndex,
3973
+ modifierBitmask: MOD_DECLARATION
3974
+ });
3975
+ };
3976
+ const collectParams = (params) => {
3977
+ for (const p of params) {
3978
+ if (!p) continue;
3979
+ if (p.type === "Identifier") pushIdentifier(p, TYPE_PARAMETER);
3980
+ else if (p.type === "AssignmentPattern" && p.left?.type === "Identifier") {
3981
+ pushIdentifier(p.left, TYPE_PARAMETER);
3982
+ } else if (p.type === "RestElement" && p.argument?.type === "Identifier") {
3983
+ pushIdentifier(p.argument, TYPE_PARAMETER);
3984
+ }
413
3985
  }
414
- return completions;
415
- }
416
- if (tv.kind === "array" || tv.kind === "tuple") {
417
- const arrayMethods = [
418
- { label: "map", detail: "map(callback)" },
419
- { label: "filter", detail: "filter(callback)" },
420
- { label: "reduce", detail: "reduce(callback, init)" },
421
- { label: "find", detail: "find(callback)" },
422
- { label: "some", detail: "some(callback)" },
423
- { label: "every", detail: "every(callback)" },
424
- { label: "forEach", detail: "forEach(callback)" },
425
- { label: "flatMap", detail: "flatMap(callback)" },
426
- { label: "includes", detail: "includes(value)" },
427
- { label: "indexOf", detail: "indexOf(value)" },
428
- { label: "join", detail: "join(separator)" },
429
- { label: "slice", detail: "slice(start, end)" },
430
- { label: "concat", detail: "concat(other)" },
431
- { label: "push", detail: "push(value)" },
432
- { label: "length", detail: tv.kind === "tuple" ? `${tv.elements.length}` : "number" }
433
- ];
434
- for (const m of arrayMethods) {
435
- completions.push({ label: m.label, kind: "method", detail: m.detail });
3986
+ };
3987
+ const isFunctionValue = (name) => ownBindings[name]?.kind === "function";
3988
+ const visit = (node) => {
3989
+ if (!node || typeof node !== "object") return;
3990
+ const n = node;
3991
+ switch (n.type) {
3992
+ case "VariableDeclarator": {
3993
+ const id = n.id;
3994
+ if (id?.type === "Identifier") {
3995
+ const isTopLevel = topLevelDeclarators.has(node);
3996
+ const typeIndex = isTopLevel && isFunctionValue(id.name) ? TYPE_FUNCTION : TYPE_VARIABLE;
3997
+ pushIdentifier(id, typeIndex);
3998
+ }
3999
+ break;
4000
+ }
4001
+ case "FunctionDeclaration":
4002
+ case "FunctionExpression": {
4003
+ const id = n.id;
4004
+ if (id?.type === "Identifier") pushIdentifier(id, TYPE_FUNCTION);
4005
+ collectParams(n.params ?? []);
4006
+ break;
4007
+ }
4008
+ case "ArrowFunctionExpression":
4009
+ collectParams(n.params ?? []);
4010
+ break;
4011
+ case "ObjectMethod": {
4012
+ const key = n.key;
4013
+ if (key?.type === "Identifier" && !n.computed) pushIdentifier(key, TYPE_METHOD);
4014
+ collectParams(n.params ?? []);
4015
+ break;
4016
+ }
4017
+ case "ObjectProperty": {
4018
+ const key = n.key;
4019
+ const value = n.value;
4020
+ if (key?.type === "Identifier" && !n.computed) {
4021
+ const isFn = value?.type === "FunctionExpression" || value?.type === "ArrowFunctionExpression";
4022
+ pushIdentifier(key, isFn ? TYPE_METHOD : TYPE_PROPERTY);
4023
+ }
4024
+ break;
4025
+ }
436
4026
  }
437
- return completions;
438
- }
439
- if (tv.kind === "promise") {
440
- completions.push({ label: "then", kind: "method", detail: "then(callback)" });
441
- completions.push({ label: "catch", kind: "method", detail: "catch(callback)" });
442
- completions.push({ label: "finally", kind: "method", detail: "finally(callback)" });
443
- return completions;
444
- }
445
- if (tv.kind === "primitive" && tv.type === "string") {
446
- const stringMethods = ["toUpperCase", "toLowerCase", "trim", "split", "slice", "includes", "indexOf", "replace", "startsWith", "endsWith", "charAt", "length"];
447
- for (const m of stringMethods) {
448
- completions.push({ label: m, kind: "method", detail: `string.${m}` });
4027
+ for (const key of Object.keys(n)) {
4028
+ if (key === "loc" || key === "start" || key === "end" || key === "leadingComments" || key === "trailingComments" || key === "innerComments") continue;
4029
+ const child = n[key];
4030
+ if (Array.isArray(child)) {
4031
+ for (const item of child) {
4032
+ if (item && typeof item === "object" && typeof item.type === "string") {
4033
+ visit(item);
4034
+ }
4035
+ }
4036
+ } else if (child && typeof child === "object" && typeof child.type === "string") {
4037
+ visit(child);
4038
+ }
449
4039
  }
450
- return completions;
451
- }
452
- return completions;
4040
+ };
4041
+ visit(program);
4042
+ tokens.sort((a, b) => a.line - b.line || a.char - b.char);
4043
+ return encodeSemanticTokens(tokens);
453
4044
  }
454
4045
 
455
4046
  // src/dts-generator.ts
456
- import { isTemplate, getTemplateParts } from "@nudojs/core";
4047
+ import { T as T3, isTemplate, getTemplateParts, simplifyUnion as simplifyUnion2, typeValueEquals, getFnSig as getFnSig4 } from "@nudojs/core";
457
4048
  function typeValueToTSType(tv) {
458
4049
  switch (tv.kind) {
459
4050
  case "literal": {
@@ -487,8 +4078,10 @@ function typeValueToTSType(tv) {
487
4078
  return `[${inner}]`;
488
4079
  }
489
4080
  case "function": {
490
- const params = tv.params.map((p, i) => `${p}: unknown`).join(", ");
491
- return `(${params}) => unknown`;
4081
+ const params = tv.params.map((p) => `${p}: unknown`).join(", ");
4082
+ const sig = getFnSig4(tv);
4083
+ const retStr = sig?.returnType ? typeValueToTSType(sig.returnType) : "unknown";
4084
+ return `(${params}) => ${retStr}`;
492
4085
  }
493
4086
  case "promise":
494
4087
  return `Promise<${typeValueToTSType(tv.value)}>`;
@@ -507,33 +4100,671 @@ function wrapComplexType(tv) {
507
4100
  if (tv.kind === "union") return `(${ts})`;
508
4101
  return ts;
509
4102
  }
4103
+ function getParamName(fn, index) {
4104
+ if (fn.paramNames && fn.paramNames[index]) {
4105
+ return fn.paramNames[index];
4106
+ }
4107
+ return `arg${index}`;
4108
+ }
4109
+ function widenLiteralToBase(tv) {
4110
+ if (tv.kind !== "literal") return tv;
4111
+ const v = tv.value;
4112
+ if (typeof v === "number") return T3.number;
4113
+ if (typeof v === "string") return T3.string;
4114
+ if (typeof v === "boolean") return T3.boolean;
4115
+ if (typeof v === "bigint") return T3.bigint;
4116
+ return tv;
4117
+ }
4118
+ function widenTopLevel(tv) {
4119
+ if (tv.kind !== "union") return widenLiteralToBase(tv);
4120
+ return simplifyUnion2(tv.members.map(widenLiteralToBase));
4121
+ }
4122
+ function widenProperties(properties) {
4123
+ const out = {};
4124
+ for (const [k, v] of Object.entries(properties)) out[k] = widenParamType(v);
4125
+ return out;
4126
+ }
4127
+ function widenParamType(tv) {
4128
+ switch (tv.kind) {
4129
+ case "literal":
4130
+ return widenLiteralToBase(tv);
4131
+ case "union":
4132
+ return simplifyUnion2(tv.members.map(widenParamType));
4133
+ case "tuple": {
4134
+ const widened = tv.elements.map(widenParamType);
4135
+ const first = widened[0];
4136
+ if (first && widened.every((el) => typeValueEquals(el, first))) {
4137
+ return T3.array(first);
4138
+ }
4139
+ return T3.tuple(widened);
4140
+ }
4141
+ case "array":
4142
+ return T3.array(widenParamType(tv.element));
4143
+ case "object":
4144
+ return T3.object(widenProperties(tv.properties));
4145
+ case "promise":
4146
+ return T3.promise(widenParamType(tv.value));
4147
+ case "instance":
4148
+ return T3.instanceOf(tv.className, widenProperties(tv.properties));
4149
+ case "refined":
4150
+ return widenParamType(tv.base);
4151
+ default:
4152
+ return tv;
4153
+ }
4154
+ }
4155
+ function widenedUnion(members) {
4156
+ const flat = [];
4157
+ const collect = (tv) => {
4158
+ if (tv.kind === "union") {
4159
+ tv.members.forEach(collect);
4160
+ return;
4161
+ }
4162
+ flat.push(tv);
4163
+ };
4164
+ members.forEach(collect);
4165
+ const widened = flat.map(widenParamType);
4166
+ const seen = /* @__PURE__ */ new Set();
4167
+ const deduped = widened.filter((tv) => {
4168
+ const key = typeValueToTSType(tv);
4169
+ if (seen.has(key)) return false;
4170
+ seen.add(key);
4171
+ return true;
4172
+ });
4173
+ return simplifyUnion2(deduped);
4174
+ }
4175
+ function computeMainSignature(fn) {
4176
+ const arity = Math.max(...fn.cases.map((c) => c.args.length));
4177
+ const minArity = Math.min(...fn.cases.map((c) => c.args.length));
4178
+ const params = [];
4179
+ const paramNames = [];
4180
+ const paramTypes = [];
4181
+ const usedNames = /* @__PURE__ */ new Set();
4182
+ for (let i = 0; i < arity; i++) {
4183
+ const members = fn.cases.filter((c) => i < c.args.length).map((c) => c.args[i]);
4184
+ const typeStr = typeValueToTSType(widenedUnion(members));
4185
+ let name = getParamName(fn, i);
4186
+ if (usedNames.has(name)) {
4187
+ let n = 2;
4188
+ while (usedNames.has(`${name}${n}`)) n++;
4189
+ name = `${name}${n}`;
4190
+ }
4191
+ usedNames.add(name);
4192
+ const optional = i >= minArity && !name.startsWith("...");
4193
+ params.push(optional ? `${name}?: ${typeStr}` : `${name}: ${typeStr}`);
4194
+ paramNames.push(name);
4195
+ paramTypes.push(typeStr);
4196
+ }
4197
+ const combined = fn.combined ?? simplifyUnion2(fn.cases.map((c) => c.result));
4198
+ const returnType = typeValueToTSType(widenTopLevel(combined));
4199
+ return { params, paramNames, paramTypes, returnType };
4200
+ }
4201
+ function generateJSDoc(fn, sig) {
4202
+ if (fn.cases.length === 0) return "";
4203
+ const lines = ["/**"];
4204
+ for (const c of fn.cases) {
4205
+ const preciseDiffers = c.args.length !== sig.paramTypes.length || c.args.some((a, i) => typeValueToTSType(a) !== sig.paramTypes[i]) || typeValueToTSType(c.result) !== sig.returnType;
4206
+ if (!preciseDiffers) continue;
4207
+ const argsStr = c.args.map(typeValueToTSType).join(", ");
4208
+ lines.push(` * Case: ${c.name} (${argsStr}) => ${typeValueToTSType(c.result)}`);
4209
+ }
4210
+ for (let i = 0; i < sig.paramTypes.length; i++) {
4211
+ lines.push(` * @param ${sig.paramNames[i]} - ${sig.paramTypes[i]}`);
4212
+ }
4213
+ lines.push(` * @returns ${sig.returnType}`);
4214
+ lines.push(" */");
4215
+ return lines.join("\n");
4216
+ }
4217
+ function generateFunctionDtsLines(fn) {
4218
+ if (fn.noDeclaration) return [];
4219
+ if (fn.cases.length === 0) {
4220
+ if (fn.combined) {
4221
+ return [
4222
+ `export declare function ${fn.name}(...args: unknown[]): ${typeValueToTSType(fn.combined)};`
4223
+ ];
4224
+ }
4225
+ return [];
4226
+ }
4227
+ const sig = computeMainSignature(fn);
4228
+ const jsdoc = generateJSDoc(fn, sig);
4229
+ const lines = [];
4230
+ if (jsdoc) lines.push(jsdoc);
4231
+ lines.push(`export declare function ${fn.name}(${sig.params.join(", ")}): ${sig.returnType};`);
4232
+ return lines;
4233
+ }
510
4234
  function generateDts(result) {
511
4235
  const lines = [];
512
4236
  for (const fn of result.functions) {
513
- if (fn.cases.length === 0 && fn.combined) {
514
- lines.push(`export declare function ${fn.name}(...args: unknown[]): ${typeValueToTSType(fn.combined)};`);
4237
+ lines.push(...generateFunctionDtsLines(fn));
4238
+ }
4239
+ return lines.join("\n") + "\n";
4240
+ }
4241
+
4242
+ // src/schema-generator.ts
4243
+ function typeValueToZodSchema(tv) {
4244
+ switch (tv.kind) {
4245
+ case "literal": {
4246
+ if (tv.value === null) return "z.null()";
4247
+ if (tv.value === void 0) return "z.undefined()";
4248
+ if (typeof tv.value === "string") return `z.literal(${JSON.stringify(tv.value)})`;
4249
+ if (typeof tv.value === "boolean") return `z.literal(${tv.value})`;
4250
+ return `z.literal(${tv.value})`;
4251
+ }
4252
+ case "primitive":
4253
+ return `z.${tv.type}()`;
4254
+ case "refined":
4255
+ return typeValueToZodSchema(tv.base);
4256
+ case "object": {
4257
+ const entries = Object.entries(tv.properties).map(([k, v]) => `${k}: ${typeValueToZodSchema(v)}`).join(", ");
4258
+ return `z.object({ ${entries} })`;
4259
+ }
4260
+ case "array":
4261
+ return `z.array(${typeValueToZodSchema(tv.element)})`;
4262
+ case "tuple": {
4263
+ const inner = tv.elements.map(typeValueToZodSchema).join(", ");
4264
+ return `z.tuple([${inner}])`;
4265
+ }
4266
+ case "function":
4267
+ return "z.function()";
4268
+ case "promise":
4269
+ return `z.promise(${typeValueToZodSchema(tv.value)})`;
4270
+ case "instance":
4271
+ return `z.instanceof(${tv.className})`;
4272
+ case "union": {
4273
+ const members = tv.members.map(typeValueToZodSchema).join(", ");
4274
+ return `z.union([${members}])`;
4275
+ }
4276
+ case "never":
4277
+ return "z.never()";
4278
+ case "unknown":
4279
+ return "z.unknown()";
4280
+ }
4281
+ }
4282
+
4283
+ // src/guard-generator.ts
4284
+ import { denoteGuard } from "@nudojs/core";
4285
+ function generateGuardFunction(name, tv) {
4286
+ const body = generateGuardBody(tv, "data");
4287
+ return `export function ${name}(data) {
4288
+ return ${body};
4289
+ }`;
4290
+ }
4291
+ function generateGuardFunctionFromAbs(name, abs) {
4292
+ const body = denoteGuard(abs, "data");
4293
+ return `export function ${name}(data) {
4294
+ return ${body};
4295
+ }`;
4296
+ }
4297
+ function generateGuardBody(tv, varName) {
4298
+ switch (tv.kind) {
4299
+ case "literal": {
4300
+ if (tv.value === null) return `${varName} === null`;
4301
+ if (tv.value === void 0) return `${varName} === undefined`;
4302
+ return `${varName} === ${JSON.stringify(tv.value)}`;
4303
+ }
4304
+ case "primitive":
4305
+ return `typeof ${varName} === '${tv.type}'`;
4306
+ case "refined":
4307
+ return generateGuardBody(tv.base, varName);
4308
+ case "object": {
4309
+ const checks = [`typeof ${varName} === 'object'`, `${varName} !== null`];
4310
+ for (const [key, val] of Object.entries(tv.properties)) {
4311
+ checks.push(generateGuardBody(val, `${varName}.${key}`));
4312
+ }
4313
+ return checks.join(" && ");
4314
+ }
4315
+ case "array":
4316
+ return `Array.isArray(${varName}) && ${varName}.every(item => ${generateGuardBody(tv.element, "item")})`;
4317
+ case "tuple": {
4318
+ const checks = [`Array.isArray(${varName})`, `${varName}.length === ${tv.elements.length}`];
4319
+ tv.elements.forEach((el, i) => {
4320
+ checks.push(generateGuardBody(el, `${varName}[${i}]`));
4321
+ });
4322
+ return checks.join(" && ");
4323
+ }
4324
+ case "union": {
4325
+ const members = tv.members.map((m) => generateGuardBody(m, varName));
4326
+ return `(${members.join(" || ")})`;
4327
+ }
4328
+ case "never":
4329
+ return "false";
4330
+ case "unknown":
4331
+ return "true";
4332
+ case "function":
4333
+ return `typeof ${varName} === 'function'`;
4334
+ case "promise":
4335
+ return `${varName} instanceof Promise`;
4336
+ case "instance":
4337
+ return `${varName} instanceof ${tv.className}`;
4338
+ }
4339
+ }
4340
+
4341
+ // src/case-emitter.ts
4342
+ import { parse as parse7, extractDirectives as extractDirectives2 } from "@nudojs/parser";
4343
+ var GENERATED_PREFIX = "call@";
4344
+ var GENERATED_CASE_LINE_REGEX = /^\s*\*?\s*@nudo:case\s+"(call@[^"]*)"/;
4345
+ var IDENT_KEY_REGEX = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
4346
+ var NUMBER_LITERAL_REGEX = /^-?\d+(\.\d+)?$/;
4347
+ var UNSAFE_STRING_CHARS = /[,()[\]{}]/;
4348
+ var UNSAFE_KEY_CHARS = /[,()[\]{}:]/;
4349
+ var CONTROL_CHARS = /[\x00-\x1f\x7f]/;
4350
+ function serializeStringLiteral(value) {
4351
+ if (CONTROL_CHARS.test(value)) return null;
4352
+ if (value.includes("*/")) return null;
4353
+ if (UNSAFE_STRING_CHARS.test(value)) return null;
4354
+ const hasDbl = value.includes('"');
4355
+ const hasSgl = value.includes("'");
4356
+ if (hasDbl && hasSgl) return null;
4357
+ return hasDbl ? `'${value}'` : `"${value}"`;
4358
+ }
4359
+ function serializeObjectKey(key) {
4360
+ if (IDENT_KEY_REGEX.test(key)) return key;
4361
+ if (CONTROL_CHARS.test(key)) return null;
4362
+ if (key.includes('"')) return null;
4363
+ if (UNSAFE_KEY_CHARS.test(key) || key.includes("*/")) return null;
4364
+ if (/^['"]|['"]$/.test(key)) return null;
4365
+ return `"${key}"`;
4366
+ }
4367
+ function serializeCaseArg(tv) {
4368
+ switch (tv.kind) {
4369
+ case "primitive":
4370
+ if (tv.type === "number" || tv.type === "string" || tv.type === "boolean") {
4371
+ return `T.${tv.type}`;
4372
+ }
4373
+ return null;
4374
+ case "unknown":
4375
+ return "T.unknown";
4376
+ case "never":
4377
+ return "T.never";
4378
+ case "literal": {
4379
+ const v = tv.value;
4380
+ if (typeof v === "number") {
4381
+ if (!Number.isFinite(v)) return null;
4382
+ const s = String(v);
4383
+ return NUMBER_LITERAL_REGEX.test(s) ? s : null;
4384
+ }
4385
+ if (typeof v === "boolean") return v ? "true" : "false";
4386
+ if (v === null) return "null";
4387
+ if (v === void 0) return "undefined";
4388
+ if (typeof v === "bigint") return null;
4389
+ return serializeStringLiteral(v);
4390
+ }
4391
+ case "union": {
4392
+ const parts = [];
4393
+ for (const member of tv.members) {
4394
+ const s = serializeCaseArg(member);
4395
+ if (s === null) return null;
4396
+ parts.push(s);
4397
+ }
4398
+ return `T.union(${parts.join(", ")})`;
4399
+ }
4400
+ case "array": {
4401
+ const el = serializeCaseArg(tv.element);
4402
+ return el === null ? null : `T.array(${el})`;
4403
+ }
4404
+ case "tuple": {
4405
+ const parts = [];
4406
+ for (const el of tv.elements) {
4407
+ const s = serializeCaseArg(el);
4408
+ if (s === null) return null;
4409
+ parts.push(s);
4410
+ }
4411
+ return `[${parts.join(", ")}]`;
4412
+ }
4413
+ case "object": {
4414
+ const parts = [];
4415
+ for (const [key, value] of Object.entries(tv.properties)) {
4416
+ const vs = serializeCaseArg(value);
4417
+ if (vs === null) return null;
4418
+ const ks = serializeObjectKey(key);
4419
+ if (ks === null) return null;
4420
+ parts.push(`${ks}: ${vs}`);
4421
+ }
4422
+ return parts.length === 0 ? "{}" : `{ ${parts.join(", ")} }`;
4423
+ }
4424
+ default:
4425
+ return null;
4426
+ }
4427
+ }
4428
+ function buildCaseDirective(name, args) {
4429
+ if (name.includes('"') || /[\r\n]/.test(name)) return null;
4430
+ const parts = [];
4431
+ for (const arg of args) {
4432
+ const s = serializeCaseArg(arg);
4433
+ if (s === null) return null;
4434
+ parts.push(s);
4435
+ }
4436
+ return ` * @nudo:case "${name}" (${parts.join(", ")})`;
4437
+ }
4438
+ function stripGeneratedCaseDirectives(source) {
4439
+ const lines = source.split("\n");
4440
+ const removed = [];
4441
+ const drop = new Array(lines.length).fill(false);
4442
+ for (let i = 0; i < lines.length; i++) {
4443
+ const m = lines[i].match(GENERATED_CASE_LINE_REGEX);
4444
+ if (m) {
4445
+ drop[i] = true;
4446
+ removed.push(m[1]);
4447
+ }
4448
+ }
4449
+ for (let i = 0; i < lines.length; i++) {
4450
+ if (!/^\s*\/\*\*\s*$/.test(lines[i])) continue;
4451
+ let j = i + 1;
4452
+ let clean = true;
4453
+ while (j < lines.length) {
4454
+ if (/\*\//.test(lines[j])) break;
4455
+ if (/\/\*/.test(lines[j])) {
4456
+ clean = false;
4457
+ break;
4458
+ }
4459
+ j++;
4460
+ }
4461
+ if (!clean || j >= lines.length) continue;
4462
+ if (!/^\s*\*\/\s*$/.test(lines[j])) continue;
4463
+ let onlyFiller = true;
4464
+ for (let k = i + 1; k < j; k++) {
4465
+ if (drop[k]) continue;
4466
+ const t = lines[k].trim();
4467
+ if (t !== "" && !/^\*{1,2}$/.test(t)) {
4468
+ onlyFiller = false;
4469
+ break;
4470
+ }
4471
+ }
4472
+ if (onlyFiller) {
4473
+ for (let k = i; k <= j; k++) drop[k] = true;
4474
+ }
4475
+ }
4476
+ return {
4477
+ source: lines.filter((_, i) => !drop[i]).join("\n"),
4478
+ removed
4479
+ };
4480
+ }
4481
+ function collectExistingCases(source) {
4482
+ const byName = /* @__PURE__ */ new Map();
4483
+ let ast;
4484
+ try {
4485
+ ast = parse7(source, { errorRecovery: true });
4486
+ } catch {
4487
+ return byName;
4488
+ }
4489
+ for (const { name, directives } of extractDirectives2(ast)) {
4490
+ for (const d of directives) {
4491
+ if (d.kind !== "case") continue;
4492
+ const list = byName.get(name);
4493
+ if (list) list.push(d);
4494
+ else byName.set(name, [d]);
4495
+ }
4496
+ }
4497
+ return byName;
4498
+ }
4499
+ function insertGeneratedCaseDirectives(source, analysis) {
4500
+ const written = [];
4501
+ const skipped = [];
4502
+ const existing = collectExistingCases(source);
4503
+ const edits = [];
4504
+ for (const fn of analysis.functions) {
4505
+ const existingCases = existing.get(fn.name) ?? [];
4506
+ if (existingCases.some((c) => !c.name.startsWith(GENERATED_PREFIX))) {
4507
+ skipped.push({ fn: fn.name, reason: "hand-written" });
4508
+ continue;
4509
+ }
4510
+ if (existingCases.length > 0) {
4511
+ skipped.push({ fn: fn.name, reason: "already-generated" });
4512
+ continue;
4513
+ }
4514
+ if (fn.skipped) {
4515
+ skipped.push({ fn: fn.name, reason: "skipped" });
4516
+ continue;
4517
+ }
4518
+ if (fn.noDeclaration) {
4519
+ skipped.push({ fn: fn.name, reason: "no-declaration" });
4520
+ continue;
4521
+ }
4522
+ if (fn.entryOnly) {
4523
+ skipped.push({ fn: fn.name, reason: "entry-only" });
4524
+ continue;
4525
+ }
4526
+ const callsiteCases = fn.cases.filter((c) => c.source === "callsite");
4527
+ if (callsiteCases.length === 0) {
4528
+ skipped.push({ fn: fn.name, reason: "no-serializable-cases", detail: "no callsite cases" });
515
4529
  continue;
516
4530
  }
517
- if (fn.cases.length === 1) {
518
- const c = fn.cases[0];
519
- const params = c.args.map((a, i) => `arg${i}: ${typeValueToTSType(a)}`).join(", ");
520
- const ret = typeValueToTSType(c.result);
521
- lines.push(`export declare function ${fn.name}(${params}): ${ret};`);
4531
+ const built = [];
4532
+ const names = [];
4533
+ for (const c of callsiteCases) {
4534
+ const directive = buildCaseDirective(c.name, c.args);
4535
+ if (directive === null) {
4536
+ skipped.push({ fn: fn.name, reason: "no-serializable-cases", detail: `case ${c.name} not serializable` });
4537
+ } else {
4538
+ built.push(directive);
4539
+ names.push(c.name);
4540
+ }
4541
+ }
4542
+ if (built.length === 0) continue;
4543
+ edits.push({ line: fn.loc.start.line, column: fn.loc.start.column, directives: built, fn: fn.name });
4544
+ written.push({ fn: fn.name, cases: names });
4545
+ }
4546
+ edits.sort((a, b) => b.line - a.line);
4547
+ const lines = source.split("\n");
4548
+ for (const edit of edits) {
4549
+ const declIdx = edit.line - 1;
4550
+ const indent = " ".repeat(edit.column);
4551
+ let insertIdx = -1;
4552
+ let blockIndent = "";
4553
+ const aboveIdx = declIdx - 1;
4554
+ if (aboveIdx >= 0 && /^\s*\*\/\s*$/.test(lines[aboveIdx])) {
4555
+ let s = aboveIdx - 1;
4556
+ while (s >= 0) {
4557
+ const t = lines[s];
4558
+ if (/^\s*\/\*\*/.test(t)) {
4559
+ insertIdx = s + 1;
4560
+ blockIndent = t.match(/^\s*/)?.[0] ?? "";
4561
+ break;
4562
+ }
4563
+ if (/^\s*\*/.test(t) || t.trim() === "") {
4564
+ s--;
4565
+ continue;
4566
+ }
4567
+ break;
4568
+ }
4569
+ }
4570
+ if (insertIdx >= 0) {
4571
+ lines.splice(insertIdx, 0, ...edit.directives.map((d) => blockIndent + d));
4572
+ } else {
4573
+ lines.splice(declIdx, 0, `${indent}/**`, ...edit.directives.map((d) => indent + d), `${indent} */`);
4574
+ }
4575
+ }
4576
+ return {
4577
+ source: lines.join("\n"),
4578
+ changed: written.length > 0,
4579
+ written,
4580
+ skipped
4581
+ };
4582
+ }
4583
+ function diffOps(a, b) {
4584
+ let start = 0;
4585
+ while (start < a.length && start < b.length && a[start] === b[start]) start++;
4586
+ let endA = a.length;
4587
+ let endB = b.length;
4588
+ while (endA > start && endB > start && a[endA - 1] === b[endB - 1]) {
4589
+ endA--;
4590
+ endB--;
4591
+ }
4592
+ const midA = a.slice(start, endA);
4593
+ const midB = b.slice(start, endB);
4594
+ const n = midA.length;
4595
+ const m = midB.length;
4596
+ const dp = new Int32Array((n + 1) * (m + 1));
4597
+ for (let i2 = n - 1; i2 >= 0; i2--) {
4598
+ for (let j2 = m - 1; j2 >= 0; j2--) {
4599
+ dp[i2 * (m + 1) + j2] = midA[i2] === midB[j2] ? dp[(i2 + 1) * (m + 1) + (j2 + 1)] + 1 : Math.max(dp[(i2 + 1) * (m + 1) + j2], dp[i2 * (m + 1) + (j2 + 1)]);
4600
+ }
4601
+ }
4602
+ const ops = [];
4603
+ for (let k = 0; k < start; k++) ops.push({ type: "equal", text: a[k] });
4604
+ let i = 0;
4605
+ let j = 0;
4606
+ while (i < n && j < m) {
4607
+ if (midA[i] === midB[j]) {
4608
+ ops.push({ type: "equal", text: midA[i] });
4609
+ i++;
4610
+ j++;
4611
+ } else if (dp[(i + 1) * (m + 1) + j] >= dp[i * (m + 1) + (j + 1)]) {
4612
+ ops.push({ type: "delete", text: midA[i] });
4613
+ i++;
4614
+ } else {
4615
+ ops.push({ type: "insert", text: midB[j] });
4616
+ j++;
4617
+ }
4618
+ }
4619
+ while (i < n) ops.push({ type: "delete", text: midA[i++] });
4620
+ while (j < m) ops.push({ type: "insert", text: midB[j++] });
4621
+ for (let k = endA; k < a.length; k++) ops.push({ type: "equal", text: a[k] });
4622
+ return ops;
4623
+ }
4624
+ var DIFF_CONTEXT = 3;
4625
+ function formatRange(start, count) {
4626
+ const s = count === 0 ? start - 1 : start;
4627
+ return count === 1 ? `${s}` : `${s},${count}`;
4628
+ }
4629
+ function unifiedDiff(a, b, path) {
4630
+ if (a === b) return "";
4631
+ const ops = diffOps(a.split("\n"), b.split("\n"));
4632
+ const hunks = [];
4633
+ let idx = 0;
4634
+ while (idx < ops.length) {
4635
+ if (ops[idx].type === "equal") {
4636
+ idx++;
522
4637
  continue;
523
4638
  }
524
- for (const c of fn.cases) {
525
- const params = c.args.map((a, i) => `arg${i}: ${typeValueToTSType(a)}`).join(", ");
526
- const ret = typeValueToTSType(c.result);
527
- lines.push(`export declare function ${fn.name}(${params}): ${ret};`);
4639
+ let start = idx;
4640
+ let ctx = 0;
4641
+ while (start > 0 && ops[start - 1].type === "equal" && ctx < DIFF_CONTEXT) {
4642
+ start--;
4643
+ ctx++;
4644
+ }
4645
+ let j = idx;
4646
+ let equalRun = 0;
4647
+ let lastChange = idx;
4648
+ while (j < ops.length) {
4649
+ if (ops[j].type === "equal") {
4650
+ equalRun++;
4651
+ if (equalRun > DIFF_CONTEXT * 2) break;
4652
+ } else {
4653
+ equalRun = 0;
4654
+ lastChange = j;
4655
+ }
4656
+ j++;
4657
+ }
4658
+ let end = lastChange + 1;
4659
+ ctx = 0;
4660
+ while (end < ops.length && ops[end].type === "equal" && ctx < DIFF_CONTEXT) {
4661
+ end++;
4662
+ ctx++;
528
4663
  }
4664
+ hunks.push({ start, ops: ops.slice(start, end) });
4665
+ idx = end;
529
4666
  }
530
- return lines.join("\n") + "\n";
4667
+ const out = [`--- a/${path}`, `+++ b/${path}`];
4668
+ for (let h = 0; h < hunks.length; h++) {
4669
+ const { start, ops: hunkOps } = hunks[h];
4670
+ let oldCount = 0;
4671
+ let newCount = 0;
4672
+ for (let k = 0; k < start; k++) {
4673
+ if (ops[k].type !== "insert") oldCount++;
4674
+ if (ops[k].type !== "delete") newCount++;
4675
+ }
4676
+ let dels = 0;
4677
+ let ins = 0;
4678
+ for (const op of hunkOps) {
4679
+ if (op.type === "delete") dels++;
4680
+ else if (op.type === "insert") ins++;
4681
+ else {
4682
+ dels++;
4683
+ ins++;
4684
+ }
4685
+ }
4686
+ out.push(`@@ -${formatRange(oldCount + 1, dels)} +${formatRange(newCount + 1, ins)} @@`);
4687
+ for (const op of hunkOps) {
4688
+ const prefix = op.type === "equal" ? " " : op.type === "delete" ? "-" : "+";
4689
+ out.push(prefix + op.text);
4690
+ }
4691
+ }
4692
+ return out.join("\n") + "\n";
531
4693
  }
532
4694
  export {
4695
+ SEMANTIC_TOKEN_MODIFIERS,
4696
+ SEMANTIC_TOKEN_TYPES,
4697
+ analyzeExportsFromSource,
533
4698
  analyzeFile,
4699
+ analyzeFileAsync,
4700
+ autoHarvestModules,
4701
+ barePackageName,
4702
+ bareSpecToAbsModules,
4703
+ buildCaseDirective,
4704
+ buildModuleGraph,
4705
+ buildSemanticTokens,
4706
+ clearAbsModuleCache,
4707
+ clearAnalysisFileCache,
4708
+ clearAnalysisSessionCaches,
4709
+ clearBPathCache,
4710
+ clearFnAnalysisCache,
4711
+ clearHarvestCache,
4712
+ collectAbsBindingsFromGraph,
4713
+ collectAbsInlays,
4714
+ collectBPathDiagnostics,
4715
+ collectBPathReplacements,
4716
+ collectBarePackages,
4717
+ collectCallRecords,
4718
+ collectDependencySpecs,
4719
+ collectDtsFromEntry,
4720
+ collectEnvGlobals,
4721
+ collectEnvModules,
4722
+ collectStaticImports,
4723
+ computeDirtySet,
4724
+ defaultAbsLoadModule,
4725
+ defaultLoadModule,
4726
+ encodeSemanticTokens,
4727
+ envValueToAbs,
4728
+ evalAbsModuleGraph,
4729
+ evalProgramAbsWithModules,
4730
+ evictAbsModuleCacheFiles,
4731
+ evictAnalysisCachesForFiles,
4732
+ evictAnalysisFileCacheForFiles,
4733
+ evictBPathCacheForFiles,
4734
+ evictFnAnalysisCacheForFiles,
4735
+ formatHarvestSummary,
534
4736
  generateDts,
4737
+ generateFunctionDtsLines,
4738
+ generateGuardFunction,
4739
+ generateGuardFunctionFromAbs,
4740
+ getAnalysisFileCacheSize,
535
4741
  getCasesForFile,
536
4742
  getCompletionsAtPosition,
4743
+ getHoverAtPosition,
537
4744
  getTypeAtPosition,
538
- typeValueToTSType
4745
+ getTypeAtPositionAsync,
4746
+ harvestNodeTypes,
4747
+ harvestPackage,
4748
+ harvestPackageCached,
4749
+ harvestToAbsModules,
4750
+ harvestedValueToAbs,
4751
+ insertGeneratedCaseDirectives,
4752
+ isBPathCapable,
4753
+ isNudoTargetPath,
4754
+ lookupHarvested,
4755
+ mockDirectivesToAbsSeeds,
4756
+ packageHarvestToAbsModules,
4757
+ resetAllAnalysisCaches,
4758
+ resolvePackageRoot,
4759
+ serializeCaseArg,
4760
+ serializeInferJson,
4761
+ stripGeneratedCaseDirectives,
4762
+ summarizeNodeEnv,
4763
+ topoSortDirty,
4764
+ tryBPathCall,
4765
+ tryBPathCallFull,
4766
+ tryRunBPath,
4767
+ typeValueToTSType,
4768
+ typeValueToZodSchema,
4769
+ unifiedDiff
539
4770
  };