All files / client/src/hooks use-sketch-analysis.ts

87.15% Statements 95/109
57.6% Branches 53/92
80% Functions 4/5
92.13% Lines 82/89

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 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158                            11x 7x 7x 7x     7x   7x 1x 1x   1x 1x 1x 1x           1x       7x 7x 2x 2x   2x 2x 1x 1x 1x 1x 1x 1x   2x       7x 7x 7x 7x 7x 7x 7x 7x   7x 7x 2x 2x 2x     5x 2x 2x   2x     3x 3x           7x   7x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 4x 4x             7x 7x 7x 7x 2x 2x   2x 2x 1x 1x 1x 1x 1x   2x 2x   2x         2x         7x 8x     7x     7x 7x   7x                      
import { useMemo } from "react";
 
export type PinMode = "INPUT" | "OUTPUT" | "INPUT_PULLUP";
 
export interface SketchAnalysisResult {
  analogPins: number[]; // concrete Arduino pin numbers (A0 -> 14)
  varMap: Record<string, number>;
  detectedPinModes: Record<number, PinMode>;
  pendingPinConflicts: number[]; // pins that are both used as analogRead and declared via pinMode
  digitalPinsFromPinMode: number[];
}
 
// Hook: pure analysis of sketch source to detect pins, defines and pinMode(...) usage
export function useSketchAnalysis(code: string): SketchAnalysisResult {
  return useMemo(() => {
    const mainCode = code || "";
    const pins = new Set<number>();
    const varMap = new Map<string, number>();
 
    // #define VAR A0  or #define VAR 0
    const defineRe = /#define\s+(\w+)\s+(A\d|\d+)/g;
    let m: RegExpExecArray | null;
    while ((m = defineRe.exec(mainCode))) {
      const name = m[1];
      const token = m[2];
      let p: number | undefined;
      const aMatch = token.match(/^A(\d+)$/i);
      if (aMatch) {
        const idx = Number(aMatch[1]);
        Eif (idx >= 0 && idx <= 5) p = 14 + idx;
      E} else if (/^\d+$/.test(token)) {
        const idx = Number(token);
        if (idx >= 0 && idx <= 5) p = 14 + idx;
        else if (idx >= 14 && idx <= 19) p = idx;
      }
      Eif (p !== undefined) varMap.set(name, p);
    }
 
    // variable assignments like: int sensorPin = A0; or const int s = 0;
    const assignRe = /(?:int|const\s+int|uint8_t|byte)\s+(\w+)\s*=\s*(A\d|\d+)\s*;/g;
    while ((m = assignRe.exec(mainCode))) {
      const name = m[1];
      const token = m[2];
      let p: number | undefined;
      const aMatch = token.match(/^A(\d+)$/i);
      if (aMatch) {
        const idx = Number(aMatch[1]);
        Eif (idx >= 0 && idx <= 5) p = 14 + idx;
      E} else if (/^\d+$/.test(token)) {
        const idx = Number(token);
        Iif (idx >= 0 && idx <= 5) p = 14 + Eidx;
        else if (idx >= 14 && idx <= 19) p = idx;
      }
      Eif (p !== undefined) varMap.set(name, p);
    }
 
    // analogRead(...) occurrences
    const areadRe = /analogRead\s*\(\s*([^\)]+)\s*\)/g;
    let analogReadMatchCount = 0;
    while ((m = areadRe.exec(mainCode))) {
      analogReadMatchCount++;
      const token = m[1].trim();
      const simple = token.match(/^(A\d+|\d+|\w+)$/i);
      Iif (!simple) continue;
      const tok = simple[1];
 
      const aMatch = tok.match(/^A(\d+)$/i);
      if (aMatch) {
        const idx = Number(aMatch[1]);
        Eif (idx >= 0 && idx <= 5) pins.add(14 + idx);
        continue;
      }
 
      if (/^\d+$/.test(tok)) {
        const idx = Number(tok);
        if (idx >= 0 && idx <= 5) pins.add(14 + idxE);
        else if (idx >= 14 && idx <= 19) pins.add(idx);
        continue;
      }
 
      Eif (varMap.has(tok)) {
        pins.add(varMap.get(tok)!);
      }
    }
 
    // for-loops that iterate a variable used in analogRead(...)
    const forLoopRe =
      /for\s*\(\s*(?:byte|int|unsigned|uint8_t)?\s*(\w+)\s*=\s*(\d+)\s*;\s*\1\s*(<|<=)\s*(\d+)\s*;[^\)]*\)\s*\{([\s\S]*?)\}/g;
    let fm: RegExpExecArray | null;
    while ((fm = forLoopRe.exec(mainCode))) {
      const varName = fm[1];
      const start = Number(fm[2]);
      const cmp = fm[3];
      const end = Number(fm[4]);
      const body = fm[5];
      const useRe = new RegExp("analogRead\\s*\\(\\s*" + varName + "\\s*\\)", "g");
      Eif (useRe.test(body)) {
        const inclusive = cmp === "<=";
        const last = inclusive ? end : end - 1;
        for (let pin = start; pin <= last; pin++) {
          Iif (pin >= 0 && pin <= 5) pins.add(14 + pin);
          else if (pin >= 14 && pin <= 19) pins.add(pinE);
          else if (pin >= 16 && pin <= 19) pins.add(pin);
        }
      }
    }
 
    // pinMode(...) detection
    const pinModeRe = /pinMode\s*\(\s*(A\d+|\d+)\s*,\s*(INPUT_PULLUP|INPUT|OUTPUT)\s*\)/g;
    const digitalPinsFromPinMode = new Set<number>();
    const detectedModes: Record<number, PinMode> = {};
    while ((m = pinModeRe.exec(mainCode))) {
      const token = m[1];
      const modeToken = m[2];
      let p: number | undefined;
      const aMatch = token.match(/^A(\d+)$/i);
      if (aMatch) {
        const idx = Number(aMatch[1]);
        Eif (idx >= 0 && idx <= 5) p = 14 + idx;
      E} else if (/^\d+$/.test(token)) {
        const idx = Number(token);
        Eif (idx >= 0 && idx <= 255) p = idx;
      }
      Eif (p !== undefined) {
        digitalPinsFromPinMode.add(p);
        const mode =
          modeToken === "INPUT_PULLUP"
            ? "INPUT_PULLUP"
            : modeToken === "OUTPUT"
            ? "OUTPUT"
            : "INPUT";
        detectedModes[p] = mode;
      }
    }
 
    // find overlaps (conflicts): pins used with analogRead and also declared via pinMode(...)
    const overlap = Array.from(pins).filter((p) =>
      digitalPinsFromPinMode.has(p),
    );
 
    const clientPins = Array.from(pins).sort((a, b) => a - b);
 
    // Convert varMap to plain object
    const varObj: Record<string, number> = {};
    for (const [k, v] of varMap.entries()) varObj[k] = v;
 
    return {
      analogPins: clientPins,
      varMap: varObj,
      detectedPinModes: detectedModes,
      pendingPinConflicts: overlap,
      digitalPinsFromPinMode: Array.from(digitalPinsFromPinMode).sort(
        (a, b) => a - b,
      ),
    };
  }, [code]);
}