From 578a8496db0134c020fd54f5511b45e3534151ed Mon Sep 17 00:00:00 2001 From: Van Nguyen Date: Wed, 11 Apr 2018 04:11:47 +0530 Subject: [PATCH] Support Async/Await based on #12 --- .eslintrc | 1 + src/index.js | 4 +- src/program/BlockStatement.js | 47 ++++++++ src/program/types/ArrowFunctionExpression.js | 3 + src/program/types/AwaitExpression.js | 20 ++++ src/program/types/FunctionDeclaration.js | 2 + src/program/types/index.js | 2 + src/support.js | 109 ++++++++++--------- test/samples/async-await.js | 25 +++++ 9 files changed, 157 insertions(+), 56 deletions(-) create mode 100644 src/program/types/AwaitExpression.js create mode 100644 test/samples/async-await.js diff --git a/.eslintrc b/.eslintrc index 04919df6..b36a2f75 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,6 +5,7 @@ "semi": [ 2, "always" ], "keyword-spacing": [ 2, { "before": true, "after": true } ], "space-before-blocks": [ 2, "always" ], + "space-in-parens": [ 2, "never" ], "no-mixed-spaces-and-tabs": [ 2, "smart-tabs" ], "no-cond-assign": [ 0 ] }, diff --git a/src/index.js b/src/index.js index 244148ad..98314283 100644 --- a/src/index.js +++ b/src/index.js @@ -15,8 +15,8 @@ const dangerousTransforms = ['dangerousTaggedTemplateString', 'dangerousForOf']; export function target(target) { const targets = Object.keys(target); let bitmask = targets.length - ? 0b11111111111111111111 - : 0b01000000000000000000; + ? 0b111111111111111111111 + : 0b010000000000000000000; Object.keys(target).forEach(environment => { const versions = matrix[environment]; diff --git a/src/program/BlockStatement.js b/src/program/BlockStatement.js index f0cb8942..cfb04bc1 100644 --- a/src/program/BlockStatement.js +++ b/src/program/BlockStatement.js @@ -176,6 +176,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() { return '); + code.insertLeft(last.end, ' })'); + } + } + if (this.createdDeclarations.length) { introStatementGenerators.push((start, prefix, suffix) => { const assignment = `${prefix}var ${this.createdDeclarations.join(', ')}${suffix}`; diff --git a/src/program/types/ArrowFunctionExpression.js b/src/program/types/ArrowFunctionExpression.js index 770bb2a6..ae5e3ef8 100644 --- a/src/program/types/ArrowFunctionExpression.js +++ b/src/program/types/ArrowFunctionExpression.js @@ -1,5 +1,6 @@ import Node from '../Node.js'; import removeTrailingComma from '../../utils/removeTrailingComma.js'; +import AwaitExpression from './AwaitExpression'; export default class ArrowFunctionExpression extends Node { initialise(transforms) { @@ -18,6 +19,7 @@ export default class ArrowFunctionExpression extends Node { } code.remove(charIndex, this.body.start); + AwaitExpression.removeAsync(code, transforms, this.async, this.start); super.transpile(code, transforms); // wrap naked parameter @@ -34,6 +36,7 @@ export default class ArrowFunctionExpression extends Node { code.prependRight(this.start, 'function '); } } else { + AwaitExpression.removeAsync(code, transforms, this.async, this.start); super.transpile(code, transforms); } diff --git a/src/program/types/AwaitExpression.js b/src/program/types/AwaitExpression.js new file mode 100644 index 00000000..b618e5d9 --- /dev/null +++ b/src/program/types/AwaitExpression.js @@ -0,0 +1,20 @@ +import Node from '../Node.js'; + +const noop = () => {}; + +export default class AwaitExpression extends Node { + static removeAsync(code, transforms, async, start, callback = noop) { + if (transforms.asyncAwait && async) { + code.remove(start, start + 6); + callback(); + } + } + + transpile (code, transforms) { + AwaitExpression.removeAsync(code, transforms, true, this.start, () => { + 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 951e90a0..978ba356 100644 --- a/src/program/types/FunctionDeclaration.js +++ b/src/program/types/FunctionDeclaration.js @@ -1,6 +1,7 @@ import Node from '../Node.js'; import CompileError from '../../utils/CompileError.js'; import removeTrailingComma from '../../utils/removeTrailingComma.js'; +import AwaitExpression from './AwaitExpression'; export default class FunctionDeclaration extends Node { initialise(transforms) { @@ -17,6 +18,7 @@ export default class FunctionDeclaration extends Node { } transpile(code, transforms) { + AwaitExpression.removeAsync(code, transforms, this.async, this.start); super.transpile(code, transforms); if (transforms.trailingFunctionCommas && this.params.length) { removeTrailingComma(code, this.params[this.params.length - 1].end); diff --git a/src/program/types/index.js b/src/program/types/index.js index 0b747e51..03b49276 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'; @@ -50,6 +51,7 @@ export default { ArrayExpression, ArrowFunctionExpression, AssignmentExpression, + AwaitExpression, BinaryExpression, BreakStatement, CallExpression, diff --git a/src/support.js b/src/support.js index 21830321..5c02c750 100644 --- a/src/support.js +++ b/src/support.js @@ -1,74 +1,75 @@ export const matrix = { chrome: { - 48: 0b01001010100011001111, - 49: 0b01001111100111111111, - 50: 0b01011111100111111111, - 51: 0b01011111100111111111, - 52: 0b01111111100111111111, - 53: 0b01111111100111111111, - 54: 0b01111111100111111111, - 55: 0b01111111100111111111, - 56: 0b01111111100111111111, - 57: 0b01111111100111111111, - 58: 0b11111111100111111111, - 59: 0b11111111100111111111, - 60: 0b11111111100111111111, - 61: 0b11111111100111111111, - 62: 0b11111111100111111111, - 63: 0b11111111100111111111 + 48: 0b010010101000110011101, + 49: 0b010011111001111111101, + 50: 0b010111111001111111101, + 51: 0b010111111001111111101, + 52: 0b011111111001111111101, + 53: 0b011111111001111111101, + 54: 0b011111111001111111101, + 55: 0b011111111001111111111, + 56: 0b011111111001111111111, + 57: 0b011111111001111111111, + 58: 0b111111111001111111111, + 59: 0b111111111001111111111, + 60: 0b111111111001111111111, + 61: 0b111111111001111111111, + 62: 0b111111111001111111111, + 63: 0b111111111001111111111 }, firefox: { - 43: 0b01001110100011011101, - 44: 0b01001110100111011101, - 45: 0b01001110100111011111, - 46: 0b01011110100111011111, - 47: 0b01011110100111111111, - 48: 0b01011110100111111111, - 49: 0b01011110100111111111, - 50: 0b01011110100111111111, - 51: 0b01011110100111111111, - 52: 0b11111111100111111111, - 53: 0b11111111100111111111, - 54: 0b11111111100111111111, - 55: 0b11111111100111111111, - 56: 0b11111111100111111111, - 57: 0b11111111100111111111, - 58: 0b11111111100111111111 + 43: 0b010011101000110111001, + 44: 0b010011101001110111001, + 45: 0b010011101001110111101, + 46: 0b010111101001110111101, + 47: 0b010111101001111111101, + 48: 0b010111101001111111101, + 49: 0b010111101001111111101, + 50: 0b010111101001111111101, + 51: 0b010111101001111111101, + 52: 0b111111111001111111111, + 53: 0b111111111001111111111, + 54: 0b111111111001111111111, + 55: 0b111111111001111111111, + 56: 0b111111111001111111111, + 57: 0b111111111001111111111, + 58: 0b111111111001111111111 }, safari: { - 8: 0b01000000000000000100, - 9: 0b01001001100001101110, - 10: 0b11011111100111111111, - '10.1': 0b11111111100111111111, - 11: 0b11111111100111111111 + 8: 0b010000000000000001000, + 9: 0b010010011000011011100, + 10: 0b110111111001111111101, + '10.1': 0b111111111001111111111, + 11: 0b111111111001111111111 }, ie: { - 8: 0b00000000000000000000, - 9: 0b01000000000000000000, - 10: 0b01000000000000000000, - 11: 0b01000000000100000000 + 8: 0b000000000000000000000, + 9: 0b010000000000000000000, + 10: 0b010000000000000000000, + 11: 0b010000000001000000000 }, edge: { - 12: 0b01001010100101001101, - 13: 0b01011110100111001111, - 14: 0b11111110100111111111, - 15: 0b11111110100111111111, - 16: 0b11111110100111111111 + 12: 0b010010101001010011001, + 13: 0b010111101001110011101, + 14: 0b111111101001111111101, + 15: 0b111111101001111111111, + 16: 0b111111101001111111111 }, node: { - '0.10': 0b01000000000000000000, - '0.12': 0b01000000000001000000, - 4: 0b01001000100011001111, - 5: 0b01001000100011001111, - 6: 0b01011111100111111111, - 8: 0b11111111100111111111, - '8.3': 0b11111111100111111111, - '8.7': 0b11111111100111111111 + '0.10': 0b010000000000000000000, + '0.12': 0b010000000000010000000, + 4: 0b010010001000110011101, + 5: 0b010010001000110011101, + 6: 0b010111111001111111101, + 8: 0b111111111001111111111, + '8.3': 0b111111111001111111111, + '8.7': 0b111111111001111111111 } }; export const features = [ 'arrow', + 'asyncAwait', 'classes', 'computedProperty', 'conciseMethodProperty', diff --git a/test/samples/async-await.js b/test/samples/async-await.js new file mode 100644 index 00000000..49a939f8 --- /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() {}) }` + } +];