< 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)
 142027125        if (typeSymbol.TypeKind != TypeKind.Class)
 53232426            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
 88794731        if (!IsAccessibleFromGeneratedCode(typeSymbol, isCurrentAssembly))
 032            return false;
 33
 88794734        if (typeSymbol.IsAbstract)
 13610535            return false;
 36
 75184237        if (typeSymbol.IsStatic)
 8657738            return false;
 39
 66526540        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
 66526545        if (typeSymbol.TypeParameters.Length > 0)
 3942546            return false;
 47
 62584048        if (typeSymbol.ContainingType != null)
 149            return false;
 50
 62583951        if (IsCompilerGenerated(typeSymbol))
 052            return false;
 53
 62583954        if (InheritsFrom(typeSymbol, "System.Exception"))
 6768155            return false;
 56
 55815857        if (InheritsFrom(typeSymbol, "System.Attribute"))
 15518358            return false;
 59
 40297560        if (typeSymbol.IsRecord)
 275861            return false;
 62
 40021763        if (HasDoNotAutoRegisterAttribute(typeSymbol))
 1564            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"
 40020268        if (HasUnsatisfiedRequiredMembers(typeSymbol))
 369            return false;
 70
 40019971        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
 440838981        foreach (var ctor in typeSymbol.InstanceConstructors)
 82        {
 140869483            if (ctor.IsStatic)
 84                continue;
 85
 86            // If a constructor has [SetsRequiredMembers], it handles all required members
 396193187            foreach (var attr in ctor.GetAttributes())
 88            {
 57227289                if (attr.AttributeClass?.Name == "SetsRequiredMembersAttribute" ||
 57227290                    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)
 79550098        var currentType = typeSymbol;
 317728999        while (currentType != null)
 100        {
 91519197101            foreach (var member in currentType.GetMembers())
 102            {
 43377807103                if (member is IPropertySymbol property && property.IsRequired)
 4104                    return true;
 43377803105                if (member is IFieldSymbol field && field.IsRequired)
 1106                    return true;
 107            }
 2381789108            currentType = currentType.BaseType;
 109        }
 110
 795495111        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
 404004120        if (HasDoNotAutoRegisterAttributeDirect(typeSymbol))
 3121            return true;
 122
 123        // Check all implemented interfaces
 1508732124        foreach (var iface in typeSymbol.AllInterfaces)
 125        {
 350372126            if (HasDoNotAutoRegisterAttributeDirect(iface))
 14127                return true;
 128        }
 129
 403987130        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    {
 10085691138        foreach (var attribute in typeSymbol.GetAttributes())
 139        {
 3141035140            var attributeClass = attribute.AttributeClass;
 3141035141            if (attributeClass == null)
 142                continue;
 143
 3141035144            var name = attributeClass.Name;
 3141035145            if (name == DoNotAutoRegisterAttributeName || name == DoNotInjectAttributeName)
 23146                return true;
 147
 3141012148            var fullName = attributeClass.ToDisplayString();
 3141012149            if (fullName == DoNotAutoRegisterAttributeFullName || fullName == DoNotInjectAttributeFullName)
 0150                return true;
 151        }
 152
 1901799153        return false;
 154    }
 155
 156    /// <summary>
 157    /// Checks if a type is compiler-generated.
 158    /// </summary>
 159    public static bool IsCompilerGenerated(INamedTypeSymbol typeSymbol)
 160    {
 8154534161        foreach (var attribute in typeSymbol.GetAttributes())
 162        {
 2630139163            var name = attribute.AttributeClass?.ToDisplayString();
 2630139164            if (name == "System.Runtime.CompilerServices.CompilerGeneratedAttribute")
 2058165                return true;
 166        }
 167
 168        // Also check if the name indicates compiler generation
 1446099169        var typeName = typeSymbol.Name;
 1446099170        if (typeName.StartsWith("<", StringComparison.Ordinal) ||
 1446099171            typeName.Contains("__") ||
 1446099172            typeName.Contains("<>"))
 173        {
 63894174            return true;
 175        }
 176
 1382205177        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    {
 1193079185        var currentType = typeSymbol.BaseType;
 3130553186        while (currentType != null)
 187        {
 2161092188            if (currentType.ToDisplayString() == baseTypeName)
 223618189                return true;
 1937474190            currentType = currentType.BaseType;
 191        }
 969461192        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
 1775882203        var accessibility = typeSymbol.DeclaredAccessibility;
 204
 1775882205        if (isCurrentAssembly)
 206        {
 207            // For current assembly, allow public or internal
 1210208            if (accessibility != Accessibility.Public && accessibility != Accessibility.Internal)
 0209                return false;
 210        }
 211        else
 212        {
 213            // For referenced assemblies, only public is accessible
 1774672214            if (accessibility != Accessibility.Public)
 0215                return false;
 216        }
 217
 218        // Check all containing types (for nested types)
 1775882219        var containingType = typeSymbol.ContainingType;
 1775883220        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
 1775882236        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    {
 896786244        var ns = typeSymbol.ContainingNamespace?.ToDisplayString() ?? string.Empty;
 245
 246        // Skip types from system assemblies
 896786247        if (ns.StartsWith("System", StringComparison.Ordinal))
 890913248            return true;
 249
 250        // Check if from mscorlib or similar
 5873251        var assembly = typeSymbol.ContainingAssembly;
 5873252        if (assembly != null)
 253        {
 5873254            var assemblyName = assembly.Name;
 5873255            if (assemblyName == "mscorlib" ||
 5873256                assemblyName == "System.Runtime" ||
 5873257                assemblyName == "System.Private.CoreLib" ||
 5873258                assemblyName == "netstandard")
 259            {
 4147260                return true;
 261            }
 262        }
 263
 1726264        return false;
 265    }
 266}