All files / packages/sds-customization-compliance/src/checks/helpers relayRouting.ts

100% Statements 15/15
100% Branches 8/8
100% Functions 3/3
100% Lines 11/11

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                                                                                                  10x                       46x 46x                                             47x 46x 46x 46x       55x       23x 17x 15x      
/**
 * Embedding-component relay routing predicate (RFC 1157, Path 4).
 *
 * A relay-routing rule writes onto an embedded component's hook from
 * the embedding component's own hook namespace, with a `revert-layer`
 * fallback. Pattern:
 *
 *     .slds-dropdown-trigger .slds-button {
 *       --slds-c-button-color-background: var(--slds-c-dropdown-trigger-color-background, revert-layer);
 *     }
 *
 * The `revert-layer` fallback is what makes the rule safe across every
 * structural rule that would otherwise flag a hook write reaching into
 * another component's scope:
 *
 *   - Layer placement: the rule sits unlayered above `@layer theme`, so
 *     `revert-layer` rolls back into the embedded component's own
 *     layered paint when the relay isn't set.
 *   - Cross-component reach (Rules 4a / 4b / 4c, Rule 5): the relay
 *     opens a channel without overriding the embedded component's
 *     defaults. When the customer hasn't supplied the relay value,
 *     the embedded component's existing customer surface stays
 *     reachable through `revert-layer`.
 *
 * The carveout is recognized only when **every** hook write in the
 * rule satisfies all three of:
 *
 *   1. **LHS is foreign to the embedding component.** The hook being
 *      written (`decl.prop`) is NOT in the embedding component's own
 *      `--slds-c-{prefix}-*` namespace — it's the embedded component's
 *      hook. A write to your own namespace isn't a relay; it's just
 *      a self-write with a relay-shaped fallback.
 *   2. **RHS sources the embedding component's own hook.** The `var()`
 *      argument inside the fallback chain is `--slds-c-{prefix}-*`
 *      where `{prefix}` is the embedding component's resolved hook
 *      prefix. This is the customer-facing surface that the relay
 *      forwards into the embedded component.
 *   3. **Fallback is `revert-layer`.** When the relay source isn't
 *      set, the value reverts to the embedded component's own theme
 *      paint, leaving its defaults intact.
 *
 * Without the prefix tightening (1) and (2), a rule with a stray
 * `var(--something-else, revert-layer)` would be accepted as a relay
 * even though it doesn't route through the component's customer
 * surface. Tightening to the component's own prefix means the
 * carveout fires only when the rule is doing what the architecture
 * documents.
 */
 
export const REVERT_LAYER_FALLBACK_RE = /\bvar\([^)]*\brevert-layer\b\s*\)/;
 
/**
 * Match a `var(--slds-c-{prefix}-...)` token where `{prefix}` is the
 * embedding component's hook prefix. The token may include further
 * segments after the prefix, since relay sources are full hook names
 * like `--slds-c-dropdown-trigger-color-background`.
 */
function buildOwnPrefixVarRe(ownHookPrefix: string): RegExp {
  // Escape regex metacharacters in the prefix. Hook prefixes are
  // lowercased and stripped of `-`/`_` by `resolveHookPrefix`, so in
  // practice they're alphanumeric, but escape defensively.
  const escaped = ownHookPrefix.replaceAll(/[\\^$.*+?()[\]{}|]/g, '\\$&');
  return new RegExp(`\\bvar\\(\\s*--slds-c-${escaped}-[a-z0-9-]+`, 'i');
}
 
/**
 * True when every hook write in this rule is a relay routing **for
 * this component**:
 *
 *   - the hook being written (LHS, `decl.prop`) is foreign to the
 *     embedding component's own `--slds-c-{prefix}-*` namespace;
 *   - the value (RHS) references the embedding component's own
 *     `--slds-c-{prefix}-*` hook as the relay source;
 *   - the fallback chain ends in `revert-layer`.
 *
 * Empty input returns false — there's no relay to recognize.
 *
 * `ownHookPrefix` is `resolveHookPrefix(componentName)`. Callers
 * compute it once per check and pass it through; the helper avoids
 * importing the resolver to keep the predicate cheap.
 */
export function isRelayRoutingRule(
  hookDecls: ReadonlyArray<{ prop: string; value: string }>,
  ownHookPrefix: string,
): boolean {
  if (hookDecls.length === 0) return false;
  const ownPrefix = `--slds-c-${ownHookPrefix}-`;
  const ownVarRe = buildOwnPrefixVarRe(ownHookPrefix);
  return hookDecls.every((decl) => {
    // LHS must be foreign to the embedding component. A write to the
    // component's own hook isn't a relay — relays forward INTO an
    // embedded component's namespace.
    if (decl.prop.startsWith(ownPrefix)) return false;
    // RHS must include `var(--slds-c-{ownPrefix}-...)` as the relay
    // source AND end in `revert-layer`. Both pieces together describe
    // a complete relay shape; either alone is incomplete.
    if (!ownVarRe.test(decl.value)) return false;
    if (!REVERT_LAYER_FALLBACK_RE.test(decl.value)) return false;
    return true;
  });
}