refactor: replaced ReturnType<typeof setTimeout> with Timer type

- Replaced usage of `ReturnType<typeof setTimeout>` and `ReturnType<typeof setInterval>` with the explicit `Timer` type across the codebase.
- Updated several type definitions and function signatures to use concrete types instead of inferred return types for improved clarity and maintainability.
This commit is contained in:
can1357
2026-06-19 17:38:07 +02:00
parent 81d9e17881
commit 9478e3cc5c
19 changed files with 38 additions and 30 deletions
+1 -1
View File
@@ -718,7 +718,7 @@ async function runLoopBody(
stepCounter: StepCounter,
streamFn?: StreamFn,
): Promise<void> {
let deadlineTimer: ReturnType<typeof setTimeout> | undefined;
let deadlineTimer: Timer | undefined;
if (config.deadline !== undefined) {
const deadlineAbortController = new AbortController();
const delay = config.deadline - Date.now();
+2 -2
View File
@@ -1,4 +1,4 @@
import { afterEach, describe, expect, it, mock, spyOn } from "bun:test";
import { afterEach, describe, expect, it, type Mock, mock, spyOn } from "bun:test";
import { streamPiNative } from "@oh-my-pi/pi-ai/providers/pi-native-client";
import type {
AssistantMessage,
@@ -288,7 +288,7 @@ describe("streamPiNative event flow", () => {
await expect(stream.result()).rejects.toThrow(/pre-aborted/);
// fetch was never called — short-circuit happened in the abort guard
expect((fetchImpl as unknown as ReturnType<typeof spyOn>).mock.calls.length).toBe(0);
expect((fetchImpl as unknown as Mock<typeof globalThis.fetch>).mock.calls.length).toBe(0);
});
it("forwards the caller's AbortSignal to the underlying fetch", async () => {
@@ -39,7 +39,6 @@ import type { LoadedConfig } from "./config";
## Exceptions
- Timer handles: `ReturnType<typeof setTimeout>` / `setInterval`.
- Generic type utilities where the function is a type parameter.
Concrete function? Export a concrete type.
@@ -177,7 +177,7 @@ export class CustomEditor extends Editor {
/** Per-render scratch flag: did any layout line in this render contain a magic
* keyword that should shimmer? Reset by {@link #scheduleShimmerIfNeeded} each
* time a frame is queued. */
#shimmerTimer: ReturnType<typeof setTimeout> | undefined;
#shimmerTimer: Timer | undefined;
/** Repaint hook the host wires once at construction. Called from the shimmer
* timer to request the next animation frame. Undefined when nobody is
* listening (tests, headless callers); the timer chain still self-cleans. */
@@ -179,9 +179,9 @@ export class ModelSelectorComponent extends Container {
#providers: ProviderTabState[] = STATIC_PROVIDER_TABS;
#activeTabIndex: number = 0;
#refreshingProviders: Set<string> = new Set();
#scheduledProviderRefreshes: Map<string, ReturnType<typeof setTimeout>> = new Map();
#scheduledProviderRefreshes: Map<string, Timer> = new Map();
#refreshSpinnerFrame: number = 0;
#refreshSpinnerInterval?: NodeJS.Timeout;
#refreshSpinnerInterval?: Timer;
// Context menu state
#isMenuOpen: boolean = false;
@@ -142,7 +142,7 @@ export interface LspServerInfo {
*/
export class WelcomeComponent implements Component {
#animStart: number | null = null;
#animTimer: ReturnType<typeof setInterval> | null = null;
#animTimer: Timer | null = null;
#selectedTip: string | undefined;
// Render cache: the welcome box is the first transcript-area component, so
// returning a stable array reference keeps the whole frame prefix stable.
@@ -27,7 +27,7 @@ import {
theme,
} from "../../modes/theme/theme";
import type { InteractiveModeContext } from "../../modes/types";
import type { ResetCreditRedeemOutcome } from "../../session/auth-storage";
import type { ResetCreditAccountStatus, ResetCreditRedeemOutcome } from "../../session/auth-storage";
import type { SessionInfo } from "../../session/session-listing";
import { SessionManager } from "../../session/session-manager";
import { FileSessionStorage } from "../../session/session-storage";
@@ -1161,7 +1161,7 @@ export class SelectorController {
async showResetUsageSelector(): Promise<void> {
const session = this.ctx.session;
this.ctx.showStatus("Checking saved rate-limit resets…", { dim: true });
let statuses: Awaited<ReturnType<typeof session.listResetCredits>>;
let statuses: ResetCreditAccountStatus[];
try {
statuses = await session.listResetCredits();
} catch (error) {
@@ -30,6 +30,7 @@ import {
} from "@oh-my-pi/pi-ai/auth-broker/discover";
import { getAgentDir } from "@oh-my-pi/pi-utils";
import { resolveConfigValue } from "../config/resolve-config-value";
import type { AuthStorage } from "./auth-storage";
export { type AuthBrokerClientConfig, getAuthBrokerTokenFilePath };
@@ -82,7 +83,7 @@ export function resolveAuthBrokerConfig(): Promise<AuthBrokerClientConfig | null
export function discoverAuthStorage(
agentDir: string = getAgentDir(),
options?: Omit<DiscoverAuthStorageOptions, "agentDir" | "configValueResolver">,
): ReturnType<typeof discoverAuthStorageShared> {
): Promise<AuthStorage> {
return discoverAuthStorageShared({
...options,
agentDir,
@@ -1,5 +1,6 @@
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "bun:test";
import * as path from "node:path";
import type { Model } from "@oh-my-pi/pi-ai";
import { Agent } from "@oh-my-pi/pi-agent-core";
import { ModelRegistry } from "@oh-my-pi/pi-coding-agent/config/model-registry";
import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
@@ -41,7 +42,7 @@ type GoalHarness = {
type SharedFixture = {
authStorage: AuthStorage;
modelRegistry: ModelRegistry;
model: NonNullable<ReturnType<ModelRegistry["find"]>>;
model: Model;
baseDir: TempDir;
};
@@ -1,4 +1,4 @@
import { afterEach, beforeAll, describe, expect, it, vi } from "bun:test";
import { afterEach, beforeAll, describe, expect, it, type Mock, vi } from "bun:test";
import { KeybindingsManager } from "@oh-my-pi/pi-coding-agent/config/keybindings";
import { HookEditorComponent } from "@oh-my-pi/pi-coding-agent/modes/components/hook-editor";
import { ExtensionUiController } from "@oh-my-pi/pi-coding-agent/modes/controllers/extension-ui-controller";
@@ -67,8 +67,8 @@ function createControllerContext() {
stop: vi.fn(),
terminal: { columns: 120 },
} as unknown as TestContext["ui"] & {
setFocus: ReturnType<typeof vi.fn>;
requestRender: ReturnType<typeof vi.fn>;
setFocus: Mock;
requestRender: Mock;
};
const ctx = {
editor,
@@ -102,7 +102,7 @@ const flushAsync = (): Promise<void> => new Promise<void>(resolve => setTimeout(
// instead of polling, racing a generous timeout so a stalled regression fails
// loudly rather than hanging.
async function settle(promise: Promise<void>, label: string, timeoutMs = 3000): Promise<void> {
let timer: ReturnType<typeof setTimeout> | undefined;
let timer: Timer | undefined;
const timeout = new Promise<never>((_, reject) => {
timer = setTimeout(() => reject(new Error(`Timed out waiting for ${label}`)), timeoutMs);
});
@@ -5,7 +5,7 @@
* when stopReason is "aborted", which would surface the sentinel to stderr
* (and exit with code 1). This test verifies the guard skips silent-abort.
*/
import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test";
import { afterEach, beforeEach, describe, expect, it, type Mock, vi } from "bun:test";
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
import { runPrintMode } from "@oh-my-pi/pi-coding-agent/modes/print-mode";
import type { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session";
@@ -47,7 +47,7 @@ function createMockSession(messages: AssistantMessage[]): AgentSession {
}
describe("Print-mode silent-abort regression", () => {
let exitSpy: ReturnType<typeof vi.spyOn>;
let exitSpy: Mock<typeof process.exit>;
let stderrOutput: string[];
let stdoutOutput: string[];
@@ -1,4 +1,4 @@
import { afterEach, describe, expect, it, vi } from "bun:test";
import { afterEach, describe, expect, it, type Mock, vi } from "bun:test";
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
import type { Skill } from "@oh-my-pi/pi-coding-agent/extensibility/skills";
import * as skillsModule from "@oh-my-pi/pi-coding-agent/extensibility/skills";
@@ -143,7 +143,7 @@ describe("autoloadSkills in executor", () => {
autoloadSkills: mockSkills,
});
const sendCustomMessage = session.sendCustomMessage as ReturnType<typeof vi.fn>;
const sendCustomMessage = session.sendCustomMessage as Mock;
expect(sendCustomMessage).toHaveBeenCalledTimes(2);
// Verify first skill
@@ -189,7 +189,7 @@ describe("autoloadSkills in executor", () => {
await runSubprocess(baseOptions);
const sendCustomMessage = session.sendCustomMessage as ReturnType<typeof vi.fn>;
const sendCustomMessage = session.sendCustomMessage as Mock;
expect(sendCustomMessage).not.toHaveBeenCalled();
});
@@ -211,7 +211,7 @@ describe("autoloadSkills in executor", () => {
await runSubprocess({ ...baseOptions, autoloadSkills: undefined });
const sendCustomMessage = session.sendCustomMessage as ReturnType<typeof vi.fn>;
const sendCustomMessage = session.sendCustomMessage as Mock;
expect(sendCustomMessage).not.toHaveBeenCalled();
});
@@ -231,7 +231,7 @@ describe("autoloadSkills in executor", () => {
});
// Track sendCustomMessage call order
(session.sendCustomMessage as ReturnType<typeof vi.fn>).mockImplementation(async () => {
(session.sendCustomMessage as Mock).mockImplementation(async () => {
callOrder.push("sendCustomMessage");
});
@@ -6,6 +6,7 @@ import type { AgentToolContext } from "@oh-my-pi/pi-agent-core";
import { getBundledModel } from "@oh-my-pi/pi-catalog/models";
import { Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
import { createAgentSession } from "@oh-my-pi/pi-coding-agent/sdk";
import type { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session";
import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager";
import { Snowflake } from "@oh-my-pi/pi-utils";
@@ -34,7 +35,7 @@ describe("tools.approvalMode setting", () => {
// settings per assertion. This avoids paying createAgentSession's cost (model registry,
// auth-storage discovery, settings init) nine times over.
let tempDir: string;
let session: Awaited<ReturnType<typeof createAgentSession>>["session"];
let session: AgentSession;
beforeAll(async () => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), `pi-approval-mode-${Snowflake.next()}-`));
+7 -2
View File
@@ -180,6 +180,11 @@ async function createPrFixture(): Promise<PrFixture> {
* `getWorktreesDir()` resolves under an isolated temp home instead of the
* user's real `~/.omp/wt`. Returns the temp home and a cleanup hook.
*/
interface TempHome {
home: string;
cleanup: () => Promise<void>;
}
async function setupTempHome(): Promise<{ home: string; cleanup: () => Promise<void> }> {
const home = await fs.mkdtemp(path.join(os.tmpdir(), "gh-pr-tool-home-"));
vi.spyOn(os, "homedir").mockReturnValue(home);
@@ -765,7 +770,7 @@ describe("github tool", () => {
// Arrange the mutable fixture + isolated $HOME once in beforeAll (excluded
// from test-body time); the body only performs the checkout and assertions.
let fixture: PrFixture;
let tempHome: Awaited<ReturnType<typeof setupTempHome>>;
let tempHome: TempHome;
beforeAll(async () => {
fixture = await createPrFixture();
tempHome = await setupTempHome();
@@ -867,7 +872,7 @@ describe("github tool", () => {
describe("pr_checkout (array of pull requests)", () => {
// Same beforeAll-hoisted arrange: the body only runs the array checkout.
let fixture: PrFixture;
let tempHome: Awaited<ReturnType<typeof setupTempHome>>;
let tempHome: TempHome;
beforeAll(async () => {
fixture = await createPrFixture();
tempHome = await setupTempHome();
@@ -78,7 +78,7 @@ function installFakeLsp(handler: FakeLspHandler): FakeLspServer {
const waiters: Array<{
predicate: (message: RpcMessage) => boolean;
resolve: (message: RpcMessage) => void;
timer: ReturnType<typeof setTimeout>;
timer: Timer;
}> = [];
let exitCode: number | null = null;
let killed = false;
@@ -12,6 +12,7 @@ import {
todoMatchesAnyDescription,
todoToolRenderer,
} from "@oh-my-pi/pi-coding-agent/tools";
import type { Component } from "@oh-my-pi/pi-tui";
function createSession(initialPhases: TodoPhase[] = []): ToolSession {
let phases = initialPhases;
@@ -435,7 +436,7 @@ describe("todoToolRenderer.renderResult phase collapsing", () => {
// Beta and Gamma untouched by this update.
return tool.execute("done", { ops: [{ op: "done", task: "a1" }] });
}
function innerLines(component: ReturnType<typeof todoToolRenderer.renderResult>): string[] {
function innerLines(component: Component): string[] {
const lines = Bun.stripANSI(component.render(100).join("\n")).split("\n");
return lines.slice(1, -1).map(line => line.replace(/^│/, "").replace(/│\s*$/, "").trim());
}
@@ -113,7 +113,7 @@ describe("extraction integration", () => {
const callback = handler as (...callbackArgs: unknown[]) => void;
queueMicrotask(() => callback(...args));
}
return 0 as unknown as ReturnType<typeof setTimeout>;
return 0 as unknown as Timer;
}) as typeof setTimeout;
class RateLimitedClient extends ExtractionClient {
+1 -1
View File
@@ -437,7 +437,7 @@ export class ProcessTerminal implements Terminal {
#reportedRows?: number;
#osc11PollTimer?: Timer;
#mode2031DebounceTimer?: Timer;
#progressTimer?: ReturnType<typeof setInterval>;
#progressTimer?: Timer;
get kittyProtocolActive(): boolean {
return this.#kittyProtocolActive;