Skip to content
This repository was archived by the owner on Jan 6, 2020. It is now read-only.

Commit 0f850a2

Browse files
committed
Hoist await and yield expressions
The helper wraps the original argument with a new function. This is a problem if the argument contains yield or await expressions, since the function they now run is is not a generator or marked as asynchronous. Instead hoist these expressions so they're resolved outside of the helper. Fixes avajs/ava#987.
1 parent 6fe23e7 commit 0f850a2

File tree

2 files changed

+100
-7
lines changed

2 files changed

+100
-7
lines changed

index.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ var buildHelper = template([
3131
'}'
3232
].join('\n'));
3333

34+
var hoistExpression = template('var ARG = EXP;');
35+
3436
var assertionVisitor = {
3537
CallExpression: function (path, state) {
3638
if (isThrowsMember(path.get('callee'))) {
@@ -40,14 +42,24 @@ var assertionVisitor = {
4042
return;
4143
}
4244

45+
var hoisted = hoistAwaitAndYield(path, arg0);
46+
4347
path.node.arguments[0] = wrapWithHelper({
4448
HELPER_ID: t.identifier(this.avaThrowHelper()),
45-
EXP: arg0,
49+
EXP: hoisted ? hoisted.arg0 : arg0,
4650
LINE: t.numericLiteral(arg0.loc.start.line),
4751
COLUMN: t.numericLiteral(arg0.loc.start.column),
4852
SOURCE: t.stringLiteral(state.file.code.substring(arg0.start, arg0.end)),
4953
FILE: t.stringLiteral(state.file.opts.filename)
5054
}).expression;
55+
56+
if (hoisted) {
57+
path.replaceWithMultiple(
58+
hoisted.expressions.concat(
59+
t.expressionStatement(path.node)
60+
)
61+
);
62+
}
5163
}
5264
}
5365
};
@@ -83,3 +95,32 @@ function isThrowsMember(path) {
8395
path.get('property').isIdentifier({name: 'notThrows'})
8496
);
8597
}
98+
99+
function hoistAwaitAndYield(path, arg0) {
100+
if (t.isAwaitExpression(arg0) || t.isYieldExpression(arg0)) {
101+
var arg = path.scope.generateUidIdentifier('arg');
102+
return {
103+
expressions: [hoistExpression({ARG: arg, EXP: arg0})],
104+
arg0: arg
105+
};
106+
}
107+
108+
if (t.isCallExpression(arg0)) {
109+
var expressions = [];
110+
arg0.arguments = arg0.arguments.map(function (arg) {
111+
var hoisted = hoistAwaitAndYield(path, arg);
112+
if (!hoisted) {
113+
return arg;
114+
}
115+
116+
expressions = expressions.concat(hoisted.expressions);
117+
return hoisted.arg0;
118+
});
119+
return {
120+
expressions: expressions,
121+
arg0: arg0
122+
};
123+
}
124+
125+
return null;
126+
}

test.js

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,24 +51,28 @@ const HELPER = `function _avaThrowsHelper(fn, data) {
5151
}
5252
}\n`;
5353

54-
function wrapped(throws, expression, line, column) {
54+
function wrapped(throws, line, column, expression, source = expression) { // eslint-disable-line max-params
5555
return `t.${throws}(_avaThrowsHelper(function () {
5656
return ${expression};
5757
}, {
5858
line: ${line},
5959
column: ${column},
60-
source: "${expression}",
60+
source: "${source}",
6161
filename: "some-file.js"
6262
}));`;
6363
}
6464

65+
function indent(str) {
66+
return str.replace(/\n/g, '\n ').trimRight();
67+
}
68+
6569
test('creates a helper', t => {
6670
const input = 't.throws(foo())';
6771
const {code} = transform(input);
6872

6973
const expected = [
7074
HELPER,
71-
wrapped('throws', 'foo()', 1, 9)
75+
wrapped('throws', 1, 9, 'foo()')
7276
].join('\n');
7377

7478
t.is(code, expected);
@@ -81,14 +85,62 @@ test('creates the helper only once', t => {
8185

8286
const expected = [
8387
HELPER,
84-
wrapped('throws', 'foo()', 1, 9),
85-
wrapped('throws', 'bar()', 2, 9)
88+
wrapped('throws', 1, 9, 'foo()'),
89+
wrapped('throws', 2, 9, 'bar()')
8690
].join('\n');
8791

8892
t.is(code, expected);
8993
addExample(input, code);
9094
});
9195

96+
test('hoists await expressions', t => {
97+
const input = `async function test() {
98+
t.throws(foo(await bar(), await baz(), qux));
99+
t.throws(await quux);
100+
}`;
101+
const {code} = transform(input);
102+
103+
const expected = `${HELPER}
104+
async function test() {
105+
var _arg = await bar();
106+
107+
var _arg2 = await baz();
108+
109+
${indent(wrapped('throws', 2, 11, 'foo(_arg, _arg2, qux)', 'foo(await bar(), await baz(), qux)'))}
110+
111+
var _arg3 = await quux;
112+
113+
${indent(wrapped('throws', 3, 11, '_arg3', 'await quux'))}
114+
}`;
115+
116+
t.is(code, expected);
117+
addExample(input, code);
118+
});
119+
120+
test('hoists yield expressions', t => {
121+
const input = `function* test() {
122+
t.throws(foo(yield bar(), yield baz(), qux));
123+
t.throws(yield quux);
124+
}`;
125+
const {code} = transform(input);
126+
127+
const expected = `${HELPER}
128+
function* test() {
129+
var _arg = yield bar();
130+
131+
var _arg2 = yield baz();
132+
133+
${indent(wrapped('throws', 2, 11, 'foo(_arg, _arg2, qux)', 'foo(yield bar(), yield baz(), qux)'))}
134+
135+
var _arg3 = yield quux;
136+
137+
${indent(wrapped('throws', 3, 11, '_arg3', 'yield quux'))}
138+
}`;
139+
140+
t.is(code, expected);
141+
addExample(input, code);
142+
});
143+
92144
test('does nothing if it does not match', t => {
93145
const input = 't.is(foo());';
94146
const {code} = transform(input);
@@ -103,7 +155,7 @@ test('helps notThrows', t => {
103155

104156
const expected = [
105157
HELPER,
106-
wrapped('notThrows', 'baz()', 1, 12)
158+
wrapped('notThrows', 1, 12, 'baz()')
107159
].join('\n');
108160

109161
t.is(code, expected);

0 commit comments

Comments
 (0)