Skip to content

Commit

Permalink
Fix await vs. class property initializers bug (acornjs/acorn#1334) + …
Browse files Browse the repository at this point in the history
…add tests
  • Loading branch information
adams85 committed Dec 29, 2024
1 parent 10d351a commit 221116b
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 20 deletions.
22 changes: 8 additions & 14 deletions src/Acornima/Parser.State.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,22 +261,16 @@ private bool CanAwait

get
{
for (var i = _scopeStack.Count - 1; i >= 0; i--)
ref var scope = ref CurrentVarScope;
if ((scope._flags & ScopeFlags.Function) != 0)
{
ref readonly var scope = ref _scopeStack.GetItemRef(i);

if ((scope._flags & (ScopeFlags.InClassFieldInit | ScopeFlags.ClassStaticBlock)) != 0)
{
return false;
}

if ((scope._flags & ScopeFlags.Function) != 0)
{
return (scope._flags & ScopeFlags.Async) != 0;
}
return (scope._flags & (ScopeFlags.Async | ScopeFlags.InClassFieldInit)) == ScopeFlags.Async;
}

return _options._allowAwaitOutsideFunction || _topLevelAwaitAllowed;
if ((scope._flags & ScopeFlags.Top) != 0)
{
return (_options._allowAwaitOutsideFunction || _topLevelAwaitAllowed) && (scope._flags & ScopeFlags.InClassFieldInit) == 0;
}
return false;
}
}

Expand Down
19 changes: 13 additions & 6 deletions src/Acornima/Parser.Statement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1439,15 +1439,22 @@ private ClassProperty ParseClassField(in Marker startMarker, Expression key, boo
if (Eat(TokenType.Eq))
{
// To raise SyntaxError if 'arguments' exists in the initializer.
var thisScopeIndex = CurrentScope._currentThisScopeIndex;
ref var scope = ref _scopeStack.GetItemRef(thisScopeIndex);
var oldScopeFlags = scope._flags;
scope._flags |= ScopeFlags.InClassFieldInit;
ref var currentScope = ref CurrentScope;
var thisScopeIndex = currentScope._currentThisScopeIndex;
var varScopeIndex = currentScope._currentVarScopeIndex;

ref var thisScopeFlags = ref _scopeStack.GetItemRef(thisScopeIndex)._flags;
ref var varScopeFlags = ref _scopeStack.GetItemRef(varScopeIndex)._flags;

var oldThisScopeFlags = thisScopeFlags;
var oldVarScopeFlags = varScopeFlags;
thisScopeFlags |= ScopeFlags.InClassFieldInit;
varScopeFlags |= ScopeFlags.InClassFieldInit;

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

scope = ref _scopeStack.GetItemRef(thisScopeIndex);
scope._flags = oldScopeFlags;
_scopeStack.GetItemRef(thisScopeIndex)._flags = oldThisScopeFlags;
_scopeStack.GetItemRef(varScopeIndex)._flags = oldVarScopeFlags;
}
else
{
Expand Down
143 changes: 143 additions & 0 deletions test/Acornima.Tests/ParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,149 @@ public void ShouldHandleStrictModeDetectionEdgeCases(string input, bool isModule
}
}

[Theory]
[InlineData("script", "(class { x = () => arguments })", EcmaVersion.Latest, "'arguments' is not allowed in class field initializer or static initialization block")]
[InlineData("script", "() => { (class { x = () => arguments }) }", EcmaVersion.Latest, "'arguments' is not allowed in class field initializer or static initialization block")]
[InlineData("script", "() => class { x = () => { arguments } }", EcmaVersion.Latest, "'arguments' is not allowed in class field initializer or static initialization block")]
[InlineData("script", "() => class { x = function() { arguments } }", EcmaVersion.Latest, null)]
public void ShouldHandleArgumentsEdgeCases(string sourceType, string input, EcmaVersion ecmaVersion, string? expectedError)
{
var parser = new Parser(new ParserOptions { EcmaVersion = ecmaVersion });
var parseAction = GetParseActionFor(sourceType);

if (expectedError is null)
{
Assert.NotNull(parseAction(parser, input));
}
else
{
var ex = Assert.Throws<SyntaxErrorException>(() => parseAction(parser, input));
Assert.Equal(expectedError, ex.Description);
}
}

[Theory]
[InlineData("script", "(class { x = () => new.target })", EcmaVersion.Latest, null)]
[InlineData("script", "() => { (class { x = () => new.target }) }", EcmaVersion.Latest, null)]
[InlineData("script", "() => class { x = () => { new.target } }", EcmaVersion.Latest, null)]
[InlineData("script", "() => class { x = function() { new.target } }", EcmaVersion.Latest, null)]
public void ShouldHandleNewTargetEdgeCases(string sourceType, string input, EcmaVersion ecmaVersion, string? expectedError)
{
var parser = new Parser(new ParserOptions { EcmaVersion = ecmaVersion });
var parseAction = GetParseActionFor(sourceType);

if (expectedError is null)
{
Assert.NotNull(parseAction(parser, input));
}
else
{
var ex = Assert.Throws<SyntaxErrorException>(() => parseAction(parser, input));
Assert.Equal(expectedError, ex.Description);
}
}

[Theory]
[InlineData("script", "(class { x = () => super.y })", EcmaVersion.Latest, null)]
[InlineData("script", "() => { (class { x = () => super.y }) }", EcmaVersion.Latest, null)]
[InlineData("script", "() => class { x = () => { super.y } }", EcmaVersion.Latest, null)]
[InlineData("script", "() => class { x = function() { super.y } }", EcmaVersion.Latest, "'super' keyword unexpected here")]
[InlineData("script", "class C { x = class extends super.constructor { [super.constructor.name] = super.constructor } }", EcmaVersion.Latest, null)]
[InlineData("script", "() => class { x = class extends super.constructor { [super.constructor.name] = super.constructor } }", EcmaVersion.Latest, null)]
public void ShouldHandleSuperKeywordEdgeCases(string sourceType, string input, EcmaVersion ecmaVersion, string? expectedError)
{
var parser = new Parser(new ParserOptions { EcmaVersion = ecmaVersion });
var parseAction = GetParseActionFor(sourceType);

if (expectedError is null)
{
Assert.NotNull(parseAction(parser, input));
}
else
{
var ex = Assert.Throws<SyntaxErrorException>(() => parseAction(parser, input));
Assert.Equal(expectedError, ex.Description);
}
}

[Theory]
[InlineData("script", "(class { x = await })", EcmaVersion.Latest, null)]
[InlineData("module", "(class { x = await })", EcmaVersion.Latest, "Unexpected reserved word")]
[InlineData("script", "(class { x = await 1 })", EcmaVersion.Latest, "await is only valid in async functions and the top level bodies of modules")]
[InlineData("module", "(class { x = await 1 })", EcmaVersion.Latest, "Unexpected reserved word")]

[InlineData("script", "(class { x = () => await })", EcmaVersion.Latest, null)]
[InlineData("module", "(class { x = () => await })", EcmaVersion.Latest, "Unexpected reserved word")]
[InlineData("script", "(class { x = () => await 1 })", EcmaVersion.Latest, "await is only valid in async functions and the top level bodies of modules")]
[InlineData("module", "(class { x = () => await 1 })", EcmaVersion.Latest, "Unexpected reserved word")]

[InlineData("script", "(class { x = async () => await })", EcmaVersion.Latest, "Unexpected token '}'")]
[InlineData("module", "(class { x = async () => await })", EcmaVersion.Latest, "Unexpected token '}'")]
[InlineData("script", "(class { x = async () => await 1 })", EcmaVersion.Latest, null)]
[InlineData("module", "(class { x = async () => await 1 })", EcmaVersion.Latest, null)]

[InlineData("script", "() => class { x = await }", EcmaVersion.Latest, null)]
[InlineData("module", "() => class { x = await }", EcmaVersion.Latest, "Unexpected reserved word")]
[InlineData("script", "() => class { x = await 1 }", EcmaVersion.Latest, "await is only valid in async functions and the top level bodies of modules")]
[InlineData("module", "() => class { x = await 1 }", EcmaVersion.Latest, "Unexpected reserved word")]

[InlineData("script", "() => class { x = () => await }", EcmaVersion.Latest, null)]
[InlineData("module", "() => class { x = () => await }", EcmaVersion.Latest, "Unexpected reserved word")]
[InlineData("script", "() => class { x = () => await 1 }", EcmaVersion.Latest, "await is only valid in async functions and the top level bodies of modules")]
[InlineData("module", "() => class { x = () => await 1 }", EcmaVersion.Latest, "Unexpected reserved word")]

[InlineData("script", "() => class { x = async () => await }", EcmaVersion.Latest, "Unexpected token '}'")]
[InlineData("module", "() => class { x = async () => await }", EcmaVersion.Latest, "Unexpected token '}'")]
[InlineData("script", "() => class { x = async () => await 1 }", EcmaVersion.Latest, null)]
[InlineData("module", "() => class { x = async () => await 1 }", EcmaVersion.Latest, null)]

[InlineData("script", "async () => class { x = await }", EcmaVersion.Latest, null)]
[InlineData("module", "async () => class { x = await }", EcmaVersion.Latest, "Unexpected reserved word")]
[InlineData("script", "async () => class { x = await 1 }", EcmaVersion.Latest, "Unexpected number")]
[InlineData("module", "async () => class { x = await 1 }", EcmaVersion.Latest, "Unexpected reserved word")]

[InlineData("script", "async () => class { x = () => await }", EcmaVersion.Latest, null)]
[InlineData("module", "async () => class { x = () => await }", EcmaVersion.Latest, "Unexpected reserved word")]
[InlineData("script", "async () => class { x = () => await 1 }", EcmaVersion.Latest, "Unexpected number")]
[InlineData("module", "async () => class { x = () => await 1 }", EcmaVersion.Latest, "Unexpected reserved word")]

[InlineData("script", "async () => class { x = async () => await }", EcmaVersion.Latest, "Unexpected token '}'")]
[InlineData("module", "async () => class { x = async () => await }", EcmaVersion.Latest, "Unexpected token '}'")]
[InlineData("script", "async () => class { x = async () => await 1 }", EcmaVersion.Latest, null)]
[InlineData("module", "async () => class { x = async () => await 1 }", EcmaVersion.Latest, null)]

[InlineData("script", "async () => class { x = (a = await) => a }", EcmaVersion.Latest, null)]
[InlineData("module", "async () => class { x = (a = await) => a }", EcmaVersion.Latest, "Unexpected reserved word")]
[InlineData("script", "async () => class { x = (a = await 1) => a }", EcmaVersion.Latest, "Unexpected number")]
[InlineData("module", "async () => class { x = (a = await 1) => a }", EcmaVersion.Latest, "Unexpected reserved word")]

[InlineData("script", "async () => class { x = class await { y = await } }", EcmaVersion.Latest, null)]
[InlineData("module", "async () => class { x = class await { y = await } }", EcmaVersion.Latest, "Unexpected reserved word")]
[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")]
[InlineData("module", "async () => class { x = class await { y = await 1 } }", EcmaVersion.Latest, "Unexpected reserved word")]

[InlineData("script", "async () => class { x = () => { { try {} catch (await) { } } } }", EcmaVersion.Latest, null)]
[InlineData("module", "async () => class { x = () => { { try {} catch (await) { } } } }", EcmaVersion.Latest, "Unexpected reserved word")]
[InlineData("script", "async () => class { x = () => { { try {} catch { var await = 1 } } } }", EcmaVersion.Latest, null)]
[InlineData("module", "async () => class { x = () => { { try {} catch { var await = 1 } } } }", EcmaVersion.Latest, "Unexpected reserved word")]
public void ShouldHandleAwaitInClassFieldInitializer(string sourceType, string input, EcmaVersion ecmaVersion, string? expectedError)
{
// See also: https://github.com/acornjs/acorn/issues/1334, https://github.com/acornjs/acorn/issues/1338

var parser = new Parser(new ParserOptions { EcmaVersion = ecmaVersion });
var parseAction = GetParseActionFor(sourceType);

if (expectedError is null)
{
Assert.NotNull(parseAction(parser, input));
}
else
{
var ex = Assert.Throws<SyntaxErrorException>(() => parseAction(parser, input));
Assert.Equal(expectedError, ex.Description);
}
}

[Theory]
[InlineData("script", "await", EcmaVersion.Latest, null)]
[InlineData("script", "await", EcmaVersion.ES13, null)]
Expand Down

0 comments on commit 221116b

Please sign in to comment.