From 137830469e66f8bc1a1052b7e53d052549a2d6a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20K=C3=BChner?= Date: Tue, 2 Nov 2021 23:18:48 +0100 Subject: [PATCH 01/23] javascript back converter --- src/Esprima/Utils/ToJavascriptConverter.cs | 1220 ++++++++++++++++++++ test/Esprima.Tests/JavascriptTest.cs | 425 +++++++ 2 files changed, 1645 insertions(+) create mode 100644 src/Esprima/Utils/ToJavascriptConverter.cs create mode 100644 test/Esprima.Tests/JavascriptTest.cs diff --git a/src/Esprima/Utils/ToJavascriptConverter.cs b/src/Esprima/Utils/ToJavascriptConverter.cs new file mode 100644 index 00000000..5a90e9b3 --- /dev/null +++ b/src/Esprima/Utils/ToJavascriptConverter.cs @@ -0,0 +1,1220 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using Esprima.Ast; + +namespace Esprima.Utils +{ + public class ToJavascriptConverter + { + public static string ToJavascript(Node node) + { + var visitor = new ToJavascriptConverter(); + visitor.Visit(node); + return visitor.ToString(); + } + + protected StringBuilder _sb = new StringBuilder(); + protected int _indentionLevel = 0; + protected bool beautify = false; + protected int _indentionSize = 4; + protected char _indentionChar = ' '; + + private static readonly ConditionalWeakTable EnumMap = new ConditionalWeakTable(); + + private string GetEnumValue(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.GetCustomAttribute() is EnumMemberAttribute a + ? a.Value : f.Name.ToLowerInvariant())); + return map[value]; + } + + private readonly List _parentStack = new List(); + protected IReadOnlyList ParentStack => _parentStack; + + /// + /// Returns parent node at specified position. + /// + /// Zero index value returns current node; one corresponds to direct + /// parent of current node. + protected Node? TryGetParentAt(int offset) + { + if (_parentStack.Count < offset + 1) + { + return null; + } + + return _parentStack[_parentStack.Count - 1 - offset]; + } + + public virtual void Visit(Node node) + { + _parentStack.Add(node); + + switch (node.Type) + { + case Nodes.AssignmentExpression: + VisitAssignmentExpression(node.As()); + break; + case Nodes.ArrayExpression: + VisitArrayExpression(node.As()); + break; + case Nodes.BlockStatement: + VisitBlockStatement(node.As()); + break; + case Nodes.BinaryExpression: + VisitBinaryExpression(node.As()); + break; + case Nodes.BreakStatement: + VisitBreakStatement(node.As()); + break; + case Nodes.CallExpression: + VisitCallExpression(node.As()); + break; + case Nodes.CatchClause: + VisitCatchClause(node.As()); + break; + case Nodes.ConditionalExpression: + VisitConditionalExpression(node.As()); + break; + case Nodes.ContinueStatement: + VisitContinueStatement(node.As()); + break; + case Nodes.DoWhileStatement: + VisitDoWhileStatement(node.As()); + break; + case Nodes.DebuggerStatement: + VisitDebuggerStatement(node.As()); + break; + case Nodes.EmptyStatement: + VisitEmptyStatement(node.As()); + break; + case Nodes.ExpressionStatement: + VisitExpressionStatement(node.As()); + break; + case Nodes.ForStatement: + VisitForStatement(node.As()); + break; + case Nodes.ForInStatement: + VisitForInStatement(node.As()); + break; + case Nodes.FunctionDeclaration: + VisitFunctionDeclaration(node.As()); + break; + case Nodes.FunctionExpression: + VisitFunctionExpression(node.As()); + break; + case Nodes.Identifier: + VisitIdentifier(node.As()); + break; + case Nodes.IfStatement: + VisitIfStatement(node.As()); + break; + case Nodes.Import: + VisitImport(node.As()); + break; + case Nodes.Literal: + VisitLiteral(node.As()); + break; + case Nodes.LabeledStatement: + VisitLabeledStatement(node.As()); + break; + case Nodes.LogicalExpression: + VisitBinaryExpression(node.As()); + break; + case Nodes.MemberExpression: + VisitMemberExpression(node.As()); + break; + case Nodes.NewExpression: + VisitNewExpression(node.As()); + break; + case Nodes.ObjectExpression: + VisitObjectExpression(node.As()); + break; + case Nodes.Program: + VisitProgram(node.As()); + break; + case Nodes.Property: + VisitProperty(node.As()); + break; + case Nodes.PropertyDefinition: + VisitPropertyDefinition(node.As()); + break; + case Nodes.RestElement: + VisitRestElement(node.As()); + break; + case Nodes.ReturnStatement: + VisitReturnStatement(node.As()); + break; + case Nodes.SequenceExpression: + VisitSequenceExpression(node.As()); + break; + case Nodes.SwitchStatement: + VisitSwitchStatement(node.As()); + break; + case Nodes.SwitchCase: + VisitSwitchCase(node.As()); + break; + case Nodes.TemplateElement: + VisitTemplateElement(node.As()); + break; + case Nodes.TemplateLiteral: + VisitTemplateLiteral(node.As()); + break; + case Nodes.ThisExpression: + VisitThisExpression(node.As()); + break; + case Nodes.ThrowStatement: + VisitThrowStatement(node.As()); + break; + case Nodes.TryStatement: + VisitTryStatement(node.As()); + break; + case Nodes.UnaryExpression: + VisitUnaryExpression(node.As()); + break; + case Nodes.UpdateExpression: + VisitUpdateExpression(node.As()); + break; + case Nodes.VariableDeclaration: + VisitVariableDeclaration(node.As()); + break; + case Nodes.VariableDeclarator: + VisitVariableDeclarator(node.As()); + break; + case Nodes.WhileStatement: + VisitWhileStatement(node.As()); + break; + case Nodes.WithStatement: + VisitWithStatement(node.As()); + break; + case Nodes.ArrayPattern: + VisitArrayPattern(node.As()); + break; + case Nodes.AssignmentPattern: + VisitAssignmentPattern(node.As()); + break; + case Nodes.SpreadElement: + VisitSpreadElement(node.As()); + break; + case Nodes.ObjectPattern: + VisitObjectPattern(node.As()); + break; + case Nodes.ArrowParameterPlaceHolder: + VisitArrowParameterPlaceHolder(node.As()); + break; + case Nodes.MetaProperty: + VisitMetaProperty(node.As()); + break; + case Nodes.Super: + VisitSuper(node.As()); + break; + case Nodes.TaggedTemplateExpression: + VisitTaggedTemplateExpression(node.As()); + break; + case Nodes.YieldExpression: + VisitYieldExpression(node.As()); + break; + case Nodes.ArrowFunctionExpression: + VisitArrowFunctionExpression(node.As()); + break; + case Nodes.AwaitExpression: + VisitAwaitExpression(node.As()); + break; + case Nodes.ClassBody: + VisitClassBody(node.As()); + break; + case Nodes.ClassDeclaration: + VisitClassDeclaration(node.As()); + break; + case Nodes.ForOfStatement: + VisitForOfStatement(node.As()); + break; + case Nodes.MethodDefinition: + VisitMethodDefinition(node.As()); + break; + case Nodes.ImportSpecifier: + VisitImportSpecifier(node.As()); + break; + case Nodes.ImportDefaultSpecifier: + VisitImportDefaultSpecifier(node.As()); + break; + case Nodes.ImportNamespaceSpecifier: + VisitImportNamespaceSpecifier(node.As()); + break; + case Nodes.ImportDeclaration: + VisitImportDeclaration(node.As()); + break; + case Nodes.ExportSpecifier: + VisitExportSpecifier(node.As()); + break; + case Nodes.ExportNamedDeclaration: + VisitExportNamedDeclaration(node.As()); + break; + case Nodes.ExportAllDeclaration: + VisitExportAllDeclaration(node.As()); + break; + case Nodes.ExportDefaultDeclaration: + VisitExportDefaultDeclaration(node.As()); + break; + case Nodes.ClassExpression: + VisitClassExpression(node.As()); + break; + case Nodes.ChainExpression: + VisitChainExpression(node.As()); + break; + default: + VisitUnknownNode(node); + break; + } + _parentStack.RemoveAt(_parentStack.Count - 1); + } + + protected virtual void VisitProgram(Program program) + { + VisitNodeList(program.Body, appendAtEnd: ";"); + } + + protected virtual void VisitUnknownNode(Node node) + { + throw new NotImplementedException($"AST visitor doesn't support nodes of type {node.Type}, you can override VisitUnknownNode to handle this case."); + } + + protected virtual void VisitChainExpression(ChainExpression chainExpression) + { + Visit(chainExpression.Expression); + } + + protected virtual void VisitCatchClause(CatchClause catchClause) + { + _sb.Append("("); + if (catchClause.Param is not null) + Visit(catchClause.Param); + _sb.Append(")"); + Visit(catchClause.Body); + } + + protected virtual void VisitFunctionDeclaration(FunctionDeclaration functionDeclaration) + { + if (functionDeclaration.Async) + _sb.Append("async "); + _sb.Append("function"); + if (functionDeclaration.Generator) + _sb.Append("*"); + if (functionDeclaration.Id != null) + { + _sb.Append(" "); + Visit(functionDeclaration.Id); + } + _sb.Append("("); + VisitNodeList(functionDeclaration.Params, appendSeperatorString: ","); + _sb.Append(")"); + Visit(functionDeclaration.Body); + } + + protected virtual void VisitWithStatement(WithStatement withStatement) + { + _sb.Append("with("); + Visit(withStatement.Object); + _sb.Append(")"); + Visit(withStatement.Body); + } + + protected virtual void VisitWhileStatement(WhileStatement whileStatement) + { + _sb.Append("while("); + Visit(whileStatement.Test); + _sb.Append(")"); + Visit(whileStatement.Body); + } + + protected virtual void VisitVariableDeclaration(VariableDeclaration variableDeclaration) + { + _sb.Append(variableDeclaration.Kind.ToString().ToLower() + " "); + VisitNodeList(variableDeclaration.Declarations, appendSeperatorString: ","); + } + + protected virtual void VisitTryStatement(TryStatement tryStatement) + { + _sb.Append("try "); + Visit(tryStatement.Block); + if (tryStatement.Handler != null) + { + _sb.Append(" catch"); + Visit(tryStatement.Handler); + } + if (tryStatement.Finalizer != null) + { + _sb.Append(" finally"); + Visit(tryStatement.Finalizer); + } + } + + protected virtual void VisitThrowStatement(ThrowStatement throwStatement) + { + _sb.Append("throw "); + Visit(throwStatement.Argument); + _sb.Append(";"); + } + + protected virtual void VisitSwitchStatement(SwitchStatement switchStatement) + { + WriteStartLineToSb("switch("); + Visit(switchStatement.Discriminant); + _sb.Append("){"); + VisitNodeList(switchStatement.Cases); + _sb.Append("}"); + } + + protected virtual void VisitSwitchCase(SwitchCase switchCase) + { + if (switchCase.Test != null) + { + //todo, remove space after case, if testcase is a string starting with " or ' + WriteStartLineToSb("case "); + Visit(switchCase.Test); + } + else + WriteStartLineToSb("default"); + WriteStartLineToSb(":"); + + VisitNodeList(switchCase.Consequent, appendAtEnd: ";"); + } + + protected virtual void VisitReturnStatement(ReturnStatement returnStatement) + { + _sb.Append("return"); + if (returnStatement.Argument != null) + { + _sb.Append(" "); + Visit(returnStatement.Argument); + } + _sb.Append(";"); + } + + protected virtual void VisitLabeledStatement(LabeledStatement labeledStatement) + { + Visit(labeledStatement.Label); + _sb.Append(":"); + Visit(labeledStatement.Body); + } + + protected virtual void VisitIfStatement(IfStatement ifStatement) + { + WriteStartLineToSb("if("); + Visit(ifStatement.Test); + WriteEndLineToSb(")"); + Visit(ifStatement.Consequent); + if (NodeNeedsSemicolon(ifStatement.Consequent)) + _sb.Append(";"); + if (ifStatement.Alternate != null) + { + _sb.Append(" else "); + Visit(ifStatement.Alternate); + if (NodeNeedsSemicolon(ifStatement.Alternate)) + _sb.Append(";"); + } + } + + protected virtual void VisitEmptyStatement(EmptyStatement emptyStatement) + { + _sb.Append(";"); + } + + protected virtual void VisitDebuggerStatement(DebuggerStatement debuggerStatement) + { + _sb.Append("debugger"); + } + + protected virtual void VisitExpressionStatement(ExpressionStatement expressionStatement) + { + if (expressionStatement.Expression is CallExpression callExpression && !(callExpression.Callee is Identifier)) + { + if (ExpressionNeedsBrackets(callExpression.Callee)) + _sb.Append("("); + Visit(callExpression.Callee); + if (ExpressionNeedsBrackets(callExpression.Callee)) + _sb.Append(")"); + _sb.Append("("); + VisitNodeList(callExpression.Arguments, appendSeperatorString: ","); + _sb.Append(")"); + } + else if (expressionStatement.Expression is ClassExpression) + { + _sb.Append("("); + Visit(expressionStatement.Expression); + _sb.Append(")"); + } + else + Visit(expressionStatement.Expression); + } + + protected virtual void VisitForStatement(ForStatement forStatement) + { + WriteStartLineToSb("for("); + if (forStatement.Init != null) + { + Visit(forStatement.Init); + } + _sb.Append(";"); + if (forStatement.Test != null) + { + Visit(forStatement.Test); + } + _sb.Append(";"); + if (forStatement.Update != null) + { + Visit(forStatement.Update); + } + _sb.Append(")"); + Visit(forStatement.Body); + if (NodeNeedsSemicolon(forStatement.Body)) + _sb.Append(";"); + } + + protected virtual void VisitForInStatement(ForInStatement forInStatement) + { + _sb.Append("for("); + Visit(forInStatement.Left); + _sb.Append(" in "); + Visit(forInStatement.Right); + _sb.Append(")"); + Visit(forInStatement.Body); + if (NodeNeedsSemicolon(forInStatement.Body)) + _sb.Append(";"); + } + + protected virtual void VisitDoWhileStatement(DoWhileStatement doWhileStatement) + { + _sb.Append("do "); + Visit(doWhileStatement.Body); + if (NodeNeedsSemicolon(doWhileStatement.Body)) + _sb.Append(";"); + _sb.Append("while("); + Visit(doWhileStatement.Test); + _sb.Append(")"); + } + + protected virtual void VisitArrowFunctionExpression(ArrowFunctionExpression arrowFunctionExpression) + { + if (arrowFunctionExpression.Async) + { + _sb.Append("async "); + } + + if (arrowFunctionExpression.Id != null) + { + Visit(arrowFunctionExpression.Id); + } + + if (arrowFunctionExpression.Params.Count == 1) + { + if (arrowFunctionExpression.Params[0] is RestElement || ExpressionNeedsBrackets(arrowFunctionExpression.Params[0])) + _sb.Append("("); + Visit(arrowFunctionExpression.Params[0]); + if (arrowFunctionExpression.Params[0] is RestElement || ExpressionNeedsBrackets(arrowFunctionExpression.Params[0])) + _sb.Append(")"); + } + else + { + _sb.Append("("); + VisitNodeList(arrowFunctionExpression.Params, appendSeperatorString: ",", appendBracketsIfNeeded: true); ; + _sb.Append(")"); + } + _sb.Append("=>"); + if (arrowFunctionExpression.Body is ObjectExpression || arrowFunctionExpression.Body is SequenceExpression) + _sb.Append("("); + Visit(arrowFunctionExpression.Body); + if (arrowFunctionExpression.Body is ObjectExpression || arrowFunctionExpression.Body is SequenceExpression) + _sb.Append(")"); + } + + protected virtual void VisitUnaryExpression(UnaryExpression unaryExpression) + { + var op = GetEnumValue("unaryoperator", unaryExpression.Operator); + if (unaryExpression.Prefix) + { + _sb.Append(op); + if (char.IsLetter(op[0])) + _sb.Append(" "); + } + if (!(unaryExpression.Argument is Literal) && !(unaryExpression.Argument is UnaryExpression)) + _sb.Append("("); + Visit(unaryExpression.Argument); + if (!(unaryExpression.Argument is Literal) && !(unaryExpression.Argument is UnaryExpression)) + _sb.Append(")"); + if (!unaryExpression.Prefix) + _sb.Append(op); + } + + protected virtual void VisitUpdateExpression(UpdateExpression updateExpression) + { + if (updateExpression.Prefix) + _sb.Append(GetEnumValue("unaryoperator", updateExpression.Operator)); + Visit(updateExpression.Argument); + if (!updateExpression.Prefix) + _sb.Append(GetEnumValue("unaryoperator", updateExpression.Operator)); + } + + protected virtual void VisitThisExpression(ThisExpression thisExpression) + { + _sb.Append("this"); + } + + protected virtual void VisitSequenceExpression(SequenceExpression sequenceExpression) + { + VisitNodeList(sequenceExpression.Expressions, appendSeperatorString: ","); + } + + protected virtual void VisitObjectExpression(ObjectExpression objectExpression) + { + _sb.Append("{"); + VisitNodeList(objectExpression.Properties, appendSeperatorString: ","); + _sb.Append("}"); + } + + protected virtual void VisitNewExpression(NewExpression newExpression) + { + _sb.Append("new"); + if (ExpressionNeedsBrackets(newExpression.Callee)) + _sb.Append("("); + else + _sb.Append(" "); + Visit(newExpression.Callee); + if (ExpressionNeedsBrackets(newExpression.Callee)) + _sb.Append(")"); + if (newExpression.Arguments.Count > 0) + { + _sb.Append("("); + VisitNodeList(newExpression.Arguments, appendSeperatorString: ","); + _sb.Append(")"); + } + } + + protected virtual void VisitMemberExpression(MemberExpression memberExpression) + { + if (ExpressionNeedsBrackets(memberExpression.Object) || (memberExpression.Object is Literal l && l.TokenType != TokenType.StringLiteral)) + _sb.Append("("); + Visit(memberExpression.Object); + if (ExpressionNeedsBrackets(memberExpression.Object) || (memberExpression.Object is Literal l2 && l2.TokenType != TokenType.StringLiteral)) + _sb.Append(")"); + if (memberExpression.Computed) + _sb.Append("["); + else + { + if (TryGetParentAt(0) is ChainExpression) + _sb.Append("?"); + _sb.Append("."); + } + Visit(memberExpression.Property); + if (memberExpression.Computed) + _sb.Append("]"); + } + + protected virtual void VisitLiteral(Literal literal) + { + _sb.Append(literal.Raw); + } + + protected virtual void VisitIdentifier(Identifier identifier) + { + _sb.Append(identifier.Name); + } + + protected virtual void VisitFunctionExpression(IFunction function) + { + var isParentMethod = TryGetParentAt(1) is MethodDefinition; + if (!isParentMethod) + { + if (function.Async) + _sb.Append("async "); + if (!(TryGetParentAt(1) is MethodDefinition)) + _sb.Append("function"); + if (function.Generator) + _sb.Append("*"); + } + if (function.Id != null) + { + _sb.Append(" "); + Visit(function.Id); + } + _sb.Append("("); + VisitNodeList(function.Params, appendSeperatorString: ","); + _sb.Append(")"); + Visit(function.Body); + } + + protected virtual void VisitClassExpression(ClassExpression classExpression) + { + _sb.Append("class "); + if (classExpression.Id != null) + { + Visit(classExpression.Id); + } + + if (classExpression.SuperClass != null) + { + _sb.Append(" extends "); + Visit(classExpression.SuperClass); + } + _sb.Append("{"); + Visit(classExpression.Body); + _sb.Append("}"); + } + + protected virtual void VisitExportDefaultDeclaration(ExportDefaultDeclaration exportDefaultDeclaration) + { + _sb.Append("export default "); + if (exportDefaultDeclaration.Declaration != null) + { + Visit(exportDefaultDeclaration.Declaration); + } + } + + protected virtual void VisitExportAllDeclaration(ExportAllDeclaration exportAllDeclaration) + { + _sb.Append("export*from"); + Visit(exportAllDeclaration.Source); + } + + protected virtual void VisitExportNamedDeclaration(ExportNamedDeclaration exportNamedDeclaration) + { + _sb.Append("export"); + if (exportNamedDeclaration.Declaration != null) + { + _sb.Append(" "); + Visit(exportNamedDeclaration.Declaration); + } + if (exportNamedDeclaration.Specifiers.Count > 0) + { + _sb.Append("{"); + VisitNodeList(exportNamedDeclaration.Specifiers, appendSeperatorString: ","); + _sb.Append("}"); + } + if (exportNamedDeclaration.Source != null) + { + _sb.Append("from"); + Visit(exportNamedDeclaration.Source); + } + if (exportNamedDeclaration.Declaration == null && exportNamedDeclaration.Specifiers.Count == 0 && exportNamedDeclaration.Source == null) + _sb.Append("{}"); + + } + + protected virtual void VisitExportSpecifier(ExportSpecifier exportSpecifier) + { + Visit(exportSpecifier.Local); + if (exportSpecifier.Local != exportSpecifier.Exported) + { + _sb.Append(" as "); + Visit(exportSpecifier.Exported); + } + } + + protected virtual void VisitImport(Import import) + { + _sb.Append("import("); + Visit(import.Source); + _sb.Append(")"); + } + + protected virtual void VisitImportDeclaration(ImportDeclaration importDeclaration) + { + _sb.Append("import "); + var firstSpecifier = importDeclaration.Specifiers.FirstOrDefault(); + if (firstSpecifier is ImportDefaultSpecifier) + { + Visit(firstSpecifier); + if (importDeclaration.Specifiers.Count > 1) + { + _sb.Append(","); + if (importDeclaration.Specifiers[1] is ImportNamespaceSpecifier) + VisitNodeList(importDeclaration.Specifiers.Skip(1), appendSeperatorString: ","); + else + { + _sb.Append("{"); + VisitNodeList(importDeclaration.Specifiers.Skip(1), appendSeperatorString: ","); + _sb.Append("}"); + } + } + } + else if (importDeclaration.Specifiers.Any()) + { + if (importDeclaration.Specifiers[0] is ImportNamespaceSpecifier) + VisitNodeList(importDeclaration.Specifiers, appendSeperatorString: ","); + else + { + _sb.Append("{"); + VisitNodeList(importDeclaration.Specifiers, appendSeperatorString: ","); + _sb.Append("}"); + } + } + if (importDeclaration.Specifiers.Count > 0) + { + _sb.Append(" from "); + } + Visit(importDeclaration.Source); + } + + protected virtual void VisitImportNamespaceSpecifier(ImportNamespaceSpecifier importNamespaceSpecifier) + { + _sb.Append("* as "); + Visit(importNamespaceSpecifier.Local); + } + + protected virtual void VisitImportDefaultSpecifier(ImportDefaultSpecifier importDefaultSpecifier) + { + Visit(importDefaultSpecifier.Local); + } + + protected virtual void VisitImportSpecifier(ImportSpecifier importSpecifier) + { + Visit(importSpecifier.Imported); + if (importSpecifier.Local != importSpecifier.Imported) + { + _sb.Append(" as "); + Visit(importSpecifier.Local); + } + } + + protected virtual void VisitMethodDefinition(MethodDefinition methodDefinition) + { + if (methodDefinition.Static) + _sb.Append("static "); + if (IsAsync(methodDefinition.Value)) + _sb.Append("async "); + if (methodDefinition.Value is FunctionExpression f && f.Generator) + _sb.Append("*"); + if (methodDefinition.Kind == PropertyKind.Get) + _sb.Append("get "); + else if (methodDefinition.Kind == PropertyKind.Set) + _sb.Append("set "); + if (methodDefinition.Key is MemberExpression || ExpressionNeedsBrackets(methodDefinition.Key)) + _sb.Append("["); + if (ExpressionNeedsBrackets(methodDefinition.Key)) + _sb.Append("("); + Visit(methodDefinition.Key); + if (ExpressionNeedsBrackets(methodDefinition.Key)) + _sb.Append(")"); + if (methodDefinition.Key is MemberExpression || ExpressionNeedsBrackets(methodDefinition.Key)) + _sb.Append("]"); + Visit(methodDefinition.Value); + } + + protected virtual void VisitForOfStatement(ForOfStatement forOfStatement) + { + _sb.Append("for("); + Visit(forOfStatement.Left); + _sb.Append(" of "); + Visit(forOfStatement.Right); + _sb.Append(")"); + Visit(forOfStatement.Body); + if (NodeNeedsSemicolon(forOfStatement.Body)) + _sb.Append(";"); + } + + protected virtual void VisitClassDeclaration(ClassDeclaration classDeclaration) + { + _sb.Append("class "); + if (classDeclaration.Id != null) + { + Visit(classDeclaration.Id); + } + + if (classDeclaration.SuperClass != null) + { + _sb.Append(" extends "); + Visit(classDeclaration.SuperClass); + } + _sb.Append("{"); + Visit(classDeclaration.Body); + _sb.Append("}"); + } + + protected virtual void VisitClassBody(ClassBody classBody) + { + VisitNodeList(classBody.Body); + } + + protected virtual void VisitYieldExpression(YieldExpression yieldExpression) + { + _sb.Append("yield "); + if (yieldExpression.Argument != null) + { + Visit(yieldExpression.Argument); + } + } + + protected virtual void VisitTaggedTemplateExpression(TaggedTemplateExpression taggedTemplateExpression) + { + Visit(taggedTemplateExpression.Tag); + Visit(taggedTemplateExpression.Quasi); + } + + protected virtual void VisitSuper(Super super) + { + _sb.Append("super"); + } + + protected virtual void VisitMetaProperty(MetaProperty metaProperty) + { + Visit(metaProperty.Meta); + _sb.Append("."); + Visit(metaProperty.Property); + } + + protected virtual void VisitArrowParameterPlaceHolder(ArrowParameterPlaceHolder arrowParameterPlaceHolder) + { + VisitNodeList(arrowParameterPlaceHolder.Params); + } + + protected virtual void VisitObjectPattern(ObjectPattern objectPattern) + { + _sb.Append("{"); + VisitNodeList(objectPattern.Properties, appendSeperatorString: ","); + _sb.Append("}"); + } + + protected virtual void VisitSpreadElement(SpreadElement spreadElement) + { + _sb.Append("..."); + Visit(spreadElement.Argument); + } + + protected virtual void VisitAssignmentPattern(AssignmentPattern assignmentPattern) + { + Visit(assignmentPattern.Left); + _sb.Append("="); + Visit(assignmentPattern.Right); + } + + protected virtual void VisitArrayPattern(ArrayPattern arrayPattern) + { + _sb.Append("["); + VisitNodeList(arrayPattern.Elements, appendSeperatorString: ","); + _sb.Append("]"); + } + + protected virtual void VisitVariableDeclarator(VariableDeclarator variableDeclarator) + { + if (variableDeclarator.Id is ObjectPattern) + { + //_sb.Append("{"); + } + Visit(variableDeclarator.Id); + if (variableDeclarator.Id is ObjectPattern) + { + //_sb.Append("}"); + } + if (variableDeclarator.Init != null) + { + _sb.Append("="); + if (ExpressionNeedsBrackets(variableDeclarator.Init)) + _sb.Append("("); + Visit(variableDeclarator.Init); + if (ExpressionNeedsBrackets(variableDeclarator.Init)) + _sb.Append(")"); + } + } + + protected virtual void VisitTemplateLiteral(TemplateLiteral templateLiteral) + { + _sb.Append("`"); + for (int n = 0; n < templateLiteral.Quasis.Count; n++) + { + Visit(templateLiteral.Quasis[n]); + if (templateLiteral.Expressions.Count > n) + { + _sb.Append("${"); + Visit(templateLiteral.Expressions[n]); + _sb.Append("}"); + } + } + _sb.Append("`"); + } + + protected virtual void VisitTemplateElement(TemplateElement templateElement) + { + _sb.Append(templateElement.Value.Raw); + } + + protected virtual void VisitRestElement(RestElement restElement) + { + _sb.Append("..."); + Visit(restElement.Argument); + } + + protected virtual void VisitProperty(Property property) + { + if (property.Key is MemberExpression || ExpressionNeedsBrackets(property.Key)) + _sb.Append("["); + if (ExpressionNeedsBrackets(property.Key)) + _sb.Append("("); + Visit(property.Key); + if (ExpressionNeedsBrackets(property.Key)) + _sb.Append(")"); + if (property.Key is MemberExpression || ExpressionNeedsBrackets(property.Key)) + _sb.Append("]"); + if (property.Key is Identifier keyI && property.Value is Identifier valueI && keyI.Name == valueI.Name) + { } + else + { + _sb.Append(":"); + if (property.Value is not ObjectPattern && ExpressionNeedsBrackets(property.Value)) + _sb.Append("("); + Visit(property.Value); + if (property.Value is not ObjectPattern && ExpressionNeedsBrackets(property.Value)) + _sb.Append(")"); + } + } + + protected virtual void VisitPropertyDefinition(PropertyDefinition propertyDefinition) + { + if (propertyDefinition.Static) + { + _sb.Append("static "); + } + if (propertyDefinition.Key is MemberExpression || ExpressionNeedsBrackets(propertyDefinition.Key)) + _sb.Append("["); + if (ExpressionNeedsBrackets(propertyDefinition.Key)) + _sb.Append("("); + Visit(propertyDefinition.Key); + if (ExpressionNeedsBrackets(propertyDefinition.Key)) + _sb.Append(")"); + if (propertyDefinition.Key is MemberExpression || ExpressionNeedsBrackets(propertyDefinition.Key)) + _sb.Append("]"); + if (propertyDefinition.Value != null) + { + _sb.Append("="); + Visit(propertyDefinition.Value); + } + _sb.Append(";"); + } + + protected virtual void VisitAwaitExpression(AwaitExpression awaitExpression) + { + _sb.Append("await "); + Visit(awaitExpression.Argument); + } + + protected virtual void VisitConditionalExpression(ConditionalExpression conditionalExpression) + { + if (conditionalExpression.Test is AssignmentExpression) + _sb.Append("("); + Visit(conditionalExpression.Test); + if (conditionalExpression.Test is AssignmentExpression) + _sb.Append(")"); + _sb.Append("?"); + if (ExpressionNeedsBrackets(conditionalExpression.Consequent)) + _sb.Append("("); + Visit(conditionalExpression.Consequent); + if (ExpressionNeedsBrackets(conditionalExpression.Consequent)) + _sb.Append(")"); + _sb.Append(":"); + if (ExpressionNeedsBrackets(conditionalExpression.Alternate)) + _sb.Append("("); + Visit(conditionalExpression.Alternate); + if (ExpressionNeedsBrackets(conditionalExpression.Alternate)) + _sb.Append(")"); + } + + protected virtual void VisitCallExpression(CallExpression callExpression) + { + if (ExpressionNeedsBrackets(callExpression.Callee)) + _sb.Append("("); + Visit(callExpression.Callee); + if (ExpressionNeedsBrackets(callExpression.Callee)) + _sb.Append(")"); + _sb.Append("("); + VisitNodeList(callExpression.Arguments, appendSeperatorString: ",", appendBracketsIfNeeded: true); + _sb.Append(")"); + } + + protected virtual void VisitBinaryExpression(BinaryExpression binaryExpression) + { + if (ExpressionNeedsBrackets(binaryExpression.Left)) + _sb.Append("("); + Visit(binaryExpression.Left); + if (ExpressionNeedsBrackets(binaryExpression.Left)) + _sb.Append(")"); + var op = GetEnumValue("operator", binaryExpression.Operator); + if (char.IsLetter(op[0])) + _sb.Append(" "); + _sb.Append(op); + if (char.IsLetter(op[0])) + _sb.Append(" "); + if (ExpressionNeedsBrackets(binaryExpression.Right)) + _sb.Append("("); + Visit(binaryExpression.Right); + if (ExpressionNeedsBrackets(binaryExpression.Right)) + _sb.Append(")"); + } + + protected virtual void VisitArrayExpression(ArrayExpression arrayExpression) + { + _sb.Append("["); + VisitNodeList(arrayExpression.Elements, appendSeperatorString: ","); + _sb.Append("]"); + } + + protected virtual void VisitAssignmentExpression(AssignmentExpression assignmentExpression) + { + if (assignmentExpression.Left is ObjectPattern) + _sb.Append("("); + var op = GetEnumValue("assignmentoperator", assignmentExpression.Operator); + Visit(assignmentExpression.Left); + _sb.Append(op); + if (ExpressionNeedsBrackets(assignmentExpression.Right) && !(assignmentExpression.Right is AssignmentExpression)) + _sb.Append("("); + Visit(assignmentExpression.Right); + if (ExpressionNeedsBrackets(assignmentExpression.Right) && !(assignmentExpression.Right is AssignmentExpression)) + _sb.Append(")"); + if (assignmentExpression.Left is ObjectPattern) + _sb.Append(")"); + } + + protected virtual void VisitContinueStatement(ContinueStatement continueStatement) + { + _sb.Append("continue "); + if (continueStatement.Label != null) + { + Visit(continueStatement.Label); + } + } + + protected virtual void VisitBreakStatement(BreakStatement breakStatement) + { + if (breakStatement.Label != null) + { + Visit(breakStatement.Label); + } + _sb.Append("break"); + } + + protected virtual void VisitBlockStatement(BlockStatement blockStatement) + { + WriteStartLineToSb("{"); + if (beautify) + _sb.AppendLine(); + _indentionLevel++; + VisitNodeList(blockStatement.Body, appendAtEnd: ";"); + _indentionLevel--; + WriteEndLineToSb("}"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void VisitNodeList(IEnumerable nodeList, string appendAtEnd = null, string appendSeperatorString = null, bool appendBracketsIfNeeded = false) + where TNode : Node + { + var notfirst = false; + foreach (var node in nodeList) + { + if (node != null) + { + if (notfirst && appendSeperatorString != null) + _sb.Append(appendSeperatorString); + if (appendBracketsIfNeeded && ExpressionNeedsBrackets(node)) + _sb.Append("("); + Visit(node); + if (appendBracketsIfNeeded && ExpressionNeedsBrackets(node)) + _sb.Append(")"); + notfirst = true; + if (appendAtEnd != null && NodeNeedsSemicolon(node)) + _sb.Append(appendAtEnd); + } + } + } + + protected virtual void WriteStartLineToSb(string text) + { + if (!beautify) + _sb.Append(text); + else + _sb.Append(text.PadLeft(_indentionLevel * _indentionSize, _indentionChar)); + } + + protected virtual void WriteEndLineToSb(string text) + { + if (!beautify) + _sb.Append(text); + else + _sb.AppendLine(text); + } + + public override string ToString() + { + return _sb.ToString(); + } + + public bool IsAsync(Node node) + { + if (node is ArrowFunctionExpression afe) + return afe.Async; + if (node is ArrowParameterPlaceHolder apph) + return apph.Async; + if (node is FunctionDeclaration fd) + return fd.Async; + if (node is FunctionExpression fe) + return fe.Async; + return false; + } + + public bool NodeNeedsSemicolon(Node? node) + { + if (node is BlockStatement || + node is IfStatement || + node is SwitchStatement || + node is ForInStatement || + node is ForOfStatement || + node is ForStatement || + node is FunctionDeclaration || + node is ReturnStatement || + node is ThrowStatement || + node is TryStatement || + node is EmptyStatement || + node is ClassDeclaration) + return false; + if (node is ExportNamedDeclaration end) + return NodeNeedsSemicolon(end.Declaration); + return true; + } + + public bool ExpressionNeedsBrackets(Node? node) + { + if (node is FunctionExpression) + return true; + if (node is ArrowFunctionExpression) + return true; + if (node is AssignmentExpression) + return true; + if (node is SequenceExpression) + return true; + if (node is ConditionalExpression) + return true; + if (node is BinaryExpression) + return true; + if (node is UnaryExpression) + return true; + if (node is CallExpression) + return true; + if (node is NewExpression) + return true; + if (node is ObjectPattern) + return true; + if (node is ArrayPattern) + return true; + if (node is YieldExpression) + return true; + return false; + } + } +} diff --git a/test/Esprima.Tests/JavascriptTest.cs b/test/Esprima.Tests/JavascriptTest.cs new file mode 100644 index 00000000..37dc4017 --- /dev/null +++ b/test/Esprima.Tests/JavascriptTest.cs @@ -0,0 +1,425 @@ +using Esprima.Utils; +using Xunit; + +namespace Esprima.Tests +{ + public class JavascriptTest + { + [Fact] + public void ToJavascriptTest1() + { + var parser = new JavaScriptParser(@"if (true) { p(); } +switch(foo) { + case 'A': + p(); + break; +} +switch(foo) { + default: + p(); + break; +} +for (var a = []; ; ) { } +for (var elem of list) { } +"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + + Assert.Equal("if(true){p();}switch(foo){case 'A':p();break;}switch(foo){default:p();break;}for(var a=[];;){}for(var elem of list){}", code); + } + + [Fact] + public void ToJavascriptTest2() + { + var parser = new JavaScriptParser(@"let tips = [ + ""Click on any AST node with a '+' to expand it"", + + ""Hovering over a node highlights the \ + corresponding location in the source code"", + + ""Shift click on an AST node to expand the whole subtree"" +]; + + function printTips() + { + tips.forEach((tip, i) => console.log(`Tip ${ i}:` +tip)); + }"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest3() + { + var parser = new JavaScriptParser(@"export class aa extends HTMLElement{ + constructor(a, b) + { + super(a); + this._div = document.createElement('div'); + } + static get is() { + return 'aa'; + } +}"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest4() + { + var parser = new JavaScriptParser(@"import { MccDialog } from '../mccDialogHandler'; +import { commonClient, bb as f } from '../commonClient/commonClient'; +import ii, { hh, jj } from '../commonClient/commonClient'; +import '../commonClient/commonClient'; +import aa from 'module-name'; +import zz, * as ff from 'module-name'; +import * as name from 'module-name'; +import('qq'); +a++; +--a; +export function checkSecurityAnswerCodeDirect(result) { + if (!result) { + MccDialog.warning({ + title: 'SecurityClientErrorOccured', + message: '

internal error, check console

', + }); + return false; + } + switch (result.SecurityAnswerCode) { + case 'Allowed': + return true; + case 'Exception': + MccDialog.warning({ + title: 'SecurityClientInfoTitle', + message: '

SecurityClientExceptionOccured

Exception: ' + result.Message + '

' + result.StackTrace, + }); + return false; + case 'Error': + MccDialog.warning({ + title: 'SecurityClientErrorOccured', + message: '

' + + commonClient.getTranslation('SecurityClientMessage') + + ': ' + + commonClient.getTranslation(result.Message) + + '

' + + (result.MessageDetails ? '

SecurityClientDetails: ' + result.MessageDetails + '

' : ' '), + }); + return false; + default: { + let messagesnippet = '

SecurityClient_' + result.SecurityAnswerCode + '

'; + if (result.Message !== undefined && result.SecurityAnswerCode === 'LoginFailed') { + messagesnippet += '\n\nSecurityClient_InternalServerErrorMessage\n' + result.Message + ''; + } + if (result.Role) { + messagesnippet += '

SecurityClient_CheckedRole' + ' [' + result.Role + ']' + '

'; + } + MccDialog.warning({ + title: 'SecurityClientInfoTitle', + message: messagesnippet, + }); + return false; + } + } +}"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest5() + { + var parser = new JavaScriptParser(@"(function () { + 'use strict'; +})(); + +(class ApplyShimInterface { + constructor() { + this.customStyleInterface = null; + applyShim['invalidCallback'] = ApplyShimUtils.invalidate; + } +}); + +( + a +)(); + + +aa({}); + +(function aa(){});"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest6() + { + var parser = new JavaScriptParser(@"function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; + }"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest7() + { + var parser = new JavaScriptParser(@"if ((x ? a.nodeName.toLowerCase() === f : 1 === a.nodeType) && ++d && (p && ((i = (o = a[S] || (a[S] = {}))[a.uniqueID] || (o[a.uniqueID] = {}))[h] = [k, d]), a === e)) +{ +}"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest8() + { + var parser = new JavaScriptParser(@" +class a extends b { + constructor() { + super(); + this.g=1; + } + + q=1; + r='cc'; +} +"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest9() + { + var parser = new JavaScriptParser(@" +d = (s = (r = (i = (o = (a = c)[S] || (a[S] = {}))[a.uniqueID] || (o[a.uniqueID] = {}))[h] || [])[0] === k && r[1]) && r[2], a = s && c.childNodes[s]; +"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest10() + { + var parser = new JavaScriptParser(@" +m = (z.document, !!v.documentElement && !!v.head && 'function' == typeof v.addEventListener && v.createElement, ~a.indexOf('MSIE') || a.indexOf('Trident/'), '___FONT_AWESOME___') +"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest11() + { + var parser = new JavaScriptParser(@" + var h = (c.navigator || {}).userAgent, + a = void 0 === h ? '' : h, + z = c, + v = l, + m = (z.document, !!v.documentElement && !!v.head && 'function' == typeof v.addEventListener && v.createElement, ~a.indexOf('MSIE') || a.indexOf('Trident/'), '___FONT_AWESOME___'), + e = function() { + try { + return !0 + } catch (c) { + return !1 + } + }(); +"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest12() + { + var parser = new JavaScriptParser(@" +var a = { +children: (b = O, 'g' === b.tag ? b.children : [b]) +} +"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest13() + { + var parser = new JavaScriptParser(@" +if (e.IsWebService) + if (h = e.HttpRequest.responseXML, 'undefined' == typeof h) Trace.Write('Error: ' + e.UniqueId + ' data has no properties!'), m = !0; + else try { + h.setProperty('SelectionLanguage', 'XPath') + } catch (l) { + Trace.Write('Error: data.setProperty('SelectionLanguage', 'XPath') because ' + l.message) + } else h = e.HttpRequest.responseText; +"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest14() + { + var parser = new JavaScriptParser(@" +function tt(t, r) { + var n, e, i = b(t), + s = b(r); + if (s && (e = ft(r)), i); + else if (s) return D(t, e) ? void $(t, e) : (n = l(e, t), G(t, n), void ht(t)); + var g, o, f; + for (f = t.length < r.length ? t.length : r.length, o = 0, g = 0; f > g; g++) o += t[g] + r[g], t[g] = o & _t, o >>= at; + for (g = f; o && g < t.length; g++) o += t[g], t[g] = o & _t, o >>= at +} +"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest15() + { + var parser = new JavaScriptParser(@" +h='M'+(+new Date).toString(36) +"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest16() + { + var parser = new JavaScriptParser(@" +input.onchange = async (e) => { + const files = await readFiles(input.files, readMode); + document.body.removeChild(input); + resolve(files); + }; +"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest17() + { + var parser = new JavaScriptParser(@" +export const Base = LegacyElementMixin(HTMLElement).prototype; +"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest18() + { + var parser = new JavaScriptParser(@" +let {is} = getIsExtends(element); +"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest19() + { + var parser = new JavaScriptParser(@" +export const wrap = + (window['ShadyDOM'] && window['ShadyDOM']['wrap']) || (node => node); +"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest20() + { + var parser = new JavaScriptParser(@" +export {}"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest21() + { + var parser = new JavaScriptParser(@" +(() => { + mutablePropertyChange = MutableData._mutablePropertyChange; +})(); +"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest22() + { + var parser = new JavaScriptParser(@" +var Ol, jl = new (function() { + var l, h, z; + return l = c + }()) +"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest23() + { + var parser = new JavaScriptParser(@" + +[y, { + [Symbol.iterator]() { + return b + },a:5 + }] + +"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest24() + { + var parser = new JavaScriptParser(@" + +class A { +*[Symbol.iterator]() { + let L = this._first; + for (; L !== _.Undefined; ) + yield L.element, + L = L.next + } +} + +"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + + [Fact] + public void ToJavascriptTest25() + { + var parser = new JavaScriptParser(@" +var i = function e(i) { + var r = n[i]; + if (void 0 !== r) + return r.exports; + var a = n[i] = { + exports: {} + }; + return t[i](a, a.exports, e), + a.exports + }(15); +"); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program); + } + } +} From fbf1cad14eb2ba5a54a91b583d38ad53254f37ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20K=C3=BChner?= Date: Thu, 4 Nov 2021 21:56:07 +0100 Subject: [PATCH 02/23] fix coding to match esprima-net style --- src/Esprima/Utils/ToJavascriptConverter.cs | 210 ++++++++++++++++++++- 1 file changed, 200 insertions(+), 10 deletions(-) diff --git a/src/Esprima/Utils/ToJavascriptConverter.cs b/src/Esprima/Utils/ToJavascriptConverter.cs index 5a90e9b3..86fd1485 100644 --- a/src/Esprima/Utils/ToJavascriptConverter.cs +++ b/src/Esprima/Utils/ToJavascriptConverter.cs @@ -298,7 +298,9 @@ protected virtual void VisitCatchClause(CatchClause catchClause) { _sb.Append("("); if (catchClause.Param is not null) + { Visit(catchClause.Param); + } _sb.Append(")"); Visit(catchClause.Body); } @@ -306,10 +308,14 @@ protected virtual void VisitCatchClause(CatchClause catchClause) protected virtual void VisitFunctionDeclaration(FunctionDeclaration functionDeclaration) { if (functionDeclaration.Async) + { _sb.Append("async "); + } _sb.Append("function"); if (functionDeclaration.Generator) + { _sb.Append("*"); + } if (functionDeclaration.Id != null) { _sb.Append(" "); @@ -379,12 +385,13 @@ protected virtual void VisitSwitchCase(SwitchCase switchCase) { if (switchCase.Test != null) { - //todo, remove space after case, if testcase is a string starting with " or ' WriteStartLineToSb("case "); Visit(switchCase.Test); } else + { WriteStartLineToSb("default"); + } WriteStartLineToSb(":"); VisitNodeList(switchCase.Consequent, appendAtEnd: ";"); @@ -415,7 +422,9 @@ protected virtual void VisitIfStatement(IfStatement ifStatement) WriteEndLineToSb(")"); Visit(ifStatement.Consequent); if (NodeNeedsSemicolon(ifStatement.Consequent)) + { _sb.Append(";"); + } if (ifStatement.Alternate != null) { _sb.Append(" else "); @@ -440,10 +449,14 @@ protected virtual void VisitExpressionStatement(ExpressionStatement expressionSt if (expressionStatement.Expression is CallExpression callExpression && !(callExpression.Callee is Identifier)) { if (ExpressionNeedsBrackets(callExpression.Callee)) + { _sb.Append("("); + } Visit(callExpression.Callee); if (ExpressionNeedsBrackets(callExpression.Callee)) + { _sb.Append(")"); + } _sb.Append("("); VisitNodeList(callExpression.Arguments, appendSeperatorString: ","); _sb.Append(")"); @@ -455,7 +468,9 @@ protected virtual void VisitExpressionStatement(ExpressionStatement expressionSt _sb.Append(")"); } else + { Visit(expressionStatement.Expression); + } } protected virtual void VisitForStatement(ForStatement forStatement) @@ -478,7 +493,9 @@ protected virtual void VisitForStatement(ForStatement forStatement) _sb.Append(")"); Visit(forStatement.Body); if (NodeNeedsSemicolon(forStatement.Body)) + { _sb.Append(";"); + } } protected virtual void VisitForInStatement(ForInStatement forInStatement) @@ -490,7 +507,9 @@ protected virtual void VisitForInStatement(ForInStatement forInStatement) _sb.Append(")"); Visit(forInStatement.Body); if (NodeNeedsSemicolon(forInStatement.Body)) + { _sb.Append(";"); + } } protected virtual void VisitDoWhileStatement(DoWhileStatement doWhileStatement) @@ -498,7 +517,9 @@ protected virtual void VisitDoWhileStatement(DoWhileStatement doWhileStatement) _sb.Append("do "); Visit(doWhileStatement.Body); if (NodeNeedsSemicolon(doWhileStatement.Body)) + { _sb.Append(";"); + } _sb.Append("while("); Visit(doWhileStatement.Test); _sb.Append(")"); @@ -519,10 +540,14 @@ protected virtual void VisitArrowFunctionExpression(ArrowFunctionExpression arro if (arrowFunctionExpression.Params.Count == 1) { if (arrowFunctionExpression.Params[0] is RestElement || ExpressionNeedsBrackets(arrowFunctionExpression.Params[0])) + { _sb.Append("("); + } Visit(arrowFunctionExpression.Params[0]); if (arrowFunctionExpression.Params[0] is RestElement || ExpressionNeedsBrackets(arrowFunctionExpression.Params[0])) + { _sb.Append(")"); + } } else { @@ -532,10 +557,14 @@ protected virtual void VisitArrowFunctionExpression(ArrowFunctionExpression arro } _sb.Append("=>"); if (arrowFunctionExpression.Body is ObjectExpression || arrowFunctionExpression.Body is SequenceExpression) + { _sb.Append("("); + } Visit(arrowFunctionExpression.Body); if (arrowFunctionExpression.Body is ObjectExpression || arrowFunctionExpression.Body is SequenceExpression) + { _sb.Append(")"); + } } protected virtual void VisitUnaryExpression(UnaryExpression unaryExpression) @@ -548,21 +577,31 @@ protected virtual void VisitUnaryExpression(UnaryExpression unaryExpression) _sb.Append(" "); } if (!(unaryExpression.Argument is Literal) && !(unaryExpression.Argument is UnaryExpression)) + { _sb.Append("("); + } Visit(unaryExpression.Argument); if (!(unaryExpression.Argument is Literal) && !(unaryExpression.Argument is UnaryExpression)) + { _sb.Append(")"); + } if (!unaryExpression.Prefix) + { _sb.Append(op); + } } protected virtual void VisitUpdateExpression(UpdateExpression updateExpression) { if (updateExpression.Prefix) + { _sb.Append(GetEnumValue("unaryoperator", updateExpression.Operator)); + } Visit(updateExpression.Argument); if (!updateExpression.Prefix) + { _sb.Append(GetEnumValue("unaryoperator", updateExpression.Operator)); + } } protected virtual void VisitThisExpression(ThisExpression thisExpression) @@ -586,12 +625,18 @@ protected virtual void VisitNewExpression(NewExpression newExpression) { _sb.Append("new"); if (ExpressionNeedsBrackets(newExpression.Callee)) + { _sb.Append("("); + } else + { _sb.Append(" "); + } Visit(newExpression.Callee); if (ExpressionNeedsBrackets(newExpression.Callee)) + { _sb.Append(")"); + } if (newExpression.Arguments.Count > 0) { _sb.Append("("); @@ -603,12 +648,18 @@ protected virtual void VisitNewExpression(NewExpression newExpression) protected virtual void VisitMemberExpression(MemberExpression memberExpression) { if (ExpressionNeedsBrackets(memberExpression.Object) || (memberExpression.Object is Literal l && l.TokenType != TokenType.StringLiteral)) + { _sb.Append("("); + } Visit(memberExpression.Object); if (ExpressionNeedsBrackets(memberExpression.Object) || (memberExpression.Object is Literal l2 && l2.TokenType != TokenType.StringLiteral)) + { _sb.Append(")"); + } if (memberExpression.Computed) + { _sb.Append("["); + } else { if (TryGetParentAt(0) is ChainExpression) @@ -617,7 +668,9 @@ protected virtual void VisitMemberExpression(MemberExpression memberExpression) } Visit(memberExpression.Property); if (memberExpression.Computed) + { _sb.Append("]"); + } } protected virtual void VisitLiteral(Literal literal) @@ -636,11 +689,17 @@ protected virtual void VisitFunctionExpression(IFunction function) if (!isParentMethod) { if (function.Async) + { _sb.Append("async "); + } if (!(TryGetParentAt(1) is MethodDefinition)) + { _sb.Append("function"); + } if (function.Generator) + { _sb.Append("*"); + } } if (function.Id != null) { @@ -660,7 +719,6 @@ protected virtual void VisitClassExpression(ClassExpression classExpression) { Visit(classExpression.Id); } - if (classExpression.SuperClass != null) { _sb.Append(" extends "); @@ -706,7 +764,9 @@ protected virtual void VisitExportNamedDeclaration(ExportNamedDeclaration export Visit(exportNamedDeclaration.Source); } if (exportNamedDeclaration.Declaration == null && exportNamedDeclaration.Specifiers.Count == 0 && exportNamedDeclaration.Source == null) + { _sb.Append("{}"); + } } @@ -738,7 +798,9 @@ protected virtual void VisitImportDeclaration(ImportDeclaration importDeclaratio { _sb.Append(","); if (importDeclaration.Specifiers[1] is ImportNamespaceSpecifier) + { VisitNodeList(importDeclaration.Specifiers.Skip(1), appendSeperatorString: ","); + } else { _sb.Append("{"); @@ -750,7 +812,9 @@ protected virtual void VisitImportDeclaration(ImportDeclaration importDeclaratio else if (importDeclaration.Specifiers.Any()) { if (importDeclaration.Specifiers[0] is ImportNamespaceSpecifier) + { VisitNodeList(importDeclaration.Specifiers, appendSeperatorString: ","); + } else { _sb.Append("{"); @@ -789,24 +853,42 @@ protected virtual void VisitImportSpecifier(ImportSpecifier importSpecifier) protected virtual void VisitMethodDefinition(MethodDefinition methodDefinition) { if (methodDefinition.Static) + { _sb.Append("static "); + } if (IsAsync(methodDefinition.Value)) + { _sb.Append("async "); + } if (methodDefinition.Value is FunctionExpression f && f.Generator) + { _sb.Append("*"); + } if (methodDefinition.Kind == PropertyKind.Get) + { _sb.Append("get "); + } else if (methodDefinition.Kind == PropertyKind.Set) + { _sb.Append("set "); + } if (methodDefinition.Key is MemberExpression || ExpressionNeedsBrackets(methodDefinition.Key)) + { _sb.Append("["); + } if (ExpressionNeedsBrackets(methodDefinition.Key)) + { _sb.Append("("); + } Visit(methodDefinition.Key); if (ExpressionNeedsBrackets(methodDefinition.Key)) + { _sb.Append(")"); + } if (methodDefinition.Key is MemberExpression || ExpressionNeedsBrackets(methodDefinition.Key)) + { _sb.Append("]"); + } Visit(methodDefinition.Value); } @@ -819,7 +901,9 @@ protected virtual void VisitForOfStatement(ForOfStatement forOfStatement) _sb.Append(")"); Visit(forOfStatement.Body); if (NodeNeedsSemicolon(forOfStatement.Body)) + { _sb.Append(";"); + } } protected virtual void VisitClassDeclaration(ClassDeclaration classDeclaration) @@ -906,23 +990,19 @@ protected virtual void VisitArrayPattern(ArrayPattern arrayPattern) protected virtual void VisitVariableDeclarator(VariableDeclarator variableDeclarator) { - if (variableDeclarator.Id is ObjectPattern) - { - //_sb.Append("{"); - } Visit(variableDeclarator.Id); - if (variableDeclarator.Id is ObjectPattern) - { - //_sb.Append("}"); - } if (variableDeclarator.Init != null) { _sb.Append("="); if (ExpressionNeedsBrackets(variableDeclarator.Init)) + { _sb.Append("("); + } Visit(variableDeclarator.Init); if (ExpressionNeedsBrackets(variableDeclarator.Init)) + { _sb.Append(")"); + } } } @@ -956,24 +1036,36 @@ protected virtual void VisitRestElement(RestElement restElement) protected virtual void VisitProperty(Property property) { if (property.Key is MemberExpression || ExpressionNeedsBrackets(property.Key)) + { _sb.Append("["); + } if (ExpressionNeedsBrackets(property.Key)) + { _sb.Append("("); + } Visit(property.Key); if (ExpressionNeedsBrackets(property.Key)) + { _sb.Append(")"); + } if (property.Key is MemberExpression || ExpressionNeedsBrackets(property.Key)) + { _sb.Append("]"); + } if (property.Key is Identifier keyI && property.Value is Identifier valueI && keyI.Name == valueI.Name) { } else { _sb.Append(":"); if (property.Value is not ObjectPattern && ExpressionNeedsBrackets(property.Value)) + { _sb.Append("("); + } Visit(property.Value); if (property.Value is not ObjectPattern && ExpressionNeedsBrackets(property.Value)) + { _sb.Append(")"); + } } } @@ -984,14 +1076,22 @@ protected virtual void VisitPropertyDefinition(PropertyDefinition propertyDefini _sb.Append("static "); } if (propertyDefinition.Key is MemberExpression || ExpressionNeedsBrackets(propertyDefinition.Key)) + { _sb.Append("["); + } if (ExpressionNeedsBrackets(propertyDefinition.Key)) + { _sb.Append("("); + } Visit(propertyDefinition.Key); if (ExpressionNeedsBrackets(propertyDefinition.Key)) + { _sb.Append(")"); + } if (propertyDefinition.Key is MemberExpression || ExpressionNeedsBrackets(propertyDefinition.Key)) + { _sb.Append("]"); + } if (propertyDefinition.Value != null) { _sb.Append("="); @@ -1009,31 +1109,47 @@ protected virtual void VisitAwaitExpression(AwaitExpression awaitExpression) protected virtual void VisitConditionalExpression(ConditionalExpression conditionalExpression) { if (conditionalExpression.Test is AssignmentExpression) + { _sb.Append("("); + } Visit(conditionalExpression.Test); if (conditionalExpression.Test is AssignmentExpression) + { _sb.Append(")"); + } _sb.Append("?"); if (ExpressionNeedsBrackets(conditionalExpression.Consequent)) + { _sb.Append("("); + } Visit(conditionalExpression.Consequent); if (ExpressionNeedsBrackets(conditionalExpression.Consequent)) + { _sb.Append(")"); + } _sb.Append(":"); if (ExpressionNeedsBrackets(conditionalExpression.Alternate)) + { _sb.Append("("); + } Visit(conditionalExpression.Alternate); if (ExpressionNeedsBrackets(conditionalExpression.Alternate)) + { _sb.Append(")"); + } } protected virtual void VisitCallExpression(CallExpression callExpression) { if (ExpressionNeedsBrackets(callExpression.Callee)) + { _sb.Append("("); + } Visit(callExpression.Callee); if (ExpressionNeedsBrackets(callExpression.Callee)) + { _sb.Append(")"); + } _sb.Append("("); VisitNodeList(callExpression.Arguments, appendSeperatorString: ",", appendBracketsIfNeeded: true); _sb.Append(")"); @@ -1042,21 +1158,33 @@ protected virtual void VisitCallExpression(CallExpression callExpression) protected virtual void VisitBinaryExpression(BinaryExpression binaryExpression) { if (ExpressionNeedsBrackets(binaryExpression.Left)) + { _sb.Append("("); + } Visit(binaryExpression.Left); if (ExpressionNeedsBrackets(binaryExpression.Left)) + { _sb.Append(")"); + } var op = GetEnumValue("operator", binaryExpression.Operator); if (char.IsLetter(op[0])) + { _sb.Append(" "); + } _sb.Append(op); if (char.IsLetter(op[0])) + { _sb.Append(" "); + } if (ExpressionNeedsBrackets(binaryExpression.Right)) + { _sb.Append("("); + } Visit(binaryExpression.Right); if (ExpressionNeedsBrackets(binaryExpression.Right)) + { _sb.Append(")"); + } } protected virtual void VisitArrayExpression(ArrayExpression arrayExpression) @@ -1069,17 +1197,25 @@ protected virtual void VisitArrayExpression(ArrayExpression arrayExpression) protected virtual void VisitAssignmentExpression(AssignmentExpression assignmentExpression) { if (assignmentExpression.Left is ObjectPattern) + { _sb.Append("("); + } var op = GetEnumValue("assignmentoperator", assignmentExpression.Operator); Visit(assignmentExpression.Left); _sb.Append(op); if (ExpressionNeedsBrackets(assignmentExpression.Right) && !(assignmentExpression.Right is AssignmentExpression)) + { _sb.Append("("); + } Visit(assignmentExpression.Right); if (ExpressionNeedsBrackets(assignmentExpression.Right) && !(assignmentExpression.Right is AssignmentExpression)) + { _sb.Append(")"); + } if (assignmentExpression.Left is ObjectPattern) + { _sb.Append(")"); + } } protected virtual void VisitContinueStatement(ContinueStatement continueStatement) @@ -1104,7 +1240,9 @@ protected virtual void VisitBlockStatement(BlockStatement blockStatement) { WriteStartLineToSb("{"); if (beautify) + { _sb.AppendLine(); + } _indentionLevel++; VisitNodeList(blockStatement.Body, appendAtEnd: ";"); _indentionLevel--; @@ -1121,15 +1259,23 @@ private void VisitNodeList(IEnumerable nodeList, string appendAtEn if (node != null) { if (notfirst && appendSeperatorString != null) + { _sb.Append(appendSeperatorString); + } if (appendBracketsIfNeeded && ExpressionNeedsBrackets(node)) + { _sb.Append("("); + } Visit(node); if (appendBracketsIfNeeded && ExpressionNeedsBrackets(node)) + { _sb.Append(")"); + } notfirst = true; if (appendAtEnd != null && NodeNeedsSemicolon(node)) + { _sb.Append(appendAtEnd); + } } } } @@ -1137,17 +1283,25 @@ private void VisitNodeList(IEnumerable nodeList, string appendAtEn protected virtual void WriteStartLineToSb(string text) { if (!beautify) + { _sb.Append(text); + } else + { _sb.Append(text.PadLeft(_indentionLevel * _indentionSize, _indentionChar)); + } } protected virtual void WriteEndLineToSb(string text) { if (!beautify) + { _sb.Append(text); + } else + { _sb.AppendLine(text); + } } public override string ToString() @@ -1158,13 +1312,21 @@ public override string ToString() public bool IsAsync(Node node) { if (node is ArrowFunctionExpression afe) + { return afe.Async; + } if (node is ArrowParameterPlaceHolder apph) + { return apph.Async; + } if (node is FunctionDeclaration fd) + { return fd.Async; + } if (node is FunctionExpression fe) + { return fe.Async; + } return false; } @@ -1182,38 +1344,66 @@ node is ThrowStatement || node is TryStatement || node is EmptyStatement || node is ClassDeclaration) + { return false; + } if (node is ExportNamedDeclaration end) + { return NodeNeedsSemicolon(end.Declaration); + } return true; } public bool ExpressionNeedsBrackets(Node? node) { if (node is FunctionExpression) + { return true; + } if (node is ArrowFunctionExpression) + { return true; + } if (node is AssignmentExpression) + { return true; + } if (node is SequenceExpression) + { return true; + } if (node is ConditionalExpression) + { return true; + } if (node is BinaryExpression) + { return true; + } if (node is UnaryExpression) + { return true; + } if (node is CallExpression) + { return true; + } if (node is NewExpression) + { return true; + } if (node is ObjectPattern) + { return true; + } if (node is ArrayPattern) + { return true; + } if (node is YieldExpression) + { return true; + } return false; } } From 294cd2d6eda4fa97f866071908a16a04011d3ba0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20K=C3=BChner?= Date: Thu, 4 Nov 2021 23:27:29 +0100 Subject: [PATCH 03/23] fixes after code review --- src/Esprima/Utils/ToJavascriptConverter.cs | 86 ++++++---------------- 1 file changed, 23 insertions(+), 63 deletions(-) diff --git a/src/Esprima/Utils/ToJavascriptConverter.cs b/src/Esprima/Utils/ToJavascriptConverter.cs index 86fd1485..dab5e1b9 100644 --- a/src/Esprima/Utils/ToJavascriptConverter.cs +++ b/src/Esprima/Utils/ToJavascriptConverter.cs @@ -9,6 +9,14 @@ namespace Esprima.Utils { + public static class ToJavascriptConverterExtension + { + public static string ToJavascript(this Node node) + { + return ToJavascriptConverter.ToJavascript(node); + } + } + public class ToJavascriptConverter { public static string ToJavascript(Node node) @@ -19,25 +27,7 @@ public static string ToJavascript(Node node) } protected StringBuilder _sb = new StringBuilder(); - protected int _indentionLevel = 0; - protected bool beautify = false; - protected int _indentionSize = 4; - protected char _indentionChar = ' '; - - private static readonly ConditionalWeakTable EnumMap = new ConditionalWeakTable(); - - private string GetEnumValue(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.GetCustomAttribute() is EnumMemberAttribute a - ? a.Value : f.Name.ToLowerInvariant())); - return map[value]; - } - + private readonly List _parentStack = new List(); protected IReadOnlyList ParentStack => _parentStack; @@ -374,7 +364,7 @@ protected virtual void VisitThrowStatement(ThrowStatement throwStatement) protected virtual void VisitSwitchStatement(SwitchStatement switchStatement) { - WriteStartLineToSb("switch("); + _sb.Append("switch("); Visit(switchStatement.Discriminant); _sb.Append("){"); VisitNodeList(switchStatement.Cases); @@ -385,14 +375,14 @@ protected virtual void VisitSwitchCase(SwitchCase switchCase) { if (switchCase.Test != null) { - WriteStartLineToSb("case "); + _sb.Append("case "); Visit(switchCase.Test); } else { - WriteStartLineToSb("default"); + _sb.Append("default"); } - WriteStartLineToSb(":"); + _sb.Append(":"); VisitNodeList(switchCase.Consequent, appendAtEnd: ";"); } @@ -417,9 +407,9 @@ protected virtual void VisitLabeledStatement(LabeledStatement labeledStatement) protected virtual void VisitIfStatement(IfStatement ifStatement) { - WriteStartLineToSb("if("); + _sb.Append("if("); Visit(ifStatement.Test); - WriteEndLineToSb(")"); + _sb.Append(")"); Visit(ifStatement.Consequent); if (NodeNeedsSemicolon(ifStatement.Consequent)) { @@ -475,7 +465,7 @@ protected virtual void VisitExpressionStatement(ExpressionStatement expressionSt protected virtual void VisitForStatement(ForStatement forStatement) { - WriteStartLineToSb("for("); + _sb.Append("for("); if (forStatement.Init != null) { Visit(forStatement.Init); @@ -569,7 +559,7 @@ protected virtual void VisitArrowFunctionExpression(ArrowFunctionExpression arro protected virtual void VisitUnaryExpression(UnaryExpression unaryExpression) { - var op = GetEnumValue("unaryoperator", unaryExpression.Operator); + var op = UnaryExpression.ConvertUnaryOperator(unaryExpression.Operator); if (unaryExpression.Prefix) { _sb.Append(op); @@ -595,12 +585,12 @@ protected virtual void VisitUpdateExpression(UpdateExpression updateExpression) { if (updateExpression.Prefix) { - _sb.Append(GetEnumValue("unaryoperator", updateExpression.Operator)); + _sb.Append(UnaryExpression.ConvertUnaryOperator(updateExpression.Operator)); } Visit(updateExpression.Argument); if (!updateExpression.Prefix) { - _sb.Append(GetEnumValue("unaryoperator", updateExpression.Operator)); + _sb.Append(UnaryExpression.ConvertUnaryOperator(updateExpression.Operator)); } } @@ -1166,7 +1156,7 @@ protected virtual void VisitBinaryExpression(BinaryExpression binaryExpression) { _sb.Append(")"); } - var op = GetEnumValue("operator", binaryExpression.Operator); + var op = BinaryExpression.ConvertBinaryOperator(binaryExpression.Operator); if (char.IsLetter(op[0])) { _sb.Append(" "); @@ -1200,7 +1190,7 @@ protected virtual void VisitAssignmentExpression(AssignmentExpression assignment { _sb.Append("("); } - var op = GetEnumValue("assignmentoperator", assignmentExpression.Operator); + var op = AssignmentExpression.ConvertAssignmentOperator(assignmentExpression.Operator); Visit(assignmentExpression.Left); _sb.Append(op); if (ExpressionNeedsBrackets(assignmentExpression.Right) && !(assignmentExpression.Right is AssignmentExpression)) @@ -1238,15 +1228,9 @@ protected virtual void VisitBreakStatement(BreakStatement breakStatement) protected virtual void VisitBlockStatement(BlockStatement blockStatement) { - WriteStartLineToSb("{"); - if (beautify) - { - _sb.AppendLine(); - } - _indentionLevel++; + _sb.Append("{"); VisitNodeList(blockStatement.Body, appendAtEnd: ";"); - _indentionLevel--; - WriteEndLineToSb("}"); + _sb.Append("}"); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -1280,30 +1264,6 @@ private void VisitNodeList(IEnumerable nodeList, string appendAtEn } } - protected virtual void WriteStartLineToSb(string text) - { - if (!beautify) - { - _sb.Append(text); - } - else - { - _sb.Append(text.PadLeft(_indentionLevel * _indentionSize, _indentionChar)); - } - } - - protected virtual void WriteEndLineToSb(string text) - { - if (!beautify) - { - _sb.Append(text); - } - else - { - _sb.AppendLine(text); - } - } - public override string ToString() { return _sb.ToString(); From 0cefdb6d33a074ab8f2bc103b46bc24a19599e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20K=C3=BChner?= Date: Tue, 9 Nov 2021 22:56:30 +0100 Subject: [PATCH 04/23] work on ToJavascriptTest and Beautification --- src/Esprima/Utils/ToJavascriptConverter.cs | 604 +++++++++++++-------- test/Esprima.Tests/JavascriptTest.cs | 146 ++++- 2 files changed, 518 insertions(+), 232 deletions(-) diff --git a/src/Esprima/Utils/ToJavascriptConverter.cs b/src/Esprima/Utils/ToJavascriptConverter.cs index dab5e1b9..8afadd69 100644 --- a/src/Esprima/Utils/ToJavascriptConverter.cs +++ b/src/Esprima/Utils/ToJavascriptConverter.cs @@ -1,8 +1,6 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; using System.Text; using Esprima.Ast; @@ -11,26 +9,70 @@ namespace Esprima.Utils { public static class ToJavascriptConverterExtension { - public static string ToJavascript(this Node node) + public static string ToJavascript(this Node node, bool beautify = false) { - return ToJavascriptConverter.ToJavascript(node); + return ToJavascriptConverter.ToJavascript(node, beautify); } } public class ToJavascriptConverter { - public static string ToJavascript(Node node) + public static string ToJavascript(Node node, bool beautify = false) { - var visitor = new ToJavascriptConverter(); + var visitor = new ToJavascriptConverter() { Beautify = beautify }; visitor.Visit(node); return visitor.ToString(); } + public bool Beautify { get; set; } + + public int IndentionSize { get; set; } = 4; + protected StringBuilder _sb = new StringBuilder(); - + private int _indentionLevel = 0; + private readonly List _parentStack = new List(); protected IReadOnlyList ParentStack => _parentStack; + private void Append(string text) + { + _sb.Append(text); + } + + private void AppendBeautificationSpace() + { + if (Beautify) + { + _sb.Append(" "); + } + } + + private void AppendIndent() + { + if (Beautify) + { + _sb.Append("".PadLeft(_indentionLevel * IndentionSize, ' ')); + } + } + + private void AppendNewline() + { + if (Beautify) + { + _sb.AppendLine(); + } + } + + private void IncreaseIndent() + { + _indentionLevel++; + } + + private void DecreaseIndent() + { + _indentionLevel--; + } + /// /// Returns parent node at specified position. /// @@ -271,7 +313,7 @@ public virtual void Visit(Node node) protected virtual void VisitProgram(Program program) { - VisitNodeList(program.Body, appendAtEnd: ";"); + VisitNodeList(program.Body, appendAtEnd: ";", addLineBreaks: true); } protected virtual void VisitUnknownNode(Node node) @@ -286,12 +328,12 @@ protected virtual void VisitChainExpression(ChainExpression chainExpression) protected virtual void VisitCatchClause(CatchClause catchClause) { - _sb.Append("("); + Append("("); if (catchClause.Param is not null) { Visit(catchClause.Param); } - _sb.Append(")"); + Append(")"); Visit(catchClause.Body); } @@ -299,139 +341,171 @@ protected virtual void VisitFunctionDeclaration(FunctionDeclaration functionDecl { if (functionDeclaration.Async) { - _sb.Append("async "); + Append("async "); } - _sb.Append("function"); + Append("function"); if (functionDeclaration.Generator) { - _sb.Append("*"); + Append("*"); } if (functionDeclaration.Id != null) { - _sb.Append(" "); + Append(" "); Visit(functionDeclaration.Id); } - _sb.Append("("); + Append("("); VisitNodeList(functionDeclaration.Params, appendSeperatorString: ","); - _sb.Append(")"); + Append(")"); + AppendBeautificationSpace(); Visit(functionDeclaration.Body); } protected virtual void VisitWithStatement(WithStatement withStatement) { - _sb.Append("with("); + Append("with("); Visit(withStatement.Object); - _sb.Append(")"); + Append(")"); Visit(withStatement.Body); } protected virtual void VisitWhileStatement(WhileStatement whileStatement) { - _sb.Append("while("); + Append("while("); Visit(whileStatement.Test); - _sb.Append(")"); + Append(")"); Visit(whileStatement.Body); } protected virtual void VisitVariableDeclaration(VariableDeclaration variableDeclaration) { - _sb.Append(variableDeclaration.Kind.ToString().ToLower() + " "); + Append(variableDeclaration.Kind.ToString().ToLower() + " "); VisitNodeList(variableDeclaration.Declarations, appendSeperatorString: ","); } protected virtual void VisitTryStatement(TryStatement tryStatement) { - _sb.Append("try "); + Append("try "); Visit(tryStatement.Block); if (tryStatement.Handler != null) { - _sb.Append(" catch"); + Append(" catch"); Visit(tryStatement.Handler); } if (tryStatement.Finalizer != null) { - _sb.Append(" finally"); + Append(" finally"); Visit(tryStatement.Finalizer); } } protected virtual void VisitThrowStatement(ThrowStatement throwStatement) { - _sb.Append("throw "); + Append("throw "); Visit(throwStatement.Argument); - _sb.Append(";"); + Append(";"); } protected virtual void VisitSwitchStatement(SwitchStatement switchStatement) { - _sb.Append("switch("); + Append("switch("); Visit(switchStatement.Discriminant); - _sb.Append("){"); + Append("){"); VisitNodeList(switchStatement.Cases); - _sb.Append("}"); + Append("}"); } protected virtual void VisitSwitchCase(SwitchCase switchCase) { if (switchCase.Test != null) { - _sb.Append("case "); + Append("case "); Visit(switchCase.Test); } else { - _sb.Append("default"); + Append("default"); } - _sb.Append(":"); + Append(":"); VisitNodeList(switchCase.Consequent, appendAtEnd: ";"); } protected virtual void VisitReturnStatement(ReturnStatement returnStatement) { - _sb.Append("return"); + Append("return"); if (returnStatement.Argument != null) { - _sb.Append(" "); + Append(" "); Visit(returnStatement.Argument); } - _sb.Append(";"); + Append(";"); } protected virtual void VisitLabeledStatement(LabeledStatement labeledStatement) { Visit(labeledStatement.Label); - _sb.Append(":"); + Append(":"); Visit(labeledStatement.Body); } protected virtual void VisitIfStatement(IfStatement ifStatement) { - _sb.Append("if("); + Append("if"); + AppendBeautificationSpace(); + Append("("); Visit(ifStatement.Test); - _sb.Append(")"); + Append(")"); + AppendBeautificationSpace(); + + if (ifStatement.Consequent is not BlockStatement) + { + AppendNewline(); + IncreaseIndent(); + AppendIndent(); + } Visit(ifStatement.Consequent); if (NodeNeedsSemicolon(ifStatement.Consequent)) { - _sb.Append(";"); + Append(";"); + } + if (ifStatement.Consequent is not BlockStatement) + { + DecreaseIndent(); + if (ifStatement.Alternate != null) + { + AppendNewline(); + AppendIndent(); + } } if (ifStatement.Alternate != null) { - _sb.Append(" else "); + Append(" else "); + if (ifStatement.Alternate is not BlockStatement && ifStatement.Alternate is not IfStatement) + { + AppendNewline(); + IncreaseIndent(); + AppendIndent(); + } Visit(ifStatement.Alternate); if (NodeNeedsSemicolon(ifStatement.Alternate)) - _sb.Append(";"); + { + Append(";"); + } + if (ifStatement.Alternate is not BlockStatement && ifStatement.Alternate is not IfStatement) + { + DecreaseIndent(); + } } } protected virtual void VisitEmptyStatement(EmptyStatement emptyStatement) { - _sb.Append(";"); + Append(";"); } protected virtual void VisitDebuggerStatement(DebuggerStatement debuggerStatement) { - _sb.Append("debugger"); + Append("debugger"); } protected virtual void VisitExpressionStatement(ExpressionStatement expressionStatement) @@ -440,22 +514,22 @@ protected virtual void VisitExpressionStatement(ExpressionStatement expressionSt { if (ExpressionNeedsBrackets(callExpression.Callee)) { - _sb.Append("("); + Append("("); } Visit(callExpression.Callee); if (ExpressionNeedsBrackets(callExpression.Callee)) { - _sb.Append(")"); + Append(")"); } - _sb.Append("("); + Append("("); VisitNodeList(callExpression.Arguments, appendSeperatorString: ","); - _sb.Append(")"); + Append(")"); } else if (expressionStatement.Expression is ClassExpression) { - _sb.Append("("); + Append("("); Visit(expressionStatement.Expression); - _sb.Append(")"); + Append(")"); } else { @@ -465,61 +539,85 @@ protected virtual void VisitExpressionStatement(ExpressionStatement expressionSt protected virtual void VisitForStatement(ForStatement forStatement) { - _sb.Append("for("); + Append("for("); if (forStatement.Init != null) { Visit(forStatement.Init); } - _sb.Append(";"); + Append(";"); if (forStatement.Test != null) { Visit(forStatement.Test); } - _sb.Append(";"); + Append(";"); if (forStatement.Update != null) { Visit(forStatement.Update); } - _sb.Append(")"); + Append(")"); + AppendBeautificationSpace(); + + if (forStatement.Body is not BlockStatement) + { + AppendNewline(); + IncreaseIndent(); + AppendIndent(); + } Visit(forStatement.Body); if (NodeNeedsSemicolon(forStatement.Body)) { - _sb.Append(";"); + Append(";"); + } + if (forStatement.Body is not BlockStatement) + { + DecreaseIndent(); } } protected virtual void VisitForInStatement(ForInStatement forInStatement) { - _sb.Append("for("); + Append("for("); Visit(forInStatement.Left); - _sb.Append(" in "); + Append(" in "); Visit(forInStatement.Right); - _sb.Append(")"); + Append(")"); + AppendBeautificationSpace(); + + if (forInStatement.Body is not BlockStatement) + { + AppendNewline(); + IncreaseIndent(); + AppendIndent(); + } Visit(forInStatement.Body); if (NodeNeedsSemicolon(forInStatement.Body)) { - _sb.Append(";"); + Append(";"); + } + if (forInStatement.Body is not BlockStatement) + { + DecreaseIndent(); } } protected virtual void VisitDoWhileStatement(DoWhileStatement doWhileStatement) { - _sb.Append("do "); + Append("do "); Visit(doWhileStatement.Body); if (NodeNeedsSemicolon(doWhileStatement.Body)) { - _sb.Append(";"); + Append(";"); } - _sb.Append("while("); + Append("while("); Visit(doWhileStatement.Test); - _sb.Append(")"); + Append(")"); } protected virtual void VisitArrowFunctionExpression(ArrowFunctionExpression arrowFunctionExpression) { if (arrowFunctionExpression.Async) { - _sb.Append("async "); + Append("async "); } if (arrowFunctionExpression.Id != null) @@ -531,29 +629,29 @@ protected virtual void VisitArrowFunctionExpression(ArrowFunctionExpression arro { if (arrowFunctionExpression.Params[0] is RestElement || ExpressionNeedsBrackets(arrowFunctionExpression.Params[0])) { - _sb.Append("("); + Append("("); } Visit(arrowFunctionExpression.Params[0]); if (arrowFunctionExpression.Params[0] is RestElement || ExpressionNeedsBrackets(arrowFunctionExpression.Params[0])) { - _sb.Append(")"); + Append(")"); } } else { - _sb.Append("("); + Append("("); VisitNodeList(arrowFunctionExpression.Params, appendSeperatorString: ",", appendBracketsIfNeeded: true); ; - _sb.Append(")"); + Append(")"); } - _sb.Append("=>"); + Append("=>"); if (arrowFunctionExpression.Body is ObjectExpression || arrowFunctionExpression.Body is SequenceExpression) { - _sb.Append("("); + Append("("); } Visit(arrowFunctionExpression.Body); if (arrowFunctionExpression.Body is ObjectExpression || arrowFunctionExpression.Body is SequenceExpression) { - _sb.Append(")"); + Append(")"); } } @@ -562,22 +660,22 @@ protected virtual void VisitUnaryExpression(UnaryExpression unaryExpression) var op = UnaryExpression.ConvertUnaryOperator(unaryExpression.Operator); if (unaryExpression.Prefix) { - _sb.Append(op); + Append(op); if (char.IsLetter(op[0])) - _sb.Append(" "); + Append(" "); } if (!(unaryExpression.Argument is Literal) && !(unaryExpression.Argument is UnaryExpression)) { - _sb.Append("("); + Append("("); } Visit(unaryExpression.Argument); if (!(unaryExpression.Argument is Literal) && !(unaryExpression.Argument is UnaryExpression)) { - _sb.Append(")"); + Append(")"); } if (!unaryExpression.Prefix) { - _sb.Append(op); + Append(op); } } @@ -585,53 +683,65 @@ protected virtual void VisitUpdateExpression(UpdateExpression updateExpression) { if (updateExpression.Prefix) { - _sb.Append(UnaryExpression.ConvertUnaryOperator(updateExpression.Operator)); + Append(UnaryExpression.ConvertUnaryOperator(updateExpression.Operator)); } Visit(updateExpression.Argument); if (!updateExpression.Prefix) { - _sb.Append(UnaryExpression.ConvertUnaryOperator(updateExpression.Operator)); + Append(UnaryExpression.ConvertUnaryOperator(updateExpression.Operator)); } } protected virtual void VisitThisExpression(ThisExpression thisExpression) { - _sb.Append("this"); + Append("this"); } protected virtual void VisitSequenceExpression(SequenceExpression sequenceExpression) { - VisitNodeList(sequenceExpression.Expressions, appendSeperatorString: ","); + VisitNodeList(sequenceExpression.Expressions, appendSeperatorString: Beautify ? ", " : ","); } protected virtual void VisitObjectExpression(ObjectExpression objectExpression) { - _sb.Append("{"); - VisitNodeList(objectExpression.Properties, appendSeperatorString: ","); - _sb.Append("}"); + Append("{"); + if (objectExpression.Properties.Count > 0) + { + AppendNewline(); + IncreaseIndent(); + AppendIndent(); + } + VisitNodeList(objectExpression.Properties, appendSeperatorString: ",", addLineBreaks: true); + if (objectExpression.Properties.Count > 0) + { + AppendNewline(); + DecreaseIndent(); + AppendIndent(); + } + Append("}"); } protected virtual void VisitNewExpression(NewExpression newExpression) { - _sb.Append("new"); + Append("new"); if (ExpressionNeedsBrackets(newExpression.Callee)) { - _sb.Append("("); + Append("("); } else { - _sb.Append(" "); + Append(" "); } Visit(newExpression.Callee); if (ExpressionNeedsBrackets(newExpression.Callee)) { - _sb.Append(")"); + Append(")"); } if (newExpression.Arguments.Count > 0) { - _sb.Append("("); + Append("("); VisitNodeList(newExpression.Arguments, appendSeperatorString: ","); - _sb.Append(")"); + Append(")"); } } @@ -639,38 +749,38 @@ protected virtual void VisitMemberExpression(MemberExpression memberExpression) { if (ExpressionNeedsBrackets(memberExpression.Object) || (memberExpression.Object is Literal l && l.TokenType != TokenType.StringLiteral)) { - _sb.Append("("); + Append("("); } Visit(memberExpression.Object); if (ExpressionNeedsBrackets(memberExpression.Object) || (memberExpression.Object is Literal l2 && l2.TokenType != TokenType.StringLiteral)) { - _sb.Append(")"); + Append(")"); } if (memberExpression.Computed) { - _sb.Append("["); + Append("["); } else { if (TryGetParentAt(0) is ChainExpression) - _sb.Append("?"); - _sb.Append("."); + Append("?"); + Append("."); } Visit(memberExpression.Property); if (memberExpression.Computed) { - _sb.Append("]"); + Append("]"); } } protected virtual void VisitLiteral(Literal literal) { - _sb.Append(literal.Raw); + Append(literal.Raw); } protected virtual void VisitIdentifier(Identifier identifier) { - _sb.Append(identifier.Name); + Append(identifier.Name); } protected virtual void VisitFunctionExpression(IFunction function) @@ -680,48 +790,49 @@ protected virtual void VisitFunctionExpression(IFunction function) { if (function.Async) { - _sb.Append("async "); + Append("async "); } if (!(TryGetParentAt(1) is MethodDefinition)) { - _sb.Append("function"); + Append("function"); } if (function.Generator) { - _sb.Append("*"); + Append("*"); } } if (function.Id != null) { - _sb.Append(" "); + Append(" "); Visit(function.Id); } - _sb.Append("("); + Append("("); VisitNodeList(function.Params, appendSeperatorString: ","); - _sb.Append(")"); + Append(")"); + AppendBeautificationSpace(); Visit(function.Body); } protected virtual void VisitClassExpression(ClassExpression classExpression) { - _sb.Append("class "); + Append("class "); if (classExpression.Id != null) { Visit(classExpression.Id); } if (classExpression.SuperClass != null) { - _sb.Append(" extends "); + Append(" extends "); Visit(classExpression.SuperClass); } - _sb.Append("{"); + Append("{"); Visit(classExpression.Body); - _sb.Append("}"); + Append("}"); } protected virtual void VisitExportDefaultDeclaration(ExportDefaultDeclaration exportDefaultDeclaration) { - _sb.Append("export default "); + Append("export default "); if (exportDefaultDeclaration.Declaration != null) { Visit(exportDefaultDeclaration.Declaration); @@ -730,32 +841,32 @@ protected virtual void VisitExportDefaultDeclaration(ExportDefaultDeclaration ex protected virtual void VisitExportAllDeclaration(ExportAllDeclaration exportAllDeclaration) { - _sb.Append("export*from"); + Append("export*from"); Visit(exportAllDeclaration.Source); } protected virtual void VisitExportNamedDeclaration(ExportNamedDeclaration exportNamedDeclaration) { - _sb.Append("export"); + Append("export"); if (exportNamedDeclaration.Declaration != null) { - _sb.Append(" "); + Append(" "); Visit(exportNamedDeclaration.Declaration); } if (exportNamedDeclaration.Specifiers.Count > 0) { - _sb.Append("{"); + Append("{"); VisitNodeList(exportNamedDeclaration.Specifiers, appendSeperatorString: ","); - _sb.Append("}"); + Append("}"); } if (exportNamedDeclaration.Source != null) { - _sb.Append("from"); + Append("from"); Visit(exportNamedDeclaration.Source); } if (exportNamedDeclaration.Declaration == null && exportNamedDeclaration.Specifiers.Count == 0 && exportNamedDeclaration.Source == null) { - _sb.Append("{}"); + Append("{}"); } } @@ -765,37 +876,40 @@ protected virtual void VisitExportSpecifier(ExportSpecifier exportSpecifier) Visit(exportSpecifier.Local); if (exportSpecifier.Local != exportSpecifier.Exported) { - _sb.Append(" as "); + Append(" as "); Visit(exportSpecifier.Exported); } } protected virtual void VisitImport(Import import) { - _sb.Append("import("); + Append("import("); Visit(import.Source); - _sb.Append(")"); + Append(")"); } protected virtual void VisitImportDeclaration(ImportDeclaration importDeclaration) { - _sb.Append("import "); + Append("import "); var firstSpecifier = importDeclaration.Specifiers.FirstOrDefault(); if (firstSpecifier is ImportDefaultSpecifier) { Visit(firstSpecifier); if (importDeclaration.Specifiers.Count > 1) { - _sb.Append(","); + Append(","); + AppendBeautificationSpace(); if (importDeclaration.Specifiers[1] is ImportNamespaceSpecifier) { - VisitNodeList(importDeclaration.Specifiers.Skip(1), appendSeperatorString: ","); + VisitNodeList(importDeclaration.Specifiers.Skip(1), appendSeperatorString: Beautify ? ", " : ","); } else { - _sb.Append("{"); - VisitNodeList(importDeclaration.Specifiers.Skip(1), appendSeperatorString: ","); - _sb.Append("}"); + Append("{"); + AppendBeautificationSpace(); + VisitNodeList(importDeclaration.Specifiers.Skip(1), appendSeperatorString: Beautify ? ", " : ","); + AppendBeautificationSpace(); + Append("}"); } } } @@ -803,25 +917,27 @@ protected virtual void VisitImportDeclaration(ImportDeclaration importDeclaratio { if (importDeclaration.Specifiers[0] is ImportNamespaceSpecifier) { - VisitNodeList(importDeclaration.Specifiers, appendSeperatorString: ","); + VisitNodeList(importDeclaration.Specifiers, appendSeperatorString: Beautify ? ", " : ","); } else { - _sb.Append("{"); - VisitNodeList(importDeclaration.Specifiers, appendSeperatorString: ","); - _sb.Append("}"); + Append("{"); + AppendBeautificationSpace(); + VisitNodeList(importDeclaration.Specifiers, appendSeperatorString: Beautify ? ", " : ","); + AppendBeautificationSpace(); + Append("}"); } } if (importDeclaration.Specifiers.Count > 0) { - _sb.Append(" from "); + Append(" from "); } Visit(importDeclaration.Source); } protected virtual void VisitImportNamespaceSpecifier(ImportNamespaceSpecifier importNamespaceSpecifier) { - _sb.Append("* as "); + Append("* as "); Visit(importNamespaceSpecifier.Local); } @@ -835,7 +951,7 @@ protected virtual void VisitImportSpecifier(ImportSpecifier importSpecifier) Visit(importSpecifier.Imported); if (importSpecifier.Local != importSpecifier.Imported) { - _sb.Append(" as "); + Append(" as "); Visit(importSpecifier.Local); } } @@ -844,61 +960,73 @@ protected virtual void VisitMethodDefinition(MethodDefinition methodDefinition) { if (methodDefinition.Static) { - _sb.Append("static "); + Append("static "); } if (IsAsync(methodDefinition.Value)) { - _sb.Append("async "); + Append("async "); } if (methodDefinition.Value is FunctionExpression f && f.Generator) { - _sb.Append("*"); + Append("*"); } if (methodDefinition.Kind == PropertyKind.Get) { - _sb.Append("get "); + Append("get "); } else if (methodDefinition.Kind == PropertyKind.Set) { - _sb.Append("set "); + Append("set "); } if (methodDefinition.Key is MemberExpression || ExpressionNeedsBrackets(methodDefinition.Key)) { - _sb.Append("["); + Append("["); } if (ExpressionNeedsBrackets(methodDefinition.Key)) { - _sb.Append("("); + Append("("); } Visit(methodDefinition.Key); if (ExpressionNeedsBrackets(methodDefinition.Key)) { - _sb.Append(")"); + Append(")"); } if (methodDefinition.Key is MemberExpression || ExpressionNeedsBrackets(methodDefinition.Key)) { - _sb.Append("]"); + Append("]"); } Visit(methodDefinition.Value); } protected virtual void VisitForOfStatement(ForOfStatement forOfStatement) { - _sb.Append("for("); + Append("for("); Visit(forOfStatement.Left); - _sb.Append(" of "); + Append(" of "); Visit(forOfStatement.Right); - _sb.Append(")"); + Append(")"); + AppendBeautificationSpace(); + + if (forOfStatement.Body is not BlockStatement) + { + AppendNewline(); + IncreaseIndent(); + AppendIndent(); + } Visit(forOfStatement.Body); if (NodeNeedsSemicolon(forOfStatement.Body)) { - _sb.Append(";"); + Append(";"); + } + if (forOfStatement.Body is not BlockStatement) + { + DecreaseIndent(); } } protected virtual void VisitClassDeclaration(ClassDeclaration classDeclaration) { - _sb.Append("class "); + Append("class "); if (classDeclaration.Id != null) { Visit(classDeclaration.Id); @@ -906,22 +1034,34 @@ protected virtual void VisitClassDeclaration(ClassDeclaration classDeclaration) if (classDeclaration.SuperClass != null) { - _sb.Append(" extends "); + Append(" extends "); Visit(classDeclaration.SuperClass); } - _sb.Append("{"); + + AppendBeautificationSpace(); + Append("{"); + + AppendNewline(); + IncreaseIndent(); + AppendIndent(); + Visit(classDeclaration.Body); - _sb.Append("}"); + + AppendNewline(); + DecreaseIndent(); + AppendIndent(); + + Append("}"); } protected virtual void VisitClassBody(ClassBody classBody) { - VisitNodeList(classBody.Body); + VisitNodeList(classBody.Body, addLineBreaks: true); } protected virtual void VisitYieldExpression(YieldExpression yieldExpression) { - _sb.Append("yield "); + Append("yield "); if (yieldExpression.Argument != null) { Visit(yieldExpression.Argument); @@ -936,13 +1076,13 @@ protected virtual void VisitTaggedTemplateExpression(TaggedTemplateExpression ta protected virtual void VisitSuper(Super super) { - _sb.Append("super"); + Append("super"); } protected virtual void VisitMetaProperty(MetaProperty metaProperty) { Visit(metaProperty.Meta); - _sb.Append("."); + Append("."); Visit(metaProperty.Property); } @@ -953,29 +1093,29 @@ protected virtual void VisitArrowParameterPlaceHolder(ArrowParameterPlaceHolder protected virtual void VisitObjectPattern(ObjectPattern objectPattern) { - _sb.Append("{"); + Append("{"); VisitNodeList(objectPattern.Properties, appendSeperatorString: ","); - _sb.Append("}"); + Append("}"); } protected virtual void VisitSpreadElement(SpreadElement spreadElement) { - _sb.Append("..."); + Append("..."); Visit(spreadElement.Argument); } protected virtual void VisitAssignmentPattern(AssignmentPattern assignmentPattern) { Visit(assignmentPattern.Left); - _sb.Append("="); + Append("="); Visit(assignmentPattern.Right); } protected virtual void VisitArrayPattern(ArrayPattern arrayPattern) { - _sb.Append("["); + Append("["); VisitNodeList(arrayPattern.Elements, appendSeperatorString: ","); - _sb.Append("]"); + Append("]"); } protected virtual void VisitVariableDeclarator(VariableDeclarator variableDeclarator) @@ -983,43 +1123,45 @@ protected virtual void VisitVariableDeclarator(VariableDeclarator variableDeclar Visit(variableDeclarator.Id); if (variableDeclarator.Init != null) { - _sb.Append("="); + AppendBeautificationSpace(); + Append("="); + AppendBeautificationSpace(); if (ExpressionNeedsBrackets(variableDeclarator.Init)) { - _sb.Append("("); + Append("("); } Visit(variableDeclarator.Init); if (ExpressionNeedsBrackets(variableDeclarator.Init)) { - _sb.Append(")"); + Append(")"); } } } protected virtual void VisitTemplateLiteral(TemplateLiteral templateLiteral) { - _sb.Append("`"); + Append("`"); for (int n = 0; n < templateLiteral.Quasis.Count; n++) { Visit(templateLiteral.Quasis[n]); if (templateLiteral.Expressions.Count > n) { - _sb.Append("${"); + Append("${"); Visit(templateLiteral.Expressions[n]); - _sb.Append("}"); + Append("}"); } } - _sb.Append("`"); + Append("`"); } protected virtual void VisitTemplateElement(TemplateElement templateElement) { - _sb.Append(templateElement.Value.Raw); + Append(templateElement.Value.Raw); } protected virtual void VisitRestElement(RestElement restElement) { - _sb.Append("..."); + Append("..."); Visit(restElement.Argument); } @@ -1027,34 +1169,36 @@ protected virtual void VisitProperty(Property property) { if (property.Key is MemberExpression || ExpressionNeedsBrackets(property.Key)) { - _sb.Append("["); + Append("["); } if (ExpressionNeedsBrackets(property.Key)) { - _sb.Append("("); + Append("("); } Visit(property.Key); if (ExpressionNeedsBrackets(property.Key)) { - _sb.Append(")"); + Append(")"); } if (property.Key is MemberExpression || ExpressionNeedsBrackets(property.Key)) { - _sb.Append("]"); + Append("]"); } if (property.Key is Identifier keyI && property.Value is Identifier valueI && keyI.Name == valueI.Name) { } else { - _sb.Append(":"); + AppendBeautificationSpace(); + Append(":"); + AppendBeautificationSpace(); if (property.Value is not ObjectPattern && ExpressionNeedsBrackets(property.Value)) { - _sb.Append("("); + Append("("); } Visit(property.Value); if (property.Value is not ObjectPattern && ExpressionNeedsBrackets(property.Value)) { - _sb.Append(")"); + Append(")"); } } } @@ -1063,36 +1207,36 @@ protected virtual void VisitPropertyDefinition(PropertyDefinition propertyDefini { if (propertyDefinition.Static) { - _sb.Append("static "); + Append("static "); } if (propertyDefinition.Key is MemberExpression || ExpressionNeedsBrackets(propertyDefinition.Key)) { - _sb.Append("["); + Append("["); } if (ExpressionNeedsBrackets(propertyDefinition.Key)) { - _sb.Append("("); + Append("("); } Visit(propertyDefinition.Key); if (ExpressionNeedsBrackets(propertyDefinition.Key)) { - _sb.Append(")"); + Append(")"); } if (propertyDefinition.Key is MemberExpression || ExpressionNeedsBrackets(propertyDefinition.Key)) { - _sb.Append("]"); + Append("]"); } if (propertyDefinition.Value != null) { - _sb.Append("="); + Append("="); Visit(propertyDefinition.Value); } - _sb.Append(";"); + Append(";"); } protected virtual void VisitAwaitExpression(AwaitExpression awaitExpression) { - _sb.Append("await "); + Append("await "); Visit(awaitExpression.Argument); } @@ -1100,32 +1244,32 @@ protected virtual void VisitConditionalExpression(ConditionalExpression conditio { if (conditionalExpression.Test is AssignmentExpression) { - _sb.Append("("); + Append("("); } Visit(conditionalExpression.Test); if (conditionalExpression.Test is AssignmentExpression) { - _sb.Append(")"); + Append(")"); } - _sb.Append("?"); + Append("?"); if (ExpressionNeedsBrackets(conditionalExpression.Consequent)) { - _sb.Append("("); + Append("("); } Visit(conditionalExpression.Consequent); if (ExpressionNeedsBrackets(conditionalExpression.Consequent)) { - _sb.Append(")"); + Append(")"); } - _sb.Append(":"); + Append(":"); if (ExpressionNeedsBrackets(conditionalExpression.Alternate)) { - _sb.Append("("); + Append("("); } Visit(conditionalExpression.Alternate); if (ExpressionNeedsBrackets(conditionalExpression.Alternate)) { - _sb.Append(")"); + Append(")"); } } @@ -1133,84 +1277,94 @@ protected virtual void VisitCallExpression(CallExpression callExpression) { if (ExpressionNeedsBrackets(callExpression.Callee)) { - _sb.Append("("); + Append("("); } Visit(callExpression.Callee); if (ExpressionNeedsBrackets(callExpression.Callee)) { - _sb.Append(")"); + Append(")"); } - _sb.Append("("); + Append("("); VisitNodeList(callExpression.Arguments, appendSeperatorString: ",", appendBracketsIfNeeded: true); - _sb.Append(")"); + Append(")"); } protected virtual void VisitBinaryExpression(BinaryExpression binaryExpression) { if (ExpressionNeedsBrackets(binaryExpression.Left)) { - _sb.Append("("); + Append("("); } Visit(binaryExpression.Left); if (ExpressionNeedsBrackets(binaryExpression.Left)) { - _sb.Append(")"); + Append(")"); } var op = BinaryExpression.ConvertBinaryOperator(binaryExpression.Operator); if (char.IsLetter(op[0])) { - _sb.Append(" "); + Append(" "); + } + else + { + AppendBeautificationSpace(); } - _sb.Append(op); + Append(op); if (char.IsLetter(op[0])) { - _sb.Append(" "); + Append(" "); + } + else + { + AppendBeautificationSpace(); } if (ExpressionNeedsBrackets(binaryExpression.Right)) { - _sb.Append("("); + Append("("); } Visit(binaryExpression.Right); if (ExpressionNeedsBrackets(binaryExpression.Right)) { - _sb.Append(")"); + Append(")"); } } protected virtual void VisitArrayExpression(ArrayExpression arrayExpression) { - _sb.Append("["); + Append("["); VisitNodeList(arrayExpression.Elements, appendSeperatorString: ","); - _sb.Append("]"); + Append("]"); } protected virtual void VisitAssignmentExpression(AssignmentExpression assignmentExpression) { if (assignmentExpression.Left is ObjectPattern) { - _sb.Append("("); + Append("("); } var op = AssignmentExpression.ConvertAssignmentOperator(assignmentExpression.Operator); Visit(assignmentExpression.Left); - _sb.Append(op); + AppendBeautificationSpace(); + Append(op); + AppendBeautificationSpace(); if (ExpressionNeedsBrackets(assignmentExpression.Right) && !(assignmentExpression.Right is AssignmentExpression)) { - _sb.Append("("); + Append("("); } Visit(assignmentExpression.Right); if (ExpressionNeedsBrackets(assignmentExpression.Right) && !(assignmentExpression.Right is AssignmentExpression)) { - _sb.Append(")"); + Append(")"); } if (assignmentExpression.Left is ObjectPattern) { - _sb.Append(")"); + Append(")"); } } protected virtual void VisitContinueStatement(ContinueStatement continueStatement) { - _sb.Append("continue "); + Append("continue "); if (continueStatement.Label != null) { Visit(continueStatement.Label); @@ -1223,18 +1377,28 @@ protected virtual void VisitBreakStatement(BreakStatement breakStatement) { Visit(breakStatement.Label); } - _sb.Append("break"); + Append("break"); } protected virtual void VisitBlockStatement(BlockStatement blockStatement) { - _sb.Append("{"); - VisitNodeList(blockStatement.Body, appendAtEnd: ";"); - _sb.Append("}"); + Append("{"); + + AppendNewline(); + IncreaseIndent(); + AppendIndent(); + + VisitNodeList(blockStatement.Body, appendAtEnd: ";", addLineBreaks: true); + + AppendNewline(); + DecreaseIndent(); + AppendIndent(); + + Append("}"); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void VisitNodeList(IEnumerable nodeList, string appendAtEnd = null, string appendSeperatorString = null, bool appendBracketsIfNeeded = false) + private void VisitNodeList(IEnumerable nodeList, string appendAtEnd = null, string appendSeperatorString = null, bool appendBracketsIfNeeded = false, bool addLineBreaks = false) where TNode : Node { var notfirst = false; @@ -1242,23 +1406,29 @@ private void VisitNodeList(IEnumerable nodeList, string appendAtEn { if (node != null) { + if (notfirst && addLineBreaks) + { + AppendNewline(); + AppendIndent(); + } + if (notfirst && appendSeperatorString != null) { - _sb.Append(appendSeperatorString); + Append(appendSeperatorString); } if (appendBracketsIfNeeded && ExpressionNeedsBrackets(node)) { - _sb.Append("("); + Append("("); } Visit(node); if (appendBracketsIfNeeded && ExpressionNeedsBrackets(node)) { - _sb.Append(")"); + Append(")"); } notfirst = true; if (appendAtEnd != null && NodeNeedsSemicolon(node)) { - _sb.Append(appendAtEnd); + Append(appendAtEnd); } } } diff --git a/test/Esprima.Tests/JavascriptTest.cs b/test/Esprima.Tests/JavascriptTest.cs index 37dc4017..24366e2c 100644 --- a/test/Esprima.Tests/JavascriptTest.cs +++ b/test/Esprima.Tests/JavascriptTest.cs @@ -1,4 +1,6 @@ -using Esprima.Utils; +using System; +using System.Text.RegularExpressions; +using Esprima.Utils; using Xunit; namespace Esprima.Tests @@ -46,6 +48,7 @@ function printTips() }"); var program = parser.ParseScript(); var code = ToJavascriptConverter.ToJavascript(program); + Assert.Equal("let tips=[\"Click on any AST node with a '+' to expand it\",\"Hovering over a node highlights the \\\r\n corresponding location in the source code\",\"Shift click on an AST node to expand the whole subtree\"];function printTips(){tips.forEach((tip,i)=>console.log((`Tip ${i}:`+tip)));}", code); } [Fact] @@ -63,12 +66,13 @@ static get is() { }"); var program = parser.ParseScript(); var code = ToJavascriptConverter.ToJavascript(program); + Assert.Equal("export class aa extends HTMLElement{constructor(a,b){super(a);this._div=(document.createElement('div'));}static get is(){return 'aa';}}", code); } [Fact] public void ToJavascriptTest4() { - var parser = new JavaScriptParser(@"import { MccDialog } from '../mccDialogHandler'; + var source = @"import { MccDialog } from '../mccDialogHandler'; import { commonClient, bb as f } from '../commonClient/commonClient'; import ii, { hh, jj } from '../commonClient/commonClient'; import '../commonClient/commonClient'; @@ -121,9 +125,52 @@ export function checkSecurityAnswerCodeDirect(result) { return false; } } -}"); +}"; + source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); + var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = ToJavascriptConverter.ToJavascript(program, true); + + var expected = @"import { MccDialog } from '../mccDialogHandler'; +import { commonClient, bb as f } from '../commonClient/commonClient'; +import ii, { hh, jj } from '../commonClient/commonClient'; +import '../commonClient/commonClient'; +import aa from 'module-name'; +import zz, * as ff from 'module-name'; +import * as name from 'module-name'; +import('qq'); +a++; +--a; +export function checkSecurityAnswerCodeDirect(result) { + if (!(result)) { + MccDialog.warning({ + title : 'SecurityClientErrorOccured' + ,message : '

internal error, check console

' + }); + return false; + } + switch(result.SecurityAnswerCode){case 'Allowed':return true;case 'Exception':MccDialog.warning({ + title : 'SecurityClientInfoTitle' + ,message : ((('

SecurityClientExceptionOccured

Exception: ' + result.Message) + '

') + result.StackTrace) + });return false;case 'Error':MccDialog.warning({ + title : 'SecurityClientErrorOccured' + ,message : ((((('

' + (commonClient.getTranslation('SecurityClientMessage'))) + ': ') + (commonClient.getTranslation(result.Message))) + '

') + (result.MessageDetails?(('

SecurityClientDetails: ' + result.MessageDetails) + '

'):' ')) + });return false;default:{ + let messagesnippet = (('

SecurityClient_' + result.SecurityAnswerCode) + '

'); + if ((result.Message !== undefined) && (result.SecurityAnswerCode === 'LoginFailed')) { + messagesnippet += (('\n\nSecurityClient_InternalServerErrorMessage\n' + result.Message) + ''); + } + if (result.Role) { + messagesnippet += (((('

SecurityClient_CheckedRole' + ' [') + result.Role) + ']') + '

'); + } + MccDialog.warning({ + title : 'SecurityClientInfoTitle' + ,message : messagesnippet + }); + return false; + }} +}"; + Assert.Equal(expected, code); } [Fact] @@ -264,8 +311,7 @@ public void ToJavascriptTest13() [Fact] public void ToJavascriptTest14() { - var parser = new JavaScriptParser(@" -function tt(t, r) { + var source = @"function tt(t, r) { var n, e, i = b(t), s = b(r); if (s && (e = ft(r)), i); @@ -274,9 +320,25 @@ function tt(t, r) { for (f = t.length < r.length ? t.length : r.length, o = 0, g = 0; f > g; g++) o += t[g] + r[g], t[g] = o & _t, o >>= at; for (g = f; o && g < t.length; g++) o += t[g], t[g] = o & _t, o >>= at } -"); +"; + source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); + var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = ToJavascriptConverter.ToJavascript(program, true); + + var expected = @"function tt(t,r) { + var n,e,i = (b(t)),s = (b(r)); + if (s && (e = (ft(r))), i) + ; + else if (s) + return D(t,e)?(void ($(t,e))):(n = (l(e,t)), G(t,n), void (ht(t))); + var g,o,f; + for(f = (t.length < r.length?t.length:r.length), o = 0, g = 0;f > g;g++) + o += (t[g] + r[g]), t[g] = (o & _t), o >>= at; + for(g = f;o && (g < t.length);g++) + o += t[g], t[g] = (o & _t), o >>= at; +}"; + Assert.Equal(expected, code); } [Fact] @@ -287,6 +349,7 @@ public void ToJavascriptTest15() "); var program = parser.ParseScript(); var code = ToJavascriptConverter.ToJavascript(program); + Assert.Equal("h=('M'+((+(new Date)).toString(36)));", code); } [Fact] @@ -301,6 +364,7 @@ public void ToJavascriptTest16() "); var program = parser.ParseScript(); var code = ToJavascriptConverter.ToJavascript(program); + Assert.Equal("input.onchange=(async e=>{const files=await readFiles(input.files,readMode);document.body.removeChild(input);resolve(files);});", code); } [Fact] @@ -311,6 +375,7 @@ public void ToJavascriptTest17() "); var program = parser.ParseScript(); var code = ToJavascriptConverter.ToJavascript(program); + Assert.Equal("export const Base=(LegacyElementMixin(HTMLElement)).prototype;", code); } [Fact] @@ -321,6 +386,7 @@ public void ToJavascriptTest18() "); var program = parser.ParseScript(); var code = ToJavascriptConverter.ToJavascript(program); + Assert.Equal("let {is}=(getIsExtends(element));", code); } [Fact] @@ -332,6 +398,7 @@ public void ToJavascriptTest19() "); var program = parser.ParseScript(); var code = ToJavascriptConverter.ToJavascript(program); + Assert.Equal("export const wrap=((window['ShadyDOM']&&window['ShadyDOM']['wrap'])||(node=>node));", code); } [Fact] @@ -341,6 +408,7 @@ public void ToJavascriptTest20() export {}"); var program = parser.ParseScript(); var code = ToJavascriptConverter.ToJavascript(program); + Assert.Equal("export{};", code); } [Fact] @@ -353,6 +421,7 @@ public void ToJavascriptTest21() "); var program = parser.ParseScript(); var code = ToJavascriptConverter.ToJavascript(program); + Assert.Equal("(()=>{mutablePropertyChange=MutableData._mutablePropertyChange;})();", code); } [Fact] @@ -366,6 +435,7 @@ public void ToJavascriptTest22() "); var program = parser.ParseScript(); var code = ToJavascriptConverter.ToJavascript(program); + Assert.Equal("var Ol,jl=(new((function(){var l,h,z;return l=c;})()));", code); } [Fact] @@ -382,12 +452,13 @@ public void ToJavascriptTest23() "); var program = parser.ParseScript(); var code = ToJavascriptConverter.ToJavascript(program); + Assert.Equal("[y,{[Symbol.iterator]:(function(){return b;}),a:5}];", code); } [Fact] public void ToJavascriptTest24() { - var parser = new JavaScriptParser(@" + var source = @" class A { *[Symbol.iterator]() { @@ -398,16 +469,26 @@ class A { } } -"); +"; + source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); + var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = ToJavascriptConverter.ToJavascript(program, true); + + var expected = @"class A { + *[Symbol.iterator]() { + let L = this._first; + for(;L !== _.Undefined;) + yield L.element, L = L.next; + } +}"; + Assert.Equal(expected, code); } [Fact] public void ToJavascriptTest25() { - var parser = new JavaScriptParser(@" -var i = function e(i) { + var source = @"var i = function e(i) { var r = n[i]; if (void 0 !== r) return r.exports; @@ -417,9 +498,44 @@ public void ToJavascriptTest25() return t[i](a, a.exports, e), a.exports }(15); -"); +"; + source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); + var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = ToJavascriptConverter.ToJavascript(program, true); + + var expected = @"var i = ((function e(i) { + var r = n[i]; + if ((void 0) !== r) + return r.exports; + var a = (n[i] = { + exports : {} + }); + return t[i](a,a.exports,e), a.exports; +})(15));"; + Assert.Equal(expected, code); + } + + [Fact] + public void ToJavascriptTest26() + { + var source = @"class A { + aa() { + let a = 1; + } +} +var b = 1; +var c; +if (b == 2) { + c = 1; +} else { + c = 3; +}"; + source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); + var parser = new JavaScriptParser(source); + var program = parser.ParseScript(); + var code = ToJavascriptConverter.ToJavascript(program, true); + Assert.Equal(source, code); } } } From f9a1029cc6a1e5ddee23c272dfa3be9ce0289a04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20K=C3=BChner?= Date: Tue, 9 Nov 2021 23:14:08 +0100 Subject: [PATCH 05/23] more tests, nicer code --- src/Esprima/Utils/ToJavascriptConverter.cs | 33 ++++++-- test/Esprima.Tests/JavascriptTest.cs | 88 +++++++++++++++------- 2 files changed, 85 insertions(+), 36 deletions(-) diff --git a/src/Esprima/Utils/ToJavascriptConverter.cs b/src/Esprima/Utils/ToJavascriptConverter.cs index 8afadd69..901b0b7d 100644 --- a/src/Esprima/Utils/ToJavascriptConverter.cs +++ b/src/Esprima/Utils/ToJavascriptConverter.cs @@ -409,8 +409,20 @@ protected virtual void VisitSwitchStatement(SwitchStatement switchStatement) { Append("switch("); Visit(switchStatement.Discriminant); - Append("){"); - VisitNodeList(switchStatement.Cases); + Append(")"); + AppendBeautificationSpace(); + Append("{"); + + AppendNewline(); + IncreaseIndent(); + AppendIndent(); + + VisitNodeList(switchStatement.Cases, addLineBreaks: true); + + AppendNewline(); + DecreaseIndent(); + AppendIndent(); + Append("}"); } @@ -427,7 +439,13 @@ protected virtual void VisitSwitchCase(SwitchCase switchCase) } Append(":"); - VisitNodeList(switchCase.Consequent, appendAtEnd: ";"); + AppendNewline(); + IncreaseIndent(); + AppendIndent(); + + VisitNodeList(switchCase.Consequent, appendAtEnd: ";", addLineBreaks: true); + + DecreaseIndent(); } protected virtual void VisitReturnStatement(ReturnStatement returnStatement) @@ -1406,16 +1424,15 @@ private void VisitNodeList(IEnumerable nodeList, string appendAtEn { if (node != null) { + if (notfirst && appendSeperatorString != null) + { + Append(appendSeperatorString); + } if (notfirst && addLineBreaks) { AppendNewline(); AppendIndent(); } - - if (notfirst && appendSeperatorString != null) - { - Append(appendSeperatorString); - } if (appendBracketsIfNeeded && ExpressionNeedsBrackets(node)) { Append("("); diff --git a/test/Esprima.Tests/JavascriptTest.cs b/test/Esprima.Tests/JavascriptTest.cs index 24366e2c..707322db 100644 --- a/test/Esprima.Tests/JavascriptTest.cs +++ b/test/Esprima.Tests/JavascriptTest.cs @@ -130,7 +130,7 @@ export function checkSecurityAnswerCodeDirect(result) { var parser = new JavaScriptParser(source); var program = parser.ParseScript(); var code = ToJavascriptConverter.ToJavascript(program, true); - + var expected = @"import { MccDialog } from '../mccDialogHandler'; import { commonClient, bb as f } from '../commonClient/commonClient'; import ii, { hh, jj } from '../commonClient/commonClient'; @@ -144,31 +144,42 @@ export function checkSecurityAnswerCodeDirect(result) { export function checkSecurityAnswerCodeDirect(result) { if (!(result)) { MccDialog.warning({ - title : 'SecurityClientErrorOccured' - ,message : '

internal error, check console

' + title : 'SecurityClientErrorOccured', + message : '

internal error, check console

' }); return false; } - switch(result.SecurityAnswerCode){case 'Allowed':return true;case 'Exception':MccDialog.warning({ - title : 'SecurityClientInfoTitle' - ,message : ((('

SecurityClientExceptionOccured

Exception: ' + result.Message) + '

') + result.StackTrace) - });return false;case 'Error':MccDialog.warning({ - title : 'SecurityClientErrorOccured' - ,message : ((((('

' + (commonClient.getTranslation('SecurityClientMessage'))) + ': ') + (commonClient.getTranslation(result.Message))) + '

') + (result.MessageDetails?(('

SecurityClientDetails: ' + result.MessageDetails) + '

'):' ')) - });return false;default:{ - let messagesnippet = (('

SecurityClient_' + result.SecurityAnswerCode) + '

'); - if ((result.Message !== undefined) && (result.SecurityAnswerCode === 'LoginFailed')) { - messagesnippet += (('\n\nSecurityClient_InternalServerErrorMessage\n' + result.Message) + ''); - } - if (result.Role) { - messagesnippet += (((('

SecurityClient_CheckedRole' + ' [') + result.Role) + ']') + '

'); - } - MccDialog.warning({ - title : 'SecurityClientInfoTitle' - ,message : messagesnippet - }); - return false; - }} + switch(result.SecurityAnswerCode) { + case 'Allowed': + return true; + case 'Exception': + MccDialog.warning({ + title : 'SecurityClientInfoTitle', + message : ((('

SecurityClientExceptionOccured

Exception: ' + result.Message) + '

') + result.StackTrace) + }); + return false; + case 'Error': + MccDialog.warning({ + title : 'SecurityClientErrorOccured', + message : ((((('

' + (commonClient.getTranslation('SecurityClientMessage'))) + ': ') + (commonClient.getTranslation(result.Message))) + '

') + (result.MessageDetails?(('

SecurityClientDetails: ' + result.MessageDetails) + '

'):' ')) + }); + return false; + default: + { + let messagesnippet = (('

SecurityClient_' + result.SecurityAnswerCode) + '

'); + if ((result.Message !== undefined) && (result.SecurityAnswerCode === 'LoginFailed')) { + messagesnippet += (('\n\nSecurityClient_InternalServerErrorMessage\n' + result.Message) + ''); + } + if (result.Role) { + messagesnippet += (((('

SecurityClient_CheckedRole' + ' [') + result.Role) + ']') + '

'); + } + MccDialog.warning({ + title : 'SecurityClientInfoTitle', + message : messagesnippet + }); + return false; + } + } }"; Assert.Equal(expected, code); } @@ -176,7 +187,7 @@ export function checkSecurityAnswerCodeDirect(result) { [Fact] public void ToJavascriptTest5() { - var parser = new JavaScriptParser(@"(function () { + var source = @"(function () { 'use strict'; })(); @@ -194,21 +205,40 @@ public void ToJavascriptTest5() aa({}); -(function aa(){});"); +(function aa(){});"; + source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); + var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = ToJavascriptConverter.ToJavascript(program, true); + + var expected = @"(function() { + 'use strict'; +})(); +(class ApplyShimInterface{constructor() { + this.customStyleInterface = null; + applyShim['invalidCallback'] = ApplyShimUtils.invalidate; +}}); +a(); +aa({}); +function aa() { + +};"; + Assert.Equal(expected, code); } [Fact] public void ToJavascriptTest6() { - var parser = new JavaScriptParser(@"function _createClass(Constructor, protoProps, staticProps) { + var source = @"function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; - }"); + }"; + source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); + var parser = new JavaScriptParser(source); var program = parser.ParseScript(); var code = ToJavascriptConverter.ToJavascript(program); + Assert.Equal("function _createClass(Constructor,protoProps,staticProps){if(protoProps)_defineProperties(Constructor.prototype,protoProps);if(staticProps)_defineProperties(Constructor,staticProps);return Constructor;}", code); } [Fact] @@ -219,6 +249,7 @@ public void ToJavascriptTest7() }"); var program = parser.ParseScript(); var code = ToJavascriptConverter.ToJavascript(program); + Assert.Equal("if(((x?((a.nodeName.toLowerCase())===f):(1===a.nodeType))&&(++d))&&(p&&((i=((o=(a[S]||(a[S]={})))[a.uniqueID]||(o[a.uniqueID]={})))[h]=[k,d]),a===e)){}", code); } [Fact] @@ -237,6 +268,7 @@ class a extends b { "); var program = parser.ParseScript(); var code = ToJavascriptConverter.ToJavascript(program); + Assert.Equal("class a extends b{constructor(){super();this.g=1;}q=1;r='cc';}", code); } [Fact] From 91727b16b378de8c153a5043852aa9a1292e0beb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20K=C3=BChner?= Date: Tue, 9 Nov 2021 23:23:05 +0100 Subject: [PATCH 06/23] bugfix rest of tests --- test/Esprima.Tests/JavascriptTest.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/Esprima.Tests/JavascriptTest.cs b/test/Esprima.Tests/JavascriptTest.cs index 707322db..73532563 100644 --- a/test/Esprima.Tests/JavascriptTest.cs +++ b/test/Esprima.Tests/JavascriptTest.cs @@ -279,6 +279,7 @@ public void ToJavascriptTest9() "); var program = parser.ParseScript(); var code = ToJavascriptConverter.ToJavascript(program); + Assert.Equal("d=((s=(((r=((i=((o=((a=c)[S]||(a[S]={})))[a.uniqueID]||(o[a.uniqueID]={})))[h]||[]))[0]===k)&&r[1]))&&r[2]),a=(s&&c.childNodes[s]);", code); } [Fact] @@ -289,6 +290,7 @@ public void ToJavascriptTest10() "); var program = parser.ParseScript(); var code = ToJavascriptConverter.ToJavascript(program); + Assert.Equal("m=(z.document,(((!!(v.documentElement))&&(!!(v.head)))&&('function'==(typeof (v.addEventListener))))&&v.createElement,(~(a.indexOf('MSIE')))||(a.indexOf('Trident/')),'___FONT_AWESOME___');", code); } [Fact] @@ -310,6 +312,7 @@ public void ToJavascriptTest11() "); var program = parser.ParseScript(); var code = ToJavascriptConverter.ToJavascript(program); + Assert.Equal("var h=(c.navigator||{}).userAgent,a=((void 0)===h?'':h),z=c,v=l,m=(z.document,(((!!(v.documentElement))&&(!!(v.head)))&&('function'==(typeof (v.addEventListener))))&&v.createElement,(~(a.indexOf('MSIE')))||(a.indexOf('Trident/')),'___FONT_AWESOME___'),e=((function(){try {return !0;} catch(c){return !1;}})());", code); } [Fact] @@ -322,6 +325,7 @@ public void ToJavascriptTest12() "); var program = parser.ParseScript(); var code = ToJavascriptConverter.ToJavascript(program); + Assert.Equal("var a={children:(b=O,'g'===b.tag?b.children:[b])};", code); } [Fact] @@ -338,6 +342,7 @@ public void ToJavascriptTest13() "); var program = parser.ParseScript(); var code = ToJavascriptConverter.ToJavascript(program); + Assert.Equal("if(e.IsWebService)if(h=e.HttpRequest.responseXML,'undefined'==(typeof (h)))Trace.Write((('Error: '+e.UniqueId)+' data has no properties!')),m=(!0); else try {h.setProperty('SelectionLanguage','XPath');} catch(l){Trace.Write('Error: data.setProperty(',SelectionLanguage,', ',XPath,') because '+l.message);} else h=e.HttpRequest.responseText;", code); } [Fact] From d1d84e426610f5a9ef095a7f19f8e12ce45dd6cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20K=C3=BChner?= Date: Wed, 10 Nov 2021 07:21:14 +0100 Subject: [PATCH 07/23] bugfx test, add newline type --- src/Esprima/Utils/ToJavascriptConverter.cs | 72 +++++++++++----------- test/Esprima.Tests/JavascriptTest.cs | 5 ++ 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/src/Esprima/Utils/ToJavascriptConverter.cs b/src/Esprima/Utils/ToJavascriptConverter.cs index 901b0b7d..12f5009e 100644 --- a/src/Esprima/Utils/ToJavascriptConverter.cs +++ b/src/Esprima/Utils/ToJavascriptConverter.cs @@ -28,6 +28,8 @@ public static string ToJavascript(Node node, bool beautify = false) public int IndentionSize { get; set; } = 4; + public string NewlineFormat { get; set; } = Environment.NewLine; + protected StringBuilder _sb = new StringBuilder(); private int _indentionLevel = 0; @@ -47,7 +49,7 @@ private void AppendBeautificationSpace() } } - private void AppendIndent() + private void AppendBeautificationIndent() { if (Beautify) { @@ -55,11 +57,11 @@ private void AppendIndent() } } - private void AppendNewline() + private void AppendBeautificationNewline() { if (Beautify) { - _sb.AppendLine(); + _sb.Append(NewlineFormat); } } @@ -413,15 +415,15 @@ protected virtual void VisitSwitchStatement(SwitchStatement switchStatement) AppendBeautificationSpace(); Append("{"); - AppendNewline(); + AppendBeautificationNewline(); IncreaseIndent(); - AppendIndent(); + AppendBeautificationIndent(); VisitNodeList(switchStatement.Cases, addLineBreaks: true); - AppendNewline(); + AppendBeautificationNewline(); DecreaseIndent(); - AppendIndent(); + AppendBeautificationIndent(); Append("}"); } @@ -439,9 +441,9 @@ protected virtual void VisitSwitchCase(SwitchCase switchCase) } Append(":"); - AppendNewline(); + AppendBeautificationNewline(); IncreaseIndent(); - AppendIndent(); + AppendBeautificationIndent(); VisitNodeList(switchCase.Consequent, appendAtEnd: ";", addLineBreaks: true); @@ -477,9 +479,9 @@ protected virtual void VisitIfStatement(IfStatement ifStatement) if (ifStatement.Consequent is not BlockStatement) { - AppendNewline(); + AppendBeautificationNewline(); IncreaseIndent(); - AppendIndent(); + AppendBeautificationIndent(); } Visit(ifStatement.Consequent); if (NodeNeedsSemicolon(ifStatement.Consequent)) @@ -491,8 +493,8 @@ protected virtual void VisitIfStatement(IfStatement ifStatement) DecreaseIndent(); if (ifStatement.Alternate != null) { - AppendNewline(); - AppendIndent(); + AppendBeautificationNewline(); + AppendBeautificationIndent(); } } if (ifStatement.Alternate != null) @@ -500,9 +502,9 @@ protected virtual void VisitIfStatement(IfStatement ifStatement) Append(" else "); if (ifStatement.Alternate is not BlockStatement && ifStatement.Alternate is not IfStatement) { - AppendNewline(); + AppendBeautificationNewline(); IncreaseIndent(); - AppendIndent(); + AppendBeautificationIndent(); } Visit(ifStatement.Alternate); if (NodeNeedsSemicolon(ifStatement.Alternate)) @@ -577,9 +579,9 @@ protected virtual void VisitForStatement(ForStatement forStatement) if (forStatement.Body is not BlockStatement) { - AppendNewline(); + AppendBeautificationNewline(); IncreaseIndent(); - AppendIndent(); + AppendBeautificationIndent(); } Visit(forStatement.Body); if (NodeNeedsSemicolon(forStatement.Body)) @@ -603,9 +605,9 @@ protected virtual void VisitForInStatement(ForInStatement forInStatement) if (forInStatement.Body is not BlockStatement) { - AppendNewline(); + AppendBeautificationNewline(); IncreaseIndent(); - AppendIndent(); + AppendBeautificationIndent(); } Visit(forInStatement.Body); if (NodeNeedsSemicolon(forInStatement.Body)) @@ -725,16 +727,16 @@ protected virtual void VisitObjectExpression(ObjectExpression objectExpression) Append("{"); if (objectExpression.Properties.Count > 0) { - AppendNewline(); + AppendBeautificationNewline(); IncreaseIndent(); - AppendIndent(); + AppendBeautificationIndent(); } VisitNodeList(objectExpression.Properties, appendSeperatorString: ",", addLineBreaks: true); if (objectExpression.Properties.Count > 0) { - AppendNewline(); + AppendBeautificationNewline(); DecreaseIndent(); - AppendIndent(); + AppendBeautificationIndent(); } Append("}"); } @@ -1027,9 +1029,9 @@ protected virtual void VisitForOfStatement(ForOfStatement forOfStatement) if (forOfStatement.Body is not BlockStatement) { - AppendNewline(); + AppendBeautificationNewline(); IncreaseIndent(); - AppendIndent(); + AppendBeautificationIndent(); } Visit(forOfStatement.Body); if (NodeNeedsSemicolon(forOfStatement.Body)) @@ -1059,15 +1061,15 @@ protected virtual void VisitClassDeclaration(ClassDeclaration classDeclaration) AppendBeautificationSpace(); Append("{"); - AppendNewline(); + AppendBeautificationNewline(); IncreaseIndent(); - AppendIndent(); + AppendBeautificationIndent(); Visit(classDeclaration.Body); - AppendNewline(); + AppendBeautificationNewline(); DecreaseIndent(); - AppendIndent(); + AppendBeautificationIndent(); Append("}"); } @@ -1402,15 +1404,15 @@ protected virtual void VisitBlockStatement(BlockStatement blockStatement) { Append("{"); - AppendNewline(); + AppendBeautificationNewline(); IncreaseIndent(); - AppendIndent(); + AppendBeautificationIndent(); VisitNodeList(blockStatement.Body, appendAtEnd: ";", addLineBreaks: true); - AppendNewline(); + AppendBeautificationNewline(); DecreaseIndent(); - AppendIndent(); + AppendBeautificationIndent(); Append("}"); } @@ -1430,8 +1432,8 @@ private void VisitNodeList(IEnumerable nodeList, string appendAtEn } if (notfirst && addLineBreaks) { - AppendNewline(); - AppendIndent(); + AppendBeautificationNewline(); + AppendBeautificationIndent(); } if (appendBracketsIfNeeded && ExpressionNeedsBrackets(node)) { diff --git a/test/Esprima.Tests/JavascriptTest.cs b/test/Esprima.Tests/JavascriptTest.cs index 73532563..15fd2357 100644 --- a/test/Esprima.Tests/JavascriptTest.cs +++ b/test/Esprima.Tests/JavascriptTest.cs @@ -181,6 +181,7 @@ export function checkSecurityAnswerCodeDirect(result) { } } }"; + expected = Regex.Replace(expected, @"\r\n|\n\r|\n|\r", Environment.NewLine); Assert.Equal(expected, code); } @@ -223,6 +224,7 @@ public void ToJavascriptTest5() function aa() { };"; + expected = Regex.Replace(expected, @"\r\n|\n\r|\n|\r", Environment.NewLine); Assert.Equal(expected, code); } @@ -375,6 +377,7 @@ public void ToJavascriptTest14() for(g = f;o && (g < t.length);g++) o += t[g], t[g] = (o & _t), o >>= at; }"; + expected = Regex.Replace(expected, @"\r\n|\n\r|\n|\r", Environment.NewLine); Assert.Equal(expected, code); } @@ -519,6 +522,7 @@ class A { yield L.element, L = L.next; } }"; + expected = Regex.Replace(expected, @"\r\n|\n\r|\n|\r", Environment.NewLine); Assert.Equal(expected, code); } @@ -550,6 +554,7 @@ public void ToJavascriptTest25() }); return t[i](a,a.exports,e), a.exports; })(15));"; + expected = Regex.Replace(expected, @"\r\n|\n\r|\n|\r", Environment.NewLine); Assert.Equal(expected, code); } From ca68863695faf164476360e366f9760b76eb22d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20K=C3=BChner?= Date: Wed, 10 Nov 2021 07:39:57 +0100 Subject: [PATCH 08/23] bugfix function expression --- src/Esprima/Utils/ToJavascriptConverter.cs | 20 ++++++++++++++++++++ test/Esprima.Tests/JavascriptTest.cs | 14 ++++++++------ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/Esprima/Utils/ToJavascriptConverter.cs b/src/Esprima/Utils/ToJavascriptConverter.cs index 12f5009e..180330c6 100644 --- a/src/Esprima/Utils/ToJavascriptConverter.cs +++ b/src/Esprima/Utils/ToJavascriptConverter.cs @@ -553,7 +553,15 @@ protected virtual void VisitExpressionStatement(ExpressionStatement expressionSt } else { + if (expressionStatement.Expression is FunctionExpression) + { + Append("("); + } Visit(expressionStatement.Expression); + if (expressionStatement.Expression is FunctionExpression) + { + Append(")"); + } } } @@ -845,8 +853,20 @@ protected virtual void VisitClassExpression(ClassExpression classExpression) Append(" extends "); Visit(classExpression.SuperClass); } + + AppendBeautificationSpace(); Append("{"); + + AppendBeautificationNewline(); + IncreaseIndent(); + AppendBeautificationIndent(); + Visit(classExpression.Body); + + AppendBeautificationNewline(); + DecreaseIndent(); + AppendBeautificationIndent(); + Append("}"); } diff --git a/test/Esprima.Tests/JavascriptTest.cs b/test/Esprima.Tests/JavascriptTest.cs index 15fd2357..a9660ec1 100644 --- a/test/Esprima.Tests/JavascriptTest.cs +++ b/test/Esprima.Tests/JavascriptTest.cs @@ -215,15 +215,17 @@ public void ToJavascriptTest5() var expected = @"(function() { 'use strict'; })(); -(class ApplyShimInterface{constructor() { - this.customStyleInterface = null; - applyShim['invalidCallback'] = ApplyShimUtils.invalidate; -}}); +(class ApplyShimInterface { + constructor() { + this.customStyleInterface = null; + applyShim['invalidCallback'] = ApplyShimUtils.invalidate; + } +}); a(); aa({}); -function aa() { +(function aa() { -};"; +});"; expected = Regex.Replace(expected, @"\r\n|\n\r|\n|\r", Environment.NewLine); Assert.Equal(expected, code); } From d2eb4dd62792af60d5c1c17224d1372037fa0870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20K=C3=BChner?= Date: Wed, 10 Nov 2021 07:45:16 +0100 Subject: [PATCH 09/23] more beautification --- src/Esprima/Utils/ToJavascriptConverter.cs | 6 ++++++ test/Esprima.Tests/JavascriptTest.cs | 10 +++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Esprima/Utils/ToJavascriptConverter.cs b/src/Esprima/Utils/ToJavascriptConverter.cs index 180330c6..3a06d283 100644 --- a/src/Esprima/Utils/ToJavascriptConverter.cs +++ b/src/Esprima/Utils/ToJavascriptConverter.cs @@ -573,11 +573,13 @@ protected virtual void VisitForStatement(ForStatement forStatement) Visit(forStatement.Init); } Append(";"); + AppendBeautificationSpace(); if (forStatement.Test != null) { Visit(forStatement.Test); } Append(";"); + AppendBeautificationSpace(); if (forStatement.Update != null) { Visit(forStatement.Update); @@ -1291,7 +1293,9 @@ protected virtual void VisitConditionalExpression(ConditionalExpression conditio { Append(")"); } + AppendBeautificationSpace(); Append("?"); + AppendBeautificationSpace(); if (ExpressionNeedsBrackets(conditionalExpression.Consequent)) { Append("("); @@ -1301,7 +1305,9 @@ protected virtual void VisitConditionalExpression(ConditionalExpression conditio { Append(")"); } + AppendBeautificationSpace(); Append(":"); + AppendBeautificationSpace(); if (ExpressionNeedsBrackets(conditionalExpression.Alternate)) { Append("("); diff --git a/test/Esprima.Tests/JavascriptTest.cs b/test/Esprima.Tests/JavascriptTest.cs index a9660ec1..fbf64f9b 100644 --- a/test/Esprima.Tests/JavascriptTest.cs +++ b/test/Esprima.Tests/JavascriptTest.cs @@ -161,7 +161,7 @@ export function checkSecurityAnswerCodeDirect(result) { case 'Error': MccDialog.warning({ title : 'SecurityClientErrorOccured', - message : ((((('

' + (commonClient.getTranslation('SecurityClientMessage'))) + ': ') + (commonClient.getTranslation(result.Message))) + '

') + (result.MessageDetails?(('

SecurityClientDetails: ' + result.MessageDetails) + '

'):' ')) + message : ((((('

' + (commonClient.getTranslation('SecurityClientMessage'))) + ': ') + (commonClient.getTranslation(result.Message))) + '

') + (result.MessageDetails ? (('

SecurityClientDetails: ' + result.MessageDetails) + '

') : ' ')) }); return false; default: @@ -372,11 +372,11 @@ public void ToJavascriptTest14() if (s && (e = (ft(r))), i) ; else if (s) - return D(t,e)?(void ($(t,e))):(n = (l(e,t)), G(t,n), void (ht(t))); + return D(t,e) ? (void ($(t,e))) : (n = (l(e,t)), G(t,n), void (ht(t))); var g,o,f; - for(f = (t.length < r.length?t.length:r.length), o = 0, g = 0;f > g;g++) + for(f = (t.length < r.length ? t.length : r.length), o = 0, g = 0; f > g; g++) o += (t[g] + r[g]), t[g] = (o & _t), o >>= at; - for(g = f;o && (g < t.length);g++) + for(g = f; o && (g < t.length); g++) o += t[g], t[g] = (o & _t), o >>= at; }"; expected = Regex.Replace(expected, @"\r\n|\n\r|\n|\r", Environment.NewLine); @@ -520,7 +520,7 @@ class A { var expected = @"class A { *[Symbol.iterator]() { let L = this._first; - for(;L !== _.Undefined;) + for(; L !== _.Undefined; ) yield L.element, L = L.next; } }"; From c038d7ad19d5ec970a0cd50c58e382cf356d4a98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jochen=20K=C3=BChner?= Date: Mon, 4 Jul 2022 21:47:31 +0200 Subject: [PATCH 10/23] fixes after rebase --- src/Esprima/Utils/ToJavascriptConverter.cs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/Esprima/Utils/ToJavascriptConverter.cs b/src/Esprima/Utils/ToJavascriptConverter.cs index 3a06d283..f4f89251 100644 --- a/src/Esprima/Utils/ToJavascriptConverter.cs +++ b/src/Esprima/Utils/ToJavascriptConverter.cs @@ -650,11 +650,6 @@ protected virtual void VisitArrowFunctionExpression(ArrowFunctionExpression arro Append("async "); } - if (arrowFunctionExpression.Id != null) - { - Visit(arrowFunctionExpression.Id); - } - if (arrowFunctionExpression.Params.Count == 1) { if (arrowFunctionExpression.Params[0] is RestElement || ExpressionNeedsBrackets(arrowFunctionExpression.Params[0])) @@ -687,7 +682,7 @@ protected virtual void VisitArrowFunctionExpression(ArrowFunctionExpression arro protected virtual void VisitUnaryExpression(UnaryExpression unaryExpression) { - var op = UnaryExpression.ConvertUnaryOperator(unaryExpression.Operator); + var op = UnaryExpression.GetUnaryOperatorToken(unaryExpression.Operator); if (unaryExpression.Prefix) { Append(op); @@ -713,12 +708,12 @@ protected virtual void VisitUpdateExpression(UpdateExpression updateExpression) { if (updateExpression.Prefix) { - Append(UnaryExpression.ConvertUnaryOperator(updateExpression.Operator)); + Append(UnaryExpression.GetUnaryOperatorToken(updateExpression.Operator)); } Visit(updateExpression.Argument); if (!updateExpression.Prefix) { - Append(UnaryExpression.ConvertUnaryOperator(updateExpression.Operator)); + Append(UnaryExpression.GetUnaryOperatorToken(updateExpression.Operator)); } } @@ -810,7 +805,7 @@ protected virtual void VisitLiteral(Literal literal) protected virtual void VisitIdentifier(Identifier identifier) { - Append(identifier.Name); + Append(identifier.Name!); } protected virtual void VisitFunctionExpression(IFunction function) @@ -1346,7 +1341,7 @@ protected virtual void VisitBinaryExpression(BinaryExpression binaryExpression) { Append(")"); } - var op = BinaryExpression.ConvertBinaryOperator(binaryExpression.Operator); + var op = BinaryExpression.GetBinaryOperatorToken(binaryExpression.Operator); if (char.IsLetter(op[0])) { Append(" "); @@ -1388,7 +1383,7 @@ protected virtual void VisitAssignmentExpression(AssignmentExpression assignment { Append("("); } - var op = AssignmentExpression.ConvertAssignmentOperator(assignmentExpression.Operator); + var op = AssignmentExpression.GetAssignmentOperatorToken(assignmentExpression.Operator); Visit(assignmentExpression.Left); AppendBeautificationSpace(); Append(op); @@ -1444,7 +1439,7 @@ protected virtual void VisitBlockStatement(BlockStatement blockStatement) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void VisitNodeList(IEnumerable nodeList, string appendAtEnd = null, string appendSeperatorString = null, bool appendBracketsIfNeeded = false, bool addLineBreaks = false) + private void VisitNodeList(IEnumerable nodeList, string? appendAtEnd = null, string? appendSeperatorString = null, bool appendBracketsIfNeeded = false, bool addLineBreaks = false) where TNode : Node { var notfirst = false; From b0f43877da3b1b5b372f2331a052cd08bbdd4cd1 Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Wed, 6 Jul 2022 21:10:21 +0200 Subject: [PATCH 11/23] Rename ToJavascriptConverter to AstToJavascriptConverter --- ...nverter.cs => AstToJavascriptConverter.cs} | 8 +-- ...ascriptTest.cs => AstToJavascriptTests.cs} | 54 +++++++++---------- 2 files changed, 31 insertions(+), 31 deletions(-) rename src/Esprima/Utils/{ToJavascriptConverter.cs => AstToJavascriptConverter.cs} (96%) rename test/Esprima.Tests/{JavascriptTest.cs => AstToJavascriptTests.cs} (88%) diff --git a/src/Esprima/Utils/ToJavascriptConverter.cs b/src/Esprima/Utils/AstToJavascriptConverter.cs similarity index 96% rename from src/Esprima/Utils/ToJavascriptConverter.cs rename to src/Esprima/Utils/AstToJavascriptConverter.cs index f4f89251..2fe6060d 100644 --- a/src/Esprima/Utils/ToJavascriptConverter.cs +++ b/src/Esprima/Utils/AstToJavascriptConverter.cs @@ -7,19 +7,19 @@ namespace Esprima.Utils { - public static class ToJavascriptConverterExtension + public static class AstToJavascriptConverterExtension { public static string ToJavascript(this Node node, bool beautify = false) { - return ToJavascriptConverter.ToJavascript(node, beautify); + return AstToJavascriptConverter.ToJavascript(node, beautify); } } - public class ToJavascriptConverter + public class AstToJavascriptConverter { public static string ToJavascript(Node node, bool beautify = false) { - var visitor = new ToJavascriptConverter() { Beautify = beautify }; + var visitor = new AstToJavascriptConverter() { Beautify = beautify }; visitor.Visit(node); return visitor.ToString(); } diff --git a/test/Esprima.Tests/JavascriptTest.cs b/test/Esprima.Tests/AstToJavascriptTests.cs similarity index 88% rename from test/Esprima.Tests/JavascriptTest.cs rename to test/Esprima.Tests/AstToJavascriptTests.cs index fbf64f9b..55d91768 100644 --- a/test/Esprima.Tests/JavascriptTest.cs +++ b/test/Esprima.Tests/AstToJavascriptTests.cs @@ -5,7 +5,7 @@ namespace Esprima.Tests { - public class JavascriptTest + public class AstToJavascriptTests { [Fact] public void ToJavascriptTest1() @@ -25,7 +25,7 @@ public void ToJavascriptTest1() for (var elem of list) { } "); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = AstToJavascriptConverter.ToJavascript(program); Assert.Equal("if(true){p();}switch(foo){case 'A':p();break;}switch(foo){default:p();break;}for(var a=[];;){}for(var elem of list){}", code); } @@ -47,7 +47,7 @@ function printTips() tips.forEach((tip, i) => console.log(`Tip ${ i}:` +tip)); }"); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = AstToJavascriptConverter.ToJavascript(program); Assert.Equal("let tips=[\"Click on any AST node with a '+' to expand it\",\"Hovering over a node highlights the \\\r\n corresponding location in the source code\",\"Shift click on an AST node to expand the whole subtree\"];function printTips(){tips.forEach((tip,i)=>console.log((`Tip ${i}:`+tip)));}", code); } @@ -65,7 +65,7 @@ static get is() { } }"); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = AstToJavascriptConverter.ToJavascript(program); Assert.Equal("export class aa extends HTMLElement{constructor(a,b){super(a);this._div=(document.createElement('div'));}static get is(){return 'aa';}}", code); } @@ -129,7 +129,7 @@ export function checkSecurityAnswerCodeDirect(result) { source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program, true); + var code = AstToJavascriptConverter.ToJavascript(program, true); var expected = @"import { MccDialog } from '../mccDialogHandler'; import { commonClient, bb as f } from '../commonClient/commonClient'; @@ -210,7 +210,7 @@ public void ToJavascriptTest5() source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program, true); + var code = AstToJavascriptConverter.ToJavascript(program, true); var expected = @"(function() { 'use strict'; @@ -241,7 +241,7 @@ public void ToJavascriptTest6() source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = AstToJavascriptConverter.ToJavascript(program); Assert.Equal("function _createClass(Constructor,protoProps,staticProps){if(protoProps)_defineProperties(Constructor.prototype,protoProps);if(staticProps)_defineProperties(Constructor,staticProps);return Constructor;}", code); } @@ -252,7 +252,7 @@ public void ToJavascriptTest7() { }"); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = AstToJavascriptConverter.ToJavascript(program); Assert.Equal("if(((x?((a.nodeName.toLowerCase())===f):(1===a.nodeType))&&(++d))&&(p&&((i=((o=(a[S]||(a[S]={})))[a.uniqueID]||(o[a.uniqueID]={})))[h]=[k,d]),a===e)){}", code); } @@ -271,7 +271,7 @@ class a extends b { } "); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = AstToJavascriptConverter.ToJavascript(program); Assert.Equal("class a extends b{constructor(){super();this.g=1;}q=1;r='cc';}", code); } @@ -282,7 +282,7 @@ public void ToJavascriptTest9() d = (s = (r = (i = (o = (a = c)[S] || (a[S] = {}))[a.uniqueID] || (o[a.uniqueID] = {}))[h] || [])[0] === k && r[1]) && r[2], a = s && c.childNodes[s]; "); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = AstToJavascriptConverter.ToJavascript(program); Assert.Equal("d=((s=(((r=((i=((o=((a=c)[S]||(a[S]={})))[a.uniqueID]||(o[a.uniqueID]={})))[h]||[]))[0]===k)&&r[1]))&&r[2]),a=(s&&c.childNodes[s]);", code); } @@ -293,7 +293,7 @@ public void ToJavascriptTest10() m = (z.document, !!v.documentElement && !!v.head && 'function' == typeof v.addEventListener && v.createElement, ~a.indexOf('MSIE') || a.indexOf('Trident/'), '___FONT_AWESOME___') "); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = AstToJavascriptConverter.ToJavascript(program); Assert.Equal("m=(z.document,(((!!(v.documentElement))&&(!!(v.head)))&&('function'==(typeof (v.addEventListener))))&&v.createElement,(~(a.indexOf('MSIE')))||(a.indexOf('Trident/')),'___FONT_AWESOME___');", code); } @@ -315,7 +315,7 @@ public void ToJavascriptTest11() }(); "); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = AstToJavascriptConverter.ToJavascript(program); Assert.Equal("var h=(c.navigator||{}).userAgent,a=((void 0)===h?'':h),z=c,v=l,m=(z.document,(((!!(v.documentElement))&&(!!(v.head)))&&('function'==(typeof (v.addEventListener))))&&v.createElement,(~(a.indexOf('MSIE')))||(a.indexOf('Trident/')),'___FONT_AWESOME___'),e=((function(){try {return !0;} catch(c){return !1;}})());", code); } @@ -328,7 +328,7 @@ public void ToJavascriptTest12() } "); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = AstToJavascriptConverter.ToJavascript(program); Assert.Equal("var a={children:(b=O,'g'===b.tag?b.children:[b])};", code); } @@ -345,7 +345,7 @@ public void ToJavascriptTest13() } else h = e.HttpRequest.responseText; "); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = AstToJavascriptConverter.ToJavascript(program); Assert.Equal("if(e.IsWebService)if(h=e.HttpRequest.responseXML,'undefined'==(typeof (h)))Trace.Write((('Error: '+e.UniqueId)+' data has no properties!')),m=(!0); else try {h.setProperty('SelectionLanguage','XPath');} catch(l){Trace.Write('Error: data.setProperty(',SelectionLanguage,', ',XPath,') because '+l.message);} else h=e.HttpRequest.responseText;", code); } @@ -365,7 +365,7 @@ public void ToJavascriptTest14() source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program, true); + var code = AstToJavascriptConverter.ToJavascript(program, true); var expected = @"function tt(t,r) { var n,e,i = (b(t)),s = (b(r)); @@ -390,7 +390,7 @@ public void ToJavascriptTest15() h='M'+(+new Date).toString(36) "); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = AstToJavascriptConverter.ToJavascript(program); Assert.Equal("h=('M'+((+(new Date)).toString(36)));", code); } @@ -405,7 +405,7 @@ public void ToJavascriptTest16() }; "); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = AstToJavascriptConverter.ToJavascript(program); Assert.Equal("input.onchange=(async e=>{const files=await readFiles(input.files,readMode);document.body.removeChild(input);resolve(files);});", code); } @@ -416,7 +416,7 @@ public void ToJavascriptTest17() export const Base = LegacyElementMixin(HTMLElement).prototype; "); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = AstToJavascriptConverter.ToJavascript(program); Assert.Equal("export const Base=(LegacyElementMixin(HTMLElement)).prototype;", code); } @@ -427,7 +427,7 @@ public void ToJavascriptTest18() let {is} = getIsExtends(element); "); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = AstToJavascriptConverter.ToJavascript(program); Assert.Equal("let {is}=(getIsExtends(element));", code); } @@ -439,7 +439,7 @@ public void ToJavascriptTest19() (window['ShadyDOM'] && window['ShadyDOM']['wrap']) || (node => node); "); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = AstToJavascriptConverter.ToJavascript(program); Assert.Equal("export const wrap=((window['ShadyDOM']&&window['ShadyDOM']['wrap'])||(node=>node));", code); } @@ -449,7 +449,7 @@ public void ToJavascriptTest20() var parser = new JavaScriptParser(@" export {}"); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = AstToJavascriptConverter.ToJavascript(program); Assert.Equal("export{};", code); } @@ -462,7 +462,7 @@ public void ToJavascriptTest21() })(); "); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = AstToJavascriptConverter.ToJavascript(program); Assert.Equal("(()=>{mutablePropertyChange=MutableData._mutablePropertyChange;})();", code); } @@ -476,7 +476,7 @@ public void ToJavascriptTest22() }()) "); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = AstToJavascriptConverter.ToJavascript(program); Assert.Equal("var Ol,jl=(new((function(){var l,h,z;return l=c;})()));", code); } @@ -493,7 +493,7 @@ public void ToJavascriptTest23() "); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program); + var code = AstToJavascriptConverter.ToJavascript(program); Assert.Equal("[y,{[Symbol.iterator]:(function(){return b;}),a:5}];", code); } @@ -515,7 +515,7 @@ class A { source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program, true); + var code = AstToJavascriptConverter.ToJavascript(program, true); var expected = @"class A { *[Symbol.iterator]() { @@ -545,7 +545,7 @@ public void ToJavascriptTest25() source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program, true); + var code = AstToJavascriptConverter.ToJavascript(program, true); var expected = @"var i = ((function e(i) { var r = n[i]; @@ -578,7 +578,7 @@ public void ToJavascriptTest26() source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = ToJavascriptConverter.ToJavascript(program, true); + var code = AstToJavascriptConverter.ToJavascript(program, true); Assert.Equal(source, code); } } From b749cb825f52b671ccd3692aa8738f04322bab33 Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Wed, 6 Jul 2022 22:06:36 +0200 Subject: [PATCH 12/23] revise design of AstJson/AstToJsonConverter and align AstToJavascriptConverter with it [BC] --- src/Esprima/Utils/AstJson.cs | 1268 +------- src/Esprima/Utils/AstToJavascript.cs | 51 + src/Esprima/Utils/AstToJavascriptConverter.cs | 2553 +++++++++-------- src/Esprima/Utils/AstToJsonConverter.cs | 1141 ++++++++ src/Esprima/Utils/Jsx/JsxAstJson.cs | 172 -- .../Utils/Jsx/JsxAstToJsonConverter.cs | 160 ++ test/Esprima.Tests/AstToJavascriptTests.cs | 52 +- test/Esprima.Tests/Fixtures.cs | 44 +- 8 files changed, 2702 insertions(+), 2739 deletions(-) create mode 100644 src/Esprima/Utils/AstToJavascript.cs create mode 100644 src/Esprima/Utils/AstToJsonConverter.cs delete mode 100644 src/Esprima/Utils/Jsx/JsxAstJson.cs create mode 100644 src/Esprima/Utils/Jsx/JsxAstToJsonConverter.cs diff --git a/src/Esprima/Utils/AstJson.cs b/src/Esprima/Utils/AstJson.cs index c9ad5562..35ca9201 100644 --- a/src/Esprima/Utils/AstJson.cs +++ b/src/Esprima/Utils/AstJson.cs @@ -1,11 +1,4 @@ -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; +using Esprima.Ast; namespace Esprima.Utils; @@ -17,1279 +10,68 @@ public enum LocationMembersPlacement public static class AstJson { - public sealed class Options + public record 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; } + 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 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 }; - } + internal bool TestCompatibilityMode { get; init; } } - public interface IConverter + public static string ToJsonString(this Node node, AstToJsonConverter.Factory? converterFactory = null) { - void WriteJson(Node node, JsonWriter writer, Options options); + return ToJsonString(node, indent: null, converterFactory); } - public static string ToJsonString(this Node node, IConverter? converter = null) + public static string ToJsonString(this Node node, string? indent, AstToJsonConverter.Factory? converterFactory = null) { - return ToJsonString(node, indent: null, converter); + return ToJsonString(node, Options.Default, indent, converterFactory); } - public static string ToJsonString(this Node node, string? indent, IConverter? converter = null) + public static string ToJsonString(this Node node, Options options, AstToJsonConverter.Factory? converterFactory = null) { - return ToJsonString(node, Options.Default, indent, converter); + return ToJsonString(node, options, indent: null, converterFactory); } - 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) + public static string ToJsonString(this Node node, Options options, string? indent, AstToJsonConverter.Factory? converterFactory = null) { using (var writer = new StringWriter()) { - WriteJson(node, writer, options, indent, converter); + WriteJson(node, writer, options, indent, converterFactory); return writer.ToString(); } } - public static void WriteJson(this Node node, TextWriter writer, IConverter? converter = null) + public static void WriteJson(this Node node, TextWriter writer, AstToJsonConverter.Factory? converterFactory = null) { - WriteJson(node, writer, indent: null, converter); + WriteJson(node, writer, indent: null, converterFactory); } - public static void WriteJson(this Node node, TextWriter writer, string? indent, IConverter? converter = null) + public static void WriteJson(this Node node, TextWriter writer, string? indent, AstToJsonConverter.Factory? converterFactory = null) { - WriteJson(node, writer, Options.Default, indent, converter); + WriteJson(node, writer, Options.Default, indent, converterFactory); } - public static void WriteJson(this Node node, TextWriter writer, Options options, IConverter? converter = null) + public static void WriteJson(this Node node, TextWriter writer, Options options, AstToJsonConverter.Factory? converterFactory = null) { - WriteJson(node, writer, options, null, converter); + WriteJson(node, writer, options, indent: null, converterFactory); } - public static void WriteJson(this Node node, TextWriter writer, Options options, string? indent, IConverter? converter = null) + public static void WriteJson(this Node node, TextWriter writer, Options options, string? indent, AstToJsonConverter.Factory? converterFactory = 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); + WriteJson(node, new JsonTextWriter(writer, indent), options, converterFactory); } - public void WriteJson(Node node, JsonWriter writer, AstJson.Options options) + public static void WriteJson(this Node node, JsonWriter writer, Options options, AstToJsonConverter.Factory? converterFactory = null) { - 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); - } + converterFactory ??= (writer, options) => new AstToJsonConverter(writer, options); - 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) - { - } + converterFactory(writer, options).Convert(node); } } diff --git a/src/Esprima/Utils/AstToJavascript.cs b/src/Esprima/Utils/AstToJavascript.cs new file mode 100644 index 00000000..8628f322 --- /dev/null +++ b/src/Esprima/Utils/AstToJavascript.cs @@ -0,0 +1,51 @@ +using Esprima.Ast; + +namespace Esprima.Utils; + +public static class AstToJavascript +{ + public record class Options + { + public static readonly Options Default = new(); + internal static readonly Options DefaultWithBeautify = Default with { Beautify = true }; + + public bool Beautify { get; init; } + public string? Indent { get; init; } + } + + public static string ToJavascriptString(this Node node, AstToJavascriptConverter.Factory? converterFactory = null) + { + return ToJavascriptString(node, Options.Default, converterFactory); + } + + public static string ToJavascriptString(this Node node, bool beautify, AstToJavascriptConverter.Factory? converterFactory = null) + { + return ToJavascriptString(node, beautify ? Options.DefaultWithBeautify : Options.Default, converterFactory); + } + + public static string ToJavascriptString(this Node node, Options options, AstToJavascriptConverter.Factory? converterFactory = null) + { + using (var writer = new StringWriter()) + { + WriteJavascript(node, writer, options, converterFactory); + return writer.ToString(); + } + } + + public static void WriteJavascript(this Node node, TextWriter writer, AstToJavascriptConverter.Factory? converterFactory = null) + { + WriteJavascript(node, writer, Options.Default, converterFactory); + } + + public static void WriteJavascript(this Node node, TextWriter writer, bool beautify, AstToJavascriptConverter.Factory? converterFactory = null) + { + WriteJavascript(node, writer, beautify ? Options.DefaultWithBeautify : Options.Default, converterFactory); + } + + public static void WriteJavascript(this Node node, TextWriter writer, Options options, AstToJavascriptConverter.Factory? converterFactory = null) + { + converterFactory ??= (writer, options) => new AstToJavascriptConverter(writer, options); + + converterFactory(writer, options).Convert(node); + } +} diff --git a/src/Esprima/Utils/AstToJavascriptConverter.cs b/src/Esprima/Utils/AstToJavascriptConverter.cs index 2fe6060d..bf840642 100644 --- a/src/Esprima/Utils/AstToJavascriptConverter.cs +++ b/src/Esprima/Utils/AstToJavascriptConverter.cs @@ -1,1580 +1,1583 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; +using System.Runtime.CompilerServices; using Esprima.Ast; +using static Esprima.EsprimaExceptionHelper; -namespace Esprima.Utils +namespace Esprima.Utils; + +public class AstToJavascriptConverter { - public static class AstToJavascriptConverterExtension + public delegate AstToJavascriptConverter Factory(TextWriter writer, AstToJavascript.Options options); + + private readonly TextWriter _writer; + + public readonly bool _beautify; + public readonly string _indent; + + private int _indentionLevel = 0; + + private readonly List _parentStack = new List(); + protected IReadOnlyList ParentStack => _parentStack; + + public AstToJavascriptConverter(TextWriter writer, AstToJavascript.Options options) { - public static string ToJavascript(this Node node, bool beautify = false) + _writer = writer ?? ThrowArgumentNullException(nameof(writer)); + + if (options is null) { - return AstToJavascriptConverter.ToJavascript(node, beautify); + ThrowArgumentNullException(nameof(options)); + throw null!; } + + _beautify = options.Beautify; + _indent = options.Indent ?? " "; } - public class AstToJavascriptConverter + protected void Append(string text) { - public static string ToJavascript(Node node, bool beautify = false) + _writer.Write(text); + } + + protected void AppendBeautificationSpace() + { + if (_beautify) { - var visitor = new AstToJavascriptConverter() { Beautify = beautify }; - visitor.Visit(node); - return visitor.ToString(); + _writer.Write(" "); } + } - public bool Beautify { get; set; } - - public int IndentionSize { get; set; } = 4; + protected void AppendBeautificationIndent() + { + if (_beautify) + { + for (var n = _indentionLevel; n > 0; n--) + { + _writer.Write(_indent); + } + } + } - public string NewlineFormat { get; set; } = Environment.NewLine; + protected void AppendBeautificationNewline() + { + if (_beautify) + { + _writer.WriteLine(); + } + } - protected StringBuilder _sb = new StringBuilder(); - private int _indentionLevel = 0; + protected void IncreaseIndent() + { + _indentionLevel++; + } - private readonly List _parentStack = new List(); - protected IReadOnlyList ParentStack => _parentStack; + protected void DecreaseIndent() + { + _indentionLevel--; + } - private void Append(string text) + /// + /// Returns parent node at specified position. + /// + /// Zero index value returns current node; one corresponds to direct + /// parent of current node. + protected Node? TryGetParentAt(int offset) + { + if (_parentStack.Count < offset + 1) { - _sb.Append(text); + return null; } - private void AppendBeautificationSpace() + return _parentStack[_parentStack.Count - 1 - offset]; + } + + public void Convert(Node node) + { + Visit(node ?? ThrowArgumentNullException(nameof(node))); + } + + public virtual void Visit(Node node) + { + _parentStack.Add(node); + + switch (node.Type) + { + case Nodes.AssignmentExpression: + VisitAssignmentExpression(node.As()); + break; + case Nodes.ArrayExpression: + VisitArrayExpression(node.As()); + break; + case Nodes.BlockStatement: + VisitBlockStatement(node.As()); + break; + case Nodes.BinaryExpression: + VisitBinaryExpression(node.As()); + break; + case Nodes.BreakStatement: + VisitBreakStatement(node.As()); + break; + case Nodes.CallExpression: + VisitCallExpression(node.As()); + break; + case Nodes.CatchClause: + VisitCatchClause(node.As()); + break; + case Nodes.ConditionalExpression: + VisitConditionalExpression(node.As()); + break; + case Nodes.ContinueStatement: + VisitContinueStatement(node.As()); + break; + case Nodes.DoWhileStatement: + VisitDoWhileStatement(node.As()); + break; + case Nodes.DebuggerStatement: + VisitDebuggerStatement(node.As()); + break; + case Nodes.EmptyStatement: + VisitEmptyStatement(node.As()); + break; + case Nodes.ExpressionStatement: + VisitExpressionStatement(node.As()); + break; + case Nodes.ForStatement: + VisitForStatement(node.As()); + break; + case Nodes.ForInStatement: + VisitForInStatement(node.As()); + break; + case Nodes.FunctionDeclaration: + VisitFunctionDeclaration(node.As()); + break; + case Nodes.FunctionExpression: + VisitFunctionExpression(node.As()); + break; + case Nodes.Identifier: + VisitIdentifier(node.As()); + break; + case Nodes.IfStatement: + VisitIfStatement(node.As()); + break; + case Nodes.Import: + VisitImport(node.As()); + break; + case Nodes.Literal: + VisitLiteral(node.As()); + break; + case Nodes.LabeledStatement: + VisitLabeledStatement(node.As()); + break; + case Nodes.LogicalExpression: + VisitBinaryExpression(node.As()); + break; + case Nodes.MemberExpression: + VisitMemberExpression(node.As()); + break; + case Nodes.NewExpression: + VisitNewExpression(node.As()); + break; + case Nodes.ObjectExpression: + VisitObjectExpression(node.As()); + break; + case Nodes.Program: + VisitProgram(node.As()); + break; + case Nodes.Property: + VisitProperty(node.As()); + break; + case Nodes.PropertyDefinition: + VisitPropertyDefinition(node.As()); + break; + case Nodes.RestElement: + VisitRestElement(node.As()); + break; + case Nodes.ReturnStatement: + VisitReturnStatement(node.As()); + break; + case Nodes.SequenceExpression: + VisitSequenceExpression(node.As()); + break; + case Nodes.SwitchStatement: + VisitSwitchStatement(node.As()); + break; + case Nodes.SwitchCase: + VisitSwitchCase(node.As()); + break; + case Nodes.TemplateElement: + VisitTemplateElement(node.As()); + break; + case Nodes.TemplateLiteral: + VisitTemplateLiteral(node.As()); + break; + case Nodes.ThisExpression: + VisitThisExpression(node.As()); + break; + case Nodes.ThrowStatement: + VisitThrowStatement(node.As()); + break; + case Nodes.TryStatement: + VisitTryStatement(node.As()); + break; + case Nodes.UnaryExpression: + VisitUnaryExpression(node.As()); + break; + case Nodes.UpdateExpression: + VisitUpdateExpression(node.As()); + break; + case Nodes.VariableDeclaration: + VisitVariableDeclaration(node.As()); + break; + case Nodes.VariableDeclarator: + VisitVariableDeclarator(node.As()); + break; + case Nodes.WhileStatement: + VisitWhileStatement(node.As()); + break; + case Nodes.WithStatement: + VisitWithStatement(node.As()); + break; + case Nodes.ArrayPattern: + VisitArrayPattern(node.As()); + break; + case Nodes.AssignmentPattern: + VisitAssignmentPattern(node.As()); + break; + case Nodes.SpreadElement: + VisitSpreadElement(node.As()); + break; + case Nodes.ObjectPattern: + VisitObjectPattern(node.As()); + break; + case Nodes.ArrowParameterPlaceHolder: + VisitArrowParameterPlaceHolder(node.As()); + break; + case Nodes.MetaProperty: + VisitMetaProperty(node.As()); + break; + case Nodes.Super: + VisitSuper(node.As()); + break; + case Nodes.TaggedTemplateExpression: + VisitTaggedTemplateExpression(node.As()); + break; + case Nodes.YieldExpression: + VisitYieldExpression(node.As()); + break; + case Nodes.ArrowFunctionExpression: + VisitArrowFunctionExpression(node.As()); + break; + case Nodes.AwaitExpression: + VisitAwaitExpression(node.As()); + break; + case Nodes.ClassBody: + VisitClassBody(node.As()); + break; + case Nodes.ClassDeclaration: + VisitClassDeclaration(node.As()); + break; + case Nodes.ForOfStatement: + VisitForOfStatement(node.As()); + break; + case Nodes.MethodDefinition: + VisitMethodDefinition(node.As()); + break; + case Nodes.ImportSpecifier: + VisitImportSpecifier(node.As()); + break; + case Nodes.ImportDefaultSpecifier: + VisitImportDefaultSpecifier(node.As()); + break; + case Nodes.ImportNamespaceSpecifier: + VisitImportNamespaceSpecifier(node.As()); + break; + case Nodes.ImportDeclaration: + VisitImportDeclaration(node.As()); + break; + case Nodes.ExportSpecifier: + VisitExportSpecifier(node.As()); + break; + case Nodes.ExportNamedDeclaration: + VisitExportNamedDeclaration(node.As()); + break; + case Nodes.ExportAllDeclaration: + VisitExportAllDeclaration(node.As()); + break; + case Nodes.ExportDefaultDeclaration: + VisitExportDefaultDeclaration(node.As()); + break; + case Nodes.ClassExpression: + VisitClassExpression(node.As()); + break; + case Nodes.ChainExpression: + VisitChainExpression(node.As()); + break; + default: + VisitUnknownNode(node); + break; + } + _parentStack.RemoveAt(_parentStack.Count - 1); + } + + protected virtual void VisitProgram(Program program) + { + VisitNodeList(program.Body, appendAtEnd: ";", addLineBreaks: true); + } + + protected virtual void VisitUnknownNode(Node node) + { + throw new NotImplementedException($"AST visitor doesn't support nodes of type {node.Type}, you can override VisitUnknownNode to handle this case."); + } + + protected virtual void VisitChainExpression(ChainExpression chainExpression) + { + Visit(chainExpression.Expression); + } + + protected virtual void VisitCatchClause(CatchClause catchClause) + { + Append("("); + if (catchClause.Param is not null) { - if (Beautify) - { - _sb.Append(" "); - } + Visit(catchClause.Param); } + Append(")"); + Visit(catchClause.Body); + } - private void AppendBeautificationIndent() + protected virtual void VisitFunctionDeclaration(FunctionDeclaration functionDeclaration) + { + if (functionDeclaration.Async) { - if (Beautify) - { - _sb.Append("".PadLeft(_indentionLevel * IndentionSize, ' ')); - } + Append("async "); } - - private void AppendBeautificationNewline() + Append("function"); + if (functionDeclaration.Generator) { - if (Beautify) - { - _sb.Append(NewlineFormat); - } + Append("*"); } - - private void IncreaseIndent() + if (functionDeclaration.Id is not null) { - _indentionLevel++; + Append(" "); + Visit(functionDeclaration.Id); } + Append("("); + VisitNodeList(functionDeclaration.Params, appendSeperatorString: ","); + Append(")"); + AppendBeautificationSpace(); + Visit(functionDeclaration.Body); + } + + protected virtual void VisitWithStatement(WithStatement withStatement) + { + Append("with("); + Visit(withStatement.Object); + Append(")"); + Visit(withStatement.Body); + } + + protected virtual void VisitWhileStatement(WhileStatement whileStatement) + { + Append("while("); + Visit(whileStatement.Test); + Append(")"); + Visit(whileStatement.Body); + } + + protected virtual void VisitVariableDeclaration(VariableDeclaration variableDeclaration) + { + Append(variableDeclaration.Kind.ToString().ToLower() + " "); + VisitNodeList(variableDeclaration.Declarations, appendSeperatorString: ","); + } - private void DecreaseIndent() + protected virtual void VisitTryStatement(TryStatement tryStatement) + { + Append("try "); + Visit(tryStatement.Block); + if (tryStatement.Handler is not null) { - _indentionLevel--; + Append(" catch"); + Visit(tryStatement.Handler); } - - /// - /// Returns parent node at specified position. - /// - /// Zero index value returns current node; one corresponds to direct - /// parent of current node. - protected Node? TryGetParentAt(int offset) + if (tryStatement.Finalizer is not null) { - if (_parentStack.Count < offset + 1) - { - return null; - } - - return _parentStack[_parentStack.Count - 1 - offset]; + Append(" finally"); + Visit(tryStatement.Finalizer); } + } - public virtual void Visit(Node node) - { - _parentStack.Add(node); + protected virtual void VisitThrowStatement(ThrowStatement throwStatement) + { + Append("throw "); + Visit(throwStatement.Argument); + Append(";"); + } - switch (node.Type) - { - case Nodes.AssignmentExpression: - VisitAssignmentExpression(node.As()); - break; - case Nodes.ArrayExpression: - VisitArrayExpression(node.As()); - break; - case Nodes.BlockStatement: - VisitBlockStatement(node.As()); - break; - case Nodes.BinaryExpression: - VisitBinaryExpression(node.As()); - break; - case Nodes.BreakStatement: - VisitBreakStatement(node.As()); - break; - case Nodes.CallExpression: - VisitCallExpression(node.As()); - break; - case Nodes.CatchClause: - VisitCatchClause(node.As()); - break; - case Nodes.ConditionalExpression: - VisitConditionalExpression(node.As()); - break; - case Nodes.ContinueStatement: - VisitContinueStatement(node.As()); - break; - case Nodes.DoWhileStatement: - VisitDoWhileStatement(node.As()); - break; - case Nodes.DebuggerStatement: - VisitDebuggerStatement(node.As()); - break; - case Nodes.EmptyStatement: - VisitEmptyStatement(node.As()); - break; - case Nodes.ExpressionStatement: - VisitExpressionStatement(node.As()); - break; - case Nodes.ForStatement: - VisitForStatement(node.As()); - break; - case Nodes.ForInStatement: - VisitForInStatement(node.As()); - break; - case Nodes.FunctionDeclaration: - VisitFunctionDeclaration(node.As()); - break; - case Nodes.FunctionExpression: - VisitFunctionExpression(node.As()); - break; - case Nodes.Identifier: - VisitIdentifier(node.As()); - break; - case Nodes.IfStatement: - VisitIfStatement(node.As()); - break; - case Nodes.Import: - VisitImport(node.As()); - break; - case Nodes.Literal: - VisitLiteral(node.As()); - break; - case Nodes.LabeledStatement: - VisitLabeledStatement(node.As()); - break; - case Nodes.LogicalExpression: - VisitBinaryExpression(node.As()); - break; - case Nodes.MemberExpression: - VisitMemberExpression(node.As()); - break; - case Nodes.NewExpression: - VisitNewExpression(node.As()); - break; - case Nodes.ObjectExpression: - VisitObjectExpression(node.As()); - break; - case Nodes.Program: - VisitProgram(node.As()); - break; - case Nodes.Property: - VisitProperty(node.As()); - break; - case Nodes.PropertyDefinition: - VisitPropertyDefinition(node.As()); - break; - case Nodes.RestElement: - VisitRestElement(node.As()); - break; - case Nodes.ReturnStatement: - VisitReturnStatement(node.As()); - break; - case Nodes.SequenceExpression: - VisitSequenceExpression(node.As()); - break; - case Nodes.SwitchStatement: - VisitSwitchStatement(node.As()); - break; - case Nodes.SwitchCase: - VisitSwitchCase(node.As()); - break; - case Nodes.TemplateElement: - VisitTemplateElement(node.As()); - break; - case Nodes.TemplateLiteral: - VisitTemplateLiteral(node.As()); - break; - case Nodes.ThisExpression: - VisitThisExpression(node.As()); - break; - case Nodes.ThrowStatement: - VisitThrowStatement(node.As()); - break; - case Nodes.TryStatement: - VisitTryStatement(node.As()); - break; - case Nodes.UnaryExpression: - VisitUnaryExpression(node.As()); - break; - case Nodes.UpdateExpression: - VisitUpdateExpression(node.As()); - break; - case Nodes.VariableDeclaration: - VisitVariableDeclaration(node.As()); - break; - case Nodes.VariableDeclarator: - VisitVariableDeclarator(node.As()); - break; - case Nodes.WhileStatement: - VisitWhileStatement(node.As()); - break; - case Nodes.WithStatement: - VisitWithStatement(node.As()); - break; - case Nodes.ArrayPattern: - VisitArrayPattern(node.As()); - break; - case Nodes.AssignmentPattern: - VisitAssignmentPattern(node.As()); - break; - case Nodes.SpreadElement: - VisitSpreadElement(node.As()); - break; - case Nodes.ObjectPattern: - VisitObjectPattern(node.As()); - break; - case Nodes.ArrowParameterPlaceHolder: - VisitArrowParameterPlaceHolder(node.As()); - break; - case Nodes.MetaProperty: - VisitMetaProperty(node.As()); - break; - case Nodes.Super: - VisitSuper(node.As()); - break; - case Nodes.TaggedTemplateExpression: - VisitTaggedTemplateExpression(node.As()); - break; - case Nodes.YieldExpression: - VisitYieldExpression(node.As()); - break; - case Nodes.ArrowFunctionExpression: - VisitArrowFunctionExpression(node.As()); - break; - case Nodes.AwaitExpression: - VisitAwaitExpression(node.As()); - break; - case Nodes.ClassBody: - VisitClassBody(node.As()); - break; - case Nodes.ClassDeclaration: - VisitClassDeclaration(node.As()); - break; - case Nodes.ForOfStatement: - VisitForOfStatement(node.As()); - break; - case Nodes.MethodDefinition: - VisitMethodDefinition(node.As()); - break; - case Nodes.ImportSpecifier: - VisitImportSpecifier(node.As()); - break; - case Nodes.ImportDefaultSpecifier: - VisitImportDefaultSpecifier(node.As()); - break; - case Nodes.ImportNamespaceSpecifier: - VisitImportNamespaceSpecifier(node.As()); - break; - case Nodes.ImportDeclaration: - VisitImportDeclaration(node.As()); - break; - case Nodes.ExportSpecifier: - VisitExportSpecifier(node.As()); - break; - case Nodes.ExportNamedDeclaration: - VisitExportNamedDeclaration(node.As()); - break; - case Nodes.ExportAllDeclaration: - VisitExportAllDeclaration(node.As()); - break; - case Nodes.ExportDefaultDeclaration: - VisitExportDefaultDeclaration(node.As()); - break; - case Nodes.ClassExpression: - VisitClassExpression(node.As()); - break; - case Nodes.ChainExpression: - VisitChainExpression(node.As()); - break; - default: - VisitUnknownNode(node); - break; - } - _parentStack.RemoveAt(_parentStack.Count - 1); - } + protected virtual void VisitSwitchStatement(SwitchStatement switchStatement) + { + Append("switch("); + Visit(switchStatement.Discriminant); + Append(")"); + AppendBeautificationSpace(); + Append("{"); + + AppendBeautificationNewline(); + IncreaseIndent(); + AppendBeautificationIndent(); - protected virtual void VisitProgram(Program program) + VisitNodeList(switchStatement.Cases, addLineBreaks: true); + + AppendBeautificationNewline(); + DecreaseIndent(); + AppendBeautificationIndent(); + + Append("}"); + } + + protected virtual void VisitSwitchCase(SwitchCase switchCase) + { + if (switchCase.Test is not null) { - VisitNodeList(program.Body, appendAtEnd: ";", addLineBreaks: true); + Append("case "); + Visit(switchCase.Test); } - - protected virtual void VisitUnknownNode(Node node) + else { - throw new NotImplementedException($"AST visitor doesn't support nodes of type {node.Type}, you can override VisitUnknownNode to handle this case."); + Append("default"); } + Append(":"); + + AppendBeautificationNewline(); + IncreaseIndent(); + AppendBeautificationIndent(); + + VisitNodeList(switchCase.Consequent, appendAtEnd: ";", addLineBreaks: true); - protected virtual void VisitChainExpression(ChainExpression chainExpression) + DecreaseIndent(); + } + + protected virtual void VisitReturnStatement(ReturnStatement returnStatement) + { + Append("return"); + if (returnStatement.Argument is not null) { - Visit(chainExpression.Expression); + Append(" "); + Visit(returnStatement.Argument); } + Append(";"); + } + + protected virtual void VisitLabeledStatement(LabeledStatement labeledStatement) + { + Visit(labeledStatement.Label); + Append(":"); + Visit(labeledStatement.Body); + } + + protected virtual void VisitIfStatement(IfStatement ifStatement) + { + Append("if"); + AppendBeautificationSpace(); + Append("("); + Visit(ifStatement.Test); + Append(")"); + AppendBeautificationSpace(); - protected virtual void VisitCatchClause(CatchClause catchClause) + if (ifStatement.Consequent is not BlockStatement) { - Append("("); - if (catchClause.Param is not null) + AppendBeautificationNewline(); + IncreaseIndent(); + AppendBeautificationIndent(); + } + Visit(ifStatement.Consequent); + if (NodeNeedsSemicolon(ifStatement.Consequent)) + { + Append(";"); + } + if (ifStatement.Consequent is not BlockStatement) + { + DecreaseIndent(); + if (ifStatement.Alternate is not null) { - Visit(catchClause.Param); + AppendBeautificationNewline(); + AppendBeautificationIndent(); } - Append(")"); - Visit(catchClause.Body); } - - protected virtual void VisitFunctionDeclaration(FunctionDeclaration functionDeclaration) + if (ifStatement.Alternate is not null) { - if (functionDeclaration.Async) + Append(" else "); + if (ifStatement.Alternate is not BlockStatement && ifStatement.Alternate is not IfStatement) { - Append("async "); + AppendBeautificationNewline(); + IncreaseIndent(); + AppendBeautificationIndent(); } - Append("function"); - if (functionDeclaration.Generator) + Visit(ifStatement.Alternate); + if (NodeNeedsSemicolon(ifStatement.Alternate)) { - Append("*"); + Append(";"); } - if (functionDeclaration.Id != null) + if (ifStatement.Alternate is not BlockStatement && ifStatement.Alternate is not IfStatement) { - Append(" "); - Visit(functionDeclaration.Id); + DecreaseIndent(); } - Append("("); - VisitNodeList(functionDeclaration.Params, appendSeperatorString: ","); - Append(")"); - AppendBeautificationSpace(); - Visit(functionDeclaration.Body); } + } - protected virtual void VisitWithStatement(WithStatement withStatement) - { - Append("with("); - Visit(withStatement.Object); - Append(")"); - Visit(withStatement.Body); - } + protected virtual void VisitEmptyStatement(EmptyStatement emptyStatement) + { + Append(";"); + } - protected virtual void VisitWhileStatement(WhileStatement whileStatement) + protected virtual void VisitDebuggerStatement(DebuggerStatement debuggerStatement) + { + Append("debugger"); + } + + protected virtual void VisitExpressionStatement(ExpressionStatement expressionStatement) + { + if (expressionStatement.Expression is CallExpression callExpression && !(callExpression.Callee is Identifier)) { - Append("while("); - Visit(whileStatement.Test); + if (ExpressionNeedsBrackets(callExpression.Callee)) + { + Append("("); + } + Visit(callExpression.Callee); + if (ExpressionNeedsBrackets(callExpression.Callee)) + { + Append(")"); + } + Append("("); + VisitNodeList(callExpression.Arguments, appendSeperatorString: ","); Append(")"); - Visit(whileStatement.Body); } - - protected virtual void VisitVariableDeclaration(VariableDeclaration variableDeclaration) + else if (expressionStatement.Expression is ClassExpression) { - Append(variableDeclaration.Kind.ToString().ToLower() + " "); - VisitNodeList(variableDeclaration.Declarations, appendSeperatorString: ","); + Append("("); + Visit(expressionStatement.Expression); + Append(")"); } - - protected virtual void VisitTryStatement(TryStatement tryStatement) + else { - Append("try "); - Visit(tryStatement.Block); - if (tryStatement.Handler != null) + if (expressionStatement.Expression is FunctionExpression) { - Append(" catch"); - Visit(tryStatement.Handler); + Append("("); } - if (tryStatement.Finalizer != null) + Visit(expressionStatement.Expression); + if (expressionStatement.Expression is FunctionExpression) { - Append(" finally"); - Visit(tryStatement.Finalizer); + Append(")"); } } + } - protected virtual void VisitThrowStatement(ThrowStatement throwStatement) + protected virtual void VisitForStatement(ForStatement forStatement) + { + Append("for("); + if (forStatement.Init is not null) { - Append("throw "); - Visit(throwStatement.Argument); - Append(";"); + Visit(forStatement.Init); } - - protected virtual void VisitSwitchStatement(SwitchStatement switchStatement) + Append(";"); + AppendBeautificationSpace(); + if (forStatement.Test is not null) { - Append("switch("); - Visit(switchStatement.Discriminant); - Append(")"); - AppendBeautificationSpace(); - Append("{"); + Visit(forStatement.Test); + } + Append(";"); + AppendBeautificationSpace(); + if (forStatement.Update is not null) + { + Visit(forStatement.Update); + } + Append(")"); + AppendBeautificationSpace(); + if (forStatement.Body is not BlockStatement) + { AppendBeautificationNewline(); IncreaseIndent(); AppendBeautificationIndent(); - - VisitNodeList(switchStatement.Cases, addLineBreaks: true); - - AppendBeautificationNewline(); + } + Visit(forStatement.Body); + if (NodeNeedsSemicolon(forStatement.Body)) + { + Append(";"); + } + if (forStatement.Body is not BlockStatement) + { DecreaseIndent(); - AppendBeautificationIndent(); - - Append("}"); } + } - protected virtual void VisitSwitchCase(SwitchCase switchCase) - { - if (switchCase.Test != null) - { - Append("case "); - Visit(switchCase.Test); - } - else - { - Append("default"); - } - Append(":"); + protected virtual void VisitForInStatement(ForInStatement forInStatement) + { + Append("for("); + Visit(forInStatement.Left); + Append(" in "); + Visit(forInStatement.Right); + Append(")"); + AppendBeautificationSpace(); + if (forInStatement.Body is not BlockStatement) + { AppendBeautificationNewline(); IncreaseIndent(); AppendBeautificationIndent(); - - VisitNodeList(switchCase.Consequent, appendAtEnd: ";", addLineBreaks: true); - - DecreaseIndent(); } - - protected virtual void VisitReturnStatement(ReturnStatement returnStatement) + Visit(forInStatement.Body); + if (NodeNeedsSemicolon(forInStatement.Body)) { - Append("return"); - if (returnStatement.Argument != null) - { - Append(" "); - Visit(returnStatement.Argument); - } Append(";"); } - - protected virtual void VisitLabeledStatement(LabeledStatement labeledStatement) - { - Visit(labeledStatement.Label); - Append(":"); - Visit(labeledStatement.Body); - } - - protected virtual void VisitIfStatement(IfStatement ifStatement) + if (forInStatement.Body is not BlockStatement) { - Append("if"); - AppendBeautificationSpace(); - Append("("); - Visit(ifStatement.Test); - Append(")"); - AppendBeautificationSpace(); - - if (ifStatement.Consequent is not BlockStatement) - { - AppendBeautificationNewline(); - IncreaseIndent(); - AppendBeautificationIndent(); - } - Visit(ifStatement.Consequent); - if (NodeNeedsSemicolon(ifStatement.Consequent)) - { - Append(";"); - } - if (ifStatement.Consequent is not BlockStatement) - { - DecreaseIndent(); - if (ifStatement.Alternate != null) - { - AppendBeautificationNewline(); - AppendBeautificationIndent(); - } - } - if (ifStatement.Alternate != null) - { - Append(" else "); - if (ifStatement.Alternate is not BlockStatement && ifStatement.Alternate is not IfStatement) - { - AppendBeautificationNewline(); - IncreaseIndent(); - AppendBeautificationIndent(); - } - Visit(ifStatement.Alternate); - if (NodeNeedsSemicolon(ifStatement.Alternate)) - { - Append(";"); - } - if (ifStatement.Alternate is not BlockStatement && ifStatement.Alternate is not IfStatement) - { - DecreaseIndent(); - } - } + DecreaseIndent(); } + } - protected virtual void VisitEmptyStatement(EmptyStatement emptyStatement) + protected virtual void VisitDoWhileStatement(DoWhileStatement doWhileStatement) + { + Append("do "); + Visit(doWhileStatement.Body); + if (NodeNeedsSemicolon(doWhileStatement.Body)) { Append(";"); } + Append("while("); + Visit(doWhileStatement.Test); + Append(")"); + } - protected virtual void VisitDebuggerStatement(DebuggerStatement debuggerStatement) + protected virtual void VisitArrowFunctionExpression(ArrowFunctionExpression arrowFunctionExpression) + { + if (arrowFunctionExpression.Async) { - Append("debugger"); + Append("async "); } - protected virtual void VisitExpressionStatement(ExpressionStatement expressionStatement) + if (arrowFunctionExpression.Params.Count == 1) { - if (expressionStatement.Expression is CallExpression callExpression && !(callExpression.Callee is Identifier)) + if (arrowFunctionExpression.Params[0] is RestElement || ExpressionNeedsBrackets(arrowFunctionExpression.Params[0])) { - if (ExpressionNeedsBrackets(callExpression.Callee)) - { - Append("("); - } - Visit(callExpression.Callee); - if (ExpressionNeedsBrackets(callExpression.Callee)) - { - Append(")"); - } Append("("); - VisitNodeList(callExpression.Arguments, appendSeperatorString: ","); - Append(")"); } - else if (expressionStatement.Expression is ClassExpression) + Visit(arrowFunctionExpression.Params[0]); + if (arrowFunctionExpression.Params[0] is RestElement || ExpressionNeedsBrackets(arrowFunctionExpression.Params[0])) { - Append("("); - Visit(expressionStatement.Expression); Append(")"); } - else - { - if (expressionStatement.Expression is FunctionExpression) - { - Append("("); - } - Visit(expressionStatement.Expression); - if (expressionStatement.Expression is FunctionExpression) - { - Append(")"); - } - } } - - protected virtual void VisitForStatement(ForStatement forStatement) + else { - Append("for("); - if (forStatement.Init != null) - { - Visit(forStatement.Init); - } - Append(";"); - AppendBeautificationSpace(); - if (forStatement.Test != null) - { - Visit(forStatement.Test); - } - Append(";"); - AppendBeautificationSpace(); - if (forStatement.Update != null) - { - Visit(forStatement.Update); - } + Append("("); + VisitNodeList(arrowFunctionExpression.Params, appendSeperatorString: ",", appendBracketsIfNeeded: true); ; Append(")"); - AppendBeautificationSpace(); - - if (forStatement.Body is not BlockStatement) - { - AppendBeautificationNewline(); - IncreaseIndent(); - AppendBeautificationIndent(); - } - Visit(forStatement.Body); - if (NodeNeedsSemicolon(forStatement.Body)) - { - Append(";"); - } - if (forStatement.Body is not BlockStatement) - { - DecreaseIndent(); - } } - - protected virtual void VisitForInStatement(ForInStatement forInStatement) + Append("=>"); + if (arrowFunctionExpression.Body is ObjectExpression || arrowFunctionExpression.Body is SequenceExpression) + { + Append("("); + } + Visit(arrowFunctionExpression.Body); + if (arrowFunctionExpression.Body is ObjectExpression || arrowFunctionExpression.Body is SequenceExpression) { - Append("for("); - Visit(forInStatement.Left); - Append(" in "); - Visit(forInStatement.Right); Append(")"); - AppendBeautificationSpace(); - - if (forInStatement.Body is not BlockStatement) - { - AppendBeautificationNewline(); - IncreaseIndent(); - AppendBeautificationIndent(); - } - Visit(forInStatement.Body); - if (NodeNeedsSemicolon(forInStatement.Body)) - { - Append(";"); - } - if (forInStatement.Body is not BlockStatement) - { - DecreaseIndent(); - } } + } - protected virtual void VisitDoWhileStatement(DoWhileStatement doWhileStatement) + protected virtual void VisitUnaryExpression(UnaryExpression unaryExpression) + { + var op = UnaryExpression.GetUnaryOperatorToken(unaryExpression.Operator); + if (unaryExpression.Prefix) + { + Append(op); + if (char.IsLetter(op[0])) + Append(" "); + } + if (!(unaryExpression.Argument is Literal) && !(unaryExpression.Argument is UnaryExpression)) + { + Append("("); + } + Visit(unaryExpression.Argument); + if (!(unaryExpression.Argument is Literal) && !(unaryExpression.Argument is UnaryExpression)) { - Append("do "); - Visit(doWhileStatement.Body); - if (NodeNeedsSemicolon(doWhileStatement.Body)) - { - Append(";"); - } - Append("while("); - Visit(doWhileStatement.Test); Append(")"); } - - protected virtual void VisitArrowFunctionExpression(ArrowFunctionExpression arrowFunctionExpression) + if (!unaryExpression.Prefix) { - if (arrowFunctionExpression.Async) - { - Append("async "); - } - - if (arrowFunctionExpression.Params.Count == 1) - { - if (arrowFunctionExpression.Params[0] is RestElement || ExpressionNeedsBrackets(arrowFunctionExpression.Params[0])) - { - Append("("); - } - Visit(arrowFunctionExpression.Params[0]); - if (arrowFunctionExpression.Params[0] is RestElement || ExpressionNeedsBrackets(arrowFunctionExpression.Params[0])) - { - Append(")"); - } - } - else - { - Append("("); - VisitNodeList(arrowFunctionExpression.Params, appendSeperatorString: ",", appendBracketsIfNeeded: true); ; - Append(")"); - } - Append("=>"); - if (arrowFunctionExpression.Body is ObjectExpression || arrowFunctionExpression.Body is SequenceExpression) - { - Append("("); - } - Visit(arrowFunctionExpression.Body); - if (arrowFunctionExpression.Body is ObjectExpression || arrowFunctionExpression.Body is SequenceExpression) - { - Append(")"); - } + Append(op); } + } - protected virtual void VisitUnaryExpression(UnaryExpression unaryExpression) + protected virtual void VisitUpdateExpression(UpdateExpression updateExpression) + { + if (updateExpression.Prefix) { - var op = UnaryExpression.GetUnaryOperatorToken(unaryExpression.Operator); - if (unaryExpression.Prefix) - { - Append(op); - if (char.IsLetter(op[0])) - Append(" "); - } - if (!(unaryExpression.Argument is Literal) && !(unaryExpression.Argument is UnaryExpression)) - { - Append("("); - } - Visit(unaryExpression.Argument); - if (!(unaryExpression.Argument is Literal) && !(unaryExpression.Argument is UnaryExpression)) - { - Append(")"); - } - if (!unaryExpression.Prefix) - { - Append(op); - } + Append(UnaryExpression.GetUnaryOperatorToken(updateExpression.Operator)); } - - protected virtual void VisitUpdateExpression(UpdateExpression updateExpression) + Visit(updateExpression.Argument); + if (!updateExpression.Prefix) { - if (updateExpression.Prefix) - { - Append(UnaryExpression.GetUnaryOperatorToken(updateExpression.Operator)); - } - Visit(updateExpression.Argument); - if (!updateExpression.Prefix) - { - Append(UnaryExpression.GetUnaryOperatorToken(updateExpression.Operator)); - } + Append(UnaryExpression.GetUnaryOperatorToken(updateExpression.Operator)); } + } + + protected virtual void VisitThisExpression(ThisExpression thisExpression) + { + Append("this"); + } + + protected virtual void VisitSequenceExpression(SequenceExpression sequenceExpression) + { + VisitNodeList(sequenceExpression.Expressions, appendSeperatorString: _beautify ? ", " : ","); + } - protected virtual void VisitThisExpression(ThisExpression thisExpression) + protected virtual void VisitObjectExpression(ObjectExpression objectExpression) + { + Append("{"); + if (objectExpression.Properties.Count > 0) + { + AppendBeautificationNewline(); + IncreaseIndent(); + AppendBeautificationIndent(); + } + VisitNodeList(objectExpression.Properties, appendSeperatorString: ",", addLineBreaks: true); + if (objectExpression.Properties.Count > 0) { - Append("this"); + AppendBeautificationNewline(); + DecreaseIndent(); + AppendBeautificationIndent(); } + Append("}"); + } - protected virtual void VisitSequenceExpression(SequenceExpression sequenceExpression) + protected virtual void VisitNewExpression(NewExpression newExpression) + { + Append("new"); + if (ExpressionNeedsBrackets(newExpression.Callee)) { - VisitNodeList(sequenceExpression.Expressions, appendSeperatorString: Beautify ? ", " : ","); + Append("("); } - - protected virtual void VisitObjectExpression(ObjectExpression objectExpression) + else { - Append("{"); - if (objectExpression.Properties.Count > 0) - { - AppendBeautificationNewline(); - IncreaseIndent(); - AppendBeautificationIndent(); - } - VisitNodeList(objectExpression.Properties, appendSeperatorString: ",", addLineBreaks: true); - if (objectExpression.Properties.Count > 0) - { - AppendBeautificationNewline(); - DecreaseIndent(); - AppendBeautificationIndent(); - } - Append("}"); + Append(" "); + } + Visit(newExpression.Callee); + if (ExpressionNeedsBrackets(newExpression.Callee)) + { + Append(")"); } + if (newExpression.Arguments.Count > 0) + { + Append("("); + VisitNodeList(newExpression.Arguments, appendSeperatorString: ","); + Append(")"); + } + } - protected virtual void VisitNewExpression(NewExpression newExpression) + protected virtual void VisitMemberExpression(MemberExpression memberExpression) + { + if (ExpressionNeedsBrackets(memberExpression.Object) || (memberExpression.Object is Literal l && l.TokenType != TokenType.StringLiteral)) { - Append("new"); - if (ExpressionNeedsBrackets(newExpression.Callee)) - { - Append("("); - } - else - { - Append(" "); - } - Visit(newExpression.Callee); - if (ExpressionNeedsBrackets(newExpression.Callee)) - { - Append(")"); - } - if (newExpression.Arguments.Count > 0) - { - Append("("); - VisitNodeList(newExpression.Arguments, appendSeperatorString: ","); - Append(")"); - } + Append("("); + } + Visit(memberExpression.Object); + if (ExpressionNeedsBrackets(memberExpression.Object) || (memberExpression.Object is Literal l2 && l2.TokenType != TokenType.StringLiteral)) + { + Append(")"); } + if (memberExpression.Computed) + { + Append("["); + } + else + { + if (TryGetParentAt(0) is ChainExpression) + Append("?"); + Append("."); + } + Visit(memberExpression.Property); + if (memberExpression.Computed) + { + Append("]"); + } + } + + protected virtual void VisitLiteral(Literal literal) + { + Append(literal.Raw); + } + + protected virtual void VisitIdentifier(Identifier identifier) + { + Append(identifier.Name!); + } - protected virtual void VisitMemberExpression(MemberExpression memberExpression) + protected virtual void VisitFunctionExpression(IFunction function) + { + var isParentMethod = TryGetParentAt(1) is MethodDefinition; + if (!isParentMethod) { - if (ExpressionNeedsBrackets(memberExpression.Object) || (memberExpression.Object is Literal l && l.TokenType != TokenType.StringLiteral)) - { - Append("("); - } - Visit(memberExpression.Object); - if (ExpressionNeedsBrackets(memberExpression.Object) || (memberExpression.Object is Literal l2 && l2.TokenType != TokenType.StringLiteral)) + if (function.Async) { - Append(")"); - } - if (memberExpression.Computed) - { - Append("["); + Append("async "); } - else + if (!(TryGetParentAt(1) is MethodDefinition)) { - if (TryGetParentAt(0) is ChainExpression) - Append("?"); - Append("."); + Append("function"); } - Visit(memberExpression.Property); - if (memberExpression.Computed) + if (function.Generator) { - Append("]"); + Append("*"); } } - - protected virtual void VisitLiteral(Literal literal) + if (function.Id is not null) { - Append(literal.Raw); + Append(" "); + Visit(function.Id); } + Append("("); + VisitNodeList(function.Params, appendSeperatorString: ","); + Append(")"); + AppendBeautificationSpace(); + Visit(function.Body); + } - protected virtual void VisitIdentifier(Identifier identifier) + protected virtual void VisitClassExpression(ClassExpression classExpression) + { + Append("class "); + if (classExpression.Id is not null) { - Append(identifier.Name!); + Visit(classExpression.Id); } - - protected virtual void VisitFunctionExpression(IFunction function) + if (classExpression.SuperClass is not null) { - var isParentMethod = TryGetParentAt(1) is MethodDefinition; - if (!isParentMethod) - { - if (function.Async) - { - Append("async "); - } - if (!(TryGetParentAt(1) is MethodDefinition)) - { - Append("function"); - } - if (function.Generator) - { - Append("*"); - } - } - if (function.Id != null) - { - Append(" "); - Visit(function.Id); - } - Append("("); - VisitNodeList(function.Params, appendSeperatorString: ","); - Append(")"); - AppendBeautificationSpace(); - Visit(function.Body); + Append(" extends "); + Visit(classExpression.SuperClass); } - protected virtual void VisitClassExpression(ClassExpression classExpression) - { - Append("class "); - if (classExpression.Id != null) - { - Visit(classExpression.Id); - } - if (classExpression.SuperClass != null) - { - Append(" extends "); - Visit(classExpression.SuperClass); - } + AppendBeautificationSpace(); + Append("{"); - AppendBeautificationSpace(); - Append("{"); + AppendBeautificationNewline(); + IncreaseIndent(); + AppendBeautificationIndent(); - AppendBeautificationNewline(); - IncreaseIndent(); - AppendBeautificationIndent(); + Visit(classExpression.Body); - Visit(classExpression.Body); + AppendBeautificationNewline(); + DecreaseIndent(); + AppendBeautificationIndent(); - AppendBeautificationNewline(); - DecreaseIndent(); - AppendBeautificationIndent(); + Append("}"); + } - Append("}"); + protected virtual void VisitExportDefaultDeclaration(ExportDefaultDeclaration exportDefaultDeclaration) + { + Append("export default "); + if (exportDefaultDeclaration.Declaration is not null) + { + Visit(exportDefaultDeclaration.Declaration); } + } + + protected virtual void VisitExportAllDeclaration(ExportAllDeclaration exportAllDeclaration) + { + Append("export*from"); + Visit(exportAllDeclaration.Source); + } - protected virtual void VisitExportDefaultDeclaration(ExportDefaultDeclaration exportDefaultDeclaration) + protected virtual void VisitExportNamedDeclaration(ExportNamedDeclaration exportNamedDeclaration) + { + Append("export"); + if (exportNamedDeclaration.Declaration is not null) { - Append("export default "); - if (exportDefaultDeclaration.Declaration != null) - { - Visit(exportDefaultDeclaration.Declaration); - } + Append(" "); + Visit(exportNamedDeclaration.Declaration); } - - protected virtual void VisitExportAllDeclaration(ExportAllDeclaration exportAllDeclaration) + if (exportNamedDeclaration.Specifiers.Count > 0) { - Append("export*from"); - Visit(exportAllDeclaration.Source); + Append("{"); + VisitNodeList(exportNamedDeclaration.Specifiers, appendSeperatorString: ","); + Append("}"); } - - protected virtual void VisitExportNamedDeclaration(ExportNamedDeclaration exportNamedDeclaration) + if (exportNamedDeclaration.Source is not null) { - Append("export"); - if (exportNamedDeclaration.Declaration != null) - { - Append(" "); - Visit(exportNamedDeclaration.Declaration); - } - if (exportNamedDeclaration.Specifiers.Count > 0) - { - Append("{"); - VisitNodeList(exportNamedDeclaration.Specifiers, appendSeperatorString: ","); - Append("}"); - } - if (exportNamedDeclaration.Source != null) - { - Append("from"); - Visit(exportNamedDeclaration.Source); - } - if (exportNamedDeclaration.Declaration == null && exportNamedDeclaration.Specifiers.Count == 0 && exportNamedDeclaration.Source == null) - { - Append("{}"); - } - + Append("from"); + Visit(exportNamedDeclaration.Source); } - - protected virtual void VisitExportSpecifier(ExportSpecifier exportSpecifier) + if (exportNamedDeclaration.Declaration is null && exportNamedDeclaration.Specifiers.Count == 0 && exportNamedDeclaration.Source is null) { - Visit(exportSpecifier.Local); - if (exportSpecifier.Local != exportSpecifier.Exported) - { - Append(" as "); - Visit(exportSpecifier.Exported); - } + Append("{}"); } - protected virtual void VisitImport(Import import) + } + + protected virtual void VisitExportSpecifier(ExportSpecifier exportSpecifier) + { + Visit(exportSpecifier.Local); + if (exportSpecifier.Local != exportSpecifier.Exported) { - Append("import("); - Visit(import.Source); - Append(")"); + Append(" as "); + Visit(exportSpecifier.Exported); } + } - protected virtual void VisitImportDeclaration(ImportDeclaration importDeclaration) + protected virtual void VisitImport(Import import) + { + Append("import("); + Visit(import.Source); + Append(")"); + } + + protected virtual void VisitImportDeclaration(ImportDeclaration importDeclaration) + { + Append("import "); + var firstSpecifier = importDeclaration.Specifiers.FirstOrDefault(); + if (firstSpecifier is ImportDefaultSpecifier) { - Append("import "); - var firstSpecifier = importDeclaration.Specifiers.FirstOrDefault(); - if (firstSpecifier is ImportDefaultSpecifier) + Visit(firstSpecifier); + if (importDeclaration.Specifiers.Count > 1) { - Visit(firstSpecifier); - if (importDeclaration.Specifiers.Count > 1) - { - Append(","); - AppendBeautificationSpace(); - if (importDeclaration.Specifiers[1] is ImportNamespaceSpecifier) - { - VisitNodeList(importDeclaration.Specifiers.Skip(1), appendSeperatorString: Beautify ? ", " : ","); - } - else - { - Append("{"); - AppendBeautificationSpace(); - VisitNodeList(importDeclaration.Specifiers.Skip(1), appendSeperatorString: Beautify ? ", " : ","); - AppendBeautificationSpace(); - Append("}"); - } - } - } - else if (importDeclaration.Specifiers.Any()) - { - if (importDeclaration.Specifiers[0] is ImportNamespaceSpecifier) + Append(","); + AppendBeautificationSpace(); + if (importDeclaration.Specifiers[1] is ImportNamespaceSpecifier) { - VisitNodeList(importDeclaration.Specifiers, appendSeperatorString: Beautify ? ", " : ","); + VisitNodeList(importDeclaration.Specifiers.Skip(1), appendSeperatorString: _beautify ? ", " : ","); } else { Append("{"); AppendBeautificationSpace(); - VisitNodeList(importDeclaration.Specifiers, appendSeperatorString: Beautify ? ", " : ","); + VisitNodeList(importDeclaration.Specifiers.Skip(1), appendSeperatorString: _beautify ? ", " : ","); AppendBeautificationSpace(); Append("}"); } } - if (importDeclaration.Specifiers.Count > 0) + } + else if (importDeclaration.Specifiers.Any()) + { + if (importDeclaration.Specifiers[0] is ImportNamespaceSpecifier) + { + VisitNodeList(importDeclaration.Specifiers, appendSeperatorString: _beautify ? ", " : ","); + } + else { - Append(" from "); + Append("{"); + AppendBeautificationSpace(); + VisitNodeList(importDeclaration.Specifiers, appendSeperatorString: _beautify ? ", " : ","); + AppendBeautificationSpace(); + Append("}"); } - Visit(importDeclaration.Source); } - - protected virtual void VisitImportNamespaceSpecifier(ImportNamespaceSpecifier importNamespaceSpecifier) + if (importDeclaration.Specifiers.Count > 0) { - Append("* as "); - Visit(importNamespaceSpecifier.Local); + Append(" from "); } + Visit(importDeclaration.Source); + } - protected virtual void VisitImportDefaultSpecifier(ImportDefaultSpecifier importDefaultSpecifier) - { - Visit(importDefaultSpecifier.Local); - } + protected virtual void VisitImportNamespaceSpecifier(ImportNamespaceSpecifier importNamespaceSpecifier) + { + Append("* as "); + Visit(importNamespaceSpecifier.Local); + } - protected virtual void VisitImportSpecifier(ImportSpecifier importSpecifier) - { - Visit(importSpecifier.Imported); - if (importSpecifier.Local != importSpecifier.Imported) - { - Append(" as "); - Visit(importSpecifier.Local); - } - } + protected virtual void VisitImportDefaultSpecifier(ImportDefaultSpecifier importDefaultSpecifier) + { + Visit(importDefaultSpecifier.Local); + } - protected virtual void VisitMethodDefinition(MethodDefinition methodDefinition) + protected virtual void VisitImportSpecifier(ImportSpecifier importSpecifier) + { + Visit(importSpecifier.Imported); + if (importSpecifier.Local != importSpecifier.Imported) { - if (methodDefinition.Static) - { - Append("static "); - } - if (IsAsync(methodDefinition.Value)) - { - Append("async "); - } - if (methodDefinition.Value is FunctionExpression f && f.Generator) - { - Append("*"); - } - if (methodDefinition.Kind == PropertyKind.Get) - { - Append("get "); - } - else if (methodDefinition.Kind == PropertyKind.Set) - { - Append("set "); - } - if (methodDefinition.Key is MemberExpression || ExpressionNeedsBrackets(methodDefinition.Key)) - { - Append("["); - } - if (ExpressionNeedsBrackets(methodDefinition.Key)) - { - Append("("); - } - Visit(methodDefinition.Key); - if (ExpressionNeedsBrackets(methodDefinition.Key)) - { - Append(")"); - } - if (methodDefinition.Key is MemberExpression || ExpressionNeedsBrackets(methodDefinition.Key)) - { - Append("]"); - } - Visit(methodDefinition.Value); + Append(" as "); + Visit(importSpecifier.Local); } + } - protected virtual void VisitForOfStatement(ForOfStatement forOfStatement) + protected virtual void VisitMethodDefinition(MethodDefinition methodDefinition) + { + if (methodDefinition.Static) { - Append("for("); - Visit(forOfStatement.Left); - Append(" of "); - Visit(forOfStatement.Right); - Append(")"); - AppendBeautificationSpace(); - - if (forOfStatement.Body is not BlockStatement) - { - AppendBeautificationNewline(); - IncreaseIndent(); - AppendBeautificationIndent(); - } - Visit(forOfStatement.Body); - if (NodeNeedsSemicolon(forOfStatement.Body)) - { - Append(";"); - } - if (forOfStatement.Body is not BlockStatement) - { - DecreaseIndent(); - } + Append("static "); } - - protected virtual void VisitClassDeclaration(ClassDeclaration classDeclaration) + if (IsAsync(methodDefinition.Value)) { - Append("class "); - if (classDeclaration.Id != null) - { - Visit(classDeclaration.Id); - } - - if (classDeclaration.SuperClass != null) - { - Append(" extends "); - Visit(classDeclaration.SuperClass); - } - - AppendBeautificationSpace(); - Append("{"); - - AppendBeautificationNewline(); - IncreaseIndent(); - AppendBeautificationIndent(); - - Visit(classDeclaration.Body); - - AppendBeautificationNewline(); - DecreaseIndent(); - AppendBeautificationIndent(); - - Append("}"); + Append("async "); } - - protected virtual void VisitClassBody(ClassBody classBody) + if (methodDefinition.Value is FunctionExpression f && f.Generator) { - VisitNodeList(classBody.Body, addLineBreaks: true); + Append("*"); } - - protected virtual void VisitYieldExpression(YieldExpression yieldExpression) + if (methodDefinition.Kind == PropertyKind.Get) { - Append("yield "); - if (yieldExpression.Argument != null) - { - Visit(yieldExpression.Argument); - } + Append("get "); } - - protected virtual void VisitTaggedTemplateExpression(TaggedTemplateExpression taggedTemplateExpression) + else if (methodDefinition.Kind == PropertyKind.Set) { - Visit(taggedTemplateExpression.Tag); - Visit(taggedTemplateExpression.Quasi); + Append("set "); } - - protected virtual void VisitSuper(Super super) + if (methodDefinition.Key is MemberExpression || ExpressionNeedsBrackets(methodDefinition.Key)) { - Append("super"); + Append("["); } - - protected virtual void VisitMetaProperty(MetaProperty metaProperty) + if (ExpressionNeedsBrackets(methodDefinition.Key)) { - Visit(metaProperty.Meta); - Append("."); - Visit(metaProperty.Property); + Append("("); } - - protected virtual void VisitArrowParameterPlaceHolder(ArrowParameterPlaceHolder arrowParameterPlaceHolder) + Visit(methodDefinition.Key); + if (ExpressionNeedsBrackets(methodDefinition.Key)) { - VisitNodeList(arrowParameterPlaceHolder.Params); + Append(")"); } - - protected virtual void VisitObjectPattern(ObjectPattern objectPattern) + if (methodDefinition.Key is MemberExpression || ExpressionNeedsBrackets(methodDefinition.Key)) { - Append("{"); - VisitNodeList(objectPattern.Properties, appendSeperatorString: ","); - Append("}"); + Append("]"); } + Visit(methodDefinition.Value); + } - protected virtual void VisitSpreadElement(SpreadElement spreadElement) - { - Append("..."); - Visit(spreadElement.Argument); - } + protected virtual void VisitForOfStatement(ForOfStatement forOfStatement) + { + Append("for("); + Visit(forOfStatement.Left); + Append(" of "); + Visit(forOfStatement.Right); + Append(")"); + AppendBeautificationSpace(); - protected virtual void VisitAssignmentPattern(AssignmentPattern assignmentPattern) + if (forOfStatement.Body is not BlockStatement) { - Visit(assignmentPattern.Left); - Append("="); - Visit(assignmentPattern.Right); + AppendBeautificationNewline(); + IncreaseIndent(); + AppendBeautificationIndent(); } - - protected virtual void VisitArrayPattern(ArrayPattern arrayPattern) + Visit(forOfStatement.Body); + if (NodeNeedsSemicolon(forOfStatement.Body)) { - Append("["); - VisitNodeList(arrayPattern.Elements, appendSeperatorString: ","); - Append("]"); + Append(";"); } - - protected virtual void VisitVariableDeclarator(VariableDeclarator variableDeclarator) + if (forOfStatement.Body is not BlockStatement) { - Visit(variableDeclarator.Id); - if (variableDeclarator.Init != null) - { - AppendBeautificationSpace(); - Append("="); - AppendBeautificationSpace(); - if (ExpressionNeedsBrackets(variableDeclarator.Init)) - { - Append("("); - } - Visit(variableDeclarator.Init); - if (ExpressionNeedsBrackets(variableDeclarator.Init)) - { - Append(")"); - } - } + DecreaseIndent(); } + } - protected virtual void VisitTemplateLiteral(TemplateLiteral templateLiteral) + protected virtual void VisitClassDeclaration(ClassDeclaration classDeclaration) + { + Append("class "); + if (classDeclaration.Id is not null) { - Append("`"); - for (int n = 0; n < templateLiteral.Quasis.Count; n++) - { - Visit(templateLiteral.Quasis[n]); - if (templateLiteral.Expressions.Count > n) - { - Append("${"); - Visit(templateLiteral.Expressions[n]); - Append("}"); - } - } - Append("`"); + Visit(classDeclaration.Id); } - protected virtual void VisitTemplateElement(TemplateElement templateElement) + if (classDeclaration.SuperClass is not null) { - Append(templateElement.Value.Raw); + Append(" extends "); + Visit(classDeclaration.SuperClass); } - protected virtual void VisitRestElement(RestElement restElement) + AppendBeautificationSpace(); + Append("{"); + + AppendBeautificationNewline(); + IncreaseIndent(); + AppendBeautificationIndent(); + + Visit(classDeclaration.Body); + + AppendBeautificationNewline(); + DecreaseIndent(); + AppendBeautificationIndent(); + + Append("}"); + } + + protected virtual void VisitClassBody(ClassBody classBody) + { + VisitNodeList(classBody.Body, addLineBreaks: true); + } + + protected virtual void VisitYieldExpression(YieldExpression yieldExpression) + { + Append("yield "); + if (yieldExpression.Argument is not null) { - Append("..."); - Visit(restElement.Argument); + Visit(yieldExpression.Argument); } + } + + protected virtual void VisitTaggedTemplateExpression(TaggedTemplateExpression taggedTemplateExpression) + { + Visit(taggedTemplateExpression.Tag); + Visit(taggedTemplateExpression.Quasi); + } + + protected virtual void VisitSuper(Super super) + { + Append("super"); + } + + protected virtual void VisitMetaProperty(MetaProperty metaProperty) + { + Visit(metaProperty.Meta); + Append("."); + Visit(metaProperty.Property); + } - protected virtual void VisitProperty(Property property) + protected virtual void VisitArrowParameterPlaceHolder(ArrowParameterPlaceHolder arrowParameterPlaceHolder) + { + VisitNodeList(arrowParameterPlaceHolder.Params); + } + + protected virtual void VisitObjectPattern(ObjectPattern objectPattern) + { + Append("{"); + VisitNodeList(objectPattern.Properties, appendSeperatorString: ","); + Append("}"); + } + + protected virtual void VisitSpreadElement(SpreadElement spreadElement) + { + Append("..."); + Visit(spreadElement.Argument); + } + + protected virtual void VisitAssignmentPattern(AssignmentPattern assignmentPattern) + { + Visit(assignmentPattern.Left); + Append("="); + Visit(assignmentPattern.Right); + } + + protected virtual void VisitArrayPattern(ArrayPattern arrayPattern) + { + Append("["); + VisitNodeList(arrayPattern.Elements, appendSeperatorString: ","); + Append("]"); + } + + protected virtual void VisitVariableDeclarator(VariableDeclarator variableDeclarator) + { + Visit(variableDeclarator.Id); + if (variableDeclarator.Init is not null) { - if (property.Key is MemberExpression || ExpressionNeedsBrackets(property.Key)) - { - Append("["); - } - if (ExpressionNeedsBrackets(property.Key)) + AppendBeautificationSpace(); + Append("="); + AppendBeautificationSpace(); + if (ExpressionNeedsBrackets(variableDeclarator.Init)) { Append("("); } - Visit(property.Key); - if (ExpressionNeedsBrackets(property.Key)) + Visit(variableDeclarator.Init); + if (ExpressionNeedsBrackets(variableDeclarator.Init)) { Append(")"); } - if (property.Key is MemberExpression || ExpressionNeedsBrackets(property.Key)) - { - Append("]"); - } - if (property.Key is Identifier keyI && property.Value is Identifier valueI && keyI.Name == valueI.Name) - { } - else - { - AppendBeautificationSpace(); - Append(":"); - AppendBeautificationSpace(); - if (property.Value is not ObjectPattern && ExpressionNeedsBrackets(property.Value)) - { - Append("("); - } - Visit(property.Value); - if (property.Value is not ObjectPattern && ExpressionNeedsBrackets(property.Value)) - { - Append(")"); - } - } } + } - protected virtual void VisitPropertyDefinition(PropertyDefinition propertyDefinition) + protected virtual void VisitTemplateLiteral(TemplateLiteral templateLiteral) + { + Append("`"); + for (int n = 0; n < templateLiteral.Quasis.Count; n++) { - if (propertyDefinition.Static) - { - Append("static "); - } - if (propertyDefinition.Key is MemberExpression || ExpressionNeedsBrackets(propertyDefinition.Key)) - { - Append("["); - } - if (ExpressionNeedsBrackets(propertyDefinition.Key)) + Visit(templateLiteral.Quasis[n]); + if (templateLiteral.Expressions.Count > n) { - Append("("); - } - Visit(propertyDefinition.Key); - if (ExpressionNeedsBrackets(propertyDefinition.Key)) - { - Append(")"); - } - if (propertyDefinition.Key is MemberExpression || ExpressionNeedsBrackets(propertyDefinition.Key)) - { - Append("]"); - } - if (propertyDefinition.Value != null) - { - Append("="); - Visit(propertyDefinition.Value); + Append("${"); + Visit(templateLiteral.Expressions[n]); + Append("}"); } - Append(";"); } + Append("`"); + } + + protected virtual void VisitTemplateElement(TemplateElement templateElement) + { + Append(templateElement.Value.Raw); + } + + protected virtual void VisitRestElement(RestElement restElement) + { + Append("..."); + Visit(restElement.Argument); + } - protected virtual void VisitAwaitExpression(AwaitExpression awaitExpression) + protected virtual void VisitProperty(Property property) + { + if (property.Key is MemberExpression || ExpressionNeedsBrackets(property.Key)) { - Append("await "); - Visit(awaitExpression.Argument); + Append("["); } - - protected virtual void VisitConditionalExpression(ConditionalExpression conditionalExpression) + if (ExpressionNeedsBrackets(property.Key)) + { + Append("("); + } + Visit(property.Key); + if (ExpressionNeedsBrackets(property.Key)) + { + Append(")"); + } + if (property.Key is MemberExpression || ExpressionNeedsBrackets(property.Key)) + { + Append("]"); + } + if (property.Key is Identifier keyI && property.Value is Identifier valueI && keyI.Name == valueI.Name) + { } + else { - if (conditionalExpression.Test is AssignmentExpression) - { - Append("("); - } - Visit(conditionalExpression.Test); - if (conditionalExpression.Test is AssignmentExpression) - { - Append(")"); - } - AppendBeautificationSpace(); - Append("?"); - AppendBeautificationSpace(); - if (ExpressionNeedsBrackets(conditionalExpression.Consequent)) - { - Append("("); - } - Visit(conditionalExpression.Consequent); - if (ExpressionNeedsBrackets(conditionalExpression.Consequent)) - { - Append(")"); - } AppendBeautificationSpace(); Append(":"); AppendBeautificationSpace(); - if (ExpressionNeedsBrackets(conditionalExpression.Alternate)) + if (property.Value is not ObjectPattern && ExpressionNeedsBrackets(property.Value)) { Append("("); } - Visit(conditionalExpression.Alternate); - if (ExpressionNeedsBrackets(conditionalExpression.Alternate)) + Visit(property.Value); + if (property.Value is not ObjectPattern && ExpressionNeedsBrackets(property.Value)) { Append(")"); } } + } - protected virtual void VisitCallExpression(CallExpression callExpression) + protected virtual void VisitPropertyDefinition(PropertyDefinition propertyDefinition) + { + if (propertyDefinition.Static) + { + Append("static "); + } + if (propertyDefinition.Key is MemberExpression || ExpressionNeedsBrackets(propertyDefinition.Key)) + { + Append("["); + } + if (ExpressionNeedsBrackets(propertyDefinition.Key)) { - if (ExpressionNeedsBrackets(callExpression.Callee)) - { - Append("("); - } - Visit(callExpression.Callee); - if (ExpressionNeedsBrackets(callExpression.Callee)) - { - Append(")"); - } Append("("); - VisitNodeList(callExpression.Arguments, appendSeperatorString: ",", appendBracketsIfNeeded: true); + } + Visit(propertyDefinition.Key); + if (ExpressionNeedsBrackets(propertyDefinition.Key)) + { Append(")"); } + if (propertyDefinition.Key is MemberExpression || ExpressionNeedsBrackets(propertyDefinition.Key)) + { + Append("]"); + } + if (propertyDefinition.Value is not null) + { + Append("="); + Visit(propertyDefinition.Value); + } + Append(";"); + } + + protected virtual void VisitAwaitExpression(AwaitExpression awaitExpression) + { + Append("await "); + Visit(awaitExpression.Argument); + } - protected virtual void VisitBinaryExpression(BinaryExpression binaryExpression) + protected virtual void VisitConditionalExpression(ConditionalExpression conditionalExpression) + { + if (conditionalExpression.Test is AssignmentExpression) { - if (ExpressionNeedsBrackets(binaryExpression.Left)) - { - Append("("); - } - Visit(binaryExpression.Left); - if (ExpressionNeedsBrackets(binaryExpression.Left)) - { - Append(")"); - } - var op = BinaryExpression.GetBinaryOperatorToken(binaryExpression.Operator); - if (char.IsLetter(op[0])) - { - Append(" "); - } - else - { - AppendBeautificationSpace(); - } - Append(op); - if (char.IsLetter(op[0])) - { - Append(" "); - } - else - { - AppendBeautificationSpace(); - } - if (ExpressionNeedsBrackets(binaryExpression.Right)) - { - Append("("); - } - Visit(binaryExpression.Right); - if (ExpressionNeedsBrackets(binaryExpression.Right)) - { - Append(")"); - } + Append("("); + } + Visit(conditionalExpression.Test); + if (conditionalExpression.Test is AssignmentExpression) + { + Append(")"); } + AppendBeautificationSpace(); + Append("?"); + AppendBeautificationSpace(); + if (ExpressionNeedsBrackets(conditionalExpression.Consequent)) + { + Append("("); + } + Visit(conditionalExpression.Consequent); + if (ExpressionNeedsBrackets(conditionalExpression.Consequent)) + { + Append(")"); + } + AppendBeautificationSpace(); + Append(":"); + AppendBeautificationSpace(); + if (ExpressionNeedsBrackets(conditionalExpression.Alternate)) + { + Append("("); + } + Visit(conditionalExpression.Alternate); + if (ExpressionNeedsBrackets(conditionalExpression.Alternate)) + { + Append(")"); + } + } - protected virtual void VisitArrayExpression(ArrayExpression arrayExpression) + protected virtual void VisitCallExpression(CallExpression callExpression) + { + if (ExpressionNeedsBrackets(callExpression.Callee)) { - Append("["); - VisitNodeList(arrayExpression.Elements, appendSeperatorString: ","); - Append("]"); + Append("("); + } + Visit(callExpression.Callee); + if (ExpressionNeedsBrackets(callExpression.Callee)) + { + Append(")"); } + Append("("); + VisitNodeList(callExpression.Arguments, appendSeperatorString: ",", appendBracketsIfNeeded: true); + Append(")"); + } - protected virtual void VisitAssignmentExpression(AssignmentExpression assignmentExpression) + protected virtual void VisitBinaryExpression(BinaryExpression binaryExpression) + { + if (ExpressionNeedsBrackets(binaryExpression.Left)) + { + Append("("); + } + Visit(binaryExpression.Left); + if (ExpressionNeedsBrackets(binaryExpression.Left)) + { + Append(")"); + } + var op = BinaryExpression.GetBinaryOperatorToken(binaryExpression.Operator); + if (char.IsLetter(op[0])) + { + Append(" "); + } + else { - if (assignmentExpression.Left is ObjectPattern) - { - Append("("); - } - var op = AssignmentExpression.GetAssignmentOperatorToken(assignmentExpression.Operator); - Visit(assignmentExpression.Left); AppendBeautificationSpace(); - Append(op); + } + Append(op); + if (char.IsLetter(op[0])) + { + Append(" "); + } + else + { AppendBeautificationSpace(); - if (ExpressionNeedsBrackets(assignmentExpression.Right) && !(assignmentExpression.Right is AssignmentExpression)) - { - Append("("); - } - Visit(assignmentExpression.Right); - if (ExpressionNeedsBrackets(assignmentExpression.Right) && !(assignmentExpression.Right is AssignmentExpression)) - { - Append(")"); - } - if (assignmentExpression.Left is ObjectPattern) - { - Append(")"); - } } + if (ExpressionNeedsBrackets(binaryExpression.Right)) + { + Append("("); + } + Visit(binaryExpression.Right); + if (ExpressionNeedsBrackets(binaryExpression.Right)) + { + Append(")"); + } + } - protected virtual void VisitContinueStatement(ContinueStatement continueStatement) + protected virtual void VisitArrayExpression(ArrayExpression arrayExpression) + { + Append("["); + VisitNodeList(arrayExpression.Elements, appendSeperatorString: ","); + Append("]"); + } + + protected virtual void VisitAssignmentExpression(AssignmentExpression assignmentExpression) + { + if (assignmentExpression.Left is ObjectPattern) { - Append("continue "); - if (continueStatement.Label != null) - { - Visit(continueStatement.Label); - } + Append("("); + } + var op = AssignmentExpression.GetAssignmentOperatorToken(assignmentExpression.Operator); + Visit(assignmentExpression.Left); + AppendBeautificationSpace(); + Append(op); + AppendBeautificationSpace(); + if (ExpressionNeedsBrackets(assignmentExpression.Right) && !(assignmentExpression.Right is AssignmentExpression)) + { + Append("("); + } + Visit(assignmentExpression.Right); + if (ExpressionNeedsBrackets(assignmentExpression.Right) && !(assignmentExpression.Right is AssignmentExpression)) + { + Append(")"); + } + if (assignmentExpression.Left is ObjectPattern) + { + Append(")"); } + } - protected virtual void VisitBreakStatement(BreakStatement breakStatement) + protected virtual void VisitContinueStatement(ContinueStatement continueStatement) + { + Append("continue "); + if (continueStatement.Label is not null) { - if (breakStatement.Label != null) - { - Visit(breakStatement.Label); - } - Append("break"); + Visit(continueStatement.Label); } + } - protected virtual void VisitBlockStatement(BlockStatement blockStatement) + protected virtual void VisitBreakStatement(BreakStatement breakStatement) + { + if (breakStatement.Label is not null) { - Append("{"); + Visit(breakStatement.Label); + } + Append("break"); + } - AppendBeautificationNewline(); - IncreaseIndent(); - AppendBeautificationIndent(); + protected virtual void VisitBlockStatement(BlockStatement blockStatement) + { + Append("{"); - VisitNodeList(blockStatement.Body, appendAtEnd: ";", addLineBreaks: true); + AppendBeautificationNewline(); + IncreaseIndent(); + AppendBeautificationIndent(); - AppendBeautificationNewline(); - DecreaseIndent(); - AppendBeautificationIndent(); + VisitNodeList(blockStatement.Body, appendAtEnd: ";", addLineBreaks: true); - Append("}"); - } + AppendBeautificationNewline(); + DecreaseIndent(); + AppendBeautificationIndent(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void VisitNodeList(IEnumerable nodeList, string? appendAtEnd = null, string? appendSeperatorString = null, bool appendBracketsIfNeeded = false, bool addLineBreaks = false) - where TNode : Node + Append("}"); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void VisitNodeList(IEnumerable nodeList, string? appendAtEnd = null, string? appendSeperatorString = null, bool appendBracketsIfNeeded = false, bool addLineBreaks = false) + where TNode : Node + { + var notfirst = false; + foreach (var node in nodeList) { - var notfirst = false; - foreach (var node in nodeList) + if (node is not null) { - if (node != null) + if (notfirst && appendSeperatorString is not null) + { + Append(appendSeperatorString); + } + if (notfirst && addLineBreaks) + { + AppendBeautificationNewline(); + AppendBeautificationIndent(); + } + if (appendBracketsIfNeeded && ExpressionNeedsBrackets(node)) + { + Append("("); + } + Visit(node); + if (appendBracketsIfNeeded && ExpressionNeedsBrackets(node)) + { + Append(")"); + } + notfirst = true; + if (appendAtEnd is not null && NodeNeedsSemicolon(node)) { - if (notfirst && appendSeperatorString != null) - { - Append(appendSeperatorString); - } - if (notfirst && addLineBreaks) - { - AppendBeautificationNewline(); - AppendBeautificationIndent(); - } - if (appendBracketsIfNeeded && ExpressionNeedsBrackets(node)) - { - Append("("); - } - Visit(node); - if (appendBracketsIfNeeded && ExpressionNeedsBrackets(node)) - { - Append(")"); - } - notfirst = true; - if (appendAtEnd != null && NodeNeedsSemicolon(node)) - { - Append(appendAtEnd); - } + Append(appendAtEnd); } } } + } + + public override string ToString() + { + return _writer.ToString(); + } - public override string ToString() + public bool IsAsync(Node node) + { + if (node is ArrowFunctionExpression afe) + { + return afe.Async; + } + if (node is ArrowParameterPlaceHolder apph) { - return _sb.ToString(); + return apph.Async; } + if (node is FunctionDeclaration fd) + { + return fd.Async; + } + if (node is FunctionExpression fe) + { + return fe.Async; + } + return false; + } - public bool IsAsync(Node node) + public bool NodeNeedsSemicolon(Node? node) + { + if (node is BlockStatement || + node is IfStatement || + node is SwitchStatement || + node is ForInStatement || + node is ForOfStatement || + node is ForStatement || + node is FunctionDeclaration || + node is ReturnStatement || + node is ThrowStatement || + node is TryStatement || + node is EmptyStatement || + node is ClassDeclaration) { - if (node is ArrowFunctionExpression afe) - { - return afe.Async; - } - if (node is ArrowParameterPlaceHolder apph) - { - return apph.Async; - } - if (node is FunctionDeclaration fd) - { - return fd.Async; - } - if (node is FunctionExpression fe) - { - return fe.Async; - } return false; } + if (node is ExportNamedDeclaration end) + { + return NodeNeedsSemicolon(end.Declaration); + } + return true; + } - public bool NodeNeedsSemicolon(Node? node) - { - if (node is BlockStatement || - node is IfStatement || - node is SwitchStatement || - node is ForInStatement || - node is ForOfStatement || - node is ForStatement || - node is FunctionDeclaration || - node is ReturnStatement || - node is ThrowStatement || - node is TryStatement || - node is EmptyStatement || - node is ClassDeclaration) - { - return false; - } - if (node is ExportNamedDeclaration end) - { - return NodeNeedsSemicolon(end.Declaration); - } + public bool ExpressionNeedsBrackets(Node? node) + { + if (node is FunctionExpression) + { return true; } - - public bool ExpressionNeedsBrackets(Node? node) + if (node is ArrowFunctionExpression) { - if (node is FunctionExpression) - { - return true; - } - if (node is ArrowFunctionExpression) - { - return true; - } - if (node is AssignmentExpression) - { - return true; - } - if (node is SequenceExpression) - { - return true; - } - if (node is ConditionalExpression) - { - return true; - } - if (node is BinaryExpression) - { - return true; - } - if (node is UnaryExpression) - { - return true; - } - if (node is CallExpression) - { - return true; - } - if (node is NewExpression) - { - return true; - } - if (node is ObjectPattern) - { - return true; - } - if (node is ArrayPattern) - { - return true; - } - if (node is YieldExpression) - { - return true; - } - return false; + return true; + } + if (node is AssignmentExpression) + { + return true; + } + if (node is SequenceExpression) + { + return true; + } + if (node is ConditionalExpression) + { + return true; + } + if (node is BinaryExpression) + { + return true; + } + if (node is UnaryExpression) + { + return true; + } + if (node is CallExpression) + { + return true; + } + if (node is NewExpression) + { + return true; + } + if (node is ObjectPattern) + { + return true; + } + if (node is ArrayPattern) + { + return true; + } + if (node is YieldExpression) + { + return true; } + return false; } } diff --git a/src/Esprima/Utils/AstToJsonConverter.cs b/src/Esprima/Utils/AstToJsonConverter.cs new file mode 100644 index 00000000..453d5a72 --- /dev/null +++ b/src/Esprima/Utils/AstToJsonConverter.cs @@ -0,0 +1,1141 @@ +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 class AstToJsonConverter : AstVisitor +{ + public delegate AstToJsonConverter Factory(JsonWriter writer, AstJson.Options options); + + 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 AstToJsonConverter(JsonWriter writer, AstJson.Options options) + { + _writer = writer ?? ThrowArgumentNullException(nameof(writer)); + + if (options is null) + { + ThrowArgumentNullException(nameof(options)); + return; + } + + _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 ?? ThrowArgumentNullException(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) + { + 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) => ((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) + { + 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(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 && 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/Jsx/JsxAstJson.cs b/src/Esprima/Utils/Jsx/JsxAstJson.cs deleted file mode 100644 index 52ed69f6..00000000 --- a/src/Esprima/Utils/Jsx/JsxAstJson.cs +++ /dev/null @@ -1,172 +0,0 @@ -using Esprima.Ast; -using Esprima.Ast.Jsx; - -namespace Esprima.Utils.Jsx; - -public sealed class JsxAstToJsonConverter : AstToJsonConverter -{ - public static new readonly JsxAstToJsonConverter Default = new(); - - private JsxAstToJsonConverter() { } - - private protected override VisitorBase CreateVisitor(JsonWriter writer, AstJson.Options options) - { - return new Visitor(writer, options); - } - - private sealed class Visitor : VisitorBase, IJsxAstVisitor - { - public Visitor(JsonWriter writer, AstJson.Options options) - : base(writer, options) - { - } - - protected override string GetNodeType(Node node) - { - if (node is JsxExpression jsxExpression) - { - // Due to the borrowed test fixtures, it's important to use the 'JSX' prefix to stay consistent with the naming used by original Esprima - // (see https://github.com/jquery/esprima/blob/4.0.1/src/jsx-nodes.ts). - return "JSX" + jsxExpression.Type.ToString(); - } - - return base.GetNodeType(node); - } - - object? IJsxAstVisitor.VisitJsxAttribute(JsxAttribute jsxAttribute) - { - using (StartNodeObject(jsxAttribute)) - { - Member("name", jsxAttribute.Name); - Member("value", jsxAttribute.Value); - } - - return jsxAttribute; - } - - object? IJsxAstVisitor.VisitJsxClosingElement(JsxClosingElement jsxClosingElement) - { - using (StartNodeObject(jsxClosingElement)) - { - Member("name", jsxClosingElement.Name); - } - - return jsxClosingElement; - } - - object? IJsxAstVisitor.VisitJsxClosingFragment(JsxClosingFragment jsxClosingFragment) - { - using (StartNodeObject(jsxClosingFragment)) - { - } - - return jsxClosingFragment; - } - - object? IJsxAstVisitor.VisitJsxElement(JsxElement jsxElement) - { - using (StartNodeObject(jsxElement)) - { - Member("openingElement", jsxElement.OpeningElement); - Member("children", jsxElement.Children); - Member("closingElement", jsxElement.ClosingElement); - } - - return jsxElement; - } - - object? IJsxAstVisitor.VisitJsxEmptyExpression(JsxEmptyExpression jsxEmptyExpression) - { - using (StartNodeObject(jsxEmptyExpression)) - { - } - - return jsxEmptyExpression; - } - - object? IJsxAstVisitor.VisitJsxExpressionContainer(JsxExpressionContainer jsxExpressionContainer) - { - using (StartNodeObject(jsxExpressionContainer)) - { - Member("expression", jsxExpressionContainer.Expression); - } - - return jsxExpressionContainer; - } - - object? IJsxAstVisitor.VisitJsxIdentifier(JsxIdentifier jsxIdentifier) - { - using (StartNodeObject(jsxIdentifier)) - { - Member("name", jsxIdentifier.Name); - } - - return jsxIdentifier; - } - - object? IJsxAstVisitor.VisitJsxMemberExpression(JsxMemberExpression jsxMemberExpression) - { - using (StartNodeObject(jsxMemberExpression)) - { - Member("object", jsxMemberExpression.Object); - Member("property", jsxMemberExpression.Property); - } - - return jsxMemberExpression; - } - - object? IJsxAstVisitor.VisitJsxNamespacedName(JsxNamespacedName jsxNamespacedName) - { - using (StartNodeObject(jsxNamespacedName)) - { - Member("namespace", jsxNamespacedName.Namespace); - Member("name", jsxNamespacedName.Name); - } - - return jsxNamespacedName; - } - - object? IJsxAstVisitor.VisitJsxOpeningElement(JsxOpeningElement jsxOpeningElement) - { - using (StartNodeObject(jsxOpeningElement)) - { - Member("name", jsxOpeningElement.Name); - Member("selfClosing", jsxOpeningElement.SelfClosing); - Member("attributes", jsxOpeningElement.Attributes); - } - - return jsxOpeningElement; - } - - object? IJsxAstVisitor.VisitJsxOpeningFragment(JsxOpeningFragment jsxOpeningFragment) - { - using (StartNodeObject(jsxOpeningFragment)) - { - Member("selfClosing", jsxOpeningFragment.SelfClosing); - } - - return jsxOpeningFragment; - } - - object? IJsxAstVisitor.VisitJsxSpreadAttribute(JsxSpreadAttribute jsxSpreadAttribute) - { - using (StartNodeObject(jsxSpreadAttribute)) - { - Member("argument", jsxSpreadAttribute.Argument); - } - - return jsxSpreadAttribute; - } - - object? IJsxAstVisitor.VisitJsxText(JsxText jsxText) - { - using (StartNodeObject(jsxText)) - { - Member("value", jsxText.Value); - Member("raw", jsxText.Raw); - } - - return jsxText; - } - } -} diff --git a/src/Esprima/Utils/Jsx/JsxAstToJsonConverter.cs b/src/Esprima/Utils/Jsx/JsxAstToJsonConverter.cs new file mode 100644 index 00000000..841299dd --- /dev/null +++ b/src/Esprima/Utils/Jsx/JsxAstToJsonConverter.cs @@ -0,0 +1,160 @@ +using Esprima.Ast; +using Esprima.Ast.Jsx; + +namespace Esprima.Utils.Jsx; + +public class JsxAstToJsonConverter : AstToJsonConverter, IJsxAstVisitor +{ + public JsxAstToJsonConverter(JsonWriter writer, AstJson.Options options) + : base(writer, options) + { + } + + protected override string GetNodeType(Node node) + { + if (node is JsxExpression jsxExpression) + { + // Due to the borrowed test fixtures, it's important to use the 'JSX' prefix to stay consistent with the naming used by original Esprima + // (see https://github.com/jquery/esprima/blob/4.0.1/src/jsx-nodes.ts). + return "JSX" + jsxExpression.Type.ToString(); + } + + return base.GetNodeType(node); + } + + object? IJsxAstVisitor.VisitJsxAttribute(JsxAttribute jsxAttribute) + { + using (StartNodeObject(jsxAttribute)) + { + Member("name", jsxAttribute.Name); + Member("value", jsxAttribute.Value); + } + + return jsxAttribute; + } + + object? IJsxAstVisitor.VisitJsxClosingElement(JsxClosingElement jsxClosingElement) + { + using (StartNodeObject(jsxClosingElement)) + { + Member("name", jsxClosingElement.Name); + } + + return jsxClosingElement; + } + + object? IJsxAstVisitor.VisitJsxClosingFragment(JsxClosingFragment jsxClosingFragment) + { + using (StartNodeObject(jsxClosingFragment)) + { + } + + return jsxClosingFragment; + } + + object? IJsxAstVisitor.VisitJsxElement(JsxElement jsxElement) + { + using (StartNodeObject(jsxElement)) + { + Member("openingElement", jsxElement.OpeningElement); + Member("children", jsxElement.Children); + Member("closingElement", jsxElement.ClosingElement); + } + + return jsxElement; + } + + object? IJsxAstVisitor.VisitJsxEmptyExpression(JsxEmptyExpression jsxEmptyExpression) + { + using (StartNodeObject(jsxEmptyExpression)) + { + } + + return jsxEmptyExpression; + } + + object? IJsxAstVisitor.VisitJsxExpressionContainer(JsxExpressionContainer jsxExpressionContainer) + { + using (StartNodeObject(jsxExpressionContainer)) + { + Member("expression", jsxExpressionContainer.Expression); + } + + return jsxExpressionContainer; + } + + object? IJsxAstVisitor.VisitJsxIdentifier(JsxIdentifier jsxIdentifier) + { + using (StartNodeObject(jsxIdentifier)) + { + Member("name", jsxIdentifier.Name); + } + + return jsxIdentifier; + } + + object? IJsxAstVisitor.VisitJsxMemberExpression(JsxMemberExpression jsxMemberExpression) + { + using (StartNodeObject(jsxMemberExpression)) + { + Member("object", jsxMemberExpression.Object); + Member("property", jsxMemberExpression.Property); + } + + return jsxMemberExpression; + } + + object? IJsxAstVisitor.VisitJsxNamespacedName(JsxNamespacedName jsxNamespacedName) + { + using (StartNodeObject(jsxNamespacedName)) + { + Member("namespace", jsxNamespacedName.Namespace); + Member("name", jsxNamespacedName.Name); + } + + return jsxNamespacedName; + } + + object? IJsxAstVisitor.VisitJsxOpeningElement(JsxOpeningElement jsxOpeningElement) + { + using (StartNodeObject(jsxOpeningElement)) + { + Member("name", jsxOpeningElement.Name); + Member("selfClosing", jsxOpeningElement.SelfClosing); + Member("attributes", jsxOpeningElement.Attributes); + } + + return jsxOpeningElement; + } + + object? IJsxAstVisitor.VisitJsxOpeningFragment(JsxOpeningFragment jsxOpeningFragment) + { + using (StartNodeObject(jsxOpeningFragment)) + { + Member("selfClosing", jsxOpeningFragment.SelfClosing); + } + + return jsxOpeningFragment; + } + + object? IJsxAstVisitor.VisitJsxSpreadAttribute(JsxSpreadAttribute jsxSpreadAttribute) + { + using (StartNodeObject(jsxSpreadAttribute)) + { + Member("argument", jsxSpreadAttribute.Argument); + } + + return jsxSpreadAttribute; + } + + object? IJsxAstVisitor.VisitJsxText(JsxText jsxText) + { + using (StartNodeObject(jsxText)) + { + Member("value", jsxText.Value); + Member("raw", jsxText.Raw); + } + + return jsxText; + } +} diff --git a/test/Esprima.Tests/AstToJavascriptTests.cs b/test/Esprima.Tests/AstToJavascriptTests.cs index 55d91768..ce18f29a 100644 --- a/test/Esprima.Tests/AstToJavascriptTests.cs +++ b/test/Esprima.Tests/AstToJavascriptTests.cs @@ -25,7 +25,7 @@ public void ToJavascriptTest1() for (var elem of list) { } "); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program); + var code = AstToJavascript.ToJavascriptString(program); Assert.Equal("if(true){p();}switch(foo){case 'A':p();break;}switch(foo){default:p();break;}for(var a=[];;){}for(var elem of list){}", code); } @@ -47,7 +47,7 @@ function printTips() tips.forEach((tip, i) => console.log(`Tip ${ i}:` +tip)); }"); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program); + var code = AstToJavascript.ToJavascriptString(program); Assert.Equal("let tips=[\"Click on any AST node with a '+' to expand it\",\"Hovering over a node highlights the \\\r\n corresponding location in the source code\",\"Shift click on an AST node to expand the whole subtree\"];function printTips(){tips.forEach((tip,i)=>console.log((`Tip ${i}:`+tip)));}", code); } @@ -65,7 +65,7 @@ static get is() { } }"); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program); + var code = AstToJavascript.ToJavascriptString(program); Assert.Equal("export class aa extends HTMLElement{constructor(a,b){super(a);this._div=(document.createElement('div'));}static get is(){return 'aa';}}", code); } @@ -129,7 +129,7 @@ export function checkSecurityAnswerCodeDirect(result) { source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program, true); + var code = AstToJavascript.ToJavascriptString(program, true); var expected = @"import { MccDialog } from '../mccDialogHandler'; import { commonClient, bb as f } from '../commonClient/commonClient'; @@ -210,7 +210,7 @@ public void ToJavascriptTest5() source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program, true); + var code = AstToJavascript.ToJavascriptString(program, true); var expected = @"(function() { 'use strict'; @@ -241,7 +241,7 @@ public void ToJavascriptTest6() source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program); + var code = AstToJavascript.ToJavascriptString(program); Assert.Equal("function _createClass(Constructor,protoProps,staticProps){if(protoProps)_defineProperties(Constructor.prototype,protoProps);if(staticProps)_defineProperties(Constructor,staticProps);return Constructor;}", code); } @@ -252,7 +252,7 @@ public void ToJavascriptTest7() { }"); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program); + var code = AstToJavascript.ToJavascriptString(program); Assert.Equal("if(((x?((a.nodeName.toLowerCase())===f):(1===a.nodeType))&&(++d))&&(p&&((i=((o=(a[S]||(a[S]={})))[a.uniqueID]||(o[a.uniqueID]={})))[h]=[k,d]),a===e)){}", code); } @@ -271,7 +271,7 @@ class a extends b { } "); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program); + var code = AstToJavascript.ToJavascriptString(program); Assert.Equal("class a extends b{constructor(){super();this.g=1;}q=1;r='cc';}", code); } @@ -282,7 +282,7 @@ public void ToJavascriptTest9() d = (s = (r = (i = (o = (a = c)[S] || (a[S] = {}))[a.uniqueID] || (o[a.uniqueID] = {}))[h] || [])[0] === k && r[1]) && r[2], a = s && c.childNodes[s]; "); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program); + var code = AstToJavascript.ToJavascriptString(program); Assert.Equal("d=((s=(((r=((i=((o=((a=c)[S]||(a[S]={})))[a.uniqueID]||(o[a.uniqueID]={})))[h]||[]))[0]===k)&&r[1]))&&r[2]),a=(s&&c.childNodes[s]);", code); } @@ -293,7 +293,7 @@ public void ToJavascriptTest10() m = (z.document, !!v.documentElement && !!v.head && 'function' == typeof v.addEventListener && v.createElement, ~a.indexOf('MSIE') || a.indexOf('Trident/'), '___FONT_AWESOME___') "); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program); + var code = AstToJavascript.ToJavascriptString(program); Assert.Equal("m=(z.document,(((!!(v.documentElement))&&(!!(v.head)))&&('function'==(typeof (v.addEventListener))))&&v.createElement,(~(a.indexOf('MSIE')))||(a.indexOf('Trident/')),'___FONT_AWESOME___');", code); } @@ -315,7 +315,7 @@ public void ToJavascriptTest11() }(); "); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program); + var code = AstToJavascript.ToJavascriptString(program); Assert.Equal("var h=(c.navigator||{}).userAgent,a=((void 0)===h?'':h),z=c,v=l,m=(z.document,(((!!(v.documentElement))&&(!!(v.head)))&&('function'==(typeof (v.addEventListener))))&&v.createElement,(~(a.indexOf('MSIE')))||(a.indexOf('Trident/')),'___FONT_AWESOME___'),e=((function(){try {return !0;} catch(c){return !1;}})());", code); } @@ -328,7 +328,7 @@ public void ToJavascriptTest12() } "); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program); + var code = AstToJavascript.ToJavascriptString(program); Assert.Equal("var a={children:(b=O,'g'===b.tag?b.children:[b])};", code); } @@ -345,7 +345,7 @@ public void ToJavascriptTest13() } else h = e.HttpRequest.responseText; "); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program); + var code = AstToJavascript.ToJavascriptString(program); Assert.Equal("if(e.IsWebService)if(h=e.HttpRequest.responseXML,'undefined'==(typeof (h)))Trace.Write((('Error: '+e.UniqueId)+' data has no properties!')),m=(!0); else try {h.setProperty('SelectionLanguage','XPath');} catch(l){Trace.Write('Error: data.setProperty(',SelectionLanguage,', ',XPath,') because '+l.message);} else h=e.HttpRequest.responseText;", code); } @@ -365,7 +365,7 @@ public void ToJavascriptTest14() source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program, true); + var code = AstToJavascript.ToJavascriptString(program, true); var expected = @"function tt(t,r) { var n,e,i = (b(t)),s = (b(r)); @@ -390,7 +390,7 @@ public void ToJavascriptTest15() h='M'+(+new Date).toString(36) "); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program); + var code = AstToJavascript.ToJavascriptString(program); Assert.Equal("h=('M'+((+(new Date)).toString(36)));", code); } @@ -405,7 +405,7 @@ public void ToJavascriptTest16() }; "); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program); + var code = AstToJavascript.ToJavascriptString(program); Assert.Equal("input.onchange=(async e=>{const files=await readFiles(input.files,readMode);document.body.removeChild(input);resolve(files);});", code); } @@ -416,7 +416,7 @@ public void ToJavascriptTest17() export const Base = LegacyElementMixin(HTMLElement).prototype; "); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program); + var code = AstToJavascript.ToJavascriptString(program); Assert.Equal("export const Base=(LegacyElementMixin(HTMLElement)).prototype;", code); } @@ -427,7 +427,7 @@ public void ToJavascriptTest18() let {is} = getIsExtends(element); "); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program); + var code = AstToJavascript.ToJavascriptString(program); Assert.Equal("let {is}=(getIsExtends(element));", code); } @@ -439,7 +439,7 @@ public void ToJavascriptTest19() (window['ShadyDOM'] && window['ShadyDOM']['wrap']) || (node => node); "); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program); + var code = AstToJavascript.ToJavascriptString(program); Assert.Equal("export const wrap=((window['ShadyDOM']&&window['ShadyDOM']['wrap'])||(node=>node));", code); } @@ -449,7 +449,7 @@ public void ToJavascriptTest20() var parser = new JavaScriptParser(@" export {}"); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program); + var code = AstToJavascript.ToJavascriptString(program); Assert.Equal("export{};", code); } @@ -462,7 +462,7 @@ public void ToJavascriptTest21() })(); "); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program); + var code = AstToJavascript.ToJavascriptString(program); Assert.Equal("(()=>{mutablePropertyChange=MutableData._mutablePropertyChange;})();", code); } @@ -476,7 +476,7 @@ public void ToJavascriptTest22() }()) "); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program); + var code = AstToJavascript.ToJavascriptString(program); Assert.Equal("var Ol,jl=(new((function(){var l,h,z;return l=c;})()));", code); } @@ -493,7 +493,7 @@ public void ToJavascriptTest23() "); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program); + var code = AstToJavascript.ToJavascriptString(program); Assert.Equal("[y,{[Symbol.iterator]:(function(){return b;}),a:5}];", code); } @@ -515,7 +515,7 @@ class A { source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program, true); + var code = AstToJavascript.ToJavascriptString(program, true); var expected = @"class A { *[Symbol.iterator]() { @@ -545,7 +545,7 @@ public void ToJavascriptTest25() source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program, true); + var code = AstToJavascript.ToJavascriptString(program, true); var expected = @"var i = ((function e(i) { var r = n[i]; @@ -578,7 +578,7 @@ public void ToJavascriptTest26() source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = AstToJavascriptConverter.ToJavascript(program, true); + var code = AstToJavascript.ToJavascriptString(program, true); Assert.Equal(source, code); } } diff --git a/test/Esprima.Tests/Fixtures.cs b/test/Esprima.Tests/Fixtures.cs index 34ae467b..f2d7daa3 100644 --- a/test/Esprima.Tests/Fixtures.cs +++ b/test/Esprima.Tests/Fixtures.cs @@ -28,12 +28,12 @@ function p() {} private static string ParseAndFormat(SourceType sourceType, string source, ParserOptions parserOptions, Func parserFactory, - AstJson.IConverter converter, AstJson.Options conversionOptions) + AstToJsonConverter.Factory converterFactory, AstJson.Options conversionOptions) { var parser = parserFactory(source, parserOptions); var program = sourceType == SourceType.Script ? (Program) parser.ParseScript() : parser.ParseModule(); - return program.ToJsonString(conversionOptions, indent: " ", converter); + return program.ToJsonString(conversionOptions, indent: " ", converterFactory); } private static bool CompareTreesInternal(JObject actualJObject, JObject expectedJObject, FixtureMetadata metadata) @@ -87,13 +87,13 @@ private static void CompareTreesAndAssert(string actual, string expected, Fixtur [MemberData(nameof(SourceFiles), "Fixtures")] public void ExecuteTestCase(string fixture) { - var (parserOptions, parserFactory, converter) = fixture.StartsWith("JSX") + var (parserOptions, parserFactory, converterFactory) = fixture.StartsWith("JSX") ? (new JsxParserOptions(), (src, opts) => new JsxParser(src, (JsxParserOptions) opts), - JsxAstToJsonConverter.Default) + (writer, options) => new JsxAstToJsonConverter(writer, options)) : (new ParserOptions(), new Func((src, opts) => new JavaScriptParser(src, opts)), - AstToJsonConverter.Default); + new AstToJsonConverter.Factory((writer, options) => new AstToJsonConverter(writer, options))); parserOptions.Tokens = true; @@ -155,7 +155,7 @@ public void ExecuteTestCase(string fixture) expected = File.ReadAllText(moduleFilePath); if (WriteBackExpectedTree && !metadata.ConversionOptions.TestCompatibilityMode) { - var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, converter, metadata.ConversionOptions); + var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, converterFactory, metadata.ConversionOptions); if (!CompareTrees(actual, expected, metadata)) File.WriteAllText(moduleFilePath, actual); } @@ -165,7 +165,7 @@ public void ExecuteTestCase(string fixture) expected = File.ReadAllText(treeFilePath); if (WriteBackExpectedTree && !metadata.ConversionOptions.TestCompatibilityMode) { - var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, converter, metadata.ConversionOptions); + var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, converterFactory, metadata.ConversionOptions); if (!CompareTrees(actual, expected, metadata)) File.WriteAllText(treeFilePath, actual); } @@ -176,7 +176,7 @@ public void ExecuteTestCase(string fixture) expected = File.ReadAllText(failureFilePath); if (WriteBackExpectedTree && !metadata.ConversionOptions.TestCompatibilityMode) { - var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, converter, metadata.ConversionOptions); + var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, converterFactory, metadata.ConversionOptions); if (!CompareTrees(actual, expected, metadata)) File.WriteAllText(failureFilePath, actual); } @@ -196,7 +196,7 @@ public void ExecuteTestCase(string fixture) { parserOptions.Tolerant = true; - var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, converter, metadata.ConversionOptions); + var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, converterFactory, metadata.ConversionOptions); CompareTreesAndAssert(actual, expected, metadata); } else @@ -204,7 +204,7 @@ public void ExecuteTestCase(string fixture) parserOptions.Tolerant = false; // TODO: check the accuracy of the message and of the location - Assert.Throws(() => ParseAndFormat(sourceType, script, parserOptions, parserFactory, converter, metadata.ConversionOptions)); + Assert.Throws(() => ParseAndFormat(sourceType, script, parserOptions, parserFactory, converterFactory, metadata.ConversionOptions)); } } @@ -282,9 +282,11 @@ public void CommentsAreParsed() private sealed class FixtureMetadata { public static readonly FixtureMetadata Default = new FixtureMetadata( - AstJson.Options.Default - .WithIncludingLineColumn(true) - .WithIncludingRange(true), + AstJson.Options.Default with + { + IncludingLineColumn = true, + IncludingRange = true + }, includesLocationSource: false, ignoresRegex: false); @@ -321,16 +323,12 @@ public static Dictionary ReadMetadata() private static FixtureMetadata CreateFrom(HashSet flags) { - var conversionOptions = AstJson.Options.Default; - - if (flags.Contains("IncludesLocation")) - conversionOptions = conversionOptions.WithIncludingLineColumn(true); - - if (flags.Contains("IncludesRange")) - conversionOptions = conversionOptions.WithIncludingRange(true); - - if (flags.Contains("BorrowedFixture")) - conversionOptions = conversionOptions.WithTestCompatibilityMode(true); + var conversionOptions = AstJson.Options.Default with + { + IncludingLineColumn = flags.Contains("IncludesLocation"), + IncludingRange = flags.Contains("IncludesRange"), + TestCompatibilityMode = flags.Contains("BorrowedFixture") + }; var includesLocationSource = flags.Contains("IncludesLocationSource"); var ignoresRegex = flags.Contains("IgnoresRegex"); From c887231bb42174362468b4dda14d3b9e70ba3309 Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Wed, 6 Jul 2022 22:52:17 +0200 Subject: [PATCH 13/23] inherit from AstVisitor, eliminate ParentStack --- src/Esprima/Utils/AstToJavascriptConverter.cs | 575 ++++++++---------- src/Esprima/Utils/AstToJsonConverter.cs | 7 +- 2 files changed, 239 insertions(+), 343 deletions(-) diff --git a/src/Esprima/Utils/AstToJavascriptConverter.cs b/src/Esprima/Utils/AstToJavascriptConverter.cs index bf840642..d892a256 100644 --- a/src/Esprima/Utils/AstToJavascriptConverter.cs +++ b/src/Esprima/Utils/AstToJavascriptConverter.cs @@ -4,7 +4,7 @@ namespace Esprima.Utils; -public class AstToJavascriptConverter +public class AstToJavascriptConverter : AstVisitor { public delegate AstToJavascriptConverter Factory(TextWriter writer, AstToJavascript.Options options); @@ -13,11 +13,9 @@ public class AstToJavascriptConverter public readonly bool _beautify; public readonly string _indent; + private Node? _parentNode, _currentNode; private int _indentionLevel = 0; - private readonly List _parentStack = new List(); - protected IReadOnlyList ParentStack => _parentStack; - public AstToJavascriptConverter(TextWriter writer, AstToJavascript.Options options) { _writer = writer ?? ThrowArgumentNullException(nameof(writer)); @@ -32,6 +30,8 @@ public AstToJavascriptConverter(TextWriter writer, AstToJavascript.Options optio _indent = options.Indent ?? " "; } + protected Node? ParentNode => _parentNode; + protected void Append(string text) { _writer.Write(text); @@ -74,265 +74,40 @@ protected void DecreaseIndent() _indentionLevel--; } - /// - /// Returns parent node at specified position. - /// - /// Zero index value returns current node; one corresponds to direct - /// parent of current node. - protected Node? TryGetParentAt(int offset) - { - if (_parentStack.Count < offset + 1) - { - return null; - } - - return _parentStack[_parentStack.Count - 1 - offset]; - } - public void Convert(Node node) { Visit(node ?? ThrowArgumentNullException(nameof(node))); } - public virtual void Visit(Node node) - { - _parentStack.Add(node); - - switch (node.Type) - { - case Nodes.AssignmentExpression: - VisitAssignmentExpression(node.As()); - break; - case Nodes.ArrayExpression: - VisitArrayExpression(node.As()); - break; - case Nodes.BlockStatement: - VisitBlockStatement(node.As()); - break; - case Nodes.BinaryExpression: - VisitBinaryExpression(node.As()); - break; - case Nodes.BreakStatement: - VisitBreakStatement(node.As()); - break; - case Nodes.CallExpression: - VisitCallExpression(node.As()); - break; - case Nodes.CatchClause: - VisitCatchClause(node.As()); - break; - case Nodes.ConditionalExpression: - VisitConditionalExpression(node.As()); - break; - case Nodes.ContinueStatement: - VisitContinueStatement(node.As()); - break; - case Nodes.DoWhileStatement: - VisitDoWhileStatement(node.As()); - break; - case Nodes.DebuggerStatement: - VisitDebuggerStatement(node.As()); - break; - case Nodes.EmptyStatement: - VisitEmptyStatement(node.As()); - break; - case Nodes.ExpressionStatement: - VisitExpressionStatement(node.As()); - break; - case Nodes.ForStatement: - VisitForStatement(node.As()); - break; - case Nodes.ForInStatement: - VisitForInStatement(node.As()); - break; - case Nodes.FunctionDeclaration: - VisitFunctionDeclaration(node.As()); - break; - case Nodes.FunctionExpression: - VisitFunctionExpression(node.As()); - break; - case Nodes.Identifier: - VisitIdentifier(node.As()); - break; - case Nodes.IfStatement: - VisitIfStatement(node.As()); - break; - case Nodes.Import: - VisitImport(node.As()); - break; - case Nodes.Literal: - VisitLiteral(node.As()); - break; - case Nodes.LabeledStatement: - VisitLabeledStatement(node.As()); - break; - case Nodes.LogicalExpression: - VisitBinaryExpression(node.As()); - break; - case Nodes.MemberExpression: - VisitMemberExpression(node.As()); - break; - case Nodes.NewExpression: - VisitNewExpression(node.As()); - break; - case Nodes.ObjectExpression: - VisitObjectExpression(node.As()); - break; - case Nodes.Program: - VisitProgram(node.As()); - break; - case Nodes.Property: - VisitProperty(node.As()); - break; - case Nodes.PropertyDefinition: - VisitPropertyDefinition(node.As()); - break; - case Nodes.RestElement: - VisitRestElement(node.As()); - break; - case Nodes.ReturnStatement: - VisitReturnStatement(node.As()); - break; - case Nodes.SequenceExpression: - VisitSequenceExpression(node.As()); - break; - case Nodes.SwitchStatement: - VisitSwitchStatement(node.As()); - break; - case Nodes.SwitchCase: - VisitSwitchCase(node.As()); - break; - case Nodes.TemplateElement: - VisitTemplateElement(node.As()); - break; - case Nodes.TemplateLiteral: - VisitTemplateLiteral(node.As()); - break; - case Nodes.ThisExpression: - VisitThisExpression(node.As()); - break; - case Nodes.ThrowStatement: - VisitThrowStatement(node.As()); - break; - case Nodes.TryStatement: - VisitTryStatement(node.As()); - break; - case Nodes.UnaryExpression: - VisitUnaryExpression(node.As()); - break; - case Nodes.UpdateExpression: - VisitUpdateExpression(node.As()); - break; - case Nodes.VariableDeclaration: - VisitVariableDeclaration(node.As()); - break; - case Nodes.VariableDeclarator: - VisitVariableDeclarator(node.As()); - break; - case Nodes.WhileStatement: - VisitWhileStatement(node.As()); - break; - case Nodes.WithStatement: - VisitWithStatement(node.As()); - break; - case Nodes.ArrayPattern: - VisitArrayPattern(node.As()); - break; - case Nodes.AssignmentPattern: - VisitAssignmentPattern(node.As()); - break; - case Nodes.SpreadElement: - VisitSpreadElement(node.As()); - break; - case Nodes.ObjectPattern: - VisitObjectPattern(node.As()); - break; - case Nodes.ArrowParameterPlaceHolder: - VisitArrowParameterPlaceHolder(node.As()); - break; - case Nodes.MetaProperty: - VisitMetaProperty(node.As()); - break; - case Nodes.Super: - VisitSuper(node.As()); - break; - case Nodes.TaggedTemplateExpression: - VisitTaggedTemplateExpression(node.As()); - break; - case Nodes.YieldExpression: - VisitYieldExpression(node.As()); - break; - case Nodes.ArrowFunctionExpression: - VisitArrowFunctionExpression(node.As()); - break; - case Nodes.AwaitExpression: - VisitAwaitExpression(node.As()); - break; - case Nodes.ClassBody: - VisitClassBody(node.As()); - break; - case Nodes.ClassDeclaration: - VisitClassDeclaration(node.As()); - break; - case Nodes.ForOfStatement: - VisitForOfStatement(node.As()); - break; - case Nodes.MethodDefinition: - VisitMethodDefinition(node.As()); - break; - case Nodes.ImportSpecifier: - VisitImportSpecifier(node.As()); - break; - case Nodes.ImportDefaultSpecifier: - VisitImportDefaultSpecifier(node.As()); - break; - case Nodes.ImportNamespaceSpecifier: - VisitImportNamespaceSpecifier(node.As()); - break; - case Nodes.ImportDeclaration: - VisitImportDeclaration(node.As()); - break; - case Nodes.ExportSpecifier: - VisitExportSpecifier(node.As()); - break; - case Nodes.ExportNamedDeclaration: - VisitExportNamedDeclaration(node.As()); - break; - case Nodes.ExportAllDeclaration: - VisitExportAllDeclaration(node.As()); - break; - case Nodes.ExportDefaultDeclaration: - VisitExportDefaultDeclaration(node.As()); - break; - case Nodes.ClassExpression: - VisitClassExpression(node.As()); - break; - case Nodes.ChainExpression: - VisitChainExpression(node.As()); - break; - default: - VisitUnknownNode(node); - break; - } - _parentStack.RemoveAt(_parentStack.Count - 1); - } - - protected virtual void VisitProgram(Program program) + public override object? Visit(Node node) { - VisitNodeList(program.Body, appendAtEnd: ";", addLineBreaks: true); - } + var originalParentNode = _parentNode; + _parentNode = _currentNode; + _currentNode = node; + + var result = base.Visit(node); + + _currentNode = _parentNode; + _parentNode = originalParentNode; - protected virtual void VisitUnknownNode(Node node) + return result; + } + + protected internal override object? VisitProgram(Program program) { - throw new NotImplementedException($"AST visitor doesn't support nodes of type {node.Type}, you can override VisitUnknownNode to handle this case."); + VisitNodeList(program.Body, appendAtEnd: ";", addLineBreaks: true); + + return program; } - protected virtual void VisitChainExpression(ChainExpression chainExpression) + protected internal override object? VisitChainExpression(ChainExpression chainExpression) { Visit(chainExpression.Expression); + + return chainExpression; } - protected virtual void VisitCatchClause(CatchClause catchClause) + protected internal override object? VisitCatchClause(CatchClause catchClause) { Append("("); if (catchClause.Param is not null) @@ -341,9 +116,11 @@ protected virtual void VisitCatchClause(CatchClause catchClause) } Append(")"); Visit(catchClause.Body); + + return catchClause; } - protected virtual void VisitFunctionDeclaration(FunctionDeclaration functionDeclaration) + protected internal override object? VisitFunctionDeclaration(FunctionDeclaration functionDeclaration) { if (functionDeclaration.Async) { @@ -364,31 +141,39 @@ protected virtual void VisitFunctionDeclaration(FunctionDeclaration functionDecl Append(")"); AppendBeautificationSpace(); Visit(functionDeclaration.Body); + + return functionDeclaration; } - protected virtual void VisitWithStatement(WithStatement withStatement) + protected internal override object? VisitWithStatement(WithStatement withStatement) { Append("with("); Visit(withStatement.Object); Append(")"); Visit(withStatement.Body); + + return withStatement; } - protected virtual void VisitWhileStatement(WhileStatement whileStatement) + protected internal override object? VisitWhileStatement(WhileStatement whileStatement) { Append("while("); Visit(whileStatement.Test); Append(")"); Visit(whileStatement.Body); + + return whileStatement; } - protected virtual void VisitVariableDeclaration(VariableDeclaration variableDeclaration) + protected internal override object? VisitVariableDeclaration(VariableDeclaration variableDeclaration) { Append(variableDeclaration.Kind.ToString().ToLower() + " "); VisitNodeList(variableDeclaration.Declarations, appendSeperatorString: ","); + + return variableDeclaration; } - protected virtual void VisitTryStatement(TryStatement tryStatement) + protected internal override object? VisitTryStatement(TryStatement tryStatement) { Append("try "); Visit(tryStatement.Block); @@ -402,16 +187,20 @@ protected virtual void VisitTryStatement(TryStatement tryStatement) Append(" finally"); Visit(tryStatement.Finalizer); } + + return tryStatement; } - protected virtual void VisitThrowStatement(ThrowStatement throwStatement) + protected internal override object? VisitThrowStatement(ThrowStatement throwStatement) { Append("throw "); Visit(throwStatement.Argument); Append(";"); + + return throwStatement; } - protected virtual void VisitSwitchStatement(SwitchStatement switchStatement) + protected internal override object? VisitSwitchStatement(SwitchStatement switchStatement) { Append("switch("); Visit(switchStatement.Discriminant); @@ -430,9 +219,11 @@ protected virtual void VisitSwitchStatement(SwitchStatement switchStatement) AppendBeautificationIndent(); Append("}"); + + return switchStatement; } - protected virtual void VisitSwitchCase(SwitchCase switchCase) + protected internal override object? VisitSwitchCase(SwitchCase switchCase) { if (switchCase.Test is not null) { @@ -452,9 +243,11 @@ protected virtual void VisitSwitchCase(SwitchCase switchCase) VisitNodeList(switchCase.Consequent, appendAtEnd: ";", addLineBreaks: true); DecreaseIndent(); + + return switchCase; } - protected virtual void VisitReturnStatement(ReturnStatement returnStatement) + protected internal override object? VisitReturnStatement(ReturnStatement returnStatement) { Append("return"); if (returnStatement.Argument is not null) @@ -463,16 +256,20 @@ protected virtual void VisitReturnStatement(ReturnStatement returnStatement) Visit(returnStatement.Argument); } Append(";"); + + return returnStatement; } - protected virtual void VisitLabeledStatement(LabeledStatement labeledStatement) + protected internal override object? VisitLabeledStatement(LabeledStatement labeledStatement) { Visit(labeledStatement.Label); Append(":"); Visit(labeledStatement.Body); + + return labeledStatement; } - protected virtual void VisitIfStatement(IfStatement ifStatement) + protected internal override object? VisitIfStatement(IfStatement ifStatement) { Append("if"); AppendBeautificationSpace(); @@ -520,19 +317,25 @@ protected virtual void VisitIfStatement(IfStatement ifStatement) DecreaseIndent(); } } + + return ifStatement; } - protected virtual void VisitEmptyStatement(EmptyStatement emptyStatement) + protected internal override object? VisitEmptyStatement(EmptyStatement emptyStatement) { Append(";"); + + return emptyStatement; } - protected virtual void VisitDebuggerStatement(DebuggerStatement debuggerStatement) + protected internal override object? VisitDebuggerStatement(DebuggerStatement debuggerStatement) { Append("debugger"); + + return debuggerStatement; } - protected virtual void VisitExpressionStatement(ExpressionStatement expressionStatement) + protected internal override object? VisitExpressionStatement(ExpressionStatement expressionStatement) { if (expressionStatement.Expression is CallExpression callExpression && !(callExpression.Callee is Identifier)) { @@ -567,9 +370,11 @@ protected virtual void VisitExpressionStatement(ExpressionStatement expressionSt Append(")"); } } + + return expressionStatement; } - protected virtual void VisitForStatement(ForStatement forStatement) + protected internal override object? VisitForStatement(ForStatement forStatement) { Append("for("); if (forStatement.Init is not null) @@ -606,9 +411,11 @@ protected virtual void VisitForStatement(ForStatement forStatement) { DecreaseIndent(); } + + return forStatement; } - protected virtual void VisitForInStatement(ForInStatement forInStatement) + protected internal override object? VisitForInStatement(ForInStatement forInStatement) { Append("for("); Visit(forInStatement.Left); @@ -632,9 +439,11 @@ protected virtual void VisitForInStatement(ForInStatement forInStatement) { DecreaseIndent(); } + + return forInStatement; } - protected virtual void VisitDoWhileStatement(DoWhileStatement doWhileStatement) + protected internal override object? VisitDoWhileStatement(DoWhileStatement doWhileStatement) { Append("do "); Visit(doWhileStatement.Body); @@ -645,9 +454,11 @@ protected virtual void VisitDoWhileStatement(DoWhileStatement doWhileStatement) Append("while("); Visit(doWhileStatement.Test); Append(")"); + + return doWhileStatement; } - protected virtual void VisitArrowFunctionExpression(ArrowFunctionExpression arrowFunctionExpression) + protected internal override object? VisitArrowFunctionExpression(ArrowFunctionExpression arrowFunctionExpression) { if (arrowFunctionExpression.Async) { @@ -682,10 +493,26 @@ protected virtual void VisitArrowFunctionExpression(ArrowFunctionExpression arro { Append(")"); } + + return arrowFunctionExpression; } - protected virtual void VisitUnaryExpression(UnaryExpression unaryExpression) + protected internal override object? VisitUnaryExpression(UnaryExpression unaryExpression) { + if (unaryExpression is UpdateExpression updateExpression) + { + if (updateExpression.Prefix) + { + Append(UnaryExpression.GetUnaryOperatorToken(updateExpression.Operator)); + } + Visit(updateExpression.Argument); + if (!updateExpression.Prefix) + { + Append(UnaryExpression.GetUnaryOperatorToken(updateExpression.Operator)); + } + } + else + { var op = UnaryExpression.GetUnaryOperatorToken(unaryExpression.Operator); if (unaryExpression.Prefix) { @@ -708,30 +535,24 @@ protected virtual void VisitUnaryExpression(UnaryExpression unaryExpression) } } - protected virtual void VisitUpdateExpression(UpdateExpression updateExpression) - { - if (updateExpression.Prefix) - { - Append(UnaryExpression.GetUnaryOperatorToken(updateExpression.Operator)); - } - Visit(updateExpression.Argument); - if (!updateExpression.Prefix) - { - Append(UnaryExpression.GetUnaryOperatorToken(updateExpression.Operator)); + return unaryExpression; } - } - protected virtual void VisitThisExpression(ThisExpression thisExpression) + protected internal override object? VisitThisExpression(ThisExpression thisExpression) { Append("this"); + + return thisExpression; } - protected virtual void VisitSequenceExpression(SequenceExpression sequenceExpression) + protected internal override object? VisitSequenceExpression(SequenceExpression sequenceExpression) { VisitNodeList(sequenceExpression.Expressions, appendSeperatorString: _beautify ? ", " : ","); + + return sequenceExpression; } - protected virtual void VisitObjectExpression(ObjectExpression objectExpression) + protected internal override object? VisitObjectExpression(ObjectExpression objectExpression) { Append("{"); if (objectExpression.Properties.Count > 0) @@ -748,9 +569,11 @@ protected virtual void VisitObjectExpression(ObjectExpression objectExpression) AppendBeautificationIndent(); } Append("}"); + + return objectExpression; } - protected virtual void VisitNewExpression(NewExpression newExpression) + protected internal override object? VisitNewExpression(NewExpression newExpression) { Append("new"); if (ExpressionNeedsBrackets(newExpression.Callee)) @@ -772,9 +595,11 @@ protected virtual void VisitNewExpression(NewExpression newExpression) VisitNodeList(newExpression.Arguments, appendSeperatorString: ","); Append(")"); } + + return newExpression; } - protected virtual void VisitMemberExpression(MemberExpression memberExpression) + protected internal override object? VisitMemberExpression(MemberExpression memberExpression) { if (ExpressionNeedsBrackets(memberExpression.Object) || (memberExpression.Object is Literal l && l.TokenType != TokenType.StringLiteral)) { @@ -791,7 +616,7 @@ protected virtual void VisitMemberExpression(MemberExpression memberExpression) } else { - if (TryGetParentAt(0) is ChainExpression) + if (_parentNode is ChainExpression) Append("?"); Append("."); } @@ -800,49 +625,57 @@ protected virtual void VisitMemberExpression(MemberExpression memberExpression) { Append("]"); } + + return memberExpression; } - protected virtual void VisitLiteral(Literal literal) + protected internal override object? VisitLiteral(Literal literal) { Append(literal.Raw); + + return literal; } - protected virtual void VisitIdentifier(Identifier identifier) + protected internal override object? VisitIdentifier(Identifier identifier) { Append(identifier.Name!); + + return identifier; } - protected virtual void VisitFunctionExpression(IFunction function) + protected internal override object? VisitFunctionExpression(FunctionExpression functionExpression) { - var isParentMethod = TryGetParentAt(1) is MethodDefinition; + var isParentMethod = _parentNode is MethodDefinition; if (!isParentMethod) { - if (function.Async) + if (functionExpression.Async) { Append("async "); } - if (!(TryGetParentAt(1) is MethodDefinition)) + if (_parentNode is not MethodDefinition) { Append("function"); } - if (function.Generator) + if (functionExpression.Generator) { Append("*"); } } - if (function.Id is not null) + if (functionExpression.Id is not null) { Append(" "); - Visit(function.Id); + Visit(functionExpression.Id); } Append("("); - VisitNodeList(function.Params, appendSeperatorString: ","); + VisitNodeList(functionExpression.Params, appendSeperatorString: ","); Append(")"); AppendBeautificationSpace(); - Visit(function.Body); + Visit(functionExpression.Body); + + return functionExpression; } - protected virtual void VisitClassExpression(ClassExpression classExpression) + protected internal override object? VisitClassExpression(ClassExpression classExpression) { Append("class "); if (classExpression.Id is not null) @@ -869,24 +702,30 @@ protected virtual void VisitClassExpression(ClassExpression classExpression) AppendBeautificationIndent(); Append("}"); + + return classExpression; } - protected virtual void VisitExportDefaultDeclaration(ExportDefaultDeclaration exportDefaultDeclaration) + protected internal override object? VisitExportDefaultDeclaration(ExportDefaultDeclaration exportDefaultDeclaration) { Append("export default "); if (exportDefaultDeclaration.Declaration is not null) { Visit(exportDefaultDeclaration.Declaration); } + + return exportDefaultDeclaration; } - protected virtual void VisitExportAllDeclaration(ExportAllDeclaration exportAllDeclaration) + protected internal override object? VisitExportAllDeclaration(ExportAllDeclaration exportAllDeclaration) { Append("export*from"); Visit(exportAllDeclaration.Source); + + return exportAllDeclaration; } - protected virtual void VisitExportNamedDeclaration(ExportNamedDeclaration exportNamedDeclaration) + protected internal override object? VisitExportNamedDeclaration(ExportNamedDeclaration exportNamedDeclaration) { Append("export"); if (exportNamedDeclaration.Declaration is not null) @@ -910,9 +749,10 @@ protected virtual void VisitExportNamedDeclaration(ExportNamedDeclaration export Append("{}"); } + return exportNamedDeclaration; } - protected virtual void VisitExportSpecifier(ExportSpecifier exportSpecifier) + protected internal override object? VisitExportSpecifier(ExportSpecifier exportSpecifier) { Visit(exportSpecifier.Local); if (exportSpecifier.Local != exportSpecifier.Exported) @@ -920,16 +760,20 @@ protected virtual void VisitExportSpecifier(ExportSpecifier exportSpecifier) Append(" as "); Visit(exportSpecifier.Exported); } + + return exportSpecifier; } - protected virtual void VisitImport(Import import) + protected internal override object? VisitImport(Import import) { Append("import("); Visit(import.Source); Append(")"); + + return import; } - protected virtual void VisitImportDeclaration(ImportDeclaration importDeclaration) + protected internal override object? VisitImportDeclaration(ImportDeclaration importDeclaration) { Append("import "); var firstSpecifier = importDeclaration.Specifiers.FirstOrDefault(); @@ -974,20 +818,26 @@ protected virtual void VisitImportDeclaration(ImportDeclaration importDeclaratio Append(" from "); } Visit(importDeclaration.Source); + + return importDeclaration; } - protected virtual void VisitImportNamespaceSpecifier(ImportNamespaceSpecifier importNamespaceSpecifier) + protected internal override object? VisitImportNamespaceSpecifier(ImportNamespaceSpecifier importNamespaceSpecifier) { Append("* as "); Visit(importNamespaceSpecifier.Local); + + return importNamespaceSpecifier; } - protected virtual void VisitImportDefaultSpecifier(ImportDefaultSpecifier importDefaultSpecifier) + protected internal override object? VisitImportDefaultSpecifier(ImportDefaultSpecifier importDefaultSpecifier) { Visit(importDefaultSpecifier.Local); + + return importDefaultSpecifier; } - protected virtual void VisitImportSpecifier(ImportSpecifier importSpecifier) + protected internal override object? VisitImportSpecifier(ImportSpecifier importSpecifier) { Visit(importSpecifier.Imported); if (importSpecifier.Local != importSpecifier.Imported) @@ -995,9 +845,11 @@ protected virtual void VisitImportSpecifier(ImportSpecifier importSpecifier) Append(" as "); Visit(importSpecifier.Local); } + + return importSpecifier; } - protected virtual void VisitMethodDefinition(MethodDefinition methodDefinition) + protected internal override object? VisitMethodDefinition(MethodDefinition methodDefinition) { if (methodDefinition.Static) { @@ -1037,9 +889,11 @@ protected virtual void VisitMethodDefinition(MethodDefinition methodDefinition) Append("]"); } Visit(methodDefinition.Value); + + return methodDefinition; } - protected virtual void VisitForOfStatement(ForOfStatement forOfStatement) + protected internal override object? VisitForOfStatement(ForOfStatement forOfStatement) { Append("for("); Visit(forOfStatement.Left); @@ -1063,9 +917,11 @@ protected virtual void VisitForOfStatement(ForOfStatement forOfStatement) { DecreaseIndent(); } + + return forOfStatement; } - protected virtual void VisitClassDeclaration(ClassDeclaration classDeclaration) + protected internal override object? VisitClassDeclaration(ClassDeclaration classDeclaration) { Append("class "); if (classDeclaration.Id is not null) @@ -1093,73 +949,88 @@ protected virtual void VisitClassDeclaration(ClassDeclaration classDeclaration) AppendBeautificationIndent(); Append("}"); + + return classDeclaration; } - protected virtual void VisitClassBody(ClassBody classBody) + protected internal override object? VisitClassBody(ClassBody classBody) { VisitNodeList(classBody.Body, addLineBreaks: true); + + return classBody; } - protected virtual void VisitYieldExpression(YieldExpression yieldExpression) + protected internal override object? VisitYieldExpression(YieldExpression yieldExpression) { Append("yield "); if (yieldExpression.Argument is not null) { Visit(yieldExpression.Argument); } + + return yieldExpression; } - protected virtual void VisitTaggedTemplateExpression(TaggedTemplateExpression taggedTemplateExpression) + protected internal override object? VisitTaggedTemplateExpression(TaggedTemplateExpression taggedTemplateExpression) { Visit(taggedTemplateExpression.Tag); Visit(taggedTemplateExpression.Quasi); + + return taggedTemplateExpression; } - protected virtual void VisitSuper(Super super) + protected internal override object? VisitSuper(Super super) { Append("super"); + + return super; } - protected virtual void VisitMetaProperty(MetaProperty metaProperty) + protected internal override object? VisitMetaProperty(MetaProperty metaProperty) { Visit(metaProperty.Meta); Append("."); Visit(metaProperty.Property); - } - protected virtual void VisitArrowParameterPlaceHolder(ArrowParameterPlaceHolder arrowParameterPlaceHolder) - { - VisitNodeList(arrowParameterPlaceHolder.Params); + return metaProperty; } - protected virtual void VisitObjectPattern(ObjectPattern objectPattern) + protected internal override object? VisitObjectPattern(ObjectPattern objectPattern) { Append("{"); VisitNodeList(objectPattern.Properties, appendSeperatorString: ","); Append("}"); + + return objectPattern; } - protected virtual void VisitSpreadElement(SpreadElement spreadElement) + protected internal override object? VisitSpreadElement(SpreadElement spreadElement) { Append("..."); Visit(spreadElement.Argument); + + return spreadElement; } - protected virtual void VisitAssignmentPattern(AssignmentPattern assignmentPattern) + protected internal override object? VisitAssignmentPattern(AssignmentPattern assignmentPattern) { Visit(assignmentPattern.Left); Append("="); Visit(assignmentPattern.Right); + + return assignmentPattern; } - protected virtual void VisitArrayPattern(ArrayPattern arrayPattern) + protected internal override object? VisitArrayPattern(ArrayPattern arrayPattern) { Append("["); VisitNodeList(arrayPattern.Elements, appendSeperatorString: ","); Append("]"); + + return arrayPattern; } - protected virtual void VisitVariableDeclarator(VariableDeclarator variableDeclarator) + protected internal override object? VisitVariableDeclarator(VariableDeclarator variableDeclarator) { Visit(variableDeclarator.Id); if (variableDeclarator.Init is not null) @@ -1177,9 +1048,11 @@ protected virtual void VisitVariableDeclarator(VariableDeclarator variableDeclar Append(")"); } } + + return variableDeclarator; } - protected virtual void VisitTemplateLiteral(TemplateLiteral templateLiteral) + protected internal override object? VisitTemplateLiteral(TemplateLiteral templateLiteral) { Append("`"); for (int n = 0; n < templateLiteral.Quasis.Count; n++) @@ -1193,20 +1066,26 @@ protected virtual void VisitTemplateLiteral(TemplateLiteral templateLiteral) } } Append("`"); + + return templateLiteral; } - protected virtual void VisitTemplateElement(TemplateElement templateElement) + protected internal override object? VisitTemplateElement(TemplateElement templateElement) { Append(templateElement.Value.Raw); + + return templateElement; } - protected virtual void VisitRestElement(RestElement restElement) + protected internal override object? VisitRestElement(RestElement restElement) { Append("..."); Visit(restElement.Argument); + + return restElement; } - protected virtual void VisitProperty(Property property) + protected internal override object? VisitProperty(Property property) { if (property.Key is MemberExpression || ExpressionNeedsBrackets(property.Key)) { @@ -1242,9 +1121,11 @@ protected virtual void VisitProperty(Property property) Append(")"); } } + + return property; } - protected virtual void VisitPropertyDefinition(PropertyDefinition propertyDefinition) + protected internal override object? VisitPropertyDefinition(PropertyDefinition propertyDefinition) { if (propertyDefinition.Static) { @@ -1273,15 +1154,19 @@ protected virtual void VisitPropertyDefinition(PropertyDefinition propertyDefini Visit(propertyDefinition.Value); } Append(";"); + + return propertyDefinition; } - protected virtual void VisitAwaitExpression(AwaitExpression awaitExpression) + protected internal override object? VisitAwaitExpression(AwaitExpression awaitExpression) { Append("await "); Visit(awaitExpression.Argument); + + return awaitExpression; } - protected virtual void VisitConditionalExpression(ConditionalExpression conditionalExpression) + protected internal override object? VisitConditionalExpression(ConditionalExpression conditionalExpression) { if (conditionalExpression.Test is AssignmentExpression) { @@ -1316,9 +1201,11 @@ protected virtual void VisitConditionalExpression(ConditionalExpression conditio { Append(")"); } + + return conditionalExpression; } - protected virtual void VisitCallExpression(CallExpression callExpression) + protected internal override object? VisitCallExpression(CallExpression callExpression) { if (ExpressionNeedsBrackets(callExpression.Callee)) { @@ -1332,9 +1219,11 @@ protected virtual void VisitCallExpression(CallExpression callExpression) Append("("); VisitNodeList(callExpression.Arguments, appendSeperatorString: ",", appendBracketsIfNeeded: true); Append(")"); + + return callExpression; } - protected virtual void VisitBinaryExpression(BinaryExpression binaryExpression) + protected internal override object? VisitBinaryExpression(BinaryExpression binaryExpression) { if (ExpressionNeedsBrackets(binaryExpression.Left)) { @@ -1372,16 +1261,20 @@ protected virtual void VisitBinaryExpression(BinaryExpression binaryExpression) { Append(")"); } + + return binaryExpression; } - protected virtual void VisitArrayExpression(ArrayExpression arrayExpression) + protected internal override object? VisitArrayExpression(ArrayExpression arrayExpression) { Append("["); VisitNodeList(arrayExpression.Elements, appendSeperatorString: ","); Append("]"); + + return arrayExpression; } - protected virtual void VisitAssignmentExpression(AssignmentExpression assignmentExpression) + protected internal override object? VisitAssignmentExpression(AssignmentExpression assignmentExpression) { if (assignmentExpression.Left is ObjectPattern) { @@ -1405,27 +1298,33 @@ protected virtual void VisitAssignmentExpression(AssignmentExpression assignment { Append(")"); } + + return assignmentExpression; } - protected virtual void VisitContinueStatement(ContinueStatement continueStatement) + protected internal override object? VisitContinueStatement(ContinueStatement continueStatement) { Append("continue "); if (continueStatement.Label is not null) { Visit(continueStatement.Label); } + + return continueStatement; } - protected virtual void VisitBreakStatement(BreakStatement breakStatement) + protected internal override object? VisitBreakStatement(BreakStatement breakStatement) { if (breakStatement.Label is not null) { Visit(breakStatement.Label); } Append("break"); + + return breakStatement; } - protected virtual void VisitBlockStatement(BlockStatement blockStatement) + protected internal override object? VisitBlockStatement(BlockStatement blockStatement) { Append("{"); @@ -1440,6 +1339,8 @@ protected virtual void VisitBlockStatement(BlockStatement blockStatement) AppendBeautificationIndent(); Append("}"); + + return blockStatement; } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/Esprima/Utils/AstToJsonConverter.cs b/src/Esprima/Utils/AstToJsonConverter.cs index 453d5a72..8a0565ba 100644 --- a/src/Esprima/Utils/AstToJsonConverter.cs +++ b/src/Esprima/Utils/AstToJsonConverter.cs @@ -512,11 +512,6 @@ public void Convert(Node node) 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)) @@ -1138,4 +1133,4 @@ public ImportCompat() : base(Nodes.Import) { } return yieldExpression; } -} +} \ No newline at end of file From 0ea888ab11c56e167cb95534678f77cb49979675 Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Thu, 7 Jul 2022 21:04:16 +0200 Subject: [PATCH 14/23] complete rework of code formatting and parenthesis handling + fix a ton of issues + implement missing bits --- .../Esprima.SourceGenerators.csproj | 1 + src/Esprima/Ast/ArrayExpression.cs | 3 + src/Esprima/Ast/ArrayPattern.cs | 3 + src/Esprima/Ast/ArrowFunctionExpression.cs | 5 +- src/Esprima/Ast/AssignmentExpression.cs | 3 +- src/Esprima/Ast/AssignmentPattern.cs | 3 + src/Esprima/Ast/CatchClause.cs | 2 +- src/Esprima/Ast/ChainExpression.cs | 2 +- src/Esprima/Ast/ClassBody.cs | 2 +- src/Esprima/Ast/ClassDeclaration.cs | 2 +- src/Esprima/Ast/ClassExpression.cs | 2 +- src/Esprima/Ast/ClassProperty.cs | 2 +- src/Esprima/Ast/ExportAllDeclaration.cs | 2 +- src/Esprima/Ast/ExportDefaultDeclaration.cs | 2 +- src/Esprima/Ast/ExportNamedDeclaration.cs | 3 + src/Esprima/Ast/ExportSpecifier.cs | 4 +- src/Esprima/Ast/ForInStatement.cs | 3 + src/Esprima/Ast/ForOfStatement.cs | 3 + src/Esprima/Ast/ForStatement.cs | 2 +- src/Esprima/Ast/FunctionDeclaration.cs | 3 + src/Esprima/Ast/FunctionExpression.cs | 3 + src/Esprima/Ast/IClass.cs | 1 + src/Esprima/Ast/IFunction.cs | 1 + src/Esprima/Ast/IProperty.cs | 1 + src/Esprima/Ast/ImportAttribute.cs | 2 +- src/Esprima/Ast/ImportSpecifier.cs | 2 +- src/Esprima/Ast/NodeExtensions.cs | 2 + src/Esprima/Ast/NodeList.cs | 8 + src/Esprima/Ast/ObjectExpression.cs | 3 + src/Esprima/Ast/ObjectPattern.cs | 3 + src/Esprima/Ast/Property.cs | 6 +- src/Esprima/Ast/RestElement.cs | 2 +- src/Esprima/Ast/VariableDeclaration.cs | 12 + src/Esprima/Ast/VariableDeclarator.cs | 2 +- src/Esprima/Esprima.csproj | 1 + src/Esprima/EsprimaExceptionHelper.cs | 11 +- src/Esprima/Scanner.cs | 2 +- src/Esprima/Utils/AstJson.cs | 2 +- src/Esprima/Utils/AstToJavascript.cs | 51 +- .../Utils/AstToJavascriptConverter.Enums.cs | 57 + src/Esprima/Utils/AstToJavascriptConverter.cs | 2212 +++++++++++------ src/Esprima/Utils/AstToJsonConverter.cs | 10 +- src/Esprima/Utils/EnumHelper.cs | 36 + src/Esprima/Utils/ExpressionHelper.cs | 153 ++ .../Utils/JavascriptTextWriter.Enums.cs | 119 + .../JavascriptTextWriter.WriteContext.cs | 88 + src/Esprima/Utils/JavascriptTextWriter.cs | 375 +++ src/Esprima/Utils/JsonTextWriter.cs | 7 +- src/Esprima/Utils/KnRJavascriptTextWriter.cs | 480 ++++ .../Compatibility/NullableAttributes.cs | 151 ++ 50 files changed, 3008 insertions(+), 847 deletions(-) create mode 100644 src/Esprima/Utils/AstToJavascriptConverter.Enums.cs create mode 100644 src/Esprima/Utils/EnumHelper.cs create mode 100644 src/Esprima/Utils/ExpressionHelper.cs create mode 100644 src/Esprima/Utils/JavascriptTextWriter.Enums.cs create mode 100644 src/Esprima/Utils/JavascriptTextWriter.WriteContext.cs create mode 100644 src/Esprima/Utils/JavascriptTextWriter.cs create mode 100644 src/Esprima/Utils/KnRJavascriptTextWriter.cs create mode 100644 src/Shared/Compatibility/NullableAttributes.cs 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/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/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..fe917682 100644 --- a/src/Esprima/EsprimaExceptionHelper.cs +++ b/src/Esprima/EsprimaExceptionHelper.cs @@ -1,37 +1,46 @@ -namespace Esprima +using System.Diagnostics.CodeAnalysis; + +namespace Esprima { 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 index 35ca9201..3d2d3d7e 100644 --- a/src/Esprima/Utils/AstJson.cs +++ b/src/Esprima/Utils/AstJson.cs @@ -70,7 +70,7 @@ public static void WriteJson(this Node node, TextWriter writer, Options options, public static void WriteJson(this Node node, JsonWriter writer, Options options, AstToJsonConverter.Factory? converterFactory = null) { - converterFactory ??= (writer, options) => new AstToJsonConverter(writer, options); + converterFactory ??= static (writer, options) => new AstToJsonConverter(writer, options); converterFactory(writer, options).Convert(node); } diff --git a/src/Esprima/Utils/AstToJavascript.cs b/src/Esprima/Utils/AstToJavascript.cs index 8628f322..d929eb04 100644 --- a/src/Esprima/Utils/AstToJavascript.cs +++ b/src/Esprima/Utils/AstToJavascript.cs @@ -7,44 +7,71 @@ public static class AstToJavascript public record class Options { public static readonly Options Default = new(); - internal static readonly Options DefaultWithBeautify = Default with { Beautify = true }; - - public bool Beautify { get; init; } - public string? Indent { get; init; } } public static string ToJavascriptString(this Node node, AstToJavascriptConverter.Factory? converterFactory = null) { - return ToJavascriptString(node, Options.Default, converterFactory); + JavascriptTextWriter.Factory writerFactory = static (writer, formattingOptions) => new JavascriptTextWriter(writer, formattingOptions); + return ToJavascriptString(node, writerFactory, JavascriptTextWriter.Options.Default, Options.Default, converterFactory); + } + + public static string ToJavascriptString(this Node node, KnRJavascriptTextWriter.Options formattingOptions, AstToJavascriptConverter.Factory? converterFactory = null) + { + JavascriptTextWriter.Factory writerFactory = static (writer, formattingOptions) => new KnRJavascriptTextWriter(writer, formattingOptions); + return ToJavascriptString(node, writerFactory, formattingOptions, Options.Default, converterFactory); } public static string ToJavascriptString(this Node node, bool beautify, AstToJavascriptConverter.Factory? converterFactory = null) { - return ToJavascriptString(node, beautify ? Options.DefaultWithBeautify : Options.Default, converterFactory); + if (beautify) + { + return ToJavascriptString(node, KnRJavascriptTextWriter.Options.Default, converterFactory); + } + else + { + return ToJavascriptString(node, converterFactory); + } } - public static string ToJavascriptString(this Node node, Options options, AstToJavascriptConverter.Factory? converterFactory = null) + public static string ToJavascriptString(this Node node, JavascriptTextWriter.Factory writerFactory, JavascriptTextWriter.Options formattingOptions, Options options, AstToJavascriptConverter.Factory? converterFactory = null) { + if (writerFactory is null) + { + throw new ArgumentNullException(nameof(writerFactory)); + } + using (var writer = new StringWriter()) { - WriteJavascript(node, writer, options, converterFactory); + WriteJavascript(node, writerFactory(writer, formattingOptions), options, converterFactory); return writer.ToString(); } } public static void WriteJavascript(this Node node, TextWriter writer, AstToJavascriptConverter.Factory? converterFactory = null) { - WriteJavascript(node, writer, Options.Default, converterFactory); + WriteJavascript(node, new JavascriptTextWriter(writer, JavascriptTextWriter.Options.Default), Options.Default, converterFactory); + } + + public static void WriteJavascript(this Node node, TextWriter writer, KnRJavascriptTextWriter.Options formattingOptions, AstToJavascriptConverter.Factory? converterFactory = null) + { + WriteJavascript(node, new KnRJavascriptTextWriter(writer, formattingOptions), Options.Default, converterFactory); } public static void WriteJavascript(this Node node, TextWriter writer, bool beautify, AstToJavascriptConverter.Factory? converterFactory = null) { - WriteJavascript(node, writer, beautify ? Options.DefaultWithBeautify : Options.Default, converterFactory); + if (beautify) + { + WriteJavascript(node, writer, KnRJavascriptTextWriter.Options.Default, converterFactory); + } + else + { + WriteJavascript(node, writer, converterFactory); + } } - public static void WriteJavascript(this Node node, TextWriter writer, Options options, AstToJavascriptConverter.Factory? converterFactory = null) + public static void WriteJavascript(this Node node, JavascriptTextWriter writer, Options options, AstToJavascriptConverter.Factory? converterFactory = null) { - converterFactory ??= (writer, options) => new AstToJavascriptConverter(writer, options); + converterFactory ??= static (writer, options) => new AstToJavascriptConverter(writer, options); converterFactory(writer, options).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.cs b/src/Esprima/Utils/AstToJavascriptConverter.cs index d892a256..c22f3530 100644 --- a/src/Esprima/Utils/AstToJavascriptConverter.cs +++ b/src/Esprima/Utils/AstToJavascriptConverter.cs @@ -1,121 +1,96 @@ -using System.Runtime.CompilerServices; +using System.Diagnostics; +using System.Runtime.CompilerServices; using Esprima.Ast; -using static Esprima.EsprimaExceptionHelper; +using static Esprima.Utils.JavascriptTextWriter; namespace Esprima.Utils; -public class AstToJavascriptConverter : AstVisitor +public partial class AstToJavascriptConverter : AstVisitor { - public delegate AstToJavascriptConverter Factory(TextWriter writer, AstToJavascript.Options options); + // 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 readonly TextWriter _writer; + public delegate AstToJavascriptConverter Factory(JavascriptTextWriter writer, AstToJavascript.Options options); - public readonly bool _beautify; - public readonly string _indent; + private static readonly object s_lastSwitchCaseFlag = new(); + private static readonly object s_forInLoopDeclarationFlag = new(); - private Node? _parentNode, _currentNode; - private int _indentionLevel = 0; + private WriteContext _writeContext; + private StatementFlags _currentStatementFlags; + private ExpressionFlags _currentExpressionFlags; + private object? _currentAuxiliaryNodeContext; - public AstToJavascriptConverter(TextWriter writer, AstToJavascript.Options options) + public AstToJavascriptConverter(JavascriptTextWriter writer, AstToJavascript.Options options) { - _writer = writer ?? ThrowArgumentNullException(nameof(writer)); + Writer = writer ?? throw new ArgumentNullException(nameof(writer)); if (options is null) { - ThrowArgumentNullException(nameof(options)); - throw null!; - } - - _beautify = options.Beautify; - _indent = options.Indent ?? " "; - } - - protected Node? ParentNode => _parentNode; - - protected void Append(string text) - { - _writer.Write(text); - } - - protected void AppendBeautificationSpace() - { - if (_beautify) - { - _writer.Write(" "); - } - } - - protected void AppendBeautificationIndent() - { - if (_beautify) - { - for (var n = _indentionLevel; n > 0; n--) - { - _writer.Write(_indent); - } + throw new ArgumentNullException(nameof(options)); } } - protected void AppendBeautificationNewline() - { - if (_beautify) - { - _writer.WriteLine(); - } - } + public JavascriptTextWriter Writer { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } - protected void IncreaseIndent() - { - _indentionLevel++; - } + protected ref readonly WriteContext WriteContext { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _writeContext; } - protected void DecreaseIndent() - { - _indentionLevel--; - } + protected Node? ParentNode { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _writeContext.ParentNode; } public void Convert(Node node) { - Visit(node ?? ThrowArgumentNullException(nameof(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 originalParentNode = _parentNode; - _parentNode = _currentNode; - _currentNode = node; + var originalWriteContext = _writeContext; + _writeContext = new WriteContext(originalWriteContext.Node, node); var result = base.Visit(node); - _currentNode = _parentNode; - _parentNode = originalParentNode; + _writeContext = originalWriteContext; return result; - } + } protected internal override object? VisitProgram(Program program) { - VisitNodeList(program.Body, appendAtEnd: ";", addLineBreaks: true); + _writeContext.SetNodeProperty(nameof(program.Body), static node => ref node.As().Body); + VisitStatementList(in program.Body); return program; } protected internal override object? VisitChainExpression(ChainExpression chainExpression) { - Visit(chainExpression.Expression); + _writeContext.SetNodeProperty(nameof(chainExpression.Expression), static node => node.As().Expression); + VisitSubExpression(chainExpression.Expression, SubExpressionFlags(needsBrackets: false, isLeftMost: true)); return chainExpression; } protected internal override object? VisitCatchClause(CatchClause catchClause) { - Append("("); if (catchClause.Param is not null) { - Visit(catchClause.Param); + _writeContext.SetNodeProperty(nameof(catchClause.Param), static node => node.As().Param); + Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, in _writeContext); + VisitAuxiliaryNode(catchClause.Param); + Writer.WritePunctuator(")", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); } - Append(")"); - Visit(catchClause.Body); + + _writeContext.SetNodeProperty(nameof(catchClause.Body), static node => node.As().Body); + VisitStatement(catchClause.Body, StatementBodyFlags(isRightMost: ParentNode!.As().Finalizer is null)); return catchClause; } @@ -124,68 +99,114 @@ public void Convert(Node node) { if (functionDeclaration.Async) { - Append("async "); + _writeContext.SetNodeProperty(nameof(functionDeclaration.Async), static node => node.As().Async); + Writer.WriteKeyword("async", TokenFlags.LeadingSpaceRecommended, in _writeContext); + + _writeContext.ClearNodeProperty(); } - Append("function"); + + Writer.WriteKeyword("function", TokenFlags.LeadingSpaceRecommended, in _writeContext); + if (functionDeclaration.Generator) { - Append("*"); + _writeContext.SetNodeProperty(nameof(functionDeclaration.Generator), static node => node.As().Generator); + Writer.WritePunctuator("*", (functionDeclaration.Id is not null).ToFlag(TokenFlags.TrailingSpaceRecommended), in _writeContext); } + if (functionDeclaration.Id is not null) { - Append(" "); - Visit(functionDeclaration.Id); + _writeContext.SetNodeProperty(nameof(functionDeclaration.Id), static node => node.As().Id); + VisitAuxiliaryNode(functionDeclaration.Id); } - Append("("); - VisitNodeList(functionDeclaration.Params, appendSeperatorString: ","); - Append(")"); - AppendBeautificationSpace(); - Visit(functionDeclaration.Body); + + _writeContext.SetNodeProperty(nameof(functionDeclaration.Params), static node => ref node.As().Params); + Writer.WritePunctuator("(", TokenFlags.Leading, in _writeContext); + VisitAuxiliaryNodeList(in functionDeclaration.Params, separator: ","); + Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); + + _writeContext.SetNodeProperty(nameof(functionDeclaration.Body), static node => node.As().Body); + VisitStatement(functionDeclaration.Body, StatementBodyFlags(isRightMost: true)); return functionDeclaration; } protected internal override object? VisitWithStatement(WithStatement withStatement) { - Append("with("); - Visit(withStatement.Object); - Append(")"); - Visit(withStatement.Body); + Writer.WriteKeyword("with", TokenFlags.SurroundingSpaceRecommended, in _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? VisitWhileStatement(WhileStatement whileStatement) { - Append("while("); - Visit(whileStatement.Test); - Append(")"); - Visit(whileStatement.Body); + Writer.WriteKeyword("while", TokenFlags.SurroundingSpaceRecommended, in _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? VisitVariableDeclaration(VariableDeclaration variableDeclaration) { - Append(variableDeclaration.Kind.ToString().ToLower() + " "); - VisitNodeList(variableDeclaration.Declarations, appendSeperatorString: ","); + _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), in _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.ForInStatement }) + { + VisitAuxiliaryNodeList(in variableDeclaration.Declarations, separator: ","); + } + else + { + VisitAuxiliaryNodeList(in variableDeclaration.Declarations, separator: ",", static delegate { return s_forInLoopDeclarationFlag; }); + } return variableDeclaration; } protected internal override object? VisitTryStatement(TryStatement tryStatement) { - Append("try "); - Visit(tryStatement.Block); + Writer.WriteKeyword("try", TokenFlags.SurroundingSpaceRecommended, in _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) { - Append(" catch"); - Visit(tryStatement.Handler); + _writeContext.ClearNodeProperty(); + Writer.WriteKeyword("catch", TokenFlags.SurroundingSpaceRecommended | StatementBodyFlagsToKeywordFlags(bodyFlags), in _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) { - Append(" finally"); - Visit(tryStatement.Finalizer); + _writeContext.ClearNodeProperty(); + Writer.WriteKeyword("finally", TokenFlags.SurroundingSpaceRecommended | StatementBodyFlagsToKeywordFlags(bodyFlags), in _writeContext); + + _writeContext.SetNodeProperty(nameof(tryStatement.Finalizer), static node => node.As().Finalizer); + VisitStatement(tryStatement.Finalizer, StatementBodyFlags(isRightMost: true)); } return tryStatement; @@ -193,32 +214,31 @@ public void Convert(Node node) protected internal override object? VisitThrowStatement(ThrowStatement throwStatement) { - Append("throw "); - Visit(throwStatement.Argument); - Append(";"); + Writer.WriteKeyword("throw", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + _writeContext.SetNodeProperty(nameof(throwStatement.Argument), static node => node.As().Argument); + VisitRootExpression(throwStatement.Argument, RootExpressionFlags(needsBrackets: false)); + + StatementNeedsSemicolon(); return throwStatement; } protected internal override object? VisitSwitchStatement(SwitchStatement switchStatement) { - Append("switch("); - Visit(switchStatement.Discriminant); - Append(")"); - AppendBeautificationSpace(); - Append("{"); + Writer.WriteKeyword("switch", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - AppendBeautificationNewline(); - IncreaseIndent(); - AppendBeautificationIndent(); + _writeContext.SetNodeProperty(nameof(switchStatement.Discriminant), static node => node.As().Discriminant); + VisitRootExpression(switchStatement.Discriminant, ExpressionFlags.SpaceAroundBracketsRecommended | RootExpressionFlags(needsBrackets: true)); - VisitNodeList(switchStatement.Cases, addLineBreaks: true); + _writeContext.SetNodeProperty(nameof(switchStatement.Cases), static node => ref node.As().Cases); + Writer.StartBlock(switchStatement.Cases.Count, in _writeContext); - AppendBeautificationNewline(); - DecreaseIndent(); - AppendBeautificationIndent(); + // 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); - Append("}"); + Writer.EndBlock(switchStatement.Cases.Count, in _writeContext); return switchStatement; } @@ -227,95 +247,83 @@ public void Convert(Node node) { if (switchCase.Test is not null) { - Append("case "); - Visit(switchCase.Test); + Writer.WriteKeyword("case", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + _writeContext.SetNodeProperty(nameof(switchCase.Test), static node => node.As().Test); + VisitRootExpression(switchCase.Test, RootExpressionFlags(needsBrackets: false)); + + _writeContext.ClearNodeProperty(); } else { - Append("default"); + Writer.WriteKeyword("default", TokenFlags.LeadingSpaceRecommended, in _writeContext); } - Append(":"); - AppendBeautificationNewline(); - IncreaseIndent(); - AppendBeautificationIndent(); + Writer.WritePunctuator(":", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); - VisitNodeList(switchCase.Consequent, appendAtEnd: ";", addLineBreaks: true); + _writeContext.SetNodeProperty(nameof(switchCase.Consequent), static node => ref node.As().Consequent); - DecreaseIndent(); + 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? VisitReturnStatement(ReturnStatement returnStatement) { - Append("return"); + Writer.WriteKeyword("return", (returnStatement.Argument is not null).ToFlag(TokenFlags.SurroundingSpaceRecommended, TokenFlags.LeadingSpaceRecommended), in _writeContext); + if (returnStatement.Argument is not null) { - Append(" "); - Visit(returnStatement.Argument); + _writeContext.SetNodeProperty(nameof(returnStatement.Argument), static node => node.As().Argument); + VisitRootExpression(returnStatement.Argument, RootExpressionFlags(needsBrackets: false)); } - Append(";"); + + StatementNeedsSemicolon(); return returnStatement; } protected internal override object? VisitLabeledStatement(LabeledStatement labeledStatement) { - Visit(labeledStatement.Label); - Append(":"); - Visit(labeledStatement.Body); + _writeContext.SetNodeProperty(nameof(labeledStatement.Label), static node => node.As().Label); + Writer.WriteEpsilon(TokenFlags.LeadingSpaceRecommended, in _writeContext); + VisitAuxiliaryNode(labeledStatement.Label); + + Writer.WritePunctuator(":", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); + + _writeContext.SetNodeProperty(nameof(labeledStatement.Body), static node => node.As().Body); + VisitStatement(labeledStatement.Body, StatementFlags.IsRightMost); return labeledStatement; } protected internal override object? VisitIfStatement(IfStatement ifStatement) { - Append("if"); - AppendBeautificationSpace(); - Append("("); - Visit(ifStatement.Test); - Append(")"); - AppendBeautificationSpace(); + Writer.WriteKeyword("if", TokenFlags.SurroundingSpaceRecommended, in _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.Consequent is not BlockStatement) - { - AppendBeautificationNewline(); - IncreaseIndent(); - AppendBeautificationIndent(); - } - Visit(ifStatement.Consequent); - if (NodeNeedsSemicolon(ifStatement.Consequent)) - { - Append(";"); - } - if (ifStatement.Consequent is not BlockStatement) - { - DecreaseIndent(); - if (ifStatement.Alternate is not null) - { - AppendBeautificationNewline(); - AppendBeautificationIndent(); - } - } if (ifStatement.Alternate is not null) { - Append(" else "); - if (ifStatement.Alternate is not BlockStatement && ifStatement.Alternate is not IfStatement) - { - AppendBeautificationNewline(); - IncreaseIndent(); - AppendBeautificationIndent(); - } - Visit(ifStatement.Alternate); - if (NodeNeedsSemicolon(ifStatement.Alternate)) - { - Append(";"); - } - if (ifStatement.Alternate is not BlockStatement && ifStatement.Alternate is not IfStatement) - { - DecreaseIndent(); - } + _writeContext.ClearNodeProperty(); + Writer.WriteKeyword("else", TokenFlags.SurroundingSpaceRecommended | StatementBodyFlagsToKeywordFlags(bodyFlags), in _writeContext); + + _writeContext.SetNodeProperty(nameof(ifStatement.Alternate), static node => node.As().Alternate); + VisitStatement(ifStatement.Alternate, StatementBodyFlags(isRightMost: true)); } return ifStatement; @@ -323,137 +331,123 @@ public void Convert(Node node) protected internal override object? VisitEmptyStatement(EmptyStatement emptyStatement) { - Append(";"); + Writer.WritePunctuator(";", TokenFlags.SurroundingSpaceRecommended, in _writeContext); return emptyStatement; } protected internal override object? VisitDebuggerStatement(DebuggerStatement debuggerStatement) { - Append("debugger"); + Writer.WriteKeyword("debugger", TokenFlags.LeadingSpaceRecommended, in _writeContext); + + StatementNeedsSemicolon(); return debuggerStatement; } protected internal override object? VisitExpressionStatement(ExpressionStatement expressionStatement) { - if (expressionStatement.Expression is CallExpression callExpression && !(callExpression.Callee is Identifier)) - { - if (ExpressionNeedsBrackets(callExpression.Callee)) - { - Append("("); - } - Visit(callExpression.Callee); - if (ExpressionNeedsBrackets(callExpression.Callee)) - { - Append(")"); - } - Append("("); - VisitNodeList(callExpression.Arguments, appendSeperatorString: ","); - Append(")"); - } - else if (expressionStatement.Expression is ClassExpression) - { - Append("("); - Visit(expressionStatement.Expression); - Append(")"); - } - else - { - if (expressionStatement.Expression is FunctionExpression) - { - Append("("); - } - Visit(expressionStatement.Expression); - if (expressionStatement.Expression is FunctionExpression) - { - Append(")"); - } - } + _writeContext.SetNodeProperty(nameof(expressionStatement.Expression), static node => node.As().Expression); + Writer.WriteEpsilon(TokenFlags.LeadingSpaceRecommended, in _writeContext); + VisitRootExpression(expressionStatement.Expression, ExpressionFlags.IsInsideStatementExpression | RootExpressionFlags(needsBrackets: false)); + + StatementNeedsSemicolon(); return expressionStatement; } protected internal override object? VisitForStatement(ForStatement forStatement) { - Append("for("); + Writer.WriteKeyword("for", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, in _writeContext); + + _writeContext.SetNodeProperty(nameof(forStatement.Init), static node => node.As().Init); + if (forStatement.Init is not null) { - Visit(forStatement.Init); + if (forStatement.Init is VariableDeclaration variableDeclaration) + { + VisitStatement(variableDeclaration, StatementFlags.NestedVariableDeclaration); + } + else + { + VisitRootExpression(forStatement.Init.As(), RootExpressionFlags(needsBrackets: false)); + } } - Append(";"); - AppendBeautificationSpace(); + + Writer.WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); + + _writeContext.SetNodeProperty(nameof(forStatement.Test), static node => node.As().Test); + if (forStatement.Test is not null) { - Visit(forStatement.Test); + VisitRootExpression(forStatement.Test, RootExpressionFlags(needsBrackets: false)); } - Append(";"); - AppendBeautificationSpace(); + + Writer.WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); + if (forStatement.Update is not null) { - Visit(forStatement.Update); - } - Append(")"); - AppendBeautificationSpace(); + _writeContext.SetNodeProperty(nameof(forStatement.Update), static node => node.As().Update); - if (forStatement.Body is not BlockStatement) - { - AppendBeautificationNewline(); - IncreaseIndent(); - AppendBeautificationIndent(); - } - Visit(forStatement.Body); - if (NodeNeedsSemicolon(forStatement.Body)) - { - Append(";"); - } - if (forStatement.Body is not BlockStatement) - { - DecreaseIndent(); + VisitRootExpression(forStatement.Update, RootExpressionFlags(needsBrackets: false)); } + _writeContext.ClearNodeProperty(); + Writer.WritePunctuator(")", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); + + _writeContext.SetNodeProperty(nameof(forStatement.Body), static node => node.As().Body); + VisitStatement(forStatement.Body, StatementBodyFlags(isRightMost: true)); + return forStatement; } protected internal override object? VisitForInStatement(ForInStatement forInStatement) { - Append("for("); - Visit(forInStatement.Left); - Append(" in "); - Visit(forInStatement.Right); - Append(")"); - AppendBeautificationSpace(); + Writer.WriteKeyword("for", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - if (forInStatement.Body is not BlockStatement) - { - AppendBeautificationNewline(); - IncreaseIndent(); - AppendBeautificationIndent(); - } - Visit(forInStatement.Body); - if (NodeNeedsSemicolon(forInStatement.Body)) + Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, in _writeContext); + + _writeContext.SetNodeProperty(nameof(forInStatement.Left), static node => node.As().Left); + + if (forInStatement.Left is VariableDeclaration variableDeclaration) { - Append(";"); + VisitStatement(variableDeclaration, StatementFlags.NestedVariableDeclaration); } - if (forInStatement.Body is not BlockStatement) + else { - DecreaseIndent(); + VisitAuxiliaryNode(forInStatement.Left); } + _writeContext.ClearNodeProperty(); + Writer.WriteKeyword("in", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _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, in _writeContext); + + _writeContext.SetNodeProperty(nameof(forInStatement.Body), static node => node.As().Body); + VisitStatement(forInStatement.Body, StatementBodyFlags(isRightMost: true)); + return forInStatement; } protected internal override object? VisitDoWhileStatement(DoWhileStatement doWhileStatement) { - Append("do "); - Visit(doWhileStatement.Body); - if (NodeNeedsSemicolon(doWhileStatement.Body)) - { - Append(";"); - } - Append("while("); - Visit(doWhileStatement.Test); - Append(")"); + Writer.WriteKeyword("do", TokenFlags.SurroundingSpaceRecommended, in _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), in _writeContext); + + _writeContext.SetNodeProperty(nameof(doWhileStatement.Test), static node => node.As().Test); + VisitRootExpression(doWhileStatement.Test, ExpressionFlags.SpaceBeforeBracketsRecommended | RootExpressionFlags(needsBrackets: true)); return doWhileStatement; } @@ -462,36 +456,37 @@ public void Convert(Node node) { if (arrowFunctionExpression.Async) { - Append("async "); + _writeContext.SetNodeProperty(nameof(arrowFunctionExpression.Async), static node => node.As().Async); + Writer.WriteKeyword("async", TokenFlags.TrailingSpaceRecommended, in _writeContext); } - if (arrowFunctionExpression.Params.Count == 1) + _writeContext.SetNodeProperty(nameof(arrowFunctionExpression.Params), static node => ref node.As().Params); + + if (arrowFunctionExpression.Params.Count == 1 && arrowFunctionExpression.Params[0].Type == Nodes.Identifier) { - if (arrowFunctionExpression.Params[0] is RestElement || ExpressionNeedsBrackets(arrowFunctionExpression.Params[0])) - { - Append("("); - } - Visit(arrowFunctionExpression.Params[0]); - if (arrowFunctionExpression.Params[0] is RestElement || ExpressionNeedsBrackets(arrowFunctionExpression.Params[0])) - { - Append(")"); - } + VisitAuxiliaryNodeList(in arrowFunctionExpression.Params, separator: ","); } else { - Append("("); - VisitNodeList(arrowFunctionExpression.Params, appendSeperatorString: ",", appendBracketsIfNeeded: true); ; - Append(")"); + Writer.WritePunctuator("(", TokenFlags.Leading, in _writeContext); + VisitAuxiliaryNodeList(in arrowFunctionExpression.Params, separator: ","); + Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); } - Append("=>"); - if (arrowFunctionExpression.Body is ObjectExpression || arrowFunctionExpression.Body is SequenceExpression) + + _writeContext.ClearNodeProperty(); + Writer.WritePunctuator("=>", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + _writeContext.SetNodeProperty(nameof(arrowFunctionExpression.Body), static node => node.As().Body); + if (arrowFunctionExpression.Body is BlockStatement bodyBlockStatement) { - Append("("); + VisitStatement(bodyBlockStatement, StatementFlags.IsRightMost); } - Visit(arrowFunctionExpression.Body); - if (arrowFunctionExpression.Body is ObjectExpression || arrowFunctionExpression.Body is SequenceExpression) + else { - Append(")"); + 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; @@ -499,101 +494,116 @@ public void Convert(Node node) protected internal override object? VisitUnaryExpression(UnaryExpression unaryExpression) { - if (unaryExpression is UpdateExpression updateExpression) + var argumentNeedsBrackets = UnaryOperandNeedsBrackets(unaryExpression, unaryExpression.Argument); + var op = UnaryExpression.GetUnaryOperatorToken(unaryExpression.Operator); + + if (unaryExpression.Prefix) { - if (updateExpression.Prefix) + _writeContext.SetNodeProperty(nameof(unaryExpression.Operator), static node => node.As().Operator); + if (char.IsLetter(op[0])) { - Append(UnaryExpression.GetUnaryOperatorToken(updateExpression.Operator)); + Writer.WriteKeyword(op, TokenFlags.TrailingSpaceRecommended, in _writeContext); } - Visit(updateExpression.Argument); - if (!updateExpression.Prefix) + else { - Append(UnaryExpression.GetUnaryOperatorToken(updateExpression.Operator)); + Writer.WritePunctuator(op, TokenFlags.Leading, in _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 { - var op = UnaryExpression.GetUnaryOperatorToken(unaryExpression.Operator); - if (unaryExpression.Prefix) - { - Append(op); - if (char.IsLetter(op[0])) - Append(" "); - } - if (!(unaryExpression.Argument is Literal) && !(unaryExpression.Argument is UnaryExpression)) - { - Append("("); - } - Visit(unaryExpression.Argument); - if (!(unaryExpression.Argument is Literal) && !(unaryExpression.Argument is UnaryExpression)) - { - Append(")"); - } - if (!unaryExpression.Prefix) - { - Append(op); + _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, in _writeContext); } - } return unaryExpression; - } + } protected internal override object? VisitThisExpression(ThisExpression thisExpression) { - Append("this"); + Writer.WriteKeyword("this", in _writeContext); return thisExpression; } protected internal override object? VisitSequenceExpression(SequenceExpression sequenceExpression) { - VisitNodeList(sequenceExpression.Expressions, appendSeperatorString: _beautify ? ", " : ","); + _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? VisitObjectExpression(ObjectExpression objectExpression) { - Append("{"); - if (objectExpression.Properties.Count > 0) - { - AppendBeautificationNewline(); - IncreaseIndent(); - AppendBeautificationIndent(); - } - VisitNodeList(objectExpression.Properties, appendSeperatorString: ",", addLineBreaks: true); - if (objectExpression.Properties.Count > 0) + _writeContext.SetNodeProperty(nameof(objectExpression.Properties), static node => ref node.As().Properties); + + Writer.StartObject(objectExpression.Properties.Count, in _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, in _writeContext); + + for (var i = 0; i < objectExpression.Properties.Count; i++) { - AppendBeautificationNewline(); - DecreaseIndent(); - AppendBeautificationIndent(); + var property = objectExpression.Properties[i]; + if (property is SpreadElement spreadElement) + { + var originalAuxiliaryNodeContext = _currentAuxiliaryNodeContext; + _currentAuxiliaryNodeContext = null; + + Writer.StartAuxiliaryNodeListItem(i, objectExpression.Properties.Count, separator: ",", _currentAuxiliaryNodeContext, in _writeContext); + VisitRootExpression(spreadElement, RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(spreadElement))); + Writer.EndAuxiliaryNodeListItem(i, objectExpression.Properties.Count, separator: ",", _currentAuxiliaryNodeContext, in _writeContext); + + _currentAuxiliaryNodeContext = originalAuxiliaryNodeContext; + } + else + { + VisitAuxiliaryNodeListItem(property, i, objectExpression.Properties.Count, separator: ",", static delegate { return null; }); + } } - Append("}"); + + Writer.EndAuxiliaryNodeList(objectExpression.Properties.Count, in _writeContext); + + Writer.EndObject(objectExpression.Properties.Count, in _writeContext); return objectExpression; } protected internal override object? VisitNewExpression(NewExpression newExpression) { - Append("new"); - if (ExpressionNeedsBrackets(newExpression.Callee)) - { - Append("("); - } - else - { - Append(" "); - } - Visit(newExpression.Callee); - if (ExpressionNeedsBrackets(newExpression.Callee)) - { - Append(")"); - } + Writer.WriteKeyword("new", TokenFlags.TrailingSpaceRecommended, in _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) { - Append("("); - VisitNodeList(newExpression.Arguments, appendSeperatorString: ","); - Append(")"); + _writeContext.SetNodeProperty(nameof(newExpression.Arguments), static node => ref node.As().Arguments); + Writer.WritePunctuator("(", TokenFlags.Leading, in _writeContext); + VisitSubExpressionList(in newExpression.Arguments); + Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); } return newExpression; @@ -601,29 +611,40 @@ public void Convert(Node node) protected internal override object? VisitMemberExpression(MemberExpression memberExpression) { - if (ExpressionNeedsBrackets(memberExpression.Object) || (memberExpression.Object is Literal l && l.TokenType != TokenType.StringLiteral)) - { - Append("("); - } - Visit(memberExpression.Object); - if (ExpressionNeedsBrackets(memberExpression.Object) || (memberExpression.Object is Literal l2 && l2.TokenType != TokenType.StringLiteral)) + 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) { - Append(")"); + 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) { - Append("["); + if (memberExpression.Optional) + { + _writeContext.ClearNodeProperty(); + Writer.WritePunctuator("?.", TokenFlags.InBetween, in _writeContext); + } + + _writeContext.SetNodeProperty(nameof(memberExpression.Property), static node => node.As().Property); + Writer.WritePunctuator("[", TokenFlags.Leading, in _writeContext); + VisitSubExpression(memberExpression.Property, SubExpressionFlags(needsBrackets: false, isLeftMost: false)); + Writer.WritePunctuator("]", TokenFlags.Trailing, in _writeContext); } else { - if (_parentNode is ChainExpression) - Append("?"); - Append("."); - } - Visit(memberExpression.Property); - if (memberExpression.Computed) - { - Append("]"); + _writeContext.ClearNodeProperty(); + Writer.WritePunctuator(memberExpression.Optional ? "?." : ".", TokenFlags.InBetween, in _writeContext); + + _writeContext.SetNodeProperty(nameof(memberExpression.Property), static node => node.As().Property); + VisitSubExpression(memberExpression.Property, SubExpressionFlags(needsBrackets: false, isLeftMost: false)); } return memberExpression; @@ -631,87 +652,142 @@ public void Convert(Node node) protected internal override object? VisitLiteral(Literal literal) { - Append(literal.Raw); + _writeContext.SetNodeProperty(nameof(literal.Raw), static node => node.As().Raw); + Writer.WriteLiteral(literal.Raw, literal.TokenType, in _writeContext); return literal; } protected internal override object? VisitIdentifier(Identifier identifier) { - Append(identifier.Name!); + _writeContext.SetNodeProperty(nameof(identifier.Name), static node => node.As().Name); + Writer.WriteIdentifier(identifier.Name!, in _writeContext); return identifier; } protected internal override object? VisitFunctionExpression(FunctionExpression functionExpression) { - var isParentMethod = _parentNode is MethodDefinition; - if (!isParentMethod) + if (!_currentExpressionFlags.HasFlagFast(ExpressionFlags.IsMethod)) { if (functionExpression.Async) { - Append("async "); + _writeContext.SetNodeProperty(nameof(functionExpression.Async), static node => node.As().Async); + Writer.WriteKeyword("async", in _writeContext); + + _writeContext.ClearNodeProperty(); } - if (_parentNode is not MethodDefinition) + + Writer.WriteKeyword("function", in _writeContext); + + if (functionExpression.Generator) { - Append("function"); + _writeContext.SetNodeProperty(nameof(functionExpression.Generator), static node => node.As().Generator); + Writer.WritePunctuator("*", (functionExpression.Id is not null).ToFlag(TokenFlags.TrailingSpaceRecommended), in _writeContext); } - if (functionExpression.Generator) + + if (functionExpression.Id is not null) { - Append("*"); + _writeContext.SetNodeProperty(nameof(functionExpression.Id), static node => node.As().Id); + VisitAuxiliaryNode(functionExpression.Id); } } - if (functionExpression.Id is not null) + else { - Append(" "); - Visit(functionExpression.Id); + var keyIsFirstToken = true; + + if (functionExpression.Async) + { + _writeContext.SetNodeProperty(nameof(functionExpression.Async), static node => node.As().Async); + Writer.WriteKeyword("async", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + keyIsFirstToken = false; + } + + if (functionExpression.Generator) + { + _writeContext.SetNodeProperty(nameof(functionExpression.Generator), static node => node.As().Generator); + Writer.WritePunctuator("*", TokenFlags.LeadingSpaceRecommended, in _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, in _writeContext); + } + + VisitPropertyKey(property.Key, property.Computed, leadingBracketFlags: keyIsFirstToken.ToFlag(TokenFlags.LeadingSpaceRecommended)); + } + else + { + Writer.WriteKeyword("constructor", TokenFlags.LeadingSpaceRecommended, in _writeContext); + } } - Append("("); - VisitNodeList(functionExpression.Params, appendSeperatorString: ","); - Append(")"); - AppendBeautificationSpace(); - Visit(functionExpression.Body); + + _writeContext.SetNodeProperty(nameof(functionExpression.Params), static node => ref node.As().Params); + Writer.WritePunctuator("(", TokenFlags.Leading, in _writeContext); + VisitAuxiliaryNodeList(in functionExpression.Params, separator: ","); + Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); + + _writeContext.SetNodeProperty(nameof(functionExpression.Body), static node => node.As().Body); + VisitStatement(functionExpression.Body, StatementBodyFlags(isRightMost: true)); return functionExpression; } protected internal override object? VisitClassExpression(ClassExpression classExpression) { - Append("class "); - if (classExpression.Id is not null) - { - Visit(classExpression.Id); - } - if (classExpression.SuperClass is not null) + if (classExpression.Decorators.Count > 0) { - Append(" extends "); - Visit(classExpression.SuperClass); + _writeContext.SetNodeProperty(nameof(classExpression.Decorators), static node => ref node.As().Decorators); + VisitAuxiliaryNodeList(classExpression.Decorators, separator: string.Empty); + + _writeContext.ClearNodeProperty(); } - AppendBeautificationSpace(); - Append("{"); + Writer.WriteKeyword("class", TokenFlags.TrailingSpaceRecommended, in _writeContext); - AppendBeautificationNewline(); - IncreaseIndent(); - AppendBeautificationIndent(); + if (classExpression.Id is not null) + { + _writeContext.SetNodeProperty(nameof(classExpression.Id), static node => node.As().Id); + VisitAuxiliaryNode(classExpression.Id); + } - Visit(classExpression.Body); + if (classExpression.SuperClass is not null) + { + _writeContext.ClearNodeProperty(); + Writer.WriteKeyword("extends", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - AppendBeautificationNewline(); - DecreaseIndent(); - AppendBeautificationIndent(); + _writeContext.SetNodeProperty(nameof(classExpression.SuperClass), static node => node.As().SuperClass); + VisitRootExpression(classExpression.SuperClass, LeftHandSideRootExpressionFlags(needsBrackets: false)); + } - Append("}"); + _writeContext.SetNodeProperty(nameof(classExpression.Body), static node => node.As().Body); + VisitAuxiliaryNode(classExpression.Body); return classExpression; } protected internal override object? VisitExportDefaultDeclaration(ExportDefaultDeclaration exportDefaultDeclaration) { - Append("export default "); - if (exportDefaultDeclaration.Declaration is not null) + Writer.WriteKeyword("export", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("default", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + _writeContext.SetNodeProperty(nameof(exportDefaultDeclaration.Declaration), static node => node.As().Declaration); + if (exportDefaultDeclaration.Declaration is Declaration declaration) { - Visit(exportDefaultDeclaration.Declaration); + VisitStatement(declaration, StatementFlags.IsRightMost); + } + else + { + VisitRootExpression(exportDefaultDeclaration.Declaration.As(), ExpressionFlags.IsInsideStatementExpression | RootExpressionFlags(needsBrackets: false)); + + StatementNeedsSemicolon(); } return exportDefaultDeclaration; @@ -719,34 +795,66 @@ public void Convert(Node node) protected internal override object? VisitExportAllDeclaration(ExportAllDeclaration exportAllDeclaration) { - Append("export*from"); - Visit(exportAllDeclaration.Source); + Writer.WriteKeyword("export", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WritePunctuator("*", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - return exportAllDeclaration; - } - - protected internal override object? VisitExportNamedDeclaration(ExportNamedDeclaration exportNamedDeclaration) - { - Append("export"); - if (exportNamedDeclaration.Declaration is not null) + if (exportAllDeclaration.Exported is not null) { - Append(" "); - Visit(exportNamedDeclaration.Declaration); + _writeContext.SetNodeProperty(nameof(exportAllDeclaration.Exported), static node => node.As().Exported); + Writer.WriteKeyword("as", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + VisitExportOrImportSpecifierIdentifier(exportAllDeclaration.Exported); } - if (exportNamedDeclaration.Specifiers.Count > 0) - { - Append("{"); - VisitNodeList(exportNamedDeclaration.Specifiers, appendSeperatorString: ","); - Append("}"); + + _writeContext.ClearNodeProperty(); + Writer.WriteKeyword("from", TokenFlags.SurroundingSpaceRecommended, in _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); } - if (exportNamedDeclaration.Source is not null) + + StatementNeedsSemicolon(); + + return exportAllDeclaration; + } + + protected internal override object? VisitExportNamedDeclaration(ExportNamedDeclaration exportNamedDeclaration) + { + Writer.WriteKeyword("export", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + if (exportNamedDeclaration.Declaration is not null) { - Append("from"); - Visit(exportNamedDeclaration.Source); + _writeContext.SetNodeProperty(nameof(exportNamedDeclaration.Declaration), static node => node.As().Declaration); + VisitStatement(exportNamedDeclaration.Declaration.As(), StatementFlags.IsRightMost); } - if (exportNamedDeclaration.Declaration is null && exportNamedDeclaration.Specifiers.Count == 0 && exportNamedDeclaration.Source is null) + else { - Append("{}"); + _writeContext.SetNodeProperty(nameof(exportNamedDeclaration.Specifiers), static node => ref node.As().Specifiers); + Writer.WritePunctuator("{", TokenFlags.Leading | TokenFlags.SurroundingSpaceRecommended, in _writeContext); + VisitAuxiliaryNodeList(in exportNamedDeclaration.Specifiers, separator: ","); + Writer.WritePunctuator("}", TokenFlags.Trailing | TokenFlags.LeadingSpaceRecommended, in _writeContext); + + if (exportNamedDeclaration.Source is not null) + { + _writeContext.ClearNodeProperty(); + Writer.WriteKeyword("from", TokenFlags.SurroundingSpaceRecommended, in _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; @@ -754,11 +862,16 @@ public void Convert(Node node) protected internal override object? VisitExportSpecifier(ExportSpecifier exportSpecifier) { - Visit(exportSpecifier.Local); + _writeContext.SetNodeProperty(nameof(exportSpecifier.Local), static node => node.As().Local); + VisitExportOrImportSpecifierIdentifier(exportSpecifier.Local); + if (exportSpecifier.Local != exportSpecifier.Exported) { - Append(" as "); - Visit(exportSpecifier.Exported); + _writeContext.ClearNodeProperty(); + Writer.WriteKeyword("as", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + _writeContext.SetNodeProperty(nameof(exportSpecifier.Exported), static node => node.As().Exported); + VisitExportOrImportSpecifierIdentifier(exportSpecifier.Exported); } return exportSpecifier; @@ -766,206 +879,275 @@ public void Convert(Node node) protected internal override object? VisitImport(Import import) { - Append("import("); - Visit(import.Source); - Append(")"); + Writer.WriteKeyword("import", in _writeContext); + + Writer.WritePunctuator("(", TokenFlags.Leading, in _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, in _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, in _writeContext); + + _writeContext.ClearNodeProperty(); + Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); return import; } protected internal override object? VisitImportDeclaration(ImportDeclaration importDeclaration) { - Append("import "); - var firstSpecifier = importDeclaration.Specifiers.FirstOrDefault(); - if (firstSpecifier is ImportDefaultSpecifier) + Writer.WriteKeyword("import", TokenFlags.SurroundingSpaceRecommended, in _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, in _writeContext); + + if (importDeclaration.Specifiers.Count == 0) { - Visit(firstSpecifier); - if (importDeclaration.Specifiers.Count > 1) - { - Append(","); - AppendBeautificationSpace(); - if (importDeclaration.Specifiers[1] is ImportNamespaceSpecifier) - { - VisitNodeList(importDeclaration.Specifiers.Skip(1), appendSeperatorString: _beautify ? ", " : ","); - } - else - { - Append("{"); - AppendBeautificationSpace(); - VisitNodeList(importDeclaration.Specifiers.Skip(1), appendSeperatorString: _beautify ? ", " : ","); - AppendBeautificationSpace(); - Append("}"); - } - } + Writer.EndAuxiliaryNodeList(count: 0, in _writeContext); + + goto WriteSource; } - else if (importDeclaration.Specifiers.Any()) + + var index = 0; + Func getNodeContext = static delegate { return null; }; + + if (importDeclaration.Specifiers[index].Type == Nodes.ImportDefaultSpecifier) { - if (importDeclaration.Specifiers[0] is ImportNamespaceSpecifier) + VisitAuxiliaryNodeListItem(importDeclaration.Specifiers[index], index, importDeclaration.Specifiers.Count, ",", getNodeContext); + + if (++index >= importDeclaration.Specifiers.Count) { - VisitNodeList(importDeclaration.Specifiers, appendSeperatorString: _beautify ? ", " : ","); + goto EndSpecifiers; } - else + } + + if (importDeclaration.Specifiers[index].Type == Nodes.ImportNamespaceSpecifier) + { + VisitAuxiliaryNodeListItem(importDeclaration.Specifiers[index], index, importDeclaration.Specifiers.Count, ",", getNodeContext); + + if (++index >= importDeclaration.Specifiers.Count) { - Append("{"); - AppendBeautificationSpace(); - VisitNodeList(importDeclaration.Specifiers, appendSeperatorString: _beautify ? ", " : ","); - AppendBeautificationSpace(); - Append("}"); + goto EndSpecifiers; } } - if (importDeclaration.Specifiers.Count > 0) + + Writer.WritePunctuator("{", TokenFlags.Leading | TokenFlags.TrailingSpaceRecommended, in _writeContext); + + for (; index < importDeclaration.Specifiers.Count; index++) + { + VisitAuxiliaryNodeListItem(importDeclaration.Specifiers[index], index, importDeclaration.Specifiers.Count, ",", getNodeContext); + } + + Writer.WritePunctuator("}", TokenFlags.Trailing | TokenFlags.LeadingSpaceRecommended, in _writeContext); + +EndSpecifiers: + Writer.EndAuxiliaryNodeList(importDeclaration.Specifiers.Count, in _writeContext); + + _writeContext.ClearNodeProperty(); + Writer.WriteKeyword("from", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + +WriteSource: + _writeContext.SetNodeProperty(nameof(importDeclaration.Source), static node => node.As().Source); + VisitRootExpression(importDeclaration.Source, RootExpressionFlags(needsBrackets: false)); + + if (importDeclaration.Assertions.Count > 0) { - Append(" from "); + _writeContext.SetNodeProperty(nameof(importDeclaration.Assertions), static node => ref node.As().Assertions); + VisitAssertions(in importDeclaration.Assertions); } - Visit(importDeclaration.Source); + + StatementNeedsSemicolon(); return importDeclaration; } protected internal override object? VisitImportNamespaceSpecifier(ImportNamespaceSpecifier importNamespaceSpecifier) { - Append("* as "); - Visit(importNamespaceSpecifier.Local); + Writer.WritePunctuator("*", TokenFlags.TrailingSpaceRecommended, in _writeContext); + + Writer.WriteKeyword("as", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + _writeContext.SetNodeProperty(nameof(importNamespaceSpecifier.Local), static node => node.As().Local); + VisitAuxiliaryNode(importNamespaceSpecifier.Local); return importNamespaceSpecifier; } protected internal override object? VisitImportDefaultSpecifier(ImportDefaultSpecifier importDefaultSpecifier) { - Visit(importDefaultSpecifier.Local); + _writeContext.SetNodeProperty(nameof(importDefaultSpecifier.Local), static node => node.As().Local); + VisitAuxiliaryNode(importDefaultSpecifier.Local); return importDefaultSpecifier; } protected internal override object? VisitImportSpecifier(ImportSpecifier importSpecifier) { - Visit(importSpecifier.Imported); - if (importSpecifier.Local != importSpecifier.Imported) + if (importSpecifier.Imported != importSpecifier.Local) { - Append(" as "); - Visit(importSpecifier.Local); + _writeContext.SetNodeProperty(nameof(importSpecifier.Imported), static node => node.As().Imported); + VisitExportOrImportSpecifierIdentifier(importSpecifier.Imported); + + _writeContext.ClearNodeProperty(); + Writer.WriteKeyword("as", TokenFlags.SurroundingSpaceRecommended, in _writeContext); } + _writeContext.SetNodeProperty(nameof(importSpecifier.Local), static node => node.As().Local); + VisitAuxiliaryNode(importSpecifier.Local); + return importSpecifier; } protected internal override object? VisitMethodDefinition(MethodDefinition methodDefinition) { - if (methodDefinition.Static) - { - Append("static "); - } - if (IsAsync(methodDefinition.Value)) + if (methodDefinition.Decorators.Count > 0) { - Append("async "); - } - if (methodDefinition.Value is FunctionExpression f && f.Generator) - { - Append("*"); - } - if (methodDefinition.Kind == PropertyKind.Get) - { - Append("get "); - } - else if (methodDefinition.Kind == PropertyKind.Set) - { - Append("set "); - } - if (methodDefinition.Key is MemberExpression || ExpressionNeedsBrackets(methodDefinition.Key)) - { - Append("["); - } - if (ExpressionNeedsBrackets(methodDefinition.Key)) - { - Append("("); + _writeContext.SetNodeProperty(nameof(methodDefinition.Decorators), static node => ref node.As().Decorators); + VisitAuxiliaryNodeList(methodDefinition.Decorators, separator: string.Empty); + + _writeContext.ClearNodeProperty(); } - Visit(methodDefinition.Key); - if (ExpressionNeedsBrackets(methodDefinition.Key)) + + if (methodDefinition.Static) { - Append(")"); + _writeContext.SetNodeProperty(nameof(methodDefinition.Static), static node => node.As().Static); + Writer.WriteKeyword("static", TokenFlags.SurroundingSpaceRecommended, in _writeContext); } - if (methodDefinition.Key is MemberExpression || ExpressionNeedsBrackets(methodDefinition.Key)) + + switch (methodDefinition.Kind) { - Append("]"); + case PropertyKind.Get: + _writeContext.SetNodeProperty(nameof(methodDefinition.Kind), static node => node.As().Kind); + Writer.WriteKeyword("get", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + break; + case PropertyKind.Set: + _writeContext.SetNodeProperty(nameof(methodDefinition.Kind), static node => node.As().Kind); + Writer.WriteKeyword("set", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + break; } - Visit(methodDefinition.Value); + + _writeContext.SetNodeProperty(nameof(methodDefinition.Value), static node => node.As().Value); + VisitRootExpression(methodDefinition.Value, ExpressionFlags.IsMethod | RootExpressionFlags(needsBrackets: false)); return methodDefinition; } protected internal override object? VisitForOfStatement(ForOfStatement forOfStatement) { - Append("for("); - Visit(forOfStatement.Left); - Append(" of "); - Visit(forOfStatement.Right); - Append(")"); - AppendBeautificationSpace(); + Writer.WriteKeyword("for", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - if (forOfStatement.Body is not BlockStatement) + if (forOfStatement.Await) { - AppendBeautificationNewline(); - IncreaseIndent(); - AppendBeautificationIndent(); + _writeContext.SetNodeProperty(nameof(forOfStatement.Await), static node => node.As().Await); + Writer.WriteKeyword("await", TokenFlags.SurroundingSpaceRecommended, in _writeContext); } - Visit(forOfStatement.Body); - if (NodeNeedsSemicolon(forOfStatement.Body)) + + Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, in _writeContext); + + _writeContext.SetNodeProperty(nameof(forOfStatement.Left), static node => node.As().Left); + + if (forOfStatement.Left is VariableDeclaration variableDeclaration) { - Append(";"); + VisitStatement(variableDeclaration, StatementFlags.NestedVariableDeclaration); } - if (forOfStatement.Body is not BlockStatement) + else { - DecreaseIndent(); + VisitAuxiliaryNode(forOfStatement.Left); } + _writeContext.ClearNodeProperty(); + Writer.WriteKeyword("of", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _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, in _writeContext); + + _writeContext.SetNodeProperty(nameof(forOfStatement.Body), static node => node.As().Body); + VisitStatement(forOfStatement.Body, StatementBodyFlags(isRightMost: true)); + return forOfStatement; } protected internal override object? VisitClassDeclaration(ClassDeclaration classDeclaration) { - Append("class "); - if (classDeclaration.Id is not null) + if (classDeclaration.Decorators.Count > 0) { - Visit(classDeclaration.Id); - } + _writeContext.SetNodeProperty(nameof(classDeclaration.Decorators), static node => ref node.As().Decorators); + VisitAuxiliaryNodeList(classDeclaration.Decorators, separator: string.Empty); - if (classDeclaration.SuperClass is not null) - { - Append(" extends "); - Visit(classDeclaration.SuperClass); + _writeContext.ClearNodeProperty(); } - AppendBeautificationSpace(); - Append("{"); + Writer.WriteKeyword("class", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - AppendBeautificationNewline(); - IncreaseIndent(); - AppendBeautificationIndent(); + if (classDeclaration.Id is not null) + { + _writeContext.SetNodeProperty(nameof(classDeclaration.Id), static node => node.As().Id); + VisitAuxiliaryNode(classDeclaration.Id); + } - Visit(classDeclaration.Body); + if (classDeclaration.SuperClass is not null) + { + _writeContext.ClearNodeProperty(); + Writer.WriteKeyword("extends", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - AppendBeautificationNewline(); - DecreaseIndent(); - AppendBeautificationIndent(); + _writeContext.SetNodeProperty(nameof(classDeclaration.SuperClass), static node => node.As().SuperClass); + VisitRootExpression(classDeclaration.SuperClass, LeftHandSideRootExpressionFlags(needsBrackets: false)); + } - Append("}"); + _writeContext.SetNodeProperty(nameof(classDeclaration.Body), static node => node.As().Body); + VisitAuxiliaryNode(classDeclaration.Body); return classDeclaration; } protected internal override object? VisitClassBody(ClassBody classBody) { - VisitNodeList(classBody.Body, addLineBreaks: true); + _writeContext.SetNodeProperty(nameof(classBody.Body), static node => ref node.As().Body); + Writer.StartBlock(classBody.Body.Count, in _writeContext); + + VisitAuxiliaryNodeList(in classBody.Body, separator: string.Empty); + + Writer.EndBlock(classBody.Body.Count, in _writeContext); return classBody; } protected internal override object? VisitYieldExpression(YieldExpression yieldExpression) { - Append("yield "); + Writer.WriteKeyword("yield", (!yieldExpression.Delegate && yieldExpression.Argument is not null).ToFlag(TokenFlags.TrailingSpaceRecommended), in _writeContext); + + if (yieldExpression.Delegate) + { + _writeContext.SetNodeProperty(nameof(yieldExpression.Delegate), static node => node.As().Delegate); + Writer.WritePunctuator("*", (yieldExpression.Argument is not null).ToFlag(TokenFlags.TrailingSpaceRecommended), in _writeContext); + } + if (yieldExpression.Argument is not null) { - Visit(yieldExpression.Argument); + 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; @@ -973,79 +1155,129 @@ public void Convert(Node node) protected internal override object? VisitTaggedTemplateExpression(TaggedTemplateExpression taggedTemplateExpression) { - Visit(taggedTemplateExpression.Tag); - Visit(taggedTemplateExpression.Quasi); + _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? VisitSuper(Super super) { - Append("super"); + Writer.WriteKeyword("super", in _writeContext); return super; } protected internal override object? VisitMetaProperty(MetaProperty metaProperty) { - Visit(metaProperty.Meta); - Append("."); - Visit(metaProperty.Property); + _writeContext.SetNodeProperty(nameof(metaProperty.Meta), static node => node.As().Meta); + Writer.WriteKeyword(metaProperty.Meta.Name!, in _writeContext); + + _writeContext.ClearNodeProperty(); + Writer.WritePunctuator(".", TokenFlags.InBetween, in _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? VisitObjectPattern(ObjectPattern objectPattern) { - Append("{"); - VisitNodeList(objectPattern.Properties, appendSeperatorString: ","); - Append("}"); + _writeContext.SetNodeProperty(nameof(objectPattern.Properties), static node => ref node.As().Properties); + + Writer.StartObject(objectPattern.Properties.Count, in _writeContext); + + VisitAuxiliaryNodeList(in objectPattern.Properties, separator: ","); + + Writer.EndObject(objectPattern.Properties.Count, in _writeContext); return objectPattern; } protected internal override object? VisitSpreadElement(SpreadElement spreadElement) { - Append("..."); - Visit(spreadElement.Argument); + var argumentNeedsBrackets = UnaryOperandNeedsBrackets(spreadElement, spreadElement.Argument); + + _writeContext.SetNodeProperty(nameof(spreadElement.Argument), static node => node.As().Argument); + Writer.WritePunctuator("...", TokenFlags.Leading, in _writeContext); + + VisitSubExpression(spreadElement.Argument, SubExpressionFlags(argumentNeedsBrackets, isLeftMost: false)); return spreadElement; } protected internal override object? VisitAssignmentPattern(AssignmentPattern assignmentPattern) { - Visit(assignmentPattern.Left); - Append("="); - Visit(assignmentPattern.Right); + _writeContext.SetNodeProperty(nameof(assignmentPattern.Left), static node => node.As().Left); + VisitAuxiliaryNode(assignmentPattern.Left); + + _writeContext.ClearNodeProperty(); + Writer.WritePunctuator("=", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + _writeContext.SetNodeProperty(nameof(assignmentPattern.Right), static node => node.As().Right); + VisitAuxiliaryNode(assignmentPattern.Right); return assignmentPattern; } protected internal override object? VisitArrayPattern(ArrayPattern arrayPattern) { - Append("["); - VisitNodeList(arrayPattern.Elements, appendSeperatorString: ","); - Append("]"); + _writeContext.SetNodeProperty(nameof(arrayPattern.Elements), static node => ref node.As().Elements); + + Writer.StartArray(arrayPattern.Elements.Count, in _writeContext); + + // Elements need special care because it may contain null values denoting omitted elements. + + Writer.StartAuxiliaryNodeList(arrayPattern.Elements.Count, in _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, in _writeContext); + if (element is not null) + { + Visit(element); + } + Writer.EndAuxiliaryNodeListItem(i, arrayPattern.Elements.Count, separator: ",", _currentAuxiliaryNodeContext, in _writeContext); + + _currentAuxiliaryNodeContext = originalAuxiliaryNodeContext; + } + + Writer.EndAuxiliaryNodeList(arrayPattern.Elements.Count, in _writeContext); + + Writer.EndArray(arrayPattern.Elements.Count, in _writeContext); return arrayPattern; } protected internal override object? VisitVariableDeclarator(VariableDeclarator variableDeclarator) { - Visit(variableDeclarator.Id); + _writeContext.SetNodeProperty(nameof(variableDeclarator.Id), static node => node.As().Id); + VisitAuxiliaryNode(variableDeclarator.Id); + if (variableDeclarator.Init is not null) { - AppendBeautificationSpace(); - Append("="); - AppendBeautificationSpace(); - if (ExpressionNeedsBrackets(variableDeclarator.Init)) + _writeContext.ClearNodeProperty(); + Writer.WritePunctuator("=", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + _writeContext.SetNodeProperty(nameof(variableDeclarator.Init), static node => node.As().Init); + + if (_currentAuxiliaryNodeContext != s_forInLoopDeclarationFlag) { - Append("("); + VisitRootExpression(variableDeclarator.Init, RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(variableDeclarator.Init))); } - Visit(variableDeclarator.Init); - if (ExpressionNeedsBrackets(variableDeclarator.Init)) + else { - Append(")"); + VisitRootExpression(variableDeclarator.Init, ExpressionFlags.InOperatorIsAmbiguousInDeclaration | RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(variableDeclarator.Init))); } } @@ -1054,72 +1286,89 @@ public void Convert(Node node) protected internal override object? VisitTemplateLiteral(TemplateLiteral templateLiteral) { - Append("`"); - for (int n = 0; n < templateLiteral.Quasis.Count; n++) + Writer.WritePunctuator("`", TokenFlags.Leading, in _writeContext); + + TemplateElement quasi; + for (var i = 0; !(quasi = templateLiteral.Quasis[i]).Tail; i++) { - Visit(templateLiteral.Quasis[n]); - if (templateLiteral.Expressions.Count > n) - { - Append("${"); - Visit(templateLiteral.Expressions[n]); - Append("}"); - } + _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, in _writeContext); + VisitRootExpression(templateLiteral.Expressions[i], RootExpressionFlags(needsBrackets: false)); + Writer.WritePunctuator("}", TokenFlags.Trailing, in _writeContext); } - Append("`"); + + _writeContext.SetNodeProperty(nameof(templateLiteral.Quasis), static node => ref node.As().Quasis); + VisitAuxiliaryNode(quasi); + + Writer.WritePunctuator("`", TokenFlags.Trailing, in _writeContext); return templateLiteral; } protected internal override object? VisitTemplateElement(TemplateElement templateElement) { - Append(templateElement.Value.Raw); + _writeContext.SetNodeProperty(nameof(templateElement.Value), static node => node.As().Value); + Writer.WriteLiteral(templateElement.Value.Raw, TokenType.Template, in _writeContext); return templateElement; } protected internal override object? VisitRestElement(RestElement restElement) { - Append("..."); - Visit(restElement.Argument); + _writeContext.SetNodeProperty(nameof(restElement.Argument), static node => node.As().Argument); + Writer.WritePunctuator("...", TokenFlags.Leading, in _writeContext); + + VisitAuxiliaryNode(restElement.Argument); return restElement; } protected internal override object? VisitProperty(Property property) { - if (property.Key is MemberExpression || ExpressionNeedsBrackets(property.Key)) - { - Append("["); - } - if (ExpressionNeedsBrackets(property.Key)) - { - Append("("); - } - Visit(property.Key); - if (ExpressionNeedsBrackets(property.Key)) + bool isMethod; + + switch (property.Kind) { - Append(")"); + case PropertyKind.Get: + _writeContext.SetNodeProperty(nameof(property.Kind), static node => node.As().Kind); + Writer.WriteKeyword("get", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + isMethod = true; + break; + case PropertyKind.Set: + _writeContext.SetNodeProperty(nameof(property.Kind), static node => node.As().Kind); + Writer.WriteKeyword("set", TokenFlags.SurroundingSpaceRecommended, in _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, in _writeContext); + } + + isMethod = false; + break; } - if (property.Key is MemberExpression || ExpressionNeedsBrackets(property.Key)) + + _writeContext.SetNodeProperty(nameof(property.Value), static node => node.As().Value); + + if (ParentNode is { Type: Nodes.ObjectPattern }) { - Append("]"); + VisitAuxiliaryNode(property.Value); } - if (property.Key is Identifier keyI && property.Value is Identifier valueI && keyI.Name == valueI.Name) - { } else { - AppendBeautificationSpace(); - Append(":"); - AppendBeautificationSpace(); - if (property.Value is not ObjectPattern && ExpressionNeedsBrackets(property.Value)) - { - Append("("); - } - Visit(property.Value); - if (property.Value is not ObjectPattern && ExpressionNeedsBrackets(property.Value)) - { - Append(")"); - } + var expression = property.Value.As(); + VisitRootExpression(expression, isMethod.ToFlag(ExpressionFlags.IsMethod) | RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(expression))); } return property; @@ -1127,358 +1376,707 @@ public void Convert(Node node) protected internal override object? VisitPropertyDefinition(PropertyDefinition propertyDefinition) { - if (propertyDefinition.Static) - { - Append("static "); - } - if (propertyDefinition.Key is MemberExpression || ExpressionNeedsBrackets(propertyDefinition.Key)) - { - Append("["); - } - if (ExpressionNeedsBrackets(propertyDefinition.Key)) + if (propertyDefinition.Decorators.Count > 0) { - Append("("); - } - Visit(propertyDefinition.Key); - if (ExpressionNeedsBrackets(propertyDefinition.Key)) - { - Append(")"); + _writeContext.SetNodeProperty(nameof(propertyDefinition.Decorators), static node => ref node.As().Decorators); + VisitAuxiliaryNodeList(propertyDefinition.Decorators, separator: string.Empty); + + _writeContext.ClearNodeProperty(); } - if (propertyDefinition.Key is MemberExpression || ExpressionNeedsBrackets(propertyDefinition.Key)) + + if (propertyDefinition.Static) { - Append("]"); + _writeContext.SetNodeProperty(nameof(propertyDefinition.Static), static node => node.As().Static); + Writer.WriteKeyword("static", TokenFlags.SurroundingSpaceRecommended, in _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) { - Append("="); - Visit(propertyDefinition.Value); + _writeContext.ClearNodeProperty(); + Writer.WritePunctuator("=", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + _writeContext.SetNodeProperty(nameof(propertyDefinition.Value), static node => node.As().Value); + VisitRootExpression(propertyDefinition.Value, RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(propertyDefinition.Value))); } - Append(";"); + + Writer.WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); return propertyDefinition; } protected internal override object? VisitAwaitExpression(AwaitExpression awaitExpression) { - Append("await "); - Visit(awaitExpression.Argument); + Writer.WriteKeyword("await", TokenFlags.TrailingSpaceRecommended, in _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? VisitConditionalExpression(ConditionalExpression conditionalExpression) { - if (conditionalExpression.Test is AssignmentExpression) - { - Append("("); - } - Visit(conditionalExpression.Test); - if (conditionalExpression.Test is AssignmentExpression) - { - Append(")"); - } - AppendBeautificationSpace(); - Append("?"); - AppendBeautificationSpace(); - if (ExpressionNeedsBrackets(conditionalExpression.Consequent)) - { - Append("("); - } - Visit(conditionalExpression.Consequent); - if (ExpressionNeedsBrackets(conditionalExpression.Consequent)) - { - Append(")"); - } - AppendBeautificationSpace(); - Append(":"); - AppendBeautificationSpace(); - if (ExpressionNeedsBrackets(conditionalExpression.Alternate)) - { - Append("("); - } - Visit(conditionalExpression.Alternate); - if (ExpressionNeedsBrackets(conditionalExpression.Alternate)) - { - Append(")"); - } + // 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, in _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, in _writeContext); + + VisitSubExpression(conditionalExpression.Alternate, SubExpressionFlags(operandNeedsBrackets, isLeftMost: false)); return conditionalExpression; } protected internal override object? VisitCallExpression(CallExpression callExpression) { - if (ExpressionNeedsBrackets(callExpression.Callee)) - { - Append("("); - } - Visit(callExpression.Callee); - if (ExpressionNeedsBrackets(callExpression.Callee)) + 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) { - Append(")"); + _writeContext.ClearNodeProperty(); + Writer.WritePunctuator("?.", TokenFlags.InBetween, in _writeContext); } - Append("("); - VisitNodeList(callExpression.Arguments, appendSeperatorString: ",", appendBracketsIfNeeded: true); - Append(")"); + + _writeContext.SetNodeProperty(nameof(callExpression.Arguments), static node => ref node.As().Arguments); + Writer.WritePunctuator("(", TokenFlags.Leading, in _writeContext); + VisitSubExpressionList(in callExpression.Arguments); + Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); return callExpression; } protected internal override object? VisitBinaryExpression(BinaryExpression binaryExpression) { - if (ExpressionNeedsBrackets(binaryExpression.Left)) - { - Append("("); - } - Visit(binaryExpression.Left); - if (ExpressionNeedsBrackets(binaryExpression.Left)) + 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) { - Append(")"); + 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])) { - Append(" "); - } - else - { - AppendBeautificationSpace(); - } - Append(op); - if (char.IsLetter(op[0])) - { - Append(" "); + Writer.WriteKeyword(op, TokenFlags.SurroundingSpaceRecommended, in _writeContext); } else { - AppendBeautificationSpace(); - } - if (ExpressionNeedsBrackets(binaryExpression.Right)) - { - Append("("); - } - Visit(binaryExpression.Right); - if (ExpressionNeedsBrackets(binaryExpression.Right)) - { - Append(")"); + Writer.WritePunctuator(op, TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _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? VisitArrayExpression(ArrayExpression arrayExpression) { - Append("["); - VisitNodeList(arrayExpression.Elements, appendSeperatorString: ","); - Append("]"); + _writeContext.SetNodeProperty(nameof(arrayExpression.Elements), static node => ref node.As().Elements); + + Writer.StartArray(arrayExpression.Elements.Count, in _writeContext); + + // Elements need special care because it may contain null values denoting omitted elements. + + Writer.StartExpressionList(arrayExpression.Elements.Count, in _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, in _writeContext); + Writer.EndExpressionListItem(i, arrayExpression.Elements.Count, (JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, in _writeContext); + + _currentExpressionFlags = originalExpressionFlags; + } + } + + Writer.EndExpressionList(arrayExpression.Elements.Count, in _writeContext); + + Writer.EndArray(arrayExpression.Elements.Count, in _writeContext); return arrayExpression; } protected internal override object? VisitAssignmentExpression(AssignmentExpression assignmentExpression) { - if (assignmentExpression.Left is ObjectPattern) - { - Append("("); - } + _writeContext.SetNodeProperty(nameof(assignmentExpression.Left), static node => node.As().Left); + VisitAuxiliaryNode(assignmentExpression.Left); + var op = AssignmentExpression.GetAssignmentOperatorToken(assignmentExpression.Operator); - Visit(assignmentExpression.Left); - AppendBeautificationSpace(); - Append(op); - AppendBeautificationSpace(); - if (ExpressionNeedsBrackets(assignmentExpression.Right) && !(assignmentExpression.Right is AssignmentExpression)) - { - Append("("); - } - Visit(assignmentExpression.Right); - if (ExpressionNeedsBrackets(assignmentExpression.Right) && !(assignmentExpression.Right is AssignmentExpression)) - { - Append(")"); - } - if (assignmentExpression.Left is ObjectPattern) - { - Append(")"); - } + + _writeContext.SetNodeProperty(nameof(assignmentExpression.Operator), static node => node.As().Operator); + Writer.WritePunctuator(op, TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _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? VisitContinueStatement(ContinueStatement continueStatement) { - Append("continue "); + Writer.WriteKeyword("continue", TokenFlags.LeadingSpaceRecommended, in _writeContext); + if (continueStatement.Label is not null) { - Visit(continueStatement.Label); + _writeContext.SetNodeProperty(nameof(continueStatement.Label), static node => node.As().Label); + VisitRootExpression(continueStatement.Label, RootExpressionFlags(needsBrackets: false)); } + StatementNeedsSemicolon(); + return continueStatement; } protected internal override object? VisitBreakStatement(BreakStatement breakStatement) { + Writer.WriteKeyword("break", TokenFlags.LeadingSpaceRecommended, in _writeContext); + if (breakStatement.Label is not null) { - Visit(breakStatement.Label); + _writeContext.SetNodeProperty(nameof(breakStatement.Label), static node => node.As().Label); + VisitRootExpression(breakStatement.Label, RootExpressionFlags(needsBrackets: false)); } - Append("break"); + + StatementNeedsSemicolon(); return breakStatement; } protected internal override object? VisitBlockStatement(BlockStatement blockStatement) { - Append("{"); + _writeContext.SetNodeProperty(nameof(blockStatement.Body), static node => ref node.As().Body); + Writer.StartBlock(blockStatement.Body.Count, in _writeContext); - AppendBeautificationNewline(); - IncreaseIndent(); - AppendBeautificationIndent(); + VisitStatementList(in blockStatement.Body); - VisitNodeList(blockStatement.Body, appendAtEnd: ";", addLineBreaks: true); + Writer.EndBlock(blockStatement.Body.Count, in _writeContext); - AppendBeautificationNewline(); - DecreaseIndent(); - AppendBeautificationIndent(); + return blockStatement; + } - Append("}"); + protected internal override object? VisitPrivateIdentifier(PrivateIdentifier privateIdentifier) + { + _writeContext.SetNodeProperty(nameof(privateIdentifier.Name), static node => node.As().Name); + Writer.WritePunctuator("#", TokenFlags.Leading, in _writeContext); + Writer.WriteIdentifier(privateIdentifier.Name!, in _writeContext); - return blockStatement; + return privateIdentifier; } + protected internal override object? VisitStaticBlock(StaticBlock staticBlock) + { + Writer.WriteKeyword("static", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + _writeContext.SetNodeProperty(nameof(staticBlock.Body), static node => ref node.As().Body); + Writer.StartBlock(staticBlock.Body.Count, in _writeContext); + + VisitStatementList(in staticBlock.Body); + + Writer.EndBlock(staticBlock.Body.Count, in _writeContext); + + return staticBlock; + } + + protected internal override object? VisitDecorator(Decorator decorator) + { + // https://github.com/tc39/proposal-decorators + + Writer.WritePunctuator("@", TokenFlags.Leading | (ParentNode is not Expression).ToFlag(TokenFlags.LeadingSpaceRecommended), in _writeContext); + + _writeContext.SetNodeProperty(nameof(decorator.Expression), static node => node.As().Expression); + VisitRootExpression(decorator.Expression, LeftHandSideRootExpressionFlags(needsBrackets: false)); + + Writer.WriteEpsilon(TokenFlags.TrailingSpaceRecommended, in _writeContext); + + return decorator; + } + + 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, in _writeContext); + + _writeContext.SetNodeProperty(nameof(importAttribute.Value), static node => node.As().Value); + + VisitRootExpression(importAttribute.Value, RootExpressionFlags(needsBrackets: false)); + + return importAttribute; + } + + #region Statements + [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void VisitNodeList(IEnumerable nodeList, string? appendAtEnd = null, string? appendSeperatorString = null, bool appendBracketsIfNeeded = false, bool addLineBreaks = false) - where TNode : Node + 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) { - var notfirst = false; - foreach (var node in nodeList) + // 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, in _writeContext); + Visit(statement); + Writer.EndStatement((JavascriptTextWriter.StatementFlags) _currentStatementFlags, in _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, in _writeContext); + + for (var i = 0; i < statementList.Count; i++) { - if (node is not null) - { - if (notfirst && appendSeperatorString is not null) - { - Append(appendSeperatorString); - } - if (notfirst && addLineBreaks) - { - AppendBeautificationNewline(); - AppendBeautificationIndent(); - } - if (appendBracketsIfNeeded && ExpressionNeedsBrackets(node)) - { - Append("("); - } - Visit(node); - if (appendBracketsIfNeeded && ExpressionNeedsBrackets(node)) - { - Append(")"); - } - notfirst = true; - if (appendAtEnd is not null && NodeNeedsSemicolon(node)) - { - Append(appendAtEnd); - } - } + VisitStatementListItem(statementList[i], i, statementList.Count, getCombinedItemFlags); } + + Writer.EndStatementList(statementList.Count, in _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, in _writeContext); + Visit(statement); + Writer.EndStatementListItem(index, count, (JavascriptTextWriter.StatementFlags) _currentStatementFlags, in _writeContext); + + _currentStatementFlags = originalStatementFlags; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void StatementNeedsSemicolon() => _currentStatementFlags |= StatementFlags.NeedsSemicolon; + + #endregion + + #region Expressions + + [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); } - public override string ToString() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private protected static ExpressionFlags SubExpressionFlags(bool needsBrackets, bool isLeftMost) { - return _writer.ToString(); + return needsBrackets.ToFlag(ExpressionFlags.NeedsBrackets) | isLeftMost.ToFlag(ExpressionFlags.IsLeftMost); } - public bool IsAsync(Node node) + protected ExpressionFlags PropagateExpressionFlags(ExpressionFlags flags) { - if (node is ArrowFunctionExpression afe) + 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)) { - return afe.Async; + flags &= ~isLeftMostFlags; } - if (node is ArrowParameterPlaceHolder apph) + else { - return apph.Async; + flags = flags & ~isLeftMostFlags | _currentExpressionFlags & isLeftMostFlags; } - if (node is FunctionDeclaration fd) + + // 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 fd.Async; + return flags & ~ExpressionFlags.InOperatorIsAmbiguousInDeclaration; } - if (node is FunctionExpression fe) + + // Puts the left-most expression in brackets if necessary (in cases where it would be interpreted differently without brackets). + if ((flags & ExpressionFlags.IsInPotentiallyAmbiguousContext) != 0) { - return fe.Async; + 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 false; + + return flags; } - public bool NodeNeedsSemicolon(Node? node) + 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) { - if (node is BlockStatement || - node is IfStatement || - node is SwitchStatement || - node is ForInStatement || - node is ForOfStatement || - node is ForStatement || - node is FunctionDeclaration || - node is ReturnStatement || - node is ThrowStatement || - node is TryStatement || - node is EmptyStatement || - node is ClassDeclaration) - { - return false; - } - if (node is ExportNamedDeclaration end) + 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, in _writeContext); + Visit(expression); + Writer.EndExpression((JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, in _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, in _writeContext); + + for (var i = 0; i < expressionList.Count; i++) { - return NodeNeedsSemicolon(end.Declaration); + VisitExpressionListItem(expressionList[i], i, expressionList.Count, getCombinedItemFlags); } - return true; + + Writer.EndExpressionList(expressionList.Count, in _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, in _writeContext); + Visit(expression); + Writer.EndExpressionListItem(index, count, (JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, in _writeContext); + + _currentExpressionFlags = originalExpressionFlags; + } + + private void VisitAssertions(in NodeList assertions) + { + // https://github.com/tc39/proposal-import-assertions + + Writer.WriteKeyword("assert", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + Writer.StartObject(assertions.Count, in _writeContext); + + VisitAuxiliaryNodeList(in assertions, separator: ","); + + Writer.EndObject(assertions.Count, in _writeContext); } - public bool ExpressionNeedsBrackets(Node? node) + private void VisitExportOrImportSpecifierIdentifier(Expression identifierExpression) { - if (node is FunctionExpression) + if (identifierExpression is Identifier identifier && identifier.Name == "default") { - return true; + Writer.WriteKeyword("default", in _writeContext); } - if (node is ArrowFunctionExpression) + else { - return true; + VisitRootExpression(identifierExpression, RootExpressionFlags(needsBrackets: false)); } - if (node is AssignmentExpression) + } + + private void VisitPropertyKey(Expression key, bool computed, TokenFlags leadingBracketFlags = TokenFlags.None, TokenFlags trailingBracketFlags = TokenFlags.None) + { + if (computed) { - return true; + Writer.WritePunctuator("[", TokenFlags.Leading | leadingBracketFlags, in _writeContext); + VisitRootExpression(key, RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(key))); + Writer.WritePunctuator("]", TokenFlags.Trailing | trailingBracketFlags, in _writeContext); } - if (node is SequenceExpression) + else if (key.Type == Nodes.Identifier) { - return true; + VisitAuxiliaryNode(key); } - if (node is ConditionalExpression) + else { - return true; + VisitRootExpression(key, RootExpressionFlags(needsBrackets: false)); } - if (node is BinaryExpression) + } + + protected virtual bool ExpressionIsAmbiguousAsStatementExpression(Expression expression) + { + switch (expression.Type) { - return true; + 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; } - if (node is UnaryExpression) + + return false; + } + + protected virtual bool ExpressionIsAmbiguousAsArrowFunctionBody(Expression expression) + { + switch (expression.Type) { - return true; + case Nodes.ObjectExpression: + case Nodes.AssignmentExpression when expression.As() is { Left.Type: Nodes.ObjectPattern }: + return true; } - if (node is CallExpression) + + return false; + } + + protected virtual bool ExpressionIsAmbiguousAsNewCallee(Expression expression) + { + switch (expression.Type) { - return true; + case Nodes.CallExpression: + return true; } - if (node is NewExpression) + + return false; + } + + protected virtual bool LeftHandSideExpressionIsParenthesized(Expression expression) + { + // https://tc39.es/ecma262/#sec-left-hand-side-expressions + + switch (expression.Type) { - return true; + 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; } - if (node is ObjectPattern) + + return false; + } + + protected virtual bool ExpressionNeedsBracketsInList(Expression expression) + { + return expression.Type is + Nodes.SequenceExpression; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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 { - return true; + result |= BinaryOperationFlags.LeftOperandNeedsBrackets; } - if (node is ArrayPattern) + + if (operationPrecedence > rightOperandPrecedence || operationPrecedence == rightOperandPrecedence && associativity < 0) // left-to-right associativity { - return true; + result |= BinaryOperationFlags.RightOperandNeedsBrackets; } - if (node is YieldExpression) + + return result; + } + + #endregion + + #region Auxiliary nodes + + [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, in _writeContext); + Visit(node); + Writer.EndAuxiliaryNode(_currentAuxiliaryNodeContext, in _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, in _writeContext); + + for (var i = 0; i < nodeList.Count; i++) { - return true; + VisitAuxiliaryNodeListItem(nodeList[i], i, nodeList.Count, separator, getNodeContext); } - return false; + + Writer.EndAuxiliaryNodeList(nodeList.Count, in _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, in _writeContext); + Visit(node); + Writer.EndAuxiliaryNodeListItem(index, count, separator, _currentAuxiliaryNodeContext, in _writeContext); + + _currentAuxiliaryNodeContext = originalAuxiliaryNodeContext; + } + + #endregion } diff --git a/src/Esprima/Utils/AstToJsonConverter.cs b/src/Esprima/Utils/AstToJsonConverter.cs index 8a0565ba..2707df35 100644 --- a/src/Esprima/Utils/AstToJsonConverter.cs +++ b/src/Esprima/Utils/AstToJsonConverter.cs @@ -5,7 +5,6 @@ using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using Esprima.Ast; -using static Esprima.EsprimaExceptionHelper; namespace Esprima.Utils; @@ -21,12 +20,11 @@ public class AstToJsonConverter : AstVisitor public AstToJsonConverter(JsonWriter writer, AstJson.Options options) { - _writer = writer ?? ThrowArgumentNullException(nameof(writer)); + _writer = writer ?? throw new ArgumentNullException(nameof(writer)); if (options is null) { - ThrowArgumentNullException(nameof(options)); - return; + throw new ArgumentNullException(nameof(options)); } _includeLineColumn = options.IncludingLineColumn; @@ -188,7 +186,7 @@ protected void Member(string name, in NodeList list, Func nodeSe public void Convert(Node node) { - Visit(node ?? ThrowArgumentNullException(nameof(node))); + Visit(node ?? throw new ArgumentNullException(nameof(node))); } public override object? Visit(Node? node) @@ -1133,4 +1131,4 @@ public ImportCompat() : base(Nodes.Import) { } return yieldExpression; } -} \ No newline at end of file +} 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) + + /// + /// The punctuator precedes the related token(s). + /// + Leading = 1 << 0, + /// + /// The punctuator is somewhere in the middle of the related token(s). + /// + InBetween = 1 << 1, + /// + /// The punctuator follows the related token(s). + /// + Trailing = 1 << 2, + + // Whitespace hints for keywords + + /// + /// The keyword follows the body of a statement and precedes another body of the same statement (e.g. the else branch of an ). + /// + FollowsStatementBody = StatementFlags.IsStatementBody, + + // General whitespace hints + + /// + /// A leading space is recommended for the current token (unless other white-space precedes it). + /// + /// + /// May or may not be respected. (It is decided by the actual implementation.) + /// + LeadingSpaceRecommended = 1 << 14, + /// + /// A trailing space is recommended for the current token (unless other white-space follows it). + /// + /// + /// May or may not be respected. (It is decided by the actual implementation.) + /// + TrailingSpaceRecommended = 1 << 15, + + /// + /// Surrounding spaces are recommended for the current token (unless other white-spaces surround it). + /// + /// + /// May or may not be respected. (It is decided by the actual implementation.) + /// + SurroundingSpaceRecommended = LeadingSpaceRecommended | TrailingSpaceRecommended, + } + + [Flags] + public enum StatementFlags + { + // Notes for maintainers: + // Don't use the high-order word as it's reserved for internal use (see AstToJavascriptConverter.StatementFlags) + + None = 0, + /// + /// The statement must be terminated with a semicolon. + /// + NeedsSemicolon = 1 << 0, + /// + /// If is set, determines if the semicolon can be omitted when the statement comes last in the current block (see ). + /// + /// + /// Automatically propagated to child statements, should be set directly only for statement list items. + /// Whether the semicolon is omitted or not is decided by the actual implementation. + /// + MayOmitRightMostSemicolon = 1 << 1, + /// + /// The statement comes last in the current statement list (more precisely, it is the right-most part in the textual representation of the current statement list). + /// + /// + /// In the the visitation handlers of the flag is interpreted differently: it indicates that the statement comes last in the parent statement. + /// (Upon visiting a statement, this flag of the parent and child statement gets combined to determine its effective value for the current statement list.) + /// + IsRightMost = 1 << 2, + /// + /// The statement represents the body of another statement (e.g. the if branch of an ). + /// + IsStatementBody = 1 << 3, + } + + [Flags] + public enum ExpressionFlags + { + // Notes for maintainers: + // Don't use the high-order word as it's reserved for internal use (see AstToJavascriptConverter.ExpressionFlags) + + None = 0, + /// + /// The expression must be wrapped in brackets. + /// + NeedsBrackets = 1 << 0, + /// + /// The expression comes first in the current expression tree, more precisely, it is the left-most part in the textual representation of the currently visited expression tree (incl. brackets). + /// + /// + /// In the the visitation handlers of the flag is interpreted differently: it indicates that the expression comes first in the parent expression. + /// (Upon visiting an expression, this flag of the parent and child expression gets combined to determine its effective value for the expression tree.) + /// + IsLeftMost = 1 << 1, + + // White-space hints + + SpaceBeforeBracketsRecommended = 1 << 14, + SpaceAfterBracketsRecommended = 1 << 15, + SpaceAroundBracketsRecommended = SpaceBeforeBracketsRecommended | SpaceAfterBracketsRecommended, + } +} diff --git a/src/Esprima/Utils/JavascriptTextWriter.WriteContext.cs b/src/Esprima/Utils/JavascriptTextWriter.WriteContext.cs new file mode 100644 index 00000000..4f47dfe5 --- /dev/null +++ b/src/Esprima/Utils/JavascriptTextWriter.WriteContext.cs @@ -0,0 +1,88 @@ +using System.Runtime.CompilerServices; +using Esprima.Ast; + +namespace Esprima.Utils; + +partial class JavascriptTextWriter +{ + public struct WriteContext + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public WriteContext From(Node? parentNode, Node node) => + new WriteContext(parentNode, node ?? throw new ArgumentNullException(nameof(node))); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal WriteContext(Node? parentNode, Node node) + { + ParentNode = parentNode; + Node = node; + _nodePropertyName = null; + _nodePropertyValueAccessor = null; + } + + public Node? ParentNode { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } + public Node Node { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } + + private string? _nodePropertyName; + public string? NodePropertyName { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _nodePropertyName; } + + private Delegate? _nodePropertyValueAccessor; + private Delegate NodePropertyAccessor + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _nodePropertyValueAccessor ?? throw new InvalidOperationException("The context has no associated node property."); + } + + public bool NodePropertyHasListValue + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => NodePropertyAccessor.GetType().IsGenericType; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Type GetNodePropertyListItemType() + { + var type = NodePropertyAccessor.GetType(); + return type.IsGenericType + ? type.GetGenericArguments()[0] + : throw new InvalidOperationException("The context has an associated node property but its value is not a node list."); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T GetNodePropertyValue() => + (T) ((NodePropertyValueAccessor) NodePropertyAccessor)(Node)!; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref readonly NodeList GetNodePropertyListValue() where T : Node? => + ref ((NodePropertyListValueAccessor) NodePropertyAccessor)(Node); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ClearNodeProperty() + { + _nodePropertyName = null; + _nodePropertyValueAccessor = null; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void SetNodeProperty(string name, NodePropertyValueAccessor valueAccessor) + { + _nodePropertyName = name; + _nodePropertyValueAccessor = valueAccessor; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ChangeNodeProperty(string name, NodePropertyValueAccessor valueAccessor) => + SetNodeProperty(name ?? throw new ArgumentNullException(nameof(name)), valueAccessor ?? throw new ArgumentNullException(nameof(valueAccessor))); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void SetNodeProperty(string name, NodePropertyListValueAccessor listValueAccessor) where T : Node? + { + _nodePropertyName = name; + _nodePropertyValueAccessor = listValueAccessor; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ChangeNodeProperty(string name, NodePropertyListValueAccessor listValueAccessor) where T : Node? => + SetNodeProperty(name ?? throw new ArgumentNullException(nameof(name)), listValueAccessor ?? throw new ArgumentNullException(nameof(listValueAccessor))); + } +} diff --git a/src/Esprima/Utils/JavascriptTextWriter.cs b/src/Esprima/Utils/JavascriptTextWriter.cs new file mode 100644 index 00000000..9ae4a4c2 --- /dev/null +++ b/src/Esprima/Utils/JavascriptTextWriter.cs @@ -0,0 +1,375 @@ +using System.Runtime.CompilerServices; +using Esprima.Ast; + +namespace Esprima.Utils; + +public delegate object? NodePropertyValueAccessor(Node node); + +public delegate ref readonly NodeList NodePropertyListValueAccessor(Node node) where T : Node?; + +/// +/// Base Javascript text writer (code formatter) which uses the most compact possible (i.e. minimal) format. +/// +public partial class JavascriptTextWriter +{ + public record class Options + { + public static readonly Options Default = new(); + } + + public delegate JavascriptTextWriter Factory(TextWriter writer, Options options); + + private readonly TextWriter _writer; + + public JavascriptTextWriter(TextWriter writer, Options options) + { + _writer = writer ?? throw new ArgumentNullException(nameof(writer)); + + if (options is null) + { + throw new ArgumentNullException(nameof(options)); + } + + LastTokenType = TokenType.EOF; + WhiteSpaceWrittenSinceLastToken = true; + } + + protected TokenType LastTokenType { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; [MethodImpl(MethodImplOptions.AggressiveInlining)] private set; } + protected TokenFlags LastTokenFlags { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; [MethodImpl(MethodImplOptions.AggressiveInlining)] private set; } + protected bool WhiteSpaceWrittenSinceLastToken { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; [MethodImpl(MethodImplOptions.AggressiveInlining)] private set; } + + #region White-space + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void WriteSpace() + { + WriteWhiteSpace(" "); + } + + protected void WriteLine() + { + _writer.WriteLine(); + WhiteSpaceWrittenSinceLastToken = true; + } + + protected void WriteWhiteSpace(string value) + { + _writer.Write(value); + WhiteSpaceWrittenSinceLastToken = true; + } + + #endregion + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void ForceRecommendedSpace() + { + LastTokenFlags |= TokenFlags.TrailingSpaceRecommended; + } + + public virtual void WriteEpsilon(TokenFlags flags, in WriteContext context) { } + + #region Identifiers + + protected virtual void StartIdentifier(string value, TokenFlags flags, in WriteContext context) + { + switch (LastTokenType) + { + case TokenType.BigIntLiteral: + case TokenType.BooleanLiteral: + case TokenType.Identifier: + case TokenType.Keyword: + case TokenType.NullLiteral: + case TokenType.NumericLiteral: + case TokenType.RegularExpression: + WriteSpace(); + break; + case TokenType.EOF: + case TokenType.Punctuator: + case TokenType.StringLiteral: + case TokenType.Template: + break; + default: + throw new InvalidOperationException(); + } + } + + public void WriteIdentifier(string value, TokenFlags flags, in WriteContext context) + { + StartIdentifier(value, flags, in context); + _writer.Write(value); + WhiteSpaceWrittenSinceLastToken = false; + EndIdentifier(value, flags, in context); + + LastTokenType = TokenType.Identifier; + LastTokenFlags = flags; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteIdentifier(string value, in WriteContext context) + { + WriteIdentifier(value, TokenFlags.None, in context); + } + + protected virtual void EndIdentifier(string value, TokenFlags flags, in WriteContext context) { } + + #endregion + + #region Keywords + + protected virtual void StartKeyword(string value, TokenFlags flags, in WriteContext context) + { + switch (LastTokenType) + { + case TokenType.BigIntLiteral: + case TokenType.BooleanLiteral: + case TokenType.Identifier: + case TokenType.Keyword: + case TokenType.NullLiteral: + case TokenType.NumericLiteral: + case TokenType.RegularExpression: + WriteSpace(); + break; + case TokenType.EOF: + case TokenType.Punctuator: + case TokenType.StringLiteral: + case TokenType.Template: + break; + default: + throw new InvalidOperationException(); + } + } + + public void WriteKeyword(string value, TokenFlags flags, in WriteContext context) + { + StartKeyword(value, flags, in context); + _writer.Write(value); + WhiteSpaceWrittenSinceLastToken = false; + EndKeyword(value, flags, in context); + + LastTokenType = TokenType.Keyword; + LastTokenFlags = flags; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteKeyword(string value, in WriteContext context) + { + WriteKeyword(value, TokenFlags.None, in context); + } + + protected virtual void EndKeyword(string value, TokenFlags flags, in WriteContext context) { } + + #endregion + + #region Literals + + protected virtual void StartLiteral(string value, TokenType type, TokenFlags flags, in WriteContext context) + { + switch (LastTokenType) + { + case TokenType.BigIntLiteral: + case TokenType.BooleanLiteral: + case TokenType.Identifier: + case TokenType.Keyword: + case TokenType.NullLiteral: + case TokenType.NumericLiteral: + case TokenType.RegularExpression: + if (type is not (TokenType.StringLiteral or TokenType.RegularExpression)) + { + WriteSpace(); + } + break; + case TokenType.EOF: + case TokenType.Punctuator: + case TokenType.StringLiteral: + case TokenType.Template: + break; + default: + throw new InvalidOperationException(); + } + } + + public void WriteLiteral(string value, TokenType type, TokenFlags flags, in WriteContext context) + { + StartLiteral(value, type, flags, in context); + _writer.Write(value); + WhiteSpaceWrittenSinceLastToken = false; + EndLiteral(value, type, flags, in context); + + LastTokenType = type; + LastTokenFlags = flags; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WriteLiteral(string value, TokenType tokenType, in WriteContext context) + { + WriteLiteral(value, tokenType, TokenFlags.None, in context); + } + + protected virtual void EndLiteral(string value, TokenType type, TokenFlags flags, in WriteContext context) { } + + #endregion + + #region Punctuators + + protected virtual void StartPunctuator(string value, TokenFlags flags, in WriteContext context) { } + + public void WritePunctuator(string value, TokenFlags flags, in WriteContext context) + { + StartPunctuator(value, flags, in context); + _writer.Write(value); + WhiteSpaceWrittenSinceLastToken = false; + EndPunctuator(value, flags, in context); + + LastTokenType = TokenType.Punctuator; + LastTokenFlags = flags; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void WritePunctuator(string value, in WriteContext context) + { + WritePunctuator(value, TokenFlags.None, in context); + } + + protected virtual void EndPunctuator(string value, TokenFlags flags, in WriteContext context) { } + + #endregion + + #region Arrays + + public virtual void StartArray(int elementCount, in WriteContext context) + { + WritePunctuator("[", TokenFlags.Leading, in context); + } + + public virtual void EndArray(int elementCount, in WriteContext context) + { + WritePunctuator("]", TokenFlags.Trailing, in context); + } + + #endregion + + #region Objects + + public virtual void StartObject(int propertyCount, in WriteContext context) + { + WritePunctuator("{", TokenFlags.Leading | TokenFlags.TrailingSpaceRecommended, in context); + } + + public virtual void EndObject(int propertyCount, in WriteContext context) + { + WritePunctuator("}", TokenFlags.Trailing | TokenFlags.LeadingSpaceRecommended, in context); + } + + #endregion + + #region Blocks + + public virtual void StartBlock(int statementCount, in WriteContext context) + { + WritePunctuator("{", TokenFlags.Leading | TokenFlags.SurroundingSpaceRecommended, in context); + } + + public virtual void EndBlock(int statementCount, in WriteContext context) + { + WritePunctuator("}", TokenFlags.Trailing | TokenFlags.LeadingSpaceRecommended, in context); + } + + #endregion + + #region Statements + + public virtual void StartStatement(StatementFlags flags, in WriteContext context) { } + + public virtual void EndStatement(StatementFlags flags, in WriteContext context) + { + // Writes statement terminator unless it can be omitted. + if (flags.HasFlagFast(StatementFlags.NeedsSemicolon) && !flags.HasFlagFast(StatementFlags.MayOmitRightMostSemicolon | StatementFlags.IsRightMost)) + { + WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in context); + } + } + + public virtual void StartStatementList(int count, in WriteContext context) { } + + public virtual void StartStatementListItem(int index, int count, StatementFlags flags, in WriteContext context) { } + + public virtual void EndStatementListItem(int index, int count, StatementFlags flags, in WriteContext context) + { + // Writes statement terminator unless it can be omitted. + if (flags.HasFlagFast(StatementFlags.NeedsSemicolon) && !flags.HasFlagFast(StatementFlags.MayOmitRightMostSemicolon | StatementFlags.IsRightMost)) + { + WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in context); + } + } + + public virtual void EndStatementList(int count, in WriteContext context) { } + + #endregion + + #region Expressions + + public virtual void StartExpression(ExpressionFlags flags, in WriteContext context) + { + if (flags.HasFlagFast(ExpressionFlags.NeedsBrackets)) + { + WritePunctuator("(", TokenFlags.Leading | flags.HasFlagFast(ExpressionFlags.SpaceAroundBracketsRecommended).ToFlag(TokenFlags.LeadingSpaceRecommended), in context); + } + } + + public virtual void EndExpression(ExpressionFlags flags, in WriteContext context) + { + if (flags.HasFlagFast(ExpressionFlags.NeedsBrackets)) + { + WritePunctuator(")", TokenFlags.Trailing | flags.HasFlagFast(ExpressionFlags.SpaceAroundBracketsRecommended).ToFlag(TokenFlags.TrailingSpaceRecommended), in context); + } + } + + public virtual void StartExpressionList(int count, in WriteContext context) { } + + public virtual void StartExpressionListItem(int index, int count, ExpressionFlags flags, in WriteContext context) + { + if (flags.HasFlagFast(ExpressionFlags.NeedsBrackets)) + { + WritePunctuator("(", TokenFlags.Leading, in context); + } + } + + public virtual void EndExpressionListItem(int index, int count, ExpressionFlags flags, in WriteContext context) + { + if (flags.HasFlagFast(ExpressionFlags.NeedsBrackets)) + { + WritePunctuator(")", TokenFlags.Trailing, in context); + } + + if (index < count - 1) + { + WritePunctuator(",", TokenFlags.InBetween | TokenFlags.TrailingSpaceRecommended, in context); + } + } + + public virtual void EndExpressionList(int count, in WriteContext context) { } + + #endregion + + #region Auxiliary nodes + + public virtual void StartAuxiliaryNode(object? nodeContext, in WriteContext context) { } + + public virtual void EndAuxiliaryNode(object? nodeContext, in WriteContext context) { } + + public virtual void StartAuxiliaryNodeList(int count, in WriteContext context) where T : Node? { } + + public virtual void StartAuxiliaryNodeListItem(int index, int count, string separator, object? nodeContext, in WriteContext context) where T : Node? { } + + public virtual void EndAuxiliaryNodeListItem(int index, int count, string separator, object? nodeContext, in WriteContext context) where T : Node? + { + if (separator.Length > 0 && index < count - 1) + { + WritePunctuator(separator, TokenFlags.InBetween | TokenFlags.TrailingSpaceRecommended, in context); + } + } + + public virtual void EndAuxiliaryNodeList(int count, in WriteContext context) where T : Node? { } + + #endregion +} diff --git a/src/Esprima/Utils/JsonTextWriter.cs b/src/Esprima/Utils/JsonTextWriter.cs index 8eb90017..95fb2e3e 100644 --- a/src/Esprima/Utils/JsonTextWriter.cs +++ b/src/Esprima/Utils/JsonTextWriter.cs @@ -53,8 +53,13 @@ public JsonTextWriter(TextWriter writer) : public JsonTextWriter(TextWriter writer, string? indent) { _writer = writer ?? ThrowArgumentNullException(nameof(writer)); - _writer = writer ?? ThrowArgumentNullException(nameof(writer)); + + if (!string.IsNullOrWhiteSpace(indent)) + { + throw new ArgumentException("Indent must be null or white-space.", nameof(indent)); + } _indent = indent ?? ""; + _counters = new Stack(8); _structures = new Stack(8); } diff --git a/src/Esprima/Utils/KnRJavascriptTextWriter.cs b/src/Esprima/Utils/KnRJavascriptTextWriter.cs new file mode 100644 index 00000000..b21dc51f --- /dev/null +++ b/src/Esprima/Utils/KnRJavascriptTextWriter.cs @@ -0,0 +1,480 @@ +using System.Runtime.CompilerServices; +using Esprima.Ast; + +namespace Esprima.Utils; + +/// +/// Javascript text writer (code formatter) which implements the most common K&R style. +/// +public class KnRJavascriptTextWriter : JavascriptTextWriter +{ + public new record class Options : JavascriptTextWriter.Options + { + public static new readonly Options Default = new(); + + internal static Options GetDefaultFrom(JavascriptTextWriter.Options baseOptions) => Default; + + public string? Indent { get; init; } + public bool UseEgyptianBraces { get; init; } = true; + public bool KeepSingleStatementBodyInLine { get; init; } + public bool KeepEmptyBlockBodyInLine { get; init; } = true; + public int MultiLineArrayLiteralThreshold { get; init; } = 7; + public int MultiLineObjectLiteralThreshold { get; init; } = 3; + } + + private const int UseEgyptianBracesFlag = 1 << 0; + private const int KeepSingleStatementBodyInLineFlag = 1 << 1; + private const int KeepEmptyBlockBodyInLineFlag = 1 << 2; + + private const int StatementBlockBodyFlag = 1 << 0; + private const int StatementEmptyBlockBodyFlag = 1 << 1; + + private readonly int _optionFlags; + private readonly string _indent; + private int _indentionLevel; + private int _currentStatementBodyFlags; + + public KnRJavascriptTextWriter(TextWriter writer, JavascriptTextWriter.Options options) : base(writer, options) + { + var extendedOptions = options as Options ?? Options.GetDefaultFrom(options); + + if (!string.IsNullOrWhiteSpace(extendedOptions.Indent)) + { + throw new ArgumentException("Indent must be null or white-space.", nameof(extendedOptions)); + } + + _indent = extendedOptions.Indent ?? " "; + + if (extendedOptions.UseEgyptianBraces) + { + _optionFlags |= UseEgyptianBracesFlag; + } + + if (extendedOptions.KeepSingleStatementBodyInLine) + { + _optionFlags |= KeepSingleStatementBodyInLineFlag; + } + + if (extendedOptions.KeepEmptyBlockBodyInLine) + { + _optionFlags |= KeepEmptyBlockBodyInLineFlag; + } + + MultiLineArrayLiteralThreshold = extendedOptions.MultiLineArrayLiteralThreshold >= 0 ? extendedOptions.MultiLineArrayLiteralThreshold : int.MaxValue; + MultiLineObjectLiteralThreshold = extendedOptions.MultiLineObjectLiteralThreshold >= 0 ? extendedOptions.MultiLineObjectLiteralThreshold : int.MaxValue; + } + + protected bool UseEgyptianBraces { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (_optionFlags & UseEgyptianBracesFlag) != 0; } + protected bool KeepSingleStatementBodyInLine { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (_optionFlags & KeepSingleStatementBodyInLineFlag) != 0; } + protected bool KeepEmptyBlockBodyInLine { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (_optionFlags & KeepEmptyBlockBodyInLineFlag) != 0; } + protected int MultiLineArrayLiteralThreshold { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } + protected int MultiLineObjectLiteralThreshold { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } + + protected int PreviousStatementBody { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } + + protected void IncreaseIndent() + { + _indentionLevel++; + } + + protected void DecreaseIndent() + { + _indentionLevel--; + } + + protected void WriteIndent() + { + for (var n = _indentionLevel; n > 0; n--) + { + WriteWhiteSpace(_indent); + } + } + + public override void WriteEpsilon(TokenFlags flags, in WriteContext context) + { + if (WhiteSpaceWrittenSinceLastToken) + { + return; + } + + if ((flags & (TokenFlags.LeadingSpaceRecommended | TokenFlags.TrailingSpaceRecommended)) != 0) + { + ForceRecommendedSpace(); + } + } + + protected override void StartKeyword(string value, TokenFlags flags, in WriteContext context) + { + if (WhiteSpaceWrittenSinceLastToken) + { + return; + } + + if (flags.HasFlagFast(TokenFlags.FollowsStatementBody)) + { + if (UseEgyptianBraces && CanUseEgyptianBraces()) + { + WriteSpace(); + } + else + { + WriteLine(); + WriteIndent(); + } + } + else if (flags.HasFlagFast(TokenFlags.LeadingSpaceRecommended) || LastTokenFlags.HasFlagFast(TokenFlags.TrailingSpaceRecommended)) + { + WriteSpace(); + } + else + { + base.StartKeyword(value, flags, context); + } + } + + protected override void StartIdentifier(string value, TokenFlags flags, in WriteContext context) + { + if (WhiteSpaceWrittenSinceLastToken) + { + return; + } + + if (flags.HasFlagFast(TokenFlags.LeadingSpaceRecommended) || LastTokenFlags.HasFlagFast(TokenFlags.TrailingSpaceRecommended)) + { + WriteSpace(); + } + else + { + base.StartIdentifier(value, flags, context); + } + } + + protected override void StartLiteral(string value, TokenType type, TokenFlags flags, in WriteContext context) + { + if (WhiteSpaceWrittenSinceLastToken) + { + return; + } + + if (flags.HasFlagFast(TokenFlags.LeadingSpaceRecommended) || LastTokenFlags.HasFlagFast(TokenFlags.TrailingSpaceRecommended)) + { + WriteSpace(); + } + else + { + base.StartLiteral(value, type, flags, context); + } + } + + protected override void StartPunctuator(string value, TokenFlags flags, in WriteContext context) + { + if (WhiteSpaceWrittenSinceLastToken) + { + return; + } + + if (flags.HasFlagFast(TokenFlags.LeadingSpaceRecommended) || LastTokenFlags.HasFlagFast(TokenFlags.TrailingSpaceRecommended)) + { + WriteSpace(); + } + else + { + base.StartPunctuator(value, flags, context); + } + } + + #region Arrays + + public override void StartArray(int elementCount, in WriteContext context) + { + base.StartArray(elementCount, context); + + if (context.Node.Type == Nodes.ArrayExpression && elementCount >= MultiLineArrayLiteralThreshold) + { + WriteLine(); + IncreaseIndent(); + } + } + + public override void EndArray(int elementCount, in WriteContext context) + { + if (context.Node.Type == Nodes.ArrayExpression && elementCount >= MultiLineArrayLiteralThreshold) + { + DecreaseIndent(); + WriteIndent(); + } + + base.EndArray(elementCount, context); + } + + #endregion + + #region Objects + + public override void StartObject(int propertyCount, in WriteContext context) + { + base.StartObject(propertyCount, context); + + if (context.Node.Type == Nodes.ObjectExpression && propertyCount >= MultiLineObjectLiteralThreshold) + { + WriteLine(); + IncreaseIndent(); + } + } + + public override void EndObject(int propertyCount, in WriteContext context) + { + if (context.Node.Type == Nodes.ObjectExpression && propertyCount >= MultiLineObjectLiteralThreshold) + { + DecreaseIndent(); + WriteIndent(); + } + + base.EndObject(propertyCount, context); + } + #endregion + + #region Blocks + + public override void StartBlock(int statementCount, in WriteContext context) + { + base.StartBlock(statementCount, context); + + if (statementCount > 0 || !KeepEmptyBlockBodyInLine) + { + WriteLine(); + IncreaseIndent(); + } + } + + public override void EndBlock(int statementCount, in WriteContext context) + { + if (statementCount > 0 || !KeepEmptyBlockBodyInLine) + { + DecreaseIndent(); + WriteIndent(); + } + + base.EndBlock(statementCount, context); + } + + #endregion + + #region Statements + + public override void StartStatement(StatementFlags flags, in WriteContext context) + { + if (flags.HasFlagFast(StatementFlags.IsStatementBody)) + { + var statement = context.GetNodePropertyValue(); + + // Is block body? + if (statement is BlockStatement blockStatement) + { + _currentStatementBodyFlags = StatementBlockBodyFlag; + + if (blockStatement.Body.Count == 0) + { + _currentStatementBodyFlags |= StatementEmptyBlockBodyFlag; + } + } + // Is single statement body? + else + { + _currentStatementBodyFlags = 0; + + if (CanInlineSingleStatementBody(statement, flags, in context)) + { + WriteSpace(); + } + else + { + WriteLine(); + IncreaseIndent(); + WriteIndent(); + } + } + } + } + + public override void EndStatement(StatementFlags flags, in WriteContext context) + { + // Is single statement body? + if (flags.HasFlagFast(StatementFlags.IsStatementBody) && (_currentStatementBodyFlags & StatementBlockBodyFlag) == 0) + { + if (!CanInlineSingleStatementBody(context.GetNodePropertyValue(), flags, in context)) + { + DecreaseIndent(); + } + } + + if (flags.HasFlagFast(StatementFlags.NeedsSemicolon) || ShouldTerminateStatementAnyway(context.GetNodePropertyValue(), flags, in context)) + { + WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in context); + } + } + + public override void StartStatementList(int count, in WriteContext context) + { + if (context.Node.Type == Nodes.SwitchCase) + { + if (count == 1 && context.GetNodePropertyListValue()[0].Type == Nodes.BlockStatement) + { + WriteSpace(); + } + else + { + WriteLine(); + IncreaseIndent(); + } + } + } + + public override void StartStatementListItem(int index, int count, StatementFlags flags, in WriteContext context) + { + if (context.Node.Type == Nodes.SwitchCase) + { + if (index == 0 && count == 1 && context.GetNodePropertyListValue()[0].Type == Nodes.BlockStatement) + { + return; + } + } + + WriteIndent(); + } + + public override void EndStatementListItem(int index, int count, StatementFlags flags, in WriteContext context) + { + if (flags.HasFlagFast(StatementFlags.NeedsSemicolon) || ShouldTerminateStatementAnyway(context.GetNodePropertyListValue()[index], flags, in context)) + { + WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in context); + } + + WriteLine(); + } + + public override void EndStatementList(int count, in WriteContext context) + { + if (context.Node.Type == Nodes.SwitchCase) + { + if (!(count == 1 && context.GetNodePropertyListValue()[0].Type == Nodes.BlockStatement)) + { + DecreaseIndent(); + } + } + } + + protected virtual bool CanUseEgyptianBraces() + { + return KeepEmptyBlockBodyInLine + ? (_currentStatementBodyFlags & (StatementBlockBodyFlag | StatementEmptyBlockBodyFlag)) == StatementBlockBodyFlag + : (_currentStatementBodyFlags & (StatementBlockBodyFlag)) != 0; + } + + protected virtual bool CanInlineSingleStatementBody(Statement statement, StatementFlags flags, in WriteContext context) + { + return statement.Type switch + { + // Statements + Nodes.BreakStatement or + Nodes.ContinueStatement or + Nodes.DebuggerStatement or + Nodes.EmptyStatement or + Nodes.ExpressionStatement or + Nodes.ReturnStatement or + Nodes.ThrowStatement => + KeepSingleStatementBodyInLine, + + Nodes.BlockStatement or + Nodes.DoWhileStatement or + Nodes.ForInStatement or + Nodes.ForOfStatement or + Nodes.ForStatement or + Nodes.LabeledStatement or + Nodes.SwitchStatement or + Nodes.TryStatement or + Nodes.WhileStatement or + Nodes.WithStatement => + false, + + Nodes.IfStatement => + context is { Node: IfStatement, NodePropertyName: nameof(IfStatement.Alternate) }, + + // Declarations + Nodes.FunctionDeclaration or + Nodes.VariableDeclaration => + KeepSingleStatementBodyInLine, + + Nodes.ClassDeclaration or + Nodes.ImportDeclaration or + Nodes.ExportAllDeclaration or + Nodes.ExportDefaultDeclaration or + Nodes.ExportNamedDeclaration => + throw new ArgumentException($"Operation is not defined for nodes of type {statement.Type}.", nameof(statement)), + + // Extensions + _ => false, + }; + } + + protected virtual bool ShouldTerminateStatementAnyway(Statement statement, StatementFlags flags, in WriteContext context) + { + return statement.Type switch + { + Nodes.DoWhileStatement => true, + _ => false + }; + } + + #endregion + + #region Expressions + + public override void StartExpressionListItem(int index, int count, ExpressionFlags flags, in WriteContext context) + { + if (context.Node.Type == Nodes.ArrayExpression && count >= MultiLineArrayLiteralThreshold) + { + WriteIndent(); + } + + base.StartExpressionListItem(index, count, flags, context); + } + + public override void EndExpressionListItem(int index, int count, ExpressionFlags flags, in WriteContext context) + { + base.EndExpressionListItem(index, count, flags, context); + + if (context.Node.Type == Nodes.ArrayExpression && count >= MultiLineArrayLiteralThreshold) + { + WriteLine(); + } + } + + #endregion + + public override void StartAuxiliaryNodeListItem(int index, int count, string separator, object? nodeContext, in WriteContext context) + { + if (typeof(T) == typeof(SwitchCase) || + context.Node.Type == Nodes.ClassBody || + context.Node.Type == Nodes.ObjectExpression && count >= MultiLineObjectLiteralThreshold) + { + WriteIndent(); + } + } + + public override void EndAuxiliaryNodeListItem(int index, int count, string separator, object? nodeContext, in WriteContext context) + { + base.EndAuxiliaryNodeListItem(index, count, separator, nodeContext, context); + + if (context.Node.Type is Nodes.ClassBody || + context.Node.Type == Nodes.ObjectExpression && count >= MultiLineObjectLiteralThreshold) + { + WriteLine(); + } + else if (typeof(T) == typeof(Decorator)) + { + WriteLine(); + WriteIndent(); + } + } +} diff --git a/src/Shared/Compatibility/NullableAttributes.cs b/src/Shared/Compatibility/NullableAttributes.cs new file mode 100644 index 00000000..b311a4dd --- /dev/null +++ b/src/Shared/Compatibility/NullableAttributes.cs @@ -0,0 +1,151 @@ +// Source: https://github.com/dotnet/runtime/blob/v6.0.5/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Diagnostics.CodeAnalysis +{ + // These attributes already shipped with .NET Core 3.1 in System.Runtime +#if !NETCOREAPP3_0 && !NETCOREAPP3_1 && !NETSTANDARD2_1 + /// Specifies that null is allowed as an input even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] + + internal sealed class AllowNullAttribute : Attribute + { } + + /// Specifies that null is disallowed as an input even if the corresponding type allows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] + + internal sealed class DisallowNullAttribute : Attribute + { } + + /// Specifies that an output may be null even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] + internal sealed class MaybeNullAttribute : Attribute + { } + + /// Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] + internal sealed class NotNullAttribute : Attribute + { } + + /// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class MaybeNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter may be null. + /// + public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } + + /// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class NotNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } + + /// Specifies that the output will be non-null if the named parameter is non-null. + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] + internal sealed class NotNullIfNotNullAttribute : Attribute + { + /// Initializes the attribute with the associated parameter name. + /// + /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. + /// + public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; + + /// Gets the associated parameter name. + public string ParameterName { get; } + } + + /// Applied to a method that will never return under any circumstance. + [AttributeUsage(AttributeTargets.Method, Inherited = false)] + internal sealed class DoesNotReturnAttribute : Attribute + { } + + /// Specifies that the method will not return if the associated Boolean parameter is passed the specified value. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class DoesNotReturnIfAttribute : Attribute + { + /// Initializes the attribute with the specified parameter value. + /// + /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to + /// the associated parameter matches this value. + /// + public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; + + /// Gets the condition parameter value. + public bool ParameterValue { get; } + } +#endif + + /// Specifies that the method or property will ensure that the listed field and property members have not-null values. + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] + internal sealed class MemberNotNullAttribute : Attribute + { + /// Initializes the attribute with a field or property member. + /// + /// The field or property member that is promised to be not-null. + /// + public MemberNotNullAttribute(string member) => Members = new[] { member }; + + /// Initializes the attribute with the list of field and property members. + /// + /// The list of field and property members that are promised to be not-null. + /// + public MemberNotNullAttribute(params string[] members) => Members = members; + + /// Gets field or property member names. + public string[] Members { get; } + } + + /// Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition. + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] + internal sealed class MemberNotNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition and a field or property member. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + /// + /// The field or property member that is promised to be not-null. + /// + public MemberNotNullWhenAttribute(bool returnValue, string member) + { + ReturnValue = returnValue; + Members = new[] { member }; + } + + /// Initializes the attribute with the specified return value condition and list of field and property members. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + /// + /// The list of field and property members that are promised to be not-null. + /// + public MemberNotNullWhenAttribute(bool returnValue, params string[] members) + { + ReturnValue = returnValue; + Members = members; + } + + /// Gets the return value condition. + public bool ReturnValue { get; } + + /// Gets field or property member names. + public string[] Members { get; } + } +} From 4eed8ace44c35221bd0fbe2ccf442d8f97c5fcee Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Mon, 18 Jul 2022 00:39:04 +0200 Subject: [PATCH 15/23] fix existing tests + implement comprehensive testing --- src/Esprima/Utils/AstJson.cs | 8 +- src/Esprima/Utils/AstToJsonConverter.cs | 20 +- test/Esprima.Tests/AstToJavascriptTests.cs | 347 +++++++++++++----- .../{VisitorTests.cs => AstVisitorTests.cs} | 2 +- test/Esprima.Tests/Fixtures.cs | 66 +--- .../Fixtures/fixtures-metadata.json | 10 +- test/Esprima.Tests/ParserTests.cs | 57 +++ 7 files changed, 344 insertions(+), 166 deletions(-) rename test/Esprima.Tests/{VisitorTests.cs => AstVisitorTests.cs} (98%) diff --git a/src/Esprima/Utils/AstJson.cs b/src/Esprima/Utils/AstJson.cs index 3d2d3d7e..1143948f 100644 --- a/src/Esprima/Utils/AstJson.cs +++ b/src/Esprima/Utils/AstJson.cs @@ -21,7 +21,13 @@ public record class Options /// 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; init; } + internal TestCompatibilityMode TestCompatibilityMode { get; init; } + } + + internal enum TestCompatibilityMode + { + None, + EsprimaOrg, } public static string ToJsonString(this Node node, AstToJsonConverter.Factory? converterFactory = null) diff --git a/src/Esprima/Utils/AstToJsonConverter.cs b/src/Esprima/Utils/AstToJsonConverter.cs index 2707df35..e3907194 100644 --- a/src/Esprima/Utils/AstToJsonConverter.cs +++ b/src/Esprima/Utils/AstToJsonConverter.cs @@ -16,7 +16,7 @@ public class AstToJsonConverter : AstVisitor private protected readonly bool _includeLineColumn; private protected readonly bool _includeRange; private protected readonly LocationMembersPlacement _locationMembersPlacement; - private protected readonly bool _testCompatibilityMode; + private protected readonly AstJson.TestCompatibilityMode _testCompatibilityMode; public AstToJsonConverter(JsonWriter writer, AstJson.Options options) { @@ -232,7 +232,7 @@ public void Convert(Node node) Member("generator", ((IFunction) arrowFunctionExpression).Generator); Member("expression", arrowFunctionExpression.Expression); // original Esprima doesn't include this information yet - if (!_testCompatibilityMode) + if (_testCompatibilityMode != AstJson.TestCompatibilityMode.EsprimaOrg) { Member("strict", arrowFunctionExpression.Strict); } @@ -444,7 +444,7 @@ public void Convert(Node node) Member("source", exportAllDeclaration.Source); // original Esprima doesn't include this information yet - if (!_testCompatibilityMode) + if (_testCompatibilityMode != AstJson.TestCompatibilityMode.EsprimaOrg) { Member("exported", exportAllDeclaration.Exported); if (exportAllDeclaration.Assertions.Count > 0) @@ -475,7 +475,7 @@ public void Convert(Node node) Member("specifiers", exportNamedDeclaration.Specifiers); Member("source", exportNamedDeclaration.Source); // original Esprima doesn't include this information yet - if (!_testCompatibilityMode && exportNamedDeclaration.Assertions.Count > 0) + if (_testCompatibilityMode != AstJson.TestCompatibilityMode.EsprimaOrg && exportNamedDeclaration.Assertions.Count > 0) { Member("assertions", exportNamedDeclaration.Assertions); } @@ -559,7 +559,7 @@ public void Convert(Node node) Member("generator", functionDeclaration.Generator); Member("expression", ((IFunction) functionDeclaration).Expression); // original Esprima doesn't include this information yet - if (!_testCompatibilityMode) + if (_testCompatibilityMode != AstJson.TestCompatibilityMode.EsprimaOrg) { Member("strict", functionDeclaration.Strict); } @@ -579,7 +579,7 @@ public void Convert(Node node) Member("generator", functionExpression.Generator); Member("expression", ((IFunction) functionExpression).Expression); // original Esprima doesn't include this information yet - if (!_testCompatibilityMode) + if (_testCompatibilityMode != AstJson.TestCompatibilityMode.EsprimaOrg) { Member("strict", functionExpression.Strict); } @@ -630,7 +630,7 @@ public ImportCompat() : base(Nodes.Import) { } { // original Esprima uses CallExpression to represent dynamic imports currently, // so we need to rewrite our representation to match this expectation - if (_testCompatibilityMode) + if (_testCompatibilityMode == AstJson.TestCompatibilityMode.EsprimaOrg) { const string importToken = "import"; @@ -651,7 +651,7 @@ public ImportCompat() : base(Nodes.Import) { } using (StartNodeObject(import)) { - if (!_testCompatibilityMode) + if (_testCompatibilityMode != AstJson.TestCompatibilityMode.EsprimaOrg) { Member("source", import.Source); @@ -744,7 +744,7 @@ public ImportCompat() : base(Nodes.Import) { } switch (value) { case null: - if (!_testCompatibilityMode && literal.TokenType == TokenType.RegularExpression) + if (_testCompatibilityMode != AstJson.TestCompatibilityMode.EsprimaOrg && literal.TokenType == TokenType.RegularExpression) { // This is how esprima.org actually renders regexes since it relies on Regex.toString _writer.String(literal.Raw); @@ -880,7 +880,7 @@ public ImportCompat() : base(Nodes.Import) { } Member("sourceType", program.SourceType); // original Esprima doesn't include this information yet - if (!_testCompatibilityMode && program is Script s) + if (_testCompatibilityMode != AstJson.TestCompatibilityMode.EsprimaOrg && program is Script s) { Member("strict", s.Strict); } diff --git a/test/Esprima.Tests/AstToJavascriptTests.cs b/test/Esprima.Tests/AstToJavascriptTests.cs index ce18f29a..c1bba303 100644 --- a/test/Esprima.Tests/AstToJavascriptTests.cs +++ b/test/Esprima.Tests/AstToJavascriptTests.cs @@ -1,12 +1,53 @@ using System; using System.Text.RegularExpressions; +using Esprima.Ast; +using Esprima.Test; using Esprima.Utils; +using Esprima.Utils.Jsx; using Xunit; namespace Esprima.Tests { public class AstToJavascriptTests { + private sealed class CustomCompactJavascriptTextWriter : JavascriptTextWriter + { + public CustomCompactJavascriptTextWriter(TextWriter writer, Options options) : base(writer, options) { } + + public override void EndStatement(StatementFlags flags, in WriteContext context) + { + if (flags.HasFlagFast(StatementFlags.NeedsSemicolon) || ShouldTerminateStatementAnyway(context.GetNodePropertyValue(), flags, in context)) + { + WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in context); + } + } + + public override void EndStatementListItem(int index, int count, StatementFlags flags, in WriteContext context) + { + if (flags.HasFlagFast(StatementFlags.NeedsSemicolon) || ShouldTerminateStatementAnyway(context.GetNodePropertyListValue()[index], flags, in context)) + { + WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in context); + } + } + + private bool ShouldTerminateStatementAnyway(Statement statement, StatementFlags flags, in WriteContext context) + { + return statement.Type switch + { + Nodes.DoWhileStatement => true, + _ => false + }; + } + } + + private static readonly JavascriptTextWriter.Factory s_customCompactWriterFactory = (writer, options) => new CustomCompactJavascriptTextWriter(writer, options); + private static readonly KnRJavascriptTextWriter.Options s_indentedWriterOptions = new KnRJavascriptTextWriter.Options + { + Indent = " ", + KeepEmptyBlockBodyInLine = false, + MultiLineObjectLiteralThreshold = 1 + }; + [Fact] public void ToJavascriptTest1() { @@ -25,9 +66,10 @@ public void ToJavascriptTest1() for (var elem of list) { } "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program); - Assert.Equal("if(true){p();}switch(foo){case 'A':p();break;}switch(foo){default:p();break;}for(var a=[];;){}for(var elem of list){}", code); + var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + + Assert.Equal("if(true){p();}switch(foo){case'A':p();break;}switch(foo){default:p();break;}for(var a=[];;){}for(var elem of list){}", code); } [Fact] @@ -47,8 +89,8 @@ function printTips() tips.forEach((tip, i) => console.log(`Tip ${ i}:` +tip)); }"); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program); - Assert.Equal("let tips=[\"Click on any AST node with a '+' to expand it\",\"Hovering over a node highlights the \\\r\n corresponding location in the source code\",\"Shift click on an AST node to expand the whole subtree\"];function printTips(){tips.forEach((tip,i)=>console.log((`Tip ${i}:`+tip)));}", code); + var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + Assert.Equal("let tips=[\"Click on any AST node with a '+' to expand it\",\"Hovering over a node highlights the \\\r\n corresponding location in the source code\",\"Shift click on an AST node to expand the whole subtree\"];function printTips(){tips.forEach((tip,i)=>console.log(`Tip ${i}:`+tip));}", code); } [Fact] @@ -65,8 +107,8 @@ static get is() { } }"); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program); - Assert.Equal("export class aa extends HTMLElement{constructor(a,b){super(a);this._div=(document.createElement('div'));}static get is(){return 'aa';}}", code); + var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + Assert.Equal("export class aa extends HTMLElement{constructor(a,b){super(a);this._div=document.createElement('div');}static get is(){return'aa';}}", code); } [Fact] @@ -129,7 +171,7 @@ export function checkSecurityAnswerCodeDirect(result) { source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, true); + var code = AstToJavascript.ToJavascriptString(program, s_indentedWriterOptions); var expected = @"import { MccDialog } from '../mccDialogHandler'; import { commonClient, bb as f } from '../commonClient/commonClient'; @@ -142,45 +184,45 @@ export function checkSecurityAnswerCodeDirect(result) { a++; --a; export function checkSecurityAnswerCodeDirect(result) { - if (!(result)) { + if (!result) { MccDialog.warning({ - title : 'SecurityClientErrorOccured', - message : '

internal error, check console

' + title: 'SecurityClientErrorOccured', + message: '

internal error, check console

' }); return false; } - switch(result.SecurityAnswerCode) { + switch (result.SecurityAnswerCode) { case 'Allowed': return true; case 'Exception': MccDialog.warning({ - title : 'SecurityClientInfoTitle', - message : ((('

SecurityClientExceptionOccured

Exception: ' + result.Message) + '

') + result.StackTrace) + title: 'SecurityClientInfoTitle', + message: '

SecurityClientExceptionOccured

Exception: ' + result.Message + '

' + result.StackTrace }); return false; case 'Error': MccDialog.warning({ - title : 'SecurityClientErrorOccured', - message : ((((('

' + (commonClient.getTranslation('SecurityClientMessage'))) + ': ') + (commonClient.getTranslation(result.Message))) + '

') + (result.MessageDetails ? (('

SecurityClientDetails: ' + result.MessageDetails) + '

') : ' ')) + title: 'SecurityClientErrorOccured', + message: '

' + commonClient.getTranslation('SecurityClientMessage') + ': ' + commonClient.getTranslation(result.Message) + '

' + (result.MessageDetails ? '

SecurityClientDetails: ' + result.MessageDetails + '

' : ' ') }); return false; - default: - { - let messagesnippet = (('

SecurityClient_' + result.SecurityAnswerCode) + '

'); - if ((result.Message !== undefined) && (result.SecurityAnswerCode === 'LoginFailed')) { - messagesnippet += (('\n\nSecurityClient_InternalServerErrorMessage\n' + result.Message) + ''); - } - if (result.Role) { - messagesnippet += (((('

SecurityClient_CheckedRole' + ' [') + result.Role) + ']') + '

'); - } - MccDialog.warning({ - title : 'SecurityClientInfoTitle', - message : messagesnippet - }); - return false; + default: { + let messagesnippet = '

SecurityClient_' + result.SecurityAnswerCode + '

'; + if (result.Message !== undefined && result.SecurityAnswerCode === 'LoginFailed') { + messagesnippet += '\n\nSecurityClient_InternalServerErrorMessage\n' + result.Message + ''; + } + if (result.Role) { + messagesnippet += '

SecurityClient_CheckedRole' + ' [' + result.Role + ']' + '

'; } + MccDialog.warning({ + title: 'SecurityClientInfoTitle', + message: messagesnippet + }); + return false; + } } -}"; +} +"; expected = Regex.Replace(expected, @"\r\n|\n\r|\n|\r", Environment.NewLine); Assert.Equal(expected, code); } @@ -210,7 +252,7 @@ public void ToJavascriptTest5() source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, true); + var code = AstToJavascript.ToJavascriptString(program, s_indentedWriterOptions); var expected = @"(function() { 'use strict'; @@ -222,10 +264,10 @@ public void ToJavascriptTest5() } }); a(); -aa({}); +aa({ }); (function aa() { - -});"; +}); +"; expected = Regex.Replace(expected, @"\r\n|\n\r|\n|\r", Environment.NewLine); Assert.Equal(expected, code); } @@ -241,7 +283,7 @@ public void ToJavascriptTest6() source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program); + var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); Assert.Equal("function _createClass(Constructor,protoProps,staticProps){if(protoProps)_defineProperties(Constructor.prototype,protoProps);if(staticProps)_defineProperties(Constructor,staticProps);return Constructor;}", code); } @@ -252,8 +294,8 @@ public void ToJavascriptTest7() { }"); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program); - Assert.Equal("if(((x?((a.nodeName.toLowerCase())===f):(1===a.nodeType))&&(++d))&&(p&&((i=((o=(a[S]||(a[S]={})))[a.uniqueID]||(o[a.uniqueID]={})))[h]=[k,d]),a===e)){}", code); + var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + Assert.Equal("if((x?a.nodeName.toLowerCase()===f:1===a.nodeType)&&++d&&(p&&((i=(o=a[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]=[k,d]),a===e)){}", code); } [Fact] @@ -271,7 +313,7 @@ class a extends b { } "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program); + var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); Assert.Equal("class a extends b{constructor(){super();this.g=1;}q=1;r='cc';}", code); } @@ -282,8 +324,8 @@ public void ToJavascriptTest9() d = (s = (r = (i = (o = (a = c)[S] || (a[S] = {}))[a.uniqueID] || (o[a.uniqueID] = {}))[h] || [])[0] === k && r[1]) && r[2], a = s && c.childNodes[s]; "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program); - Assert.Equal("d=((s=(((r=((i=((o=((a=c)[S]||(a[S]={})))[a.uniqueID]||(o[a.uniqueID]={})))[h]||[]))[0]===k)&&r[1]))&&r[2]),a=(s&&c.childNodes[s]);", code); + var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + Assert.Equal("d=(s=(r=(i=(o=(a=c)[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===k&&r[1])&&r[2],a=s&&c.childNodes[s];", code); } [Fact] @@ -293,8 +335,8 @@ public void ToJavascriptTest10() m = (z.document, !!v.documentElement && !!v.head && 'function' == typeof v.addEventListener && v.createElement, ~a.indexOf('MSIE') || a.indexOf('Trident/'), '___FONT_AWESOME___') "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program); - Assert.Equal("m=(z.document,(((!!(v.documentElement))&&(!!(v.head)))&&('function'==(typeof (v.addEventListener))))&&v.createElement,(~(a.indexOf('MSIE')))||(a.indexOf('Trident/')),'___FONT_AWESOME___');", code); + var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + Assert.Equal("m=(z.document,!!v.documentElement&&!!v.head&&'function'==typeof v.addEventListener&&v.createElement,~a.indexOf('MSIE')||a.indexOf('Trident/'),'___FONT_AWESOME___');", code); } [Fact] @@ -315,8 +357,8 @@ public void ToJavascriptTest11() }(); "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program); - Assert.Equal("var h=(c.navigator||{}).userAgent,a=((void 0)===h?'':h),z=c,v=l,m=(z.document,(((!!(v.documentElement))&&(!!(v.head)))&&('function'==(typeof (v.addEventListener))))&&v.createElement,(~(a.indexOf('MSIE')))||(a.indexOf('Trident/')),'___FONT_AWESOME___'),e=((function(){try {return !0;} catch(c){return !1;}})());", code); + var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + Assert.Equal("var h=(c.navigator||{}).userAgent,a=void 0===h?'':h,z=c,v=l,m=(z.document,!!v.documentElement&&!!v.head&&'function'==typeof v.addEventListener&&v.createElement,~a.indexOf('MSIE')||a.indexOf('Trident/'),'___FONT_AWESOME___'),e=function(){try{return!0;}catch(c){return!1;}}();", code); } [Fact] @@ -328,7 +370,7 @@ public void ToJavascriptTest12() } "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program); + var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); Assert.Equal("var a={children:(b=O,'g'===b.tag?b.children:[b])};", code); } @@ -345,8 +387,8 @@ public void ToJavascriptTest13() } else h = e.HttpRequest.responseText; "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program); - Assert.Equal("if(e.IsWebService)if(h=e.HttpRequest.responseXML,'undefined'==(typeof (h)))Trace.Write((('Error: '+e.UniqueId)+' data has no properties!')),m=(!0); else try {h.setProperty('SelectionLanguage','XPath');} catch(l){Trace.Write('Error: data.setProperty(',SelectionLanguage,', ',XPath,') because '+l.message);} else h=e.HttpRequest.responseText;", code); + var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + Assert.Equal("if(e.IsWebService)if(h=e.HttpRequest.responseXML,'undefined'==typeof h)Trace.Write('Error: '+e.UniqueId+' data has no properties!'),m=!0;else try{h.setProperty('SelectionLanguage','XPath');}catch(l){Trace.Write('Error: data.setProperty(',SelectionLanguage,', ',XPath,') because '+l.message);}else h=e.HttpRequest.responseText;", code); } [Fact] @@ -365,20 +407,21 @@ public void ToJavascriptTest14() source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, true); + var code = AstToJavascript.ToJavascriptString(program, s_indentedWriterOptions); - var expected = @"function tt(t,r) { - var n,e,i = (b(t)),s = (b(r)); - if (s && (e = (ft(r))), i) + var expected = @"function tt(t, r) { + var n, e, i = b(t), s = b(r); + if (s && (e = ft(r)), i) ; - else if (s) - return D(t,e) ? (void ($(t,e))) : (n = (l(e,t)), G(t,n), void (ht(t))); - var g,o,f; - for(f = (t.length < r.length ? t.length : r.length), o = 0, g = 0; f > g; g++) - o += (t[g] + r[g]), t[g] = (o & _t), o >>= at; - for(g = f; o && (g < t.length); g++) - o += t[g], t[g] = (o & _t), o >>= at; -}"; + else if (s) + return D(t, e) ? void $(t, e) : (n = l(e, t), G(t, n), void ht(t)); + var g, o, f; + for (f = t.length < r.length ? t.length : r.length, o = 0, g = 0; f > g; g++) + o += t[g] + r[g], t[g] = o & _t, o >>= at; + for (g = f; o && g < t.length; g++) + o += t[g], t[g] = o & _t, o >>= at; +} +"; expected = Regex.Replace(expected, @"\r\n|\n\r|\n|\r", Environment.NewLine); Assert.Equal(expected, code); } @@ -390,8 +433,8 @@ public void ToJavascriptTest15() h='M'+(+new Date).toString(36) "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program); - Assert.Equal("h=('M'+((+(new Date)).toString(36)));", code); + var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + Assert.Equal("h='M'+(+new Date).toString(36);", code); } [Fact] @@ -405,8 +448,8 @@ public void ToJavascriptTest16() }; "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program); - Assert.Equal("input.onchange=(async e=>{const files=await readFiles(input.files,readMode);document.body.removeChild(input);resolve(files);});", code); + var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + Assert.Equal("input.onchange=async e=>{const files=await readFiles(input.files,readMode);document.body.removeChild(input);resolve(files);};", code); } [Fact] @@ -416,8 +459,8 @@ public void ToJavascriptTest17() export const Base = LegacyElementMixin(HTMLElement).prototype; "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program); - Assert.Equal("export const Base=(LegacyElementMixin(HTMLElement)).prototype;", code); + var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + Assert.Equal("export const Base=LegacyElementMixin(HTMLElement).prototype;", code); } [Fact] @@ -427,8 +470,8 @@ public void ToJavascriptTest18() let {is} = getIsExtends(element); "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program); - Assert.Equal("let {is}=(getIsExtends(element));", code); + var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + Assert.Equal("let{is}=getIsExtends(element);", code); } [Fact] @@ -439,8 +482,8 @@ public void ToJavascriptTest19() (window['ShadyDOM'] && window['ShadyDOM']['wrap']) || (node => node); "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program); - Assert.Equal("export const wrap=((window['ShadyDOM']&&window['ShadyDOM']['wrap'])||(node=>node));", code); + var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + Assert.Equal("export const wrap=window['ShadyDOM']&&window['ShadyDOM']['wrap']||(node=>node);", code); } [Fact] @@ -449,7 +492,7 @@ public void ToJavascriptTest20() var parser = new JavaScriptParser(@" export {}"); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program); + var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); Assert.Equal("export{};", code); } @@ -462,7 +505,7 @@ public void ToJavascriptTest21() })(); "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program); + var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); Assert.Equal("(()=>{mutablePropertyChange=MutableData._mutablePropertyChange;})();", code); } @@ -476,8 +519,8 @@ public void ToJavascriptTest22() }()) "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program); - Assert.Equal("var Ol,jl=(new((function(){var l,h,z;return l=c;})()));", code); + var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + Assert.Equal("var Ol,jl=new(function(){var l,h,z;return l=c;}());", code); } [Fact] @@ -493,8 +536,8 @@ public void ToJavascriptTest23() "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program); - Assert.Equal("[y,{[Symbol.iterator]:(function(){return b;}),a:5}];", code); + var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + Assert.Equal("[y,{[Symbol.iterator](){return b;},a:5}];", code); } [Fact] @@ -515,15 +558,16 @@ class A { source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, true); + var code = AstToJavascript.ToJavascriptString(program, s_indentedWriterOptions); var expected = @"class A { *[Symbol.iterator]() { let L = this._first; - for(; L !== _.Undefined; ) + for (; L !== _.Undefined; ) yield L.element, L = L.next; } -}"; +} +"; expected = Regex.Replace(expected, @"\r\n|\n\r|\n|\r", Environment.NewLine); Assert.Equal(expected, code); } @@ -545,17 +589,18 @@ public void ToJavascriptTest25() source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, true); + var code = AstToJavascript.ToJavascriptString(program, s_indentedWriterOptions); - var expected = @"var i = ((function e(i) { + var expected = @"var i = function e(i) { var r = n[i]; - if ((void 0) !== r) + if (void 0 !== r) return r.exports; - var a = (n[i] = { - exports : {} - }); - return t[i](a,a.exports,e), a.exports; -})(15));"; + var a = n[i] = { + exports: { } + }; + return t[i](a, a.exports, e), a.exports; +}(15); +"; expected = Regex.Replace(expected, @"\r\n|\n\r|\n|\r", Environment.NewLine); Assert.Equal(expected, code); } @@ -574,12 +619,138 @@ public void ToJavascriptTest26() c = 1; } else { c = 3; -}"; +} +"; source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, true); + var code = AstToJavascript.ToJavascriptString(program, s_indentedWriterOptions); Assert.Equal(source, code); } + + private sealed class NodeTypeEqualityComparer : IEqualityComparer + { + public static NodeTypeEqualityComparer Default = new NodeTypeEqualityComparer(); + + public bool Equals(Node? x, Node? y) => + x is null && y is null ? true : + x is null || y is null ? false : + x.Type == y.Type; + + public int GetHashCode(Node? obj) => obj?.GetHashCode() ?? 0; + } + + // TODO: this should be removed once the related parser bugs get resolved + private static readonly HashSet s_falseNegatives = new() + { + @"es2017\async\methods\async-line-terminator-method.js", + @"es2017\async\methods\async-line-terminator-static-method.js", + @"es2017\async\arrows\export-default-async-arrow.module.js" + }; + + public static IEnumerable SourceFiles(string relativePath) => Fixtures.SourceFiles(relativePath) + // TODO: enable JSX fixtures once JSX writer gets implemented + .Where(items => !((string) items[0]).StartsWith("JSX")) + .Where(items => !s_falseNegatives.Contains(((string) items[0]).Replace('/', '\\'))); + + private static Program Parse(SourceType sourceType, string source, + ParserOptions parserOptions, Func parserFactory) + { + var parser = parserFactory(source, parserOptions); + var program = sourceType == SourceType.Script ? (Program) parser.ParseScript() : parser.ParseModule(); + + return program; + } + + [Theory] + [MemberData(nameof(SourceFiles), "Fixtures")] + public void OriginalAndReparsedASTsShouldMatch(string fixture) + { + var (parserOptions, parserFactory) = fixture.StartsWith("JSX") + ? (new JsxParserOptions(), + (src, opts) => new JsxParser(src, (JsxParserOptions) opts)) + : (new ParserOptions(), + new Func((src, opts) => new JavaScriptParser(src, opts))); + + parserOptions.Tokens = false; + parserOptions.AdaptRegexp = false; + parserOptions.Tolerant = false; + + string treeFilePath, failureFilePath, moduleFilePath; + var jsFilePath = Path.Combine(Fixtures.GetFixturesPath(), Fixtures.FixturesDirName, fixture); + var jsFileDirectoryName = Path.GetDirectoryName(jsFilePath)!; + if (jsFilePath.EndsWith(".source.js")) + { + treeFilePath = Path.Combine(jsFileDirectoryName, Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(jsFilePath))) + ".tree.json"; + failureFilePath = Path.Combine(jsFileDirectoryName, Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(jsFilePath))) + ".failure.json"; + moduleFilePath = Path.Combine(jsFileDirectoryName, Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(jsFilePath))) + ".module.json"; + } + else + { + treeFilePath = Path.Combine(jsFileDirectoryName, Path.GetFileNameWithoutExtension(jsFilePath)) + ".tree.json"; + failureFilePath = Path.Combine(jsFileDirectoryName, Path.GetFileNameWithoutExtension(jsFilePath)) + ".failure.json"; + moduleFilePath = Path.Combine(jsFileDirectoryName, Path.GetFileNameWithoutExtension(jsFilePath)) + ".module.json"; + } + + var script = File.ReadAllText(jsFilePath); + if (jsFilePath.EndsWith(".source.js")) + { + var parser = new JavaScriptParser(script); + var program = parser.ParseScript(); + var source = program.Body.First().As().Declarations.First().As().Init!.As().StringValue!; + script = source; + } + + var filename = Path.GetFileNameWithoutExtension(jsFilePath); + + if (filename.Contains("error") || + filename.Contains("invalid") && (!filename.Contains("invalid-yield-object-") && !filename.Contains("attribute-invalid-entity"))) + { + return; + } + + var isModule = + filename.Contains("module") || + filename.Contains("export") || + filename.Contains("import"); + + if (!filename.Contains(".module")) + { + isModule &= !jsFilePath.Contains("dynamic-import") && !jsFilePath.Contains("script"); + } + + var sourceType = isModule + ? SourceType.Module + : SourceType.Script; + + Program expectedAst; + if (File.Exists(moduleFilePath)) + { + sourceType = SourceType.Module; + } + else if (!File.Exists(treeFilePath)) + { + return; + } + + try { expectedAst = Parse(sourceType, script, parserOptions, parserFactory); } + catch (ParserException) { return; } + + var generatedScript = expectedAst.ToJavascriptString(); + + var actualAst = Parse(sourceType, generatedScript, parserOptions, parserFactory); + + // This compares just the node type. + // TODO: more detailed comparison. + Assert.Equal(expectedAst.DescendantNodesAndSelf(), actualAst.DescendantNodesAndSelf(), NodeTypeEqualityComparer.Default); + + generatedScript = expectedAst.ToJavascriptString(beautify: true); + + actualAst = Parse(sourceType, generatedScript, parserOptions, parserFactory); + + // This compares just the node type. + // TODO: more detailed comparison. + Assert.Equal(expectedAst.DescendantNodesAndSelf(), actualAst.DescendantNodesAndSelf(), NodeTypeEqualityComparer.Default); + } } } diff --git a/test/Esprima.Tests/VisitorTests.cs b/test/Esprima.Tests/AstVisitorTests.cs similarity index 98% rename from test/Esprima.Tests/VisitorTests.cs rename to test/Esprima.Tests/AstVisitorTests.cs index d114cb51..74e0e18c 100644 --- a/test/Esprima.Tests/VisitorTests.cs +++ b/test/Esprima.Tests/AstVisitorTests.cs @@ -7,7 +7,7 @@ namespace Esprima.Tests { - public class VisitorTests + public class AstVisitorTests { [Fact] public void CanVisitIfWithNoElse() diff --git a/test/Esprima.Tests/Fixtures.cs b/test/Esprima.Tests/Fixtures.cs index f2d7daa3..ebc91261 100644 --- a/test/Esprima.Tests/Fixtures.cs +++ b/test/Esprima.Tests/Fixtures.cs @@ -13,19 +13,10 @@ public class Fixtures // Only use this when the test is deemed wrong. private const bool WriteBackExpectedTree = false; - private const string FixturesDirName = "Fixtures"; + internal const string FixturesDirName = "Fixtures"; private static Lazy> Metadata { get; } = new(() => FixtureMetadata.ReadMetadata()); - [Fact] - public void HoistingScopeShouldWork() - { - var parser = new JavaScriptParser(@" - function p() {} - var x;"); - var program = parser.ParseScript(); - } - private static string ParseAndFormat(SourceType sourceType, string source, ParserOptions parserOptions, Func parserFactory, AstToJsonConverter.Factory converterFactory, AstJson.Options conversionOptions) @@ -153,7 +144,7 @@ public void ExecuteTestCase(string fixture) { sourceType = SourceType.Module; expected = File.ReadAllText(moduleFilePath); - if (WriteBackExpectedTree && !metadata.ConversionOptions.TestCompatibilityMode) + if (WriteBackExpectedTree && metadata.ConversionOptions.TestCompatibilityMode == AstJson.TestCompatibilityMode.None) { var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, converterFactory, metadata.ConversionOptions); if (!CompareTrees(actual, expected, metadata)) @@ -163,7 +154,7 @@ public void ExecuteTestCase(string fixture) else if (File.Exists(treeFilePath)) { expected = File.ReadAllText(treeFilePath); - if (WriteBackExpectedTree && !metadata.ConversionOptions.TestCompatibilityMode) + if (WriteBackExpectedTree && metadata.ConversionOptions.TestCompatibilityMode == AstJson.TestCompatibilityMode.None) { var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, converterFactory, metadata.ConversionOptions); if (!CompareTrees(actual, expected, metadata)) @@ -174,7 +165,7 @@ public void ExecuteTestCase(string fixture) { invalid = true; expected = File.ReadAllText(failureFilePath); - if (WriteBackExpectedTree && !metadata.ConversionOptions.TestCompatibilityMode) + if (WriteBackExpectedTree && metadata.ConversionOptions.TestCompatibilityMode == AstJson.TestCompatibilityMode.None) { var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, converterFactory, metadata.ConversionOptions); if (!CompareTrees(actual, expected, metadata)) @@ -232,53 +223,6 @@ internal static string GetFixturesPath() return root ?? ""; } - private sealed class ParentNodeChecker : AstVisitor - { - public void Check(Node node) - { - Assert.Null(node.Data); - - base.Visit(node); - } - - public override object? Visit(Node node) - { - var parent = (Node?) node.Data; - Assert.NotNull(parent); - Assert.Contains(node, parent!.ChildNodes); - - return base.Visit(node); - } - } - - [Fact] - public void NodeDataCanBeSetToParentNode() - { - Action action = node => - { - foreach (var child in node.ChildNodes) - { - child.Data = node; - } - }; - - var parser = new JavaScriptParser("function add(a, b) { return a + b; }", new ParserOptions { OnNodeCreated = action }); - var script = parser.ParseScript(); - - new ParentNodeChecker().Check(script); - } - - [Fact] - public void CommentsAreParsed() - { - var count = 0; - Action action = node => count++; - var parser = new JavaScriptParser("// this is a comment", new ParserOptions { OnNodeCreated = action }); - parser.ParseScript(); - - Assert.Equal(1, count); - } - private sealed class FixtureMetadata { public static readonly FixtureMetadata Default = new FixtureMetadata( @@ -327,7 +271,7 @@ private static FixtureMetadata CreateFrom(HashSet flags) { IncludingLineColumn = flags.Contains("IncludesLocation"), IncludingRange = flags.Contains("IncludesRange"), - TestCompatibilityMode = flags.Contains("BorrowedFixture") + TestCompatibilityMode = flags.Contains("EsprimaOrgFixture") ? AstJson.TestCompatibilityMode.EsprimaOrg : AstJson.TestCompatibilityMode.None }; var includesLocationSource = flags.Contains("IncludesLocationSource"); diff --git a/test/Esprima.Tests/Fixtures/fixtures-metadata.json b/test/Esprima.Tests/Fixtures/fixtures-metadata.json index 78ebd2f5..dca44489 100644 --- a/test/Esprima.Tests/Fixtures/fixtures-metadata.json +++ b/test/Esprima.Tests/Fixtures/fixtures-metadata.json @@ -10,7 +10,7 @@ [ { - "flags": [ "BorrowedFixture", "IncludesLocation", "IncludesRange" ], + "flags": [ "EsprimaOrgFixture", "IncludesLocation", "IncludesRange" ], "files": [ "3rdparty/angular-1.2.5.js", "3rdparty/angular-1.7.9.js", @@ -1683,13 +1683,13 @@ ] }, { - "flags": [ "BorrowedFixture", "IncludesLocation", "IncludesRange", "IncludesLocationSource" ], + "flags": [ "EsprimaOrgFixture", "IncludesLocation", "IncludesRange", "IncludesLocationSource" ], "files": [ "expression/complex/migrated_0001.js" ] }, { - "flags": [ "BorrowedFixture", "IncludesLocation", "IncludesRange", "IgnoresRegex" ], + "flags": [ "EsprimaOrgFixture", "IncludesLocation", "IncludesRange", "IgnoresRegex" ], "files": [ "expression/primary/literal/regular-expression/migrated_0003.js", "expression/primary/literal/regular-expression/migrated_0004.js", @@ -1698,14 +1698,14 @@ ] }, { - "flags": [ "BorrowedFixture", "IncludesRange" ], + "flags": [ "EsprimaOrgFixture", "IncludesRange" ], "files": [ "expression/primary/literal/numeric/migrated_0002.js", "expression/primary/literal/regular-expression/migrated_0007.js" ] }, { - "flags": [ "BorrowedFixture", "IncludesLocation" ], + "flags": [ "EsprimaOrgFixture", "IncludesLocation" ], "files": [ "expression/primary/literal/numeric/migrated_0003.js", "expression/primary/literal/regular-expression/migrated_0008.js" diff --git a/test/Esprima.Tests/ParserTests.cs b/test/Esprima.Tests/ParserTests.cs index 8e5db31e..a396b9a0 100644 --- a/test/Esprima.Tests/ParserTests.cs +++ b/test/Esprima.Tests/ParserTests.cs @@ -1,5 +1,6 @@ using Esprima.Ast; using Esprima.Test; +using Esprima.Utils; namespace Esprima.Tests { @@ -368,5 +369,61 @@ public void TemplateLiteralChildNodesShouldCorrectOrder(string source, params st return string.Empty; } } + + [Fact] + public void HoistingScopeShouldWork() + { + var parser = new JavaScriptParser(@" + function p() {} + var x;"); + var program = parser.ParseScript(); + } + + private sealed class ParentNodeChecker : AstVisitor + { + public void Check(Node node) + { + Assert.Null(node.Data); + + base.Visit(node); + } + + public override object? Visit(Node node) + { + var parent = (Node?) node.Data; + Assert.NotNull(parent); + Assert.Contains(node, parent!.ChildNodes); + + return base.Visit(node); + } + } + + [Fact] + public void NodeDataCanBeSetToParentNode() + { + Action action = node => + { + foreach (var child in node.ChildNodes) + { + child.Data = node; + } + }; + + var parser = new JavaScriptParser("function add(a, b) { return a + b; }", new ParserOptions { OnNodeCreated = action }); + var script = parser.ParseScript(); + + new ParentNodeChecker().Check(script); + } + + [Fact] + public void CommentsAreParsed() + { + var count = 0; + Action action = node => count++; + var parser = new JavaScriptParser("// this is a comment", new ParserOptions { OnNodeCreated = action }); + parser.ParseScript(); + + Assert.Equal(1, count); + } } } From 5a8088cc5cb3f3f4e6e82a6902973027e4f6b941 Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Mon, 18 Jul 2022 09:18:22 +0200 Subject: [PATCH 16/23] implement Node.ToString + define debugger display --- src/Esprima/Ast/Identifier.cs | 4 +--- src/Esprima/Ast/Literal.cs | 4 +--- src/Esprima/Ast/Node.cs | 11 ++++++++++- 3 files changed, 12 insertions(+), 7 deletions(-) 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/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}"; + } } } From 358b7ce008ee9729aa3a8a767ba9ff0e40ef21c7 Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Mon, 18 Jul 2022 09:32:43 +0200 Subject: [PATCH 17/23] improve code organization, sort VisitXXX methods alphabetically --- .../Utils/AstToJavascriptConverter.Helpers.cs | 425 +++ src/Esprima/Utils/AstToJavascriptConverter.cs | 2347 +++++++---------- 2 files changed, 1390 insertions(+), 1382 deletions(-) create mode 100644 src/Esprima/Utils/AstToJavascriptConverter.Helpers.cs diff --git a/src/Esprima/Utils/AstToJavascriptConverter.Helpers.cs b/src/Esprima/Utils/AstToJavascriptConverter.Helpers.cs new file mode 100644 index 00000000..7276f361 --- /dev/null +++ b/src/Esprima/Utils/AstToJavascriptConverter.Helpers.cs @@ -0,0 +1,425 @@ +using System.Diagnostics; +using System.Runtime.CompilerServices; +using Esprima.Ast; +using static Esprima.Utils.JavascriptTextWriter; + +namespace Esprima.Utils; + +partial class AstToJavascriptConverter +{ + #region Statements + + [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, in _writeContext); + Visit(statement); + Writer.EndStatement((JavascriptTextWriter.StatementFlags) _currentStatementFlags, in _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, in _writeContext); + + for (var i = 0; i < statementList.Count; i++) + { + VisitStatementListItem(statementList[i], i, statementList.Count, getCombinedItemFlags); + } + + Writer.EndStatementList(statementList.Count, in _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, in _writeContext); + Visit(statement); + Writer.EndStatementListItem(index, count, (JavascriptTextWriter.StatementFlags) _currentStatementFlags, in _writeContext); + + _currentStatementFlags = originalStatementFlags; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected void StatementNeedsSemicolon() => _currentStatementFlags |= StatementFlags.NeedsSemicolon; + + #endregion + + #region Expressions + + [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, in _writeContext); + Visit(expression); + Writer.EndExpression((JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, in _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, in _writeContext); + + for (var i = 0; i < expressionList.Count; i++) + { + VisitExpressionListItem(expressionList[i], i, expressionList.Count, getCombinedItemFlags); + } + + Writer.EndExpressionList(expressionList.Count, in _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, in _writeContext); + Visit(expression); + Writer.EndExpressionListItem(index, count, (JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, in _writeContext); + + _currentExpressionFlags = originalExpressionFlags; + } + + private void VisitAssertions(in NodeList assertions) + { + // https://github.com/tc39/proposal-import-assertions + + Writer.WriteKeyword("assert", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + Writer.StartObject(assertions.Count, in _writeContext); + + VisitAuxiliaryNodeList(in assertions, separator: ","); + + Writer.EndObject(assertions.Count, in _writeContext); + } + + private void VisitExportOrImportSpecifierIdentifier(Expression identifierExpression) + { + if (identifierExpression is Identifier identifier && identifier.Name == "default") + { + Writer.WriteKeyword("default", in _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, in _writeContext); + VisitRootExpression(key, RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(key))); + Writer.WritePunctuator("]", TokenFlags.Trailing | trailingBracketFlags, in _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; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + 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; + } + + #endregion + + #region Auxiliary nodes + + [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, in _writeContext); + Visit(node); + Writer.EndAuxiliaryNode(_currentAuxiliaryNodeContext, in _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, in _writeContext); + + for (var i = 0; i < nodeList.Count; i++) + { + VisitAuxiliaryNodeListItem(nodeList[i], i, nodeList.Count, separator, getNodeContext); + } + + Writer.EndAuxiliaryNodeList(nodeList.Count, in _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, in _writeContext); + Visit(node); + Writer.EndAuxiliaryNodeListItem(index, count, separator, _currentAuxiliaryNodeContext, in _writeContext); + + _currentAuxiliaryNodeContext = originalAuxiliaryNodeContext; + } + + #endregion +} diff --git a/src/Esprima/Utils/AstToJavascriptConverter.cs b/src/Esprima/Utils/AstToJavascriptConverter.cs index c22f3530..66f761d8 100644 --- a/src/Esprima/Utils/AstToJavascriptConverter.cs +++ b/src/Esprima/Utils/AstToJavascriptConverter.cs @@ -1,5 +1,4 @@ -using System.Diagnostics; -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; using Esprima.Ast; using static Esprima.Utils.JavascriptTextWriter; @@ -63,734 +62,446 @@ public void Convert(Node node) return result; } - protected internal override object? VisitProgram(Program program) + protected internal override object? VisitArrayExpression(ArrayExpression arrayExpression) { - _writeContext.SetNodeProperty(nameof(program.Body), static node => ref node.As().Body); - VisitStatementList(in program.Body); + _writeContext.SetNodeProperty(nameof(arrayExpression.Elements), static node => ref node.As().Elements); - return program; - } + Writer.StartArray(arrayExpression.Elements.Count, in _writeContext); - 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)); + // Elements need special care because it may contain null values denoting omitted elements. - return chainExpression; - } + Writer.StartExpressionList(arrayExpression.Elements.Count, in _writeContext); - protected internal override object? VisitCatchClause(CatchClause catchClause) - { - if (catchClause.Param is not null) + for (var i = 0; i < arrayExpression.Elements.Count; i++) { - _writeContext.SetNodeProperty(nameof(catchClause.Param), static node => node.As().Param); - Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, in _writeContext); - VisitAuxiliaryNode(catchClause.Param); - Writer.WritePunctuator(")", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); - } - - _writeContext.SetNodeProperty(nameof(catchClause.Body), static node => node.As().Body); - VisitStatement(catchClause.Body, StatementBodyFlags(isRightMost: ParentNode!.As().Finalizer is null)); + var element = arrayExpression.Elements[i]; - return catchClause; - } + 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)); - 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, in _writeContext); + Writer.StartExpressionListItem(i, arrayExpression.Elements.Count, (JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, in _writeContext); + Writer.EndExpressionListItem(i, arrayExpression.Elements.Count, (JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, in _writeContext); - _writeContext.ClearNodeProperty(); + _currentExpressionFlags = originalExpressionFlags; + } } - Writer.WriteKeyword("function", TokenFlags.LeadingSpaceRecommended, in _writeContext); + Writer.EndExpressionList(arrayExpression.Elements.Count, in _writeContext); - if (functionDeclaration.Generator) - { - _writeContext.SetNodeProperty(nameof(functionDeclaration.Generator), static node => node.As().Generator); - Writer.WritePunctuator("*", (functionDeclaration.Id is not null).ToFlag(TokenFlags.TrailingSpaceRecommended), in _writeContext); - } + Writer.EndArray(arrayExpression.Elements.Count, in _writeContext); - if (functionDeclaration.Id is not null) - { - _writeContext.SetNodeProperty(nameof(functionDeclaration.Id), static node => node.As().Id); - VisitAuxiliaryNode(functionDeclaration.Id); - } + return arrayExpression; + } - _writeContext.SetNodeProperty(nameof(functionDeclaration.Params), static node => ref node.As().Params); - Writer.WritePunctuator("(", TokenFlags.Leading, in _writeContext); - VisitAuxiliaryNodeList(in functionDeclaration.Params, separator: ","); - Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); + protected internal override object? VisitArrayPattern(ArrayPattern arrayPattern) + { + _writeContext.SetNodeProperty(nameof(arrayPattern.Elements), static node => ref node.As().Elements); - _writeContext.SetNodeProperty(nameof(functionDeclaration.Body), static node => node.As().Body); - VisitStatement(functionDeclaration.Body, StatementBodyFlags(isRightMost: true)); + Writer.StartArray(arrayPattern.Elements.Count, in _writeContext); - return functionDeclaration; - } + // Elements need special care because it may contain null values denoting omitted elements. - protected internal override object? VisitWithStatement(WithStatement withStatement) - { - Writer.WriteKeyword("with", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.StartAuxiliaryNodeList(arrayPattern.Elements.Count, in _writeContext); - _writeContext.SetNodeProperty(nameof(withStatement.Object), static node => node.As().Object); - VisitRootExpression(withStatement.Object, ExpressionFlags.SpaceAroundBracketsRecommended | RootExpressionFlags(needsBrackets: true)); + for (var i = 0; i < arrayPattern.Elements.Count; i++) + { + var element = arrayPattern.Elements[i]; - _writeContext.SetNodeProperty(nameof(withStatement.Body), static node => node.As().Body); - VisitStatement(withStatement.Body, StatementBodyFlags(isRightMost: true)); + var originalAuxiliaryNodeContext = _currentAuxiliaryNodeContext; + _currentAuxiliaryNodeContext = null; - return withStatement; - } + Writer.StartAuxiliaryNodeListItem(i, arrayPattern.Elements.Count, separator: ",", _currentAuxiliaryNodeContext, in _writeContext); + if (element is not null) + { + Visit(element); + } + Writer.EndAuxiliaryNodeListItem(i, arrayPattern.Elements.Count, separator: ",", _currentAuxiliaryNodeContext, in _writeContext); - protected internal override object? VisitWhileStatement(WhileStatement whileStatement) - { - Writer.WriteKeyword("while", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + _currentAuxiliaryNodeContext = originalAuxiliaryNodeContext; + } - _writeContext.SetNodeProperty(nameof(whileStatement.Test), static node => node.As().Test); - VisitRootExpression(whileStatement.Test, ExpressionFlags.SpaceAroundBracketsRecommended | RootExpressionFlags(needsBrackets: true)); + Writer.EndAuxiliaryNodeList(arrayPattern.Elements.Count, in _writeContext); - _writeContext.SetNodeProperty(nameof(whileStatement.Body), static node => node.As().Body); - VisitStatement(whileStatement.Body, StatementBodyFlags(isRightMost: true)); + Writer.EndArray(arrayPattern.Elements.Count, in _writeContext); - return whileStatement; + return arrayPattern; } - protected internal override object? VisitVariableDeclaration(VariableDeclaration variableDeclaration) + protected internal override object? VisitArrowFunctionExpression(ArrowFunctionExpression arrowFunctionExpression) { - _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), in _writeContext); + if (arrowFunctionExpression.Async) + { + _writeContext.SetNodeProperty(nameof(arrowFunctionExpression.Async), static node => node.As().Async); + Writer.WriteKeyword("async", TokenFlags.TrailingSpaceRecommended, in _writeContext); + } - _writeContext.SetNodeProperty(nameof(variableDeclaration.Declarations), static node => ref node.As().Declarations); + _writeContext.SetNodeProperty(nameof(arrowFunctionExpression.Params), static node => ref node.As().Params); - if (!_currentStatementFlags.HasFlagFast(StatementFlags.NestedVariableDeclaration)) + if (arrowFunctionExpression.Params.Count == 1 && arrowFunctionExpression.Params[0].Type == Nodes.Identifier) { - VisitAuxiliaryNodeList(in variableDeclaration.Declarations, separator: ","); - - StatementNeedsSemicolon(); + VisitAuxiliaryNodeList(in arrowFunctionExpression.Params, separator: ","); } - else if (ParentNode is not { Type: Nodes.ForInStatement }) + else { - VisitAuxiliaryNodeList(in variableDeclaration.Declarations, separator: ","); + Writer.WritePunctuator("(", TokenFlags.Leading, in _writeContext); + VisitAuxiliaryNodeList(in arrowFunctionExpression.Params, separator: ","); + Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); + } + + _writeContext.ClearNodeProperty(); + Writer.WritePunctuator("=>", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + _writeContext.SetNodeProperty(nameof(arrowFunctionExpression.Body), static node => node.As().Body); + if (arrowFunctionExpression.Body is BlockStatement bodyBlockStatement) + { + VisitStatement(bodyBlockStatement, StatementFlags.IsRightMost); } else { - VisitAuxiliaryNodeList(in variableDeclaration.Declarations, separator: ",", static delegate { return s_forInLoopDeclarationFlag; }); + 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 variableDeclaration; + return arrowFunctionExpression; } - protected internal override object? VisitTryStatement(TryStatement tryStatement) + protected internal override object? VisitAssignmentExpression(AssignmentExpression assignmentExpression) { - Writer.WriteKeyword("try", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - - _writeContext.SetNodeProperty(nameof(tryStatement.Block), static node => node.As().Block); - StatementFlags bodyFlags; - VisitStatement(tryStatement.Block, bodyFlags = StatementBodyFlags(isRightMost: false)); + _writeContext.SetNodeProperty(nameof(assignmentExpression.Left), static node => node.As().Left); + VisitAuxiliaryNode(assignmentExpression.Left); - if (tryStatement.Handler is not null) - { - _writeContext.ClearNodeProperty(); - Writer.WriteKeyword("catch", TokenFlags.SurroundingSpaceRecommended | StatementBodyFlagsToKeywordFlags(bodyFlags), in _writeContext); + var op = AssignmentExpression.GetAssignmentOperatorToken(assignmentExpression.Operator); - _writeContext.SetNodeProperty(nameof(tryStatement.Handler), static node => node.As().Handler); - VisitAuxiliaryNode(tryStatement.Handler); - bodyFlags = StatementBodyFlags(isRightMost: tryStatement.Finalizer is null); - } + _writeContext.SetNodeProperty(nameof(assignmentExpression.Operator), static node => node.As().Operator); + Writer.WritePunctuator(op, TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); - if (tryStatement.Finalizer is not null) - { - _writeContext.ClearNodeProperty(); - Writer.WriteKeyword("finally", TokenFlags.SurroundingSpaceRecommended | StatementBodyFlagsToKeywordFlags(bodyFlags), in _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(tryStatement.Finalizer), static node => node.As().Finalizer); - VisitStatement(tryStatement.Finalizer, StatementBodyFlags(isRightMost: true)); - } + _writeContext.SetNodeProperty(nameof(assignmentExpression.Right), static node => node.As().Right); + VisitSubExpression(assignmentExpression.Right, SubExpressionFlags(rightNeedsBrackets, isLeftMost: false)); - return tryStatement; + return assignmentExpression; } - protected internal override object? VisitThrowStatement(ThrowStatement throwStatement) + protected internal override object? VisitAssignmentPattern(AssignmentPattern assignmentPattern) { - Writer.WriteKeyword("throw", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + _writeContext.SetNodeProperty(nameof(assignmentPattern.Left), static node => node.As().Left); + VisitAuxiliaryNode(assignmentPattern.Left); - _writeContext.SetNodeProperty(nameof(throwStatement.Argument), static node => node.As().Argument); - VisitRootExpression(throwStatement.Argument, RootExpressionFlags(needsBrackets: false)); + _writeContext.ClearNodeProperty(); + Writer.WritePunctuator("=", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); - StatementNeedsSemicolon(); + _writeContext.SetNodeProperty(nameof(assignmentPattern.Right), static node => node.As().Right); + VisitAuxiliaryNode(assignmentPattern.Right); - return throwStatement; + return assignmentPattern; } - protected internal override object? VisitSwitchStatement(SwitchStatement switchStatement) + protected internal override object? VisitAwaitExpression(AwaitExpression awaitExpression) { - Writer.WriteKeyword("switch", TokenFlags.SurroundingSpaceRecommended, in _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, in _writeContext); + Writer.WriteKeyword("await", TokenFlags.TrailingSpaceRecommended, in _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); + var argumentNeedsBrackets = UnaryOperandNeedsBrackets(awaitExpression, awaitExpression.Argument); - Writer.EndBlock(switchStatement.Cases.Count, in _writeContext); + _writeContext.SetNodeProperty(nameof(awaitExpression.Argument), static node => node.As().Argument); + VisitSubExpression(awaitExpression.Argument, SubExpressionFlags(argumentNeedsBrackets, isLeftMost: false)); - return switchStatement; + return awaitExpression; } - protected internal override object? VisitSwitchCase(SwitchCase switchCase) + protected internal override object? VisitBinaryExpression(BinaryExpression binaryExpression) { - if (switchCase.Test is not null) - { - Writer.WriteKeyword("case", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - - _writeContext.SetNodeProperty(nameof(switchCase.Test), static node => node.As().Test); - VisitRootExpression(switchCase.Test, RootExpressionFlags(needsBrackets: false)); + var operationFlags = BinaryOperandsNeedBrackets(binaryExpression, binaryExpression.Left, binaryExpression.Right); - _writeContext.ClearNodeProperty(); - } - else + // 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) { - Writer.WriteKeyword("default", TokenFlags.LeadingSpaceRecommended, in _writeContext); + operationFlags |= BinaryOperationFlags.LeftOperandNeedsBrackets; } - Writer.WritePunctuator(":", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); + _writeContext.SetNodeProperty(nameof(binaryExpression.Left), static node => node.As().Left); + VisitSubExpression(binaryExpression.Left, SubExpressionFlags(operationFlags.HasFlagFast(BinaryOperationFlags.LeftOperandNeedsBrackets), isLeftMost: true)); - _writeContext.SetNodeProperty(nameof(switchCase.Consequent), static node => ref node.As().Consequent); + var op = BinaryExpression.GetBinaryOperatorToken(binaryExpression.Operator); - if (_currentAuxiliaryNodeContext == s_lastSwitchCaseFlag) + _writeContext.SetNodeProperty(nameof(binaryExpression.Operator), static node => node.As().Operator); + if (char.IsLetter(op[0])) { - // If this is the last case, then the right-most semicolon can be omitted. - VisitStatementList(in switchCase.Consequent); + Writer.WriteKeyword(op, TokenFlags.SurroundingSpaceRecommended, in _writeContext); } 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; - } + Writer.WritePunctuator(op, TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); - protected internal override object? VisitReturnStatement(ReturnStatement returnStatement) - { - Writer.WriteKeyword("return", (returnStatement.Argument is not null).ToFlag(TokenFlags.SurroundingSpaceRecommended, TokenFlags.LeadingSpaceRecommended), in _writeContext); - - if (returnStatement.Argument is not null) - { - _writeContext.SetNodeProperty(nameof(returnStatement.Argument), static node => node.As().Argument); - VisitRootExpression(returnStatement.Argument, RootExpressionFlags(needsBrackets: false)); + // 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; + } } - StatementNeedsSemicolon(); + _writeContext.SetNodeProperty(nameof(binaryExpression.Right), static node => node.As().Right); + VisitSubExpression(binaryExpression.Right, SubExpressionFlags(operationFlags.HasFlagFast(BinaryOperationFlags.RightOperandNeedsBrackets), isLeftMost: false)); - return returnStatement; + return binaryExpression; } - protected internal override object? VisitLabeledStatement(LabeledStatement labeledStatement) + protected internal override object? VisitBlockStatement(BlockStatement blockStatement) { - _writeContext.SetNodeProperty(nameof(labeledStatement.Label), static node => node.As().Label); - Writer.WriteEpsilon(TokenFlags.LeadingSpaceRecommended, in _writeContext); - VisitAuxiliaryNode(labeledStatement.Label); + _writeContext.SetNodeProperty(nameof(blockStatement.Body), static node => ref node.As().Body); + Writer.StartBlock(blockStatement.Body.Count, in _writeContext); - Writer.WritePunctuator(":", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); + VisitStatementList(in blockStatement.Body); - _writeContext.SetNodeProperty(nameof(labeledStatement.Body), static node => node.As().Body); - VisitStatement(labeledStatement.Body, StatementFlags.IsRightMost); + Writer.EndBlock(blockStatement.Body.Count, in _writeContext); - return labeledStatement; + return blockStatement; } - protected internal override object? VisitIfStatement(IfStatement ifStatement) + protected internal override object? VisitBreakStatement(BreakStatement breakStatement) { - Writer.WriteKeyword("if", TokenFlags.SurroundingSpaceRecommended, in _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)); + Writer.WriteKeyword("break", TokenFlags.LeadingSpaceRecommended, in _writeContext); - if (ifStatement.Alternate is not null) + if (breakStatement.Label is not null) { - _writeContext.ClearNodeProperty(); - Writer.WriteKeyword("else", TokenFlags.SurroundingSpaceRecommended | StatementBodyFlagsToKeywordFlags(bodyFlags), in _writeContext); - - _writeContext.SetNodeProperty(nameof(ifStatement.Alternate), static node => node.As().Alternate); - VisitStatement(ifStatement.Alternate, StatementBodyFlags(isRightMost: true)); + _writeContext.SetNodeProperty(nameof(breakStatement.Label), static node => node.As().Label); + VisitRootExpression(breakStatement.Label, RootExpressionFlags(needsBrackets: false)); } - return ifStatement; + StatementNeedsSemicolon(); + + return breakStatement; } - protected internal override object? VisitEmptyStatement(EmptyStatement emptyStatement) + protected internal override object? VisitCallExpression(CallExpression callExpression) { - Writer.WritePunctuator(";", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + var calleeNeedsBrackets = UnaryOperandNeedsBrackets(callExpression, callExpression.Callee); - return emptyStatement; + _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, in _writeContext); + } + + _writeContext.SetNodeProperty(nameof(callExpression.Arguments), static node => ref node.As().Arguments); + Writer.WritePunctuator("(", TokenFlags.Leading, in _writeContext); + VisitSubExpressionList(in callExpression.Arguments); + Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); + + return callExpression; } - protected internal override object? VisitDebuggerStatement(DebuggerStatement debuggerStatement) + protected internal override object? VisitCatchClause(CatchClause catchClause) { - Writer.WriteKeyword("debugger", TokenFlags.LeadingSpaceRecommended, in _writeContext); + if (catchClause.Param is not null) + { + _writeContext.SetNodeProperty(nameof(catchClause.Param), static node => node.As().Param); + Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, in _writeContext); + VisitAuxiliaryNode(catchClause.Param); + Writer.WritePunctuator(")", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); + } - StatementNeedsSemicolon(); + _writeContext.SetNodeProperty(nameof(catchClause.Body), static node => node.As().Body); + VisitStatement(catchClause.Body, StatementBodyFlags(isRightMost: ParentNode!.As().Finalizer is null)); - return debuggerStatement; + return catchClause; } - protected internal override object? VisitExpressionStatement(ExpressionStatement expressionStatement) + protected internal override object? VisitChainExpression(ChainExpression chainExpression) { - _writeContext.SetNodeProperty(nameof(expressionStatement.Expression), static node => node.As().Expression); - Writer.WriteEpsilon(TokenFlags.LeadingSpaceRecommended, in _writeContext); - VisitRootExpression(expressionStatement.Expression, ExpressionFlags.IsInsideStatementExpression | RootExpressionFlags(needsBrackets: false)); - - StatementNeedsSemicolon(); + _writeContext.SetNodeProperty(nameof(chainExpression.Expression), static node => node.As().Expression); + VisitSubExpression(chainExpression.Expression, SubExpressionFlags(needsBrackets: false, isLeftMost: true)); - return expressionStatement; + return chainExpression; } - protected internal override object? VisitForStatement(ForStatement forStatement) + protected internal override object? VisitClassBody(ClassBody classBody) { - Writer.WriteKeyword("for", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + _writeContext.SetNodeProperty(nameof(classBody.Body), static node => ref node.As().Body); + Writer.StartBlock(classBody.Body.Count, in _writeContext); - Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, in _writeContext); + VisitAuxiliaryNodeList(in classBody.Body, separator: string.Empty); - _writeContext.SetNodeProperty(nameof(forStatement.Init), static node => node.As().Init); + Writer.EndBlock(classBody.Body.Count, in _writeContext); - if (forStatement.Init is not null) + return classBody; + } + + protected internal override object? VisitClassDeclaration(ClassDeclaration classDeclaration) + { + if (classDeclaration.Decorators.Count > 0) { - if (forStatement.Init is VariableDeclaration variableDeclaration) - { - VisitStatement(variableDeclaration, StatementFlags.NestedVariableDeclaration); - } - else - { - VisitRootExpression(forStatement.Init.As(), RootExpressionFlags(needsBrackets: false)); - } - } + _writeContext.SetNodeProperty(nameof(classDeclaration.Decorators), static node => ref node.As().Decorators); + VisitAuxiliaryNodeList(classDeclaration.Decorators, separator: string.Empty); - Writer.WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); + _writeContext.ClearNodeProperty(); + } - _writeContext.SetNodeProperty(nameof(forStatement.Test), static node => node.As().Test); + Writer.WriteKeyword("class", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - if (forStatement.Test is not null) + if (classDeclaration.Id is not null) { - VisitRootExpression(forStatement.Test, RootExpressionFlags(needsBrackets: false)); + _writeContext.SetNodeProperty(nameof(classDeclaration.Id), static node => node.As().Id); + VisitAuxiliaryNode(classDeclaration.Id); } - Writer.WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); - - if (forStatement.Update is not null) + if (classDeclaration.SuperClass is not null) { - _writeContext.SetNodeProperty(nameof(forStatement.Update), static node => node.As().Update); + _writeContext.ClearNodeProperty(); + Writer.WriteKeyword("extends", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - VisitRootExpression(forStatement.Update, RootExpressionFlags(needsBrackets: false)); + _writeContext.SetNodeProperty(nameof(classDeclaration.SuperClass), static node => node.As().SuperClass); + VisitRootExpression(classDeclaration.SuperClass, LeftHandSideRootExpressionFlags(needsBrackets: false)); } - _writeContext.ClearNodeProperty(); - Writer.WritePunctuator(")", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); - - _writeContext.SetNodeProperty(nameof(forStatement.Body), static node => node.As().Body); - VisitStatement(forStatement.Body, StatementBodyFlags(isRightMost: true)); + _writeContext.SetNodeProperty(nameof(classDeclaration.Body), static node => node.As().Body); + VisitAuxiliaryNode(classDeclaration.Body); - return forStatement; + return classDeclaration; } - protected internal override object? VisitForInStatement(ForInStatement forInStatement) + protected internal override object? VisitClassExpression(ClassExpression classExpression) { - Writer.WriteKeyword("for", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + if (classExpression.Decorators.Count > 0) + { + _writeContext.SetNodeProperty(nameof(classExpression.Decorators), static node => ref node.As().Decorators); + VisitAuxiliaryNodeList(classExpression.Decorators, separator: string.Empty); - Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, in _writeContext); + _writeContext.ClearNodeProperty(); + } - _writeContext.SetNodeProperty(nameof(forInStatement.Left), static node => node.As().Left); + Writer.WriteKeyword("class", TokenFlags.TrailingSpaceRecommended, in _writeContext); - if (forInStatement.Left is VariableDeclaration variableDeclaration) + if (classExpression.Id is not null) { - VisitStatement(variableDeclaration, StatementFlags.NestedVariableDeclaration); + _writeContext.SetNodeProperty(nameof(classExpression.Id), static node => node.As().Id); + VisitAuxiliaryNode(classExpression.Id); } - else + + if (classExpression.SuperClass is not null) { - VisitAuxiliaryNode(forInStatement.Left); + _writeContext.ClearNodeProperty(); + Writer.WriteKeyword("extends", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + _writeContext.SetNodeProperty(nameof(classExpression.SuperClass), static node => node.As().SuperClass); + VisitRootExpression(classExpression.SuperClass, LeftHandSideRootExpressionFlags(needsBrackets: false)); } - _writeContext.ClearNodeProperty(); - Writer.WriteKeyword("in", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); + _writeContext.SetNodeProperty(nameof(classExpression.Body), static node => node.As().Body); + VisitAuxiliaryNode(classExpression.Body); - _writeContext.SetNodeProperty(nameof(forInStatement.Right), static node => node.As().Right); - VisitRootExpression(forInStatement.Right, RootExpressionFlags(needsBrackets: false)); + return classExpression; + } - _writeContext.ClearNodeProperty(); - Writer.WritePunctuator(")", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); + 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(forInStatement.Body), static node => node.As().Body); - VisitStatement(forInStatement.Body, StatementBodyFlags(isRightMost: true)); + _writeContext.SetNodeProperty(nameof(conditionalExpression.Test), static node => node.As().Test); + VisitSubExpression(conditionalExpression.Test, SubExpressionFlags(operandNeedsBrackets, isLeftMost: true)); - return forInStatement; - } + // Consequent expressions with the same precendence as ternary operator are unambiguous without brackets. + operandNeedsBrackets = GetOperatorPrecedence(conditionalExpression, out _) > GetOperatorPrecedence(conditionalExpression.Consequent, out _); - protected internal override object? VisitDoWhileStatement(DoWhileStatement doWhileStatement) - { - Writer.WriteKeyword("do", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + _writeContext.SetNodeProperty(nameof(conditionalExpression.Consequent), static node => node.As().Consequent); + Writer.WritePunctuator("?", TokenFlags.Leading | TokenFlags.SurroundingSpaceRecommended, in _writeContext); - _writeContext.SetNodeProperty(nameof(doWhileStatement.Body), static node => node.As().Body); - StatementFlags bodyFlags; - VisitStatement(doWhileStatement.Body, bodyFlags = StatementBodyFlags(isRightMost: false)); + 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))); - _writeContext.ClearNodeProperty(); - Writer.WriteKeyword("while", TokenFlags.SurroundingSpaceRecommended | StatementBodyFlagsToKeywordFlags(bodyFlags), in _writeContext); + // 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(doWhileStatement.Test), static node => node.As().Test); - VisitRootExpression(doWhileStatement.Test, ExpressionFlags.SpaceBeforeBracketsRecommended | RootExpressionFlags(needsBrackets: true)); + _writeContext.SetNodeProperty(nameof(conditionalExpression.Alternate), static node => node.As().Alternate); + Writer.WritePunctuator(":", TokenFlags.Leading | TokenFlags.SurroundingSpaceRecommended, in _writeContext); - return doWhileStatement; + VisitSubExpression(conditionalExpression.Alternate, SubExpressionFlags(operandNeedsBrackets, isLeftMost: false)); + + return conditionalExpression; } - protected internal override object? VisitArrowFunctionExpression(ArrowFunctionExpression arrowFunctionExpression) + protected internal override object? VisitContinueStatement(ContinueStatement continueStatement) { - if (arrowFunctionExpression.Async) - { - _writeContext.SetNodeProperty(nameof(arrowFunctionExpression.Async), static node => node.As().Async); - Writer.WriteKeyword("async", TokenFlags.TrailingSpaceRecommended, in _writeContext); - } - - _writeContext.SetNodeProperty(nameof(arrowFunctionExpression.Params), static node => ref node.As().Params); + Writer.WriteKeyword("continue", TokenFlags.LeadingSpaceRecommended, in _writeContext); - if (arrowFunctionExpression.Params.Count == 1 && arrowFunctionExpression.Params[0].Type == Nodes.Identifier) - { - VisitAuxiliaryNodeList(in arrowFunctionExpression.Params, separator: ","); - } - else + if (continueStatement.Label is not null) { - Writer.WritePunctuator("(", TokenFlags.Leading, in _writeContext); - VisitAuxiliaryNodeList(in arrowFunctionExpression.Params, separator: ","); - Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); + _writeContext.SetNodeProperty(nameof(continueStatement.Label), static node => node.As().Label); + VisitRootExpression(continueStatement.Label, RootExpressionFlags(needsBrackets: false)); } - _writeContext.ClearNodeProperty(); - Writer.WritePunctuator("=>", TokenFlags.SurroundingSpaceRecommended, in _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? 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, in _writeContext); - } - else - { - Writer.WritePunctuator(op, TokenFlags.Leading, in _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, in _writeContext); - } - - return unaryExpression; - } - - protected internal override object? VisitThisExpression(ThisExpression thisExpression) - { - Writer.WriteKeyword("this", in _writeContext); - - return thisExpression; - } - - 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? VisitObjectExpression(ObjectExpression objectExpression) - { - _writeContext.SetNodeProperty(nameof(objectExpression.Properties), static node => ref node.As().Properties); - - Writer.StartObject(objectExpression.Properties.Count, in _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, in _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, in _writeContext); - VisitRootExpression(spreadElement, RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(spreadElement))); - Writer.EndAuxiliaryNodeListItem(i, objectExpression.Properties.Count, separator: ",", _currentAuxiliaryNodeContext, in _writeContext); - - _currentAuxiliaryNodeContext = originalAuxiliaryNodeContext; - } - else - { - VisitAuxiliaryNodeListItem(property, i, objectExpression.Properties.Count, separator: ",", static delegate { return null; }); - } - } - - Writer.EndAuxiliaryNodeList(objectExpression.Properties.Count, in _writeContext); - - Writer.EndObject(objectExpression.Properties.Count, in _writeContext); - - return objectExpression; - } - - protected internal override object? VisitNewExpression(NewExpression newExpression) - { - Writer.WriteKeyword("new", TokenFlags.TrailingSpaceRecommended, in _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, in _writeContext); - VisitSubExpressionList(in newExpression.Arguments); - Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); - } - - return newExpression; - } - - 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, in _writeContext); - } - - _writeContext.SetNodeProperty(nameof(memberExpression.Property), static node => node.As().Property); - Writer.WritePunctuator("[", TokenFlags.Leading, in _writeContext); - VisitSubExpression(memberExpression.Property, SubExpressionFlags(needsBrackets: false, isLeftMost: false)); - Writer.WritePunctuator("]", TokenFlags.Trailing, in _writeContext); - } - else - { - _writeContext.ClearNodeProperty(); - Writer.WritePunctuator(memberExpression.Optional ? "?." : ".", TokenFlags.InBetween, in _writeContext); - - _writeContext.SetNodeProperty(nameof(memberExpression.Property), static node => node.As().Property); - VisitSubExpression(memberExpression.Property, SubExpressionFlags(needsBrackets: false, isLeftMost: false)); - } + StatementNeedsSemicolon(); - return memberExpression; + return continueStatement; } - protected internal override object? VisitLiteral(Literal literal) + protected internal override object? VisitDebuggerStatement(DebuggerStatement debuggerStatement) { - _writeContext.SetNodeProperty(nameof(literal.Raw), static node => node.As().Raw); - Writer.WriteLiteral(literal.Raw, literal.TokenType, in _writeContext); - - return literal; - } + Writer.WriteKeyword("debugger", TokenFlags.LeadingSpaceRecommended, in _writeContext); - protected internal override object? VisitIdentifier(Identifier identifier) - { - _writeContext.SetNodeProperty(nameof(identifier.Name), static node => node.As().Name); - Writer.WriteIdentifier(identifier.Name!, in _writeContext); + StatementNeedsSemicolon(); - return identifier; + return debuggerStatement; } - protected internal override object? VisitFunctionExpression(FunctionExpression functionExpression) + protected internal override object? VisitDecorator(Decorator decorator) { - if (!_currentExpressionFlags.HasFlagFast(ExpressionFlags.IsMethod)) - { - if (functionExpression.Async) - { - _writeContext.SetNodeProperty(nameof(functionExpression.Async), static node => node.As().Async); - Writer.WriteKeyword("async", in _writeContext); - - _writeContext.ClearNodeProperty(); - } - - Writer.WriteKeyword("function", in _writeContext); - - if (functionExpression.Generator) - { - _writeContext.SetNodeProperty(nameof(functionExpression.Generator), static node => node.As().Generator); - Writer.WritePunctuator("*", (functionExpression.Id is not null).ToFlag(TokenFlags.TrailingSpaceRecommended), in _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, in _writeContext); - - keyIsFirstToken = false; - } - - if (functionExpression.Generator) - { - _writeContext.SetNodeProperty(nameof(functionExpression.Generator), static node => node.As().Generator); - Writer.WritePunctuator("*", TokenFlags.LeadingSpaceRecommended, in _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, in _writeContext); - } + // https://github.com/tc39/proposal-decorators - VisitPropertyKey(property.Key, property.Computed, leadingBracketFlags: keyIsFirstToken.ToFlag(TokenFlags.LeadingSpaceRecommended)); - } - else - { - Writer.WriteKeyword("constructor", TokenFlags.LeadingSpaceRecommended, in _writeContext); - } - } + Writer.WritePunctuator("@", TokenFlags.Leading | (ParentNode is not Expression).ToFlag(TokenFlags.LeadingSpaceRecommended), in _writeContext); - _writeContext.SetNodeProperty(nameof(functionExpression.Params), static node => ref node.As().Params); - Writer.WritePunctuator("(", TokenFlags.Leading, in _writeContext); - VisitAuxiliaryNodeList(in functionExpression.Params, separator: ","); - Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); + _writeContext.SetNodeProperty(nameof(decorator.Expression), static node => node.As().Expression); + VisitRootExpression(decorator.Expression, LeftHandSideRootExpressionFlags(needsBrackets: false)); - _writeContext.SetNodeProperty(nameof(functionExpression.Body), static node => node.As().Body); - VisitStatement(functionExpression.Body, StatementBodyFlags(isRightMost: true)); + Writer.WriteEpsilon(TokenFlags.TrailingSpaceRecommended, in _writeContext); - return functionExpression; + return decorator; } - protected internal override object? VisitClassExpression(ClassExpression classExpression) + protected internal override object? VisitDoWhileStatement(DoWhileStatement doWhileStatement) { - 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, in _writeContext); - - if (classExpression.Id is not null) - { - _writeContext.SetNodeProperty(nameof(classExpression.Id), static node => node.As().Id); - VisitAuxiliaryNode(classExpression.Id); - } + Writer.WriteKeyword("do", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - if (classExpression.SuperClass is not null) - { - _writeContext.ClearNodeProperty(); - Writer.WriteKeyword("extends", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + _writeContext.SetNodeProperty(nameof(doWhileStatement.Body), static node => node.As().Body); + StatementFlags bodyFlags; + VisitStatement(doWhileStatement.Body, bodyFlags = StatementBodyFlags(isRightMost: false)); - _writeContext.SetNodeProperty(nameof(classExpression.SuperClass), static node => node.As().SuperClass); - VisitRootExpression(classExpression.SuperClass, LeftHandSideRootExpressionFlags(needsBrackets: false)); - } + _writeContext.ClearNodeProperty(); + Writer.WriteKeyword("while", TokenFlags.SurroundingSpaceRecommended | StatementBodyFlagsToKeywordFlags(bodyFlags), in _writeContext); - _writeContext.SetNodeProperty(nameof(classExpression.Body), static node => node.As().Body); - VisitAuxiliaryNode(classExpression.Body); + _writeContext.SetNodeProperty(nameof(doWhileStatement.Test), static node => node.As().Test); + VisitRootExpression(doWhileStatement.Test, ExpressionFlags.SpaceBeforeBracketsRecommended | RootExpressionFlags(needsBrackets: true)); - return classExpression; + return doWhileStatement; } - protected internal override object? VisitExportDefaultDeclaration(ExportDefaultDeclaration exportDefaultDeclaration) + protected internal override object? VisitEmptyStatement(EmptyStatement emptyStatement) { - Writer.WriteKeyword("export", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - Writer.WriteKeyword("default", TokenFlags.SurroundingSpaceRecommended, in _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(); - } + Writer.WritePunctuator(";", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - return exportDefaultDeclaration; + return emptyStatement; } protected internal override object? VisitExportAllDeclaration(ExportAllDeclaration exportAllDeclaration) @@ -812,15 +523,35 @@ unaryExpression.Argument is UnaryExpression argumentUnaryExpression && _writeContext.SetNodeProperty(nameof(exportAllDeclaration.Source), static node => node.As().Source); VisitRootExpression(exportAllDeclaration.Source, RootExpressionFlags(needsBrackets: false)); - if (exportAllDeclaration.Assertions.Count > 0) + 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, in _writeContext); + Writer.WriteKeyword("default", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + _writeContext.SetNodeProperty(nameof(exportDefaultDeclaration.Declaration), static node => node.As().Declaration); + if (exportDefaultDeclaration.Declaration is Declaration declaration) { - _writeContext.SetNodeProperty(nameof(exportAllDeclaration.Assertions), static node => ref node.As().Assertions); - VisitAssertions(in exportAllDeclaration.Assertions); + VisitStatement(declaration, StatementFlags.IsRightMost); } + else + { + VisitRootExpression(exportDefaultDeclaration.Declaration.As(), ExpressionFlags.IsInsideStatementExpression | RootExpressionFlags(needsBrackets: false)); - StatementNeedsSemicolon(); + StatementNeedsSemicolon(); + } - return exportAllDeclaration; + return exportDefaultDeclaration; } protected internal override object? VisitExportNamedDeclaration(ExportNamedDeclaration exportNamedDeclaration) @@ -877,176 +608,47 @@ unaryExpression.Argument is UnaryExpression argumentUnaryExpression && return exportSpecifier; } - protected internal override object? VisitImport(Import import) + protected internal override object? VisitExpressionStatement(ExpressionStatement expressionStatement) { - Writer.WriteKeyword("import", in _writeContext); - - Writer.WritePunctuator("(", TokenFlags.Leading, in _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, in _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, in _writeContext); + _writeContext.SetNodeProperty(nameof(expressionStatement.Expression), static node => node.As().Expression); + Writer.WriteEpsilon(TokenFlags.LeadingSpaceRecommended, in _writeContext); + VisitRootExpression(expressionStatement.Expression, ExpressionFlags.IsInsideStatementExpression | RootExpressionFlags(needsBrackets: false)); - _writeContext.ClearNodeProperty(); - Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); + StatementNeedsSemicolon(); - return import; + return expressionStatement; } - protected internal override object? VisitImportDeclaration(ImportDeclaration importDeclaration) + protected internal override object? VisitForInStatement(ForInStatement forInStatement) { - Writer.WriteKeyword("import", TokenFlags.SurroundingSpaceRecommended, in _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, in _writeContext); - - if (importDeclaration.Specifiers.Count == 0) - { - Writer.EndAuxiliaryNodeList(count: 0, in _writeContext); - - goto WriteSource; - } - - var index = 0; - Func getNodeContext = static delegate { return null; }; + Writer.WriteKeyword("for", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - if (importDeclaration.Specifiers[index].Type == Nodes.ImportDefaultSpecifier) - { - VisitAuxiliaryNodeListItem(importDeclaration.Specifiers[index], index, importDeclaration.Specifiers.Count, ",", getNodeContext); + Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, in _writeContext); - if (++index >= importDeclaration.Specifiers.Count) - { - goto EndSpecifiers; - } - } + _writeContext.SetNodeProperty(nameof(forInStatement.Left), static node => node.As().Left); - if (importDeclaration.Specifiers[index].Type == Nodes.ImportNamespaceSpecifier) + if (forInStatement.Left is VariableDeclaration variableDeclaration) { - VisitAuxiliaryNodeListItem(importDeclaration.Specifiers[index], index, importDeclaration.Specifiers.Count, ",", getNodeContext); - - if (++index >= importDeclaration.Specifiers.Count) - { - goto EndSpecifiers; - } + VisitStatement(variableDeclaration, StatementFlags.NestedVariableDeclaration); } - - Writer.WritePunctuator("{", TokenFlags.Leading | TokenFlags.TrailingSpaceRecommended, in _writeContext); - - for (; index < importDeclaration.Specifiers.Count; index++) + else { - VisitAuxiliaryNodeListItem(importDeclaration.Specifiers[index], index, importDeclaration.Specifiers.Count, ",", getNodeContext); + VisitAuxiliaryNode(forInStatement.Left); } - Writer.WritePunctuator("}", TokenFlags.Trailing | TokenFlags.LeadingSpaceRecommended, in _writeContext); - -EndSpecifiers: - Writer.EndAuxiliaryNodeList(importDeclaration.Specifiers.Count, in _writeContext); - _writeContext.ClearNodeProperty(); - Writer.WriteKeyword("from", TokenFlags.SurroundingSpaceRecommended, in _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? VisitImportNamespaceSpecifier(ImportNamespaceSpecifier importNamespaceSpecifier) - { - Writer.WritePunctuator("*", TokenFlags.TrailingSpaceRecommended, in _writeContext); - - Writer.WriteKeyword("as", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - - _writeContext.SetNodeProperty(nameof(importNamespaceSpecifier.Local), static node => node.As().Local); - VisitAuxiliaryNode(importNamespaceSpecifier.Local); - - return importNamespaceSpecifier; - } - - 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? 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, in _writeContext); - } - - _writeContext.SetNodeProperty(nameof(importSpecifier.Local), static node => node.As().Local); - VisitAuxiliaryNode(importSpecifier.Local); - - return importSpecifier; - } - - 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(); - } + Writer.WriteKeyword("in", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); - if (methodDefinition.Static) - { - _writeContext.SetNodeProperty(nameof(methodDefinition.Static), static node => node.As().Static); - Writer.WriteKeyword("static", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - } + _writeContext.SetNodeProperty(nameof(forInStatement.Right), static node => node.As().Right); + VisitRootExpression(forInStatement.Right, RootExpressionFlags(needsBrackets: false)); - switch (methodDefinition.Kind) - { - case PropertyKind.Get: - _writeContext.SetNodeProperty(nameof(methodDefinition.Kind), static node => node.As().Kind); - Writer.WriteKeyword("get", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - break; - case PropertyKind.Set: - _writeContext.SetNodeProperty(nameof(methodDefinition.Kind), static node => node.As().Kind); - Writer.WriteKeyword("set", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - break; - } + _writeContext.ClearNodeProperty(); + Writer.WritePunctuator(")", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); - _writeContext.SetNodeProperty(nameof(methodDefinition.Value), static node => node.As().Value); - VisitRootExpression(methodDefinition.Value, ExpressionFlags.IsMethod | RootExpressionFlags(needsBrackets: false)); + _writeContext.SetNodeProperty(nameof(forInStatement.Body), static node => node.As().Body); + VisitStatement(forInStatement.Body, StatementBodyFlags(isRightMost: true)); - return methodDefinition; + return forInStatement; } protected internal override object? VisitForOfStatement(ForOfStatement forOfStatement) @@ -1087,529 +689,526 @@ unaryExpression.Argument is UnaryExpression argumentUnaryExpression && return forOfStatement; } - 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, in _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, in _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? VisitClassBody(ClassBody classBody) + protected internal override object? VisitForStatement(ForStatement forStatement) { - _writeContext.SetNodeProperty(nameof(classBody.Body), static node => ref node.As().Body); - Writer.StartBlock(classBody.Body.Count, in _writeContext); - - VisitAuxiliaryNodeList(in classBody.Body, separator: string.Empty); - - Writer.EndBlock(classBody.Body.Count, in _writeContext); - - return classBody; - } + Writer.WriteKeyword("for", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - protected internal override object? VisitYieldExpression(YieldExpression yieldExpression) - { - Writer.WriteKeyword("yield", (!yieldExpression.Delegate && yieldExpression.Argument is not null).ToFlag(TokenFlags.TrailingSpaceRecommended), in _writeContext); + Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, in _writeContext); - if (yieldExpression.Delegate) - { - _writeContext.SetNodeProperty(nameof(yieldExpression.Delegate), static node => node.As().Delegate); - Writer.WritePunctuator("*", (yieldExpression.Argument is not null).ToFlag(TokenFlags.TrailingSpaceRecommended), in _writeContext); - } + _writeContext.SetNodeProperty(nameof(forStatement.Init), static node => node.As().Init); - if (yieldExpression.Argument is not null) + if (forStatement.Init 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)); + if (forStatement.Init is VariableDeclaration variableDeclaration) + { + VisitStatement(variableDeclaration, StatementFlags.NestedVariableDeclaration); + } + else + { + VisitRootExpression(forStatement.Init.As(), RootExpressionFlags(needsBrackets: false)); + } } - return yieldExpression; - } - - 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))); + Writer.WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); - _writeContext.SetNodeProperty(nameof(taggedTemplateExpression.Quasi), static node => node.As().Quasi); - VisitSubExpression(taggedTemplateExpression.Quasi, SubExpressionFlags(needsBrackets: false, isLeftMost: false)); + _writeContext.SetNodeProperty(nameof(forStatement.Test), static node => node.As().Test); - return taggedTemplateExpression; - } + if (forStatement.Test is not null) + { + VisitRootExpression(forStatement.Test, RootExpressionFlags(needsBrackets: false)); + } - protected internal override object? VisitSuper(Super super) - { - Writer.WriteKeyword("super", in _writeContext); + Writer.WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); - return super; - } + if (forStatement.Update is not null) + { + _writeContext.SetNodeProperty(nameof(forStatement.Update), static node => node.As().Update); - protected internal override object? VisitMetaProperty(MetaProperty metaProperty) - { - _writeContext.SetNodeProperty(nameof(metaProperty.Meta), static node => node.As().Meta); - Writer.WriteKeyword(metaProperty.Meta.Name!, in _writeContext); + VisitRootExpression(forStatement.Update, RootExpressionFlags(needsBrackets: false)); + } _writeContext.ClearNodeProperty(); - Writer.WritePunctuator(".", TokenFlags.InBetween, in _writeContext); + Writer.WritePunctuator(")", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); - _writeContext.SetNodeProperty(nameof(metaProperty.Property), static node => node.As().Property); - VisitSubExpression(metaProperty.Property, SubExpressionFlags(needsBrackets: false, isLeftMost: false)); + _writeContext.SetNodeProperty(nameof(forStatement.Body), static node => node.As().Body); + VisitStatement(forStatement.Body, StatementBodyFlags(isRightMost: true)); - return metaProperty; + return forStatement; } - protected internal override object? VisitObjectPattern(ObjectPattern objectPattern) + protected internal override object? VisitFunctionDeclaration(FunctionDeclaration functionDeclaration) { - _writeContext.SetNodeProperty(nameof(objectPattern.Properties), static node => ref node.As().Properties); - - Writer.StartObject(objectPattern.Properties.Count, in _writeContext); + if (functionDeclaration.Async) + { + _writeContext.SetNodeProperty(nameof(functionDeclaration.Async), static node => node.As().Async); + Writer.WriteKeyword("async", TokenFlags.LeadingSpaceRecommended, in _writeContext); - VisitAuxiliaryNodeList(in objectPattern.Properties, separator: ","); + _writeContext.ClearNodeProperty(); + } - Writer.EndObject(objectPattern.Properties.Count, in _writeContext); + Writer.WriteKeyword("function", TokenFlags.LeadingSpaceRecommended, in _writeContext); - return objectPattern; - } + if (functionDeclaration.Generator) + { + _writeContext.SetNodeProperty(nameof(functionDeclaration.Generator), static node => node.As().Generator); + Writer.WritePunctuator("*", (functionDeclaration.Id is not null).ToFlag(TokenFlags.TrailingSpaceRecommended), in _writeContext); + } - protected internal override object? VisitSpreadElement(SpreadElement spreadElement) - { - var argumentNeedsBrackets = UnaryOperandNeedsBrackets(spreadElement, spreadElement.Argument); + if (functionDeclaration.Id is not null) + { + _writeContext.SetNodeProperty(nameof(functionDeclaration.Id), static node => node.As().Id); + VisitAuxiliaryNode(functionDeclaration.Id); + } - _writeContext.SetNodeProperty(nameof(spreadElement.Argument), static node => node.As().Argument); - Writer.WritePunctuator("...", TokenFlags.Leading, in _writeContext); + _writeContext.SetNodeProperty(nameof(functionDeclaration.Params), static node => ref node.As().Params); + Writer.WritePunctuator("(", TokenFlags.Leading, in _writeContext); + VisitAuxiliaryNodeList(in functionDeclaration.Params, separator: ","); + Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); - VisitSubExpression(spreadElement.Argument, SubExpressionFlags(argumentNeedsBrackets, isLeftMost: false)); + _writeContext.SetNodeProperty(nameof(functionDeclaration.Body), static node => node.As().Body); + VisitStatement(functionDeclaration.Body, StatementBodyFlags(isRightMost: true)); - return spreadElement; + return functionDeclaration; } - protected internal override object? VisitAssignmentPattern(AssignmentPattern assignmentPattern) + protected internal override object? VisitFunctionExpression(FunctionExpression functionExpression) { - _writeContext.SetNodeProperty(nameof(assignmentPattern.Left), static node => node.As().Left); - VisitAuxiliaryNode(assignmentPattern.Left); + if (!_currentExpressionFlags.HasFlagFast(ExpressionFlags.IsMethod)) + { + if (functionExpression.Async) + { + _writeContext.SetNodeProperty(nameof(functionExpression.Async), static node => node.As().Async); + Writer.WriteKeyword("async", in _writeContext); - _writeContext.ClearNodeProperty(); - Writer.WritePunctuator("=", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); + _writeContext.ClearNodeProperty(); + } - _writeContext.SetNodeProperty(nameof(assignmentPattern.Right), static node => node.As().Right); - VisitAuxiliaryNode(assignmentPattern.Right); + Writer.WriteKeyword("function", in _writeContext); - return assignmentPattern; - } + if (functionExpression.Generator) + { + _writeContext.SetNodeProperty(nameof(functionExpression.Generator), static node => node.As().Generator); + Writer.WritePunctuator("*", (functionExpression.Id is not null).ToFlag(TokenFlags.TrailingSpaceRecommended), in _writeContext); + } - protected internal override object? VisitArrayPattern(ArrayPattern arrayPattern) - { - _writeContext.SetNodeProperty(nameof(arrayPattern.Elements), static node => ref node.As().Elements); + if (functionExpression.Id is not null) + { + _writeContext.SetNodeProperty(nameof(functionExpression.Id), static node => node.As().Id); + VisitAuxiliaryNode(functionExpression.Id); + } + } + else + { + var keyIsFirstToken = true; - Writer.StartArray(arrayPattern.Elements.Count, in _writeContext); + if (functionExpression.Async) + { + _writeContext.SetNodeProperty(nameof(functionExpression.Async), static node => node.As().Async); + Writer.WriteKeyword("async", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - // Elements need special care because it may contain null values denoting omitted elements. + keyIsFirstToken = false; + } - Writer.StartAuxiliaryNodeList(arrayPattern.Elements.Count, in _writeContext); + if (functionExpression.Generator) + { + _writeContext.SetNodeProperty(nameof(functionExpression.Generator), static node => node.As().Generator); + Writer.WritePunctuator("*", TokenFlags.LeadingSpaceRecommended, in _writeContext); - for (var i = 0; i < arrayPattern.Elements.Count; i++) - { - var element = arrayPattern.Elements[i]; + keyIsFirstToken = false; + } - var originalAuxiliaryNodeContext = _currentAuxiliaryNodeContext; - _currentAuxiliaryNodeContext = null; + _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, in _writeContext); + } - Writer.StartAuxiliaryNodeListItem(i, arrayPattern.Elements.Count, separator: ",", _currentAuxiliaryNodeContext, in _writeContext); - if (element is not null) + VisitPropertyKey(property.Key, property.Computed, leadingBracketFlags: keyIsFirstToken.ToFlag(TokenFlags.LeadingSpaceRecommended)); + } + else { - Visit(element); + Writer.WriteKeyword("constructor", TokenFlags.LeadingSpaceRecommended, in _writeContext); } - Writer.EndAuxiliaryNodeListItem(i, arrayPattern.Elements.Count, separator: ",", _currentAuxiliaryNodeContext, in _writeContext); - - _currentAuxiliaryNodeContext = originalAuxiliaryNodeContext; } - Writer.EndAuxiliaryNodeList(arrayPattern.Elements.Count, in _writeContext); + _writeContext.SetNodeProperty(nameof(functionExpression.Params), static node => ref node.As().Params); + Writer.WritePunctuator("(", TokenFlags.Leading, in _writeContext); + VisitAuxiliaryNodeList(in functionExpression.Params, separator: ","); + Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); - Writer.EndArray(arrayPattern.Elements.Count, in _writeContext); + _writeContext.SetNodeProperty(nameof(functionExpression.Body), static node => node.As().Body); + VisitStatement(functionExpression.Body, StatementBodyFlags(isRightMost: true)); - return arrayPattern; + return functionExpression; } - protected internal override object? VisitVariableDeclarator(VariableDeclarator variableDeclarator) + protected internal override object? VisitIdentifier(Identifier identifier) { - _writeContext.SetNodeProperty(nameof(variableDeclarator.Id), static node => node.As().Id); - VisitAuxiliaryNode(variableDeclarator.Id); + _writeContext.SetNodeProperty(nameof(identifier.Name), static node => node.As().Name); + Writer.WriteIdentifier(identifier.Name!, in _writeContext); - if (variableDeclarator.Init is not null) + return identifier; + } + + protected internal override object? VisitIfStatement(IfStatement ifStatement) + { + Writer.WriteKeyword("if", TokenFlags.SurroundingSpaceRecommended, in _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.WritePunctuator("=", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); - - _writeContext.SetNodeProperty(nameof(variableDeclarator.Init), static node => node.As().Init); + Writer.WriteKeyword("else", TokenFlags.SurroundingSpaceRecommended | StatementBodyFlagsToKeywordFlags(bodyFlags), in _writeContext); - if (_currentAuxiliaryNodeContext != s_forInLoopDeclarationFlag) - { - VisitRootExpression(variableDeclarator.Init, RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(variableDeclarator.Init))); - } - else - { - VisitRootExpression(variableDeclarator.Init, ExpressionFlags.InOperatorIsAmbiguousInDeclaration | RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(variableDeclarator.Init))); - } + _writeContext.SetNodeProperty(nameof(ifStatement.Alternate), static node => node.As().Alternate); + VisitStatement(ifStatement.Alternate, StatementBodyFlags(isRightMost: true)); } - return variableDeclarator; + return ifStatement; } - protected internal override object? VisitTemplateLiteral(TemplateLiteral templateLiteral) + protected internal override object? VisitImport(Import import) { - Writer.WritePunctuator("`", TokenFlags.Leading, in _writeContext); + Writer.WriteKeyword("import", in _writeContext); - TemplateElement quasi; - for (var i = 0; !(quasi = templateLiteral.Quasis[i]).Tail; i++) + Writer.WritePunctuator("(", TokenFlags.Leading, in _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, in _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) { - _writeContext.SetNodeProperty(nameof(templateLiteral.Quasis), static node => ref node.As().Quasis); - VisitAuxiliaryNode(quasi); + // https://github.com/tc39/proposal-import-assertions - _writeContext.SetNodeProperty(nameof(templateLiteral.Expressions), static node => ref node.As().Expressions); - Writer.WritePunctuator("${", TokenFlags.Leading, in _writeContext); - VisitRootExpression(templateLiteral.Expressions[i], RootExpressionFlags(needsBrackets: false)); - Writer.WritePunctuator("}", TokenFlags.Trailing, in _writeContext); + _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))); } - _writeContext.SetNodeProperty(nameof(templateLiteral.Quasis), static node => ref node.As().Quasis); - VisitAuxiliaryNode(quasi); + Writer.EndExpressionList(paramCount, in _writeContext); - Writer.WritePunctuator("`", TokenFlags.Trailing, in _writeContext); + _writeContext.ClearNodeProperty(); + Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); - return templateLiteral; + return import; } - protected internal override object? VisitTemplateElement(TemplateElement templateElement) + protected internal override object? VisitImportAttribute(ImportAttribute importAttribute) { - _writeContext.SetNodeProperty(nameof(templateElement.Value), static node => node.As().Value); - Writer.WriteLiteral(templateElement.Value.Raw, TokenType.Template, in _writeContext); - - return templateElement; - } + // https://github.com/tc39/proposal-import-assertions - protected internal override object? VisitRestElement(RestElement restElement) - { - _writeContext.SetNodeProperty(nameof(restElement.Argument), static node => node.As().Argument); - Writer.WritePunctuator("...", TokenFlags.Leading, in _writeContext); + _writeContext.SetNodeProperty(nameof(importAttribute.Key), static node => node.As().Key); + VisitPropertyKey(importAttribute.Key, computed: false); + Writer.WritePunctuator(":", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); - VisitAuxiliaryNode(restElement.Argument); + _writeContext.SetNodeProperty(nameof(importAttribute.Value), static node => node.As().Value); - return restElement; + VisitRootExpression(importAttribute.Value, RootExpressionFlags(needsBrackets: false)); + + return importAttribute; } - protected internal override object? VisitProperty(Property property) + protected internal override object? VisitImportDeclaration(ImportDeclaration importDeclaration) { - bool isMethod; + Writer.WriteKeyword("import", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - switch (property.Kind) - { - case PropertyKind.Get: - _writeContext.SetNodeProperty(nameof(property.Kind), static node => node.As().Kind); - Writer.WriteKeyword("get", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + // Specifiers need special care because of the unusual syntax. - isMethod = true; - break; - case PropertyKind.Set: - _writeContext.SetNodeProperty(nameof(property.Kind), static node => node.As().Kind); - Writer.WriteKeyword("set", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + _writeContext.SetNodeProperty(nameof(importDeclaration.Specifiers), static node => ref node.As().Specifiers); + Writer.StartAuxiliaryNodeList(importDeclaration.Specifiers.Count, in _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, in _writeContext); - } + if (importDeclaration.Specifiers.Count == 0) + { + Writer.EndAuxiliaryNodeList(count: 0, in _writeContext); - isMethod = false; - break; + goto WriteSource; } - _writeContext.SetNodeProperty(nameof(property.Value), static node => node.As().Value); + var index = 0; + Func getNodeContext = static delegate { return null; }; - if (ParentNode is { Type: Nodes.ObjectPattern }) - { - VisitAuxiliaryNode(property.Value); - } - else + if (importDeclaration.Specifiers[index].Type == Nodes.ImportDefaultSpecifier) { - var expression = property.Value.As(); - VisitRootExpression(expression, isMethod.ToFlag(ExpressionFlags.IsMethod) | RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(expression))); - } + VisitAuxiliaryNodeListItem(importDeclaration.Specifiers[index], index, importDeclaration.Specifiers.Count, ",", getNodeContext); - return property; - } + if (++index >= importDeclaration.Specifiers.Count) + { + goto EndSpecifiers; + } + } - protected internal override object? VisitPropertyDefinition(PropertyDefinition propertyDefinition) - { - if (propertyDefinition.Decorators.Count > 0) + if (importDeclaration.Specifiers[index].Type == Nodes.ImportNamespaceSpecifier) { - _writeContext.SetNodeProperty(nameof(propertyDefinition.Decorators), static node => ref node.As().Decorators); - VisitAuxiliaryNodeList(propertyDefinition.Decorators, separator: string.Empty); + VisitAuxiliaryNodeListItem(importDeclaration.Specifiers[index], index, importDeclaration.Specifiers.Count, ",", getNodeContext); - _writeContext.ClearNodeProperty(); + if (++index >= importDeclaration.Specifiers.Count) + { + goto EndSpecifiers; + } } - if (propertyDefinition.Static) + Writer.WritePunctuator("{", TokenFlags.Leading | TokenFlags.TrailingSpaceRecommended, in _writeContext); + + for (; index < importDeclaration.Specifiers.Count; index++) { - _writeContext.SetNodeProperty(nameof(propertyDefinition.Static), static node => node.As().Static); - Writer.WriteKeyword("static", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + VisitAuxiliaryNodeListItem(importDeclaration.Specifiers[index], index, importDeclaration.Specifiers.Count, ",", getNodeContext); } - _writeContext.SetNodeProperty(nameof(propertyDefinition.Key), static node => node.As().Key); - VisitPropertyKey(propertyDefinition.Key, propertyDefinition.Computed, leadingBracketFlags: TokenFlags.LeadingSpaceRecommended); + Writer.WritePunctuator("}", TokenFlags.Trailing | TokenFlags.LeadingSpaceRecommended, in _writeContext); - if (propertyDefinition.Value is not null) - { - _writeContext.ClearNodeProperty(); - Writer.WritePunctuator("=", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); +EndSpecifiers: + Writer.EndAuxiliaryNodeList(importDeclaration.Specifiers.Count, in _writeContext); - _writeContext.SetNodeProperty(nameof(propertyDefinition.Value), static node => node.As().Value); - VisitRootExpression(propertyDefinition.Value, RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(propertyDefinition.Value))); + _writeContext.ClearNodeProperty(); + Writer.WriteKeyword("from", TokenFlags.SurroundingSpaceRecommended, in _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); } - Writer.WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); + StatementNeedsSemicolon(); - return propertyDefinition; + return importDeclaration; } - protected internal override object? VisitAwaitExpression(AwaitExpression awaitExpression) + protected internal override object? VisitImportDefaultSpecifier(ImportDefaultSpecifier importDefaultSpecifier) { - Writer.WriteKeyword("await", TokenFlags.TrailingSpaceRecommended, in _writeContext); - - var argumentNeedsBrackets = UnaryOperandNeedsBrackets(awaitExpression, awaitExpression.Argument); - - _writeContext.SetNodeProperty(nameof(awaitExpression.Argument), static node => node.As().Argument); - VisitSubExpression(awaitExpression.Argument, SubExpressionFlags(argumentNeedsBrackets, isLeftMost: false)); + _writeContext.SetNodeProperty(nameof(importDefaultSpecifier.Local), static node => node.As().Local); + VisitAuxiliaryNode(importDefaultSpecifier.Local); - return awaitExpression; + return importDefaultSpecifier; } - protected internal override object? VisitConditionalExpression(ConditionalExpression conditionalExpression) + protected internal override object? VisitImportNamespaceSpecifier(ImportNamespaceSpecifier importNamespaceSpecifier) { - // 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, in _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 _); + Writer.WritePunctuator("*", TokenFlags.TrailingSpaceRecommended, in _writeContext); - _writeContext.SetNodeProperty(nameof(conditionalExpression.Alternate), static node => node.As().Alternate); - Writer.WritePunctuator(":", TokenFlags.Leading | TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("as", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - VisitSubExpression(conditionalExpression.Alternate, SubExpressionFlags(operandNeedsBrackets, isLeftMost: false)); + _writeContext.SetNodeProperty(nameof(importNamespaceSpecifier.Local), static node => node.As().Local); + VisitAuxiliaryNode(importNamespaceSpecifier.Local); - return conditionalExpression; + return importNamespaceSpecifier; } - protected internal override object? VisitCallExpression(CallExpression callExpression) + protected internal override object? VisitImportSpecifier(ImportSpecifier importSpecifier) { - 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) + if (importSpecifier.Imported != importSpecifier.Local) { + _writeContext.SetNodeProperty(nameof(importSpecifier.Imported), static node => node.As().Imported); + VisitExportOrImportSpecifierIdentifier(importSpecifier.Imported); + _writeContext.ClearNodeProperty(); - Writer.WritePunctuator("?.", TokenFlags.InBetween, in _writeContext); + Writer.WriteKeyword("as", TokenFlags.SurroundingSpaceRecommended, in _writeContext); } - _writeContext.SetNodeProperty(nameof(callExpression.Arguments), static node => ref node.As().Arguments); - Writer.WritePunctuator("(", TokenFlags.Leading, in _writeContext); - VisitSubExpressionList(in callExpression.Arguments); - Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); + _writeContext.SetNodeProperty(nameof(importSpecifier.Local), static node => node.As().Local); + VisitAuxiliaryNode(importSpecifier.Local); - return callExpression; + return importSpecifier; } - protected internal override object? VisitBinaryExpression(BinaryExpression binaryExpression) + protected internal override object? VisitLabeledStatement(LabeledStatement labeledStatement) { - 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)); + _writeContext.SetNodeProperty(nameof(labeledStatement.Label), static node => node.As().Label); + Writer.WriteEpsilon(TokenFlags.LeadingSpaceRecommended, in _writeContext); + VisitAuxiliaryNode(labeledStatement.Label); - var op = BinaryExpression.GetBinaryOperatorToken(binaryExpression.Operator); + Writer.WritePunctuator(":", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); - _writeContext.SetNodeProperty(nameof(binaryExpression.Operator), static node => node.As().Operator); - if (char.IsLetter(op[0])) - { - Writer.WriteKeyword(op, TokenFlags.SurroundingSpaceRecommended, in _writeContext); - } - else - { - Writer.WritePunctuator(op, TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); + _writeContext.SetNodeProperty(nameof(labeledStatement.Body), static node => node.As().Body); + VisitStatement(labeledStatement.Body, StatementFlags.IsRightMost); - // 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; - } - } + return labeledStatement; + } - _writeContext.SetNodeProperty(nameof(binaryExpression.Right), static node => node.As().Right); - VisitSubExpression(binaryExpression.Right, SubExpressionFlags(operationFlags.HasFlagFast(BinaryOperationFlags.RightOperandNeedsBrackets), isLeftMost: false)); + protected internal override object? VisitLiteral(Literal literal) + { + _writeContext.SetNodeProperty(nameof(literal.Raw), static node => node.As().Raw); + Writer.WriteLiteral(literal.Raw, literal.TokenType, in _writeContext); - return binaryExpression; + return literal; } - protected internal override object? VisitArrayExpression(ArrayExpression arrayExpression) + protected internal override object? VisitMemberExpression(MemberExpression memberExpression) { - _writeContext.SetNodeProperty(nameof(arrayExpression.Elements), static node => ref node.As().Elements); - - Writer.StartArray(arrayExpression.Elements.Count, in _writeContext); + var operationFlags = BinaryOperandsNeedBrackets(memberExpression, memberExpression.Object, memberExpression.Property); - // Elements need special care because it may contain null values denoting omitted elements. + // 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; + } - Writer.StartExpressionList(arrayExpression.Elements.Count, in _writeContext); + _writeContext.SetNodeProperty(nameof(memberExpression.Object), static node => node.As().Object); + VisitSubExpression(memberExpression.Object, SubExpressionFlags(operationFlags.HasFlagFast(BinaryOperationFlags.LeftOperandNeedsBrackets), isLeftMost: true)); - for (var i = 0; i < arrayExpression.Elements.Count; i++) + if (memberExpression.Computed) { - var element = arrayExpression.Elements[i]; - - if (element is not null) + if (memberExpression.Optional) { - VisitExpressionListItem(element, i, arrayExpression.Elements.Count, static (@this, expression, index, _) => - s_getCombinedSubExpressionFlags(@this, expression, SubExpressionFlags(@this.ExpressionNeedsBracketsInList(expression), isLeftMost: false))); + _writeContext.ClearNodeProperty(); + Writer.WritePunctuator("?.", TokenFlags.InBetween, in _writeContext); } - else - { - var originalExpressionFlags = _currentExpressionFlags; - _currentExpressionFlags = PropagateExpressionFlags(SubExpressionFlags(needsBrackets: false, isLeftMost: false)); - Writer.StartExpressionListItem(i, arrayExpression.Elements.Count, (JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, in _writeContext); - Writer.EndExpressionListItem(i, arrayExpression.Elements.Count, (JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, in _writeContext); + _writeContext.SetNodeProperty(nameof(memberExpression.Property), static node => node.As().Property); + Writer.WritePunctuator("[", TokenFlags.Leading, in _writeContext); + VisitSubExpression(memberExpression.Property, SubExpressionFlags(needsBrackets: false, isLeftMost: false)); + Writer.WritePunctuator("]", TokenFlags.Trailing, in _writeContext); + } + else + { + _writeContext.ClearNodeProperty(); + Writer.WritePunctuator(memberExpression.Optional ? "?." : ".", TokenFlags.InBetween, in _writeContext); - _currentExpressionFlags = originalExpressionFlags; - } + _writeContext.SetNodeProperty(nameof(memberExpression.Property), static node => node.As().Property); + VisitSubExpression(memberExpression.Property, SubExpressionFlags(needsBrackets: false, isLeftMost: false)); } - Writer.EndExpressionList(arrayExpression.Elements.Count, in _writeContext); + return memberExpression; + } - Writer.EndArray(arrayExpression.Elements.Count, in _writeContext); + protected internal override object? VisitMetaProperty(MetaProperty metaProperty) + { + _writeContext.SetNodeProperty(nameof(metaProperty.Meta), static node => node.As().Meta); + Writer.WriteKeyword(metaProperty.Meta.Name!, in _writeContext); - return arrayExpression; + _writeContext.ClearNodeProperty(); + Writer.WritePunctuator(".", TokenFlags.InBetween, in _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? VisitAssignmentExpression(AssignmentExpression assignmentExpression) + protected internal override object? VisitMethodDefinition(MethodDefinition methodDefinition) { - _writeContext.SetNodeProperty(nameof(assignmentExpression.Left), static node => node.As().Left); - VisitAuxiliaryNode(assignmentExpression.Left); + if (methodDefinition.Decorators.Count > 0) + { + _writeContext.SetNodeProperty(nameof(methodDefinition.Decorators), static node => ref node.As().Decorators); + VisitAuxiliaryNodeList(methodDefinition.Decorators, separator: string.Empty); - var op = AssignmentExpression.GetAssignmentOperatorToken(assignmentExpression.Operator); + _writeContext.ClearNodeProperty(); + } - _writeContext.SetNodeProperty(nameof(assignmentExpression.Operator), static node => node.As().Operator); - Writer.WritePunctuator(op, TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); + if (methodDefinition.Static) + { + _writeContext.SetNodeProperty(nameof(methodDefinition.Static), static node => node.As().Static); + Writer.WriteKeyword("static", TokenFlags.SurroundingSpaceRecommended, in _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 _); + switch (methodDefinition.Kind) + { + case PropertyKind.Get: + _writeContext.SetNodeProperty(nameof(methodDefinition.Kind), static node => node.As().Kind); + Writer.WriteKeyword("get", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + break; + case PropertyKind.Set: + _writeContext.SetNodeProperty(nameof(methodDefinition.Kind), static node => node.As().Kind); + Writer.WriteKeyword("set", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + break; + } - _writeContext.SetNodeProperty(nameof(assignmentExpression.Right), static node => node.As().Right); - VisitSubExpression(assignmentExpression.Right, SubExpressionFlags(rightNeedsBrackets, isLeftMost: false)); + _writeContext.SetNodeProperty(nameof(methodDefinition.Value), static node => node.As().Value); + VisitRootExpression(methodDefinition.Value, ExpressionFlags.IsMethod | RootExpressionFlags(needsBrackets: false)); - return assignmentExpression; + return methodDefinition; } - protected internal override object? VisitContinueStatement(ContinueStatement continueStatement) + protected internal override object? VisitNewExpression(NewExpression newExpression) { - Writer.WriteKeyword("continue", TokenFlags.LeadingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("new", TokenFlags.TrailingSpaceRecommended, in _writeContext); - if (continueStatement.Label is not null) + 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(continueStatement.Label), static node => node.As().Label); - VisitRootExpression(continueStatement.Label, RootExpressionFlags(needsBrackets: false)); + _writeContext.SetNodeProperty(nameof(newExpression.Arguments), static node => ref node.As().Arguments); + Writer.WritePunctuator("(", TokenFlags.Leading, in _writeContext); + VisitSubExpressionList(in newExpression.Arguments); + Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); } - StatementNeedsSemicolon(); - - return continueStatement; + return newExpression; } - protected internal override object? VisitBreakStatement(BreakStatement breakStatement) + protected internal override object? VisitObjectExpression(ObjectExpression objectExpression) { - Writer.WriteKeyword("break", TokenFlags.LeadingSpaceRecommended, in _writeContext); + _writeContext.SetNodeProperty(nameof(objectExpression.Properties), static node => ref node.As().Properties); - if (breakStatement.Label is not null) + Writer.StartObject(objectExpression.Properties.Count, in _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, in _writeContext); + + for (var i = 0; i < objectExpression.Properties.Count; i++) { - _writeContext.SetNodeProperty(nameof(breakStatement.Label), static node => node.As().Label); - VisitRootExpression(breakStatement.Label, RootExpressionFlags(needsBrackets: false)); + var property = objectExpression.Properties[i]; + if (property is SpreadElement spreadElement) + { + var originalAuxiliaryNodeContext = _currentAuxiliaryNodeContext; + _currentAuxiliaryNodeContext = null; + + Writer.StartAuxiliaryNodeListItem(i, objectExpression.Properties.Count, separator: ",", _currentAuxiliaryNodeContext, in _writeContext); + VisitRootExpression(spreadElement, RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(spreadElement))); + Writer.EndAuxiliaryNodeListItem(i, objectExpression.Properties.Count, separator: ",", _currentAuxiliaryNodeContext, in _writeContext); + + _currentAuxiliaryNodeContext = originalAuxiliaryNodeContext; + } + else + { + VisitAuxiliaryNodeListItem(property, i, objectExpression.Properties.Count, separator: ",", static delegate { return null; }); + } } - StatementNeedsSemicolon(); + Writer.EndAuxiliaryNodeList(objectExpression.Properties.Count, in _writeContext); - return breakStatement; + Writer.EndObject(objectExpression.Properties.Count, in _writeContext); + + return objectExpression; } - protected internal override object? VisitBlockStatement(BlockStatement blockStatement) + protected internal override object? VisitObjectPattern(ObjectPattern objectPattern) { - _writeContext.SetNodeProperty(nameof(blockStatement.Body), static node => ref node.As().Body); - Writer.StartBlock(blockStatement.Body.Count, in _writeContext); + _writeContext.SetNodeProperty(nameof(objectPattern.Properties), static node => ref node.As().Properties); - VisitStatementList(in blockStatement.Body); + Writer.StartObject(objectPattern.Properties.Count, in _writeContext); - Writer.EndBlock(blockStatement.Body.Count, in _writeContext); + VisitAuxiliaryNodeList(in objectPattern.Properties, separator: ","); - return blockStatement; + Writer.EndObject(objectPattern.Properties.Count, in _writeContext); + + return objectPattern; } protected internal override object? VisitPrivateIdentifier(PrivateIdentifier privateIdentifier) @@ -1621,462 +1220,446 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && return privateIdentifier; } - protected internal override object? VisitStaticBlock(StaticBlock staticBlock) + protected internal override object? VisitProgram(Program program) { - Writer.WriteKeyword("static", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + _writeContext.SetNodeProperty(nameof(program.Body), static node => ref node.As().Body); + VisitStatementList(in program.Body); - _writeContext.SetNodeProperty(nameof(staticBlock.Body), static node => ref node.As().Body); - Writer.StartBlock(staticBlock.Body.Count, in _writeContext); + return program; + } - VisitStatementList(in staticBlock.Body); + protected internal override object? VisitProperty(Property property) + { + bool isMethod; - Writer.EndBlock(staticBlock.Body.Count, in _writeContext); + switch (property.Kind) + { + case PropertyKind.Get: + _writeContext.SetNodeProperty(nameof(property.Kind), static node => node.As().Kind); + Writer.WriteKeyword("get", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - return staticBlock; - } + isMethod = true; + break; + case PropertyKind.Set: + _writeContext.SetNodeProperty(nameof(property.Kind), static node => node.As().Kind); + Writer.WriteKeyword("set", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - protected internal override object? VisitDecorator(Decorator decorator) - { - // https://github.com/tc39/proposal-decorators + 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, in _writeContext); + } - Writer.WritePunctuator("@", TokenFlags.Leading | (ParentNode is not Expression).ToFlag(TokenFlags.LeadingSpaceRecommended), in _writeContext); + isMethod = false; + break; + } - _writeContext.SetNodeProperty(nameof(decorator.Expression), static node => node.As().Expression); - VisitRootExpression(decorator.Expression, LeftHandSideRootExpressionFlags(needsBrackets: false)); + _writeContext.SetNodeProperty(nameof(property.Value), static node => node.As().Value); - Writer.WriteEpsilon(TokenFlags.TrailingSpaceRecommended, in _writeContext); + 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 decorator; + return property; } - protected internal override object? VisitImportAttribute(ImportAttribute importAttribute) + protected internal override object? VisitPropertyDefinition(PropertyDefinition propertyDefinition) { - // https://github.com/tc39/proposal-import-assertions + if (propertyDefinition.Decorators.Count > 0) + { + _writeContext.SetNodeProperty(nameof(propertyDefinition.Decorators), static node => ref node.As().Decorators); + VisitAuxiliaryNodeList(propertyDefinition.Decorators, separator: string.Empty); - _writeContext.SetNodeProperty(nameof(importAttribute.Key), static node => node.As().Key); - VisitPropertyKey(importAttribute.Key, computed: false); - Writer.WritePunctuator(":", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); + _writeContext.ClearNodeProperty(); + } - _writeContext.SetNodeProperty(nameof(importAttribute.Value), static node => node.As().Value); + if (propertyDefinition.Static) + { + _writeContext.SetNodeProperty(nameof(propertyDefinition.Static), static node => node.As().Static); + Writer.WriteKeyword("static", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + } - VisitRootExpression(importAttribute.Value, RootExpressionFlags(needsBrackets: false)); + _writeContext.SetNodeProperty(nameof(propertyDefinition.Key), static node => node.As().Key); + VisitPropertyKey(propertyDefinition.Key, propertyDefinition.Computed, leadingBracketFlags: TokenFlags.LeadingSpaceRecommended); - return importAttribute; - } + if (propertyDefinition.Value is not null) + { + _writeContext.ClearNodeProperty(); + Writer.WritePunctuator("=", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); - #region Statements + _writeContext.SetNodeProperty(nameof(propertyDefinition.Value), static node => node.As().Value); + VisitRootExpression(propertyDefinition.Value, RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(propertyDefinition.Value))); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private protected static StatementFlags StatementBodyFlags(bool isRightMost) - { - return StatementFlags.IsStatementBody | isRightMost.ToFlag(StatementFlags.IsRightMost); - } + Writer.WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private protected static TokenFlags StatementBodyFlagsToKeywordFlags(StatementFlags previousBodyFlags) - { - // Maps IsStatementBody to keyword flags. - return (TokenFlags) (previousBodyFlags & StatementFlags.IsStatementBody); + return propertyDefinition; } - protected StatementFlags PropagateStatementFlags(StatementFlags flags) + protected internal override object? VisitRestElement(RestElement restElement) { - // 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; - } + _writeContext.SetNodeProperty(nameof(restElement.Argument), static node => node.As().Argument); + Writer.WritePunctuator("...", TokenFlags.Leading, in _writeContext); - private protected static readonly Func s_getCombinedStatementFlags = static (@this, statement, flags) => - @this.PropagateStatementFlags(flags); + VisitAuxiliaryNode(restElement.Argument); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void VisitStatement(Statement statement, StatementFlags flags) - { - VisitStatement(statement, flags, s_getCombinedStatementFlags); + return restElement; } - protected void VisitStatement(Statement statement, StatementFlags flags, Func getCombinedFlags) + protected internal override object? VisitReturnStatement(ReturnStatement returnStatement) { - var originalStatementFlags = _currentStatementFlags; - _currentStatementFlags = getCombinedFlags(this, statement, flags); + Writer.WriteKeyword("return", (returnStatement.Argument is not null).ToFlag(TokenFlags.SurroundingSpaceRecommended, TokenFlags.LeadingSpaceRecommended), in _writeContext); - Writer.StartStatement((JavascriptTextWriter.StatementFlags) _currentStatementFlags, in _writeContext); - Visit(statement); - Writer.EndStatement((JavascriptTextWriter.StatementFlags) _currentStatementFlags, in _writeContext); + if (returnStatement.Argument is not null) + { + _writeContext.SetNodeProperty(nameof(returnStatement.Argument), static node => node.As().Argument); + VisitRootExpression(returnStatement.Argument, RootExpressionFlags(needsBrackets: false)); + } - _currentStatementFlags = originalStatementFlags; - } + StatementNeedsSemicolon(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void VisitStatementList(in NodeList statementList) - { - VisitStatementList(in statementList, static (_, _, index, count) => - (index == count - 1).ToFlag(StatementFlags.IsRightMost | StatementFlags.MayOmitRightMostSemicolon)); + return returnStatement; } - protected void VisitStatementList(in NodeList statementList, Func getCombinedItemFlags) + protected internal override object? VisitSequenceExpression(SequenceExpression sequenceExpression) { - Writer.StartStatementList(statementList.Count, in _writeContext); + _writeContext.SetNodeProperty(nameof(sequenceExpression.Expressions), static node => ref node.As().Expressions); - for (var i = 0; i < statementList.Count; i++) - { - VisitStatementListItem(statementList[i], i, statementList.Count, getCombinedItemFlags); - } + VisitExpressionList(in sequenceExpression.Expressions, static (@this, expression, index, _) => + s_getCombinedSubExpressionFlags(@this, expression, SubExpressionFlags(@this.ExpressionNeedsBracketsInList(expression), isLeftMost: index == 0))); - Writer.EndStatementList(statementList.Count, in _writeContext); + return sequenceExpression; } - protected void VisitStatementListItem(Statement statement, int index, int count, Func getCombinedFlags) + protected internal override object? VisitSpreadElement(SpreadElement spreadElement) { - var originalStatementFlags = _currentStatementFlags; - _currentStatementFlags = getCombinedFlags(this, statement, index, count); + var argumentNeedsBrackets = UnaryOperandNeedsBrackets(spreadElement, spreadElement.Argument); + + _writeContext.SetNodeProperty(nameof(spreadElement.Argument), static node => node.As().Argument); + Writer.WritePunctuator("...", TokenFlags.Leading, in _writeContext); - Writer.StartStatementListItem(index, count, (JavascriptTextWriter.StatementFlags) _currentStatementFlags, in _writeContext); - Visit(statement); - Writer.EndStatementListItem(index, count, (JavascriptTextWriter.StatementFlags) _currentStatementFlags, in _writeContext); + VisitSubExpression(spreadElement.Argument, SubExpressionFlags(argumentNeedsBrackets, isLeftMost: false)); - _currentStatementFlags = originalStatementFlags; + return spreadElement; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void StatementNeedsSemicolon() => _currentStatementFlags |= StatementFlags.NeedsSemicolon; + protected internal override object? VisitStaticBlock(StaticBlock staticBlock) + { + Writer.WriteKeyword("static", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - #endregion + _writeContext.SetNodeProperty(nameof(staticBlock.Body), static node => ref node.As().Body); + Writer.StartBlock(staticBlock.Body.Count, in _writeContext); - #region Expressions + VisitStatementList(in staticBlock.Body); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private protected static ExpressionFlags RootExpressionFlags(bool needsBrackets) - { - return ExpressionFlags.IsLeftMost | needsBrackets.ToFlag(ExpressionFlags.NeedsBrackets); - } + Writer.EndBlock(staticBlock.Body.Count, in _writeContext); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private protected static ExpressionFlags LeftHandSideRootExpressionFlags(bool needsBrackets) - { - return ExpressionFlags.IsInsideLeftHandSideExpression | ExpressionFlags.IsLeftMostInLeftHandSideExpression | RootExpressionFlags(needsBrackets); + return staticBlock; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private protected static ExpressionFlags SubExpressionFlags(bool needsBrackets, bool isLeftMost) + protected internal override object? VisitSuper(Super super) { - return needsBrackets.ToFlag(ExpressionFlags.NeedsBrackets) | isLeftMost.ToFlag(ExpressionFlags.IsLeftMost); + Writer.WriteKeyword("super", in _writeContext); + + return super; } - protected ExpressionFlags PropagateExpressionFlags(ExpressionFlags flags) + protected internal override object? VisitSwitchCase(SwitchCase switchCase) { - 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)) + if (switchCase.Test is not null) { - flags &= ~isLeftMostFlags; + Writer.WriteKeyword("case", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + _writeContext.SetNodeProperty(nameof(switchCase.Test), static node => node.As().Test); + VisitRootExpression(switchCase.Test, RootExpressionFlags(needsBrackets: false)); + + _writeContext.ClearNodeProperty(); } else { - flags = flags & ~isLeftMostFlags | _currentExpressionFlags & isLeftMostFlags; + Writer.WriteKeyword("default", TokenFlags.LeadingSpaceRecommended, in _writeContext); } - // Propagates IsInsideStatementExpression, IsInsideArrowFunctionBody and IsInsideLeftHandSideExpression to current expression. - flags |= _currentExpressionFlags & ExpressionFlags.IsInPotentiallyAmbiguousContext; + Writer.WritePunctuator(":", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); - return flags; - } + _writeContext.SetNodeProperty(nameof(switchCase.Consequent), static node => ref node.As().Consequent); - protected ExpressionFlags DisambiguateExpression(Expression expression, ExpressionFlags flags) - { - if (flags.HasFlagFast(ExpressionFlags.NeedsBrackets)) + if (_currentAuxiliaryNodeContext == s_lastSwitchCaseFlag) { - return flags & ~ExpressionFlags.InOperatorIsAmbiguousInDeclaration; + // If this is the last case, then the right-most semicolon can be omitted. + VisitStatementList(in switchCase.Consequent); } - - // Puts the left-most expression in brackets if necessary (in cases where it would be interpreted differently without brackets). - if ((flags & ExpressionFlags.IsInPotentiallyAmbiguousContext) != 0) + else { - 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; - } + // 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 flags; + return switchCase; } - 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) + protected internal override object? VisitSwitchStatement(SwitchStatement switchStatement) { - VisitExpression(expression, flags, s_getCombinedRootExpressionFlags); - } + Writer.WriteKeyword("switch", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - private protected static readonly Func s_getCombinedSubExpressionFlags = static (@this, expression, flags) => - @this.DisambiguateExpression(expression, @this.PropagateExpressionFlags(flags)); + _writeContext.SetNodeProperty(nameof(switchStatement.Discriminant), static node => node.As().Discriminant); + VisitRootExpression(switchStatement.Discriminant, ExpressionFlags.SpaceAroundBracketsRecommended | RootExpressionFlags(needsBrackets: true)); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void VisitSubExpression(Expression expression, ExpressionFlags flags) - { - VisitExpression(expression, flags, s_getCombinedSubExpressionFlags); + _writeContext.SetNodeProperty(nameof(switchStatement.Cases), static node => ref node.As().Cases); + Writer.StartBlock(switchStatement.Cases.Count, in _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, in _writeContext); + + return switchStatement; } - protected void VisitExpression(Expression expression, ExpressionFlags flags, Func getCombinedFlags) + protected internal override object? VisitTaggedTemplateExpression(TaggedTemplateExpression taggedTemplateExpression) { - var originalExpressionFlags = _currentExpressionFlags; - _currentExpressionFlags = getCombinedFlags(this, expression, flags); + _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))); - Writer.StartExpression((JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, in _writeContext); - Visit(expression); - Writer.EndExpression((JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, in _writeContext); + _writeContext.SetNodeProperty(nameof(taggedTemplateExpression.Quasi), static node => node.As().Quasi); + VisitSubExpression(taggedTemplateExpression.Quasi, SubExpressionFlags(needsBrackets: false, isLeftMost: false)); - _currentExpressionFlags = originalExpressionFlags; + return taggedTemplateExpression; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void VisitSubExpressionList(in NodeList expressionList) + protected internal override object? VisitTemplateElement(TemplateElement templateElement) { - VisitExpressionList(in expressionList, static (@this, expression, index, _) => - s_getCombinedSubExpressionFlags(@this, expression, SubExpressionFlags(@this.ExpressionNeedsBracketsInList(expression), isLeftMost: false))); + _writeContext.SetNodeProperty(nameof(templateElement.Value), static node => node.As().Value); + Writer.WriteLiteral(templateElement.Value.Raw, TokenType.Template, in _writeContext); + + return templateElement; } - protected void VisitExpressionList(in NodeList expressionList, Func getCombinedItemFlags) + protected internal override object? VisitTemplateLiteral(TemplateLiteral templateLiteral) { - Writer.StartExpressionList(expressionList.Count, in _writeContext); + Writer.WritePunctuator("`", TokenFlags.Leading, in _writeContext); - for (var i = 0; i < expressionList.Count; i++) + TemplateElement quasi; + for (var i = 0; !(quasi = templateLiteral.Quasis[i]).Tail; i++) { - VisitExpressionListItem(expressionList[i], i, expressionList.Count, getCombinedItemFlags); + _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, in _writeContext); + VisitRootExpression(templateLiteral.Expressions[i], RootExpressionFlags(needsBrackets: false)); + Writer.WritePunctuator("}", TokenFlags.Trailing, in _writeContext); } - Writer.EndExpressionList(expressionList.Count, in _writeContext); + _writeContext.SetNodeProperty(nameof(templateLiteral.Quasis), static node => ref node.As().Quasis); + VisitAuxiliaryNode(quasi); + + Writer.WritePunctuator("`", TokenFlags.Trailing, in _writeContext); + + return templateLiteral; } - protected void VisitExpressionListItem(Expression expression, int index, int count, Func getCombinedFlags) + protected internal override object? VisitThisExpression(ThisExpression thisExpression) { - var originalExpressionFlags = _currentExpressionFlags; - _currentExpressionFlags = getCombinedFlags(this, expression, index, count); - - Writer.StartExpressionListItem(index, count, (JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, in _writeContext); - Visit(expression); - Writer.EndExpressionListItem(index, count, (JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, in _writeContext); + Writer.WriteKeyword("this", in _writeContext); - _currentExpressionFlags = originalExpressionFlags; + return thisExpression; } - private void VisitAssertions(in NodeList assertions) + protected internal override object? VisitThrowStatement(ThrowStatement throwStatement) { - // https://github.com/tc39/proposal-import-assertions - - Writer.WriteKeyword("assert", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("throw", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - Writer.StartObject(assertions.Count, in _writeContext); + _writeContext.SetNodeProperty(nameof(throwStatement.Argument), static node => node.As().Argument); + VisitRootExpression(throwStatement.Argument, RootExpressionFlags(needsBrackets: false)); - VisitAuxiliaryNodeList(in assertions, separator: ","); + StatementNeedsSemicolon(); - Writer.EndObject(assertions.Count, in _writeContext); + return throwStatement; } - private void VisitExportOrImportSpecifierIdentifier(Expression identifierExpression) + protected internal override object? VisitTryStatement(TryStatement tryStatement) { - if (identifierExpression is Identifier identifier && identifier.Name == "default") + Writer.WriteKeyword("try", TokenFlags.SurroundingSpaceRecommended, in _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) { - Writer.WriteKeyword("default", in _writeContext); + _writeContext.ClearNodeProperty(); + Writer.WriteKeyword("catch", TokenFlags.SurroundingSpaceRecommended | StatementBodyFlagsToKeywordFlags(bodyFlags), in _writeContext); + + _writeContext.SetNodeProperty(nameof(tryStatement.Handler), static node => node.As().Handler); + VisitAuxiliaryNode(tryStatement.Handler); + bodyFlags = StatementBodyFlags(isRightMost: tryStatement.Finalizer is null); } - else + + if (tryStatement.Finalizer is not null) { - VisitRootExpression(identifierExpression, RootExpressionFlags(needsBrackets: false)); + _writeContext.ClearNodeProperty(); + Writer.WriteKeyword("finally", TokenFlags.SurroundingSpaceRecommended | StatementBodyFlagsToKeywordFlags(bodyFlags), in _writeContext); + + _writeContext.SetNodeProperty(nameof(tryStatement.Finalizer), static node => node.As().Finalizer); + VisitStatement(tryStatement.Finalizer, StatementBodyFlags(isRightMost: true)); } + + return tryStatement; } - private void VisitPropertyKey(Expression key, bool computed, TokenFlags leadingBracketFlags = TokenFlags.None, TokenFlags trailingBracketFlags = TokenFlags.None) + protected internal override object? VisitUnaryExpression(UnaryExpression unaryExpression) { - if (computed) - { - Writer.WritePunctuator("[", TokenFlags.Leading | leadingBracketFlags, in _writeContext); - VisitRootExpression(key, RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(key))); - Writer.WritePunctuator("]", TokenFlags.Trailing | trailingBracketFlags, in _writeContext); - } - else if (key.Type == Nodes.Identifier) + var argumentNeedsBrackets = UnaryOperandNeedsBrackets(unaryExpression, unaryExpression.Argument); + var op = UnaryExpression.GetUnaryOperatorToken(unaryExpression.Operator); + + if (unaryExpression.Prefix) { - VisitAuxiliaryNode(key); + _writeContext.SetNodeProperty(nameof(unaryExpression.Operator), static node => node.As().Operator); + if (char.IsLetter(op[0])) + { + Writer.WriteKeyword(op, TokenFlags.TrailingSpaceRecommended, in _writeContext); + } + else + { + Writer.WritePunctuator(op, TokenFlags.Leading, in _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 { - VisitRootExpression(key, RootExpressionFlags(needsBrackets: false)); - } - } + _writeContext.SetNodeProperty(nameof(unaryExpression.Argument), static node => node.As().Argument); + VisitSubExpression(unaryExpression.Argument, SubExpressionFlags(argumentNeedsBrackets, isLeftMost: true)); - 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; + _writeContext.SetNodeProperty(nameof(unaryExpression.Operator), static node => node.As().Operator); + Writer.WritePunctuator(op, TokenFlags.Trailing, in _writeContext); } - return false; + return unaryExpression; } - protected virtual bool ExpressionIsAmbiguousAsArrowFunctionBody(Expression expression) + protected internal override object? VisitVariableDeclaration(VariableDeclaration variableDeclaration) { - switch (expression.Type) - { - case Nodes.ObjectExpression: - case Nodes.AssignmentExpression when expression.As() is { Left.Type: Nodes.ObjectPattern }: - return true; - } + _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), in _writeContext); - return false; - } + _writeContext.SetNodeProperty(nameof(variableDeclaration.Declarations), static node => ref node.As().Declarations); - protected virtual bool ExpressionIsAmbiguousAsNewCallee(Expression expression) - { - switch (expression.Type) + if (!_currentStatementFlags.HasFlagFast(StatementFlags.NestedVariableDeclaration)) { - case Nodes.CallExpression: - return true; - } - - return false; - } - - protected virtual bool LeftHandSideExpressionIsParenthesized(Expression expression) - { - // https://tc39.es/ecma262/#sec-left-hand-side-expressions + VisitAuxiliaryNodeList(in variableDeclaration.Declarations, separator: ","); - switch (expression.Type) + StatementNeedsSemicolon(); + } + else if (ParentNode is not { Type: Nodes.ForInStatement }) { - 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; + VisitAuxiliaryNodeList(in variableDeclaration.Declarations, separator: ","); + } + else + { + VisitAuxiliaryNodeList(in variableDeclaration.Declarations, separator: ",", static delegate { return s_forInLoopDeclarationFlag; }); } - return false; - } - - protected virtual bool ExpressionNeedsBracketsInList(Expression expression) - { - return expression.Type is - Nodes.SequenceExpression; + return variableDeclaration; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - 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) + protected internal override object? VisitVariableDeclarator(VariableDeclarator variableDeclarator) { - var operationPrecedence = GetOperatorPrecedence(operation, out var associativity); - var leftOperandPrecedence = GetOperatorPrecedence(leftOperand, out _); - var rightOperandPrecedence = GetOperatorPrecedence(rightOperand, out _); - - var result = BinaryOperationFlags.None; + _writeContext.SetNodeProperty(nameof(variableDeclarator.Id), static node => node.As().Id); + VisitAuxiliaryNode(variableDeclarator.Id); - if (operationPrecedence > leftOperandPrecedence || operationPrecedence == leftOperandPrecedence && associativity > 0) // right-to-left associativity + if (variableDeclarator.Init is not null) { - result |= BinaryOperationFlags.LeftOperandNeedsBrackets; - } + _writeContext.ClearNodeProperty(); + Writer.WritePunctuator("=", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); - if (operationPrecedence > rightOperandPrecedence || operationPrecedence == rightOperandPrecedence && associativity < 0) // left-to-right associativity - { - result |= BinaryOperationFlags.RightOperandNeedsBrackets; + _writeContext.SetNodeProperty(nameof(variableDeclarator.Init), static node => node.As().Init); + + if (_currentAuxiliaryNodeContext != s_forInLoopDeclarationFlag) + { + VisitRootExpression(variableDeclarator.Init, RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(variableDeclarator.Init))); + } + else + { + VisitRootExpression(variableDeclarator.Init, ExpressionFlags.InOperatorIsAmbiguousInDeclaration | RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(variableDeclarator.Init))); + } } - return result; + return variableDeclarator; } - #endregion + protected internal override object? VisitWhileStatement(WhileStatement whileStatement) + { + Writer.WriteKeyword("while", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + + _writeContext.SetNodeProperty(nameof(whileStatement.Test), static node => node.As().Test); + VisitRootExpression(whileStatement.Test, ExpressionFlags.SpaceAroundBracketsRecommended | RootExpressionFlags(needsBrackets: true)); - #region Auxiliary nodes + _writeContext.SetNodeProperty(nameof(whileStatement.Body), static node => node.As().Body); + VisitStatement(whileStatement.Body, StatementBodyFlags(isRightMost: true)); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void VisitAuxiliaryNode(Node node) - { - VisitAuxiliaryNode(node, static delegate { return null; }); + return whileStatement; } - protected void VisitAuxiliaryNode(Node node, Func getNodeContext) + protected internal override object? VisitWithStatement(WithStatement withStatement) { - var originalAuxiliaryNodeContext = _currentAuxiliaryNodeContext; - _currentAuxiliaryNodeContext = getNodeContext(this, node); + Writer.WriteKeyword("with", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - Writer.StartAuxiliaryNode(_currentAuxiliaryNodeContext, in _writeContext); - Visit(node); - Writer.EndAuxiliaryNode(_currentAuxiliaryNodeContext, in _writeContext); + _writeContext.SetNodeProperty(nameof(withStatement.Object), static node => node.As().Object); + VisitRootExpression(withStatement.Object, ExpressionFlags.SpaceAroundBracketsRecommended | RootExpressionFlags(needsBrackets: true)); - _currentAuxiliaryNodeContext = originalAuxiliaryNodeContext; - } + _writeContext.SetNodeProperty(nameof(withStatement.Body), static node => node.As().Body); + VisitStatement(withStatement.Body, StatementBodyFlags(isRightMost: true)); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected void VisitAuxiliaryNodeList(in NodeList nodeList, string separator) - where TNode : Node - { - VisitAuxiliaryNodeList(in nodeList, separator, static delegate { return null; }); + return withStatement; } - protected void VisitAuxiliaryNodeList(in NodeList nodeList, string separator, Func getNodeContext) - where TNode : Node + protected internal override object? VisitYieldExpression(YieldExpression yieldExpression) { - Writer.StartAuxiliaryNodeList(nodeList.Count, in _writeContext); + Writer.WriteKeyword("yield", (!yieldExpression.Delegate && yieldExpression.Argument is not null).ToFlag(TokenFlags.TrailingSpaceRecommended), in _writeContext); - for (var i = 0; i < nodeList.Count; i++) + if (yieldExpression.Delegate) { - VisitAuxiliaryNodeListItem(nodeList[i], i, nodeList.Count, separator, getNodeContext); + _writeContext.SetNodeProperty(nameof(yieldExpression.Delegate), static node => node.As().Delegate); + Writer.WritePunctuator("*", (yieldExpression.Argument is not null).ToFlag(TokenFlags.TrailingSpaceRecommended), in _writeContext); } - Writer.EndAuxiliaryNodeList(nodeList.Count, in _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); + if (yieldExpression.Argument is not null) + { + var argumentNeedsBrackets = UnaryOperandNeedsBrackets(yieldExpression, yieldExpression.Argument); - Writer.StartAuxiliaryNodeListItem(index, count, separator, _currentAuxiliaryNodeContext, in _writeContext); - Visit(node); - Writer.EndAuxiliaryNodeListItem(index, count, separator, _currentAuxiliaryNodeContext, in _writeContext); + _writeContext.SetNodeProperty(nameof(yieldExpression.Argument), static node => node.As().Argument); + VisitSubExpression(yieldExpression.Argument, SubExpressionFlags(argumentNeedsBrackets, isLeftMost: false)); + } - _currentAuxiliaryNodeContext = originalAuxiliaryNodeContext; + return yieldExpression; } - - #endregion } From 0d073ee4ad11d8887eb66a7e554b32f6efa97d98 Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Tue, 19 Jul 2022 11:13:38 +0200 Subject: [PATCH 18/23] remove regions --- .../Utils/AstToJavascriptConverter.Helpers.cs | 12 ----- src/Esprima/Utils/JavascriptTextWriter.cs | 44 ------------------- src/Esprima/Utils/KnRJavascriptTextWriter.cs | 19 -------- 3 files changed, 75 deletions(-) diff --git a/src/Esprima/Utils/AstToJavascriptConverter.Helpers.cs b/src/Esprima/Utils/AstToJavascriptConverter.Helpers.cs index 7276f361..aeed6951 100644 --- a/src/Esprima/Utils/AstToJavascriptConverter.Helpers.cs +++ b/src/Esprima/Utils/AstToJavascriptConverter.Helpers.cs @@ -7,8 +7,6 @@ namespace Esprima.Utils; partial class AstToJavascriptConverter { - #region Statements - [MethodImpl(MethodImplOptions.AggressiveInlining)] private protected static StatementFlags StatementBodyFlags(bool isRightMost) { @@ -93,10 +91,6 @@ protected void VisitStatementListItem(Statement statement, int index, int count, [MethodImpl(MethodImplOptions.AggressiveInlining)] protected void StatementNeedsSemicolon() => _currentStatementFlags |= StatementFlags.NeedsSemicolon; - #endregion - - #region Expressions - [MethodImpl(MethodImplOptions.AggressiveInlining)] private protected static ExpressionFlags RootExpressionFlags(bool needsBrackets) { @@ -366,10 +360,6 @@ protected BinaryOperationFlags BinaryOperandsNeedBrackets(Expression operation, return result; } - #endregion - - #region Auxiliary nodes - [MethodImpl(MethodImplOptions.AggressiveInlining)] protected void VisitAuxiliaryNode(Node node) { @@ -420,6 +410,4 @@ protected void VisitAuxiliaryNodeListItem(TNode node, int index, int coun _currentAuxiliaryNodeContext = originalAuxiliaryNodeContext; } - - #endregion } diff --git a/src/Esprima/Utils/JavascriptTextWriter.cs b/src/Esprima/Utils/JavascriptTextWriter.cs index 9ae4a4c2..dd1dea71 100644 --- a/src/Esprima/Utils/JavascriptTextWriter.cs +++ b/src/Esprima/Utils/JavascriptTextWriter.cs @@ -38,8 +38,6 @@ public JavascriptTextWriter(TextWriter writer, Options options) protected TokenFlags LastTokenFlags { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; [MethodImpl(MethodImplOptions.AggressiveInlining)] private set; } protected bool WhiteSpaceWrittenSinceLastToken { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; [MethodImpl(MethodImplOptions.AggressiveInlining)] private set; } - #region White-space - [MethodImpl(MethodImplOptions.AggressiveInlining)] protected void WriteSpace() { @@ -58,8 +56,6 @@ protected void WriteWhiteSpace(string value) WhiteSpaceWrittenSinceLastToken = true; } - #endregion - [MethodImpl(MethodImplOptions.AggressiveInlining)] protected void ForceRecommendedSpace() { @@ -68,8 +64,6 @@ protected void ForceRecommendedSpace() public virtual void WriteEpsilon(TokenFlags flags, in WriteContext context) { } - #region Identifiers - protected virtual void StartIdentifier(string value, TokenFlags flags, in WriteContext context) { switch (LastTokenType) @@ -112,10 +106,6 @@ public void WriteIdentifier(string value, in WriteContext context) protected virtual void EndIdentifier(string value, TokenFlags flags, in WriteContext context) { } - #endregion - - #region Keywords - protected virtual void StartKeyword(string value, TokenFlags flags, in WriteContext context) { switch (LastTokenType) @@ -158,10 +148,6 @@ public void WriteKeyword(string value, in WriteContext context) protected virtual void EndKeyword(string value, TokenFlags flags, in WriteContext context) { } - #endregion - - #region Literals - protected virtual void StartLiteral(string value, TokenType type, TokenFlags flags, in WriteContext context) { switch (LastTokenType) @@ -207,10 +193,6 @@ public void WriteLiteral(string value, TokenType tokenType, in WriteContext cont protected virtual void EndLiteral(string value, TokenType type, TokenFlags flags, in WriteContext context) { } - #endregion - - #region Punctuators - protected virtual void StartPunctuator(string value, TokenFlags flags, in WriteContext context) { } public void WritePunctuator(string value, TokenFlags flags, in WriteContext context) @@ -232,10 +214,6 @@ public void WritePunctuator(string value, in WriteContext context) protected virtual void EndPunctuator(string value, TokenFlags flags, in WriteContext context) { } - #endregion - - #region Arrays - public virtual void StartArray(int elementCount, in WriteContext context) { WritePunctuator("[", TokenFlags.Leading, in context); @@ -246,10 +224,6 @@ public virtual void EndArray(int elementCount, in WriteContext context) WritePunctuator("]", TokenFlags.Trailing, in context); } - #endregion - - #region Objects - public virtual void StartObject(int propertyCount, in WriteContext context) { WritePunctuator("{", TokenFlags.Leading | TokenFlags.TrailingSpaceRecommended, in context); @@ -260,10 +234,6 @@ public virtual void EndObject(int propertyCount, in WriteContext context) WritePunctuator("}", TokenFlags.Trailing | TokenFlags.LeadingSpaceRecommended, in context); } - #endregion - - #region Blocks - public virtual void StartBlock(int statementCount, in WriteContext context) { WritePunctuator("{", TokenFlags.Leading | TokenFlags.SurroundingSpaceRecommended, in context); @@ -274,10 +244,6 @@ public virtual void EndBlock(int statementCount, in WriteContext context) WritePunctuator("}", TokenFlags.Trailing | TokenFlags.LeadingSpaceRecommended, in context); } - #endregion - - #region Statements - public virtual void StartStatement(StatementFlags flags, in WriteContext context) { } public virtual void EndStatement(StatementFlags flags, in WriteContext context) @@ -304,10 +270,6 @@ public virtual void EndStatementListItem(int index, int count, StatementFlags fl public virtual void EndStatementList(int count, in WriteContext context) { } - #endregion - - #region Expressions - public virtual void StartExpression(ExpressionFlags flags, in WriteContext context) { if (flags.HasFlagFast(ExpressionFlags.NeedsBrackets)) @@ -349,10 +311,6 @@ public virtual void EndExpressionListItem(int index, int count, ExpressionFlags public virtual void EndExpressionList(int count, in WriteContext context) { } - #endregion - - #region Auxiliary nodes - public virtual void StartAuxiliaryNode(object? nodeContext, in WriteContext context) { } public virtual void EndAuxiliaryNode(object? nodeContext, in WriteContext context) { } @@ -370,6 +328,4 @@ public virtual void EndAuxiliaryNodeListItem(int index, int count, string sep } public virtual void EndAuxiliaryNodeList(int count, in WriteContext context) where T : Node? { } - - #endregion } diff --git a/src/Esprima/Utils/KnRJavascriptTextWriter.cs b/src/Esprima/Utils/KnRJavascriptTextWriter.cs index b21dc51f..f1ed72b6 100644 --- a/src/Esprima/Utils/KnRJavascriptTextWriter.cs +++ b/src/Esprima/Utils/KnRJavascriptTextWriter.cs @@ -183,8 +183,6 @@ protected override void StartPunctuator(string value, TokenFlags flags, in Write } } - #region Arrays - public override void StartArray(int elementCount, in WriteContext context) { base.StartArray(elementCount, context); @@ -207,10 +205,6 @@ public override void EndArray(int elementCount, in WriteContext context) base.EndArray(elementCount, context); } - #endregion - - #region Objects - public override void StartObject(int propertyCount, in WriteContext context) { base.StartObject(propertyCount, context); @@ -232,9 +226,6 @@ public override void EndObject(int propertyCount, in WriteContext context) base.EndObject(propertyCount, context); } - #endregion - - #region Blocks public override void StartBlock(int statementCount, in WriteContext context) { @@ -258,10 +249,6 @@ public override void EndBlock(int statementCount, in WriteContext context) base.EndBlock(statementCount, context); } - #endregion - - #region Statements - public override void StartStatement(StatementFlags flags, in WriteContext context) { if (flags.HasFlagFast(StatementFlags.IsStatementBody)) @@ -426,10 +413,6 @@ protected virtual bool ShouldTerminateStatementAnyway(Statement statement, State }; } - #endregion - - #region Expressions - public override void StartExpressionListItem(int index, int count, ExpressionFlags flags, in WriteContext context) { if (context.Node.Type == Nodes.ArrayExpression && count >= MultiLineArrayLiteralThreshold) @@ -450,8 +433,6 @@ public override void EndExpressionListItem(int index, int count, ExpressionFlags } } - #endregion - public override void StartAuxiliaryNodeListItem(int index, int count, string separator, object? nodeContext, in WriteContext context) { if (typeof(T) == typeof(SwitchCase) || From 05667a31608021e6a569da4493d045eb654073f9 Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Tue, 19 Jul 2022 13:13:58 +0200 Subject: [PATCH 19/23] revise throw usage in inlined methods --- src/Esprima/EsprimaExceptionHelper.cs | 3 +++ src/Esprima/Utils/AstToJavascriptConverter.Helpers.cs | 1 - .../Utils/JavascriptTextWriter.WriteContext.cs | 11 ++++++----- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Esprima/EsprimaExceptionHelper.cs b/src/Esprima/EsprimaExceptionHelper.cs index fe917682..c0abe03d 100644 --- a/src/Esprima/EsprimaExceptionHelper.cs +++ b/src/Esprima/EsprimaExceptionHelper.cs @@ -2,6 +2,9 @@ namespace Esprima { + /// + /// JIT cannot inline methods that have in them. These helper methods allow us to work around this. + /// internal static class EsprimaExceptionHelper { [DoesNotReturn] diff --git a/src/Esprima/Utils/AstToJavascriptConverter.Helpers.cs b/src/Esprima/Utils/AstToJavascriptConverter.Helpers.cs index aeed6951..00aac0f1 100644 --- a/src/Esprima/Utils/AstToJavascriptConverter.Helpers.cs +++ b/src/Esprima/Utils/AstToJavascriptConverter.Helpers.cs @@ -330,7 +330,6 @@ protected virtual bool ExpressionNeedsBracketsInList(Expression expression) Nodes.SequenceExpression; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] protected virtual int GetOperatorPrecedence(Expression expression, out int associativity) => expression.GetOperatorPrecedence(out associativity) is >= 0 and var result ? result diff --git a/src/Esprima/Utils/JavascriptTextWriter.WriteContext.cs b/src/Esprima/Utils/JavascriptTextWriter.WriteContext.cs index 4f47dfe5..74dcc861 100644 --- a/src/Esprima/Utils/JavascriptTextWriter.WriteContext.cs +++ b/src/Esprima/Utils/JavascriptTextWriter.WriteContext.cs @@ -1,5 +1,6 @@ using System.Runtime.CompilerServices; using Esprima.Ast; +using static Esprima.EsprimaExceptionHelper; namespace Esprima.Utils; @@ -9,7 +10,7 @@ public struct WriteContext { [MethodImpl(MethodImplOptions.AggressiveInlining)] public WriteContext From(Node? parentNode, Node node) => - new WriteContext(parentNode, node ?? throw new ArgumentNullException(nameof(node))); + new WriteContext(parentNode, node ?? ThrowArgumentNullException(nameof(node))); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal WriteContext(Node? parentNode, Node node) @@ -30,7 +31,7 @@ internal WriteContext(Node? parentNode, Node node) private Delegate NodePropertyAccessor { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _nodePropertyValueAccessor ?? throw new InvalidOperationException("The context has no associated node property."); + get => _nodePropertyValueAccessor ?? ThrowInvalidOperationException("The context has no associated node property."); } public bool NodePropertyHasListValue @@ -45,7 +46,7 @@ public Type GetNodePropertyListItemType() var type = NodePropertyAccessor.GetType(); return type.IsGenericType ? type.GetGenericArguments()[0] - : throw new InvalidOperationException("The context has an associated node property but its value is not a node list."); + : ThrowInvalidOperationException("The context has an associated node property but its value is not a node list."); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -72,7 +73,7 @@ internal void SetNodeProperty(string name, NodePropertyValueAccessor valueAccess [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ChangeNodeProperty(string name, NodePropertyValueAccessor valueAccessor) => - SetNodeProperty(name ?? throw new ArgumentNullException(nameof(name)), valueAccessor ?? throw new ArgumentNullException(nameof(valueAccessor))); + SetNodeProperty(name ?? ThrowArgumentNullException(nameof(name)), valueAccessor ?? ThrowArgumentNullException(nameof(valueAccessor))); [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void SetNodeProperty(string name, NodePropertyListValueAccessor listValueAccessor) where T : Node? @@ -83,6 +84,6 @@ internal void SetNodeProperty(string name, NodePropertyListValueAccessor l [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ChangeNodeProperty(string name, NodePropertyListValueAccessor listValueAccessor) where T : Node? => - SetNodeProperty(name ?? throw new ArgumentNullException(nameof(name)), listValueAccessor ?? throw new ArgumentNullException(nameof(listValueAccessor))); + SetNodeProperty(name ?? ThrowArgumentNullException(nameof(name)), listValueAccessor ?? ThrowArgumentNullException>(nameof(listValueAccessor))); } } From 5912c0b6d69f66fed1166b5794a3fafd4d35fd36 Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Tue, 19 Jul 2022 14:35:42 +0200 Subject: [PATCH 20/23] fix 'in' operator bracketing bug in for loop variable declarations --- src/Esprima/Utils/AstToJavascriptConverter.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Esprima/Utils/AstToJavascriptConverter.cs b/src/Esprima/Utils/AstToJavascriptConverter.cs index 66f761d8..9dcb1098 100644 --- a/src/Esprima/Utils/AstToJavascriptConverter.cs +++ b/src/Esprima/Utils/AstToJavascriptConverter.cs @@ -17,7 +17,7 @@ public partial class AstToJavascriptConverter : AstVisitor public delegate AstToJavascriptConverter Factory(JavascriptTextWriter writer, AstToJavascript.Options options); private static readonly object s_lastSwitchCaseFlag = new(); - private static readonly object s_forInLoopDeclarationFlag = new(); + private static readonly object s_forLoopInitDeclarationFlag = new(); private WriteContext _writeContext; private StatementFlags _currentStatementFlags; @@ -1579,13 +1579,13 @@ unaryExpression.Argument is UnaryExpression argumentUnaryExpression && StatementNeedsSemicolon(); } - else if (ParentNode is not { Type: Nodes.ForInStatement }) + 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_forInLoopDeclarationFlag; }); + VisitAuxiliaryNodeList(in variableDeclaration.Declarations, separator: ",", static delegate { return s_forLoopInitDeclarationFlag; }); } return variableDeclaration; @@ -1603,7 +1603,7 @@ unaryExpression.Argument is UnaryExpression argumentUnaryExpression && _writeContext.SetNodeProperty(nameof(variableDeclarator.Init), static node => node.As().Init); - if (_currentAuxiliaryNodeContext != s_forInLoopDeclarationFlag) + if (_currentAuxiliaryNodeContext != s_forLoopInitDeclarationFlag) { VisitRootExpression(variableDeclarator.Init, RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(variableDeclarator.Init))); } From 5e573c9a73ced2d4bf57e74d0ea0bc928db9d3fd Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Tue, 19 Jul 2022 21:40:25 +0200 Subject: [PATCH 21/23] fix indentation bug in KnRJavascriptTextWriter + add Data field to WriteContext --- .../Utils/AstToJavascriptConverter.Helpers.cs | 48 +-- src/Esprima/Utils/AstToJavascriptConverter.cs | 332 +++++++++--------- .../JavascriptTextWriter.WriteContext.cs | 3 + src/Esprima/Utils/JavascriptTextWriter.cs | 134 +++---- src/Esprima/Utils/KnRJavascriptTextWriter.cs | 137 ++++---- test/Esprima.Tests/AstToJavascriptTests.cs | 14 +- 6 files changed, 335 insertions(+), 333 deletions(-) diff --git a/src/Esprima/Utils/AstToJavascriptConverter.Helpers.cs b/src/Esprima/Utils/AstToJavascriptConverter.Helpers.cs index 00aac0f1..cee8d1bf 100644 --- a/src/Esprima/Utils/AstToJavascriptConverter.Helpers.cs +++ b/src/Esprima/Utils/AstToJavascriptConverter.Helpers.cs @@ -50,9 +50,9 @@ protected void VisitStatement(Statement statement, StatementFlags flags, Func statementList) protected void VisitStatementList(in NodeList statementList, Func getCombinedItemFlags) { - Writer.StartStatementList(statementList.Count, in _writeContext); + 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, in _writeContext); + Writer.EndStatementList(statementList.Count, ref _writeContext); } protected void VisitStatementListItem(Statement statement, int index, int count, Func getCombinedFlags) @@ -81,9 +81,9 @@ protected void VisitStatementListItem(Statement statement, int index, int count, var originalStatementFlags = _currentStatementFlags; _currentStatementFlags = getCombinedFlags(this, statement, index, count); - Writer.StartStatementListItem(index, count, (JavascriptTextWriter.StatementFlags) _currentStatementFlags, in _writeContext); + Writer.StartStatementListItem(index, count, (JavascriptTextWriter.StatementFlags) _currentStatementFlags, ref _writeContext); Visit(statement); - Writer.EndStatementListItem(index, count, (JavascriptTextWriter.StatementFlags) _currentStatementFlags, in _writeContext); + Writer.EndStatementListItem(index, count, (JavascriptTextWriter.StatementFlags) _currentStatementFlags, ref _writeContext); _currentStatementFlags = originalStatementFlags; } @@ -183,9 +183,9 @@ protected void VisitExpression(Expression expression, ExpressionFlags flags, Fun var originalExpressionFlags = _currentExpressionFlags; _currentExpressionFlags = getCombinedFlags(this, expression, flags); - Writer.StartExpression((JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, in _writeContext); + Writer.StartExpression((JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, ref _writeContext); Visit(expression); - Writer.EndExpression((JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, in _writeContext); + Writer.EndExpression((JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, ref _writeContext); _currentExpressionFlags = originalExpressionFlags; } @@ -199,14 +199,14 @@ protected void VisitSubExpressionList(in NodeList expressionList) protected void VisitExpressionList(in NodeList expressionList, Func getCombinedItemFlags) { - Writer.StartExpressionList(expressionList.Count, in _writeContext); + 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, in _writeContext); + Writer.EndExpressionList(expressionList.Count, ref _writeContext); } protected void VisitExpressionListItem(Expression expression, int index, int count, Func getCombinedFlags) @@ -214,9 +214,9 @@ protected void VisitExpressionListItem(Expression expression, int index, int cou var originalExpressionFlags = _currentExpressionFlags; _currentExpressionFlags = getCombinedFlags(this, expression, index, count); - Writer.StartExpressionListItem(index, count, (JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, in _writeContext); + Writer.StartExpressionListItem(index, count, (JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, ref _writeContext); Visit(expression); - Writer.EndExpressionListItem(index, count, (JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, in _writeContext); + Writer.EndExpressionListItem(index, count, (JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, ref _writeContext); _currentExpressionFlags = originalExpressionFlags; } @@ -225,20 +225,20 @@ private void VisitAssertions(in NodeList assertions) { // https://github.com/tc39/proposal-import-assertions - Writer.WriteKeyword("assert", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("assert", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); - Writer.StartObject(assertions.Count, in _writeContext); + Writer.StartObject(assertions.Count, ref _writeContext); VisitAuxiliaryNodeList(in assertions, separator: ","); - Writer.EndObject(assertions.Count, in _writeContext); + Writer.EndObject(assertions.Count, ref _writeContext); } private void VisitExportOrImportSpecifierIdentifier(Expression identifierExpression) { if (identifierExpression is Identifier identifier && identifier.Name == "default") { - Writer.WriteKeyword("default", in _writeContext); + Writer.WriteKeyword("default", ref _writeContext); } else { @@ -250,9 +250,9 @@ private void VisitPropertyKey(Expression key, bool computed, TokenFlags leadingB { if (computed) { - Writer.WritePunctuator("[", TokenFlags.Leading | leadingBracketFlags, in _writeContext); + Writer.WritePunctuator("[", TokenFlags.Leading | leadingBracketFlags, ref _writeContext); VisitRootExpression(key, RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(key))); - Writer.WritePunctuator("]", TokenFlags.Trailing | trailingBracketFlags, in _writeContext); + Writer.WritePunctuator("]", TokenFlags.Trailing | trailingBracketFlags, ref _writeContext); } else if (key.Type == Nodes.Identifier) { @@ -370,9 +370,9 @@ protected void VisitAuxiliaryNode(Node node, Func(in NodeList nodeList, string protected void VisitAuxiliaryNodeList(in NodeList nodeList, string separator, Func getNodeContext) where TNode : Node { - Writer.StartAuxiliaryNodeList(nodeList.Count, in _writeContext); + 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, in _writeContext); + Writer.EndAuxiliaryNodeList(nodeList.Count, ref _writeContext); } protected void VisitAuxiliaryNodeListItem(TNode node, int index, int count, string separator, Func getNodeContext) @@ -403,9 +403,9 @@ protected void VisitAuxiliaryNodeListItem(TNode node, int index, int coun var originalAuxiliaryNodeContext = _currentAuxiliaryNodeContext; _currentAuxiliaryNodeContext = getNodeContext(this, node, index, count); - Writer.StartAuxiliaryNodeListItem(index, count, separator, _currentAuxiliaryNodeContext, in _writeContext); + Writer.StartAuxiliaryNodeListItem(index, count, separator, _currentAuxiliaryNodeContext, ref _writeContext); Visit(node); - Writer.EndAuxiliaryNodeListItem(index, count, separator, _currentAuxiliaryNodeContext, in _writeContext); + Writer.EndAuxiliaryNodeListItem(index, count, separator, _currentAuxiliaryNodeContext, ref _writeContext); _currentAuxiliaryNodeContext = originalAuxiliaryNodeContext; } diff --git a/src/Esprima/Utils/AstToJavascriptConverter.cs b/src/Esprima/Utils/AstToJavascriptConverter.cs index 9dcb1098..4bf661bb 100644 --- a/src/Esprima/Utils/AstToJavascriptConverter.cs +++ b/src/Esprima/Utils/AstToJavascriptConverter.cs @@ -66,11 +66,11 @@ public void Convert(Node node) { _writeContext.SetNodeProperty(nameof(arrayExpression.Elements), static node => ref node.As().Elements); - Writer.StartArray(arrayExpression.Elements.Count, in _writeContext); + 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, in _writeContext); + Writer.StartExpressionList(arrayExpression.Elements.Count, ref _writeContext); for (var i = 0; i < arrayExpression.Elements.Count; i++) { @@ -86,16 +86,16 @@ public void Convert(Node node) var originalExpressionFlags = _currentExpressionFlags; _currentExpressionFlags = PropagateExpressionFlags(SubExpressionFlags(needsBrackets: false, isLeftMost: false)); - Writer.StartExpressionListItem(i, arrayExpression.Elements.Count, (JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, in _writeContext); - Writer.EndExpressionListItem(i, arrayExpression.Elements.Count, (JavascriptTextWriter.ExpressionFlags) _currentExpressionFlags, in _writeContext); + 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, in _writeContext); + Writer.EndExpressionList(arrayExpression.Elements.Count, ref _writeContext); - Writer.EndArray(arrayExpression.Elements.Count, in _writeContext); + Writer.EndArray(arrayExpression.Elements.Count, ref _writeContext); return arrayExpression; } @@ -104,11 +104,11 @@ public void Convert(Node node) { _writeContext.SetNodeProperty(nameof(arrayPattern.Elements), static node => ref node.As().Elements); - Writer.StartArray(arrayPattern.Elements.Count, in _writeContext); + 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, in _writeContext); + Writer.StartAuxiliaryNodeList(arrayPattern.Elements.Count, ref _writeContext); for (var i = 0; i < arrayPattern.Elements.Count; i++) { @@ -117,19 +117,19 @@ public void Convert(Node node) var originalAuxiliaryNodeContext = _currentAuxiliaryNodeContext; _currentAuxiliaryNodeContext = null; - Writer.StartAuxiliaryNodeListItem(i, arrayPattern.Elements.Count, separator: ",", _currentAuxiliaryNodeContext, in _writeContext); + 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, in _writeContext); + Writer.EndAuxiliaryNodeListItem(i, arrayPattern.Elements.Count, separator: ",", _currentAuxiliaryNodeContext, ref _writeContext); _currentAuxiliaryNodeContext = originalAuxiliaryNodeContext; } - Writer.EndAuxiliaryNodeList(arrayPattern.Elements.Count, in _writeContext); + Writer.EndAuxiliaryNodeList(arrayPattern.Elements.Count, ref _writeContext); - Writer.EndArray(arrayPattern.Elements.Count, in _writeContext); + Writer.EndArray(arrayPattern.Elements.Count, ref _writeContext); return arrayPattern; } @@ -139,7 +139,7 @@ public void Convert(Node node) if (arrowFunctionExpression.Async) { _writeContext.SetNodeProperty(nameof(arrowFunctionExpression.Async), static node => node.As().Async); - Writer.WriteKeyword("async", TokenFlags.TrailingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("async", TokenFlags.TrailingSpaceRecommended, ref _writeContext); } _writeContext.SetNodeProperty(nameof(arrowFunctionExpression.Params), static node => ref node.As().Params); @@ -150,13 +150,13 @@ public void Convert(Node node) } else { - Writer.WritePunctuator("(", TokenFlags.Leading, in _writeContext); + Writer.WritePunctuator("(", TokenFlags.Leading, ref _writeContext); VisitAuxiliaryNodeList(in arrowFunctionExpression.Params, separator: ","); - Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); + Writer.WritePunctuator(")", TokenFlags.Trailing, ref _writeContext); } _writeContext.ClearNodeProperty(); - Writer.WritePunctuator("=>", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WritePunctuator("=>", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(arrowFunctionExpression.Body), static node => node.As().Body); if (arrowFunctionExpression.Body is BlockStatement bodyBlockStatement) @@ -182,7 +182,7 @@ public void Convert(Node node) var op = AssignmentExpression.GetAssignmentOperatorToken(assignmentExpression.Operator); _writeContext.SetNodeProperty(nameof(assignmentExpression.Operator), static node => node.As().Operator); - Writer.WritePunctuator(op, TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); + 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 _); @@ -199,7 +199,7 @@ public void Convert(Node node) VisitAuxiliaryNode(assignmentPattern.Left); _writeContext.ClearNodeProperty(); - Writer.WritePunctuator("=", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WritePunctuator("=", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(assignmentPattern.Right), static node => node.As().Right); VisitAuxiliaryNode(assignmentPattern.Right); @@ -209,7 +209,7 @@ public void Convert(Node node) protected internal override object? VisitAwaitExpression(AwaitExpression awaitExpression) { - Writer.WriteKeyword("await", TokenFlags.TrailingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("await", TokenFlags.TrailingSpaceRecommended, ref _writeContext); var argumentNeedsBrackets = UnaryOperandNeedsBrackets(awaitExpression, awaitExpression.Argument); @@ -240,11 +240,11 @@ public void Convert(Node node) _writeContext.SetNodeProperty(nameof(binaryExpression.Operator), static node => node.As().Operator); if (char.IsLetter(op[0])) { - Writer.WriteKeyword(op, TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword(op, TokenFlags.SurroundingSpaceRecommended, ref _writeContext); } else { - Writer.WritePunctuator(op, TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WritePunctuator(op, TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, ref _writeContext); // Cases like 1 + (+x) must be disambiguated with brackets. if (!operationFlags.HasFlagFast(BinaryOperationFlags.RightOperandNeedsBrackets) && @@ -266,18 +266,18 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitBlockStatement(BlockStatement blockStatement) { _writeContext.SetNodeProperty(nameof(blockStatement.Body), static node => ref node.As().Body); - Writer.StartBlock(blockStatement.Body.Count, in _writeContext); + Writer.StartBlock(blockStatement.Body.Count, ref _writeContext); VisitStatementList(in blockStatement.Body); - Writer.EndBlock(blockStatement.Body.Count, in _writeContext); + Writer.EndBlock(blockStatement.Body.Count, ref _writeContext); return blockStatement; } protected internal override object? VisitBreakStatement(BreakStatement breakStatement) { - Writer.WriteKeyword("break", TokenFlags.LeadingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("break", TokenFlags.LeadingSpaceRecommended, ref _writeContext); if (breakStatement.Label is not null) { @@ -300,13 +300,13 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && if (callExpression.Optional) { _writeContext.ClearNodeProperty(); - Writer.WritePunctuator("?.", TokenFlags.InBetween, in _writeContext); + Writer.WritePunctuator("?.", TokenFlags.InBetween, ref _writeContext); } _writeContext.SetNodeProperty(nameof(callExpression.Arguments), static node => ref node.As().Arguments); - Writer.WritePunctuator("(", TokenFlags.Leading, in _writeContext); + Writer.WritePunctuator("(", TokenFlags.Leading, ref _writeContext); VisitSubExpressionList(in callExpression.Arguments); - Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); + Writer.WritePunctuator(")", TokenFlags.Trailing, ref _writeContext); return callExpression; } @@ -316,9 +316,9 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && if (catchClause.Param is not null) { _writeContext.SetNodeProperty(nameof(catchClause.Param), static node => node.As().Param); - Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, in _writeContext); + Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, ref _writeContext); VisitAuxiliaryNode(catchClause.Param); - Writer.WritePunctuator(")", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); + Writer.WritePunctuator(")", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext); } _writeContext.SetNodeProperty(nameof(catchClause.Body), static node => node.As().Body); @@ -338,11 +338,11 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitClassBody(ClassBody classBody) { _writeContext.SetNodeProperty(nameof(classBody.Body), static node => ref node.As().Body); - Writer.StartBlock(classBody.Body.Count, in _writeContext); + Writer.StartBlock(classBody.Body.Count, ref _writeContext); VisitAuxiliaryNodeList(in classBody.Body, separator: string.Empty); - Writer.EndBlock(classBody.Body.Count, in _writeContext); + Writer.EndBlock(classBody.Body.Count, ref _writeContext); return classBody; } @@ -357,7 +357,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && _writeContext.ClearNodeProperty(); } - Writer.WriteKeyword("class", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("class", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); if (classDeclaration.Id is not null) { @@ -368,7 +368,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && if (classDeclaration.SuperClass is not null) { _writeContext.ClearNodeProperty(); - Writer.WriteKeyword("extends", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("extends", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(classDeclaration.SuperClass), static node => node.As().SuperClass); VisitRootExpression(classDeclaration.SuperClass, LeftHandSideRootExpressionFlags(needsBrackets: false)); @@ -390,7 +390,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && _writeContext.ClearNodeProperty(); } - Writer.WriteKeyword("class", TokenFlags.TrailingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("class", TokenFlags.TrailingSpaceRecommended, ref _writeContext); if (classExpression.Id is not null) { @@ -401,7 +401,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && if (classExpression.SuperClass is not null) { _writeContext.ClearNodeProperty(); - Writer.WriteKeyword("extends", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("extends", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(classExpression.SuperClass), static node => node.As().SuperClass); VisitRootExpression(classExpression.SuperClass, LeftHandSideRootExpressionFlags(needsBrackets: false)); @@ -425,7 +425,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && operandNeedsBrackets = GetOperatorPrecedence(conditionalExpression, out _) > GetOperatorPrecedence(conditionalExpression.Consequent, out _); _writeContext.SetNodeProperty(nameof(conditionalExpression.Consequent), static node => node.As().Consequent); - Writer.WritePunctuator("?", TokenFlags.Leading | TokenFlags.SurroundingSpaceRecommended, in _writeContext); + 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. @@ -435,7 +435,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && operandNeedsBrackets = GetOperatorPrecedence(conditionalExpression, out _) > GetOperatorPrecedence(conditionalExpression.Alternate, out _); _writeContext.SetNodeProperty(nameof(conditionalExpression.Alternate), static node => node.As().Alternate); - Writer.WritePunctuator(":", TokenFlags.Leading | TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WritePunctuator(":", TokenFlags.Leading | TokenFlags.SurroundingSpaceRecommended, ref _writeContext); VisitSubExpression(conditionalExpression.Alternate, SubExpressionFlags(operandNeedsBrackets, isLeftMost: false)); @@ -444,7 +444,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitContinueStatement(ContinueStatement continueStatement) { - Writer.WriteKeyword("continue", TokenFlags.LeadingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("continue", TokenFlags.LeadingSpaceRecommended, ref _writeContext); if (continueStatement.Label is not null) { @@ -459,7 +459,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitDebuggerStatement(DebuggerStatement debuggerStatement) { - Writer.WriteKeyword("debugger", TokenFlags.LeadingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("debugger", TokenFlags.LeadingSpaceRecommended, ref _writeContext); StatementNeedsSemicolon(); @@ -470,26 +470,26 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && { // https://github.com/tc39/proposal-decorators - Writer.WritePunctuator("@", TokenFlags.Leading | (ParentNode is not Expression).ToFlag(TokenFlags.LeadingSpaceRecommended), in _writeContext); + 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, in _writeContext); + Writer.WriteEpsilon(TokenFlags.TrailingSpaceRecommended, ref _writeContext); return decorator; } protected internal override object? VisitDoWhileStatement(DoWhileStatement doWhileStatement) { - Writer.WriteKeyword("do", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + 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), in _writeContext); + 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)); @@ -499,26 +499,26 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitEmptyStatement(EmptyStatement emptyStatement) { - Writer.WritePunctuator(";", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WritePunctuator(";", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); return emptyStatement; } protected internal override object? VisitExportAllDeclaration(ExportAllDeclaration exportAllDeclaration) { - Writer.WriteKeyword("export", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - Writer.WritePunctuator("*", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + 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, in _writeContext); + Writer.WriteKeyword("as", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); VisitExportOrImportSpecifierIdentifier(exportAllDeclaration.Exported); } _writeContext.ClearNodeProperty(); - Writer.WriteKeyword("from", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("from", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(exportAllDeclaration.Source), static node => node.As().Source); VisitRootExpression(exportAllDeclaration.Source, RootExpressionFlags(needsBrackets: false)); @@ -536,8 +536,8 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitExportDefaultDeclaration(ExportDefaultDeclaration exportDefaultDeclaration) { - Writer.WriteKeyword("export", TokenFlags.SurroundingSpaceRecommended, in _writeContext); - Writer.WriteKeyword("default", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + 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) @@ -556,7 +556,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitExportNamedDeclaration(ExportNamedDeclaration exportNamedDeclaration) { - Writer.WriteKeyword("export", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("export", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); if (exportNamedDeclaration.Declaration is not null) { @@ -566,14 +566,14 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && else { _writeContext.SetNodeProperty(nameof(exportNamedDeclaration.Specifiers), static node => ref node.As().Specifiers); - Writer.WritePunctuator("{", TokenFlags.Leading | TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WritePunctuator("{", TokenFlags.Leading | TokenFlags.SurroundingSpaceRecommended, ref _writeContext); VisitAuxiliaryNodeList(in exportNamedDeclaration.Specifiers, separator: ","); - Writer.WritePunctuator("}", TokenFlags.Trailing | TokenFlags.LeadingSpaceRecommended, in _writeContext); + Writer.WritePunctuator("}", TokenFlags.Trailing | TokenFlags.LeadingSpaceRecommended, ref _writeContext); if (exportNamedDeclaration.Source is not null) { _writeContext.ClearNodeProperty(); - Writer.WriteKeyword("from", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("from", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(exportNamedDeclaration.Source), static node => node.As().Source); VisitRootExpression(exportNamedDeclaration.Source, RootExpressionFlags(needsBrackets: false)); @@ -599,7 +599,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && if (exportSpecifier.Local != exportSpecifier.Exported) { _writeContext.ClearNodeProperty(); - Writer.WriteKeyword("as", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("as", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(exportSpecifier.Exported), static node => node.As().Exported); VisitExportOrImportSpecifierIdentifier(exportSpecifier.Exported); @@ -611,7 +611,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitExpressionStatement(ExpressionStatement expressionStatement) { _writeContext.SetNodeProperty(nameof(expressionStatement.Expression), static node => node.As().Expression); - Writer.WriteEpsilon(TokenFlags.LeadingSpaceRecommended, in _writeContext); + Writer.WriteEpsilon(TokenFlags.LeadingSpaceRecommended, ref _writeContext); VisitRootExpression(expressionStatement.Expression, ExpressionFlags.IsInsideStatementExpression | RootExpressionFlags(needsBrackets: false)); StatementNeedsSemicolon(); @@ -621,9 +621,9 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitForInStatement(ForInStatement forInStatement) { - Writer.WriteKeyword("for", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("for", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); - Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, in _writeContext); + Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(forInStatement.Left), static node => node.As().Left); @@ -637,13 +637,13 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && } _writeContext.ClearNodeProperty(); - Writer.WriteKeyword("in", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); + 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, in _writeContext); + Writer.WritePunctuator(")", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(forInStatement.Body), static node => node.As().Body); VisitStatement(forInStatement.Body, StatementBodyFlags(isRightMost: true)); @@ -653,15 +653,15 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitForOfStatement(ForOfStatement forOfStatement) { - Writer.WriteKeyword("for", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + 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, in _writeContext); + Writer.WriteKeyword("await", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); } - Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, in _writeContext); + Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(forOfStatement.Left), static node => node.As().Left); @@ -675,13 +675,13 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && } _writeContext.ClearNodeProperty(); - Writer.WriteKeyword("of", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); + 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, in _writeContext); + Writer.WritePunctuator(")", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(forOfStatement.Body), static node => node.As().Body); VisitStatement(forOfStatement.Body, StatementBodyFlags(isRightMost: true)); @@ -691,9 +691,9 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitForStatement(ForStatement forStatement) { - Writer.WriteKeyword("for", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("for", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); - Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, in _writeContext); + Writer.WritePunctuator("(", TokenFlags.Leading | TokenFlags.LeadingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(forStatement.Init), static node => node.As().Init); @@ -709,7 +709,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && } } - Writer.WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); + Writer.WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(forStatement.Test), static node => node.As().Test); @@ -718,7 +718,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && VisitRootExpression(forStatement.Test, RootExpressionFlags(needsBrackets: false)); } - Writer.WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); + Writer.WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext); if (forStatement.Update is not null) { @@ -728,7 +728,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && } _writeContext.ClearNodeProperty(); - Writer.WritePunctuator(")", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); + Writer.WritePunctuator(")", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(forStatement.Body), static node => node.As().Body); VisitStatement(forStatement.Body, StatementBodyFlags(isRightMost: true)); @@ -741,17 +741,17 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && if (functionDeclaration.Async) { _writeContext.SetNodeProperty(nameof(functionDeclaration.Async), static node => node.As().Async); - Writer.WriteKeyword("async", TokenFlags.LeadingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("async", TokenFlags.LeadingSpaceRecommended, ref _writeContext); _writeContext.ClearNodeProperty(); } - Writer.WriteKeyword("function", TokenFlags.LeadingSpaceRecommended, in _writeContext); + 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), in _writeContext); + Writer.WritePunctuator("*", (functionDeclaration.Id is not null).ToFlag(TokenFlags.TrailingSpaceRecommended), ref _writeContext); } if (functionDeclaration.Id is not null) @@ -761,9 +761,9 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && } _writeContext.SetNodeProperty(nameof(functionDeclaration.Params), static node => ref node.As().Params); - Writer.WritePunctuator("(", TokenFlags.Leading, in _writeContext); + Writer.WritePunctuator("(", TokenFlags.Leading, ref _writeContext); VisitAuxiliaryNodeList(in functionDeclaration.Params, separator: ","); - Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); + Writer.WritePunctuator(")", TokenFlags.Trailing, ref _writeContext); _writeContext.SetNodeProperty(nameof(functionDeclaration.Body), static node => node.As().Body); VisitStatement(functionDeclaration.Body, StatementBodyFlags(isRightMost: true)); @@ -778,17 +778,17 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && if (functionExpression.Async) { _writeContext.SetNodeProperty(nameof(functionExpression.Async), static node => node.As().Async); - Writer.WriteKeyword("async", in _writeContext); + Writer.WriteKeyword("async", ref _writeContext); _writeContext.ClearNodeProperty(); } - Writer.WriteKeyword("function", in _writeContext); + 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), in _writeContext); + Writer.WritePunctuator("*", (functionExpression.Id is not null).ToFlag(TokenFlags.TrailingSpaceRecommended), ref _writeContext); } if (functionExpression.Id is not null) @@ -804,7 +804,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && if (functionExpression.Async) { _writeContext.SetNodeProperty(nameof(functionExpression.Async), static node => node.As().Async); - Writer.WriteKeyword("async", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("async", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); keyIsFirstToken = false; } @@ -812,7 +812,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && if (functionExpression.Generator) { _writeContext.SetNodeProperty(nameof(functionExpression.Generator), static node => node.As().Generator); - Writer.WritePunctuator("*", TokenFlags.LeadingSpaceRecommended, in _writeContext); + Writer.WritePunctuator("*", TokenFlags.LeadingSpaceRecommended, ref _writeContext); keyIsFirstToken = false; } @@ -823,21 +823,21 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && { if (keyIsFirstToken && !property.Computed) { - Writer.WriteEpsilon(TokenFlags.LeadingSpaceRecommended, in _writeContext); + Writer.WriteEpsilon(TokenFlags.LeadingSpaceRecommended, ref _writeContext); } VisitPropertyKey(property.Key, property.Computed, leadingBracketFlags: keyIsFirstToken.ToFlag(TokenFlags.LeadingSpaceRecommended)); } else { - Writer.WriteKeyword("constructor", TokenFlags.LeadingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("constructor", TokenFlags.LeadingSpaceRecommended, ref _writeContext); } } _writeContext.SetNodeProperty(nameof(functionExpression.Params), static node => ref node.As().Params); - Writer.WritePunctuator("(", TokenFlags.Leading, in _writeContext); + Writer.WritePunctuator("(", TokenFlags.Leading, ref _writeContext); VisitAuxiliaryNodeList(in functionExpression.Params, separator: ","); - Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); + Writer.WritePunctuator(")", TokenFlags.Trailing, ref _writeContext); _writeContext.SetNodeProperty(nameof(functionExpression.Body), static node => node.As().Body); VisitStatement(functionExpression.Body, StatementBodyFlags(isRightMost: true)); @@ -848,14 +848,14 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitIdentifier(Identifier identifier) { _writeContext.SetNodeProperty(nameof(identifier.Name), static node => node.As().Name); - Writer.WriteIdentifier(identifier.Name!, in _writeContext); + Writer.WriteIdentifier(identifier.Name!, ref _writeContext); return identifier; } protected internal override object? VisitIfStatement(IfStatement ifStatement) { - Writer.WriteKeyword("if", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + 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)); @@ -867,7 +867,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && if (ifStatement.Alternate is not null) { _writeContext.ClearNodeProperty(); - Writer.WriteKeyword("else", TokenFlags.SurroundingSpaceRecommended | StatementBodyFlagsToKeywordFlags(bodyFlags), in _writeContext); + 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)); @@ -878,14 +878,14 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitImport(Import import) { - Writer.WriteKeyword("import", in _writeContext); + Writer.WriteKeyword("import", ref _writeContext); - Writer.WritePunctuator("(", TokenFlags.Leading, in _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, in _writeContext); + Writer.StartExpressionList(paramCount, ref _writeContext); _writeContext.SetNodeProperty(nameof(Import.Source), static node => node.As().Source); VisitExpressionListItem(import.Source, 0, paramCount, static (@this, expression, _, _) => @@ -900,10 +900,10 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && s_getCombinedSubExpressionFlags(@this, expression, SubExpressionFlags(@this.ExpressionNeedsBracketsInList(expression), isLeftMost: false))); } - Writer.EndExpressionList(paramCount, in _writeContext); + Writer.EndExpressionList(paramCount, ref _writeContext); _writeContext.ClearNodeProperty(); - Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); + Writer.WritePunctuator(")", TokenFlags.Trailing, ref _writeContext); return import; } @@ -914,7 +914,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && _writeContext.SetNodeProperty(nameof(importAttribute.Key), static node => node.As().Key); VisitPropertyKey(importAttribute.Key, computed: false); - Writer.WritePunctuator(":", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); + Writer.WritePunctuator(":", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(importAttribute.Value), static node => node.As().Value); @@ -925,16 +925,16 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitImportDeclaration(ImportDeclaration importDeclaration) { - Writer.WriteKeyword("import", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + 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, in _writeContext); + Writer.StartAuxiliaryNodeList(importDeclaration.Specifiers.Count, ref _writeContext); if (importDeclaration.Specifiers.Count == 0) { - Writer.EndAuxiliaryNodeList(count: 0, in _writeContext); + Writer.EndAuxiliaryNodeList(count: 0, ref _writeContext); goto WriteSource; } @@ -962,20 +962,20 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && } } - Writer.WritePunctuator("{", TokenFlags.Leading | TokenFlags.TrailingSpaceRecommended, in _writeContext); + 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, in _writeContext); + Writer.WritePunctuator("}", TokenFlags.Trailing | TokenFlags.LeadingSpaceRecommended, ref _writeContext); EndSpecifiers: - Writer.EndAuxiliaryNodeList(importDeclaration.Specifiers.Count, in _writeContext); + Writer.EndAuxiliaryNodeList(importDeclaration.Specifiers.Count, ref _writeContext); _writeContext.ClearNodeProperty(); - Writer.WriteKeyword("from", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("from", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); WriteSource: _writeContext.SetNodeProperty(nameof(importDeclaration.Source), static node => node.As().Source); @@ -1002,9 +1002,9 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitImportNamespaceSpecifier(ImportNamespaceSpecifier importNamespaceSpecifier) { - Writer.WritePunctuator("*", TokenFlags.TrailingSpaceRecommended, in _writeContext); + Writer.WritePunctuator("*", TokenFlags.TrailingSpaceRecommended, ref _writeContext); - Writer.WriteKeyword("as", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("as", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(importNamespaceSpecifier.Local), static node => node.As().Local); VisitAuxiliaryNode(importNamespaceSpecifier.Local); @@ -1020,7 +1020,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && VisitExportOrImportSpecifierIdentifier(importSpecifier.Imported); _writeContext.ClearNodeProperty(); - Writer.WriteKeyword("as", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("as", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); } _writeContext.SetNodeProperty(nameof(importSpecifier.Local), static node => node.As().Local); @@ -1032,10 +1032,10 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitLabeledStatement(LabeledStatement labeledStatement) { _writeContext.SetNodeProperty(nameof(labeledStatement.Label), static node => node.As().Label); - Writer.WriteEpsilon(TokenFlags.LeadingSpaceRecommended, in _writeContext); + Writer.WriteEpsilon(TokenFlags.LeadingSpaceRecommended, ref _writeContext); VisitAuxiliaryNode(labeledStatement.Label); - Writer.WritePunctuator(":", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); + Writer.WritePunctuator(":", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(labeledStatement.Body), static node => node.As().Body); VisitStatement(labeledStatement.Body, StatementFlags.IsRightMost); @@ -1046,7 +1046,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitLiteral(Literal literal) { _writeContext.SetNodeProperty(nameof(literal.Raw), static node => node.As().Raw); - Writer.WriteLiteral(literal.Raw, literal.TokenType, in _writeContext); + Writer.WriteLiteral(literal.Raw, literal.TokenType, ref _writeContext); return literal; } @@ -1072,18 +1072,18 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && if (memberExpression.Optional) { _writeContext.ClearNodeProperty(); - Writer.WritePunctuator("?.", TokenFlags.InBetween, in _writeContext); + Writer.WritePunctuator("?.", TokenFlags.InBetween, ref _writeContext); } _writeContext.SetNodeProperty(nameof(memberExpression.Property), static node => node.As().Property); - Writer.WritePunctuator("[", TokenFlags.Leading, in _writeContext); + Writer.WritePunctuator("[", TokenFlags.Leading, ref _writeContext); VisitSubExpression(memberExpression.Property, SubExpressionFlags(needsBrackets: false, isLeftMost: false)); - Writer.WritePunctuator("]", TokenFlags.Trailing, in _writeContext); + Writer.WritePunctuator("]", TokenFlags.Trailing, ref _writeContext); } else { _writeContext.ClearNodeProperty(); - Writer.WritePunctuator(memberExpression.Optional ? "?." : ".", TokenFlags.InBetween, in _writeContext); + 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)); @@ -1095,10 +1095,10 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitMetaProperty(MetaProperty metaProperty) { _writeContext.SetNodeProperty(nameof(metaProperty.Meta), static node => node.As().Meta); - Writer.WriteKeyword(metaProperty.Meta.Name!, in _writeContext); + Writer.WriteKeyword(metaProperty.Meta.Name!, ref _writeContext); _writeContext.ClearNodeProperty(); - Writer.WritePunctuator(".", TokenFlags.InBetween, in _writeContext); + Writer.WritePunctuator(".", TokenFlags.InBetween, ref _writeContext); _writeContext.SetNodeProperty(nameof(metaProperty.Property), static node => node.As().Property); VisitSubExpression(metaProperty.Property, SubExpressionFlags(needsBrackets: false, isLeftMost: false)); @@ -1119,18 +1119,18 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && if (methodDefinition.Static) { _writeContext.SetNodeProperty(nameof(methodDefinition.Static), static node => node.As().Static); - Writer.WriteKeyword("static", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + 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, in _writeContext); + 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, in _writeContext); + Writer.WriteKeyword("set", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); break; } @@ -1142,7 +1142,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitNewExpression(NewExpression newExpression) { - Writer.WriteKeyword("new", TokenFlags.TrailingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("new", TokenFlags.TrailingSpaceRecommended, ref _writeContext); var calleeNeedsBrackets = UnaryOperandNeedsBrackets(newExpression, newExpression.Callee); @@ -1153,9 +1153,9 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && if (newExpression.Arguments.Count > 0) { _writeContext.SetNodeProperty(nameof(newExpression.Arguments), static node => ref node.As().Arguments); - Writer.WritePunctuator("(", TokenFlags.Leading, in _writeContext); + Writer.WritePunctuator("(", TokenFlags.Leading, ref _writeContext); VisitSubExpressionList(in newExpression.Arguments); - Writer.WritePunctuator(")", TokenFlags.Trailing, in _writeContext); + Writer.WritePunctuator(")", TokenFlags.Trailing, ref _writeContext); } return newExpression; @@ -1165,11 +1165,11 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && { _writeContext.SetNodeProperty(nameof(objectExpression.Properties), static node => ref node.As().Properties); - Writer.StartObject(objectExpression.Properties.Count, in _writeContext); + 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, in _writeContext); + Writer.StartAuxiliaryNodeList(objectExpression.Properties.Count, ref _writeContext); for (var i = 0; i < objectExpression.Properties.Count; i++) { @@ -1179,9 +1179,9 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && var originalAuxiliaryNodeContext = _currentAuxiliaryNodeContext; _currentAuxiliaryNodeContext = null; - Writer.StartAuxiliaryNodeListItem(i, objectExpression.Properties.Count, separator: ",", _currentAuxiliaryNodeContext, in _writeContext); + Writer.StartAuxiliaryNodeListItem(i, objectExpression.Properties.Count, separator: ",", _currentAuxiliaryNodeContext, ref _writeContext); VisitRootExpression(spreadElement, RootExpressionFlags(needsBrackets: ExpressionNeedsBracketsInList(spreadElement))); - Writer.EndAuxiliaryNodeListItem(i, objectExpression.Properties.Count, separator: ",", _currentAuxiliaryNodeContext, in _writeContext); + Writer.EndAuxiliaryNodeListItem(i, objectExpression.Properties.Count, separator: ",", _currentAuxiliaryNodeContext, ref _writeContext); _currentAuxiliaryNodeContext = originalAuxiliaryNodeContext; } @@ -1191,9 +1191,9 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && } } - Writer.EndAuxiliaryNodeList(objectExpression.Properties.Count, in _writeContext); + Writer.EndAuxiliaryNodeList(objectExpression.Properties.Count, ref _writeContext); - Writer.EndObject(objectExpression.Properties.Count, in _writeContext); + Writer.EndObject(objectExpression.Properties.Count, ref _writeContext); return objectExpression; } @@ -1202,11 +1202,11 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && { _writeContext.SetNodeProperty(nameof(objectPattern.Properties), static node => ref node.As().Properties); - Writer.StartObject(objectPattern.Properties.Count, in _writeContext); + Writer.StartObject(objectPattern.Properties.Count, ref _writeContext); VisitAuxiliaryNodeList(in objectPattern.Properties, separator: ","); - Writer.EndObject(objectPattern.Properties.Count, in _writeContext); + Writer.EndObject(objectPattern.Properties.Count, ref _writeContext); return objectPattern; } @@ -1214,8 +1214,8 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitPrivateIdentifier(PrivateIdentifier privateIdentifier) { _writeContext.SetNodeProperty(nameof(privateIdentifier.Name), static node => node.As().Name); - Writer.WritePunctuator("#", TokenFlags.Leading, in _writeContext); - Writer.WriteIdentifier(privateIdentifier.Name!, in _writeContext); + Writer.WritePunctuator("#", TokenFlags.Leading, ref _writeContext); + Writer.WriteIdentifier(privateIdentifier.Name!, ref _writeContext); return privateIdentifier; } @@ -1236,13 +1236,13 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && { case PropertyKind.Get: _writeContext.SetNodeProperty(nameof(property.Kind), static node => node.As().Kind); - Writer.WriteKeyword("get", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + 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, in _writeContext); + Writer.WriteKeyword("set", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); isMethod = true; break; @@ -1254,7 +1254,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && { _writeContext.SetNodeProperty(nameof(property.Key), static node => node.As().Key); VisitPropertyKey(property.Key, property.Computed, leadingBracketFlags: TokenFlags.LeadingSpaceRecommended); - Writer.WritePunctuator(":", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); + Writer.WritePunctuator(":", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext); } isMethod = false; @@ -1289,7 +1289,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && if (propertyDefinition.Static) { _writeContext.SetNodeProperty(nameof(propertyDefinition.Static), static node => node.As().Static); - Writer.WriteKeyword("static", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("static", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); } _writeContext.SetNodeProperty(nameof(propertyDefinition.Key), static node => node.As().Key); @@ -1298,13 +1298,13 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && if (propertyDefinition.Value is not null) { _writeContext.ClearNodeProperty(); - Writer.WritePunctuator("=", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); + 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, in _writeContext); + Writer.WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext); return propertyDefinition; } @@ -1312,7 +1312,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitRestElement(RestElement restElement) { _writeContext.SetNodeProperty(nameof(restElement.Argument), static node => node.As().Argument); - Writer.WritePunctuator("...", TokenFlags.Leading, in _writeContext); + Writer.WritePunctuator("...", TokenFlags.Leading, ref _writeContext); VisitAuxiliaryNode(restElement.Argument); @@ -1321,7 +1321,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitReturnStatement(ReturnStatement returnStatement) { - Writer.WriteKeyword("return", (returnStatement.Argument is not null).ToFlag(TokenFlags.SurroundingSpaceRecommended, TokenFlags.LeadingSpaceRecommended), in _writeContext); + Writer.WriteKeyword("return", (returnStatement.Argument is not null).ToFlag(TokenFlags.SurroundingSpaceRecommended, TokenFlags.LeadingSpaceRecommended), ref _writeContext); if (returnStatement.Argument is not null) { @@ -1349,7 +1349,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && var argumentNeedsBrackets = UnaryOperandNeedsBrackets(spreadElement, spreadElement.Argument); _writeContext.SetNodeProperty(nameof(spreadElement.Argument), static node => node.As().Argument); - Writer.WritePunctuator("...", TokenFlags.Leading, in _writeContext); + Writer.WritePunctuator("...", TokenFlags.Leading, ref _writeContext); VisitSubExpression(spreadElement.Argument, SubExpressionFlags(argumentNeedsBrackets, isLeftMost: false)); @@ -1358,21 +1358,21 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitStaticBlock(StaticBlock staticBlock) { - Writer.WriteKeyword("static", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("static", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(staticBlock.Body), static node => ref node.As().Body); - Writer.StartBlock(staticBlock.Body.Count, in _writeContext); + Writer.StartBlock(staticBlock.Body.Count, ref _writeContext); VisitStatementList(in staticBlock.Body); - Writer.EndBlock(staticBlock.Body.Count, in _writeContext); + Writer.EndBlock(staticBlock.Body.Count, ref _writeContext); return staticBlock; } protected internal override object? VisitSuper(Super super) { - Writer.WriteKeyword("super", in _writeContext); + Writer.WriteKeyword("super", ref _writeContext); return super; } @@ -1381,7 +1381,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && { if (switchCase.Test is not null) { - Writer.WriteKeyword("case", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("case", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(switchCase.Test), static node => node.As().Test); VisitRootExpression(switchCase.Test, RootExpressionFlags(needsBrackets: false)); @@ -1390,10 +1390,10 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && } else { - Writer.WriteKeyword("default", TokenFlags.LeadingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("default", TokenFlags.LeadingSpaceRecommended, ref _writeContext); } - Writer.WritePunctuator(":", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in _writeContext); + Writer.WritePunctuator(":", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(switchCase.Consequent), static node => ref node.As().Consequent); @@ -1413,19 +1413,19 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitSwitchStatement(SwitchStatement switchStatement) { - Writer.WriteKeyword("switch", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + 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, in _writeContext); + 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, in _writeContext); + Writer.EndBlock(switchStatement.Cases.Count, ref _writeContext); return switchStatement; } @@ -1445,14 +1445,14 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitTemplateElement(TemplateElement templateElement) { _writeContext.SetNodeProperty(nameof(templateElement.Value), static node => node.As().Value); - Writer.WriteLiteral(templateElement.Value.Raw, TokenType.Template, in _writeContext); + Writer.WriteLiteral(templateElement.Value.Raw, TokenType.Template, ref _writeContext); return templateElement; } protected internal override object? VisitTemplateLiteral(TemplateLiteral templateLiteral) { - Writer.WritePunctuator("`", TokenFlags.Leading, in _writeContext); + Writer.WritePunctuator("`", TokenFlags.Leading, ref _writeContext); TemplateElement quasi; for (var i = 0; !(quasi = templateLiteral.Quasis[i]).Tail; i++) @@ -1461,29 +1461,29 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && VisitAuxiliaryNode(quasi); _writeContext.SetNodeProperty(nameof(templateLiteral.Expressions), static node => ref node.As().Expressions); - Writer.WritePunctuator("${", TokenFlags.Leading, in _writeContext); + Writer.WritePunctuator("${", TokenFlags.Leading, ref _writeContext); VisitRootExpression(templateLiteral.Expressions[i], RootExpressionFlags(needsBrackets: false)); - Writer.WritePunctuator("}", TokenFlags.Trailing, in _writeContext); + Writer.WritePunctuator("}", TokenFlags.Trailing, ref _writeContext); } _writeContext.SetNodeProperty(nameof(templateLiteral.Quasis), static node => ref node.As().Quasis); VisitAuxiliaryNode(quasi); - Writer.WritePunctuator("`", TokenFlags.Trailing, in _writeContext); + Writer.WritePunctuator("`", TokenFlags.Trailing, ref _writeContext); return templateLiteral; } protected internal override object? VisitThisExpression(ThisExpression thisExpression) { - Writer.WriteKeyword("this", in _writeContext); + Writer.WriteKeyword("this", ref _writeContext); return thisExpression; } protected internal override object? VisitThrowStatement(ThrowStatement throwStatement) { - Writer.WriteKeyword("throw", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("throw", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(throwStatement.Argument), static node => node.As().Argument); VisitRootExpression(throwStatement.Argument, RootExpressionFlags(needsBrackets: false)); @@ -1495,7 +1495,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && protected internal override object? VisitTryStatement(TryStatement tryStatement) { - Writer.WriteKeyword("try", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WriteKeyword("try", TokenFlags.SurroundingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(tryStatement.Block), static node => node.As().Block); StatementFlags bodyFlags; @@ -1504,7 +1504,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && if (tryStatement.Handler is not null) { _writeContext.ClearNodeProperty(); - Writer.WriteKeyword("catch", TokenFlags.SurroundingSpaceRecommended | StatementBodyFlagsToKeywordFlags(bodyFlags), in _writeContext); + Writer.WriteKeyword("catch", TokenFlags.SurroundingSpaceRecommended | StatementBodyFlagsToKeywordFlags(bodyFlags), ref _writeContext); _writeContext.SetNodeProperty(nameof(tryStatement.Handler), static node => node.As().Handler); VisitAuxiliaryNode(tryStatement.Handler); @@ -1514,7 +1514,7 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && if (tryStatement.Finalizer is not null) { _writeContext.ClearNodeProperty(); - Writer.WriteKeyword("finally", TokenFlags.SurroundingSpaceRecommended | StatementBodyFlagsToKeywordFlags(bodyFlags), in _writeContext); + 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)); @@ -1533,11 +1533,11 @@ binaryExpression.Right is UnaryExpression rightUnaryExpression && _writeContext.SetNodeProperty(nameof(unaryExpression.Operator), static node => node.As().Operator); if (char.IsLetter(op[0])) { - Writer.WriteKeyword(op, TokenFlags.TrailingSpaceRecommended, in _writeContext); + Writer.WriteKeyword(op, TokenFlags.TrailingSpaceRecommended, ref _writeContext); } else { - Writer.WritePunctuator(op, TokenFlags.Leading, in _writeContext); + Writer.WritePunctuator(op, TokenFlags.Leading, ref _writeContext); // Cases like +(+x) or +(++x) must be disambiguated with brackets. if (!argumentNeedsBrackets && @@ -1559,7 +1559,7 @@ unaryExpression.Argument is UnaryExpression argumentUnaryExpression && VisitSubExpression(unaryExpression.Argument, SubExpressionFlags(argumentNeedsBrackets, isLeftMost: true)); _writeContext.SetNodeProperty(nameof(unaryExpression.Operator), static node => node.As().Operator); - Writer.WritePunctuator(op, TokenFlags.Trailing, in _writeContext); + Writer.WritePunctuator(op, TokenFlags.Trailing, ref _writeContext); } return unaryExpression; @@ -1569,7 +1569,7 @@ unaryExpression.Argument is UnaryExpression argumentUnaryExpression && { _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), in _writeContext); + _currentStatementFlags.HasFlagFast(StatementFlags.NestedVariableDeclaration).ToFlag(TokenFlags.TrailingSpaceRecommended, TokenFlags.SurroundingSpaceRecommended), ref _writeContext); _writeContext.SetNodeProperty(nameof(variableDeclaration.Declarations), static node => ref node.As().Declarations); @@ -1599,7 +1599,7 @@ unaryExpression.Argument is UnaryExpression argumentUnaryExpression && if (variableDeclarator.Init is not null) { _writeContext.ClearNodeProperty(); - Writer.WritePunctuator("=", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, in _writeContext); + Writer.WritePunctuator("=", TokenFlags.InBetween | TokenFlags.SurroundingSpaceRecommended, ref _writeContext); _writeContext.SetNodeProperty(nameof(variableDeclarator.Init), static node => node.As().Init); @@ -1618,7 +1618,7 @@ unaryExpression.Argument is UnaryExpression argumentUnaryExpression && protected internal override object? VisitWhileStatement(WhileStatement whileStatement) { - Writer.WriteKeyword("while", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + 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)); @@ -1631,7 +1631,7 @@ unaryExpression.Argument is UnaryExpression argumentUnaryExpression && protected internal override object? VisitWithStatement(WithStatement withStatement) { - Writer.WriteKeyword("with", TokenFlags.SurroundingSpaceRecommended, in _writeContext); + 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)); @@ -1644,12 +1644,12 @@ unaryExpression.Argument is UnaryExpression argumentUnaryExpression && protected internal override object? VisitYieldExpression(YieldExpression yieldExpression) { - Writer.WriteKeyword("yield", (!yieldExpression.Delegate && yieldExpression.Argument is not null).ToFlag(TokenFlags.TrailingSpaceRecommended), in _writeContext); + 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), in _writeContext); + Writer.WritePunctuator("*", (yieldExpression.Argument is not null).ToFlag(TokenFlags.TrailingSpaceRecommended), ref _writeContext); } if (yieldExpression.Argument is not null) diff --git a/src/Esprima/Utils/JavascriptTextWriter.WriteContext.cs b/src/Esprima/Utils/JavascriptTextWriter.WriteContext.cs index 74dcc861..5ca5ca93 100644 --- a/src/Esprima/Utils/JavascriptTextWriter.WriteContext.cs +++ b/src/Esprima/Utils/JavascriptTextWriter.WriteContext.cs @@ -19,6 +19,7 @@ internal WriteContext(Node? parentNode, Node node) Node = node; _nodePropertyName = null; _nodePropertyValueAccessor = null; + Data = null; } public Node? ParentNode { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } @@ -85,5 +86,7 @@ internal void SetNodeProperty(string name, NodePropertyListValueAccessor l [MethodImpl(MethodImplOptions.AggressiveInlining)] public void ChangeNodeProperty(string name, NodePropertyListValueAccessor listValueAccessor) where T : Node? => SetNodeProperty(name ?? ThrowArgumentNullException(nameof(name)), listValueAccessor ?? ThrowArgumentNullException>(nameof(listValueAccessor))); + + public object? Data; } } diff --git a/src/Esprima/Utils/JavascriptTextWriter.cs b/src/Esprima/Utils/JavascriptTextWriter.cs index dd1dea71..becae097 100644 --- a/src/Esprima/Utils/JavascriptTextWriter.cs +++ b/src/Esprima/Utils/JavascriptTextWriter.cs @@ -62,9 +62,9 @@ protected void ForceRecommendedSpace() LastTokenFlags |= TokenFlags.TrailingSpaceRecommended; } - public virtual void WriteEpsilon(TokenFlags flags, in WriteContext context) { } + public virtual void WriteEpsilon(TokenFlags flags, ref WriteContext context) { } - protected virtual void StartIdentifier(string value, TokenFlags flags, in WriteContext context) + protected virtual void StartIdentifier(string value, TokenFlags flags, ref WriteContext context) { switch (LastTokenType) { @@ -87,26 +87,26 @@ protected virtual void StartIdentifier(string value, TokenFlags flags, in WriteC } } - public void WriteIdentifier(string value, TokenFlags flags, in WriteContext context) + public void WriteIdentifier(string value, TokenFlags flags, ref WriteContext context) { - StartIdentifier(value, flags, in context); + StartIdentifier(value, flags, ref context); _writer.Write(value); WhiteSpaceWrittenSinceLastToken = false; - EndIdentifier(value, flags, in context); + EndIdentifier(value, flags, ref context); LastTokenType = TokenType.Identifier; LastTokenFlags = flags; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteIdentifier(string value, in WriteContext context) + public void WriteIdentifier(string value, ref WriteContext context) { - WriteIdentifier(value, TokenFlags.None, in context); + WriteIdentifier(value, TokenFlags.None, ref context); } - protected virtual void EndIdentifier(string value, TokenFlags flags, in WriteContext context) { } + protected virtual void EndIdentifier(string value, TokenFlags flags, ref WriteContext context) { } - protected virtual void StartKeyword(string value, TokenFlags flags, in WriteContext context) + protected virtual void StartKeyword(string value, TokenFlags flags, ref WriteContext context) { switch (LastTokenType) { @@ -129,26 +129,26 @@ protected virtual void StartKeyword(string value, TokenFlags flags, in WriteCont } } - public void WriteKeyword(string value, TokenFlags flags, in WriteContext context) + public void WriteKeyword(string value, TokenFlags flags, ref WriteContext context) { - StartKeyword(value, flags, in context); + StartKeyword(value, flags, ref context); _writer.Write(value); WhiteSpaceWrittenSinceLastToken = false; - EndKeyword(value, flags, in context); + EndKeyword(value, flags, ref context); LastTokenType = TokenType.Keyword; LastTokenFlags = flags; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteKeyword(string value, in WriteContext context) + public void WriteKeyword(string value, ref WriteContext context) { - WriteKeyword(value, TokenFlags.None, in context); + WriteKeyword(value, TokenFlags.None, ref context); } - protected virtual void EndKeyword(string value, TokenFlags flags, in WriteContext context) { } + protected virtual void EndKeyword(string value, TokenFlags flags, ref WriteContext context) { } - protected virtual void StartLiteral(string value, TokenType type, TokenFlags flags, in WriteContext context) + protected virtual void StartLiteral(string value, TokenType type, TokenFlags flags, ref WriteContext context) { switch (LastTokenType) { @@ -174,158 +174,158 @@ protected virtual void StartLiteral(string value, TokenType type, TokenFlags fla } } - public void WriteLiteral(string value, TokenType type, TokenFlags flags, in WriteContext context) + public void WriteLiteral(string value, TokenType type, TokenFlags flags, ref WriteContext context) { - StartLiteral(value, type, flags, in context); + StartLiteral(value, type, flags, ref context); _writer.Write(value); WhiteSpaceWrittenSinceLastToken = false; - EndLiteral(value, type, flags, in context); + EndLiteral(value, type, flags, ref context); LastTokenType = type; LastTokenFlags = flags; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WriteLiteral(string value, TokenType tokenType, in WriteContext context) + public void WriteLiteral(string value, TokenType tokenType, ref WriteContext context) { - WriteLiteral(value, tokenType, TokenFlags.None, in context); + WriteLiteral(value, tokenType, TokenFlags.None, ref context); } - protected virtual void EndLiteral(string value, TokenType type, TokenFlags flags, in WriteContext context) { } + protected virtual void EndLiteral(string value, TokenType type, TokenFlags flags, ref WriteContext context) { } - protected virtual void StartPunctuator(string value, TokenFlags flags, in WriteContext context) { } + protected virtual void StartPunctuator(string value, TokenFlags flags, ref WriteContext context) { } - public void WritePunctuator(string value, TokenFlags flags, in WriteContext context) + public void WritePunctuator(string value, TokenFlags flags, ref WriteContext context) { - StartPunctuator(value, flags, in context); + StartPunctuator(value, flags, ref context); _writer.Write(value); WhiteSpaceWrittenSinceLastToken = false; - EndPunctuator(value, flags, in context); + EndPunctuator(value, flags, ref context); LastTokenType = TokenType.Punctuator; LastTokenFlags = flags; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void WritePunctuator(string value, in WriteContext context) + public void WritePunctuator(string value, ref WriteContext context) { - WritePunctuator(value, TokenFlags.None, in context); + WritePunctuator(value, TokenFlags.None, ref context); } - protected virtual void EndPunctuator(string value, TokenFlags flags, in WriteContext context) { } + protected virtual void EndPunctuator(string value, TokenFlags flags, ref WriteContext context) { } - public virtual void StartArray(int elementCount, in WriteContext context) + public virtual void StartArray(int elementCount, ref WriteContext context) { - WritePunctuator("[", TokenFlags.Leading, in context); + WritePunctuator("[", TokenFlags.Leading, ref context); } - public virtual void EndArray(int elementCount, in WriteContext context) + public virtual void EndArray(int elementCount, ref WriteContext context) { - WritePunctuator("]", TokenFlags.Trailing, in context); + WritePunctuator("]", TokenFlags.Trailing, ref context); } - public virtual void StartObject(int propertyCount, in WriteContext context) + public virtual void StartObject(int propertyCount, ref WriteContext context) { - WritePunctuator("{", TokenFlags.Leading | TokenFlags.TrailingSpaceRecommended, in context); + WritePunctuator("{", TokenFlags.Leading | TokenFlags.TrailingSpaceRecommended, ref context); } - public virtual void EndObject(int propertyCount, in WriteContext context) + public virtual void EndObject(int propertyCount, ref WriteContext context) { - WritePunctuator("}", TokenFlags.Trailing | TokenFlags.LeadingSpaceRecommended, in context); + WritePunctuator("}", TokenFlags.Trailing | TokenFlags.LeadingSpaceRecommended, ref context); } - public virtual void StartBlock(int statementCount, in WriteContext context) + public virtual void StartBlock(int statementCount, ref WriteContext context) { - WritePunctuator("{", TokenFlags.Leading | TokenFlags.SurroundingSpaceRecommended, in context); + WritePunctuator("{", TokenFlags.Leading | TokenFlags.SurroundingSpaceRecommended, ref context); } - public virtual void EndBlock(int statementCount, in WriteContext context) + public virtual void EndBlock(int statementCount, ref WriteContext context) { - WritePunctuator("}", TokenFlags.Trailing | TokenFlags.LeadingSpaceRecommended, in context); + WritePunctuator("}", TokenFlags.Trailing | TokenFlags.LeadingSpaceRecommended, ref context); } - public virtual void StartStatement(StatementFlags flags, in WriteContext context) { } + public virtual void StartStatement(StatementFlags flags, ref WriteContext context) { } - public virtual void EndStatement(StatementFlags flags, in WriteContext context) + public virtual void EndStatement(StatementFlags flags, ref WriteContext context) { // Writes statement terminator unless it can be omitted. if (flags.HasFlagFast(StatementFlags.NeedsSemicolon) && !flags.HasFlagFast(StatementFlags.MayOmitRightMostSemicolon | StatementFlags.IsRightMost)) { - WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in context); + WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref context); } } - public virtual void StartStatementList(int count, in WriteContext context) { } + public virtual void StartStatementList(int count, ref WriteContext context) { } - public virtual void StartStatementListItem(int index, int count, StatementFlags flags, in WriteContext context) { } + public virtual void StartStatementListItem(int index, int count, StatementFlags flags, ref WriteContext context) { } - public virtual void EndStatementListItem(int index, int count, StatementFlags flags, in WriteContext context) + public virtual void EndStatementListItem(int index, int count, StatementFlags flags, ref WriteContext context) { // Writes statement terminator unless it can be omitted. if (flags.HasFlagFast(StatementFlags.NeedsSemicolon) && !flags.HasFlagFast(StatementFlags.MayOmitRightMostSemicolon | StatementFlags.IsRightMost)) { - WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in context); + WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref context); } } - public virtual void EndStatementList(int count, in WriteContext context) { } + public virtual void EndStatementList(int count, ref WriteContext context) { } - public virtual void StartExpression(ExpressionFlags flags, in WriteContext context) + public virtual void StartExpression(ExpressionFlags flags, ref WriteContext context) { if (flags.HasFlagFast(ExpressionFlags.NeedsBrackets)) { - WritePunctuator("(", TokenFlags.Leading | flags.HasFlagFast(ExpressionFlags.SpaceAroundBracketsRecommended).ToFlag(TokenFlags.LeadingSpaceRecommended), in context); + WritePunctuator("(", TokenFlags.Leading | flags.HasFlagFast(ExpressionFlags.SpaceAroundBracketsRecommended).ToFlag(TokenFlags.LeadingSpaceRecommended), ref context); } } - public virtual void EndExpression(ExpressionFlags flags, in WriteContext context) + public virtual void EndExpression(ExpressionFlags flags, ref WriteContext context) { if (flags.HasFlagFast(ExpressionFlags.NeedsBrackets)) { - WritePunctuator(")", TokenFlags.Trailing | flags.HasFlagFast(ExpressionFlags.SpaceAroundBracketsRecommended).ToFlag(TokenFlags.TrailingSpaceRecommended), in context); + WritePunctuator(")", TokenFlags.Trailing | flags.HasFlagFast(ExpressionFlags.SpaceAroundBracketsRecommended).ToFlag(TokenFlags.TrailingSpaceRecommended), ref context); } } - public virtual void StartExpressionList(int count, in WriteContext context) { } + public virtual void StartExpressionList(int count, ref WriteContext context) { } - public virtual void StartExpressionListItem(int index, int count, ExpressionFlags flags, in WriteContext context) + public virtual void StartExpressionListItem(int index, int count, ExpressionFlags flags, ref WriteContext context) { if (flags.HasFlagFast(ExpressionFlags.NeedsBrackets)) { - WritePunctuator("(", TokenFlags.Leading, in context); + WritePunctuator("(", TokenFlags.Leading, ref context); } } - public virtual void EndExpressionListItem(int index, int count, ExpressionFlags flags, in WriteContext context) + public virtual void EndExpressionListItem(int index, int count, ExpressionFlags flags, ref WriteContext context) { if (flags.HasFlagFast(ExpressionFlags.NeedsBrackets)) { - WritePunctuator(")", TokenFlags.Trailing, in context); + WritePunctuator(")", TokenFlags.Trailing, ref context); } if (index < count - 1) { - WritePunctuator(",", TokenFlags.InBetween | TokenFlags.TrailingSpaceRecommended, in context); + WritePunctuator(",", TokenFlags.InBetween | TokenFlags.TrailingSpaceRecommended, ref context); } } - public virtual void EndExpressionList(int count, in WriteContext context) { } + public virtual void EndExpressionList(int count, ref WriteContext context) { } - public virtual void StartAuxiliaryNode(object? nodeContext, in WriteContext context) { } + public virtual void StartAuxiliaryNode(object? nodeContext, ref WriteContext context) { } - public virtual void EndAuxiliaryNode(object? nodeContext, in WriteContext context) { } + public virtual void EndAuxiliaryNode(object? nodeContext, ref WriteContext context) { } - public virtual void StartAuxiliaryNodeList(int count, in WriteContext context) where T : Node? { } + public virtual void StartAuxiliaryNodeList(int count, ref WriteContext context) where T : Node? { } - public virtual void StartAuxiliaryNodeListItem(int index, int count, string separator, object? nodeContext, in WriteContext context) where T : Node? { } + public virtual void StartAuxiliaryNodeListItem(int index, int count, string separator, object? nodeContext, ref WriteContext context) where T : Node? { } - public virtual void EndAuxiliaryNodeListItem(int index, int count, string separator, object? nodeContext, in WriteContext context) where T : Node? + public virtual void EndAuxiliaryNodeListItem(int index, int count, string separator, object? nodeContext, ref WriteContext context) where T : Node? { if (separator.Length > 0 && index < count - 1) { - WritePunctuator(separator, TokenFlags.InBetween | TokenFlags.TrailingSpaceRecommended, in context); + WritePunctuator(separator, TokenFlags.InBetween | TokenFlags.TrailingSpaceRecommended, ref context); } } - public virtual void EndAuxiliaryNodeList(int count, in WriteContext context) where T : Node? { } + public virtual void EndAuxiliaryNodeList(int count, ref WriteContext context) where T : Node? { } } diff --git a/src/Esprima/Utils/KnRJavascriptTextWriter.cs b/src/Esprima/Utils/KnRJavascriptTextWriter.cs index f1ed72b6..0f821e32 100644 --- a/src/Esprima/Utils/KnRJavascriptTextWriter.cs +++ b/src/Esprima/Utils/KnRJavascriptTextWriter.cs @@ -1,4 +1,5 @@ -using System.Runtime.CompilerServices; +using System.Diagnostics; +using System.Runtime.CompilerServices; using Esprima.Ast; namespace Esprima.Utils; @@ -26,13 +27,9 @@ public class KnRJavascriptTextWriter : JavascriptTextWriter private const int KeepSingleStatementBodyInLineFlag = 1 << 1; private const int KeepEmptyBlockBodyInLineFlag = 1 << 2; - private const int StatementBlockBodyFlag = 1 << 0; - private const int StatementEmptyBlockBodyFlag = 1 << 1; - private readonly int _optionFlags; private readonly string _indent; private int _indentionLevel; - private int _currentStatementBodyFlags; public KnRJavascriptTextWriter(TextWriter writer, JavascriptTextWriter.Options options) : base(writer, options) { @@ -70,8 +67,6 @@ public KnRJavascriptTextWriter(TextWriter writer, JavascriptTextWriter.Options o protected int MultiLineArrayLiteralThreshold { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } protected int MultiLineObjectLiteralThreshold { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } - protected int PreviousStatementBody { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; } - protected void IncreaseIndent() { _indentionLevel++; @@ -90,7 +85,7 @@ protected void WriteIndent() } } - public override void WriteEpsilon(TokenFlags flags, in WriteContext context) + public override void WriteEpsilon(TokenFlags flags, ref WriteContext context) { if (WhiteSpaceWrittenSinceLastToken) { @@ -103,7 +98,7 @@ public override void WriteEpsilon(TokenFlags flags, in WriteContext context) } } - protected override void StartKeyword(string value, TokenFlags flags, in WriteContext context) + protected override void StartKeyword(string value, TokenFlags flags, ref WriteContext context) { if (WhiteSpaceWrittenSinceLastToken) { @@ -112,7 +107,7 @@ protected override void StartKeyword(string value, TokenFlags flags, in WriteCon if (flags.HasFlagFast(TokenFlags.FollowsStatementBody)) { - if (UseEgyptianBraces && CanUseEgyptianBraces()) + if (UseEgyptianBraces && CanUseEgyptianBraces(ref context)) { WriteSpace(); } @@ -128,11 +123,11 @@ protected override void StartKeyword(string value, TokenFlags flags, in WriteCon } else { - base.StartKeyword(value, flags, context); + base.StartKeyword(value, flags, ref context); } } - protected override void StartIdentifier(string value, TokenFlags flags, in WriteContext context) + protected override void StartIdentifier(string value, TokenFlags flags, ref WriteContext context) { if (WhiteSpaceWrittenSinceLastToken) { @@ -145,11 +140,11 @@ protected override void StartIdentifier(string value, TokenFlags flags, in Write } else { - base.StartIdentifier(value, flags, context); + base.StartIdentifier(value, flags, ref context); } } - protected override void StartLiteral(string value, TokenType type, TokenFlags flags, in WriteContext context) + protected override void StartLiteral(string value, TokenType type, TokenFlags flags, ref WriteContext context) { if (WhiteSpaceWrittenSinceLastToken) { @@ -162,11 +157,11 @@ protected override void StartLiteral(string value, TokenType type, TokenFlags fl } else { - base.StartLiteral(value, type, flags, context); + base.StartLiteral(value, type, flags, ref context); } } - protected override void StartPunctuator(string value, TokenFlags flags, in WriteContext context) + protected override void StartPunctuator(string value, TokenFlags flags, ref WriteContext context) { if (WhiteSpaceWrittenSinceLastToken) { @@ -179,13 +174,13 @@ protected override void StartPunctuator(string value, TokenFlags flags, in Write } else { - base.StartPunctuator(value, flags, context); + base.StartPunctuator(value, flags, ref context); } } - public override void StartArray(int elementCount, in WriteContext context) + public override void StartArray(int elementCount, ref WriteContext context) { - base.StartArray(elementCount, context); + base.StartArray(elementCount, ref context); if (context.Node.Type == Nodes.ArrayExpression && elementCount >= MultiLineArrayLiteralThreshold) { @@ -194,7 +189,7 @@ public override void StartArray(int elementCount, in WriteContext context) } } - public override void EndArray(int elementCount, in WriteContext context) + public override void EndArray(int elementCount, ref WriteContext context) { if (context.Node.Type == Nodes.ArrayExpression && elementCount >= MultiLineArrayLiteralThreshold) { @@ -202,12 +197,12 @@ public override void EndArray(int elementCount, in WriteContext context) WriteIndent(); } - base.EndArray(elementCount, context); + base.EndArray(elementCount, ref context); } - public override void StartObject(int propertyCount, in WriteContext context) + public override void StartObject(int propertyCount, ref WriteContext context) { - base.StartObject(propertyCount, context); + base.StartObject(propertyCount, ref context); if (context.Node.Type == Nodes.ObjectExpression && propertyCount >= MultiLineObjectLiteralThreshold) { @@ -216,7 +211,7 @@ public override void StartObject(int propertyCount, in WriteContext context) } } - public override void EndObject(int propertyCount, in WriteContext context) + public override void EndObject(int propertyCount, ref WriteContext context) { if (context.Node.Type == Nodes.ObjectExpression && propertyCount >= MultiLineObjectLiteralThreshold) { @@ -224,12 +219,12 @@ public override void EndObject(int propertyCount, in WriteContext context) WriteIndent(); } - base.EndObject(propertyCount, context); + base.EndObject(propertyCount, ref context); } - public override void StartBlock(int statementCount, in WriteContext context) + public override void StartBlock(int statementCount, ref WriteContext context) { - base.StartBlock(statementCount, context); + base.StartBlock(statementCount, ref context); if (statementCount > 0 || !KeepEmptyBlockBodyInLine) { @@ -238,7 +233,7 @@ public override void StartBlock(int statementCount, in WriteContext context) } } - public override void EndBlock(int statementCount, in WriteContext context) + public override void EndBlock(int statementCount, ref WriteContext context) { if (statementCount > 0 || !KeepEmptyBlockBodyInLine) { @@ -246,31 +241,30 @@ public override void EndBlock(int statementCount, in WriteContext context) WriteIndent(); } - base.EndBlock(statementCount, context); + base.EndBlock(statementCount, ref context); + } + + protected virtual void StoreStatementBodyIntoContext(Statement statement, ref WriteContext context) + { + context.Data = statement; } - public override void StartStatement(StatementFlags flags, in WriteContext context) + protected virtual Statement RetrieveStatementBodyFromContext(ref WriteContext context) + { + return (Statement) (context.Data ?? throw new InvalidOperationException()); + } + + public override void StartStatement(StatementFlags flags, ref WriteContext context) { if (flags.HasFlagFast(StatementFlags.IsStatementBody)) { var statement = context.GetNodePropertyValue(); + StoreStatementBodyIntoContext(statement, ref context); - // Is block body? - if (statement is BlockStatement blockStatement) - { - _currentStatementBodyFlags = StatementBlockBodyFlag; - - if (blockStatement.Body.Count == 0) - { - _currentStatementBodyFlags |= StatementEmptyBlockBodyFlag; - } - } // Is single statement body? - else + if (statement.Type != Nodes.BlockStatement) { - _currentStatementBodyFlags = 0; - - if (CanInlineSingleStatementBody(statement, flags, in context)) + if (CanInlineSingleStatementBody(statement, flags, ref context)) { WriteSpace(); } @@ -284,24 +278,29 @@ public override void StartStatement(StatementFlags flags, in WriteContext contex } } - public override void EndStatement(StatementFlags flags, in WriteContext context) + public override void EndStatement(StatementFlags flags, ref WriteContext context) { - // Is single statement body? - if (flags.HasFlagFast(StatementFlags.IsStatementBody) && (_currentStatementBodyFlags & StatementBlockBodyFlag) == 0) + if (flags.HasFlagFast(StatementFlags.IsStatementBody)) { - if (!CanInlineSingleStatementBody(context.GetNodePropertyValue(), flags, in context)) + var statement = RetrieveStatementBodyFromContext(ref context); + + // Is single statement body? + if (statement.Type != Nodes.BlockStatement) { - DecreaseIndent(); + if (!CanInlineSingleStatementBody(statement, flags, ref context)) + { + DecreaseIndent(); + } } } - if (flags.HasFlagFast(StatementFlags.NeedsSemicolon) || ShouldTerminateStatementAnyway(context.GetNodePropertyValue(), flags, in context)) + if (flags.HasFlagFast(StatementFlags.NeedsSemicolon) || ShouldTerminateStatementAnyway(context.GetNodePropertyValue(), flags, ref context)) { - WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in context); + WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref context); } } - public override void StartStatementList(int count, in WriteContext context) + public override void StartStatementList(int count, ref WriteContext context) { if (context.Node.Type == Nodes.SwitchCase) { @@ -317,7 +316,7 @@ public override void StartStatementList(int count, in WriteContext context) } } - public override void StartStatementListItem(int index, int count, StatementFlags flags, in WriteContext context) + public override void StartStatementListItem(int index, int count, StatementFlags flags, ref WriteContext context) { if (context.Node.Type == Nodes.SwitchCase) { @@ -330,17 +329,17 @@ public override void StartStatementListItem(int index, int count, StatementFlags WriteIndent(); } - public override void EndStatementListItem(int index, int count, StatementFlags flags, in WriteContext context) + public override void EndStatementListItem(int index, int count, StatementFlags flags, ref WriteContext context) { - if (flags.HasFlagFast(StatementFlags.NeedsSemicolon) || ShouldTerminateStatementAnyway(context.GetNodePropertyListValue()[index], flags, in context)) + if (flags.HasFlagFast(StatementFlags.NeedsSemicolon) || ShouldTerminateStatementAnyway(context.GetNodePropertyListValue()[index], flags, ref context)) { - WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in context); + WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref context); } WriteLine(); } - public override void EndStatementList(int count, in WriteContext context) + public override void EndStatementList(int count, ref WriteContext context) { if (context.Node.Type == Nodes.SwitchCase) { @@ -351,14 +350,14 @@ public override void EndStatementList(int count, in WriteContext context) } } - protected virtual bool CanUseEgyptianBraces() + protected virtual bool CanUseEgyptianBraces(ref WriteContext context) { return KeepEmptyBlockBodyInLine - ? (_currentStatementBodyFlags & (StatementBlockBodyFlag | StatementEmptyBlockBodyFlag)) == StatementBlockBodyFlag - : (_currentStatementBodyFlags & (StatementBlockBodyFlag)) != 0; + ? RetrieveStatementBodyFromContext(ref context) is BlockStatement blockStatement && blockStatement.Body.Count > 0 + : RetrieveStatementBodyFromContext(ref context).Type == Nodes.BlockStatement; } - protected virtual bool CanInlineSingleStatementBody(Statement statement, StatementFlags flags, in WriteContext context) + protected virtual bool CanInlineSingleStatementBody(Statement statement, StatementFlags flags, ref WriteContext context) { return statement.Type switch { @@ -404,7 +403,7 @@ Nodes.ExportDefaultDeclaration or }; } - protected virtual bool ShouldTerminateStatementAnyway(Statement statement, StatementFlags flags, in WriteContext context) + protected virtual bool ShouldTerminateStatementAnyway(Statement statement, StatementFlags flags, ref WriteContext context) { return statement.Type switch { @@ -413,19 +412,19 @@ protected virtual bool ShouldTerminateStatementAnyway(Statement statement, State }; } - public override void StartExpressionListItem(int index, int count, ExpressionFlags flags, in WriteContext context) + public override void StartExpressionListItem(int index, int count, ExpressionFlags flags, ref WriteContext context) { if (context.Node.Type == Nodes.ArrayExpression && count >= MultiLineArrayLiteralThreshold) { WriteIndent(); } - base.StartExpressionListItem(index, count, flags, context); + base.StartExpressionListItem(index, count, flags, ref context); } - public override void EndExpressionListItem(int index, int count, ExpressionFlags flags, in WriteContext context) + public override void EndExpressionListItem(int index, int count, ExpressionFlags flags, ref WriteContext context) { - base.EndExpressionListItem(index, count, flags, context); + base.EndExpressionListItem(index, count, flags, ref context); if (context.Node.Type == Nodes.ArrayExpression && count >= MultiLineArrayLiteralThreshold) { @@ -433,7 +432,7 @@ public override void EndExpressionListItem(int index, int count, ExpressionFlags } } - public override void StartAuxiliaryNodeListItem(int index, int count, string separator, object? nodeContext, in WriteContext context) + public override void StartAuxiliaryNodeListItem(int index, int count, string separator, object? nodeContext, ref WriteContext context) { if (typeof(T) == typeof(SwitchCase) || context.Node.Type == Nodes.ClassBody || @@ -443,9 +442,9 @@ public override void StartAuxiliaryNodeListItem(int index, int count, string } } - public override void EndAuxiliaryNodeListItem(int index, int count, string separator, object? nodeContext, in WriteContext context) + public override void EndAuxiliaryNodeListItem(int index, int count, string separator, object? nodeContext, ref WriteContext context) { - base.EndAuxiliaryNodeListItem(index, count, separator, nodeContext, context); + base.EndAuxiliaryNodeListItem(index, count, separator, nodeContext, ref context); if (context.Node.Type is Nodes.ClassBody || context.Node.Type == Nodes.ObjectExpression && count >= MultiLineObjectLiteralThreshold) diff --git a/test/Esprima.Tests/AstToJavascriptTests.cs b/test/Esprima.Tests/AstToJavascriptTests.cs index c1bba303..631d6bef 100644 --- a/test/Esprima.Tests/AstToJavascriptTests.cs +++ b/test/Esprima.Tests/AstToJavascriptTests.cs @@ -14,23 +14,23 @@ private sealed class CustomCompactJavascriptTextWriter : JavascriptTextWriter { public CustomCompactJavascriptTextWriter(TextWriter writer, Options options) : base(writer, options) { } - public override void EndStatement(StatementFlags flags, in WriteContext context) + public override void EndStatement(StatementFlags flags, ref WriteContext context) { - if (flags.HasFlagFast(StatementFlags.NeedsSemicolon) || ShouldTerminateStatementAnyway(context.GetNodePropertyValue(), flags, in context)) + if (flags.HasFlagFast(StatementFlags.NeedsSemicolon) || ShouldTerminateStatementAnyway(context.GetNodePropertyValue(), flags, ref context)) { - WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in context); + WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref context); } } - public override void EndStatementListItem(int index, int count, StatementFlags flags, in WriteContext context) + public override void EndStatementListItem(int index, int count, StatementFlags flags, ref WriteContext context) { - if (flags.HasFlagFast(StatementFlags.NeedsSemicolon) || ShouldTerminateStatementAnyway(context.GetNodePropertyListValue()[index], flags, in context)) + if (flags.HasFlagFast(StatementFlags.NeedsSemicolon) || ShouldTerminateStatementAnyway(context.GetNodePropertyListValue()[index], flags, ref context)) { - WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, in context); + WritePunctuator(";", TokenFlags.Trailing | TokenFlags.TrailingSpaceRecommended, ref context); } } - private bool ShouldTerminateStatementAnyway(Statement statement, StatementFlags flags, in WriteContext context) + private bool ShouldTerminateStatementAnyway(Statement statement, StatementFlags flags, ref WriteContext context) { return statement.Type switch { From dc1c492180dbb0c9ddb5c8c3f7f81864441bde9c Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Wed, 20 Jul 2022 13:31:27 +0200 Subject: [PATCH 22/23] rename AstJson to AstToJson for consistency --- .../Utils/{AstJson.cs => AstToJson.cs} | 2 +- src/Esprima/Utils/AstToJsonConverter.cs | 24 +++++++++---------- .../Utils/Jsx/JsxAstToJsonConverter.cs | 2 +- test/Esprima.Tests/Fixtures.cs | 18 +++++++------- 4 files changed, 23 insertions(+), 23 deletions(-) rename src/Esprima/Utils/{AstJson.cs => AstToJson.cs} (99%) diff --git a/src/Esprima/Utils/AstJson.cs b/src/Esprima/Utils/AstToJson.cs similarity index 99% rename from src/Esprima/Utils/AstJson.cs rename to src/Esprima/Utils/AstToJson.cs index 1143948f..42bd0df1 100644 --- a/src/Esprima/Utils/AstJson.cs +++ b/src/Esprima/Utils/AstToJson.cs @@ -8,7 +8,7 @@ public enum LocationMembersPlacement Start } -public static class AstJson +public static class AstToJson { public record class Options { diff --git a/src/Esprima/Utils/AstToJsonConverter.cs b/src/Esprima/Utils/AstToJsonConverter.cs index e3907194..c98f928c 100644 --- a/src/Esprima/Utils/AstToJsonConverter.cs +++ b/src/Esprima/Utils/AstToJsonConverter.cs @@ -10,15 +10,15 @@ namespace Esprima.Utils; public class AstToJsonConverter : AstVisitor { - public delegate AstToJsonConverter Factory(JsonWriter writer, AstJson.Options options); + public delegate AstToJsonConverter Factory(JsonWriter writer, AstToJson.Options options); private readonly JsonWriter _writer; private protected readonly bool _includeLineColumn; private protected readonly bool _includeRange; private protected readonly LocationMembersPlacement _locationMembersPlacement; - private protected readonly AstJson.TestCompatibilityMode _testCompatibilityMode; + private protected readonly AstToJson.TestCompatibilityMode _testCompatibilityMode; - public AstToJsonConverter(JsonWriter writer, AstJson.Options options) + public AstToJsonConverter(JsonWriter writer, AstToJson.Options options) { _writer = writer ?? throw new ArgumentNullException(nameof(writer)); @@ -232,7 +232,7 @@ public void Convert(Node node) Member("generator", ((IFunction) arrowFunctionExpression).Generator); Member("expression", arrowFunctionExpression.Expression); // original Esprima doesn't include this information yet - if (_testCompatibilityMode != AstJson.TestCompatibilityMode.EsprimaOrg) + if (_testCompatibilityMode != AstToJson.TestCompatibilityMode.EsprimaOrg) { Member("strict", arrowFunctionExpression.Strict); } @@ -444,7 +444,7 @@ public void Convert(Node node) Member("source", exportAllDeclaration.Source); // original Esprima doesn't include this information yet - if (_testCompatibilityMode != AstJson.TestCompatibilityMode.EsprimaOrg) + if (_testCompatibilityMode != AstToJson.TestCompatibilityMode.EsprimaOrg) { Member("exported", exportAllDeclaration.Exported); if (exportAllDeclaration.Assertions.Count > 0) @@ -475,7 +475,7 @@ public void Convert(Node node) Member("specifiers", exportNamedDeclaration.Specifiers); Member("source", exportNamedDeclaration.Source); // original Esprima doesn't include this information yet - if (_testCompatibilityMode != AstJson.TestCompatibilityMode.EsprimaOrg && exportNamedDeclaration.Assertions.Count > 0) + if (_testCompatibilityMode != AstToJson.TestCompatibilityMode.EsprimaOrg && exportNamedDeclaration.Assertions.Count > 0) { Member("assertions", exportNamedDeclaration.Assertions); } @@ -559,7 +559,7 @@ public void Convert(Node node) Member("generator", functionDeclaration.Generator); Member("expression", ((IFunction) functionDeclaration).Expression); // original Esprima doesn't include this information yet - if (_testCompatibilityMode != AstJson.TestCompatibilityMode.EsprimaOrg) + if (_testCompatibilityMode != AstToJson.TestCompatibilityMode.EsprimaOrg) { Member("strict", functionDeclaration.Strict); } @@ -579,7 +579,7 @@ public void Convert(Node node) Member("generator", functionExpression.Generator); Member("expression", ((IFunction) functionExpression).Expression); // original Esprima doesn't include this information yet - if (_testCompatibilityMode != AstJson.TestCompatibilityMode.EsprimaOrg) + if (_testCompatibilityMode != AstToJson.TestCompatibilityMode.EsprimaOrg) { Member("strict", functionExpression.Strict); } @@ -630,7 +630,7 @@ public ImportCompat() : base(Nodes.Import) { } { // original Esprima uses CallExpression to represent dynamic imports currently, // so we need to rewrite our representation to match this expectation - if (_testCompatibilityMode == AstJson.TestCompatibilityMode.EsprimaOrg) + if (_testCompatibilityMode == AstToJson.TestCompatibilityMode.EsprimaOrg) { const string importToken = "import"; @@ -651,7 +651,7 @@ public ImportCompat() : base(Nodes.Import) { } using (StartNodeObject(import)) { - if (_testCompatibilityMode != AstJson.TestCompatibilityMode.EsprimaOrg) + if (_testCompatibilityMode != AstToJson.TestCompatibilityMode.EsprimaOrg) { Member("source", import.Source); @@ -744,7 +744,7 @@ public ImportCompat() : base(Nodes.Import) { } switch (value) { case null: - if (_testCompatibilityMode != AstJson.TestCompatibilityMode.EsprimaOrg && literal.TokenType == TokenType.RegularExpression) + if (_testCompatibilityMode != AstToJson.TestCompatibilityMode.EsprimaOrg && literal.TokenType == TokenType.RegularExpression) { // This is how esprima.org actually renders regexes since it relies on Regex.toString _writer.String(literal.Raw); @@ -880,7 +880,7 @@ public ImportCompat() : base(Nodes.Import) { } Member("sourceType", program.SourceType); // original Esprima doesn't include this information yet - if (_testCompatibilityMode != AstJson.TestCompatibilityMode.EsprimaOrg && program is Script s) + if (_testCompatibilityMode != AstToJson.TestCompatibilityMode.EsprimaOrg && program is Script s) { Member("strict", s.Strict); } diff --git a/src/Esprima/Utils/Jsx/JsxAstToJsonConverter.cs b/src/Esprima/Utils/Jsx/JsxAstToJsonConverter.cs index 841299dd..2e5d536c 100644 --- a/src/Esprima/Utils/Jsx/JsxAstToJsonConverter.cs +++ b/src/Esprima/Utils/Jsx/JsxAstToJsonConverter.cs @@ -5,7 +5,7 @@ namespace Esprima.Utils.Jsx; public class JsxAstToJsonConverter : AstToJsonConverter, IJsxAstVisitor { - public JsxAstToJsonConverter(JsonWriter writer, AstJson.Options options) + public JsxAstToJsonConverter(JsonWriter writer, AstToJson.Options options) : base(writer, options) { } diff --git a/test/Esprima.Tests/Fixtures.cs b/test/Esprima.Tests/Fixtures.cs index ebc91261..44ca0d00 100644 --- a/test/Esprima.Tests/Fixtures.cs +++ b/test/Esprima.Tests/Fixtures.cs @@ -19,7 +19,7 @@ public class Fixtures private static string ParseAndFormat(SourceType sourceType, string source, ParserOptions parserOptions, Func parserFactory, - AstToJsonConverter.Factory converterFactory, AstJson.Options conversionOptions) + AstToJsonConverter.Factory converterFactory, AstToJson.Options conversionOptions) { var parser = parserFactory(source, parserOptions); var program = sourceType == SourceType.Script ? (Program) parser.ParseScript() : parser.ParseModule(); @@ -144,7 +144,7 @@ public void ExecuteTestCase(string fixture) { sourceType = SourceType.Module; expected = File.ReadAllText(moduleFilePath); - if (WriteBackExpectedTree && metadata.ConversionOptions.TestCompatibilityMode == AstJson.TestCompatibilityMode.None) + if (WriteBackExpectedTree && metadata.ConversionOptions.TestCompatibilityMode == AstToJson.TestCompatibilityMode.None) { var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, converterFactory, metadata.ConversionOptions); if (!CompareTrees(actual, expected, metadata)) @@ -154,7 +154,7 @@ public void ExecuteTestCase(string fixture) else if (File.Exists(treeFilePath)) { expected = File.ReadAllText(treeFilePath); - if (WriteBackExpectedTree && metadata.ConversionOptions.TestCompatibilityMode == AstJson.TestCompatibilityMode.None) + if (WriteBackExpectedTree && metadata.ConversionOptions.TestCompatibilityMode == AstToJson.TestCompatibilityMode.None) { var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, converterFactory, metadata.ConversionOptions); if (!CompareTrees(actual, expected, metadata)) @@ -165,7 +165,7 @@ public void ExecuteTestCase(string fixture) { invalid = true; expected = File.ReadAllText(failureFilePath); - if (WriteBackExpectedTree && metadata.ConversionOptions.TestCompatibilityMode == AstJson.TestCompatibilityMode.None) + if (WriteBackExpectedTree && metadata.ConversionOptions.TestCompatibilityMode == AstToJson.TestCompatibilityMode.None) { var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, converterFactory, metadata.ConversionOptions); if (!CompareTrees(actual, expected, metadata)) @@ -226,7 +226,7 @@ internal static string GetFixturesPath() private sealed class FixtureMetadata { public static readonly FixtureMetadata Default = new FixtureMetadata( - AstJson.Options.Default with + AstToJson.Options.Default with { IncludingLineColumn = true, IncludingRange = true @@ -267,11 +267,11 @@ public static Dictionary ReadMetadata() private static FixtureMetadata CreateFrom(HashSet flags) { - var conversionOptions = AstJson.Options.Default with + var conversionOptions = AstToJson.Options.Default with { IncludingLineColumn = flags.Contains("IncludesLocation"), IncludingRange = flags.Contains("IncludesRange"), - TestCompatibilityMode = flags.Contains("EsprimaOrgFixture") ? AstJson.TestCompatibilityMode.EsprimaOrg : AstJson.TestCompatibilityMode.None + TestCompatibilityMode = flags.Contains("EsprimaOrgFixture") ? AstToJson.TestCompatibilityMode.EsprimaOrg : AstToJson.TestCompatibilityMode.None }; var includesLocationSource = flags.Contains("IncludesLocationSource"); @@ -280,14 +280,14 @@ private static FixtureMetadata CreateFrom(HashSet flags) return new FixtureMetadata(conversionOptions, includesLocationSource, ignoresRegex); } - private FixtureMetadata(AstJson.Options conversionOptions, bool includesLocationSource, bool ignoresRegex) + private FixtureMetadata(AstToJson.Options conversionOptions, bool includesLocationSource, bool ignoresRegex) { ConversionOptions = conversionOptions; IncludesLocationSource = includesLocationSource; IgnoresRegex = ignoresRegex; } - public AstJson.Options ConversionOptions { get; } + public AstToJson.Options ConversionOptions { get; } public bool IncludesLocationSource { get; } public bool IgnoresRegex { get; } } From fe28071e485bcea253ca804ac1d75bdda0356287 Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Wed, 20 Jul 2022 15:17:22 +0200 Subject: [PATCH 23/23] improve public API --- src/Esprima/Utils/AstToJavascript.cs | 74 ++++++++--------- src/Esprima/Utils/AstToJavascriptConverter.cs | 4 +- src/Esprima/Utils/AstToJson.cs | 81 ++++++++++--------- src/Esprima/Utils/AstToJsonConverter.cs | 24 +++--- src/Esprima/Utils/JavascriptTextWriter.cs | 16 ++-- .../Utils/Jsx/JsxAstToJsonConverter.cs | 9 ++- src/Esprima/Utils/KnRJavascriptTextWriter.cs | 51 ++++++------ test/Esprima.Tests/AstToJavascriptTests.cs | 56 ++++++------- test/Esprima.Tests/Fixtures.cs | 70 ++++++++-------- 9 files changed, 196 insertions(+), 189 deletions(-) diff --git a/src/Esprima/Utils/AstToJavascript.cs b/src/Esprima/Utils/AstToJavascript.cs index d929eb04..e36e0d1f 100644 --- a/src/Esprima/Utils/AstToJavascript.cs +++ b/src/Esprima/Utils/AstToJavascript.cs @@ -2,77 +2,71 @@ 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 record class Options + public static string ToJavascriptString(this Node node) { - public static readonly Options Default = new(); + return ToJavascriptString(node, JavascriptTextWriterOptions.Default, AstToJavascriptOptions.Default); } - public static string ToJavascriptString(this Node node, AstToJavascriptConverter.Factory? converterFactory = null) + public static string ToJavascriptString(this Node node, KnRJavascriptTextWriterOptions formattingOptions) { - JavascriptTextWriter.Factory writerFactory = static (writer, formattingOptions) => new JavascriptTextWriter(writer, formattingOptions); - return ToJavascriptString(node, writerFactory, JavascriptTextWriter.Options.Default, Options.Default, converterFactory); + return ToJavascriptString(node, formattingOptions, AstToJavascriptOptions.Default); } - public static string ToJavascriptString(this Node node, KnRJavascriptTextWriter.Options formattingOptions, AstToJavascriptConverter.Factory? converterFactory = null) + public static string ToJavascriptString(this Node node, bool beautify) { - JavascriptTextWriter.Factory writerFactory = static (writer, formattingOptions) => new KnRJavascriptTextWriter(writer, formattingOptions); - return ToJavascriptString(node, writerFactory, formattingOptions, Options.Default, converterFactory); + return ToJavascriptString(node, beautify ? KnRJavascriptTextWriterOptions.Default : JavascriptTextWriterOptions.Default, AstToJavascriptOptions.Default); } - public static string ToJavascriptString(this Node node, bool beautify, AstToJavascriptConverter.Factory? converterFactory = null) + public static string ToJavascriptString(this Node node, JavascriptTextWriterOptions writerOptions, AstToJavascriptOptions options) { - if (beautify) - { - return ToJavascriptString(node, KnRJavascriptTextWriter.Options.Default, converterFactory); - } - else + using (var writer = new StringWriter()) { - return ToJavascriptString(node, converterFactory); + WriteJavascript(node, writer, writerOptions, options); + return writer.ToString(); } } - public static string ToJavascriptString(this Node node, JavascriptTextWriter.Factory writerFactory, JavascriptTextWriter.Options formattingOptions, Options options, AstToJavascriptConverter.Factory? converterFactory = null) + public static void WriteJavascript(this Node node, TextWriter writer) { - if (writerFactory is null) - { - throw new ArgumentNullException(nameof(writerFactory)); - } - - using (var writer = new StringWriter()) - { - WriteJavascript(node, writerFactory(writer, formattingOptions), options, converterFactory); - return writer.ToString(); - } + WriteJavascript(node, writer, JavascriptTextWriterOptions.Default, AstToJavascriptOptions.Default); } - public static void WriteJavascript(this Node node, TextWriter writer, AstToJavascriptConverter.Factory? converterFactory = null) + public static void WriteJavascript(this Node node, TextWriter writer, KnRJavascriptTextWriterOptions formattingOptions) { - WriteJavascript(node, new JavascriptTextWriter(writer, JavascriptTextWriter.Options.Default), Options.Default, converterFactory); + WriteJavascript(node, writer, formattingOptions, AstToJavascriptOptions.Default); } - public static void WriteJavascript(this Node node, TextWriter writer, KnRJavascriptTextWriter.Options formattingOptions, AstToJavascriptConverter.Factory? converterFactory = null) + public static void WriteJavascript(this Node node, TextWriter writer, bool beautify) { - WriteJavascript(node, new KnRJavascriptTextWriter(writer, formattingOptions), Options.Default, converterFactory); + WriteJavascript(node, writer, beautify ? KnRJavascriptTextWriterOptions.Default : JavascriptTextWriterOptions.Default, AstToJavascriptOptions.Default); } - public static void WriteJavascript(this Node node, TextWriter writer, bool beautify, AstToJavascriptConverter.Factory? converterFactory = null) + public static void WriteJavascript(this Node node, TextWriter writer, JavascriptTextWriterOptions writerOptions, AstToJavascriptOptions options) { - if (beautify) + if (writerOptions is null) { - WriteJavascript(node, writer, KnRJavascriptTextWriter.Options.Default, converterFactory); - } - else - { - WriteJavascript(node, writer, converterFactory); + throw new ArgumentNullException(nameof(writerOptions)); } + + WriteJavascript(node, writerOptions.CreateWriter(writer), options); } - public static void WriteJavascript(this Node node, JavascriptTextWriter writer, Options options, AstToJavascriptConverter.Factory? converterFactory = null) + public static void WriteJavascript(this Node node, JavascriptTextWriter writer, AstToJavascriptOptions options) { - converterFactory ??= static (writer, options) => new AstToJavascriptConverter(writer, options); + if (options is null) + { + throw new ArgumentNullException(nameof(options)); + } - converterFactory(writer, options).Convert(node); + options.CreateConverter(writer).Convert(node); } } diff --git a/src/Esprima/Utils/AstToJavascriptConverter.cs b/src/Esprima/Utils/AstToJavascriptConverter.cs index 4bf661bb..fc257321 100644 --- a/src/Esprima/Utils/AstToJavascriptConverter.cs +++ b/src/Esprima/Utils/AstToJavascriptConverter.cs @@ -14,8 +14,6 @@ public partial class AstToJavascriptConverter : AstVisitor // * 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. - public delegate AstToJavascriptConverter Factory(JavascriptTextWriter writer, AstToJavascript.Options options); - private static readonly object s_lastSwitchCaseFlag = new(); private static readonly object s_forLoopInitDeclarationFlag = new(); @@ -24,7 +22,7 @@ public partial class AstToJavascriptConverter : AstVisitor private ExpressionFlags _currentExpressionFlags; private object? _currentAuxiliaryNodeContext; - public AstToJavascriptConverter(JavascriptTextWriter writer, AstToJavascript.Options options) + public AstToJavascriptConverter(JavascriptTextWriter writer, AstToJavascriptOptions options) { Writer = writer ?? throw new ArgumentNullException(nameof(writer)); diff --git a/src/Esprima/Utils/AstToJson.cs b/src/Esprima/Utils/AstToJson.cs index 42bd0df1..cbb090d9 100644 --- a/src/Esprima/Utils/AstToJson.cs +++ b/src/Esprima/Utils/AstToJson.cs @@ -8,76 +8,81 @@ public enum LocationMembersPlacement Start } -public static class AstToJson +internal enum AstToJsonTestCompatibilityMode { - public record class Options - { - public static readonly Options 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 TestCompatibilityMode TestCompatibilityMode { get; init; } - } + None, + EsprimaOrg, +} - internal enum TestCompatibilityMode - { - None, - EsprimaOrg, - } +public record class AstToJsonOptions +{ + public static readonly AstToJsonOptions Default = new(); - public static string ToJsonString(this Node node, AstToJsonConverter.Factory? converterFactory = null) + 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, converterFactory); + return ToJsonString(node, indent: null); } - public static string ToJsonString(this Node node, string? indent, AstToJsonConverter.Factory? converterFactory = null) + public static string ToJsonString(this Node node, string? indent) { - return ToJsonString(node, Options.Default, indent, converterFactory); + return ToJsonString(node, AstToJsonOptions.Default, indent); } - public static string ToJsonString(this Node node, Options options, AstToJsonConverter.Factory? converterFactory = null) + public static string ToJsonString(this Node node, AstToJsonOptions options) { - return ToJsonString(node, options, indent: null, converterFactory); + return ToJsonString(node, options, indent: null); } - public static string ToJsonString(this Node node, Options options, string? indent, AstToJsonConverter.Factory? converterFactory = null) + public static string ToJsonString(this Node node, AstToJsonOptions options, string? indent) { using (var writer = new StringWriter()) { - WriteJson(node, writer, options, indent, converterFactory); + WriteJson(node, writer, options, indent); return writer.ToString(); } } - public static void WriteJson(this Node node, TextWriter writer, AstToJsonConverter.Factory? converterFactory = null) + public static void WriteJson(this Node node, TextWriter writer) { - WriteJson(node, writer, indent: null, converterFactory); + WriteJson(node, writer, indent: null); } - public static void WriteJson(this Node node, TextWriter writer, string? indent, AstToJsonConverter.Factory? converterFactory = null) + public static void WriteJson(this Node node, TextWriter writer, string? indent) { - WriteJson(node, writer, Options.Default, indent, converterFactory); + WriteJson(node, writer, AstToJsonOptions.Default, indent); } - public static void WriteJson(this Node node, TextWriter writer, Options options, AstToJsonConverter.Factory? converterFactory = null) + public static void WriteJson(this Node node, TextWriter writer, AstToJsonOptions options) { - WriteJson(node, writer, options, indent: null, converterFactory); + WriteJson(node, writer, options, indent: null); } - public static void WriteJson(this Node node, TextWriter writer, Options options, string? indent, AstToJsonConverter.Factory? converterFactory = null) + public static void WriteJson(this Node node, TextWriter writer, AstToJsonOptions options, string? indent) { - WriteJson(node, new JsonTextWriter(writer, indent), options, converterFactory); + WriteJson(node, new JsonTextWriter(writer, indent), options); } - public static void WriteJson(this Node node, JsonWriter writer, Options options, AstToJsonConverter.Factory? converterFactory = null) + public static void WriteJson(this Node node, JsonWriter writer, AstToJsonOptions options) { - converterFactory ??= static (writer, options) => new AstToJsonConverter(writer, options); + if (options is null) + { + throw new ArgumentNullException(nameof(options)); + } - converterFactory(writer, options).Convert(node); + options.CreateConverter(writer).Convert(node); } } diff --git a/src/Esprima/Utils/AstToJsonConverter.cs b/src/Esprima/Utils/AstToJsonConverter.cs index c98f928c..1e00aa21 100644 --- a/src/Esprima/Utils/AstToJsonConverter.cs +++ b/src/Esprima/Utils/AstToJsonConverter.cs @@ -10,15 +10,13 @@ namespace Esprima.Utils; public class AstToJsonConverter : AstVisitor { - public delegate AstToJsonConverter Factory(JsonWriter writer, AstToJson.Options options); - private readonly JsonWriter _writer; private protected readonly bool _includeLineColumn; private protected readonly bool _includeRange; private protected readonly LocationMembersPlacement _locationMembersPlacement; - private protected readonly AstToJson.TestCompatibilityMode _testCompatibilityMode; + private protected readonly AstToJsonTestCompatibilityMode _testCompatibilityMode; - public AstToJsonConverter(JsonWriter writer, AstToJson.Options options) + public AstToJsonConverter(JsonWriter writer, AstToJsonOptions options) { _writer = writer ?? throw new ArgumentNullException(nameof(writer)); @@ -232,7 +230,7 @@ public void Convert(Node node) Member("generator", ((IFunction) arrowFunctionExpression).Generator); Member("expression", arrowFunctionExpression.Expression); // original Esprima doesn't include this information yet - if (_testCompatibilityMode != AstToJson.TestCompatibilityMode.EsprimaOrg) + if (_testCompatibilityMode != AstToJsonTestCompatibilityMode.EsprimaOrg) { Member("strict", arrowFunctionExpression.Strict); } @@ -444,7 +442,7 @@ public void Convert(Node node) Member("source", exportAllDeclaration.Source); // original Esprima doesn't include this information yet - if (_testCompatibilityMode != AstToJson.TestCompatibilityMode.EsprimaOrg) + if (_testCompatibilityMode != AstToJsonTestCompatibilityMode.EsprimaOrg) { Member("exported", exportAllDeclaration.Exported); if (exportAllDeclaration.Assertions.Count > 0) @@ -475,7 +473,7 @@ public void Convert(Node node) Member("specifiers", exportNamedDeclaration.Specifiers); Member("source", exportNamedDeclaration.Source); // original Esprima doesn't include this information yet - if (_testCompatibilityMode != AstToJson.TestCompatibilityMode.EsprimaOrg && exportNamedDeclaration.Assertions.Count > 0) + if (_testCompatibilityMode != AstToJsonTestCompatibilityMode.EsprimaOrg && exportNamedDeclaration.Assertions.Count > 0) { Member("assertions", exportNamedDeclaration.Assertions); } @@ -559,7 +557,7 @@ public void Convert(Node node) Member("generator", functionDeclaration.Generator); Member("expression", ((IFunction) functionDeclaration).Expression); // original Esprima doesn't include this information yet - if (_testCompatibilityMode != AstToJson.TestCompatibilityMode.EsprimaOrg) + if (_testCompatibilityMode != AstToJsonTestCompatibilityMode.EsprimaOrg) { Member("strict", functionDeclaration.Strict); } @@ -579,7 +577,7 @@ public void Convert(Node node) Member("generator", functionExpression.Generator); Member("expression", ((IFunction) functionExpression).Expression); // original Esprima doesn't include this information yet - if (_testCompatibilityMode != AstToJson.TestCompatibilityMode.EsprimaOrg) + if (_testCompatibilityMode != AstToJsonTestCompatibilityMode.EsprimaOrg) { Member("strict", functionExpression.Strict); } @@ -630,7 +628,7 @@ public ImportCompat() : base(Nodes.Import) { } { // original Esprima uses CallExpression to represent dynamic imports currently, // so we need to rewrite our representation to match this expectation - if (_testCompatibilityMode == AstToJson.TestCompatibilityMode.EsprimaOrg) + if (_testCompatibilityMode == AstToJsonTestCompatibilityMode.EsprimaOrg) { const string importToken = "import"; @@ -651,7 +649,7 @@ public ImportCompat() : base(Nodes.Import) { } using (StartNodeObject(import)) { - if (_testCompatibilityMode != AstToJson.TestCompatibilityMode.EsprimaOrg) + if (_testCompatibilityMode != AstToJsonTestCompatibilityMode.EsprimaOrg) { Member("source", import.Source); @@ -744,7 +742,7 @@ public ImportCompat() : base(Nodes.Import) { } switch (value) { case null: - if (_testCompatibilityMode != AstToJson.TestCompatibilityMode.EsprimaOrg && literal.TokenType == TokenType.RegularExpression) + 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); @@ -880,7 +878,7 @@ public ImportCompat() : base(Nodes.Import) { } Member("sourceType", program.SourceType); // original Esprima doesn't include this information yet - if (_testCompatibilityMode != AstToJson.TestCompatibilityMode.EsprimaOrg && program is Script s) + if (_testCompatibilityMode != AstToJsonTestCompatibilityMode.EsprimaOrg && program is Script s) { Member("strict", s.Strict); } diff --git a/src/Esprima/Utils/JavascriptTextWriter.cs b/src/Esprima/Utils/JavascriptTextWriter.cs index becae097..57eeb3e8 100644 --- a/src/Esprima/Utils/JavascriptTextWriter.cs +++ b/src/Esprima/Utils/JavascriptTextWriter.cs @@ -7,21 +7,21 @@ namespace Esprima.Utils; public delegate ref readonly NodeList NodePropertyListValueAccessor(Node node) where T : Node?; +public record class JavascriptTextWriterOptions +{ + public static readonly JavascriptTextWriterOptions Default = new(); + + protected internal virtual JavascriptTextWriter CreateWriter(TextWriter writer) => new JavascriptTextWriter(writer, this); +} + /// /// Base Javascript text writer (code formatter) which uses the most compact possible (i.e. minimal) format. /// public partial class JavascriptTextWriter { - public record class Options - { - public static readonly Options Default = new(); - } - - public delegate JavascriptTextWriter Factory(TextWriter writer, Options options); - private readonly TextWriter _writer; - public JavascriptTextWriter(TextWriter writer, Options options) + public JavascriptTextWriter(TextWriter writer, JavascriptTextWriterOptions options) { _writer = writer ?? throw new ArgumentNullException(nameof(writer)); diff --git a/src/Esprima/Utils/Jsx/JsxAstToJsonConverter.cs b/src/Esprima/Utils/Jsx/JsxAstToJsonConverter.cs index 2e5d536c..6d385820 100644 --- a/src/Esprima/Utils/Jsx/JsxAstToJsonConverter.cs +++ b/src/Esprima/Utils/Jsx/JsxAstToJsonConverter.cs @@ -3,9 +3,16 @@ namespace Esprima.Utils.Jsx; +public record class JsxAstToJsonOptions : AstToJsonOptions +{ + public static new readonly JsxAstToJsonOptions Default = new(); + + protected internal override AstToJsonConverter CreateConverter(JsonWriter writer) => new JsxAstToJsonConverter(writer, this); +} + public class JsxAstToJsonConverter : AstToJsonConverter, IJsxAstVisitor { - public JsxAstToJsonConverter(JsonWriter writer, AstToJson.Options options) + public JsxAstToJsonConverter(JsonWriter writer, JsxAstToJsonOptions options) : base(writer, options) { } diff --git a/src/Esprima/Utils/KnRJavascriptTextWriter.cs b/src/Esprima/Utils/KnRJavascriptTextWriter.cs index 0f821e32..43e27ceb 100644 --- a/src/Esprima/Utils/KnRJavascriptTextWriter.cs +++ b/src/Esprima/Utils/KnRJavascriptTextWriter.cs @@ -1,28 +1,27 @@ -using System.Diagnostics; -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; using Esprima.Ast; namespace Esprima.Utils; +public record class KnRJavascriptTextWriterOptions : JavascriptTextWriterOptions +{ + public static new readonly KnRJavascriptTextWriterOptions Default = new(); + + public string? Indent { get; init; } + public bool UseEgyptianBraces { get; init; } = true; + public bool KeepSingleStatementBodyInLine { get; init; } + public bool KeepEmptyBlockBodyInLine { get; init; } = true; + public int MultiLineArrayLiteralThreshold { get; init; } = 7; + public int MultiLineObjectLiteralThreshold { get; init; } = 3; + + protected internal override JavascriptTextWriter CreateWriter(TextWriter writer) => new KnRJavascriptTextWriter(writer, this); +} + /// /// Javascript text writer (code formatter) which implements the most common K&R style. /// public class KnRJavascriptTextWriter : JavascriptTextWriter { - public new record class Options : JavascriptTextWriter.Options - { - public static new readonly Options Default = new(); - - internal static Options GetDefaultFrom(JavascriptTextWriter.Options baseOptions) => Default; - - public string? Indent { get; init; } - public bool UseEgyptianBraces { get; init; } = true; - public bool KeepSingleStatementBodyInLine { get; init; } - public bool KeepEmptyBlockBodyInLine { get; init; } = true; - public int MultiLineArrayLiteralThreshold { get; init; } = 7; - public int MultiLineObjectLiteralThreshold { get; init; } = 3; - } - private const int UseEgyptianBracesFlag = 1 << 0; private const int KeepSingleStatementBodyInLineFlag = 1 << 1; private const int KeepEmptyBlockBodyInLineFlag = 1 << 2; @@ -31,34 +30,32 @@ public class KnRJavascriptTextWriter : JavascriptTextWriter private readonly string _indent; private int _indentionLevel; - public KnRJavascriptTextWriter(TextWriter writer, JavascriptTextWriter.Options options) : base(writer, options) + public KnRJavascriptTextWriter(TextWriter writer, KnRJavascriptTextWriterOptions options) : base(writer, options) { - var extendedOptions = options as Options ?? Options.GetDefaultFrom(options); - - if (!string.IsNullOrWhiteSpace(extendedOptions.Indent)) + if (!string.IsNullOrWhiteSpace(options.Indent)) { - throw new ArgumentException("Indent must be null or white-space.", nameof(extendedOptions)); + throw new ArgumentException("Indent must be null or white-space.", nameof(options)); } - _indent = extendedOptions.Indent ?? " "; + _indent = options.Indent ?? " "; - if (extendedOptions.UseEgyptianBraces) + if (options.UseEgyptianBraces) { _optionFlags |= UseEgyptianBracesFlag; } - if (extendedOptions.KeepSingleStatementBodyInLine) + if (options.KeepSingleStatementBodyInLine) { _optionFlags |= KeepSingleStatementBodyInLineFlag; } - if (extendedOptions.KeepEmptyBlockBodyInLine) + if (options.KeepEmptyBlockBodyInLine) { _optionFlags |= KeepEmptyBlockBodyInLineFlag; } - MultiLineArrayLiteralThreshold = extendedOptions.MultiLineArrayLiteralThreshold >= 0 ? extendedOptions.MultiLineArrayLiteralThreshold : int.MaxValue; - MultiLineObjectLiteralThreshold = extendedOptions.MultiLineObjectLiteralThreshold >= 0 ? extendedOptions.MultiLineObjectLiteralThreshold : int.MaxValue; + MultiLineArrayLiteralThreshold = options.MultiLineArrayLiteralThreshold >= 0 ? options.MultiLineArrayLiteralThreshold : int.MaxValue; + MultiLineObjectLiteralThreshold = options.MultiLineObjectLiteralThreshold >= 0 ? options.MultiLineObjectLiteralThreshold : int.MaxValue; } protected bool UseEgyptianBraces { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => (_optionFlags & UseEgyptianBracesFlag) != 0; } diff --git a/test/Esprima.Tests/AstToJavascriptTests.cs b/test/Esprima.Tests/AstToJavascriptTests.cs index 631d6bef..51fc5706 100644 --- a/test/Esprima.Tests/AstToJavascriptTests.cs +++ b/test/Esprima.Tests/AstToJavascriptTests.cs @@ -1,18 +1,20 @@ -using System; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; using Esprima.Ast; using Esprima.Test; using Esprima.Utils; -using Esprima.Utils.Jsx; -using Xunit; namespace Esprima.Tests { public class AstToJavascriptTests { + private record class CustomCompactJavascriptTextWriterOptions : JavascriptTextWriterOptions + { + protected internal override JavascriptTextWriter CreateWriter(TextWriter writer) => new CustomCompactJavascriptTextWriter(writer, this); + } + private sealed class CustomCompactJavascriptTextWriter : JavascriptTextWriter { - public CustomCompactJavascriptTextWriter(TextWriter writer, Options options) : base(writer, options) { } + public CustomCompactJavascriptTextWriter(TextWriter writer, CustomCompactJavascriptTextWriterOptions options) : base(writer, options) { } public override void EndStatement(StatementFlags flags, ref WriteContext context) { @@ -40,8 +42,8 @@ private bool ShouldTerminateStatementAnyway(Statement statement, StatementFlags } } - private static readonly JavascriptTextWriter.Factory s_customCompactWriterFactory = (writer, options) => new CustomCompactJavascriptTextWriter(writer, options); - private static readonly KnRJavascriptTextWriter.Options s_indentedWriterOptions = new KnRJavascriptTextWriter.Options + private static readonly CustomCompactJavascriptTextWriterOptions s_customCompactWriterOptions = new(); + private static readonly KnRJavascriptTextWriterOptions s_indentedWriterOptions = new() { Indent = " ", KeepEmptyBlockBodyInLine = false, @@ -67,7 +69,7 @@ public void ToJavascriptTest1() "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + var code = program.ToJavascriptString(s_customCompactWriterOptions, AstToJavascriptOptions.Default); Assert.Equal("if(true){p();}switch(foo){case'A':p();break;}switch(foo){default:p();break;}for(var a=[];;){}for(var elem of list){}", code); } @@ -89,7 +91,7 @@ function printTips() tips.forEach((tip, i) => console.log(`Tip ${ i}:` +tip)); }"); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + var code = program.ToJavascriptString(s_customCompactWriterOptions, AstToJavascriptOptions.Default); Assert.Equal("let tips=[\"Click on any AST node with a '+' to expand it\",\"Hovering over a node highlights the \\\r\n corresponding location in the source code\",\"Shift click on an AST node to expand the whole subtree\"];function printTips(){tips.forEach((tip,i)=>console.log(`Tip ${i}:`+tip));}", code); } @@ -107,7 +109,7 @@ static get is() { } }"); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + var code = program.ToJavascriptString(s_customCompactWriterOptions, AstToJavascriptOptions.Default); Assert.Equal("export class aa extends HTMLElement{constructor(a,b){super(a);this._div=document.createElement('div');}static get is(){return'aa';}}", code); } @@ -283,7 +285,7 @@ public void ToJavascriptTest6() source = Regex.Replace(source, @"\r\n|\n\r|\n|\r", Environment.NewLine); var parser = new JavaScriptParser(source); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + var code = program.ToJavascriptString(s_customCompactWriterOptions, AstToJavascriptOptions.Default); Assert.Equal("function _createClass(Constructor,protoProps,staticProps){if(protoProps)_defineProperties(Constructor.prototype,protoProps);if(staticProps)_defineProperties(Constructor,staticProps);return Constructor;}", code); } @@ -294,7 +296,7 @@ public void ToJavascriptTest7() { }"); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + var code = program.ToJavascriptString(s_customCompactWriterOptions, AstToJavascriptOptions.Default); Assert.Equal("if((x?a.nodeName.toLowerCase()===f:1===a.nodeType)&&++d&&(p&&((i=(o=a[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]=[k,d]),a===e)){}", code); } @@ -313,7 +315,7 @@ class a extends b { } "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + var code = program.ToJavascriptString(s_customCompactWriterOptions, AstToJavascriptOptions.Default); Assert.Equal("class a extends b{constructor(){super();this.g=1;}q=1;r='cc';}", code); } @@ -324,7 +326,7 @@ public void ToJavascriptTest9() d = (s = (r = (i = (o = (a = c)[S] || (a[S] = {}))[a.uniqueID] || (o[a.uniqueID] = {}))[h] || [])[0] === k && r[1]) && r[2], a = s && c.childNodes[s]; "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + var code = program.ToJavascriptString(s_customCompactWriterOptions, AstToJavascriptOptions.Default); Assert.Equal("d=(s=(r=(i=(o=(a=c)[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===k&&r[1])&&r[2],a=s&&c.childNodes[s];", code); } @@ -335,7 +337,7 @@ public void ToJavascriptTest10() m = (z.document, !!v.documentElement && !!v.head && 'function' == typeof v.addEventListener && v.createElement, ~a.indexOf('MSIE') || a.indexOf('Trident/'), '___FONT_AWESOME___') "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + var code = program.ToJavascriptString(s_customCompactWriterOptions, AstToJavascriptOptions.Default); Assert.Equal("m=(z.document,!!v.documentElement&&!!v.head&&'function'==typeof v.addEventListener&&v.createElement,~a.indexOf('MSIE')||a.indexOf('Trident/'),'___FONT_AWESOME___');", code); } @@ -357,7 +359,7 @@ public void ToJavascriptTest11() }(); "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + var code = program.ToJavascriptString(s_customCompactWriterOptions, AstToJavascriptOptions.Default); Assert.Equal("var h=(c.navigator||{}).userAgent,a=void 0===h?'':h,z=c,v=l,m=(z.document,!!v.documentElement&&!!v.head&&'function'==typeof v.addEventListener&&v.createElement,~a.indexOf('MSIE')||a.indexOf('Trident/'),'___FONT_AWESOME___'),e=function(){try{return!0;}catch(c){return!1;}}();", code); } @@ -370,7 +372,7 @@ public void ToJavascriptTest12() } "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + var code = program.ToJavascriptString(s_customCompactWriterOptions, AstToJavascriptOptions.Default); Assert.Equal("var a={children:(b=O,'g'===b.tag?b.children:[b])};", code); } @@ -387,7 +389,7 @@ public void ToJavascriptTest13() } else h = e.HttpRequest.responseText; "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + var code = program.ToJavascriptString(s_customCompactWriterOptions, AstToJavascriptOptions.Default); Assert.Equal("if(e.IsWebService)if(h=e.HttpRequest.responseXML,'undefined'==typeof h)Trace.Write('Error: '+e.UniqueId+' data has no properties!'),m=!0;else try{h.setProperty('SelectionLanguage','XPath');}catch(l){Trace.Write('Error: data.setProperty(',SelectionLanguage,', ',XPath,') because '+l.message);}else h=e.HttpRequest.responseText;", code); } @@ -433,7 +435,7 @@ public void ToJavascriptTest15() h='M'+(+new Date).toString(36) "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + var code = program.ToJavascriptString(s_customCompactWriterOptions, AstToJavascriptOptions.Default); Assert.Equal("h='M'+(+new Date).toString(36);", code); } @@ -448,7 +450,7 @@ public void ToJavascriptTest16() }; "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + var code = program.ToJavascriptString(s_customCompactWriterOptions, AstToJavascriptOptions.Default); Assert.Equal("input.onchange=async e=>{const files=await readFiles(input.files,readMode);document.body.removeChild(input);resolve(files);};", code); } @@ -459,7 +461,7 @@ public void ToJavascriptTest17() export const Base = LegacyElementMixin(HTMLElement).prototype; "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + var code = program.ToJavascriptString(s_customCompactWriterOptions, AstToJavascriptOptions.Default); Assert.Equal("export const Base=LegacyElementMixin(HTMLElement).prototype;", code); } @@ -470,7 +472,7 @@ public void ToJavascriptTest18() let {is} = getIsExtends(element); "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + var code = program.ToJavascriptString(s_customCompactWriterOptions, AstToJavascriptOptions.Default); Assert.Equal("let{is}=getIsExtends(element);", code); } @@ -482,7 +484,7 @@ public void ToJavascriptTest19() (window['ShadyDOM'] && window['ShadyDOM']['wrap']) || (node => node); "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + var code = program.ToJavascriptString(s_customCompactWriterOptions, AstToJavascriptOptions.Default); Assert.Equal("export const wrap=window['ShadyDOM']&&window['ShadyDOM']['wrap']||(node=>node);", code); } @@ -492,7 +494,7 @@ public void ToJavascriptTest20() var parser = new JavaScriptParser(@" export {}"); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + var code = program.ToJavascriptString(s_customCompactWriterOptions, AstToJavascriptOptions.Default); Assert.Equal("export{};", code); } @@ -505,7 +507,7 @@ public void ToJavascriptTest21() })(); "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + var code = program.ToJavascriptString(s_customCompactWriterOptions, AstToJavascriptOptions.Default); Assert.Equal("(()=>{mutablePropertyChange=MutableData._mutablePropertyChange;})();", code); } @@ -519,7 +521,7 @@ public void ToJavascriptTest22() }()) "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + var code = program.ToJavascriptString(s_customCompactWriterOptions, AstToJavascriptOptions.Default); Assert.Equal("var Ol,jl=new(function(){var l,h,z;return l=c;}());", code); } @@ -536,7 +538,7 @@ public void ToJavascriptTest23() "); var program = parser.ParseScript(); - var code = AstToJavascript.ToJavascriptString(program, s_customCompactWriterFactory, JavascriptTextWriter.Options.Default, AstToJavascript.Options.Default); + var code = program.ToJavascriptString(s_customCompactWriterOptions, AstToJavascriptOptions.Default); Assert.Equal("[y,{[Symbol.iterator](){return b;},a:5}];", code); } diff --git a/test/Esprima.Tests/Fixtures.cs b/test/Esprima.Tests/Fixtures.cs index 44ca0d00..db59ece1 100644 --- a/test/Esprima.Tests/Fixtures.cs +++ b/test/Esprima.Tests/Fixtures.cs @@ -19,12 +19,12 @@ public class Fixtures private static string ParseAndFormat(SourceType sourceType, string source, ParserOptions parserOptions, Func parserFactory, - AstToJsonConverter.Factory converterFactory, AstToJson.Options conversionOptions) + AstToJsonOptions conversionOptions) { var parser = parserFactory(source, parserOptions); var program = sourceType == SourceType.Script ? (Program) parser.ParseScript() : parser.ParseModule(); - return program.ToJsonString(conversionOptions, indent: " ", converterFactory); + return program.ToJsonString(conversionOptions, indent: " "); } private static bool CompareTreesInternal(JObject actualJObject, JObject expectedJObject, FixtureMetadata metadata) @@ -78,13 +78,13 @@ private static void CompareTreesAndAssert(string actual, string expected, Fixtur [MemberData(nameof(SourceFiles), "Fixtures")] public void ExecuteTestCase(string fixture) { - var (parserOptions, parserFactory, converterFactory) = fixture.StartsWith("JSX") + var (parserOptions, parserFactory, conversionDefaultOptions) = fixture.StartsWith("JSX") ? (new JsxParserOptions(), (src, opts) => new JsxParser(src, (JsxParserOptions) opts), - (writer, options) => new JsxAstToJsonConverter(writer, options)) + JsxAstToJsonOptions.Default) : (new ParserOptions(), new Func((src, opts) => new JavaScriptParser(src, opts)), - new AstToJsonConverter.Factory((writer, options) => new AstToJsonConverter(writer, options))); + AstToJsonOptions.Default); parserOptions.Tokens = true; @@ -139,14 +139,16 @@ public void ExecuteTestCase(string fixture) parserOptions.AdaptRegexp = !metadata.IgnoresRegex; + var conversionOptions = metadata.CreateConversionOptions(conversionDefaultOptions); + #pragma warning disable 162 if (File.Exists(moduleFilePath)) { sourceType = SourceType.Module; expected = File.ReadAllText(moduleFilePath); - if (WriteBackExpectedTree && metadata.ConversionOptions.TestCompatibilityMode == AstToJson.TestCompatibilityMode.None) + if (WriteBackExpectedTree && conversionOptions.TestCompatibilityMode == AstToJsonTestCompatibilityMode.None) { - var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, converterFactory, metadata.ConversionOptions); + var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, conversionOptions); if (!CompareTrees(actual, expected, metadata)) File.WriteAllText(moduleFilePath, actual); } @@ -154,9 +156,9 @@ public void ExecuteTestCase(string fixture) else if (File.Exists(treeFilePath)) { expected = File.ReadAllText(treeFilePath); - if (WriteBackExpectedTree && metadata.ConversionOptions.TestCompatibilityMode == AstToJson.TestCompatibilityMode.None) + if (WriteBackExpectedTree && conversionOptions.TestCompatibilityMode == AstToJsonTestCompatibilityMode.None) { - var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, converterFactory, metadata.ConversionOptions); + var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, conversionOptions); if (!CompareTrees(actual, expected, metadata)) File.WriteAllText(treeFilePath, actual); } @@ -165,9 +167,9 @@ public void ExecuteTestCase(string fixture) { invalid = true; expected = File.ReadAllText(failureFilePath); - if (WriteBackExpectedTree && metadata.ConversionOptions.TestCompatibilityMode == AstToJson.TestCompatibilityMode.None) + if (WriteBackExpectedTree && conversionOptions.TestCompatibilityMode == AstToJsonTestCompatibilityMode.None) { - var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, converterFactory, metadata.ConversionOptions); + var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, conversionOptions); if (!CompareTrees(actual, expected, metadata)) File.WriteAllText(failureFilePath, actual); } @@ -187,7 +189,7 @@ public void ExecuteTestCase(string fixture) { parserOptions.Tolerant = true; - var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, converterFactory, metadata.ConversionOptions); + var actual = ParseAndFormat(sourceType, script, parserOptions, parserFactory, conversionOptions); CompareTreesAndAssert(actual, expected, metadata); } else @@ -195,7 +197,7 @@ public void ExecuteTestCase(string fixture) parserOptions.Tolerant = false; // TODO: check the accuracy of the message and of the location - Assert.Throws(() => ParseAndFormat(sourceType, script, parserOptions, parserFactory, converterFactory, metadata.ConversionOptions)); + Assert.Throws(() => ParseAndFormat(sourceType, script, parserOptions, parserFactory, conversionOptions)); } } @@ -226,11 +228,9 @@ internal static string GetFixturesPath() private sealed class FixtureMetadata { public static readonly FixtureMetadata Default = new FixtureMetadata( - AstToJson.Options.Default with - { - IncludingLineColumn = true, - IncludingRange = true - }, + testCompatibilityMode: AstToJsonTestCompatibilityMode.None, + includesLocation: true, + includesRange: true, includesLocationSource: false, ignoresRegex: false); @@ -267,29 +267,35 @@ public static Dictionary ReadMetadata() private static FixtureMetadata CreateFrom(HashSet flags) { - var conversionOptions = AstToJson.Options.Default with - { - IncludingLineColumn = flags.Contains("IncludesLocation"), - IncludingRange = flags.Contains("IncludesRange"), - TestCompatibilityMode = flags.Contains("EsprimaOrgFixture") ? AstToJson.TestCompatibilityMode.EsprimaOrg : AstToJson.TestCompatibilityMode.None - }; - - var includesLocationSource = flags.Contains("IncludesLocationSource"); - var ignoresRegex = flags.Contains("IgnoresRegex"); - - return new FixtureMetadata(conversionOptions, includesLocationSource, ignoresRegex); + return new FixtureMetadata( + testCompatibilityMode: flags.Contains("EsprimaOrgFixture") ? AstToJsonTestCompatibilityMode.EsprimaOrg : AstToJsonTestCompatibilityMode.None, + includesLocation: flags.Contains("IncludesLocation"), + includesRange: flags.Contains("IncludesRange"), + includesLocationSource: flags.Contains("IncludesLocationSource"), + ignoresRegex: flags.Contains("IgnoresRegex")); } - private FixtureMetadata(AstToJson.Options conversionOptions, bool includesLocationSource, bool ignoresRegex) + private FixtureMetadata(AstToJsonTestCompatibilityMode testCompatibilityMode, bool includesLocation, bool includesRange, bool includesLocationSource, bool ignoresRegex) { - ConversionOptions = conversionOptions; + TestCompatibilityMode = testCompatibilityMode; + IncludesLocation = includesLocation; + IncludesRange = includesRange; IncludesLocationSource = includesLocationSource; IgnoresRegex = ignoresRegex; } - public AstToJson.Options ConversionOptions { get; } + public AstToJsonTestCompatibilityMode TestCompatibilityMode { get; } + public bool IncludesLocation { get; } + public bool IncludesRange { get; } public bool IncludesLocationSource { get; } public bool IgnoresRegex { get; } + + public AstToJsonOptions CreateConversionOptions(AstToJsonOptions defaultOptions) => defaultOptions with + { + TestCompatibilityMode = TestCompatibilityMode, + IncludingLineColumn = IncludesLocation, + IncludingRange = IncludesRange, + }; } } }