< Summary

Information
Class: NexusLabs.Needlr.Generators.TypeRegistryGenerator
Assembly: NexusLabs.Needlr.Generators
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/TypeRegistryGenerator.cs
Line coverage
91%
Covered lines: 1411
Uncovered lines: 135
Coverable lines: 1546
Total lines: 3022
Line coverage: 91.2%
Branch coverage
80%
Covered branches: 753
Total branches: 932
Branch coverage: 80.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Initialize(...)93.33%303099.31%
ReportDisposableCaptiveDependencies(...)100%88100%
CheckForCaptiveDependencies(...)100%1212100%
IsFactoryPattern(...)50%14855.55%
IsShorterLifetime(...)100%1010100%
GetLifetimeName(...)75%4485.71%
GetBreadcrumbLevel(...)100%88100%
GetProjectDirectory(...)50%4475%
GetDiagnosticOptions(...)100%11100%
IsAotProject(...)100%88100%
DetectPositionalRecord(...)75%121296%
IsPrimaryConstructor(...)100%66100%
ExtractBindableProperties(...)100%2626100%
ExtractDataAnnotations(...)82.14%675684.9%
IsValidationAttribute(...)0%2040%
AnalyzeComplexType(...)91.66%121295.45%
TryGetNestedProperties(...)83.33%66100%
IsDictionaryType(...)75%44100%
IsListType(...)100%88100%
IsBindableClass(...)68.18%272278.57%
GetAttributeInfoFromCompilation(...)88.88%181894.11%
DiscoverTypes(...)100%2424100%
CollectTypesFromAssembly(...)86.73%9898100%
GenerateTypeRegistrySource(...)100%1010100%
GenerateModuleInitializerBootstrapSource(...)100%1616100%
GenerateInjectableTypesArray(...)86.36%232288%
GeneratePluginTypesArray(...)80%202095%
GenerateRegisterOptionsMethod(...)75%4493.33%
GenerateReflectionOptionsRegistration(...)100%2020100%
GenerateAotOptionsRegistration(...)100%88100%
GenerateConfigureBinding(...)100%66100%
GenerateInitOnlyBinding(...)100%88100%
GeneratePositionalRecordBinding(...)87.5%8895.23%
GeneratePropertyParseVariable(...)53.12%703266.66%
GenerateComplexTypeParseVariable(...)31.03%1662945.45%
GenerateNestedPropertyAssignment(...)50%171266.66%
GenerateChildPropertyAssignment(...)0%7280%
GeneratePropertyInitializer(...)100%11100%
GenerateParameterParseVariable(...)87.5%161692.3%
RegisterValidator(...)100%1212100%
RegisterValidatorForFactory(...)60%111076.92%
GeneratePropertyBinding(...)69.64%1175673.17%
GenerateComplexTypeBinding(...)80%55100%
GenerateNestedObjectBinding(...)100%66100%
GenerateNestedPropertyBinding(...)64.28%372877.77%
GenerateCollectionBindingInNested(...)25%5455.55%
GenerateArrayBinding(...)100%11100%
GenerateArrayBindingCore(...)87.5%88100%
GenerateListBinding(...)100%11100%
GenerateListBindingCore(...)87.5%88100%
GenerateDictionaryBinding(...)100%11100%
GenerateDictionaryBindingCore(...)87.5%88100%
GenerateChildPropertyBinding(...)50%311661.53%
GeneratePrimitiveCollectionAdd(...)75%9871.42%
GeneratePrimitiveListAdd(...)75%9871.42%
GeneratePrimitiveDictionaryAdd(...)75%9871.42%
GetNonNullableTypeName(...)50%2266.66%
GetBaseTypeName(...)66.66%6680%
GenerateApplyDecoratorsMethod(...)75%452466.66%
GenerateRegisterProvidersMethod(...)87.5%88100%
GenerateRegisterHostedServicesMethod(...)75%44100%
GenerateInterceptorProxiesSource(...)83.33%121296.55%
GenerateFactoriesSource(...)100%1212100%
GenerateProvidersSource(...)100%22100%
GenerateShorthandProviderSource(...)100%11100%
GetNamespaceFromTypeName(...)75%44100%
FilterNestedOptions(...)100%1414100%
FindTypeSymbol(...)50%22100%
ExpandOpenDecorators(...)100%88100%
DiscoverReferencedAssembliesWithTypeRegistry(...)100%88100%
DiscoverReferencedAssemblyTypesForDiagnostics(...)97.05%3434100%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/TypeRegistryGenerator.cs

#LineLine coverage
 1using Microsoft.CodeAnalysis;
 2using Microsoft.CodeAnalysis.Text;
 3using NexusLabs.Needlr.Generators.Helpers;
 4using NexusLabs.Needlr.Generators.Models;
 5using System.Text;
 6
 7namespace NexusLabs.Needlr.Generators;
 8
 9/// <summary>
 10/// Incremental source generator that produces a compile-time type registry
 11/// for dependency injection, eliminating runtime reflection.
 12/// </summary>
 13[Generator(LanguageNames.CSharp)]
 14public sealed class TypeRegistryGenerator : IIncrementalGenerator
 15{
 16    private const string GenerateTypeRegistryAttributeName = "NexusLabs.Needlr.Generators.GenerateTypeRegistryAttribute"
 17
 18    public void Initialize(IncrementalGeneratorInitializationContext context)
 19    {
 20        // Combine compilation with analyzer config options to read MSBuild properties
 43421        var compilationAndOptions = context.CompilationProvider
 43422            .Combine(context.AnalyzerConfigOptionsProvider);
 23
 24        // ForAttributeWithMetadataName doesn't work for assembly-level attributes.
 25        // Instead, we register directly on the compilation provider and check
 26        // compilation.Assembly.GetAttributes() for [GenerateTypeRegistry].
 43427        context.RegisterSourceOutput(compilationAndOptions, static (spc, source) =>
 43428        {
 43429            var (compilation, configOptions) = source;
 43430
 43431            var attributeInfo = GetAttributeInfoFromCompilation(compilation);
 43432            if (attributeInfo == null)
 033                return;
 43434
 43435            var info = attributeInfo.Value;
 43436            var assemblyName = compilation.AssemblyName ?? "Generated";
 43437
 43438            // Read breadcrumb level from MSBuild property
 43439            var breadcrumbLevel = GetBreadcrumbLevel(configOptions);
 43440            var projectDirectory = GetProjectDirectory(configOptions);
 43441            var breadcrumbs = new BreadcrumbWriter(breadcrumbLevel);
 43442
 43443            // Check if this is an AOT project
 43444            var isAotProject = IsAotProject(configOptions);
 43445
 43446            var discoveryResult = DiscoverTypes(
 43447                compilation,
 43448                info.NamespacePrefixes,
 43449                info.IncludeSelf);
 43450
 43451            // Report errors for inaccessible internal types in referenced assemblies
 540052            foreach (var inaccessibleType in discoveryResult.InaccessibleTypes)
 43453            {
 226654                spc.ReportDiagnostic(Diagnostic.Create(
 226655                    DiagnosticDescriptors.InaccessibleInternalType,
 226656                    Location.None,
 226657                    inaccessibleType.TypeName,
 226658                    inaccessibleType.AssemblyName));
 43459            }
 43460
 43461            // Report errors for referenced assemblies with internal plugin types but no [GenerateTypeRegistry]
 87062            foreach (var missingPlugin in discoveryResult.MissingTypeRegistryPlugins)
 43463            {
 164                spc.ReportDiagnostic(Diagnostic.Create(
 165                    DiagnosticDescriptors.MissingGenerateTypeRegistryAttribute,
 166                    Location.None,
 167                    missingPlugin.AssemblyName,
 168                    missingPlugin.TypeName));
 43469            }
 43470
 43471            // NDLRGEN020: Previously reported error if [Options] used in AOT project
 43472            // Now removed for parity - we generate best-effort code and let unsupported
 43473            // types fail at runtime (matching non-AOT ConfigurationBinder behavior)
 43474
 43475            // NDLRGEN021: Report warning for non-partial positional records
 103776            foreach (var opt in discoveryResult.Options.Where(o => o.IsNonPartialPositionalRecord))
 43477            {
 278                spc.ReportDiagnostic(Diagnostic.Create(
 279                    DiagnosticDescriptors.PositionalRecordMustBePartial,
 280                    Location.None,
 281                    opt.TypeName));
 43482            }
 43483
 43484            // NDLRGEN022: Detect disposable captive dependencies using inferred lifetimes
 43485            ReportDisposableCaptiveDependencies(spc, discoveryResult);
 43486
 43487            var sourceText = GenerateTypeRegistrySource(discoveryResult, assemblyName, breadcrumbs, projectDirectory, is
 43488            spc.AddSource("TypeRegistry.g.cs", SourceText.From(sourceText, Encoding.UTF8));
 43489
 43490            // Discover referenced assemblies with [GenerateTypeRegistry] for forced loading
 43491            // Note: Order of force-loading doesn't matter; ordering is applied at service registration time
 43492            var referencedAssemblies = DiscoverReferencedAssembliesWithTypeRegistry(compilation)
 293                .OrderBy(a => a, StringComparer.OrdinalIgnoreCase)
 43494                .ToList();
 43495
 43496            var bootstrapText = GenerateModuleInitializerBootstrapSource(assemblyName, referencedAssemblies, breadcrumbs
 43497            spc.AddSource("NeedlrSourceGenBootstrap.g.cs", SourceText.From(bootstrapText, Encoding.UTF8));
 43498
 43499            // Generate interceptor proxy classes if any were discovered
 434100            if (discoveryResult.InterceptedServices.Count > 0)
 434101            {
 15102                var interceptorProxiesText = GenerateInterceptorProxiesSource(discoveryResult.InterceptedServices, assem
 15103                spc.AddSource("InterceptorProxies.g.cs", SourceText.From(interceptorProxiesText, Encoding.UTF8));
 434104            }
 434105
 434106            // Generate factory classes if any were discovered
 434107            if (discoveryResult.Factories.Count > 0)
 434108            {
 26109                var factoriesText = GenerateFactoriesSource(discoveryResult.Factories, assemblyName, breadcrumbs, projec
 26110                spc.AddSource("Factories.g.cs", SourceText.From(factoriesText, Encoding.UTF8));
 434111            }
 434112
 434113            // Generate provider classes if any were discovered
 434114            if (discoveryResult.Providers.Count > 0)
 434115            {
 434116                // Interface-based providers go in the Generated namespace
 35117                var interfaceProviders = discoveryResult.Providers.Where(p => p.IsInterface).ToList();
 17118                if (interfaceProviders.Count > 0)
 434119                {
 11120                    var providersText = GenerateProvidersSource(interfaceProviders, assemblyName, breadcrumbs, projectDi
 11121                    spc.AddSource("Providers.g.cs", SourceText.From(providersText, Encoding.UTF8));
 434122                }
 434123
 434124                // Shorthand class providers need to be generated in their original namespace
 35125                var classProviders = discoveryResult.Providers.Where(p => !p.IsInterface && p.IsPartial).ToList();
 46126                foreach (var provider in classProviders)
 434127                {
 6128                    var providerText = GenerateShorthandProviderSource(provider, assemblyName, breadcrumbs, projectDirec
 6129                    spc.AddSource($"Provider.{provider.SimpleTypeName}.g.cs", SourceText.From(providerText, Encoding.UTF
 434130                }
 434131            }
 434132
 434133            // Generate options validator classes if any have validation methods
 599134            var optionsWithValidators = discoveryResult.Options.Where(o => o.HasValidatorMethod).ToList();
 434135            if (optionsWithValidators.Count > 0)
 434136            {
 18137                var validatorsText = CodeGen.OptionsCodeGenerator.GenerateOptionsValidatorsSource(optionsWithValidators,
 18138                spc.AddSource("OptionsValidators.g.cs", SourceText.From(validatorsText, Encoding.UTF8));
 434139            }
 434140
 434141            // Generate DataAnnotations validator classes if any have DataAnnotation attributes
 599142            var optionsWithDataAnnotations = discoveryResult.Options.Where(o => o.HasDataAnnotations).ToList();
 434143            if (optionsWithDataAnnotations.Count > 0)
 434144            {
 18145                var dataAnnotationsValidatorsText = CodeGen.OptionsCodeGenerator.GenerateDataAnnotationsValidatorsSource
 18146                spc.AddSource("OptionsDataAnnotationsValidators.g.cs", SourceText.From(dataAnnotationsValidatorsText, En
 434147            }
 434148
 434149            // Generate parameterless constructors for partial positional records with [Options]
 599150            var optionsNeedingConstructors = discoveryResult.Options.Where(o => o.NeedsGeneratedConstructor).ToList();
 434151            if (optionsNeedingConstructors.Count > 0)
 434152            {
 7153                var constructorsText = CodeGen.OptionsCodeGenerator.GeneratePositionalRecordConstructorsSource(optionsNe
 7154                spc.AddSource("OptionsConstructors.g.cs", SourceText.From(constructorsText, Encoding.UTF8));
 434155            }
 434156
 434157            // Generate ServiceCatalog for runtime introspection
 434158            var catalogText = CodeGen.ServiceCatalogCodeGenerator.GenerateServiceCatalogSource(discoveryResult, assembly
 434159            spc.AddSource("ServiceCatalog.g.cs", SourceText.From(catalogText, Encoding.UTF8));
 434160
 434161            // Generate diagnostic output files if configured
 434162            var diagnosticOptions = GetDiagnosticOptions(configOptions);
 434163            if (diagnosticOptions.Enabled)
 434164            {
 95165                var referencedAssemblyTypes = DiscoverReferencedAssemblyTypesForDiagnostics(compilation);
 95166                var diagnosticsText = DiagnosticsGenerator.GenerateDiagnosticsSource(discoveryResult, assemblyName, proj
 95167                spc.AddSource("NeedlrDiagnostics.g.cs", SourceText.From(diagnosticsText, Encoding.UTF8));
 434168            }
 868169        });
 434170    }
 171
 172    /// <summary>
 173    /// Detects disposable captive dependencies using inferred lifetimes from DiscoveryResult.
 174    /// Reports NDLRGEN022 when a longer-lived service depends on a shorter-lived disposable.
 175    /// </summary>
 176    private static void ReportDisposableCaptiveDependencies(SourceProductionContext spc, DiscoveryResult discoveryResult
 177    {
 178        // Build lookup from type name to DiscoveredType for O(1) lifetime lookups
 434179        var typeLookup = new Dictionary<string, DiscoveredType>();
 457032180        foreach (var type in discoveryResult.InjectableTypes)
 181        {
 228082182            typeLookup[type.TypeName] = type;
 183            // Also map by interfaces so we can look up dependencies by interface
 456656184            foreach (var iface in type.InterfaceNames)
 185            {
 186                // Only add if not already present (first registration wins for interface resolution)
 246187                if (!typeLookup.ContainsKey(iface))
 188                {
 240189                    typeLookup[iface] = type;
 190                }
 191            }
 192        }
 193
 194        // Check each injectable type for captive dependencies
 457032195        foreach (var type in discoveryResult.InjectableTypes)
 196        {
 228082197            CheckForCaptiveDependencies(spc, type, typeLookup);
 198        }
 434199    }
 200
 201    /// <summary>
 202    /// Checks a single type for captive dependency issues.
 203    /// </summary>
 204    private static void CheckForCaptiveDependencies(
 205        SourceProductionContext spc,
 206        DiscoveredType type,
 207        Dictionary<string, DiscoveredType> typeLookup)
 208    {
 209        // Skip types with transient lifetime - they can't capture shorter-lived dependencies
 228082210        if (type.Lifetime == GeneratorLifetime.Transient)
 4211            return;
 212
 564086213        foreach (var param in type.ConstructorParameters)
 214        {
 215            // Skip factory patterns that create new instances on demand
 53965216            if (IsFactoryPattern(param.TypeName))
 217                continue;
 218
 219            // Try to find the dependency in our discovered types
 53965220            if (!typeLookup.TryGetValue(param.TypeName, out var dependency))
 221                continue;
 222
 223            // Check if the dependency is shorter-lived
 17765224            if (!IsShorterLifetime(type.Lifetime, dependency.Lifetime))
 225                continue;
 226
 227            // Check if the dependency is disposable
 6228            if (!dependency.IsDisposable)
 229                continue;
 230
 231            // Report the captive dependency
 5232            spc.ReportDiagnostic(Diagnostic.Create(
 5233                DiagnosticDescriptors.DisposableCaptiveDependency,
 5234                Location.None,
 5235                type.TypeName,
 5236                GetLifetimeName(type.Lifetime),
 5237                dependency.TypeName,
 5238                GetLifetimeName(dependency.Lifetime)));
 239        }
 228078240    }
 241
 242    /// <summary>
 243    /// Checks if a type name represents a factory pattern that creates new instances on demand.
 244    /// </summary>
 245    private static bool IsFactoryPattern(string typeName)
 246    {
 247        // Func<T> - factory delegate
 53965248        if (typeName.StartsWith("System.Func<", StringComparison.Ordinal))
 0249            return true;
 250
 251        // Lazy<T> - deferred creation
 53965252        if (typeName.StartsWith("System.Lazy<", StringComparison.Ordinal))
 0253            return true;
 254
 255        // IServiceScopeFactory - creates new scopes
 53965256        if (typeName == "Microsoft.Extensions.DependencyInjection.IServiceScopeFactory")
 0257            return true;
 258
 259        // IServiceProvider - resolves services dynamically
 53965260        if (typeName == "System.IServiceProvider")
 0261            return true;
 262
 53965263        return false;
 264    }
 265
 266    /// <summary>
 267    /// Checks if dependency lifetime is shorter than consumer lifetime.
 268    /// </summary>
 269    private static bool IsShorterLifetime(GeneratorLifetime consumer, GeneratorLifetime dependency)
 270    {
 271        // Singleton > Scoped > Transient (in terms of lifetime duration)
 272        // A shorter lifetime means the dependency will be disposed sooner
 17765273        return (consumer, dependency) switch
 17765274        {
 4275            (GeneratorLifetime.Singleton, GeneratorLifetime.Scoped) => true,
 1276            (GeneratorLifetime.Singleton, GeneratorLifetime.Transient) => true,
 1277            (GeneratorLifetime.Scoped, GeneratorLifetime.Transient) => true,
 17759278            _ => false
 17765279        };
 280    }
 281
 282    /// <summary>
 283    /// Gets the human-readable name for a lifetime.
 284    /// </summary>
 10285    private static string GetLifetimeName(GeneratorLifetime lifetime) => lifetime switch
 10286    {
 4287        GeneratorLifetime.Singleton => "Singleton",
 4288        GeneratorLifetime.Scoped => "Scoped",
 2289        GeneratorLifetime.Transient => "Transient",
 0290        _ => lifetime.ToString()
 10291    };
 292
 293    private static BreadcrumbLevel GetBreadcrumbLevel(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider c
 294    {
 434295        if (configOptions.GlobalOptions.TryGetValue("build_property.NeedlrBreadcrumbLevel", out var levelStr) &&
 434296            !string.IsNullOrWhiteSpace(levelStr))
 297        {
 259298            if (levelStr.Equals("None", StringComparison.OrdinalIgnoreCase))
 17299                return BreadcrumbLevel.None;
 242300            if (levelStr.Equals("Verbose", StringComparison.OrdinalIgnoreCase))
 28301                return BreadcrumbLevel.Verbose;
 302        }
 303
 304        // Default to Minimal
 389305        return BreadcrumbLevel.Minimal;
 306    }
 307
 308    private static string? GetProjectDirectory(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider configOp
 309    {
 310        // Try to get the project directory from MSBuild properties
 434311        if (configOptions.GlobalOptions.TryGetValue("build_property.ProjectDir", out var projectDir) &&
 434312            !string.IsNullOrWhiteSpace(projectDir))
 313        {
 0314            return projectDir.TrimEnd('/', '\\');
 315        }
 316
 434317        return null;
 318    }
 319
 320    private static DiagnosticOptions GetDiagnosticOptions(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvid
 321    {
 434322        configOptions.GlobalOptions.TryGetValue("build_property.NeedlrDiagnostics", out var enabled);
 434323        configOptions.GlobalOptions.TryGetValue("build_property.NeedlrDiagnosticsPath", out var outputPath);
 434324        configOptions.GlobalOptions.TryGetValue("build_property.NeedlrDiagnosticsFilter", out var filter);
 325
 434326        return DiagnosticOptions.Parse(enabled, outputPath, filter);
 327    }
 328
 329    /// <summary>
 330    /// Checks if the project is configured for AOT compilation.
 331    /// Returns true if either PublishAot or IsAotCompatible is set to true.
 332    /// </summary>
 333    private static bool IsAotProject(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider configOptions)
 334    {
 434335        if (configOptions.GlobalOptions.TryGetValue("build_property.PublishAot", out var publishAot) &&
 434336            publishAot.Equals("true", StringComparison.OrdinalIgnoreCase))
 337        {
 79338            return true;
 339        }
 340
 355341        if (configOptions.GlobalOptions.TryGetValue("build_property.IsAotCompatible", out var isAotCompatible) &&
 355342            isAotCompatible.Equals("true", StringComparison.OrdinalIgnoreCase))
 343        {
 1344            return true;
 345        }
 346
 354347        return false;
 348    }
 349
 350    /// <summary>
 351    /// Detects if a type is a positional record (record with primary constructor parameters).
 352    /// Returns null if not a positional record, or PositionalRecordInfo if it is.
 353    /// </summary>
 354    private static PositionalRecordInfo? DetectPositionalRecord(INamedTypeSymbol typeSymbol)
 355    {
 356        // Must be a record
 167357        if (!typeSymbol.IsRecord)
 155358            return null;
 359
 360        // Check for primary constructor with parameters
 361        // Records with positional parameters have a primary constructor generated from the record declaration
 12362        var primaryCtor = typeSymbol.InstanceConstructors
 27363            .FirstOrDefault(c => c.Parameters.Length > 0 && IsPrimaryConstructor(c, typeSymbol));
 364
 12365        if (primaryCtor == null)
 3366            return null;
 367
 368        // Check if the record has a parameterless constructor already
 369        // (user-defined or from record with init-only properties)
 9370        var hasParameterlessCtor = typeSymbol.InstanceConstructors
 27371            .Any(c => c.Parameters.Length == 0 && !c.IsImplicitlyDeclared);
 372
 9373        if (hasParameterlessCtor)
 0374            return null; // Doesn't need generated constructor
 375
 376        // Check if partial
 9377        var isPartial = typeSymbol.DeclaringSyntaxReferences
 9378            .Select(r => r.GetSyntax())
 9379            .OfType<Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax>()
 34380            .Any(s => s.Modifiers.Any(m => m.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.PartialKeyword)));
 381
 382        // Extract constructor parameters
 9383        var parameters = primaryCtor.Parameters
 23384            .Select(p => new PositionalRecordParameter(p.Name, p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualified
 9385            .ToList();
 386
 387        // Get namespace
 9388        var containingNamespace = typeSymbol.ContainingNamespace.IsGlobalNamespace
 9389            ? ""
 9390            : typeSymbol.ContainingNamespace.ToDisplayString();
 391
 9392        return new PositionalRecordInfo(
 9393            typeSymbol.Name,
 9394            containingNamespace,
 9395            isPartial,
 9396            parameters);
 397    }
 398
 399    /// <summary>
 400    /// Determines if a constructor is the primary constructor of a record.
 401    /// Primary constructors for positional records are synthesized and have matching properties.
 402    /// </summary>
 403    private static bool IsPrimaryConstructor(IMethodSymbol ctor, INamedTypeSymbol recordType)
 404    {
 405        // For positional records, the primary constructor parameters correspond to auto-properties
 406        // Check if each parameter has a matching property
 73407        foreach (var param in ctor.Parameters)
 408        {
 26409            var hasMatchingProperty = recordType.GetMembers()
 26410                .OfType<IPropertySymbol>()
 101411                .Any(p => p.Name.Equals(param.Name, StringComparison.Ordinal) &&
 101412                         SymbolEqualityComparer.Default.Equals(p.Type, param.Type));
 413
 26414            if (!hasMatchingProperty)
 3415                return false;
 416        }
 417
 9418        return true;
 419    }
 420
 421    /// <summary>
 422    /// Extracts bindable properties from an options type for AOT code generation.
 423    /// </summary>
 424    private static IReadOnlyList<OptionsPropertyInfo> ExtractBindableProperties(INamedTypeSymbol typeSymbol, HashSet<str
 425    {
 195426        var properties = new List<OptionsPropertyInfo>();
 195427        visitedTypes ??= new HashSet<string>();
 428
 429        // Prevent infinite recursion for circular references
 195430        var typeFullName = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 195431        if (!visitedTypes.Add(typeFullName))
 432        {
 6433            return properties; // Already visited - circular reference
 434        }
 435
 3294436        foreach (var member in typeSymbol.GetMembers())
 437        {
 1458438            if (member is not IPropertySymbol property)
 439                continue;
 440
 441            // Skip static, indexers, readonly properties without init
 291442            if (property.IsStatic || property.IsIndexer)
 443                continue;
 444
 445            // Must have a setter (set or init)
 291446            if (property.SetMethod == null)
 447                continue;
 448
 449            // Check if it's init-only
 279450            var isInitOnly = property.SetMethod.IsInitOnly;
 451
 452            // Get nullability info
 279453            var isNullable = property.NullableAnnotation == NullableAnnotation.Annotated ||
 279454                             (property.Type is INamedTypeSymbol namedType &&
 279455                              namedType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T);
 456
 279457            var typeName = property.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 458
 459            // Check if it's an enum type
 279460            var isEnum = false;
 279461            string? enumTypeName = null;
 279462            var actualType = property.Type;
 463
 464            // For nullable types, get the underlying type
 279465            if (actualType is INamedTypeSymbol nullableType &&
 279466                nullableType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T &&
 279467                nullableType.TypeArguments.Length == 1)
 468            {
 4469                actualType = nullableType.TypeArguments[0];
 470            }
 471
 279472            if (actualType.TypeKind == TypeKind.Enum)
 473            {
 15474                isEnum = true;
 15475                enumTypeName = actualType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 476            }
 477
 478            // Detect complex types
 279479            var (complexKind, elementTypeName, nestedProps) = AnalyzeComplexType(property.Type, visitedTypes);
 480
 481            // Extract DataAnnotation attributes
 279482            var dataAnnotations = ExtractDataAnnotations(property);
 483
 279484            properties.Add(new OptionsPropertyInfo(
 279485                property.Name,
 279486                typeName,
 279487                isNullable,
 279488                isInitOnly,
 279489                isEnum,
 279490                enumTypeName,
 279491                complexKind,
 279492                elementTypeName,
 279493                nestedProps,
 279494                dataAnnotations));
 495        }
 496
 189497        return properties;
 498    }
 499
 500    private static IReadOnlyList<DataAnnotationInfo> ExtractDataAnnotations(IPropertySymbol property)
 501    {
 279502        var annotations = new List<DataAnnotationInfo>();
 503
 604504        foreach (var attr in property.GetAttributes())
 505        {
 23506            var attrClass = attr.AttributeClass;
 23507            if (attrClass == null) continue;
 508
 509            // Get the attribute type name - use ContainingNamespace + Name for reliable matching
 23510            var attrNamespace = attrClass.ContainingNamespace?.ToDisplayString() ?? "";
 23511            var attrTypeName = attrClass.Name;
 512
 513            // Only process System.ComponentModel.DataAnnotations attributes
 23514            if (attrNamespace != "System.ComponentModel.DataAnnotations")
 515                continue;
 516
 517            // Extract error message if present
 23518            string? errorMessage = null;
 51519            foreach (var namedArg in attr.NamedArguments)
 520            {
 3521                if (namedArg.Key == "ErrorMessage" && namedArg.Value.Value is string msg)
 522                {
 1523                    errorMessage = msg;
 1524                    break;
 525                }
 526            }
 527
 528            // Check for known DataAnnotation attributes
 23529            if (attrTypeName == "RequiredAttribute")
 530            {
 12531                annotations.Add(new DataAnnotationInfo(DataAnnotationKind.Required, errorMessage));
 532            }
 11533            else if (attrTypeName == "RangeAttribute")
 534            {
 12535                object? min = null, max = null;
 6536                if (attr.ConstructorArguments.Length >= 2)
 537                {
 6538                    min = attr.ConstructorArguments[0].Value;
 6539                    max = attr.ConstructorArguments[1].Value;
 540                }
 6541                annotations.Add(new DataAnnotationInfo(DataAnnotationKind.Range, errorMessage, min, max));
 542            }
 5543            else if (attrTypeName == "StringLengthAttribute")
 544            {
 2545                object? maxLen = null;
 2546                int? minLen = null;
 2547                if (attr.ConstructorArguments.Length >= 1)
 548                {
 2549                    maxLen = attr.ConstructorArguments[0].Value;
 550                }
 8551                foreach (var namedArg in attr.NamedArguments)
 552                {
 2553                    if (namedArg.Key == "MinimumLength" && namedArg.Value.Value is int ml)
 554                    {
 2555                        minLen = ml;
 556                    }
 557                }
 2558                annotations.Add(new DataAnnotationInfo(DataAnnotationKind.StringLength, errorMessage, null, maxLen, null
 559            }
 3560            else if (attrTypeName == "MinLengthAttribute")
 561            {
 1562                int? minLen = null;
 1563                if (attr.ConstructorArguments.Length >= 1 && attr.ConstructorArguments[0].Value is int ml)
 564                {
 1565                    minLen = ml;
 566                }
 1567                annotations.Add(new DataAnnotationInfo(DataAnnotationKind.MinLength, errorMessage, null, null, null, min
 568            }
 2569            else if (attrTypeName == "MaxLengthAttribute")
 570            {
 1571                object? maxLen = null;
 1572                if (attr.ConstructorArguments.Length >= 1)
 573                {
 1574                    maxLen = attr.ConstructorArguments[0].Value;
 575                }
 1576                annotations.Add(new DataAnnotationInfo(DataAnnotationKind.MaxLength, errorMessage, null, maxLen));
 577            }
 1578            else if (attrTypeName == "RegularExpressionAttribute")
 579            {
 1580                string? pattern = null;
 1581                if (attr.ConstructorArguments.Length >= 1 && attr.ConstructorArguments[0].Value is string p)
 582                {
 1583                    pattern = p;
 584                }
 1585                annotations.Add(new DataAnnotationInfo(DataAnnotationKind.RegularExpression, errorMessage, null, null, p
 586            }
 0587            else if (attrTypeName == "EmailAddressAttribute")
 588            {
 0589                annotations.Add(new DataAnnotationInfo(DataAnnotationKind.EmailAddress, errorMessage));
 590            }
 0591            else if (attrTypeName == "PhoneAttribute")
 592            {
 0593                annotations.Add(new DataAnnotationInfo(DataAnnotationKind.Phone, errorMessage));
 594            }
 0595            else if (attrTypeName == "UrlAttribute")
 596            {
 0597                annotations.Add(new DataAnnotationInfo(DataAnnotationKind.Url, errorMessage));
 598            }
 0599            else if (IsValidationAttribute(attrClass))
 600            {
 601                // Unsupported validation attribute
 0602                annotations.Add(new DataAnnotationInfo(DataAnnotationKind.Unsupported, errorMessage));
 603            }
 604        }
 605
 279606        return annotations;
 607    }
 608
 609    private static bool IsValidationAttribute(INamedTypeSymbol attrClass)
 610    {
 611        // Check if this inherits from ValidationAttribute
 0612        var current = attrClass.BaseType;
 0613        while (current != null)
 614        {
 0615            if (current.ToDisplayString() == "System.ComponentModel.DataAnnotations.ValidationAttribute")
 0616                return true;
 0617            current = current.BaseType;
 618        }
 0619        return false;
 620    }
 621
 622    private static (ComplexTypeKind Kind, string? ElementTypeName, IReadOnlyList<OptionsPropertyInfo>? NestedProperties)
 623        ITypeSymbol typeSymbol,
 624        HashSet<string> visitedTypes)
 625    {
 626        // Check for array
 279627        if (typeSymbol is IArrayTypeSymbol arrayType)
 628        {
 3629            var elementType = arrayType.ElementType;
 3630            var elementTypeName = elementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 3631            var nestedProps = TryGetNestedProperties(elementType, visitedTypes);
 3632            return (ComplexTypeKind.Array, elementTypeName, nestedProps);
 633        }
 634
 276635        if (typeSymbol is not INamedTypeSymbol namedType)
 636        {
 0637            return (ComplexTypeKind.None, null, null);
 638        }
 639
 640        // Check for Dictionary<string, T>
 276641        if (IsDictionaryType(namedType))
 642        {
 4643            var valueType = namedType.TypeArguments[1];
 4644            var valueTypeName = valueType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 4645            var nestedProps = TryGetNestedProperties(valueType, visitedTypes);
 4646            return (ComplexTypeKind.Dictionary, valueTypeName, nestedProps);
 647        }
 648
 649        // Check for List<T>, IList<T>, ICollection<T>, IEnumerable<T>
 272650        if (IsListType(namedType))
 651        {
 7652            var elementType = namedType.TypeArguments[0];
 7653            var elementTypeName = elementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 7654            var nestedProps = TryGetNestedProperties(elementType, visitedTypes);
 7655            return (ComplexTypeKind.List, elementTypeName, nestedProps);
 656        }
 657
 658        // Check for nested object (class with bindable properties)
 265659        if (IsBindableClass(namedType))
 660        {
 25661            var nestedProps = ExtractBindableProperties(namedType, visitedTypes);
 25662            if (nestedProps.Count > 0)
 663            {
 19664                return (ComplexTypeKind.NestedObject, null, nestedProps);
 665            }
 666        }
 667
 246668        return (ComplexTypeKind.None, null, null);
 669    }
 670
 671    private static IReadOnlyList<OptionsPropertyInfo>? TryGetNestedProperties(ITypeSymbol elementType, HashSet<string> v
 672    {
 14673        if (elementType is INamedTypeSymbol namedElement && IsBindableClass(namedElement))
 674        {
 3675            var props = ExtractBindableProperties(namedElement, visitedTypes);
 3676            return props.Count > 0 ? props : null;
 677        }
 11678        return null;
 679    }
 680
 681    private static bool IsDictionaryType(INamedTypeSymbol type)
 682    {
 683        // Check for Dictionary<TKey, TValue> or IDictionary<TKey, TValue>
 276684        if (type.TypeArguments.Length != 2)
 272685            return false;
 686
 4687        var typeName = type.OriginalDefinition.ToDisplayString();
 4688        return typeName == "System.Collections.Generic.Dictionary<TKey, TValue>" ||
 4689               typeName == "System.Collections.Generic.IDictionary<TKey, TValue>";
 690    }
 691
 692    private static bool IsListType(INamedTypeSymbol type)
 693    {
 272694        if (type.TypeArguments.Length != 1)
 261695            return false;
 696
 11697        var typeName = type.OriginalDefinition.ToDisplayString();
 11698        return typeName == "System.Collections.Generic.List<T>" ||
 11699               typeName == "System.Collections.Generic.IList<T>" ||
 11700               typeName == "System.Collections.Generic.ICollection<T>" ||
 11701               typeName == "System.Collections.Generic.IEnumerable<T>";
 702    }
 703
 704    private static bool IsBindableClass(INamedTypeSymbol type)
 705    {
 706        // Must be a class or struct, not abstract, not a system type
 279707        if (type.TypeKind != TypeKind.Class && type.TypeKind != TypeKind.Struct)
 17708            return false;
 709
 262710        if (type.IsAbstract)
 4711            return false;
 712
 713        // Skip system types and primitives
 258714        var ns = type.ContainingNamespace?.ToDisplayString() ?? "";
 258715        if (ns.StartsWith("System"))
 716        {
 717            // Skip known non-bindable System namespaces
 230718            if (ns == "System" || ns.StartsWith("System.Collections") || ns.StartsWith("System.Threading"))
 230719                return false;
 720        }
 721
 722        // Must have a parameterless constructor (explicit or implicit)
 723        // Note: Classes without any explicit constructors have an implicit parameterless constructor
 56724        var hasExplicitConstructors = type.InstanceConstructors.Any(c => !c.IsImplicitlyDeclared);
 28725        if (hasExplicitConstructors)
 726        {
 0727            var hasParameterlessCtor = type.InstanceConstructors
 0728                .Any(c => c.Parameters.Length == 0 && c.DeclaredAccessibility == Accessibility.Public);
 0729            return hasParameterlessCtor;
 730        }
 731
 732        // No explicit constructors means implicit parameterless constructor exists
 28733        return true;
 734    }
 735
 736    private static AttributeInfo? GetAttributeInfoFromCompilation(Compilation compilation)
 737    {
 738        // Get assembly-level attributes directly from the compilation
 1302739        foreach (var attribute in compilation.Assembly.GetAttributes())
 740        {
 434741            var attrClassName = attribute.AttributeClass?.ToDisplayString();
 742
 743            // Check if this is our attribute (various name format possibilities)
 434744            if (attrClassName != GenerateTypeRegistryAttributeName)
 745                continue;
 746
 434747            string[]? namespacePrefixes = null;
 434748            var includeSelf = true;
 749
 1002750            foreach (var namedArg in attribute.NamedArguments)
 751            {
 67752                switch (namedArg.Key)
 753                {
 754                    case "IncludeNamespacePrefixes":
 57755                        if (!namedArg.Value.IsNull && namedArg.Value.Values.Length > 0)
 756                        {
 57757                            namespacePrefixes = namedArg.Value.Values
 58758                                .Where(v => v.Value is string)
 58759                                .Select(v => (string)v.Value!)
 57760                                .ToArray();
 761                        }
 57762                        break;
 763
 764                    case "IncludeSelf":
 10765                        if (namedArg.Value.Value is bool selfValue)
 766                        {
 10767                            includeSelf = selfValue;
 768                        }
 769                        break;
 770                }
 771            }
 772
 434773            return new AttributeInfo(namespacePrefixes, includeSelf);
 774        }
 775
 0776        return null;
 777    }
 778
 779    private static DiscoveryResult DiscoverTypes(
 780        Compilation compilation,
 781        string[]? namespacePrefixes,
 782        bool includeSelf)
 783    {
 434784        var injectableTypes = new List<DiscoveredType>();
 434785        var pluginTypes = new List<DiscoveredPlugin>();
 434786        var decorators = new List<DiscoveredDecorator>();
 434787        var openDecorators = new List<DiscoveredOpenDecorator>();
 434788        var interceptedServices = new List<DiscoveredInterceptedService>();
 434789        var factories = new List<DiscoveredFactory>();
 434790        var options = new List<DiscoveredOptions>();
 434791        var hostedServices = new List<DiscoveredHostedService>();
 434792        var providers = new List<DiscoveredProvider>();
 434793        var inaccessibleTypes = new List<InaccessibleType>();
 434794        var prefixList = namespacePrefixes?.ToList();
 795
 796        // Compute the generated namespace for the current assembly
 434797        var currentAssemblyName = compilation.Assembly.Name;
 434798        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(currentAssemblyName);
 434799        var generatedNamespace = $"{safeAssemblyName}.Generated";
 800
 801        // Collect types from the current compilation if includeSelf is true
 434802        if (includeSelf)
 803        {
 433804            CollectTypesFromAssembly(compilation.Assembly, prefixList, injectableTypes, pluginTypes, decorators, openDec
 805        }
 806
 807        // Collect types from all referenced assemblies
 147466808        foreach (var reference in compilation.References)
 809        {
 73299810            if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assemblySymbol)
 811            {
 812                // For referenced assemblies, they use their own generated namespace
 73112813                var refSafeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblySymbol.Name);
 73112814                var refGeneratedNamespace = $"{refSafeAssemblyName}.Generated";
 73112815                CollectTypesFromAssembly(assemblySymbol, prefixList, injectableTypes, pluginTypes, decorators, openDecor
 816            }
 817        }
 818
 819        // Expand open generic decorators into closed decorator registrations
 434820        if (openDecorators.Count > 0)
 821        {
 6822            ExpandOpenDecorators(injectableTypes, openDecorators, decorators);
 823        }
 824
 825        // Filter out nested options types (types used as properties in other options types)
 434826        if (options.Count > 1)
 827        {
 19828            options = FilterNestedOptions(options, compilation);
 829        }
 830
 831        // Check for referenced assemblies with internal plugin types but no [GenerateTypeRegistry]
 434832        var missingTypeRegistryPlugins = new List<MissingTypeRegistryPlugin>();
 147466833        foreach (var reference in compilation.References)
 834        {
 73299835            if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assemblySymbol)
 836            {
 837                // Skip assemblies that already have [GenerateTypeRegistry]
 73112838                if (TypeDiscoveryHelper.HasGenerateTypeRegistryAttribute(assemblySymbol))
 839                    continue;
 840
 841                // Look for internal types that implement Needlr plugin interfaces
 3575976842                foreach (var typeSymbol in TypeDiscoveryHelper.GetAllTypes(assemblySymbol.GlobalNamespace))
 843                {
 1714893844                    if (!TypeDiscoveryHelper.IsInternalOrLessAccessible(typeSymbol))
 845                        continue;
 846
 81920847                    if (!TypeDiscoveryHelper.ImplementsNeedlrPluginInterface(typeSymbol))
 848                        continue;
 849
 850                    // This is an internal plugin type in an assembly without [GenerateTypeRegistry]
 1851                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 1852                    missingTypeRegistryPlugins.Add(new MissingTypeRegistryPlugin(typeName, assemblySymbol.Name));
 853                }
 854            }
 855        }
 856
 434857        return new DiscoveryResult(injectableTypes, pluginTypes, decorators, inaccessibleTypes, missingTypeRegistryPlugi
 858    }
 859
 860    private static void CollectTypesFromAssembly(
 861        IAssemblySymbol assembly,
 862        IReadOnlyList<string>? namespacePrefixes,
 863        List<DiscoveredType> injectableTypes,
 864        List<DiscoveredPlugin> pluginTypes,
 865        List<DiscoveredDecorator> decorators,
 866        List<DiscoveredOpenDecorator> openDecorators,
 867        List<DiscoveredInterceptedService> interceptedServices,
 868        List<DiscoveredFactory> factories,
 869        List<DiscoveredOptions> options,
 870        List<DiscoveredHostedService> hostedServices,
 871        List<DiscoveredProvider> providers,
 872        List<InaccessibleType> inaccessibleTypes,
 873        Compilation compilation,
 874        bool isCurrentAssembly,
 875        string generatedNamespace)
 876    {
 3579478877        foreach (var typeSymbol in TypeDiscoveryHelper.GetAllTypes(assembly.GlobalNamespace))
 878        {
 1716194879            if (!TypeDiscoveryHelper.MatchesNamespacePrefix(typeSymbol, namespacePrefixes))
 880                continue;
 881
 882            // For referenced assemblies, check if the type would be registerable but is inaccessible
 1492084883            if (!isCurrentAssembly && TypeDiscoveryHelper.IsInternalOrLessAccessible(typeSymbol))
 884            {
 885                // Check if this type would have been registered if it were accessible
 71804886                if (TypeDiscoveryHelper.WouldBeInjectableIgnoringAccessibility(typeSymbol) ||
 71804887                    TypeDiscoveryHelper.WouldBePluginIgnoringAccessibility(typeSymbol))
 888                {
 2266889                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 2266890                    inaccessibleTypes.Add(new InaccessibleType(typeName, assembly.Name));
 891                }
 2266892                continue; // Skip further processing for inaccessible types
 893            }
 894
 895            // Check for [Options] attribute
 1420280896            if (OptionsAttributeHelper.HasOptionsAttribute(typeSymbol))
 897            {
 167898                var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 167899                var optionsAttrs = OptionsAttributeHelper.GetOptionsAttributes(typeSymbol);
 167900                var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 901
 902                // Detect positional record (record with primary constructor parameters)
 167903                var positionalRecordInfo = DetectPositionalRecord(typeSymbol);
 904
 905                // Extract bindable properties for AOT code generation
 167906                var properties = ExtractBindableProperties(typeSymbol);
 907
 680908                foreach (var optionsAttr in optionsAttrs)
 909                {
 910                    // Determine validator type and method
 173911                    var validatorTypeSymbol = optionsAttr.ValidatorType;
 173912                    var targetType = validatorTypeSymbol ?? typeSymbol; // Look for method on options class or external 
 173913                    var methodName = optionsAttr.ValidateMethod ?? "Validate"; // Convention: "Validate"
 914
 915                    // Find validation method using convention-based discovery
 173916                    var validatorMethodInfo = OptionsAttributeHelper.FindValidationMethod(targetType, methodName);
 173917                    OptionsValidatorInfo? validatorInfo = validatorMethodInfo.HasValue
 173918                        ? new OptionsValidatorInfo(validatorMethodInfo.Value.MethodName, validatorMethodInfo.Value.IsSta
 173919                        : null;
 920
 921                    // Infer section name if not provided
 173922                    var sectionName = optionsAttr.SectionName
 173923                        ?? Helpers.OptionsNamingHelper.InferSectionName(typeSymbol.Name);
 924
 173925                    var validatorTypeName = validatorTypeSymbol != null
 173926                        ? TypeDiscoveryHelper.GetFullyQualifiedName(validatorTypeSymbol)
 173927                        : null;
 928
 173929                    options.Add(new DiscoveredOptions(
 173930                        typeName,
 173931                        sectionName,
 173932                        optionsAttr.Name,
 173933                        optionsAttr.ValidateOnStart,
 173934                        assembly.Name,
 173935                        sourceFilePath,
 173936                        validatorInfo,
 173937                        optionsAttr.ValidateMethod,
 173938                        validatorTypeName,
 173939                        positionalRecordInfo,
 173940                        properties));
 941                }
 942            }
 943
 944            // Check for [GenerateFactory] attribute - these types get factories instead of direct registration
 1420280945            if (FactoryDiscoveryHelper.HasGenerateFactoryAttribute(typeSymbol))
 946            {
 27947                var factoryConstructors = FactoryDiscoveryHelper.GetFactoryConstructors(typeSymbol);
 27948                if (factoryConstructors.Count > 0)
 949                {
 950                    // Has at least one constructor with runtime params - generate factory
 26951                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 26952                    var interfaces = TypeDiscoveryHelper.GetRegisterableInterfaces(typeSymbol);
 35953                    var interfaceNames = interfaces.Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i)).ToArray();
 26954                    var generationMode = FactoryDiscoveryHelper.GetFactoryGenerationMode(typeSymbol);
 26955                    var returnTypeOverride = FactoryDiscoveryHelper.GetFactoryReturnInterfaceType(typeSymbol);
 26956                    var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 957
 26958                    factories.Add(new DiscoveredFactory(
 26959                        typeName,
 26960                        interfaceNames,
 26961                        assembly.Name,
 26962                        generationMode,
 26963                        factoryConstructors.ToArray(),
 26964                        returnTypeOverride,
 26965                        sourceFilePath));
 966
 26967                    continue; // Don't add to injectable types - factory handles registration
 968                }
 969                // If no runtime params, fall through to normal registration (with warning in future analyzer)
 970            }
 971
 972            // Check for DecoratorFor<T> attributes
 1420254973            var decoratorInfos = TypeDiscoveryHelper.GetDecoratorForAttributes(typeSymbol);
 2840548974            foreach (var decoratorInfo in decoratorInfos)
 975            {
 20976                var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 20977                decorators.Add(new DiscoveredDecorator(
 20978                    decoratorInfo.DecoratorTypeName,
 20979                    decoratorInfo.ServiceTypeName,
 20980                    decoratorInfo.Order,
 20981                    assembly.Name,
 20982                    sourceFilePath));
 983            }
 984
 985            // Check for OpenDecoratorFor attributes (source-gen only open generic decorators)
 1420254986            var openDecoratorInfos = OpenDecoratorDiscoveryHelper.GetOpenDecoratorForAttributes(typeSymbol);
 2840522987            foreach (var openDecoratorInfo in openDecoratorInfos)
 988            {
 7989                var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 7990                openDecorators.Add(new DiscoveredOpenDecorator(
 7991                    openDecoratorInfo.DecoratorType,
 7992                    openDecoratorInfo.OpenGenericInterface,
 7993                    openDecoratorInfo.Order,
 7994                    assembly.Name,
 7995                    sourceFilePath));
 996            }
 997
 998            // Check for Intercept attributes and collect intercepted services
 1420254999            if (InterceptorDiscoveryHelper.HasInterceptAttributes(typeSymbol))
 1000            {
 151001                var lifetime = TypeDiscoveryHelper.DetermineLifetime(typeSymbol);
 151002                if (lifetime.HasValue)
 1003                {
 151004                    var classLevelInterceptors = InterceptorDiscoveryHelper.GetInterceptAttributes(typeSymbol);
 151005                    var methodLevelInterceptors = InterceptorDiscoveryHelper.GetMethodLevelInterceptAttributes(typeSymbo
 151006                    var methods = InterceptorDiscoveryHelper.GetInterceptedMethods(typeSymbol, classLevelInterceptors, m
 1007
 151008                    if (methods.Count > 0)
 1009                    {
 151010                        var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 151011                        var interfaces = TypeDiscoveryHelper.GetRegisterableInterfaces(typeSymbol);
 301012                        var interfaceNames = interfaces.Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i)).ToArra
 1013
 1014                        // Collect all unique interceptor types
 151015                        var allInterceptorTypes = classLevelInterceptors
 151016                            .Concat(methodLevelInterceptors)
 181017                            .Select(i => i.InterceptorTypeName)
 151018                            .Distinct()
 151019                            .ToArray();
 1020
 151021                        var interceptedSourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 1022
 151023                        interceptedServices.Add(new DiscoveredInterceptedService(
 151024                            typeName,
 151025                            interfaceNames,
 151026                            assembly.Name,
 151027                            lifetime.Value,
 151028                            methods.ToArray(),
 151029                            allInterceptorTypes,
 151030                            interceptedSourceFilePath));
 1031                    }
 1032                }
 1033            }
 1034
 1035            // Check for injectable types (but skip types that are providers, which are handled separately)
 14202541036            if (TypeDiscoveryHelper.IsInjectableType(typeSymbol, isCurrentAssembly) && !ProviderDiscoveryHelper.HasProvi
 1037            {
 1038                // Determine lifetime first - only include types that are actually injectable
 4001901039                var lifetime = TypeDiscoveryHelper.DetermineLifetime(typeSymbol);
 4001901040                if (lifetime.HasValue)
 1041                {
 2280821042                    var interfaces = TypeDiscoveryHelper.GetRegisterableInterfaces(typeSymbol);
 2280821043                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 2283281044                    var interfaceNames = interfaces.Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i)).ToArray();
 1045
 1046                    // Check for [DeferToContainer] attribute - use declared types instead of discovered constructors
 2280821047                    var deferredParams = TypeDiscoveryHelper.GetDeferToContainerParameterTypes(typeSymbol);
 1048                    TypeDiscoveryHelper.ConstructorParameterInfo[] constructorParams;
 2280821049                    if (deferredParams != null)
 1050                    {
 1051                        // DeferToContainer doesn't support keyed services - convert to simple params
 101052                        constructorParams = deferredParams.Select(t => new TypeDiscoveryHelper.ConstructorParameterInfo(
 1053                    }
 1054                    else
 1055                    {
 2280771056                        constructorParams = TypeDiscoveryHelper.GetBestConstructorParametersWithKeys(typeSymbol)?.ToArra
 1057                    }
 1058
 1059                    // Get source file path for breadcrumbs (null for external assemblies)
 2280821060                    var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 1061
 1062                    // Get [Keyed] attribute keys
 2280821063                    var serviceKeys = TypeDiscoveryHelper.GetKeyedServiceKeys(typeSymbol);
 1064
 1065                    // Check if this type implements IDisposable or IAsyncDisposable
 2280821066                    var isDisposable = TypeDiscoveryHelper.IsDisposableType(typeSymbol);
 1067
 2280821068                    injectableTypes.Add(new DiscoveredType(typeName, interfaceNames, assembly.Name, lifetime.Value, cons
 1069                }
 1070            }
 1071
 1072            // Check for hosted service types (BackgroundService or IHostedService implementations)
 14202541073            if (TypeDiscoveryHelper.IsHostedServiceType(typeSymbol, isCurrentAssembly))
 1074            {
 71075                var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 71076                var constructorParams = TypeDiscoveryHelper.GetBestConstructorParametersWithKeys(typeSymbol)?.ToArray() 
 71077                var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 1078
 71079                hostedServices.Add(new DiscoveredHostedService(
 71080                    typeName,
 71081                    assembly.Name,
 71082                    GeneratorLifetime.Singleton, // Hosted services are always singleton
 71083                    constructorParams,
 71084                    sourceFilePath));
 1085            }
 1086
 1087            // Check for [Provider] attribute
 14202541088            if (ProviderDiscoveryHelper.HasProviderAttribute(typeSymbol))
 1089            {
 181090                var discoveredProvider = ProviderDiscoveryHelper.DiscoverProvider(typeSymbol, assembly.Name, generatedNa
 181091                if (discoveredProvider.HasValue)
 1092                {
 181093                    providers.Add(discoveredProvider.Value);
 1094                }
 1095            }
 1096
 1097            // Check for plugin types (concrete class with parameterless ctor and interfaces)
 14202541098            if (TypeDiscoveryHelper.IsPluginType(typeSymbol, isCurrentAssembly))
 1099            {
 3952941100                var pluginInterfaces = TypeDiscoveryHelper.GetPluginInterfaces(typeSymbol);
 3952941101                if (pluginInterfaces.Count > 0)
 1102                {
 13791103                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 27681104                    var interfaceNames = pluginInterfaces.Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i)).ToAr
 13791105                    var attributeNames = TypeDiscoveryHelper.GetPluginAttributes(typeSymbol).ToArray();
 13791106                    var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 13791107                    var order = PluginOrderHelper.GetPluginOrder(typeSymbol);
 1108
 13791109                    pluginTypes.Add(new DiscoveredPlugin(typeName, interfaceNames, assembly.Name, attributeNames, source
 1110                }
 1111            }
 1112
 1113            // Check for IHubRegistrationPlugin implementations
 1114            // NOTE: SignalR hub discovery is now handled by NexusLabs.Needlr.SignalR.Generators
 1115
 1116            // Check for SemanticKernel plugin types (classes/statics with [KernelFunction] methods)
 1117            // NOTE: SemanticKernel plugin discovery is now handled by NexusLabs.Needlr.SemanticKernel.Generators
 1118        }
 735451119    }
 1120
 1121    private static string GenerateTypeRegistrySource(DiscoveryResult discoveryResult, string assemblyName, BreadcrumbWri
 1122    {
 4341123        var builder = new StringBuilder();
 4341124        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblyName);
 4341125        var hasOptions = discoveryResult.Options.Count > 0;
 1126
 4341127        breadcrumbs.WriteFileHeader(builder, assemblyName, "Needlr Type Registry");
 4341128        builder.AppendLine("#nullable enable");
 4341129        builder.AppendLine();
 4341130        builder.AppendLine("using System;");
 4341131        builder.AppendLine("using System.Collections.Generic;");
 4341132        builder.AppendLine();
 4341133        if (hasOptions)
 1134        {
 1461135            builder.AppendLine("using Microsoft.Extensions.Configuration;");
 1461136            if (isAotProject)
 1137            {
 781138                builder.AppendLine("using Microsoft.Extensions.Options;");
 1139            }
 1140        }
 4341141        builder.AppendLine("using Microsoft.Extensions.DependencyInjection;");
 4341142        builder.AppendLine();
 4341143        builder.AppendLine("using NexusLabs.Needlr;");
 4341144        builder.AppendLine("using NexusLabs.Needlr.Generators;");
 4341145        builder.AppendLine();
 4341146        builder.AppendLine($"namespace {safeAssemblyName}.Generated;");
 4341147        builder.AppendLine();
 4341148        builder.AppendLine("/// <summary>");
 4341149        builder.AppendLine("/// Compile-time generated registry of injectable types and plugins.");
 4341150        builder.AppendLine("/// This eliminates the need for runtime reflection-based type discovery.");
 4341151        builder.AppendLine("/// </summary>");
 4341152        builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\", \"1
 4341153        builder.AppendLine("public static class TypeRegistry");
 4341154        builder.AppendLine("{");
 1155
 4341156        GenerateInjectableTypesArray(builder, discoveryResult.InjectableTypes, breadcrumbs, projectDirectory);
 4341157        builder.AppendLine();
 4341158        GeneratePluginTypesArray(builder, discoveryResult.PluginTypes, breadcrumbs, projectDirectory);
 1159
 4341160        builder.AppendLine();
 4341161        builder.AppendLine("    /// <summary>");
 4341162        builder.AppendLine("    /// Gets all injectable types discovered at compile time.");
 4341163        builder.AppendLine("    /// </summary>");
 4341164        builder.AppendLine("    /// <returns>A read-only list of injectable type information.</returns>");
 4341165        builder.AppendLine("    public static IReadOnlyList<InjectableTypeInfo> GetInjectableTypes() => _types;");
 4341166        builder.AppendLine();
 4341167        builder.AppendLine("    /// <summary>");
 4341168        builder.AppendLine("    /// Gets all plugin types discovered at compile time.");
 4341169        builder.AppendLine("    /// </summary>");
 4341170        builder.AppendLine("    /// <returns>A read-only list of plugin type information.</returns>");
 4341171        builder.AppendLine("    public static IReadOnlyList<PluginTypeInfo> GetPluginTypes() => _plugins;");
 1172
 4341173        if (hasOptions)
 1174        {
 1461175            builder.AppendLine();
 1461176            GenerateRegisterOptionsMethod(builder, discoveryResult.Options, safeAssemblyName, breadcrumbs, projectDirect
 1177        }
 1178
 4341179        if (discoveryResult.Providers.Count > 0)
 1180        {
 171181            builder.AppendLine();
 171182            GenerateRegisterProvidersMethod(builder, discoveryResult.Providers, safeAssemblyName, breadcrumbs, projectDi
 1183        }
 1184
 4341185        builder.AppendLine();
 4341186        GenerateApplyDecoratorsMethod(builder, discoveryResult.Decorators, discoveryResult.InterceptedServices.Count > 0
 1187
 4341188        if (discoveryResult.HostedServices.Count > 0)
 1189        {
 61190            builder.AppendLine();
 61191            GenerateRegisterHostedServicesMethod(builder, discoveryResult.HostedServices, breadcrumbs, projectDirectory)
 1192        }
 1193
 4341194        builder.AppendLine("}");
 1195
 4341196        return builder.ToString();
 1197    }
 1198
 1199    private static string GenerateModuleInitializerBootstrapSource(string assemblyName, IReadOnlyList<string> referenced
 1200    {
 4341201        var builder = new StringBuilder();
 4341202        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblyName);
 1203
 4341204        breadcrumbs.WriteFileHeader(builder, assemblyName, "Needlr Source-Gen Bootstrap");
 4341205        builder.AppendLine("#nullable enable");
 4341206        builder.AppendLine();
 4341207        builder.AppendLine("using System.Runtime.CompilerServices;");
 4341208        builder.AppendLine();
 4341209        builder.AppendLine("using Microsoft.Extensions.Configuration;");
 4341210        builder.AppendLine("using Microsoft.Extensions.DependencyInjection;");
 4341211        builder.AppendLine();
 4341212        builder.AppendLine($"namespace {safeAssemblyName}.Generated;");
 4341213        builder.AppendLine();
 4341214        builder.AppendLine("internal static class NeedlrSourceGenModuleInitializer");
 4341215        builder.AppendLine("{");
 4341216        builder.AppendLine("    [global::System.Runtime.CompilerServices.ModuleInitializer]");
 4341217        builder.AppendLine("    internal static void Initialize()");
 4341218        builder.AppendLine("    {");
 1219
 1220        // Generate ForceLoadAssemblies call if there are referenced assemblies with [GenerateTypeRegistry]
 4341221        if (referencedAssemblies.Count > 0)
 1222        {
 161223            builder.AppendLine("        // Force-load referenced assemblies to ensure their module initializers run");
 161224            builder.AppendLine("        ForceLoadReferencedAssemblies();");
 161225            builder.AppendLine();
 1226        }
 1227
 4341228        builder.AppendLine("        global::NexusLabs.Needlr.Generators.NeedlrSourceGenBootstrap.Register(");
 4341229        builder.AppendLine($"            global::{safeAssemblyName}.Generated.TypeRegistry.GetInjectableTypes,");
 4341230        builder.AppendLine($"            global::{safeAssemblyName}.Generated.TypeRegistry.GetPluginTypes,");
 1231
 1232        // Generate the decorator/factory/provider applier lambda
 4341233        if (hasFactories || hasProviders)
 1234        {
 431235            builder.AppendLine("            services =>");
 431236            builder.AppendLine("            {");
 431237            builder.AppendLine($"                global::{safeAssemblyName}.Generated.TypeRegistry.ApplyDecorators((ISer
 431238            if (hasFactories)
 1239            {
 261240                builder.AppendLine($"                global::{safeAssemblyName}.Generated.FactoryRegistrations.RegisterF
 1241            }
 431242            if (hasProviders)
 1243            {
 171244                builder.AppendLine($"                global::{safeAssemblyName}.Generated.TypeRegistry.RegisterProviders
 1245            }
 431246            builder.AppendLine("            },");
 1247        }
 1248        else
 1249        {
 3911250            builder.AppendLine($"            services => global::{safeAssemblyName}.Generated.TypeRegistry.ApplyDecorato
 1251        }
 1252
 1253        // Generate the options registrar lambda for NeedlrSourceGenBootstrap (for backward compat)
 4341254        if (hasOptions)
 1255        {
 1461256            builder.AppendLine($"            (services, config) => global::{safeAssemblyName}.Generated.TypeRegistry.Reg
 1257        }
 1258        else
 1259        {
 2881260            builder.AppendLine("            null);");
 1261        }
 1262
 1263        // Also register with SourceGenRegistry (for ConfiguredSyringe without Generators.Attributes dependency)
 4341264        if (hasOptions)
 1265        {
 1461266            builder.AppendLine();
 1461267            builder.AppendLine("        // Register options with core SourceGenRegistry for ConfiguredSyringe");
 1461268            builder.AppendLine($"        global::NexusLabs.Needlr.SourceGenRegistry.RegisterOptionsRegistrar(");
 1461269            builder.AppendLine($"            (services, config) => global::{safeAssemblyName}.Generated.TypeRegistry.Reg
 1270        }
 1271
 4341272        builder.AppendLine("    }");
 1273
 1274        // Generate ForceLoadReferencedAssemblies method if needed
 4341275        if (referencedAssemblies.Count > 0)
 1276        {
 161277            builder.AppendLine();
 161278            builder.AppendLine("    /// <summary>");
 161279            builder.AppendLine("    /// Forces referenced assemblies with [GenerateTypeRegistry] to load,");
 161280            builder.AppendLine("    /// ensuring their module initializers execute and register their types.");
 161281            builder.AppendLine("    /// </summary>");
 161282            builder.AppendLine("    /// <remarks>");
 161283            builder.AppendLine("    /// Without this, transitive dependencies that are never directly referenced");
 161284            builder.AppendLine("    /// in code would not be loaded by the CLR, and their plugins would not be discovere
 161285            builder.AppendLine("    /// </remarks>");
 161286            builder.AppendLine("    [MethodImpl(MethodImplOptions.NoInlining)]");
 161287            builder.AppendLine("    private static void ForceLoadReferencedAssemblies()");
 161288            builder.AppendLine("    {");
 1289
 661290            foreach (var referencedAssembly in referencedAssemblies)
 1291            {
 171292                var safeRefAssemblyName = GeneratorHelpers.SanitizeIdentifier(referencedAssembly);
 171293                builder.AppendLine($"        _ = typeof(global::{safeRefAssemblyName}.Generated.TypeRegistry).Assembly;"
 1294            }
 1295
 161296            builder.AppendLine("    }");
 1297        }
 1298
 4341299        builder.AppendLine("}");
 1300
 4341301        return builder.ToString();
 1302    }
 1303
 1304    private static void GenerateInjectableTypesArray(StringBuilder builder, IReadOnlyList<DiscoveredType> types, Breadcr
 1305    {
 4341306        builder.AppendLine("    private static readonly InjectableTypeInfo[] _types =");
 4341307        builder.AppendLine("    [");
 1308
 2562581309        var typesByAssembly = types.GroupBy(t => t.AssemblyName).OrderBy(g => g.Key);
 1310
 563521311        foreach (var group in typesByAssembly)
 1312        {
 277421313            breadcrumbs.WriteInlineComment(builder, "        ", $"From {group.Key}");
 1314
 7397301315            foreach (var type in group.OrderBy(t => t.TypeName))
 1316            {
 1317                // Write breadcrumb for this type
 2280821318                if (breadcrumbs.Level == BreadcrumbLevel.Verbose)
 1319                {
 151321320                    var sourcePath = type.SourceFilePath != null
 151321321                        ? BreadcrumbWriter.GetRelativeSourcePath(type.SourceFilePath, projectDirectory)
 151321322                        : $"[{type.AssemblyName}]";
 151321323                    var interfaces = type.InterfaceNames.Length > 0
 111324                        ? string.Join(", ", type.InterfaceNames.Select(i => i.Split('.').Last()))
 151321325                        : "none";
 151321326                    var keysInfo = type.ServiceKeys.Length > 0
 01327                        ? $"Keys: {string.Join(", ", type.ServiceKeys.Select(k => $"\"{k}\""))}"
 151321328                        : null;
 1329
 151321330                    if (keysInfo != null)
 1331                    {
 01332                        breadcrumbs.WriteVerboseBox(builder, "        ",
 01333                            $"{type.TypeName.Split('.').Last()} → {interfaces}",
 01334                            $"Source: {sourcePath}",
 01335                            $"Lifetime: {type.Lifetime}",
 01336                            keysInfo);
 1337                    }
 1338                    else
 1339                    {
 151321340                        breadcrumbs.WriteVerboseBox(builder, "        ",
 151321341                            $"{type.TypeName.Split('.').Last()} → {interfaces}",
 151321342                            $"Source: {sourcePath}",
 151321343                            $"Lifetime: {type.Lifetime}");
 1344                    }
 1345                }
 1346
 2280821347                builder.Append($"        new(typeof({type.TypeName}), ");
 1348
 1349                // Interfaces
 2280821350                if (type.InterfaceNames.Length == 0)
 1351                {
 2278411352                    builder.Append("Array.Empty<Type>(), ");
 1353                }
 1354                else
 1355                {
 2411356                    builder.Append("[");
 4871357                    builder.Append(string.Join(", ", type.InterfaceNames.Select(i => $"typeof({i})")));
 2411358                    builder.Append("], ");
 1359                }
 1360
 1361                // Lifetime
 2280821362                builder.Append($"InjectableLifetime.{type.Lifetime}, ");
 1363
 1364                // Factory lambda - resolves dependencies and creates instance without reflection
 2280821365                builder.Append("sp => new ");
 2280821366                builder.Append(type.TypeName);
 2280821367                builder.Append("(");
 2280821368                if (type.ConstructorParameters.Length > 0)
 1369                {
 456681370                    var parameterExpressions = type.ConstructorParameters
 996341371                        .Select(p => p.IsKeyed
 996341372                            ? $"sp.GetRequiredKeyedService<{p.TypeName}>(\"{GeneratorHelpers.EscapeStringLiteral(p.Servi
 996341373                            : $"sp.GetRequiredService<{p.TypeName}>()");
 456681374                    builder.Append(string.Join(", ", parameterExpressions));
 1375                }
 2280821376                builder.Append("), ");
 1377
 1378                // Service keys from [Keyed] attributes
 2280821379                if (type.ServiceKeys.Length == 0)
 1380                {
 2280771381                    builder.AppendLine("Array.Empty<string>()),");
 1382                }
 1383                else
 1384                {
 51385                    builder.Append("[");
 101386                    builder.Append(string.Join(", ", type.ServiceKeys.Select(k => $"\"{GeneratorHelpers.EscapeStringLite
 51387                    builder.AppendLine("]),");
 1388                }
 1389            }
 1390        }
 1391
 4341392        builder.AppendLine("    ];");
 4341393    }
 1394
 1395    private static void GeneratePluginTypesArray(StringBuilder builder, IReadOnlyList<DiscoveredPlugin> plugins, Breadcr
 1396    {
 4341397        builder.AppendLine("    private static readonly PluginTypeInfo[] _plugins =");
 4341398        builder.AppendLine("    [");
 1399
 1400        // Sort plugins by Order first, then by TypeName for determinism
 4341401        var sortedPlugins = plugins
 13611402            .OrderBy(p => p.Order)
 13611403            .ThenBy(p => p.TypeName, StringComparer.Ordinal)
 4341404            .ToList();
 1405
 1406        // Group for breadcrumb display, but maintain the sorted order
 23671407        var pluginsByAssembly = sortedPlugins.GroupBy(p => p.AssemblyName).OrderBy(g => g.Key);
 1408
 19761409        foreach (var group in pluginsByAssembly)
 1410        {
 5541411            breadcrumbs.WriteInlineComment(builder, "        ", $"From {group.Key}");
 1412
 1413            // Maintain order within assembly group
 66241414            foreach (var plugin in group.OrderBy(p => p.Order).ThenBy(p => p.TypeName, StringComparer.Ordinal))
 1415            {
 1416                // Write verbose breadcrumb for this plugin
 13791417                if (breadcrumbs.Level == BreadcrumbLevel.Verbose)
 1418                {
 911419                    var sourcePath = plugin.SourceFilePath != null
 911420                        ? BreadcrumbWriter.GetRelativeSourcePath(plugin.SourceFilePath, projectDirectory)
 911421                        : $"[{plugin.AssemblyName}]";
 911422                    var interfaces = plugin.InterfaceNames.Length > 0
 911423                        ? string.Join(", ", plugin.InterfaceNames.Select(i => i.Split('.').Last()))
 911424                        : "none";
 911425                    var orderInfo = plugin.Order != 0 ? $"Order: {plugin.Order}" : "Order: 0 (default)";
 1426
 911427                    breadcrumbs.WriteVerboseBox(builder, "        ",
 911428                        $"Plugin: {plugin.TypeName.Split('.').Last()}",
 911429                        $"Source: {sourcePath}",
 911430                        $"Implements: {interfaces}",
 911431                        orderInfo);
 1432                }
 12881433                else if (breadcrumbs.Level == BreadcrumbLevel.Minimal && plugin.Order != 0)
 1434                {
 1435                    // Show order in minimal mode only if non-default
 01436                    breadcrumbs.WriteInlineComment(builder, "        ", $"{plugin.TypeName.Split('.').Last()} (Order: {p
 1437                }
 1438
 13791439                builder.Append($"        new(typeof({plugin.TypeName}), ");
 1440
 1441                // Interfaces
 13791442                if (plugin.InterfaceNames.Length == 0)
 1443                {
 01444                    builder.Append("Array.Empty<Type>(), ");
 1445                }
 1446                else
 1447                {
 13791448                    builder.Append("[");
 27681449                    builder.Append(string.Join(", ", plugin.InterfaceNames.Select(i => $"typeof({i})")));
 13791450                    builder.Append("], ");
 1451                }
 1452
 1453                // Factory lambda - no Activator.CreateInstance needed
 13791454                builder.Append($"() => new {plugin.TypeName}(), ");
 1455
 1456                // Attributes
 13791457                if (plugin.AttributeNames.Length == 0)
 1458                {
 13391459                    builder.Append("Array.Empty<Type>(), ");
 1460                }
 1461                else
 1462                {
 401463                    builder.Append("[");
 961464                    builder.Append(string.Join(", ", plugin.AttributeNames.Select(a => $"typeof({a})")));
 401465                    builder.Append("], ");
 1466                }
 1467
 1468                // Order
 13791469                builder.AppendLine($"{plugin.Order}),");
 1470            }
 1471        }
 1472
 4341473        builder.AppendLine("    ];");
 4341474    }
 1475
 1476    private static void GenerateRegisterOptionsMethod(StringBuilder builder, IReadOnlyList<DiscoveredOptions> options, s
 1477    {
 1461478        builder.AppendLine("    /// <summary>");
 1461479        builder.AppendLine("    /// Registers all discovered options types with the service collection.");
 1461480        builder.AppendLine("    /// This binds configuration sections to strongly-typed options classes.");
 1461481        builder.AppendLine("    /// </summary>");
 1461482        builder.AppendLine("    /// <param name=\"services\">The service collection to configure.</param>");
 1461483        builder.AppendLine("    /// <param name=\"configuration\">The configuration root to bind options from.</param>")
 1461484        builder.AppendLine("    public static void RegisterOptions(IServiceCollection services, IConfiguration configura
 1461485        builder.AppendLine("    {");
 1486
 1461487        if (options.Count == 0)
 1488        {
 01489            breadcrumbs.WriteInlineComment(builder, "        ", "No options types discovered");
 1490        }
 1461491        else if (isAotProject)
 1492        {
 781493            GenerateAotOptionsRegistration(builder, options, safeAssemblyName, breadcrumbs, projectDirectory);
 1494        }
 1495        else
 1496        {
 681497            GenerateReflectionOptionsRegistration(builder, options, safeAssemblyName, breadcrumbs);
 1498        }
 1499
 1461500        builder.AppendLine("    }");
 1461501    }
 1502
 1503    private static void GenerateReflectionOptionsRegistration(StringBuilder builder, IReadOnlyList<DiscoveredOptions> op
 1504    {
 1505        // Track external validators to register (avoid duplicates)
 681506        var externalValidatorsToRegister = new HashSet<string>();
 1507
 2921508        foreach (var opt in options)
 1509        {
 781510            var typeName = opt.TypeName;
 1511
 781512            if (opt.ValidateOnStart)
 1513            {
 1514                // Use AddOptions pattern for validation support
 1515                // services.AddOptions<T>().BindConfiguration("Section").ValidateDataAnnotations().ValidateOnStart();
 231516                builder.Append($"        services.AddOptions<{typeName}>");
 1517
 231518                if (opt.IsNamed)
 1519                {
 11520                    builder.Append($"(\"{opt.Name}\")");
 1521                }
 1522                else
 1523                {
 221524                    builder.Append("()");
 1525                }
 1526
 231527                builder.Append($".BindConfiguration(\"{opt.SectionName}\")");
 231528                builder.Append(".ValidateDataAnnotations()");
 231529                builder.AppendLine(".ValidateOnStart();");
 1530
 1531                // Register source-generated DataAnnotations validator if present
 1532                // This runs alongside .ValidateDataAnnotations() - source-gen handles supported attributes,
 1533                // reflection fallback handles unsupported attributes (like [CustomValidation])
 231534                if (opt.HasDataAnnotations)
 1535                {
 21536                    var shortTypeName = GeneratorHelpers.GetShortTypeName(typeName);
 21537                    var dataAnnotationsValidatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}DataAn
 21538                    builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOpt
 1539                }
 1540
 1541                // If there's a custom validator method, register the generated validator
 231542                if (opt.HasValidatorMethod)
 1543                {
 121544                    var shortTypeName = GeneratorHelpers.GetShortTypeName(typeName);
 121545                    var validatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}Validator";
 121546                    builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOpt
 1547
 1548                    // If external validator with instance method, register it too
 121549                    if (opt.HasExternalValidator && opt.ValidatorMethod != null && !opt.ValidatorMethod.Value.IsStatic)
 1550                    {
 31551                        externalValidatorsToRegister.Add(opt.ValidatorTypeName!);
 1552                    }
 1553                }
 1554            }
 551555            else if (opt.IsNamed)
 1556            {
 1557                // Named options: OptionsConfigurationServiceCollectionExtensions.Configure<T>(services, "name", section
 81558                builder.AppendLine($"        global::Microsoft.Extensions.DependencyInjection.OptionsConfigurationServic
 1559            }
 1560            else
 1561            {
 1562                // Default options: OptionsConfigurationServiceCollectionExtensions.Configure<T>(services, section)
 471563                builder.AppendLine($"        global::Microsoft.Extensions.DependencyInjection.OptionsConfigurationServic
 1564            }
 1565        }
 1566
 1567        // Register external validators that have instance methods
 1421568        foreach (var validatorType in externalValidatorsToRegister)
 1569        {
 31570            builder.AppendLine($"        services.AddSingleton<{validatorType}>();");
 1571        }
 681572    }
 1573
 1574    private static void GenerateAotOptionsRegistration(StringBuilder builder, IReadOnlyList<DiscoveredOptions> options, 
 1575    {
 781576        breadcrumbs.WriteInlineComment(builder, "        ", "AOT-compatible options binding (no reflection)");
 1577
 781578        var externalValidatorsToRegister = new HashSet<string>();
 1579
 3301580        foreach (var opt in options)
 1581        {
 871582            var typeName = opt.TypeName;
 871583            builder.AppendLine();
 871584            builder.AppendLine($"        // Bind {opt.SectionName} section to {GeneratorHelpers.GetShortTypeName(typeNam
 1585
 1586            // Choose binding pattern based on type characteristics
 871587            if (opt.IsPositionalRecord)
 1588            {
 1589                // Positional records: Use constructor binding with Options.Create
 51590                GeneratePositionalRecordBinding(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 1591            }
 821592            else if (opt.HasInitOnlyProperties)
 1593            {
 1594                // Classes/records with init-only properties: Use object initializer with Options.Create
 71595                GenerateInitOnlyBinding(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 1596            }
 1597            else
 1598            {
 1599                // Regular classes with setters: Use Configure delegate pattern
 751600                GenerateConfigureBinding(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 1601            }
 1602        }
 1603
 1604        // Register external validators that have instance methods
 1581605        foreach (var validatorType in externalValidatorsToRegister)
 1606        {
 11607            builder.AppendLine($"        services.AddSingleton<{validatorType}>();");
 1608        }
 781609    }
 1610
 1611    private static void GenerateConfigureBinding(StringBuilder builder, DiscoveredOptions opt, string safeAssemblyName, 
 1612    {
 751613        var typeName = opt.TypeName;
 1614
 751615        if (opt.IsNamed)
 1616        {
 131617            builder.AppendLine($"        services.AddOptions<{typeName}>(\"{opt.Name}\")");
 1618        }
 1619        else
 1620        {
 621621            builder.AppendLine($"        services.AddOptions<{typeName}>()");
 1622        }
 1623
 751624        builder.AppendLine("            .Configure<IConfiguration>((options, config) =>");
 751625        builder.AppendLine("            {");
 751626        builder.AppendLine($"                var section = config.GetSection(\"{opt.SectionName}\");");
 1627
 1628        // Generate property binding for each property
 751629        var propIndex = 0;
 3901630        foreach (var prop in opt.Properties)
 1631        {
 1201632            GeneratePropertyBinding(builder, prop, propIndex);
 1201633            propIndex++;
 1634        }
 1635
 751636        builder.Append("            })");
 1637
 1638        // Add validation chain if ValidateOnStart is enabled
 751639        if (opt.ValidateOnStart)
 1640        {
 201641            builder.AppendLine();
 201642            builder.Append("            .ValidateOnStart()");
 1643        }
 1644
 751645        builder.AppendLine(";");
 1646
 751647        RegisterValidator(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 751648    }
 1649
 1650    private static void GenerateInitOnlyBinding(StringBuilder builder, DiscoveredOptions opt, string safeAssemblyName, H
 1651    {
 71652        var typeName = opt.TypeName;
 1653
 1654        // Use AddSingleton with IOptions<T> factory pattern for init-only
 71655        builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IOptions<{typeName}>>(sp
 71656        builder.AppendLine("        {");
 71657        builder.AppendLine($"            var config = sp.GetRequiredService<IConfiguration>();");
 71658        builder.AppendLine($"            var section = config.GetSection(\"{opt.SectionName}\");");
 1659
 1660        // Generate parsing variables first
 71661        var propIndex = 0;
 441662        foreach (var prop in opt.Properties)
 1663        {
 151664            GeneratePropertyParseVariable(builder, prop, propIndex);
 151665            propIndex++;
 1666        }
 1667
 1668        // Create object with initializer
 71669        builder.AppendLine($"            return global::Microsoft.Extensions.Options.Options.Create(new {typeName}");
 71670        builder.AppendLine("            {");
 1671
 71672        propIndex = 0;
 441673        foreach (var prop in opt.Properties)
 1674        {
 151675            var comma = propIndex < opt.Properties.Count - 1 ? "," : "";
 151676            GeneratePropertyInitializer(builder, prop, propIndex, comma);
 151677            propIndex++;
 1678        }
 1679
 71680        builder.AppendLine("            });");
 71681        builder.AppendLine("        });");
 1682
 1683        // For validation with factory pattern, we need to register the validator separately
 71684        if (opt.ValidateOnStart)
 1685        {
 11686            RegisterValidatorForFactory(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 1687        }
 71688    }
 1689
 1690    private static void GeneratePositionalRecordBinding(StringBuilder builder, DiscoveredOptions opt, string safeAssembl
 1691    {
 51692        var typeName = opt.TypeName;
 51693        var recordInfo = opt.PositionalRecordInfo!.Value;
 1694
 1695        // Use AddSingleton with IOptions<T> factory pattern for positional records
 51696        builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IOptions<{typeName}>>(sp
 51697        builder.AppendLine("        {");
 51698        builder.AppendLine($"            var config = sp.GetRequiredService<IConfiguration>();");
 51699        builder.AppendLine($"            var section = config.GetSection(\"{opt.SectionName}\");");
 1700
 1701        // Generate parsing variables for each constructor parameter
 51702        var paramIndex = 0;
 361703        foreach (var param in recordInfo.Parameters)
 1704        {
 131705            GenerateParameterParseVariable(builder, param, paramIndex);
 131706            paramIndex++;
 1707        }
 1708
 1709        // Create record with constructor
 51710        builder.Append($"            return global::Microsoft.Extensions.Options.Options.Create(new {typeName}(");
 1711
 51712        paramIndex = 0;
 361713        foreach (var param in recordInfo.Parameters)
 1714        {
 211715            if (paramIndex > 0) builder.Append(", ");
 131716            builder.Append($"p{paramIndex}");
 131717            paramIndex++;
 1718        }
 1719
 51720        builder.AppendLine("));");
 51721        builder.AppendLine("        });");
 1722
 1723        // For validation with factory pattern, we need to register the validator separately
 51724        if (opt.ValidateOnStart)
 1725        {
 01726            RegisterValidatorForFactory(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 1727        }
 51728    }
 1729
 1730    private static void GeneratePropertyParseVariable(StringBuilder builder, OptionsPropertyInfo prop, int index)
 1731    {
 151732        var varName = $"p{index}";
 151733        var typeName = prop.TypeName;
 151734        var baseTypeName = GetBaseTypeName(typeName);
 1735
 1736        // Handle complex types
 151737        if (prop.ComplexTypeKind != ComplexTypeKind.None)
 1738        {
 21739            GenerateComplexTypeParseVariable(builder, prop, index);
 21740            return;
 1741        }
 1742
 1743        // Handle enums
 131744        if (prop.IsEnum && prop.EnumTypeName != null)
 1745        {
 01746            var defaultVal = prop.IsNullable ? "null" : "default";
 01747            builder.AppendLine($"            var {varName} = section[\"{prop.Name}\"] is {{ }} v{index} && global::Syste
 01748            return;
 1749        }
 1750
 1751        // Handle primitives
 131752        if (baseTypeName == "string" || baseTypeName == "global::System.String")
 1753        {
 71754            var defaultVal = prop.IsNullable ? "null" : "\"\"";
 71755            builder.AppendLine($"            var {varName} = section[\"{prop.Name}\"] ?? {defaultVal};");
 1756        }
 61757        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 1758        {
 51759            var defaultVal = prop.IsNullable ? "null" : "0";
 51760            builder.AppendLine($"            var {varName} = section[\"{prop.Name}\"] is {{ }} v{index} && int.TryParse(
 1761        }
 11762        else if (baseTypeName == "bool" || baseTypeName == "global::System.Boolean")
 1763        {
 11764            var defaultVal = prop.IsNullable ? "null" : "false";
 11765            builder.AppendLine($"            var {varName} = section[\"{prop.Name}\"] is {{ }} v{index} && bool.TryParse
 1766        }
 01767        else if (baseTypeName == "double" || baseTypeName == "global::System.Double")
 1768        {
 01769            var defaultVal = prop.IsNullable ? "null" : "0.0";
 01770            builder.AppendLine($"            var {varName} = section[\"{prop.Name}\"] is {{ }} v{index} && double.TryPar
 1771        }
 1772        else
 1773        {
 1774            // Default to default value for unsupported types
 01775            builder.AppendLine($"            var {varName} = default({typeName}); // Unsupported type");
 1776        }
 01777    }
 1778
 1779    private static void GenerateComplexTypeParseVariable(StringBuilder builder, OptionsPropertyInfo prop, int index)
 1780    {
 21781        var varName = $"p{index}";
 21782        var sectionVar = $"sec{index}";
 1783
 21784        switch (prop.ComplexTypeKind)
 1785        {
 1786            case ComplexTypeKind.NestedObject:
 11787                builder.AppendLine($"            var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 11788                builder.AppendLine($"            var {varName} = new {GetNonNullableTypeName(prop.TypeName)}();");
 11789                if (prop.NestedProperties != null)
 1790                {
 11791                    var nestedIndex = index * 100;
 61792                    foreach (var nested in prop.NestedProperties)
 1793                    {
 21794                        GenerateNestedPropertyAssignment(builder, nested, nestedIndex, varName, sectionVar);
 21795                        nestedIndex++;
 1796                    }
 1797                }
 1798                break;
 1799
 1800            case ComplexTypeKind.List:
 11801                var listElemType = prop.ElementTypeName ?? "string";
 11802                builder.AppendLine($"            var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 11803                builder.AppendLine($"            var {varName} = new global::System.Collections.Generic.List<{listElemTy
 11804                builder.AppendLine($"            foreach (var child in {sectionVar}.GetChildren())");
 11805                builder.AppendLine("            {");
 11806                if (prop.NestedProperties != null && prop.NestedProperties.Count > 0)
 1807                {
 01808                    builder.AppendLine($"                var item = new {listElemType}();");
 01809                    var ni = index * 100;
 01810                    foreach (var np in prop.NestedProperties)
 1811                    {
 01812                        GenerateChildPropertyAssignment(builder, np, ni, "item", "child");
 01813                        ni++;
 1814                    }
 01815                    builder.AppendLine($"                {varName}.Add(item);");
 1816                }
 1817                else
 1818                {
 11819                    builder.AppendLine($"                if (child.Value is {{ }} val) {varName}.Add(val);");
 1820                }
 11821                builder.AppendLine("            }");
 11822                break;
 1823
 1824            case ComplexTypeKind.Dictionary:
 01825                var dictValType = prop.ElementTypeName ?? "string";
 01826                builder.AppendLine($"            var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 01827                builder.AppendLine($"            var {varName} = new global::System.Collections.Generic.Dictionary<strin
 01828                builder.AppendLine($"            foreach (var child in {sectionVar}.GetChildren())");
 01829                builder.AppendLine("            {");
 01830                if (prop.NestedProperties != null && prop.NestedProperties.Count > 0)
 1831                {
 01832                    builder.AppendLine($"                var item = new {dictValType}();");
 01833                    var ni = index * 100;
 01834                    foreach (var np in prop.NestedProperties)
 1835                    {
 01836                        GenerateChildPropertyAssignment(builder, np, ni, "item", "child");
 01837                        ni++;
 1838                    }
 01839                    builder.AppendLine($"                {varName}[child.Key] = item;");
 1840                }
 01841                else if (dictValType == "int" || dictValType == "global::System.Int32")
 1842                {
 01843                    builder.AppendLine($"                if (child.Value is {{ }} val && int.TryParse(val, out var iv)) 
 1844                }
 1845                else
 1846                {
 01847                    builder.AppendLine($"                if (child.Value is {{ }} val) {varName}[child.Key] = val;");
 1848                }
 01849                builder.AppendLine("            }");
 01850                break;
 1851
 1852            default:
 01853                builder.AppendLine($"            var {varName} = default({prop.TypeName}); // Complex type");
 1854                break;
 1855        }
 11856    }
 1857
 1858    private static void GenerateNestedPropertyAssignment(StringBuilder builder, OptionsPropertyInfo prop, int index, str
 1859    {
 21860        var varName = $"nv{index}";
 21861        var baseTypeName = GetBaseTypeName(prop.TypeName);
 1862
 21863        if (baseTypeName == "string" || baseTypeName == "global::System.String")
 1864        {
 11865            builder.AppendLine($"            if ({sectionVar}[\"{prop.Name}\"] is {{ }} {varName}) {targetVar}.{prop.Nam
 1866        }
 11867        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 1868        {
 11869            builder.AppendLine($"            if ({sectionVar}[\"{prop.Name}\"] is {{ }} {varName} && int.TryParse({varNa
 1870        }
 01871        else if (baseTypeName == "bool" || baseTypeName == "global::System.Boolean")
 1872        {
 01873            builder.AppendLine($"            if ({sectionVar}[\"{prop.Name}\"] is {{ }} {varName} && bool.TryParse({varN
 1874        }
 01875    }
 1876
 1877    private static void GenerateChildPropertyAssignment(StringBuilder builder, OptionsPropertyInfo prop, int index, stri
 1878    {
 01879        var varName = $"cv{index}";
 01880        var baseTypeName = GetBaseTypeName(prop.TypeName);
 1881
 01882        if (baseTypeName == "string" || baseTypeName == "global::System.String")
 1883        {
 01884            builder.AppendLine($"                if ({sectionVar}[\"{prop.Name}\"] is {{ }} {varName}) {targetVar}.{prop
 1885        }
 01886        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 1887        {
 01888            builder.AppendLine($"                if ({sectionVar}[\"{prop.Name}\"] is {{ }} {varName} && int.TryParse({v
 1889        }
 01890    }
 1891
 1892    private static void GeneratePropertyInitializer(StringBuilder builder, OptionsPropertyInfo prop, int index, string c
 1893    {
 151894        builder.AppendLine($"                {prop.Name} = p{index}{comma}");
 151895    }
 1896
 1897    private static void GenerateParameterParseVariable(StringBuilder builder, PositionalRecordParameter param, int index
 1898    {
 131899        var varName = $"p{index}";
 131900        var typeName = param.TypeName;
 131901        var baseTypeName = GetBaseTypeName(typeName);
 1902
 1903        // Check if it's an enum
 1904        // For simplicity, check if it's a known primitive, otherwise assume it could be an enum
 131905        if (baseTypeName == "string" || baseTypeName == "global::System.String")
 1906        {
 51907            builder.AppendLine($"            var {varName} = section[\"{param.Name}\"] ?? \"\";");
 1908        }
 81909        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 1910        {
 41911            builder.AppendLine($"            var {varName} = section[\"{param.Name}\"] is {{ }} v{index} && int.TryParse
 1912        }
 41913        else if (baseTypeName == "bool" || baseTypeName == "global::System.Boolean")
 1914        {
 31915            builder.AppendLine($"            var {varName} = section[\"{param.Name}\"] is {{ }} v{index} && bool.TryPars
 1916        }
 11917        else if (baseTypeName == "double" || baseTypeName == "global::System.Double")
 1918        {
 01919            builder.AppendLine($"            var {varName} = section[\"{param.Name}\"] is {{ }} v{index} && double.TryPa
 1920        }
 1921        else
 1922        {
 1923            // Try enum parsing for other types
 11924            builder.AppendLine($"            var {varName} = section[\"{param.Name}\"] is {{ }} v{index} && global::Syst
 1925        }
 11926    }
 1927
 1928    private static void RegisterValidator(StringBuilder builder, DiscoveredOptions opt, string safeAssemblyName, HashSet
 1929    {
 751930        var typeName = opt.TypeName;
 751931        var shortTypeName = GeneratorHelpers.GetShortTypeName(typeName);
 1932
 1933        // Register DataAnnotations validator if present
 751934        if (opt.HasDataAnnotations)
 1935        {
 171936            var dataAnnotationsValidatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}DataAnnotation
 171937            builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOptions<{ty
 1938        }
 1939
 751940        if (opt.ValidateOnStart && opt.HasValidatorMethod)
 1941        {
 41942            var validatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}Validator";
 41943            builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOptions<{ty
 1944
 41945            if (opt.HasExternalValidator && opt.ValidatorMethod != null && !opt.ValidatorMethod.Value.IsStatic)
 1946            {
 11947                externalValidatorsToRegister.Add(opt.ValidatorTypeName!);
 1948            }
 1949        }
 751950    }
 1951
 1952    private static void RegisterValidatorForFactory(StringBuilder builder, DiscoveredOptions opt, string safeAssemblyNam
 1953    {
 11954        var typeName = opt.TypeName;
 11955        var shortTypeName = GeneratorHelpers.GetShortTypeName(typeName);
 1956
 1957        // For factory pattern, we need to add OptionsBuilder validation manually
 1958        // Since we're using AddSingleton<IOptions<T>>, we also need to register for IOptionsSnapshot and IOptionsMonito
 11959        builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IOptionsSnapshot<{typeNa
 1960
 1961        // Add startup validation
 11962        builder.AppendLine($"        services.AddOptions<{typeName}>().ValidateOnStart();");
 1963
 1964        // Register DataAnnotations validator if present
 11965        if (opt.HasDataAnnotations)
 1966        {
 01967            var dataAnnotationsValidatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}DataAnnotation
 01968            builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOptions<{ty
 1969        }
 1970
 11971        if (opt.HasValidatorMethod)
 1972        {
 11973            var validatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}Validator";
 11974            builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOptions<{ty
 1975
 11976            if (opt.HasExternalValidator && opt.ValidatorMethod != null && !opt.ValidatorMethod.Value.IsStatic)
 1977            {
 01978                externalValidatorsToRegister.Add(opt.ValidatorTypeName!);
 1979            }
 1980        }
 11981    }
 1982
 1983    private static void GeneratePropertyBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string targe
 1984    {
 1985        // Handle complex types first
 1201986        if (prop.ComplexTypeKind != ComplexTypeKind.None)
 1987        {
 181988            GenerateComplexTypeBinding(builder, prop, index, targetPath);
 181989            return;
 1990        }
 1991
 1021992        var varName = $"v{index}";
 1993
 1994        // Determine how to parse the value based on type
 1021995        var typeName = prop.TypeName;
 1021996        var baseTypeName = GetBaseTypeName(typeName);
 1997
 1021998        builder.Append($"                if (section[\"{prop.Name}\"] is {{ }} {varName}");
 1999
 2000        // Check if it's an enum first
 1022001        if (prop.IsEnum && prop.EnumTypeName != null)
 2002        {
 122003            builder.AppendLine($" && global::System.Enum.TryParse<{prop.EnumTypeName}>({varName}, true, out var p{index}
 2004        }
 902005        else if (baseTypeName == "string" || baseTypeName == "global::System.String")
 2006        {
 2007            // String: direct assignment
 502008            builder.AppendLine($") {targetPath}.{prop.Name} = {varName};");
 2009        }
 402010        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 2011        {
 232012            builder.AppendLine($" && int.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name} = p{index};");
 2013        }
 172014        else if (baseTypeName == "bool" || baseTypeName == "global::System.Boolean")
 2015        {
 52016            builder.AppendLine($" && bool.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name} = p{index};");
 2017        }
 122018        else if (baseTypeName == "double" || baseTypeName == "global::System.Double")
 2019        {
 52020            builder.AppendLine($" && double.TryParse({varName}, System.Globalization.NumberStyles.Any, System.Globalizat
 2021        }
 72022        else if (baseTypeName == "float" || baseTypeName == "global::System.Single")
 2023        {
 02024            builder.AppendLine($" && float.TryParse({varName}, System.Globalization.NumberStyles.Any, System.Globalizati
 2025        }
 72026        else if (baseTypeName == "decimal" || baseTypeName == "global::System.Decimal")
 2027        {
 02028            builder.AppendLine($" && decimal.TryParse({varName}, System.Globalization.NumberStyles.Any, System.Globaliza
 2029        }
 72030        else if (baseTypeName == "long" || baseTypeName == "global::System.Int64")
 2031        {
 02032            builder.AppendLine($" && long.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name} = p{index};");
 2033        }
 72034        else if (baseTypeName == "short" || baseTypeName == "global::System.Int16")
 2035        {
 02036            builder.AppendLine($" && short.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name} = p{index};")
 2037        }
 72038        else if (baseTypeName == "byte" || baseTypeName == "global::System.Byte")
 2039        {
 02040            builder.AppendLine($" && byte.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name} = p{index};");
 2041        }
 72042        else if (baseTypeName == "char" || baseTypeName == "global::System.Char")
 2043        {
 02044            builder.AppendLine($" && {varName}.Length == 1) {targetPath}.{prop.Name} = {varName}[0];");
 2045        }
 72046        else if (baseTypeName == "global::System.TimeSpan")
 2047        {
 02048            builder.AppendLine($" && global::System.TimeSpan.TryParse({varName}, out var p{index})) {targetPath}.{prop.N
 2049        }
 72050        else if (baseTypeName == "global::System.DateTime")
 2051        {
 02052            builder.AppendLine($" && global::System.DateTime.TryParse({varName}, out var p{index})) {targetPath}.{prop.N
 2053        }
 72054        else if (baseTypeName == "global::System.DateTimeOffset")
 2055        {
 02056            builder.AppendLine($" && global::System.DateTimeOffset.TryParse({varName}, out var p{index})) {targetPath}.{
 2057        }
 72058        else if (baseTypeName == "global::System.Guid")
 2059        {
 02060            builder.AppendLine($" && global::System.Guid.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name}
 2061        }
 72062        else if (baseTypeName == "global::System.Uri")
 2063        {
 02064            builder.AppendLine($" && global::System.Uri.TryCreate({varName}, global::System.UriKind.RelativeOrAbsolute, 
 2065        }
 2066        else
 2067        {
 2068            // Unsupported type - skip silently (matching ConfigurationBinder behavior)
 72069            builder.AppendLine($") {{ }} // Skipped: {typeName} (not a supported primitive)");
 2070        }
 72071    }
 2072
 2073    private static void GenerateComplexTypeBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string ta
 2074    {
 182075        var sectionVar = $"sec{index}";
 2076
 182077        switch (prop.ComplexTypeKind)
 2078        {
 2079            case ComplexTypeKind.NestedObject:
 62080                GenerateNestedObjectBinding(builder, prop, index, targetPath, sectionVar);
 62081                break;
 2082
 2083            case ComplexTypeKind.Array:
 32084                GenerateArrayBinding(builder, prop, index, targetPath, sectionVar);
 32085                break;
 2086
 2087            case ComplexTypeKind.List:
 52088                GenerateListBinding(builder, prop, index, targetPath, sectionVar);
 52089                break;
 2090
 2091            case ComplexTypeKind.Dictionary:
 42092                GenerateDictionaryBinding(builder, prop, index, targetPath, sectionVar);
 2093                break;
 2094        }
 42095    }
 2096
 2097    private static void GenerateNestedObjectBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string t
 2098    {
 62099        var nestedPath = $"{targetPath}.{prop.Name}";
 2100
 62101        builder.AppendLine($"                // Bind nested object: {prop.Name}");
 62102        builder.AppendLine($"                var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 2103
 2104        // Initialize if null (for nullable properties)
 62105        if (prop.IsNullable)
 2106        {
 12107            builder.AppendLine($"                {nestedPath} ??= new {GetNonNullableTypeName(prop.TypeName)}();");
 2108        }
 2109
 2110        // Generate bindings for nested properties
 62111        if (prop.NestedProperties != null)
 2112        {
 62113            var nestedIndex = index * 100; // Use offset to avoid variable name collisions
 302114            foreach (var nestedProp in prop.NestedProperties)
 2115            {
 2116                // Temporarily swap section context for nested binding
 92117                GenerateNestedPropertyBinding(builder, nestedProp, nestedIndex, nestedPath, sectionVar);
 92118                nestedIndex++;
 2119            }
 2120        }
 62121    }
 2122
 2123    private static void GenerateNestedPropertyBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string
 2124    {
 2125        // Handle complex types recursively
 102126        if (prop.ComplexTypeKind != ComplexTypeKind.None)
 2127        {
 22128            var innerSectionVar = $"sec{index}";
 22129            switch (prop.ComplexTypeKind)
 2130            {
 2131                case ComplexTypeKind.NestedObject:
 12132                    builder.AppendLine($"                // Bind nested object: {prop.Name}");
 12133                    builder.AppendLine($"                var {innerSectionVar} = {sectionVarName}.GetSection(\"{prop.Nam
 12134                    if (prop.IsNullable)
 2135                    {
 02136                        builder.AppendLine($"                {targetPath}.{prop.Name} ??= new {GetNonNullableTypeName(pr
 2137                    }
 12138                    if (prop.NestedProperties != null)
 2139                    {
 12140                        var nestedIndex = index * 100;
 42141                        foreach (var nestedProp in prop.NestedProperties)
 2142                        {
 12143                            GenerateNestedPropertyBinding(builder, nestedProp, nestedIndex, $"{targetPath}.{prop.Name}",
 12144                            nestedIndex++;
 2145                        }
 2146                    }
 2147                    break;
 2148
 2149                case ComplexTypeKind.Array:
 2150                case ComplexTypeKind.List:
 2151                case ComplexTypeKind.Dictionary:
 2152                    // For collections inside nested objects, generate appropriate binding
 12153                    GenerateCollectionBindingInNested(builder, prop, index, targetPath, sectionVarName);
 2154                    break;
 2155            }
 22156            return;
 2157        }
 2158
 2159        // Generate primitive binding using the nested section
 82160        var varName = $"v{index}";
 82161        var baseTypeName = GetBaseTypeName(prop.TypeName);
 2162
 82163        builder.Append($"                if ({sectionVarName}[\"{prop.Name}\"] is {{ }} {varName}");
 2164
 82165        if (prop.IsEnum && prop.EnumTypeName != null)
 2166        {
 02167            builder.AppendLine($" && global::System.Enum.TryParse<{prop.EnumTypeName}>({varName}, true, out var p{index}
 2168        }
 82169        else if (baseTypeName == "string" || baseTypeName == "global::System.String")
 2170        {
 62171            builder.AppendLine($") {targetPath}.{prop.Name} = {varName};");
 2172        }
 22173        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 2174        {
 22175            builder.AppendLine($" && int.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name} = p{index};");
 2176        }
 02177        else if (baseTypeName == "bool" || baseTypeName == "global::System.Boolean")
 2178        {
 02179            builder.AppendLine($" && bool.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name} = p{index};");
 2180        }
 2181        else
 2182        {
 2183            // For other types, generate appropriate TryParse
 02184            builder.AppendLine($") {{ }} // Skipped: {prop.TypeName}");
 2185        }
 02186    }
 2187
 2188    private static void GenerateCollectionBindingInNested(StringBuilder builder, OptionsPropertyInfo prop, int index, st
 2189    {
 12190        var collectionSection = $"colSec{index}";
 12191        builder.AppendLine($"                var {collectionSection} = {sectionVarName}.GetSection(\"{prop.Name}\");");
 2192
 12193        switch (prop.ComplexTypeKind)
 2194        {
 2195            case ComplexTypeKind.List:
 12196                GenerateListBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", collectionSection);
 12197                break;
 2198            case ComplexTypeKind.Array:
 02199                GenerateArrayBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", collectionSection);
 02200                break;
 2201            case ComplexTypeKind.Dictionary:
 02202                GenerateDictionaryBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", collectionSection);
 2203                break;
 2204        }
 02205    }
 2206
 2207    private static void GenerateArrayBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string targetPa
 2208    {
 32209        builder.AppendLine($"                // Bind array: {prop.Name}");
 32210        builder.AppendLine($"                var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 32211        GenerateArrayBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", sectionVar);
 32212    }
 2213
 2214    private static void GenerateArrayBindingCore(StringBuilder builder, OptionsPropertyInfo prop, int index, string targ
 2215    {
 32216        var itemsVar = $"items{index}";
 32217        var elementType = prop.ElementTypeName ?? "string";
 32218        var hasNestedProps = prop.NestedProperties != null && prop.NestedProperties.Count > 0;
 2219
 32220        builder.AppendLine($"                var {itemsVar} = new global::System.Collections.Generic.List<{elementType}>
 32221        builder.AppendLine($"                foreach (var child in {sectionVar}.GetChildren())");
 32222        builder.AppendLine("                {");
 2223
 32224        if (hasNestedProps)
 2225        {
 2226            // Complex element type
 12227            var itemVar = $"item{index}";
 12228            builder.AppendLine($"                    var {itemVar} = new {elementType}();");
 12229            var nestedIndex = index * 100;
 62230            foreach (var nestedProp in prop.NestedProperties!)
 2231            {
 22232                GenerateChildPropertyBinding(builder, nestedProp, nestedIndex, itemVar, "child");
 22233                nestedIndex++;
 2234            }
 12235            builder.AppendLine($"                    {itemsVar}.Add({itemVar});");
 2236        }
 2237        else
 2238        {
 2239            // Primitive element type
 22240            GeneratePrimitiveCollectionAdd(builder, elementType, index, itemsVar);
 2241        }
 2242
 32243        builder.AppendLine("                }");
 32244        builder.AppendLine($"                {targetPath} = {itemsVar}.ToArray();");
 32245    }
 2246
 2247    private static void GenerateListBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string targetPat
 2248    {
 52249        builder.AppendLine($"                // Bind list: {prop.Name}");
 52250        builder.AppendLine($"                var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 52251        GenerateListBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", sectionVar);
 52252    }
 2253
 2254    private static void GenerateListBindingCore(StringBuilder builder, OptionsPropertyInfo prop, int index, string targe
 2255    {
 62256        var elementType = prop.ElementTypeName ?? "string";
 62257        var hasNestedProps = prop.NestedProperties != null && prop.NestedProperties.Count > 0;
 2258
 62259        builder.AppendLine($"                {targetPath}.Clear();");
 62260        builder.AppendLine($"                foreach (var child in {sectionVar}.GetChildren())");
 62261        builder.AppendLine("                {");
 2262
 62263        if (hasNestedProps)
 2264        {
 2265            // Complex element type
 12266            var itemVar = $"item{index}";
 12267            builder.AppendLine($"                    var {itemVar} = new {elementType}();");
 12268            var nestedIndex = index * 100;
 62269            foreach (var nestedProp in prop.NestedProperties!)
 2270            {
 22271                GenerateChildPropertyBinding(builder, nestedProp, nestedIndex, itemVar, "child");
 22272                nestedIndex++;
 2273            }
 12274            builder.AppendLine($"                    {targetPath}.Add({itemVar});");
 2275        }
 2276        else
 2277        {
 2278            // Primitive element type
 52279            GeneratePrimitiveListAdd(builder, elementType, index, targetPath);
 2280        }
 2281
 62282        builder.AppendLine("                }");
 62283    }
 2284
 2285    private static void GenerateDictionaryBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string tar
 2286    {
 42287        builder.AppendLine($"                // Bind dictionary: {prop.Name}");
 42288        builder.AppendLine($"                var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 42289        GenerateDictionaryBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", sectionVar);
 42290    }
 2291
 2292    private static void GenerateDictionaryBindingCore(StringBuilder builder, OptionsPropertyInfo prop, int index, string
 2293    {
 42294        var elementType = prop.ElementTypeName ?? "string";
 42295        var hasNestedProps = prop.NestedProperties != null && prop.NestedProperties.Count > 0;
 2296
 42297        builder.AppendLine($"                {targetPath}.Clear();");
 42298        builder.AppendLine($"                foreach (var child in {sectionVar}.GetChildren())");
 42299        builder.AppendLine("                {");
 2300
 42301        if (hasNestedProps)
 2302        {
 2303            // Complex value type
 12304            var itemVar = $"item{index}";
 12305            builder.AppendLine($"                    var {itemVar} = new {elementType}();");
 12306            var nestedIndex = index * 100;
 62307            foreach (var nestedProp in prop.NestedProperties!)
 2308            {
 22309                GenerateChildPropertyBinding(builder, nestedProp, nestedIndex, itemVar, "child");
 22310                nestedIndex++;
 2311            }
 12312            builder.AppendLine($"                    {targetPath}[child.Key] = {itemVar};");
 2313        }
 2314        else
 2315        {
 2316            // Primitive value type
 32317            GeneratePrimitiveDictionaryAdd(builder, elementType, index, targetPath);
 2318        }
 2319
 42320        builder.AppendLine("                }");
 42321    }
 2322
 2323    private static void GenerateChildPropertyBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string 
 2324    {
 62325        var varName = $"cv{index}";
 62326        var baseTypeName = GetBaseTypeName(prop.TypeName);
 2327
 62328        builder.Append($"                    if ({sectionVar}[\"{prop.Name}\"] is {{ }} {varName}");
 2329
 62330        if (prop.IsEnum && prop.EnumTypeName != null)
 2331        {
 02332            builder.AppendLine($" && global::System.Enum.TryParse<{prop.EnumTypeName}>({varName}, true, out var cp{index
 2333        }
 62334        else if (baseTypeName == "string" || baseTypeName == "global::System.String")
 2335        {
 32336            builder.AppendLine($") {targetVar}.{prop.Name} = {varName};");
 2337        }
 32338        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 2339        {
 32340            builder.AppendLine($" && int.TryParse({varName}, out var cp{index})) {targetVar}.{prop.Name} = cp{index};");
 2341        }
 02342        else if (baseTypeName == "bool" || baseTypeName == "global::System.Boolean")
 2343        {
 02344            builder.AppendLine($" && bool.TryParse({varName}, out var cp{index})) {targetVar}.{prop.Name} = cp{index};")
 2345        }
 2346        else
 2347        {
 02348            builder.AppendLine($") {{ }} // Skipped: {prop.TypeName}");
 2349        }
 02350    }
 2351
 2352    private static void GeneratePrimitiveCollectionAdd(StringBuilder builder, string elementType, int index, string list
 2353    {
 22354        var baseType = GetBaseTypeName(elementType);
 2355
 22356        if (baseType == "string" || baseType == "global::System.String")
 2357        {
 12358            builder.AppendLine($"                    if (child.Value is {{ }} val{index}) {listVar}.Add(val{index});");
 2359        }
 12360        else if (baseType == "int" || baseType == "global::System.Int32")
 2361        {
 12362            builder.AppendLine($"                    if (child.Value is {{ }} val{index} && int.TryParse(val{index}, out
 2363        }
 2364        else
 2365        {
 02366            builder.AppendLine($"                    // Skipped: unsupported element type {elementType}");
 2367        }
 02368    }
 2369
 2370    private static void GeneratePrimitiveListAdd(StringBuilder builder, string elementType, int index, string targetPath
 2371    {
 52372        var baseType = GetBaseTypeName(elementType);
 2373
 52374        if (baseType == "string" || baseType == "global::System.String")
 2375        {
 42376            builder.AppendLine($"                    if (child.Value is {{ }} val{index}) {targetPath}.Add(val{index});"
 2377        }
 12378        else if (baseType == "int" || baseType == "global::System.Int32")
 2379        {
 12380            builder.AppendLine($"                    if (child.Value is {{ }} val{index} && int.TryParse(val{index}, out
 2381        }
 2382        else
 2383        {
 02384            builder.AppendLine($"                    // Skipped: unsupported element type {elementType}");
 2385        }
 02386    }
 2387
 2388    private static void GeneratePrimitiveDictionaryAdd(StringBuilder builder, string elementType, int index, string targ
 2389    {
 32390        var baseType = GetBaseTypeName(elementType);
 2391
 32392        if (baseType == "string" || baseType == "global::System.String")
 2393        {
 12394            builder.AppendLine($"                    if (child.Value is {{ }} val{index}) {targetPath}[child.Key] = val{
 2395        }
 22396        else if (baseType == "int" || baseType == "global::System.Int32")
 2397        {
 22398            builder.AppendLine($"                    if (child.Value is {{ }} val{index} && int.TryParse(val{index}, out
 2399        }
 2400        else
 2401        {
 02402            builder.AppendLine($"                    // Skipped: unsupported element type {elementType}");
 2403        }
 02404    }
 2405
 2406    private static string GetNonNullableTypeName(string typeName)
 2407    {
 22408        if (typeName.EndsWith("?"))
 02409            return typeName.Substring(0, typeName.Length - 1);
 22410        return typeName;
 2411    }
 2412
 2413    private static string GetBaseTypeName(string typeName)
 2414    {
 2415        // Handle nullable types like "global::System.Nullable<int>" or "int?"
 1562416        if (typeName.StartsWith("global::System.Nullable<") && typeName.EndsWith(">"))
 2417        {
 02418            return typeName.Substring("global::System.Nullable<".Length, typeName.Length - "global::System.Nullable<".Le
 2419        }
 1562420        if (typeName.EndsWith("?"))
 2421        {
 42422            return typeName.Substring(0, typeName.Length - 1);
 2423        }
 1522424        return typeName;
 2425    }
 2426
 2427    private static void GenerateApplyDecoratorsMethod(StringBuilder builder, IReadOnlyList<DiscoveredDecorator> decorato
 2428    {
 4342429        builder.AppendLine("    /// <summary>");
 4342430        builder.AppendLine("    /// Applies all discovered decorators, interceptors, and hosted services to the service 
 4342431        builder.AppendLine("    /// Decorators are applied in order, with lower Order values applied first (closer to th
 4342432        builder.AppendLine("    /// </summary>");
 4342433        builder.AppendLine("    /// <param name=\"services\">The service collection to apply decorators to.</param>");
 4342434        builder.AppendLine("    public static void ApplyDecorators(IServiceCollection services)");
 4342435        builder.AppendLine("    {");
 2436
 2437        // Register ServiceCatalog first
 4342438        breadcrumbs.WriteInlineComment(builder, "        ", "Register service catalog for DI resolution");
 4342439        builder.AppendLine($"        services.AddSingleton<global::NexusLabs.Needlr.Catalog.IServiceCatalog, global::{sa
 4342440        builder.AppendLine();
 2441
 2442        // Register hosted services first (before decorators apply)
 4342443        if (hasHostedServices)
 2444        {
 62445            breadcrumbs.WriteInlineComment(builder, "        ", "Register hosted services");
 62446            builder.AppendLine("        RegisterHostedServices(services);");
 62447            if (decorators.Count > 0 || hasInterceptors)
 2448            {
 02449                builder.AppendLine();
 2450            }
 2451        }
 2452
 4342453        if (decorators.Count == 0 && !hasInterceptors)
 2454        {
 4012455            if (!hasHostedServices)
 2456            {
 3952457                breadcrumbs.WriteInlineComment(builder, "        ", "No decorators, interceptors, or hosted services dis
 2458            }
 2459        }
 2460        else
 2461        {
 332462            if (decorators.Count > 0)
 2463            {
 2464                // Group decorators by service type and order by Order property
 182465                var decoratorsByService = decorators
 272466                    .GroupBy(d => d.ServiceTypeName)
 392467                    .OrderBy(g => g.Key);
 2468
 782469                foreach (var serviceGroup in decoratorsByService)
 2470                {
 2471                    // Write verbose breadcrumb for decorator chain
 212472                    if (breadcrumbs.Level == BreadcrumbLevel.Verbose)
 2473                    {
 02474                        var chainItems = serviceGroup.OrderBy(d => d.Order).ToList();
 02475                        var lines = new List<string>
 02476                        {
 02477                            "Resolution order (outer → inner → target):"
 02478                        };
 02479                        for (int i = 0; i < chainItems.Count; i++)
 2480                        {
 02481                            var dec = chainItems[i];
 02482                            var sourcePath = dec.SourceFilePath != null
 02483                                ? BreadcrumbWriter.GetRelativeSourcePath(dec.SourceFilePath, projectDirectory)
 02484                                : $"[{dec.AssemblyName}]";
 02485                            lines.Add($"  {i + 1}. {dec.DecoratorTypeName.Split('.').Last()} (Order={dec.Order}) ← {sour
 2486                        }
 02487                        lines.Add($"Triggered by: [DecoratorFor<{serviceGroup.Key.Split('.').Last()}>] attributes");
 2488
 02489                        breadcrumbs.WriteVerboseBox(builder, "        ",
 02490                            $"Decorator Chain: {serviceGroup.Key.Split('.').Last()}",
 02491                            lines.ToArray());
 2492                    }
 2493                    else
 2494                    {
 212495                        breadcrumbs.WriteInlineComment(builder, "        ", $"Decorators for {serviceGroup.Key}");
 2496                    }
 2497
 1232498                    foreach (var decorator in serviceGroup.OrderBy(d => d.Order))
 2499                    {
 272500                        builder.AppendLine($"        services.AddDecorator<{decorator.ServiceTypeName}, {decorator.Decor
 2501                    }
 2502                }
 2503            }
 2504
 332505            if (hasInterceptors)
 2506            {
 152507                builder.AppendLine();
 152508                breadcrumbs.WriteInlineComment(builder, "        ", "Register intercepted services with their proxies");
 152509                builder.AppendLine($"        global::{safeAssemblyName}.Generated.InterceptorRegistrations.RegisterInter
 2510            }
 2511        }
 2512
 4342513        builder.AppendLine("    }");
 4342514    }
 2515
 2516    private static void GenerateRegisterProvidersMethod(StringBuilder builder, IReadOnlyList<DiscoveredProvider> provide
 2517    {
 172518        builder.AppendLine("    /// <summary>");
 172519        builder.AppendLine("    /// Registers all generated providers as Singletons.");
 172520        builder.AppendLine("    /// Providers are strongly-typed service locators that expose services via typed propert
 172521        builder.AppendLine("    /// </summary>");
 172522        builder.AppendLine("    /// <param name=\"services\">The service collection to register to.</param>");
 172523        builder.AppendLine("    public static void RegisterProviders(IServiceCollection services)");
 172524        builder.AppendLine("    {");
 2525
 702526        foreach (var provider in providers)
 2527        {
 182528            var shortName = provider.SimpleTypeName;
 182529            var sourcePath = provider.SourceFilePath != null
 182530                ? BreadcrumbWriter.GetRelativeSourcePath(provider.SourceFilePath, projectDirectory)
 182531                : $"[{provider.AssemblyName}]";
 2532
 182533            breadcrumbs.WriteInlineComment(builder, "        ", $"Provider: {shortName} ← {sourcePath}");
 2534
 182535            if (provider.IsInterface)
 2536            {
 2537                // Interface mode: register the generated implementation
 122538                var implName = provider.ImplementationTypeName;
 122539                builder.AppendLine($"        services.AddSingleton<{provider.TypeName}, global::{safeAssemblyName}.Gener
 2540            }
 62541            else if (provider.IsPartial)
 2542            {
 2543                // Shorthand class mode: register the partial class as its generated interface
 62544                var interfaceName = provider.InterfaceTypeName;
 62545                var providerNamespace = GetNamespaceFromTypeName(provider.TypeName);
 62546                builder.AppendLine($"        services.AddSingleton<global::{providerNamespace}.{interfaceName}, {provide
 2547            }
 2548        }
 2549
 172550        builder.AppendLine("    }");
 172551    }
 2552
 2553    private static void GenerateRegisterHostedServicesMethod(StringBuilder builder, IReadOnlyList<DiscoveredHostedServic
 2554    {
 62555        builder.AppendLine("    /// <summary>");
 62556        builder.AppendLine("    /// Registers all discovered hosted services (BackgroundService and IHostedService imple
 62557        builder.AppendLine("    /// Each service is registered as singleton and also as IHostedService for the host to d
 62558        builder.AppendLine("    /// </summary>");
 62559        builder.AppendLine("    /// <param name=\"services\">The service collection to register to.</param>");
 62560        builder.AppendLine("    private static void RegisterHostedServices(IServiceCollection services)");
 62561        builder.AppendLine("    {");
 2562
 262563        foreach (var hostedService in hostedServices)
 2564        {
 72565            var typeName = hostedService.TypeName;
 72566            var shortName = typeName.Split('.').Last();
 72567            var sourcePath = hostedService.SourceFilePath != null
 72568                ? BreadcrumbWriter.GetRelativeSourcePath(hostedService.SourceFilePath, projectDirectory)
 72569                : $"[{hostedService.AssemblyName}]";
 2570
 72571            breadcrumbs.WriteInlineComment(builder, "        ", $"Hosted service: {shortName} ← {sourcePath}");
 2572
 2573            // Register the concrete type as singleton
 72574            builder.AppendLine($"        services.AddSingleton<{typeName}>();");
 2575
 2576            // Register as IHostedService that forwards to the concrete type
 72577            builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Hosting.IHostedService>(sp =
 2578        }
 2579
 62580        builder.AppendLine("    }");
 62581    }
 2582
 2583
 2584
 2585    private static string GenerateInterceptorProxiesSource(IReadOnlyList<DiscoveredInterceptedService> interceptedServic
 2586    {
 152587        var builder = new StringBuilder();
 152588        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblyName);
 2589
 152590        breadcrumbs.WriteFileHeader(builder, assemblyName, "Needlr Interceptor Proxies");
 152591        builder.AppendLine("#nullable enable");
 152592        builder.AppendLine();
 152593        builder.AppendLine("using System;");
 152594        builder.AppendLine("using System.Reflection;");
 152595        builder.AppendLine("using System.Threading.Tasks;");
 152596        builder.AppendLine();
 152597        builder.AppendLine("using Microsoft.Extensions.DependencyInjection;");
 152598        builder.AppendLine();
 152599        builder.AppendLine("using NexusLabs.Needlr;");
 152600        builder.AppendLine();
 152601        builder.AppendLine($"namespace {safeAssemblyName}.Generated;");
 152602        builder.AppendLine();
 2603
 2604        // Generate each proxy class
 602605        foreach (var service in interceptedServices)
 2606        {
 152607            CodeGen.InterceptorCodeGenerator.GenerateInterceptorProxyClass(builder, service, breadcrumbs, projectDirecto
 152608            builder.AppendLine();
 2609        }
 2610
 2611        // Generate the registration helper
 152612        builder.AppendLine("/// <summary>");
 152613        builder.AppendLine("/// Helper class for registering intercepted services.");
 152614        builder.AppendLine("/// </summary>");
 152615        builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\", \"1
 152616        builder.AppendLine("public static class InterceptorRegistrations");
 152617        builder.AppendLine("{");
 152618        builder.AppendLine("    /// <summary>");
 152619        builder.AppendLine("    /// Registers all intercepted services and their proxies.");
 152620        builder.AppendLine("    /// </summary>");
 152621        builder.AppendLine("    /// <param name=\"services\">The service collection to register to.</param>");
 152622        builder.AppendLine("    public static void RegisterInterceptedServices(IServiceCollection services)");
 152623        builder.AppendLine("    {");
 2624
 602625        foreach (var service in interceptedServices)
 2626        {
 152627            var proxyTypeName = GeneratorHelpers.GetProxyTypeName(service.TypeName);
 152628            var lifetime = service.Lifetime switch
 152629            {
 22630                GeneratorLifetime.Singleton => "Singleton",
 132631                GeneratorLifetime.Scoped => "Scoped",
 02632                GeneratorLifetime.Transient => "Transient",
 02633                _ => "Scoped"
 152634            };
 2635
 2636            // Register all interceptor types
 662637            foreach (var interceptorType in service.AllInterceptorTypeNames)
 2638            {
 182639                breadcrumbs.WriteInlineComment(builder, "        ", $"Register interceptor: {interceptorType.Split('.').
 182640                builder.AppendLine($"        if (!services.Any(d => d.ServiceType == typeof({interceptorType})))");
 182641                builder.AppendLine($"            services.Add{lifetime}<{interceptorType}>();");
 2642            }
 2643
 2644            // Register the actual implementation type
 152645            builder.AppendLine($"        // Register actual implementation");
 152646            builder.AppendLine($"        services.Add{lifetime}<{service.TypeName}>();");
 2647
 2648            // Register proxy for each interface
 602649            foreach (var iface in service.InterfaceNames)
 2650            {
 152651                builder.AppendLine($"        // Register proxy for {iface}");
 152652                builder.AppendLine($"        services.Add{lifetime}<{iface}>(sp => new {proxyTypeName}(");
 152653                builder.AppendLine($"            sp.GetRequiredService<{service.TypeName}>(),");
 152654                builder.AppendLine($"            sp));");
 2655            }
 2656        }
 2657
 152658        builder.AppendLine("    }");
 152659        builder.AppendLine();
 152660        builder.AppendLine("    /// <summary>");
 152661        builder.AppendLine("    /// Gets the number of intercepted services discovered at compile time.");
 152662        builder.AppendLine("    /// </summary>");
 152663        builder.AppendLine($"    public static int Count => {interceptedServices.Count};");
 152664        builder.AppendLine("}");
 2665
 152666        return builder.ToString();
 2667    }
 2668
 2669    private static string GenerateFactoriesSource(IReadOnlyList<DiscoveredFactory> factories, string assemblyName, Bread
 2670    {
 262671        var builder = new StringBuilder();
 262672        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblyName);
 2673
 262674        breadcrumbs.WriteFileHeader(builder, assemblyName, "Needlr Generated Factories");
 262675        builder.AppendLine("#nullable enable");
 262676        builder.AppendLine();
 262677        builder.AppendLine("using System;");
 262678        builder.AppendLine();
 262679        builder.AppendLine("using Microsoft.Extensions.DependencyInjection;");
 262680        builder.AppendLine();
 262681        builder.AppendLine($"namespace {safeAssemblyName}.Generated;");
 262682        builder.AppendLine();
 2683
 2684        // Generate factory interfaces and implementations for each type
 1042685        foreach (var factory in factories)
 2686        {
 262687            if (factory.GenerateInterface)
 2688            {
 252689                CodeGen.FactoryCodeGenerator.GenerateFactoryInterface(builder, factory, breadcrumbs, projectDirectory);
 252690                builder.AppendLine();
 252691                CodeGen.FactoryCodeGenerator.GenerateFactoryImplementation(builder, factory, breadcrumbs, projectDirecto
 252692                builder.AppendLine();
 2693            }
 2694        }
 2695
 2696        // Generate the registration helper
 262697        builder.AppendLine("/// <summary>");
 262698        builder.AppendLine("/// Helper class for registering factory types.");
 262699        builder.AppendLine("/// </summary>");
 262700        builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\", \"1
 262701        builder.AppendLine("public static class FactoryRegistrations");
 262702        builder.AppendLine("{");
 262703        builder.AppendLine("    /// <summary>");
 262704        builder.AppendLine("    /// Registers all generated factories.");
 262705        builder.AppendLine("    /// </summary>");
 262706        builder.AppendLine("    /// <param name=\"services\">The service collection to register to.</param>");
 262707        builder.AppendLine("    public static void RegisterFactories(IServiceCollection services)");
 262708        builder.AppendLine("    {");
 2709
 1042710        foreach (var factory in factories)
 2711        {
 262712            breadcrumbs.WriteInlineComment(builder, "        ", $"Factory for {factory.SimpleTypeName}");
 2713
 2714            // Register Func<> for each constructor
 262715            if (factory.GenerateFunc)
 2716            {
 1062717                foreach (var ctor in factory.Constructors)
 2718                {
 282719                    CodeGen.FactoryCodeGenerator.GenerateFuncRegistration(builder, factory, ctor, "        ");
 2720                }
 2721            }
 2722
 2723            // Register interface factory
 262724            if (factory.GenerateInterface)
 2725            {
 252726                var factoryInterfaceName = $"I{factory.SimpleTypeName}Factory";
 252727                var factoryImplName = $"{factory.SimpleTypeName}Factory";
 252728                builder.AppendLine($"        services.AddSingleton<global::{safeAssemblyName}.Generated.{factoryInterfac
 2729            }
 2730        }
 2731
 262732        builder.AppendLine("    }");
 262733        builder.AppendLine();
 262734        builder.AppendLine("    /// <summary>");
 262735        builder.AppendLine("    /// Gets the number of factory types generated at compile time.");
 262736        builder.AppendLine("    /// </summary>");
 262737        builder.AppendLine($"    public static int Count => {factories.Count};");
 262738        builder.AppendLine("}");
 2739
 262740        return builder.ToString();
 2741    }
 2742
 2743    private static string GenerateProvidersSource(IReadOnlyList<DiscoveredProvider> providers, string assemblyName, Brea
 2744    {
 112745        var builder = new StringBuilder();
 112746        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblyName);
 2747
 112748        breadcrumbs.WriteFileHeader(builder, assemblyName, "Needlr Generated Providers");
 112749        builder.AppendLine("#nullable enable");
 112750        builder.AppendLine();
 112751        builder.AppendLine("using System;");
 112752        builder.AppendLine();
 112753        builder.AppendLine("using Microsoft.Extensions.DependencyInjection;");
 112754        builder.AppendLine();
 112755        builder.AppendLine($"namespace {safeAssemblyName}.Generated;");
 112756        builder.AppendLine();
 2757
 2758        // Generate provider implementations (interface-based only)
 462759        foreach (var provider in providers)
 2760        {
 122761            CodeGen.ProviderCodeGenerator.GenerateProviderImplementation(builder, provider, $"{safeAssemblyName}.Generat
 122762            builder.AppendLine();
 2763        }
 2764
 112765        return builder.ToString();
 2766    }
 2767
 2768    private static string GenerateShorthandProviderSource(DiscoveredProvider provider, string assemblyName, BreadcrumbWr
 2769    {
 62770        var builder = new StringBuilder();
 62771        var providerNamespace = GetNamespaceFromTypeName(provider.TypeName);
 2772
 62773        breadcrumbs.WriteFileHeader(builder, assemblyName, $"Needlr Generated Provider: {provider.SimpleTypeName}");
 62774        builder.AppendLine("#nullable enable");
 62775        builder.AppendLine();
 62776        builder.AppendLine("using System;");
 62777        builder.AppendLine();
 62778        builder.AppendLine($"namespace {providerNamespace};");
 62779        builder.AppendLine();
 2780
 62781        CodeGen.ProviderCodeGenerator.GenerateProviderInterfaceAndPartialClass(builder, provider, providerNamespace, bre
 2782
 62783        return builder.ToString();
 2784    }
 2785
 2786    private static string GetNamespaceFromTypeName(string fullyQualifiedName)
 2787    {
 122788        var name = fullyQualifiedName;
 122789        if (name.StartsWith("global::"))
 2790        {
 122791            name = name.Substring(8);
 2792        }
 2793
 122794        var lastDot = name.LastIndexOf('.');
 122795        return lastDot >= 0 ? name.Substring(0, lastDot) : string.Empty;
 2796    }
 2797
 2798
 2799
 2800
 2801
 2802    /// <summary>
 2803    /// Filters out nested options types.
 2804    /// A nested options type is one that is used as a property type in another options type.
 2805    /// These should not be registered separately - they are bound as part of their parent.
 2806    /// </summary>
 2807    private static List<DiscoveredOptions> FilterNestedOptions(List<DiscoveredOptions> options, Compilation compilation)
 2808    {
 2809        // Build a set of all options type names
 632810        var optionsTypeNames = new HashSet<string>(options.Select(o => o.TypeName));
 2811
 2812        // Find all options types that are used as properties in other options types
 192813        var nestedTypeNames = new HashSet<string>();
 2814
 1262815        foreach (var opt in options)
 2816        {
 2817            // Find the type symbol for this options type
 442818            var typeSymbol = FindTypeSymbol(compilation, opt.TypeName);
 442819            if (typeSymbol == null)
 2820                continue;
 2821
 2822            // Check all properties of this type
 6402823            foreach (var member in typeSymbol.GetMembers())
 2824            {
 2762825                if (member is not IPropertySymbol property)
 2826                    continue;
 2827
 2828                // Skip non-class property types (primitives, structs, etc.)
 562829                if (property.Type is not INamedTypeSymbol propertyType)
 2830                    continue;
 2831
 562832                if (propertyType.TypeKind != TypeKind.Class)
 2833                    continue;
 2834
 2835                // Get the fully qualified name of the property type
 422836                var propertyTypeName = TypeDiscoveryHelper.GetFullyQualifiedName(propertyType);
 2837
 2838                // If this property type is also an [Options] type, mark it as nested
 422839                if (optionsTypeNames.Contains(propertyTypeName))
 2840                {
 92841                    nestedTypeNames.Add(propertyTypeName);
 2842                }
 2843            }
 2844        }
 2845
 2846        // Return only root options (those not used as properties in other options)
 632847        return options.Where(o => !nestedTypeNames.Contains(o.TypeName)).ToList();
 2848    }
 2849
 2850    /// <summary>
 2851    /// Finds a type symbol by its fully qualified name.
 2852    /// </summary>
 2853    private static INamedTypeSymbol? FindTypeSymbol(Compilation compilation, string fullyQualifiedName)
 2854    {
 2855        // Strip global:: prefix if present
 442856        var typeName = fullyQualifiedName.StartsWith("global::")
 442857            ? fullyQualifiedName.Substring(8)
 442858            : fullyQualifiedName;
 2859
 442860        return compilation.GetTypeByMetadataName(typeName);
 2861    }
 2862
 2863    /// <summary>
 2864    /// Expands open generic decorators into concrete decorator registrations
 2865    /// for each discovered closed implementation of the open generic interface.
 2866    /// </summary>
 2867    private static void ExpandOpenDecorators(
 2868        IReadOnlyList<DiscoveredType> injectableTypes,
 2869        IReadOnlyList<DiscoveredOpenDecorator> openDecorators,
 2870        List<DiscoveredDecorator> decorators)
 2871    {
 2872        // Group injectable types by the open generic interfaces they implement
 62873        var interfaceImplementations = new Dictionary<INamedTypeSymbol, List<(INamedTypeSymbol closedInterface, Discover
 2874
 402875        foreach (var discoveredType in injectableTypes)
 2876        {
 2877            // We need to check each interface this type implements to see if it's a closed version of an open generic
 602878            foreach (var openDecorator in openDecorators)
 2879            {
 2880                // Check if this type implements the open generic interface
 462881                foreach (var interfaceName in discoveredType.InterfaceNames)
 2882                {
 2883                    // This is string-based matching - we need to match the interface name pattern
 2884                    // For example, if open generic is IHandler<> and the interface is IHandler<Order>, we should match
 72885                    var openGenericName = TypeDiscoveryHelper.GetFullyQualifiedName(openDecorator.OpenGenericInterface);
 2886
 2887                    // Extract the base name (before the <>)
 72888                    var openGenericBaseName = GeneratorHelpers.GetGenericBaseName(openGenericName);
 72889                    var interfaceBaseName = GeneratorHelpers.GetGenericBaseName(interfaceName);
 2890
 72891                    if (openGenericBaseName == interfaceBaseName)
 2892                    {
 2893                        // This interface is a closed version of the open generic
 2894                        // Create a closed decorator registration
 72895                        var closedDecoratorTypeName = GeneratorHelpers.CreateClosedGenericType(
 72896                            TypeDiscoveryHelper.GetFullyQualifiedName(openDecorator.DecoratorType),
 72897                            interfaceName,
 72898                            openGenericName);
 2899
 72900                        decorators.Add(new DiscoveredDecorator(
 72901                            closedDecoratorTypeName,
 72902                            interfaceName,
 72903                            openDecorator.Order,
 72904                            openDecorator.AssemblyName,
 72905                            openDecorator.SourceFilePath));
 2906                    }
 2907                }
 2908            }
 2909        }
 62910    }
 2911
 2912
 2913    /// <summary>
 2914    /// Discovers all referenced assemblies that have the [GenerateTypeRegistry] attribute.
 2915    /// These assemblies need to be force-loaded to ensure their module initializers run.
 2916    /// </summary>
 2917    private static IReadOnlyList<string> DiscoverReferencedAssembliesWithTypeRegistry(Compilation compilation)
 2918    {
 4342919        var result = new List<string>();
 2920
 1474662921        foreach (var reference in compilation.References)
 2922        {
 732992923            if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assemblySymbol)
 2924            {
 2925                // Skip the current assembly
 731122926                if (SymbolEqualityComparer.Default.Equals(assemblySymbol, compilation.Assembly))
 2927                    continue;
 2928
 731122929                if (TypeDiscoveryHelper.HasGenerateTypeRegistryAttribute(assemblySymbol))
 2930                {
 172931                    result.Add(assemblySymbol.Name);
 2932                }
 2933            }
 2934        }
 2935
 4342936        return result;
 2937    }
 2938
 2939    /// <summary>
 2940    /// Discovers types from referenced assemblies with [GenerateTypeRegistry] for diagnostics purposes.
 2941    /// Unlike the main discovery, this includes internal types since we're just showing them in diagnostics.
 2942    /// </summary>
 2943    private static Dictionary<string, List<DiagnosticTypeInfo>> DiscoverReferencedAssemblyTypesForDiagnostics(Compilatio
 2944    {
 952945        var result = new Dictionary<string, List<DiagnosticTypeInfo>>();
 2946
 323282947        foreach (var reference in compilation.References)
 2948        {
 160692949            if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assemblySymbol)
 2950            {
 2951                // Skip the current assembly
 160692952                if (SymbolEqualityComparer.Default.Equals(assemblySymbol, compilation.Assembly))
 2953                    continue;
 2954
 160692955                if (!TypeDiscoveryHelper.HasGenerateTypeRegistryAttribute(assemblySymbol))
 2956                    continue;
 2957
 142958                var assemblyTypes = new List<DiagnosticTypeInfo>();
 2959
 2960                // First pass: collect intercepted service names so we can identify their proxies
 142961                var interceptedServiceNames = new HashSet<string>();
 1202962                foreach (var typeSymbol in TypeDiscoveryHelper.GetAllTypes(assemblySymbol.GlobalNamespace))
 2963                {
 462964                    if (InterceptorDiscoveryHelper.HasInterceptAttributes(typeSymbol))
 2965                    {
 22966                        interceptedServiceNames.Add(typeSymbol.Name);
 2967                    }
 2968                }
 2969
 1202970                foreach (var typeSymbol in TypeDiscoveryHelper.GetAllTypes(assemblySymbol.GlobalNamespace))
 2971                {
 2972                    // Check if it's a registerable type (injectable, plugin, factory source, or interceptor)
 462973                    var hasFactoryAttr = FactoryDiscoveryHelper.HasGenerateFactoryAttribute(typeSymbol);
 462974                    var hasInterceptAttr = InterceptorDiscoveryHelper.HasInterceptAttributes(typeSymbol);
 462975                    var isInterceptorProxy = typeSymbol.Name.EndsWith("_InterceptorProxy");
 2976
 462977                    if (!hasFactoryAttr && !hasInterceptAttr && !isInterceptorProxy &&
 462978                        !TypeDiscoveryHelper.WouldBeInjectableIgnoringAccessibility(typeSymbol) &&
 462979                        !TypeDiscoveryHelper.WouldBePluginIgnoringAccessibility(typeSymbol))
 2980                        continue;
 2981
 182982                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 182983                    var shortName = typeSymbol.Name;
 182984                    var lifetime = TypeDiscoveryHelper.DetermineLifetime(typeSymbol) ?? GeneratorLifetime.Singleton;
 182985                    var interfaces = TypeDiscoveryHelper.GetRegisterableInterfaces(typeSymbol)
 142986                        .Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i))
 182987                        .ToArray();
 182988                    var dependencies = TypeDiscoveryHelper.GetBestConstructorParameters(typeSymbol)?
 182989                        .ToArray() ?? Array.Empty<string>();
 182990                    var isDecorator = TypeDiscoveryHelper.HasDecoratorForAttribute(typeSymbol) ||
 182991                                      OpenDecoratorDiscoveryHelper.HasOpenDecoratorForAttribute(typeSymbol);
 182992                    var isPlugin = TypeDiscoveryHelper.WouldBePluginIgnoringAccessibility(typeSymbol);
 182993                    var keyedValues = TypeDiscoveryHelper.GetKeyedServiceKeys(typeSymbol);
 182994                    var keyedValue = keyedValues.Length > 0 ? keyedValues[0] : null;
 2995
 2996                    // Check if this service has an interceptor proxy (its name + "_InterceptorProxy" exists)
 182997                    var hasInterceptorProxy = interceptedServiceNames.Contains(shortName);
 2998
 182999                    assemblyTypes.Add(new DiagnosticTypeInfo(
 183000                        typeName,
 183001                        shortName,
 183002                        lifetime,
 183003                        interfaces,
 183004                        dependencies,
 183005                        isDecorator,
 183006                        isPlugin,
 183007                        hasFactoryAttr,
 183008                        keyedValue,
 183009                        isInterceptor: hasInterceptAttr,
 183010                        hasInterceptorProxy: hasInterceptorProxy));
 3011                }
 3012
 143013                if (assemblyTypes.Count > 0)
 3014                {
 143015                    result[assemblySymbol.Name] = assemblyTypes;
 3016                }
 3017            }
 3018        }
 3019
 953020        return result;
 3021    }
 3022}

Methods/Properties

Initialize(Microsoft.CodeAnalysis.IncrementalGeneratorInitializationContext)
ReportDisposableCaptiveDependencies(Microsoft.CodeAnalysis.SourceProductionContext,NexusLabs.Needlr.Generators.Models.DiscoveryResult)
CheckForCaptiveDependencies(Microsoft.CodeAnalysis.SourceProductionContext,NexusLabs.Needlr.Generators.Models.DiscoveredType,System.Collections.Generic.Dictionary`2<System.String,NexusLabs.Needlr.Generators.Models.DiscoveredType>)
IsFactoryPattern(System.String)
IsShorterLifetime(NexusLabs.Needlr.Generators.GeneratorLifetime,NexusLabs.Needlr.Generators.GeneratorLifetime)
GetLifetimeName(NexusLabs.Needlr.Generators.GeneratorLifetime)
GetBreadcrumbLevel(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider)
GetProjectDirectory(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider)
GetDiagnosticOptions(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider)
IsAotProject(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider)
DetectPositionalRecord(Microsoft.CodeAnalysis.INamedTypeSymbol)
IsPrimaryConstructor(Microsoft.CodeAnalysis.IMethodSymbol,Microsoft.CodeAnalysis.INamedTypeSymbol)
ExtractBindableProperties(Microsoft.CodeAnalysis.INamedTypeSymbol,System.Collections.Generic.HashSet`1<System.String>)
ExtractDataAnnotations(Microsoft.CodeAnalysis.IPropertySymbol)
IsValidationAttribute(Microsoft.CodeAnalysis.INamedTypeSymbol)
AnalyzeComplexType(Microsoft.CodeAnalysis.ITypeSymbol,System.Collections.Generic.HashSet`1<System.String>)
TryGetNestedProperties(Microsoft.CodeAnalysis.ITypeSymbol,System.Collections.Generic.HashSet`1<System.String>)
IsDictionaryType(Microsoft.CodeAnalysis.INamedTypeSymbol)
IsListType(Microsoft.CodeAnalysis.INamedTypeSymbol)
IsBindableClass(Microsoft.CodeAnalysis.INamedTypeSymbol)
GetAttributeInfoFromCompilation(Microsoft.CodeAnalysis.Compilation)
DiscoverTypes(Microsoft.CodeAnalysis.Compilation,System.String[],System.Boolean)
CollectTypesFromAssembly(Microsoft.CodeAnalysis.IAssemblySymbol,System.Collections.Generic.IReadOnlyList`1<System.String>,System.Collections.Generic.List`1<NexusLabs.Needlr.Generators.Models.DiscoveredType>,System.Collections.Generic.List`1<NexusLabs.Needlr.Generators.Models.DiscoveredPlugin>,System.Collections.Generic.List`1<NexusLabs.Needlr.Generators.Models.DiscoveredDecorator>,System.Collections.Generic.List`1<NexusLabs.Needlr.Generators.Models.DiscoveredOpenDecorator>,System.Collections.Generic.List`1<NexusLabs.Needlr.Generators.Models.DiscoveredInterceptedService>,System.Collections.Generic.List`1<NexusLabs.Needlr.Generators.Models.DiscoveredFactory>,System.Collections.Generic.List`1<NexusLabs.Needlr.Generators.Models.DiscoveredOptions>,System.Collections.Generic.List`1<NexusLabs.Needlr.Generators.Models.DiscoveredHostedService>,System.Collections.Generic.List`1<NexusLabs.Needlr.Generators.Models.DiscoveredProvider>,System.Collections.Generic.List`1<NexusLabs.Needlr.Generators.Models.InaccessibleType>,Microsoft.CodeAnalysis.Compilation,System.Boolean,System.String)
GenerateTypeRegistrySource(NexusLabs.Needlr.Generators.Models.DiscoveryResult,System.String,NexusLabs.Needlr.Generators.BreadcrumbWriter,System.String,System.Boolean)
GenerateModuleInitializerBootstrapSource(System.String,System.Collections.Generic.IReadOnlyList`1<System.String>,NexusLabs.Needlr.Generators.BreadcrumbWriter,System.Boolean,System.Boolean,System.Boolean)
GenerateInjectableTypesArray(System.Text.StringBuilder,System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredType>,NexusLabs.Needlr.Generators.BreadcrumbWriter,System.String)
GeneratePluginTypesArray(System.Text.StringBuilder,System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredPlugin>,NexusLabs.Needlr.Generators.BreadcrumbWriter,System.String)
GenerateRegisterOptionsMethod(System.Text.StringBuilder,System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredOptions>,System.String,NexusLabs.Needlr.Generators.BreadcrumbWriter,System.String,System.Boolean)
GenerateReflectionOptionsRegistration(System.Text.StringBuilder,System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredOptions>,System.String,NexusLabs.Needlr.Generators.BreadcrumbWriter)
GenerateAotOptionsRegistration(System.Text.StringBuilder,System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredOptions>,System.String,NexusLabs.Needlr.Generators.BreadcrumbWriter,System.String)
GenerateConfigureBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.DiscoveredOptions,System.String,System.Collections.Generic.HashSet`1<System.String>)
GenerateInitOnlyBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.DiscoveredOptions,System.String,System.Collections.Generic.HashSet`1<System.String>)
GeneratePositionalRecordBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.DiscoveredOptions,System.String,System.Collections.Generic.HashSet`1<System.String>)
GeneratePropertyParseVariable(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32)
GenerateComplexTypeParseVariable(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32)
GenerateNestedPropertyAssignment(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GenerateChildPropertyAssignment(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GeneratePropertyInitializer(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String)
GenerateParameterParseVariable(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.PositionalRecordParameter,System.Int32)
RegisterValidator(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.DiscoveredOptions,System.String,System.Collections.Generic.HashSet`1<System.String>)
RegisterValidatorForFactory(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.DiscoveredOptions,System.String,System.Collections.Generic.HashSet`1<System.String>)
GeneratePropertyBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String)
GenerateComplexTypeBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String)
GenerateNestedObjectBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GenerateNestedPropertyBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GenerateCollectionBindingInNested(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GenerateArrayBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GenerateArrayBindingCore(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GenerateListBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GenerateListBindingCore(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GenerateDictionaryBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GenerateDictionaryBindingCore(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GenerateChildPropertyBinding(System.Text.StringBuilder,NexusLabs.Needlr.Generators.Models.OptionsPropertyInfo,System.Int32,System.String,System.String)
GeneratePrimitiveCollectionAdd(System.Text.StringBuilder,System.String,System.Int32,System.String)
GeneratePrimitiveListAdd(System.Text.StringBuilder,System.String,System.Int32,System.String)
GeneratePrimitiveDictionaryAdd(System.Text.StringBuilder,System.String,System.Int32,System.String)
GetNonNullableTypeName(System.String)
GetBaseTypeName(System.String)
GenerateApplyDecoratorsMethod(System.Text.StringBuilder,System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredDecorator>,System.Boolean,System.Boolean,System.String,NexusLabs.Needlr.Generators.BreadcrumbWriter,System.String)
GenerateRegisterProvidersMethod(System.Text.StringBuilder,System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredProvider>,System.String,NexusLabs.Needlr.Generators.BreadcrumbWriter,System.String)
GenerateRegisterHostedServicesMethod(System.Text.StringBuilder,System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredHostedService>,NexusLabs.Needlr.Generators.BreadcrumbWriter,System.String)
GenerateInterceptorProxiesSource(System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredInterceptedService>,System.String,NexusLabs.Needlr.Generators.BreadcrumbWriter,System.String)
GenerateFactoriesSource(System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredFactory>,System.String,NexusLabs.Needlr.Generators.BreadcrumbWriter,System.String)
GenerateProvidersSource(System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredProvider>,System.String,NexusLabs.Needlr.Generators.BreadcrumbWriter,System.String)
GenerateShorthandProviderSource(NexusLabs.Needlr.Generators.Models.DiscoveredProvider,System.String,NexusLabs.Needlr.Generators.BreadcrumbWriter,System.String)
GetNamespaceFromTypeName(System.String)
FilterNestedOptions(System.Collections.Generic.List`1<NexusLabs.Needlr.Generators.Models.DiscoveredOptions>,Microsoft.CodeAnalysis.Compilation)
FindTypeSymbol(Microsoft.CodeAnalysis.Compilation,System.String)
ExpandOpenDecorators(System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredType>,System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredOpenDecorator>,System.Collections.Generic.List`1<NexusLabs.Needlr.Generators.Models.DiscoveredDecorator>)
DiscoverReferencedAssembliesWithTypeRegistry(Microsoft.CodeAnalysis.Compilation)
DiscoverReferencedAssemblyTypesForDiagnostics(Microsoft.CodeAnalysis.Compilation)