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.
@@ -14,7 +14,24 @@ export const PROTOCOL = {
14
14
  capabilities: [
15
15
  "requiresTotal-v1",
16
16
  "assertion-witness-issues-v1",
17
+ "complete-passed-test-inventory-v1",
18
+ "witnessed-callback-scope-v1",
17
19
  "assertion-hints-v1",
20
+ "awaited-observation-sources-v1",
21
+ "first-test-call-omission-v1",
22
+ "closed-count-sensitivity-v1",
23
+ "closed-payload-sensitivity-v1",
24
+ "payload-native-predicates-v1",
25
+ "first-test-direct-return-v1",
26
+ "mock-observation-projections-v1",
27
+ "assertion-comparison-relations-v2",
28
+ "process-exit-source-v1",
29
+ "process-exit-consumer-v1",
30
+ "mock-count-lifetimes-v1",
31
+ "mock-count-factories-v1",
32
+ "mock-count-rows-v1",
33
+ "mock-count-array-projections-v1",
34
+ "primitive-decision-sensitivity-v1",
18
35
  ],
19
36
  };
20
37
  type Location = {
@@ -102,6 +119,7 @@ type RecordData = {
102
119
  status?: string;
103
120
  expectedStatus?: string;
104
121
  flaky?: boolean;
122
+ provenance?: { runner?: string };
105
123
  scope?: Scope;
106
124
  runtime: Snapshot[];
107
125
  browser: Snapshot[];
@@ -216,7 +234,9 @@ function analyzeArchiveWithFrontend(
216
234
  atoms.forEach((n, i) => {
217
235
  const begin = sf.getLineAndCharacterOfPosition(n.getStart(sf)),
218
236
  end = sf.getLineAndCharacterOfPosition(n.getEnd());
219
- // The flow pass resolves the actual AST owner; this fallback is only for presentation.
237
+ // This owner also participates in source-hint selection. Named arrow
238
+ // bindings must not be mislabeled <module> merely because they are not
239
+ // function declarations.
220
240
  let parent: ts.Node | undefined = n.parent,
221
241
  owner = "<module>";
222
242
  while (parent) {
@@ -224,6 +244,15 @@ function analyzeArchiveWithFrontend(
224
244
  owner = parent.name.text;
225
245
  break;
226
246
  }
247
+ if (
248
+ (compiler.isArrowFunction(parent) ||
249
+ compiler.isFunctionExpression(parent)) &&
250
+ compiler.isVariableDeclaration(parent.parent) &&
251
+ compiler.isIdentifier(parent.parent.name)
252
+ ) {
253
+ owner = parent.parent.name.text;
254
+ break;
255
+ }
227
256
  parent = parent.parent;
228
257
  }
229
258
  sites.push({
@@ -290,8 +319,11 @@ function analyzeArchiveWithFrontend(
290
319
  for (const [i, r] of accepted.entries()) {
291
320
  const id = `A${i + 1}`;
292
321
  if (!r.testFile) {
293
- limitations.add("A passed test has no source file.");
294
- continue;
322
+ // Dropping even one passing attempt could turn its executed/asserted sites
323
+ // into apparently certain gaps. Ordinary coverage can still be queried.
324
+ throw new Error(
325
+ "A passed test has no source file; assertion analysis requires test-source provenance. Recapture with a supported runner and stack formatter.",
326
+ );
295
327
  }
296
328
  if (r.browser.length)
297
329
  limitations.add(
@@ -393,6 +425,7 @@ function analyzeArchiveWithFrontend(
393
425
  line: 0,
394
426
  ok: true,
395
427
  phaseLines,
428
+ runner: r.provenance?.runner,
396
429
  });
397
430
  attempts.push({
398
431
  id,
@@ -0,0 +1,561 @@
1
+ import type ts from "typescript";
2
+ import type { SyntaxAPI } from "./frontend.js";
3
+
4
+ /** Source applicability only. This is not a runtime observation or a witness. */
5
+ export interface AwaitedObservationSource {
6
+ model: "node-child-capture-poll-v1";
7
+ factorySource: string;
8
+ predicateSource: string;
9
+ captures: { stream: "stdout" | "stderr"; source: string }[];
10
+ pattern: string;
11
+ }
12
+
13
+ interface Context {
14
+ syntax: SyntaxAPI;
15
+ declaration(node: ts.Node): ts.Declaration | undefined;
16
+ rawDeclaration(node: ts.Node): ts.Declaration | undefined;
17
+ relativeFile(file: ts.SourceFile): string;
18
+ }
19
+
20
+ /**
21
+ * Recognize a direct awaited factory method which polls an append-only native
22
+ * child capture. Names, paths, regex and timeout values come from bindings/source.
23
+ * Unsupported control flow or aliases are not evidence of a missing assertion.
24
+ * Native APIs and absence of external monkey-patching remain model premises.
25
+ */
26
+ export function awaitedObservationSource(
27
+ context: Context,
28
+ callback: ts.Node,
29
+ call: ts.CallExpression,
30
+ ): AwaitedObservationSource | undefined {
31
+ const { syntax: t, declaration: decl, rawDeclaration: raw } = context;
32
+ function require(value: unknown): asserts value {
33
+ if (!value) throw unsupported;
34
+ }
35
+ const unsupported = Symbol("unsupported awaited observation");
36
+ const nodes = (root: ts.Node, predicate: (node: ts.Node) => boolean) => {
37
+ const found: ts.Node[] = [];
38
+ const visit = (n: ts.Node) => {
39
+ if (predicate(n)) found.push(n);
40
+ t.forEachChild(n, visit);
41
+ };
42
+ visit(root);
43
+ return found;
44
+ };
45
+ const one = <N extends ts.Node>(values: readonly N[]): N => {
46
+ require(values.length === 1);
47
+ return values[0];
48
+ };
49
+ const at = (node: ts.Node) => {
50
+ const sf = node.getSourceFile(),
51
+ p = sf.getLineAndCharacterOfPosition(node.getStart(sf));
52
+ return `${context.relativeFile(sf)}:${p.line + 1}:${p.character + 1}`;
53
+ };
54
+ const importedFrom = (node: ts.Node, name: string): string | undefined => {
55
+ const d = raw(node);
56
+ return !!d &&
57
+ t.isImportSpecifier(d) &&
58
+ !d.isTypeOnly &&
59
+ !d.parent.parent.isTypeOnly &&
60
+ (d.propertyName ?? d.name).text === name &&
61
+ t.isStringLiteral(d.parent.parent.parent.moduleSpecifier)
62
+ ? d.parent.parent.parent.moduleSpecifier.text
63
+ : undefined;
64
+ };
65
+ const constant = (node: ts.Node): node is ts.VariableDeclaration =>
66
+ t.isVariableDeclaration(node) &&
67
+ t.isIdentifier(node.name) &&
68
+ t.isVariableDeclarationList(node.parent) &&
69
+ !!(node.parent.flags & t.NodeFlags.Const) &&
70
+ node.parent.declarations.length === 1;
71
+ const simpleArrow = (node: ts.Node): node is ts.ArrowFunction =>
72
+ t.isArrowFunction(node) &&
73
+ node.parameters.length === 0 &&
74
+ !node.modifiers?.length;
75
+ const bodyExpression = (node: ts.ArrowFunction): ts.Expression => {
76
+ if (!t.isBlock(node.body)) return node.body;
77
+ require(node.body.statements.length === 1);
78
+ const returned = node.body.statements[0];
79
+ require(t.isReturnStatement(returned) && returned.expression);
80
+ return returned.expression;
81
+ };
82
+ const positiveNumber = (node: ts.Node) =>
83
+ t.isNumericLiteral(node) &&
84
+ Number.isFinite(Number(node.text)) &&
85
+ Number(node.text) > 0;
86
+ const global = (node: ts.Node, name: string) => {
87
+ const d = decl(node);
88
+ return (
89
+ t.isIdentifier(node) &&
90
+ node.text === name &&
91
+ (!d || d.getSourceFile().isDeclarationFile)
92
+ );
93
+ };
94
+ const dateNow = (node: ts.Node) =>
95
+ t.isCallExpression(node) &&
96
+ !node.questionDotToken &&
97
+ node.arguments.length === 0 &&
98
+ t.isPropertyAccessExpression(node.expression) &&
99
+ node.expression.name.text === "now" &&
100
+ global(node.expression.expression, "Date");
101
+ const references = (root: ts.Node, binding: ts.Declaration) =>
102
+ nodes(root, (n) => t.isIdentifier(n) && decl(n) === binding);
103
+ try {
104
+ require(
105
+ t.isArrowFunction(callback) &&
106
+ t.isBlock(callback.body) &&
107
+ callback.modifiers?.some((m) => m.kind === t.SyntaxKind.AsyncKeyword),
108
+ );
109
+ const registration = callback.parent;
110
+ require(
111
+ t.isCallExpression(registration) &&
112
+ registration.arguments.at(-1) === callback &&
113
+ importedFrom(registration.expression, "test") === "node:test" &&
114
+ t.isExpressionStatement(registration.parent) &&
115
+ t.isSourceFile(registration.parent.parent),
116
+ );
117
+ require(
118
+ callback.body.statements.every(
119
+ (s) => t.isVariableStatement(s) || t.isExpressionStatement(s),
120
+ ),
121
+ );
122
+ require(
123
+ t.isAwaitExpression(call.parent) &&
124
+ t.isExpressionStatement(call.parent.parent) &&
125
+ call.parent.parent.parent === callback.body &&
126
+ call.arguments.length === 0 &&
127
+ !call.questionDotToken &&
128
+ t.isPropertyAccessExpression(call.expression) &&
129
+ !call.expression.questionDotToken,
130
+ );
131
+ const member = call.expression.name.text,
132
+ receiver = decl(call.expression.expression);
133
+ require(
134
+ receiver &&
135
+ constant(receiver) &&
136
+ receiver.initializer &&
137
+ t.isCallExpression(receiver.initializer),
138
+ );
139
+ const launch = receiver.initializer,
140
+ factory = decl(launch.expression);
141
+ require(
142
+ factory &&
143
+ t.isFunctionDeclaration(factory) &&
144
+ factory.body &&
145
+ !factory.asteriskToken &&
146
+ !factory.modifiers?.some((m) => m.kind === t.SyntaxKind.AsyncKeyword) &&
147
+ factory.parameters.every(
148
+ (p) => t.isIdentifier(p.name) && !p.initializer && !p.dotDotDotToken,
149
+ ),
150
+ );
151
+ require(receiver.parent.parent.parent === callback.body);
152
+ const statements: readonly ts.Statement[] = callback.body.statements;
153
+ require(
154
+ statements.indexOf(call.parent.parent) ===
155
+ statements.indexOf(receiver.parent.parent as ts.Statement) + 1,
156
+ );
157
+ require(
158
+ nodes(
159
+ callback,
160
+ (n) => t.isCallExpression(n) && decl(n.expression) === factory,
161
+ ).length === 1,
162
+ );
163
+ require(
164
+ nodes(
165
+ callback,
166
+ (n) =>
167
+ t.isCallExpression(n) &&
168
+ t.isPropertyAccessExpression(n.expression) &&
169
+ decl(n.expression.expression) === receiver &&
170
+ n.expression.name.text === member,
171
+ ).length === 1,
172
+ );
173
+ require(
174
+ nodes(
175
+ callback,
176
+ (n) => t.isIdentifier(n) && ["eval", "Function"].includes(n.text),
177
+ ).length === 0,
178
+ );
179
+
180
+ const factoryBody = factory.body;
181
+ require(
182
+ factoryBody.statements.every(
183
+ (s) =>
184
+ t.isVariableStatement(s) ||
185
+ t.isExpressionStatement(s) ||
186
+ t.isReturnStatement(s),
187
+ ),
188
+ );
189
+ const returned = one(factoryBody.statements.filter(t.isReturnStatement));
190
+ require(
191
+ returned === factoryBody.statements.at(-1) &&
192
+ returned.expression &&
193
+ t.isObjectLiteralExpression(returned.expression),
194
+ );
195
+ const members = returned.expression.properties;
196
+ require(
197
+ members.every(
198
+ (p) =>
199
+ (t.isPropertyAssignment(p) || t.isShorthandPropertyAssignment(p)) &&
200
+ t.isIdentifier(p.name),
201
+ ),
202
+ );
203
+ require(
204
+ new Set(members.map((p) => p.name!.getText())).size === members.length,
205
+ );
206
+ const property = one(members.filter((p) => p.name?.getText() === member));
207
+ require(
208
+ t.isPropertyAssignment(property) && simpleArrow(property.initializer),
209
+ );
210
+ const pollCall = bodyExpression(property.initializer);
211
+ require(
212
+ t.isCallExpression(pollCall) &&
213
+ !pollCall.questionDotToken &&
214
+ pollCall.arguments.length >= 1,
215
+ );
216
+ const poll = decl(pollCall.expression);
217
+ require(
218
+ poll &&
219
+ constant(poll) &&
220
+ poll.parent.parent.parent === factoryBody &&
221
+ poll.initializer &&
222
+ t.isArrowFunction(poll.initializer) &&
223
+ t.isBlock(poll.initializer.body) &&
224
+ poll.initializer.modifiers?.some(
225
+ (m) => m.kind === t.SyntaxKind.AsyncKeyword,
226
+ ),
227
+ );
228
+ const pollFn = poll.initializer,
229
+ pollBody = pollFn.body as ts.Block;
230
+ require(
231
+ pollFn.parameters.length === pollCall.arguments.length &&
232
+ pollFn.parameters.every(
233
+ (p) => t.isIdentifier(p.name) && !p.initializer && !p.dotDotDotToken,
234
+ ),
235
+ );
236
+ require(
237
+ pollBody.statements.length === 2 &&
238
+ t.isVariableStatement(pollBody.statements[0]),
239
+ );
240
+ const deadline = one(pollBody.statements[0].declarationList.declarations);
241
+ require(
242
+ constant(deadline) &&
243
+ deadline.initializer &&
244
+ t.isBinaryExpression(deadline.initializer) &&
245
+ deadline.initializer.operatorToken.kind === t.SyntaxKind.PlusToken &&
246
+ dateNow(deadline.initializer.left) &&
247
+ positiveNumber(deadline.initializer.right),
248
+ );
249
+ const loop = pollBody.statements[1];
250
+ require(
251
+ t.isWhileStatement(loop) &&
252
+ t.isPrefixUnaryExpression(loop.expression) &&
253
+ loop.expression.operator === t.SyntaxKind.ExclamationToken &&
254
+ t.isCallExpression(loop.expression.operand),
255
+ );
256
+ const predicateCall = loop.expression.operand;
257
+ require(
258
+ predicateCall.arguments.length === 0 &&
259
+ decl(predicateCall.expression) === pollFn.parameters[0] &&
260
+ !predicateCall.questionDotToken &&
261
+ t.isBlock(loop.statement) &&
262
+ loop.statement.statements.length === 2,
263
+ );
264
+ require(
265
+ references(pollFn, pollFn.parameters[0]).every(
266
+ (n) =>
267
+ n === pollFn.parameters[0].name || n === predicateCall.expression,
268
+ ),
269
+ );
270
+ const [guard, pause] = loop.statement.statements;
271
+ require(
272
+ t.isIfStatement(guard) &&
273
+ !guard.elseStatement &&
274
+ t.isBlock(guard.thenStatement) &&
275
+ guard.thenStatement.statements.length === 1 &&
276
+ t.isThrowStatement(guard.thenStatement.statements[0]),
277
+ );
278
+ require(
279
+ t.isExpressionStatement(pause) &&
280
+ t.isAwaitExpression(pause.expression) &&
281
+ t.isCallExpression(pause.expression.expression),
282
+ );
283
+ const delay = pause.expression.expression;
284
+ require(
285
+ importedFrom(delay.expression, "setTimeout") === "node:timers/promises" &&
286
+ !delay.questionDotToken &&
287
+ delay.arguments.length === 1 &&
288
+ positiveNumber(delay.arguments[0]),
289
+ );
290
+ const predicate = pollCall.arguments[0];
291
+ require(simpleArrow(predicate));
292
+ const read = bodyExpression(predicate);
293
+ require(
294
+ t.isCallExpression(read) &&
295
+ !read.questionDotToken &&
296
+ read.arguments.length === 1 &&
297
+ t.isPropertyAccessExpression(read.expression) &&
298
+ read.expression.name.text === "test" &&
299
+ t.isRegularExpressionLiteral(read.expression.expression),
300
+ );
301
+ const regex = read.expression.expression.text;
302
+ require(!/[gy]/.test(regex.slice(regex.lastIndexOf("/") + 1)));
303
+ const input = read.arguments[0];
304
+ const operands =
305
+ t.isBinaryExpression(input) &&
306
+ input.operatorToken.kind === t.SyntaxKind.PlusToken
307
+ ? [input.left, input.right]
308
+ : [input];
309
+ const buffers = operands.map((o) => decl(o));
310
+ require(
311
+ buffers.every((b) => b && t.isVariableDeclaration(b)) &&
312
+ new Set(buffers).size === buffers.length,
313
+ );
314
+ let child: ts.VariableDeclaration | undefined;
315
+ const captures: AwaitedObservationSource["captures"] = [];
316
+ for (const [index, maybeBuffer] of buffers.entries()) {
317
+ const buffer = maybeBuffer as ts.VariableDeclaration;
318
+ require(
319
+ buffer.parent.parent.parent === factoryBody &&
320
+ t.isVariableDeclarationList(buffer.parent) &&
321
+ !!(buffer.parent.flags & t.NodeFlags.Let) &&
322
+ t.isIdentifier(buffer.name) &&
323
+ buffer.initializer &&
324
+ t.isStringLiteral(buffer.initializer) &&
325
+ buffer.initializer.text === "",
326
+ );
327
+ const append = one(
328
+ nodes(
329
+ factoryBody,
330
+ (n) =>
331
+ t.isBinaryExpression(n) &&
332
+ decl(n.left) === buffer &&
333
+ n.operatorToken.kind >= t.SyntaxKind.FirstAssignment &&
334
+ n.operatorToken.kind <= t.SyntaxKind.LastAssignment,
335
+ ),
336
+ );
337
+ require(
338
+ t.isBinaryExpression(append) &&
339
+ append.operatorToken.kind === t.SyntaxKind.PlusEqualsToken &&
340
+ t.isExpressionStatement(append.parent) &&
341
+ t.isBlock(append.parent.parent),
342
+ );
343
+ const capture = append.parent.parent.parent;
344
+ require(
345
+ t.isArrowFunction(capture) &&
346
+ !capture.modifiers?.length &&
347
+ capture.parameters.length === 1 &&
348
+ !capture.parameters[0].initializer &&
349
+ !capture.parameters[0].dotDotDotToken &&
350
+ decl(append.right) === capture.parameters[0] &&
351
+ t.isBlock(capture.body) &&
352
+ capture.body.statements.length === 1,
353
+ );
354
+ const on = capture.parent;
355
+ require(
356
+ t.isCallExpression(on) &&
357
+ on.arguments.length === 2 &&
358
+ on.arguments[1] === capture &&
359
+ !on.questionDotToken &&
360
+ t.isStringLiteral(on.arguments[0]) &&
361
+ on.arguments[0].text === "data" &&
362
+ t.isPropertyAccessExpression(on.expression) &&
363
+ !on.expression.questionDotToken &&
364
+ on.expression.name.text === "on" &&
365
+ t.isCallExpression(on.expression.expression),
366
+ );
367
+ const encoding = on.expression.expression;
368
+ require(
369
+ !encoding.questionDotToken &&
370
+ encoding.arguments.length === 1 &&
371
+ t.isStringLiteral(encoding.arguments[0]) &&
372
+ encoding.arguments[0].text === "utf8" &&
373
+ t.isPropertyAccessExpression(encoding.expression) &&
374
+ encoding.expression.name.text === "setEncoding" &&
375
+ !encoding.expression.questionDotToken &&
376
+ t.isPropertyAccessExpression(encoding.expression.expression),
377
+ );
378
+ const stream = encoding.expression.expression;
379
+ require(
380
+ !stream.questionDotToken &&
381
+ (stream.name.text === "stdout" || stream.name.text === "stderr"),
382
+ );
383
+ const streamChild = decl(stream.expression);
384
+ require(
385
+ streamChild &&
386
+ constant(streamChild) &&
387
+ (!child || child === streamChild),
388
+ );
389
+ child = streamChild;
390
+ require(
391
+ t.isExpressionStatement(on.parent) && on.parent.parent === factoryBody,
392
+ );
393
+ // Only initialization, append, the predicate, trivial accessors and the
394
+ // throw diagnostic can read this binding. No transformed/escaped capture.
395
+ const allowed = new Set<ts.Node>([
396
+ buffer.name,
397
+ append.left,
398
+ operands[index],
399
+ ...references(guard.thenStatement, buffer),
400
+ ]);
401
+ for (const p of members)
402
+ if (t.isPropertyAssignment(p) && simpleArrow(p.initializer)) {
403
+ const value = bodyExpression(p.initializer);
404
+ if (decl(value) === buffer) allowed.add(value);
405
+ }
406
+ require(references(factoryBody, buffer).every((n) => allowed.has(n)));
407
+ const streamRefs = nodes(
408
+ factoryBody,
409
+ (n) =>
410
+ t.isPropertyAccessExpression(n) &&
411
+ decl(n.expression) === child &&
412
+ n.name.text === stream.name.text,
413
+ );
414
+ require(streamRefs.length === 1);
415
+ captures.push({ stream: stream.name.text, source: at(append) });
416
+ }
417
+ require(
418
+ child &&
419
+ child.initializer &&
420
+ t.isCallExpression(child.initializer) &&
421
+ child.parent.parent.parent === factoryBody &&
422
+ importedFrom(child.initializer.expression, "spawn") ===
423
+ "node:child_process",
424
+ );
425
+ const spawn = child.initializer;
426
+ require(
427
+ !spawn.questionDotToken &&
428
+ spawn.arguments.length === 3 &&
429
+ t.isObjectLiteralExpression(spawn.arguments[2]),
430
+ );
431
+ const options = spawn.arguments[2].properties;
432
+ require(
433
+ options.every(
434
+ (p) => t.isPropertyAssignment(p) && t.isIdentifier(p.name),
435
+ ) &&
436
+ new Set(options.map((p) => p.name!.getText())).size === options.length,
437
+ );
438
+ const stdio = one(options.filter((p) => p.name?.getText() === "stdio"));
439
+ require(
440
+ t.isPropertyAssignment(stdio) &&
441
+ t.isStringLiteral(stdio.initializer) &&
442
+ stdio.initializer.text === "pipe",
443
+ );
444
+ for (const ref of references(factoryBody, child)) {
445
+ if (
446
+ ref === child.name ||
447
+ (t.isShorthandPropertyAssignment(ref.parent) &&
448
+ ref.parent.parent === returned.expression)
449
+ )
450
+ continue;
451
+ require(
452
+ t.isPropertyAccessExpression(ref.parent) &&
453
+ ref.parent.expression === ref,
454
+ );
455
+ const access = ref.parent;
456
+ require(
457
+ [
458
+ "stdout",
459
+ "stderr",
460
+ "exitCode",
461
+ "signalCode",
462
+ "pid",
463
+ "once",
464
+ "kill",
465
+ ].includes(access.name.text),
466
+ );
467
+ require(
468
+ !(
469
+ t.isBinaryExpression(access.parent) &&
470
+ access.parent.left === access &&
471
+ access.parent.operatorToken.kind >= t.SyntaxKind.FirstAssignment &&
472
+ access.parent.operatorToken.kind <= t.SyntaxKind.LastAssignment
473
+ ) &&
474
+ !t.isDeleteExpression(access.parent) &&
475
+ !t.isPostfixUnaryExpression(access.parent) &&
476
+ !(
477
+ t.isPrefixUnaryExpression(access.parent) &&
478
+ [t.SyntaxKind.PlusPlusToken, t.SyntaxKind.MinusMinusToken].includes(
479
+ access.parent.operator,
480
+ )
481
+ ),
482
+ );
483
+ if (["once", "kill"].includes(access.name.text))
484
+ require(
485
+ t.isCallExpression(access.parent) &&
486
+ access.parent.expression === access,
487
+ );
488
+ }
489
+ const disjuncts = (n: ts.Expression): ts.Expression[] =>
490
+ t.isBinaryExpression(n) &&
491
+ n.operatorToken.kind === t.SyntaxKind.BarBarToken
492
+ ? [...disjuncts(n.left), ...disjuncts(n.right)]
493
+ : [n];
494
+ const checks = disjuncts(guard.expression);
495
+ require(checks.length === 3);
496
+ for (const [index, name] of ["exitCode", "signalCode"].entries()) {
497
+ const check = checks[index];
498
+ require(
499
+ t.isBinaryExpression(check) &&
500
+ check.operatorToken.kind ===
501
+ t.SyntaxKind.ExclamationEqualsEqualsToken &&
502
+ check.right.kind === t.SyntaxKind.NullKeyword &&
503
+ t.isPropertyAccessExpression(check.left) &&
504
+ check.left.name.text === name &&
505
+ decl(check.left.expression) === child,
506
+ );
507
+ }
508
+ const expired = checks[2];
509
+ require(
510
+ t.isBinaryExpression(expired) &&
511
+ expired.operatorToken.kind === t.SyntaxKind.GreaterThanToken &&
512
+ dateNow(expired.left) &&
513
+ decl(expired.right) === deadline,
514
+ );
515
+ require(
516
+ references(factoryBody, poll).every(
517
+ (n) =>
518
+ n === poll.name ||
519
+ n === pollCall.expression ||
520
+ (t.isShorthandPropertyAssignment(n.parent) &&
521
+ n.parent.parent === returned.expression),
522
+ ),
523
+ );
524
+ for (const ref of references(callback, receiver)) {
525
+ if (ref === receiver.name) continue;
526
+ require(
527
+ t.isPropertyAccessExpression(ref.parent) &&
528
+ ref.parent.expression === ref,
529
+ );
530
+ const use = ref.parent;
531
+ require(t.isCallExpression(use.parent) && use.parent.expression === use);
532
+ const usedProperty = one(
533
+ members.filter((p) => p.name?.getText() === use.name.text),
534
+ );
535
+ require(
536
+ usedProperty === property ||
537
+ (t.isShorthandPropertyAssignment(usedProperty) &&
538
+ decl(usedProperty.name) === poll) ||
539
+ (t.isPropertyAssignment(usedProperty) &&
540
+ simpleArrow(usedProperty.initializer) &&
541
+ buffers.includes(decl(bodyExpression(usedProperty.initializer)))),
542
+ );
543
+ }
544
+ require(
545
+ nodes(
546
+ factoryBody,
547
+ (n) => t.isIdentifier(n) && ["eval", "Function"].includes(n.text),
548
+ ).length === 0,
549
+ );
550
+ return {
551
+ model: "node-child-capture-poll-v1",
552
+ factorySource: at(factory),
553
+ predicateSource: at(read),
554
+ captures,
555
+ pattern: regex,
556
+ };
557
+ } catch (error) {
558
+ if (error === unsupported) return undefined;
559
+ throw error;
560
+ }
561
+ }