From c9ae17fe3d30cbba2b3ab583cb445762e7d51a00 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Tue, 15 Sep 2026 00:02:04 -0700 Subject: [PATCH] fix(lint): enable anti-slop/no-unknown-type-aliases (#20784) Flips anti-slop/no-unknown-type-aliases from "off" to "error" and fixes the 3 baseline violations. The rule rejects a named type alias whose resolved type is `unknown` (directly, through another alias, through parentheses, or as a member of a union). Such an alias is strictly worse than writing `unknown`: it reads like a real domain type at every use site while accepting anything, so the compiler stops helping and readers are actively misled. `unknown` is fine, but it must stay visible at the boundary that actually parses it. Violations fixed (3 at baseline, 5 source files touched): - src/main/runtime/workspace-session-failed-write-rollback.ts `type RollbackValue = unknown` -> a real recursive JSON-shaped union `RollbackSlot` (primitives | null | undefined | typeof MISSING | readonly RollbackSlot[] | RollbackRecord), with a named `type RollbackRecord = { readonly [key: string]: RollbackSlot }`. The record is a named alias rather than an inline index signature because inline violates typescript/consistent-indexed-object-style, `interface` violates consistent-type-definitions, and `Readonly>` trips TS2456 circular-reference. The named alias satisfies all three. - src/renderer/src/hooks/direct-ssh-reconnect-coordinator-types.ts `type DirectSshReconnectTimer = unknown` -> `ReturnType`, the handle that actually flows. `DirectSshReconnectTargetState.timer` is widened to `DirectSshReconnectTimer | null` to match the state machine, which initializes to null and resets to null in the scheduled callback. - src/renderer/src/hooks/direct-ssh-host-hydration.ts `type HostReadTimer = unknown` -> `ReturnType`. Fix pattern throughout: replace the alias with the type that already flows through the code, never with `any` and never with a relabelled `unknown`. Because the timer aliases are now honest, two pre-existing `as ReturnType` casts at the clearTimeout boundaries could be deleted, a net win under the repo's type-assertion policy. Suppressions added: none. No eslint-disable, oxlint-disable, `any`, or `as` cast was introduced anywhere in this change. The diff is type-annotation-only; no runtime statement changed. --- config/oxlint-anti-slop.json | 2 +- ...workspace-session-failed-write-rollback.ts | 26 ++++++++++++++----- .../src/hooks/direct-ssh-host-hydration.ts | 4 +-- ...ssh-reconnect-coordinator-stabilization.ts | 2 +- .../direct-ssh-reconnect-coordinator-types.ts | 2 +- .../hooks/direct-ssh-reconnect-coordinator.ts | 4 +-- 6 files changed, 25 insertions(+), 15 deletions(-) diff --git a/config/oxlint-anti-slop.json b/config/oxlint-anti-slop.json index 3e48b189302..d2ac1d35d75 100644 --- a/config/oxlint-anti-slop.json +++ b/config/oxlint-anti-slop.json @@ -38,7 +38,7 @@ "anti-slop/no-shape-in-symbol-names": "off", "anti-slop/no-unknown-parameters": "off", "anti-slop/no-unknown-returns": "off", - "anti-slop/no-unknown-type-aliases": "off", + "anti-slop/no-unknown-type-aliases": "error", "anti-slop/no-unsafe-dictionary-type": "off", "anti-slop/no-widen-then-assert": "error", "anti-slop/require-readable-spacing": "off", diff --git a/src/main/runtime/workspace-session-failed-write-rollback.ts b/src/main/runtime/workspace-session-failed-write-rollback.ts index 4f9e79a03d7..7234446cb9d 100644 --- a/src/main/runtime/workspace-session-failed-write-rollback.ts +++ b/src/main/runtime/workspace-session-failed-write-rollback.ts @@ -2,9 +2,21 @@ import { isDeepStrictEqual } from 'node:util' import type { WorkspaceSessionState } from '../../shared/workspace-session-state-types' const MISSING = Symbol('missing') -type RollbackValue = unknown -function isRecord(value: RollbackValue): value is Record { +/** A JSON-shaped slot of persisted session state, or the absent-key sentinel. */ +type RollbackSlot = + | string + | number + | boolean + | null + | undefined + | typeof MISSING + | readonly RollbackSlot[] + | RollbackRecord + +type RollbackRecord = { readonly [key: string]: RollbackSlot } + +function isRecord(value: RollbackSlot): value is RollbackRecord { return ( value !== MISSING && typeof value === 'object' && @@ -15,10 +27,10 @@ function isRecord(value: RollbackValue): value is Record { } function rollbackValue( - original: RollbackValue, - staged: RollbackValue, - current: RollbackValue -): RollbackValue { + original: RollbackSlot, + staged: RollbackSlot, + current: RollbackSlot +): RollbackSlot { if (isDeepStrictEqual(original, staged)) { return current } @@ -29,7 +41,7 @@ function rollbackValue( return current } let changed = false - const next: Record = { ...current } + const next: Record = { ...current } for (const key of new Set([ ...Object.keys(original), ...Object.keys(staged), diff --git a/src/renderer/src/hooks/direct-ssh-host-hydration.ts b/src/renderer/src/hooks/direct-ssh-host-hydration.ts index 419dc134075..2c326501760 100644 --- a/src/renderer/src/hooks/direct-ssh-host-hydration.ts +++ b/src/renderer/src/hooks/direct-ssh-host-hydration.ts @@ -18,7 +18,7 @@ import { directSshAuthoritiesEqual } from './direct-ssh-reconnect-tokens' export const DIRECT_SSH_HOST_READ_TIMEOUT_MS = 5_000 -type HostReadTimer = unknown +type HostReadTimer = ReturnType export type DirectSshHostHydrationDeps = { store: Pick, 'getState' | 'setState'> @@ -121,7 +121,7 @@ export function createDirectSshHostHydration( const setTimer: NonNullable = deps.setTimer ?? ((callback, delayMs) => setTimeout(callback, delayMs)) const clearTimer: NonNullable = - deps.clearTimer ?? ((timer) => clearTimeout(timer as ReturnType)) + deps.clearTimer ?? ((timer) => clearTimeout(timer)) const catalogRevisionByTarget = new Map() const catalogInFlight = new Map>() const pendingDeadlines = new Set<{ timer: HostReadTimer; settle: () => void }>() diff --git a/src/renderer/src/hooks/direct-ssh-reconnect-coordinator-stabilization.ts b/src/renderer/src/hooks/direct-ssh-reconnect-coordinator-stabilization.ts index f20ea1e4a30..9924e55d70b 100644 --- a/src/renderer/src/hooks/direct-ssh-reconnect-coordinator-stabilization.ts +++ b/src/renderer/src/hooks/direct-ssh-reconnect-coordinator-stabilization.ts @@ -5,7 +5,7 @@ export type DirectSshReconnectTargetState = { authority: DirectSshAuthority installedAt: number dampUntil: number | null - timer: DirectSshReconnectTimer + timer: DirectSshReconnectTimer | null } export function createDirectSshReconnectTargetState( diff --git a/src/renderer/src/hooks/direct-ssh-reconnect-coordinator-types.ts b/src/renderer/src/hooks/direct-ssh-reconnect-coordinator-types.ts index 6cc76174ed1..31dfbe5a8ea 100644 --- a/src/renderer/src/hooks/direct-ssh-reconnect-coordinator-types.ts +++ b/src/renderer/src/hooks/direct-ssh-reconnect-coordinator-types.ts @@ -113,7 +113,7 @@ export type DirectSshCoordinatorTelemetry = { damped: boolean } -export type DirectSshReconnectTimer = unknown +export type DirectSshReconnectTimer = ReturnType export type DirectSshReconnectCoordinatorDeps = { scheduler: DirectSshWorktreeRefreshScheduler diff --git a/src/renderer/src/hooks/direct-ssh-reconnect-coordinator.ts b/src/renderer/src/hooks/direct-ssh-reconnect-coordinator.ts index 7ddf3892ff8..01474789f1c 100644 --- a/src/renderer/src/hooks/direct-ssh-reconnect-coordinator.ts +++ b/src/renderer/src/hooks/direct-ssh-reconnect-coordinator.ts @@ -42,9 +42,7 @@ export function createDirectSshReconnectCoordinator( const now = deps.now ?? Date.now const setTimer = deps.setTimer ?? ((callback: () => void, delayMs: number) => setTimeout(callback, delayMs)) - const clearTimer = - deps.clearTimer ?? - ((timer: DirectSshReconnectTimer) => clearTimeout(timer as ReturnType)) + const clearTimer = deps.clearTimer ?? ((timer: DirectSshReconnectTimer) => clearTimeout(timer)) const stabilizationMs = deps.stabilizationMs ?? DIRECT_SSH_RELAY_STABILIZATION_MS const targets = new Map() let stopped = false