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

100% Statements 35/35
100% Branches 40/40
100% Functions 8/8
100% Lines 34/34

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                                                    562x 6x       556x         61x       495x 243x       252x 13x       239x 5x       234x 230x       4x                       61x 78x     61x 3x       58x 10x           48x     61x                 243x 243x               13x 13x                                 235x     235x 235x             4x 4x                         309x   309x 147x   162x          
/**
 * ParameterSignatureBuilder - Stateless builder for C/C++ parameter signatures
 *
 * Takes a normalized IParameterInput and produces the final parameter string.
 * All decisions (const, pass-by-value, etc.) are pre-computed in the input.
 *
 * This is the single source of truth for parameter formatting, used by both:
 * - CodeGenerator (via ParameterInputAdapter.fromAST)
 * - BaseHeaderGenerator (via ParameterInputAdapter.fromSymbol)
 */
 
import IParameterInput from "../types/IParameterInput";
 
/**
 * Static helper class for building C/C++ parameter signature strings.
 */
class ParameterSignatureBuilder {
  /**
   * Build a parameter signature string from normalized input.
   *
   * @param param - Normalized parameter input (all decisions pre-computed)
   * @param refSuffix - '*' for C mode (pointer), '&' for C++ mode (reference)
   * @returns The formatted parameter string (e.g., "const uint32_t* value")
   */
  static build(param: IParameterInput, refSuffix: string): string {
    // Callback parameters: just typedef name and param name
    if (param.isCallback && param.callbackTypedefName) {
      return `${param.callbackTypedefName} ${param.name}`;
    }
 
    // Array parameters with dimensions
    if (
      param.isArray &&
      param.arrayDimensions &&
      param.arrayDimensions.length > 0
    ) {
      return this._buildArrayParam(param);
    }
 
    // Pass-by-value parameters (ISR, float, enum, small primitives)
    if (param.isPassByValue) {
      return this._buildPassByValueParam(param);
    }
 
    // Non-array string: string<N> -> const char* name
    if (param.isString && !param.isArray) {
      return this._buildStringParam(param);
    }
 
    // Issue #995: Opaque handles are always pass-by-reference with pointer syntax
    if (param.isOpaqueHandle) {
      return this._buildRefParam(param, refSuffix);
    }
 
    // Known struct or known primitive: pass by reference
    if (param.isPassByReference) {
      return this._buildRefParam(param, refSuffix);
    }
 
    // Unknown types: pass by value (standard C semantics)
    return this._buildUnknownParam(param);
  }
 
  /**
   * Build array parameter signature.
   * Examples:
   * - u32[10] arr -> const uint32_t arr[10]
   * - u8[4][4] matrix -> const uint8_t matrix[4][4]
   * - string<32>[5] names -> const char names[5][33]
   * - string[5] names -> char* names[5] (unbounded string array)
   */
  private static _buildArrayParam(param: IParameterInput): string {
    const constPrefix = this._getConstPrefix(param);
    const dims = param.arrayDimensions!.map((d) => `[${d}]`).join("");
 
    // Unbounded string arrays use char* (array of char pointers)
    if (param.isUnboundedString) {
      return `${constPrefix}char* ${param.name}${dims}`;
    }
 
    // Bounded string arrays use char (dimensions include capacity)
    if (param.isString) {
      return `${constPrefix}char ${param.name}${dims}`;
    }
 
    // ADR-030 / #996: an array of opaque handles is an array of pointers, the
    // same shape a scope member of that type takes -- `Dev arr[2]` is an array
    // of an incomplete type, which C rejects.
    const elementType = param.isOpaqueHandle
      ? `${param.mappedType}*`
      : param.mappedType;
    return `${constPrefix}${elementType} ${param.name}${dims}`;
  }
 
  /**
   * Build pass-by-value parameter signature.
   * Used for: ISR, f32, f64, enums, small unmodified primitives.
   * Example: float value, ISR handler, Status s
   */
  private static _buildPassByValueParam(param: IParameterInput): string {
    const constMod = param.isConst ? "const " : "";
    return `${constMod}${param.mappedType} ${param.name}`;
  }
 
  /**
   * Build non-array string parameter signature.
   * string<N> -> const char* name (with auto-const if unmodified)
   */
  private static _buildStringParam(param: IParameterInput): string {
    const constPrefix = this._getConstPrefix(param);
    return `${constPrefix}char* ${param.name}`;
  }
 
  /**
   * Build pass-by-reference parameter signature.
   * C mode: const Point* p
   * C++ mode: const Point& p (unless forcePointerSyntax or isOpaqueHandle)
   *
   * Issue #895: When forcePointerSyntax is set, always use pointer syntax
   * because C callback typedefs expect pointers, not C++ references.
   * Issue #995: Opaque handles must use pointer syntax because C APIs
   * expect pointers to incomplete struct types (e.g., widget_t*).
   */
  private static _buildRefParam(
    param: IParameterInput,
    refSuffix: string,
  ): string {
    const constPrefix = this._getConstPrefix(param);
    // Issue #895/#995: Override refSuffix for callback-compatible or opaque handle params
    const actualSuffix =
      param.forcePointerSyntax || param.isOpaqueHandle ? "*" : refSuffix;
    return `${constPrefix}${param.mappedType}${actualSuffix} ${param.name}`;
  }
 
  /**
   * Build unknown type parameter (pass by value, standard C semantics).
   */
  private static _buildUnknownParam(param: IParameterInput): string {
    const constMod = param.isConst ? "const " : "";
    return `${constMod}${param.mappedType} ${param.name}`;
  }
 
  /**
   * Get const prefix combining explicit const, auto-const, and forced const.
   * Priority: forceConst > isConst > isAutoConst
   * Issue #895: forceConst preserves const from callback typedef signature.
   * Issue #995: Opaque handles suppress auto-const (they must be passed to
   * C APIs that expect non-const pointers).
   */
  private static _getConstPrefix(param: IParameterInput): string {
    // Issue #995: Opaque handles never get auto-const — they're passed to C APIs
    // expecting mutable pointers (e.g., LVGL's lv_obj_t)
    const effectiveAutoConst = param.isOpaqueHandle ? false : param.isAutoConst;
    // Any source of const results in "const " prefix
    if (param.forceConst || param.isConst || effectiveAutoConst) {
      return "const ";
    }
    return "";
  }
}
 
export default ParameterSignatureBuilder;