< 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: 681
Uncovered lines: 7
Coverable lines: 688
Total lines: 1069
Line coverage: 98.9%
Branch coverage
90%
Covered branches: 274
Total branches: 304
Branch coverage: 90.1%
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(...)100%44100%
GetDiagnosticOptions(...)100%11100%
ShouldExportGraph(...)100%44100%
ShouldEmitRegistrationManifest(...)100%22100%
IsAotProject(...)100%88100%
GetAttributeInfoFromCompilation(...)83.33%332475%
DiscoverTypes(...)96.66%3030100%
CollectTypesFromAssembly(...)83.33%138138100%
GenerateTypeRegistrySource(...)100%1414100%
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
 60121        var compilationAndOptions = context.CompilationProvider
 60122            .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].
 60127        context.RegisterSourceOutput(compilationAndOptions, static (spc, source) =>
 60128        {
 60129            var (compilation, configOptions) = source;
 60130
 60131            var attributeInfo = GetAttributeInfoFromCompilation(compilation);
 60132            if (attributeInfo == null)
 133                return;
 60134
 60035            var info = attributeInfo.Value;
 60036            var assemblyName = compilation.AssemblyName ?? "Generated";
 60137
 60138            // Read breadcrumb level from MSBuild property
 60039            var breadcrumbLevel = GetBreadcrumbLevel(configOptions);
 60040            var projectDirectory = GetProjectDirectory(configOptions);
 60041            var breadcrumbs = new BreadcrumbWriter(breadcrumbLevel);
 60142
 60143            // Check if this is an AOT project
 60044            var isAotProject = IsAotProject(configOptions);
 60145
 60046            var discoveryResult = DiscoverTypes(
 60047                compilation,
 60048                info.NamespacePrefixes,
 60049                info.ExcludeNamespacePrefixes,
 60050                info.IncludeSelf);
 60151
 60152            // Discover referenced assemblies with [GenerateTypeRegistry] for forced loading.
 60153            // Done early so the empty-result check below can include this in its decision.
 60154            // Note: Order of force-loading doesn't matter; ordering is applied at service registration time
 60055            var referencedAssemblies = AssemblyDiscoveryHelper.DiscoverReferencedAssembliesWithTypeRegistry(compilation)
 256                .OrderBy(a => a, StringComparer.OrdinalIgnoreCase)
 60057                .ToList();
 60158
 60159            // Nothing was discovered: no injectable types, factories, providers, options,
 60160            // interceptors, hosted services, plugins, no referenced assemblies to force-load,
 60161            // no inaccessible type errors, and no missing TypeRegistry warnings.
 60062            var nothingDiscovered =
 60063                discoveryResult.InjectableTypes.Count == 0 &&
 60064                discoveryResult.PluginTypes.Count == 0 &&
 60065                discoveryResult.Decorators.Count == 0 &&
 60066                discoveryResult.InterceptedServices.Count == 0 &&
 60067                discoveryResult.Factories.Count == 0 &&
 60068                discoveryResult.Options.Count == 0 &&
 60069                discoveryResult.HttpClients.Count == 0 &&
 60070                discoveryResult.HostedServices.Count == 0 &&
 60071                discoveryResult.Providers.Count == 0 &&
 60072                discoveryResult.ComposedRegistrations.Count == 0 &&
 60073                discoveryResult.InaccessibleTypes.Count == 0 &&
 60074                discoveryResult.MissingTypeRegistryPlugins.Count == 0 &&
 60075                referencedAssemblies.Count == 0;
 60176
 60077            var registrationPlan = GeneratedRegistrationPlanBuilder.Create(
 60078                assemblyName,
 60079                isAotProject,
 60080                registersServiceCatalog: !nothingDiscovered,
 60081                referencedAssemblies,
 60082                discoveryResult);
 60083            if (ShouldEmitRegistrationManifest(configOptions))
 60184            {
 1085                var registrationManifestSource =
 1086                    CodeGen.RegistrationManifestCodeGenerator.GenerateSource(
 1087                        registrationPlan,
 1088                        breadcrumbs);
 1089                spc.AddSource(
 1090                    "NeedlrRegistrationManifest.g.cs",
 1091                    GeneratedSourceText.Create(registrationManifestSource));
 60192            }
 60193
 60194            // A type-less assembly that still carries [GenerateTypeRegistry] (guaranteed here by the
 60195            // attributeInfo guard above) is a declared Needlr participant. Consumers force-load
 60196            // typeof({Assembly}.Generated.TypeRegistry) for every attribute-carrying referenced
 60197            // assembly, so emitting nothing makes those consumers fail to compile with CS0234. Emit
 60198            // a minimal registry instead. It depends only on the attributes package (never the
 60199            // injection packages), so it compiles whether or not this assembly references them — a
 601100            // domain, contracts, or documentation-only project participates without being forced to
 601101            // take a dependency it would not otherwise have.
 600102            if (nothingDiscovered)
 601103            {
 10104                var emptyRegistrySource =
 10105                    CodeGen.EmptyTypeRegistryCodeGenerator.GenerateTypeRegistrySource(
 10106                        registrationPlan,
 10107                        breadcrumbs);
 10108                spc.AddSource("TypeRegistry.g.cs", GeneratedSourceText.Create(emptyRegistrySource));
 601109
 10110                var emptyBootstrapSource =
 10111                    CodeGen.EmptyTypeRegistryCodeGenerator.GenerateBootstrapSource(
 10112                        registrationPlan,
 10113                        breadcrumbs);
 10114                spc.AddSource("NeedlrSourceGenBootstrap.g.cs", GeneratedSourceText.Create(emptyBootstrapSource));
 10115                return;
 601116            }
 601117
 601118            // Report errors for inaccessible internal types in referenced assemblies
 6454119            foreach (var inaccessibleType in discoveryResult.InaccessibleTypes)
 601120            {
 2637121                spc.ReportDiagnostic(Diagnostic.Create(
 2637122                    DiagnosticDescriptors.InaccessibleInternalType,
 2637123                    Location.None,
 2637124                    inaccessibleType.TypeName,
 2637125                    inaccessibleType.AssemblyName));
 601126            }
 601127
 601128            // Report errors for referenced assemblies with internal plugin types but no [GenerateTypeRegistry]
 1182129            foreach (var missingPlugin in discoveryResult.MissingTypeRegistryPlugins)
 601130            {
 1131                spc.ReportDiagnostic(Diagnostic.Create(
 1132                    DiagnosticDescriptors.MissingGenerateTypeRegistryAttribute,
 1133                    Location.None,
 1134                    missingPlugin.AssemblyName,
 1135                    missingPlugin.TypeName));
 601136            }
 601137
 601138            // NDLRGEN020: Previously reported error if [Options] used in AOT project
 601139            // Now removed for parity - we generate best-effort code and let unsupported
 601140            // types fail at runtime (matching non-AOT ConfigurationBinder behavior)
 601141
 601142            // NDLRGEN021: Report warning for non-partial positional records
 1360143            foreach (var opt in discoveryResult.Options.Where(o => o.IsNonPartialPositionalRecord))
 601144            {
 2145                spc.ReportDiagnostic(Diagnostic.Create(
 2146                    DiagnosticDescriptors.PositionalRecordMustBePartial,
 2147                    Location.None,
 2148                    opt.TypeName));
 601149            }
 601150
 601151            // NDLRGEN022: Detect disposable captive dependencies using inferred lifetimes
 590152            CaptiveDependencyAnalyzer.ReportDisposableCaptiveDependencies(spc, discoveryResult);
 601153
 601154            // NDLRGEN038: Report skipped composition registrations whose discovered type argument(s)
 601155            // violate the composition's generic constraints.
 1248156            foreach (var violation in discoveryResult.ComposedConstraintViolations)
 601157            {
 34158                spc.ReportDiagnostic(Diagnostic.Create(
 34159                    DiagnosticDescriptors.ComposedTypeArgumentViolatesConstraints,
 34160                    Location.None,
 34161                    violation.CompositionTypeName,
 34162                    violation.TypeArgumentName,
 34163                    violation.SourceInterfaceName));
 601164            }
 601165
 590166            var sourceText = GenerateTypeRegistrySource(
 590167                registrationPlan,
 590168                breadcrumbs,
 590169                projectDirectory);
 590170            spc.AddSource("TypeRegistry.g.cs", GeneratedSourceText.Create(sourceText));
 601171
 590172            var bootstrapText = CodeGen.BootstrapCodeGenerator.GenerateModuleInitializerBootstrapSource(
 590173                registrationPlan.AssemblyName,
 590174                registrationPlan.ReferencedRegistryAssemblies,
 590175                breadcrumbs,
 590176                registrationPlan.HasFactoryRegistrations,
 590177                registrationPlan.HasConfigBoundRegistrations,
 590178                registrationPlan.HasProviderRegistrations);
 590179            spc.AddSource("NeedlrSourceGenBootstrap.g.cs", GeneratedSourceText.Create(bootstrapText));
 601180
 601181            // Generate interceptor proxy classes if any were discovered
 590182            if (registrationPlan.DiscoveryResult.InterceptedServices.Count > 0)
 601183            {
 15184                var interceptorProxiesText = CodeGen.InterceptorCodeGenerator.GenerateInterceptorProxiesSource(
 15185                    registrationPlan.DiscoveryResult.InterceptedServices,
 15186                    registrationPlan.AssemblyName,
 15187                    breadcrumbs,
 15188                    projectDirectory);
 15189                spc.AddSource("InterceptorProxies.g.cs", GeneratedSourceText.Create(interceptorProxiesText));
 601190            }
 601191
 601192            // Generate factory classes if any were discovered
 590193            if (registrationPlan.HasFactoryRegistrations)
 601194            {
 39195                var factoriesText = CodeGen.FactoryCodeGenerator.GenerateFactoriesSource(
 39196                    registrationPlan.EmittedFactories,
 39197                    registrationPlan.AssemblyName,
 39198                    breadcrumbs,
 39199                    projectDirectory);
 39200                spc.AddSource("Factories.g.cs", GeneratedSourceText.Create(factoriesText));
 601201            }
 601202
 601203            // Generate provider classes if any were discovered
 590204            if (registrationPlan.HasProviderRegistrations)
 601205            {
 601206                // Interface-based providers go in the Generated namespace
 18207                var interfaceProviders = registrationPlan.EmittedProviders
 19208                    .Where(p => p.IsInterface)
 18209                    .ToList();
 18210                if (interfaceProviders.Count > 0)
 601211                {
 12212                    var providersText = CodeGen.ProviderCodeGenerator.GenerateProvidersSource(
 12213                        interfaceProviders,
 12214                        registrationPlan.AssemblyName,
 12215                        breadcrumbs,
 12216                        projectDirectory);
 12217                    spc.AddSource("Providers.g.cs", GeneratedSourceText.Create(providersText));
 601218                }
 601219
 601220                // Shorthand class providers need to be generated in their original namespace
 18221                var classProviders = registrationPlan.EmittedProviders
 19222                    .Where(p => !p.IsInterface && p.IsPartial)
 18223                    .ToList();
 48224                foreach (var provider in classProviders)
 601225                {
 6226                    var providerText = CodeGen.ProviderCodeGenerator.GenerateShorthandProviderSource(
 6227                        provider,
 6228                        registrationPlan.AssemblyName,
 6229                        breadcrumbs,
 6230                        projectDirectory);
 6231                    spc.AddSource($"Provider.{provider.SimpleTypeName}.g.cs", GeneratedSourceText.Create(providerText));
 601232                }
 601233            }
 601234
 601235            // Generate options validator classes if any have validation methods
 590236            var optionsWithValidators = registrationPlan.DiscoveryResult.Options
 176237                .Where(o => o.HasValidatorMethod)
 590238                .ToList();
 590239            if (optionsWithValidators.Count > 0)
 601240            {
 22241                var validatorsText = CodeGen.OptionsCodeGenerator.GenerateOptionsValidatorsSource(
 22242                    optionsWithValidators,
 22243                    registrationPlan.AssemblyName,
 22244                    breadcrumbs,
 22245                    projectDirectory);
 22246                spc.AddSource("OptionsValidators.g.cs", GeneratedSourceText.Create(validatorsText));
 601247            }
 601248
 601249            // Generate DataAnnotations validator classes if any have DataAnnotation attributes
 590250            var optionsWithDataAnnotations = registrationPlan.DiscoveryResult.Options
 176251                .Where(o => o.HasDataAnnotations)
 590252                .ToList();
 590253            if (optionsWithDataAnnotations.Count > 0)
 601254            {
 19255                var dataAnnotationsValidatorsText = CodeGen.OptionsCodeGenerator.GenerateDataAnnotationsValidatorsSource
 19256                    optionsWithDataAnnotations,
 19257                    registrationPlan.AssemblyName,
 19258                    breadcrumbs,
 19259                    projectDirectory);
 19260                spc.AddSource("OptionsDataAnnotationsValidators.g.cs", GeneratedSourceText.Create(dataAnnotationsValidat
 601261            }
 601262
 601263            // Generate parameterless constructors for partial positional records with [Options]
 590264            var optionsNeedingConstructors = registrationPlan.DiscoveryResult.Options
 176265                .Where(o => o.NeedsGeneratedConstructor)
 590266                .ToList();
 590267            if (optionsNeedingConstructors.Count > 0)
 601268            {
 12269                var constructorsText = CodeGen.OptionsCodeGenerator.GeneratePositionalRecordConstructorsSource(
 12270                    optionsNeedingConstructors,
 12271                    registrationPlan.AssemblyName,
 12272                    breadcrumbs,
 12273                    projectDirectory);
 12274                spc.AddSource("OptionsConstructors.g.cs", GeneratedSourceText.Create(constructorsText));
 601275            }
 601276
 601277            // Generate ServiceCatalog for runtime introspection
 590278            var catalogText = CodeGen.ServiceCatalogCodeGenerator.GenerateServiceCatalogSource(
 590279                registrationPlan,
 590280                projectDirectory,
 590281                breadcrumbs);
 590282            spc.AddSource("ServiceCatalog.g.cs", GeneratedSourceText.Create(catalogText));
 601283
 601284            // Generate diagnostic output files if configured
 590285            var diagnosticOptions = GetDiagnosticOptions(configOptions);
 590286            if (diagnosticOptions.Enabled)
 601287            {
 109288                var referencedAssemblyTypes = AssemblyDiscoveryHelper.DiscoverReferencedAssemblyTypesForDiagnostics(comp
 109289                var diagnosticsText = DiagnosticsGenerator.GenerateDiagnosticsSource(discoveryResult, assemblyName, proj
 109290                spc.AddSource("NeedlrDiagnostics.g.cs", GeneratedSourceText.Create(diagnosticsText));
 601291            }
 601292
 601293            // Generate IDE graph export if configured
 590294            if (ShouldExportGraph(configOptions))
 601295            {
 601296                // Discover types from referenced assemblies with [GenerateTypeRegistry] for graph inclusion
 19297                var referencedAssemblyTypesForGraph = AssemblyDiscoveryHelper.DiscoverReferencedAssemblyTypesForGraph(co
 601298
 19299                var graphJson = Export.GraphExporter.GenerateGraphJson(
 19300                    discoveryResult,
 19301                    assemblyName,
 19302                    projectDirectory,
 19303                    diagnostics: null,
 19304                    referencedAssemblyTypes: referencedAssemblyTypesForGraph);
 601305
 601306                // Embed graph as a comment in a generated file so it's accessible
 601307                // The actual JSON is written to obj folder via the generated code
 19308                var graphSourceText = Export.GraphExporter.GenerateGraphExportSource(graphJson, assemblyName, breadcrumb
 19309                spc.AddSource("NeedlrGraph.g.cs", GeneratedSourceText.Create(graphSourceText));
 601310            }
 1191311        });
 601312    }
 313
 314    internal static BreadcrumbLevel GetBreadcrumbLevel(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider 
 315    {
 715316        if (configOptions.GlobalOptions.TryGetValue("build_property.NeedlrBreadcrumbLevel", out var levelStr) &&
 715317            !string.IsNullOrWhiteSpace(levelStr))
 318        {
 259319            if (levelStr.Equals("None", StringComparison.OrdinalIgnoreCase))
 17320                return BreadcrumbLevel.None;
 242321            if (levelStr.Equals("Verbose", StringComparison.OrdinalIgnoreCase))
 28322                return BreadcrumbLevel.Verbose;
 323        }
 324
 325        // Default to Minimal
 670326        return BreadcrumbLevel.Minimal;
 327    }
 328
 329    private static string? GetProjectDirectory(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider configOp
 330    {
 331        // Try to get the project directory from MSBuild properties
 600332        if (configOptions.GlobalOptions.TryGetValue("build_property.ProjectDir", out var projectDir) &&
 600333            !string.IsNullOrWhiteSpace(projectDir))
 334        {
 4335            return projectDir.TrimEnd('/', '\\');
 336        }
 337
 596338        return null;
 339    }
 340
 341    private static DiagnosticOptions GetDiagnosticOptions(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvid
 342    {
 590343        configOptions.GlobalOptions.TryGetValue("build_property.NeedlrDiagnostics", out var enabled);
 590344        configOptions.GlobalOptions.TryGetValue("build_property.NeedlrDiagnosticsPath", out var outputPath);
 590345        configOptions.GlobalOptions.TryGetValue("build_property.NeedlrDiagnosticsFilter", out var filter);
 346
 590347        return DiagnosticOptions.Parse(enabled, outputPath, filter);
 348    }
 349
 350    /// <summary>
 351    /// Checks if the IDE graph export is enabled.
 352    /// </summary>
 353    private static bool ShouldExportGraph(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider configOptions
 354    {
 355        // Export graph is disabled by default
 356        // Enable with NeedlrExportGraph=true in project file
 590357        if (configOptions.GlobalOptions.TryGetValue("build_property.NeedlrExportGraph", out var exportGraph) &&
 590358            exportGraph.Equals("true", StringComparison.OrdinalIgnoreCase))
 359        {
 19360            return true;
 361        }
 571362        return false;
 363    }
 364
 365    private static bool ShouldEmitRegistrationManifest(
 366        Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider configOptions)
 367    {
 600368        return configOptions.GlobalOptions.TryGetValue(
 600369                "build_property.NeedlrEmitRegistrationManifest",
 600370                out var enabled) &&
 600371            enabled.Equals(
 600372                "true",
 600373                StringComparison.OrdinalIgnoreCase);
 374    }
 375
 376    /// <summary>
 377    /// Checks if the project is configured for AOT compilation.
 378    /// Returns true if either PublishAot or IsAotCompatible is set to true.
 379    /// </summary>
 380    private static bool IsAotProject(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider configOptions)
 381    {
 600382        if (configOptions.GlobalOptions.TryGetValue("build_property.PublishAot", out var publishAot) &&
 600383            publishAot.Equals("true", StringComparison.OrdinalIgnoreCase))
 384        {
 84385            return true;
 386        }
 387
 516388        if (configOptions.GlobalOptions.TryGetValue("build_property.IsAotCompatible", out var isAotCompatible) &&
 516389            isAotCompatible.Equals("true", StringComparison.OrdinalIgnoreCase))
 390        {
 1391            return true;
 392        }
 393
 515394        return false;
 395    }
 396
 397    private static AttributeInfo? GetAttributeInfoFromCompilation(Compilation compilation)
 398    {
 399        // Get assembly-level attributes directly from the compilation
 1802400        foreach (var attribute in compilation.Assembly.GetAttributes())
 401        {
 600402            var attrClassName = attribute.AttributeClass?.ToDisplayString();
 403
 404            // Check if this is our attribute (various name format possibilities)
 600405            if (attrClassName != GenerateTypeRegistryAttributeName)
 406                continue;
 407
 600408            string[]? namespacePrefixes = null;
 600409            string[]? excludeNamespacePrefixes = null;
 600410            var includeSelf = true;
 411
 1544412            foreach (var namedArg in attribute.NamedArguments)
 413            {
 172414                switch (namedArg.Key)
 415                {
 416                    case "IncludeNamespacePrefixes":
 161417                        if (!namedArg.Value.IsNull && namedArg.Value.Values.Length > 0)
 418                        {
 161419                            namespacePrefixes = namedArg.Value.Values
 163420                                .Where(v => v.Value is string)
 163421                                .Select(v => (string)v.Value!)
 161422                                .ToArray();
 423                        }
 161424                        break;
 425
 426                    case "ExcludeNamespacePrefixes":
 0427                        if (!namedArg.Value.IsNull && namedArg.Value.Values.Length > 0)
 428                        {
 0429                            excludeNamespacePrefixes = namedArg.Value.Values
 0430                                .Where(v => v.Value is string)
 0431                                .Select(v => (string)v.Value!)
 0432                                .ToArray();
 433                        }
 0434                        break;
 435
 436                    case "IncludeSelf":
 11437                        if (namedArg.Value.Value is bool selfValue)
 438                        {
 11439                            includeSelf = selfValue;
 440                        }
 441                        break;
 442                }
 443            }
 444
 600445            return new AttributeInfo(namespacePrefixes, excludeNamespacePrefixes, includeSelf);
 446        }
 447
 1448        return null;
 449    }
 450
 451    private static DiscoveryResult DiscoverTypes(
 452        Compilation compilation,
 453        string[]? namespacePrefixes,
 454        string[]? excludeNamespacePrefixes,
 455        bool includeSelf)
 456    {
 600457        var injectableTypes = new List<DiscoveredType>();
 600458        var pluginTypes = new List<DiscoveredPlugin>();
 600459        var decorators = new List<DiscoveredDecorator>();
 600460        var openDecorators = new List<DiscoveredOpenDecorator>();
 600461        var composedMarkers = new List<DiscoveredComposedMarker>();
 600462        var composedCandidateTypes = new List<INamedTypeSymbol>();
 600463        var interceptedServices = new List<DiscoveredInterceptedService>();
 600464        var factories = new List<DiscoveredFactory>();
 600465        var options = new List<DiscoveredOptions>();
 600466        var hostedServices = new List<DiscoveredHostedService>();
 600467        var providers = new List<DiscoveredProvider>();
 600468        var httpClients = new List<DiscoveredHttpClient>();
 600469        var inaccessibleTypes = new List<InaccessibleType>();
 600470        var prefixList = namespacePrefixes?.ToList();
 600471        var excludePrefixList = excludeNamespacePrefixes?.ToList();
 472
 473        // Compute the generated namespace for the current assembly
 600474        var currentAssemblyName = compilation.Assembly.Name;
 600475        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(currentAssemblyName);
 600476        var generatedNamespace = $"{safeAssemblyName}.Generated";
 477
 478        // Collect types from the current compilation if includeSelf is true
 600479        if (includeSelf)
 480        {
 598481            CollectTypesFromAssembly(compilation.Assembly, prefixList, excludePrefixList, injectableTypes, pluginTypes, 
 482        }
 483
 484        // Collect types from all referenced assemblies
 203854485        foreach (var reference in compilation.References)
 486        {
 101327487            if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assemblySymbol)
 488            {
 489                // Skip assemblies that already have [GenerateTypeRegistry] — those assemblies
 490                // register their own types at runtime via their own TypeRegistry and cascade
 491                // loading. Scanning them here would trigger false NDLRGEN001 errors for their
 492                // internal types.
 101070493                if (TypeDiscoveryHelper.HasGenerateTypeRegistryAttribute(assemblySymbol))
 494                    continue;
 495
 496                // For referenced assemblies, they use their own generated namespace
 101051497                var refSafeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblySymbol.Name);
 101051498                var refGeneratedNamespace = $"{refSafeAssemblyName}.Generated";
 101051499                CollectTypesFromAssembly(assemblySymbol, prefixList, excludePrefixList, injectableTypes, pluginTypes, de
 500            }
 501        }
 502
 503        // Expand open generic decorators into closed decorator registrations
 600504        if (openDecorators.Count > 0)
 505        {
 6506            CodeGen.DecoratorsCodeGenerator.ExpandOpenDecorators(injectableTypes, openDecorators, decorators);
 507        }
 508
 509        // Expand [RegisterClosedOverImplementationsOf] markers into closed composition registrations.
 600510        var composedRegistrations = new List<DiscoveredComposedRegistration>();
 600511        var composedConstraintViolations = new List<ComposedConstraintViolation>();
 600512        if (composedMarkers.Count > 0)
 513        {
 68514            ComposedRegistrationDiscoveryHelper.Expand(
 68515                composedMarkers,
 68516                composedCandidateTypes,
 68517                composedRegistrations,
 68518                composedConstraintViolations);
 519        }
 520
 521        // Filter out nested options types (types used as properties in other options types)
 600522        if (options.Count > 1)
 523        {
 19524            options = OptionsDiscoveryHelper.FilterNestedOptions(options, compilation);
 525        }
 526
 527        // Check for referenced assemblies with internal plugin types but no [GenerateTypeRegistry]
 600528        var missingTypeRegistryPlugins = new List<MissingTypeRegistryPlugin>();
 203854529        foreach (var reference in compilation.References)
 530        {
 101327531            if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assemblySymbol)
 532            {
 533                // Skip assemblies that already have [GenerateTypeRegistry]
 101070534                if (TypeDiscoveryHelper.HasGenerateTypeRegistryAttribute(assemblySymbol))
 535                    continue;
 536
 537                // Look for internal types that implement Needlr plugin interfaces
 4958614538                foreach (var typeSymbol in TypeDiscoveryHelper.GetAllTypes(assemblySymbol.GlobalNamespace))
 539                {
 2378256540                    if (!TypeDiscoveryHelper.IsInternalOrLessAccessible(typeSymbol))
 541                        continue;
 542
 112897543                    if (!TypeDiscoveryHelper.ImplementsNeedlrPluginInterface(typeSymbol))
 544                        continue;
 545
 546                    // This is an internal plugin type in an assembly without [GenerateTypeRegistry]
 1547                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 1548                    missingTypeRegistryPlugins.Add(new MissingTypeRegistryPlugin(typeName, assemblySymbol.Name));
 549                }
 550            }
 551        }
 552
 600553        return new DiscoveryResult(injectableTypes, pluginTypes, decorators, inaccessibleTypes, missingTypeRegistryPlugi
 554    }
 555
 556    private static void CollectTypesFromAssembly(
 557        IAssemblySymbol assembly,
 558        IReadOnlyList<string>? namespacePrefixes,
 559        IReadOnlyList<string>? excludeNamespacePrefixes,
 560        List<DiscoveredType> injectableTypes,
 561        List<DiscoveredPlugin> pluginTypes,
 562        List<DiscoveredDecorator> decorators,
 563        List<DiscoveredOpenDecorator> openDecorators,
 564        List<DiscoveredInterceptedService> interceptedServices,
 565        List<DiscoveredFactory> factories,
 566        List<DiscoveredOptions> options,
 567        List<DiscoveredHostedService> hostedServices,
 568        List<DiscoveredProvider> providers,
 569        List<DiscoveredHttpClient> httpClients,
 570        List<InaccessibleType> inaccessibleTypes,
 571        List<DiscoveredComposedMarker> composedMarkers,
 572        List<INamedTypeSymbol> composedCandidateTypes,
 573        Compilation compilation,
 574        bool isCurrentAssembly,
 575        string generatedNamespace)
 576    {
 4964374577        foreach (var typeSymbol in TypeDiscoveryHelper.GetAllTypes(assembly.GlobalNamespace))
 578        {
 2380538579            if (!TypeDiscoveryHelper.MatchesNamespacePrefix(typeSymbol, namespacePrefixes))
 580                continue;
 581
 1745303582            if (TypeDiscoveryHelper.MatchesExclusionFilter(typeSymbol, excludeNamespacePrefixes))
 583                continue;
 584
 585            // .NET MAUI per-platform application entry points are framework-owned and carry
 586            // platform-generated interop members that are inaccessible from generated code.
 587            // Scanning them breaks the head build, so skip them before any discovery path runs.
 1745303588            if (TypeDiscoveryHelper.IsMauiPlatformEntryType(typeSymbol))
 589                continue;
 590
 591            // For referenced assemblies, check if the type would be registerable but is inaccessible
 1745299592            if (!isCurrentAssembly && TypeDiscoveryHelper.IsInternalOrLessAccessible(typeSymbol))
 593            {
 594                // Check if this type would have been registered if it were accessible
 83437595                if (TypeDiscoveryHelper.WouldBeInjectableIgnoringAccessibility(typeSymbol) ||
 83437596                    TypeDiscoveryHelper.WouldBePluginIgnoringAccessibility(typeSymbol, compilation.Assembly))
 597                {
 2637598                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 2637599                    inaccessibleTypes.Add(new InaccessibleType(typeName, assembly.Name));
 600                }
 2637601                continue; // Skip further processing for inaccessible types
 602            }
 603
 604            // Check for [Options] attribute
 1661862605            if (OptionsAttributeHelper.HasOptionsAttribute(typeSymbol))
 606            {
 178607                var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 178608                var optionsAttrs = OptionsAttributeHelper.GetOptionsAttributes(typeSymbol);
 178609                var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 610
 611                // Extract bindable properties for AOT code generation
 178612                var properties = OptionsDiscoveryHelper.ExtractBindableProperties(typeSymbol);
 613
 614                // Detect positional record (record with primary constructor parameters)
 178615                var positionalRecordInfo = OptionsDiscoveryHelper.DetectPositionalRecord(typeSymbol, properties);
 616
 724617                foreach (var optionsAttr in optionsAttrs)
 618                {
 619                    // Determine validator type and method
 184620                    var validatorTypeSymbol = optionsAttr.ValidatorType;
 184621                    var targetType = validatorTypeSymbol ?? typeSymbol; // Look for method on options class or external 
 184622                    var methodName = optionsAttr.ValidateMethod ?? "Validate"; // Convention: "Validate"
 623
 624                    // Find validation method using convention-based discovery
 184625                    var validatorMethodInfo = OptionsAttributeHelper.FindValidationMethod(
 184626                        targetType,
 184627                        typeSymbol,
 184628                        methodName,
 184629                        validatorTypeSymbol is not null,
 184630                        optionsAttr.ValidateMethod is null);
 184631                    OptionsValidatorInfo? validatorInfo = validatorMethodInfo.HasValue
 184632                        ? new OptionsValidatorInfo(
 184633                            validatorMethodInfo.Value.MethodName,
 184634                            validatorMethodInfo.Value.IsStatic,
 184635                            validatorMethodInfo.Value.UsesOptionsValidatorInterface)
 184636                        : null;
 637
 638                    // Infer section name if not provided
 184639                    var sectionName = optionsAttr.SectionName
 184640                        ?? Helpers.OptionsNamingHelper.InferSectionName(typeSymbol.Name);
 641
 184642                    var validatorTypeName = validatorTypeSymbol != null
 184643                        ? TypeDiscoveryHelper.GetFullyQualifiedName(validatorTypeSymbol)
 184644                        : null;
 645
 184646                    options.Add(new DiscoveredOptions(
 184647                        typeName,
 184648                        sectionName,
 184649                        optionsAttr.Name,
 184650                        optionsAttr.ValidateOnStart,
 184651                        assembly.Name,
 184652                        sourceFilePath,
 184653                        validatorInfo,
 184654                        optionsAttr.ValidateMethod,
 184655                        validatorTypeName,
 184656                        positionalRecordInfo,
 184657                        properties));
 658                }
 659            }
 660
 1661862661            var httpAttrInfo =
 1661862662                HttpClientOptionsAttributeHelper.GetHttpClientOptionsAttribute(
 1661862663                    typeSymbol);
 1661862664            if (httpAttrInfo.HasValue)
 665            {
 12666                var clientNamePropResult =
 12667                    HttpClientOptionsAttributeHelper.TryGetClientNameProperty(
 12668                        typeSymbol,
 12669                        out var literalValue);
 12670                var propertyNameFromType =
 12671                    clientNamePropResult == ClientNamePropertyResult.Literal
 12672                        ? literalValue
 12673                        : null;
 674
 12675                if (HttpClientOptionsAttributeHelper.TryResolveClientName(
 12676                    typeSymbol,
 12677                    httpAttrInfo.Value,
 12678                    propertyNameFromType,
 12679                    out var resolvedClientName))
 680                {
 11681                    var httpSectionName =
 11682                        HttpClientOptionsAttributeHelper.ResolveSectionName(
 11683                            httpAttrInfo.Value,
 11684                            resolvedClientName);
 11685                    var httpTypeName =
 11686                        TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 11687                    var httpSourceFilePath =
 11688                        typeSymbol.Locations.FirstOrDefault()?
 11689                            .SourceTree?.FilePath;
 11690                    var capabilities =
 11691                        HttpClientOptionsAttributeHelper.DetectCapabilities(
 11692                            typeSymbol);
 693
 11694                    httpClients.Add(new DiscoveredHttpClient(
 11695                        httpTypeName,
 11696                        resolvedClientName,
 11697                        httpSectionName,
 11698                        assembly.Name,
 11699                        capabilities,
 11700                        httpSourceFilePath));
 701                }
 702            }
 703
 704            // Check for [GenerateFactory] attribute - these types get factories instead of direct registration
 1661862705            if (FactoryDiscoveryHelper.HasGenerateFactoryAttribute(typeSymbol))
 706            {
 41707                var factoryConstructors = FactoryDiscoveryHelper.GetFactoryConstructors(typeSymbol);
 41708                if (factoryConstructors.Count > 0)
 709                {
 710                    // Has at least one constructor with runtime params - generate factory
 39711                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 39712                    var interfaces = TypeDiscoveryHelper.GetRegisterableInterfaces(typeSymbol, compilation.Assembly);
 45713                    var interfaceNames = interfaces.Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i)).ToArray();
 39714                    var generationMode = FactoryDiscoveryHelper.GetFactoryGenerationMode(typeSymbol);
 39715                    var returnTypeOverride = FactoryDiscoveryHelper.GetFactoryReturnInterfaceType(typeSymbol);
 39716                    var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 717
 39718                    factories.Add(new DiscoveredFactory(
 39719                        typeName,
 39720                        interfaceNames,
 39721                        assembly.Name,
 39722                        generationMode,
 39723                        factoryConstructors.ToArray(),
 39724                        returnTypeOverride,
 39725                        sourceFilePath));
 726
 39727                    continue; // Don't add to injectable types - factory handles registration
 728                }
 729                // If no runtime params, fall through to normal direct-type registration.
 730            }
 731
 732            // Check for DecoratorFor<T> attributes
 1661823733            var decoratorInfos = TypeDiscoveryHelper.GetDecoratorForAttributes(typeSymbol);
 3323726734            foreach (var decoratorInfo in decoratorInfos)
 735            {
 40736                var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 40737                decorators.Add(new DiscoveredDecorator(
 40738                    decoratorInfo.DecoratorTypeName,
 40739                    decoratorInfo.ServiceTypeName,
 40740                    decoratorInfo.Order,
 40741                    assembly.Name,
 40742                    sourceFilePath));
 743            }
 744
 745            // Check for OpenDecoratorFor attributes (source-gen only open generic decorators)
 1661823746            var openDecoratorInfos = OpenDecoratorDiscoveryHelper.GetOpenDecoratorForAttributes(typeSymbol);
 3323660747            foreach (var openDecoratorInfo in openDecoratorInfos)
 748            {
 7749                var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 7750                openDecorators.Add(new DiscoveredOpenDecorator(
 7751                    openDecoratorInfo.DecoratorType,
 7752                    openDecoratorInfo.OpenGenericInterface,
 7753                    openDecoratorInfo.Order,
 7754                    assembly.Name,
 7755                    sourceFilePath));
 756            }
 757
 758            // Check for RegisterClosedOverImplementationsOf attributes (source-gen only composition markers)
 1661823759            var composedMarkerInfos = ComposedRegistrationDiscoveryHelper.GetComposedMarkers(typeSymbol);
 3323784760            foreach (var composedMarkerInfo in composedMarkerInfos)
 761            {
 69762                var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 69763                composedMarkers.Add(new DiscoveredComposedMarker(
 69764                    composedMarkerInfo.CompositionType,
 69765                    composedMarkerInfo.SourceOpenGenericInterface,
 69766                    composedMarkerInfo.AsServiceType,
 69767                    composedMarkerInfo.Lifetime,
 69768                    assembly.Name,
 69769                    sourceFilePath));
 770            }
 771
 772            // Check for Intercept attributes and collect intercepted services
 1661823773            if (InterceptorDiscoveryHelper.HasInterceptAttributes(typeSymbol))
 774            {
 15775                var lifetime = TypeDiscoveryHelper.DetermineLifetime(typeSymbol);
 15776                if (lifetime.HasValue)
 777                {
 15778                    var classLevelInterceptors = InterceptorDiscoveryHelper.GetInterceptAttributes(typeSymbol);
 15779                    var methodLevelInterceptors = InterceptorDiscoveryHelper.GetMethodLevelInterceptAttributes(typeSymbo
 15780                    var methods = InterceptorDiscoveryHelper.GetInterceptedMethods(typeSymbol, classLevelInterceptors, m
 781
 15782                    if (methods.Count > 0)
 783                    {
 15784                        var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 15785                        var interfaces = TypeDiscoveryHelper.GetRegisterableInterfaces(typeSymbol, compilation.Assembly)
 30786                        var interfaceNames = interfaces.Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i)).ToArra
 787
 788                        // Collect all unique interceptor types
 15789                        var allInterceptorTypes = classLevelInterceptors
 15790                            .Concat(methodLevelInterceptors)
 18791                            .Select(i => i.InterceptorTypeName)
 15792                            .Distinct()
 15793                            .ToArray();
 794
 15795                        var interceptedSourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 796
 15797                        interceptedServices.Add(new DiscoveredInterceptedService(
 15798                            typeName,
 15799                            interfaceNames,
 15800                            assembly.Name,
 15801                            lifetime.Value,
 15802                            methods.ToArray(),
 15803                            allInterceptorTypes,
 15804                            interceptedSourceFilePath));
 805                    }
 806                }
 807            }
 808
 809            // Check for injectable types (but skip types that are providers, which are handled separately)
 1661823810            if (TypeDiscoveryHelper.IsInjectableType(typeSymbol, isCurrentAssembly) && !ProviderDiscoveryHelper.HasProvi
 811            {
 812                // Determine lifetime first - only include types that are actually injectable
 466233813                var lifetime = TypeDiscoveryHelper.DetermineLifetime(typeSymbol);
 466233814                if (lifetime.HasValue)
 815                {
 265807816                    var interfaces = TypeDiscoveryHelper.GetRegisterableInterfaces(typeSymbol, compilation.Assembly);
 265807817                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 266228818                    var interfaceNames = interfaces.Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i)).ToArray();
 819
 820                    // Capture interface locations for navigation
 265807821                    var interfaceInfos = interfaces.Select(i =>
 265807822                    {
 421823                        var ifaceLocation = i.Locations.FirstOrDefault();
 421824                        var ifaceFilePath = ifaceLocation?.SourceTree?.FilePath;
 421825                        var ifaceLine = ifaceLocation?.GetLineSpan().StartLinePosition.Line + 1 ?? 0;
 421826                        return new InterfaceInfo(TypeDiscoveryHelper.GetFullyQualifiedName(i), ifaceFilePath, ifaceLine)
 265807827                    }).ToArray();
 828
 829                    // Check for [DeferToContainer] attribute - use declared types instead of discovered constructors
 265807830                    var deferredParams = TypeDiscoveryHelper.GetDeferToContainerParameterTypes(typeSymbol);
 831                    TypeDiscoveryHelper.ConstructorParameterInfo[] constructorParams;
 265807832                    if (deferredParams != null)
 833                    {
 834                        // DeferToContainer doesn't support keyed services - convert to simple params
 10835                        constructorParams = deferredParams.Select(t => new TypeDiscoveryHelper.ConstructorParameterInfo(
 836                    }
 837                    else
 838                    {
 839                        // [GenerateConstructor]/field-triggered types get their constructor
 840                        // emitted by a sibling generator pass this compilation can't see yet.
 841                        // Use the same field-derived model instead of the symbol-based
 842                        // constructor lookup, which would otherwise still see only the
 843                        // implicit parameterless constructor.
 265802844                        constructorParams = ConstructorGenerationDiscoveryHelper.TryGetEffectiveConstructorParameters(ty
 265802845                            ?? TypeDiscoveryHelper.GetBestConstructorParametersWithKeys(typeSymbol)?.ToArray() ?? [];
 846                    }
 847
 848                    // Get source file path and line for breadcrumbs (null for external assemblies)
 265807849                    var location = typeSymbol.Locations.FirstOrDefault();
 265807850                    var sourceFilePath = location?.SourceTree?.FilePath;
 265807851                    var sourceLine = location?.GetLineSpan().StartLinePosition.Line + 1 ?? 0; // Convert to 1-based
 852
 853                    // Get [Keyed] attribute keys
 265807854                    var serviceKeys = TypeDiscoveryHelper.GetKeyedServiceKeys(typeSymbol);
 855
 856                    // Check if this type implements IDisposable or IAsyncDisposable
 265807857                    var isDisposable = TypeDiscoveryHelper.IsDisposableType(typeSymbol);
 858
 265807859                    injectableTypes.Add(new DiscoveredType(typeName, interfaceNames, assembly.Name, lifetime.Value, cons
 860
 861                    // Every injectable type is a candidate implementation for [RegisterClosedOverImplementationsOf]
 862                    // expansion. Tying collection to the injectable-registration site means a composition composes
 863                    // over exactly the set Needlr registers — current assembly plus referenced libraries — so
 864                    // cross-assembly definitions are handled the same way decorators expand over injectable types.
 265807865                    composedCandidateTypes.Add(typeSymbol);
 866                }
 867            }
 868
 869            // Check for hosted service types (BackgroundService or IHostedService implementations)
 1661823870            if (TypeDiscoveryHelper.IsHostedServiceType(typeSymbol, isCurrentAssembly))
 871            {
 872                // Use the same field-derived model that drives constructor generation
 873                // when one applies, since the symbol-based lookup can't see the
 874                // sibling generator's constructor output within this compilation.
 10875                var effectiveGeneratedParams = ConstructorGenerationDiscoveryHelper.TryGetEffectiveConstructorParameters
 10876                var isGeneratedButNotInjectable = effectiveGeneratedParams is null &&
 10877                    ConstructorGenerationDiscoveryHelper.TryGetModel(typeSymbol) != null;
 878
 879                // A type eligible for constructor generation whose generated constructor
 880                // isn't fully container-resolvable (e.g. a field-triggered guard on a
 881                // plain string) must not fall back to the symbol-based scan below: that
 882                // scan only sees the implicit parameterless constructor that exists
 883                // before the sibling GeneratedConstructorGenerator pass runs, so it would
 884                // register `services.AddSingleton<T>()` for a hosted service whose real
 885                // (generated) constructor the container can never actually activate.
 886                // Skip automatic hosted registration entirely rather than emit metadata
 887                // — and a registration call — for an unactivatable worker.
 10888                if (!isGeneratedButNotInjectable)
 889                {
 9890                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 9891                    var constructorParams = effectiveGeneratedParams?.ToArray()
 9892                        ?? TypeDiscoveryHelper.GetBestConstructorParametersWithKeys(typeSymbol)?.ToArray() ?? [];
 9893                    var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 894
 9895                    hostedServices.Add(new DiscoveredHostedService(
 9896                        typeName,
 9897                        assembly.Name,
 9898                        GeneratorLifetime.Singleton, // Hosted services are always singleton
 9899                        constructorParams,
 9900                        sourceFilePath));
 901                }
 902            }
 903
 904            // Check for [Provider] attribute
 1661823905            if (ProviderDiscoveryHelper.HasProviderAttribute(typeSymbol))
 906            {
 20907                var discoveredProvider = ProviderDiscoveryHelper.DiscoverProvider(typeSymbol, assembly.Name, generatedNa
 20908                if (discoveredProvider.HasValue)
 909                {
 20910                    providers.Add(discoveredProvider.Value);
 911                }
 912            }
 913
 914            // Check for plugin types (concrete class with parameterless ctor and interfaces)
 1661823915            if (TypeDiscoveryHelper.IsPluginType(typeSymbol, isCurrentAssembly))
 916            {
 462322917                var pluginInterfaces = TypeDiscoveryHelper.GetPluginInterfaces(typeSymbol, compilation.Assembly);
 462322918                if (pluginInterfaces.Count > 0)
 919                {
 1735920                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 3484921                    var interfaceNames = pluginInterfaces.Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i)).ToAr
 1735922                    var attributeNames = TypeDiscoveryHelper.GetPluginAttributes(typeSymbol).ToArray();
 1735923                    var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 1735924                    var order = PluginOrderHelper.GetPluginOrder(typeSymbol);
 925
 1735926                    pluginTypes.Add(new DiscoveredPlugin(typeName, interfaceNames, assembly.Name, attributeNames, source
 927                }
 928            }
 929
 930        }
 101649931    }
 932
 933    private static string GenerateTypeRegistrySource(
 934        GeneratedRegistrationPlan registrationPlan,
 935        BreadcrumbWriter breadcrumbs,
 936        string? projectDirectory)
 937    {
 590938        var builder = new StringBuilder();
 590939        var discoveryResult = registrationPlan.DiscoveryResult;
 590940        var assemblyName = registrationPlan.AssemblyName;
 590941        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblyName);
 590942        var isAotProject = registrationPlan.IsAotProject;
 590943        var hasConfigBoundRegistrations =
 590944            registrationPlan.HasConfigBoundRegistrations;
 945
 590946        breadcrumbs.WriteFileHeader(builder, assemblyName, "Needlr Type Registry");
 590947        builder.AppendLine("#nullable enable");
 590948        builder.AppendLine();
 590949        builder.AppendLine("using System;");
 590950        builder.AppendLine("using System.Collections.Generic;");
 590951        builder.AppendLine();
 590952        if (hasConfigBoundRegistrations)
 953        {
 162954            builder.AppendLine("using Microsoft.Extensions.Configuration;");
 162955            if (isAotProject ||
 162956                registrationPlan.HasHttpClientRegistrations)
 957            {
 89958                builder.AppendLine("using Microsoft.Extensions.Options;");
 959            }
 960        }
 590961        builder.AppendLine("using Microsoft.Extensions.DependencyInjection;");
 590962        builder.AppendLine();
 590963        builder.AppendLine("using NexusLabs.Needlr;");
 590964        builder.AppendLine("using NexusLabs.Needlr.Generators;");
 590965        builder.AppendLine();
 590966        builder.AppendLine($"namespace {safeAssemblyName}.Generated;");
 590967        builder.AppendLine();
 590968        builder.AppendLine("/// <summary>");
 590969        builder.AppendLine("/// Compile-time generated registry of injectable types and plugins.");
 590970        builder.AppendLine("/// This eliminates the need for runtime reflection-based type discovery.");
 590971        builder.AppendLine("/// </summary>");
 590972        builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\", \"1
 590973        builder.AppendLine("public static class TypeRegistry");
 590974        builder.AppendLine("{");
 975
 590976        CodeGen.InjectableTypesCodeGenerator.GenerateInjectableTypesArray(builder, discoveryResult.InjectableTypes, brea
 590977        builder.AppendLine();
 590978        CodeGen.PluginsCodeGenerator.GeneratePluginTypesArray(builder, discoveryResult.PluginTypes, breadcrumbs, project
 979
 590980        builder.AppendLine();
 590981        builder.AppendLine("    /// <summary>");
 590982        builder.AppendLine("    /// Gets all injectable types discovered at compile time.");
 590983        builder.AppendLine("    /// </summary>");
 590984        builder.AppendLine("    /// <returns>A read-only list of injectable type information.</returns>");
 590985        builder.AppendLine("    public static IReadOnlyList<InjectableTypeInfo> GetInjectableTypes() => _types;");
 590986        builder.AppendLine();
 590987        builder.AppendLine("    /// <summary>");
 590988        builder.AppendLine("    /// Gets all plugin types discovered at compile time.");
 590989        builder.AppendLine("    /// </summary>");
 590990        builder.AppendLine("    /// <returns>A read-only list of plugin type information.</returns>");
 590991        builder.AppendLine("    public static IReadOnlyList<PluginTypeInfo> GetPluginTypes() => _plugins;");
 992
 590993        if (hasConfigBoundRegistrations)
 994        {
 162995            builder.AppendLine();
 162996            GenerateRegisterOptionsMethod(builder, discoveryResult.Options, discoveryResult.HttpClients, safeAssemblyNam
 997        }
 998
 590999        if (registrationPlan.HasProviderRegistrations)
 1000        {
 181001            builder.AppendLine();
 181002            CodeGen.DecoratorsCodeGenerator.GenerateRegisterProvidersMethod(
 181003                builder,
 181004                registrationPlan.EmittedProviders,
 181005                safeAssemblyName,
 181006                breadcrumbs,
 181007                projectDirectory);
 1008        }
 1009
 5901010        builder.AppendLine();
 5901011        CodeGen.DecoratorsCodeGenerator.GenerateApplyDecoratorsMethod(builder, discoveryResult.Decorators, discoveryResu
 1012
 5901013        if (discoveryResult.ComposedRegistrations.Count > 0)
 1014        {
 431015            builder.AppendLine();
 431016            CodeGen.ComposedRegistrationsCodeGenerator.GenerateRegisterComposedTypesMethod(builder, discoveryResult.Comp
 1017        }
 1018
 5901019        if (discoveryResult.HostedServices.Count > 0)
 1020        {
 81021            builder.AppendLine();
 81022            CodeGen.DecoratorsCodeGenerator.GenerateRegisterHostedServicesMethod(builder, discoveryResult.HostedServices
 1023        }
 1024
 5901025        builder.AppendLine("}");
 1026
 5901027        return builder.ToString();
 1028    }
 1029
 1030    private static void GenerateRegisterOptionsMethod(StringBuilder builder, IReadOnlyList<DiscoveredOptions> options, I
 1031    {
 1621032        builder.AppendLine("    /// <summary>");
 1621033        builder.AppendLine("    /// Registers all discovered options types with the service collection.");
 1621034        builder.AppendLine("    /// This binds configuration sections to strongly-typed options classes,");
 1621035        builder.AppendLine("    /// and wires up named HttpClient registrations for [HttpClientOptions] types.");
 1621036        builder.AppendLine("    /// </summary>");
 1621037        builder.AppendLine("    /// <param name=\"services\">The service collection to configure.</param>");
 1621038        builder.AppendLine("    /// <param name=\"configuration\">The configuration root to bind options from.</param>")
 1621039        builder.AppendLine("    public static void RegisterOptions(IServiceCollection services, IConfiguration configura
 1621040        builder.AppendLine("    {");
 1041
 1621042        if (options.Count == 0 && httpClients.Count == 0)
 1043        {
 01044            breadcrumbs.WriteInlineComment(builder, "        ", "No options or HttpClient types discovered");
 1045        }
 1046        else
 1047        {
 1621048            if (options.Count > 0)
 1049            {
 1571050                if (isAotProject)
 1051                {
 831052                    CodeGen.OptionsCodeGenerator.GenerateAotOptionsRegistration(builder, options, safeAssemblyName, brea
 1053                }
 1054                else
 1055                {
 741056                    CodeGen.OptionsCodeGenerator.GenerateReflectionOptionsRegistration(builder, options, safeAssemblyNam
 1057                }
 1058            }
 1059
 1621060            if (httpClients.Count > 0)
 1061            {
 61062                CodeGen.HttpClientCodeGenerator.EmitHttpClientRegistrations(builder, httpClients);
 1063            }
 1064        }
 1065
 1621066        builder.AppendLine("    }");
 1621067    }
 1068
 1069}

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)
ShouldEmitRegistrationManifest(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.GeneratedRegistrationPlan,NexusLabs.Needlr.Generators.BreadcrumbWriter,System.String)
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)