Implement support for async generator functions and for-await statements

This commit is contained in:
zenparsing 2016-04-14 09:35:25 -04:00 committed by Henry Zhu
parent bf0e256c3a
commit 26e79c5433
44 changed files with 779 additions and 25 deletions

View File

@ -84,7 +84,13 @@ let buildForXStatement = function (op) {
return function (node: Object) { return function (node: Object) {
this.word("for"); this.word("for");
this.space(); this.space();
if (op === "await") {
this.word("await");
this.space();
op = "of";
}
this.token("("); this.token("(");
this.print(node.left, node); this.print(node.left, node);
this.space(); this.space();
this.word(op); this.word(op);
@ -97,6 +103,7 @@ let buildForXStatement = function (op) {
export let ForInStatement = buildForXStatement("in"); export let ForInStatement = buildForXStatement("in");
export let ForOfStatement = buildForXStatement("of"); export let ForOfStatement = buildForXStatement("of");
export let ForAwaitStatement = buildForXStatement("await");
export function DoWhileStatement(node: Object) { export function DoWhileStatement(node: Object) {
this.word("do"); this.word("do");

View File

@ -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
};
}

View File

@ -4,6 +4,7 @@ import type { NodePath } from "babel-traverse";
import nameFunction from "babel-helper-function-name"; import nameFunction from "babel-helper-function-name";
import template from "babel-template"; import template from "babel-template";
import * as t from "babel-types"; import * as t from "babel-types";
import rewriteForAwait from "./for-await";
let buildWrapper = template(` let buildWrapper = template(`
(() => { (() => {
@ -25,15 +26,54 @@ let namedBuildWrapper = template(`
`); `);
let awaitVisitor = { let awaitVisitor = {
ArrowFunctionExpression(path) { Function(path) {
if (!path.node.async) { if (path.isArrowFunctionExpression() && !path.node.async) {
path.arrowFunctionToShadowed(); path.arrowFunctionToShadowed();
return;
}
path.skip();
},
AwaitExpression({ node }, { wrapAwait }) {
node.type = "YieldExpression";
if (wrapAwait) {
node.argument = t.callExpression(wrapAwait, [node.argument]);
} }
}, },
AwaitExpression({ node }) { ForAwaitStatement(path, { file, wrapAwait }) {
node.type = "YieldExpression"; 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) { function classOrObjectMethod(path: NodePath, callId: Object) {
@ -111,15 +151,15 @@ function plainFunction(path: NodePath, callId: Object) {
} }
} }
export default function (path: NodePath, callId: Object) { export default function (path: NodePath, file: Object, helpers: Object) {
let node = path.node; path.get("body").traverse(awaitVisitor, {
if (node.generator) return; file,
wrapAwait: helpers.wrapAwait
path.traverse(awaitVisitor); });
if (path.isClassMethod() || path.isObjectMethod()) { if (path.isClassMethod() || path.isObjectMethod()) {
return classOrObjectMethod(path, callId); classOrObjectMethod(path, helpers.wrapAsync);
} else { } else {
return plainFunction(path, callId); plainFunction(path, helpers.wrapAsync);
} }
} }

View File

@ -61,6 +61,158 @@ helpers.jsx = template(`
})() })()
`); `);
helpers.asyncIterator = template(`
(function (iterable) {
if (typeof Symbol === "function") {
if (Symbol.asyncIterator) {
var method = iterable[Symbol.asyncIterator];
if (method != null) return method.call(iterable);
}
if (Symbol.iterator) {
return iterable[Symbol.iterator]();
}
}
throw new TypeError("Object is not async iterable");
})
`);
helpers.asyncGenerator = template(`
(function () {
function AwaitValue(value) {
this.value = value;
}
function AsyncGenerator(gen) {
var front, back;
function send(key, arg) {
return new Promise(function (resolve, reject) {
var request = {
key: key,
arg: arg,
resolve: resolve,
reject: reject,
next: null
};
if (back) {
back = back.next = request;
} else {
front = back = request;
resume(key, arg);
}
});
}
function resume(key, arg) {
try {
var result = gen[key](arg)
var value = result.value;
if (value instanceof AwaitValue) {
Promise.resolve(value.value).then(
function (arg) { resume("next", arg); },
function (arg) { resume("throw", arg); });
} else {
settle(result.done ? "return" : "normal", result.value);
}
} catch (err) {
settle("throw", err);
}
}
function settle(type, value) {
switch (type) {
case "return":
front.resolve({ value: value, done: true });
break;
case "throw":
front.reject(value);
break;
default:
front.resolve({ value: value, done: false });
break;
}
front = front.next;
if (front) {
resume(front.key, front.arg);
} else {
back = null;
}
}
this._invoke = send;
// Hide "return" method if generator return is not supported
if (typeof gen.return !== "function") {
this.return = undefined;
}
}
if (typeof Symbol === "function" && Symbol.asyncIterator) {
AsyncGenerator.prototype[Symbol.asyncIterator] = function () { return this; };
}
AsyncGenerator.prototype.next = function (arg) { return this._invoke("next", arg); };
AsyncGenerator.prototype.throw = function (arg) { return this._invoke("throw", arg); };
AsyncGenerator.prototype.return = function (arg) { return this._invoke("return", arg); };
return {
wrap: function (fn) {
return function () {
return new AsyncGenerator(fn.apply(this, arguments));
};
},
await: function (value) {
return new AwaitValue(value);
}
};
})()
`);
helpers.asyncGeneratorDelegate = template(`
(function (inner, awaitWrap) {
var iter = {}, waiting = false;
function pump(key, value) {
waiting = true;
value = new Promise(function (resolve) { resolve(inner[key](value)); });
return { done: false, value: awaitWrap(value) };
};
if (typeof Symbol === "function" && Symbol.iterator) {
iter[Symbol.iterator] = function () { return this; };
}
iter.next = function (value) {
if (waiting) {
waiting = false;
return value;
}
return pump("next", value);
};
if (typeof inner.throw === "function") {
iter.throw = function (value) {
if (waiting) {
waiting = false;
throw value;
}
return pump("throw", value);
};
}
if (typeof inner.return === "function") {
iter.return = function (value) {
return pump("return", value);
};
}
return iter;
})
`);
helpers.asyncToGenerator = template(` helpers.asyncToGenerator = template(`
(function (fn) { (function (fn) {
return function () { return function () {
@ -92,7 +244,6 @@ helpers.asyncToGenerator = template(`
}) })
`); `);
helpers.classCallCheck = template(` helpers.classCallCheck = template(`
(function (instance, Constructor) { (function (instance, Constructor) {
if (!(instance instanceof Constructor)) { if (!(instance instanceof Constructor)) {

View File

@ -0,0 +1,4 @@
node_modules
*.log
src
test

View File

@ -0,0 +1,35 @@
# babel-plugin-transform-async-functions
Turn async generator functions and for-await statements to ES2015 generators
## Installation
```sh
$ npm install babel-plugin-transform-async-generator-functions
```
## Usage
### Via `.babelrc` (Recommended)
**.babelrc**
```json
{
"plugins": ["transform-async-generator-functions"]
}
```
### Via CLI
```sh
$ babel --plugins transform-async-generator-functions script.js
```
### Via Node API
```javascript
require("babel-core").transform("code", {
plugins: ["transform-async-generator-functions"]
});
```

View File

@ -0,0 +1,19 @@
{
"name": "babel-plugin-transform-async-generator-functions",
"version": "6.7.4",
"description": "Turn async generator functions into ES2015 generators",
"repository": "https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-async-generator-functions",
"license": "MIT",
"main": "lib/index.js",
"keywords": [
"babel-plugin"
],
"dependencies": {
"babel-helper-remap-async-to-generator": "^6.7.0",
"babel-plugin-syntax-async-generators": "^6.5.0",
"babel-runtime": "^5.0.0"
},
"devDependencies": {
"babel-helper-plugin-test-runner": "^6.3.13"
}
}

View File

@ -0,0 +1,36 @@
import remapAsyncToGenerator from "babel-helper-remap-async-to-generator";
export default function ({ types: t }) {
let yieldStarVisitor = {
Function(path) {
path.skip();
},
YieldExpression({ node }, state) {
if (!node.delegate) return;
let callee = state.addHelper("asyncGeneratorDelegate");
node.argument = t.callExpression(callee, [
t.callExpression(state.addHelper("asyncIterator"), [node.argument]),
t.memberExpression(state.addHelper("asyncGenerator"), t.identifier("await"))
]);
}
};
return {
inherits: require("babel-plugin-syntax-async-generators"),
visitor: {
Function(path, state) {
if (!path.node.async || !path.node.generator) return;
path.get("body").traverse(yieldStarVisitor, state);
remapAsyncToGenerator(path, state.file, {
wrapAsync: t.memberExpression(
state.addHelper("asyncGenerator"), t.identifier("wrap")),
wrapAwait: t.memberExpression(
state.addHelper("asyncGenerator"), t.identifier("await"))
});
}
}
};
}

View File

@ -0,0 +1,8 @@
class C {
async *g() {
this;
await 1;
yield 2;
return 3;
}
}

View File

@ -0,0 +1,12 @@
class C {
*g() {
var _this = this;
return babelHelpers.asyncGenerator.wrap(function* () {
_this;
yield babelHelpers.asyncGenerator.await(1);
yield 2;
return 3;
})();
}
}

View File

@ -0,0 +1,6 @@
async function* agf() {
this;
await 1;
yield 2;
return 3;
}

View File

@ -0,0 +1,11 @@
let agf = (() => {
var ref = babelHelpers.asyncGenerator.wrap(function* () {
this;
yield babelHelpers.asyncGenerator.await(1);
yield 2;
return 3;
});
return function agf() {
return ref.apply(this, arguments);
};
})();

View File

@ -0,0 +1,6 @@
(async function* agf() {
this;
await 1;
yield 2;
return 3;
});

View File

@ -0,0 +1,14 @@
(() => {
var ref = babelHelpers.asyncGenerator.wrap(function* () {
this;
yield babelHelpers.asyncGenerator.await(1);
yield 2;
return 3;
});
function agf() {
return ref.apply(this, arguments);
}
return agf;
})();

View File

@ -0,0 +1,8 @@
({
async *g() {
this;
await 1;
yield 2;
return 3;
}
});

View File

@ -0,0 +1,12 @@
({
*g() {
var _this = this;
return babelHelpers.asyncGenerator.wrap(function* () {
_this;
yield babelHelpers.asyncGenerator.await(1);
yield 2;
return 3;
})();
}
});

View File

@ -0,0 +1,3 @@
{
"plugins": ["external-helpers", "transform-async-generator-functions"]
}

View File

@ -0,0 +1,8 @@
class C {
static async *g() {
this;
await 1;
yield 2;
return 3;
}
}

View File

@ -0,0 +1,12 @@
class C {
static *g() {
var _this = this;
return babelHelpers.asyncGenerator.wrap(function* () {
_this;
yield babelHelpers.asyncGenerator.await(1);
yield 2;
return 3;
})();
}
}

View File

@ -0,0 +1,4 @@
async function* g() {
yield* [1, 2, 3];
yield* iterable;
}

View File

@ -0,0 +1,9 @@
let g = (() => {
var ref = babelHelpers.asyncGenerator.wrap(function* () {
yield* babelHelpers.asyncGeneratorDelegate(babelHelpers.asyncIterator([1, 2, 3]), babelHelpers.asyncGenerator.await);
yield* babelHelpers.asyncGeneratorDelegate(babelHelpers.asyncIterator(iterable), babelHelpers.asyncGenerator.await);
});
return function g() {
return ref.apply(this, arguments);
};
})();

View File

@ -0,0 +1,5 @@
async () => {
for await (let x of y) {
f(x);
}
};

View File

@ -0,0 +1,26 @@
babelHelpers.asyncToGenerator(function* () {
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = babelHelpers.asyncIterator(y), _step, _value; _step = yield _iterator.next(), _iteratorNormalCompletion = _step.done, _value = yield _step.value, !_iteratorNormalCompletion; _iteratorNormalCompletion = true) {
let x = _value;
f(x);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
yield _iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
});

View File

@ -0,0 +1,5 @@
async function f() {
for await (let x of y) {
g(x);
}
}

View File

@ -0,0 +1,31 @@
let f = (() => {
var ref = babelHelpers.asyncToGenerator(function* () {
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = babelHelpers.asyncIterator(y), _step, _value; _step = yield _iterator.next(), _iteratorNormalCompletion = _step.done, _value = yield _step.value, !_iteratorNormalCompletion; _iteratorNormalCompletion = true) {
let x = _value;
g(x);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
yield _iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
});
return function f() {
return ref.apply(this, arguments);
};
})();

View File

@ -0,0 +1,5 @@
async function* g() {
for await (let x of y) {
f(x);
}
}

View File

@ -0,0 +1,31 @@
let g = (() => {
var ref = babelHelpers.asyncGenerator.wrap(function* () {
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = babelHelpers.asyncIterator(y), _step, _value; _step = yield babelHelpers.asyncGenerator.await(_iterator.next()), _iteratorNormalCompletion = _step.done, _value = yield babelHelpers.asyncGenerator.await(_step.value), !_iteratorNormalCompletion; _iteratorNormalCompletion = true) {
let x = _value;
f(x);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
yield babelHelpers.asyncGenerator.await(_iterator.return());
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
});
return function g() {
return ref.apply(this, arguments);
};
})();

View File

@ -0,0 +1,5 @@
async function f() {
for await (let { x, y: [z] } of a) {
g(x, z);
}
}

View File

@ -0,0 +1,31 @@
let f = (() => {
var ref = babelHelpers.asyncToGenerator(function* () {
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = babelHelpers.asyncIterator(a), _step, _value; _step = yield _iterator.next(), _iteratorNormalCompletion = _step.done, _value = yield _step.value, !_iteratorNormalCompletion; _iteratorNormalCompletion = true) {
let { x, y: [z] } = _value;
g(x, z);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
yield _iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
});
return function f() {
return ref.apply(this, arguments);
};
})();

View File

@ -0,0 +1,7 @@
{
"plugins": [
"external-helpers",
"transform-async-to-generator",
"transform-async-generator-functions"
]
}

View File

@ -0,0 +1,11 @@
async function* g() {
() => this;
function f() {
() => this;
}
async () => {
this;
await 1;
}
await 1;
}

View File

@ -0,0 +1,20 @@
let g = (() => {
var ref = babelHelpers.asyncGenerator.wrap(function* () {
var _this = this;
(function () {
return _this;
});
function f() {
() => this;
}
babelHelpers.asyncToGenerator(function* () {
_this;
yield 1;
});
yield babelHelpers.asyncGenerator.await(1);
});
return function g() {
return ref.apply(this, arguments);
};
})();

View File

@ -0,0 +1,4 @@
async function* g(x = async function() { await 1 }) {
await 2;
yield 3;
}

View File

@ -0,0 +1,11 @@
let g = (() => {
var ref = babelHelpers.asyncGenerator.wrap(function* (x = babelHelpers.asyncToGenerator(function* () {
yield 1;
})) {
yield babelHelpers.asyncGenerator.await(2);
yield 3;
});
return function g(_x) {
return ref.apply(this, arguments);
};
})();

View File

@ -0,0 +1,7 @@
async function f() {
await 1;
async function* g() {
await 2;
yield 3;
}
}

View File

@ -0,0 +1,18 @@
let f = (() => {
var ref = babelHelpers.asyncToGenerator(function* () {
let g = (() => {
var ref = babelHelpers.asyncGenerator.wrap(function* () {
yield babelHelpers.asyncGenerator.await(2);
yield 3;
});
return function g() {
return ref.apply(this, arguments);
};
})();
yield 1;
});
return function f() {
return ref.apply(this, arguments);
};
})();

View File

@ -0,0 +1,7 @@
{
"plugins": [
"external-helpers",
"transform-async-to-generator",
"transform-async-generator-functions"
]
}

View File

@ -0,0 +1 @@
require("babel-helper-plugin-test-runner")(__dirname);

View File

@ -8,7 +8,9 @@ export default function () {
Function(path, state) { Function(path, state) {
if (!path.node.async || path.node.generator) return; if (!path.node.async || path.node.generator) return;
remapAsyncToGenerator(path, state.addHelper("asyncToGenerator")); remapAsyncToGenerator(path, state.file, {
wrapAsync: state.addHelper("asyncToGenerator")
});
} }
} }
}; };

View File

@ -1,6 +1,6 @@
class Class { class Class {
async method() { async method() {
this; this;
() => this; () => this;
() => { () => {
this; this;

View File

@ -16,9 +16,9 @@ class Class {
var _this2 = this; var _this2 = this;
this; this;
(function () { () => {
_this2; this;
}); };
babelHelpers.asyncToGenerator(function* () { babelHelpers.asyncToGenerator(function* () {
_this2; _this2;
}); });
@ -27,10 +27,10 @@ class Class {
function x() { function x() {
var _this3 = this; var _this3 = this;
this; this;
(function () { () => {
_this3; this;
}); };
babelHelpers.asyncToGenerator(function* () { babelHelpers.asyncToGenerator(function* () {
_this3; _this3;
}); });

View File

@ -8,10 +8,9 @@ export default function () {
Function(path, state) { Function(path, state) {
if (!path.node.async || path.node.generator) return; if (!path.node.async || path.node.generator) return;
remapAsyncToGenerator( remapAsyncToGenerator(path, state.file, {
path, wrapAsync: state.addImport(state.opts.module, state.opts.method)
state.addImport(state.opts.module, state.opts.method) });
);
} }
} }
}; };

View File

@ -8,6 +8,7 @@
"repository": "https://github.com/babel/babel/tree/master/packages/babel-preset-stage-2", "repository": "https://github.com/babel/babel/tree/master/packages/babel-preset-stage-2",
"main": "lib/index.js", "main": "lib/index.js",
"dependencies": { "dependencies": {
"babel-plugin-transform-async-generator-functions": "^6.7.4",
"babel-plugin-transform-class-properties": "^6.3.13", "babel-plugin-transform-class-properties": "^6.3.13",
"babel-plugin-transform-decorators": "^6.13.0", "babel-plugin-transform-decorators": "^6.13.0",
"babel-plugin-transform-object-rest-spread": "^6.3.13", "babel-plugin-transform-object-rest-spread": "^6.3.13",

View File

@ -11,6 +11,22 @@ defineType("AwaitExpression", {
} }
}); });
defineType("ForAwaitStatement", {
visitor: ["left", "right", "body"],
aliases: ["Scopable", "Statement", "For", "BlockParent", "Loop", "ForXStatement"],
fields: {
left: {
validate: assertNodeType("VariableDeclaration", "LVal")
},
right: {
validate: assertNodeType("Expression")
},
body: {
validate: assertNodeType("Statement")
}
}
});
defineType("BindExpression", { defineType("BindExpression", {
visitor: ["object", "callee"], visitor: ["object", "callee"],
aliases: ["Expression"], aliases: ["Expression"],