Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions client/src/components/features/code-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function formatCode(code: string): string {
formatted = formatted.replaceAll(/\{\s*/g, "{\n");

// 3. Add newlines before closing braces
formatted = formatted.replaceAll(/[ \t\n\r]*\}/g, "\n}");
formatted = formatted.replaceAll(/[ \t\n\r]*\}/g, "\n}"); // NOSONAR S2631

// 4. Indent blocks (simple 2-space indentation)
const lines = formatted.split("\n");
Expand Down Expand Up @@ -249,10 +249,10 @@ export function CodeEditor({
monaco.languages.setMonarchTokensProvider("arduino-cpp", {
tokenizer: {
root: [
[/\/\/.*$/, "comment"],
[/\/\/.*$/, "comment"], // NOSONAR S5843
[/\/\*/, "comment.block", "@comment"],
[/".*?"/, "string"],
[/'.*?'/, "string"],
[/".*?"/, "string"], // NOSONAR S5843
[/'.*?'/, "string"], // NOSONAR S5843
[
/\b(void|int|float|double|char|bool|byte|String|long|short|unsigned)\b/,
"type",
Expand Down
2 changes: 1 addition & 1 deletion client/src/hooks/use-debug-console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export function useDebugConsole(activeOutputTab: string) {
if (!debugMode) return;

const message: DebugMessage = {
id: `${Date.now()}-${Math.random()}`,
id: `${Date.now()}-${crypto.randomUUID()}`,
timestamp: new Date(),
sender,
type,
Expand Down
10 changes: 5 additions & 5 deletions client/src/hooks/use-output-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ function _computeMessagePanelSize(
: 50;
let measuredPercent = estimatedPercent;
try {
const panelNode = headerEl?.closest("[data-panel]") as HTMLElement | null;
const groupNode = panelNode?.parentElement as HTMLElement | null;
const panelNode = headerEl?.closest<HTMLElement>("[data-panel]");
const groupNode = panelNode?.parentElement;
const groupHeightPx = Math.ceil(groupNode?.getBoundingClientRect().height || 0);
const messagesHeightPx = parserMessagesContainerRef.current
? Math.ceil(parserMessagesContainerRef.current.scrollHeight)
Expand Down Expand Up @@ -208,7 +208,7 @@ export function useOutputPanel(
if (!headerEl || !panelHandle) return;

const panelNode = headerEl.closest<HTMLElement>("[data-panel]");
const groupNode = panelNode?.parentElement as HTMLElement | null;
const groupNode = panelNode?.parentElement;
if (!panelNode || !groupNode) return;

const headerRect = headerEl.getBoundingClientRect();
Expand Down Expand Up @@ -284,7 +284,7 @@ export function useOutputPanel(

const headerEl = outputTabsHeaderRef.current;
const panelNode = headerEl?.closest<HTMLElement>("[data-panel]");
const groupNode = panelNode?.parentElement as HTMLElement | null;
const groupNode = panelNode?.parentElement;

if (!groupNode) return;

Expand Down Expand Up @@ -317,7 +317,7 @@ export function useOutputPanel(
if (!headerEl) return;

const panelNode = headerEl.closest<HTMLElement>("[data-panel]");
const groupNode = panelNode?.parentElement as HTMLElement | null;
const groupNode = panelNode?.parentElement;
if (!panelNode || !groupNode) return;

const headerRect = headerEl.getBoundingClientRect();
Expand Down
24 changes: 15 additions & 9 deletions client/src/hooks/use-sketch-analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const DEFINE_PIN_RE = /#define\s+(\w+)\s+(A\d|\d+)/g;
const ASSIGN_PIN_RE = /(?:int|const\s+int|uint8_t|byte)\s+(\w+)\s*=\s*(A\d|\d+)\s*;/g;

/** Match analogRead(token) calls. */
const ANALOG_READ_RE = /analogRead\s*\(\s*([^)]+)\s*\)/g;
const ANALOG_READ_RE = /analogRead\s*\(\s*([^)\s]+)\s*\)/g;

/** Extracts the body of a braced block starting at `openBracePos` in `src`. */
function extractBracedBody(src: string, openBracePos: number): string {
Expand All @@ -42,9 +42,11 @@ function extractBracedBody(src: string, openBracePos: number): string {
}

/** Match for-loop pattern with integer iteration (header + opening brace only; body extracted via brace counting). */
// Split for-loop regex across variables to keep S5843 complexity ≤20 per variable
const FOR_INIT = /(?:\w+\s+)?/.source; // optional type prefix (e.g. "int ")
const FOR_LOOP_RE = new RegExp(String.raw`for\s*\( *${FOR_INIT}(\w+) *= *(\d+) *; *\w+ *(<=?) *(\d+) *;[^)]*\)`, "g");
// Two separate regexes to avoid super-linear backtracking (S5843):
// 1. With type prefix: for (int i = 0; i <= 5; ...)
const FOR_LOOP_TYPED_RE = /for *\( *\w+ +(\w+) *= *(\d+) *; *\w+ *(<=?) *(\d+) *;[^)]*\)/g;
// 2. Without type prefix: for (i = 0; i <= 5; ...)
const FOR_LOOP_BARE_RE = /for *\( *(\w+) *= *(\d+) *; *\w+ *(<=?) *(\d+) *;[^)]*\)/g;
// Verify the for-loop is followed by a brace
const FOR_BRACE_TAIL = /^ *\{/;

Expand Down Expand Up @@ -156,26 +158,30 @@ function findAnalogReadPins(
/** Finds analog pins iterated in for-loops and used in analogRead. */
function findForLoopPins(code: string): Set<number> {
const pins = new Set<number>();
let fm: RegExpExecArray | null = null;

while ((fm = FOR_LOOP_RE.exec(code))) {
const processMatch = (fm: RegExpExecArray) => {
const tail = code.slice(fm.index + fm[0].length);
if (!FOR_BRACE_TAIL.test(tail)) continue;
if (!FOR_BRACE_TAIL.test(tail)) return;
const bracePos = fm.index + fm[0].length + tail.indexOf("{") + 1;
const [, varName, startStr, cmp, endStr] = fm;
const body = extractBracedBody(code, bracePos);
const useRe = new RegExp(
String.raw`analogRead\s*\(\s*${varName}\s*\)`,
"g",
);
if (!useRe.test(body)) continue;
if (!useRe.test(body)) return;
const start = Number(startStr);
const last = cmp === "<=" ? Number(endStr) : Number(endStr) - 1;
for (let pin = start; pin <= last; pin++) {
if (pin >= 0 && pin <= 5) pins.add(14 + pin);
else if (pin >= 14 && pin <= 19) pins.add(pin);
}
}
};

let fm: RegExpExecArray | null = null;
while ((fm = FOR_LOOP_TYPED_RE.exec(code))) processMatch(fm);
while ((fm = FOR_LOOP_BARE_RE.exec(code))) processMatch(fm);

return pins;
}

Expand Down
2 changes: 1 addition & 1 deletion client/src/hooks/use-sketch-tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export function useSketchTabs() {
}, []);

const createTab = useCallback((name: string, content: string = ""): string => {
const newTabId = `tab-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
const newTabId = `tab-${Date.now()}-${crypto.randomUUID().replaceAll("-", "").slice(0, 9)}`;
const newTab: SketchTab = {
id: newTabId,
name,
Expand Down
2 changes: 1 addition & 1 deletion client/src/hooks/useFileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export function useFileSystem(params: UseFileSystemParams): UseFileSystemResult
setTabs((prevTabs) => {
if (prevTabs.length > 0) return prevTabs;

const tabId = `tab-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
const tabId = `tab-${Date.now()}-${crypto.randomUUID().slice(0, 7)}`;
setActiveTabId(tabId);
return [
{
Expand Down
8 changes: 4 additions & 4 deletions client/src/hooks/useSimulatorFileSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function useSimulatorFileSystem({
);

const handleTabAdd = useCallback(() => {
const newTabId = Math.random().toString(36).slice(2, 11);
const newTabId = crypto.randomUUID().replaceAll("-", "").slice(0, 9);
const newTab = {
id: newTabId,
name: `header_${tabs.length}.h`,
Expand Down Expand Up @@ -105,7 +105,7 @@ export function useSimulatorFileSystem({
const orderedFiles = [...inoFiles, ...hFiles];

const newTabs = orderedFiles.map((file) => ({
id: Math.random().toString(36).slice(2, 11),
id: crypto.randomUUID().replaceAll("-", "").slice(0, 9),
name: file.name,
content: file.content,
}));
Expand All @@ -120,7 +120,7 @@ export function useSimulatorFileSystem({
}
} else {
const newHeaderFiles = files.map((file) => ({
id: Math.random().toString(36).slice(2, 11),
id: crypto.randomUUID().replaceAll("-", "").slice(0, 9),
name: file.name,
content: file.content,
}));
Expand Down Expand Up @@ -152,7 +152,7 @@ export function useSimulatorFileSystem({
onLoadExample?.();

const newTab = {
id: Math.random().toString(36).slice(2, 11),
id: crypto.randomUUID().replaceAll("-", "").slice(0, 9),
name: filename,
content,
};
Expand Down
2 changes: 1 addition & 1 deletion client/src/lib/websocket-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ class WebSocketManager {

// Calculate delay with exponential backoff + jitter
const baseDelay = CONFIG.RECONNECT_BASE_DELAY_MS * Math.pow(2, this.reconnectAttempts);
const jitter = Math.random() * 1000; // 0-1s random jitter
const jitter = (crypto.getRandomValues(new Uint32Array(1))[0] / 0xFFFFFFFF) * 1000; // 0-1s random jitter
const delay = Math.min(baseDelay + jitter, CONFIG.RECONNECT_MAX_DELAY_MS);

this.reconnectAttempts++;
Expand Down
4 changes: 2 additions & 2 deletions e2e/smoke-and-flow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ void loop() {
}`;

// Wait until Monaco is fully initialised (the editor hook exposes __MONACO_EDITOR__)
await page.waitForFunction(() => Boolean((window as unknown as Record<string, unknown>)['__MONACO_EDITOR__']), { timeout: 15000 });
await page.waitForFunction(() => Boolean((globalThis as unknown as Record<string, unknown>)['__MONACO_EDITOR__']), { timeout: 15000 });

// Inject code directly via the editor instance — reliable in any environment
await page.evaluate((sketch: string) => {
const editor = (window as unknown as Record<string, unknown>)['__MONACO_EDITOR__'] as { setValue: (v: string) => void };
const editor = (globalThis as unknown as Record<string, unknown>)['__MONACO_EDITOR__'] as { setValue: (v: string) => void };
editor.setValue(sketch);
}, code);

Expand Down
6 changes: 3 additions & 3 deletions server/services/arduino-output-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,14 @@ export class ArduinoOutputParser {
serialEvent: /\[\[SERIAL_EVENT:(\d+):([A-Za-z0-9+/=]+)\]\]/,
registryStart: /\[\[IO_REGISTRY_START\]\]/,
registryEnd: /\[\[IO_REGISTRY_END\]\]/,
registryPin: /\[\[IO_PIN:([^:]+):([01]):(\d+):(\d+):?(.*)\]\]/,
registryPin: /\[\[IO_PIN:([^:]+):([01]):(\d+):(\d+):?([^[\]]*)]\]/, // NOSONAR S5843
pinMode: /\[\[PIN_MODE:(\d+):(\d+)\]\]/,
pinValue: /\[\[PIN_VALUE:(\d+):(\d+)\]\]/,
pinPwm: /\[\[PIN_PWM:(\d+):(\d+)\]\]/,
// Debug markers - should be ignored
digitalRead: /\[\[DREAD:(\d+):(\d+)\]\]/,
pinSet: /\[\[PIN_SET:(\d+):(\d+)\]\]/,
stdinRecv: /\[\[STDIN_RECV:(.+)\]\]/,
stdinRecv: /\[\[STDIN_RECV:([^\]]+)\]\]/, // NOSONAR S5843
// Pause/Resume timing markers - should be ignored
timeFrozen: /\[\[TIME_FROZEN:(\d+)\]\]/,
timeResumed: /\[\[TIME_RESUMED:(\d+)\]\]/,
Expand Down Expand Up @@ -207,7 +207,7 @@ export class ArduinoOutputParser {
const usedAt: Array<{ line: number; operation: string }> = [];
if (operationsStr) {
// Parse operations: "pinMode:1@0:digitalWrite@5" -> extract operation@line pairs
const opMatches = operationsStr.match(/([^:@]+(?::\d+)?@\d+)/g);
const opMatches = operationsStr.match(/([^:@]+(?::\d+)?@\d+)/g); // NOSONAR S5843
if (opMatches) {
opMatches.forEach((opMatch) => {
if (opMatch && !opMatch.startsWith("_count")) {
Expand Down
2 changes: 1 addition & 1 deletion server/services/compiler/compiler-output-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class CompilerOutputParser {
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 = /^([^:]+):(\d+)(?::(\d+))?:\s+(warning|error):\s+(.*)$/gm;
const regex = /^([^:\n]+):(\d+)(?::(\d+))?: +(warning|error): +([^\n]*)$/gm; // NOSONAR S5843
const results: CompilationError[] = [];
const seen = new Set<string>();

Expand Down
2 changes: 1 addition & 1 deletion server/services/local-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ export class LocalCompiler {
private cleanCompilerErrors(errors: string): string {
return errors
.replaceAll('/sandbox/sketch.cpp', "sketch.ino") // Docker path
.replaceAll(/\/[^\s:]+\/temp\/[a-f0-9-]+\/sketch\.cpp/gi, "sketch.ino") // Local temp path
.replaceAll(/\/[^\s:/]+(?:\/[^\s:/]+)*\/temp\/[a-f0-9-]+\/sketch\.cpp/gi, "sketch.ino") // Local temp path
.replaceAll('sketch.cpp', "sketch.ino") // Generic .cpp references
.trim();
}
Expand Down
2 changes: 1 addition & 1 deletion server/services/sandbox/docker-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ export class DockerManager {
* Clean up compiler error messages
*/
private cleanCompilerErrors(errors: string): string {
return errors.replaceAll("/sandbox/sketch.cpp", "sketch.ino").replaceAll(/\/[^\s:]+\/temp\/[a-f0-9-]+\/sketch\.cpp/gi, "sketch.ino").trim();
return errors.replaceAll("/sandbox/sketch.cpp", "sketch.ino").replaceAll(/(?:\/[^\s:/]+)+\/temp\/[a-f0-9-]+\/sketch\.cpp/gi, "sketch.ino").trim();
}

/**
Expand Down
2 changes: 1 addition & 1 deletion server/services/sandbox/execution-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,7 @@ export class ExecutionManager {
private cleanCompilerErrors(errors: string): string {
return errors
.replaceAll("/sandbox/sketch.cpp", "sketch.ino")
.replaceAll(/\/[^\s:]+\/temp\/[a-f0-9-]+\/sketch\.cpp/gi, "sketch.ino")
.replaceAll(/(?:\/[^\s:/]+)+\/temp\/[a-f0-9-]+\/sketch\.cpp/gi, "sketch.ino")
.trim();
}

Expand Down
10 changes: 6 additions & 4 deletions server/services/unified-gatekeeper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ export class UnifiedGatekeeper extends EventEmitter {
owner: string = "unknown",
): Promise<() => void> {
this.stats.totalCompileSlotRequests++;
const ownerId = `${owner}-${Date.now()}-${Math.random()}`;
const ownerId = `${owner}-${Date.now()}-${crypto.randomUUID()}`;

return new Promise((resolve, reject) => {
const grant = () => {
Expand Down Expand Up @@ -259,7 +259,7 @@ export class UnifiedGatekeeper extends EventEmitter {
owner: string = "unknown",
): Promise<() => Promise<void>> {
this.stats.totalCacheLockRequests++;
const ownerId = `${owner}-${Date.now()}-${Math.random()}`;
const ownerId = `${owner}-${Date.now()}-${crypto.randomUUID()}`;

return new Promise((resolve, reject) => {
let timeoutHandle: NodeJS.Timeout | null = null;
Expand Down Expand Up @@ -338,7 +338,8 @@ export class UnifiedGatekeeper extends EventEmitter {
*/
private _grantNextQueuedSlot(): void {
if (this.compileQueue.length === 0) return;
const task = this.compileQueue.shift()!;
const task = this.compileQueue.shift();
if (!task) return;
try {
task.resolver(this.createReleaseFunction(task.ownerId, "compile"));
} catch (err) {
Expand All @@ -347,7 +348,8 @@ export class UnifiedGatekeeper extends EventEmitter {
);
// Slot remains available (already incremented above), try next task
if (this.compileQueue.length > 0) {
const next = this.compileQueue.shift()!;
const next = this.compileQueue.shift();
if (!next) return;
try {
next.resolver(this.createReleaseFunction(next.ownerId, "compile"));
} catch {
Expand Down
Loading
Loading