diff --git a/lib/ast.js b/lib/ast.js index 358862b1..2752454b 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -437,9 +437,10 @@ var AST_ArrowParametersOrSeq = DEFNODE("ArrowParametersOrSeq", "expressions", { } }); -var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments", { +var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments isgenerator", { $documentation: "Base class for functions", $propdoc: { + isgenerator: "is generatorFn or not", name: "[AST_SymbolDeclaration?] the name of this function", argnames: "[AST_SymbolFunarg|AST_Destructuring|AST_Expansion*] array of function arguments, destructurings, or expanding arguments", uses_arguments: "[boolean/S] tells whether this function accesses the arguments array" diff --git a/lib/output.js b/lib/output.js index c0b86b96..304149cf 100644 --- a/lib/output.js +++ b/lib/output.js @@ -226,6 +226,10 @@ function OutputStream(options) { OUTPUT += str; }; + var star = function(){ + print("*"); + } + var space = options.beautify ? function() { print(" "); } : function() { @@ -343,6 +347,7 @@ function OutputStream(options) { should_break : function() { return options.width && this.current_width() >= options.width }, newline : newline, print : print, + star : star, space : space, comma : comma, colon : colon, @@ -783,6 +788,9 @@ function OutputStream(options) { var self = this; if (!nokeyword) { output.print("function"); + if (this.isgenerator) { + output.star(); + } if (self.name) { output.space(); } @@ -1141,8 +1149,13 @@ function OutputStream(options) { output.print(self.operator); }); DEFPRINT(AST_Binary, function(self, output){ + var isYield = (self.left.operator == "yield" || self.left.operator === "yield*"); var op = self.operator; + + isYield && output.print("("); self.left.print(output); + isYield && output.print(")"); + if (op[0] == ">" /* ">>" ">>>" ">" ">=" */ && self.left instanceof AST_UnaryPostfix && self.left.operator == "--") { @@ -1152,7 +1165,12 @@ function OutputStream(options) { // the space is optional depending on "beautify" output.space(); } + + isYield = (self.right.operator == "yield" || self.right.operator === "yield*"); + isYield && output.print("("); output.print(op); + isYield && output.print(")"); + if ((op == "<" || op == "<<") && self.right instanceof AST_UnaryPrefix && self.right.operator == "!" diff --git a/lib/parse.js b/lib/parse.js index 072e7fb4..32374bb7 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -44,7 +44,7 @@ "use strict"; -var KEYWORDS = 'break case catch class const continue debugger default delete do else extends finally for function if in instanceof new return switch throw try typeof var let void while with'; +var KEYWORDS = 'break case catch class const continue debugger default delete do else extends finally for function if in instanceof new return yield switch throw try typeof var let void while with'; var KEYWORDS_ATOM = 'false null true'; var RESERVED_WORDS = 'abstract boolean byte char double enum export final float goto implements import int interface long native package private protected public short static super synchronized this throws transient volatile yield' + " " + KEYWORDS_ATOM + " " + KEYWORDS; @@ -68,6 +68,7 @@ var OPERATORS = makePredicate([ "instanceof", "typeof", "new", + "yield", "void", "delete", "++", @@ -627,6 +628,7 @@ var UNARY_PREFIX = makePredicate([ "typeof", "void", "delete", + "yield", "--", "++", "!", @@ -1050,6 +1052,11 @@ function parse($TEXT, options) { var start = S.token var in_statement = ctor === AST_Defun; + var isgenerator = is("operator", "*"); + if (isgenerator) { + next(); + } + var name = is("name") ? as_symbol(in_statement ? AST_SymbolDefun : AST_SymbolLambda) : null; if (in_statement && !name) unexpected(); @@ -1059,6 +1066,7 @@ function parse($TEXT, options) { return new ctor({ start : args.start, end : body.end, + isgenerator: isgenerator, name : name, argnames: args, body : body @@ -1716,6 +1724,10 @@ function parse($TEXT, options) { var start = S.token; if (is("operator") && UNARY_PREFIX(start.value)) { next(); + if (start.type === "operator" && start.value === "yield" && is("operator", "*")) { + start.value = "yield*"; + next(); + } handle_regexp(); var ex = make_unary(AST_UnaryPrefix, start.value, maybe_unary(allow_calls)); ex.start = start; diff --git a/test/compress/harmony.js b/test/compress/harmony.js index c3f48b42..8044161d 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -321,3 +321,19 @@ regression_cannot_use_of: { foo(); /* Label statement missing? No prob. */ } } + +generators: { + input: { + function* fn() {}; + } + expect_exact: "function*fn(){}" +} + +generators_yield: { + input: { + function* fn() { + yield remote(); + } + } + expect_exact: "function*fn(){yield remote()}" +} diff --git a/test/parser.js b/test/parser.js index a84c2df9..2f2af7e4 100644 --- a/test/parser.js +++ b/test/parser.js @@ -118,6 +118,17 @@ module.exports = function () { ok.equal(expanding_def.name.names[0].TYPE, 'SymbolVar'); ok.equal(expanding_def.name.names[1].TYPE, 'Expansion'); ok.equal(expanding_def.name.names[1].symbol.TYPE, 'SymbolVar'); + + // generators + var generators_def = UglifyJS.parse('function* fn() {}').body[0]; + ok.equal(generators_def.isgenerator, true); + + ok.throws(function () { + UglifyJS.parse('function* (){ }'); + }); + + var generators_yield_def = UglifyJS.parse('function* fn() {\nyield remote();\}').body[0].body[0]; + ok.equal(generators_yield_def.body.operator, 'yield'); } // Run standalone