< Summary

Information
Class: NexusLabs.Needlr.Roslyn.Shared.TypeDiscoveryHelper
Assembly: NexusLabs.Needlr.Generators
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Roslyn.Shared/TypeDiscoveryHelper.cs
Line coverage
91%
Covered lines: 93
Uncovered lines: 9
Coverable lines: 102
Total lines: 266
Line coverage: 91.1%
Branch coverage
86%
Covered branches: 104
Total branches: 120
Branch coverage: 86.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
IsInjectableType(...)88.46%272688.88%
HasUnsatisfiedRequiredMembers(...)92.3%2626100%
HasDoNotAutoRegisterAttribute(...)100%66100%
HasDoNotAutoRegisterAttributeDirect(...)83.33%121290%
IsCompilerGenerated(...)91.66%1212100%
InheritsFrom(...)100%44100%
IsAccessibleFromGeneratedCode(...)66.66%281868.75%
IsSystemType(...)87.5%1616100%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Roslyn.Shared/TypeDiscoveryHelper.cs

#LineLine coverage
 1using Microsoft.CodeAnalysis;
 2
 3namespace NexusLabs.Needlr.Roslyn.Shared;
 4
 5/// <summary>
 6/// Shared helper utilities for discovering injectable types from Roslyn symbols.
 7/// Used by both Generators and Analyzers to ensure consistent type discovery logic.
 8/// </summary>
 9public static class TypeDiscoveryHelper
 10{
 11    private const string DoNotAutoRegisterAttributeName = "DoNotAutoRegisterAttribute";
 12    private const string DoNotAutoRegisterAttributeFullName = "NexusLabs.Needlr.DoNotAutoRegisterAttribute";
 13    private const string DoNotInjectAttributeName = "DoNotInjectAttribute";
 14    private const string DoNotInjectAttributeFullName = "NexusLabs.Needlr.DoNotInjectAttribute";
 15
 16    /// <summary>
 17    /// Determines whether a type symbol represents a concrete injectable type.
 18    /// </summary>
 19    /// <param name="typeSymbol">The type symbol to check.</param>
 20    /// <param name="isCurrentAssembly">True if the type is from the current compilation's assembly (allows internal typ
 21    /// <returns>True if the type is a valid injectable type; otherwise, false.</returns>
 22    public static bool IsInjectableType(INamedTypeSymbol typeSymbol, bool isCurrentAssembly = false)
 23    {
 24        // Must be a class (not interface, struct, enum, delegate)
 143939825        if (typeSymbol.TypeKind != TypeKind.Class)
 53939926            return false;
 27
 28        // Must be accessible from generated code
 29        // - Current assembly: internal and public types are accessible
 30        // - Referenced assemblies: only public types are accessible
 89999931        if (!IsAccessibleFromGeneratedCode(typeSymbol, isCurrentAssembly))
 032            return false;
 33
 89999934        if (typeSymbol.IsAbstract)
 13791035            return false;
 36
 76208937        if (typeSymbol.IsStatic)
 8774438            return false;
 39
 67434540        if (typeSymbol.IsUnboundGenericType)
 041            return false;
 42
 43        // Exclude open generic types (type definitions with type parameters like MyClass<T>)
 44        // These cannot be instantiated directly and would produce invalid typeof() expressions
 67434545        if (typeSymbol.TypeParameters.Length > 0)
 3995546            return false;
 47
 63439048        if (typeSymbol.ContainingType != null)
 149            return false;
 50
 63438951        if (IsCompilerGenerated(typeSymbol))
 052            return false;
 53
 63438954        if (InheritsFrom(typeSymbol, "System.Exception"))
 6858155            return false;
 56
 56580857        if (InheritsFrom(typeSymbol, "System.Attribute"))
 15726258            return false;
 59
 40854660        if (typeSymbol.IsRecord)
 302961            return false;
 62
 40551763        if (HasDoNotAutoRegisterAttribute(typeSymbol))
 1364            return false;
 65
 66        // Exclude types with required members that can't be set via constructor
 67        // These would cause compilation errors: "Required member 'X' must be set"
 40550468        if (HasUnsatisfiedRequiredMembers(typeSymbol))
 369            return false;
 70
 40550171        return true;
 72    }
 73
 74    /// <summary>
 75    /// Checks if a type has required members that aren't satisfied by any constructor
 76    /// with [SetsRequiredMembers] attribute.
 77    /// </summary>
 78    public static bool HasUnsatisfiedRequiredMembers(INamedTypeSymbol typeSymbol)
 79    {
 80        // Check if any constructor has [SetsRequiredMembers] attribute
 446683981        foreach (var ctor in typeSymbol.InstanceConstructors)
 82        {
 142737483            if (ctor.IsStatic)
 84                continue;
 85
 86            // If a constructor has [SetsRequiredMembers], it handles all required members
 401447187            foreach (var attr in ctor.GetAttributes())
 88            {
 57986289                if (attr.AttributeClass?.Name == "SetsRequiredMembersAttribute" ||
 57986290                    attr.AttributeClass?.ToDisplayString() == "System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttrib
 91                {
 192                    return false; // This constructor handles required members
 93                }
 94            }
 95        }
 96
 97        // Check for required properties (including inherited)
 80604598        var currentType = typeSymbol;
 321941799        while (currentType != null)
 100        {
 92733891101            foreach (var member in currentType.GetMembers())
 102            {
 43953571103                if (member is IPropertySymbol property && property.IsRequired)
 4104                    return true;
 43953567105                if (member is IFieldSymbol field && field.IsRequired)
 1106                    return true;
 107            }
 2413372108            currentType = currentType.BaseType;
 109        }
 110
 806040111        return false;
 112    }
 113
 114    /// <summary>
 115    /// Checks if a type has the [DoNotAutoRegister] attribute (directly or on interfaces).
 116    /// </summary>
 117    public static bool HasDoNotAutoRegisterAttribute(INamedTypeSymbol typeSymbol)
 118    {
 119        // Check the type itself
 409353120        if (HasDoNotAutoRegisterAttributeDirect(typeSymbol))
 3121            return true;
 122
 123        // Check all implemented interfaces
 1528712124        foreach (var iface in typeSymbol.AllInterfaces)
 125        {
 355012126            if (HasDoNotAutoRegisterAttributeDirect(iface))
 12127                return true;
 128        }
 129
 409338130        return false;
 131    }
 132
 133    /// <summary>
 134    /// Checks if a type has the [DoNotAutoRegister] or [DoNotInject] attribute directly applied.
 135    /// </summary>
 136    public static bool HasDoNotAutoRegisterAttributeDirect(INamedTypeSymbol typeSymbol)
 137    {
 7959041138        foreach (var attribute in typeSymbol.GetAttributes())
 139        {
 2452776140            var attributeClass = attribute.AttributeClass;
 2452776141            if (attributeClass == null)
 142                continue;
 143
 2452776144            var name = attributeClass.Name;
 2452776145            if (name == DoNotAutoRegisterAttributeName || name == DoNotInjectAttributeName)
 19146                return true;
 147
 2452757148            var fullName = attributeClass.ToDisplayString();
 2452757149            if (fullName == DoNotAutoRegisterAttributeFullName || fullName == DoNotInjectAttributeFullName)
 0150                return true;
 151        }
 152
 1526735153        return false;
 154    }
 155
 156    /// <summary>
 157    /// Checks if a type is compiler-generated.
 158    /// </summary>
 159    public static bool IsCompilerGenerated(INamedTypeSymbol typeSymbol)
 160    {
 8265892161        foreach (var attribute in typeSymbol.GetAttributes())
 162        {
 2666116163            var name = attribute.AttributeClass?.ToDisplayString();
 2666116164            if (name == "System.Runtime.CompilerServices.CompilerGeneratedAttribute")
 2088165                return true;
 166        }
 167
 168        // Also check if the name indicates compiler generation
 1465786169        var typeName = typeSymbol.Name;
 1465786170        if (typeName.StartsWith("<", StringComparison.Ordinal) ||
 1465786171            typeName.Contains("__") ||
 1465786172            typeName.Contains("<>"))
 173        {
 64725174            return true;
 175        }
 176
 1401061177        return false;
 178    }
 179
 180    /// <summary>
 181    /// Checks if a type inherits from a base type by name.
 182    /// </summary>
 183    public static bool InheritsFrom(INamedTypeSymbol typeSymbol, string baseTypeName)
 184    {
 1209397185        var currentType = typeSymbol.BaseType;
 3173059186        while (currentType != null)
 187        {
 2190269188            if (currentType.ToDisplayString() == baseTypeName)
 226607189                return true;
 1963662190            currentType = currentType.BaseType;
 191        }
 982790192        return false;
 193    }
 194
 195    /// <summary>
 196    /// Checks if a type is accessible from generated code.
 197    /// For types in the current assembly, internal and public types are accessible.
 198    /// For types in referenced assemblies, only public types are accessible.
 199    /// </summary>
 200    public static bool IsAccessibleFromGeneratedCode(INamedTypeSymbol typeSymbol, bool isCurrentAssembly)
 201    {
 202        // Check the type itself
 1799988203        var accessibility = typeSymbol.DeclaredAccessibility;
 204
 1799988205        if (isCurrentAssembly)
 206        {
 207            // For current assembly, allow public or internal
 1238208            if (accessibility != Accessibility.Public && accessibility != Accessibility.Internal)
 0209                return false;
 210        }
 211        else
 212        {
 213            // For referenced assemblies, only public is accessible
 1798750214            if (accessibility != Accessibility.Public)
 0215                return false;
 216        }
 217
 218        // Check all containing types (for nested types)
 1799988219        var containingType = typeSymbol.ContainingType;
 1799989220        while (containingType != null)
 221        {
 1222            var containingAccessibility = containingType.DeclaredAccessibility;
 1223            if (isCurrentAssembly)
 224            {
 0225                if (containingAccessibility != Accessibility.Public && containingAccessibility != Accessibility.Internal
 0226                    return false;
 227            }
 228            else
 229            {
 1230                if (containingAccessibility != Accessibility.Public)
 0231                    return false;
 232            }
 1233            containingType = containingType.ContainingType;
 234        }
 235
 1799988236        return true;
 237    }
 238
 239    /// <summary>
 240    /// Checks if a type is from the System namespace or system assemblies.
 241    /// </summary>
 242    public static bool IsSystemType(INamedTypeSymbol typeSymbol)
 243    {
 908656244        var ns = typeSymbol.ContainingNamespace?.ToDisplayString() ?? string.Empty;
 245
 246        // Skip types from system assemblies
 908656247        if (ns.StartsWith("System", StringComparison.Ordinal))
 902736248            return true;
 249
 250        // Check if from mscorlib or similar
 5920251        var assembly = typeSymbol.ContainingAssembly;
 5920252        if (assembly != null)
 253        {
 5920254            var assemblyName = assembly.Name;
 5920255            if (assemblyName == "mscorlib" ||
 5920256                assemblyName == "System.Runtime" ||
 5920257                assemblyName == "System.Private.CoreLib" ||
 5920258                assemblyName == "netstandard")
 259            {
 4202260                return true;
 261            }
 262        }
 263
 1718264        return false;
 265    }
 266}