< Summary

Information
Class: NexusLabs.Needlr.Generators.AssemblyDiscoveryHelper
Assembly: NexusLabs.Needlr.Generators
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/AssemblyDiscoveryHelper.cs
Line coverage
100%
Covered lines: 114
Uncovered lines: 0
Coverable lines: 114
Total lines: 233
Line coverage: 100%
Branch coverage
94%
Covered branches: 79
Total branches: 84
Branch coverage: 94%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
DiscoverReferencedAssembliesWithTypeRegistry(...)100%88100%
DiscoverReferencedAssemblyTypesForDiagnostics(...)97.05%3434100%
DiscoverReferencedAssemblyTypesForGraph(...)90.47%4242100%

File(s)

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

#LineLine coverage
 1// Copyright (c) NexusLabs. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.Collections.Generic;
 6using System.Linq;
 7
 8using Microsoft.CodeAnalysis;
 9
 10using NexusLabs.Needlr.Generators.Models;
 11
 12namespace NexusLabs.Needlr.Generators;
 13
 14/// <summary>
 15/// Discovers types and assemblies from referenced projects that have
 16/// the <c>[GenerateTypeRegistry]</c> attribute for diagnostics, graph export,
 17/// and force-loading.
 18/// </summary>
 19internal static class AssemblyDiscoveryHelper
 20{
 21    /// <summary>
 22    /// Discovers all referenced assemblies that have the [GenerateTypeRegistry] attribute.
 23    /// These assemblies need to be force-loaded to ensure their module initializers run.
 24    /// </summary>
 25    internal static IReadOnlyList<string> DiscoverReferencedAssembliesWithTypeRegistry(Compilation compilation)
 26    {
 56827        var result = new List<string>();
 28
 19288029        foreach (var reference in compilation.References)
 30        {
 9587231            if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assemblySymbol)
 32            {
 33                // Skip the current assembly
 9564034                if (SymbolEqualityComparer.Default.Equals(assemblySymbol, compilation.Assembly))
 35                    continue;
 36
 9564037                if (TypeDiscoveryHelper.HasGenerateTypeRegistryAttribute(assemblySymbol))
 38                {
 1839                    result.Add(assemblySymbol.Name);
 40                }
 41            }
 42        }
 43
 56844        return result;
 45    }
 46
 47    /// <summary>
 48    /// Discovers types from referenced assemblies with [GenerateTypeRegistry] for diagnostics purposes.
 49    /// Unlike the main discovery, this includes internal types since we're just showing them in diagnostics.
 50    /// </summary>
 51    internal static Dictionary<string, List<DiagnosticTypeInfo>> DiscoverReferencedAssemblyTypesForDiagnostics(Compilati
 52    {
 9553        var result = new Dictionary<string, List<DiagnosticTypeInfo>>();
 54
 3232855        foreach (var reference in compilation.References)
 56        {
 1606957            if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assemblySymbol)
 58            {
 59                // Skip the current assembly
 1606960                if (SymbolEqualityComparer.Default.Equals(assemblySymbol, compilation.Assembly))
 61                    continue;
 62
 1606963                if (!TypeDiscoveryHelper.HasGenerateTypeRegistryAttribute(assemblySymbol))
 64                    continue;
 65
 1466                var assemblyTypes = new List<DiagnosticTypeInfo>();
 67
 68                // First pass: collect intercepted service names so we can identify their proxies
 1469                var interceptedServiceNames = new HashSet<string>();
 12070                foreach (var typeSymbol in TypeDiscoveryHelper.GetAllTypes(assemblySymbol.GlobalNamespace))
 71                {
 4672                    if (InterceptorDiscoveryHelper.HasInterceptAttributes(typeSymbol))
 73                    {
 274                        interceptedServiceNames.Add(typeSymbol.Name);
 75                    }
 76                }
 77
 12078                foreach (var typeSymbol in TypeDiscoveryHelper.GetAllTypes(assemblySymbol.GlobalNamespace))
 79                {
 80                    // Check if it's a registerable type (injectable, plugin, factory source, or interceptor)
 4681                    var hasFactoryAttr = FactoryDiscoveryHelper.HasGenerateFactoryAttribute(typeSymbol);
 4682                    var hasInterceptAttr = InterceptorDiscoveryHelper.HasInterceptAttributes(typeSymbol);
 4683                    var isInterceptorProxy = typeSymbol.Name.EndsWith("_InterceptorProxy");
 84
 4685                    if (!hasFactoryAttr && !hasInterceptAttr && !isInterceptorProxy &&
 4686                        !TypeDiscoveryHelper.WouldBeInjectableIgnoringAccessibility(typeSymbol) &&
 4687                        !TypeDiscoveryHelper.WouldBePluginIgnoringAccessibility(typeSymbol, compilation.Assembly))
 88                        continue;
 89
 1890                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 1891                    var shortName = typeSymbol.Name;
 1892                    var lifetime = TypeDiscoveryHelper.DetermineLifetime(typeSymbol) ?? GeneratorLifetime.Singleton;
 1893                    var interfaces = TypeDiscoveryHelper.GetRegisterableInterfaces(typeSymbol, compilation.Assembly)
 1494                        .Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i))
 1895                        .ToArray();
 1896                    var dependencies = TypeDiscoveryHelper.GetBestConstructorParameters(typeSymbol)?
 1897                        .ToArray() ?? Array.Empty<string>();
 1898                    var isDecorator = TypeDiscoveryHelper.HasDecoratorForAttribute(typeSymbol) ||
 1899                                      OpenDecoratorDiscoveryHelper.HasOpenDecoratorForAttribute(typeSymbol);
 18100                    var isPlugin = TypeDiscoveryHelper.WouldBePluginIgnoringAccessibility(typeSymbol, compilation.Assemb
 18101                    var keyedValues = TypeDiscoveryHelper.GetKeyedServiceKeys(typeSymbol);
 18102                    var keyedValue = keyedValues.Length > 0 ? keyedValues[0] : null;
 103
 104                    // Check if this service has an interceptor proxy (its name + "_InterceptorProxy" exists)
 18105                    var hasInterceptorProxy = interceptedServiceNames.Contains(shortName);
 106
 18107                    assemblyTypes.Add(new DiagnosticTypeInfo(
 18108                        typeName,
 18109                        shortName,
 18110                        lifetime,
 18111                        interfaces,
 18112                        dependencies,
 18113                        isDecorator,
 18114                        isPlugin,
 18115                        hasFactoryAttr,
 18116                        keyedValue,
 18117                        isInterceptor: hasInterceptAttr,
 18118                        hasInterceptorProxy: hasInterceptorProxy));
 119                }
 120
 14121                if (assemblyTypes.Count > 0)
 122                {
 14123                    result[assemblySymbol.Name] = assemblyTypes;
 124                }
 125            }
 126        }
 127
 95128        return result;
 129    }
 130
 131    /// <summary>
 132    /// Discovers types from referenced assemblies with [GenerateTypeRegistry] for graph export.
 133    /// Unlike the main discovery, this includes internal types since they are registered by their own TypeRegistry.
 134    /// Returns DiscoveredType objects that can be included in the graph export.
 135    /// </summary>
 136    internal static Dictionary<string, IReadOnlyList<DiscoveredType>> DiscoverReferencedAssemblyTypesForGraph(Compilatio
 137    {
 7138        var result = new Dictionary<string, IReadOnlyList<DiscoveredType>>();
 139
 2384140        foreach (var reference in compilation.References)
 141        {
 1185142            if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assemblySymbol)
 143            {
 144                // Skip the current assembly
 1185145                if (SymbolEqualityComparer.Default.Equals(assemblySymbol, compilation.Assembly))
 146                    continue;
 147
 1185148                if (!TypeDiscoveryHelper.HasGenerateTypeRegistryAttribute(assemblySymbol))
 149                    continue;
 150
 2151                var assemblyTypes = new List<DiscoveredType>();
 152
 18153                foreach (var typeSymbol in TypeDiscoveryHelper.GetAllTypes(assemblySymbol.GlobalNamespace))
 154                {
 155                    // Check if it's a registerable type
 7156                    var hasFactoryAttr = FactoryDiscoveryHelper.HasGenerateFactoryAttribute(typeSymbol);
 157
 158                    // Skip types that are only factories (handled separately)
 7159                    if (hasFactoryAttr)
 160                        continue;
 161
 7162                    if (!TypeDiscoveryHelper.WouldBeInjectableIgnoringAccessibility(typeSymbol) &&
 7163                        !TypeDiscoveryHelper.WouldBePluginIgnoringAccessibility(typeSymbol, compilation.Assembly))
 164                        continue;
 165
 166                    // Skip decorators - they modify other services, not registered directly as services
 2167                    if (TypeDiscoveryHelper.HasDecoratorForAttribute(typeSymbol) ||
 2168                        OpenDecoratorDiscoveryHelper.HasOpenDecoratorForAttribute(typeSymbol))
 169                        continue;
 170
 2171                    var typeName = TypeDiscoveryHelper.GetFullyQualifiedName(typeSymbol);
 2172                    var interfaceSymbols = TypeDiscoveryHelper.GetRegisterableInterfaces(typeSymbol, compilation.Assembl
 2173                    var interfaces = interfaceSymbols
 2174                        .Select(i => TypeDiscoveryHelper.GetFullyQualifiedName(i))
 2175                        .ToArray();
 176
 177                    // Source/CompilationReference symbols retain locations. PE metadata
 178                    // symbols deliberately produce null locations for IDE graph merging.
 2179                    var interfaceInfos = interfaceSymbols.Select(i =>
 2180                    {
 2181                        var ifaceFullName = TypeDiscoveryHelper.GetFullyQualifiedName(i);
 2182                        var ifaceLocation = i.Locations.FirstOrDefault(
 2183                            location =>
 4184                                location.IsInSource &&
 4185                                location.SourceTree is not null);
 2186                        var ifaceFilePath = ifaceLocation?.SourceTree?.FilePath;
 2187                        var ifaceLine = ifaceLocation is null
 2188                            ? 0
 2189                            : ifaceLocation.GetLineSpan()
 2190                                .StartLinePosition.Line + 1;
 2191                        return new InterfaceInfo(ifaceFullName, ifaceFilePath, ifaceLine);
 2192                    }).ToArray();
 193
 2194                    var lifetime = TypeDiscoveryHelper.DetermineLifetime(typeSymbol) ?? GeneratorLifetime.Singleton;
 2195                    var constructorParams = TypeDiscoveryHelper.GetBestConstructorParametersWithKeys(typeSymbol)?.ToArra
 2196                        ?? Array.Empty<TypeDiscoveryHelper.ConstructorParameterInfo>();
 2197                    var keyedValues = TypeDiscoveryHelper.GetKeyedServiceKeys(typeSymbol);
 2198                    var sourceLocation = typeSymbol.Locations.FirstOrDefault(
 2199                        location =>
 4200                            location.IsInSource &&
 4201                            location.SourceTree is not null);
 2202                    var sourceFilePath =
 2203                        sourceLocation?.SourceTree?.FilePath;
 2204                    var sourceLine = sourceLocation is null
 2205                        ? 0
 2206                        : sourceLocation.GetLineSpan()
 2207                            .StartLinePosition.Line + 1;
 208
 2209                    var discoveredType = new DiscoveredType(
 2210                        typeName,
 2211                        interfaces,
 2212                        assemblySymbol.Name,
 2213                        lifetime,
 2214                        constructorParams,
 2215                        keyedValues,
 2216                        sourceFilePath,
 2217                        sourceLine,
 2218                        TypeDiscoveryHelper.IsDisposableType(typeSymbol),
 2219                        interfaceInfos);
 220
 2221                    assemblyTypes.Add(discoveredType);
 222                }
 223
 2224                if (assemblyTypes.Count > 0)
 225                {
 2226                    result[assemblySymbol.Name] = assemblyTypes;
 227                }
 228            }
 229        }
 230
 7231        return result;
 232    }
 233}