All files / packages/sds-customization-compliance/src/checks hooks-missing-from-all-themes.ts

94.87% Statements 37/39
70.58% Branches 24/34
100% Functions 6/6
100% Lines 34/34

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                          4x     2x 1x 1x     4x 10x 10x 4x                 6x   6x 6x 5x 5x 5x         6x 10x 10x 10x 5x 5x 5x 5x 3x 3x   5x     6x 6x 5x 8x 5x 1x       6x                     1x           1x                
/**
 * Compliance row: component hooks with no theme assignment (informational).
 *
 * Enumerates hooks that the base file reads via `var()` that no tracked
 * theme ever assigns. Informational — the missing hook might be a future-
 * facing customer knob we haven't populated yet — but useful to see at a
 * glance during rollout.
 */
 
import type { ComplianceCheck, ComplianceRow } from '../types.js';
import { resolveHookPrefix } from './helpers/resolveHookPrefix.js';
import { hookDefsFromThemeData, NOT_MIGRATED_DETAIL } from './helpers/themeDataAccess.js';
 
const LABEL = 'Component hooks with no theme assignment (informational)';
 
function fixForUnassignedHook(hook: string, missingFromThemes: readonly string[]): string {
  const themeFiles = missingFromThemes.map((t) => `themes/${t}.css`);
  const target = themeFiles.length === 1 ? themeFiles[0] : `each of: ${themeFiles.join(', ')}`;
  return `Either assign \`${hook}\` a value in ${target}, or remove the public read in base if the hook is not yet a customer-facing API.`;
}
 
export const hooksMissingFromAllThemes: ComplianceCheck = (input): ComplianceRow => {
  const { componentName: name, themeData } = input;
  if (!themeData) {
    return {
      id: 'hooks-missing-from-all-themes',
      label: LABEL,
      status: 'info',
      count: 0,
      detail: NOT_MIGRATED_DETAIL,
    };
  }
 
  const hookPrefix = `--slds-c-${resolveHookPrefix(name)}-`;
 
  const hooksReadInBase = new Set<string>();
  for (const cats of Object.values(themeData.selectors ?? {})) {
    for (const list of Object.values(cats)) {
      for (const h of list ?? []) {
        Eif (h.startsWith(hookPrefix)) hooksReadInBase.add(h);
      }
    }
  }
 
  const trackedThemes = Object.keys(themeData.themes ?? {}).sort();
  const assignedBy = new Map<string, Set<string>>();
  const defs = hookDefsFromThemeData(themeData);
  for (const d of defs) {
    Iif (!d.prop?.startsWith(hookPrefix)) continue;
    Iif (String(d.rawValue ?? '').trim() === 'revert') continue;
    let bucket = assignedBy.get(d.prop);
    if (!bucket) {
      bucket = new Set();
      assignedBy.set(d.prop, bucket);
    }
    bucket.add(d.theme);
  }
 
  const unassigned: Array<{ hook: string; missingFromThemes: string[] }> = [];
  for (const hook of [...hooksReadInBase].sort()) {
    const assigned = assignedBy.get(hook) ?? new Set();
    const missing = trackedThemes.filter((t) => !assigned.has(t));
    if (trackedThemes.length > 0 && missing.length === trackedThemes.length) {
      unassigned.push({ hook, missingFromThemes: missing });
    }
  }
 
  return {
    id: 'hooks-missing-from-all-themes',
    label: LABEL,
    status: 'info',
    count: unassigned.length,
    detail:
      unassigned.length === 0
        ? 'Every hook that the component base reads is assigned by at least one tracked theme — no future-facing hooks are sitting unwired.'
        : `${unassigned.length} component hook${unassigned.length === 1 ? ' is' : 's are'} read by base but not assigned by any tracked theme (likely future-facing customer knobs). Examples: ` +
          unassigned
            .slice(0, 3)
            .map((u) => `\`${u.hook}\` (missing from ${u.missingFromThemes.join(', ')})`)
            .join('; ') +
          (unassigned.length > 3 ? `; +${unassigned.length - 3} more` : '') +
          '.',
    offenders:
      unassigned.length > 0
        ? unassigned.map((u) => ({
            hook: u.hook,
            note: `missing from ${u.missingFromThemes.join(', ')}`,
            fix: fixForUnassignedHook(u.hook, u.missingFromThemes),
          }))
        : undefined,
  };
};