diff --git a/src/org/mozilla/javascript/Node.java b/src/org/mozilla/javascript/Node.java index 359d60947a..db26a92c70 100644 --- a/src/org/mozilla/javascript/Node.java +++ b/src/org/mozilla/javascript/Node.java @@ -63,7 +63,8 @@ public class Node implements Iterable { SHORTHAND_PROPERTY_NAME = 26, ARROW_FUNCTION_PROP = 27, TEMPLATE_LITERAL_PROP = 28, - LAST_PROP = 28; + TRAILING_COMMA = 29, + LAST_PROP = 29; // values of ISNUMBER_PROP to specify // which of the children are Number types @@ -429,6 +430,8 @@ private static final String propToString(int propType) { return "expression_closure_prop"; case TEMPLATE_LITERAL_PROP: return "template_literal"; + case TRAILING_COMMA: + return "trailing comma"; default: Kit.codeBug(); diff --git a/src/org/mozilla/javascript/Parser.java b/src/org/mozilla/javascript/Parser.java index 8c01726595..9cc30fe574 100644 --- a/src/org/mozilla/javascript/Parser.java +++ b/src/org/mozilla/javascript/Parser.java @@ -786,6 +786,10 @@ private void parseFunctionParams(FunctionNode fnNode) throws IOException { Set paramNames = new HashSet<>(); do { int tt = peekToken(); + if (tt == Token.RP) { + fnNode.putIntProp(Node.TRAILING_COMMA, 1); + break; + } if (tt == Token.LB || tt == Token.LC) { AstNode expr = destructuringPrimaryExpr(); markDestructuring(expr); @@ -969,6 +973,9 @@ private AstNode arrowFunction(AstNode params) throws IOException { try { if (params instanceof ParenthesizedExpression) { fnNode.setParens(0, params.getLength()); + if (params.getIntProp(Node.TRAILING_COMMA, 0) == 1) { + fnNode.putIntProp(Node.TRAILING_COMMA, 1); + } AstNode p = ((ParenthesizedExpression) params).getExpression(); if (!(p instanceof EmptyExpression)) { arrowFunctionParams(fnNode, p, destructuring, paramNames); @@ -1081,7 +1088,7 @@ private ConditionData condition() throws IOException { if (mustMatchToken(Token.LP, "msg.no.paren.cond", true)) data.lp = ts.tokenBeg; - data.condition = expr(); + data.condition = expr(false); if (mustMatchToken(Token.RP, "msg.no.paren.after.cond", true)) data.rp = ts.tokenBeg; @@ -1245,7 +1252,7 @@ private AstNode statementHelper() throws IOException { return pn; default: lineno = ts.lineno; - pn = new ExpressionStatement(expr(), !insideFunction()); + pn = new ExpressionStatement(expr(false), !insideFunction()); pn.setLineno(lineno); break; } @@ -1319,7 +1326,7 @@ private SwitchStatement switchStatement() throws IOException { if (mustMatchToken(Token.LP, "msg.no.paren.switch", true)) pn.setLp(ts.tokenBeg - pos); pn.setLineno(ts.lineno); - AstNode discriminant = expr(); + AstNode discriminant = expr(false); pn.setExpression(discriminant); enterSwitch(pn); @@ -1343,7 +1350,7 @@ private SwitchStatement switchStatement() throws IOException { break switchLoop; case Token.CASE: - caseExpression = expr(); + caseExpression = expr(false); mustMatchToken(Token.COLON, "msg.no.colon.case", true); break; @@ -1499,14 +1506,14 @@ private Loop forLoop() throws IOException { isForIn = true; inPos = ts.tokenBeg - forPos; markDestructuring(init); - cond = expr(); // object over which we're iterating + cond = expr(false); // object over which we're iterating } else if (compilerEnv.getLanguageVersion() >= Context.VERSION_ES6 && matchToken(Token.NAME, true) && "of".equals(ts.getString())) { isForOf = true; inPos = ts.tokenBeg - forPos; markDestructuring(init); - cond = expr(); // object over which we're iterating + cond = expr(false); // object over which we're iterating } else { // ordinary for-loop mustMatchToken(Token.SEMI, "msg.no.semi.for", true); if (peekToken() == Token.SEMI) { @@ -1514,7 +1521,7 @@ && matchToken(Token.NAME, true) cond = new EmptyExpression(ts.tokenBeg, 1); cond.setLineno(ts.lineno); } else { - cond = expr(); + cond = expr(false); } mustMatchToken(Token.SEMI, "msg.no.semi.for.cond", true); @@ -1523,7 +1530,7 @@ && matchToken(Token.NAME, true) incr = new EmptyExpression(tmpPos, 1); incr.setLineno(ts.lineno); } else { - incr = expr(); + incr = expr(false); } } @@ -1593,7 +1600,7 @@ private AstNode forLoopInit(int tt) throws IOException { consumeToken(); init = variables(tt, ts.tokenBeg, false); } else { - init = expr(); + init = expr(false); } return init; } finally { @@ -1667,7 +1674,7 @@ private TryStatement tryStatement() throws IOException { if (matchToken(Token.IF, true)) { guardPos = ts.tokenBeg; - catchCond = expr(); + catchCond = expr(false); } else { sawDefaultCatch = true; } @@ -1751,7 +1758,7 @@ private ThrowStatement throwStatement() throws IOException { // see bug 256617 reportError("msg.bad.throw.eol"); } - AstNode expr = expr(); + AstNode expr = expr(false); ThrowStatement pn = new ThrowStatement(pos, expr); pn.setLineno(lineno); return pn; @@ -1853,7 +1860,7 @@ private WithStatement withStatement() throws IOException { int lineno = ts.lineno, pos = ts.tokenBeg, lp = -1, rp = -1; if (mustMatchToken(Token.LP, "msg.no.paren.with", true)) lp = ts.tokenBeg; - AstNode obj = expr(); + AstNode obj = expr(false); if (mustMatchToken(Token.RP, "msg.no.paren.after.with", true)) rp = ts.tokenBeg; @@ -1927,7 +1934,7 @@ private AstNode returnOrYield(int tt, boolean exprContext) throws IOException { } // fallthrough default: - e = expr(); + e = expr(false); end = getNodeEnd(e); } @@ -2004,7 +2011,7 @@ private AstNode defaultXmlNamespace() throws IOException { reportError("msg.bad.namespace"); } - AstNode e = expr(); + AstNode e = expr(false); UnaryExpression dxmln = new UnaryExpression(pos, getNodeEnd(e) - pos); dxmln.setOperator(Token.DEFAULTNAMESPACE); dxmln.setOperand(e); @@ -2046,7 +2053,7 @@ private AstNode nameOrLabel() throws IOException { // set check for label and call down to primaryExpr currentFlaggedToken |= TI_CHECK_LABEL; - AstNode expr = expr(); + AstNode expr = expr(false); if (expr.getType() != Token.LABEL) { AstNode n = new ExpressionStatement(expr, !insideFunction()); @@ -2061,7 +2068,7 @@ private AstNode nameOrLabel() throws IOException { AstNode stmt = null; while (peekToken() == Token.NAME) { currentFlaggedToken |= TI_CHECK_LABEL; - expr = expr(); + expr = expr(false); if (expr.getType() != Token.LABEL) { stmt = new ExpressionStatement(expr, !insideFunction()); autoInsertSemicolon(stmt); @@ -2203,7 +2210,7 @@ private AstNode let(boolean isStatement, int pos) throws IOException { pn.setType(Token.LET); } else { // let expression - AstNode expr = expr(); + AstNode expr = expr(false); pn.setLength(getNodeEnd(expr) - pos); pn.setBody(expr); if (isStatement) { @@ -2287,7 +2294,7 @@ else if (symDeclType == Token.LP) { } } - private AstNode expr() throws IOException { + private AstNode expr(boolean allowTrailingComma) throws IOException { AstNode pn = assignExpr(); int pos = pn.getPosition(); while (matchToken(Token.COMMA, true)) { @@ -2295,6 +2302,10 @@ private AstNode expr() throws IOException { if (compilerEnv.isStrictMode() && !pn.hasSideEffects()) addStrictWarning("msg.no.side.effects", "", pos, nodeEnd(pn) - pos); if (peekToken() == Token.YIELD) reportError("msg.yield.parenthesized"); + if (allowTrailingComma && peekToken() == Token.RP) { + pn.putIntProp(Node.TRAILING_COMMA, 1); + return pn; + } pn = new InfixExpression(Token.COMMA, pn, assignExpr(), opPos); } return pn; @@ -2633,7 +2644,7 @@ private AstNode xmlInitializer() throws IOException { AstNode expr = (peekToken() == Token.RC) ? new EmptyExpression(beg, ts.tokenEnd - beg) - : expr(); + : expr(false); mustMatchToken(Token.RC, "msg.syntax", true); XmlExpression xexpr = new XmlExpression(beg, expr); xexpr.setIsXmlAttribute(ts.isXMLAttribute()); @@ -2765,7 +2776,7 @@ private AstNode memberExprTail(boolean allowCallSyntax, AstNode pn) throws IOExc lineno = ts.lineno; mustHaveXML(); setRequiresActivation(); - AstNode filter = expr(); + AstNode filter = expr(false); int end = getNodeEnd(filter); if (mustMatchToken(Token.RP, "msg.no.paren", true)) { rp = ts.tokenBeg; @@ -2784,7 +2795,7 @@ private AstNode memberExprTail(boolean allowCallSyntax, AstNode pn) throws IOExc consumeToken(); int lb = ts.tokenBeg, rb = -1; lineno = ts.lineno; - AstNode expr = expr(); + AstNode expr = expr(false); end = getNodeEnd(expr); if (mustMatchToken(Token.RB, "msg.no.bracket.index", true)) { rb = ts.tokenBeg; @@ -3035,7 +3046,7 @@ private AstNode propertyName(int atPos, int memberTypeFlags) throws IOException */ private XmlElemRef xmlElemRef(int atPos, Name namespace, int colonPos) throws IOException { int lb = ts.tokenBeg, rb = -1, pos = atPos != -1 ? atPos : lb; - AstNode expr = expr(); + AstNode expr = expr(false); int end = getNodeEnd(expr); if (mustMatchToken(Token.RB, "msg.no.bracket.index", true)) { rb = ts.tokenBeg; @@ -3160,16 +3171,20 @@ private AstNode parenExpr() throws IOException { Comment jsdocNode = getAndResetJsDoc(); int lineno = ts.lineno; int begin = ts.tokenBeg; - AstNode e = (peekToken() == Token.RP ? new EmptyExpression(begin) : expr()); + AstNode e = (peekToken() == Token.RP ? new EmptyExpression(begin) : expr(true)); if (peekToken() == Token.FOR) { return generatorExpression(e, begin); } mustMatchToken(Token.RP, "msg.no.paren", true); - if (e.getType() == Token.EMPTY && peekToken() != Token.ARROW) { + + int length = ts.tokenEnd - begin; + + boolean hasTrailingComma = e.getIntProp(Node.TRAILING_COMMA, 0) == 1; + if ((hasTrailingComma || e.getType() == Token.EMPTY) && peekToken() != Token.ARROW) { reportError("msg.syntax"); return makeErrorNode(); } - int length = ts.tokenEnd - begin; + ParenthesizedExpression pn = new ParenthesizedExpression(begin, length, e); pn.setLineno(lineno); if (jsdocNode == null) { @@ -3178,6 +3193,9 @@ private AstNode parenExpr() throws IOException { if (jsdocNode != null) { pn.setJsDocNode(jsdocNode); } + if (hasTrailingComma) { + pn.putIntProp(Node.TRAILING_COMMA, 1); + } return pn; } finally { inForInit = wasInForInit; @@ -3352,7 +3370,7 @@ private ArrayComprehensionLoop arrayComprehensionLoop() throws IOException { default: reportError("msg.in.after.for.name"); } - AstNode obj = expr(); + AstNode obj = expr(false); if (mustMatchToken(Token.RP, "msg.no.paren.for.ctrl", true)) rp = ts.tokenBeg - pos; pn.setLength(ts.tokenEnd - pos); @@ -3437,7 +3455,7 @@ private GeneratorExpressionLoop generatorExpressionLoop() throws IOException { } if (mustMatchToken(Token.IN, "msg.in.after.for.name", true)) inPos = ts.tokenBeg - pos; - AstNode obj = expr(); + AstNode obj = expr(false); if (mustMatchToken(Token.RP, "msg.no.paren.for.ctrl", true)) rp = ts.tokenBeg - pos; pn.setLength(ts.tokenEnd - pos); @@ -3719,7 +3737,7 @@ private AstNode templateLiteral(boolean isTaggedLiteral) throws IOException { int tt = ts.readTemplateLiteral(isTaggedLiteral); while (tt == Token.TEMPLATE_LITERAL_SUBST) { elements.add(createTemplateLiteralCharacters(posChars)); - elements.add(expr()); + elements.add(expr(false)); mustMatchToken(Token.RC, "msg.syntax", true); posChars = ts.tokenBeg + 1; tt = ts.readTemplateLiteral(isTaggedLiteral); diff --git a/src/org/mozilla/javascript/ast/FunctionNode.java b/src/org/mozilla/javascript/ast/FunctionNode.java index e999e3d953..a19f7ce4f6 100644 --- a/src/org/mozilla/javascript/ast/FunctionNode.java +++ b/src/org/mozilla/javascript/ast/FunctionNode.java @@ -391,6 +391,9 @@ public String toSource(int depth) { } else { sb.append("("); printList(params, sb); + if (getIntProp(TRAILING_COMMA, 0) == 1) { + sb.append(", "); + } sb.append(") "); } if (isArrow) { diff --git a/testsrc/test262.properties b/testsrc/test262.properties index 30b5b4c2af..b4869c376d 100644 --- a/testsrc/test262.properties +++ b/testsrc/test262.properties @@ -3030,7 +3030,7 @@ language/expressions/addition 9/48 (18.75%) get-symbol-to-prim-err.js order-of-evaluation.js -language/expressions/arrow-function 212/333 (63.66%) +language/expressions/arrow-function 210/333 (63.06%) dstr/ary-init-iter-close.js dstr/ary-init-iter-get-err.js dstr/ary-init-iter-get-err-array-prototype.js @@ -3233,8 +3233,6 @@ language/expressions/arrow-function 212/333 (63.66%) param-dflt-yield-id-non-strict.js {unsupported: [default-parameters]} param-dflt-yield-id-strict.js {unsupported: [default-parameters]} params-duplicate.js non-strict - params-trailing-comma-multiple.js - params-trailing-comma-single.js rest-param-strict-body.js {unsupported: [rest-parameters]} scope-body-lex-distinct.js non-strict scope-param-elem-var-close.js non-strict @@ -3464,7 +3462,7 @@ language/expressions/exponentiation 4/44 (9.09%) bigint-wrapped-values.js {unsupported: [computed-property-names]} order-of-evaluation.js -language/expressions/function 205/248 (82.66%) +language/expressions/function 203/248 (81.85%) dstr/ary-init-iter-close.js dstr/ary-init-iter-get-err.js dstr/ary-init-iter-get-err-array-prototype.js @@ -3655,8 +3653,6 @@ language/expressions/function 205/248 (82.66%) param-eval-strict-body.js non-strict params-dflt-args-unmapped.js {unsupported: [default-parameters]} params-dflt-ref-arguments.js {unsupported: [default-parameters]} - params-trailing-comma-multiple.js - params-trailing-comma-single.js rest-param-strict-body.js {unsupported: [rest-parameters]} scope-body-lex-distinct.js non-strict scope-name-var-open-non-strict.js non-strict @@ -3871,8 +3867,8 @@ language/expressions/generators 227/275 (82.55%) param-dflt-yield.js {unsupported: [default-parameters]} params-dflt-args-unmapped.js {unsupported: [default-parameters]} params-dflt-ref-arguments.js {unsupported: [default-parameters]} - params-trailing-comma-multiple.js - params-trailing-comma-single.js + params-trailing-comma-multiple.js non-interpreted + params-trailing-comma-single.js non-interpreted prototype-own-properties.js prototype-relation-to-function.js prototype-value.js @@ -3951,7 +3947,7 @@ language/expressions/multiplication 4/40 (10.0%) bigint-wrapped-values.js {unsupported: [computed-property-names]} order-of-evaluation.js -language/expressions/object 841/1081 (77.8%) +language/expressions/object 839/1081 (77.61%) dstr/async-gen-meth-ary-init-iter-close.js {unsupported: [async-iteration, async]} dstr/async-gen-meth-ary-init-iter-get-err.js {unsupported: [async-iteration]} dstr/async-gen-meth-ary-init-iter-get-err-array-prototype.js {unsupported: [async-iteration]} @@ -4667,8 +4663,6 @@ language/expressions/object 841/1081 (77.8%) method-definition/meth-dflt-params-trailing-comma.js method-definition/meth-eval-var-scope-syntax-err.js {unsupported: [default-parameters]} method-definition/meth-object-destructuring-param-strict-body.js {unsupported: [rest-parameters]} - method-definition/meth-params-trailing-comma-multiple.js - method-definition/meth-params-trailing-comma-single.js method-definition/meth-rest-param-strict-body.js {unsupported: [rest-parameters]} method-definition/name-invoke-ctor.js method-definition/name-invoke-fn-strict.js non-strict @@ -6152,8 +6146,8 @@ language/statements/generators 220/259 (84.94%) param-dflt-yield.js {unsupported: [default-parameters]} params-dflt-args-unmapped.js {unsupported: [default-parameters]} params-dflt-ref-arguments.js {unsupported: [default-parameters]} - params-trailing-comma-multiple.js - params-trailing-comma-single.js + params-trailing-comma-multiple.js non-interpreted + params-trailing-comma-single.js non-interpreted prototype-own-properties.js prototype-relation-to-function.js prototype-value.js