All files / TRANSPILE/3-Render/codegen/helpers VariableModifierBuilder.ts

100% Statements 10/10
100% Branches 22/22
100% Functions 3/3
100% Lines 10/10

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                                                                                                1293x 1293x 1293x 1293x                         1293x 1293x           1293x         1293x                                 15x                         1152x          
/**
 * VariableModifierBuilder - Extracts and validates variable modifiers
 *
 * Issue #696: Extracted from CodeGenerator to reduce cognitive complexity
 * and eliminate duplication across generateVariableDecl, generateParameter,
 * and ControlFlowGenerator.generateForVarDecl.
 *
 * Handles:
 * - const, atomic, volatile, extern modifiers
 * - Validation that atomic and volatile are not both specified
 */
 
import IRenderedModifiers from "../types/IRenderedModifiers";
import invariant from "../../../../utils/invariant";
 
/**
 * Context interface for variable declarations that have modifiers.
 * This allows the builder to work with different parser contexts.
 * Uses unknown since we only check truthiness of modifier methods.
 * constModifier is optional because ForVarDeclContext doesn't have it.
 */
interface IModifierContext {
  constModifier?: () => unknown;
  atomicModifier(): unknown;
  volatileModifier(): unknown;
  start?: { line?: number } | null;
}
 
/**
 * Builds and validates variable modifiers from parser context.
 */
class VariableModifierBuilder {
  /**
   * Build modifiers for a variable declaration.
   *
   * @param ctx - Parser context with modifier methods
   * @param inFunctionBody - Whether we're inside a function body (affects extern)
   * @param hasInitializer - Whether the variable has an initializer (affects extern in C mode)
   * @param cppMode - Whether we're generating C++ code (affects extern behavior)
   * @returns Modifier strings ready for use in generated code
   * @throws Error if both atomic and volatile are specified
   */
  static build(
    ctx: IModifierContext,
    inFunctionBody: boolean,
    hasInitializer: boolean = false,
    cppMode: boolean = false,
  ): IRenderedModifiers {
    const hasConst = ctx.constModifier?.() ?? false;
    const constMod = hasConst ? "const " : "";
    const atomicMod = ctx.atomicModifier() ? "volatile " : "";
    const volatileMod = ctx.volatileModifier() ? "volatile " : "";
 
    // Issue #525: Add extern for top-level const in C++ for external linkage
    // In C++, const at file scope has internal linkage by default, so extern is needed.
    //
    // Issue #852 (MISRA Rule 8.5): In C mode, do NOT add extern to definitions
    // (variables with initializers). The extern declaration comes from the header.
    //
    // Summary:
    // - C mode + no initializer: extern (declaration)
    // - C mode + initializer: NO extern (definition - MISRA 8.5)
    // - C++ mode: ALWAYS extern for external linkage (both declarations and definitions)
    const needsExtern =
      hasConst && !inFunctionBody && (cppMode || !hasInitializer);
    const externMod = needsExtern ? "extern " : "";
 
    // #1322: ADR-049's `atomic` + `volatile` rule is E0889 in pass 2.1. It is
    // purely syntactic -- two modifier tokens on one declaration -- so it did
    // not belong in the builder that also decides linkage, and its position no
    // longer has to be spelled into the message.
    invariant(
      !(ctx.atomicModifier() && ctx.volatileModifier()),
      "a declaration carries `atomic` or `volatile`, not both -- E0889 rejects this in pass 2.1, before this runs",
    );
 
    return {
      const: constMod,
      atomic: atomicMod,
      volatile: volatileMod,
      extern: externMod,
    };
  }
 
  /**
   * Build simple modifiers (atomic and volatile only) for contexts like for-loop vars.
   *
   * @param ctx - Parser context with modifier methods
   * @returns Modifier strings (just atomic and volatile)
   */
  static buildSimple(
    ctx: IModifierContext,
  ): Pick<IRenderedModifiers, "atomic" | "volatile"> {
    return {
      atomic: ctx.atomicModifier() ? "volatile " : "",
      volatile: ctx.volatileModifier() ? "volatile " : "",
    };
  }
 
  /**
   * Build the combined modifier prefix string.
   *
   * @param modifiers - The modifier object
   * @returns Combined string like "extern const volatile "
   */
  static toPrefix(modifiers: IRenderedModifiers): string {
    return `${modifiers.extern}${modifiers.const}${modifiers.atomic}${modifiers.volatile}`;
  }
}
 
export default VariableModifierBuilder;