All files / src/builder applyContextToSegments.ts

100% Statements 66/66
95% Branches 38/40
100% Functions 16/16
100% Lines 61/61

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166              4x     121x           120x           120x   120x     28x     396x 219x     177x   39x     39x     18x 9x     9x     159x 88x 39x 99x   99x     39x 21x     18x         49x 28x 69x   69x     28x 6x     22x         21x 21x 42x   42x         21x 11x         10x           71x     44x           374x 22x     352x 23x 45x       329x 240x 95x     145x 145x     145x 67x       167x   89x 37x   75x         52x 31x   61x         21x 21x   30x           78x    
import type {
  GroupSegment,
  AndGroupSegment,
  OrGroupSegment,
  NotGroupSegment,
  Context,
} from "@featurevisor/types";
import { allSegmentsAreMatched } from "@featurevisor/sdk";
import type { Featurevisor } from "@featurevisor/sdk";
 
export function applyContextToSegments(
  featurevisor: Featurevisor,
  segments: GroupSegment | GroupSegment[],
  context: Context,
  removeSegments: string[] = [],
): GroupSegment | GroupSegment[] {
  const withContextApplied = applyContextToGroupSegments(
    featurevisor,
    segments,
    context,
    removeSegments,
  );
  const removed = removeRedundantGroupSegments(withContextApplied);
 
  return removed;
}
 
export function removeRedundantGroupSegments(
  groupSegments: GroupSegment | GroupSegment[],
): GroupSegment | GroupSegment[] {
  if (groupSegments === "*") {
    return groupSegments;
  }
 
  if (Array.isArray(groupSegments)) {
    // Recursively process each group segment
    const processed = groupSegments.map((gs) => removeRedundantGroupSegments(gs)) as GroupSegment[];
 
    // Filter out "*" values
    const filtered = processed.filter((gs) => gs !== "*");
 
    // If all were "*", return "*"
    if (filtered.length === 0) {
      return "*";
    }
 
    return filtered;
  }
 
  if (typeof groupSegments === "object") {
    if ("and" in groupSegments) {
      const processed = groupSegments.and.map((gs) =>
        removeRedundantGroupSegments(gs),
      ) as GroupSegment[];
      const filtered = processed.filter((gs) => gs !== "*");
 
      // If all were "*", return "*"
      if (filtered.length === 0) {
        return "*";
      }
 
      return {
        and: filtered,
      } as AndGroupSegment;
    }
 
    if ("or" in groupSegments) {
      const processed = groupSegments.or.map((gs) =>
        removeRedundantGroupSegments(gs),
      ) as GroupSegment[];
      const filtered = processed.filter((gs) => gs !== "*");
 
      // If all were "*", return "*"
      if (filtered.length === 0) {
        return "*";
      }
 
      return {
        or: filtered,
      } as OrGroupSegment;
    }
 
    Eif ("not" in groupSegments) {
      const processed = groupSegments.not.map((gs) =>
        removeRedundantGroupSegments(gs),
      ) as GroupSegment[];
      const filtered = processed.filter((gs) => gs !== "*");
 
      // `not` negates the implicit AND of its children. Matched children are true
      // and can be removed: not(true && X) becomes not(X). If every child was
      // true, keep not("*") so it remains an always-false expression.
      if (filtered.length === 0) {
        return {
          not: ["*"],
        } as NotGroupSegment;
      }
 
      return {
        not: filtered,
      } as NotGroupSegment;
    }
  }
 
  return groupSegments;
}
 
export function applyContextToGroupSegments(
  featurevisor: Featurevisor,
  groupSegments: GroupSegment | GroupSegment[],
  context: Context,
  removeSegments: string[] = [],
): GroupSegment | GroupSegment[] {
  if (groupSegments === "*") {
    return groupSegments;
  }
 
  if (Array.isArray(groupSegments)) {
    return groupSegments.map((gs) =>
      applyContextToGroupSegments(featurevisor, gs, context, removeSegments),
    ) as GroupSegment[];
  }
 
  if (typeof groupSegments === "string") {
    if (removeSegments.includes(groupSegments)) {
      return "*";
    }
 
    const matched = allSegmentsAreMatched(groupSegments, context, (segmentKey) =>
      featurevisor.getSegment(segmentKey),
    );
 
    if (matched) {
      return "*";
    }
  }
 
  if (typeof groupSegments === "object") {
    // AND, OR, NOT group segments
    if ("and" in groupSegments) {
      return {
        and: groupSegments.and.map((gs) =>
          applyContextToGroupSegments(featurevisor, gs, context, removeSegments),
        ) as GroupSegment[],
      } as AndGroupSegment;
    }
 
    if ("or" in groupSegments) {
      return {
        or: groupSegments.or.map((gs) =>
          applyContextToGroupSegments(featurevisor, gs, context, removeSegments),
        ) as GroupSegment[],
      } as OrGroupSegment;
    }
 
    Eif ("not" in groupSegments) {
      return {
        not: groupSegments.not.map((gs) =>
          applyContextToGroupSegments(featurevisor, gs, context, removeSegments),
        ) as GroupSegment[],
      } as NotGroupSegment;
    }
  }
 
  return groupSegments;
}