Bug 1303788 - Add support for trailing comma in argument and parameter lists (ES2017). r=arai
authorAndré Bargull <andre.bargull@gmail.com>
Thu, 06 Oct 2016 23:38:16 -0700
changeset 316958 6e6438f5d89f
parent 316957 95bf06da8e8d
child 316959 b0b8eccc47b2
push id30790
push userryanvm@gmail.com
push dateSat, 08 Oct 2016 03:00:21 +0000
treeherdermozilla-central@a835589ae0c6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersarai
bugs1303788
milestone52.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1303788 - Add support for trailing comma in argument and parameter lists (ES2017). r=arai
js/src/frontend/Parser.cpp
js/src/jit-test/tests/basic/spread-call-invalid-syntax.js
js/src/jit-test/tests/debug/Debugger-isCompilableUnit.js
js/src/js.msg
js/src/tests/Makefile.in
js/src/tests/ecma_2017/Expressions/browser.js
js/src/tests/ecma_2017/Expressions/shell.js
js/src/tests/ecma_2017/Expressions/trailing_comma_arguments.js
js/src/tests/ecma_2017/Expressions/trailing_comma_arrow.js
js/src/tests/ecma_2017/Expressions/trailing_comma_getter_setter.js
js/src/tests/ecma_2017/Expressions/trailing_comma_parameters.js
js/src/tests/ecma_2017/Statements/browser.js
js/src/tests/ecma_2017/Statements/shell.js
js/src/tests/ecma_2017/Statements/trailing_comma_parameters.js
js/src/tests/ecma_2017/browser.js
js/src/tests/ecma_2017/shell.js
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -2741,16 +2741,25 @@ Parser<ParseHandler>::functionArguments(
 
             if (parenFreeArrow || IsSetterKind(kind))
                 break;
 
             if (!tokenStream.matchToken(&matched, TOK_COMMA))
                 return false;
             if (!matched)
                 break;
+
+            if (!hasRest) {
+                if (!tokenStream.peekToken(&tt, TokenStream::Operand))
+                    return null();
+                if (tt == TOK_RP) {
+                    tokenStream.addModifierException(TokenStream::NoneIsOperand);
+                    break;
+                }
+            }
         }
 
         if (!parenFreeArrow) {
             TokenKind tt;
             if (!tokenStream.getToken(&tt))
                 return false;
             if (tt != TOK_RP) {
                 if (IsSetterKind(kind)) {
@@ -6928,16 +6937,42 @@ Parser<ParseHandler>::expr(InHandling in
         return null();
     if (!matched)
         return pn;
 
     Node seq = handler.newCommaExpressionList(pn);
     if (!seq)
         return null();
     while (true) {
+        // Trailing comma before the closing parenthesis is valid in an arrow
+        // function parameters list: `(a, b, ) => body`. Check if we are
+        // directly under CoverParenthesizedExpressionAndArrowParameterList,
+        // and the next two tokens are closing parenthesis and arrow. If all
+        // are present allow the trailing comma.
+        if (tripledotHandling == TripledotAllowed) {
+            TokenKind tt;
+            if (!tokenStream.peekToken(&tt, TokenStream::Operand))
+                return null();
+
+            if (tt == TOK_RP) {
+                tokenStream.consumeKnownToken(TOK_RP, TokenStream::Operand);
+
+                if (!tokenStream.peekToken(&tt))
+                    return null();
+                if (tt != TOK_ARROW) {
+                    report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
+                        "expression", TokenKindToDesc(TOK_RP));
+                    return null();
+                }
+
+                tokenStream.ungetToken();  // put back right paren
+                tokenStream.addModifierException(TokenStream::NoneIsOperand);
+                break;
+            }
+        }
 
         // Additional calls to assignExpr should not reuse the possibleError
         // which had been passed into the function. Otherwise we would lose
         // information needed to determine whether or not we're dealing with
         // a non-recoverable situation.
         PossibleError possibleErrorInner(*this);
         pn = assignExpr(inHandling, yieldHandling, tripledotHandling,
                         &possibleErrorInner);
@@ -7319,20 +7354,23 @@ Parser<ParseHandler>::assignExpr(InHandl
       case TOK_DIVASSIGN:    kind = PNK_DIVASSIGN;    op = JSOP_DIV;    break;
       case TOK_MODASSIGN:    kind = PNK_MODASSIGN;    op = JSOP_MOD;    break;
       case TOK_POWASSIGN:    kind = PNK_POWASSIGN;    op = JSOP_POW;    break;
 
       case TOK_ARROW: {
 
         // A line terminator between ArrowParameters and the => should trigger a SyntaxError.
         tokenStream.ungetToken();
-        TokenKind next = TOK_EOF;
-        if (!tokenStream.peekTokenSameLine(&next) || next != TOK_ARROW) {
-            report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
-                   "expression", TokenKindToDesc(TOK_ARROW));
+        TokenKind next;
+        if (!tokenStream.peekTokenSameLine(&next))
+            return null();
+        MOZ_ASSERT(next == TOK_ARROW || next == TOK_EOL);
+
+        if (next != TOK_ARROW) {
+            report(ParseError, false, null(), JSMSG_LINE_BREAK_BEFORE_ARROW);
             return null();
         }
         tokenStream.consumeKnownToken(TOK_ARROW);
 
         bool isBlock = false;
         if (!tokenStream.peekToken(&next, TokenStream::Operand))
             return null();
         if (next == TOK_LC)
@@ -8004,16 +8042,24 @@ Parser<ParseHandler>::argumentList(Yield
 
         handler.addList(listNode, argNode);
 
         bool matched;
         if (!tokenStream.matchToken(&matched, TOK_COMMA))
             return false;
         if (!matched)
             break;
+
+        TokenKind tt;
+        if (!tokenStream.peekToken(&tt, TokenStream::Operand))
+            return null();
+        if (tt == TOK_RP) {
+            tokenStream.addModifierException(TokenStream::NoneIsOperand);
+            break;
+        }
     }
 
     TokenKind tt;
     if (!tokenStream.getToken(&tt))
         return false;
     if (tt != TOK_RP) {
         report(ParseError, false, null(), JSMSG_PAREN_AFTER_ARGS);
         return false;
@@ -9067,19 +9113,21 @@ Parser<ParseHandler>::primaryExpr(YieldH
         if (!tokenStream.getToken(&next))
             return null();
         if (next != TOK_RP) {
             report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
                    "closing parenthesis", TokenKindToDesc(next));
             return null();
         }
 
-        if (!tokenStream.peekTokenSameLine(&next))
+        if (!tokenStream.peekToken(&next))
             return null();
         if (next != TOK_ARROW) {
+            // Advance the scanner for proper error location reporting.
+            tokenStream.consumeKnownToken(next);
             report(ParseError, false, null(), JSMSG_UNEXPECTED_TOKEN,
                    "'=>' after argument list", TokenKindToDesc(next));
             return null();
         }
 
         tokenStream.ungetToken();  // put back right paren
 
         // Return an arbitrary expression node. See case TOK_RP above.
--- a/js/src/jit-test/tests/basic/spread-call-invalid-syntax.js
+++ b/js/src/jit-test/tests/basic/spread-call-invalid-syntax.js
@@ -1,16 +1,15 @@
 load(libdir + "asserts.js");
 
 var offenders = [
     "f(1 ... n)",
     "f(...x for (x in y))",
     "f(...)",
     "f(...,)",
     "f(... ...[])",
-    "f(...x,)",
     "f(x, ...)",
     "f(...x, x for (x in y))",
     "f(x for (x in y), ...x)"
 ];
 for (var sample of offenders) {
     assertThrowsInstanceOf(function() { eval(sample); }, SyntaxError);
 }
--- a/js/src/jit-test/tests/debug/Debugger-isCompilableUnit.js
+++ b/js/src/jit-test/tests/debug/Debugger-isCompilableUnit.js
@@ -15,17 +15,17 @@ for (var badType of bad_types) {
     }, TypeError);
 }
 
 const compilable_units = [
    "wubba-lubba-dub-dub",
    "'Get Schwifty!'",
    "1 + 2",
    "function f(x) {}",
-   "function x(f,) {", // statements with bad syntax are always compilable
+   "function x(...f,) {", // statements with bad syntax are always compilable
    "let x = 100",
    ";;;;;;;;",
    "",
    " ",
    "\n",
    "let x",
 ]
 
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -262,16 +262,17 @@ MSG_DEF(JSMSG_LABEL_NOT_FOUND,         0
 MSG_DEF(JSMSG_LET_CLASS_BINDING,       0, JSEXN_SYNTAXERR, "'let' is not a valid name for a class")
 MSG_DEF(JSMSG_LET_COMP_BINDING,        0, JSEXN_SYNTAXERR, "'let' is not a valid name for a comprehension variable")
 MSG_DEF(JSMSG_LEXICAL_DECL_NOT_IN_BLOCK,   1, JSEXN_SYNTAXERR, "{0} declaration not directly within block")
 MSG_DEF(JSMSG_LEXICAL_DECL_LABEL,      1, JSEXN_SYNTAXERR, "{0} declarations cannot be labelled")
 MSG_DEF(JSMSG_GENERATOR_LABEL,         0, JSEXN_SYNTAXERR, "generator functions cannot be labelled")
 MSG_DEF(JSMSG_FUNCTION_LABEL,          0, JSEXN_SYNTAXERR, "functions cannot be labelled")
 MSG_DEF(JSMSG_SLOPPY_FUNCTION_LABEL,   0, JSEXN_SYNTAXERR, "functions can only be labelled inside blocks")
 MSG_DEF(JSMSG_LINE_BREAK_AFTER_THROW,  0, JSEXN_SYNTAXERR, "no line break is allowed between 'throw' and its expression")
+MSG_DEF(JSMSG_LINE_BREAK_BEFORE_ARROW, 0, JSEXN_SYNTAXERR, "no line break is allowed before '=>'")
 MSG_DEF(JSMSG_MALFORMED_ESCAPE,        1, JSEXN_SYNTAXERR, "malformed {0} character escape sequence")
 MSG_DEF(JSMSG_MISSING_BINARY_DIGITS,   0, JSEXN_SYNTAXERR, "missing binary digits after '0b'")
 MSG_DEF(JSMSG_MISSING_EXPONENT,        0, JSEXN_SYNTAXERR, "missing exponent")
 MSG_DEF(JSMSG_MISSING_EXPR_AFTER_THROW,0, JSEXN_SYNTAXERR, "throw statement is missing an expression")
 MSG_DEF(JSMSG_MISSING_FORMAL,          0, JSEXN_SYNTAXERR, "missing formal parameter")
 MSG_DEF(JSMSG_MISSING_HEXDIGITS,       0, JSEXN_SYNTAXERR, "missing hexadecimal digits after '0x'")
 MSG_DEF(JSMSG_MISSING_OCTAL_DIGITS,    0, JSEXN_SYNTAXERR, "missing octal digits after '0o'")
 MSG_DEF(JSMSG_MODULE_SPEC_AFTER_FROM,  0, JSEXN_SYNTAXERR, "missing module specifier after 'from' keyword")
--- a/js/src/tests/Makefile.in
+++ b/js/src/tests/Makefile.in
@@ -14,16 +14,17 @@ TEST_FILES = \
   user.js \
   ecma/ \
   ecma_2/ \
   ecma_3/ \
   ecma_3_1/ \
   ecma_5/ \
   ecma_6/ \
   ecma_7/ \
+  ecma_2017/ \
   Intl/ \
   js1_1/ \
   js1_2/ \
   js1_3/ \
   js1_4/ \
   js1_5/ \
   js1_6/ \
   js1_7/ \
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_2017/Expressions/shell.js
@@ -0,0 +1,1 @@
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_2017/Expressions/trailing_comma_arguments.js
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Trailing comma in Arguments production.
+
+// 12.3 Left-Hand-Side Expressions
+// Arguments[Yield]:
+//   ()
+//   ( ArgumentList[?Yield] )
+//   ( ArgumentList[?Yield] , )
+
+
+function argsLength() {
+    return {value: arguments.length};
+}
+function sum(...rest) {
+    return {value: rest.reduce((a, c) => a + c, 0)};
+}
+
+function call(f, argList) {
+    return eval(`(${f}(${argList})).value`);
+}
+
+function newCall(F, argList) {
+    return eval(`(new ${F}(${argList})).value`);
+}
+
+function superCall(superClass, argList) {
+    return eval(`(new class extends ${superClass} {
+        constructor() {
+            super(${argList});
+        }
+    }).value`);
+}
+
+// Ensure the correct number of arguments is passed.
+for (let type of [call, newCall, superCall]) {
+    let test = type.bind(null, "argsLength");
+
+    assertEq(test("10, "), 1);
+    assertEq(test("10, 20, "), 2);
+    assertEq(test("10, 20, 30, "), 3);
+    assertEq(test("10, 20, 30, 40, "), 4);
+
+    assertEq(test("...[10, 20], "), 2);
+    assertEq(test("...[10, 20], 30, "), 3);
+    assertEq(test("...[10, 20], ...[30], "), 3);
+}
+
+// Ensure the arguments themselves are passed correctly.
+for (let type of [call, newCall, superCall]) {
+    let test = type.bind(null, "sum");
+
+    assertEq(test("10, "), 10);
+    assertEq(test("10, 20, "), 30);
+    assertEq(test("10, 20, 30, "), 60);
+    assertEq(test("10, 20, 30, 40, "), 100);
+
+    assertEq(test("...[10, 20], "), 30);
+    assertEq(test("...[10, 20], 30, "), 60);
+    assertEq(test("...[10, 20], ...[30], "), 60);
+}
+
+// Error cases.
+for (let type of [call, newCall, superCall]) {
+    let test = type.bind(null, "f");
+
+    // Trailing comma in empty arguments list.
+    assertThrowsInstanceOf(() => test(","), SyntaxError);
+
+    // Leading comma.
+    assertThrowsInstanceOf(() => test(", a"), SyntaxError);
+    assertThrowsInstanceOf(() => test(", ...a"), SyntaxError);
+
+    // Multiple trailing comma.
+    assertThrowsInstanceOf(() => test("a, , "), SyntaxError);
+    assertThrowsInstanceOf(() => test("...a, , "), SyntaxError);
+
+    // Elision.
+    assertThrowsInstanceOf(() => test("a, , b"), SyntaxError);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_2017/Expressions/trailing_comma_arrow.js
@@ -0,0 +1,108 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Trailing comma in CoverParenthesizedExpressionAndArrowParameterList production.
+
+// 12.2 Primary Expression
+// CoverParenthesizedExpressionAndArrowParameterList[Yield]:
+//   ( Expression[In, ?Yield] )
+//   ( Expression[In, ?Yield] , )
+//   ()
+//   ( ...BindingIdentifier[?Yield] )
+//   ( Expression[In, ?Yield] , ...BindingIdentifier[?Yield] )
+
+
+function arrow(argList, parameters = "", returnExpr = "") {
+    return eval(`
+        let fun = (${argList}) => {
+            return ${returnExpr};
+        }
+        fun(${parameters});
+    `);
+}
+
+function arrowConcise(argList, parameters = "", returnExpr = "null") {
+    return eval(`
+        let fun = (${argList}) => ${returnExpr};
+        fun(${parameters});
+    `);
+}
+
+const tests = [
+    arrow,
+    arrowConcise,
+];
+
+// Ensure parameters are passed correctly.
+for (let test of tests) {
+    assertEq(test("a, ", "10", "a"), 10);
+    assertEq(test("a, b, ", "10, 20", "a + b"), 30);
+    assertEq(test("a = 30, ", "", "a"), 30);
+    assertEq(test("a = 30, b = 40, ", "", "a + b"), 70);
+
+    assertEq(test("[a], ", "[10]", "a"), 10);
+    assertEq(test("[a], [b], ", "[10], [20]", "a + b"), 30);
+    assertEq(test("[a] = [30], ", "", "a"), 30);
+    assertEq(test("[a] = [30], [b] = [40], ", "", "a + b"), 70);
+
+    assertEq(test("{a}, ", "{a: 10}", "a"), 10);
+    assertEq(test("{a}, {b}, ", "{a: 10}, {b: 20}", "a + b"), 30);
+    assertEq(test("{a} = {a: 30}, ", "", "a"), 30);
+    assertEq(test("{a} = {a: 30}, {b} = {b: 40}, ", "", "a + b"), 70);
+}
+
+// Ensure function length doesn't change.
+for (let test of tests) {
+    assertEq(test("a, ", "", "fun.length"), 1);
+    assertEq(test("a, b, ", "", "fun.length"), 2);
+
+    assertEq(test("[a], ", "[]", "fun.length"), 1);
+    assertEq(test("[a], [b], ", "[], []", "fun.length"), 2);
+
+    assertEq(test("{a}, ", "{}", "fun.length"), 1);
+    assertEq(test("{a}, {b}, ", "{}, {}", "fun.length"), 2);
+}
+
+for (let test of tests) {
+    // Trailing comma in empty parameters list.
+    assertThrowsInstanceOf(() => test(","), SyntaxError);
+
+    // Leading comma.
+    assertThrowsInstanceOf(() => test(", a"), SyntaxError);
+    assertThrowsInstanceOf(() => test(", ...a"), SyntaxError);
+
+    // Multiple trailing comma.
+    assertThrowsInstanceOf(() => test("a, , "), SyntaxError);
+    assertThrowsInstanceOf(() => test("a..., , "), SyntaxError);
+
+    // Trailing comma after rest parameter.
+    assertThrowsInstanceOf(() => test("...a, "), SyntaxError);
+    assertThrowsInstanceOf(() => test("a, ...b, "), SyntaxError);
+
+    // Elision.
+    assertThrowsInstanceOf(() => test("a, , b"), SyntaxError);
+}
+
+// Trailing comma in non-parenthesized arrow head.
+assertThrowsInstanceOf(() => eval("a, => {}"), SyntaxError);
+assertThrowsInstanceOf(() => eval("a, => null"), SyntaxError);
+
+// Parenthesized expression is not an arrow function expression.
+for (let trail of ["", ";", "\n => {}"]) {
+    assertThrowsInstanceOf(() => eval("(a,)" + trail), SyntaxError);
+    assertThrowsInstanceOf(() => eval("(a, b,)" + trail), SyntaxError);
+    assertThrowsInstanceOf(() => eval("(...a, )" + trail), SyntaxError);
+    assertThrowsInstanceOf(() => eval("(a, ...b, )" + trail), SyntaxError);
+    assertThrowsInstanceOf(() => eval("(a, , b)" + trail), SyntaxError);
+
+    assertThrowsInstanceOf(() => eval("(,)" + trail), SyntaxError);
+    assertThrowsInstanceOf(() => eval("(, a)" + trail), SyntaxError);
+    assertThrowsInstanceOf(() => eval("(, ...a)" + trail), SyntaxError);
+    assertThrowsInstanceOf(() => eval("(a, , )" + trail), SyntaxError);
+    assertThrowsInstanceOf(() => eval("(...a, , )" + trail), SyntaxError);
+}
+
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_2017/Expressions/trailing_comma_getter_setter.js
@@ -0,0 +1,88 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Trailing comma is not allowed in getter and setter methods
+
+// 14.3 Method Definitions
+// MethodDefinition[Yield]:
+//   get PropertyName[?Yield] () { FunctionBody[~Yield] }
+//   set PropertyName[?Yield] ( PropertySetParameterList ) { FunctionBody[~Yield] }
+// PropertySetParameterList:
+//   FormalParameter[~Yield]
+
+function objectGetter(argList) {
+    return eval(`({
+        get m(${argList}) {}
+    })`);
+}
+
+function objectSetter(argList) {
+    return eval(`({
+        set m(${argList}) {}
+    })`);
+}
+
+function classGetter(argList) {
+    return eval(`(class {
+        get m(${argList}) {}
+    })`);
+}
+
+function classStaticGetter(argList) {
+    return eval(`(class {
+        static get m(${argList}) {}
+    })`);
+}
+
+function classSetter(argList) {
+    return eval(`(class {
+        set m(${argList}) {}
+    })`);
+}
+
+function classStaticSetter(argList) {
+    return eval(`(class {
+        static set m(${argList}) {}
+    })`);
+}
+
+const tests = [
+    objectGetter,
+    objectSetter,
+    classGetter,
+    classStaticGetter,
+    classSetter,
+    classStaticSetter,
+];
+
+for (let test of tests) {
+    // Trailing comma.
+    assertThrowsInstanceOf(() => test("a, "), SyntaxError);
+    assertThrowsInstanceOf(() => test("[], "), SyntaxError);
+    assertThrowsInstanceOf(() => test("{}, "), SyntaxError);
+    assertThrowsInstanceOf(() => test("a = 0, "), SyntaxError);
+    assertThrowsInstanceOf(() => test("[] = [], "), SyntaxError);
+    assertThrowsInstanceOf(() => test("{} = {}, "), SyntaxError);
+
+    // Trailing comma in empty parameters list.
+    assertThrowsInstanceOf(() => test(","), SyntaxError);
+
+    // Leading comma.
+    assertThrowsInstanceOf(() => test(", a"), SyntaxError);
+    assertThrowsInstanceOf(() => test(", ...a"), SyntaxError);
+
+    // Multiple trailing comma.
+    assertThrowsInstanceOf(() => test("a, ,"), SyntaxError);
+    assertThrowsInstanceOf(() => test("a..., ,"), SyntaxError);
+
+    // Trailing comma after rest parameter.
+    assertThrowsInstanceOf(() => test("...a ,"), SyntaxError);
+    assertThrowsInstanceOf(() => test("a, ...b, "), SyntaxError);
+
+    // Elision.
+    assertThrowsInstanceOf(() => test("a, , b"), SyntaxError);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_2017/Expressions/trailing_comma_parameters.js
@@ -0,0 +1,165 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Trailing comma in functions and methods.
+
+// 14.1 Function Definitions
+// FunctionExpression:
+//   function BindingIdentifier[~Yield]opt ( FormalParameters[~Yield] ) { FunctionBody[~Yield] }
+
+// 14.3 Method Definitions
+// MethodDefinition[Yield]:
+//   PropertyName[?Yield] ( UniqueFormalParameters[~Yield] ) { FunctionBody[~Yield] }
+//   GeneratorMethod[?Yield]
+// PropertySetParameterList:
+//   FormalParameter[~Yield]
+
+// 14.4 Generator Function Definitions
+// GeneratorExpression:
+//   function * BindingIdentifier[+Yield]opt ( FormalParameters[+Yield] ) { GeneratorBody }
+// GeneratorMethod[Yield]:
+//   * PropertyName[?Yield] ( UniqueFormalParameters[+Yield] ) { GeneratorBody }
+
+
+function functionExpression(argList, parameters = "", returnExpr = "") {
+    return eval(`(function f(${argList}) {
+        var fun = f;
+        return ${returnExpr};
+    })(${parameters})`);
+}
+
+function generatorExpression(argList, parameters = "", returnExpr = "") {
+    return eval(`(function* f(${argList}) {
+        var fun = f;
+        return ${returnExpr};
+    })(${parameters}).next().value`);
+}
+
+function objectMethod(argList, parameters = "", returnExpr = "") {
+    return eval(`({
+        m(${argList}) {
+            var fun = this.m;
+            return ${returnExpr};
+        }
+    }).m(${parameters})`);
+}
+
+function objectGeneratorMethod(argList, parameters = "", returnExpr = "") {
+    return eval(`({
+        * m(${argList}) {
+            var fun = this.m;
+            return ${returnExpr};
+        }
+    }).m(${parameters}).next().value`);
+}
+
+function classMethod(argList, parameters = "", returnExpr = "") {
+    return eval(`(new class {
+        m(${argList}) {
+            var fun = this.m;
+            return ${returnExpr};
+        }
+    }).m(${parameters})`);
+}
+
+function classStaticMethod(argList, parameters = "", returnExpr = "") {
+    return eval(`(class {
+        static m(${argList}) {
+            var fun = this.m;
+            return ${returnExpr};
+        }
+    }).m(${parameters})`);
+}
+
+function classGeneratorMethod(argList, parameters = "", returnExpr = "") {
+    return eval(`(new class {
+        * m(${argList}) {
+            var fun = this.m;
+            return ${returnExpr};
+        }
+    }).m(${parameters}).next().value`);
+}
+
+function classStaticGeneratorMethod(argList, parameters = "", returnExpr = "") {
+    return eval(`(class {
+        static * m(${argList}) {
+            var fun = this.m;
+            return ${returnExpr};
+        }
+    }).m(${parameters}).next().value`);
+}
+
+function classConstructorMethod(argList, parameters = "", returnExpr = "null") {
+    return eval(`new (class {
+        constructor(${argList}) {
+            var fun = this.constructor;
+            return { value: ${returnExpr} };
+        }
+    })(${parameters}).value`);
+}
+
+const tests = [
+    functionExpression,
+    generatorExpression,
+    objectMethod,
+    objectGeneratorMethod,
+    classMethod,
+    classStaticMethod,
+    classGeneratorMethod,
+    classStaticGeneratorMethod,
+    classConstructorMethod,
+];
+
+// Ensure parameters are passed correctly.
+for (let test of tests) {
+    assertEq(test("a, ", "10", "a"), 10);
+    assertEq(test("a, b, ", "10, 20", "a + b"), 30);
+    assertEq(test("a = 30, ", "", "a"), 30);
+    assertEq(test("a = 30, b = 40, ", "", "a + b"), 70);
+
+    assertEq(test("[a], ", "[10]", "a"), 10);
+    assertEq(test("[a], [b], ", "[10], [20]", "a + b"), 30);
+    assertEq(test("[a] = [30], ", "", "a"), 30);
+    assertEq(test("[a] = [30], [b] = [40], ", "", "a + b"), 70);
+
+    assertEq(test("{a}, ", "{a: 10}", "a"), 10);
+    assertEq(test("{a}, {b}, ", "{a: 10}, {b: 20}", "a + b"), 30);
+    assertEq(test("{a} = {a: 30}, ", "", "a"), 30);
+    assertEq(test("{a} = {a: 30}, {b} = {b: 40}, ", "", "a + b"), 70);
+}
+
+// Ensure function length doesn't change.
+for (let test of tests) {
+    assertEq(test("a, ", "", "fun.length"), 1);
+    assertEq(test("a, b, ", "", "fun.length"), 2);
+
+    assertEq(test("[a], ", "[]", "fun.length"), 1);
+    assertEq(test("[a], [b], ", "[], []", "fun.length"), 2);
+
+    assertEq(test("{a}, ", "{}", "fun.length"), 1);
+    assertEq(test("{a}, {b}, ", "{}, {}", "fun.length"), 2);
+}
+
+for (let test of tests) {
+    // Trailing comma in empty parameters list.
+    assertThrowsInstanceOf(() => test(","), SyntaxError);
+
+    // Leading comma.
+    assertThrowsInstanceOf(() => test(", a"), SyntaxError);
+    assertThrowsInstanceOf(() => test(", ...a"), SyntaxError);
+
+    // Multiple trailing comma.
+    assertThrowsInstanceOf(() => test("a, , "), SyntaxError);
+    assertThrowsInstanceOf(() => test("a..., , "), SyntaxError);
+
+    // Trailing comma after rest parameter.
+    assertThrowsInstanceOf(() => test("...a, "), SyntaxError);
+    assertThrowsInstanceOf(() => test("a, ...b, "), SyntaxError);
+
+    // Elision.
+    assertThrowsInstanceOf(() => test("a, , b"), SyntaxError);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_2017/Statements/shell.js
@@ -0,0 +1,1 @@
+
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_2017/Statements/trailing_comma_parameters.js
@@ -0,0 +1,92 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Trailing comma in functions and generators.
+
+// 14.1 Function Definitions
+// FunctionDeclaration[Yield, Default]:
+//   function BindingIdentifier[?Yield] ( FormalParameters[~Yield] ) { FunctionBody[~Yield] }
+
+// 14.4 Generator Function Definitions
+// GeneratorDeclaration[Yield, Default]:
+//   function * BindingIdentifier[?Yield] ( FormalParameters[+Yield] ) { GeneratorBody }
+
+
+function functionDeclaration(argList, parameters = "", returnExpr = "") {
+    return eval(`
+        function f(${argList}) {
+            var fun = f;
+            return ${returnExpr};
+        }
+        f(${parameters});
+    `);
+}
+
+function generatorDeclaration(argList, parameters = "", returnExpr = "") {
+    return eval(`
+        function* f(${argList}) {
+            var fun = f;
+            return ${returnExpr};
+        }
+        f(${parameters}).next().value;
+    `);
+}
+
+const tests = [
+    functionDeclaration,
+    generatorDeclaration,
+];
+
+// Ensure parameters are passed correctly.
+for (let test of tests) {
+    assertEq(test("a, ", "10", "a"), 10);
+    assertEq(test("a, b, ", "10, 20", "a + b"), 30);
+    assertEq(test("a = 30, ", "", "a"), 30);
+    assertEq(test("a = 30, b = 40, ", "", "a + b"), 70);
+
+    assertEq(test("[a], ", "[10]", "a"), 10);
+    assertEq(test("[a], [b], ", "[10], [20]", "a + b"), 30);
+    assertEq(test("[a] = [30], ", "", "a"), 30);
+    assertEq(test("[a] = [30], [b] = [40], ", "", "a + b"), 70);
+
+    assertEq(test("{a}, ", "{a: 10}", "a"), 10);
+    assertEq(test("{a}, {b}, ", "{a: 10}, {b: 20}", "a + b"), 30);
+    assertEq(test("{a} = {a: 30}, ", "", "a"), 30);
+    assertEq(test("{a} = {a: 30}, {b} = {b: 40}, ", "", "a + b"), 70);
+}
+
+// Ensure function length doesn't change.
+for (let test of tests) {
+    assertEq(test("a, ", "", "fun.length"), 1);
+    assertEq(test("a, b, ", "", "fun.length"), 2);
+
+    assertEq(test("[a], ", "[]", "fun.length"), 1);
+    assertEq(test("[a], [b], ", "[], []", "fun.length"), 2);
+
+    assertEq(test("{a}, ", "{}", "fun.length"), 1);
+    assertEq(test("{a}, {b}, ", "{}, {}", "fun.length"), 2);
+}
+
+for (let test of tests) {
+    // Trailing comma in empty parameters list.
+    assertThrowsInstanceOf(() => test(","), SyntaxError);
+
+    // Leading comma.
+    assertThrowsInstanceOf(() => test(", a"), SyntaxError);
+    assertThrowsInstanceOf(() => test(", ...a"), SyntaxError);
+
+    // Multiple trailing comma.
+    assertThrowsInstanceOf(() => test("a, , "), SyntaxError);
+    assertThrowsInstanceOf(() => test("a..., , "), SyntaxError);
+
+    // Trailing comma after rest parameter.
+    assertThrowsInstanceOf(() => test("...a, "), SyntaxError);
+    assertThrowsInstanceOf(() => test("a, ...b, "), SyntaxError);
+
+    // Elision.
+    assertThrowsInstanceOf(() => test("a, , b"), SyntaxError);
+}
+
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_2017/shell.js
@@ -0,0 +1,1 @@
+