Skip to content

Commit 17ab1c0

Browse files
committed
feat(visitors): support blockless if-statements
- `if (true); else if (false); else;` - empty statement branch - `if (true) true; else if (false) false; else null;` - blockless statements
1 parent 90d03a3 commit 17ab1c0

File tree

5 files changed

+83
-15
lines changed

5 files changed

+83
-15
lines changed

src/instrumenters.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,26 @@ export function instrumentClassProperty(path, state, tags = ['statement', 'prope
5151
const marker = createMarker(state, {loc, tags}, node);
5252
value.replaceWith(marker);
5353
}
54+
55+
export function instrumentConditionBranch(path, state, tags = ['branch']) {
56+
if (path.isBlockStatement()) {
57+
// before: if (true) {};
58+
// after: if (true) { instrument(0); };
59+
instrumentBlock('body', path, state, tags);
60+
} else if (path.isEmptyStatement()) {
61+
// before: if (true);
62+
// after: if (true) instrument(0);
63+
const marker = createMarker(state, {loc: path.node.loc, tags});
64+
path.replaceWith(markAsInstrumented(
65+
t.expressionStatement(marker)
66+
));
67+
} else {
68+
// before: if (true) true;
69+
// after: if (true) { instrument(0); true; }
70+
const branchBody = t.blockStatement([path.node]);
71+
const locEnd = path.node.loc.end;
72+
branchBody.loc = {start: locEnd, end: locEnd};
73+
path.replaceWith(markAsInstrumented(branchBody));
74+
instrumentBlock('body', path, state, tags);
75+
}
76+
}

src/visitors.js

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
instrumentExpression,
77
instrumentObjectProperty,
88
instrumentClassProperty,
9+
instrumentConditionBranch,
910
isInstrumentableStatement
1011
} from './instrumenters';
1112

@@ -111,25 +112,23 @@ export default safeguardVisitors({
111112
// Source: if (true) {}
112113
// Instrumented: ++count; if (++count, true) { ++count; } else { ++count; }
113114
IfStatement(path, state) {
114-
// There is always a "consequent" branch:
115-
instrumentBlock('body', path.get('consequent'), state, ['branch']);
116-
117-
// An "alternate" branch may exist:
118-
const alternate = path.get('alternate');
119-
if (alternate.isBlockStatement()) {
120-
instrumentBlock('body', path.get('alternate'), state, ['branch']);
121-
} else if (!alternate.isIfStatement()) {
122-
const body = t.blockStatement([]);
123-
const locEnd = path.node.loc.end;
124-
body.loc = {start: locEnd, end: locEnd};
125-
alternate.replaceWith(body);
126-
instrumentBlock('body', alternate, state, ['branch']);
127-
}
128-
129115
// We want to instrument entire if-statements as statements:
130116
if (path.key !== 'alternate') {
131117
instrumentStatement(path, state);
132118
}
119+
120+
// Consequent branch:
121+
instrumentConditionBranch(path.get('consequent'), state, ['branch']);
122+
123+
// Alternate branch:
124+
if (!path.has('alternate')) {
125+
const placeholder = t.emptyStatement();
126+
placeholder.loc = {start: path.node.loc.end, end: path.node.loc.end};
127+
path.get('alternate').replaceWith(placeholder);
128+
instrumentConditionBranch(path.get('alternate'), state, ['branch']);
129+
} else if (!path.get('alternate').isIfStatement()) {
130+
instrumentConditionBranch(path.get('alternate'), state, ['branch']);
131+
}
133132
},
134133

135134
// Source: case 'a':
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/* eslint-disable */
2+
3+
if (true); // one branch (taken)
4+
else if (false); // one branch (not taken)
5+
else; // one branch (not taken)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/* eslint-disable */
2+
3+
if (true) true; // one branch (taken)
4+
else if (false) false; // one branch (not taken)
5+
else null; // one branch (not taken)

test/spec/if-statements.spec.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,42 @@ test('coverage should count if-statement branches', t => {
3434
});
3535
});
3636

37+
test('coverage should count empty branches in if-statements', t => {
38+
t.plan(3);
39+
runFixture('if-statements-empty').then(({locations}) => {
40+
const branchLocations = locations.filter(isBranch);
41+
const executedOnceBranchLocations = branchLocations
42+
.filter(l => l.count === 1);
43+
const executedNeverBranchLocations = branchLocations
44+
.filter(l => l.count === 0);
45+
46+
// There are three branches (all implicit):
47+
t.equal(branchLocations.length, 3);
48+
// Only one of the three branches has actually run:
49+
t.equal(executedOnceBranchLocations.length, 1);
50+
// Other two branches have never run:
51+
t.equal(executedNeverBranchLocations.length, 2);
52+
});
53+
});
54+
55+
test('coverage should count blockless branches in if-statements', t => {
56+
t.plan(3);
57+
runFixture('if-statements-no-block').then(({locations}) => {
58+
const branchLocations = locations.filter(isBranch);
59+
const executedOnceBranchLocations = branchLocations
60+
.filter(l => l.count === 1);
61+
const executedNeverBranchLocations = branchLocations
62+
.filter(l => l.count === 0);
63+
64+
// There are three branches (all implicit):
65+
t.equal(branchLocations.length, 3);
66+
// Only one of the three branches has actually run:
67+
t.equal(executedOnceBranchLocations.length, 1);
68+
// Other two branches have never run:
69+
t.equal(executedNeverBranchLocations.length, 2);
70+
});
71+
});
72+
3773
test('coverage should count missing alternate branches in if-statements', t => {
3874
t.plan(2);
3975
runFixture('if-statements-no-alternate').then(({locations}) => {

0 commit comments

Comments
 (0)