supercov 0.0.43 → 0.0.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/analyzers/typescript/README.md +4 -0
- package/analyzers/typescript/bin/identity.mjs +4 -0
- package/analyzers/typescript/dist/analyze.js +1352 -51
- package/analyzers/typescript/dist/archive.js +31 -3
- package/analyzers/typescript/dist/awaited-observations.js +376 -0
- package/analyzers/typescript/dist/build-identity.json +1 -1
- package/analyzers/typescript/dist/mock-counts.js +2517 -0
- package/analyzers/typescript/dist/pragmas.js +59 -16
- package/analyzers/typescript/src/analyze.ts +1738 -96
- package/analyzers/typescript/src/archive.ts +36 -3
- package/analyzers/typescript/src/awaited-observations.ts +561 -0
- package/analyzers/typescript/src/mock-counts.ts +3219 -0
- package/analyzers/typescript/src/pragmas.ts +90 -24
- package/docs/agent-loop.md +116 -31
- package/docs/assertion-evidence.md +560 -1
- package/docs/cli.md +15 -15
- package/docs/code-verification.md +3 -181
- package/docs/coverage-model.md +6 -6
- package/docs/evidence.md +6 -6
- package/docs/getting-started.md +60 -72
- package/docs/performance.md +4 -4
- package/docs/troubleshooting.md +8 -8
- package/docs/verification.md +2 -2
- package/docs/workspace-isolation.md +1 -1
- package/package.json +11 -9
- package/runtime/javascript/nodeAssertAdapter.mjs +32 -8
- package/runtime/javascript/nodeTest.mjs +13 -5
- package/runtime/javascript/runnerEvidence.mjs +33 -11
- package/runtime/javascript/runtime.mjs +22 -3
|
@@ -29,7 +29,18 @@ export function collectPragmas(compiler, files, relativeFile, sites) {
|
|
|
29
29
|
const raw = sf.text.slice(range.pos, range.end);
|
|
30
30
|
if (!/^\/\/\s*observes:/.test(raw) || comments.has(key(sf, range.pos)))
|
|
31
31
|
continue;
|
|
32
|
-
const
|
|
32
|
+
const parts = raw.split(/;\s*check\s+/);
|
|
33
|
+
const check = parts.length === 2 && parts[1].trim() === "missing call"
|
|
34
|
+
? "missing-call"
|
|
35
|
+
: parts.length === 2 && parts[1].trim() === "count"
|
|
36
|
+
? "count"
|
|
37
|
+
: parts.length === 2 && parts[1].trim() === "value"
|
|
38
|
+
? "value"
|
|
39
|
+
: parts.length === 2 && parts[1].trim() === "completion"
|
|
40
|
+
? "completion"
|
|
41
|
+
: undefined;
|
|
42
|
+
const checkIssue = parts.length > 1 && !check ? "unsupported-check-recipe" : undefined;
|
|
43
|
+
const parsed = /^\/\/\s*observes:\s*(\S+?)#(\S+)(?:\s+(.+?))?\s*$/.exec(parts[0]);
|
|
33
44
|
const suffix = parsed?.[3]?.split(/(?:^|\s+)via\s+/);
|
|
34
45
|
const target = parsed
|
|
35
46
|
? {
|
|
@@ -44,28 +55,42 @@ export function collectPragmas(compiler, files, relativeFile, sites) {
|
|
|
44
55
|
target.file
|
|
45
56
|
.split("/")
|
|
46
57
|
.every((part) => part && part !== "." && part !== "..");
|
|
47
|
-
const
|
|
48
|
-
? sites
|
|
49
|
-
|
|
58
|
+
const matchingSites = validPath
|
|
59
|
+
? sites.filter((s) => s.file === target.file &&
|
|
60
|
+
// The recipe selects the emission, not a containing callback-return site.
|
|
61
|
+
(!check ||
|
|
62
|
+
(check === "missing-call"
|
|
63
|
+
? s.category === "log"
|
|
64
|
+
: check === "completion"
|
|
65
|
+
? s.category === "return" || s.category === "throw"
|
|
66
|
+
: s.kind === "decision" || s.category === "return")) &&
|
|
50
67
|
(s.owner === target.function || s.fn === target.function) &&
|
|
51
68
|
(!target.snippet || s.text.includes(target.snippet)))
|
|
52
|
-
.map((s) => s.id)
|
|
53
69
|
: [];
|
|
70
|
+
// An exact whole-site selector is more specific than a containing return
|
|
71
|
+
// whose display text happens to include that expression. Equal sibling
|
|
72
|
+
// sites remain ambiguous. This only selects a node; it proves no behavior.
|
|
73
|
+
const exact = check && target?.snippet
|
|
74
|
+
? matchingSites.filter((s) => s.text.trim() === target.snippet.trim())
|
|
75
|
+
: [];
|
|
76
|
+
const candidates = (exact.length ? exact : matchingSites).map((s) => s.id);
|
|
54
77
|
comments.set(key(sf, range.pos), {
|
|
55
78
|
id: location(sf, range.pos),
|
|
56
79
|
where: location(sf, range.pos),
|
|
57
80
|
raw,
|
|
58
81
|
target,
|
|
82
|
+
...(check ? { check } : {}),
|
|
59
83
|
candidateSites: candidates,
|
|
60
|
-
issue:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
84
|
+
issue: checkIssue ??
|
|
85
|
+
(!target
|
|
86
|
+
? "invalid-syntax"
|
|
87
|
+
: !validPath
|
|
88
|
+
? "invalid-target-path"
|
|
89
|
+
: !candidates.length
|
|
90
|
+
? "target-not-in-inventory"
|
|
91
|
+
: candidates.length > 1
|
|
92
|
+
? "ambiguous-target"
|
|
93
|
+
: undefined),
|
|
69
94
|
attachments: [],
|
|
70
95
|
});
|
|
71
96
|
}
|
|
@@ -78,7 +103,17 @@ export function collectPragmas(compiler, files, relativeFile, sites) {
|
|
|
78
103
|
visit(sf);
|
|
79
104
|
}
|
|
80
105
|
return {
|
|
81
|
-
|
|
106
|
+
hasHint(node) {
|
|
107
|
+
let statement = node;
|
|
108
|
+
while (statement.parent && !compiler.isStatement(statement))
|
|
109
|
+
statement = statement.parent;
|
|
110
|
+
if (!compiler.isExpressionStatement(statement))
|
|
111
|
+
return false;
|
|
112
|
+
const sf = node.getSourceFile();
|
|
113
|
+
return (compiler.getLeadingCommentRanges(sf.text, statement.getFullStart()) ??
|
|
114
|
+
[]).some((range) => comments.has(key(sf, range.pos)));
|
|
115
|
+
},
|
|
116
|
+
register(node, method, testKey, inert, awaitedObservation) {
|
|
82
117
|
let statement = node;
|
|
83
118
|
while (statement.parent && !compiler.isStatement(statement))
|
|
84
119
|
statement = statement.parent;
|
|
@@ -95,6 +130,7 @@ export function collectPragmas(compiler, files, relativeFile, sites) {
|
|
|
95
130
|
source: location(sf, node.getStart(sf)),
|
|
96
131
|
method,
|
|
97
132
|
inert,
|
|
133
|
+
...(awaitedObservation ? { awaitedObservation } : {}),
|
|
98
134
|
};
|
|
99
135
|
if (!comment.attachments.some((a) => a.testKey === testKey &&
|
|
100
136
|
a.source === attachment.source &&
|
|
@@ -123,7 +159,11 @@ export function collectPragmas(compiler, files, relativeFile, sites) {
|
|
|
123
159
|
},
|
|
124
160
|
];
|
|
125
161
|
return matched.map(({ a, b }) => {
|
|
126
|
-
|
|
162
|
+
// A source-checked poll is not an explicit assertion phase. Neither
|
|
163
|
+
// a matching phase name nor a passing test can supply its read receipt.
|
|
164
|
+
const witnessIssue = a.awaitedObservation
|
|
165
|
+
? "observation-capture-unavailable"
|
|
166
|
+
: assertionWitnessIssue(b.phases, a.source, a.method);
|
|
127
167
|
return {
|
|
128
168
|
...comment,
|
|
129
169
|
id: `${comment.id}@${b.id}`,
|
|
@@ -131,6 +171,9 @@ export function collectPragmas(compiler, files, relativeFile, sites) {
|
|
|
131
171
|
test: b.id,
|
|
132
172
|
assertionSource: a.source,
|
|
133
173
|
assertionMethod: a.method,
|
|
174
|
+
...(a.awaitedObservation
|
|
175
|
+
? { awaitedObservation: a.awaitedObservation }
|
|
176
|
+
: {}),
|
|
134
177
|
witness: witnessIssue
|
|
135
178
|
? "unavailable"
|
|
136
179
|
: "passed",
|