From a96f087ac33b6d18d5ffa3cacd39dd693defb7cf Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Thu, 17 Dec 2020 10:23:41 +0000 Subject: [PATCH] support arrow function (#4385) --- README.md | 8 + lib/ast.js | 55 ++++++- lib/compress.js | 65 ++++++-- lib/output.js | 26 +++- lib/parse.js | 156 ++++++++++++++----- lib/scope.js | 13 +- lib/transform.js | 8 + lib/utils.js | 8 +- test/compress/arrows.js | 338 ++++++++++++++++++++++++++++++++++++++++ test/reduce.js | 3 +- test/ufuzz/index.js | 169 ++++++++++++++------ 11 files changed, 732 insertions(+), 117 deletions(-) create mode 100644 test/compress/arrows.js diff --git a/README.md b/README.md index c65229fa..4753b4c2 100644 --- a/README.md +++ b/README.md @@ -1209,3 +1209,11 @@ To allow for better optimizations, the compiler makes various assumptions: `function({}, arguments) {}` will result in `SyntaxError` in earlier versions of Chrome and Node.js - UglifyJS may modify the input which in turn may suppress those errors. +- Later versions of JavaScript will throw `SyntaxError` with the following: + ```js + a => { + let a; + }; + // SyntaxError: Identifier 'a' has already been declared + ``` + UglifyJS may modify the input which in turn may suppress those errors. diff --git a/lib/ast.js b/lib/ast.js index 31281c1e..87d578fc 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -502,10 +502,9 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", { } }, AST_Scope); -var AST_Lambda = DEFNODE("Lambda", "name argnames length_read uses_arguments", { +var AST_Lambda = DEFNODE("Lambda", "argnames length_read uses_arguments", { $documentation: "Base class for functions", $propdoc: { - name: "[AST_SymbolDeclaration?] the name of this function", argnames: "[(AST_Destructured|AST_SymbolFunarg)*] array of function arguments and/or destructured literals", uses_arguments: "[boolean/S] tells whether this function accesses the arguments array", }, @@ -541,18 +540,49 @@ var AST_Lambda = DEFNODE("Lambda", "name argnames length_read uses_arguments", { }, AST_Scope); var AST_Accessor = DEFNODE("Accessor", null, { - $documentation: "A setter/getter function. The `name` property is always null.", + $documentation: "A getter/setter function", _validate: function() { if (this.name != null) throw new Error("name must be null"); }, }, AST_Lambda); function is_function(node) { - return node instanceof AST_AsyncFunction || node instanceof AST_Function; + return node instanceof AST_Arrow || node instanceof AST_AsyncFunction || node instanceof AST_Function; } -var AST_AsyncFunction = DEFNODE("AsyncFunction", "inlined", { +var AST_Arrow = DEFNODE("Arrow", "inlined value", { + $documentation: "An arrow function expression", + $propdoc: { + value: "[AST_Node?] simple return expression, or null if using function body.", + }, + walk: function(visitor) { + var node = this; + visitor.visit(node, function() { + node.argnames.forEach(function(argname) { + argname.walk(visitor); + }); + if (node.value) { + node.value.walk(visitor); + } else { + walk_body(node, visitor); + } + }); + }, + _validate: function() { + if (this.name != null) throw new Error("name must be null"); + if (this.uses_arguments) throw new Error("uses_arguments must be false"); + if (this.value != null) { + must_be_expression(this, "value"); + if (this.body.length) throw new Error("body must be empty if value exists"); + } + }, +}, AST_Lambda); + +var AST_AsyncFunction = DEFNODE("AsyncFunction", "inlined name", { $documentation: "An asynchronous function expression", + $propdoc: { + name: "[AST_SymbolLambda?] the name of this function", + }, _validate: function() { if (this.name != null) { if (!(this.name instanceof AST_SymbolLambda)) throw new Error("name must be AST_SymbolLambda"); @@ -560,8 +590,11 @@ var AST_AsyncFunction = DEFNODE("AsyncFunction", "inlined", { }, }, AST_Lambda); -var AST_Function = DEFNODE("Function", "inlined", { +var AST_Function = DEFNODE("Function", "inlined name", { $documentation: "A function expression", + $propdoc: { + name: "[AST_SymbolLambda?] the name of this function", + }, _validate: function() { if (this.name != null) { if (!(this.name instanceof AST_SymbolLambda)) throw new Error("name must be AST_SymbolLambda"); @@ -573,15 +606,21 @@ function is_defun(node) { return node instanceof AST_AsyncDefun || node instanceof AST_Defun; } -var AST_AsyncDefun = DEFNODE("AsyncDefun", "inlined", { +var AST_AsyncDefun = DEFNODE("AsyncDefun", "inlined name", { $documentation: "An asynchronous function definition", + $propdoc: { + name: "[AST_SymbolDefun] the name of this function", + }, _validate: function() { if (!(this.name instanceof AST_SymbolDefun)) throw new Error("name must be AST_SymbolDefun"); }, }, AST_Lambda); -var AST_Defun = DEFNODE("Defun", "inlined", { +var AST_Defun = DEFNODE("Defun", "inlined name", { $documentation: "A function definition", + $propdoc: { + name: "[AST_SymbolDefun] the name of this function", + }, _validate: function() { if (!(this.name instanceof AST_SymbolDefun)) throw new Error("name must be AST_SymbolDefun"); }, diff --git a/lib/compress.js b/lib/compress.js index a46cd42b..ffc19b25 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -49,6 +49,7 @@ function Compressor(options, false_by_default) { TreeTransformer.call(this, this.before, this.after); this.options = defaults(options, { arguments : !false_by_default, + arrows : !false_by_default, assignments : !false_by_default, booleans : !false_by_default, collapse_vars : !false_by_default, @@ -1372,7 +1373,8 @@ merge(Compressor.prototype, { function is_iife_call(node) { if (node.TYPE != "Call") return false; - return is_function(node.expression) || is_iife_call(node.expression); + var exp = node.expression; + return exp instanceof AST_AsyncFunction || exp instanceof AST_Function || is_iife_call(exp); } function is_undeclared_ref(node) { @@ -3573,12 +3575,20 @@ merge(Compressor.prototype, { return map; } - AST_Lambda.DEFMETHOD("first_statement", function() { - var body = this.body; + function skip_directives(body) { for (var i = 0; i < body.length; i++) { var stat = body[i]; if (!(stat instanceof AST_Directive)) return stat; } + } + AST_Arrow.DEFMETHOD("first_statement", function() { + if (this.value) return make_node(AST_Return, this.value, { + value: this.value + }); + return skip_directives(this.body); + }); + AST_Lambda.DEFMETHOD("first_statement", function() { + return skip_directives(this.body); }); function try_evaluate(compressor, node) { @@ -4170,6 +4180,9 @@ merge(Compressor.prototype, { def(AST_Statement, function() { throw new Error("Cannot negate a statement"); }); + def(AST_Arrow, function() { + return basic_negation(this); + }); def(AST_AsyncFunction, function() { return basic_negation(this); }); @@ -4568,6 +4581,10 @@ merge(Compressor.prototype, { result = false; return true; } + if (node instanceof AST_This) { + if (scopes.length == 0 && self instanceof AST_Arrow) result = false; + return true; + } })); return result; }); @@ -4651,6 +4668,28 @@ merge(Compressor.prototype, { return trim_block(self); }); + OPT(AST_Arrow, function(self, compressor) { + if (!compressor.option("arrows")) return self; + if (self.value) { + var value = self.value; + if (is_undefined(value, compressor)) { + self.value = null; + } else if (value instanceof AST_UnaryPrefix && value.operator == "void") { + self.body.push(make_node(AST_SimpleStatement, value, { + body: value.expression + })); + self.value = null; + } + } else if (self.body.length == 1) { + var stat = self.body[0]; + if (stat instanceof AST_Return && stat.value) { + self.body.pop(); + self.value = stat.value; + } + } + return self; + }); + OPT(AST_Function, function(self, compressor) { self.body = tighten_body(self.body, compressor); if (compressor.option("inline")) for (var i = 0; i < self.body.length; i++) { @@ -6316,6 +6355,7 @@ merge(Compressor.prototype, { })) return this; return make_sequence(this, values.map(convert_spread)); }); + def(AST_Arrow, return_null); def(AST_Assign, function(compressor) { var left = this.left; if (left instanceof AST_PropAccess) { @@ -7702,7 +7742,7 @@ merge(Compressor.prototype, { } } var fn = exp instanceof AST_SymbolRef ? exp.fixed_value() : exp; - var is_func = fn instanceof AST_Defun || fn instanceof AST_Function; + var is_func = fn instanceof AST_Arrow || fn instanceof AST_Defun || fn instanceof AST_Function; var stat = is_func && fn.first_statement(); var can_inline = is_func && compressor.option("inline") @@ -7772,10 +7812,11 @@ merge(Compressor.prototype, { } if (compressor.option("side_effects") && all(fn.body, is_empty) + && (fn !== exp || fn_name_unused(fn, compressor)) + && !(fn instanceof AST_Arrow && fn.value) && all(fn.argnames, function(argname) { return !(argname instanceof AST_Destructured); - }) - && (fn !== exp || fn_name_unused(fn, compressor))) { + })) { var args = self.args.map(function(arg) { return arg instanceof AST_Spread ? make_node(AST_Array, arg, { elements: [ arg ], @@ -7814,9 +7855,11 @@ merge(Compressor.prototype, { function can_flatten_body(stat) { var len = fn.body.length; - if (compressor.option("inline") < 3) { - return len == 1 && return_value(stat); + if (len < 2) { + stat = return_value(stat); + if (stat) return stat; } + if (compressor.option("inline") < 3) return false; stat = null; for (var i = 0; i < len; i++) { var line = fn.body[i]; @@ -9833,7 +9876,7 @@ merge(Compressor.prototype, { && expr instanceof AST_SymbolRef && is_arguments(def = expr.definition()) && prop instanceof AST_Number - && (fn = expr.scope.resolve()) === find_lambda() + && (fn = def.scope) === find_lambda() && !(assigned && fn.uses_arguments === "d")) { var index = prop.value; if (parent instanceof AST_UnaryPrefix && parent.operator == "delete") { @@ -9936,6 +9979,7 @@ merge(Compressor.prototype, { while (p = compressor.parent(i++)) { if (p instanceof AST_Lambda) { if (p instanceof AST_Accessor) return; + if (p instanceof AST_Arrow) continue; fn_parent = compressor.parent(i); return p; } @@ -9943,13 +9987,14 @@ merge(Compressor.prototype, { } }); + AST_Arrow.DEFMETHOD("contains_this", return_false); AST_Scope.DEFMETHOD("contains_this", function() { var result; var self = this; self.walk(new TreeWalker(function(node) { if (result) return true; if (node instanceof AST_This) return result = true; - if (node !== self && node instanceof AST_Scope) return true; + if (node !== self && node instanceof AST_Scope && !(node instanceof AST_Arrow)) return true; })); return result; }); diff --git a/lib/output.js b/lib/output.js index a6d0d0da..7bb27d9b 100644 --- a/lib/output.js +++ b/lib/output.js @@ -678,7 +678,7 @@ function OutputStream(options) { // same goes for an object literal, because otherwise it would be // interpreted as a block of code. function needs_parens_obj(output) { - return !output.has_parens() && first_in_statement(output); + return !output.has_parens() && first_in_statement(output, true); } PARENS(AST_Object, needs_parens_obj); @@ -691,6 +691,8 @@ function OutputStream(options) { var p = output.parent(); // [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ] return p instanceof AST_Array + // () => (foo, bar) + || p instanceof AST_Arrow && p.value === this // await (foo, bar) || p instanceof AST_Await // 1 + (2, 3) + 4 ==> 8 @@ -798,6 +800,9 @@ function OutputStream(options) { // !(a = false) → true if (p instanceof AST_Unary) return true; } + PARENS(AST_Arrow, function(output) { + return needs_parens_assign_cond(this, output); + }); PARENS(AST_Assign, function(output) { if (needs_parens_assign_cond(this, output)) return true; // v8 parser bug => workaround @@ -985,6 +990,25 @@ function OutputStream(options) { }); /* -----[ functions ]----- */ + DEFPRINT(AST_Arrow, function(output) { + var self = this; + if (self.argnames.length == 1 && self.argnames[0] instanceof AST_SymbolFunarg) { + self.argnames[0].print(output); + } else output.with_parens(function() { + self.argnames.forEach(function(arg, i) { + if (i) output.comma(); + arg.print(output); + }); + }); + output.space(); + output.print("=>"); + output.space(); + if (self.value) { + self.value.print(output); + } else { + print_braced(self, output, true); + } + }); function print_lambda(self, output) { if (self.name) { output.space(); diff --git a/lib/parse.js b/lib/parse.js index c65b2f42..f760d0de 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -569,6 +569,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { } if (is_digit(code)) return read_num(); if (PUNC_CHARS[ch]) return token("punc", next()); + if (looking_at("=>")) return token("punc", next() + next()); if (OPERATOR_CHARS[ch]) return read_operator(); if (code == 92 || !NON_IDENTIFIER_CHARS[ch]) return read_word(); break; @@ -634,7 +635,7 @@ var PRECEDENCE = function(a, ret) { ["*", "/", "%"] ], {}); -var ATOMIC_START_TOKEN = makePredicate("atom num string regexp name"); +var ATOMIC_START_TOKEN = makePredicate("atom num regexp string"); /* -----[ Parser ]----- */ @@ -741,7 +742,7 @@ function parse($TEXT, options) { function parenthesised() { expect("("); - var exp = expression(true); + var exp = expression(); expect(")"); return exp; } @@ -769,7 +770,7 @@ function parse($TEXT, options) { switch (S.token.type) { case "string": var dir = S.in_directives; - var body = expression(true); + var body = expression(); if (dir) { if (body instanceof AST_String) { var value = body.start.raw.slice(1, -1); @@ -887,7 +888,7 @@ function parse($TEXT, options) { if (is("punc", ";")) { next(); } else if (!can_insert_semicolon()) { - value = expression(true); + value = expression(); semicolon(); } return new AST_Return({ @@ -905,7 +906,7 @@ function parse($TEXT, options) { next(); if (has_newline_before(S.token)) croak("Illegal newline after 'throw'"); - var value = expression(true); + var value = expression(); semicolon(); return new AST_Throw({ value: value @@ -956,9 +957,7 @@ function parse($TEXT, options) { // https://github.com/mishoo/UglifyJS/issues/287 label.references.forEach(function(ref) { if (ref instanceof AST_Continue) { - ref = ref.label.start; - croak("Continue label `" + label.name + "` refers to non-IterationStatement.", - ref.line, ref.col, ref.pos); + token_error(ref.label.start, "Continue label `" + label.name + "` must refer to IterationStatement"); } }); } @@ -966,7 +965,7 @@ function parse($TEXT, options) { } function simple_statement() { - var body = expression(true); + var body = expression(); semicolon(); return new AST_SimpleStatement({ body: body }); } @@ -980,7 +979,7 @@ function parse($TEXT, options) { ldef = find_if(function(l) { return l.name == label.name; }, S.labels); - if (!ldef) croak("Undefined label " + label.name); + if (!ldef) token_error(label.start, "Undefined label " + label.name); label.thedef = ldef; } else if (S.in_loop == 0) croak(type.TYPE + " not inside a loop or switch"); semicolon(); @@ -999,13 +998,14 @@ function parse($TEXT, options) { ? (next(), let_(true)) : is("keyword", "var") ? (next(), var_(true)) - : expression(true, true); + : expression(true); if (is("operator", "in")) { if (init instanceof AST_Definitions) { - if (init.definitions.length > 1) - croak("Only one variable declaration allowed in for..in loop", init.start.line, init.start.col, init.start.pos); + if (init.definitions.length > 1) { + token_error(init.start, "Only one variable declaration allowed in for..in loop"); + } } else if (!(is_assignable(init) || (init = to_destructured(init)) instanceof AST_Destructured)) { - croak("Invalid left-hand side in for..in loop", init.start.line, init.start.col, init.start.pos); + token_error(init.start, "Invalid left-hand side in for..in loop"); } next(); return for_in(init); @@ -1016,9 +1016,9 @@ function parse($TEXT, options) { function regular_for(init) { expect(";"); - var test = is("punc", ";") ? null : expression(true); + var test = is("punc", ";") ? null : expression(); expect(";"); - var step = is("punc", ")") ? null : expression(true); + var step = is("punc", ")") ? null : expression(); expect(")"); return new AST_For({ init : init, @@ -1029,7 +1029,7 @@ function parse($TEXT, options) { } function for_in(init) { - var obj = expression(true); + var obj = expression(); expect(")"); return new AST_ForIn({ init : init, @@ -1038,6 +1038,71 @@ function parse($TEXT, options) { }); } + function to_funarg(node) { + if (node instanceof AST_Array) return new AST_DestructuredArray({ + start: node.start, + elements: node.elements.map(function(node) { + return node instanceof AST_Hole ? node : to_funarg(node); + }), + end: node.end, + }); + if (node instanceof AST_Object) return new AST_DestructuredObject({ + start: node.start, + properties: node.properties.map(function(prop) { + if (!(prop instanceof AST_ObjectKeyVal)) token_error(prop.start, "Invalid destructuring assignment"); + return new AST_DestructuredKeyVal({ + start: prop.start, + key: prop.key, + value: to_funarg(prop.value), + end: prop.end, + }); + }), + end: node.end, + }); + if (node instanceof AST_SymbolRef) return new AST_SymbolFunarg(node); + token_error(node.start, "Invalid arrow parameter"); + } + + function arrow(exprs, start) { + var was_async = S.in_async; + S.in_async = false; + var was_funarg = S.in_funarg; + S.in_funarg = S.in_function; + var argnames = exprs.map(to_funarg); + S.in_funarg = was_funarg; + expect("=>"); + var body, value; + var loop = S.in_loop; + var labels = S.labels; + ++S.in_function; + S.in_directives = true; + S.input.push_directives_stack(); + S.in_loop = 0; + S.labels = []; + if (is("punc", "{")) { + body = block_(); + value = null; + if (S.input.has_directive("use strict")) { + argnames.forEach(strict_verify_symbol); + } + } else { + body = []; + value = maybe_assign(); + } + S.input.pop_directives_stack(); + --S.in_function; + S.in_loop = loop; + S.labels = labels; + S.in_async = was_async; + return new AST_Arrow({ + start: start, + argnames: argnames, + body: body, + value: value, + end: prev(), + }); + } + var function_ = function(ctor) { var was_async = S.in_async; var name; @@ -1118,7 +1183,7 @@ function parse($TEXT, options) { cur = []; branch = new AST_Case({ start : (tmp = S.token, next(), tmp), - expression : expression(true), + expression : expression(), body : cur }); a.push(branch); @@ -1187,7 +1252,7 @@ function parse($TEXT, options) { var value = null; if (is("operator", "=")) { next(); - value = expression(false, no_in); + value = maybe_assign(no_in); } else if (!no_in && (type === AST_SymbolConst || name instanceof AST_Destructured)) { croak("Missing initializer in declaration"); } @@ -1251,9 +1316,6 @@ function parse($TEXT, options) { function as_atom_node() { var tok = S.token, ret; switch (tok.type) { - case "name": - ret = _make_symbol(AST_SymbolRef, tok); - break; case "num": ret = new AST_Number({ start: tok, end: tok, value: tok.value }); break; @@ -1295,7 +1357,11 @@ function parse($TEXT, options) { switch (start.value) { case "(": next(); - var ex = expression(true); + if (is("punc", ")")) { + next(); + return arrow([], start); + } + var ex = expression(false, true); var len = start.comments_before.length; [].unshift.apply(ex.start.comments_before, start.comments_before); start.comments_before.length = 0; @@ -1318,6 +1384,7 @@ function parse($TEXT, options) { end.comments_after = ex.end.comments_after; ex.end = end; if (ex instanceof AST_Call) mark_pure(ex); + if (is("punc", "=>")) return arrow(ex instanceof AST_Sequence ? ex.expressions : [ ex ], start); return subscripts(ex, allow_calls); case "[": return subscripts(array_(), allow_calls); @@ -1340,6 +1407,11 @@ function parse($TEXT, options) { func.end = prev(); return subscripts(func, allow_calls); } + if (is("name")) { + var sym = _make_symbol(AST_SymbolRef, start); + next(); + return is("punc", "=>") ? arrow([ sym ], start) : subscripts(sym, allow_calls); + } if (ATOMIC_START_TOKEN[S.token.type]) { return subscripts(as_atom_node(), allow_calls); } @@ -1347,14 +1419,14 @@ function parse($TEXT, options) { }; function expr_list(closing, allow_trailing_comma, allow_empty, parser) { - if (!parser) parser = expression; + if (!parser) parser = maybe_assign; var first = true, a = []; while (!is("punc", closing)) { if (first) first = false; else expect(","); if (allow_trailing_comma && is("punc", closing)) break; if (allow_empty && is("punc", ",")) { a.push(new AST_Hole({ start: S.token, end: S.token })); - } else if (parser === expression && is("operator", "...")) { + } else if (parser === maybe_assign && is("operator", "...")) { a.push(new AST_Spread({ start: S.token, expression: (next(), parser()), @@ -1391,7 +1463,7 @@ function parse($TEXT, options) { next(); a.push(new AST_Spread({ start: start, - expression: expression(false), + expression: maybe_assign(), end: prev(), })); continue; @@ -1440,7 +1512,7 @@ function parse($TEXT, options) { a.push(new AST_ObjectKeyVal({ start: start, key: key, - value: expression(false), + value: maybe_assign(), end: prev(), })); } @@ -1463,7 +1535,7 @@ function parse($TEXT, options) { case "punc": if (tmp.value != "[") unexpected(); next(); - var key = expression(false); + var key = maybe_assign(); expect("]"); return key; default: @@ -1490,7 +1562,7 @@ function parse($TEXT, options) { function strict_verify_symbol(sym) { if (sym.name == "arguments" || sym.name == "eval") - croak("Unexpected " + sym.name + " in strict mode", sym.start.line, sym.start.col, sym.start.pos); + token_error(sym.start, "Unexpected " + sym.name + " in strict mode"); } function as_symbol(type, noerror) { @@ -1580,7 +1652,7 @@ function parse($TEXT, options) { } if (is("punc", "[")) { next(); - var prop = expression(true); + var prop = expression(); expect("]"); return subscripts(new AST_Sub({ start : start, @@ -1629,11 +1701,11 @@ function parse($TEXT, options) { case "++": case "--": if (!is_assignable(expr)) - croak("Invalid use of " + op + " operator", token.line, token.col, token.pos); + token_error(token, "Invalid use of " + op + " operator"); break; case "delete": if (expr instanceof AST_SymbolRef && S.input.has_directive("use strict")) - croak("Calling delete on expression not allowed in strict mode", expr.start.line, expr.start.col, expr.start.pos); + token_error(expr.start, "Calling delete on expression not allowed in strict mode"); break; } return new ctor({ operator: op, expression: expr }); @@ -1679,13 +1751,13 @@ function parse($TEXT, options) { var expr = expr_ops(no_in); if (is("operator", "?")) { next(); - var yes = expression(false); + var yes = maybe_assign(); expect(":"); return new AST_Conditional({ start : start, condition : expr, consequent : yes, - alternative : expression(false, no_in), + alternative : maybe_assign(no_in), end : prev() }); } @@ -1728,7 +1800,7 @@ function parse($TEXT, options) { }); } - var maybe_assign = function(no_in) { + function maybe_assign(no_in) { var start = S.token; var left = maybe_conditional(no_in), val = S.token.value; if (is("operator") && ASSIGNMENT[val]) { @@ -1745,23 +1817,23 @@ function parse($TEXT, options) { croak("Invalid assignment"); } return left; - }; + } - var expression = function(commas, no_in) { + function expression(no_in, maybe_arrow) { var start = S.token; var exprs = []; while (true) { exprs.push(maybe_assign(no_in)); - if (!commas || !is("punc", ",")) break; + if (!is("punc", ",")) break; next(); - commas = true; + if (maybe_arrow && is("punc", ")") && is_token(peek(), "punc", "=>")) break; } return exprs.length == 1 ? exprs[0] : new AST_Sequence({ start : start, expressions : exprs, - end : peek() + end : prev() }); - }; + } function in_loop(cont) { ++S.in_loop; @@ -1772,7 +1844,7 @@ function parse($TEXT, options) { if (options.expression) { handle_regexp(); - return expression(true); + return expression(); } return function() { diff --git a/lib/scope.js b/lib/scope.js index ff0cb58c..a285f919 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -212,7 +212,11 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) { argname.walk(tw); }); in_arg.pop(); - walk_body(node, tw); + if (node instanceof AST_Arrow && node.value) { + node.value.walk(tw); + } else { + walk_body(node, tw); + } return true; } if (node instanceof AST_LoopControl) { @@ -249,7 +253,9 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) { } if (!sym) { sym = self.def_global(node); - } else if (name == "arguments" && sym.scope instanceof AST_Lambda) { + } else if (name == "arguments" + && sym.orig[0] instanceof AST_SymbolFunarg + && !(sym.scope instanceof AST_Arrow)) { if (!(tw.parent() instanceof AST_PropAccess)) { sym.scope.uses_arguments = "d"; } else if (!sym.scope.uses_arguments) { @@ -360,6 +366,9 @@ AST_BlockScope.DEFMETHOD("init_vars", function(parent_scope) { AST_Scope.DEFMETHOD("init_vars", function(parent_scope) { init_scope_vars(this, parent_scope); }); +AST_Arrow.DEFMETHOD("init_vars", function(parent_scope) { + init_scope_vars(this, parent_scope); +}); AST_Lambda.DEFMETHOD("init_vars", function(parent_scope) { init_scope_vars(this, parent_scope); this.uses_arguments = false; diff --git a/lib/transform.js b/lib/transform.js index 398cf73d..5372cc59 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -131,6 +131,14 @@ TreeTransformer.prototype = new TreeWalker; self.argnames = do_list(self.argnames, tw); self.body = do_list(self.body, tw); }); + DEF(AST_Arrow, function(self, tw) { + self.argnames = do_list(self.argnames, tw); + if (self.value) { + self.value = self.value.transform(tw); + } else { + self.body = do_list(self.body, tw); + } + }); DEF(AST_Call, function(self, tw) { self.expression = self.expression.transform(tw); self.args = do_list(self.args, tw); diff --git a/lib/utils.js b/lib/utils.js index 2a0df8c5..286266a2 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -238,13 +238,15 @@ function HOP(obj, prop) { // return true if the node at the top of the stack (that means the // innermost node in the current output) is lexically the first in // a statement. -function first_in_statement(stack) { +function first_in_statement(stack, arrow) { var node = stack.parent(-1); for (var i = 0, p; p = stack.parent(i++); node = p) { - if (p.TYPE == "Call") { - if (p.expression === node) continue; + if (p instanceof AST_Arrow) { + return arrow && p.value === node; } else if (p instanceof AST_Binary) { if (p.left === node) continue; + } else if (p.TYPE == "Call") { + if (p.expression === node) continue; } else if (p instanceof AST_Conditional) { if (p.condition === node) continue; } else if (p instanceof AST_PropAccess) { diff --git a/test/compress/arrows.js b/test/compress/arrows.js new file mode 100644 index 00000000..02c4acde --- /dev/null +++ b/test/compress/arrows.js @@ -0,0 +1,338 @@ +no_funarg: { + input: { + (() => console.log(42))(); + } + expect_exact: "(()=>console.log(42))();" + expect_stdout: "42" + node_version: ">=4" +} + +single_funarg: { + input: { + (a => console.log(a))(42); + } + expect_exact: "(a=>console.log(a))(42);" + expect_stdout: "42" + node_version: ">=4" +} + +multiple_funargs: { + input: { + ((a, b) => console.log(a, b))("foo", "bar"); + } + expect_exact: '((a,b)=>console.log(a,b))("foo","bar");' + expect_stdout: "foo bar" + node_version: ">=4" +} + +destructured_funarg: { + input: { + (([ a, b, c ]) => console.log(a, b, c))("foo"); + } + expect_exact: '(([a,b,c])=>console.log(a,b,c))("foo");' + expect_stdout: "f o o" + node_version: ">=6" +} + +await_parenthesis: { + input: { + async function f() { + await (a => a); + } + } + expect_exact: "async function f(){await(a=>a)}" +} + +body_call: { + input: { + (() => { + console.log("foo"); + console.log("bar"); + })(); + } + expect_exact: '(()=>{console.log("foo");console.log("bar")})();' + expect_stdout: [ + "foo", + "bar", + ] + node_version: ">=4" +} + +body_conditional: { + input: { + console.log((a => {}) ? "PASS" : "FAIL"); + } + expect_exact: 'console.log((a=>{})?"PASS":"FAIL");' + expect_stdout: "PASS" + node_version: ">=4" +} + +destructured_object_value: { + input: { + console.log((a => ({} = a))(42)); + } + expect_exact: "console.log((a=>({}=a))(42));" + expect_stdout: "42" + node_version: ">=6" +} + +function_value: { + input: { + console.log((a => function() { + return a; + })(42)()); + } + expect_exact: "console.log((a=>function(){return a})(42)());" + expect_stdout: "42" + node_version: ">=4" +} + +in_value: { + input: { + console.log((a => a in { + foo: 42, + })("foo")); + } + expect_exact: 'console.log((a=>a in{foo:42})("foo"));' + expect_stdout: "true" + node_version: ">=4" +} + +object_value: { + input: { + console.log((() => ({ + 4: 2, + }))()[4]); + } + expect_exact: "console.log((()=>({4:2}))()[4]);" + expect_stdout: "2" + node_version: ">=4" +} + +object_first_in_value: { + input: { + console.log((a => ({ + p: a, + }.p ? "FAIL" : "PASS"))()); + } + expect_exact: 'console.log((a=>({p:a}).p?"FAIL":"PASS")());' + expect_stdout: "PASS" + node_version: ">=4" +} + +sequence_value: { + input: { + console.log((a => (console.log("foo"), a))("bar")); + } + expect_exact: 'console.log((a=>(console.log("foo"),a))("bar"));' + expect_stdout: [ + "foo", + "bar", + ] + node_version: ">=4" +} + +side_effects_value: { + options = { + side_effects: true, + } + input: { + console.log((a => function() { + return a; + })(42)()); + } + expect: { + console.log((a => function() { + return a; + })(42)()); + } + expect_stdout: "42" + node_version: ">=4" +} + +arrow_property: { + input: { + console.log((a => 42).prototype); + } + expect_exact: "console.log((a=>42).prototype);" + expect_stdout: "undefined" + node_version: ">=4" +} + +assign_arrow: { + input: { + var f = a => a; + console.log(f(42)); + } + expect_exact: "var f=a=>a;console.log(f(42));" + expect_stdout: "42" + node_version: ">=4" +} + +binary_arrow: { + input: { + console.log(4 || (() => 2)); + } + expect_exact: "console.log(4||(()=>2));" + expect_stdout: "4" + node_version: ">=4" +} + +unary_arrow: { + input: { + console.log(+(() => 42)); + } + expect_exact: "console.log(+(()=>42));" + expect_stdout: "NaN" + node_version: ">=4" +} + +trailing_comma: { + input: { + ((a,) => console.log(a))(42); + } + expect_exact: "(a=>console.log(a))(42);" + expect_stdout: "42" + node_version: ">=4" +} + +drop_arguments: { + options = { + arguments: true, + keep_fargs: false, + } + input: { + console.log(function() { + return () => arguments[0]; + }("PASS")("FAIL")); + } + expect: { + console.log(function(argument_0) { + return () => argument_0; + }("PASS")("FAIL")); + } + expect_stdout: "PASS" + node_version: ">=4" +} + +funarg_arguments: { + options = { + inline: true, + } + input: { + console.log((arguments => arguments)(42)); + } + expect: { + console.log(42); + } + expect_stdout: "42" + node_version: ">=4" +} + +inline_arguments: { + options = { + inline: true, + } + input: { + console.log(function() { + return () => arguments[0]; + }("PASS")("FAIL")); + } + expect: { + console.log(function() { + return () => arguments[0]; + }("PASS")("FAIL")); + } + expect_stdout: "PASS" + node_version: ">=4" +} + +var_arguments: { + options = { + inline: true, + properties: true, + reduce_vars: true, + side_effects: true, + unused: true, + } + input: { + console.log(function() { + return () => { + var arguments = [ "PASS" ]; + return arguments; + }; + }("FAIL 1")("FAIL 2")[0]); + } + expect: { + console.log("PASS"); + } + expect_stdout: "PASS" + node_version: ">=4" +} + +negate: { + options = { + conditionals: true, + } + input: { + if (!console ? 0 : () => 1) + console.log("PASS"); + } + expect: { + (console ? () => 1 : 0) && console.log("PASS"); + } + expect_stdout: "PASS" + node_version: ">=4" +} + +inline_this: { + options = { + inline: true, + } + input: { + var o = { + p: function() { + return function() { + return () => this.q; + }(); + }, + q: "FAIL", + }; + q = "PASS"; + console.log(o.p()()); + } + expect: { + var o = { + p: function() { + return function() { + return () => this.q; + }(); + }, + q: "FAIL", + }; + q = "PASS"; + console.log(o.p()()); + } + expect_stdout: "PASS" + node_version: ">=4" +} + +trim_body: { + options = { + arrows: true, + } + input: { + var f = a => { + return a; + }; + var g = b => void b; + console.log(f("PASS"), g("FAIL")); + } + expect: { + var f = a => a; + var g = b => {}; + console.log(f("PASS"), g("FAIL")); + } + expect_stdout: "PASS undefined" + node_version: ">=4" +} diff --git a/test/reduce.js b/test/reduce.js index 40c447b2..db4b7521 100644 --- a/test/reduce.js +++ b/test/reduce.js @@ -629,7 +629,8 @@ function is_timed_out(result) { } function is_statement(node) { - return node instanceof U.AST_Statement && !(node instanceof U.AST_AsyncFunction || node instanceof U.AST_Function); + return node instanceof U.AST_Statement + && !(node instanceof U.AST_Arrow || node instanceof U.AST_AsyncFunction || node instanceof U.AST_Function); } function merge_sequence(array, node) { diff --git a/test/ufuzz/index.js b/test/ufuzz/index.js index 976fc44a..c4ad4a78 100644 --- a/test/ufuzz/index.js +++ b/test/ufuzz/index.js @@ -132,9 +132,11 @@ var SUPPORT = function(matrix) { } return matrix; }({ + arrow: "a => 0;", async: "async function f(){}", catch_omit_var: "try {} catch {}", computed_key: "({[0]: 0});", + const_block: "var a; { const a = 0; }", destructuring: "[] = [];", let: "let a;", spread: "[...[]];", @@ -361,7 +363,7 @@ function createTopLevelCode() { return [ strictMode(), "var _calls_ = 10, a = 100, b = 10, c = 0;", - rng(2) == 0 + rng(2) ? createStatements(3, MAX_GENERATION_RECURSION_DEPTH, CANNOT_THROW, CANNOT_BREAK, CANNOT_CONTINUE, CANNOT_RETURN, 0) : createFunctions(rng(MAX_GENERATED_TOPLEVELS_PER_RUN) + 1, MAX_GENERATION_RECURSION_DEPTH, DEFUN_OK, CANNOT_THROW, 0), // preceding `null` makes for a cleaner output (empty string still shows up etc) @@ -382,7 +384,9 @@ function addTrailingComma(list) { return SUPPORT.trailing_comma && list && rng(20) == 0 ? list + "," : list; } -function createParams(noDuplicate) { +function createParams(was_async, noDuplicate) { + var save_async = async; + if (was_async) async = true; var len = unique_vars.length; var params = []; for (var n = rng(4); --n >= 0;) { @@ -391,6 +395,7 @@ function createParams(noDuplicate) { params.push(name); } unique_vars.length = len; + async = save_async; return addTrailingComma(params.join(", ")); } @@ -411,13 +416,13 @@ function createArgs(recurmax, stmtDepth, canThrow) { args.push("..." + createArrayLiteral(recurmax, stmtDepth, canThrow)); break; default: - args.push(rng(2) ? createValue() : createExpression(recurmax, COMMA_OK, stmtDepth, canThrow)); + args.push(rng(2) ? createValue() : createExpression(recurmax, NO_COMMA, stmtDepth, canThrow)); break; } return addTrailingComma(args.join(", ")); } -function createAssignmentPairs(recurmax, noComma, stmtDepth, canThrow, varNames, was_async) { +function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was_async) { var avoid = []; var len = unique_vars.length; var pairs = createPairs(recurmax); @@ -425,42 +430,46 @@ function createAssignmentPairs(recurmax, noComma, stmtDepth, canThrow, varNames, return pairs; function createAssignmentValue(recurmax) { - var current = VAR_NAMES; - VAR_NAMES = (varNames || VAR_NAMES).slice(); var save_async = async; if (was_async != null) async = was_async; - var value = varNames && rng(2) ? createValue() : createExpression(recurmax, noComma, stmtDepth, canThrow); + var save_vars = nameLenBefore && VAR_NAMES.splice(nameLenBefore); + var value = nameLenBefore && rng(2) ? createValue() : createExpression(recurmax, NO_COMMA, stmtDepth, canThrow); + if (save_vars) [].push.apply(VAR_NAMES, save_vars); async = save_async; - VAR_NAMES = current; return value; } function createKey(recurmax, keys) { - addAvoidVars(avoid); var key; do { key = createObjectKey(recurmax, stmtDepth, canThrow); } while (keys.indexOf(key) >= 0); - removeAvoidVars(avoid); return key; } + function createName() { + unique_vars.push("a", "b", "c", "undefined", "NaN", "Infinity"); + var save_async = async; + if (was_async) async = true; + var name = createVarName(MANDATORY); + async = save_async; + unique_vars.length -= 6; + avoid.push(name); + unique_vars.push(name); + return name; + } + function createPairs(recurmax) { var names = [], values = []; var m = rng(4), n = rng(4); - if (!varNames) m = Math.max(m, n, 1); + if (!nameLenBefore) m = Math.max(m, n, 1); for (var i = Math.max(m, n); --i >= 0;) { if (i < m && i < n) { createDestructured(recurmax, names, values); continue; } if (i < m) { - unique_vars.push("a", "b", "c", "undefined", "NaN", "Infinity"); - var name = createVarName(MANDATORY); - unique_vars.length -= 6; - avoid.push(name); - unique_vars.push(name); - names.unshift(name); + names.unshift(createName()); } if (i < n) { values.unshift(createAssignmentValue(recurmax)); @@ -512,26 +521,28 @@ function createAssignmentPairs(recurmax, noComma, stmtDepth, canThrow, varNames, keys[index] = key; } }); + if (was_async) avoid.push("await"); + addAvoidVars(avoid); + var save_async = async; + async = false; names.unshift("{ " + addTrailingComma(pairs.names.map(function(name, index) { var key = index in keys ? keys[index] : rng(10) && createKey(recurmax, keys); return key ? key + ": " + name : name; }).join(", ")) + " }"); - var save_async = async; + if (was_async) removeAvoidVars([ avoid.pop() ]); if (was_async != null) async = was_async; + var save_vars = nameLenBefore && VAR_NAMES.splice(nameLenBefore); values.unshift("{ " + addTrailingComma(pairs.values.map(function(value, index) { var key = index in keys ? keys[index] : createKey(recurmax, keys); return key + ": " + value; }).join(", ")) + " }"); + if (save_vars) [].push.apply(VAR_NAMES, save_vars); async = save_async; + removeAvoidVars(avoid); } break; default: - unique_vars.push("a", "b", "c", "undefined", "NaN", "Infinity"); - var name = createVarName(MANDATORY); - unique_vars.length -= 6; - avoid.push(name); - unique_vars.push(name); - names.unshift(name); + names.unshift(createName()); values.unshift(createAssignmentValue(recurmax)); break; } @@ -545,12 +556,12 @@ function filterDirective(s) { function createBlockVariables(recurmax, stmtDepth, canThrow, fn) { var block_len = block_vars.length; - var var_len = VAR_NAMES.length; + var nameLenBefore = VAR_NAMES.length; var consts = []; var lets = []; unique_vars.push("a", "b", "c", "undefined", "NaN", "Infinity"); while (!rng(block_vars.length > block_len ? 10 : 100)) { - var name = createVarName(MANDATORY, DONT_STORE); + var name = createVarName(MANDATORY); if (SUPPORT.let && rng(2)) { lets.push(name); } else { @@ -568,8 +579,8 @@ function createBlockVariables(recurmax, stmtDepth, canThrow, fn) { return createDefinitions("let", lets) + "\n" + createDefinitions("const", consts) + "\n"; } }); + VAR_NAMES.length = nameLenBefore; block_vars.length = block_len; - if (consts.length || lets.length) VAR_NAMES.splice(var_len, consts.length + lets.length); function createDefinitions(type, names) { if (!names.length) return ""; @@ -602,6 +613,7 @@ function createBlockVariables(recurmax, stmtDepth, canThrow, fn) { removeAvoidVars([ name ]); return name + " = " + value; }).join(", ") + ";"; + names.length = 0; break; } removeAvoidVars(names); @@ -609,6 +621,16 @@ function createBlockVariables(recurmax, stmtDepth, canThrow, fn) { } } +function mayCreateBlockVariables(recurmax, stmtDepth, canThrow, fn) { + if (SUPPORT.const_block) { + createBlockVariables(recurmax, stmtDepth, canThrow, fn); + } else { + fn(function() { + return ""; + }); + } +} + function makeFunction(name) { return (async ? "async function " : "function ") + name; } @@ -618,7 +640,7 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) { if (!STMT_COUNT_FROM_GLOBAL) stmtDepth = 0; var s = []; var name, args; - var varNames = VAR_NAMES.slice(); + var nameLenBefore = VAR_NAMES.length; var save_async = async; async = SUPPORT.async && rng(50) == 0; createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) { @@ -632,11 +654,11 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) { var params; if (SUPPORT.destructuring && (!allowDefun || !(name in called)) && rng(2)) { called[name] = false; - var pairs = createAssignmentPairs(recurmax, COMMA_OK, stmtDepth, canThrow, varNames, save_async); + var pairs = createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, save_async); params = addTrailingComma(pairs.names.join(", ")); args = addTrailingComma(pairs.values.join(", ")); } else { - params = createParams(); + params = createParams(save_async); } s.push(makeFunction(name) + "(" + params + "){", strictMode()); s.push(defns()); @@ -651,7 +673,7 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) { s = filterDirective(s).join("\n"); }); async = save_async; - VAR_NAMES = varNames; + VAR_NAMES.length = nameLenBefore; if (!allowDefun) { // avoid "function statements" (decl inside statements) @@ -676,7 +698,7 @@ function _createStatements(n, recurmax, canThrow, canBreak, canContinue, cannotR function createStatements(n, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) { var s = ""; - createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) { + mayCreateBlockVariables(recurmax, stmtDepth, canThrow, function(defns) { s += defns() + "\n"; s += _createStatements(n, recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth); }); @@ -736,7 +758,7 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn var label = createLabel(canBreak); return label.target + "{" + createStatements(rng(5) + 1, recurmax, canThrow, label.break, canContinue, cannotReturn, stmtDepth) + "}"; case STMT_IF_ELSE: - return "if (" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ")" + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + (rng(2) === 1 ? " else " + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) : ""); + return "if (" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ")" + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + (rng(2) ? " else " + createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) : ""); case STMT_DO_WHILE: var label = createLabel(canBreak, canContinue); canBreak = label.break || enableLoopControl(canBreak, CAN_BREAK); @@ -777,7 +799,7 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn return "switch (" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + ") { " + createSwitchParts(recurmax, 4, canThrow, canBreak, canContinue, cannotReturn, stmtDepth) + "}"; case STMT_VAR: if (SUPPORT.destructuring && rng(20) == 0) { - var pairs = createAssignmentPairs(recurmax, NO_COMMA, stmtDepth, canThrow); + var pairs = createAssignmentPairs(recurmax, stmtDepth, canThrow); return "var " + pairs.names.map(function(name, index) { return index in pairs.values ? name + " = " + pairs.values[index] : name; }).join(", ") + ";"; @@ -837,7 +859,7 @@ function createStatement(recurmax, canThrow, canBreak, canContinue, cannotReturn // the catch var should only be accessible in the catch clause... // we have to do go through some trouble here to prevent leaking it var nameLenBefore = VAR_NAMES.length; - createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) { + mayCreateBlockVariables(recurmax, stmtDepth, canThrow, function(defns) { if (SUPPORT.catch_omit_var && rng(20) == 0) { s += " catch { "; } else { @@ -911,10 +933,10 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { switch (rng(_createExpression.N)) { case p++: case p++: - return createUnaryPrefix() + (rng(2) === 1 ? "a" : "b"); + return createUnaryPrefix() + (rng(2) ? "a" : "b"); case p++: case p++: - return (rng(2) === 1 ? "a" : "b") + createUnaryPostfix(); + return (rng(2) ? "a" : "b") + createUnaryPostfix(); case p++: case p++: // parens needed because assignments aren't valid unless they're the left-most op(s) in an expression @@ -966,12 +988,56 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { var s = []; switch (rng(5)) { case 0: - s.push( - "(" + makeFunction(name) + "(){", - strictMode(), - createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), - rng(2) == 0 ? "})" : "})()" - ); + if (SUPPORT.arrow && !async && !name && rng(2)) { + var args, suffix; + (rng(2) ? createBlockVariables : function() { + arguments[3](); + })(recurmax, stmtDepth, canThrow, function(defns) { + var params; + if (SUPPORT.destructuring && rng(2)) { + var pairs = createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, save_async); + params = addTrailingComma(pairs.names.join(", ")); + args = addTrailingComma(pairs.values.join(", ")); + } else { + params = createParams(save_async, NO_DUPLICATE); + } + if (defns) { + s.push( + "((" + params + ") => {", + strictMode(), + defns(), + _createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth) + ); + suffix = "})"; + } else { + s.push("((" + params + ") => "); + switch (rng(4)) { + case 0: + s.push('(typeof arguments != "undefined" && arguments && arguments[' + rng(3) + "])"); + break; + case 1: + s.push("(this && this." + getDotKey() + ")"); + break; + default: + s.push(createExpression(recurmax, NO_COMMA, stmtDepth, canThrow)); + break; + } + suffix = ")"; + } + }); + async = save_async; + VAR_NAMES.length = nameLenBefore; + if (!args && rng(2)) args = createArgs(recurmax, stmtDepth, canThrow); + if (args) suffix += "(" + args + ")"; + s.push(suffix); + } else { + s.push( + "(" + makeFunction(name) + "(){", + strictMode(), + createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), + rng(2) ? "})" : "})()" + ); + } break; case 1: s.push( @@ -1002,7 +1068,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) { var instantiate = rng(4) ? "new " : ""; s.push( - instantiate + "function " + name + "(" + createParams() + "){", + instantiate + "function " + name + "(" + createParams(save_async) + "){", strictMode(), defns() ); @@ -1014,7 +1080,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { }); async = save_async; VAR_NAMES.length = nameLenBefore; - s.push(rng(2) == 0 ? "}" : "}(" + createArgs(recurmax, stmtDepth, canThrow) + ")"); + s.push(rng(2) ? "}" : "}(" + createArgs(recurmax, stmtDepth, canThrow) + ")"); break; } async = save_async; @@ -1175,7 +1241,9 @@ function createObjectKey(recurmax, stmtDepth, canThrow) { } function createObjectFunction(recurmax, stmtDepth, canThrow) { - var namesLenBefore = VAR_NAMES.length; + var nameLenBefore = VAR_NAMES.length; + var save_async = async; + async = SUPPORT.async && rng(50) == 0; var s; createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) { switch (rng(SUPPORT.computed_key ? 3 : 2)) { @@ -1206,7 +1274,7 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) { break; default: s = [ - createObjectKey(recurmax, stmtDepth, canThrow) + "(" + createParams(NO_DUPLICATE) + "){", + createObjectKey(recurmax, stmtDepth, canThrow) + "(" + createParams(save_async, NO_DUPLICATE) + "){", strictMode(), defns(), _createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), @@ -1215,7 +1283,8 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) { break; } }); - VAR_NAMES.length = namesLenBefore; + async = save_async; + VAR_NAMES.length = nameLenBefore; return filterDirective(s).join("\n"); } @@ -1406,7 +1475,7 @@ function getVarName(noConst) { do { if (--tries < 0) return "a"; name = VAR_NAMES[INITIAL_NAMES_LEN + rng(VAR_NAMES.length - INITIAL_NAMES_LEN)]; - } while (!name || avoid_vars.indexOf(name) >= 0 || noConst && block_vars.indexOf(name) >= 0); + } while (!name || avoid_vars.indexOf(name) >= 0 || noConst && block_vars.indexOf(name) >= 0 || async && name == "await"); return name; } @@ -1418,7 +1487,7 @@ function createVarName(maybe, dontStore) { name = VAR_NAMES[rng(VAR_NAMES.length)]; if (suffix) name += "_" + suffix; } while (unique_vars.indexOf(name) >= 0 || block_vars.indexOf(name) >= 0 || async && name == "await"); - if (suffix && !dontStore) VAR_NAMES.push(name); + if (!dontStore) VAR_NAMES.push(name); return name; } return ""; -- 2.34.1