diff --git a/lib/ast.js b/lib/ast.js index 452c5bd0..234616f7 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -355,13 +355,14 @@ var AST_Expansion = DEFNODE("Expansion", "expression", { } }); -var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments is_generator", { +var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments is_generator async", { $documentation: "Base class for functions", $propdoc: { - is_generator: "[boolean] is generatorFn or not", name: "[AST_SymbolDeclaration?] the name of this function", argnames: "[AST_SymbolFunarg|AST_Destructuring|AST_Expansion|AST_DefaultAssign*] array of function arguments, destructurings, or expanding arguments", - uses_arguments: "[boolean/S] tells whether this function accesses the arguments array" + uses_arguments: "[boolean/S] tells whether this function accesses the arguments array", + is_generator: "[boolean] is this a generator method", + async: "[boolean] is this method async", }, args_as_names: function () { var out = []; @@ -893,11 +894,12 @@ var AST_ObjectGetter = DEFNODE("ObjectGetter", "quote static", { $documentation: "An object getter property", }, AST_ObjectProperty); -var AST_ConciseMethod = DEFNODE("ConciseMethod", "quote static is_generator", { +var AST_ConciseMethod = DEFNODE("ConciseMethod", "quote static is_generator async", { $propdoc: { quote: "[string|undefined] the original quote character, if any", - static: "[boolean] whether this method is static (classes only)", - is_generator: "[boolean] is generatorFn or not", + static: "[boolean] is this method static (classes only)", + is_generator: "[boolean] is this a generator method", + async: "[boolean] is this method async", }, $documentation: "An ES6 concise method inside an object or class" }, AST_ObjectProperty); @@ -1101,7 +1103,17 @@ var AST_True = DEFNODE("True", null, { value: true }, AST_Boolean); -/* -----[ Yield ]----- */ +var AST_Await = DEFNODE("Await", "expression", { + $documentation: "An `await` statement", + $propdoc: { + expression: "[AST_Node] the mandatory expression being awaited", + }, + _walk: function(visitor) { + return visitor._visit(this, function(){ + this.expression._walk(visitor); + }); + } +}); var AST_Yield = DEFNODE("Yield", "expression is_star", { $documentation: "A `yield` statement", diff --git a/lib/compress.js b/lib/compress.js index 23afb045..ab8b5f3a 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -751,6 +751,7 @@ merge(Compressor.prototype, { // Stop immediately if these node types are encountered var parent = tt.parent(); if (node instanceof AST_Assign && node.operator != "=" && lhs.equivalent_to(node.left) + || node instanceof AST_Await || node instanceof AST_Debugger || node instanceof AST_Destructuring || node instanceof AST_IterationStatement && !(node instanceof AST_For) @@ -3329,7 +3330,7 @@ merge(Compressor.prototype, { } } } - if (exp instanceof AST_Function && !self.expression.is_generator) { + if (exp instanceof AST_Function && !self.expression.is_generator && !self.expression.async) { var stat = exp.body[0]; if (compressor.option("inline") && stat instanceof AST_Return) { var value = stat && stat.value; diff --git a/lib/output.js b/lib/output.js index bbc84681..a30800c1 100644 --- a/lib/output.js +++ b/lib/output.js @@ -978,6 +978,10 @@ function OutputStream(options) { AST_Lambda.DEFMETHOD("_do_print", function(output, nokeyword){ var self = this; if (!nokeyword) { + if (this.async) { + output.print("async"); + output.space(); + } output.print("function"); if (this.is_generator) { output.star(); @@ -1083,6 +1087,22 @@ function OutputStream(options) { } }); + DEFPRINT(AST_Await, function(self, output){ + output.print("await"); + output.space(); + var e = self.expression; + var parens = !( + e instanceof AST_Call + || e instanceof AST_SymbolRef + || e instanceof AST_PropAccess + || e instanceof AST_Unary + || e instanceof AST_Constant + ); + if (parens) output.print("("); + self.expression.print(output); + if (parens) output.print(")"); + }); + /* -----[ loop control ]----- */ AST_LoopControl.DEFMETHOD("_do_print", function(output, kind){ output.print(kind); @@ -1629,7 +1649,7 @@ function OutputStream(options) { self._print_getter_setter("get", output); }); DEFPRINT(AST_ConciseMethod, function(self, output){ - self._print_getter_setter(self.is_generator && "*", output); + self._print_getter_setter(self.is_generator && "*" || self.async && "async", output); }); AST_Symbol.DEFMETHOD("_do_print", function(output){ var def = this.definition(); diff --git a/lib/parse.js b/lib/parse.js index c7431ceb..d5d2b69f 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -47,7 +47,7 @@ var KEYWORDS = 'break case catch class const continue debugger default delete do else export extends finally for function if in instanceof new return switch throw try typeof var let void while with import'; var KEYWORDS_ATOM = 'false null true'; var RESERVED_WORDS = 'enum implements interface package private protected public static super this ' + KEYWORDS_ATOM + " " + KEYWORDS; -var KEYWORDS_BEFORE_EXPRESSION = 'return new delete throw else case yield'; +var KEYWORDS_BEFORE_EXPRESSION = 'return new delete throw else case yield await'; KEYWORDS = makePredicate(KEYWORDS); RESERVED_WORDS = makePredicate(RESERVED_WORDS); @@ -873,6 +873,7 @@ function parse($TEXT, options) { prev : null, peeked : null, in_function : 0, + in_async : -1, in_generator : -1, in_directives : true, in_loop : 0, @@ -943,6 +944,10 @@ function parse($TEXT, options) { return S.in_generator === S.in_function; } + function is_in_async() { + return S.in_async === S.in_function; + } + function semicolon(optional) { if (is("punc", ";")) next(); else if (!optional && !can_insert_semicolon()) unexpected(); @@ -999,6 +1004,11 @@ function parse($TEXT, options) { return simple_statement(); case "name": + if (S.token.value == "async" && is_token(peek(), "keyword", "function")) { + next(); + next(); + return function_(AST_Defun, false, true); + } return is_token(peek(), "punc", ":") ? labeled_statement() : simple_statement(); @@ -1155,6 +1165,9 @@ function parse($TEXT, options) { // Ecma-262, 12.1.1 Static Semantics: Early Errors token_error(S.prev, "Yield cannot be used as label inside generators"); } + if (label.name === "await" && is_in_async()) { + token_error(S.prev, "await cannot be used as label inside async function"); + } if (find_if(function(l){ return l.name == label.name }, S.labels)) { // ECMA-262, 12.12: An ECMAScript program is considered // syntactically incorrect if it contains a @@ -1285,13 +1298,14 @@ function parse($TEXT, options) { }); }; - var function_ = function(ctor, is_generator_property) { + var function_ = function(ctor, is_generator_property, is_async) { + if (is_generator_property && is_async) croak("generators cannot be async"); var start = S.token var in_statement = ctor === AST_Defun; var is_generator = is("operator", "*"); if (is_generator) { - next(); + next(); } var name = is("name") ? as_symbol(in_statement ? AST_SymbolDefun : AST_SymbolLambda) : null; @@ -1299,11 +1313,12 @@ function parse($TEXT, options) { unexpected(); var args = parameters(); - var body = _function_body(true, is_generator || is_generator_property, name, args); + var body = _function_body(true, is_generator || is_generator_property, is_async, name, args); return new ctor({ start : args.start, end : body.end, is_generator: is_generator, + async : is_async, name : name, argnames: args, body : body @@ -1624,13 +1639,16 @@ function parse($TEXT, options) { return a; } - function _function_body(block, generator, name, args) { + function _function_body(block, generator, is_async, name, args) { var loop = S.in_loop; var labels = S.labels; var current_generator = S.in_generator; + var current_async = S.in_async; ++S.in_function; if (generator) S.in_generator = S.in_function; + if (is_async) + S.in_async = S.in_function; if (block) S.in_directives = true; S.in_loop = 0; @@ -1650,9 +1668,22 @@ function parse($TEXT, options) { S.in_loop = loop; S.labels = labels; S.in_generator = current_generator; + S.in_async = current_async; return a; } + function _await_expression() { + // Previous token must be "await" and not be interpreted as an identifier + if (!is_in_async()) { + croak("Unexpected await expression outside async function", + S.prev.line, S.prev.col, S.prev.pos); + } + // the await expression is parsed as a unary expression in Babel + return new AST_Await({ + expression : maybe_unary(true), + }); + } + function _yield_expression() { // Previous token must be keyword yield and not be interpret as an identifier if (!is_in_generator()) { @@ -1661,7 +1692,6 @@ function parse($TEXT, options) { } var star = false; var has_expression = true; - var tmp; // Attempt to get expression or star (and then the mandatory expression) // behind yield on the same line. @@ -1986,6 +2016,14 @@ function parse($TEXT, options) { } unexpected(); } + if (is("name", "async") && is_token(peek(), "keyword", "function")) { + next(); + next(); + var func = function_(AST_Function, false, true); + func.start = start; + func.end = prev(); + return subscripts(func, allow_calls); + } if (is("keyword", "function")) { next(); var func = function_(AST_Function); @@ -2067,8 +2105,8 @@ function parse($TEXT, options) { }); }); - var create_accessor = embed_tokens(function(is_generator) { - return function_(AST_Accessor, is_generator); + var create_accessor = embed_tokens(function(is_generator, is_async) { + return function_(AST_Accessor, is_generator, is_async); }); var object_or_object_destructuring_ = embed_tokens(function() { @@ -2184,6 +2222,7 @@ function parse($TEXT, options) { } return name; } + var is_async = false; var is_static = false; var is_generator = false; var property_token = start; @@ -2192,6 +2231,11 @@ function parse($TEXT, options) { property_token = S.token; name = as_property_name(); } + if (name === "async" && !is("punc", "(")) { + is_async = true; + property_token = S.token; + name = as_property_name(); + } if (name === null) { is_generator = true; property_token = S.token; @@ -2206,10 +2250,11 @@ function parse($TEXT, options) { start : start, static : is_static, is_generator: is_generator, + async : is_async, key : name, quote : name instanceof AST_SymbolMethod ? property_token.quote : undefined, - value : create_accessor(is_generator), + value : create_accessor(is_generator, is_async), end : prev() }); return node; @@ -2574,6 +2619,14 @@ function parse($TEXT, options) { var maybe_unary = function(allow_calls) { var start = S.token; + if (start.type == "name" && start.value == "await") { + if (is_in_async()) { + next(); + return _await_expression(); + } else if (S.input.has_directive("use strict")) { + token_error(S.token, "Unexpected await identifier inside strict mode") + } + } if (is("operator") && UNARY_PREFIX(start.value)) { next(); handle_regexp(); diff --git a/lib/scope.js b/lib/scope.js index 83b7576e..345a3a09 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -590,6 +590,8 @@ AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){ base54.consider("finally"); else if (node instanceof AST_Yield) base54.consider("yield"); + else if (node instanceof AST_Await) + base54.consider("await"); else if (node instanceof AST_Symbol && node.unmangleable(options)) base54.consider(node.name); else if (node instanceof AST_Unary || node instanceof AST_Binary) diff --git a/lib/transform.js b/lib/transform.js index 3e9118e6..1f10e274 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -199,6 +199,10 @@ TreeTransformer.prototype = new TreeWalker; if (self.expression) self.expression = self.expression.transform(tw); }); + _(AST_Await, function(self, tw){ + self.expression = self.expression.transform(tw); + }); + _(AST_Unary, function(self, tw){ self.expression = self.expression.transform(tw); }); diff --git a/test/compress/async.js b/test/compress/async.js new file mode 100644 index 00000000..8c84f94f --- /dev/null +++ b/test/compress/async.js @@ -0,0 +1,200 @@ +await_precedence: { + input: { + async function f1() { await x + y; } + async function f2() { await (x + y); } + } + expect_exact: "async function f1(){await x+y}async function f2(){await(x+y)}" +} + +async_function_declaration: { + options = { + side_effects: true, + unused: true, + } + input: { + async function f0() {} + async function f1() { await x + y; } + async function f2() { await (x + y); } + async function f3() { await x + await y; } + async function f4() { await (x + await y); } + async function f5() { await x; await y; } + async function f6() { await x, await y; } + } + expect: { + async function f0() {} + async function f1() { await x, y; } + async function f2() { await (x + y); } + async function f3() { await x, await y; } + async function f4() { await (x + await y); } + async function f5() { await x; await y; } + async function f6() { await x, await y; } + } +} + +async_function_expression: { + options = { + evaluate: true, + side_effects: true, + unused: true, + } + input: { + var named = async function foo() { + await bar(1 + 0) + (2 + 0); + } + var anon = async function() { + await (1 + 0) + bar(2 + 0); + } + } + expect: { + var named = async function() { + await bar(1); + }; + var anon = async function() { + await 1, bar(2); + }; + } +} + +async_class: { + options = { + evaluate: true, + } + input: { + class Foo { + async m1() { + return await foo(1 + 2); + } + static async m2() { + return await foo(3 + 4); + } + } + } + expect: { + class Foo { + async m1() { + return await foo(3); + } + static async m2() { + return await foo(7); + } + } + } +} + +async_object_literal: { + options = { + evaluate: true, + } + input: { + var obj = { + async a() { + await foo(1 + 0); + }, + anon: async function(){ + await foo(2 + 0); + } + }; + } + expect: { + var obj = { + async a() { + await foo(1); + }, + anon: async function() { + await foo(2); + } + }; + } +} + +async_export: { + input: { + export async function run() {}; + export default async function def() {}; + } + expect: { + export async function run() {}; + export default async function def() {}; + } +} + +async_inline: { + options = { + collapse_vars: true, + conditionals: true, + evaluate: true, + inline: true, + negate_iife: true, + reduce_vars: true, + side_effects: true, + toplevel: true, + unused: true, + } + input: { + (async function(){ return await 3; })(); + (async function(x){ await console.log(x); })(4); + + function echo(x) { return x; } + echo( async function(){ return await 1; }() ); + echo( async function(x){ await console.log(x); }(2) ); + + function top() { console.log("top"); } + top(); + + async function async_top() { console.log("async_top"); } + async_top(); + } + expect: { + !async function(){await 3}(); + !async function(x){await console.log(4)}(); + + function echo(x){return x} + echo(async function(){return await 1}()); + echo(async function(x){await console.log(2)}()); + + console.log("top"); + + !async function(){console.log("async_top")}(); + } + expect_stdout: [ + "4", + "2", + "top", + "async_top", + ] + node_version: ">=8" +} + +async_identifiers: { + input: { + let async = function(x){ console.log("async", x); }; + let await = function(x){ console.log("await", x); }; + async(1); + await(2); + } + expect: { + let async = function(x){ console.log("async", x); }; + let await = function(x){ console.log("await", x); }; + async(1); + await(2); + } + expect_stdout: [ + "async 1", + "await 2", + ] + node_version: ">=8" +} + +/* FIXME: add test when supported by parser +async_arrow: { + input: { + let a1 = async x => await foo(x); + let a2 = async () => await bar(); + let a3 = async (x) => await baz(x); + let a4 = async (x, y) => { await far(x, y); } + let a5 = async ({x = [1], y: z = 2}) => { await wow(x, y); } + } + expect: { + } +} +*/