Skip to content

Commit c16768b

Browse files
Improvement on error reporting of 'for await' loops (#189)
* minor code readability improvements * improves error reporting of 'for await' loops (#183) Co-authored-by: Sébastien Ros <[email protected]>
1 parent 046a54a commit c16768b

File tree

2 files changed

+83
-6
lines changed

2 files changed

+83
-6
lines changed

src/Esprima/JavascriptParser.cs

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,6 @@ private void ConsumeSemicolon()
546546
_lastMarker.Line = _startMarker.Line;
547547
_lastMarker.Column = _startMarker.Column;
548548
}
549-
550549
}
551550

552551
// https://tc39.github.io/ecma262/#sec-primary-expression
@@ -2938,7 +2937,7 @@ private Statement ParseForStatement()
29382937
var forIn = true;
29392938
Node? left = null;
29402939
Expression? right = null;
2941-
var _await = false;
2940+
var @await = false;
29422941

29432942
var node = CreateNode();
29442943
ExpectKeyword("for");
@@ -2948,14 +2947,19 @@ private Statement ParseForStatement()
29482947
{
29492948
TolerateUnexpectedToken(_lookahead);
29502949
}
2951-
_await = true;
2950+
@await = true;
29522951
NextToken();
29532952
}
29542953

29552954
Expect("(");
29562955

29572956
if (Match(";"))
29582957
{
2958+
if (@await)
2959+
{
2960+
TolerateUnexpectedToken(_lookahead);
2961+
}
2962+
29592963
NextToken();
29602964
}
29612965
else
@@ -2971,8 +2975,13 @@ private Statement ParseForStatement()
29712975
var declarations = ParseVariableDeclarationList(ref inFor);
29722976
_context.AllowIn = previousAllowIn;
29732977

2974-
if (!_await && declarations.Count == 1 && MatchKeyword("in"))
2978+
if (declarations.Count == 1 && MatchKeyword("in"))
29752979
{
2980+
if (@await)
2981+
{
2982+
TolerateUnexpectedToken(_lookahead);
2983+
}
2984+
29762985
var decl = declarations[0];
29772986
if (decl.Init != null && (decl.Id.Type == Nodes.ArrayPattern || decl.Id.Type == Nodes.ObjectPattern || _context.Strict))
29782987
{
@@ -2994,6 +3003,11 @@ private Statement ParseForStatement()
29943003
}
29953004
else
29963005
{
3006+
if (@await)
3007+
{
3008+
TolerateUnexpectedToken(_lookahead);
3009+
}
3010+
29973011
init = Finalize(initNode, new VariableDeclaration(declarations, VariableDeclarationKind.Var));
29983012
Expect(";");
29993013
}
@@ -3005,6 +3019,11 @@ private Statement ParseForStatement()
30053019
var kind = ParseVariableDeclarationKind(kindString);
30063020
if (!_context.Strict && (string?) _lookahead.Value == "in")
30073021
{
3022+
if (@await)
3023+
{
3024+
TolerateUnexpectedToken(_lookahead);
3025+
}
3026+
30083027
left = Finalize(initNode, new Identifier(kindString));
30093028
NextToken();
30103029
right = ParseExpression();
@@ -3020,6 +3039,11 @@ private Statement ParseForStatement()
30203039

30213040
if (declarations.Count == 1 && declarations[0]!.Init == null && MatchKeyword("in"))
30223041
{
3042+
if (@await)
3043+
{
3044+
TolerateUnexpectedToken(_lookahead);
3045+
}
3046+
30233047
left = Finalize(initNode, new VariableDeclaration(declarations, kind));
30243048
NextToken();
30253049
right = ParseExpression();
@@ -3035,6 +3059,11 @@ private Statement ParseForStatement()
30353059
}
30363060
else
30373061
{
3062+
if (@await)
3063+
{
3064+
TolerateUnexpectedToken(_lookahead);
3065+
}
3066+
30383067
ConsumeSemicolon();
30393068
init = Finalize(initNode, new VariableDeclaration(declarations, kind));
30403069
}
@@ -3054,6 +3083,11 @@ private Statement ParseForStatement()
30543083

30553084
if (MatchKeyword("in"))
30563085
{
3086+
if (@await)
3087+
{
3088+
TolerateUnexpectedToken(_lookahead);
3089+
}
3090+
30573091
if (!_context.IsAssignmentTarget || init.Type == Nodes.AssignmentExpression)
30583092
{
30593093
TolerateError(Messages.InvalidLHSInForIn);
@@ -3081,6 +3115,11 @@ private Statement ParseForStatement()
30813115
}
30823116
else
30833117
{
3118+
if (@await)
3119+
{
3120+
TolerateUnexpectedToken(_lookahead);
3121+
}
3122+
30843123
// The `init` node was not parsed isolated, but we would have wanted it to.
30853124
_context.IsBindingElement = previousIsBindingElement;
30863125
_context.IsAssignmentTarget = previousIsAssignmentTarget;
@@ -3135,8 +3174,8 @@ private Statement ParseForStatement()
31353174
return left == null
31363175
? Finalize(node, new ForStatement(init, test, update, body))
31373176
: forIn
3138-
? Finalize(node, new ForInStatement(left, right!, body))
3139-
: Finalize(node, new ForOfStatement(left, right!, body, _await));
3177+
? (Statement) Finalize(node, new ForInStatement(left, right!, body))
3178+
: Finalize(node, new ForOfStatement(left, right!, body, @await));
31403179
}
31413180

31423181
// https://tc39.github.io/ecma262/#sec-continue-statement

test/Esprima.Tests/ParserTests.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,5 +202,43 @@ public void ThrowsErrorForDuplicateProto()
202202
var parser = new JavaScriptParser("if({ __proto__: [], __proto__: [] } instanceof Array) {}", new ParserOptions { Tolerant = false });
203203
Assert.Throws<ParserException>(() => parser.ParseScript());
204204
}
205+
206+
[Theory]
207+
[InlineData("(async () => { for await (var x of []) { } })()")]
208+
[InlineData("(async () => { for await (let x of []) { } })()")]
209+
[InlineData("(async () => { for await (const x of []) { } })()")]
210+
[InlineData("(async () => { for await (x of []) { } })()")]
211+
public void ParsesValidForAwaitLoops(string code)
212+
{
213+
var errorHandler = new CollectingErrorHandler();
214+
var parser = new JavaScriptParser(code, new ParserOptions { Tolerant = true, ErrorHandler = errorHandler });
215+
parser.ParseScript();
216+
217+
Assert.False(errorHandler.Errors.Any());
218+
}
219+
220+
[Theory]
221+
[InlineData("(async () => { for await (;;) { } })()")]
222+
[InlineData("(async () => { for await (var i = 0, j = 1;;) { } })()")]
223+
[InlineData("(async () => { for await (let i = 0, j = 1;;) { } })()")]
224+
[InlineData("(async () => { for await (const i = 0, j = 1;;) { } })()")]
225+
[InlineData("(async () => { for await (i = 0, j = 1;;) { } })()")]
226+
[InlineData("(async () => { for await (var x = (0 in []) in {}) { } })()")]
227+
[InlineData("(async () => { for await (let x in {}) { } })()")]
228+
[InlineData("(async () => { for await (const x in {}) { } })()")]
229+
[InlineData("(async () => { for await (let in {}) { } })()")]
230+
[InlineData("(async () => { for await (const in {}) { } })()")]
231+
[InlineData("(async () => { for await (x in {}) { } })()")]
232+
public void ToleratesInvalidForAwaitLoops(string code)
233+
{
234+
var errorHandler = new CollectingErrorHandler();
235+
var parser = new JavaScriptParser(code, new ParserOptions { Tolerant = true, ErrorHandler = errorHandler });
236+
parser.ParseScript();
237+
238+
Assert.True(errorHandler.Errors.Any());
239+
240+
parser = new JavaScriptParser(code, new ParserOptions { Tolerant = false });
241+
Assert.Throws<ParserException>(() => parser.ParseScript());
242+
}
205243
}
206244
}

0 commit comments

Comments
 (0)