diff --git a/src/Esprima.SourceGenerators/Esprima.SourceGenerators.csproj b/src/Esprima.SourceGenerators/Esprima.SourceGenerators.csproj
index 5dfd28a0..5be2b0b2 100644
--- a/src/Esprima.SourceGenerators/Esprima.SourceGenerators.csproj
+++ b/src/Esprima.SourceGenerators/Esprima.SourceGenerators.csproj
@@ -15,6 +15,7 @@
+
diff --git a/src/Esprima/Ast/ArrayExpression.cs b/src/Esprima/Ast/ArrayExpression.cs
index 39a22c49..2f8055c8 100644
--- a/src/Esprima/Ast/ArrayExpression.cs
+++ b/src/Esprima/Ast/ArrayExpression.cs
@@ -12,6 +12,9 @@ public ArrayExpression(in NodeList elements) : base(Nodes.ArrayExpr
_elements = elements;
}
+ ///
+ /// { (incl. ) | }
+ ///
public ref readonly NodeList Elements { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _elements; }
internal override Node? NextChildNode(ref ChildNodes.Enumerator enumerator) => enumerator.MoveNextNullable(Elements);
diff --git a/src/Esprima/Ast/ArrayPattern.cs b/src/Esprima/Ast/ArrayPattern.cs
index 2726f3dc..ed6f5535 100644
--- a/src/Esprima/Ast/ArrayPattern.cs
+++ b/src/Esprima/Ast/ArrayPattern.cs
@@ -12,6 +12,9 @@ public ArrayPattern(in NodeList elements) : base(Nodes.ArrayPattern
_elements = elements;
}
+ ///
+ /// { | | | | }
+ ///
public ref readonly NodeList Elements { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _elements; }
internal override Node? NextChildNode(ref ChildNodes.Enumerator enumerator) => enumerator.MoveNextNullable(Elements);
diff --git a/src/Esprima/Ast/ArrowFunctionExpression.cs b/src/Esprima/Ast/ArrowFunctionExpression.cs
index f0bd0073..02272851 100644
--- a/src/Esprima/Ast/ArrowFunctionExpression.cs
+++ b/src/Esprima/Ast/ArrowFunctionExpression.cs
@@ -23,9 +23,12 @@ public ArrowFunctionExpression(
}
Identifier? IFunction.Id => null;
+ ///
+ /// { | | | }
+ ///
public ref readonly NodeList Params { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _params; }
///
- /// |
+ /// |
///
public Node Body { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
bool IFunction.Generator => false;
diff --git a/src/Esprima/Ast/AssignmentExpression.cs b/src/Esprima/Ast/AssignmentExpression.cs
index bbde336c..2024ed2e 100644
--- a/src/Esprima/Ast/AssignmentExpression.cs
+++ b/src/Esprima/Ast/AssignmentExpression.cs
@@ -94,8 +94,9 @@ public static string GetAssignmentOperatorToken(AssignmentOperator op)
}
public AssignmentOperator Operator { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
+
///
- /// Can be something else than Expression (, ) in case of destructuring assignment
+ /// |
///
public Expression Left { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public Expression Right { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
diff --git a/src/Esprima/Ast/AssignmentPattern.cs b/src/Esprima/Ast/AssignmentPattern.cs
index ea9b4d95..16a61d72 100644
--- a/src/Esprima/Ast/AssignmentPattern.cs
+++ b/src/Esprima/Ast/AssignmentPattern.cs
@@ -13,6 +13,9 @@ public AssignmentPattern(Expression left, Expression right) : base(Nodes.Assignm
_right = right;
}
+ ///
+ /// |
+ ///
public Expression Left { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public Expression Right { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _right; }
diff --git a/src/Esprima/Ast/CatchClause.cs b/src/Esprima/Ast/CatchClause.cs
index 5a17b185..7037dfc2 100644
--- a/src/Esprima/Ast/CatchClause.cs
+++ b/src/Esprima/Ast/CatchClause.cs
@@ -13,7 +13,7 @@ public CatchClause(Expression? param, BlockStatement body) :
}
///
- /// BindingIdentifier | |
+ /// |
///
public Expression? Param { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public BlockStatement Body { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
diff --git a/src/Esprima/Ast/ChainExpression.cs b/src/Esprima/Ast/ChainExpression.cs
index 0b8ec339..79615016 100644
--- a/src/Esprima/Ast/ChainExpression.cs
+++ b/src/Esprima/Ast/ChainExpression.cs
@@ -11,7 +11,7 @@ public ChainExpression(Expression expression) : base(Nodes.ChainExpression)
}
///
- /// | |
+ /// | |
///
public Expression Expression { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
diff --git a/src/Esprima/Ast/ClassBody.cs b/src/Esprima/Ast/ClassBody.cs
index cd435a86..b5a857f8 100644
--- a/src/Esprima/Ast/ClassBody.cs
+++ b/src/Esprima/Ast/ClassBody.cs
@@ -13,7 +13,7 @@ public ClassBody(in NodeList body) : base(Nodes.ClassBody)
}
///
- /// | |
+ /// | |
///
public ref readonly NodeList Body { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _body; }
diff --git a/src/Esprima/Ast/ClassDeclaration.cs b/src/Esprima/Ast/ClassDeclaration.cs
index a9691e5a..7a15b29e 100644
--- a/src/Esprima/Ast/ClassDeclaration.cs
+++ b/src/Esprima/Ast/ClassDeclaration.cs
@@ -18,7 +18,7 @@ public ClassDeclaration(Identifier? id, Expression? superClass, ClassBody body,
public Identifier? Id { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
///
- /// |
+ /// |
///
public Expression? SuperClass { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public ClassBody Body { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
diff --git a/src/Esprima/Ast/ClassExpression.cs b/src/Esprima/Ast/ClassExpression.cs
index e163ee6d..dbfb34b2 100644
--- a/src/Esprima/Ast/ClassExpression.cs
+++ b/src/Esprima/Ast/ClassExpression.cs
@@ -21,7 +21,7 @@ public ClassExpression(
public Identifier? Id { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
///
- /// |
+ /// |
///
public Expression? SuperClass { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public ClassBody Body { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
diff --git a/src/Esprima/Ast/ClassProperty.cs b/src/Esprima/Ast/ClassProperty.cs
index 3b58eea2..567895b8 100644
--- a/src/Esprima/Ast/ClassProperty.cs
+++ b/src/Esprima/Ast/ClassProperty.cs
@@ -13,7 +13,7 @@ protected ClassProperty(Nodes type, PropertyKind kind, Expression key, bool comp
public PropertyKind Kind { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
///
- /// | | '[' ']'
+ /// | (string or numeric) | '[' ']' |
///
public Expression Key { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public bool Computed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
diff --git a/src/Esprima/Ast/ExportAllDeclaration.cs b/src/Esprima/Ast/ExportAllDeclaration.cs
index 49119e88..d14d6e07 100644
--- a/src/Esprima/Ast/ExportAllDeclaration.cs
+++ b/src/Esprima/Ast/ExportAllDeclaration.cs
@@ -20,7 +20,7 @@ public ExportAllDeclaration(Literal source, Expression? exported, in NodeList
- /// | StringLiteral ()
+ /// | (string)
///
public Expression? Exported { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public ref readonly NodeList Assertions { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _assertions; }
diff --git a/src/Esprima/Ast/ExportDefaultDeclaration.cs b/src/Esprima/Ast/ExportDefaultDeclaration.cs
index 8ecdfe8d..2c2c3588 100644
--- a/src/Esprima/Ast/ExportDefaultDeclaration.cs
+++ b/src/Esprima/Ast/ExportDefaultDeclaration.cs
@@ -11,7 +11,7 @@ public ExportDefaultDeclaration(StatementListItem declaration) : base(Nodes.Expo
}
///
- /// BindingIdentifier | | | |
+ /// | |
///
public StatementListItem Declaration { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
diff --git a/src/Esprima/Ast/ExportNamedDeclaration.cs b/src/Esprima/Ast/ExportNamedDeclaration.cs
index fb7d4bd9..ebccf22b 100644
--- a/src/Esprima/Ast/ExportNamedDeclaration.cs
+++ b/src/Esprima/Ast/ExportNamedDeclaration.cs
@@ -21,6 +21,9 @@ public ExportNamedDeclaration(
_assertions = assertions;
}
+ ///
+ /// | |
+ ///
public StatementListItem? Declaration { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public ref readonly NodeList Specifiers { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _specifiers; }
public Literal? Source { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
diff --git a/src/Esprima/Ast/ExportSpecifier.cs b/src/Esprima/Ast/ExportSpecifier.cs
index 52d01c38..19bf0af1 100644
--- a/src/Esprima/Ast/ExportSpecifier.cs
+++ b/src/Esprima/Ast/ExportSpecifier.cs
@@ -12,11 +12,11 @@ public ExportSpecifier(Expression local, Expression exported) : base(Nodes.Expor
}
///
- /// | StringLiteral ()
+ /// | (string)
///
public Expression Local { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
///
- /// |
+ /// | (string)
///
public Expression Exported { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
diff --git a/src/Esprima/Ast/ForInStatement.cs b/src/Esprima/Ast/ForInStatement.cs
index 241c1563..9afcc487 100644
--- a/src/Esprima/Ast/ForInStatement.cs
+++ b/src/Esprima/Ast/ForInStatement.cs
@@ -15,6 +15,9 @@ public ForInStatement(
Body = body;
}
+ ///
+ /// (may have an initializer in non-strict mode) | |
+ ///
public Node Left { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public Expression Right { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public Statement Body { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
diff --git a/src/Esprima/Ast/ForOfStatement.cs b/src/Esprima/Ast/ForOfStatement.cs
index 5723a7c6..45944e9a 100644
--- a/src/Esprima/Ast/ForOfStatement.cs
+++ b/src/Esprima/Ast/ForOfStatement.cs
@@ -17,6 +17,9 @@ public ForOfStatement(
Await = await;
}
+ ///
+ /// (cannot have an initializer) | |
+ ///
public Node Left { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public Expression Right { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public Statement Body { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
diff --git a/src/Esprima/Ast/ForStatement.cs b/src/Esprima/Ast/ForStatement.cs
index 46bf441f..6872cd58 100644
--- a/src/Esprima/Ast/ForStatement.cs
+++ b/src/Esprima/Ast/ForStatement.cs
@@ -19,7 +19,7 @@ public ForStatement(
}
///
- /// (var i) | (i=0)
+ /// (var i) | (i=0)
///
public StatementListItem? Init { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public Expression? Test { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
diff --git a/src/Esprima/Ast/FunctionDeclaration.cs b/src/Esprima/Ast/FunctionDeclaration.cs
index 3bc0eb11..ad69a8ea 100644
--- a/src/Esprima/Ast/FunctionDeclaration.cs
+++ b/src/Esprima/Ast/FunctionDeclaration.cs
@@ -25,6 +25,9 @@ public FunctionDeclaration(
}
public Identifier? Id { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
+ ///
+ /// { | | | }
+ ///
public ref readonly NodeList Params { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _params; }
public BlockStatement Body { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
diff --git a/src/Esprima/Ast/FunctionExpression.cs b/src/Esprima/Ast/FunctionExpression.cs
index 4b1abf41..703532e4 100644
--- a/src/Esprima/Ast/FunctionExpression.cs
+++ b/src/Esprima/Ast/FunctionExpression.cs
@@ -25,6 +25,9 @@ public FunctionExpression(
}
public Identifier? Id { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
+ ///
+ /// { | | | }
+ ///
public ref readonly NodeList Params { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _params; }
public BlockStatement Body { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
diff --git a/src/Esprima/Ast/IClass.cs b/src/Esprima/Ast/IClass.cs
index 453b64d7..cecda0e8 100644
--- a/src/Esprima/Ast/IClass.cs
+++ b/src/Esprima/Ast/IClass.cs
@@ -5,6 +5,7 @@
///
public interface IClass
{
+ Nodes Type { get; }
Identifier? Id { get; }
Expression? SuperClass { get; }
ClassBody Body { get; }
diff --git a/src/Esprima/Ast/IFunction.cs b/src/Esprima/Ast/IFunction.cs
index 88d99920..4ecf3271 100644
--- a/src/Esprima/Ast/IFunction.cs
+++ b/src/Esprima/Ast/IFunction.cs
@@ -5,6 +5,7 @@
///
public interface IFunction
{
+ Nodes Type { get; }
Identifier? Id { get; }
ref readonly NodeList Params { get; }
Node Body { get; }
diff --git a/src/Esprima/Ast/IProperty.cs b/src/Esprima/Ast/IProperty.cs
index 9b9b4811..0fdecd41 100644
--- a/src/Esprima/Ast/IProperty.cs
+++ b/src/Esprima/Ast/IProperty.cs
@@ -2,6 +2,7 @@
{
public interface IProperty
{
+ Nodes Type { get; }
PropertyKind Kind { get; }
Expression Key { get; }
bool Computed { get; }
diff --git a/src/Esprima/Ast/Identifier.cs b/src/Esprima/Ast/Identifier.cs
index 8d654f36..56eabb5d 100644
--- a/src/Esprima/Ast/Identifier.cs
+++ b/src/Esprima/Ast/Identifier.cs
@@ -1,10 +1,8 @@
-using System.Diagnostics;
-using System.Runtime.CompilerServices;
+using System.Runtime.CompilerServices;
using Esprima.Utils;
namespace Esprima.Ast
{
- [DebuggerDisplay("{Name,nq}")]
public sealed class Identifier : Expression
{
public Identifier(string? name) : base(Nodes.Identifier)
diff --git a/src/Esprima/Ast/ImportAttribute.cs b/src/Esprima/Ast/ImportAttribute.cs
index 30e41b7a..95956995 100644
--- a/src/Esprima/Ast/ImportAttribute.cs
+++ b/src/Esprima/Ast/ImportAttribute.cs
@@ -12,7 +12,7 @@ public ImportAttribute(Expression key, Literal value) : base(Nodes.ImportAttribu
}
///
- /// |
+ /// |
///
public Expression Key { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public Literal Value { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
diff --git a/src/Esprima/Ast/ImportSpecifier.cs b/src/Esprima/Ast/ImportSpecifier.cs
index 91cfd18e..3e1f40e9 100644
--- a/src/Esprima/Ast/ImportSpecifier.cs
+++ b/src/Esprima/Ast/ImportSpecifier.cs
@@ -11,7 +11,7 @@ public ImportSpecifier(Identifier local, Expression imported) : base(local, Node
}
///
- /// | StringLiteral ()
+ /// | (string)
///
public Expression Imported { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
diff --git a/src/Esprima/Ast/Literal.cs b/src/Esprima/Ast/Literal.cs
index 271d31fe..99d083fc 100644
--- a/src/Esprima/Ast/Literal.cs
+++ b/src/Esprima/Ast/Literal.cs
@@ -1,12 +1,10 @@
-using System.Diagnostics;
-using System.Numerics;
+using System.Numerics;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using Esprima.Utils;
namespace Esprima.Ast
{
- [DebuggerDisplay("{Raw,nq}")]
public sealed class Literal : Expression
{
internal Literal(TokenType tokenType, object? value, string raw) : base(Nodes.Literal)
diff --git a/src/Esprima/Ast/Node.cs b/src/Esprima/Ast/Node.cs
index 020eee43..2a5d7290 100644
--- a/src/Esprima/Ast/Node.cs
+++ b/src/Esprima/Ast/Node.cs
@@ -1,8 +1,10 @@
-using System.Runtime.CompilerServices;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
using Esprima.Utils;
namespace Esprima.Ast
{
+ [DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(), nq}}")]
public abstract class Node
{
protected Node(Nodes type)
@@ -43,5 +45,12 @@ protected Node(Nodes type)
{
return visitor.VisitExtension(this);
}
+
+ public override string ToString() => this.ToJavascriptString(beautify: true);
+
+ private string GetDebuggerDisplay()
+ {
+ return $"/*{Type}*/ {this}";
+ }
}
}
diff --git a/src/Esprima/Ast/NodeExtensions.cs b/src/Esprima/Ast/NodeExtensions.cs
index 54287624..7f2eb388 100644
--- a/src/Esprima/Ast/NodeExtensions.cs
+++ b/src/Esprima/Ast/NodeExtensions.cs
@@ -1,4 +1,5 @@
using System.Diagnostics;
+using System.Runtime.CompilerServices;
using static Esprima.EsprimaExceptionHelper;
using NodeSysList = System.Collections.Generic.List;
@@ -7,6 +8,7 @@ namespace Esprima.Ast
public static class NodeExtensions
{
[DebuggerStepThrough]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T As(this Node node) where T : Node
{
return (T) node;
diff --git a/src/Esprima/Ast/NodeList.cs b/src/Esprima/Ast/NodeList.cs
index 41f18801..33b6a2ac 100644
--- a/src/Esprima/Ast/NodeList.cs
+++ b/src/Esprima/Ast/NodeList.cs
@@ -32,11 +32,18 @@ public int Count
get => _count;
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public NodeList AsNodes()
{
return new NodeList(_items /* conversion by co-variance! */, _count);
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public NodeList As() where TTo : Node?
+ {
+ return new NodeList((TTo[]?) (object?) _items, _count);
+ }
+
public T this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -203,6 +210,7 @@ var count
}
}
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool AreSame(in NodeList nodeList1, in NodeList nodeList2) where T : Node?
{
return nodeList1._items == nodeList2._items;
diff --git a/src/Esprima/Ast/ObjectExpression.cs b/src/Esprima/Ast/ObjectExpression.cs
index d838a9f4..e600e033 100644
--- a/src/Esprima/Ast/ObjectExpression.cs
+++ b/src/Esprima/Ast/ObjectExpression.cs
@@ -12,6 +12,9 @@ public ObjectExpression(in NodeList properties) : base(Nodes.ObjectE
_properties = properties;
}
+ ///
+ /// { | }
+ ///
public ref readonly NodeList Properties { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _properties; }
internal override Node? NextChildNode(ref ChildNodes.Enumerator enumerator) => enumerator.MoveNext(Properties);
diff --git a/src/Esprima/Ast/ObjectPattern.cs b/src/Esprima/Ast/ObjectPattern.cs
index b8d3c5be..1025262e 100644
--- a/src/Esprima/Ast/ObjectPattern.cs
+++ b/src/Esprima/Ast/ObjectPattern.cs
@@ -12,6 +12,9 @@ public ObjectPattern(in NodeList properties) : base(Nodes.ObjectPattern)
_properties = properties;
}
+ ///
+ /// { | }
+ ///
public ref readonly NodeList Properties { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _properties; }
internal override Node? NextChildNode(ref ChildNodes.Enumerator enumerator) => enumerator.MoveNext(Properties);
diff --git a/src/Esprima/Ast/Property.cs b/src/Esprima/Ast/Property.cs
index 51e5e77a..0a9ba311 100644
--- a/src/Esprima/Ast/Property.cs
+++ b/src/Esprima/Ast/Property.cs
@@ -26,11 +26,15 @@ public Property(
public PropertyKind Kind { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
///
- /// | | '[' ']'
+ /// | (string or numeric) | '[' ']'
///
public Expression Key { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public bool Computed { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
+ ///
+ /// When property of an object literal: (incl. and for getters/setters/methods)
+ /// When property of an object binding pattern: | | |
+ ///
public Expression Value { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _value; }
Expression? IProperty.Value => Value;
diff --git a/src/Esprima/Ast/RestElement.cs b/src/Esprima/Ast/RestElement.cs
index bfc525bc..36604083 100644
--- a/src/Esprima/Ast/RestElement.cs
+++ b/src/Esprima/Ast/RestElement.cs
@@ -11,7 +11,7 @@ public RestElement(Expression argument) : base(Nodes.RestElement)
}
///
- /// BindingIdentifier |
+ /// |
///
public Expression Argument { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
diff --git a/src/Esprima/Ast/VariableDeclaration.cs b/src/Esprima/Ast/VariableDeclaration.cs
index 06c24b87..e8b40799 100644
--- a/src/Esprima/Ast/VariableDeclaration.cs
+++ b/src/Esprima/Ast/VariableDeclaration.cs
@@ -1,10 +1,22 @@
using System.Runtime.CompilerServices;
using Esprima.Utils;
+using static Esprima.EsprimaExceptionHelper;
namespace Esprima.Ast
{
public sealed class VariableDeclaration : Declaration
{
+ public static string GetVariableDeclarationKindToken(VariableDeclarationKind kind)
+ {
+ return kind switch
+ {
+ VariableDeclarationKind.Var => "var",
+ VariableDeclarationKind.Let => "let",
+ VariableDeclarationKind.Const => "const",
+ _ => ThrowArgumentOutOfRangeException(nameof(kind), "Invalid variable declaration kind: " + kind)
+ };
+ }
+
private readonly NodeList _declarations;
public VariableDeclaration(
diff --git a/src/Esprima/Ast/VariableDeclarator.cs b/src/Esprima/Ast/VariableDeclarator.cs
index ad66128d..4df78d07 100644
--- a/src/Esprima/Ast/VariableDeclarator.cs
+++ b/src/Esprima/Ast/VariableDeclarator.cs
@@ -13,7 +13,7 @@ public VariableDeclarator(Expression id, Expression? init) :
}
///
- /// BindingIdentifier |
+ /// |
///
public Expression Id { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
public Expression? Init { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
diff --git a/src/Esprima/Esprima.csproj b/src/Esprima/Esprima.csproj
index 19e4d566..00461454 100644
--- a/src/Esprima/Esprima.csproj
+++ b/src/Esprima/Esprima.csproj
@@ -37,6 +37,7 @@
+
diff --git a/src/Esprima/EsprimaExceptionHelper.cs b/src/Esprima/EsprimaExceptionHelper.cs
index 7f1f1164..c0abe03d 100644
--- a/src/Esprima/EsprimaExceptionHelper.cs
+++ b/src/Esprima/EsprimaExceptionHelper.cs
@@ -1,37 +1,49 @@
-namespace Esprima
+using System.Diagnostics.CodeAnalysis;
+
+namespace Esprima
{
+ ///
+ /// JIT cannot inline methods that have in them. These helper methods allow us to work around this.
+ ///
internal static class EsprimaExceptionHelper
{
+ [DoesNotReturn]
public static void ThrowIndexOutOfRangeException()
{
throw new IndexOutOfRangeException();
}
+ [DoesNotReturn]
public static T ThrowArgumentOutOfRangeException(string paramName, object actualValue, string? message = null)
{
throw new ArgumentOutOfRangeException(paramName, actualValue, message);
}
+ [DoesNotReturn]
public static void ThrowArgumentOutOfRangeException(string paramName, object actualValue, string? message = null)
{
throw new ArgumentOutOfRangeException(paramName, actualValue, message);
}
+ [DoesNotReturn]
public static T ThrowInvalidOperationException(string? message = null)
{
throw new InvalidOperationException(message);
}
+ [DoesNotReturn]
public static void ThrowInvalidOperationException(string? message = null)
{
throw new InvalidOperationException(message);
}
+ [DoesNotReturn]
public static void ThrowArgumentNullException(string message)
{
throw new ArgumentNullException(message);
}
+ [DoesNotReturn]
public static T ThrowArgumentNullException(string message)
{
throw new ArgumentNullException(message);
diff --git a/src/Esprima/Scanner.cs b/src/Esprima/Scanner.cs
index 3b8e32f8..0e45c5e9 100644
--- a/src/Esprima/Scanner.cs
+++ b/src/Esprima/Scanner.cs
@@ -1800,7 +1800,7 @@ public Regex ParseRegex(string pattern, string flags, TimeSpan matchTimeout)
var index = 0;
var newPattern = tmp;
- if (options.HasFlag(RegexOptions.Multiline))
+ if ((options & RegexOptions.Multiline) == RegexOptions.Multiline)
{
while ((index = newPattern.IndexOf("$", index, StringComparison.Ordinal)) != -1)
{
diff --git a/src/Esprima/Utils/AstJson.cs b/src/Esprima/Utils/AstJson.cs
deleted file mode 100644
index c9ad5562..00000000
--- a/src/Esprima/Utils/AstJson.cs
+++ /dev/null
@@ -1,1295 +0,0 @@
-using System.Collections;
-using System.Globalization;
-using System.Numerics;
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Text.RegularExpressions;
-using Esprima.Ast;
-using static Esprima.EsprimaExceptionHelper;
-
-namespace Esprima.Utils;
-
-public enum LocationMembersPlacement
-{
- End,
- Start
-}
-
-public static class AstJson
-{
- public sealed class Options
- {
- public static readonly Options Default = new();
-
- public bool IncludingLineColumn { get; private set; }
- public bool IncludingRange { get; private set; }
- public LocationMembersPlacement LocationMembersPlacement { get; private set; }
- ///
- /// This switch is intended for enabling a compatibility mode for to build a JSON output
- /// which matches the format of the test fixtures of the original Esprima project.
- ///
- internal bool TestCompatibilityMode { get; private set; }
-
- public Options() { }
-
- private Options(Options options)
- {
- IncludingLineColumn = options.IncludingLineColumn;
- IncludingRange = options.IncludingRange;
- LocationMembersPlacement = options.LocationMembersPlacement;
- }
-
- public Options WithIncludingLineColumn(bool value)
- {
- return value == IncludingLineColumn ? this : new Options(this) { IncludingLineColumn = value };
- }
-
- public Options WithIncludingRange(bool value)
- {
- return value == IncludingRange ? this : new Options(this) { IncludingRange = value };
- }
-
- public Options WithLocationMembersPlacement(LocationMembersPlacement value)
- {
- return value == LocationMembersPlacement ? this : new Options(this) { LocationMembersPlacement = value };
- }
-
- internal Options WithTestCompatibilityMode(bool value)
- {
- return value == TestCompatibilityMode ? this : new Options(this) { TestCompatibilityMode = value };
- }
- }
-
- public interface IConverter
- {
- void WriteJson(Node node, JsonWriter writer, Options options);
- }
-
- public static string ToJsonString(this Node node, IConverter? converter = null)
- {
- return ToJsonString(node, indent: null, converter);
- }
-
- public static string ToJsonString(this Node node, string? indent, IConverter? converter = null)
- {
- return ToJsonString(node, Options.Default, indent, converter);
- }
-
- public static string ToJsonString(this Node node, Options options, IConverter? converter = null)
- {
- return ToJsonString(node, options, null, converter);
- }
-
- public static string ToJsonString(this Node node, Options options, string? indent, IConverter? converter = null)
- {
- using (var writer = new StringWriter())
- {
- WriteJson(node, writer, options, indent, converter);
- return writer.ToString();
- }
- }
-
- public static void WriteJson(this Node node, TextWriter writer, IConverter? converter = null)
- {
- WriteJson(node, writer, indent: null, converter);
- }
-
- public static void WriteJson(this Node node, TextWriter writer, string? indent, IConverter? converter = null)
- {
- WriteJson(node, writer, Options.Default, indent, converter);
- }
-
- public static void WriteJson(this Node node, TextWriter writer, Options options, IConverter? converter = null)
- {
- WriteJson(node, writer, options, null, converter);
- }
-
- public static void WriteJson(this Node node, TextWriter writer, Options options, string? indent, IConverter? converter = null)
- {
- if (node == null)
- {
- ThrowArgumentNullException(nameof(node));
- return;
- }
-
- if (writer == null)
- {
- ThrowArgumentNullException(nameof(writer));
- return;
- }
-
- if (options == null)
- {
- ThrowArgumentNullException(nameof(options));
- return;
- }
-
- (converter ?? AstToJsonConverter.Default).WriteJson(node, new JsonTextWriter(writer, indent), options);
- }
-
- public static void WriteJson(this Node node, JsonWriter writer, Options options, IConverter? converter = null)
- {
- if (node == null)
- {
- ThrowArgumentNullException(nameof(node));
- return;
- }
-
- if (writer == null)
- {
- ThrowArgumentNullException(nameof(writer));
- return;
- }
-
- if (options == null)
- {
- ThrowArgumentNullException(nameof(options));
- return;
- }
-
- (converter ?? AstToJsonConverter.Default).WriteJson(node, writer, options);
- }
-}
-
-public class AstToJsonConverter : AstJson.IConverter
-{
- public static readonly AstToJsonConverter Default = new();
-
- private protected AstToJsonConverter() { }
-
- private protected virtual VisitorBase CreateVisitor(JsonWriter writer, AstJson.Options options)
- {
- return new Visitor(writer, options);
- }
-
- public void WriteJson(Node node, JsonWriter writer, AstJson.Options options)
- {
- CreateVisitor(writer, options).Visit(node);
- }
-
- private protected abstract class VisitorBase : AstVisitor
- {
- private readonly JsonWriter _writer;
- private protected readonly bool _includeLineColumn;
- private protected readonly bool _includeRange;
- private protected readonly LocationMembersPlacement _locationMembersPlacement;
- private protected readonly bool _testCompatibilityMode;
-
- public VisitorBase(JsonWriter writer, AstJson.Options options)
- {
- _writer = writer ?? ThrowArgumentNullException(nameof(writer));
-
- _includeLineColumn = options.IncludingLineColumn;
- _includeRange = options.IncludingRange;
- _locationMembersPlacement = options.LocationMembersPlacement;
- _testCompatibilityMode = options.TestCompatibilityMode;
- }
-
- protected virtual string GetNodeType(Node node)
- {
- return node.Type.ToString();
- }
-
- private void WriteLocationInfo(Node node)
- {
- if (node is ChainExpression)
- {
- return;
- }
-
- if (_includeRange)
- {
- _writer.Member("range");
- _writer.StartArray();
- _writer.Number(node.Range.Start);
- _writer.Number(node.Range.End);
- _writer.EndArray();
- }
-
- if (_includeLineColumn)
- {
- _writer.Member("loc");
- _writer.StartObject();
- _writer.Member("start");
- Write(node.Location.Start);
- _writer.Member("end");
- Write(node.Location.End);
- _writer.EndObject();
- }
-
- void Write(Position position)
- {
- _writer.StartObject();
- Member("line", position.Line);
- Member("column", position.Column);
- _writer.EndObject();
- }
- }
-
- private void OnStartNodeObject(Node node)
- {
- _writer.StartObject();
-
- if ((_includeLineColumn || _includeRange)
- && _locationMembersPlacement == LocationMembersPlacement.Start)
- {
- WriteLocationInfo(node);
- }
-
- Member("type", GetNodeType(node));
- }
-
- private void OnFinishNodeObject(Node node)
- {
- if ((_includeLineColumn || _includeRange)
- && _locationMembersPlacement == LocationMembersPlacement.End)
- {
- WriteLocationInfo(node);
- }
-
- _writer.EndObject();
- }
-
- protected readonly struct NodeObjectDisposable : IDisposable
- {
- private readonly VisitorBase _visitor;
- private readonly Node _node;
-
- public NodeObjectDisposable(VisitorBase visitor, Node node)
- {
- _visitor = visitor;
- _node = node;
- }
-
- public void Dispose()
- {
- _visitor.OnFinishNodeObject(_node);
- }
- }
-
- protected NodeObjectDisposable StartNodeObject(Node node)
- {
- OnStartNodeObject(node);
- return new NodeObjectDisposable(this, node);
- }
-
- protected void EmptyNodeObject(Node node)
- {
- using (StartNodeObject(node)) { }
- }
-
- protected void Member(string name)
- {
- _writer.Member(name);
- }
-
- protected void Member(string name, Node? node)
- {
- Member(name);
- Visit(node);
- }
-
- protected void Member(string name, string? value)
- {
- Member(name);
- _writer.String(value);
- }
-
- protected void Member(string name, bool value)
- {
- Member(name);
- _writer.Boolean(value);
- }
-
- protected void Member(string name, int value)
- {
- Member(name);
- _writer.Number(value);
- }
-
- private static readonly ConditionalWeakTable EnumMap = new();
-
- protected void Member(string name, T value) where T : Enum
- {
- var map = (Dictionary)
- EnumMap.GetValue(value.GetType(),
- t => t.GetRuntimeFields()
- .Where(f => f.IsStatic)
- .ToDictionary(f => (T) f.GetValue(null), f => f.Name.ToLowerInvariant()));
- Member(name, map[value]);
- }
-
- protected void Member(string name, in NodeList nodes) where T : Node?
- {
- Member(name, nodes, node => node);
- }
-
- protected void Member(string name, in NodeList list, Func nodeSelector) where T : Node?
- {
- Member(name);
- _writer.StartArray();
- foreach (var item in list)
- {
- Visit(nodeSelector(item));
- }
-
- _writer.EndArray();
- }
-
- public override object? Visit(Node? node)
- {
- if (node is not null)
- {
- return base.Visit(node);
- }
- else
- {
- _writer.Null();
- return node!;
- }
- }
-
- protected internal override object? VisitArrayExpression(ArrayExpression arrayExpression)
- {
- using (StartNodeObject(arrayExpression))
- {
- Member("elements", arrayExpression.Elements);
- }
-
- return arrayExpression;
- }
-
- protected internal override object? VisitArrayPattern(ArrayPattern arrayPattern)
- {
- using (StartNodeObject(arrayPattern))
- {
- Member("elements", arrayPattern.Elements);
- }
-
- return arrayPattern;
- }
-
- protected internal override object? VisitArrowFunctionExpression(ArrowFunctionExpression arrowFunctionExpression)
- {
- using (StartNodeObject(arrowFunctionExpression))
- {
- Member("id", ((IFunction) arrowFunctionExpression).Id);
- Member("params", arrowFunctionExpression.Params);
- Member("body", arrowFunctionExpression.Body);
- Member("generator", ((IFunction) arrowFunctionExpression).Generator);
- Member("expression", arrowFunctionExpression.Expression);
- // original Esprima doesn't include this information yet
- if (!_testCompatibilityMode)
- {
- Member("strict", arrowFunctionExpression.Strict);
- }
- Member("async", arrowFunctionExpression.Async);
- }
-
- return arrowFunctionExpression;
- }
-
- protected internal override object? VisitAssignmentExpression(AssignmentExpression assignmentExpression)
- {
- using (StartNodeObject(assignmentExpression))
- {
- Member("operator", AssignmentExpression.GetAssignmentOperatorToken(assignmentExpression.Operator));
- Member("left", assignmentExpression.Left);
- Member("right", assignmentExpression.Right);
- }
-
- return assignmentExpression;
- }
-
- protected internal override object? VisitAssignmentPattern(AssignmentPattern assignmentPattern)
- {
- using (StartNodeObject(assignmentPattern))
- {
- Member("left", assignmentPattern.Left);
- Member("right", assignmentPattern.Right);
- }
-
- return assignmentPattern;
- }
-
- protected internal override object? VisitAwaitExpression(AwaitExpression awaitExpression)
- {
- using (StartNodeObject(awaitExpression))
- {
- Member("argument", awaitExpression.Argument);
- }
-
- return awaitExpression;
- }
-
- protected internal override object? VisitBinaryExpression(BinaryExpression binaryExpression)
- {
- using (StartNodeObject(binaryExpression))
- {
- Member("operator", BinaryExpression.GetBinaryOperatorToken(binaryExpression.Operator));
- Member("left", binaryExpression.Left);
- Member("right", binaryExpression.Right);
- }
-
- return binaryExpression;
- }
-
- protected internal override object? VisitBlockStatement(BlockStatement blockStatement)
- {
- using (StartNodeObject(blockStatement))
- {
- Member("body", blockStatement.Body, e => (Statement) e);
- }
-
- return blockStatement;
- }
-
- protected internal override object? VisitBreakStatement(BreakStatement breakStatement)
- {
- using (StartNodeObject(breakStatement))
- {
- Member("label", breakStatement.Label);
- }
-
- return breakStatement;
- }
-
- protected internal override object? VisitCallExpression(CallExpression callExpression)
- {
- using (StartNodeObject(callExpression))
- {
- Member("callee", callExpression.Callee);
- Member("arguments", callExpression.Arguments, e => e);
- Member("optional", callExpression.Optional);
- }
-
- return callExpression;
- }
-
- protected internal override object? VisitCatchClause(CatchClause catchClause)
- {
- using (StartNodeObject(catchClause))
- {
- Member("param", catchClause.Param);
- Member("body", catchClause.Body);
- }
-
- return catchClause;
- }
-
- protected internal override object? VisitChainExpression(ChainExpression chainExpression)
- {
- using (StartNodeObject(chainExpression))
- {
- Member("expression", chainExpression.Expression);
- }
-
- return chainExpression;
- }
-
- protected internal override object? VisitClassBody(ClassBody classBody)
- {
- using (StartNodeObject(classBody))
- {
- Member("body", classBody.Body);
- }
-
- return classBody;
- }
-
- protected internal override object? VisitClassDeclaration(ClassDeclaration classDeclaration)
- {
- using (StartNodeObject(classDeclaration))
- {
- Member("id", classDeclaration.Id);
- Member("superClass", classDeclaration.SuperClass);
- Member("body", classDeclaration.Body);
- if (classDeclaration.Decorators.Count > 0)
- {
- Member("decorators", classDeclaration.Decorators);
- }
- }
-
- return classDeclaration;
- }
-
- protected internal override object? VisitClassExpression(ClassExpression classExpression)
- {
- using (StartNodeObject(classExpression))
- {
- Member("id", classExpression.Id);
- Member("superClass", classExpression.SuperClass);
- Member("body", classExpression.Body);
- if (classExpression.Decorators.Count > 0)
- {
- Member("decorators", classExpression.Decorators);
- }
- }
-
- return classExpression;
- }
-
- protected internal override object? VisitConditionalExpression(ConditionalExpression conditionalExpression)
- {
- using (StartNodeObject(conditionalExpression))
- {
- Member("test", conditionalExpression.Test);
- Member("consequent", conditionalExpression.Consequent);
- Member("alternate", conditionalExpression.Alternate);
- }
-
- return conditionalExpression;
- }
-
- protected internal override object? VisitContinueStatement(ContinueStatement continueStatement)
- {
- using (StartNodeObject(continueStatement))
- {
- Member("label", continueStatement.Label);
- }
-
- return continueStatement;
- }
-
- protected internal override object? VisitDebuggerStatement(DebuggerStatement debuggerStatement)
- {
- EmptyNodeObject(debuggerStatement);
- return debuggerStatement;
- }
-
- protected internal override object? VisitDecorator(Decorator decorator)
- {
- using (StartNodeObject(decorator))
- {
- Member("expression", decorator.Expression);
- }
-
- return decorator;
- }
-
- protected internal override object? VisitDoWhileStatement(DoWhileStatement doWhileStatement)
- {
- using (StartNodeObject(doWhileStatement))
- {
- Member("body", doWhileStatement.Body);
- Member("test", doWhileStatement.Test);
- }
-
- return doWhileStatement;
- }
-
- protected internal override object? VisitEmptyStatement(EmptyStatement emptyStatement)
- {
- EmptyNodeObject(emptyStatement);
- return emptyStatement;
- }
-
- protected internal override object? VisitExportAllDeclaration(ExportAllDeclaration exportAllDeclaration)
- {
- using (StartNodeObject(exportAllDeclaration))
- {
- Member("source", exportAllDeclaration.Source);
-
- // original Esprima doesn't include this information yet
- if (!_testCompatibilityMode)
- {
- Member("exported", exportAllDeclaration.Exported);
- if (exportAllDeclaration.Assertions.Count > 0)
- {
- Member("assertions", exportAllDeclaration.Assertions);
- }
- }
- }
-
- return exportAllDeclaration;
- }
-
- protected internal override object? VisitExportDefaultDeclaration(ExportDefaultDeclaration exportDefaultDeclaration)
- {
- using (StartNodeObject(exportDefaultDeclaration))
- {
- Member("declaration", exportDefaultDeclaration.Declaration);
- }
-
- return exportDefaultDeclaration;
- }
-
- protected internal override object? VisitExportNamedDeclaration(ExportNamedDeclaration exportNamedDeclaration)
- {
- using (StartNodeObject(exportNamedDeclaration))
- {
- Member("declaration", exportNamedDeclaration.Declaration);
- Member("specifiers", exportNamedDeclaration.Specifiers);
- Member("source", exportNamedDeclaration.Source);
- // original Esprima doesn't include this information yet
- if (!_testCompatibilityMode && exportNamedDeclaration.Assertions.Count > 0)
- {
- Member("assertions", exportNamedDeclaration.Assertions);
- }
- }
-
- return exportNamedDeclaration;
- }
-
- protected internal override object? VisitExportSpecifier(ExportSpecifier exportSpecifier)
- {
- using (StartNodeObject(exportSpecifier))
- {
- Member("exported", exportSpecifier.Exported);
- Member("local", exportSpecifier.Local);
- }
-
- return exportSpecifier;
- }
-
- protected internal override object? VisitExpressionStatement(ExpressionStatement expressionStatement)
- {
- using (StartNodeObject(expressionStatement))
- {
- if (expressionStatement is Directive d)
- {
- Member("directive", d.Directiv);
- }
-
- Member("expression", expressionStatement.Expression);
- }
-
- return expressionStatement;
- }
-
- protected internal override object? VisitExtension(Node node)
- {
- throw new NotSupportedException("Unknown node type: " + node.Type);
- }
-
- protected internal override object? VisitForInStatement(ForInStatement forInStatement)
- {
- using (StartNodeObject(forInStatement))
- {
- Member("left", forInStatement.Left);
- Member("right", forInStatement.Right);
- Member("body", forInStatement.Body);
- Member("each", false);
- }
-
- return forInStatement;
- }
-
- protected internal override object? VisitForOfStatement(ForOfStatement forOfStatement)
- {
- using (StartNodeObject(forOfStatement))
- {
- Member("await", forOfStatement.Await);
- Member("left", forOfStatement.Left);
- Member("right", forOfStatement.Right);
- Member("body", forOfStatement.Body);
- }
-
- return forOfStatement;
- }
-
- protected internal override object? VisitForStatement(ForStatement forStatement)
- {
- using (StartNodeObject(forStatement))
- {
- Member("init", forStatement.Init);
- Member("test", forStatement.Test);
- Member("update", forStatement.Update);
- Member("body", forStatement.Body);
- }
-
- return forStatement;
- }
-
- protected internal override object? VisitFunctionDeclaration(FunctionDeclaration functionDeclaration)
- {
- using (StartNodeObject(functionDeclaration))
- {
- Member("id", functionDeclaration.Id);
- Member("params", functionDeclaration.Params);
- Member("body", functionDeclaration.Body);
- Member("generator", functionDeclaration.Generator);
- Member("expression", ((IFunction) functionDeclaration).Expression);
- // original Esprima doesn't include this information yet
- if (!_testCompatibilityMode)
- {
- Member("strict", functionDeclaration.Strict);
- }
- Member("async", functionDeclaration.Async);
- }
-
- return functionDeclaration;
- }
-
- protected internal override object? VisitFunctionExpression(FunctionExpression functionExpression)
- {
- using (StartNodeObject(functionExpression))
- {
- Member("id", functionExpression.Id);
- Member("params", functionExpression.Params);
- Member("body", functionExpression.Body);
- Member("generator", functionExpression.Generator);
- Member("expression", ((IFunction) functionExpression).Expression);
- // original Esprima doesn't include this information yet
- if (!_testCompatibilityMode)
- {
- Member("strict", functionExpression.Strict);
- }
- Member("async", functionExpression.Async);
- }
-
- return functionExpression;
- }
-
- protected internal override object? VisitIdentifier(Identifier identifier)
- {
- using (StartNodeObject(identifier))
- {
- Member("name", identifier.Name);
- }
-
- return identifier;
- }
-
- protected internal override object? VisitIfStatement(IfStatement ifStatement)
- {
- using (StartNodeObject(ifStatement))
- {
- Member("test", ifStatement.Test);
- Member("consequent", ifStatement.Consequent);
- Member("alternate", ifStatement.Alternate);
- }
-
- return ifStatement;
- }
-
- private object? VisitImportCompat(ImportCompat import)
- {
- EmptyNodeObject(import);
- return import;
- }
-
- private sealed class ImportCompat : Expression
- {
- public ImportCompat() : base(Nodes.Import) { }
-
- internal override Node? NextChildNode(ref ChildNodes.Enumerator enumerator) => null;
-
- protected internal override object? Accept(AstVisitor visitor) => ((VisitorBase) visitor).VisitImportCompat(this);
- }
-
- protected internal override object? VisitImport(Import import)
- {
- // original Esprima uses CallExpression to represent dynamic imports currently,
- // so we need to rewrite our representation to match this expectation
- if (_testCompatibilityMode)
- {
- const string importToken = "import";
-
- var callee = new ImportCompat
- {
- Location = new Location(import.Location.Start, new Position(import.Location.Start.Line, import.Location.Start.Column + importToken.Length)),
- Range = new Ast.Range(import.Range.Start, import.Range.Start + importToken.Length)
- };
- var args = new NodeList(new Expression[] { import.Source });
- var callExpression = new CallExpression(callee, args, optional: false)
- {
- Location = import.Location,
- Range = import.Range,
- };
-
- return Visit(callExpression);
- }
-
- using (StartNodeObject(import))
- {
- if (!_testCompatibilityMode)
- {
- Member("source", import.Source);
-
- if (import.Attributes is not null)
- {
- Member("attributes", import.Attributes);
- }
- }
- }
-
- return import;
- }
-
- protected internal override object? VisitImportAttribute(ImportAttribute importAttribute)
- {
- using (StartNodeObject(importAttribute))
- {
- Member("key", importAttribute.Key);
- Member("value", importAttribute.Value);
- }
-
- return importAttribute;
- }
-
- protected internal override object? VisitImportDeclaration(ImportDeclaration importDeclaration)
- {
- using (StartNodeObject(importDeclaration))
- {
- Member("specifiers", importDeclaration.Specifiers, e => (Node) e);
- Member("source", importDeclaration.Source);
- // original Esprima doesn't include this information yet
- if (importDeclaration.Assertions.Count > 0)
- {
- Member("assertions", importDeclaration.Assertions);
- }
- }
-
- return importDeclaration;
- }
-
- protected internal override object? VisitImportDefaultSpecifier(ImportDefaultSpecifier importDefaultSpecifier)
- {
- using (StartNodeObject(importDefaultSpecifier))
- {
- Member("local", importDefaultSpecifier.Local);
- }
-
- return importDefaultSpecifier;
- }
-
- protected internal override object? VisitImportNamespaceSpecifier(ImportNamespaceSpecifier importNamespaceSpecifier)
- {
- using (StartNodeObject(importNamespaceSpecifier))
- {
- Member("local", importNamespaceSpecifier.Local);
- }
-
- return importNamespaceSpecifier;
- }
-
- protected internal override object? VisitImportSpecifier(ImportSpecifier importSpecifier)
- {
- using (StartNodeObject(importSpecifier))
- {
- Member("local", importSpecifier.Local);
- Member("imported", importSpecifier.Imported);
- }
-
- return importSpecifier;
- }
-
- protected internal override object? VisitLabeledStatement(LabeledStatement labeledStatement)
- {
- using (StartNodeObject(labeledStatement))
- {
- Member("label", labeledStatement.Label);
- Member("body", labeledStatement.Body);
- }
-
- return labeledStatement;
- }
-
- protected internal override object? VisitLiteral(Literal literal)
- {
- using (StartNodeObject(literal))
- {
- _writer.Member("value");
- var value = literal.Value;
-
- switch (value)
- {
- case null:
- if (!_testCompatibilityMode && literal.TokenType == TokenType.RegularExpression)
- {
- // This is how esprima.org actually renders regexes since it relies on Regex.toString
- _writer.String(literal.Raw);
- }
- else
- {
- _writer.Null();
- }
-
- break;
- case bool b:
- _writer.Boolean(b);
- break;
- case Regex _:
- _writer.StartObject();
- _writer.EndObject();
- break;
- case double d:
- _writer.Number(d);
- break;
- default:
- _writer.String(Convert.ToString(value, CultureInfo.InvariantCulture));
- break;
- }
-
- Member("raw", literal.Raw);
-
- if (literal.Regex != null)
- {
- _writer.Member("regex");
- _writer.StartObject();
- Member("pattern", literal.Regex.Pattern);
- Member("flags", literal.Regex.Flags);
- _writer.EndObject();
- }
- else if (literal.Value is BigInteger bigInt)
- {
- Member("bigint", bigInt.ToString(CultureInfo.InvariantCulture));
- }
- }
-
- return literal;
- }
-
- protected internal override object? VisitMemberExpression(MemberExpression memberExpression)
- {
- using (StartNodeObject(memberExpression))
- {
- Member("computed", memberExpression.Computed);
- Member("object", memberExpression.Object);
- Member("property", memberExpression.Property);
- Member("optional", memberExpression.Optional);
- }
-
- return memberExpression;
- }
-
- protected internal override object? VisitMetaProperty(MetaProperty metaProperty)
- {
- using (StartNodeObject(metaProperty))
- {
- Member("meta", metaProperty.Meta);
- Member("property", metaProperty.Property);
- }
-
- return metaProperty;
- }
-
- protected internal override object? VisitMethodDefinition(MethodDefinition methodDefinition)
- {
- using (StartNodeObject(methodDefinition))
- {
- Member("key", methodDefinition.Key);
- Member("computed", methodDefinition.Computed);
- Member("value", methodDefinition.Value);
- Member("kind", methodDefinition.Kind);
- Member("static", methodDefinition.Static);
- if (methodDefinition.Decorators.Count > 0)
- {
- Member("decorators", methodDefinition.Decorators);
- }
- }
-
- return methodDefinition;
- }
-
- protected internal override object? VisitNewExpression(NewExpression newExpression)
- {
- using (StartNodeObject(newExpression))
- {
- Member("callee", newExpression.Callee);
- Member("arguments", newExpression.Arguments, e => (Node) e);
- }
-
- return newExpression;
- }
-
- protected internal override object? VisitObjectExpression(ObjectExpression objectExpression)
- {
- using (StartNodeObject(objectExpression))
- {
- Member("properties", objectExpression.Properties);
- }
-
- return objectExpression;
- }
-
- protected internal override object? VisitObjectPattern(ObjectPattern objectPattern)
- {
- using (StartNodeObject(objectPattern))
- {
- Member("properties", objectPattern.Properties);
- }
-
- return objectPattern;
- }
-
- protected internal override object? VisitPrivateIdentifier(PrivateIdentifier privateIdentifier)
- {
- using (StartNodeObject(privateIdentifier))
- {
- Member("name", privateIdentifier.Name);
- }
-
- return privateIdentifier;
- }
-
- protected internal override object? VisitProgram(Program program)
- {
- using (StartNodeObject(program))
- {
- Member("body", program.Body, e => (Node) e);
- Member("sourceType", program.SourceType);
-
- // original Esprima doesn't include this information yet
- if (!_testCompatibilityMode && program is Script s)
- {
- Member("strict", s.Strict);
- }
- }
-
- return program;
- }
-
- protected internal override object? VisitProperty(Property property)
- {
- using (StartNodeObject(property))
- {
- Member("key", property.Key);
- Member("computed", property.Computed);
- Member("value", property.Value);
- Member("kind", property.Kind);
- Member("method", property.Method);
- Member("shorthand", property.Shorthand);
- }
-
- return property;
- }
-
- protected internal override object? VisitPropertyDefinition(PropertyDefinition propertyDefinition)
- {
- using (StartNodeObject(propertyDefinition))
- {
- Member("key", propertyDefinition.Key);
- Member("computed", propertyDefinition.Computed);
- Member("value", propertyDefinition.Value);
- Member("kind", propertyDefinition.Kind);
- Member("static", propertyDefinition.Static);
- if (propertyDefinition.Decorators.Count > 0)
- {
- Member("decorators", propertyDefinition.Decorators);
- }
- }
-
- return propertyDefinition;
- }
-
- protected internal override object? VisitRestElement(RestElement restElement)
- {
- using (StartNodeObject(restElement))
- {
- Member("argument", restElement.Argument);
- }
-
- return restElement;
- }
-
- protected internal override object? VisitReturnStatement(ReturnStatement returnStatement)
- {
- using (StartNodeObject(returnStatement))
- {
- Member("argument", returnStatement.Argument);
- }
-
- return returnStatement;
- }
-
- protected internal override object? VisitSequenceExpression(SequenceExpression sequenceExpression)
- {
- using (StartNodeObject(sequenceExpression))
- {
- Member("expressions", sequenceExpression.Expressions);
- }
-
- return sequenceExpression;
- }
-
- protected internal override object? VisitSpreadElement(SpreadElement spreadElement)
- {
- using (StartNodeObject(spreadElement))
- {
- Member("argument", spreadElement.Argument);
- }
-
- return spreadElement;
- }
-
- protected internal override object? VisitStaticBlock(StaticBlock staticBlock)
- {
- using (StartNodeObject(staticBlock))
- {
- Member("body", staticBlock.Body, e => (Statement) e);
- }
-
- return staticBlock;
- }
-
- protected internal override object? VisitSuper(Super super)
- {
- EmptyNodeObject(super);
- return super;
- }
-
- protected internal override object? VisitSwitchCase(SwitchCase switchCase)
- {
- using (StartNodeObject(switchCase))
- {
- Member("test", switchCase.Test);
- Member("consequent", switchCase.Consequent, e => (Node) e);
- }
-
- return switchCase;
- }
-
- protected internal override object? VisitSwitchStatement(SwitchStatement switchStatement)
- {
- using (StartNodeObject(switchStatement))
- {
- Member("discriminant", switchStatement.Discriminant);
- Member("cases", switchStatement.Cases);
- }
-
- return switchStatement;
- }
-
- protected internal override object? VisitTaggedTemplateExpression(TaggedTemplateExpression taggedTemplateExpression)
- {
- using (StartNodeObject(taggedTemplateExpression))
- {
- Member("tag", taggedTemplateExpression.Tag);
- Member("quasi", taggedTemplateExpression.Quasi);
- }
-
- return taggedTemplateExpression;
- }
-
- protected internal override object? VisitTemplateElement(TemplateElement templateElement)
- {
- using (StartNodeObject(templateElement))
- {
- _writer.Member("value");
- _writer.StartObject();
- Member("raw", templateElement.Value.Raw);
- Member("cooked", templateElement.Value.Cooked);
- _writer.EndObject();
- Member("tail", templateElement.Tail);
- }
-
- return templateElement;
- }
-
- protected internal override object? VisitTemplateLiteral(TemplateLiteral templateLiteral)
- {
- using (StartNodeObject(templateLiteral))
- {
- Member("quasis", templateLiteral.Quasis);
- Member("expressions", templateLiteral.Expressions);
- }
-
- return templateLiteral;
- }
-
- protected internal override object? VisitThisExpression(ThisExpression thisExpression)
- {
- EmptyNodeObject(thisExpression);
- return thisExpression;
- }
-
- protected internal override object? VisitThrowStatement(ThrowStatement throwStatement)
- {
- using (StartNodeObject(throwStatement))
- {
- Member("argument", throwStatement.Argument);
- }
-
- return throwStatement;
- }
-
- protected internal override object? VisitTryStatement(TryStatement tryStatement)
- {
- using (StartNodeObject(tryStatement))
- {
- Member("block", tryStatement.Block);
- Member("handler", tryStatement.Handler);
- Member("finalizer", tryStatement.Finalizer);
- }
-
- return tryStatement;
- }
-
- protected internal override object? VisitUnaryExpression(UnaryExpression unaryExpression)
- {
- using (StartNodeObject(unaryExpression))
- {
- Member("operator", UnaryExpression.GetUnaryOperatorToken(unaryExpression.Operator));
- Member("argument", unaryExpression.Argument);
- Member("prefix", unaryExpression.Prefix);
- }
-
- return unaryExpression;
- }
-
- protected internal override object? VisitVariableDeclaration(VariableDeclaration variableDeclaration)
- {
- using (StartNodeObject(variableDeclaration))
- {
- Member("declarations", variableDeclaration.Declarations);
- Member("kind", variableDeclaration.Kind);
- }
-
- return variableDeclaration;
- }
-
- protected internal override object? VisitVariableDeclarator(VariableDeclarator variableDeclarator)
- {
- using (StartNodeObject(variableDeclarator))
- {
- Member("id", variableDeclarator.Id);
- Member("init", variableDeclarator.Init);
- }
-
- return variableDeclarator;
- }
-
- protected internal override object? VisitWhileStatement(WhileStatement whileStatement)
- {
- using (StartNodeObject(whileStatement))
- {
- Member("test", whileStatement.Test);
- Member("body", whileStatement.Body);
- }
-
- return whileStatement;
- }
-
- protected internal override object? VisitWithStatement(WithStatement withStatement)
- {
- using (StartNodeObject(withStatement))
- {
- Member("object", withStatement.Object);
- Member("body", withStatement.Body);
- }
-
- return withStatement;
- }
-
- protected internal override object? VisitYieldExpression(YieldExpression yieldExpression)
- {
- using (StartNodeObject(yieldExpression))
- {
- Member("argument", yieldExpression.Argument);
- Member("delegate", yieldExpression.Delegate);
- }
-
- return yieldExpression;
- }
- }
-
- private sealed class Visitor : VisitorBase
- {
- public Visitor(JsonWriter writer, AstJson.Options options)
- : base(writer, options)
- {
- }
- }
-}
diff --git a/src/Esprima/Utils/AstToJavascript.cs b/src/Esprima/Utils/AstToJavascript.cs
new file mode 100644
index 00000000..e36e0d1f
--- /dev/null
+++ b/src/Esprima/Utils/AstToJavascript.cs
@@ -0,0 +1,72 @@
+using Esprima.Ast;
+
+namespace Esprima.Utils;
+
+public record class AstToJavascriptOptions
+{
+ public static readonly AstToJavascriptOptions Default = new();
+
+ protected internal virtual AstToJavascriptConverter CreateConverter(JavascriptTextWriter writer) => new AstToJavascriptConverter(writer, this);
+}
+
+public static class AstToJavascript
+{
+ public static string ToJavascriptString(this Node node)
+ {
+ return ToJavascriptString(node, JavascriptTextWriterOptions.Default, AstToJavascriptOptions.Default);
+ }
+
+ public static string ToJavascriptString(this Node node, KnRJavascriptTextWriterOptions formattingOptions)
+ {
+ return ToJavascriptString(node, formattingOptions, AstToJavascriptOptions.Default);
+ }
+
+ public static string ToJavascriptString(this Node node, bool beautify)
+ {
+ return ToJavascriptString(node, beautify ? KnRJavascriptTextWriterOptions.Default : JavascriptTextWriterOptions.Default, AstToJavascriptOptions.Default);
+ }
+
+ public static string ToJavascriptString(this Node node, JavascriptTextWriterOptions writerOptions, AstToJavascriptOptions options)
+ {
+ using (var writer = new StringWriter())
+ {
+ WriteJavascript(node, writer, writerOptions, options);
+ return writer.ToString();
+ }
+ }
+
+ public static void WriteJavascript(this Node node, TextWriter writer)
+ {
+ WriteJavascript(node, writer, JavascriptTextWriterOptions.Default, AstToJavascriptOptions.Default);
+ }
+
+ public static void WriteJavascript(this Node node, TextWriter writer, KnRJavascriptTextWriterOptions formattingOptions)
+ {
+ WriteJavascript(node, writer, formattingOptions, AstToJavascriptOptions.Default);
+ }
+
+ public static void WriteJavascript(this Node node, TextWriter writer, bool beautify)
+ {
+ WriteJavascript(node, writer, beautify ? KnRJavascriptTextWriterOptions.Default : JavascriptTextWriterOptions.Default, AstToJavascriptOptions.Default);
+ }
+
+ public static void WriteJavascript(this Node node, TextWriter writer, JavascriptTextWriterOptions writerOptions, AstToJavascriptOptions options)
+ {
+ if (writerOptions is null)
+ {
+ throw new ArgumentNullException(nameof(writerOptions));
+ }
+
+ WriteJavascript(node, writerOptions.CreateWriter(writer), options);
+ }
+
+ public static void WriteJavascript(this Node node, JavascriptTextWriter writer, AstToJavascriptOptions options)
+ {
+ if (options is null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ options.CreateConverter(writer).Convert(node);
+ }
+}
diff --git a/src/Esprima/Utils/AstToJavascriptConverter.Enums.cs b/src/Esprima/Utils/AstToJavascriptConverter.Enums.cs
new file mode 100644
index 00000000..11133e24
--- /dev/null
+++ b/src/Esprima/Utils/AstToJavascriptConverter.Enums.cs
@@ -0,0 +1,57 @@
+namespace Esprima.Utils;
+
+partial class AstToJavascriptConverter
+{
+ [Flags]
+ protected internal enum BinaryOperationFlags
+ {
+ None = 0,
+ LeftOperandNeedsBrackets = 1 << 0,
+ RightOperandNeedsBrackets = 1 << 1,
+ BothOperandsNeedBrackets = LeftOperandNeedsBrackets | RightOperandNeedsBrackets
+ }
+
+ [Flags]
+ protected internal enum StatementFlags
+ {
+ None = 0,
+
+ NeedsSemicolon = JavascriptTextWriter.StatementFlags.NeedsSemicolon,
+ MayOmitRightMostSemicolon = JavascriptTextWriter.StatementFlags.MayOmitRightMostSemicolon,
+ IsRightMost = JavascriptTextWriter.StatementFlags.IsRightMost,
+ IsStatementBody = JavascriptTextWriter.StatementFlags.IsStatementBody,
+
+ NestedVariableDeclaration = 1 << 16,
+ }
+
+ [Flags]
+ protected internal enum ExpressionFlags
+ {
+ None = 0,
+
+ NeedsBrackets = JavascriptTextWriter.ExpressionFlags.NeedsBrackets,
+ IsLeftMost = JavascriptTextWriter.ExpressionFlags.IsLeftMost,
+
+ SpaceBeforeBracketsRecommended = JavascriptTextWriter.ExpressionFlags.SpaceBeforeBracketsRecommended,
+ SpaceAfterBracketsRecommended = JavascriptTextWriter.ExpressionFlags.SpaceAfterBracketsRecommended,
+ SpaceAroundBracketsRecommended = JavascriptTextWriter.ExpressionFlags.SpaceAroundBracketsRecommended,
+
+ IsMethod = 1 << 16,
+
+ InOperatorIsAmbiguousInDeclaration = 1 << 24, // automatically propagated to sub-expressions
+
+ IsLeftMostInArrowFunctionBody = 1 << 25, // automatically combined and propagated to sub-expressions
+ IsInsideArrowFunctionBody = 1 << 26, // automatically propagated to sub-expressions
+
+ // https://stackoverflow.com/a/17587899/8656352
+ IsLeftMostInNewCallee = 1 << 27, // automatically combined and propagated to sub-expressions
+ IsInsideNewCallee = 1 << 28, // automatically propagated to sub-expressions
+
+ IsLeftMostInLeftHandSideExpression = 1 << 29, // automatically combined and propagated to sub-expressions
+ IsInsideLeftHandSideExpression = 1 << 30, // automatically propagated to sub-expressions
+
+ IsInsideStatementExpression = 1 << 31, // automatically propagated to sub-expressions
+
+ IsInPotentiallyAmbiguousContext = InOperatorIsAmbiguousInDeclaration | IsInsideArrowFunctionBody | IsInsideNewCallee | IsInsideLeftHandSideExpression | IsInsideStatementExpression,
+ }
+}
diff --git a/src/Esprima/Utils/AstToJavascriptConverter.Helpers.cs b/src/Esprima/Utils/AstToJavascriptConverter.Helpers.cs
new file mode 100644
index 00000000..cee8d1bf
--- /dev/null
+++ b/src/Esprima/Utils/AstToJavascriptConverter.Helpers.cs
@@ -0,0 +1,412 @@
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using Esprima.Ast;
+using static Esprima.Utils.JavascriptTextWriter;
+
+namespace Esprima.Utils;
+
+partial class AstToJavascriptConverter
+{
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private protected static StatementFlags StatementBodyFlags(bool isRightMost)
+ {
+ return StatementFlags.IsStatementBody | isRightMost.ToFlag(StatementFlags.IsRightMost);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private protected static TokenFlags StatementBodyFlagsToKeywordFlags(StatementFlags previousBodyFlags)
+ {
+ // Maps IsStatementBody to keyword flags.
+ return (TokenFlags) (previousBodyFlags & StatementFlags.IsStatementBody);
+ }
+
+ protected StatementFlags PropagateStatementFlags(StatementFlags flags)
+ {
+ // Caller must not set NeedsSemicolon or MayOmitRightMostSemicolon.
+ // NeedsSemicolon is set by the visitation handler of statement via the StatementNeedsSemicolon method,
+ // MayOmitRightMostSemicolon is set by VisitStatementList.
+ Debug.Assert((flags & (StatementFlags.NeedsSemicolon | StatementFlags.MayOmitRightMostSemicolon)) == 0);
+
+ // Combines IsRightMost of parent and current statement to determine its effective value for the current statement list.
+ flags &= ~StatementFlags.IsRightMost | _currentStatementFlags & StatementFlags.IsRightMost;
+
+ // Propagates MayOmitRightMostSemicolon to current statement.
+ flags |= _currentStatementFlags & StatementFlags.MayOmitRightMostSemicolon;
+
+ return flags;
+ }
+
+ private protected static readonly Func s_getCombinedStatementFlags = static (@this, statement, flags) =>
+ @this.PropagateStatementFlags(flags);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected void VisitStatement(Statement statement, StatementFlags flags)
+ {
+ VisitStatement(statement, flags, s_getCombinedStatementFlags);
+ }
+
+ protected void VisitStatement(Statement statement, StatementFlags flags, Func getCombinedFlags)
+ {
+ var originalStatementFlags = _currentStatementFlags;
+ _currentStatementFlags = getCombinedFlags(this, statement, flags);
+
+ Writer.StartStatement((JavascriptTextWriter.StatementFlags) _currentStatementFlags, ref _writeContext);
+ Visit(statement);
+ Writer.EndStatement((JavascriptTextWriter.StatementFlags) _currentStatementFlags, ref _writeContext);
+
+ _currentStatementFlags = originalStatementFlags;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected void VisitStatementList(in NodeList statementList)
+ {
+ VisitStatementList(in statementList, static (_, _, index, count) =>
+ (index == count - 1).ToFlag(StatementFlags.IsRightMost | StatementFlags.MayOmitRightMostSemicolon));
+ }
+
+ protected void VisitStatementList(in NodeList statementList, Func getCombinedItemFlags)
+ {
+ Writer.StartStatementList(statementList.Count, ref _writeContext);
+
+ for (var i = 0; i < statementList.Count; i++)
+ {
+ VisitStatementListItem(statementList[i], i, statementList.Count, getCombinedItemFlags);
+ }
+
+ Writer.EndStatementList(statementList.Count, ref _writeContext);
+ }
+
+ protected void VisitStatementListItem(Statement statement, int index, int count, Func getCombinedFlags)
+ {
+ var originalStatementFlags = _currentStatementFlags;
+ _currentStatementFlags = getCombinedFlags(this, statement, index, count);
+
+ Writer.StartStatementListItem(index, count, (JavascriptTextWriter.StatementFlags) _currentStatementFlags, ref _writeContext);
+ Visit(statement);
+ Writer.EndStatementListItem(index, count, (JavascriptTextWriter.StatementFlags) _currentStatementFlags, ref _writeContext);
+
+ _currentStatementFlags = originalStatementFlags;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected void StatementNeedsSemicolon() => _currentStatementFlags |= StatementFlags.NeedsSemicolon;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private protected static ExpressionFlags RootExpressionFlags(bool needsBrackets)
+ {
+ return ExpressionFlags.IsLeftMost | needsBrackets.ToFlag(ExpressionFlags.NeedsBrackets);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private protected static ExpressionFlags LeftHandSideRootExpressionFlags(bool needsBrackets)
+ {
+ return ExpressionFlags.IsInsideLeftHandSideExpression | ExpressionFlags.IsLeftMostInLeftHandSideExpression | RootExpressionFlags(needsBrackets);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private protected static ExpressionFlags SubExpressionFlags(bool needsBrackets, bool isLeftMost)
+ {
+ return needsBrackets.ToFlag(ExpressionFlags.NeedsBrackets) | isLeftMost.ToFlag(ExpressionFlags.IsLeftMost);
+ }
+
+ protected ExpressionFlags PropagateExpressionFlags(ExpressionFlags flags)
+ {
+ const ExpressionFlags isLeftMostFlags =
+ ExpressionFlags.IsLeftMost |
+ ExpressionFlags.IsLeftMostInArrowFunctionBody |
+ ExpressionFlags.IsLeftMostInNewCallee |
+ ExpressionFlags.IsLeftMostInLeftHandSideExpression;
+
+ // Combines IsLeftMost* flags of parent and current statement to determine their effective values for the current expression tree.
+ if (_currentExpressionFlags.HasFlagFast(ExpressionFlags.NeedsBrackets) || !flags.HasFlagFast(ExpressionFlags.IsLeftMost))
+ {
+ flags &= ~isLeftMostFlags;
+ }
+ else
+ {
+ flags = flags & ~isLeftMostFlags | _currentExpressionFlags & isLeftMostFlags;
+ }
+
+ // Propagates IsInsideStatementExpression, IsInsideArrowFunctionBody and IsInsideLeftHandSideExpression to current expression.
+ flags |= _currentExpressionFlags & ExpressionFlags.IsInPotentiallyAmbiguousContext;
+
+ return flags;
+ }
+
+ protected ExpressionFlags DisambiguateExpression(Expression expression, ExpressionFlags flags)
+ {
+ if (flags.HasFlagFast(ExpressionFlags.NeedsBrackets))
+ {
+ return flags & ~ExpressionFlags.InOperatorIsAmbiguousInDeclaration;
+ }
+
+ // Puts the left-most expression in brackets if necessary (in cases where it would be interpreted differently without brackets).
+ if ((flags & ExpressionFlags.IsInPotentiallyAmbiguousContext) != 0)
+ {
+ if (flags.HasFlagFast(ExpressionFlags.IsInsideStatementExpression | ExpressionFlags.IsLeftMost) && ExpressionIsAmbiguousAsStatementExpression(expression) ||
+ flags.HasFlagFast(ExpressionFlags.IsInsideLeftHandSideExpression | ExpressionFlags.IsLeftMostInLeftHandSideExpression) && LeftHandSideExpressionIsParenthesized(expression) ||
+ flags.HasFlagFast(ExpressionFlags.IsInsideArrowFunctionBody | ExpressionFlags.IsLeftMostInArrowFunctionBody) && ExpressionIsAmbiguousAsArrowFunctionBody(expression) ||
+ flags.HasFlagFast(ExpressionFlags.IsInsideNewCallee | ExpressionFlags.IsLeftMostInNewCallee) && ExpressionIsAmbiguousAsNewCallee(expression))
+ {
+ return (flags | ExpressionFlags.NeedsBrackets) & ~ExpressionFlags.InOperatorIsAmbiguousInDeclaration;
+ }
+ // Edge case: for (var a = b = (c in d in e) in x);
+ else if (flags.HasFlagFast(ExpressionFlags.InOperatorIsAmbiguousInDeclaration) && expression is BinaryExpression { Operator: BinaryOperator.In })
+ {
+ return (flags | ExpressionFlags.NeedsBrackets) & ~ExpressionFlags.InOperatorIsAmbiguousInDeclaration;
+ }
+ }
+
+ return flags;
+ }
+
+ private protected static readonly Func s_getCombinedRootExpressionFlags = static (@this, expression, flags) =>
+ @this.DisambiguateExpression(expression, flags);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected void VisitRootExpression(Expression expression, ExpressionFlags flags)
+ {
+ VisitExpression(expression, flags, s_getCombinedRootExpressionFlags);
+ }
+
+ private protected static readonly Func s_getCombinedSubExpressionFlags = static (@this, expression, flags) =>
+ @this.DisambiguateExpression(expression, @this.PropagateExpressionFlags(flags));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected void VisitSubExpression(Expression expression, ExpressionFlags flags)
+ {
+ VisitExpression(expression, flags, s_getCombinedSubExpressionFlags);
+ }
+
+ protected void VisitExpression(Expression expression, ExpressionFlags flags, Func getCombinedFlags)
+ {
+ var originalExpressionFlags = _currentExpressionFlags;
+ _currentExpressionFlags = getCombinedFlags(this, expression, flags);
+
+ Writer.StartExpression((JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, ref _writeContext);
+ Visit(expression);
+ Writer.EndExpression((JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, ref _writeContext);
+
+ _currentExpressionFlags = originalExpressionFlags;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected void VisitSubExpressionList(in NodeList expressionList)
+ {
+ VisitExpressionList(in expressionList, static (@this, expression, index, _) =>
+ s_getCombinedSubExpressionFlags(@this, expression, SubExpressionFlags(@this.ExpressionNeedsBracketsInList(expression), isLeftMost: false)));
+ }
+
+ protected void VisitExpressionList(in NodeList expressionList, Func getCombinedItemFlags)
+ {
+ Writer.StartExpressionList(expressionList.Count, ref _writeContext);
+
+ for (var i = 0; i < expressionList.Count; i++)
+ {
+ VisitExpressionListItem(expressionList[i], i, expressionList.Count, getCombinedItemFlags);
+ }
+
+ Writer.EndExpressionList(expressionList.Count, ref _writeContext);
+ }
+
+ protected void VisitExpressionListItem(Expression expression, int index, int count, Func getCombinedFlags)
+ {
+ var originalExpressionFlags = _currentExpressionFlags;
+ _currentExpressionFlags = getCombinedFlags(this, expression, index, count);
+
+ Writer.StartExpressionListItem(index, count, (JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, ref _writeContext);
+ Visit(expression);
+ Writer.EndExpressionListItem(index, count, (JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, ref _writeContext);
+
+ _currentExpressionFlags = originalExpressionFlags;
+ }
+
+ private void VisitAssertions(in NodeList assertions)
+ {
+ // https://github.com/tc39/proposal-import-assertions
+
+ Writer.WriteKeyword("assert", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ Writer.StartObject(assertions.Count, ref _writeContext);
+
+ VisitAuxiliaryNodeList(in assertions, separator: ",");
+
+ Writer.EndObject(assertions.Count, ref _writeContext);
+ }
+
+ private void VisitExportOrImportSpecifierIdentifier(Expression identifierExpression)
+ {
+ if (identifierExpression is Identifier identifier && identifier.Name == "default")
+ {
+ Writer.WriteKeyword("default", ref _writeContext);
+ }
+ else
+ {
+ VisitRootExpression(identifierExpression, RootExpressionFlags(needsBrackets: false));
+ }
+ }
+
+ private void VisitPropertyKey(Expression key, bool computed, TokenFlags leadingBracketFlags = TokenFlags.None, TokenFlags trailingBracketFlags = TokenFlags.None)
+ {
+ if (computed)
+ {
+ Writer.WritePunctuator("[", TokenFlags.Leading | leadingBracketFlags, ref _writeContext);
+ VisitRootExpression(key, RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(key)));
+ Writer.WritePunctuator("]", TokenFlags.Trailing | trailingBracketFlags, ref _writeContext);
+ }
+ else if (key.Type == Nodes.Identifier)
+ {
+ VisitAuxiliaryNode(key);
+ }
+ else
+ {
+ VisitRootExpression(key, RootExpressionFlags(needsBrackets: false));
+ }
+ }
+
+ protected virtual bool ExpressionIsAmbiguousAsStatementExpression(Expression expression)
+ {
+ switch (expression.Type)
+ {
+ case Nodes.ClassExpression:
+ case Nodes.FunctionExpression:
+ case Nodes.ObjectExpression:
+ case Nodes.AssignmentExpression when expression.As() is { Left.Type: Nodes.ObjectPattern }:
+ case Nodes.Identifier when Scanner.IsStrictModeReservedWord(expression.As().Name!):
+ return true;
+ }
+
+ return false;
+ }
+
+ protected virtual bool ExpressionIsAmbiguousAsArrowFunctionBody(Expression expression)
+ {
+ switch (expression.Type)
+ {
+ case Nodes.ObjectExpression:
+ case Nodes.AssignmentExpression when expression.As() is { Left.Type: Nodes.ObjectPattern }:
+ return true;
+ }
+
+ return false;
+ }
+
+ protected virtual bool ExpressionIsAmbiguousAsNewCallee(Expression expression)
+ {
+ switch (expression.Type)
+ {
+ case Nodes.CallExpression:
+ return true;
+ }
+
+ return false;
+ }
+
+ protected virtual bool LeftHandSideExpressionIsParenthesized(Expression expression)
+ {
+ // https://tc39.es/ecma262/#sec-left-hand-side-expressions
+
+ switch (expression.Type)
+ {
+ case Nodes.ArrowFunctionExpression:
+ case Nodes.AssignmentExpression:
+ case Nodes.AwaitExpression:
+ case Nodes.BinaryExpression:
+ case Nodes.LogicalExpression:
+ case Nodes.ConditionalExpression:
+ case Nodes.SequenceExpression:
+ case Nodes.UnaryExpression:
+ case Nodes.UpdateExpression:
+ case Nodes.YieldExpression:
+ return true;
+ }
+
+ return false;
+ }
+
+ protected virtual bool ExpressionNeedsBracketsInList(Expression expression)
+ {
+ return expression.Type is
+ Nodes.SequenceExpression;
+ }
+
+ protected virtual int GetOperatorPrecedence(Expression expression, out int associativity) =>
+ expression.GetOperatorPrecedence(out associativity) is >= 0 and var result
+ ? result
+ : throw new NotImplementedException($"Operator precedence for expression of type {expression.GetType()} is not defined.");
+
+ protected bool UnaryOperandNeedsBrackets(Expression operation, Expression operand) =>
+ GetOperatorPrecedence(operation, out _) > GetOperatorPrecedence(operand, out _);
+
+ protected BinaryOperationFlags BinaryOperandsNeedBrackets(Expression operation, Expression leftOperand, Expression rightOperand)
+ {
+ var operationPrecedence = GetOperatorPrecedence(operation, out var associativity);
+ var leftOperandPrecedence = GetOperatorPrecedence(leftOperand, out _);
+ var rightOperandPrecedence = GetOperatorPrecedence(rightOperand, out _);
+
+ var result = BinaryOperationFlags.None;
+
+ if (operationPrecedence > leftOperandPrecedence || operationPrecedence == leftOperandPrecedence && associativity > 0) // right-to-left associativity
+ {
+ result |= BinaryOperationFlags.LeftOperandNeedsBrackets;
+ }
+
+ if (operationPrecedence > rightOperandPrecedence || operationPrecedence == rightOperandPrecedence && associativity < 0) // left-to-right associativity
+ {
+ result |= BinaryOperationFlags.RightOperandNeedsBrackets;
+ }
+
+ return result;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected void VisitAuxiliaryNode(Node node)
+ {
+ VisitAuxiliaryNode(node, static delegate { return null; });
+ }
+
+ protected void VisitAuxiliaryNode(Node node, Func getNodeContext)
+ {
+ var originalAuxiliaryNodeContext = _currentAuxiliaryNodeContext;
+ _currentAuxiliaryNodeContext = getNodeContext(this, node);
+
+ Writer.StartAuxiliaryNode(_currentAuxiliaryNodeContext, ref _writeContext);
+ Visit(node);
+ Writer.EndAuxiliaryNode(_currentAuxiliaryNodeContext, ref _writeContext);
+
+ _currentAuxiliaryNodeContext = originalAuxiliaryNodeContext;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected void VisitAuxiliaryNodeList(in NodeList nodeList, string separator)
+ where TNode : Node
+ {
+ VisitAuxiliaryNodeList(in nodeList, separator, static delegate { return null; });
+ }
+
+ protected void VisitAuxiliaryNodeList(in NodeList nodeList, string separator, Func getNodeContext)
+ where TNode : Node
+ {
+ Writer.StartAuxiliaryNodeList(nodeList.Count, ref _writeContext);
+
+ for (var i = 0; i < nodeList.Count; i++)
+ {
+ VisitAuxiliaryNodeListItem(nodeList[i], i, nodeList.Count, separator, getNodeContext);
+ }
+
+ Writer.EndAuxiliaryNodeList(nodeList.Count, ref _writeContext);
+ }
+
+ protected void VisitAuxiliaryNodeListItem(TNode node, int index, int count, string separator, Func getNodeContext)
+ where TNode : Node
+ {
+ var originalAuxiliaryNodeContext = _currentAuxiliaryNodeContext;
+ _currentAuxiliaryNodeContext = getNodeContext(this, node, index, count);
+
+ Writer.StartAuxiliaryNodeListItem(index, count, separator, _currentAuxiliaryNodeContext, ref _writeContext);
+ Visit(node);
+ Writer.EndAuxiliaryNodeListItem(index, count, separator, _currentAuxiliaryNodeContext, ref _writeContext);
+
+ _currentAuxiliaryNodeContext = originalAuxiliaryNodeContext;
+ }
+}
diff --git a/src/Esprima/Utils/AstToJavascriptConverter.cs b/src/Esprima/Utils/AstToJavascriptConverter.cs
new file mode 100644
index 00000000..fc257321
--- /dev/null
+++ b/src/Esprima/Utils/AstToJavascriptConverter.cs
@@ -0,0 +1,1663 @@
+using System.Runtime.CompilerServices;
+using Esprima.Ast;
+using static Esprima.Utils.JavascriptTextWriter;
+
+namespace Esprima.Utils;
+
+public partial class AstToJavascriptConverter : AstVisitor
+{
+ // Notes for maintainers:
+ // Don't visit nodes directly (by calling Visit) unless it's necessary for some special reason (but in that case you'll need to setup the context of the visitation manually!)
+ // For examples of special reason, see VisitArrayExpression, VisitObjectExpression, VisitImport, etc. In usual cases just use the following predefined visitation helper methods:
+ // * Visit statements using VisitStatement / VisitStatementList.
+ // * Visit expressions using VisitRootExpression and sub-expressions (expressions inside another expression) using VisitSubExpression / VisitSubExpressionList.
+ // * Visit identifiers using VisitAuxiliaryNode when they are binding identifiers (declarations) and visit them using VisitRootExpression when they are identifier references (actual expressions).
+ // * Visit any other nodes using VisitAuxiliaryNode / VisitAuxiliaryNodeList.
+
+ private static readonly object s_lastSwitchCaseFlag = new();
+ private static readonly object s_forLoopInitDeclarationFlag = new();
+
+ private WriteContext _writeContext;
+ private StatementFlags _currentStatementFlags;
+ private ExpressionFlags _currentExpressionFlags;
+ private object? _currentAuxiliaryNodeContext;
+
+ public AstToJavascriptConverter(JavascriptTextWriter writer, AstToJavascriptOptions options)
+ {
+ Writer = writer ?? throw new ArgumentNullException(nameof(writer));
+
+ if (options is null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+ }
+
+ public JavascriptTextWriter Writer { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
+
+ protected ref readonly WriteContext WriteContext { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _writeContext; }
+
+ protected Node? ParentNode { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _writeContext.ParentNode; }
+
+ public void Convert(Node node)
+ {
+ _writeContext = default;
+ _currentStatementFlags = StatementFlags.None;
+ _currentExpressionFlags = ExpressionFlags.None;
+ _currentAuxiliaryNodeContext = null;
+
+ Visit(node ?? throw new ArgumentNullException(nameof(node)));
+ }
+
+ public override object? Visit(Node node)
+ {
+ var originalWriteContext = _writeContext;
+ _writeContext = new WriteContext(originalWriteContext.Node, node);
+
+ var result = base.Visit(node);
+
+ _writeContext = originalWriteContext;
+
+ return result;
+ }
+
+ protected internal override object? VisitArrayExpression(ArrayExpression arrayExpression)
+ {
+ _writeContext.SetNodeProperty(nameof(arrayExpression.Elements), static node => ref node.As().Elements);
+
+ Writer.StartArray(arrayExpression.Elements.Count, ref _writeContext);
+
+ // Elements need special care because it may contain null values denoting omitted elements.
+
+ Writer.StartExpressionList(arrayExpression.Elements.Count, ref _writeContext);
+
+ for (var i = 0; i < arrayExpression.Elements.Count; i++)
+ {
+ var element = arrayExpression.Elements[i];
+
+ if (element is not null)
+ {
+ VisitExpressionListItem(element, i, arrayExpression.Elements.Count, static (@this, expression, index, _) =>
+ s_getCombinedSubExpressionFlags(@this, expression, SubExpressionFlags(@this.ExpressionNeedsBracketsInList(expression), isLeftMost: false)));
+ }
+ else
+ {
+ var originalExpressionFlags = _currentExpressionFlags;
+ _currentExpressionFlags = PropagateExpressionFlags(SubExpressionFlags(needsBrackets: false, isLeftMost: false));
+
+ Writer.StartExpressionListItem(i, arrayExpression.Elements.Count, (JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, ref _writeContext);
+ Writer.EndExpressionListItem(i, arrayExpression.Elements.Count, (JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, ref _writeContext);
+
+ _currentExpressionFlags = originalExpressionFlags;
+ }
+ }
+
+ Writer.EndExpressionList(arrayExpression.Elements.Count, ref _writeContext);
+
+ Writer.EndArray(arrayExpression.Elements.Count, ref _writeContext);
+
+ return arrayExpression;
+ }
+
+ protected internal override object? VisitArrayPattern(ArrayPattern arrayPattern)
+ {
+ _writeContext.SetNodeProperty(nameof(arrayPattern.Elements), static node => ref node.As().Elements);
+
+ Writer.StartArray(arrayPattern.Elements.Count, ref _writeContext);
+
+ // Elements need special care because it may contain null values denoting omitted elements.
+
+ Writer.StartAuxiliaryNodeList(arrayPattern.Elements.Count, ref _writeContext);
+
+ for (var i = 0; i < arrayPattern.Elements.Count; i++)
+ {
+ var element = arrayPattern.Elements[i];
+
+ var originalAuxiliaryNodeContext = _currentAuxiliaryNodeContext;
+ _currentAuxiliaryNodeContext = null;
+
+ Writer.StartAuxiliaryNodeListItem(i, arrayPattern.Elements.Count, separator: ",", _currentAuxiliaryNodeContext, ref _writeContext);
+ if (element is not null)
+ {
+ Visit(element);
+ }
+ Writer.EndAuxiliaryNodeListItem(i, arrayPattern.Elements.Count, separator: ",", _currentAuxiliaryNodeContext, ref _writeContext);
+
+ _currentAuxiliaryNodeContext = originalAuxiliaryNodeContext;
+ }
+
+ Writer.EndAuxiliaryNodeList(arrayPattern.Elements.Count, ref _writeContext);
+
+ Writer.EndArray(arrayPattern.Elements.Count, ref _writeContext);
+
+ return arrayPattern;
+ }
+
+ protected internal override object? VisitArrowFunctionExpression(ArrowFunctionExpression arrowFunctionExpression)
+ {
+ if (arrowFunctionExpression.Async)
+ {
+ _writeContext.SetNodeProperty(nameof(arrowFunctionExpression.Async), static node => node.As().Async);
+ Writer.WriteKeyword("async", TokenFlags.TrailingSpaceRecommended, ref _writeContext);
+ }
+
+ _writeContext.SetNodeProperty(nameof(arrowFunctionExpression.Params), static node => ref node.As().Params);
+
+ if (arrowFunctionExpression.Params.Count == 1 && arrowFunctionExpression.Params[0].Type == Nodes.Identifier)
+ {
+ VisitAuxiliaryNodeList(in arrowFunctionExpression.Params, separator: ",");
+ }
+ else
+ {
+ Writer.WritePunctuator("(", TokenFlags.Leading, ref _writeContext);
+ VisitAuxiliaryNodeList(in arrowFunctionExpression.Params, separator: ",");
+ Writer.WritePunctuator(")", TokenFlags.Trailing, ref _writeContext);
+ }
+
+ _writeContext.ClearNodeProperty();
+ Writer.WritePunctuator("=>", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(arrowFunctionExpression.Body), static node => node.As().Body);
+ if (arrowFunctionExpression.Body is BlockStatement bodyBlockStatement)
+ {
+ VisitStatement(bodyBlockStatement, StatementFlags.IsRightMost);
+ }
+ else
+ {
+ var bodyExpression = arrowFunctionExpression.Body.As();
+ var bodyNeedsBrackets = UnaryOperandNeedsBrackets(arrowFunctionExpression, bodyExpression);
+ VisitExpression(bodyExpression, SubExpressionFlags(bodyNeedsBrackets, isLeftMost: false), static (@this, expression, flags) =>
+ @this.DisambiguateExpression(expression, ExpressionFlags.IsInsideArrowFunctionBody | ExpressionFlags.IsLeftMostInArrowFunctionBody | @this.PropagateExpressionFlags(flags)));
+ }
+
+ return arrowFunctionExpression;
+ }
+
+ protected internal override object? VisitAssignmentExpression(AssignmentExpression assignmentExpression)
+ {
+ _writeContext.SetNodeProperty(nameof(assignmentExpression.Left), static node => node.As().Left);
+ VisitAuxiliaryNode(assignmentExpression.Left);
+
+ var op = AssignmentExpression.GetAssignmentOperatorToken(assignmentExpression.Operator);
+
+ _writeContext.SetNodeProperty(nameof(assignmentExpression.Operator), static node => node.As().Operator);
+ Writer.WritePunctuator(op, TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ // AssignmentExpression is not a real binary operation because its left side is not an expression.
+ var rightNeedsBrackets = GetOperatorPrecedence(assignmentExpression, out _) > GetOperatorPrecedence(assignmentExpression.Right, out _);
+
+ _writeContext.SetNodeProperty(nameof(assignmentExpression.Right), static node => node.As().Right);
+ VisitSubExpression(assignmentExpression.Right, SubExpressionFlags(rightNeedsBrackets, isLeftMost: false));
+
+ return assignmentExpression;
+ }
+
+ protected internal override object? VisitAssignmentPattern(AssignmentPattern assignmentPattern)
+ {
+ _writeContext.SetNodeProperty(nameof(assignmentPattern.Left), static node => node.As().Left);
+ VisitAuxiliaryNode(assignmentPattern.Left);
+
+ _writeContext.ClearNodeProperty();
+ Writer.WritePunctuator("=", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(assignmentPattern.Right), static node => node.As().Right);
+ VisitAuxiliaryNode(assignmentPattern.Right);
+
+ return assignmentPattern;
+ }
+
+ protected internal override object? VisitAwaitExpression(AwaitExpression awaitExpression)
+ {
+ Writer.WriteKeyword("await", TokenFlags.TrailingSpaceRecommended, ref _writeContext);
+
+ var argumentNeedsBrackets = UnaryOperandNeedsBrackets(awaitExpression, awaitExpression.Argument);
+
+ _writeContext.SetNodeProperty(nameof(awaitExpression.Argument), static node => node.As().Argument);
+ VisitSubExpression(awaitExpression.Argument, SubExpressionFlags(argumentNeedsBrackets, isLeftMost: false));
+
+ return awaitExpression;
+ }
+
+ protected internal override object? VisitBinaryExpression(BinaryExpression binaryExpression)
+ {
+ var operationFlags = BinaryOperandsNeedBrackets(binaryExpression, binaryExpression.Left, binaryExpression.Right);
+
+ // The operand of unary operators cannot be an exponentiation without grouping.
+ // E.g. -1 ** 2 is syntactically unambiguous but the language requires (-1) ** 2 instead.
+ if (!operationFlags.HasFlagFast(BinaryOperationFlags.LeftOperandNeedsBrackets) &&
+ binaryExpression.Operator == BinaryOperator.Exponentiation &&
+ binaryExpression.Left is UnaryExpression leftUnaryExpression)
+ {
+ operationFlags |= BinaryOperationFlags.LeftOperandNeedsBrackets;
+ }
+
+ _writeContext.SetNodeProperty(nameof(binaryExpression.Left), static node => node.As().Left);
+ VisitSubExpression(binaryExpression.Left, SubExpressionFlags(operationFlags.HasFlagFast(BinaryOperationFlags.LeftOperandNeedsBrackets), isLeftMost: true));
+
+ var op = BinaryExpression.GetBinaryOperatorToken(binaryExpression.Operator);
+
+ _writeContext.SetNodeProperty(nameof(binaryExpression.Operator), static node => node.As().Operator);
+ if (char.IsLetter(op[0]))
+ {
+ Writer.WriteKeyword(op, TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+ }
+ else
+ {
+ Writer.WritePunctuator(op, TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ // Cases like 1 + (+x) must be disambiguated with brackets.
+ if (!operationFlags.HasFlagFast(BinaryOperationFlags.RightOperandNeedsBrackets) &&
+ binaryExpression.Right is UnaryExpression rightUnaryExpression &&
+ rightUnaryExpression.Prefix &&
+ op[op.Length - 1] is '+' or '-' &&
+ op[op.Length - 1] == UnaryExpression.GetUnaryOperatorToken(rightUnaryExpression.Operator)[0])
+ {
+ operationFlags |= BinaryOperationFlags.RightOperandNeedsBrackets;
+ }
+ }
+
+ _writeContext.SetNodeProperty(nameof(binaryExpression.Right), static node => node.As().Right);
+ VisitSubExpression(binaryExpression.Right, SubExpressionFlags(operationFlags.HasFlagFast(BinaryOperationFlags.RightOperandNeedsBrackets), isLeftMost: false));
+
+ return binaryExpression;
+ }
+
+ protected internal override object? VisitBlockStatement(BlockStatement blockStatement)
+ {
+ _writeContext.SetNodeProperty(nameof(blockStatement.Body), static node => ref node.As().Body);
+ Writer.StartBlock(blockStatement.Body.Count, ref _writeContext);
+
+ VisitStatementList(in blockStatement.Body);
+
+ Writer.EndBlock(blockStatement.Body.Count, ref _writeContext);
+
+ return blockStatement;
+ }
+
+ protected internal override object? VisitBreakStatement(BreakStatement breakStatement)
+ {
+ Writer.WriteKeyword("break", TokenFlags.LeadingSpaceRecommended, ref _writeContext);
+
+ if (breakStatement.Label is not null)
+ {
+ _writeContext.SetNodeProperty(nameof(breakStatement.Label), static node => node.As().Label);
+ VisitRootExpression(breakStatement.Label, RootExpressionFlags(needsBrackets: false));
+ }
+
+ StatementNeedsSemicolon();
+
+ return breakStatement;
+ }
+
+ protected internal override object? VisitCallExpression(CallExpression callExpression)
+ {
+ var calleeNeedsBrackets = UnaryOperandNeedsBrackets(callExpression, callExpression.Callee);
+
+ _writeContext.SetNodeProperty(nameof(callExpression.Callee), static node => node.As().Callee);
+ VisitSubExpression(callExpression.Callee, SubExpressionFlags(calleeNeedsBrackets, isLeftMost: true));
+
+ if (callExpression.Optional)
+ {
+ _writeContext.ClearNodeProperty();
+ Writer.WritePunctuator("?.", TokenFlags.InBetween, ref _writeContext);
+ }
+
+ _writeContext.SetNodeProperty(nameof(callExpression.Arguments), static node => ref node.As().Arguments);
+ Writer.WritePunctuator("(", TokenFlags.Leading, ref _writeContext);
+ VisitSubExpressionList(in callExpression.Arguments);
+ Writer.WritePunctuator(")", TokenFlags.Trailing, ref _writeContext);
+
+ return callExpression;
+ }
+
+ protected internal override object? VisitCatchClause(CatchClause catchClause)
+ {
+ if (catchClause.Param is not null)
+ {
+ _writeContext.SetNodeProperty(nameof(catchClause.Param), static node => node.As().Param);
+ Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, ref _writeContext);
+ VisitAuxiliaryNode(catchClause.Param);
+ Writer.WritePunctuator(")", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext);
+ }
+
+ _writeContext.SetNodeProperty(nameof(catchClause.Body), static node => node.As().Body);
+ VisitStatement(catchClause.Body, StatementBodyFlags(isRightMost: ParentNode!.As().Finalizer is null));
+
+ return catchClause;
+ }
+
+ protected internal override object? VisitChainExpression(ChainExpression chainExpression)
+ {
+ _writeContext.SetNodeProperty(nameof(chainExpression.Expression), static node => node.As().Expression);
+ VisitSubExpression(chainExpression.Expression, SubExpressionFlags(needsBrackets: false, isLeftMost: true));
+
+ return chainExpression;
+ }
+
+ protected internal override object? VisitClassBody(ClassBody classBody)
+ {
+ _writeContext.SetNodeProperty(nameof(classBody.Body), static node => ref node.As().Body);
+ Writer.StartBlock(classBody.Body.Count, ref _writeContext);
+
+ VisitAuxiliaryNodeList(in classBody.Body, separator: string.Empty);
+
+ Writer.EndBlock(classBody.Body.Count, ref _writeContext);
+
+ return classBody;
+ }
+
+ protected internal override object? VisitClassDeclaration(ClassDeclaration classDeclaration)
+ {
+ if (classDeclaration.Decorators.Count > 0)
+ {
+ _writeContext.SetNodeProperty(nameof(classDeclaration.Decorators), static node => ref node.As().Decorators);
+ VisitAuxiliaryNodeList(classDeclaration.Decorators, separator: string.Empty);
+
+ _writeContext.ClearNodeProperty();
+ }
+
+ Writer.WriteKeyword("class", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ if (classDeclaration.Id is not null)
+ {
+ _writeContext.SetNodeProperty(nameof(classDeclaration.Id), static node => node.As().Id);
+ VisitAuxiliaryNode(classDeclaration.Id);
+ }
+
+ if (classDeclaration.SuperClass is not null)
+ {
+ _writeContext.ClearNodeProperty();
+ Writer.WriteKeyword("extends", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(classDeclaration.SuperClass), static node => node.As().SuperClass);
+ VisitRootExpression(classDeclaration.SuperClass, LeftHandSideRootExpressionFlags(needsBrackets: false));
+ }
+
+ _writeContext.SetNodeProperty(nameof(classDeclaration.Body), static node => node.As().Body);
+ VisitAuxiliaryNode(classDeclaration.Body);
+
+ return classDeclaration;
+ }
+
+ protected internal override object? VisitClassExpression(ClassExpression classExpression)
+ {
+ if (classExpression.Decorators.Count > 0)
+ {
+ _writeContext.SetNodeProperty(nameof(classExpression.Decorators), static node => ref node.As().Decorators);
+ VisitAuxiliaryNodeList(classExpression.Decorators, separator: string.Empty);
+
+ _writeContext.ClearNodeProperty();
+ }
+
+ Writer.WriteKeyword("class", TokenFlags.TrailingSpaceRecommended, ref _writeContext);
+
+ if (classExpression.Id is not null)
+ {
+ _writeContext.SetNodeProperty(nameof(classExpression.Id), static node => node.As().Id);
+ VisitAuxiliaryNode(classExpression.Id);
+ }
+
+ if (classExpression.SuperClass is not null)
+ {
+ _writeContext.ClearNodeProperty();
+ Writer.WriteKeyword("extends", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(classExpression.SuperClass), static node => node.As().SuperClass);
+ VisitRootExpression(classExpression.SuperClass, LeftHandSideRootExpressionFlags(needsBrackets: false));
+ }
+
+ _writeContext.SetNodeProperty(nameof(classExpression.Body), static node => node.As().Body);
+ VisitAuxiliaryNode(classExpression.Body);
+
+ return classExpression;
+ }
+
+ protected internal override object? VisitConditionalExpression(ConditionalExpression conditionalExpression)
+ {
+ // Test expressions with the same precendence as ternary operator (such as nested conditional expression, assignment, yield, etc.) also needs brackets.
+ var operandNeedsBrackets = GetOperatorPrecedence(conditionalExpression, out _) >= GetOperatorPrecedence(conditionalExpression.Test, out _);
+
+ _writeContext.SetNodeProperty(nameof(conditionalExpression.Test), static node => node.As().Test);
+ VisitSubExpression(conditionalExpression.Test, SubExpressionFlags(operandNeedsBrackets, isLeftMost: true));
+
+ // Consequent expressions with the same precendence as ternary operator are unambiguous without brackets.
+ operandNeedsBrackets = GetOperatorPrecedence(conditionalExpression, out _) > GetOperatorPrecedence(conditionalExpression.Consequent, out _);
+
+ _writeContext.SetNodeProperty(nameof(conditionalExpression.Consequent), static node => node.As().Consequent);
+ Writer.WritePunctuator("?", TokenFlags.Leading | TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ VisitExpression(conditionalExpression.Consequent, SubExpressionFlags(operandNeedsBrackets, isLeftMost: false), static (@this, expression, flags) =>
+ // Edge case: 'in' operators in for...in loop declarations are not ambigous when they are in the consequent part of the conditional expression.
+ @this.DisambiguateExpression(expression, ~ExpressionFlags.InOperatorIsAmbiguousInDeclaration & @this.PropagateExpressionFlags(flags)));
+
+ // Alternate expressions with the same precendence as ternary operator are unambiguous without brackets, even conditional expressions because of right-to-left associativity.
+ operandNeedsBrackets = GetOperatorPrecedence(conditionalExpression, out _) > GetOperatorPrecedence(conditionalExpression.Alternate, out _);
+
+ _writeContext.SetNodeProperty(nameof(conditionalExpression.Alternate), static node => node.As().Alternate);
+ Writer.WritePunctuator(":", TokenFlags.Leading | TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ VisitSubExpression(conditionalExpression.Alternate, SubExpressionFlags(operandNeedsBrackets, isLeftMost: false));
+
+ return conditionalExpression;
+ }
+
+ protected internal override object? VisitContinueStatement(ContinueStatement continueStatement)
+ {
+ Writer.WriteKeyword("continue", TokenFlags.LeadingSpaceRecommended, ref _writeContext);
+
+ if (continueStatement.Label is not null)
+ {
+ _writeContext.SetNodeProperty(nameof(continueStatement.Label), static node => node.As().Label);
+ VisitRootExpression(continueStatement.Label, RootExpressionFlags(needsBrackets: false));
+ }
+
+ StatementNeedsSemicolon();
+
+ return continueStatement;
+ }
+
+ protected internal override object? VisitDebuggerStatement(DebuggerStatement debuggerStatement)
+ {
+ Writer.WriteKeyword("debugger", TokenFlags.LeadingSpaceRecommended, ref _writeContext);
+
+ StatementNeedsSemicolon();
+
+ return debuggerStatement;
+ }
+
+ protected internal override object? VisitDecorator(Decorator decorator)
+ {
+ // https://github.com/tc39/proposal-decorators
+
+ Writer.WritePunctuator("@", TokenFlags.Leading | (ParentNode is not Expression).ToFlag(TokenFlags.LeadingSpaceRecommended), ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(decorator.Expression), static node => node.As().Expression);
+ VisitRootExpression(decorator.Expression, LeftHandSideRootExpressionFlags(needsBrackets: false));
+
+ Writer.WriteEpsilon(TokenFlags.TrailingSpaceRecommended, ref _writeContext);
+
+ return decorator;
+ }
+
+ protected internal override object? VisitDoWhileStatement(DoWhileStatement doWhileStatement)
+ {
+ Writer.WriteKeyword("do", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(doWhileStatement.Body), static node => node.As().Body);
+ StatementFlags bodyFlags;
+ VisitStatement(doWhileStatement.Body, bodyFlags = StatementBodyFlags(isRightMost: false));
+
+ _writeContext.ClearNodeProperty();
+ Writer.WriteKeyword("while", TokenFlags.SurroundingSpaceRecommended | StatementBodyFlagsToKeywordFlags(bodyFlags), ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(doWhileStatement.Test), static node => node.As().Test);
+ VisitRootExpression(doWhileStatement.Test, ExpressionFlags.SpaceBeforeBracketsRecommended | RootExpressionFlags(needsBrackets: true));
+
+ return doWhileStatement;
+ }
+
+ protected internal override object? VisitEmptyStatement(EmptyStatement emptyStatement)
+ {
+ Writer.WritePunctuator(";", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ return emptyStatement;
+ }
+
+ protected internal override object? VisitExportAllDeclaration(ExportAllDeclaration exportAllDeclaration)
+ {
+ Writer.WriteKeyword("export", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+ Writer.WritePunctuator("*", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ if (exportAllDeclaration.Exported is not null)
+ {
+ _writeContext.SetNodeProperty(nameof(exportAllDeclaration.Exported), static node => node.As().Exported);
+ Writer.WriteKeyword("as", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ VisitExportOrImportSpecifierIdentifier(exportAllDeclaration.Exported);
+ }
+
+ _writeContext.ClearNodeProperty();
+ Writer.WriteKeyword("from", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(exportAllDeclaration.Source), static node => node.As().Source);
+ VisitRootExpression(exportAllDeclaration.Source, RootExpressionFlags(needsBrackets: false));
+
+ if (exportAllDeclaration.Assertions.Count > 0)
+ {
+ _writeContext.SetNodeProperty(nameof(exportAllDeclaration.Assertions), static node => ref node.As().Assertions);
+ VisitAssertions(in exportAllDeclaration.Assertions);
+ }
+
+ StatementNeedsSemicolon();
+
+ return exportAllDeclaration;
+ }
+
+ protected internal override object? VisitExportDefaultDeclaration(ExportDefaultDeclaration exportDefaultDeclaration)
+ {
+ Writer.WriteKeyword("export", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+ Writer.WriteKeyword("default", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(exportDefaultDeclaration.Declaration), static node => node.As().Declaration);
+ if (exportDefaultDeclaration.Declaration is Declaration declaration)
+ {
+ VisitStatement(declaration, StatementFlags.IsRightMost);
+ }
+ else
+ {
+ VisitRootExpression(exportDefaultDeclaration.Declaration.As(), ExpressionFlags.IsInsideStatementExpression | RootExpressionFlags(needsBrackets: false));
+
+ StatementNeedsSemicolon();
+ }
+
+ return exportDefaultDeclaration;
+ }
+
+ protected internal override object? VisitExportNamedDeclaration(ExportNamedDeclaration exportNamedDeclaration)
+ {
+ Writer.WriteKeyword("export", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ if (exportNamedDeclaration.Declaration is not null)
+ {
+ _writeContext.SetNodeProperty(nameof(exportNamedDeclaration.Declaration), static node => node.As().Declaration);
+ VisitStatement(exportNamedDeclaration.Declaration.As(), StatementFlags.IsRightMost);
+ }
+ else
+ {
+ _writeContext.SetNodeProperty(nameof(exportNamedDeclaration.Specifiers), static node => ref node.As().Specifiers);
+ Writer.WritePunctuator("{", TokenFlags.Leading | TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+ VisitAuxiliaryNodeList(in exportNamedDeclaration.Specifiers, separator: ",");
+ Writer.WritePunctuator("}", TokenFlags.Trailing | TokenFlags.LeadingSpaceRecommended, ref _writeContext);
+
+ if (exportNamedDeclaration.Source is not null)
+ {
+ _writeContext.ClearNodeProperty();
+ Writer.WriteKeyword("from", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(exportNamedDeclaration.Source), static node => node.As().Source);
+ VisitRootExpression(exportNamedDeclaration.Source, RootExpressionFlags(needsBrackets: false));
+
+ if (exportNamedDeclaration.Assertions.Count > 0)
+ {
+ _writeContext.SetNodeProperty(nameof(exportNamedDeclaration.Assertions), static node => ref node.As().Assertions);
+ VisitAssertions(in exportNamedDeclaration.Assertions);
+ }
+ }
+
+ StatementNeedsSemicolon();
+ }
+
+ return exportNamedDeclaration;
+ }
+
+ protected internal override object? VisitExportSpecifier(ExportSpecifier exportSpecifier)
+ {
+ _writeContext.SetNodeProperty(nameof(exportSpecifier.Local), static node => node.As().Local);
+ VisitExportOrImportSpecifierIdentifier(exportSpecifier.Local);
+
+ if (exportSpecifier.Local != exportSpecifier.Exported)
+ {
+ _writeContext.ClearNodeProperty();
+ Writer.WriteKeyword("as", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(exportSpecifier.Exported), static node => node.As().Exported);
+ VisitExportOrImportSpecifierIdentifier(exportSpecifier.Exported);
+ }
+
+ return exportSpecifier;
+ }
+
+ protected internal override object? VisitExpressionStatement(ExpressionStatement expressionStatement)
+ {
+ _writeContext.SetNodeProperty(nameof(expressionStatement.Expression), static node => node.As().Expression);
+ Writer.WriteEpsilon(TokenFlags.LeadingSpaceRecommended, ref _writeContext);
+ VisitRootExpression(expressionStatement.Expression, ExpressionFlags.IsInsideStatementExpression | RootExpressionFlags(needsBrackets: false));
+
+ StatementNeedsSemicolon();
+
+ return expressionStatement;
+ }
+
+ protected internal override object? VisitForInStatement(ForInStatement forInStatement)
+ {
+ Writer.WriteKeyword("for", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(forInStatement.Left), static node => node.As().Left);
+
+ if (forInStatement.Left is VariableDeclaration variableDeclaration)
+ {
+ VisitStatement(variableDeclaration, StatementFlags.NestedVariableDeclaration);
+ }
+ else
+ {
+ VisitAuxiliaryNode(forInStatement.Left);
+ }
+
+ _writeContext.ClearNodeProperty();
+ Writer.WriteKeyword("in", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(forInStatement.Right), static node => node.As().Right);
+ VisitRootExpression(forInStatement.Right, RootExpressionFlags(needsBrackets: false));
+
+ _writeContext.ClearNodeProperty();
+ Writer.WritePunctuator(")", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(forInStatement.Body), static node => node.As().Body);
+ VisitStatement(forInStatement.Body, StatementBodyFlags(isRightMost: true));
+
+ return forInStatement;
+ }
+
+ protected internal override object? VisitForOfStatement(ForOfStatement forOfStatement)
+ {
+ Writer.WriteKeyword("for", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ if (forOfStatement.Await)
+ {
+ _writeContext.SetNodeProperty(nameof(forOfStatement.Await), static node => node.As().Await);
+ Writer.WriteKeyword("await", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+ }
+
+ Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(forOfStatement.Left), static node => node.As().Left);
+
+ if (forOfStatement.Left is VariableDeclaration variableDeclaration)
+ {
+ VisitStatement(variableDeclaration, StatementFlags.NestedVariableDeclaration);
+ }
+ else
+ {
+ VisitAuxiliaryNode(forOfStatement.Left);
+ }
+
+ _writeContext.ClearNodeProperty();
+ Writer.WriteKeyword("of", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(forOfStatement.Right), static node => node.As().Right);
+ VisitRootExpression(forOfStatement.Right, RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(forOfStatement.Right)));
+
+ _writeContext.ClearNodeProperty();
+ Writer.WritePunctuator(")", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(forOfStatement.Body), static node => node.As().Body);
+ VisitStatement(forOfStatement.Body, StatementBodyFlags(isRightMost: true));
+
+ return forOfStatement;
+ }
+
+ protected internal override object? VisitForStatement(ForStatement forStatement)
+ {
+ Writer.WriteKeyword("for", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(forStatement.Init), static node => node.As().Init);
+
+ if (forStatement.Init is not null)
+ {
+ if (forStatement.Init is VariableDeclaration variableDeclaration)
+ {
+ VisitStatement(variableDeclaration, StatementFlags.NestedVariableDeclaration);
+ }
+ else
+ {
+ VisitRootExpression(forStatement.Init.As(), RootExpressionFlags(needsBrackets: false));
+ }
+ }
+
+ Writer.WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(forStatement.Test), static node => node.As().Test);
+
+ if (forStatement.Test is not null)
+ {
+ VisitRootExpression(forStatement.Test, RootExpressionFlags(needsBrackets: false));
+ }
+
+ Writer.WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext);
+
+ if (forStatement.Update is not null)
+ {
+ _writeContext.SetNodeProperty(nameof(forStatement.Update), static node => node.As().Update);
+
+ VisitRootExpression(forStatement.Update, RootExpressionFlags(needsBrackets: false));
+ }
+
+ _writeContext.ClearNodeProperty();
+ Writer.WritePunctuator(")", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(forStatement.Body), static node => node.As().Body);
+ VisitStatement(forStatement.Body, StatementBodyFlags(isRightMost: true));
+
+ return forStatement;
+ }
+
+ protected internal override object? VisitFunctionDeclaration(FunctionDeclaration functionDeclaration)
+ {
+ if (functionDeclaration.Async)
+ {
+ _writeContext.SetNodeProperty(nameof(functionDeclaration.Async), static node => node.As().Async);
+ Writer.WriteKeyword("async", TokenFlags.LeadingSpaceRecommended, ref _writeContext);
+
+ _writeContext.ClearNodeProperty();
+ }
+
+ Writer.WriteKeyword("function", TokenFlags.LeadingSpaceRecommended, ref _writeContext);
+
+ if (functionDeclaration.Generator)
+ {
+ _writeContext.SetNodeProperty(nameof(functionDeclaration.Generator), static node => node.As().Generator);
+ Writer.WritePunctuator("*", (functionDeclaration.Id is not null).ToFlag(TokenFlags.TrailingSpaceRecommended), ref _writeContext);
+ }
+
+ if (functionDeclaration.Id is not null)
+ {
+ _writeContext.SetNodeProperty(nameof(functionDeclaration.Id), static node => node.As().Id);
+ VisitAuxiliaryNode(functionDeclaration.Id);
+ }
+
+ _writeContext.SetNodeProperty(nameof(functionDeclaration.Params), static node => ref node.As().Params);
+ Writer.WritePunctuator("(", TokenFlags.Leading, ref _writeContext);
+ VisitAuxiliaryNodeList(in functionDeclaration.Params, separator: ",");
+ Writer.WritePunctuator(")", TokenFlags.Trailing, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(functionDeclaration.Body), static node => node.As().Body);
+ VisitStatement(functionDeclaration.Body, StatementBodyFlags(isRightMost: true));
+
+ return functionDeclaration;
+ }
+
+ protected internal override object? VisitFunctionExpression(FunctionExpression functionExpression)
+ {
+ if (!_currentExpressionFlags.HasFlagFast(ExpressionFlags.IsMethod))
+ {
+ if (functionExpression.Async)
+ {
+ _writeContext.SetNodeProperty(nameof(functionExpression.Async), static node => node.As().Async);
+ Writer.WriteKeyword("async", ref _writeContext);
+
+ _writeContext.ClearNodeProperty();
+ }
+
+ Writer.WriteKeyword("function", ref _writeContext);
+
+ if (functionExpression.Generator)
+ {
+ _writeContext.SetNodeProperty(nameof(functionExpression.Generator), static node => node.As().Generator);
+ Writer.WritePunctuator("*", (functionExpression.Id is not null).ToFlag(TokenFlags.TrailingSpaceRecommended), ref _writeContext);
+ }
+
+ if (functionExpression.Id is not null)
+ {
+ _writeContext.SetNodeProperty(nameof(functionExpression.Id), static node => node.As().Id);
+ VisitAuxiliaryNode(functionExpression.Id);
+ }
+ }
+ else
+ {
+ var keyIsFirstToken = true;
+
+ if (functionExpression.Async)
+ {
+ _writeContext.SetNodeProperty(nameof(functionExpression.Async), static node => node.As().Async);
+ Writer.WriteKeyword("async", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ keyIsFirstToken = false;
+ }
+
+ if (functionExpression.Generator)
+ {
+ _writeContext.SetNodeProperty(nameof(functionExpression.Generator), static node => node.As().Generator);
+ Writer.WritePunctuator("*", TokenFlags.LeadingSpaceRecommended, ref _writeContext);
+
+ keyIsFirstToken = false;
+ }
+
+ _writeContext.SetNodeProperty(nameof(functionExpression.Id), static node => node.As().Id);
+ var property = (IProperty) ParentNode!;
+ if (property.Kind != PropertyKind.Constructor || property.Key.Type == Nodes.Literal)
+ {
+ if (keyIsFirstToken && !property.Computed)
+ {
+ Writer.WriteEpsilon(TokenFlags.LeadingSpaceRecommended, ref _writeContext);
+ }
+
+ VisitPropertyKey(property.Key, property.Computed, leadingBracketFlags: keyIsFirstToken.ToFlag(TokenFlags.LeadingSpaceRecommended));
+ }
+ else
+ {
+ Writer.WriteKeyword("constructor", TokenFlags.LeadingSpaceRecommended, ref _writeContext);
+ }
+ }
+
+ _writeContext.SetNodeProperty(nameof(functionExpression.Params), static node => ref node.As().Params);
+ Writer.WritePunctuator("(", TokenFlags.Leading, ref _writeContext);
+ VisitAuxiliaryNodeList(in functionExpression.Params, separator: ",");
+ Writer.WritePunctuator(")", TokenFlags.Trailing, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(functionExpression.Body), static node => node.As().Body);
+ VisitStatement(functionExpression.Body, StatementBodyFlags(isRightMost: true));
+
+ return functionExpression;
+ }
+
+ protected internal override object? VisitIdentifier(Identifier identifier)
+ {
+ _writeContext.SetNodeProperty(nameof(identifier.Name), static node => node.As().Name);
+ Writer.WriteIdentifier(identifier.Name!, ref _writeContext);
+
+ return identifier;
+ }
+
+ protected internal override object? VisitIfStatement(IfStatement ifStatement)
+ {
+ Writer.WriteKeyword("if", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(ifStatement.Test), static node => node.As().Test);
+ VisitRootExpression(ifStatement.Test, ExpressionFlags.SpaceAroundBracketsRecommended | RootExpressionFlags(needsBrackets: true));
+
+ _writeContext.SetNodeProperty(nameof(ifStatement.Consequent), static node => node.As().Consequent);
+ StatementFlags bodyFlags;
+ VisitStatement(ifStatement.Consequent, bodyFlags = StatementBodyFlags(isRightMost: ifStatement.Alternate is null));
+
+ if (ifStatement.Alternate is not null)
+ {
+ _writeContext.ClearNodeProperty();
+ Writer.WriteKeyword("else", TokenFlags.SurroundingSpaceRecommended | StatementBodyFlagsToKeywordFlags(bodyFlags), ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(ifStatement.Alternate), static node => node.As().Alternate);
+ VisitStatement(ifStatement.Alternate, StatementBodyFlags(isRightMost: true));
+ }
+
+ return ifStatement;
+ }
+
+ protected internal override object? VisitImport(Import import)
+ {
+ Writer.WriteKeyword("import", ref _writeContext);
+
+ Writer.WritePunctuator("(", TokenFlags.Leading, ref _writeContext);
+
+ // Import arguments need special care because of the unusual model (separate expressions instead of an expression list).
+
+ var paramCount = import.Attributes is null ? 1 : 2;
+ Writer.StartExpressionList(paramCount, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(Import.Source), static node => node.As().Source);
+ VisitExpressionListItem(import.Source, 0, paramCount, static (@this, expression, _, _) =>
+ s_getCombinedSubExpressionFlags(@this, expression, SubExpressionFlags(@this.ExpressionNeedsBracketsInList(expression), isLeftMost: false)));
+
+ if (import.Attributes is not null)
+ {
+ // https://github.com/tc39/proposal-import-assertions
+
+ _writeContext.SetNodeProperty(nameof(Import.Attributes), static node => node.As().Attributes);
+ VisitExpressionListItem(import.Attributes, 1, paramCount, static (@this, expression, _, _) =>
+ s_getCombinedSubExpressionFlags(@this, expression, SubExpressionFlags(@this.ExpressionNeedsBracketsInList(expression), isLeftMost: false)));
+ }
+
+ Writer.EndExpressionList(paramCount, ref _writeContext);
+
+ _writeContext.ClearNodeProperty();
+ Writer.WritePunctuator(")", TokenFlags.Trailing, ref _writeContext);
+
+ return import;
+ }
+
+ protected internal override object? VisitImportAttribute(ImportAttribute importAttribute)
+ {
+ // https://github.com/tc39/proposal-import-assertions
+
+ _writeContext.SetNodeProperty(nameof(importAttribute.Key), static node => node.As().Key);
+ VisitPropertyKey(importAttribute.Key, computed: false);
+ Writer.WritePunctuator(":", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(importAttribute.Value), static node => node.As().Value);
+
+ VisitRootExpression(importAttribute.Value, RootExpressionFlags(needsBrackets: false));
+
+ return importAttribute;
+ }
+
+ protected internal override object? VisitImportDeclaration(ImportDeclaration importDeclaration)
+ {
+ Writer.WriteKeyword("import", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ // Specifiers need special care because of the unusual syntax.
+
+ _writeContext.SetNodeProperty(nameof(importDeclaration.Specifiers), static node => ref node.As().Specifiers);
+ Writer.StartAuxiliaryNodeList(importDeclaration.Specifiers.Count, ref _writeContext);
+
+ if (importDeclaration.Specifiers.Count == 0)
+ {
+ Writer.EndAuxiliaryNodeList(count: 0, ref _writeContext);
+
+ goto WriteSource;
+ }
+
+ var index = 0;
+ Func getNodeContext = static delegate { return null; };
+
+ if (importDeclaration.Specifiers[index].Type == Nodes.ImportDefaultSpecifier)
+ {
+ VisitAuxiliaryNodeListItem(importDeclaration.Specifiers[index], index, importDeclaration.Specifiers.Count, ",", getNodeContext);
+
+ if (++index >= importDeclaration.Specifiers.Count)
+ {
+ goto EndSpecifiers;
+ }
+ }
+
+ if (importDeclaration.Specifiers[index].Type == Nodes.ImportNamespaceSpecifier)
+ {
+ VisitAuxiliaryNodeListItem(importDeclaration.Specifiers[index], index, importDeclaration.Specifiers.Count, ",", getNodeContext);
+
+ if (++index >= importDeclaration.Specifiers.Count)
+ {
+ goto EndSpecifiers;
+ }
+ }
+
+ Writer.WritePunctuator("{", TokenFlags.Leading | TokenFlags.TrailingSpaceRecommended, ref _writeContext);
+
+ for (; index < importDeclaration.Specifiers.Count; index++)
+ {
+ VisitAuxiliaryNodeListItem(importDeclaration.Specifiers[index], index, importDeclaration.Specifiers.Count, ",", getNodeContext);
+ }
+
+ Writer.WritePunctuator("}", TokenFlags.Trailing | TokenFlags.LeadingSpaceRecommended, ref _writeContext);
+
+EndSpecifiers:
+ Writer.EndAuxiliaryNodeList(importDeclaration.Specifiers.Count, ref _writeContext);
+
+ _writeContext.ClearNodeProperty();
+ Writer.WriteKeyword("from", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+WriteSource:
+ _writeContext.SetNodeProperty(nameof(importDeclaration.Source), static node => node.As().Source);
+ VisitRootExpression(importDeclaration.Source, RootExpressionFlags(needsBrackets: false));
+
+ if (importDeclaration.Assertions.Count > 0)
+ {
+ _writeContext.SetNodeProperty(nameof(importDeclaration.Assertions), static node => ref node.As().Assertions);
+ VisitAssertions(in importDeclaration.Assertions);
+ }
+
+ StatementNeedsSemicolon();
+
+ return importDeclaration;
+ }
+
+ protected internal override object? VisitImportDefaultSpecifier(ImportDefaultSpecifier importDefaultSpecifier)
+ {
+ _writeContext.SetNodeProperty(nameof(importDefaultSpecifier.Local), static node => node.As().Local);
+ VisitAuxiliaryNode(importDefaultSpecifier.Local);
+
+ return importDefaultSpecifier;
+ }
+
+ protected internal override object? VisitImportNamespaceSpecifier(ImportNamespaceSpecifier importNamespaceSpecifier)
+ {
+ Writer.WritePunctuator("*", TokenFlags.TrailingSpaceRecommended, ref _writeContext);
+
+ Writer.WriteKeyword("as", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(importNamespaceSpecifier.Local), static node => node.As().Local);
+ VisitAuxiliaryNode(importNamespaceSpecifier.Local);
+
+ return importNamespaceSpecifier;
+ }
+
+ protected internal override object? VisitImportSpecifier(ImportSpecifier importSpecifier)
+ {
+ if (importSpecifier.Imported != importSpecifier.Local)
+ {
+ _writeContext.SetNodeProperty(nameof(importSpecifier.Imported), static node => node.As().Imported);
+ VisitExportOrImportSpecifierIdentifier(importSpecifier.Imported);
+
+ _writeContext.ClearNodeProperty();
+ Writer.WriteKeyword("as", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+ }
+
+ _writeContext.SetNodeProperty(nameof(importSpecifier.Local), static node => node.As().Local);
+ VisitAuxiliaryNode(importSpecifier.Local);
+
+ return importSpecifier;
+ }
+
+ protected internal override object? VisitLabeledStatement(LabeledStatement labeledStatement)
+ {
+ _writeContext.SetNodeProperty(nameof(labeledStatement.Label), static node => node.As().Label);
+ Writer.WriteEpsilon(TokenFlags.LeadingSpaceRecommended, ref _writeContext);
+ VisitAuxiliaryNode(labeledStatement.Label);
+
+ Writer.WritePunctuator(":", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(labeledStatement.Body), static node => node.As().Body);
+ VisitStatement(labeledStatement.Body, StatementFlags.IsRightMost);
+
+ return labeledStatement;
+ }
+
+ protected internal override object? VisitLiteral(Literal literal)
+ {
+ _writeContext.SetNodeProperty(nameof(literal.Raw), static node => node.As().Raw);
+ Writer.WriteLiteral(literal.Raw, literal.TokenType, ref _writeContext);
+
+ return literal;
+ }
+
+ protected internal override object? VisitMemberExpression(MemberExpression memberExpression)
+ {
+ var operationFlags = BinaryOperandsNeedBrackets(memberExpression, memberExpression.Object, memberExpression.Property);
+
+ // Cases like 1.toString() must be disambiguated with brackets.
+ if (!operationFlags.HasFlagFast(BinaryOperationFlags.LeftOperandNeedsBrackets) &&
+ memberExpression is { Computed: false, Optional: false, Object: Literal objectLiteral } &&
+ objectLiteral.TokenType == TokenType.NumericLiteral &&
+ objectLiteral.Raw.IndexOf('.') < 0)
+ {
+ operationFlags |= BinaryOperationFlags.LeftOperandNeedsBrackets;
+ }
+
+ _writeContext.SetNodeProperty(nameof(memberExpression.Object), static node => node.As().Object);
+ VisitSubExpression(memberExpression.Object, SubExpressionFlags(operationFlags.HasFlagFast(BinaryOperationFlags.LeftOperandNeedsBrackets), isLeftMost: true));
+
+ if (memberExpression.Computed)
+ {
+ if (memberExpression.Optional)
+ {
+ _writeContext.ClearNodeProperty();
+ Writer.WritePunctuator("?.", TokenFlags.InBetween, ref _writeContext);
+ }
+
+ _writeContext.SetNodeProperty(nameof(memberExpression.Property), static node => node.As().Property);
+ Writer.WritePunctuator("[", TokenFlags.Leading, ref _writeContext);
+ VisitSubExpression(memberExpression.Property, SubExpressionFlags(needsBrackets: false, isLeftMost: false));
+ Writer.WritePunctuator("]", TokenFlags.Trailing, ref _writeContext);
+ }
+ else
+ {
+ _writeContext.ClearNodeProperty();
+ Writer.WritePunctuator(memberExpression.Optional ? "?." : ".", TokenFlags.InBetween, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(memberExpression.Property), static node => node.As().Property);
+ VisitSubExpression(memberExpression.Property, SubExpressionFlags(needsBrackets: false, isLeftMost: false));
+ }
+
+ return memberExpression;
+ }
+
+ protected internal override object? VisitMetaProperty(MetaProperty metaProperty)
+ {
+ _writeContext.SetNodeProperty(nameof(metaProperty.Meta), static node => node.As().Meta);
+ Writer.WriteKeyword(metaProperty.Meta.Name!, ref _writeContext);
+
+ _writeContext.ClearNodeProperty();
+ Writer.WritePunctuator(".", TokenFlags.InBetween, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(metaProperty.Property), static node => node.As().Property);
+ VisitSubExpression(metaProperty.Property, SubExpressionFlags(needsBrackets: false, isLeftMost: false));
+
+ return metaProperty;
+ }
+
+ protected internal override object? VisitMethodDefinition(MethodDefinition methodDefinition)
+ {
+ if (methodDefinition.Decorators.Count > 0)
+ {
+ _writeContext.SetNodeProperty(nameof(methodDefinition.Decorators), static node => ref node.As().Decorators);
+ VisitAuxiliaryNodeList(methodDefinition.Decorators, separator: string.Empty);
+
+ _writeContext.ClearNodeProperty();
+ }
+
+ if (methodDefinition.Static)
+ {
+ _writeContext.SetNodeProperty(nameof(methodDefinition.Static), static node => node.As().Static);
+ Writer.WriteKeyword("static", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+ }
+
+ switch (methodDefinition.Kind)
+ {
+ case PropertyKind.Get:
+ _writeContext.SetNodeProperty(nameof(methodDefinition.Kind), static node => node.As().Kind);
+ Writer.WriteKeyword("get", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+ break;
+ case PropertyKind.Set:
+ _writeContext.SetNodeProperty(nameof(methodDefinition.Kind), static node => node.As().Kind);
+ Writer.WriteKeyword("set", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+ break;
+ }
+
+ _writeContext.SetNodeProperty(nameof(methodDefinition.Value), static node => node.As().Value);
+ VisitRootExpression(methodDefinition.Value, ExpressionFlags.IsMethod | RootExpressionFlags(needsBrackets: false));
+
+ return methodDefinition;
+ }
+
+ protected internal override object? VisitNewExpression(NewExpression newExpression)
+ {
+ Writer.WriteKeyword("new", TokenFlags.TrailingSpaceRecommended, ref _writeContext);
+
+ var calleeNeedsBrackets = UnaryOperandNeedsBrackets(newExpression, newExpression.Callee);
+
+ _writeContext.SetNodeProperty(nameof(newExpression.Callee), static node => node.As().Callee);
+ VisitExpression(newExpression.Callee, SubExpressionFlags(calleeNeedsBrackets, isLeftMost: false), static (@this, expression, flags) =>
+ @this.DisambiguateExpression(expression, ExpressionFlags.IsInsideNewCallee | ExpressionFlags.IsLeftMostInNewCallee | @this.PropagateExpressionFlags(flags)));
+
+ if (newExpression.Arguments.Count > 0)
+ {
+ _writeContext.SetNodeProperty(nameof(newExpression.Arguments), static node => ref node.As().Arguments);
+ Writer.WritePunctuator("(", TokenFlags.Leading, ref _writeContext);
+ VisitSubExpressionList(in newExpression.Arguments);
+ Writer.WritePunctuator(")", TokenFlags.Trailing, ref _writeContext);
+ }
+
+ return newExpression;
+ }
+
+ protected internal override object? VisitObjectExpression(ObjectExpression objectExpression)
+ {
+ _writeContext.SetNodeProperty(nameof(objectExpression.Properties), static node => ref node.As().Properties);
+
+ Writer.StartObject(objectExpression.Properties.Count, ref _writeContext);
+
+ // Properties need special care because it may contain spread elements, which are actual expressions (as opposed to normal properties).
+
+ Writer.StartAuxiliaryNodeList(objectExpression.Properties.Count, ref _writeContext);
+
+ for (var i = 0; i < objectExpression.Properties.Count; i++)
+ {
+ var property = objectExpression.Properties[i];
+ if (property is SpreadElement spreadElement)
+ {
+ var originalAuxiliaryNodeContext = _currentAuxiliaryNodeContext;
+ _currentAuxiliaryNodeContext = null;
+
+ Writer.StartAuxiliaryNodeListItem(i, objectExpression.Properties.Count, separator: ",", _currentAuxiliaryNodeContext, ref _writeContext);
+ VisitRootExpression(spreadElement, RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(spreadElement)));
+ Writer.EndAuxiliaryNodeListItem(i, objectExpression.Properties.Count, separator: ",", _currentAuxiliaryNodeContext, ref _writeContext);
+
+ _currentAuxiliaryNodeContext = originalAuxiliaryNodeContext;
+ }
+ else
+ {
+ VisitAuxiliaryNodeListItem(property, i, objectExpression.Properties.Count, separator: ",", static delegate { return null; });
+ }
+ }
+
+ Writer.EndAuxiliaryNodeList(objectExpression.Properties.Count, ref _writeContext);
+
+ Writer.EndObject(objectExpression.Properties.Count, ref _writeContext);
+
+ return objectExpression;
+ }
+
+ protected internal override object? VisitObjectPattern(ObjectPattern objectPattern)
+ {
+ _writeContext.SetNodeProperty(nameof(objectPattern.Properties), static node => ref node.As().Properties);
+
+ Writer.StartObject(objectPattern.Properties.Count, ref _writeContext);
+
+ VisitAuxiliaryNodeList(in objectPattern.Properties, separator: ",");
+
+ Writer.EndObject(objectPattern.Properties.Count, ref _writeContext);
+
+ return objectPattern;
+ }
+
+ protected internal override object? VisitPrivateIdentifier(PrivateIdentifier privateIdentifier)
+ {
+ _writeContext.SetNodeProperty(nameof(privateIdentifier.Name), static node => node.As().Name);
+ Writer.WritePunctuator("#", TokenFlags.Leading, ref _writeContext);
+ Writer.WriteIdentifier(privateIdentifier.Name!, ref _writeContext);
+
+ return privateIdentifier;
+ }
+
+ protected internal override object? VisitProgram(Program program)
+ {
+ _writeContext.SetNodeProperty(nameof(program.Body), static node => ref node.As().Body);
+ VisitStatementList(in program.Body);
+
+ return program;
+ }
+
+ protected internal override object? VisitProperty(Property property)
+ {
+ bool isMethod;
+
+ switch (property.Kind)
+ {
+ case PropertyKind.Get:
+ _writeContext.SetNodeProperty(nameof(property.Kind), static node => node.As().Kind);
+ Writer.WriteKeyword("get", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ isMethod = true;
+ break;
+ case PropertyKind.Set:
+ _writeContext.SetNodeProperty(nameof(property.Kind), static node => node.As().Kind);
+ Writer.WriteKeyword("set", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ isMethod = true;
+ break;
+ case PropertyKind.Init when property.Method:
+ isMethod = true;
+ break;
+ default:
+ if (!property.Shorthand)
+ {
+ _writeContext.SetNodeProperty(nameof(property.Key), static node => node.As().Key);
+ VisitPropertyKey(property.Key, property.Computed, leadingBracketFlags: TokenFlags.LeadingSpaceRecommended);
+ Writer.WritePunctuator(":", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext);
+ }
+
+ isMethod = false;
+ break;
+ }
+
+ _writeContext.SetNodeProperty(nameof(property.Value), static node => node.As().Value);
+
+ if (ParentNode is { Type: Nodes.ObjectPattern })
+ {
+ VisitAuxiliaryNode(property.Value);
+ }
+ else
+ {
+ var expression = property.Value.As();
+ VisitRootExpression(expression, isMethod.ToFlag(ExpressionFlags.IsMethod) | RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(expression)));
+ }
+
+ return property;
+ }
+
+ protected internal override object? VisitPropertyDefinition(PropertyDefinition propertyDefinition)
+ {
+ if (propertyDefinition.Decorators.Count > 0)
+ {
+ _writeContext.SetNodeProperty(nameof(propertyDefinition.Decorators), static node => ref node.As().Decorators);
+ VisitAuxiliaryNodeList(propertyDefinition.Decorators, separator: string.Empty);
+
+ _writeContext.ClearNodeProperty();
+ }
+
+ if (propertyDefinition.Static)
+ {
+ _writeContext.SetNodeProperty(nameof(propertyDefinition.Static), static node => node.As().Static);
+ Writer.WriteKeyword("static", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+ }
+
+ _writeContext.SetNodeProperty(nameof(propertyDefinition.Key), static node => node.As().Key);
+ VisitPropertyKey(propertyDefinition.Key, propertyDefinition.Computed, leadingBracketFlags: TokenFlags.LeadingSpaceRecommended);
+
+ if (propertyDefinition.Value is not null)
+ {
+ _writeContext.ClearNodeProperty();
+ Writer.WritePunctuator("=", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(propertyDefinition.Value), static node => node.As().Value);
+ VisitRootExpression(propertyDefinition.Value, RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(propertyDefinition.Value)));
+ }
+
+ Writer.WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext);
+
+ return propertyDefinition;
+ }
+
+ protected internal override object? VisitRestElement(RestElement restElement)
+ {
+ _writeContext.SetNodeProperty(nameof(restElement.Argument), static node => node.As().Argument);
+ Writer.WritePunctuator("...", TokenFlags.Leading, ref _writeContext);
+
+ VisitAuxiliaryNode(restElement.Argument);
+
+ return restElement;
+ }
+
+ protected internal override object? VisitReturnStatement(ReturnStatement returnStatement)
+ {
+ Writer.WriteKeyword("return", (returnStatement.Argument is not null).ToFlag(TokenFlags.SurroundingSpaceRecommended, TokenFlags.LeadingSpaceRecommended), ref _writeContext);
+
+ if (returnStatement.Argument is not null)
+ {
+ _writeContext.SetNodeProperty(nameof(returnStatement.Argument), static node => node.As().Argument);
+ VisitRootExpression(returnStatement.Argument, RootExpressionFlags(needsBrackets: false));
+ }
+
+ StatementNeedsSemicolon();
+
+ return returnStatement;
+ }
+
+ protected internal override object? VisitSequenceExpression(SequenceExpression sequenceExpression)
+ {
+ _writeContext.SetNodeProperty(nameof(sequenceExpression.Expressions), static node => ref node.As().Expressions);
+
+ VisitExpressionList(in sequenceExpression.Expressions, static (@this, expression, index, _) =>
+ s_getCombinedSubExpressionFlags(@this, expression, SubExpressionFlags(@this.ExpressionNeedsBracketsInList(expression), isLeftMost: index == 0)));
+
+ return sequenceExpression;
+ }
+
+ protected internal override object? VisitSpreadElement(SpreadElement spreadElement)
+ {
+ var argumentNeedsBrackets = UnaryOperandNeedsBrackets(spreadElement, spreadElement.Argument);
+
+ _writeContext.SetNodeProperty(nameof(spreadElement.Argument), static node => node.As().Argument);
+ Writer.WritePunctuator("...", TokenFlags.Leading, ref _writeContext);
+
+ VisitSubExpression(spreadElement.Argument, SubExpressionFlags(argumentNeedsBrackets, isLeftMost: false));
+
+ return spreadElement;
+ }
+
+ protected internal override object? VisitStaticBlock(StaticBlock staticBlock)
+ {
+ Writer.WriteKeyword("static", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(staticBlock.Body), static node => ref node.As().Body);
+ Writer.StartBlock(staticBlock.Body.Count, ref _writeContext);
+
+ VisitStatementList(in staticBlock.Body);
+
+ Writer.EndBlock(staticBlock.Body.Count, ref _writeContext);
+
+ return staticBlock;
+ }
+
+ protected internal override object? VisitSuper(Super super)
+ {
+ Writer.WriteKeyword("super", ref _writeContext);
+
+ return super;
+ }
+
+ protected internal override object? VisitSwitchCase(SwitchCase switchCase)
+ {
+ if (switchCase.Test is not null)
+ {
+ Writer.WriteKeyword("case", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(switchCase.Test), static node => node.As().Test);
+ VisitRootExpression(switchCase.Test, RootExpressionFlags(needsBrackets: false));
+
+ _writeContext.ClearNodeProperty();
+ }
+ else
+ {
+ Writer.WriteKeyword("default", TokenFlags.LeadingSpaceRecommended, ref _writeContext);
+ }
+
+ Writer.WritePunctuator(":", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(switchCase.Consequent), static node => ref node.As().Consequent);
+
+ if (_currentAuxiliaryNodeContext == s_lastSwitchCaseFlag)
+ {
+ // If this is the last case, then the right-most semicolon can be omitted.
+ VisitStatementList(in switchCase.Consequent);
+ }
+ else
+ {
+ // If this isn't the last case, then the right-most semicolon must not be omitted!
+ VisitStatementList(in switchCase.Consequent, static delegate { return StatementFlags.None; });
+ }
+
+ return switchCase;
+ }
+
+ protected internal override object? VisitSwitchStatement(SwitchStatement switchStatement)
+ {
+ Writer.WriteKeyword("switch", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(switchStatement.Discriminant), static node => node.As().Discriminant);
+ VisitRootExpression(switchStatement.Discriminant, ExpressionFlags.SpaceAroundBracketsRecommended | RootExpressionFlags(needsBrackets: true));
+
+ _writeContext.SetNodeProperty(nameof(switchStatement.Cases), static node => ref node.As().Cases);
+ Writer.StartBlock(switchStatement.Cases.Count, ref _writeContext);
+
+ // Passes contextual information about whether it's the last one in the statement or not to each SwitchCase.
+ VisitAuxiliaryNodeList(in switchStatement.Cases, separator: string.Empty, static (_, _, index, count) =>
+ index == count - 1 ? s_lastSwitchCaseFlag : null);
+
+ Writer.EndBlock(switchStatement.Cases.Count, ref _writeContext);
+
+ return switchStatement;
+ }
+
+ protected internal override object? VisitTaggedTemplateExpression(TaggedTemplateExpression taggedTemplateExpression)
+ {
+ _writeContext.SetNodeProperty(nameof(taggedTemplateExpression.Tag), static node => node.As().Tag);
+ VisitExpression(taggedTemplateExpression.Tag, SubExpressionFlags(needsBrackets: false, isLeftMost: true), static (@this, expression, flags) =>
+ @this.DisambiguateExpression(expression, ExpressionFlags.IsInsideLeftHandSideExpression | ExpressionFlags.IsLeftMostInLeftHandSideExpression | @this.PropagateExpressionFlags(flags)));
+
+ _writeContext.SetNodeProperty(nameof(taggedTemplateExpression.Quasi), static node => node.As().Quasi);
+ VisitSubExpression(taggedTemplateExpression.Quasi, SubExpressionFlags(needsBrackets: false, isLeftMost: false));
+
+ return taggedTemplateExpression;
+ }
+
+ protected internal override object? VisitTemplateElement(TemplateElement templateElement)
+ {
+ _writeContext.SetNodeProperty(nameof(templateElement.Value), static node => node.As().Value);
+ Writer.WriteLiteral(templateElement.Value.Raw, TokenType.Template, ref _writeContext);
+
+ return templateElement;
+ }
+
+ protected internal override object? VisitTemplateLiteral(TemplateLiteral templateLiteral)
+ {
+ Writer.WritePunctuator("`", TokenFlags.Leading, ref _writeContext);
+
+ TemplateElement quasi;
+ for (var i = 0; !(quasi = templateLiteral.Quasis[i]).Tail; i++)
+ {
+ _writeContext.SetNodeProperty(nameof(templateLiteral.Quasis), static node => ref node.As().Quasis);
+ VisitAuxiliaryNode(quasi);
+
+ _writeContext.SetNodeProperty(nameof(templateLiteral.Expressions), static node => ref node.As().Expressions);
+ Writer.WritePunctuator("${", TokenFlags.Leading, ref _writeContext);
+ VisitRootExpression(templateLiteral.Expressions[i], RootExpressionFlags(needsBrackets: false));
+ Writer.WritePunctuator("}", TokenFlags.Trailing, ref _writeContext);
+ }
+
+ _writeContext.SetNodeProperty(nameof(templateLiteral.Quasis), static node => ref node.As().Quasis);
+ VisitAuxiliaryNode(quasi);
+
+ Writer.WritePunctuator("`", TokenFlags.Trailing, ref _writeContext);
+
+ return templateLiteral;
+ }
+
+ protected internal override object? VisitThisExpression(ThisExpression thisExpression)
+ {
+ Writer.WriteKeyword("this", ref _writeContext);
+
+ return thisExpression;
+ }
+
+ protected internal override object? VisitThrowStatement(ThrowStatement throwStatement)
+ {
+ Writer.WriteKeyword("throw", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(throwStatement.Argument), static node => node.As().Argument);
+ VisitRootExpression(throwStatement.Argument, RootExpressionFlags(needsBrackets: false));
+
+ StatementNeedsSemicolon();
+
+ return throwStatement;
+ }
+
+ protected internal override object? VisitTryStatement(TryStatement tryStatement)
+ {
+ Writer.WriteKeyword("try", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(tryStatement.Block), static node => node.As().Block);
+ StatementFlags bodyFlags;
+ VisitStatement(tryStatement.Block, bodyFlags = StatementBodyFlags(isRightMost: false));
+
+ if (tryStatement.Handler is not null)
+ {
+ _writeContext.ClearNodeProperty();
+ Writer.WriteKeyword("catch", TokenFlags.SurroundingSpaceRecommended | StatementBodyFlagsToKeywordFlags(bodyFlags), ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(tryStatement.Handler), static node => node.As().Handler);
+ VisitAuxiliaryNode(tryStatement.Handler);
+ bodyFlags = StatementBodyFlags(isRightMost: tryStatement.Finalizer is null);
+ }
+
+ if (tryStatement.Finalizer is not null)
+ {
+ _writeContext.ClearNodeProperty();
+ Writer.WriteKeyword("finally", TokenFlags.SurroundingSpaceRecommended | StatementBodyFlagsToKeywordFlags(bodyFlags), ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(tryStatement.Finalizer), static node => node.As().Finalizer);
+ VisitStatement(tryStatement.Finalizer, StatementBodyFlags(isRightMost: true));
+ }
+
+ return tryStatement;
+ }
+
+ protected internal override object? VisitUnaryExpression(UnaryExpression unaryExpression)
+ {
+ var argumentNeedsBrackets = UnaryOperandNeedsBrackets(unaryExpression, unaryExpression.Argument);
+ var op = UnaryExpression.GetUnaryOperatorToken(unaryExpression.Operator);
+
+ if (unaryExpression.Prefix)
+ {
+ _writeContext.SetNodeProperty(nameof(unaryExpression.Operator), static node => node.As().Operator);
+ if (char.IsLetter(op[0]))
+ {
+ Writer.WriteKeyword(op, TokenFlags.TrailingSpaceRecommended, ref _writeContext);
+ }
+ else
+ {
+ Writer.WritePunctuator(op, TokenFlags.Leading, ref _writeContext);
+
+ // Cases like +(+x) or +(++x) must be disambiguated with brackets.
+ if (!argumentNeedsBrackets &&
+ unaryExpression.Argument is UnaryExpression argumentUnaryExpression &&
+ argumentUnaryExpression.Prefix &&
+ op[op.Length - 1] is '+' or '-' &&
+ op[op.Length - 1] == UnaryExpression.GetUnaryOperatorToken(argumentUnaryExpression.Operator)[0])
+ {
+ argumentNeedsBrackets = true;
+ }
+ }
+
+ _writeContext.SetNodeProperty(nameof(unaryExpression.Argument), static node => node.As().Argument);
+ VisitSubExpression(unaryExpression.Argument, SubExpressionFlags(argumentNeedsBrackets, isLeftMost: false));
+ }
+ else
+ {
+ _writeContext.SetNodeProperty(nameof(unaryExpression.Argument), static node => node.As().Argument);
+ VisitSubExpression(unaryExpression.Argument, SubExpressionFlags(argumentNeedsBrackets, isLeftMost: true));
+
+ _writeContext.SetNodeProperty(nameof(unaryExpression.Operator), static node => node.As().Operator);
+ Writer.WritePunctuator(op, TokenFlags.Trailing, ref _writeContext);
+ }
+
+ return unaryExpression;
+ }
+
+ protected internal override object? VisitVariableDeclaration(VariableDeclaration variableDeclaration)
+ {
+ _writeContext.SetNodeProperty(nameof(variableDeclaration.Kind), static node => node.As().Kind);
+ Writer.WriteKeyword(VariableDeclaration.GetVariableDeclarationKindToken(variableDeclaration.Kind),
+ _currentStatementFlags.HasFlagFast(StatementFlags.NestedVariableDeclaration).ToFlag(TokenFlags.TrailingSpaceRecommended, TokenFlags.SurroundingSpaceRecommended), ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(variableDeclaration.Declarations), static node => ref node.As().Declarations);
+
+ if (!_currentStatementFlags.HasFlagFast(StatementFlags.NestedVariableDeclaration))
+ {
+ VisitAuxiliaryNodeList(in variableDeclaration.Declarations, separator: ",");
+
+ StatementNeedsSemicolon();
+ }
+ else if (ParentNode is not { Type: Nodes.ForStatement or Nodes.ForInStatement })
+ {
+ VisitAuxiliaryNodeList(in variableDeclaration.Declarations, separator: ",");
+ }
+ else
+ {
+ VisitAuxiliaryNodeList(in variableDeclaration.Declarations, separator: ",", static delegate { return s_forLoopInitDeclarationFlag; });
+ }
+
+ return variableDeclaration;
+ }
+
+ protected internal override object? VisitVariableDeclarator(VariableDeclarator variableDeclarator)
+ {
+ _writeContext.SetNodeProperty(nameof(variableDeclarator.Id), static node => node.As().Id);
+ VisitAuxiliaryNode(variableDeclarator.Id);
+
+ if (variableDeclarator.Init is not null)
+ {
+ _writeContext.ClearNodeProperty();
+ Writer.WritePunctuator("=", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(variableDeclarator.Init), static node => node.As().Init);
+
+ if (_currentAuxiliaryNodeContext != s_forLoopInitDeclarationFlag)
+ {
+ VisitRootExpression(variableDeclarator.Init, RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(variableDeclarator.Init)));
+ }
+ else
+ {
+ VisitRootExpression(variableDeclarator.Init, ExpressionFlags.InOperatorIsAmbiguousInDeclaration | RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(variableDeclarator.Init)));
+ }
+ }
+
+ return variableDeclarator;
+ }
+
+ protected internal override object? VisitWhileStatement(WhileStatement whileStatement)
+ {
+ Writer.WriteKeyword("while", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(whileStatement.Test), static node => node.As().Test);
+ VisitRootExpression(whileStatement.Test, ExpressionFlags.SpaceAroundBracketsRecommended | RootExpressionFlags(needsBrackets: true));
+
+ _writeContext.SetNodeProperty(nameof(whileStatement.Body), static node => node.As().Body);
+ VisitStatement(whileStatement.Body, StatementBodyFlags(isRightMost: true));
+
+ return whileStatement;
+ }
+
+ protected internal override object? VisitWithStatement(WithStatement withStatement)
+ {
+ Writer.WriteKeyword("with", TokenFlags.SurroundingSpaceRecommended, ref _writeContext);
+
+ _writeContext.SetNodeProperty(nameof(withStatement.Object), static node => node.As().Object);
+ VisitRootExpression(withStatement.Object, ExpressionFlags.SpaceAroundBracketsRecommended | RootExpressionFlags(needsBrackets: true));
+
+ _writeContext.SetNodeProperty(nameof(withStatement.Body), static node => node.As().Body);
+ VisitStatement(withStatement.Body, StatementBodyFlags(isRightMost: true));
+
+ return withStatement;
+ }
+
+ protected internal override object? VisitYieldExpression(YieldExpression yieldExpression)
+ {
+ Writer.WriteKeyword("yield", (!yieldExpression.Delegate && yieldExpression.Argument is not null).ToFlag(TokenFlags.TrailingSpaceRecommended), ref _writeContext);
+
+ if (yieldExpression.Delegate)
+ {
+ _writeContext.SetNodeProperty(nameof(yieldExpression.Delegate), static node => node.As().Delegate);
+ Writer.WritePunctuator("*", (yieldExpression.Argument is not null).ToFlag(TokenFlags.TrailingSpaceRecommended), ref _writeContext);
+ }
+
+ if (yieldExpression.Argument is not null)
+ {
+ var argumentNeedsBrackets = UnaryOperandNeedsBrackets(yieldExpression, yieldExpression.Argument);
+
+ _writeContext.SetNodeProperty(nameof(yieldExpression.Argument), static node => node.As().Argument);
+ VisitSubExpression(yieldExpression.Argument, SubExpressionFlags(argumentNeedsBrackets, isLeftMost: false));
+ }
+
+ return yieldExpression;
+ }
+}
diff --git a/src/Esprima/Utils/AstToJson.cs b/src/Esprima/Utils/AstToJson.cs
new file mode 100644
index 00000000..cbb090d9
--- /dev/null
+++ b/src/Esprima/Utils/AstToJson.cs
@@ -0,0 +1,88 @@
+using Esprima.Ast;
+
+namespace Esprima.Utils;
+
+public enum LocationMembersPlacement
+{
+ End,
+ Start
+}
+
+internal enum AstToJsonTestCompatibilityMode
+{
+ None,
+ EsprimaOrg,
+}
+
+public record class AstToJsonOptions
+{
+ public static readonly AstToJsonOptions Default = new();
+
+ public bool IncludingLineColumn { get; init; }
+ public bool IncludingRange { get; init; }
+ public LocationMembersPlacement LocationMembersPlacement { get; init; }
+ ///
+ /// This switch is intended for enabling a compatibility mode for to build a JSON output
+ /// which matches the format of the test fixtures of the original Esprima project.
+ ///
+ internal AstToJsonTestCompatibilityMode TestCompatibilityMode { get; init; }
+
+ protected internal virtual AstToJsonConverter CreateConverter(JsonWriter writer) => new AstToJsonConverter(writer, this);
+}
+
+public static class AstToJson
+{
+ public static string ToJsonString(this Node node)
+ {
+ return ToJsonString(node, indent: null);
+ }
+
+ public static string ToJsonString(this Node node, string? indent)
+ {
+ return ToJsonString(node, AstToJsonOptions.Default, indent);
+ }
+
+ public static string ToJsonString(this Node node, AstToJsonOptions options)
+ {
+ return ToJsonString(node, options, indent: null);
+ }
+
+ public static string ToJsonString(this Node node, AstToJsonOptions options, string? indent)
+ {
+ using (var writer = new StringWriter())
+ {
+ WriteJson(node, writer, options, indent);
+ return writer.ToString();
+ }
+ }
+
+ public static void WriteJson(this Node node, TextWriter writer)
+ {
+ WriteJson(node, writer, indent: null);
+ }
+
+ public static void WriteJson(this Node node, TextWriter writer, string? indent)
+ {
+ WriteJson(node, writer, AstToJsonOptions.Default, indent);
+ }
+
+ public static void WriteJson(this Node node, TextWriter writer, AstToJsonOptions options)
+ {
+ WriteJson(node, writer, options, indent: null);
+ }
+
+ public static void WriteJson(this Node node, TextWriter writer, AstToJsonOptions options, string? indent)
+ {
+ WriteJson(node, new JsonTextWriter(writer, indent), options);
+ }
+
+ public static void WriteJson(this Node node, JsonWriter writer, AstToJsonOptions options)
+ {
+ if (options is null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ options.CreateConverter(writer).Convert(node);
+ }
+}
diff --git a/src/Esprima/Utils/AstToJsonConverter.cs b/src/Esprima/Utils/AstToJsonConverter.cs
new file mode 100644
index 00000000..1e00aa21
--- /dev/null
+++ b/src/Esprima/Utils/AstToJsonConverter.cs
@@ -0,0 +1,1132 @@
+using System.Collections;
+using System.Globalization;
+using System.Numerics;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Text.RegularExpressions;
+using Esprima.Ast;
+
+namespace Esprima.Utils;
+
+public class AstToJsonConverter : AstVisitor
+{
+ private readonly JsonWriter _writer;
+ private protected readonly bool _includeLineColumn;
+ private protected readonly bool _includeRange;
+ private protected readonly LocationMembersPlacement _locationMembersPlacement;
+ private protected readonly AstToJsonTestCompatibilityMode _testCompatibilityMode;
+
+ public AstToJsonConverter(JsonWriter writer, AstToJsonOptions options)
+ {
+ _writer = writer ?? throw new ArgumentNullException(nameof(writer));
+
+ if (options is null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ _includeLineColumn = options.IncludingLineColumn;
+ _includeRange = options.IncludingRange;
+ _locationMembersPlacement = options.LocationMembersPlacement;
+ _testCompatibilityMode = options.TestCompatibilityMode;
+ }
+
+ protected virtual string GetNodeType(Node node)
+ {
+ return node.Type.ToString();
+ }
+
+ private void WriteLocationInfo(Node node)
+ {
+ if (node is ChainExpression)
+ {
+ return;
+ }
+
+ if (_includeRange)
+ {
+ _writer.Member("range");
+ _writer.StartArray();
+ _writer.Number(node.Range.Start);
+ _writer.Number(node.Range.End);
+ _writer.EndArray();
+ }
+
+ if (_includeLineColumn)
+ {
+ _writer.Member("loc");
+ _writer.StartObject();
+ _writer.Member("start");
+ Write(node.Location.Start);
+ _writer.Member("end");
+ Write(node.Location.End);
+ _writer.EndObject();
+ }
+
+ void Write(Position position)
+ {
+ _writer.StartObject();
+ Member("line", position.Line);
+ Member("column", position.Column);
+ _writer.EndObject();
+ }
+ }
+
+ private void OnStartNodeObject(Node node)
+ {
+ _writer.StartObject();
+
+ if ((_includeLineColumn || _includeRange)
+ && _locationMembersPlacement == LocationMembersPlacement.Start)
+ {
+ WriteLocationInfo(node);
+ }
+
+ Member("type", GetNodeType(node));
+ }
+
+ private void OnFinishNodeObject(Node node)
+ {
+ if ((_includeLineColumn || _includeRange)
+ && _locationMembersPlacement == LocationMembersPlacement.End)
+ {
+ WriteLocationInfo(node);
+ }
+
+ _writer.EndObject();
+ }
+
+ protected readonly struct NodeObjectDisposable : IDisposable
+ {
+ private readonly AstToJsonConverter _converter;
+ private readonly Node _node;
+
+ public NodeObjectDisposable(AstToJsonConverter converter, Node node)
+ {
+ _converter = converter;
+ _node = node;
+ }
+
+ public void Dispose()
+ {
+ _converter.OnFinishNodeObject(_node);
+ }
+ }
+
+ protected NodeObjectDisposable StartNodeObject(Node node)
+ {
+ OnStartNodeObject(node);
+ return new NodeObjectDisposable(this, node);
+ }
+
+ protected void EmptyNodeObject(Node node)
+ {
+ using (StartNodeObject(node)) { }
+ }
+
+ protected void Member(string name)
+ {
+ _writer.Member(name);
+ }
+
+ protected void Member(string name, Node? node)
+ {
+ Member(name);
+ Visit(node);
+ }
+
+ protected void Member(string name, string? value)
+ {
+ Member(name);
+ _writer.String(value);
+ }
+
+ protected void Member(string name, bool value)
+ {
+ Member(name);
+ _writer.Boolean(value);
+ }
+
+ protected void Member(string name, int value)
+ {
+ Member(name);
+ _writer.Number(value);
+ }
+
+ private static readonly ConditionalWeakTable EnumMap = new();
+
+ protected void Member(string name, T value) where T : Enum
+ {
+ var map = (Dictionary)
+ EnumMap.GetValue(value.GetType(),
+ t => t.GetRuntimeFields()
+ .Where(f => f.IsStatic)
+ .ToDictionary(f => (T) f.GetValue(null), f => f.Name.ToLowerInvariant()));
+ Member(name, map[value]);
+ }
+
+ protected void Member(string name, in NodeList nodes) where T : Node?
+ {
+ Member(name, nodes, node => node);
+ }
+
+ protected void Member(string name, in NodeList list, Func nodeSelector) where T : Node?
+ {
+ Member(name);
+ _writer.StartArray();
+ foreach (var item in list)
+ {
+ Visit(nodeSelector(item));
+ }
+
+ _writer.EndArray();
+ }
+
+ public void Convert(Node node)
+ {
+ Visit(node ?? throw new ArgumentNullException(nameof(node)));
+ }
+
+ public override object? Visit(Node? node)
+ {
+ if (node is not null)
+ {
+ return base.Visit(node);
+ }
+ else
+ {
+ _writer.Null();
+ return node!;
+ }
+ }
+
+ protected internal override object? VisitArrayExpression(ArrayExpression arrayExpression)
+ {
+ using (StartNodeObject(arrayExpression))
+ {
+ Member("elements", arrayExpression.Elements);
+ }
+
+ return arrayExpression;
+ }
+
+ protected internal override object? VisitArrayPattern(ArrayPattern arrayPattern)
+ {
+ using (StartNodeObject(arrayPattern))
+ {
+ Member("elements", arrayPattern.Elements);
+ }
+
+ return arrayPattern;
+ }
+
+ protected internal override object? VisitArrowFunctionExpression(ArrowFunctionExpression arrowFunctionExpression)
+ {
+ using (StartNodeObject(arrowFunctionExpression))
+ {
+ Member("id", ((IFunction) arrowFunctionExpression).Id);
+ Member("params", arrowFunctionExpression.Params);
+ Member("body", arrowFunctionExpression.Body);
+ Member("generator", ((IFunction) arrowFunctionExpression).Generator);
+ Member("expression", arrowFunctionExpression.Expression);
+ // original Esprima doesn't include this information yet
+ if (_testCompatibilityMode != AstToJsonTestCompatibilityMode.EsprimaOrg)
+ {
+ Member("strict", arrowFunctionExpression.Strict);
+ }
+ Member("async", arrowFunctionExpression.Async);
+ }
+
+ return arrowFunctionExpression;
+ }
+
+ protected internal override object? VisitAssignmentExpression(AssignmentExpression assignmentExpression)
+ {
+ using (StartNodeObject(assignmentExpression))
+ {
+ Member("operator", AssignmentExpression.GetAssignmentOperatorToken(assignmentExpression.Operator));
+ Member("left", assignmentExpression.Left);
+ Member("right", assignmentExpression.Right);
+ }
+
+ return assignmentExpression;
+ }
+
+ protected internal override object? VisitAssignmentPattern(AssignmentPattern assignmentPattern)
+ {
+ using (StartNodeObject(assignmentPattern))
+ {
+ Member("left", assignmentPattern.Left);
+ Member("right", assignmentPattern.Right);
+ }
+
+ return assignmentPattern;
+ }
+
+ protected internal override object? VisitAwaitExpression(AwaitExpression awaitExpression)
+ {
+ using (StartNodeObject(awaitExpression))
+ {
+ Member("argument", awaitExpression.Argument);
+ }
+
+ return awaitExpression;
+ }
+
+ protected internal override object? VisitBinaryExpression(BinaryExpression binaryExpression)
+ {
+ using (StartNodeObject(binaryExpression))
+ {
+ Member("operator", BinaryExpression.GetBinaryOperatorToken(binaryExpression.Operator));
+ Member("left", binaryExpression.Left);
+ Member("right", binaryExpression.Right);
+ }
+
+ return binaryExpression;
+ }
+
+ protected internal override object? VisitBlockStatement(BlockStatement blockStatement)
+ {
+ using (StartNodeObject(blockStatement))
+ {
+ Member("body", blockStatement.Body, e => (Statement) e);
+ }
+
+ return blockStatement;
+ }
+
+ protected internal override object? VisitBreakStatement(BreakStatement breakStatement)
+ {
+ using (StartNodeObject(breakStatement))
+ {
+ Member("label", breakStatement.Label);
+ }
+
+ return breakStatement;
+ }
+
+ protected internal override object? VisitCallExpression(CallExpression callExpression)
+ {
+ using (StartNodeObject(callExpression))
+ {
+ Member("callee", callExpression.Callee);
+ Member("arguments", callExpression.Arguments, e => e);
+ Member("optional", callExpression.Optional);
+ }
+
+ return callExpression;
+ }
+
+ protected internal override object? VisitCatchClause(CatchClause catchClause)
+ {
+ using (StartNodeObject(catchClause))
+ {
+ Member("param", catchClause.Param);
+ Member("body", catchClause.Body);
+ }
+
+ return catchClause;
+ }
+
+ protected internal override object? VisitChainExpression(ChainExpression chainExpression)
+ {
+ using (StartNodeObject(chainExpression))
+ {
+ Member("expression", chainExpression.Expression);
+ }
+
+ return chainExpression;
+ }
+
+ protected internal override object? VisitClassBody(ClassBody classBody)
+ {
+ using (StartNodeObject(classBody))
+ {
+ Member("body", classBody.Body);
+ }
+
+ return classBody;
+ }
+
+ protected internal override object? VisitClassDeclaration(ClassDeclaration classDeclaration)
+ {
+ using (StartNodeObject(classDeclaration))
+ {
+ Member("id", classDeclaration.Id);
+ Member("superClass", classDeclaration.SuperClass);
+ Member("body", classDeclaration.Body);
+ if (classDeclaration.Decorators.Count > 0)
+ {
+ Member("decorators", classDeclaration.Decorators);
+ }
+ }
+
+ return classDeclaration;
+ }
+
+ protected internal override object? VisitClassExpression(ClassExpression classExpression)
+ {
+ using (StartNodeObject(classExpression))
+ {
+ Member("id", classExpression.Id);
+ Member("superClass", classExpression.SuperClass);
+ Member("body", classExpression.Body);
+ if (classExpression.Decorators.Count > 0)
+ {
+ Member("decorators", classExpression.Decorators);
+ }
+ }
+
+ return classExpression;
+ }
+
+ protected internal override object? VisitConditionalExpression(ConditionalExpression conditionalExpression)
+ {
+ using (StartNodeObject(conditionalExpression))
+ {
+ Member("test", conditionalExpression.Test);
+ Member("consequent", conditionalExpression.Consequent);
+ Member("alternate", conditionalExpression.Alternate);
+ }
+
+ return conditionalExpression;
+ }
+
+ protected internal override object? VisitContinueStatement(ContinueStatement continueStatement)
+ {
+ using (StartNodeObject(continueStatement))
+ {
+ Member("label", continueStatement.Label);
+ }
+
+ return continueStatement;
+ }
+
+ protected internal override object? VisitDebuggerStatement(DebuggerStatement debuggerStatement)
+ {
+ EmptyNodeObject(debuggerStatement);
+ return debuggerStatement;
+ }
+
+ protected internal override object? VisitDecorator(Decorator decorator)
+ {
+ using (StartNodeObject(decorator))
+ {
+ Member("expression", decorator.Expression);
+ }
+
+ return decorator;
+ }
+
+ protected internal override object? VisitDoWhileStatement(DoWhileStatement doWhileStatement)
+ {
+ using (StartNodeObject(doWhileStatement))
+ {
+ Member("body", doWhileStatement.Body);
+ Member("test", doWhileStatement.Test);
+ }
+
+ return doWhileStatement;
+ }
+
+ protected internal override object? VisitEmptyStatement(EmptyStatement emptyStatement)
+ {
+ EmptyNodeObject(emptyStatement);
+ return emptyStatement;
+ }
+
+ protected internal override object? VisitExportAllDeclaration(ExportAllDeclaration exportAllDeclaration)
+ {
+ using (StartNodeObject(exportAllDeclaration))
+ {
+ Member("source", exportAllDeclaration.Source);
+
+ // original Esprima doesn't include this information yet
+ if (_testCompatibilityMode != AstToJsonTestCompatibilityMode.EsprimaOrg)
+ {
+ Member("exported", exportAllDeclaration.Exported);
+ if (exportAllDeclaration.Assertions.Count > 0)
+ {
+ Member("assertions", exportAllDeclaration.Assertions);
+ }
+ }
+ }
+
+ return exportAllDeclaration;
+ }
+
+ protected internal override object? VisitExportDefaultDeclaration(ExportDefaultDeclaration exportDefaultDeclaration)
+ {
+ using (StartNodeObject(exportDefaultDeclaration))
+ {
+ Member("declaration", exportDefaultDeclaration.Declaration);
+ }
+
+ return exportDefaultDeclaration;
+ }
+
+ protected internal override object? VisitExportNamedDeclaration(ExportNamedDeclaration exportNamedDeclaration)
+ {
+ using (StartNodeObject(exportNamedDeclaration))
+ {
+ Member("declaration", exportNamedDeclaration.Declaration);
+ Member("specifiers", exportNamedDeclaration.Specifiers);
+ Member("source", exportNamedDeclaration.Source);
+ // original Esprima doesn't include this information yet
+ if (_testCompatibilityMode != AstToJsonTestCompatibilityMode.EsprimaOrg && exportNamedDeclaration.Assertions.Count > 0)
+ {
+ Member("assertions", exportNamedDeclaration.Assertions);
+ }
+ }
+
+ return exportNamedDeclaration;
+ }
+
+ protected internal override object? VisitExportSpecifier(ExportSpecifier exportSpecifier)
+ {
+ using (StartNodeObject(exportSpecifier))
+ {
+ Member("exported", exportSpecifier.Exported);
+ Member("local", exportSpecifier.Local);
+ }
+
+ return exportSpecifier;
+ }
+
+ protected internal override object? VisitExpressionStatement(ExpressionStatement expressionStatement)
+ {
+ using (StartNodeObject(expressionStatement))
+ {
+ if (expressionStatement is Directive d)
+ {
+ Member("directive", d.Directiv);
+ }
+
+ Member("expression", expressionStatement.Expression);
+ }
+
+ return expressionStatement;
+ }
+
+ protected internal override object? VisitForInStatement(ForInStatement forInStatement)
+ {
+ using (StartNodeObject(forInStatement))
+ {
+ Member("left", forInStatement.Left);
+ Member("right", forInStatement.Right);
+ Member("body", forInStatement.Body);
+ Member("each", false);
+ }
+
+ return forInStatement;
+ }
+
+ protected internal override object? VisitForOfStatement(ForOfStatement forOfStatement)
+ {
+ using (StartNodeObject(forOfStatement))
+ {
+ Member("await", forOfStatement.Await);
+ Member("left", forOfStatement.Left);
+ Member("right", forOfStatement.Right);
+ Member("body", forOfStatement.Body);
+ }
+
+ return forOfStatement;
+ }
+
+ protected internal override object? VisitForStatement(ForStatement forStatement)
+ {
+ using (StartNodeObject(forStatement))
+ {
+ Member("init", forStatement.Init);
+ Member("test", forStatement.Test);
+ Member("update", forStatement.Update);
+ Member("body", forStatement.Body);
+ }
+
+ return forStatement;
+ }
+
+ protected internal override object? VisitFunctionDeclaration(FunctionDeclaration functionDeclaration)
+ {
+ using (StartNodeObject(functionDeclaration))
+ {
+ Member("id", functionDeclaration.Id);
+ Member("params", functionDeclaration.Params);
+ Member("body", functionDeclaration.Body);
+ Member("generator", functionDeclaration.Generator);
+ Member("expression", ((IFunction) functionDeclaration).Expression);
+ // original Esprima doesn't include this information yet
+ if (_testCompatibilityMode != AstToJsonTestCompatibilityMode.EsprimaOrg)
+ {
+ Member("strict", functionDeclaration.Strict);
+ }
+ Member("async", functionDeclaration.Async);
+ }
+
+ return functionDeclaration;
+ }
+
+ protected internal override object? VisitFunctionExpression(FunctionExpression functionExpression)
+ {
+ using (StartNodeObject(functionExpression))
+ {
+ Member("id", functionExpression.Id);
+ Member("params", functionExpression.Params);
+ Member("body", functionExpression.Body);
+ Member("generator", functionExpression.Generator);
+ Member("expression", ((IFunction) functionExpression).Expression);
+ // original Esprima doesn't include this information yet
+ if (_testCompatibilityMode != AstToJsonTestCompatibilityMode.EsprimaOrg)
+ {
+ Member("strict", functionExpression.Strict);
+ }
+ Member("async", functionExpression.Async);
+ }
+
+ return functionExpression;
+ }
+
+ protected internal override object? VisitIdentifier(Identifier identifier)
+ {
+ using (StartNodeObject(identifier))
+ {
+ Member("name", identifier.Name);
+ }
+
+ return identifier;
+ }
+
+ protected internal override object? VisitIfStatement(IfStatement ifStatement)
+ {
+ using (StartNodeObject(ifStatement))
+ {
+ Member("test", ifStatement.Test);
+ Member("consequent", ifStatement.Consequent);
+ Member("alternate", ifStatement.Alternate);
+ }
+
+ return ifStatement;
+ }
+
+ private object? VisitImportCompat(ImportCompat import)
+ {
+ EmptyNodeObject(import);
+ return import;
+ }
+
+ private sealed class ImportCompat : Expression
+ {
+ public ImportCompat() : base(Nodes.Import) { }
+
+ internal override Node? NextChildNode(ref ChildNodes.Enumerator enumerator) => null;
+
+ protected internal override object? Accept(AstVisitor visitor) => ((AstToJsonConverter) visitor).VisitImportCompat(this);
+ }
+
+ protected internal override object? VisitImport(Import import)
+ {
+ // original Esprima uses CallExpression to represent dynamic imports currently,
+ // so we need to rewrite our representation to match this expectation
+ if (_testCompatibilityMode == AstToJsonTestCompatibilityMode.EsprimaOrg)
+ {
+ const string importToken = "import";
+
+ var callee = new ImportCompat
+ {
+ Location = new Location(import.Location.Start, new Position(import.Location.Start.Line, import.Location.Start.Column + importToken.Length)),
+ Range = new Ast.Range(import.Range.Start, import.Range.Start + importToken.Length)
+ };
+ var args = new NodeList(new Expression[] { import.Source });
+ var callExpression = new CallExpression(callee, args, optional: false)
+ {
+ Location = import.Location,
+ Range = import.Range,
+ };
+
+ return Visit(callExpression);
+ }
+
+ using (StartNodeObject(import))
+ {
+ if (_testCompatibilityMode != AstToJsonTestCompatibilityMode.EsprimaOrg)
+ {
+ Member("source", import.Source);
+
+ if (import.Attributes is not null)
+ {
+ Member("attributes", import.Attributes);
+ }
+ }
+ }
+
+ return import;
+ }
+
+ protected internal override object? VisitImportAttribute(ImportAttribute importAttribute)
+ {
+ using (StartNodeObject(importAttribute))
+ {
+ Member("key", importAttribute.Key);
+ Member("value", importAttribute.Value);
+ }
+
+ return importAttribute;
+ }
+
+ protected internal override object? VisitImportDeclaration(ImportDeclaration importDeclaration)
+ {
+ using (StartNodeObject(importDeclaration))
+ {
+ Member("specifiers", importDeclaration.Specifiers, e => (Node) e);
+ Member("source", importDeclaration.Source);
+ // original Esprima doesn't include this information yet
+ if (importDeclaration.Assertions.Count > 0)
+ {
+ Member("assertions", importDeclaration.Assertions);
+ }
+ }
+
+ return importDeclaration;
+ }
+
+ protected internal override object? VisitImportDefaultSpecifier(ImportDefaultSpecifier importDefaultSpecifier)
+ {
+ using (StartNodeObject(importDefaultSpecifier))
+ {
+ Member("local", importDefaultSpecifier.Local);
+ }
+
+ return importDefaultSpecifier;
+ }
+
+ protected internal override object? VisitImportNamespaceSpecifier(ImportNamespaceSpecifier importNamespaceSpecifier)
+ {
+ using (StartNodeObject(importNamespaceSpecifier))
+ {
+ Member("local", importNamespaceSpecifier.Local);
+ }
+
+ return importNamespaceSpecifier;
+ }
+
+ protected internal override object? VisitImportSpecifier(ImportSpecifier importSpecifier)
+ {
+ using (StartNodeObject(importSpecifier))
+ {
+ Member("local", importSpecifier.Local);
+ Member("imported", importSpecifier.Imported);
+ }
+
+ return importSpecifier;
+ }
+
+ protected internal override object? VisitLabeledStatement(LabeledStatement labeledStatement)
+ {
+ using (StartNodeObject(labeledStatement))
+ {
+ Member("label", labeledStatement.Label);
+ Member("body", labeledStatement.Body);
+ }
+
+ return labeledStatement;
+ }
+
+ protected internal override object? VisitLiteral(Literal literal)
+ {
+ using (StartNodeObject(literal))
+ {
+ _writer.Member("value");
+ var value = literal.Value;
+
+ switch (value)
+ {
+ case null:
+ if (_testCompatibilityMode != AstToJsonTestCompatibilityMode.EsprimaOrg && literal.TokenType == TokenType.RegularExpression)
+ {
+ // This is how esprima.org actually renders regexes since it relies on Regex.toString
+ _writer.String(literal.Raw);
+ }
+ else
+ {
+ _writer.Null();
+ }
+
+ break;
+ case bool b:
+ _writer.Boolean(b);
+ break;
+ case Regex _:
+ _writer.StartObject();
+ _writer.EndObject();
+ break;
+ case double d:
+ _writer.Number(d);
+ break;
+ default:
+ _writer.String(System.Convert.ToString(value, CultureInfo.InvariantCulture));
+ break;
+ }
+
+ Member("raw", literal.Raw);
+
+ if (literal.Regex is not null)
+ {
+ _writer.Member("regex");
+ _writer.StartObject();
+ Member("pattern", literal.Regex.Pattern);
+ Member("flags", literal.Regex.Flags);
+ _writer.EndObject();
+ }
+ else if (literal.Value is BigInteger bigInt)
+ {
+ Member("bigint", bigInt.ToString(CultureInfo.InvariantCulture));
+ }
+ }
+
+ return literal;
+ }
+
+ protected internal override object? VisitMemberExpression(MemberExpression memberExpression)
+ {
+ using (StartNodeObject(memberExpression))
+ {
+ Member("computed", memberExpression.Computed);
+ Member("object", memberExpression.Object);
+ Member("property", memberExpression.Property);
+ Member("optional", memberExpression.Optional);
+ }
+
+ return memberExpression;
+ }
+
+ protected internal override object? VisitMetaProperty(MetaProperty metaProperty)
+ {
+ using (StartNodeObject(metaProperty))
+ {
+ Member("meta", metaProperty.Meta);
+ Member("property", metaProperty.Property);
+ }
+
+ return metaProperty;
+ }
+
+ protected internal override object? VisitMethodDefinition(MethodDefinition methodDefinition)
+ {
+ using (StartNodeObject(methodDefinition))
+ {
+ Member("key", methodDefinition.Key);
+ Member("computed", methodDefinition.Computed);
+ Member("value", methodDefinition.Value);
+ Member("kind", methodDefinition.Kind);
+ Member("static", methodDefinition.Static);
+ if (methodDefinition.Decorators.Count > 0)
+ {
+ Member("decorators", methodDefinition.Decorators);
+ }
+ }
+
+ return methodDefinition;
+ }
+
+ protected internal override object? VisitNewExpression(NewExpression newExpression)
+ {
+ using (StartNodeObject(newExpression))
+ {
+ Member("callee", newExpression.Callee);
+ Member("arguments", newExpression.Arguments, e => (Node) e);
+ }
+
+ return newExpression;
+ }
+
+ protected internal override object? VisitObjectExpression(ObjectExpression objectExpression)
+ {
+ using (StartNodeObject(objectExpression))
+ {
+ Member("properties", objectExpression.Properties);
+ }
+
+ return objectExpression;
+ }
+
+ protected internal override object? VisitObjectPattern(ObjectPattern objectPattern)
+ {
+ using (StartNodeObject(objectPattern))
+ {
+ Member("properties", objectPattern.Properties);
+ }
+
+ return objectPattern;
+ }
+
+ protected internal override object? VisitPrivateIdentifier(PrivateIdentifier privateIdentifier)
+ {
+ using (StartNodeObject(privateIdentifier))
+ {
+ Member("name", privateIdentifier.Name);
+ }
+
+ return privateIdentifier;
+ }
+
+ protected internal override object? VisitProgram(Program program)
+ {
+ using (StartNodeObject(program))
+ {
+ Member("body", program.Body, e => (Node) e);
+ Member("sourceType", program.SourceType);
+
+ // original Esprima doesn't include this information yet
+ if (_testCompatibilityMode != AstToJsonTestCompatibilityMode.EsprimaOrg && program is Script s)
+ {
+ Member("strict", s.Strict);
+ }
+ }
+
+ return program;
+ }
+
+ protected internal override object? VisitProperty(Property property)
+ {
+ using (StartNodeObject(property))
+ {
+ Member("key", property.Key);
+ Member("computed", property.Computed);
+ Member("value", property.Value);
+ Member("kind", property.Kind);
+ Member("method", property.Method);
+ Member("shorthand", property.Shorthand);
+ }
+
+ return property;
+ }
+
+ protected internal override object? VisitPropertyDefinition(PropertyDefinition propertyDefinition)
+ {
+ using (StartNodeObject(propertyDefinition))
+ {
+ Member("key", propertyDefinition.Key);
+ Member("computed", propertyDefinition.Computed);
+ Member("value", propertyDefinition.Value);
+ Member("kind", propertyDefinition.Kind);
+ Member("static", propertyDefinition.Static);
+ if (propertyDefinition.Decorators.Count > 0)
+ {
+ Member("decorators", propertyDefinition.Decorators);
+ }
+ }
+
+ return propertyDefinition;
+ }
+
+ protected internal override object? VisitRestElement(RestElement restElement)
+ {
+ using (StartNodeObject(restElement))
+ {
+ Member("argument", restElement.Argument);
+ }
+
+ return restElement;
+ }
+
+ protected internal override object? VisitReturnStatement(ReturnStatement returnStatement)
+ {
+ using (StartNodeObject(returnStatement))
+ {
+ Member("argument", returnStatement.Argument);
+ }
+
+ return returnStatement;
+ }
+
+ protected internal override object? VisitSequenceExpression(SequenceExpression sequenceExpression)
+ {
+ using (StartNodeObject(sequenceExpression))
+ {
+ Member("expressions", sequenceExpression.Expressions);
+ }
+
+ return sequenceExpression;
+ }
+
+ protected internal override object? VisitSpreadElement(SpreadElement spreadElement)
+ {
+ using (StartNodeObject(spreadElement))
+ {
+ Member("argument", spreadElement.Argument);
+ }
+
+ return spreadElement;
+ }
+
+ protected internal override object? VisitStaticBlock(StaticBlock staticBlock)
+ {
+ using (StartNodeObject(staticBlock))
+ {
+ Member("body", staticBlock.Body, e => (Statement) e);
+ }
+
+ return staticBlock;
+ }
+
+ protected internal override object? VisitSuper(Super super)
+ {
+ EmptyNodeObject(super);
+ return super;
+ }
+
+ protected internal override object? VisitSwitchCase(SwitchCase switchCase)
+ {
+ using (StartNodeObject(switchCase))
+ {
+ Member("test", switchCase.Test);
+ Member("consequent", switchCase.Consequent, e => (Node) e);
+ }
+
+ return switchCase;
+ }
+
+ protected internal override object? VisitSwitchStatement(SwitchStatement switchStatement)
+ {
+ using (StartNodeObject(switchStatement))
+ {
+ Member("discriminant", switchStatement.Discriminant);
+ Member("cases", switchStatement.Cases);
+ }
+
+ return switchStatement;
+ }
+
+ protected internal override object? VisitTaggedTemplateExpression(TaggedTemplateExpression taggedTemplateExpression)
+ {
+ using (StartNodeObject(taggedTemplateExpression))
+ {
+ Member("tag", taggedTemplateExpression.Tag);
+ Member("quasi", taggedTemplateExpression.Quasi);
+ }
+
+ return taggedTemplateExpression;
+ }
+
+ protected internal override object? VisitTemplateElement(TemplateElement templateElement)
+ {
+ using (StartNodeObject(templateElement))
+ {
+ _writer.Member("value");
+ _writer.StartObject();
+ Member("raw", templateElement.Value.Raw);
+ Member("cooked", templateElement.Value.Cooked);
+ _writer.EndObject();
+ Member("tail", templateElement.Tail);
+ }
+
+ return templateElement;
+ }
+
+ protected internal override object? VisitTemplateLiteral(TemplateLiteral templateLiteral)
+ {
+ using (StartNodeObject(templateLiteral))
+ {
+ Member("quasis", templateLiteral.Quasis);
+ Member("expressions", templateLiteral.Expressions);
+ }
+
+ return templateLiteral;
+ }
+
+ protected internal override object? VisitThisExpression(ThisExpression thisExpression)
+ {
+ EmptyNodeObject(thisExpression);
+ return thisExpression;
+ }
+
+ protected internal override object? VisitThrowStatement(ThrowStatement throwStatement)
+ {
+ using (StartNodeObject(throwStatement))
+ {
+ Member("argument", throwStatement.Argument);
+ }
+
+ return throwStatement;
+ }
+
+ protected internal override object? VisitTryStatement(TryStatement tryStatement)
+ {
+ using (StartNodeObject(tryStatement))
+ {
+ Member("block", tryStatement.Block);
+ Member("handler", tryStatement.Handler);
+ Member("finalizer", tryStatement.Finalizer);
+ }
+
+ return tryStatement;
+ }
+
+ protected internal override object? VisitUnaryExpression(UnaryExpression unaryExpression)
+ {
+ using (StartNodeObject(unaryExpression))
+ {
+ Member("operator", UnaryExpression.GetUnaryOperatorToken(unaryExpression.Operator));
+ Member("argument", unaryExpression.Argument);
+ Member("prefix", unaryExpression.Prefix);
+ }
+
+ return unaryExpression;
+ }
+
+ protected internal override object? VisitVariableDeclaration(VariableDeclaration variableDeclaration)
+ {
+ using (StartNodeObject(variableDeclaration))
+ {
+ Member("declarations", variableDeclaration.Declarations);
+ Member("kind", variableDeclaration.Kind);
+ }
+
+ return variableDeclaration;
+ }
+
+ protected internal override object? VisitVariableDeclarator(VariableDeclarator variableDeclarator)
+ {
+ using (StartNodeObject(variableDeclarator))
+ {
+ Member("id", variableDeclarator.Id);
+ Member("init", variableDeclarator.Init);
+ }
+
+ return variableDeclarator;
+ }
+
+ protected internal override object? VisitWhileStatement(WhileStatement whileStatement)
+ {
+ using (StartNodeObject(whileStatement))
+ {
+ Member("test", whileStatement.Test);
+ Member("body", whileStatement.Body);
+ }
+
+ return whileStatement;
+ }
+
+ protected internal override object? VisitWithStatement(WithStatement withStatement)
+ {
+ using (StartNodeObject(withStatement))
+ {
+ Member("object", withStatement.Object);
+ Member("body", withStatement.Body);
+ }
+
+ return withStatement;
+ }
+
+ protected internal override object? VisitYieldExpression(YieldExpression yieldExpression)
+ {
+ using (StartNodeObject(yieldExpression))
+ {
+ Member("argument", yieldExpression.Argument);
+ Member("delegate", yieldExpression.Delegate);
+ }
+
+ return yieldExpression;
+ }
+}
diff --git a/src/Esprima/Utils/EnumHelper.cs b/src/Esprima/Utils/EnumHelper.cs
new file mode 100644
index 00000000..72f6fbfd
--- /dev/null
+++ b/src/Esprima/Utils/EnumHelper.cs
@@ -0,0 +1,36 @@
+using System.Runtime.CompilerServices;
+
+namespace Esprima.Utils;
+
+internal static class EnumHelper
+{
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static TEnum ToFlag(this bool value, TEnum flag) where TEnum : struct, Enum =>
+ value.ToFlag(flag, default);
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static TEnum ToFlag(this bool value, TEnum flag, TEnum fallbackFlag) where TEnum : struct, Enum =>
+ value ? flag : fallbackFlag;
+
+ // Enum.HasFlag is slow (at least, on older runtimes). However, a non-allocating, generic solution would require System.Runtime.CompilerServices.Unsafe:
+ // https://github.com/dotnet/csharplang/discussions/1993#discussioncomment-104840
+ // In case System.Runtime.CompilerServices.Unsafe becomes available, these methods should be replaced with a generic implementation.
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool HasFlagFast(this AstToJavascriptConverter.BinaryOperationFlags flags, AstToJavascriptConverter.BinaryOperationFlags flag) => (flags & flag) == flag;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool HasFlagFast(this AstToJavascriptConverter.StatementFlags flags, AstToJavascriptConverter.StatementFlags flag) => (flags & flag) == flag;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool HasFlagFast(this AstToJavascriptConverter.ExpressionFlags flags, AstToJavascriptConverter.ExpressionFlags flag) => (flags & flag) == flag;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool HasFlagFast(this JavascriptTextWriter.TokenFlags flags, JavascriptTextWriter.TokenFlags flag) => (flags & flag) == flag;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool HasFlagFast(this JavascriptTextWriter.StatementFlags flags, JavascriptTextWriter.StatementFlags flag) => (flags & flag) == flag;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool HasFlagFast(this JavascriptTextWriter.ExpressionFlags flags, JavascriptTextWriter.ExpressionFlags flag) => (flags & flag) == flag;
+}
diff --git a/src/Esprima/Utils/ExpressionHelper.cs b/src/Esprima/Utils/ExpressionHelper.cs
new file mode 100644
index 00000000..350542f3
--- /dev/null
+++ b/src/Esprima/Utils/ExpressionHelper.cs
@@ -0,0 +1,153 @@
+using Esprima.Ast;
+
+namespace Esprima.Utils;
+
+internal static class ExpressionHelper
+{
+ ///
+ /// Maps operator precedence to an integer value.
+ ///
+ /// The expression representing the operation.
+ ///
+ /// If less than zero, the operation has left-to-right associativity.
+ /// If zero, associativity is not defined for the operation.
+ /// If greater than zero, the operation has right-to-left associativity.
+ ///
+ ///
+ /// Precedence value as defined based on this table. Higher value means higher precedence.
+ /// Negative value is returned if the precedence is not defined for the specified expression. is returned for primitive expressions like .
+ ///
+ public static int GetOperatorPrecedence(this Expression expression, out int associativity)
+ {
+ const int leftToRightAssociativity = -1;
+ const int undefinedAssociativity = 0;
+ const int rightToLeftAssociativity = 1;
+
+ associativity = undefinedAssociativity;
+
+Reenter:
+ switch (expression.Type)
+ {
+ case Nodes.ArrayExpression:
+ case Nodes.ClassExpression:
+ case Nodes.FunctionExpression:
+ case Nodes.Identifier:
+ case Nodes.Literal:
+ case Nodes.ObjectExpression:
+ case Nodes.PrivateIdentifier:
+ case Nodes.Super:
+ case Nodes.TaggedTemplateExpression:
+ case Nodes.TemplateLiteral:
+ case Nodes.ThisExpression:
+ return int.MaxValue;
+
+ case Nodes.MemberExpression when !expression.As().Computed:
+ case Nodes.MetaProperty:
+ associativity = leftToRightAssociativity;
+ goto case Nodes.MemberExpression;
+ case Nodes.MemberExpression:
+ case Nodes.CallExpression:
+ case Nodes.Import:
+ case Nodes.NewExpression when expression.As().Arguments.Count > 0:
+ return 1700;
+
+ case Nodes.NewExpression:
+ return 1600;
+
+ case Nodes.UpdateExpression when !expression.As().Prefix:
+ return 1500;
+
+ case Nodes.UpdateExpression:
+ case Nodes.UnaryExpression:
+ case Nodes.AwaitExpression:
+ return 1400;
+
+ case Nodes.BinaryExpression:
+ switch (expression.As().Operator)
+ {
+ case BinaryOperator.Exponentiation:
+ associativity = rightToLeftAssociativity;
+ return 1300;
+
+ case BinaryOperator.Times:
+ case BinaryOperator.Divide:
+ case BinaryOperator.Modulo:
+ associativity = leftToRightAssociativity;
+ return 1200;
+
+ case BinaryOperator.Plus:
+ case BinaryOperator.Minus:
+ associativity = leftToRightAssociativity;
+ return 1100;
+
+ case BinaryOperator.LeftShift:
+ case BinaryOperator.RightShift:
+ case BinaryOperator.UnsignedRightShift:
+ associativity = leftToRightAssociativity;
+ return 1000;
+
+ case BinaryOperator.Less:
+ case BinaryOperator.LessOrEqual:
+ case BinaryOperator.Greater:
+ case BinaryOperator.GreaterOrEqual:
+ case BinaryOperator.In:
+ case BinaryOperator.InstanceOf:
+ associativity = leftToRightAssociativity;
+ return 900;
+
+ case BinaryOperator.Equal:
+ case BinaryOperator.NotEqual:
+ case BinaryOperator.StrictlyEqual:
+ case BinaryOperator.StricltyNotEqual:
+ associativity = leftToRightAssociativity;
+ return 800;
+
+ case BinaryOperator.BitwiseAnd:
+ associativity = leftToRightAssociativity;
+ return 700;
+
+ case BinaryOperator.BitwiseXor:
+ associativity = leftToRightAssociativity;
+ return 600;
+
+ case BinaryOperator.BitwiseOr:
+ associativity = leftToRightAssociativity;
+ return 500;
+ }
+ break;
+
+ case Nodes.LogicalExpression:
+ switch (expression.As().Operator)
+ {
+ case BinaryOperator.LogicalAnd:
+ associativity = leftToRightAssociativity;
+ return 400;
+ case BinaryOperator.LogicalOr:
+ case BinaryOperator.NullishCoalescing:
+ associativity = leftToRightAssociativity;
+ return 300;
+ }
+ break;
+
+ case Nodes.AssignmentExpression:
+ case Nodes.ConditionalExpression:
+ associativity = rightToLeftAssociativity;
+ goto case Nodes.ArrowFunctionExpression;
+ case Nodes.ArrowFunctionExpression:
+ case Nodes.YieldExpression:
+ case Nodes.SpreadElement:
+ return 200;
+
+ case Nodes.SequenceExpression:
+ associativity = leftToRightAssociativity;
+ return 100;
+
+ case Nodes.ChainExpression:
+ // This can be improved when tail recursion becomes available (see https://github.com/dotnet/csharplang/issues/2304).
+ expression = expression.As().Expression;
+ goto Reenter;
+ }
+
+ return -1;
+ }
+}
diff --git a/src/Esprima/Utils/JavascriptTextWriter.Enums.cs b/src/Esprima/Utils/JavascriptTextWriter.Enums.cs
new file mode 100644
index 00000000..52a8f297
--- /dev/null
+++ b/src/Esprima/Utils/JavascriptTextWriter.Enums.cs
@@ -0,0 +1,119 @@
+using Esprima.Ast;
+
+namespace Esprima.Utils;
+
+partial class JavascriptTextWriter
+{
+ [Flags]
+ public enum TokenFlags
+ {
+ None = 0,
+
+ // Position hints for punctuators (exclusive, i.e at most one of these flags should be set)
+
+ ///