All files / transpiler/logic/analysis ParameterNamingAnalyzer.ts

100% Statements 21/21
100% Branches 4/4
100% Functions 6/6
100% Lines 21/21

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                                                                    121x                           11x                         327x 327x     388x     388x 388x   388x 91x 121x 121x   121x 121x   121x 11x                     327x                 327x   327x 327x   327x                       11x                          
/**
 * Parameter Naming Analyzer
 * Issue #227: Validates that function parameters don't use reserved naming patterns
 *
 * Parameters cannot start with their containing function's name followed by underscore.
 * This naming pattern is reserved for scope-level variables and would bypass the
 * conflict detection heuristic in SymbolTable.detectConflict().
 */
 
import { ParseTreeWalker } from "antlr4ng";
import { CNextListener } from "../parser/grammar/CNextListener";
import * as Parser from "../parser/grammar/CNextParser";
import IParameterNamingError from "./types/IParameterNamingError";
 
/**
 * Pure function to check if a parameter name uses a reserved pattern
 *
 * @param parameterName - The parameter name to check
 * @param functionName - The name of the containing function (without scope prefix)
 * @returns true if the parameter name is problematic
 */
function isReservedParameterName(
  parameterName: string,
  functionName: string,
): boolean {
  // Deliberately a literal single underscore, NOT QualifiedCName.SEPARATOR.
  //
  // E0227 exists because `process_value` could shadow a scope variable that also
  // generated the C name `process_value`. ADR-063 makes that collision
  // unrepresentable — a scope member now generates `process__value`, which E0201
  // already rejects as a parameter name. So this rule is arguably obsolete.
  //
  // Retiring it would change what C-Next accepts, which needs its own ADR, so the
  // existing behavior is preserved verbatim here. Tracked separately.
  return parameterName.startsWith(functionName + "_");
}
 
/**
 * Pure function to create the error message
 *
 * @param parameterName - The parameter name
 * @param functionName - The containing function name
 * @returns The formatted error message
 */
function formatParameterNamingError(
  parameterName: string,
  functionName: string,
): string {
  return (
    `Parameter '${parameterName}' cannot start with function name prefix ` +
    `'${functionName}_'. This naming pattern is reserved for scope-level variables.`
  );
}
 
/**
 * Listener that walks the parse tree to find parameter naming violations
 */
class ParameterNamingListener extends CNextListener {
  private readonly analyzer: ParameterNamingAnalyzer;
 
  constructor(analyzer: ParameterNamingAnalyzer) {
    super();
    this.analyzer = analyzer;
  }
 
  override enterFunctionDeclaration = (
    ctx: Parser.FunctionDeclarationContext,
  ): void => {
    const functionName = ctx.IDENTIFIER().getText();
    const paramList = ctx.parameterList();
 
    if (paramList) {
      for (const param of paramList.parameter()) {
        const paramIdentifier = param.IDENTIFIER();
        const paramName = paramIdentifier.getText();
        // Use the identifier's position for more precise error location
        const line = paramIdentifier.symbol.line;
        const column = paramIdentifier.symbol.column;
 
        if (isReservedParameterName(paramName, functionName)) {
          this.analyzer.addError(paramName, functionName, line, column);
        }
      }
    }
  };
}
 
/**
 * Analyzer for parameter naming violations
 */
class ParameterNamingAnalyzer {
  private errors: IParameterNamingError[] = [];
 
  /**
   * Analyze the parse tree for parameter naming violations
   *
   * @param tree - The parsed program AST
   * @returns Array of errors (empty if all pass)
   */
  public analyze(tree: Parser.ProgramContext): IParameterNamingError[] {
    this.errors = [];
 
    const listener = new ParameterNamingListener(this);
    ParseTreeWalker.DEFAULT.walk(listener, tree);
 
    return this.errors;
  }
 
  /**
   * Add a parameter naming error
   */
  public addError(
    parameterName: string,
    functionName: string,
    line: number,
    column: number,
  ): void {
    this.errors.push({
      code: "E0227",
      parameterName,
      functionName,
      line,
      column,
      message: formatParameterNamingError(parameterName, functionName),
      helpText: `Consider renaming to a name that doesn't start with '${functionName}_'`,
    });
  }
}
 
export default ParameterNamingAnalyzer;