From a3f43df06a8f62f7d9723b5f748075382887417d Mon Sep 17 00:00:00 2001 From: Paul Bakker Date: Sat, 4 Nov 2023 10:38:54 +0100 Subject: [PATCH 1/3] Adds support for trailing commas in function parameters --- src/org/mozilla/javascript/Node.java | 5 ++++- src/org/mozilla/javascript/Parser.java | 18 ++++++++++++++++-- testsrc/test262.properties | 20 +++++++------------- 3 files changed, 27 insertions(+), 16 deletions(-) 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..55071b9749 100644 --- a/src/org/mozilla/javascript/Parser.java +++ b/src/org/mozilla/javascript/Parser.java @@ -821,7 +821,7 @@ private void parseFunctionParams(FunctionNode fnNode) throws IOException { fnNode.addParam(makeErrorNode()); } } - } while (matchToken(Token.COMMA, true)); + } while (matchToken(Token.COMMA, true) && peekToken() != Token.RP); if (destructuring != null) { Node destructuringNode = new Node(Token.COMMA); @@ -2288,6 +2288,10 @@ else if (symDeclType == Token.LP) { } private AstNode expr() throws IOException { + return expr(false); + } + + private AstNode expr(boolean allowTrailingComma) throws IOException { AstNode pn = assignExpr(); int pos = pn.getPosition(); while (matchToken(Token.COMMA, true)) { @@ -2295,6 +2299,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 (peekToken() == Token.RP && allowTrailingComma) { + pn.putIntProp(Node.TRAILING_COMMA, 1); + return pn; + } pn = new InfixExpression(Token.COMMA, pn, assignExpr(), opPos); } return pn; @@ -3160,11 +3168,17 @@ 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.getIntProp(Node.TRAILING_COMMA, 0) == 1 && peekToken() != Token.ARROW) { + reportError("msg.syntax"); + return makeErrorNode(); + } + if (e.getType() == Token.EMPTY && peekToken() != Token.ARROW) { reportError("msg.syntax"); return makeErrorNode(); diff --git a/testsrc/test262.properties b/testsrc/test262.properties index 23251c6462..c21fc67391 100644 --- a/testsrc/test262.properties +++ b/testsrc/test262.properties @@ -3029,7 +3029,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 @@ -3232,8 +3232,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 @@ -3463,7 +3461,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 @@ -3654,8 +3652,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 @@ -3870,8 +3866,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 @@ -3950,7 +3946,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]} @@ -4666,8 +4662,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 @@ -6151,8 +6145,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 From c17868ed3fea435f8c4ffedcad1882a95156a9b2 Mon Sep 17 00:00:00 2001 From: Paul Bakker Date: Mon, 6 Nov 2023 09:49:32 +0100 Subject: [PATCH 2/3] eliminate private AstNode expr() in favor of private AstNode expr(boolean) --- src/org/mozilla/javascript/Parser.java | 52 ++++++++++++-------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/src/org/mozilla/javascript/Parser.java b/src/org/mozilla/javascript/Parser.java index 55071b9749..f3d5f9a60b 100644 --- a/src/org/mozilla/javascript/Parser.java +++ b/src/org/mozilla/javascript/Parser.java @@ -1081,7 +1081,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 +1245,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 +1319,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 +1343,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 +1499,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 +1514,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 +1523,7 @@ && matchToken(Token.NAME, true) incr = new EmptyExpression(tmpPos, 1); incr.setLineno(ts.lineno); } else { - incr = expr(); + incr = expr(false); } } @@ -1593,7 +1593,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 +1667,7 @@ private TryStatement tryStatement() throws IOException { if (matchToken(Token.IF, true)) { guardPos = ts.tokenBeg; - catchCond = expr(); + catchCond = expr(false); } else { sawDefaultCatch = true; } @@ -1751,7 +1751,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 +1853,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 +1927,7 @@ private AstNode returnOrYield(int tt, boolean exprContext) throws IOException { } // fallthrough default: - e = expr(); + e = expr(false); end = getNodeEnd(e); } @@ -2004,7 +2004,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 +2046,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 +2061,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 +2203,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,10 +2287,6 @@ else if (symDeclType == Token.LP) { } } - private AstNode expr() throws IOException { - return expr(false); - } - private AstNode expr(boolean allowTrailingComma) throws IOException { AstNode pn = assignExpr(); int pos = pn.getPosition(); @@ -2641,7 +2637,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()); @@ -2773,7 +2769,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; @@ -2792,7 +2788,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; @@ -3043,7 +3039,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; @@ -3366,7 +3362,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); @@ -3451,7 +3447,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); @@ -3733,7 +3729,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); From 3be3addb45d5ac8e57112e8ce2d065d7cdbbf2ae Mon Sep 17 00:00:00 2001 From: Paul Bakker Date: Mon, 6 Nov 2023 10:31:51 +0100 Subject: [PATCH 3/3] Include trailing commas in function params in AST.toSource --- src/org/mozilla/javascript/Parser.java | 24 ++++++++++++------- .../mozilla/javascript/ast/FunctionNode.java | 3 +++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/org/mozilla/javascript/Parser.java b/src/org/mozilla/javascript/Parser.java index f3d5f9a60b..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); @@ -821,7 +825,7 @@ private void parseFunctionParams(FunctionNode fnNode) throws IOException { fnNode.addParam(makeErrorNode()); } } - } while (matchToken(Token.COMMA, true) && peekToken() != Token.RP); + } while (matchToken(Token.COMMA, true)); if (destructuring != null) { Node destructuringNode = new Node(Token.COMMA); @@ -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); @@ -2295,7 +2302,7 @@ private AstNode expr(boolean allowTrailingComma) 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 (peekToken() == Token.RP && allowTrailingComma) { + if (allowTrailingComma && peekToken() == Token.RP) { pn.putIntProp(Node.TRAILING_COMMA, 1); return pn; } @@ -3170,16 +3177,14 @@ private AstNode parenExpr() throws IOException { } mustMatchToken(Token.RP, "msg.no.paren", true); - if (e.getIntProp(Node.TRAILING_COMMA, 0) == 1 && peekToken() != Token.ARROW) { - reportError("msg.syntax"); - return makeErrorNode(); - } + int length = ts.tokenEnd - begin; - if (e.getType() == Token.EMPTY && peekToken() != Token.ARROW) { + 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) { @@ -3188,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; 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) {