Skip to content

Commit 4e83c3f

Browse files
committed
Trailing comma in an arrow function should be valid.
Trailing commas is allowed for all function including arrow function. Since we don't know that an expression can be formals of the lambda function we need to defer the error till we determine that we indeed going to have the arrow function.
1 parent 841036a commit 4e83c3f

File tree

4 files changed

+64
-18
lines changed

4 files changed

+64
-18
lines changed

lib/Parser/Parse.cpp

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ Parser::Parser(Js::ScriptContext* scriptContext, BOOL strictMode, PageAllocator
108108
m_isInParsingArgList(false),
109109
m_hasDestructuringPattern(false),
110110
m_hasDeferredShorthandInitError(false),
111+
m_deferCommaError(false),
111112
m_pnestedCount(nullptr),
112113

113114
wellKnownPropertyPids(), // should initialize to nullptrs
@@ -125,7 +126,7 @@ Parser::Parser(Js::ScriptContext* scriptContext, BOOL strictMode, PageAllocator
125126
m_funcParenExprDepth(0),
126127
m_deferEllipsisError(false),
127128
m_deferEllipsisErrorLoc(), // calls default initializer
128-
129+
m_deferCommaErrorLoc(),
129130
m_tryCatchOrFinallyDepth(0),
130131

131132
m_pstmtCur(nullptr),
@@ -3107,9 +3108,7 @@ ParseNodePtr Parser::ParseTerm(BOOL fAllowCall,
31073108
uint saveCurrBlockId = GetCurrentBlock()->blockId;
31083109
GetCurrentBlock()->blockId = m_nextBlockId++;
31093110

3110-
// Push the deferred error state for ellipsis errors. It is possible that another syntax error will occur before we undefer this one.
3111-
bool deferEllipsisErrorSave = m_deferEllipsisError;
3112-
RestorePoint ellipsisErrorLocSave = m_deferEllipsisErrorLoc;
3111+
AutoDeferErrorsRestore deferErrorRestore(this);
31133112

31143113
this->m_funcParenExprDepth++;
31153114
pnode = ParseExpr<buildAST>(koplNo, &fCanAssign, TRUE, FALSE, nullptr, nullptr /*nameLength*/, nullptr /*pShortNameOffset*/, &term, true, nullptr, plastRParen);
@@ -3129,22 +3128,22 @@ ParseNodePtr Parser::ParseTerm(BOOL fAllowCall,
31293128
// Put back the original next-block-ID so the existing pid ref stacks will be correct.
31303129
m_nextBlockId = saveNextBlockId;
31313130
}
3132-
3133-
// Emit a deferred ... error if one was parsed.
3134-
if (m_deferEllipsisError && m_token.tk != tkDArrow)
3135-
{
3136-
this->GetScanner()->SeekTo(m_deferEllipsisErrorLoc);
3137-
Error(ERRInvalidSpreadUse);
3138-
}
31393131
else
31403132
{
3141-
m_deferEllipsisError = false;
3133+
// Emit a deferred ... error if one was parsed.
3134+
if (m_deferEllipsisError)
3135+
{
3136+
this->GetScanner()->SeekTo(m_deferEllipsisErrorLoc);
3137+
Error(ERRInvalidSpreadUse);
3138+
}
3139+
else if (m_deferCommaError)
3140+
{
3141+
// Emit a comma error if that was deferred.
3142+
this->GetScanner()->SeekTo(m_deferCommaErrorLoc);
3143+
Error(ERRsyntax);
3144+
}
31423145
}
31433146

3144-
// We didn't error out, so restore the deferred error state.
3145-
m_deferEllipsisError = deferEllipsisErrorSave;
3146-
m_deferEllipsisErrorLoc = ellipsisErrorLocSave;
3147-
31483147
break;
31493148
}
31503149

@@ -8866,6 +8865,14 @@ ParseNodePtr Parser::ParseExpr(int oplMin,
88668865
}
88678866
else // a binary operator
88688867
{
8868+
if (nop == knopComma && m_token.tk == tkRParen)
8869+
{
8870+
// Trailing comma
8871+
this->GetScanner()->Capture(&m_deferCommaErrorLoc);
8872+
m_deferCommaError = true;
8873+
break;
8874+
}
8875+
88698876
ParseNode* pnode1 = pnode;
88708877

88718878
// Parse the operand, make a new node, and look for more

lib/Parser/Parse.h

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,8 @@ class Parser
431431
bool m_hasDestructuringPattern;
432432
// This bool is used for deferring the shorthand initializer error ( {x = 1}) - as it is allowed in the destructuring grammar.
433433
bool m_hasDeferredShorthandInitError;
434+
bool m_deferEllipsisError;
435+
bool m_deferCommaError;
434436
uint * m_pnestedCount; // count of functions nested at one level below the current node
435437

436438
struct WellKnownPropertyPids
@@ -480,8 +482,8 @@ class Parser
480482

481483
// Used for issuing spread and rest errors when there is ambiguity with lambda parameter lists and parenthesized expressions
482484
uint m_funcParenExprDepth;
483-
bool m_deferEllipsisError;
484485
RestorePoint m_deferEllipsisErrorLoc;
486+
RestorePoint m_deferCommaErrorLoc;
485487

486488
uint m_tryCatchOrFinallyDepth; // Used to determine if parsing is currently in a try/catch/finally block in order to throw error on yield expressions inside them
487489

@@ -570,6 +572,33 @@ class Parser
570572
#endif
571573
};
572574

575+
class AutoDeferErrorsRestore
576+
{
577+
public:
578+
AutoDeferErrorsRestore(Parser *p)
579+
: m_parser(p)
580+
{
581+
m_deferEllipsisErrorSave = m_parser->m_deferEllipsisError;
582+
m_deferCommaError = m_parser->m_deferCommaError;
583+
m_ellipsisErrorLocSave = m_parser->m_deferEllipsisErrorLoc;
584+
m_commaErrorLocSave = m_parser->m_deferCommaErrorLoc;
585+
}
586+
587+
~AutoDeferErrorsRestore()
588+
{
589+
m_parser->m_deferEllipsisError = m_deferEllipsisErrorSave;
590+
m_parser->m_deferCommaError = m_deferCommaError;
591+
m_parser->m_deferEllipsisErrorLoc = m_ellipsisErrorLocSave;
592+
m_parser->m_deferCommaErrorLoc = m_commaErrorLocSave;
593+
}
594+
private:
595+
Parser *m_parser;
596+
RestorePoint m_ellipsisErrorLocSave;
597+
RestorePoint m_commaErrorLocSave;
598+
bool m_deferEllipsisErrorSave;
599+
bool m_deferCommaError;
600+
};
601+
573602
// This function is going to capture some of the important current state of the parser to an object. Once we learn
574603
// that we need to reparse the grammar again we could use RestoreStateFrom to restore that state to the parser.
575604
void CaptureState(ParserState *state);

test/es6/lambda1.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,8 @@ var tests = [
169169
{
170170
name: "Interesting valid and invalid syntax",
171171
body: function () {
172+
assert.doesNotThrow(() => { eval('(x, ) => {};'); }, SyntaxError, "Trailing comma is valid syntax");
172173
assert.throws(() => { eval('(var x) => {};'); }, SyntaxError, "var not used in formals declaration", "Syntax error");
173-
assert.throws(() => { eval('(x, ) => {};'); }, SyntaxError, "missing formal identifier in parameter list", "Syntax error");
174174
assert.throws(() => { eval('a.x => {};'); }, SyntaxError, "valid expression syntax that is invalid parameter list syntax on lhs of =>", "Syntax error");
175175
assert.throws(() => { eval('(x, y)[7] => {};'); }, SyntaxError, "valid expression syntax that is invalid parameter list syntax on lhs of =>", "Expected '=>'");
176176
assert.throws(() => { eval('x() => {};'); }, SyntaxError, "valid expression syntax that is invalid parameter list syntax on lhs of =>", "Syntax error");

test/es6/trailingcomma.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,16 @@ var tests = [
2424
assert.doesNotThrow(function () { eval("function foo() {}; foo(1, 2,);"); }, "Trailing comma in a user function call is a valid syntax");
2525
assert.doesNotThrow(function () { eval("Math.min(1, 2, );"); }, "Trailing comma in built-in function call is a valid syntax");
2626
}
27+
},
28+
{
29+
name: "Trailing comma in arrow function",
30+
body: function () {
31+
assert.doesNotThrow(function () { eval("(a,) => {}"); }, "Trailing comma in an arrow function is a valid syntax");
32+
assert.doesNotThrow(function () { eval("let f = (a,) => {}"); }, "Trailing comma in an arrow function is a valid syntax");
33+
assert.doesNotThrow(function () { eval("(b = (a,) => {}) => {}"); }, "Trailing comma in an nested arrow function is a valid syntax");
34+
assert.doesNotThrow(function () { eval("({b = (a,) => {}}) => {}"); }, "Trailing comma in an nested arrow function in destructuring is a valid syntax");
35+
assert.throws(function () { eval("(b = (a,)) => {}"); }, SyntaxError, "Trailing comma in a comma expression is not valid syntax");
36+
}
2737
}
2838
];
2939

0 commit comments

Comments
 (0)