< 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
89%
Covered lines: 429
Uncovered lines: 53
Coverable lines: 482
Total lines: 1568
Line coverage: 89%
Branch coverage
84%
Covered branches: 412
Total branches: 486
Branch coverage: 84.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%1010100%
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%
HasGenerateTypeRegistryAttribute(...)100%66100%
DetermineLifetime(...)100%2222100%
GetExplicitLifetime(...)100%1616100%
AllParametersAreInjectable(...)100%44100%
IsInjectableParameterType(...)100%1010100%
HasDoNotInjectAttribute(...)87.5%8890%
IsPluginType(...)90%202089.47%
GetPluginInterfaces(...)92.85%141486.66%
HasParameterlessConstructor(...)100%1010100%
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)
 158188248        => 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
 25314169        var registerAsInterfaces = GetRegisterAsInterfaces(typeSymbol);
 25314170        if (registerAsInterfaces.Count > 0)
 71        {
 072            return registerAsInterfaces;
 73        }
 74
 25314175        var result = new List<INamedTypeSymbol>();
 76
 77        // Get all constructor parameter types to detect decorator pattern
 25314178        var constructorParamTypes = GetConstructorParameterTypes(typeSymbol);
 79
 96714080        foreach (var iface in typeSymbol.AllInterfaces)
 81        {
 23042982            if (iface.IsUnboundGenericType)
 83                continue;
 84
 23042985            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
 477110            if (!IsAccessibleFromGeneratedCode(iface, compilationAssembly))
 111                continue;
 112
 471113            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
 469119            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
 440124            if (IsHostedServiceInterface(iface))
 125                continue;
 126
 432127            result.Add(iface);
 128        }
 129
 253141130        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    {
 253141140        var result = new List<INamedTypeSymbol>();
 141
 1429330142        foreach (var attribute in typeSymbol.GetAttributes())
 143        {
 461524144            var attrClass = attribute.AttributeClass;
 461524145            if (attrClass == null)
 146                continue;
 147
 148            // Check for RegisterAsAttribute<T>
 461524149            var attrFullName = attrClass.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 461524150            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
 253141163        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    {
 253141171        var paramTypes = new HashSet<string>(StringComparer.Ordinal);
 172
 173        // A type eligible for generated-constructor generation has its constructor
 174        // emitted by a sibling GeneratedConstructorGenerator pass this compilation
 175        // can't see yet, so the symbol-based scan below would only see the implicit
 176        // parameterless constructor and incorrectly report zero parameter types. Use
 177        // the same field-derived model instead, so decorator-interface classification
 178        // for a generated constructor matches a hand-written one. This intentionally
 179        // considers every eligible field (not just container-resolvable ones), since a
 180        // field's suitability as a decorator-pattern parameter is independent of
 181        // whether it happens to also be injectable.
 253141182        var generatedModel = ConstructorGenerationDiscoveryHelper.TryGetModel(typeSymbol);
 253141183        if (generatedModel != null)
 184        {
 56185            foreach (var field in generatedModel.Value.Fields)
 186            {
 187                // ParameterTypeName preserves nullable-reference annotations (e.g. a
 188                // trailing '?'), while the interface names compared against it in
 189                // IsDecoratorInterface do not carry that annotation. Trim it so the
 190                // comparison lines up for nullable decorator-pattern fields.
 17191                paramTypes.Add(field.ParameterTypeName.TrimEnd('?'));
 192            }
 193
 11194            return paramTypes;
 195        }
 196
 1631282197        foreach (var ctor in typeSymbol.InstanceConstructors)
 198        {
 562511199            if (ctor.IsStatic)
 200                continue;
 201
 2415654202            foreach (var param in ctor.Parameters)
 203            {
 645316204                var paramTypeName = param.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 645316205                paramTypes.Add(paramTypeName);
 206            }
 207        }
 208
 253130209        return paramTypes;
 210    }
 211
 212    /// <summary>
 213    /// Checks if an interface is a decorator interface (also taken as a constructor parameter).
 214    /// </summary>
 215    private static bool IsDecoratorInterface(INamedTypeSymbol iface, HashSet<string> constructorParamTypes)
 216    {
 469217        var ifaceName = iface.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 469218        return constructorParamTypes.Contains(ifaceName);
 219    }
 220
 221    /// <summary>
 222    /// Gets the fully qualified name for a type symbol suitable for code generation.
 223    /// For generic type definitions (open generics), outputs open generic syntax (e.g., MyClass&lt;&gt;).
 224    /// For constructed generics with concrete type arguments, outputs the full type (e.g., MyClass&lt;int&gt;).
 225    /// </summary>
 226    /// <param name="typeSymbol">The type symbol.</param>
 227    /// <returns>The fully qualified type name with global:: prefix.</returns>
 228    public static string GetFullyQualifiedName(INamedTypeSymbol typeSymbol)
 229    {
 230        // Check if this is an open generic type definition (has type parameters, not type arguments)
 231        // e.g., JobScheduler<TJob> where TJob is a TypeParameter, not a concrete type
 232        // We need to convert these to open generic syntax: JobScheduler<>
 4595409233        if (typeSymbol.TypeParameters.Length > 0 && !typeSymbol.IsUnboundGenericType)
 234        {
 235            // Check if type arguments are still type parameters (meaning this is a generic definition)
 236            // For a closed generic like ILogger<MyService>, TypeArguments contains MyService (a NamedTypeSymbol)
 237            // For an open generic like JobScheduler<TJob>, TypeArguments contains TJob (a TypeParameterSymbol)
 446274238            var hasUnresolvedTypeParameters = typeSymbol.TypeArguments.Any(ta => ta.TypeKind == TypeKind.TypeParameter);
 239
 219765240            if (hasUnresolvedTypeParameters)
 241            {
 242                // Build the open generic name manually
 66060243                var containingNamespace = typeSymbol.ContainingNamespace?.ToDisplayString();
 66060244                var typeName = typeSymbol.Name;
 66060245                var arity = typeSymbol.TypeParameters.Length;
 246
 247                // Create the open generic syntax: MyClass<,> for 2 type params, MyClass<> for 1
 66060248                var commas = arity > 1 ? new string(',', arity - 1) : string.Empty;
 66060249                var openGenericPart = $"<{commas}>";
 250
 66060251                if (string.IsNullOrEmpty(containingNamespace) || containingNamespace == "<global namespace>")
 252                {
 0253                    return $"global::{typeName}{openGenericPart}";
 254                }
 255
 66060256                return $"global::{containingNamespace}.{typeName}{openGenericPart}";
 257            }
 258        }
 259
 4529349260        return typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 261    }
 262
 263    /// <summary>
 264    /// Checks if a type matches any of the given namespace prefixes.
 265    /// </summary>
 266    /// <param name="typeSymbol">The type symbol to check.</param>
 267    /// <param name="namespacePrefixes">The namespace prefixes to match.</param>
 268    /// <returns>True if the type's namespace starts with any of the prefixes.</returns>
 269    public static bool MatchesNamespacePrefix(INamedTypeSymbol typeSymbol, IReadOnlyList<string>? namespacePrefixes)
 270    {
 2252125271        if (namespacePrefixes == null || namespacePrefixes.Count == 0)
 1660300272            return true;
 273
 591825274        var typeNamespace = typeSymbol.ContainingNamespace?.ToDisplayString() ?? string.Empty;
 275
 276        // Check if type is in the global namespace
 591825277        var isGlobalNamespace = typeSymbol.ContainingNamespace?.IsGlobalNamespace == true;
 278
 2382084279        foreach (var prefix in namespacePrefixes)
 280        {
 281            // Empty string prefix matches global namespace types
 599735282            if (string.IsNullOrEmpty(prefix))
 283            {
 7991284                if (isGlobalNamespace)
 346285                    return true;
 286                continue;
 287            }
 288
 591744289            if (IsNamespacePrefixMatch(typeNamespace, prefix))
 690290                return true;
 291        }
 292
 590789293        return false;
 1036294    }
 295
 296    /// <summary>
 297    /// Checks if a type matches any of the given exclusion namespace prefixes.
 298    /// Returns <c>true</c> if the type should be EXCLUDED (its namespace starts with
 299    /// any of the exclusion prefixes).
 300    /// </summary>
 301    /// <param name="typeSymbol">The type symbol to check.</param>
 302    /// <param name="excludeNamespacePrefixes">The namespace prefixes to exclude. If null or empty, nothing is excluded.
 303    /// <returns>True if the type should be excluded from the registry.</returns>
 304    public static bool MatchesExclusionFilter(INamedTypeSymbol typeSymbol, IReadOnlyList<string>? excludeNamespacePrefix
 305    {
 1661333306        if (excludeNamespacePrefixes == null || excludeNamespacePrefixes.Count == 0)
 1661333307            return false;
 308
 0309        var typeNamespace = typeSymbol.ContainingNamespace?.ToDisplayString() ?? string.Empty;
 310
 0311        foreach (var prefix in excludeNamespacePrefixes)
 312        {
 0313            if (string.IsNullOrEmpty(prefix))
 314                continue;
 315
 0316            if (IsNamespacePrefixMatch(typeNamespace, prefix))
 0317                return true;
 318        }
 319
 0320        return false;
 0321    }
 322
 323    /// <summary>
 324    /// Dot-boundary-aware prefix match. <c>"Avalonia"</c> matches <c>"Avalonia"</c>
 325    /// and <c>"Avalonia.Controls"</c> but NOT <c>"AvaloniaDemoApp"</c>.
 326    /// A prefix ending with <c>"."</c> requires an exact sub-namespace match
 327    /// (e.g., <c>"Avalonia."</c> matches only <c>"Avalonia.Controls"</c>, not <c>"Avalonia"</c> itself).
 328    /// </summary>
 329    private static bool IsNamespacePrefixMatch(string typeNamespace, string prefix)
 330    {
 591744331        if (!typeNamespace.StartsWith(prefix, StringComparison.Ordinal))
 591054332            return false;
 333
 334        // Exact match (namespace equals prefix)
 690335        if (typeNamespace.Length == prefix.Length)
 684336            return true;
 337
 338        // Prefix already ends with dot — the StartsWith check is sufficient
 339        // (e.g., prefix "Avalonia." matches "Avalonia.Controls")
 6340        if (prefix[prefix.Length - 1] == '.')
 0341            return true;
 342
 343        // Sub-namespace: next char after prefix must be a dot
 344        // "Avalonia" matches "Avalonia.Controls" but not "AvaloniaDemoApp"
 6345        return typeNamespace[prefix.Length] == '.';
 346    }
 347
 348    /// <summary>
 349    /// Recursively iterates all named type symbols in a namespace.
 350    /// </summary>
 351    /// <param name="namespaceSymbol">The namespace to iterate.</param>
 352    /// <returns>All named type symbols in the namespace and nested namespaces.</returns>
 353    public static IEnumerable<INamedTypeSymbol> GetAllTypes(INamespaceSymbol namespaceSymbol)
 354    {
 13415620355        foreach (var member in namespaceSymbol.GetMembers())
 356        {
 5509063357            if (member is INamedTypeSymbol typeSymbol)
 358            {
 4502157359                yield return typeSymbol;
 360            }
 1006906361            else if (member is INamespaceSymbol nestedNamespace)
 362            {
 24987970363                foreach (var nestedType in GetAllTypes(nestedNamespace))
 364                {
 11487079365                    yield return nestedType;
 366                }
 367            }
 368        }
 1198747369    }
 370
 371    private static bool HasDoNotAutoRegisterAttribute(INamedTypeSymbol typeSymbol)
 4200372        => SharedHelper.HasDoNotAutoRegisterAttribute(typeSymbol);
 373
 374    private static bool HasDoNotAutoRegisterAttributeDirect(INamedTypeSymbol typeSymbol)
 837544375        => SharedHelper.HasDoNotAutoRegisterAttributeDirect(typeSymbol);
 376
 377    private static bool IsCompilerGenerated(INamedTypeSymbol typeSymbol)
 914977378        => SharedHelper.IsCompilerGenerated(typeSymbol);
 379
 380    private static bool InheritsFrom(INamedTypeSymbol typeSymbol, string baseTypeName)
 4994066381        => SharedHelper.InheritsFrom(typeSymbol, baseTypeName);
 382
 383    /// <summary>
 384    /// Determines whether a type is a .NET MAUI per-platform application entry point — the Windows
 385    /// <c>App : Microsoft.Maui.MauiWinUIApplication</c>, the Android
 386    /// <c>MainApplication : Microsoft.Maui.MauiApplication</c>, or the iOS/Mac
 387    /// <c>AppDelegate : Microsoft.Maui.MauiUIApplicationDelegate</c> that live under <c>Platforms/</c>.
 388    /// </summary>
 389    /// <remarks>
 390    /// These types are framework-owned and constructed by the MAUI platform host; they are never
 391    /// resolved as Needlr services. They are also decorated by the platform's own source generators
 392    /// with interop members (for example WinRT plumbing such as <c>ApplicationRcwFactoryAttribute</c>)
 393    /// that are inaccessible from generated code, so including them in the registry breaks the head
 394    /// build. They are therefore excluded from all discovery. The cross-platform
 395    /// <c>App : Microsoft.Maui.Controls.Application</c>, pages, views, and view models are ordinary
 396    /// types and remain scannable.
 397    /// </remarks>
 398    /// <param name="typeSymbol">The type symbol to check.</param>
 399    /// <returns><see langword="true"/> if the type derives from a MAUI platform application base type.</returns>
 400    public static bool IsMauiPlatformEntryType(INamedTypeSymbol typeSymbol)
 1661333401        => InheritsFrom(typeSymbol, "Microsoft.Maui.MauiWinUIApplication")
 1661333402        || InheritsFrom(typeSymbol, "Microsoft.Maui.MauiApplication")
 1661333403        || InheritsFrom(typeSymbol, "Microsoft.Maui.MauiUIApplicationDelegate");
 404
 405    private static bool IsSystemInterface(INamedTypeSymbol interfaceSymbol)
 552355406        => SharedHelper.IsSystemType(interfaceSymbol);
 407
 408    private static bool IsHostedServiceInterface(INamedTypeSymbol interfaceSymbol)
 409    {
 440410        var fullName = GetFullyQualifiedName(interfaceSymbol);
 440411        return fullName == "global::Microsoft.Extensions.Hosting.IHostedService";
 412    }
 413
 414    /// <summary>
 415    /// Determines whether a type is a hosted service (implements IHostedService or inherits from BackgroundService).
 416    /// </summary>
 417    /// <param name="typeSymbol">The type symbol to check.</param>
 418    /// <param name="isCurrentAssembly">True if the type is from the current compilation's assembly.</param>
 419    /// <returns>True if the type is a hosted service.</returns>
 420    public static bool IsHostedServiceType(INamedTypeSymbol typeSymbol, bool isCurrentAssembly = false)
 421    {
 422        // Must be a concrete, non-abstract class
 1581865423        if (typeSymbol.IsAbstract || typeSymbol.TypeKind != TypeKind.Class)
 744792424            return false;
 425
 426        // Check accessibility
 837073427        if (!isCurrentAssembly && IsInternalOrLessAccessible(typeSymbol))
 0428            return false;
 429
 430        // Skip if marked with [DoNotAutoRegister]
 837073431        if (HasDoNotAutoRegisterAttributeDirect(typeSymbol))
 8432            return false;
 433
 434        // Skip compiler-generated types
 837065435        if (IsCompilerGenerated(typeSymbol))
 0436            return false;
 437
 438        // Skip decorators - types with [DecoratorFor<IHostedService>] should not be
 439        // registered as hosted services (they decorate hosted services, not are hosted services)
 837065440        if (IsDecoratorForHostedService(typeSymbol))
 0441            return false;
 442
 443        // Check if inherits from BackgroundService
 837065444        if (InheritsFromBackgroundService(typeSymbol))
 8445            return true;
 446
 447        // Check if directly implements IHostedService (not via BackgroundService)
 837057448        if (ImplementsIHostedService(typeSymbol))
 1449            return true;
 450
 837056451        return false;
 452    }
 453
 454    private static bool IsDecoratorForHostedService(INamedTypeSymbol typeSymbol)
 455    {
 4830638456        foreach (var attribute in typeSymbol.GetAttributes())
 457        {
 1578254458            var attrClass = attribute.AttributeClass;
 1578254459            if (attrClass == null)
 460                continue;
 461
 1578254462            var attrFullName = attrClass.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 463
 464            // Check for DecoratorForAttribute<IHostedService>
 1578254465            if (attrFullName.StartsWith("global::NexusLabs.Needlr.DecoratorForAttribute<", StringComparison.Ordinal))
 466            {
 467                // Get the type argument
 19468                if (attrClass.IsGenericType && attrClass.TypeArguments.Length == 1)
 469                {
 19470                    var typeArg = attrClass.TypeArguments[0];
 19471                    if (typeArg is INamedTypeSymbol namedTypeArg)
 472                    {
 19473                        var typeArgName = GetFullyQualifiedName(namedTypeArg);
 19474                        if (typeArgName == "global::Microsoft.Extensions.Hosting.IHostedService")
 0475                            return true;
 476                    }
 477                }
 478            }
 479        }
 837065480        return false;
 481    }
 482
 483    private static bool InheritsFromBackgroundService(INamedTypeSymbol typeSymbol)
 484    {
 1534307485        var baseType = typeSymbol.BaseType;
 4591754486        while (baseType != null)
 487        {
 3057464488            var fullName = GetFullyQualifiedName(baseType);
 3057464489            if (fullName == "global::Microsoft.Extensions.Hosting.BackgroundService")
 17490                return true;
 3057447491            baseType = baseType.BaseType;
 492        }
 1534290493        return false;
 494    }
 495
 496    private static bool ImplementsIHostedService(INamedTypeSymbol typeSymbol)
 497    {
 5217418498        foreach (var iface in typeSymbol.AllInterfaces)
 499        {
 1074420500            var fullName = GetFullyQualifiedName(iface);
 1074420501            if (fullName == "global::Microsoft.Extensions.Hosting.IHostedService")
 2502                return true;
 503        }
 1534288504        return false;
 505    }
 506
 507    private static bool IsSystemType(INamedTypeSymbol typeSymbol)
 443920508        => SharedHelper.IsSystemType(typeSymbol);
 509
 510    private static bool HasUnsatisfiedRequiredMembers(INamedTypeSymbol typeSymbol)
 440134511        => SharedHelper.HasUnsatisfiedRequiredMembers(typeSymbol);
 512
 513    /// <summary>
 514    /// Checks if a type is accessible from generated code.
 515    /// For types in the current assembly, internal and public types are accessible.
 516    /// For types in referenced assemblies, only public types are accessible.
 517    /// </summary>
 518    /// <param name="typeSymbol">The type symbol to check.</param>
 519    /// <param name="isCurrentAssembly">True if the type is from the current compilation's assembly.</param>
 520    /// <returns>True if the type is accessible from generated code.</returns>
 521    private static bool IsAccessibleFromGeneratedCode(INamedTypeSymbol typeSymbol, bool isCurrentAssembly)
 987986522        => SharedHelper.IsAccessibleFromGeneratedCode(typeSymbol, isCurrentAssembly);
 523
 524    /// <summary>
 525    /// Checks if a type would be registerable as injectable, ignoring accessibility constraints.
 526    /// This is used to detect internal types that match namespace filters but cannot be included.
 527    /// </summary>
 528    /// <param name="typeSymbol">The type symbol to check.</param>
 529    /// <returns>True if the type would be injectable if it were accessible.</returns>
 530    public static bool WouldBeInjectableIgnoringAccessibility(INamedTypeSymbol typeSymbol)
 531    {
 532        // Must be a class (not interface, struct, enum, delegate)
 79474533        if (typeSymbol.TypeKind != TypeKind.Class)
 46534            return false;
 535
 79428536        if (typeSymbol.IsAbstract)
 0537            return false;
 538
 79428539        if (typeSymbol.IsStatic)
 1296540            return false;
 541
 78132542        if (typeSymbol.IsUnboundGenericType)
 0543            return false;
 544
 545        // Exclude open generic types (type definitions with type parameters like MyClass<T>)
 78132546        if (typeSymbol.TypeParameters.Length > 0)
 220547            return false;
 548
 77912549        if (typeSymbol.ContainingType != null)
 0550            return false;
 551
 77912552        if (IsCompilerGenerated(typeSymbol))
 72876553            return false;
 554
 5036555        if (InheritsFrom(typeSymbol, "System.Exception"))
 0556            return false;
 557
 5036558        if (InheritsFrom(typeSymbol, "System.Attribute"))
 836559            return false;
 560
 4200561        if (typeSymbol.IsRecord)
 0562            return false;
 563
 4200564        if (HasDoNotAutoRegisterAttribute(typeSymbol))
 4565            return false;
 566
 567        // Must have a determinable lifetime to be injectable
 4196568        var lifetime = DetermineLifetime(typeSymbol);
 4196569        if (!lifetime.HasValue)
 1672570            return false;
 571
 2524572        return true;
 573    }
 574
 575    /// <summary>
 576    /// Checks if a type would be registerable as a plugin, ignoring accessibility constraints.
 577    /// This is used to detect internal types that match namespace filters but cannot be included.
 578    /// </summary>
 579    /// <param name="typeSymbol">The type symbol to check.</param>
 580    /// <param name="compilationAssembly">
 581    /// The compilation's assembly for same-assembly accessibility checks.
 582    /// Passed through to <see cref="GetPluginInterfaces"/> so that same-assembly internal
 583    /// interfaces are correctly recognized as plugin interfaces.
 584    /// </param>
 585    /// <returns>True if the type would be a plugin if it were accessible.</returns>
 586    public static bool WouldBePluginIgnoringAccessibility(
 587        INamedTypeSymbol typeSymbol,
 588        IAssemblySymbol? compilationAssembly = null)
 589    {
 590        // Must be a concrete class
 76968591        if (typeSymbol.TypeKind != TypeKind.Class)
 46592            return false;
 593
 76922594        if (typeSymbol.IsAbstract)
 0595            return false;
 596
 76922597        if (typeSymbol.IsStatic)
 1296598            return false;
 599
 75626600        if (typeSymbol.IsUnboundGenericType)
 0601            return false;
 602
 603        // Exclude open generic types (type definitions with type parameters like MyClass<T>)
 75626604        if (typeSymbol.TypeParameters.Length > 0)
 220605            return false;
 606
 607        // NOTE: Records ARE allowed as plugins (they are classes with parameterless constructors).
 608        // Records are excluded from IsInjectableType (auto-registration) but not from plugin discovery.
 609
 610        // Must have a parameterless constructor
 75406611        if (!HasParameterlessConstructor(typeSymbol))
 1662612            return false;
 613
 614        // Must have at least one plugin interface
 73744615        var pluginInterfaces = GetPluginInterfaces(typeSymbol, compilationAssembly);
 73744616        return pluginInterfaces.Count > 0;
 617    }
 618
 619    /// <summary>
 620    /// Checks if a type is internal (not public) and would be inaccessible from generated code
 621    /// in a different assembly.
 622    /// </summary>
 623    /// <param name="typeSymbol">The type symbol to check.</param>
 624    /// <returns>True if the type is internal or less accessible.</returns>
 625    public static bool IsInternalOrLessAccessible(INamedTypeSymbol typeSymbol)
 626    {
 627        // Check the type itself
 4745883628        if (typeSymbol.DeclaredAccessibility != Accessibility.Public)
 186061629            return true;
 630
 631        // Check all containing types (for nested types)
 4559822632        var containingType = typeSymbol.ContainingType;
 4559822633        while (containingType != null)
 634        {
 0635            if (containingType.DeclaredAccessibility != Accessibility.Public)
 0636                return true;
 0637            containingType = containingType.ContainingType;
 638        }
 639
 4559822640        return false;
 641    }
 642
 643    /// <summary>
 644    /// Determines whether a type symbol is accessible from generated code emitted into
 645    /// the given compilation assembly.
 646    /// </summary>
 647    /// <remarks>
 648    /// <para>
 649    /// The generated TypeRegistry is emitted into the compilation's assembly. It can
 650    /// reference any type that is accessible from that assembly:
 651    /// </para>
 652    /// <list type="bullet">
 653    /// <item><c>public</c> types — always accessible.</item>
 654    /// <item><c>internal</c> / <c>protected internal</c> types — accessible only when
 655    /// they belong to the same assembly as the compilation (same-assembly check).</item>
 656    /// <item><c>private</c> / <c>protected</c> / <c>private protected</c> types — never
 657    /// accessible from generated top-level code (even in the same assembly, generated code
 658    /// is not inside the containing type's hierarchy).</item>
 659    /// </list>
 660    /// <para>
 661    /// Containing types are checked recursively for nested types.
 662    /// </para>
 663    /// </remarks>
 664    /// <param name="typeSymbol">The type symbol to check.</param>
 665    /// <param name="compilationAssembly">
 666    /// The compilation's output assembly. When <see langword="null"/>, falls back to
 667    /// the conservative behavior of <see cref="IsInternalOrLessAccessible"/> (i.e.,
 668    /// any non-public type is considered inaccessible).
 669    /// </param>
 670    /// <returns><see langword="true"/> if the type is accessible from generated code.</returns>
 671    public static bool IsAccessibleFromGeneratedCode(
 672        INamedTypeSymbol typeSymbol,
 673        IAssemblySymbol? compilationAssembly)
 674    {
 2154675        return IsAccessibleCore(typeSymbol, compilationAssembly);
 676    }
 677
 678    private static bool IsAccessibleCore(
 679        INamedTypeSymbol typeSymbol,
 680        IAssemblySymbol? compilationAssembly)
 681    {
 2154682        var accessibility = typeSymbol.DeclaredAccessibility;
 683
 2154684        if (accessibility == Accessibility.Public)
 685        {
 686            // Public is universally accessible, but check containing types for nested types
 2124687            return typeSymbol.ContainingType == null ||
 2124688                   IsAccessibleCore(typeSymbol.ContainingType, compilationAssembly);
 689        }
 690
 691        // internal and protected-internal are accessible from the same assembly.
 692        // The generated code lives in the compilation assembly, so if the type is
 693        // also in that assembly, we can emit typeof() for it.
 30694        if (accessibility == Accessibility.Internal ||
 30695            accessibility == Accessibility.ProtectedOrInternal)
 696        {
 24697            bool isSameAssembly = compilationAssembly != null &&
 24698                SymbolEqualityComparer.Default.Equals(
 24699                    typeSymbol.ContainingAssembly, compilationAssembly);
 700
 24701            if (isSameAssembly)
 702            {
 703                // Same assembly — accessible via internal path.
 704                // Still need to check containing types for nested types.
 18705                return typeSymbol.ContainingType == null ||
 18706                       IsAccessibleCore(typeSymbol.ContainingType, compilationAssembly);
 707            }
 708        }
 709
 710        // private, protected, private-protected, or cross-assembly internal
 711        // → inaccessible from generated code.
 12712        return false;
 713    }
 714
 715    /// <summary>
 716    /// Checks if a type implements IDisposable or IAsyncDisposable.
 717    /// </summary>
 718    /// <param name="typeSymbol">The type symbol to check.</param>
 719    /// <returns>True if the type implements IDisposable or IAsyncDisposable.</returns>
 720    public static bool IsDisposableType(INamedTypeSymbol typeSymbol)
 721    {
 847777722        foreach (var iface in typeSymbol.AllInterfaces)
 723        {
 202800724            var fullName = GetFullyQualifiedName(iface);
 202800725            if (fullName == "global::System.IDisposable" || fullName == "global::System.IAsyncDisposable")
 63963726                return true;
 727        }
 189107728        return false;
 729    }
 730
 731    /// <summary>
 732    /// Known Needlr plugin interface names that indicate a type is a plugin.
 733    /// </summary>
 1734    private static readonly string[] NeedlrPluginInterfaceNames =
 1735    [
 1736        "NexusLabs.Needlr.IServiceCollectionPlugin",
 1737        "NexusLabs.Needlr.IPostBuildServiceCollectionPlugin",
 1738        "NexusLabs.Needlr.AspNet.IWebApplicationPlugin",
 1739        "NexusLabs.Needlr.AspNet.IWebApplicationBuilderPlugin",
 1740        "NexusLabs.Needlr.SignalR.IHubRegistrationPlugin",
 1741        "NexusLabs.Needlr.Hosting.IHostApplicationBuilderPlugin",
 1742        "NexusLabs.Needlr.Hosting.IHostPlugin"
 1743    ];
 744
 745    /// <summary>
 746    /// Checks if a type implements any known Needlr plugin interface.
 747    /// </summary>
 748    /// <param name="typeSymbol">The type symbol to check.</param>
 749    /// <returns>True if the type implements a Needlr plugin interface.</returns>
 750    public static bool ImplementsNeedlrPluginInterface(INamedTypeSymbol typeSymbol)
 751    {
 217517752        foreach (var iface in typeSymbol.AllInterfaces)
 753        {
 2124754            var ifaceName = iface.ToDisplayString();
 33971755            foreach (var pluginInterface in NeedlrPluginInterfaceNames)
 756            {
 14862757                if (ifaceName == pluginInterface)
 1758                    return true;
 759            }
 760        }
 106634761        return false;
 762    }
 763
 764    /// <summary>
 765    /// Checks if an assembly has the [GenerateTypeRegistry] attribute.
 766    /// </summary>
 767    /// <param name="assembly">The assembly symbol to check.</param>
 768    /// <returns>True if the assembly has the attribute.</returns>
 769    public static bool HasGenerateTypeRegistryAttribute(IAssemblySymbol assembly)
 770    {
 771        const string attributeName = "NexusLabs.Needlr.Generators.GenerateTypeRegistryAttribute";
 772
 12097368773        foreach (var attribute in assembly.GetAttributes())
 774        {
 5744545775            var attrClass = attribute.AttributeClass;
 5744545776            if (attrClass == null)
 777                continue;
 778
 5744545779            if (attrClass.ToDisplayString() == attributeName)
 70780                return true;
 781        }
 782
 304104783        return false;
 784    }
 785
 786    /// <summary>
 787    /// Determines the injectable lifetime for a type by analyzing its attributes and constructors.
 788    /// Checks for explicit lifetime attributes first, then falls back to Singleton if injectable.
 789    /// </summary>
 790    /// <param name="typeSymbol">The type symbol to analyze.</param>
 791    /// <returns>The determined lifetime, or null if the type is not injectable.</returns>
 792    public static GeneratorLifetime? DetermineLifetime(INamedTypeSymbol typeSymbol)
 793    {
 794        // Check for DoNotInjectAttribute
 448136795        if (HasDoNotInjectAttribute(typeSymbol))
 1796            return null;
 797
 798        // Exclude open generic types (type definitions with type parameters like MyClass<T>)
 448135799        if (typeSymbol.TypeParameters.Length > 0)
 1800            return null;
 801
 802        // Types with [DeferToContainer] are always injectable as Singleton
 803        // (the attribute declares constructor params that will be added by another generator)
 448134804        if (HasDeferToContainerAttribute(typeSymbol))
 6805            return GetExplicitLifetime(typeSymbol) ?? GeneratorLifetime.Singleton;
 806
 807        // Types eligible for generated-constructor generation ([GenerateConstructor] or a
 808        // positive field-level constructor guard trigger) are always injectable. This
 809        // generator cannot see the constructor emitted by the sibling
 810        // GeneratedConstructorGenerator pass within the same compilation, so it must use
 811        // the same field-derived model to determine the effective constructor shape
 812        // instead of relying on typeSymbol.InstanceConstructors (which would otherwise
 813        // still show only the implicit parameterless constructor and incorrectly emit
 814        // `new Service()`). Referenced-assembly types already have their generated
 815        // constructor compiled, so this returns null for them and falls through below.
 448128816        if (ConstructorGenerationDiscoveryHelper.TryGetEffectiveConstructorParameters(typeSymbol) != null)
 7817            return GetExplicitLifetime(typeSymbol) ?? GeneratorLifetime.Singleton;
 818
 819        // A type can be eligible for constructor generation but not for automatic DI
 820        // discovery (e.g. a field-triggered guard on a plain string parameter, which
 821        // isn't a container-resolvable service type). Such a type must not fall through
 822        // to the instance-constructor scan below: once the sibling generator emits its
 823        // constructor, the implicit parameterless constructor this scan would otherwise
 824        // find no longer exists, so falling through would incorrectly treat the type as
 825        // injectable with zero parameters.
 448121826        if (ConstructorGenerationDiscoveryHelper.TryGetModel(typeSymbol) != null)
 2827            return null;
 828
 829        // Get all instance constructors
 448119830        var constructors = typeSymbol.InstanceConstructors;
 831
 1592516832        foreach (var ctor in constructors)
 833        {
 834            // Skip static constructors
 475946835            if (ctor.IsStatic)
 836                continue;
 837
 475946838            var parameters = ctor.Parameters;
 839
 840            // Parameterless constructor is always valid
 475946841            if (parameters.Length == 0)
 206643842                return GetExplicitLifetime(typeSymbol) ?? GeneratorLifetime.Singleton;
 843
 844            // Single parameter of same type (copy constructor) - not injectable
 269303845            if (parameters.Length == 1 && SymbolEqualityComparer.Default.Equals(parameters[0].Type, typeSymbol))
 846                continue;
 847
 848            // Check if all parameters are injectable types
 261360849            if (AllParametersAreInjectable(parameters))
 48971850                return GetExplicitLifetime(typeSymbol) ?? GeneratorLifetime.Singleton;
 851        }
 852
 192505853        return null;
 854    }
 855
 856    /// <summary>
 857    /// Gets the explicit lifetime from attributes if specified.
 858    /// </summary>
 859    private static GeneratorLifetime? GetExplicitLifetime(INamedTypeSymbol typeSymbol)
 860    {
 1434153861        foreach (var attribute in typeSymbol.GetAttributes())
 862        {
 461482863            var attributeClass = attribute.AttributeClass;
 461482864            if (attributeClass == null)
 865                continue;
 866
 461482867            var name = attributeClass.Name;
 461482868            var fullName = attributeClass.ToDisplayString();
 869
 461482870            if (name == TransientAttributeName || fullName == TransientAttributeFullName)
 5871                return GeneratorLifetime.Transient;
 872
 461477873            if (name == ScopedAttributeName || fullName == ScopedAttributeFullName)
 37874                return GeneratorLifetime.Scoped;
 875
 461440876            if (name == SingletonAttributeName || fullName == SingletonAttributeFullName)
 23877                return GeneratorLifetime.Singleton;
 878        }
 879
 255562880        return null;
 881    }
 882
 883    private static bool AllParametersAreInjectable(System.Collections.Immutable.ImmutableArray<IParameterSymbol> paramet
 884    {
 2253052885        foreach (var param in parameters)
 886        {
 747418887            if (!IsInjectableParameterType(param.Type))
 441080888                return false;
 889        }
 158568890        return true;
 891    }
 892
 893    internal static bool IsInjectableParameterType(ITypeSymbol typeSymbol)
 894    {
 895        // Must not be a delegate
 747443896        if (typeSymbol.TypeKind == TypeKind.Delegate)
 7964897            return false;
 898
 899        // Must not be a value type
 739479900        if (typeSymbol.IsValueType)
 207789901            return false;
 902
 903        // Must not be string
 531690904        if (typeSymbol.SpecialType == SpecialType.System_String)
 178496905            return false;
 906
 907        // Must be a class or interface
 353194908        if (typeSymbol.TypeKind != TypeKind.Class && typeSymbol.TypeKind != TypeKind.Interface)
 46834909            return false;
 910
 306360911        return true;
 912    }
 913
 914    private static bool HasDoNotInjectAttribute(INamedTypeSymbol typeSymbol)
 915    {
 2539059916        foreach (var attribute in typeSymbol.GetAttributes())
 917        {
 821394918            var attributeClass = attribute.AttributeClass;
 821394919            if (attributeClass == null)
 920                continue;
 921
 821394922            var name = attributeClass.Name;
 821394923            if (name == DoNotInjectAttributeName)
 1924                return true;
 925
 821393926            var fullName = attributeClass.ToDisplayString();
 821393927            if (fullName == DoNotInjectAttributeFullName)
 0928                return true;
 929        }
 930
 448135931        return false;
 932    }
 933
 934    /// <summary>
 935    /// Determines if a type is a valid plugin type (concrete class with parameterless constructor).
 936    /// </summary>
 937    /// <param name="typeSymbol">The type symbol to check.</param>
 938    /// <param name="isCurrentAssembly">True if the type is from the current compilation's assembly (allows internal typ
 939    /// <returns>True if the type is a valid plugin type.</returns>
 940    public static bool IsPluginType(INamedTypeSymbol typeSymbol, bool isCurrentAssembly = false)
 941    {
 942        // Must be a concrete class
 1581871943        if (typeSymbol.TypeKind != TypeKind.Class)
 593885944            return false;
 945
 946        // Must be accessible from generated code
 947        // - Current assembly: internal and public types are accessible
 948        // - Referenced assemblies: only public types are accessible
 987986949        if (!IsAccessibleFromGeneratedCode(typeSymbol, isCurrentAssembly))
 0950            return false;
 951
 987986952        if (typeSymbol.IsAbstract)
 150907953            return false;
 954
 837079955        if (typeSymbol.IsStatic)
 96057956            return false;
 957
 741022958        if (typeSymbol.IsUnboundGenericType)
 0959            return false;
 960
 961        // Exclude open generic types (type definitions with type parameters like MyClass<T>)
 962        // These cannot be instantiated directly and would produce invalid typeof() expressions
 741022963        if (typeSymbol.TypeParameters.Length > 0)
 43780964            return false;
 965
 966        // NOTE: Records ARE allowed as plugins (they are classes with parameterless constructors).
 967        // Records are excluded from IsInjectableType (auto-registration) but not from plugin discovery.
 968        // Use case: CacheConfiguration records can be discovered via IPluginFactory.CreatePluginsFromAssemblies<T>()
 969        // IMPORTANT: If the plugin type is emitted by a DIFFERENT source generator, TypeRegistryGenerator
 970        // cannot see it (Roslyn generators receive the original compilation in isolation). In that case,
 971        // the other generator should emit a [ModuleInitializer] that calls
 972        // NeedlrSourceGenBootstrap.RegisterPlugins(() => [...]) to contribute those types at runtime.
 973
 974        // Exclude hosted service types — they have their own dedicated registration path
 975        // (RegisterHostedServices) and must not be included in plugin types.
 697242976        if (InheritsFromBackgroundService(typeSymbol) || ImplementsIHostedService(typeSymbol))
 10977            return false;
 978
 979        // Must have a parameterless constructor
 697232980        if (!HasParameterlessConstructor(typeSymbol))
 257098981            return false;
 982
 983        // Exclude types with required members that can't be set via constructor
 984        // These would cause compilation errors: "Required member 'X' must be set"
 440134985        if (HasUnsatisfiedRequiredMembers(typeSymbol))
 2986            return false;
 987
 440132988        return true;
 989    }
 990
 991    /// <summary>
 992    /// Gets the plugin base types (interfaces and base classes) for a type.
 993    /// Plugin base types are non-System interfaces and non-System/non-object base classes.
 994    /// </summary>
 995    /// <param name="typeSymbol">The type symbol to check.</param>
 996    /// <param name="compilationAssembly">
 997    /// The compilation's assembly, used for same-assembly accessibility checks.
 998    /// See <see cref="IsAccessibleFromGeneratedCode(INamedTypeSymbol, IAssemblySymbol?)"/> for details.
 999    /// </param>
 1000    /// <returns>A list of plugin base type symbols (interfaces and base classes).</returns>
 1001    public static IReadOnlyList<INamedTypeSymbol> GetPluginInterfaces(
 1002        INamedTypeSymbol typeSymbol,
 1003        IAssemblySymbol? compilationAssembly = null)
 1004    {
 5138731005        var result = new List<INamedTypeSymbol>();
 1006
 1007        // Add non-system interfaces that are accessible from generated code.
 1008        //
 1009        // WHY THIS EXISTS: Same rules as GetRegisterableInterfaces — the generated
 1010        // TypeRegistry emits typeof() for each plugin interface. Same-assembly internal
 1011        // interfaces are valid (generated code is in the same compilation). Cross-assembly
 1012        // internal interfaces MUST be skipped to avoid CS0122.
 1013        //
 1014        // CRITICAL: Same-assembly internal interfaces MUST be kept. This is the standard
 1015        // pattern for internal plugin contracts within a single project.
 1016        //
 1017        // Example that MUST work (same assembly):
 1018        //   internal interface IMyPlugin { }
 1019        //   internal class MyPlugin : IMyPlugin { }
 1020        //   → typeof(IMyPlugin) in generated code is VALID
 1021        //
 1022        // Example that MUST be skipped (cross-assembly, e.g., Avalonia):
 1023        //   // In Framework.dll (internal):
 1024        //   internal interface IInternalHook { }
 1025        //   // In consumer app:
 1026        //   public class MyControl : Framework.BaseControl { } // inherits IInternalHook
 1027        //   → typeof(IInternalHook) in generated code produces CS0122
 16715981028        foreach (var iface in typeSymbol.AllInterfaces)
 1029        {
 3219261030            if (iface.IsUnboundGenericType)
 1031                continue;
 1032
 3219261033            if (IsSystemInterface(iface))
 1034                continue;
 1035
 4111036            if (!IsAccessibleFromGeneratedCode(iface, compilationAssembly))
 1037                continue;
 1038
 4051039            result.Add(iface);
 1040        }
 1041
 1042        // Add non-system base classes (walking up the hierarchy)
 5138731043        var baseType = typeSymbol.BaseType;
 5151391044        while (baseType != null)
 1045        {
 1046            // Stop at System.Object or System types
 4439201047            if (IsSystemType(baseType))
 1048                break;
 1049
 1050            // Skip inaccessible base types (same rules as interfaces)
 12661051            if (!IsAccessibleFromGeneratedCode(baseType, compilationAssembly))
 1052            {
 01053                baseType = baseType.BaseType;
 01054                continue;
 1055            }
 1056
 12661057            result.Add(baseType);
 12661058            baseType = baseType.BaseType;
 1059        }
 1060
 5138731061        return result;
 1062    }
 1063
 1064    /// <summary>
 1065    /// Checks if a type has a public parameterless constructor.
 1066    /// </summary>
 1067    /// <param name="typeSymbol">The type symbol to check.</param>
 1068    /// <returns>True if the type has a parameterless constructor.</returns>
 1069    public static bool HasParameterlessConstructor(INamedTypeSymbol typeSymbol)
 1070    {
 1071        // A type eligible for generated-constructor generation ([GenerateConstructor] or
 1072        // a positive field-level constructor guard trigger) always has at least one
 1073        // eligible field, so its generated constructor always has at least one
 1074        // parameter. This symbol-based scan only sees the implicit parameterless
 1075        // constructor that exists before the sibling GeneratedConstructorGenerator pass
 1076        // runs within the same compilation, so it must be excluded here — otherwise
 1077        // plugin discovery would emit `() => new Type()` for a type that will no longer
 1078        // have a parameterless constructor once generation completes.
 7726381079        if (ConstructorGenerationDiscoveryHelper.TryGetModel(typeSymbol) != null)
 71080            return false;
 1081
 29737191082        foreach (var ctor in typeSymbol.InstanceConstructors)
 1083        {
 8860201084            if (ctor.DeclaredAccessibility == Accessibility.Public &&
 8860201085                ctor.Parameters.Length == 0)
 1086            {
 3435831087                return true;
 1088            }
 1089        }
 1090
 1091        // If no explicit constructors, the default constructor is available
 1092        // (unless there are other constructors with parameters)
 4290481093        if (typeSymbol.InstanceConstructors.Length == 0)
 1702951094            return true;
 1095
 2587531096        return false;
 1097    }
 1098
 1099    /// <summary>
 1100    /// Gets the attribute types applied to a plugin type.
 1101    /// </summary>
 1102    /// <param name="typeSymbol">The type symbol to check.</param>
 1103    /// <returns>A list of attribute type names (fully qualified).</returns>
 1104    public static IReadOnlyList<string> GetPluginAttributes(INamedTypeSymbol typeSymbol)
 1105    {
 16441106        var result = new List<string>();
 1107
 35321108        foreach (var attribute in typeSymbol.GetAttributes())
 1109        {
 1221110            var attributeClass = attribute.AttributeClass;
 1221111            if (attributeClass == null)
 1112                continue;
 1113
 1114            // Skip system attributes and compiler-generated attributes
 1221115            var ns = attributeClass.ContainingNamespace?.ToDisplayString() ?? string.Empty;
 1221116            if (ns.StartsWith("System.Runtime.CompilerServices", StringComparison.Ordinal))
 1117                continue;
 1118
 1119            // Include this attribute
 1021120            var attributeName = GetFullyQualifiedName(attributeClass);
 1021121            if (!result.Contains(attributeName))
 1122            {
 1021123                result.Add(attributeName);
 1124            }
 1125        }
 1126
 1127        // Also check for inherited attributes from base types
 16441128        var baseType = typeSymbol.BaseType;
 66721129        while (baseType != null && baseType.SpecialType != SpecialType.System_Object)
 1130        {
 100561131            foreach (var attribute in baseType.GetAttributes())
 1132            {
 01133                var attributeClass = attribute.AttributeClass;
 01134                if (attributeClass == null)
 1135                    continue;
 1136
 1137                // Check if attribute is inherited
 01138                if (!IsInheritedAttribute(attributeClass))
 1139                    continue;
 1140
 01141                var ns = attributeClass.ContainingNamespace?.ToDisplayString() ?? string.Empty;
 01142                if (ns.StartsWith("System.Runtime.CompilerServices", StringComparison.Ordinal))
 1143                    continue;
 1144
 01145                var attributeName = GetFullyQualifiedName(attributeClass);
 01146                if (!result.Contains(attributeName))
 1147                {
 01148                    result.Add(attributeName);
 1149                }
 1150            }
 1151
 50281152            baseType = baseType.BaseType;
 1153        }
 1154
 16441155        return result;
 1156    }
 1157
 1158    /// <summary>
 1159    /// Checks if an attribute type has [AttributeUsage(Inherited = true)].
 1160    /// </summary>
 1161    private static bool IsInheritedAttribute(INamedTypeSymbol attributeClass)
 1162    {
 01163        foreach (var attr in attributeClass.GetAttributes())
 1164        {
 01165            if (attr.AttributeClass?.ToDisplayString() != "System.AttributeUsageAttribute")
 1166                continue;
 1167
 01168            foreach (var namedArg in attr.NamedArguments)
 1169            {
 01170                if (namedArg.Key == "Inherited" && namedArg.Value.Value is bool inherited)
 1171                {
 01172                    return inherited;
 1173                }
 1174            }
 1175
 1176            // Default for AttributeUsage is Inherited = true
 01177            return true;
 1178        }
 1179
 1180        // Default is Inherited = true
 01181        return true;
 1182    }
 1183
 1184    /// <summary>
 1185    /// Gets the parameters of the best injectable constructor for a type.
 1186    /// Returns the first constructor where all parameters are injectable types.
 1187    /// </summary>
 1188    /// <param name="typeSymbol">The type symbol to analyze.</param>
 1189    /// <returns>
 1190    /// A list of fully qualified parameter type names, or null if no injectable constructor was found.
 1191    /// </returns>
 1192    public static IReadOnlyList<string>? GetBestConstructorParameters(INamedTypeSymbol typeSymbol)
 1193    {
 1194        // Collect ALL satisfiable constructors, then pick the richest (most parameters).
 1195        // This matches the standard .NET DI behavior (ActivatorUtilities) where the
 1196        // constructor with the most resolvable parameters wins.
 231197        string[]? best = null;
 1198
 1001199        foreach (var ctor in typeSymbol.InstanceConstructors)
 1200        {
 271201            if (ctor.IsStatic)
 1202                continue;
 1203
 271204            if (ctor.DeclaredAccessibility != Accessibility.Public)
 1205                continue;
 1206
 271207            var parameters = ctor.Parameters;
 1208
 1209            // Single parameter of same type (copy constructor) - skip
 271210            if (parameters.Length == 1 && SymbolEqualityComparer.Default.Equals(parameters[0].Type, typeSymbol))
 1211                continue;
 1212
 1213            // Parameterless constructor is a valid candidate
 271214            if (parameters.Length == 0)
 1215            {
 171216                if (best == null)
 171217                    best = Array.Empty<string>();
 171218                continue;
 1219            }
 1220
 1221            // Check if all parameters are injectable
 101222            if (!AllParametersAreInjectable(parameters))
 1223                continue;
 1224
 1225            // This constructor is satisfiable — prefer it if it's richer
 61226            if (best == null || parameters.Length > best.Length)
 1227            {
 61228                var parameterTypes = new string[parameters.Length];
 261229                for (int i = 0; i < parameters.Length; i++)
 1230                {
 71231                    parameterTypes[i] = GetFullyQualifiedNameForType(parameters[i].Type);
 1232                }
 61233                best = parameterTypes;
 1234            }
 1235        }
 1236
 231237        return best;
 1238    }
 1239
 1240    /// <summary>
 1241    /// Gets the fully qualified name for any type symbol (including generics like Lazy&lt;T&gt;).
 1242    /// </summary>
 1243    private static string GetFullyQualifiedNameForType(ITypeSymbol typeSymbol)
 1244    {
 1192471245        return typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 1246    }
 1247
 1248    /// <summary>
 1249    /// Represents a constructor parameter with optional keyed service information.
 1250    /// </summary>
 1251    public readonly struct ConstructorParameterInfo
 1252    {
 1253        public ConstructorParameterInfo(string typeName, string? serviceKey = null, string? parameterName = null, string
 1254        {
 1193921255            TypeName = typeName;
 1193921256            ServiceKey = serviceKey;
 1193921257            ParameterName = parameterName;
 1193921258            DocumentationComment = documentationComment;
 1193921259        }
 1260
 1261        /// <summary>
 1262        /// The fully qualified type name of the parameter.
 1263        /// </summary>
 5125491264        public string TypeName { get; }
 1265
 1266        /// <summary>
 1267        /// The service key from [FromKeyedServices] attribute, or null if not a keyed service.
 1268        /// </summary>
 3138591269        public string? ServiceKey { get; }
 1270
 1271        /// <summary>
 1272        /// The original parameter name from the constructor (used for factory generation).
 1273        /// </summary>
 1057671274        public string? ParameterName { get; }
 1275
 1276        /// <summary>
 1277        /// XML documentation comment for this parameter, extracted from the constructor's XML docs.
 1278        /// </summary>
 551279        public string? DocumentationComment { get; }
 1280
 1281        /// <summary>
 1282        /// True if this parameter should be resolved as a keyed service.
 1283        /// </summary>
 2088341284        public bool IsKeyed => ServiceKey is not null;
 1285    }
 1286
 1287    /// <summary>
 1288    /// Gets the parameters of the best injectable constructor for a type, including keyed service info.
 1289    /// Picks the constructor with the most satisfiable parameters (richest constructor wins),
 1290    /// matching the standard .NET DI behavior (ActivatorUtilities).
 1291    /// </summary>
 1292    /// <param name="typeSymbol">The type symbol to analyze.</param>
 1293    /// <returns>
 1294    /// A list of constructor parameter info, or null if no injectable constructor was found.
 1295    /// </returns>
 1296    public static IReadOnlyList<ConstructorParameterInfo>? GetBestConstructorParametersWithKeys(INamedTypeSymbol typeSym
 1297    {
 1298        const string FromKeyedServicesAttributeName = "Microsoft.Extensions.DependencyInjection.FromKeyedServicesAttribu
 1299
 2530651300        ConstructorParameterInfo[]? best = null;
 1301
 16310121302        foreach (var ctor in typeSymbol.InstanceConstructors)
 1303        {
 5624411304            if (ctor.IsStatic)
 1305                continue;
 1306
 5624411307            if (ctor.DeclaredAccessibility != Accessibility.Public)
 1308                continue;
 1309
 5369431310            var parameters = ctor.Parameters;
 1311
 1312            // Single parameter of same type (copy constructor) - skip
 5369431313            if (parameters.Length == 1 && SymbolEqualityComparer.Default.Equals(parameters[0].Type, typeSymbol))
 1314                continue;
 1315
 1316            // Parameterless constructor is a valid candidate
 5302551317            if (parameters.Length == 0)
 1318            {
 1919771319                if (best == null)
 1919771320                    best = Array.Empty<ConstructorParameterInfo>();
 1919771321                continue;
 1322            }
 1323
 1324            // Check if all parameters are injectable
 3382781325            if (!AllParametersAreInjectable(parameters))
 1326                continue;
 1327
 1328            // This constructor is satisfiable — prefer it if it's richer
 1095911329            if (best == null || parameters.Length > best.Length)
 1330            {
 932891331                var parameterInfos = new ConstructorParameterInfo[parameters.Length];
 4250581332                for (int i = 0; i < parameters.Length; i++)
 1333                {
 1192401334                    var param = parameters[i];
 1192401335                    var typeName = GetFullyQualifiedNameForType(param.Type);
 1192401336                    string? serviceKey = null;
 1337
 1338                    // Check for [FromKeyedServices("key")] attribute
 2685961339                    foreach (var attr in param.GetAttributes())
 1340                    {
 150581341                        var attrClass = attr.AttributeClass;
 150581342                        if (attrClass is null)
 1343                            continue;
 1344
 150581345                        var attrFullName = attrClass.ToDisplayString();
 150581346                        if (attrFullName == FromKeyedServicesAttributeName)
 1347                        {
 01348                            if (attr.ConstructorArguments.Length > 0)
 1349                            {
 01350                                var keyArg = attr.ConstructorArguments[0];
 01351                                if (keyArg.Value is string keyValue)
 1352                                {
 01353                                    serviceKey = keyValue;
 1354                                }
 1355                            }
 01356                            break;
 1357                        }
 1358                    }
 1359
 1192401360                    parameterInfos[i] = new ConstructorParameterInfo(typeName, serviceKey);
 1361                }
 932891362                best = parameterInfos;
 1363            }
 1364        }
 1365
 2530651366        return best;
 1367    }
 1368
 1369    /// <summary>
 1370    /// Gets the service keys from [Keyed] attributes on a type.
 1371    /// </summary>
 1372    /// <param name="typeSymbol">The type symbol to check.</param>
 1373    /// <returns>Array of service keys, or empty array if no [Keyed] attributes found.</returns>
 1374    public static string[] GetKeyedServiceKeys(INamedTypeSymbol typeSymbol)
 1375    {
 2530881376        var keys = new List<string>();
 1377
 14290861378        foreach (var attribute in typeSymbol.GetAttributes())
 1379        {
 4614551380            var attributeClass = attribute.AttributeClass;
 4614551381            if (attributeClass == null)
 1382                continue;
 1383
 4614551384            var name = attributeClass.Name;
 4614551385            var fullName = attributeClass.ToDisplayString();
 1386
 4614551387            if (name == KeyedAttributeName || fullName == KeyedAttributeFullName)
 1388            {
 1389                // Extract the key from the constructor argument
 51390                if (attribute.ConstructorArguments.Length > 0)
 1391                {
 51392                    var keyArg = attribute.ConstructorArguments[0];
 51393                    if (keyArg.Value is string keyValue)
 1394                    {
 51395                        keys.Add(keyValue);
 1396                    }
 1397                }
 1398            }
 1399        }
 1400
 2530881401        return keys.ToArray();
 1402    }
 1403
 1404    // NOTE: TryGetHubRegistrationInfo, TryGetPropertyStringValue, TryGetPropertyTypeValue
 1405    // were moved to NexusLabs.Needlr.SignalR.Generators
 1406
 1407    /// <summary>
 1408    /// Checks if a type has the <c>[DeferToContainer]</c> attribute.
 1409    /// </summary>
 1410    /// <param name="typeSymbol">The type symbol to check.</param>
 1411    /// <returns>True if the type has the DeferToContainer attribute.</returns>
 1412    public static bool HasDeferToContainerAttribute(INamedTypeSymbol typeSymbol)
 1413    {
 25390531414        foreach (var attribute in typeSymbol.GetAttributes())
 1415        {
 8213941416            var attrClass = attribute.AttributeClass;
 8213941417            if (attrClass is null)
 1418                continue;
 1419
 8213941420            var name = attrClass.Name;
 8213941421            var fullName = attrClass.ToDisplayString();
 1422
 8213941423            if (name == DeferToContainerAttributeName || fullName == DeferToContainerAttributeFullName)
 71424                return true;
 1425        }
 1426
 4481291427        return false;
 1428    }
 1429
 1430    /// <summary>
 1431    /// Gets the constructor parameter types declared in the <c>[DeferToContainer]</c> attribute.
 1432    /// </summary>
 1433    /// <param name="typeSymbol">The type symbol to check.</param>
 1434    /// <returns>
 1435    /// A list of fully qualified parameter type names from the attribute,
 1436    /// or null if the attribute is not present.
 1437    /// </returns>
 1438    public static IReadOnlyList<string>? GetDeferToContainerParameterTypes(INamedTypeSymbol typeSymbol)
 1439    {
 14290371440        foreach (var attribute in typeSymbol.GetAttributes())
 1441        {
 4614501442            var attrClass = attribute.AttributeClass;
 4614501443            if (attrClass is null)
 1444                continue;
 1445
 4614501446            var name = attrClass.Name;
 4614501447            var fullName = attrClass.ToDisplayString();
 1448
 4614501449            if (name != DeferToContainerAttributeName && fullName != DeferToContainerAttributeFullName)
 1450                continue;
 1451
 1452            // The attribute has a params Type[] constructor parameter
 1453            // Check constructor arguments
 91454            if (attribute.ConstructorArguments.Length == 0)
 01455                return Array.Empty<string>();
 1456
 91457            var arg = attribute.ConstructorArguments[0];
 1458
 1459            // params array is passed as a single array argument
 91460            if (arg.Kind == TypedConstantKind.Array)
 1461            {
 91462                var types = new List<string>();
 401463                foreach (var element in arg.Values)
 1464                {
 111465                    if (element.Value is INamedTypeSymbol namedType)
 1466                    {
 111467                        types.Add(GetFullyQualifiedName(namedType));
 1468                    }
 1469                }
 91470                return types;
 1471            }
 1472        }
 1473
 2530641474        return null;
 1475    }
 1476
 1477    /// <summary>
 1478    /// Result of decorator discovery.
 1479    /// </summary>
 1480    public readonly struct DecoratorInfo
 1481    {
 1482        public DecoratorInfo(string decoratorTypeName, string serviceTypeName, int order)
 1483        {
 191484            DecoratorTypeName = decoratorTypeName;
 191485            ServiceTypeName = serviceTypeName;
 191486            Order = order;
 191487        }
 1488
 191489        public string DecoratorTypeName { get; }
 191490        public string ServiceTypeName { get; }
 191491        public int Order { get; }
 1492    }
 1493
 1494    /// <summary>
 1495    /// Gets all DecoratorFor&lt;T&gt; attributes applied to a type.
 1496    /// </summary>
 1497    /// <param name="typeSymbol">The type symbol to check.</param>
 1498    /// <returns>A list of decorator info for each DecoratorFor attribute found.</returns>
 1499    public static IReadOnlyList<DecoratorInfo> GetDecoratorForAttributes(INamedTypeSymbol typeSymbol)
 1500    {
 15818651501        var result = new List<DecoratorInfo>();
 1502
 76008901503        foreach (var attribute in typeSymbol.GetAttributes())
 1504        {
 22185801505            var attrClass = attribute.AttributeClass;
 22185801506            if (attrClass is null)
 1507                continue;
 1508
 1509            // Check if this is a generic DecoratorForAttribute<T>
 22185801510            if (!attrClass.IsGenericType)
 1511                continue;
 1512
 341513            var unboundTypeName = attrClass.ConstructedFrom?.ToDisplayString();
 341514            if (unboundTypeName is null || !unboundTypeName.StartsWith(DecoratorForAttributePrefix, StringComparison.Ord
 1515                continue;
 1516
 1517            // Get the service type from the generic type argument
 191518            if (attrClass.TypeArguments.Length != 1)
 1519                continue;
 1520
 191521            var serviceType = attrClass.TypeArguments[0] as INamedTypeSymbol;
 191522            if (serviceType is null)
 1523                continue;
 1524
 191525            var serviceTypeName = GetFullyQualifiedName(serviceType);
 191526            var decoratorTypeName = GetFullyQualifiedName(typeSymbol);
 1527
 1528            // Get the Order property value
 191529            int order = 0;
 571530            foreach (var namedArg in attribute.NamedArguments)
 1531            {
 191532                if (namedArg.Key == "Order" && namedArg.Value.Value is int orderValue)
 1533                {
 191534                    order = orderValue;
 191535                    break;
 1536                }
 1537            }
 1538
 191539            result.Add(new DecoratorInfo(decoratorTypeName, serviceTypeName, order));
 1540        }
 1541
 15818651542        return result;
 1543    }
 1544
 1545    /// <summary>
 1546    /// Checks if a type has any DecoratorFor&lt;T&gt; attributes.
 1547    /// </summary>
 1548    /// <param name="typeSymbol">The type symbol to check.</param>
 1549    /// <returns>True if the type has at least one DecoratorFor attribute.</returns>
 1550    public static bool HasDecoratorForAttribute(INamedTypeSymbol typeSymbol)
 1551    {
 561552        foreach (var attribute in typeSymbol.GetAttributes())
 1553        {
 91554            var attrClass = attribute.AttributeClass;
 91555            if (attrClass is null)
 1556                continue;
 1557
 91558            if (!attrClass.IsGenericType)
 1559                continue;
 1560
 41561            var unboundTypeName = attrClass.ConstructedFrom?.ToDisplayString();
 41562            if (unboundTypeName is not null && unboundTypeName.StartsWith(DecoratorForAttributePrefix, StringComparison.
 21563                return true;
 1564        }
 1565
 181566        return false;
 1567    }
 1568}

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)
HasGenerateTypeRegistryAttribute(Microsoft.CodeAnalysis.IAssemblySymbol)
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)
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)