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