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;
}
|