supercov 0.0.9 → 0.0.11
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/bin/native.js +87 -0
- package/bin/supercov.js +20 -3
- package/dist/agentJson.d.ts +2 -1
- package/dist/agentJson.d.ts.map +1 -1
- package/dist/agentJson.js +18 -0
- package/dist/agentJson.js.map +1 -1
- package/dist/analyze.d.ts.map +1 -1
- package/dist/analyze.js +9 -4
- package/dist/analyze.js.map +1 -1
- package/dist/cli.js +115 -24
- package/dist/cli.js.map +1 -1
- package/dist/directInstrumenter.d.ts.map +1 -1
- package/dist/directInstrumenter.js +39 -19
- package/dist/directInstrumenter.js.map +1 -1
- package/dist/engineEvidence.d.ts +9 -0
- package/dist/engineEvidence.d.ts.map +1 -0
- package/dist/engineEvidence.js +31 -0
- package/dist/engineEvidence.js.map +1 -0
- package/dist/engineInstrumenter.d.ts +16 -0
- package/dist/engineInstrumenter.d.ts.map +1 -0
- package/dist/engineInstrumenter.js +50 -0
- package/dist/engineInstrumenter.js.map +1 -0
- package/dist/engineProcess.d.ts +11 -0
- package/dist/engineProcess.d.ts.map +1 -0
- package/dist/engineProcess.js +59 -0
- package/dist/engineProcess.js.map +1 -0
- package/dist/evidenceArchive.d.ts +1 -0
- package/dist/evidenceArchive.d.ts.map +1 -1
- package/dist/evidenceArchive.js +59 -13
- package/dist/evidenceArchive.js.map +1 -1
- package/dist/instrumenter.d.ts +8 -1
- package/dist/instrumenter.d.ts.map +1 -1
- package/dist/instrumenter.js +478 -80
- package/dist/instrumenter.js.map +1 -1
- package/dist/integrity.d.ts.map +1 -1
- package/dist/integrity.js +9 -1
- package/dist/integrity.js.map +1 -1
- package/dist/launchSupervisor.d.ts.map +1 -1
- package/dist/launchSupervisor.js +3 -0
- package/dist/launchSupervisor.js.map +1 -1
- package/dist/merge.d.ts.map +1 -1
- package/dist/merge.js +9 -6
- package/dist/merge.js.map +1 -1
- package/dist/nodeAssertionInstrumenter.d.ts +2 -2
- package/dist/nodeAssertionInstrumenter.d.ts.map +1 -1
- package/dist/nodeAssertionInstrumenter.js +12 -4
- package/dist/nodeAssertionInstrumenter.js.map +1 -1
- package/dist/nodeTest.d.ts.map +1 -1
- package/dist/nodeTest.js +13 -4
- package/dist/nodeTest.js.map +1 -1
- package/dist/playwright.d.ts.map +1 -1
- package/dist/playwright.js +177 -89
- package/dist/playwright.js.map +1 -1
- package/dist/processDiagnostics.d.ts +34 -0
- package/dist/processDiagnostics.d.ts.map +1 -0
- package/dist/processDiagnostics.js +155 -0
- package/dist/processDiagnostics.js.map +1 -0
- package/dist/project.d.ts +1 -0
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +39 -4
- package/dist/project.js.map +1 -1
- package/dist/query.d.ts +11 -0
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +224 -11
- package/dist/query.js.map +1 -1
- package/dist/register.mjs +52 -0
- package/dist/register.mjs.map +1 -1
- package/dist/resolve-loader.d.mts.map +1 -1
- package/dist/resolve-loader.mjs +8 -2
- package/dist/resolve-loader.mjs.map +1 -1
- package/dist/runAnalysis.d.ts.map +1 -1
- package/dist/runAnalysis.js +3 -1
- package/dist/runAnalysis.js.map +1 -1
- package/dist/runnerEvidence.d.ts +2 -2
- package/dist/runnerEvidence.d.ts.map +1 -1
- package/dist/runnerEvidence.js +6 -4
- package/dist/runnerEvidence.js.map +1 -1
- package/dist/runtime.d.ts +56 -4
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +308 -27
- package/dist/runtime.js.map +1 -1
- package/dist/sourceDiscovery.d.ts.map +1 -1
- package/dist/sourceDiscovery.js +6 -2
- package/dist/sourceDiscovery.js.map +1 -1
- package/dist/transport.js +2 -2
- package/dist/transport.js.map +1 -1
- package/dist/vitePlugin.d.ts.map +1 -1
- package/dist/vitePlugin.js +21 -7
- package/dist/vitePlugin.js.map +1 -1
- package/dist/vitest.js +38 -7
- package/dist/vitest.js.map +1 -1
- package/dist/waivers.d.ts +52 -0
- package/dist/waivers.d.ts.map +1 -0
- package/dist/waivers.js +121 -0
- package/dist/waivers.js.map +1 -0
- package/dist/workspace.d.ts +36 -0
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +212 -36
- package/dist/workspace.js.map +1 -1
- package/package.json +29 -3
package/dist/instrumenter.js
CHANGED
|
@@ -8,6 +8,14 @@ const BEGIN = "__supercovMcdcBegin";
|
|
|
8
8
|
const CONDITION = "__supercovMcdcCondition";
|
|
9
9
|
const END = "__supercovMcdcEnd";
|
|
10
10
|
const HIT = "__supercovCoverageHit";
|
|
11
|
+
const REGISTER_V2 = "__supercovRegisterProbeV2";
|
|
12
|
+
const END_V2 = "__supercovMcdcEndV2";
|
|
13
|
+
const HIT_V2 = "__supercovCoverageHitV2";
|
|
14
|
+
const FILE_V2 = "__supercovProbeFileV2";
|
|
15
|
+
const CLOCK_V2 = "__supercovProbeClockV2";
|
|
16
|
+
const HITS_V2 = "__supercovProbeHitsV2";
|
|
17
|
+
const DECISIONS_V2 = "__supercovProbeDecisionsV2";
|
|
18
|
+
const COMPLETE_V2 = "__supercovProbeCompleteV2";
|
|
11
19
|
const SELECTION_BEGIN = "__supercovSelectionBegin";
|
|
12
20
|
const SELECTION_RIGHT = "__supercovSelectionRight";
|
|
13
21
|
const SELECTION_END = "__supercovSelectionEnd";
|
|
@@ -40,6 +48,26 @@ function sourceFor(code, node) {
|
|
|
40
48
|
}
|
|
41
49
|
return generate(node).code;
|
|
42
50
|
}
|
|
51
|
+
function restoreClonedOffsets(original, clone) {
|
|
52
|
+
clone.start = original.start;
|
|
53
|
+
clone.end = original.end;
|
|
54
|
+
const keys = t.VISITOR_KEYS[original.type] ?? [];
|
|
55
|
+
for (const key of keys) {
|
|
56
|
+
const originalChild = original[key];
|
|
57
|
+
const clonedChild = clone[key];
|
|
58
|
+
if (Array.isArray(originalChild) && Array.isArray(clonedChild)) {
|
|
59
|
+
for (let index = 0; index < originalChild.length; index += 1) {
|
|
60
|
+
const sourceNode = originalChild[index];
|
|
61
|
+
const targetNode = clonedChild[index];
|
|
62
|
+
if (t.isNode(sourceNode) && t.isNode(targetNode))
|
|
63
|
+
restoreClonedOffsets(sourceNode, targetNode);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else if (t.isNode(originalChild) && t.isNode(clonedChild)) {
|
|
67
|
+
restoreClonedOffsets(originalChild, clonedChild);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
43
71
|
function stableId(file, kind, node, suffix = "") {
|
|
44
72
|
return createHash("sha256")
|
|
45
73
|
.update(`${file}:${kind}:${node.start ?? 0}:${node.end ?? 0}:${suffix}`)
|
|
@@ -68,6 +96,82 @@ function collectConditions(node, conditions) {
|
|
|
68
96
|
}
|
|
69
97
|
conditions.push(node);
|
|
70
98
|
}
|
|
99
|
+
function probeV2ReachableVectorCount(node, conditions) {
|
|
100
|
+
const indices = new WeakMap();
|
|
101
|
+
for (const [index, condition] of conditions.entries())
|
|
102
|
+
indices.set(condition, index);
|
|
103
|
+
const vectors = new Set();
|
|
104
|
+
const evaluate = (expression, assignment, encoded) => {
|
|
105
|
+
if (t.isLogicalExpression(expression) &&
|
|
106
|
+
(expression.operator === "&&" || expression.operator === "||")) {
|
|
107
|
+
const left = evaluate(expression.left, assignment, encoded);
|
|
108
|
+
return expression.operator === "&&"
|
|
109
|
+
? left && evaluate(expression.right, assignment, encoded)
|
|
110
|
+
: left || evaluate(expression.right, assignment, encoded);
|
|
111
|
+
}
|
|
112
|
+
if (t.isUnaryExpression(expression, { operator: "!" }) &&
|
|
113
|
+
hasCompoundBooleanDecision(expression.argument))
|
|
114
|
+
return !evaluate(expression.argument, assignment, encoded);
|
|
115
|
+
const index = indices.get(expression);
|
|
116
|
+
if (index === undefined)
|
|
117
|
+
throw new Error("probe v2 condition index missing");
|
|
118
|
+
const value = (assignment & (1 << index)) !== 0;
|
|
119
|
+
encoded.value += (value ? 2 : 1) * 3 ** index;
|
|
120
|
+
return value;
|
|
121
|
+
};
|
|
122
|
+
for (let assignment = 0; assignment < 2 ** conditions.length; assignment += 1) {
|
|
123
|
+
const encoded = { value: 0 };
|
|
124
|
+
const outcome = evaluate(node, assignment, encoded);
|
|
125
|
+
vectors.add(encoded.value * 2 + (outcome ? 1 : 0));
|
|
126
|
+
}
|
|
127
|
+
return vectors.size;
|
|
128
|
+
}
|
|
129
|
+
function markDecisionLogicalNodes(node, decisions) {
|
|
130
|
+
if (t.isLogicalExpression(node) &&
|
|
131
|
+
(node.operator === "&&" || node.operator === "||")) {
|
|
132
|
+
decisions.add(node);
|
|
133
|
+
markDecisionLogicalNodes(node.left, decisions);
|
|
134
|
+
markDecisionLogicalNodes(node.right, decisions);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
if (t.isUnaryExpression(node, { operator: "!" }) &&
|
|
138
|
+
hasCompoundBooleanDecision(node.argument))
|
|
139
|
+
markDecisionLogicalNodes(node.argument, decisions);
|
|
140
|
+
}
|
|
141
|
+
function hasNestedCoverageSurface(node) {
|
|
142
|
+
let found = false;
|
|
143
|
+
const visit = (candidate) => {
|
|
144
|
+
if (found)
|
|
145
|
+
return;
|
|
146
|
+
if (t.isLogicalExpression(candidate) ||
|
|
147
|
+
t.isConditionalExpression(candidate) ||
|
|
148
|
+
t.isOptionalMemberExpression(candidate) ||
|
|
149
|
+
t.isOptionalCallExpression(candidate) ||
|
|
150
|
+
t.isFunction(candidate) ||
|
|
151
|
+
t.isClassExpression(candidate) ||
|
|
152
|
+
(t.isAssignmentExpression(candidate) &&
|
|
153
|
+
["&&=", "||=", "??="].includes(candidate.operator))) {
|
|
154
|
+
found = true;
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
for (const key of t.VISITOR_KEYS[candidate.type] ?? []) {
|
|
158
|
+
const child = candidate[key];
|
|
159
|
+
if (Array.isArray(child)) {
|
|
160
|
+
for (const item of child)
|
|
161
|
+
if (item && typeof item === "object" && "type" in item)
|
|
162
|
+
visit(item);
|
|
163
|
+
}
|
|
164
|
+
else if (child && typeof child === "object" && "type" in child) {
|
|
165
|
+
visit(child);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
visit(node);
|
|
170
|
+
return found;
|
|
171
|
+
}
|
|
172
|
+
function isCoverageTransparentDecision(conditions) {
|
|
173
|
+
return conditions.every((condition) => !hasNestedCoverageSurface(condition));
|
|
174
|
+
}
|
|
71
175
|
function instrumentConditions(node, frameId, nextIndex, decisionLogicalNodes, conditionRuntimeName) {
|
|
72
176
|
if (t.isLogicalExpression(node) &&
|
|
73
177
|
(node.operator === "&&" || node.operator === "||")) {
|
|
@@ -87,7 +191,38 @@ function instrumentConditions(node, frameId, nextIndex, decisionLogicalNodes, co
|
|
|
87
191
|
node,
|
|
88
192
|
]);
|
|
89
193
|
}
|
|
90
|
-
function
|
|
194
|
+
function instrumentConditionsV2(node, frameId, conditionTemps, nextIndex, decisionLogicalNodes) {
|
|
195
|
+
if (t.isLogicalExpression(node) &&
|
|
196
|
+
(node.operator === "&&" || node.operator === "||")) {
|
|
197
|
+
const logical = t.logicalExpression(node.operator, instrumentConditionsV2(node.left, frameId, conditionTemps, nextIndex, decisionLogicalNodes), instrumentConditionsV2(node.right, frameId, conditionTemps, nextIndex, decisionLogicalNodes));
|
|
198
|
+
decisionLogicalNodes.add(logical);
|
|
199
|
+
return logical;
|
|
200
|
+
}
|
|
201
|
+
if (t.isUnaryExpression(node, { operator: "!" }) &&
|
|
202
|
+
hasCompoundBooleanDecision(node.argument)) {
|
|
203
|
+
return t.unaryExpression("!", instrumentConditionsV2(node.argument, frameId, conditionTemps, nextIndex, decisionLogicalNodes), true);
|
|
204
|
+
}
|
|
205
|
+
const index = nextIndex.value;
|
|
206
|
+
nextIndex.value += 1;
|
|
207
|
+
const temporary = conditionTemps[index];
|
|
208
|
+
const weight = 3 ** index;
|
|
209
|
+
return t.sequenceExpression([
|
|
210
|
+
t.assignmentExpression("=", t.cloneNode(temporary), node),
|
|
211
|
+
t.assignmentExpression("+=", t.cloneNode(frameId), t.conditionalExpression(t.cloneNode(temporary), t.numericLiteral(weight * 2), t.numericLiteral(weight))),
|
|
212
|
+
t.cloneNode(temporary),
|
|
213
|
+
]);
|
|
214
|
+
}
|
|
215
|
+
function hitStatement(id, hitRuntimeName, v2) {
|
|
216
|
+
if (v2) {
|
|
217
|
+
const fileState = t.cloneNode(v2.fileState);
|
|
218
|
+
const clock = t.cloneNode(v2.clock);
|
|
219
|
+
return t.expressionStatement(t.logicalExpression("||", t.binaryExpression("===", t.memberExpression(t.cloneNode(v2.hitEpochs), t.numericLiteral(v2.pointIndex), true), v2.epoch
|
|
220
|
+
? t.cloneNode(v2.epoch)
|
|
221
|
+
: t.memberExpression(t.cloneNode(clock), t.identifier("epoch"))), t.callExpression(t.identifier(hitRuntimeName), [
|
|
222
|
+
t.cloneNode(fileState),
|
|
223
|
+
t.numericLiteral(v2.pointIndex),
|
|
224
|
+
])));
|
|
225
|
+
}
|
|
91
226
|
return t.expressionStatement(t.callExpression(t.identifier(hitRuntimeName), [t.stringLiteral(id)]));
|
|
92
227
|
}
|
|
93
228
|
function allocatedRuntimeNames(code) {
|
|
@@ -96,6 +231,14 @@ function allocatedRuntimeNames(code) {
|
|
|
96
231
|
CONDITION,
|
|
97
232
|
END,
|
|
98
233
|
HIT,
|
|
234
|
+
REGISTER_V2,
|
|
235
|
+
END_V2,
|
|
236
|
+
HIT_V2,
|
|
237
|
+
FILE_V2,
|
|
238
|
+
CLOCK_V2,
|
|
239
|
+
HITS_V2,
|
|
240
|
+
DECISIONS_V2,
|
|
241
|
+
COMPLETE_V2,
|
|
99
242
|
SELECTION_BEGIN,
|
|
100
243
|
SELECTION_RIGHT,
|
|
101
244
|
SELECTION_END,
|
|
@@ -151,6 +294,16 @@ function isExecutableStatement(node) {
|
|
|
151
294
|
}
|
|
152
295
|
return !("declare" in node && node.declare === true);
|
|
153
296
|
}
|
|
297
|
+
function executablePointNode(node) {
|
|
298
|
+
if (t.isExportNamedDeclaration(node) || t.isExportDefaultDeclaration(node)) {
|
|
299
|
+
const declaration = node.declaration;
|
|
300
|
+
return declaration && t.isStatement(declaration) &&
|
|
301
|
+
isExecutableStatement(declaration)
|
|
302
|
+
? declaration
|
|
303
|
+
: undefined;
|
|
304
|
+
}
|
|
305
|
+
return isExecutableStatement(node) ? node : undefined;
|
|
306
|
+
}
|
|
154
307
|
function isAnonymousFunctionDefinition(node) {
|
|
155
308
|
return (t.isArrowFunctionExpression(node) ||
|
|
156
309
|
(t.isFunctionExpression(node) && node.id === null) ||
|
|
@@ -222,12 +375,24 @@ function isInsideFunctionParameter(path) {
|
|
|
222
375
|
}
|
|
223
376
|
return false;
|
|
224
377
|
}
|
|
225
|
-
|
|
378
|
+
/** Number encoding remains exact while both 3^n and its doubled outcome index are safe. */
|
|
379
|
+
export const PROBE_V2_MAX_CONDITIONS = 32;
|
|
380
|
+
export const PROBE_V2_DENSE_CONDITION_LIMIT = 6;
|
|
381
|
+
export function instrumentMcdc(code, file, options = {}) {
|
|
226
382
|
const names = allocatedRuntimeNames(code);
|
|
227
383
|
const BEGIN = names["__supercovMcdcBegin"];
|
|
228
384
|
const CONDITION = names["__supercovMcdcCondition"];
|
|
229
385
|
const END = names["__supercovMcdcEnd"];
|
|
230
386
|
const HIT = names["__supercovCoverageHit"];
|
|
387
|
+
const REGISTER_V2 = names["__supercovRegisterProbeV2"];
|
|
388
|
+
const END_V2 = names["__supercovMcdcEndV2"];
|
|
389
|
+
const HIT_V2 = names["__supercovCoverageHitV2"];
|
|
390
|
+
const FILE_V2 = names["__supercovProbeFileV2"];
|
|
391
|
+
const CLOCK_V2 = names["__supercovProbeClockV2"];
|
|
392
|
+
const HITS_V2 = names["__supercovProbeHitsV2"];
|
|
393
|
+
const DECISIONS_V2 = names["__supercovProbeDecisionsV2"];
|
|
394
|
+
const COMPLETE_V2 = names["__supercovProbeCompleteV2"];
|
|
395
|
+
const probeVersion = options.probeVersion ?? 1;
|
|
231
396
|
const SELECTION_BEGIN = names["__supercovSelectionBegin"];
|
|
232
397
|
const SELECTION_RIGHT = names["__supercovSelectionRight"];
|
|
233
398
|
const SELECTION_END = names["__supercovSelectionEnd"];
|
|
@@ -258,6 +423,7 @@ export function instrumentMcdc(code, file) {
|
|
|
258
423
|
plugins: parserPlugins,
|
|
259
424
|
});
|
|
260
425
|
const decisions = [];
|
|
426
|
+
const decisionVectorCounts = [];
|
|
261
427
|
const points = [];
|
|
262
428
|
const branches = [];
|
|
263
429
|
const limitations = [];
|
|
@@ -304,28 +470,103 @@ export function instrumentMcdc(code, file) {
|
|
|
304
470
|
},
|
|
305
471
|
});
|
|
306
472
|
const isUnsafeInstrumentationContext = (path) => isWithinSourceSensitiveFunction(path) || isInsideWithStatement(path);
|
|
473
|
+
// A synchronous function cannot suspend while its body is executing. A
|
|
474
|
+
// nested coverage scope restores the caller's epoch before returning, so
|
|
475
|
+
// interior probes can compare against one scalar captured at entry instead
|
|
476
|
+
// of repeatedly loading the shared clock object. Async functions and
|
|
477
|
+
// generators deliberately keep the shared clock: either may resume under a
|
|
478
|
+
// later attribution epoch. Parameter initializers also use the shared clock
|
|
479
|
+
// because they execute before this body-local declaration.
|
|
480
|
+
const functionEpochs = new WeakMap();
|
|
481
|
+
const functionDecisionCaches = new WeakMap();
|
|
482
|
+
const decisionCacheDeclarations = [];
|
|
483
|
+
if (probeVersion === 2) {
|
|
484
|
+
traverse(ast, {
|
|
485
|
+
Function(path) {
|
|
486
|
+
const node = path.node;
|
|
487
|
+
if (node.async ||
|
|
488
|
+
node.generator ||
|
|
489
|
+
!t.isBlockStatement(node.body) ||
|
|
490
|
+
isUnsafeInstrumentationContext(path))
|
|
491
|
+
return;
|
|
492
|
+
const epoch = path.scope.generateUidIdentifier("supercovEpoch");
|
|
493
|
+
const declaration = t.variableDeclaration("const", [
|
|
494
|
+
t.variableDeclarator(t.cloneNode(epoch), t.memberExpression(t.identifier(CLOCK_V2), t.identifier("epoch"))),
|
|
495
|
+
]);
|
|
496
|
+
generatedStatements.add(declaration);
|
|
497
|
+
node.body.body.unshift(declaration);
|
|
498
|
+
functionEpochs.set(node, epoch);
|
|
499
|
+
},
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
const functionOwnerForPath = (path) => {
|
|
503
|
+
const owner = path.findParent((parent) => parent.isFunction());
|
|
504
|
+
return owner?.isFunction() ? owner : undefined;
|
|
505
|
+
};
|
|
506
|
+
const epochForPath = (path) => {
|
|
507
|
+
const owner = functionOwnerForPath(path);
|
|
508
|
+
return owner ? functionEpochs.get(owner.node) : undefined;
|
|
509
|
+
};
|
|
510
|
+
const localDecisionForPath = (path) => {
|
|
511
|
+
const owner = functionOwnerForPath(path);
|
|
512
|
+
if (!owner ||
|
|
513
|
+
!functionEpochs.has(owner.node) ||
|
|
514
|
+
!t.isBlockStatement(owner.node.body))
|
|
515
|
+
return undefined;
|
|
516
|
+
let cache = functionDecisionCaches.get(owner.node);
|
|
517
|
+
if (!cache) {
|
|
518
|
+
cache = {
|
|
519
|
+
bits: owner.scope.generateUidIdentifier("supercovComplete"),
|
|
520
|
+
next: 0,
|
|
521
|
+
};
|
|
522
|
+
functionDecisionCaches.set(owner.node, cache);
|
|
523
|
+
decisionCacheDeclarations.push({
|
|
524
|
+
body: owner.node.body,
|
|
525
|
+
bits: cache.bits,
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
const bit = cache.next++;
|
|
529
|
+
return bit < 30 ? { bits: cache.bits, mask: 2 ** bit } : undefined;
|
|
530
|
+
};
|
|
307
531
|
// Record executable statements before adding any instrumentation statements.
|
|
532
|
+
const instrumentedStatements = new WeakSet();
|
|
308
533
|
traverse(ast, {
|
|
309
534
|
Statement(path) {
|
|
310
535
|
const node = path.node;
|
|
311
|
-
if (
|
|
536
|
+
if (instrumentedStatements.has(node))
|
|
537
|
+
return;
|
|
538
|
+
if ((path.parentPath.isExportNamedDeclaration() ||
|
|
539
|
+
path.parentPath.isExportDefaultDeclaration()) &&
|
|
540
|
+
path.parentPath.node.declaration === node)
|
|
541
|
+
return;
|
|
542
|
+
const pointNode = executablePointNode(node);
|
|
543
|
+
if (!pointNode?.loc ||
|
|
312
544
|
isUnsafeInstrumentationContext(path) ||
|
|
313
545
|
generatedStatements.has(node) ||
|
|
314
|
-
!isExecutableStatement(
|
|
546
|
+
!isExecutableStatement(pointNode))
|
|
315
547
|
return;
|
|
316
548
|
if (path.parentPath.isLabeledStatement())
|
|
317
549
|
return;
|
|
318
|
-
const id = stableId(file, "statement",
|
|
550
|
+
const id = stableId(file, "statement", pointNode);
|
|
319
551
|
points.push({
|
|
320
552
|
id,
|
|
321
553
|
kind: "statement",
|
|
322
554
|
file,
|
|
323
|
-
line:
|
|
324
|
-
column:
|
|
325
|
-
source: sourceFor(code,
|
|
555
|
+
line: pointNode.loc.start.line,
|
|
556
|
+
column: pointNode.loc.start.column + 1,
|
|
557
|
+
source: sourceFor(code, pointNode),
|
|
326
558
|
});
|
|
327
|
-
const probe = hitStatement(id, HIT
|
|
559
|
+
const probe = hitStatement(id, probeVersion === 2 ? HIT_V2 : HIT, probeVersion === 2
|
|
560
|
+
? {
|
|
561
|
+
fileState: t.identifier(FILE_V2),
|
|
562
|
+
clock: t.identifier(CLOCK_V2),
|
|
563
|
+
hitEpochs: t.identifier(HITS_V2),
|
|
564
|
+
pointIndex: points.length - 1,
|
|
565
|
+
epoch: epochForPath(path),
|
|
566
|
+
}
|
|
567
|
+
: undefined);
|
|
328
568
|
generatedStatements.add(probe);
|
|
569
|
+
instrumentedStatements.add(node);
|
|
329
570
|
if (path.parentPath.isProgram() ||
|
|
330
571
|
path.parentPath.isBlockStatement() ||
|
|
331
572
|
path.parentPath.isSwitchCase()) {
|
|
@@ -339,7 +580,6 @@ export function instrumentMcdc(code, file) {
|
|
|
339
580
|
path.key === "consequent" ||
|
|
340
581
|
path.key === "alternate") {
|
|
341
582
|
path.replaceWith(t.blockStatement([probe, node]));
|
|
342
|
-
path.skip();
|
|
343
583
|
}
|
|
344
584
|
},
|
|
345
585
|
});
|
|
@@ -443,10 +683,14 @@ export function instrumentMcdc(code, file) {
|
|
|
443
683
|
for (const site of chain.sites) {
|
|
444
684
|
chain.path.scope.push({ id: t.cloneNode(site.frame), kind: "let" });
|
|
445
685
|
const callee = site.node.callee;
|
|
446
|
-
if (t.isMemberExpression(callee) ||
|
|
686
|
+
if (t.isMemberExpression(callee) ||
|
|
687
|
+
t.isOptionalMemberExpression(callee)) {
|
|
447
688
|
if (t.isPrivateName(callee.property)) {
|
|
448
689
|
if (t.isExpression(callee.object))
|
|
449
690
|
callee.object = t.callExpression(t.identifier(OPTIONAL_CALL_REACHED), [t.cloneNode(site.frame), callee.object]);
|
|
691
|
+
site.node.arguments.unshift(t.spreadElement(t.callExpression(t.identifier(OPTIONAL_CALL_CONTINUED), [
|
|
692
|
+
t.cloneNode(site.frame),
|
|
693
|
+
])));
|
|
450
694
|
continue;
|
|
451
695
|
}
|
|
452
696
|
const property = callee.computed
|
|
@@ -540,8 +784,6 @@ export function instrumentMcdc(code, file) {
|
|
|
540
784
|
Function(path) {
|
|
541
785
|
if (isUnsafeInstrumentationContext(path))
|
|
542
786
|
return;
|
|
543
|
-
if (!t.isBlockStatement(path.node.body))
|
|
544
|
-
return;
|
|
545
787
|
const entries = [];
|
|
546
788
|
const visitPattern = (pattern) => {
|
|
547
789
|
if (t.isTSParameterProperty(pattern)) {
|
|
@@ -600,7 +842,19 @@ export function instrumentMcdc(code, file) {
|
|
|
600
842
|
};
|
|
601
843
|
for (const parameter of path.node.params)
|
|
602
844
|
visitPattern(parameter);
|
|
603
|
-
|
|
845
|
+
if (entries.length === 0)
|
|
846
|
+
return;
|
|
847
|
+
if (t.isBlockStatement(path.node.body)) {
|
|
848
|
+
path.node.body.body.unshift(...entries);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
if (!path.isArrowFunctionExpression())
|
|
852
|
+
throw new Error("Only arrow functions may have expression bodies");
|
|
853
|
+
const value = path.node.body;
|
|
854
|
+
path.node.body = t.blockStatement([
|
|
855
|
+
...entries,
|
|
856
|
+
t.returnStatement(value),
|
|
857
|
+
]);
|
|
604
858
|
},
|
|
605
859
|
});
|
|
606
860
|
// Destructuring declarations use the same token protocol. For loop-binding
|
|
@@ -701,6 +955,8 @@ export function instrumentMcdc(code, file) {
|
|
|
701
955
|
});
|
|
702
956
|
// Try/catch and enumeration loops use frames finalized in `finally`, so
|
|
703
957
|
// return, break, continue, rejection, and throw paths remain observable.
|
|
958
|
+
const tryTargets = new WeakMap();
|
|
959
|
+
const enumerationTargets = new WeakMap();
|
|
704
960
|
traverse(ast, {
|
|
705
961
|
TryStatement(path) {
|
|
706
962
|
if (isUnsafeInstrumentationContext(path))
|
|
@@ -723,23 +979,7 @@ export function instrumentMcdc(code, file) {
|
|
|
723
979
|
{ id: catchId, label: "catch entered" },
|
|
724
980
|
],
|
|
725
981
|
});
|
|
726
|
-
|
|
727
|
-
const frameScope = path.scope.getFunctionParent() ?? path.scope.getProgramParent();
|
|
728
|
-
frameScope.push({ id: t.cloneNode(frameId), kind: "let" });
|
|
729
|
-
path.insertBefore(t.expressionStatement(t.assignmentExpression("=", t.cloneNode(frameId), t.callExpression(t.identifier(TRY_BEGIN), [
|
|
730
|
-
t.stringLiteral(successId),
|
|
731
|
-
t.stringLiteral(catchId),
|
|
732
|
-
]))));
|
|
733
|
-
node.handler.body.body.unshift(t.expressionStatement(t.callExpression(t.identifier(TRY_CATCH), [
|
|
734
|
-
t.cloneNode(frameId),
|
|
735
|
-
t.identifier("undefined"),
|
|
736
|
-
])));
|
|
737
|
-
const end = t.expressionStatement(t.callExpression(t.identifier(TRY_END), [t.cloneNode(frameId)]));
|
|
738
|
-
if (node.finalizer)
|
|
739
|
-
node.finalizer.body.unshift(end);
|
|
740
|
-
else
|
|
741
|
-
node.finalizer = t.blockStatement([end]);
|
|
742
|
-
path.skip();
|
|
982
|
+
tryTargets.set(node, { successId, catchId });
|
|
743
983
|
},
|
|
744
984
|
"ForInStatement|ForOfStatement"(path) {
|
|
745
985
|
if (isUnsafeInstrumentationContext(path))
|
|
@@ -763,39 +1003,86 @@ export function instrumentMcdc(code, file) {
|
|
|
763
1003
|
{ id: enteredId, label: "one or more iterations" },
|
|
764
1004
|
],
|
|
765
1005
|
});
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
1006
|
+
enumerationTargets.set(node, { zeroId, enteredId });
|
|
1007
|
+
},
|
|
1008
|
+
});
|
|
1009
|
+
// Transform bottom-up after the denominator is frozen. This keeps nested
|
|
1010
|
+
// try/loop nodes attached to the tree while they are rewritten; mutating an
|
|
1011
|
+
// outer path first can leave a nested Babel path detached while still
|
|
1012
|
+
// recording an obligation that can never emit evidence.
|
|
1013
|
+
traverse(ast, {
|
|
1014
|
+
TryStatement: {
|
|
1015
|
+
exit(path) {
|
|
1016
|
+
if (isUnsafeInstrumentationContext(path))
|
|
1017
|
+
return;
|
|
1018
|
+
const node = path.node;
|
|
1019
|
+
const target = tryTargets.get(node);
|
|
1020
|
+
if (!target || !node.handler)
|
|
1021
|
+
return;
|
|
1022
|
+
const frameId = path.scope.generateUidIdentifier("supercovTryFrame");
|
|
1023
|
+
const frameScope = path.scope.getFunctionParent() ?? path.scope.getProgramParent();
|
|
1024
|
+
frameScope.push({ id: t.cloneNode(frameId), kind: "let" });
|
|
1025
|
+
path.insertBefore(t.expressionStatement(t.assignmentExpression("=", t.cloneNode(frameId), t.callExpression(t.identifier(TRY_BEGIN), [
|
|
1026
|
+
t.stringLiteral(target.successId),
|
|
1027
|
+
t.stringLiteral(target.catchId),
|
|
1028
|
+
]))));
|
|
1029
|
+
node.handler.body.body.unshift(t.expressionStatement(t.callExpression(t.identifier(TRY_CATCH), [
|
|
1030
|
+
t.cloneNode(frameId),
|
|
1031
|
+
t.identifier("undefined"),
|
|
1032
|
+
])));
|
|
1033
|
+
const end = t.expressionStatement(t.callExpression(t.identifier(TRY_END), [t.cloneNode(frameId)]));
|
|
1034
|
+
if (node.finalizer)
|
|
1035
|
+
node.finalizer.body.unshift(end);
|
|
1036
|
+
else
|
|
1037
|
+
node.finalizer = t.blockStatement([end]);
|
|
793
1038
|
path.skip();
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
1039
|
+
},
|
|
1040
|
+
},
|
|
1041
|
+
"ForInStatement|ForOfStatement": {
|
|
1042
|
+
exit(path) {
|
|
1043
|
+
if (isUnsafeInstrumentationContext(path))
|
|
1044
|
+
return;
|
|
1045
|
+
const node = path.node;
|
|
1046
|
+
const target = enumerationTargets.get(node);
|
|
1047
|
+
if (!target)
|
|
1048
|
+
return;
|
|
1049
|
+
const frameId = path.scope.generateUidIdentifier("supercovLoopFrame");
|
|
1050
|
+
const frameScope = path.scope.getFunctionParent() ?? path.scope.getProgramParent();
|
|
1051
|
+
frameScope.push({ id: t.cloneNode(frameId), kind: "let" });
|
|
1052
|
+
const assignment = t.expressionStatement(t.assignmentExpression("=", t.cloneNode(frameId), t.callExpression(t.identifier(LOOP_BEGIN), [
|
|
1053
|
+
t.stringLiteral(target.zeroId),
|
|
1054
|
+
t.stringLiteral(target.enteredId),
|
|
1055
|
+
])));
|
|
1056
|
+
const loop = t.cloneNode(node, true);
|
|
1057
|
+
restoreClonedOffsets(node, loop);
|
|
1058
|
+
loop.body = t.isBlockStatement(loop.body)
|
|
1059
|
+
? loop.body
|
|
1060
|
+
: t.blockStatement([loop.body]);
|
|
1061
|
+
loop.body.body.unshift(t.expressionStatement(probeVersion === 2
|
|
1062
|
+
? t.assignmentExpression("=", t.memberExpression(t.cloneNode(frameId), t.identifier("entered")), t.booleanLiteral(true))
|
|
1063
|
+
: t.callExpression(t.identifier(LOOP_ENTERED), [
|
|
1064
|
+
t.cloneNode(frameId),
|
|
1065
|
+
])));
|
|
1066
|
+
let loopStatement = loop;
|
|
1067
|
+
let replacementPath = path;
|
|
1068
|
+
while (replacementPath.parentPath?.isLabeledStatement() &&
|
|
1069
|
+
replacementPath.parentPath.node.body === replacementPath.node) {
|
|
1070
|
+
loopStatement = t.labeledStatement(t.cloneNode(replacementPath.parentPath.node.label), loopStatement);
|
|
1071
|
+
replacementPath = replacementPath.parentPath;
|
|
1072
|
+
}
|
|
1073
|
+
const wrapped = t.tryStatement(t.blockStatement([loopStatement]), null, t.blockStatement([
|
|
1074
|
+
t.expressionStatement(t.callExpression(t.identifier(LOOP_END), [t.cloneNode(frameId)])),
|
|
1075
|
+
]));
|
|
1076
|
+
if (replacementPath === path) {
|
|
1077
|
+
path.insertBefore(assignment);
|
|
1078
|
+
path.replaceWith(wrapped);
|
|
1079
|
+
path.skip();
|
|
1080
|
+
}
|
|
1081
|
+
else {
|
|
1082
|
+
replacementPath.replaceWith(t.blockStatement([assignment, wrapped]));
|
|
1083
|
+
replacementPath.skip();
|
|
1084
|
+
}
|
|
1085
|
+
},
|
|
799
1086
|
},
|
|
800
1087
|
});
|
|
801
1088
|
// Function entry is separate from statement coverage so empty functions and
|
|
@@ -817,7 +1104,14 @@ export function instrumentMcdc(code, file) {
|
|
|
817
1104
|
source: sourceFor(code, node),
|
|
818
1105
|
...(functionLabel(path) ? { label: functionLabel(path) } : {}),
|
|
819
1106
|
});
|
|
820
|
-
const probe = hitStatement(id, HIT
|
|
1107
|
+
const probe = hitStatement(id, probeVersion === 2 ? HIT_V2 : HIT, probeVersion === 2
|
|
1108
|
+
? {
|
|
1109
|
+
fileState: t.identifier(FILE_V2),
|
|
1110
|
+
clock: t.identifier(CLOCK_V2),
|
|
1111
|
+
hitEpochs: t.identifier(HITS_V2),
|
|
1112
|
+
pointIndex: points.length - 1,
|
|
1113
|
+
}
|
|
1114
|
+
: undefined);
|
|
821
1115
|
generatedStatements.add(probe);
|
|
822
1116
|
if (t.isBlockStatement(node.body)) {
|
|
823
1117
|
node.body.body.unshift(probe);
|
|
@@ -844,6 +1138,7 @@ export function instrumentMcdc(code, file) {
|
|
|
844
1138
|
conditions: originalConditions.map((condition) => sourceFor(code, condition)),
|
|
845
1139
|
kind,
|
|
846
1140
|
};
|
|
1141
|
+
const decisionIndex = decisions.length;
|
|
847
1142
|
decisions.push(meta);
|
|
848
1143
|
// A loop predicate's path scope may be represented by Babel as the loop
|
|
849
1144
|
// body's block. Declaring the frame there puts it after the predicate that
|
|
@@ -856,29 +1151,99 @@ export function instrumentMcdc(code, file) {
|
|
|
856
1151
|
path.scope.parent
|
|
857
1152
|
? path.scope.parent
|
|
858
1153
|
: path.scope;
|
|
859
|
-
const frameScope = evaluationScope.getFunctionParent() ??
|
|
860
|
-
evaluationScope.getProgramParent();
|
|
1154
|
+
const frameScope = evaluationScope.getFunctionParent() ?? evaluationScope.getProgramParent();
|
|
861
1155
|
const frameId = frameScope.generateUidIdentifier("supercovMcdcFrame");
|
|
862
|
-
|
|
1156
|
+
const useV2 = probeVersion === 2 &&
|
|
1157
|
+
originalConditions.length <= PROBE_V2_MAX_CONDITIONS;
|
|
1158
|
+
const conditionTemps = useV2
|
|
1159
|
+
? originalConditions.map(() => frameScope.generateUidIdentifier("supercovMcdcValue"))
|
|
1160
|
+
: [];
|
|
1161
|
+
const resultTemp = useV2
|
|
1162
|
+
? frameScope.generateUidIdentifier("supercovMcdcResult")
|
|
1163
|
+
: undefined;
|
|
1164
|
+
const useDenseV2 = useV2 && originalConditions.length <= PROBE_V2_DENSE_CONDITION_LIMIT;
|
|
1165
|
+
const useSaturatedV2 = useDenseV2 && isCoverageTransparentDecision(originalConditions);
|
|
1166
|
+
const originalDecision = useSaturatedV2
|
|
1167
|
+
? t.cloneNode(path.node, true)
|
|
1168
|
+
: undefined;
|
|
1169
|
+
if (originalDecision)
|
|
1170
|
+
markDecisionLogicalNodes(originalDecision, decisionLogicalNodes);
|
|
1171
|
+
decisionVectorCounts.push(useSaturatedV2
|
|
1172
|
+
? probeV2ReachableVectorCount(path.node, originalConditions)
|
|
1173
|
+
: 0);
|
|
1174
|
+
const decisionEpoch = inlineFrame ? undefined : epochForPath(path);
|
|
1175
|
+
const localComplete = useSaturatedV2 && !inlineFrame ? localDecisionForPath(path) : undefined;
|
|
1176
|
+
const vectorTemp = useDenseV2
|
|
1177
|
+
? frameScope.generateUidIdentifier("supercovMcdcVector")
|
|
1178
|
+
: undefined;
|
|
1179
|
+
if (!inlineFrame) {
|
|
863
1180
|
frameScope.push({ id: t.cloneNode(frameId), kind: "let" });
|
|
864
|
-
|
|
1181
|
+
for (const temporary of conditionTemps)
|
|
1182
|
+
frameScope.push({ id: t.cloneNode(temporary), kind: "let" });
|
|
1183
|
+
if (resultTemp)
|
|
1184
|
+
frameScope.push({ id: t.cloneNode(resultTemp), kind: "let" });
|
|
1185
|
+
if (vectorTemp)
|
|
1186
|
+
frameScope.push({ id: t.cloneNode(vectorTemp), kind: "let" });
|
|
1187
|
+
}
|
|
1188
|
+
const instrumented = useV2
|
|
1189
|
+
? instrumentConditionsV2(path.node, frameId, conditionTemps, { value: 0 }, decisionLogicalNodes)
|
|
1190
|
+
: instrumentConditions(path.node, frameId, { value: 0 }, decisionLogicalNodes, CONDITION);
|
|
865
1191
|
const begin = t.callExpression(t.identifier(BEGIN), [
|
|
866
1192
|
t.stringLiteral(id),
|
|
867
1193
|
t.valueToNode(meta),
|
|
868
1194
|
]);
|
|
869
|
-
const assignFrame = t.assignmentExpression("=", t.cloneNode(frameId), begin);
|
|
870
|
-
const end =
|
|
871
|
-
t.
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1195
|
+
const assignFrame = t.assignmentExpression("=", t.cloneNode(frameId), useV2 ? t.numericLiteral(0) : begin);
|
|
1196
|
+
const end = useV2
|
|
1197
|
+
? t.callExpression(t.identifier(END_V2), [
|
|
1198
|
+
t.identifier(FILE_V2),
|
|
1199
|
+
t.numericLiteral(decisionIndex),
|
|
1200
|
+
t.cloneNode(frameId),
|
|
1201
|
+
t.cloneNode(resultTemp),
|
|
1202
|
+
])
|
|
1203
|
+
: t.callExpression(t.identifier(END), [
|
|
1204
|
+
t.cloneNode(frameId),
|
|
1205
|
+
instrumented,
|
|
1206
|
+
]);
|
|
1207
|
+
const v2Tail = useV2
|
|
1208
|
+
? [
|
|
1209
|
+
t.assignmentExpression("=", t.cloneNode(resultTemp), instrumented),
|
|
1210
|
+
...(useDenseV2
|
|
1211
|
+
? [
|
|
1212
|
+
t.assignmentExpression("=", t.cloneNode(vectorTemp), t.binaryExpression("+", t.binaryExpression("*", t.cloneNode(frameId), t.numericLiteral(2)), t.conditionalExpression(t.cloneNode(resultTemp), t.numericLiteral(1), t.numericLiteral(0)))),
|
|
1213
|
+
t.logicalExpression("||", t.binaryExpression("===", t.memberExpression(t.memberExpression(t.identifier(DECISIONS_V2), t.numericLiteral(decisionIndex), true), t.cloneNode(vectorTemp), true), decisionEpoch
|
|
1214
|
+
? t.cloneNode(decisionEpoch)
|
|
1215
|
+
: t.memberExpression(t.identifier(CLOCK_V2), t.identifier("epoch"))), end),
|
|
1216
|
+
t.cloneNode(resultTemp),
|
|
1217
|
+
]
|
|
1218
|
+
: [end]),
|
|
1219
|
+
]
|
|
1220
|
+
: [end];
|
|
1221
|
+
const observedDecision = inlineFrame
|
|
875
1222
|
? t.callExpression(t.arrowFunctionExpression([], t.blockStatement([
|
|
876
1223
|
t.variableDeclaration("let", [
|
|
877
|
-
t.variableDeclarator(t.cloneNode(frameId), begin),
|
|
1224
|
+
t.variableDeclarator(t.cloneNode(frameId), useV2 ? t.numericLiteral(0) : begin),
|
|
1225
|
+
...conditionTemps.map((temporary) => t.variableDeclarator(t.cloneNode(temporary))),
|
|
1226
|
+
...(resultTemp
|
|
1227
|
+
? [t.variableDeclarator(t.cloneNode(resultTemp))]
|
|
1228
|
+
: []),
|
|
1229
|
+
...(vectorTemp
|
|
1230
|
+
? [t.variableDeclarator(t.cloneNode(vectorTemp))]
|
|
1231
|
+
: []),
|
|
878
1232
|
]),
|
|
879
|
-
t.returnStatement(end),
|
|
1233
|
+
t.returnStatement(useV2 ? t.sequenceExpression(v2Tail) : end),
|
|
880
1234
|
])), [])
|
|
881
|
-
: t.sequenceExpression([assignFrame, end]));
|
|
1235
|
+
: t.sequenceExpression([assignFrame, ...(useV2 ? v2Tail : [end])]);
|
|
1236
|
+
const completeCheck = useSaturatedV2
|
|
1237
|
+
? t.binaryExpression("===", t.memberExpression(t.identifier(COMPLETE_V2), t.numericLiteral(decisionIndex), true), decisionEpoch
|
|
1238
|
+
? t.cloneNode(decisionEpoch)
|
|
1239
|
+
: t.memberExpression(t.identifier(CLOCK_V2), t.identifier("epoch")))
|
|
1240
|
+
: undefined;
|
|
1241
|
+
const completeFastPath = completeCheck && localComplete
|
|
1242
|
+
? t.logicalExpression("||", t.binaryExpression("!==", t.binaryExpression("&", t.cloneNode(localComplete.bits), t.numericLiteral(localComplete.mask)), t.numericLiteral(0)), t.logicalExpression("&&", completeCheck, t.assignmentExpression("|=", t.cloneNode(localComplete.bits), t.numericLiteral(localComplete.mask))))
|
|
1243
|
+
: completeCheck;
|
|
1244
|
+
path.replaceWith(useSaturatedV2
|
|
1245
|
+
? t.conditionalExpression(completeFastPath, originalDecision, observedDecision)
|
|
1246
|
+
: observedDecision);
|
|
882
1247
|
path.skip();
|
|
883
1248
|
};
|
|
884
1249
|
traverse(ast, {
|
|
@@ -1158,14 +1523,17 @@ export function instrumentMcdc(code, file) {
|
|
|
1158
1523
|
if (isUnsafeInstrumentationContext(path))
|
|
1159
1524
|
return;
|
|
1160
1525
|
const callee = path.node.callee;
|
|
1161
|
-
const property = (t.isMemberExpression(callee) ||
|
|
1526
|
+
const property = (t.isMemberExpression(callee) ||
|
|
1527
|
+
t.isOptionalMemberExpression(callee)) &&
|
|
1162
1528
|
!callee.computed &&
|
|
1163
1529
|
t.isIdentifier(callee.property)
|
|
1164
1530
|
? callee.property.name
|
|
1165
1531
|
: undefined;
|
|
1166
1532
|
const identifier = t.isIdentifier(callee) ? callee.name : property;
|
|
1167
1533
|
let callbackIndex = -1;
|
|
1168
|
-
if ((property === "on" ||
|
|
1534
|
+
if ((property === "on" ||
|
|
1535
|
+
property === "once" ||
|
|
1536
|
+
property === "addListener") &&
|
|
1169
1537
|
t.isStringLiteral(path.node.arguments[0]) &&
|
|
1170
1538
|
["request", "upgrade", "connection"].includes(path.node.arguments[0].value)) {
|
|
1171
1539
|
callbackIndex = 1;
|
|
@@ -1194,6 +1562,13 @@ export function instrumentMcdc(code, file) {
|
|
|
1194
1562
|
usesRequestPhaseHandler = true;
|
|
1195
1563
|
},
|
|
1196
1564
|
});
|
|
1565
|
+
// Decision cache identifiers are discovered during instrumentation. Insert
|
|
1566
|
+
// their declarations only after all source traversals so no live NodePath is
|
|
1567
|
+
// shifted while a source statement is being rewritten.
|
|
1568
|
+
for (const { body, bits } of decisionCacheDeclarations)
|
|
1569
|
+
body.body.unshift(t.variableDeclaration("let", [
|
|
1570
|
+
t.variableDeclarator(t.cloneNode(bits), t.numericLiteral(0)),
|
|
1571
|
+
]));
|
|
1197
1572
|
const manifest = {
|
|
1198
1573
|
decisions,
|
|
1199
1574
|
points,
|
|
@@ -1209,6 +1584,13 @@ export function instrumentMcdc(code, file) {
|
|
|
1209
1584
|
t.importSpecifier(t.identifier(CONDITION), t.identifier("mcdcCondition")),
|
|
1210
1585
|
t.importSpecifier(t.identifier(END), t.identifier("mcdcEnd")),
|
|
1211
1586
|
t.importSpecifier(t.identifier(HIT), t.identifier("coverageHit")),
|
|
1587
|
+
...(probeVersion === 2
|
|
1588
|
+
? [
|
|
1589
|
+
t.importSpecifier(t.identifier(REGISTER_V2), t.identifier("registerProbeV2")),
|
|
1590
|
+
t.importSpecifier(t.identifier(END_V2), t.identifier("mcdcEndV2")),
|
|
1591
|
+
t.importSpecifier(t.identifier(HIT_V2), t.identifier("coverageHitV2")),
|
|
1592
|
+
]
|
|
1593
|
+
: []),
|
|
1212
1594
|
t.importSpecifier(t.identifier(SELECTION_BEGIN), t.identifier("selectionBegin")),
|
|
1213
1595
|
t.importSpecifier(t.identifier(SELECTION_RIGHT), t.identifier("selectionRight")),
|
|
1214
1596
|
t.importSpecifier(t.identifier(SELECTION_END), t.identifier("selectionEnd")),
|
|
@@ -1230,7 +1612,23 @@ export function instrumentMcdc(code, file) {
|
|
|
1230
1612
|
t.importSpecifier(t.identifier(WITH_REQUEST_PHASE), t.identifier("withRequestPhase")),
|
|
1231
1613
|
]
|
|
1232
1614
|
: []),
|
|
1233
|
-
], t.stringLiteral(RUNTIME_MODULE))
|
|
1615
|
+
], t.stringLiteral(RUNTIME_MODULE)), ...(probeVersion === 2
|
|
1616
|
+
? [
|
|
1617
|
+
t.variableDeclaration("const", [
|
|
1618
|
+
t.variableDeclarator(t.identifier(FILE_V2), t.callExpression(t.identifier(REGISTER_V2), [
|
|
1619
|
+
t.valueToNode({
|
|
1620
|
+
decisions,
|
|
1621
|
+
pointIds: points.map((point) => point.id),
|
|
1622
|
+
decisionVectorCounts,
|
|
1623
|
+
}),
|
|
1624
|
+
])),
|
|
1625
|
+
t.variableDeclarator(t.identifier(CLOCK_V2), t.memberExpression(t.identifier(FILE_V2), t.identifier("clock"))),
|
|
1626
|
+
t.variableDeclarator(t.identifier(HITS_V2), t.memberExpression(t.identifier(FILE_V2), t.identifier("hitEpochs"))),
|
|
1627
|
+
t.variableDeclarator(t.identifier(DECISIONS_V2), t.memberExpression(t.identifier(FILE_V2), t.identifier("decisionEpochs"))),
|
|
1628
|
+
t.variableDeclarator(t.identifier(COMPLETE_V2), t.memberExpression(t.identifier(FILE_V2), t.identifier("decisionCompleteEpochs"))),
|
|
1629
|
+
]),
|
|
1630
|
+
]
|
|
1631
|
+
: []));
|
|
1234
1632
|
}
|
|
1235
1633
|
const output = generate({
|
|
1236
1634
|
...ast,
|