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.
@@ -10,7 +10,24 @@ export const PROTOCOL = {
10
10
  capabilities: [
11
11
  "requiresTotal-v1",
12
12
  "assertion-witness-issues-v1",
13
+ "complete-passed-test-inventory-v1",
14
+ "witnessed-callback-scope-v1",
13
15
  "assertion-hints-v1",
16
+ "awaited-observation-sources-v1",
17
+ "first-test-call-omission-v1",
18
+ "closed-count-sensitivity-v1",
19
+ "closed-payload-sensitivity-v1",
20
+ "payload-native-predicates-v1",
21
+ "first-test-direct-return-v1",
22
+ "mock-observation-projections-v1",
23
+ "assertion-comparison-relations-v2",
24
+ "process-exit-source-v1",
25
+ "process-exit-consumer-v1",
26
+ "mock-count-lifetimes-v1",
27
+ "mock-count-factories-v1",
28
+ "mock-count-rows-v1",
29
+ "mock-count-array-projections-v1",
30
+ "primitive-decision-sensitivity-v1",
14
31
  ],
15
32
  };
16
33
  function sameScope(a, b) {
@@ -122,13 +139,22 @@ function analyzeArchiveWithFrontend(input, frontend) {
122
139
  throw new Error(`Unsupported decision atom layout: ${d.id}`);
123
140
  atoms.forEach((n, i) => {
124
141
  const begin = sf.getLineAndCharacterOfPosition(n.getStart(sf)), end = sf.getLineAndCharacterOfPosition(n.getEnd());
125
- // The flow pass resolves the actual AST owner; this fallback is only for presentation.
142
+ // This owner also participates in source-hint selection. Named arrow
143
+ // bindings must not be mislabeled <module> merely because they are not
144
+ // function declarations.
126
145
  let parent = n.parent, owner = "<module>";
127
146
  while (parent) {
128
147
  if (compiler.isFunctionDeclaration(parent) && parent.name) {
129
148
  owner = parent.name.text;
130
149
  break;
131
150
  }
151
+ if ((compiler.isArrowFunction(parent) ||
152
+ compiler.isFunctionExpression(parent)) &&
153
+ compiler.isVariableDeclaration(parent.parent) &&
154
+ compiler.isIdentifier(parent.parent.name)) {
155
+ owner = parent.parent.name.text;
156
+ break;
157
+ }
132
158
  parent = parent.parent;
133
159
  }
134
160
  sites.push({
@@ -181,8 +207,9 @@ function analyzeArchiveWithFrontend(input, frontend) {
181
207
  for (const [i, r] of accepted.entries()) {
182
208
  const id = `A${i + 1}`;
183
209
  if (!r.testFile) {
184
- limitations.add("A passed test has no source file.");
185
- continue;
210
+ // Dropping even one passing attempt could turn its executed/asserted sites
211
+ // into apparently certain gaps. Ordinary coverage can still be queried.
212
+ throw new Error("A passed test has no source file; assertion analysis requires test-source provenance. Recapture with a supported runner and stack formatter.");
186
213
  }
187
214
  if (r.browser.length)
188
215
  limitations.add("Browser evidence is not joined in this archive adapter.");
@@ -272,6 +299,7 @@ function analyzeArchiveWithFrontend(input, frontend) {
272
299
  line: 0,
273
300
  ok: true,
274
301
  phaseLines,
302
+ runner: r.provenance?.runner,
275
303
  });
276
304
  attempts.push({
277
305
  id,
@@ -0,0 +1,376 @@
1
+ /**
2
+ * Recognize a direct awaited factory method which polls an append-only native
3
+ * child capture. Names, paths, regex and timeout values come from bindings/source.
4
+ * Unsupported control flow or aliases are not evidence of a missing assertion.
5
+ * Native APIs and absence of external monkey-patching remain model premises.
6
+ */
7
+ export function awaitedObservationSource(context, callback, call) {
8
+ const { syntax: t, declaration: decl, rawDeclaration: raw } = context;
9
+ function require(value) {
10
+ if (!value)
11
+ throw unsupported;
12
+ }
13
+ const unsupported = Symbol("unsupported awaited observation");
14
+ const nodes = (root, predicate) => {
15
+ const found = [];
16
+ const visit = (n) => {
17
+ if (predicate(n))
18
+ found.push(n);
19
+ t.forEachChild(n, visit);
20
+ };
21
+ visit(root);
22
+ return found;
23
+ };
24
+ const one = (values) => {
25
+ require(values.length === 1);
26
+ return values[0];
27
+ };
28
+ const at = (node) => {
29
+ const sf = node.getSourceFile(), p = sf.getLineAndCharacterOfPosition(node.getStart(sf));
30
+ return `${context.relativeFile(sf)}:${p.line + 1}:${p.character + 1}`;
31
+ };
32
+ const importedFrom = (node, name) => {
33
+ const d = raw(node);
34
+ return !!d &&
35
+ t.isImportSpecifier(d) &&
36
+ !d.isTypeOnly &&
37
+ !d.parent.parent.isTypeOnly &&
38
+ (d.propertyName ?? d.name).text === name &&
39
+ t.isStringLiteral(d.parent.parent.parent.moduleSpecifier)
40
+ ? d.parent.parent.parent.moduleSpecifier.text
41
+ : undefined;
42
+ };
43
+ const constant = (node) => t.isVariableDeclaration(node) &&
44
+ t.isIdentifier(node.name) &&
45
+ t.isVariableDeclarationList(node.parent) &&
46
+ !!(node.parent.flags & t.NodeFlags.Const) &&
47
+ node.parent.declarations.length === 1;
48
+ const simpleArrow = (node) => t.isArrowFunction(node) &&
49
+ node.parameters.length === 0 &&
50
+ !node.modifiers?.length;
51
+ const bodyExpression = (node) => {
52
+ if (!t.isBlock(node.body))
53
+ return node.body;
54
+ require(node.body.statements.length === 1);
55
+ const returned = node.body.statements[0];
56
+ require(t.isReturnStatement(returned) && returned.expression);
57
+ return returned.expression;
58
+ };
59
+ const positiveNumber = (node) => t.isNumericLiteral(node) &&
60
+ Number.isFinite(Number(node.text)) &&
61
+ Number(node.text) > 0;
62
+ const global = (node, name) => {
63
+ const d = decl(node);
64
+ return (t.isIdentifier(node) &&
65
+ node.text === name &&
66
+ (!d || d.getSourceFile().isDeclarationFile));
67
+ };
68
+ const dateNow = (node) => t.isCallExpression(node) &&
69
+ !node.questionDotToken &&
70
+ node.arguments.length === 0 &&
71
+ t.isPropertyAccessExpression(node.expression) &&
72
+ node.expression.name.text === "now" &&
73
+ global(node.expression.expression, "Date");
74
+ const references = (root, binding) => nodes(root, (n) => t.isIdentifier(n) && decl(n) === binding);
75
+ try {
76
+ require(t.isArrowFunction(callback) &&
77
+ t.isBlock(callback.body) &&
78
+ callback.modifiers?.some((m) => m.kind === t.SyntaxKind.AsyncKeyword));
79
+ const registration = callback.parent;
80
+ require(t.isCallExpression(registration) &&
81
+ registration.arguments.at(-1) === callback &&
82
+ importedFrom(registration.expression, "test") === "node:test" &&
83
+ t.isExpressionStatement(registration.parent) &&
84
+ t.isSourceFile(registration.parent.parent));
85
+ require(callback.body.statements.every((s) => t.isVariableStatement(s) || t.isExpressionStatement(s)));
86
+ require(t.isAwaitExpression(call.parent) &&
87
+ t.isExpressionStatement(call.parent.parent) &&
88
+ call.parent.parent.parent === callback.body &&
89
+ call.arguments.length === 0 &&
90
+ !call.questionDotToken &&
91
+ t.isPropertyAccessExpression(call.expression) &&
92
+ !call.expression.questionDotToken);
93
+ const member = call.expression.name.text, receiver = decl(call.expression.expression);
94
+ require(receiver &&
95
+ constant(receiver) &&
96
+ receiver.initializer &&
97
+ t.isCallExpression(receiver.initializer));
98
+ const launch = receiver.initializer, factory = decl(launch.expression);
99
+ require(factory &&
100
+ t.isFunctionDeclaration(factory) &&
101
+ factory.body &&
102
+ !factory.asteriskToken &&
103
+ !factory.modifiers?.some((m) => m.kind === t.SyntaxKind.AsyncKeyword) &&
104
+ factory.parameters.every((p) => t.isIdentifier(p.name) && !p.initializer && !p.dotDotDotToken));
105
+ require(receiver.parent.parent.parent === callback.body);
106
+ const statements = callback.body.statements;
107
+ require(statements.indexOf(call.parent.parent) ===
108
+ statements.indexOf(receiver.parent.parent) + 1);
109
+ require(nodes(callback, (n) => t.isCallExpression(n) && decl(n.expression) === factory).length === 1);
110
+ require(nodes(callback, (n) => t.isCallExpression(n) &&
111
+ t.isPropertyAccessExpression(n.expression) &&
112
+ decl(n.expression.expression) === receiver &&
113
+ n.expression.name.text === member).length === 1);
114
+ require(nodes(callback, (n) => t.isIdentifier(n) && ["eval", "Function"].includes(n.text)).length === 0);
115
+ const factoryBody = factory.body;
116
+ require(factoryBody.statements.every((s) => t.isVariableStatement(s) ||
117
+ t.isExpressionStatement(s) ||
118
+ t.isReturnStatement(s)));
119
+ const returned = one(factoryBody.statements.filter(t.isReturnStatement));
120
+ require(returned === factoryBody.statements.at(-1) &&
121
+ returned.expression &&
122
+ t.isObjectLiteralExpression(returned.expression));
123
+ const members = returned.expression.properties;
124
+ require(members.every((p) => (t.isPropertyAssignment(p) || t.isShorthandPropertyAssignment(p)) &&
125
+ t.isIdentifier(p.name)));
126
+ require(new Set(members.map((p) => p.name.getText())).size === members.length);
127
+ const property = one(members.filter((p) => p.name?.getText() === member));
128
+ require(t.isPropertyAssignment(property) && simpleArrow(property.initializer));
129
+ const pollCall = bodyExpression(property.initializer);
130
+ require(t.isCallExpression(pollCall) &&
131
+ !pollCall.questionDotToken &&
132
+ pollCall.arguments.length >= 1);
133
+ const poll = decl(pollCall.expression);
134
+ require(poll &&
135
+ constant(poll) &&
136
+ poll.parent.parent.parent === factoryBody &&
137
+ poll.initializer &&
138
+ t.isArrowFunction(poll.initializer) &&
139
+ t.isBlock(poll.initializer.body) &&
140
+ poll.initializer.modifiers?.some((m) => m.kind === t.SyntaxKind.AsyncKeyword));
141
+ const pollFn = poll.initializer, pollBody = pollFn.body;
142
+ require(pollFn.parameters.length === pollCall.arguments.length &&
143
+ pollFn.parameters.every((p) => t.isIdentifier(p.name) && !p.initializer && !p.dotDotDotToken));
144
+ require(pollBody.statements.length === 2 &&
145
+ t.isVariableStatement(pollBody.statements[0]));
146
+ const deadline = one(pollBody.statements[0].declarationList.declarations);
147
+ require(constant(deadline) &&
148
+ deadline.initializer &&
149
+ t.isBinaryExpression(deadline.initializer) &&
150
+ deadline.initializer.operatorToken.kind === t.SyntaxKind.PlusToken &&
151
+ dateNow(deadline.initializer.left) &&
152
+ positiveNumber(deadline.initializer.right));
153
+ const loop = pollBody.statements[1];
154
+ require(t.isWhileStatement(loop) &&
155
+ t.isPrefixUnaryExpression(loop.expression) &&
156
+ loop.expression.operator === t.SyntaxKind.ExclamationToken &&
157
+ t.isCallExpression(loop.expression.operand));
158
+ const predicateCall = loop.expression.operand;
159
+ require(predicateCall.arguments.length === 0 &&
160
+ decl(predicateCall.expression) === pollFn.parameters[0] &&
161
+ !predicateCall.questionDotToken &&
162
+ t.isBlock(loop.statement) &&
163
+ loop.statement.statements.length === 2);
164
+ require(references(pollFn, pollFn.parameters[0]).every((n) => n === pollFn.parameters[0].name || n === predicateCall.expression));
165
+ const [guard, pause] = loop.statement.statements;
166
+ require(t.isIfStatement(guard) &&
167
+ !guard.elseStatement &&
168
+ t.isBlock(guard.thenStatement) &&
169
+ guard.thenStatement.statements.length === 1 &&
170
+ t.isThrowStatement(guard.thenStatement.statements[0]));
171
+ require(t.isExpressionStatement(pause) &&
172
+ t.isAwaitExpression(pause.expression) &&
173
+ t.isCallExpression(pause.expression.expression));
174
+ const delay = pause.expression.expression;
175
+ require(importedFrom(delay.expression, "setTimeout") === "node:timers/promises" &&
176
+ !delay.questionDotToken &&
177
+ delay.arguments.length === 1 &&
178
+ positiveNumber(delay.arguments[0]));
179
+ const predicate = pollCall.arguments[0];
180
+ require(simpleArrow(predicate));
181
+ const read = bodyExpression(predicate);
182
+ require(t.isCallExpression(read) &&
183
+ !read.questionDotToken &&
184
+ read.arguments.length === 1 &&
185
+ t.isPropertyAccessExpression(read.expression) &&
186
+ read.expression.name.text === "test" &&
187
+ t.isRegularExpressionLiteral(read.expression.expression));
188
+ const regex = read.expression.expression.text;
189
+ require(!/[gy]/.test(regex.slice(regex.lastIndexOf("/") + 1)));
190
+ const input = read.arguments[0];
191
+ const operands = t.isBinaryExpression(input) &&
192
+ input.operatorToken.kind === t.SyntaxKind.PlusToken
193
+ ? [input.left, input.right]
194
+ : [input];
195
+ const buffers = operands.map((o) => decl(o));
196
+ require(buffers.every((b) => b && t.isVariableDeclaration(b)) &&
197
+ new Set(buffers).size === buffers.length);
198
+ let child;
199
+ const captures = [];
200
+ for (const [index, maybeBuffer] of buffers.entries()) {
201
+ const buffer = maybeBuffer;
202
+ require(buffer.parent.parent.parent === factoryBody &&
203
+ t.isVariableDeclarationList(buffer.parent) &&
204
+ !!(buffer.parent.flags & t.NodeFlags.Let) &&
205
+ t.isIdentifier(buffer.name) &&
206
+ buffer.initializer &&
207
+ t.isStringLiteral(buffer.initializer) &&
208
+ buffer.initializer.text === "");
209
+ const append = one(nodes(factoryBody, (n) => t.isBinaryExpression(n) &&
210
+ decl(n.left) === buffer &&
211
+ n.operatorToken.kind >= t.SyntaxKind.FirstAssignment &&
212
+ n.operatorToken.kind <= t.SyntaxKind.LastAssignment));
213
+ require(t.isBinaryExpression(append) &&
214
+ append.operatorToken.kind === t.SyntaxKind.PlusEqualsToken &&
215
+ t.isExpressionStatement(append.parent) &&
216
+ t.isBlock(append.parent.parent));
217
+ const capture = append.parent.parent.parent;
218
+ require(t.isArrowFunction(capture) &&
219
+ !capture.modifiers?.length &&
220
+ capture.parameters.length === 1 &&
221
+ !capture.parameters[0].initializer &&
222
+ !capture.parameters[0].dotDotDotToken &&
223
+ decl(append.right) === capture.parameters[0] &&
224
+ t.isBlock(capture.body) &&
225
+ capture.body.statements.length === 1);
226
+ const on = capture.parent;
227
+ require(t.isCallExpression(on) &&
228
+ on.arguments.length === 2 &&
229
+ on.arguments[1] === capture &&
230
+ !on.questionDotToken &&
231
+ t.isStringLiteral(on.arguments[0]) &&
232
+ on.arguments[0].text === "data" &&
233
+ t.isPropertyAccessExpression(on.expression) &&
234
+ !on.expression.questionDotToken &&
235
+ on.expression.name.text === "on" &&
236
+ t.isCallExpression(on.expression.expression));
237
+ const encoding = on.expression.expression;
238
+ require(!encoding.questionDotToken &&
239
+ encoding.arguments.length === 1 &&
240
+ t.isStringLiteral(encoding.arguments[0]) &&
241
+ encoding.arguments[0].text === "utf8" &&
242
+ t.isPropertyAccessExpression(encoding.expression) &&
243
+ encoding.expression.name.text === "setEncoding" &&
244
+ !encoding.expression.questionDotToken &&
245
+ t.isPropertyAccessExpression(encoding.expression.expression));
246
+ const stream = encoding.expression.expression;
247
+ require(!stream.questionDotToken &&
248
+ (stream.name.text === "stdout" || stream.name.text === "stderr"));
249
+ const streamChild = decl(stream.expression);
250
+ require(streamChild &&
251
+ constant(streamChild) &&
252
+ (!child || child === streamChild));
253
+ child = streamChild;
254
+ require(t.isExpressionStatement(on.parent) && on.parent.parent === factoryBody);
255
+ // Only initialization, append, the predicate, trivial accessors and the
256
+ // throw diagnostic can read this binding. No transformed/escaped capture.
257
+ const allowed = new Set([
258
+ buffer.name,
259
+ append.left,
260
+ operands[index],
261
+ ...references(guard.thenStatement, buffer),
262
+ ]);
263
+ for (const p of members)
264
+ if (t.isPropertyAssignment(p) && simpleArrow(p.initializer)) {
265
+ const value = bodyExpression(p.initializer);
266
+ if (decl(value) === buffer)
267
+ allowed.add(value);
268
+ }
269
+ require(references(factoryBody, buffer).every((n) => allowed.has(n)));
270
+ const streamRefs = nodes(factoryBody, (n) => t.isPropertyAccessExpression(n) &&
271
+ decl(n.expression) === child &&
272
+ n.name.text === stream.name.text);
273
+ require(streamRefs.length === 1);
274
+ captures.push({ stream: stream.name.text, source: at(append) });
275
+ }
276
+ require(child &&
277
+ child.initializer &&
278
+ t.isCallExpression(child.initializer) &&
279
+ child.parent.parent.parent === factoryBody &&
280
+ importedFrom(child.initializer.expression, "spawn") ===
281
+ "node:child_process");
282
+ const spawn = child.initializer;
283
+ require(!spawn.questionDotToken &&
284
+ spawn.arguments.length === 3 &&
285
+ t.isObjectLiteralExpression(spawn.arguments[2]));
286
+ const options = spawn.arguments[2].properties;
287
+ require(options.every((p) => t.isPropertyAssignment(p) && t.isIdentifier(p.name)) &&
288
+ new Set(options.map((p) => p.name.getText())).size === options.length);
289
+ const stdio = one(options.filter((p) => p.name?.getText() === "stdio"));
290
+ require(t.isPropertyAssignment(stdio) &&
291
+ t.isStringLiteral(stdio.initializer) &&
292
+ stdio.initializer.text === "pipe");
293
+ for (const ref of references(factoryBody, child)) {
294
+ if (ref === child.name ||
295
+ (t.isShorthandPropertyAssignment(ref.parent) &&
296
+ ref.parent.parent === returned.expression))
297
+ continue;
298
+ require(t.isPropertyAccessExpression(ref.parent) &&
299
+ ref.parent.expression === ref);
300
+ const access = ref.parent;
301
+ require([
302
+ "stdout",
303
+ "stderr",
304
+ "exitCode",
305
+ "signalCode",
306
+ "pid",
307
+ "once",
308
+ "kill",
309
+ ].includes(access.name.text));
310
+ require(!(t.isBinaryExpression(access.parent) &&
311
+ access.parent.left === access &&
312
+ access.parent.operatorToken.kind >= t.SyntaxKind.FirstAssignment &&
313
+ access.parent.operatorToken.kind <= t.SyntaxKind.LastAssignment) &&
314
+ !t.isDeleteExpression(access.parent) &&
315
+ !t.isPostfixUnaryExpression(access.parent) &&
316
+ !(t.isPrefixUnaryExpression(access.parent) &&
317
+ [t.SyntaxKind.PlusPlusToken, t.SyntaxKind.MinusMinusToken].includes(access.parent.operator)));
318
+ if (["once", "kill"].includes(access.name.text))
319
+ require(t.isCallExpression(access.parent) &&
320
+ access.parent.expression === access);
321
+ }
322
+ const disjuncts = (n) => t.isBinaryExpression(n) &&
323
+ n.operatorToken.kind === t.SyntaxKind.BarBarToken
324
+ ? [...disjuncts(n.left), ...disjuncts(n.right)]
325
+ : [n];
326
+ const checks = disjuncts(guard.expression);
327
+ require(checks.length === 3);
328
+ for (const [index, name] of ["exitCode", "signalCode"].entries()) {
329
+ const check = checks[index];
330
+ require(t.isBinaryExpression(check) &&
331
+ check.operatorToken.kind ===
332
+ t.SyntaxKind.ExclamationEqualsEqualsToken &&
333
+ check.right.kind === t.SyntaxKind.NullKeyword &&
334
+ t.isPropertyAccessExpression(check.left) &&
335
+ check.left.name.text === name &&
336
+ decl(check.left.expression) === child);
337
+ }
338
+ const expired = checks[2];
339
+ require(t.isBinaryExpression(expired) &&
340
+ expired.operatorToken.kind === t.SyntaxKind.GreaterThanToken &&
341
+ dateNow(expired.left) &&
342
+ decl(expired.right) === deadline);
343
+ require(references(factoryBody, poll).every((n) => n === poll.name ||
344
+ n === pollCall.expression ||
345
+ (t.isShorthandPropertyAssignment(n.parent) &&
346
+ n.parent.parent === returned.expression)));
347
+ for (const ref of references(callback, receiver)) {
348
+ if (ref === receiver.name)
349
+ continue;
350
+ require(t.isPropertyAccessExpression(ref.parent) &&
351
+ ref.parent.expression === ref);
352
+ const use = ref.parent;
353
+ require(t.isCallExpression(use.parent) && use.parent.expression === use);
354
+ const usedProperty = one(members.filter((p) => p.name?.getText() === use.name.text));
355
+ require(usedProperty === property ||
356
+ (t.isShorthandPropertyAssignment(usedProperty) &&
357
+ decl(usedProperty.name) === poll) ||
358
+ (t.isPropertyAssignment(usedProperty) &&
359
+ simpleArrow(usedProperty.initializer) &&
360
+ buffers.includes(decl(bodyExpression(usedProperty.initializer)))));
361
+ }
362
+ require(nodes(factoryBody, (n) => t.isIdentifier(n) && ["eval", "Function"].includes(n.text)).length === 0);
363
+ return {
364
+ model: "node-child-capture-poll-v1",
365
+ factorySource: at(factory),
366
+ predicateSource: at(read),
367
+ captures,
368
+ pattern: regex,
369
+ };
370
+ }
371
+ catch (error) {
372
+ if (error === unsupported)
373
+ return undefined;
374
+ throw error;
375
+ }
376
+ }
@@ -1 +1 @@
1
- {"schema":1,"sourceSha256":"58c5fd835878413942c6944032f3bcd0b6c76ce82fd0ae6048b0aeef4bd65612","compiledSha256":"8c2ee2f19f780465bffd589b5cff23cec80234744316a2067063318b2dc7906f"}
1
+ {"schema":1,"sourceSha256":"b9f0ed836fd8bda03ee3793896da9769e70e9a4b56f6b8716ad683c84cf77680","compiledSha256":"80ce9da5db394e7958b1e2c67dc2b12434c539c3890e32ac6fd0f40fc509df5d"}