< 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: 594
Uncovered lines: 7
Coverable lines: 601
Total lines: 974
Line coverage: 98.8%
Branch coverage
90%
Covered branches: 270
Total branches: 300
Branch coverage: 90%
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%
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
 59021        var compilationAndOptions = context.CompilationProvider
 59022            .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].
 59027        context.RegisterSourceOutput(compilationAndOptions, static (spc, source) =>
 59028        {
 59029            var (compilation, configOptions) = source;
 59030
 59031            var attributeInfo = GetAttributeInfoFromCompilation(compilation);
 59032            if (attributeInfo == null)
 133                return;
 59034
 58935            var info = attributeInfo.Value;
 58936            var assemblyName = compilation.AssemblyName ?? "Generated";
 59037
 59038            // Read breadcrumb level from MSBuild property
 58939            var breadcrumbLevel = GetBreadcrumbLevel(configOptions);
 58940            var projectDirectory = GetProjectDirectory(configOptions);
 58941            var breadcrumbs = new BreadcrumbWriter(breadcrumbLevel);
 59042
 59043            // Check if this is an AOT project
 58944            var isAotProject = IsAotProject(configOptions);
 59045
 58946            var discoveryResult = DiscoverTypes(
 58947                compilation,
 58948                info.NamespacePrefixes,
 58949                info.ExcludeNamespacePrefixes,
 58950                info.IncludeSelf);
 59051
 59052            // Discover referenced assemblies with [GenerateTypeRegistry] for forced loading.
 59053            // Done early so the empty-result check below can include this in its decision.
 59054            // Note: Order of force-loading doesn't matter; ordering is applied at service registration time
 58955            var referencedAssemblies = AssemblyDiscoveryHelper.DiscoverReferencedAssembliesWithTypeRegistry(compilation)
 256                .OrderBy(a => a, StringComparer.OrdinalIgnoreCase)
 58957                .ToList();
 59058
 59059            // Nothing was discovered: no injectable types, factories, providers, options,
 59060            // interceptors, hosted services, plugins, no referenced assemblies to force-load,
 59061            // no inaccessible type errors, and no missing TypeRegistry warnings.
 58962            var nothingDiscovered =
 58963                discoveryResult.InjectableTypes.Count == 0 &&
 58964                discoveryResult.PluginTypes.Count == 0 &&
 58965                discoveryResult.Decorators.Count == 0 &&
 58966                discoveryResult.InterceptedServices.Count == 0 &&
 58967                discoveryResult.Factories.Count == 0 &&
 58968                discoveryResult.Options.Count == 0 &&
 58969                discoveryResult.HttpClients.Count == 0 &&
 58970                discoveryResult.HostedServices.Count == 0 &&
 58971                discoveryResult.Providers.Count == 0 &&
 58972                discoveryResult.ComposedRegistrations.Count == 0 &&
 58973                discoveryResult.InaccessibleTypes.Count == 0 &&
 58974                discoveryResult.MissingTypeRegistryPlugins.Count == 0 &&
 58975                referencedAssemblies.Count == 0;
 59076
 59077            // A type-less assembly that still carries [GenerateTypeRegistry] (guaranteed here by the
 59078            // attributeInfo guard above) is a declared Needlr participant. Consumers force-load
 59079            // typeof({Assembly}.Generated.TypeRegistry) for every attribute-carrying referenced
 59080            // assembly, so emitting nothing makes those consumers fail to compile with CS0234. Emit
 59081            // a minimal registry instead. It depends only on the attributes package (never the
 59082            // injection packages), so it compiles whether or not this assembly references them — a
 59083            // domain, contracts, or documentation-only project participates without being forced to
 59084            // take a dependency it would not otherwise have.
 58985            if (nothingDiscovered)
 59086            {
 987                var emptyRegistrySource = CodeGen.EmptyTypeRegistryCodeGenerator.GenerateTypeRegistrySource(assemblyName
 988                spc.AddSource("TypeRegistry.g.cs", GeneratedSourceText.Create(emptyRegistrySource));
 59089
 990                var emptyBootstrapSource = CodeGen.EmptyTypeRegistryCodeGenerator.GenerateBootstrapSource(assemblyName, 
 991                spc.AddSource("NeedlrSourceGenBootstrap.g.cs", GeneratedSourceText.Create(emptyBootstrapSource));
 992                return;
 59093            }
 59094
 59095            // Report errors for inaccessible internal types in referenced assemblies
 643496            foreach (var inaccessibleType in discoveryResult.InaccessibleTypes)
 59097            {
 263798                spc.ReportDiagnostic(Diagnostic.Create(
 263799                    DiagnosticDescriptors.InaccessibleInternalType,
 2637100                    Location.None,
 2637101                    inaccessibleType.TypeName,
 2637102                    inaccessibleType.AssemblyName));
 590103            }
 590104
 590105            // Report errors for referenced assemblies with internal plugin types but no [GenerateTypeRegistry]
 1162106            foreach (var missingPlugin in discoveryResult.MissingTypeRegistryPlugins)
 590107            {
 1108                spc.ReportDiagnostic(Diagnostic.Create(
 1109                    DiagnosticDescriptors.MissingGenerateTypeRegistryAttribute,
 1110                    Location.None,
 1111                    missingPlugin.AssemblyName,
 1112                    missingPlugin.TypeName));
 590113            }
 590114
 590115            // NDLRGEN020: Previously reported error if [Options] used in AOT project
 590116            // Now removed for parity - we generate best-effort code and let unsupported
 590117            // types fail at runtime (matching non-AOT ConfigurationBinder behavior)
 590118
 590119            // NDLRGEN021: Report warning for non-partial positional records
 1338120            foreach (var opt in discoveryResult.Options.Where(o => o.IsNonPartialPositionalRecord))
 590121            {
 2122                spc.ReportDiagnostic(Diagnostic.Create(
 2123                    DiagnosticDescriptors.PositionalRecordMustBePartial,
 2124                    Location.None,
 2125                    opt.TypeName));
 590126            }
 590127
 590128            // NDLRGEN022: Detect disposable captive dependencies using inferred lifetimes
 580129            CaptiveDependencyAnalyzer.ReportDisposableCaptiveDependencies(spc, discoveryResult);
 590130
 590131            // NDLRGEN038: Report skipped composition registrations whose discovered type argument(s)
 590132            // violate the composition's generic constraints.
 1228133            foreach (var violation in discoveryResult.ComposedConstraintViolations)
 590134            {
 34135                spc.ReportDiagnostic(Diagnostic.Create(
 34136                    DiagnosticDescriptors.ComposedTypeArgumentViolatesConstraints,
 34137                    Location.None,
 34138                    violation.CompositionTypeName,
 34139                    violation.TypeArgumentName,
 34140                    violation.SourceInterfaceName));
 590141            }
 590142
 580143            var sourceText = GenerateTypeRegistrySource(discoveryResult, assemblyName, breadcrumbs, projectDirectory, is
 580144            spc.AddSource("TypeRegistry.g.cs", GeneratedSourceText.Create(sourceText));
 590145
 580146            var bootstrapText = CodeGen.BootstrapCodeGenerator.GenerateModuleInitializerBootstrapSource(assemblyName, re
 580147            spc.AddSource("NeedlrSourceGenBootstrap.g.cs", GeneratedSourceText.Create(bootstrapText));
 590148
 590149            // Generate interceptor proxy classes if any were discovered
 580150            if (discoveryResult.InterceptedServices.Count > 0)
 590151            {
 14152                var interceptorProxiesText = CodeGen.InterceptorCodeGenerator.GenerateInterceptorProxiesSource(discovery
 14153                spc.AddSource("InterceptorProxies.g.cs", GeneratedSourceText.Create(interceptorProxiesText));
 590154            }
 590155
 590156            // Generate factory classes if any were discovered
 580157            if (discoveryResult.Factories.Count > 0)
 590158            {
 38159                var factoriesText = CodeGen.FactoryCodeGenerator.GenerateFactoriesSource(discoveryResult.Factories, asse
 38160                spc.AddSource("Factories.g.cs", GeneratedSourceText.Create(factoriesText));
 590161            }
 590162
 590163            // Generate provider classes if any were discovered
 580164            if (discoveryResult.Providers.Count > 0)
 590165            {
 590166                // Interface-based providers go in the Generated namespace
 35167                var interfaceProviders = discoveryResult.Providers.Where(p => p.IsInterface).ToList();
 17168                if (interfaceProviders.Count > 0)
 590169                {
 11170                    var providersText = CodeGen.ProviderCodeGenerator.GenerateProvidersSource(interfaceProviders, assemb
 11171                    spc.AddSource("Providers.g.cs", GeneratedSourceText.Create(providersText));
 590172                }
 590173
 590174                // 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)
 590177                {
 6178                    var providerText = CodeGen.ProviderCodeGenerator.GenerateShorthandProviderSource(provider, assemblyN
 6179                    spc.AddSource($"Provider.{provider.SimpleTypeName}.g.cs", GeneratedSourceText.Create(providerText));
 590180                }
 590181            }
 590182
 590183            // Generate options validator classes if any have validation methods
 754184            var optionsWithValidators = discoveryResult.Options.Where(o => o.HasValidatorMethod).ToList();
 580185            if (optionsWithValidators.Count > 0)
 590186            {
 22187                var validatorsText = CodeGen.OptionsCodeGenerator.GenerateOptionsValidatorsSource(optionsWithValidators,
 22188                spc.AddSource("OptionsValidators.g.cs", GeneratedSourceText.Create(validatorsText));
 590189            }
 590190
 590191            // Generate DataAnnotations validator classes if any have DataAnnotation attributes
 754192            var optionsWithDataAnnotations = discoveryResult.Options.Where(o => o.HasDataAnnotations).ToList();
 580193            if (optionsWithDataAnnotations.Count > 0)
 590194            {
 18195                var dataAnnotationsValidatorsText = CodeGen.OptionsCodeGenerator.GenerateDataAnnotationsValidatorsSource
 18196                spc.AddSource("OptionsDataAnnotationsValidators.g.cs", GeneratedSourceText.Create(dataAnnotationsValidat
 590197            }
 590198
 590199            // Generate parameterless constructors for partial positional records with [Options]
 754200            var optionsNeedingConstructors = discoveryResult.Options.Where(o => o.NeedsGeneratedConstructor).ToList();
 580201            if (optionsNeedingConstructors.Count > 0)
 590202            {
 12203                var constructorsText = CodeGen.OptionsCodeGenerator.GeneratePositionalRecordConstructorsSource(optionsNe
 12204                spc.AddSource("OptionsConstructors.g.cs", GeneratedSourceText.Create(constructorsText));
 590205            }
 590206
 590207            // Generate ServiceCatalog for runtime introspection
 580208            var catalogText = CodeGen.ServiceCatalogCodeGenerator.GenerateServiceCatalogSource(discoveryResult, assembly
 580209            spc.AddSource("ServiceCatalog.g.cs", GeneratedSourceText.Create(catalogText));
 590210
 590211            // Generate diagnostic output files if configured
 580212            var diagnosticOptions = GetDiagnosticOptions(configOptions);
 580213            if (diagnosticOptions.Enabled)
 590214            {
 109215                var referencedAssemblyTypes = AssemblyDiscoveryHelper.DiscoverReferencedAssemblyTypesForDiagnostics(comp
 109216                var diagnosticsText = DiagnosticsGenerator.GenerateDiagnosticsSource(discoveryResult, assemblyName, proj
 109217                spc.AddSource("NeedlrDiagnostics.g.cs", GeneratedSourceText.Create(diagnosticsText));
 590218            }
 590219
 590220            // Generate IDE graph export if configured
 580221            if (ShouldExportGraph(configOptions))
 590222            {
 590223                // Discover types from referenced assemblies with [GenerateTypeRegistry] for graph inclusion
 19224                var referencedAssemblyTypesForGraph = AssemblyDiscoveryHelper.DiscoverReferencedAssemblyTypesForGraph(co
 590225
 19226                var graphJson = Export.GraphExporter.GenerateGraphJson(
 19227                    discoveryResult,
 19228                    assemblyName,
 19229                    projectDirectory,
 19230                    diagnostics: null,
 19231                    referencedAssemblyTypes: referencedAssemblyTypesForGraph);
 590232
 590233                // Embed graph as a comment in a generated file so it's accessible
 590234                // The actual JSON is written to obj folder via the generated code
 19235                var graphSourceText = Export.GraphExporter.GenerateGraphExportSource(graphJson, assemblyName, breadcrumb
 19236                spc.AddSource("NeedlrGraph.g.cs", GeneratedSourceText.Create(graphSourceText));
 590237            }
 1170238        });
 590239    }
 240
 241    internal static BreadcrumbLevel GetBreadcrumbLevel(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvider 
 242    {
 704243        if (configOptions.GlobalOptions.TryGetValue("build_property.NeedlrBreadcrumbLevel", out var levelStr) &&
 704244            !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
 659253        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
 589259        if (configOptions.GlobalOptions.TryGetValue("build_property.ProjectDir", out var projectDir) &&
 589260            !string.IsNullOrWhiteSpace(projectDir))
 261        {
 4262            return projectDir.TrimEnd('/', '\\');
 263        }
 264
 585265        return null;
 266    }
 267
 268    private static DiagnosticOptions GetDiagnosticOptions(Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptionsProvid
 269    {
 580270        configOptions.GlobalOptions.TryGetValue("build_property.NeedlrDiagnostics", out var enabled);
 580271        configOptions.GlobalOptions.TryGetValue("build_property.NeedlrDiagnosticsPath", out var outputPath);
 580272        configOptions.GlobalOptions.TryGetValue("build_property.NeedlrDiagnosticsFilter", out var filter);
 273
 580274        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
 580284        if (configOptions.GlobalOptions.TryGetValue("build_property.NeedlrExportGraph", out var exportGraph) &&
 580285            exportGraph.Equals("true", StringComparison.OrdinalIgnoreCase))
 286        {
 19287            return true;
 288        }
 561289        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    {
 589298        if (configOptions.GlobalOptions.TryGetValue("build_property.PublishAot", out var publishAot) &&
 589299            publishAot.Equals("true", StringComparison.OrdinalIgnoreCase))
 300        {
 83301            return true;
 302        }
 303
 506304        if (configOptions.GlobalOptions.TryGetValue("build_property.IsAotCompatible", out var isAotCompatible) &&
 506305            isAotCompatible.Equals("true", StringComparison.OrdinalIgnoreCase))
 306        {
 1307            return true;
 308        }
 309
 505310        return false;
 311    }
 312
 313    private static AttributeInfo? GetAttributeInfoFromCompilation(Compilation compilation)
 314    {
 315        // Get assembly-level attributes directly from the compilation
 1769316        foreach (var attribute in compilation.Assembly.GetAttributes())
 317        {
 589318            var attrClassName = attribute.AttributeClass?.ToDisplayString();
 319
 320            // Check if this is our attribute (various name format possibilities)
 589321            if (attrClassName != GenerateTypeRegistryAttributeName)
 322                continue;
 323
 589324            string[]? namespacePrefixes = null;
 589325            string[]? excludeNamespacePrefixes = null;
 589326            var includeSelf = true;
 327
 1498328            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
 589361            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    {
 589373        var injectableTypes = new List<DiscoveredType>();
 589374        var pluginTypes = new List<DiscoveredPlugin>();
 589375        var decorators = new List<DiscoveredDecorator>();
 589376        var openDecorators = new List<DiscoveredOpenDecorator>();
 589377        var composedMarkers = new List<DiscoveredComposedMarker>();
 589378        var composedCandidateTypes = new List<INamedTypeSymbol>();
 589379        var interceptedServices = new List<DiscoveredInterceptedService>();
 589380        var factories = new List<DiscoveredFactory>();
 589381        var options = new List<DiscoveredOptions>();
 589382        var hostedServices = new List<DiscoveredHostedService>();
 589383        var providers = new List<DiscoveredProvider>();
 589384        var httpClients = new List<DiscoveredHttpClient>();
 589385        var inaccessibleTypes = new List<InaccessibleType>();
 589386        var prefixList = namespacePrefixes?.ToList();
 589387        var excludePrefixList = excludeNamespacePrefixes?.ToList();
 388
 389        // Compute the generated namespace for the current assembly
 589390        var currentAssemblyName = compilation.Assembly.Name;
 589391        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(currentAssemblyName);
 589392        var generatedNamespace = $"{safeAssemblyName}.Generated";
 393
 394        // Collect types from the current compilation if includeSelf is true
 589395        if (includeSelf)
 396        {
 588397            CollectTypesFromAssembly(compilation.Assembly, prefixList, excludePrefixList, injectableTypes, pluginTypes, 
 398        }
 399
 400        // Collect types from all referenced assemblies
 200050401        foreach (var reference in compilation.References)
 402        {
 99436403            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.
 99189409                if (TypeDiscoveryHelper.HasGenerateTypeRegistryAttribute(assemblySymbol))
 410                    continue;
 411
 412                // For referenced assemblies, they use their own generated namespace
 99171413                var refSafeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblySymbol.Name);
 99171414                var refGeneratedNamespace = $"{refSafeAssemblyName}.Generated";
 99171415                CollectTypesFromAssembly(assemblySymbol, prefixList, excludePrefixList, injectableTypes, pluginTypes, de
 416            }
 417        }
 418
 419        // Expand open generic decorators into closed decorator registrations
 589420        if (openDecorators.Count > 0)
 421        {
 6422            CodeGen.DecoratorsCodeGenerator.ExpandOpenDecorators(injectableTypes, openDecorators, decorators);
 423        }
 424
 425        // Expand [RegisterClosedOverImplementationsOf] markers into closed composition registrations.
 589426        var composedRegistrations = new List<DiscoveredComposedRegistration>();
 589427        var composedConstraintViolations = new List<ComposedConstraintViolation>();
 589428        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)
 589438        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]
 589444        var missingTypeRegistryPlugins = new List<MissingTypeRegistryPlugin>();
 200050445        foreach (var reference in compilation.References)
 446        {
 99436447            if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assemblySymbol)
 448            {
 449                // Skip assemblies that already have [GenerateTypeRegistry]
 99189450                if (TypeDiscoveryHelper.HasGenerateTypeRegistryAttribute(assemblySymbol))
 451                    continue;
 452
 453                // Look for internal types that implement Needlr plugin interfaces
 4865964454                foreach (var typeSymbol in TypeDiscoveryHelper.GetAllTypes(assemblySymbol.GlobalNamespace))
 455                {
 2333811456                    if (!TypeDiscoveryHelper.IsInternalOrLessAccessible(typeSymbol))
 457                        continue;
 458
 110646459                    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
 589469        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    {
 4871630493        foreach (var typeSymbol in TypeDiscoveryHelper.GetAllTypes(assembly.GlobalNamespace))
 494        {
 2336056495            if (!TypeDiscoveryHelper.MatchesNamespacePrefix(typeSymbol, namespacePrefixes))
 496                continue;
 497
 1745268498            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.
 1745268504            if (TypeDiscoveryHelper.IsMauiPlatformEntryType(typeSymbol))
 505                continue;
 506
 507            // For referenced assemblies, check if the type would be registerable but is inaccessible
 1745264508            if (!isCurrentAssembly && TypeDiscoveryHelper.IsInternalOrLessAccessible(typeSymbol))
 509            {
 510                // Check if this type would have been registered if it were accessible
 83437511                if (TypeDiscoveryHelper.WouldBeInjectableIgnoringAccessibility(typeSymbol) ||
 83437512                    TypeDiscoveryHelper.WouldBePluginIgnoringAccessibility(typeSymbol, compilation.Assembly))
 513                {
 2637514                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 2637515                    inaccessibleTypes.Add(new InaccessibleType(typeName, assembly.Name));
 516                }
 2637517                continue; // Skip further processing for inaccessible types
 518            }
 519
 520            // Check for [Options] attribute
 1661827521            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
 1661827577            var httpAttrInfo =
 1661827578                HttpClientOptionsAttributeHelper.GetHttpClientOptionsAttribute(
 1661827579                    typeSymbol);
 1661827580            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
 1661827621            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
 1661789649            var decoratorInfos = TypeDiscoveryHelper.GetDecoratorForAttributes(typeSymbol);
 3323656650            foreach (var decoratorInfo in decoratorInfos)
 651            {
 39652                var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 39653                decorators.Add(new DiscoveredDecorator(
 39654                    decoratorInfo.DecoratorTypeName,
 39655                    decoratorInfo.ServiceTypeName,
 39656                    decoratorInfo.Order,
 39657                    assembly.Name,
 39658                    sourceFilePath));
 659            }
 660
 661            // Check for OpenDecoratorFor attributes (source-gen only open generic decorators)
 1661789662            var openDecoratorInfos = OpenDecoratorDiscoveryHelper.GetOpenDecoratorForAttributes(typeSymbol);
 3323592663            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)
 1661789675            var composedMarkerInfos = ComposedRegistrationDiscoveryHelper.GetComposedMarkers(typeSymbol);
 3323714676            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
 1661789689            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)
 1661789726            if (TypeDiscoveryHelper.IsInjectableType(typeSymbol, isCurrentAssembly) && !ProviderDiscoveryHelper.HasProvi
 727            {
 728                // Determine lifetime first - only include types that are actually injectable
 466218729                var lifetime = TypeDiscoveryHelper.DetermineLifetime(typeSymbol);
 466218730                if (lifetime.HasValue)
 731                {
 265793732                    var interfaces = TypeDiscoveryHelper.GetRegisterableInterfaces(typeSymbol, compilation.Assembly);
 265793733                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 266208734                    var interfaceNames = interfaces.Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i)).ToArray();
 735
 736                    // Capture interface locations for navigation
 265793737                    var interfaceInfos = interfaces.Select(i =>
 265793738                    {
 415739                        var ifaceLocation = i.Locations.FirstOrDefault();
 415740                        var ifaceFilePath = ifaceLocation?.SourceTree?.FilePath;
 415741                        var ifaceLine = ifaceLocation?.GetLineSpan().StartLinePosition.Line + 1 ?? 0;
 415742                        return new InterfaceInfo(TypeDiscoveryHelper.GetFullyQualifiedName(i), ifaceFilePath, ifaceLine)
 265793743                    }).ToArray();
 744
 745                    // Check for [DeferToContainer] attribute - use declared types instead of discovered constructors
 265793746                    var deferredParams = TypeDiscoveryHelper.GetDeferToContainerParameterTypes(typeSymbol);
 747                    TypeDiscoveryHelper.ConstructorParameterInfo[] constructorParams;
 265793748                    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.
 265788760                        constructorParams = ConstructorGenerationDiscoveryHelper.TryGetEffectiveConstructorParameters(ty
 265788761                            ?? TypeDiscoveryHelper.GetBestConstructorParametersWithKeys(typeSymbol)?.ToArray() ?? [];
 762                    }
 763
 764                    // Get source file path and line for breadcrumbs (null for external assemblies)
 265793765                    var location = typeSymbol.Locations.FirstOrDefault();
 265793766                    var sourceFilePath = location?.SourceTree?.FilePath;
 265793767                    var sourceLine = location?.GetLineSpan().StartLinePosition.Line + 1 ?? 0; // Convert to 1-based
 768
 769                    // Get [Keyed] attribute keys
 265793770                    var serviceKeys = TypeDiscoveryHelper.GetKeyedServiceKeys(typeSymbol);
 771
 772                    // Check if this type implements IDisposable or IAsyncDisposable
 265793773                    var isDisposable = TypeDiscoveryHelper.IsDisposableType(typeSymbol);
 774
 265793775                    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.
 265793781                    composedCandidateTypes.Add(typeSymbol);
 782                }
 783            }
 784
 785            // Check for hosted service types (BackgroundService or IHostedService implementations)
 1661789786            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
 1661789821            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)
 1661789831            if (TypeDiscoveryHelper.IsPluginType(typeSymbol, isCurrentAssembly))
 832            {
 462305833                var pluginInterfaces = TypeDiscoveryHelper.GetPluginInterfaces(typeSymbol, compilation.Assembly);
 462305834                if (pluginInterfaces.Count > 0)
 835                {
 1727836                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 3466837                    var interfaceNames = pluginInterfaces.Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i)).ToAr
 1727838                    var attributeNames = TypeDiscoveryHelper.GetPluginAttributes(typeSymbol).ToArray();
 1727839                    var sourceFilePath = typeSymbol.Locations.FirstOrDefault()?.SourceTree?.FilePath;
 1727840                    var order = PluginOrderHelper.GetPluginOrder(typeSymbol);
 841
 1727842                    pluginTypes.Add(new DiscoveredPlugin(typeName, interfaceNames, assembly.Name, attributeNames, source
 843                }
 844            }
 845
 846        }
 99759847    }
 848
 849    private static string GenerateTypeRegistrySource(DiscoveryResult discoveryResult, string assemblyName, BreadcrumbWri
 850    {
 580851        var builder = new StringBuilder();
 580852        var safeAssemblyName = GeneratorHelpers.SanitizeIdentifier(assemblyName);
 580853        var hasOptions = discoveryResult.Options.Count > 0;
 580854        var hasHttpClients = discoveryResult.HttpClients.Count > 0;
 580855        var hasConfigBoundRegistrations = hasOptions || hasHttpClients;
 856
 580857        breadcrumbs.WriteFileHeader(builder, assemblyName, "Needlr Type Registry");
 580858        builder.AppendLine("#nullable enable");
 580859        builder.AppendLine();
 580860        builder.AppendLine("using System;");
 580861        builder.AppendLine("using System.Collections.Generic;");
 580862        builder.AppendLine();
 580863        if (hasConfigBoundRegistrations)
 864        {
 160865            builder.AppendLine("using Microsoft.Extensions.Configuration;");
 160866            if (isAotProject || hasHttpClients)
 867            {
 87868                builder.AppendLine("using Microsoft.Extensions.Options;");
 869            }
 870        }
 580871        builder.AppendLine("using Microsoft.Extensions.DependencyInjection;");
 580872        builder.AppendLine();
 580873        builder.AppendLine("using NexusLabs.Needlr;");
 580874        builder.AppendLine("using NexusLabs.Needlr.Generators;");
 580875        builder.AppendLine();
 580876        builder.AppendLine($"namespace {safeAssemblyName}.Generated;");
 580877        builder.AppendLine();
 580878        builder.AppendLine("/// <summary>");
 580879        builder.AppendLine("/// Compile-time generated registry of injectable types and plugins.");
 580880        builder.AppendLine("/// This eliminates the need for runtime reflection-based type discovery.");
 580881        builder.AppendLine("/// </summary>");
 580882        builder.AppendLine("[global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"NexusLabs.Needlr.Generators\", \"1
 580883        builder.AppendLine("public static class TypeRegistry");
 580884        builder.AppendLine("{");
 885
 580886        CodeGen.InjectableTypesCodeGenerator.GenerateInjectableTypesArray(builder, discoveryResult.InjectableTypes, brea
 580887        builder.AppendLine();
 580888        CodeGen.PluginsCodeGenerator.GeneratePluginTypesArray(builder, discoveryResult.PluginTypes, breadcrumbs, project
 889
 580890        builder.AppendLine();
 580891        builder.AppendLine("    /// <summary>");
 580892        builder.AppendLine("    /// Gets all injectable types discovered at compile time.");
 580893        builder.AppendLine("    /// </summary>");
 580894        builder.AppendLine("    /// <returns>A read-only list of injectable type information.</returns>");
 580895        builder.AppendLine("    public static IReadOnlyList<InjectableTypeInfo> GetInjectableTypes() => _types;");
 580896        builder.AppendLine();
 580897        builder.AppendLine("    /// <summary>");
 580898        builder.AppendLine("    /// Gets all plugin types discovered at compile time.");
 580899        builder.AppendLine("    /// </summary>");
 580900        builder.AppendLine("    /// <returns>A read-only list of plugin type information.</returns>");
 580901        builder.AppendLine("    public static IReadOnlyList<PluginTypeInfo> GetPluginTypes() => _plugins;");
 902
 580903        if (hasConfigBoundRegistrations)
 904        {
 160905            builder.AppendLine();
 160906            GenerateRegisterOptionsMethod(builder, discoveryResult.Options, discoveryResult.HttpClients, safeAssemblyNam
 907        }
 908
 580909        if (discoveryResult.Providers.Count > 0)
 910        {
 17911            builder.AppendLine();
 17912            CodeGen.DecoratorsCodeGenerator.GenerateRegisterProvidersMethod(builder, discoveryResult.Providers, safeAsse
 913        }
 914
 580915        builder.AppendLine();
 580916        CodeGen.DecoratorsCodeGenerator.GenerateApplyDecoratorsMethod(builder, discoveryResult.Decorators, discoveryResu
 917
 580918        if (discoveryResult.ComposedRegistrations.Count > 0)
 919        {
 42920            builder.AppendLine();
 42921            CodeGen.ComposedRegistrationsCodeGenerator.GenerateRegisterComposedTypesMethod(builder, discoveryResult.Comp
 922        }
 923
 580924        if (discoveryResult.HostedServices.Count > 0)
 925        {
 7926            builder.AppendLine();
 7927            CodeGen.DecoratorsCodeGenerator.GenerateRegisterHostedServicesMethod(builder, discoveryResult.HostedServices
 928        }
 929
 580930        builder.AppendLine("}");
 931
 580932        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)