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
|
@@ -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
|
-
//
|
|
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
|
-
|
|
185
|
-
|
|
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":"
|
|
1
|
+
{"schema":1,"sourceSha256":"b9f0ed836fd8bda03ee3793896da9769e70e9a4b56f6b8716ad683c84cf77680","compiledSha256":"80ce9da5db394e7958b1e2c67dc2b12434c539c3890e32ac6fd0f40fc509df5d"}
|