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