@nudojs/core 1.2.2 → 1.4.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/dist/{check-report-0B9Z66n0.d.ts → check-report-Ci1dDysO.d.ts} +102 -25
- package/dist/{chunk-TOURNNEA.js → chunk-7QUVTEYZ.js} +1154 -254
- package/dist/{chunk-4V3CRVSD.js → chunk-LFCF6BLE.js} +3486 -2107
- package/dist/exec.d.ts +590 -3
- package/dist/exec.js +21 -1
- package/dist/index.d.ts +127 -132
- package/dist/index.js +417 -269
- package/dist/internal.d.ts +111 -7
- package/dist/internal.js +102 -28
- package/dist/run-WJfj477N.d.ts +769 -0
- package/package.json +1 -1
- package/dist/hof-types-DqDpAxfh.d.ts +0 -382
- package/dist/index-BNiZvVHw.d.ts +0 -739
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
getFnImpl,
|
|
22
22
|
getSlot,
|
|
23
23
|
gt,
|
|
24
|
+
identBoundaryRegex,
|
|
24
25
|
joinAbs,
|
|
25
26
|
le,
|
|
26
27
|
lit,
|
|
@@ -36,12 +37,704 @@ import {
|
|
|
36
37
|
ptypeof,
|
|
37
38
|
runTranspiledOptionsMemoKey,
|
|
38
39
|
snapshotAbs,
|
|
40
|
+
stripStringsKeepComments,
|
|
39
41
|
termToString,
|
|
40
42
|
tryRunTranspiled,
|
|
41
43
|
unknown,
|
|
42
44
|
v,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
withEvalBudgetOverride,
|
|
46
|
+
withExecPhi,
|
|
47
|
+
withFnBudgetOverride
|
|
48
|
+
} from "./chunk-LFCF6BLE.js";
|
|
49
|
+
|
|
50
|
+
// src/algebra/directive-scan.ts
|
|
51
|
+
function cleanDirectiveLine(raw, kind) {
|
|
52
|
+
let s = raw;
|
|
53
|
+
if (kind === "block") {
|
|
54
|
+
s = s.replace(/^\s*\*\s?/, "");
|
|
55
|
+
} else {
|
|
56
|
+
s = s.replace(/^\//, "");
|
|
57
|
+
}
|
|
58
|
+
return s.trim();
|
|
59
|
+
}
|
|
60
|
+
function commentTextToLines(text, kind) {
|
|
61
|
+
return text.split("\n").map((line) => cleanDirectiveLine(line, kind));
|
|
62
|
+
}
|
|
63
|
+
function commentKind(c) {
|
|
64
|
+
return c.type === "CommentLine" ? "line" : "block";
|
|
65
|
+
}
|
|
66
|
+
function buildScope(name, node, wrapper, comments, wrapperComments) {
|
|
67
|
+
const seen = /* @__PURE__ */ new Set();
|
|
68
|
+
const texts = [];
|
|
69
|
+
const startLines = [];
|
|
70
|
+
const lines = [];
|
|
71
|
+
for (const c of [...wrapperComments ?? [], ...comments ?? []]) {
|
|
72
|
+
if (seen.has(c)) continue;
|
|
73
|
+
seen.add(c);
|
|
74
|
+
texts.push(c.value);
|
|
75
|
+
startLines.push(c.loc?.start?.line ?? 0);
|
|
76
|
+
for (const line of commentTextToLines(c.value, commentKind(c))) lines.push(line);
|
|
77
|
+
}
|
|
78
|
+
const loc = node.loc;
|
|
79
|
+
return {
|
|
80
|
+
name,
|
|
81
|
+
node,
|
|
82
|
+
commentTexts: texts,
|
|
83
|
+
commentLines: lines,
|
|
84
|
+
commentStartLines: startLines,
|
|
85
|
+
startLine: loc?.start?.line ?? 0
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function isFnExpr(n) {
|
|
89
|
+
return !!n && (n.type === "ArrowFunctionExpression" || n.type === "FunctionExpression" || n.type === "FunctionDeclaration");
|
|
90
|
+
}
|
|
91
|
+
function keyNameOf(key) {
|
|
92
|
+
if (!key) return void 0;
|
|
93
|
+
const k = key;
|
|
94
|
+
if (k.type === "Identifier") return k.name;
|
|
95
|
+
if (k.type === "StringLiteral" || k.type === "NumericLiteral") return String(k.value);
|
|
96
|
+
return void 0;
|
|
97
|
+
}
|
|
98
|
+
function listFnDirectiveScopes(file) {
|
|
99
|
+
const out = [];
|
|
100
|
+
const pushNamedFn = (name, fnNode, commentNode, wrapper) => {
|
|
101
|
+
out.push(
|
|
102
|
+
buildScope(
|
|
103
|
+
name,
|
|
104
|
+
fnNode,
|
|
105
|
+
wrapper,
|
|
106
|
+
commentNode?.leadingComments,
|
|
107
|
+
wrapper?.leadingComments
|
|
108
|
+
)
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
const visit2 = (node, ctx) => {
|
|
112
|
+
if (!node || typeof node !== "object") return;
|
|
113
|
+
const n = node;
|
|
114
|
+
switch (n.type) {
|
|
115
|
+
case "File":
|
|
116
|
+
visit2(n.program, ctx);
|
|
117
|
+
return;
|
|
118
|
+
case "Program": {
|
|
119
|
+
for (const s of n.body ?? []) visit2(s, ctx);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
case "ExportNamedDeclaration":
|
|
123
|
+
case "ExportDefaultDeclaration": {
|
|
124
|
+
const decl = n.declaration;
|
|
125
|
+
if (!decl) return;
|
|
126
|
+
const wrapCtx = { ...ctx, wrapperNode: node };
|
|
127
|
+
visitWithWrapper(decl, node, wrapCtx);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
case "FunctionDeclaration": {
|
|
131
|
+
const name = n.id?.name ?? ctx.fallbackName ?? "<anonymous>";
|
|
132
|
+
pushNamedFn(name, node, node, ctx.wrapperNode);
|
|
133
|
+
visit2(n.body, { owner: name });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
case "FunctionExpression": {
|
|
137
|
+
const name = n.id?.name ?? ctx.fallbackName ?? "<anonymous>";
|
|
138
|
+
pushNamedFn(name, node, node, ctx.wrapperNode);
|
|
139
|
+
visit2(n.body, { owner: name });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
case "ArrowFunctionExpression": {
|
|
143
|
+
const name = ctx.fallbackName ?? "<anonymous>";
|
|
144
|
+
pushNamedFn(name, node, ctx.commentHost ?? node, ctx.wrapperNode);
|
|
145
|
+
visit2(n.body, { owner: name });
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
case "VariableDeclaration": {
|
|
149
|
+
for (const d of n.declarations ?? []) {
|
|
150
|
+
const decl = d;
|
|
151
|
+
const idName = decl.id?.type === "Identifier" ? decl.id.name : void 0;
|
|
152
|
+
if (isFnExpr(decl.init)) {
|
|
153
|
+
const fn2 = decl.init;
|
|
154
|
+
const scope = buildScope(
|
|
155
|
+
idName ?? "<anonymous>",
|
|
156
|
+
fn2,
|
|
157
|
+
ctx.wrapperNode,
|
|
158
|
+
node.leadingComments,
|
|
159
|
+
ctx.wrapperNode?.leadingComments
|
|
160
|
+
);
|
|
161
|
+
out.push(scope);
|
|
162
|
+
visit2(fn2, { owner: idName });
|
|
163
|
+
} else {
|
|
164
|
+
visit2(decl.init, { owner: idName, commentHost: node, wrapperNode: ctx.wrapperNode });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
case "ClassDeclaration":
|
|
170
|
+
case "ClassExpression": {
|
|
171
|
+
const cname = n.id?.name ?? ctx.fallbackName ?? "<anonymous>";
|
|
172
|
+
if (n.type === "ClassDeclaration") {
|
|
173
|
+
}
|
|
174
|
+
const body = n.body?.body ?? [];
|
|
175
|
+
for (const m of body) visit2(m, { owner: cname });
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
case "ClassMethod":
|
|
179
|
+
case "ClassPrivateMethod":
|
|
180
|
+
case "ObjectMethod": {
|
|
181
|
+
const key = keyNameOf(n.key);
|
|
182
|
+
const owner = ctx.owner;
|
|
183
|
+
const name = key ? owner ? `${owner}.${key}` : key : "<anonymous>";
|
|
184
|
+
pushNamedFn(name, node, node, void 0);
|
|
185
|
+
visit2(n.body, { owner: name });
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
case "ObjectProperty":
|
|
189
|
+
case "ClassProperty":
|
|
190
|
+
case "ClassPrivateProperty": {
|
|
191
|
+
const key = keyNameOf(n.key);
|
|
192
|
+
const val = n.value ?? n.init;
|
|
193
|
+
if (isFnExpr(val)) {
|
|
194
|
+
const owner = ctx.owner;
|
|
195
|
+
const name = key ? owner ? `${owner}.${key}` : key : "<anonymous>";
|
|
196
|
+
out.push(
|
|
197
|
+
buildScope(
|
|
198
|
+
name,
|
|
199
|
+
val,
|
|
200
|
+
void 0,
|
|
201
|
+
val.leadingComments ?? node.leadingComments,
|
|
202
|
+
void 0
|
|
203
|
+
)
|
|
204
|
+
);
|
|
205
|
+
visit2(val, { owner: name });
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
visit2(val, ctx);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
case "ReturnStatement":
|
|
212
|
+
case "ThrowStatement":
|
|
213
|
+
case "AwaitExpression":
|
|
214
|
+
case "SpreadElement":
|
|
215
|
+
case "RestElement":
|
|
216
|
+
visit2(n.argument, ctx);
|
|
217
|
+
return;
|
|
218
|
+
case "UnaryExpression":
|
|
219
|
+
case "UpdateExpression":
|
|
220
|
+
visit2(n.argument, ctx);
|
|
221
|
+
return;
|
|
222
|
+
case "BinaryExpression":
|
|
223
|
+
case "LogicalExpression":
|
|
224
|
+
case "AssignmentExpression":
|
|
225
|
+
visit2(n.left, ctx);
|
|
226
|
+
visit2(n.right, ctx);
|
|
227
|
+
return;
|
|
228
|
+
case "MemberExpression":
|
|
229
|
+
case "OptionalMemberExpression":
|
|
230
|
+
visit2(n.object, ctx);
|
|
231
|
+
visit2(n.property, ctx);
|
|
232
|
+
return;
|
|
233
|
+
case "CallExpression":
|
|
234
|
+
case "OptionalCallExpression":
|
|
235
|
+
case "NewExpression": {
|
|
236
|
+
visit2(n.callee, ctx);
|
|
237
|
+
for (const a of n.arguments ?? []) visit2(a, ctx);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
case "ConditionalExpression":
|
|
241
|
+
visit2(n.test, ctx);
|
|
242
|
+
visit2(n.consequent, ctx);
|
|
243
|
+
visit2(n.alternate, ctx);
|
|
244
|
+
return;
|
|
245
|
+
case "ArrayExpression": {
|
|
246
|
+
for (const e of n.elements ?? []) visit2(e, ctx);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
case "ObjectExpression": {
|
|
250
|
+
for (const p of n.properties ?? []) visit2(p, ctx);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
case "SequenceExpression": {
|
|
254
|
+
for (const e of n.expressions ?? []) visit2(e, ctx);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
case "ParenthesizedExpression":
|
|
258
|
+
visit2(n.expression, ctx);
|
|
259
|
+
return;
|
|
260
|
+
case "ExpressionStatement":
|
|
261
|
+
visit2(n.expression, ctx);
|
|
262
|
+
return;
|
|
263
|
+
case "BlockStatement": {
|
|
264
|
+
for (const s of n.body ?? []) visit2(s, ctx);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
case "IfStatement":
|
|
268
|
+
visit2(n.test, ctx);
|
|
269
|
+
visit2(n.consequent, ctx);
|
|
270
|
+
visit2(n.alternate, ctx);
|
|
271
|
+
return;
|
|
272
|
+
case "ForStatement":
|
|
273
|
+
visit2(n.init, ctx);
|
|
274
|
+
visit2(n.test, ctx);
|
|
275
|
+
visit2(n.update, ctx);
|
|
276
|
+
visit2(n.body, ctx);
|
|
277
|
+
return;
|
|
278
|
+
case "ForInStatement":
|
|
279
|
+
case "ForOfStatement":
|
|
280
|
+
visit2(n.left, ctx);
|
|
281
|
+
visit2(n.right, ctx);
|
|
282
|
+
visit2(n.body, ctx);
|
|
283
|
+
return;
|
|
284
|
+
case "WhileStatement":
|
|
285
|
+
case "DoWhileStatement":
|
|
286
|
+
visit2(n.test, ctx);
|
|
287
|
+
visit2(n.body, ctx);
|
|
288
|
+
return;
|
|
289
|
+
case "TryStatement":
|
|
290
|
+
visit2(n.block, ctx);
|
|
291
|
+
visit2(n.handler, ctx);
|
|
292
|
+
visit2(n.finalizer, ctx);
|
|
293
|
+
return;
|
|
294
|
+
case "CatchClause":
|
|
295
|
+
visit2(n.body, ctx);
|
|
296
|
+
return;
|
|
297
|
+
case "SwitchStatement":
|
|
298
|
+
visit2(n.discriminant, ctx);
|
|
299
|
+
for (const c of n.cases ?? []) visit2(c, ctx);
|
|
300
|
+
return;
|
|
301
|
+
case "SwitchCase":
|
|
302
|
+
visit2(n.test, ctx);
|
|
303
|
+
for (const s of n.consequent ?? []) visit2(s, ctx);
|
|
304
|
+
return;
|
|
305
|
+
case "LabeledStatement":
|
|
306
|
+
visit2(n.body, ctx);
|
|
307
|
+
return;
|
|
308
|
+
default: {
|
|
309
|
+
for (const key of [
|
|
310
|
+
"body",
|
|
311
|
+
"expression",
|
|
312
|
+
"init",
|
|
313
|
+
"value",
|
|
314
|
+
"argument",
|
|
315
|
+
"declaration",
|
|
316
|
+
"left",
|
|
317
|
+
"right",
|
|
318
|
+
"test",
|
|
319
|
+
"consequent",
|
|
320
|
+
"alternate",
|
|
321
|
+
"block",
|
|
322
|
+
"handler",
|
|
323
|
+
"finalizer",
|
|
324
|
+
"callee",
|
|
325
|
+
"object",
|
|
326
|
+
"property",
|
|
327
|
+
"argument"
|
|
328
|
+
]) {
|
|
329
|
+
const v2 = n[key];
|
|
330
|
+
if (Array.isArray(v2)) v2.forEach((x) => visit2(x, ctx));
|
|
331
|
+
else if (v2 && typeof v2 === "object" && "type" in v2) visit2(v2, ctx);
|
|
332
|
+
}
|
|
333
|
+
for (const key of ["declarations", "properties", "elements", "arguments", "cases", "params"]) {
|
|
334
|
+
const v2 = n[key];
|
|
335
|
+
if (Array.isArray(v2)) v2.forEach((x) => visit2(x, ctx));
|
|
336
|
+
}
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
const visitWithWrapper = (decl, wrapper, ctx) => {
|
|
342
|
+
const d = decl;
|
|
343
|
+
if (d.type === "FunctionDeclaration" || d.type === "FunctionExpression") {
|
|
344
|
+
const name = d.id?.name ?? "default";
|
|
345
|
+
pushNamedFn(name, decl, decl, wrapper);
|
|
346
|
+
visit2(d.body, { owner: name });
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (d.type === "ClassDeclaration" || d.type === "ClassExpression") {
|
|
350
|
+
const cname = d.id?.name ?? "<anonymous>";
|
|
351
|
+
const body = d.body?.body ?? [];
|
|
352
|
+
for (const m of body) visit2(m, { owner: cname });
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (d.type === "VariableDeclaration") {
|
|
356
|
+
for (const raw of d.declarations ?? []) {
|
|
357
|
+
const vd = raw;
|
|
358
|
+
const idName = vd.id?.type === "Identifier" ? vd.id.name : void 0;
|
|
359
|
+
if (isFnExpr(vd.init)) {
|
|
360
|
+
const fn2 = vd.init;
|
|
361
|
+
out.push(
|
|
362
|
+
buildScope(
|
|
363
|
+
idName ?? "default",
|
|
364
|
+
fn2,
|
|
365
|
+
wrapper,
|
|
366
|
+
d.leadingComments,
|
|
367
|
+
wrapper.leadingComments
|
|
368
|
+
)
|
|
369
|
+
);
|
|
370
|
+
visit2(fn2, { owner: idName });
|
|
371
|
+
} else {
|
|
372
|
+
visit2(vd.init, {
|
|
373
|
+
owner: idName,
|
|
374
|
+
commentHost: decl,
|
|
375
|
+
wrapperNode: wrapper
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (d.type === "ArrowFunctionExpression" || d.type === "FunctionExpression") {
|
|
382
|
+
const name = "default";
|
|
383
|
+
out.push(
|
|
384
|
+
buildScope(
|
|
385
|
+
name,
|
|
386
|
+
decl,
|
|
387
|
+
wrapper,
|
|
388
|
+
d.leadingComments,
|
|
389
|
+
wrapper.leadingComments
|
|
390
|
+
)
|
|
391
|
+
);
|
|
392
|
+
visit2(d.body, { owner: name });
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
visit2(decl, { ...ctx, wrapperNode: wrapper });
|
|
396
|
+
};
|
|
397
|
+
for (const stmt of file.program.body) {
|
|
398
|
+
visit2(stmt, {});
|
|
399
|
+
}
|
|
400
|
+
return out;
|
|
401
|
+
}
|
|
402
|
+
function findFnDirectiveScope(file, fnName) {
|
|
403
|
+
return listFnDirectiveScopes(file).find((s) => s.name === fnName);
|
|
404
|
+
}
|
|
405
|
+
function fnDirectiveCommentLines(source, fnName) {
|
|
406
|
+
let file;
|
|
407
|
+
try {
|
|
408
|
+
file = parseSource(source);
|
|
409
|
+
} catch {
|
|
410
|
+
return [];
|
|
411
|
+
}
|
|
412
|
+
return findFnDirectiveScope(file, fnName)?.commentLines ?? [];
|
|
413
|
+
}
|
|
414
|
+
var isPathLikeEnv = (s) => /^[^\s]+$/.test(s) && (s.startsWith("./") || s.startsWith("../") || s.startsWith("/") || /\.(ts|js|mjs|cjs|tsx|jsx)$/.test(s));
|
|
415
|
+
function parseEnvPayload(payload) {
|
|
416
|
+
return payload.split(",").map((e) => e.trim().replace(/^['"]|['"]$/g, "")).filter((e) => e && (/^\w+$/.test(e) || isPathLikeEnv(e)));
|
|
417
|
+
}
|
|
418
|
+
function parseMockModulePayload(payload) {
|
|
419
|
+
const partial2 = payload.match(/^\s*"([^"]+)"\s*\{([^}]+)\}\s*from\s*"([^"]+)"\s*$/);
|
|
420
|
+
if (partial2) {
|
|
421
|
+
const names = partial2[2].split(",").map((n) => n.trim()).filter(Boolean);
|
|
422
|
+
return { source: partial2[1], names, fromPath: partial2[3] };
|
|
423
|
+
}
|
|
424
|
+
const full = payload.match(/^\s*"([^"]+)"\s+from\s*"([^"]+)"\s*$/);
|
|
425
|
+
if (full) {
|
|
426
|
+
return { source: full[1], fromPath: full[2] };
|
|
427
|
+
}
|
|
428
|
+
return void 0;
|
|
429
|
+
}
|
|
430
|
+
function parseNudoImportPayload(payload) {
|
|
431
|
+
const s = payload.trim();
|
|
432
|
+
if (s === "") return void 0;
|
|
433
|
+
const named = s.match(/^\{([^}]+)\}\s*from\s*["']([^"']+)["']$/);
|
|
434
|
+
if (named) {
|
|
435
|
+
const names = named[1].split("\n").map((line) => line.replace(/^\s*\/\/\/?\s?/, "").trim()).join("\n").split(",").map((x) => x.trim()).filter(Boolean).map((x) => x.split(/\s+as\s+/).pop().trim());
|
|
436
|
+
return { names, spec: named[2] };
|
|
437
|
+
}
|
|
438
|
+
const ns = s.match(/^\*\s+as\s+(\w+)\s+from\s*["']([^"']+)["']$/);
|
|
439
|
+
if (ns) {
|
|
440
|
+
return { names: [`*${ns[1]}`], spec: ns[2] };
|
|
441
|
+
}
|
|
442
|
+
if (/^[\w$]+\s+from\b/.test(s) || /^[\w$]+\s*,/.test(s)) return "malformed-default";
|
|
443
|
+
return "malformed";
|
|
444
|
+
}
|
|
445
|
+
var LINE_COMMENT_PREFIX = "(?:^|[^/:])\\/\\/\\/?";
|
|
446
|
+
function fileDirectiveVisibleText(source) {
|
|
447
|
+
return stripStringsKeepComments(source).replace(/\/\*[\s\S]*?\*\//g, " ");
|
|
448
|
+
}
|
|
449
|
+
function extractFileEnvNames(source) {
|
|
450
|
+
const text = fileDirectiveVisibleText(source);
|
|
451
|
+
const re = new RegExp(`${LINE_COMMENT_PREFIX}\\s*@nudo:env\\s+([^\\n*]+)`, "g");
|
|
452
|
+
const out = [];
|
|
453
|
+
let m;
|
|
454
|
+
while (m = re.exec(text)) {
|
|
455
|
+
out.push(...parseEnvPayload(m[1]));
|
|
456
|
+
}
|
|
457
|
+
return out;
|
|
458
|
+
}
|
|
459
|
+
function extractMockModuleRecords(source) {
|
|
460
|
+
const text = fileDirectiveVisibleText(source);
|
|
461
|
+
const re = new RegExp(
|
|
462
|
+
`${LINE_COMMENT_PREFIX}\\s*@nudo:mock-module\\s+([^\\n]+)`,
|
|
463
|
+
"g"
|
|
464
|
+
);
|
|
465
|
+
const out = [];
|
|
466
|
+
let m;
|
|
467
|
+
while (m = re.exec(text)) {
|
|
468
|
+
const rec = parseMockModulePayload(m[1]);
|
|
469
|
+
if (rec) out.push(rec);
|
|
470
|
+
}
|
|
471
|
+
return out;
|
|
472
|
+
}
|
|
473
|
+
function directiveCommentLinesRaw(source) {
|
|
474
|
+
const src = stripStringsKeepComments(source);
|
|
475
|
+
const out = [];
|
|
476
|
+
const lineRe = /(?:^|[^/:])\/\/\/?([^\n]*)/g;
|
|
477
|
+
let m;
|
|
478
|
+
while (m = lineRe.exec(src)) {
|
|
479
|
+
out.push(cleanDirectiveLine(m[1], "line"));
|
|
480
|
+
}
|
|
481
|
+
const blockRe = /\/\*([\s\S]*?)\*\//g;
|
|
482
|
+
while (m = blockRe.exec(src)) {
|
|
483
|
+
out.push(...commentTextToLines(m[1], "block"));
|
|
484
|
+
}
|
|
485
|
+
return out;
|
|
486
|
+
}
|
|
487
|
+
function nudoImportPayloads(source) {
|
|
488
|
+
const lines = directiveCommentLinesRaw(source);
|
|
489
|
+
const out = [];
|
|
490
|
+
for (let i = 0; i < lines.length; i++) {
|
|
491
|
+
const line = lines[i];
|
|
492
|
+
if (!/^@nudo:import\s/.test(line)) continue;
|
|
493
|
+
let payload = line.replace(/^@nudo:import\s+/, "");
|
|
494
|
+
if (payload.startsWith("{")) {
|
|
495
|
+
let depth = 0;
|
|
496
|
+
for (const ch of payload) {
|
|
497
|
+
if (ch === "{") depth++;
|
|
498
|
+
else if (ch === "}") depth--;
|
|
499
|
+
}
|
|
500
|
+
while (depth > 0 && i + 1 < lines.length) {
|
|
501
|
+
i += 1;
|
|
502
|
+
const cont = lines[i];
|
|
503
|
+
if (/^@nudo:/.test(cont)) {
|
|
504
|
+
i -= 1;
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
payload += `
|
|
508
|
+
${cont}`;
|
|
509
|
+
for (const ch of cont) {
|
|
510
|
+
if (ch === "{") depth++;
|
|
511
|
+
else if (ch === "}") depth--;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
out.push(payload);
|
|
516
|
+
}
|
|
517
|
+
return out;
|
|
518
|
+
}
|
|
519
|
+
function extractNudoImportRecords(source) {
|
|
520
|
+
const out = [];
|
|
521
|
+
for (const payload of nudoImportPayloads(source)) {
|
|
522
|
+
const rec = parseNudoImportPayload(payload);
|
|
523
|
+
if (rec && rec !== "malformed" && rec !== "malformed-default") out.push(rec);
|
|
524
|
+
}
|
|
525
|
+
return out;
|
|
526
|
+
}
|
|
527
|
+
function scanMalformedNudoImports(source) {
|
|
528
|
+
const out = [];
|
|
529
|
+
for (const payload of nudoImportPayloads(source)) {
|
|
530
|
+
const rec = parseNudoImportPayload(payload);
|
|
531
|
+
if (rec === "malformed-default" || rec === "malformed") out.push(rec);
|
|
532
|
+
}
|
|
533
|
+
return out;
|
|
534
|
+
}
|
|
535
|
+
function extractBalancedParens(text, startIdx) {
|
|
536
|
+
if (text[startIdx] !== "(") return null;
|
|
537
|
+
let depth = 0;
|
|
538
|
+
let inString = null;
|
|
539
|
+
for (let i = startIdx; i < text.length; i++) {
|
|
540
|
+
const ch = text[i];
|
|
541
|
+
if (inString) {
|
|
542
|
+
if (ch === inString) inString = null;
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
if (ch === '"' || ch === "'") {
|
|
546
|
+
inString = ch;
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
if (ch === "(") depth++;
|
|
550
|
+
if (ch === ")") depth--;
|
|
551
|
+
if (depth === 0) return text.slice(startIdx + 1, i);
|
|
552
|
+
}
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
var CASE_NAME_REGEX = /(?:^|\n)[ \t]*(?:\*[ \t]*|\/\/\/?\s*)?@nudo:case\s+"([^"]+)"\s*\(/g;
|
|
556
|
+
function extractCaseExpectedExpr(text, afterParen) {
|
|
557
|
+
const lines = text.slice(afterParen).split("\n");
|
|
558
|
+
for (let i = 0; i < lines.length; i++) {
|
|
559
|
+
let line = lines[i];
|
|
560
|
+
if (i > 0) line = line.replace(/^\s*\*\s?/, "").replace(/^\s*\/\/\/?\s?/, "");
|
|
561
|
+
if (/^\s*@nudo:/.test(line)) return void 0;
|
|
562
|
+
const arrow = line.match(/^\s*=>\s*(\S.*)$/);
|
|
563
|
+
if (arrow) return arrow[1].trim();
|
|
564
|
+
if (line.trim() !== "") return void 0;
|
|
565
|
+
}
|
|
566
|
+
return void 0;
|
|
567
|
+
}
|
|
568
|
+
function scanCaseTags(text) {
|
|
569
|
+
const caseArgSpans = [];
|
|
570
|
+
const out = [];
|
|
571
|
+
CASE_NAME_REGEX.lastIndex = 0;
|
|
572
|
+
let m;
|
|
573
|
+
while ((m = CASE_NAME_REGEX.exec(text)) !== null) {
|
|
574
|
+
const parenStart = m.index + m[0].length - 1;
|
|
575
|
+
const argsStr = extractBalancedParens(text, parenStart);
|
|
576
|
+
if (argsStr === null) continue;
|
|
577
|
+
const afterParen = parenStart + argsStr.length + 2;
|
|
578
|
+
if (caseArgSpans.some(([s, e]) => m.index >= s && m.index < e)) continue;
|
|
579
|
+
caseArgSpans.push([m.index, afterParen]);
|
|
580
|
+
const tagOffset = m.index + m[0].indexOf("@nudo:case");
|
|
581
|
+
const expectedText = extractCaseExpectedExpr(text, afterParen);
|
|
582
|
+
out.push({ name: m[1], argsText: argsStr, expectedText, tagOffset });
|
|
583
|
+
}
|
|
584
|
+
return out;
|
|
585
|
+
}
|
|
586
|
+
function scanCaseArgSpans(text) {
|
|
587
|
+
const spans = [];
|
|
588
|
+
CASE_NAME_REGEX.lastIndex = 0;
|
|
589
|
+
let m;
|
|
590
|
+
while ((m = CASE_NAME_REGEX.exec(text)) !== null) {
|
|
591
|
+
const parenStart = m.index + m[0].length - 1;
|
|
592
|
+
const argsStr = extractBalancedParens(text, parenStart);
|
|
593
|
+
if (argsStr === null) continue;
|
|
594
|
+
const afterParen = parenStart + argsStr.length + 2;
|
|
595
|
+
if (spans.some(([s, e]) => m.index >= s && m.index < e)) continue;
|
|
596
|
+
spans.push([m.index, afterParen]);
|
|
597
|
+
}
|
|
598
|
+
return spans;
|
|
599
|
+
}
|
|
600
|
+
function scanContractSegments(lines) {
|
|
601
|
+
const reqs = [];
|
|
602
|
+
for (const line of lines) {
|
|
603
|
+
const m = line.match(/^@nudo:contract\s+(.+)$/);
|
|
604
|
+
if (m) reqs.push(m[1].trim().replace(/\*\/$/, "").trim());
|
|
605
|
+
}
|
|
606
|
+
return reqs;
|
|
607
|
+
}
|
|
608
|
+
function scanThrowsDecl(lines) {
|
|
609
|
+
const kinds = /* @__PURE__ */ new Set();
|
|
610
|
+
let any2 = false;
|
|
611
|
+
for (const line of lines) {
|
|
612
|
+
const th = line.match(/^@nudo:throws\s+(.+)$/i);
|
|
613
|
+
if (th) {
|
|
614
|
+
const spec = th[1].trim();
|
|
615
|
+
if (spec === "*") any2 = true;
|
|
616
|
+
else {
|
|
617
|
+
for (const k of spec.split(/[,\s|]+/).map((s) => s.trim()).filter(Boolean)) {
|
|
618
|
+
if (k === "*") any2 = true;
|
|
619
|
+
else kinds.add(k);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
const cs = line.match(/!!\s*throws(?:\s+([A-Za-z*][\w*|,\s]*))?/i);
|
|
624
|
+
if (cs) {
|
|
625
|
+
const spec = (cs[1] ?? "*").trim();
|
|
626
|
+
if (spec === "" || spec === "*") any2 = true;
|
|
627
|
+
else {
|
|
628
|
+
for (const k of spec.split(/[,\s|]+/).map((s) => s.trim()).filter(Boolean)) {
|
|
629
|
+
if (k === "*") any2 = true;
|
|
630
|
+
else kinds.add(k);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (any2) return "*";
|
|
636
|
+
if (kinds.size > 0) return [...kinds].join(",");
|
|
637
|
+
return void 0;
|
|
638
|
+
}
|
|
639
|
+
function scanBudgetDecl(lines) {
|
|
640
|
+
const out = {};
|
|
641
|
+
for (const line of lines) {
|
|
642
|
+
const bd = line.match(/^@nudo:budget\s+(.+)$/i);
|
|
643
|
+
if (!bd) continue;
|
|
644
|
+
for (const part of bd[1].split(/[,\s|]+/).map((s) => s.trim()).filter(Boolean)) {
|
|
645
|
+
const kv = part.match(/^(forks|calls|depth)\s*=\s*(\d+)$/i);
|
|
646
|
+
if (kv) {
|
|
647
|
+
const num = Number(kv[2]);
|
|
648
|
+
if (Number.isFinite(num) && num >= 1) {
|
|
649
|
+
out[kv[1].toLowerCase()] = Math.floor(num);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// src/algebra/hash-source.ts
|
|
658
|
+
var lastHashSource;
|
|
659
|
+
var lastHashOut;
|
|
660
|
+
function hashSource(s) {
|
|
661
|
+
if (s === lastHashSource && lastHashOut !== void 0) return lastHashOut;
|
|
662
|
+
let h1 = 2166136261;
|
|
663
|
+
let h2 = 2654435769;
|
|
664
|
+
for (let i = 0; i < s.length; i++) {
|
|
665
|
+
const c = s.charCodeAt(i);
|
|
666
|
+
h1 ^= c;
|
|
667
|
+
h1 = Math.imul(h1, 16777619);
|
|
668
|
+
h2 = Math.imul(h2 ^ c, 16777619);
|
|
669
|
+
}
|
|
670
|
+
lastHashSource = s;
|
|
671
|
+
lastHashOut = `${(h1 >>> 0).toString(36)}-${(h2 >>> 0).toString(36)}_${s.length.toString(36)}`;
|
|
672
|
+
return lastHashOut;
|
|
673
|
+
}
|
|
674
|
+
function resetHashSourceCache() {
|
|
675
|
+
lastHashSource = void 0;
|
|
676
|
+
lastHashOut = void 0;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// src/algebra/sidecar-path.ts
|
|
680
|
+
function normPath(p) {
|
|
681
|
+
return p.replace(/\\/g, "/");
|
|
682
|
+
}
|
|
683
|
+
function stablePathKey(p) {
|
|
684
|
+
let fp = p;
|
|
685
|
+
if (fp.startsWith("file://")) {
|
|
686
|
+
fp = fp.slice("file://".length);
|
|
687
|
+
try {
|
|
688
|
+
fp = decodeURIComponent(fp);
|
|
689
|
+
} catch {
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
fp = normPath(fp);
|
|
693
|
+
const drive = /^\/([A-Za-z]:)(.*)$/.exec(fp);
|
|
694
|
+
if (drive) fp = `${drive[1]}${drive[2]}`;
|
|
695
|
+
if (/^[A-Za-z]:\//.test(fp)) return fp[0].toLowerCase() + fp.slice(1);
|
|
696
|
+
return fp;
|
|
697
|
+
}
|
|
698
|
+
function stablePathKeyGraph(m) {
|
|
699
|
+
const out = /* @__PURE__ */ new Map();
|
|
700
|
+
for (const [k, vs] of m) {
|
|
701
|
+
const key = stablePathKey(k);
|
|
702
|
+
let set = out.get(key);
|
|
703
|
+
if (!set) {
|
|
704
|
+
set = /* @__PURE__ */ new Set();
|
|
705
|
+
out.set(key, set);
|
|
706
|
+
}
|
|
707
|
+
for (const v2 of vs) set.add(stablePathKey(v2));
|
|
708
|
+
}
|
|
709
|
+
return out;
|
|
710
|
+
}
|
|
711
|
+
function resolveDepPath(fromFile, spec) {
|
|
712
|
+
const s = normPath(spec);
|
|
713
|
+
if (!s.startsWith(".")) return s;
|
|
714
|
+
const from = normPath(fromFile);
|
|
715
|
+
const i = from.lastIndexOf("/");
|
|
716
|
+
const base = i >= 0 ? from.slice(0, i) : "";
|
|
717
|
+
const parts = base ? base.split("/") : [];
|
|
718
|
+
for (const seg of s.split("/")) {
|
|
719
|
+
if (seg === "" || seg === ".") continue;
|
|
720
|
+
if (seg === "..") parts.pop();
|
|
721
|
+
else parts.push(seg);
|
|
722
|
+
}
|
|
723
|
+
return parts.join("/");
|
|
724
|
+
}
|
|
725
|
+
function isNodeModulesPath(p) {
|
|
726
|
+
const f = normPath(p);
|
|
727
|
+
if (f === "node_modules" || f.startsWith("node_modules/")) return true;
|
|
728
|
+
return f.includes("/node_modules/") || f.endsWith("/node_modules");
|
|
729
|
+
}
|
|
730
|
+
function sidecarPathOf(file) {
|
|
731
|
+
const f = normPath(file);
|
|
732
|
+
if (f.endsWith(".mjs")) return f.slice(0, -4) + ".nudo.js";
|
|
733
|
+
if (f.endsWith(".js")) return f.slice(0, -3) + ".nudo.js";
|
|
734
|
+
if (f.endsWith(".mts")) return f.slice(0, -4) + ".nudo.ts";
|
|
735
|
+
if (f.endsWith(".ts")) return f.slice(0, -3) + ".nudo.ts";
|
|
736
|
+
return `${f}.nudo.js`;
|
|
737
|
+
}
|
|
45
738
|
|
|
46
739
|
// src/algebra/constraint.ts
|
|
47
740
|
var SELF = "__nudo_self__";
|
|
@@ -190,6 +883,28 @@ function union(...cs) {
|
|
|
190
883
|
members: cs.map((c) => asNestedConstraint(c, "union member"))
|
|
191
884
|
});
|
|
192
885
|
}
|
|
886
|
+
function nullable(c) {
|
|
887
|
+
return makeBuilder(void 0, [], {
|
|
888
|
+
members: [
|
|
889
|
+
asNestedConstraint(c, "nullable()"),
|
|
890
|
+
toPlainConstraint(litC(null)),
|
|
891
|
+
toPlainConstraint(litC(void 0))
|
|
892
|
+
]
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
function constraintAdmitsNullish(c) {
|
|
896
|
+
if (c.members) return c.members.some((m) => constraintAdmitsNullish(m));
|
|
897
|
+
if (!c.prim && c.preds.length === 0 && !c.fields && !c.element && !c.fn) return true;
|
|
898
|
+
for (const p of c.preds) {
|
|
899
|
+
for (const flat of p.op === "and" ? p.args : [p]) {
|
|
900
|
+
if (flat.op !== "eq") continue;
|
|
901
|
+
for (const side of [flat.a, flat.b]) {
|
|
902
|
+
if (side.op === "lit" && (side.value === null || side.value === void 0)) return true;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
return false;
|
|
907
|
+
}
|
|
193
908
|
function fn(params, returns, opts) {
|
|
194
909
|
const normalized = {};
|
|
195
910
|
for (const [k, v2] of Object.entries(params)) {
|
|
@@ -261,6 +976,28 @@ function omit(c, keys) {
|
|
|
261
976
|
}
|
|
262
977
|
return makeBuilder(c.prim, c.preds, { fields });
|
|
263
978
|
}
|
|
979
|
+
var CONSTRAINT_BUILDERS = {
|
|
980
|
+
number,
|
|
981
|
+
string,
|
|
982
|
+
boolean,
|
|
983
|
+
any,
|
|
984
|
+
array,
|
|
985
|
+
shape,
|
|
986
|
+
lit: litC,
|
|
987
|
+
union,
|
|
988
|
+
nullable,
|
|
989
|
+
fn,
|
|
990
|
+
and: andC,
|
|
991
|
+
partial,
|
|
992
|
+
pick,
|
|
993
|
+
omit
|
|
994
|
+
};
|
|
995
|
+
var CONSTRAINT_BUILDER_NAMES = Object.keys(
|
|
996
|
+
CONSTRAINT_BUILDERS
|
|
997
|
+
);
|
|
998
|
+
var CONSTRAINT_EXPR_RE = new RegExp(
|
|
999
|
+
`^(${CONSTRAINT_BUILDER_NAMES.join("|")})\\s*\\(`
|
|
1000
|
+
);
|
|
264
1001
|
function substTerm(t, paramName) {
|
|
265
1002
|
if (t.op === "var" && t.id === SELF) return v(paramName);
|
|
266
1003
|
if (t.op === "app" && t.fn === "get" && t.args.length === 2) {
|
|
@@ -388,21 +1125,26 @@ function memberLitValue(m) {
|
|
|
388
1125
|
leaves.push(p2);
|
|
389
1126
|
};
|
|
390
1127
|
m.preds.forEach(visit2);
|
|
391
|
-
if (leaves.length !== 1) return
|
|
1128
|
+
if (leaves.length !== 1) return { ok: false };
|
|
392
1129
|
const p = leaves[0];
|
|
393
|
-
if (p.op !== "eq") return
|
|
394
|
-
if (p.a.op === "var" && p.b.op === "lit")
|
|
395
|
-
|
|
396
|
-
|
|
1130
|
+
if (p.op !== "eq") return { ok: false };
|
|
1131
|
+
if (p.a.op === "var" && p.b.op === "lit") {
|
|
1132
|
+
return { ok: true, v: p.b.value };
|
|
1133
|
+
}
|
|
1134
|
+
if (p.b.op === "var" && p.a.op === "lit") {
|
|
1135
|
+
return { ok: true, v: p.a.value };
|
|
1136
|
+
}
|
|
1137
|
+
return { ok: false };
|
|
397
1138
|
}
|
|
398
1139
|
function samePrimLiteralUnionAbs(members, t) {
|
|
399
1140
|
if (members.length === 0) return void 0;
|
|
400
1141
|
const values = [];
|
|
401
1142
|
let prim;
|
|
402
1143
|
for (const m of members) {
|
|
403
|
-
const
|
|
404
|
-
if (
|
|
405
|
-
|
|
1144
|
+
const mk = memberLitValue(m);
|
|
1145
|
+
if (!mk.ok) return void 0;
|
|
1146
|
+
const v2 = mk.v;
|
|
1147
|
+
if (v2 === void 0 || v2 === null) return void 0;
|
|
406
1148
|
const mp = m.prim ?? (typeof v2 === "number" ? "number" : typeof v2 === "string" ? "string" : "boolean");
|
|
407
1149
|
if (prim === void 0) prim = mp;
|
|
408
1150
|
else if (prim !== mp) return void 0;
|
|
@@ -461,10 +1203,10 @@ function constraintOnTermAbs(c, t) {
|
|
|
461
1203
|
if (c.prim) {
|
|
462
1204
|
return abs({ k: "prim", type: c.prim }, t, predOut, "path");
|
|
463
1205
|
}
|
|
464
|
-
const
|
|
465
|
-
(p) => p.op === "eq" && (p.b.op === "lit" && p.b.value === null || p.a.op === "lit" && p.a.value === null)
|
|
1206
|
+
const allEqNullish = c.preds.length > 0 && c.preds.every(
|
|
1207
|
+
(p) => p.op === "eq" && (p.b.op === "lit" && (p.b.value === null || p.b.value === void 0) || p.a.op === "lit" && (p.a.value === null || p.a.value === void 0))
|
|
466
1208
|
);
|
|
467
|
-
if (
|
|
1209
|
+
if (allEqNullish) {
|
|
468
1210
|
return abs({ k: "unknown" }, t, predOut, "path");
|
|
469
1211
|
}
|
|
470
1212
|
if (c.preds.length > 0) {
|
|
@@ -505,72 +1247,24 @@ function throwConstraintToKinds(c) {
|
|
|
505
1247
|
return void 0;
|
|
506
1248
|
}
|
|
507
1249
|
|
|
508
|
-
// src/algebra/hash-source.ts
|
|
509
|
-
var lastHashSource;
|
|
510
|
-
var lastHashOut;
|
|
511
|
-
function hashSource(s) {
|
|
512
|
-
if (s === lastHashSource && lastHashOut !== void 0) return lastHashOut;
|
|
513
|
-
let h1 = 2166136261;
|
|
514
|
-
let h2 = 2654435769;
|
|
515
|
-
for (let i = 0; i < s.length; i++) {
|
|
516
|
-
const c = s.charCodeAt(i);
|
|
517
|
-
h1 ^= c;
|
|
518
|
-
h1 = Math.imul(h1, 16777619);
|
|
519
|
-
h2 = Math.imul(h2 ^ c, 16777619);
|
|
520
|
-
}
|
|
521
|
-
lastHashSource = s;
|
|
522
|
-
lastHashOut = `${(h1 >>> 0).toString(36)}-${(h2 >>> 0).toString(36)}_${s.length.toString(36)}`;
|
|
523
|
-
return lastHashOut;
|
|
524
|
-
}
|
|
525
|
-
function resetHashSourceCache() {
|
|
526
|
-
lastHashSource = void 0;
|
|
527
|
-
lastHashOut = void 0;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// src/algebra/sidecar-path.ts
|
|
531
|
-
function normPath(p) {
|
|
532
|
-
return p.replace(/\\/g, "/");
|
|
533
|
-
}
|
|
534
|
-
function resolveDepPath(fromFile, spec) {
|
|
535
|
-
const s = normPath(spec);
|
|
536
|
-
if (!s.startsWith(".")) return s;
|
|
537
|
-
const from = normPath(fromFile);
|
|
538
|
-
const i = from.lastIndexOf("/");
|
|
539
|
-
const base = i >= 0 ? from.slice(0, i) : "";
|
|
540
|
-
const parts = base ? base.split("/") : [];
|
|
541
|
-
for (const seg of s.split("/")) {
|
|
542
|
-
if (seg === "" || seg === ".") continue;
|
|
543
|
-
if (seg === "..") parts.pop();
|
|
544
|
-
else parts.push(seg);
|
|
545
|
-
}
|
|
546
|
-
return parts.join("/");
|
|
547
|
-
}
|
|
548
|
-
function isNodeModulesPath(p) {
|
|
549
|
-
const f = normPath(p);
|
|
550
|
-
if (f === "node_modules" || f.startsWith("node_modules/")) return true;
|
|
551
|
-
return f.includes("/node_modules/") || f.endsWith("/node_modules");
|
|
552
|
-
}
|
|
553
|
-
function sidecarPathOf(file) {
|
|
554
|
-
const f = normPath(file);
|
|
555
|
-
if (f.endsWith(".mjs")) return f.slice(0, -4) + ".nudo.js";
|
|
556
|
-
if (f.endsWith(".js")) return f.slice(0, -3) + ".nudo.js";
|
|
557
|
-
if (f.endsWith(".mts")) return f.slice(0, -4) + ".nudo.ts";
|
|
558
|
-
if (f.endsWith(".ts")) return f.slice(0, -3) + ".nudo.ts";
|
|
559
|
-
return `${f}.nudo.js`;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
1250
|
// src/algebra/refine.ts
|
|
563
1251
|
function extractNudoImports(source) {
|
|
564
1252
|
const out = [];
|
|
565
|
-
const
|
|
566
|
-
|
|
567
|
-
while (m = named.exec(source)) {
|
|
568
|
-
const names = m[1].split(",").map((s) => s.trim()).filter(Boolean).map((s) => s.split(/\s+as\s+/).pop().trim());
|
|
569
|
-
out.push({ names, spec: m[2] });
|
|
1253
|
+
for (const rec of extractNudoImportRecords(source)) {
|
|
1254
|
+
out.push({ names: rec.names, spec: rec.spec });
|
|
570
1255
|
}
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
1256
|
+
for (const bad of scanMalformedNudoImports(source)) {
|
|
1257
|
+
if (bad === "malformed-default") {
|
|
1258
|
+
collectDiag({
|
|
1259
|
+
code: "nudo:contract-syntax",
|
|
1260
|
+
message: `Default @nudo:import is not supported (use named { a, b } or namespace * as ns)`
|
|
1261
|
+
});
|
|
1262
|
+
} else {
|
|
1263
|
+
collectDiag({
|
|
1264
|
+
code: "nudo:contract-syntax",
|
|
1265
|
+
message: `Unrecognized @nudo:import form (expected { names } from "path" or * as ns from "path")`
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
574
1268
|
}
|
|
575
1269
|
return out;
|
|
576
1270
|
}
|
|
@@ -612,20 +1306,7 @@ var NudoSidecarError = class extends Error {
|
|
|
612
1306
|
this.code = code;
|
|
613
1307
|
}
|
|
614
1308
|
};
|
|
615
|
-
var sidecarInjects =
|
|
616
|
-
number,
|
|
617
|
-
string,
|
|
618
|
-
boolean,
|
|
619
|
-
shape,
|
|
620
|
-
array,
|
|
621
|
-
lit: litC,
|
|
622
|
-
union,
|
|
623
|
-
fn,
|
|
624
|
-
and: andC,
|
|
625
|
-
partial,
|
|
626
|
-
pick,
|
|
627
|
-
omit
|
|
628
|
-
};
|
|
1309
|
+
var sidecarInjects = CONSTRAINT_BUILDERS;
|
|
629
1310
|
function isSidecarSpec(spec) {
|
|
630
1311
|
return spec.endsWith(".nudo.js") || spec.endsWith(".nudo.ts");
|
|
631
1312
|
}
|
|
@@ -974,6 +1655,7 @@ function execNudoModule(src, opts) {
|
|
|
974
1655
|
return execNudoModuleCached(src, opts ?? {});
|
|
975
1656
|
}
|
|
976
1657
|
var nudoModuleExecCache = /* @__PURE__ */ new Map();
|
|
1658
|
+
var nudoModuleFailCache = /* @__PURE__ */ new Map();
|
|
977
1659
|
var MAX_NUDO_MODULE_EXEC = 64;
|
|
978
1660
|
var MAX_SIDECAR_CLOSURE = 64;
|
|
979
1661
|
function sidecarDepClosure(src, fromFile, loadModule, out) {
|
|
@@ -991,7 +1673,12 @@ function sidecarDepClosure(src, fromFile, loadModule, out) {
|
|
|
991
1673
|
if (!isSidecarSpec(spec)) continue;
|
|
992
1674
|
const canonical = resolveDepPath(fromFile, spec);
|
|
993
1675
|
if (out.has(canonical)) continue;
|
|
994
|
-
|
|
1676
|
+
let depSrc;
|
|
1677
|
+
try {
|
|
1678
|
+
depSrc = loadModule(spec, fromFile);
|
|
1679
|
+
} catch {
|
|
1680
|
+
depSrc = void 0;
|
|
1681
|
+
}
|
|
995
1682
|
out.set(canonical, depSrc);
|
|
996
1683
|
if (depSrc === void 0) continue;
|
|
997
1684
|
const r = sidecarDepClosure(depSrc, canonical, loadModule, out);
|
|
@@ -1020,19 +1707,34 @@ function execNudoModuleCached(src, opts) {
|
|
|
1020
1707
|
nudoModuleExecCache.set(key, hit);
|
|
1021
1708
|
return hit;
|
|
1022
1709
|
}
|
|
1710
|
+
if (nudoModuleFailCache.has(key)) {
|
|
1711
|
+
throw nudoModuleFailCache.get(key);
|
|
1712
|
+
}
|
|
1023
1713
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
if (
|
|
1027
|
-
|
|
1028
|
-
|
|
1714
|
+
try {
|
|
1715
|
+
const out = execSidecar(src, opts, opts.fromFile ? [opts.fromFile] : [], preloaded);
|
|
1716
|
+
if (key !== void 0) {
|
|
1717
|
+
if (nudoModuleExecCache.size >= MAX_NUDO_MODULE_EXEC) {
|
|
1718
|
+
const oldest = nudoModuleExecCache.keys().next().value;
|
|
1719
|
+
if (oldest !== void 0) nudoModuleExecCache.delete(oldest);
|
|
1720
|
+
}
|
|
1721
|
+
nudoModuleExecCache.set(key, out);
|
|
1029
1722
|
}
|
|
1030
|
-
|
|
1723
|
+
return out;
|
|
1724
|
+
} catch (e) {
|
|
1725
|
+
if (key !== void 0) {
|
|
1726
|
+
if (nudoModuleFailCache.size >= MAX_NUDO_MODULE_EXEC) {
|
|
1727
|
+
const oldest = nudoModuleFailCache.keys().next().value;
|
|
1728
|
+
if (oldest !== void 0) nudoModuleFailCache.delete(oldest);
|
|
1729
|
+
}
|
|
1730
|
+
nudoModuleFailCache.set(key, e);
|
|
1731
|
+
}
|
|
1732
|
+
throw e;
|
|
1031
1733
|
}
|
|
1032
|
-
return out;
|
|
1033
1734
|
}
|
|
1034
1735
|
function resetNudoModuleExecCache() {
|
|
1035
1736
|
nudoModuleExecCache.clear();
|
|
1737
|
+
nudoModuleFailCache.clear();
|
|
1036
1738
|
}
|
|
1037
1739
|
function collectConstraints(source, opts) {
|
|
1038
1740
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -1046,7 +1748,17 @@ function collectConstraints(source, opts) {
|
|
|
1046
1748
|
});
|
|
1047
1749
|
continue;
|
|
1048
1750
|
}
|
|
1049
|
-
|
|
1751
|
+
let src;
|
|
1752
|
+
try {
|
|
1753
|
+
src = opts.loadModule(imp.spec, opts.fromFile);
|
|
1754
|
+
} catch (e) {
|
|
1755
|
+
collectDiag({
|
|
1756
|
+
code: "nudo:interface-load",
|
|
1757
|
+
message: `sidecar '${imp.spec}' failed to load from '${opts.fromFile}': ${e instanceof Error ? e.message : String(e)}`,
|
|
1758
|
+
file: opts.fromFile
|
|
1759
|
+
});
|
|
1760
|
+
continue;
|
|
1761
|
+
}
|
|
1050
1762
|
if (src === void 0) {
|
|
1051
1763
|
collectDiag({
|
|
1052
1764
|
code: "nudo:interface-load",
|
|
@@ -1102,36 +1814,25 @@ function collectConstraints(source, opts) {
|
|
|
1102
1814
|
return map;
|
|
1103
1815
|
}
|
|
1104
1816
|
function extractRefineLines(source, fnName) {
|
|
1105
|
-
const
|
|
1106
|
-
|
|
1107
|
-
`(?:export\\s+(?:default\\s+)?)?(?:async\\s+)?(?:function\\s+${escaped}\\b|const\\s+${escaped}\\s*=)`
|
|
1108
|
-
);
|
|
1109
|
-
const m = source.match(fnRe);
|
|
1110
|
-
if (!m || m.index === void 0) return [];
|
|
1111
|
-
const before = source.slice(0, m.index);
|
|
1112
|
-
const lines = before.split("\n");
|
|
1113
|
-
const reqs = [];
|
|
1114
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1115
|
-
const line = lines[i].trim();
|
|
1116
|
-
if (line === "" || line === "*/") continue;
|
|
1117
|
-
if (line.startsWith("*") || line.startsWith("/*") || line.startsWith("//")) {
|
|
1118
|
-
const rm = line.match(/@nudo:contract\s+(.+)$/);
|
|
1119
|
-
if (rm) reqs.unshift(rm[1].trim().replace(/\*\/$/, "").trim());
|
|
1120
|
-
continue;
|
|
1121
|
-
}
|
|
1122
|
-
break;
|
|
1123
|
-
}
|
|
1124
|
-
return reqs;
|
|
1817
|
+
const lines = fnDirectiveCommentLines(source, fnName);
|
|
1818
|
+
return scanContractSegments(lines);
|
|
1125
1819
|
}
|
|
1126
1820
|
function extractRefinesFromSource(source, fnName, opts = {}) {
|
|
1127
|
-
if (!source.includes("@nudo:contract")) return [];
|
|
1821
|
+
if (!stripStringsKeepComments(source).includes("@nudo:contract")) return [];
|
|
1128
1822
|
const constraints = collectConstraints(source, opts);
|
|
1129
1823
|
const out = [];
|
|
1130
1824
|
for (const line of extractRefineLines(source, fnName)) {
|
|
1131
1825
|
const parts = line.split(/&&|,/).map((s) => s.trim()).filter(Boolean);
|
|
1132
1826
|
for (const part of parts) {
|
|
1133
1827
|
const m = part.match(/^(\w+)\s+([\w.]+)$/);
|
|
1134
|
-
if (!m)
|
|
1828
|
+
if (!m) {
|
|
1829
|
+
if (/^return\b/.test(part)) continue;
|
|
1830
|
+
collectDiag({
|
|
1831
|
+
code: "nudo:contract-syntax",
|
|
1832
|
+
message: `Malformed @nudo:contract segment '${part}' (expected <param> <constraintName> or return <constraintName>; e.g. @nudo:contract x positive)`
|
|
1833
|
+
});
|
|
1834
|
+
continue;
|
|
1835
|
+
}
|
|
1135
1836
|
const [, param, cName] = m;
|
|
1136
1837
|
if (param === "return") continue;
|
|
1137
1838
|
const c = constraints.get(cName);
|
|
@@ -1155,13 +1856,21 @@ function refineToIndexedFull(source, fnName, paramNames, opts = {}) {
|
|
|
1155
1856
|
return out;
|
|
1156
1857
|
}
|
|
1157
1858
|
function extractRefineReturnFromSource(source, fnName, opts = {}) {
|
|
1158
|
-
if (!source.includes("@nudo:contract")) return void 0;
|
|
1859
|
+
if (!stripStringsKeepComments(source).includes("@nudo:contract")) return void 0;
|
|
1159
1860
|
const constraints = collectConstraints(source, opts);
|
|
1160
1861
|
for (const line of extractRefineLines(source, fnName)) {
|
|
1161
1862
|
const parts = line.split(/&&|,/).map((s) => s.trim()).filter(Boolean);
|
|
1162
1863
|
for (const part of parts) {
|
|
1163
1864
|
const m = part.match(/^return\s+([\w.]+)$/);
|
|
1164
|
-
if (!m)
|
|
1865
|
+
if (!m) {
|
|
1866
|
+
if (/^return\b/.test(part) && !/^return\s+[\w.]+$/.test(part)) {
|
|
1867
|
+
collectDiag({
|
|
1868
|
+
code: "nudo:contract-syntax",
|
|
1869
|
+
message: `Malformed @nudo:contract return segment '${part}' (expected return <constraintName>)`
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
continue;
|
|
1873
|
+
}
|
|
1165
1874
|
const cName = m[1];
|
|
1166
1875
|
const c = constraints.get(cName);
|
|
1167
1876
|
if (!c) continue;
|
|
@@ -1171,59 +1880,21 @@ function extractRefineReturnFromSource(source, fnName, opts = {}) {
|
|
|
1171
1880
|
return void 0;
|
|
1172
1881
|
}
|
|
1173
1882
|
function extractDeclaredThrows(source, fnName) {
|
|
1174
|
-
|
|
1883
|
+
const directiveSrc = stripStringsKeepComments(source);
|
|
1884
|
+
if (!directiveSrc.includes("@nudo:throws") && !directiveSrc.includes("!! throws")) {
|
|
1175
1885
|
return void 0;
|
|
1176
1886
|
}
|
|
1177
|
-
const
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
const lines =
|
|
1187
|
-
|
|
1188
|
-
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1189
|
-
const line = lines[i].trim();
|
|
1190
|
-
if (line === "") {
|
|
1191
|
-
if (seenComment) break;
|
|
1192
|
-
continue;
|
|
1193
|
-
}
|
|
1194
|
-
if (line === "*/") continue;
|
|
1195
|
-
if (line.startsWith("*") || line.startsWith("/*") || line.startsWith("//")) {
|
|
1196
|
-
seenComment = true;
|
|
1197
|
-
const body = line.replace(/^[*/\s]+/, "").replace(/\*\/$/, "").trim();
|
|
1198
|
-
const th = body.match(/@nudo:throws\s+(.+)$/i);
|
|
1199
|
-
if (th) {
|
|
1200
|
-
const spec = th[1].trim();
|
|
1201
|
-
if (spec === "*") any2 = true;
|
|
1202
|
-
else {
|
|
1203
|
-
for (const k of spec.split(/[,\s|]+/).map((s) => s.trim()).filter(Boolean)) {
|
|
1204
|
-
if (k === "*") any2 = true;
|
|
1205
|
-
else kinds.add(k);
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
const cs = body.match(/!!\s*throws(?:\s+([A-Za-z*][\w*|,\s]*))?/i);
|
|
1210
|
-
if (cs) {
|
|
1211
|
-
const spec = (cs[1] ?? "*").trim();
|
|
1212
|
-
if (spec === "" || spec === "*") any2 = true;
|
|
1213
|
-
else {
|
|
1214
|
-
for (const k of spec.split(/[,\s|]+/).map((s) => s.trim()).filter(Boolean)) {
|
|
1215
|
-
if (k === "*") any2 = true;
|
|
1216
|
-
else kinds.add(k);
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
continue;
|
|
1221
|
-
}
|
|
1222
|
-
break;
|
|
1223
|
-
}
|
|
1224
|
-
if (any2) return "*";
|
|
1225
|
-
if (kinds.size > 0) return [...kinds];
|
|
1226
|
-
return void 0;
|
|
1887
|
+
const lines = fnDirectiveCommentLines(source, fnName);
|
|
1888
|
+
const spec = scanThrowsDecl(lines);
|
|
1889
|
+
if (spec === void 0) return void 0;
|
|
1890
|
+
if (spec === "*") return "*";
|
|
1891
|
+
return spec.split(",").filter(Boolean);
|
|
1892
|
+
}
|
|
1893
|
+
function extractFnBudget(source, fnName) {
|
|
1894
|
+
const directiveSrc = stripStringsKeepComments(source);
|
|
1895
|
+
if (!directiveSrc.includes("@nudo:budget")) return void 0;
|
|
1896
|
+
const lines = fnDirectiveCommentLines(source, fnName);
|
|
1897
|
+
return scanBudgetDecl(lines);
|
|
1227
1898
|
}
|
|
1228
1899
|
|
|
1229
1900
|
// src/algebra/interface.ts
|
|
@@ -1321,11 +1992,24 @@ function localNamedExports(source) {
|
|
|
1321
1992
|
for (const stmt of ast.program.body) {
|
|
1322
1993
|
const s = stmt;
|
|
1323
1994
|
if (s.type === "ExportNamedDeclaration") {
|
|
1324
|
-
if (s.source)
|
|
1995
|
+
if (s.source) {
|
|
1996
|
+
const specs = s.specifiers ?? [];
|
|
1997
|
+
for (const spec of specs) {
|
|
1998
|
+
if (spec.type !== "ExportNamespaceSpecifier") continue;
|
|
1999
|
+
const name = identName(spec.exported);
|
|
2000
|
+
if (name) out.add(name);
|
|
2001
|
+
}
|
|
2002
|
+
continue;
|
|
2003
|
+
}
|
|
1325
2004
|
const d = s.declaration;
|
|
1326
2005
|
if (!d) {
|
|
1327
2006
|
const specs = s.specifiers ?? [];
|
|
1328
2007
|
for (const spec of specs) {
|
|
2008
|
+
if (spec.type === "ExportNamespaceSpecifier") {
|
|
2009
|
+
const name2 = identName(spec.exported);
|
|
2010
|
+
if (name2) out.add(name2);
|
|
2011
|
+
continue;
|
|
2012
|
+
}
|
|
1329
2013
|
if (spec.type !== "ExportSpecifier") continue;
|
|
1330
2014
|
const name = identName(spec.exported);
|
|
1331
2015
|
if (!name) continue;
|
|
@@ -1473,17 +2157,53 @@ function sidecarAutoBindAllowed(sidecarPath, autoBind, projectDir) {
|
|
|
1473
2157
|
return false;
|
|
1474
2158
|
}
|
|
1475
2159
|
function isDefaultExportLocal(source, localName) {
|
|
2160
|
+
const name = identBoundaryRegex(localName);
|
|
1476
2161
|
const reList = new RegExp(
|
|
1477
|
-
`export\\s*\\{[^}]
|
|
2162
|
+
`export\\s*\\{[^}]*${name}\\s+as\\s+default(?![\\w$])[^}]*\\}`,
|
|
1478
2163
|
"m"
|
|
1479
2164
|
);
|
|
1480
2165
|
const reDefaultFn = new RegExp(
|
|
1481
|
-
`export\\s+default\\s+(?:async\\s+)?function\\s+${
|
|
2166
|
+
`export\\s+default\\s+(?:async\\s+)?function\\s+${name}`,
|
|
2167
|
+
"m"
|
|
2168
|
+
);
|
|
2169
|
+
const reDefaultId = new RegExp(
|
|
2170
|
+
`export\\s+default\\s+${name}`,
|
|
1482
2171
|
"m"
|
|
1483
2172
|
);
|
|
1484
|
-
const reDefaultId = new RegExp(`export\\s+default\\s+${localName}\\b`, "m");
|
|
1485
2173
|
return reList.test(source) || reDefaultFn.test(source) || reDefaultId.test(source);
|
|
1486
2174
|
}
|
|
2175
|
+
function sidecarExportNames(sidecarSrc) {
|
|
2176
|
+
try {
|
|
2177
|
+
return localNamedExports(sidecarSrc);
|
|
2178
|
+
} catch {
|
|
2179
|
+
return void 0;
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
function sidecarMayBind(sidecarNames, fnName, source) {
|
|
2183
|
+
if (!sidecarNames) return true;
|
|
2184
|
+
if (sidecarNames.size === 0) return true;
|
|
2185
|
+
if (sidecarNames.has(fnName)) return true;
|
|
2186
|
+
if (sidecarNames.has("default") && isDefaultExportLocal(source, fnName)) return true;
|
|
2187
|
+
if (fnName.includes(".")) {
|
|
2188
|
+
const [cls, method] = fnName.split(".", 2);
|
|
2189
|
+
if (cls && method) {
|
|
2190
|
+
if (sidecarNames.has(`${cls}_${method}`)) return true;
|
|
2191
|
+
if (sidecarNames.has(cls)) return true;
|
|
2192
|
+
if (sidecarNames.has(method)) return true;
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
return false;
|
|
2196
|
+
}
|
|
2197
|
+
var sidecarLoadFailures = /* @__PURE__ */ new Map();
|
|
2198
|
+
function noteSidecarLoadFailure(sidecarPath, reason) {
|
|
2199
|
+
const key = `${sidecarPath}\0${reason}`;
|
|
2200
|
+
if (sidecarLoadFailures.has(key)) return false;
|
|
2201
|
+
sidecarLoadFailures.set(key, reason);
|
|
2202
|
+
return true;
|
|
2203
|
+
}
|
|
2204
|
+
function resetSidecarLoadFailureCache() {
|
|
2205
|
+
sidecarLoadFailures.clear();
|
|
2206
|
+
}
|
|
1487
2207
|
function loadSidecarBinding(source, fnName, opts) {
|
|
1488
2208
|
const { loadModule, fromFile, autoBind, projectDir } = opts;
|
|
1489
2209
|
if (!loadModule || !fromFile) return { ok: false };
|
|
@@ -1491,18 +2211,46 @@ function loadSidecarBinding(source, fnName, opts) {
|
|
|
1491
2211
|
if (!sidecarAutoBindAllowed(sidecarPath, autoBind, projectDir)) return { ok: false };
|
|
1492
2212
|
if (!localNamedExports(source).has(fnName)) return { ok: false };
|
|
1493
2213
|
const spec = `./${sidecarPath.slice(sidecarPath.lastIndexOf("/") + 1)}`;
|
|
1494
|
-
|
|
2214
|
+
let sidecarSrc;
|
|
2215
|
+
try {
|
|
2216
|
+
sidecarSrc = loadModule(spec, fromFile);
|
|
2217
|
+
} catch (e) {
|
|
2218
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
2219
|
+
if (noteSidecarLoadFailure(sidecarPath, reason)) {
|
|
2220
|
+
collectDiag2({
|
|
2221
|
+
code: "nudo:interface-load",
|
|
2222
|
+
message: `sidecar '${spec}' failed to load: ${reason}`,
|
|
2223
|
+
file: sidecarPath
|
|
2224
|
+
});
|
|
2225
|
+
}
|
|
2226
|
+
return { ok: false };
|
|
2227
|
+
}
|
|
1495
2228
|
if (sidecarSrc === void 0) return { ok: false };
|
|
1496
2229
|
if (sidecarSrc === source) return { ok: false };
|
|
2230
|
+
const scNames = sidecarExportNames(sidecarSrc);
|
|
2231
|
+
if (!sidecarMayBind(scNames, fnName, source)) return { ok: false };
|
|
1497
2232
|
let exports;
|
|
1498
2233
|
try {
|
|
1499
2234
|
exports = execNudoModule(sidecarSrc, { loadModule, fromFile: sidecarPath });
|
|
1500
2235
|
} catch (e) {
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
2236
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
2237
|
+
const code = e instanceof NudoSidecarError ? e.code : "nudo:interface-load";
|
|
2238
|
+
if (noteSidecarLoadFailure(sidecarPath, reason)) {
|
|
2239
|
+
let affected = 1;
|
|
2240
|
+
if (scNames && scNames.size > 0) {
|
|
2241
|
+
const srcExports = localNamedExports(source);
|
|
2242
|
+
let n = 0;
|
|
2243
|
+
for (const name of srcExports) {
|
|
2244
|
+
if (sidecarMayBind(scNames, name, source)) n++;
|
|
2245
|
+
}
|
|
2246
|
+
if (n > 0) affected = n;
|
|
2247
|
+
}
|
|
2248
|
+
collectDiag2({
|
|
2249
|
+
code,
|
|
2250
|
+
message: affected > 1 ? `sidecar '${spec}' failed to load: ${reason} (affects ${affected} bindings)` : `sidecar '${spec}' failed to load: ${reason}`,
|
|
2251
|
+
file: sidecarPath
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
1506
2254
|
return { ok: false };
|
|
1507
2255
|
}
|
|
1508
2256
|
let binding = exports[fnName];
|
|
@@ -1585,14 +2333,17 @@ function eqLitsOf(c) {
|
|
|
1585
2333
|
if (p.op !== "eq") return;
|
|
1586
2334
|
let term;
|
|
1587
2335
|
let value;
|
|
2336
|
+
let hasLit = false;
|
|
1588
2337
|
if (p.a.op === "var" && p.b.op === "lit") {
|
|
1589
2338
|
term = p.a;
|
|
1590
2339
|
value = p.b.value;
|
|
2340
|
+
hasLit = true;
|
|
1591
2341
|
} else if (p.b.op === "var" && p.a.op === "lit") {
|
|
1592
2342
|
term = p.b;
|
|
1593
2343
|
value = p.a.value;
|
|
2344
|
+
hasLit = true;
|
|
1594
2345
|
}
|
|
1595
|
-
if (term !== void 0 &&
|
|
2346
|
+
if (term !== void 0 && hasLit) {
|
|
1596
2347
|
out.push({ term: termToString(term), value });
|
|
1597
2348
|
}
|
|
1598
2349
|
};
|
|
@@ -1758,7 +2509,12 @@ function sidecarClosureFingerprint(fromFile, opts) {
|
|
|
1758
2509
|
const sidecarPath = sidecarPathOf(fromFile);
|
|
1759
2510
|
if (!sidecarAutoBindAllowed(sidecarPath, autoBind, projectDir)) return void 0;
|
|
1760
2511
|
const spec = `./${sidecarPath.slice(sidecarPath.lastIndexOf("/") + 1)}`;
|
|
1761
|
-
|
|
2512
|
+
let sidecarSrc;
|
|
2513
|
+
try {
|
|
2514
|
+
sidecarSrc = loadModule(spec, fromFile);
|
|
2515
|
+
} catch {
|
|
2516
|
+
return void 0;
|
|
2517
|
+
}
|
|
1762
2518
|
if (sidecarSrc === void 0) return void 0;
|
|
1763
2519
|
const parts = [];
|
|
1764
2520
|
const seen = /* @__PURE__ */ new Set([sidecarPath]);
|
|
@@ -1772,7 +2528,12 @@ function sidecarClosureFingerprint(fromFile, opts) {
|
|
|
1772
2528
|
}
|
|
1773
2529
|
const { spec: s, from } = queue.shift();
|
|
1774
2530
|
const depPath = resolveDepPath(from, s);
|
|
1775
|
-
|
|
2531
|
+
let depSrc;
|
|
2532
|
+
try {
|
|
2533
|
+
depSrc = loadModule(s, from);
|
|
2534
|
+
} catch {
|
|
2535
|
+
depSrc = void 0;
|
|
2536
|
+
}
|
|
1776
2537
|
parts.push(`${depPath}=${depSrc === void 0 ? "miss" : hashSource(depSrc)}`);
|
|
1777
2538
|
if (depSrc !== void 0) enqueueSidecarDeps(depSrc, depPath, seen, queue);
|
|
1778
2539
|
}
|
|
@@ -2055,7 +2816,7 @@ function hasModuleSyntax(file) {
|
|
|
2055
2816
|
return false;
|
|
2056
2817
|
}
|
|
2057
2818
|
function canSkipLiteralCallScan(source, file) {
|
|
2058
|
-
if (source.includes("@nudo:contract")) return false;
|
|
2819
|
+
if (stripStringsKeepComments(source).includes("@nudo:contract")) return false;
|
|
2059
2820
|
if (hasModuleSyntax(file)) return false;
|
|
2060
2821
|
if (hasAnyCallLike(file)) return false;
|
|
2061
2822
|
const fps = fnFingerprints(source, file);
|
|
@@ -2071,17 +2832,12 @@ function extractAllLoadSpecs(source) {
|
|
|
2071
2832
|
const specs = /* @__PURE__ */ new Set();
|
|
2072
2833
|
for (const imp of extractNudoImports(source)) specs.add(imp.spec);
|
|
2073
2834
|
const isPathLikeSpec = (spec) => spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("/") || /\.(ts|js|mjs|cjs|tsx|jsx)$/.test(spec);
|
|
2074
|
-
const
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
if (spec && isPathLikeSpec(spec)) specs.add(spec);
|
|
2080
|
-
}
|
|
2835
|
+
for (const name of extractFileEnvNames(source)) {
|
|
2836
|
+
if (isPathLikeSpec(name)) specs.add(name);
|
|
2837
|
+
}
|
|
2838
|
+
for (const rec of extractMockModuleRecords(source)) {
|
|
2839
|
+
if (rec.fromPath) specs.add(rec.fromPath);
|
|
2081
2840
|
}
|
|
2082
|
-
const mockFromRe = /@nudo:mock-module\s+"[^"]+"\s+(?:\{[^}]*\}\s+)?from\s+["']([^"']+)["']/g;
|
|
2083
|
-
let mockM;
|
|
2084
|
-
while (mockM = mockFromRe.exec(source)) specs.add(mockM[1]);
|
|
2085
2841
|
const patterns = [
|
|
2086
2842
|
/\bfrom\s*['"]([^'"]+)['"]/g,
|
|
2087
2843
|
/\brequire\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
@@ -2098,6 +2854,13 @@ function sidecarSpecsOf(source) {
|
|
|
2098
2854
|
(spec) => (spec.startsWith(".") || spec.startsWith("/")) && (spec.endsWith(".nudo.js") || spec.endsWith(".nudo.ts"))
|
|
2099
2855
|
);
|
|
2100
2856
|
}
|
|
2857
|
+
function loadOrMiss(loadModule, spec, from) {
|
|
2858
|
+
try {
|
|
2859
|
+
return loadModule(spec, from);
|
|
2860
|
+
} catch {
|
|
2861
|
+
return void 0;
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2101
2864
|
function loadModuleDepsFingerprint(source, loadModule, fromFile) {
|
|
2102
2865
|
const parts = [];
|
|
2103
2866
|
const paths = [];
|
|
@@ -2116,7 +2879,7 @@ function loadModuleDepsFingerprint(source, loadModule, fromFile) {
|
|
|
2116
2879
|
const sidecarPath = sidecarPathOf(entryFile);
|
|
2117
2880
|
if (seen.has(sidecarPath)) return;
|
|
2118
2881
|
const sidecarSpec = `./${sidecarPath.slice(sidecarPath.lastIndexOf("/") + 1)}`;
|
|
2119
|
-
const sidecarSrc = loadModule
|
|
2882
|
+
const sidecarSrc = loadOrMiss(loadModule, sidecarSpec, entryFile);
|
|
2120
2883
|
if (sidecarSrc === void 0) return;
|
|
2121
2884
|
if (n >= MAX_LOAD_DEP_NODES) {
|
|
2122
2885
|
truncated = true;
|
|
@@ -2140,7 +2903,7 @@ function loadModuleDepsFingerprint(source, loadModule, fromFile) {
|
|
|
2140
2903
|
if (seen.has(path)) continue;
|
|
2141
2904
|
seen.add(path);
|
|
2142
2905
|
n++;
|
|
2143
|
-
const src = loadModule
|
|
2906
|
+
const src = loadOrMiss(loadModule, cur.spec, cur.from);
|
|
2144
2907
|
parts.push(`sidecar:${path}=${src === void 0 ? "miss" : hashSource(src)}`);
|
|
2145
2908
|
paths.push(path);
|
|
2146
2909
|
contents.push({ path, content: src ?? null });
|
|
@@ -2161,7 +2924,7 @@ function loadModuleDepsFingerprint(source, loadModule, fromFile) {
|
|
|
2161
2924
|
if (seen.has(path)) continue;
|
|
2162
2925
|
seen.add(path);
|
|
2163
2926
|
n++;
|
|
2164
|
-
const src = loadModule
|
|
2927
|
+
const src = loadOrMiss(loadModule, cur.spec, cur.from);
|
|
2165
2928
|
parts.push(`${path}=${src === void 0 ? "miss" : hashSource(src)}`);
|
|
2166
2929
|
paths.push(path);
|
|
2167
2930
|
contents.push({ path, content: src ?? null });
|
|
@@ -2209,6 +2972,8 @@ function predKey(p, rename) {
|
|
|
2209
2972
|
return `not(${predKey(p.arg, rename)})`;
|
|
2210
2973
|
case "typeof":
|
|
2211
2974
|
return `typeof(${termKey(p.t, rename)},${p.type})`;
|
|
2975
|
+
case "assumeFinite":
|
|
2976
|
+
return `assumeFinite(${termKey(p.t, rename)})`;
|
|
2212
2977
|
}
|
|
2213
2978
|
}
|
|
2214
2979
|
function shapeKey(s, seen, rename) {
|
|
@@ -2359,6 +3124,8 @@ function renamePred(p, map) {
|
|
|
2359
3124
|
return { op: "not", arg: renamePred(p.arg, map) };
|
|
2360
3125
|
case "typeof":
|
|
2361
3126
|
return { op: "typeof", t: renameTerm(p.t, map), type: p.type };
|
|
3127
|
+
case "assumeFinite":
|
|
3128
|
+
return { op: "assumeFinite", t: renameTerm(p.t, map) };
|
|
2362
3129
|
}
|
|
2363
3130
|
}
|
|
2364
3131
|
function renameAbs(a, map) {
|
|
@@ -2767,7 +3534,7 @@ function unindexMemoKey(key) {
|
|
|
2767
3534
|
function evictGeneralizeMemoForPaths(paths) {
|
|
2768
3535
|
let n = 0;
|
|
2769
3536
|
for (const raw of paths) {
|
|
2770
|
-
const p =
|
|
3537
|
+
const p = stablePathKey(raw);
|
|
2771
3538
|
const keys = memoDepIndex.get(p);
|
|
2772
3539
|
if (!keys) continue;
|
|
2773
3540
|
for (const key of [...keys]) {
|
|
@@ -2809,7 +3576,7 @@ function generalizeMemoKey(fnName, source, opts) {
|
|
|
2809
3576
|
const budget = opts.budget ?? defaultLeakBudget;
|
|
2810
3577
|
const deps = opts.depsFp ?? (r ? refineDepsFingerprint(source, r) : { fp: "-", paths: [], contents: [], truncated: false });
|
|
2811
3578
|
const sc = opts.sidecarFp ?? (r?.loadModule && r.fromFile ? sidecarClosureFingerprint(r.fromFile, r) : void 0);
|
|
2812
|
-
const scPath = sc !== void 0 && !sc.startsWith("trunc:") && r?.fromFile ?
|
|
3579
|
+
const scPath = sc !== void 0 && !sc.startsWith("trunc:") && r?.fromFile ? sidecarPathOf(r.fromFile) : void 0;
|
|
2813
3580
|
const srcPart = generalizeSourceKeyPart(source, fnName, opts.file);
|
|
2814
3581
|
const key = [
|
|
2815
3582
|
srcPart,
|
|
@@ -2845,8 +3612,9 @@ function generalizeMemoSet(key, value, depPaths) {
|
|
|
2845
3612
|
}
|
|
2846
3613
|
generalizeMemo.set(key, value);
|
|
2847
3614
|
if (depPaths.length > 0) {
|
|
2848
|
-
|
|
2849
|
-
|
|
3615
|
+
const normDeps = [...new Set(depPaths.map(stablePathKey))];
|
|
3616
|
+
memoKeyDeps.set(key, normDeps);
|
|
3617
|
+
for (const p of normDeps) {
|
|
2850
3618
|
let set = memoDepIndex.get(p);
|
|
2851
3619
|
if (!set) {
|
|
2852
3620
|
set = /* @__PURE__ */ new Set();
|
|
@@ -2993,6 +3761,40 @@ function extractFn(source, fnName, fileAst) {
|
|
|
2993
3761
|
registerFnNode(propName, right);
|
|
2994
3762
|
}
|
|
2995
3763
|
}
|
|
3764
|
+
for (const stmt of file.program.body) {
|
|
3765
|
+
if (stmt.type !== "ExportNamedDeclaration" || stmt.declaration) continue;
|
|
3766
|
+
if (stmt.source) continue;
|
|
3767
|
+
const specs = stmt.specifiers ?? [];
|
|
3768
|
+
for (const spec of specs) {
|
|
3769
|
+
if (spec.type !== "ExportSpecifier") continue;
|
|
3770
|
+
const localName = spec.local?.name;
|
|
3771
|
+
const exportedName = spec.exported?.name ?? spec.exported?.value;
|
|
3772
|
+
if (!localName || exportedName == null) continue;
|
|
3773
|
+
const exp = String(exportedName);
|
|
3774
|
+
if (exp === localName || env.fns.has(exp)) continue;
|
|
3775
|
+
const target = env.fns.get(localName);
|
|
3776
|
+
if (!target) continue;
|
|
3777
|
+
env.fns.set(exp, target);
|
|
3778
|
+
formalsByName.set(exp, formalsByName.get(localName) ?? []);
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3781
|
+
for (const scope of listFnDirectiveScopes(file)) {
|
|
3782
|
+
if (env.fns.has(scope.name)) continue;
|
|
3783
|
+
if (scope.name === "<anonymous>") continue;
|
|
3784
|
+
const n = scope.node;
|
|
3785
|
+
const isFnLike = n.type === "FunctionDeclaration" || n.type === "FunctionExpression" || n.type === "ArrowFunctionExpression" || n.type === "ClassMethod" || n.type === "ClassPrivateMethod" || n.type === "ObjectMethod";
|
|
3786
|
+
if (!isFnLike || !n.body) continue;
|
|
3787
|
+
if ((n.type === "ClassMethod" || n.type === "ClassPrivateMethod" || n.type === "ObjectMethod") && n.kind && n.kind !== "method") {
|
|
3788
|
+
continue;
|
|
3789
|
+
}
|
|
3790
|
+
const formals = formalParamsFromNodes(n.params ?? []);
|
|
3791
|
+
env.fns.set(scope.name, {
|
|
3792
|
+
params: formalParamDisplayNames(formals),
|
|
3793
|
+
body: n.body,
|
|
3794
|
+
async: n.async === true
|
|
3795
|
+
});
|
|
3796
|
+
formalsByName.set(scope.name, formals);
|
|
3797
|
+
}
|
|
2996
3798
|
const fn2 = env.fns.get(fnName);
|
|
2997
3799
|
if (!fn2) return void 0;
|
|
2998
3800
|
return { params: fn2.params, body: fn2.body, env, formals: formalsByName.get(fnName) ?? [] };
|
|
@@ -3027,7 +3829,7 @@ function generalizeFromAstUncached(fnName, source, opts = {}) {
|
|
|
3027
3829
|
if (opts.refine) {
|
|
3028
3830
|
try {
|
|
3029
3831
|
const r = opts.refine;
|
|
3030
|
-
const hasDirective = source
|
|
3832
|
+
const hasDirective = stripStringsKeepComments(source).includes("@nudo:contract");
|
|
3031
3833
|
const sc = opts.sidecarFp ?? (r.loadModule && r.fromFile ? sidecarClosureFingerprint(r.fromFile, r) : void 0);
|
|
3032
3834
|
const eff = hasDirective || sc !== void 0 ? effectiveInterface(source, fnName, r) : void 0;
|
|
3033
3835
|
const reqs = eff?.params ?? [];
|
|
@@ -3036,6 +3838,7 @@ function generalizeFromAstUncached(fnName, source, opts = {}) {
|
|
|
3036
3838
|
param: e.param,
|
|
3037
3839
|
pred: instantiateConstraint(e.constraint, e.param)
|
|
3038
3840
|
}));
|
|
3841
|
+
const patternFields = /* @__PURE__ */ new Map();
|
|
3039
3842
|
for (const e of reqs) {
|
|
3040
3843
|
const idx = params.indexOf(e.param);
|
|
3041
3844
|
if (idx >= 0) {
|
|
@@ -3048,7 +3851,54 @@ function generalizeFromAstUncached(fnName, source, opts = {}) {
|
|
|
3048
3851
|
abs: snapshotAbs(entryAbs),
|
|
3049
3852
|
source: "refine"
|
|
3050
3853
|
});
|
|
3854
|
+
continue;
|
|
3855
|
+
}
|
|
3856
|
+
const hit = formals.length > 0 ? locateContractParam(formals, e.param) : void 0;
|
|
3857
|
+
if (hit && hit.field !== void 0) {
|
|
3858
|
+
let bag = patternFields.get(hit.index);
|
|
3859
|
+
if (!bag) {
|
|
3860
|
+
bag = {};
|
|
3861
|
+
patternFields.set(hit.index, bag);
|
|
3862
|
+
}
|
|
3863
|
+
const fieldAbs = constraintToEntryAbs(e.constraint, e.param);
|
|
3864
|
+
bag[hit.field] = {
|
|
3865
|
+
abs: fieldAbs,
|
|
3866
|
+
...e.constraint.isOptional ? { optional: true } : {}
|
|
3867
|
+
};
|
|
3868
|
+
} else if (hit) {
|
|
3869
|
+
const entryAbs = constraintToEntryAbs(e.constraint, e.param);
|
|
3870
|
+
typeParams[hit.index] = {
|
|
3871
|
+
id: typeParams[hit.index].id,
|
|
3872
|
+
value: entryAbs
|
|
3873
|
+
};
|
|
3874
|
+
refineEntryShapes.set(e.param, {
|
|
3875
|
+
abs: snapshotAbs(entryAbs),
|
|
3876
|
+
source: "refine"
|
|
3877
|
+
});
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
for (const [pIdx, bag] of patternFields) {
|
|
3881
|
+
const slots = {};
|
|
3882
|
+
for (const [key, rec] of Object.entries(bag)) {
|
|
3883
|
+
slots[key] = {
|
|
3884
|
+
value: rec.abs,
|
|
3885
|
+
...rec.optional ? { optional: true } : {}
|
|
3886
|
+
};
|
|
3051
3887
|
}
|
|
3888
|
+
const objAbs = abs(
|
|
3889
|
+
{ k: "obj", slots },
|
|
3890
|
+
v(params[pIdx] ?? `_p${pIdx}`),
|
|
3891
|
+
void 0,
|
|
3892
|
+
"path"
|
|
3893
|
+
);
|
|
3894
|
+
typeParams[pIdx] = {
|
|
3895
|
+
id: typeParams[pIdx].id,
|
|
3896
|
+
value: objAbs
|
|
3897
|
+
};
|
|
3898
|
+
refineEntryShapes.set(params[pIdx] ?? `_p${pIdx}`, {
|
|
3899
|
+
abs: snapshotAbs(objAbs),
|
|
3900
|
+
source: "refine"
|
|
3901
|
+
});
|
|
3052
3902
|
}
|
|
3053
3903
|
}
|
|
3054
3904
|
} catch {
|
|
@@ -3080,17 +3930,49 @@ function generalizeFromAstUncached(fnName, source, opts = {}) {
|
|
|
3080
3930
|
const hasMocks = inject && Object.keys(inject.mocks ?? {}).length > 0;
|
|
3081
3931
|
const hasEnv = inject && Object.keys(inject.envGlobals ?? {}).length > 0;
|
|
3082
3932
|
const hasReps = inject && Object.keys(inject.replacements ?? {}).length > 0;
|
|
3083
|
-
const
|
|
3084
|
-
const
|
|
3933
|
+
const directiveSrc = stripStringsKeepComments(source);
|
|
3934
|
+
const hasFnMockDirective = /@nudo:mock\s+\w+\s*(?:=|from\b)/.test(directiveSrc);
|
|
3935
|
+
const hasModMockDirective = /@nudo:mock-module\b/.test(directiveSrc);
|
|
3085
3936
|
const hasModInject = inject && inject.modules && Object.keys(inject.modules).length > 0;
|
|
3086
3937
|
const mockGated = hasFnMockDirective && !hasMocks || hasModMockDirective && !hasModInject;
|
|
3087
|
-
const envGatedSource = /@nudo:env\b/.test(
|
|
3938
|
+
const envGatedSource = /@nudo:env\b/.test(directiveSrc) && !hasEnv;
|
|
3088
3939
|
const boundNames = formals.flatMap(
|
|
3089
3940
|
(f) => f.kind === "pattern" ? f.bound : [f.name]
|
|
3090
3941
|
);
|
|
3091
3942
|
const envGated = envGatedSource && freeIdentifiers(body, boundNames).size > 0;
|
|
3092
|
-
const replaceGated = /@nudo:replace\b/.test(
|
|
3943
|
+
const replaceGated = /@nudo:replace\b/.test(directiveSrc) && !hasReps;
|
|
3093
3944
|
const evalEligible = !unresolvableImports && !mockGated && !envGated && !replaceGated;
|
|
3945
|
+
const fnBudget = extractFnBudget(source, fnName);
|
|
3946
|
+
const runCore = (args, phi) => {
|
|
3947
|
+
if (!evalEligible) return void 0;
|
|
3948
|
+
try {
|
|
3949
|
+
const evalRun = evalRunOf(source, opts.modules, opts.inject);
|
|
3950
|
+
if (!evalRun) {
|
|
3951
|
+
return void 0;
|
|
3952
|
+
} else if (fnName.includes(".")) {
|
|
3953
|
+
const [clsName, methodName] = fnName.split(".", 2);
|
|
3954
|
+
const clsAbs = evalRun[clsName ?? ""];
|
|
3955
|
+
if (clsAbs && typeof clsAbs === "object" && "shape" in clsAbs) {
|
|
3956
|
+
const nCtor = ctorParamCountOf(fileAst, clsName ?? "");
|
|
3957
|
+
const inst = $new(clsAbs, Array.from({ length: nCtor }, () => abs({ k: "any" }, void 0, pTrue, "path")));
|
|
3958
|
+
return withExecPhi(phi, () => $invoke(inst, methodName ?? "", args));
|
|
3959
|
+
}
|
|
3960
|
+
return void 0;
|
|
3961
|
+
} else if (fnName in evalRun) {
|
|
3962
|
+
const evalArgs = args.map((a, i) => {
|
|
3963
|
+
const shape2 = promoteScan.promotedShapes.get(params[i]);
|
|
3964
|
+
if (shape2 && (a.shape.k === "any" || a.shape.k === "unknown")) {
|
|
3965
|
+
return { shape: shape2, term: a.term, pred: a.pred, conf: "path" };
|
|
3966
|
+
}
|
|
3967
|
+
return a;
|
|
3968
|
+
});
|
|
3969
|
+
return callTranspiledExportFull(evalRun, fnName, evalArgs, { phi }).result;
|
|
3970
|
+
}
|
|
3971
|
+
return void 0;
|
|
3972
|
+
} catch {
|
|
3973
|
+
return void 0;
|
|
3974
|
+
}
|
|
3975
|
+
};
|
|
3094
3976
|
const run = (args, phi = pTrue) => {
|
|
3095
3977
|
const { key, varOrder } = instantiateMemoKey(args, phi);
|
|
3096
3978
|
const hit = instMemo.get(key);
|
|
@@ -3098,30 +3980,14 @@ function generalizeFromAstUncached(fnName, source, opts = {}) {
|
|
|
3098
3980
|
return alphaRenameResult(hit.result, hit.varOrder, varOrder);
|
|
3099
3981
|
}
|
|
3100
3982
|
let result;
|
|
3101
|
-
if (
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
}
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
const nCtor = ctorParamCountOf(fileAst, clsName ?? "");
|
|
3110
|
-
const inst = $new(clsAbs, Array.from({ length: nCtor }, () => abs({ k: "any" }, void 0, pTrue, "path")));
|
|
3111
|
-
result = withExecPhi(phi, () => $invoke(inst, methodName ?? "", args));
|
|
3112
|
-
}
|
|
3113
|
-
} else if (fnName in evalRun) {
|
|
3114
|
-
const evalArgs = args.map((a, i) => {
|
|
3115
|
-
const shape2 = promoteScan.promotedShapes.get(params[i]);
|
|
3116
|
-
if (shape2 && (a.shape.k === "any" || a.shape.k === "unknown")) {
|
|
3117
|
-
return { shape: shape2, term: a.term, pred: a.pred, conf: "path" };
|
|
3118
|
-
}
|
|
3119
|
-
return a;
|
|
3120
|
-
});
|
|
3121
|
-
result = callTranspiledExportFull(evalRun, fnName, evalArgs, { phi }).result;
|
|
3122
|
-
}
|
|
3123
|
-
} catch {
|
|
3124
|
-
}
|
|
3983
|
+
if (fnBudget) {
|
|
3984
|
+
withFnBudgetOverride(fnBudget, () => {
|
|
3985
|
+
withEvalBudgetOverride(fnBudget, () => {
|
|
3986
|
+
result = runCore(args, phi);
|
|
3987
|
+
});
|
|
3988
|
+
});
|
|
3989
|
+
} else {
|
|
3990
|
+
result = runCore(args, phi);
|
|
3125
3991
|
}
|
|
3126
3992
|
if (result === void 0) {
|
|
3127
3993
|
result = abs({ k: "unknown" }, void 0, void 0, "opaque");
|
|
@@ -3310,6 +4176,8 @@ function primMatches(prim, lv) {
|
|
|
3310
4176
|
return typeof lv === "boolean";
|
|
3311
4177
|
case "object":
|
|
3312
4178
|
return lv === null;
|
|
4179
|
+
case "undefined":
|
|
4180
|
+
return lv === void 0;
|
|
3313
4181
|
default:
|
|
3314
4182
|
return false;
|
|
3315
4183
|
}
|
|
@@ -3317,8 +4185,9 @@ function primMatches(prim, lv) {
|
|
|
3317
4185
|
function predHolds(lv, p) {
|
|
3318
4186
|
switch (p.op) {
|
|
3319
4187
|
case "eq": {
|
|
3320
|
-
const
|
|
3321
|
-
|
|
4188
|
+
const litT = isSelfVar(p.a) && p.b.op === "lit" ? p.b : isSelfVar(p.b) && p.a.op === "lit" ? p.a : void 0;
|
|
4189
|
+
if (!litT) return false;
|
|
4190
|
+
return Object.is(lv, litT.value);
|
|
3322
4191
|
}
|
|
3323
4192
|
case "gt":
|
|
3324
4193
|
case "ge":
|
|
@@ -3336,11 +4205,6 @@ function predHolds(lv, p) {
|
|
|
3336
4205
|
return false;
|
|
3337
4206
|
}
|
|
3338
4207
|
}
|
|
3339
|
-
function selfEqLiteral(p) {
|
|
3340
|
-
if (isSelfVar(p.a) && p.b.op === "lit") return p.b.value;
|
|
3341
|
-
if (isSelfVar(p.b) && p.a.op === "lit") return p.a.value;
|
|
3342
|
-
return void 0;
|
|
3343
|
-
}
|
|
3344
4208
|
function cmpHolds(x, op, n) {
|
|
3345
4209
|
switch (op) {
|
|
3346
4210
|
case "gt":
|
|
@@ -3629,7 +4493,8 @@ function extractLiteralEvidence(rec, idx) {
|
|
|
3629
4493
|
const absArg = rec.argAbs?.[idx];
|
|
3630
4494
|
if (!absArg) return void 0;
|
|
3631
4495
|
if (absArg.conf !== "exact" && absArg.conf !== "path") return void 0;
|
|
3632
|
-
const
|
|
4496
|
+
const lvR = litValue(absArg);
|
|
4497
|
+
const lv = lvR.ok ? lvR.value : void 0;
|
|
3633
4498
|
if (typeof lv === "number" || typeof lv === "string" || typeof lv === "boolean") {
|
|
3634
4499
|
return lv;
|
|
3635
4500
|
}
|
|
@@ -3689,6 +4554,7 @@ function resolveExportSource(modSrc, fnName, loadSpecFrom, depth = 0, baseFromFi
|
|
|
3689
4554
|
}
|
|
3690
4555
|
const file = parseSource(modSrc);
|
|
3691
4556
|
let nextSpec;
|
|
4557
|
+
let nextFnName = fnName;
|
|
3692
4558
|
for (const stmt of file.program.body) {
|
|
3693
4559
|
if (stmt.type !== "ExportNamedDeclaration" && stmt.type !== "ExportAllDeclaration") continue;
|
|
3694
4560
|
const src = stmt.source;
|
|
@@ -3701,8 +4567,11 @@ function resolveExportSource(modSrc, fnName, loadSpecFrom, depth = 0, baseFromFi
|
|
|
3701
4567
|
for (const sp of clause) {
|
|
3702
4568
|
if (sp.type !== "ExportSpecifier") continue;
|
|
3703
4569
|
const local = sp.local;
|
|
3704
|
-
|
|
4570
|
+
const exported = sp.exported ?? sp.local;
|
|
4571
|
+
const exportedName = exported?.type === "Identifier" ? exported.name : exported && "value" in exported ? String(exported.value ?? "") : void 0;
|
|
4572
|
+
if (exportedName === fnName) {
|
|
3705
4573
|
nextSpec = String(src.value);
|
|
4574
|
+
nextFnName = local?.type === "Identifier" && local.name ? local.name : fnName;
|
|
3706
4575
|
break;
|
|
3707
4576
|
}
|
|
3708
4577
|
}
|
|
@@ -3715,7 +4584,7 @@ function resolveExportSource(modSrc, fnName, loadSpecFrom, depth = 0, baseFromFi
|
|
|
3715
4584
|
const nextFromFile = hopFrom ? resolveDepPath(hopFrom, nextSpec) : "";
|
|
3716
4585
|
return resolveExportSource(
|
|
3717
4586
|
next,
|
|
3718
|
-
|
|
4587
|
+
nextFnName,
|
|
3719
4588
|
loadSpecFrom,
|
|
3720
4589
|
depth + 1,
|
|
3721
4590
|
baseFromFile,
|
|
@@ -4143,7 +5012,8 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
4143
5012
|
}
|
|
4144
5013
|
arg = projected;
|
|
4145
5014
|
}
|
|
4146
|
-
const
|
|
5015
|
+
const lvR = litValue(arg);
|
|
5016
|
+
const lv = lvR.ok ? lvR.value : void 0;
|
|
4147
5017
|
const isStr = arg.shape.k === "prim" && arg.shape.type === "string";
|
|
4148
5018
|
const strLen = typeof lv === "string" ? lv.length : void 0;
|
|
4149
5019
|
const argIsUndef = isExactUndef(arg);
|
|
@@ -4352,7 +5222,8 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
4352
5222
|
}
|
|
4353
5223
|
}
|
|
4354
5224
|
if (isIntFlag(constraint)) {
|
|
4355
|
-
const
|
|
5225
|
+
const ivR = litValue(fieldAbs);
|
|
5226
|
+
const iv = ivR.ok ? ivR.value : void 0;
|
|
4356
5227
|
if (typeof iv === "number" && !Number.isInteger(iv)) {
|
|
4357
5228
|
out.push({
|
|
4358
5229
|
severity: "error",
|
|
@@ -4394,7 +5265,8 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
4394
5265
|
});
|
|
4395
5266
|
}
|
|
4396
5267
|
}
|
|
4397
|
-
const
|
|
5268
|
+
const lvR = litValue(fieldAbs);
|
|
5269
|
+
const lv = lvR.ok ? lvR.value : void 0;
|
|
4398
5270
|
const sv = typeof lv === "string" ? lv.length : void 0;
|
|
4399
5271
|
const isStr = fieldAbs.shape.k === "prim" && fieldAbs.shape.type === "string";
|
|
4400
5272
|
for (const p of constraint.preds) {
|
|
@@ -4447,7 +5319,9 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
4447
5319
|
}
|
|
4448
5320
|
}
|
|
4449
5321
|
}
|
|
4450
|
-
|
|
5322
|
+
const fieldLit = fieldAbs.term?.op === "lit" ? fieldAbs.term.value : void 0;
|
|
5323
|
+
const fieldIsLit = fieldAbs.term?.op === "lit";
|
|
5324
|
+
if (fieldIsLit && (typeof fieldLit === "number" || typeof fieldLit === "string" || typeof fieldLit === "boolean" || fieldLit === null || fieldLit === void 0) && ((constraint.members?.length ?? 0) > 0 || constraint.preds.some((p) => p.op === "eq")) && !literalMeetsConstraint(fieldLit, constraint)) {
|
|
4451
5325
|
out.push({
|
|
4452
5326
|
severity: "error",
|
|
4453
5327
|
code: "nudo:constraint-violated",
|
|
@@ -4531,7 +5405,8 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
4531
5405
|
}
|
|
4532
5406
|
}
|
|
4533
5407
|
if (isIntFlag(c)) {
|
|
4534
|
-
const
|
|
5408
|
+
const ivR = litValue(arg);
|
|
5409
|
+
const iv = ivR.ok ? ivR.value : void 0;
|
|
4535
5410
|
if (typeof iv === "number" && !Number.isInteger(iv)) {
|
|
4536
5411
|
out.push({
|
|
4537
5412
|
severity: "error",
|
|
@@ -4845,6 +5720,29 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
4845
5720
|
}
|
|
4846
5721
|
|
|
4847
5722
|
export {
|
|
5723
|
+
listFnDirectiveScopes,
|
|
5724
|
+
findFnDirectiveScope,
|
|
5725
|
+
fnDirectiveCommentLines,
|
|
5726
|
+
parseEnvPayload,
|
|
5727
|
+
parseMockModulePayload,
|
|
5728
|
+
parseNudoImportPayload,
|
|
5729
|
+
extractFileEnvNames,
|
|
5730
|
+
extractMockModuleRecords,
|
|
5731
|
+
extractNudoImportRecords,
|
|
5732
|
+
extractBalancedParens,
|
|
5733
|
+
scanCaseTags,
|
|
5734
|
+
scanCaseArgSpans,
|
|
5735
|
+
scanContractSegments,
|
|
5736
|
+
scanThrowsDecl,
|
|
5737
|
+
scanBudgetDecl,
|
|
5738
|
+
hashSource,
|
|
5739
|
+
resetHashSourceCache,
|
|
5740
|
+
normPath,
|
|
5741
|
+
stablePathKey,
|
|
5742
|
+
stablePathKeyGraph,
|
|
5743
|
+
resolveDepPath,
|
|
5744
|
+
isNodeModulesPath,
|
|
5745
|
+
sidecarPathOf,
|
|
4848
5746
|
SELF,
|
|
4849
5747
|
getTerm,
|
|
4850
5748
|
lenTerm,
|
|
@@ -4858,21 +5756,21 @@ export {
|
|
|
4858
5756
|
shape,
|
|
4859
5757
|
litC,
|
|
4860
5758
|
union,
|
|
5759
|
+
nullable,
|
|
5760
|
+
constraintAdmitsNullish,
|
|
4861
5761
|
fn,
|
|
4862
5762
|
andC,
|
|
4863
5763
|
partial,
|
|
4864
5764
|
pick,
|
|
4865
5765
|
omit,
|
|
5766
|
+
CONSTRAINT_BUILDERS,
|
|
5767
|
+
CONSTRAINT_BUILDER_NAMES,
|
|
5768
|
+
CONSTRAINT_EXPR_RE,
|
|
4866
5769
|
instantiateConstraint,
|
|
5770
|
+
instantiateOnTerm,
|
|
4867
5771
|
constraintToEntryAbs,
|
|
4868
5772
|
fnConstraintToEntryReqs,
|
|
4869
5773
|
throwConstraintToKinds,
|
|
4870
|
-
hashSource,
|
|
4871
|
-
resetHashSourceCache,
|
|
4872
|
-
normPath,
|
|
4873
|
-
resolveDepPath,
|
|
4874
|
-
isNodeModulesPath,
|
|
4875
|
-
sidecarPathOf,
|
|
4876
5774
|
extractNudoImports,
|
|
4877
5775
|
setRefineDiagCollector,
|
|
4878
5776
|
refineDiagCount,
|
|
@@ -4885,12 +5783,14 @@ export {
|
|
|
4885
5783
|
refineToIndexedFull,
|
|
4886
5784
|
extractRefineReturnFromSource,
|
|
4887
5785
|
extractDeclaredThrows,
|
|
5786
|
+
extractFnBudget,
|
|
4888
5787
|
setInterfaceDiagCollector,
|
|
4889
5788
|
interfaceDiagCount,
|
|
4890
5789
|
takeInterfaceDiags,
|
|
4891
5790
|
takeInterfaceDiagsSince,
|
|
4892
5791
|
localNamedExports,
|
|
4893
5792
|
generatedExportNames,
|
|
5793
|
+
resetSidecarLoadFailureCache,
|
|
4894
5794
|
effectiveInterface,
|
|
4895
5795
|
sidecarClosureFingerprint,
|
|
4896
5796
|
formatConstraint,
|