supercov 0.0.11 → 0.0.13
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 +28 -23
- package/bin/supercov.js +8 -19
- package/package.json +31 -43
- package/{dist → runtime/javascript}/atomic.js +0 -1
- package/{dist → runtime/javascript}/launchSupervisor.js +45 -2
- package/{dist → runtime/javascript}/nodeAssert.js +0 -1
- package/{dist → runtime/javascript}/nodeAssertAdapter.js +0 -1
- package/{dist → runtime/javascript}/nodeAssertStrict.js +0 -1
- package/{dist → runtime/javascript}/nodeTest.js +0 -1
- package/{dist → runtime/javascript}/playwright.js +0 -1
- package/{dist → runtime/javascript}/playwrightReporter.js +0 -1
- package/{dist → runtime/javascript}/provenance.js +0 -1
- package/{dist → runtime/javascript}/register.mjs +3 -2
- package/{dist → runtime/javascript}/resolve-loader.mjs +0 -42
- package/{dist → runtime/javascript}/runnerEvidence.js +0 -1
- package/{dist → runtime/javascript}/transport.js +0 -1
- package/{dist → runtime/javascript}/vitest.js +0 -1
- package/{dist → runtime/javascript}/vitestReporter.js +0 -1
- package/dist/agentJson.d.ts +0 -45
- package/dist/agentJson.d.ts.map +0 -1
- package/dist/agentJson.js +0 -102
- package/dist/agentJson.js.map +0 -1
- package/dist/analyze.d.ts +0 -12
- package/dist/analyze.d.ts.map +0 -1
- package/dist/analyze.js +0 -704
- package/dist/analyze.js.map +0 -1
- package/dist/atomic.d.ts +0 -6
- package/dist/atomic.d.ts.map +0 -1
- package/dist/atomic.js.map +0 -1
- package/dist/buildCache.d.ts +0 -14
- package/dist/buildCache.d.ts.map +0 -1
- package/dist/buildCache.js +0 -84
- package/dist/buildCache.js.map +0 -1
- package/dist/cli.d.ts +0 -2
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -797
- package/dist/cli.js.map +0 -1
- package/dist/directInstrumenter.d.ts +0 -8
- package/dist/directInstrumenter.d.ts.map +0 -1
- package/dist/directInstrumenter.js +0 -112
- package/dist/directInstrumenter.js.map +0 -1
- package/dist/engineEvidence.d.ts +0 -9
- package/dist/engineEvidence.d.ts.map +0 -1
- package/dist/engineEvidence.js +0 -31
- package/dist/engineEvidence.js.map +0 -1
- package/dist/engineInstrumenter.d.ts +0 -16
- package/dist/engineInstrumenter.d.ts.map +0 -1
- package/dist/engineInstrumenter.js +0 -50
- package/dist/engineInstrumenter.js.map +0 -1
- package/dist/engineProcess.d.ts +0 -11
- package/dist/engineProcess.d.ts.map +0 -1
- package/dist/engineProcess.js +0 -59
- package/dist/engineProcess.js.map +0 -1
- package/dist/esmInterceptor.d.ts +0 -6
- package/dist/esmInterceptor.d.ts.map +0 -1
- package/dist/esmInterceptor.js +0 -63
- package/dist/esmInterceptor.js.map +0 -1
- package/dist/evidenceArchive.d.ts +0 -38
- package/dist/evidenceArchive.d.ts.map +0 -1
- package/dist/evidenceArchive.js +0 -156
- package/dist/evidenceArchive.js.map +0 -1
- package/dist/instrumenter.d.ts +0 -18
- package/dist/instrumenter.d.ts.map +0 -1
- package/dist/instrumenter.js +0 -1644
- package/dist/instrumenter.js.map +0 -1
- package/dist/integrity.d.ts +0 -10
- package/dist/integrity.d.ts.map +0 -1
- package/dist/integrity.js +0 -213
- package/dist/integrity.js.map +0 -1
- package/dist/launchSupervisor.d.ts +0 -38
- package/dist/launchSupervisor.d.ts.map +0 -1
- package/dist/launchSupervisor.js.map +0 -1
- package/dist/merge.d.ts +0 -2
- package/dist/merge.d.ts.map +0 -1
- package/dist/merge.js +0 -119
- package/dist/merge.js.map +0 -1
- package/dist/nodeAssert.d.ts +0 -31
- package/dist/nodeAssert.d.ts.map +0 -1
- package/dist/nodeAssert.js.map +0 -1
- package/dist/nodeAssertAdapter.d.ts +0 -6
- package/dist/nodeAssertAdapter.d.ts.map +0 -1
- package/dist/nodeAssertAdapter.js.map +0 -1
- package/dist/nodeAssertStrict.d.ts +0 -31
- package/dist/nodeAssertStrict.d.ts.map +0 -1
- package/dist/nodeAssertStrict.js.map +0 -1
- package/dist/nodeAssertionInstrumenter.d.ts +0 -8
- package/dist/nodeAssertionInstrumenter.d.ts.map +0 -1
- package/dist/nodeAssertionInstrumenter.js +0 -271
- package/dist/nodeAssertionInstrumenter.js.map +0 -1
- package/dist/nodeTest.d.ts +0 -15
- package/dist/nodeTest.d.ts.map +0 -1
- package/dist/nodeTest.js.map +0 -1
- package/dist/playwright.d.ts +0 -9
- package/dist/playwright.d.ts.map +0 -1
- package/dist/playwright.js.map +0 -1
- package/dist/playwrightReporter.d.ts +0 -6
- package/dist/playwrightReporter.d.ts.map +0 -1
- package/dist/playwrightReporter.js.map +0 -1
- package/dist/processDiagnostics.d.ts +0 -34
- package/dist/processDiagnostics.d.ts.map +0 -1
- package/dist/processDiagnostics.js +0 -155
- package/dist/processDiagnostics.js.map +0 -1
- package/dist/project.d.ts +0 -21
- package/dist/project.d.ts.map +0 -1
- package/dist/project.js +0 -331
- package/dist/project.js.map +0 -1
- package/dist/provenance.d.ts +0 -9
- package/dist/provenance.d.ts.map +0 -1
- package/dist/provenance.js.map +0 -1
- package/dist/query.d.ts +0 -81
- package/dist/query.d.ts.map +0 -1
- package/dist/query.js +0 -1656
- package/dist/query.js.map +0 -1
- package/dist/queryCache.d.ts +0 -15
- package/dist/queryCache.d.ts.map +0 -1
- package/dist/queryCache.js +0 -95
- package/dist/queryCache.js.map +0 -1
- package/dist/queueAdapters.d.ts +0 -15
- package/dist/queueAdapters.d.ts.map +0 -1
- package/dist/queueAdapters.js +0 -55
- package/dist/queueAdapters.js.map +0 -1
- package/dist/register.d.mts +0 -2
- package/dist/register.d.mts.map +0 -1
- package/dist/register.mjs.map +0 -1
- package/dist/reporter.d.ts +0 -13
- package/dist/reporter.d.ts.map +0 -1
- package/dist/reporter.js +0 -252
- package/dist/reporter.js.map +0 -1
- package/dist/resolve-loader.d.mts +0 -3
- package/dist/resolve-loader.d.mts.map +0 -1
- package/dist/resolve-loader.mjs.map +0 -1
- package/dist/runAnalysis.d.ts +0 -19
- package/dist/runAnalysis.d.ts.map +0 -1
- package/dist/runAnalysis.js +0 -171
- package/dist/runAnalysis.js.map +0 -1
- package/dist/runnerEvidence.d.ts +0 -19
- package/dist/runnerEvidence.d.ts.map +0 -1
- package/dist/runnerEvidence.js.map +0 -1
- package/dist/runtime.d.ts +0 -140
- package/dist/runtime.d.ts.map +0 -1
- package/dist/runtime.js.map +0 -1
- package/dist/runtimeIsolation.d.ts +0 -3
- package/dist/runtimeIsolation.d.ts.map +0 -1
- package/dist/runtimeIsolation.js +0 -9
- package/dist/runtimeIsolation.js.map +0 -1
- package/dist/sourceDiscovery.d.ts +0 -9
- package/dist/sourceDiscovery.d.ts.map +0 -1
- package/dist/sourceDiscovery.js +0 -242
- package/dist/sourceDiscovery.js.map +0 -1
- package/dist/transport.d.ts +0 -17
- package/dist/transport.d.ts.map +0 -1
- package/dist/transport.js.map +0 -1
- package/dist/types.d.ts +0 -378
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -2
- package/dist/types.js.map +0 -1
- package/dist/vitePlugin.d.ts +0 -13
- package/dist/vitePlugin.d.ts.map +0 -1
- package/dist/vitePlugin.js +0 -139
- package/dist/vitePlugin.js.map +0 -1
- package/dist/vitest.d.ts +0 -2
- package/dist/vitest.d.ts.map +0 -1
- package/dist/vitest.js.map +0 -1
- package/dist/vitestReporter.d.ts +0 -43
- package/dist/vitestReporter.d.ts.map +0 -1
- package/dist/vitestReporter.js.map +0 -1
- package/dist/waivers.d.ts +0 -52
- package/dist/waivers.d.ts.map +0 -1
- package/dist/waivers.js +0 -121
- package/dist/waivers.js.map +0 -1
- package/dist/workspace.d.ts +0 -117
- package/dist/workspace.d.ts.map +0 -1
- package/dist/workspace.js +0 -680
- package/dist/workspace.js.map +0 -1
- /package/{dist → runtime/javascript}/runtime.js +0 -0
package/dist/instrumenter.js
DELETED
|
@@ -1,1644 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import generate from "@babel/generator";
|
|
3
|
-
import { parse } from "@babel/parser";
|
|
4
|
-
import traverse from "@babel/traverse";
|
|
5
|
-
import * as t from "@babel/types";
|
|
6
|
-
const RUNTIME_MODULE = "virtual:supercov-runtime";
|
|
7
|
-
const BEGIN = "__supercovMcdcBegin";
|
|
8
|
-
const CONDITION = "__supercovMcdcCondition";
|
|
9
|
-
const END = "__supercovMcdcEnd";
|
|
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";
|
|
19
|
-
const SELECTION_BEGIN = "__supercovSelectionBegin";
|
|
20
|
-
const SELECTION_RIGHT = "__supercovSelectionRight";
|
|
21
|
-
const SELECTION_END = "__supercovSelectionEnd";
|
|
22
|
-
const WITH_REQUEST_PHASE = "__supercovWithRequestPhase";
|
|
23
|
-
const OPTIONAL_SELECT = "__supercovOptionalSelect";
|
|
24
|
-
const OPTIONAL_CALL_BEGIN = "__supercovOptionalCallBegin";
|
|
25
|
-
const OPTIONAL_CALL_REACHED = "__supercovOptionalCallReached";
|
|
26
|
-
const OPTIONAL_CALL_CONTINUED = "__supercovOptionalCallContinued";
|
|
27
|
-
const OPTIONAL_CALL_END = "__supercovOptionalCallEnd";
|
|
28
|
-
const DEFAULT_SELECTED = "__supercovDefaultSelected";
|
|
29
|
-
const DEFAULT_ENTERED = "__supercovDefaultEntered";
|
|
30
|
-
const TRY_BEGIN = "__supercovTryBegin";
|
|
31
|
-
const TRY_CATCH = "__supercovTryCatch";
|
|
32
|
-
const TRY_END = "__supercovTryEnd";
|
|
33
|
-
const LOOP_BEGIN = "__supercovLoopBegin";
|
|
34
|
-
const LOOP_ENTERED = "__supercovLoopEntered";
|
|
35
|
-
const LOOP_END = "__supercovLoopEnd";
|
|
36
|
-
function isRequestHandlerName(file, name) {
|
|
37
|
-
if (name === "loader" || name === "action")
|
|
38
|
-
return /^app\/routes\//.test(file);
|
|
39
|
-
return (/(?:^|\/)app\/.*\/route\.[cm]?[jt]sx?$/.test(file) &&
|
|
40
|
-
["GET", "HEAD", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"].includes(name ?? ""));
|
|
41
|
-
}
|
|
42
|
-
function sourceFor(code, node) {
|
|
43
|
-
if (node.start !== null &&
|
|
44
|
-
node.start !== undefined &&
|
|
45
|
-
node.end !== null &&
|
|
46
|
-
node.end !== undefined) {
|
|
47
|
-
return code.slice(node.start, node.end);
|
|
48
|
-
}
|
|
49
|
-
return generate(node).code;
|
|
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
|
-
}
|
|
71
|
-
function stableId(file, kind, node, suffix = "") {
|
|
72
|
-
return createHash("sha256")
|
|
73
|
-
.update(`${file}:${kind}:${node.start ?? 0}:${node.end ?? 0}:${suffix}`)
|
|
74
|
-
.digest("hex")
|
|
75
|
-
.slice(0, 16);
|
|
76
|
-
}
|
|
77
|
-
function hasCompoundBooleanDecision(node) {
|
|
78
|
-
if (t.isLogicalExpression(node) &&
|
|
79
|
-
(node.operator === "&&" || node.operator === "||")) {
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
return (t.isUnaryExpression(node, { operator: "!" }) &&
|
|
83
|
-
hasCompoundBooleanDecision(node.argument));
|
|
84
|
-
}
|
|
85
|
-
function collectConditions(node, conditions) {
|
|
86
|
-
if (t.isLogicalExpression(node) &&
|
|
87
|
-
(node.operator === "&&" || node.operator === "||")) {
|
|
88
|
-
collectConditions(node.left, conditions);
|
|
89
|
-
collectConditions(node.right, conditions);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
if (t.isUnaryExpression(node, { operator: "!" }) &&
|
|
93
|
-
hasCompoundBooleanDecision(node.argument)) {
|
|
94
|
-
collectConditions(node.argument, conditions);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
conditions.push(node);
|
|
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
|
-
}
|
|
175
|
-
function instrumentConditions(node, frameId, nextIndex, decisionLogicalNodes, conditionRuntimeName) {
|
|
176
|
-
if (t.isLogicalExpression(node) &&
|
|
177
|
-
(node.operator === "&&" || node.operator === "||")) {
|
|
178
|
-
const logical = t.logicalExpression(node.operator, instrumentConditions(node.left, frameId, nextIndex, decisionLogicalNodes, conditionRuntimeName), instrumentConditions(node.right, frameId, nextIndex, decisionLogicalNodes, conditionRuntimeName));
|
|
179
|
-
decisionLogicalNodes.add(logical);
|
|
180
|
-
return logical;
|
|
181
|
-
}
|
|
182
|
-
if (t.isUnaryExpression(node, { operator: "!" }) &&
|
|
183
|
-
hasCompoundBooleanDecision(node.argument)) {
|
|
184
|
-
return t.unaryExpression("!", instrumentConditions(node.argument, frameId, nextIndex, decisionLogicalNodes, conditionRuntimeName), true);
|
|
185
|
-
}
|
|
186
|
-
const index = nextIndex.value;
|
|
187
|
-
nextIndex.value += 1;
|
|
188
|
-
return t.callExpression(t.identifier(conditionRuntimeName), [
|
|
189
|
-
t.cloneNode(frameId),
|
|
190
|
-
t.numericLiteral(index),
|
|
191
|
-
node,
|
|
192
|
-
]);
|
|
193
|
-
}
|
|
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
|
-
}
|
|
226
|
-
return t.expressionStatement(t.callExpression(t.identifier(hitRuntimeName), [t.stringLiteral(id)]));
|
|
227
|
-
}
|
|
228
|
-
function allocatedRuntimeNames(code) {
|
|
229
|
-
const preferred = [
|
|
230
|
-
BEGIN,
|
|
231
|
-
CONDITION,
|
|
232
|
-
END,
|
|
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,
|
|
242
|
-
SELECTION_BEGIN,
|
|
243
|
-
SELECTION_RIGHT,
|
|
244
|
-
SELECTION_END,
|
|
245
|
-
WITH_REQUEST_PHASE,
|
|
246
|
-
OPTIONAL_SELECT,
|
|
247
|
-
OPTIONAL_CALL_BEGIN,
|
|
248
|
-
OPTIONAL_CALL_REACHED,
|
|
249
|
-
OPTIONAL_CALL_CONTINUED,
|
|
250
|
-
OPTIONAL_CALL_END,
|
|
251
|
-
DEFAULT_SELECTED,
|
|
252
|
-
DEFAULT_ENTERED,
|
|
253
|
-
TRY_BEGIN,
|
|
254
|
-
TRY_CATCH,
|
|
255
|
-
TRY_END,
|
|
256
|
-
LOOP_BEGIN,
|
|
257
|
-
LOOP_ENTERED,
|
|
258
|
-
LOOP_END,
|
|
259
|
-
];
|
|
260
|
-
const names = {};
|
|
261
|
-
const used = new Set();
|
|
262
|
-
for (const base of preferred) {
|
|
263
|
-
let candidate = base;
|
|
264
|
-
while (used.has(candidate) || code.includes(candidate))
|
|
265
|
-
candidate = `_${candidate}`;
|
|
266
|
-
names[base] = candidate;
|
|
267
|
-
used.add(candidate);
|
|
268
|
-
}
|
|
269
|
-
return names;
|
|
270
|
-
}
|
|
271
|
-
function functionLabel(path) {
|
|
272
|
-
if ("id" in path.node && t.isIdentifier(path.node.id))
|
|
273
|
-
return path.node.id.name;
|
|
274
|
-
const parent = path.parentPath;
|
|
275
|
-
if (parent.isObjectProperty() ||
|
|
276
|
-
parent.isObjectMethod() ||
|
|
277
|
-
parent.isClassMethod()) {
|
|
278
|
-
const key = parent.node.key;
|
|
279
|
-
if (t.isIdentifier(key))
|
|
280
|
-
return key.name;
|
|
281
|
-
if (t.isStringLiteral(key))
|
|
282
|
-
return key.value;
|
|
283
|
-
}
|
|
284
|
-
if (parent.isVariableDeclarator() && t.isIdentifier(parent.node.id))
|
|
285
|
-
return parent.node.id.name;
|
|
286
|
-
return undefined;
|
|
287
|
-
}
|
|
288
|
-
function isExecutableStatement(node) {
|
|
289
|
-
if (t.isBlockStatement(node) ||
|
|
290
|
-
t.isEmptyStatement(node) ||
|
|
291
|
-
t.isFunctionDeclaration(node) ||
|
|
292
|
-
node.type.startsWith("TS")) {
|
|
293
|
-
return false;
|
|
294
|
-
}
|
|
295
|
-
return !("declare" in node && node.declare === true);
|
|
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
|
-
}
|
|
307
|
-
function isAnonymousFunctionDefinition(node) {
|
|
308
|
-
return (t.isArrowFunctionExpression(node) ||
|
|
309
|
-
(t.isFunctionExpression(node) && node.id === null) ||
|
|
310
|
-
(t.isClassExpression(node) && node.id === null));
|
|
311
|
-
}
|
|
312
|
-
function isSourceSensitiveFunction(path) {
|
|
313
|
-
let child = path;
|
|
314
|
-
for (let parent = path.parentPath; parent; parent = parent.parentPath) {
|
|
315
|
-
const candidate = parent.node;
|
|
316
|
-
const value = child.node;
|
|
317
|
-
if ((t.isParenthesizedExpression(candidate) ||
|
|
318
|
-
t.isTSAsExpression(candidate) ||
|
|
319
|
-
t.isTSTypeAssertion(candidate) ||
|
|
320
|
-
t.isTSNonNullExpression(candidate)) &&
|
|
321
|
-
candidate.expression === value) {
|
|
322
|
-
child = parent;
|
|
323
|
-
continue;
|
|
324
|
-
}
|
|
325
|
-
if ((t.isConditionalExpression(candidate) &&
|
|
326
|
-
(candidate.consequent === value || candidate.alternate === value)) ||
|
|
327
|
-
(t.isLogicalExpression(candidate) &&
|
|
328
|
-
(candidate.left === value || candidate.right === value)) ||
|
|
329
|
-
(t.isSequenceExpression(candidate) &&
|
|
330
|
-
candidate.expressions.at(-1) === value) ||
|
|
331
|
-
(t.isAssignmentExpression(candidate) && candidate.right === value)) {
|
|
332
|
-
child = parent;
|
|
333
|
-
continue;
|
|
334
|
-
}
|
|
335
|
-
if (((t.isClassMethod(candidate) ||
|
|
336
|
-
t.isClassProperty(candidate) ||
|
|
337
|
-
t.isObjectMethod(candidate) ||
|
|
338
|
-
t.isObjectProperty(candidate)) &&
|
|
339
|
-
candidate.computed &&
|
|
340
|
-
candidate.key === value) ||
|
|
341
|
-
((t.isMemberExpression(candidate) ||
|
|
342
|
-
t.isOptionalMemberExpression(candidate)) &&
|
|
343
|
-
candidate.computed &&
|
|
344
|
-
candidate.property === value))
|
|
345
|
-
return true;
|
|
346
|
-
if (t.isCallExpression(candidate) &&
|
|
347
|
-
t.isIdentifier(candidate.callee, { name: "String" }) &&
|
|
348
|
-
candidate.arguments.includes(value))
|
|
349
|
-
return true;
|
|
350
|
-
if (t.isBinaryExpression(candidate) &&
|
|
351
|
-
["+", "<", "<=", ">", ">="].includes(candidate.operator) &&
|
|
352
|
-
(candidate.left === value || candidate.right === value))
|
|
353
|
-
return true;
|
|
354
|
-
if ((t.isMemberExpression(candidate) ||
|
|
355
|
-
t.isOptionalMemberExpression(candidate)) &&
|
|
356
|
-
!candidate.computed &&
|
|
357
|
-
t.isIdentifier(candidate.property, { name: "toString" }) &&
|
|
358
|
-
candidate.object === value)
|
|
359
|
-
return true;
|
|
360
|
-
// Any other operation consumes or stores the function value. Static
|
|
361
|
-
// ancestry can no longer prove that this exact function is coerced.
|
|
362
|
-
return false;
|
|
363
|
-
}
|
|
364
|
-
return false;
|
|
365
|
-
}
|
|
366
|
-
function isInsideWithStatement(path) {
|
|
367
|
-
return Boolean(path.findParent((parent) => parent.isWithStatement()));
|
|
368
|
-
}
|
|
369
|
-
function isInsideFunctionParameter(path) {
|
|
370
|
-
let current = path;
|
|
371
|
-
while (current?.parentPath) {
|
|
372
|
-
if (current.parentPath.isFunction())
|
|
373
|
-
return current.listKey === "params";
|
|
374
|
-
current = current.parentPath;
|
|
375
|
-
}
|
|
376
|
-
return false;
|
|
377
|
-
}
|
|
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 = {}) {
|
|
382
|
-
const names = allocatedRuntimeNames(code);
|
|
383
|
-
const BEGIN = names["__supercovMcdcBegin"];
|
|
384
|
-
const CONDITION = names["__supercovMcdcCondition"];
|
|
385
|
-
const END = names["__supercovMcdcEnd"];
|
|
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;
|
|
396
|
-
const SELECTION_BEGIN = names["__supercovSelectionBegin"];
|
|
397
|
-
const SELECTION_RIGHT = names["__supercovSelectionRight"];
|
|
398
|
-
const SELECTION_END = names["__supercovSelectionEnd"];
|
|
399
|
-
const WITH_REQUEST_PHASE = names["__supercovWithRequestPhase"];
|
|
400
|
-
const OPTIONAL_SELECT = names["__supercovOptionalSelect"];
|
|
401
|
-
const OPTIONAL_CALL_BEGIN = names["__supercovOptionalCallBegin"];
|
|
402
|
-
const OPTIONAL_CALL_REACHED = names["__supercovOptionalCallReached"];
|
|
403
|
-
const OPTIONAL_CALL_CONTINUED = names["__supercovOptionalCallContinued"];
|
|
404
|
-
const OPTIONAL_CALL_END = names["__supercovOptionalCallEnd"];
|
|
405
|
-
const DEFAULT_SELECTED = names["__supercovDefaultSelected"];
|
|
406
|
-
const DEFAULT_ENTERED = names["__supercovDefaultEntered"];
|
|
407
|
-
const TRY_BEGIN = names["__supercovTryBegin"];
|
|
408
|
-
const TRY_CATCH = names["__supercovTryCatch"];
|
|
409
|
-
const TRY_END = names["__supercovTryEnd"];
|
|
410
|
-
const LOOP_BEGIN = names["__supercovLoopBegin"];
|
|
411
|
-
const LOOP_ENTERED = names["__supercovLoopEntered"];
|
|
412
|
-
const LOOP_END = names["__supercovLoopEnd"];
|
|
413
|
-
const parserPlugins = [
|
|
414
|
-
"typescript",
|
|
415
|
-
"decorators-legacy",
|
|
416
|
-
"sourcePhaseImports",
|
|
417
|
-
...(file.endsWith("x") ? ["jsx"] : []),
|
|
418
|
-
];
|
|
419
|
-
const ast = parse(code, {
|
|
420
|
-
sourceType: "unambiguous",
|
|
421
|
-
sourceFilename: file,
|
|
422
|
-
errorRecovery: true,
|
|
423
|
-
plugins: parserPlugins,
|
|
424
|
-
});
|
|
425
|
-
const decisions = [];
|
|
426
|
-
const decisionVectorCounts = [];
|
|
427
|
-
const points = [];
|
|
428
|
-
const branches = [];
|
|
429
|
-
const limitations = [];
|
|
430
|
-
const generatedStatements = new WeakSet();
|
|
431
|
-
const decisionLogicalNodes = new WeakSet();
|
|
432
|
-
let usesRequestPhaseHandler = false;
|
|
433
|
-
// Instrumentation necessarily changes a function's source text. When code
|
|
434
|
-
// directly observes or coerces that source, keep the entire function body
|
|
435
|
-
// untouched and report the resulting coverage boundary explicitly.
|
|
436
|
-
const sourceSensitiveFunctions = new WeakSet();
|
|
437
|
-
traverse(ast, {
|
|
438
|
-
Function(path) {
|
|
439
|
-
if (!isSourceSensitiveFunction(path))
|
|
440
|
-
return;
|
|
441
|
-
sourceSensitiveFunctions.add(path.node);
|
|
442
|
-
if (!path.node.loc)
|
|
443
|
-
return;
|
|
444
|
-
limitations.push({
|
|
445
|
-
id: stableId(file, "semantic-safety", path.node, "function-source"),
|
|
446
|
-
kind: "semantic-safety",
|
|
447
|
-
file,
|
|
448
|
-
line: path.node.loc.start.line,
|
|
449
|
-
column: path.node.loc.start.column + 1,
|
|
450
|
-
source: sourceFor(code, path.node),
|
|
451
|
-
reason: "function body is left uninstrumented because this expression observes or coerces Function source text",
|
|
452
|
-
});
|
|
453
|
-
},
|
|
454
|
-
});
|
|
455
|
-
const isWithinSourceSensitiveFunction = (path) => (path.isFunction() && sourceSensitiveFunctions.has(path.node)) ||
|
|
456
|
-
Boolean(path.findParent((parent) => parent.isFunction() && sourceSensitiveFunctions.has(parent.node)));
|
|
457
|
-
traverse(ast, {
|
|
458
|
-
WithStatement(path) {
|
|
459
|
-
if (!path.node.loc)
|
|
460
|
-
return;
|
|
461
|
-
limitations.push({
|
|
462
|
-
id: stableId(file, "semantic-safety", path.node, "with-environment"),
|
|
463
|
-
kind: "semantic-safety",
|
|
464
|
-
file,
|
|
465
|
-
line: path.node.loc.start.line,
|
|
466
|
-
column: path.node.loc.start.column + 1,
|
|
467
|
-
source: sourceFor(code, path.node),
|
|
468
|
-
reason: "with-statement body is left uninstrumented because its object environment can intercept probe identifiers",
|
|
469
|
-
});
|
|
470
|
-
},
|
|
471
|
-
});
|
|
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
|
-
};
|
|
531
|
-
// Record executable statements before adding any instrumentation statements.
|
|
532
|
-
const instrumentedStatements = new WeakSet();
|
|
533
|
-
traverse(ast, {
|
|
534
|
-
Statement(path) {
|
|
535
|
-
const node = path.node;
|
|
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 ||
|
|
544
|
-
isUnsafeInstrumentationContext(path) ||
|
|
545
|
-
generatedStatements.has(node) ||
|
|
546
|
-
!isExecutableStatement(pointNode))
|
|
547
|
-
return;
|
|
548
|
-
if (path.parentPath.isLabeledStatement())
|
|
549
|
-
return;
|
|
550
|
-
const id = stableId(file, "statement", pointNode);
|
|
551
|
-
points.push({
|
|
552
|
-
id,
|
|
553
|
-
kind: "statement",
|
|
554
|
-
file,
|
|
555
|
-
line: pointNode.loc.start.line,
|
|
556
|
-
column: pointNode.loc.start.column + 1,
|
|
557
|
-
source: sourceFor(code, pointNode),
|
|
558
|
-
});
|
|
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);
|
|
568
|
-
generatedStatements.add(probe);
|
|
569
|
-
instrumentedStatements.add(node);
|
|
570
|
-
if (path.parentPath.isProgram() ||
|
|
571
|
-
path.parentPath.isBlockStatement() ||
|
|
572
|
-
path.parentPath.isSwitchCase()) {
|
|
573
|
-
path.insertBefore(probe);
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
// Bare control-flow bodies become blocks. This preserves dangling-else,
|
|
577
|
-
// break, continue, return, and throw semantics while giving the body an
|
|
578
|
-
// independently observable statement entry.
|
|
579
|
-
if (path.key === "body" ||
|
|
580
|
-
path.key === "consequent" ||
|
|
581
|
-
path.key === "alternate") {
|
|
582
|
-
path.replaceWith(t.blockStatement([probe, node]));
|
|
583
|
-
}
|
|
584
|
-
},
|
|
585
|
-
});
|
|
586
|
-
// Optional links are measured at the exact nullish operand. Unlike checking
|
|
587
|
-
// the chain's final value, this remains accurate when a successful property
|
|
588
|
-
// access or function call legitimately returns undefined.
|
|
589
|
-
const instrumentOptionalOperand = (path, operand) => {
|
|
590
|
-
const node = path.node;
|
|
591
|
-
if (!node.loc)
|
|
592
|
-
return operand;
|
|
593
|
-
const id = stableId(file, "optional-chain", node);
|
|
594
|
-
const shortId = `${id}:short`;
|
|
595
|
-
const continuedId = `${id}:continued`;
|
|
596
|
-
branches.push({
|
|
597
|
-
id,
|
|
598
|
-
kind: "optional-chain",
|
|
599
|
-
file,
|
|
600
|
-
line: node.loc.start.line,
|
|
601
|
-
column: node.loc.start.column + 1,
|
|
602
|
-
source: sourceFor(code, node),
|
|
603
|
-
alternatives: [
|
|
604
|
-
{ id: shortId, label: "nullish / short-circuited" },
|
|
605
|
-
{ id: continuedId, label: "non-nullish / continued" },
|
|
606
|
-
],
|
|
607
|
-
});
|
|
608
|
-
return t.callExpression(t.identifier(OPTIONAL_SELECT), [
|
|
609
|
-
t.stringLiteral(shortId),
|
|
610
|
-
t.stringLiteral(continuedId),
|
|
611
|
-
operand,
|
|
612
|
-
]);
|
|
613
|
-
};
|
|
614
|
-
traverse(ast, {
|
|
615
|
-
OptionalMemberExpression(path) {
|
|
616
|
-
if (isUnsafeInstrumentationContext(path))
|
|
617
|
-
return;
|
|
618
|
-
if (!path.node.optional || !t.isExpression(path.node.object))
|
|
619
|
-
return;
|
|
620
|
-
path.node.object = instrumentOptionalOperand(path, path.node.object);
|
|
621
|
-
},
|
|
622
|
-
});
|
|
623
|
-
const optionalCallChains = new Map();
|
|
624
|
-
traverse(ast, {
|
|
625
|
-
OptionalCallExpression(path) {
|
|
626
|
-
if (isUnsafeInstrumentationContext(path) ||
|
|
627
|
-
!path.node.optional ||
|
|
628
|
-
!path.node.loc ||
|
|
629
|
-
!t.isExpression(path.node.callee))
|
|
630
|
-
return;
|
|
631
|
-
const callee = path.node.callee;
|
|
632
|
-
if (t.isOptionalMemberExpression(callee) &&
|
|
633
|
-
t.isPrivateName(callee.property)) {
|
|
634
|
-
limitations.push({
|
|
635
|
-
id: stableId(file, "semantic-safety", path.node, "optional-private-call"),
|
|
636
|
-
kind: "semantic-safety",
|
|
637
|
-
file,
|
|
638
|
-
line: path.node.loc.start.line,
|
|
639
|
-
column: path.node.loc.start.column + 1,
|
|
640
|
-
source: sourceFor(code, path.node),
|
|
641
|
-
reason: "optional private-method calls are left native because private names cannot be wrapped as computed reference keys",
|
|
642
|
-
});
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
let root = path;
|
|
646
|
-
while ((root.parentPath.isOptionalCallExpression() &&
|
|
647
|
-
root.parentPath.node.callee === root.node) ||
|
|
648
|
-
(root.parentPath.isOptionalMemberExpression() &&
|
|
649
|
-
root.parentPath.node.object === root.node))
|
|
650
|
-
root = root.parentPath;
|
|
651
|
-
if (root.parentPath.isUnaryExpression({ operator: "delete" }) &&
|
|
652
|
-
root.parentPath.node.argument === root.node)
|
|
653
|
-
root = root.parentPath;
|
|
654
|
-
const id = stableId(file, "optional-chain", path.node, "call");
|
|
655
|
-
const shortId = `${id}:short`;
|
|
656
|
-
const continuedId = `${id}:continued`;
|
|
657
|
-
branches.push({
|
|
658
|
-
id,
|
|
659
|
-
kind: "optional-chain",
|
|
660
|
-
file,
|
|
661
|
-
line: path.node.loc.start.line,
|
|
662
|
-
column: path.node.loc.start.column + 1,
|
|
663
|
-
source: sourceFor(code, path.node),
|
|
664
|
-
alternatives: [
|
|
665
|
-
{ id: shortId, label: "nullish / short-circuited" },
|
|
666
|
-
{ id: continuedId, label: "non-nullish / continued" },
|
|
667
|
-
],
|
|
668
|
-
});
|
|
669
|
-
const chain = optionalCallChains.get(root.node) ?? {
|
|
670
|
-
path: root,
|
|
671
|
-
sites: [],
|
|
672
|
-
};
|
|
673
|
-
chain.sites.push({
|
|
674
|
-
node: path.node,
|
|
675
|
-
frame: root.scope.generateUidIdentifier("optionalCall"),
|
|
676
|
-
shortId,
|
|
677
|
-
continuedId,
|
|
678
|
-
});
|
|
679
|
-
optionalCallChains.set(root.node, chain);
|
|
680
|
-
},
|
|
681
|
-
});
|
|
682
|
-
for (const chain of optionalCallChains.values()) {
|
|
683
|
-
for (const site of chain.sites) {
|
|
684
|
-
chain.path.scope.push({ id: t.cloneNode(site.frame), kind: "let" });
|
|
685
|
-
const callee = site.node.callee;
|
|
686
|
-
if (t.isMemberExpression(callee) ||
|
|
687
|
-
t.isOptionalMemberExpression(callee)) {
|
|
688
|
-
if (t.isPrivateName(callee.property)) {
|
|
689
|
-
if (t.isExpression(callee.object))
|
|
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
|
-
])));
|
|
694
|
-
continue;
|
|
695
|
-
}
|
|
696
|
-
const property = callee.computed
|
|
697
|
-
? callee.property
|
|
698
|
-
: t.isIdentifier(callee.property)
|
|
699
|
-
? t.stringLiteral(callee.property.name)
|
|
700
|
-
: callee.property;
|
|
701
|
-
callee.computed = true;
|
|
702
|
-
callee.property = t.callExpression(t.identifier(OPTIONAL_CALL_REACHED), [t.cloneNode(site.frame), property]);
|
|
703
|
-
}
|
|
704
|
-
else {
|
|
705
|
-
site.node.callee = t.callExpression(t.identifier(OPTIONAL_CALL_REACHED), [t.cloneNode(site.frame), callee]);
|
|
706
|
-
}
|
|
707
|
-
site.node.arguments.unshift(t.spreadElement(t.callExpression(t.identifier(OPTIONAL_CALL_CONTINUED), [
|
|
708
|
-
t.cloneNode(site.frame),
|
|
709
|
-
])));
|
|
710
|
-
}
|
|
711
|
-
let measured = chain.path.node;
|
|
712
|
-
for (const site of [...chain.sites].reverse())
|
|
713
|
-
measured = t.callExpression(t.identifier(OPTIONAL_CALL_END), [
|
|
714
|
-
t.cloneNode(site.frame),
|
|
715
|
-
measured,
|
|
716
|
-
]);
|
|
717
|
-
chain.path.replaceWith(t.sequenceExpression([
|
|
718
|
-
...chain.sites.map((site) => t.assignmentExpression("=", t.cloneNode(site.frame), t.callExpression(t.identifier(OPTIONAL_CALL_BEGIN), [
|
|
719
|
-
t.stringLiteral(site.shortId),
|
|
720
|
-
t.stringLiteral(site.continuedId),
|
|
721
|
-
]))),
|
|
722
|
-
measured,
|
|
723
|
-
]));
|
|
724
|
-
}
|
|
725
|
-
// Logical assignments have the same short/right split as value-selection
|
|
726
|
-
// expressions, but wrapping only the RHS preserves one-time LHS evaluation.
|
|
727
|
-
traverse(ast, {
|
|
728
|
-
AssignmentExpression: {
|
|
729
|
-
exit(path) {
|
|
730
|
-
if (isUnsafeInstrumentationContext(path))
|
|
731
|
-
return;
|
|
732
|
-
const node = path.node;
|
|
733
|
-
if (!node.loc ||
|
|
734
|
-
(node.operator !== "&&=" &&
|
|
735
|
-
node.operator !== "||=" &&
|
|
736
|
-
node.operator !== "??="))
|
|
737
|
-
return;
|
|
738
|
-
const id = stableId(file, "logical-assignment", node, node.operator);
|
|
739
|
-
const shortId = `${id}:short`;
|
|
740
|
-
const rightId = `${id}:right`;
|
|
741
|
-
branches.push({
|
|
742
|
-
id,
|
|
743
|
-
kind: "logical-assignment",
|
|
744
|
-
file,
|
|
745
|
-
line: node.loc.start.line,
|
|
746
|
-
column: node.loc.start.column + 1,
|
|
747
|
-
source: sourceFor(code, node),
|
|
748
|
-
alternatives: [
|
|
749
|
-
{ id: shortId, label: "assignment skipped" },
|
|
750
|
-
{ id: rightId, label: "right evaluated / assigned" },
|
|
751
|
-
],
|
|
752
|
-
});
|
|
753
|
-
const frameId = path.scope.generateUidIdentifier("supercovSelectionFrame");
|
|
754
|
-
const frameScope = path.scope.getFunctionParent() ?? path.scope.getProgramParent();
|
|
755
|
-
frameScope.push({ id: t.cloneNode(frameId), kind: "let" });
|
|
756
|
-
const assignFrame = t.assignmentExpression("=", t.cloneNode(frameId), t.callExpression(t.identifier(SELECTION_BEGIN), [
|
|
757
|
-
t.stringLiteral(shortId),
|
|
758
|
-
t.stringLiteral(rightId),
|
|
759
|
-
]));
|
|
760
|
-
const assignment = t.assignmentExpression(node.operator, t.cloneNode(node.left), t.callExpression(t.identifier(SELECTION_RIGHT), [
|
|
761
|
-
t.cloneNode(frameId),
|
|
762
|
-
node.right,
|
|
763
|
-
...(t.isIdentifier(node.left) &&
|
|
764
|
-
!node.left.extra?.parenthesized &&
|
|
765
|
-
isAnonymousFunctionDefinition(node.right)
|
|
766
|
-
? [t.stringLiteral(node.left.name)]
|
|
767
|
-
: []),
|
|
768
|
-
]));
|
|
769
|
-
path.replaceWith(t.sequenceExpression([
|
|
770
|
-
assignFrame,
|
|
771
|
-
t.callExpression(t.identifier(SELECTION_END), [
|
|
772
|
-
t.cloneNode(frameId),
|
|
773
|
-
assignment,
|
|
774
|
-
]),
|
|
775
|
-
]));
|
|
776
|
-
path.skip();
|
|
777
|
-
},
|
|
778
|
-
},
|
|
779
|
-
});
|
|
780
|
-
// Parameter defaults execute before a function body. A tiny per-default
|
|
781
|
-
// token lets the body distinguish an evaluated default from a supplied
|
|
782
|
-
// value without comparing values or evaluating either expression twice.
|
|
783
|
-
traverse(ast, {
|
|
784
|
-
Function(path) {
|
|
785
|
-
if (isUnsafeInstrumentationContext(path))
|
|
786
|
-
return;
|
|
787
|
-
const entries = [];
|
|
788
|
-
const visitPattern = (pattern) => {
|
|
789
|
-
if (t.isTSParameterProperty(pattern)) {
|
|
790
|
-
visitPattern(pattern.parameter);
|
|
791
|
-
return;
|
|
792
|
-
}
|
|
793
|
-
if (t.isAssignmentPattern(pattern)) {
|
|
794
|
-
if (!pattern.loc)
|
|
795
|
-
return;
|
|
796
|
-
const id = stableId(file, "default-value", pattern);
|
|
797
|
-
const defaultId = `${id}:default`;
|
|
798
|
-
const providedId = `${id}:provided`;
|
|
799
|
-
branches.push({
|
|
800
|
-
id,
|
|
801
|
-
kind: "default-value",
|
|
802
|
-
file,
|
|
803
|
-
line: pattern.loc.start.line,
|
|
804
|
-
column: pattern.loc.start.column + 1,
|
|
805
|
-
source: sourceFor(code, pattern),
|
|
806
|
-
alternatives: [
|
|
807
|
-
{ id: defaultId, label: "default evaluated" },
|
|
808
|
-
{ id: providedId, label: "value provided" },
|
|
809
|
-
],
|
|
810
|
-
});
|
|
811
|
-
pattern.right = t.callExpression(t.identifier(DEFAULT_SELECTED), [
|
|
812
|
-
t.stringLiteral(defaultId),
|
|
813
|
-
pattern.right,
|
|
814
|
-
...(t.isIdentifier(pattern.left) &&
|
|
815
|
-
isAnonymousFunctionDefinition(pattern.right)
|
|
816
|
-
? [t.stringLiteral(pattern.left.name)]
|
|
817
|
-
: []),
|
|
818
|
-
]);
|
|
819
|
-
entries.push(t.expressionStatement(t.callExpression(t.identifier(DEFAULT_ENTERED), [
|
|
820
|
-
t.stringLiteral(defaultId),
|
|
821
|
-
t.stringLiteral(providedId),
|
|
822
|
-
])));
|
|
823
|
-
visitPattern(pattern.left);
|
|
824
|
-
return;
|
|
825
|
-
}
|
|
826
|
-
if (t.isRestElement(pattern))
|
|
827
|
-
return visitPattern(pattern.argument);
|
|
828
|
-
if (t.isObjectPattern(pattern)) {
|
|
829
|
-
for (const property of pattern.properties) {
|
|
830
|
-
if (t.isRestElement(property))
|
|
831
|
-
visitPattern(property.argument);
|
|
832
|
-
else
|
|
833
|
-
visitPattern(property.value);
|
|
834
|
-
}
|
|
835
|
-
return;
|
|
836
|
-
}
|
|
837
|
-
if (t.isArrayPattern(pattern)) {
|
|
838
|
-
for (const element of pattern.elements)
|
|
839
|
-
if (element)
|
|
840
|
-
visitPattern(element);
|
|
841
|
-
}
|
|
842
|
-
};
|
|
843
|
-
for (const parameter of path.node.params)
|
|
844
|
-
visitPattern(parameter);
|
|
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
|
-
]);
|
|
858
|
-
},
|
|
859
|
-
});
|
|
860
|
-
// Destructuring declarations use the same token protocol. For loop-binding
|
|
861
|
-
// declarations consume the token at the start of each iteration, directly
|
|
862
|
-
// after JavaScript has completed the binding operation.
|
|
863
|
-
traverse(ast, {
|
|
864
|
-
VariableDeclaration(path) {
|
|
865
|
-
if (isUnsafeInstrumentationContext(path))
|
|
866
|
-
return;
|
|
867
|
-
const parent = path.parentPath;
|
|
868
|
-
if (parent.isForStatement() && path.key === "init") {
|
|
869
|
-
let hasDefault = false;
|
|
870
|
-
t.traverseFast(path.node, (node) => {
|
|
871
|
-
if (t.isAssignmentPattern(node))
|
|
872
|
-
hasDefault = true;
|
|
873
|
-
});
|
|
874
|
-
const loc = path.node.loc;
|
|
875
|
-
if (hasDefault && loc) {
|
|
876
|
-
const node = path.node;
|
|
877
|
-
limitations.push({
|
|
878
|
-
id: stableId(file, "dynamic-code", node, "for-init-default"),
|
|
879
|
-
kind: "dynamic-code",
|
|
880
|
-
file,
|
|
881
|
-
line: loc.start.line,
|
|
882
|
-
column: loc.start.column + 1,
|
|
883
|
-
source: sourceFor(code, node),
|
|
884
|
-
reason: "destructuring defaults in a classic for initializer cannot yet be finalized without restructuring control flow",
|
|
885
|
-
});
|
|
886
|
-
}
|
|
887
|
-
return;
|
|
888
|
-
}
|
|
889
|
-
const entries = [];
|
|
890
|
-
const visitPattern = (pattern) => {
|
|
891
|
-
if (t.isAssignmentPattern(pattern)) {
|
|
892
|
-
if (!pattern.loc)
|
|
893
|
-
return;
|
|
894
|
-
const id = stableId(file, "default-value", pattern);
|
|
895
|
-
const defaultId = `${id}:default`;
|
|
896
|
-
const providedId = `${id}:provided`;
|
|
897
|
-
branches.push({
|
|
898
|
-
id,
|
|
899
|
-
kind: "default-value",
|
|
900
|
-
file,
|
|
901
|
-
line: pattern.loc.start.line,
|
|
902
|
-
column: pattern.loc.start.column + 1,
|
|
903
|
-
source: sourceFor(code, pattern),
|
|
904
|
-
alternatives: [
|
|
905
|
-
{ id: defaultId, label: "default evaluated" },
|
|
906
|
-
{ id: providedId, label: "value provided" },
|
|
907
|
-
],
|
|
908
|
-
});
|
|
909
|
-
pattern.right = t.callExpression(t.identifier(DEFAULT_SELECTED), [
|
|
910
|
-
t.stringLiteral(defaultId),
|
|
911
|
-
pattern.right,
|
|
912
|
-
...(t.isIdentifier(pattern.left) &&
|
|
913
|
-
isAnonymousFunctionDefinition(pattern.right)
|
|
914
|
-
? [t.stringLiteral(pattern.left.name)]
|
|
915
|
-
: []),
|
|
916
|
-
]);
|
|
917
|
-
entries.push(t.expressionStatement(t.callExpression(t.identifier(DEFAULT_ENTERED), [
|
|
918
|
-
t.stringLiteral(defaultId),
|
|
919
|
-
t.stringLiteral(providedId),
|
|
920
|
-
])));
|
|
921
|
-
visitPattern(pattern.left);
|
|
922
|
-
return;
|
|
923
|
-
}
|
|
924
|
-
if (t.isRestElement(pattern))
|
|
925
|
-
return visitPattern(pattern.argument);
|
|
926
|
-
if (t.isObjectPattern(pattern)) {
|
|
927
|
-
for (const property of pattern.properties)
|
|
928
|
-
visitPattern(t.isRestElement(property) ? property.argument : property.value);
|
|
929
|
-
return;
|
|
930
|
-
}
|
|
931
|
-
if (t.isArrayPattern(pattern)) {
|
|
932
|
-
for (const element of pattern.elements)
|
|
933
|
-
if (element)
|
|
934
|
-
visitPattern(element);
|
|
935
|
-
}
|
|
936
|
-
};
|
|
937
|
-
for (const declaration of path.node.declarations)
|
|
938
|
-
visitPattern(declaration.id);
|
|
939
|
-
if (entries.length === 0)
|
|
940
|
-
return;
|
|
941
|
-
if ((parent.isForOfStatement() || parent.isForInStatement()) &&
|
|
942
|
-
path.key === "left") {
|
|
943
|
-
const body = parent.node.body;
|
|
944
|
-
parent.node.body = t.isBlockStatement(body)
|
|
945
|
-
? body
|
|
946
|
-
: t.blockStatement([body]);
|
|
947
|
-
parent.node.body.body.unshift(...entries);
|
|
948
|
-
return;
|
|
949
|
-
}
|
|
950
|
-
if (parent.isExportNamedDeclaration())
|
|
951
|
-
parent.insertAfter(entries);
|
|
952
|
-
else
|
|
953
|
-
path.insertAfter(entries);
|
|
954
|
-
},
|
|
955
|
-
});
|
|
956
|
-
// Try/catch and enumeration loops use frames finalized in `finally`, so
|
|
957
|
-
// return, break, continue, rejection, and throw paths remain observable.
|
|
958
|
-
const tryTargets = new WeakMap();
|
|
959
|
-
const enumerationTargets = new WeakMap();
|
|
960
|
-
traverse(ast, {
|
|
961
|
-
TryStatement(path) {
|
|
962
|
-
if (isUnsafeInstrumentationContext(path))
|
|
963
|
-
return;
|
|
964
|
-
const node = path.node;
|
|
965
|
-
if (!node.loc || !node.handler)
|
|
966
|
-
return;
|
|
967
|
-
const id = stableId(file, "try-catch", node);
|
|
968
|
-
const successId = `${id}:success`;
|
|
969
|
-
const catchId = `${id}:catch`;
|
|
970
|
-
branches.push({
|
|
971
|
-
id,
|
|
972
|
-
kind: "try-catch",
|
|
973
|
-
file,
|
|
974
|
-
line: node.loc.start.line,
|
|
975
|
-
column: node.loc.start.column + 1,
|
|
976
|
-
source: "try / catch",
|
|
977
|
-
alternatives: [
|
|
978
|
-
{ id: successId, label: "try completed without catch" },
|
|
979
|
-
{ id: catchId, label: "catch entered" },
|
|
980
|
-
],
|
|
981
|
-
});
|
|
982
|
-
tryTargets.set(node, { successId, catchId });
|
|
983
|
-
},
|
|
984
|
-
"ForInStatement|ForOfStatement"(path) {
|
|
985
|
-
if (isUnsafeInstrumentationContext(path))
|
|
986
|
-
return;
|
|
987
|
-
const node = path.node;
|
|
988
|
-
if (!node.loc)
|
|
989
|
-
return;
|
|
990
|
-
const kind = t.isForOfStatement(node) ? "for-of" : "for-in";
|
|
991
|
-
const id = stableId(file, kind, node);
|
|
992
|
-
const zeroId = `${id}:zero`;
|
|
993
|
-
const enteredId = `${id}:entered`;
|
|
994
|
-
branches.push({
|
|
995
|
-
id,
|
|
996
|
-
kind,
|
|
997
|
-
file,
|
|
998
|
-
line: node.loc.start.line,
|
|
999
|
-
column: node.loc.start.column + 1,
|
|
1000
|
-
source: sourceFor(code, node.right),
|
|
1001
|
-
alternatives: [
|
|
1002
|
-
{ id: zeroId, label: "zero iterations" },
|
|
1003
|
-
{ id: enteredId, label: "one or more iterations" },
|
|
1004
|
-
],
|
|
1005
|
-
});
|
|
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]);
|
|
1038
|
-
path.skip();
|
|
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
|
-
},
|
|
1086
|
-
},
|
|
1087
|
-
});
|
|
1088
|
-
// Function entry is separate from statement coverage so empty functions and
|
|
1089
|
-
// expression-bodied arrows remain visible in the completeness denominator.
|
|
1090
|
-
traverse(ast, {
|
|
1091
|
-
Function(path) {
|
|
1092
|
-
const node = path.node;
|
|
1093
|
-
if (!node.loc || !node.body)
|
|
1094
|
-
return;
|
|
1095
|
-
if (isUnsafeInstrumentationContext(path))
|
|
1096
|
-
return;
|
|
1097
|
-
const id = stableId(file, "function", node);
|
|
1098
|
-
points.push({
|
|
1099
|
-
id,
|
|
1100
|
-
kind: "function",
|
|
1101
|
-
file,
|
|
1102
|
-
line: node.loc.start.line,
|
|
1103
|
-
column: node.loc.start.column + 1,
|
|
1104
|
-
source: sourceFor(code, node),
|
|
1105
|
-
...(functionLabel(path) ? { label: functionLabel(path) } : {}),
|
|
1106
|
-
});
|
|
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);
|
|
1115
|
-
generatedStatements.add(probe);
|
|
1116
|
-
if (t.isBlockStatement(node.body)) {
|
|
1117
|
-
node.body.body.unshift(probe);
|
|
1118
|
-
}
|
|
1119
|
-
else {
|
|
1120
|
-
node.body = t.blockStatement([probe, t.returnStatement(node.body)]);
|
|
1121
|
-
}
|
|
1122
|
-
},
|
|
1123
|
-
});
|
|
1124
|
-
const instrumentDecision = (path, kind) => {
|
|
1125
|
-
if (!path.node.loc)
|
|
1126
|
-
return;
|
|
1127
|
-
const originalConditions = [];
|
|
1128
|
-
collectConditions(path.node, originalConditions);
|
|
1129
|
-
if (originalConditions.length === 0)
|
|
1130
|
-
return;
|
|
1131
|
-
const id = stableId(file, "decision", path.node, kind);
|
|
1132
|
-
const meta = {
|
|
1133
|
-
id,
|
|
1134
|
-
file,
|
|
1135
|
-
line: path.node.loc.start.line,
|
|
1136
|
-
column: path.node.loc.start.column + 1,
|
|
1137
|
-
source: sourceFor(code, path.node),
|
|
1138
|
-
conditions: originalConditions.map((condition) => sourceFor(code, condition)),
|
|
1139
|
-
kind,
|
|
1140
|
-
};
|
|
1141
|
-
const decisionIndex = decisions.length;
|
|
1142
|
-
decisions.push(meta);
|
|
1143
|
-
// A loop predicate's path scope may be represented by Babel as the loop
|
|
1144
|
-
// body's block. Declaring the frame there puts it after the predicate that
|
|
1145
|
-
// uses it (and is especially visible in async generators). Hoist scratch
|
|
1146
|
-
// frames to the nearest function/program scope instead.
|
|
1147
|
-
const inlineFrame = isInsideFunctionParameter(path);
|
|
1148
|
-
const evaluationScope = (path.isFunctionExpression() ||
|
|
1149
|
-
path.isArrowFunctionExpression() ||
|
|
1150
|
-
path.isClassExpression()) &&
|
|
1151
|
-
path.scope.parent
|
|
1152
|
-
? path.scope.parent
|
|
1153
|
-
: path.scope;
|
|
1154
|
-
const frameScope = evaluationScope.getFunctionParent() ?? evaluationScope.getProgramParent();
|
|
1155
|
-
const frameId = frameScope.generateUidIdentifier("supercovMcdcFrame");
|
|
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) {
|
|
1180
|
-
frameScope.push({ id: t.cloneNode(frameId), kind: "let" });
|
|
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);
|
|
1191
|
-
const begin = t.callExpression(t.identifier(BEGIN), [
|
|
1192
|
-
t.stringLiteral(id),
|
|
1193
|
-
t.valueToNode(meta),
|
|
1194
|
-
]);
|
|
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
|
|
1222
|
-
? t.callExpression(t.arrowFunctionExpression([], t.blockStatement([
|
|
1223
|
-
t.variableDeclaration("let", [
|
|
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
|
-
: []),
|
|
1232
|
-
]),
|
|
1233
|
-
t.returnStatement(useV2 ? t.sequenceExpression(v2Tail) : end),
|
|
1234
|
-
])), [])
|
|
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);
|
|
1247
|
-
path.skip();
|
|
1248
|
-
};
|
|
1249
|
-
traverse(ast, {
|
|
1250
|
-
IfStatement(path) {
|
|
1251
|
-
if (isUnsafeInstrumentationContext(path))
|
|
1252
|
-
return;
|
|
1253
|
-
instrumentDecision(path.get("test"), "if");
|
|
1254
|
-
},
|
|
1255
|
-
ConditionalExpression(path) {
|
|
1256
|
-
if (isUnsafeInstrumentationContext(path))
|
|
1257
|
-
return;
|
|
1258
|
-
instrumentDecision(path.get("test"), "ternary");
|
|
1259
|
-
},
|
|
1260
|
-
WhileStatement(path) {
|
|
1261
|
-
if (isUnsafeInstrumentationContext(path))
|
|
1262
|
-
return;
|
|
1263
|
-
instrumentDecision(path.get("test"), "while");
|
|
1264
|
-
},
|
|
1265
|
-
DoWhileStatement(path) {
|
|
1266
|
-
if (isUnsafeInstrumentationContext(path))
|
|
1267
|
-
return;
|
|
1268
|
-
instrumentDecision(path.get("test"), "do-while");
|
|
1269
|
-
},
|
|
1270
|
-
ForStatement(path) {
|
|
1271
|
-
if (isUnsafeInstrumentationContext(path))
|
|
1272
|
-
return;
|
|
1273
|
-
const test = path.get("test");
|
|
1274
|
-
if (test.node)
|
|
1275
|
-
instrumentDecision(test, "for");
|
|
1276
|
-
},
|
|
1277
|
-
});
|
|
1278
|
-
// A logical expression outside a control predicate selects a value or
|
|
1279
|
-
// render path. Record whether it short-circuited or evaluated its RHS;
|
|
1280
|
-
// Boolean MC/DC would be misleading when both selected values are truthy.
|
|
1281
|
-
traverse(ast, {
|
|
1282
|
-
LogicalExpression: {
|
|
1283
|
-
exit(path) {
|
|
1284
|
-
if (isUnsafeInstrumentationContext(path))
|
|
1285
|
-
return;
|
|
1286
|
-
const node = path.node;
|
|
1287
|
-
if (!node.loc || decisionLogicalNodes.has(node))
|
|
1288
|
-
return;
|
|
1289
|
-
const id = stableId(file, "logical-value", node, node.operator);
|
|
1290
|
-
const shortId = `${id}:short`;
|
|
1291
|
-
const rightId = `${id}:right`;
|
|
1292
|
-
branches.push({
|
|
1293
|
-
id,
|
|
1294
|
-
kind: "logical-value",
|
|
1295
|
-
file,
|
|
1296
|
-
line: node.loc.start.line,
|
|
1297
|
-
column: node.loc.start.column + 1,
|
|
1298
|
-
source: sourceFor(code, node),
|
|
1299
|
-
alternatives: [
|
|
1300
|
-
{ id: shortId, label: "short-circuit / left selected" },
|
|
1301
|
-
{ id: rightId, label: "right evaluated / selected" },
|
|
1302
|
-
],
|
|
1303
|
-
});
|
|
1304
|
-
const frameId = path.scope.generateUidIdentifier("supercovSelectionFrame");
|
|
1305
|
-
const frameScope = path.scope.getFunctionParent() ?? path.scope.getProgramParent();
|
|
1306
|
-
frameScope.push({ id: t.cloneNode(frameId), kind: "let" });
|
|
1307
|
-
const begin = t.callExpression(t.identifier(SELECTION_BEGIN), [
|
|
1308
|
-
t.stringLiteral(shortId),
|
|
1309
|
-
t.stringLiteral(rightId),
|
|
1310
|
-
]);
|
|
1311
|
-
const assign = t.assignmentExpression("=", t.cloneNode(frameId), begin);
|
|
1312
|
-
const right = t.callExpression(t.identifier(SELECTION_RIGHT), [
|
|
1313
|
-
t.cloneNode(frameId),
|
|
1314
|
-
node.right,
|
|
1315
|
-
]);
|
|
1316
|
-
const selection = t.logicalExpression(node.operator, node.left, right);
|
|
1317
|
-
const end = t.callExpression(t.identifier(SELECTION_END), [
|
|
1318
|
-
t.cloneNode(frameId),
|
|
1319
|
-
selection,
|
|
1320
|
-
]);
|
|
1321
|
-
path.replaceWith(t.sequenceExpression([assign, end]));
|
|
1322
|
-
path.skip();
|
|
1323
|
-
},
|
|
1324
|
-
},
|
|
1325
|
-
});
|
|
1326
|
-
// Switch alternatives are observable independently of whether their bodies
|
|
1327
|
-
// are empty or fall through to another case. An implicit no-match probe must
|
|
1328
|
-
// live after the switch rather than in a synthetic default: a matched case
|
|
1329
|
-
// can legally fall through to the end and must not be counted as no-match.
|
|
1330
|
-
traverse(ast, {
|
|
1331
|
-
SwitchStatement(path) {
|
|
1332
|
-
if (isUnsafeInstrumentationContext(path))
|
|
1333
|
-
return;
|
|
1334
|
-
const node = path.node;
|
|
1335
|
-
if (!node.loc)
|
|
1336
|
-
return;
|
|
1337
|
-
const id = stableId(file, "switch", node);
|
|
1338
|
-
const hasDefault = node.cases.some((switchCase) => switchCase.test === null);
|
|
1339
|
-
const enteredId = hasDefault
|
|
1340
|
-
? undefined
|
|
1341
|
-
: path.scope.generateUidIdentifier("supercovSwitchEntered");
|
|
1342
|
-
const alternatives = node.cases.map((switchCase, index) => {
|
|
1343
|
-
const alternativeId = `${id}:case:${index}`;
|
|
1344
|
-
const label = switchCase.test
|
|
1345
|
-
? `case ${sourceFor(code, switchCase.test)}`
|
|
1346
|
-
: "default";
|
|
1347
|
-
const probe = hitStatement(alternativeId, HIT);
|
|
1348
|
-
generatedStatements.add(probe);
|
|
1349
|
-
switchCase.consequent.unshift(...(enteredId
|
|
1350
|
-
? [
|
|
1351
|
-
t.expressionStatement(t.assignmentExpression("=", t.cloneNode(enteredId), t.booleanLiteral(true))),
|
|
1352
|
-
]
|
|
1353
|
-
: []), probe);
|
|
1354
|
-
return { id: alternativeId, label };
|
|
1355
|
-
});
|
|
1356
|
-
let noMatchId;
|
|
1357
|
-
if (!hasDefault) {
|
|
1358
|
-
const alternativeId = `${id}:no-match`;
|
|
1359
|
-
noMatchId = alternativeId;
|
|
1360
|
-
alternatives.push({
|
|
1361
|
-
id: alternativeId,
|
|
1362
|
-
label: "no matching case",
|
|
1363
|
-
});
|
|
1364
|
-
}
|
|
1365
|
-
branches.push({
|
|
1366
|
-
id,
|
|
1367
|
-
kind: "switch",
|
|
1368
|
-
file,
|
|
1369
|
-
line: node.loc.start.line,
|
|
1370
|
-
column: node.loc.start.column + 1,
|
|
1371
|
-
source: sourceFor(code, node.discriminant),
|
|
1372
|
-
alternatives,
|
|
1373
|
-
});
|
|
1374
|
-
if (!enteredId || !noMatchId)
|
|
1375
|
-
return;
|
|
1376
|
-
let switchStatement = node;
|
|
1377
|
-
let replacementPath = path;
|
|
1378
|
-
while (replacementPath.parentPath?.isLabeledStatement() &&
|
|
1379
|
-
replacementPath.parentPath.node.body === replacementPath.node) {
|
|
1380
|
-
switchStatement = t.labeledStatement(t.cloneNode(replacementPath.parentPath.node.label), switchStatement);
|
|
1381
|
-
replacementPath = replacementPath.parentPath;
|
|
1382
|
-
}
|
|
1383
|
-
const noMatchProbe = hitStatement(noMatchId, HIT);
|
|
1384
|
-
generatedStatements.add(noMatchProbe);
|
|
1385
|
-
replacementPath.replaceWith(t.blockStatement([
|
|
1386
|
-
t.variableDeclaration("let", [
|
|
1387
|
-
t.variableDeclarator(t.cloneNode(enteredId), t.booleanLiteral(false)),
|
|
1388
|
-
]),
|
|
1389
|
-
switchStatement,
|
|
1390
|
-
t.ifStatement(t.unaryExpression("!", t.cloneNode(enteredId)), noMatchProbe),
|
|
1391
|
-
]));
|
|
1392
|
-
replacementPath.skip();
|
|
1393
|
-
},
|
|
1394
|
-
});
|
|
1395
|
-
// Runtime-generated source cannot be assigned a truthful static
|
|
1396
|
-
// denominator without parsing and instrumenting the generated program in
|
|
1397
|
-
// its own execution realm. Discover it and block a completeness verdict
|
|
1398
|
-
// instead of silently claiming 100% for only the surrounding file.
|
|
1399
|
-
traverse(ast, {
|
|
1400
|
-
CallExpression(path) {
|
|
1401
|
-
if (isUnsafeInstrumentationContext(path))
|
|
1402
|
-
return;
|
|
1403
|
-
const callee = path.node.callee;
|
|
1404
|
-
if (!path.node.loc || !t.isIdentifier(callee, { name: "eval" }))
|
|
1405
|
-
return;
|
|
1406
|
-
limitations.push({
|
|
1407
|
-
id: stableId(file, "dynamic-code", path.node, "eval"),
|
|
1408
|
-
kind: "dynamic-code",
|
|
1409
|
-
file,
|
|
1410
|
-
line: path.node.loc.start.line,
|
|
1411
|
-
column: path.node.loc.start.column + 1,
|
|
1412
|
-
source: sourceFor(code, path.node),
|
|
1413
|
-
reason: "eval-generated source has no stable pre-run coverage denominator",
|
|
1414
|
-
});
|
|
1415
|
-
},
|
|
1416
|
-
NewExpression(path) {
|
|
1417
|
-
if (isUnsafeInstrumentationContext(path))
|
|
1418
|
-
return;
|
|
1419
|
-
if (!path.node.loc ||
|
|
1420
|
-
!t.isIdentifier(path.node.callee, { name: "Function" }))
|
|
1421
|
-
return;
|
|
1422
|
-
limitations.push({
|
|
1423
|
-
id: stableId(file, "dynamic-code", path.node, "Function"),
|
|
1424
|
-
kind: "dynamic-code",
|
|
1425
|
-
file,
|
|
1426
|
-
line: path.node.loc.start.line,
|
|
1427
|
-
column: path.node.loc.start.column + 1,
|
|
1428
|
-
source: sourceFor(code, path.node),
|
|
1429
|
-
reason: "Function-generated source has no stable pre-run coverage denominator",
|
|
1430
|
-
});
|
|
1431
|
-
},
|
|
1432
|
-
});
|
|
1433
|
-
// Route requests carry the Playwright phase as a private test-only header.
|
|
1434
|
-
// Wrap Remix request entry points so Node's AsyncLocalStorage can propagate
|
|
1435
|
-
// that exact phase through every awaited helper without application edits.
|
|
1436
|
-
// This runs after source instrumentation so generated wrappers never become
|
|
1437
|
-
// coverage obligations themselves.
|
|
1438
|
-
if (/^app\/routes\//.test(file) ||
|
|
1439
|
-
/(?:^|\/)app\/.*\/route\.[cm]?[jt]sx?$/.test(file)) {
|
|
1440
|
-
traverse(ast, {
|
|
1441
|
-
ExportNamedDeclaration(path) {
|
|
1442
|
-
const declaration = path.node.declaration;
|
|
1443
|
-
if (t.isVariableDeclaration(declaration)) {
|
|
1444
|
-
for (const declarator of declaration.declarations) {
|
|
1445
|
-
if (t.isIdentifier(declarator.id) &&
|
|
1446
|
-
isRequestHandlerName(file, declarator.id.name) &&
|
|
1447
|
-
declarator.init) {
|
|
1448
|
-
declarator.init = t.callExpression(t.identifier(WITH_REQUEST_PHASE), [declarator.init]);
|
|
1449
|
-
usesRequestPhaseHandler = true;
|
|
1450
|
-
}
|
|
1451
|
-
}
|
|
1452
|
-
return;
|
|
1453
|
-
}
|
|
1454
|
-
if (t.isFunctionDeclaration(declaration) &&
|
|
1455
|
-
declaration.id &&
|
|
1456
|
-
isRequestHandlerName(file, declaration.id.name)) {
|
|
1457
|
-
const exportedName = declaration.id.name;
|
|
1458
|
-
const originalId = path.scope.generateUidIdentifier(`${exportedName}CoverageOriginal`);
|
|
1459
|
-
declaration.id = originalId;
|
|
1460
|
-
const wrappedExport = t.exportNamedDeclaration(t.variableDeclaration("const", [
|
|
1461
|
-
t.variableDeclarator(t.identifier(exportedName), t.callExpression(t.identifier(WITH_REQUEST_PHASE), [
|
|
1462
|
-
t.cloneNode(originalId),
|
|
1463
|
-
])),
|
|
1464
|
-
]));
|
|
1465
|
-
path.replaceWithMultiple([declaration, wrappedExport]);
|
|
1466
|
-
usesRequestPhaseHandler = true;
|
|
1467
|
-
return;
|
|
1468
|
-
}
|
|
1469
|
-
if (!path.node.source)
|
|
1470
|
-
return;
|
|
1471
|
-
const handlerSpecifiers = path.node.specifiers.filter((specifier) => t.isExportSpecifier(specifier) &&
|
|
1472
|
-
t.isIdentifier(specifier.exported) &&
|
|
1473
|
-
isRequestHandlerName(file, specifier.exported.name));
|
|
1474
|
-
if (handlerSpecifiers.length === 0)
|
|
1475
|
-
return;
|
|
1476
|
-
const replacements = [];
|
|
1477
|
-
const untouched = path.node.specifiers.filter((specifier) => !handlerSpecifiers.includes(specifier));
|
|
1478
|
-
if (untouched.length > 0) {
|
|
1479
|
-
replacements.push(t.exportNamedDeclaration(null, untouched, t.cloneNode(path.node.source)));
|
|
1480
|
-
}
|
|
1481
|
-
for (const specifier of handlerSpecifiers) {
|
|
1482
|
-
const exportedName = specifier.exported.name;
|
|
1483
|
-
const importedId = path.scope.generateUidIdentifier(`${exportedName}CoverageOriginal`);
|
|
1484
|
-
replacements.push(t.importDeclaration([
|
|
1485
|
-
t.importSpecifier(t.cloneNode(importedId), t.cloneNode(specifier.local)),
|
|
1486
|
-
], t.cloneNode(path.node.source)), t.exportNamedDeclaration(t.variableDeclaration("const", [
|
|
1487
|
-
t.variableDeclarator(t.identifier(exportedName), t.callExpression(t.identifier(WITH_REQUEST_PHASE), [
|
|
1488
|
-
t.cloneNode(importedId),
|
|
1489
|
-
])),
|
|
1490
|
-
])));
|
|
1491
|
-
}
|
|
1492
|
-
path.replaceWithMultiple(replacements);
|
|
1493
|
-
usesRequestPhaseHandler = true;
|
|
1494
|
-
},
|
|
1495
|
-
});
|
|
1496
|
-
}
|
|
1497
|
-
if (/^app\/entry\.server\.[cm]?[jt]sx?$/.test(file)) {
|
|
1498
|
-
traverse(ast, {
|
|
1499
|
-
ExportDefaultDeclaration(path) {
|
|
1500
|
-
const declaration = path.node.declaration;
|
|
1501
|
-
if (t.isFunctionDeclaration(declaration)) {
|
|
1502
|
-
const originalId = path.scope.generateUidIdentifier("handleRequestCoverageOriginal");
|
|
1503
|
-
declaration.id = originalId;
|
|
1504
|
-
path.replaceWithMultiple([
|
|
1505
|
-
declaration,
|
|
1506
|
-
t.exportDefaultDeclaration(t.callExpression(t.identifier(WITH_REQUEST_PHASE), [
|
|
1507
|
-
t.cloneNode(originalId),
|
|
1508
|
-
])),
|
|
1509
|
-
]);
|
|
1510
|
-
}
|
|
1511
|
-
else {
|
|
1512
|
-
path.node.declaration = t.callExpression(t.identifier(WITH_REQUEST_PHASE), [declaration]);
|
|
1513
|
-
}
|
|
1514
|
-
usesRequestPhaseHandler = true;
|
|
1515
|
-
},
|
|
1516
|
-
});
|
|
1517
|
-
}
|
|
1518
|
-
// HTTP servers and WebSocket libraries expose request-bearing callbacks
|
|
1519
|
-
// outside framework route exports. Wrap well-known listener boundaries and
|
|
1520
|
-
// let the runtime scan callback arguments for Fetch or Node request headers.
|
|
1521
|
-
traverse(ast, {
|
|
1522
|
-
CallExpression(path) {
|
|
1523
|
-
if (isUnsafeInstrumentationContext(path))
|
|
1524
|
-
return;
|
|
1525
|
-
const callee = path.node.callee;
|
|
1526
|
-
const property = (t.isMemberExpression(callee) ||
|
|
1527
|
-
t.isOptionalMemberExpression(callee)) &&
|
|
1528
|
-
!callee.computed &&
|
|
1529
|
-
t.isIdentifier(callee.property)
|
|
1530
|
-
? callee.property.name
|
|
1531
|
-
: undefined;
|
|
1532
|
-
const identifier = t.isIdentifier(callee) ? callee.name : property;
|
|
1533
|
-
let callbackIndex = -1;
|
|
1534
|
-
if ((property === "on" ||
|
|
1535
|
-
property === "once" ||
|
|
1536
|
-
property === "addListener") &&
|
|
1537
|
-
t.isStringLiteral(path.node.arguments[0]) &&
|
|
1538
|
-
["request", "upgrade", "connection"].includes(path.node.arguments[0].value)) {
|
|
1539
|
-
callbackIndex = 1;
|
|
1540
|
-
}
|
|
1541
|
-
else if (identifier === "createServer") {
|
|
1542
|
-
for (let index = path.node.arguments.length - 1; index >= 0; index -= 1) {
|
|
1543
|
-
const argument = path.node.arguments[index];
|
|
1544
|
-
if (t.isFunctionExpression(argument) ||
|
|
1545
|
-
t.isArrowFunctionExpression(argument) ||
|
|
1546
|
-
t.isIdentifier(argument) ||
|
|
1547
|
-
t.isMemberExpression(argument)) {
|
|
1548
|
-
callbackIndex = index;
|
|
1549
|
-
break;
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
if (callbackIndex < 0)
|
|
1554
|
-
return;
|
|
1555
|
-
const callback = path.node.arguments[callbackIndex];
|
|
1556
|
-
if (!callback || !t.isExpression(callback))
|
|
1557
|
-
return;
|
|
1558
|
-
if (t.isCallExpression(callback) &&
|
|
1559
|
-
t.isIdentifier(callback.callee, { name: WITH_REQUEST_PHASE }))
|
|
1560
|
-
return;
|
|
1561
|
-
path.node.arguments[callbackIndex] = t.callExpression(t.identifier(WITH_REQUEST_PHASE), [callback]);
|
|
1562
|
-
usesRequestPhaseHandler = true;
|
|
1563
|
-
},
|
|
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
|
-
]));
|
|
1572
|
-
const manifest = {
|
|
1573
|
-
decisions,
|
|
1574
|
-
points,
|
|
1575
|
-
branches,
|
|
1576
|
-
...(limitations.length > 0 ? { limitations } : {}),
|
|
1577
|
-
};
|
|
1578
|
-
if (decisions.length > 0 ||
|
|
1579
|
-
points.length > 0 ||
|
|
1580
|
-
branches.length > 0 ||
|
|
1581
|
-
usesRequestPhaseHandler) {
|
|
1582
|
-
ast.program.body.unshift(t.importDeclaration([
|
|
1583
|
-
t.importSpecifier(t.identifier(BEGIN), t.identifier("mcdcBegin")),
|
|
1584
|
-
t.importSpecifier(t.identifier(CONDITION), t.identifier("mcdcCondition")),
|
|
1585
|
-
t.importSpecifier(t.identifier(END), t.identifier("mcdcEnd")),
|
|
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
|
-
: []),
|
|
1594
|
-
t.importSpecifier(t.identifier(SELECTION_BEGIN), t.identifier("selectionBegin")),
|
|
1595
|
-
t.importSpecifier(t.identifier(SELECTION_RIGHT), t.identifier("selectionRight")),
|
|
1596
|
-
t.importSpecifier(t.identifier(SELECTION_END), t.identifier("selectionEnd")),
|
|
1597
|
-
t.importSpecifier(t.identifier(OPTIONAL_SELECT), t.identifier("optionalSelect")),
|
|
1598
|
-
t.importSpecifier(t.identifier(OPTIONAL_CALL_BEGIN), t.identifier("optionalCallBegin")),
|
|
1599
|
-
t.importSpecifier(t.identifier(OPTIONAL_CALL_REACHED), t.identifier("optionalCallReached")),
|
|
1600
|
-
t.importSpecifier(t.identifier(OPTIONAL_CALL_CONTINUED), t.identifier("optionalCallContinued")),
|
|
1601
|
-
t.importSpecifier(t.identifier(OPTIONAL_CALL_END), t.identifier("optionalCallEnd")),
|
|
1602
|
-
t.importSpecifier(t.identifier(DEFAULT_SELECTED), t.identifier("defaultSelected")),
|
|
1603
|
-
t.importSpecifier(t.identifier(DEFAULT_ENTERED), t.identifier("defaultEntered")),
|
|
1604
|
-
t.importSpecifier(t.identifier(TRY_BEGIN), t.identifier("tryBegin")),
|
|
1605
|
-
t.importSpecifier(t.identifier(TRY_CATCH), t.identifier("tryCatch")),
|
|
1606
|
-
t.importSpecifier(t.identifier(TRY_END), t.identifier("tryEnd")),
|
|
1607
|
-
t.importSpecifier(t.identifier(LOOP_BEGIN), t.identifier("loopBegin")),
|
|
1608
|
-
t.importSpecifier(t.identifier(LOOP_ENTERED), t.identifier("loopEntered")),
|
|
1609
|
-
t.importSpecifier(t.identifier(LOOP_END), t.identifier("loopEnd")),
|
|
1610
|
-
...(usesRequestPhaseHandler
|
|
1611
|
-
? [
|
|
1612
|
-
t.importSpecifier(t.identifier(WITH_REQUEST_PHASE), t.identifier("withRequestPhase")),
|
|
1613
|
-
]
|
|
1614
|
-
: []),
|
|
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
|
-
: []));
|
|
1632
|
-
}
|
|
1633
|
-
const output = generate({
|
|
1634
|
-
...ast,
|
|
1635
|
-
}, {
|
|
1636
|
-
sourceMaps: true,
|
|
1637
|
-
sourceFileName: file,
|
|
1638
|
-
retainLines: true,
|
|
1639
|
-
comments: true,
|
|
1640
|
-
}, code);
|
|
1641
|
-
return { code: output.code, map: output.map, manifest, decisions };
|
|
1642
|
-
}
|
|
1643
|
-
export const mcdcRuntimeModuleId = RUNTIME_MODULE;
|
|
1644
|
-
//# sourceMappingURL=instrumenter.js.map
|