< Summary

Information
Class: NGql.Core.Abstractions.DirectiveListOps
Assembly: NGql.Core
File(s): /home/runner/work/NGql/NGql/src/Core/Abstractions/DirectiveListOps.cs
Line coverage
100%
Covered lines: 20
Uncovered lines: 0
Coverable lines: 20
Total lines: 87
Line coverage: 100%
Branch coverage
100%
Covered branches: 18
Total branches: 18
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Add(...)100%88100%
AddOrReplaceIfDirective(...)100%88100%
IsNonRepeatableConditional(...)100%22100%

File(s)

/home/runner/work/NGql/NGql/src/Core/Abstractions/DirectiveListOps.cs

#LineLine coverage
 1using System.Diagnostics.CodeAnalysis;
 2
 3namespace NGql.Core.Abstractions;
 4
 5/// <summary>
 6/// Shared directive-list mutation logic for the two node kinds that carry a <c>List&lt;FieldDirective&gt;</c>
 7/// — <see cref="FieldDefinition"/> and <see cref="InlineFragmentDefinition"/>. Kept as a single
 8/// static implementation (rather than duplicated per type) so the dedup/collapse rules — and any
 9/// future fix to them — apply identically everywhere a directive can be attached.
 10/// </summary>
 11internal static class DirectiveListOps
 12{
 13    /// <summary>
 14    /// Append <paramref name="directive"/> to <paramref name="directives"/>. Order is preserved. A
 15    /// directive that is structurally identical to one already present is skipped. Directives that
 16    /// differ (name or arguments) are all kept, EXCEPT for the two non-repeatable, spec-constrained
 17    /// directive names <c>include</c> and <c>skip</c> (GraphQL spec §5.7.3: a non-repeatable
 18    /// directive may appear at most once per location) — see <see cref="AddOrReplaceIfDirective"/>.
 19    /// </summary>
 20    [SuppressMessage(
 21        "Major Code Smell", "S3267:Loops should be simplified using the \"Where\" LINQ method",
 22        Justification = "The plain loop short-circuits without allocating an enumerator on the builder hot path.")]
 23    public static void Add(ref List<FieldDirective>? directives, FieldDirective directive)
 24    {
 989425        if (IsNonRepeatableConditional(directive.Name))
 26        {
 24027            AddOrReplaceIfDirective(ref directives, directive);
 24028            return;
 29        }
 30
 965431        directives ??= new List<FieldDirective>();
 1931132        foreach (var existing in directives)
 33        {
 334            if (existing.IsStructurallyEqualTo(directive))
 35            {
 336                return;
 37            }
 38        }
 965139        directives.Add(directive);
 965440    }
 41
 42    /// <summary>
 43    /// <c>@include</c> and <c>@skip</c> are non-repeatable per the GraphQL spec (§5.7.3) — a field
 44    /// or inline fragment may carry at most one of each. Rather than appending a second occurrence
 45    /// (which would render spec-invalid GraphQL like <c>@include(if:$a) @include(if:$b)</c>), a
 46    /// later call with the SAME directive name replaces the earlier one IN PLACE (same list
 47    /// position), so ordering relative to any other directives is preserved. This is
 48    /// last-call-wins: calling <c>IncludeIf(a)</c> then <c>IncludeIf(b)</c> keeps only
 49    /// <c>@include(if:$b)</c>. A structurally identical replacement (same name AND same arguments)
 50    /// is a no-op, matching the existing dedup behavior for repeatable directives.
 51    /// </summary>
 52    [SuppressMessage(
 53        "Major Code Smell", "S3267:Loops should be simplified using the \"Where\" LINQ method",
 54        Justification = "The plain loop finds the replacement index without allocating an enumerator.")]
 55    private static void AddOrReplaceIfDirective(ref List<FieldDirective>? directives, FieldDirective directive)
 56    {
 24057        directives ??= new List<FieldDirective>();
 50458        for (var i = 0; i < directives.Count; i++)
 59        {
 4560            var existing = directives[i];
 4561            if (!string.Equals(existing.Name, directive.Name, StringComparison.Ordinal))
 62            {
 63                continue;
 64            }
 65
 3366            if (existing.IsStructurallyEqualTo(directive))
 67            {
 2168                return;
 69            }
 70
 1271            directives[i] = directive;
 1272            return;
 73        }
 74
 20775        directives.Add(directive);
 20776    }
 77
 78    /// <summary>
 79    /// Whether <paramref name="name"/> is one of the two universal client directives constrained by
 80    /// the GraphQL spec to appear at most once per location. Checked by <see cref="Add"/> so
 81    /// <c>Directive("include", …)</c> called directly collapses exactly like
 82    /// <c>IncludeIf(Variable)</c> — there is no entry point that can silently emit two
 83    /// <c>@include</c>s on one field or fragment.
 84    /// </summary>
 85    private static bool IsNonRepeatableConditional(string name)
 989486        => name.Equals("include", StringComparison.Ordinal) || name.Equals("skip", StringComparison.Ordinal);
 87}