Skip to content

Commit 221116b

Browse files
committed
Fix await vs. class property initializers bug (acornjs/acorn#1334) + add tests
1 parent 10d351a commit 221116b

File tree

3 files changed

+164
-20
lines changed

3 files changed

+164
-20
lines changed

src/Acornima/Parser.State.cs

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -261,22 +261,16 @@ private bool CanAwait
261261

262262
get
263263
{
264-
for (var i = _scopeStack.Count - 1; i >= 0; i--)
264+
ref var scope = ref CurrentVarScope;
265+
if ((scope._flags & ScopeFlags.Function) != 0)
265266
{
266-
ref readonly var scope = ref _scopeStack.GetItemRef(i);
267-
268-
if ((scope._flags & (ScopeFlags.InClassFieldInit | ScopeFlags.ClassStaticBlock)) != 0)
269-
{
270-
return false;
271-
}
272-
273-
if ((scope._flags & ScopeFlags.Function) != 0)
274-
{
275-
return (scope._flags & ScopeFlags.Async) != 0;
276-
}
267+
return (scope._flags & (ScopeFlags.Async | ScopeFlags.InClassFieldInit)) == ScopeFlags.Async;
277268
}
278-
279-
return _options._allowAwaitOutsideFunction || _topLevelAwaitAllowed;
269+
if ((scope._flags & ScopeFlags.Top) != 0)
270+
{
271+
return (_options._allowAwaitOutsideFunction || _topLevelAwaitAllowed) && (scope._flags & ScopeFlags.InClassFieldInit) == 0;
272+
}
273+
return false;
280274
}
281275
}
282276

src/Acornima/Parser.Statement.cs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1439,15 +1439,22 @@ private ClassProperty ParseClassField(in Marker startMarker, Expression key, boo
14391439
if (Eat(TokenType.Eq))
14401440
{
14411441
// To raise SyntaxError if 'arguments' exists in the initializer.
1442-
var thisScopeIndex = CurrentScope._currentThisScopeIndex;
1443-
ref var scope = ref _scopeStack.GetItemRef(thisScopeIndex);
1444-
var oldScopeFlags = scope._flags;
1445-
scope._flags |= ScopeFlags.InClassFieldInit;
1442+
ref var currentScope = ref CurrentScope;
1443+
var thisScopeIndex = currentScope._currentThisScopeIndex;
1444+
var varScopeIndex = currentScope._currentVarScopeIndex;
1445+
1446+
ref var thisScopeFlags = ref _scopeStack.GetItemRef(thisScopeIndex)._flags;
1447+
ref var varScopeFlags = ref _scopeStack.GetItemRef(varScopeIndex)._flags;
1448+
1449+
var oldThisScopeFlags = thisScopeFlags;
1450+
var oldVarScopeFlags = varScopeFlags;
1451+
thisScopeFlags |= ScopeFlags.InClassFieldInit;
1452+
varScopeFlags |= ScopeFlags.InClassFieldInit;
14461453

14471454
value = ParseMaybeAssign(ref NullRef<DestructuringErrors>());
14481455

1449-
scope = ref _scopeStack.GetItemRef(thisScopeIndex);
1450-
scope._flags = oldScopeFlags;
1456+
_scopeStack.GetItemRef(thisScopeIndex)._flags = oldThisScopeFlags;
1457+
_scopeStack.GetItemRef(varScopeIndex)._flags = oldVarScopeFlags;
14511458
}
14521459
else
14531460
{

test/Acornima.Tests/ParserTests.cs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,149 @@ public void ShouldHandleStrictModeDetectionEdgeCases(string input, bool isModule
814814
}
815815
}
816816

817+
[Theory]
818+
[InlineData("script", "(class { x = () => arguments })", EcmaVersion.Latest, "'arguments' is not allowed in class field initializer or static initialization block")]
819+
[InlineData("script", "() => { (class { x = () => arguments }) }", EcmaVersion.Latest, "'arguments' is not allowed in class field initializer or static initialization block")]
820+
[InlineData("script", "() => class { x = () => { arguments } }", EcmaVersion.Latest, "'arguments' is not allowed in class field initializer or static initialization block")]
821+
[InlineData("script", "() => class { x = function() { arguments } }", EcmaVersion.Latest, null)]
822+
public void ShouldHandleArgumentsEdgeCases(string sourceType, string input, EcmaVersion ecmaVersion, string? expectedError)
823+
{
824+
var parser = new Parser(new ParserOptions { EcmaVersion = ecmaVersion });
825+
var parseAction = GetParseActionFor(sourceType);
826+
827+
if (expectedError is null)
828+
{
829+
Assert.NotNull(parseAction(parser, input));
830+
}
831+
else
832+
{
833+
var ex = Assert.Throws<SyntaxErrorException>(() => parseAction(parser, input));
834+
Assert.Equal(expectedError, ex.Description);
835+
}
836+
}
837+
838+
[Theory]
839+
[InlineData("script", "(class { x = () => new.target })", EcmaVersion.Latest, null)]
840+
[InlineData("script", "() => { (class { x = () => new.target }) }", EcmaVersion.Latest, null)]
841+
[InlineData("script", "() => class { x = () => { new.target } }", EcmaVersion.Latest, null)]
842+
[InlineData("script", "() => class { x = function() { new.target } }", EcmaVersion.Latest, null)]
843+
public void ShouldHandleNewTargetEdgeCases(string sourceType, string input, EcmaVersion ecmaVersion, string? expectedError)
844+
{
845+
var parser = new Parser(new ParserOptions { EcmaVersion = ecmaVersion });
846+
var parseAction = GetParseActionFor(sourceType);
847+
848+
if (expectedError is null)
849+
{
850+
Assert.NotNull(parseAction(parser, input));
851+
}
852+
else
853+
{
854+
var ex = Assert.Throws<SyntaxErrorException>(() => parseAction(parser, input));
855+
Assert.Equal(expectedError, ex.Description);
856+
}
857+
}
858+
859+
[Theory]
860+
[InlineData("script", "(class { x = () => super.y })", EcmaVersion.Latest, null)]
861+
[InlineData("script", "() => { (class { x = () => super.y }) }", EcmaVersion.Latest, null)]
862+
[InlineData("script", "() => class { x = () => { super.y } }", EcmaVersion.Latest, null)]
863+
[InlineData("script", "() => class { x = function() { super.y } }", EcmaVersion.Latest, "'super' keyword unexpected here")]
864+
[InlineData("script", "class C { x = class extends super.constructor { [super.constructor.name] = super.constructor } }", EcmaVersion.Latest, null)]
865+
[InlineData("script", "() => class { x = class extends super.constructor { [super.constructor.name] = super.constructor } }", EcmaVersion.Latest, null)]
866+
public void ShouldHandleSuperKeywordEdgeCases(string sourceType, string input, EcmaVersion ecmaVersion, string? expectedError)
867+
{
868+
var parser = new Parser(new ParserOptions { EcmaVersion = ecmaVersion });
869+
var parseAction = GetParseActionFor(sourceType);
870+
871+
if (expectedError is null)
872+
{
873+
Assert.NotNull(parseAction(parser, input));
874+
}
875+
else
876+
{
877+
var ex = Assert.Throws<SyntaxErrorException>(() => parseAction(parser, input));
878+
Assert.Equal(expectedError, ex.Description);
879+
}
880+
}
881+
882+
[Theory]
883+
[InlineData("script", "(class { x = await })", EcmaVersion.Latest, null)]
884+
[InlineData("module", "(class { x = await })", EcmaVersion.Latest, "Unexpected reserved word")]
885+
[InlineData("script", "(class { x = await 1 })", EcmaVersion.Latest, "await is only valid in async functions and the top level bodies of modules")]
886+
[InlineData("module", "(class { x = await 1 })", EcmaVersion.Latest, "Unexpected reserved word")]
887+
888+
[InlineData("script", "(class { x = () => await })", EcmaVersion.Latest, null)]
889+
[InlineData("module", "(class { x = () => await })", EcmaVersion.Latest, "Unexpected reserved word")]
890+
[InlineData("script", "(class { x = () => await 1 })", EcmaVersion.Latest, "await is only valid in async functions and the top level bodies of modules")]
891+
[InlineData("module", "(class { x = () => await 1 })", EcmaVersion.Latest, "Unexpected reserved word")]
892+
893+
[InlineData("script", "(class { x = async () => await })", EcmaVersion.Latest, "Unexpected token '}'")]
894+
[InlineData("module", "(class { x = async () => await })", EcmaVersion.Latest, "Unexpected token '}'")]
895+
[InlineData("script", "(class { x = async () => await 1 })", EcmaVersion.Latest, null)]
896+
[InlineData("module", "(class { x = async () => await 1 })", EcmaVersion.Latest, null)]
897+
898+
[InlineData("script", "() => class { x = await }", EcmaVersion.Latest, null)]
899+
[InlineData("module", "() => class { x = await }", EcmaVersion.Latest, "Unexpected reserved word")]
900+
[InlineData("script", "() => class { x = await 1 }", EcmaVersion.Latest, "await is only valid in async functions and the top level bodies of modules")]
901+
[InlineData("module", "() => class { x = await 1 }", EcmaVersion.Latest, "Unexpected reserved word")]
902+
903+
[InlineData("script", "() => class { x = () => await }", EcmaVersion.Latest, null)]
904+
[InlineData("module", "() => class { x = () => await }", EcmaVersion.Latest, "Unexpected reserved word")]
905+
[InlineData("script", "() => class { x = () => await 1 }", EcmaVersion.Latest, "await is only valid in async functions and the top level bodies of modules")]
906+
[InlineData("module", "() => class { x = () => await 1 }", EcmaVersion.Latest, "Unexpected reserved word")]
907+
908+
[InlineData("script", "() => class { x = async () => await }", EcmaVersion.Latest, "Unexpected token '}'")]
909+
[InlineData("module", "() => class { x = async () => await }", EcmaVersion.Latest, "Unexpected token '}'")]
910+
[InlineData("script", "() => class { x = async () => await 1 }", EcmaVersion.Latest, null)]
911+
[InlineData("module", "() => class { x = async () => await 1 }", EcmaVersion.Latest, null)]
912+
913+
[InlineData("script", "async () => class { x = await }", EcmaVersion.Latest, null)]
914+
[InlineData("module", "async () => class { x = await }", EcmaVersion.Latest, "Unexpected reserved word")]
915+
[InlineData("script", "async () => class { x = await 1 }", EcmaVersion.Latest, "Unexpected number")]
916+
[InlineData("module", "async () => class { x = await 1 }", EcmaVersion.Latest, "Unexpected reserved word")]
917+
918+
[InlineData("script", "async () => class { x = () => await }", EcmaVersion.Latest, null)]
919+
[InlineData("module", "async () => class { x = () => await }", EcmaVersion.Latest, "Unexpected reserved word")]
920+
[InlineData("script", "async () => class { x = () => await 1 }", EcmaVersion.Latest, "Unexpected number")]
921+
[InlineData("module", "async () => class { x = () => await 1 }", EcmaVersion.Latest, "Unexpected reserved word")]
922+
923+
[InlineData("script", "async () => class { x = async () => await }", EcmaVersion.Latest, "Unexpected token '}'")]
924+
[InlineData("module", "async () => class { x = async () => await }", EcmaVersion.Latest, "Unexpected token '}'")]
925+
[InlineData("script", "async () => class { x = async () => await 1 }", EcmaVersion.Latest, null)]
926+
[InlineData("module", "async () => class { x = async () => await 1 }", EcmaVersion.Latest, null)]
927+
928+
[InlineData("script", "async () => class { x = (a = await) => a }", EcmaVersion.Latest, null)]
929+
[InlineData("module", "async () => class { x = (a = await) => a }", EcmaVersion.Latest, "Unexpected reserved word")]
930+
[InlineData("script", "async () => class { x = (a = await 1) => a }", EcmaVersion.Latest, "Unexpected number")]
931+
[InlineData("module", "async () => class { x = (a = await 1) => a }", EcmaVersion.Latest, "Unexpected reserved word")]
932+
933+
[InlineData("script", "async () => class { x = class await { y = await } }", EcmaVersion.Latest, null)]
934+
[InlineData("module", "async () => class { x = class await { y = await } }", EcmaVersion.Latest, "Unexpected reserved word")]
935+
[InlineData("script", "async () => class { x = class await { y = await 1 } }", EcmaVersion.Latest, "await is only valid in async functions and the top level bodies of modules")]
936+
[InlineData("module", "async () => class { x = class await { y = await 1 } }", EcmaVersion.Latest, "Unexpected reserved word")]
937+
938+
[InlineData("script", "async () => class { x = () => { { try {} catch (await) { } } } }", EcmaVersion.Latest, null)]
939+
[InlineData("module", "async () => class { x = () => { { try {} catch (await) { } } } }", EcmaVersion.Latest, "Unexpected reserved word")]
940+
[InlineData("script", "async () => class { x = () => { { try {} catch { var await = 1 } } } }", EcmaVersion.Latest, null)]
941+
[InlineData("module", "async () => class { x = () => { { try {} catch { var await = 1 } } } }", EcmaVersion.Latest, "Unexpected reserved word")]
942+
public void ShouldHandleAwaitInClassFieldInitializer(string sourceType, string input, EcmaVersion ecmaVersion, string? expectedError)
943+
{
944+
// See also: https://github.com/acornjs/acorn/issues/1334, https://github.com/acornjs/acorn/issues/1338
945+
946+
var parser = new Parser(new ParserOptions { EcmaVersion = ecmaVersion });
947+
var parseAction = GetParseActionFor(sourceType);
948+
949+
if (expectedError is null)
950+
{
951+
Assert.NotNull(parseAction(parser, input));
952+
}
953+
else
954+
{
955+
var ex = Assert.Throws<SyntaxErrorException>(() => parseAction(parser, input));
956+
Assert.Equal(expectedError, ex.Description);
957+
}
958+
}
959+
817960
[Theory]
818961
[InlineData("script", "await", EcmaVersion.Latest, null)]
819962
[InlineData("script", "await", EcmaVersion.ES13, null)]

0 commit comments

Comments
 (0)