Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Zenparsing async generator functions #4576

Merged
merged 2 commits into from Sep 27, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/babel-core/src/transformation/file/index.js
Expand Up @@ -414,7 +414,7 @@ export default class File extends Store {

parse(code: string) {
let parseCode = parse;
let parserOpts = this.opts.parserOpts || this.parserOpts;
let parserOpts = this.opts.parserOpts;

if (parserOpts) {
parserOpts = Object.assign({}, this.parserOpts, parserOpts);
Expand All @@ -441,7 +441,7 @@ export default class File extends Store {
}

this.log.debug("Parse start");
let ast = parseCode(code, parserOpts);
let ast = parseCode(code, parserOpts || this.parserOpts);
this.log.debug("Parse stop");
return ast;
}
Expand Down
7 changes: 7 additions & 0 deletions packages/babel-generator/src/generators/statements.js
Expand Up @@ -84,7 +84,13 @@ let buildForXStatement = function (op) {
return function (node: Object) {
this.word("for");
this.space();
if (op === "await") {
this.word("await");
this.space();
op = "of";
}
this.token("(");

this.print(node.left, node);
this.space();
this.word(op);
Expand All @@ -97,6 +103,7 @@ let buildForXStatement = function (op) {

export let ForInStatement = buildForXStatement("in");
export let ForOfStatement = buildForXStatement("of");
export let ForAwaitStatement = buildForXStatement("await");

export function DoWhileStatement(node: Object) {
this.word("do");
Expand Down
2 changes: 1 addition & 1 deletion packages/babel-generator/src/index.js
Expand Up @@ -122,7 +122,7 @@ function findCommonStringDelimiter(code, tokens) {
if (occurences.single > occurences.double) {
return "single";
} else {
return DEFAULT_STRING_DELIMITER;
return "double";
}
}

Expand Down
106 changes: 106 additions & 0 deletions packages/babel-helper-remap-async-to-generator/src/for-await.js
@@ -0,0 +1,106 @@
import * as t from "babel-types";
import template from "babel-template";
import traverse from "babel-traverse";

let buildForAwait = template(`
function* wrapper() {
var ITERATOR_COMPLETION = true;
var ITERATOR_HAD_ERROR_KEY = false;
var ITERATOR_ERROR_KEY = undefined;
try {
for (
var ITERATOR_KEY = GET_ITERATOR(OBJECT), STEP_KEY, STEP_VALUE;
(
STEP_KEY = yield AWAIT(ITERATOR_KEY.next()),
ITERATOR_COMPLETION = STEP_KEY.done,
STEP_VALUE = yield AWAIT(STEP_KEY.value),
!ITERATOR_COMPLETION
);
ITERATOR_COMPLETION = true) {
}
} catch (err) {
ITERATOR_HAD_ERROR_KEY = true;
ITERATOR_ERROR_KEY = err;
} finally {
try {
if (!ITERATOR_COMPLETION && ITERATOR_KEY.return) {
yield AWAIT(ITERATOR_KEY.return());
}
} finally {
if (ITERATOR_HAD_ERROR_KEY) {
throw ITERATOR_ERROR_KEY;
}
}
}
}
`);

let forAwaitVisitor = {
noScope: true,

Identifier(path, replacements) {
if (path.node.name in replacements) {
path.replaceInline(replacements[path.node.name]);
}
},

CallExpression(path, replacements) {
let callee = path.node.callee;

// if no await wrapping is being applied, unwrap the call expression
if (t.isIdentifier(callee) && callee.name === "AWAIT" && !replacements.AWAIT) {
path.replaceWith(path.node.arguments[0]);
}
}
};

export default function (path, helpers) {
let { node, scope, parent } = path;

let stepKey = scope.generateUidIdentifier("step");
let stepValue = scope.generateUidIdentifier("value");
let left = node.left;
let declar;

if (t.isIdentifier(left) || t.isPattern(left) || t.isMemberExpression(left)) {
// for await (i of test), for await ({ i } of test)
declar = t.expressionStatement(t.assignmentExpression("=", left, stepValue));
} else if (t.isVariableDeclaration(left)) {
// for await (let i of test)
declar = t.variableDeclaration(left.kind, [
t.variableDeclarator(left.declarations[0].id, stepValue)
]);
}

let template = buildForAwait();

traverse(template, forAwaitVisitor, null, {
ITERATOR_HAD_ERROR_KEY: scope.generateUidIdentifier("didIteratorError"),
ITERATOR_COMPLETION: scope.generateUidIdentifier("iteratorNormalCompletion"),
ITERATOR_ERROR_KEY: scope.generateUidIdentifier("iteratorError"),
ITERATOR_KEY: scope.generateUidIdentifier("iterator"),
GET_ITERATOR: helpers.getAsyncIterator,
OBJECT: node.right,
STEP_VALUE: stepValue,
STEP_KEY: stepKey,
AWAIT: helpers.wrapAwait
});

// remove generator function wrapper
template = template.body.body;

let isLabeledParent = t.isLabeledStatement(parent);
let tryBody = template[3].block.body;
let loop = tryBody[0];

if (isLabeledParent) {
tryBody[0] = t.labeledStatement(parent.label, loop);
}

return {
replaceParent: isLabeledParent,
node: template,
declar,
loop
};
}
60 changes: 50 additions & 10 deletions packages/babel-helper-remap-async-to-generator/src/index.js
Expand Up @@ -4,6 +4,7 @@ import type { NodePath } from "babel-traverse";
import nameFunction from "babel-helper-function-name";
import template from "babel-template";
import * as t from "babel-types";
import rewriteForAwait from "./for-await";

let buildWrapper = template(`
(() => {
Expand All @@ -25,15 +26,54 @@ let namedBuildWrapper = template(`
`);

let awaitVisitor = {
ArrowFunctionExpression(path) {
if (!path.node.async) {
Function(path) {
if (path.isArrowFunctionExpression() && !path.node.async) {
path.arrowFunctionToShadowed();
return;
}
path.skip();
},

AwaitExpression({ node }) {
AwaitExpression({ node }, { wrapAwait }) {
node.type = "YieldExpression";
if (wrapAwait) {
node.argument = t.callExpression(wrapAwait, [node.argument]);
}
},

ForAwaitStatement(path, { file, wrapAwait }) {
let { node } = path;

let build = rewriteForAwait(path, {
getAsyncIterator: file.addHelper("asyncIterator"),
wrapAwait
});

let { declar, loop } = build;
let block = loop.body;

// ensure that it's a block so we can take all its statements
path.ensureBlock();

// add the value declaration to the new loop body
if (declar) {
block.body.push(declar);
}

// push the rest of the original loop body onto our new body
block.body = block.body.concat(node.body.body);

t.inherits(loop, node);
t.inherits(loop.body, node.body);

if (build.replaceParent) {
path.parentPath.replaceWithMultiple(build.node);
path.remove();
} else {
path.replaceWithMultiple(build.node);
}
}

};

function classOrObjectMethod(path: NodePath, callId: Object) {
Expand Down Expand Up @@ -111,15 +151,15 @@ function plainFunction(path: NodePath, callId: Object) {
}
}

export default function (path: NodePath, callId: Object) {
let node = path.node;
if (node.generator) return;

path.traverse(awaitVisitor);
export default function (path: NodePath, file: Object, helpers: Object) {
path.get("body").traverse(awaitVisitor, {
file,
wrapAwait: helpers.wrapAwait
});

if (path.isClassMethod() || path.isObjectMethod()) {
return classOrObjectMethod(path, callId);
classOrObjectMethod(path, helpers.wrapAsync);
} else {
return plainFunction(path, callId);
plainFunction(path, helpers.wrapAsync);
}
}
16 changes: 14 additions & 2 deletions packages/babel-helper-transform-fixture-test-runner/src/index.js
Expand Up @@ -47,14 +47,15 @@ function run(task) {

let execCode = exec.code;
let result;
let resultExec;

if (execCode) {
let execOpts = getOpts(exec);
result = babel.transform(execCode, execOpts);
execCode = result.code;

try {
runExec(execOpts, execCode);
resultExec = runExec(execOpts, execCode);
} catch (err) {
err.message = exec.loc + ": " + err.message;
err.message += codeFrame(execCode);
Expand Down Expand Up @@ -90,6 +91,10 @@ function run(task) {
chai.expect({ line: expect.line, column: expect.column }).to.deep.equal(actual);
});
}

if (execCode && resultExec) {
return resultExec;
}
}

function runExec(opts, execCode) {
Expand Down Expand Up @@ -151,7 +156,14 @@ export default function (
return throwMsg === true || err.message.indexOf(throwMsg) >= 0;
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we try to handle the promise case here too, to allow async tests to reject.

Copy link
Member Author

@hzoo hzoo Sep 27, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should, do you want to do that in another pr if you want to and with an example test?

} else {
runTask();
if (task.exec.code) {
let result = run(task);
if (result && typeof result.then === "function") {
return result;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically just returning the result of the runExec(execOpts, execCode); if it's a promise. We could also just always return

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the .then check? I'd have thought we could return it without worrying to let Mocha worry about whether it is a promise.

}
} else {
runTask();
}
}
});
}
Expand Down