< Summary

Information
Class: NexusLabs.Needlr.Generators.Models.DiscoveredType
Assembly: NexusLabs.Needlr.Generators
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/Models/DiscoveryModels.cs
Line coverage
95%
Covered lines: 23
Uncovered lines: 1
Coverable lines: 24
Total lines: 764
Line coverage: 95.8%
Branch coverage
50%
Covered branches: 1
Total branches: 2
Branch coverage: 50%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%22100%
get_TypeName()100%11100%
get_InterfaceNames()100%11100%
get_InterfaceInfos()100%11100%
get_AssemblyName()100%11100%
get_Lifetime()100%11100%
get_ConstructorParameters()100%11100%
get_ServiceKeys()100%11100%
get_SourceFilePath()100%11100%
get_SourceLine()100%11100%
get_IsDisposable()100%11100%
get_ConstructorParameterTypes()100%11100%
get_HasKeyedParameters()100%210%
get_IsKeyed()100%11100%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/Models/DiscoveryModels.cs

#LineLine coverage
 1using System.Collections.Generic;
 2using System.Linq;
 3using Microsoft.CodeAnalysis;
 4
 5namespace NexusLabs.Needlr.Generators.Models;
 6
 7/// <summary>
 8/// Information about an interface implemented by a service, including its source location.
 9/// </summary>
 10internal readonly struct InterfaceInfo
 11{
 12    public InterfaceInfo(string fullName, string? sourceFilePath = null, int sourceLine = 0)
 13    {
 14        FullName = fullName;
 15        SourceFilePath = sourceFilePath;
 16        SourceLine = sourceLine;
 17    }
 18
 19    public string FullName { get; }
 20    public string? SourceFilePath { get; }
 21    public int SourceLine { get; }
 22
 23    public bool HasLocation => SourceFilePath != null && SourceLine > 0;
 24}
 25
 26/// <summary>
 27/// Information about a discovered injectable type.
 28/// </summary>
 29internal readonly struct DiscoveredType
 30{
 31    public DiscoveredType(string typeName, string[] interfaceNames, string assemblyName, GeneratorLifetime lifetime, Typ
 32    {
 23109933        TypeName = typeName;
 23109934        InterfaceNames = interfaceNames;
 23109935        AssemblyName = assemblyName;
 23109936        Lifetime = lifetime;
 23109937        ConstructorParameters = constructorParameters;
 23109938        ServiceKeys = serviceKeys;
 23109939        SourceFilePath = sourceFilePath;
 23109940        SourceLine = sourceLine;
 23109941        IsDisposable = isDisposable;
 23109942        InterfaceInfos = interfaceInfos ?? Array.Empty<InterfaceInfo>();
 23109943    }
 44
 736568645    public string TypeName { get; }
 621302746    public string[] InterfaceNames { get; }
 47    /// <summary>
 48    /// Detailed interface information including source locations.
 49    /// </summary>
 23595150    public InterfaceInfo[] InterfaceInfos { get; }
 47729851    public string AssemblyName { get; }
 112032252    public GeneratorLifetime Lifetime { get; }
 93286053    public TypeDiscoveryHelper.ConstructorParameterInfo[] ConstructorParameters { get; }
 54    /// <summary>
 55    /// Service keys from [Keyed] attributes on this type.
 56    /// </summary>
 57309057    public string[] ServiceKeys { get; }
 29414358    public string? SourceFilePath { get; }
 59    /// <summary>
 60    /// The 1-based line number where this type is declared.
 61    /// </summary>
 23110962    public int SourceLine { get; }
 63    /// <summary>
 64    /// True if this type implements IDisposable or IAsyncDisposable.
 65    /// </summary>
 243366    public bool IsDisposable { get; }
 67
 68    /// <summary>
 69    /// Gets the constructor parameter types (for backward compatibility with existing code paths).
 70    /// </summary>
 24457871    public string[] ConstructorParameterTypes => ConstructorParameters.Select(p => p.TypeName).ToArray();
 72
 73    /// <summary>
 74    /// True if any constructor parameters are keyed services.
 75    /// </summary>
 076    public bool HasKeyedParameters => ConstructorParameters.Any(p => p.IsKeyed);
 77
 78    /// <summary>
 79    /// True if this type has [Keyed] attributes for keyed registration.
 80    /// </summary>
 4787081    public bool IsKeyed => ServiceKeys.Length > 0;
 82}
 83
 84/// <summary>
 85/// Information about a discovered plugin type (implements INeedlrPlugin interfaces).
 86/// </summary>
 87internal readonly struct DiscoveredPlugin
 88{
 89    public DiscoveredPlugin(string typeName, string[] interfaceNames, string assemblyName, string[] attributeNames, stri
 90    {
 91        TypeName = typeName;
 92        InterfaceNames = interfaceNames;
 93        AssemblyName = assemblyName;
 94        AttributeNames = attributeNames;
 95        SourceFilePath = sourceFilePath;
 96        Order = order;
 97    }
 98
 99    public string TypeName { get; }
 100    public string[] InterfaceNames { get; }
 101    public string AssemblyName { get; }
 102    public string[] AttributeNames { get; }
 103    public string? SourceFilePath { get; }
 104    public int Order { get; }
 105}
 106
 107/// <summary>
 108/// Information about a closed-generic decorator (from [DecoratorFor&lt;T&gt;]).
 109/// </summary>
 110internal readonly struct DiscoveredDecorator
 111{
 112    public DiscoveredDecorator(string decoratorTypeName, string serviceTypeName, int order, string assemblyName, string?
 113    {
 114        DecoratorTypeName = decoratorTypeName;
 115        ServiceTypeName = serviceTypeName;
 116        Order = order;
 117        AssemblyName = assemblyName;
 118        SourceFilePath = sourceFilePath;
 119    }
 120
 121    public string DecoratorTypeName { get; }
 122    public string ServiceTypeName { get; }
 123    public int Order { get; }
 124    public string AssemblyName { get; }
 125    public string? SourceFilePath { get; }
 126}
 127
 128/// <summary>
 129/// Information about a discovered hosted service (BackgroundService or IHostedService implementation).
 130/// </summary>
 131internal readonly struct DiscoveredHostedService
 132{
 133    public DiscoveredHostedService(
 134        string typeName,
 135        string assemblyName,
 136        GeneratorLifetime lifetime,
 137        TypeDiscoveryHelper.ConstructorParameterInfo[] constructorParameters,
 138        string? sourceFilePath = null)
 139    {
 140        TypeName = typeName;
 141        AssemblyName = assemblyName;
 142        Lifetime = lifetime;
 143        ConstructorParameters = constructorParameters;
 144        SourceFilePath = sourceFilePath;
 145    }
 146
 147    public string TypeName { get; }
 148    public string AssemblyName { get; }
 149    public GeneratorLifetime Lifetime { get; }
 150    public TypeDiscoveryHelper.ConstructorParameterInfo[] ConstructorParameters { get; }
 151    public string? SourceFilePath { get; }
 152}
 153
 154/// <summary>
 155/// Information about an open-generic decorator (from [OpenDecoratorFor(typeof(IHandler&lt;&gt;))]).
 156/// </summary>
 157internal readonly struct DiscoveredOpenDecorator
 158{
 159    public DiscoveredOpenDecorator(
 160        INamedTypeSymbol decoratorType,
 161        INamedTypeSymbol openGenericInterface,
 162        int order,
 163        string assemblyName,
 164        string? sourceFilePath = null)
 165    {
 166        DecoratorType = decoratorType;
 167        OpenGenericInterface = openGenericInterface;
 168        Order = order;
 169        AssemblyName = assemblyName;
 170        SourceFilePath = sourceFilePath;
 171    }
 172
 173    public INamedTypeSymbol DecoratorType { get; }
 174    public INamedTypeSymbol OpenGenericInterface { get; }
 175    public int Order { get; }
 176    public string AssemblyName { get; }
 177    public string? SourceFilePath { get; }
 178}
 179
 180/// <summary>
 181/// Information about a type that would be injectable but is inaccessible (internal/private).
 182/// </summary>
 183internal readonly struct InaccessibleType
 184{
 185    public InaccessibleType(string typeName, string assemblyName)
 186    {
 187        TypeName = typeName;
 188        AssemblyName = assemblyName;
 189    }
 190
 191    public string TypeName { get; }
 192    public string AssemblyName { get; }
 193}
 194
 195/// <summary>
 196/// Information about a plugin type from a referenced assembly that's missing [GenerateTypeRegistry].
 197/// </summary>
 198internal readonly struct MissingTypeRegistryPlugin
 199{
 200    public MissingTypeRegistryPlugin(string typeName, string assemblyName)
 201    {
 202        TypeName = typeName;
 203        AssemblyName = assemblyName;
 204    }
 205
 206    public string TypeName { get; }
 207    public string AssemblyName { get; }
 208}
 209
 210/// <summary>
 211/// Information about an intercepted service (from [Intercept&lt;T&gt;]).
 212/// </summary>
 213internal readonly struct DiscoveredInterceptedService
 214{
 215    public DiscoveredInterceptedService(
 216        string typeName,
 217        string[] interfaceNames,
 218        string assemblyName,
 219        GeneratorLifetime lifetime,
 220        InterceptorDiscoveryHelper.InterceptedMethodInfo[] methods,
 221        string[] allInterceptorTypeNames,
 222        string? sourceFilePath = null)
 223    {
 224        TypeName = typeName;
 225        InterfaceNames = interfaceNames;
 226        AssemblyName = assemblyName;
 227        Lifetime = lifetime;
 228        Methods = methods;
 229        AllInterceptorTypeNames = allInterceptorTypeNames;
 230        SourceFilePath = sourceFilePath;
 231    }
 232
 233    public string TypeName { get; }
 234    public string[] InterfaceNames { get; }
 235    public string AssemblyName { get; }
 236    public GeneratorLifetime Lifetime { get; }
 237    public InterceptorDiscoveryHelper.InterceptedMethodInfo[] Methods { get; }
 238    public string[] AllInterceptorTypeNames { get; }
 239    public string? SourceFilePath { get; }
 240}
 241
 242/// <summary>
 243/// Information about a factory-generated type (from [GenerateFactory]).
 244/// </summary>
 245internal readonly struct DiscoveredFactory
 246{
 247    public DiscoveredFactory(
 248        string typeName,
 249        string[] interfaceNames,
 250        string assemblyName,
 251        int generationMode,
 252        FactoryDiscoveryHelper.FactoryConstructorInfo[] constructors,
 253        string? returnTypeName = null,
 254        string? sourceFilePath = null)
 255    {
 256        TypeName = typeName;
 257        InterfaceNames = interfaceNames;
 258        AssemblyName = assemblyName;
 259        GenerationMode = generationMode;
 260        Constructors = constructors;
 261        ReturnTypeOverride = returnTypeName;
 262        SourceFilePath = sourceFilePath;
 263    }
 264
 265    public string TypeName { get; }
 266    public string[] InterfaceNames { get; }
 267    public string AssemblyName { get; }
 268    /// <summary>Mode flags: 1=Func, 2=Interface, 3=All</summary>
 269    public int GenerationMode { get; }
 270    public FactoryDiscoveryHelper.FactoryConstructorInfo[] Constructors { get; }
 271    /// <summary>
 272    /// If set, the factory Create() and Func return this type instead of the concrete type.
 273    /// Used when [GenerateFactory&lt;T&gt;] is applied.
 274    /// </summary>
 275    public string? ReturnTypeOverride { get; }
 276    public string? SourceFilePath { get; }
 277
 278    public bool GenerateFunc => (GenerationMode & 1) != 0;
 279    public bool GenerateInterface => (GenerationMode & 2) != 0;
 280
 281    /// <summary>Gets the type that factory Create() and Func should return.</summary>
 282    public string ReturnTypeName => ReturnTypeOverride ?? TypeName;
 283
 284    /// <summary>Gets just the type name without namespace (e.g., "MyService" from "global::TestApp.MyService").</summar
 285    public string SimpleTypeName
 286    {
 287        get
 288        {
 289            var parts = TypeName.Split('.');
 290            return parts[parts.Length - 1];
 291        }
 292    }
 293}
 294
 295/// <summary>
 296/// Information about a discovered options type (from [Options]).
 297/// </summary>
 298internal readonly struct DiscoveredOptions
 299{
 300    public DiscoveredOptions(
 301        string typeName,
 302        string sectionName,
 303        string? name,
 304        bool validateOnStart,
 305        string assemblyName,
 306        string? sourceFilePath = null,
 307        OptionsValidatorInfo? validatorMethod = null,
 308        string? validateMethodOverride = null,
 309        string? validatorTypeName = null,
 310        PositionalRecordInfo? positionalRecordInfo = null,
 311        IReadOnlyList<OptionsPropertyInfo>? properties = null)
 312    {
 313        TypeName = typeName;
 314        SectionName = sectionName;
 315        Name = name;
 316        ValidateOnStart = validateOnStart;
 317        AssemblyName = assemblyName;
 318        SourceFilePath = sourceFilePath;
 319        ValidatorMethod = validatorMethod;
 320        ValidateMethodOverride = validateMethodOverride;
 321        ValidatorTypeName = validatorTypeName;
 322        PositionalRecordInfo = positionalRecordInfo;
 323        Properties = properties ?? Array.Empty<OptionsPropertyInfo>();
 324    }
 325
 326    /// <summary>Fully qualified type name of the options class.</summary>
 327    public string TypeName { get; }
 328
 329    /// <summary>Configuration section name (e.g., "Database").</summary>
 330    public string SectionName { get; }
 331
 332    /// <summary>Named options name (e.g., "Primary"), or null for default options.</summary>
 333    public string? Name { get; }
 334
 335    /// <summary>Whether to validate options on startup.</summary>
 336    public bool ValidateOnStart { get; }
 337
 338    public string AssemblyName { get; }
 339    public string? SourceFilePath { get; }
 340
 341    /// <summary>Information about the validation method (discovered or specified).</summary>
 342    public OptionsValidatorInfo? ValidatorMethod { get; }
 343
 344    /// <summary>Custom validation method name override from [Options(ValidateMethod = "...")], or null to use conventio
 345    public string? ValidateMethodOverride { get; }
 346
 347    /// <summary>External validator type name from [Options(Validator = typeof(...))], or null to use options class.</su
 348    public string? ValidatorTypeName { get; }
 349
 350    /// <summary>Information about positional record primary constructor, if applicable.</summary>
 351    public PositionalRecordInfo? PositionalRecordInfo { get; }
 352
 353    /// <summary>Bindable properties for AOT code generation.</summary>
 354    public IReadOnlyList<OptionsPropertyInfo> Properties { get; }
 355
 356    /// <summary>True if this is a named options registration (not default).</summary>
 357    public bool IsNamed => Name != null;
 358
 359    /// <summary>True if this options type has a custom validator method.</summary>
 360    public bool HasValidatorMethod => ValidatorMethod != null;
 361
 362    /// <summary>True if an external validator type is specified.</summary>
 363    public bool HasExternalValidator => ValidatorTypeName != null;
 364
 365    /// <summary>True if this is a positional record that needs a generated parameterless constructor.</summary>
 366    public bool NeedsGeneratedConstructor => PositionalRecordInfo?.IsPartial == true;
 367
 368    /// <summary>True if this is a non-partial positional record (will emit diagnostic).</summary>
 369    public bool IsNonPartialPositionalRecord => PositionalRecordInfo != null && !PositionalRecordInfo.Value.IsPartial;
 370
 371    /// <summary>True if this type has any init-only properties (requires factory pattern in AOT).</summary>
 372    public bool HasInitOnlyProperties => Properties.Any(p => p.HasInitOnlySetter);
 373
 374    /// <summary>True if this is a positional record (uses constructor binding in AOT).</summary>
 375    public bool IsPositionalRecord => PositionalRecordInfo != null;
 376
 377    /// <summary>True if this type requires factory pattern (Options.Create) instead of Configure delegate in AOT.</summ
 378    public bool RequiresFactoryPattern => IsPositionalRecord || HasInitOnlyProperties;
 379
 380    /// <summary>True if any property has DataAnnotation validation attributes.</summary>
 381    public bool HasDataAnnotations => Properties.Any(p => p.HasDataAnnotations);
 382}
 383
 384/// <summary>
 385/// Information about a positional record's primary constructor parameters.
 386/// </summary>
 387internal readonly struct PositionalRecordInfo
 388{
 389    public PositionalRecordInfo(
 390        string shortTypeName,
 391        string containingNamespace,
 392        bool isPartial,
 393        IReadOnlyList<PositionalRecordParameter> parameters)
 394    {
 395        ShortTypeName = shortTypeName;
 396        ContainingNamespace = containingNamespace;
 397        IsPartial = isPartial;
 398        Parameters = parameters;
 399    }
 400
 401    /// <summary>The simple type name (without namespace).</summary>
 402    public string ShortTypeName { get; }
 403
 404    /// <summary>The containing namespace.</summary>
 405    public string ContainingNamespace { get; }
 406
 407    /// <summary>Whether the record is declared as partial.</summary>
 408    public bool IsPartial { get; }
 409
 410    /// <summary>The primary constructor parameters.</summary>
 411    public IReadOnlyList<PositionalRecordParameter> Parameters { get; }
 412}
 413
 414/// <summary>
 415/// A parameter in a positional record's primary constructor.
 416/// </summary>
 417internal readonly struct PositionalRecordParameter
 418{
 419    public PositionalRecordParameter(string name, string typeName)
 420    {
 421        Name = name;
 422        TypeName = typeName;
 423    }
 424
 425    public string Name { get; }
 426    public string TypeName { get; }
 427}
 428
 429/// <summary>
 430/// Information about a bindable property on an options class (for AOT code generation).
 431/// </summary>
 432internal readonly struct OptionsPropertyInfo
 433{
 434    public OptionsPropertyInfo(
 435        string name,
 436        string typeName,
 437        bool isNullable,
 438        bool hasInitOnlySetter,
 439        bool isEnum = false,
 440        string? enumTypeName = null,
 441        ComplexTypeKind complexTypeKind = ComplexTypeKind.None,
 442        string? elementTypeName = null,
 443        IReadOnlyList<OptionsPropertyInfo>? nestedProperties = null,
 444        IReadOnlyList<DataAnnotationInfo>? dataAnnotations = null)
 445    {
 446        Name = name;
 447        TypeName = typeName;
 448        IsNullable = isNullable;
 449        HasInitOnlySetter = hasInitOnlySetter;
 450        IsEnum = isEnum;
 451        EnumTypeName = enumTypeName;
 452        ComplexTypeKind = complexTypeKind;
 453        ElementTypeName = elementTypeName;
 454        NestedProperties = nestedProperties;
 455        DataAnnotations = dataAnnotations ?? Array.Empty<DataAnnotationInfo>();
 456    }
 457
 458    /// <summary>Property name.</summary>
 459    public string Name { get; }
 460
 461    /// <summary>Fully qualified type name.</summary>
 462    public string TypeName { get; }
 463
 464    /// <summary>True if the property type is nullable.</summary>
 465    public bool IsNullable { get; }
 466
 467    /// <summary>True if the property has an init-only setter.</summary>
 468    public bool HasInitOnlySetter { get; }
 469
 470    /// <summary>True if the property type is an enum.</summary>
 471    public bool IsEnum { get; }
 472
 473    /// <summary>The underlying enum type name (for nullable enums, this is the non-nullable type).</summary>
 474    public string? EnumTypeName { get; }
 475
 476    /// <summary>The kind of complex type (nested object, array, list, dictionary).</summary>
 477    public ComplexTypeKind ComplexTypeKind { get; }
 478
 479    /// <summary>For collections, the element type. For dictionaries, the value type.</summary>
 480    public string? ElementTypeName { get; }
 481
 482    /// <summary>For nested objects and collection element types, the bindable properties.</summary>
 483    public IReadOnlyList<OptionsPropertyInfo>? NestedProperties { get; }
 484
 485    /// <summary>DataAnnotation validation attributes on this property.</summary>
 486    public IReadOnlyList<DataAnnotationInfo> DataAnnotations { get; }
 487
 488    /// <summary>True if this property has any DataAnnotation validation attributes.</summary>
 489    public bool HasDataAnnotations => DataAnnotations.Count > 0;
 490}
 491
 492/// <summary>
 493/// Identifies the kind of complex type for AOT binding generation.
 494/// </summary>
 495internal enum ComplexTypeKind
 496{
 497    /// <summary>Not a complex type (primitive, enum, etc).</summary>
 498    None,
 499    /// <summary>A nested object with properties to bind.</summary>
 500    NestedObject,
 501    /// <summary>An array type (T[]).</summary>
 502    Array,
 503    /// <summary>A list type (List&lt;T&gt;, IList&lt;T&gt;, etc).</summary>
 504    List,
 505    /// <summary>A dictionary type (Dictionary&lt;string, T&gt;).</summary>
 506    Dictionary
 507}
 508
 509/// <summary>
 510/// Identifies the kind of DataAnnotation attribute for source-generated validation.
 511/// </summary>
 512internal enum DataAnnotationKind
 513{
 514    Required,
 515    Range,
 516    StringLength,
 517    MinLength,
 518    MaxLength,
 519    RegularExpression,
 520    EmailAddress,
 521    Phone,
 522    Url,
 523    Unsupported
 524}
 525
 526/// <summary>
 527/// Information about a DataAnnotation attribute on an options property.
 528/// </summary>
 529internal readonly struct DataAnnotationInfo
 530{
 531    public DataAnnotationInfo(
 532        DataAnnotationKind kind,
 533        string? errorMessage = null,
 534        object? minimum = null,
 535        object? maximum = null,
 536        string? pattern = null,
 537        int? minimumLength = null)
 538    {
 539        Kind = kind;
 540        ErrorMessage = errorMessage;
 541        Minimum = minimum;
 542        Maximum = maximum;
 543        Pattern = pattern;
 544        MinimumLength = minimumLength;
 545    }
 546
 547    /// <summary>The kind of DataAnnotation attribute.</summary>
 548    public DataAnnotationKind Kind { get; }
 549
 550    /// <summary>Custom error message if specified.</summary>
 551    public string? ErrorMessage { get; }
 552
 553    /// <summary>Minimum value for Range attribute.</summary>
 554    public object? Minimum { get; }
 555
 556    /// <summary>Maximum value for Range/StringLength/MaxLength attributes.</summary>
 557    public object? Maximum { get; }
 558
 559    /// <summary>Pattern for RegularExpression attribute.</summary>
 560    public string? Pattern { get; }
 561
 562    /// <summary>Minimum length for StringLength/MinLength attributes.</summary>
 563    public int? MinimumLength { get; }
 564}
 565
 566/// <summary>
 567/// Information about a validation method.
 568/// </summary>
 569internal readonly struct OptionsValidatorInfo
 570{
 571    public OptionsValidatorInfo(string methodName, bool isStatic)
 572    {
 573        MethodName = methodName;
 574        IsStatic = isStatic;
 575    }
 576
 577    /// <summary>Name of the validator method.</summary>
 578    public string MethodName { get; }
 579
 580    /// <summary>True if the method is static.</summary>
 581    public bool IsStatic { get; }
 582}
 583
 584/// <summary>
 585/// Information about a discovered Provider (from [Provider] attribute).
 586/// </summary>
 587internal readonly struct DiscoveredProvider
 588{
 589    public DiscoveredProvider(
 590        string typeName,
 591        string assemblyName,
 592        bool isInterface,
 593        bool isPartial,
 594        IReadOnlyList<ProviderPropertyInfo> properties,
 595        string? sourceFilePath = null)
 596    {
 597        TypeName = typeName;
 598        AssemblyName = assemblyName;
 599        IsInterface = isInterface;
 600        IsPartial = isPartial;
 601        Properties = properties;
 602        SourceFilePath = sourceFilePath;
 603    }
 604
 605    /// <summary>Fully qualified type name of the interface or class.</summary>
 606    public string TypeName { get; }
 607
 608    public string AssemblyName { get; }
 609
 610    /// <summary>True if the [Provider] attribute is on an interface.</summary>
 611    public bool IsInterface { get; }
 612
 613    /// <summary>True if the type is a partial class (required for shorthand mode).</summary>
 614    public bool IsPartial { get; }
 615
 616    /// <summary>Properties to generate on the provider.</summary>
 617    public IReadOnlyList<ProviderPropertyInfo> Properties { get; }
 618
 619    public string? SourceFilePath { get; }
 620
 621    /// <summary>Gets simple type name without namespace (e.g., "IOrderProvider" from "global::TestApp.IOrderProvider").
 622    public string SimpleTypeName
 623    {
 624        get
 625        {
 626            var name = TypeName;
 627            var lastDot = name.LastIndexOf('.');
 628            return lastDot >= 0 ? name.Substring(lastDot + 1) : name;
 629        }
 630    }
 631
 632    /// <summary>Gets the implementation class name (removes leading "I" from interface name if present).</summary>
 633    public string ImplementationTypeName
 634    {
 635        get
 636        {
 637            var simple = SimpleTypeName;
 638            if (IsInterface && simple.StartsWith("I") && simple.Length > 1 && char.IsUpper(simple[1]))
 639            {
 640                return simple.Substring(1);
 641            }
 642            return simple;
 643        }
 644    }
 645
 646    /// <summary>Gets the interface name (adds leading "I" to class name if needed).</summary>
 647    public string InterfaceTypeName
 648    {
 649        get
 650        {
 651            var simple = SimpleTypeName;
 652            // For non-interfaces, add "I" prefix unless the name already follows interface naming convention (IXxx)
 653            if (!IsInterface)
 654            {
 655                // Only treat as already having interface prefix if it starts with "I" followed by uppercase letter
 656                if (simple.Length > 1 && simple[0] == 'I' && char.IsUpper(simple[1]))
 657                {
 658                    return simple;
 659                }
 660                return "I" + simple;
 661            }
 662            return simple;
 663        }
 664    }
 665}
 666
 667/// <summary>
 668/// Information about a property on a Provider.
 669/// </summary>
 670internal readonly struct ProviderPropertyInfo
 671{
 672    public ProviderPropertyInfo(
 673        string propertyName,
 674        string serviceTypeName,
 675        ProviderPropertyKind kind)
 676    {
 677        PropertyName = propertyName;
 678        ServiceTypeName = serviceTypeName;
 679        Kind = kind;
 680    }
 681
 682    /// <summary>Property name on the generated provider.</summary>
 683    public string PropertyName { get; }
 684
 685    /// <summary>Fully qualified service type name.</summary>
 686    public string ServiceTypeName { get; }
 687
 688    /// <summary>How this property should be resolved.</summary>
 689    public ProviderPropertyKind Kind { get; }
 690}
 691
 692/// <summary>
 693/// Indicates how a provider property should be resolved.
 694/// </summary>
 695internal enum ProviderPropertyKind
 696{
 697    /// <summary>Required service - uses GetRequiredService&lt;T&gt;().</summary>
 698    Required,
 699
 700    /// <summary>Optional service - uses GetService&lt;T&gt;() and is nullable.</summary>
 701    Optional,
 702
 703    /// <summary>Collection of services - uses GetServices&lt;T&gt;().</summary>
 704    Collection,
 705
 706    /// <summary>Factory for creating new instances.</summary>
 707    Factory
 708}
 709
 710/// <summary>
 711/// Aggregated result of type discovery for an assembly.
 712/// </summary>
 713internal readonly struct DiscoveryResult
 714{
 715    public DiscoveryResult(
 716        IReadOnlyList<DiscoveredType> injectableTypes,
 717        IReadOnlyList<DiscoveredPlugin> pluginTypes,
 718        IReadOnlyList<DiscoveredDecorator> decorators,
 719        IReadOnlyList<InaccessibleType> inaccessibleTypes,
 720        IReadOnlyList<MissingTypeRegistryPlugin> missingTypeRegistryPlugins,
 721        IReadOnlyList<DiscoveredInterceptedService> interceptedServices,
 722        IReadOnlyList<DiscoveredFactory> factories,
 723        IReadOnlyList<DiscoveredOptions> options,
 724        IReadOnlyList<DiscoveredHostedService> hostedServices,
 725        IReadOnlyList<DiscoveredProvider> providers)
 726    {
 727        InjectableTypes = injectableTypes;
 728        PluginTypes = pluginTypes;
 729        Decorators = decorators;
 730        InaccessibleTypes = inaccessibleTypes;
 731        MissingTypeRegistryPlugins = missingTypeRegistryPlugins;
 732        InterceptedServices = interceptedServices;
 733        Factories = factories;
 734        Options = options;
 735        HostedServices = hostedServices;
 736        Providers = providers;
 737    }
 738
 739    public IReadOnlyList<DiscoveredType> InjectableTypes { get; }
 740    public IReadOnlyList<DiscoveredPlugin> PluginTypes { get; }
 741    public IReadOnlyList<DiscoveredDecorator> Decorators { get; }
 742    public IReadOnlyList<InaccessibleType> InaccessibleTypes { get; }
 743    public IReadOnlyList<MissingTypeRegistryPlugin> MissingTypeRegistryPlugins { get; }
 744    public IReadOnlyList<DiscoveredInterceptedService> InterceptedServices { get; }
 745    public IReadOnlyList<DiscoveredFactory> Factories { get; }
 746    public IReadOnlyList<DiscoveredOptions> Options { get; }
 747    public IReadOnlyList<DiscoveredHostedService> HostedServices { get; }
 748    public IReadOnlyList<DiscoveredProvider> Providers { get; }
 749}
 750
 751/// <summary>
 752/// Information parsed from [GenerateTypeRegistry] attribute.
 753/// </summary>
 754internal readonly struct AttributeInfo
 755{
 756    public AttributeInfo(string[]? namespacePrefixes, bool includeSelf)
 757    {
 758        NamespacePrefixes = namespacePrefixes;
 759        IncludeSelf = includeSelf;
 760    }
 761
 762    public string[]? NamespacePrefixes { get; }
 763    public bool IncludeSelf { get; }
 764}