< Summary

Information
Class: NexusLabs.Needlr.Generators.TypeDiscoveryHelper
Assembly: NexusLabs.Needlr.Generators
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/TypeDiscoveryHelper.cs
Line coverage
85%
Covered lines: 419
Uncovered lines: 71
Coverable lines: 490
Total lines: 1574
Line coverage: 85.5%
Branch coverage
81%
Covered branches: 402
Total branches: 492
Branch coverage: 81.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
IsInjectableType(...)100%11100%
GetRegisterableInterfaces(...)93.75%161692.85%
GetRegisterAsInterfaces(...)66.66%161270%
GetConstructorParameterTypes(...)100%66100%
IsDecoratorInterface(...)100%11100%
GetFullyQualifiedName(...)78.57%141491.66%
MatchesNamespacePrefix(...)83.33%1818100%
MatchesExclusionFilter(...)14.28%1061422.22%
IsNamespacePrefixMatch(...)83.33%6685.71%
GetAllTypes()100%88100%
HasDoNotAutoRegisterAttribute(...)100%11100%
HasDoNotAutoRegisterAttributeDirect(...)100%11100%
IsCompilerGenerated(...)100%11100%
InheritsFrom(...)100%11100%
IsMauiPlatformEntryType(...)100%44100%
IsSystemInterface(...)100%11100%
IsHostedServiceInterface(...)100%11100%
IsHostedServiceType(...)83.33%211880%
IsDecoratorForHostedService(...)92.85%141491.66%
InheritsFromBackgroundService(...)100%44100%
ImplementsIHostedService(...)100%44100%
IsSystemType(...)100%11100%
HasUnsatisfiedRequiredMembers(...)100%11100%
IsAccessibleFromGeneratedCode(...)100%11100%
WouldBeInjectableIgnoringAccessibility(...)79.16%282480.76%
WouldBePluginIgnoringAccessibility(...)83.33%121285.71%
IsInternalOrLessAccessible(...)50%8662.5%
IsAccessibleFromGeneratedCode(...)100%11100%
IsAccessibleCore(...)78.57%1414100%
IsDisposableType(...)100%66100%
.cctor()100%11100%
ImplementsNeedlrPluginInterface(...)100%66100%
GetNeedlrPluginInterfaceName(...)0%4260%
HasGenerateTypeRegistryAttribute(...)100%66100%
IsPubliclyAccessible(...)0%4260%
DetermineLifetime(...)100%1818100%
GetExplicitLifetime(...)100%1616100%
AllParametersAreInjectable(...)100%44100%
IsInjectableParameterType(...)100%1010100%
HasDoNotInjectAttribute(...)87.5%8890%
IsPluginType(...)90%202089.47%
GetPluginInterfaces(...)92.85%141486.66%
ImplementsInterface(...)0%2040%
HasParameterlessConstructor(...)100%88100%
GetPluginAttributes(...)63.33%733063.63%
IsInheritedAttribute(...)0%156120%
GetBestConstructorParameters(...)100%2222100%
GetFullyQualifiedNameForType(...)100%11100%
.ctor(...)100%11100%
get_TypeName()100%11100%
get_ServiceKey()100%11100%
get_ParameterName()100%11100%
get_DocumentationComment()100%11100%
get_IsKeyed()100%11100%
GetBestConstructorParametersWithKeys(...)90.62%373283.33%
GetKeyedServiceKeys(...)100%1212100%
HasDeferToContainerAttribute(...)100%88100%
GetDeferToContainerParameterTypes(...)93.75%161693.75%
.ctor(...)100%11100%
get_DecoratorTypeName()100%11100%
get_ServiceTypeName()100%11100%
get_Order()100%11100%
GetDecoratorForAttributes(...)95.45%2222100%
HasDecoratorForAttribute(...)91.66%1212100%

File(s)

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

#LineLine coverage
 1using Microsoft.CodeAnalysis;
 2using SharedHelper = NexusLabs.Needlr.Roslyn.Shared.TypeDiscoveryHelper;
 3
 4namespace NexusLabs.Needlr.Generators;
 5
 6/// <summary>
 7/// Internal lifetime enum used by the generator to avoid runtime dependency on Attributes assembly.
 8/// Maps 1:1 with InjectableLifetime in the Attributes package.
 9/// </summary>
 10internal enum GeneratorLifetime
 11{
 12    Singleton = 0,
 13    Scoped = 1,
 14    Transient = 2
 15}
 16
 17/// <summary>
 18/// Helper utilities for discovering injectable types from Roslyn symbols.
 19/// </summary>
 20internal static class TypeDiscoveryHelper
 21{
 22    private const string DoNotInjectAttributeName = "DoNotInjectAttribute";
 23    private const string DoNotInjectAttributeFullName = "NexusLabs.Needlr.DoNotInjectAttribute";
 24    private const string DeferToContainerAttributeName = "DeferToContainerAttribute";
 25    private const string DeferToContainerAttributeFullName = "NexusLabs.Needlr.DeferToContainerAttribute";
 26    private const string DecoratorForAttributePrefix = "NexusLabs.Needlr.DecoratorForAttribute";
 27    private const string KeyedAttributeName = "KeyedAttribute";
 28    private const string KeyedAttributeFullName = "NexusLabs.Needlr.KeyedAttribute";
 29    private const string SingletonAttributeName = "SingletonAttribute";
 30    private const string SingletonAttributeFullName = "NexusLabs.Needlr.SingletonAttribute";
 31    private const string ScopedAttributeName = "ScopedAttribute";
 32    private const string ScopedAttributeFullName = "NexusLabs.Needlr.ScopedAttribute";
 33    private const string TransientAttributeName = "TransientAttribute";
 34    private const string TransientAttributeFullName = "NexusLabs.Needlr.TransientAttribute";
 35    private const string GenerateFactoryAttributeName = "GenerateFactoryAttribute";
 36    private const string GenerateFactoryAttributeFullName = "NexusLabs.Needlr.Generators.GenerateFactoryAttribute";
 37    private const string OptionsAttributeName = "OptionsAttribute";
 38    private const string OptionsAttributeFullName = "NexusLabs.Needlr.Generators.OptionsAttribute";
 39
 40    /// <summary>
 41    /// Determines whether a type symbol represents a concrete injectable type.
 42    /// Delegates to the shared library for consistency with analyzers.
 43    /// </summary>
 44    /// <param name="typeSymbol">The type symbol to check.</param>
 45    /// <param name="isCurrentAssembly">True if the type is from the current compilation's assembly (allows internal typ
 46    /// <returns>True if the type is a valid injectable type; otherwise, false.</returns>
 47    public static bool IsInjectableType(INamedTypeSymbol typeSymbol, bool isCurrentAssembly = false)
 145725348        => SharedHelper.IsInjectableType(typeSymbol, isCurrentAssembly);
 49
 50    private const string RegisterAsAttributePrefix = "NexusLabs.Needlr.RegisterAsAttribute";
 51
 52    /// <summary>
 53    /// Gets the interfaces that should be registered for a type.
 54    /// </summary>
 55    /// <param name="typeSymbol">The type symbol to get interfaces for.</param>
 56    /// <param name="compilationAssembly">
 57    /// The assembly of the current compilation. Used to determine whether an internal
 58    /// interface is accessible from the generated code. Same-assembly internal interfaces
 59    /// are valid registration targets because the generated TypeRegistry is emitted into
 60    /// the same compilation unit. Cross-assembly internal interfaces (e.g., Avalonia's
 61    /// <c>IContentPresenterHost</c>) are inaccessible and must be skipped to avoid CS0122.
 62    /// </param>
 63    /// <returns>A list of interface symbols suitable for registration.</returns>
 64    public static IReadOnlyList<INamedTypeSymbol> GetRegisterableInterfaces(
 65        INamedTypeSymbol typeSymbol,
 66        IAssemblySymbol? compilationAssembly = null)
 67    {
 68        // Check for [RegisterAs<T>] attributes - if present, only register as those interfaces
 23359869        var registerAsInterfaces = GetRegisterAsInterfaces(typeSymbol);
 23359870        if (registerAsInterfaces.Count > 0)
 71        {
 072            return registerAsInterfaces;
 73        }
 74
 23359875        var result = new List<INamedTypeSymbol>();
 76
 77        // Get all constructor parameter types to detect decorator pattern
 23359878        var constructorParamTypes = GetConstructorParameterTypes(typeSymbol);
 79
 89247880        foreach (var iface in typeSymbol.AllInterfaces)
 81        {
 21264182            if (iface.IsUnboundGenericType)
 83                continue;
 84
 21264185            if (IsSystemInterface(iface))
 86                continue;
 87
 88            // Skip interfaces that are inaccessible from the generated code.
 89            //
 90            // WHY THIS EXISTS: The generated TypeRegistry emits typeof(IFoo) for each
 91            // registered interface. If IFoo is internal to a DIFFERENT assembly, the
 92            // generated code (which lives in THIS compilation) cannot access it → CS0122.
 93            //
 94            // CRITICAL: Same-assembly internal interfaces MUST be kept. This is the
 95            // standard .NET DI pattern — an internal class implements an internal interface,
 96            // both in the same project. The generated TypeRegistry is emitted into that
 97            // same compilation and CAN legally reference typeof(InternalInterface).
 98            //
 99            // Example that MUST work (same assembly):
 100            //   internal interface IAuthConfig { ... }
 101            //   internal class AuthConfig : IAuthConfig { ... }
 102            //   → typeof(IAuthConfig) in generated code is VALID
 103            //
 104            // Example that MUST be skipped (cross-assembly, e.g., Avalonia):
 105            //   // In Avalonia.Controls.dll (internal):
 106            //   internal interface IContentPresenterHost { ... }
 107            //   // In consumer app:
 108            //   public class MainWindow : Window { ... } // inherits IContentPresenterHost via Window
 109            //   → typeof(IContentPresenterHost) in generated code produces CS0122
 331110            if (!IsAccessibleFromGeneratedCode(iface, compilationAssembly))
 111                continue;
 112
 325113            if (HasDoNotAutoRegisterAttributeDirect(iface))
 114                continue;
 115
 116            // Skip interfaces that this type also takes as constructor parameters (decorator pattern)
 117            // A type that implements IFoo and takes IFoo in its constructor is likely a decorator
 118            // and should not be auto-registered as IFoo to avoid circular dependencies
 323119            if (IsDecoratorInterface(iface, constructorParamTypes))
 120                continue;
 121
 122            // Skip IHostedService - hosted services are registered separately via RegisterHostedServices()
 123            // to ensure proper concrete + interface forwarding pattern
 295124            if (IsHostedServiceInterface(iface))
 125                continue;
 126
 288127            result.Add(iface);
 128        }
 129
 233598130        return result;
 131    }
 132
 133    /// <summary>
 134    /// Gets interface types specified by [RegisterAs&lt;T&gt;] attributes on the type.
 135    /// </summary>
 136    /// <param name="typeSymbol">The type symbol to check.</param>
 137    /// <returns>A list of interface symbols from RegisterAs attributes.</returns>
 138    public static IReadOnlyList<INamedTypeSymbol> GetRegisterAsInterfaces(INamedTypeSymbol typeSymbol)
 139    {
 233598140        var result = new List<INamedTypeSymbol>();
 141
 1319424142        foreach (var attribute in typeSymbol.GetAttributes())
 143        {
 426114144            var attrClass = attribute.AttributeClass;
 426114145            if (attrClass == null)
 146                continue;
 147
 148            // Check for RegisterAsAttribute<T>
 426114149            var attrFullName = attrClass.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 426114150            if (!attrFullName.StartsWith("global::" + RegisterAsAttributePrefix, StringComparison.Ordinal))
 151                continue;
 152
 153            // Get the type argument
 0154            if (attrClass.IsGenericType && attrClass.TypeArguments.Length == 1)
 155            {
 0156                if (attrClass.TypeArguments[0] is INamedTypeSymbol interfaceType)
 157                {
 0158                    result.Add(interfaceType);
 159                }
 160            }
 161        }
 162
 233598163        return result;
 164    }
 165
 166    /// <summary>
 167    /// Gets all parameter types from all constructors of a type.
 168    /// </summary>
 169    private static HashSet<string> GetConstructorParameterTypes(INamedTypeSymbol typeSymbol)
 170    {
 233598171        var paramTypes = new HashSet<string>(StringComparer.Ordinal);
 172
 1505690173        foreach (var ctor in typeSymbol.InstanceConstructors)
 174        {
 519247175            if (ctor.IsStatic)
 176                continue;
 177
 2229938178            foreach (var param in ctor.Parameters)
 179            {
 595722180                var paramTypeName = param.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 595722181                paramTypes.Add(paramTypeName);
 182            }
 183        }
 184
 233598185        return paramTypes;
 186    }
 187
 188    /// <summary>
 189    /// Checks if an interface is a decorator interface (also taken as a constructor parameter).
 190    /// </summary>
 191    private static bool IsDecoratorInterface(INamedTypeSymbol iface, HashSet<string> constructorParamTypes)
 192    {
 323193        var ifaceName = iface.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 323194        return constructorParamTypes.Contains(ifaceName);
 195    }
 196
 197    /// <summary>
 198    /// Gets the fully qualified name for a type symbol suitable for code generation.
 199    /// For generic type definitions (open generics), outputs open generic syntax (e.g., MyClass&lt;&gt;).
 200    /// For constructed generics with concrete type arguments, outputs the full type (e.g., MyClass&lt;int&gt;).
 201    /// </summary>
 202    /// <param name="typeSymbol">The type symbol.</param>
 203    /// <returns>The fully qualified type name with global:: prefix.</returns>
 204    public static string GetFullyQualifiedName(INamedTypeSymbol typeSymbol)
 205    {
 206        // Check if this is an open generic type definition (has type parameters, not type arguments)
 207        // e.g., JobScheduler<TJob> where TJob is a TypeParameter, not a concrete type
 208        // We need to convert these to open generic syntax: JobScheduler<>
 4233050209        if (typeSymbol.TypeParameters.Length > 0 && !typeSymbol.IsUnboundGenericType)
 210        {
 211            // Check if type arguments are still type parameters (meaning this is a generic definition)
 212            // For a closed generic like ILogger<MyService>, TypeArguments contains MyService (a NamedTypeSymbol)
 213            // For an open generic like JobScheduler<TJob>, TypeArguments contains TJob (a TypeParameterSymbol)
 410907214            var hasUnresolvedTypeParameters = typeSymbol.TypeArguments.Any(ta => ta.TypeKind == TypeKind.TypeParameter);
 215
 202362216            if (hasUnresolvedTypeParameters)
 217            {
 218                // Build the open generic name manually
 61004219                var containingNamespace = typeSymbol.ContainingNamespace?.ToDisplayString();
 61004220                var typeName = typeSymbol.Name;
 61004221                var arity = typeSymbol.TypeParameters.Length;
 222
 223                // Create the open generic syntax: MyClass<,> for 2 type params, MyClass<> for 1
 61004224                var commas = arity > 1 ? new string(',', arity - 1) : string.Empty;
 61004225                var openGenericPart = $"<{commas}>";
 226
 61004227                if (string.IsNullOrEmpty(containingNamespace) || containingNamespace == "<global namespace>")
 228                {
 0229                    return $"global::{typeName}{openGenericPart}";
 230                }
 231
 61004232                return $"global::{containingNamespace}.{typeName}{openGenericPart}";
 233            }
 234        }
 235
 4172046236        return typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 237    }
 238
 239    /// <summary>
 240    /// Checks if a type matches any of the given namespace prefixes.
 241    /// </summary>
 242    /// <param name="typeSymbol">The type symbol to check.</param>
 243    /// <param name="namespacePrefixes">The namespace prefixes to match.</param>
 244    /// <returns>True if the type's namespace starts with any of the prefixes.</returns>
 245    public static bool MatchesNamespacePrefix(INamedTypeSymbol typeSymbol, IReadOnlyList<string>? namespacePrefixes)
 246    {
 1838404247        if (namespacePrefixes == null || namespacePrefixes.Count == 0)
 1530009248            return true;
 249
 308395250        var typeNamespace = typeSymbol.ContainingNamespace?.ToDisplayString() ?? string.Empty;
 251
 252        // Check if type is in the global namespace
 308395253        var isGlobalNamespace = typeSymbol.ContainingNamespace?.IsGlobalNamespace == true;
 254
 1240988255        foreach (var prefix in namespacePrefixes)
 256        {
 257            // Empty string prefix matches global namespace types
 312383258            if (string.IsNullOrEmpty(prefix))
 259            {
 7975260                if (isGlobalNamespace)
 346261                    return true;
 262                continue;
 263            }
 264
 304408265            if (IsNamespacePrefixMatch(typeNamespace, prefix))
 222266                return true;
 267        }
 268
 307827269        return false;
 568270    }
 271
 272    /// <summary>
 273    /// Checks if a type matches any of the given exclusion namespace prefixes.
 274    /// Returns <c>true</c> if the type should be EXCLUDED (its namespace starts with
 275    /// any of the exclusion prefixes).
 276    /// </summary>
 277    /// <param name="typeSymbol">The type symbol to check.</param>
 278    /// <param name="excludeNamespacePrefixes">The namespace prefixes to exclude. If null or empty, nothing is excluded.
 279    /// <returns>True if the type should be excluded from the registry.</returns>
 280    public static bool MatchesExclusionFilter(INamedTypeSymbol typeSymbol, IReadOnlyList<string>? excludeNamespacePrefix
 281    {
 1530574282        if (excludeNamespacePrefixes == null || excludeNamespacePrefixes.Count == 0)
 1530574283            return false;
 284
 0285        var typeNamespace = typeSymbol.ContainingNamespace?.ToDisplayString() ?? string.Empty;
 286
 0287        foreach (var prefix in excludeNamespacePrefixes)
 288        {
 0289            if (string.IsNullOrEmpty(prefix))
 290                continue;
 291
 0292            if (IsNamespacePrefixMatch(typeNamespace, prefix))
 0293                return true;
 294        }
 295
 0296        return false;
 0297    }
 298
 299    /// <summary>
 300    /// Dot-boundary-aware prefix match. <c>"Avalonia"</c> matches <c>"Avalonia"</c>
 301    /// and <c>"Avalonia.Controls"</c> but NOT <c>"AvaloniaDemoApp"</c>.
 302    /// A prefix ending with <c>"."</c> requires an exact sub-namespace match
 303    /// (e.g., <c>"Avalonia."</c> matches only <c>"Avalonia.Controls"</c>, not <c>"Avalonia"</c> itself).
 304    /// </summary>
 305    private static bool IsNamespacePrefixMatch(string typeNamespace, string prefix)
 306    {
 304408307        if (!typeNamespace.StartsWith(prefix, StringComparison.Ordinal))
 304186308            return false;
 309
 310        // Exact match (namespace equals prefix)
 222311        if (typeNamespace.Length == prefix.Length)
 216312            return true;
 313
 314        // Prefix already ends with dot — the StartsWith check is sufficient
 315        // (e.g., prefix "Avalonia." matches "Avalonia.Controls")
 6316        if (prefix[prefix.Length - 1] == '.')
 0317            return true;
 318
 319        // Sub-namespace: next char after prefix must be a dot
 320        // "Avalonia" matches "Avalonia.Controls" but not "AvaloniaDemoApp"
 6321        return typeNamespace[prefix.Length] == '.';
 322    }
 323
 324    /// <summary>
 325    /// Recursively iterates all named type symbols in a namespace.
 326    /// </summary>
 327    /// <param name="namespaceSymbol">The namespace to iterate.</param>
 328    /// <returns>All named type symbols in the namespace and nested namespaces.</returns>
 329    public static IEnumerable<INamedTypeSymbol> GetAllTypes(INamespaceSymbol namespaceSymbol)
 330    {
 10958720331        foreach (var member in namespaceSymbol.GetMembers())
 332        {
 4499052333            if (member is INamedTypeSymbol typeSymbol)
 334            {
 3675573335                yield return typeSymbol;
 336            }
 823479337            else if (member is INamespaceSymbol nestedNamespace)
 338            {
 20397174339                foreach (var nestedType in GetAllTypes(nestedNamespace))
 340                {
 9375108341                    yield return nestedType;
 342                }
 343            }
 344        }
 980308345    }
 346
 347    private static bool HasDoNotAutoRegisterAttribute(INamedTypeSymbol typeSymbol)
 3876348        => SharedHelper.HasDoNotAutoRegisterAttribute(typeSymbol);
 349
 350    private static bool HasDoNotAutoRegisterAttributeDirect(INamedTypeSymbol typeSymbol)
 770850351        => SharedHelper.HasDoNotAutoRegisterAttributeDirect(typeSymbol);
 352
 353    private static bool IsCompilerGenerated(INamedTypeSymbol typeSymbol)
 842477354        => SharedHelper.IsCompilerGenerated(typeSymbol);
 355
 356    private static bool InheritsFrom(INamedTypeSymbol typeSymbol, string baseTypeName)
 4601013357        => SharedHelper.InheritsFrom(typeSymbol, baseTypeName);
 358
 359    /// <summary>
 360    /// Determines whether a type is a .NET MAUI per-platform application entry point — the Windows
 361    /// <c>App : Microsoft.Maui.MauiWinUIApplication</c>, the Android
 362    /// <c>MainApplication : Microsoft.Maui.MauiApplication</c>, or the iOS/Mac
 363    /// <c>AppDelegate : Microsoft.Maui.MauiUIApplicationDelegate</c> that live under <c>Platforms/</c>.
 364    /// </summary>
 365    /// <remarks>
 366    /// These types are framework-owned and constructed by the MAUI platform host; they are never
 367    /// resolved as Needlr services. They are also decorated by the platform's own source generators
 368    /// with interop members (for example WinRT plumbing such as <c>ApplicationRcwFactoryAttribute</c>)
 369    /// that are inaccessible from generated code, so including them in the registry breaks the head
 370    /// build. They are therefore excluded from all discovery. The cross-platform
 371    /// <c>App : Microsoft.Maui.Controls.Application</c>, pages, views, and view models are ordinary
 372    /// types and remain scannable.
 373    /// </remarks>
 374    /// <param name="typeSymbol">The type symbol to check.</param>
 375    /// <returns><see langword="true"/> if the type derives from a MAUI platform application base type.</returns>
 376    public static bool IsMauiPlatformEntryType(INamedTypeSymbol typeSymbol)
 1530574377        => InheritsFrom(typeSymbol, "Microsoft.Maui.MauiWinUIApplication")
 1530574378        || InheritsFrom(typeSymbol, "Microsoft.Maui.MauiApplication")
 1530574379        || InheritsFrom(typeSymbol, "Microsoft.Maui.MauiUIApplicationDelegate");
 380
 381    private static bool IsSystemInterface(INamedTypeSymbol interfaceSymbol)
 509782382        => SharedHelper.IsSystemType(interfaceSymbol);
 383
 384    private static bool IsHostedServiceInterface(INamedTypeSymbol interfaceSymbol)
 385    {
 295386        var fullName = GetFullyQualifiedName(interfaceSymbol);
 295387        return fullName == "global::Microsoft.Extensions.Hosting.IHostedService";
 388    }
 389
 390    /// <summary>
 391    /// Determines whether a type is a hosted service (implements IHostedService or inherits from BackgroundService).
 392    /// </summary>
 393    /// <param name="typeSymbol">The type symbol to check.</param>
 394    /// <param name="isCurrentAssembly">True if the type is from the current compilation's assembly.</param>
 395    /// <returns>True if the type is a hosted service.</returns>
 396    public static bool IsHostedServiceType(INamedTypeSymbol typeSymbol, bool isCurrentAssembly = false)
 397    {
 398        // Must be a concrete, non-abstract class
 1457236399        if (typeSymbol.IsAbstract || typeSymbol.TypeKind != TypeKind.Class)
 686711400            return false;
 401
 402        // Check accessibility
 770525403        if (!isCurrentAssembly && IsInternalOrLessAccessible(typeSymbol))
 0404            return false;
 405
 406        // Skip if marked with [DoNotAutoRegister]
 770525407        if (HasDoNotAutoRegisterAttributeDirect(typeSymbol))
 2408            return false;
 409
 410        // Skip compiler-generated types
 770523411        if (IsCompilerGenerated(typeSymbol))
 0412            return false;
 413
 414        // Skip decorators - types with [DecoratorFor<IHostedService>] should not be
 415        // registered as hosted services (they decorate hosted services, not are hosted services)
 770523416        if (IsDecoratorForHostedService(typeSymbol))
 0417            return false;
 418
 419        // Check if inherits from BackgroundService
 770523420        if (InheritsFromBackgroundService(typeSymbol))
 6421            return true;
 422
 423        // Check if directly implements IHostedService (not via BackgroundService)
 770517424        if (ImplementsIHostedService(typeSymbol))
 1425            return true;
 426
 770516427        return false;
 428    }
 429
 430    private static bool IsDecoratorForHostedService(INamedTypeSymbol typeSymbol)
 431    {
 4446674432        foreach (var attribute in typeSymbol.GetAttributes())
 433        {
 1452814434            var attrClass = attribute.AttributeClass;
 1452814435            if (attrClass == null)
 436                continue;
 437
 1452814438            var attrFullName = attrClass.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 439
 440            // Check for DecoratorForAttribute<IHostedService>
 1452814441            if (attrFullName.StartsWith("global::NexusLabs.Needlr.DecoratorForAttribute<", StringComparison.Ordinal))
 442            {
 443                // Get the type argument
 19444                if (attrClass.IsGenericType && attrClass.TypeArguments.Length == 1)
 445                {
 19446                    var typeArg = attrClass.TypeArguments[0];
 19447                    if (typeArg is INamedTypeSymbol namedTypeArg)
 448                    {
 19449                        var typeArgName = GetFullyQualifiedName(namedTypeArg);
 19450                        if (typeArgName == "global::Microsoft.Extensions.Hosting.IHostedService")
 0451                            return true;
 452                    }
 453                }
 454            }
 455        }
 770523456        return false;
 457    }
 458
 459    private static bool InheritsFromBackgroundService(INamedTypeSymbol typeSymbol)
 460    {
 1412003461        var baseType = typeSymbol.BaseType;
 4225979462        while (baseType != null)
 463        {
 2813989464            var fullName = GetFullyQualifiedName(baseType);
 2813989465            if (fullName == "global::Microsoft.Extensions.Hosting.BackgroundService")
 13466                return true;
 2813976467            baseType = baseType.BaseType;
 468        }
 1411990469        return false;
 470    }
 471
 472    private static bool ImplementsIHostedService(INamedTypeSymbol typeSymbol)
 473    {
 4807834474        foreach (var iface in typeSymbol.AllInterfaces)
 475        {
 991928476            var fullName = GetFullyQualifiedName(iface);
 991928477            if (fullName == "global::Microsoft.Extensions.Hosting.IHostedService")
 2478                return true;
 479        }
 1411988480        return false;
 481    }
 482
 483    private static bool IsSystemType(INamedTypeSymbol typeSymbol)
 408630484        => SharedHelper.IsSystemType(typeSymbol);
 485
 486    private static bool HasUnsatisfiedRequiredMembers(INamedTypeSymbol typeSymbol)
 405152487        => SharedHelper.HasUnsatisfiedRequiredMembers(typeSymbol);
 488
 489    /// <summary>
 490    /// Checks if a type is accessible from generated code.
 491    /// For types in the current assembly, internal and public types are accessible.
 492    /// For types in referenced assemblies, only public types are accessible.
 493    /// </summary>
 494    /// <param name="typeSymbol">The type symbol to check.</param>
 495    /// <param name="isCurrentAssembly">True if the type is from the current compilation's assembly.</param>
 496    /// <returns>True if the type is accessible from generated code.</returns>
 497    private static bool IsAccessibleFromGeneratedCode(INamedTypeSymbol typeSymbol, bool isCurrentAssembly)
 909884498        => SharedHelper.IsAccessibleFromGeneratedCode(typeSymbol, isCurrentAssembly);
 499
 500    /// <summary>
 501    /// Checks if a type would be registerable as injectable, ignoring accessibility constraints.
 502    /// This is used to detect internal types that match namespace filters but cannot be included.
 503    /// </summary>
 504    /// <param name="typeSymbol">The type symbol to check.</param>
 505    /// <returns>True if the type would be injectable if it were accessible.</returns>
 506    public static bool WouldBeInjectableIgnoringAccessibility(INamedTypeSymbol typeSymbol)
 507    {
 508        // Must be a class (not interface, struct, enum, delegate)
 73351509        if (typeSymbol.TypeKind != TypeKind.Class)
 14510            return false;
 511
 73337512        if (typeSymbol.IsAbstract)
 0513            return false;
 514
 73337515        if (typeSymbol.IsStatic)
 1176516            return false;
 517
 72161518        if (typeSymbol.IsUnboundGenericType)
 0519            return false;
 520
 521        // Exclude open generic types (type definitions with type parameters like MyClass<T>)
 72161522        if (typeSymbol.TypeParameters.Length > 0)
 207523            return false;
 524
 71954525        if (typeSymbol.ContainingType != null)
 0526            return false;
 527
 71954528        if (IsCompilerGenerated(typeSymbol))
 67306529            return false;
 530
 4648531        if (InheritsFrom(typeSymbol, "System.Exception"))
 0532            return false;
 533
 4648534        if (InheritsFrom(typeSymbol, "System.Attribute"))
 772535            return false;
 536
 3876537        if (typeSymbol.IsRecord)
 0538            return false;
 539
 3876540        if (HasDoNotAutoRegisterAttribute(typeSymbol))
 2541            return false;
 542
 543        // Must have a determinable lifetime to be injectable
 3874544        var lifetime = DetermineLifetime(typeSymbol);
 3874545        if (!lifetime.HasValue)
 1544546            return false;
 547
 2330548        return true;
 549    }
 550
 551    /// <summary>
 552    /// Checks if a type would be registerable as a plugin, ignoring accessibility constraints.
 553    /// This is used to detect internal types that match namespace filters but cannot be included.
 554    /// </summary>
 555    /// <param name="typeSymbol">The type symbol to check.</param>
 556    /// <param name="compilationAssembly">
 557    /// The compilation's assembly for same-assembly accessibility checks.
 558    /// Passed through to <see cref="GetPluginInterfaces"/> so that same-assembly internal
 559    /// interfaces are correctly recognized as plugin interfaces.
 560    /// </param>
 561    /// <returns>True if the type would be a plugin if it were accessible.</returns>
 562    public static bool WouldBePluginIgnoringAccessibility(
 563        INamedTypeSymbol typeSymbol,
 564        IAssemblySymbol? compilationAssembly = null)
 565    {
 566        // Must be a concrete class
 71039567        if (typeSymbol.TypeKind != TypeKind.Class)
 14568            return false;
 569
 71025570        if (typeSymbol.IsAbstract)
 0571            return false;
 572
 71025573        if (typeSymbol.IsStatic)
 1176574            return false;
 575
 69849576        if (typeSymbol.IsUnboundGenericType)
 0577            return false;
 578
 579        // Exclude open generic types (type definitions with type parameters like MyClass<T>)
 69849580        if (typeSymbol.TypeParameters.Length > 0)
 207581            return false;
 582
 583        // NOTE: Records ARE allowed as plugins (they are classes with parameterless constructors).
 584        // Records are excluded from IsInjectableType (auto-registration) but not from plugin discovery.
 585
 586        // Must have a parameterless constructor
 69642587        if (!HasParameterlessConstructor(typeSymbol))
 1534588            return false;
 589
 590        // Must have at least one plugin interface
 68108591        var pluginInterfaces = GetPluginInterfaces(typeSymbol, compilationAssembly);
 68108592        return pluginInterfaces.Count > 0;
 593    }
 594
 595    /// <summary>
 596    /// Checks if a type is internal (not public) and would be inaccessible from generated code
 597    /// in a different assembly.
 598    /// </summary>
 599    /// <param name="typeSymbol">The type symbol to check.</param>
 600    /// <returns>True if the type is internal or less accessible.</returns>
 601    public static bool IsInternalOrLessAccessible(INamedTypeSymbol typeSymbol)
 602    {
 603        // Check the type itself
 4136545604        if (typeSymbol.DeclaredAccessibility != Accessibility.Public)
 160733605            return true;
 606
 607        // Check all containing types (for nested types)
 3975812608        var containingType = typeSymbol.ContainingType;
 3975812609        while (containingType != null)
 610        {
 0611            if (containingType.DeclaredAccessibility != Accessibility.Public)
 0612                return true;
 0613            containingType = containingType.ContainingType;
 614        }
 615
 3975812616        return false;
 617    }
 618
 619    /// <summary>
 620    /// Determines whether a type symbol is accessible from generated code emitted into
 621    /// the given compilation assembly.
 622    /// </summary>
 623    /// <remarks>
 624    /// <para>
 625    /// The generated TypeRegistry is emitted into the compilation's assembly. It can
 626    /// reference any type that is accessible from that assembly:
 627    /// </para>
 628    /// <list type="bullet">
 629    /// <item><c>public</c> types — always accessible.</item>
 630    /// <item><c>internal</c> / <c>protected internal</c> types — accessible only when
 631    /// they belong to the same assembly as the compilation (same-assembly check).</item>
 632    /// <item><c>private</c> / <c>protected</c> / <c>private protected</c> types — never
 633    /// accessible from generated top-level code (even in the same assembly, generated code
 634    /// is not inside the containing type's hierarchy).</item>
 635    /// </list>
 636    /// <para>
 637    /// Containing types are checked recursively for nested types.
 638    /// </para>
 639    /// </remarks>
 640    /// <param name="typeSymbol">The type symbol to check.</param>
 641    /// <param name="compilationAssembly">
 642    /// The compilation's output assembly. When <see langword="null"/>, falls back to
 643    /// the conservative behavior of <see cref="IsInternalOrLessAccessible"/> (i.e.,
 644    /// any non-public type is considered inaccessible).
 645    /// </param>
 646    /// <returns><see langword="true"/> if the type is accessible from generated code.</returns>
 647    public static bool IsAccessibleFromGeneratedCode(
 648        INamedTypeSymbol typeSymbol,
 649        IAssemblySymbol? compilationAssembly)
 650    {
 1763651        return IsAccessibleCore(typeSymbol, compilationAssembly);
 652    }
 653
 654    private static bool IsAccessibleCore(
 655        INamedTypeSymbol typeSymbol,
 656        IAssemblySymbol? compilationAssembly)
 657    {
 1763658        var accessibility = typeSymbol.DeclaredAccessibility;
 659
 1763660        if (accessibility == Accessibility.Public)
 661        {
 662            // Public is universally accessible, but check containing types for nested types
 1733663            return typeSymbol.ContainingType == null ||
 1733664                   IsAccessibleCore(typeSymbol.ContainingType, compilationAssembly);
 665        }
 666
 667        // internal and protected-internal are accessible from the same assembly.
 668        // The generated code lives in the compilation assembly, so if the type is
 669        // also in that assembly, we can emit typeof() for it.
 30670        if (accessibility == Accessibility.Internal ||
 30671            accessibility == Accessibility.ProtectedOrInternal)
 672        {
 24673            bool isSameAssembly = compilationAssembly != null &&
 24674                SymbolEqualityComparer.Default.Equals(
 24675                    typeSymbol.ContainingAssembly, compilationAssembly);
 676
 24677            if (isSameAssembly)
 678            {
 679                // Same assembly — accessible via internal path.
 680                // Still need to check containing types for nested types.
 18681                return typeSymbol.ContainingType == null ||
 18682                       IsAccessibleCore(typeSymbol.ContainingType, compilationAssembly);
 683            }
 684        }
 685
 686        // private, protected, private-protected, or cross-assembly internal
 687        // → inaccessible from generated code.
 12688        return false;
 689    }
 690
 691    /// <summary>
 692    /// Checks if a type implements IDisposable or IAsyncDisposable.
 693    /// </summary>
 694    /// <param name="typeSymbol">The type symbol to check.</param>
 695    /// <returns>True if the type implements IDisposable or IAsyncDisposable.</returns>
 696    public static bool IsDisposableType(INamedTypeSymbol typeSymbol)
 697    {
 782263698        foreach (var iface in typeSymbol.AllInterfaces)
 699        {
 187124700            var fullName = GetFullyQualifiedName(iface);
 187124701            if (fullName == "global::System.IDisposable" || fullName == "global::System.IAsyncDisposable")
 59067702                return true;
 703        }
 174474704        return false;
 705    }
 706
 707    /// <summary>
 708    /// Known Needlr plugin interface names that indicate a type is a plugin.
 709    /// </summary>
 1710    private static readonly string[] NeedlrPluginInterfaceNames =
 1711    [
 1712        "NexusLabs.Needlr.IServiceCollectionPlugin",
 1713        "NexusLabs.Needlr.IPostBuildServiceCollectionPlugin",
 1714        "NexusLabs.Needlr.AspNet.IWebApplicationPlugin",
 1715        "NexusLabs.Needlr.AspNet.IWebApplicationBuilderPlugin",
 1716        "NexusLabs.Needlr.SignalR.IHubRegistrationPlugin",
 1717        "NexusLabs.Needlr.SemanticKernel.IKernelBuilderPlugin",
 1718        "NexusLabs.Needlr.Hosting.IHostApplicationBuilderPlugin",
 1719        "NexusLabs.Needlr.Hosting.IHostPlugin"
 1720    ];
 721
 722    /// <summary>
 723    /// Checks if a type implements any known Needlr plugin interface.
 724    /// </summary>
 725    /// <param name="typeSymbol">The type symbol to check.</param>
 726    /// <returns>True if the type implements a Needlr plugin interface.</returns>
 727    public static bool ImplementsNeedlrPluginInterface(INamedTypeSymbol typeSymbol)
 728    {
 178757729        foreach (var iface in typeSymbol.AllInterfaces)
 730        {
 1956731            var ifaceName = iface.ToDisplayString();
 35193732            foreach (var pluginInterface in NeedlrPluginInterfaceNames)
 733            {
 15641734                if (ifaceName == pluginInterface)
 1735                    return true;
 736            }
 737        }
 87422738        return false;
 739    }
 740
 741    /// <summary>
 742    /// Gets the name of the first Needlr plugin interface implemented by the type.
 743    /// </summary>
 744    /// <param name="typeSymbol">The type symbol to check.</param>
 745    /// <returns>The interface name, or null if none found.</returns>
 746    public static string? GetNeedlrPluginInterfaceName(INamedTypeSymbol typeSymbol)
 747    {
 0748        foreach (var iface in typeSymbol.AllInterfaces)
 749        {
 0750            var ifaceName = iface.ToDisplayString();
 0751            foreach (var pluginInterface in NeedlrPluginInterfaceNames)
 752            {
 0753                if (ifaceName == pluginInterface)
 0754                    return ifaceName;
 755            }
 756        }
 0757        return null;
 758    }
 759
 760    /// <summary>
 761    /// Checks if an assembly has the [GenerateTypeRegistry] attribute.
 762    /// </summary>
 763    /// <param name="assembly">The assembly symbol to check.</param>
 764    /// <returns>True if the assembly has the attribute.</returns>
 765    public static bool HasGenerateTypeRegistryAttribute(IAssemblySymbol assembly)
 766    {
 767        const string attributeName = "NexusLabs.Needlr.Generators.GenerateTypeRegistryAttribute";
 768
 9990597769        foreach (var attribute in assembly.GetAttributes())
 770        {
 4744025771            var attrClass = attribute.AttributeClass;
 4744025772            if (attrClass == null)
 773                continue;
 774
 4744025775            if (attrClass.ToDisplayString() == attributeName)
 71776                return true;
 777        }
 778
 251238779        return false;
 780    }
 781
 782    /// <summary>
 783    /// Checks if a type is publicly accessible (can be referenced from generated code).
 784    /// A type is publicly accessible if it and all its containing types are public.
 785    /// </summary>
 786    /// <param name="typeSymbol">The type symbol to check.</param>
 787    /// <returns>True if the type is publicly accessible.</returns>
 788    private static bool IsPubliclyAccessible(INamedTypeSymbol typeSymbol)
 789    {
 790        // Check the type itself
 0791        if (typeSymbol.DeclaredAccessibility != Accessibility.Public)
 0792            return false;
 793
 794        // Check all containing types (for nested types)
 0795        var containingType = typeSymbol.ContainingType;
 0796        while (containingType != null)
 797        {
 0798            if (containingType.DeclaredAccessibility != Accessibility.Public)
 0799                return false;
 0800            containingType = containingType.ContainingType;
 801        }
 802
 0803        return true;
 804    }
 805
 806    /// <summary>
 807    /// Determines the injectable lifetime for a type by analyzing its attributes and constructors.
 808    /// Checks for explicit lifetime attributes first, then falls back to Singleton if injectable.
 809    /// </summary>
 810    /// <param name="typeSymbol">The type symbol to analyze.</param>
 811    /// <returns>The determined lifetime, or null if the type is not injectable.</returns>
 812    public static GeneratorLifetime? DetermineLifetime(INamedTypeSymbol typeSymbol)
 813    {
 814        // Check for DoNotInjectAttribute
 413678815        if (HasDoNotInjectAttribute(typeSymbol))
 1816            return null;
 817
 818        // Exclude open generic types (type definitions with type parameters like MyClass<T>)
 413677819        if (typeSymbol.TypeParameters.Length > 0)
 1820            return null;
 821
 822        // Types with [DeferToContainer] are always injectable as Singleton
 823        // (the attribute declares constructor params that will be added by another generator)
 413676824        if (HasDeferToContainerAttribute(typeSymbol))
 6825            return GetExplicitLifetime(typeSymbol) ?? GeneratorLifetime.Singleton;
 826
 827        // Get all instance constructors
 413670828        var constructors = typeSymbol.InstanceConstructors;
 829
 1470146830        foreach (var ctor in constructors)
 831        {
 832            // Skip static constructors
 439352833            if (ctor.IsStatic)
 834                continue;
 835
 439352836            var parameters = ctor.Parameters;
 837
 838            // Parameterless constructor is always valid
 439352839            if (parameters.Length == 0)
 190681840                return GetExplicitLifetime(typeSymbol) ?? GeneratorLifetime.Singleton;
 841
 842            // Single parameter of same type (copy constructor) - not injectable
 248671843            if (parameters.Length == 1 && SymbolEqualityComparer.Default.Equals(parameters[0].Type, typeSymbol))
 844                continue;
 845
 846            // Check if all parameters are injectable types
 241336847            if (AllParametersAreInjectable(parameters))
 45217848                return GetExplicitLifetime(typeSymbol) ?? GeneratorLifetime.Singleton;
 849        }
 850
 177772851        return null;
 852    }
 853
 854    /// <summary>
 855    /// Gets the explicit lifetime from attributes if specified.
 856    /// </summary>
 857    private static GeneratorLifetime? GetExplicitLifetime(INamedTypeSymbol typeSymbol)
 858    {
 1323924859        foreach (var attribute in typeSymbol.GetAttributes())
 860        {
 426090861            var attributeClass = attribute.AttributeClass;
 426090862            if (attributeClass == null)
 863                continue;
 864
 426090865            var name = attributeClass.Name;
 426090866            var fullName = attributeClass.ToDisplayString();
 867
 426090868            if (name == TransientAttributeName || fullName == TransientAttributeFullName)
 5869                return GeneratorLifetime.Transient;
 870
 426085871            if (name == ScopedAttributeName || fullName == ScopedAttributeFullName)
 36872                return GeneratorLifetime.Scoped;
 873
 426049874            if (name == SingletonAttributeName || fullName == SingletonAttributeFullName)
 23875                return GeneratorLifetime.Singleton;
 876        }
 877
 235840878        return null;
 879    }
 880
 881    private static bool AllParametersAreInjectable(System.Collections.Immutable.ImmutableArray<IParameterSymbol> paramet
 882    {
 2080070883        foreach (var param in parameters)
 884        {
 689992885            if (!IsInjectableParameterType(param.Type))
 407266886                return false;
 887        }
 146410888        return true;
 889    }
 890
 891    private static bool IsInjectableParameterType(ITypeSymbol typeSymbol)
 892    {
 893        // Must not be a delegate
 689992894        if (typeSymbol.TypeKind == TypeKind.Delegate)
 7336895            return false;
 896
 897        // Must not be a value type
 682656898        if (typeSymbol.IsValueType)
 191845899            return false;
 900
 901        // Must not be string
 490811902        if (typeSymbol.SpecialType == SpecialType.System_String)
 164829903            return false;
 904
 905        // Must be a class or interface
 325982906        if (typeSymbol.TypeKind != TypeKind.Class && typeSymbol.TypeKind != TypeKind.Interface)
 43256907            return false;
 908
 282726909        return true;
 910    }
 911
 912    private static bool HasDoNotInjectAttribute(INamedTypeSymbol typeSymbol)
 913    {
 2344265914        foreach (var attribute in typeSymbol.GetAttributes())
 915        {
 758455916            var attributeClass = attribute.AttributeClass;
 758455917            if (attributeClass == null)
 918                continue;
 919
 758455920            var name = attributeClass.Name;
 758455921            if (name == DoNotInjectAttributeName)
 1922                return true;
 923
 758454924            var fullName = attributeClass.ToDisplayString();
 758454925            if (fullName == DoNotInjectAttributeFullName)
 0926                return true;
 927        }
 928
 413677929        return false;
 930    }
 931
 932    /// <summary>
 933    /// Determines if a type is a valid plugin type (concrete class with parameterless constructor).
 934    /// </summary>
 935    /// <param name="typeSymbol">The type symbol to check.</param>
 936    /// <param name="isCurrentAssembly">True if the type is from the current compilation's assembly (allows internal typ
 937    /// <returns>True if the type is a valid plugin type.</returns>
 938    public static bool IsPluginType(INamedTypeSymbol typeSymbol, bool isCurrentAssembly = false)
 939    {
 940        // Must be a concrete class
 1457242941        if (typeSymbol.TypeKind != TypeKind.Class)
 547358942            return false;
 943
 944        // Must be accessible from generated code
 945        // - Current assembly: internal and public types are accessible
 946        // - Referenced assemblies: only public types are accessible
 909884947        if (!IsAccessibleFromGeneratedCode(typeSymbol, isCurrentAssembly))
 0948            return false;
 949
 909884950        if (typeSymbol.IsAbstract)
 139353951            return false;
 952
 770531953        if (typeSymbol.IsStatic)
 88673954            return false;
 955
 681858956        if (typeSymbol.IsUnboundGenericType)
 0957            return false;
 958
 959        // Exclude open generic types (type definitions with type parameters like MyClass<T>)
 960        // These cannot be instantiated directly and would produce invalid typeof() expressions
 681858961        if (typeSymbol.TypeParameters.Length > 0)
 40378962            return false;
 963
 964        // NOTE: Records ARE allowed as plugins (they are classes with parameterless constructors).
 965        // Records are excluded from IsInjectableType (auto-registration) but not from plugin discovery.
 966        // Use case: CacheConfiguration records can be discovered via IPluginFactory.CreatePluginsFromAssemblies<T>()
 967        // IMPORTANT: If the plugin type is emitted by a DIFFERENT source generator, TypeRegistryGenerator
 968        // cannot see it (Roslyn generators receive the original compilation in isolation). In that case,
 969        // the other generator should emit a [ModuleInitializer] that calls
 970        // NeedlrSourceGenBootstrap.RegisterPlugins(() => [...]) to contribute those types at runtime.
 971
 972        // Exclude hosted service types — they have their own dedicated registration path
 973        // (RegisterHostedServices) and must not be included in plugin types.
 641480974        if (InheritsFromBackgroundService(typeSymbol) || ImplementsIHostedService(typeSymbol))
 8975            return false;
 976
 977        // Must have a parameterless constructor
 641472978        if (!HasParameterlessConstructor(typeSymbol))
 236320979            return false;
 980
 981        // Exclude types with required members that can't be set via constructor
 982        // These would cause compilation errors: "Required member 'X' must be set"
 405152983        if (HasUnsatisfiedRequiredMembers(typeSymbol))
 2984            return false;
 985
 405150986        return true;
 987    }
 988
 989    /// <summary>
 990    /// Gets the plugin base types (interfaces and base classes) for a type.
 991    /// Plugin base types are non-System interfaces and non-System/non-object base classes.
 992    /// </summary>
 993    /// <param name="typeSymbol">The type symbol to check.</param>
 994    /// <param name="compilationAssembly">
 995    /// The compilation's assembly, used for same-assembly accessibility checks.
 996    /// See <see cref="IsAccessibleFromGeneratedCode(INamedTypeSymbol, IAssemblySymbol?)"/> for details.
 997    /// </param>
 998    /// <returns>A list of plugin base type symbols (interfaces and base classes).</returns>
 999    public static IReadOnlyList<INamedTypeSymbol> GetPluginInterfaces(
 1000        INamedTypeSymbol typeSymbol,
 1001        IAssemblySymbol? compilationAssembly = null)
 1002    {
 4732551003        var result = new List<INamedTypeSymbol>();
 1004
 1005        // Add non-system interfaces that are accessible from generated code.
 1006        //
 1007        // WHY THIS EXISTS: Same rules as GetRegisterableInterfaces — the generated
 1008        // TypeRegistry emits typeof() for each plugin interface. Same-assembly internal
 1009        // interfaces are valid (generated code is in the same compilation). Cross-assembly
 1010        // internal interfaces MUST be skipped to avoid CS0122.
 1011        //
 1012        // CRITICAL: Same-assembly internal interfaces MUST be kept. This is the standard
 1013        // pattern for internal plugin contracts within a single project.
 1014        //
 1015        // Example that MUST work (same assembly):
 1016        //   internal interface IMyPlugin { }
 1017        //   internal class MyPlugin : IMyPlugin { }
 1018        //   → typeof(IMyPlugin) in generated code is VALID
 1019        //
 1020        // Example that MUST be skipped (cross-assembly, e.g., Avalonia):
 1021        //   // In Framework.dll (internal):
 1022        //   internal interface IInternalHook { }
 1023        //   // In consumer app:
 1024        //   public class MyControl : Framework.BaseControl { } // inherits IInternalHook
 1025        //   → typeof(IInternalHook) in generated code produces CS0122
 15407921026        foreach (var iface in typeSymbol.AllInterfaces)
 1027        {
 2971411028            if (iface.IsUnboundGenericType)
 1029                continue;
 1030
 2971411031            if (IsSystemInterface(iface))
 1032                continue;
 1033
 2701034            if (!IsAccessibleFromGeneratedCode(iface, compilationAssembly))
 1035                continue;
 1036
 2641037            result.Add(iface);
 1038        }
 1039
 1040        // Add non-system base classes (walking up the hierarchy)
 4732551041        var baseType = typeSymbol.BaseType;
 4744171042        while (baseType != null)
 1043        {
 1044            // Stop at System.Object or System types
 4086301045            if (IsSystemType(baseType))
 1046                break;
 1047
 1048            // Skip inaccessible base types (same rules as interfaces)
 11621049            if (!IsAccessibleFromGeneratedCode(baseType, compilationAssembly))
 1050            {
 01051                baseType = baseType.BaseType;
 01052                continue;
 1053            }
 1054
 11621055            result.Add(baseType);
 11621056            baseType = baseType.BaseType;
 1057        }
 1058
 4732551059        return result;
 1060    }
 1061
 1062    /// <summary>
 1063    /// Checks if a type implements a specific interface by name.
 1064    /// </summary>
 1065    /// <param name="typeSymbol">The type symbol to check.</param>
 1066    /// <param name="interfaceFullName">The full name of the interface.</param>
 1067    /// <returns>True if the type implements the interface.</returns>
 1068    public static bool ImplementsInterface(INamedTypeSymbol typeSymbol, string interfaceFullName)
 1069    {
 01070        foreach (var iface in typeSymbol.AllInterfaces)
 1071        {
 01072            if (iface.ToDisplayString() == interfaceFullName)
 01073                return true;
 1074        }
 01075        return false;
 1076    }
 1077
 1078    /// <summary>
 1079    /// Checks if a type has a public parameterless constructor.
 1080    /// </summary>
 1081    /// <param name="typeSymbol">The type symbol to check.</param>
 1082    /// <returns>True if the type has a parameterless constructor.</returns>
 1083    public static bool HasParameterlessConstructor(INamedTypeSymbol typeSymbol)
 1084    {
 27354611085        foreach (var ctor in typeSymbol.InstanceConstructors)
 1086        {
 8146121087            if (ctor.DeclaredAccessibility == Accessibility.Public &&
 8146121088                ctor.Parameters.Length == 0)
 1089            {
 3159911090                return true;
 1091            }
 1092        }
 1093
 1094        // If no explicit constructors, the default constructor is available
 1095        // (unless there are other constructors with parameters)
 3951231096        if (typeSymbol.InstanceConstructors.Length == 0)
 1572691097            return true;
 1098
 2378541099        return false;
 1100    }
 1101
 1102    /// <summary>
 1103    /// Gets the attribute types applied to a plugin type.
 1104    /// </summary>
 1105    /// <param name="typeSymbol">The type symbol to check.</param>
 1106    /// <returns>A list of attribute type names (fully qualified).</returns>
 1107    public static IReadOnlyList<string> GetPluginAttributes(INamedTypeSymbol typeSymbol)
 1108    {
 14061109        var result = new List<string>();
 1110
 29341111        foreach (var attribute in typeSymbol.GetAttributes())
 1112        {
 611113            var attributeClass = attribute.AttributeClass;
 611114            if (attributeClass == null)
 1115                continue;
 1116
 1117            // Skip system attributes and compiler-generated attributes
 611118            var ns = attributeClass.ContainingNamespace?.ToDisplayString() ?? string.Empty;
 611119            if (ns.StartsWith("System.Runtime.CompilerServices", StringComparison.Ordinal))
 1120                continue;
 1121
 1122            // Include this attribute
 611123            var attributeName = GetFullyQualifiedName(attributeClass);
 611124            if (!result.Contains(attributeName))
 1125            {
 611126                result.Add(attributeName);
 1127            }
 1128        }
 1129
 1130        // Also check for inherited attributes from base types
 14061131        var baseType = typeSymbol.BaseType;
 60421132        while (baseType != null && baseType.SpecialType != SpecialType.System_Object)
 1133        {
 92721134            foreach (var attribute in baseType.GetAttributes())
 1135            {
 01136                var attributeClass = attribute.AttributeClass;
 01137                if (attributeClass == null)
 1138                    continue;
 1139
 1140                // Check if attribute is inherited
 01141                if (!IsInheritedAttribute(attributeClass))
 1142                    continue;
 1143
 01144                var ns = attributeClass.ContainingNamespace?.ToDisplayString() ?? string.Empty;
 01145                if (ns.StartsWith("System.Runtime.CompilerServices", StringComparison.Ordinal))
 1146                    continue;
 1147
 01148                var attributeName = GetFullyQualifiedName(attributeClass);
 01149                if (!result.Contains(attributeName))
 1150                {
 01151                    result.Add(attributeName);
 1152                }
 1153            }
 1154
 46361155            baseType = baseType.BaseType;
 1156        }
 1157
 14061158        return result;
 1159    }
 1160
 1161    /// <summary>
 1162    /// Checks if an attribute type has [AttributeUsage(Inherited = true)].
 1163    /// </summary>
 1164    private static bool IsInheritedAttribute(INamedTypeSymbol attributeClass)
 1165    {
 01166        foreach (var attr in attributeClass.GetAttributes())
 1167        {
 01168            if (attr.AttributeClass?.ToDisplayString() != "System.AttributeUsageAttribute")
 1169                continue;
 1170
 01171            foreach (var namedArg in attr.NamedArguments)
 1172            {
 01173                if (namedArg.Key == "Inherited" && namedArg.Value.Value is bool inherited)
 1174                {
 01175                    return inherited;
 1176                }
 1177            }
 1178
 1179            // Default for AttributeUsage is Inherited = true
 01180            return true;
 1181        }
 1182
 1183        // Default is Inherited = true
 01184        return true;
 1185    }
 1186
 1187    /// <summary>
 1188    /// Gets the parameters of the best injectable constructor for a type.
 1189    /// Returns the first constructor where all parameters are injectable types.
 1190    /// </summary>
 1191    /// <param name="typeSymbol">The type symbol to analyze.</param>
 1192    /// <returns>
 1193    /// A list of fully qualified parameter type names, or null if no injectable constructor was found.
 1194    /// </returns>
 1195    public static IReadOnlyList<string>? GetBestConstructorParameters(INamedTypeSymbol typeSymbol)
 1196    {
 1197        // Collect ALL satisfiable constructors, then pick the richest (most parameters).
 1198        // This matches the standard .NET DI behavior (ActivatorUtilities) where the
 1199        // constructor with the most resolvable parameters wins.
 231200        string[]? best = null;
 1201
 1001202        foreach (var ctor in typeSymbol.InstanceConstructors)
 1203        {
 271204            if (ctor.IsStatic)
 1205                continue;
 1206
 271207            if (ctor.DeclaredAccessibility != Accessibility.Public)
 1208                continue;
 1209
 271210            var parameters = ctor.Parameters;
 1211
 1212            // Single parameter of same type (copy constructor) - skip
 271213            if (parameters.Length == 1 && SymbolEqualityComparer.Default.Equals(parameters[0].Type, typeSymbol))
 1214                continue;
 1215
 1216            // Parameterless constructor is a valid candidate
 271217            if (parameters.Length == 0)
 1218            {
 171219                if (best == null)
 171220                    best = Array.Empty<string>();
 171221                continue;
 1222            }
 1223
 1224            // Check if all parameters are injectable
 101225            if (!AllParametersAreInjectable(parameters))
 1226                continue;
 1227
 1228            // This constructor is satisfiable — prefer it if it's richer
 61229            if (best == null || parameters.Length > best.Length)
 1230            {
 61231                var parameterTypes = new string[parameters.Length];
 261232                for (int i = 0; i < parameters.Length; i++)
 1233                {
 71234                    parameterTypes[i] = GetFullyQualifiedNameForType(parameters[i].Type);
 1235                }
 61236                best = parameterTypes;
 1237            }
 1238        }
 1239
 231240        return best;
 1241    }
 1242
 1243    /// <summary>
 1244    /// Gets the fully qualified name for any type symbol (including generics like Lazy&lt;T&gt;).
 1245    /// </summary>
 1246    private static string GetFullyQualifiedNameForType(ITypeSymbol typeSymbol)
 1247    {
 1100771248        return typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 1249    }
 1250
 1251    // NOTE: HasKernelFunctions was moved to NexusLabs.Needlr.SemanticKernel.Generators
 1252    // NOTE: HubRegistrationInfo was moved to NexusLabs.Needlr.SignalR.Generators
 1253
 1254    /// <summary>
 1255    /// Represents a constructor parameter with optional keyed service information.
 1256    /// </summary>
 1257    public readonly struct ConstructorParameterInfo
 1258    {
 1259        public ConstructorParameterInfo(string typeName, string? serviceKey = null, string? parameterName = null, string
 1260        {
 1101341261            TypeName = typeName;
 1101341262            ServiceKey = serviceKey;
 1101341263            ParameterName = parameterName;
 1101341264            DocumentationComment = documentationComment;
 1101341265        }
 1266
 1267        /// <summary>
 1268        /// The fully qualified type name of the parameter.
 1269        /// </summary>
 4795531270        public string TypeName { get; }
 1271
 1272        /// <summary>
 1273        /// The service key from [FromKeyedServices] attribute, or null if not a keyed service.
 1274        /// </summary>
 2893901275        public string? ServiceKey { get; }
 1276
 1277        /// <summary>
 1278        /// The original parameter name from the constructor (used for factory generation).
 1279        /// </summary>
 969551280        public string? ParameterName { get; }
 1281
 1282        /// <summary>
 1283        /// XML documentation comment for this parameter, extracted from the constructor's XML docs.
 1284        /// </summary>
 391285        public string? DocumentationComment { get; }
 1286
 1287        /// <summary>
 1288        /// True if this parameter should be resolved as a keyed service.
 1289        /// </summary>
 1926021290        public bool IsKeyed => ServiceKey is not null;
 1291    }
 1292
 1293    /// <summary>
 1294    /// Gets the parameters of the best injectable constructor for a type, including keyed service info.
 1295    /// Picks the constructor with the most satisfiable parameters (richest constructor wins),
 1296    /// matching the standard .NET DI behavior (ActivatorUtilities).
 1297    /// </summary>
 1298    /// <param name="typeSymbol">The type symbol to analyze.</param>
 1299    /// <returns>
 1300    /// A list of constructor parameter info, or null if no injectable constructor was found.
 1301    /// </returns>
 1302    public static IReadOnlyList<ConstructorParameterInfo>? GetBestConstructorParametersWithKeys(INamedTypeSymbol typeSym
 1303    {
 1304        const string FromKeyedServicesAttributeName = "Microsoft.Extensions.DependencyInjection.FromKeyedServicesAttribu
 1305
 2335431306        ConstructorParameterInfo[]? best = null;
 1307
 15054641308        foreach (var ctor in typeSymbol.InstanceConstructors)
 1309        {
 5191891310            if (ctor.IsStatic)
 1311                continue;
 1312
 5191891313            if (ctor.DeclaredAccessibility != Accessibility.Public)
 1314                continue;
 1315
 4956431316            var parameters = ctor.Parameters;
 1317
 1318            // Single parameter of same type (copy constructor) - skip
 4956431319            if (parameters.Length == 1 && SymbolEqualityComparer.Default.Equals(parameters[0].Type, typeSymbol))
 1320                continue;
 1321
 1322            // Parameterless constructor is a valid candidate
 4894671323            if (parameters.Length == 0)
 1324            {
 1771371325                if (best == null)
 1771371326                    best = Array.Empty<ConstructorParameterInfo>();
 1771371327                continue;
 1328            }
 1329
 1330            // Check if all parameters are injectable
 3123301331            if (!AllParametersAreInjectable(parameters))
 1332                continue;
 1333
 1334            // This constructor is satisfiable — prefer it if it's richer
 1011871335            if (best == null || parameters.Length > best.Length)
 1336            {
 861331337                var parameterInfos = new ConstructorParameterInfo[parameters.Length];
 3924061338                for (int i = 0; i < parameters.Length; i++)
 1339                {
 1100701340                    var param = parameters[i];
 1100701341                    var typeName = GetFullyQualifiedNameForType(param.Type);
 1100701342                    string? serviceKey = null;
 1343
 1344                    // Check for [FromKeyedServices("key")] attribute
 2479321345                    foreach (var attr in param.GetAttributes())
 1346                    {
 138961347                        var attrClass = attr.AttributeClass;
 138961348                        if (attrClass is null)
 1349                            continue;
 1350
 138961351                        var attrFullName = attrClass.ToDisplayString();
 138961352                        if (attrFullName == FromKeyedServicesAttributeName)
 1353                        {
 01354                            if (attr.ConstructorArguments.Length > 0)
 1355                            {
 01356                                var keyArg = attr.ConstructorArguments[0];
 01357                                if (keyArg.Value is string keyValue)
 1358                                {
 01359                                    serviceKey = keyValue;
 1360                                }
 1361                            }
 01362                            break;
 1363                        }
 1364                    }
 1365
 1100701366                    parameterInfos[i] = new ConstructorParameterInfo(typeName, serviceKey);
 1367                }
 861331368                best = parameterInfos;
 1369            }
 1370        }
 1371
 2335431372        return best;
 1373    }
 1374
 1375    /// <summary>
 1376    /// Gets the service keys from [Keyed] attributes on a type.
 1377    /// </summary>
 1378    /// <param name="typeSymbol">The type symbol to check.</param>
 1379    /// <returns>Array of service keys, or empty array if no [Keyed] attributes found.</returns>
 1380    public static string[] GetKeyedServiceKeys(INamedTypeSymbol typeSymbol)
 1381    {
 2335591382        var keys = new List<string>();
 1383
 13192421384        foreach (var attribute in typeSymbol.GetAttributes())
 1385        {
 4260621386            var attributeClass = attribute.AttributeClass;
 4260621387            if (attributeClass == null)
 1388                continue;
 1389
 4260621390            var name = attributeClass.Name;
 4260621391            var fullName = attributeClass.ToDisplayString();
 1392
 4260621393            if (name == KeyedAttributeName || fullName == KeyedAttributeFullName)
 1394            {
 1395                // Extract the key from the constructor argument
 51396                if (attribute.ConstructorArguments.Length > 0)
 1397                {
 51398                    var keyArg = attribute.ConstructorArguments[0];
 51399                    if (keyArg.Value is string keyValue)
 1400                    {
 51401                        keys.Add(keyValue);
 1402                    }
 1403                }
 1404            }
 1405        }
 1406
 2335591407        return keys.ToArray();
 1408    }
 1409
 1410    // NOTE: TryGetHubRegistrationInfo, TryGetPropertyStringValue, TryGetPropertyTypeValue
 1411    // were moved to NexusLabs.Needlr.SignalR.Generators
 1412
 1413    /// <summary>
 1414    /// Checks if a type has the <c>[DeferToContainer]</c> attribute.
 1415    /// </summary>
 1416    /// <param name="typeSymbol">The type symbol to check.</param>
 1417    /// <returns>True if the type has the DeferToContainer attribute.</returns>
 1418    public static bool HasDeferToContainerAttribute(INamedTypeSymbol typeSymbol)
 1419    {
 23442591420        foreach (var attribute in typeSymbol.GetAttributes())
 1421        {
 7584551422            var attrClass = attribute.AttributeClass;
 7584551423            if (attrClass is null)
 1424                continue;
 1425
 7584551426            var name = attrClass.Name;
 7584551427            var fullName = attrClass.ToDisplayString();
 1428
 7584551429            if (name == DeferToContainerAttributeName || fullName == DeferToContainerAttributeFullName)
 71430                return true;
 1431        }
 1432
 4136711433        return false;
 1434    }
 1435
 1436    /// <summary>
 1437    /// Gets the constructor parameter types declared in the <c>[DeferToContainer]</c> attribute.
 1438    /// </summary>
 1439    /// <param name="typeSymbol">The type symbol to check.</param>
 1440    /// <returns>
 1441    /// A list of fully qualified parameter type names from the attribute,
 1442    /// or null if the attribute is not present.
 1443    /// </returns>
 1444    public static IReadOnlyList<string>? GetDeferToContainerParameterTypes(INamedTypeSymbol typeSymbol)
 1445    {
 13191971446        foreach (var attribute in typeSymbol.GetAttributes())
 1447        {
 4260571448            var attrClass = attribute.AttributeClass;
 4260571449            if (attrClass is null)
 1450                continue;
 1451
 4260571452            var name = attrClass.Name;
 4260571453            var fullName = attrClass.ToDisplayString();
 1454
 4260571455            if (name != DeferToContainerAttributeName && fullName != DeferToContainerAttributeFullName)
 1456                continue;
 1457
 1458            // The attribute has a params Type[] constructor parameter
 1459            // Check constructor arguments
 91460            if (attribute.ConstructorArguments.Length == 0)
 01461                return Array.Empty<string>();
 1462
 91463            var arg = attribute.ConstructorArguments[0];
 1464
 1465            // params array is passed as a single array argument
 91466            if (arg.Kind == TypedConstantKind.Array)
 1467            {
 91468                var types = new List<string>();
 401469                foreach (var element in arg.Values)
 1470                {
 111471                    if (element.Value is INamedTypeSymbol namedType)
 1472                    {
 111473                        types.Add(GetFullyQualifiedName(namedType));
 1474                    }
 1475                }
 91476                return types;
 1477            }
 1478        }
 1479
 2335371480        return null;
 1481    }
 1482
 1483    /// <summary>
 1484    /// Result of decorator discovery.
 1485    /// </summary>
 1486    public readonly struct DecoratorInfo
 1487    {
 1488        public DecoratorInfo(string decoratorTypeName, string serviceTypeName, int order)
 1489        {
 191490            DecoratorTypeName = decoratorTypeName;
 191491            ServiceTypeName = serviceTypeName;
 191492            Order = order;
 191493        }
 1494
 191495        public string DecoratorTypeName { get; }
 191496        public string ServiceTypeName { get; }
 191497        public int Order { get; }
 1498    }
 1499
 1500    /// <summary>
 1501    /// Gets all DecoratorFor&lt;T&gt; attributes applied to a type.
 1502    /// </summary>
 1503    /// <param name="typeSymbol">The type symbol to check.</param>
 1504    /// <returns>A list of decorator info for each DecoratorFor attribute found.</returns>
 1505    public static IReadOnlyList<DecoratorInfo> GetDecoratorForAttributes(INamedTypeSymbol typeSymbol)
 1506    {
 14572361507        var result = new List<DecoratorInfo>();
 1508
 70026821509        foreach (var attribute in typeSymbol.GetAttributes())
 1510        {
 20441051511            var attrClass = attribute.AttributeClass;
 20441051512            if (attrClass is null)
 1513                continue;
 1514
 1515            // Check if this is a generic DecoratorForAttribute<T>
 20441051516            if (!attrClass.IsGenericType)
 1517                continue;
 1518
 341519            var unboundTypeName = attrClass.ConstructedFrom?.ToDisplayString();
 341520            if (unboundTypeName is null || !unboundTypeName.StartsWith(DecoratorForAttributePrefix, StringComparison.Ord
 1521                continue;
 1522
 1523            // Get the service type from the generic type argument
 191524            if (attrClass.TypeArguments.Length != 1)
 1525                continue;
 1526
 191527            var serviceType = attrClass.TypeArguments[0] as INamedTypeSymbol;
 191528            if (serviceType is null)
 1529                continue;
 1530
 191531            var serviceTypeName = GetFullyQualifiedName(serviceType);
 191532            var decoratorTypeName = GetFullyQualifiedName(typeSymbol);
 1533
 1534            // Get the Order property value
 191535            int order = 0;
 571536            foreach (var namedArg in attribute.NamedArguments)
 1537            {
 191538                if (namedArg.Key == "Order" && namedArg.Value.Value is int orderValue)
 1539                {
 191540                    order = orderValue;
 191541                    break;
 1542                }
 1543            }
 1544
 191545            result.Add(new DecoratorInfo(decoratorTypeName, serviceTypeName, order));
 1546        }
 1547
 14572361548        return result;
 1549    }
 1550
 1551    /// <summary>
 1552    /// Checks if a type has any DecoratorFor&lt;T&gt; attributes.
 1553    /// </summary>
 1554    /// <param name="typeSymbol">The type symbol to check.</param>
 1555    /// <returns>True if the type has at least one DecoratorFor attribute.</returns>
 1556    public static bool HasDecoratorForAttribute(INamedTypeSymbol typeSymbol)
 1557    {
 521558        foreach (var attribute in typeSymbol.GetAttributes())
 1559        {
 91560            var attrClass = attribute.AttributeClass;
 91561            if (attrClass is null)
 1562                continue;
 1563
 91564            if (!attrClass.IsGenericType)
 1565                continue;
 1566
 41567            var unboundTypeName = attrClass.ConstructedFrom?.ToDisplayString();
 41568            if (unboundTypeName is not null && unboundTypeName.StartsWith(DecoratorForAttributePrefix, StringComparison.
 21569                return true;
 1570        }
 1571
 161572        return false;
 1573    }
 1574}

Methods/Properties

IsInjectableType(Microsoft.CodeAnalysis.INamedTypeSymbol,System.Boolean)
GetRegisterableInterfaces(Microsoft.CodeAnalysis.INamedTypeSymbol,Microsoft.CodeAnalysis.IAssemblySymbol)
GetRegisterAsInterfaces(Microsoft.CodeAnalysis.INamedTypeSymbol)
GetConstructorParameterTypes(Microsoft.CodeAnalysis.INamedTypeSymbol)
IsDecoratorInterface(Microsoft.CodeAnalysis.INamedTypeSymbol,System.Collections.Generic.HashSet`1<System.String>)
GetFullyQualifiedName(Microsoft.CodeAnalysis.INamedTypeSymbol)
MatchesNamespacePrefix(Microsoft.CodeAnalysis.INamedTypeSymbol,System.Collections.Generic.IReadOnlyList`1<System.String>)
MatchesExclusionFilter(Microsoft.CodeAnalysis.INamedTypeSymbol,System.Collections.Generic.IReadOnlyList`1<System.String>)
IsNamespacePrefixMatch(System.String,System.String)
GetAllTypes()
HasDoNotAutoRegisterAttribute(Microsoft.CodeAnalysis.INamedTypeSymbol)
HasDoNotAutoRegisterAttributeDirect(Microsoft.CodeAnalysis.INamedTypeSymbol)
IsCompilerGenerated(Microsoft.CodeAnalysis.INamedTypeSymbol)
InheritsFrom(Microsoft.CodeAnalysis.INamedTypeSymbol,System.String)
IsMauiPlatformEntryType(Microsoft.CodeAnalysis.INamedTypeSymbol)
IsSystemInterface(Microsoft.CodeAnalysis.INamedTypeSymbol)
IsHostedServiceInterface(Microsoft.CodeAnalysis.INamedTypeSymbol)
IsHostedServiceType(Microsoft.CodeAnalysis.INamedTypeSymbol,System.Boolean)
IsDecoratorForHostedService(Microsoft.CodeAnalysis.INamedTypeSymbol)
InheritsFromBackgroundService(Microsoft.CodeAnalysis.INamedTypeSymbol)
ImplementsIHostedService(Microsoft.CodeAnalysis.INamedTypeSymbol)
IsSystemType(Microsoft.CodeAnalysis.INamedTypeSymbol)
HasUnsatisfiedRequiredMembers(Microsoft.CodeAnalysis.INamedTypeSymbol)
IsAccessibleFromGeneratedCode(Microsoft.CodeAnalysis.INamedTypeSymbol,System.Boolean)
WouldBeInjectableIgnoringAccessibility(Microsoft.CodeAnalysis.INamedTypeSymbol)
WouldBePluginIgnoringAccessibility(Microsoft.CodeAnalysis.INamedTypeSymbol,Microsoft.CodeAnalysis.IAssemblySymbol)
IsInternalOrLessAccessible(Microsoft.CodeAnalysis.INamedTypeSymbol)
IsAccessibleFromGeneratedCode(Microsoft.CodeAnalysis.INamedTypeSymbol,Microsoft.CodeAnalysis.IAssemblySymbol)
IsAccessibleCore(Microsoft.CodeAnalysis.INamedTypeSymbol,Microsoft.CodeAnalysis.IAssemblySymbol)
IsDisposableType(Microsoft.CodeAnalysis.INamedTypeSymbol)
.cctor()
ImplementsNeedlrPluginInterface(Microsoft.CodeAnalysis.INamedTypeSymbol)
GetNeedlrPluginInterfaceName(Microsoft.CodeAnalysis.INamedTypeSymbol)
HasGenerateTypeRegistryAttribute(Microsoft.CodeAnalysis.IAssemblySymbol)
IsPubliclyAccessible(Microsoft.CodeAnalysis.INamedTypeSymbol)
DetermineLifetime(Microsoft.CodeAnalysis.INamedTypeSymbol)
GetExplicitLifetime(Microsoft.CodeAnalysis.INamedTypeSymbol)
AllParametersAreInjectable(System.Collections.Immutable.ImmutableArray`1<Microsoft.CodeAnalysis.IParameterSymbol>)
IsInjectableParameterType(Microsoft.CodeAnalysis.ITypeSymbol)
HasDoNotInjectAttribute(Microsoft.CodeAnalysis.INamedTypeSymbol)
IsPluginType(Microsoft.CodeAnalysis.INamedTypeSymbol,System.Boolean)
GetPluginInterfaces(Microsoft.CodeAnalysis.INamedTypeSymbol,Microsoft.CodeAnalysis.IAssemblySymbol)
ImplementsInterface(Microsoft.CodeAnalysis.INamedTypeSymbol,System.String)
HasParameterlessConstructor(Microsoft.CodeAnalysis.INamedTypeSymbol)
GetPluginAttributes(Microsoft.CodeAnalysis.INamedTypeSymbol)
IsInheritedAttribute(Microsoft.CodeAnalysis.INamedTypeSymbol)
GetBestConstructorParameters(Microsoft.CodeAnalysis.INamedTypeSymbol)
GetFullyQualifiedNameForType(Microsoft.CodeAnalysis.ITypeSymbol)
.ctor(System.String,System.String,System.String,System.String)
get_TypeName()
get_ServiceKey()
get_ParameterName()
get_DocumentationComment()
get_IsKeyed()
GetBestConstructorParametersWithKeys(Microsoft.CodeAnalysis.INamedTypeSymbol)
GetKeyedServiceKeys(Microsoft.CodeAnalysis.INamedTypeSymbol)
HasDeferToContainerAttribute(Microsoft.CodeAnalysis.INamedTypeSymbol)
GetDeferToContainerParameterTypes(Microsoft.CodeAnalysis.INamedTypeSymbol)
.ctor(System.String,System.String,System.Int32)
get_DecoratorTypeName()
get_ServiceTypeName()
get_Order()
GetDecoratorForAttributes(Microsoft.CodeAnalysis.INamedTypeSymbol)
HasDecoratorForAttribute(Microsoft.CodeAnalysis.INamedTypeSymbol)