Skip to content

Commit

Permalink
Adds support for trailing commas in function parameters
Browse files Browse the repository at this point in the history
* Adds support for trailing commas in function parameters
  • Loading branch information
p-bakker committed Nov 14, 2023
1 parent 648a654 commit ef18cb9
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 42 deletions.
5 changes: 4 additions & 1 deletion src/org/mozilla/javascript/Node.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ public class Node implements Iterable<Node> {
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
Expand Down Expand Up @@ -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();
Expand Down
74 changes: 46 additions & 28 deletions src/org/mozilla/javascript/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,10 @@ private void parseFunctionParams(FunctionNode fnNode) throws IOException {
Set<String> 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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);

Expand All @@ -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;

Expand Down Expand Up @@ -1499,22 +1506,22 @@ 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) {
// no loop condition
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);
Expand All @@ -1523,7 +1530,7 @@ && matchToken(Token.NAME, true)
incr = new EmptyExpression(tmpPos, 1);
incr.setLineno(ts.lineno);
} else {
incr = expr();
incr = expr(false);
}
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -1927,7 +1934,7 @@ private AstNode returnOrYield(int tt, boolean exprContext) throws IOException {
}
// fallthrough
default:
e = expr();
e = expr(false);
end = getNodeEnd(e);
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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());
Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -2287,14 +2294,18 @@ 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)) {
int opPos = ts.tokenBeg;
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;
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions src/org/mozilla/javascript/ast/FunctionNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
20 changes: 7 additions & 13 deletions testsrc/test262.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit ef18cb9

Please sign in to comment.