All files / packages/fds-uif/generator-static/src generator.ts

85.29% Statements 29/34
75% Branches 15/20
100% Functions 7/7
84.37% Lines 27/32

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 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164                                                                                          25x 25x   25x       25x   25x                             25x 25x                         2x                               22x       22x 22x 22x               22x 22x   22x       22x                       3x 3x   3x 516x   516x     516x 516x     516x   3x                   25x       25x    
/**
 * Static Generator
 *
 * Main entry point for generating static usage of UIF components. Orchestrates
 * the analyzer, the target serializer, the class resolver, and the variant
 * matrix.
 *
 * Output is shaped by two orthogonal options:
 *   - `target`  — output language. Today only `'html'` is supported.
 *   - `compose` — composition strategy. `'reference'` (default) emits external
 *                 component library tags wherever a child UIF declares a
 *                 mapping (Pure LBC / LBC Recipe for the HTML target);
 *                 `'expand'` recursively inlines child UIFs into a
 *                 self-contained snapshot (Pure Blueprint).
 *
 * Two render modes:
 *   - Default: a single output snapshot for the component's default state.
 *   - Variant matrix (`variants: true`): every permutation of modifiers and
 *     variants, each annotated with its active configuration.
 */
 
import {
  analyzeUif,
  formatGeneratedCode,
  type ResolvedUIF,
  type ComponentMetadata,
} from '@fds-uif/generator-base/browser';
import type {
  StaticGeneratorOptions,
  StaticGeneratedCode,
  StaticGenerationContext,
  VariantPermutation,
  PermutationState,
} from './types.js';
import { DEFAULT_OPTIONS } from './types.js';
import { buildHtmlFragment, buildLbcRootElement, buildLbcSlotChildren } from './html-builder.js';
import { buildDefaultState, generatePermutations, buildPermutationLabel } from './variant-matrix.js';
 
/**
 * Generate static usage for a single UIF component.
 */
export async function generateStaticComponent(
  uif: ResolvedUIF,
  options: StaticGeneratorOptions = {},
): Promise<StaticGeneratedCode> {
  const opts: StaticGeneratorOptions = { ...DEFAULT_OPTIONS, ...options };
  const metadata = analyzeUif(uif);
 
  const { code, permutations } = opts.variants
    ? buildVariantMatrix(metadata, opts, uif)
    : { code: buildSingleState(metadata, opts, uif), permutations: undefined };
 
  const fileContent = assembleFile(metadata.name, metadata.description, code);
 
  const result: StaticGeneratedCode = {
    files: [
      {
        path: `${metadata.name}.html`,
        content: fileContent,
        type: 'html',
      },
    ],
    metadata,
    artifacts: {
      code: fileContent,
      permutations,
    },
  };
 
  Eif (opts.skipFormat) {
    return result;
  }
 
  return (await formatGeneratedCode(result)) as StaticGeneratedCode;
}
 
/**
 * Generate static usage for multiple UIF components.
 */
export async function generateStaticComponents(
  uifs: ResolvedUIF[],
  options: StaticGeneratorOptions = {},
): Promise<StaticGeneratedCode[]> {
  return Promise.all(uifs.map((uif) => generateStaticComponent(uif, options)));
}
 
// ---------------------------------------------------------------------------
// Internal
// ---------------------------------------------------------------------------
 
/**
 * Build output for the component's default state, optionally overriding
 * individual variant selections supplied by the caller.
 */
function buildSingleState(
  metadata: ComponentMetadata,
  options: StaticGeneratorOptions,
  uif: ResolvedUIF,
): string {
  const defaultState = buildDefaultState(metadata);
 
  // Grouped (enum) modifier overrides are string values → go into state.variants.
  // Boolean modifier overrides stay in state.modifiers.
  const variants = { ...defaultState.variants, ...options.variantOverrides };
  const modifiers = { ...defaultState.modifiers };
  for (const [key, val] of Object.entries(options.modifierOverrides ?? {})) {
    if (typeof val === 'string') {
      variants[key] = val;
    } else {
      modifiers[key] = val;
    }
  }
 
  const state: PermutationState = { variants, modifiers };
  const context: StaticGenerationContext = { metadata, options, indent: 0, state, depth: 0 };
 
  Iif (options.compose === 'reference' && uif.structure?.component) {
    return buildLbcRootElement(uif, context, buildHtmlFragment, buildLbcSlotChildren);
  }
 
  return buildHtmlFragment(metadata.structure, context);
}
 
/**
 * Build the full variant matrix: one output snapshot per permutation,
 * each prefixed with a comment describing the active configuration.
 */
function buildVariantMatrix(
  metadata: ComponentMetadata,
  options: StaticGeneratorOptions,
  uif: ResolvedUIF,
): { code: string; permutations: VariantPermutation[] } {
  const states = generatePermutations(metadata);
  const permutations: VariantPermutation[] = [];
 
  for (const state of states) {
    const context: StaticGenerationContext = { metadata, options, indent: 0, state, depth: 0 };
    const code =
      options.compose === 'reference' && uif.structure?.component
        ? buildLbcRootElement(uif, context, buildHtmlFragment, buildLbcSlotChildren)
        : buildHtmlFragment(metadata.structure, context);
    const label = buildPermutationLabel(state);
    permutations.push({ label, state, code });
  }
 
  const sections = permutations.map((p) => `<!-- ${p.label} -->\n${p.code}`);
 
  return {
    code: sections.join('\n\n'),
    permutations,
  };
}
 
/**
 * Assemble the final file content with header comments.
 */
function assembleFile(name: string, description: string, body: string): string {
  const header = description
    ? [`<!-- ${name} -->`, `<!-- ${description} -->`, '<!-- @generated from UIF -->']
    : [`<!-- ${name} -->`, '<!-- @generated from UIF -->'];
 
  return [...header, '', body].join('\n');
}