Skip to content

Commit 0828a8b

Browse files
authored
Implement top-level await in modules feature (#361)
* Enable top level await in modules (and expressions) * Enable test262 tests
1 parent 7696695 commit 0828a8b

File tree

5 files changed

+77
-34
lines changed

5 files changed

+77
-34
lines changed

src/Esprima/JavascriptParser.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ public Module ParseModule(string code, string? source = null)
207207
try
208208
{
209209
_context.Strict = true;
210+
_context.IsAsync = true;
210211
_context.IsModule = true;
211212
_scanner._isModule = true;
212213

@@ -675,7 +676,7 @@ private protected virtual Expression ParsePrimaryExpression()
675676
switch (_lookahead.Type)
676677
{
677678
case TokenType.Identifier:
678-
if ((_context.IsModule || _context.IsAsync) && "await".Equals(_lookahead.Value))
679+
if (_context.IsAsync && "await".Equals(_lookahead.Value))
679680
{
680681
TolerateUnexpectedToken(_lookahead);
681682
}
@@ -2058,6 +2059,11 @@ private Expression ParseUnaryExpression()
20582059
}
20592060
else if (_context.IsAsync && MatchContextualKeyword("await"))
20602061
{
2062+
if (_lookahead.End - _lookahead.Start != "await".Length)
2063+
{
2064+
TolerateUnexpectedToken(_lookahead, Messages.InvalidEscapedReservedWord);
2065+
}
2066+
20612067
expr = ParseAwaitExpression();
20622068
}
20632069
else
@@ -2668,6 +2674,7 @@ public Expression ParseExpression(string code)
26682674
Reset(code, source: null);
26692675
try
26702676
{
2677+
_context.IsAsync = true;
26712678
return FinalizeRoot(ParseExpression());
26722679
}
26732680
finally
@@ -3079,7 +3086,7 @@ private Identifier ParseVariableIdentifier(VariableDeclarationKind? kind = null,
30793086
}
30803087
}
30813088
}
3082-
else if ((_context.IsModule || _context.IsAsync) && !allowAwaitKeyword && token.Type == TokenType.Identifier && (string?) token.Value == "await")
3089+
else if (_context.IsAsync && !allowAwaitKeyword && token.Type == TokenType.Identifier && (string?) token.Value == "await")
30833090
{
30843091
TolerateUnexpectedToken(token);
30853092
}

test/Esprima.Tests.Test262/Test262Harness.settings.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
"Namespace": "Esprima.Tests.Test262",
66
"Parallel": true,
77
"ExcludedFeatures": [
8-
"regexp-unicode-property-escapes",
9-
"top-level-await"
8+
"regexp-unicode-property-escapes"
109
],
1110
"ExcludedFlags": [],
1211
"ExcludedDirectories": [],

test/Esprima.Tests/Fixtures.cs

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,14 @@ public void ExecuteTestCase(string fixture)
146146
metadata = FixtureMetadata.Default;
147147
}
148148

149+
if (metadata.Skip)
150+
{
151+
return;
152+
}
153+
149154
var parserOptions = parserOptionsFactory(false, !metadata.IgnoresRegex);
150155

151156
var conversionOptions = metadata.CreateConversionOptions(conversionDefaultOptions);
152-
153-
#pragma warning disable 162
154157
if (File.Exists(moduleFilePath))
155158
{
156159
sourceType = SourceType.Module;
@@ -182,7 +185,6 @@ public void ExecuteTestCase(string fixture)
182185
if (!CompareTrees(actual, expected, metadata))
183186
File.WriteAllText(failureFilePath, actual);
184187
}
185-
#pragma warning restore 162
186188
}
187189
else
188190
{
@@ -236,12 +238,15 @@ internal static string GetFixturesPath()
236238

237239
private sealed class FixtureMetadata
238240
{
239-
public static readonly FixtureMetadata Default = new FixtureMetadata(
240-
testCompatibilityMode: AstToJsonTestCompatibilityMode.None,
241-
includesLocation: true,
242-
includesRange: true,
243-
includesLocationSource: false,
244-
ignoresRegex: false);
241+
public static readonly FixtureMetadata Default = new()
242+
{
243+
TestCompatibilityMode = AstToJsonTestCompatibilityMode.None,
244+
IncludesLocation = true,
245+
IncludesRange = true,
246+
IncludesLocationSource = false,
247+
IgnoresRegex = false,
248+
Skip = false,
249+
};
245250

246251
private sealed class Group
247252
{
@@ -276,28 +281,25 @@ public static Dictionary<string, FixtureMetadata> ReadMetadata()
276281

277282
private static FixtureMetadata CreateFrom(HashSet<string> flags)
278283
{
279-
return new FixtureMetadata(
280-
testCompatibilityMode: flags.Contains("EsprimaOrgFixture") ? AstToJsonTestCompatibilityMode.EsprimaOrg : AstToJsonTestCompatibilityMode.None,
281-
includesLocation: flags.Contains("IncludesLocation"),
282-
includesRange: flags.Contains("IncludesRange"),
283-
includesLocationSource: flags.Contains("IncludesLocationSource"),
284-
ignoresRegex: flags.Contains("IgnoresRegex"));
284+
return new FixtureMetadata
285+
{
286+
TestCompatibilityMode = flags.Contains("EsprimaOrgFixture") ? AstToJsonTestCompatibilityMode.EsprimaOrg : AstToJsonTestCompatibilityMode.None,
287+
IncludesLocation = flags.Contains("IncludesLocation"),
288+
IncludesRange = flags.Contains("IncludesRange"),
289+
IncludesLocationSource = flags.Contains("IncludesLocationSource"),
290+
IgnoresRegex = flags.Contains("IgnoresRegex"),
291+
Skip = flags.Contains("Skip"),
292+
};
285293
}
286294

287-
private FixtureMetadata(AstToJsonTestCompatibilityMode testCompatibilityMode, bool includesLocation, bool includesRange, bool includesLocationSource, bool ignoresRegex)
288-
{
289-
TestCompatibilityMode = testCompatibilityMode;
290-
IncludesLocation = includesLocation;
291-
IncludesRange = includesRange;
292-
IncludesLocationSource = includesLocationSource;
293-
IgnoresRegex = ignoresRegex;
294-
}
295+
private FixtureMetadata() { }
295296

296-
public AstToJsonTestCompatibilityMode TestCompatibilityMode { get; }
297-
public bool IncludesLocation { get; }
298-
public bool IncludesRange { get; }
299-
public bool IncludesLocationSource { get; }
300-
public bool IgnoresRegex { get; }
297+
public AstToJsonTestCompatibilityMode TestCompatibilityMode { get; init; }
298+
public bool IncludesLocation { get; init; }
299+
public bool IncludesRange { get; init; }
300+
public bool IncludesLocationSource { get; init; }
301+
public bool IgnoresRegex { get; init; }
302+
public bool Skip { get; init; }
301303

302304
public AstToJsonOptions CreateConversionOptions(AstToJsonOptions defaultOptions) => defaultOptions with
303305
{

test/Esprima.Tests/Fixtures/fixtures-metadata.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,6 @@
619619
"ES6/identifier/estimated.js",
620620
"ES6/identifier/ethiopic_digits.js",
621621
"ES6/identifier/invalid_escaped_surrogate_pairs.js",
622-
"ES6/identifier/invalid_expression_await.module.js",
623622
"ES6/identifier/invalid_function_await.module.js",
624623
"ES6/identifier/invalid_id_smp.js",
625624
"ES6/identifier/invalid_lone_surrogate.source.js",
@@ -629,7 +628,6 @@
629628
"ES6/identifier/math_dal_part.js",
630629
"ES6/identifier/math_kaf_lam.js",
631630
"ES6/identifier/math_zain_start.js",
632-
"ES6/identifier/module_await.js",
633631
"ES6/identifier/valid_await.js",
634632
"ES6/identifier/weierstrass.js",
635633
"ES6/identifier/weierstrass_weierstrass.js",
@@ -1710,5 +1708,12 @@
17101708
"expression/primary/literal/numeric/migrated_0003.js",
17111709
"expression/primary/literal/regular-expression/migrated_0008.js"
17121710
]
1711+
},
1712+
{
1713+
"flags": [ "EsprimaOrgFixture", "Skip" ],
1714+
"files": [
1715+
"ES6/identifier/invalid_expression_await.module.js",
1716+
"ES6/identifier/module_await.js"
1717+
]
17131718
}
17141719
]

test/Esprima.Tests/ParserTests.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,4 +616,34 @@ class X {
616616

617617
Assert.Equal(expected, json);
618618
}
619+
620+
[Theory]
621+
[InlineData("script", true)]
622+
[InlineData("module", false)]
623+
[InlineData("expression", false)]
624+
public void ShouldParseTopLevelAwait(string sourceType, bool shouldThrow)
625+
{
626+
const string code = "await import('x')";
627+
628+
var parser = new JavaScriptParser();
629+
Func<JavaScriptParser, Node> parseAction = sourceType switch
630+
{
631+
"script" => parser => parser.ParseScript(code),
632+
"module" => parser => parser.ParseModule(code),
633+
"expression" => parser => parser.ParseExpression(code),
634+
_ => throw new InvalidOperationException()
635+
};
636+
637+
if (!shouldThrow)
638+
{
639+
var node = parseAction(parser);
640+
var awaitExpression = node.DescendantNodesAndSelf().OfType<AwaitExpression>().FirstOrDefault();
641+
Assert.NotNull(awaitExpression);
642+
Assert.IsType<Import>(awaitExpression.Argument);
643+
}
644+
else
645+
{
646+
Assert.Throws<ParserException>(() => parseAction(parser));
647+
}
648+
}
619649
}

0 commit comments

Comments
 (0)