diff --git a/lib/output.js b/lib/output.js index 2f8faf7d..47d07678 100644 --- a/lib/output.js +++ b/lib/output.js @@ -799,6 +799,10 @@ function OutputStream(options) { // a = yield 3 if (p instanceof AST_Binary && p.operator !== "=") return true; + // (yield 1)() + // new (yield 1)() + if (p instanceof AST_Call && p.expression === this) + return true; // (yield 1) ? yield 2 : yield 3 if (p instanceof AST_Conditional && p.condition === this) return true; diff --git a/lib/parse.js b/lib/parse.js index ee904962..fb381ec3 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1168,10 +1168,6 @@ function parse($TEXT, options) { function labeled_statement() { var label = as_symbol(AST_Label); - if (label.name === "yield" && is_in_generator()) { - // Ecma-262, 12.1.1 Static Semantics: Early Errors - token_error(S.prev, "Yield cannot be used as label inside generators"); - } if (label.name === "await" && is_in_async()) { token_error(S.prev, "await cannot be used as label inside async function"); } @@ -1331,7 +1327,7 @@ function parse($TEXT, options) { if (name && ctor !== AST_Accessor && !(name instanceof AST_SymbolDeclaration)) unexpected(prev()); - var args = parameters(); + var args = []; var body = _function_body(true, is_generator || is_generator_property, is_async, name, args); return new ctor({ start : args.start, @@ -1402,9 +1398,8 @@ function parse($TEXT, options) { return tracker; } - function parameters() { + function parameters(params) { var start = S.token; - var params = []; var used_parameters = track_used_binding_identifiers(true, S.input.has_directive("use strict")); expect("("); @@ -1424,7 +1419,6 @@ function parse($TEXT, options) { } next(); - return params; } function parameter(used_parameters, symbol_type) { @@ -1511,12 +1505,7 @@ function parse($TEXT, options) { } } else if (is("name")) { used_parameters.add_parameter(S.token); - elements.push(new symbol_type({ - start: S.token, - name: S.token.value, - end: S.token - })); - next(); + elements.push(as_symbol(symbol_type)); } else { croak("Invalid function parameter"); } @@ -1566,11 +1555,8 @@ function parse($TEXT, options) { } if (is("name") && (is_token(peek(), "punc") || is_token(peek(), "operator")) && [",", "}", "="].indexOf(peek().value) !== -1) { used_parameters.add_parameter(S.token); - var value = new symbol_type({ - start: S.token, - name: S.token.value, - end: S.token, - }); + var start = prev(); + var value = as_symbol(symbol_type); if (is_expand) { elements.push(new AST_Expansion({ start: expand_token, @@ -1579,13 +1565,12 @@ function parse($TEXT, options) { })); } else { elements.push(new AST_ObjectKeyVal({ - start: prev(), - key: S.token.value, + start: start, + key: value.name, value: value, end: value.end, })); } - next(); } else if (is("punc", "}")) { continue; // Allow trailing hole } else { @@ -1642,12 +1627,7 @@ function parse($TEXT, options) { }); } else if (is("name")) { used_parameters.add_parameter(S.token); - next(); - return new symbol_type({ - start: prev(), - name: prev().value, - end: prev() - }); + return as_symbol(symbol_type); } else { croak("Invalid function parameter"); } @@ -1701,6 +1681,7 @@ function parse($TEXT, options) { S.in_generator = S.in_function; if (is_async) S.in_async = S.in_function; + if (args) parameters(args); if (block) S.in_directives = true; S.in_loop = 0; @@ -1708,10 +1689,8 @@ function parse($TEXT, options) { if (block) { S.input.push_directives_stack(); var a = block_(); - if (S.input.has_directive("use strict")) { - if (name) strict_verify_symbol(name); - if (args) args.forEach(strict_verify_symbol); - } + if (name) _verify_symbol(name); + if (args) args.forEach(_verify_symbol); S.input.pop_directives_stack(); } else { var a = expression(false); @@ -2593,9 +2572,14 @@ function parse($TEXT, options) { unexpected(tmp); } case "name": - if (tmp.value == "yield" && !is_token(peek(), "punc", ":") && !is_token(peek(), "punc", "(") - && S.input.has_directive("use strict") && !is_in_generator()) { - token_error(tmp, "Unexpected yield identifier inside strict mode"); + if (tmp.value == "yield") { + if (is_in_generator()) { + token_error(tmp, "Yield cannot be used as identifier inside generators"); + } else if (!is_token(peek(), "punc", ":") + && !is_token(peek(), "punc", "(") + && S.input.has_directive("use strict")) { + token_error(tmp, "Unexpected yield identifier inside strict mode"); + } } case "string": case "num": @@ -2626,9 +2610,19 @@ 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); + function _verify_symbol(sym) { + var name = sym.name; + if (is_in_generator() && name == "yield") { + token_error(sym.start, "Yield cannot be used as identifier inside generators"); + } + if (S.input.has_directive("use strict")) { + if (name == "yield") { + token_error(sym.start, "Unexpected yield identifier inside strict mode"); + } + if (sym instanceof AST_SymbolDeclaration && (name == "arguments" || name == "eval")) { + token_error(sym.start, "Unexpected " + name + " in strict mode"); + } + } } function as_symbol(type, noerror) { @@ -2636,13 +2630,8 @@ function parse($TEXT, options) { if (!noerror) croak("Name expected"); return null; } - if (is("name", "yield") && S.input.has_directive("use strict")) { - token_error(S.prev, "Unexpected yield identifier inside strict mode"); - } var sym = _make_symbol(type); - if (S.input.has_directive("use strict") && sym instanceof AST_SymbolDeclaration) { - strict_verify_symbol(sym); - } + _verify_symbol(sym); next(); return sym; }; @@ -2870,7 +2859,7 @@ function parse($TEXT, options) { next(); return _yield_expression(); } else if (S.input.has_directive("use strict")) { - token_error(S.token, "Unexpected yield identifier inside strict mode") + token_error(S.token, "Unexpected yield identifier inside strict mode"); } } diff --git a/test/compress/yield.js b/test/compress/yield.js index d64605b1..cb11147c 100644 --- a/test/compress/yield.js +++ b/test/compress/yield.js @@ -88,9 +88,8 @@ yield_before_punctuators: { })(); function* g1() { (yield) } function* g2() { [yield] } - function* g3() { return {yield} } // Added return to avoid {} drop - function* g4() { yield, yield; } - function* g5() { (yield) ? yield : yield; } + function* g3() { yield, yield; } + function* g4() { (yield) ? yield : yield; } } expect: { iter = (function*() { @@ -98,9 +97,8 @@ yield_before_punctuators: { })(); function* g1() { (yield) } function* g2() { [yield] } - function* g3() { return {yield} } - function* g4() { yield, yield; } - function* g5() { (yield) ? yield : yield; } + function* g3() { yield, yield; } + function* g4() { (yield) ? yield : yield; } } } @@ -199,3 +197,21 @@ yield_as_ES5_property: { expect_exact: '"use strict";console.log({yield:42}.yield);' expect_stdout: "42" } + +issue_2689: { + options = { + collapse_vars: true, + unused: true, + } + input: { + function* y() { + var t = yield x(); + return new t(); + } + } + expect: { + function* y() { + return new (yield x()); + } + } +} diff --git a/test/mocha/yield.js b/test/mocha/yield.js index 26a66ec1..ce286968 100644 --- a/test/mocha/yield.js +++ b/test/mocha/yield.js @@ -8,18 +8,6 @@ describe("Yield", function() { assert.strictEqual(result.code, 'function*foo(e){return yield 1,yield 2,3}'); }); - it("Should not allow yield as labelIdentifier within generators", function() { - var js = "function* g() {yield: 1}" - var test = function() { - UglifyJS.parse(js); - } - var expect = function(e) { - return e instanceof UglifyJS.JS_Parse_Error && - e.message === "Yield cannot be used as label inside generators"; - } - assert.throws(test, expect); - }); - it("Should not allow yield* followed by a semicolon in generators", function() { var js = "function* test() {yield*\n;}"; var test = function() { @@ -65,42 +53,68 @@ describe("Yield", function() { ); }); + var identifiers = [ + // Fail in as_symbol + 'import yield from "bar";', + 'yield = 123;', + 'yield: "123";', + 'for(;;){break yield;}', + 'for(;;){continue yield;}', + 'function yield(){}', + 'try { new Error("")} catch (yield) {}', + 'var yield = "foo";', + 'class yield {}', + // Fail in as_property_name + 'var foo = {yield};', + ]; + it("Should not allow yield to be used as symbol, identifier or shorthand property outside generators in strict mode", function() { - var tests = [ - // Fail in as_symbol - '"use strict"; import yield from "bar";', - '"use strict"; yield = 123;', - '"use strict"; yield: "123";', - '"use strict"; for(;;){break yield;}', - '"use strict"; for(;;){continue yield;}', - '"use strict"; function yield(){}', - '"use strict"; function foo(...yield){}', - '"use strict"; try { new Error("")} catch (yield) {}', - '"use strict"; var yield = "foo";', - '"use strict"; class yield {}', - - // Fail in maybe_assign - '"use strict"; var foo = yield;', - '"use strict"; var foo = bar = yield', - - // Fail in as_property_name - '"use strict"; var foo = {yield};', - ]; - - var fail = function(e) { + function fail(e) { return e instanceof UglifyJS.JS_Parse_Error && /^Unexpected yield identifier (?:as parameter )?inside strict mode/.test(e.message); } - var test = function(input) { + function test(input) { return function() { UglifyJS.parse(input); } } - for (var i = 0; i < tests.length; i++) { - assert.throws(test(tests[i]), fail, tests[i]); + identifiers.concat([ + // Fail in as_symbol + "function foo(...yield){}", + // Fail in maybe_assign + 'var foo = yield;', + 'var foo = bar = yield', + ]).map(function(code) { + return '"use strict"; ' + code; + }).forEach(function(code) { + assert.throws(test(code), fail, code); + }); + }); + + it("Should not allow yield to be used as symbol, identifier or shorthand property inside generators", function() { + function fail(e) { + return e instanceof UglifyJS.JS_Parse_Error && [ + "Unexpected token: operator (=)", + "Yield cannot be used as identifier inside generators", + ].indexOf(e.message) >= 0; } + + function test(input) { + return function() { + UglifyJS.parse(input); + } + } + + identifiers.map(function(code) { + return "function* f() { " + code + " }"; + }).concat([ + // Fail in as_symbol + "function* f(yield) {}", + ]).forEach(function(code) { + assert.throws(test(code), fail, code); + }); }); it("Should allow yield to be used as class/object property name", function() {