< 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
98%
Covered lines: 593
Uncovered lines: 8
Coverable lines: 601
Total lines: 974
Line coverage: 98.6%
Branch coverage
89%
Covered branches: 268
Total branches: 300
Branch coverage: 89.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Initialize(...)98.38%6262100%
GetBreadcrumbLevel(...)100%88100%
GetProjectDirectory(...)50%4475%
GetDiagnosticOptions(...)100%11100%
ShouldExportGraph(...)100%44100%
IsAotProject(...)100%88100%
GetAttributeInfoFromCompilation(...)83.33%332475%
DiscoverTypes(...)96.66%3030100%
CollectTypesFromAssembly(...)83.33%138138100%
GenerateTypeRegistrySource(...)100%1212100%
GenerateRegisterOptionsMethod(...)90%101094.73%

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
 56921        var compilationAndOptions = context.CompilationProvider
 56922            .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].
 56927        context.RegisterSourceOutput(compilationAndOptions, static (spc, source) =>
 56928        {
 56929            var (compilation, configOptions) = source;
 56930
 56931            var attributeInfo = GetAttributeInfoFromCompilation(compilation);
 56932            if (attributeInfo == null)
 133                return;
 56934
 56835            var info = attributeInfo.Value;
 56836            var assemblyName = compilation.AssemblyName ?? "Generated";
 56937
 56938            // Read breadcrumb level from MSBuild property
 56839            var breadcrumbLevel = GetBreadcrumbLevel(configOptions);
 56840            var projectDirectory = GetProjectDirectory(configOptions);
 56841            var breadcrumbs = new BreadcrumbWriter(breadcrumbLevel);
 56942
 56943            // Check if this is an AOT project
 56844            var isAotProject = IsAotProject(configOptions);
 56945
 56846            var discoveryResult = DiscoverTypes(
 56847                compilation,
 56848                info.NamespacePrefixes,
 56849                info.ExcludeNamespacePrefixes,
 56850                info.IncludeSelf);
 56951
 56952            // Discover referenced assemblies with [GenerateTypeRegistry] for forced loading.
 56953            // Done early so the empty-result check below can include this in its decision.
 56954            // Note: Order of force-loading doesn't matter; ordering is applied at service registration time
 56855            var referencedAssemblies = AssemblyDiscoveryHelper.DiscoverReferencedAssembliesWithTypeRegistry(compilation)
 256                .OrderBy(a => a, StringComparer.OrdinalIgnoreCase)
 56857                .ToList();
 56958
 56959            // Nothing was discovered: no injectable types, factories, providers, options,
 56960            // interceptors, hosted services, plugins, no referenced assemblies to force-load,
 56961            // no inaccessible type errors, and no missing TypeRegistry warnings.
 56862            var nothingDiscovered =
 56863                discoveryResult.InjectableTypes.Count == 0 &&
 56864                discoveryResult.PluginTypes.Count == 0 &&
 56865                discoveryResult.Decorators.Count == 0 &&
 56866                discoveryResult.InterceptedServices.Count == 0 &&
 56867                discoveryResult.Factories.Count == 0 &&
 56868                discoveryResult.Options.Count == 0 &&
 56869                discoveryResult.HttpClients.Count == 0 &&
 56870                discoveryResult.HostedServices.Count == 0 &&
 56871                discoveryResult.Providers.Count == 0 &&
 56872                discoveryResult.ComposedRegistrations.Count == 0 &&
 56873                discoveryResult.InaccessibleTypes.Count == 0 &&
 56874                discoveryResult.MissingTypeRegistryPlugins.Count == 0 &&
 56875                referencedAssemblies.Count == 0;
 56976
 56977            // A type-less assembly that still carries [GenerateTypeRegistry] (guaranteed here by the
 56978            // attributeInfo guard above) is a declared Needlr participant. Consumers force-load
 56979            // typeof({Assembly}.Generated.TypeRegistry) for every attribute-carrying referenced
 56980            // assembly, so emitting nothing makes those consumers fail to compile with CS0234. Emit
 56981            // a minimal registry instead. It depends only on the attributes package (never the
 56982            // injection packages), so it compiles whether or not this assembly references them — a
 56983            // domain, contracts, or documentation-only project participates without being forced to
 56984            // take a dependency it would not otherwise have.
 56885            if (nothingDiscovered)
 56986            {
 987                var emptyRegistrySource = CodeGen.EmptyTypeRegistryCodeGenerator.GenerateTypeRegistrySource(assemblyName
 988                spc.AddSource("TypeRegistry.g.cs", SourceText.From(emptyRegistrySource, Encoding.UTF8));
 56989
 990                var emptyBootstrapSource = CodeGen.EmptyTypeRegistryCodeGenerator.GenerateBootstrapSource(assemblyName, 
 991                spc.AddSource("NeedlrSourceGenBootstrap.g.cs", SourceText.From(emptyBootstrapSource, Encoding.UTF8));
 992                return;
 56993            }
 56994
 56995            // Report errors for inaccessible internal types in referenced assemblies
 614096            foreach (var inaccessibleType in discoveryResult.InaccessibleTypes)
 56997            {
 251198                spc.ReportDiagnostic(Diagnostic.Create(
 251199                    DiagnosticDescriptors.InaccessibleInternalType,
 2511100                    Location.None,
 2511101                    inaccessibleType.TypeName,
 2511102                    inaccessibleType.AssemblyName));
 569103            }
 569104
 569105            // Report errors for referenced assemblies with internal plugin types but no [GenerateTypeRegistry]
 1120106            foreach (var missingPlugin in discoveryResult.MissingTypeRegistryPlugins)
 569107            {
 1108                spc.ReportDiagnostic(Diagnostic.Create(
 1109                    DiagnosticDescriptors.MissingGenerateTypeRegistryAttribute,
 1110                    Location.None,
 1111                    missingPlugin.AssemblyName,
 1112                    missingPlugin.TypeName));
 569113            }
 569114
 569115            // NDLRGEN020: Previously reported error if [Options] used in AOT project
 569116            // Now removed for parity - we generate best-effort code and let unsupported
 569117            // types fail at runtime (matching non-AOT ConfigurationBinder behavior)
 569118
 569119            // NDLRGEN021: Report warning for non-partial positional records
 1296120            foreach (var opt in discoveryResult.Options.Where(o => o.IsNonPartialPositionalRecord))
 569121            {
 2122                spc.ReportDiagnostic(Diagnostic.Create(
 2123                    DiagnosticDescriptors.PositionalRecordMustBePartial,
 2124                    Location.None,
 2125                    opt.TypeName));
 569126            }
 569127
 569128            // NDLRGEN022: Detect disposable captive dependencies using inferred lifetimes
 559129            CaptiveDependencyAnalyzer.ReportDisposableCaptiveDependencies(spc, discoveryResult);
 569130
 569131            // NDLRGEN038: Report skipped composition registrations whose discovered type argument(s)
 569132            // violate the composition's generic constraints.
 1186133            foreach (var violation in discoveryResult.ComposedConstraintViolations)
 569134            {
 34135                spc.ReportDiagnostic(Diagnostic.Create(
 34136                    DiagnosticDescriptors.ComposedTypeArgumentViolatesConstraints,
 34137                    Location.None,
 34138                    violation.CompositionTypeName,
 34139                    violation.TypeArgumentName,
 34140                    violation.SourceInterfaceName));
 569141            }
 569142
 559143            var sourceText = GenerateTypeRegistrySource(discoveryResult, assemblyName, breadcrumbs, projectDirectory, is
 559144            spc.AddSource("TypeRegistry.g.cs", SourceText.From(sourceText, Encoding.UTF8));
 569145
 559146            var bootstrapText = CodeGen.BootstrapCodeGenerator.GenerateModuleInitializerBootstrapSource(assemblyName, re
 559147            spc.AddSource("NeedlrSourceGenBootstrap.g.cs", SourceText.From(bootstrapText, Encoding.UTF8));
 569148
 569149            // Generate interceptor proxy classes if any were discovered
 559150            if (discoveryResult.InterceptedServices.Count > 0)
 569151            {
 14152                var interceptorProxiesText = CodeGen.InterceptorCodeGenerator.GenerateInterceptorProxiesSource(discovery
 14153                spc.AddSource("InterceptorProxies.g.cs", SourceText.From(interceptorProxiesText, Encoding.UTF8));
 569154            }
 569155
 569156            // Generate factory classes if any were discovered
 559157            if (discoveryResult.Factories.Count > 0)
 569158            {
 38159                var factoriesText = CodeGen.FactoryCodeGenerator.GenerateFactoriesSource(discoveryResult.Factories, asse
 38160                spc.AddSource("Factories.g.cs", SourceText.From(factoriesText, Encoding.UTF8));
 569161            }
 569162
 569163            // Generate provider classes if any were discovered
 559164            if (discoveryResult.Providers.Count > 0)
 569165            {
 569166                // Interface-based providers go in the Generated namespace
 35167                var interfaceProviders = discoveryResult.Providers.Where(p => p.IsInterface).ToList();
 17168                if (interfaceProviders.Count > 0)
 569169                {
 11170                    var providersText = CodeGen.ProviderCodeGenerator.GenerateProvidersSource(interfaceProviders, assemb
 11171                    spc.AddSource("Providers.g.cs", SourceText.From(providersText, Encoding.UTF8));
 569172                }
 569173
 569174                // Shorthand class providers need to be generated in their original namespace
 35175                var classProviders = discoveryResult.Providers.Where(p => !p.IsInterface && p.IsPartial).ToList();
 46176                foreach (var provider in classProviders)
 569177                {
 6178                    var providerText = CodeGen.ProviderCodeGenerator.GenerateShorthandProviderSource(provider, assemblyN
 6179                    spc.AddSource($"Provider.{provider.SimpleTypeName}.g.cs", SourceText.From(providerText, Encoding.UTF
 569180                }
 569181            }
 569182
 569183            // Generate options validator classes if any have validation methods
 733184            var optionsWithValidators = discoveryResult.Options.Where(o => o.HasValidatorMethod).ToList();
 559185            if (optionsWithValidators.Count > 0)
 569186            {
 22187                var validatorsText = CodeGen.OptionsCodeGenerator.GenerateOptionsValidatorsSource(optionsWithValidators,
 22188                spc.AddSource("OptionsValidators.g.cs", SourceText.From(validatorsText, Encoding.UTF8));
 569189            }
 569190
 569191            // Generate DataAnnotations validator classes if any have DataAnnotation attributes
 733192            var optionsWithDataAnnotations = discoveryResult.Options.Where(o => o.HasDataAnnotations).ToList();
 559193            if (optionsWithDataAnnotations.Count > 0)
 569194            {
 18195                var dataAnnotationsValidatorsText = CodeGen.OptionsCodeGenerator.GenerateDataAnnotationsValidatorsSource
 18196                spc.AddSource("OptionsDataAnnotationsValidators.g.cs", SourceText.From(dataAnnotationsValidatorsText, En
 569197            }
 569198
 569199            // Generate parameterless constructors for partial positional records with [Options]
 733200            var optionsNeedingConstructors = discoveryResult.Options.Where(o => o.NeedsGeneratedConstructor).ToList();
 559201            if (optionsNeedingConstructors.Count > 0)
 569202            {
 12203                var constructorsText = CodeGen.OptionsCodeGenerator.GeneratePositionalRecordConstructorsSource(optionsNe
 12204                spc.AddSource("OptionsConstructors.g.cs", SourceText.From(constructorsText, Encoding.UTF8));
 569205            }
 569206
 569207            // Generate ServiceCatalog for runtime introspection
 559208            var catalogText = CodeGen.ServiceCatalogCodeGenerator.GenerateServiceCatalogSource(discoveryResult, assembly
 559209            spc.AddSource("ServiceCatalog.g.cs", SourceText.From(catalogText, Encoding.UTF8));
 569210
 569211            // Generate diagnostic output files if configured
 559212            var diagnosticOptions = GetDiagnosticOptions(configOptions);
 559213            if (diagnosticOptions.Enabled)
 569214            {
 95215                var referencedAssemblyTypes = AssemblyDiscoveryHelper.DiscoverReferencedAssemblyTypesForDiagnostics(comp
 95216                var diagnosticsText = DiagnosticsGenerator.GenerateDiagnosticsSource(discoveryResult, assemblyName, proj
 95217                spc.AddSource("NeedlrDiagnostics.g.cs", SourceText.From(diagnosticsText, Encoding.UTF8));
 569218            }
 569219
 569220            // Generate IDE graph export if configured
 559221            if (ShouldExportGraph(configOptions))
 569222            {
 569223                // Discover types from referenced assemblies with [GenerateTypeRegistry] for graph inclusion
 5224                var referencedAssemblyTypesForGraph = AssemblyDiscoveryHelper.DiscoverReferencedAssemblyTypesForGraph(co
 569225
 5226                var graphJson = Export.GraphExporter.GenerateGraphJson(
 5227                    discoveryResult,
 5228                    assemblyName,
 5229                    projectDirectory,
 5230                    diagnostics: null,
 5231                    referencedAssemblyTypes: referencedAssemblyTypesForGraph);
 569232
 569233                // Embed graph as a comment in a generated file so it's accessible
 569234                // The actual JSON is written to obj folder via the generated code
 5235                var graphSourceText = Export.GraphExporter.GenerateGraphExportSource(graphJson, assemblyName, breadcrumb
 5236                spc.AddSource("NeedlrGraph.g.cs", SourceText.From(graphSourceText, Encoding.UTF8));
 569237            }
 1128238        });
 569239    }
 240
 241    internal static BreadcrumbLevel GetBreadcrumbLevel(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider 
 242    {
 683243        if (configOptions.GlobalOptions.TryGetValue("build_property.NeedlrBreadcrumbLevel", out var levelStr) &&
 683244            !string.IsNullOrWhiteSpace(levelStr))
 245        {
 259246            if (levelStr.Equals("None", StringComparison.OrdinalIgnoreCase))
 17247                return BreadcrumbLevel.None;
 242248            if (levelStr.Equals("Verbose", StringComparison.OrdinalIgnoreCase))
 28249                return BreadcrumbLevel.Verbose;
 250        }
 251
 252        // Default to Minimal
 638253        return BreadcrumbLevel.Minimal;
 254    }
 255
 256    private static string? GetProjectDirectory(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider configOp
 257    {
 258        // Try to get the project directory from MSBuild properties
 568259        if (configOptions.GlobalOptions.TryGetValue("build_property.ProjectDir", out var projectDir) &&
 568260            !string.IsNullOrWhiteSpace(projectDir))
 261        {
 0262            return projectDir.TrimEnd('/', '\\');
 263        }
 264
 568265        return null;
 266    }
 267
 268    private static DiagnosticOptions GetDiagnosticOptions(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvid
 269    {
 559270        configOptions.GlobalOptions.TryGetValue("build_property.NeedlrDiagnostics", out var enabled);
 559271        configOptions.GlobalOptions.TryGetValue("build_property.NeedlrDiagnosticsPath", out var outputPath);
 559272        configOptions.GlobalOptions.TryGetValue("build_property.NeedlrDiagnosticsFilter", out var filter);
 273
 559274        return DiagnosticOptions.Parse(enabled, outputPath, filter);
 275    }
 276
 277    /// <summary>
 278    /// Checks if the IDE graph export is enabled.
 279    /// </summary>
 280    private static bool ShouldExportGraph(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider configOptions
 281    {
 282        // Export graph is disabled by default
 283        // Enable with NeedlrExportGraph=true in project file
 559284        if (configOptions.GlobalOptions.TryGetValue("build_property.NeedlrExportGraph", out var exportGraph) &&
 559285            exportGraph.Equals("true", StringComparison.OrdinalIgnoreCase))
 286        {
 5287            return true;
 288        }
 554289        return false;
 290    }
 291
 292    /// <summary>
 293    /// Checks if the project is configured for AOT compilation.
 294    /// Returns true if either PublishAot or IsAotCompatible is set to true.
 295    /// </summary>
 296    private static bool IsAotProject(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider configOptions)
 297    {
 568298        if (configOptions.GlobalOptions.TryGetValue("build_property.PublishAot", out var publishAot) &&
 568299            publishAot.Equals("true", StringComparison.OrdinalIgnoreCase))
 300        {
 83301            return true;
 302        }
 303
 485304        if (configOptions.GlobalOptions.TryGetValue("build_property.IsAotCompatible", out var isAotCompatible) &&
 485305            isAotCompatible.Equals("true", StringComparison.OrdinalIgnoreCase))
 306        {
 1307            return true;
 308        }
 309
 484310        return false;
 311    }
 312
 313    private static AttributeInfo? GetAttributeInfoFromCompilation(Compilation compilation)
 314    {
 315        // Get assembly-level attributes directly from the compilation
 1706316        foreach (var attribute in compilation.Assembly.GetAttributes())
 317        {
 568318            var attrClassName = attribute.AttributeClass?.ToDisplayString();
 319
 320            // Check if this is our attribute (various name format possibilities)
 568321            if (attrClassName != GenerateTypeRegistryAttributeName)
 322                continue;
 323
 568324            string[]? namespacePrefixes = null;
 568325            string[]? excludeNamespacePrefixes = null;
 568326            var includeSelf = true;
 327
 1456328            foreach (var namedArg in attribute.NamedArguments)
 329            {
 160330                switch (namedArg.Key)
 331                {
 332                    case "IncludeNamespacePrefixes":
 150333                        if (!namedArg.Value.IsNull && namedArg.Value.Values.Length > 0)
 334                        {
 150335                            namespacePrefixes = namedArg.Value.Values
 152336                                .Where(v => v.Value is string)
 152337                                .Select(v => (string)v.Value!)
 150338                                .ToArray();
 339                        }
 150340                        break;
 341
 342                    case "ExcludeNamespacePrefixes":
 0343                        if (!namedArg.Value.IsNull && namedArg.Value.Values.Length > 0)
 344                        {
 0345                            excludeNamespacePrefixes = namedArg.Value.Values
 0346                                .Where(v => v.Value is string)
 0347                                .Select(v => (string)v.Value!)
 0348                                .ToArray();
 349                        }
 0350                        break;
 351
 352                    case "IncludeSelf":
 10353                        if (namedArg.Value.Value is bool selfValue)
 354                        {
 10355                            includeSelf = selfValue;
 356                        }
 357                        break;
 358                }
 359            }
 360
 568361            return new AttributeInfo(namespacePrefixes, excludeNamespacePrefixes, includeSelf);
 362        }
 363
 1364        return null;
 365    }
 366
 367    private static DiscoveryResult DiscoverTypes(
 368        Compilation compilation,
 369        string[]? namespacePrefixes,
 370        string[]? excludeNamespacePrefixes,
 371        bool includeSelf)
 372    {
 568373        var injectableTypes = new List<DiscoveredType>();
 568374        var pluginTypes = new List<DiscoveredPlugin>();
 568375        var decorators = new List<DiscoveredDecorator>();
 568376        var openDecorators = new List<DiscoveredOpenDecorator>();
 568377        var composedMarkers = new List<DiscoveredComposedMarker>();
 568378        var composedCandidateTypes = new List<INamedTypeSymbol>();
 568379        var interceptedServices = new List<DiscoveredInterceptedService>();
 568380        var factories = new List<DiscoveredFactory>();
 568381        var options = new List<DiscoveredOptions>();
 568382        var hostedServices = new List<DiscoveredHostedService>();
 568383        var providers = new List<DiscoveredProvider>();
 568384        var httpClients = new List<DiscoveredHttpClient>();
 568385        var inaccessibleTypes = new List<InaccessibleType>();
 568386        var prefixList = namespacePrefixes?.ToList();
 568387        var excludePrefixList = excludeNamespacePrefixes?.ToList();
 388
 389        // Compute the generated namespace for the current assembly
 568390        var currentAssemblyName = compilation.Assembly.Name;
 568391        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(currentAssemblyName);
 568392        var generatedNamespace = $"{safeAssemblyName}.Generated";
 393
 394        // Collect types from the current compilation if includeSelf is true
 568395        if (includeSelf)
 396        {
 567397            CollectTypesFromAssembly(compilation.Assembly, prefixList, excludePrefixList, injectableTypes, pluginTypes, 
 398        }
 399
 400        // Collect types from all referenced assemblies
 192880401        foreach (var reference in compilation.References)
 402        {
 95872403            if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assemblySymbol)
 404            {
 405                // Skip assemblies that already have [GenerateTypeRegistry] — those assemblies
 406                // register their own types at runtime via their own TypeRegistry and cascade
 407                // loading. Scanning them here would trigger false NDLRGEN001 errors for their
 408                // internal types.
 95640409                if (TypeDiscoveryHelper.HasGenerateTypeRegistryAttribute(assemblySymbol))
 410                    continue;
 411
 412                // For referenced assemblies, they use their own generated namespace
 95622413                var refSafeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblySymbol.Name);
 95622414                var refGeneratedNamespace = $"{refSafeAssemblyName}.Generated";
 95622415                CollectTypesFromAssembly(assemblySymbol, prefixList, excludePrefixList, injectableTypes, pluginTypes, de
 416            }
 417        }
 418
 419        // Expand open generic decorators into closed decorator registrations
 568420        if (openDecorators.Count > 0)
 421        {
 6422            CodeGen.DecoratorsCodeGenerator.ExpandOpenDecorators(injectableTypes, openDecorators, decorators);
 423        }
 424
 425        // Expand [RegisterClosedOverImplementationsOf] markers into closed composition registrations.
 568426        var composedRegistrations = new List<DiscoveredComposedRegistration>();
 568427        var composedConstraintViolations = new List<ComposedConstraintViolation>();
 568428        if (composedMarkers.Count > 0)
 429        {
 67430            ComposedRegistrationDiscoveryHelper.Expand(
 67431                composedMarkers,
 67432                composedCandidateTypes,
 67433                composedRegistrations,
 67434                composedConstraintViolations);
 435        }
 436
 437        // Filter out nested options types (types used as properties in other options types)
 568438        if (options.Count > 1)
 439        {
 19440            options = OptionsDiscoveryHelper.FilterNestedOptions(options, compilation);
 441        }
 442
 443        // Check for referenced assemblies with internal plugin types but no [GenerateTypeRegistry]
 568444        var missingTypeRegistryPlugins = new List<MissingTypeRegistryPlugin>();
 192880445        foreach (var reference in compilation.References)
 446        {
 95872447            if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assemblySymbol)
 448            {
 449                // Skip assemblies that already have [GenerateTypeRegistry]
 95640450                if (TypeDiscoveryHelper.HasGenerateTypeRegistryAttribute(assemblySymbol))
 451                    continue;
 452
 453                // Look for internal types that implement Needlr plugin interfaces
 4691118454                foreach (var typeSymbol in TypeDiscoveryHelper.GetAllTypes(assemblySymbol.GlobalNamespace))
 455                {
 2249937456                    if (!TypeDiscoveryHelper.IsInternalOrLessAccessible(typeSymbol))
 457                        continue;
 458
 106635459                    if (!TypeDiscoveryHelper.ImplementsNeedlrPluginInterface(typeSymbol))
 460                        continue;
 461
 462                    // This is an internal plugin type in an assembly without [GenerateTypeRegistry]
 1463                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 1464                    missingTypeRegistryPlugins.Add(new MissingTypeRegistryPlugin(typeName, assemblySymbol.Name));
 465                }
 466            }
 467        }
 468
 568469        return new DiscoveryResult(injectableTypes, pluginTypes, decorators, inaccessibleTypes, missingTypeRegistryPlugi
 470    }
 471
 472    private static void CollectTypesFromAssembly(
 473        IAssemblySymbol assembly,
 474        IReadOnlyList<string>? namespacePrefixes,
 475        IReadOnlyList<string>? excludeNamespacePrefixes,
 476        List<DiscoveredType> injectableTypes,
 477        List<DiscoveredPlugin> pluginTypes,
 478        List<DiscoveredDecorator> decorators,
 479        List<DiscoveredOpenDecorator> openDecorators,
 480        List<DiscoveredInterceptedService> interceptedServices,
 481        List<DiscoveredFactory> factories,
 482        List<DiscoveredOptions> options,
 483        List<DiscoveredHostedService> hostedServices,
 484        List<DiscoveredProvider> providers,
 485        List<DiscoveredHttpClient> httpClients,
 486        List<InaccessibleType> inaccessibleTypes,
 487        List<DiscoveredComposedMarker> composedMarkers,
 488        List<INamedTypeSymbol> composedCandidateTypes,
 489        Compilation compilation,
 490        bool isCurrentAssembly,
 491        string generatedNamespace)
 492    {
 4696620493        foreach (var typeSymbol in TypeDiscoveryHelper.GetAllTypes(assembly.GlobalNamespace))
 494        {
 2252121495            if (!TypeDiscoveryHelper.MatchesNamespacePrefix(typeSymbol, namespacePrefixes))
 496                continue;
 497
 1661333498            if (TypeDiscoveryHelper.MatchesExclusionFilter(typeSymbol, excludeNamespacePrefixes))
 499                continue;
 500
 501            // .NET MAUI per-platform application entry points are framework-owned and carry
 502            // platform-generated interop members that are inaccessible from generated code.
 503            // Scanning them breaks the head build, so skip them before any discovery path runs.
 1661333504            if (TypeDiscoveryHelper.IsMauiPlatformEntryType(typeSymbol))
 505                continue;
 506
 507            // For referenced assemblies, check if the type would be registerable but is inaccessible
 1661329508            if (!isCurrentAssembly && TypeDiscoveryHelper.IsInternalOrLessAccessible(typeSymbol))
 509            {
 510                // Check if this type would have been registered if it were accessible
 79426511                if (TypeDiscoveryHelper.WouldBeInjectableIgnoringAccessibility(typeSymbol) ||
 79426512                    TypeDiscoveryHelper.WouldBePluginIgnoringAccessibility(typeSymbol, compilation.Assembly))
 513                {
 2511514                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 2511515                    inaccessibleTypes.Add(new InaccessibleType(typeName, assembly.Name));
 516                }
 2511517                continue; // Skip further processing for inaccessible types
 518            }
 519
 520            // Check for [Options] attribute
 1581903521            if (OptionsAttributeHelper.HasOptionsAttribute(typeSymbol))
 522            {
 176523                var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 176524                var optionsAttrs = OptionsAttributeHelper.GetOptionsAttributes(typeSymbol);
 176525                var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 526
 527                // Extract bindable properties for AOT code generation
 176528                var properties = OptionsDiscoveryHelper.ExtractBindableProperties(typeSymbol);
 529
 530                // Detect positional record (record with primary constructor parameters)
 176531                var positionalRecordInfo = OptionsDiscoveryHelper.DetectPositionalRecord(typeSymbol, properties);
 532
 716533                foreach (var optionsAttr in optionsAttrs)
 534                {
 535                    // Determine validator type and method
 182536                    var validatorTypeSymbol = optionsAttr.ValidatorType;
 182537                    var targetType = validatorTypeSymbol ?? typeSymbol; // Look for method on options class or external 
 182538                    var methodName = optionsAttr.ValidateMethod ?? "Validate"; // Convention: "Validate"
 539
 540                    // Find validation method using convention-based discovery
 182541                    var validatorMethodInfo = OptionsAttributeHelper.FindValidationMethod(
 182542                        targetType,
 182543                        typeSymbol,
 182544                        methodName,
 182545                        validatorTypeSymbol is not null,
 182546                        optionsAttr.ValidateMethod is null);
 182547                    OptionsValidatorInfo? validatorInfo = validatorMethodInfo.HasValue
 182548                        ? new OptionsValidatorInfo(
 182549                            validatorMethodInfo.Value.MethodName,
 182550                            validatorMethodInfo.Value.IsStatic,
 182551                            validatorMethodInfo.Value.UsesOptionsValidatorInterface)
 182552                        : null;
 553
 554                    // Infer section name if not provided
 182555                    var sectionName = optionsAttr.SectionName
 182556                        ?? Helpers.OptionsNamingHelper.InferSectionName(typeSymbol.Name);
 557
 182558                    var validatorTypeName = validatorTypeSymbol != null
 182559                        ? TypeDiscoveryHelper.GetFullyQualifiedName(validatorTypeSymbol)
 182560                        : null;
 561
 182562                    options.Add(new DiscoveredOptions(
 182563                        typeName,
 182564                        sectionName,
 182565                        optionsAttr.Name,
 182566                        optionsAttr.ValidateOnStart,
 182567                        assembly.Name,
 182568                        sourceFilePath,
 182569                        validatorInfo,
 182570                        optionsAttr.ValidateMethod,
 182571                        validatorTypeName,
 182572                        positionalRecordInfo,
 182573                        properties));
 574                }
 575            }
 576
 1581903577            var httpAttrInfo =
 1581903578                HttpClientOptionsAttributeHelper.GetHttpClientOptionsAttribute(
 1581903579                    typeSymbol);
 1581903580            if (httpAttrInfo.HasValue)
 581            {
 11582                var clientNamePropResult =
 11583                    HttpClientOptionsAttributeHelper.TryGetClientNameProperty(
 11584                        typeSymbol,
 11585                        out var literalValue);
 11586                var propertyNameFromType =
 11587                    clientNamePropResult == ClientNamePropertyResult.Literal
 11588                        ? literalValue
 11589                        : null;
 590
 11591                if (HttpClientOptionsAttributeHelper.TryResolveClientName(
 11592                    typeSymbol,
 11593                    httpAttrInfo.Value,
 11594                    propertyNameFromType,
 11595                    out var resolvedClientName))
 596                {
 10597                    var httpSectionName =
 10598                        HttpClientOptionsAttributeHelper.ResolveSectionName(
 10599                            httpAttrInfo.Value,
 10600                            resolvedClientName);
 10601                    var httpTypeName =
 10602                        TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 10603                    var httpSourceFilePath =
 10604                        typeSymbol.Locations.FirstOrDefault()?
 10605                            .SourceTree?.FilePath;
 10606                    var capabilities =
 10607                        HttpClientOptionsAttributeHelper.DetectCapabilities(
 10608                            typeSymbol);
 609
 10610                    httpClients.Add(new DiscoveredHttpClient(
 10611                        httpTypeName,
 10612                        resolvedClientName,
 10613                        httpSectionName,
 10614                        assembly.Name,
 10615                        capabilities,
 10616                        httpSourceFilePath));
 617                }
 618            }
 619
 620            // Check for [GenerateFactory] attribute - these types get factories instead of direct registration
 1581903621            if (FactoryDiscoveryHelper.HasGenerateFactoryAttribute(typeSymbol))
 622            {
 40623                var factoryConstructors = FactoryDiscoveryHelper.GetFactoryConstructors(typeSymbol);
 40624                if (factoryConstructors.Count > 0)
 625                {
 626                    // Has at least one constructor with runtime params - generate factory
 38627                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 38628                    var interfaces = TypeDiscoveryHelper.GetRegisterableInterfaces(typeSymbol, compilation.Assembly);
 44629                    var interfaceNames = interfaces.Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i)).ToArray();
 38630                    var generationMode = FactoryDiscoveryHelper.GetFactoryGenerationMode(typeSymbol);
 38631                    var returnTypeOverride = FactoryDiscoveryHelper.GetFactoryReturnInterfaceType(typeSymbol);
 38632                    var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 633
 38634                    factories.Add(new DiscoveredFactory(
 38635                        typeName,
 38636                        interfaceNames,
 38637                        assembly.Name,
 38638                        generationMode,
 38639                        factoryConstructors.ToArray(),
 38640                        returnTypeOverride,
 38641                        sourceFilePath));
 642
 38643                    continue; // Don't add to injectable types - factory handles registration
 644                }
 645                // If no runtime params, fall through to normal direct-type registration.
 646            }
 647
 648            // Check for DecoratorFor<T> attributes
 1581865649            var decoratorInfos = TypeDiscoveryHelper.GetDecoratorForAttributes(typeSymbol);
 3163768650            foreach (var decoratorInfo in decoratorInfos)
 651            {
 19652                var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 19653                decorators.Add(new DiscoveredDecorator(
 19654                    decoratorInfo.DecoratorTypeName,
 19655                    decoratorInfo.ServiceTypeName,
 19656                    decoratorInfo.Order,
 19657                    assembly.Name,
 19658                    sourceFilePath));
 659            }
 660
 661            // Check for OpenDecoratorFor attributes (source-gen only open generic decorators)
 1581865662            var openDecoratorInfos = OpenDecoratorDiscoveryHelper.GetOpenDecoratorForAttributes(typeSymbol);
 3163744663            foreach (var openDecoratorInfo in openDecoratorInfos)
 664            {
 7665                var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 7666                openDecorators.Add(new DiscoveredOpenDecorator(
 7667                    openDecoratorInfo.DecoratorType,
 7668                    openDecoratorInfo.OpenGenericInterface,
 7669                    openDecoratorInfo.Order,
 7670                    assembly.Name,
 7671                    sourceFilePath));
 672            }
 673
 674            // Check for RegisterClosedOverImplementationsOf attributes (source-gen only composition markers)
 1581865675            var composedMarkerInfos = ComposedRegistrationDiscoveryHelper.GetComposedMarkers(typeSymbol);
 3163866676            foreach (var composedMarkerInfo in composedMarkerInfos)
 677            {
 68678                var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 68679                composedMarkers.Add(new DiscoveredComposedMarker(
 68680                    composedMarkerInfo.CompositionType,
 68681                    composedMarkerInfo.SourceOpenGenericInterface,
 68682                    composedMarkerInfo.AsServiceType,
 68683                    composedMarkerInfo.Lifetime,
 68684                    assembly.Name,
 68685                    sourceFilePath));
 686            }
 687
 688            // Check for Intercept attributes and collect intercepted services
 1581865689            if (InterceptorDiscoveryHelper.HasInterceptAttributes(typeSymbol))
 690            {
 14691                var lifetime = TypeDiscoveryHelper.DetermineLifetime(typeSymbol);
 14692                if (lifetime.HasValue)
 693                {
 14694                    var classLevelInterceptors = InterceptorDiscoveryHelper.GetInterceptAttributes(typeSymbol);
 14695                    var methodLevelInterceptors = InterceptorDiscoveryHelper.GetMethodLevelInterceptAttributes(typeSymbo
 14696                    var methods = InterceptorDiscoveryHelper.GetInterceptedMethods(typeSymbol, classLevelInterceptors, m
 697
 14698                    if (methods.Count > 0)
 699                    {
 14700                        var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 14701                        var interfaces = TypeDiscoveryHelper.GetRegisterableInterfaces(typeSymbol, compilation.Assembly)
 28702                        var interfaceNames = interfaces.Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i)).ToArra
 703
 704                        // Collect all unique interceptor types
 14705                        var allInterceptorTypes = classLevelInterceptors
 14706                            .Concat(methodLevelInterceptors)
 17707                            .Select(i => i.InterceptorTypeName)
 14708                            .Distinct()
 14709                            .ToArray();
 710
 14711                        var interceptedSourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 712
 14713                        interceptedServices.Add(new DiscoveredInterceptedService(
 14714                            typeName,
 14715                            interfaceNames,
 14716                            assembly.Name,
 14717                            lifetime.Value,
 14718                            methods.ToArray(),
 14719                            allInterceptorTypes,
 14720                            interceptedSourceFilePath));
 721                    }
 722                }
 723            }
 724
 725            // Check for injectable types (but skip types that are providers, which are handled separately)
 1581865726            if (TypeDiscoveryHelper.IsInjectableType(typeSymbol, isCurrentAssembly) && !ProviderDiscoveryHelper.HasProvi
 727            {
 728                // Determine lifetime first - only include types that are actually injectable
 443896729                var lifetime = TypeDiscoveryHelper.DetermineLifetime(typeSymbol);
 443896730                if (lifetime.HasValue)
 731                {
 253068732                    var interfaces = TypeDiscoveryHelper.GetRegisterableInterfaces(typeSymbol, compilation.Assembly);
 253068733                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 253463734                    var interfaceNames = interfaces.Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i)).ToArray();
 735
 736                    // Capture interface locations for navigation
 253068737                    var interfaceInfos = interfaces.Select(i =>
 253068738                    {
 395739                        var ifaceLocation = i.Locations.FirstOrDefault();
 395740                        var ifaceFilePath = ifaceLocation?.SourceTree?.FilePath;
 395741                        var ifaceLine = ifaceLocation?.GetLineSpan().StartLinePosition.Line + 1 ?? 0;
 395742                        return new InterfaceInfo(TypeDiscoveryHelper.GetFullyQualifiedName(i), ifaceFilePath, ifaceLine)
 253068743                    }).ToArray();
 744
 745                    // Check for [DeferToContainer] attribute - use declared types instead of discovered constructors
 253068746                    var deferredParams = TypeDiscoveryHelper.GetDeferToContainerParameterTypes(typeSymbol);
 747                    TypeDiscoveryHelper.ConstructorParameterInfo[] constructorParams;
 253068748                    if (deferredParams != null)
 749                    {
 750                        // DeferToContainer doesn't support keyed services - convert to simple params
 10751                        constructorParams = deferredParams.Select(t => new TypeDiscoveryHelper.ConstructorParameterInfo(
 752                    }
 753                    else
 754                    {
 755                        // [GenerateConstructor]/field-triggered types get their constructor
 756                        // emitted by a sibling generator pass this compilation can't see yet.
 757                        // Use the same field-derived model instead of the symbol-based
 758                        // constructor lookup, which would otherwise still see only the
 759                        // implicit parameterless constructor.
 253063760                        constructorParams = ConstructorGenerationDiscoveryHelper.TryGetEffectiveConstructorParameters(ty
 253063761                            ?? TypeDiscoveryHelper.GetBestConstructorParametersWithKeys(typeSymbol)?.ToArray() ?? [];
 762                    }
 763
 764                    // Get source file path and line for breadcrumbs (null for external assemblies)
 253068765                    var location = typeSymbol.Locations.FirstOrDefault();
 253068766                    var sourceFilePath = location?.SourceTree?.FilePath;
 253068767                    var sourceLine = location?.GetLineSpan().StartLinePosition.Line + 1 ?? 0; // Convert to 1-based
 768
 769                    // Get [Keyed] attribute keys
 253068770                    var serviceKeys = TypeDiscoveryHelper.GetKeyedServiceKeys(typeSymbol);
 771
 772                    // Check if this type implements IDisposable or IAsyncDisposable
 253068773                    var isDisposable = TypeDiscoveryHelper.IsDisposableType(typeSymbol);
 774
 253068775                    injectableTypes.Add(new DiscoveredType(typeName, interfaceNames, assembly.Name, lifetime.Value, cons
 776
 777                    // Every injectable type is a candidate implementation for [RegisterClosedOverImplementationsOf]
 778                    // expansion. Tying collection to the injectable-registration site means a composition composes
 779                    // over exactly the set Needlr registers — current assembly plus referenced libraries — so
 780                    // cross-assembly definitions are handled the same way decorators expand over injectable types.
 253068781                    composedCandidateTypes.Add(typeSymbol);
 782                }
 783            }
 784
 785            // Check for hosted service types (BackgroundService or IHostedService implementations)
 1581865786            if (TypeDiscoveryHelper.IsHostedServiceType(typeSymbol, isCurrentAssembly))
 787            {
 788                // Use the same field-derived model that drives constructor generation
 789                // when one applies, since the symbol-based lookup can't see the
 790                // sibling generator's constructor output within this compilation.
 9791                var effectiveGeneratedParams = ConstructorGenerationDiscoveryHelper.TryGetEffectiveConstructorParameters
 9792                var isGeneratedButNotInjectable = effectiveGeneratedParams is null &&
 9793                    ConstructorGenerationDiscoveryHelper.TryGetModel(typeSymbol) != null;
 794
 795                // A type eligible for constructor generation whose generated constructor
 796                // isn't fully container-resolvable (e.g. a field-triggered guard on a
 797                // plain string) must not fall back to the symbol-based scan below: that
 798                // scan only sees the implicit parameterless constructor that exists
 799                // before the sibling GeneratedConstructorGenerator pass runs, so it would
 800                // register `services.AddSingleton<T>()` for a hosted service whose real
 801                // (generated) constructor the container can never actually activate.
 802                // Skip automatic hosted registration entirely rather than emit metadata
 803                // — and a registration call — for an unactivatable worker.
 9804                if (!isGeneratedButNotInjectable)
 805                {
 8806                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 8807                    var constructorParams = effectiveGeneratedParams?.ToArray()
 8808                        ?? TypeDiscoveryHelper.GetBestConstructorParametersWithKeys(typeSymbol)?.ToArray() ?? [];
 8809                    var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 810
 8811                    hostedServices.Add(new DiscoveredHostedService(
 8812                        typeName,
 8813                        assembly.Name,
 8814                        GeneratorLifetime.Singleton, // Hosted services are always singleton
 8815                        constructorParams,
 8816                        sourceFilePath));
 817                }
 818            }
 819
 820            // Check for [Provider] attribute
 1581865821            if (ProviderDiscoveryHelper.HasProviderAttribute(typeSymbol))
 822            {
 18823                var discoveredProvider = ProviderDiscoveryHelper.DiscoverProvider(typeSymbol, assembly.Name, generatedNa
 18824                if (discoveredProvider.HasValue)
 825                {
 18826                    providers.Add(discoveredProvider.Value);
 827                }
 828            }
 829
 830            // Check for plugin types (concrete class with parameterless ctor and interfaces)
 1581865831            if (TypeDiscoveryHelper.IsPluginType(typeSymbol, isCurrentAssembly))
 832            {
 440129833                var pluginInterfaces = TypeDiscoveryHelper.GetPluginInterfaces(typeSymbol, compilation.Assembly);
 440129834                if (pluginInterfaces.Count > 0)
 835                {
 1644836                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 3300837                    var interfaceNames = pluginInterfaces.Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i)).ToAr
 1644838                    var attributeNames = TypeDiscoveryHelper.GetPluginAttributes(typeSymbol).ToArray();
 1644839                    var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 1644840                    var order = PluginOrderHelper.GetPluginOrder(typeSymbol);
 841
 1644842                    pluginTypes.Add(new DiscoveredPlugin(typeName, interfaceNames, assembly.Name, attributeNames, source
 843                }
 844            }
 845
 846        }
 96189847    }
 848
 849    private static string GenerateTypeRegistrySource(DiscoveryResult discoveryResult, string assemblyName, BreadcrumbWri
 850    {
 559851        var builder = new StringBuilder();
 559852        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblyName);
 559853        var hasOptions = discoveryResult.Options.Count > 0;
 559854        var hasHttpClients = discoveryResult.HttpClients.Count > 0;
 559855        var hasConfigBoundRegistrations = hasOptions || hasHttpClients;
 856
 559857        breadcrumbs.WriteFileHeader(builder, assemblyName, "Needlr Type Registry");
 559858        builder.AppendLine("#nullable enable");
 559859        builder.AppendLine();
 559860        builder.AppendLine("using System;");
 559861        builder.AppendLine("using System.Collections.Generic;");
 559862        builder.AppendLine();
 559863        if (hasConfigBoundRegistrations)
 864        {
 160865            builder.AppendLine("using Microsoft.Extensions.Configuration;");
 160866            if (isAotProject || hasHttpClients)
 867            {
 87868                builder.AppendLine("using Microsoft.Extensions.Options;");
 869            }
 870        }
 559871        builder.AppendLine("using Microsoft.Extensions.DependencyInjection;");
 559872        builder.AppendLine();
 559873        builder.AppendLine("using NexusLabs.Needlr;");
 559874        builder.AppendLine("using NexusLabs.Needlr.Generators;");
 559875        builder.AppendLine();
 559876        builder.AppendLine($"namespace {safeAssemblyName}.Generated;");
 559877        builder.AppendLine();
 559878        builder.AppendLine("/// <summary>");
 559879        builder.AppendLine("/// Compile-time generated registry of injectable types and plugins.");
 559880        builder.AppendLine("/// This eliminates the need for runtime reflection-based type discovery.");
 559881        builder.AppendLine("/// </summary>");
 559882        builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\", \"1
 559883        builder.AppendLine("public static class TypeRegistry");
 559884        builder.AppendLine("{");
 885
 559886        CodeGen.InjectableTypesCodeGenerator.GenerateInjectableTypesArray(builder, discoveryResult.InjectableTypes, brea
 559887        builder.AppendLine();
 559888        CodeGen.PluginsCodeGenerator.GeneratePluginTypesArray(builder, discoveryResult.PluginTypes, breadcrumbs, project
 889
 559890        builder.AppendLine();
 559891        builder.AppendLine("    /// <summary>");
 559892        builder.AppendLine("    /// Gets all injectable types discovered at compile time.");
 559893        builder.AppendLine("    /// </summary>");
 559894        builder.AppendLine("    /// <returns>A read-only list of injectable type information.</returns>");
 559895        builder.AppendLine("    public static IReadOnlyList<InjectableTypeInfo> GetInjectableTypes() => _types;");
 559896        builder.AppendLine();
 559897        builder.AppendLine("    /// <summary>");
 559898        builder.AppendLine("    /// Gets all plugin types discovered at compile time.");
 559899        builder.AppendLine("    /// </summary>");
 559900        builder.AppendLine("    /// <returns>A read-only list of plugin type information.</returns>");
 559901        builder.AppendLine("    public static IReadOnlyList<PluginTypeInfo> GetPluginTypes() => _plugins;");
 902
 559903        if (hasConfigBoundRegistrations)
 904        {
 160905            builder.AppendLine();
 160906            GenerateRegisterOptionsMethod(builder, discoveryResult.Options, discoveryResult.HttpClients, safeAssemblyNam
 907        }
 908
 559909        if (discoveryResult.Providers.Count > 0)
 910        {
 17911            builder.AppendLine();
 17912            CodeGen.DecoratorsCodeGenerator.GenerateRegisterProvidersMethod(builder, discoveryResult.Providers, safeAsse
 913        }
 914
 559915        builder.AppendLine();
 559916        CodeGen.DecoratorsCodeGenerator.GenerateApplyDecoratorsMethod(builder, discoveryResult.Decorators, discoveryResu
 917
 559918        if (discoveryResult.ComposedRegistrations.Count > 0)
 919        {
 42920            builder.AppendLine();
 42921            CodeGen.ComposedRegistrationsCodeGenerator.GenerateRegisterComposedTypesMethod(builder, discoveryResult.Comp
 922        }
 923
 559924        if (discoveryResult.HostedServices.Count > 0)
 925        {
 7926            builder.AppendLine();
 7927            CodeGen.DecoratorsCodeGenerator.GenerateRegisterHostedServicesMethod(builder, discoveryResult.HostedServices
 928        }
 929
 559930        builder.AppendLine("}");
 931
 559932        return builder.ToString();
 933    }
 934
 935    private static void GenerateRegisterOptionsMethod(StringBuilder builder, IReadOnlyList<DiscoveredOptions> options, I
 936    {
 160937        builder.AppendLine("    /// <summary>");
 160938        builder.AppendLine("    /// Registers all discovered options types with the service collection.");
 160939        builder.AppendLine("    /// This binds configuration sections to strongly-typed options classes,");
 160940        builder.AppendLine("    /// and wires up named HttpClient registrations for [HttpClientOptions] types.");
 160941        builder.AppendLine("    /// </summary>");
 160942        builder.AppendLine("    /// <param name=\"services\">The service collection to configure.</param>");
 160943        builder.AppendLine("    /// <param name=\"configuration\">The configuration root to bind options from.</param>")
 160944        builder.AppendLine("    public static void RegisterOptions(IServiceCollection services, IConfiguration configura
 160945        builder.AppendLine("    {");
 946
 160947        if (options.Count == 0 && httpClients.Count == 0)
 948        {
 0949            breadcrumbs.WriteInlineComment(builder, "        ", "No options or HttpClient types discovered");
 950        }
 951        else
 952        {
 160953            if (options.Count > 0)
 954            {
 155955                if (isAotProject)
 956                {
 82957                    CodeGen.OptionsCodeGenerator.GenerateAotOptionsRegistration(builder, options, safeAssemblyName, brea
 958                }
 959                else
 960                {
 73961                    CodeGen.OptionsCodeGenerator.GenerateReflectionOptionsRegistration(builder, options, safeAssemblyNam
 962                }
 963            }
 964
 160965            if (httpClients.Count > 0)
 966            {
 5967                CodeGen.HttpClientCodeGenerator.EmitHttpClientRegistrations(builder, httpClients);
 968            }
 969        }
 970
 160971        builder.AppendLine("    }");
 160972    }
 973
 974}

Methods/Properties

Initialize(Microsoft.CodeAnalysis.IncrementalGeneratorInitializationContext)
GetBreadcrumbLevel(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider)
GetProjectDirectory(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider)
GetDiagnosticOptions(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider)
ShouldExportGraph(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider)
IsAotProject(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider)
GetAttributeInfoFromCompilation(Microsoft.CodeAnalysis.Compilation)
DiscoverTypes(Microsoft.CodeAnalysis.Compilation,System.String[],System.String[],System.Boolean)
CollectTypesFromAssembly(Microsoft.CodeAnalysis.IAssemblySymbol,System.Collections.Generic.IReadOnlyList`1<System.String>,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.DiscoveredHttpClient>,System.Collections.Generic.List`1<NexusLabs.Needlr.Generators.Models.InaccessibleType>,System.Collections.Generic.List`1<NexusLabs.Needlr.Generators.Models.DiscoveredComposedMarker>,System.Collections.Generic.List`1<Microsoft.CodeAnalysis.INamedTypeSymbol>,Microsoft.CodeAnalysis.Compilation,System.Boolean,System.String)
GenerateTypeRegistrySource(NexusLabs.Needlr.Generators.Models.DiscoveryResult,System.String,NexusLabs.Needlr.Generators.BreadcrumbWriter,System.String,System.Boolean)
GenerateRegisterOptionsMethod(System.Text.StringBuilder,System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredOptions>,System.Collections.Generic.IReadOnlyList`1<NexusLabs.Needlr.Generators.Models.DiscoveredHttpClient>,System.String,NexusLabs.Needlr.Generators.BreadcrumbWriter,System.String,System.Boolean)