using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; namespace Nebula.SourceGenerators; [Generator] public class DependencyAutoGenerator : IIncrementalGenerator { private static readonly string ConstructGeneratorAttributeName = "ConstructGeneratorAttribute"; private static readonly string GeneratePropertyAttributeName = "GeneratePropertyAttribute"; public void Initialize(IncrementalGeneratorInitializationContext context) { GenerateAttribute(ConstructGeneratorAttributeName, "Class", context); GenerateAttribute(GeneratePropertyAttributeName, "Property", context); var provider = context.SyntaxProvider .CreateSyntaxProvider( (s, _) => s is ClassDeclarationSyntax, (ctx, _) => GetClassDeclarationForSourceGen(ctx)) .Where(t => t.reportAttributeFound) .Select((t, _) => t.Item1); context.RegisterSourceOutput(context.CompilationProvider.Combine(provider.Collect()), (ctx, t) => GenerateCode(ctx, t.Left, t.Right)); } private static void GenerateAttribute(string attributeName, string usage, IncrementalGeneratorInitializationContext context) { var attributeSourceCode = $@"// namespace SourceGen {{ [System.AttributeUsage(System.AttributeTargets.{usage})] public class {attributeName} : System.Attribute {{ }} }}"; context.RegisterPostInitializationOutput(ctx => ctx.AddSource( $"{attributeName}.g.cs", SourceText.From(attributeSourceCode, Encoding.UTF8))); } private static (ClassDeclarationSyntax, bool reportAttributeFound) GetClassDeclarationForSourceGen( GeneratorSyntaxContext context) { var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node; foreach (var attributeListSyntax in classDeclarationSyntax.AttributeLists) foreach (var attributeSyntax in attributeListSyntax.Attributes) { if (context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol is not IMethodSymbol attributeSymbol) continue; var attributeName = attributeSymbol.ContainingType.ToDisplayString(); if (attributeName == $"SourceGen.{ConstructGeneratorAttributeName}") return (classDeclarationSyntax, true); } return (classDeclarationSyntax, false); } private void GenerateCode(SourceProductionContext context, Compilation compilation, ImmutableArray classDeclarations) { foreach (var classDeclarationSyntax in classDeclarations) { var semanticModel = compilation.GetSemanticModel(classDeclarationSyntax.SyntaxTree); if (semanticModel.GetDeclaredSymbol(classDeclarationSyntax) is not INamedTypeSymbol classSymbol) continue; var namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); var className = classDeclarationSyntax.Identifier.Text; var defaultConstruct = $@"public {className}(){{}}"; var propertiesGenerated = GetProperties(classSymbol).ToList(); var constr = propertiesGenerated.Select(a => $"{a.Type.ToDisplayString()} g{a.Name}"); var body = propertiesGenerated.Select(a => $"this.{a.Name} = g{a.Name};"); if (!constr.Any()) defaultConstruct = ""; var code = $@"// using System; using System.Collections.Generic; namespace {namespaceName}; partial class {className} {{ {defaultConstruct} public {className}( {string.Join(",\n\t\t", constr)} ) : base(){{ {string.Join("\n\t\t", body)} if (Avalonia.Controls.Design.IsDesignMode) InitialiseInDesignMode(); else Initialise(); }} }} "; // Add the source code to the compilation. context.AddSource($"{className}.g.cs", SourceText.From(code, Encoding.UTF8)); } } private static IEnumerable GetProperties(INamedTypeSymbol classSymbol) { return classSymbol.GetMembers().OfType().Where(a => HasAttribute(a, $"SourceGen.{GeneratePropertyAttributeName}")); } private static bool HasAttribute(ISymbol type, string attributeName) { foreach (var attribute in type.GetAttributes()) if (attribute.AttributeClass?.ToDisplayString() == attributeName) return true; return false; } }