@nudojs/core 1.14.0 → 1.15.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/{chunk-N7AGFMSD.js → chunk-47UPDZHE.js} +450 -53
- package/dist/{chunk-W3Z77Y2I.js → chunk-NUTYUT3I.js} +14439 -13885
- package/dist/exec.d.ts +206 -57
- package/dist/exec.js +1 -1
- package/dist/index.d.ts +141 -8
- package/dist/index.js +61 -395
- package/dist/internal.d.ts +101 -5
- package/dist/internal.js +8 -2
- package/dist/{projection-budget-DPPRBOLp.d.ts → projection-budget-07VnMktV.d.ts} +44 -2
- package/dist/{run-DhSqP3in.d.ts → run-DfloWX6i.d.ts} +9 -0
- package/package.json +2 -2
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
$invoke,
|
|
3
3
|
$new,
|
|
4
4
|
CONSTRAINT_BUILDERS,
|
|
5
|
+
DIAG,
|
|
5
6
|
abs,
|
|
6
7
|
andC,
|
|
7
8
|
anyAbs,
|
|
@@ -47,7 +48,7 @@ import {
|
|
|
47
48
|
withEvalBudgetOverride,
|
|
48
49
|
withExecPhi,
|
|
49
50
|
withFnBudgetOverride
|
|
50
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-NUTYUT3I.js";
|
|
51
52
|
|
|
52
53
|
// src/algebra/directive-scan.ts
|
|
53
54
|
function cleanDirectiveLine(raw, kind) {
|
|
@@ -823,12 +824,12 @@ function diagnoseMalformedNudoImports(source) {
|
|
|
823
824
|
for (const bad of scanMalformedNudoImports(source)) {
|
|
824
825
|
if (bad === "malformed-default") {
|
|
825
826
|
collectDiag({
|
|
826
|
-
code:
|
|
827
|
+
code: DIAG.contractSyntax,
|
|
827
828
|
message: `Default @nudo:import is not supported (use named { a, b } or namespace * as ns)`
|
|
828
829
|
});
|
|
829
830
|
} else {
|
|
830
831
|
collectDiag({
|
|
831
|
-
code:
|
|
832
|
+
code: DIAG.contractSyntax,
|
|
832
833
|
message: `Unrecognized @nudo:import form (expected { names } from "path" or * as ns from "path")`
|
|
833
834
|
});
|
|
834
835
|
}
|
|
@@ -896,7 +897,7 @@ function rewriteSidecarSource(src, fromFile) {
|
|
|
896
897
|
const DEFAULT_LOCAL = "__nudoDefault";
|
|
897
898
|
const badExport = (what) => {
|
|
898
899
|
collectDiag({
|
|
899
|
-
code:
|
|
900
|
+
code: DIAG.interfaceLoad,
|
|
900
901
|
message: `sidecar ${what}; recognized export forms are 'export const <name> = \u2026' and 'export default <constraint|fn>'`,
|
|
901
902
|
file: fromFile
|
|
902
903
|
});
|
|
@@ -1195,7 +1196,7 @@ function execSidecar(src, opts, chain, preloaded) {
|
|
|
1195
1196
|
for (const n of imp.names) {
|
|
1196
1197
|
if (n.ns) {
|
|
1197
1198
|
collectDiag({
|
|
1198
|
-
code:
|
|
1199
|
+
code: DIAG.interfaceLoad,
|
|
1199
1200
|
message: `sidecar namespace import '${n.local}' from '${imp.spec}' is not supported (import named builders only)`,
|
|
1200
1201
|
file: fromFile
|
|
1201
1202
|
});
|
|
@@ -1204,7 +1205,7 @@ function execSidecar(src, opts, chain, preloaded) {
|
|
|
1204
1205
|
const hasOwnInject = Object.hasOwn(sidecarInjects, n.imported);
|
|
1205
1206
|
if (!hasOwnInject) {
|
|
1206
1207
|
collectDiag({
|
|
1207
|
-
code:
|
|
1208
|
+
code: DIAG.interfaceLoad,
|
|
1208
1209
|
message: `sidecar import '{ ${n.imported} }' from '${imp.spec}' is not an injected builder`,
|
|
1209
1210
|
file: fromFile
|
|
1210
1211
|
});
|
|
@@ -1214,7 +1215,7 @@ function execSidecar(src, opts, chain, preloaded) {
|
|
|
1214
1215
|
}
|
|
1215
1216
|
} else {
|
|
1216
1217
|
collectDiag({
|
|
1217
|
-
code:
|
|
1218
|
+
code: DIAG.interfaceLoad,
|
|
1218
1219
|
message: `sidecar default import '${n.local}' from '${imp.spec}' has no default export to bind (sidecars export named constraints only)`,
|
|
1219
1220
|
file: fromFile
|
|
1220
1221
|
});
|
|
@@ -1226,7 +1227,7 @@ function execSidecar(src, opts, chain, preloaded) {
|
|
|
1226
1227
|
const load = opts?.loadModule;
|
|
1227
1228
|
if (!load || !fromFile) {
|
|
1228
1229
|
collectDiag({
|
|
1229
|
-
code:
|
|
1230
|
+
code: DIAG.interfaceLoad,
|
|
1230
1231
|
message: `sidecar import '${imp.spec}' is unresolvable (no loadModule for relative specifier)`,
|
|
1231
1232
|
file: fromFile
|
|
1232
1233
|
});
|
|
@@ -1235,7 +1236,7 @@ function execSidecar(src, opts, chain, preloaded) {
|
|
|
1235
1236
|
}
|
|
1236
1237
|
if (!isSidecarSpec(imp.spec)) {
|
|
1237
1238
|
collectDiag({
|
|
1238
|
-
code:
|
|
1239
|
+
code: DIAG.interfaceLoad,
|
|
1239
1240
|
message: `sidecar import '${imp.spec}' unsupported: relative imports must target .nudo.js/.nudo.ts`,
|
|
1240
1241
|
file: fromFile
|
|
1241
1242
|
});
|
|
@@ -1245,7 +1246,7 @@ function execSidecar(src, opts, chain, preloaded) {
|
|
|
1245
1246
|
const canonical = resolveDepPath(fromFile, imp.spec);
|
|
1246
1247
|
if (isNodeModulesPath(canonical)) {
|
|
1247
1248
|
collectDiag({
|
|
1248
|
-
code:
|
|
1249
|
+
code: DIAG.interfaceLoad,
|
|
1249
1250
|
message: `sidecar import '${imp.spec}' resolves into node_modules (${canonical}); ambient sidecar loading never crosses node_modules`,
|
|
1250
1251
|
file: fromFile
|
|
1251
1252
|
});
|
|
@@ -1254,14 +1255,14 @@ function execSidecar(src, opts, chain, preloaded) {
|
|
|
1254
1255
|
}
|
|
1255
1256
|
if (chain.includes(canonical)) {
|
|
1256
1257
|
throw new NudoSidecarError(
|
|
1257
|
-
|
|
1258
|
+
DIAG.interfaceCycle,
|
|
1258
1259
|
`sidecar import cycle: ${[...chain, canonical].join(" \u2192 ")}`
|
|
1259
1260
|
);
|
|
1260
1261
|
}
|
|
1261
1262
|
const depSrc = preloaded.has(canonical) ? preloaded.get(canonical) : load(imp.spec, fromFile);
|
|
1262
1263
|
if (depSrc === void 0) {
|
|
1263
1264
|
collectDiag({
|
|
1264
|
-
code:
|
|
1265
|
+
code: DIAG.interfaceLoad,
|
|
1265
1266
|
message: `sidecar import '${imp.spec}' is unresolvable (loadModule returned no source)`,
|
|
1266
1267
|
file: fromFile
|
|
1267
1268
|
});
|
|
@@ -1283,7 +1284,7 @@ function execSidecar(src, opts, chain, preloaded) {
|
|
|
1283
1284
|
deps[n.local] = depExports[n.imported];
|
|
1284
1285
|
} else {
|
|
1285
1286
|
collectDiag({
|
|
1286
|
-
code:
|
|
1287
|
+
code: DIAG.interfaceLoad,
|
|
1287
1288
|
message: `sidecar '${imp.spec}' has no export '${n.imported}'`,
|
|
1288
1289
|
file: fromFile
|
|
1289
1290
|
});
|
|
@@ -1291,7 +1292,7 @@ function execSidecar(src, opts, chain, preloaded) {
|
|
|
1291
1292
|
}
|
|
1292
1293
|
} else {
|
|
1293
1294
|
collectDiag({
|
|
1294
|
-
code:
|
|
1295
|
+
code: DIAG.interfaceLoad,
|
|
1295
1296
|
message: `sidecar default import '${n.local}' from '${imp.spec}' has no default export (sidecars export named constraints only)`,
|
|
1296
1297
|
file: fromFile
|
|
1297
1298
|
});
|
|
@@ -1430,7 +1431,7 @@ function collectConstraints(source, opts) {
|
|
|
1430
1431
|
for (const imp of imports) {
|
|
1431
1432
|
if (!opts.loadModule || !opts.fromFile) {
|
|
1432
1433
|
collectDiag({
|
|
1433
|
-
code:
|
|
1434
|
+
code: DIAG.interfaceLoad,
|
|
1434
1435
|
message: `sidecar '${imp.spec}' not loaded: loadModule/fromFile missing from host options`,
|
|
1435
1436
|
file: opts.fromFile
|
|
1436
1437
|
});
|
|
@@ -1441,7 +1442,7 @@ function collectConstraints(source, opts) {
|
|
|
1441
1442
|
src = opts.loadModule(imp.spec, opts.fromFile);
|
|
1442
1443
|
} catch (e) {
|
|
1443
1444
|
collectDiag({
|
|
1444
|
-
code:
|
|
1445
|
+
code: DIAG.interfaceLoad,
|
|
1445
1446
|
message: `sidecar '${imp.spec}' failed to load from '${opts.fromFile}': ${e instanceof Error ? e.message : String(e)}`,
|
|
1446
1447
|
file: opts.fromFile
|
|
1447
1448
|
});
|
|
@@ -1449,7 +1450,7 @@ function collectConstraints(source, opts) {
|
|
|
1449
1450
|
}
|
|
1450
1451
|
if (src === void 0) {
|
|
1451
1452
|
collectDiag({
|
|
1452
|
-
code:
|
|
1453
|
+
code: DIAG.interfaceLoad,
|
|
1453
1454
|
message: `sidecar '${imp.spec}' failed to load from '${opts.fromFile}'`,
|
|
1454
1455
|
file: opts.fromFile
|
|
1455
1456
|
});
|
|
@@ -1464,7 +1465,7 @@ function collectConstraints(source, opts) {
|
|
|
1464
1465
|
collectDiag({ code: e.code, message: e.message, file: opts.fromFile });
|
|
1465
1466
|
} else {
|
|
1466
1467
|
collectDiag({
|
|
1467
|
-
code:
|
|
1468
|
+
code: DIAG.interfaceLoad,
|
|
1468
1469
|
message: `sidecar '${imp.spec}' failed to execute: ${e instanceof Error ? e.message : String(e)}`,
|
|
1469
1470
|
file: opts.fromFile
|
|
1470
1471
|
});
|
|
@@ -1487,13 +1488,13 @@ function collectConstraints(source, opts) {
|
|
|
1487
1488
|
map.set(name, v2);
|
|
1488
1489
|
} else if (v2 === void 0) {
|
|
1489
1490
|
collectDiag({
|
|
1490
|
-
code:
|
|
1491
|
+
code: DIAG.interfaceLoad,
|
|
1491
1492
|
message: `sidecar '${imp.spec}' has no export '${imported}'`,
|
|
1492
1493
|
file: opts.fromFile
|
|
1493
1494
|
});
|
|
1494
1495
|
} else {
|
|
1495
1496
|
collectDiag({
|
|
1496
|
-
code:
|
|
1497
|
+
code: DIAG.interfaceLoad,
|
|
1497
1498
|
message: `sidecar '${imp.spec}' export '${imported}' is not a Nudo constraint`,
|
|
1498
1499
|
file: opts.fromFile
|
|
1499
1500
|
});
|
|
@@ -1580,7 +1581,7 @@ function extractRefinesFromSource(source, fnName, opts = {}) {
|
|
|
1580
1581
|
if (!m) {
|
|
1581
1582
|
if (/^return\b/.test(part)) continue;
|
|
1582
1583
|
collectDiag({
|
|
1583
|
-
code:
|
|
1584
|
+
code: DIAG.contractSyntax,
|
|
1584
1585
|
message: `Malformed @nudo:contract segment '${part}' (expected <param> <constraintName> or return <constraintName>; e.g. @nudo:contract x positive)`
|
|
1585
1586
|
});
|
|
1586
1587
|
continue;
|
|
@@ -1590,7 +1591,7 @@ function extractRefinesFromSource(source, fnName, opts = {}) {
|
|
|
1590
1591
|
const c = constraints.get(cName);
|
|
1591
1592
|
if (!c) {
|
|
1592
1593
|
collectDiag({
|
|
1593
|
-
code:
|
|
1594
|
+
code: DIAG.contractSyntax,
|
|
1594
1595
|
message: `Unknown constraint name '${cName}' in @nudo:contract (expected a name bound by @nudo:import)`
|
|
1595
1596
|
});
|
|
1596
1597
|
continue;
|
|
@@ -1624,7 +1625,7 @@ function extractRefineReturnFromSource(source, fnName, opts = {}) {
|
|
|
1624
1625
|
if (!m) {
|
|
1625
1626
|
if (/^return\b/.test(part) && !/^return\s+[\w.]+$/.test(part)) {
|
|
1626
1627
|
collectDiag({
|
|
1627
|
-
code:
|
|
1628
|
+
code: DIAG.contractSyntax,
|
|
1628
1629
|
message: `Malformed @nudo:contract return segment '${part}' (expected return <constraintName>)`
|
|
1629
1630
|
});
|
|
1630
1631
|
}
|
|
@@ -1634,7 +1635,7 @@ function extractRefineReturnFromSource(source, fnName, opts = {}) {
|
|
|
1634
1635
|
const c = constraints.get(cName);
|
|
1635
1636
|
if (!c) {
|
|
1636
1637
|
collectDiag({
|
|
1637
|
-
code:
|
|
1638
|
+
code: DIAG.contractSyntax,
|
|
1638
1639
|
message: `Unknown constraint name '${cName}' in @nudo:contract return (expected a name bound by @nudo:import)`
|
|
1639
1640
|
});
|
|
1640
1641
|
continue;
|
|
@@ -2010,7 +2011,7 @@ function loadSidecarBinding(source, fnName, opts) {
|
|
|
2010
2011
|
const reason = e instanceof Error ? e.message : String(e);
|
|
2011
2012
|
if (noteSidecarLoadFailure(sidecarPath, reason)) {
|
|
2012
2013
|
collectDiag2({
|
|
2013
|
-
code:
|
|
2014
|
+
code: DIAG.interfaceLoad,
|
|
2014
2015
|
message: `sidecar '${spec}' failed to load: ${reason}`,
|
|
2015
2016
|
file: sidecarPath
|
|
2016
2017
|
});
|
|
@@ -2026,7 +2027,7 @@ function loadSidecarBinding(source, fnName, opts) {
|
|
|
2026
2027
|
exports = execNudoModule(sidecarSrc, { loadModule, fromFile: sidecarPath });
|
|
2027
2028
|
} catch (e) {
|
|
2028
2029
|
const reason = e instanceof Error ? e.message : String(e);
|
|
2029
|
-
const code = e instanceof NudoSidecarError ? e.code :
|
|
2030
|
+
const code = e instanceof NudoSidecarError ? e.code : DIAG.interfaceLoad;
|
|
2030
2031
|
if (noteSidecarLoadFailure(sidecarPath, reason)) {
|
|
2031
2032
|
let affected = 1;
|
|
2032
2033
|
if (scNames && scNames.size > 0) {
|
|
@@ -2073,7 +2074,7 @@ function loadSidecarBinding(source, fnName, opts) {
|
|
|
2073
2074
|
}
|
|
2074
2075
|
if (binding === void 0 && method !== void 0 && Object.hasOwn(exports, method)) {
|
|
2075
2076
|
collectDiag2({
|
|
2076
|
-
code:
|
|
2077
|
+
code: DIAG.interfaceLoad,
|
|
2077
2078
|
message: `sidecar key '${method}' does not bind '${fnName}' \u2014 use '${cls}.${method}', '${cls}_${method}', or nested { ${cls}: { ${method}: \u2026 } }`,
|
|
2078
2079
|
file: sidecarPath
|
|
2079
2080
|
});
|
|
@@ -2082,7 +2083,7 @@ function loadSidecarBinding(source, fnName, opts) {
|
|
|
2082
2083
|
if (binding === void 0) return { ok: false };
|
|
2083
2084
|
if (!isNudoConstraint(binding)) {
|
|
2084
2085
|
collectDiag2({
|
|
2085
|
-
code:
|
|
2086
|
+
code: DIAG.interfaceLoad,
|
|
2086
2087
|
message: `sidecar '${spec}' export '${fnName}' is not a Nudo constraint`,
|
|
2087
2088
|
file: sidecarPath
|
|
2088
2089
|
});
|
|
@@ -2090,7 +2091,7 @@ function loadSidecarBinding(source, fnName, opts) {
|
|
|
2090
2091
|
}
|
|
2091
2092
|
if (!binding.fn) {
|
|
2092
2093
|
collectDiag2({
|
|
2093
|
-
code:
|
|
2094
|
+
code: DIAG.interfaceLoad,
|
|
2094
2095
|
message: `sidecar '${spec}' binding '${fnName}' is not fn() form (Phase 1 consumes only fn({ \u2026 }, \u2026) bindings)`,
|
|
2095
2096
|
file: sidecarPath
|
|
2096
2097
|
});
|
|
@@ -4298,7 +4299,7 @@ function checkInjectedDomainEvidence(fnName, source, records, opts) {
|
|
|
4298
4299
|
return [
|
|
4299
4300
|
{
|
|
4300
4301
|
severity: "warning",
|
|
4301
|
-
code:
|
|
4302
|
+
code: DIAG.interfaceLoad,
|
|
4302
4303
|
message: `${fnName}: effective interface load failed (${e instanceof Error ? e.message : String(e)}); skipping domain-exceeds check`,
|
|
4303
4304
|
fn: fnName,
|
|
4304
4305
|
line: opts.loc?.line,
|
|
@@ -4322,7 +4323,7 @@ function checkInjectedDomainEvidence(fnName, source, records, opts) {
|
|
|
4322
4323
|
const shown = [...new Set(failures)].map(evidenceToString).join(", ");
|
|
4323
4324
|
out.push({
|
|
4324
4325
|
severity: "error",
|
|
4325
|
-
code:
|
|
4326
|
+
code: DIAG.interfaceDomainExceeds,
|
|
4326
4327
|
message: `${fnName}[${param}]: cross-file call-site domain evidence ${shown} exceeds handwritten contract`,
|
|
4327
4328
|
actual: shown,
|
|
4328
4329
|
expected: formatConstraint(constraint),
|
|
@@ -4848,7 +4849,7 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
4848
4849
|
const field = fieldPath.join(".");
|
|
4849
4850
|
out.push({
|
|
4850
4851
|
severity: "error",
|
|
4851
|
-
code:
|
|
4852
|
+
code: DIAG.constraintViolated,
|
|
4852
4853
|
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4853
4854
|
actual: formatAbs(arg),
|
|
4854
4855
|
expected: `missing field ${field}`,
|
|
@@ -4875,7 +4876,7 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
4875
4876
|
if (actualPrim !== void 0 && actualPrim !== expected) {
|
|
4876
4877
|
out.push({
|
|
4877
4878
|
severity: "error",
|
|
4878
|
-
code:
|
|
4879
|
+
code: DIAG.constraintViolated,
|
|
4879
4880
|
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4880
4881
|
actual: formatAbs(arg),
|
|
4881
4882
|
expected: `typeof ${paramName} = "${expected}"`,
|
|
@@ -4891,7 +4892,7 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
4891
4892
|
if (p.op === "gt" || p.op === "ge" || p.op === "lt" || p.op === "le" || p.op === "eq" || p.op === "or") {
|
|
4892
4893
|
out.push({
|
|
4893
4894
|
severity: "error",
|
|
4894
|
-
code:
|
|
4895
|
+
code: DIAG.constraintViolated,
|
|
4895
4896
|
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4896
4897
|
actual: formatAbs(arg),
|
|
4897
4898
|
expected: predToString(p),
|
|
@@ -4910,7 +4911,7 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
4910
4911
|
if (v2 !== void 0 && lv !== v2) {
|
|
4911
4912
|
out.push({
|
|
4912
4913
|
severity: "error",
|
|
4913
|
-
code:
|
|
4914
|
+
code: DIAG.constraintViolated,
|
|
4914
4915
|
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4915
4916
|
actual: formatAbs(arg),
|
|
4916
4917
|
expected: predToString(p),
|
|
@@ -4936,7 +4937,7 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
4936
4937
|
if (!pass && !unknown2) {
|
|
4937
4938
|
out.push({
|
|
4938
4939
|
severity: "error",
|
|
4939
|
-
code:
|
|
4940
|
+
code: DIAG.constraintViolated,
|
|
4940
4941
|
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4941
4942
|
actual: formatAbs(arg),
|
|
4942
4943
|
expected: predToString(p),
|
|
@@ -4961,7 +4962,7 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
4961
4962
|
if (!ok2) {
|
|
4962
4963
|
out.push({
|
|
4963
4964
|
severity: "error",
|
|
4964
|
-
code:
|
|
4965
|
+
code: DIAG.constraintViolated,
|
|
4965
4966
|
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4966
4967
|
actual: formatAbs(arg),
|
|
4967
4968
|
expected: `length(${paramName}) ${opSym} ${n}`,
|
|
@@ -4976,7 +4977,7 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
4976
4977
|
if (typeof lv === "boolean" || lv === null) {
|
|
4977
4978
|
out.push({
|
|
4978
4979
|
severity: "error",
|
|
4979
|
-
code:
|
|
4980
|
+
code: DIAG.constraintViolated,
|
|
4980
4981
|
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
4981
4982
|
actual: formatAbs(arg),
|
|
4982
4983
|
expected: predToString(p),
|
|
@@ -4996,7 +4997,7 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
4996
4997
|
if (!ok) {
|
|
4997
4998
|
out.push({
|
|
4998
4999
|
severity: "error",
|
|
4999
|
-
code:
|
|
5000
|
+
code: DIAG.constraintViolated,
|
|
5000
5001
|
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
5001
5002
|
actual: formatAbs(arg),
|
|
5002
5003
|
expected: predToString(p),
|
|
@@ -5018,7 +5019,7 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
5018
5019
|
if (absArg.shape.k !== "unknown" && absArg.shape.k !== "never" && absArg.shape.k !== "any") {
|
|
5019
5020
|
out.push({
|
|
5020
5021
|
severity: "error",
|
|
5021
|
-
code:
|
|
5022
|
+
code: DIAG.constraintViolated,
|
|
5022
5023
|
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
5023
5024
|
actual: formatAbs(absArg),
|
|
5024
5025
|
expected: `object shape at ${path}`,
|
|
@@ -5037,7 +5038,7 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
5037
5038
|
if (!field.optional && !field.constraint.isOptional) {
|
|
5038
5039
|
out.push({
|
|
5039
5040
|
severity: "error",
|
|
5040
|
-
code:
|
|
5041
|
+
code: DIAG.constraintViolated,
|
|
5041
5042
|
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
5042
5043
|
actual: formatAbs(absArg),
|
|
5043
5044
|
expected: `missing field ${fieldPath}`,
|
|
@@ -5059,7 +5060,7 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
5059
5060
|
if (actualPrim !== constraint.prim) {
|
|
5060
5061
|
out.push({
|
|
5061
5062
|
severity: "error",
|
|
5062
|
-
code:
|
|
5063
|
+
code: DIAG.constraintViolated,
|
|
5063
5064
|
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
5064
5065
|
actual: formatAbs(fieldAbs),
|
|
5065
5066
|
expected: `typeof ${fieldPath} = "${constraint.prim}"`,
|
|
@@ -5077,7 +5078,7 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
5077
5078
|
if (typeof iv === "number" && !Number.isInteger(iv)) {
|
|
5078
5079
|
out.push({
|
|
5079
5080
|
severity: "error",
|
|
5080
|
-
code:
|
|
5081
|
+
code: DIAG.constraintViolated,
|
|
5081
5082
|
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
5082
5083
|
actual: formatAbs(fieldAbs),
|
|
5083
5084
|
expected: `${fieldPath} is int`,
|
|
@@ -5135,7 +5136,7 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
5135
5136
|
if (!ok2) {
|
|
5136
5137
|
out.push({
|
|
5137
5138
|
severity: "error",
|
|
5138
|
-
code:
|
|
5139
|
+
code: DIAG.constraintViolated,
|
|
5139
5140
|
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
5140
5141
|
actual: formatAbs(fieldAbs),
|
|
5141
5142
|
expected: `length(${fieldPath}) ${opSym} ${n}`,
|
|
@@ -5156,7 +5157,7 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
5156
5157
|
if (!ok) {
|
|
5157
5158
|
out.push({
|
|
5158
5159
|
severity: "error",
|
|
5159
|
-
code:
|
|
5160
|
+
code: DIAG.constraintViolated,
|
|
5160
5161
|
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
5161
5162
|
actual: formatAbs(fieldAbs),
|
|
5162
5163
|
expected: `${fieldPath} ${opSym} ${n}`,
|
|
@@ -5174,7 +5175,7 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
5174
5175
|
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)) {
|
|
5175
5176
|
out.push({
|
|
5176
5177
|
severity: "error",
|
|
5177
|
-
code:
|
|
5178
|
+
code: DIAG.constraintViolated,
|
|
5178
5179
|
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
5179
5180
|
actual: formatAbs(fieldAbs),
|
|
5180
5181
|
expected: `${fieldPath} \u2208 ${formatConstraint(constraint)}`,
|
|
@@ -5202,7 +5203,7 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
5202
5203
|
const field = fieldPath.join(".");
|
|
5203
5204
|
out.push({
|
|
5204
5205
|
severity: "error",
|
|
5205
|
-
code:
|
|
5206
|
+
code: DIAG.constraintViolated,
|
|
5206
5207
|
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
5207
5208
|
actual: formatAbs(arg),
|
|
5208
5209
|
expected: `missing field ${field}`,
|
|
@@ -5244,7 +5245,7 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
5244
5245
|
} else if (isExactUndef(arg) || arg.shape.k !== "unknown" && arg.shape.k !== "any") {
|
|
5245
5246
|
out.push({
|
|
5246
5247
|
severity: "error",
|
|
5247
|
-
code:
|
|
5248
|
+
code: DIAG.constraintViolated,
|
|
5248
5249
|
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
5249
5250
|
actual: formatAbs(arg),
|
|
5250
5251
|
expected: `array at ${paramName}`,
|
|
@@ -5261,7 +5262,7 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
5261
5262
|
if (typeof iv === "number" && !Number.isInteger(iv)) {
|
|
5262
5263
|
out.push({
|
|
5263
5264
|
severity: "error",
|
|
5264
|
-
code:
|
|
5265
|
+
code: DIAG.constraintViolated,
|
|
5265
5266
|
message: `${displayName}[${paramName}]: argument \u22AD precondition`,
|
|
5266
5267
|
actual: formatAbs(arg),
|
|
5267
5268
|
expected: `${paramName} is int`,
|
|
@@ -5450,7 +5451,7 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
5450
5451
|
} catch {
|
|
5451
5452
|
out.push({
|
|
5452
5453
|
severity: "warning",
|
|
5453
|
-
code:
|
|
5454
|
+
code: DIAG.noSignature,
|
|
5454
5455
|
message: `${displayName}: external callee contract could not be resolved; skipping call-site precondition check`,
|
|
5455
5456
|
fn: displayName,
|
|
5456
5457
|
line: loc?.start.line,
|
|
@@ -5493,7 +5494,7 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
5493
5494
|
const isPromote = rel.source === "promote";
|
|
5494
5495
|
out.push({
|
|
5495
5496
|
severity: isPromote ? "warning" : "error",
|
|
5496
|
-
code:
|
|
5497
|
+
code: DIAG.argStructure,
|
|
5497
5498
|
message: `${displayName}[${pname}]: argument is not a callable fn`,
|
|
5498
5499
|
actual: formatAbs(absArg),
|
|
5499
5500
|
expected: formatShape(expected),
|
|
@@ -5527,7 +5528,7 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
5527
5528
|
} catch (e) {
|
|
5528
5529
|
out.push({
|
|
5529
5530
|
severity: "warning",
|
|
5530
|
-
code:
|
|
5531
|
+
code: DIAG.noSignature,
|
|
5531
5532
|
message: `${displayName ?? fnName}: signature recovery failed (${e instanceof Error ? e.message : String(e)}); skipping call-site checks`,
|
|
5532
5533
|
fn: displayName ?? fnName,
|
|
5533
5534
|
line: loc?.start.line,
|
|
@@ -5570,6 +5571,395 @@ function scanLiteralCalls(source, knownFns, phi, opts) {
|
|
|
5570
5571
|
return out;
|
|
5571
5572
|
}
|
|
5572
5573
|
|
|
5574
|
+
// src/algebra/check-report.ts
|
|
5575
|
+
function actionsForIssue(i) {
|
|
5576
|
+
const draft = {
|
|
5577
|
+
kind: "draft",
|
|
5578
|
+
command: "nudo contract --draft",
|
|
5579
|
+
label: "emit a sidecar draft you can edit"
|
|
5580
|
+
};
|
|
5581
|
+
switch (i.code) {
|
|
5582
|
+
case DIAG.constraintViolated:
|
|
5583
|
+
return [
|
|
5584
|
+
{
|
|
5585
|
+
kind: "callsite",
|
|
5586
|
+
label: "use a value satisfying the constraint",
|
|
5587
|
+
...i.expected ? { hint: i.expected } : {}
|
|
5588
|
+
},
|
|
5589
|
+
{
|
|
5590
|
+
kind: "relax",
|
|
5591
|
+
label: "relax the precondition (edit *.nudo.js / @nudo:contract)",
|
|
5592
|
+
...i.expected ? { hint: i.expected } : {}
|
|
5593
|
+
},
|
|
5594
|
+
draft
|
|
5595
|
+
];
|
|
5596
|
+
case DIAG.assignMismatch:
|
|
5597
|
+
return [
|
|
5598
|
+
{
|
|
5599
|
+
kind: "callsite",
|
|
5600
|
+
label: "align the assigned value with the existing shape",
|
|
5601
|
+
...i.expected ? { hint: i.expected } : {}
|
|
5602
|
+
},
|
|
5603
|
+
draft
|
|
5604
|
+
];
|
|
5605
|
+
case DIAG.entryMayThrow:
|
|
5606
|
+
return [
|
|
5607
|
+
{
|
|
5608
|
+
kind: "draft",
|
|
5609
|
+
command: "nudo contract --draft",
|
|
5610
|
+
label: "narrow the entry with a sidecar fn({ \u2026 }) param contract \u2014 most honest fix for helpers (also tightens the signature)",
|
|
5611
|
+
...i.fn ? { hint: i.fn } : {}
|
|
5612
|
+
},
|
|
5613
|
+
{
|
|
5614
|
+
kind: "relax",
|
|
5615
|
+
label: "declare intentional fail-fast: @nudo:throws Error (or case `!! throws`)",
|
|
5616
|
+
...i.fn ? { hint: i.fn } : {}
|
|
5617
|
+
},
|
|
5618
|
+
{
|
|
5619
|
+
kind: "info",
|
|
5620
|
+
label: "or refine / guard / try-catch the entry so it is total"
|
|
5621
|
+
},
|
|
5622
|
+
{
|
|
5623
|
+
kind: "ignore-throws",
|
|
5624
|
+
command: "nudo check --ignore-throws TypeError",
|
|
5625
|
+
label: "migration switch: ignore this throw class on L2"
|
|
5626
|
+
}
|
|
5627
|
+
];
|
|
5628
|
+
case DIAG.unprovenReturn:
|
|
5629
|
+
return [
|
|
5630
|
+
{
|
|
5631
|
+
kind: "relax",
|
|
5632
|
+
label: `relax the return contract${i.fn ? ` of ${i.fn}` : ""} to the inferred surface (review the diff \u2014 not a silent green)`,
|
|
5633
|
+
...i.expected ? { hint: i.expected } : {}
|
|
5634
|
+
},
|
|
5635
|
+
draft,
|
|
5636
|
+
{
|
|
5637
|
+
kind: "info",
|
|
5638
|
+
label: "or narrow the return expression / add a precondition so the bound is implied"
|
|
5639
|
+
}
|
|
5640
|
+
];
|
|
5641
|
+
case DIAG.unknownInference:
|
|
5642
|
+
case DIAG.opaqueResult:
|
|
5643
|
+
return [
|
|
5644
|
+
{ kind: "mock", label: "pin the native/unknown face with @nudo:mock" },
|
|
5645
|
+
{ kind: "assume", command: "nudo check --assume", label: "state preconditions via --assume" },
|
|
5646
|
+
{ kind: "info", label: "or add a call site / @nudo:case for evidence" }
|
|
5647
|
+
];
|
|
5648
|
+
case DIAG.argOpaque:
|
|
5649
|
+
case DIAG.constraintUnproven:
|
|
5650
|
+
return [
|
|
5651
|
+
{ kind: "assume", command: "nudo check --assume", label: "state preconditions via --assume" },
|
|
5652
|
+
{ kind: "info", label: "add a call site or @nudo:case" }
|
|
5653
|
+
];
|
|
5654
|
+
case DIAG.forkTruncated:
|
|
5655
|
+
case DIAG.promiseMicroTruncated:
|
|
5656
|
+
case DIAG.promiseMicroError:
|
|
5657
|
+
return [
|
|
5658
|
+
{
|
|
5659
|
+
kind: "info",
|
|
5660
|
+
label: "results widened to unknown \u2014 raise budget (@nudo:budget forks/calls/depth on the function) or narrow control flow"
|
|
5661
|
+
}
|
|
5662
|
+
];
|
|
5663
|
+
case DIAG.loopTruncated:
|
|
5664
|
+
return [
|
|
5665
|
+
{
|
|
5666
|
+
kind: "info",
|
|
5667
|
+
label: "loop state widened \u2014 the exit condition stayed concrete-true past the iteration cap; make the bound data-dependent (abstract) or pin the result with @nudo:case / @nudo:contract"
|
|
5668
|
+
}
|
|
5669
|
+
];
|
|
5670
|
+
case DIAG.recursionTruncated:
|
|
5671
|
+
return [
|
|
5672
|
+
{
|
|
5673
|
+
kind: "info",
|
|
5674
|
+
label: i.reason === "cycle" ? "call cycle: same abstract arguments already on the stack \u2014 raising @nudo:budget cannot lift it; make the arguments vary per recursion level or declare @nudo:contract" : i.reason === "depth" || i.reason === "calls" || i.reason === "budget" ? `results widened to unknown \u2014 raise budget (@nudo:budget ${i.reason === "depth" ? "depth" : i.reason === "calls" ? "calls" : "calls/depth"} on the function) or narrow the recursion` : "results widened to unknown \u2014 raise budget (@nudo:budget calls/depth on the function) or narrow control flow"
|
|
5675
|
+
}
|
|
5676
|
+
];
|
|
5677
|
+
case DIAG.hostEffectBlocked:
|
|
5678
|
+
return [
|
|
5679
|
+
{ kind: "mock", label: "pin the host API with @nudo:mock / @nudo:env" },
|
|
5680
|
+
{ kind: "info", label: "results widened to unknown \u2014 or feed the value in from a call site" }
|
|
5681
|
+
];
|
|
5682
|
+
case DIAG.interfaceDrift:
|
|
5683
|
+
return [
|
|
5684
|
+
{
|
|
5685
|
+
kind: "emit",
|
|
5686
|
+
command: "nudo contract --emit",
|
|
5687
|
+
label: "refresh the @generated segment"
|
|
5688
|
+
}
|
|
5689
|
+
];
|
|
5690
|
+
case DIAG.dualEntry:
|
|
5691
|
+
return [
|
|
5692
|
+
{
|
|
5693
|
+
kind: "info",
|
|
5694
|
+
label: "browser/node records do not cross files \u2014 analyze the entry you ship; mock or skip the other variant"
|
|
5695
|
+
}
|
|
5696
|
+
];
|
|
5697
|
+
default:
|
|
5698
|
+
return i.suggestion ? [{ kind: "info", label: i.suggestion }] : [];
|
|
5699
|
+
}
|
|
5700
|
+
}
|
|
5701
|
+
function serializeCheckJson(r) {
|
|
5702
|
+
return {
|
|
5703
|
+
version: 1,
|
|
5704
|
+
file: r.file,
|
|
5705
|
+
ok: r.ok,
|
|
5706
|
+
summary: { ...r.summary },
|
|
5707
|
+
signatures: r.signatures.map((s) => ({
|
|
5708
|
+
name: s.name,
|
|
5709
|
+
params: [...s.params],
|
|
5710
|
+
...s.paramTypes ? { paramTypes: [...s.paramTypes] } : {},
|
|
5711
|
+
display: s.display,
|
|
5712
|
+
detail: s.detail,
|
|
5713
|
+
conf: s.conf,
|
|
5714
|
+
abs: formatAbs(s.abs),
|
|
5715
|
+
...s.throws ? { throws: s.throws } : {},
|
|
5716
|
+
...s.entry ? { entry: true } : {}
|
|
5717
|
+
})),
|
|
5718
|
+
issues: r.issues.map((i) => {
|
|
5719
|
+
const acts = i.actions ?? actionsForIssue(i);
|
|
5720
|
+
return {
|
|
5721
|
+
severity: i.severity,
|
|
5722
|
+
code: i.code,
|
|
5723
|
+
message: i.message,
|
|
5724
|
+
...i.fn !== void 0 ? { fn: i.fn } : {},
|
|
5725
|
+
...i.line !== void 0 ? { line: i.line } : {},
|
|
5726
|
+
...i.column !== void 0 ? { column: i.column } : {},
|
|
5727
|
+
...i.actual !== void 0 ? { actual: i.actual } : {},
|
|
5728
|
+
...i.expected !== void 0 ? { expected: i.expected } : {},
|
|
5729
|
+
...i.suggestion !== void 0 ? { suggestion: i.suggestion } : {},
|
|
5730
|
+
...i.reason !== void 0 ? { reason: i.reason } : {},
|
|
5731
|
+
...acts.length > 0 ? { actions: acts } : {}
|
|
5732
|
+
};
|
|
5733
|
+
}),
|
|
5734
|
+
...r.budget ? { budget: { ...r.budget } } : {}
|
|
5735
|
+
};
|
|
5736
|
+
}
|
|
5737
|
+
function serializeCheckJsonMulti(reports) {
|
|
5738
|
+
const summary = {
|
|
5739
|
+
errors: 0,
|
|
5740
|
+
warnings: 0,
|
|
5741
|
+
infos: 0,
|
|
5742
|
+
functions: 0,
|
|
5743
|
+
files: reports.length
|
|
5744
|
+
};
|
|
5745
|
+
let calls = 0;
|
|
5746
|
+
let forks = 0;
|
|
5747
|
+
let maxCalls = 0;
|
|
5748
|
+
let maxForks = 0;
|
|
5749
|
+
let callTruncated = false;
|
|
5750
|
+
let forkTruncated = false;
|
|
5751
|
+
let evalCalls = 0;
|
|
5752
|
+
let maxEvalCalls = 0;
|
|
5753
|
+
let evalCallTruncated = false;
|
|
5754
|
+
let cycleTruncated = false;
|
|
5755
|
+
let loopTruncated = false;
|
|
5756
|
+
const callReasons = /* @__PURE__ */ new Set();
|
|
5757
|
+
const evalReasons = /* @__PURE__ */ new Set();
|
|
5758
|
+
let depth = 0;
|
|
5759
|
+
let maxDepth = 0;
|
|
5760
|
+
let evalDepth = 0;
|
|
5761
|
+
let maxEvalDepth = 0;
|
|
5762
|
+
const reasons = /* @__PURE__ */ new Set();
|
|
5763
|
+
for (const r of reports) {
|
|
5764
|
+
summary.errors += r.summary.errors;
|
|
5765
|
+
summary.warnings += r.summary.warnings;
|
|
5766
|
+
summary.infos += r.summary.infos;
|
|
5767
|
+
summary.functions += r.summary.functions;
|
|
5768
|
+
if (r.budget) {
|
|
5769
|
+
calls += r.budget.calls;
|
|
5770
|
+
forks += r.budget.forks;
|
|
5771
|
+
maxCalls = Math.max(maxCalls, r.budget.maxCalls);
|
|
5772
|
+
maxForks = Math.max(maxForks, r.budget.maxForks);
|
|
5773
|
+
callTruncated = callTruncated || r.budget.callTruncated;
|
|
5774
|
+
forkTruncated = forkTruncated || r.budget.forkTruncated;
|
|
5775
|
+
evalCalls += r.budget.evalCalls ?? 0;
|
|
5776
|
+
maxEvalCalls = Math.max(maxEvalCalls, r.budget.maxEvalCalls ?? 0);
|
|
5777
|
+
evalCallTruncated = evalCallTruncated || (r.budget.evalCallTruncated ?? false);
|
|
5778
|
+
cycleTruncated = cycleTruncated || (r.budget.cycleTruncated ?? false);
|
|
5779
|
+
loopTruncated = loopTruncated || (r.budget.loopTruncated ?? false);
|
|
5780
|
+
for (const x of r.budget.callReasons ?? []) callReasons.add(x);
|
|
5781
|
+
for (const x of r.budget.evalReasons ?? []) evalReasons.add(x);
|
|
5782
|
+
depth = Math.max(depth, r.budget.depth ?? 0);
|
|
5783
|
+
maxDepth = Math.max(maxDepth, r.budget.maxDepth ?? 0);
|
|
5784
|
+
evalDepth = Math.max(evalDepth, r.budget.evalDepth ?? 0);
|
|
5785
|
+
maxEvalDepth = Math.max(maxEvalDepth, r.budget.maxEvalDepth ?? 0);
|
|
5786
|
+
for (const x of r.budget.reasons ?? []) reasons.add(x);
|
|
5787
|
+
}
|
|
5788
|
+
}
|
|
5789
|
+
const budgetTruncated = callTruncated || evalCallTruncated || forkTruncated || loopTruncated;
|
|
5790
|
+
return {
|
|
5791
|
+
version: 1,
|
|
5792
|
+
kind: "multi",
|
|
5793
|
+
ok: reports.every((r) => r.ok),
|
|
5794
|
+
summary: {
|
|
5795
|
+
...summary,
|
|
5796
|
+
...budgetTruncated ? { budgetTruncated: true } : {}
|
|
5797
|
+
},
|
|
5798
|
+
...budgetTruncated || reports.some((r) => r.budget) ? {
|
|
5799
|
+
budget: {
|
|
5800
|
+
truncated: budgetTruncated,
|
|
5801
|
+
callTruncated,
|
|
5802
|
+
forkTruncated,
|
|
5803
|
+
calls,
|
|
5804
|
+
maxCalls,
|
|
5805
|
+
forks,
|
|
5806
|
+
maxForks,
|
|
5807
|
+
evalCalls,
|
|
5808
|
+
maxEvalCalls,
|
|
5809
|
+
evalCallTruncated,
|
|
5810
|
+
cycleTruncated,
|
|
5811
|
+
loopTruncated,
|
|
5812
|
+
callReasons: [...callReasons].sort(),
|
|
5813
|
+
depth,
|
|
5814
|
+
maxDepth,
|
|
5815
|
+
evalReasons: [...evalReasons].sort(),
|
|
5816
|
+
evalDepth,
|
|
5817
|
+
maxEvalDepth,
|
|
5818
|
+
reasons: [...reasons].sort()
|
|
5819
|
+
}
|
|
5820
|
+
} : {},
|
|
5821
|
+
reports
|
|
5822
|
+
};
|
|
5823
|
+
}
|
|
5824
|
+
var CONTRACT_FIX_CODES = /* @__PURE__ */ new Set([
|
|
5825
|
+
DIAG.constraintViolated,
|
|
5826
|
+
DIAG.assignMismatch,
|
|
5827
|
+
DIAG.argStructure,
|
|
5828
|
+
DIAG.interfaceDomainExceeds,
|
|
5829
|
+
DIAG.entryMayThrow,
|
|
5830
|
+
DIAG.missingSlot
|
|
5831
|
+
]);
|
|
5832
|
+
function budgetAttributionSegs(b) {
|
|
5833
|
+
const segs = [];
|
|
5834
|
+
if ((b.callReasons ?? []).includes("calls")) segs.push(`calls ${b.calls}/${b.maxCalls}`);
|
|
5835
|
+
if ((b.callReasons ?? []).includes("depth")) segs.push(`call depth ${b.depth}/${b.maxDepth}`);
|
|
5836
|
+
if ((b.evalReasons ?? []).includes("calls")) {
|
|
5837
|
+
segs.push(`eval calls ${b.evalCalls}/${b.maxEvalCalls}`);
|
|
5838
|
+
}
|
|
5839
|
+
if ((b.evalReasons ?? []).includes("depth")) {
|
|
5840
|
+
segs.push(`eval depth ${b.evalDepth}/${b.maxEvalDepth}`);
|
|
5841
|
+
}
|
|
5842
|
+
if (b.forkTruncated) segs.push(`forks ${b.forks}/${b.maxForks}`);
|
|
5843
|
+
if (b.loopTruncated) segs.push("loop iterations");
|
|
5844
|
+
const cycleOnly = segs.length === 0;
|
|
5845
|
+
if (cycleOnly) segs.push("call cycle \u2014 not a budget limit");
|
|
5846
|
+
return { segs, cycleOnly };
|
|
5847
|
+
}
|
|
5848
|
+
function formatCheckReport(r, opts = {}) {
|
|
5849
|
+
const lines = [];
|
|
5850
|
+
lines.push(`nudo check ${r.file}`);
|
|
5851
|
+
lines.push(r.ok ? "OK" : "FAILED");
|
|
5852
|
+
lines.push(
|
|
5853
|
+
` ${r.summary.errors} error \xB7 ${r.summary.warnings} warning \xB7 ${r.summary.infos} info \xB7 ${r.summary.functions} fn`
|
|
5854
|
+
);
|
|
5855
|
+
if (r.budget?.truncated) {
|
|
5856
|
+
const b = r.budget;
|
|
5857
|
+
const onlyLoop = b.loopTruncated && !b.callTruncated && !b.evalCallTruncated && !b.forkTruncated;
|
|
5858
|
+
const { segs } = budgetAttributionSegs(b);
|
|
5859
|
+
lines.push("");
|
|
5860
|
+
lines.push("budget");
|
|
5861
|
+
lines.push(` truncated ${segs.join(" \xB7 ")}`);
|
|
5862
|
+
lines.push(
|
|
5863
|
+
r.budget.cycleTruncated ? ` \u2192 some results widened to unknown (not a contract failure) \u2014 includes call cycles (same abstract args; a budget cannot lift them)` : onlyLoop ? ` \u2192 loop state widened (iteration budget, not a contract failure) \u2014 @nudo:budget does not apply; make the bound data-dependent or pin with @nudo:case / @nudo:contract` : ` \u2192 some results widened to unknown (not a contract failure) \u2014 raise @nudo:budget / nudo.analysis.maxForks or simplify recursion`
|
|
5864
|
+
);
|
|
5865
|
+
}
|
|
5866
|
+
if (r.signatures.length > 0) {
|
|
5867
|
+
lines.push("");
|
|
5868
|
+
lines.push("signatures");
|
|
5869
|
+
for (const s of r.signatures) {
|
|
5870
|
+
const paramStr = s.paramTypes && s.paramTypes.length > 0 ? s.params.map((p, i) => `${p}: ${s.paramTypes[i] ?? "any"}`).join(", ") : s.params.join(", ");
|
|
5871
|
+
const throwsStr = s.throws ? ` throws ${s.throws}` : "";
|
|
5872
|
+
const displayBase = s.display.replace(/\s+throws\s+[\s\S]+$/, "").trim();
|
|
5873
|
+
const shapeK = s.abs?.shape?.k;
|
|
5874
|
+
const looksLikeFakeAny = shapeK === "any" && displayBase.length > 0 && !/^any(\b|$)/.test(displayBase);
|
|
5875
|
+
const retStr = !looksLikeFakeAny && shapeK ? formatShape(s.abs) || displayBase.replace(/\s+#\w+$/, "").trim() : displayBase.replace(/\s+#\w+$/, "").trim();
|
|
5876
|
+
lines.push(` ${s.name}(${paramStr}) => ${retStr}${throwsStr}`);
|
|
5877
|
+
if (opts.verbose) {
|
|
5878
|
+
for (const ln of s.detail.split("\n").slice(1)) {
|
|
5879
|
+
lines.push(` ${ln}`);
|
|
5880
|
+
}
|
|
5881
|
+
}
|
|
5882
|
+
}
|
|
5883
|
+
}
|
|
5884
|
+
if (r.issues.length === 0) {
|
|
5885
|
+
lines.push("");
|
|
5886
|
+
lines.push("(no issues)");
|
|
5887
|
+
} else {
|
|
5888
|
+
lines.push("");
|
|
5889
|
+
lines.push("issues");
|
|
5890
|
+
for (const i of r.issues) {
|
|
5891
|
+
const loc = i.line != null ? `L${i.line}` : "";
|
|
5892
|
+
const head = [i.severity.toUpperCase(), loc, i.fn].filter(Boolean).join(" ");
|
|
5893
|
+
lines.push(` [${head}] ${i.message} (${i.code})`);
|
|
5894
|
+
const showDetail = i.severity === "error" || opts.verbose;
|
|
5895
|
+
if (showDetail && i.actual) lines.push(` actual: ${i.actual}`);
|
|
5896
|
+
if (showDetail && i.expected) lines.push(` expected: ${i.expected}`);
|
|
5897
|
+
if (i.suggestion) {
|
|
5898
|
+
lines.push(` \u2192 ${i.suggestion}`);
|
|
5899
|
+
} else if (CONTRACT_FIX_CODES.has(i.code)) {
|
|
5900
|
+
lines.push(` \u2192 add a refine / sidecar contract, or fix the call-site value`);
|
|
5901
|
+
}
|
|
5902
|
+
if (CONTRACT_FIX_CODES.has(i.code)) {
|
|
5903
|
+
lines.push(` fix: nudo contract --draft (emit a sidecar draft you can edit)`);
|
|
5904
|
+
}
|
|
5905
|
+
}
|
|
5906
|
+
}
|
|
5907
|
+
return lines.join("\n");
|
|
5908
|
+
}
|
|
5909
|
+
function escapeGhaData(s) {
|
|
5910
|
+
return s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
|
5911
|
+
}
|
|
5912
|
+
function escapeGhaProp(s) {
|
|
5913
|
+
return escapeGhaData(s).replace(/:/g, "%3A").replace(/,/g, "%2C");
|
|
5914
|
+
}
|
|
5915
|
+
function formatGithubAnnotations(r, opts = {}) {
|
|
5916
|
+
const lines = [];
|
|
5917
|
+
let file = r.file;
|
|
5918
|
+
if (opts.workspaceRoot && file.startsWith(opts.workspaceRoot)) {
|
|
5919
|
+
file = file.slice(opts.workspaceRoot.length).replace(/^[/\\]/, "");
|
|
5920
|
+
}
|
|
5921
|
+
for (const i of r.issues) {
|
|
5922
|
+
const level = i.severity === "error" ? "error" : i.severity === "warning" ? "warning" : "notice";
|
|
5923
|
+
const line = i.line ?? 1;
|
|
5924
|
+
const col = (i.column ?? 0) + 1;
|
|
5925
|
+
const title = escapeGhaProp(i.code);
|
|
5926
|
+
const msgBits = [i.message];
|
|
5927
|
+
if (i.actual && i.expected) msgBits.push(`${i.actual} \u22AD ${i.expected}`);
|
|
5928
|
+
else if (i.suggestion) msgBits.push(i.suggestion);
|
|
5929
|
+
if (CONTRACT_FIX_CODES.has(i.code)) {
|
|
5930
|
+
msgBits.push(`fix: nudo contract --draft`);
|
|
5931
|
+
}
|
|
5932
|
+
const msg = escapeGhaData(msgBits.join(" \xB7 "));
|
|
5933
|
+
const props = [
|
|
5934
|
+
`file=${escapeGhaProp(file)}`,
|
|
5935
|
+
`line=${line}`,
|
|
5936
|
+
`col=${col}`,
|
|
5937
|
+
`title=${title}`
|
|
5938
|
+
].join(",");
|
|
5939
|
+
lines.push(`::${level} ${props}::${msg}`);
|
|
5940
|
+
}
|
|
5941
|
+
return lines.join("\n");
|
|
5942
|
+
}
|
|
5943
|
+
function formatGitlabCodeQuality(r, opts = {}) {
|
|
5944
|
+
let file = r.file;
|
|
5945
|
+
if (opts.workspaceRoot && file.startsWith(opts.workspaceRoot)) {
|
|
5946
|
+
file = file.slice(opts.workspaceRoot.length).replace(/^[/\\]/, "");
|
|
5947
|
+
}
|
|
5948
|
+
return r.issues.map((i) => {
|
|
5949
|
+
const begin = i.line ?? 1;
|
|
5950
|
+
const severity = i.severity === "error" ? "major" : i.severity === "warning" ? "minor" : "info";
|
|
5951
|
+
const description = i.actual && i.expected ? `${i.message} (${i.actual} \u22AD ${i.expected})` : i.message;
|
|
5952
|
+
const fingerprint = `${file}:${i.code}:${begin}:${i.message}`;
|
|
5953
|
+
return {
|
|
5954
|
+
description,
|
|
5955
|
+
check_name: i.code,
|
|
5956
|
+
fingerprint,
|
|
5957
|
+
severity,
|
|
5958
|
+
location: { path: file, lines: { begin } }
|
|
5959
|
+
};
|
|
5960
|
+
});
|
|
5961
|
+
}
|
|
5962
|
+
|
|
5573
5963
|
export {
|
|
5574
5964
|
listFnDirectiveScopes,
|
|
5575
5965
|
findFnDirectiveScope,
|
|
@@ -5648,5 +6038,12 @@ export {
|
|
|
5648
6038
|
stableAnalyzeKeySource,
|
|
5649
6039
|
listTopFunctions,
|
|
5650
6040
|
scanLiteralCalls,
|
|
5651
|
-
checkInjectedDomainEvidence
|
|
6041
|
+
checkInjectedDomainEvidence,
|
|
6042
|
+
actionsForIssue,
|
|
6043
|
+
serializeCheckJson,
|
|
6044
|
+
serializeCheckJsonMulti,
|
|
6045
|
+
budgetAttributionSegs,
|
|
6046
|
+
formatCheckReport,
|
|
6047
|
+
formatGithubAnnotations,
|
|
6048
|
+
formatGitlabCodeQuality
|
|
5652
6049
|
};
|