All files / packages/fds-uif/generator-lwc/src/utils classGetterPlan.ts

100% Statements 34/34
95.45% Branches 21/22
100% Functions 9/9
100% Lines 29/29

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                                                                                        36x 36x 40x 40x 40x 40x 40x     36x 36x   36x   36x 319x 317x 311x 303x 303x 311x 29x 29x   303x 283x         36x   36x       339x       614x       303x               303x 303x 1x    
import type {
  ClassNameRule,
  ComponentMetadata,
  ComposedComponentMetadata,
  SlotMetadata,
  StructureMetadata,
} from '@fds-uif/generator-base';
 
/**
 * Plan of class getters that need to exist on the generated class for the
 * template to bind to. Keyed by structure node identity (the node object
 * reference) so that the template walker can look up the same getter by
 * walking the same tree.
 *
 * Names are derived deterministically from a node's `name` field (or
 * synthesized from depth/index for unnamed nodes), with disambiguation if
 * collisions occur within a single component.
 */
export interface ClassGetterPlan {
  /** Component-level rules that apply to the root structure node. */
  rootRules: ClassNameRule[];
  /** Stable getter name for the root, or `null` when the root has no
   * conditional rules (template should render a static class instead). */
  rootGetterName: string | null;
 
  /** Per-node rules for non-root nodes that need a dynamic class binding. */
  nodes: Map<StructureMetadata | ComposedComponentMetadata, NodeGetter>;
}
 
export interface NodeGetter {
  name: string;
  rules: ClassNameRule[];
}
 
/**
 * Build a class-getter plan by walking the metadata structure tree.
 *
 * - The root node's effective rules come from `metadata.classNames`
 *   (component-level), which already carries state/variant/modifier
 *   conditionals.
 * - Non-root nodes use their own `node.classNames` array.
 * - A node only gets a getter when at least one rule has conditional entries.
 */
export function buildClassGetterPlan(metadata: ComponentMetadata): ClassGetterPlan {
  const used = new Set<string>();
  const reserve = (preferred: string): string => {
    let name = preferred;
    let i = 2;
    while (used.has(name)) name = `${preferred}${i++}`;
    used.add(name);
    return name;
  };
 
  const rootRules = metadata.classNames ?? [];
  const rootGetterName = hasConditional(rootRules) ? reserve('computedClass') : null;
 
  const nodes = new Map<StructureMetadata | ComposedComponentMetadata, NodeGetter>();
 
  const walk = (children: StructureMetadata['children'] | undefined, parentName: string): void => {
    if (!children) return;
    children.forEach((child, index) => {
      if (isSlot(child)) return;
      const baseName = nodeBaseName(child, parentName, index);
      const rules = child.classNames ?? [];
      if (hasConditional(rules)) {
        const name = reserve(`${baseName}Class`);
        nodes.set(child, { name, rules });
      }
      if (isStructure(child)) {
        walk(child.children, baseName);
      }
    });
  };
 
  walk(metadata.structure.children, 'root');
 
  return { rootRules, rootGetterName, nodes };
}
 
function hasConditional(rules: ClassNameRule[]): boolean {
  return rules.some((r) => r.conditional && r.conditional.length > 0);
}
 
function isSlot(c: StructureMetadata['children'][number]): c is SlotMetadata {
  return (c as SlotMetadata).type === 'slot';
}
 
function isStructure(c: StructureMetadata['children'][number]): c is StructureMetadata {
  return !isSlot(c) && (c as ComposedComponentMetadata).type !== 'component';
}
 
function nodeBaseName(
  node: StructureMetadata | ComposedComponentMetadata,
  parentName: string,
  index: number,
): string {
  const explicit = (node as StructureMetadata).name;
  if (explicit && explicit !== 'root') return explicit;
  return `${parentName}Child${index}`;
}