using System; 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; using Microsoft.CodeAnalysis.Text; using SourceGenerators.Utils; namespace SourceGenerators; [Generator] public class VContainerAutoRegistrationGenerator : IIncrementalGenerator { private const string Attribute = "CodeBase.CompositionRoot.Helpers.VContainerAutoRegistrationAttribute"; private static Dictionary preparedInterfaces = new() { {"Scellecs.Morpeh.IInitializer", (TargetLifetime.Singleton, RegisterType.Self)}, }; public void Initialize(IncrementalGeneratorInitializationContext context) { // Шаг 1: Собираем все классы-кандидаты var classProvider = context.SyntaxProvider .CreateSyntaxProvider( predicate: static (s, _) => IsClassCandidate(s), transform: static (ctx, _) => GetClassSymbol(ctx)) .Where(static m => m is not null); // Шаг 2: Комбинируем классы с информацией о компиляции var combinedProvider = classProvider .Combine(context.CompilationProvider); // Шаг 3: Обрабатываем каждый класс и находим подходящий интерфейс var processedClasses = combinedProvider .Select(static (combined, token) => ProcessClass(combined.Left, combined.Right)) .Where(static m => m is not null); // Шаг 4: Группируем регистрации по интерфейсам для эффективной кодогенерации var groupedRegistrations = processedClasses .Collect() .Select(static (registrations, _) => registrations .GroupBy(r => r.InterfaceSymbol.ToDisplayString()) .Select(g => new InterfaceRegistrationGroup { InterfaceFullName = g.Key, InterfaceSymbol = g.First().InterfaceSymbol, ClassRegistrations = g.ToList(), Lifetime = g.First().Lifetime, RegisterType = g.First().RegisterType }) .ToList()); // Шаг 5: Генерируем код context.RegisterSourceOutput(groupedRegistrations, static (spc, source) => GenerateRegistrationCode(source, spc)); } private static bool IsClassCandidate(SyntaxNode node) { return node is ClassDeclarationSyntax classDecl && !classDecl.IsAbstract() && !classDecl.IsStatic(); } private static ClassInfo? GetClassSymbol(GeneratorSyntaxContext context) { var classDeclaration = (ClassDeclarationSyntax)context.Node; var classSymbol = context.SemanticModel.GetDeclaredSymbol(classDeclaration) as INamedTypeSymbol; if (classSymbol == null || classSymbol.IsAbstract) return null; return new ClassInfo { Symbol = classSymbol, FullName = classSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), Name = classSymbol.Name }; } private static ClassRegistration? ProcessClass(ClassInfo? classInfo, Compilation compilation) { if (!classInfo.HasValue) return null; var classSymbol = classInfo.Value.Symbol; // Собираем все интерфейсы, которые реализует класс var allInterfaces = classSymbol.AllInterfaces; // Ищем первый подходящий интерфейс foreach (var interfaceSymbol in allInterfaces) { var interfaceFullName = interfaceSymbol.ToDisplayString(); // Проверяем наличие атрибута VContainerAutoRegistration var hasAttribute = HasAutoRegistrationAttribute(interfaceSymbol, compilation); if (hasAttribute.hasAttribute) { return new ClassRegistration { ClassSymbol = classSymbol, InterfaceSymbol = interfaceSymbol, Lifetime = hasAttribute.lifetime, RegisterType = hasAttribute.registerType, FoundBy = "Attribute" }; } // Проверяем вхождение в preparedInterfaces if (preparedInterfaces.TryGetValue(interfaceFullName, out var preparedSettings)) { return new ClassRegistration { ClassSymbol = classSymbol, InterfaceSymbol = interfaceSymbol, Lifetime = preparedSettings.Item1, RegisterType = preparedSettings.Item2, FoundBy = "PreparedList" }; } } return null; } private static (bool hasAttribute, TargetLifetime lifetime, RegisterType registerType) HasAutoRegistrationAttribute(INamedTypeSymbol interfaceSymbol, Compilation compilation) { var autoRegistrationAttribute = compilation.GetTypeByMetadataName(Attribute); if (autoRegistrationAttribute == null) return (false, default, default); var attribute = interfaceSymbol.GetAttributes() .FirstOrDefault(attr => attr.AttributeClass?.Equals(autoRegistrationAttribute, SymbolEqualityComparer.Default) == true); if (attribute == null) return (false, default, default); var lifetime = GetLifetimeFromAttribute(attribute); var registerType = GetRegisterTypeFromAttribute(attribute); return (true, lifetime, registerType); } private static TargetLifetime GetLifetimeFromAttribute(AttributeData attribute) { foreach (var namedArgument in attribute.NamedArguments) { if (namedArgument.Key == "Lifetime" && namedArgument.Value.Value is int lifetimeValue) { return (TargetLifetime)lifetimeValue; } } return TargetLifetime.Transient; } private static RegisterType GetRegisterTypeFromAttribute(AttributeData attribute) { foreach (var namedArgument in attribute.NamedArguments) { if (namedArgument.Key == "RegisterAs" && namedArgument.Value.Value is int registerAsValue) { return (RegisterType)registerAsValue; } } return RegisterType.ImplementedInterfaces; } private static void GenerateRegistrationCode( List groups, SourceProductionContext context) { if (!groups.Any()) return; // Генерируем основной файл регистраций GenerateMainRegistrationFile(groups, context); // Генерируем отдельные файлы для каждой группы интерфейсов foreach (var group in groups) { GenerateInterfaceRegistrationFile(group, context); } } private static void GenerateMainRegistrationFile( List groups, SourceProductionContext context) { var sourceBuilder = StringBuilderPool.Get(); sourceBuilder.AppendLine("// "); sourceBuilder.AppendLine("#nullable enable"); sourceBuilder.AppendLine("using VContainer;"); sourceBuilder.AppendLine(); sourceBuilder.AppendLine("namespace VContainer.AutoRegistration.Generated"); sourceBuilder.AppendLine("{"); sourceBuilder.AppendLine(" public static partial class AutoGeneratedRegistrations"); sourceBuilder.AppendLine(" {"); sourceBuilder.AppendLine(" public static IContainerBuilder RegisterAllAutoDiscoveredTypes(this IContainerBuilder builder)"); sourceBuilder.AppendLine(" {"); foreach (var group in groups) { var methodName = GetSafeMethodName(group.InterfaceSymbol); sourceBuilder.AppendLine($" builder.{methodName}();"); } sourceBuilder.AppendLine(" return builder;"); sourceBuilder.AppendLine(" }"); sourceBuilder.AppendLine(" }"); sourceBuilder.AppendLine("}"); context.AddSource("AutoGeneratedRegistrations.Main.g.cs", SourceText.From(sourceBuilder.ToStringAndReturn(), Encoding.UTF8)); } private static string GetSafeMethodName(INamedTypeSymbol interfaceSymbol) { var interfaceName = SafeInterfaceName(interfaceSymbol); return $"Register{interfaceName}Implementations"; } private static string SafeInterfaceName(INamedTypeSymbol interfaceSymbol) { return interfaceSymbol.ToDisplayString() .Replace(".", "_") .Replace("<", "_") .Replace(">", "_") .Replace(",", "_"); } private static string GetSafeFileName(INamedTypeSymbol interfaceSymbol) { var interfaceName = SafeInterfaceName(interfaceSymbol); return interfaceName; } private static void GenerateInterfaceRegistrationFile( InterfaceRegistrationGroup group, SourceProductionContext context) { var sourceBuilder = StringBuilderPool.Get(); sourceBuilder.AppendLine("// "); sourceBuilder.AppendLine("#nullable enable"); sourceBuilder.AppendLine("using VContainer;"); sourceBuilder.AppendLine(); sourceBuilder.AppendLine("namespace VContainer.AutoRegistration.Generated"); sourceBuilder.AppendLine("{"); sourceBuilder.AppendLine(" public static partial class AutoGeneratedRegistrations"); sourceBuilder.AppendLine(" {"); var methodName = GetSafeMethodName(group.InterfaceSymbol); var interfaceFullName = group.InterfaceSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); sourceBuilder.AppendLine($" public static IContainerBuilder {methodName}(this IContainerBuilder builder)"); sourceBuilder.AppendLine(" {"); foreach (var registration in group.ClassRegistrations) { var className = registration.ClassSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); sourceBuilder.AppendLine($" // {registration.ClassSymbol.Name} -> {registration.InterfaceSymbol.Name} ({registration.FoundBy})"); var registrationLine = registration.RegisterType switch { RegisterType.Self => $"builder.Register<{className}>(Lifetime.{registration.Lifetime}).AsSelf();", RegisterType.ImplementedInterfaces => $"builder.Register<{className}>(Lifetime.{registration.Lifetime}).AsImplementedInterfaces();", RegisterType.Both => $"builder.Register<{className}>(Lifetime.{registration.Lifetime}).AsImplementedInterfaces().AsSelf();", _ => string.Empty }; if (!string.IsNullOrEmpty(registrationLine)) { sourceBuilder.AppendLine($" {registrationLine}"); } sourceBuilder.AppendLine(); } sourceBuilder.AppendLine(" return builder;"); sourceBuilder.AppendLine(" }"); sourceBuilder.AppendLine(" }"); sourceBuilder.AppendLine("}"); var fileName = $"AutoGeneratedRegistrations.{GetSafeFileName(group.InterfaceSymbol)}.g.cs"; context.AddSource(fileName, SourceText.From(sourceBuilder.ToStringAndReturn(), Encoding.UTF8)); } } // Вспомогательные структуры internal struct ClassInfo { public INamedTypeSymbol Symbol { get; set; } public string FullName { get; set; } public string Name { get; set; } } internal class ClassRegistration { public INamedTypeSymbol ClassSymbol { get; set; } = null!; public INamedTypeSymbol InterfaceSymbol { get; set; } = null!; public TargetLifetime Lifetime { get; set; } public RegisterType RegisterType { get; set; } public string FoundBy { get; set; } = string.Empty; } internal class InterfaceRegistrationGroup { public string InterfaceFullName { get; set; } = string.Empty; public INamedTypeSymbol InterfaceSymbol { get; set; } = null!; public List ClassRegistrations { get; set; } = new(); public TargetLifetime Lifetime { get; set; } public RegisterType RegisterType { get; set; } } public enum TargetLifetime { Transient, Scoped, Singleton } public enum RegisterType { Self, ImplementedInterfaces, Both }