support trailing commas in function parameter lists and calls (#2156)
fixes #2155
This commit is contained in:
56
lib/parse.js
56
lib/parse.js
@@ -856,6 +856,7 @@ function parse($TEXT, options) {
|
|||||||
|
|
||||||
options = defaults(options, {
|
options = defaults(options, {
|
||||||
bare_returns : false,
|
bare_returns : false,
|
||||||
|
ecma : 8,
|
||||||
expression : false,
|
expression : false,
|
||||||
filename : null,
|
filename : null,
|
||||||
html5_comments : true,
|
html5_comments : true,
|
||||||
@@ -1386,22 +1387,20 @@ function parse($TEXT, options) {
|
|||||||
|
|
||||||
function parameters() {
|
function parameters() {
|
||||||
var start = S.token;
|
var start = S.token;
|
||||||
var first = true;
|
|
||||||
var params = [];
|
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("(");
|
||||||
|
|
||||||
while (!is("punc", ")")) {
|
while (!is("punc", ")")) {
|
||||||
if (first) {
|
|
||||||
first = false;
|
|
||||||
} else {
|
|
||||||
expect(",");
|
|
||||||
}
|
|
||||||
|
|
||||||
var param = parameter(used_parameters);
|
var param = parameter(used_parameters);
|
||||||
params.push(param);
|
params.push(param);
|
||||||
|
|
||||||
|
if (!is("punc", ")")) {
|
||||||
|
expect(",");
|
||||||
|
if (is("punc", ")") && options.ecma < 8) unexpected();
|
||||||
|
}
|
||||||
|
|
||||||
if (param instanceof AST_Expansion) {
|
if (param instanceof AST_Expansion) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1617,25 +1616,40 @@ function parse($TEXT, options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function params_or_seq_() {
|
function params_or_seq_(allow_arrows, maybe_sequence) {
|
||||||
var first = true;
|
var spread_token;
|
||||||
|
var invalid_sequence;
|
||||||
|
var trailing_comma;
|
||||||
var a = [];
|
var a = [];
|
||||||
|
expect("(");
|
||||||
while (!is("punc", ")")) {
|
while (!is("punc", ")")) {
|
||||||
if (first) first = false; else expect(",");
|
if (spread_token) unexpected(spread_token);
|
||||||
if (is("expand", "...")) {
|
if (is("expand", "...")) {
|
||||||
var spread_token = S.token;
|
spread_token = S.token;
|
||||||
|
if (maybe_sequence) invalid_sequence = S.token;
|
||||||
next();
|
next();
|
||||||
a.push(new AST_Expansion({
|
a.push(new AST_Expansion({
|
||||||
start: prev(),
|
start: prev(),
|
||||||
expression: expression(),
|
expression: expression(),
|
||||||
end: S.token,
|
end: S.token,
|
||||||
}));
|
}));
|
||||||
if (!is("punc", ")")) {
|
|
||||||
unexpected(spread_token);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
a.push(expression());
|
a.push(expression());
|
||||||
}
|
}
|
||||||
|
if (!is("punc", ")")) {
|
||||||
|
expect(",");
|
||||||
|
if (is("punc", ")")) {
|
||||||
|
if (options.ecma < 8) unexpected();
|
||||||
|
trailing_comma = prev();
|
||||||
|
if (maybe_sequence) invalid_sequence = trailing_comma;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(")");
|
||||||
|
if (allow_arrows && is("arrow", "=>")) {
|
||||||
|
if (spread_token && trailing_comma) unexpected(trailing_comma);
|
||||||
|
} else if (invalid_sequence) {
|
||||||
|
unexpected(invalid_sequence);
|
||||||
}
|
}
|
||||||
return a;
|
return a;
|
||||||
}
|
}
|
||||||
@@ -1883,7 +1897,7 @@ function parse($TEXT, options) {
|
|||||||
var newexp = expr_atom(false), args;
|
var newexp = expr_atom(false), args;
|
||||||
if (is("punc", "(")) {
|
if (is("punc", "(")) {
|
||||||
next();
|
next();
|
||||||
args = expr_list(")");
|
args = expr_list(")", options.ecma >= 8);
|
||||||
} else {
|
} else {
|
||||||
args = [];
|
args = [];
|
||||||
}
|
}
|
||||||
@@ -2000,9 +2014,7 @@ function parse($TEXT, options) {
|
|||||||
switch (S.token.value) {
|
switch (S.token.value) {
|
||||||
case "(":
|
case "(":
|
||||||
if (async && !allow_calls) break;
|
if (async && !allow_calls) break;
|
||||||
next();
|
var exprs = params_or_seq_(allow_arrows, !async);
|
||||||
var exprs = params_or_seq_();
|
|
||||||
expect(")");
|
|
||||||
if (allow_arrows && is("arrow", "=>")) {
|
if (allow_arrows && is("arrow", "=>")) {
|
||||||
return arrow_function(start, exprs.map(to_fun_args), !!async);
|
return arrow_function(start, exprs.map(to_fun_args), !!async);
|
||||||
}
|
}
|
||||||
@@ -2608,11 +2620,9 @@ function parse($TEXT, options) {
|
|||||||
return expr;
|
return expr;
|
||||||
};
|
};
|
||||||
|
|
||||||
var call_args = embed_tokens(function call_args() {
|
var call_args = embed_tokens(function _call_args() {
|
||||||
var first = true;
|
|
||||||
var args = [];
|
var args = [];
|
||||||
while (!is("punc", ")")) {
|
while (!is("punc", ")")) {
|
||||||
if (first) first = false; else expect(",");
|
|
||||||
if (is("expand", "...")) {
|
if (is("expand", "...")) {
|
||||||
next();
|
next();
|
||||||
args.push(new AST_Expansion({
|
args.push(new AST_Expansion({
|
||||||
@@ -2622,6 +2632,10 @@ function parse($TEXT, options) {
|
|||||||
} else {
|
} else {
|
||||||
args.push(expression(false));
|
args.push(expression(false));
|
||||||
}
|
}
|
||||||
|
if (!is("punc", ")")) {
|
||||||
|
expect(",");
|
||||||
|
if (is("punc", ")") && options.ecma < 8) unexpected();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
next();
|
next();
|
||||||
return args;
|
return args;
|
||||||
|
|||||||
1
test/input/invalid/sequence.js
Normal file
1
test/input/invalid/sequence.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
(a, ...b);
|
||||||
@@ -596,6 +596,21 @@ describe("bin/uglifyjs", function () {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it("Should throw syntax error (spread in sequence)", function(done) {
|
||||||
|
var command = uglifyjscmd + ' test/input/invalid/sequence.js';
|
||||||
|
|
||||||
|
exec(command, function (err, stdout, stderr) {
|
||||||
|
assert.ok(err);
|
||||||
|
assert.strictEqual(stdout, "");
|
||||||
|
assert.strictEqual(stderr.split(/\n/).slice(0, 4).join("\n"), [
|
||||||
|
"Parse error at test/input/invalid/sequence.js:1,4",
|
||||||
|
"(a, ...b);",
|
||||||
|
" ^",
|
||||||
|
"ERROR: Unexpected token: expand (...)"
|
||||||
|
].join("\n"));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
it("Should handle literal string as source map input", function(done) {
|
it("Should handle literal string as source map input", function(done) {
|
||||||
var command = [
|
var command = [
|
||||||
uglifyjscmd,
|
uglifyjscmd,
|
||||||
|
|||||||
@@ -191,15 +191,51 @@ describe("Function", function() {
|
|||||||
];
|
];
|
||||||
var test = function(code) {
|
var test = function(code) {
|
||||||
return function() {
|
return function() {
|
||||||
uglify.parse(code);
|
uglify.parse(code, { ecma: 5 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var error = function(e) {
|
var error = function(e) {
|
||||||
return e instanceof uglify.JS_Parse_Error &&
|
return e instanceof uglify.JS_Parse_Error;
|
||||||
e.message === "Invalid function parameter";
|
|
||||||
}
|
}
|
||||||
for (var i = 0; i < tests.length; i++) {
|
for (var i = 0; i < tests.length; i++) {
|
||||||
assert.throws(test(tests[i]), error);
|
assert.throws(test(tests[i]), error, tests[i]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it("Should accept trailing commas only for ES8", function() {
|
||||||
|
[
|
||||||
|
"new Foo(a, );",
|
||||||
|
"async(...[1, 2], );",
|
||||||
|
"console.log(...[1, 2], );",
|
||||||
|
"!function(a, b, ){ console.log(a + b); }(3, 4, );",
|
||||||
|
].forEach(function(code) {
|
||||||
|
uglify.parse(code, { ecma: 8 });
|
||||||
|
assert.throws(function() {
|
||||||
|
uglify.parse(code, { ecma: 6 });
|
||||||
|
}, function(e) {
|
||||||
|
return e instanceof uglify.JS_Parse_Error;
|
||||||
|
}, code);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it("Should not accept invalid trailing commas", function() {
|
||||||
|
var tests = [
|
||||||
|
"f(, );",
|
||||||
|
"(, ) => {};",
|
||||||
|
"(...p, ) => {};",
|
||||||
|
"function f(, ) {}",
|
||||||
|
"function f(...p, ) {}",
|
||||||
|
"function foo(a, b, , ) {}",
|
||||||
|
'console.log("hello", , );',
|
||||||
|
];
|
||||||
|
var test = function(code) {
|
||||||
|
return function() {
|
||||||
|
uglify.parse(code, { ecma: 8 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var error = function(e) {
|
||||||
|
return e instanceof uglify.JS_Parse_Error;
|
||||||
|
}
|
||||||
|
for (var i = 0; i < tests.length; i++) {
|
||||||
|
assert.throws(test(tests[i]), error, tests[i]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
it("Should not accept an initializer when parameter is a rest parameter", function() {
|
it("Should not accept an initializer when parameter is a rest parameter", function() {
|
||||||
|
|||||||
Reference in New Issue
Block a user