supercov 0.0.2 → 0.0.4
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/LICENSE +21 -0
- package/README.md +159 -30
- package/bin/supercov.js +10 -0
- package/dist/atomic.d.ts +6 -0
- package/dist/atomic.d.ts.map +1 -0
- package/dist/atomic.js +47 -0
- package/dist/atomic.js.map +1 -0
- package/dist/buildCache.d.ts +14 -0
- package/dist/buildCache.d.ts.map +1 -0
- package/dist/buildCache.js +84 -0
- package/dist/buildCache.js.map +1 -0
- package/dist/cli.js +536 -215
- package/dist/cli.js.map +1 -1
- package/dist/directInstrumenter.d.ts +8 -0
- package/dist/directInstrumenter.d.ts.map +1 -0
- package/dist/directInstrumenter.js +70 -0
- package/dist/directInstrumenter.js.map +1 -0
- package/dist/evidenceArchive.d.ts +35 -0
- package/dist/evidenceArchive.d.ts.map +1 -0
- package/dist/evidenceArchive.js +101 -0
- package/dist/evidenceArchive.js.map +1 -0
- package/dist/instrumenter.d.ts.map +1 -1
- package/dist/instrumenter.js +251 -41
- package/dist/instrumenter.js.map +1 -1
- package/dist/integrity.d.ts.map +1 -1
- package/dist/integrity.js +27 -0
- package/dist/integrity.js.map +1 -1
- package/dist/launchSupervisor.d.ts +24 -0
- package/dist/launchSupervisor.d.ts.map +1 -0
- package/dist/launchSupervisor.js +397 -0
- package/dist/launchSupervisor.js.map +1 -0
- package/dist/playwright.d.ts +0 -4
- package/dist/playwright.d.ts.map +1 -1
- package/dist/playwright.js +33 -17
- package/dist/playwright.js.map +1 -1
- package/dist/playwrightReporter.d.ts.map +1 -1
- package/dist/playwrightReporter.js +3 -2
- package/dist/playwrightReporter.js.map +1 -1
- package/dist/project.d.ts +5 -2
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +192 -27
- package/dist/project.js.map +1 -1
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +29 -21
- package/dist/query.js.map +1 -1
- package/dist/register.mjs +18 -13
- package/dist/register.mjs.map +1 -1
- package/dist/reporter.d.ts +6 -10
- package/dist/reporter.d.ts.map +1 -1
- package/dist/reporter.js +23 -82
- package/dist/reporter.js.map +1 -1
- package/dist/resolve-loader.d.mts.map +1 -1
- package/dist/resolve-loader.mjs +11 -3
- package/dist/resolve-loader.mjs.map +1 -1
- package/dist/runAnalysis.d.ts +19 -0
- package/dist/runAnalysis.d.ts.map +1 -0
- package/dist/runAnalysis.js +112 -0
- package/dist/runAnalysis.js.map +1 -0
- package/dist/runtime.d.ts +2 -2
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +35 -7
- package/dist/runtime.js.map +1 -1
- package/dist/transport.d.ts +2 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +13 -5
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/vitePlugin.d.ts +1 -0
- package/dist/vitePlugin.d.ts.map +1 -1
- package/dist/vitePlugin.js +35 -3
- package/dist/vitePlugin.js.map +1 -1
- package/dist/vitest.js +3 -2
- package/dist/vitest.js.map +1 -1
- package/dist/vitestReporter.d.ts +17 -2
- package/dist/vitestReporter.d.ts.map +1 -1
- package/dist/vitestReporter.js +51 -2
- package/dist/vitestReporter.js.map +1 -1
- package/dist/workspace.d.ts +81 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +501 -0
- package/dist/workspace.js.map +1 -0
- package/docs/performance.md +107 -0
- package/docs/workspace-isolation.md +118 -0
- package/package.json +21 -5
package/dist/instrumenter.js
CHANGED
|
@@ -61,27 +61,58 @@ function collectConditions(node, conditions) {
|
|
|
61
61
|
}
|
|
62
62
|
conditions.push(node);
|
|
63
63
|
}
|
|
64
|
-
function instrumentConditions(node, frameId, nextIndex, decisionLogicalNodes) {
|
|
64
|
+
function instrumentConditions(node, frameId, nextIndex, decisionLogicalNodes, conditionRuntimeName) {
|
|
65
65
|
if (t.isLogicalExpression(node) &&
|
|
66
66
|
(node.operator === "&&" || node.operator === "||")) {
|
|
67
|
-
const logical = t.logicalExpression(node.operator, instrumentConditions(node.left, frameId, nextIndex, decisionLogicalNodes), instrumentConditions(node.right, frameId, nextIndex, decisionLogicalNodes));
|
|
67
|
+
const logical = t.logicalExpression(node.operator, instrumentConditions(node.left, frameId, nextIndex, decisionLogicalNodes, conditionRuntimeName), instrumentConditions(node.right, frameId, nextIndex, decisionLogicalNodes, conditionRuntimeName));
|
|
68
68
|
decisionLogicalNodes.add(logical);
|
|
69
69
|
return logical;
|
|
70
70
|
}
|
|
71
71
|
if (t.isUnaryExpression(node, { operator: "!" }) &&
|
|
72
72
|
hasCompoundBooleanDecision(node.argument)) {
|
|
73
|
-
return t.unaryExpression("!", instrumentConditions(node.argument, frameId, nextIndex, decisionLogicalNodes), true);
|
|
73
|
+
return t.unaryExpression("!", instrumentConditions(node.argument, frameId, nextIndex, decisionLogicalNodes, conditionRuntimeName), true);
|
|
74
74
|
}
|
|
75
75
|
const index = nextIndex.value;
|
|
76
76
|
nextIndex.value += 1;
|
|
77
|
-
return t.callExpression(t.identifier(
|
|
77
|
+
return t.callExpression(t.identifier(conditionRuntimeName), [
|
|
78
78
|
t.cloneNode(frameId),
|
|
79
79
|
t.numericLiteral(index),
|
|
80
|
-
|
|
80
|
+
node,
|
|
81
81
|
]);
|
|
82
82
|
}
|
|
83
|
-
function hitStatement(id) {
|
|
84
|
-
return t.expressionStatement(t.callExpression(t.identifier(
|
|
83
|
+
function hitStatement(id, hitRuntimeName) {
|
|
84
|
+
return t.expressionStatement(t.callExpression(t.identifier(hitRuntimeName), [t.stringLiteral(id)]));
|
|
85
|
+
}
|
|
86
|
+
function allocatedRuntimeNames(code) {
|
|
87
|
+
const preferred = [
|
|
88
|
+
BEGIN,
|
|
89
|
+
CONDITION,
|
|
90
|
+
END,
|
|
91
|
+
HIT,
|
|
92
|
+
SELECTION_BEGIN,
|
|
93
|
+
SELECTION_RIGHT,
|
|
94
|
+
SELECTION_END,
|
|
95
|
+
WITH_REQUEST_PHASE,
|
|
96
|
+
OPTIONAL_SELECT,
|
|
97
|
+
DEFAULT_SELECTED,
|
|
98
|
+
DEFAULT_ENTERED,
|
|
99
|
+
TRY_BEGIN,
|
|
100
|
+
TRY_CATCH,
|
|
101
|
+
TRY_END,
|
|
102
|
+
LOOP_BEGIN,
|
|
103
|
+
LOOP_ENTERED,
|
|
104
|
+
LOOP_END,
|
|
105
|
+
];
|
|
106
|
+
const names = {};
|
|
107
|
+
const used = new Set();
|
|
108
|
+
for (const base of preferred) {
|
|
109
|
+
let candidate = base;
|
|
110
|
+
while (used.has(candidate) || code.includes(candidate))
|
|
111
|
+
candidate = `_${candidate}`;
|
|
112
|
+
names[base] = candidate;
|
|
113
|
+
used.add(candidate);
|
|
114
|
+
}
|
|
115
|
+
return names;
|
|
85
116
|
}
|
|
86
117
|
function functionLabel(path) {
|
|
87
118
|
if ("id" in path.node && t.isIdentifier(path.node.id))
|
|
@@ -109,10 +140,91 @@ function isExecutableStatement(node) {
|
|
|
109
140
|
}
|
|
110
141
|
return !("declare" in node && node.declare === true);
|
|
111
142
|
}
|
|
143
|
+
function isAnonymousFunctionDefinition(node) {
|
|
144
|
+
return (t.isArrowFunctionExpression(node) ||
|
|
145
|
+
(t.isFunctionExpression(node) && node.id === null) ||
|
|
146
|
+
(t.isClassExpression(node) && node.id === null));
|
|
147
|
+
}
|
|
148
|
+
function isSourceSensitiveFunction(path) {
|
|
149
|
+
let child = path;
|
|
150
|
+
for (let parent = path.parentPath; parent; parent = parent.parentPath) {
|
|
151
|
+
const candidate = parent.node;
|
|
152
|
+
const value = child.node;
|
|
153
|
+
if ((t.isParenthesizedExpression(candidate) ||
|
|
154
|
+
t.isTSAsExpression(candidate) ||
|
|
155
|
+
t.isTSTypeAssertion(candidate) ||
|
|
156
|
+
t.isTSNonNullExpression(candidate)) &&
|
|
157
|
+
candidate.expression === value) {
|
|
158
|
+
child = parent;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if ((t.isConditionalExpression(candidate) &&
|
|
162
|
+
(candidate.consequent === value || candidate.alternate === value)) ||
|
|
163
|
+
(t.isLogicalExpression(candidate) &&
|
|
164
|
+
(candidate.left === value || candidate.right === value)) ||
|
|
165
|
+
(t.isSequenceExpression(candidate) &&
|
|
166
|
+
candidate.expressions.at(-1) === value) ||
|
|
167
|
+
(t.isAssignmentExpression(candidate) && candidate.right === value)) {
|
|
168
|
+
child = parent;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (((t.isClassMethod(candidate) ||
|
|
172
|
+
t.isClassProperty(candidate) ||
|
|
173
|
+
t.isObjectMethod(candidate) ||
|
|
174
|
+
t.isObjectProperty(candidate)) &&
|
|
175
|
+
candidate.computed &&
|
|
176
|
+
candidate.key === value) ||
|
|
177
|
+
((t.isMemberExpression(candidate) ||
|
|
178
|
+
t.isOptionalMemberExpression(candidate)) &&
|
|
179
|
+
candidate.computed &&
|
|
180
|
+
candidate.property === value))
|
|
181
|
+
return true;
|
|
182
|
+
if (t.isCallExpression(candidate) &&
|
|
183
|
+
t.isIdentifier(candidate.callee, { name: "String" }) &&
|
|
184
|
+
candidate.arguments.includes(value))
|
|
185
|
+
return true;
|
|
186
|
+
if (t.isBinaryExpression(candidate) &&
|
|
187
|
+
["+", "<", "<=", ">", ">="].includes(candidate.operator) &&
|
|
188
|
+
(candidate.left === value || candidate.right === value))
|
|
189
|
+
return true;
|
|
190
|
+
if ((t.isMemberExpression(candidate) ||
|
|
191
|
+
t.isOptionalMemberExpression(candidate)) &&
|
|
192
|
+
!candidate.computed &&
|
|
193
|
+
t.isIdentifier(candidate.property, { name: "toString" }) &&
|
|
194
|
+
candidate.object === value)
|
|
195
|
+
return true;
|
|
196
|
+
// Any other operation consumes or stores the function value. Static
|
|
197
|
+
// ancestry can no longer prove that this exact function is coerced.
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
function isInsideWithStatement(path) {
|
|
203
|
+
return Boolean(path.findParent((parent) => parent.isWithStatement()));
|
|
204
|
+
}
|
|
112
205
|
export function instrumentMcdc(code, file) {
|
|
206
|
+
const names = allocatedRuntimeNames(code);
|
|
207
|
+
const BEGIN = names["__supercovMcdcBegin"];
|
|
208
|
+
const CONDITION = names["__supercovMcdcCondition"];
|
|
209
|
+
const END = names["__supercovMcdcEnd"];
|
|
210
|
+
const HIT = names["__supercovCoverageHit"];
|
|
211
|
+
const SELECTION_BEGIN = names["__supercovSelectionBegin"];
|
|
212
|
+
const SELECTION_RIGHT = names["__supercovSelectionRight"];
|
|
213
|
+
const SELECTION_END = names["__supercovSelectionEnd"];
|
|
214
|
+
const WITH_REQUEST_PHASE = names["__supercovWithRequestPhase"];
|
|
215
|
+
const OPTIONAL_SELECT = names["__supercovOptionalSelect"];
|
|
216
|
+
const DEFAULT_SELECTED = names["__supercovDefaultSelected"];
|
|
217
|
+
const DEFAULT_ENTERED = names["__supercovDefaultEntered"];
|
|
218
|
+
const TRY_BEGIN = names["__supercovTryBegin"];
|
|
219
|
+
const TRY_CATCH = names["__supercovTryCatch"];
|
|
220
|
+
const TRY_END = names["__supercovTryEnd"];
|
|
221
|
+
const LOOP_BEGIN = names["__supercovLoopBegin"];
|
|
222
|
+
const LOOP_ENTERED = names["__supercovLoopEntered"];
|
|
223
|
+
const LOOP_END = names["__supercovLoopEnd"];
|
|
113
224
|
const parserPlugins = [
|
|
114
225
|
"typescript",
|
|
115
226
|
"decorators-legacy",
|
|
227
|
+
"sourcePhaseImports",
|
|
116
228
|
...(file.endsWith("x") ? ["jsx"] : []),
|
|
117
229
|
];
|
|
118
230
|
const ast = parse(code, {
|
|
@@ -128,11 +240,52 @@ export function instrumentMcdc(code, file) {
|
|
|
128
240
|
const generatedStatements = new WeakSet();
|
|
129
241
|
const decisionLogicalNodes = new WeakSet();
|
|
130
242
|
let usesRequestPhaseHandler = false;
|
|
243
|
+
// Instrumentation necessarily changes a function's source text. When code
|
|
244
|
+
// directly observes or coerces that source, keep the entire function body
|
|
245
|
+
// untouched and report the resulting coverage boundary explicitly.
|
|
246
|
+
const sourceSensitiveFunctions = new WeakSet();
|
|
247
|
+
traverse(ast, {
|
|
248
|
+
Function(path) {
|
|
249
|
+
if (!isSourceSensitiveFunction(path))
|
|
250
|
+
return;
|
|
251
|
+
sourceSensitiveFunctions.add(path.node);
|
|
252
|
+
if (!path.node.loc)
|
|
253
|
+
return;
|
|
254
|
+
limitations.push({
|
|
255
|
+
id: stableId(file, "semantic-safety", path.node, "function-source"),
|
|
256
|
+
kind: "semantic-safety",
|
|
257
|
+
file,
|
|
258
|
+
line: path.node.loc.start.line,
|
|
259
|
+
column: path.node.loc.start.column + 1,
|
|
260
|
+
source: sourceFor(code, path.node),
|
|
261
|
+
reason: "function body is left uninstrumented because this expression observes or coerces Function source text",
|
|
262
|
+
});
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
const isWithinSourceSensitiveFunction = (path) => (path.isFunction() && sourceSensitiveFunctions.has(path.node)) ||
|
|
266
|
+
Boolean(path.findParent((parent) => parent.isFunction() && sourceSensitiveFunctions.has(parent.node)));
|
|
267
|
+
traverse(ast, {
|
|
268
|
+
WithStatement(path) {
|
|
269
|
+
if (!path.node.loc)
|
|
270
|
+
return;
|
|
271
|
+
limitations.push({
|
|
272
|
+
id: stableId(file, "semantic-safety", path.node, "with-environment"),
|
|
273
|
+
kind: "semantic-safety",
|
|
274
|
+
file,
|
|
275
|
+
line: path.node.loc.start.line,
|
|
276
|
+
column: path.node.loc.start.column + 1,
|
|
277
|
+
source: sourceFor(code, path.node),
|
|
278
|
+
reason: "with-statement body is left uninstrumented because its object environment can intercept probe identifiers",
|
|
279
|
+
});
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
const isUnsafeInstrumentationContext = (path) => isWithinSourceSensitiveFunction(path) || isInsideWithStatement(path);
|
|
131
283
|
// Record executable statements before adding any instrumentation statements.
|
|
132
284
|
traverse(ast, {
|
|
133
285
|
Statement(path) {
|
|
134
286
|
const node = path.node;
|
|
135
287
|
if (!node.loc ||
|
|
288
|
+
isUnsafeInstrumentationContext(path) ||
|
|
136
289
|
generatedStatements.has(node) ||
|
|
137
290
|
!isExecutableStatement(node))
|
|
138
291
|
return;
|
|
@@ -147,7 +300,7 @@ export function instrumentMcdc(code, file) {
|
|
|
147
300
|
column: node.loc.start.column + 1,
|
|
148
301
|
source: sourceFor(code, node),
|
|
149
302
|
});
|
|
150
|
-
const probe = hitStatement(id);
|
|
303
|
+
const probe = hitStatement(id, HIT);
|
|
151
304
|
generatedStatements.add(probe);
|
|
152
305
|
if (path.parentPath.isProgram() ||
|
|
153
306
|
path.parentPath.isBlockStatement() ||
|
|
@@ -196,43 +349,48 @@ export function instrumentMcdc(code, file) {
|
|
|
196
349
|
};
|
|
197
350
|
traverse(ast, {
|
|
198
351
|
OptionalMemberExpression(path) {
|
|
352
|
+
if (isUnsafeInstrumentationContext(path))
|
|
353
|
+
return;
|
|
199
354
|
if (!path.node.optional || !t.isExpression(path.node.object))
|
|
200
355
|
return;
|
|
201
356
|
path.node.object = instrumentOptionalOperand(path, path.node.object);
|
|
202
357
|
},
|
|
203
358
|
OptionalCallExpression(path) {
|
|
359
|
+
if (isUnsafeInstrumentationContext(path))
|
|
360
|
+
return;
|
|
204
361
|
if (!path.node.optional || !t.isExpression(path.node.callee))
|
|
205
362
|
return;
|
|
206
363
|
const callee = path.node.callee;
|
|
364
|
+
if (t.isOptionalMemberExpression(callee)) {
|
|
365
|
+
if (path.node.loc) {
|
|
366
|
+
limitations.push({
|
|
367
|
+
id: stableId(file, "semantic-safety", path.node, "optional-chain-call"),
|
|
368
|
+
kind: "semantic-safety",
|
|
369
|
+
file,
|
|
370
|
+
line: path.node.loc.start.line,
|
|
371
|
+
column: path.node.loc.start.column + 1,
|
|
372
|
+
source: sourceFor(code, path.node),
|
|
373
|
+
reason: "optional calls whose receiver is itself an optional chain are left native to preserve chain short-circuiting and method receiver semantics",
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
207
378
|
if (t.isMemberExpression(callee) || t.isOptionalMemberExpression(callee)) {
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
return;
|
|
379
|
+
// Rewriting `object.method?.()` through `.call` preserves `this` for
|
|
380
|
+
// the immediate invocation but breaks the specification's optional
|
|
381
|
+
// chain continuation (`object.method?.()()`). Leave it native until a
|
|
382
|
+
// probe can preserve the chain's internal short-circuit target.
|
|
383
|
+
if (path.node.loc) {
|
|
384
|
+
limitations.push({
|
|
385
|
+
id: stableId(file, "semantic-safety", path.node, "optional-method-call"),
|
|
386
|
+
kind: "semantic-safety",
|
|
387
|
+
file,
|
|
388
|
+
line: path.node.loc.start.line,
|
|
389
|
+
column: path.node.loc.start.column + 1,
|
|
390
|
+
source: sourceFor(code, path.node),
|
|
391
|
+
reason: "optional method calls are left native to preserve receiver and whole-chain short-circuit semantics",
|
|
392
|
+
});
|
|
223
393
|
}
|
|
224
|
-
const objectId = path.scope.generateUidIdentifier("supercovOptionalObject");
|
|
225
|
-
const callableId = path.scope.generateUidIdentifier("supercovOptionalCallable");
|
|
226
|
-
const frameScope = path.scope.getFunctionParent() ?? path.scope.getProgramParent();
|
|
227
|
-
frameScope.push({ id: t.cloneNode(objectId), kind: "let" });
|
|
228
|
-
frameScope.push({ id: t.cloneNode(callableId), kind: "let" });
|
|
229
|
-
const objectAssignment = t.assignmentExpression("=", t.cloneNode(objectId), callee.object);
|
|
230
|
-
const member = t.memberExpression(t.cloneNode(objectId), t.cloneNode(callee.property), callee.computed);
|
|
231
|
-
const callableAssignment = t.assignmentExpression("=", t.cloneNode(callableId), member);
|
|
232
|
-
const selected = instrumentOptionalOperand(path, t.cloneNode(callableId));
|
|
233
|
-
const call = t.optionalCallExpression(t.optionalMemberExpression(selected, t.identifier("call"), false, true), [t.cloneNode(objectId), ...path.node.arguments.map((argument) => t.cloneNode(argument))], false);
|
|
234
|
-
path.replaceWith(t.sequenceExpression([objectAssignment, callableAssignment, call]));
|
|
235
|
-
path.skip();
|
|
236
394
|
return;
|
|
237
395
|
}
|
|
238
396
|
path.node.callee = instrumentOptionalOperand(path, callee);
|
|
@@ -243,6 +401,8 @@ export function instrumentMcdc(code, file) {
|
|
|
243
401
|
traverse(ast, {
|
|
244
402
|
AssignmentExpression: {
|
|
245
403
|
exit(path) {
|
|
404
|
+
if (isUnsafeInstrumentationContext(path))
|
|
405
|
+
return;
|
|
246
406
|
const node = path.node;
|
|
247
407
|
if (!node.loc ||
|
|
248
408
|
(node.operator !== "&&=" &&
|
|
@@ -274,6 +434,11 @@ export function instrumentMcdc(code, file) {
|
|
|
274
434
|
const assignment = t.assignmentExpression(node.operator, t.cloneNode(node.left), t.callExpression(t.identifier(SELECTION_RIGHT), [
|
|
275
435
|
t.cloneNode(frameId),
|
|
276
436
|
node.right,
|
|
437
|
+
...(t.isIdentifier(node.left) &&
|
|
438
|
+
!node.left.extra?.parenthesized &&
|
|
439
|
+
isAnonymousFunctionDefinition(node.right)
|
|
440
|
+
? [t.stringLiteral(node.left.name)]
|
|
441
|
+
: []),
|
|
277
442
|
]));
|
|
278
443
|
path.replaceWith(t.sequenceExpression([
|
|
279
444
|
assignFrame,
|
|
@@ -291,6 +456,8 @@ export function instrumentMcdc(code, file) {
|
|
|
291
456
|
// value without comparing values or evaluating either expression twice.
|
|
292
457
|
traverse(ast, {
|
|
293
458
|
Function(path) {
|
|
459
|
+
if (isUnsafeInstrumentationContext(path))
|
|
460
|
+
return;
|
|
294
461
|
if (!t.isBlockStatement(path.node.body))
|
|
295
462
|
return;
|
|
296
463
|
const entries = [];
|
|
@@ -320,6 +487,10 @@ export function instrumentMcdc(code, file) {
|
|
|
320
487
|
pattern.right = t.callExpression(t.identifier(DEFAULT_SELECTED), [
|
|
321
488
|
t.stringLiteral(defaultId),
|
|
322
489
|
pattern.right,
|
|
490
|
+
...(t.isIdentifier(pattern.left) &&
|
|
491
|
+
isAnonymousFunctionDefinition(pattern.right)
|
|
492
|
+
? [t.stringLiteral(pattern.left.name)]
|
|
493
|
+
: []),
|
|
323
494
|
]);
|
|
324
495
|
entries.push(t.expressionStatement(t.callExpression(t.identifier(DEFAULT_ENTERED), [
|
|
325
496
|
t.stringLiteral(defaultId),
|
|
@@ -355,6 +526,8 @@ export function instrumentMcdc(code, file) {
|
|
|
355
526
|
// after JavaScript has completed the binding operation.
|
|
356
527
|
traverse(ast, {
|
|
357
528
|
VariableDeclaration(path) {
|
|
529
|
+
if (isUnsafeInstrumentationContext(path))
|
|
530
|
+
return;
|
|
358
531
|
const parent = path.parentPath;
|
|
359
532
|
if (parent.isForStatement() && path.key === "init") {
|
|
360
533
|
let hasDefault = false;
|
|
@@ -400,6 +573,10 @@ export function instrumentMcdc(code, file) {
|
|
|
400
573
|
pattern.right = t.callExpression(t.identifier(DEFAULT_SELECTED), [
|
|
401
574
|
t.stringLiteral(defaultId),
|
|
402
575
|
pattern.right,
|
|
576
|
+
...(t.isIdentifier(pattern.left) &&
|
|
577
|
+
isAnonymousFunctionDefinition(pattern.right)
|
|
578
|
+
? [t.stringLiteral(pattern.left.name)]
|
|
579
|
+
: []),
|
|
403
580
|
]);
|
|
404
581
|
entries.push(t.expressionStatement(t.callExpression(t.identifier(DEFAULT_ENTERED), [
|
|
405
582
|
t.stringLiteral(defaultId),
|
|
@@ -444,6 +621,8 @@ export function instrumentMcdc(code, file) {
|
|
|
444
621
|
// return, break, continue, rejection, and throw paths remain observable.
|
|
445
622
|
traverse(ast, {
|
|
446
623
|
TryStatement(path) {
|
|
624
|
+
if (isUnsafeInstrumentationContext(path))
|
|
625
|
+
return;
|
|
447
626
|
const node = path.node;
|
|
448
627
|
if (!node.loc || !node.handler)
|
|
449
628
|
return;
|
|
@@ -481,6 +660,8 @@ export function instrumentMcdc(code, file) {
|
|
|
481
660
|
path.skip();
|
|
482
661
|
},
|
|
483
662
|
"ForInStatement|ForOfStatement"(path) {
|
|
663
|
+
if (isUnsafeInstrumentationContext(path))
|
|
664
|
+
return;
|
|
484
665
|
const node = path.node;
|
|
485
666
|
if (!node.loc)
|
|
486
667
|
return;
|
|
@@ -542,6 +723,8 @@ export function instrumentMcdc(code, file) {
|
|
|
542
723
|
const node = path.node;
|
|
543
724
|
if (!node.loc || !node.body)
|
|
544
725
|
return;
|
|
726
|
+
if (isUnsafeInstrumentationContext(path))
|
|
727
|
+
return;
|
|
545
728
|
const id = stableId(file, "function", node);
|
|
546
729
|
points.push({
|
|
547
730
|
id,
|
|
@@ -552,7 +735,7 @@ export function instrumentMcdc(code, file) {
|
|
|
552
735
|
source: sourceFor(code, node),
|
|
553
736
|
...(functionLabel(path) ? { label: functionLabel(path) } : {}),
|
|
554
737
|
});
|
|
555
|
-
const probe = hitStatement(id);
|
|
738
|
+
const probe = hitStatement(id, HIT);
|
|
556
739
|
generatedStatements.add(probe);
|
|
557
740
|
if (t.isBlockStatement(node.body)) {
|
|
558
741
|
node.body.body.unshift(probe);
|
|
@@ -580,14 +763,21 @@ export function instrumentMcdc(code, file) {
|
|
|
580
763
|
kind,
|
|
581
764
|
};
|
|
582
765
|
decisions.push(meta);
|
|
583
|
-
const frameId = path.scope.generateUidIdentifier("supercovMcdcFrame");
|
|
584
766
|
// A loop predicate's path scope may be represented by Babel as the loop
|
|
585
767
|
// body's block. Declaring the frame there puts it after the predicate that
|
|
586
768
|
// uses it (and is especially visible in async generators). Hoist scratch
|
|
587
769
|
// frames to the nearest function/program scope instead.
|
|
588
|
-
const
|
|
770
|
+
const evaluationScope = (path.isFunctionExpression() ||
|
|
771
|
+
path.isArrowFunctionExpression() ||
|
|
772
|
+
path.isClassExpression()) &&
|
|
773
|
+
path.scope.parent
|
|
774
|
+
? path.scope.parent
|
|
775
|
+
: path.scope;
|
|
776
|
+
const frameScope = evaluationScope.getFunctionParent() ??
|
|
777
|
+
evaluationScope.getProgramParent();
|
|
778
|
+
const frameId = frameScope.generateUidIdentifier("supercovMcdcFrame");
|
|
589
779
|
frameScope.push({ id: t.cloneNode(frameId), kind: "let" });
|
|
590
|
-
const instrumented = instrumentConditions(path.node, frameId, { value: 0 }, decisionLogicalNodes);
|
|
780
|
+
const instrumented = instrumentConditions(path.node, frameId, { value: 0 }, decisionLogicalNodes, CONDITION);
|
|
591
781
|
const begin = t.callExpression(t.identifier(BEGIN), [
|
|
592
782
|
t.stringLiteral(id),
|
|
593
783
|
t.valueToNode(meta),
|
|
@@ -602,18 +792,28 @@ export function instrumentMcdc(code, file) {
|
|
|
602
792
|
};
|
|
603
793
|
traverse(ast, {
|
|
604
794
|
IfStatement(path) {
|
|
795
|
+
if (isUnsafeInstrumentationContext(path))
|
|
796
|
+
return;
|
|
605
797
|
instrumentDecision(path.get("test"), "if");
|
|
606
798
|
},
|
|
607
799
|
ConditionalExpression(path) {
|
|
800
|
+
if (isUnsafeInstrumentationContext(path))
|
|
801
|
+
return;
|
|
608
802
|
instrumentDecision(path.get("test"), "ternary");
|
|
609
803
|
},
|
|
610
804
|
WhileStatement(path) {
|
|
805
|
+
if (isUnsafeInstrumentationContext(path))
|
|
806
|
+
return;
|
|
611
807
|
instrumentDecision(path.get("test"), "while");
|
|
612
808
|
},
|
|
613
809
|
DoWhileStatement(path) {
|
|
810
|
+
if (isUnsafeInstrumentationContext(path))
|
|
811
|
+
return;
|
|
614
812
|
instrumentDecision(path.get("test"), "do-while");
|
|
615
813
|
},
|
|
616
814
|
ForStatement(path) {
|
|
815
|
+
if (isUnsafeInstrumentationContext(path))
|
|
816
|
+
return;
|
|
617
817
|
const test = path.get("test");
|
|
618
818
|
if (test.node)
|
|
619
819
|
instrumentDecision(test, "for");
|
|
@@ -625,6 +825,8 @@ export function instrumentMcdc(code, file) {
|
|
|
625
825
|
traverse(ast, {
|
|
626
826
|
LogicalExpression: {
|
|
627
827
|
exit(path) {
|
|
828
|
+
if (isUnsafeInstrumentationContext(path))
|
|
829
|
+
return;
|
|
628
830
|
const node = path.node;
|
|
629
831
|
if (!node.loc || decisionLogicalNodes.has(node))
|
|
630
832
|
return;
|
|
@@ -671,6 +873,8 @@ export function instrumentMcdc(code, file) {
|
|
|
671
873
|
// can legally fall through to the end and must not be counted as no-match.
|
|
672
874
|
traverse(ast, {
|
|
673
875
|
SwitchStatement(path) {
|
|
876
|
+
if (isUnsafeInstrumentationContext(path))
|
|
877
|
+
return;
|
|
674
878
|
const node = path.node;
|
|
675
879
|
if (!node.loc)
|
|
676
880
|
return;
|
|
@@ -684,7 +888,7 @@ export function instrumentMcdc(code, file) {
|
|
|
684
888
|
const label = switchCase.test
|
|
685
889
|
? `case ${sourceFor(code, switchCase.test)}`
|
|
686
890
|
: "default";
|
|
687
|
-
const probe = hitStatement(alternativeId);
|
|
891
|
+
const probe = hitStatement(alternativeId, HIT);
|
|
688
892
|
generatedStatements.add(probe);
|
|
689
893
|
switchCase.consequent.unshift(...(enteredId
|
|
690
894
|
? [
|
|
@@ -720,7 +924,7 @@ export function instrumentMcdc(code, file) {
|
|
|
720
924
|
switchStatement = t.labeledStatement(t.cloneNode(replacementPath.parentPath.node.label), switchStatement);
|
|
721
925
|
replacementPath = replacementPath.parentPath;
|
|
722
926
|
}
|
|
723
|
-
const noMatchProbe = hitStatement(noMatchId);
|
|
927
|
+
const noMatchProbe = hitStatement(noMatchId, HIT);
|
|
724
928
|
generatedStatements.add(noMatchProbe);
|
|
725
929
|
replacementPath.replaceWith(t.blockStatement([
|
|
726
930
|
t.variableDeclaration("let", [
|
|
@@ -738,6 +942,8 @@ export function instrumentMcdc(code, file) {
|
|
|
738
942
|
// instead of silently claiming 100% for only the surrounding file.
|
|
739
943
|
traverse(ast, {
|
|
740
944
|
CallExpression(path) {
|
|
945
|
+
if (isUnsafeInstrumentationContext(path))
|
|
946
|
+
return;
|
|
741
947
|
const callee = path.node.callee;
|
|
742
948
|
if (!path.node.loc || !t.isIdentifier(callee, { name: "eval" }))
|
|
743
949
|
return;
|
|
@@ -752,6 +958,8 @@ export function instrumentMcdc(code, file) {
|
|
|
752
958
|
});
|
|
753
959
|
},
|
|
754
960
|
NewExpression(path) {
|
|
961
|
+
if (isUnsafeInstrumentationContext(path))
|
|
962
|
+
return;
|
|
755
963
|
if (!path.node.loc ||
|
|
756
964
|
!t.isIdentifier(path.node.callee, { name: "Function" }))
|
|
757
965
|
return;
|
|
@@ -855,6 +1063,8 @@ export function instrumentMcdc(code, file) {
|
|
|
855
1063
|
// let the runtime scan callback arguments for Fetch or Node request headers.
|
|
856
1064
|
traverse(ast, {
|
|
857
1065
|
CallExpression(path) {
|
|
1066
|
+
if (isUnsafeInstrumentationContext(path))
|
|
1067
|
+
return;
|
|
858
1068
|
const callee = path.node.callee;
|
|
859
1069
|
const property = (t.isMemberExpression(callee) || t.isOptionalMemberExpression(callee)) &&
|
|
860
1070
|
!callee.computed &&
|