/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Tools/CompilerGeneratorTools/Source/CSharpSyntaxGenerator/SourceWriter.cs
1 931 строка
78 KB
Joey Robichaud
Remove dotnet-format tool from dotnet-tools.json (#84728)
04 авг 2026, 19:58
Не верифицирован
04 авг 2026, 19:58
331e174
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. #nullable disable using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; using System.Xml; namespace CSharpSyntaxGenerator { internal class SourceWriter : AbstractFileWriter { private SourceWriter(TextWriter writer, Tree tree, CancellationToken cancellationToken = default) : base(writer, tree, cancellationToken) { } public static void WriteMain(TextWriter writer, Tree tree, CancellationToken cancellationToken = default) => new SourceWriter(writer, tree, cancellationToken).WriteMain(); public static void WriteInternal(TextWriter writer, Tree tree, CancellationToken cancellationToken = default) => new SourceWriter(writer, tree, cancellationToken).WriteInternal(); public static void WriteSyntax(TextWriter writer, Tree tree, CancellationToken cancellationToken = default) => new SourceWriter(writer, tree, cancellationToken).WriteSyntax(); private static string QuoteString(string value) => "@\"" + value.Replace("\"", "\"\"") + "\""; private void WriteExperimentalIfNeeded(string experimentalUrl) { if (!string.IsNullOrEmpty(experimentalUrl)) { WriteLine($"[Experimental(global::Microsoft.CodeAnalysis.RoslynExperiments.PreviewLanguageFeatureApi, UrlFormat = {QuoteString(experimentalUrl)})]"); } } /// <summary> /// Returns the experimental URL that should annotate a factory method whose signature /// includes the given fields. Node-level and kind-level marks apply regardless of which /// fields appear in the signature; field-level marks only apply if that field actually /// appears as a parameter. This prevents the shorthand factory from being annotated as /// experimental when it doesn't take the experimental field as a parameter. /// </summary> private static string GetFactorySignatureExperimentalUrl(Node node, IEnumerable<Field> signatureFields) { if (!string.IsNullOrEmpty(node.ExperimentalUrl)) { return node.ExperimentalUrl; } foreach (var field in signatureFields) { if (!string.IsNullOrEmpty(field.ExperimentalUrl)) { return field.ExperimentalUrl; } } if (node.Kinds.Count <= 1) { return null; } var experimentalKindCount = 0; string experimentalUrl = null; foreach (var kind in node.Kinds) { if (!string.IsNullOrEmpty(kind.ExperimentalUrl)) { experimentalKindCount++; experimentalUrl ??= kind.ExperimentalUrl; } } // We use this to flag the transitional factory shape introduced when adding a new // syntax kind, while avoiding broad experimental annotation of long-standing APIs. return experimentalKindCount == node.Kinds.Count - 1 ? experimentalUrl : null; } /// <summary> /// Used by Update: its signature always includes every field on the node, so any /// field-level ExperimentalUrl always applies. Kind additions don't change Update's /// signature, so kind-level marks are intentionally not propagated here. /// </summary> private static string GetFieldExperimentalUrl(Node node) { foreach (var field in node.Fields) { if (!string.IsNullOrEmpty(field.ExperimentalUrl)) { return field.ExperimentalUrl; } } return null; } private void WriteFileHeader() { WriteLine("// <auto-generated />"); WriteLine(); WriteLine("#nullable enable"); WriteLine(); WriteLine("using System;"); WriteLine("using System.Collections.Generic;"); WriteLine("using System.Diagnostics.CodeAnalysis;"); WriteLine("using Microsoft.CodeAnalysis.Syntax.InternalSyntax;"); WriteLine("using Roslyn.Utilities;"); WriteLine("using CoreSyntax = Microsoft.CodeAnalysis.Syntax.InternalSyntax;"); WriteLine(); } private void WriteInternal() { WriteFileHeader(); WriteLine("namespace Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax;"); this.WriteGreenTypes(); this.WriteGreenVisitors(); this.WriteGreenRewriter(); this.WriteContextualGreenFactories(); this.WriteStaticGreenFactories(); } private void WriteSyntax() { WriteFileHeader(); WriteLine("namespace Microsoft.CodeAnalysis.CSharp.Syntax;"); this.WriteRedTypes(); } private void WriteMain() { WriteFileHeader(); WriteLine("namespace Microsoft.CodeAnalysis.CSharp;"); WriteLine(); WriteLine("using System.Diagnostics.CodeAnalysis;"); WriteLine("using Microsoft.CodeAnalysis.CSharp.Syntax;"); this.WriteRedVisitors(); this.WriteRedRewriter(); this.WriteRedFactories(); } private void WriteGreenTypes() { var nodes = Tree.Types.Where(n => n is not PredefinedNode).ToList(); foreach (var node in nodes) { WriteLine(); this.WriteGreenType(node); } } private void WriteGreenType(TreeType node) { WriteComment(node.TypeComment, ""); if (node is AbstractNode) { var nd = (AbstractNode)node; WriteLine($"internal abstract partial class {node.Name} : {node.Base}"); OpenBlock(); // ctor with diagnostics and annotations WriteLine($"internal {node.Name}(SyntaxKind kind, DiagnosticInfo[]? diagnostics, SyntaxAnnotation[]? annotations)"); WriteLine(" : base(kind, diagnostics, annotations)"); OpenBlock(); if (node.Name == "DirectiveTriviaSyntax") { WriteLine("SetFlags(NodeFlags.ContainsDirectives);"); } CloseBlock(); WriteLine(); // ctor without diagnostics and annotations WriteLine($"internal {node.Name}(SyntaxKind kind)"); WriteLine(" : base(kind)"); OpenBlock(); if (node.Name == "DirectiveTriviaSyntax") { WriteLine("SetFlags(NodeFlags.ContainsDirectives);"); } CloseBlock(); var valueFields = nd.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList(); var nodeFields = nd.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList(); foreach (var field in nodeFields) { if (IsNodeOrNodeList(field.Type)) { WriteLine(); WriteComment(field.PropertyComment, ""); if (IsSeparatedNodeList(field.Type) || IsNodeList(field.Type)) { WriteLine($"public abstract {(IsNew(field) ? "new " : "")}CoreSyntax.{field.Type} {field.Name} {{ get; }}"); } else { WriteLine($"public abstract {(IsNew(field) ? "new " : "")}{(GetFieldType(field, green: true))} {field.Name} {{ get; }}"); } } } foreach (var field in valueFields) { WriteLine(); WriteComment(field.PropertyComment, ""); WriteLine($"public abstract {(IsNew(field) ? "new " : "")}{field.Type} {field.Name} {{ get; }}"); } CloseBlock(); } else if (node is Node) { var nd = (Node)node; WriteLine($"internal sealed partial class {node.Name} : {node.Base}"); OpenBlock(); var valueFields = nd.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList(); var nodeFields = nd.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList(); foreach (var field in nodeFields) { var type = GetFieldType(field, green: true); WriteLine($"internal readonly {type} {CamelCase(field.Name)};"); } foreach (var field in valueFields) { WriteLine($"internal readonly {field.Type} {CamelCase(field.Name)};"); } // write constructor with diagnostics and annotations WriteLine(); Write($"internal {node.Name}(SyntaxKind kind"); WriteGreenNodeConstructorArgs(nodeFields, valueFields); WriteLine(", DiagnosticInfo[]? diagnostics, SyntaxAnnotation[]? annotations)"); WriteLine(" : base(kind, diagnostics, annotations)"); OpenBlock(); WriteCtorBody(nd, valueFields, nodeFields); CloseBlock(); // write constructor with async WriteLine(); Write($"internal {node.Name}(SyntaxKind kind"); WriteGreenNodeConstructorArgs(nodeFields, valueFields); WriteLine(", SyntaxFactoryContext context)"); WriteLine(" : base(kind)"); OpenBlock(); WriteLine("this.SetFactoryContext(context);"); WriteCtorBody(nd, valueFields, nodeFields); CloseBlock(); // write constructor without diagnostics and annotations WriteLine(); Write($"internal {node.Name}(SyntaxKind kind"); WriteGreenNodeConstructorArgs(nodeFields, valueFields); WriteLine(")"); WriteLine(" : base(kind)"); OpenBlock(); WriteCtorBody(nd, valueFields, nodeFields); CloseBlock(); WriteLine(); // property accessors foreach (var field in nodeFields) { WriteComment(field.PropertyComment, ""); if (IsNodeList(field.Type)) { var type = $"CoreSyntax.{field.Type}"; WriteLine($"public {OverrideOrNewModifier(field)}{type} {field.Name} => new {type}(this.{CamelCase(field.Name)});"); } else if (IsSeparatedNodeList(field.Type)) { var type = $"CoreSyntax.{field.Type}"; WriteLine($"public {OverrideOrNewModifier(field)}{type} {field.Name} => new {type}(new CoreSyntax.SyntaxList<CSharpSyntaxNode>(this.{CamelCase(field.Name)}));"); } else if (field.Type == "SyntaxNodeOrTokenList") { var type = $"CoreSyntax.SyntaxList<CSharpSyntaxNode>"; WriteLine($"public {OverrideOrNewModifier(field)}{type} {field.Name} => new {type}(this.{CamelCase(field.Name)});"); } else { WriteLine($"public {OverrideOrNewModifier(field)}{(GetFieldType(field, green: true))} {field.Name} => this.{CamelCase(field.Name)};"); } } foreach (var field in valueFields) { WriteComment(field.PropertyComment, ""); WriteLine($"public {OverrideOrNewModifier(field)}{field.Type} {field.Name} => this.{CamelCase(field.Name)};"); } // GetSlot WriteLine(); Write("internal override GreenNode? GetSlot(int index)"); if (nodeFields.Count == 0) { WriteLine(" => null;"); } else if (nodeFields.Count == 1) { WriteLine(); Indent(); WriteLine($"=> index == 0 ? this.{CamelCase(nodeFields[0].Name)} : null;"); Unindent(); } else { WriteLine(); Indent(); WriteLine("=> index switch"); OpenBlock(); for (int i = 0, n = nodeFields.Count; i < n; i++) { var field = nodeFields[i]; WriteLine($"{i} => this.{CamelCase(field.Name)},"); } WriteLine("_ => null,"); CloseBlock(";"); Unindent(); } WriteLine(); WriteLine($"internal override SyntaxNode CreateRed(SyntaxNode? parent, int position) => new CSharp.Syntax.{node.Name}(this, parent, position);"); this.WriteGreenAcceptMethods(nd); this.WriteGreenUpdateMethod(nd); this.WriteSetDiagnostics(nd); this.WriteSetAnnotations(nd); CloseBlock(); } } private void WriteGreenNodeConstructorArgs(List<Field> nodeFields, List<Field> valueFields) { foreach (var field in nodeFields) { Write($", {(GetFieldType(field, green: true))} {CamelCase(field.Name)}"); } foreach (var field in valueFields) { Write($", {field.Type} {CamelCase(field.Name)}"); } } private void WriteCtorBody(Node node, List<Field> valueFields, List<Field> nodeFields) { if (node.Name == "AttributeSyntax") { WriteLine("SetFlags(NodeFlags.ContainsAttributes);"); } // constructor body WriteLine($"this.SlotCount = {nodeFields.Count};"); foreach (var field in nodeFields) { if (IsAnyList(field.Type) || IsOptional(field)) { WriteLine($"if ({CamelCase(field.Name)} != null)"); OpenBlock(); WriteLine($"this.AdjustFlagsAndWidth({CamelCase(field.Name)});"); WriteLine($"this.{CamelCase(field.Name)} = {CamelCase(field.Name)};"); CloseBlock(); } else { WriteLine($"this.AdjustFlagsAndWidth({CamelCase(field.Name)});"); WriteLine($"this.{CamelCase(field.Name)} = {CamelCase(field.Name)};"); } } foreach (var field in valueFields) { WriteLine($"this.{CamelCase(field.Name)} = {CamelCase(field.Name)};"); } } private void WriteSetAnnotations(Node node) { WriteLine(); WriteLine("internal override GreenNode SetAnnotations(SyntaxAnnotation[]? annotations)"); Write($" => new {node.Name}("); Write(CommaJoin( "this.Kind", node.Fields.Select(f => $"this.{CamelCase(f.Name)}"), "GetDiagnostics()", "annotations")); WriteLine(");"); } private void WriteSetDiagnostics(Node node) { WriteLine(); WriteLine("internal override GreenNode SetDiagnostics(DiagnosticInfo[]? diagnostics)"); Write($" => new {node.Name}("); Write(CommaJoin( "this.Kind", node.Fields.Select(f => $"this.{CamelCase(f.Name)}"), "diagnostics", "GetAnnotations()")); WriteLine(");"); } private void WriteGreenAcceptMethods(Node node) { WriteLine(); WriteLine($"public override void Accept(CSharpSyntaxVisitor visitor) => visitor.Visit{StripPost(node.Name, "Syntax")}(this);"); WriteLine($"public override TResult Accept<TResult>(CSharpSyntaxVisitor<TResult> visitor) => visitor.Visit{StripPost(node.Name, "Syntax")}(this);"); } private void WriteGreenVisitors() { WriteGreenVisitor(withResult: true); WriteGreenVisitor(withResult: false); } private void WriteGreenVisitor(bool withResult) { var nodes = Tree.Types.Where(n => n is not PredefinedNode).ToList(); WriteLine(); WriteLine("internal partial class CSharpSyntaxVisitor" + (withResult ? "<TResult>" : "")); OpenBlock(); foreach (var node in nodes.OfType<Node>()) { WriteLine($"public virtual {(withResult ? "TResult" : "void")} Visit{StripPost(node.Name, "Syntax")}({node.Name} node) => this.DefaultVisit(node);"); } CloseBlock(); } private void WriteGreenUpdateMethod(Node node) { WriteLine(); Write($"public {node.Name} Update("); Write(CommaJoin(node.Fields.Select(f => { var type = f.Type == "SyntaxNodeOrTokenList" ? "CoreSyntax.SyntaxList<CSharpSyntaxNode>" : f.Type == "SyntaxTokenList" ? "CoreSyntax.SyntaxList<SyntaxToken>" : IsNodeList(f.Type) ? "CoreSyntax." + f.Type : IsSeparatedNodeList(f.Type) ? "CoreSyntax." + f.Type : f.Type; return $"{type} {CamelCase(f.Name)}"; }))); WriteLine(")"); OpenBlock(); Write("if ("); int nCompared = 0; foreach (var field in node.Fields) { if (IsDerivedOrListOfDerived("SyntaxNode", field.Type) || IsDerivedOrListOfDerived("SyntaxToken", field.Type) || field.Type == "SyntaxNodeOrTokenList") { if (nCompared > 0) Write(" || "); Write($"{CamelCase(field.Name)} != this.{field.Name}"); nCompared++; } } if (nCompared > 0) { WriteLine(")"); OpenBlock(); Write($"var newNode = SyntaxFactory.{StripPost(node.Name, "Syntax")}("); Write(CommaJoin( node.Kinds.Count > 1 ? "this.Kind" : "", node.Fields.Select(f => CamelCase(f.Name)))); WriteLine(");"); WriteLine("var diags = GetDiagnostics();"); WriteLine("if (diags?.Length > 0)"); WriteLine(" newNode = newNode.WithDiagnosticsGreen(diags);"); WriteLine("var annotations = GetAnnotations();"); WriteLine("if (annotations?.Length > 0)"); WriteLine(" newNode = newNode.WithAnnotationsGreen(annotations);"); WriteLine("return newNode;"); CloseBlock(); } WriteLine(); WriteLine("return this;"); CloseBlock(); } private void WriteGreenRewriter() { var nodes = Tree.Types.Where(n => n is not PredefinedNode).ToList(); WriteLine(); WriteLine("internal partial class CSharpSyntaxRewriter : CSharpSyntaxVisitor<CSharpSyntaxNode>"); OpenBlock(); int nWritten = 0; foreach (var node in nodes.OfType<Node>()) { var nodeFields = node.Fields.Where(nd => IsNodeOrNodeList(nd.Type)).ToList(); if (nWritten > 0) WriteLine(); nWritten++; WriteLine($"public override CSharpSyntaxNode Visit{StripPost(node.Name, "Syntax")}({node.Name} node)"); Indent(); if (nodeFields.Count == 0) { WriteLine("=> node;"); } else { Write("=> node.Update("); Write(CommaJoin(node.Fields.Select(f => { if (IsAnyList(f.Type)) return $"VisitList(node.{f.Name})"; else if (IsNode(f.Type)) return $"({f.Type})Visit(node.{f.Name})"; else return $"node.{f.Name}"; }))); WriteLine(");"); } Unindent(); } CloseBlock(); } private void WriteContextualGreenFactories() { var nodes = Tree.Types.Where(n => n is not PredefinedNode and not AbstractNode).ToList(); WriteLine(); WriteLine("internal partial class ContextAwareSyntax"); OpenBlock(); WriteLine(); WriteLine("private SyntaxFactoryContext context;"); WriteLine(); WriteLine("public ContextAwareSyntax(SyntaxFactoryContext context)"); WriteLine(" => this.context = context;"); WriteGreenFactories(nodes, withSyntaxFactoryContext: true); CloseBlock(); } private void WriteStaticGreenFactories() { var nodes = Tree.Types.Where(n => n is not PredefinedNode and not AbstractNode).ToList(); WriteLine(); WriteLine("internal static partial class SyntaxFactory"); OpenBlock(); WriteGreenFactories(nodes); CloseBlock(); } private void WriteGreenFactories(List<TreeType> nodes, bool withSyntaxFactoryContext = false) { foreach (var node in nodes.OfType<Node>()) { WriteLine(); this.WriteGreenFactory(node, withSyntaxFactoryContext); } } private void WriteGreenFactory(Node nd, bool withSyntaxFactoryContext = false) { var valueFields = nd.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList(); var nodeFields = nd.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList(); Write($"public {(withSyntaxFactoryContext ? "" : "static ")}{nd.Name} {StripPost(nd.Name, "Syntax")}("); WriteGreenFactoryParameters(nd); WriteLine(")"); OpenBlock(); // validate kind if (nd.Kinds.Count >= 2) { WriteLine("switch (kind)"); OpenBlock(); var kinds = nd.Kinds.Distinct().ToList(); foreach (var kind in kinds) { WriteLine($"case SyntaxKind.{kind.Name}:{(kind == kinds.Last() ? " break;" : "")}"); } WriteLine("default: throw new ArgumentException(nameof(kind));"); CloseBlock(); } // validate parameters WriteLineWithoutIndent("#if DEBUG"); foreach (var field in nodeFields) { var pname = CamelCase(field.Name); if (!IsAnyList(field.Type) && !IsOptional(field)) { WriteLine($"if ({CamelCase(field.Name)} == null) throw new ArgumentNullException(nameof({CamelCase(field.Name)}));"); } if (field.Type == "SyntaxToken" && field.Kinds != null && field.Kinds.Count > 0) { if (IsOptional(field)) { WriteLine($"if ({CamelCase(field.Name)} != null)"); OpenBlock(); } if (field.Kinds.Count == 1 && !IsOptional(field)) { WriteLine($"if ({pname}.Kind != SyntaxKind.{field.Kinds[0].Name}) throw new ArgumentException(nameof({pname}));"); } else { WriteLine($"switch ({pname}.Kind)"); OpenBlock(); var kinds = field.Kinds.Distinct().ToList(); //we need to check for Kind=None as well as node == null because that's what the red factory will pass if (IsOptional(field)) { kinds.Add(new Kind { Name = "None" }); } foreach (var kind in kinds) { WriteLine($"case SyntaxKind.{kind.Name}:{(kind == kinds.Last() ? " break;" : "")}"); } WriteLine($"default: throw new ArgumentException(nameof({pname}));"); CloseBlock(); } if (IsOptional(field)) { CloseBlock(); } } } WriteLineWithoutIndent("#endif"); if (nd.Name != "SkippedTokensTriviaSyntax" && nd.Name != "DocumentationCommentTriviaSyntax" && nd.Name != "IncompleteMemberSyntax" && nd.Name != "AttributeSyntax" && valueFields.Count + nodeFields.Count <= 3) { //int hash; //var cached = SyntaxNodeCache.TryGetNode((int)SyntaxKind.IdentifierName, identifier, this.context, out hash); //if (cached != null) return (IdentifierNameSyntax)cached; //var result = new IdentifierNameSyntax(SyntaxKind.IdentifierName, identifier, this.context); //if (hash >= 0) //{ // SyntaxNodeCache.AddNode(result, hash); //} //return result; WriteLine(); //int hash; WriteLine("int hash;"); //SyntaxNode cached = SyntaxNodeCache.TryGetNode(SyntaxKind.IdentifierName, identifier, this.context, out hash); if (withSyntaxFactoryContext) { Write("var cached = CSharpSyntaxNodeCache.TryGetNode((int)"); } else { Write("var cached = SyntaxNodeCache.TryGetNode((int)"); } WriteCtorArgList(nd, withSyntaxFactoryContext, valueFields, nodeFields); WriteLine(", out hash);"); // if (cached != null) return (IdentifierNameSyntax)cached; WriteLine($"if (cached != null) return ({nd.Name})cached;"); WriteLine(); //var result = new IdentifierNameSyntax(SyntaxKind.IdentifierName, identifier); Write($"var result = new {nd.Name}("); WriteCtorArgList(nd, withSyntaxFactoryContext, valueFields, nodeFields); WriteLine(");"); //if (hash >= 0) WriteLine("if (hash >= 0)"); //{ OpenBlock(); // SyntaxNodeCache.AddNode(result, hash); WriteLine("SyntaxNodeCache.AddNode(result, hash);"); //} CloseBlock(); WriteLine(); //return result; WriteLine("return result;"); } else { WriteLine(); Write($"return new {nd.Name}("); WriteCtorArgList(nd, withSyntaxFactoryContext, valueFields, nodeFields); WriteLine(");"); } CloseBlock(); } private void WriteGreenFactoryParameters(Node nd) { Write(CommaJoin( nd.Kinds.Count > 1 ? "SyntaxKind kind" : "", nd.Fields.Select(f => { var type = f.Type switch { "SyntaxNodeOrTokenList" => "CoreSyntax.SyntaxList<CSharpSyntaxNode>", _ when IsSeparatedNodeList(f.Type) || IsNodeList(f.Type) => $"CoreSyntax.{f.Type}", _ => GetFieldType(f, green: true), }; return $"{type} {CamelCase(f.Name)}"; }))); } private void WriteCtorArgList(Node nd, bool withSyntaxFactoryContext, List<Field> valueFields, List<Field> nodeFields) { Write(CommaJoin( nd.Kinds.Count == 1 ? $"SyntaxKind.{nd.Kinds[0].Name}" : "kind", nodeFields.Select(f => f.Type == "SyntaxList<SyntaxToken>" || IsAnyList(f.Type) ? $"{CamelCase(f.Name)}.Node" : CamelCase(f.Name)), // values are at end valueFields.Select(f => CamelCase(f.Name)), withSyntaxFactoryContext ? "this.context" : "")); } private void WriteRedTypes() { var nodes = Tree.Types.Where(n => n is not PredefinedNode).ToList(); foreach (var node in nodes) { WriteLine(); this.WriteRedType(node); } } private List<Field> GetNodeOrNodeListFields(TreeType node) => node is AbstractNode an ? an.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList() : node is Node nd ? nd.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList() : new List<Field>(); private void WriteRedType(TreeType node) { WriteComment(node.TypeComment, ""); if (node is AbstractNode) { var nd = (AbstractNode)node; WriteExperimentalIfNeeded(node.ExperimentalUrl); WriteLine($"public abstract partial class {node.Name} : {node.Base}"); OpenBlock(); WriteLine($"internal {node.Name}(InternalSyntax.CSharpSyntaxNode green, SyntaxNode? parent, int position)"); WriteLine(" : base(green, parent, position)"); OpenBlock(); CloseBlock(); var valueFields = nd.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList(); var nodeFields = GetNodeOrNodeListFields(nd); foreach (var field in nodeFields) { if (IsNodeOrNodeList(field.Type)) { //red SyntaxLists can't contain tokens, so we switch to SyntaxTokenList var fieldType = GetRedFieldType(field); WriteLine(); WriteComment(field.PropertyComment, ""); WriteExperimentalIfNeeded(field.ExperimentalUrl); WriteLine($"{"public"} abstract {(IsNew(field) ? "new " : "")}{fieldType} {field.Name} {{ get; }}"); WriteLine($"public {node.Name} With{field.Name}({fieldType} {CamelCase(field.Name)}) => With{field.Name}Core({CamelCase(field.Name)});"); WriteLine($"internal abstract {node.Name} With{field.Name}Core({fieldType} {CamelCase(field.Name)});"); if (IsAnyList(field.Type)) { var argType = GetElementType(field.Type); WriteLine(); WriteLine($"public {node.Name} Add{field.Name}(params {argType}[] items) => Add{field.Name}Core(items);"); WriteLine($"internal abstract {node.Name} Add{field.Name}Core(params {argType}[] items);"); } else { var referencedNode = TryGetNodeForNestedList(field); if (referencedNode != null) { foreach (var referencedNodeField in referencedNode.Fields) { if (IsAnyList(referencedNodeField.Type)) { var argType = GetElementType(referencedNodeField.Type); WriteLine(); WriteLine($"public {node.Name} Add{StripPost(field.Name, "Opt")}{referencedNodeField.Name}(params {argType}[] items) => Add{StripPost(field.Name, "Opt")}{referencedNodeField.Name}Core(items);"); WriteLine($"internal abstract {node.Name} Add{StripPost(field.Name, "Opt")}{referencedNodeField.Name}Core(params {argType}[] items);"); } } } } } } foreach (var field in valueFields) { WriteLine(); WriteComment(field.PropertyComment, ""); WriteExperimentalIfNeeded(field.ExperimentalUrl); WriteLine($"{"public"} abstract {(IsNew(field) ? "new " : "")}{field.Type} {field.Name} {{ get; }}"); } var baseType = GetTreeType(node.Base); if (baseType != null) { var baseNodeFields = GetNodeOrNodeListFields(baseType); if (baseNodeFields.Count > 0) { WriteLine(); } foreach (var baseField in baseNodeFields) { WriteLine($"public new {node.Name} With{baseField.Name}({GetRedFieldType(baseField)} {CamelCase(baseField.Name)}) => ({node.Name})With{baseField.Name}Core({CamelCase(baseField.Name)});"); } foreach (var baseField in baseNodeFields) { if (IsAnyList(baseField.Type)) { var argType = GetElementType(baseField.Type); WriteLine(); WriteLine($"public new {node.Name} Add{baseField.Name}(params {argType}[] items) => ({node.Name})Add{baseField.Name}Core(items);"); } else { var referencedNode = TryGetNodeForNestedList(baseField); if (referencedNode != null) { // look for list members... foreach (var referencedNodeField in referencedNode.Fields) { if (IsAnyList(referencedNodeField.Type)) { var argType = GetElementType(referencedNodeField.Type); WriteLine(); WriteLine($"public new {baseType.Name} Add{StripPost(baseField.Name, "Opt")}{referencedNodeField.Name}(params {argType}[] items) => Add{StripPost(baseField.Name, "Opt")}{referencedNodeField.Name}Core(items);"); } } } } } } CloseBlock(); } else if (node is Node) { var nd = (Node)node; WriteComment($"<remarks>"); WriteComment($"<para>This node is associated with the following syntax kinds:</para>"); WriteComment($"<list type=\"bullet\">"); foreach (var kind in nd.Kinds) { WriteComment($"<item><description><see cref=\"SyntaxKind.{kind.Name}\"/></description></item>"); } WriteComment($"</list>"); WriteComment($"</remarks>"); WriteExperimentalIfNeeded(node.ExperimentalUrl); WriteLine($"public sealed partial class {node.Name} : {node.Base}"); OpenBlock(); var valueFields = nd.Fields.Where(n => !IsNodeOrNodeList(n.Type)).ToList(); var nodeFields = nd.Fields.Where(n => IsNodeOrNodeList(n.Type)).ToList(); foreach (var field in nodeFields) { if (field.Type is not "SyntaxToken" and not "SyntaxList<SyntaxToken>") { if (IsSeparatedNodeList(field.Type) || field.Type == "SyntaxNodeOrTokenList") { WriteLine($"private SyntaxNode? {CamelCase(field.Name)};"); } else { var type = GetFieldType(field, green: false); WriteLine($"private {type} {CamelCase(field.Name)};"); } } } // write constructor WriteLine(); WriteLine($"internal {node.Name}(InternalSyntax.CSharpSyntaxNode green, SyntaxNode? parent, int position)"); WriteLine(" : base(green, parent, position)"); OpenBlock(); bool hasValidate = HasValidate(node); if (hasValidate) { WriteLine("Validate();"); } CloseBlock(); WriteLine(); if (hasValidate) { WriteLine("private partial void Validate();"); WriteLine(); } // property accessors for (int i = 0, n = nodeFields.Count; i < n; i++) { var field = nodeFields[i]; if (field.Type == "SyntaxToken") { WriteComment(field.PropertyComment, ""); WriteExperimentalIfNeeded(field.ExperimentalUrl); Write($"public {OverrideOrNewModifier(field)}{GetRedPropertyType(field)} {field.Name}"); if (IsOptional(field)) { WriteLine(); OpenBlock(); WriteLine("get"); OpenBlock(); WriteLine($"var slot = ((Syntax.InternalSyntax.{node.Name})this.Green).{CamelCase(field.Name)};"); WriteLine($"return slot != null ? new SyntaxToken(this, slot, {GetChildPosition(i)}, {GetChildIndex(i)}) : default;"); CloseBlock(); CloseBlock(); } else { WriteLine($" => new SyntaxToken(this, ((InternalSyntax.{node.Name})this.Green).{CamelCase(field.Name)}, {GetChildPosition(i)}, {GetChildIndex(i)});"); } } else if (field.Type == "SyntaxList<SyntaxToken>") { WriteComment(field.PropertyComment, ""); WriteExperimentalIfNeeded(field.ExperimentalUrl); WriteLine($"public {OverrideOrNewModifier(field)}SyntaxTokenList {field.Name}"); OpenBlock(); WriteLine("get"); OpenBlock(); WriteLine($"var slot = this.Green.GetSlot({i});"); WriteLine($"return slot != null ? new SyntaxTokenList(this, slot, {GetChildPosition(i)}, {GetChildIndex(i)}) : default;"); CloseBlock(); CloseBlock(); } else { WriteComment(field.PropertyComment, ""); WriteExperimentalIfNeeded(field.ExperimentalUrl); Write($"public {OverrideOrNewModifier(field)}{GetRedPropertyType(field)} {field.Name}"); if (IsNodeList(field.Type)) { WriteLine($" => new {field.Type}(GetRed(ref this.{CamelCase(field.Name)}, {i}));"); } else if (IsSeparatedNodeList(field.Type)) { WriteLine(); OpenBlock(); WriteLine("get"); OpenBlock(); WriteLine($"var red = GetRed(ref this.{CamelCase(field.Name)}, {i});"); WriteLine($"return red != null ? new {field.Type}(red, {GetChildIndex(i)}) : default;"); CloseBlock(); CloseBlock(); } else if (field.Type == "SyntaxNodeOrTokenList") { throw new InvalidOperationException("field cannot be a random SyntaxNodeOrTokenList"); } else { var suffix = IsOptional(field) ? "" : "!"; if (i == 0) { WriteLine($" => GetRedAtZero(ref this.{CamelCase(field.Name)}){suffix};"); } else { WriteLine($" => GetRed(ref this.{CamelCase(field.Name)}, {i}){suffix};"); } } } WriteLine(); } foreach (var field in valueFields) { WriteComment(field.PropertyComment, ""); WriteExperimentalIfNeeded(field.ExperimentalUrl); WriteLine($"{"public"} {OverrideOrNewModifier(field)}{field.Type} {field.Name} => ((InternalSyntax.{node.Name})this.Green).{field.Name};"); WriteLine(); } { //GetNodeSlot forces creation of a red node. Write("internal override SyntaxNode? GetNodeSlot(int index)"); var relevantNodes = nodeFields.Select((field, index) => (field, index)) .Where(t => t.field.Type is not "SyntaxToken" and not "SyntaxList<SyntaxToken>"); if (!relevantNodes.Any()) { WriteLine(" => null;"); } else if (relevantNodes.Count() == 1) { var (field, index) = relevantNodes.Single(); var whenTrue = index == 0 ? $"GetRedAtZero(ref this.{CamelCase(field.Name)})" : $"GetRed(ref this.{CamelCase(field.Name)}, {index})"; var suffix = IsOptional(field) ? "" : "!"; WriteLine($" => index == {index} ? {whenTrue}{suffix} : null;"); } else { WriteLine(); Indent(); WriteLine("=> index switch"); OpenBlock(); foreach (var (field, index) in relevantNodes) { var suffix = IsOptional(field) ? "" : "!"; if (index == 0) { WriteLine($"{index} => GetRedAtZero(ref this.{CamelCase(field.Name)}){suffix},"); } else { WriteLine($"{index} => GetRed(ref this.{CamelCase(field.Name)}, {index}){suffix},"); } } WriteLine("_ => null,"); CloseBlock(";"); Unindent(); } } WriteLine(); { //GetCachedSlot returns a red node if we have it. Write("internal override SyntaxNode? GetCachedSlot(int index)"); var relevantNodes = nodeFields.Select((field, index) => (field, index)) .Where(t => t.field.Type is not "SyntaxToken" and not "SyntaxList<SyntaxToken>"); if (!relevantNodes.Any()) { WriteLine(" => null;"); } else if (relevantNodes.Count() == 1) { var (field, index) = relevantNodes.Single(); WriteLine($" => index == {index} ? this.{CamelCase(field.Name)} : null;"); } else { WriteLine(); Indent(); WriteLine("=> index switch"); OpenBlock(); foreach (var (field, index) in relevantNodes) { WriteLine($"{index} => this.{CamelCase(field.Name)},"); } WriteLine("_ => null,"); CloseBlock(";"); Unindent(); } } this.WriteRedAcceptMethods(nd); this.WriteRedUpdateMethod(nd); this.WriteRedWithMethods(nd); this.WriteRedListHelperMethods(nd); CloseBlock(); } } private static bool HasValidate(TreeType node) { return node.HasValidate != null && string.Equals(node.HasValidate, "true", StringComparison.OrdinalIgnoreCase); } private string GetRedFieldType(Field field) { if (field.Type == "SyntaxList<SyntaxToken>") return "SyntaxTokenList"; if (IsOptional(field) && IsNode(field.Type) && field.Type != "SyntaxToken") return field.Type + "?"; return field.Type; } private string GetChildPosition(int i) => i == 0 ? "Position" : "GetChildPosition(" + i + ")"; private string GetChildIndex(int i) => i == 0 ? "0" : "GetChildIndex(" + i + ")"; private void WriteRedAcceptMethods(Node node) { WriteLine(); WriteRedAcceptMethod(node, false); WriteRedAcceptMethod(node, true); } private void WriteRedAcceptMethod(Node node, bool genericResult) { string genericArgs = genericResult ? "<TResult>" : ""; WriteLine($"public override {(genericResult ? "TResult?" : "void")} Accept{genericArgs}(CSharpSyntaxVisitor{genericArgs} visitor){(genericResult ? " where TResult : default" : "")} => visitor.Visit{StripPost(node.Name, "Syntax")}(this);"); } private void WriteRedVisitors() { WriteRedVisitor(genericResult: true); WriteRedVisitor(genericResult: false); } private void WriteRedVisitor(bool genericResult) { string genericArgs = genericResult ? "<TResult>" : ""; var nodes = Tree.Types.Where(n => n is not PredefinedNode).ToList(); WriteLine(); WriteLine("public partial class CSharpSyntaxVisitor" + genericArgs); OpenBlock(); int nWritten = 0; foreach (var node in nodes.OfType<Node>()) { if (nWritten > 0) WriteLine(); nWritten++; WriteComment($"<summary>Called when the visitor visits a {node.Name} node.</summary>"); WriteExperimentalIfNeeded(node.ExperimentalUrl); WriteLine($"public virtual {(genericResult ? "TResult?" : "void")} Visit{StripPost(node.Name, "Syntax")}({node.Name} node) => this.DefaultVisit(node);"); } CloseBlock(); } private void WriteRedUpdateMethod(Node node) { WriteLine(); WriteExperimentalIfNeeded(node.ExperimentalUrl ?? GetFieldExperimentalUrl(node)); Write($"public {node.Name} Update("); Write(CommaJoin( node.Fields.Select(f => $"{GetRedPropertyType(f)} {CamelCase(f.Name)}"))); WriteLine(")"); OpenBlock(); Write("if ("); int nCompared = 0; foreach (var field in node.Fields) { if (IsDerivedOrListOfDerived("SyntaxNode", field.Type) || IsDerivedOrListOfDerived("SyntaxToken", field.Type) || field.Type == "SyntaxNodeOrTokenList") { if (nCompared > 0) Write(" || "); Write($"{CamelCase(field.Name)} != this.{field.Name}"); nCompared++; } } if (nCompared > 0) { WriteLine(")"); OpenBlock(); Write($"var newNode = SyntaxFactory.{StripPost(node.Name, "Syntax")}("); Write(CommaJoin( node.Kinds.Count > 1 ? "this.Kind()" : "", node.Fields.Select(f => CamelCase(f.Name)))); WriteLine(");"); WriteLine("var annotations = GetAnnotations();"); WriteLine("return annotations?.Length > 0 ? newNode.WithAnnotations(annotations) : newNode;"); CloseBlock(); } WriteLine(); WriteLine("return this;"); CloseBlock(); } private void WriteRedWithMethods(Node node) { foreach (var field in node.Fields) { var type = this.GetRedPropertyType(field); if (field == node.Fields.First()) { WriteLine(); } var isNew = false; if (IsOverride(field)) { var (baseType, baseField) = GetHighestBaseTypeWithField(node, field.Name); if (baseType != null) { Write($"internal override {baseType.Name} With{field.Name}Core({GetRedPropertyType(baseField)} {CamelCase(field.Name)}) => With{field.Name}({CamelCase(field.Name)}"); if (baseField.Type != "SyntaxToken" && IsOptional(baseField) && !IsOptional(field)) { Write($" ?? throw new ArgumentNullException(nameof({CamelCase(field.Name)}))"); } WriteLine(");"); isNew = true; } } WriteExperimentalIfNeeded(field.ExperimentalUrl); Write( $"public{(isNew ? " new " : " ")}{node.Name} With{StripPost(field.Name, "Opt")}({type} {CamelCase(field.Name)})" + " => Update("); // call update inside each setter Write(CommaJoin(node.Fields.Select(f => f == field ? CamelCase(f.Name) : $"this.{f.Name}"))); WriteLine(");"); } } private (TreeType type, Field field) GetHighestBaseTypeWithField(TreeType node, string name) { TreeType bestType = null; Field bestField = null; for (var current = node; current != null; current = TryGetBaseType(current)) { var fields = GetNodeOrNodeListFields(current); var field = fields.FirstOrDefault(f => f.Name == name); if (field != null) { bestType = current; bestField = field; } } return (bestType, bestField); } private TreeType TryGetBaseType(TreeType node) => node is AbstractNode an ? GetTreeType(an.Base) : node is Node n ? GetTreeType(n.Base) : null; private void WriteRedListHelperMethods(Node node) { var wroteNewLine = false; foreach (var field in node.Fields) { if (IsAnyList(field.Type)) { if (!wroteNewLine) { WriteLine(); wroteNewLine = true; } // write list helper methods for list properties WriteRedListHelperMethods(node, field); } else { var referencedNode = TryGetNodeForNestedList(field); if (referencedNode != null) { // look for list members... foreach (var referencedNodeField in referencedNode.Fields) { if (IsAnyList(referencedNodeField.Type)) { if (!wroteNewLine) { WriteLine(); wroteNewLine = true; } WriteRedNestedListHelperMethods(node, field, referencedNode, referencedNodeField); } } } } } } private Node TryGetNodeForNestedList(Field field) { Node referencedNode = GetNode(field.Type); if (referencedNode != null && (!IsOptional(field) || RequiredFactoryArgumentCount(referencedNode) == 0)) { return referencedNode; } return null; } private void WriteRedListHelperMethods(Node node, Field field) { var argType = GetElementType(field.Type); var isNew = false; if (IsOverride(field)) { var (baseType, baseField) = GetHighestBaseTypeWithField(node, field.Name); if (baseType != null) { var baseArgType = GetElementType(baseField.Type); WriteLine($"internal override {baseType.Name} Add{field.Name}Core(params {baseArgType}[] items) => Add{field.Name}(items);"); isNew = true; } } WriteExperimentalIfNeeded(field.ExperimentalUrl); WriteLine($"public{(isNew ? " new " : " ")}{node.Name} Add{field.Name}(params {argType}[] items) => With{StripPost(field.Name, "Opt")}(this.{field.Name}.AddRange(items));"); } private void WriteRedNestedListHelperMethods(Node node, Field field, Node referencedNode, Field referencedNodeField) { var argType = GetElementType(referencedNodeField.Type); var isNew = false; if (IsOverride(field)) { var (baseType, _) = GetHighestBaseTypeWithField(node, field.Name); if (baseType != null) { WriteLine($"internal override {baseType.Name} Add{StripPost(field.Name, "Opt")}{referencedNodeField.Name}Core(params {argType}[] items) => Add{StripPost(field.Name, "Opt")}{referencedNodeField.Name}(items);"); isNew = true; } } // AddBaseListTypes WriteExperimentalIfNeeded(field.ExperimentalUrl); Write($"public{(isNew ? " new " : " ")}{node.Name} Add{StripPost(field.Name, "Opt")}{referencedNodeField.Name}(params {argType}[] items)"); if (IsOptional(field)) { WriteLine(); OpenBlock(); var factoryName = StripPost(referencedNode.Name, "Syntax"); var varName = StripPost(CamelCase(field.Name), "Opt"); WriteLine($"var {varName} = this.{field.Name} ?? SyntaxFactory.{factoryName}();"); WriteLine($"return With{StripPost(field.Name, "Opt")}({varName}.With{StripPost(referencedNodeField.Name, "Opt")}({varName}.{referencedNodeField.Name}.AddRange(items)));"); CloseBlock(); } else { WriteLine($" => With{StripPost(field.Name, "Opt")}(this.{field.Name}.With{StripPost(referencedNodeField.Name, "Opt")}(this.{field.Name}.{referencedNodeField.Name}.AddRange(items)));"); } } private void WriteRedRewriter() { var nodes = Tree.Types.Where(n => n is not PredefinedNode).ToList(); WriteLine(); WriteLine("public partial class CSharpSyntaxRewriter : CSharpSyntaxVisitor<SyntaxNode?>"); OpenBlock(); int nWritten = 0; foreach (var node in nodes.OfType<Node>()) { if (nWritten > 0) WriteLine(); nWritten++; WriteExperimentalIfNeeded(node.ExperimentalUrl); WriteLine($"public override SyntaxNode? Visit{StripPost(node.Name, "Syntax")}({node.Name} node)"); if (node.Fields.Count == 0) { WriteLine(" => node;"); } else { Write(" => node.Update("); Write(CommaJoin(node.Fields.Select(f => { if (IsNodeOrNodeList(f.Type)) { if (IsAnyList(f.Type)) return $"VisitList(node.{f.Name})"; else if (f.Type == "SyntaxToken") return $"VisitToken(node.{f.Name})"; else if (IsOptional(f)) return $"({(GetFieldType(f, green: false))})Visit(node.{f.Name})"; else return $"({(GetFieldType(f, green: false))})Visit(node.{f.Name}) ?? throw new ArgumentNullException(\"{CamelCase(f.Name)}\")"; } return $"node.{f.Name}"; }))); WriteLine(");"); } } CloseBlock(); } private void WriteRedFactories() { var nodes = Tree.Types.Where(n => n is not PredefinedNode and not AbstractNode).OfType<Node>().ToList(); WriteLine(); WriteLine("public static partial class SyntaxFactory"); OpenBlock(); foreach (var node in nodes) { this.WriteRedFactory(node); bool skipConvenienceFactories = node.SkipConvenienceFactories != null && string.Equals(node.SkipConvenienceFactories, "true", StringComparison.OrdinalIgnoreCase); if (!skipConvenienceFactories) { this.WriteRedFactoryWithNoAutoCreatableTokens(node); this.WriteRedMinimalFactory(node); this.WriteRedMinimalFactory(node, withStringNames: true); } this.WriteKindConverters(node); } CloseBlock(); } protected bool CanBeAutoCreated(Node node, Field field) => IsAutoCreatableToken(node, field) || IsAutoCreatableNode(field); private bool IsAutoCreatableToken(Node node, Field field) { return field.Type == "SyntaxToken" && field.Kinds != null && ((field.Kinds.Count == 1 && field.Kinds[0].Name != "IdentifierToken" && !field.Kinds[0].Name.EndsWith("LiteralToken", StringComparison.Ordinal)) || (field.Kinds.Count > 1 && field.Kinds.Count == node.Kinds.Count)); } private bool IsAutoCreatableNode(Field field) { var referencedNode = GetNode(field.Type); return (referencedNode != null && RequiredFactoryArgumentCount(referencedNode) == 0); } private bool IsRequiredFactoryField(Node node, Field field) { return (!IsOptional(field) && !IsAnyList(field.Type) && !CanBeAutoCreated(node, field)) || IsValueField(field); } private bool IsValueField(Field field) { return !IsNodeOrNodeList(field.Type); } private int RequiredFactoryArgumentCount(Node nd, bool includeKind = true) { int count = 0; // kind must be specified in factory if (nd.Kinds.Count > 1 && includeKind) { count++; } foreach (var field in nd.Fields) { if (IsRequiredFactoryField(nd, field)) { count++; } } return count; } private int OptionalFactoryArgumentCount(Node nd) { int count = 0; foreach (var field in nd.Fields) { if (IsOptional(field) || CanBeAutoCreated(nd, field) || IsAnyList(field.Type)) { count++; } } return count; } // full factory signature with nothing optional private void WriteRedFactory(Node nd) { this.WriteLine(); var valueFields = nd.Fields.Where(n => IsValueField(n)).ToList(); var nodeFields = nd.Fields.Where(n => !IsValueField(n)).ToList(); WriteComment($"<summary>Creates a new {nd.Name} instance.</summary>"); WriteExperimentalIfNeeded(GetFactorySignatureExperimentalUrl(nd, nd.Fields)); Write($"public static {nd.Name} {StripPost(nd.Name, "Syntax")}("); WriteRedFactoryParameters(nd); WriteLine(")"); OpenBlock(); // validate kinds if (nd.Kinds.Count >= 2) { WriteLine("switch (kind)"); OpenBlock(); var kinds = nd.Kinds.Distinct().ToList(); foreach (var kind in kinds) { WriteLine($"case SyntaxKind.{kind.Name}:{(kind == kinds.Last() ? " break;" : "")}"); } WriteLine("default: throw new ArgumentException(nameof(kind));"); CloseBlock(); } // validate parameters foreach (var field in nodeFields) { var pname = CamelCase(field.Name); if (field.Type == "SyntaxToken") { var fieldKinds = GetKindsOfFieldOrNearestParent(nd, field); if (fieldKinds != null && fieldKinds.Count > 0) { var kinds = fieldKinds.ToList(); if (IsOptional(field)) { kinds.Add(new Kind { Name = "None" }); } if (kinds.Count == 1) { WriteLine($"if ({pname}.Kind() != SyntaxKind.{kinds[0].Name}) throw new ArgumentException(nameof({pname}));"); } else { WriteLine($"switch ({pname}.Kind())"); OpenBlock(); foreach (var kind in kinds) { WriteLine($"case SyntaxKind.{kind.Name}:{(kind == kinds.Last() ? " break;" : "")}"); } WriteLine($"default: throw new ArgumentException(nameof({pname}));"); CloseBlock(); } } } else if (!IsAnyList(field.Type) && !IsOptional(field)) { WriteLine($"if ({CamelCase(field.Name)} == null) throw new ArgumentNullException(nameof({CamelCase(field.Name)}));"); } } Write($"return ({nd.Name})Syntax.InternalSyntax.SyntaxFactory.{StripPost(nd.Name, "Syntax")}("); Write(CommaJoin( nd.Kinds.Count > 1 ? "kind" : "", nodeFields.Select(f => { if (f.Type == "SyntaxToken") { if (IsOptional(f)) return $"(Syntax.InternalSyntax.SyntaxToken?){CamelCase(f.Name)}.Node"; else // We know the GreenNode is not null because it gets a type check earlier in the generated method return $"(Syntax.InternalSyntax.SyntaxToken){CamelCase(f.Name)}.Node!"; } else if (f.Type == "SyntaxList<SyntaxToken>") return $"{CamelCase(f.Name)}.Node.ToGreenList<Syntax.InternalSyntax.SyntaxToken>()"; else if (IsNodeList(f.Type)) return $"{CamelCase(f.Name)}.Node.ToGreenList<Syntax.InternalSyntax.{GetElementType(f.Type)}>()"; else if (IsSeparatedNodeList(f.Type)) return $"{CamelCase(f.Name)}.Node.ToGreenSeparatedList<Syntax.InternalSyntax.{GetElementType(f.Type)}>()"; else if (f.Type == "SyntaxNodeOrTokenList") return $"{CamelCase(f.Name)}.Node.ToGreenList<Syntax.InternalSyntax.CSharpSyntaxNode>()"; else if (IsOptional(f)) return $"{CamelCase(f.Name)} == null ? null : (Syntax.InternalSyntax.{f.Type}){CamelCase(f.Name)}.Green"; else return $"(Syntax.InternalSyntax.{f.Type}){CamelCase(f.Name)}.Green"; }), // values are at end valueFields.Select(f => CamelCase(f.Name)))); WriteLine(").CreateRed();"); CloseBlock(); } private void WriteRedFactoryParameters(Node nd) { Write(CommaJoin( nd.Kinds.Count > 1 ? "SyntaxKind kind" : "", nd.Fields.Select(f => $"{this.GetRedPropertyType(f)} {CamelCase(f.Name)}"))); } private string GetRedPropertyType(Field field) { if (field.Type == "SyntaxList<SyntaxToken>") return "SyntaxTokenList"; if (IsOptional(field) && IsNode(field.Type) && field.Type != "SyntaxToken") return field.Type + "?"; return field.Type; } private string GetDefaultValue(Node nd, Field field) { System.Diagnostics.Debug.Assert(!IsRequiredFactoryField(nd, field)); if (IsOptional(field) || IsAnyList(field.Type)) { var type = GetRedPropertyType(field); return type == "SyntaxTokenList" ? "default(SyntaxTokenList)" : "default"; } else if (field.Type == "SyntaxToken") { // auto construct token? if (field.Kinds.Count == 1) { return $"SyntaxFactory.Token(SyntaxKind.{field.Kinds[0].Name})"; } else { return $"SyntaxFactory.Token(Get{StripPost(nd.Name, "Syntax")}{StripPost(field.Name, "Opt")}Kind(kind))"; } } else { var referencedNode = GetNode(field.Type); return $"SyntaxFactory.{StripPost(referencedNode.Name, "Syntax")}()"; } } // Writes Get<Property>Kind() methods for converting between node kind and member token kinds... private void WriteKindConverters(Node nd) { foreach (var field in nd.Fields) { if (field.Type == "SyntaxToken" && CanBeAutoCreated(nd, field) && field.Kinds.Count > 1) { WriteLine(); WriteLine($"private static SyntaxKind Get{StripPost(nd.Name, "Syntax")}{StripPost(field.Name, "Opt")}Kind(SyntaxKind kind)"); Indent(); WriteLine("=> kind switch"); OpenBlock(); for (int k = 0; k < field.Kinds.Count; k++) { var nKind = nd.Kinds[k]; var pKind = field.Kinds[k]; WriteLine($"SyntaxKind.{nKind.Name} => SyntaxKind.{pKind.Name},"); } WriteLine("_ => throw new ArgumentOutOfRangeException(),"); CloseBlock(";"); Unindent(); } } } private IEnumerable<Field> DetermineRedFactoryWithNoAutoCreatableTokenFields(Node nd) { return nd.Fields.Where(f => !IsAutoCreatableToken(nd, f)); } // creates a factory without auto-creatable token arguments private void WriteRedFactoryWithNoAutoCreatableTokens(Node nd) { var nAutoCreatableTokens = nd.Fields.Count(f => IsAutoCreatableToken(nd, f)); if (nAutoCreatableTokens == 0) return; // already handled by general factory var factoryWithNoAutoCreatableTokenFields = new HashSet<Field>(DetermineRedFactoryWithNoAutoCreatableTokenFields(nd)); var minimalFactoryFields = DetermineMinimalFactoryFields(nd); if (minimalFactoryFields != null && factoryWithNoAutoCreatableTokenFields.SetEquals(minimalFactoryFields)) { return; // will be handled in minimal factory case } this.WriteLine(); WriteComment($"<summary>Creates a new {nd.Name} instance.</summary>"); WriteExperimentalIfNeeded(GetFactorySignatureExperimentalUrl(nd, nd.Fields.Where(factoryWithNoAutoCreatableTokenFields.Contains))); Write($"public static {nd.Name} {StripPost(nd.Name, "Syntax")}("); Write(CommaJoin( nd.Kinds.Count > 1 ? "SyntaxKind kind" : "", nd.Fields.Where(factoryWithNoAutoCreatableTokenFields.Contains).Select( f => $"{GetRedPropertyType(f)} {CamelCase(f.Name)}"))); WriteLine(")"); Write($" => SyntaxFactory.{StripPost(nd.Name, "Syntax")}("); Write(CommaJoin( nd.Kinds.Count > 1 ? "kind" : "", nd.Fields.Select(f => factoryWithNoAutoCreatableTokenFields.Contains(f) ? CamelCase(f.Name) : GetDefaultValue(nd, f)))); WriteLine(");"); } private Field DetermineMinimalOptionalField(Node nd) { // first if there is a single list, then choose the list because it would not have been optional int listCount = nd.Fields.Count(f => IsAnyNodeList(f.Type) && !IsAttributeOrModifiersList(f)); if (listCount == 1) { return nd.Fields.First(f => IsAnyNodeList(f.Type) && !IsAttributeOrModifiersList(f)); } else { // otherwise, if there is a single optional node, use that.. int nodeCount = nd.Fields.Count(f => IsNode(f.Type) && f.Type != "SyntaxToken"); if (nodeCount == 1) { return nd.Fields.First(f => IsNode(f.Type) && f.Type != "SyntaxToken"); } else { return null; } } } private static bool IsAttributeOrModifiersList(Field f) { return f.Name is "AttributeLists" or "Modifiers"; } private IEnumerable<Field> DetermineMinimalFactoryFields(Node nd) { // special case to allow a single optional argument if there would have been no arguments // and we can determine a best single argument. Field allowOptionalField = null; var optionalCount = OptionalFactoryArgumentCount(nd); if (optionalCount == 0) { return null; // no fields... } var requiredCount = RequiredFactoryArgumentCount(nd, includeKind: false); if (requiredCount == 0 && optionalCount > 1) { allowOptionalField = DetermineMinimalOptionalField(nd); } return nd.Fields.Where(f => IsRequiredFactoryField(nd, f) || allowOptionalField == f); } // creates a factory with only the required arguments (everything else is defaulted) private void WriteRedMinimalFactory(Node nd, bool withStringNames = false) { var optionalCount = OptionalFactoryArgumentCount(nd); if (optionalCount == 0) return; // already handled w/ general factory method var minimalFactoryFields = new HashSet<Field>(DetermineMinimalFactoryFields(nd)); if (withStringNames && minimalFactoryFields.Count(f => IsRequiredFactoryField(nd, f) && CanAutoConvertFromString(f)) == 0) return; // no string-name overload necessary this.WriteLine(); var hasOptional = minimalFactoryFields.Any(f => !IsRequiredFactoryField(nd, f)); var hasAttributeOrModifiersList = nd.Fields.Any(f => IsAttributeOrModifiersList(f)); if (hasOptional && hasAttributeOrModifiersList) { WriteLineWithoutIndent("#pragma warning disable RS0027"); } WriteComment($"<summary>Creates a new {nd.Name} instance.</summary>"); WriteExperimentalIfNeeded(GetFactorySignatureExperimentalUrl(nd, nd.Fields.Where(minimalFactoryFields.Contains))); Write($"public static {nd.Name} {StripPost(nd.Name, "Syntax")}("); Write(CommaJoin( nd.Kinds.Count > 1 ? "SyntaxKind kind" : "", nd.Fields.Where(minimalFactoryFields.Contains).Select(f => { var type = GetRedPropertyType(f); if (IsRequiredFactoryField(nd, f)) { if (withStringNames && CanAutoConvertFromString(f)) type = "string"; return $"{type} {CamelCase(f.Name)}"; } else { if (IsNode(f.Type) && !IsOptional(f) && f.Type != "SyntaxToken") type += "?"; return $"{type} {CamelCase(f.Name)} = default"; } }))); WriteLine(")"); Write($" => SyntaxFactory.{StripPost(nd.Name, "Syntax")}("); Write(CommaJoin( nd.Kinds.Count > 1 ? "kind" : "", nd.Fields.Select(f => { if (minimalFactoryFields.Contains(f)) { if (IsRequiredFactoryField(nd, f)) { if (withStringNames && CanAutoConvertFromString(f)) return $"{GetStringConverterMethod(f)}({CamelCase(f.Name)})"; else return CamelCase(f.Name); } else { if (IsOptional(f) || IsAnyList(f.Type)) return CamelCase(f.Name); else return $"{CamelCase(f.Name)} ?? {GetDefaultValue(nd, f)}"; } } return GetDefaultValue(nd, f); }))); WriteLine(");"); if (hasOptional && hasAttributeOrModifiersList) { WriteLineWithoutIndent("#pragma warning restore RS0027"); } } private bool CanAutoConvertFromString(Field field) { return IsIdentifierToken(field) || IsIdentifierNameSyntax(field); } private bool IsIdentifierToken(Field field) { return field.Type == "SyntaxToken" && field.Kinds != null && field.Kinds.Count == 1 && field.Kinds[0].Name == "IdentifierToken"; } private bool IsIdentifierNameSyntax(Field field) { return field.Type == "IdentifierNameSyntax"; } private string GetStringConverterMethod(Field field) { if (IsIdentifierToken(field)) { return "SyntaxFactory.Identifier"; } else if (IsIdentifierNameSyntax(field)) { return "SyntaxFactory.IdentifierName"; } else { throw new NotSupportedException(); } } /// <summary> /// Anything inside a <Comment> tag gets written out (escaping untouched) as the /// XML doc comment. Line breaks will be preserved. /// </summary> private void WriteComment(string comment) { if (comment != null) { var lines = comment.Split(new string[] { "\r", "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries); foreach (var line in lines.Where(l => !string.IsNullOrWhiteSpace(l))) { WriteLine($"/// {line.TrimStart()}"); } } } /// <summary> /// Anything inside a <Comment> tag gets written out (escaping untouched) as the /// XML doc comment. Line breaks will be preserved. /// </summary> private void WriteComment(Comment comment, string indent) { if (comment != null) { foreach (XmlElement element in comment.Body) { string[] lines = element.OuterXml.Split(new string[] { "\r", "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries); foreach (string line in lines.Where(l => !string.IsNullOrWhiteSpace(l))) { WriteLine($"{indent}/// {line.TrimStart()}"); } } } } } }