< 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: 416
Uncovered lines: 71
Coverable lines: 487
Total lines: 1552
Line coverage: 85.4%
Branch coverage
81%
Covered branches: 398
Total branches: 488
Branch coverage: 81.5%
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%
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)
 145725048        => 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
 23359569        var registerAsInterfaces = GetRegisterAsInterfaces(typeSymbol);
 23359570        if (registerAsInterfaces.Count > 0)
 71        {
 072            return registerAsInterfaces;
 73        }
 74
 23359575        var result = new List<INamedTypeSymbol>();
 76
 77        // Get all constructor parameter types to detect decorator pattern
 23359578        var constructorParamTypes = GetConstructorParameterTypes(typeSymbol);
 79
 89247280        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
 233595130        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    {
 233595140        var result = new List<INamedTypeSymbol>();
 141
 1319418142        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
 233595163        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    {
 233595171        var paramTypes = new HashSet<string>(StringComparer.Ordinal);
 172
 1505678173        foreach (var ctor in typeSymbol.InstanceConstructors)
 174        {
 519244175            if (ctor.IsStatic)
 176                continue;
 177
 2229932178            foreach (var param in ctor.Parameters)
 179            {
 595722180                var paramTypeName = param.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 595722181                paramTypes.Add(paramTypeName);
 182            }
 183        }
 184
 233595185        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<>
 4233035209        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)
 410899214            var hasUnresolvedTypeParameters = typeSymbol.TypeArguments.Any(ta => ta.TypeKind == TypeKind.TypeParameter);
 215
 202358216            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
 4172031236        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    {
 1814469247        if (namespacePrefixes == null || namespacePrefixes.Count == 0)
 1530009248            return true;
 249
 284460250        var typeNamespace = typeSymbol.ContainingNamespace?.ToDisplayString() ?? string.Empty;
 251
 252        // Check if type is in the global namespace
 284460253        var isGlobalNamespace = typeSymbol.ContainingNamespace?.IsGlobalNamespace == true;
 254
 1145255255        foreach (var prefix in namespacePrefixes)
 256        {
 257            // Empty string prefix matches global namespace types
 288448258            if (string.IsNullOrEmpty(prefix))
 259            {
 7975260                if (isGlobalNamespace)
 346261                    return true;
 262                continue;
 263            }
 264
 280473265            if (IsNamespacePrefixMatch(typeNamespace, prefix))
 215266                return true;
 267        }
 268
 283899269        return false;
 561270    }
 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    {
 1530567282        if (excludeNamespacePrefixes == null || excludeNamespacePrefixes.Count == 0)
 1530567283            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    {
 280473307        if (!typeNamespace.StartsWith(prefix, StringComparison.Ordinal))
 280258308            return false;
 309
 310        // Exact match (namespace equals prefix)
 215311        if (typeNamespace.Length == prefix.Length)
 211312            return true;
 313
 314        // Prefix already ends with dot — the StartsWith check is sufficient
 315        // (e.g., prefix "Avalonia." matches "Avalonia.Controls")
 4316        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"
 4321        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    {
 10815938331        foreach (var member in namespaceSymbol.GetMembers())
 332        {
 4440447333            if (member is INamedTypeSymbol typeSymbol)
 334            {
 3627710335                yield return typeSymbol;
 336            }
 812737337            else if (member is INamespaceSymbol nestedNamespace)
 338            {
 20131696339                foreach (var nestedType in GetAllTypes(nestedNamespace))
 340                {
 9253111341                    yield return nestedType;
 342                }
 343            }
 344        }
 967522345    }
 346
 347    private static bool HasDoNotAutoRegisterAttribute(INamedTypeSymbol typeSymbol)
 3876348        => SharedHelper.HasDoNotAutoRegisterAttribute(typeSymbol);
 349
 350    private static bool HasDoNotAutoRegisterAttributeDirect(INamedTypeSymbol typeSymbol)
 770847351        => SharedHelper.HasDoNotAutoRegisterAttributeDirect(typeSymbol);
 352
 353    private static bool IsCompilerGenerated(INamedTypeSymbol typeSymbol)
 842474354        => SharedHelper.IsCompilerGenerated(typeSymbol);
 355
 356    private static bool InheritsFrom(INamedTypeSymbol typeSymbol, string baseTypeName)
 9296357        => SharedHelper.InheritsFrom(typeSymbol, baseTypeName);
 358
 359    private static bool IsSystemInterface(INamedTypeSymbol interfaceSymbol)
 509782360        => SharedHelper.IsSystemType(interfaceSymbol);
 361
 362    private static bool IsHostedServiceInterface(INamedTypeSymbol interfaceSymbol)
 363    {
 295364        var fullName = GetFullyQualifiedName(interfaceSymbol);
 295365        return fullName == "global::Microsoft.Extensions.Hosting.IHostedService";
 366    }
 367
 368    /// <summary>
 369    /// Determines whether a type is a hosted service (implements IHostedService or inherits from BackgroundService).
 370    /// </summary>
 371    /// <param name="typeSymbol">The type symbol to check.</param>
 372    /// <param name="isCurrentAssembly">True if the type is from the current compilation's assembly.</param>
 373    /// <returns>True if the type is a hosted service.</returns>
 374    public static bool IsHostedServiceType(INamedTypeSymbol typeSymbol, bool isCurrentAssembly = false)
 375    {
 376        // Must be a concrete, non-abstract class
 1457233377        if (typeSymbol.IsAbstract || typeSymbol.TypeKind != TypeKind.Class)
 686711378            return false;
 379
 380        // Check accessibility
 770522381        if (!isCurrentAssembly && IsInternalOrLessAccessible(typeSymbol))
 0382            return false;
 383
 384        // Skip if marked with [DoNotAutoRegister]
 770522385        if (HasDoNotAutoRegisterAttributeDirect(typeSymbol))
 2386            return false;
 387
 388        // Skip compiler-generated types
 770520389        if (IsCompilerGenerated(typeSymbol))
 0390            return false;
 391
 392        // Skip decorators - types with [DecoratorFor<IHostedService>] should not be
 393        // registered as hosted services (they decorate hosted services, not are hosted services)
 770520394        if (IsDecoratorForHostedService(typeSymbol))
 0395            return false;
 396
 397        // Check if inherits from BackgroundService
 770520398        if (InheritsFromBackgroundService(typeSymbol))
 6399            return true;
 400
 401        // Check if directly implements IHostedService (not via BackgroundService)
 770514402        if (ImplementsIHostedService(typeSymbol))
 1403            return true;
 404
 770513405        return false;
 406    }
 407
 408    private static bool IsDecoratorForHostedService(INamedTypeSymbol typeSymbol)
 409    {
 4446668410        foreach (var attribute in typeSymbol.GetAttributes())
 411        {
 1452814412            var attrClass = attribute.AttributeClass;
 1452814413            if (attrClass == null)
 414                continue;
 415
 1452814416            var attrFullName = attrClass.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 417
 418            // Check for DecoratorForAttribute<IHostedService>
 1452814419            if (attrFullName.StartsWith("global::NexusLabs.Needlr.DecoratorForAttribute<", StringComparison.Ordinal))
 420            {
 421                // Get the type argument
 19422                if (attrClass.IsGenericType && attrClass.TypeArguments.Length == 1)
 423                {
 19424                    var typeArg = attrClass.TypeArguments[0];
 19425                    if (typeArg is INamedTypeSymbol namedTypeArg)
 426                    {
 19427                        var typeArgName = GetFullyQualifiedName(namedTypeArg);
 19428                        if (typeArgName == "global::Microsoft.Extensions.Hosting.IHostedService")
 0429                            return true;
 430                    }
 431                }
 432            }
 433        }
 770520434        return false;
 435    }
 436
 437    private static bool InheritsFromBackgroundService(INamedTypeSymbol typeSymbol)
 438    {
 1411995439        var baseType = typeSymbol.BaseType;
 4225963440        while (baseType != null)
 441        {
 2813981442            var fullName = GetFullyQualifiedName(baseType);
 2813981443            if (fullName == "global::Microsoft.Extensions.Hosting.BackgroundService")
 13444                return true;
 2813968445            baseType = baseType.BaseType;
 446        }
 1411982447        return false;
 448    }
 449
 450    private static bool ImplementsIHostedService(INamedTypeSymbol typeSymbol)
 451    {
 4807810452        foreach (var iface in typeSymbol.AllInterfaces)
 453        {
 991924454            var fullName = GetFullyQualifiedName(iface);
 991924455            if (fullName == "global::Microsoft.Extensions.Hosting.IHostedService")
 2456                return true;
 457        }
 1411980458        return false;
 459    }
 460
 461    private static bool IsSystemType(INamedTypeSymbol typeSymbol)
 408627462        => SharedHelper.IsSystemType(typeSymbol);
 463
 464    private static bool HasUnsatisfiedRequiredMembers(INamedTypeSymbol typeSymbol)
 405149465        => SharedHelper.HasUnsatisfiedRequiredMembers(typeSymbol);
 466
 467    /// <summary>
 468    /// Checks if a type is accessible from generated code.
 469    /// For types in the current assembly, internal and public types are accessible.
 470    /// For types in referenced assemblies, only public types are accessible.
 471    /// </summary>
 472    /// <param name="typeSymbol">The type symbol to check.</param>
 473    /// <param name="isCurrentAssembly">True if the type is from the current compilation's assembly.</param>
 474    /// <returns>True if the type is accessible from generated code.</returns>
 475    private static bool IsAccessibleFromGeneratedCode(INamedTypeSymbol typeSymbol, bool isCurrentAssembly)
 909881476        => SharedHelper.IsAccessibleFromGeneratedCode(typeSymbol, isCurrentAssembly);
 477
 478    /// <summary>
 479    /// Checks if a type would be registerable as injectable, ignoring accessibility constraints.
 480    /// This is used to detect internal types that match namespace filters but cannot be included.
 481    /// </summary>
 482    /// <param name="typeSymbol">The type symbol to check.</param>
 483    /// <returns>True if the type would be injectable if it were accessible.</returns>
 484    public static bool WouldBeInjectableIgnoringAccessibility(INamedTypeSymbol typeSymbol)
 485    {
 486        // Must be a class (not interface, struct, enum, delegate)
 73351487        if (typeSymbol.TypeKind != TypeKind.Class)
 14488            return false;
 489
 73337490        if (typeSymbol.IsAbstract)
 0491            return false;
 492
 73337493        if (typeSymbol.IsStatic)
 1176494            return false;
 495
 72161496        if (typeSymbol.IsUnboundGenericType)
 0497            return false;
 498
 499        // Exclude open generic types (type definitions with type parameters like MyClass<T>)
 72161500        if (typeSymbol.TypeParameters.Length > 0)
 207501            return false;
 502
 71954503        if (typeSymbol.ContainingType != null)
 0504            return false;
 505
 71954506        if (IsCompilerGenerated(typeSymbol))
 67306507            return false;
 508
 4648509        if (InheritsFrom(typeSymbol, "System.Exception"))
 0510            return false;
 511
 4648512        if (InheritsFrom(typeSymbol, "System.Attribute"))
 772513            return false;
 514
 3876515        if (typeSymbol.IsRecord)
 0516            return false;
 517
 3876518        if (HasDoNotAutoRegisterAttribute(typeSymbol))
 2519            return false;
 520
 521        // Must have a determinable lifetime to be injectable
 3874522        var lifetime = DetermineLifetime(typeSymbol);
 3874523        if (!lifetime.HasValue)
 1544524            return false;
 525
 2330526        return true;
 527    }
 528
 529    /// <summary>
 530    /// Checks if a type would be registerable as a plugin, ignoring accessibility constraints.
 531    /// This is used to detect internal types that match namespace filters but cannot be included.
 532    /// </summary>
 533    /// <param name="typeSymbol">The type symbol to check.</param>
 534    /// <param name="compilationAssembly">
 535    /// The compilation's assembly for same-assembly accessibility checks.
 536    /// Passed through to <see cref="GetPluginInterfaces"/> so that same-assembly internal
 537    /// interfaces are correctly recognized as plugin interfaces.
 538    /// </param>
 539    /// <returns>True if the type would be a plugin if it were accessible.</returns>
 540    public static bool WouldBePluginIgnoringAccessibility(
 541        INamedTypeSymbol typeSymbol,
 542        IAssemblySymbol? compilationAssembly = null)
 543    {
 544        // Must be a concrete class
 71039545        if (typeSymbol.TypeKind != TypeKind.Class)
 14546            return false;
 547
 71025548        if (typeSymbol.IsAbstract)
 0549            return false;
 550
 71025551        if (typeSymbol.IsStatic)
 1176552            return false;
 553
 69849554        if (typeSymbol.IsUnboundGenericType)
 0555            return false;
 556
 557        // Exclude open generic types (type definitions with type parameters like MyClass<T>)
 69849558        if (typeSymbol.TypeParameters.Length > 0)
 207559            return false;
 560
 561        // NOTE: Records ARE allowed as plugins (they are classes with parameterless constructors).
 562        // Records are excluded from IsInjectableType (auto-registration) but not from plugin discovery.
 563
 564        // Must have a parameterless constructor
 69642565        if (!HasParameterlessConstructor(typeSymbol))
 1534566            return false;
 567
 568        // Must have at least one plugin interface
 68108569        var pluginInterfaces = GetPluginInterfaces(typeSymbol, compilationAssembly);
 68108570        return pluginInterfaces.Count > 0;
 571    }
 572
 573    /// <summary>
 574    /// Checks if a type is internal (not public) and would be inaccessible from generated code
 575    /// in a different assembly.
 576    /// </summary>
 577    /// <param name="typeSymbol">The type symbol to check.</param>
 578    /// <returns>True if the type is internal or less accessible.</returns>
 579    public static bool IsInternalOrLessAccessible(INamedTypeSymbol typeSymbol)
 580    {
 581        // Check the type itself
 4112617582        if (typeSymbol.DeclaredAccessibility != Accessibility.Public)
 159576583            return true;
 584
 585        // Check all containing types (for nested types)
 3953041586        var containingType = typeSymbol.ContainingType;
 3953041587        while (containingType != null)
 588        {
 0589            if (containingType.DeclaredAccessibility != Accessibility.Public)
 0590                return true;
 0591            containingType = containingType.ContainingType;
 592        }
 593
 3953041594        return false;
 595    }
 596
 597    /// <summary>
 598    /// Determines whether a type symbol is accessible from generated code emitted into
 599    /// the given compilation assembly.
 600    /// </summary>
 601    /// <remarks>
 602    /// <para>
 603    /// The generated TypeRegistry is emitted into the compilation's assembly. It can
 604    /// reference any type that is accessible from that assembly:
 605    /// </para>
 606    /// <list type="bullet">
 607    /// <item><c>public</c> types — always accessible.</item>
 608    /// <item><c>internal</c> / <c>protected internal</c> types — accessible only when
 609    /// they belong to the same assembly as the compilation (same-assembly check).</item>
 610    /// <item><c>private</c> / <c>protected</c> / <c>private protected</c> types — never
 611    /// accessible from generated top-level code (even in the same assembly, generated code
 612    /// is not inside the containing type's hierarchy).</item>
 613    /// </list>
 614    /// <para>
 615    /// Containing types are checked recursively for nested types.
 616    /// </para>
 617    /// </remarks>
 618    /// <param name="typeSymbol">The type symbol to check.</param>
 619    /// <param name="compilationAssembly">
 620    /// The compilation's output assembly. When <see langword="null"/>, falls back to
 621    /// the conservative behavior of <see cref="IsInternalOrLessAccessible"/> (i.e.,
 622    /// any non-public type is considered inaccessible).
 623    /// </param>
 624    /// <returns><see langword="true"/> if the type is accessible from generated code.</returns>
 625    public static bool IsAccessibleFromGeneratedCode(
 626        INamedTypeSymbol typeSymbol,
 627        IAssemblySymbol? compilationAssembly)
 628    {
 1763629        return IsAccessibleCore(typeSymbol, compilationAssembly);
 630    }
 631
 632    private static bool IsAccessibleCore(
 633        INamedTypeSymbol typeSymbol,
 634        IAssemblySymbol? compilationAssembly)
 635    {
 1763636        var accessibility = typeSymbol.DeclaredAccessibility;
 637
 1763638        if (accessibility == Accessibility.Public)
 639        {
 640            // Public is universally accessible, but check containing types for nested types
 1733641            return typeSymbol.ContainingType == null ||
 1733642                   IsAccessibleCore(typeSymbol.ContainingType, compilationAssembly);
 643        }
 644
 645        // internal and protected-internal are accessible from the same assembly.
 646        // The generated code lives in the compilation assembly, so if the type is
 647        // also in that assembly, we can emit typeof() for it.
 30648        if (accessibility == Accessibility.Internal ||
 30649            accessibility == Accessibility.ProtectedOrInternal)
 650        {
 24651            bool isSameAssembly = compilationAssembly != null &&
 24652                SymbolEqualityComparer.Default.Equals(
 24653                    typeSymbol.ContainingAssembly, compilationAssembly);
 654
 24655            if (isSameAssembly)
 656            {
 657                // Same assembly — accessible via internal path.
 658                // Still need to check containing types for nested types.
 18659                return typeSymbol.ContainingType == null ||
 18660                       IsAccessibleCore(typeSymbol.ContainingType, compilationAssembly);
 661            }
 662        }
 663
 664        // private, protected, private-protected, or cross-assembly internal
 665        // → inaccessible from generated code.
 12666        return false;
 667    }
 668
 669    /// <summary>
 670    /// Checks if a type implements IDisposable or IAsyncDisposable.
 671    /// </summary>
 672    /// <param name="typeSymbol">The type symbol to check.</param>
 673    /// <returns>True if the type implements IDisposable or IAsyncDisposable.</returns>
 674    public static bool IsDisposableType(INamedTypeSymbol typeSymbol)
 675    {
 782257676        foreach (var iface in typeSymbol.AllInterfaces)
 677        {
 187124678            var fullName = GetFullyQualifiedName(iface);
 187124679            if (fullName == "global::System.IDisposable" || fullName == "global::System.IAsyncDisposable")
 59067680                return true;
 681        }
 174471682        return false;
 683    }
 684
 685    /// <summary>
 686    /// Known Needlr plugin interface names that indicate a type is a plugin.
 687    /// </summary>
 1688    private static readonly string[] NeedlrPluginInterfaceNames =
 1689    [
 1690        "NexusLabs.Needlr.IServiceCollectionPlugin",
 1691        "NexusLabs.Needlr.IPostBuildServiceCollectionPlugin",
 1692        "NexusLabs.Needlr.AspNet.IWebApplicationPlugin",
 1693        "NexusLabs.Needlr.AspNet.IWebApplicationBuilderPlugin",
 1694        "NexusLabs.Needlr.SignalR.IHubRegistrationPlugin",
 1695        "NexusLabs.Needlr.SemanticKernel.IKernelBuilderPlugin",
 1696        "NexusLabs.Needlr.Hosting.IHostApplicationBuilderPlugin",
 1697        "NexusLabs.Needlr.Hosting.IHostPlugin"
 1698    ];
 699
 700    /// <summary>
 701    /// Checks if a type implements any known Needlr plugin interface.
 702    /// </summary>
 703    /// <param name="typeSymbol">The type symbol to check.</param>
 704    /// <returns>True if the type implements a Needlr plugin interface.</returns>
 705    public static bool ImplementsNeedlrPluginInterface(INamedTypeSymbol typeSymbol)
 706    {
 176363707        foreach (var iface in typeSymbol.AllInterfaces)
 708        {
 1916709            var ifaceName = iface.ToDisplayString();
 34473710            foreach (var pluginInterface in NeedlrPluginInterfaceNames)
 711            {
 15321712                if (ifaceName == pluginInterface)
 1713                    return true;
 714            }
 715        }
 86265716        return false;
 717    }
 718
 719    /// <summary>
 720    /// Gets the name of the first Needlr plugin interface implemented by the type.
 721    /// </summary>
 722    /// <param name="typeSymbol">The type symbol to check.</param>
 723    /// <returns>The interface name, or null if none found.</returns>
 724    public static string? GetNeedlrPluginInterfaceName(INamedTypeSymbol typeSymbol)
 725    {
 0726        foreach (var iface in typeSymbol.AllInterfaces)
 727        {
 0728            var ifaceName = iface.ToDisplayString();
 0729            foreach (var pluginInterface in NeedlrPluginInterfaceNames)
 730            {
 0731                if (ifaceName == pluginInterface)
 0732                    return ifaceName;
 733            }
 734        }
 0735        return null;
 736    }
 737
 738    /// <summary>
 739    /// Checks if an assembly has the [GenerateTypeRegistry] attribute.
 740    /// </summary>
 741    /// <param name="assembly">The assembly symbol to check.</param>
 742    /// <returns>True if the assembly has the attribute.</returns>
 743    public static bool HasGenerateTypeRegistryAttribute(IAssemblySymbol assembly)
 744    {
 745        const string attributeName = "NexusLabs.Needlr.Generators.GenerateTypeRegistryAttribute";
 746
 9869484747        foreach (var attribute in assembly.GetAttributes())
 748        {
 4686527749            var attrClass = attribute.AttributeClass;
 4686527750            if (attrClass == null)
 751                continue;
 752
 4686527753            if (attrClass.ToDisplayString() == attributeName)
 68754                return true;
 755        }
 756
 248181757        return false;
 758    }
 759
 760    /// <summary>
 761    /// Checks if a type is publicly accessible (can be referenced from generated code).
 762    /// A type is publicly accessible if it and all its containing types are public.
 763    /// </summary>
 764    /// <param name="typeSymbol">The type symbol to check.</param>
 765    /// <returns>True if the type is publicly accessible.</returns>
 766    private static bool IsPubliclyAccessible(INamedTypeSymbol typeSymbol)
 767    {
 768        // Check the type itself
 0769        if (typeSymbol.DeclaredAccessibility != Accessibility.Public)
 0770            return false;
 771
 772        // Check all containing types (for nested types)
 0773        var containingType = typeSymbol.ContainingType;
 0774        while (containingType != null)
 775        {
 0776            if (containingType.DeclaredAccessibility != Accessibility.Public)
 0777                return false;
 0778            containingType = containingType.ContainingType;
 779        }
 780
 0781        return true;
 782    }
 783
 784    /// <summary>
 785    /// Determines the injectable lifetime for a type by analyzing its attributes and constructors.
 786    /// Checks for explicit lifetime attributes first, then falls back to Singleton if injectable.
 787    /// </summary>
 788    /// <param name="typeSymbol">The type symbol to analyze.</param>
 789    /// <returns>The determined lifetime, or null if the type is not injectable.</returns>
 790    public static GeneratorLifetime? DetermineLifetime(INamedTypeSymbol typeSymbol)
 791    {
 792        // Check for DoNotInjectAttribute
 413675793        if (HasDoNotInjectAttribute(typeSymbol))
 1794            return null;
 795
 796        // Exclude open generic types (type definitions with type parameters like MyClass<T>)
 413674797        if (typeSymbol.TypeParameters.Length > 0)
 1798            return null;
 799
 800        // Types with [DeferToContainer] are always injectable as Singleton
 801        // (the attribute declares constructor params that will be added by another generator)
 413673802        if (HasDeferToContainerAttribute(typeSymbol))
 6803            return GetExplicitLifetime(typeSymbol) ?? GeneratorLifetime.Singleton;
 804
 805        // Get all instance constructors
 413667806        var constructors = typeSymbol.InstanceConstructors;
 807
 1470137808        foreach (var ctor in constructors)
 809        {
 810            // Skip static constructors
 439349811            if (ctor.IsStatic)
 812                continue;
 813
 439349814            var parameters = ctor.Parameters;
 815
 816            // Parameterless constructor is always valid
 439349817            if (parameters.Length == 0)
 190678818                return GetExplicitLifetime(typeSymbol) ?? GeneratorLifetime.Singleton;
 819
 820            // Single parameter of same type (copy constructor) - not injectable
 248671821            if (parameters.Length == 1 && SymbolEqualityComparer.Default.Equals(parameters[0].Type, typeSymbol))
 822                continue;
 823
 824            // Check if all parameters are injectable types
 241336825            if (AllParametersAreInjectable(parameters))
 45217826                return GetExplicitLifetime(typeSymbol) ?? GeneratorLifetime.Singleton;
 827        }
 828
 177772829        return null;
 830    }
 831
 832    /// <summary>
 833    /// Gets the explicit lifetime from attributes if specified.
 834    /// </summary>
 835    private static GeneratorLifetime? GetExplicitLifetime(INamedTypeSymbol typeSymbol)
 836    {
 1323918837        foreach (var attribute in typeSymbol.GetAttributes())
 838        {
 426090839            var attributeClass = attribute.AttributeClass;
 426090840            if (attributeClass == null)
 841                continue;
 842
 426090843            var name = attributeClass.Name;
 426090844            var fullName = attributeClass.ToDisplayString();
 845
 426090846            if (name == TransientAttributeName || fullName == TransientAttributeFullName)
 5847                return GeneratorLifetime.Transient;
 848
 426085849            if (name == ScopedAttributeName || fullName == ScopedAttributeFullName)
 36850                return GeneratorLifetime.Scoped;
 851
 426049852            if (name == SingletonAttributeName || fullName == SingletonAttributeFullName)
 23853                return GeneratorLifetime.Singleton;
 854        }
 855
 235837856        return null;
 857    }
 858
 859    private static bool AllParametersAreInjectable(System.Collections.Immutable.ImmutableArray<IParameterSymbol> paramet
 860    {
 2080070861        foreach (var param in parameters)
 862        {
 689992863            if (!IsInjectableParameterType(param.Type))
 407266864                return false;
 865        }
 146410866        return true;
 867    }
 868
 869    private static bool IsInjectableParameterType(ITypeSymbol typeSymbol)
 870    {
 871        // Must not be a delegate
 689992872        if (typeSymbol.TypeKind == TypeKind.Delegate)
 7336873            return false;
 874
 875        // Must not be a value type
 682656876        if (typeSymbol.IsValueType)
 191845877            return false;
 878
 879        // Must not be string
 490811880        if (typeSymbol.SpecialType == SpecialType.System_String)
 164829881            return false;
 882
 883        // Must be a class or interface
 325982884        if (typeSymbol.TypeKind != TypeKind.Class && typeSymbol.TypeKind != TypeKind.Interface)
 43256885            return false;
 886
 282726887        return true;
 888    }
 889
 890    private static bool HasDoNotInjectAttribute(INamedTypeSymbol typeSymbol)
 891    {
 2344259892        foreach (var attribute in typeSymbol.GetAttributes())
 893        {
 758455894            var attributeClass = attribute.AttributeClass;
 758455895            if (attributeClass == null)
 896                continue;
 897
 758455898            var name = attributeClass.Name;
 758455899            if (name == DoNotInjectAttributeName)
 1900                return true;
 901
 758454902            var fullName = attributeClass.ToDisplayString();
 758454903            if (fullName == DoNotInjectAttributeFullName)
 0904                return true;
 905        }
 906
 413674907        return false;
 908    }
 909
 910    /// <summary>
 911    /// Determines if a type is a valid plugin type (concrete class with parameterless constructor).
 912    /// </summary>
 913    /// <param name="typeSymbol">The type symbol to check.</param>
 914    /// <param name="isCurrentAssembly">True if the type is from the current compilation's assembly (allows internal typ
 915    /// <returns>True if the type is a valid plugin type.</returns>
 916    public static bool IsPluginType(INamedTypeSymbol typeSymbol, bool isCurrentAssembly = false)
 917    {
 918        // Must be a concrete class
 1457239919        if (typeSymbol.TypeKind != TypeKind.Class)
 547358920            return false;
 921
 922        // Must be accessible from generated code
 923        // - Current assembly: internal and public types are accessible
 924        // - Referenced assemblies: only public types are accessible
 909881925        if (!IsAccessibleFromGeneratedCode(typeSymbol, isCurrentAssembly))
 0926            return false;
 927
 909881928        if (typeSymbol.IsAbstract)
 139353929            return false;
 930
 770528931        if (typeSymbol.IsStatic)
 88675932            return false;
 933
 681853934        if (typeSymbol.IsUnboundGenericType)
 0935            return false;
 936
 937        // Exclude open generic types (type definitions with type parameters like MyClass<T>)
 938        // These cannot be instantiated directly and would produce invalid typeof() expressions
 681853939        if (typeSymbol.TypeParameters.Length > 0)
 40378940            return false;
 941
 942        // NOTE: Records ARE allowed as plugins (they are classes with parameterless constructors).
 943        // Records are excluded from IsInjectableType (auto-registration) but not from plugin discovery.
 944        // Use case: CacheConfiguration records can be discovered via IPluginFactory.CreatePluginsFromAssemblies<T>()
 945        // IMPORTANT: If the plugin type is emitted by a DIFFERENT source generator, TypeRegistryGenerator
 946        // cannot see it (Roslyn generators receive the original compilation in isolation). In that case,
 947        // the other generator should emit a [ModuleInitializer] that calls
 948        // NeedlrSourceGenBootstrap.RegisterPlugins(() => [...]) to contribute those types at runtime.
 949
 950        // Exclude hosted service types — they have their own dedicated registration path
 951        // (RegisterHostedServices) and must not be included in plugin types.
 641475952        if (InheritsFromBackgroundService(typeSymbol) || ImplementsIHostedService(typeSymbol))
 8953            return false;
 954
 955        // Must have a parameterless constructor
 641467956        if (!HasParameterlessConstructor(typeSymbol))
 236318957            return false;
 958
 959        // Exclude types with required members that can't be set via constructor
 960        // These would cause compilation errors: "Required member 'X' must be set"
 405149961        if (HasUnsatisfiedRequiredMembers(typeSymbol))
 2962            return false;
 963
 405147964        return true;
 965    }
 966
 967    /// <summary>
 968    /// Gets the plugin base types (interfaces and base classes) for a type.
 969    /// Plugin base types are non-System interfaces and non-System/non-object base classes.
 970    /// </summary>
 971    /// <param name="typeSymbol">The type symbol to check.</param>
 972    /// <param name="compilationAssembly">
 973    /// The compilation's assembly, used for same-assembly accessibility checks.
 974    /// See <see cref="IsAccessibleFromGeneratedCode(INamedTypeSymbol, IAssemblySymbol?)"/> for details.
 975    /// </param>
 976    /// <returns>A list of plugin base type symbols (interfaces and base classes).</returns>
 977    public static IReadOnlyList<INamedTypeSymbol> GetPluginInterfaces(
 978        INamedTypeSymbol typeSymbol,
 979        IAssemblySymbol? compilationAssembly = null)
 980    {
 473252981        var result = new List<INamedTypeSymbol>();
 982
 983        // Add non-system interfaces that are accessible from generated code.
 984        //
 985        // WHY THIS EXISTS: Same rules as GetRegisterableInterfaces — the generated
 986        // TypeRegistry emits typeof() for each plugin interface. Same-assembly internal
 987        // interfaces are valid (generated code is in the same compilation). Cross-assembly
 988        // internal interfaces MUST be skipped to avoid CS0122.
 989        //
 990        // CRITICAL: Same-assembly internal interfaces MUST be kept. This is the standard
 991        // pattern for internal plugin contracts within a single project.
 992        //
 993        // Example that MUST work (same assembly):
 994        //   internal interface IMyPlugin { }
 995        //   internal class MyPlugin : IMyPlugin { }
 996        //   → typeof(IMyPlugin) in generated code is VALID
 997        //
 998        // Example that MUST be skipped (cross-assembly, e.g., Avalonia):
 999        //   // In Framework.dll (internal):
 1000        //   internal interface IInternalHook { }
 1001        //   // In consumer app:
 1002        //   public class MyControl : Framework.BaseControl { } // inherits IInternalHook
 1003        //   → typeof(IInternalHook) in generated code produces CS0122
 15407861004        foreach (var iface in typeSymbol.AllInterfaces)
 1005        {
 2971411006            if (iface.IsUnboundGenericType)
 1007                continue;
 1008
 2971411009            if (IsSystemInterface(iface))
 1010                continue;
 1011
 2701012            if (!IsAccessibleFromGeneratedCode(iface, compilationAssembly))
 1013                continue;
 1014
 2641015            result.Add(iface);
 1016        }
 1017
 1018        // Add non-system base classes (walking up the hierarchy)
 4732521019        var baseType = typeSymbol.BaseType;
 4744141020        while (baseType != null)
 1021        {
 1022            // Stop at System.Object or System types
 4086271023            if (IsSystemType(baseType))
 1024                break;
 1025
 1026            // Skip inaccessible base types (same rules as interfaces)
 11621027            if (!IsAccessibleFromGeneratedCode(baseType, compilationAssembly))
 1028            {
 01029                baseType = baseType.BaseType;
 01030                continue;
 1031            }
 1032
 11621033            result.Add(baseType);
 11621034            baseType = baseType.BaseType;
 1035        }
 1036
 4732521037        return result;
 1038    }
 1039
 1040    /// <summary>
 1041    /// Checks if a type implements a specific interface by name.
 1042    /// </summary>
 1043    /// <param name="typeSymbol">The type symbol to check.</param>
 1044    /// <param name="interfaceFullName">The full name of the interface.</param>
 1045    /// <returns>True if the type implements the interface.</returns>
 1046    public static bool ImplementsInterface(INamedTypeSymbol typeSymbol, string interfaceFullName)
 1047    {
 01048        foreach (var iface in typeSymbol.AllInterfaces)
 1049        {
 01050            if (iface.ToDisplayString() == interfaceFullName)
 01051                return true;
 1052        }
 01053        return false;
 1054    }
 1055
 1056    /// <summary>
 1057    /// Checks if a type has a public parameterless constructor.
 1058    /// </summary>
 1059    /// <param name="typeSymbol">The type symbol to check.</param>
 1060    /// <returns>True if the type has a parameterless constructor.</returns>
 1061    public static bool HasParameterlessConstructor(INamedTypeSymbol typeSymbol)
 1062    {
 27354401063        foreach (var ctor in typeSymbol.InstanceConstructors)
 1064        {
 8146051065            if (ctor.DeclaredAccessibility == Accessibility.Public &&
 8146051066                ctor.Parameters.Length == 0)
 1067            {
 3159881068                return true;
 1069            }
 1070        }
 1071
 1072        // If no explicit constructors, the default constructor is available
 1073        // (unless there are other constructors with parameters)
 3951211074        if (typeSymbol.InstanceConstructors.Length == 0)
 1572691075            return true;
 1076
 2378521077        return false;
 1078    }
 1079
 1080    /// <summary>
 1081    /// Gets the attribute types applied to a plugin type.
 1082    /// </summary>
 1083    /// <param name="typeSymbol">The type symbol to check.</param>
 1084    /// <returns>A list of attribute type names (fully qualified).</returns>
 1085    public static IReadOnlyList<string> GetPluginAttributes(INamedTypeSymbol typeSymbol)
 1086    {
 14061087        var result = new List<string>();
 1088
 29341089        foreach (var attribute in typeSymbol.GetAttributes())
 1090        {
 611091            var attributeClass = attribute.AttributeClass;
 611092            if (attributeClass == null)
 1093                continue;
 1094
 1095            // Skip system attributes and compiler-generated attributes
 611096            var ns = attributeClass.ContainingNamespace?.ToDisplayString() ?? string.Empty;
 611097            if (ns.StartsWith("System.Runtime.CompilerServices", StringComparison.Ordinal))
 1098                continue;
 1099
 1100            // Include this attribute
 611101            var attributeName = GetFullyQualifiedName(attributeClass);
 611102            if (!result.Contains(attributeName))
 1103            {
 611104                result.Add(attributeName);
 1105            }
 1106        }
 1107
 1108        // Also check for inherited attributes from base types
 14061109        var baseType = typeSymbol.BaseType;
 60421110        while (baseType != null && baseType.SpecialType != SpecialType.System_Object)
 1111        {
 92721112            foreach (var attribute in baseType.GetAttributes())
 1113            {
 01114                var attributeClass = attribute.AttributeClass;
 01115                if (attributeClass == null)
 1116                    continue;
 1117
 1118                // Check if attribute is inherited
 01119                if (!IsInheritedAttribute(attributeClass))
 1120                    continue;
 1121
 01122                var ns = attributeClass.ContainingNamespace?.ToDisplayString() ?? string.Empty;
 01123                if (ns.StartsWith("System.Runtime.CompilerServices", StringComparison.Ordinal))
 1124                    continue;
 1125
 01126                var attributeName = GetFullyQualifiedName(attributeClass);
 01127                if (!result.Contains(attributeName))
 1128                {
 01129                    result.Add(attributeName);
 1130                }
 1131            }
 1132
 46361133            baseType = baseType.BaseType;
 1134        }
 1135
 14061136        return result;
 1137    }
 1138
 1139    /// <summary>
 1140    /// Checks if an attribute type has [AttributeUsage(Inherited = true)].
 1141    /// </summary>
 1142    private static bool IsInheritedAttribute(INamedTypeSymbol attributeClass)
 1143    {
 01144        foreach (var attr in attributeClass.GetAttributes())
 1145        {
 01146            if (attr.AttributeClass?.ToDisplayString() != "System.AttributeUsageAttribute")
 1147                continue;
 1148
 01149            foreach (var namedArg in attr.NamedArguments)
 1150            {
 01151                if (namedArg.Key == "Inherited" && namedArg.Value.Value is bool inherited)
 1152                {
 01153                    return inherited;
 1154                }
 1155            }
 1156
 1157            // Default for AttributeUsage is Inherited = true
 01158            return true;
 1159        }
 1160
 1161        // Default is Inherited = true
 01162        return true;
 1163    }
 1164
 1165    /// <summary>
 1166    /// Gets the parameters of the best injectable constructor for a type.
 1167    /// Returns the first constructor where all parameters are injectable types.
 1168    /// </summary>
 1169    /// <param name="typeSymbol">The type symbol to analyze.</param>
 1170    /// <returns>
 1171    /// A list of fully qualified parameter type names, or null if no injectable constructor was found.
 1172    /// </returns>
 1173    public static IReadOnlyList<string>? GetBestConstructorParameters(INamedTypeSymbol typeSymbol)
 1174    {
 1175        // Collect ALL satisfiable constructors, then pick the richest (most parameters).
 1176        // This matches the standard .NET DI behavior (ActivatorUtilities) where the
 1177        // constructor with the most resolvable parameters wins.
 231178        string[]? best = null;
 1179
 1001180        foreach (var ctor in typeSymbol.InstanceConstructors)
 1181        {
 271182            if (ctor.IsStatic)
 1183                continue;
 1184
 271185            if (ctor.DeclaredAccessibility != Accessibility.Public)
 1186                continue;
 1187
 271188            var parameters = ctor.Parameters;
 1189
 1190            // Single parameter of same type (copy constructor) - skip
 271191            if (parameters.Length == 1 && SymbolEqualityComparer.Default.Equals(parameters[0].Type, typeSymbol))
 1192                continue;
 1193
 1194            // Parameterless constructor is a valid candidate
 271195            if (parameters.Length == 0)
 1196            {
 171197                if (best == null)
 171198                    best = Array.Empty<string>();
 171199                continue;
 1200            }
 1201
 1202            // Check if all parameters are injectable
 101203            if (!AllParametersAreInjectable(parameters))
 1204                continue;
 1205
 1206            // This constructor is satisfiable — prefer it if it's richer
 61207            if (best == null || parameters.Length > best.Length)
 1208            {
 61209                var parameterTypes = new string[parameters.Length];
 261210                for (int i = 0; i < parameters.Length; i++)
 1211                {
 71212                    parameterTypes[i] = GetFullyQualifiedNameForType(parameters[i].Type);
 1213                }
 61214                best = parameterTypes;
 1215            }
 1216        }
 1217
 231218        return best;
 1219    }
 1220
 1221    /// <summary>
 1222    /// Gets the fully qualified name for any type symbol (including generics like Lazy&lt;T&gt;).
 1223    /// </summary>
 1224    private static string GetFullyQualifiedNameForType(ITypeSymbol typeSymbol)
 1225    {
 1100771226        return typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
 1227    }
 1228
 1229    // NOTE: HasKernelFunctions was moved to NexusLabs.Needlr.SemanticKernel.Generators
 1230    // NOTE: HubRegistrationInfo was moved to NexusLabs.Needlr.SignalR.Generators
 1231
 1232    /// <summary>
 1233    /// Represents a constructor parameter with optional keyed service information.
 1234    /// </summary>
 1235    public readonly struct ConstructorParameterInfo
 1236    {
 1237        public ConstructorParameterInfo(string typeName, string? serviceKey = null, string? parameterName = null, string
 1238        {
 1101341239            TypeName = typeName;
 1101341240            ServiceKey = serviceKey;
 1101341241            ParameterName = parameterName;
 1101341242            DocumentationComment = documentationComment;
 1101341243        }
 1244
 1245        /// <summary>
 1246        /// The fully qualified type name of the parameter.
 1247        /// </summary>
 4795531248        public string TypeName { get; }
 1249
 1250        /// <summary>
 1251        /// The service key from [FromKeyedServices] attribute, or null if not a keyed service.
 1252        /// </summary>
 2893901253        public string? ServiceKey { get; }
 1254
 1255        /// <summary>
 1256        /// The original parameter name from the constructor (used for factory generation).
 1257        /// </summary>
 969551258        public string? ParameterName { get; }
 1259
 1260        /// <summary>
 1261        /// XML documentation comment for this parameter, extracted from the constructor's XML docs.
 1262        /// </summary>
 391263        public string? DocumentationComment { get; }
 1264
 1265        /// <summary>
 1266        /// True if this parameter should be resolved as a keyed service.
 1267        /// </summary>
 1926021268        public bool IsKeyed => ServiceKey is not null;
 1269    }
 1270
 1271    /// <summary>
 1272    /// Gets the parameters of the best injectable constructor for a type, including keyed service info.
 1273    /// Picks the constructor with the most satisfiable parameters (richest constructor wins),
 1274    /// matching the standard .NET DI behavior (ActivatorUtilities).
 1275    /// </summary>
 1276    /// <param name="typeSymbol">The type symbol to analyze.</param>
 1277    /// <returns>
 1278    /// A list of constructor parameter info, or null if no injectable constructor was found.
 1279    /// </returns>
 1280    public static IReadOnlyList<ConstructorParameterInfo>? GetBestConstructorParametersWithKeys(INamedTypeSymbol typeSym
 1281    {
 1282        const string FromKeyedServicesAttributeName = "Microsoft.Extensions.DependencyInjection.FromKeyedServicesAttribu
 1283
 2335401284        ConstructorParameterInfo[]? best = null;
 1285
 15054521286        foreach (var ctor in typeSymbol.InstanceConstructors)
 1287        {
 5191861288            if (ctor.IsStatic)
 1289                continue;
 1290
 5191861291            if (ctor.DeclaredAccessibility != Accessibility.Public)
 1292                continue;
 1293
 4956401294            var parameters = ctor.Parameters;
 1295
 1296            // Single parameter of same type (copy constructor) - skip
 4956401297            if (parameters.Length == 1 && SymbolEqualityComparer.Default.Equals(parameters[0].Type, typeSymbol))
 1298                continue;
 1299
 1300            // Parameterless constructor is a valid candidate
 4894641301            if (parameters.Length == 0)
 1302            {
 1771341303                if (best == null)
 1771341304                    best = Array.Empty<ConstructorParameterInfo>();
 1771341305                continue;
 1306            }
 1307
 1308            // Check if all parameters are injectable
 3123301309            if (!AllParametersAreInjectable(parameters))
 1310                continue;
 1311
 1312            // This constructor is satisfiable — prefer it if it's richer
 1011871313            if (best == null || parameters.Length > best.Length)
 1314            {
 861331315                var parameterInfos = new ConstructorParameterInfo[parameters.Length];
 3924061316                for (int i = 0; i < parameters.Length; i++)
 1317                {
 1100701318                    var param = parameters[i];
 1100701319                    var typeName = GetFullyQualifiedNameForType(param.Type);
 1100701320                    string? serviceKey = null;
 1321
 1322                    // Check for [FromKeyedServices("key")] attribute
 2479321323                    foreach (var attr in param.GetAttributes())
 1324                    {
 138961325                        var attrClass = attr.AttributeClass;
 138961326                        if (attrClass is null)
 1327                            continue;
 1328
 138961329                        var attrFullName = attrClass.ToDisplayString();
 138961330                        if (attrFullName == FromKeyedServicesAttributeName)
 1331                        {
 01332                            if (attr.ConstructorArguments.Length > 0)
 1333                            {
 01334                                var keyArg = attr.ConstructorArguments[0];
 01335                                if (keyArg.Value is string keyValue)
 1336                                {
 01337                                    serviceKey = keyValue;
 1338                                }
 1339                            }
 01340                            break;
 1341                        }
 1342                    }
 1343
 1100701344                    parameterInfos[i] = new ConstructorParameterInfo(typeName, serviceKey);
 1345                }
 861331346                best = parameterInfos;
 1347            }
 1348        }
 1349
 2335401350        return best;
 1351    }
 1352
 1353    /// <summary>
 1354    /// Gets the service keys from [Keyed] attributes on a type.
 1355    /// </summary>
 1356    /// <param name="typeSymbol">The type symbol to check.</param>
 1357    /// <returns>Array of service keys, or empty array if no [Keyed] attributes found.</returns>
 1358    public static string[] GetKeyedServiceKeys(INamedTypeSymbol typeSymbol)
 1359    {
 2335561360        var keys = new List<string>();
 1361
 13192361362        foreach (var attribute in typeSymbol.GetAttributes())
 1363        {
 4260621364            var attributeClass = attribute.AttributeClass;
 4260621365            if (attributeClass == null)
 1366                continue;
 1367
 4260621368            var name = attributeClass.Name;
 4260621369            var fullName = attributeClass.ToDisplayString();
 1370
 4260621371            if (name == KeyedAttributeName || fullName == KeyedAttributeFullName)
 1372            {
 1373                // Extract the key from the constructor argument
 51374                if (attribute.ConstructorArguments.Length > 0)
 1375                {
 51376                    var keyArg = attribute.ConstructorArguments[0];
 51377                    if (keyArg.Value is string keyValue)
 1378                    {
 51379                        keys.Add(keyValue);
 1380                    }
 1381                }
 1382            }
 1383        }
 1384
 2335561385        return keys.ToArray();
 1386    }
 1387
 1388    // NOTE: TryGetHubRegistrationInfo, TryGetPropertyStringValue, TryGetPropertyTypeValue
 1389    // were moved to NexusLabs.Needlr.SignalR.Generators
 1390
 1391    /// <summary>
 1392    /// Checks if a type has the <c>[DeferToContainer]</c> attribute.
 1393    /// </summary>
 1394    /// <param name="typeSymbol">The type symbol to check.</param>
 1395    /// <returns>True if the type has the DeferToContainer attribute.</returns>
 1396    public static bool HasDeferToContainerAttribute(INamedTypeSymbol typeSymbol)
 1397    {
 23442531398        foreach (var attribute in typeSymbol.GetAttributes())
 1399        {
 7584551400            var attrClass = attribute.AttributeClass;
 7584551401            if (attrClass is null)
 1402                continue;
 1403
 7584551404            var name = attrClass.Name;
 7584551405            var fullName = attrClass.ToDisplayString();
 1406
 7584551407            if (name == DeferToContainerAttributeName || fullName == DeferToContainerAttributeFullName)
 71408                return true;
 1409        }
 1410
 4136681411        return false;
 1412    }
 1413
 1414    /// <summary>
 1415    /// Gets the constructor parameter types declared in the <c>[DeferToContainer]</c> attribute.
 1416    /// </summary>
 1417    /// <param name="typeSymbol">The type symbol to check.</param>
 1418    /// <returns>
 1419    /// A list of fully qualified parameter type names from the attribute,
 1420    /// or null if the attribute is not present.
 1421    /// </returns>
 1422    public static IReadOnlyList<string>? GetDeferToContainerParameterTypes(INamedTypeSymbol typeSymbol)
 1423    {
 13191911424        foreach (var attribute in typeSymbol.GetAttributes())
 1425        {
 4260571426            var attrClass = attribute.AttributeClass;
 4260571427            if (attrClass is null)
 1428                continue;
 1429
 4260571430            var name = attrClass.Name;
 4260571431            var fullName = attrClass.ToDisplayString();
 1432
 4260571433            if (name != DeferToContainerAttributeName && fullName != DeferToContainerAttributeFullName)
 1434                continue;
 1435
 1436            // The attribute has a params Type[] constructor parameter
 1437            // Check constructor arguments
 91438            if (attribute.ConstructorArguments.Length == 0)
 01439                return Array.Empty<string>();
 1440
 91441            var arg = attribute.ConstructorArguments[0];
 1442
 1443            // params array is passed as a single array argument
 91444            if (arg.Kind == TypedConstantKind.Array)
 1445            {
 91446                var types = new List<string>();
 401447                foreach (var element in arg.Values)
 1448                {
 111449                    if (element.Value is INamedTypeSymbol namedType)
 1450                    {
 111451                        types.Add(GetFullyQualifiedName(namedType));
 1452                    }
 1453                }
 91454                return types;
 1455            }
 1456        }
 1457
 2335341458        return null;
 1459    }
 1460
 1461    /// <summary>
 1462    /// Result of decorator discovery.
 1463    /// </summary>
 1464    public readonly struct DecoratorInfo
 1465    {
 1466        public DecoratorInfo(string decoratorTypeName, string serviceTypeName, int order)
 1467        {
 191468            DecoratorTypeName = decoratorTypeName;
 191469            ServiceTypeName = serviceTypeName;
 191470            Order = order;
 191471        }
 1472
 191473        public string DecoratorTypeName { get; }
 191474        public string ServiceTypeName { get; }
 191475        public int Order { get; }
 1476    }
 1477
 1478    /// <summary>
 1479    /// Gets all DecoratorFor&lt;T&gt; attributes applied to a type.
 1480    /// </summary>
 1481    /// <param name="typeSymbol">The type symbol to check.</param>
 1482    /// <returns>A list of decorator info for each DecoratorFor attribute found.</returns>
 1483    public static IReadOnlyList<DecoratorInfo> GetDecoratorForAttributes(INamedTypeSymbol typeSymbol)
 1484    {
 14572331485        var result = new List<DecoratorInfo>();
 1486
 70026761487        foreach (var attribute in typeSymbol.GetAttributes())
 1488        {
 20441051489            var attrClass = attribute.AttributeClass;
 20441051490            if (attrClass is null)
 1491                continue;
 1492
 1493            // Check if this is a generic DecoratorForAttribute<T>
 20441051494            if (!attrClass.IsGenericType)
 1495                continue;
 1496
 341497            var unboundTypeName = attrClass.ConstructedFrom?.ToDisplayString();
 341498            if (unboundTypeName is null || !unboundTypeName.StartsWith(DecoratorForAttributePrefix, StringComparison.Ord
 1499                continue;
 1500
 1501            // Get the service type from the generic type argument
 191502            if (attrClass.TypeArguments.Length != 1)
 1503                continue;
 1504
 191505            var serviceType = attrClass.TypeArguments[0] as INamedTypeSymbol;
 191506            if (serviceType is null)
 1507                continue;
 1508
 191509            var serviceTypeName = GetFullyQualifiedName(serviceType);
 191510            var decoratorTypeName = GetFullyQualifiedName(typeSymbol);
 1511
 1512            // Get the Order property value
 191513            int order = 0;
 571514            foreach (var namedArg in attribute.NamedArguments)
 1515            {
 191516                if (namedArg.Key == "Order" && namedArg.Value.Value is int orderValue)
 1517                {
 191518                    order = orderValue;
 191519                    break;
 1520                }
 1521            }
 1522
 191523            result.Add(new DecoratorInfo(decoratorTypeName, serviceTypeName, order));
 1524        }
 1525
 14572331526        return result;
 1527    }
 1528
 1529    /// <summary>
 1530    /// Checks if a type has any DecoratorFor&lt;T&gt; attributes.
 1531    /// </summary>
 1532    /// <param name="typeSymbol">The type symbol to check.</param>
 1533    /// <returns>True if the type has at least one DecoratorFor attribute.</returns>
 1534    public static bool HasDecoratorForAttribute(INamedTypeSymbol typeSymbol)
 1535    {
 521536        foreach (var attribute in typeSymbol.GetAttributes())
 1537        {
 91538            var attrClass = attribute.AttributeClass;
 91539            if (attrClass is null)
 1540                continue;
 1541
 91542            if (!attrClass.IsGenericType)
 1543                continue;
 1544
 41545            var unboundTypeName = attrClass.ConstructedFrom?.ToDisplayString();
 41546            if (unboundTypeName is not null && unboundTypeName.StartsWith(DecoratorForAttributePrefix, StringComparison.
 21547                return true;
 1548        }
 1549
 161550        return false;
 1551    }
 1552}

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)
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)