From 4eba3e5d7db963d6fbdcfa8abf305500b184eb1f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 31 May 2026 07:38:24 +0100 Subject: [PATCH] chore(lint): enable more readability rules --- .oxlintrc.json | 4 ++ .../browser/src/browser/chrome.executables.ts | 3 +- extensions/matrix/src/matrix/sdk.ts | 6 +-- .../memory-core/src/concept-vocabulary.ts | 2 +- extensions/nextcloud-talk/src/inbound.ts | 40 +++++++------- extensions/qqbot/src/engine/utils/audio.ts | 12 ++--- extensions/signal/src/client-adapter.test.ts | 12 ++--- extensions/telegram/src/account-throttler.ts | 2 +- .../telegram/src/bot/delivery.replies.ts | 3 +- extensions/telegram/src/fetch.ts | 3 +- .../src/webhook/realtime-audio-pacer.ts | 4 +- .../src/auto-reply/monitor/on-message.ts | 12 ++--- .../src/harness/compaction/compaction.ts | 6 +-- .../src/harness/prompt-template-arguments.ts | 3 +- scripts/check-cli-bootstrap-imports.mjs | 3 +- scripts/check-temp-path-guardrails.ts | 3 +- .../lib/plugin-lifecycle-matrix/measure.mjs | 4 +- scripts/lib/package-dist-imports.mjs | 3 +- scripts/run-vitest.mjs | 3 +- scripts/test-projects.test-support.mjs | 6 +-- src/agents/bash-tools.exec.ts | 6 +-- src/agents/cli-runner/helpers.ts | 3 +- src/agents/harness/native-hook-relay.ts | 4 +- src/agents/subagent-registry-queries.ts | 6 +-- src/agents/tool-display-exec-shell.ts | 4 +- src/cli/logs-cli.ts | 6 +-- src/commands/doctor-state-integrity.ts | 4 +- src/commands/health.ts | 2 +- src/config/io.audit.ts | 3 +- src/cron/normalize.ts | 22 ++++---- src/cron/run-log-jsonl.ts | 6 +-- src/gateway/chat-display-projection.ts | 3 +- src/gateway/server-methods/artifacts.ts | 3 +- src/infra/command-explainer/extract.ts | 4 +- src/infra/exec-approvals-analysis.ts | 52 +++++++++---------- src/llm/providers/transform-messages.ts | 4 +- src/media/png-encode.ts | 4 +- src/plugins/contracts/tts-contract-suites.ts | 6 +-- src/secrets/channel-env-vars.ts | 2 +- src/secrets/provider-env-vars.ts | 6 +-- src/skills/loading/workspace.ts | 9 ++-- src/skills/runtime/refresh.ts | 4 +- src/tui/tui-event-handlers.ts | 6 +-- test/scripts/oxlint-config.test.ts | 4 ++ ui/src/ui/app-render.ts | 18 +++---- ui/src/ui/markdown.ts | 20 +++---- ui/src/ui/uuid.ts | 4 +- ui/src/ui/views/chat.ts | 3 +- 48 files changed, 152 insertions(+), 200 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index bae3e40abb5..d62def0e065 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -32,11 +32,13 @@ "eslint/no-useless-computed-key": "error", "eslint/no-useless-concat": "error", "eslint/no-useless-constructor": "error", + "eslint/no-useless-rename": "error", "eslint/no-unused-vars": "off", "eslint/no-warning-comments": "error", "eslint/no-unmodified-loop-condition": "error", "eslint/no-new-wrappers": "error", "eslint/no-else-return": "error", + "eslint/no-lonely-if": "error", "eslint/no-case-declarations": "error", "eslint/default-case-last": "error", "eslint/default-param-last": "error", @@ -89,6 +91,7 @@ "typescript/prefer-namespace-keyword": "error", "typescript/prefer-return-this-type": "error", "typescript/prefer-find": "error", + "typescript/prefer-for-of": "error", "typescript/prefer-function-type": "error", "typescript/prefer-includes": "error", "typescript/prefer-reduce-type-parameter": "error", @@ -118,6 +121,7 @@ "unicorn/prefer-date-now": "error", "unicorn/prefer-dom-node-text-content": "error", "unicorn/prefer-keyboard-event-key": "error", + "unicorn/prefer-array-flat": "error", "unicorn/prefer-array-some": "error", "unicorn/prefer-math-min-max": "error", "unicorn/prefer-node-protocol": "error", diff --git a/extensions/browser/src/browser/chrome.executables.ts b/extensions/browser/src/browser/chrome.executables.ts index 188e9c112a5..c0cc9641693 100644 --- a/extensions/browser/src/browser/chrome.executables.ts +++ b/extensions/browser/src/browser/chrome.executables.ts @@ -373,8 +373,7 @@ function splitExecLine(line: string): string[] { let current = ""; let inQuotes = false; let quoteChar = ""; - for (let i = 0; i < line.length; i += 1) { - const ch = line[i]; + for (const ch of line) { if ((ch === '"' || ch === "'") && (!inQuotes || ch === quoteChar)) { if (inQuotes) { inQuotes = false; diff --git a/extensions/matrix/src/matrix/sdk.ts b/extensions/matrix/src/matrix/sdk.ts index 7cd07590a10..07d1b43d758 100644 --- a/extensions/matrix/src/matrix/sdk.ts +++ b/extensions/matrix/src/matrix/sdk.ts @@ -2051,10 +2051,8 @@ export class MatrixClient { this.emitter.emit("room.event", roomId, raw); if (isEncryptedEvent) { this.emitter.emit("room.encrypted_event", roomId, raw); - } else { - if (decryptBridge.shouldEmitUnencryptedMessage(roomId, raw.event_id)) { - this.emitter.emit("room.message", roomId, raw); - } + } else if (decryptBridge.shouldEmitUnencryptedMessage(roomId, raw.event_id)) { + this.emitter.emit("room.message", roomId, raw); } const stateKey = raw.state_key ?? ""; diff --git a/extensions/memory-core/src/concept-vocabulary.ts b/extensions/memory-core/src/concept-vocabulary.ts index aee36b17d98..f80c0d95704 100644 --- a/extensions/memory-core/src/concept-vocabulary.ts +++ b/extensions/memory-core/src/concept-vocabulary.ts @@ -231,7 +231,7 @@ const LANGUAGE_STOP_WORDS = { const CONCEPT_STOP_WORDS = new Set( Object.values(LANGUAGE_STOP_WORDS) - .flatMap((words) => words) + .flat() .map((word) => normalizeLowercaseStringOrEmpty(word)), ); diff --git a/extensions/nextcloud-talk/src/inbound.ts b/extensions/nextcloud-talk/src/inbound.ts index 77f86bf9245..9fdcf2317c7 100644 --- a/extensions/nextcloud-talk/src/inbound.ts +++ b/extensions/nextcloud-talk/src/inbound.ts @@ -255,28 +255,26 @@ export async function handleNextcloudTalkInbound(params: { runtime.log?.(`nextcloud-talk: drop group sender ${senderId} (reason=${accessReason})`); return; } - } else { - if (access.senderAccess.decision !== "allow") { - if (access.senderAccess.decision === "pairing") { - await pairing.issueChallenge({ - senderId, - senderIdLine: `Your Nextcloud user id: ${senderId}`, - meta: { name: senderName || undefined }, - sendPairingReply: async (text) => { - await sendMessageNextcloudTalk(roomToken, text, { - cfg: config, - accountId: account.accountId, - }); - statusSink?.({ lastOutboundAt: Date.now() }); - }, - onReplyError: (err) => { - runtime.error?.(`nextcloud-talk: pairing reply failed for ${senderId}: ${String(err)}`); - }, - }); - } - runtime.log?.(`nextcloud-talk: drop DM sender ${senderId} (reason=${accessReason})`); - return; + } else if (access.senderAccess.decision !== "allow") { + if (access.senderAccess.decision === "pairing") { + await pairing.issueChallenge({ + senderId, + senderIdLine: `Your Nextcloud user id: ${senderId}`, + meta: { name: senderName || undefined }, + sendPairingReply: async (text) => { + await sendMessageNextcloudTalk(roomToken, text, { + cfg: config, + accountId: account.accountId, + }); + statusSink?.({ lastOutboundAt: Date.now() }); + }, + onReplyError: (err) => { + runtime.error?.(`nextcloud-talk: pairing reply failed for ${senderId}: ${String(err)}`); + }, + }); } + runtime.log?.(`nextcloud-talk: drop DM sender ${senderId} (reason=${accessReason})`); + return; } if (access.commandAccess.shouldBlockControlCommand) { diff --git a/extensions/qqbot/src/engine/utils/audio.ts b/extensions/qqbot/src/engine/utils/audio.ts index 53e8c2d2125..b374ebc67ff 100644 --- a/extensions/qqbot/src/engine/utils/audio.ts +++ b/extensions/qqbot/src/engine/utils/audio.ts @@ -312,13 +312,11 @@ export async function waitForFile( stableCount = 0; } lastSize = stat.size; - } else { - if (Date.now() - fileAppearedAt > emptyGiveUpMs) { - debugError( - `[audio-convert] waitForFile: file still empty after ${emptyGiveUpMs}ms, giving up: ${path.basename(filePath)}`, - ); - return 0; - } + } else if (Date.now() - fileAppearedAt > emptyGiveUpMs) { + debugError( + `[audio-convert] waitForFile: file still empty after ${emptyGiveUpMs}ms, giving up: ${path.basename(filePath)}`, + ); + return 0; } } catch { if (!fileExists && Date.now() - start > noFileGiveUpMs) { diff --git a/extensions/signal/src/client-adapter.test.ts b/extensions/signal/src/client-adapter.test.ts index 27eec13003f..b14e71106eb 100644 --- a/extensions/signal/src/client-adapter.test.ts +++ b/extensions/signal/src/client-adapter.test.ts @@ -107,17 +107,13 @@ function expectRpcCall(params: { expect(method).toBe(params.method); if (params.rpcParams) { expectFields(requireRecord(rpcParams, "rpc params"), params.rpcParams); - } else { - if (rpcParams === undefined) { - throw new Error("expected rpc params argument"); - } + } else if (rpcParams === undefined) { + throw new Error("expected rpc params argument"); } if (params.options) { expectFields(requireRecord(options, "rpc options"), params.options); - } else { - if (options === undefined) { - throw new Error("expected rpc options argument"); - } + } else if (options === undefined) { + throw new Error("expected rpc options argument"); } } diff --git a/extensions/telegram/src/account-throttler.ts b/extensions/telegram/src/account-throttler.ts index 1258875f00e..db40db446da 100644 --- a/extensions/telegram/src/account-throttler.ts +++ b/extensions/telegram/src/account-throttler.ts @@ -68,7 +68,7 @@ class GroupFairQueue { } private takeNext(): QueuedApiRequest | undefined { - for (let scanned = 0; scanned < this.laneOrder.length; scanned += 1) { + for (const ignoredLaneKey of this.laneOrder) { this.nextLaneIndex %= this.laneOrder.length; const laneKey = this.laneOrder[this.nextLaneIndex]; const queue = this.lanes.get(laneKey); diff --git a/extensions/telegram/src/bot/delivery.replies.ts b/extensions/telegram/src/bot/delivery.replies.ts index 6ed5f03fdf1..864a9b486b4 100644 --- a/extensions/telegram/src/bot/delivery.replies.ts +++ b/extensions/telegram/src/bot/delivery.replies.ts @@ -294,8 +294,7 @@ async function sendTelegramVoiceFallbackText(opts: { let firstDeliveredMessageId: number | undefined; const chunks = filterEmptyTelegramTextChunks(opts.chunkText(opts.text)); let appliedReplyTo = false; - for (let i = 0; i < chunks.length; i += 1) { - const chunk = chunks[i]; + for (const chunk of chunks) { // Only apply reply reference, quote text, and buttons to the first chunk. const replyToForChunk = !appliedReplyTo ? opts.replyToId : undefined; const applyQuoteForChunk = !appliedReplyTo; diff --git a/extensions/telegram/src/fetch.ts b/extensions/telegram/src/fetch.ts index 21f5343f0b3..c98f69bdc61 100644 --- a/extensions/telegram/src/fetch.ts +++ b/extensions/telegram/src/fetch.ts @@ -216,8 +216,7 @@ function shouldBypassEnvProxyForTelegramApi(env: NodeJS.ProcessEnv = process.env const targetHostname = normalizeLowercaseStringOrEmpty(TELEGRAM_API_HOSTNAME); const targetPort = 443; const noProxyEntries = noProxyValue.split(/[,\s]/); - for (let i = 0; i < noProxyEntries.length; i++) { - const entry = noProxyEntries[i]; + for (const entry of noProxyEntries) { if (!entry) { continue; } diff --git a/extensions/voice-call/src/webhook/realtime-audio-pacer.ts b/extensions/voice-call/src/webhook/realtime-audio-pacer.ts index 5f49942d6d4..aef61803062 100644 --- a/extensions/voice-call/src/webhook/realtime-audio-pacer.ts +++ b/extensions/voice-call/src/webhook/realtime-audio-pacer.ts @@ -149,8 +149,8 @@ export function calculateMulawRms(muLaw: Buffer): number { return 0; } let sum = 0; - for (let i = 0; i < muLaw.length; i += 1) { - const normalized = (MULAW_LINEAR_SAMPLES[muLaw[i] ?? 0] ?? 0) / PCM16_MAX_AMPLITUDE; + for (const sample of muLaw) { + const normalized = (MULAW_LINEAR_SAMPLES[sample] ?? 0) / PCM16_MAX_AMPLITUDE; sum += normalized * normalized; } return Math.sqrt(sum / muLaw.length); diff --git a/extensions/whatsapp/src/auto-reply/monitor/on-message.ts b/extensions/whatsapp/src/auto-reply/monitor/on-message.ts index 4bd2c75cdcf..9ade3b0b6e9 100644 --- a/extensions/whatsapp/src/auto-reply/monitor/on-message.ts +++ b/extensions/whatsapp/src/auto-reply/monitor/on-message.ts @@ -287,14 +287,12 @@ export function createWebOnMessageHandler(params: { if (!gating.shouldProcess) { return; } - } else { + } else if (!msg.sender?.e164 && !msg.senderE164 && peerId && peerId.startsWith("+")) { // Ensure `peerId` for DMs is stable and stored as E.164 when possible. - if (!msg.sender?.e164 && !msg.senderE164 && peerId && peerId.startsWith("+")) { - const normalized = normalizeE164(peerId); - if (normalized) { - msg.sender = { ...msg.sender, e164: normalized }; - msg.senderE164 = normalized; - } + const normalized = normalizeE164(peerId); + if (normalized) { + msg.sender = { ...msg.sender, e164: normalized }; + msg.senderE164 = normalized; } } diff --git a/packages/agent-core/src/harness/compaction/compaction.ts b/packages/agent-core/src/harness/compaction/compaction.ts index 42d4ed895d5..32f9c7c84c7 100644 --- a/packages/agent-core/src/harness/compaction/compaction.ts +++ b/packages/agent-core/src/harness/compaction/compaction.ts @@ -401,9 +401,9 @@ export function findCutPoint( const messageTokens = estimateTokens(entry.message); accumulatedTokens += messageTokens; if (accumulatedTokens >= keepRecentTokens) { - for (let c = 0; c < cutPoints.length; c++) { - if (cutPoints[c] >= i) { - cutIndex = cutPoints[c]; + for (const cutPoint of cutPoints) { + if (cutPoint >= i) { + cutIndex = cutPoint; break; } } diff --git a/packages/agent-core/src/harness/prompt-template-arguments.ts b/packages/agent-core/src/harness/prompt-template-arguments.ts index 0c463b04402..42375be99f4 100644 --- a/packages/agent-core/src/harness/prompt-template-arguments.ts +++ b/packages/agent-core/src/harness/prompt-template-arguments.ts @@ -4,8 +4,7 @@ export function parseCommandArgs(argsString: string): string[] { let current = ""; let inQuote: string | null = null; - for (let i = 0; i < argsString.length; i++) { - const char = argsString[i]; + for (const char of argsString) { if (inQuote) { if (char === inQuote) { inQuote = null; diff --git a/scripts/check-cli-bootstrap-imports.mjs b/scripts/check-cli-bootstrap-imports.mjs index 4ddd131fa75..c888e218b31 100644 --- a/scripts/check-cli-bootstrap-imports.mjs +++ b/scripts/check-cli-bootstrap-imports.mjs @@ -67,8 +67,7 @@ function walkStaticImportGraph(params) { const visited = new Set(); const errors = []; - for (let index = 0; index < queue.length; index += 1) { - const filePath = queue[index]; + for (const filePath of queue) { if (!filePath || visited.has(filePath)) { continue; } diff --git a/scripts/check-temp-path-guardrails.ts b/scripts/check-temp-path-guardrails.ts index 648dbb53950..6b522e08b98 100644 --- a/scripts/check-temp-path-guardrails.ts +++ b/scripts/check-temp-path-guardrails.ts @@ -101,8 +101,7 @@ function splitTopLevelArguments(source: string): string[] { let bracketDepth = 0; let braceDepth = 0; const quoteState: QuoteScanState = { quote: null, escaped: false }; - for (let i = 0; i < source.length; i += 1) { - const ch = source[i]; + for (const ch of source) { if (quoteState.quote) { current += ch; consumeQuotedChar(quoteState, ch); diff --git a/scripts/e2e/lib/plugin-lifecycle-matrix/measure.mjs b/scripts/e2e/lib/plugin-lifecycle-matrix/measure.mjs index 0b1272d7126..cf5b961a90d 100644 --- a/scripts/e2e/lib/plugin-lifecycle-matrix/measure.mjs +++ b/scripts/e2e/lib/plugin-lifecycle-matrix/measure.mjs @@ -85,8 +85,8 @@ function descendantsOf(rootPid, stats) { } const seen = new Set([rootPid]); const queue = [rootPid]; - for (let index = 0; index < queue.length; index += 1) { - for (const child of children.get(queue[index]) ?? []) { + for (const queuedPid of queue) { + for (const child of children.get(queuedPid) ?? []) { if (!seen.has(child)) { seen.add(child); queue.push(child); diff --git a/scripts/lib/package-dist-imports.mjs b/scripts/lib/package-dist-imports.mjs index fb1589d9755..78b4cc66d5e 100644 --- a/scripts/lib/package-dist-imports.mjs +++ b/scripts/lib/package-dist-imports.mjs @@ -157,8 +157,7 @@ export function expandPackageDistImportClosure(params) { } const queue = [...expectedSet].filter((file) => fileSet.has(file)); - for (let index = 0; index < queue.length; index += 1) { - const importerPath = queue[index]; + for (const importerPath of queue) { for (const importedPath of importsByImporter.get(importerPath) ?? []) { if (fileSet.has(importedPath) && !expectedSet.has(importedPath)) { expectedSet.add(importedPath); diff --git a/scripts/run-vitest.mjs b/scripts/run-vitest.mjs index d8cc39fe85b..9d509630e97 100644 --- a/scripts/run-vitest.mjs +++ b/scripts/run-vitest.mjs @@ -391,8 +391,7 @@ function hasExplicitDisabledRunFlag(argv) { } function hasSeparateVitestOptionValueArg(argv) { - for (let index = 0; index < argv.length; index += 1) { - const arg = argv[index]; + for (const arg of argv) { if (arg === "--") { return false; } diff --git a/scripts/test-projects.test-support.mjs b/scripts/test-projects.test-support.mjs index fd14a0a8d51..937071c19f6 100644 --- a/scripts/test-projects.test-support.mjs +++ b/scripts/test-projects.test-support.mjs @@ -1338,8 +1338,7 @@ function resolveAffectedTestsFromTargetedImportScan(changedPath, cwd) { const seen = new Set(queue); const targets = []; - for (let index = 0; index < queue.length; index += 1) { - const current = queue[index]; + for (const current of queue) { const importers = findDirectImportersWithGitGrep(cwd, current, fileSet); if (importers === null) { return null; @@ -1409,8 +1408,7 @@ function resolveAffectedTestsFromImportGraph(changedPath, cwd, options = {}) { const seen = new Set(queue); const targets = []; - for (let index = 0; index < queue.length; index += 1) { - const current = queue[index]; + for (const current of queue) { for (const importer of reverseImports.get(current) ?? []) { if (seen.has(importer)) { continue; diff --git a/src/agents/bash-tools.exec.ts b/src/agents/bash-tools.exec.ts index 8daf6e54300..5a76f3c9840 100644 --- a/src/agents/bash-tools.exec.ts +++ b/src/agents/bash-tools.exec.ts @@ -422,8 +422,7 @@ function extractScriptTargetFromCommand( } }; - for (let i = 0; i < value.length; i += 1) { - const ch = value[i]; + for (const ch of value) { if (inSingle) { if (ch === "'") { inSingle = false; @@ -669,8 +668,7 @@ function hasUnquotedScriptHint(raw: string): boolean { return false; }; - for (let i = 0; i < raw.length; i += 1) { - const ch = raw[i]; + for (const ch of raw) { if (escaped) { if (!inSingle && !inDouble) { token += ch; diff --git a/src/agents/cli-runner/helpers.ts b/src/agents/cli-runner/helpers.ts index bfd0c3e0c86..7dab057c42b 100644 --- a/src/agents/cli-runner/helpers.ts +++ b/src/agents/cli-runner/helpers.ts @@ -286,8 +286,7 @@ export async function writeCliImages(params: { await fs.mkdir(imageRoot, { recursive: true, mode: 0o700 }); const store = privateFileStore(imageRoot); const paths: string[] = []; - for (let i = 0; i < params.images.length; i += 1) { - const image = params.images[i]; + for (const image of params.images) { const fileName = path.basename(resolveCliImagePath(image)); const buffer = Buffer.from(image.data, "base64"); await store.writeText(fileName, buffer); diff --git a/src/agents/harness/native-hook-relay.ts b/src/agents/harness/native-hook-relay.ts index 4a61c8d1108..0c60cff9f8c 100644 --- a/src/agents/harness/native-hook-relay.ts +++ b/src/agents/harness/native-hook-relay.ts @@ -2221,11 +2221,11 @@ function isJsonValue(value: unknown): value is JsonValue { continue; } if (Array.isArray(current.value)) { - for (let index = 0; index < current.value.length; index += 1) { + for (const value of current.value) { if (nodes + stack.length + 1 > MAX_NATIVE_HOOK_RELAY_JSON_NODES) { return false; } - stack.push({ value: current.value[index], depth: current.depth + 1 }); + stack.push({ value, depth: current.depth + 1 }); } continue; } diff --git a/src/agents/subagent-registry-queries.ts b/src/agents/subagent-registry-queries.ts index 093399c8575..a4e3f1c2760 100644 --- a/src/agents/subagent-registry-queries.ts +++ b/src/agents/subagent-registry-queries.ts @@ -196,8 +196,7 @@ export function buildSubagentRunReadIndexFromRuns(params: { let count = 0; const pending = [root]; const visited = new Set([root]); - for (let index = 0; index < pending.length; index += 1) { - const requester = pending[index]; + for (const requester of pending) { if (!requester) { continue; } @@ -377,8 +376,7 @@ function forEachDescendantRun( } const pending = [root]; const visited = new Set([root]); - for (let index = 0; index < pending.length; index += 1) { - const requester = pending[index]; + for (const requester of pending) { if (!requester) { continue; } diff --git a/src/agents/tool-display-exec-shell.ts b/src/agents/tool-display-exec-shell.ts index df02252013b..097b234d1aa 100644 --- a/src/agents/tool-display-exec-shell.ts +++ b/src/agents/tool-display-exec-shell.ts @@ -30,9 +30,7 @@ export function splitShellWords(input: string | undefined, maxWords = 48): strin let quote: '"' | "'" | undefined; let escaped = false; - for (let i = 0; i < input.length; i += 1) { - const char = input[i]; - + for (const char of input) { if (escaped) { current += char; escaped = false; diff --git a/src/cli/logs-cli.ts b/src/cli/logs-cli.ts index 2a3e9f82552..aefcc444d62 100644 --- a/src/cli/logs-cli.ts +++ b/src/cli/logs-cli.ts @@ -589,10 +589,8 @@ export function registerLogsCli(program: Command) { if (!emitJsonLine({ type: "log", ...parsed })) { return; } - } else { - if (!emitJsonLine({ type: "raw", raw: line })) { - return; - } + } else if (!emitJsonLine({ type: "raw", raw: line })) { + return; } } if (payload.truncated) { diff --git a/src/commands/doctor-state-integrity.ts b/src/commands/doctor-state-integrity.ts index e9fdef5358f..96e443fd524 100644 --- a/src/commands/doctor-state-integrity.ts +++ b/src/commands/doctor-state-integrity.ts @@ -218,8 +218,8 @@ function countJsonlLines(filePath: string): number { return 0; } let count = 0; - for (let i = 0; i < raw.length; i += 1) { - if (raw[i] === "\n") { + for (const char of raw) { + if (char === "\n") { count += 1; } } diff --git a/src/commands/health.ts b/src/commands/health.ts index 66329b0326a..84edcb565a5 100644 --- a/src/commands/health.ts +++ b/src/commands/health.ts @@ -454,7 +454,7 @@ export async function getHealthSnapshot(params?: { boundAccounts, }); const boundAccountIdsAll = Array.from( - new Set(Array.from(channelBindings.get(plugin.id)?.values() ?? []).flatMap((ids) => ids)), + new Set(Array.from(channelBindings.get(plugin.id)?.values() ?? []).flat()), ); const accountIdsToProbe = Array.from( new Set( diff --git a/src/config/io.audit.ts b/src/config/io.audit.ts index 4a741e253a8..e1420ab1e7b 100644 --- a/src/config/io.audit.ts +++ b/src/config/io.audit.ts @@ -86,8 +86,7 @@ function parseFlagName(arg: string): string | null { export function redactConfigAuditArgv(argv: readonly string[]): string[] { const result: string[] = []; let redactNext = false; - for (let i = 0; i < argv.length; i++) { - const current = argv[i]; + for (const current of argv) { if (typeof current !== "string") { result.push(current); redactNext = false; diff --git a/src/cron/normalize.ts b/src/cron/normalize.ts index 676df714275..d5be2d1938a 100644 --- a/src/cron/normalize.ts +++ b/src/cron/normalize.ts @@ -91,18 +91,16 @@ function coerceSchedule(schedule: UnknownRecord) { if (kind) { next.kind = kind; - } else { - if ( - typeof schedule.atMs === "number" || - typeof schedule.at === "string" || - typeof schedule.atMs === "string" - ) { - next.kind = "at"; - } else if (everyMs !== undefined) { - next.kind = "every"; - } else if (normalizedExpr) { - next.kind = "cron"; - } + } else if ( + typeof schedule.atMs === "number" || + typeof schedule.at === "string" || + typeof schedule.atMs === "string" + ) { + next.kind = "at"; + } else if (everyMs !== undefined) { + next.kind = "every"; + } else if (normalizedExpr) { + next.kind = "cron"; } const parsedAtIso = parsedAtMs !== null ? timestampMsToIsoString(parsedAtMs) : undefined; diff --git a/src/cron/run-log-jsonl.ts b/src/cron/run-log-jsonl.ts index 126c85927c3..e4a56ad9ce8 100644 --- a/src/cron/run-log-jsonl.ts +++ b/src/cron/run-log-jsonl.ts @@ -1,6 +1,6 @@ +import { normalizeOptionalString } from "../../packages/normalization-core/src/string-coerce.js"; import type { FailoverReason } from "../agents/embedded-agent-helpers/types.js"; import { resolveFailoverReasonFromError } from "../agents/failover-error.js"; -import { normalizeOptionalString } from "../../packages/normalization-core/src/string-coerce.js"; import { normalizeCronRunDiagnostics } from "./run-diagnostics.js"; import type { CronRunLogEntry } from "./run-log-types.js"; import type { CronDeliveryStatus } from "./types.js"; @@ -38,8 +38,8 @@ export function parseCronRunLogEntriesFromJsonl( } const parsed: CronRunLogEntry[] = []; const lines = raw.split("\n"); - for (let i = 0; i < lines.length; i++) { - const line = lines[i]?.trim(); + for (const rawLine of lines) { + const line = rawLine.trim(); if (!line) { continue; } diff --git a/src/gateway/chat-display-projection.ts b/src/gateway/chat-display-projection.ts index d7ea6b05263..58d47ae6272 100644 --- a/src/gateway/chat-display-projection.ts +++ b/src/gateway/chat-display-projection.ts @@ -784,8 +784,7 @@ function mirrorMessageToolVisibleReplies(messages: unknown[]): unknown[] { clearPending(); }; - for (let i = 0; i < messages.length; i++) { - const message = messages[i]; + for (const message of messages) { const record = readRecord(message); if (!record) { next.push(message); diff --git a/src/gateway/server-methods/artifacts.ts b/src/gateway/server-methods/artifacts.ts index 61b475c61e4..4bf7c712c52 100644 --- a/src/gateway/server-methods/artifacts.ts +++ b/src/gateway/server-methods/artifacts.ts @@ -164,8 +164,7 @@ function estimateBase64Size(value: string | undefined): number | undefined { } let encodedLength = 0; let padding = 0; - for (let index = 0; index < value.length; index += 1) { - const char = value[index]; + for (const char of value) { if (!char || isBase64Whitespace(char)) { continue; } diff --git a/src/infra/command-explainer/extract.ts b/src/infra/command-explainer/extract.ts index 8fb4070479e..153534b8183 100644 --- a/src/infra/command-explainer/extract.ts +++ b/src/infra/command-explainer/extract.ts @@ -262,9 +262,7 @@ function appendDecodedText( sourceEndOffset: number, ): void { decoded.value += value; - for (let index = 0; index < value.length; index += 1) { - decoded.sourceOffsets.push(sourceEndOffset); - } + decoded.sourceOffsets.push(...Array.from({ length: value.length }, () => sourceEndOffset)); } function identityDecodedShellText(text: string, sourceOffset = 0): DecodedShellText { diff --git a/src/infra/exec-approvals-analysis.ts b/src/infra/exec-approvals-analysis.ts index 1d4992fdc43..a24cf88a597 100644 --- a/src/infra/exec-approvals-analysis.ts +++ b/src/infra/exec-approvals-analysis.ts @@ -225,40 +225,38 @@ function splitShellPipeline(command: string): { ok: boolean; reason?: string; se if (line === current.delimiter) { pendingHeredocs.shift(); } - } else { + } else if (line === current.delimiter && unquotedHeredocLogicalChunks.length === 0) { // An unquoted heredoc body whose previous physical line ended with // `\` is spliced into the next line at runtime. In that // case bash does not treat the next physical line as the delimiter, // even if it matches literally — the splice wins and the body // continues. Only recognize the delimiter when no continuation is // pending. - if (line === current.delimiter && unquotedHeredocLogicalChunks.length === 0) { - pendingHeredocs.shift(); - } else { - const continued = stripUnquotedHeredocLineContinuation(line); - unquotedHeredocLogicalChunks.push(continued.line); - if (unquotedHeredocLogicalChunks.length > MAX_UNQUOTED_HEREDOC_CONTINUATION_LINES) { - return { - ok: false, - reason: "heredoc continuation too long", - segments: [], - }; - } - unquotedHeredocLogicalLength += continued.line.length; - if (unquotedHeredocLogicalLength > MAX_UNQUOTED_HEREDOC_LOGICAL_LINE_LENGTH) { - return { - ok: false, - reason: "heredoc logical line too large", - segments: [], - }; - } - if (!continued.continues) { - if (hasUnquotedHeredocExpansionToken(unquotedHeredocLogicalChunks.join(""))) { - return { ok: false, reason: "shell expansion in unquoted heredoc", segments: [] }; - } - unquotedHeredocLogicalChunks = []; - unquotedHeredocLogicalLength = 0; + pendingHeredocs.shift(); + } else { + const continued = stripUnquotedHeredocLineContinuation(line); + unquotedHeredocLogicalChunks.push(continued.line); + if (unquotedHeredocLogicalChunks.length > MAX_UNQUOTED_HEREDOC_CONTINUATION_LINES) { + return { + ok: false, + reason: "heredoc continuation too long", + segments: [], + }; + } + unquotedHeredocLogicalLength += continued.line.length; + if (unquotedHeredocLogicalLength > MAX_UNQUOTED_HEREDOC_LOGICAL_LINE_LENGTH) { + return { + ok: false, + reason: "heredoc logical line too large", + segments: [], + }; + } + if (!continued.continues) { + if (hasUnquotedHeredocExpansionToken(unquotedHeredocLogicalChunks.join(""))) { + return { ok: false, reason: "shell expansion in unquoted heredoc", segments: [] }; } + unquotedHeredocLogicalChunks = []; + unquotedHeredocLogicalLength = 0; } } } diff --git a/src/llm/providers/transform-messages.ts b/src/llm/providers/transform-messages.ts index f4f8dbde6da..b262c0c047c 100644 --- a/src/llm/providers/transform-messages.ts +++ b/src/llm/providers/transform-messages.ts @@ -187,9 +187,7 @@ export function transformMessages( } }; - for (let i = 0; i < transformed.length; i++) { - const msg = transformed[i]; - + for (const msg of transformed) { if (msg.role === "assistant") { // If we have pending orphaned tool calls from a previous assistant, insert synthetic results now insertSyntheticToolResults(); diff --git a/src/media/png-encode.ts b/src/media/png-encode.ts index cc170abc7c0..1e849ff2188 100644 --- a/src/media/png-encode.ts +++ b/src/media/png-encode.ts @@ -19,8 +19,8 @@ const CRC_TABLE = (() => { /** Compute CRC32 checksum for a buffer (used in PNG chunk encoding). */ function crc32(buf: Buffer): number { let crc = 0xffffffff; - for (let i = 0; i < buf.length; i += 1) { - crc = CRC_TABLE[(crc ^ buf[i]) & 0xff] ^ (crc >>> 8); + for (const byte of buf) { + crc = CRC_TABLE[(crc ^ byte) & 0xff] ^ (crc >>> 8); } return (crc ^ 0xffffffff) >>> 0; } diff --git a/src/plugins/contracts/tts-contract-suites.ts b/src/plugins/contracts/tts-contract-suites.ts index ec7481b7d78..29808358e85 100644 --- a/src/plugins/contracts/tts-contract-suites.ts +++ b/src/plugins/contracts/tts-contract-suites.ts @@ -1277,10 +1277,8 @@ export function describeTtsAutoApplyContract() { expect(fetchMock).toHaveBeenCalledTimes(params.expectedFetchCalls); if (params.expectSamePayload) { expect(result).toBe(params.payload); - } else { - if (typeof result.mediaUrl !== "string" || result.mediaUrl.length === 0) { - throw new Error("expected auto TTS to attach mediaUrl"); - } + } else if (typeof result.mediaUrl !== "string" || result.mediaUrl.length === 0) { + throw new Error("expected auto TTS to attach mediaUrl"); } }); } diff --git a/src/secrets/channel-env-vars.ts b/src/secrets/channel-env-vars.ts index 0a4841eaede..da32fa6bbb4 100644 --- a/src/secrets/channel-env-vars.ts +++ b/src/secrets/channel-env-vars.ts @@ -59,5 +59,5 @@ export function getChannelEnvVars(channelId: string, params?: ChannelEnvVarLooku } export function listKnownChannelEnvVarNames(params?: ChannelEnvVarLookupParams): string[] { - return uniqueStrings(Object.values(resolveChannelEnvVars(params)).flatMap((keys) => keys)); + return uniqueStrings(Object.values(resolveChannelEnvVars(params)).flat()); } diff --git a/src/secrets/provider-env-vars.ts b/src/secrets/provider-env-vars.ts index 20e77704024..5de1cddca4a 100644 --- a/src/secrets/provider-env-vars.ts +++ b/src/secrets/provider-env-vars.ts @@ -438,13 +438,13 @@ export function getProviderEnvVars( // remain available to child bridge/runtime processes. export function listKnownProviderAuthEnvVarNames(params?: ProviderEnvVarLookupParams): string[] { return uniqueStrings([ - ...Object.values(resolveProviderAuthEnvVarCandidates(params)).flatMap((keys) => keys), - ...Object.values(resolveProviderEnvVars(params)).flatMap((keys) => keys), + ...Object.values(resolveProviderAuthEnvVarCandidates(params)).flat(), + ...Object.values(resolveProviderEnvVars(params)).flat(), ]); } export function listKnownSecretEnvVarNames(params?: ProviderEnvVarLookupParams): string[] { - return uniqueStrings(Object.values(resolveProviderEnvVars(params)).flatMap((keys) => keys)); + return uniqueStrings(Object.values(resolveProviderEnvVars(params)).flat()); } export function omitEnvKeysCaseInsensitive( diff --git a/src/skills/loading/workspace.ts b/src/skills/loading/workspace.ts index 16426da8477..e9f174fe99e 100644 --- a/src/skills/loading/workspace.ts +++ b/src/skills/loading/workspace.ts @@ -291,8 +291,7 @@ function containsDiscoverableSkill( ): boolean { const discoveryBudget = createSkillDiscoveryBudget(opts.maxCandidateDirs); const queue: Array<{ dir: string; depth: number }> = [{ dir, depth: 0 }]; - for (let index = 0; index < queue.length; index += 1) { - const candidate = queue[index]; + for (const candidate of queue) { if (!candidate) { continue; } @@ -520,8 +519,7 @@ export function resolveNestedSkillsRoot( // child-directory filter as discovery so ignored folders cannot re-root. const discoveryBudget = createSkillDiscoveryBudget(scanLimit); const queue: Array<{ dir: string; depth: number }> = [{ dir: nested, depth: 0 }]; - for (let index = 0; index < queue.length; index += 1) { - const candidate = queue[index]; + for (const candidate of queue) { if (!candidate) { continue; } @@ -999,8 +997,7 @@ function loadSkillEntries( }), ); - for (let queueIndex = 0; queueIndex < scanQueue.length; queueIndex += 1) { - const candidate = scanQueue[queueIndex]; + for (const candidate of scanQueue) { if (!candidate) { continue; } diff --git a/src/skills/runtime/refresh.ts b/src/skills/runtime/refresh.ts index 2c586cd7558..e6f7b9cbcd0 100644 --- a/src/skills/runtime/refresh.ts +++ b/src/skills/runtime/refresh.ts @@ -243,7 +243,7 @@ function addTrustedSymlinkSkillWatchTargets( let watched = 0; let directoryScans = 0; let rawEntries = 0; - for (let queueIndex = 0; queueIndex < queue.length; queueIndex += 1) { + for (const queued of queue) { if ( watched >= MAX_SYMLINK_WATCH_TARGETS_PER_ROOT || directoryScans >= MAX_SYMLINK_WATCH_DIRECTORY_SCANS_PER_ROOT || @@ -251,7 +251,7 @@ function addTrustedSymlinkSkillWatchTargets( ) { break; } - const current = queue[queueIndex]; + const current = queued; if (!current) { continue; } diff --git a/src/tui/tui-event-handlers.ts b/src/tui/tui-event-handlers.ts index c36d86e1ae2..7195afea6ba 100644 --- a/src/tui/tui-event-handlers.ts +++ b/src/tui/tui-event-handlers.ts @@ -350,10 +350,8 @@ export function createEventHandlers(context: EventHandlerContext) { if (params.wasActiveRun) { setActivityStatus(params.status); clearStreamingWatchdog(); - } else { - if (streamingWatchdogRunId === params.runId) { - clearStreamingWatchdog(); - } + } else if (streamingWatchdogRunId === params.runId) { + clearStreamingWatchdog(); } void refreshSessionInfo?.(); }; diff --git a/test/scripts/oxlint-config.test.ts b/test/scripts/oxlint-config.test.ts index f816ac824cb..04ed4bc88b0 100644 --- a/test/scripts/oxlint-config.test.ts +++ b/test/scripts/oxlint-config.test.ts @@ -23,8 +23,10 @@ const ZERO_BASELINE_RULES = [ "eslint/no-self-compare", "eslint/no-var", "eslint/no-implicit-coercion", + "eslint/no-useless-rename", "eslint/no-new-wrappers", "eslint/no-else-return", + "eslint/no-lonely-if", "eslint/no-case-declarations", "eslint/prefer-exponentiation-operator", "eslint/prefer-numeric-literals", @@ -43,6 +45,7 @@ const ZERO_BASELINE_RULES = [ "typescript/no-non-null-asserted-nullish-coalescing", "typescript/no-unnecessary-qualifier", "typescript/prefer-find", + "typescript/prefer-for-of", "typescript/prefer-function-type", "typescript/prefer-includes", "typescript/prefer-reduce-type-parameter", @@ -57,6 +60,7 @@ const ZERO_BASELINE_RULES = [ "unicorn/no-typeof-undefined", "unicorn/no-useless-error-capture-stack-trace", "unicorn/no-zero-fractions", + "unicorn/prefer-array-flat", "unicorn/prefer-array-some", "unicorn/prefer-dom-node-text-content", "unicorn/prefer-keyboard-event-key", diff --git a/ui/src/ui/app-render.ts b/ui/src/ui/app-render.ts index e6ca1459617..55dd57a6e9b 100644 --- a/ui/src/ui/app-render.ts +++ b/ui/src/ui/app-render.ts @@ -2672,17 +2672,15 @@ export function renderApp(state: AppViewState) { const { basePath, existing } = modelEntry; if (!modelId) { removeConfigFormValue(state, basePath); + } else if (existing && typeof existing === "object" && !Array.isArray(existing)) { + const fallbacks = (existing as { fallbacks?: unknown }).fallbacks; + const next = { + primary: modelId, + ...(Array.isArray(fallbacks) ? { fallbacks } : {}), + }; + updateConfigFormValue(state, basePath, next); } else { - if (existing && typeof existing === "object" && !Array.isArray(existing)) { - const fallbacks = (existing as { fallbacks?: unknown }).fallbacks; - const next = { - primary: modelId, - ...(Array.isArray(fallbacks) ? { fallbacks } : {}), - }; - updateConfigFormValue(state, basePath, next); - } else { - updateConfigFormValue(state, basePath, modelId); - } + updateConfigFormValue(state, basePath, modelId); } void refreshVisibleToolsEffectiveForCurrentSession(state); }, diff --git a/ui/src/ui/markdown.ts b/ui/src/ui/markdown.ts index 3f0d64ac197..0978bbadc32 100644 --- a/ui/src/ui/markdown.ts +++ b/ui/src/ui/markdown.ts @@ -349,13 +349,11 @@ md.linkify.add("www", { if (c === open) { balance[close] = balance[close] === 0 ? 1 : 0; } - } else { + } else if (c === open) { // Distinct open/close (e.g., ()) - if (c === open) { - balance[close]++; - } else if (c === close) { - balance[close]--; - } + balance[close]++; + } else if (c === close) { + balance[close]--; } } } @@ -394,13 +392,11 @@ md.linkify.add("www", { len--; continue; } - } else { + } else if (balance[ch] < 0) { // Distinct pair: strip if more closes than opens - if (balance[ch] < 0) { - balance[ch]++; - len--; - continue; - } + balance[ch]++; + len--; + continue; } } break; diff --git a/ui/src/ui/uuid.ts b/ui/src/ui/uuid.ts index c6c22282f03..ffa1c8ff4b9 100644 --- a/ui/src/ui/uuid.ts +++ b/ui/src/ui/uuid.ts @@ -10,8 +10,8 @@ function uuidFromBytes(bytes: Uint8Array): string { bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant 1 let hex = ""; - for (let i = 0; i < bytes.length; i++) { - hex += bytes[i].toString(16).padStart(2, "0"); + for (const byte of bytes) { + hex += byte.toString(16).padStart(2, "0"); } return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice( diff --git a/ui/src/ui/views/chat.ts b/ui/src/ui/views/chat.ts index 9777d59d40c..0c1ecccfe63 100644 --- a/ui/src/ui/views/chat.ts +++ b/ui/src/ui/views/chat.ts @@ -503,8 +503,7 @@ function handlePaste(e: ClipboardEvent, props: ChatProps) { return; } const imageItems: DataTransferItem[] = []; - for (let i = 0; i < items.length; i++) { - const item = items[i]; + for (const item of items) { if (item.type.startsWith("image/")) { imageItems.push(item); }