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

91.3% Statements 42/46
89.47% Branches 34/38
100% Functions 3/3
97.5% Lines 39/40

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 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204                                14x                                                                                                     47x 31x       16x 11x 11x 9x       7x                                 36x 36x 36x     36x         9x     8x 8x     8x   8x   8x 1x       7x 1x     6x                                                         32x 32x   32x 32x     32x   7x 7x     7x 7x   7x     7x 7x 2x         5x 3x           2x   2x                          
/**
 * StringOperationsHelper - String operation detection and extraction
 *
 * Extracted from CodeGenerator to reduce file size.
 * Handles detection of string concatenation and substring patterns.
 *
 * ADR-045: String type support
 * Issue #707: Uses ExpressionUnwrapper for tree navigation
 */
 
import * as Parser from "../../../logic/parser/grammar/CNextParser.js";
import CodeGenState from "../../../state/CodeGenState.js";
import StringUtils from "../../../../utils/StringUtils.js";
import ExpressionUnwrapper from "../../../../utils/ExpressionUnwrapper";
 
/** Regex for identifying valid C/C++ identifiers */
const IDENTIFIER_REGEX = /^[a-zA-Z_]\w*$/;
 
/**
 * String concatenation operands extracted from expression.
 */
interface IStringConcatOps {
  left: string;
  right: string;
  leftCapacity: number;
  rightCapacity: number;
}
 
/**
 * Substring extraction operands extracted from expression.
 */
interface ISubstringOps {
  source: string;
  start: string;
  length: string;
  sourceCapacity: number;
}
 
/**
 * Callbacks for substring operand extraction.
 */
interface ISubstringCallbacks {
  /** Generate expression code */
  generateExpression: (ctx: Parser.ExpressionContext) => string;
}
 
/**
 * Helper for string operation detection and extraction.
 * All methods are static - uses CodeGenState for shared state.
 */
class StringOperationsHelper {
  // ========================================================================
  // Tier 1: Pure Utilities (no callbacks needed)
  // ========================================================================
 
  /**
   * Get the capacity of a string expression.
   * For string literals, capacity equals content length.
   * For string variables, capacity is from the type registry.
   *
   * ADR-045: String capacity resolution for concatenation and bounds checking.
   *
   * @param exprCode - Expression code text (e.g., "hello" or varName)
   * @returns Capacity in characters, or null if not a string
   */
  static getStringExprCapacity(exprCode: string): number | null {
    // String literal - capacity equals content length
    if (exprCode.startsWith('"') && exprCode.endsWith('"')) {
      return StringUtils.literalLength(exprCode);
    }
 
    // Variable - check type registry
    if (IDENTIFIER_REGEX.test(exprCode)) {
      const typeInfo = CodeGenState.getVariableTypeInfo(exprCode);
      if (typeInfo?.isString && typeInfo.stringCapacity !== undefined) {
        return typeInfo.stringCapacity;
      }
    }
 
    return null;
  }
 
  /**
   * Check if an expression is a string concatenation (contains + with string operands).
   * Returns the operand expressions and capacities if it is, null otherwise.
   *
   * ADR-045: String concatenation detection for strncpy/strncat generation.
   * Issue #707: Uses ExpressionUnwrapper for tree navigation.
   *
   * @param ctx - Expression context to check
   * @returns Concatenation operands or null if not a string concat
   */
  static getStringConcatOperands(
    ctx: Parser.ExpressionContext,
  ): IStringConcatOps | null {
    // Navigate to the additive expression level using ExpressionUnwrapper
    const add = ExpressionUnwrapper.getAdditiveExpression(ctx);
    Iif (!add) return null;
    const multExprs = add.multiplicativeExpression();
 
    // Need exactly 2 operands for simple concatenation
    if (multExprs.length !== 2) return null;
 
    // Check if this is addition (not subtraction)
    // Use MINUS() token check instead of text.includes("-") to avoid
    // false positives from identifiers/literals containing hyphens
    if (add.MINUS().length > 0) return null;
 
    // Get the operand texts
    const leftText = multExprs[0].getText();
    const rightText = multExprs[1].getText();
 
    // Check if at least one operand is a string
    const leftCapacity = StringOperationsHelper.getStringExprCapacity(leftText);
    const rightCapacity =
      StringOperationsHelper.getStringExprCapacity(rightText);
 
    if (leftCapacity === null && rightCapacity === null) {
      return null; // Neither is a string
    }
 
    // If one is null, it's not a valid string concatenation
    if (leftCapacity === null || rightCapacity === null) {
      return null;
    }
 
    return {
      left: leftText,
      right: rightText,
      leftCapacity,
      rightCapacity,
    };
  }
 
  // ========================================================================
  // Tier 2: Operations with Callbacks
  // ========================================================================
 
  /**
   * Check if an expression is a substring extraction (string[start, length]).
   * Returns the source string, start, length, and source capacity if it is.
   *
   * ADR-045: Substring extraction detection for safe string slicing.
   * Issue #707: Uses ExpressionUnwrapper for tree navigation.
   * Issue #140: Handles both [start, length] and single-char [index] patterns.
   *
   * @param ctx - Expression context to check
   * @param callbacks - Callbacks for expression generation
   * @returns Substring operands or null if not a substring extraction
   */
  static getSubstringOperands(
    ctx: Parser.ExpressionContext,
    callbacks: ISubstringCallbacks,
  ): ISubstringOps | null {
    // Navigate to the postfix expression level using shared utility
    const postfix = ExpressionUnwrapper.getPostfixExpression(ctx);
    Iif (!postfix) return null;
 
    const primary = postfix.primaryExpression();
    const ops = postfix.postfixOp();
 
    // Need exactly one postfix operation (the [start, length])
    if (ops.length !== 1) return null;
 
    const op = ops[0];
    const exprs = op.expression();
 
    // Get the source variable name first
    const sourceId = primary.IDENTIFIER();
    Iif (!sourceId) return null;
 
    const sourceName = sourceId.getText();
 
    // Check if source is a string type
    const typeInfo = CodeGenState.getVariableTypeInfo(sourceName);
    if (!typeInfo?.isString || typeInfo.stringCapacity === undefined) {
      return null;
    }
 
    // Issue #140: Handle both [start, length] pattern (2 expressions)
    // and single-character access [index] pattern (1 expression, treated as [index, 1])
    if (exprs.length === 2) {
      return {
        source: sourceName,
        start: callbacks.generateExpression(exprs[0]),
        length: callbacks.generateExpression(exprs[1]),
        sourceCapacity: typeInfo.stringCapacity,
      };
    E} else if (exprs.length === 1) {
      // Single-character access: source[i] is sugar for source[i, 1]
      return {
        source: sourceName,
        start: callbacks.generateExpression(exprs[0]),
        length: "1",
        sourceCapacity: typeInfo.stringCapacity,
      };
    }
 
    return null;
  }
}
 
export default StringOperationsHelper;