namespace Celeste.Mod.Roslyn.ModLifecycleAttributes;
[Generator]
public class ModLifecycleAttributeSourceGenerator : IIncrementalGenerator
{
internal const string GeneratorNamespace = "Celeste.Mod.Roslyn.ModLifecycleAttributes";
private const string OnLoadAttributeTypeName = "OnLoadAttribute";
private const string OnLoadAttributeFqn = $"{GeneratorNamespace}.{OnLoadAttributeTypeName}";
private const string LifecycleMethodAttributeDefinitions =
$$"""
//
using System;
namespace {{GeneratorNamespace}};
[AttributeUsage(AttributeTargets.Method)]
[Microsoft.CodeAnalysis.Embedded]
internal class {{OnLoadAttributeTypeName}} : Attribute;
""";
private const string EmbeddedAttributeDefinition =
"""
using System;
namespace Microsoft.CodeAnalysis;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface | AttributeTargets.Enum | AttributeTargets.Delegate)]
internal sealed class EmbeddedAttribute : Attribute;
""";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput(static ctx =>
{
// ctx.AddEmbeddedAttributeDefinition() is available in compiler version 3.14.0, but we're using 3.12.0...
ctx.AddSource(
"EmbeddedAttribute.g.cs",
SourceText.From(EmbeddedAttributeDefinition, Encoding.UTF8));
// add the lifecycle attribute definition
ctx.AddSource(
"LifecycleMethodAttributes.g.cs",
SourceText.From(LifecycleMethodAttributeDefinitions, Encoding.UTF8));
});
var loadMethodsProvider = context.SyntaxProvider.ForAttributeWithMetadataName(
OnLoadAttributeFqn, FilterEligibleMethods, LifecycleMethod.AsLoad);
context.RegisterSourceOutput(
context.CompilationProvider.Combine(loadMethodsProvider.Collect()),
GenerateLoadMethodSource);
}
private static bool FilterEligibleMethods(SyntaxNode node, CancellationToken token)
=> node is MethodDeclarationSyntax;
private const string GeneratedOutputClass = "LifecycleMethods";
private static void GenerateLoadMethodSource(
SourceProductionContext source,
(Compilation, ImmutableArray) arg2)
{
Compilation compilation = arg2.Item1;
ImmutableArray loadMethods = arg2.Item2;
SimpleSourceGenerator sourceGen = new();
sourceGen.WriteLine($"internal static partial class {GeneratedOutputClass}");
using (sourceGen.UseCodeBlock())
{
sourceGen.WriteLine("public static void OnLoad()");
using (sourceGen.UseCodeBlock())
{
foreach (LifecycleMethod loadMethod in loadMethods)
{
SemanticModel model = compilation.GetSemanticModel(loadMethod.Declaration.SyntaxTree);
if (model.GetDeclaredSymbol(loadMethod.Declaration) is not { } methodSymbol)
continue;
// the fully qualified format includes the global:: alias
string typeFqn =
methodSymbol.ContainingType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
sourceGen.WriteLine($"{typeFqn}.{methodSymbol.Name}();");
}
}
}
source.AddSource("LifecycleMethods.Load.g.cs", sourceGen.Generate());
}
}