All files / shared parser-patterns.ts

100% Statements 19/19
100% Branches 2/2
100% Functions 5/5
100% Lines 17/17

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                        54x 54x     54x 54x     54x                   54x               54x             54x                             1446x 6x     1446x 1446x                   37x 37x 27x 27x             295x    
/**
 * Parser Patterns and Helper Functions
 * 
 * Centralized patterns and utilities shared across all parser modules.
 * Extracted to avoid duplication and ensure consistency.
 */
 
// ─────────────────────────────────────────────────────────────────────────────
// Common Regex Patterns
// ─────────────────────────────────────────────────────────────────────────────
/** Two separate for-loop regexes to avoid super-linear backtracking (S5843). */
// Groups: 1=type/empty, 2=var, 3=start, 4=op, 5=limit
export const FOR_LOOP_TYPED = /\bfor\s*\(\s*(\w+)\s+(\w+)\s*=\s*(\d+)\s*;\s*\w+\s*([<>]=?)\s*(\w+)\s*;[^)]*\)/g;
export const FOR_LOOP_BARE = /\bfor\s*\(\s*()(\w+)\s*=\s*(\d+)\s*;\s*\w+\s*([<>]=?)\s*(\w+)\s*;[^)]*\)/g;
 
/** Two separate function-def regexes to reduce alternation complexity (S5843). */
export const FUNCTION_DEF_BASIC = /(?:void|int|bool|byte|long|float|double|char|String)\s+(\w+)\s*\([^)]*\)\s*\{/g;
export const FUNCTION_DEF_UNSIGNED = /unsigned\s+(?:int|long)\s+(\w+)\s*\([^)]*\)\s*\{/g;
 
/** Serial configuration patterns. */
export const SERIAL_PATTERNS = {
  USAGE: /Serial\s*\.\s*(print|println|write|read|available|peek|readString|readBytes|parseInt|parseFloat|find|findUntil)/,
  BEGIN: /Serial\s*\.\s*begin\s*\(\s*\d+\s*\)/,
  BEGIN_EXTRACT: /Serial\s*\.\s*begin\s*\(\s*(\d+)\s*\)/,
  WHILE_NOT: /while\s*\(\s*!\s*Serial\s*\)/,
  READ: /Serial\s*\.\s*read\s*\(\s*\)/,
  AVAILABLE: /Serial\s*\.\s*available\s*\(\s*\)/,
} as const;
 
/** Structure patterns. */
export const STRUCTURE_PATTERNS = {
  SETUP_FUNCTION: /void\s+setup\s*\(\s*\)/,
  SETUP_ANY: /void\s+setup\s*\([^)]*\)/,
  LOOP_FUNCTION: /void\s+loop\s*\(\s*\)/,
  LOOP_ANY: /void\s+loop\s*\([^)]*\)/,
} as const;
 
/** Performance patterns. */
export const PERFORMANCE_PATTERNS = {
  WHILE_TRUE: /while\s*\(\s*true\s*\)/,
  FOR_NO_EXIT: /for *\( *[^;\n]+; *; *[^)\n]+\)/, // NOSONAR S5843
  LARGE_ARRAY: /\[\s*(\d{4,})\s*\]/,
} as const;
 
/** Comment patterns. */
export const COMMENT_PATTERNS = {
  SINGLE_LINE: /\/\/[^\n]*$/gm, // NOSONAR S5843
  MULTI_LINE: /\/\*[^*]*(?:\*+[^*/][^*]*)*\*\//g,
} as const;
 
// ─────────────────────────────────────────────────────────────────────────────
// Helper Functions
// ─────────────────────────────────────────────────────────────────────────────
 
/**
 * Strip comments while preserving character positions (newlines kept intact).
 * This allows line numbers in the stripped code to match the original source.
 */
export function stripComments(code: string): string {
  // Multi-line comments → spaces (preserve newlines for correct line counting)
  let result = code.replaceAll(COMMENT_PATTERNS.MULTI_LINE, (m) =>
    m.replaceAll(/[^\n]/g, " "),
  );
  // Single-line comments → spaces (preserve line length)
  result = result.replaceAll(COMMENT_PATTERNS.SINGLE_LINE, (m) => " ".repeat(m.length));
  return result;
}
 
/**
 * Find line number for a pattern in code.
 * @param code - Full source code
 * @param pattern - Regex pattern to find
 * @returns 1-based line number, or undefined if not found
 */
export function findLineNumber(code: string, pattern: RegExp): number | undefined {
  const match = pattern.exec(code);
  if (!match) return undefined;
  const upToMatch = code.slice(0, Math.max(0, match.index));
  return upToMatch.split("\n").length;
}
 
/**
 * 1-based line number for a character position in a string.
 */
export function lineAt(code: string, pos: number): number {
  return code.slice(0, pos).split("\n").length;
}