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                                                                                        34x 34x 16x 16x 16x 16x 16x     34x 34x   34x   34x 87x 85x 65x 55x 55x 65x 5x 5x   55x 53x         34x   34x       89x       120x       55x               55x 55x 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}`;
}