< Summary

Information
Class: NexusLabs.Needlr.Injection.AssemblyOrdering.AssemblyOrderBuilder
Assembly: NexusLabs.Needlr.Injection
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Injection/AssemblyOrdering/AssemblyOrderBuilder.cs
Line coverage
97%
Covered lines: 48
Uncovered lines: 1
Coverable lines: 49
Total lines: 129
Line coverage: 97.9%
Branch coverage
85%
Covered branches: 12
Total branches: 14
Branch coverage: 85.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
By(...)100%11100%
ThenBy(...)100%22100%
get_Rules()100%11100%
Sort(...)50%4494.11%
SortNames(...)100%44100%
GetTier(...)100%44100%

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Injection/AssemblyOrdering/AssemblyOrderBuilder.cs

#LineLine coverage
 1using System.Linq.Expressions;
 2using System.Reflection;
 3
 4namespace NexusLabs.Needlr.Injection.AssemblyOrdering;
 5
 6/// <summary>
 7/// Fluent builder for specifying assembly ordering rules.
 8/// Assemblies are sorted into tiers based on the first matching rule.
 9/// Unmatched assemblies are placed last.
 10/// </summary>
 11public sealed class AssemblyOrderBuilder
 12{
 4713    private readonly List<AssemblyOrderRule> _rules = new();
 14    private int _currentTier = 0;
 15
 16    /// <summary>
 17    /// Adds the first ordering rule. Assemblies matching this expression go first.
 18    /// </summary>
 19    /// <param name="predicate">Expression that determines if an assembly matches this tier.</param>
 20    /// <returns>The builder for chaining.</returns>
 21    public AssemblyOrderBuilder By(Expression<Func<AssemblyInfo, bool>> predicate)
 22    {
 4423        ArgumentNullException.ThrowIfNull(predicate);
 4324        _rules.Add(new AssemblyOrderRule(predicate, _currentTier++));
 4325        return this;
 26    }
 27
 28    /// <summary>
 29    /// Adds a subsequent ordering rule. Assemblies matching this expression
 30    /// (and not matching any previous rules) go in the next tier.
 31    /// </summary>
 32    /// <param name="predicate">Expression that determines if an assembly matches this tier.</param>
 33    /// <returns>The builder for chaining.</returns>
 34    public AssemblyOrderBuilder ThenBy(Expression<Func<AssemblyInfo, bool>> predicate)
 35    {
 2936        if (_rules.Count == 0)
 37        {
 138            throw new InvalidOperationException("ThenBy() must be called after By().");
 39        }
 40
 2841        ArgumentNullException.ThrowIfNull(predicate);
 2842        _rules.Add(new AssemblyOrderRule(predicate, _currentTier++));
 2843        return this;
 44    }
 45
 46    /// <summary>
 47    /// Gets the ordering rules that have been configured.
 48    /// </summary>
 149    public IReadOnlyList<AssemblyOrderRule> Rules => _rules;
 50
 51    /// <summary>
 52    /// Sorts assemblies according to the configured rules.
 53    /// Each assembly is placed in the first tier it matches.
 54    /// Unmatched assemblies are placed last.
 55    /// </summary>
 56    /// <param name="assemblies">The assemblies to sort.</param>
 57    /// <returns>The sorted assemblies.</returns>
 58    public IReadOnlyList<Assembly> Sort(IEnumerable<Assembly> assemblies)
 59    {
 3060        ArgumentNullException.ThrowIfNull(assemblies);
 61
 3062        var assemblyList = assemblies.ToList();
 3063        if (assemblyList.Count == 0 || _rules.Count == 0)
 64        {
 065            return assemblyList;
 66        }
 67
 3068        var unmatchedTier = _currentTier; // Unmatched assemblies go last
 69
 3070        var tieredAssemblies = assemblyList
 3071            .Select(assembly =>
 3072            {
 15373                var info = AssemblyInfo.FromAssembly(assembly);
 15374                var tier = GetTier(info, unmatchedTier);
 15375                return (Assembly: assembly, Tier: tier, Name: info.Name);
 3076            })
 15377            .OrderBy(x => x.Tier)
 15378            .ThenBy(x => x.Name, StringComparer.OrdinalIgnoreCase) // Alphabetical within tier
 15379            .Select(x => x.Assembly)
 3080            .ToList();
 81
 3082        return tieredAssemblies;
 83    }
 84
 85    /// <summary>
 86    /// Sorts assembly names according to the configured rules.
 87    /// Used for source-gen scenarios where only names are available.
 88    /// </summary>
 89    /// <param name="assemblyNames">The assembly names to sort.</param>
 90    /// <returns>The sorted assembly names.</returns>
 91    public IReadOnlyList<string> SortNames(IEnumerable<string> assemblyNames)
 92    {
 1093        ArgumentNullException.ThrowIfNull(assemblyNames);
 94
 995        var nameList = assemblyNames.ToList();
 996        if (nameList.Count == 0 || _rules.Count == 0)
 97        {
 298            return nameList;
 99        }
 100
 7101        var unmatchedTier = _currentTier;
 102
 7103        var tieredNames = nameList
 7104            .Select(name =>
 7105            {
 27106                var info = AssemblyInfo.FromStrings(name);
 27107                var tier = GetTier(info, unmatchedTier);
 27108                return (Name: name, Tier: tier);
 7109            })
 27110            .OrderBy(x => x.Tier)
 27111            .ThenBy(x => x.Name, StringComparer.OrdinalIgnoreCase)
 27112            .Select(x => x.Name)
 7113            .ToList();
 114
 7115        return tieredNames;
 116    }
 117
 118    private int GetTier(AssemblyInfo info, int unmatchedTier)
 119    {
 680120        foreach (var rule in _rules)
 121        {
 218122            if (rule.CompiledPredicate(info))
 123            {
 116124                return rule.Tier;
 125            }
 126        }
 64127        return unmatchedTier;
 116128    }
 129}