From fd4caf7a9cbfaa482d8cc77d2bc8cd00e8bc256a Mon Sep 17 00:00:00 2001 From: "Alex Lam S.L" Date: Sun, 7 Feb 2021 22:44:20 +0000 Subject: [PATCH] support generator functions (#4620) --- README.md | 2 + lib/ast.js | 182 +++++++-- lib/compress.js | 174 +++++---- lib/output.js | 55 ++- lib/parse.js | 120 ++++-- lib/scope.js | 6 +- lib/transform.js | 3 + test/compress/yields.js | 589 +++++++++++++++++++++++++++++ test/mocha/{async.js => awaits.js} | 0 test/mocha/yields.js | 82 ++++ test/reduce.js | 6 +- test/ufuzz/index.js | 133 ++++++- 12 files changed, 1178 insertions(+), 174 deletions(-) create mode 100644 test/compress/yields.js rename test/mocha/{async.js => awaits.js} (100%) create mode 100644 test/mocha/yields.js diff --git a/README.md b/README.md index 9f69ce00..6884d8c8 100644 --- a/README.md +++ b/README.md @@ -811,6 +811,8 @@ to be `false` and all symbol names will be omitted. - `varify` (default: `true`) -- convert block-scoped declaractions into `var` whenever safe to do so +- `yields` (default: `true`) -- apply optimizations to `yield` expressions + ## Mangle options - `eval` (default `false`) -- Pass `true` to mangle names visible in scopes diff --git a/lib/ast.js b/lib/ast.js index ead2c7d5..ea823b28 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -113,7 +113,9 @@ var AST_Node = DEFNODE("Node", "start end", { walk: function(visitor) { visitor.visit(this); }, - _validate: noop, + _validate: function() { + if (this.TYPE == "Node") throw new Error("should not instantiate AST_Node"); + }, validate: function() { var ctor = this.CTOR; do { @@ -187,6 +189,9 @@ AST_Node.disable_validation = function() { var AST_Statement = DEFNODE("Statement", null, { $documentation: "Base class of all statements", + _validate: function() { + if (this.TYPE == "Statement") throw new Error("should not instantiate AST_Statement"); + }, }); var AST_Debugger = DEFNODE("Debugger", null, { @@ -215,7 +220,7 @@ function validate_expression(value, prop, multiple, allow_spread, allow_hole) { if (value instanceof AST_Destructured) throw new Error(prop + " cannot " + multiple + " AST_Destructured"); if (value instanceof AST_Hole && !allow_hole) throw new Error(prop + " cannot " + multiple + " AST_Hole"); if (value instanceof AST_Spread && !allow_spread) throw new Error(prop + " cannot " + multiple + " AST_Spread"); - if (value instanceof AST_Statement && !is_function(value)) { + if (value instanceof AST_Statement && !(value instanceof AST_LambdaExpression)) { throw new Error(prop + " cannot " + multiple + " AST_Statement"); } if (value instanceof AST_SymbolDeclaration) { @@ -265,6 +270,7 @@ var AST_BlockScope = DEFNODE("BlockScope", "enclosed functions make_def parent_s return this.parent_scope.resolve(); }, _validate: function() { + if (this.TYPE == "BlockScope") throw new Error("should not instantiate AST_BlockScope"); if (this.parent_scope == null) return; if (!(this.parent_scope instanceof AST_BlockScope)) throw new Error("parent_scope must be AST_BlockScope"); if (!(this.resolve() instanceof AST_Scope)) throw new Error("must be contained within AST_Scope"); @@ -289,9 +295,10 @@ var AST_Block = DEFNODE("Block", "body", { }); }, _validate: function() { + if (this.TYPE == "Block") throw new Error("should not instantiate AST_Block"); this.body.forEach(function(node) { if (!(node instanceof AST_Statement)) throw new Error("body must be AST_Statement[]"); - if (is_function(node)) throw new Error("body cannot contain AST_Function"); + if (node instanceof AST_LambdaExpression) throw new Error("body cannot contain AST_LambdaExpression"); }); }, }, AST_BlockScope); @@ -306,8 +313,9 @@ var AST_StatementWithBody = DEFNODE("StatementWithBody", "body", { body: "[AST_Statement] the body; this should always be present, even if it's an AST_EmptyStatement" }, _validate: function() { + if (this.TYPE == "StatementWithBody") throw new Error("should not instantiate AST_StatementWithBody"); if (!(this.body instanceof AST_Statement)) throw new Error("body must be AST_Statement"); - if (is_function(this.body)) throw new Error("body cannot be AST_Function"); + if (this.body instanceof AST_LambdaExpression) throw new Error("body cannot be AST_LambdaExpression"); }, }, AST_BlockScope); @@ -346,7 +354,10 @@ var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", { }, AST_StatementWithBody); var AST_IterationStatement = DEFNODE("IterationStatement", null, { - $documentation: "Internal class. All loops inherit from it." + $documentation: "Internal class. All loops inherit from it.", + _validate: function() { + if (this.TYPE == "IterationStatement") throw new Error("should not instantiate AST_IterationStatement"); + }, }, AST_StatementWithBody); var AST_DWLoop = DEFNODE("DWLoop", "condition", { @@ -355,6 +366,7 @@ var AST_DWLoop = DEFNODE("DWLoop", "condition", { condition: "[AST_Node] the loop condition. Should not be instanceof AST_Statement" }, _validate: function() { + if (this.TYPE == "DWLoop") throw new Error("should not instantiate AST_DWLoop"); must_be_expression(this, "condition"); }, }, AST_IterationStatement); @@ -401,7 +413,7 @@ var AST_For = DEFNODE("For", "init condition step", { if (this.init != null) { if (!(this.init instanceof AST_Node)) throw new Error("init must be AST_Node"); if (this.init instanceof AST_Statement - && !(this.init instanceof AST_Definitions || is_function(this.init))) { + && !(this.init instanceof AST_Definitions || this.init instanceof AST_LambdaExpression)) { throw new Error("init cannot be AST_Statement"); } } @@ -467,6 +479,9 @@ var AST_Scope = DEFNODE("Scope", "uses_eval uses_with", { return this.uses_eval || this.uses_with; }, resolve: return_this, + _validate: function() { + if (this.TYPE == "Scope") throw new Error("should not instantiate AST_Scope"); + }, }, AST_Block); var AST_Toplevel = DEFNODE("Toplevel", "globals", { @@ -517,8 +532,9 @@ var AST_Lambda = DEFNODE("Lambda", "argnames length_read rest uses_arguments", { $documentation: "Base class for functions", $propdoc: { argnames: "[(AST_DefaultValue|AST_Destructured|AST_SymbolFunarg)*] array of function arguments and/or destructured literals", + length_read: "[boolean/S] whether length property of this function is accessed", rest: "[(AST_Destructured|AST_SymbolFunarg)?] rest parameter, or null if absent", - uses_arguments: "[boolean/S] tells whether this function accesses the arguments array", + uses_arguments: "[boolean/S] whether this function accesses the arguments array", }, each_argname: function(visit) { var tw = new TreeWalker(function(node) { @@ -549,6 +565,7 @@ var AST_Lambda = DEFNODE("Lambda", "argnames length_read rest uses_arguments", { }); }, _validate: function() { + if (this.TYPE == "Lambda") throw new Error("should not instantiate AST_Lambda"); this.argnames.forEach(function(node) { validate_destructured(node, function(node) { if (!(node instanceof AST_SymbolFunarg)) throw new Error("argnames must be AST_SymbolFunarg[]"); @@ -567,12 +584,33 @@ var AST_Accessor = DEFNODE("Accessor", null, { }, }, AST_Lambda); +var AST_LambdaExpression = DEFNODE("LambdaExpression", "inlined", { + $documentation: "Base class for function expressions", + $propdoc: { + inlined: "[boolean/S] whether this function has been inlined", + }, + _validate: function() { + if (this.TYPE == "LambdaExpression") throw new Error("should not instantiate AST_LambdaExpression"); + }, +}, AST_Lambda); + function is_arrow(node) { - return node instanceof AST_AsyncArrow || node instanceof AST_Arrow; + return node instanceof AST_Arrow || node instanceof AST_AsyncArrow; +} + +function is_async(node) { + return node instanceof AST_AsyncArrow + || node instanceof AST_AsyncDefun + || node instanceof AST_AsyncFunction + || node instanceof AST_AsyncGeneratorDefun + || node instanceof AST_AsyncGeneratorFunction; } -function is_function(node) { - return is_arrow(node) || node instanceof AST_AsyncFunction || node instanceof AST_Function; +function is_generator(node) { + return node instanceof AST_AsyncGeneratorDefun + || node instanceof AST_AsyncGeneratorFunction + || node instanceof AST_GeneratorDefun + || node instanceof AST_GeneratorFunction; } function walk_lambda(node, tw) { @@ -583,7 +621,7 @@ function walk_lambda(node, tw) { } } -var AST_Arrow = DEFNODE("Arrow", "inlined value", { +var AST_Arrow = DEFNODE("Arrow", "value", { $documentation: "An arrow function expression", $propdoc: { value: "[AST_Node?] simple return expression, or null if using function body.", @@ -610,13 +648,9 @@ var AST_Arrow = DEFNODE("Arrow", "inlined value", { if (this.body.length) throw new Error("body must be empty if value exists"); } }, -}, AST_Lambda); +}, AST_LambdaExpression); -function is_async(node) { - return node instanceof AST_AsyncArrow || node instanceof AST_AsyncDefun || node instanceof AST_AsyncFunction; -} - -var AST_AsyncArrow = DEFNODE("AsyncArrow", "inlined value", { +var AST_AsyncArrow = DEFNODE("AsyncArrow", "value", { $documentation: "An asynchronous arrow function expression", $propdoc: { value: "[AST_Node?] simple return expression, or null if using function body.", @@ -643,9 +677,9 @@ var AST_AsyncArrow = DEFNODE("AsyncArrow", "inlined value", { if (this.body.length) throw new Error("body must be empty if value exists"); } }, -}, AST_Lambda); +}, AST_LambdaExpression); -var AST_AsyncFunction = DEFNODE("AsyncFunction", "inlined name", { +var AST_AsyncFunction = DEFNODE("AsyncFunction", "name", { $documentation: "An asynchronous function expression", $propdoc: { name: "[AST_SymbolLambda?] the name of this function", @@ -655,10 +689,10 @@ var AST_AsyncFunction = DEFNODE("AsyncFunction", "inlined name", { if (!(this.name instanceof AST_SymbolLambda)) throw new Error("name must be AST_SymbolLambda"); } }, -}, AST_Lambda); +}, AST_LambdaExpression); -var AST_Function = DEFNODE("Function", "inlined name", { - $documentation: "A function expression", +var AST_AsyncGeneratorFunction = DEFNODE("AsyncGeneratorFunction", "name", { + $documentation: "An asynchronous generator function expression", $propdoc: { name: "[AST_SymbolLambda?] the name of this function", }, @@ -667,36 +701,67 @@ var AST_Function = DEFNODE("Function", "inlined name", { if (!(this.name instanceof AST_SymbolLambda)) throw new Error("name must be AST_SymbolLambda"); } }, -}, AST_Lambda); +}, AST_LambdaExpression); -function is_defun(node) { - return node instanceof AST_AsyncDefun || node instanceof AST_Defun; -} +var AST_Function = DEFNODE("Function", "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"); + } + }, +}, AST_LambdaExpression); -var AST_AsyncDefun = DEFNODE("AsyncDefun", "inlined name", { - $documentation: "An asynchronous function definition", +var AST_GeneratorFunction = DEFNODE("GeneratorFunction", "name", { + $documentation: "A generator function expression", $propdoc: { - name: "[AST_SymbolDefun] the name of this function", + name: "[AST_SymbolLambda?] the name of this function", }, _validate: function() { - if (!(this.name instanceof AST_SymbolDefun)) throw new Error("name must be AST_SymbolDefun"); + if (this.name != null) { + if (!(this.name instanceof AST_SymbolLambda)) throw new Error("name must be AST_SymbolLambda"); + } }, -}, AST_Lambda); +}, AST_LambdaExpression); -var AST_Defun = DEFNODE("Defun", "inlined name", { - $documentation: "A function definition", +var AST_LambdaDefinition = DEFNODE("LambdaDefinition", "inlined name", { + $documentation: "Base class for function definitions", $propdoc: { + inlined: "[boolean/S] whether this function has been inlined", name: "[AST_SymbolDefun] the name of this function", }, _validate: function() { + if (this.TYPE == "LambdaDefinition") throw new Error("should not instantiate AST_LambdaDefinition"); if (!(this.name instanceof AST_SymbolDefun)) throw new Error("name must be AST_SymbolDefun"); }, }, AST_Lambda); +var AST_AsyncDefun = DEFNODE("AsyncDefun", null, { + $documentation: "An asynchronous function definition", +}, AST_LambdaDefinition); + +var AST_AsyncGeneratorDefun = DEFNODE("AsyncGeneratorDefun", null, { + $documentation: "An asynchronous generator function definition", +}, AST_LambdaDefinition); + +var AST_Defun = DEFNODE("Defun", null, { + $documentation: "A function definition", +}, AST_LambdaDefinition); + +var AST_GeneratorDefun = DEFNODE("GeneratorDefun", null, { + $documentation: "A generator function definition", +}, AST_LambdaDefinition); + /* -----[ JUMPS ]----- */ var AST_Jump = DEFNODE("Jump", null, { - $documentation: "Base class for “jumps” (for now that's `return`, `throw`, `break` and `continue`)" + $documentation: "Base class for “jumps” (for now that's `return`, `throw`, `break` and `continue`)", + _validate: function() { + if (this.TYPE == "Jump") throw new Error("should not instantiate AST_Jump"); + }, }, AST_Statement); var AST_Exit = DEFNODE("Exit", "value", { @@ -709,7 +774,10 @@ var AST_Exit = DEFNODE("Exit", "value", { visitor.visit(node, function() { if (node.value) node.value.walk(visitor); }); - } + }, + _validate: function() { + if (this.TYPE == "Exit") throw new Error("should not instantiate AST_Exit"); + }, }, AST_Jump); var AST_Return = DEFNODE("Return", null, { @@ -738,6 +806,7 @@ var AST_LoopControl = DEFNODE("LoopControl", "label", { }); }, _validate: function() { + if (this.TYPE == "LoopControl") throw new Error("should not instantiate AST_LoopControl"); if (this.label != null) { if (!(this.label instanceof AST_LabelRef)) throw new Error("label must be AST_LabelRef"); } @@ -772,7 +841,7 @@ var AST_If = DEFNODE("If", "condition alternative", { must_be_expression(this, "condition"); if (this.alternative != null) { if (!(this.alternative instanceof AST_Statement)) throw new Error("alternative must be AST_Statement"); - if (is_function(this.alternative)) throw new error("alternative cannot be AST_Function"); + if (this.alternative instanceof AST_LambdaExpression) throw new error("alternative cannot be AST_LambdaExpression"); } }, }, AST_StatementWithBody); @@ -801,6 +870,9 @@ var AST_Switch = DEFNODE("Switch", "expression", { var AST_SwitchBranch = DEFNODE("SwitchBranch", null, { $documentation: "Base class for `switch` branches", + _validate: function() { + if (this.TYPE == "SwitchBranch") throw new Error("should not instantiate AST_SwitchBranch"); + }, }, AST_Block); var AST_Default = DEFNODE("Default", null, { @@ -889,6 +961,7 @@ var AST_Definitions = DEFNODE("Definitions", "definitions", { }); }, _validate: function() { + if (this.TYPE == "Definitions") throw new Error("should not instantiate AST_Definitions"); if (this.definitions.length < 1) throw new Error("must have at least one definition"); }, }, AST_Statement); @@ -1036,6 +1109,7 @@ var AST_PropAccess = DEFNODE("PropAccess", "expression property", { return p; }, _validate: function() { + if (this.TYPE == "PropAccess") throw new Error("should not instantiate AST_PropAccess"); must_be_expression(this, "expression"); }, }); @@ -1096,6 +1170,7 @@ var AST_Unary = DEFNODE("Unary", "operator expression", { }); }, _validate: function() { + if (this.TYPE == "Unary") throw new Error("should not instantiate AST_Unary"); if (typeof this.operator != "string") throw new Error("operator must be string"); must_be_expression(this, "expression"); }, @@ -1183,6 +1258,27 @@ var AST_Await = DEFNODE("Await", "expression", { }, }); +var AST_Yield = DEFNODE("Yield", "expression nested", { + $documentation: "A yield expression", + $propdoc: { + expression: "[AST_Node?] return value for iterator, or null if undefined", + nested: "[boolean] whether to iterate over expression as generator", + }, + walk: function(visitor) { + var node = this; + visitor.visit(node, function() { + if (node.expression) node.expression.walk(visitor); + }); + }, + _validate: function() { + if (this.expression != null) { + must_be_expression(this, "expression"); + } else if (this.nested) { + throw new Error("yield* must contain expression"); + } + }, +}); + /* -----[ LITERALS ]----- */ var AST_Array = DEFNODE("Array", "elements", { @@ -1208,6 +1304,9 @@ var AST_Destructured = DEFNODE("Destructured", "rest", { $propdoc: { rest: "[(AST_Destructured|AST_SymbolDeclaration|AST_SymbolRef)?] rest parameter, or null if absent", }, + _validate: function() { + if (this.TYPE == "Destructured") throw new Error("should not instantiate AST_Destructured"); + }, }); function validate_destructured(node, check, allow_default) { @@ -1319,6 +1418,7 @@ var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", { }); }, _validate: function() { + if (this.TYPE == "ObjectProperty") throw new Error("should not instantiate AST_ObjectProperty"); if (typeof this.key != "string") { if (!(this.key instanceof AST_Node)) throw new Error("key must be string or AST_Node"); must_be_expression(this, "key"); @@ -1356,6 +1456,7 @@ var AST_Symbol = DEFNODE("Symbol", "scope name thedef", { thedef: "[SymbolDef/S] the definition of this symbol" }, _validate: function() { + if (this.TYPE == "Symbol") throw new Error("should not instantiate AST_Symbol"); if (typeof this.name != "string") throw new Error("name must be string"); }, }); @@ -1448,6 +1549,9 @@ var AST_Template = DEFNODE("Template", "expressions strings tag", { var AST_Constant = DEFNODE("Constant", null, { $documentation: "Base class for all constants", + _validate: function() { + if (this.TYPE == "Constant") throw new Error("should not instantiate AST_Constant"); + }, }); var AST_String = DEFNODE("String", "value quote", { @@ -1496,6 +1600,9 @@ var AST_RegExp = DEFNODE("RegExp", "value", { var AST_Atom = DEFNODE("Atom", null, { $documentation: "Base class for atoms", + _validate: function() { + if (this.TYPE == "Atom") throw new Error("should not instantiate AST_Atom"); + }, }, AST_Constant); var AST_Null = DEFNODE("Null", null, { @@ -1525,6 +1632,9 @@ var AST_Infinity = DEFNODE("Infinity", null, { var AST_Boolean = DEFNODE("Boolean", null, { $documentation: "Base class for booleans", + _validate: function() { + if (this.TYPE == "Boolean") throw new Error("should not instantiate AST_Boolean"); + }, }, AST_Atom); var AST_False = DEFNODE("False", null, { diff --git a/lib/compress.js b/lib/compress.js index afb555f0..9e9f15f6 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -104,6 +104,7 @@ function Compressor(options, false_by_default) { unsafe_undefined: false, unused : !false_by_default, varify : !false_by_default, + yields : !false_by_default, }, true); var evaluate = this.options["evaluate"]; this.eval_threshold = /eager/.test(evaluate) ? 1 / 0 : +evaluate; @@ -344,7 +345,7 @@ merge(Compressor.prototype, { return !immutable && parent.expression === node && !parent.is_expr_pure(compressor) - && (!is_function(value) || !(parent instanceof AST_New) && value.contains_this()); + && (!(value instanceof AST_LambdaExpression) || !(parent instanceof AST_New) && value.contains_this()); } if (parent instanceof AST_ForIn) return parent.init === node; if (parent instanceof AST_ObjectKeyVal) { @@ -396,13 +397,13 @@ merge(Compressor.prototype, { def.fixed = !def.const_redefs && !def.scope.pinned() && !compressor.exposed(def) - && !(is_function(def.init) && def.init !== def.scope) + && !(def.init instanceof AST_LambdaExpression && def.init !== def.scope) && def.init; - if (is_defun(def.fixed) && !all(def.references, function(ref) { + if (def.fixed instanceof AST_LambdaDefinition && !all(def.references, function(ref) { var scope = ref.scope.resolve(); do { if (def.scope === scope) return true; - } while (is_function(scope) && (scope = scope.parent_scope.resolve())); + } while (scope instanceof AST_LambdaExpression && (scope = scope.parent_scope.resolve())); })) { tw.defun_ids[def.id] = false; } @@ -427,7 +428,7 @@ merge(Compressor.prototype, { scope.may_call_this = noop; if (!scope.contains_this()) return; scope.functions.each(function(def) { - if (is_defun(def.init) && !(def.id in tw.defun_ids)) { + if (def.init instanceof AST_LambdaDefinition && !(def.id in tw.defun_ids)) { tw.defun_ids[def.id] = false; } }); @@ -467,7 +468,7 @@ merge(Compressor.prototype, { function walk_defuns(tw, scope) { scope.functions.each(function(def) { - if (is_defun(def.init) && !tw.defun_visited[def.id]) { + if (def.init instanceof AST_LambdaDefinition && !tw.defun_visited[def.id]) { tw.defun_ids[def.id] = tw.safe_ids; def.init.walk(tw); } @@ -505,7 +506,7 @@ merge(Compressor.prototype, { } return !safe.assign || safe.assign === tw.safe_ids; } - return is_defun(def.fixed); + return def.fixed instanceof AST_LambdaDefinition; } function safe_to_assign(tw, def, declare) { @@ -702,25 +703,11 @@ merge(Compressor.prototype, { lhs.walk(scanner); } - function reduce_defun(tw, descend, compressor) { - var id = this.name.definition().id; - if (tw.defun_visited[id]) return true; - if (tw.defun_ids[id] !== tw.safe_ids) return true; - tw.defun_visited[id] = true; - this.inlined = false; - push(tw); - reset_variables(tw, compressor, this); - descend(); - pop(tw); - walk_defuns(tw, this); - return true; - } - function reduce_iife(tw, descend, compressor) { var fn = this; fn.inlined = false; var iife = tw.parent(); - var hit = is_async(fn); + var hit = is_async(fn) || is_generator(fn); var aborts = false; fn.walk(new TreeWalker(function(node) { if (hit) return aborts = true; @@ -848,7 +835,6 @@ merge(Compressor.prototype, { } } }); - def(AST_AsyncDefun, reduce_defun); def(AST_Binary, function(tw) { if (!lazy_op[this.operator]) return; this.left.walk(tw); @@ -865,7 +851,7 @@ merge(Compressor.prototype, { def(AST_Call, function(tw, descend) { tw.find_parent(AST_Scope).may_call_this(); var exp = this.expression; - if (is_function(exp)) { + if (exp instanceof AST_LambdaExpression) { var iife = !exp.name; this.args.forEach(function(arg) { arg.walk(tw); @@ -878,7 +864,7 @@ merge(Compressor.prototype, { } else if (exp instanceof AST_SymbolRef) { var def = exp.definition(); if (this.TYPE == "Call" && tw.in_boolean_context()) def.bool_fn++; - if (!is_defun(def.fixed)) return; + if (!(def.fixed instanceof AST_LambdaDefinition)) return; var defun = mark_defun(tw, def); if (!defun) return; descend(); @@ -909,7 +895,6 @@ merge(Compressor.prototype, { pop(tw); return true; }); - def(AST_Defun, reduce_defun); def(AST_Do, function(tw) { var saved_loop = tw.in_loop; tw.in_loop = this; @@ -1008,6 +993,19 @@ merge(Compressor.prototype, { walk_defuns(tw, fn); return true; }); + def(AST_LambdaDefinition, function(tw, descend, compressor) { + var id = this.name.definition().id; + if (tw.defun_visited[id]) return true; + if (tw.defun_ids[id] !== tw.safe_ids) return true; + tw.defun_visited[id] = true; + this.inlined = false; + push(tw); + reset_variables(tw, compressor, this); + descend(); + pop(tw); + walk_defuns(tw, this); + return true; + }); def(AST_Switch, function(tw, descend, compressor) { this.variables.each(function(def) { reset_def(tw, compressor, def); @@ -1079,7 +1077,8 @@ merge(Compressor.prototype, { } if (!this.fixed) this.fixed = d.fixed; var parent; - if (is_defun(d.fixed) && !((parent = tw.parent()) instanceof AST_Call && parent.expression === this)) { + if (d.fixed instanceof AST_LambdaDefinition + && !((parent = tw.parent()) instanceof AST_Call && parent.expression === this)) { var defun = mark_defun(tw, d); if (defun) defun.walk(tw); } @@ -1455,8 +1454,10 @@ merge(Compressor.prototype, { function is_iife_call(node) { if (node.TYPE != "Call") return false; - var exp = node.expression; - return exp instanceof AST_AsyncFunction || exp instanceof AST_Function || is_iife_call(exp); + do { + node = node.expression; + } while (node instanceof AST_PropAccess); + return node instanceof AST_LambdaExpression ? !is_arrow(node) : is_iife_call(node); } function is_undeclared_ref(node) { @@ -1843,7 +1844,7 @@ merge(Compressor.prototype, { if (node instanceof AST_Call) { if (!(lhs instanceof AST_PropAccess)) return false; if (!lhs.equivalent_to(node.expression)) return false; - return !(is_function(rvalue) && !rvalue.contains_this()); + return !(rvalue instanceof AST_LambdaExpression && !rvalue.contains_this()); } if (node instanceof AST_Debugger) return true; if (node instanceof AST_Defun) return funarg && lhs.name === node.name.name; @@ -1951,6 +1952,7 @@ merge(Compressor.prototype, { && (lvalues.has(node.name) || side_effects && may_modify(node)); }, true); } + if (node instanceof AST_Yield) return true; var sym = is_lhs(node.left, node); if (!sym) return false; if (sym instanceof AST_PropAccess) return true; @@ -1988,7 +1990,8 @@ merge(Compressor.prototype, { function extract_args() { var iife, fn = compressor.self(); - if (is_function(fn) + if (fn instanceof AST_LambdaExpression + && !is_generator(fn) && !fn.name && !fn.uses_arguments && !fn.pinned() @@ -2482,7 +2485,7 @@ merge(Compressor.prototype, { if (modify_toplevel) return; var exp = node.expression; if (exp instanceof AST_PropAccess) return; - if (is_function(exp) && !exp.contains_this()) return; + if (exp instanceof AST_LambdaExpression && !exp.contains_this()) return; modify_toplevel = true; } else if (node instanceof AST_PropAccess && may_be_global(node.expression)) { if (node === lhs && !(expr instanceof AST_Unary)) { @@ -3249,7 +3252,7 @@ merge(Compressor.prototype, { } function extract_declarations_from_unreachable_code(compressor, stat, target) { - if (!(stat instanceof AST_Definitions || is_defun(stat))) { + if (!(stat instanceof AST_Definitions || stat instanceof AST_LambdaDefinition)) { AST_Node.warn("Dropping unreachable code [{file}:{line},{col}]", stat.start); } var block; @@ -3265,7 +3268,7 @@ merge(Compressor.prototype, { } return true; } - if (is_defun(node)) { + if (node instanceof AST_LambdaDefinition) { push(node); return true; } @@ -5114,7 +5117,7 @@ merge(Compressor.prototype, { if (node instanceof AST_Call) { var exp = node.expression; var tail = exp.tail_node(); - if (!is_function(tail)) return; + if (!(tail instanceof AST_LambdaExpression)) return; if (exp !== tail) exp.expressions.slice(0, -1).forEach(function(node) { node.walk(tw); }); @@ -5534,7 +5537,7 @@ merge(Compressor.prototype, { } if (node === self) return; if (scope === self) { - if (is_defun(node)) { + if (node instanceof AST_LambdaDefinition) { var def = node.name.definition(); if (!drop_funcs && !(def.id in in_use_ids)) { in_use_ids[def.id] = true; @@ -5735,7 +5738,7 @@ merge(Compressor.prototype, { if (node instanceof AST_Call) calls_to_drop_args.push(node); if (scope !== self) return; if (node instanceof AST_Lambda) { - if (drop_funcs && node !== self && is_defun(node)) { + if (drop_funcs && node !== self && node instanceof AST_LambdaDefinition) { var def = node.name.definition(); if (!(def.id in in_use_ids)) { log(node.name, "Dropping unused function {name}"); @@ -5743,7 +5746,7 @@ merge(Compressor.prototype, { return in_list ? List.skip : make_node(AST_EmptyStatement, node); } } - if (is_function(node) && node.name && drop_fn_name(node.name.definition())) { + if (node instanceof AST_LambdaExpression && node.name && drop_fn_name(node.name.definition())) { unused_fn_names.push(node); } if (!(node instanceof AST_Accessor)) { @@ -5853,12 +5856,28 @@ merge(Compressor.prototype, { && node instanceof AST_Var && var_defs[sym.id] == 1 && sym.assignments == 0 - && (value instanceof AST_AsyncFunction || value instanceof AST_Function) + && value instanceof AST_LambdaExpression + && !is_arrow(value) && assigned_once(value, sym.references) && can_declare_defun() && can_rename(value, def.name.name)) { AST_Node.warn("Declaring {name} as function [{file}:{line},{col}]", template(def.name)); - var defun = make_node(value instanceof AST_Function ? AST_Defun : AST_AsyncDefun, def, value); + var ctor; + switch (value.CTOR) { + case AST_AsyncFunction: + ctor = AST_AsyncDefun; + break; + case AST_AsyncGeneratorFunction: + ctor = AST_AsyncGeneratorDefun; + break; + case AST_Function: + ctor = AST_Defun; + break; + case AST_GeneratorFunction: + ctor = AST_GeneratorDefun; + break; + } + var defun = make_node(ctor, def, value); defun.name = make_node(AST_SymbolDefun, def.name, def.name); var name_def = def.name.scope.resolve().def_function(defun.name); if (old_def) old_def.forEach(function(node) { @@ -5916,6 +5935,7 @@ merge(Compressor.prototype, { if (old_def.assignments > 0) return false; if (old_def.name == name) return true; if (name == "await" && is_async(fn)) return false; + if (name == "yield" && is_generator(fn)) return false; return all(old_def.references, function(ref) { return ref.scope.find_variable(name) === sym; }); @@ -6838,7 +6858,6 @@ 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) { @@ -6855,8 +6874,6 @@ merge(Compressor.prototype, { } return this; }); - def(AST_AsyncArrow, return_null); - def(AST_AsyncFunction, return_null); def(AST_Await, function(compressor) { if (!compressor.option("awaits")) return this; var exp = this.expression.drop_side_effect_free(compressor); @@ -6991,6 +7008,7 @@ merge(Compressor.prototype, { def(AST_Function, function(compressor) { return fn_name_unused(this, compressor) ? null : this; }); + def(AST_LambdaExpression, return_null); def(AST_Object, function(compressor, first_in_statement) { var exprs = []; this.properties.forEach(function(prop) { @@ -7553,7 +7571,7 @@ merge(Compressor.prototype, { var exprs = []; for (var i = 0; i < stat.body.length; i++) { var line = stat.body[i]; - if (is_defun(line)) { + if (line instanceof AST_LambdaDefinition) { defuns.push(line); } else if (line instanceof AST_EmptyStatement) { continue; @@ -7570,7 +7588,7 @@ merge(Compressor.prototype, { } return exprs; } - if (is_defun(stat)) { + if (stat instanceof AST_LambdaDefinition) { defuns.push(stat); return []; } @@ -8274,8 +8292,10 @@ merge(Compressor.prototype, { } } var fn = exp instanceof AST_SymbolRef ? exp.fixed_value() : exp; - var is_func = fn instanceof AST_Lambda && (!is_async(fn) - || compressor.option("awaits") && compressor.parent() instanceof AST_Await); + var parent = compressor.parent(), current = compressor.self(); + var is_func = fn instanceof AST_Lambda + && (!is_async(fn) || compressor.option("awaits") && parent instanceof AST_Await) + && (!is_generator(fn) || compressor.option("yields") && current instanceof AST_Yield && current.nested); var stat = is_func && fn.first_statement(); var has_default = 0, has_destructured = false; var has_spread = !all(self.args, function(arg) { @@ -8308,7 +8328,7 @@ merge(Compressor.prototype, { if (can_inline && !fn.uses_arguments && !fn.pinned() - && !(fn.name && is_function(fn)) + && !(fn.name && fn instanceof AST_LambdaExpression) && (exp === fn || !recursive_ref(compressor, def = exp.definition()) && fn.is_constant_expression(find_scope(compressor))) && !has_spread @@ -8339,7 +8359,7 @@ merge(Compressor.prototype, { return arg; })).optimize(compressor); fn.inlined = save_inlined; - node = maintain_this_binding(compressor, compressor.parent(), compressor.self(), node); + node = maintain_this_binding(compressor, parent, current, node); if (replacing || best_of_expression(node, self) === node) { refs.forEach(function(ref) { var def = ref.definition(); @@ -8358,7 +8378,7 @@ merge(Compressor.prototype, { fn._squeezed = true; if (exp !== fn) fn.parent_scope = exp.scope; var node = make_sequence(self, flatten_fn()).optimize(compressor); - return maintain_this_binding(compressor, compressor.parent(), compressor.self(), node); + return maintain_this_binding(compressor, parent, current, node); } } if (compressor.option("side_effects") @@ -8380,9 +8400,7 @@ merge(Compressor.prototype, { } } } - if (compressor.option("negate_iife") - && compressor.parent() instanceof AST_SimpleStatement - && is_iife_call(self)) { + if (compressor.option("negate_iife") && parent instanceof AST_SimpleStatement && is_iife_call(current)) { return self.negate(compressor, true); } return try_evaluate(compressor, self); @@ -8480,7 +8498,7 @@ merge(Compressor.prototype, { if (node === fn) return; if (is_arrow(node)) { for (var i = 0; safe && i < node.argnames.length; i++) node.argnames[i].walk(tw); - } else if (is_defun(node) && node.name.name == "await") { + } else if (node instanceof AST_LambdaDefinition && node.name.name == "await") { safe = false; } return true; @@ -8566,7 +8584,7 @@ merge(Compressor.prototype, { in_order = null; return; } - if (is_defun(def.init)) return abort = true; + if (def.init instanceof AST_LambdaDefinition) return abort = true; if (is_lhs(node, this.parent())) return abort = true; var index = resolve_index(def); if (!(begin < index)) begin = index; @@ -8619,7 +8637,7 @@ merge(Compressor.prototype, { function can_inject_vars(defined, used, safe_to_inject) { for (var i = 0; i < fn.body.length; i++) { var stat = fn.body[i]; - if (is_defun(stat)) { + if (stat instanceof AST_LambdaDefinition) { if (!safe_to_inject || var_exists(used, stat.name.name)) return false; if (!all(stat.enclosed, function(def) { return def.scope === stat || !defined[def.name]; @@ -8640,7 +8658,7 @@ merge(Compressor.prototype, { function can_inject_symbols() { var defined = Object.create(null); var level = 0, child; - scope = compressor.self(); + scope = current; do { if (scope.variables) scope.variables.each(function(def) { defined[def.name] = true; @@ -8806,7 +8824,7 @@ merge(Compressor.prototype, { flatten_vars(decls, expressions); expressions.push(value); var args = fn.body.filter(function(stat) { - if (is_defun(stat)) { + if (stat instanceof AST_LambdaDefinition) { var def = stat.name.definition(); scope.functions.set(def.name, def); scope.variables.set(def.name, def); @@ -9040,6 +9058,20 @@ merge(Compressor.prototype, { return self; }); + OPT(AST_Yield, function(self, compressor) { + if (!compressor.option("yields")) return self; + if (compressor.option("sequences")) { + var seq = lift_sequence_in_expression(self, compressor); + if (seq !== self) return seq.optimize(compressor); + } + var exp = self.expression; + if (self.nested && exp.TYPE == "Call") { + var inlined = exp.clone().optimize(compressor); + if (inlined.TYPE != "Call") return inlined; + } + return self; + }); + AST_Binary.DEFMETHOD("lift_sequences", function(compressor) { if (this.left instanceof AST_PropAccess) { if (!(this.left.expression instanceof AST_Sequence)) return this; @@ -9802,7 +9834,9 @@ merge(Compressor.prototype, { if (single_use == "f") { var scope = self.scope; do { - if (is_defun(scope) || is_function(scope)) scope.inlined = true; + if (scope instanceof AST_LambdaDefinition || scope instanceof AST_LambdaExpression) { + scope.inlined = true; + } } while (scope = scope.parent_scope); } } else if (fixed.name && fixed.name.name == "await" && is_async(fixed)) { @@ -9817,11 +9851,23 @@ merge(Compressor.prototype, { def.single_use = false; fixed._squeezed = true; fixed.single_use = true; - if (fixed instanceof AST_AsyncDefun) { - fixed = make_node(AST_AsyncFunction, fixed, fixed); - fixed.name = make_node(AST_SymbolLambda, fixed.name, fixed.name); - } else if (fixed instanceof AST_Defun) { - fixed = make_node(AST_Function, fixed, fixed); + if (fixed instanceof AST_LambdaDefinition) { + var ctor; + switch (fixed.CTOR) { + case AST_AsyncDefun: + ctor = AST_AsyncFunction; + break; + case AST_AsyncGeneratorDefun: + ctor = AST_AsyncGeneratorFunction; + break; + case AST_Defun: + ctor = AST_Function; + break; + case AST_GeneratorDefun: + ctor = AST_GeneratorFunction; + break; + } + fixed = make_node(ctor, fixed, fixed); fixed.name = make_node(AST_SymbolLambda, fixed.name, fixed.name); } if (fixed instanceof AST_Lambda) { diff --git a/lib/output.js b/lib/output.js index a171c7e7..b2213f74 100644 --- a/lib/output.js +++ b/lib/output.js @@ -673,7 +673,9 @@ function OutputStream(options) { } } PARENS(AST_AsyncFunction, needs_parens_function); + PARENS(AST_AsyncGeneratorFunction, needs_parens_function); PARENS(AST_Function, needs_parens_function); + PARENS(AST_GeneratorFunction, needs_parens_function); // same goes for an object literal, because otherwise it would be // interpreted as a block of code. @@ -682,14 +684,19 @@ function OutputStream(options) { } PARENS(AST_Object, needs_parens_obj); - PARENS(AST_Unary, function(output) { + function needs_parens_unary(output) { var p = output.parent(); // (-x) ** y if (p instanceof AST_Binary) return p.operator == "**" && p.left === this; - // (x++).toString(3) - // (typeof x).length - return (p instanceof AST_Call || p instanceof AST_PropAccess) && p.expression === this; - }); + // (await x)(y) + // new (await x) + if (p instanceof AST_Call) return p.expression === this; + // (x++)[y] + // (typeof x).y + if (p instanceof AST_PropAccess) return p.expression === this; + } + PARENS(AST_Await, needs_parens_unary); + PARENS(AST_Unary, needs_parens_unary); PARENS(AST_Sequence, function(output) { var p = output.parent(); @@ -719,7 +726,9 @@ function OutputStream(options) { // !(foo, bar, baz) || p instanceof AST_Unary // var a = (1, 2), b = a + a; ---> b == 4 - || p instanceof AST_VarDef; + || p instanceof AST_VarDef + // yield (foo, bar) + || p instanceof AST_Yield; }); PARENS(AST_Binary, function(output) { @@ -826,17 +835,8 @@ function OutputStream(options) { PARENS(AST_Conditional, function(output) { return needs_parens_assign_cond(this, output); }); - - PARENS(AST_Await, function(output) { - var p = output.parent(); - // (await x) ** y - if (p instanceof AST_Binary) return p.operator == "**" && p.left === this; - // new (await foo) - // (await foo)(bar) - if (p instanceof AST_Call) return p.expression === this; - // (await foo).prop - // (await foo)["prop"] - if (p instanceof AST_PropAccess) return p.expression === this; + PARENS(AST_Yield, function(output) { + return needs_parens_assign_cond(this, output); }); /* -----[ PRINTERS ]----- */ @@ -1061,6 +1061,20 @@ function OutputStream(options) { } DEFPRINT(AST_AsyncDefun, print_async); DEFPRINT(AST_AsyncFunction, print_async); + function print_async_generator(output) { + output.print("async"); + output.space(); + output.print("function*"); + print_lambda(this, output); + } + DEFPRINT(AST_AsyncGeneratorDefun, print_async_generator); + DEFPRINT(AST_AsyncGeneratorFunction, print_async_generator); + function print_generator(output) { + output.print("function*"); + print_lambda(this, output); + } + DEFPRINT(AST_GeneratorDefun, print_generator); + DEFPRINT(AST_GeneratorFunction, print_generator); /* -----[ jumps ]----- */ function print_jump(kind, prop) { @@ -1360,6 +1374,13 @@ function OutputStream(options) { output.space(); this.expression.print(output); }); + DEFPRINT(AST_Yield, function(output) { + output.print(this.nested ? "yield*" : "yield"); + if (this.expression) { + output.space(); + this.expression.print(output); + } + }); /* -----[ literals ]----- */ DEFPRINT(AST_Array, function(output) { diff --git a/lib/parse.js b/lib/parse.js index bc72550f..d5098ff5 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -112,13 +112,18 @@ var OPERATORS = makePredicate([ var NEWLINE_CHARS = "\n\r\u2028\u2029"; var OPERATOR_CHARS = "+-*&%=<>!?|~^"; -var PUNC_BEFORE_EXPRESSION = "[{(,;:"; -var PUNC_CHARS = PUNC_BEFORE_EXPRESSION + "`)}]"; +var PUNC_OPENERS = "[{("; +var PUNC_SEPARATORS = ",;:"; +var PUNC_CLOSERS = ")}]"; +var PUNC_AFTER_EXPRESSION = PUNC_SEPARATORS + PUNC_CLOSERS; +var PUNC_BEFORE_EXPRESSION = PUNC_OPENERS + PUNC_SEPARATORS; +var PUNC_CHARS = PUNC_BEFORE_EXPRESSION + "`" + PUNC_CLOSERS; var WHITESPACE_CHARS = NEWLINE_CHARS + " \u00a0\t\f\u000b\u200b\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\uFEFF"; var NON_IDENTIFIER_CHARS = makePredicate(characters("./'\"" + OPERATOR_CHARS + PUNC_CHARS + WHITESPACE_CHARS)); NEWLINE_CHARS = makePredicate(characters(NEWLINE_CHARS)); OPERATOR_CHARS = makePredicate(characters(OPERATOR_CHARS)); +PUNC_AFTER_EXPRESSION = makePredicate(characters(PUNC_AFTER_EXPRESSION)); PUNC_BEFORE_EXPRESSION = makePredicate(characters(PUNC_BEFORE_EXPRESSION)); PUNC_CHARS = makePredicate(characters(PUNC_CHARS)); WHITESPACE_CHARS = makePredicate(characters(WHITESPACE_CHARS)); @@ -692,6 +697,7 @@ function parse($TEXT, options) { in_directives : true, in_funarg : -1, in_function : 0, + in_generator : false, in_loop : 0, labels : [], peeked : null, @@ -829,12 +835,17 @@ function parse($TEXT, options) { if (is_token(peek(), "keyword", "function")) { next(); next(); - return function_(AST_AsyncDefun); + if (!is("operator", "*")) return function_(AST_AsyncDefun); + next(); + return function_(AST_AsyncGeneratorDefun); } break; case "await": if (S.in_async) return simple_statement(); break; + case "yield": + if (S.in_generator) return simple_statement(); + break; } return is_token(peek(), "punc", ":") ? labeled_statement() @@ -905,7 +916,9 @@ function parse($TEXT, options) { case "function": next(); - return function_(AST_Defun); + if (!is("operator", "*")) return function_(AST_Defun); + next(); + return function_(AST_GeneratorDefun); case "if": next(); @@ -1181,15 +1194,15 @@ function parse($TEXT, options) { var function_ = function(ctor) { var was_async = S.in_async; + var was_gen = S.in_generator; var name; - if (ctor === AST_AsyncDefun) { + if (/Defun$/.test(ctor.TYPE)) { name = as_symbol(AST_SymbolDefun); - S.in_async = true; - } else if (ctor === AST_Defun) { - name = as_symbol(AST_SymbolDefun); - S.in_async = false; + S.in_async = /^Async/.test(ctor.TYPE); + S.in_generator = /Generator/.test(ctor.TYPE); } else { - S.in_async = ctor === AST_AsyncFunction; + S.in_async = /^Async/.test(ctor.TYPE); + S.in_generator = /Generator/.test(ctor.TYPE); name = as_symbol(AST_SymbolLambda, true); } if (name && ctor !== AST_Accessor && !(name instanceof AST_SymbolDeclaration)) @@ -1218,6 +1231,7 @@ function parse($TEXT, options) { --S.in_function; S.in_loop = loop; S.labels = labels; + S.in_generator = was_gen; S.in_async = was_async; return new ctor({ name: name, @@ -1481,7 +1495,13 @@ function parse($TEXT, options) { } if (is("keyword", "function")) { next(); - var func = function_(AST_Function); + var func; + if (is("operator", "*")) { + next(); + func = function_(AST_GeneratorFunction); + } else { + func = function_(AST_Function); + } func.start = start; func.end = prev(); return subscripts(func, allow_calls); @@ -1492,7 +1512,13 @@ function parse($TEXT, options) { if (sym.name == "async") { if (is("keyword", "function")) { next(); - var func = function_(AST_AsyncFunction); + var func; + if (is("operator", "*")) { + next(); + func = function_(AST_AsyncGeneratorFunction); + } else { + func = function_(AST_AsyncFunction); + } func.start = start; func.end = prev(); return subscripts(func, allow_calls); @@ -1567,6 +1593,21 @@ function parse($TEXT, options) { // allow trailing comma if (!options.strict && is("punc", "}")) break; var start = S.token; + if (is("operator", "*")) { + next(); + var key = as_property_key(); + var gen_start = S.token; + var gen = function_(AST_GeneratorFunction); + gen.start = gen_start; + gen.end = prev(); + a.push(new AST_ObjectKeyVal({ + start: start, + key: key, + value: gen, + end: prev(), + })); + continue; + } if (is("operator", "...")) { next(); a.push(new AST_Spread({ @@ -1628,9 +1669,10 @@ function parse($TEXT, options) { } if (start.type == "name") switch (key) { case "async": + var is_gen = is("operator", "*") && next(); key = as_property_key(); var func_start = S.token; - var func = function_(AST_AsyncFunction); + var func = function_(is_gen ? AST_AsyncGeneratorFunction : AST_AsyncFunction); func.start = func_start; func.end = prev(); a.push(new AST_ObjectKeyVal({ @@ -1694,6 +1736,7 @@ function parse($TEXT, options) { function _make_symbol(type, token) { var name = token.value; if (name === "await" && S.in_async) unexpected(token); + if (name === "yield" && S.in_generator) unexpected(token); return new (name === "this" ? AST_This : type)({ name: "" + name, start: token, @@ -1870,12 +1913,42 @@ function parse($TEXT, options) { return expr; }; - function maybe_unary() { + function maybe_unary(no_in) { var start = S.token; + if (S.in_async && is("name", "await")) { + if (S.in_funarg === S.in_function) croak("Invalid use of await in function argument"); + S.input.context().regex_allowed = true; + next(); + return new AST_Await({ + start: start, + expression: maybe_unary(no_in), + end: prev(), + }); + } + if (S.in_generator && is("name", "yield")) { + if (S.in_funarg === S.in_function) croak("Invalid use of yield in function argument"); + S.input.context().regex_allowed = true; + next(); + var exp = null; + var nested = false; + if (is("operator", "*")) { + next(); + exp = maybe_assign(no_in); + nested = true; + } else if (is("punc") ? !PUNC_AFTER_EXPRESSION[S.token.value] : !can_insert_semicolon()) { + exp = maybe_assign(no_in); + } + return new AST_Yield({ + start: start, + expression: exp, + nested: nested, + end: prev(), + }); + } if (is("operator") && UNARY_PREFIX[start.value]) { next(); handle_regexp(); - var ex = make_unary(AST_UnaryPrefix, start, maybe_await()); + var ex = make_unary(AST_UnaryPrefix, start, maybe_unary(no_in)); ex.start = start; ex.end = prev(); return ex; @@ -1906,26 +1979,13 @@ function parse($TEXT, options) { return new ctor({ operator: op, expression: expr }); } - function maybe_await() { - var start = S.token; - if (!(S.in_async && is("name", "await"))) return maybe_unary(); - if (S.in_funarg === S.in_function) croak("Invalid use of await in function argument"); - S.input.context().regex_allowed = true; - next(); - return new AST_Await({ - start: start, - expression: maybe_await(), - end: prev(), - }); - } - var expr_op = function(left, min_prec, no_in) { var op = is("operator") ? S.token.value : null; if (op == "in" && no_in) op = null; var prec = op != null ? PRECEDENCE[op] : null; if (prec != null && prec > min_prec) { next(); - var right = expr_op(maybe_await(), op == "**" ? prec - 1 : prec, no_in); + var right = expr_op(maybe_unary(no_in), op == "**" ? prec - 1 : prec, no_in); return expr_op(new AST_Binary({ start : left.start, left : left, @@ -1938,7 +1998,7 @@ function parse($TEXT, options) { }; function expr_ops(no_in) { - return expr_op(maybe_await(), 0, no_in); + return expr_op(maybe_unary(no_in), 0, no_in); } var maybe_conditional = function(no_in) { diff --git a/lib/scope.js b/lib/scope.js index 5f10e33d..fb51486a 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -121,7 +121,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) { var next_def_id = 0; var scope = self.parent_scope = null; var tw = new TreeWalker(function(node, descend) { - if (is_defun(node)) { + if (node instanceof AST_LambdaDefinition) { node.name.walk(tw); walk_scope(function() { node.argnames.forEach(function(argname) { @@ -439,7 +439,7 @@ AST_BlockScope.DEFMETHOD("find_variable", function(name) { AST_BlockScope.DEFMETHOD("def_function", function(symbol, init) { var def = this.def_variable(symbol, init); - if (!def.init || is_defun(def.init)) def.init = init; + if (!def.init || def.init instanceof AST_LambdaDefinition) def.init = init; this.functions.set(symbol.name, def); return def; }); @@ -448,7 +448,7 @@ AST_BlockScope.DEFMETHOD("def_variable", function(symbol, init) { var def = this.variables.get(symbol.name); if (def) { def.orig.push(symbol); - if (is_function(def.init)) def.init = init; + if (def.init instanceof AST_LambdaExpression) def.init = init; } else { def = this.make_def(symbol, init); this.variables.set(symbol.name, def); diff --git a/lib/transform.js b/lib/transform.js index b84bf0b9..ede6e254 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -157,6 +157,9 @@ TreeTransformer.prototype = new TreeWalker; DEF(AST_Await, function(self, tw) { self.expression = self.expression.transform(tw); }); + DEF(AST_Yield, function(self, tw) { + if (self.expression) self.expression = self.expression.transform(tw); + }); DEF(AST_Dot, function(self, tw) { self.expression = self.expression.transform(tw); }); diff --git a/test/compress/yields.js b/test/compress/yields.js new file mode 100644 index 00000000..2c06a4ac --- /dev/null +++ b/test/compress/yields.js @@ -0,0 +1,589 @@ +binary: { + input: { + var a = function*() { + console.log(6 * (yield "PA" + "SS")); + }(); + console.log(a.next("FAIL").value); + console.log(a.next(7).done); + } + expect_exact: 'var a=function*(){console.log(6*(yield"PA"+"SS"))}();console.log(a.next("FAIL").value);console.log(a.next(7).done);' + expect_stdout: [ + "PASS", + "42", + "true", + ] + node_version: ">=4" +} + +empty_yield: { + input: { + var a = function*() { + yield; + console.log(yield); + yield + "FAIL 1"; + }(); + console.log(a.next("FAIL 2").value); + console.log(a.next("FAIL 3").value); + console.log(a.next("PASS").value); + console.log(a.next("FAIL 4").done); + } + expect_exact: 'var a=function*(){yield;console.log(yield);yield;"FAIL 1"}();console.log(a.next("FAIL 2").value);console.log(a.next("FAIL 3").value);console.log(a.next("PASS").value);console.log(a.next("FAIL 4").done);' + expect_stdout: [ + "undefined", + "undefined", + "PASS", + "undefined", + "true", + ] + node_version: ">=4" +} + +empty_yield_conditional: { + input: { + var a = function*() { + console.log((yield) ? yield : yield); + }(); + console.log(a.next("FAIL 1").value); + console.log(a.next("FAIL 2").value); + console.log(a.next("PASS").value); + console.log(a.next("FAIL 3").done); + } + expect_exact: 'var a=function*(){console.log((yield)?yield:yield)}();console.log(a.next("FAIL 1").value);console.log(a.next("FAIL 2").value);console.log(a.next("PASS").value);console.log(a.next("FAIL 3").done);' + expect_stdout: [ + "undefined", + "undefined", + "PASS", + "undefined", + "true", + ] + node_version: ">=4" +} + +nested_yield: { + input: { + console.log(function*() { + (yield* + f()) + function* f() { + return "FAIL"; + } + yield* + f(); + yield *f(); + }().next().value || "PASS"); + } + expect_exact: 'console.log(function*(){yield*f();function*f(){return"FAIL"}yield*f();yield*f()}().next().value||"PASS");' + expect_stdout: "PASS" + node_version: ">=4" +} + +pause_resume: { + input: { + function* f() { + console.log(yield "PASS"); + } + var a = f(); + console.log(a.next("FAIL").value); + console.log(a.next(42).done); + } + expect_exact: 'function*f(){console.log(yield"PASS")}var a=f();console.log(a.next("FAIL").value);console.log(a.next(42).done);' + expect_stdout: [ + "PASS", + "42", + "true", + ] + node_version: ">=4" +} + +collapse_vars_1: { + options = { + collapse_vars: true, + } + input: { + var a = "FAIL"; + (function*() { + a = "PASS"; + yield 42; + return "PASS"; + })().next(); + console.log(a); + } + expect: { + var a = "FAIL"; + (function*() { + a = "PASS"; + yield 42; + return "PASS"; + })().next(); + console.log(a); + } + expect_stdout: "PASS" + node_version: ">=4" +} + +collapse_vars_2: { + options = { + collapse_vars: true, + } + input: { + var a = "FAIL"; + (function*() { + yield (a = "PASS"); + return "PASS"; + })().next(); + console.log(a); + } + expect: { + var a = "FAIL"; + (function*() { + yield (a = "PASS"); + return "PASS"; + })().next(); + console.log(a); + } + expect_stdout: "PASS" + node_version: ">=4" +} + +collapse_vars_3: { + options = { + collapse_vars: true, + } + input: { + var a = "FAIL"; + (function*() { + yield (a = "PASS", 42); + return "PASS"; + })().next(); + console.log(a); + } + expect: { + var a = "FAIL"; + (function*() { + yield (a = "PASS", 42); + return "PASS"; + })().next(); + console.log(a); + } + expect_stdout: "PASS" + node_version: ">=4" +} + +collapse_vars_4: { + options = { + collapse_vars: true, + unused: true, + } + input: { + var a = "FAIL"; + var b = function*(c) { + return c; + }(a = "PASS"); + console.log(a, b.next().done); + } + expect: { + var a = "FAIL"; + var b = function*(c) { + return c; + }(a = "PASS"); + console.log(a, b.next().done); + } + expect_stdout: "PASS true" + node_version: ">=4" +} + +collapse_property_lambda: { + options = { + collapse_vars: true, + pure_getters: "strict", + } + input: { + console.log(function* f() { + f.g = () => 42; + return f.g(); + }().next().value); + } + expect: { + console.log(function* f() { + return (f.g = () => 42)(); + }().next().value); + } + expect_stdout: "42" + node_version: ">=4" +} + +evaluate: { + options = { + evaluate: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var a = function*() {}(); + console.log(typeof a); + } + expect: { + var a = function*() {}(); + console.log(typeof a); + } + expect_stdout: "object" + node_version: ">=4" +} + +functions: { + options = { + functions: true, + reduce_vars: true, + unused: true, + } + input: { + !function*() { + var a = function* a() { + return a && "a"; + }; + var b = function* x() { + return !!x; + }; + var c = function*(c) { + return c; + }; + if (yield* c(yield* b(yield* a()))) { + var d = function*() {}; + var e = function* y() { + return typeof y; + }; + var f = function*(f) { + return f; + }; + console.log(yield* a(yield* d()), yield* b(yield* e()), yield* c(yield* f(42)), typeof d, yield* e(), typeof f); + } + }().next(); + } + expect: { + !function*() { + function* a() { + return a && "a"; + } + function* b() { + return !!b; + } + var c = function*(c) { + return c; + }; + if (yield* c(yield* b(yield* a()))) { + function* d() {} + function* e() { + return typeof e; + } + var f = function*(f) { + return f; + }; + console.log(yield* a(yield* d()), yield* b(yield* e()), yield* c(yield* f(42)), typeof d, yield* e(), typeof f); + } + }().next(); + } + expect_stdout: "a true 42 function function function" + node_version: ">=4" +} + +functions_use_strict: { + options = { + functions: true, + reduce_vars: true, + unused: true, + } + input: { + "use strict"; + !function*() { + var a = function* a() { + return a && "a"; + }; + var b = function* x() { + return !!x; + }; + var c = function*(c) { + return c; + }; + if (yield* c(yield* b(yield* a()))) { + var d = function*() {}; + var e = function* y() { + return typeof y; + }; + var f = function*(f) { + return f; + }; + console.log(yield* a(yield* d()), yield* b(yield* e()), yield* c(yield* f(42)), typeof d, yield* e(), typeof f); + } + }().next(); + } + expect: { + "use strict"; + !function*() { + function* a() { + return a && "a"; + } + function* b() { + return !!b; + } + var c = function*(c) { + return c; + }; + if (yield* c(yield* b(yield* a()))) { + var d = function*() {}; + var e = function* y() { + return typeof y; + }; + var f = function*(f) { + return f; + }; + console.log(yield* a(yield* d()), yield* b(yield* e()), yield* c(yield* f(42)), typeof d, yield* e(), typeof f); + } + }().next(); + } + expect_stdout: "a true 42 function function function" + node_version: ">=4" +} + +negate_iife: { + options = { + negate_iife: true, + side_effects: true, + } + input: { + (function*(a) { + console.log(a); + })("PASS").next(); + } + expect: { + !function*(a) { + console.log(a); + }("PASS").next(); + } + expect_stdout: "PASS" + node_version: ">=4" +} + +reduce_iife_1: { + options = { + reduce_vars: true, + unused: true, + } + input: { + console.log(function*(a) { + yield a; + }(42).next().value); + } + expect: { + console.log(function*(a) { + yield 42; + }().next().value); + } + expect_stdout: "42" + node_version: ">=4" +} + +reduce_iife_2: { + options = { + evaluate: true, + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + var a = "PASS"; + (function*() { + a = "FAIL"; + })(); + console.log(a); + } + expect: { + var a = "PASS"; + (function*() { + a = "FAIL"; + })(); + console.log(a); + } + expect_stdout: "PASS" + node_version: ">=4" +} + +reduce_single_use_defun: { + options = { + reduce_vars: true, + toplevel: true, + unused: true, + } + input: { + function* f(a) { + console.log(a); + } + f("PASS").next(); + } + expect: { + (function*(a) { + console.log(a); + })("PASS").next(); + } + expect_stdout: "PASS" + node_version: ">=4" +} + +reduce_tagged: { + options = { + reduce_funcs: true, + reduce_vars: true, + unused: true, + } + input: { + function* f() { + function g() { + h`foo`; + } + g(); + function h(s) { + console.log(s[0]); + } + h([ "bar" ]); + } + f().next(); + } + expect: { + function* f() { + (function() { + h`foo`; + })(); + function h(s) { + console.log(s[0]); + } + h([ "bar" ]); + } + f().next(); + } + expect_stdout: [ + "foo", + "bar", + ] + node_version: ">=4" +} + +reduce_tagged_async: { + options = { + reduce_funcs: true, + reduce_vars: true, + unused: true, + } + input: { + async function* f() { + function g() { + h`foo`; + } + g(); + function h(s) { + console.log(s[0]); + } + h([ "bar" ]); + } + f().next(); + } + expect: { + async function* f() { + (function() { + h`foo`; + })(); + function h(s) { + console.log(s[0]); + } + h([ "bar" ]); + } + f().next(); + } + expect_stdout: [ + "foo", + "bar", + ] + node_version: ">=10" +} + +lift_sequence: { + options = { + sequences: true, + yields: true, + } + input: { + console.log(function*() { + yield (console, "PASS"); + }().next().value); + } + expect: { + console.log(function*() { + console, yield "PASS"; + }().next().value); + } + expect_stdout: "PASS" + node_version: ">=4" +} + +inline_nested_yield: { + options = { + inline: true, + sequences: true, + yields: true, + } + input: { + var a = function*() { + yield* function*() { + yield "foo"; + return "FAIL"; + }(); + }(), b; + do { + b = a.next(); + console.log(b.value); + } while (!b.done); + } + expect: { + var a = function*() { + yield "foo", + "FAIL"; + }(), b; + do { + b = a.next(), + console.log(b.value); + } while (!b.done); + } + expect_stdout: [ + "foo", + "undefined", + ] + node_version: ">=4" +} + +issue_4618: { + options = { + functions: true, + reduce_vars: true, + unused: true, + } + input: { + console.log(typeof function() { + var yield = function* f() { + console || f(); + }; + console.log; + return yield; + }()); + } + expect: { + console.log(typeof function() { + var yield = function* f() { + console || f(); + }; + console.log; + return yield; + }()); + } + expect_stdout: "function" + node_version: ">=4" +} diff --git a/test/mocha/async.js b/test/mocha/awaits.js similarity index 100% rename from test/mocha/async.js rename to test/mocha/awaits.js diff --git a/test/mocha/yields.js b/test/mocha/yields.js new file mode 100644 index 00000000..d995d61d --- /dev/null +++ b/test/mocha/yields.js @@ -0,0 +1,82 @@ +var assert = require("assert"); +var UglifyJS = require("../node"); + +describe("generator", function() { + it("Should reject `yield` as symbol name within generator functions only", function() { + [ + "function yield() {}", + "function(yield) {}", + "function() { yield:{} }", + "function() { var yield; }", + "function() { function yield() {} }", + "function() { try {} catch (yield) {} }", + ].forEach(function(code) { + var ast = UglifyJS.parse("(" + code + ")();"); + assert.strictEqual(ast.TYPE, "Toplevel"); + assert.strictEqual(ast.body.length, 1); + assert.strictEqual(ast.body[0].TYPE, "SimpleStatement"); + assert.strictEqual(ast.body[0].body.TYPE, "Call"); + assert.strictEqual(ast.body[0].body.expression.TYPE, "Function"); + assert.throws(function() { + UglifyJS.parse("(" + code.replace(/^function/, "function*") + ")();"); + }, function(e) { + return e instanceof UglifyJS.JS_Parse_Error; + }, code); + }); + }); + it("Should reject `yield` expression outside of generator functions", function() { + [ + "yield 42;", + "function f() { yield 42; }", + "function* f() { function g() { yield 42; } }", + ].forEach(function(code) { + assert.throws(function() { + UglifyJS.parse(code); + }, function(e) { + return e instanceof UglifyJS.JS_Parse_Error; + }, code); + }); + }); + it("Should reject `yield` expression directly on computed key of function argument", function() { + [ + "function f({ [yield 42]: a }) {}", + "function* f({ [yield 42]: a }) {}", + ].forEach(function(code) { + assert.throws(function() { + UglifyJS.parse(code); + }, function(e) { + return e instanceof UglifyJS.JS_Parse_Error; + }, code); + }); + }); + it("Should accept `yield` expression nested within computed key of function argument", function() { + [ + "function f({ [function*() { yield 42; }()]: a }) {}", + "function* f({ [function*() { yield 42; }()]: a }) {}", + ].forEach(function(code) { + var ast = UglifyJS.parse(code); + assert.strictEqual(ast.TYPE, "Toplevel"); + assert.strictEqual(ast.body.length, 1); + assert.strictEqual(ast.body[0].argnames.length, 1); + assert.strictEqual(ast.body[0].argnames[0].TYPE, "DestructuredObject"); + }); + }); + it("Should reject `yield*` without an expression", function() { + [ + "yield*", + "yield*;", + "yield*,", + "(yield*)", + "[ yield* ]", + "42[yield*]", + "yield* && 42", + ].forEach(function(code) { + code = "function* f() { " + code + " }"; + assert.throws(function() { + UglifyJS.parse(code); + }, function(e) { + return e instanceof UglifyJS.JS_Parse_Error; + }, code); + }); + }); +}); diff --git a/test/reduce.js b/test/reduce.js index 9d5f053c..5f192aca 100644 --- a/test/reduce.js +++ b/test/reduce.js @@ -666,11 +666,7 @@ function is_timed_out(result) { } function is_statement(node) { - return node instanceof U.AST_Statement - && !(node instanceof U.AST_Arrow - || node instanceof U.AST_AsyncArrow - || node instanceof U.AST_AsyncFunction - || node instanceof U.AST_Function); + return node instanceof U.AST_Statement && !(node instanceof U.AST_LambdaExpression); } function merge_sequence(array, node) { diff --git a/test/ufuzz/index.js b/test/ufuzz/index.js index 5256ec63..58228bdb 100644 --- a/test/ufuzz/index.js +++ b/test/ufuzz/index.js @@ -134,6 +134,7 @@ var SUPPORT = function(matrix) { }({ arrow: "a => 0;", async: "async function f(){}", + async_generator: "async function* f(){}", bigint: "42n", catch_omit_var: "try {} catch {}", computed_key: "({[0]: 0});", @@ -141,6 +142,7 @@ var SUPPORT = function(matrix) { default_value: "[ a = 0 ] = [];", destructuring: "[] = [];", exponentiation: "0 ** 0", + generator: "function* f(){}", let: "let a;", rest: "var [...a] = [];", rest_object: "var {...a} = {};", @@ -349,6 +351,7 @@ var avoid_vars = []; var block_vars = []; var unique_vars = []; var async = false; +var generator = false; var loops = 0; var funcs = 0; var called = Object.create(null); @@ -368,6 +371,7 @@ function createTopLevelCode() { block_vars.length = 0; unique_vars.length = 0; async = false; + generator = false; loops = 0; funcs = 0; called = Object.create(null); @@ -395,9 +399,11 @@ function addTrailingComma(list) { return SUPPORT.trailing_comma && list && rng(20) == 0 ? list + "," : list; } -function createParams(was_async, noDuplicate) { +function createParams(was_async, was_generator, noDuplicate) { var save_async = async; if (was_async) async = true; + var save_generator = generator; + if (was_generator) generator = true; var len = unique_vars.length; var params = []; for (var n = rng(4); --n >= 0;) { @@ -406,6 +412,7 @@ function createParams(was_async, noDuplicate) { params.push(name); } unique_vars.length = len; + generator = save_generator; async = save_async; return addTrailingComma(params.join(", ")); } @@ -434,7 +441,7 @@ function createArgs(recurmax, stmtDepth, canThrow, noTemplate) { return "(" + addTrailingComma(args.join(", ")) + ")"; } -function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was_async) { +function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was_async, was_generator) { var avoid = []; var len = unique_vars.length; var pairs = createPairs(recurmax, !nameLenBefore); @@ -476,9 +483,18 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was async = false; if (save_async || was_async) addAvoidVar("await"); } + var save_generator = generator; + if (was_generator != null) { + generator = false; + if (save_generator || was_generator) addAvoidVar("yield"); + } avoid.forEach(addAvoidVar); var save_vars = nameLenBefore && VAR_NAMES.splice(nameLenBefore); if (nameFn) nameFn(); + if (was_generator != null) { + generator = was_generator; + if (save_generator || was_generator) removeAvoidVar("yield"); + } if (was_async != null) { async = was_async; if (save_async || was_async) removeAvoidVar("await"); @@ -486,6 +502,7 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was if (valueFn) valueFn(); if (save_vars) [].push.apply(VAR_NAMES, save_vars); avoid.forEach(removeAvoidVar); + generator = save_generator; async = save_async; } @@ -509,7 +526,10 @@ function createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, was unique_vars.push("a", "b", "c", "undefined", "NaN", "Infinity"); var save_async = async; if (was_async) async = true; + var save_generator = generator; + if (was_generator) generator = true; var name = createVarName(MANDATORY); + generator = save_generator; async = save_async; unique_vars.length -= 6; avoid.push(name); @@ -707,7 +727,25 @@ function mayCreateBlockVariables(recurmax, stmtDepth, canThrow, fn) { } function makeFunction(name) { - return (async ? "async function " : "function ") + name; + if (generator) { + name = "function* " + name; + } else { + name = "function " + name; + } + if (async) name = "async " + name; + return name; +} + +function invokeGenerator(was_generator) { + if (generator && !was_generator) switch (rng(4)) { + case 0: + return ".next()"; + case 1: + return ".next().done"; + case 2: + return ".next().value"; + } + return ""; } function createFunction(recurmax, allowDefun, canThrow, stmtDepth) { @@ -718,6 +756,15 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) { var nameLenBefore = VAR_NAMES.length; var save_async = async; async = SUPPORT.async && rng(50) == 0; + var save_generator = generator; + generator = SUPPORT.generator && rng(50) == 0; + if (async && generator && !SUPPORT.async_generator) { + if (rng(2)) { + async = false; + } else { + generator = false; + } + } createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) { if (allowDefun || rng(5) > 0) { name = "f" + funcs++; @@ -729,12 +776,12 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) { var params; if (SUPPORT.destructuring && (!allowDefun || !(name in called)) && rng(2)) { called[name] = false; - var pairs = createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, save_async); + var pairs = createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, save_async, save_generator); params = pairs.names.join(", "); if (!pairs.has_rest) params = addTrailingComma(params); args = "(" + addTrailingComma(pairs.values.join(", ")) + ")"; } else { - params = createParams(save_async); + params = createParams(save_async, save_generator); } s.push(makeFunction(name) + "(" + params + "){", strictMode()); s.push(defns()); @@ -748,6 +795,8 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) { s.push("}", ""); s = filterDirective(s).join("\n"); }); + var call_next = invokeGenerator(save_generator); + generator = save_generator; async = save_async; VAR_NAMES.length = nameLenBefore; @@ -755,9 +804,11 @@ function createFunction(recurmax, allowDefun, canThrow, stmtDepth) { // avoid "function statements" (decl inside statements) s = "var " + createVarName(MANDATORY) + " = " + s; s += args || createArgs(recurmax, stmtDepth, canThrow); + s += call_next; } else if (!(name in called) || args || rng(3)) { s += "var " + createVarName(MANDATORY) + " = " + name; s += args || createArgs(recurmax, stmtDepth, canThrow); + s += call_next; } return s + ";"; @@ -1019,7 +1070,9 @@ function createExpression(recurmax, noComma, stmtDepth, canThrow) { return "((c = c + 1) + (" + _createExpression(recurmax, noComma, stmtDepth, canThrow) + "))"; // c only gets incremented default: var expr = "(" + _createExpression(recurmax, noComma, stmtDepth, canThrow) + ")"; - return async && rng(50) == 0 ? "(await" + expr + ")" : expr; + if (async && rng(50) == 0) return "(await" + expr + ")"; + if (generator && rng(50) == 0) return "(yield" + (canThrow && rng(20) == 0 ? "*" : "") + expr + ")"; + return expr; } } @@ -1027,6 +1080,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { var p = 0; switch (rng(_createExpression.N)) { case p++: + if (generator && rng(50) == 0) return "yield"; case p++: return createUnaryPrefix() + (rng(2) ? "a" : "b"); case p++: @@ -1082,25 +1136,34 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { var nameLenBefore = VAR_NAMES.length; var save_async = async; async = SUPPORT.async && rng(50) == 0; + var save_generator = generator; + generator = SUPPORT.generator && rng(50) == 0; + if (async && generator && !SUPPORT.async_generator) { + if (rng(2)) { + async = false; + } else { + generator = false; + } + } unique_vars.push("c"); var name = createVarName(MAYBE); // note: this name is only accessible from _within_ the function. and immutable at that. unique_vars.pop(); var s = []; switch (rng(5)) { case 0: - if (SUPPORT.arrow && !name && rng(2)) { + if (SUPPORT.arrow && !name && !generator && 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); + var pairs = createAssignmentPairs(recurmax, stmtDepth, canThrow, nameLenBefore, save_async, save_generator); params = pairs.names.join(", "); if (!pairs.has_rest) params = addTrailingComma(params); args = "(" + addTrailingComma(pairs.values.join(", ")) + ")"; } else { - params = createParams(save_async, NO_DUPLICATE); + params = createParams(save_async, save_generator, NO_DUPLICATE); } params = (async ? "async (" : "(") + params + ") => "; if (defns) { @@ -1127,6 +1190,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { suffix = ")"; } }); + generator = save_generator; async = save_async; VAR_NAMES.length = nameLenBefore; if (!args && rng(2)) args = createArgs(recurmax, stmtDepth, canThrow); @@ -1137,7 +1201,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { "(" + makeFunction(name) + "(){", strictMode(), createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), - rng(2) ? "})" : "})()" + rng(2) ? "})" : "})()" + invokeGenerator(save_generator) ); } break; @@ -1146,7 +1210,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { "+" + makeFunction(name) + "(){", strictMode(), createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), - "}()" + "}()" + invokeGenerator(save_generator) ); break; case 2: @@ -1154,7 +1218,7 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { "!" + makeFunction(name) + "(){", strictMode(), createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), - "}()" + "}()" + invokeGenerator(save_generator) ); break; case 3: @@ -1162,15 +1226,16 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { "void " + makeFunction(name) + "(){", strictMode(), createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), - "}()" + "}()" + invokeGenerator(save_generator) ); break; default: async = false; + generator = false; var instantiate = rng(4) ? "new " : ""; createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) { s.push( - instantiate + "function " + name + "(" + createParams(save_async) + "){", + instantiate + "function " + name + "(" + createParams(save_async, save_generator) + "){", strictMode(), defns() ); @@ -1180,11 +1245,13 @@ function _createExpression(recurmax, noComma, stmtDepth, canThrow) { } s.push(_createStatements(rng(5) + 1, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth)); }); + generator = save_generator; async = save_async; VAR_NAMES.length = nameLenBefore; s.push(rng(2) ? "}" : "}" + createArgs(recurmax, stmtDepth, canThrow, instantiate)); break; } + generator = save_generator; async = save_async; VAR_NAMES.length = nameLenBefore; return filterDirective(s).join("\n"); @@ -1328,8 +1395,6 @@ function createTemplateLiteral(recurmax, stmtDepth, canThrow) { } var SAFE_KEYS = [ - "length", - "foo", "a", "b", "c", @@ -1337,7 +1402,13 @@ var SAFE_KEYS = [ "null", "NaN", "Infinity", + "done", + "foo", "in", + "length", + "next", + "then", + "value", "var", ]; var KEYS = [ @@ -1367,12 +1438,14 @@ function createObjectKey(recurmax, stmtDepth, canThrow) { function createObjectFunction(recurmax, stmtDepth, canThrow) { var nameLenBefore = VAR_NAMES.length; var save_async = async; + var save_generator = generator; var s; var name = createObjectKey(recurmax, stmtDepth, canThrow); var fn; switch (rng(SUPPORT.computed_key ? 3 : 2)) { case 0: async = false; + generator = false; fn = function(defns) { s = [ "get " + name + "(){", @@ -1390,6 +1463,7 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) { prop = getDotKey(); } while (name == prop); async = false; + generator = false; fn = function(defns) { s = [ "set " + name + "(" + createVarName(MANDATORY) + "){", @@ -1403,9 +1477,19 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) { break; default: async = SUPPORT.async && rng(50) == 0; + generator = SUPPORT.generator && rng(50) == 0; + if (async && generator && !SUPPORT.async_generator) { + if (rng(2)) { + async = false; + } else { + generator = false; + } + } fn = function(defns) { + if (generator) name = "*" + name; + if (async) name = "async "+ name; s = [ - (async ? "async " : "") + name + "(" + createParams(save_async, NO_DUPLICATE) + "){", + name + "(" + createParams(save_async, save_generator, NO_DUPLICATE) + "){", strictMode(), defns(), _createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth), @@ -1415,6 +1499,7 @@ function createObjectFunction(recurmax, stmtDepth, canThrow) { break; } createBlockVariables(recurmax, stmtDepth, canThrow, fn); + generator = save_generator; async = save_async; VAR_NAMES.length = nameLenBefore; return filterDirective(s).join("\n"); @@ -1563,8 +1648,11 @@ function createTypeofExpr(recurmax, stmtDepth, canThrow) { function createVar() { var save_async = async; + var save_generator = generator; if (!async && avoid_vars.indexOf("await") >= 0) async = true; + if (!generator && avoid_vars.indexOf("yield") >= 0) generator = true; var name = createVarName(MANDATORY, DONT_STORE); + generator = save_generator; async = save_async; return name; } @@ -1613,7 +1701,11 @@ 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 || async && name == "await"); + } while (!name + || avoid_vars.indexOf(name) >= 0 + || noConst && block_vars.indexOf(name) >= 0 + || async && name == "await" + || generator && name == "yield"); return name; } @@ -1625,7 +1717,10 @@ function createVarName(maybe, dontStore) { name = VAR_NAMES[rng(VAR_NAMES.length)]; if (--tries < 0) suffix++; if (suffix) name += "_" + suffix; - } while (unique_vars.indexOf(name) >= 0 || block_vars.indexOf(name) >= 0 || async && name == "await"); + } while (unique_vars.indexOf(name) >= 0 + || block_vars.indexOf(name) >= 0 + || async && name == "await" + || generator && name == "yield"); if (!dontStore) VAR_NAMES.push(name); return name; } -- 2.34.1