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()); } }