All files / utils ExpressionUtils.ts

95.2% Statements 119/125
90.54% Branches 67/74
100% Functions 28/28
100% Lines 96/96

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 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318                                                              15x 15x   6x                           488x 488x   467x 467x       465x 465x 86x     379x                             492x 492x     492x 492x     490x 490x     489x 489x     488x 488x     488x 488x     487x 487x     486x 486x     486x 486x     486x 486x     485x 485x     472x 472x   469x                       467x 467x   371x 371x                     6030x 6030x 6043x 6062x 6212x 6391x 6398x 6404x 6412x                             6030x 6475x 6770x                         1339x 1339x   1339x 1341x   1301x                       1345x 1347x   1307x           1347x 1352x   1309x           1352x 1408x   1314x           1408x 1425x   1370x           1425x 1426x   1387x           1426x 1427x   1388x           1427x 1428x   1389x           1428x 1428x   1390x           1428x 1439x   1390x           1439x 1444x   1401x               1450x 1450x 6x       1444x 1444x   1444x 85x 38x     1406x          
/**
 * Utility class for traversing expression trees in the parse tree.
 *
 * C-Next's expression grammar is deeply nested:
 *   expression -> ternaryExpression -> orExpression -> andExpression ->
 *   equalityExpression -> relationalExpression -> bitwiseOrExpression ->
 *   bitwiseXorExpression -> bitwiseAndExpression -> shiftExpression ->
 *   additiveExpression -> multiplicativeExpression -> unaryExpression ->
 *   postfixExpression -> primaryExpression -> literal/identifier
 *
 * This utility extracts common traversal patterns used by multiple analyzers.
 */
 
import * as Parser from "../PARSE/2-Parse/grammar/CNextParser";
 
/**
 * Static utility methods for expression tree traversal
 */
class ExpressionUtils {
  /**
   * Extract the literal from a simple expression (if it's just a literal).
   *
   * Returns null if the expression is complex (has operators, function calls, etc.)
   * Only returns a value if the expression resolves to a single literal.
   *
   * @param ctx - The expression context from the parse tree
   * @returns The literal context, or null if not a simple literal expression
   */
  static extractLiteral(
    ctx: Parser.ExpressionContext,
  ): Parser.LiteralContext | null {
    const primary = ExpressionUtils.extractPrimaryExpression(ctx);
    if (!primary) return null;
 
    return primary.literal() ?? null;
  }
 
  /**
   * Extract the primary expression from an expression (if it's simple).
   *
   * Returns null if the expression is complex (has operators, function calls, etc.)
   *
   * @param ctx - The expression context from the parse tree
   * @returns The primary expression context, or null if complex
   */
  static extractPrimaryExpression(
    ctx: Parser.ExpressionContext,
  ): Parser.PrimaryExpressionContext | null {
    const unary = ExpressionUtils.extractUnaryExpression(ctx);
    if (!unary) return null;
 
    const postfix = unary.postfixExpression();
    if (!postfix) return null;
 
    // If there are postfix operations (array access, member access, function call),
    // this is not a simple primary expression
    const postfixOps = postfix.postfixOp();
    if (postfixOps && postfixOps.length > 0) {
      return null;
    }
 
    return postfix.primaryExpression() ?? null;
  }
 
  /**
   * Extract the unary expression from an expression (if it's simple).
   *
   * Returns null if the expression has binary operators at any level.
   *
   * @param ctx - The expression context from the parse tree
   * @returns The unary expression context, or null if complex
   */
  static extractUnaryExpression(
    ctx: Parser.ExpressionContext,
  ): Parser.UnaryExpressionContext | null {
    // expression -> ternaryExpression
    const ternary = ctx.ternaryExpression();
    Iif (!ternary) return null;
 
    // ternaryExpression has multiple orExpressions if it's a ternary (condition ? a : b)
    const orExprs = ternary.orExpression();
    if (orExprs?.length !== 1) return null;
 
    // orExpression -> andExpression (multiple = has || operator)
    const andExprs = orExprs[0].andExpression();
    if (andExprs?.length !== 1) return null;
 
    // andExpression -> equalityExpression (multiple = has && operator)
    const eqExprs = andExprs[0].equalityExpression();
    if (eqExprs?.length !== 1) return null;
 
    // equalityExpression -> relationalExpression (multiple = has = or != operator)
    const relExprs = eqExprs[0].relationalExpression();
    Iif (relExprs?.length !== 1) return null;
 
    // relationalExpression -> bitwiseOrExpression (multiple = has <, >, <=, >= operator)
    const bitorExprs = relExprs[0].bitwiseOrExpression();
    if (bitorExprs?.length !== 1) return null;
 
    // bitwiseOrExpression -> bitwiseXorExpression (multiple = has | operator)
    const bitxorExprs = bitorExprs[0].bitwiseXorExpression();
    if (bitxorExprs?.length !== 1) return null;
 
    // bitwiseXorExpression -> bitwiseAndExpression (multiple = has ^ operator)
    const bitandExprs = bitxorExprs[0].bitwiseAndExpression();
    Iif (bitandExprs?.length !== 1) return null;
 
    // bitwiseAndExpression -> shiftExpression (multiple = has & operator)
    const shiftExprs = bitandExprs[0].shiftExpression();
    Iif (shiftExprs?.length !== 1) return null;
 
    // shiftExpression -> additiveExpression (multiple = has << or >> operator)
    const addExprs = shiftExprs[0].additiveExpression();
    if (addExprs?.length !== 1) return null;
 
    // additiveExpression -> multiplicativeExpression (multiple = has + or - operator)
    const multExprs = addExprs[0].multiplicativeExpression();
    if (multExprs?.length !== 1) return null;
 
    // multiplicativeExpression -> unaryExpression (multiple = has *, /, % operator)
    const unaryExprs = multExprs[0].unaryExpression();
    if (unaryExprs?.length !== 1) return null;
 
    return unaryExprs[0];
  }
 
  /**
   * Extract the identifier from a simple expression (if it's just an identifier).
   *
   * Returns null if the expression is complex or not a simple identifier.
   *
   * @param ctx - The expression context from the parse tree
   * @returns The identifier text, or null if not a simple identifier expression
   */
  static extractIdentifier(ctx: Parser.ExpressionContext): string | null {
    const primary = ExpressionUtils.extractPrimaryExpression(ctx);
    if (!primary) return null;
 
    const identifier = primary.IDENTIFIER();
    return identifier?.getText() ?? null;
  }
 
  /**
   * Collect additive expressions from an array of orExpression contexts.
   *
   * Internal helper that performs the actual flatMap traversal.
   */
  private static collectAdditiveFromOrExprs(
    orExprs: Parser.OrExpressionContext[],
  ): Parser.AdditiveExpressionContext[] {
    return orExprs
      .flatMap((or) => or.andExpression())
      .flatMap((and) => and.equalityExpression())
      .flatMap((eq) => eq.relationalExpression())
      .flatMap((rel) => rel.bitwiseOrExpression())
      .flatMap((bor) => bor.bitwiseXorExpression())
      .flatMap((bxor) => bxor.bitwiseAndExpression())
      .flatMap((band) => band.shiftExpression())
      .flatMap((shift) => shift.additiveExpression());
  }
 
  /**
   * Collect all unary expressions from an orExpression context.
   *
   * Traverses through the entire expression hierarchy to find all unary expressions.
   * Useful for analyzers that need to examine leaf operands.
   *
   * @param orExpr - The orExpression context
   * @returns Array of all unary expression contexts in the tree
   */
  static collectUnaryFromOrExpr(
    orExpr: Parser.OrExpressionContext,
  ): Parser.UnaryExpressionContext[] {
    return ExpressionUtils.collectAdditiveFromOrExprs([orExpr])
      .flatMap((add) => add.multiplicativeExpression())
      .flatMap((mul) => mul.unaryExpression());
  }
 
  /**
   * ADR-023: Check if an expression contains a function call anywhere in its tree.
   *
   * This traverses the full expression hierarchy to find any postfix expression
   * with an argument list (function call syntax).
   *
   * @param ctx - The expression context to check
   * @returns true if a function call is found anywhere in the expression
   */
  static hasFunctionCall(ctx: Parser.ExpressionContext): boolean {
    const ternary = ctx.ternaryExpression();
    Iif (!ternary) return false;
 
    for (const or of ternary.orExpression()) {
      if (ExpressionUtils.hasFunctionCallInOr(or)) return true;
    }
    return false;
  }
 
  /**
   * Issue #254: Check if an orExpression contains a function call.
   *
   * Used for ternary conditions where the condition is an OrExpressionContext.
   *
   * @param ctx - The orExpression context to check
   * @returns true if a function call is found anywhere in the expression
   */
  static hasFunctionCallInOr(ctx: Parser.OrExpressionContext): boolean {
    for (const and of ctx.andExpression()) {
      if (ExpressionUtils.hasFunctionCallInAnd(and)) return true;
    }
    return false;
  }
 
  private static hasFunctionCallInAnd(
    ctx: Parser.AndExpressionContext,
  ): boolean {
    for (const eq of ctx.equalityExpression()) {
      if (ExpressionUtils.hasFunctionCallInEquality(eq)) return true;
    }
    return false;
  }
 
  private static hasFunctionCallInEquality(
    ctx: Parser.EqualityExpressionContext,
  ): boolean {
    for (const rel of ctx.relationalExpression()) {
      if (ExpressionUtils.hasFunctionCallInRelational(rel)) return true;
    }
    return false;
  }
 
  private static hasFunctionCallInRelational(
    ctx: Parser.RelationalExpressionContext,
  ): boolean {
    for (const bor of ctx.bitwiseOrExpression()) {
      if (ExpressionUtils.hasFunctionCallInBitwiseOr(bor)) return true;
    }
    return false;
  }
 
  private static hasFunctionCallInBitwiseOr(
    ctx: Parser.BitwiseOrExpressionContext,
  ): boolean {
    for (const bxor of ctx.bitwiseXorExpression()) {
      if (ExpressionUtils.hasFunctionCallInBitwiseXor(bxor)) return true;
    }
    return false;
  }
 
  private static hasFunctionCallInBitwiseXor(
    ctx: Parser.BitwiseXorExpressionContext,
  ): boolean {
    for (const band of ctx.bitwiseAndExpression()) {
      if (ExpressionUtils.hasFunctionCallInBitwiseAnd(band)) return true;
    }
    return false;
  }
 
  private static hasFunctionCallInBitwiseAnd(
    ctx: Parser.BitwiseAndExpressionContext,
  ): boolean {
    for (const shift of ctx.shiftExpression()) {
      if (ExpressionUtils.hasFunctionCallInShift(shift)) return true;
    }
    return false;
  }
 
  private static hasFunctionCallInShift(
    ctx: Parser.ShiftExpressionContext,
  ): boolean {
    for (const add of ctx.additiveExpression()) {
      if (ExpressionUtils.hasFunctionCallInAdditive(add)) return true;
    }
    return false;
  }
 
  private static hasFunctionCallInAdditive(
    ctx: Parser.AdditiveExpressionContext,
  ): boolean {
    for (const mult of ctx.multiplicativeExpression()) {
      if (ExpressionUtils.hasFunctionCallInMultiplicative(mult)) return true;
    }
    return false;
  }
 
  private static hasFunctionCallInMultiplicative(
    ctx: Parser.MultiplicativeExpressionContext,
  ): boolean {
    for (const unary of ctx.unaryExpression()) {
      if (ExpressionUtils.hasFunctionCallInUnary(unary)) return true;
    }
    return false;
  }
 
  private static hasFunctionCallInUnary(
    ctx: Parser.UnaryExpressionContext,
  ): boolean {
    // Issue #366: Handle nested unary operators (!, -, ~, &) that wrap function calls
    // e.g., !!isReady() or -getValue()
    const nestedUnary = ctx.unaryExpression();
    if (nestedUnary) {
      return ExpressionUtils.hasFunctionCallInUnary(nestedUnary);
    }
 
    // Base case: check postfixExpression
    const postfix = ctx.postfixExpression();
    Iif (!postfix) return false;
 
    for (const op of postfix.postfixOp()) {
      if (op.argumentList() || op.getText().startsWith("(")) {
        return true;
      }
    }
    return false;
  }
}
 
export default ExpressionUtils;