diff --git a/package.json b/package.json index bf065a5d..8b7b9b6b 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ }, "dependencies": { "acorn": "^3.1.0", + "acorn-es7-plugin": "^1.0.14", "acorn-jsx": "^3.0.1", "acorn-object-spread": "^1.0.0", "chalk": "^1.1.3", diff --git a/src/index.js b/src/index.js index 245bf824..61d29f3d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,15 @@ +import acornAsyncAwait from 'acorn-es7-plugin'; import acornJsx from 'acorn-jsx'; import acornObjectSpread from 'acorn-object-spread/inject'; import Program from './program/Program.js'; import { features, matrix } from './support.js'; import getSnippet from './utils/getSnippet.js'; -const acorn = acornObjectSpread(acornJsx); +const acorn = acornAsyncAwait( + acornObjectSpread( + acornJsx + ) +); const dangerousTransforms = [ 'dangerousTaggedTemplateString', @@ -14,8 +19,8 @@ const dangerousTransforms = [ export function target ( target ) { const targets = Object.keys( target ); let bitmask = targets.length ? - 0b1111111111111111111111111111111 : - 0b1000000000000000000000000000000; + 0b11111111111111111111111111111111 : + 0b10000000000000000000000000000000; Object.keys( target ).forEach( environment => { const versions = matrix[ environment ]; @@ -48,10 +53,11 @@ export function transform ( source, options = {} ) { ecmaVersion: 7, preserveParens: true, sourceType: 'module', - plugins: { - jsx: true, - objectSpread: true - } + plugins: { + asyncawait: true, + jsx: true, + objectSpread: true + } }); } catch ( err ) { err.snippet = getSnippet( source, err.loc ); diff --git a/src/program/BlockStatement.js b/src/program/BlockStatement.js index fa489268..2f5a36a4 100644 --- a/src/program/BlockStatement.js +++ b/src/program/BlockStatement.js @@ -147,6 +147,53 @@ export default class BlockStatement extends Node { super.transpile( code, transforms ); + if ( transforms.asyncAwait && this.isFunctionBlock && this.parent.async && this.body.length ) { + const first = this.body[ 0 ]; + const last = this.body[ this.body.length - 1 ]; + const hasOnlyOneLine = this.body.length === 1; + + // TODO refactor :) + if ( this.parent.type === 'FunctionDeclaration' ) { + if ( hasOnlyOneLine ) { + if ( first.type === 'ReturnStatement' ) { + code.insertLeft( first.argument.start, 'Promise.resolve().then(function() { ' ); + code.insertLeft( first.end, ' })' ); + } else { + code.insertLeft( first.start, 'return Promise.resolve().then(function() { ' ); + code.insertRight( last.end, ' }).then(function() {})' ); + } + } else { + code.insertLeft( first.start, 'return Promise.resolve()' ); + code.insertRight( last.end, '.then(function() {})' ); + + for ( let i = 0; i < this.body.length; i++ ) { + const prev = this.body[ i - 1 ]; + const cur = this.body[ i ]; + const next = this.body[ i + 1 ]; + + if ( cur.expression.type === 'AwaitExpression' ) { + code.insertLeft( cur.start, '.then(function() { ' ); + code.insertRight( cur.end, ' })' ); + } else { + if ( !prev || prev.expression.type === 'AwaitExpression' ) { + code.insertLeft( cur.start, '.then(function() { ' ); + } + + if ( !next || next.expression.type === 'AwaitExpression' ) { + code.insertRight( cur.end, ' })' ); + } + } + } + } + + } else if ( this.parent.type === 'ArrowFunctionExpression' ) { + // TODO merge with ^ + // wrap the function's body in a promise + code.insertLeft( first.start + 1, 'Promise.resolve().then(function() { ' ); + code.insertLeft( last.end, ' })' ); + } + } + if ( this.synthetic ) { if ( this.parent.type === 'ArrowFunctionExpression' ) { const expr = this.body[0]; diff --git a/src/program/types/ArrowFunctionExpression.js b/src/program/types/ArrowFunctionExpression.js index 2d5584c1..6a21bb56 100644 --- a/src/program/types/ArrowFunctionExpression.js +++ b/src/program/types/ArrowFunctionExpression.js @@ -25,6 +25,11 @@ export default class ArrowFunctionExpression extends Node { code.insertRight( this.start, 'function ' ); } + if ( transforms.asyncAwait && this.async ) { + // remove async keyword + code.remove( this.start, this.start + 6 ); + } + super.transpile( code, transforms ); } } diff --git a/src/program/types/AwaitExpression.js b/src/program/types/AwaitExpression.js new file mode 100644 index 00000000..306dbb0a --- /dev/null +++ b/src/program/types/AwaitExpression.js @@ -0,0 +1,15 @@ +import Node from '../Node.js'; +import reserved from '../../utils/reserved.js'; + +export default class AwaitExpression extends Node { + transpile ( code, transforms ) { + if ( transforms.asyncAwait ) { + // remove await keyword + code.remove( this.start, this.start + 6 ); + // return the awaited expression + code.insertLeft( this.argument.start, 'return ' ); + } + + super.transpile( code, transforms ); + } +} diff --git a/src/program/types/FunctionDeclaration.js b/src/program/types/FunctionDeclaration.js index d0fd207b..04380668 100644 --- a/src/program/types/FunctionDeclaration.js +++ b/src/program/types/FunctionDeclaration.js @@ -10,6 +10,16 @@ export default class FunctionDeclaration extends Node { this.body.createScope(); this.findScope( true ).addDeclaration( this.id, 'function' ); + super.initialise( transforms ); } + + transpile( code, transforms ) { + if ( transforms.asyncAwait && this.async ) { + // remove async keyword + code.remove( this.start, this.start + 6 ); + } + + super.transpile( code, transforms ); + } } diff --git a/src/program/types/index.js b/src/program/types/index.js index ebf84d1b..c0c03170 100644 --- a/src/program/types/index.js +++ b/src/program/types/index.js @@ -1,6 +1,7 @@ import ArrayExpression from './ArrayExpression.js'; import ArrowFunctionExpression from './ArrowFunctionExpression.js'; import AssignmentExpression from './AssignmentExpression.js'; +import AwaitExpression from './AwaitExpression.js'; import BinaryExpression from './BinaryExpression.js'; import BreakStatement from './BreakStatement.js'; import CallExpression from './CallExpression.js'; @@ -45,6 +46,7 @@ export default { ArrayExpression, ArrowFunctionExpression, AssignmentExpression, + AwaitExpression, BinaryExpression, BreakStatement, CallExpression, @@ -69,7 +71,7 @@ export default { JSXElement, JSXExpressionContainer, JSXOpeningElement, - JSXSpreadAttribute, + JSXSpreadAttribute, Literal, MemberExpression, ObjectExpression, diff --git a/src/support.js b/src/support.js index 1bfe22f2..179dd737 100644 --- a/src/support.js +++ b/src/support.js @@ -1,39 +1,40 @@ export const matrix = { chrome: { - 48: 0b1001111011111100111110101111101, - 49: 0b1001111111111100111111111111111, - 50: 0b1011111111111100111111111111111 + 48: 0b10011110111111001111101011111001, + 49: 0b10011111111111001111111111111101, + 50: 0b10111111111111001111111111111101 }, firefox: { - 43: 0b1000111111101100000110111011101, - 44: 0b1000111111101100000110111011101, - 45: 0b1000111111101100000110111011101 + 43: 0b10001111111011000001101110111001, + 44: 0b10001111111011000001101110111001, + 45: 0b10001111111011000001101110111001 }, safari: { - 8: 0b1000000000000000000000000000000, - 9: 0b1001111001101100000011101011110 + 8: 0b10000000000000000000000000000000, + 9: 0b10011110011011000000111010111100 }, ie: { - 8: 0b0000000000000000000000000000000, - 9: 0b1000000000000000000000000000000, - 10: 0b1000000000000000000000000000000, - 11: 0b1000000000000000111000001100000 + 8: 0b00000000000000000000000000000000, + 9: 0b10000000000000000000000000000000, + 10: 0b10000000000000000000000000000000, + 11: 0b10000000000000001110000011000000 }, edge: { - 12: 0b1011110110111100011010001011101, - 13: 0b1011111110111100011111001011111 + 12: 0b10111101101111000110100010111001, + 13: 0b10111111101111000111110010111101 }, node: { - '0.10': 0b1000000000101000000000001000000, - '0.12': 0b1000001000101000000010001000100, - 4: 0b1001111000111100111111001111111, - 5: 0b1001111000111100111111001111111, - 6: 0b1011111111111100111111111111111 + '0.10': 0b10000000001010000000000010000000, + '0.12': 0b10000010001010000000100010001000, + 4: 0b10011110001111001111110011111101, + 5: 0b10011110001111001111110011111101, + 6: 0b10111111111111001111111111111101 } }; export const features = [ 'arrow', + 'asyncAwait', 'classes', 'collections', 'computedProperty', diff --git a/test/samples/async-await.js b/test/samples/async-await.js new file mode 100644 index 00000000..cec3cb3f --- /dev/null +++ b/test/samples/async-await.js @@ -0,0 +1,25 @@ +module.exports = [ + { + description: 'transpiles await arrow function call', + input: `async () => await a()`, + output: `function () { return Promise.resolve().then(function() { return a() }); }` + }, + + { + description: 'transpiles await function call', + input: `async function f() { await a(); }`, + output: `function f() { return Promise.resolve().then(function() { return a(); }).then(function() {}) }` + }, + + { + description: 'transpiles await function call with return statement', + input: `async function f() { return await a(); }`, + output: `function f() { return Promise.resolve().then(function() { return a(); }) }` + }, + + { + description: 'transpiles await function call with more than one line of code', + input: `async function f() { await a(); thing(); await a2(); stuff(); await a3(); await a4(); }`, + output: `function f() { return Promise.resolve().then(function() { return a(); }) .then(function() { thing(); }) .then(function() { return a2(); }) .then(function() { stuff(); }) .then(function() { return a3(); }) .then(function() { return a4(); }).then(function() {}) }` + } +];