using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; namespace MfGames.Nitride.Generators; /// /// Implements a source generator that creates Set* methods for the various /// properties that also returns the same object for purposes of chaining /// together calls. /// [Generator] public class WithPropertySourceGenerator : ISourceGenerator { public void Execute(GeneratorExecutionContext context) { // Get the generator infrastructure will create a receiver and // populate it we can retrieve the populated instance via the // context. var syntaxReceiver = (WithPropertySyntaxReceiver?)context.SyntaxReceiver; if (syntaxReceiver == null) { return; } // Report any messages. foreach (string? message in syntaxReceiver.Messages) { context.Information( MessageCode.Debug, Location.Create( "Temporary.g.cs", TextSpan.FromBounds(0, 0), new LinePositionSpan( new LinePosition(0, 0), new LinePosition(0, 0))), "Generating additional identifier code: {0}", message); } // If we didn't find anything, then there is nothing to do. if (syntaxReceiver.ClassList.Count == 0) { return; } // Go through each one. foreach (WithPropertyClass classInfo in syntaxReceiver.ClassList) { this.GenerateClassFile(context, classInfo); } } public void Initialize(GeneratorInitializationContext context) { // Register a factory that can create our custom syntax receiver context.RegisterForSyntaxNotifications( () => new WithPropertySyntaxReceiver(context)); } private void GenerateClassFile( GeneratorExecutionContext context, WithPropertyClass unit) { // Pull out some fields. ClassDeclarationSyntax? cds = unit.ClassDeclaration; // Create the partial class. StringBuilder buffer = new(); buffer.AppendLine("#nullable enable"); // Copy the using statements from the file. foreach (UsingDirectiveSyntax? uds in unit.UsingDirectiveList) { buffer.AppendLine(uds.ToString()); } buffer.AppendLine(); // Create the namespace. buffer.AppendLine($"namespace {unit.Namespace}"); buffer.AppendLine("{"); buffer.AppendLine($" public partial class {cds.Identifier}"); buffer.AppendLine(" {"); // Go through the properties of the namespace. IEnumerable properties = cds.Members .Where(m => m.Kind() == SyntaxKind.PropertyDeclaration) .Cast(); bool first = true; foreach (PropertyDeclarationSyntax pds in properties) { // See if we have a setter. bool found = pds.AccessorList?.Accessors.Any( x => x.Keyword.ToString() == "set") ?? false; if (!found) { continue; } // If we aren't first, then add a newline before it. if (first) { first = false; } else { buffer.AppendLine(); } // Write some documentation. buffer.AppendLine(" /// "); buffer.AppendLine( string.Format( " /// Sets the {0} value and returns the operation for chaining.", pds.Identifier.ToString())); buffer.AppendLine(" /// "); // We have the components for writing out a setter. buffer.AppendLine( string.Format( " public virtual {0} With{1}({2} value)", cds.Identifier, pds.Identifier, pds.Type)); buffer.AppendLine(" {"); buffer.AppendLine( string.Format(" this.{0} = value;", pds.Identifier)); buffer.AppendLine(" return this;"); buffer.AppendLine(" }"); } // Finish up the class. buffer.AppendLine(" }"); buffer.AppendLine("}"); // Create the source text and write out the file. var sourceText = SourceText.From(buffer.ToString(), Encoding.UTF8); context.AddSource(cds.Identifier + ".Generated.cs", sourceText); } }