@nudojs/service 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,79 +1,169 @@
1
1
  import {
2
- BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS,
3
- USAGE_SITE_MODULE,
4
- evaluate,
5
- evaluateFunction,
6
- evaluateFunctionFull,
7
- evaluateProgram,
2
+ builtinProtoMember,
3
+ builtinProtoMemberNames,
4
+ describeAbsMember,
8
5
  findProjectConfig,
9
- getUnreachableRanges,
10
6
  interfaceConfig,
11
7
  loadEnvs,
12
- memberMayExistOn,
13
- preloadPathEnvs,
14
- resetEnvModules,
15
- resetMemo,
16
- resetMockModules,
17
- resetUnreachableRanges,
18
- resolveNpmNudo,
19
- setCallCollector,
20
- setCurrentFileDir,
21
- setCurrentSource,
22
- setEnvModules,
23
- setMockModules,
24
- setModuleResolver,
25
- setNodeTypeCollector,
26
- setProvenanceTracking,
27
- setSampleCount,
28
- setUnknownBuiltinHandler,
29
- setUnknownCollector,
30
- setUsageSiteTag
31
- } from "./chunk-FSIHAE7I.js";
8
+ matchesEmitAllowlist,
9
+ preloadPathEnvs
10
+ } from "./chunk-2VHITMTM.js";
32
11
 
33
12
  // src/analyzer.ts
34
13
  import { readFileSync as readFileSync5, existsSync as existsSync4, statSync as statSync4, realpathSync } from "fs";
35
14
  import { resolve as resolve5, dirname as dirname6 } from "path";
36
15
  import traverse from "@babel/traverse";
37
16
  import {
38
- T as T2,
39
- typeValueToString as typeValueToString3,
40
- simplifyUnion,
41
- widenLiteral,
42
- collapseLiteralUnion,
43
17
  createEnvironment as createEnvironment2,
44
- isSubtypeOf,
45
- mockHelperToTypeValue,
46
18
  generalizeFromAst as generalizeFromAst2,
47
19
  termToString,
48
20
  predToString,
49
- analyzeFn,
21
+ analyzeFnFull,
50
22
  evalProgramAbs as evalProgramAbs2,
23
+ callFunctionFull,
51
24
  setAbsCallCollector,
25
+ unknown as absUnknown2,
52
26
  setAbsNodeCollector,
53
- absFunction as absFunction5,
27
+ absFunction as absFunction4,
54
28
  checkInjectedDomainEvidence,
55
- typeValueToAbs as typeValueToAbs5,
56
- absToTypeValue as absToTypeValue2,
57
- formatAbs as formatAbs3,
29
+ formatAbs as formatAbs4,
58
30
  formatAbsMultiline,
31
+ formatShape as formatShape2,
32
+ leqAbs,
33
+ numLit,
34
+ strLit,
35
+ boolLit,
36
+ abs as makeAbsVal,
59
37
  stableAnalyzeKeySource as stableAnalyzeKeySource2,
60
38
  fnFingerprints
61
39
  } from "@nudojs/core";
62
- import { parse as parse5, extractDirectives, extractFileDirectives, parseTypeValueExpr as parseTypeValueExpr2 } from "@nudojs/parser";
40
+ import { parse as parse5, extractDirectives, extractFileDirectives } from "@nudojs/parser";
41
+
42
+ // src/evaluator/call-record.ts
43
+ import { formatAbs, joinAbs, litValue } from "@nudojs/core";
44
+ var neverAbs = { shape: { k: "never" }, conf: "exact" };
45
+ var undefAbs = {
46
+ shape: { k: "unknown" },
47
+ term: { op: "lit", value: void 0 },
48
+ conf: "exact"
49
+ };
50
+ function absStructureKey(a) {
51
+ const v = litValue(a);
52
+ if (v !== void 0) return `L:${typeof v}:${String(v)}`;
53
+ if (a.term?.op === "lit") return `L:${typeof a.term.value}:${String(a.term.value)}`;
54
+ try {
55
+ return formatAbs(a);
56
+ } catch {
57
+ return a.shape.k;
58
+ }
59
+ }
60
+ function shapeNodeCount(a, seen) {
61
+ if (!a || typeof a !== "object") return 1;
62
+ if (seen.has(a)) return 0;
63
+ seen.add(a);
64
+ const s = a.shape;
65
+ switch (s.k) {
66
+ case "arr":
67
+ return 1 + shapeNodeCount(s.element, seen);
68
+ case "tuple":
69
+ return 1 + s.elements.reduce((acc, e) => acc + shapeNodeCount(e, seen), 0);
70
+ case "obj": {
71
+ let n = 1;
72
+ for (const slot of Object.values(s.slots)) n += shapeNodeCount(slot.value, seen);
73
+ return n;
74
+ }
75
+ case "eff":
76
+ return 1 + shapeNodeCount(s.inner, seen);
77
+ case "sum":
78
+ return 1 + s.members.reduce((acc, e) => acc + shapeNodeCount(e, seen), 0);
79
+ case "brand":
80
+ return 1 + shapeNodeCount(s.shape, seen);
81
+ case "fn": {
82
+ let n = 1;
83
+ for (const p of s.paramTypes ?? []) n += shapeNodeCount(p, seen);
84
+ if (s.returnType) n += shapeNodeCount(s.returnType, seen);
85
+ return n;
86
+ }
87
+ default:
88
+ return 1;
89
+ }
90
+ }
91
+ var MAX_RECORD_TYPE_NODES = 2e3;
92
+ function absNodeCount(a, seen = /* @__PURE__ */ new Set()) {
93
+ return shapeNodeCount(a, seen);
94
+ }
95
+ function isOversizedCallRecord(rec) {
96
+ const seen = /* @__PURE__ */ new Set();
97
+ for (const a of rec.argAbs) {
98
+ if (absNodeCount(a, seen) > MAX_RECORD_TYPE_NODES) return true;
99
+ }
100
+ return absNodeCount(rec.resultAbs, seen) > MAX_RECORD_TYPE_NODES;
101
+ }
102
+ function isLeakedCallRecord(rec) {
103
+ return rec.resultAbs.shape.k === "never" && rec.throwsAbs.shape.k === "never";
104
+ }
105
+ function joinAllAbs(members) {
106
+ if (members.length === 0) return neverAbs;
107
+ return members.reduce((a, b) => joinAbs(a, b));
108
+ }
109
+ function isPrimLit(a) {
110
+ return a.shape.k === "prim" && a.term?.op === "lit";
111
+ }
112
+ function widenAbsPrim(a) {
113
+ if (a.shape.k === "prim" && a.term?.op === "lit") {
114
+ return { shape: a.shape, conf: a.conf === "exact" ? "path" : a.conf };
115
+ }
116
+ return a;
117
+ }
118
+ function collapseAbsLits(members, maxLits) {
119
+ const uniq = [];
120
+ const seen = /* @__PURE__ */ new Set();
121
+ for (const m of members) {
122
+ if (m.shape.k === "never") continue;
123
+ const k = absStructureKey(m);
124
+ if (seen.has(k)) continue;
125
+ seen.add(k);
126
+ uniq.push(m);
127
+ }
128
+ if (uniq.length === 0) return neverAbs;
129
+ if (uniq.length === 1) return uniq[0];
130
+ const nonLitPrims = /* @__PURE__ */ new Set();
131
+ for (const m of uniq) {
132
+ if (m.shape.k === "prim" && m.term?.op !== "lit") {
133
+ nonLitPrims.add(m.shape.type);
134
+ }
135
+ }
136
+ if (nonLitPrims.size > 0) {
137
+ const absorbed = uniq.filter(
138
+ (m) => !(m.shape.k === "prim" && m.term?.op === "lit" && nonLitPrims.has(m.shape.type))
139
+ );
140
+ if (absorbed.length !== uniq.length) {
141
+ return collapseAbsLits(absorbed, maxLits);
142
+ }
143
+ }
144
+ if (uniq.length > maxLits && uniq.every(isPrimLit)) {
145
+ const t = uniq[0].shape.type;
146
+ if (uniq.every((m) => m.shape.k === "prim" && m.shape.type === t)) {
147
+ return { shape: { k: "prim", type: t }, conf: "path" };
148
+ }
149
+ }
150
+ return { shape: { k: "sum", members: uniq }, conf: "exact" };
151
+ }
152
+ function widenJoinAbs(members) {
153
+ return widenAbsPrim(joinAllAbs(members));
154
+ }
63
155
 
64
156
  // src/mock-abs.ts
65
- import { parseTypeValueExpr } from "@nudojs/parser";
157
+ import { parseCaseArgExpr } from "@nudojs/parser";
66
158
  import {
67
159
  absFunction,
68
160
  emptyEnv,
69
- typeValueToAbs,
70
161
  abs as makeAbs,
71
162
  confJoin,
72
163
  unknown as absUnknown,
73
- litValue,
74
- formatAbs,
75
- getFnImpl,
76
- typeValueToString
164
+ litValue as litValue2,
165
+ formatAbs as formatAbs2,
166
+ getFnImpl
77
167
  } from "@nudojs/core";
78
168
  function markMockConf(a) {
79
169
  a.conf = confJoin(a.conf, "mock");
@@ -120,7 +210,7 @@ function constantMockFn(result) {
120
210
  };
121
211
  let retKey;
122
212
  try {
123
- retKey = formatAbs(marked);
213
+ retKey = formatAbs2(marked);
124
214
  } catch {
125
215
  retKey = "?";
126
216
  }
@@ -131,17 +221,16 @@ function constantMockFn(result) {
131
221
  }
132
222
  function absArgMatches(declared, actual) {
133
223
  if (!actual) return false;
134
- const declaredAbs = typeValueToAbs(declared);
135
- const av = litValue(actual);
136
- const dv = litValue(declaredAbs);
224
+ const av = litValue2(actual);
225
+ const dv = litValue2(declared);
137
226
  if (dv !== void 0) {
138
227
  return av !== void 0 && Object.is(av, dv);
139
228
  }
140
- if (declared.kind === "primitive") {
141
- if (actual.shape.k === "prim") return actual.shape.type === declared.type;
229
+ if (declared.shape.k === "prim") {
230
+ if (actual.shape.k === "prim") return actual.shape.type === declared.shape.type;
142
231
  if (av !== void 0) {
143
232
  const t = typeof av;
144
- return declared.type === "number" && t === "number" || declared.type === "string" && t === "string" || declared.type === "boolean" && t === "boolean" || declared.type === "bigint" && t === "bigint";
233
+ return declared.shape.type === "number" && t === "number" || declared.shape.type === "string" && t === "string" || declared.shape.type === "boolean" && t === "boolean" || declared.shape.type === "bigint" && t === "bigint";
145
234
  }
146
235
  }
147
236
  return false;
@@ -150,7 +239,7 @@ function dispatchMockFn(defaultReturn, cases) {
150
239
  if (!cases?.length) return constantMockFn(defaultReturn);
151
240
  const caseAbs = cases.map((c) => ({
152
241
  declared: c.args,
153
- result: markMockConf(typeValueToAbs(c.returnValue))
242
+ result: markMockConf(c.returnValue)
154
243
  }));
155
244
  const dummyBody = {
156
245
  type: "BlockStatement",
@@ -159,12 +248,12 @@ function dispatchMockFn(defaultReturn, cases) {
159
248
  const markedDefault = markMockConf(defaultReturn);
160
249
  let defaultKey;
161
250
  try {
162
- defaultKey = formatAbs(markedDefault);
251
+ defaultKey = formatAbs2(markedDefault);
163
252
  } catch {
164
253
  defaultKey = "?";
165
254
  }
166
255
  const caseKey = cases.map(
167
- (c) => `${c.args.map((a) => typeValueToString(a)).join(",")}->${typeValueToString(c.returnValue)}`
256
+ (c) => `${c.args.map((a) => formatAbs2(a)).join(",")}->${formatAbs2(c.returnValue)}`
168
257
  ).join("|");
169
258
  return stampFingerprint(
170
259
  markMockConf(
@@ -185,18 +274,17 @@ function dispatchMockFn(defaultReturn, cases) {
185
274
  );
186
275
  }
187
276
  function absFromMockHelper(h) {
188
- if (h.callsFakeImpl && h.callsFakeImpl.kind === "function") {
189
- const fn = h.callsFakeImpl;
190
- const body = fn.body;
277
+ if (h.callsFakeImpl) {
278
+ const { params, body, async } = h.callsFakeImpl;
191
279
  return stampFingerprint(
192
- absFunction(fn.params, { body, async: false }),
193
- `fake=${fn.params.join(",")}:${astContentKey(body)}`
280
+ absFunction(params, { body, async: async ?? false }),
281
+ `fake=${params.join(",")}:${astContentKey(body)}`
194
282
  );
195
283
  }
196
284
  let defaultReturn;
197
285
  if (h.resolvedValue) {
198
286
  defaultReturn = makeAbs(
199
- { k: "eff", eff: "promise", inner: typeValueToAbs(h.resolvedValue) },
287
+ { k: "eff", eff: "promise", inner: h.resolvedValue },
200
288
  void 0,
201
289
  void 0,
202
290
  "path"
@@ -204,9 +292,9 @@ function absFromMockHelper(h) {
204
292
  } else if (h.rejectedValue) {
205
293
  defaultReturn = makeAbs({ k: "never" }, void 0, void 0, "exact");
206
294
  } else if (h.returnValue) {
207
- defaultReturn = typeValueToAbs(h.returnValue);
295
+ defaultReturn = h.returnValue;
208
296
  } else if (h.onFirstCallValue) {
209
- defaultReturn = typeValueToAbs(h.onFirstCallValue);
297
+ defaultReturn = h.onFirstCallValue;
210
298
  } else {
211
299
  defaultReturn = absUnknown;
212
300
  }
@@ -216,7 +304,7 @@ function absFromSinon(sinonExpr) {
216
304
  if (sinonExpr.resolvedValue) {
217
305
  return constantMockFn(
218
306
  makeAbs(
219
- { k: "eff", eff: "promise", inner: typeValueToAbs(sinonExpr.resolvedValue) },
307
+ { k: "eff", eff: "promise", inner: sinonExpr.resolvedValue },
220
308
  void 0,
221
309
  void 0,
222
310
  "path"
@@ -227,7 +315,7 @@ function absFromSinon(sinonExpr) {
227
315
  return constantMockFn(makeAbs({ k: "never" }, void 0, void 0, "exact"));
228
316
  }
229
317
  if (sinonExpr.returnValue) {
230
- return constantMockFn(typeValueToAbs(sinonExpr.returnValue));
318
+ return constantMockFn(sinonExpr.returnValue);
231
319
  }
232
320
  return constantMockFn(absUnknown);
233
321
  }
@@ -258,7 +346,7 @@ function mockDirectivesToAbsSeeds(functions) {
258
346
  seedVars[d.name] = markMockConf(absFromSinon(d.sinonExpr));
259
347
  } else if (d.expression) {
260
348
  try {
261
- seedVars[d.name] = typeValueToAbs(parseTypeValueExpr(d.expression));
349
+ seedVars[d.name] = parseCaseArgExpr(d.expression);
262
350
  } catch {
263
351
  seedVars[d.name] = absUnknown;
264
352
  }
@@ -295,6 +383,23 @@ function defaultLoadModule(spec, fromFile) {
295
383
  }
296
384
  }
297
385
 
386
+ // src/abs-modules-graph.ts
387
+ import { readFileSync as readFileSync4, statSync as statSync3 } from "fs";
388
+ import { dirname as dirname5, resolve as resolve4 } from "path";
389
+ import { parse as parse3 } from "@nudojs/parser";
390
+ import {
391
+ evalProgramAbs,
392
+ collectAbsExports,
393
+ absFunction as absFunction3
394
+ } from "@nudojs/core";
395
+
396
+ // src/harvest-to-abs.ts
397
+ import {
398
+ confJoin as confJoin2,
399
+ absFunction as absFunction2
400
+ } from "@nudojs/core";
401
+ import { dirname as dirname4 } from "path";
402
+
298
403
  // src/harvest-auto.ts
299
404
  import { parse as parse2 } from "@nudojs/parser";
300
405
 
@@ -462,7 +567,7 @@ function collectStaticImports(entryFile, maxDepth = 8) {
462
567
  import { existsSync as existsSync3, readdirSync, statSync as statSync2, readFileSync as readFileSync3 } from "fs";
463
568
  import { join as join3, resolve as resolve3, dirname as dirname3, basename } from "path";
464
569
  import { harvestDts } from "@nudojs/harvester";
465
- import { typeValueToString as typeValueToString2 } from "@nudojs/core";
570
+ import { formatShape } from "@nudojs/core";
466
571
  function resolvePackageRoot(pkg, fromDir = process.cwd()) {
467
572
  let dir = resolve3(fromDir);
468
573
  const bare = pkg.replace(/^@/, "").replace(/\//g, "__");
@@ -599,9 +704,9 @@ function formatHarvestSummary(h) {
599
704
  }
600
705
  function lookupHarvested(h, moduleName, exportName) {
601
706
  const mod = h.env.modules[moduleName];
602
- const tv = mod?.[exportName] ?? h.env.globals[exportName];
603
- if (!tv) return void 0;
604
- return typeValueToString2(tv);
707
+ const a = mod?.[exportName] ?? h.env.globals[exportName];
708
+ if (!a) return void 0;
709
+ return formatShape(a);
605
710
  }
606
711
 
607
712
  // src/harvest-auto.ts
@@ -665,35 +770,15 @@ function autoHarvestModules(source, fromDir) {
665
770
  return modules;
666
771
  }
667
772
 
668
- // src/abs-modules-graph.ts
669
- import { readFileSync as readFileSync4, statSync as statSync3 } from "fs";
670
- import { dirname as dirname5, resolve as resolve4 } from "path";
671
- import { parse as parse3 } from "@nudojs/parser";
672
- import {
673
- evalProgramAbs,
674
- collectAbsExports,
675
- absFunction as absFunction3
676
- } from "@nudojs/core";
677
-
678
773
  // src/harvest-to-abs.ts
679
- import {
680
- T,
681
- typeValueToAbs as typeValueToAbs2,
682
- confJoin as confJoin2,
683
- getFnSig,
684
- absFunction as absFunction2
685
- } from "@nudojs/core";
686
- import { dirname as dirname4 } from "path";
687
774
  function markMockConf2(a) {
688
775
  a.conf = confJoin2(a.conf, "mock");
689
776
  return a;
690
777
  }
691
- function harvestedValueToAbs(tv) {
692
- if (!tv) return markMockConf2(typeValueToAbs2(T.unknown));
693
- const sig = getFnSig(tv);
694
- if (sig) {
695
- const ret = markMockConf2(typeValueToAbs2(sig.returnType));
696
- const params = tv.kind === "function" && tv.params?.length ? tv.params : sig.paramTypes.map((_, i) => `_arg${i}`);
778
+ function harvestedValueToAbs(a) {
779
+ if (a.shape.k === "fn") {
780
+ const ret = a.shape.returnType ?? { shape: { k: "unknown" }, conf: "mock" };
781
+ const params = a.shape.params.length ? a.shape.params : a.shape.paramTypes?.map((_, i) => `_arg${i}`) ?? ["...args"];
697
782
  const dummyBody = {
698
783
  type: "BlockStatement",
699
784
  body: [],
@@ -702,11 +787,11 @@ function harvestedValueToAbs(tv) {
702
787
  return markMockConf2(
703
788
  absFunction2(params, {
704
789
  body: dummyBody,
705
- apply: () => ret
790
+ apply: () => markMockConf2({ ...ret })
706
791
  })
707
792
  );
708
793
  }
709
- return markMockConf2(typeValueToAbs2(tv));
794
+ return markMockConf2({ ...a });
710
795
  }
711
796
  function harvestToAbsModules(pkg, fromDir) {
712
797
  const h = harvestPackageCached(pkg, fromDir);
@@ -1013,68 +1098,14 @@ import {
1013
1098
  setBCallCollector,
1014
1099
  setMemberDiagCollector,
1015
1100
  setAbsTruncationCollector,
1016
- typeValueToAbs as typeValueToAbs4,
1017
1101
  createEnvironment,
1018
1102
  stableAnalyzeKeySource,
1019
- formatAbs as formatAbs2,
1103
+ formatAbs as formatAbs3,
1020
1104
  hashSource,
1021
1105
  getFnImpl as getFnImpl2
1022
1106
  } from "@nudojs/core";
1023
1107
  import { parse as parse4, extractInlineDirectives } from "@nudojs/parser";
1024
1108
 
1025
- // src/env-to-abs.ts
1026
- import {
1027
- typeValueToAbs as typeValueToAbs3,
1028
- absToTypeValue,
1029
- getFnSig as getFnSig2,
1030
- absFunction as absFunction4,
1031
- relationFn,
1032
- objOf
1033
- } from "@nudojs/core";
1034
- function envValueToAbs(tv) {
1035
- if (!tv) return typeValueToAbs3({ kind: "unknown" });
1036
- if (tv.kind === "function") {
1037
- const sig = getFnSig2(tv);
1038
- if (sig) {
1039
- const paramTypes = sig.paramTypes.map(typeValueToAbs3);
1040
- const returnType = typeValueToAbs3(sig.returnType);
1041
- if (sig.impl) {
1042
- const params = tv.params?.length ? tv.params : sig.paramTypes.map((_, i) => `_arg${i}`);
1043
- const dummyBody = {
1044
- type: "BlockStatement",
1045
- body: [],
1046
- directives: []
1047
- };
1048
- const impl = sig.impl;
1049
- return absFunction4(params, {
1050
- body: dummyBody,
1051
- apply: (args) => {
1052
- const tvArgs = args.map((a) => absToTypeValue(a));
1053
- try {
1054
- const r = impl(tvArgs);
1055
- if (!r) return returnType;
1056
- return envValueToAbs(r);
1057
- } catch {
1058
- return returnType;
1059
- }
1060
- }
1061
- });
1062
- }
1063
- return relationFn(paramTypes, returnType, {
1064
- params: tv.params?.length ? tv.params : void 0
1065
- });
1066
- }
1067
- }
1068
- if (tv.kind === "object") {
1069
- const slots = {};
1070
- for (const [k, v] of Object.entries(tv.properties)) {
1071
- slots[k] = { value: envValueToAbs(v) };
1072
- }
1073
- return objOf(slots);
1074
- }
1075
- return typeValueToAbs3(tv);
1076
- }
1077
-
1078
1109
  // src/analysis-file-cache.ts
1079
1110
  var analysisByFile = /* @__PURE__ */ new Map();
1080
1111
  var MAX_ANALYSIS_FILE_CACHE = 64;
@@ -1140,10 +1171,10 @@ function evictFnAnalysisCacheForFiles(files) {
1140
1171
  }
1141
1172
  return n;
1142
1173
  }
1143
- function caseDirectiveKey(directives, typeValueToString7) {
1174
+ function caseDirectiveKey(directives, formatAbs7) {
1144
1175
  return directives.map((d) => {
1145
1176
  if (d.kind === "case") {
1146
- const args = (d.args ?? []).map((a) => typeValueToString7(a)).join(",");
1177
+ const args = (d.argsAbs ?? []).map((a) => formatAbs7(a)).join(",");
1147
1178
  return `c:${d.name ?? ""}:${args}`;
1148
1179
  }
1149
1180
  return d.kind;
@@ -1158,7 +1189,7 @@ function absSeedPart(a) {
1158
1189
  const ret = impl?.env?.vars?.get("__nudo_mock_ret");
1159
1190
  if (ret) {
1160
1191
  try {
1161
- return `ret=${formatAbs2(ret)}`;
1192
+ return `ret=${formatAbs3(ret)}`;
1162
1193
  } catch {
1163
1194
  return "ret=?";
1164
1195
  }
@@ -1166,7 +1197,7 @@ function absSeedPart(a) {
1166
1197
  if (impl?.apply) return "apply";
1167
1198
  }
1168
1199
  try {
1169
- return formatAbs2(a);
1200
+ return formatAbs3(a);
1170
1201
  } catch {
1171
1202
  return "?";
1172
1203
  }
@@ -1194,15 +1225,10 @@ function collectEnvGlobals(envNames) {
1194
1225
  if (envNames.length === 0) return {};
1195
1226
  const env = createEnvironment();
1196
1227
  try {
1197
- loadEnvs(envNames, env);
1228
+ return { ...loadEnvs(envNames, env).globals };
1198
1229
  } catch {
1199
1230
  return {};
1200
1231
  }
1201
- const out = {};
1202
- for (const [k, v] of Object.entries(env.getOwnBindings())) {
1203
- out[k] = envValueToAbs(v);
1204
- }
1205
- return out;
1206
1232
  }
1207
1233
  function collectEnvModules(envNames) {
1208
1234
  if (envNames.length === 0) return {};
@@ -1214,12 +1240,8 @@ function collectEnvModules(envNames) {
1214
1240
  return {};
1215
1241
  }
1216
1242
  const out = {};
1217
- for (const [spec, exports] of Object.entries(mods)) {
1218
- const named = {};
1219
- for (const [k, v] of Object.entries(exports)) {
1220
- named[k] = envValueToAbs(v);
1221
- }
1222
- out[spec] = { named };
1243
+ for (const [spec, named] of Object.entries(mods)) {
1244
+ out[spec] = { named: { ...named } };
1223
1245
  }
1224
1246
  return out;
1225
1247
  }
@@ -1245,7 +1267,7 @@ function collectBPathReplacements(source) {
1245
1267
  stmtStart: loc?.start.line,
1246
1268
  stmtEnd: loc?.end.line
1247
1269
  });
1248
- values[varName] = typeValueToAbs4(d.typeExpr);
1270
+ values[varName] = d.typeAbs;
1249
1271
  } else if (d.kind === "as" && loc) {
1250
1272
  const varName = `__as${i++}`;
1251
1273
  asTargets.push({
@@ -1253,7 +1275,7 @@ function collectBPathReplacements(source) {
1253
1275
  stmtStart: loc.start.line,
1254
1276
  stmtEnd: loc.end.line
1255
1277
  });
1256
- asValues[varName] = typeValueToAbs4(d.typeExpr);
1278
+ asValues[varName] = d.typeAbs;
1257
1279
  }
1258
1280
  }
1259
1281
  const s = stmt;
@@ -1598,60 +1620,6 @@ function collectBPathDiagnostics(source, extraKnown) {
1598
1620
 
1599
1621
  // src/analyzer.ts
1600
1622
  import { setAbsTruncationCollector as setAbsTruncationCollector2 } from "@nudojs/core";
1601
- function resolveModule(source, fromDir) {
1602
- const extensions = [".js", ".ts", ".mjs"];
1603
- const nudoPath = resolveNpmNudo(source, fromDir);
1604
- if (nudoPath) {
1605
- const src = readFileSync5(nudoPath, "utf-8");
1606
- return { ast: parse5(src), filePath: nudoPath };
1607
- }
1608
- const basePath = resolve5(fromDir, source);
1609
- for (const ext of ["", ...extensions]) {
1610
- const candidate = basePath + ext;
1611
- if (!existsSync4(candidate)) continue;
1612
- if (statSync4(candidate).isDirectory()) {
1613
- let entry = null;
1614
- const pkgPath = resolve5(candidate, "package.json");
1615
- if (existsSync4(pkgPath)) {
1616
- try {
1617
- const main = JSON.parse(readFileSync5(pkgPath, "utf-8")).main;
1618
- if (typeof main === "string") {
1619
- for (const e of ["", ...extensions]) {
1620
- const p = resolve5(candidate, main + e);
1621
- if (existsSync4(p) && statSync4(p).isFile()) {
1622
- entry = p;
1623
- break;
1624
- }
1625
- }
1626
- }
1627
- } catch {
1628
- }
1629
- }
1630
- if (!entry) {
1631
- for (const e of ["", ...extensions]) {
1632
- const p = resolve5(candidate, "index" + e);
1633
- if (existsSync4(p) && statSync4(p).isFile()) {
1634
- entry = p;
1635
- break;
1636
- }
1637
- }
1638
- }
1639
- if (!entry) return null;
1640
- const src2 = readFileSync5(entry, "utf-8");
1641
- return { ast: parse5(src2), filePath: entry };
1642
- }
1643
- if (candidate.endsWith(".json")) {
1644
- try {
1645
- return { ast: parse5("module.exports = undefined;"), filePath: candidate, json: JSON.parse(readFileSync5(candidate, "utf-8")) };
1646
- } catch {
1647
- return null;
1648
- }
1649
- }
1650
- const src = readFileSync5(candidate, "utf-8");
1651
- return { ast: parse5(src), filePath: candidate };
1652
- }
1653
- return null;
1654
- }
1655
1623
  function resolveImportPath(specifier, fromDir) {
1656
1624
  const basePath = resolve5(fromDir, specifier);
1657
1625
  for (const ext of ["", ".js", ".ts", ".mjs"]) {
@@ -1754,61 +1722,23 @@ function topoSortDirty(imports, dirty) {
1754
1722
  }
1755
1723
  return ordered;
1756
1724
  }
1757
- function applyMocks(directives, env, filePath, diagnostics) {
1725
+ function validateMockDirectives(directives, diagnostics) {
1758
1726
  for (const d of directives) {
1759
- if (d.kind !== "mock") continue;
1760
- if (d.arrowFn) {
1761
- const fnType = T2.fn(d.arrowFn.params, d.arrowFn.body, env);
1762
- fnType._paramPatterns = d.arrowFn.paramPatterns;
1763
- env.bind(d.name, fnType);
1764
- } else if (d.nudoMock) {
1765
- const typeVal = mockHelperToTypeValue(d.nudoMock, env);
1766
- env.bind(d.name, typeVal);
1767
- } else if (d.sinonExpr) {
1768
- const sinonType = createSinonTypeValue(d.sinonExpr, env);
1769
- env.bind(d.name, sinonType);
1770
- } else if (d.expression) {
1771
- const expr = d.expression.trim();
1772
- if (expr.includes("(") && expr.includes(")") && !expr.startsWith("T.")) {
1773
- diagnostics.push({
1774
- range: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
1775
- severity: "warning",
1776
- message: `Mock expression "${expr}" could not be parsed as a known pattern`,
1777
- code: "nudo:mock-invalid",
1778
- suggestions: [
1779
- "Supported formats: stub(), stub().returns(value), spy(), mock()",
1780
- "Arrow functions: (args) => expression or (args) => { statements; return value; }"
1781
- ]
1782
- });
1783
- }
1784
- env.bind(d.name, parseTypeValueExpr2(d.expression));
1785
- } else if (d.fromPath) {
1786
- const mockPath = resolve5(dirname6(filePath), d.fromPath);
1787
- const mockSource = readFileSync5(mockPath, "utf-8");
1788
- const mockAst = parse5(mockSource);
1789
- const mockEnv = createEnvironment2();
1790
- evaluateProgram(mockAst, mockEnv);
1791
- const mockVal = mockEnv.lookup(d.name);
1792
- env.bind(d.name, mockVal);
1793
- }
1794
- }
1795
- }
1796
- function createSinonTypeValue(sinonExpr, env) {
1797
- if (sinonExpr.type === "stub" || sinonExpr.type === "spy") {
1798
- const body = { type: "BlockStatement", body: [] };
1799
- const fn = T2.fn(["...args"], body, env);
1800
- if (sinonExpr.returnValue) {
1801
- fn._directReturn = sinonExpr.returnValue;
1802
- } else if (sinonExpr.resolvedValue) {
1803
- fn._directReturn = T2.promise(sinonExpr.resolvedValue);
1804
- } else if (sinonExpr.rejectedValue) {
1805
- fn._directReturn = T2.never;
1806
- } else {
1807
- fn._directReturn = T2.unknown;
1727
+ if (d.kind !== "mock" || !d.expression) continue;
1728
+ const expr = d.expression.trim();
1729
+ if (expr.includes("(") && expr.includes(")") && !expr.startsWith("T.")) {
1730
+ diagnostics.push({
1731
+ range: { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
1732
+ severity: "warning",
1733
+ message: `Mock expression "${expr}" could not be parsed as a known pattern`,
1734
+ code: "nudo:mock-invalid",
1735
+ suggestions: [
1736
+ "Supported formats: stub(), stub().returns(value), spy(), mock()",
1737
+ "Arrow functions: (args) => expression or (args) => { statements; return value; }"
1738
+ ]
1739
+ });
1808
1740
  }
1809
- return fn;
1810
1741
  }
1811
- return T2.unknown;
1812
1742
  }
1813
1743
  function locFromNode(node) {
1814
1744
  return {
@@ -1947,95 +1877,20 @@ function findSingleModuleExportsFunction(ast) {
1947
1877
  }
1948
1878
  return found;
1949
1879
  }
1950
- function typeStructureKey(tv) {
1951
- return typeStructureKeyUncached(tv, /* @__PURE__ */ new Set());
1952
- }
1953
- function typeStructureKeyUncached(tv, seen) {
1954
- const enter = (inner) => {
1955
- if (inner && typeof inner === "object") {
1956
- if (seen.has(inner)) return "\xABcycle\xBB";
1957
- seen.add(inner);
1958
- const out = typeStructureKeyUncached(inner, seen);
1959
- seen.delete(inner);
1960
- return out;
1961
- }
1962
- return typeStructureKeyUncached(inner, seen);
1963
- };
1964
- switch (tv.kind) {
1965
- case "literal":
1966
- return `lit(${typeof tv.value}:${String(tv.value)})`;
1967
- case "primitive":
1968
- return `prim(${tv.type})`;
1969
- case "array":
1970
- return `arr(${enter(tv.element)})`;
1971
- case "tuple":
1972
- return `tup(${tv.elements.map(enter).join(",")})`;
1973
- case "object":
1974
- return `obj(${Object.keys(tv.properties).sort().map((k) => `${k}:${enter(tv.properties[k])}`).join(",")})`;
1975
- case "function":
1976
- return `fn(${tv.params.join(",")})`;
1977
- case "promise":
1978
- return `prom(${enter(tv.value)})`;
1979
- case "instance":
1980
- return `inst(${tv.className})`;
1981
- case "refined":
1982
- return `ref(${enter(tv.base)})`;
1983
- case "union":
1984
- return `uni(${tv.members.map(enter).sort().join("|")})`;
1985
- default:
1986
- return tv.kind;
1987
- }
1988
- }
1989
1880
  var MAX_PRECISE_CALLSITE_CASES = 3;
1990
1881
  var COLLAPSE_LITERAL_THRESHOLD = 4;
1991
- function widenType(tv) {
1992
- if (tv.kind === "union") return simplifyUnion(tv.members.map(widenLiteral));
1993
- return widenLiteral(tv);
1994
- }
1995
1882
  function dedupeCallRecords(records) {
1996
1883
  const seen = /* @__PURE__ */ new Set();
1997
1884
  const out = [];
1998
1885
  for (const rec of records) {
1999
- if (isOversizedRecord(rec)) continue;
2000
- const key = rec.argTypes.map(typeStructureKey).join(",") + "=>" + typeStructureKey(rec.resultType) + "!" + typeStructureKey(rec.throws);
1886
+ if (isOversizedCallRecord(rec)) continue;
1887
+ const key = rec.argAbs.map(absStructureKey).join(",") + "=>" + absStructureKey(rec.resultAbs) + "!" + absStructureKey(rec.throwsAbs);
2001
1888
  if (seen.has(key)) continue;
2002
1889
  seen.add(key);
2003
1890
  out.push(rec);
2004
1891
  }
2005
1892
  return out;
2006
1893
  }
2007
- var MAX_RECORD_TYPE_NODES = 2e3;
2008
- function typeNodeCount(tv, seen) {
2009
- if (tv === null || typeof tv !== "object") return 1;
2010
- if (seen.has(tv)) return 0;
2011
- seen.add(tv);
2012
- switch (tv.kind) {
2013
- case "array":
2014
- return 1 + typeNodeCount(tv.element, seen);
2015
- case "tuple":
2016
- return 1 + tv.elements.reduce((acc, e) => acc + typeNodeCount(e, seen), 0);
2017
- case "object": {
2018
- let n = 1;
2019
- for (const v of Object.values(tv.properties)) n += typeNodeCount(v, seen);
2020
- return n;
2021
- }
2022
- case "promise":
2023
- return 1 + typeNodeCount(tv.value, seen);
2024
- case "union":
2025
- return 1 + tv.members.reduce((acc, e) => acc + typeNodeCount(e, seen), 0);
2026
- case "refined":
2027
- return 1 + typeNodeCount(tv.base, seen);
2028
- default:
2029
- return 1;
2030
- }
2031
- }
2032
- function isOversizedRecord(rec) {
2033
- const seen = /* @__PURE__ */ new Set();
2034
- for (const a of rec.argTypes) {
2035
- if (typeNodeCount(a, seen) > MAX_RECORD_TYPE_NODES) return true;
2036
- }
2037
- return typeNodeCount(rec.resultType, seen) > MAX_RECORD_TYPE_NODES;
2038
- }
2039
1894
  function locFromCallLoc(loc) {
2040
1895
  const p = loc ?? { line: 0, column: 0 };
2041
1896
  return { start: { line: p.line, column: p.column }, end: { line: p.line, column: p.column } };
@@ -2056,7 +1911,7 @@ function synthesizeExternalFunctions(records, currentFile) {
2056
1911
  const out = [];
2057
1912
  for (const { module, exportName, records: records2 } of groups.values()) {
2058
1913
  const deduped = dedupeCallRecords(records2);
2059
- const arity = Math.max(...deduped.map((r) => r.argTypes.length));
1914
+ const arity = Math.max(...deduped.map((r) => r.argAbs.length));
2060
1915
  const analysis = {
2061
1916
  name: exportName,
2062
1917
  loc: locFromCallLoc(deduped[0].callLoc),
@@ -2068,149 +1923,48 @@ function synthesizeExternalFunctions(records, currentFile) {
2068
1923
  for (const rec of precise) {
2069
1924
  analysis.cases.push({
2070
1925
  name: `call@L${rec.callLoc?.line ?? 0}`,
2071
- args: rec.argTypes,
2072
- result: rec.resultType,
2073
- throws: rec.throws,
1926
+ argAbs: [...rec.argAbs],
1927
+ abs: rec.resultAbs,
1928
+ throwsAbs: rec.throwsAbs,
2074
1929
  source: "callsite"
2075
1930
  });
2076
1931
  }
2077
1932
  const remaining = deduped.slice(MAX_PRECISE_CALLSITE_CASES);
2078
1933
  if (remaining.length > 0) {
1934
+ const symArgsAbs = Array.from(
1935
+ { length: arity },
1936
+ (_, i) => (
1937
+ // 缺参按真实 JS 语义 widen 成 undefined 而非 unknown——可选参守卫
1938
+ // (target || [] 等)对 unknown 全塌,对 undefined 正常走默认分支
1939
+ widenJoinAbs(remaining.map((rec) => rec.argAbs[i] ?? undefAbs))
1940
+ )
1941
+ );
1942
+ const symResultAbs = collapseAbsLits(
1943
+ remaining.map((r) => r.resultAbs),
1944
+ COLLAPSE_LITERAL_THRESHOLD
1945
+ );
1946
+ const symThrowsAbs = joinAllAbs(remaining.map((r) => r.throwsAbs));
2079
1947
  analysis.cases.push({
2080
1948
  name: "call@symbolic",
2081
- args: Array.from(
2082
- { length: arity },
2083
- (_, i) => (
2084
- // 缺参按真实 JS 语义 widen 成 undefined 而非 unknown——可选参守卫
2085
- // (target || [] 等)对 unknown 全塌,对 undefined 正常走默认分支
2086
- widenType(simplifyUnion(remaining.map((rec) => rec.argTypes[i] ?? T2.undefined)))
2087
- )
2088
- ),
2089
- result: collapseLiteralUnion(simplifyUnion(remaining.map((r) => r.resultType)), COLLAPSE_LITERAL_THRESHOLD),
2090
- throws: simplifyUnion(remaining.map((r) => r.throws)),
1949
+ argAbs: symArgsAbs,
1950
+ abs: symResultAbs,
1951
+ throwsAbs: symThrowsAbs,
2091
1952
  source: "callsite",
2092
1953
  aggregatedFrom: remaining.length
2093
1954
  });
2094
1955
  }
2095
1956
  if (deduped.length > 1) {
2096
- analysis.combined = collapseLiteralUnion(
2097
- simplifyUnion(deduped.map((r) => r.resultType)),
1957
+ analysis.combinedAbs = collapseAbsLits(
1958
+ deduped.map((r) => r.resultAbs),
2098
1959
  COLLAPSE_LITERAL_THRESHOLD
2099
1960
  );
2100
1961
  } else {
2101
- analysis.combined = deduped[0].resultType;
1962
+ analysis.combinedAbs = deduped[0].resultAbs;
2102
1963
  }
2103
1964
  out.push(analysis);
2104
1965
  }
2105
1966
  return out;
2106
1967
  }
2107
- function receiverTypeToDisplay(tv) {
2108
- switch (tv.kind) {
2109
- case "literal": {
2110
- const v = tv.value;
2111
- if (v === null) return "null";
2112
- if (v === void 0) return "undefined";
2113
- return typeof v;
2114
- }
2115
- case "union":
2116
- return tv.members.map(receiverTypeToDisplay).join(" | ");
2117
- default:
2118
- return typeValueToString3(tv);
2119
- }
2120
- }
2121
- function receiverIsConcrete(tv) {
2122
- if (tv.kind === "unknown") return false;
2123
- if (tv.kind === "union") return tv.members.every((m) => m.kind !== "unknown");
2124
- return true;
2125
- }
2126
- function unknownRecordsToDiagnostics(records) {
2127
- const out = [];
2128
- const seen = /* @__PURE__ */ new Set();
2129
- for (const r of records) {
2130
- const line = r.loc?.line ?? 0;
2131
- const column = r.loc?.column ?? 0;
2132
- const key = `${line}:${column}:${r.name}`;
2133
- if (seen.has(key)) continue;
2134
- seen.add(key);
2135
- const range = {
2136
- start: { line, column },
2137
- end: { line, column: column + r.name.length }
2138
- };
2139
- if (r.kind === "global") {
2140
- if (r.name.startsWith("recursion:")) {
2141
- out.push({
2142
- range,
2143
- severity: "warning",
2144
- message: `Recursive evaluation of '${r.name.slice("recursion:".length)}' was truncated (depth/size budget); result widened to unknown`,
2145
- code: "nudo:recursion-truncated"
2146
- });
2147
- continue;
2148
- }
2149
- if (r.name.startsWith("module-cycle:")) {
2150
- out.push({
2151
- range,
2152
- severity: "warning",
2153
- message: r.reason ?? "Circular module load detected",
2154
- code: "nudo:module-cycle"
2155
- });
2156
- continue;
2157
- }
2158
- if (r.name.startsWith("module-depth:")) {
2159
- out.push({
2160
- range,
2161
- severity: "warning",
2162
- message: r.reason ?? "Module load chain too deep",
2163
- code: "nudo:module-depth"
2164
- });
2165
- continue;
2166
- }
2167
- if (r.name.startsWith("module-missing:")) {
2168
- out.push({
2169
- range,
2170
- severity: "error",
2171
- message: r.reason ?? "Module file not found",
2172
- code: "nudo:module-missing"
2173
- });
2174
- continue;
2175
- }
2176
- out.push({
2177
- range,
2178
- severity: "warning",
2179
- message: `Unknown global identifier '${r.name}'`,
2180
- code: "nudo:unknown-global"
2181
- });
2182
- continue;
2183
- }
2184
- const receiver = r.receiverType;
2185
- if (receiver && receiverIsConcrete(receiver)) {
2186
- const isApprox = (() => {
2187
- const members = receiver.kind === "union" ? receiver.members : [receiver];
2188
- return members.some(
2189
- (m) => m.kind === "instance" || m.kind === "function" || m.kind === "tuple" || m.kind === "array" || m.kind === "object" || m.kind === "refined" || // wrapper/instance 近似表上可能持有该成员('x'.charCodeAt 于
2190
- // number|string:string 侧存在,number 侧缺失 → 不确定 → warning)
2191
- memberMayExistOn(m, r.name)
2192
- );
2193
- })();
2194
- const kindLabel = r.kind === "method" ? "Method" : "Property";
2195
- out.push({
2196
- range,
2197
- severity: isApprox ? "warning" : "error",
2198
- message: `${kindLabel} '${r.name}' does not exist on type '${receiverTypeToDisplay(receiver)}'`,
2199
- code: "nudo:no-method",
2200
- ...r.origin ? { origin: r.origin } : {}
2201
- });
2202
- } else {
2203
- out.push({
2204
- range,
2205
- severity: "warning",
2206
- message: `Cannot resolve '${r.name}' on unknown value`,
2207
- code: "nudo:unknown-recv",
2208
- ...r.origin ? { origin: r.origin } : {}
2209
- });
2210
- }
2211
- }
2212
- return out;
2213
- }
2214
1968
  function collectEnvNames(filePath, source, includeProject) {
2215
1969
  const ast = parse5(source);
2216
1970
  const fileDirectives = extractFileDirectives(ast);
@@ -2228,19 +1982,34 @@ async function analyzeFileAsync(filePath, source, activeCases, externalCallRecor
2228
1982
  return analyzeFile(filePath, source, activeCases, externalCallRecords);
2229
1983
  }
2230
1984
  function collectCallRecords(filePath, source) {
2231
- const records = [];
2232
- const ast = parse5(source, { errorRecovery: true });
2233
- resetMemo();
2234
- resetUnreachableRanges();
2235
- resetEnvModules();
2236
- resetMockModules();
2237
- setModuleResolver(resolveModule);
2238
- setCurrentFileDir(dirname6(filePath));
2239
- setCurrentSource(source);
2240
- setCallCollector((record) => records.push(record));
1985
+ if (/\brequire\s*\(/.test(source) && filePath) {
1986
+ try {
1987
+ const run = tryRunBPath(source, filePath, { mode: "exec" });
1988
+ if (run?.calls?.length) {
1989
+ const importLocals2 = buildAbsImportLocalMap(source, filePath);
1990
+ return run.calls.map((r) => callRecordFromAbsCall(r, importLocals2));
1991
+ }
1992
+ } catch {
1993
+ }
1994
+ }
1995
+ let ast;
1996
+ try {
1997
+ ast = parse5(source, { errorRecovery: true });
1998
+ } catch {
1999
+ return [];
2000
+ }
2001
+ const absCalls = [];
2002
+ const prevCollector = setAbsCallCollector((r) => absCalls.push(r));
2003
+ let importLocals;
2241
2004
  try {
2242
- const env = createEnvironment2();
2243
- evaluateProgram(ast, env);
2005
+ importLocals = buildAbsImportLocalMap(source, filePath);
2006
+ let modules;
2007
+ try {
2008
+ modules = evalAbsModuleGraph(source, filePath).modules;
2009
+ } catch {
2010
+ modules = void 0;
2011
+ }
2012
+ const { env } = evalProgramAbs2(source, { file: ast, modules });
2244
2013
  const runCallbacks = (statements) => {
2245
2014
  for (const stmt of statements) {
2246
2015
  if (stmt.type !== "ExpressionStatement" || !("expression" in stmt)) continue;
@@ -2254,8 +2023,16 @@ function collectCallRecords(filePath, source) {
2254
2023
  const cb = args.find((a) => a.type === "ArrowFunctionExpression" || a.type === "FunctionExpression");
2255
2024
  if (!cb) continue;
2256
2025
  try {
2257
- const params = cb.params ?? [];
2258
- evaluateFunction(cb, params.map(() => T2.unknown), env);
2026
+ const params = (cb.params ?? []).map(
2027
+ (p) => p.type === "Identifier" ? p.name : `_arg${Math.random().toString(36).slice(2, 6)}`
2028
+ );
2029
+ const tmpName = `__nudo_test_cb_${absCalls.length}`;
2030
+ env.fns.set(tmpName, {
2031
+ params,
2032
+ body: cb.body,
2033
+ async: false
2034
+ });
2035
+ callFunctionFull(env, tmpName, params.map(() => absUnknown2));
2259
2036
  } catch {
2260
2037
  }
2261
2038
  if (name === "describe" && cb.body.type === "BlockStatement") {
@@ -2266,11 +2043,9 @@ function collectCallRecords(filePath, source) {
2266
2043
  runCallbacks(ast.program.body);
2267
2044
  } catch {
2268
2045
  } finally {
2269
- setCallCollector(null);
2270
- setModuleResolver(null);
2271
- setUnknownBuiltinHandler(null);
2046
+ setAbsCallCollector(prevCollector);
2272
2047
  }
2273
- return records;
2048
+ return absCalls.map((r) => callRecordFromAbsCall(r, importLocals));
2274
2049
  }
2275
2050
  var TEST_CALLBACK_NAMES = /* @__PURE__ */ new Set(["it", "test", "describe"]);
2276
2051
  var externalRecordIds = /* @__PURE__ */ new WeakMap();
@@ -2302,7 +2077,7 @@ function cloneAnalysisResult(r) {
2302
2077
  diagnostics: r.diagnostics.map((d) => ({ ...d })),
2303
2078
  bindings: new Map(r.bindings),
2304
2079
  // Node 键与 AST LRU 共享身份;Map 浅拷贝即可
2305
- nodeTypeMap: new Map(r.nodeTypeMap),
2080
+ nodeAbsMap: new Map(r.nodeAbsMap),
2306
2081
  caseHints: r.caseHints.map((h) => ({ ...h })),
2307
2082
  ...r.externalFunctions ? { externalFunctions: r.externalFunctions.map(cloneFunctionAnalysis) } : {}
2308
2083
  };
@@ -2313,7 +2088,7 @@ function cloneFunctionAnalysis(a) {
2313
2088
  paramNames: [...a.paramNames],
2314
2089
  cases: a.cases.map((c) => ({
2315
2090
  ...c,
2316
- args: [...c.args],
2091
+ argAbs: [...c.argAbs],
2317
2092
  ...c.intension ? { intension: { ...c.intension } } : {}
2318
2093
  })),
2319
2094
  loc: { start: { ...a.loc.start }, end: { ...a.loc.end } }
@@ -2351,66 +2126,20 @@ function analyzeFile(filePath, source, activeCases, externalCallRecords) {
2351
2126
  return cloneAnalysisResult(result);
2352
2127
  }
2353
2128
  function analyzeFileUncached(filePath, source, activeCases, externalCallRecords) {
2354
- for (const rec of externalCallRecords ?? []) {
2355
- for (const a of rec.argTypes) setUsageSiteTag(a);
2356
- }
2357
2129
  const ast = parse5(source);
2358
2130
  const functions = extractDirectives(ast);
2359
2131
  const diagnostics = [];
2360
2132
  const bindings = /* @__PURE__ */ new Map();
2361
- const nodeTypeMap = /* @__PURE__ */ new Map();
2133
+ const nodeAbsMap = /* @__PURE__ */ new Map();
2362
2134
  const functionResults = [];
2363
2135
  const caseHints = [];
2364
- resetMemo();
2365
- resetUnreachableRanges();
2366
- resetEnvModules();
2367
- resetMockModules();
2368
- setModuleResolver(resolveModule);
2369
- setCurrentFileDir(dirname6(filePath));
2370
- setCurrentSource(source);
2371
2136
  const fileDirectives = extractFileDirectives(ast);
2372
2137
  const fileEnvNames = fileDirectives.filter((d) => d.kind === "env").flatMap((d) => d.envs);
2373
2138
  const projectConfig = findProjectConfig(dirname6(filePath));
2374
2139
  const projectEnvNames = projectConfig?.config.env ?? [];
2375
2140
  const envNames = [.../* @__PURE__ */ new Set([...projectEnvNames, ...fileEnvNames])];
2376
- setUnknownBuiltinHandler((name, loc) => {
2377
- if (isBPathCapable(source, envNames)) return;
2378
- diagnostics.push({
2379
- range: loc ? { start: loc.start, end: loc.end } : { start: { line: 0, column: 0 }, end: { line: 0, column: 0 } },
2380
- severity: "warning",
2381
- message: `Built-in API "${name}" is not covered by Nudo's type inference`,
2382
- code: "nudo:builtin-unknown",
2383
- suggestions: [
2384
- `Use @nudo:mock to define the type: @nudo:mock ${name} = stub().returns(...)`,
2385
- `Or use @nudo:refine return <constraint> to declare the return contract`
2386
- ]
2387
- });
2388
- });
2389
- const globalEnv = createEnvironment2();
2390
- {
2391
- const loaded = envNames.length > 0 ? loadEnvs(envNames, globalEnv) : { modules: {} };
2392
- const auto = autoHarvestModules(source, dirname6(filePath));
2393
- const modules = { ...loaded.modules, ...auto };
2394
- if (Object.keys(modules).length > 0) setEnvModules(modules);
2395
- }
2396
- const mocks = /* @__PURE__ */ new Map();
2397
- if (projectConfig?.config.mocks) {
2398
- for (const [source2, mockPath] of Object.entries(projectConfig.config.mocks)) {
2399
- mocks.set(source2, { fromPath: resolve5(projectConfig.projectDir, mockPath) });
2400
- }
2401
- }
2402
- const mockModuleDirectives = fileDirectives.filter((d) => d.kind === "mock-module");
2403
- for (const d of mockModuleDirectives) {
2404
- mocks.set(d.source, { fromPath: d.fromPath, names: d.names });
2405
- }
2406
- if (mocks.size > 0) {
2407
- setMockModules(mocks);
2408
- }
2409
2141
  const callRecords = [];
2410
- setCallCollector((record) => callRecords.push(record));
2411
- const unknownRecords = [];
2412
- setUnknownCollector((r) => unknownRecords.push(r));
2413
- setProvenanceTracking(true);
2142
+ const globalEnv = createEnvironment2();
2414
2143
  const bMemberDiagNames = /* @__PURE__ */ new Set();
2415
2144
  const bMemberDiagSeen = /* @__PURE__ */ new Set();
2416
2145
  const pushBMemberDiag = (d, fallbackLine) => {
@@ -2443,7 +2172,7 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2443
2172
  });
2444
2173
  };
2445
2174
  for (const fn of functions) {
2446
- applyMocks(fn.directives, globalEnv, filePath, diagnostics);
2175
+ validateMockDirectives(fn.directives, diagnostics);
2447
2176
  }
2448
2177
  const selfContained = isSelfContainedSource(source, envNames);
2449
2178
  const canAbsModules = absModulesOk(source, envNames);
@@ -2498,29 +2227,7 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2498
2227
  if (bRun.calls?.length) {
2499
2228
  const impMap = buildAbsImportLocalMap(source, filePath);
2500
2229
  for (const c of bRun.calls) {
2501
- const safeAbs = (a) => {
2502
- if (!a || typeof a !== "object" || !("shape" in a) || !a.shape) {
2503
- return T2.unknown;
2504
- }
2505
- try {
2506
- return absToTypeValue2(a);
2507
- } catch {
2508
- return T2.unknown;
2509
- }
2510
- };
2511
- const rec = {
2512
- fnName: c.fnName,
2513
- argTypes: c.args.map(safeAbs),
2514
- resultType: c.threw ? T2.never : safeAbs(c.result),
2515
- throws: c.threw ? safeAbs(c.result) : T2.never,
2516
- callLoc: c.callLoc
2517
- };
2518
- const imp = impMap.get(c.fnName);
2519
- if (imp) {
2520
- rec.targetModule = imp.modulePath;
2521
- rec.targetExport = imp.exportName;
2522
- }
2523
- bTopCallRecords.push(rec);
2230
+ bTopCallRecords.push(callRecordFromAbsCall(c, impMap));
2524
2231
  }
2525
2232
  }
2526
2233
  }
@@ -2541,16 +2248,14 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2541
2248
  for (const [name, absVal] of absBindsShared) {
2542
2249
  if (absVal?.shape?.k === "fn") continue;
2543
2250
  if (!globalEnv.has(name)) {
2544
- globalEnv.bind(name, absToTypeValue2(absVal));
2251
+ globalEnv.bind(name, absVal);
2545
2252
  }
2546
2253
  }
2547
2254
  for (const [node, absVal] of absNodesShared) {
2548
- nodeTypeMap.set(node, absToTypeValue2(absVal));
2255
+ nodeAbsMap.set(node, absVal);
2549
2256
  }
2550
2257
  } catch {
2551
2258
  }
2552
- } else {
2553
- evaluateProgram(ast, globalEnv);
2554
2259
  }
2555
2260
  if ((selfContained || canAbsModules) && absCallRecords.length > 0) {
2556
2261
  callRecords.length = 0;
@@ -2559,7 +2264,7 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2559
2264
  callRecords.length = 0;
2560
2265
  callRecords.push(...bTopCallRecords);
2561
2266
  }
2562
- const unreachableRanges = bCapable ? [] : getUnreachableRanges();
2267
+ const unreachableRanges = [];
2563
2268
  if (bCapable) {
2564
2269
  const bKnownGlobals = /* @__PURE__ */ new Set([
2565
2270
  ...Object.keys(collectEnvGlobals(envNames)),
@@ -2601,7 +2306,15 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2601
2306
  });
2602
2307
  }
2603
2308
  }
2604
- collectBindings(ast, globalEnv, bindings);
2309
+ if (!absNodesShared && !bHostedEval && (selfContained || canAbsModules)) {
2310
+ try {
2311
+ const collected = collectAbsBindsAndNodes(source, seeds, absGraphModules);
2312
+ if (!absBindsShared) absBindsShared = collected.binds;
2313
+ absNodesShared = collected.nodes;
2314
+ } catch {
2315
+ }
2316
+ }
2317
+ collectBindings(ast, globalEnv, bindings, absBindsShared);
2605
2318
  if (isBPathCapable(source, envNames)) {
2606
2319
  try {
2607
2320
  const absBinds = absBindsShared ?? collectAbsBindingsFromGraph(source, filePath, {
@@ -2611,7 +2324,7 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2611
2324
  for (const [name, absVal] of absBinds) {
2612
2325
  const prev = bindings.get(name);
2613
2326
  bindings.set(name, {
2614
- type: absToTypeValue2(absVal),
2327
+ abs: absVal,
2615
2328
  loc: prev?.loc
2616
2329
  });
2617
2330
  }
@@ -2636,7 +2349,7 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2636
2349
  if (skipDirective && skipDirective.kind === "skip") {
2637
2350
  analysis.skipped = true;
2638
2351
  if (skipDirective.returns) {
2639
- analysis.combined = skipDirective.returns;
2352
+ analysis.combinedAbs = skipDirective.returns;
2640
2353
  }
2641
2354
  functionResults.push(analysis);
2642
2355
  continue;
@@ -2649,7 +2362,7 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2649
2362
  fp.own,
2650
2363
  fp.deps,
2651
2364
  String(activeCaseIdx),
2652
- caseDirectiveKey(caseDirectives, typeValueToString3),
2365
+ caseDirectiveKey(caseDirectives, formatAbs4),
2653
2366
  envKeyFn,
2654
2367
  mockKeyFn
2655
2368
  ].join("\0") : void 0;
@@ -2688,35 +2401,30 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2688
2401
  }
2689
2402
  if (isPure) {
2690
2403
  const fnVal = globalEnv.has(fn.name) ? globalEnv.lookup(fn.name) : null;
2691
- if (fnVal && fnVal.kind === "function") {
2404
+ if (fnVal && fnVal.shape.k === "fn") {
2692
2405
  fnVal._memoize = fn.name;
2693
2406
  }
2694
2407
  }
2695
2408
  const sampleDirective = fn.directives.find((d) => d.kind === "sample");
2696
- if (sampleDirective && sampleDirective.kind === "sample") {
2697
- setSampleCount(sampleDirective.count);
2698
- } else {
2699
- setSampleCount(3);
2700
- }
2409
+ void sampleDirective;
2701
2410
  if (caseDirectives.length === 0) {
2702
2411
  synthCandidates.push({ name: fn.name, node: fn.node, analysis });
2703
2412
  }
2704
2413
  for (let ci = 0; ci < caseDirectives.length; ci++) {
2705
2414
  const directive = caseDirectives[ci];
2706
- resetUnreachableRanges();
2707
- let caseValue;
2708
2415
  let caseAbs;
2709
- let caseThrows;
2710
- let fullResult = null;
2416
+ let caseThrowsAbs;
2417
+ let caseThrowLoc;
2711
2418
  let caseUnreachable = [];
2712
2419
  const bCapable2 = isBPathCapable(source, envNames);
2713
2420
  const bPrimary = bCapable2;
2714
2421
  if (bCapable2 && filePath) {
2422
+ const caseArgsAbs = directive.argsAbs;
2715
2423
  const bFull = tryBPathCallFull(
2716
2424
  source,
2717
2425
  filePath,
2718
2426
  fn.name,
2719
- directive.args.map((a) => typeValueToAbs5(a)),
2427
+ caseArgsAbs,
2720
2428
  { collectCalls: true, envNames, mocks: mockSeedsToAbsMocks(seeds) }
2721
2429
  );
2722
2430
  const res = bFull?.result;
@@ -2724,87 +2432,75 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2724
2432
  const bOk = !!res && (bHostedEval || !weakUnknown && res.conf !== "opaque");
2725
2433
  if (bOk && bFull && bPrimary) {
2726
2434
  caseAbs = bFull.result;
2727
- caseValue = absToTypeValue2(bFull.result);
2728
- caseThrows = bFull.throws.shape.k !== "never" ? absToTypeValue2(bFull.throws) : T2.never;
2729
- fullResult = { value: caseValue, throws: caseThrows };
2435
+ caseThrowsAbs = bFull.throws;
2730
2436
  for (const d of bFull.memberDiags ?? []) {
2731
2437
  pushBMemberDiag(d, fnLoc.start.line);
2732
2438
  }
2733
2439
  if (bFull.calls?.length) {
2734
2440
  const impMap = buildAbsImportLocalMap(source, filePath);
2735
- const safeAbs = (a) => {
2736
- if (!a || typeof a !== "object" || !("shape" in a) || !a.shape) {
2737
- return T2.unknown;
2738
- }
2739
- try {
2740
- return absToTypeValue2(a);
2741
- } catch {
2742
- return T2.unknown;
2743
- }
2744
- };
2745
2441
  for (const c of bFull.calls) {
2746
- const rec = {
2747
- fnName: c.fnName,
2748
- argTypes: c.args.map(safeAbs),
2749
- resultType: c.threw ? T2.never : safeAbs(c.result),
2750
- throws: c.threw ? safeAbs(c.result) : T2.never,
2751
- callLoc: c.callLoc
2752
- };
2753
- const imp = impMap.get(c.fnName);
2754
- if (imp) {
2755
- rec.targetModule = imp.modulePath;
2756
- rec.targetExport = imp.exportName;
2757
- }
2758
- callRecords.push(rec);
2442
+ callRecords.push(callRecordFromAbsCall(c, impMap));
2759
2443
  }
2760
2444
  }
2761
2445
  }
2762
2446
  }
2763
- if (!fullResult) {
2447
+ if (!caseAbs) {
2764
2448
  if (bHostedEval) {
2765
- fullResult = { value: T2.unknown, throws: T2.never };
2766
- caseValue = T2.unknown;
2449
+ caseAbs = absUnknown2;
2450
+ caseThrowsAbs = neverAbs;
2767
2451
  } else {
2768
- fullResult = evaluateFunctionFull(fn.node, directive.args, globalEnv);
2769
- caseUnreachable = [...getUnreachableRanges()];
2770
- caseValue = fullResult.value;
2771
- if ((selfContained || canAbsModules) && fullResult.value.kind !== "never") {
2772
- caseAbs = tryEvalAbsRaw(source, fn.name, directive.args, filePath, mockSeedsToAbsMocks(seeds));
2773
- if (caseAbs) {
2774
- const projected = absToTypeValue2(caseAbs);
2775
- if (absIsBetter(projected, fullResult.value)) caseValue = projected;
2776
- }
2452
+ const caseArgsAbs = directive.argsAbs;
2453
+ const absFull = selfContained || canAbsModules ? tryEvalAbsFull(
2454
+ source,
2455
+ fn.name,
2456
+ caseArgsAbs,
2457
+ filePath,
2458
+ mockSeedsToAbsMocks(seeds)
2459
+ ) : void 0;
2460
+ const weak = !!absFull && absFull.result.shape.k === "unknown" && (!absFull.result.term || absFull.result.term.op === "lit" && absFull.result.term.value === void 0);
2461
+ const absOk = !!absFull && !weak && absFull.result.conf !== "opaque" && absFull.throws.shape.k === "never";
2462
+ const absThrew = !!absFull && !weak && absFull.result.shape.k === "never" && absFull.throws.shape.k !== "never";
2463
+ if (absOk) {
2464
+ caseAbs = absFull.result;
2465
+ caseThrowsAbs = neverAbs;
2466
+ } else if (absThrew) {
2467
+ caseAbs = absFull.result;
2468
+ caseThrowsAbs = absFull.throws;
2469
+ const tl = absFull.throwLoc;
2470
+ if (tl) caseThrowLoc = { start: { ...tl }, end: { ...tl } };
2471
+ } else {
2472
+ caseAbs = absUnknown2;
2473
+ caseThrowsAbs = neverAbs;
2777
2474
  }
2778
2475
  }
2779
2476
  }
2780
- const tv = caseValue;
2781
- const throwsTv = caseThrows ?? fullResult.throws;
2477
+ if (!caseThrowsAbs) caseThrowsAbs = neverAbs;
2782
2478
  const caseEntry = {
2783
2479
  name: directive.name,
2784
- args: directive.args,
2785
- result: tv,
2786
- throws: throwsTv,
2787
- throwLoc: fullResult.throwLoc,
2480
+ argAbs: directive.argsAbs,
2481
+ abs: caseAbs,
2482
+ throwsAbs: caseThrowsAbs,
2483
+ throwLoc: caseThrowLoc,
2788
2484
  expected: directive.expected,
2789
2485
  source: "directive"
2790
2486
  };
2791
2487
  tryAttachIntension(caseEntry, source, fn.name);
2792
- if (caseAbs) attachAbsToIntension(caseEntry, caseAbs, fn.name);
2488
+ attachAbsToIntension(caseEntry, caseAbs, fn.name);
2793
2489
  analysis.cases.push(caseEntry);
2794
2490
  if (directive.commentLine) {
2795
- const hasThrow = throwsTv.kind !== "never";
2796
- const resultStr = tv.kind !== "never" ? typeValueToString3(tv) : "";
2797
- const throwStr = hasThrow ? `throws ${typeValueToString3(throwsTv)}` : "";
2491
+ const hasThrow = caseThrowsAbs.shape.k !== "never";
2492
+ const resultStr = caseAbs.shape.k !== "never" ? formatShape2(caseAbs) : "";
2493
+ const throwStr = hasThrow ? `throws ${formatShape2(caseThrowsAbs)}` : "";
2798
2494
  const label = [resultStr, throwStr].filter(Boolean).join(" ");
2799
2495
  const hintLabel = `=> ${label}`;
2800
2496
  let ok = true;
2801
2497
  if (directive.expected) {
2802
- ok = isSubtypeOf(tv, directive.expected);
2498
+ ok = leqAbs(caseAbs, directive.expected).ok;
2803
2499
  if (!ok) {
2804
2500
  diagnostics.push({
2805
2501
  range: { start: { line: directive.commentLine, column: 0 }, end: { line: directive.commentLine, column: 999 } },
2806
2502
  severity: "error",
2807
- 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`,
2503
+ message: `Case "${directive.name}": expected ${formatShape2(directive.expected)}, got ${formatShape2(caseAbs)}. The inferred return type does not match the expected type declared in the @nudo:case directive`,
2808
2504
  code: "nudo:case-expected"
2809
2505
  });
2810
2506
  }
@@ -2813,12 +2509,12 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2813
2509
  }
2814
2510
  const isActive = ci === Math.min(activeCaseIdx, caseDirectives.length - 1);
2815
2511
  if (isActive) {
2816
- if (throwsTv.kind !== "never") {
2817
- const throwRange = fullResult?.throwLoc ?? fnLoc;
2512
+ if (caseThrowsAbs.shape.k !== "never") {
2513
+ const throwRange = caseThrowLoc ?? fnLoc;
2818
2514
  diagnostics.push({
2819
2515
  range: throwRange,
2820
2516
  severity: "warning",
2821
- message: `Function "${fn.name}" case "${directive.name}" may throw: ${typeValueToString3(throwsTv)}. Consider adding a try-catch block or using @nudo:refine return <constraint>`,
2517
+ message: `Function "${fn.name}" case "${directive.name}" may throw: ${formatShape2(caseThrowsAbs)}. Consider adding a try-catch block or using @nudo:refine return <constraint>`,
2822
2518
  code: "nudo-may-throw"
2823
2519
  });
2824
2520
  }
@@ -2836,10 +2532,11 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2836
2532
  }
2837
2533
  }
2838
2534
  }
2839
- if (analysis.cases.length > 1) {
2840
- analysis.combined = collapseLiteralUnion(simplifyUnion(analysis.cases.map((c) => c.result)), COLLAPSE_LITERAL_THRESHOLD);
2841
- } else if (analysis.cases.length === 1) {
2842
- analysis.combined = analysis.cases[0].result;
2535
+ if (analysis.cases.length > 0) {
2536
+ analysis.combinedAbs = collapseAbsLits(
2537
+ analysis.cases.map((c) => c.abs),
2538
+ COLLAPSE_LITERAL_THRESHOLD
2539
+ );
2843
2540
  }
2844
2541
  if (fnCacheKey) {
2845
2542
  fnAnalysisCacheSet(fnCacheKey, {
@@ -2932,13 +2629,13 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2932
2629
  [
2933
2630
  ...callRecords.filter(matchingLocal),
2934
2631
  ...(externalCallRecords ?? []).filter(matchingExternal)
2935
- ].filter((r) => !(r.resultType.kind === "never" && r.throws.kind === "never"))
2632
+ ].filter((r) => !isLeakedCallRecord(r))
2936
2633
  );
2937
2634
  reportInjectedDomainExceeds(candidate.name, candidate.node, candidate.analysis.loc);
2938
2635
  if (records.length > 0) {
2939
2636
  const informativeness = (r) => {
2940
- if (r.resultType.kind === "unknown") return 2;
2941
- if (r.resultType.kind === "never") return 1;
2637
+ if (r.resultAbs.shape.k === "unknown") return 2;
2638
+ if (r.resultAbs.shape.k === "never") return 1;
2942
2639
  return 0;
2943
2640
  };
2944
2641
  const ordered = records.map((r, i) => ({ r, i })).sort((a, b) => informativeness(a.r) - informativeness(b.r) || a.i - b.i).map(({ r }) => r);
@@ -2946,71 +2643,72 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
2946
2643
  for (const rec of precise) {
2947
2644
  let absRaw;
2948
2645
  let absResult;
2949
- if (rec.resultType.kind !== "never" && rec.argTypes.length > 0) {
2950
- absRaw = tryEvalAbsRaw(source, candidate.name, rec.argTypes, filePath, mockSeedsToAbsMocks(seeds));
2646
+ if (rec.resultAbs.shape.k !== "never" && rec.argAbs.length > 0) {
2647
+ absRaw = tryEvalAbsRaw(source, candidate.name, rec.argAbs, filePath, mockSeedsToAbsMocks(seeds));
2951
2648
  if (absRaw) {
2952
- const projected = absToTypeValue2(absRaw);
2953
- if (absIsBetter(projected, rec.resultType)) absResult = projected;
2649
+ if (absIsBetter(absRaw, rec.resultAbs)) {
2650
+ absResult = absRaw;
2651
+ }
2954
2652
  }
2955
2653
  }
2654
+ if (!absRaw && rec.resultAbs.shape.k !== "never") {
2655
+ absRaw = rec.resultAbs;
2656
+ }
2657
+ const caseAbs = absResult ?? rec.resultAbs;
2956
2658
  const caseResult2 = {
2957
2659
  name: `call@L${rec.callLoc?.line ?? candidate.analysis.loc.start.line}`,
2958
- args: rec.argTypes,
2959
- result: absResult ?? rec.resultType,
2960
- throws: rec.throws,
2660
+ argAbs: [...rec.argAbs],
2661
+ abs: caseAbs,
2662
+ throwsAbs: rec.throwsAbs,
2961
2663
  source: "callsite"
2962
2664
  };
2963
2665
  tryAttachIntension(caseResult2, source, candidate.name);
2964
2666
  if (absRaw) attachAbsToIntension(caseResult2, absRaw, candidate.name);
2965
2667
  candidate.analysis.cases.push(caseResult2);
2966
2668
  }
2967
- const remaining = ordered.slice(MAX_PRECISE_CALLSITE_CASES).filter((rec) => !rec.argTypes.some((a) => a.kind === "unknown"));
2669
+ const remaining = ordered.slice(MAX_PRECISE_CALLSITE_CASES).filter((rec) => !rec.argAbs.some((a) => a.shape.k === "unknown" && !a.term));
2968
2670
  if (remaining.length > 0) {
2969
2671
  const fnNode2 = resolveFunctionNode(candidate.node);
2970
2672
  const paramCount = extractParamNames(fnNode2).length;
2971
- const widenedArgs = Array.from(
2673
+ const widenedArgsAbs = Array.from(
2972
2674
  { length: paramCount },
2973
2675
  (_, i) => (
2974
2676
  // 缺参按真实 JS 语义 widen 成 undefined 而非 unknown——可选参守卫
2975
2677
  // (target || [] 等)对 unknown 全塌,对 undefined 正常走默认分支
2976
- widenType(simplifyUnion(remaining.map((rec) => rec.argTypes[i] ?? T2.undefined)))
2678
+ widenJoinAbs(remaining.map((rec) => rec.argAbs[i] ?? undefAbs))
2977
2679
  )
2978
2680
  );
2979
- const absSymRaw = tryEvalAbs(source, candidate.name, widenedArgs, filePath, mockSeedsToAbsMocks(seeds));
2980
- const absSym = absSymRaw && absIsBetter(
2981
- absSymRaw,
2982
- /* 无先验:仅 unknown 时 */
2983
- { kind: "unknown" }
2984
- ) ? absSymRaw : void 0;
2985
- let symValue;
2986
- let symThrows = T2.never;
2987
- let symLoc;
2988
2681
  let symAbs;
2989
2682
  if (isBPathCapable(source, envNames) && filePath) {
2990
2683
  const bSym = tryBPathCall(
2991
2684
  source,
2992
2685
  filePath,
2993
2686
  candidate.name,
2994
- widenedArgs.map((a) => typeValueToAbs5(a)),
2687
+ widenedArgsAbs,
2995
2688
  { envNames, mocks: mockSeedsToAbsMocks(seeds) }
2996
2689
  );
2997
2690
  if (bSym && (bHostedEval || !(bSym.shape.k === "unknown" && !bSym.term))) {
2998
2691
  symAbs = bSym;
2999
- symValue = absToTypeValue2(bSym);
3000
2692
  }
3001
2693
  }
3002
2694
  if (!symAbs && !bHostedEval) {
3003
- const full = evaluateFunctionFull(fnNode2, widenedArgs, globalEnv);
3004
- symValue = full.value.kind === "unknown" && absSym ? absSym : full.value;
3005
- symThrows = full.throws;
3006
- symLoc = full.throwLoc;
2695
+ const absTry = tryEvalAbsRaw(
2696
+ source,
2697
+ candidate.name,
2698
+ widenedArgsAbs,
2699
+ filePath,
2700
+ mockSeedsToAbsMocks(seeds)
2701
+ );
2702
+ const weak = !!absTry && absTry.shape.k === "unknown" && (!absTry.term || absTry.term.op === "lit" && absTry.term.value === void 0);
2703
+ if (absTry && !weak && absTry.shape.k !== "never" && absTry.conf !== "opaque") {
2704
+ symAbs = absTry;
2705
+ }
3007
2706
  }
3008
2707
  const symCase = {
3009
2708
  name: "call@symbolic",
3010
- args: widenedArgs,
3011
- result: symValue ?? T2.unknown,
3012
- throws: symThrows,
3013
- throwLoc: symLoc,
2709
+ argAbs: widenedArgsAbs,
2710
+ abs: symAbs ?? absUnknown2,
2711
+ throwsAbs: neverAbs,
3014
2712
  source: "callsite",
3015
2713
  aggregatedFrom: remaining.length
3016
2714
  };
@@ -3018,24 +2716,22 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
3018
2716
  if (symAbs) attachAbsToIntension(symCase, symAbs, candidate.name);
3019
2717
  candidate.analysis.cases.push(symCase);
3020
2718
  }
3021
- candidate.analysis.combined = collapseLiteralUnion(
3022
- simplifyUnion(records.map((r) => r.resultType)),
2719
+ candidate.analysis.combinedAbs = collapseAbsLits(
2720
+ records.map((r) => r.resultAbs),
3023
2721
  COLLAPSE_LITERAL_THRESHOLD
3024
2722
  );
3025
2723
  continue;
3026
2724
  }
3027
2725
  const fnNode = resolveFunctionNode(candidate.node);
3028
- const args = extractParamNames(fnNode).map(() => T2.unknown);
3029
- let entryValue;
3030
- let entryThrows = T2.never;
3031
- let entryLoc;
2726
+ const argAbsEntry = extractParamNames(fnNode).map(() => absUnknown2);
3032
2727
  let entryAbs;
2728
+ let entryThrowsAbs = neverAbs;
3033
2729
  if (isBPathCapable(source, envNames) && filePath) {
3034
2730
  const bEntryFull = tryBPathCallFull(
3035
2731
  source,
3036
2732
  filePath,
3037
2733
  candidate.analysis.name,
3038
- args.map((a) => typeValueToAbs5(a)),
2734
+ argAbsEntry,
3039
2735
  { envNames, mocks: mockSeedsToAbsMocks(seeds), collectMemberDiags: true }
3040
2736
  );
3041
2737
  if (bEntryFull?.memberDiags?.length) {
@@ -3046,129 +2742,106 @@ function analyzeFileUncached(filePath, source, activeCases, externalCallRecords)
3046
2742
  const bEntry = bEntryFull?.result;
3047
2743
  if (bEntry && (bHostedEval || !(bEntry.shape.k === "unknown" && !bEntry.term))) {
3048
2744
  entryAbs = bEntry;
3049
- entryValue = absToTypeValue2(bEntry);
2745
+ if (bEntryFull?.throws) entryThrowsAbs = bEntryFull.throws;
3050
2746
  }
3051
2747
  }
3052
- if (!entryValue) {
2748
+ if (!entryAbs) {
3053
2749
  if (bHostedEval) {
3054
- entryValue = T2.unknown;
3055
- entryThrows = T2.never;
2750
+ entryAbs = absUnknown2;
2751
+ entryThrowsAbs = neverAbs;
3056
2752
  } else {
3057
- const absEntry = tryEvalEntryAbs(source, candidate.analysis.name, args, filePath, seeds.seedVars);
3058
- const full = absEntry ? { value: absEntry, throws: T2.never, throwLoc: void 0 } : evaluateFunctionFull(fnNode, args, globalEnv);
3059
- entryValue = full.value;
3060
- entryThrows = full.throws;
3061
- entryLoc = full.throwLoc;
2753
+ const absEntry = tryEvalEntryAbs(source, candidate.analysis.name, argAbsEntry, filePath, seeds.seedVars);
2754
+ if (absEntry) {
2755
+ entryAbs = absEntry;
2756
+ entryThrowsAbs = neverAbs;
2757
+ } else {
2758
+ entryAbs = absUnknown2;
2759
+ entryThrowsAbs = neverAbs;
2760
+ }
3062
2761
  }
3063
2762
  }
3064
2763
  const caseResult = {
3065
2764
  name: `entry@L${candidate.analysis.loc.start.line}`,
3066
- args,
3067
- result: entryValue,
3068
- throws: entryThrows,
3069
- throwLoc: entryLoc
2765
+ argAbs: argAbsEntry,
2766
+ abs: entryAbs,
2767
+ throwsAbs: entryThrowsAbs
3070
2768
  };
3071
2769
  tryAttachIntension(caseResult, source, candidate.analysis.name);
3072
- if (entryAbs) attachAbsToIntension(caseResult, entryAbs, candidate.analysis.name);
2770
+ attachAbsToIntension(caseResult, entryAbs, candidate.analysis.name);
3073
2771
  candidate.analysis.cases.push(caseResult);
3074
2772
  candidate.analysis.entryOnly = true;
3075
- candidate.analysis.combined = collapseLiteralUnion(entryValue, COLLAPSE_LITERAL_THRESHOLD);
2773
+ candidate.analysis.combinedAbs = entryAbs;
3076
2774
  }
3077
2775
  if (!bHostedEval) {
3078
- buildNodeTypeMap(ast, globalEnv, nodeTypeMap);
3079
- }
3080
- setUnknownBuiltinHandler(null);
3081
- setCallCollector(null);
3082
- setUnknownCollector(null);
3083
- setProvenanceTracking(false);
3084
- setModuleResolver(null);
3085
- resetEnvModules();
3086
- resetMockModules();
3087
- const maxLine = source.split("\n").length;
3088
- diagnostics.push(
3089
- ...unknownRecordsToDiagnostics(
3090
- unknownRecords.filter((r) => {
3091
- if ((r.loc?.line ?? 0) > maxLine) return false;
3092
- if (r.originModule === USAGE_SITE_MODULE) return false;
3093
- if (bHostedEval && (r.kind === "method" || r.kind === "property")) {
3094
- return false;
3095
- }
3096
- if ((r.kind === "method" || r.kind === "property") && bMemberDiagNames.has(r.name)) {
3097
- return false;
3098
- }
3099
- if (r.kind === "global") {
3100
- if (r.name.startsWith("module-cycle:") && bModuleIssueKinds.has("cycle")) return false;
3101
- if (r.name.startsWith("module-depth:") && bModuleIssueKinds.has("depth")) return false;
3102
- if (r.name.startsWith("module-missing:") && bModuleIssueKinds.has("missing")) return false;
3103
- if (r.name.startsWith("recursion:")) {
3104
- const fn = r.name.slice("recursion:".length);
3105
- if (bTruncatedFns.has(fn) || bTruncatedFns.has(fn.split(".").pop() ?? fn)) {
3106
- return false;
3107
- }
3108
- }
3109
- if (bBuiltinUnknownNames.has(r.name)) return false;
3110
- }
3111
- return true;
3112
- })
3113
- )
3114
- );
2776
+ buildNodeTypeMap(ast, globalEnv, nodeAbsMap);
2777
+ }
3115
2778
  const externalFunctions = synthesizeExternalFunctions(callRecords, filePath);
3116
2779
  return {
3117
2780
  functions: functionResults,
3118
2781
  diagnostics,
3119
2782
  bindings,
3120
- nodeTypeMap,
2783
+ nodeAbsMap,
3121
2784
  caseHints,
3122
2785
  ...externalFunctions.length > 0 ? { externalFunctions } : {}
3123
2786
  };
3124
2787
  }
3125
- function collectBindings(ast, env, bindings) {
2788
+ function collectBindings(ast, env, bindings, absBinds) {
3126
2789
  if (ast.type !== "File") return;
2790
+ const setBinding = (name, loc) => {
2791
+ const absVal = absBinds?.get(name) ?? absUnknown2;
2792
+ bindings.set(name, {
2793
+ abs: absVal,
2794
+ loc
2795
+ });
2796
+ };
3127
2797
  for (const stmt of ast.program.body) {
3128
2798
  if (stmt.type === "FunctionDeclaration" && stmt.id) {
3129
- const val = env.has(stmt.id.name) ? env.lookup(stmt.id.name) : T2.unknown;
3130
- bindings.set(stmt.id.name, { type: val, loc: locFromNode(stmt) });
2799
+ setBinding(stmt.id.name, locFromNode(stmt));
3131
2800
  }
3132
2801
  if (stmt.type === "VariableDeclaration") {
3133
2802
  for (const decl of stmt.declarations) {
3134
2803
  if (decl.id.type === "Identifier") {
3135
- const val = env.has(decl.id.name) ? env.lookup(decl.id.name) : T2.unknown;
3136
- bindings.set(decl.id.name, { type: val, loc: locFromNode(decl) });
2804
+ setBinding(decl.id.name, locFromNode(decl));
3137
2805
  }
3138
2806
  }
3139
2807
  }
3140
2808
  if (stmt.type === "ClassDeclaration" && stmt.id) {
3141
- const val = env.has(stmt.id.name) ? env.lookup(stmt.id.name) : T2.unknown;
3142
- bindings.set(stmt.id.name, { type: val, loc: locFromNode(stmt) });
2809
+ setBinding(stmt.id.name, locFromNode(stmt));
3143
2810
  }
3144
2811
  if (stmt.type === "ExportNamedDeclaration" && stmt.declaration) {
3145
- collectBindings({ type: "File", program: { type: "Program", body: [stmt.declaration] } }, env, bindings);
2812
+ collectBindings({ type: "File", program: { type: "Program", body: [stmt.declaration] } }, env, bindings, absBinds);
3146
2813
  }
3147
2814
  }
3148
2815
  }
3149
- function buildNodeTypeMap(ast, env, nodeTypeMap) {
2816
+ function nullLitAbs() {
2817
+ return makeAbsVal({ k: "unknown" }, { op: "lit", value: null }, void 0, "exact");
2818
+ }
2819
+ function buildNodeTypeMap(ast, env, nodeAbsMap) {
3150
2820
  const traverseFn = typeof traverse === "function" ? traverse : traverse.default;
3151
2821
  try {
3152
2822
  traverseFn(ast, {
3153
2823
  enter(path) {
3154
2824
  const node = path.node;
3155
2825
  try {
2826
+ const setAbs = (a) => {
2827
+ nodeAbsMap.set(node, a);
2828
+ };
3156
2829
  if (node.type === "Identifier" && node.name !== "undefined" && path.parentPath?.node.type !== "FunctionDeclaration" && path.parentPath?.node.type !== "VariableDeclarator") {
3157
2830
  if (env.has(node.name)) {
3158
- nodeTypeMap.set(node, env.lookup(node.name));
2831
+ setAbs(env.lookup(node.name));
3159
2832
  }
3160
2833
  }
3161
2834
  if (node.type === "NumericLiteral") {
3162
- nodeTypeMap.set(node, T2.literal(node.value));
2835
+ setAbs(numLit(node.value));
3163
2836
  }
3164
2837
  if (node.type === "StringLiteral") {
3165
- nodeTypeMap.set(node, T2.literal(node.value));
2838
+ setAbs(strLit(node.value));
3166
2839
  }
3167
2840
  if (node.type === "BooleanLiteral") {
3168
- nodeTypeMap.set(node, T2.literal(node.value));
2841
+ setAbs(boolLit(node.value));
3169
2842
  }
3170
2843
  if (node.type === "NullLiteral") {
3171
- nodeTypeMap.set(node, T2.null);
2844
+ setAbs(nullLitAbs());
3172
2845
  }
3173
2846
  } catch {
3174
2847
  }
@@ -3177,13 +2850,15 @@ function buildNodeTypeMap(ast, env, nodeTypeMap) {
3177
2850
  } catch {
3178
2851
  }
3179
2852
  }
3180
- function absIsBetter(absTv, prev) {
3181
- if (prev.kind === "unknown") return absTv.kind !== "unknown";
3182
- if (absTv.kind === "unknown" || absTv.kind === "never") return false;
3183
- 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") {
3184
- return false;
3185
- }
3186
- if (absTv.kind === "refined" && prev.kind === "primitive") return true;
2853
+ function absIsBetter(next, prev) {
2854
+ const isBareUnknown = (a) => a.shape.k === "unknown" && !a.term;
2855
+ const isNever = (a) => a.shape.k === "never";
2856
+ const isStructured = (a) => a.term?.op === "lit" || a.shape.k === "obj" || a.shape.k === "arr" || a.shape.k === "tuple" || a.shape.k === "sum" || a.shape.k === "fn" || a.shape.k === "brand" || a.shape.k === "eff";
2857
+ const hasPred = (a) => !!a.pred && a.pred.op !== "true";
2858
+ if (isBareUnknown(prev)) return !isBareUnknown(next);
2859
+ if (isBareUnknown(next) || isNever(next)) return false;
2860
+ if (isStructured(prev)) return false;
2861
+ if (hasPred(next) && prev.shape.k === "prim" && !hasPred(prev)) return true;
3187
2862
  return false;
3188
2863
  }
3189
2864
  function isSelfContainedSource(source, envNames) {
@@ -3227,21 +2902,7 @@ function collectAbsCallRecords(source, seeds, filePath, precomputedModules, envN
3227
2902
  } finally {
3228
2903
  setAbsCallCollector(null);
3229
2904
  }
3230
- return absCalls.map((r) => {
3231
- const rec = {
3232
- fnName: r.fnName,
3233
- argTypes: r.args.map((a) => absToTypeValue2(a)),
3234
- resultType: r.threw ? T2.never : absToTypeValue2(r.result),
3235
- throws: r.threw ? absToTypeValue2(r.result) : T2.never,
3236
- callLoc: r.callLoc
3237
- };
3238
- const imp = importLocals.get(r.fnName);
3239
- if (imp) {
3240
- rec.targetModule = imp.modulePath;
3241
- rec.targetExport = imp.exportName;
3242
- }
3243
- return rec;
3244
- });
2905
+ return absCalls.map((r) => callRecordFromAbsCall(r, importLocals));
3245
2906
  }
3246
2907
  function collectAbsBindsAndNodes(source, seeds, modules) {
3247
2908
  const binds = /* @__PURE__ */ new Map();
@@ -3259,7 +2920,7 @@ function collectAbsBindsAndNodes(source, seeds, modules) {
3259
2920
  if (!binds.has(name)) {
3260
2921
  binds.set(
3261
2922
  name,
3262
- absFunction5(impl.params, { body: impl.body, async: impl.async, env })
2923
+ absFunction4(impl.params, { body: impl.body, async: impl.async, env })
3263
2924
  );
3264
2925
  }
3265
2926
  }
@@ -3274,21 +2935,49 @@ function buildAbsImportLocalMap(source, fromFile) {
3274
2935
  try {
3275
2936
  const file = parse5(source);
3276
2937
  for (const stmt of file.program.body) {
3277
- if (stmt.type !== "ImportDeclaration") continue;
3278
- const spec = stmt.source.value;
3279
- let modulePath = null;
3280
- if (spec.startsWith(".") || spec.startsWith("/")) {
3281
- modulePath = resolveImportAbs(spec, fromFile);
3282
- } else if (!spec.startsWith("node:")) {
3283
- modulePath = spec;
2938
+ if (stmt.type === "ImportDeclaration") {
2939
+ const spec = stmt.source.value;
2940
+ let modulePath = null;
2941
+ if (spec.startsWith(".") || spec.startsWith("/")) {
2942
+ modulePath = resolveImportAbs(spec, fromFile);
2943
+ } else if (!spec.startsWith("node:")) {
2944
+ modulePath = spec;
2945
+ }
2946
+ if (!modulePath) continue;
2947
+ for (const s of stmt.specifiers) {
2948
+ if (s.type === "ImportSpecifier") {
2949
+ const imported = s.imported.type === "Identifier" ? s.imported.name : String(s.imported);
2950
+ out.set(s.local.name, { modulePath, exportName: imported });
2951
+ } else if (s.type === "ImportDefaultSpecifier") {
2952
+ out.set(s.local.name, { modulePath, exportName: "default" });
2953
+ }
2954
+ }
2955
+ continue;
3284
2956
  }
3285
- if (!modulePath) continue;
3286
- for (const s of stmt.specifiers) {
3287
- if (s.type === "ImportSpecifier") {
3288
- const imported = s.imported.type === "Identifier" ? s.imported.name : String(s.imported);
3289
- out.set(s.local.name, { modulePath, exportName: imported });
3290
- } else if (s.type === "ImportDefaultSpecifier") {
3291
- out.set(s.local.name, { modulePath, exportName: "default" });
2957
+ if (stmt.type === "VariableDeclaration") {
2958
+ for (const d of stmt.declarations) {
2959
+ if (d.init?.type !== "CallExpression") continue;
2960
+ const callee = d.init.callee;
2961
+ if (callee.type !== "Identifier" || callee.name !== "require") continue;
2962
+ const arg0 = d.init.arguments[0];
2963
+ if (!arg0 || arg0.type !== "StringLiteral") continue;
2964
+ const spec = arg0.value;
2965
+ let modulePath = null;
2966
+ if (spec.startsWith(".") || spec.startsWith("/")) {
2967
+ modulePath = resolveImportAbs(spec, fromFile);
2968
+ } else if (!spec.startsWith("node:")) {
2969
+ modulePath = spec;
2970
+ }
2971
+ if (!modulePath) continue;
2972
+ if (d.id.type === "ObjectPattern") {
2973
+ for (const p of d.id.properties) {
2974
+ if (p.type !== "ObjectProperty" || p.computed) continue;
2975
+ if (p.key.type !== "Identifier" || p.value.type !== "Identifier") continue;
2976
+ out.set(p.value.name, { modulePath, exportName: p.key.name });
2977
+ }
2978
+ } else if (d.id.type === "Identifier") {
2979
+ out.set(d.id.name, { modulePath, exportName: "default" });
2980
+ }
3292
2981
  }
3293
2982
  }
3294
2983
  }
@@ -3304,33 +2993,54 @@ function resolveImportAbs(spec, fromFile) {
3304
2993
  }
3305
2994
  return null;
3306
2995
  }
3307
- function tryEvalAbs(source, fnName, args, filePath, mocks) {
3308
- const raw = tryEvalAbsRaw(source, fnName, args, filePath, mocks);
3309
- return raw ? absToTypeValue2(raw) : void 0;
3310
- }
3311
2996
  function tryEvalAbsRaw(source, fnName, args, filePath, mocks) {
2997
+ return tryEvalAbsFull(source, fnName, args, filePath, mocks)?.result;
2998
+ }
2999
+ function tryEvalAbsFull(source, fnName, args, filePath, mocks) {
3312
3000
  if (/\brequire\s*\(/.test(source)) return void 0;
3313
3001
  try {
3314
- const absArgs = args.map((a) => typeValueToAbs5(a));
3002
+ const absArgs = args;
3315
3003
  if (filePath) {
3316
- const viaB = tryBPathCall(source, filePath, fnName, absArgs, { mocks });
3317
- if (viaB) return viaB;
3004
+ const viaB = tryBPathCallFull(source, filePath, fnName, absArgs, { mocks });
3005
+ if (viaB) {
3006
+ const r = viaB.result;
3007
+ if (r && !(r.shape.k === "unknown" && !r.term)) {
3008
+ const throws = viaB.throws ?? { shape: { k: "never" }, conf: "exact" };
3009
+ if (throws.shape.k !== "never") {
3010
+ try {
3011
+ let modules2;
3012
+ if (/\bimport\s*[{'"*]/.test(source)) {
3013
+ modules2 = evalAbsModuleGraph(source, filePath).modules;
3014
+ }
3015
+ const viaAst = analyzeFnFull(source, fnName, absArgs, { modules: modules2 });
3016
+ return {
3017
+ result: r,
3018
+ throws,
3019
+ ...viaAst.throwLoc ? { throwLoc: viaAst.throwLoc } : {}
3020
+ };
3021
+ } catch {
3022
+ return { result: r, throws };
3023
+ }
3024
+ }
3025
+ return { result: r, throws };
3026
+ }
3027
+ }
3318
3028
  }
3319
3029
  let modules;
3320
3030
  if (filePath && /\bimport\s*[{'"*]/.test(source)) {
3321
3031
  modules = evalAbsModuleGraph(source, filePath).modules;
3322
3032
  }
3323
- const result = analyzeFn(source, fnName, absArgs, void 0, void 0, void 0, modules);
3324
- if (result.shape.k === "unknown" && !result.term) {
3033
+ const full = analyzeFnFull(source, fnName, absArgs, { modules });
3034
+ if (full.result.shape.k === "unknown" && !full.result.term) {
3325
3035
  return void 0;
3326
3036
  }
3327
- return result;
3037
+ return full;
3328
3038
  } catch {
3329
3039
  return void 0;
3330
3040
  }
3331
3041
  }
3332
3042
  function tryEvalEntryAbs(source, fnName, args, filePath, mocks) {
3333
- return tryEvalAbs(source, fnName, args, filePath, mocks);
3043
+ return tryEvalAbsRaw(source, fnName, args, filePath, mocks);
3334
3044
  }
3335
3045
  function tryAttachIntension(caseResult, source, fnName) {
3336
3046
  try {
@@ -3339,7 +3049,7 @@ function tryAttachIntension(caseResult, source, fnName) {
3339
3049
  const sym = g.symbolic;
3340
3050
  const intension = {
3341
3051
  display: g.display,
3342
- abs: formatAbs3(sym),
3052
+ abs: formatAbs4(sym),
3343
3053
  absMultiline: formatAbsMultiline(sym, fnName)
3344
3054
  };
3345
3055
  if (sym.term && sym.term.op !== "lit") {
@@ -3358,25 +3068,48 @@ function attachAbsToIntension(caseResult, absVal, label) {
3358
3068
  caseResult.abs = absVal;
3359
3069
  caseResult.intension = {
3360
3070
  ...prev,
3361
- abs: formatAbs3(absVal),
3071
+ abs: formatAbs4(absVal),
3362
3072
  absMultiline: formatAbsMultiline(absVal, label ?? caseResult.name),
3363
3073
  conf: absVal.conf
3364
3074
  };
3365
3075
  }
3076
+ function safeAbsOrUnknown(a) {
3077
+ if (!a || typeof a !== "object" || !("shape" in a) || !a.shape) {
3078
+ return { shape: { k: "unknown" }, conf: "opaque" };
3079
+ }
3080
+ return a;
3081
+ }
3082
+ function callRecordFromAbsCall(r, impMap) {
3083
+ const argAbs = r.args.map(safeAbsOrUnknown);
3084
+ const threw = !!r.threw;
3085
+ const thrownOrResult = safeAbsOrUnknown(r.result);
3086
+ const rec = {
3087
+ fnName: r.fnName,
3088
+ argAbs,
3089
+ resultAbs: threw ? neverAbs : thrownOrResult,
3090
+ throwsAbs: threw ? thrownOrResult : neverAbs,
3091
+ callLoc: r.callLoc
3092
+ };
3093
+ const imp = impMap?.get(r.fnName);
3094
+ if (imp) {
3095
+ rec.targetModule = imp.modulePath;
3096
+ rec.targetExport = imp.exportName;
3097
+ }
3098
+ return rec;
3099
+ }
3366
3100
 
3367
3101
  // src/lsp-surface.ts
3368
3102
  import { dirname as dirname7 } from "path";
3369
3103
  import traverse2 from "@babel/traverse";
3370
3104
  import {
3371
- typeValueToString as typeValueToString4,
3372
- createEnvironment as createEnvironment3,
3373
3105
  generalizeFromAst as generalizeFromAst3,
3374
- formatAbs as formatAbs4,
3106
+ formatAbs as formatAbs5,
3375
3107
  formatAbsMultiline as formatAbsMultiline2,
3376
- getFnSig as getFnSig3,
3108
+ formatShape as formatShape3,
3377
3109
  collectAbsNodeTypes,
3378
3110
  findAbsAtPosition,
3379
- absToTypeValue as absToTypeValue3
3111
+ evalSource,
3112
+ setAbsNodeCollector as setAbsNodeCollector2
3380
3113
  } from "@nudojs/core";
3381
3114
  import { parse as parse6, extractDirectives as extractDirectives2, extractFileDirectives as extractFileDirectives2 } from "@nudojs/parser";
3382
3115
  function getCasesForFile(filePath, source) {
@@ -3394,6 +3127,13 @@ async function getTypeAtPositionAsync(filePath, source, line, column, activeCase
3394
3127
  }
3395
3128
  return getTypeAtPosition(filePath, source, line, column, activeCases);
3396
3129
  }
3130
+ async function getAbsAtPositionAsync(filePath, source, line, column, activeCases) {
3131
+ const envNames = collectEnvNames(filePath, source, false);
3132
+ if (envNames.length > 0) {
3133
+ await preloadPathEnvs(envNames, dirname7(filePath));
3134
+ }
3135
+ return getAbsAtPosition(filePath, source, line, column, activeCases);
3136
+ }
3397
3137
  function positionInsideCaseFunction(source, ast, line) {
3398
3138
  try {
3399
3139
  const enclosing = findEnclosingFunction(extractDirectives2(ast), line);
@@ -3402,68 +3142,81 @@ function positionInsideCaseFunction(source, ast, line) {
3402
3142
  return false;
3403
3143
  }
3404
3144
  }
3405
- function getTypeAtPosition(filePath, source, line, column, activeCases) {
3406
- const ast = parse6(source);
3407
- const fileDirectives = extractFileDirectives2(ast);
3408
- const envNames = fileDirectives.filter((d) => d.kind === "env").flatMap((d) => d.envs);
3409
- if (isBPathCapable(source, envNames) && !positionInsideCaseFunction(source, ast, line)) {
3410
- try {
3411
- const seeds = mockDirectivesToAbsSeeds(extractDirectives2(ast));
3412
- const { modules } = evalAbsModuleGraph(source, filePath);
3413
- const absNodes = collectAbsNodeTypes(source, {
3414
- ...seeds,
3415
- modules,
3416
- file: ast
3145
+ function absFromBPath(filePath, source, line, column, ast, envNames) {
3146
+ if (!isBPathCapable(source, envNames)) return null;
3147
+ if (positionInsideCaseFunction(source, ast, line)) return null;
3148
+ try {
3149
+ const seeds = mockDirectivesToAbsSeeds(extractDirectives2(ast));
3150
+ const { modules } = evalAbsModuleGraph(source, filePath);
3151
+ const absNodes = collectAbsNodeTypes(source, {
3152
+ ...seeds,
3153
+ modules,
3154
+ file: ast
3155
+ });
3156
+ const absAt = findAbsAtPosition(absNodes, line, column);
3157
+ if (absAt) return absAt;
3158
+ const ident = findIdentNameAtPosition(source, line, column, ast);
3159
+ if (ident) {
3160
+ const binds = collectAbsBindingsFromGraph(source, filePath, {
3161
+ seedVars: seeds.seedVars,
3162
+ seedFns: seeds.seedFns
3417
3163
  });
3418
- const absAt = findAbsAtPosition(absNodes, line, column);
3419
- if (absAt) return absToTypeValue3(absAt);
3420
- const ident = findIdentNameAtPosition(source, line, column, ast);
3421
- if (ident) {
3422
- const binds = collectAbsBindingsFromGraph(source, filePath, {
3423
- seedVars: seeds.seedVars,
3424
- seedFns: seeds.seedFns
3425
- });
3426
- const bound = binds.get(ident);
3427
- if (bound) return absToTypeValue3(bound);
3428
- }
3429
- } catch {
3164
+ const bound = binds.get(ident);
3165
+ if (bound) return bound;
3430
3166
  }
3167
+ } catch {
3431
3168
  }
3432
- resetMemo();
3433
- resetEnvModules();
3434
- setModuleResolver(resolveModule);
3435
- setCurrentFileDir(dirname7(filePath));
3436
- setCurrentSource(source);
3437
- const globalEnv = createEnvironment3();
3438
- {
3439
- const loaded = envNames.length > 0 ? loadEnvs(envNames, globalEnv) : { modules: {} };
3440
- const auto = autoHarvestModules(source, dirname7(filePath));
3441
- const modules = { ...loaded.modules, ...auto };
3442
- if (Object.keys(modules).length > 0) setEnvModules(modules);
3443
- }
3444
- evaluateProgram(ast, globalEnv);
3445
- const nodeTypeMap = /* @__PURE__ */ new Map();
3446
- buildNodeTypeMap(ast, globalEnv, nodeTypeMap);
3169
+ return null;
3170
+ }
3171
+ function absFromCaseReplay(filePath, source, line, column, ast, activeCases) {
3447
3172
  const functions = extractDirectives2(ast);
3448
3173
  const enclosingFn = findEnclosingFunction(functions, line);
3449
- if (enclosingFn) {
3450
- const caseDirectives = enclosingFn.directives.filter((d) => d.kind === "case");
3451
- if (caseDirectives.length > 0) {
3452
- const caseIndex = activeCases?.get(enclosingFn.name) ?? 0;
3453
- const directive = caseDirectives[Math.min(caseIndex, caseDirectives.length - 1)];
3454
- const fnNodeTypeMap = /* @__PURE__ */ new Map();
3455
- setNodeTypeCollector((node, tv) => fnNodeTypeMap.set(node, tv));
3456
- evaluateFunctionFull(enclosingFn.node, directive.args, globalEnv);
3457
- setNodeTypeCollector(null);
3458
- for (const [node, tv] of fnNodeTypeMap) {
3459
- nodeTypeMap.set(node, tv);
3460
- }
3174
+ if (!enclosingFn) return null;
3175
+ const caseDirectives = enclosingFn.directives.filter((d) => d.kind === "case");
3176
+ if (caseDirectives.length === 0) return null;
3177
+ const caseIndex = activeCases?.get(enclosingFn.name) ?? 0;
3178
+ const directive = caseDirectives[Math.min(caseIndex, caseDirectives.length - 1)];
3179
+ const caseArgs = directive.argsAbs;
3180
+ const map = /* @__PURE__ */ new Map();
3181
+ setAbsNodeCollector2((node, value) => map.set(node, value));
3182
+ try {
3183
+ const seeds = mockDirectivesToAbsSeeds(functions);
3184
+ let modules;
3185
+ try {
3186
+ modules = evalAbsModuleGraph(source, filePath).modules;
3187
+ } catch {
3188
+ modules = void 0;
3461
3189
  }
3190
+ evalSource(
3191
+ source,
3192
+ { fn: enclosingFn.name, args: caseArgs },
3193
+ {
3194
+ file: ast,
3195
+ modules,
3196
+ seedVars: seeds.seedVars,
3197
+ seedFns: seeds.seedFns
3198
+ }
3199
+ );
3200
+ } catch {
3201
+ } finally {
3202
+ setAbsNodeCollector2(null);
3203
+ }
3204
+ if (map.size === 0) return null;
3205
+ return findAbsAtPosition(map, line, column) ?? null;
3206
+ }
3207
+ function getAbsAtPosition(filePath, source, line, column, activeCases) {
3208
+ const ast = parse6(source);
3209
+ const envNames = extractFileDirectives2(ast).filter((d) => d.kind === "env").flatMap((d) => d.envs);
3210
+ const fromB = absFromBPath(filePath, source, line, column, ast, envNames);
3211
+ if (fromB) return fromB;
3212
+ if (positionInsideCaseFunction(source, ast, line)) {
3213
+ const fromCase = absFromCaseReplay(filePath, source, line, column, ast, activeCases);
3214
+ if (fromCase) return fromCase;
3462
3215
  }
3463
- setModuleResolver(null);
3464
- resetEnvModules();
3465
- resetMockModules();
3466
- return findBestTypeAtPosition(nodeTypeMap, globalEnv, ast, line, column);
3216
+ return null;
3217
+ }
3218
+ function getTypeAtPosition(filePath, source, line, column, activeCases) {
3219
+ return getAbsAtPosition(filePath, source, line, column, activeCases);
3467
3220
  }
3468
3221
  function getHoverAtPosition(filePath, source, line, column, activeCases) {
3469
3222
  let file;
@@ -3482,23 +3235,23 @@ function getHoverAtPosition(filePath, source, line, column, activeCases) {
3482
3235
  const g = generalizeFromAst3(fnName, source, file ? { file } : {});
3483
3236
  if (g) {
3484
3237
  gDisplay = g.display;
3485
- gAbs = formatAbs4(g.symbolic);
3238
+ gAbs = formatAbs5(g.symbolic);
3486
3239
  gMulti = formatAbsMultiline2(g.symbolic, fnName);
3487
3240
  }
3488
3241
  } catch {
3489
3242
  }
3490
3243
  }
3491
- const attachIntension = (info2) => {
3492
- if (!gDisplay) return info2;
3493
- if (!info2) {
3244
+ const attachIntension = (info) => {
3245
+ if (!gDisplay) return info;
3246
+ if (!info) {
3494
3247
  return { typeText: gDisplay, intension: gDisplay, abs: gAbs, absMultiline: gMulti };
3495
3248
  }
3496
3249
  return {
3497
- ...info2,
3250
+ ...info,
3498
3251
  intension: gDisplay,
3499
3252
  // 外延侧已有更准 Abs 时保留;否则用 symbolic 兜底
3500
- abs: info2.abs ?? gAbs,
3501
- absMultiline: info2.absMultiline ?? gMulti
3253
+ abs: info.abs ?? gAbs,
3254
+ absMultiline: info.absMultiline ?? gMulti
3502
3255
  };
3503
3256
  };
3504
3257
  const insideCaseFn = positionInsideCaseFunction(
@@ -3506,39 +3259,41 @@ function getHoverAtPosition(filePath, source, line, column, activeCases) {
3506
3259
  file ?? parse6(source),
3507
3260
  line
3508
3261
  );
3509
- if (isBPathCapable(source, envNames) && !insideCaseFn) {
3510
- try {
3511
- const seeds = mockDirectivesToAbsSeeds(extractDirectives2(file ?? parse6(source)));
3512
- const { modules } = evalAbsModuleGraph(source, filePath);
3513
- const absNodes = collectAbsNodeTypes(source, {
3514
- ...seeds,
3515
- modules,
3516
- ...file ? { file } : {}
3517
- });
3518
- const absAt = findAbsAtPosition(absNodes, line, column);
3519
- const ident2 = findIdentNameAtPosition(source, line, column, file);
3520
- if (ident2 && !fnName) {
3521
- const binds = collectAbsBindingsFromGraph(source, filePath, {
3522
- seedVars: seeds.seedVars,
3523
- seedFns: seeds.seedFns
3524
- });
3525
- const bound = binds.get(ident2);
3526
- if (bound) {
3527
- const absLine = formatAbs4(bound);
3528
- const absMulti = formatAbsMultiline2(bound, ident2);
3529
- return attachIntension({ typeText: absLine, abs: absLine, absMultiline: absMulti });
3530
- }
3531
- }
3532
- if (absAt) {
3533
- const absLine = formatAbs4(absAt);
3534
- const absMulti = formatAbsMultiline2(absAt, void 0);
3535
- return attachIntension({ typeText: absLine, abs: absLine, absMultiline: absMulti });
3536
- }
3537
- } catch {
3262
+ if (!insideCaseFn) {
3263
+ const fromB = absFromBPath(
3264
+ filePath,
3265
+ source,
3266
+ line,
3267
+ column,
3268
+ file ?? parse6(source),
3269
+ envNames
3270
+ );
3271
+ if (fromB) {
3272
+ const absLine = formatAbs5(fromB);
3273
+ const absMulti = formatAbsMultiline2(fromB, void 0);
3274
+ return attachIntension({ typeText: absLine, abs: absLine, absMultiline: absMulti });
3275
+ }
3276
+ } else {
3277
+ const fromCase = absFromCaseReplay(
3278
+ filePath,
3279
+ source,
3280
+ line,
3281
+ column,
3282
+ file ?? parse6(source),
3283
+ activeCases
3284
+ );
3285
+ if (fromCase) {
3286
+ const absLine = formatAbs5(fromCase);
3287
+ const absMulti = formatAbsMultiline2(fromCase, void 0);
3288
+ return attachIntension({ typeText: absLine, abs: absLine, absMultiline: absMulti });
3538
3289
  }
3539
3290
  }
3540
- const tv = getTypeAtPosition(filePath, source, line, column, activeCases);
3541
- const info = tv ? { typeText: typeValueToString4(tv) } : null;
3291
+ const absFallback = getAbsAtPosition(filePath, source, line, column, activeCases);
3292
+ if (absFallback) {
3293
+ const absLine = formatAbs5(absFallback);
3294
+ const absMulti = formatAbsMultiline2(absFallback, void 0);
3295
+ return attachIntension({ typeText: absLine, abs: absLine, absMultiline: absMulti });
3296
+ }
3542
3297
  const ident = findIdentNameAtPosition(source, line, column, file);
3543
3298
  if (ident && !fnName && !insideCaseFn) {
3544
3299
  try {
@@ -3550,13 +3305,8 @@ function getHoverAtPosition(filePath, source, line, column, activeCases) {
3550
3305
  });
3551
3306
  const absBound = absBinds.get(ident);
3552
3307
  if (absBound) {
3553
- const absLine = formatAbs4(absBound);
3308
+ const absLine = formatAbs5(absBound);
3554
3309
  const absMulti = formatAbsMultiline2(absBound, ident);
3555
- if (info) {
3556
- info.abs = absLine;
3557
- info.absMultiline = absMulti;
3558
- return attachIntension(info);
3559
- }
3560
3310
  return attachIntension({ typeText: absLine, abs: absLine, absMultiline: absMulti });
3561
3311
  }
3562
3312
  }
@@ -3576,19 +3326,14 @@ function getHoverAtPosition(filePath, source, line, column, activeCases) {
3576
3326
  });
3577
3327
  const absAt = findAbsAtPosition(nodeTypes, line, column);
3578
3328
  if (absAt) {
3579
- const absLine = formatAbs4(absAt);
3329
+ const absLine = formatAbs5(absAt);
3580
3330
  const absMulti = formatAbsMultiline2(absAt, void 0);
3581
- if (info) {
3582
- info.abs = absLine;
3583
- info.absMultiline = absMulti;
3584
- } else {
3585
- return attachIntension({ typeText: absLine, abs: absLine, absMultiline: absMulti });
3586
- }
3331
+ return attachIntension({ typeText: absLine, abs: absLine, absMultiline: absMulti });
3587
3332
  }
3588
3333
  }
3589
3334
  } catch {
3590
3335
  }
3591
- return attachIntension(info);
3336
+ return null;
3592
3337
  }
3593
3338
  function findIdentNameAtPosition(source, line, column, fileAst) {
3594
3339
  try {
@@ -3641,46 +3386,6 @@ function findEnclosingFunction(functions, line) {
3641
3386
  }
3642
3387
  return null;
3643
3388
  }
3644
- function findBestTypeAtPosition(nodeTypeMap, globalEnv, ast, line, column) {
3645
- let bestMatch = null;
3646
- let bestSize = Infinity;
3647
- for (const [node, tv] of nodeTypeMap) {
3648
- const loc = node.loc;
3649
- if (!loc) continue;
3650
- if (loc.start.line <= line && loc.end.line >= line && (loc.start.line < line || loc.start.column <= column) && (loc.end.line > line || loc.end.column >= column)) {
3651
- const size = (loc.end.line - loc.start.line) * 1e4 + (loc.end.column - loc.start.column);
3652
- if (size < bestSize) {
3653
- bestSize = size;
3654
- bestMatch = tv;
3655
- }
3656
- }
3657
- }
3658
- if (!bestMatch) {
3659
- const identAtPos = findIdentifierAtPosition(ast, line, column);
3660
- if (identAtPos && globalEnv.has(identAtPos)) {
3661
- bestMatch = globalEnv.lookup(identAtPos);
3662
- }
3663
- }
3664
- return bestMatch;
3665
- }
3666
- function findIdentifierAtPosition(ast, line, column) {
3667
- let found = null;
3668
- const traverseFn = typeof traverse2 === "function" ? traverse2 : traverse2.default;
3669
- try {
3670
- traverseFn(ast, {
3671
- Identifier(path) {
3672
- const loc = path.node.loc;
3673
- if (!loc) return;
3674
- if (loc.start.line === line && loc.start.column <= column && loc.end.column >= column) {
3675
- found = path.node.name;
3676
- path.stop();
3677
- }
3678
- }
3679
- });
3680
- } catch {
3681
- }
3682
- return found;
3683
- }
3684
3389
  function getCompletionsAtPosition(filePath, source, line, column) {
3685
3390
  const textBefore = getTextBeforePosition(source, line, column);
3686
3391
  const dotMatch = textBefore.match(/(\w+)\.\s*\w*$/);
@@ -3697,19 +3402,20 @@ function getCompletionsAtPosition(filePath, source, line, column) {
3697
3402
  return [];
3698
3403
  }
3699
3404
  }
3700
- resetMemo();
3701
- setModuleResolver(resolveModule);
3702
- setCurrentFileDir(dirname7(filePath));
3703
- const globalEnv = createEnvironment3();
3704
- evaluateProgram(ast, globalEnv);
3705
- if (!globalEnv.has(objName)) {
3706
- setModuleResolver(null);
3707
- return [];
3405
+ try {
3406
+ const seeds = mockDirectivesToAbsSeeds(extractDirectives2(ast));
3407
+ const binds = collectAbsBindingsFromGraph(source, filePath, {
3408
+ seedVars: seeds.seedVars,
3409
+ seedFns: seeds.seedFns
3410
+ });
3411
+ const bound = binds.get(objName);
3412
+ if (bound && bound.shape.k !== "unknown" && bound.shape.k !== "never") {
3413
+ const absCompletions = getCompletionsForAbs(bound);
3414
+ if (absCompletions.length > 0) return absCompletions;
3415
+ }
3416
+ } catch {
3708
3417
  }
3709
- const objType = globalEnv.lookup(objName);
3710
- const completions = getCompletionsForType(objType);
3711
- setModuleResolver(null);
3712
- return completions;
3418
+ return [];
3713
3419
  }
3714
3420
  function sanitizeSourceForParsing(source) {
3715
3421
  return source.replace(/(\w+)\.\s*$/gm, "$1._ ");
@@ -3721,68 +3427,46 @@ function getTextBeforePosition(source, line, column) {
3721
3427
  }
3722
3428
  function getVariableCompletions(filePath, source) {
3723
3429
  const ast = parse6(source);
3724
- resetMemo();
3725
- setModuleResolver(resolveModule);
3726
- setCurrentFileDir(dirname7(filePath));
3727
- const globalEnv = createEnvironment3();
3728
- evaluateProgram(ast, globalEnv);
3729
- const ownBindings = globalEnv.getOwnBindings();
3730
- const completions = [];
3731
- for (const [name, tv] of Object.entries(ownBindings)) {
3732
- if (name.startsWith("__export_")) continue;
3733
- completions.push({
3734
- label: name,
3735
- kind: tv.kind === "function" ? "method" : "variable",
3736
- detail: typeValueToString4(tv)
3737
- });
3738
- }
3739
- setModuleResolver(null);
3740
- return completions;
3741
- }
3742
- function builtinMemberType(memberExpr) {
3743
3430
  try {
3744
- const result = evaluate(parse6(`${memberExpr};`), createEnvironment3());
3745
- if (!result || typeof result !== "object" || !("kind" in result)) return null;
3746
- return result;
3431
+ const seeds = mockDirectivesToAbsSeeds(extractDirectives2(ast));
3432
+ const binds = collectAbsBindingsFromGraph(source, filePath, {
3433
+ seedVars: seeds.seedVars,
3434
+ seedFns: seeds.seedFns
3435
+ });
3436
+ if (binds.size > 0) {
3437
+ const completions = [];
3438
+ for (const [name, absVal] of binds) {
3439
+ if (name.startsWith("__export_")) continue;
3440
+ completions.push({
3441
+ label: name,
3442
+ kind: absVal.shape.k === "fn" ? "method" : "variable",
3443
+ detail: absDetail(absVal)
3444
+ });
3445
+ }
3446
+ if (completions.length > 0) return completions;
3447
+ }
3747
3448
  } catch {
3748
- return null;
3749
3449
  }
3450
+ return [];
3451
+ }
3452
+ function builtinMemberAbs(className, member) {
3453
+ return builtinProtoMember(className, member);
3750
3454
  }
3751
3455
  function builtinProtoMembers(className) {
3752
- const table = BUILTIN_PROTOTYPE_METHOD_APPROXIMATIONS[className];
3753
- return table ? Object.keys(table) : [];
3456
+ return builtinProtoMemberNames(className);
3754
3457
  }
3755
- function describeMember(label, tv, fallbackClass) {
3756
- if (tv) {
3757
- const sig = getFnSig3(tv);
3758
- if (sig) {
3759
- const paramNames = tv.kind === "function" ? tv.params : [];
3760
- const params = sig.paramTypes.map((p, i) => `${paramNames[i] ?? `arg${i}`}: ${typeValueToString4(p)}`).join(", ");
3761
- return `(${params}) => ${typeValueToString4(sig.returnType)}`;
3762
- }
3763
- if (tv.kind !== "function") return typeValueToString4(tv);
3458
+ function describeMember(label, a, fallbackClass) {
3459
+ if (a) {
3460
+ const s = describeAbsMember(a);
3461
+ if (s) return s;
3764
3462
  }
3765
3463
  return `${label}(\u2026)@${fallbackClass}`;
3766
3464
  }
3767
- function getArrayCompletions(tv) {
3768
- const completions = [];
3769
- for (const m of builtinProtoMembers("Array")) {
3770
- const detail = describeMember(m, builtinMemberType(`Array.prototype.${m}`), "Array");
3771
- completions.push({ label: m, kind: "method", detail });
3772
- }
3773
- completions.push({
3774
- label: "length",
3775
- kind: "property",
3776
- // tuple 长度是精确字面量;array 是 number(保持原展示语义)
3777
- detail: tv.kind === "tuple" ? `${tv.elements.length}` : "number"
3778
- });
3779
- return completions;
3780
- }
3781
3465
  function getPromiseCompletions() {
3782
3466
  return builtinProtoMembers("Promise").map((m) => ({
3783
3467
  label: m,
3784
3468
  kind: "method",
3785
- detail: describeMember(m, builtinMemberType(`Promise.prototype.${m}`), "Promise")
3469
+ detail: describeMember(m, builtinMemberAbs("Promise", m), "Promise")
3786
3470
  }));
3787
3471
  }
3788
3472
  function getStringCompletions() {
@@ -3791,16 +3475,71 @@ function getStringCompletions() {
3791
3475
  completions.push({
3792
3476
  label: m,
3793
3477
  kind: "method",
3794
- detail: describeMember(m, builtinMemberType(`"s".${m}`), "String")
3478
+ detail: describeMember(m, builtinMemberAbs("String", m), "String")
3795
3479
  });
3796
3480
  }
3797
3481
  completions.push({ label: "length", kind: "property", detail: "number" });
3798
3482
  return completions;
3799
3483
  }
3800
- function getUnionCompletions(tv) {
3801
- const members = tv.members;
3484
+ function absDetail(a) {
3485
+ try {
3486
+ return formatShape3(a);
3487
+ } catch {
3488
+ return formatAbs5(a);
3489
+ }
3490
+ }
3491
+ function absIsFn(a) {
3492
+ return a.shape.k === "fn";
3493
+ }
3494
+ function getCompletionsForAbs(a) {
3495
+ const s = a.shape;
3496
+ switch (s.k) {
3497
+ case "obj": {
3498
+ const completions = [];
3499
+ for (const [key, slot] of Object.entries(s.slots)) {
3500
+ completions.push({
3501
+ label: key,
3502
+ kind: absIsFn(slot.value) ? "method" : "property",
3503
+ detail: absDetail(slot.value)
3504
+ });
3505
+ }
3506
+ return completions;
3507
+ }
3508
+ case "brand":
3509
+ return getCompletionsForAbs(s.shape);
3510
+ case "sum":
3511
+ return getSumCompletions(s.members);
3512
+ case "tuple":
3513
+ if (a.conf === "exact") return getArrayCompletionsAbs(s.elements.length);
3514
+ return [];
3515
+ case "arr":
3516
+ return getArrayCompletionsAbs(void 0);
3517
+ case "eff":
3518
+ if (s.eff === "promise") return getPromiseCompletions();
3519
+ return [];
3520
+ case "prim":
3521
+ if (s.type === "string") return getStringCompletions();
3522
+ return [];
3523
+ default:
3524
+ return [];
3525
+ }
3526
+ }
3527
+ function getArrayCompletionsAbs(tupleLen) {
3528
+ const completions = [];
3529
+ for (const m of builtinProtoMembers("Array")) {
3530
+ const detail = describeMember(m, builtinMemberAbs("Array", m), "Array");
3531
+ completions.push({ label: m, kind: "method", detail });
3532
+ }
3533
+ completions.push({
3534
+ label: "length",
3535
+ kind: "property",
3536
+ detail: tupleLen !== void 0 ? `${tupleLen}` : "number"
3537
+ });
3538
+ return completions;
3539
+ }
3540
+ function getSumCompletions(members) {
3802
3541
  if (members.length === 0) return [];
3803
- const labelsByMember = members.map((m) => getCompletionsForType(m));
3542
+ const labelsByMember = members.map((m) => getCompletionsForAbs(m));
3804
3543
  const baseIdx = labelsByMember.findIndex((labels) => labels.length > 0);
3805
3544
  if (baseIdx === -1) return [];
3806
3545
  const common = [];
@@ -3824,57 +3563,34 @@ function getUnionCompletions(tv) {
3824
3563
  }
3825
3564
  return common;
3826
3565
  }
3827
- function getCompletionsForType(tv) {
3828
- const completions = [];
3829
- if (tv.kind === "object") {
3830
- for (const [key, val] of Object.entries(tv.properties)) {
3831
- completions.push({
3832
- label: key,
3833
- kind: val.kind === "function" ? "method" : "property",
3834
- detail: typeValueToString4(val)
3835
- });
3836
- }
3837
- return completions;
3838
- }
3839
- if (tv.kind === "instance") {
3840
- for (const [key, val] of Object.entries(tv.properties)) {
3841
- completions.push({
3842
- label: key,
3843
- kind: val.kind === "function" ? "method" : "property",
3844
- detail: typeValueToString4(val)
3845
- });
3846
- }
3847
- return completions;
3848
- }
3849
- if (tv.kind === "union") {
3850
- return getUnionCompletions(tv);
3851
- }
3852
- if (tv.kind === "array" || tv.kind === "tuple") {
3853
- return getArrayCompletions(tv);
3854
- }
3855
- if (tv.kind === "promise") {
3856
- return getPromiseCompletions();
3857
- }
3858
- if (tv.kind === "primitive" && tv.type === "string") {
3859
- return getStringCompletions();
3860
- }
3861
- return completions;
3862
- }
3863
3566
 
3864
3567
  // src/index.ts
3865
3568
  import { collectAbsInlays } from "@nudojs/core";
3866
3569
 
3867
3570
  // src/infer-json.ts
3868
- import { typeValueToString as typeValueToString5 } from "@nudojs/core";
3571
+ import { formatAbs as formatAbs6, formatShape as formatShape4 } from "@nudojs/core";
3869
3572
  function mapCase(c) {
3870
3573
  const out = {
3871
3574
  name: c.name,
3872
- args: c.args.map((a) => typeValueToString5(a)),
3873
- result: typeValueToString5(c.result),
3874
- throws: c.throws.kind !== "never" ? typeValueToString5(c.throws) : null,
3575
+ args: c.argAbs.map((a) => formatShape4(a)),
3576
+ result: formatShape4(c.abs),
3577
+ throws: c.throwsAbs.shape.k !== "never" ? formatShape4(c.throwsAbs) : null,
3875
3578
  source: c.source ?? null
3876
3579
  };
3877
3580
  if (c.aggregatedFrom !== void 0) out.aggregatedFrom = c.aggregatedFrom;
3581
+ if (c.argAbs.length > 0) {
3582
+ out.argsAbs = c.argAbs.map((a) => {
3583
+ try {
3584
+ return formatAbs6(a);
3585
+ } catch {
3586
+ return "unknown";
3587
+ }
3588
+ });
3589
+ }
3590
+ try {
3591
+ out.resultAbs = formatAbs6(c.abs);
3592
+ } catch {
3593
+ }
3878
3594
  if (c.intension) {
3879
3595
  const i = c.intension;
3880
3596
  out.intension = {
@@ -3896,7 +3612,13 @@ function mapFunction(f) {
3896
3612
  cases: f.cases.map(mapCase)
3897
3613
  };
3898
3614
  if (f.noDeclaration) out.noDeclaration = true;
3899
- if (f.combined) out.combined = typeValueToString5(f.combined);
3615
+ if (f.combinedAbs) {
3616
+ out.combined = formatShape4(f.combinedAbs);
3617
+ try {
3618
+ out.combinedAbs = formatAbs6(f.combinedAbs);
3619
+ } catch {
3620
+ }
3621
+ }
3900
3622
  return out;
3901
3623
  }
3902
3624
  function serializeInferJson(result, file) {
@@ -3989,8 +3711,6 @@ function summarizeNodeEnv(env) {
3989
3711
  }
3990
3712
 
3991
3713
  // src/semantic-tokens.ts
3992
- import { dirname as dirname8 } from "path";
3993
- import { createEnvironment as createEnvironment4 } from "@nudojs/core";
3994
3714
  import { parse as parse7 } from "@nudojs/parser";
3995
3715
  var SEMANTIC_TOKEN_TYPES = [
3996
3716
  "function",
@@ -4030,6 +3750,20 @@ var TYPE_PARAMETER = SEMANTIC_TOKEN_TYPES.indexOf("parameter");
4030
3750
  var TYPE_PROPERTY = SEMANTIC_TOKEN_TYPES.indexOf("property");
4031
3751
  var TYPE_METHOD = SEMANTIC_TOKEN_TYPES.indexOf("method");
4032
3752
  var MOD_DECLARATION = 1 << SEMANTIC_TOKEN_MODIFIERS.indexOf("declaration");
3753
+ function collectFunctionBindingNames(filePath, source, _ast) {
3754
+ try {
3755
+ const binds = collectAbsBindingsFromGraph(source, filePath);
3756
+ if (binds.size > 0) {
3757
+ const fns = /* @__PURE__ */ new Set();
3758
+ for (const [name, a] of binds) {
3759
+ if (a.shape.k === "fn") fns.add(name);
3760
+ }
3761
+ return fns;
3762
+ }
3763
+ } catch {
3764
+ }
3765
+ return /* @__PURE__ */ new Set();
3766
+ }
4033
3767
  function buildSemanticTokens(filePath, source) {
4034
3768
  let ast;
4035
3769
  try {
@@ -4037,18 +3771,8 @@ function buildSemanticTokens(filePath, source) {
4037
3771
  } catch {
4038
3772
  return [];
4039
3773
  }
4040
- const env = createEnvironment4();
4041
- resetMemo();
4042
- setModuleResolver(resolveModule);
4043
- setCurrentFileDir(dirname8(filePath));
4044
- try {
4045
- evaluateProgram(ast, env);
4046
- } catch {
4047
- } finally {
4048
- setModuleResolver(null);
4049
- }
3774
+ const functionNames = collectFunctionBindingNames(filePath, source, ast);
4050
3775
  const program = ast.program ?? ast;
4051
- const ownBindings = env.getOwnBindings();
4052
3776
  const topLevelDeclarators = /* @__PURE__ */ new Set();
4053
3777
  for (const stmt of program.body ?? []) {
4054
3778
  const decl = stmt.type === "ExportNamedDeclaration" || stmt.type === "ExportDefaultDeclaration" ? stmt.declaration ?? null : stmt;
@@ -4082,7 +3806,7 @@ function buildSemanticTokens(filePath, source) {
4082
3806
  }
4083
3807
  }
4084
3808
  };
4085
- const isFunctionValue = (name) => ownBindings[name]?.kind === "function";
3809
+ const isFunctionValue = (name) => functionNames.has(name);
4086
3810
  const visit = (node) => {
4087
3811
  if (!node || typeof node !== "object") return;
4088
3812
  const n = node;
@@ -4142,144 +3866,206 @@ function buildSemanticTokens(filePath, source) {
4142
3866
  }
4143
3867
 
4144
3868
  // src/dts-generator.ts
4145
- import { T as T3, isTemplate, getTemplateParts, simplifyUnion as simplifyUnion2, typeValueEquals, getFnSig as getFnSig4 } from "@nudojs/core";
4146
- function typeValueToTSType(tv) {
4147
- switch (tv.kind) {
4148
- case "literal": {
4149
- const v = tv.value;
4150
- if (v === null) return "null";
4151
- if (v === void 0) return "undefined";
3869
+ import {
3870
+ joinAbs as joinAbs2,
3871
+ litValue as litValue3,
3872
+ abs as makeAbs2,
3873
+ isTemplateLike,
3874
+ templatePartsOf
3875
+ } from "@nudojs/core";
3876
+ function wrapComplexAbs(a) {
3877
+ const ts = absToTSType(a);
3878
+ if (a.shape.k === "sum") return `(${ts})`;
3879
+ return ts;
3880
+ }
3881
+ function absToTSType(a) {
3882
+ if (a.term?.op === "lit") {
3883
+ const v = a.term.value;
3884
+ if (v === null) return "null";
3885
+ if (v === void 0) return "undefined";
3886
+ if (a.shape.k === "prim") {
4152
3887
  if (typeof v === "string") return JSON.stringify(v);
4153
3888
  if (typeof v === "boolean") return String(v);
4154
- return String(v);
4155
- }
4156
- case "primitive":
4157
- return tv.type;
4158
- case "refined": {
4159
- if (isTemplate(tv)) {
4160
- const parts = getTemplateParts(tv);
4161
- const inner = parts.map((p) => p.kind === "literal" && typeof p.value === "string" ? p.value : `\${${typeValueToTSType(p)}}`).join("");
4162
- return `\`${inner}\``;
4163
- }
4164
- return typeValueToTSType(tv.base);
4165
- }
4166
- case "object": {
4167
- const entries = Object.entries(tv.properties);
4168
- if (entries.length === 0) return "{}";
4169
- const inner = entries.map(([k, v]) => `${k}: ${typeValueToTSType(v)}`).join("; ");
4170
- return `{ ${inner} }`;
4171
- }
4172
- case "array":
4173
- return `${wrapComplexType(tv.element)}[]`;
4174
- case "tuple": {
4175
- const inner = tv.elements.map(typeValueToTSType).join(", ");
4176
- return `[${inner}]`;
3889
+ if (typeof v === "number") return String(v);
4177
3890
  }
4178
- case "function": {
4179
- const params = tv.params.map((p) => `${p}: unknown`).join(", ");
4180
- const sig = getFnSig4(tv);
4181
- const retStr = sig?.returnType ? typeValueToTSType(sig.returnType) : "unknown";
4182
- return `(${params}) => ${retStr}`;
4183
- }
4184
- case "promise":
4185
- return `Promise<${typeValueToTSType(tv.value)}>`;
4186
- case "instance":
4187
- return tv.className;
4188
- case "union":
4189
- return tv.members.map(typeValueToTSType).join(" | ");
3891
+ }
3892
+ if (a.shape.k === "prim" && a.shape.type === "string" && isTemplateLike(a)) {
3893
+ const parts = templatePartsOf(a);
3894
+ const inner = parts.map((p) => {
3895
+ const lv = litValue3(p);
3896
+ if (typeof lv === "string") return lv;
3897
+ return `\${${absToTSType(p)}}`;
3898
+ }).join("");
3899
+ return `\`${inner}\``;
3900
+ }
3901
+ switch (a.shape.k) {
4190
3902
  case "never":
4191
3903
  return "never";
4192
3904
  case "unknown":
3905
+ case "any":
3906
+ return "unknown";
3907
+ case "prim":
3908
+ return a.shape.type;
3909
+ case "obj": {
3910
+ const entries = Object.entries(a.shape.slots).map(([k, slot]) => {
3911
+ const inner = absToTSType(slot.value);
3912
+ if (slot.optional) {
3913
+ const t = slot.value.shape.k === "sum" ? `(${inner})` : inner;
3914
+ return `${k}: ${t} | undefined`;
3915
+ }
3916
+ return `${k}: ${inner}`;
3917
+ });
3918
+ if (entries.length === 0) return "{}";
3919
+ return `{ ${entries.join("; ")} }`;
3920
+ }
3921
+ case "arr":
3922
+ return `${wrapComplexAbs(a.shape.element)}[]`;
3923
+ case "tuple": {
3924
+ const inner = a.shape.elements.map(absToTSType).join(", ");
3925
+ return `[${inner}]`;
3926
+ }
3927
+ case "fn": {
3928
+ const params = a.shape.params.map((p) => `${p}: unknown`).join(", ");
3929
+ const ret = a.shape.returnType ? absToTSType(a.shape.returnType) : "unknown";
3930
+ return `(${params}) => ${ret}`;
3931
+ }
3932
+ case "brand":
3933
+ return a.shape.name;
3934
+ case "eff":
3935
+ if (a.shape.eff === "promise") return `Promise<${absToTSType(a.shape.inner)}>`;
3936
+ return absToTSType(a.shape.inner);
3937
+ case "sum": {
3938
+ const parts = a.shape.members.map(absToTSType).filter((p) => p !== "never");
3939
+ const uniq = [...new Set(parts)];
3940
+ if (uniq.length === 0) return "never";
3941
+ if (uniq.length === 1) return uniq[0];
3942
+ return uniq.join(" | ");
3943
+ }
3944
+ default:
4193
3945
  return "unknown";
4194
3946
  }
4195
3947
  }
4196
- function wrapComplexType(tv) {
4197
- const ts = typeValueToTSType(tv);
4198
- if (tv.kind === "union") return `(${ts})`;
4199
- return ts;
4200
- }
4201
- function getParamName(fn, index) {
4202
- if (fn.paramNames && fn.paramNames[index]) {
4203
- return fn.paramNames[index];
4204
- }
4205
- return `arg${index}`;
4206
- }
4207
- function widenLiteralToBase(tv) {
4208
- if (tv.kind !== "literal") return tv;
4209
- const v = tv.value;
4210
- if (typeof v === "number") return T3.number;
4211
- if (typeof v === "string") return T3.string;
4212
- if (typeof v === "boolean") return T3.boolean;
4213
- if (typeof v === "bigint") return T3.bigint;
4214
- return tv;
3948
+ function caseArgAbs(c, i) {
3949
+ return c.argAbs[i];
4215
3950
  }
4216
- function widenTopLevel(tv) {
4217
- if (tv.kind !== "union") return widenLiteralToBase(tv);
4218
- return simplifyUnion2(tv.members.map(widenLiteralToBase));
3951
+ function caseResultAbs(c) {
3952
+ return c.abs;
4219
3953
  }
4220
- function widenProperties(properties) {
4221
- const out = {};
4222
- for (const [k, v] of Object.entries(properties)) out[k] = widenParamType(v);
4223
- return out;
4224
- }
4225
- function widenParamType(tv) {
4226
- switch (tv.kind) {
4227
- case "literal":
4228
- return widenLiteralToBase(tv);
4229
- case "union":
4230
- return simplifyUnion2(tv.members.map(widenParamType));
3954
+ function widenParamAbs(a) {
3955
+ const s = a.shape;
3956
+ switch (s.k) {
3957
+ case "prim":
3958
+ return makeAbs2(s, void 0, void 0, "exact");
4231
3959
  case "tuple": {
4232
- const widened = tv.elements.map(widenParamType);
3960
+ const widened = s.elements.map(widenParamAbs);
4233
3961
  const first = widened[0];
4234
- if (first && widened.every((el) => typeValueEquals(el, first))) {
4235
- return T3.array(first);
3962
+ if (first && widened.length > 0 && widened.every((el) => absToTSType(el) === absToTSType(first))) {
3963
+ return makeAbs2({ k: "arr", element: first }, void 0, void 0, "exact");
4236
3964
  }
4237
- return T3.tuple(widened);
4238
- }
4239
- case "array":
4240
- return T3.array(widenParamType(tv.element));
4241
- case "object":
4242
- return T3.object(widenProperties(tv.properties));
4243
- case "promise":
4244
- return T3.promise(widenParamType(tv.value));
4245
- case "instance":
4246
- return T3.instanceOf(tv.className, widenProperties(tv.properties));
4247
- case "refined":
4248
- return widenParamType(tv.base);
3965
+ return makeAbs2({ k: "tuple", elements: widened }, void 0, void 0, "exact");
3966
+ }
3967
+ case "arr":
3968
+ return makeAbs2({ k: "arr", element: widenParamAbs(s.element) }, void 0, void 0, "exact");
3969
+ case "obj": {
3970
+ const slots = {};
3971
+ for (const [k, slot] of Object.entries(s.slots)) {
3972
+ slots[k] = {
3973
+ value: widenParamAbs(slot.value),
3974
+ ...slot.optional ? { optional: true } : {}
3975
+ };
3976
+ }
3977
+ return makeAbs2({ k: "obj", slots }, void 0, void 0, "exact");
3978
+ }
3979
+ case "eff":
3980
+ return makeAbs2(
3981
+ { k: "eff", eff: s.eff, inner: widenParamAbs(s.inner) },
3982
+ void 0,
3983
+ void 0,
3984
+ "exact"
3985
+ );
3986
+ case "brand":
3987
+ return makeAbs2(
3988
+ { k: "brand", name: s.name, shape: widenParamAbs(s.shape) },
3989
+ void 0,
3990
+ void 0,
3991
+ "exact"
3992
+ );
3993
+ case "sum":
3994
+ return makeAbs2(
3995
+ { k: "sum", members: s.members.map(widenParamAbs) },
3996
+ void 0,
3997
+ void 0,
3998
+ "exact"
3999
+ );
4249
4000
  default:
4250
- return tv;
4001
+ return a;
4251
4002
  }
4252
4003
  }
4253
- function widenedUnion(members) {
4254
- const flat = [];
4255
- const collect = (tv) => {
4256
- if (tv.kind === "union") {
4257
- tv.members.forEach(collect);
4258
- return;
4004
+ function widenTopLevelAbs(a) {
4005
+ if (a.shape.k === "sum") {
4006
+ return makeAbs2(
4007
+ { k: "sum", members: a.shape.members.map(widenTopLevelAbs) },
4008
+ void 0,
4009
+ void 0,
4010
+ a.conf
4011
+ );
4012
+ }
4013
+ return widenLiteralToPrimAbs(a);
4014
+ }
4015
+ function widenLiteralToPrimAbs(a) {
4016
+ if (a.term?.op === "lit" && a.term.value === null) return a;
4017
+ if (a.term?.op === "lit" && a.term.value === void 0) return a;
4018
+ if (a.term?.op === "lit" && a.shape.k === "prim") {
4019
+ const v = a.term.value;
4020
+ if (typeof v === "number" || typeof v === "string" || typeof v === "boolean" || typeof v === "bigint") {
4021
+ return makeAbs2(a.shape, void 0, void 0, a.conf);
4259
4022
  }
4260
- flat.push(tv);
4261
- };
4262
- members.forEach(collect);
4263
- const widened = flat.map(widenParamType);
4264
- const seen = /* @__PURE__ */ new Set();
4265
- const deduped = widened.filter((tv) => {
4266
- const key = typeValueToTSType(tv);
4267
- if (seen.has(key)) return false;
4268
- seen.add(key);
4269
- return true;
4270
- });
4271
- return simplifyUnion2(deduped);
4023
+ }
4024
+ return a;
4025
+ }
4026
+ function paramTypeFromAbs(members) {
4027
+ if (members.length === 0) return "unknown";
4028
+ let joined;
4029
+ try {
4030
+ joined = members.reduce((a, b) => joinAbs2(a, b));
4031
+ } catch {
4032
+ joined = members[0];
4033
+ }
4034
+ return absToTSType(widenParamAbs(joined));
4035
+ }
4036
+ function returnAbsOf(fn) {
4037
+ if (fn.combinedAbs) return fn.combinedAbs;
4038
+ const results = fn.cases.map(caseResultAbs);
4039
+ if (results.length > 0) {
4040
+ try {
4041
+ return results.reduce((a, b) => joinAbs2(a, b));
4042
+ } catch {
4043
+ }
4044
+ return results[0];
4045
+ }
4046
+ return void 0;
4047
+ }
4048
+ function getParamName(fn, index) {
4049
+ if (fn.paramNames && fn.paramNames[index]) {
4050
+ return fn.paramNames[index];
4051
+ }
4052
+ return `arg${index}`;
4272
4053
  }
4273
4054
  function computeMainSignature(fn) {
4274
- const arity = Math.max(...fn.cases.map((c) => c.args.length));
4275
- const minArity = Math.min(...fn.cases.map((c) => c.args.length));
4055
+ const arity = Math.max(...fn.cases.map((c) => c.argAbs.length));
4056
+ const minArity = Math.min(...fn.cases.map((c) => c.argAbs.length));
4276
4057
  const params = [];
4277
4058
  const paramNames = [];
4278
4059
  const paramTypes = [];
4279
4060
  const usedNames = /* @__PURE__ */ new Set();
4280
4061
  for (let i = 0; i < arity; i++) {
4281
- const members = fn.cases.filter((c) => i < c.args.length).map((c) => c.args[i]);
4282
- const typeStr = typeValueToTSType(widenedUnion(members));
4062
+ const members = [];
4063
+ for (const c of fn.cases) {
4064
+ if (i >= c.argAbs.length) continue;
4065
+ const a = caseArgAbs(c, i);
4066
+ if (a) members.push(a);
4067
+ }
4068
+ const typeStr = paramTypeFromAbs(members);
4283
4069
  let name = getParamName(fn, i);
4284
4070
  if (usedNames.has(name)) {
4285
4071
  let n = 2;
@@ -4292,18 +4078,18 @@ function computeMainSignature(fn) {
4292
4078
  paramNames.push(name);
4293
4079
  paramTypes.push(typeStr);
4294
4080
  }
4295
- const combined = fn.combined ?? simplifyUnion2(fn.cases.map((c) => c.result));
4296
- const returnType = typeValueToTSType(widenTopLevel(combined));
4081
+ const retAbs = returnAbsOf(fn);
4082
+ const returnType = retAbs ? absToTSType(widenTopLevelAbs(retAbs)) : "unknown";
4297
4083
  return { params, paramNames, paramTypes, returnType };
4298
4084
  }
4299
4085
  function generateJSDoc(fn, sig) {
4300
4086
  if (fn.cases.length === 0) return "";
4301
4087
  const lines = ["/**"];
4302
4088
  for (const c of fn.cases) {
4303
- const preciseDiffers = c.args.length !== sig.paramTypes.length || c.args.some((a, i) => typeValueToTSType(a) !== sig.paramTypes[i]) || typeValueToTSType(c.result) !== sig.returnType;
4089
+ const preciseDiffers = c.argAbs.length !== sig.paramTypes.length || c.argAbs.some((a, i) => absToTSType(a) !== sig.paramTypes[i]) || absToTSType(c.abs) !== sig.returnType;
4304
4090
  if (!preciseDiffers) continue;
4305
- const argsStr = c.args.map(typeValueToTSType).join(", ");
4306
- lines.push(` * Case: ${c.name} (${argsStr}) => ${typeValueToTSType(c.result)}`);
4091
+ const argsStr = c.argAbs.map(absToTSType).join(", ");
4092
+ lines.push(` * Case: ${c.name} (${argsStr}) => ${absToTSType(c.abs)}`);
4307
4093
  }
4308
4094
  for (let i = 0; i < sig.paramTypes.length; i++) {
4309
4095
  lines.push(` * @param ${sig.paramNames[i]} - ${sig.paramTypes[i]}`);
@@ -4315,12 +4101,14 @@ function generateJSDoc(fn, sig) {
4315
4101
  function generateFunctionDtsLines(fn) {
4316
4102
  if (fn.noDeclaration) return [];
4317
4103
  if (fn.cases.length === 0) {
4318
- if (fn.combined) {
4104
+ const retAbs = fn.combinedAbs;
4105
+ if (retAbs) {
4319
4106
  return [
4320
- `export declare function ${fn.name}(...args: unknown[]): ${typeValueToTSType(fn.combined)};`
4107
+ `export declare function ${fn.name}(...args: unknown[]): ${absToTSType(retAbs)};`
4321
4108
  ];
4322
4109
  }
4323
4110
  return [];
4111
+ return [];
4324
4112
  }
4325
4113
  const sig = computeMainSignature(fn);
4326
4114
  const jsdoc = generateJSDoc(fn, sig);
@@ -4338,102 +4126,59 @@ function generateDts(result) {
4338
4126
  }
4339
4127
 
4340
4128
  // src/schema-generator.ts
4341
- function typeValueToZodSchema(tv) {
4342
- switch (tv.kind) {
4343
- case "literal": {
4344
- if (tv.value === null) return "z.null()";
4345
- if (tv.value === void 0) return "z.undefined()";
4346
- if (typeof tv.value === "string") return `z.literal(${JSON.stringify(tv.value)})`;
4347
- if (typeof tv.value === "boolean") return `z.literal(${tv.value})`;
4348
- return `z.literal(${tv.value})`;
4349
- }
4350
- case "primitive":
4351
- return `z.${tv.type}()`;
4352
- case "refined":
4353
- return typeValueToZodSchema(tv.base);
4354
- case "object": {
4355
- const entries = Object.entries(tv.properties).map(([k, v]) => `${k}: ${typeValueToZodSchema(v)}`).join(", ");
4129
+ function absToZodSchema(a) {
4130
+ if (a.term?.op === "lit") {
4131
+ const v = a.term.value;
4132
+ if (v === null) return "z.null()";
4133
+ if (v === void 0) return "z.undefined()";
4134
+ if (typeof v === "string") return `z.literal(${JSON.stringify(v)})`;
4135
+ if (typeof v === "boolean") return `z.literal(${v})`;
4136
+ if (typeof v === "number") return `z.literal(${v})`;
4137
+ if (typeof v === "bigint") return `z.literal(${v}n)`;
4138
+ }
4139
+ switch (a.shape.k) {
4140
+ case "prim":
4141
+ return `z.${a.shape.type}()`;
4142
+ case "obj": {
4143
+ const entries = Object.entries(a.shape.slots).map(([k, slot]) => `${k}: ${absToZodSchema(slot.value)}`).join(", ");
4356
4144
  return `z.object({ ${entries} })`;
4357
4145
  }
4358
- case "array":
4359
- return `z.array(${typeValueToZodSchema(tv.element)})`;
4146
+ case "arr":
4147
+ return `z.array(${absToZodSchema(a.shape.element)})`;
4360
4148
  case "tuple": {
4361
- const inner = tv.elements.map(typeValueToZodSchema).join(", ");
4149
+ const inner = a.shape.elements.map(absToZodSchema).join(", ");
4362
4150
  return `z.tuple([${inner}])`;
4363
4151
  }
4364
- case "function":
4152
+ case "fn":
4365
4153
  return "z.function()";
4366
- case "promise":
4367
- return `z.promise(${typeValueToZodSchema(tv.value)})`;
4368
- case "instance":
4369
- return `z.instanceof(${tv.className})`;
4370
- case "union": {
4371
- const members = tv.members.map(typeValueToZodSchema).join(", ");
4154
+ case "eff":
4155
+ if (a.shape.eff === "promise") return `z.promise(${absToZodSchema(a.shape.inner)})`;
4156
+ return absToZodSchema(a.shape.inner);
4157
+ case "brand":
4158
+ return `z.instanceof(${a.shape.name})`;
4159
+ case "sum": {
4160
+ const members = a.shape.members.map(absToZodSchema).join(", ");
4372
4161
  return `z.union([${members}])`;
4373
4162
  }
4374
4163
  case "never":
4375
4164
  return "z.never()";
4376
4165
  case "unknown":
4166
+ case "any":
4167
+ default:
4377
4168
  return "z.unknown()";
4378
4169
  }
4379
4170
  }
4380
4171
 
4381
4172
  // src/guard-generator.ts
4382
4173
  import { denoteGuard } from "@nudojs/core";
4383
- function generateGuardFunction(name, tv) {
4384
- const body = generateGuardBody(tv, "data");
4385
- return `export function ${name}(data) {
4386
- return ${body};
4387
- }`;
4388
- }
4389
4174
  function generateGuardFunctionFromAbs(name, abs) {
4390
4175
  const body = denoteGuard(abs, "data");
4391
4176
  return `export function ${name}(data) {
4392
4177
  return ${body};
4393
4178
  }`;
4394
4179
  }
4395
- function generateGuardBody(tv, varName) {
4396
- switch (tv.kind) {
4397
- case "literal": {
4398
- if (tv.value === null) return `${varName} === null`;
4399
- if (tv.value === void 0) return `${varName} === undefined`;
4400
- return `${varName} === ${JSON.stringify(tv.value)}`;
4401
- }
4402
- case "primitive":
4403
- return `typeof ${varName} === '${tv.type}'`;
4404
- case "refined":
4405
- return generateGuardBody(tv.base, varName);
4406
- case "object": {
4407
- const checks = [`typeof ${varName} === 'object'`, `${varName} !== null`];
4408
- for (const [key, val] of Object.entries(tv.properties)) {
4409
- checks.push(generateGuardBody(val, `${varName}.${key}`));
4410
- }
4411
- return checks.join(" && ");
4412
- }
4413
- case "array":
4414
- return `Array.isArray(${varName}) && ${varName}.every(item => ${generateGuardBody(tv.element, "item")})`;
4415
- case "tuple": {
4416
- const checks = [`Array.isArray(${varName})`, `${varName}.length === ${tv.elements.length}`];
4417
- tv.elements.forEach((el, i) => {
4418
- checks.push(generateGuardBody(el, `${varName}[${i}]`));
4419
- });
4420
- return checks.join(" && ");
4421
- }
4422
- case "union": {
4423
- const members = tv.members.map((m) => generateGuardBody(m, varName));
4424
- return `(${members.join(" || ")})`;
4425
- }
4426
- case "never":
4427
- return "false";
4428
- case "unknown":
4429
- return "true";
4430
- case "function":
4431
- return `typeof ${varName} === 'function'`;
4432
- case "promise":
4433
- return `${varName} instanceof Promise`;
4434
- case "instance":
4435
- return `${varName} instanceof ${tv.className}`;
4436
- }
4180
+ function generateGuardFunction(name, abs) {
4181
+ return generateGuardFunctionFromAbs(name, abs);
4437
4182
  }
4438
4183
 
4439
4184
  // src/case-emitter.ts
@@ -4462,56 +4207,59 @@ function serializeObjectKey(key) {
4462
4207
  if (/^['"]|['"]$/.test(key)) return null;
4463
4208
  return `"${key}"`;
4464
4209
  }
4465
- function serializeCaseArg(tv) {
4466
- switch (tv.kind) {
4467
- case "primitive":
4468
- if (tv.type === "number" || tv.type === "string" || tv.type === "boolean") {
4469
- return `T.${tv.type}`;
4210
+ function serializeCaseArg(a) {
4211
+ const s = a.shape;
4212
+ if (a.term?.op === "lit") {
4213
+ const v = a.term.value;
4214
+ if (typeof v === "number") {
4215
+ if (!Number.isFinite(v)) return null;
4216
+ const n = String(v);
4217
+ return NUMBER_LITERAL_REGEX.test(n) ? n : null;
4218
+ }
4219
+ if (typeof v === "boolean") return v ? "true" : "false";
4220
+ if (v === null) return "null";
4221
+ if (v === void 0) return "undefined";
4222
+ if (typeof v === "bigint") return null;
4223
+ if (typeof v === "string") return serializeStringLiteral(v);
4224
+ return null;
4225
+ }
4226
+ switch (s.k) {
4227
+ case "prim":
4228
+ if (s.type === "number" || s.type === "string" || s.type === "boolean") {
4229
+ return `T.${s.type}`;
4470
4230
  }
4471
4231
  return null;
4472
4232
  case "unknown":
4233
+ case "any":
4473
4234
  return "T.unknown";
4474
4235
  case "never":
4475
4236
  return "T.never";
4476
- case "literal": {
4477
- const v = tv.value;
4478
- if (typeof v === "number") {
4479
- if (!Number.isFinite(v)) return null;
4480
- const s = String(v);
4481
- return NUMBER_LITERAL_REGEX.test(s) ? s : null;
4482
- }
4483
- if (typeof v === "boolean") return v ? "true" : "false";
4484
- if (v === null) return "null";
4485
- if (v === void 0) return "undefined";
4486
- if (typeof v === "bigint") return null;
4487
- return serializeStringLiteral(v);
4488
- }
4489
- case "union": {
4237
+ case "sum": {
4490
4238
  const parts = [];
4491
- for (const member of tv.members) {
4492
- const s = serializeCaseArg(member);
4493
- if (s === null) return null;
4494
- parts.push(s);
4239
+ for (const member of s.members) {
4240
+ const ser = serializeCaseArg(member);
4241
+ if (ser === null) return null;
4242
+ parts.push(ser);
4495
4243
  }
4496
4244
  return `T.union(${parts.join(", ")})`;
4497
4245
  }
4498
- case "array": {
4499
- const el = serializeCaseArg(tv.element);
4246
+ case "arr": {
4247
+ const el = serializeCaseArg(s.element);
4500
4248
  return el === null ? null : `T.array(${el})`;
4501
4249
  }
4502
4250
  case "tuple": {
4503
4251
  const parts = [];
4504
- for (const el of tv.elements) {
4505
- const s = serializeCaseArg(el);
4506
- if (s === null) return null;
4507
- parts.push(s);
4252
+ for (const el of s.elements) {
4253
+ const ser = serializeCaseArg(el);
4254
+ if (ser === null) return null;
4255
+ parts.push(ser);
4508
4256
  }
4509
4257
  return `[${parts.join(", ")}]`;
4510
4258
  }
4511
- case "object": {
4259
+ case "obj": {
4512
4260
  const parts = [];
4513
- for (const [key, value] of Object.entries(tv.properties)) {
4514
- const vs = serializeCaseArg(value);
4261
+ for (const [key, slot] of Object.entries(s.slots)) {
4262
+ const vs = serializeCaseArg(slot.value);
4515
4263
  if (vs === null) return null;
4516
4264
  const ks = serializeObjectKey(key);
4517
4265
  if (ks === null) return null;
@@ -4523,10 +4271,10 @@ function serializeCaseArg(tv) {
4523
4271
  return null;
4524
4272
  }
4525
4273
  }
4526
- function buildCaseDirective(name, args) {
4274
+ function buildCaseDirective(name, argsAbs) {
4527
4275
  if (name.includes('"') || /[\r\n]/.test(name)) return null;
4528
4276
  const parts = [];
4529
- for (const arg of args) {
4277
+ for (const arg of argsAbs) {
4530
4278
  const s = serializeCaseArg(arg);
4531
4279
  if (s === null) return null;
4532
4280
  parts.push(s);
@@ -4629,7 +4377,7 @@ function insertGeneratedCaseDirectives(source, analysis) {
4629
4377
  const built = [];
4630
4378
  const names = [];
4631
4379
  for (const c of callsiteCases) {
4632
- const directive = buildCaseDirective(c.name, c.args);
4380
+ const directive = buildCaseDirective(c.name, c.argAbs ?? []);
4633
4381
  if (directive === null) {
4634
4382
  skipped.push({ fn: fn.name, reason: "no-serializable-cases", detail: `case ${c.name} not serializable` });
4635
4383
  } else {
@@ -4792,16 +4540,16 @@ function unifiedDiff(a, b, path) {
4792
4540
 
4793
4541
  // src/interface-surface.ts
4794
4542
  import { readFileSync as readFileSync6 } from "fs";
4795
- import { dirname as dirname9, resolve as resolve6 } from "path";
4543
+ import { dirname as dirname8, resolve as resolve6 } from "path";
4796
4544
  import {
4797
4545
  effectiveInterface,
4798
4546
  formatConstraint,
4547
+ formatShape as formatShape5,
4799
4548
  interfaceDiagCount,
4800
4549
  localNamedExports,
4801
4550
  refineDiagCount,
4802
4551
  takeInterfaceDiagsSince,
4803
- takeRefineDiagsSince,
4804
- typeValueToString as typeValueToString6
4552
+ takeRefineDiagsSince
4805
4553
  } from "@nudojs/core";
4806
4554
  function formatInterfaceSurfaceLine(e) {
4807
4555
  const params = `(${e.params.map((p) => `${p.name}: ${p.display}`).join(", ")})`;
@@ -4810,12 +4558,19 @@ function formatInterfaceSurfaceLine(e) {
4810
4558
  if (e.kind === "local") line += " (local)";
4811
4559
  return line;
4812
4560
  }
4561
+ function absToImplicitDisplay(a) {
4562
+ try {
4563
+ return formatShape5(a);
4564
+ } catch {
4565
+ return "unknown";
4566
+ }
4567
+ }
4813
4568
  async function interfaceSurface(filePath, opts = {}) {
4814
4569
  const abs = resolve6(filePath);
4815
4570
  const source = readFileSync6(abs, "utf-8");
4816
4571
  const ifaceSince = interfaceDiagCount();
4817
4572
  const refineSince = refineDiagCount();
4818
- const autoBind = opts.autoBind ?? interfaceConfig(findProjectConfig(dirname9(abs))?.config).autoBind;
4573
+ const autoBind = opts.autoBind ?? interfaceConfig(findProjectConfig(dirname8(abs))?.config).autoBind;
4819
4574
  const loadModule = opts.loadModule ?? defaultLoadModule;
4820
4575
  const exported = localNamedExports(source);
4821
4576
  const kindOf = (fnName) => exported.has(fnName) ? "export" : "local";
@@ -4836,14 +4591,21 @@ async function interfaceSurface(filePath, opts = {}) {
4836
4591
  const params = fn.paramNames.map((name, i) => {
4837
4592
  const seen = [];
4838
4593
  for (const c of fn.cases) {
4839
- const arg = c.args[i];
4840
- if (!arg) continue;
4841
- const s = typeValueToString6(arg);
4842
- if (!seen.includes(s)) seen.push(s);
4594
+ const absArg = c.argAbs[i];
4595
+ if (absArg) {
4596
+ const s = absToImplicitDisplay(absArg);
4597
+ if (!seen.includes(s)) seen.push(s);
4598
+ }
4843
4599
  }
4844
4600
  return { name, display: seen.length > 0 ? seen.join(" | ") : "unknown" };
4845
4601
  });
4846
- const ret = fn.combined !== void 0 ? typeValueToString6(fn.combined) : fn.cases.length > 0 ? typeValueToString6(fn.cases[fn.cases.length - 1].result) : void 0;
4602
+ let ret;
4603
+ if (fn.combinedAbs) {
4604
+ ret = absToImplicitDisplay(fn.combinedAbs);
4605
+ } else if (fn.cases.length > 0) {
4606
+ const last = fn.cases[fn.cases.length - 1];
4607
+ ret = absToImplicitDisplay(last.abs);
4608
+ }
4847
4609
  entries.push({ fn: fn.name, kind: kindOf(fn.name), source: "implicit", params, returns: ret });
4848
4610
  }
4849
4611
  takeInterfaceDiagsSince(ifaceSince);
@@ -4853,7 +4615,7 @@ async function interfaceSurface(filePath, opts = {}) {
4853
4615
 
4854
4616
  // src/interface-emitter.ts
4855
4617
  import { existsSync as existsSync6, readFileSync as readFileSync7, writeFileSync, renameSync, unlinkSync } from "fs";
4856
- import { basename as basename2, dirname as dirname10, relative, resolve as resolve7 } from "path";
4618
+ import { basename as basename2, dirname as dirname9, relative, resolve as resolve7 } from "path";
4857
4619
  import {
4858
4620
  execNudoModule,
4859
4621
  formatConstraint as formatConstraint2,
@@ -4866,8 +4628,7 @@ import {
4866
4628
  refineDiagCount as refineDiagCount2,
4867
4629
  sidecarPathOf,
4868
4630
  takeInterfaceDiagsSince as takeInterfaceDiagsSince2,
4869
- takeRefineDiagsSince as takeRefineDiagsSince2,
4870
- typeValueToAbs as typeValueToAbs6
4631
+ takeRefineDiagsSince as takeRefineDiagsSince2
4871
4632
  } from "@nudojs/core";
4872
4633
  import { randomBytes } from "crypto";
4873
4634
  var GENERATED_HEADER = "// @generated by nudo \u2014 do not edit; regenerate with `nudo interface --emit`";
@@ -4878,12 +4639,29 @@ async function emitInterface(filePath, opts) {
4878
4639
  `emit target '${abs}' is inside node_modules; contract sidecars are never written there`
4879
4640
  );
4880
4641
  }
4642
+ const proj = findProjectConfig(dirname9(abs));
4643
+ const allow = interfaceConfig(proj?.config).emit;
4644
+ if (!matchesEmitAllowlist(abs, proj?.projectDir, allow)) {
4645
+ return {
4646
+ written: [],
4647
+ skipped: [{ fn: opts.fnNames?.[0] ?? "*", reason: "emit-denied" }],
4648
+ changed: false,
4649
+ issues: [
4650
+ {
4651
+ code: "nudo:interface-emit-denied",
4652
+ severity: "warning",
4653
+ message: `emit target '${relative(process.cwd(), abs) || abs}' is outside package.json#nudo.interface.emit allowlist`
4654
+ }
4655
+ ],
4656
+ sidecarPath: sidecarPathOf(abs)
4657
+ };
4658
+ }
4881
4659
  const source = readFileSync7(abs, "utf-8");
4882
4660
  const ifaceSince = interfaceDiagCount2();
4883
4661
  const refineSince = refineDiagCount2();
4884
4662
  const sidecarPath = sidecarPathOf(abs);
4885
4663
  const sidecarSrc = existsSync6(sidecarPath) ? readFileSync7(sidecarPath, "utf-8") : "";
4886
- const srcRel = relative(dirname10(sidecarPath), abs) || basename2(abs);
4664
+ const srcRel = relative(dirname9(sidecarPath), abs) || basename2(abs);
4887
4665
  const sections = collectGeneratedSections(sidecarSrc);
4888
4666
  const generatedNames = new Set(sections.flatMap((s) => s.names));
4889
4667
  const declared = topLevelDeclaredNames(sidecarSrc);
@@ -5052,9 +4830,8 @@ function projectFunctionDsl(fn) {
5052
4830
  for (let i = 0; i < fn.paramNames.length; i++) {
5053
4831
  const argAbs = [];
5054
4832
  for (const c of paramCases) {
5055
- const a = c.args[i];
5056
- if (a === void 0) continue;
5057
- argAbs.push(safeTypeValueToAbs(a));
4833
+ const a = c.argAbs[i];
4834
+ if (a !== void 0) argAbs.push(a);
5058
4835
  }
5059
4836
  if (argAbs.length === 0) continue;
5060
4837
  const constraint = joinThenProject(argAbs);
@@ -5063,8 +4840,8 @@ function projectFunctionDsl(fn) {
5063
4840
  }
5064
4841
  const retAbs = [];
5065
4842
  for (const c of returnCases) {
5066
- if (c.throws !== void 0 && c.throws.kind !== "never") continue;
5067
- retAbs.push(c.abs ?? safeTypeValueToAbs(c.result));
4843
+ if (c.throwsAbs.shape.k !== "never") continue;
4844
+ retAbs.push(c.abs);
5068
4845
  }
5069
4846
  const retConstraint = retAbs.length > 0 ? joinThenProject(retAbs) : void 0;
5070
4847
  if (retConstraint === void 0 && paramParts.length === 0) return void 0;
@@ -5072,13 +4849,6 @@ function projectFunctionDsl(fn) {
5072
4849
  const ret = retConstraint === void 0 ? "" : `, ${formatConstraint2(retConstraint)}`;
5073
4850
  return `fn({ ${paramParts.join(", ")} }${ret})`;
5074
4851
  }
5075
- function safeTypeValueToAbs(v) {
5076
- try {
5077
- return typeValueToAbs6(v);
5078
- } catch {
5079
- return { shape: { k: "unknown" }, conf: "opaque" };
5080
- }
5081
- }
5082
4852
  function sectionText(fn, dsl, srcRel) {
5083
4853
  return `${GENERATED_HEADER}
5084
4854
  // source: ${srcRel}:${fn}
@@ -5201,9 +4971,939 @@ function joinSections(base, sectionTexts) {
5201
4971
  `;
5202
4972
  return lead + normalized.join("\n");
5203
4973
  }
4974
+
4975
+ // src/interface-derivation.ts
4976
+ import { existsSync as existsSync7, readFileSync as readFileSync8, renameSync as renameSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "fs";
4977
+ import { basename as basename3, dirname as dirname10, relative as relative2, resolve as resolve8 } from "path";
4978
+ import { parse as parse9 } from "@nudojs/parser";
4979
+ import {
4980
+ analyzeFn,
4981
+ beginDerivationSession,
4982
+ constraintToEntryAbs,
4983
+ derivationChain,
4984
+ effectiveInterface as effectiveInterface2,
4985
+ endDerivationSession,
4986
+ execNudoModule as execNudoModule2,
4987
+ formatConstraint as formatConstraint3,
4988
+ getDerivation,
4989
+ interfaceDiagCount as interfaceDiagCount3,
4990
+ isNodeModulesPath as isNodeModulesPath2,
4991
+ isNudoConstraint as isNudoConstraint2,
4992
+ joinThenProject as joinThenProject2,
4993
+ parseSource as parseSource3,
4994
+ projectDerivationDsl,
4995
+ refineDiagCount as refineDiagCount3,
4996
+ setAbsCallCollector as setAbsCallCollector2,
4997
+ sidecarPathOf as sidecarPathOf2,
4998
+ tagDerivationRoot,
4999
+ takeInterfaceDiagsSince as takeInterfaceDiagsSince3,
5000
+ takeRefineDiagsSince as takeRefineDiagsSince3,
5001
+ unknown as unknownAbs
5002
+ } from "@nudojs/core";
5003
+ function extractFnConstraintSources(sidecarSrc, fnName) {
5004
+ const out = { params: {} };
5005
+ let ast;
5006
+ try {
5007
+ ast = parse9(sidecarSrc);
5008
+ } catch {
5009
+ return out;
5010
+ }
5011
+ const imports = /* @__PURE__ */ new Map();
5012
+ const locals = /* @__PURE__ */ new Map();
5013
+ const srcOf = (node) => {
5014
+ const n = node;
5015
+ if (!n?.type) return void 0;
5016
+ if (n.type === "Identifier" && n.name) {
5017
+ const imp = imports.get(n.name);
5018
+ if (imp) {
5019
+ return { expr: imp.imported, importFrom: imp.from, importName: imp.imported };
5020
+ }
5021
+ const local = locals.get(n.name);
5022
+ if (local !== void 0) return { expr: local };
5023
+ return { expr: n.name };
5024
+ }
5025
+ if (n.start != null && n.end != null) {
5026
+ return { expr: sidecarSrc.slice(n.start, n.end) };
5027
+ }
5028
+ return void 0;
5029
+ };
5030
+ for (const stmt of ast.program.body) {
5031
+ if (stmt.type === "ImportDeclaration") {
5032
+ const from = stmt.source.value;
5033
+ for (const s of stmt.specifiers) {
5034
+ if (s.type === "ImportSpecifier") {
5035
+ const imported = s.imported.type === "Identifier" ? s.imported.name : String(s.imported);
5036
+ imports.set(s.local.name, { from, imported });
5037
+ }
5038
+ }
5039
+ continue;
5040
+ }
5041
+ if (stmt.type !== "ExportNamedDeclaration" || stmt.source) continue;
5042
+ const d = stmt.declaration;
5043
+ if (!d || d.type !== "VariableDeclaration") continue;
5044
+ for (const decl of d.declarations) {
5045
+ if (decl.id.type !== "Identifier") continue;
5046
+ const name = decl.id.name;
5047
+ const init = decl.init;
5048
+ if (!init) continue;
5049
+ const text = init.start != null && init.end != null ? sidecarSrc.slice(init.start, init.end) : name;
5050
+ if (init.type !== "CallExpression") {
5051
+ locals.set(name, text);
5052
+ continue;
5053
+ }
5054
+ const calleeName = init.callee.type === "Identifier" ? init.callee.name : void 0;
5055
+ locals.set(name, text);
5056
+ if (calleeName !== "fn" || name !== fnName) continue;
5057
+ const paramsNode = init.arguments[0];
5058
+ if (paramsNode?.type === "ObjectExpression") {
5059
+ for (const prop of paramsNode.properties) {
5060
+ if (prop.type !== "ObjectProperty") continue;
5061
+ const key = prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "StringLiteral" ? prop.key.value : void 0;
5062
+ if (!key) continue;
5063
+ const expr = srcOf(prop.value);
5064
+ if (expr) out.params[key] = expr;
5065
+ }
5066
+ }
5067
+ const retNode = init.arguments[1];
5068
+ if (retNode) {
5069
+ const expr = srcOf(retNode);
5070
+ if (expr) out.returns = expr;
5071
+ }
5072
+ }
5073
+ }
5074
+ return out;
5075
+ }
5076
+ function functionParamNames(source, fnName) {
5077
+ try {
5078
+ const ast = parse9(source);
5079
+ for (const stmt of ast.program.body) {
5080
+ const d = stmt.type === "ExportNamedDeclaration" ? stmt.declaration ?? void 0 : stmt;
5081
+ if (!d) continue;
5082
+ if (d.type === "FunctionDeclaration" && d.id?.name === fnName) {
5083
+ return d.params.map(paramNameOf);
5084
+ }
5085
+ if (d.type === "VariableDeclaration") {
5086
+ for (const decl of d.declarations) {
5087
+ if (decl.id.type === "Identifier" && decl.id.name === fnName && decl.init && (decl.init.type === "ArrowFunctionExpression" || decl.init.type === "FunctionExpression")) {
5088
+ return decl.init.params.map(paramNameOf);
5089
+ }
5090
+ }
5091
+ }
5092
+ }
5093
+ } catch {
5094
+ }
5095
+ return [];
5096
+ }
5097
+ function paramNameOf(p) {
5098
+ if (p.type === "Identifier" && p.name) return p.name;
5099
+ if (p.type === "AssignmentPattern" && p.left?.type === "Identifier" && p.left.name) {
5100
+ return p.left.name;
5101
+ }
5102
+ return "_";
5103
+ }
5104
+ function topLevelFnNames(source) {
5105
+ const out = [];
5106
+ try {
5107
+ const ast = parse9(source);
5108
+ for (const stmt of ast.program.body) {
5109
+ const d = stmt.type === "ExportNamedDeclaration" ? stmt.declaration ?? void 0 : stmt;
5110
+ if (!d) continue;
5111
+ if (d.type === "FunctionDeclaration" && d.id) out.push(d.id.name);
5112
+ if (d.type === "VariableDeclaration") {
5113
+ for (const decl of d.declarations) {
5114
+ if (decl.id.type === "Identifier" && decl.init && (decl.init.type === "ArrowFunctionExpression" || decl.init.type === "FunctionExpression")) {
5115
+ out.push(decl.id.name);
5116
+ }
5117
+ }
5118
+ }
5119
+ }
5120
+ } catch {
5121
+ }
5122
+ return out;
5123
+ }
5124
+ function importLocalMap(source, fromFile) {
5125
+ const out = /* @__PURE__ */ new Map();
5126
+ try {
5127
+ const ast = parse9(source);
5128
+ const base = dirname10(resolve8(fromFile));
5129
+ for (const stmt of ast.program.body) {
5130
+ if (stmt.type !== "ImportDeclaration") continue;
5131
+ const spec = stmt.source.value;
5132
+ if (!spec.startsWith(".") && !spec.startsWith("/")) continue;
5133
+ const raw = resolve8(base, spec);
5134
+ let modulePath = null;
5135
+ for (const cand of [raw, `${raw}.js`, `${raw}.ts`, `${raw}.mjs`]) {
5136
+ if (existsSync7(cand)) {
5137
+ modulePath = cand;
5138
+ break;
5139
+ }
5140
+ }
5141
+ if (!modulePath) continue;
5142
+ for (const s of stmt.specifiers) {
5143
+ if (s.type === "ImportSpecifier") {
5144
+ const imported = s.imported.type === "Identifier" ? s.imported.name : String(s.imported);
5145
+ out.set(s.local.name, { modulePath, exportName: imported });
5146
+ }
5147
+ }
5148
+ }
5149
+ } catch {
5150
+ }
5151
+ return out;
5152
+ }
5153
+ function resolveRelImport(fromSpec, fromDir, targetDir) {
5154
+ const abs = resolve8(fromDir, fromSpec);
5155
+ let rel = relative2(targetDir, abs);
5156
+ if (!rel.startsWith(".")) rel = `./${rel}`;
5157
+ return rel.split("\\").join("/");
5158
+ }
5159
+ function projectParamSlot(absList, paramName) {
5160
+ if (absList.length === 0) return void 0;
5161
+ const constraint = joinThenProject2(absList);
5162
+ if (constraint === void 0) return void 0;
5163
+ if (absList.length === 1) {
5164
+ const node = getDerivation(absList[0]);
5165
+ if (node) {
5166
+ const proj = projectDerivationDsl(node, paramName);
5167
+ if (proj) {
5168
+ const chain = derivationChain(node);
5169
+ const root = chain[chain.length - 1];
5170
+ const shiftCount = chain.filter((n) => n.kind === "shift").length;
5171
+ return {
5172
+ constraint,
5173
+ dsl: proj.expr,
5174
+ prelude: proj.prelude,
5175
+ imports: proj.imports,
5176
+ compositional: true,
5177
+ ...root?.kind === "root" ? { rootNodeId: root.id } : {},
5178
+ shiftCount
5179
+ };
5180
+ }
5181
+ }
5182
+ }
5183
+ return {
5184
+ constraint,
5185
+ dsl: formatConstraint3(constraint),
5186
+ prelude: [],
5187
+ imports: [],
5188
+ compositional: false
5189
+ };
5190
+ }
5191
+ function projectReturnSlot(retAbs, params, paramNames) {
5192
+ if (retAbs.length === 0) return void 0;
5193
+ const constraint = joinThenProject2(retAbs);
5194
+ if (constraint === void 0) return void 0;
5195
+ if (retAbs.length === 1) {
5196
+ const node = getDerivation(retAbs[0]);
5197
+ if (node && !derivationChain(node).some((n) => n.kind === "join" || n.kind === "opaque")) {
5198
+ const rel = projectReturnRelativeToParams(node, params, paramNames);
5199
+ if (rel) {
5200
+ return {
5201
+ constraint,
5202
+ dsl: rel.dsl,
5203
+ prelude: rel.prelude,
5204
+ imports: rel.imports,
5205
+ compositional: true
5206
+ };
5207
+ }
5208
+ const proj = projectDerivationDsl(node, paramNames[0] ?? "ret");
5209
+ if (proj) {
5210
+ return {
5211
+ constraint,
5212
+ dsl: proj.expr,
5213
+ prelude: proj.prelude,
5214
+ imports: proj.imports,
5215
+ compositional: true
5216
+ };
5217
+ }
5218
+ }
5219
+ }
5220
+ return {
5221
+ constraint,
5222
+ dsl: formatConstraint3(constraint),
5223
+ prelude: [],
5224
+ imports: [],
5225
+ compositional: false
5226
+ };
5227
+ }
5228
+ function projectReturnRelativeToParams(node, params, paramNames) {
5229
+ const chain = derivationChain(node);
5230
+ const root = chain[chain.length - 1];
5231
+ if (!root || root.kind !== "root") return void 0;
5232
+ const shifts = [];
5233
+ for (let j = chain.length - 2; j >= 0; j--) {
5234
+ const n = chain[j];
5235
+ if (n.kind !== "shift" || n.offset === void 0) return void 0;
5236
+ shifts.push(n.offset);
5237
+ }
5238
+ for (let i = 0; i < params.length; i++) {
5239
+ const p = params[i];
5240
+ if (p.rootNodeId === void 0 || p.rootNodeId !== root.id) continue;
5241
+ const paramShiftCount = p.shiftCount ?? 0;
5242
+ if (shifts.length < paramShiftCount) continue;
5243
+ const tail = shifts.slice(paramShiftCount);
5244
+ if (tail.length === 0) {
5245
+ return { dsl: p.dsl, prelude: [], imports: [] };
5246
+ }
5247
+ let expr = p.name || paramNames[i] || "x";
5248
+ for (const off of tail) expr = `${expr}.shift(${off})`;
5249
+ return { dsl: expr, prelude: [], imports: [] };
5250
+ }
5251
+ return void 0;
5252
+ }
5253
+ function deriveFromRoot(filePath, opts = {}) {
5254
+ const abs = resolve8(filePath);
5255
+ const source = readFileSync8(abs, "utf-8");
5256
+ const since = interfaceDiagCount3();
5257
+ const autoBind = opts.autoBind ?? interfaceConfig(findProjectConfig(dirname10(abs))?.config).autoBind;
5258
+ const loadModule = opts.loadModule ?? defaultLoadModule;
5259
+ const sidecarPath = sidecarPathOf2(abs);
5260
+ const sidecarSrc = existsSync7(sidecarPath) && !isNodeModulesPath2(sidecarPath) ? readFileSync8(sidecarPath, "utf-8") : "";
5261
+ const fnNames = topLevelFnNames(source);
5262
+ const roots = [];
5263
+ const plans = [];
5264
+ for (const fn of fnNames) {
5265
+ const eff = effectiveInterface2(source, fn, { loadModule, fromFile: abs, autoBind });
5266
+ if (!eff || eff.source !== "handwritten") continue;
5267
+ roots.push(fn);
5268
+ const sources = sidecarSrc ? extractFnConstraintSources(sidecarSrc, fn) : { params: {} };
5269
+ const srcParams = functionParamNames(source, fn);
5270
+ const planParams = srcParams.map((pname) => {
5271
+ const hit = eff.params.find((p) => p.param === pname);
5272
+ if (hit) {
5273
+ const src = sources.params[pname];
5274
+ return {
5275
+ name: pname,
5276
+ constraint: hit.constraint,
5277
+ ...src ? { src } : {}
5278
+ };
5279
+ }
5280
+ return {
5281
+ name: pname,
5282
+ constraint: { __nudoConstraint: true, preds: [] }
5283
+ };
5284
+ });
5285
+ plans.push({ fnName: fn, params: planParams });
5286
+ }
5287
+ if (plans.length === 0) {
5288
+ takeInterfaceDiagsSince3(since);
5289
+ return { roots: [], derived: [], hasRoot: false };
5290
+ }
5291
+ let modules = {};
5292
+ try {
5293
+ modules = evalAbsModuleGraph(source, abs, { loadModule }).modules;
5294
+ } catch {
5295
+ modules = {};
5296
+ }
5297
+ const importLocals = importLocalMap(source, abs);
5298
+ const wanted = opts.fnNames && opts.fnNames.length > 0 ? new Set(opts.fnNames) : void 0;
5299
+ const derived = [];
5300
+ for (const plan of plans) {
5301
+ const relRoot = relative2(process.cwd(), abs);
5302
+ const label = `${relRoot === "" || relRoot.startsWith("..") ? basename3(abs) : relRoot}:${plan.fnName}`;
5303
+ const rows = deriveOneRoot(plan, source, abs, modules, importLocals, label);
5304
+ for (const row of rows) {
5305
+ if (wanted && !wanted.has(row.fn)) continue;
5306
+ derived.push(row);
5307
+ }
5308
+ }
5309
+ takeInterfaceDiagsSince3(since);
5310
+ return { roots, derived, hasRoot: true };
5311
+ }
5312
+ function deriveOneRoot(plan, source, file, modules, importLocals, label) {
5313
+ beginDerivationSession();
5314
+ const entryArgs = [];
5315
+ try {
5316
+ for (const p of plan.params) {
5317
+ const entry = constraintToEntryAbs(p.constraint, p.name);
5318
+ if (p.src) {
5319
+ tagDerivationRoot(entry, {
5320
+ expr: p.src.expr,
5321
+ ...p.src.importFrom !== void 0 ? { importFrom: p.src.importFrom } : {},
5322
+ ...p.src.importName !== void 0 ? { importName: p.src.importName } : {}
5323
+ });
5324
+ } else if (p.constraint.preds.length === 0 && !p.constraint.prim) {
5325
+ }
5326
+ entryArgs.push(entry);
5327
+ }
5328
+ const calls = [];
5329
+ const prevCallCollector = setAbsCallCollector2((r) => calls.push(r));
5330
+ try {
5331
+ analyzeFn(source, plan.fnName, entryArgs, void 0, void 0, void 0, modules);
5332
+ } catch {
5333
+ return [];
5334
+ } finally {
5335
+ setAbsCallCollector2(prevCallCollector);
5336
+ }
5337
+ const byCallee = /* @__PURE__ */ new Map();
5338
+ for (const call of calls) {
5339
+ const imp = importLocals.get(call.fnName);
5340
+ let targetFile;
5341
+ let targetExport = call.fnName;
5342
+ if (imp) {
5343
+ targetFile = imp.modulePath;
5344
+ targetExport = imp.exportName;
5345
+ } else {
5346
+ targetFile = file;
5347
+ }
5348
+ if (isNodeModulesPath2(targetFile)) continue;
5349
+ if (!existsSync7(targetFile)) continue;
5350
+ const key = `${targetFile}::${targetExport}`;
5351
+ let agg = byCallee.get(key);
5352
+ if (!agg) {
5353
+ agg = { targetFile, targetExport, argAbs: [], resultAbs: [] };
5354
+ byCallee.set(key, agg);
5355
+ }
5356
+ agg.argAbs.push(call.args);
5357
+ agg.resultAbs.push(call.threw ? unknownAbs : call.result);
5358
+ }
5359
+ const out = [];
5360
+ for (const agg of byCallee.values()) {
5361
+ let targetSource;
5362
+ try {
5363
+ targetSource = readFileSync8(agg.targetFile, "utf-8");
5364
+ } catch {
5365
+ continue;
5366
+ }
5367
+ const paramNames = functionParamNames(targetSource, agg.targetExport);
5368
+ if (paramNames.length === 0 && agg.argAbs.every((a) => a.length === 0)) continue;
5369
+ const width = Math.max(
5370
+ paramNames.length,
5371
+ ...agg.argAbs.map((a) => a.length),
5372
+ 0
5373
+ );
5374
+ const params = [];
5375
+ let allCompositional = true;
5376
+ let sawEvidence = false;
5377
+ for (let i = 0; i < width; i++) {
5378
+ const name = paramNames[i] ?? `_${i}`;
5379
+ const absList = [];
5380
+ for (const args of agg.argAbs) {
5381
+ const a = args[i];
5382
+ if (a) absList.push(a);
5383
+ }
5384
+ const slot = projectParamSlot(absList, name);
5385
+ if (!slot) {
5386
+ allCompositional = false;
5387
+ continue;
5388
+ }
5389
+ sawEvidence = true;
5390
+ if (!slot.compositional) allCompositional = false;
5391
+ params.push({
5392
+ name,
5393
+ constraint: slot.constraint,
5394
+ dsl: slot.dsl,
5395
+ prelude: slot.prelude,
5396
+ imports: slot.imports,
5397
+ ...slot.rootNodeId !== void 0 ? { rootNodeId: slot.rootNodeId } : {},
5398
+ ...slot.shiftCount !== void 0 ? { shiftCount: slot.shiftCount } : {}
5399
+ });
5400
+ }
5401
+ const retAbs = agg.resultAbs.filter(
5402
+ (a) => a.conf === "exact" || a.conf === "path"
5403
+ );
5404
+ const retSlot = projectReturnSlot(retAbs, params, paramNames);
5405
+ if (retSlot && !retSlot.compositional) allCompositional = false;
5406
+ if (retSlot) sawEvidence = true;
5407
+ if (!sawEvidence) {
5408
+ out.push({
5409
+ file: agg.targetFile,
5410
+ fn: agg.targetExport,
5411
+ paramNames,
5412
+ params: [],
5413
+ derivedFrom: label,
5414
+ compositional: false,
5415
+ underivable: true
5416
+ });
5417
+ continue;
5418
+ }
5419
+ out.push({
5420
+ file: agg.targetFile,
5421
+ fn: agg.targetExport,
5422
+ paramNames,
5423
+ params,
5424
+ ...retSlot ? {
5425
+ returns: {
5426
+ constraint: retSlot.constraint,
5427
+ dsl: retSlot.dsl,
5428
+ prelude: retSlot.prelude,
5429
+ imports: retSlot.imports
5430
+ }
5431
+ } : {},
5432
+ derivedFrom: label,
5433
+ compositional: allCompositional && params.length > 0
5434
+ });
5435
+ }
5436
+ return out;
5437
+ } finally {
5438
+ endDerivationSession();
5439
+ }
5440
+ }
5441
+ var NameAllocator = class {
5442
+ taken;
5443
+ constructor(initial) {
5444
+ this.taken = new Set(initial ?? []);
5445
+ }
5446
+ claim(preferred) {
5447
+ if (!this.taken.has(preferred)) {
5448
+ this.taken.add(preferred);
5449
+ return preferred;
5450
+ }
5451
+ let i = 2;
5452
+ while (this.taken.has(`${preferred}_${i}`)) i++;
5453
+ const name = `${preferred}_${i}`;
5454
+ this.taken.add(name);
5455
+ return name;
5456
+ }
5457
+ };
5458
+ function escapeRegExp(s) {
5459
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5460
+ }
5461
+ function rewriteIdents(src, map) {
5462
+ if (map.size === 0) return src;
5463
+ let out = src;
5464
+ const keys = [...map.keys()].sort((a, b) => b.length - a.length);
5465
+ for (const k of keys) {
5466
+ const v = map.get(k);
5467
+ if (k === v) continue;
5468
+ out = out.replace(new RegExp(`(?<![\\w$.])${escapeRegExp(k)}(?![\\w$])`, "g"), v);
5469
+ }
5470
+ return out;
5471
+ }
5472
+ function preludeLocalName(line) {
5473
+ const m = /^const\s+([A-Za-z_$][\w$]*)\s*=/.exec(line.trim());
5474
+ return m?.[1];
5475
+ }
5476
+ function formatDerivedSection(row, opts) {
5477
+ if (row.underivable || row.params.length === 0) return void 0;
5478
+ const importMap = /* @__PURE__ */ new Map();
5479
+ const preludes = [];
5480
+ const paramDsls = [];
5481
+ for (const p of row.params) {
5482
+ for (const imp of p.imports) {
5483
+ const rel = resolveRelImport(imp.from, opts.rootSidecarDir, opts.targetSidecarDir);
5484
+ const prev = importMap.get(imp.name);
5485
+ if (prev !== void 0 && prev !== rel) return void 0;
5486
+ importMap.set(imp.name, rel);
5487
+ }
5488
+ for (const line of p.prelude) {
5489
+ if (!preludes.includes(line)) preludes.push(line);
5490
+ }
5491
+ paramDsls.push(p.dsl);
5492
+ }
5493
+ let retDsl = "";
5494
+ if (row.returns) {
5495
+ for (const imp of row.returns.imports) {
5496
+ const rel = resolveRelImport(imp.from, opts.rootSidecarDir, opts.targetSidecarDir);
5497
+ const prev = importMap.get(imp.name);
5498
+ if (prev !== void 0 && prev !== rel) return void 0;
5499
+ importMap.set(imp.name, rel);
5500
+ }
5501
+ for (const line of row.returns.prelude) {
5502
+ if (!preludes.includes(line)) preludes.push(line);
5503
+ }
5504
+ retDsl = `, ${row.returns.dsl}`;
5505
+ }
5506
+ const namer = new NameAllocator(opts.takenNames);
5507
+ namer.claim(row.fn);
5508
+ const renames = /* @__PURE__ */ new Map();
5509
+ const importLocals = [];
5510
+ for (const [name, from] of [...importMap.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
5511
+ const local = namer.claim(name);
5512
+ if (local !== name) renames.set(name, local);
5513
+ importLocals.push({ original: name, local, from });
5514
+ }
5515
+ const preludeClaimed = /* @__PURE__ */ new Set();
5516
+ const renamedPreludes = [];
5517
+ for (const line of preludes) {
5518
+ const local = preludeLocalName(line);
5519
+ if (local !== void 0 && !preludeClaimed.has(local)) {
5520
+ preludeClaimed.add(local);
5521
+ if (!renames.has(local)) {
5522
+ const next = namer.claim(local);
5523
+ if (next !== local) renames.set(local, next);
5524
+ }
5525
+ }
5526
+ renamedPreludes.push(rewriteIdents(line, renames));
5527
+ }
5528
+ const paramParts = row.params.map((p, i) => {
5529
+ const dsl = rewriteIdents(paramDsls[i], renames);
5530
+ const name = rewriteIdents(p.name, renames);
5531
+ return dsl === name ? name : `${name}: ${dsl}`;
5532
+ });
5533
+ const retPart = retDsl === "" ? "" : `, ${rewriteIdents(row.returns.dsl, renames)}`;
5534
+ const importLineTexts = importLocals.map(({ original, local, from }) => {
5535
+ const spec = original === local ? local : `${original} as ${local}`;
5536
+ return `import { ${spec} } from ${JSON.stringify(from)};`;
5537
+ }).sort((a, b) => a.localeCompare(b));
5538
+ const lines = [
5539
+ ...importLineTexts,
5540
+ ...importLineTexts.length > 0 && renamedPreludes.length > 0 ? [""] : [],
5541
+ ...renamedPreludes,
5542
+ `export const ${row.fn} = fn({ ${paramParts.join(", ")} }${retPart});`
5543
+ ];
5544
+ const usedNames = /* @__PURE__ */ new Set();
5545
+ for (const { local } of importLocals) usedNames.add(local);
5546
+ for (const line of renamedPreludes) {
5547
+ const n = preludeLocalName(line);
5548
+ if (n) usedNames.add(n);
5549
+ }
5550
+ usedNames.add(row.fn);
5551
+ return { text: lines.join("\n"), usedNames: [...usedNames] };
5552
+ }
5553
+ var DERIVED_HEADER = "// @generated by nudo \u2014 do not edit; regenerate with `nudo interface --emit`";
5554
+ function collectTopLevelNames(src) {
5555
+ const names = /* @__PURE__ */ new Set();
5556
+ if (src.trim() === "") return names;
5557
+ let ast;
5558
+ try {
5559
+ ast = parseSource3(src);
5560
+ } catch {
5561
+ return names;
5562
+ }
5563
+ const addDecl = (d) => {
5564
+ if (d.type === "VariableDeclaration") {
5565
+ for (const decl of d.declarations) {
5566
+ if (decl.id.type === "Identifier") names.add(decl.id.name);
5567
+ }
5568
+ } else if (d.type === "FunctionDeclaration" || d.type === "ClassDeclaration") {
5569
+ if (d.id) names.add(d.id.name);
5570
+ }
5571
+ };
5572
+ for (const stmt of ast.program.body) {
5573
+ if (stmt.type === "ImportDeclaration") {
5574
+ for (const s of stmt.specifiers) names.add(s.local.name);
5575
+ continue;
5576
+ }
5577
+ if (stmt.type === "ExportNamedDeclaration") {
5578
+ if (stmt.declaration) addDecl(stmt.declaration);
5579
+ for (const spec of stmt.specifiers) {
5580
+ names.add(
5581
+ spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value
5582
+ );
5583
+ }
5584
+ continue;
5585
+ }
5586
+ addDecl(stmt);
5587
+ }
5588
+ return names;
5589
+ }
5590
+ function sidecarAssembles(text, fromFile, loadModule) {
5591
+ try {
5592
+ parseSource3(text);
5593
+ execNudoModule2(text, { loadModule, fromFile });
5594
+ return true;
5595
+ } catch {
5596
+ return false;
5597
+ }
5598
+ }
5599
+ function emitDerivedFromRoot(rootFile, opts) {
5600
+ const abs = resolve8(rootFile);
5601
+ const derive = deriveFromRoot(abs, {
5602
+ ...opts.loadModule ? { loadModule: opts.loadModule } : {},
5603
+ ...opts.autoBind !== void 0 ? { autoBind: opts.autoBind } : {},
5604
+ ...opts.fnNames && opts.fnNames.length > 0 ? { fnNames: opts.fnNames } : {}
5605
+ });
5606
+ if (!derive.hasRoot) {
5607
+ return { sidecars: [], hasRoot: false, roots: [], entryOnly: true };
5608
+ }
5609
+ const proj = findProjectConfig(dirname10(abs));
5610
+ const allow = interfaceConfig(proj?.config).emit;
5611
+ const projectDir = proj?.projectDir;
5612
+ const rootSidecarDir = dirname10(sidecarPathOf2(abs));
5613
+ const wanted = opts.fnNames && opts.fnNames.length > 0 ? new Set(opts.fnNames) : void 0;
5614
+ const result = {
5615
+ sidecars: [],
5616
+ hasRoot: true,
5617
+ roots: derive.roots
5618
+ };
5619
+ const bySidecar = /* @__PURE__ */ new Map();
5620
+ for (const row of derive.derived) {
5621
+ if (wanted && !wanted.has(row.fn)) continue;
5622
+ if (!matchesEmitAllowlist(row.file, projectDir, allow)) continue;
5623
+ const sp = sidecarPathOf2(row.file);
5624
+ if (isNodeModulesPath2(sp)) continue;
5625
+ const list = bySidecar.get(sp) ?? [];
5626
+ list.push(row);
5627
+ bySidecar.set(sp, list);
5628
+ }
5629
+ for (const [sidecarPath, rows0] of bySidecar) {
5630
+ const targetFile = rows0[0].file;
5631
+ const targetSidecarDir = dirname10(sidecarPath);
5632
+ const prevSrc = existsSync7(sidecarPath) ? readFileSync8(sidecarPath, "utf-8") : "";
5633
+ const rows = opts.refreshExistingOnly ? rows0.filter((r) => findGeneratedSectionText(prevSrc, r.fn) !== void 0) : rows0;
5634
+ if (rows.length === 0) continue;
5635
+ const handwritten = handwrittenNames(prevSrc);
5636
+ const loadModule = opts.loadModule ?? defaultLoadModule;
5637
+ const batchFns = new Set(
5638
+ rows.filter((r) => !r.underivable && !handwritten.has(r.fn)).map((r) => r.fn)
5639
+ );
5640
+ const baseForNames = opts.mode === "update" ? stripGeneratedFor(prevSrc, batchFns) : prevSrc;
5641
+ const taken = collectTopLevelNames(baseForNames);
5642
+ const accepted = [];
5643
+ const issues = [];
5644
+ const written = [];
5645
+ let anySkip;
5646
+ for (const row of rows) {
5647
+ if (handwritten.has(row.fn)) {
5648
+ issues.push({
5649
+ code: "nudo:interface-name-clash",
5650
+ severity: "error",
5651
+ message: `sidecar already has a handwritten binding '${row.fn}' (${relative2(process.cwd(), sidecarPath) || sidecarPath}); handwritten wins \u2014 skipping emit`
5652
+ });
5653
+ anySkip = "name-clash";
5654
+ continue;
5655
+ }
5656
+ if (row.underivable) {
5657
+ issues.push({
5658
+ code: "nudo:interface-underivable",
5659
+ severity: "info",
5660
+ message: `${row.fn}: contract underivable from ${row.derivedFrom} (opaque / truncated / no evidence)`
5661
+ });
5662
+ anySkip = "underivable";
5663
+ continue;
5664
+ }
5665
+ const prevSection = findGeneratedSectionText(prevSrc, row.fn);
5666
+ const body = formatDerivedSection(row, {
5667
+ rootSidecarDir,
5668
+ targetSidecarDir,
5669
+ takenNames: taken
5670
+ });
5671
+ if (!body) {
5672
+ anySkip = "not-projectable";
5673
+ if (prevSection !== void 0) {
5674
+ for (const n of collectTopLevelNames(prevSection)) taken.add(n);
5675
+ }
5676
+ continue;
5677
+ }
5678
+ for (const n of body.usedNames) taken.add(n);
5679
+ const srcRel = relative2(targetSidecarDir, targetFile) || basename3(targetFile);
5680
+ const section = [
5681
+ DERIVED_HEADER,
5682
+ `// source: ${srcRel}:${row.fn}`,
5683
+ `// derived-from: ${row.derivedFrom}`,
5684
+ body.text,
5685
+ ""
5686
+ ].join("\n");
5687
+ if (!derivedRoundTrips(section, row.fn, sidecarPath, loadModule)) {
5688
+ anySkip = "not-projectable";
5689
+ if (prevSection !== void 0) {
5690
+ for (const n of collectTopLevelNames(prevSection)) taken.add(n);
5691
+ }
5692
+ continue;
5693
+ }
5694
+ if (opts.mode === "add" && prevSection !== void 0) {
5695
+ anySkip = "no-change";
5696
+ for (const n of collectTopLevelNames(prevSection)) taken.add(n);
5697
+ continue;
5698
+ }
5699
+ const norm = normalizeSectionText(section);
5700
+ if (prevSection !== void 0 && normalizeSectionText(prevSection) === norm) {
5701
+ anySkip = "no-change";
5702
+ accepted.push({ fn: row.fn, text: prevSection, prevText: prevSection });
5703
+ for (const n of collectTopLevelNames(prevSection)) taken.add(n);
5704
+ continue;
5705
+ }
5706
+ written.push(row.fn);
5707
+ accepted.push({ fn: row.fn, text: section, ...prevSection !== void 0 ? { prevText: prevSection } : {} });
5708
+ }
5709
+ let finalContent;
5710
+ if (accepted.length === 0) {
5711
+ finalContent = prevSrc;
5712
+ } else if (opts.mode === "add") {
5713
+ finalContent = joinSectionTexts(prevSrc, accepted.map((a) => a.text));
5714
+ } else {
5715
+ const acceptedFns = new Set(accepted.map((a) => a.fn));
5716
+ const stripped = stripGeneratedFor(prevSrc, acceptedFns);
5717
+ const preserved = collectGeneratedSectionsRaw(prevSrc).filter(
5718
+ (s) => !s.names.some((n) => acceptedFns.has(n))
5719
+ );
5720
+ finalContent = joinSectionTexts(stripped, [
5721
+ ...preserved.map((s) => normalizeSectionText(s.text)),
5722
+ ...accepted.map((a) => a.text)
5723
+ ]);
5724
+ }
5725
+ if (finalContent.trim() !== "" && !sidecarAssembles(finalContent, sidecarPath, loadModule)) {
5726
+ issues.push({
5727
+ code: "nudo:interface-not-projectable",
5728
+ severity: "error",
5729
+ message: `assembled sidecar failed round-trip (${relative2(process.cwd(), sidecarPath) || sidecarPath}); refusing to write`
5730
+ });
5731
+ anySkip = "not-projectable";
5732
+ result.sidecars.push({
5733
+ file: targetFile,
5734
+ sidecarPath,
5735
+ fn: rows.map((r) => r.fn).join(","),
5736
+ written: false,
5737
+ changed: false,
5738
+ ...anySkip ? { skipped: anySkip } : {},
5739
+ issues
5740
+ });
5741
+ continue;
5742
+ }
5743
+ const changed = finalContent !== prevSrc;
5744
+ const diff = changed ? unifiedDiff(prevSrc, finalContent, relative2(process.cwd(), sidecarPath) || sidecarPath) : void 0;
5745
+ if (changed && !opts.dryRun) {
5746
+ const tmp = `${sidecarPath}.tmp-${process.pid}-${Math.random().toString(16).slice(2, 10)}`;
5747
+ try {
5748
+ writeFileSync2(tmp, finalContent, "utf-8");
5749
+ renameSync2(tmp, sidecarPath);
5750
+ } catch (e) {
5751
+ try {
5752
+ if (existsSync7(tmp)) unlinkSync2(tmp);
5753
+ } catch {
5754
+ }
5755
+ throw e;
5756
+ }
5757
+ }
5758
+ result.sidecars.push({
5759
+ file: targetFile,
5760
+ sidecarPath,
5761
+ fn: rows.map((r) => r.fn).join(","),
5762
+ written: written.length > 0 && changed,
5763
+ changed,
5764
+ ...anySkip ? { skipped: anySkip } : {},
5765
+ ...diff !== void 0 ? { diff } : {},
5766
+ issues
5767
+ });
5768
+ }
5769
+ return result;
5770
+ }
5771
+ function collectGeneratedSectionsRaw(src) {
5772
+ if (src.trim() === "") return [];
5773
+ let ast;
5774
+ try {
5775
+ ast = parseSource3(src);
5776
+ } catch {
5777
+ return [];
5778
+ }
5779
+ const stmts = ast.program.body;
5780
+ const out = [];
5781
+ for (const headerPos of findGeneratedHeaderOffsets(src)) {
5782
+ for (let i = 0; i < stmts.length; i++) {
5783
+ const stmt = stmts[i];
5784
+ if (stmt.type !== "ExportNamedDeclaration" || stmt.source) continue;
5785
+ if (stmt.start == null || stmt.end == null || stmt.start < headerPos) continue;
5786
+ const d = stmt.declaration;
5787
+ if (!d) continue;
5788
+ const names = [];
5789
+ if (d.type === "VariableDeclaration") {
5790
+ for (const decl of d.declarations) {
5791
+ if (decl.id.type === "Identifier") names.push(decl.id.name);
5792
+ }
5793
+ } else if (d.type === "FunctionDeclaration" || d.type === "ClassDeclaration") {
5794
+ if (d.id) names.push(d.id.name);
5795
+ }
5796
+ if (names.length === 0) continue;
5797
+ out.push({
5798
+ names,
5799
+ start: headerPos,
5800
+ end: stmt.end,
5801
+ text: src.slice(headerPos, stmt.end)
5802
+ });
5803
+ break;
5804
+ }
5805
+ }
5806
+ return out;
5807
+ }
5808
+ function findGeneratedHeaderOffsets(src) {
5809
+ const out = [];
5810
+ let pos = 0;
5811
+ for (const line of src.split("\n")) {
5812
+ const t = line.trim();
5813
+ if ((t.startsWith("//") || t.startsWith("/*") || t.startsWith("*")) && /@generated/.test(t)) {
5814
+ out.push(pos);
5815
+ }
5816
+ pos += line.length + 1;
5817
+ }
5818
+ return out;
5819
+ }
5820
+ function generatedSectionNames(src) {
5821
+ return new Set(collectGeneratedSectionsRaw(src).flatMap((s) => s.names));
5822
+ }
5823
+ function handwrittenNames(src) {
5824
+ if (src.trim() === "") return /* @__PURE__ */ new Set();
5825
+ let ast;
5826
+ try {
5827
+ ast = parseSource3(src);
5828
+ } catch {
5829
+ return /* @__PURE__ */ new Set();
5830
+ }
5831
+ const names = /* @__PURE__ */ new Set();
5832
+ const collect = (stmt) => {
5833
+ if (stmt.type === "VariableDeclaration") {
5834
+ for (const d of stmt.declarations) {
5835
+ if (d.id.type === "Identifier") names.add(d.id.name);
5836
+ }
5837
+ } else if (stmt.type === "FunctionDeclaration" || stmt.type === "ClassDeclaration") {
5838
+ if (stmt.id) names.add(stmt.id.name);
5839
+ }
5840
+ };
5841
+ for (const stmt of ast.program.body) {
5842
+ if (stmt.type === "ExportNamedDeclaration") {
5843
+ if (stmt.declaration) collect(stmt.declaration);
5844
+ if (!stmt.source) {
5845
+ for (const spec of stmt.specifiers) {
5846
+ names.add(
5847
+ spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value
5848
+ );
5849
+ }
5850
+ }
5851
+ } else {
5852
+ collect(stmt);
5853
+ }
5854
+ }
5855
+ for (const n of generatedSectionNames(src)) names.delete(n);
5856
+ return names;
5857
+ }
5858
+ function findGeneratedSectionText(src, fn) {
5859
+ return collectGeneratedSectionsRaw(src).find((s) => s.names.includes(fn))?.text;
5860
+ }
5861
+ function stripGeneratedFor(src, fns) {
5862
+ const sections = collectGeneratedSectionsRaw(src).filter(
5863
+ (s) => s.names.some((n) => fns.has(n))
5864
+ );
5865
+ if (sections.length === 0) return src;
5866
+ const sorted = [...sections].sort((a, b) => a.start - b.start);
5867
+ let out = "";
5868
+ let pos = 0;
5869
+ for (const s of sorted) {
5870
+ out += src.slice(pos, s.start);
5871
+ pos = Math.max(pos, s.end);
5872
+ }
5873
+ out += src.slice(pos);
5874
+ return out;
5875
+ }
5876
+ function normalizeSectionText(text) {
5877
+ return text.replace(/\s*$/, "") + "\n";
5878
+ }
5879
+ function joinSectionTexts(base, sectionTexts) {
5880
+ const normalized = sectionTexts.map(normalizeSectionText).filter((t) => t.trim() !== "");
5881
+ const tailTrimmed = base.replace(/\s+$/, "");
5882
+ if (normalized.length === 0) return tailTrimmed === "" ? "" : `${tailTrimmed}
5883
+ `;
5884
+ const trimmed = tailTrimmed.replace(/^\s+/, "");
5885
+ const lead = trimmed === "" ? "" : `${trimmed}
5886
+
5887
+ `;
5888
+ return lead + normalized.join("\n");
5889
+ }
5890
+ function derivedRoundTrips(text, fn, fromFile, loadModule) {
5891
+ const since = refineDiagCount3();
5892
+ try {
5893
+ const exports = execNudoModule2(text, { loadModule, fromFile });
5894
+ const v = exports[fn];
5895
+ return isNudoConstraint2(v) && v.fn !== void 0;
5896
+ } catch {
5897
+ return false;
5898
+ } finally {
5899
+ takeRefineDiagsSince3(since);
5900
+ }
5901
+ }
5204
5902
  export {
5205
5903
  SEMANTIC_TOKEN_MODIFIERS,
5206
5904
  SEMANTIC_TOKEN_TYPES,
5905
+ absToTSType,
5906
+ absToZodSchema,
5207
5907
  analyzeExportsFromSource,
5208
5908
  analyzeFile,
5209
5909
  analyzeFileAsync,
@@ -5233,9 +5933,10 @@ export {
5233
5933
  computeDirtySet,
5234
5934
  defaultAbsLoadModule,
5235
5935
  defaultLoadModule,
5936
+ deriveFromRoot,
5937
+ emitDerivedFromRoot,
5236
5938
  emitInterface,
5237
5939
  encodeSemanticTokens,
5238
- envValueToAbs,
5239
5940
  evalAbsModuleGraph,
5240
5941
  evalProgramAbsWithModules,
5241
5942
  evictAbsModuleCacheFiles,
@@ -5243,7 +5944,9 @@ export {
5243
5944
  evictAnalysisFileCacheForFiles,
5244
5945
  evictBPathCacheForFiles,
5245
5946
  evictFnAnalysisCacheForFiles,
5947
+ extractFnConstraintSources,
5246
5948
  findProjectConfig,
5949
+ formatDerivedSection,
5247
5950
  formatEmitSummary,
5248
5951
  formatHarvestSummary,
5249
5952
  formatInterfaceSurfaceLine,
@@ -5251,6 +5954,8 @@ export {
5251
5954
  generateFunctionDtsLines,
5252
5955
  generateGuardFunction,
5253
5956
  generateGuardFunctionFromAbs,
5957
+ getAbsAtPosition,
5958
+ getAbsAtPositionAsync,
5254
5959
  getAnalysisFileCacheSize,
5255
5960
  getCasesForFile,
5256
5961
  getCompletionsAtPosition,
@@ -5268,6 +5973,7 @@ export {
5268
5973
  isBPathCapable,
5269
5974
  isNudoTargetPath,
5270
5975
  lookupHarvested,
5976
+ matchesEmitAllowlist,
5271
5977
  mockDirectivesToAbsSeeds,
5272
5978
  packageHarvestToAbsModules,
5273
5979
  resetAllAnalysisCaches,
@@ -5280,7 +5986,5 @@ export {
5280
5986
  tryBPathCall,
5281
5987
  tryBPathCallFull,
5282
5988
  tryRunBPath,
5283
- typeValueToTSType,
5284
- typeValueToZodSchema,
5285
5989
  unifiedDiff
5286
5990
  };