This repository has been archived on 2023-02-02. You can view files and clone it, but cannot push or open issues or pull requests.
mfgames-nitride-cil/src/MfGames.Nitride.Generators/WithPropertySourceGenerator.cs

149 lines
4.7 KiB
C#

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;
/// <summary>
/// Implements a source generator that creates Set* methods for the various
/// properties that also returns the same object for purposes of chaining
/// together calls.
/// </summary>
[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<PropertyDeclarationSyntax> properties = cds.Members
.Where(m => m.Kind() == SyntaxKind.PropertyDeclaration)
.Cast<PropertyDeclarationSyntax>();
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(" /// <summary>");
buffer.AppendLine(
string.Format(
" /// Sets the {0} value and returns the operation for chaining.",
pds.Identifier.ToString()));
buffer.AppendLine(" /// </summary>");
// 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);
}
}