@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
package/dist/index.js
CHANGED
|
@@ -2,12 +2,14 @@ import "./chunk-US532PZA.js";
|
|
|
2
2
|
import {
|
|
3
3
|
INTERFACE_SOURCE_ABBR,
|
|
4
4
|
NudoSidecarError,
|
|
5
|
+
actionsForIssue,
|
|
5
6
|
buildArgsFromAssume,
|
|
6
7
|
canSkipLiteralCallScan,
|
|
7
8
|
collectAbsFreeVars,
|
|
8
9
|
effectiveInterface,
|
|
9
10
|
evictGeneralizeMemoForPaths,
|
|
10
11
|
execNudoModule,
|
|
12
|
+
extractAllLoadSpecs,
|
|
11
13
|
extractAssumeSegs,
|
|
12
14
|
extractBalancedParens,
|
|
13
15
|
extractDeclaredThrows,
|
|
@@ -23,8 +25,11 @@ import {
|
|
|
23
25
|
findFnDirectiveScope,
|
|
24
26
|
fnDirectiveCommentLines,
|
|
25
27
|
fnTopLevelLocalNames,
|
|
28
|
+
formatCheckReport,
|
|
26
29
|
formatConstraint,
|
|
27
30
|
formatEffectiveInterfaceDisplay,
|
|
31
|
+
formatGithubAnnotations,
|
|
32
|
+
formatGitlabCodeQuality,
|
|
28
33
|
formatInterfaceTierLine,
|
|
29
34
|
generalizeAll,
|
|
30
35
|
generalizeFromAst,
|
|
@@ -57,6 +62,8 @@ import {
|
|
|
57
62
|
scanContractSegments,
|
|
58
63
|
scanLiteralCalls,
|
|
59
64
|
scanThrowsDecl,
|
|
65
|
+
serializeCheckJson,
|
|
66
|
+
serializeCheckJsonMulti,
|
|
60
67
|
setInterfaceDiagCollector,
|
|
61
68
|
setRefineDiagCollector,
|
|
62
69
|
sidecarClosureFingerprint,
|
|
@@ -67,7 +74,7 @@ import {
|
|
|
67
74
|
takeInterfaceDiagsSince,
|
|
68
75
|
takeRefineDiags,
|
|
69
76
|
takeRefineDiagsSince
|
|
70
|
-
} from "./chunk-
|
|
77
|
+
} from "./chunk-47UPDZHE.js";
|
|
71
78
|
import {
|
|
72
79
|
$add,
|
|
73
80
|
$arguments,
|
|
@@ -183,6 +190,7 @@ import {
|
|
|
183
190
|
CONSTRAINT_BUILDER_NAMES,
|
|
184
191
|
CONSTRAINT_EXPR_RE,
|
|
185
192
|
DEFAULT_MAX_LOOP_ITERS,
|
|
193
|
+
DIAG,
|
|
186
194
|
FORK_TRUNCATION_LABEL,
|
|
187
195
|
HOST_EFFECT_LABEL_PREFIX,
|
|
188
196
|
LOOP_TRUNCATION_LABEL,
|
|
@@ -528,7 +536,7 @@ import {
|
|
|
528
536
|
withExecPhi,
|
|
529
537
|
withInvariantSpecs,
|
|
530
538
|
withVar
|
|
531
|
-
} from "./chunk-
|
|
539
|
+
} from "./chunk-NUTYUT3I.js";
|
|
532
540
|
|
|
533
541
|
// src/environment.ts
|
|
534
542
|
function createEnvironment(parent, bindings = /* @__PURE__ */ new Map()) {
|
|
@@ -650,7 +658,7 @@ function checkArg(arg, expect, phi = pTrue) {
|
|
|
650
658
|
if (arg.shape.k === "unknown" && !arg.term) {
|
|
651
659
|
return {
|
|
652
660
|
severity: "warning",
|
|
653
|
-
code:
|
|
661
|
+
code: DIAG.argOpaque,
|
|
654
662
|
message: `argument type is unknown; cannot verify constraint ${predToString(expect)}`,
|
|
655
663
|
suggestion: "add a call site or @nudo:case, or provide preconditions via --assume"
|
|
656
664
|
};
|
|
@@ -664,7 +672,7 @@ function checkArg(arg, expect, phi = pTrue) {
|
|
|
664
672
|
if (ok === false) {
|
|
665
673
|
return {
|
|
666
674
|
severity: "error",
|
|
667
|
-
code:
|
|
675
|
+
code: DIAG.constraintViolated,
|
|
668
676
|
message: `argument ${JSON.stringify(lvR.value)} does not satisfy ${predToString(expect)}`,
|
|
669
677
|
suggestion: `use a value satisfying the constraint, or relax the function precondition`
|
|
670
678
|
};
|
|
@@ -672,7 +680,7 @@ function checkArg(arg, expect, phi = pTrue) {
|
|
|
672
680
|
}
|
|
673
681
|
return {
|
|
674
682
|
severity: "warning",
|
|
675
|
-
code:
|
|
683
|
+
code: DIAG.constraintUnproven,
|
|
676
684
|
message: `cannot prove the argument satisfies ${predToString(expect)}`,
|
|
677
685
|
suggestion: `not enough information; use --assume or add a case`
|
|
678
686
|
};
|
|
@@ -701,7 +709,7 @@ function checkCall(source, fnName, args, phi = pTrue) {
|
|
|
701
709
|
if (!g) {
|
|
702
710
|
diags.push({
|
|
703
711
|
severity: "error",
|
|
704
|
-
code:
|
|
712
|
+
code: DIAG.fnNotFound,
|
|
705
713
|
message: `function ${fnName} not found`
|
|
706
714
|
});
|
|
707
715
|
return diags;
|
|
@@ -709,7 +717,7 @@ function checkCall(source, fnName, args, phi = pTrue) {
|
|
|
709
717
|
if (args.length !== g.params.length) {
|
|
710
718
|
diags.push({
|
|
711
719
|
severity: "error",
|
|
712
|
-
code:
|
|
720
|
+
code: DIAG.argCount,
|
|
713
721
|
message: `${fnName} expects ${g.params.length} argument(s), got ${args.length}`,
|
|
714
722
|
fn: fnName
|
|
715
723
|
});
|
|
@@ -724,7 +732,7 @@ function checkCall(source, fnName, args, phi = pTrue) {
|
|
|
724
732
|
if (g.symbolic.conf === "partial" || g.symbolic.conf === "opaque") {
|
|
725
733
|
diags.push({
|
|
726
734
|
severity: "info",
|
|
727
|
-
code:
|
|
735
|
+
code: DIAG.partialResult,
|
|
728
736
|
message: `${fnName}(...) result confidence ${g.symbolic.conf}`,
|
|
729
737
|
suggestion: "add a call site or constraint to improve precision",
|
|
730
738
|
fn: fnName
|
|
@@ -761,6 +769,9 @@ function formatDiagnostics(diags) {
|
|
|
761
769
|
}).join("\n");
|
|
762
770
|
}
|
|
763
771
|
|
|
772
|
+
// src/algebra/check.ts
|
|
773
|
+
import { isBuiltin } from "module";
|
|
774
|
+
|
|
764
775
|
// src/algebra/postcondition.ts
|
|
765
776
|
function proved() {
|
|
766
777
|
return { status: "proved" };
|
|
@@ -1489,7 +1500,7 @@ function interfaceDriftIssues(candidates, callRecords, evalResult) {
|
|
|
1489
1500
|
if (leqAbs(today2, expected2).ok && leqAbs(expected2, today2).ok) continue;
|
|
1490
1501
|
out.push({
|
|
1491
1502
|
severity: "warning",
|
|
1492
|
-
code:
|
|
1503
|
+
code: DIAG.interfaceDrift,
|
|
1493
1504
|
message: `${cand.fnName}[${param}]: persisted @generated segment \u2260 today's call-site domain`,
|
|
1494
1505
|
actual: formatAbs(today2),
|
|
1495
1506
|
expected: formatConstraint(constraint),
|
|
@@ -1514,7 +1525,7 @@ function interfaceDriftIssues(candidates, callRecords, evalResult) {
|
|
|
1514
1525
|
if (leqAbs(today, expected).ok && leqAbs(expected, today).ok) continue;
|
|
1515
1526
|
out.push({
|
|
1516
1527
|
severity: "warning",
|
|
1517
|
-
code:
|
|
1528
|
+
code: DIAG.interfaceDrift,
|
|
1518
1529
|
message: `${cand.fnName}[return]: persisted @generated segment \u2260 today's inferred return`,
|
|
1519
1530
|
actual: formatAbs(today),
|
|
1520
1531
|
expected: formatConstraint(retC),
|
|
@@ -1715,7 +1726,9 @@ function moduleMapId(m) {
|
|
|
1715
1726
|
function checkMemoKey(filePath, source, identityOpts, deps, sidecarFp) {
|
|
1716
1727
|
return [
|
|
1717
1728
|
hashSource(source),
|
|
1718
|
-
filePath
|
|
1729
|
+
// filePath 过 stablePathKey 归一(与依赖索引/逐出同口径):同一文件的
|
|
1730
|
+
// 裸路径与 file:// URL 两种形态不得各占一条 memo 条目(命中率损耗)。
|
|
1731
|
+
stablePathKey(filePath),
|
|
1719
1732
|
`${loadModuleId(identityOpts.loadModule)}:${identityOpts.fromFile ?? ""}`,
|
|
1720
1733
|
deps.fp,
|
|
1721
1734
|
sidecarFp ?? "-",
|
|
@@ -1933,7 +1946,7 @@ function structuralAssignIssues(records) {
|
|
|
1933
1946
|
if (!leq.ok) {
|
|
1934
1947
|
out.push({
|
|
1935
1948
|
severity: "error",
|
|
1936
|
-
code:
|
|
1949
|
+
code: DIAG.assignMismatch,
|
|
1937
1950
|
message: `${r.name}: assignment \u22AD existing shape`,
|
|
1938
1951
|
actual: formatAbs(r.next),
|
|
1939
1952
|
expected: formatAbs(r.prev),
|
|
@@ -2076,7 +2089,7 @@ function scanCaseInconsistency(source, knownFns, opts) {
|
|
|
2076
2089
|
const field = fieldPath.join(".");
|
|
2077
2090
|
out.push({
|
|
2078
2091
|
severity: "error",
|
|
2079
|
-
code:
|
|
2092
|
+
code: DIAG.caseInconsistency,
|
|
2080
2093
|
message: `${fnName} case "${caseName}": witness \u22AD contract`,
|
|
2081
2094
|
actual: formatAbs(arg),
|
|
2082
2095
|
expected: `missing field ${field}`,
|
|
@@ -2100,7 +2113,7 @@ function scanCaseInconsistency(source, knownFns, opts) {
|
|
|
2100
2113
|
const paramName = entry.param || paramNames[idx] || `arg${idx}`;
|
|
2101
2114
|
out.push({
|
|
2102
2115
|
severity: "error",
|
|
2103
|
-
code:
|
|
2116
|
+
code: DIAG.caseInconsistency,
|
|
2104
2117
|
message: `${fnName} case "${caseName}": witness \u22AD contract`,
|
|
2105
2118
|
actual: formatAbs(arg),
|
|
2106
2119
|
expected: formatConstraint(entry.constraint),
|
|
@@ -2116,7 +2129,7 @@ function scanCaseInconsistency(source, knownFns, opts) {
|
|
|
2116
2129
|
const paramName = entry.param || paramNames[idx] || `arg${idx}`;
|
|
2117
2130
|
out.push({
|
|
2118
2131
|
severity: "error",
|
|
2119
|
-
code:
|
|
2132
|
+
code: DIAG.caseInconsistency,
|
|
2120
2133
|
message: `${fnName} case "${caseName}": witness \u22AD contract`,
|
|
2121
2134
|
actual: formatAbs(arg),
|
|
2122
2135
|
expected: formatConstraint(entry.constraint),
|
|
@@ -2138,7 +2151,7 @@ function scanCaseInconsistency(source, knownFns, opts) {
|
|
|
2138
2151
|
const paramName = entry.param || paramNames[idx] || `arg${idx}`;
|
|
2139
2152
|
out.push({
|
|
2140
2153
|
severity: "error",
|
|
2141
|
-
code:
|
|
2154
|
+
code: DIAG.caseInconsistency,
|
|
2142
2155
|
message: `${fnName} case "${caseName}": witness \u22AD contract`,
|
|
2143
2156
|
actual: formatAbs(arg),
|
|
2144
2157
|
expected: predToString(p),
|
|
@@ -2365,7 +2378,7 @@ function sidecarDiagIssues(diags) {
|
|
|
2365
2378
|
const k = `${d.code}\0${d.message}`;
|
|
2366
2379
|
if (seen.has(k)) continue;
|
|
2367
2380
|
seen.add(k);
|
|
2368
|
-
const severity = d.code ===
|
|
2381
|
+
const severity = d.code === DIAG.contractSyntax || d.code === DIAG.directiveSyntax ? "warning" : "error";
|
|
2369
2382
|
out.push({ severity, code: d.code, message: d.message });
|
|
2370
2383
|
}
|
|
2371
2384
|
return out;
|
|
@@ -2387,7 +2400,7 @@ function assumePremisesPhiOf(name, source, file, issues) {
|
|
|
2387
2400
|
if (seg.kind === "bad") {
|
|
2388
2401
|
issues.push({
|
|
2389
2402
|
severity: "error",
|
|
2390
|
-
code:
|
|
2403
|
+
code: DIAG.assumeInvalid,
|
|
2391
2404
|
message: `${name}: malformed @nudo:assume '${seg.raw}' (expected <param> <= <param> or <param> >= <param>; constant bounds belong to sidecar contracts)`,
|
|
2392
2405
|
actual: seg.raw,
|
|
2393
2406
|
expected: "<param> <= <param>",
|
|
@@ -2400,7 +2413,7 @@ function assumePremisesPhiOf(name, source, file, issues) {
|
|
|
2400
2413
|
if (bad.length > 0) {
|
|
2401
2414
|
issues.push({
|
|
2402
2415
|
severity: "error",
|
|
2403
|
-
code:
|
|
2416
|
+
code: DIAG.assumeInvalid,
|
|
2404
2417
|
message: `${name}: @nudo:assume name(s) not bound as parameters or top-level locals (${bad.join(", ")})`,
|
|
2405
2418
|
actual: bad.join(", "),
|
|
2406
2419
|
expected: surface || "(no params/locals)",
|
|
@@ -2431,7 +2444,7 @@ function invariantSpecsOf(name, source, file, issues) {
|
|
|
2431
2444
|
if (seg.kind === "bad") {
|
|
2432
2445
|
issues.push({
|
|
2433
2446
|
severity: "warning",
|
|
2434
|
-
code:
|
|
2447
|
+
code: DIAG.invariantInvalid,
|
|
2435
2448
|
message: `${name}: malformed @nudo:invariant '${seg.raw}' (expected <name><path> <rel> <number>; path = [*] or .member steps)`,
|
|
2436
2449
|
actual: seg.raw,
|
|
2437
2450
|
expected: "<name><path> <rel> <number>",
|
|
@@ -2443,7 +2456,7 @@ function invariantSpecsOf(name, source, file, issues) {
|
|
|
2443
2456
|
if (!bindable.has(seg.name)) {
|
|
2444
2457
|
issues.push({
|
|
2445
2458
|
severity: "warning",
|
|
2446
|
-
code:
|
|
2459
|
+
code: DIAG.invariantInvalid,
|
|
2447
2460
|
message: `${name}: @nudo:invariant name not bound as a parameter or top-level local (${seg.name})`,
|
|
2448
2461
|
actual: seg.name,
|
|
2449
2462
|
expected: surface || "(no params/locals)",
|
|
@@ -2458,12 +2471,20 @@ function invariantSpecsOf(name, source, file, issues) {
|
|
|
2458
2471
|
if (specs.length === 0) return void 0;
|
|
2459
2472
|
return { specs, display };
|
|
2460
2473
|
}
|
|
2474
|
+
var OPAQUE_EVIDENCE_HINT = "add @nudo:case or a call site";
|
|
2475
|
+
var OPAQUE_ENV_HINT = "declare the runtime env to type native modules: '/// @nudo:env node' (or web/es) \u2014 see guides/env-harvest.md";
|
|
2476
|
+
function isBuiltinModuleSpec(spec) {
|
|
2477
|
+
if (spec.startsWith(".") || spec.startsWith("/")) return false;
|
|
2478
|
+
const base = (spec.startsWith("node:") ? spec.slice("node:".length) : spec).split("/")[0];
|
|
2479
|
+
return base.length > 0 && isBuiltin(base);
|
|
2480
|
+
}
|
|
2461
2481
|
function checkSourceInner(filePath, source, file, phi, opts, issues, signatures, names, truncated, identityOpts = opts, depsFp, sidecarFp) {
|
|
2462
2482
|
const hasRefineDirective = stripStringsKeepComments(source).includes("@nudo:contract");
|
|
2463
2483
|
const entryNames = localNamedExports(source);
|
|
2464
2484
|
const exportedNames = sidecarFp !== void 0 ? entryNames : void 0;
|
|
2465
2485
|
const entryThrowsMode = opts.entryThrows ?? "error";
|
|
2466
2486
|
const ignoreThrows = opts.ignoreThrows;
|
|
2487
|
+
const opaqueEnvHint = Object.keys(opts.inject?.envGlobals ?? {}).length === 0 && extractFileEnvNames(source).length === 0 && extractAllLoadSpecs(source).some(isBuiltinModuleSpec);
|
|
2467
2488
|
const driftCandidates = [];
|
|
2468
2489
|
const returnContracts = /* @__PURE__ */ new Map();
|
|
2469
2490
|
const returnViolated = /* @__PURE__ */ new Set();
|
|
@@ -2574,7 +2595,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2574
2595
|
const loc = findFnLoc(file, name);
|
|
2575
2596
|
issues.push({
|
|
2576
2597
|
severity: entryThrowsMode === "warning" ? "warning" : "error",
|
|
2577
|
-
code:
|
|
2598
|
+
code: DIAG.entryMayThrow,
|
|
2578
2599
|
message: `${name} (export): may throw ${gateDisplay}`,
|
|
2579
2600
|
actual: `${name}(\u2026) throws ${gateDisplay}`,
|
|
2580
2601
|
expected: "entry total, or @nudo:throws / try-catch",
|
|
@@ -2599,7 +2620,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2599
2620
|
}
|
|
2600
2621
|
issues.push({
|
|
2601
2622
|
severity: "warning",
|
|
2602
|
-
code:
|
|
2623
|
+
code: DIAG.noSignature,
|
|
2603
2624
|
message: `${name}: could not generalize a symbolic Abs`,
|
|
2604
2625
|
suggestion: "add @nudo:case or make the body algebraically evaluable",
|
|
2605
2626
|
fn: name
|
|
@@ -2619,7 +2640,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2619
2640
|
const loc = findFnLoc(file, name);
|
|
2620
2641
|
issues.push({
|
|
2621
2642
|
severity: entryThrowsMode === "warning" ? "warning" : "error",
|
|
2622
|
-
code:
|
|
2643
|
+
code: DIAG.entryMayThrow,
|
|
2623
2644
|
message: `${name} (export): may throw ${gateDisplay}`,
|
|
2624
2645
|
actual: `${formatEntrySigLine(name, g, gateDisplay)}`,
|
|
2625
2646
|
expected: "entry total, or @nudo:throws / try-catch",
|
|
@@ -2663,7 +2684,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2663
2684
|
const where = symbolicTrueUnknown ? `${name} => unknown` : `${name} param ${g.params[unknownParamIdx] ?? `#${unknownParamIdx}`}: unknown`;
|
|
2664
2685
|
issues.push({
|
|
2665
2686
|
severity: "warning",
|
|
2666
|
-
code:
|
|
2687
|
+
code: DIAG.unknownInference,
|
|
2667
2688
|
message: `${name}: signature has true unknown (inference failed)`,
|
|
2668
2689
|
actual: where,
|
|
2669
2690
|
expected: "computable Abs (any = unconstrained, unknown = engine debt)",
|
|
@@ -2684,7 +2705,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2684
2705
|
const surface = [...contractNames].filter((n) => !n.startsWith("_p") && n !== "_").join(", ");
|
|
2685
2706
|
issues.push({
|
|
2686
2707
|
severity: "error",
|
|
2687
|
-
code:
|
|
2708
|
+
code: DIAG.interfaceParamMismatch,
|
|
2688
2709
|
message: `${name}: contract parameter name(s) not in the formal parameter list (${unknownParams.join(", ")})`,
|
|
2689
2710
|
actual: unknownParams.join(", "),
|
|
2690
2711
|
expected: surface || g.params.join(", ") || "(no params)",
|
|
@@ -2697,7 +2718,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2697
2718
|
if (eff.conflict.params.length > 0) {
|
|
2698
2719
|
issues.push({
|
|
2699
2720
|
severity: "error",
|
|
2700
|
-
code:
|
|
2721
|
+
code: DIAG.interfaceConflict,
|
|
2701
2722
|
message: `${name}: handwritten contract conjunction unsatisfiable (${eff.conflict.params.join(", ")})`,
|
|
2702
2723
|
suggestion: "check whether the source @nudo:contract and the same-name sidecar binding have contradictory constant bounds",
|
|
2703
2724
|
fn: name
|
|
@@ -2706,7 +2727,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2706
2727
|
if (eff.conflict.returns) {
|
|
2707
2728
|
issues.push({
|
|
2708
2729
|
severity: "error",
|
|
2709
|
-
code:
|
|
2730
|
+
code: DIAG.interfaceConflict,
|
|
2710
2731
|
message: `${name}: handwritten contract conjunction unsatisfiable on the return slot`,
|
|
2711
2732
|
suggestion: "check whether the source @nudo:contract return and the sidecar fn() return constraint are contradictory (the return slot is not enforced when they are)",
|
|
2712
2733
|
fn: name
|
|
@@ -2742,9 +2763,9 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2742
2763
|
if (g.symbolic.conf === "opaque" && !truncated.has(name)) {
|
|
2743
2764
|
issues.push({
|
|
2744
2765
|
severity: "info",
|
|
2745
|
-
code:
|
|
2766
|
+
code: DIAG.opaqueResult,
|
|
2746
2767
|
message: `${name}(...): conf=opaque (path not covered, or native)`,
|
|
2747
|
-
suggestion:
|
|
2768
|
+
suggestion: opaqueEnvHint ? OPAQUE_ENV_HINT : OPAQUE_EVIDENCE_HINT,
|
|
2748
2769
|
fn: name
|
|
2749
2770
|
});
|
|
2750
2771
|
}
|
|
@@ -2753,7 +2774,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2753
2774
|
if (label === FORK_TRUNCATION_LABEL) {
|
|
2754
2775
|
issues.push({
|
|
2755
2776
|
severity: "info",
|
|
2756
|
-
code:
|
|
2777
|
+
code: DIAG.forkTruncated,
|
|
2757
2778
|
message: `Branch expansion was truncated (fork budget); affected results widened to unknown#opaque (budget)`,
|
|
2758
2779
|
suggestion: "optional: raise nudo.analysis.maxForks, or @nudo:budget forks=\u2026 on the function \u2014 non-blocking"
|
|
2759
2780
|
});
|
|
@@ -2762,7 +2783,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2762
2783
|
if (label === PROMISE_MICRO_OVERFLOW_LABEL) {
|
|
2763
2784
|
issues.push({
|
|
2764
2785
|
severity: "info",
|
|
2765
|
-
code:
|
|
2786
|
+
code: DIAG.promiseMicroTruncated,
|
|
2766
2787
|
message: `Promise microtask queue hit its hard cap; further then/catch callbacks were dropped (results stay fail-closed)`,
|
|
2767
2788
|
suggestion: "optional: reduce top-level Promise.then fan-out \u2014 non-blocking"
|
|
2768
2789
|
});
|
|
@@ -2771,7 +2792,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2771
2792
|
if (label === PROMISE_MICRO_ERROR_LABEL) {
|
|
2772
2793
|
issues.push({
|
|
2773
2794
|
severity: "info",
|
|
2774
|
-
code:
|
|
2795
|
+
code: DIAG.promiseMicroError,
|
|
2775
2796
|
message: `A promise microtask threw while draining; the error was recorded and did not rewrite the synchronous return value`,
|
|
2776
2797
|
suggestion: "optional: inspect the then/catch callback for a throw path \u2014 non-blocking"
|
|
2777
2798
|
});
|
|
@@ -2780,7 +2801,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2780
2801
|
if (label === MATH_FOLD_ERROR_LABEL) {
|
|
2781
2802
|
issues.push({
|
|
2782
2803
|
severity: "info",
|
|
2783
|
-
code:
|
|
2804
|
+
code: DIAG.mathFoldError,
|
|
2784
2805
|
message: `A Math native fold threw during analysis (host-tampered or divergent Math.*); the result was widened to number`,
|
|
2785
2806
|
suggestion: "optional: keep Math.* builtins unmodified in analyzed code, or mock them with @nudo:mock \u2014 non-blocking"
|
|
2786
2807
|
});
|
|
@@ -2790,7 +2811,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2790
2811
|
const hostName = label.slice(HOST_EFFECT_LABEL_PREFIX.length);
|
|
2791
2812
|
issues.push({
|
|
2792
2813
|
severity: "info",
|
|
2793
|
-
code:
|
|
2814
|
+
code: DIAG.hostEffectBlocked,
|
|
2794
2815
|
message: `Host function '${hostName}' is not executed during analysis (network/timer side effect); result widened to unknown#opaque`,
|
|
2795
2816
|
suggestion: "optional: mock it with @nudo:mock / @nudo:env, or pass the value in from a call site",
|
|
2796
2817
|
fn: hostName
|
|
@@ -2800,7 +2821,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2800
2821
|
if (label === LOOP_TRUNCATION_LABEL) {
|
|
2801
2822
|
issues.push({
|
|
2802
2823
|
severity: "info",
|
|
2803
|
-
code:
|
|
2824
|
+
code: DIAG.loopTruncated,
|
|
2804
2825
|
message: `A loop did not terminate within its iteration budget (abstract unroll cap reached and the exit condition stayed concrete-true); affected loop state was widened (bounded, not exact)`,
|
|
2805
2826
|
suggestion: "optional: restructure so the exit condition becomes abstract (data-dependent) within a few iterations, or pin the result with @nudo:case / @nudo:contract \u2014 non-blocking"
|
|
2806
2827
|
});
|
|
@@ -2811,7 +2832,7 @@ function checkSourceInner(filePath, source, file, phi, opts, issues, signatures,
|
|
|
2811
2832
|
const calls = reasons.has("calls");
|
|
2812
2833
|
issues.push({
|
|
2813
2834
|
severity: "info",
|
|
2814
|
-
code:
|
|
2835
|
+
code: DIAG.recursionTruncated,
|
|
2815
2836
|
message: allCycle ? `Recursive evaluation of '${label}' hit a call cycle (same abstract arguments already on the stack); result widened to unknown#opaque (cycle \u2014 budget cannot lift this)` : `Recursive evaluation of '${label}' was truncated (${depth && calls ? "depth/size budget" : depth ? "call-depth budget" : calls ? "total-calls budget" : "depth/size budget"}); result widened to unknown#opaque (budget \u2014 not inference debt)`,
|
|
2816
2837
|
suggestion: allCycle ? "a call cycle is not a budget: make the recursion arguments vary per level, narrow the base case, or declare @nudo:contract \u2014 raising @nudo:budget will not change this" : depth && !calls ? "raise the function budget with @nudo:budget depth=\u2026 (or narrow the recursion base case / declare @nudo:contract return)" : calls && !depth ? "raise the function budget with @nudo:budget calls=\u2026 (or narrow the recursion base case / declare @nudo:contract return)" : "raise the function budget with @nudo:budget calls=\u2026 depth=\u2026 (or narrow the recursion base case / declare @nudo:contract return)",
|
|
2817
2838
|
reason: allCycle ? "cycle" : depth && calls ? "budget" : depth ? "depth" : calls ? "calls" : "budget",
|
|
@@ -2934,7 +2955,7 @@ function checkReturnConstraint(fnName, cName, constraint, ret, assumeDisplay, in
|
|
|
2934
2955
|
if (assumeDisplay && assumeDisplay.length > 0) {
|
|
2935
2956
|
relied.push({
|
|
2936
2957
|
severity: "info",
|
|
2937
|
-
code:
|
|
2958
|
+
code: DIAG.assumeRelied,
|
|
2938
2959
|
message: `${fnName}: return contract proved via @nudo:assume ${assumeDisplay.join("; ")}`,
|
|
2939
2960
|
actual: `proof relies on user lemma${assumeDisplay.length > 1 ? "s" : ""}`,
|
|
2940
2961
|
expected: "engine verifies the obligation, not the lemma itself",
|
|
@@ -2945,7 +2966,7 @@ function checkReturnConstraint(fnName, cName, constraint, ret, assumeDisplay, in
|
|
|
2945
2966
|
if (invariantDisplay && invariantDisplay.length > 0) {
|
|
2946
2967
|
relied.push({
|
|
2947
2968
|
severity: "info",
|
|
2948
|
-
code:
|
|
2969
|
+
code: DIAG.invariantRelied,
|
|
2949
2970
|
message: `${fnName}: return contract proved via @nudo:invariant ${invariantDisplay.join("; ")}`,
|
|
2950
2971
|
actual: `proof relies on user invariant${invariantDisplay.length > 1 ? "s" : ""}`,
|
|
2951
2972
|
expected: "engine verifies the obligation, not the loop invariant itself",
|
|
@@ -2958,7 +2979,7 @@ function checkReturnConstraint(fnName, cName, constraint, ret, assumeDisplay, in
|
|
|
2958
2979
|
const push = (severity, actual, expected, suggestion) => [
|
|
2959
2980
|
{
|
|
2960
2981
|
severity,
|
|
2961
|
-
code: severity === "error" ?
|
|
2982
|
+
code: severity === "error" ? DIAG.constraintViolated : DIAG.unprovenReturn,
|
|
2962
2983
|
message: `${fnName}: return value \u22AD @nudo:contract return ${cName}`,
|
|
2963
2984
|
actual,
|
|
2964
2985
|
expected,
|
|
@@ -2981,361 +3002,6 @@ function checkReturnConstraint(fnName, cName, constraint, ret, assumeDisplay, in
|
|
|
2981
3002
|
`${proof.reason}; add a precondition, narrow the return expression, or relax the return contract`
|
|
2982
3003
|
);
|
|
2983
3004
|
}
|
|
2984
|
-
|
|
2985
|
-
// src/algebra/check-report.ts
|
|
2986
|
-
function actionsForIssue(i) {
|
|
2987
|
-
const draft = {
|
|
2988
|
-
kind: "draft",
|
|
2989
|
-
command: "nudo contract --draft",
|
|
2990
|
-
label: "emit a sidecar draft you can edit"
|
|
2991
|
-
};
|
|
2992
|
-
switch (i.code) {
|
|
2993
|
-
case "nudo:constraint-violated":
|
|
2994
|
-
return [
|
|
2995
|
-
{
|
|
2996
|
-
kind: "callsite",
|
|
2997
|
-
label: "use a value satisfying the constraint",
|
|
2998
|
-
...i.expected ? { hint: i.expected } : {}
|
|
2999
|
-
},
|
|
3000
|
-
{
|
|
3001
|
-
kind: "relax",
|
|
3002
|
-
label: "relax the precondition (edit *.nudo.js / @nudo:contract)",
|
|
3003
|
-
...i.expected ? { hint: i.expected } : {}
|
|
3004
|
-
},
|
|
3005
|
-
draft
|
|
3006
|
-
];
|
|
3007
|
-
case "nudo:assign-mismatch":
|
|
3008
|
-
return [
|
|
3009
|
-
{
|
|
3010
|
-
kind: "callsite",
|
|
3011
|
-
label: "align the assigned value with the existing shape",
|
|
3012
|
-
...i.expected ? { hint: i.expected } : {}
|
|
3013
|
-
},
|
|
3014
|
-
draft
|
|
3015
|
-
];
|
|
3016
|
-
case "nudo:entry-may-throw":
|
|
3017
|
-
return [
|
|
3018
|
-
{
|
|
3019
|
-
kind: "draft",
|
|
3020
|
-
command: "nudo contract --draft",
|
|
3021
|
-
label: "narrow the entry with a sidecar fn({ \u2026 }) param contract \u2014 most honest fix for helpers (also tightens the signature)",
|
|
3022
|
-
...i.fn ? { hint: i.fn } : {}
|
|
3023
|
-
},
|
|
3024
|
-
{
|
|
3025
|
-
kind: "relax",
|
|
3026
|
-
label: "declare intentional fail-fast: @nudo:throws Error (or case `!! throws`)",
|
|
3027
|
-
...i.fn ? { hint: i.fn } : {}
|
|
3028
|
-
},
|
|
3029
|
-
{
|
|
3030
|
-
kind: "info",
|
|
3031
|
-
label: "or refine / guard / try-catch the entry so it is total"
|
|
3032
|
-
},
|
|
3033
|
-
{
|
|
3034
|
-
kind: "ignore-throws",
|
|
3035
|
-
command: "nudo check --ignore-throws TypeError",
|
|
3036
|
-
label: "migration switch: ignore this throw class on L2"
|
|
3037
|
-
}
|
|
3038
|
-
];
|
|
3039
|
-
case "nudo:unproven-return":
|
|
3040
|
-
return [
|
|
3041
|
-
{
|
|
3042
|
-
kind: "relax",
|
|
3043
|
-
label: `relax the return contract${i.fn ? ` of ${i.fn}` : ""} to the inferred surface (review the diff \u2014 not a silent green)`,
|
|
3044
|
-
...i.expected ? { hint: i.expected } : {}
|
|
3045
|
-
},
|
|
3046
|
-
draft,
|
|
3047
|
-
{
|
|
3048
|
-
kind: "info",
|
|
3049
|
-
label: "or narrow the return expression / add a precondition so the bound is implied"
|
|
3050
|
-
}
|
|
3051
|
-
];
|
|
3052
|
-
case "nudo:unknown-inference":
|
|
3053
|
-
case "nudo:opaque-result":
|
|
3054
|
-
return [
|
|
3055
|
-
{ kind: "mock", label: "pin the native/unknown face with @nudo:mock" },
|
|
3056
|
-
{ kind: "assume", command: "nudo check --assume", label: "state preconditions via --assume" },
|
|
3057
|
-
{ kind: "info", label: "or add a call site / @nudo:case for evidence" }
|
|
3058
|
-
];
|
|
3059
|
-
case "nudo:arg-opaque":
|
|
3060
|
-
case "nudo:constraint-unproven":
|
|
3061
|
-
return [
|
|
3062
|
-
{ kind: "assume", command: "nudo check --assume", label: "state preconditions via --assume" },
|
|
3063
|
-
{ kind: "info", label: "add a call site or @nudo:case" }
|
|
3064
|
-
];
|
|
3065
|
-
case "nudo:fork-truncated":
|
|
3066
|
-
case "nudo:promise-micro-truncated":
|
|
3067
|
-
case "nudo:promise-micro-error":
|
|
3068
|
-
return [
|
|
3069
|
-
{
|
|
3070
|
-
kind: "info",
|
|
3071
|
-
label: "results widened to unknown \u2014 raise budget (@nudo:budget forks/calls/depth on the function) or narrow control flow"
|
|
3072
|
-
}
|
|
3073
|
-
];
|
|
3074
|
-
case "nudo:loop-truncated":
|
|
3075
|
-
return [
|
|
3076
|
-
{
|
|
3077
|
-
kind: "info",
|
|
3078
|
-
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"
|
|
3079
|
-
}
|
|
3080
|
-
];
|
|
3081
|
-
case "nudo:recursion-truncated":
|
|
3082
|
-
return [
|
|
3083
|
-
{
|
|
3084
|
-
kind: "info",
|
|
3085
|
-
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"
|
|
3086
|
-
}
|
|
3087
|
-
];
|
|
3088
|
-
case "nudo:host-effect-blocked":
|
|
3089
|
-
return [
|
|
3090
|
-
{ kind: "mock", label: "pin the host API with @nudo:mock / @nudo:env" },
|
|
3091
|
-
{ kind: "info", label: "results widened to unknown \u2014 or feed the value in from a call site" }
|
|
3092
|
-
];
|
|
3093
|
-
case "nudo:interface-drift":
|
|
3094
|
-
return [
|
|
3095
|
-
{
|
|
3096
|
-
kind: "emit",
|
|
3097
|
-
command: "nudo contract --emit",
|
|
3098
|
-
label: "refresh the @generated segment"
|
|
3099
|
-
}
|
|
3100
|
-
];
|
|
3101
|
-
case "nudo:dual-entry":
|
|
3102
|
-
return [
|
|
3103
|
-
{
|
|
3104
|
-
kind: "info",
|
|
3105
|
-
label: "browser/node records do not cross files \u2014 analyze the entry you ship; mock or skip the other variant"
|
|
3106
|
-
}
|
|
3107
|
-
];
|
|
3108
|
-
default:
|
|
3109
|
-
return i.suggestion ? [{ kind: "info", label: i.suggestion }] : [];
|
|
3110
|
-
}
|
|
3111
|
-
}
|
|
3112
|
-
function serializeCheckJson(r) {
|
|
3113
|
-
return {
|
|
3114
|
-
version: 1,
|
|
3115
|
-
file: r.file,
|
|
3116
|
-
ok: r.ok,
|
|
3117
|
-
summary: { ...r.summary },
|
|
3118
|
-
signatures: r.signatures.map((s) => ({
|
|
3119
|
-
name: s.name,
|
|
3120
|
-
params: [...s.params],
|
|
3121
|
-
...s.paramTypes ? { paramTypes: [...s.paramTypes] } : {},
|
|
3122
|
-
display: s.display,
|
|
3123
|
-
detail: s.detail,
|
|
3124
|
-
conf: s.conf,
|
|
3125
|
-
abs: formatAbs(s.abs),
|
|
3126
|
-
...s.throws ? { throws: s.throws } : {},
|
|
3127
|
-
...s.entry ? { entry: true } : {}
|
|
3128
|
-
})),
|
|
3129
|
-
issues: r.issues.map((i) => {
|
|
3130
|
-
const acts = i.actions ?? actionsForIssue(i);
|
|
3131
|
-
return {
|
|
3132
|
-
severity: i.severity,
|
|
3133
|
-
code: i.code,
|
|
3134
|
-
message: i.message,
|
|
3135
|
-
...i.fn !== void 0 ? { fn: i.fn } : {},
|
|
3136
|
-
...i.line !== void 0 ? { line: i.line } : {},
|
|
3137
|
-
...i.column !== void 0 ? { column: i.column } : {},
|
|
3138
|
-
...i.actual !== void 0 ? { actual: i.actual } : {},
|
|
3139
|
-
...i.expected !== void 0 ? { expected: i.expected } : {},
|
|
3140
|
-
...i.suggestion !== void 0 ? { suggestion: i.suggestion } : {},
|
|
3141
|
-
...i.reason !== void 0 ? { reason: i.reason } : {},
|
|
3142
|
-
...acts.length > 0 ? { actions: acts } : {}
|
|
3143
|
-
};
|
|
3144
|
-
}),
|
|
3145
|
-
...r.budget ? { budget: { ...r.budget } } : {}
|
|
3146
|
-
};
|
|
3147
|
-
}
|
|
3148
|
-
function serializeCheckJsonMulti(reports) {
|
|
3149
|
-
const summary = {
|
|
3150
|
-
errors: 0,
|
|
3151
|
-
warnings: 0,
|
|
3152
|
-
infos: 0,
|
|
3153
|
-
functions: 0,
|
|
3154
|
-
files: reports.length
|
|
3155
|
-
};
|
|
3156
|
-
let calls = 0;
|
|
3157
|
-
let forks = 0;
|
|
3158
|
-
let maxCalls = 0;
|
|
3159
|
-
let maxForks = 0;
|
|
3160
|
-
let callTruncated = false;
|
|
3161
|
-
let forkTruncated = false;
|
|
3162
|
-
let evalCalls = 0;
|
|
3163
|
-
let maxEvalCalls = 0;
|
|
3164
|
-
let evalCallTruncated = false;
|
|
3165
|
-
let cycleTruncated = false;
|
|
3166
|
-
let loopTruncated = false;
|
|
3167
|
-
const reasons = /* @__PURE__ */ new Set();
|
|
3168
|
-
for (const r of reports) {
|
|
3169
|
-
summary.errors += r.summary.errors;
|
|
3170
|
-
summary.warnings += r.summary.warnings;
|
|
3171
|
-
summary.infos += r.summary.infos;
|
|
3172
|
-
summary.functions += r.summary.functions;
|
|
3173
|
-
if (r.budget) {
|
|
3174
|
-
calls += r.budget.calls;
|
|
3175
|
-
forks += r.budget.forks;
|
|
3176
|
-
maxCalls = Math.max(maxCalls, r.budget.maxCalls);
|
|
3177
|
-
maxForks = Math.max(maxForks, r.budget.maxForks);
|
|
3178
|
-
callTruncated = callTruncated || r.budget.callTruncated;
|
|
3179
|
-
forkTruncated = forkTruncated || r.budget.forkTruncated;
|
|
3180
|
-
evalCalls += r.budget.evalCalls ?? 0;
|
|
3181
|
-
maxEvalCalls = Math.max(maxEvalCalls, r.budget.maxEvalCalls ?? 0);
|
|
3182
|
-
evalCallTruncated = evalCallTruncated || (r.budget.evalCallTruncated ?? false);
|
|
3183
|
-
cycleTruncated = cycleTruncated || (r.budget.cycleTruncated ?? false);
|
|
3184
|
-
loopTruncated = loopTruncated || (r.budget.loopTruncated ?? false);
|
|
3185
|
-
for (const x of r.budget.reasons ?? []) reasons.add(x);
|
|
3186
|
-
}
|
|
3187
|
-
}
|
|
3188
|
-
const budgetTruncated = callTruncated || evalCallTruncated || forkTruncated || loopTruncated;
|
|
3189
|
-
return {
|
|
3190
|
-
version: 1,
|
|
3191
|
-
kind: "multi",
|
|
3192
|
-
ok: reports.every((r) => r.ok),
|
|
3193
|
-
summary: {
|
|
3194
|
-
...summary,
|
|
3195
|
-
...budgetTruncated ? { budgetTruncated: true } : {}
|
|
3196
|
-
},
|
|
3197
|
-
...budgetTruncated || reports.some((r) => r.budget) ? {
|
|
3198
|
-
budget: {
|
|
3199
|
-
truncated: budgetTruncated,
|
|
3200
|
-
callTruncated,
|
|
3201
|
-
forkTruncated,
|
|
3202
|
-
calls,
|
|
3203
|
-
maxCalls,
|
|
3204
|
-
forks,
|
|
3205
|
-
maxForks,
|
|
3206
|
-
evalCalls,
|
|
3207
|
-
maxEvalCalls,
|
|
3208
|
-
evalCallTruncated,
|
|
3209
|
-
cycleTruncated,
|
|
3210
|
-
loopTruncated,
|
|
3211
|
-
reasons: [...reasons].sort()
|
|
3212
|
-
}
|
|
3213
|
-
} : {},
|
|
3214
|
-
reports
|
|
3215
|
-
};
|
|
3216
|
-
}
|
|
3217
|
-
var CONTRACT_FIX_CODES = /* @__PURE__ */ new Set([
|
|
3218
|
-
"nudo:constraint-violated",
|
|
3219
|
-
"nudo:assign-mismatch",
|
|
3220
|
-
"nudo:arg-structure",
|
|
3221
|
-
"nudo:interface-domain-exceeds",
|
|
3222
|
-
"nudo:entry-may-throw",
|
|
3223
|
-
"nudo:missing-slot"
|
|
3224
|
-
]);
|
|
3225
|
-
function formatCheckReport(r, opts = {}) {
|
|
3226
|
-
const lines = [];
|
|
3227
|
-
lines.push(`nudo check ${r.file}`);
|
|
3228
|
-
lines.push(r.ok ? "OK" : "FAILED");
|
|
3229
|
-
lines.push(
|
|
3230
|
-
` ${r.summary.errors} error \xB7 ${r.summary.warnings} warning \xB7 ${r.summary.infos} info \xB7 ${r.summary.functions} fn`
|
|
3231
|
-
);
|
|
3232
|
-
if (r.budget?.truncated) {
|
|
3233
|
-
const onlyLoop = r.budget.loopTruncated && !r.budget.callTruncated && !r.budget.evalCallTruncated && !r.budget.forkTruncated;
|
|
3234
|
-
lines.push("");
|
|
3235
|
-
lines.push("budget");
|
|
3236
|
-
lines.push(
|
|
3237
|
-
` truncated calls ${r.budget.calls}/${r.budget.maxCalls} \xB7 eval calls ${r.budget.evalCalls}/${r.budget.maxEvalCalls} \xB7 forks ${r.budget.forks}/${r.budget.maxForks}${r.budget.callTruncated ? " (calls)" : ""}${r.budget.evalCallTruncated ? " (eval calls)" : ""}${r.budget.forkTruncated ? " (forks)" : ""}${r.budget.loopTruncated ? " (loop iters)" : ""}`
|
|
3238
|
-
);
|
|
3239
|
-
lines.push(
|
|
3240
|
-
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`
|
|
3241
|
-
);
|
|
3242
|
-
}
|
|
3243
|
-
if (r.signatures.length > 0) {
|
|
3244
|
-
lines.push("");
|
|
3245
|
-
lines.push("signatures");
|
|
3246
|
-
for (const s of r.signatures) {
|
|
3247
|
-
const paramStr = s.paramTypes && s.paramTypes.length > 0 ? s.params.map((p, i) => `${p}: ${s.paramTypes[i] ?? "any"}`).join(", ") : s.params.join(", ");
|
|
3248
|
-
const throwsStr = s.throws ? ` throws ${s.throws}` : "";
|
|
3249
|
-
const displayBase = s.display.replace(/\s+throws\s+[\s\S]+$/, "").trim();
|
|
3250
|
-
const shapeK = s.abs?.shape?.k;
|
|
3251
|
-
const looksLikeFakeAny = shapeK === "any" && displayBase.length > 0 && !/^any(\b|$)/.test(displayBase);
|
|
3252
|
-
const retStr = !looksLikeFakeAny && shapeK ? formatShape(s.abs) || displayBase.replace(/\s+#\w+$/, "").trim() : displayBase.replace(/\s+#\w+$/, "").trim();
|
|
3253
|
-
lines.push(` ${s.name}(${paramStr}) => ${retStr}${throwsStr}`);
|
|
3254
|
-
if (opts.verbose) {
|
|
3255
|
-
for (const ln of s.detail.split("\n").slice(1)) {
|
|
3256
|
-
lines.push(` ${ln}`);
|
|
3257
|
-
}
|
|
3258
|
-
}
|
|
3259
|
-
}
|
|
3260
|
-
}
|
|
3261
|
-
if (r.issues.length === 0) {
|
|
3262
|
-
lines.push("");
|
|
3263
|
-
lines.push("(no issues)");
|
|
3264
|
-
} else {
|
|
3265
|
-
lines.push("");
|
|
3266
|
-
lines.push("issues");
|
|
3267
|
-
for (const i of r.issues) {
|
|
3268
|
-
const loc = i.line != null ? `L${i.line}` : "";
|
|
3269
|
-
const head = [i.severity.toUpperCase(), loc, i.fn].filter(Boolean).join(" ");
|
|
3270
|
-
lines.push(` [${head}] ${i.message} (${i.code})`);
|
|
3271
|
-
const showDetail = i.severity === "error" || opts.verbose;
|
|
3272
|
-
if (showDetail && i.actual) lines.push(` actual: ${i.actual}`);
|
|
3273
|
-
if (showDetail && i.expected) lines.push(` expected: ${i.expected}`);
|
|
3274
|
-
if (i.suggestion) {
|
|
3275
|
-
lines.push(` \u2192 ${i.suggestion}`);
|
|
3276
|
-
} else if (CONTRACT_FIX_CODES.has(i.code)) {
|
|
3277
|
-
lines.push(` \u2192 add a refine / sidecar contract, or fix the call-site value`);
|
|
3278
|
-
}
|
|
3279
|
-
if (CONTRACT_FIX_CODES.has(i.code)) {
|
|
3280
|
-
lines.push(` fix: nudo contract --draft (emit a sidecar draft you can edit)`);
|
|
3281
|
-
}
|
|
3282
|
-
}
|
|
3283
|
-
}
|
|
3284
|
-
return lines.join("\n");
|
|
3285
|
-
}
|
|
3286
|
-
function escapeGhaData(s) {
|
|
3287
|
-
return s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
|
3288
|
-
}
|
|
3289
|
-
function escapeGhaProp(s) {
|
|
3290
|
-
return escapeGhaData(s).replace(/:/g, "%3A").replace(/,/g, "%2C");
|
|
3291
|
-
}
|
|
3292
|
-
function formatGithubAnnotations(r, opts = {}) {
|
|
3293
|
-
const lines = [];
|
|
3294
|
-
let file = r.file;
|
|
3295
|
-
if (opts.workspaceRoot && file.startsWith(opts.workspaceRoot)) {
|
|
3296
|
-
file = file.slice(opts.workspaceRoot.length).replace(/^[/\\]/, "");
|
|
3297
|
-
}
|
|
3298
|
-
for (const i of r.issues) {
|
|
3299
|
-
const level = i.severity === "error" ? "error" : i.severity === "warning" ? "warning" : "notice";
|
|
3300
|
-
const line = i.line ?? 1;
|
|
3301
|
-
const col = (i.column ?? 0) + 1;
|
|
3302
|
-
const title = escapeGhaProp(i.code);
|
|
3303
|
-
const msgBits = [i.message];
|
|
3304
|
-
if (i.actual && i.expected) msgBits.push(`${i.actual} \u22AD ${i.expected}`);
|
|
3305
|
-
else if (i.suggestion) msgBits.push(i.suggestion);
|
|
3306
|
-
if (CONTRACT_FIX_CODES.has(i.code)) {
|
|
3307
|
-
msgBits.push(`fix: nudo contract --draft`);
|
|
3308
|
-
}
|
|
3309
|
-
const msg = escapeGhaData(msgBits.join(" \xB7 "));
|
|
3310
|
-
const props = [
|
|
3311
|
-
`file=${escapeGhaProp(file)}`,
|
|
3312
|
-
`line=${line}`,
|
|
3313
|
-
`col=${col}`,
|
|
3314
|
-
`title=${title}`
|
|
3315
|
-
].join(",");
|
|
3316
|
-
lines.push(`::${level} ${props}::${msg}`);
|
|
3317
|
-
}
|
|
3318
|
-
return lines.join("\n");
|
|
3319
|
-
}
|
|
3320
|
-
function formatGitlabCodeQuality(r, opts = {}) {
|
|
3321
|
-
let file = r.file;
|
|
3322
|
-
if (opts.workspaceRoot && file.startsWith(opts.workspaceRoot)) {
|
|
3323
|
-
file = file.slice(opts.workspaceRoot.length).replace(/^[/\\]/, "");
|
|
3324
|
-
}
|
|
3325
|
-
return r.issues.map((i) => {
|
|
3326
|
-
const begin = i.line ?? 1;
|
|
3327
|
-
const severity = i.severity === "error" ? "major" : i.severity === "warning" ? "minor" : "info";
|
|
3328
|
-
const description = i.actual && i.expected ? `${i.message} (${i.actual} \u22AD ${i.expected})` : i.message;
|
|
3329
|
-
const fingerprint = `${file}:${i.code}:${begin}:${i.message}`;
|
|
3330
|
-
return {
|
|
3331
|
-
description,
|
|
3332
|
-
check_name: i.code,
|
|
3333
|
-
fingerprint,
|
|
3334
|
-
severity,
|
|
3335
|
-
location: { path: file, lines: { begin } }
|
|
3336
|
-
};
|
|
3337
|
-
});
|
|
3338
|
-
}
|
|
3339
3005
|
export {
|
|
3340
3006
|
$add,
|
|
3341
3007
|
$arguments,
|