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/Nitride.Generators/WithPropertySourceGenerator.cs
Dylan R. E. Moonfire 78054ee2a7 feat: initial release
2021-09-07 00:15:45 -05:00

210 lines
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 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 = (SyntaxReceiver?)context.SyntaxReceiver;
if (syntaxReceiver == null)
{
return;
}
// Report any messages.
foreach (var 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.ClassesToAugment.Count == 0)
{
return;
}
// Go through each one.
foreach (var cds in syntaxReceiver.ClassesToAugment)
{
this.GenerateClassFile(context, cds);
}
}
public void Initialize(GeneratorInitializationContext context)
{
// Register a factory that can create our custom syntax receiver
context.RegisterForSyntaxNotifications(
() => new SyntaxReceiver(context));
}
private void GenerateClassFile(
GeneratorExecutionContext context,
ClassDeclarationSyntax cds)
{
// Get the namespace.
var nds = (NamespaceDeclarationSyntax?)cds.Parent;
if (nds == null)
{
return;
}
string? ns = nds.Name.ToString();
// Create the partial class.
StringBuilder buffer = new();
buffer.AppendLine("#nullable enable");
// Copy the using statements from the file.
if (nds.Parent is CompilationUnitSyntax cus)
{
foreach (var uds in cus.Usings)
{
buffer.AppendLine(uds.ToString());
}
buffer.AppendLine();
}
// Create the namespace.
buffer.AppendLine($"namespace {ns}");
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));
buffer.AppendLine(" /// </summary>");
// We have the components for writing out a setter.
buffer.AppendLine(
string.Format(
" public {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.
SourceText sourceText = SourceText.From(
buffer.ToString(),
Encoding.UTF8);
context.AddSource(cds.Identifier + ".Generated.cs", sourceText);
}
private class SyntaxReceiver : ISyntaxReceiver
{
private readonly GeneratorInitializationContext context;
public SyntaxReceiver(GeneratorInitializationContext context)
{
this.context = context;
this.ClassesToAugment = new List<ClassDeclarationSyntax>();
this.Messages = new List<string>();
}
public List<ClassDeclarationSyntax> ClassesToAugment { get; }
public List<string> Messages { get; }
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
// We only care about class declarations.
if (syntaxNode is not ClassDeclarationSyntax cds)
{
return;
}
// See if the class has our set properties attribute.
bool found = cds
.AttributeLists
.AsEnumerable()
.SelectMany(x => x.Attributes)
.Select(x => x.Name.ToString())
.Any(
x => x switch
{
"WithProperties" => true,
"WithPropertiesAttribute" => true,
_ => false,
});
if (found)
{
this.ClassesToAugment.Add(cds);
}
}
}
}
}