< 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
87%
Covered lines: 1504
Uncovered lines: 214
Coverable lines: 1718
Total lines: 3301
Line coverage: 87.5%
Branch coverage
77%
Covered branches: 795
Total branches: 1026
Branch coverage: 77.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Initialize(...)96.29%545499.46%
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%
ShouldExportGraph(...)100%44100%
GenerateGraphExportSource(...)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%2626100%
CollectTypesFromAssembly(...)83.96%106106100%
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%
DiscoverReferencedAssemblyTypesForGraph(...)19.04%13134210.34%
GetInterfaceLocationsFromServiceCatalog(...)0%210140%

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
 44121        var compilationAndOptions = context.CompilationProvider
 44122            .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].
 44127        context.RegisterSourceOutput(compilationAndOptions, static (spc, source) =>
 44128        {
 44129            var (compilation, configOptions) = source;
 44130
 44131            var attributeInfo = GetAttributeInfoFromCompilation(compilation);
 44132            if (attributeInfo == null)
 033                return;
 44134
 44135            var info = attributeInfo.Value;
 44136            var assemblyName = compilation.AssemblyName ?? "Generated";
 44137
 44138            // Read breadcrumb level from MSBuild property
 44139            var breadcrumbLevel = GetBreadcrumbLevel(configOptions);
 44140            var projectDirectory = GetProjectDirectory(configOptions);
 44141            var breadcrumbs = new BreadcrumbWriter(breadcrumbLevel);
 44142
 44143            // Check if this is an AOT project
 44144            var isAotProject = IsAotProject(configOptions);
 44145
 44146            var discoveryResult = DiscoverTypes(
 44147                compilation,
 44148                info.NamespacePrefixes,
 44149                info.IncludeSelf);
 44150
 44151            // Discover referenced assemblies with [GenerateTypeRegistry] for forced loading.
 44152            // Done early so the empty-result check below can include this in its decision.
 44153            // Note: Order of force-loading doesn't matter; ordering is applied at service registration time
 44154            var referencedAssemblies = DiscoverReferencedAssembliesWithTypeRegistry(compilation)
 255                .OrderBy(a => a, StringComparer.OrdinalIgnoreCase)
 44156                .ToList();
 44157
 44158            // If nothing was discovered — no injectable types, factories, providers, options,
 44159            // interceptors, hosted services, plugins, no referenced assemblies to force-load,
 44160            // no inaccessible type errors, and no missing TypeRegistry warnings —
 44161            // emit nothing. This avoids compile errors in projects that don't reference Needlr
 44162            // injection packages (e.g. a documentation-only project): the generated bootstrap code
 44163            // references types from those packages and would fail to build without them.
 44164            if (discoveryResult.InjectableTypes.Count == 0 &&
 44165                discoveryResult.PluginTypes.Count == 0 &&
 44166                discoveryResult.Decorators.Count == 0 &&
 44167                discoveryResult.InterceptedServices.Count == 0 &&
 44168                discoveryResult.Factories.Count == 0 &&
 44169                discoveryResult.Options.Count == 0 &&
 44170                discoveryResult.HostedServices.Count == 0 &&
 44171                discoveryResult.Providers.Count == 0 &&
 44172                discoveryResult.InaccessibleTypes.Count == 0 &&
 44173                discoveryResult.MissingTypeRegistryPlugins.Count == 0 &&
 44174                referencedAssemblies.Count == 0)
 44175            {
 376                return;
 44177            }
 44178
 44179            // Report errors for inaccessible internal types in referenced assemblies
 546680            foreach (var inaccessibleType in discoveryResult.InaccessibleTypes)
 44181            {
 229582                spc.ReportDiagnostic(Diagnostic.Create(
 229583                    DiagnosticDescriptors.InaccessibleInternalType,
 229584                    Location.None,
 229585                    inaccessibleType.TypeName,
 229586                    inaccessibleType.AssemblyName));
 44187            }
 44188
 44189            // Report errors for referenced assemblies with internal plugin types but no [GenerateTypeRegistry]
 87890            foreach (var missingPlugin in discoveryResult.MissingTypeRegistryPlugins)
 44191            {
 192                spc.ReportDiagnostic(Diagnostic.Create(
 193                    DiagnosticDescriptors.MissingGenerateTypeRegistryAttribute,
 194                    Location.None,
 195                    missingPlugin.AssemblyName,
 196                    missingPlugin.TypeName));
 44197            }
 44198
 44199            // NDLRGEN020: Previously reported error if [Options] used in AOT project
 441100            // Now removed for parity - we generate best-effort code and let unsupported
 441101            // types fail at runtime (matching non-AOT ConfigurationBinder behavior)
 441102
 441103            // NDLRGEN021: Report warning for non-partial positional records
 1045104            foreach (var opt in discoveryResult.Options.Where(o => o.IsNonPartialPositionalRecord))
 441105            {
 2106                spc.ReportDiagnostic(Diagnostic.Create(
 2107                    DiagnosticDescriptors.PositionalRecordMustBePartial,
 2108                    Location.None,
 2109                    opt.TypeName));
 441110            }
 441111
 441112            // NDLRGEN022: Detect disposable captive dependencies using inferred lifetimes
 438113            ReportDisposableCaptiveDependencies(spc, discoveryResult);
 441114
 438115            var sourceText = GenerateTypeRegistrySource(discoveryResult, assemblyName, breadcrumbs, projectDirectory, is
 438116            spc.AddSource("TypeRegistry.g.cs", SourceText.From(sourceText, Encoding.UTF8));
 441117
 438118            var bootstrapText = GenerateModuleInitializerBootstrapSource(assemblyName, referencedAssemblies, breadcrumbs
 438119            spc.AddSource("NeedlrSourceGenBootstrap.g.cs", SourceText.From(bootstrapText, Encoding.UTF8));
 441120
 441121            // Generate interceptor proxy classes if any were discovered
 438122            if (discoveryResult.InterceptedServices.Count > 0)
 441123            {
 13124                var interceptorProxiesText = GenerateInterceptorProxiesSource(discoveryResult.InterceptedServices, assem
 13125                spc.AddSource("InterceptorProxies.g.cs", SourceText.From(interceptorProxiesText, Encoding.UTF8));
 441126            }
 441127
 441128            // Generate factory classes if any were discovered
 438129            if (discoveryResult.Factories.Count > 0)
 441130            {
 23131                var factoriesText = GenerateFactoriesSource(discoveryResult.Factories, assemblyName, breadcrumbs, projec
 23132                spc.AddSource("Factories.g.cs", SourceText.From(factoriesText, Encoding.UTF8));
 441133            }
 441134
 441135            // Generate provider classes if any were discovered
 438136            if (discoveryResult.Providers.Count > 0)
 441137            {
 441138                // Interface-based providers go in the Generated namespace
 35139                var interfaceProviders = discoveryResult.Providers.Where(p => p.IsInterface).ToList();
 17140                if (interfaceProviders.Count > 0)
 441141                {
 11142                    var providersText = GenerateProvidersSource(interfaceProviders, assemblyName, breadcrumbs, projectDi
 11143                    spc.AddSource("Providers.g.cs", SourceText.From(providersText, Encoding.UTF8));
 441144                }
 441145
 441146                // Shorthand class providers need to be generated in their original namespace
 35147                var classProviders = discoveryResult.Providers.Where(p => !p.IsInterface && p.IsPartial).ToList();
 46148                foreach (var provider in classProviders)
 441149                {
 6150                    var providerText = GenerateShorthandProviderSource(provider, assemblyName, breadcrumbs, projectDirec
 6151                    spc.AddSource($"Provider.{provider.SimpleTypeName}.g.cs", SourceText.From(providerText, Encoding.UTF
 441152                }
 441153            }
 441154
 441155            // Generate options validator classes if any have validation methods
 603156            var optionsWithValidators = discoveryResult.Options.Where(o => o.HasValidatorMethod).ToList();
 438157            if (optionsWithValidators.Count > 0)
 441158            {
 18159                var validatorsText = CodeGen.OptionsCodeGenerator.GenerateOptionsValidatorsSource(optionsWithValidators,
 18160                spc.AddSource("OptionsValidators.g.cs", SourceText.From(validatorsText, Encoding.UTF8));
 441161            }
 441162
 441163            // Generate DataAnnotations validator classes if any have DataAnnotation attributes
 603164            var optionsWithDataAnnotations = discoveryResult.Options.Where(o => o.HasDataAnnotations).ToList();
 438165            if (optionsWithDataAnnotations.Count > 0)
 441166            {
 18167                var dataAnnotationsValidatorsText = CodeGen.OptionsCodeGenerator.GenerateDataAnnotationsValidatorsSource
 18168                spc.AddSource("OptionsDataAnnotationsValidators.g.cs", SourceText.From(dataAnnotationsValidatorsText, En
 441169            }
 441170
 441171            // Generate parameterless constructors for partial positional records with [Options]
 603172            var optionsNeedingConstructors = discoveryResult.Options.Where(o => o.NeedsGeneratedConstructor).ToList();
 438173            if (optionsNeedingConstructors.Count > 0)
 441174            {
 7175                var constructorsText = CodeGen.OptionsCodeGenerator.GeneratePositionalRecordConstructorsSource(optionsNe
 7176                spc.AddSource("OptionsConstructors.g.cs", SourceText.From(constructorsText, Encoding.UTF8));
 441177            }
 441178
 441179            // Generate ServiceCatalog for runtime introspection
 438180            var catalogText = CodeGen.ServiceCatalogCodeGenerator.GenerateServiceCatalogSource(discoveryResult, assembly
 438181            spc.AddSource("ServiceCatalog.g.cs", SourceText.From(catalogText, Encoding.UTF8));
 441182
 441183            // Generate diagnostic output files if configured
 438184            var diagnosticOptions = GetDiagnosticOptions(configOptions);
 438185            if (diagnosticOptions.Enabled)
 441186            {
 95187                var referencedAssemblyTypes = DiscoverReferencedAssemblyTypesForDiagnostics(compilation);
 95188                var diagnosticsText = DiagnosticsGenerator.GenerateDiagnosticsSource(discoveryResult, assemblyName, proj
 95189                spc.AddSource("NeedlrDiagnostics.g.cs", SourceText.From(diagnosticsText, Encoding.UTF8));
 441190            }
 441191
 441192            // Generate IDE graph export if configured
 438193            if (ShouldExportGraph(configOptions))
 441194            {
 441195                // Discover types from referenced assemblies with [GenerateTypeRegistry] for graph inclusion
 4196                var referencedAssemblyTypesForGraph = DiscoverReferencedAssemblyTypesForGraph(compilation);
 441197
 4198                var graphJson = Export.GraphExporter.GenerateGraphJson(
 4199                    discoveryResult,
 4200                    assemblyName,
 4201                    projectDirectory,
 4202                    diagnostics: null,
 4203                    referencedAssemblyTypes: referencedAssemblyTypesForGraph);
 441204
 441205                // Embed graph as a comment in a generated file so it's accessible
 441206                // The actual JSON is written to obj folder via the generated code
 4207                var graphSourceText = GenerateGraphExportSource(graphJson, assemblyName, breadcrumbs, projectDirectory);
 4208                spc.AddSource("NeedlrGraph.g.cs", SourceText.From(graphSourceText, Encoding.UTF8));
 441209            }
 879210        });
 441211    }
 212
 213    /// <summary>
 214    /// Detects disposable captive dependencies using inferred lifetimes from DiscoveryResult.
 215    /// Reports NDLRGEN022 when a longer-lived service depends on a shorter-lived disposable.
 216    /// </summary>
 217    private static void ReportDisposableCaptiveDependencies(SourceProductionContext spc, DiscoveryResult discoveryResult
 218    {
 219        // Build lookup from type name to DiscoveredType for O(1) lifetime lookups
 438220        var typeLookup = new Dictionary<string, DiscoveredType>();
 463074221        foreach (var type in discoveryResult.InjectableTypes)
 222        {
 231099223            typeLookup[type.TypeName] = type;
 224            // Also map by interfaces so we can look up dependencies by interface
 462688225            foreach (var iface in type.InterfaceNames)
 226            {
 227                // Only add if not already present (first registration wins for interface resolution)
 245228                if (!typeLookup.ContainsKey(iface))
 229                {
 239230                    typeLookup[iface] = type;
 231                }
 232            }
 233        }
 234
 235        // Check each injectable type for captive dependencies
 463074236        foreach (var type in discoveryResult.InjectableTypes)
 237        {
 231099238            CheckForCaptiveDependencies(spc, type, typeLookup);
 239        }
 438240    }
 241
 242    /// <summary>
 243    /// Checks a single type for captive dependency issues.
 244    /// </summary>
 245    private static void CheckForCaptiveDependencies(
 246        SourceProductionContext spc,
 247        DiscoveredType type,
 248        Dictionary<string, DiscoveredType> typeLookup)
 249    {
 250        // Skip types with transient lifetime - they can't capture shorter-lived dependencies
 231099251        if (type.Lifetime == GeneratorLifetime.Transient)
 5252            return;
 253
 571552254        foreach (var param in type.ConstructorParameters)
 255        {
 256            // Skip factory patterns that create new instances on demand
 54682257            if (IsFactoryPattern(param.TypeName))
 258                continue;
 259
 260            // Try to find the dependency in our discovered types
 54682261            if (!typeLookup.TryGetValue(param.TypeName, out var dependency))
 262                continue;
 263
 264            // Check if the dependency is shorter-lived
 18002265            if (!IsShorterLifetime(type.Lifetime, dependency.Lifetime))
 266                continue;
 267
 268            // Check if the dependency is disposable
 7269            if (!dependency.IsDisposable)
 270                continue;
 271
 272            // Report the captive dependency
 5273            spc.ReportDiagnostic(Diagnostic.Create(
 5274                DiagnosticDescriptors.DisposableCaptiveDependency,
 5275                Location.None,
 5276                type.TypeName,
 5277                GetLifetimeName(type.Lifetime),
 5278                dependency.TypeName,
 5279                GetLifetimeName(dependency.Lifetime)));
 280        }
 231094281    }
 282
 283    /// <summary>
 284    /// Checks if a type name represents a factory pattern that creates new instances on demand.
 285    /// </summary>
 286    private static bool IsFactoryPattern(string typeName)
 287    {
 288        // Func<T> - factory delegate
 54682289        if (typeName.StartsWith("System.Func<", StringComparison.Ordinal))
 0290            return true;
 291
 292        // Lazy<T> - deferred creation
 54682293        if (typeName.StartsWith("System.Lazy<", StringComparison.Ordinal))
 0294            return true;
 295
 296        // IServiceScopeFactory - creates new scopes
 54682297        if (typeName == "Microsoft.Extensions.DependencyInjection.IServiceScopeFactory")
 0298            return true;
 299
 300        // IServiceProvider - resolves services dynamically
 54682301        if (typeName == "System.IServiceProvider")
 0302            return true;
 303
 54682304        return false;
 305    }
 306
 307    /// <summary>
 308    /// Checks if dependency lifetime is shorter than consumer lifetime.
 309    /// </summary>
 310    private static bool IsShorterLifetime(GeneratorLifetime consumer, GeneratorLifetime dependency)
 311    {
 312        // Singleton > Scoped > Transient (in terms of lifetime duration)
 313        // A shorter lifetime means the dependency will be disposed sooner
 18002314        return (consumer, dependency) switch
 18002315        {
 4316            (GeneratorLifetime.Singleton, GeneratorLifetime.Scoped) => true,
 1317            (GeneratorLifetime.Singleton, GeneratorLifetime.Transient) => true,
 2318            (GeneratorLifetime.Scoped, GeneratorLifetime.Transient) => true,
 17995319            _ => false
 18002320        };
 321    }
 322
 323    /// <summary>
 324    /// Gets the human-readable name for a lifetime.
 325    /// </summary>
 10326    private static string GetLifetimeName(GeneratorLifetime lifetime) => lifetime switch
 10327    {
 4328        GeneratorLifetime.Singleton => "Singleton",
 4329        GeneratorLifetime.Scoped => "Scoped",
 2330        GeneratorLifetime.Transient => "Transient",
 0331        _ => lifetime.ToString()
 10332    };
 333
 334    private static BreadcrumbLevel GetBreadcrumbLevel(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider c
 335    {
 441336        if (configOptions.GlobalOptions.TryGetValue("build_property.NeedlrBreadcrumbLevel", out var levelStr) &&
 441337            !string.IsNullOrWhiteSpace(levelStr))
 338        {
 259339            if (levelStr.Equals("None", StringComparison.OrdinalIgnoreCase))
 17340                return BreadcrumbLevel.None;
 242341            if (levelStr.Equals("Verbose", StringComparison.OrdinalIgnoreCase))
 28342                return BreadcrumbLevel.Verbose;
 343        }
 344
 345        // Default to Minimal
 396346        return BreadcrumbLevel.Minimal;
 347    }
 348
 349    private static string? GetProjectDirectory(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider configOp
 350    {
 351        // Try to get the project directory from MSBuild properties
 441352        if (configOptions.GlobalOptions.TryGetValue("build_property.ProjectDir", out var projectDir) &&
 441353            !string.IsNullOrWhiteSpace(projectDir))
 354        {
 0355            return projectDir.TrimEnd('/', '\\');
 356        }
 357
 441358        return null;
 359    }
 360
 361    private static DiagnosticOptions GetDiagnosticOptions(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvid
 362    {
 438363        configOptions.GlobalOptions.TryGetValue("build_property.NeedlrDiagnostics", out var enabled);
 438364        configOptions.GlobalOptions.TryGetValue("build_property.NeedlrDiagnosticsPath", out var outputPath);
 438365        configOptions.GlobalOptions.TryGetValue("build_property.NeedlrDiagnosticsFilter", out var filter);
 366
 438367        return DiagnosticOptions.Parse(enabled, outputPath, filter);
 368    }
 369
 370    /// <summary>
 371    /// Checks if the IDE graph export is enabled.
 372    /// </summary>
 373    private static bool ShouldExportGraph(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider configOptions
 374    {
 375        // Export graph is disabled by default
 376        // Enable with NeedlrExportGraph=true in project file
 438377        if (configOptions.GlobalOptions.TryGetValue("build_property.NeedlrExportGraph", out var exportGraph) &&
 438378            exportGraph.Equals("true", StringComparison.OrdinalIgnoreCase))
 379        {
 4380            return true;
 381        }
 434382        return false;
 383    }
 384
 385    /// <summary>
 386    /// Generates source code that writes the graph JSON to a file at build time.
 387    /// The graph is embedded in the source and written via a module initializer.
 388    /// </summary>
 389    private static string GenerateGraphExportSource(string graphJson, string assemblyName, BreadcrumbWriter breadcrumbs,
 390    {
 4391        var sb = new StringBuilder();
 392
 4393        breadcrumbs.WriteFileHeader(sb, assemblyName, "Needlr IDE Graph Export");
 394
 4395        sb.AppendLine("using System;");
 4396        sb.AppendLine("using System.IO;");
 4397        sb.AppendLine();
 4398        sb.AppendLine($"namespace {assemblyName}.Generated");
 4399        sb.AppendLine("{");
 4400        sb.AppendLine("    /// <summary>");
 4401        sb.AppendLine("    /// Provides the Needlr dependency graph for IDE tooling.");
 4402        sb.AppendLine("    /// </summary>");
 4403        sb.AppendLine("    internal static class NeedlrGraphExport");
 4404        sb.AppendLine("    {");
 4405        sb.AppendLine("        /// <summary>");
 4406        sb.AppendLine("        /// Gets the dependency graph JSON.");
 4407        sb.AppendLine("        /// </summary>");
 4408        sb.AppendLine("        public static string GraphJson => GraphJsonContent;");
 4409        sb.AppendLine();
 4410        sb.AppendLine("        private const string GraphJsonContent = @\"");
 411
 412        // Escape the JSON for C# verbatim string (double quotes only)
 4413        var escapedJson = graphJson.Replace("\"", "\"\"");
 4414        sb.Append(escapedJson);
 415
 4416        sb.AppendLine("\";");
 4417        sb.AppendLine();
 4418        sb.AppendLine("        /// <summary>");
 4419        sb.AppendLine("        /// Writes the graph to the specified path.");
 4420        sb.AppendLine("        /// </summary>");
 4421        sb.AppendLine("        public static void WriteGraphToFile(string path)");
 4422        sb.AppendLine("        {");
 4423        sb.AppendLine("            File.WriteAllText(path, GraphJson);");
 4424        sb.AppendLine("        }");
 4425        sb.AppendLine("    }");
 4426        sb.AppendLine("}");
 427
 4428        return sb.ToString();
 429    }
 430
 431    /// <summary>
 432    /// Checks if the project is configured for AOT compilation.
 433    /// Returns true if either PublishAot or IsAotCompatible is set to true.
 434    /// </summary>
 435    private static bool IsAotProject(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider configOptions)
 436    {
 441437        if (configOptions.GlobalOptions.TryGetValue("build_property.PublishAot", out var publishAot) &&
 441438            publishAot.Equals("true", StringComparison.OrdinalIgnoreCase))
 439        {
 79440            return true;
 441        }
 442
 362443        if (configOptions.GlobalOptions.TryGetValue("build_property.IsAotCompatible", out var isAotCompatible) &&
 362444            isAotCompatible.Equals("true", StringComparison.OrdinalIgnoreCase))
 445        {
 1446            return true;
 447        }
 448
 361449        return false;
 450    }
 451
 452    /// <summary>
 453    /// Detects if a type is a positional record (record with primary constructor parameters).
 454    /// Returns null if not a positional record, or PositionalRecordInfo if it is.
 455    /// </summary>
 456    private static PositionalRecordInfo? DetectPositionalRecord(INamedTypeSymbol typeSymbol)
 457    {
 458        // Must be a record
 167459        if (!typeSymbol.IsRecord)
 155460            return null;
 461
 462        // Check for primary constructor with parameters
 463        // Records with positional parameters have a primary constructor generated from the record declaration
 12464        var primaryCtor = typeSymbol.InstanceConstructors
 27465            .FirstOrDefault(c => c.Parameters.Length > 0 && IsPrimaryConstructor(c, typeSymbol));
 466
 12467        if (primaryCtor == null)
 3468            return null;
 469
 470        // Check if the record has a parameterless constructor already
 471        // (user-defined or from record with init-only properties)
 9472        var hasParameterlessCtor = typeSymbol.InstanceConstructors
 27473            .Any(c => c.Parameters.Length == 0 && !c.IsImplicitlyDeclared);
 474
 9475        if (hasParameterlessCtor)
 0476            return null; // Doesn't need generated constructor
 477
 478        // Check if partial
 9479        var isPartial = typeSymbol.DeclaringSyntaxReferences
 9480            .Select(r => r.GetSyntax())
 9481            .OfType<Microsoft.CodeAnalysis.CSharp.Syntax.TypeDeclarationSyntax>()
 34482            .Any(s => s.Modifiers.Any(m => m.IsKind(Microsoft.CodeAnalysis.CSharp.SyntaxKind.PartialKeyword)));
 483
 484        // Extract constructor parameters
 9485        var parameters = primaryCtor.Parameters
 23486            .Select(p => new PositionalRecordParameter(p.Name, p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualified
 9487            .ToList();
 488
 489        // Get namespace
 9490        var containingNamespace = typeSymbol.ContainingNamespace.IsGlobalNamespace
 9491            ? ""
 9492            : typeSymbol.ContainingNamespace.ToDisplayString();
 493
 9494        return new PositionalRecordInfo(
 9495            typeSymbol.Name,
 9496            containingNamespace,
 9497            isPartial,
 9498            parameters);
 499    }
 500
 501    /// <summary>
 502    /// Determines if a constructor is the primary constructor of a record.
 503    /// Primary constructors for positional records are synthesized and have matching properties.
 504    /// </summary>
 505    private static bool IsPrimaryConstructor(IMethodSymbol ctor, INamedTypeSymbol recordType)
 506    {
 507        // For positional records, the primary constructor parameters correspond to auto-properties
 508        // Check if each parameter has a matching property
 73509        foreach (var param in ctor.Parameters)
 510        {
 26511            var hasMatchingProperty = recordType.GetMembers()
 26512                .OfType<IPropertySymbol>()
 101513                .Any(p => p.Name.Equals(param.Name, StringComparison.Ordinal) &&
 101514                         SymbolEqualityComparer.Default.Equals(p.Type, param.Type));
 515
 26516            if (!hasMatchingProperty)
 3517                return false;
 518        }
 519
 9520        return true;
 521    }
 522
 523    /// <summary>
 524    /// Extracts bindable properties from an options type for AOT code generation.
 525    /// </summary>
 526    private static IReadOnlyList<OptionsPropertyInfo> ExtractBindableProperties(INamedTypeSymbol typeSymbol, HashSet<str
 527    {
 195528        var properties = new List<OptionsPropertyInfo>();
 195529        visitedTypes ??= new HashSet<string>();
 530
 531        // Prevent infinite recursion for circular references
 195532        var typeFullName = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 195533        if (!visitedTypes.Add(typeFullName))
 534        {
 6535            return properties; // Already visited - circular reference
 536        }
 537
 3294538        foreach (var member in typeSymbol.GetMembers())
 539        {
 1458540            if (member is not IPropertySymbol property)
 541                continue;
 542
 543            // Skip static, indexers, readonly properties without init
 291544            if (property.IsStatic || property.IsIndexer)
 545                continue;
 546
 547            // Must have a setter (set or init)
 291548            if (property.SetMethod == null)
 549                continue;
 550
 551            // Check if it's init-only
 279552            var isInitOnly = property.SetMethod.IsInitOnly;
 553
 554            // Get nullability info
 279555            var isNullable = property.NullableAnnotation == NullableAnnotation.Annotated ||
 279556                             (property.Type is INamedTypeSymbol namedType &&
 279557                              namedType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T);
 558
 279559            var typeName = property.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 560
 561            // Check if it's an enum type
 279562            var isEnum = false;
 279563            string? enumTypeName = null;
 279564            var actualType = property.Type;
 565
 566            // For nullable types, get the underlying type
 279567            if (actualType is INamedTypeSymbol nullableType &&
 279568                nullableType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T &&
 279569                nullableType.TypeArguments.Length == 1)
 570            {
 4571                actualType = nullableType.TypeArguments[0];
 572            }
 573
 279574            if (actualType.TypeKind == TypeKind.Enum)
 575            {
 15576                isEnum = true;
 15577                enumTypeName = actualType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 578            }
 579
 580            // Detect complex types
 279581            var (complexKind, elementTypeName, nestedProps) = AnalyzeComplexType(property.Type, visitedTypes);
 582
 583            // Extract DataAnnotation attributes
 279584            var dataAnnotations = ExtractDataAnnotations(property);
 585
 279586            properties.Add(new OptionsPropertyInfo(
 279587                property.Name,
 279588                typeName,
 279589                isNullable,
 279590                isInitOnly,
 279591                isEnum,
 279592                enumTypeName,
 279593                complexKind,
 279594                elementTypeName,
 279595                nestedProps,
 279596                dataAnnotations));
 597        }
 598
 189599        return properties;
 600    }
 601
 602    private static IReadOnlyList<DataAnnotationInfo> ExtractDataAnnotations(IPropertySymbol property)
 603    {
 279604        var annotations = new List<DataAnnotationInfo>();
 605
 604606        foreach (var attr in property.GetAttributes())
 607        {
 23608            var attrClass = attr.AttributeClass;
 23609            if (attrClass == null) continue;
 610
 611            // Get the attribute type name - use ContainingNamespace + Name for reliable matching
 23612            var attrNamespace = attrClass.ContainingNamespace?.ToDisplayString() ?? "";
 23613            var attrTypeName = attrClass.Name;
 614
 615            // Only process System.ComponentModel.DataAnnotations attributes
 23616            if (attrNamespace != "System.ComponentModel.DataAnnotations")
 617                continue;
 618
 619            // Extract error message if present
 23620            string? errorMessage = null;
 51621            foreach (var namedArg in attr.NamedArguments)
 622            {
 3623                if (namedArg.Key == "ErrorMessage" && namedArg.Value.Value is string msg)
 624                {
 1625                    errorMessage = msg;
 1626                    break;
 627                }
 628            }
 629
 630            // Check for known DataAnnotation attributes
 23631            if (attrTypeName == "RequiredAttribute")
 632            {
 12633                annotations.Add(new DataAnnotationInfo(DataAnnotationKind.Required, errorMessage));
 634            }
 11635            else if (attrTypeName == "RangeAttribute")
 636            {
 12637                object? min = null, max = null;
 6638                if (attr.ConstructorArguments.Length >= 2)
 639                {
 6640                    min = attr.ConstructorArguments[0].Value;
 6641                    max = attr.ConstructorArguments[1].Value;
 642                }
 6643                annotations.Add(new DataAnnotationInfo(DataAnnotationKind.Range, errorMessage, min, max));
 644            }
 5645            else if (attrTypeName == "StringLengthAttribute")
 646            {
 2647                object? maxLen = null;
 2648                int? minLen = null;
 2649                if (attr.ConstructorArguments.Length >= 1)
 650                {
 2651                    maxLen = attr.ConstructorArguments[0].Value;
 652                }
 8653                foreach (var namedArg in attr.NamedArguments)
 654                {
 2655                    if (namedArg.Key == "MinimumLength" && namedArg.Value.Value is int ml)
 656                    {
 2657                        minLen = ml;
 658                    }
 659                }
 2660                annotations.Add(new DataAnnotationInfo(DataAnnotationKind.StringLength, errorMessage, null, maxLen, null
 661            }
 3662            else if (attrTypeName == "MinLengthAttribute")
 663            {
 1664                int? minLen = null;
 1665                if (attr.ConstructorArguments.Length >= 1 && attr.ConstructorArguments[0].Value is int ml)
 666                {
 1667                    minLen = ml;
 668                }
 1669                annotations.Add(new DataAnnotationInfo(DataAnnotationKind.MinLength, errorMessage, null, null, null, min
 670            }
 2671            else if (attrTypeName == "MaxLengthAttribute")
 672            {
 1673                object? maxLen = null;
 1674                if (attr.ConstructorArguments.Length >= 1)
 675                {
 1676                    maxLen = attr.ConstructorArguments[0].Value;
 677                }
 1678                annotations.Add(new DataAnnotationInfo(DataAnnotationKind.MaxLength, errorMessage, null, maxLen));
 679            }
 1680            else if (attrTypeName == "RegularExpressionAttribute")
 681            {
 1682                string? pattern = null;
 1683                if (attr.ConstructorArguments.Length >= 1 && attr.ConstructorArguments[0].Value is string p)
 684                {
 1685                    pattern = p;
 686                }
 1687                annotations.Add(new DataAnnotationInfo(DataAnnotationKind.RegularExpression, errorMessage, null, null, p
 688            }
 0689            else if (attrTypeName == "EmailAddressAttribute")
 690            {
 0691                annotations.Add(new DataAnnotationInfo(DataAnnotationKind.EmailAddress, errorMessage));
 692            }
 0693            else if (attrTypeName == "PhoneAttribute")
 694            {
 0695                annotations.Add(new DataAnnotationInfo(DataAnnotationKind.Phone, errorMessage));
 696            }
 0697            else if (attrTypeName == "UrlAttribute")
 698            {
 0699                annotations.Add(new DataAnnotationInfo(DataAnnotationKind.Url, errorMessage));
 700            }
 0701            else if (IsValidationAttribute(attrClass))
 702            {
 703                // Unsupported validation attribute
 0704                annotations.Add(new DataAnnotationInfo(DataAnnotationKind.Unsupported, errorMessage));
 705            }
 706        }
 707
 279708        return annotations;
 709    }
 710
 711    private static bool IsValidationAttribute(INamedTypeSymbol attrClass)
 712    {
 713        // Check if this inherits from ValidationAttribute
 0714        var current = attrClass.BaseType;
 0715        while (current != null)
 716        {
 0717            if (current.ToDisplayString() == "System.ComponentModel.DataAnnotations.ValidationAttribute")
 0718                return true;
 0719            current = current.BaseType;
 720        }
 0721        return false;
 722    }
 723
 724    private static (ComplexTypeKind Kind, string? ElementTypeName, IReadOnlyList<OptionsPropertyInfo>? NestedProperties)
 725        ITypeSymbol typeSymbol,
 726        HashSet<string> visitedTypes)
 727    {
 728        // Check for array
 279729        if (typeSymbol is IArrayTypeSymbol arrayType)
 730        {
 3731            var elementType = arrayType.ElementType;
 3732            var elementTypeName = elementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 3733            var nestedProps = TryGetNestedProperties(elementType, visitedTypes);
 3734            return (ComplexTypeKind.Array, elementTypeName, nestedProps);
 735        }
 736
 276737        if (typeSymbol is not INamedTypeSymbol namedType)
 738        {
 0739            return (ComplexTypeKind.None, null, null);
 740        }
 741
 742        // Check for Dictionary<string, T>
 276743        if (IsDictionaryType(namedType))
 744        {
 4745            var valueType = namedType.TypeArguments[1];
 4746            var valueTypeName = valueType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 4747            var nestedProps = TryGetNestedProperties(valueType, visitedTypes);
 4748            return (ComplexTypeKind.Dictionary, valueTypeName, nestedProps);
 749        }
 750
 751        // Check for List<T>, IList<T>, ICollection<T>, IEnumerable<T>
 272752        if (IsListType(namedType))
 753        {
 7754            var elementType = namedType.TypeArguments[0];
 7755            var elementTypeName = elementType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 7756            var nestedProps = TryGetNestedProperties(elementType, visitedTypes);
 7757            return (ComplexTypeKind.List, elementTypeName, nestedProps);
 758        }
 759
 760        // Check for nested object (class with bindable properties)
 265761        if (IsBindableClass(namedType))
 762        {
 25763            var nestedProps = ExtractBindableProperties(namedType, visitedTypes);
 25764            if (nestedProps.Count > 0)
 765            {
 19766                return (ComplexTypeKind.NestedObject, null, nestedProps);
 767            }
 768        }
 769
 246770        return (ComplexTypeKind.None, null, null);
 771    }
 772
 773    private static IReadOnlyList<OptionsPropertyInfo>? TryGetNestedProperties(ITypeSymbol elementType, HashSet<string> v
 774    {
 14775        if (elementType is INamedTypeSymbol namedElement && IsBindableClass(namedElement))
 776        {
 3777            var props = ExtractBindableProperties(namedElement, visitedTypes);
 3778            return props.Count > 0 ? props : null;
 779        }
 11780        return null;
 781    }
 782
 783    private static bool IsDictionaryType(INamedTypeSymbol type)
 784    {
 785        // Check for Dictionary<TKey, TValue> or IDictionary<TKey, TValue>
 276786        if (type.TypeArguments.Length != 2)
 272787            return false;
 788
 4789        var typeName = type.OriginalDefinition.ToDisplayString();
 4790        return typeName == "System.Collections.Generic.Dictionary<TKey, TValue>" ||
 4791               typeName == "System.Collections.Generic.IDictionary<TKey, TValue>";
 792    }
 793
 794    private static bool IsListType(INamedTypeSymbol type)
 795    {
 272796        if (type.TypeArguments.Length != 1)
 261797            return false;
 798
 11799        var typeName = type.OriginalDefinition.ToDisplayString();
 11800        return typeName == "System.Collections.Generic.List<T>" ||
 11801               typeName == "System.Collections.Generic.IList<T>" ||
 11802               typeName == "System.Collections.Generic.ICollection<T>" ||
 11803               typeName == "System.Collections.Generic.IEnumerable<T>";
 804    }
 805
 806    private static bool IsBindableClass(INamedTypeSymbol type)
 807    {
 808        // Must be a class or struct, not abstract, not a system type
 279809        if (type.TypeKind != TypeKind.Class && type.TypeKind != TypeKind.Struct)
 17810            return false;
 811
 262812        if (type.IsAbstract)
 4813            return false;
 814
 815        // Skip system types and primitives
 258816        var ns = type.ContainingNamespace?.ToDisplayString() ?? "";
 258817        if (ns.StartsWith("System"))
 818        {
 819            // Skip known non-bindable System namespaces
 230820            if (ns == "System" || ns.StartsWith("System.Collections") || ns.StartsWith("System.Threading"))
 230821                return false;
 822        }
 823
 824        // Must have a parameterless constructor (explicit or implicit)
 825        // Note: Classes without any explicit constructors have an implicit parameterless constructor
 56826        var hasExplicitConstructors = type.InstanceConstructors.Any(c => !c.IsImplicitlyDeclared);
 28827        if (hasExplicitConstructors)
 828        {
 0829            var hasParameterlessCtor = type.InstanceConstructors
 0830                .Any(c => c.Parameters.Length == 0 && c.DeclaredAccessibility == Accessibility.Public);
 0831            return hasParameterlessCtor;
 832        }
 833
 834        // No explicit constructors means implicit parameterless constructor exists
 28835        return true;
 836    }
 837
 838    private static AttributeInfo? GetAttributeInfoFromCompilation(Compilation compilation)
 839    {
 840        // Get assembly-level attributes directly from the compilation
 1323841        foreach (var attribute in compilation.Assembly.GetAttributes())
 842        {
 441843            var attrClassName = attribute.AttributeClass?.ToDisplayString();
 844
 845            // Check if this is our attribute (various name format possibilities)
 441846            if (attrClassName != GenerateTypeRegistryAttributeName)
 847                continue;
 848
 441849            string[]? namespacePrefixes = null;
 441850            var includeSelf = true;
 851
 1020852            foreach (var namedArg in attribute.NamedArguments)
 853            {
 69854                switch (namedArg.Key)
 855                {
 856                    case "IncludeNamespacePrefixes":
 59857                        if (!namedArg.Value.IsNull && namedArg.Value.Values.Length > 0)
 858                        {
 59859                            namespacePrefixes = namedArg.Value.Values
 60860                                .Where(v => v.Value is string)
 60861                                .Select(v => (string)v.Value!)
 59862                                .ToArray();
 863                        }
 59864                        break;
 865
 866                    case "IncludeSelf":
 10867                        if (namedArg.Value.Value is bool selfValue)
 868                        {
 10869                            includeSelf = selfValue;
 870                        }
 871                        break;
 872                }
 873            }
 874
 441875            return new AttributeInfo(namespacePrefixes, includeSelf);
 876        }
 877
 0878        return null;
 879    }
 880
 881    private static DiscoveryResult DiscoverTypes(
 882        Compilation compilation,
 883        string[]? namespacePrefixes,
 884        bool includeSelf)
 885    {
 441886        var injectableTypes = new List<DiscoveredType>();
 441887        var pluginTypes = new List<DiscoveredPlugin>();
 441888        var decorators = new List<DiscoveredDecorator>();
 441889        var openDecorators = new List<DiscoveredOpenDecorator>();
 441890        var interceptedServices = new List<DiscoveredInterceptedService>();
 441891        var factories = new List<DiscoveredFactory>();
 441892        var options = new List<DiscoveredOptions>();
 441893        var hostedServices = new List<DiscoveredHostedService>();
 441894        var providers = new List<DiscoveredProvider>();
 441895        var inaccessibleTypes = new List<InaccessibleType>();
 441896        var prefixList = namespacePrefixes?.ToList();
 897
 898        // Compute the generated namespace for the current assembly
 441899        var currentAssemblyName = compilation.Assembly.Name;
 441900        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(currentAssemblyName);
 441901        var generatedNamespace = $"{safeAssemblyName}.Generated";
 902
 903        // Collect types from the current compilation if includeSelf is true
 441904        if (includeSelf)
 905        {
 440906            CollectTypesFromAssembly(compilation.Assembly, prefixList, injectableTypes, pluginTypes, decorators, openDec
 907        }
 908
 909        // Collect types from all referenced assemblies
 149844910        foreach (var reference in compilation.References)
 911        {
 74481912            if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assemblySymbol)
 913            {
 914                // Skip assemblies that already have [GenerateTypeRegistry] — those assemblies
 915                // register their own types at runtime via their own TypeRegistry and cascade
 916                // loading. Scanning them here would trigger false NDLRGEN001 errors for their
 917                // internal types.
 74294918                if (TypeDiscoveryHelper.HasGenerateTypeRegistryAttribute(assemblySymbol))
 919                    continue;
 920
 921                // For referenced assemblies, they use their own generated namespace
 74276922                var refSafeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblySymbol.Name);
 74276923                var refGeneratedNamespace = $"{refSafeAssemblyName}.Generated";
 74276924                CollectTypesFromAssembly(assemblySymbol, prefixList, injectableTypes, pluginTypes, decorators, openDecor
 925            }
 926        }
 927
 928        // Expand open generic decorators into closed decorator registrations
 441929        if (openDecorators.Count > 0)
 930        {
 6931            ExpandOpenDecorators(injectableTypes, openDecorators, decorators);
 932        }
 933
 934        // Filter out nested options types (types used as properties in other options types)
 441935        if (options.Count > 1)
 936        {
 19937            options = FilterNestedOptions(options, compilation);
 938        }
 939
 940        // Check for referenced assemblies with internal plugin types but no [GenerateTypeRegistry]
 441941        var missingTypeRegistryPlugins = new List<MissingTypeRegistryPlugin>();
 149844942        foreach (var reference in compilation.References)
 943        {
 74481944            if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assemblySymbol)
 945            {
 946                // Skip assemblies that already have [GenerateTypeRegistry]
 74294947                if (TypeDiscoveryHelper.HasGenerateTypeRegistryAttribute(assemblySymbol))
 948                    continue;
 949
 950                // Look for internal types that implement Needlr plugin interfaces
 3634342951                foreach (var typeSymbol in TypeDiscoveryHelper.GetAllTypes(assemblySymbol.GlobalNamespace))
 952                {
 1742895953                    if (!TypeDiscoveryHelper.IsInternalOrLessAccessible(typeSymbol))
 954                        continue;
 955
 83251956                    if (!TypeDiscoveryHelper.ImplementsNeedlrPluginInterface(typeSymbol))
 957                        continue;
 958
 959                    // This is an internal plugin type in an assembly without [GenerateTypeRegistry]
 1960                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 1961                    missingTypeRegistryPlugins.Add(new MissingTypeRegistryPlugin(typeName, assemblySymbol.Name));
 962                }
 963            }
 964        }
 965
 441966        return new DiscoveryResult(injectableTypes, pluginTypes, decorators, inaccessibleTypes, missingTypeRegistryPlugi
 967    }
 968
 969    private static void CollectTypesFromAssembly(
 970        IAssemblySymbol assembly,
 971        IReadOnlyList<string>? namespacePrefixes,
 972        List<DiscoveredType> injectableTypes,
 973        List<DiscoveredPlugin> pluginTypes,
 974        List<DiscoveredDecorator> decorators,
 975        List<DiscoveredOpenDecorator> openDecorators,
 976        List<DiscoveredInterceptedService> interceptedServices,
 977        List<DiscoveredFactory> factories,
 978        List<DiscoveredOptions> options,
 979        List<DiscoveredHostedService> hostedServices,
 980        List<DiscoveredProvider> providers,
 981        List<InaccessibleType> inaccessibleTypes,
 982        Compilation compilation,
 983        bool isCurrentAssembly,
 984        string generatedNamespace)
 985    {
 3637750986        foreach (var typeSymbol in TypeDiscoveryHelper.GetAllTypes(assembly.GlobalNamespace))
 987        {
 1744159988            if (!TypeDiscoveryHelper.MatchesNamespacePrefix(typeSymbol, namespacePrefixes))
 989                continue;
 990
 991            // For referenced assemblies, check if the type would be registerable but is inaccessible
 1512153992            if (!isCurrentAssembly && TypeDiscoveryHelper.IsInternalOrLessAccessible(typeSymbol))
 993            {
 994                // Check if this type would have been registered if it were accessible
 72749995                if (TypeDiscoveryHelper.WouldBeInjectableIgnoringAccessibility(typeSymbol) ||
 72749996                    TypeDiscoveryHelper.WouldBePluginIgnoringAccessibility(typeSymbol))
 997                {
 2295998                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 2295999                    inaccessibleTypes.Add(new InaccessibleType(typeName, assembly.Name));
 1000                }
 22951001                continue; // Skip further processing for inaccessible types
 1002            }
 1003
 1004            // Check for [Options] attribute
 14394041005            if (OptionsAttributeHelper.HasOptionsAttribute(typeSymbol))
 1006            {
 1671007                var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 1671008                var optionsAttrs = OptionsAttributeHelper.GetOptionsAttributes(typeSymbol);
 1671009                var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 1010
 1011                // Detect positional record (record with primary constructor parameters)
 1671012                var positionalRecordInfo = DetectPositionalRecord(typeSymbol);
 1013
 1014                // Extract bindable properties for AOT code generation
 1671015                var properties = ExtractBindableProperties(typeSymbol);
 1016
 6801017                foreach (var optionsAttr in optionsAttrs)
 1018                {
 1019                    // Determine validator type and method
 1731020                    var validatorTypeSymbol = optionsAttr.ValidatorType;
 1731021                    var targetType = validatorTypeSymbol ?? typeSymbol; // Look for method on options class or external 
 1731022                    var methodName = optionsAttr.ValidateMethod ?? "Validate"; // Convention: "Validate"
 1023
 1024                    // Find validation method using convention-based discovery
 1731025                    var validatorMethodInfo = OptionsAttributeHelper.FindValidationMethod(targetType, methodName);
 1731026                    OptionsValidatorInfo? validatorInfo = validatorMethodInfo.HasValue
 1731027                        ? new OptionsValidatorInfo(validatorMethodInfo.Value.MethodName, validatorMethodInfo.Value.IsSta
 1731028                        : null;
 1029
 1030                    // Infer section name if not provided
 1731031                    var sectionName = optionsAttr.SectionName
 1731032                        ?? Helpers.OptionsNamingHelper.InferSectionName(typeSymbol.Name);
 1033
 1731034                    var validatorTypeName = validatorTypeSymbol != null
 1731035                        ? TypeDiscoveryHelper.GetFullyQualifiedName(validatorTypeSymbol)
 1731036                        : null;
 1037
 1731038                    options.Add(new DiscoveredOptions(
 1731039                        typeName,
 1731040                        sectionName,
 1731041                        optionsAttr.Name,
 1731042                        optionsAttr.ValidateOnStart,
 1731043                        assembly.Name,
 1731044                        sourceFilePath,
 1731045                        validatorInfo,
 1731046                        optionsAttr.ValidateMethod,
 1731047                        validatorTypeName,
 1731048                        positionalRecordInfo,
 1731049                        properties));
 1050                }
 1051            }
 1052
 1053            // Check for [GenerateFactory] attribute - these types get factories instead of direct registration
 14394041054            if (FactoryDiscoveryHelper.HasGenerateFactoryAttribute(typeSymbol))
 1055            {
 241056                var factoryConstructors = FactoryDiscoveryHelper.GetFactoryConstructors(typeSymbol);
 241057                if (factoryConstructors.Count > 0)
 1058                {
 1059                    // Has at least one constructor with runtime params - generate factory
 231060                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 231061                    var interfaces = TypeDiscoveryHelper.GetRegisterableInterfaces(typeSymbol);
 291062                    var interfaceNames = interfaces.Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i)).ToArray();
 231063                    var generationMode = FactoryDiscoveryHelper.GetFactoryGenerationMode(typeSymbol);
 231064                    var returnTypeOverride = FactoryDiscoveryHelper.GetFactoryReturnInterfaceType(typeSymbol);
 231065                    var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 1066
 231067                    factories.Add(new DiscoveredFactory(
 231068                        typeName,
 231069                        interfaceNames,
 231070                        assembly.Name,
 231071                        generationMode,
 231072                        factoryConstructors.ToArray(),
 231073                        returnTypeOverride,
 231074                        sourceFilePath));
 1075
 231076                    continue; // Don't add to injectable types - factory handles registration
 1077                }
 1078                // If no runtime params, fall through to normal registration (with warning in future analyzer)
 1079            }
 1080
 1081            // Check for DecoratorFor<T> attributes
 14393811082            var decoratorInfos = TypeDiscoveryHelper.GetDecoratorForAttributes(typeSymbol);
 28788001083            foreach (var decoratorInfo in decoratorInfos)
 1084            {
 191085                var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 191086                decorators.Add(new DiscoveredDecorator(
 191087                    decoratorInfo.DecoratorTypeName,
 191088                    decoratorInfo.ServiceTypeName,
 191089                    decoratorInfo.Order,
 191090                    assembly.Name,
 191091                    sourceFilePath));
 1092            }
 1093
 1094            // Check for OpenDecoratorFor attributes (source-gen only open generic decorators)
 14393811095            var openDecoratorInfos = OpenDecoratorDiscoveryHelper.GetOpenDecoratorForAttributes(typeSymbol);
 28787761096            foreach (var openDecoratorInfo in openDecoratorInfos)
 1097            {
 71098                var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 71099                openDecorators.Add(new DiscoveredOpenDecorator(
 71100                    openDecoratorInfo.DecoratorType,
 71101                    openDecoratorInfo.OpenGenericInterface,
 71102                    openDecoratorInfo.Order,
 71103                    assembly.Name,
 71104                    sourceFilePath));
 1105            }
 1106
 1107            // Check for Intercept attributes and collect intercepted services
 14393811108            if (InterceptorDiscoveryHelper.HasInterceptAttributes(typeSymbol))
 1109            {
 131110                var lifetime = TypeDiscoveryHelper.DetermineLifetime(typeSymbol);
 131111                if (lifetime.HasValue)
 1112                {
 131113                    var classLevelInterceptors = InterceptorDiscoveryHelper.GetInterceptAttributes(typeSymbol);
 131114                    var methodLevelInterceptors = InterceptorDiscoveryHelper.GetMethodLevelInterceptAttributes(typeSymbo
 131115                    var methods = InterceptorDiscoveryHelper.GetInterceptedMethods(typeSymbol, classLevelInterceptors, m
 1116
 131117                    if (methods.Count > 0)
 1118                    {
 131119                        var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 131120                        var interfaces = TypeDiscoveryHelper.GetRegisterableInterfaces(typeSymbol);
 261121                        var interfaceNames = interfaces.Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i)).ToArra
 1122
 1123                        // Collect all unique interceptor types
 131124                        var allInterceptorTypes = classLevelInterceptors
 131125                            .Concat(methodLevelInterceptors)
 161126                            .Select(i => i.InterceptorTypeName)
 131127                            .Distinct()
 131128                            .ToArray();
 1129
 131130                        var interceptedSourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 1131
 131132                        interceptedServices.Add(new DiscoveredInterceptedService(
 131133                            typeName,
 131134                            interfaceNames,
 131135                            assembly.Name,
 131136                            lifetime.Value,
 131137                            methods.ToArray(),
 131138                            allInterceptorTypes,
 131139                            interceptedSourceFilePath));
 1140                    }
 1141                }
 1142            }
 1143
 1144            // Check for injectable types (but skip types that are providers, which are handled separately)
 14393811145            if (TypeDiscoveryHelper.IsInjectableType(typeSymbol, isCurrentAssembly) && !ProviderDiscoveryHelper.HasProvi
 1146            {
 1147                // Determine lifetime first - only include types that are actually injectable
 4054921148                var lifetime = TypeDiscoveryHelper.DetermineLifetime(typeSymbol);
 4054921149                if (lifetime.HasValue)
 1150                {
 2310991151                    var interfaces = TypeDiscoveryHelper.GetRegisterableInterfaces(typeSymbol);
 2310991152                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 2313441153                    var interfaceNames = interfaces.Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i)).ToArray();
 1154
 1155                    // Capture interface locations for navigation
 2310991156                    var interfaceInfos = interfaces.Select(i =>
 2310991157                    {
 2451158                        var ifaceLocation = i.Locations.FirstOrDefault();
 2451159                        var ifaceFilePath = ifaceLocation?.SourceTree?.FilePath;
 2451160                        var ifaceLine = ifaceLocation?.GetLineSpan().StartLinePosition.Line + 1 ?? 0;
 2451161                        return new InterfaceInfo(TypeDiscoveryHelper.GetFullyQualifiedName(i), ifaceFilePath, ifaceLine)
 2310991162                    }).ToArray();
 1163
 1164                    // Check for [DeferToContainer] attribute - use declared types instead of discovered constructors
 2310991165                    var deferredParams = TypeDiscoveryHelper.GetDeferToContainerParameterTypes(typeSymbol);
 1166                    TypeDiscoveryHelper.ConstructorParameterInfo[] constructorParams;
 2310991167                    if (deferredParams != null)
 1168                    {
 1169                        // DeferToContainer doesn't support keyed services - convert to simple params
 101170                        constructorParams = deferredParams.Select(t => new TypeDiscoveryHelper.ConstructorParameterInfo(
 1171                    }
 1172                    else
 1173                    {
 2310941174                        constructorParams = TypeDiscoveryHelper.GetBestConstructorParametersWithKeys(typeSymbol)?.ToArra
 1175                    }
 1176
 1177                    // Get source file path and line for breadcrumbs (null for external assemblies)
 2310991178                    var location = typeSymbol.Locations.FirstOrDefault();
 2310991179                    var sourceFilePath = location?.SourceTree?.FilePath;
 2310991180                    var sourceLine = location?.GetLineSpan().StartLinePosition.Line + 1 ?? 0; // Convert to 1-based
 1181
 1182                    // Get [Keyed] attribute keys
 2310991183                    var serviceKeys = TypeDiscoveryHelper.GetKeyedServiceKeys(typeSymbol);
 1184
 1185                    // Check if this type implements IDisposable or IAsyncDisposable
 2310991186                    var isDisposable = TypeDiscoveryHelper.IsDisposableType(typeSymbol);
 1187
 2310991188                    injectableTypes.Add(new DiscoveredType(typeName, interfaceNames, assembly.Name, lifetime.Value, cons
 1189                }
 1190            }
 1191
 1192            // Check for hosted service types (BackgroundService or IHostedService implementations)
 14393811193            if (TypeDiscoveryHelper.IsHostedServiceType(typeSymbol, isCurrentAssembly))
 1194            {
 71195                var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 71196                var constructorParams = TypeDiscoveryHelper.GetBestConstructorParametersWithKeys(typeSymbol)?.ToArray() 
 71197                var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 1198
 71199                hostedServices.Add(new DiscoveredHostedService(
 71200                    typeName,
 71201                    assembly.Name,
 71202                    GeneratorLifetime.Singleton, // Hosted services are always singleton
 71203                    constructorParams,
 71204                    sourceFilePath));
 1205            }
 1206
 1207            // Check for [Provider] attribute
 14393811208            if (ProviderDiscoveryHelper.HasProviderAttribute(typeSymbol))
 1209            {
 181210                var discoveredProvider = ProviderDiscoveryHelper.DiscoverProvider(typeSymbol, assembly.Name, generatedNa
 181211                if (discoveredProvider.HasValue)
 1212                {
 181213                    providers.Add(discoveredProvider.Value);
 1214                }
 1215            }
 1216
 1217            // Check for plugin types (concrete class with parameterless ctor and interfaces)
 14393811218            if (TypeDiscoveryHelper.IsPluginType(typeSymbol, isCurrentAssembly))
 1219            {
 4005371220                var pluginInterfaces = TypeDiscoveryHelper.GetPluginInterfaces(typeSymbol);
 4005371221                if (pluginInterfaces.Count > 0)
 1222                {
 13831223                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 27711224                    var interfaceNames = pluginInterfaces.Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i)).ToAr
 13831225                    var attributeNames = TypeDiscoveryHelper.GetPluginAttributes(typeSymbol).ToArray();
 13831226                    var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 13831227                    var order = PluginOrderHelper.GetPluginOrder(typeSymbol);
 1228
 13831229                    pluginTypes.Add(new DiscoveredPlugin(typeName, interfaceNames, assembly.Name, attributeNames, source
 1230                }
 1231            }
 1232
 1233            // Check for IHubRegistrationPlugin implementations
 1234            // NOTE: SignalR hub discovery is now handled by NexusLabs.Needlr.SignalR.Generators
 1235
 1236            // Check for SemanticKernel plugin types (classes/statics with [KernelFunction] methods)
 1237            // NOTE: SemanticKernel plugin discovery is now handled by NexusLabs.Needlr.SemanticKernel.Generators
 1238        }
 747161239    }
 1240
 1241    private static string GenerateTypeRegistrySource(DiscoveryResult discoveryResult, string assemblyName, BreadcrumbWri
 1242    {
 4381243        var builder = new StringBuilder();
 4381244        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblyName);
 4381245        var hasOptions = discoveryResult.Options.Count > 0;
 1246
 4381247        breadcrumbs.WriteFileHeader(builder, assemblyName, "Needlr Type Registry");
 4381248        builder.AppendLine("#nullable enable");
 4381249        builder.AppendLine();
 4381250        builder.AppendLine("using System;");
 4381251        builder.AppendLine("using System.Collections.Generic;");
 4381252        builder.AppendLine();
 4381253        if (hasOptions)
 1254        {
 1461255            builder.AppendLine("using Microsoft.Extensions.Configuration;");
 1461256            if (isAotProject)
 1257            {
 781258                builder.AppendLine("using Microsoft.Extensions.Options;");
 1259            }
 1260        }
 4381261        builder.AppendLine("using Microsoft.Extensions.DependencyInjection;");
 4381262        builder.AppendLine();
 4381263        builder.AppendLine("using NexusLabs.Needlr;");
 4381264        builder.AppendLine("using NexusLabs.Needlr.Generators;");
 4381265        builder.AppendLine();
 4381266        builder.AppendLine($"namespace {safeAssemblyName}.Generated;");
 4381267        builder.AppendLine();
 4381268        builder.AppendLine("/// <summary>");
 4381269        builder.AppendLine("/// Compile-time generated registry of injectable types and plugins.");
 4381270        builder.AppendLine("/// This eliminates the need for runtime reflection-based type discovery.");
 4381271        builder.AppendLine("/// </summary>");
 4381272        builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\", \"1
 4381273        builder.AppendLine("public static class TypeRegistry");
 4381274        builder.AppendLine("{");
 1275
 4381276        GenerateInjectableTypesArray(builder, discoveryResult.InjectableTypes, breadcrumbs, projectDirectory);
 4381277        builder.AppendLine();
 4381278        GeneratePluginTypesArray(builder, discoveryResult.PluginTypes, breadcrumbs, projectDirectory);
 1279
 4381280        builder.AppendLine();
 4381281        builder.AppendLine("    /// <summary>");
 4381282        builder.AppendLine("    /// Gets all injectable types discovered at compile time.");
 4381283        builder.AppendLine("    /// </summary>");
 4381284        builder.AppendLine("    /// <returns>A read-only list of injectable type information.</returns>");
 4381285        builder.AppendLine("    public static IReadOnlyList<InjectableTypeInfo> GetInjectableTypes() => _types;");
 4381286        builder.AppendLine();
 4381287        builder.AppendLine("    /// <summary>");
 4381288        builder.AppendLine("    /// Gets all plugin types discovered at compile time.");
 4381289        builder.AppendLine("    /// </summary>");
 4381290        builder.AppendLine("    /// <returns>A read-only list of plugin type information.</returns>");
 4381291        builder.AppendLine("    public static IReadOnlyList<PluginTypeInfo> GetPluginTypes() => _plugins;");
 1292
 4381293        if (hasOptions)
 1294        {
 1461295            builder.AppendLine();
 1461296            GenerateRegisterOptionsMethod(builder, discoveryResult.Options, safeAssemblyName, breadcrumbs, projectDirect
 1297        }
 1298
 4381299        if (discoveryResult.Providers.Count > 0)
 1300        {
 171301            builder.AppendLine();
 171302            GenerateRegisterProvidersMethod(builder, discoveryResult.Providers, safeAssemblyName, breadcrumbs, projectDi
 1303        }
 1304
 4381305        builder.AppendLine();
 4381306        GenerateApplyDecoratorsMethod(builder, discoveryResult.Decorators, discoveryResult.InterceptedServices.Count > 0
 1307
 4381308        if (discoveryResult.HostedServices.Count > 0)
 1309        {
 61310            builder.AppendLine();
 61311            GenerateRegisterHostedServicesMethod(builder, discoveryResult.HostedServices, breadcrumbs, projectDirectory)
 1312        }
 1313
 4381314        builder.AppendLine("}");
 1315
 4381316        return builder.ToString();
 1317    }
 1318
 1319    private static string GenerateModuleInitializerBootstrapSource(string assemblyName, IReadOnlyList<string> referenced
 1320    {
 4381321        var builder = new StringBuilder();
 4381322        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblyName);
 1323
 4381324        breadcrumbs.WriteFileHeader(builder, assemblyName, "Needlr Source-Gen Bootstrap");
 4381325        builder.AppendLine("#nullable enable");
 4381326        builder.AppendLine();
 4381327        builder.AppendLine("using System.Runtime.CompilerServices;");
 4381328        builder.AppendLine();
 4381329        builder.AppendLine("using Microsoft.Extensions.Configuration;");
 4381330        builder.AppendLine("using Microsoft.Extensions.DependencyInjection;");
 4381331        builder.AppendLine();
 4381332        builder.AppendLine($"namespace {safeAssemblyName}.Generated;");
 4381333        builder.AppendLine();
 4381334        builder.AppendLine("internal static class NeedlrSourceGenModuleInitializer");
 4381335        builder.AppendLine("{");
 4381336        builder.AppendLine("    [global::System.Runtime.CompilerServices.ModuleInitializer]");
 4381337        builder.AppendLine("    internal static void Initialize()");
 4381338        builder.AppendLine("    {");
 1339
 1340        // Generate ForceLoadAssemblies call if there are referenced assemblies with [GenerateTypeRegistry]
 4381341        if (referencedAssemblies.Count > 0)
 1342        {
 171343            builder.AppendLine("        // Force-load referenced assemblies to ensure their module initializers run");
 171344            builder.AppendLine("        ForceLoadReferencedAssemblies();");
 171345            builder.AppendLine();
 1346        }
 1347
 4381348        builder.AppendLine("        global::NexusLabs.Needlr.Generators.NeedlrSourceGenBootstrap.Register(");
 4381349        builder.AppendLine($"            global::{safeAssemblyName}.Generated.TypeRegistry.GetInjectableTypes,");
 4381350        builder.AppendLine($"            global::{safeAssemblyName}.Generated.TypeRegistry.GetPluginTypes,");
 1351
 1352        // Generate the decorator/factory/provider applier lambda
 4381353        if (hasFactories || hasProviders)
 1354        {
 401355            builder.AppendLine("            services =>");
 401356            builder.AppendLine("            {");
 401357            builder.AppendLine($"                global::{safeAssemblyName}.Generated.TypeRegistry.ApplyDecorators((ISer
 401358            if (hasFactories)
 1359            {
 231360                builder.AppendLine($"                global::{safeAssemblyName}.Generated.FactoryRegistrations.RegisterF
 1361            }
 401362            if (hasProviders)
 1363            {
 171364                builder.AppendLine($"                global::{safeAssemblyName}.Generated.TypeRegistry.RegisterProviders
 1365            }
 401366            builder.AppendLine("            },");
 1367        }
 1368        else
 1369        {
 3981370            builder.AppendLine($"            services => global::{safeAssemblyName}.Generated.TypeRegistry.ApplyDecorato
 1371        }
 1372
 1373        // Generate the options registrar lambda for NeedlrSourceGenBootstrap (for backward compat)
 4381374        if (hasOptions)
 1375        {
 1461376            builder.AppendLine($"            (services, config) => global::{safeAssemblyName}.Generated.TypeRegistry.Reg
 1377        }
 1378        else
 1379        {
 2921380            builder.AppendLine("            null);");
 1381        }
 1382
 1383        // Also register with SourceGenRegistry (for ConfiguredSyringe without Generators.Attributes dependency)
 4381384        if (hasOptions)
 1385        {
 1461386            builder.AppendLine();
 1461387            builder.AppendLine("        // Register options with core SourceGenRegistry for ConfiguredSyringe");
 1461388            builder.AppendLine($"        global::NexusLabs.Needlr.SourceGenRegistry.RegisterOptionsRegistrar(");
 1461389            builder.AppendLine($"            (services, config) => global::{safeAssemblyName}.Generated.TypeRegistry.Reg
 1390        }
 1391
 4381392        builder.AppendLine("    }");
 1393
 1394        // Generate ForceLoadReferencedAssemblies method if needed
 4381395        if (referencedAssemblies.Count > 0)
 1396        {
 171397            builder.AppendLine();
 171398            builder.AppendLine("    /// <summary>");
 171399            builder.AppendLine("    /// Forces referenced assemblies with [GenerateTypeRegistry] to load,");
 171400            builder.AppendLine("    /// ensuring their module initializers execute and register their types.");
 171401            builder.AppendLine("    /// </summary>");
 171402            builder.AppendLine("    /// <remarks>");
 171403            builder.AppendLine("    /// Without this, transitive dependencies that are never directly referenced");
 171404            builder.AppendLine("    /// in code would not be loaded by the CLR, and their plugins would not be discovere
 171405            builder.AppendLine("    /// </remarks>");
 171406            builder.AppendLine("    [MethodImpl(MethodImplOptions.NoInlining)]");
 171407            builder.AppendLine("    private static void ForceLoadReferencedAssemblies()");
 171408            builder.AppendLine("    {");
 1409
 701410            foreach (var referencedAssembly in referencedAssemblies)
 1411            {
 181412                var safeRefAssemblyName = GeneratorHelpers.SanitizeIdentifier(referencedAssembly);
 181413                builder.AppendLine($"        _ = typeof(global::{safeRefAssemblyName}.Generated.TypeRegistry).Assembly;"
 1414            }
 1415
 171416            builder.AppendLine("    }");
 1417        }
 1418
 4381419        builder.AppendLine("}");
 1420
 4381421        return builder.ToString();
 1422    }
 1423
 1424    private static void GenerateInjectableTypesArray(StringBuilder builder, IReadOnlyList<DiscoveredType> types, Breadcr
 1425    {
 4381426        builder.AppendLine("    private static readonly InjectableTypeInfo[] _types =");
 4381427        builder.AppendLine("    [");
 1428
 2596371429        var typesByAssembly = types.GroupBy(t => t.AssemblyName).OrderBy(g => g.Key);
 1430
 570761431        foreach (var group in typesByAssembly)
 1432        {
 281001433            breadcrumbs.WriteInlineComment(builder, "        ", $"From {group.Key}");
 1434
 7494971435            foreach (var type in group.OrderBy(t => t.TypeName))
 1436            {
 1437                // Write breadcrumb for this type
 2310991438                if (breadcrumbs.Level == BreadcrumbLevel.Verbose)
 1439                {
 151321440                    var sourcePath = type.SourceFilePath != null
 151321441                        ? BreadcrumbWriter.GetRelativeSourcePath(type.SourceFilePath, projectDirectory)
 151321442                        : $"[{type.AssemblyName}]";
 151321443                    var interfaces = type.InterfaceNames.Length > 0
 111444                        ? string.Join(", ", type.InterfaceNames.Select(i => i.Split('.').Last()))
 151321445                        : "none";
 151321446                    var keysInfo = type.ServiceKeys.Length > 0
 01447                        ? $"Keys: {string.Join(", ", type.ServiceKeys.Select(k => $"\"{k}\""))}"
 151321448                        : null;
 1449
 151321450                    if (keysInfo != null)
 1451                    {
 01452                        breadcrumbs.WriteVerboseBox(builder, "        ",
 01453                            $"{type.TypeName.Split('.').Last()} → {interfaces}",
 01454                            $"Source: {sourcePath}",
 01455                            $"Lifetime: {type.Lifetime}",
 01456                            keysInfo);
 1457                    }
 1458                    else
 1459                    {
 151321460                        breadcrumbs.WriteVerboseBox(builder, "        ",
 151321461                            $"{type.TypeName.Split('.').Last()} → {interfaces}",
 151321462                            $"Source: {sourcePath}",
 151321463                            $"Lifetime: {type.Lifetime}");
 1464                    }
 1465                }
 1466
 2310991467                builder.Append($"        new(typeof({type.TypeName}), ");
 1468
 1469                // Interfaces
 2310991470                if (type.InterfaceNames.Length == 0)
 1471                {
 2308591472                    builder.Append("Array.Empty<Type>(), ");
 1473                }
 1474                else
 1475                {
 2401476                    builder.Append("[");
 4851477                    builder.Append(string.Join(", ", type.InterfaceNames.Select(i => $"typeof({i})")));
 2401478                    builder.Append("], ");
 1479                }
 1480
 1481                // Lifetime
 2310991482                builder.Append($"InjectableLifetime.{type.Lifetime}, ");
 1483
 1484                // Factory lambda - resolves dependencies and creates instance without reflection
 2310991485                builder.Append("sp => new ");
 2310991486                builder.Append(type.TypeName);
 2310991487                builder.Append("(");
 2310991488                if (type.ConstructorParameters.Length > 0)
 1489                {
 462741490                    var parameterExpressions = type.ConstructorParameters
 1009571491                        .Select(p => p.IsKeyed
 1009571492                            ? $"sp.GetRequiredKeyedService<{p.TypeName}>(\"{GeneratorHelpers.EscapeStringLiteral(p.Servi
 1009571493                            : $"sp.GetRequiredService<{p.TypeName}>()");
 462741494                    builder.Append(string.Join(", ", parameterExpressions));
 1495                }
 2310991496                builder.Append("), ");
 1497
 1498                // Service keys from [Keyed] attributes
 2310991499                if (type.ServiceKeys.Length == 0)
 1500                {
 2310941501                    builder.AppendLine("Array.Empty<string>()),");
 1502                }
 1503                else
 1504                {
 51505                    builder.Append("[");
 101506                    builder.Append(string.Join(", ", type.ServiceKeys.Select(k => $"\"{GeneratorHelpers.EscapeStringLite
 51507                    builder.AppendLine("]),");
 1508                }
 1509            }
 1510        }
 1511
 4381512        builder.AppendLine("    ];");
 4381513    }
 1514
 1515    private static void GeneratePluginTypesArray(StringBuilder builder, IReadOnlyList<DiscoveredPlugin> plugins, Breadcr
 1516    {
 4381517        builder.AppendLine("    private static readonly PluginTypeInfo[] _plugins =");
 4381518        builder.AppendLine("    [");
 1519
 1520        // Sort plugins by Order first, then by TypeName for determinism
 4381521        var sortedPlugins = plugins
 13681522            .OrderBy(p => p.Order)
 13681523            .ThenBy(p => p.TypeName, StringComparer.Ordinal)
 4381524            .ToList();
 1525
 1526        // Group for breadcrumb display, but maintain the sorted order
 23691527        var pluginsByAssembly = sortedPlugins.GroupBy(p => p.AssemblyName).OrderBy(g => g.Key);
 1528
 19721529        foreach (var group in pluginsByAssembly)
 1530        {
 5481531            breadcrumbs.WriteInlineComment(builder, "        ", $"From {group.Key}");
 1532
 1533            // Maintain order within assembly group
 66281534            foreach (var plugin in group.OrderBy(p => p.Order).ThenBy(p => p.TypeName, StringComparer.Ordinal))
 1535            {
 1536                // Write verbose breadcrumb for this plugin
 13831537                if (breadcrumbs.Level == BreadcrumbLevel.Verbose)
 1538                {
 911539                    var sourcePath = plugin.SourceFilePath != null
 911540                        ? BreadcrumbWriter.GetRelativeSourcePath(plugin.SourceFilePath, projectDirectory)
 911541                        : $"[{plugin.AssemblyName}]";
 911542                    var interfaces = plugin.InterfaceNames.Length > 0
 911543                        ? string.Join(", ", plugin.InterfaceNames.Select(i => i.Split('.').Last()))
 911544                        : "none";
 911545                    var orderInfo = plugin.Order != 0 ? $"Order: {plugin.Order}" : "Order: 0 (default)";
 1546
 911547                    breadcrumbs.WriteVerboseBox(builder, "        ",
 911548                        $"Plugin: {plugin.TypeName.Split('.').Last()}",
 911549                        $"Source: {sourcePath}",
 911550                        $"Implements: {interfaces}",
 911551                        orderInfo);
 1552                }
 12921553                else if (breadcrumbs.Level == BreadcrumbLevel.Minimal && plugin.Order != 0)
 1554                {
 1555                    // Show order in minimal mode only if non-default
 01556                    breadcrumbs.WriteInlineComment(builder, "        ", $"{plugin.TypeName.Split('.').Last()} (Order: {p
 1557                }
 1558
 13831559                builder.Append($"        new(typeof({plugin.TypeName}), ");
 1560
 1561                // Interfaces
 13831562                if (plugin.InterfaceNames.Length == 0)
 1563                {
 01564                    builder.Append("Array.Empty<Type>(), ");
 1565                }
 1566                else
 1567                {
 13831568                    builder.Append("[");
 27711569                    builder.Append(string.Join(", ", plugin.InterfaceNames.Select(i => $"typeof({i})")));
 13831570                    builder.Append("], ");
 1571                }
 1572
 1573                // Factory lambda - no Activator.CreateInstance needed
 13831574                builder.Append($"() => new {plugin.TypeName}(), ");
 1575
 1576                // Attributes
 13831577                if (plugin.AttributeNames.Length == 0)
 1578                {
 13371579                    builder.Append("Array.Empty<Type>(), ");
 1580                }
 1581                else
 1582                {
 461583                    builder.Append("[");
 1061584                    builder.Append(string.Join(", ", plugin.AttributeNames.Select(a => $"typeof({a})")));
 461585                    builder.Append("], ");
 1586                }
 1587
 1588                // Order
 13831589                builder.AppendLine($"{plugin.Order}),");
 1590            }
 1591        }
 1592
 4381593        builder.AppendLine("    ];");
 4381594    }
 1595
 1596    private static void GenerateRegisterOptionsMethod(StringBuilder builder, IReadOnlyList<DiscoveredOptions> options, s
 1597    {
 1461598        builder.AppendLine("    /// <summary>");
 1461599        builder.AppendLine("    /// Registers all discovered options types with the service collection.");
 1461600        builder.AppendLine("    /// This binds configuration sections to strongly-typed options classes.");
 1461601        builder.AppendLine("    /// </summary>");
 1461602        builder.AppendLine("    /// <param name=\"services\">The service collection to configure.</param>");
 1461603        builder.AppendLine("    /// <param name=\"configuration\">The configuration root to bind options from.</param>")
 1461604        builder.AppendLine("    public static void RegisterOptions(IServiceCollection services, IConfiguration configura
 1461605        builder.AppendLine("    {");
 1606
 1461607        if (options.Count == 0)
 1608        {
 01609            breadcrumbs.WriteInlineComment(builder, "        ", "No options types discovered");
 1610        }
 1461611        else if (isAotProject)
 1612        {
 781613            GenerateAotOptionsRegistration(builder, options, safeAssemblyName, breadcrumbs, projectDirectory);
 1614        }
 1615        else
 1616        {
 681617            GenerateReflectionOptionsRegistration(builder, options, safeAssemblyName, breadcrumbs);
 1618        }
 1619
 1461620        builder.AppendLine("    }");
 1461621    }
 1622
 1623    private static void GenerateReflectionOptionsRegistration(StringBuilder builder, IReadOnlyList<DiscoveredOptions> op
 1624    {
 1625        // Track external validators to register (avoid duplicates)
 681626        var externalValidatorsToRegister = new HashSet<string>();
 1627
 2921628        foreach (var opt in options)
 1629        {
 781630            var typeName = opt.TypeName;
 1631
 781632            if (opt.ValidateOnStart)
 1633            {
 1634                // Use AddOptions pattern for validation support
 1635                // services.AddOptions<T>().BindConfiguration("Section").ValidateDataAnnotations().ValidateOnStart();
 231636                builder.Append($"        services.AddOptions<{typeName}>");
 1637
 231638                if (opt.IsNamed)
 1639                {
 11640                    builder.Append($"(\"{opt.Name}\")");
 1641                }
 1642                else
 1643                {
 221644                    builder.Append("()");
 1645                }
 1646
 231647                builder.Append($".BindConfiguration(\"{opt.SectionName}\")");
 231648                builder.Append(".ValidateDataAnnotations()");
 231649                builder.AppendLine(".ValidateOnStart();");
 1650
 1651                // Register source-generated DataAnnotations validator if present
 1652                // This runs alongside .ValidateDataAnnotations() - source-gen handles supported attributes,
 1653                // reflection fallback handles unsupported attributes (like [CustomValidation])
 231654                if (opt.HasDataAnnotations)
 1655                {
 21656                    var shortTypeName = GeneratorHelpers.GetShortTypeName(typeName);
 21657                    var dataAnnotationsValidatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}DataAn
 21658                    builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOpt
 1659                }
 1660
 1661                // If there's a custom validator method, register the generated validator
 231662                if (opt.HasValidatorMethod)
 1663                {
 121664                    var shortTypeName = GeneratorHelpers.GetShortTypeName(typeName);
 121665                    var validatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}Validator";
 121666                    builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOpt
 1667
 1668                    // If external validator with instance method, register it too
 121669                    if (opt.HasExternalValidator && opt.ValidatorMethod != null && !opt.ValidatorMethod.Value.IsStatic)
 1670                    {
 31671                        externalValidatorsToRegister.Add(opt.ValidatorTypeName!);
 1672                    }
 1673                }
 1674            }
 551675            else if (opt.IsNamed)
 1676            {
 1677                // Named options: OptionsConfigurationServiceCollectionExtensions.Configure<T>(services, "name", section
 81678                builder.AppendLine($"        global::Microsoft.Extensions.DependencyInjection.OptionsConfigurationServic
 1679            }
 1680            else
 1681            {
 1682                // Default options: OptionsConfigurationServiceCollectionExtensions.Configure<T>(services, section)
 471683                builder.AppendLine($"        global::Microsoft.Extensions.DependencyInjection.OptionsConfigurationServic
 1684            }
 1685        }
 1686
 1687        // Register external validators that have instance methods
 1421688        foreach (var validatorType in externalValidatorsToRegister)
 1689        {
 31690            builder.AppendLine($"        services.AddSingleton<{validatorType}>();");
 1691        }
 681692    }
 1693
 1694    private static void GenerateAotOptionsRegistration(StringBuilder builder, IReadOnlyList<DiscoveredOptions> options, 
 1695    {
 781696        breadcrumbs.WriteInlineComment(builder, "        ", "AOT-compatible options binding (no reflection)");
 1697
 781698        var externalValidatorsToRegister = new HashSet<string>();
 1699
 3301700        foreach (var opt in options)
 1701        {
 871702            var typeName = opt.TypeName;
 871703            builder.AppendLine();
 871704            builder.AppendLine($"        // Bind {opt.SectionName} section to {GeneratorHelpers.GetShortTypeName(typeNam
 1705
 1706            // Choose binding pattern based on type characteristics
 871707            if (opt.IsPositionalRecord)
 1708            {
 1709                // Positional records: Use constructor binding with Options.Create
 51710                GeneratePositionalRecordBinding(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 1711            }
 821712            else if (opt.HasInitOnlyProperties)
 1713            {
 1714                // Classes/records with init-only properties: Use object initializer with Options.Create
 71715                GenerateInitOnlyBinding(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 1716            }
 1717            else
 1718            {
 1719                // Regular classes with setters: Use Configure delegate pattern
 751720                GenerateConfigureBinding(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 1721            }
 1722        }
 1723
 1724        // Register external validators that have instance methods
 1581725        foreach (var validatorType in externalValidatorsToRegister)
 1726        {
 11727            builder.AppendLine($"        services.AddSingleton<{validatorType}>();");
 1728        }
 781729    }
 1730
 1731    private static void GenerateConfigureBinding(StringBuilder builder, DiscoveredOptions opt, string safeAssemblyName, 
 1732    {
 751733        var typeName = opt.TypeName;
 1734
 751735        if (opt.IsNamed)
 1736        {
 131737            builder.AppendLine($"        services.AddOptions<{typeName}>(\"{opt.Name}\")");
 1738        }
 1739        else
 1740        {
 621741            builder.AppendLine($"        services.AddOptions<{typeName}>()");
 1742        }
 1743
 751744        builder.AppendLine("            .Configure<IConfiguration>((options, config) =>");
 751745        builder.AppendLine("            {");
 751746        builder.AppendLine($"                var section = config.GetSection(\"{opt.SectionName}\");");
 1747
 1748        // Generate property binding for each property
 751749        var propIndex = 0;
 3901750        foreach (var prop in opt.Properties)
 1751        {
 1201752            GeneratePropertyBinding(builder, prop, propIndex);
 1201753            propIndex++;
 1754        }
 1755
 751756        builder.Append("            })");
 1757
 1758        // Add validation chain if ValidateOnStart is enabled
 751759        if (opt.ValidateOnStart)
 1760        {
 201761            builder.AppendLine();
 201762            builder.Append("            .ValidateOnStart()");
 1763        }
 1764
 751765        builder.AppendLine(";");
 1766
 751767        RegisterValidator(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 751768    }
 1769
 1770    private static void GenerateInitOnlyBinding(StringBuilder builder, DiscoveredOptions opt, string safeAssemblyName, H
 1771    {
 71772        var typeName = opt.TypeName;
 1773
 1774        // Use AddSingleton with IOptions<T> factory pattern for init-only
 71775        builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IOptions<{typeName}>>(sp
 71776        builder.AppendLine("        {");
 71777        builder.AppendLine($"            var config = sp.GetRequiredService<IConfiguration>();");
 71778        builder.AppendLine($"            var section = config.GetSection(\"{opt.SectionName}\");");
 1779
 1780        // Generate parsing variables first
 71781        var propIndex = 0;
 441782        foreach (var prop in opt.Properties)
 1783        {
 151784            GeneratePropertyParseVariable(builder, prop, propIndex);
 151785            propIndex++;
 1786        }
 1787
 1788        // Create object with initializer
 71789        builder.AppendLine($"            return global::Microsoft.Extensions.Options.Options.Create(new {typeName}");
 71790        builder.AppendLine("            {");
 1791
 71792        propIndex = 0;
 441793        foreach (var prop in opt.Properties)
 1794        {
 151795            var comma = propIndex < opt.Properties.Count - 1 ? "," : "";
 151796            GeneratePropertyInitializer(builder, prop, propIndex, comma);
 151797            propIndex++;
 1798        }
 1799
 71800        builder.AppendLine("            });");
 71801        builder.AppendLine("        });");
 1802
 1803        // For validation with factory pattern, we need to register the validator separately
 71804        if (opt.ValidateOnStart)
 1805        {
 11806            RegisterValidatorForFactory(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 1807        }
 71808    }
 1809
 1810    private static void GeneratePositionalRecordBinding(StringBuilder builder, DiscoveredOptions opt, string safeAssembl
 1811    {
 51812        var typeName = opt.TypeName;
 51813        var recordInfo = opt.PositionalRecordInfo!.Value;
 1814
 1815        // Use AddSingleton with IOptions<T> factory pattern for positional records
 51816        builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IOptions<{typeName}>>(sp
 51817        builder.AppendLine("        {");
 51818        builder.AppendLine($"            var config = sp.GetRequiredService<IConfiguration>();");
 51819        builder.AppendLine($"            var section = config.GetSection(\"{opt.SectionName}\");");
 1820
 1821        // Generate parsing variables for each constructor parameter
 51822        var paramIndex = 0;
 361823        foreach (var param in recordInfo.Parameters)
 1824        {
 131825            GenerateParameterParseVariable(builder, param, paramIndex);
 131826            paramIndex++;
 1827        }
 1828
 1829        // Create record with constructor
 51830        builder.Append($"            return global::Microsoft.Extensions.Options.Options.Create(new {typeName}(");
 1831
 51832        paramIndex = 0;
 361833        foreach (var param in recordInfo.Parameters)
 1834        {
 211835            if (paramIndex > 0) builder.Append(", ");
 131836            builder.Append($"p{paramIndex}");
 131837            paramIndex++;
 1838        }
 1839
 51840        builder.AppendLine("));");
 51841        builder.AppendLine("        });");
 1842
 1843        // For validation with factory pattern, we need to register the validator separately
 51844        if (opt.ValidateOnStart)
 1845        {
 01846            RegisterValidatorForFactory(builder, opt, safeAssemblyName, externalValidatorsToRegister);
 1847        }
 51848    }
 1849
 1850    private static void GeneratePropertyParseVariable(StringBuilder builder, OptionsPropertyInfo prop, int index)
 1851    {
 151852        var varName = $"p{index}";
 151853        var typeName = prop.TypeName;
 151854        var baseTypeName = GetBaseTypeName(typeName);
 1855
 1856        // Handle complex types
 151857        if (prop.ComplexTypeKind != ComplexTypeKind.None)
 1858        {
 21859            GenerateComplexTypeParseVariable(builder, prop, index);
 21860            return;
 1861        }
 1862
 1863        // Handle enums
 131864        if (prop.IsEnum && prop.EnumTypeName != null)
 1865        {
 01866            var defaultVal = prop.IsNullable ? "null" : "default";
 01867            builder.AppendLine($"            var {varName} = section[\"{prop.Name}\"] is {{ }} v{index} && global::Syste
 01868            return;
 1869        }
 1870
 1871        // Handle primitives
 131872        if (baseTypeName == "string" || baseTypeName == "global::System.String")
 1873        {
 71874            var defaultVal = prop.IsNullable ? "null" : "\"\"";
 71875            builder.AppendLine($"            var {varName} = section[\"{prop.Name}\"] ?? {defaultVal};");
 1876        }
 61877        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 1878        {
 51879            var defaultVal = prop.IsNullable ? "null" : "0";
 51880            builder.AppendLine($"            var {varName} = section[\"{prop.Name}\"] is {{ }} v{index} && int.TryParse(
 1881        }
 11882        else if (baseTypeName == "bool" || baseTypeName == "global::System.Boolean")
 1883        {
 11884            var defaultVal = prop.IsNullable ? "null" : "false";
 11885            builder.AppendLine($"            var {varName} = section[\"{prop.Name}\"] is {{ }} v{index} && bool.TryParse
 1886        }
 01887        else if (baseTypeName == "double" || baseTypeName == "global::System.Double")
 1888        {
 01889            var defaultVal = prop.IsNullable ? "null" : "0.0";
 01890            builder.AppendLine($"            var {varName} = section[\"{prop.Name}\"] is {{ }} v{index} && double.TryPar
 1891        }
 1892        else
 1893        {
 1894            // Default to default value for unsupported types
 01895            builder.AppendLine($"            var {varName} = default({typeName}); // Unsupported type");
 1896        }
 01897    }
 1898
 1899    private static void GenerateComplexTypeParseVariable(StringBuilder builder, OptionsPropertyInfo prop, int index)
 1900    {
 21901        var varName = $"p{index}";
 21902        var sectionVar = $"sec{index}";
 1903
 21904        switch (prop.ComplexTypeKind)
 1905        {
 1906            case ComplexTypeKind.NestedObject:
 11907                builder.AppendLine($"            var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 11908                builder.AppendLine($"            var {varName} = new {GetNonNullableTypeName(prop.TypeName)}();");
 11909                if (prop.NestedProperties != null)
 1910                {
 11911                    var nestedIndex = index * 100;
 61912                    foreach (var nested in prop.NestedProperties)
 1913                    {
 21914                        GenerateNestedPropertyAssignment(builder, nested, nestedIndex, varName, sectionVar);
 21915                        nestedIndex++;
 1916                    }
 1917                }
 1918                break;
 1919
 1920            case ComplexTypeKind.List:
 11921                var listElemType = prop.ElementTypeName ?? "string";
 11922                builder.AppendLine($"            var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 11923                builder.AppendLine($"            var {varName} = new global::System.Collections.Generic.List<{listElemTy
 11924                builder.AppendLine($"            foreach (var child in {sectionVar}.GetChildren())");
 11925                builder.AppendLine("            {");
 11926                if (prop.NestedProperties != null && prop.NestedProperties.Count > 0)
 1927                {
 01928                    builder.AppendLine($"                var item = new {listElemType}();");
 01929                    var ni = index * 100;
 01930                    foreach (var np in prop.NestedProperties)
 1931                    {
 01932                        GenerateChildPropertyAssignment(builder, np, ni, "item", "child");
 01933                        ni++;
 1934                    }
 01935                    builder.AppendLine($"                {varName}.Add(item);");
 1936                }
 1937                else
 1938                {
 11939                    builder.AppendLine($"                if (child.Value is {{ }} val) {varName}.Add(val);");
 1940                }
 11941                builder.AppendLine("            }");
 11942                break;
 1943
 1944            case ComplexTypeKind.Dictionary:
 01945                var dictValType = prop.ElementTypeName ?? "string";
 01946                builder.AppendLine($"            var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 01947                builder.AppendLine($"            var {varName} = new global::System.Collections.Generic.Dictionary<strin
 01948                builder.AppendLine($"            foreach (var child in {sectionVar}.GetChildren())");
 01949                builder.AppendLine("            {");
 01950                if (prop.NestedProperties != null && prop.NestedProperties.Count > 0)
 1951                {
 01952                    builder.AppendLine($"                var item = new {dictValType}();");
 01953                    var ni = index * 100;
 01954                    foreach (var np in prop.NestedProperties)
 1955                    {
 01956                        GenerateChildPropertyAssignment(builder, np, ni, "item", "child");
 01957                        ni++;
 1958                    }
 01959                    builder.AppendLine($"                {varName}[child.Key] = item;");
 1960                }
 01961                else if (dictValType == "int" || dictValType == "global::System.Int32")
 1962                {
 01963                    builder.AppendLine($"                if (child.Value is {{ }} val && int.TryParse(val, out var iv)) 
 1964                }
 1965                else
 1966                {
 01967                    builder.AppendLine($"                if (child.Value is {{ }} val) {varName}[child.Key] = val;");
 1968                }
 01969                builder.AppendLine("            }");
 01970                break;
 1971
 1972            default:
 01973                builder.AppendLine($"            var {varName} = default({prop.TypeName}); // Complex type");
 1974                break;
 1975        }
 11976    }
 1977
 1978    private static void GenerateNestedPropertyAssignment(StringBuilder builder, OptionsPropertyInfo prop, int index, str
 1979    {
 21980        var varName = $"nv{index}";
 21981        var baseTypeName = GetBaseTypeName(prop.TypeName);
 1982
 21983        if (baseTypeName == "string" || baseTypeName == "global::System.String")
 1984        {
 11985            builder.AppendLine($"            if ({sectionVar}[\"{prop.Name}\"] is {{ }} {varName}) {targetVar}.{prop.Nam
 1986        }
 11987        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 1988        {
 11989            builder.AppendLine($"            if ({sectionVar}[\"{prop.Name}\"] is {{ }} {varName} && int.TryParse({varNa
 1990        }
 01991        else if (baseTypeName == "bool" || baseTypeName == "global::System.Boolean")
 1992        {
 01993            builder.AppendLine($"            if ({sectionVar}[\"{prop.Name}\"] is {{ }} {varName} && bool.TryParse({varN
 1994        }
 01995    }
 1996
 1997    private static void GenerateChildPropertyAssignment(StringBuilder builder, OptionsPropertyInfo prop, int index, stri
 1998    {
 01999        var varName = $"cv{index}";
 02000        var baseTypeName = GetBaseTypeName(prop.TypeName);
 2001
 02002        if (baseTypeName == "string" || baseTypeName == "global::System.String")
 2003        {
 02004            builder.AppendLine($"                if ({sectionVar}[\"{prop.Name}\"] is {{ }} {varName}) {targetVar}.{prop
 2005        }
 02006        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 2007        {
 02008            builder.AppendLine($"                if ({sectionVar}[\"{prop.Name}\"] is {{ }} {varName} && int.TryParse({v
 2009        }
 02010    }
 2011
 2012    private static void GeneratePropertyInitializer(StringBuilder builder, OptionsPropertyInfo prop, int index, string c
 2013    {
 152014        builder.AppendLine($"                {prop.Name} = p{index}{comma}");
 152015    }
 2016
 2017    private static void GenerateParameterParseVariable(StringBuilder builder, PositionalRecordParameter param, int index
 2018    {
 132019        var varName = $"p{index}";
 132020        var typeName = param.TypeName;
 132021        var baseTypeName = GetBaseTypeName(typeName);
 2022
 2023        // Check if it's an enum
 2024        // For simplicity, check if it's a known primitive, otherwise assume it could be an enum
 132025        if (baseTypeName == "string" || baseTypeName == "global::System.String")
 2026        {
 52027            builder.AppendLine($"            var {varName} = section[\"{param.Name}\"] ?? \"\";");
 2028        }
 82029        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 2030        {
 42031            builder.AppendLine($"            var {varName} = section[\"{param.Name}\"] is {{ }} v{index} && int.TryParse
 2032        }
 42033        else if (baseTypeName == "bool" || baseTypeName == "global::System.Boolean")
 2034        {
 32035            builder.AppendLine($"            var {varName} = section[\"{param.Name}\"] is {{ }} v{index} && bool.TryPars
 2036        }
 12037        else if (baseTypeName == "double" || baseTypeName == "global::System.Double")
 2038        {
 02039            builder.AppendLine($"            var {varName} = section[\"{param.Name}\"] is {{ }} v{index} && double.TryPa
 2040        }
 2041        else
 2042        {
 2043            // Try enum parsing for other types
 12044            builder.AppendLine($"            var {varName} = section[\"{param.Name}\"] is {{ }} v{index} && global::Syst
 2045        }
 12046    }
 2047
 2048    private static void RegisterValidator(StringBuilder builder, DiscoveredOptions opt, string safeAssemblyName, HashSet
 2049    {
 752050        var typeName = opt.TypeName;
 752051        var shortTypeName = GeneratorHelpers.GetShortTypeName(typeName);
 2052
 2053        // Register DataAnnotations validator if present
 752054        if (opt.HasDataAnnotations)
 2055        {
 172056            var dataAnnotationsValidatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}DataAnnotation
 172057            builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOptions<{ty
 2058        }
 2059
 752060        if (opt.ValidateOnStart && opt.HasValidatorMethod)
 2061        {
 42062            var validatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}Validator";
 42063            builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOptions<{ty
 2064
 42065            if (opt.HasExternalValidator && opt.ValidatorMethod != null && !opt.ValidatorMethod.Value.IsStatic)
 2066            {
 12067                externalValidatorsToRegister.Add(opt.ValidatorTypeName!);
 2068            }
 2069        }
 752070    }
 2071
 2072    private static void RegisterValidatorForFactory(StringBuilder builder, DiscoveredOptions opt, string safeAssemblyNam
 2073    {
 12074        var typeName = opt.TypeName;
 12075        var shortTypeName = GeneratorHelpers.GetShortTypeName(typeName);
 2076
 2077        // For factory pattern, we need to add OptionsBuilder validation manually
 2078        // Since we're using AddSingleton<IOptions<T>>, we also need to register for IOptionsSnapshot and IOptionsMonito
 12079        builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IOptionsSnapshot<{typeNa
 2080
 2081        // Add startup validation
 12082        builder.AppendLine($"        services.AddOptions<{typeName}>().ValidateOnStart();");
 2083
 2084        // Register DataAnnotations validator if present
 12085        if (opt.HasDataAnnotations)
 2086        {
 02087            var dataAnnotationsValidatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}DataAnnotation
 02088            builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOptions<{ty
 2089        }
 2090
 12091        if (opt.HasValidatorMethod)
 2092        {
 12093            var validatorClassName = $"global::{safeAssemblyName}.Generated.{shortTypeName}Validator";
 12094            builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Options.IValidateOptions<{ty
 2095
 12096            if (opt.HasExternalValidator && opt.ValidatorMethod != null && !opt.ValidatorMethod.Value.IsStatic)
 2097            {
 02098                externalValidatorsToRegister.Add(opt.ValidatorTypeName!);
 2099            }
 2100        }
 12101    }
 2102
 2103    private static void GeneratePropertyBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string targe
 2104    {
 2105        // Handle complex types first
 1202106        if (prop.ComplexTypeKind != ComplexTypeKind.None)
 2107        {
 182108            GenerateComplexTypeBinding(builder, prop, index, targetPath);
 182109            return;
 2110        }
 2111
 1022112        var varName = $"v{index}";
 2113
 2114        // Determine how to parse the value based on type
 1022115        var typeName = prop.TypeName;
 1022116        var baseTypeName = GetBaseTypeName(typeName);
 2117
 1022118        builder.Append($"                if (section[\"{prop.Name}\"] is {{ }} {varName}");
 2119
 2120        // Check if it's an enum first
 1022121        if (prop.IsEnum && prop.EnumTypeName != null)
 2122        {
 122123            builder.AppendLine($" && global::System.Enum.TryParse<{prop.EnumTypeName}>({varName}, true, out var p{index}
 2124        }
 902125        else if (baseTypeName == "string" || baseTypeName == "global::System.String")
 2126        {
 2127            // String: direct assignment
 502128            builder.AppendLine($") {targetPath}.{prop.Name} = {varName};");
 2129        }
 402130        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 2131        {
 232132            builder.AppendLine($" && int.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name} = p{index};");
 2133        }
 172134        else if (baseTypeName == "bool" || baseTypeName == "global::System.Boolean")
 2135        {
 52136            builder.AppendLine($" && bool.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name} = p{index};");
 2137        }
 122138        else if (baseTypeName == "double" || baseTypeName == "global::System.Double")
 2139        {
 52140            builder.AppendLine($" && double.TryParse({varName}, System.Globalization.NumberStyles.Any, System.Globalizat
 2141        }
 72142        else if (baseTypeName == "float" || baseTypeName == "global::System.Single")
 2143        {
 02144            builder.AppendLine($" && float.TryParse({varName}, System.Globalization.NumberStyles.Any, System.Globalizati
 2145        }
 72146        else if (baseTypeName == "decimal" || baseTypeName == "global::System.Decimal")
 2147        {
 02148            builder.AppendLine($" && decimal.TryParse({varName}, System.Globalization.NumberStyles.Any, System.Globaliza
 2149        }
 72150        else if (baseTypeName == "long" || baseTypeName == "global::System.Int64")
 2151        {
 02152            builder.AppendLine($" && long.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name} = p{index};");
 2153        }
 72154        else if (baseTypeName == "short" || baseTypeName == "global::System.Int16")
 2155        {
 02156            builder.AppendLine($" && short.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name} = p{index};")
 2157        }
 72158        else if (baseTypeName == "byte" || baseTypeName == "global::System.Byte")
 2159        {
 02160            builder.AppendLine($" && byte.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name} = p{index};");
 2161        }
 72162        else if (baseTypeName == "char" || baseTypeName == "global::System.Char")
 2163        {
 02164            builder.AppendLine($" && {varName}.Length == 1) {targetPath}.{prop.Name} = {varName}[0];");
 2165        }
 72166        else if (baseTypeName == "global::System.TimeSpan")
 2167        {
 02168            builder.AppendLine($" && global::System.TimeSpan.TryParse({varName}, out var p{index})) {targetPath}.{prop.N
 2169        }
 72170        else if (baseTypeName == "global::System.DateTime")
 2171        {
 02172            builder.AppendLine($" && global::System.DateTime.TryParse({varName}, out var p{index})) {targetPath}.{prop.N
 2173        }
 72174        else if (baseTypeName == "global::System.DateTimeOffset")
 2175        {
 02176            builder.AppendLine($" && global::System.DateTimeOffset.TryParse({varName}, out var p{index})) {targetPath}.{
 2177        }
 72178        else if (baseTypeName == "global::System.Guid")
 2179        {
 02180            builder.AppendLine($" && global::System.Guid.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name}
 2181        }
 72182        else if (baseTypeName == "global::System.Uri")
 2183        {
 02184            builder.AppendLine($" && global::System.Uri.TryCreate({varName}, global::System.UriKind.RelativeOrAbsolute, 
 2185        }
 2186        else
 2187        {
 2188            // Unsupported type - skip silently (matching ConfigurationBinder behavior)
 72189            builder.AppendLine($") {{ }} // Skipped: {typeName} (not a supported primitive)");
 2190        }
 72191    }
 2192
 2193    private static void GenerateComplexTypeBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string ta
 2194    {
 182195        var sectionVar = $"sec{index}";
 2196
 182197        switch (prop.ComplexTypeKind)
 2198        {
 2199            case ComplexTypeKind.NestedObject:
 62200                GenerateNestedObjectBinding(builder, prop, index, targetPath, sectionVar);
 62201                break;
 2202
 2203            case ComplexTypeKind.Array:
 32204                GenerateArrayBinding(builder, prop, index, targetPath, sectionVar);
 32205                break;
 2206
 2207            case ComplexTypeKind.List:
 52208                GenerateListBinding(builder, prop, index, targetPath, sectionVar);
 52209                break;
 2210
 2211            case ComplexTypeKind.Dictionary:
 42212                GenerateDictionaryBinding(builder, prop, index, targetPath, sectionVar);
 2213                break;
 2214        }
 42215    }
 2216
 2217    private static void GenerateNestedObjectBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string t
 2218    {
 62219        var nestedPath = $"{targetPath}.{prop.Name}";
 2220
 62221        builder.AppendLine($"                // Bind nested object: {prop.Name}");
 62222        builder.AppendLine($"                var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 2223
 2224        // Initialize if null (for nullable properties)
 62225        if (prop.IsNullable)
 2226        {
 12227            builder.AppendLine($"                {nestedPath} ??= new {GetNonNullableTypeName(prop.TypeName)}();");
 2228        }
 2229
 2230        // Generate bindings for nested properties
 62231        if (prop.NestedProperties != null)
 2232        {
 62233            var nestedIndex = index * 100; // Use offset to avoid variable name collisions
 302234            foreach (var nestedProp in prop.NestedProperties)
 2235            {
 2236                // Temporarily swap section context for nested binding
 92237                GenerateNestedPropertyBinding(builder, nestedProp, nestedIndex, nestedPath, sectionVar);
 92238                nestedIndex++;
 2239            }
 2240        }
 62241    }
 2242
 2243    private static void GenerateNestedPropertyBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string
 2244    {
 2245        // Handle complex types recursively
 102246        if (prop.ComplexTypeKind != ComplexTypeKind.None)
 2247        {
 22248            var innerSectionVar = $"sec{index}";
 22249            switch (prop.ComplexTypeKind)
 2250            {
 2251                case ComplexTypeKind.NestedObject:
 12252                    builder.AppendLine($"                // Bind nested object: {prop.Name}");
 12253                    builder.AppendLine($"                var {innerSectionVar} = {sectionVarName}.GetSection(\"{prop.Nam
 12254                    if (prop.IsNullable)
 2255                    {
 02256                        builder.AppendLine($"                {targetPath}.{prop.Name} ??= new {GetNonNullableTypeName(pr
 2257                    }
 12258                    if (prop.NestedProperties != null)
 2259                    {
 12260                        var nestedIndex = index * 100;
 42261                        foreach (var nestedProp in prop.NestedProperties)
 2262                        {
 12263                            GenerateNestedPropertyBinding(builder, nestedProp, nestedIndex, $"{targetPath}.{prop.Name}",
 12264                            nestedIndex++;
 2265                        }
 2266                    }
 2267                    break;
 2268
 2269                case ComplexTypeKind.Array:
 2270                case ComplexTypeKind.List:
 2271                case ComplexTypeKind.Dictionary:
 2272                    // For collections inside nested objects, generate appropriate binding
 12273                    GenerateCollectionBindingInNested(builder, prop, index, targetPath, sectionVarName);
 2274                    break;
 2275            }
 22276            return;
 2277        }
 2278
 2279        // Generate primitive binding using the nested section
 82280        var varName = $"v{index}";
 82281        var baseTypeName = GetBaseTypeName(prop.TypeName);
 2282
 82283        builder.Append($"                if ({sectionVarName}[\"{prop.Name}\"] is {{ }} {varName}");
 2284
 82285        if (prop.IsEnum && prop.EnumTypeName != null)
 2286        {
 02287            builder.AppendLine($" && global::System.Enum.TryParse<{prop.EnumTypeName}>({varName}, true, out var p{index}
 2288        }
 82289        else if (baseTypeName == "string" || baseTypeName == "global::System.String")
 2290        {
 62291            builder.AppendLine($") {targetPath}.{prop.Name} = {varName};");
 2292        }
 22293        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 2294        {
 22295            builder.AppendLine($" && int.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name} = p{index};");
 2296        }
 02297        else if (baseTypeName == "bool" || baseTypeName == "global::System.Boolean")
 2298        {
 02299            builder.AppendLine($" && bool.TryParse({varName}, out var p{index})) {targetPath}.{prop.Name} = p{index};");
 2300        }
 2301        else
 2302        {
 2303            // For other types, generate appropriate TryParse
 02304            builder.AppendLine($") {{ }} // Skipped: {prop.TypeName}");
 2305        }
 02306    }
 2307
 2308    private static void GenerateCollectionBindingInNested(StringBuilder builder, OptionsPropertyInfo prop, int index, st
 2309    {
 12310        var collectionSection = $"colSec{index}";
 12311        builder.AppendLine($"                var {collectionSection} = {sectionVarName}.GetSection(\"{prop.Name}\");");
 2312
 12313        switch (prop.ComplexTypeKind)
 2314        {
 2315            case ComplexTypeKind.List:
 12316                GenerateListBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", collectionSection);
 12317                break;
 2318            case ComplexTypeKind.Array:
 02319                GenerateArrayBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", collectionSection);
 02320                break;
 2321            case ComplexTypeKind.Dictionary:
 02322                GenerateDictionaryBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", collectionSection);
 2323                break;
 2324        }
 02325    }
 2326
 2327    private static void GenerateArrayBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string targetPa
 2328    {
 32329        builder.AppendLine($"                // Bind array: {prop.Name}");
 32330        builder.AppendLine($"                var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 32331        GenerateArrayBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", sectionVar);
 32332    }
 2333
 2334    private static void GenerateArrayBindingCore(StringBuilder builder, OptionsPropertyInfo prop, int index, string targ
 2335    {
 32336        var itemsVar = $"items{index}";
 32337        var elementType = prop.ElementTypeName ?? "string";
 32338        var hasNestedProps = prop.NestedProperties != null && prop.NestedProperties.Count > 0;
 2339
 32340        builder.AppendLine($"                var {itemsVar} = new global::System.Collections.Generic.List<{elementType}>
 32341        builder.AppendLine($"                foreach (var child in {sectionVar}.GetChildren())");
 32342        builder.AppendLine("                {");
 2343
 32344        if (hasNestedProps)
 2345        {
 2346            // Complex element type
 12347            var itemVar = $"item{index}";
 12348            builder.AppendLine($"                    var {itemVar} = new {elementType}();");
 12349            var nestedIndex = index * 100;
 62350            foreach (var nestedProp in prop.NestedProperties!)
 2351            {
 22352                GenerateChildPropertyBinding(builder, nestedProp, nestedIndex, itemVar, "child");
 22353                nestedIndex++;
 2354            }
 12355            builder.AppendLine($"                    {itemsVar}.Add({itemVar});");
 2356        }
 2357        else
 2358        {
 2359            // Primitive element type
 22360            GeneratePrimitiveCollectionAdd(builder, elementType, index, itemsVar);
 2361        }
 2362
 32363        builder.AppendLine("                }");
 32364        builder.AppendLine($"                {targetPath} = {itemsVar}.ToArray();");
 32365    }
 2366
 2367    private static void GenerateListBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string targetPat
 2368    {
 52369        builder.AppendLine($"                // Bind list: {prop.Name}");
 52370        builder.AppendLine($"                var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 52371        GenerateListBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", sectionVar);
 52372    }
 2373
 2374    private static void GenerateListBindingCore(StringBuilder builder, OptionsPropertyInfo prop, int index, string targe
 2375    {
 62376        var elementType = prop.ElementTypeName ?? "string";
 62377        var hasNestedProps = prop.NestedProperties != null && prop.NestedProperties.Count > 0;
 2378
 62379        builder.AppendLine($"                {targetPath}.Clear();");
 62380        builder.AppendLine($"                foreach (var child in {sectionVar}.GetChildren())");
 62381        builder.AppendLine("                {");
 2382
 62383        if (hasNestedProps)
 2384        {
 2385            // Complex element type
 12386            var itemVar = $"item{index}";
 12387            builder.AppendLine($"                    var {itemVar} = new {elementType}();");
 12388            var nestedIndex = index * 100;
 62389            foreach (var nestedProp in prop.NestedProperties!)
 2390            {
 22391                GenerateChildPropertyBinding(builder, nestedProp, nestedIndex, itemVar, "child");
 22392                nestedIndex++;
 2393            }
 12394            builder.AppendLine($"                    {targetPath}.Add({itemVar});");
 2395        }
 2396        else
 2397        {
 2398            // Primitive element type
 52399            GeneratePrimitiveListAdd(builder, elementType, index, targetPath);
 2400        }
 2401
 62402        builder.AppendLine("                }");
 62403    }
 2404
 2405    private static void GenerateDictionaryBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string tar
 2406    {
 42407        builder.AppendLine($"                // Bind dictionary: {prop.Name}");
 42408        builder.AppendLine($"                var {sectionVar} = section.GetSection(\"{prop.Name}\");");
 42409        GenerateDictionaryBindingCore(builder, prop, index, $"{targetPath}.{prop.Name}", sectionVar);
 42410    }
 2411
 2412    private static void GenerateDictionaryBindingCore(StringBuilder builder, OptionsPropertyInfo prop, int index, string
 2413    {
 42414        var elementType = prop.ElementTypeName ?? "string";
 42415        var hasNestedProps = prop.NestedProperties != null && prop.NestedProperties.Count > 0;
 2416
 42417        builder.AppendLine($"                {targetPath}.Clear();");
 42418        builder.AppendLine($"                foreach (var child in {sectionVar}.GetChildren())");
 42419        builder.AppendLine("                {");
 2420
 42421        if (hasNestedProps)
 2422        {
 2423            // Complex value type
 12424            var itemVar = $"item{index}";
 12425            builder.AppendLine($"                    var {itemVar} = new {elementType}();");
 12426            var nestedIndex = index * 100;
 62427            foreach (var nestedProp in prop.NestedProperties!)
 2428            {
 22429                GenerateChildPropertyBinding(builder, nestedProp, nestedIndex, itemVar, "child");
 22430                nestedIndex++;
 2431            }
 12432            builder.AppendLine($"                    {targetPath}[child.Key] = {itemVar};");
 2433        }
 2434        else
 2435        {
 2436            // Primitive value type
 32437            GeneratePrimitiveDictionaryAdd(builder, elementType, index, targetPath);
 2438        }
 2439
 42440        builder.AppendLine("                }");
 42441    }
 2442
 2443    private static void GenerateChildPropertyBinding(StringBuilder builder, OptionsPropertyInfo prop, int index, string 
 2444    {
 62445        var varName = $"cv{index}";
 62446        var baseTypeName = GetBaseTypeName(prop.TypeName);
 2447
 62448        builder.Append($"                    if ({sectionVar}[\"{prop.Name}\"] is {{ }} {varName}");
 2449
 62450        if (prop.IsEnum && prop.EnumTypeName != null)
 2451        {
 02452            builder.AppendLine($" && global::System.Enum.TryParse<{prop.EnumTypeName}>({varName}, true, out var cp{index
 2453        }
 62454        else if (baseTypeName == "string" || baseTypeName == "global::System.String")
 2455        {
 32456            builder.AppendLine($") {targetVar}.{prop.Name} = {varName};");
 2457        }
 32458        else if (baseTypeName == "int" || baseTypeName == "global::System.Int32")
 2459        {
 32460            builder.AppendLine($" && int.TryParse({varName}, out var cp{index})) {targetVar}.{prop.Name} = cp{index};");
 2461        }
 02462        else if (baseTypeName == "bool" || baseTypeName == "global::System.Boolean")
 2463        {
 02464            builder.AppendLine($" && bool.TryParse({varName}, out var cp{index})) {targetVar}.{prop.Name} = cp{index};")
 2465        }
 2466        else
 2467        {
 02468            builder.AppendLine($") {{ }} // Skipped: {prop.TypeName}");
 2469        }
 02470    }
 2471
 2472    private static void GeneratePrimitiveCollectionAdd(StringBuilder builder, string elementType, int index, string list
 2473    {
 22474        var baseType = GetBaseTypeName(elementType);
 2475
 22476        if (baseType == "string" || baseType == "global::System.String")
 2477        {
 12478            builder.AppendLine($"                    if (child.Value is {{ }} val{index}) {listVar}.Add(val{index});");
 2479        }
 12480        else if (baseType == "int" || baseType == "global::System.Int32")
 2481        {
 12482            builder.AppendLine($"                    if (child.Value is {{ }} val{index} && int.TryParse(val{index}, out
 2483        }
 2484        else
 2485        {
 02486            builder.AppendLine($"                    // Skipped: unsupported element type {elementType}");
 2487        }
 02488    }
 2489
 2490    private static void GeneratePrimitiveListAdd(StringBuilder builder, string elementType, int index, string targetPath
 2491    {
 52492        var baseType = GetBaseTypeName(elementType);
 2493
 52494        if (baseType == "string" || baseType == "global::System.String")
 2495        {
 42496            builder.AppendLine($"                    if (child.Value is {{ }} val{index}) {targetPath}.Add(val{index});"
 2497        }
 12498        else if (baseType == "int" || baseType == "global::System.Int32")
 2499        {
 12500            builder.AppendLine($"                    if (child.Value is {{ }} val{index} && int.TryParse(val{index}, out
 2501        }
 2502        else
 2503        {
 02504            builder.AppendLine($"                    // Skipped: unsupported element type {elementType}");
 2505        }
 02506    }
 2507
 2508    private static void GeneratePrimitiveDictionaryAdd(StringBuilder builder, string elementType, int index, string targ
 2509    {
 32510        var baseType = GetBaseTypeName(elementType);
 2511
 32512        if (baseType == "string" || baseType == "global::System.String")
 2513        {
 12514            builder.AppendLine($"                    if (child.Value is {{ }} val{index}) {targetPath}[child.Key] = val{
 2515        }
 22516        else if (baseType == "int" || baseType == "global::System.Int32")
 2517        {
 22518            builder.AppendLine($"                    if (child.Value is {{ }} val{index} && int.TryParse(val{index}, out
 2519        }
 2520        else
 2521        {
 02522            builder.AppendLine($"                    // Skipped: unsupported element type {elementType}");
 2523        }
 02524    }
 2525
 2526    private static string GetNonNullableTypeName(string typeName)
 2527    {
 22528        if (typeName.EndsWith("?"))
 02529            return typeName.Substring(0, typeName.Length - 1);
 22530        return typeName;
 2531    }
 2532
 2533    private static string GetBaseTypeName(string typeName)
 2534    {
 2535        // Handle nullable types like "global::System.Nullable<int>" or "int?"
 1562536        if (typeName.StartsWith("global::System.Nullable<") && typeName.EndsWith(">"))
 2537        {
 02538            return typeName.Substring("global::System.Nullable<".Length, typeName.Length - "global::System.Nullable<".Le
 2539        }
 1562540        if (typeName.EndsWith("?"))
 2541        {
 42542            return typeName.Substring(0, typeName.Length - 1);
 2543        }
 1522544        return typeName;
 2545    }
 2546
 2547    private static void GenerateApplyDecoratorsMethod(StringBuilder builder, IReadOnlyList<DiscoveredDecorator> decorato
 2548    {
 4382549        builder.AppendLine("    /// <summary>");
 4382550        builder.AppendLine("    /// Applies all discovered decorators, interceptors, and hosted services to the service 
 4382551        builder.AppendLine("    /// Decorators are applied in order, with lower Order values applied first (closer to th
 4382552        builder.AppendLine("    /// </summary>");
 4382553        builder.AppendLine("    /// <param name=\"services\">The service collection to apply decorators to.</param>");
 4382554        builder.AppendLine("    public static void ApplyDecorators(IServiceCollection services)");
 4382555        builder.AppendLine("    {");
 2556
 2557        // Register ServiceCatalog first
 4382558        breadcrumbs.WriteInlineComment(builder, "        ", "Register service catalog for DI resolution");
 4382559        builder.AppendLine($"        services.AddSingleton<global::NexusLabs.Needlr.Catalog.IServiceCatalog, global::{sa
 4382560        builder.AppendLine();
 2561
 2562        // Register hosted services first (before decorators apply)
 4382563        if (hasHostedServices)
 2564        {
 62565            breadcrumbs.WriteInlineComment(builder, "        ", "Register hosted services");
 62566            builder.AppendLine("        RegisterHostedServices(services);");
 62567            if (decorators.Count > 0 || hasInterceptors)
 2568            {
 02569                builder.AppendLine();
 2570            }
 2571        }
 2572
 4382573        if (decorators.Count == 0 && !hasInterceptors)
 2574        {
 4082575            if (!hasHostedServices)
 2576            {
 4022577                breadcrumbs.WriteInlineComment(builder, "        ", "No decorators, interceptors, or hosted services dis
 2578            }
 2579        }
 2580        else
 2581        {
 302582            if (decorators.Count > 0)
 2583            {
 2584                // Group decorators by service type and order by Order property
 172585                var decoratorsByService = decorators
 262586                    .GroupBy(d => d.ServiceTypeName)
 372587                    .OrderBy(g => g.Key);
 2588
 742589                foreach (var serviceGroup in decoratorsByService)
 2590                {
 2591                    // Write verbose breadcrumb for decorator chain
 202592                    if (breadcrumbs.Level == BreadcrumbLevel.Verbose)
 2593                    {
 02594                        var chainItems = serviceGroup.OrderBy(d => d.Order).ToList();
 02595                        var lines = new List<string>
 02596                        {
 02597                            "Resolution order (outer → inner → target):"
 02598                        };
 02599                        for (int i = 0; i < chainItems.Count; i++)
 2600                        {
 02601                            var dec = chainItems[i];
 02602                            var sourcePath = dec.SourceFilePath != null
 02603                                ? BreadcrumbWriter.GetRelativeSourcePath(dec.SourceFilePath, projectDirectory)
 02604                                : $"[{dec.AssemblyName}]";
 02605                            lines.Add($"  {i + 1}. {dec.DecoratorTypeName.Split('.').Last()} (Order={dec.Order}) ← {sour
 2606                        }
 02607                        lines.Add($"Triggered by: [DecoratorFor<{serviceGroup.Key.Split('.').Last()}>] attributes");
 2608
 02609                        breadcrumbs.WriteVerboseBox(builder, "        ",
 02610                            $"Decorator Chain: {serviceGroup.Key.Split('.').Last()}",
 02611                            lines.ToArray());
 2612                    }
 2613                    else
 2614                    {
 202615                        breadcrumbs.WriteInlineComment(builder, "        ", $"Decorators for {serviceGroup.Key}");
 2616                    }
 2617
 1182618                    foreach (var decorator in serviceGroup.OrderBy(d => d.Order))
 2619                    {
 262620                        builder.AppendLine($"        services.AddDecorator<{decorator.ServiceTypeName}, {decorator.Decor
 2621                    }
 2622                }
 2623            }
 2624
 302625            if (hasInterceptors)
 2626            {
 132627                builder.AppendLine();
 132628                breadcrumbs.WriteInlineComment(builder, "        ", "Register intercepted services with their proxies");
 132629                builder.AppendLine($"        global::{safeAssemblyName}.Generated.InterceptorRegistrations.RegisterInter
 2630            }
 2631        }
 2632
 4382633        builder.AppendLine("    }");
 4382634    }
 2635
 2636    private static void GenerateRegisterProvidersMethod(StringBuilder builder, IReadOnlyList<DiscoveredProvider> provide
 2637    {
 172638        builder.AppendLine("    /// <summary>");
 172639        builder.AppendLine("    /// Registers all generated providers as Singletons.");
 172640        builder.AppendLine("    /// Providers are strongly-typed service locators that expose services via typed propert
 172641        builder.AppendLine("    /// </summary>");
 172642        builder.AppendLine("    /// <param name=\"services\">The service collection to register to.</param>");
 172643        builder.AppendLine("    public static void RegisterProviders(IServiceCollection services)");
 172644        builder.AppendLine("    {");
 2645
 702646        foreach (var provider in providers)
 2647        {
 182648            var shortName = provider.SimpleTypeName;
 182649            var sourcePath = provider.SourceFilePath != null
 182650                ? BreadcrumbWriter.GetRelativeSourcePath(provider.SourceFilePath, projectDirectory)
 182651                : $"[{provider.AssemblyName}]";
 2652
 182653            breadcrumbs.WriteInlineComment(builder, "        ", $"Provider: {shortName} ← {sourcePath}");
 2654
 182655            if (provider.IsInterface)
 2656            {
 2657                // Interface mode: register the generated implementation
 122658                var implName = provider.ImplementationTypeName;
 122659                builder.AppendLine($"        services.AddSingleton<{provider.TypeName}, global::{safeAssemblyName}.Gener
 2660            }
 62661            else if (provider.IsPartial)
 2662            {
 2663                // Shorthand class mode: register the partial class as its generated interface
 62664                var interfaceName = provider.InterfaceTypeName;
 62665                var providerNamespace = GetNamespaceFromTypeName(provider.TypeName);
 62666                builder.AppendLine($"        services.AddSingleton<global::{providerNamespace}.{interfaceName}, {provide
 2667            }
 2668        }
 2669
 172670        builder.AppendLine("    }");
 172671    }
 2672
 2673    private static void GenerateRegisterHostedServicesMethod(StringBuilder builder, IReadOnlyList<DiscoveredHostedServic
 2674    {
 62675        builder.AppendLine("    /// <summary>");
 62676        builder.AppendLine("    /// Registers all discovered hosted services (BackgroundService and IHostedService imple
 62677        builder.AppendLine("    /// Each service is registered as singleton and also as IHostedService for the host to d
 62678        builder.AppendLine("    /// </summary>");
 62679        builder.AppendLine("    /// <param name=\"services\">The service collection to register to.</param>");
 62680        builder.AppendLine("    private static void RegisterHostedServices(IServiceCollection services)");
 62681        builder.AppendLine("    {");
 2682
 262683        foreach (var hostedService in hostedServices)
 2684        {
 72685            var typeName = hostedService.TypeName;
 72686            var shortName = typeName.Split('.').Last();
 72687            var sourcePath = hostedService.SourceFilePath != null
 72688                ? BreadcrumbWriter.GetRelativeSourcePath(hostedService.SourceFilePath, projectDirectory)
 72689                : $"[{hostedService.AssemblyName}]";
 2690
 72691            breadcrumbs.WriteInlineComment(builder, "        ", $"Hosted service: {shortName} ← {sourcePath}");
 2692
 2693            // Register the concrete type as singleton
 72694            builder.AppendLine($"        services.AddSingleton<{typeName}>();");
 2695
 2696            // Register as IHostedService that forwards to the concrete type
 72697            builder.AppendLine($"        services.AddSingleton<global::Microsoft.Extensions.Hosting.IHostedService>(sp =
 2698        }
 2699
 62700        builder.AppendLine("    }");
 62701    }
 2702
 2703
 2704
 2705    private static string GenerateInterceptorProxiesSource(IReadOnlyList<DiscoveredInterceptedService> interceptedServic
 2706    {
 132707        var builder = new StringBuilder();
 132708        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblyName);
 2709
 132710        breadcrumbs.WriteFileHeader(builder, assemblyName, "Needlr Interceptor Proxies");
 132711        builder.AppendLine("#nullable enable");
 132712        builder.AppendLine();
 132713        builder.AppendLine("using System;");
 132714        builder.AppendLine("using System.Reflection;");
 132715        builder.AppendLine("using System.Threading.Tasks;");
 132716        builder.AppendLine();
 132717        builder.AppendLine("using Microsoft.Extensions.DependencyInjection;");
 132718        builder.AppendLine();
 132719        builder.AppendLine("using NexusLabs.Needlr;");
 132720        builder.AppendLine();
 132721        builder.AppendLine($"namespace {safeAssemblyName}.Generated;");
 132722        builder.AppendLine();
 2723
 2724        // Generate each proxy class
 522725        foreach (var service in interceptedServices)
 2726        {
 132727            CodeGen.InterceptorCodeGenerator.GenerateInterceptorProxyClass(builder, service, breadcrumbs, projectDirecto
 132728            builder.AppendLine();
 2729        }
 2730
 2731        // Generate the registration helper
 132732        builder.AppendLine("/// <summary>");
 132733        builder.AppendLine("/// Helper class for registering intercepted services.");
 132734        builder.AppendLine("/// </summary>");
 132735        builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\", \"1
 132736        builder.AppendLine("public static class InterceptorRegistrations");
 132737        builder.AppendLine("{");
 132738        builder.AppendLine("    /// <summary>");
 132739        builder.AppendLine("    /// Registers all intercepted services and their proxies.");
 132740        builder.AppendLine("    /// </summary>");
 132741        builder.AppendLine("    /// <param name=\"services\">The service collection to register to.</param>");
 132742        builder.AppendLine("    public static void RegisterInterceptedServices(IServiceCollection services)");
 132743        builder.AppendLine("    {");
 2744
 522745        foreach (var service in interceptedServices)
 2746        {
 132747            var proxyTypeName = GeneratorHelpers.GetProxyTypeName(service.TypeName);
 132748            var lifetime = service.Lifetime switch
 132749            {
 22750                GeneratorLifetime.Singleton => "Singleton",
 112751                GeneratorLifetime.Scoped => "Scoped",
 02752                GeneratorLifetime.Transient => "Transient",
 02753                _ => "Scoped"
 132754            };
 2755
 2756            // Register all interceptor types
 582757            foreach (var interceptorType in service.AllInterceptorTypeNames)
 2758            {
 162759                breadcrumbs.WriteInlineComment(builder, "        ", $"Register interceptor: {interceptorType.Split('.').
 162760                builder.AppendLine($"        if (!services.Any(d => d.ServiceType == typeof({interceptorType})))");
 162761                builder.AppendLine($"            services.Add{lifetime}<{interceptorType}>();");
 2762            }
 2763
 2764            // Register the actual implementation type
 132765            builder.AppendLine($"        // Register actual implementation");
 132766            builder.AppendLine($"        services.Add{lifetime}<{service.TypeName}>();");
 2767
 2768            // Register proxy for each interface
 522769            foreach (var iface in service.InterfaceNames)
 2770            {
 132771                builder.AppendLine($"        // Register proxy for {iface}");
 132772                builder.AppendLine($"        services.Add{lifetime}<{iface}>(sp => new {proxyTypeName}(");
 132773                builder.AppendLine($"            sp.GetRequiredService<{service.TypeName}>(),");
 132774                builder.AppendLine($"            sp));");
 2775            }
 2776        }
 2777
 132778        builder.AppendLine("    }");
 132779        builder.AppendLine();
 132780        builder.AppendLine("    /// <summary>");
 132781        builder.AppendLine("    /// Gets the number of intercepted services discovered at compile time.");
 132782        builder.AppendLine("    /// </summary>");
 132783        builder.AppendLine($"    public static int Count => {interceptedServices.Count};");
 132784        builder.AppendLine("}");
 2785
 132786        return builder.ToString();
 2787    }
 2788
 2789    private static string GenerateFactoriesSource(IReadOnlyList<DiscoveredFactory> factories, string assemblyName, Bread
 2790    {
 232791        var builder = new StringBuilder();
 232792        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblyName);
 2793
 232794        breadcrumbs.WriteFileHeader(builder, assemblyName, "Needlr Generated Factories");
 232795        builder.AppendLine("#nullable enable");
 232796        builder.AppendLine();
 232797        builder.AppendLine("using System;");
 232798        builder.AppendLine();
 232799        builder.AppendLine("using Microsoft.Extensions.DependencyInjection;");
 232800        builder.AppendLine();
 232801        builder.AppendLine($"namespace {safeAssemblyName}.Generated;");
 232802        builder.AppendLine();
 2803
 2804        // Generate factory interfaces and implementations for each type
 922805        foreach (var factory in factories)
 2806        {
 232807            if (factory.GenerateInterface)
 2808            {
 222809                CodeGen.FactoryCodeGenerator.GenerateFactoryInterface(builder, factory, breadcrumbs, projectDirectory);
 222810                builder.AppendLine();
 222811                CodeGen.FactoryCodeGenerator.GenerateFactoryImplementation(builder, factory, breadcrumbs, projectDirecto
 222812                builder.AppendLine();
 2813            }
 2814        }
 2815
 2816        // Generate the registration helper
 232817        builder.AppendLine("/// <summary>");
 232818        builder.AppendLine("/// Helper class for registering factory types.");
 232819        builder.AppendLine("/// </summary>");
 232820        builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\", \"1
 232821        builder.AppendLine("public static class FactoryRegistrations");
 232822        builder.AppendLine("{");
 232823        builder.AppendLine("    /// <summary>");
 232824        builder.AppendLine("    /// Registers all generated factories.");
 232825        builder.AppendLine("    /// </summary>");
 232826        builder.AppendLine("    /// <param name=\"services\">The service collection to register to.</param>");
 232827        builder.AppendLine("    public static void RegisterFactories(IServiceCollection services)");
 232828        builder.AppendLine("    {");
 2829
 922830        foreach (var factory in factories)
 2831        {
 232832            breadcrumbs.WriteInlineComment(builder, "        ", $"Factory for {factory.SimpleTypeName}");
 2833
 2834            // Register Func<> for each constructor
 232835            if (factory.GenerateFunc)
 2836            {
 942837                foreach (var ctor in factory.Constructors)
 2838                {
 252839                    CodeGen.FactoryCodeGenerator.GenerateFuncRegistration(builder, factory, ctor, "        ");
 2840                }
 2841            }
 2842
 2843            // Register interface factory
 232844            if (factory.GenerateInterface)
 2845            {
 222846                var factoryInterfaceName = $"I{factory.SimpleTypeName}Factory";
 222847                var factoryImplName = $"{factory.SimpleTypeName}Factory";
 222848                builder.AppendLine($"        services.AddSingleton<global::{safeAssemblyName}.Generated.{factoryInterfac
 2849            }
 2850        }
 2851
 232852        builder.AppendLine("    }");
 232853        builder.AppendLine();
 232854        builder.AppendLine("    /// <summary>");
 232855        builder.AppendLine("    /// Gets the number of factory types generated at compile time.");
 232856        builder.AppendLine("    /// </summary>");
 232857        builder.AppendLine($"    public static int Count => {factories.Count};");
 232858        builder.AppendLine("}");
 2859
 232860        return builder.ToString();
 2861    }
 2862
 2863    private static string GenerateProvidersSource(IReadOnlyList<DiscoveredProvider> providers, string assemblyName, Brea
 2864    {
 112865        var builder = new StringBuilder();
 112866        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblyName);
 2867
 112868        breadcrumbs.WriteFileHeader(builder, assemblyName, "Needlr Generated Providers");
 112869        builder.AppendLine("#nullable enable");
 112870        builder.AppendLine();
 112871        builder.AppendLine("using System;");
 112872        builder.AppendLine();
 112873        builder.AppendLine("using Microsoft.Extensions.DependencyInjection;");
 112874        builder.AppendLine();
 112875        builder.AppendLine($"namespace {safeAssemblyName}.Generated;");
 112876        builder.AppendLine();
 2877
 2878        // Generate provider implementations (interface-based only)
 462879        foreach (var provider in providers)
 2880        {
 122881            CodeGen.ProviderCodeGenerator.GenerateProviderImplementation(builder, provider, $"{safeAssemblyName}.Generat
 122882            builder.AppendLine();
 2883        }
 2884
 112885        return builder.ToString();
 2886    }
 2887
 2888    private static string GenerateShorthandProviderSource(DiscoveredProvider provider, string assemblyName, BreadcrumbWr
 2889    {
 62890        var builder = new StringBuilder();
 62891        var providerNamespace = GetNamespaceFromTypeName(provider.TypeName);
 2892
 62893        breadcrumbs.WriteFileHeader(builder, assemblyName, $"Needlr Generated Provider: {provider.SimpleTypeName}");
 62894        builder.AppendLine("#nullable enable");
 62895        builder.AppendLine();
 62896        builder.AppendLine("using System;");
 62897        builder.AppendLine();
 62898        builder.AppendLine($"namespace {providerNamespace};");
 62899        builder.AppendLine();
 2900
 62901        CodeGen.ProviderCodeGenerator.GenerateProviderInterfaceAndPartialClass(builder, provider, providerNamespace, bre
 2902
 62903        return builder.ToString();
 2904    }
 2905
 2906    private static string GetNamespaceFromTypeName(string fullyQualifiedName)
 2907    {
 122908        var name = fullyQualifiedName;
 122909        if (name.StartsWith("global::"))
 2910        {
 122911            name = name.Substring(8);
 2912        }
 2913
 122914        var lastDot = name.LastIndexOf('.');
 122915        return lastDot >= 0 ? name.Substring(0, lastDot) : string.Empty;
 2916    }
 2917
 2918
 2919
 2920
 2921
 2922    /// <summary>
 2923    /// Filters out nested options types.
 2924    /// A nested options type is one that is used as a property type in another options type.
 2925    /// These should not be registered separately - they are bound as part of their parent.
 2926    /// </summary>
 2927    private static List<DiscoveredOptions> FilterNestedOptions(List<DiscoveredOptions> options, Compilation compilation)
 2928    {
 2929        // Build a set of all options type names
 632930        var optionsTypeNames = new HashSet<string>(options.Select(o => o.TypeName));
 2931
 2932        // Find all options types that are used as properties in other options types
 192933        var nestedTypeNames = new HashSet<string>();
 2934
 1262935        foreach (var opt in options)
 2936        {
 2937            // Find the type symbol for this options type
 442938            var typeSymbol = FindTypeSymbol(compilation, opt.TypeName);
 442939            if (typeSymbol == null)
 2940                continue;
 2941
 2942            // Check all properties of this type
 6402943            foreach (var member in typeSymbol.GetMembers())
 2944            {
 2762945                if (member is not IPropertySymbol property)
 2946                    continue;
 2947
 2948                // Skip non-class property types (primitives, structs, etc.)
 562949                if (property.Type is not INamedTypeSymbol propertyType)
 2950                    continue;
 2951
 562952                if (propertyType.TypeKind != TypeKind.Class)
 2953                    continue;
 2954
 2955                // Get the fully qualified name of the property type
 422956                var propertyTypeName = TypeDiscoveryHelper.GetFullyQualifiedName(propertyType);
 2957
 2958                // If this property type is also an [Options] type, mark it as nested
 422959                if (optionsTypeNames.Contains(propertyTypeName))
 2960                {
 92961                    nestedTypeNames.Add(propertyTypeName);
 2962                }
 2963            }
 2964        }
 2965
 2966        // Return only root options (those not used as properties in other options)
 632967        return options.Where(o => !nestedTypeNames.Contains(o.TypeName)).ToList();
 2968    }
 2969
 2970    /// <summary>
 2971    /// Finds a type symbol by its fully qualified name.
 2972    /// </summary>
 2973    private static INamedTypeSymbol? FindTypeSymbol(Compilation compilation, string fullyQualifiedName)
 2974    {
 2975        // Strip global:: prefix if present
 442976        var typeName = fullyQualifiedName.StartsWith("global::")
 442977            ? fullyQualifiedName.Substring(8)
 442978            : fullyQualifiedName;
 2979
 442980        return compilation.GetTypeByMetadataName(typeName);
 2981    }
 2982
 2983    /// <summary>
 2984    /// Expands open generic decorators into concrete decorator registrations
 2985    /// for each discovered closed implementation of the open generic interface.
 2986    /// </summary>
 2987    private static void ExpandOpenDecorators(
 2988        IReadOnlyList<DiscoveredType> injectableTypes,
 2989        IReadOnlyList<DiscoveredOpenDecorator> openDecorators,
 2990        List<DiscoveredDecorator> decorators)
 2991    {
 2992        // Group injectable types by the open generic interfaces they implement
 62993        var interfaceImplementations = new Dictionary<INamedTypeSymbol, List<(INamedTypeSymbol closedInterface, Discover
 2994
 402995        foreach (var discoveredType in injectableTypes)
 2996        {
 2997            // We need to check each interface this type implements to see if it's a closed version of an open generic
 602998            foreach (var openDecorator in openDecorators)
 2999            {
 3000                // Check if this type implements the open generic interface
 463001                foreach (var interfaceName in discoveredType.InterfaceNames)
 3002                {
 3003                    // This is string-based matching - we need to match the interface name pattern
 3004                    // For example, if open generic is IHandler<> and the interface is IHandler<Order>, we should match
 73005                    var openGenericName = TypeDiscoveryHelper.GetFullyQualifiedName(openDecorator.OpenGenericInterface);
 3006
 3007                    // Extract the base name (before the <>)
 73008                    var openGenericBaseName = GeneratorHelpers.GetGenericBaseName(openGenericName);
 73009                    var interfaceBaseName = GeneratorHelpers.GetGenericBaseName(interfaceName);
 3010
 73011                    if (openGenericBaseName == interfaceBaseName)
 3012                    {
 3013                        // This interface is a closed version of the open generic
 3014                        // Create a closed decorator registration
 73015                        var closedDecoratorTypeName = GeneratorHelpers.CreateClosedGenericType(
 73016                            TypeDiscoveryHelper.GetFullyQualifiedName(openDecorator.DecoratorType),
 73017                            interfaceName,
 73018                            openGenericName);
 3019
 73020                        decorators.Add(new DiscoveredDecorator(
 73021                            closedDecoratorTypeName,
 73022                            interfaceName,
 73023                            openDecorator.Order,
 73024                            openDecorator.AssemblyName,
 73025                            openDecorator.SourceFilePath));
 3026                    }
 3027                }
 3028            }
 3029        }
 63030    }
 3031
 3032
 3033    /// <summary>
 3034    /// Discovers all referenced assemblies that have the [GenerateTypeRegistry] attribute.
 3035    /// These assemblies need to be force-loaded to ensure their module initializers run.
 3036    /// </summary>
 3037    private static IReadOnlyList<string> DiscoverReferencedAssembliesWithTypeRegistry(Compilation compilation)
 3038    {
 4413039        var result = new List<string>();
 3040
 1498443041        foreach (var reference in compilation.References)
 3042        {
 744813043            if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assemblySymbol)
 3044            {
 3045                // Skip the current assembly
 742943046                if (SymbolEqualityComparer.Default.Equals(assemblySymbol, compilation.Assembly))
 3047                    continue;
 3048
 742943049                if (TypeDiscoveryHelper.HasGenerateTypeRegistryAttribute(assemblySymbol))
 3050                {
 183051                    result.Add(assemblySymbol.Name);
 3052                }
 3053            }
 3054        }
 3055
 4413056        return result;
 3057    }
 3058
 3059    /// <summary>
 3060    /// Discovers types from referenced assemblies with [GenerateTypeRegistry] for diagnostics purposes.
 3061    /// Unlike the main discovery, this includes internal types since we're just showing them in diagnostics.
 3062    /// </summary>
 3063    private static Dictionary<string, List<DiagnosticTypeInfo>> DiscoverReferencedAssemblyTypesForDiagnostics(Compilatio
 3064    {
 953065        var result = new Dictionary<string, List<DiagnosticTypeInfo>>();
 3066
 323283067        foreach (var reference in compilation.References)
 3068        {
 160693069            if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assemblySymbol)
 3070            {
 3071                // Skip the current assembly
 160693072                if (SymbolEqualityComparer.Default.Equals(assemblySymbol, compilation.Assembly))
 3073                    continue;
 3074
 160693075                if (!TypeDiscoveryHelper.HasGenerateTypeRegistryAttribute(assemblySymbol))
 3076                    continue;
 3077
 143078                var assemblyTypes = new List<DiagnosticTypeInfo>();
 3079
 3080                // First pass: collect intercepted service names so we can identify their proxies
 143081                var interceptedServiceNames = new HashSet<string>();
 1203082                foreach (var typeSymbol in TypeDiscoveryHelper.GetAllTypes(assemblySymbol.GlobalNamespace))
 3083                {
 463084                    if (InterceptorDiscoveryHelper.HasInterceptAttributes(typeSymbol))
 3085                    {
 23086                        interceptedServiceNames.Add(typeSymbol.Name);
 3087                    }
 3088                }
 3089
 1203090                foreach (var typeSymbol in TypeDiscoveryHelper.GetAllTypes(assemblySymbol.GlobalNamespace))
 3091                {
 3092                    // Check if it's a registerable type (injectable, plugin, factory source, or interceptor)
 463093                    var hasFactoryAttr = FactoryDiscoveryHelper.HasGenerateFactoryAttribute(typeSymbol);
 463094                    var hasInterceptAttr = InterceptorDiscoveryHelper.HasInterceptAttributes(typeSymbol);
 463095                    var isInterceptorProxy = typeSymbol.Name.EndsWith("_InterceptorProxy");
 3096
 463097                    if (!hasFactoryAttr && !hasInterceptAttr && !isInterceptorProxy &&
 463098                        !TypeDiscoveryHelper.WouldBeInjectableIgnoringAccessibility(typeSymbol) &&
 463099                        !TypeDiscoveryHelper.WouldBePluginIgnoringAccessibility(typeSymbol))
 3100                        continue;
 3101
 183102                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 183103                    var shortName = typeSymbol.Name;
 183104                    var lifetime = TypeDiscoveryHelper.DetermineLifetime(typeSymbol) ?? GeneratorLifetime.Singleton;
 183105                    var interfaces = TypeDiscoveryHelper.GetRegisterableInterfaces(typeSymbol)
 143106                        .Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i))
 183107                        .ToArray();
 183108                    var dependencies = TypeDiscoveryHelper.GetBestConstructorParameters(typeSymbol)?
 183109                        .ToArray() ?? Array.Empty<string>();
 183110                    var isDecorator = TypeDiscoveryHelper.HasDecoratorForAttribute(typeSymbol) ||
 183111                                      OpenDecoratorDiscoveryHelper.HasOpenDecoratorForAttribute(typeSymbol);
 183112                    var isPlugin = TypeDiscoveryHelper.WouldBePluginIgnoringAccessibility(typeSymbol);
 183113                    var keyedValues = TypeDiscoveryHelper.GetKeyedServiceKeys(typeSymbol);
 183114                    var keyedValue = keyedValues.Length > 0 ? keyedValues[0] : null;
 3115
 3116                    // Check if this service has an interceptor proxy (its name + "_InterceptorProxy" exists)
 183117                    var hasInterceptorProxy = interceptedServiceNames.Contains(shortName);
 3118
 183119                    assemblyTypes.Add(new DiagnosticTypeInfo(
 183120                        typeName,
 183121                        shortName,
 183122                        lifetime,
 183123                        interfaces,
 183124                        dependencies,
 183125                        isDecorator,
 183126                        isPlugin,
 183127                        hasFactoryAttr,
 183128                        keyedValue,
 183129                        isInterceptor: hasInterceptAttr,
 183130                        hasInterceptorProxy: hasInterceptorProxy));
 3131                }
 3132
 143133                if (assemblyTypes.Count > 0)
 3134                {
 143135                    result[assemblySymbol.Name] = assemblyTypes;
 3136                }
 3137            }
 3138        }
 3139
 953140        return result;
 3141    }
 3142
 3143    /// <summary>
 3144    /// Discovers types from referenced assemblies with [GenerateTypeRegistry] for graph export.
 3145    /// Unlike the main discovery, this includes internal types since they are registered by their own TypeRegistry.
 3146    /// Returns DiscoveredType objects that can be included in the graph export.
 3147    /// </summary>
 3148    private static Dictionary<string, IReadOnlyList<DiscoveredType>> DiscoverReferencedAssemblyTypesForGraph(Compilation
 3149    {
 43150        var result = new Dictionary<string, IReadOnlyList<DiscoveredType>>();
 3151
 13603152        foreach (var reference in compilation.References)
 3153        {
 6763154            if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assemblySymbol)
 3155            {
 3156                // Skip the current assembly
 6763157                if (SymbolEqualityComparer.Default.Equals(assemblySymbol, compilation.Assembly))
 3158                    continue;
 3159
 6763160                if (!TypeDiscoveryHelper.HasGenerateTypeRegistryAttribute(assemblySymbol))
 3161                    continue;
 3162
 3163                // Try to get interface locations from the assembly's ServiceCatalog
 03164                var interfaceLocationLookup = GetInterfaceLocationsFromServiceCatalog(assemblySymbol);
 3165
 03166                var assemblyTypes = new List<DiscoveredType>();
 3167
 03168                foreach (var typeSymbol in TypeDiscoveryHelper.GetAllTypes(assemblySymbol.GlobalNamespace))
 3169                {
 3170                    // Check if it's a registerable type
 03171                    var hasFactoryAttr = FactoryDiscoveryHelper.HasGenerateFactoryAttribute(typeSymbol);
 3172
 3173                    // Skip types that are only factories (handled separately)
 03174                    if (hasFactoryAttr)
 3175                        continue;
 3176
 03177                    if (!TypeDiscoveryHelper.WouldBeInjectableIgnoringAccessibility(typeSymbol) &&
 03178                        !TypeDiscoveryHelper.WouldBePluginIgnoringAccessibility(typeSymbol))
 3179                        continue;
 3180
 3181                    // Skip decorators - they modify other services, not registered directly as services
 03182                    if (TypeDiscoveryHelper.HasDecoratorForAttribute(typeSymbol) ||
 03183                        OpenDecoratorDiscoveryHelper.HasOpenDecoratorForAttribute(typeSymbol))
 3184                        continue;
 3185
 03186                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 03187                    var interfaceSymbols = TypeDiscoveryHelper.GetRegisterableInterfaces(typeSymbol);
 03188                    var interfaces = interfaceSymbols
 03189                        .Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i))
 03190                        .ToArray();
 3191
 3192                    // Get interface locations from ServiceCatalog lookup, falling back to symbol locations
 03193                    var interfaceInfos = interfaceSymbols.Select(i =>
 03194                    {
 03195                        var ifaceFullName = TypeDiscoveryHelper.GetFullyQualifiedName(i);
 03196
 03197                        // First try the ServiceCatalog lookup
 03198                        if (interfaceLocationLookup.TryGetValue(ifaceFullName, out var catalogInfo))
 03199                        {
 03200                            return catalogInfo;
 03201                        }
 03202
 03203                        // Fall back to symbol locations (works for source references)
 03204                        var ifaceLocation = i.Locations.FirstOrDefault();
 03205                        var ifaceFilePath = ifaceLocation?.SourceTree?.FilePath;
 03206                        var ifaceLine = ifaceLocation?.GetLineSpan().StartLinePosition.Line + 1 ?? 0;
 03207                        return new InterfaceInfo(ifaceFullName, ifaceFilePath, ifaceLine);
 03208                    }).ToArray();
 3209
 03210                    var lifetime = TypeDiscoveryHelper.DetermineLifetime(typeSymbol) ?? GeneratorLifetime.Singleton;
 03211                    var constructorParams = TypeDiscoveryHelper.GetBestConstructorParametersWithKeys(typeSymbol)?.ToArra
 03212                        ?? Array.Empty<TypeDiscoveryHelper.ConstructorParameterInfo>();
 03213                    var keyedValues = TypeDiscoveryHelper.GetKeyedServiceKeys(typeSymbol);
 03214                    var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 03215                    var sourceLine = typeSymbol.Locations.FirstOrDefault() is { } location
 03216                        ? location.GetLineSpan().StartLinePosition.Line + 1
 03217                        : 0;
 3218
 03219                    var discoveredType = new DiscoveredType(
 03220                        typeName,
 03221                        interfaces,
 03222                        assemblySymbol.Name,
 03223                        lifetime,
 03224                        constructorParams,
 03225                        keyedValues,
 03226                        sourceFilePath,
 03227                        sourceLine,
 03228                        TypeDiscoveryHelper.IsDisposableType(typeSymbol),
 03229                        interfaceInfos);
 3230
 03231                    assemblyTypes.Add(discoveredType);
 3232                }
 3233
 03234                if (assemblyTypes.Count > 0)
 3235                {
 03236                    result[assemblySymbol.Name] = assemblyTypes;
 3237                }
 3238            }
 3239        }
 3240
 43241        return result;
 3242    }
 3243
 3244    /// <summary>
 3245    /// Extracts interface location information from a referenced assembly's ServiceCatalog.
 3246    /// The ServiceCatalog is generated by Needlr and contains compile-time interface location data.
 3247    /// </summary>
 3248    private static Dictionary<string, InterfaceInfo> GetInterfaceLocationsFromServiceCatalog(IAssemblySymbol assemblySym
 3249    {
 03250        var result = new Dictionary<string, InterfaceInfo>(StringComparer.Ordinal);
 3251
 3252        // Look for the ServiceCatalog class in the Generated namespace
 03253        var serviceCatalogTypeName = $"{assemblySymbol.Name}.Generated.ServiceCatalog";
 03254        var serviceCatalogType = assemblySymbol.GetTypeByMetadataName(serviceCatalogTypeName);
 3255
 03256        if (serviceCatalogType == null)
 03257            return result;
 3258
 3259        // Find the Services property
 03260        var servicesProperty = serviceCatalogType.GetMembers("Services")
 03261            .OfType<IPropertySymbol>()
 03262            .FirstOrDefault();
 3263
 03264        if (servicesProperty == null)
 03265            return result;
 3266
 3267        // The Services property has an initializer with ServiceCatalogEntry array
 3268        // We need to parse the initializer to extract interface locations
 3269        // This requires looking at the declaring syntax reference
 03270        var syntaxRef = servicesProperty.DeclaringSyntaxReferences.FirstOrDefault();
 03271        if (syntaxRef == null)
 03272            return result;
 3273
 03274        var syntax = syntaxRef.GetSyntax();
 03275        if (syntax == null)
 03276            return result;
 3277
 3278        // Parse the array initializer to extract InterfaceEntry data
 3279        // The format is: new InterfaceEntry("fullName", "filePath", line)
 03280        var text = syntax.ToFullString();
 3281
 3282        // Use regex to extract InterfaceEntry values
 03283        var interfaceEntryPattern = new System.Text.RegularExpressions.Regex(
 03284            @"new\s+global::NexusLabs\.Needlr\.Catalog\.InterfaceEntry\(\s*""([^""]+)""\s*,\s*(""([^""]+)""|null)\s*,\s*
 03285            System.Text.RegularExpressions.RegexOptions.Compiled);
 3286
 03287        foreach (System.Text.RegularExpressions.Match match in interfaceEntryPattern.Matches(text))
 3288        {
 03289            var fullName = match.Groups[1].Value;
 03290            var filePath = match.Groups[3].Success ? match.Groups[3].Value : null;
 03291            var line = int.Parse(match.Groups[4].Value);
 3292
 03293            if (!result.ContainsKey(fullName))
 3294            {
 03295                result[fullName] = new InterfaceInfo(fullName, filePath, line);
 3296            }
 3297        }
 3298
 03299        return result;
 3300    }
 3301}

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)
ShouldExportGraph(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider)
GenerateGraphExportSource(System.String,System.String,NexusLabs.Needlr.Generators.BreadcrumbWriter,System.String)
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)
DiscoverReferencedAssemblyTypesForGraph(Microsoft.CodeAnalysis.Compilation)
GetInterfaceLocationsFromServiceCatalog(Microsoft.CodeAnalysis.IAssemblySymbol)