fix parse and output of yield (#2690)

fixes #2689
This commit is contained in:
Alex Lam S.L
2017-12-30 03:27:26 +08:00
committed by GitHub
parent 53600e9869
commit 725aac8b46
4 changed files with 111 additions and 88 deletions

View File

@@ -799,6 +799,10 @@ function OutputStream(options) {
// a = yield 3 // a = yield 3
if (p instanceof AST_Binary && p.operator !== "=") if (p instanceof AST_Binary && p.operator !== "=")
return true; return true;
// (yield 1)()
// new (yield 1)()
if (p instanceof AST_Call && p.expression === this)
return true;
// (yield 1) ? yield 2 : yield 3 // (yield 1) ? yield 2 : yield 3
if (p instanceof AST_Conditional && p.condition === this) if (p instanceof AST_Conditional && p.condition === this)
return true; return true;

View File

@@ -1168,10 +1168,6 @@ function parse($TEXT, options) {
function labeled_statement() { function labeled_statement() {
var label = as_symbol(AST_Label); 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()) { if (label.name === "await" && is_in_async()) {
token_error(S.prev, "await cannot be used as label inside async function"); 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)) if (name && ctor !== AST_Accessor && !(name instanceof AST_SymbolDeclaration))
unexpected(prev()); unexpected(prev());
var args = parameters(); var args = [];
var body = _function_body(true, is_generator || is_generator_property, is_async, name, args); var body = _function_body(true, is_generator || is_generator_property, is_async, name, args);
return new ctor({ return new ctor({
start : args.start, start : args.start,
@@ -1402,9 +1398,8 @@ function parse($TEXT, options) {
return tracker; return tracker;
} }
function parameters() { function parameters(params) {
var start = S.token; var start = S.token;
var params = [];
var used_parameters = track_used_binding_identifiers(true, S.input.has_directive("use strict")); var used_parameters = track_used_binding_identifiers(true, S.input.has_directive("use strict"));
expect("("); expect("(");
@@ -1424,7 +1419,6 @@ function parse($TEXT, options) {
} }
next(); next();
return params;
} }
function parameter(used_parameters, symbol_type) { function parameter(used_parameters, symbol_type) {
@@ -1511,12 +1505,7 @@ function parse($TEXT, options) {
} }
} else if (is("name")) { } else if (is("name")) {
used_parameters.add_parameter(S.token); used_parameters.add_parameter(S.token);
elements.push(new symbol_type({ elements.push(as_symbol(symbol_type));
start: S.token,
name: S.token.value,
end: S.token
}));
next();
} else { } else {
croak("Invalid function parameter"); 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) { if (is("name") && (is_token(peek(), "punc") || is_token(peek(), "operator")) && [",", "}", "="].indexOf(peek().value) !== -1) {
used_parameters.add_parameter(S.token); used_parameters.add_parameter(S.token);
var value = new symbol_type({ var start = prev();
start: S.token, var value = as_symbol(symbol_type);
name: S.token.value,
end: S.token,
});
if (is_expand) { if (is_expand) {
elements.push(new AST_Expansion({ elements.push(new AST_Expansion({
start: expand_token, start: expand_token,
@@ -1579,13 +1565,12 @@ function parse($TEXT, options) {
})); }));
} else { } else {
elements.push(new AST_ObjectKeyVal({ elements.push(new AST_ObjectKeyVal({
start: prev(), start: start,
key: S.token.value, key: value.name,
value: value, value: value,
end: value.end, end: value.end,
})); }));
} }
next();
} else if (is("punc", "}")) { } else if (is("punc", "}")) {
continue; // Allow trailing hole continue; // Allow trailing hole
} else { } else {
@@ -1642,12 +1627,7 @@ function parse($TEXT, options) {
}); });
} else if (is("name")) { } else if (is("name")) {
used_parameters.add_parameter(S.token); used_parameters.add_parameter(S.token);
next(); return as_symbol(symbol_type);
return new symbol_type({
start: prev(),
name: prev().value,
end: prev()
});
} else { } else {
croak("Invalid function parameter"); croak("Invalid function parameter");
} }
@@ -1701,6 +1681,7 @@ function parse($TEXT, options) {
S.in_generator = S.in_function; S.in_generator = S.in_function;
if (is_async) if (is_async)
S.in_async = S.in_function; S.in_async = S.in_function;
if (args) parameters(args);
if (block) if (block)
S.in_directives = true; S.in_directives = true;
S.in_loop = 0; S.in_loop = 0;
@@ -1708,10 +1689,8 @@ function parse($TEXT, options) {
if (block) { if (block) {
S.input.push_directives_stack(); S.input.push_directives_stack();
var a = block_(); var a = block_();
if (S.input.has_directive("use strict")) { if (name) _verify_symbol(name);
if (name) strict_verify_symbol(name); if (args) args.forEach(_verify_symbol);
if (args) args.forEach(strict_verify_symbol);
}
S.input.pop_directives_stack(); S.input.pop_directives_stack();
} else { } else {
var a = expression(false); var a = expression(false);
@@ -2593,9 +2572,14 @@ function parse($TEXT, options) {
unexpected(tmp); unexpected(tmp);
} }
case "name": case "name":
if (tmp.value == "yield" && !is_token(peek(), "punc", ":") && !is_token(peek(), "punc", "(") if (tmp.value == "yield") {
&& S.input.has_directive("use strict") && !is_in_generator()) { if (is_in_generator()) {
token_error(tmp, "Unexpected yield identifier inside strict mode"); 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 "string":
case "num": case "num":
@@ -2626,9 +2610,19 @@ function parse($TEXT, options) {
}); });
}; };
function strict_verify_symbol(sym) { function _verify_symbol(sym) {
if (sym.name == "arguments" || sym.name == "eval") var name = sym.name;
croak("Unexpected " + sym.name + " in strict mode", sym.start.line, sym.start.col, sym.start.pos); 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) { function as_symbol(type, noerror) {
@@ -2636,13 +2630,8 @@ function parse($TEXT, options) {
if (!noerror) croak("Name expected"); if (!noerror) croak("Name expected");
return null; 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); var sym = _make_symbol(type);
if (S.input.has_directive("use strict") && sym instanceof AST_SymbolDeclaration) { _verify_symbol(sym);
strict_verify_symbol(sym);
}
next(); next();
return sym; return sym;
}; };
@@ -2870,7 +2859,7 @@ function parse($TEXT, options) {
next(); next();
return _yield_expression(); return _yield_expression();
} else if (S.input.has_directive("use strict")) { } 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");
} }
} }

View File

@@ -88,9 +88,8 @@ yield_before_punctuators: {
})(); })();
function* g1() { (yield) } function* g1() { (yield) }
function* g2() { [yield] } function* g2() { [yield] }
function* g3() { return {yield} } // Added return to avoid {} drop function* g3() { yield, yield; }
function* g4() { yield, yield; } function* g4() { (yield) ? yield : yield; }
function* g5() { (yield) ? yield : yield; }
} }
expect: { expect: {
iter = (function*() { iter = (function*() {
@@ -98,9 +97,8 @@ yield_before_punctuators: {
})(); })();
function* g1() { (yield) } function* g1() { (yield) }
function* g2() { [yield] } function* g2() { [yield] }
function* g3() { return {yield} } function* g3() { yield, yield; }
function* g4() { yield, yield; } function* g4() { (yield) ? yield : yield; }
function* g5() { (yield) ? yield : yield; }
} }
} }
@@ -199,3 +197,21 @@ yield_as_ES5_property: {
expect_exact: '"use strict";console.log({yield:42}.yield);' expect_exact: '"use strict";console.log({yield:42}.yield);'
expect_stdout: "42" 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());
}
}
}

View File

@@ -8,18 +8,6 @@ describe("Yield", function() {
assert.strictEqual(result.code, 'function*foo(e){return yield 1,yield 2,3}'); 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() { it("Should not allow yield* followed by a semicolon in generators", function() {
var js = "function* test() {yield*\n;}"; var js = "function* test() {yield*\n;}";
var test = function() { 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() { it("Should not allow yield to be used as symbol, identifier or shorthand property outside generators in strict mode", function() {
var tests = [ function fail(e) {
// 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) {
return e instanceof UglifyJS.JS_Parse_Error && return e instanceof UglifyJS.JS_Parse_Error &&
/^Unexpected yield identifier (?:as parameter )?inside strict mode/.test(e.message); /^Unexpected yield identifier (?:as parameter )?inside strict mode/.test(e.message);
} }
var test = function(input) { function test(input) {
return function() { return function() {
UglifyJS.parse(input); UglifyJS.parse(input);
} }
} }
for (var i = 0; i < tests.length; i++) { identifiers.concat([
assert.throws(test(tests[i]), fail, tests[i]); // 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() { it("Should allow yield to be used as class/object property name", function() {