All files / server/services/compiler compiler-output-parser.ts

100% Statements 21/21
100% Branches 11/11
100% Functions 2/2
100% Lines 20/20

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                  14x                                                   14x 14x 14x     14x 14x   14x 14x 14x 2x   14x 14x             14x 14x 13x 13x         14x 6x 4x                   14x      
/**
 * Compiler Output Parser
 * 
 * Centralizes Arduino CLI output parsing logic including:
 * - Error/warning extraction from gcc-style error messages
 * - Deduplication of error entries
 * - Fallback generic error parsing when regex doesn't match
 */
 
import { basename } from "node:path";
 
export interface CompilationError {
  file: string;
  line: number;
  column: number;
  type: 'error' | 'warning';
  message: string;
}
 
export class CompilerOutputParser {
  /**
   * Parse compiler stderr output into structured error list.
   * 
   * Handles patterns like:
   * - 'file:line:column: error: message'
   * - 'file:line: error: message' (column optional)
   * - Falls back to per-line generic errors if regex doesn't match
   * 
   * @param stderr Raw stderr output from arduino-cli
   * @param lineOffset Optional offset to adjust line numbers (e.g., header injection)
   * @returns Array of structured compilation errors/warnings
   */
  static parseErrors(stderr: string, lineOffset: number = 0): CompilationError[] {
    // match patterns like 'file:line:column: error: message' or
    // 'file:line: error: message' (column optional)
    const regex = /^([^:\n]+):(\d+)(?::(\d+))?: +(warning|error): +([^\n]*)$/gm; // NOSONAR S5843
    const results: CompilationError[] = [];
    const seen = new Set<string>();
 
    let match: RegExpExecArray | null;
    while ((match = regex.exec(stderr))) {
      let [_, file, lineStr, colStr, type, message] = match;
      // shorten to basename so frontend sees just the filename
      file = basename(file);
      let lineNum = Number.parseInt(lineStr, 10);
      if (lineOffset > 0) {
        lineNum = Math.max(1, lineNum - lineOffset);
      }
      const colNum = colStr ? Number.parseInt(colStr, 10) : 0;
      const item: CompilationError = {
        file,
        line: lineNum,
        column: colNum,
        type: type as 'error' | 'warning',
        message,
      };
      const key = `${file}:${lineNum}:${colNum}:${type}:${message}`;
      if (!seen.has(key)) {
        seen.add(key);
        results.push(item);
      }
    }
 
    // if nothing parsed but stderr is present, create generic entries per line
    if (results.length === 0 && stderr.trim()) {
      for (const line of stderr.split(/\r?\n/).filter((l) => l.trim())) {
        results.push({
          file: "",
          line: 0,
          column: 0,
          type: "error",
          message: line.trim(),
        });
      }
    }
 
    return results;
  }
}