Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for trailing commas in function parameters #1416

Merged
merged 3 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
gbrail marked this conversation as resolved.
Show resolved Hide resolved
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));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To my points above, if this is the only place where we pass "true," does it make more sense to add a new function instead?

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 @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean it fails in compiled mode?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The testcase fails due to #1412 now, but the params-trailing-comma part is fixed

params-trailing-comma-single.js non-interpreted
prototype-own-properties.js
prototype-relation-to-function.js
prototype-value.js
Expand Down