@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/chunk-2VHITMTM.js +488 -0
- package/dist/config-NqDRWWpn.d.ts +68 -0
- package/dist/evaluator/evaluator-api.d.ts +13 -15
- package/dist/evaluator/evaluator-api.js +9 -53
- package/dist/index.d.ts +182 -46
- package/dist/index.js +2034 -1330
- package/package.json +5 -5
- package/dist/chunk-FSIHAE7I.js +0 -5871
- package/dist/config-UlReSTfh.d.ts +0 -152
package/dist/index.js
CHANGED
|
@@ -1,79 +1,169 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
evaluateFunction,
|
|
6
|
-
evaluateFunctionFull,
|
|
7
|
-
evaluateProgram,
|
|
2
|
+
builtinProtoMember,
|
|
3
|
+
builtinProtoMemberNames,
|
|
4
|
+
describeAbsMember,
|
|
8
5
|
findProjectConfig,
|
|
9
|
-
getUnreachableRanges,
|
|
10
6
|
interfaceConfig,
|
|
11
7
|
loadEnvs,
|
|
12
|
-
|
|
13
|
-
preloadPathEnvs
|
|
14
|
-
|
|
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
|
-
|
|
21
|
+
analyzeFnFull,
|
|
50
22
|
evalProgramAbs as evalProgramAbs2,
|
|
23
|
+
callFunctionFull,
|
|
51
24
|
setAbsCallCollector,
|
|
25
|
+
unknown as absUnknown2,
|
|
52
26
|
setAbsNodeCollector,
|
|
53
|
-
absFunction as
|
|
27
|
+
absFunction as absFunction4,
|
|
54
28
|
checkInjectedDomainEvidence,
|
|
55
|
-
|
|
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
|
|
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 {
|
|
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 =
|
|
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
|
|
135
|
-
const
|
|
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.
|
|
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(
|
|
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 =
|
|
251
|
+
defaultKey = formatAbs2(markedDefault);
|
|
163
252
|
} catch {
|
|
164
253
|
defaultKey = "?";
|
|
165
254
|
}
|
|
166
255
|
const caseKey = cases.map(
|
|
167
|
-
(c) => `${c.args.map((a) =>
|
|
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
|
|
189
|
-
const
|
|
190
|
-
const body = fn.body;
|
|
277
|
+
if (h.callsFakeImpl) {
|
|
278
|
+
const { params, body, async } = h.callsFakeImpl;
|
|
191
279
|
return stampFingerprint(
|
|
192
|
-
absFunction(
|
|
193
|
-
`fake=${
|
|
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:
|
|
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 =
|
|
295
|
+
defaultReturn = h.returnValue;
|
|
208
296
|
} else if (h.onFirstCallValue) {
|
|
209
|
-
defaultReturn =
|
|
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:
|
|
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(
|
|
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] =
|
|
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 {
|
|
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
|
|
603
|
-
if (!
|
|
604
|
-
return
|
|
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(
|
|
692
|
-
if (
|
|
693
|
-
|
|
694
|
-
|
|
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(
|
|
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
|
|
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,
|
|
1174
|
+
function caseDirectiveKey(directives, formatAbs7) {
|
|
1144
1175
|
return directives.map((d) => {
|
|
1145
1176
|
if (d.kind === "case") {
|
|
1146
|
-
const args = (d.
|
|
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=${
|
|
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
|
|
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,
|
|
1218
|
-
|
|
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] =
|
|
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] =
|
|
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
|
|
1725
|
+
function validateMockDirectives(directives, diagnostics) {
|
|
1758
1726
|
for (const d of directives) {
|
|
1759
|
-
if (d.kind !== "mock") continue;
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
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 (
|
|
2000
|
-
const key = rec.
|
|
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.
|
|
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
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
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
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
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.
|
|
2097
|
-
|
|
1957
|
+
analysis.combinedAbs = collapseAbsLits(
|
|
1958
|
+
deduped.map((r) => r.resultAbs),
|
|
2098
1959
|
COLLAPSE_LITERAL_THRESHOLD
|
|
2099
1960
|
);
|
|
2100
1961
|
} else {
|
|
2101
|
-
analysis.
|
|
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
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
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
|
-
|
|
2243
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2270
|
-
setModuleResolver(null);
|
|
2271
|
-
setUnknownBuiltinHandler(null);
|
|
2046
|
+
setAbsCallCollector(prevCollector);
|
|
2272
2047
|
}
|
|
2273
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
2251
|
+
globalEnv.bind(name, absVal);
|
|
2545
2252
|
}
|
|
2546
2253
|
}
|
|
2547
2254
|
for (const [node, absVal] of absNodesShared) {
|
|
2548
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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,
|
|
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.
|
|
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
|
-
|
|
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
|
|
2710
|
-
let
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
2447
|
+
if (!caseAbs) {
|
|
2764
2448
|
if (bHostedEval) {
|
|
2765
|
-
|
|
2766
|
-
|
|
2449
|
+
caseAbs = absUnknown2;
|
|
2450
|
+
caseThrowsAbs = neverAbs;
|
|
2767
2451
|
} else {
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
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
|
-
|
|
2781
|
-
const throwsTv = caseThrows ?? fullResult.throws;
|
|
2477
|
+
if (!caseThrowsAbs) caseThrowsAbs = neverAbs;
|
|
2782
2478
|
const caseEntry = {
|
|
2783
2479
|
name: directive.name,
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
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
|
-
|
|
2488
|
+
attachAbsToIntension(caseEntry, caseAbs, fn.name);
|
|
2793
2489
|
analysis.cases.push(caseEntry);
|
|
2794
2490
|
if (directive.commentLine) {
|
|
2795
|
-
const hasThrow =
|
|
2796
|
-
const resultStr =
|
|
2797
|
-
const throwStr = hasThrow ? `throws ${
|
|
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 =
|
|
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 ${
|
|
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 (
|
|
2817
|
-
const throwRange =
|
|
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: ${
|
|
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 >
|
|
2840
|
-
analysis.
|
|
2841
|
-
|
|
2842
|
-
|
|
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
|
|
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.
|
|
2941
|
-
if (r.
|
|
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.
|
|
2950
|
-
absRaw = tryEvalAbsRaw(source, candidate.name, rec.
|
|
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
|
-
|
|
2953
|
-
|
|
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
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
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.
|
|
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
|
|
2673
|
+
const widenedArgsAbs = Array.from(
|
|
2972
2674
|
{ length: paramCount },
|
|
2973
2675
|
(_, i) => (
|
|
2974
2676
|
// 缺参按真实 JS 语义 widen 成 undefined 而非 unknown——可选参守卫
|
|
2975
2677
|
// (target || [] 等)对 unknown 全塌,对 undefined 正常走默认分支
|
|
2976
|
-
|
|
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
|
-
|
|
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
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
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
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
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.
|
|
3022
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2745
|
+
if (bEntryFull?.throws) entryThrowsAbs = bEntryFull.throws;
|
|
3050
2746
|
}
|
|
3051
2747
|
}
|
|
3052
|
-
if (!
|
|
2748
|
+
if (!entryAbs) {
|
|
3053
2749
|
if (bHostedEval) {
|
|
3054
|
-
|
|
3055
|
-
|
|
2750
|
+
entryAbs = absUnknown2;
|
|
2751
|
+
entryThrowsAbs = neverAbs;
|
|
3056
2752
|
} else {
|
|
3057
|
-
const absEntry = tryEvalEntryAbs(source, candidate.analysis.name,
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
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
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
throwLoc: entryLoc
|
|
2765
|
+
argAbs: argAbsEntry,
|
|
2766
|
+
abs: entryAbs,
|
|
2767
|
+
throwsAbs: entryThrowsAbs
|
|
3070
2768
|
};
|
|
3071
2769
|
tryAttachIntension(caseResult, source, candidate.analysis.name);
|
|
3072
|
-
|
|
2770
|
+
attachAbsToIntension(caseResult, entryAbs, candidate.analysis.name);
|
|
3073
2771
|
candidate.analysis.cases.push(caseResult);
|
|
3074
2772
|
candidate.analysis.entryOnly = true;
|
|
3075
|
-
candidate.analysis.
|
|
2773
|
+
candidate.analysis.combinedAbs = entryAbs;
|
|
3076
2774
|
}
|
|
3077
2775
|
if (!bHostedEval) {
|
|
3078
|
-
buildNodeTypeMap(ast, globalEnv,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
2831
|
+
setAbs(env.lookup(node.name));
|
|
3159
2832
|
}
|
|
3160
2833
|
}
|
|
3161
2834
|
if (node.type === "NumericLiteral") {
|
|
3162
|
-
|
|
2835
|
+
setAbs(numLit(node.value));
|
|
3163
2836
|
}
|
|
3164
2837
|
if (node.type === "StringLiteral") {
|
|
3165
|
-
|
|
2838
|
+
setAbs(strLit(node.value));
|
|
3166
2839
|
}
|
|
3167
2840
|
if (node.type === "BooleanLiteral") {
|
|
3168
|
-
|
|
2841
|
+
setAbs(boolLit(node.value));
|
|
3169
2842
|
}
|
|
3170
2843
|
if (node.type === "NullLiteral") {
|
|
3171
|
-
|
|
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(
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
if (
|
|
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
|
-
|
|
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
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
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 (
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
const
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
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
|
|
3002
|
+
const absArgs = args;
|
|
3315
3003
|
if (filePath) {
|
|
3316
|
-
const viaB =
|
|
3317
|
-
if (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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
3106
|
+
formatAbs as formatAbs5,
|
|
3375
3107
|
formatAbsMultiline as formatAbsMultiline2,
|
|
3376
|
-
|
|
3108
|
+
formatShape as formatShape3,
|
|
3377
3109
|
collectAbsNodeTypes,
|
|
3378
3110
|
findAbsAtPosition,
|
|
3379
|
-
|
|
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
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
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
|
|
3419
|
-
if (
|
|
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
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
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
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
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
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
return
|
|
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 =
|
|
3238
|
+
gAbs = formatAbs5(g.symbolic);
|
|
3486
3239
|
gMulti = formatAbsMultiline2(g.symbolic, fnName);
|
|
3487
3240
|
}
|
|
3488
3241
|
} catch {
|
|
3489
3242
|
}
|
|
3490
3243
|
}
|
|
3491
|
-
const attachIntension = (
|
|
3492
|
-
if (!gDisplay) return
|
|
3493
|
-
if (!
|
|
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
|
-
...
|
|
3250
|
+
...info,
|
|
3498
3251
|
intension: gDisplay,
|
|
3499
3252
|
// 外延侧已有更准 Abs 时保留;否则用 symbolic 兜底
|
|
3500
|
-
abs:
|
|
3501
|
-
absMultiline:
|
|
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 (
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
const
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
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
|
|
3541
|
-
|
|
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 =
|
|
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 =
|
|
3329
|
+
const absLine = formatAbs5(absAt);
|
|
3580
3330
|
const absMulti = formatAbsMultiline2(absAt, void 0);
|
|
3581
|
-
|
|
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
|
|
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
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
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
|
-
|
|
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
|
|
3745
|
-
|
|
3746
|
-
|
|
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
|
-
|
|
3753
|
-
return table ? Object.keys(table) : [];
|
|
3456
|
+
return builtinProtoMemberNames(className);
|
|
3754
3457
|
}
|
|
3755
|
-
function describeMember(label,
|
|
3756
|
-
if (
|
|
3757
|
-
const
|
|
3758
|
-
if (
|
|
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,
|
|
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,
|
|
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
|
|
3801
|
-
|
|
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) =>
|
|
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 {
|
|
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.
|
|
3873
|
-
result:
|
|
3874
|
-
throws: c.
|
|
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.
|
|
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
|
|
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) =>
|
|
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 {
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
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
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
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
|
|
4197
|
-
|
|
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
|
|
4217
|
-
|
|
4218
|
-
return simplifyUnion2(tv.members.map(widenLiteralToBase));
|
|
3951
|
+
function caseResultAbs(c) {
|
|
3952
|
+
return c.abs;
|
|
4219
3953
|
}
|
|
4220
|
-
function
|
|
4221
|
-
const
|
|
4222
|
-
|
|
4223
|
-
|
|
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 =
|
|
3960
|
+
const widened = s.elements.map(widenParamAbs);
|
|
4233
3961
|
const first = widened[0];
|
|
4234
|
-
if (first && widened.every((el) =>
|
|
4235
|
-
return
|
|
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
|
|
4238
|
-
}
|
|
4239
|
-
case "
|
|
4240
|
-
return
|
|
4241
|
-
case "
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
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
|
|
4001
|
+
return a;
|
|
4251
4002
|
}
|
|
4252
4003
|
}
|
|
4253
|
-
function
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
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
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
}
|
|
4271
|
-
return
|
|
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.
|
|
4275
|
-
const minArity = Math.min(...fn.cases.map((c) => c.
|
|
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 =
|
|
4282
|
-
const
|
|
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
|
|
4296
|
-
const returnType =
|
|
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.
|
|
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.
|
|
4306
|
-
lines.push(` * Case: ${c.name} (${argsStr}) => ${
|
|
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
|
-
|
|
4104
|
+
const retAbs = fn.combinedAbs;
|
|
4105
|
+
if (retAbs) {
|
|
4319
4106
|
return [
|
|
4320
|
-
`export declare function ${fn.name}(...args: unknown[]): ${
|
|
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
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
}
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
case "
|
|
4353
|
-
return
|
|
4354
|
-
case "
|
|
4355
|
-
const entries = Object.entries(
|
|
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 "
|
|
4359
|
-
return `z.array(${
|
|
4146
|
+
case "arr":
|
|
4147
|
+
return `z.array(${absToZodSchema(a.shape.element)})`;
|
|
4360
4148
|
case "tuple": {
|
|
4361
|
-
const inner =
|
|
4149
|
+
const inner = a.shape.elements.map(absToZodSchema).join(", ");
|
|
4362
4150
|
return `z.tuple([${inner}])`;
|
|
4363
4151
|
}
|
|
4364
|
-
case "
|
|
4152
|
+
case "fn":
|
|
4365
4153
|
return "z.function()";
|
|
4366
|
-
case "
|
|
4367
|
-
return `z.promise(${
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
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
|
|
4396
|
-
|
|
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(
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
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 "
|
|
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
|
|
4492
|
-
const
|
|
4493
|
-
if (
|
|
4494
|
-
parts.push(
|
|
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 "
|
|
4499
|
-
const el = serializeCaseArg(
|
|
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
|
|
4505
|
-
const
|
|
4506
|
-
if (
|
|
4507
|
-
parts.push(
|
|
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 "
|
|
4259
|
+
case "obj": {
|
|
4512
4260
|
const parts = [];
|
|
4513
|
-
for (const [key,
|
|
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,
|
|
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
|
|
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.
|
|
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
|
|
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(
|
|
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
|
|
4840
|
-
if (
|
|
4841
|
-
|
|
4842
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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.
|
|
5056
|
-
if (a
|
|
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.
|
|
5067
|
-
retAbs.push(c.abs
|
|
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
|
};
|