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

94.11% Statements 32/34
90.9% Branches 20/22
100% Functions 9/9
96.55% Lines 28/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                                                                                        30x 30x 12x 12x 12x 12x 12x     30x 30x   30x   30x 80x 78x 62x 52x 52x 62x 2x 2x   52x 50x         30x   30x       82x       114x       52x               52x 52x      
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;
  Eif (explicit && explicit !== 'root') return explicit;
  return `${parentName}Child${index}`;
}