All files / transpiler/output/codegen/helpers VariableDeclarationFormatter.ts

100% Statements 21/21
100% Branches 16/16
100% Functions 5/5
100% Lines 20/20

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                                                        112x     112x           112x                                 115x   115x 104x       115x 6x   115x 18x     115x                   116x 94x   27x                                             112x 112x 19x 19x   19x       93x          
/**
 * VariableDeclarationFormatter - Unified variable declaration string generation.
 *
 * Phase 2 of unified code generation: Provides a single source of truth for
 * variable declaration formatting, eliminating sync issues between
 * CodeGenerator and HeaderGeneratorUtils.
 *
 * Handles:
 * - Modifier ordering (extern const volatile)
 * - Type formatting with embedded dimensions (string<N> → char[N+1])
 * - Array dimension placement after variable name
 *
 * This class is STATELESS - all decisions are pre-computed in IVariableFormatInput.
 */
 
import type IVariableFormatInput from "../types/IVariableFormatInput";
 
/** Modifier flags for variable declarations */
type IVariableModifiers = IVariableFormatInput["modifiers"];
 
class VariableDeclarationFormatter {
  /**
   * Format a variable declaration string.
   *
   * @param input - Normalized variable declaration input (all decisions pre-computed)
   * @returns Formatted declaration string (e.g., "extern const uint32_t count")
   */
  static format(input: IVariableFormatInput): string {
    const modifierPrefix = VariableDeclarationFormatter.buildModifierPrefix(
      input.modifiers,
    );
    const arrayDimsStr = VariableDeclarationFormatter.buildArrayDimensions(
      input.arrayDimensions,
    );
 
    // Handle types with embedded dimensions (e.g., char[33] from string<32>)
    // In C, array dimensions follow the variable name, not the type
    return VariableDeclarationFormatter.formatWithEmbeddedDimensions(
      modifierPrefix,
      input.mappedType,
      input.name,
      arrayDimsStr,
    );
  }
 
  /**
   * Build the modifier prefix string with consistent ordering.
   *
   * Order: extern volatile const
   * - Matches the established C-Next output format
   * - atomic maps to volatile, so atomic and volatile are mutually exclusive
   * - The caller should validate this before calling
   */
  static buildModifierPrefix(modifiers: IVariableModifiers): string {
    const parts: string[] = [];
 
    if (modifiers.isExtern) {
      parts.push("extern");
    }
    // atomic and volatile both map to volatile in C
    // volatile comes before const to match established format
    if (modifiers.isAtomic || modifiers.isVolatile) {
      parts.push("volatile");
    }
    if (modifiers.isConst) {
      parts.push("const");
    }
 
    return parts.length > 0 ? parts.join(" ") + " " : "";
  }
 
  /**
   * Build array dimension string from dimensions array.
   *
   * @param dimensions - Array of dimension strings (e.g., ['10', '20'])
   * @returns Formatted dimensions (e.g., '[10][20]')
   */
  static buildArrayDimensions(dimensions?: readonly string[]): string {
    if (!dimensions || dimensions.length === 0) {
      return "";
    }
    return dimensions.map((d) => `[${d}]`).join("");
  }
 
  /**
   * Format declaration handling types with embedded array dimensions.
   *
   * Handles types like char[33] from string<32> where the dimension is
   * embedded in the mapped type. In C, array dimensions must follow the
   * variable name:
   *   char greeting[33];      // Correct
   *   char[33] greeting;      // Wrong
   *
   * For string arrays (string<32>[5] names), produces:
   *   char names[5][33];      // Additional dims first, embedded dim last
   */
  private static formatWithEmbeddedDimensions(
    modifierPrefix: string,
    mappedType: string,
    name: string,
    additionalDims: string,
  ): string {
    // Check if the mapped type has embedded array dimensions (e.g., char[33])
    // This happens for string<N> types which map to char[N+1]
    const embeddedMatch = /^(\w+)\[(\d+)\]$/.exec(mappedType);
    if (embeddedMatch) {
      const baseType = embeddedMatch[1];
      const embeddedDim = embeddedMatch[2];
      // Format: modifiers baseType name[additionalDims][embeddedDim]
      return `${modifierPrefix}${baseType} ${name}${additionalDims}[${embeddedDim}]`;
    }
 
    // No embedded dimensions - standard format
    return `${modifierPrefix}${mappedType} ${name}${additionalDims}`;
  }
}
 
export default VariableDeclarationFormatter;