< Summary

Information
Class: NexusLabs.Needlr.Injection.Reflection.PluginFactories.ReflectionPluginFactory
Assembly: NexusLabs.Needlr.Injection.Reflection
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Injection.Reflection/PluginFactories/ReflectionPluginFactory.cs
Line coverage
96%
Covered lines: 49
Uncovered lines: 2
Coverable lines: 51
Total lines: 199
Line coverage: 96%
Branch coverage
100%
Covered branches: 48
Total branches: 48
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
CreatePluginsFromAssemblies(...)100%2626100%
CreatePluginsWithAttributeFromAssemblies(...)100%1212100%
GetTypesFromAssemblies(...)100%1187.5%
IsValidPluginType(...)100%88100%
HasDoNotAutoRegisterAttribute(...)100%11100%
HasParameterlessConstructor(...)100%11100%
HasAttribute(...)100%11100%
GetPluginOrder(...)100%22100%
CreateInstance(...)100%11100%
CreateInstance(...)100%11100%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Injection.Reflection/PluginFactories/ReflectionPluginFactory.cs

#LineLine coverage
 1using Microsoft.Extensions.DependencyInjection;
 2
 3using System.Diagnostics.CodeAnalysis;
 4using System.Reflection;
 5
 6namespace NexusLabs.Needlr.Injection.Reflection.PluginFactories;
 7
 8/// <summary>
 9/// Plugin factory that uses runtime reflection to scan assemblies for types that implement specified interfaces,
 10/// are decorated with particular attributes, or meet both criteria. All discovered types are instantiated
 11/// using their parameterless constructors.
 12/// </summary>
 13/// <remarks>
 14/// This factory uses runtime reflection and is not compatible with NativeAOT or trimming.
 15/// For AOT scenarios, use the GeneratedPluginFactory from NexusLabs.Needlr.Injection.SourceGen instead.
 16/// </remarks>
 17[RequiresUnreferencedCode("ReflectionPluginFactory uses reflection to discover and instantiate plugins. Use GeneratedPlu
 18public sealed class ReflectionPluginFactory : IPluginFactory
 19{
 20    /// <inheritdoc />
 21    /// <remarks>
 22    /// <para>
 23    /// Only non-abstract, non-generic classes assignable to <typeparamref name="TPlugin"/> are instantiated.
 24    /// Types that cannot be loaded from an assembly are skipped.
 25    /// </para>
 26    /// <para>
 27    /// Plugins are sorted by their <see cref="PluginOrderAttribute.Order"/> value (lower first),
 28    /// then alphabetically by fully qualified type name for deterministic execution order.
 29    /// </para>
 30    /// </remarks>
 31    public IEnumerable<TPlugin> CreatePluginsFromAssemblies<TPlugin>(
 32        IEnumerable<Assembly> assemblies)
 33        where TPlugin : class
 34    {
 73435        return GetTypesFromAssemblies(assemblies)
 23878136            .Where(t => IsValidPluginType(t) && t.IsAssignableTo(typeof(TPlugin)))
 222337            .Select(t => (Type: t, Order: GetPluginOrder(t)))
 222338            .OrderBy(x => x.Order)
 222339            .ThenBy(x => x.Type.FullName, StringComparer.Ordinal)
 295740            .Select(x => CreateInstance<TPlugin>(x.Type));
 41    }
 42
 43    /// <inheritdoc />
 44    /// <remarks>
 45    /// <para>
 46    /// Only non-abstract, non-generic classes decorated with <typeparamref name="TAttribute"/> are instantiated.
 47    /// Types that cannot be loaded from an assembly are skipped. The attribute is searched in the type hierarchy
 48    /// using inheritance.
 49    /// </para>
 50    /// <para>
 51    /// Plugins are sorted by their <see cref="PluginOrderAttribute.Order"/> value (lower first),
 52    /// then alphabetically by fully qualified type name for deterministic execution order.
 53    /// </para>
 54    /// </remarks>
 55    public IEnumerable<object> CreatePluginsWithAttributeFromAssemblies<TAttribute>(
 56        IEnumerable<Assembly> assemblies)
 57        where TAttribute : Attribute
 58    {
 759        return GetTypesFromAssemblies(assemblies)
 760            .Where(t =>
 158261                IsValidPluginType(t) &&
 158262                HasAttribute<TAttribute>(t))
 1263            .Select(t => (Type: t, Order: GetPluginOrder(t)))
 1264            .OrderBy(x => x.Order)
 1265            .ThenBy(x => x.Type.FullName, StringComparer.Ordinal)
 1966            .Select(x => CreateInstance(x.Type));
 67    }
 68
 69    /// <inheritdoc />
 70    /// <remarks>
 71    /// <para>
 72    /// Only non-abstract, non-generic classes assignable to <typeparamref name="TPlugin"/>
 73    /// and decorated with <typeparamref name="TAttribute"/> are instantiated.
 74    /// Types that cannot be loaded from an assembly are skipped. The attribute is
 75    /// searched in the type hierarchy using inheritance.
 76    /// </para>
 77    /// <para>
 78    /// Plugins are sorted by their <see cref="PluginOrderAttribute.Order"/> value (lower first),
 79    /// then alphabetically by fully qualified type name for deterministic execution order.
 80    /// </para>
 81    /// </remarks>
 82    public IEnumerable<TPlugin> CreatePluginsFromAssemblies<TPlugin, TAttribute>(
 83        IEnumerable<Assembly> assemblies)
 84        where TPlugin : class
 85        where TAttribute : Attribute
 86    {
 1287        return GetTypesFromAssemblies(assemblies)
 1288            .Where(t =>
 444489                IsValidPluginType(t) &&
 444490                t.IsAssignableTo(typeof(TPlugin)) &&
 444491                HasAttribute<TAttribute>(t))
 2392            .Select(t => (Type: t, Order: GetPluginOrder(t)))
 2393            .OrderBy(x => x.Order)
 2394            .ThenBy(x => x.Type.FullName, StringComparer.Ordinal)
 3595            .Select(x => CreateInstance<TPlugin>(x.Type));
 96    }
 97
 98    /// <summary>
 99    /// Gets all types from the provided assemblies, handling assembly loading exceptions gracefully.
 100    /// </summary>
 101    /// <param name="assemblies">The assemblies to extract types from.</param>
 102    /// <returns>An enumerable of types that were successfully loaded.</returns>
 103    private static IEnumerable<Type> GetTypesFromAssemblies(IEnumerable<Assembly> assemblies)
 104    {
 753105        return assemblies
 753106            .SelectMany(assembly =>
 753107            {
 753108                try
 753109                {
 912110                    return assembly.GetTypes();
 753111                }
 753112                catch (ReflectionTypeLoadException ex)
 753113                {
 1114                    return ex.Types.OfType<Type>();
 753115                }
 0116                catch
 753117                {
 0118                    return [];
 753119                }
 1665120            });
 121    }
 122
 123    /// <summary>
 124    /// Determines if a type is a valid plugin type (non-abstract, non-generic class with parameterless constructor).
 125    /// </summary>
 126    /// <param name="type">The type to validate.</param>
 127    /// <returns>True if the type is a valid plugin type; otherwise, false.</returns>
 128    private static bool IsValidPluginType(Type type)
 129    {
 244807130        return type.IsClass &&
 244807131               !type.IsAbstract &&
 244807132               !type.IsGenericTypeDefinition &&
 244807133               HasParameterlessConstructor(type) &&
 244807134               !HasDoNotAutoRegisterAttribute(type);
 135    }
 136
 137    /// <summary>
 138    /// Determines if a type has the [DoNotAutoRegister] attribute directly applied.
 139    /// </summary>
 140    /// <param name="type">The type to check.</param>
 141    /// <returns>True if the type has the attribute; otherwise, false.</returns>
 142    private static bool HasDoNotAutoRegisterAttribute(Type type)
 143    {
 140081144        return type.GetCustomAttribute<DoNotAutoRegisterAttribute>(inherit: false) is not null;
 145    }
 146
 147    /// <summary>
 148    /// Determines if a type has a parameterless constructor.
 149    /// </summary>
 150    /// <param name="type">The type to check.</param>
 151    /// <returns>True if the type has a parameterless constructor; otherwise, false.</returns>
 152    private static bool HasParameterlessConstructor(Type type)
 153    {
 170820154        return type.GetConstructor(Type.EmptyTypes) is not null;
 155    }
 156
 157    /// <summary>
 158    /// Determines if a type has the specified attribute in its type hierarchy.
 159    /// </summary>
 160    /// <typeparam name="TAttribute">The attribute type to search for.</typeparam>
 161    /// <param name="type">The type to check for the attribute.</param>
 162    /// <returns>True if the type has the attribute; otherwise, false.</returns>
 163    private static bool HasAttribute<TAttribute>(Type type) where TAttribute : Attribute
 164    {
 1048165        return type.GetCustomAttribute<TAttribute>(inherit: true) is not null;
 166    }
 167
 168    /// <summary>
 169    /// Gets the plugin execution order from the <see cref="PluginOrderAttribute"/>.
 170    /// </summary>
 171    /// <param name="type">The type to check.</param>
 172    /// <returns>The order value, or 0 if no attribute is present.</returns>
 173    private static int GetPluginOrder(Type type)
 174    {
 2258175        var attr = type.GetCustomAttribute<PluginOrderAttribute>(inherit: false);
 2258176        return attr?.Order ?? 0;
 177    }
 178
 179    /// <summary>
 180    /// Creates an instance of the specified type.
 181    /// </summary>
 182    /// <param name="type">The type to instantiate.</param>
 183    /// <returns>An instance of the type.</returns>
 184    private static object CreateInstance(Type type)
 185    {
 12186        return Activator.CreateInstance(type)!;
 187    }
 188
 189    /// <summary>
 190    /// Creates an instance of the specified type and casts it to the target type.
 191    /// </summary>
 192    /// <typeparam name="T">The target type to cast to.</typeparam>
 193    /// <param name="type">The type to instantiate.</param>
 194    /// <returns>An instance of the type cast to the target type.</returns>
 195    private static T CreateInstance<T>(Type type) where T : class
 196    {
 2246197        return (T)Activator.CreateInstance(type)!;
 198    }
 199}