All files / shared parser-patterns.ts

96% Statements 24/25
83.33% Branches 5/6
100% Functions 6/6
95.65% Lines 22/23

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                        13x 13x     13x 13x     13x                   13x               13x                                 13x             13x                             642x 11x     642x 642x                   45x 45x 35x 35x             33x             166x 132x   34x 34x        
/**
 * 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;
 
/** Pin-related patterns. */
export const PIN_PATTERNS = {
  MODE: /pinMode\s*\(\s*(\d+|A\d+)\s*,/g,
  MODE_WITH_MODE: /pinMode\s*\(\s*(\d+|A\d+)\s*,\s*(INPUT_PULLUP|INPUT|OUTPUT)\s*\)/g,
  MODE_VAR: /pinMode\s*\(\s*([a-zA-Z_]\w*)\s*,/g,
  ANALOG_WRITE: /analogWrite\s*\(\s*(\d+|A\d+)\s*,/g,
  DIGITAL_READ_WRITE: /digital(?:Read|Write)\s*\(\s*(\d+|A\d+|[a-zA-Z_]\w*)/g,
  DIGITAL_READ_LITERAL: /\bdigitalRead\s*\(\s*(\d+|A\d+)\s*\)/g,
  WRITE_READ_PIN: /pinMode\s*\(\s*(\d+|A\d+)/gi,
  WRITE_READ_DIO: /digital(?:Write|Read)\s*\(\s*(\d+|A\d+)/gi,
  ANALOG_READ_WRITE: /analog(?:Read|Write)\s*\(\s*(\d+|A\d+)/gi,
  MODE_ANY: /pinMode *\( *[^,)\n]+,/, // NOSONAR S5843
  DYNAMIC_PIN_READ: /digitalRead\s*\(\s*[^0-9A\s][^,)]*/,
  DYNAMIC_PIN_WRITE: /digitalWrite\s*\(\s*[^0-9A\s][^,)]*/,
  ANALOG_PIN_FORMAT: /^A\d+$/,
} 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;
}
 
/**
 * Parse pin number from string (handles literals and A0-A5).
 */
export function parsePinNumber(pinStr: string): number | undefined {
  if (/^\d+$/.test(pinStr)) {
    return Number.parseInt(pinStr, 10);
  }
  Eif (/^A\d+$/.test(pinStr)) {
    return 14 + Number.parseInt(pinStr.slice(1), 10);
  }
  return undefined;
}