< Summary

Information
Class: NexusLabs.Needlr.Roslyn.Shared.TypeDiscoveryHelper
Assembly: NexusLabs.Needlr.Analyzers
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Roslyn.Shared/TypeDiscoveryHelper.cs
Line coverage
59%
Covered lines: 61
Uncovered lines: 41
Coverable lines: 102
Total lines: 266
Line coverage: 59.8%
Branch coverage
50%
Covered branches: 61
Total branches: 120
Branch coverage: 50.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
IsInjectableType(...)61.53%602662.96%
HasUnsatisfiedRequiredMembers(...)53.84%512666.66%
HasDoNotAutoRegisterAttribute(...)83.33%6683.33%
HasDoNotAutoRegisterAttributeDirect(...)83.33%121290%
IsCompilerGenerated(...)58.33%131280%
InheritsFrom(...)100%44100%
IsAccessibleFromGeneratedCode(...)27.77%971837.5%
IsSystemType(...)0%272160%

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)
 14225        if (typeSymbol.TypeKind != TypeKind.Class)
 1126            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
 13131        if (!IsAccessibleFromGeneratedCode(typeSymbol, isCurrentAssembly))
 032            return false;
 33
 13134        if (typeSymbol.IsAbstract)
 035            return false;
 36
 13137        if (typeSymbol.IsStatic)
 038            return false;
 39
 13140        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
 13145        if (typeSymbol.TypeParameters.Length > 0)
 046            return false;
 47
 13148        if (typeSymbol.ContainingType != null)
 049            return false;
 50
 13151        if (IsCompilerGenerated(typeSymbol))
 052            return false;
 53
 13154        if (InheritsFrom(typeSymbol, "System.Exception"))
 055            return false;
 56
 13157        if (InheritsFrom(typeSymbol, "System.Attribute"))
 10558            return false;
 59
 2660        if (typeSymbol.IsRecord)
 061            return false;
 62
 2663        if (HasDoNotAutoRegisterAttribute(typeSymbol))
 564            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"
 2168        if (HasUnsatisfiedRequiredMembers(typeSymbol))
 069            return false;
 70
 2171        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
 8481        foreach (var ctor in typeSymbol.InstanceConstructors)
 82        {
 2183            if (ctor.IsStatic)
 84                continue;
 85
 86            // If a constructor has [SetsRequiredMembers], it handles all required members
 4287            foreach (var attr in ctor.GetAttributes())
 88            {
 089                if (attr.AttributeClass?.Name == "SetsRequiredMembersAttribute" ||
 090                    attr.AttributeClass?.ToDisplayString() == "System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttrib
 91                {
 092                    return false; // This constructor handles required members
 93                }
 94            }
 95        }
 96
 97        // Check for required properties (including inherited)
 2198        var currentType = typeSymbol;
 6399        while (currentType != null)
 100        {
 504101            foreach (var member in currentType.GetMembers())
 102            {
 210103                if (member is IPropertySymbol property && property.IsRequired)
 0104                    return true;
 210105                if (member is IFieldSymbol field && field.IsRequired)
 0106                    return true;
 107            }
 42108            currentType = currentType.BaseType;
 109        }
 110
 21111        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
 26120        if (HasDoNotAutoRegisterAttributeDirect(typeSymbol))
 5121            return true;
 122
 123        // Check all implemented interfaces
 50124        foreach (var iface in typeSymbol.AllInterfaces)
 125        {
 4126            if (HasDoNotAutoRegisterAttributeDirect(iface))
 0127                return true;
 128        }
 129
 21130        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    {
 71138        foreach (var attribute in typeSymbol.GetAttributes())
 139        {
 8140            var attributeClass = attribute.AttributeClass;
 8141            if (attributeClass == null)
 142                continue;
 143
 8144            var name = attributeClass.Name;
 8145            if (name == DoNotAutoRegisterAttributeName || name == DoNotInjectAttributeName)
 5146                return true;
 147
 3148            var fullName = attributeClass.ToDisplayString();
 3149            if (fullName == DoNotAutoRegisterAttributeFullName || fullName == DoNotInjectAttributeFullName)
 0150                return true;
 151        }
 152
 25153        return false;
 154    }
 155
 156    /// <summary>
 157    /// Checks if a type is compiler-generated.
 158    /// </summary>
 159    public static bool IsCompilerGenerated(INamedTypeSymbol typeSymbol)
 160    {
 488161        foreach (var attribute in typeSymbol.GetAttributes())
 162        {
 113163            var name = attribute.AttributeClass?.ToDisplayString();
 113164            if (name == "System.Runtime.CompilerServices.CompilerGeneratedAttribute")
 0165                return true;
 166        }
 167
 168        // Also check if the name indicates compiler generation
 131169        var typeName = typeSymbol.Name;
 131170        if (typeName.StartsWith("<", StringComparison.Ordinal) ||
 131171            typeName.Contains("__") ||
 131172            typeName.Contains("<>"))
 173        {
 0174            return true;
 175        }
 176
 131177        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    {
 262185        var currentType = typeSymbol.BaseType;
 524186        while (currentType != null)
 187        {
 367188            if (currentType.ToDisplayString() == baseTypeName)
 105189                return true;
 262190            currentType = currentType.BaseType;
 191        }
 157192        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
 131203        var accessibility = typeSymbol.DeclaredAccessibility;
 204
 131205        if (isCurrentAssembly)
 206        {
 207            // For current assembly, allow public or internal
 131208            if (accessibility != Accessibility.Public && accessibility != Accessibility.Internal)
 0209                return false;
 210        }
 211        else
 212        {
 213            // For referenced assemblies, only public is accessible
 0214            if (accessibility != Accessibility.Public)
 0215                return false;
 216        }
 217
 218        // Check all containing types (for nested types)
 131219        var containingType = typeSymbol.ContainingType;
 131220        while (containingType != null)
 221        {
 0222            var containingAccessibility = containingType.DeclaredAccessibility;
 0223            if (isCurrentAssembly)
 224            {
 0225                if (containingAccessibility != Accessibility.Public && containingAccessibility != Accessibility.Internal
 0226                    return false;
 227            }
 228            else
 229            {
 0230                if (containingAccessibility != Accessibility.Public)
 0231                    return false;
 232            }
 0233            containingType = containingType.ContainingType;
 234        }
 235
 131236        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    {
 0244        var ns = typeSymbol.ContainingNamespace?.ToDisplayString() ?? string.Empty;
 245
 246        // Skip types from system assemblies
 0247        if (ns.StartsWith("System", StringComparison.Ordinal))
 0248            return true;
 249
 250        // Check if from mscorlib or similar
 0251        var assembly = typeSymbol.ContainingAssembly;
 0252        if (assembly != null)
 253        {
 0254            var assemblyName = assembly.Name;
 0255            if (assemblyName == "mscorlib" ||
 0256                assemblyName == "System.Runtime" ||
 0257                assemblyName == "System.Private.CoreLib" ||
 0258                assemblyName == "netstandard")
 259            {
 0260                return true;
 261            }
 262        }
 263
 0264        return false;
 265    }
 266}