< Summary

Information
Class: NexusLabs.Needlr.Injection.SourceGen.PluginFactories.GeneratedPluginFactory
Assembly: NexusLabs.Needlr.Injection.SourceGen
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Injection.SourceGen/PluginFactories/GeneratedPluginFactory.cs
Line coverage
97%
Covered lines: 81
Uncovered lines: 2
Coverable lines: 83
Total lines: 211
Line coverage: 97.5%
Branch coverage
88%
Covered branches: 53
Total branches: 60
Branch coverage: 88.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
CreatePlugins()100%66100%
CreatePluginsFromAssemblies(...)82.35%343495.34%
CreatePluginsWithAttribute()100%44100%
CreatePluginsWithAttributeFromAssemblies(...)81.25%161694.73%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Injection.SourceGen/PluginFactories/GeneratedPluginFactory.cs

#LineLine coverage
 1using System.Reflection;
 2
 3using NexusLabs.Needlr.Generators;
 4
 5namespace NexusLabs.Needlr.Injection.SourceGen.PluginFactories;
 6
 7/// <summary>
 8/// A plugin factory that uses compile-time generated type information
 9/// instead of runtime reflection for plugin discovery and instantiation.
 10/// </summary>
 11/// <remarks>
 12/// <para>
 13/// This factory provides better performance and AOT compatibility by using
 14/// pre-computed plugin information and factory delegates generated at compile time.
 15/// </para>
 16/// <para>
 17/// To use this factory, your assembly must have:
 18/// <list type="bullet">
 19/// <item>A reference to <c>NexusLabs.Needlr.Generators</c></item>
 20/// <item>The <c>[assembly: GenerateTypeRegistry(...)]</c> attribute</item>
 21/// </list>
 22/// </para>
 23/// <para>
 24/// Plugins are sorted by their <see cref="PluginTypeInfo.Order"/> value (lower first),
 25/// then alphabetically by fully qualified type name for deterministic execution order.
 26/// Sorting is performed once during initialization.
 27/// </para>
 28/// </remarks>
 29[DoNotAutoRegister]
 30public sealed class GeneratedPluginFactory : IPluginFactory
 31{
 32    private readonly Lazy<IReadOnlyList<PluginTypeInfo>> _lazyPlugins;
 33    private readonly bool _allowAllWhenAssembliesEmpty;
 34
 35    /// <summary>
 36    /// Initializes a new instance of the <see cref="GeneratedPluginFactory"/> class
 37    /// with a custom plugin provider.
 38    /// </summary>
 39    /// <param name="pluginProvider">A function that returns the plugin types.</param>
 40    /// <param name="allowAllWhenAssembliesEmpty">
 41    /// When true, passing an empty assemblies collection to the factory methods will not filter plugins.
 42    /// This is useful for NativeAOT scenarios where producing an <see cref="Assembly"/> list without reflection is not 
 43    /// </param>
 59144    public GeneratedPluginFactory(
 59145        Func<IReadOnlyList<PluginTypeInfo>> pluginProvider,
 59146        bool allowAllWhenAssembliesEmpty = false)
 47    {
 59148        ArgumentNullException.ThrowIfNull(pluginProvider);
 49        // Sort once during initialization - filtering maintains relative order
 116450        _lazyPlugins = new(() => pluginProvider.Invoke()
 4602051            .OrderBy(info => info.Order)
 4602052            .ThenBy(info => info.PluginType.FullName, StringComparer.Ordinal)
 116453            .ToArray());
 59054        _allowAllWhenAssembliesEmpty = allowAllWhenAssembliesEmpty;
 59055    }
 56
 57    /// <summary>
 58    /// Creates instances of plugins of type <typeparamref name="TPlugin"/>.
 59    /// </summary>
 60    /// <typeparam name="TPlugin">The plugin interface or base type to search for.</typeparam>
 61    /// <returns>An enumerable of instantiated plugins implementing <typeparamref name="TPlugin"/>.</returns>
 62    /// <remarks>
 63    /// Plugins are returned in order by their <see cref="PluginTypeInfo.Order"/> value (lower first),
 64    /// then alphabetically by fully qualified type name for deterministic execution order.
 65    /// </remarks>
 66    public IEnumerable<TPlugin> CreatePlugins<TPlugin>()
 67        where TPlugin : class
 68    {
 25369        var pluginType = typeof(TPlugin);
 25370        return _lazyPlugins.Value
 2100171            .Where(info => pluginType.IsAssignableFrom(info.PluginType))
 25772            .Select(info => (TPlugin)info.Factory());
 73    }
 74
 75    /// <inheritdoc />
 76    public IEnumerable<TPlugin> CreatePluginsFromAssemblies<TPlugin>(
 77        IEnumerable<Assembly> assemblies)
 78        where TPlugin : class
 79    {
 31580        var assemblyList = assemblies as IReadOnlyCollection<Assembly> ?? assemblies.ToArray();
 31581        if (_allowAllWhenAssembliesEmpty && assemblyList.Count == 0)
 82        {
 483            return CreatePlugins<TPlugin>();
 84        }
 85
 31186        var assemblySet = assemblyList
 125487            .Select(a => a.GetName().Name)
 125488            .Where(n => n is not null)
 31189            .ToHashSet(StringComparer.Ordinal)!;
 90
 31191        var pluginType = typeof(TPlugin);
 31192        return _lazyPlugins.Value
 31193            .Where(info =>
 31194            {
 31195                // Check if this plugin is from one of the specified assemblies
 2424996                var pluginAssemblyName = info.PluginType.Assembly.GetName().Name;
 2424997                if (pluginAssemblyName is null || !assemblySet.Contains(pluginAssemblyName))
 45398                    return false;
 31199
 311100                // Check if plugin implements the requested type
 23796101                return pluginType.IsAssignableFrom(info.PluginType);
 311102            })
 2195103            .Select(info => (TPlugin)info.Factory());
 104    }
 105
 106    /// <summary>
 107    /// Creates instances of plugins that are decorated with the specified attribute.
 108    /// </summary>
 109    /// <typeparam name="TAttribute">The attribute type to search for in the type hierarchy.</typeparam>
 110    /// <returns>An enumerable of instantiated plugins decorated with <typeparamref name="TAttribute"/>.</returns>
 111    /// <remarks>
 112    /// Plugins are returned in order by their <see cref="PluginTypeInfo.Order"/> value (lower first),
 113    /// then alphabetically by fully qualified type name for deterministic execution order.
 114    /// </remarks>
 115    public IEnumerable<object> CreatePluginsWithAttribute<TAttribute>()
 116        where TAttribute : Attribute
 117    {
 3118        return _lazyPlugins.Value
 4119            .Where(info => info.HasAttribute<TAttribute>())
 5120            .Select(info => info.Factory());
 121    }
 122
 123    /// <inheritdoc />
 124    public IEnumerable<object> CreatePluginsWithAttributeFromAssemblies<TAttribute>(
 125        IEnumerable<Assembly> assemblies)
 126        where TAttribute : Attribute
 127    {
 4128        var assemblyList = assemblies as IReadOnlyCollection<Assembly> ?? assemblies.ToArray();
 4129        if (_allowAllWhenAssembliesEmpty && assemblyList.Count == 0)
 130        {
 0131            return CreatePluginsWithAttribute<TAttribute>();
 132        }
 133
 4134        var assemblySet = assemblyList
 4135            .Select(a => a.GetName().Name)
 4136            .Where(n => n is not null)
 4137            .ToHashSet(StringComparer.Ordinal)!;
 138
 4139        return _lazyPlugins.Value
 4140            .Where(info =>
 4141            {
 4142                // Check if this plugin is from one of the specified assemblies
 182143                var pluginAssemblyName = info.PluginType.Assembly.GetName().Name;
 182144                if (pluginAssemblyName is null || !assemblySet.Contains(pluginAssemblyName))
 16145                    return false;
 4146
 4147                // Use pre-computed attribute info - no reflection needed
 166148                return info.HasAttribute<TAttribute>();
 4149            })
 11150            .Select(info => info.Factory());
 151    }
 152
 153    /// <summary>
 154    /// Creates instances of plugins of type <typeparamref name="TPlugin"/> that are
 155    /// decorated with the specified attribute.
 156    /// </summary>
 157    /// <typeparam name="TPlugin">The plugin interface or base type to search for.</typeparam>
 158    /// <typeparam name="TAttribute">The attribute type to search for in the type hierarchy.</typeparam>
 159    /// <returns>
 160    /// An enumerable of instantiated plugins implementing <typeparamref name="TPlugin"/> and
 161    /// decorated with <typeparamref name="TAttribute"/>.
 162    /// </returns>
 163    /// <remarks>
 164    /// Plugins are returned in order by their <see cref="PluginTypeInfo.Order"/> value (lower first),
 165    /// then alphabetically by fully qualified type name for deterministic execution order.
 166    /// </remarks>
 167    public IEnumerable<TPlugin> CreatePlugins<TPlugin, TAttribute>()
 168        where TPlugin : class
 169        where TAttribute : Attribute
 170    {
 4171        var pluginType = typeof(TPlugin);
 4172        return _lazyPlugins.Value
 5173            .Where(info => pluginType.IsAssignableFrom(info.PluginType) && info.HasAttribute<TAttribute>())
 6174            .Select(info => (TPlugin)info.Factory());
 175    }
 176
 177    /// <inheritdoc />
 178    public IEnumerable<TPlugin> CreatePluginsFromAssemblies<TPlugin, TAttribute>(
 179        IEnumerable<Assembly> assemblies)
 180        where TPlugin : class
 181        where TAttribute : Attribute
 182    {
 10183        var assemblyList = assemblies as IReadOnlyCollection<Assembly> ?? assemblies.ToArray();
 10184        if (_allowAllWhenAssembliesEmpty && assemblyList.Count == 0)
 185        {
 0186            return CreatePlugins<TPlugin, TAttribute>();
 187        }
 188
 10189        var assemblySet = assemblyList
 10190            .Select(a => a.GetName().Name)
 10191            .Where(n => n is not null)
 10192            .ToHashSet(StringComparer.Ordinal)!;
 193
 10194        var pluginType = typeof(TPlugin);
 10195        return _lazyPlugins.Value
 10196            .Where(info =>
 10197            {
 10198                // Check if this plugin is from one of the specified assemblies
 811199                var pluginAssemblyName = info.PluginType.Assembly.GetName().Name;
 811200                if (pluginAssemblyName is null || !assemblySet.Contains(pluginAssemblyName))
 72201                    return false;
 10202
 739203                if (!pluginType.IsAssignableFrom(info.PluginType))
 657204                    return false;
 10205
 10206                // Use pre-computed attribute info - no reflection needed
 82207                return info.HasAttribute<TAttribute>();
 10208            })
 33209            .Select(info => (TPlugin)info.Factory());
 210    }
 211}