All files / packages/fds-uif/generator-base/src/analyzer index.ts

95% Statements 38/40
86.95% Branches 20/23
100% Functions 7/7
100% Lines 35/35

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                                                      16x               1x 1x   1x 1x                       96x     96x 95x 95x 1x 1x       95x   95x   95x 95x 95x 95x 95x 95x     95x       95x       95x 96x     96x   96x                 96x 11x   84x         94x 3x     94x   1x 1x 1x                     2x                
/**
 * UIF Analyzer
 *
 * Extracts component metadata from resolved UIFs for code generation.
 * Uses extraction utilities from @fds-uif/core.
 */
 
import type { ResolvedUIF } from '@fds-uif/schema';
 
import {
  validateResolved,
  UifResolvedValidationError,
  getStates,
  getModifiers,
  getVariants,
  getSlots,
  getBoundAttributes,
  getStateClasses,
} from '@fds-uif/core/browser';
 
import type { ComponentMetadata, AnalyzerOptions, StructureMetadata } from '../types/index.js';
 
import { buildProps } from './props.js';
import { analyzeStructure } from './structure.js';
import { buildClassNameRules } from './classNames.js';
import { detectDependencies } from './dependencies.js';
 
const DEFAULT_ELEMENT = 'div';
 
/**
 * Error thrown during UIF analysis
 */
export class UifAnalyzerError extends Error {
  constructor(
    message: string,
    public readonly componentName?: string,
    public readonly details?: Record<string, unknown>,
  ) {
    super(message);
    this.name = 'UifAnalyzerError';
  }
}
 
/**
 * Analyze a resolved UIF and extract component metadata
 *
 * @param uif - Resolved UIF (with $extends already merged)
 * @param options - Analyzer options
 * @returns Component metadata for code generation
 */
export function analyzeUif(uif: unknown, options: AnalyzerOptions = {}): ComponentMetadata {
  const { defaultElement = DEFAULT_ELEMENT, inferDescriptions = false, validate = true } = options;
 
  // Validate before analysis
  if (validate) {
    const validation = validateResolved(uif);
    if (!validation.valid) {
      const messages = validation.errors?.map((e) => `${e.path}: ${e.message}`).join('; ');
      throw new UifResolvedValidationError(`Invalid UIF: ${messages}`, validation.errors);
    }
  }
 
  const resolvedUif = uif as ResolvedUIF;
 
  try {
    // Use schema extraction utilities
    const states = getStates(resolvedUif);
    const modifiers = getModifiers(resolvedUif); // all modifiers (including child nodes) for props
    const variants = getVariants(resolvedUif);
    const slots = getSlots(resolvedUif);
    const boundAttrs = getBoundAttributes(resolvedUif);
    const stateClasses = getStateClasses(resolvedUif);
 
    // Build props from extracted data
    const props = buildProps(states, modifiers, variants, slots, boundAttrs, inferDescriptions);
 
    // Analyze structure for rendering (also builds per-node classNames for child modifiers).
    // Root structure is always a StructureMetadata (never a bare composed component).
    const structure = analyzeStructure(resolvedUif.structure, defaultElement) as StructureMetadata;
 
    // Build ROOT class name rules — only root-level modifiers (not child-node modifiers,
    // which are already handled per-node in analyzeStructure above).
    const rootModifiers = [...(resolvedUif.modifiers ?? []), ...(resolvedUif.structure?.modifiers ?? [])];
    const classNames = buildClassNameRules(resolvedUif, rootModifiers, stateClasses);
 
    // Detect dependencies
    const dependencies = detectDependencies(resolvedUif.structure, defaultElement);
 
    const metadata: ComponentMetadata = {
      name: resolvedUif.name,
      description: resolvedUif.description,
      props,
      structure,
      classNames,
      dependencies,
    };
 
    if (resolvedUif.accessibility) {
      metadata.accessibility = {
        role: resolvedUif.accessibility.role,
        ariaProps: props.filter((p) => p.name.startsWith('aria')).map((p) => p.name),
        landmarks: resolvedUif.accessibility.landmarks,
      };
    }
 
    if (resolvedUif.behavior) {
      metadata.behavior = resolvedUif.behavior;
    }
 
    return metadata;
  } catch (error) {
    Iif (error instanceof UifAnalyzerError) throw error;
    Iif (error instanceof UifResolvedValidationError) throw error;
    throw new UifAnalyzerError(
      `Failed to analyze UIF: ${error instanceof Error ? error.message : String(error)}`,
      resolvedUif.name,
    );
  }
}
 
/**
 * Analyze multiple UIFs
 */
export function analyzeMultipleUifs(uifs: ResolvedUIF[], options: AnalyzerOptions = {}): ComponentMetadata[] {
  return uifs.map((uif) => analyzeUif(uif, options));
}
 
// Re-export sub-modules for advanced usage
export { buildProps } from './props.js';
export { analyzeStructure, extractComponentPropsMetadata } from './structure.js';
export { buildClassNameRules } from './classNames.js';
export { detectDependencies } from './dependencies.js';