BizHawk/ExternalProjects/BizHawk.SrcGen.SettingsUtil/DefaultSetterGenerator.cs

118 lines
3.1 KiB
C#

namespace BizHawk.SrcGen.SettingsUtil;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
[Generator]
public class DefaultSetterGenerator : ISourceGenerator
{
public class SyntaxReceiver : ISyntaxContextReceiver
{
public readonly List<(ClassDeclarationSyntax, SemanticModel)> ClassDeclarations = new();
public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
{
if (context.Node is ClassDeclarationSyntax cds)
{
ClassDeclarations.Add((cds, context.SemanticModel));
}
}
}
public void Initialize(GeneratorInitializationContext context)
=> context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
private static void CreateDefaultSetter(StringBuilder source, INamespaceOrTypeSymbol symbol)
{
var props = symbol
.GetMembers()
.Where(m => m.Kind == SymbolKind.Property)
.ToImmutableArray();
source.Append($@"
public static void SetDefaultValues({symbol} settings)
{{");
foreach (var prop in props)
{
var defaultValueAttribute = prop
.GetAttributes()
.FirstOrDefault(
a => a.AttributeClass?.Name == "DefaultValueAttribute");
var ctorArgs = defaultValueAttribute?.ConstructorArguments;
if (!ctorArgs.HasValue)
{
continue;
}
switch (ctorArgs.Value.Length)
{
case 1:
// this single arg is just the value assigned to the default value
var arg = ctorArgs.Value[0];
// a bit lame, but it'll work
// TODO: do we even want to handle arrays? we don't even have arrays in default values...
var converionStr = arg.Kind == TypedConstantKind.Array
? $"new {arg.Type}[] " // new T[]
: ""; // do we need a cast (i.e. (T)) here? probably not?
source.Append($@"
settings.{prop.Name} = {converionStr}{arg.ToCSharpString()};");
break;
case 2:
// first arg is the type, the second arg is a string which converts it
source.Append($@"
settings.{prop.Name} = ({ctorArgs.Value[0].Value})System.ComponentModel.TypeDescriptor
.GetConverter({ctorArgs.Value[0].ToCSharpString()})
.ConvertFromInvariantString({ctorArgs.Value[1].ToCSharpString()});");
break;
}
}
source.Append(@"
}
");
}
public void Execute(GeneratorExecutionContext context)
{
if (context.SyntaxContextReceiver is not SyntaxReceiver syntaxReceiver)
{
return;
}
// Generated source code
var source = new StringBuilder(@"
namespace BizHawk.Common
{
public static partial class SettingsUtil
{");
foreach (var (cds, semanticModel) in syntaxReceiver.ClassDeclarations)
{
if (cds.AttributeLists.SelectMany(e => e.Attributes)
.Any(e => e.Name.NormalizeWhitespace().ToFullString() == "CoreSettings"))
{
var symbol = semanticModel.GetDeclaredSymbol(cds);
if (symbol is not null) // probably never happens?
{
CreateDefaultSetter(source, symbol);
}
}
}
source.Append(@"
}
}");
// Add the source code to the compilation
context.AddSource("DefaultSetters.g.cs", source.ToString());
}
}