diff --git a/src/Acornima/Parser.State.cs b/src/Acornima/Parser.State.cs
index aa4f0f6..a31d15e 100644
--- a/src/Acornima/Parser.State.cs
+++ b/src/Acornima/Parser.State.cs
@@ -105,7 +105,16 @@ internal void Reset(string input, int start, int length, SourceType sourceType,
_scopeId = 0;
_scopeStack.Clear();
- EnterScope(ScopeFlags.Top);
+
+ // NOTE: Seeding the root scope with the super flags (as opposed to just short-circuiting the `AllowSuper`/`AllowDirectSuper` getters)
+ // makes both options follow the this binding: as `EnterScope` propagates the current this scope through arrow function scopes
+ // but not through ordinary function scopes, super is allowed exactly where the top level's this binding is in effect.
+ var superFlags =
+ _options._allowSuperCallOutsideConstructor ? ScopeFlags.Super | ScopeFlags.DirectSuper
+ : _options._allowSuperOutsideMethod ? ScopeFlags.Super
+ : ScopeFlags.None;
+
+ EnterScope(ScopeFlags.Top | superFlags);
_privateNameStack.Clear();
@@ -285,7 +294,7 @@ private bool AllowSuper
// https://github.com/acornjs/acorn/blob/8.11.3/acorn/src/state.js > `get allowSuper`
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- get => _options._allowSuperOutsideMethod || (CurrentThisScope._flags & (ScopeFlags.Super | ScopeFlags.InClassFieldInit)) != 0;
+ get => (CurrentThisScope._flags & (ScopeFlags.Super | ScopeFlags.InClassFieldInit)) != 0;
}
private bool AllowDirectSuper
diff --git a/src/Acornima/ParserOptions.cs b/src/Acornima/ParserOptions.cs
index d783d76..4d34b67 100644
--- a/src/Acornima/ParserOptions.cs
+++ b/src/Acornima/ParserOptions.cs
@@ -40,6 +40,7 @@ protected ParserOptions(ParserOptions original)
_allowAwaitOutsideFunction = original._allowAwaitOutsideFunction;
_allowNewTargetOutsideFunction = original._allowNewTargetOutsideFunction;
_allowSuperOutsideMethod = original._allowSuperOutsideMethod;
+ _allowSuperCallOutsideConstructor = original._allowSuperCallOutsideConstructor;
_checkPrivateFields = original._checkPrivateFields;
_onInsertedSemicolon = original._onInsertedSemicolon;
_onTrailingComma = original._onTrailingComma;
@@ -119,15 +120,43 @@ public ExperimentalESFeatures ExperimentalESFeatures
/// Gets or sets whether to allow new.target meta-properties in the top-level scope.
/// Defaults to .
///
+ ///
+ /// This option is intended for parsing code which is evaluated in the context of a function
+ /// (see PerformEval and its inFunc parameter).
+ /// Enabling it means that new.target accesses are permitted at the top level of the parsed code.
+ ///
public bool AllowNewTargetOutsideFunction { get => _allowNewTargetOutsideFunction; init => _allowNewTargetOutsideFunction = value; }
internal readonly bool _allowSuperOutsideMethod;
///
- /// Gets or sets whether to allow super identifiers to appear outside methods.
+ /// Gets or sets whether to allow super property accesses to appear outside methods.
/// Defaults to .
///
+ ///
+ /// This option is intended for parsing code which is evaluated in the context of a method
+ /// (see PerformEval and its inMethod parameter).
+ /// Enabling it means that super property accesses are permitted at the top level of the parsed code, that is,
+ /// in the same places where this refers to the top level's this binding.
+ /// (E.g. they are allowed in arrow functions declared at the top level but not in ordinary functions.)
+ /// Direct super calls remain disallowed; see for those.
+ ///
public bool AllowSuperOutsideMethod { get => _allowSuperOutsideMethod; init => _allowSuperOutsideMethod = value; }
+ internal readonly bool _allowSuperCallOutsideConstructor;
+ ///
+ /// Gets or sets whether to allow super calls to appear outside the constructor of a derived class.
+ /// Defaults to .
+ ///
+ ///
+ /// This option is intended for parsing code which is evaluated in the context of the constructor of a derived class
+ /// (see PerformEval and its inDerivedConstructor parameter).
+ /// Enabling it means that direct super calls (as well as super property accesses) are permitted at the top level
+ /// of the parsed code, that is, in the same places where this refers to the top level's this binding.
+ /// (E.g. they are allowed in arrow functions declared at the top level but not in ordinary functions.)
+ /// This option implies .
+ ///
+ public bool AllowSuperCallOutsideConstructor { get => _allowSuperCallOutsideConstructor; init => _allowSuperCallOutsideConstructor = value; }
+
internal readonly bool _allowTopLevelUsing;
///
/// Gets or sets whether to allow using declarations to appear at the top level of scripts as well.
diff --git a/test/Acornima.Tests/ParserTests.cs b/test/Acornima.Tests/ParserTests.cs
index dd9ef69..83f1c27 100644
--- a/test/Acornima.Tests/ParserTests.cs
+++ b/test/Acornima.Tests/ParserTests.cs
@@ -983,6 +983,104 @@ public void ShouldHandleSuperKeywordEdgeCases(string sourceType, string input, E
}
}
+ [Theory]
+ // Direct super calls are not allowed at the top level unless AllowSuperCallOutsideConstructor is enabled.
+ // (AllowSuperOutsideMethod alone doesn't enable them.)
+ [InlineData("script", "super()", false, false, "'super' keyword unexpected here")]
+ [InlineData("script", "super()", true, false, "'super' keyword unexpected here")]
+ [InlineData("script", "super()", false, true, null)]
+ [InlineData("script", "super()", true, true, null)]
+ [InlineData("module", "super()", false, false, "'super' keyword unexpected here")]
+ [InlineData("module", "super()", false, true, null)]
+ [InlineData("expression", "super()", false, false, "'super' keyword unexpected here")]
+ [InlineData("expression", "super()", false, true, null)]
+
+ // Arrow functions inherit the this binding of the top level, so direct super calls are allowed in them as well.
+ [InlineData("script", "(() => super())()", false, false, "'super' keyword unexpected here")]
+ [InlineData("script", "(() => super())()", false, true, null)]
+ [InlineData("script", "() => () => super()", false, true, null)]
+ [InlineData("script", "async () => super()", false, true, null)]
+ // (The argument of the nested eval call is just a string literal to the parser. When the host parses that string,
+ // the top level cases above apply to it.)
+ [InlineData("script", "(() => eval('super()'))()", false, false, null)]
+ [InlineData("script", "(() => eval('super()'))()", false, true, null)]
+
+ // Ordinary functions introduce a this binding of their own, so direct super calls remain disallowed in them.
+ [InlineData("script", "function f() { super() }", false, true, "'super' keyword unexpected here")]
+ [InlineData("script", "function f() { super() }", true, true, "'super' keyword unexpected here")]
+ [InlineData("script", "(function () { super() })", false, true, "'super' keyword unexpected here")]
+ [InlineData("script", "() => function () { super() }", false, true, "'super' keyword unexpected here")]
+ [InlineData("script", "({ m() { super() } })", false, true, "'super' keyword unexpected here")]
+
+ // Super property accesses are allowed wherever direct super calls are, and AllowSuperOutsideMethod allows
+ // exactly those, without allowing direct super calls.
+ [InlineData("script", "super.x", false, false, "'super' keyword unexpected here")]
+ [InlineData("script", "super.x", true, false, null)]
+ [InlineData("script", "super.x", false, true, null)]
+ [InlineData("script", "(() => super.x)()", false, true, null)]
+ [InlineData("script", "(() => super.x)()", true, false, null)]
+ // (Both options follow the top level's this binding, so neither of them reaches into an ordinary function.)
+ [InlineData("script", "function f() { super.x }", false, true, "'super' keyword unexpected here")]
+ [InlineData("script", "function f() { super.x }", true, false, "'super' keyword unexpected here")]
+ [InlineData("script", "function f() { super.x }", true, true, "'super' keyword unexpected here")]
+ // (Methods bring a home object of their own, so they are unaffected by either option.)
+ [InlineData("script", "({ m() { super.x } })", false, false, null)]
+ [InlineData("script", "({ m() { super.x } })", true, false, null)]
+
+ // Classes are unaffected: the constructor of a derived class remains the only place where direct super calls are allowed.
+ [InlineData("script", "class A extends B { constructor() { super() } }", false, false, null)]
+ [InlineData("script", "class A extends B { constructor() { super() } }", false, true, null)]
+ [InlineData("script", "class A { constructor() { super() } }", false, false, "'super' keyword unexpected here")]
+ [InlineData("script", "class A { constructor() { super() } }", false, true, "'super' keyword unexpected here")]
+ [InlineData("script", "class A extends B { m() { super() } }", false, true, "'super' keyword unexpected here")]
+ [InlineData("script", "class A extends B { constructor() { function f() { super() } } }", false, true, "'super' keyword unexpected here")]
+ [InlineData("script", "class C { x = super.y }", false, false, null)]
+ [InlineData("script", "class C { x = super.y }", false, true, null)]
+ // (Class field initializers don't introduce a this binding of their own, but they are never a place for a direct
+ // super call either: https://tc39.es/ecma262/#sec-class-definitions-static-semantics-early-errors makes it a
+ // Syntax Error if the Initializer Contains SuperCall. So neither the constructor of a derived class nor the
+ // option enables one there.)
+ [InlineData("script", "class A extends B { constructor() { class C { x = super() } } }", false, false, "'super' keyword unexpected here")]
+ [InlineData("script", "class C { x = super() }", false, false, "'super' keyword unexpected here")]
+ [InlineData("script", "class C { x = super() }", false, true, "'super' keyword unexpected here")]
+ // (A computed class element name, on the other hand, is evaluated in the enclosing scope, so it inherits
+ // whatever that scope allows.)
+ [InlineData("script", "class C { [super()]() { } }", false, false, "'super' keyword unexpected here")]
+ [InlineData("script", "class C { [super()]() { } }", false, true, null)]
+ public void ShouldHandleSuperCallOutsideConstructor(string sourceType, string input, bool allowSuperOutsideMethod, bool allowSuperCallOutsideConstructor, string? expectedError)
+ {
+ var parser = new Parser(new ParserOptions
+ {
+ AllowSuperOutsideMethod = allowSuperOutsideMethod,
+ AllowSuperCallOutsideConstructor = allowSuperCallOutsideConstructor,
+ });
+ var parseAction = GetParseActionFor(sourceType);
+
+ if (expectedError is null)
+ {
+ Assert.NotNull(parseAction(parser, input));
+ }
+ else
+ {
+ var ex = Assert.Throws(() => parseAction(parser, input));
+ Assert.Equal(expectedError, ex.Description);
+ }
+ }
+
+ [Fact]
+ public void AllowSuperCallOutsideConstructorShouldDefaultToFalse()
+ {
+ Assert.False(new ParserOptions().AllowSuperCallOutsideConstructor);
+ Assert.False(ParserOptions.Default.AllowSuperCallOutsideConstructor);
+
+ var options = ParserOptions.Default with { AllowSuperCallOutsideConstructor = true };
+ Assert.True(options.AllowSuperCallOutsideConstructor);
+ Assert.False(ParserOptions.Default.AllowSuperCallOutsideConstructor);
+
+ // The option must survive further copies of the options object.
+ Assert.True((options with { EcmaVersion = EcmaVersion.ES2022 }).AllowSuperCallOutsideConstructor);
+ }
+
[Theory]
[InlineData("script", "(class { x = await })", EcmaVersion.Latest, null)]
[InlineData("module", "(class { x = await })", EcmaVersion.Latest, "Unexpected reserved word")]