chore(lint): enable unnecessary type parameter rule

This commit is contained in:
Peter Steinberger
2026-04-18 18:28:20 +01:00
parent 630f2bcabe
commit df525b90f2
94 changed files with 186 additions and 152 deletions

View File

@@ -22,6 +22,7 @@ async function normalizeUploadPaths(paths: string[]): Promise<string[]> {
return result.paths;
}
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Browser request result type is shared between request and success formatter.
async function runBrowserPostAction<T>(params: {
parent: BrowserParentOpts;
profile: string | undefined;

View File

@@ -126,6 +126,7 @@ async function readBrowserProxyFile(filePath: string): Promise<BrowserProxyFile
return { path: filePath, base64: buffer.toString("base64"), mimeType };
}
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- CLI JSON params are typed by the invoked method.
function decodeParams<T>(raw?: string | null): T {
if (!raw) {
throw new Error("INVALID_REQUEST: paramsJSON required");

View File

@@ -118,7 +118,7 @@ export async function handleDiscordMessagingAction(
: accountId
? { accountId }
: undefined;
const withReactionRuntimeOptions = <T extends Record<string, unknown>>(extra?: T) => ({
const withReactionRuntimeOptions = (extra?: Record<string, unknown>) => ({
...(reactionRuntimeOptions ?? cfgOptions),
...extra,
});

View File

@@ -311,6 +311,7 @@ export function createMentionRequiredGuildConfig(overrides?: Partial<Config>): C
}
export function captureNextDispatchCtx<
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Test helper lets assertions ascribe captured dispatch context shape.
T extends {
SessionKey?: string;
ParentSessionKey?: string;

View File

@@ -6,8 +6,8 @@ type ReplyThreadingContext = {
ReplyThreading?: ReplyThreadingPolicy;
};
export function applyImplicitReplyBatchGate<T extends object>(
ctx: T,
export function applyImplicitReplyBatchGate(
ctx: object,
replyToMode: ReplyToMode,
isBatched: boolean,
) {
@@ -15,5 +15,5 @@ export function applyImplicitReplyBatchGate<T extends object>(
if (!replyThreading) {
return;
}
(ctx as T & ReplyThreadingContext).ReplyThreading = replyThreading;
(ctx as ReplyThreadingContext).ReplyThreading = replyThreading;
}

View File

@@ -15,6 +15,7 @@ type DiscordSendModule = typeof import("./send.js");
type DiscordSendComponentsModule = typeof import("./send.components.js");
type DiscordThreadBindingsModule = typeof import("./monitor/thread-bindings.js");
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Test helper preserves mock call and result types.
function invokeMock<TArgs extends unknown[], TResult>(
mock: (...args: unknown[]) => unknown,
...args: TArgs

View File

@@ -205,6 +205,7 @@ export function mockResolvedDiscordAccountConfig(overrides: Record<string, unkno
}));
}
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Test helper lets assertions ascribe handler params shape.
export function getFirstDiscordMessageHandlerParams<T extends object>() {
expect(createDiscordMessageHandlerMock).toHaveBeenCalledTimes(1);
const firstCall = createDiscordMessageHandlerMock.mock.calls.at(0) as [T] | undefined;

View File

@@ -558,7 +558,10 @@ export function registerFeishuBitableTools(api: OpenClawPluginApi) {
const getClient = (params: AccountAwareParams | undefined, defaultAccountId?: string) =>
createFeishuToolClient({ api, executeParams: params, defaultAccountId });
const registerBitableTool = <TParams extends AccountAwareParams>(params: {
const registerBitableTool = <
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Tool params bind each schema-specific executor to its registered tool.
TParams extends AccountAwareParams,
>(params: {
name: string;
label: string;
description: string;

View File

@@ -413,13 +413,13 @@ async function loadMonitorSingleAccount() {
return module.monitorSingleAccount;
}
export async function setupFeishuLifecycleHandler<T extends RuntimeEnv>(params: {
export async function setupFeishuLifecycleHandler(params: {
createEventDispatcherMock: {
mockReturnValue: (value: unknown) => unknown;
mockReturnValueOnce: (value: unknown) => unknown;
};
onRegister: (registered: Record<string, (data: unknown) => Promise<void>>) => void;
runtime: T;
runtime: RuntimeEnv;
cfg: ClawdbotConfig;
account: ResolvedFeishuAccount;
handlerKey: string;

View File

@@ -32,11 +32,11 @@ type DeviceTokenResponse =
error_uri?: string;
};
function parseJsonResponse<T>(value: unknown): T {
function parseJsonResponse(value: unknown): Record<string, unknown> {
if (!value || typeof value !== "object") {
throw new Error("Unexpected response from GitHub");
}
return value as T;
return value as Record<string, unknown>;
}
async function requestDeviceCode(params: { scope: string }): Promise<DeviceCodeResponse> {
@@ -58,7 +58,7 @@ async function requestDeviceCode(params: { scope: string }): Promise<DeviceCodeR
throw new Error(`GitHub device code failed: HTTP ${res.status}`);
}
const json = parseJsonResponse<DeviceCodeResponse>(await res.json());
const json = parseJsonResponse(await res.json()) as DeviceCodeResponse;
if (!json.device_code || !json.user_code || !json.verification_uri) {
throw new Error("GitHub device code response missing fields");
}
@@ -90,7 +90,7 @@ async function pollForAccessToken(params: {
throw new Error(`GitHub device token failed: HTTP ${res.status}`);
}
const json = parseJsonResponse<DeviceTokenResponse>(await res.json());
const json = parseJsonResponse(await res.json()) as DeviceTokenResponse;
if ("access_token" in json && typeof json.access_token === "string") {
return json.access_token;
}

View File

@@ -22,12 +22,12 @@ vi.mock("openclaw/plugin-sdk/ssrf-runtime", async (importOriginal) => {
});
describe("lmstudio-models", () => {
const asFetch = <T>(mock: T) => mock as unknown as typeof fetch;
const parseJsonRequestBody = <T>(init: RequestInit | undefined): T => {
const asFetch = (mock: unknown) => mock as typeof fetch;
const parseJsonRequestBody = (init: RequestInit | undefined): unknown => {
if (typeof init?.body !== "string") {
throw new Error("Expected request body to be a JSON string");
}
return JSON.parse(init.body) as T;
return JSON.parse(init.body) as unknown;
};
afterEach(() => {
@@ -215,7 +215,7 @@ describe("lmstudio-models", () => {
const loadCall = fetchMock.mock.calls.find((call) => String(call[0]).endsWith("/models/load"));
expect(loadCall).toBeDefined();
const loadInit = loadCall?.[1] as RequestInit;
const loadBody = parseJsonRequestBody<{ context_length: number }>(loadInit);
const loadBody = parseJsonRequestBody(loadInit) as { context_length: number };
expect(loadBody.context_length).toBe(8192);
});
@@ -258,7 +258,7 @@ describe("lmstudio-models", () => {
const loadCall = fetchMock.mock.calls.find((call) => String(call[0]).endsWith("/models/load"));
expect(loadCall).toBeDefined();
const loadInit = loadCall?.[1] as RequestInit;
const loadBody = parseJsonRequestBody<{ context_length: number }>(loadInit);
const loadBody = parseJsonRequestBody(loadInit) as { context_length: number };
expect(loadBody.context_length).toBe(32768);
});
@@ -318,7 +318,7 @@ describe("lmstudio-models", () => {
}),
});
const loadInit = loadCall![1] as RequestInit;
const loadBody = parseJsonRequestBody<{ context_length: number }>(loadInit);
const loadBody = parseJsonRequestBody(loadInit) as { context_length: number };
expect(loadBody.context_length).not.toBe(LMSTUDIO_DEFAULT_LOAD_CONTEXT_LENGTH);
});
@@ -360,7 +360,7 @@ describe("lmstudio-models", () => {
const loadCall = fetchMock.mock.calls.find((call) => String(call[0]).endsWith("/models/load"));
expect(loadCall).toBeDefined();
const loadInit = loadCall?.[1] as unknown as RequestInit;
const loadBody = parseJsonRequestBody<{ context_length: number }>(loadInit);
const loadBody = parseJsonRequestBody(loadInit) as { context_length: number };
expect(loadBody.context_length).toBe(8192);
});

View File

@@ -23,6 +23,7 @@ const previousMatrixEnv = Object.fromEntries(
MATRIX_ENV_KEYS.map((key) => [key, process.env[key]]),
) as Record<(typeof MATRIX_ENV_KEYS)[number], string | undefined>;
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Test helper lets callers ascribe plugin runtime shape.
function createNonExitingTypedRuntimeEnv<TRuntime>(): TRuntime {
return {
log: vi.fn(),

View File

@@ -124,8 +124,6 @@ export async function runMemoryEmbeddingRetryLoop<T>(params: {
}
}
export function buildTextEmbeddingInputs<T extends MemoryEmbeddingChunk>(
chunks: T[],
): MemoryEmbeddingInput[] {
export function buildTextEmbeddingInputs(chunks: MemoryEmbeddingChunk[]): MemoryEmbeddingInput[] {
return chunks.map((chunk) => chunk.embeddingInput ?? { text: chunk.text });
}

View File

@@ -7,6 +7,7 @@ import {
} from "./api.js";
import { contributeMistralResolvedModelCompat } from "./provider-compat.js";
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Test helper lets assertions ascribe provider compat shape.
function readCompat<T>(model: unknown): T | undefined {
return (model as { compat?: T }).compat;
}

View File

@@ -119,12 +119,12 @@ export function buildMSTeamsGraphMessageUrls(params: {
return Array.from(new Set(urls));
}
async function fetchGraphCollection<T>(params: {
async function fetchGraphCollection(params: {
url: string;
accessToken: string;
fetchFn?: typeof fetch;
ssrfPolicy?: SsrFPolicy;
}): Promise<{ status: number; items: T[] }> {
}): Promise<{ status: number; items: unknown[] }> {
const fetchFn = params.fetchFn ?? fetch;
const { response, release } = await fetchWithSsrFGuard({
url: params.url,
@@ -141,7 +141,7 @@ async function fetchGraphCollection<T>(params: {
return { status, items: [] };
}
try {
const data = (await response.json()) as { value?: T[] };
const data = (await response.json()) as { value?: unknown[] };
return { status, items: Array.isArray(data.value) ? data.value : [] };
} catch {
return { status, items: [] };
@@ -182,12 +182,12 @@ async function downloadGraphHostedContent(params: {
ssrfPolicy?: SsrFPolicy;
logger?: MSTeamsAttachmentDownloadLogger;
}): Promise<{ media: MSTeamsInboundMedia[]; status: number; count: number }> {
const hosted = await fetchGraphCollection<GraphHostedContent>({
const hosted = (await fetchGraphCollection({
url: `${params.messageUrl}/hostedContents`,
accessToken: params.accessToken,
fetchFn: params.fetchFn,
ssrfPolicy: params.ssrfPolicy,
});
})) as { status: number; items: GraphHostedContent[] };
if (hosted.items.length === 0) {
return { media: [], status: hosted.status, count: 0 };
}

View File

@@ -83,7 +83,7 @@ function graphCollection<T>(...items: T[]) {
return { value: items };
}
function mockGraphCollection<T>(...items: T[]) {
function mockGraphCollection(...items: unknown[]) {
mockJsonFetchResponse(graphCollection(...items));
}

View File

@@ -160,7 +160,7 @@ function writeMatrixQaProgress(message: string) {
process.stderr.write(`[matrix-qa] ${message}\n`);
}
function countMatrixQaStatuses<T extends { status: "fail" | "pass" | "skip" }>(entries: T[]) {
function countMatrixQaStatuses(entries: Array<{ status: "fail" | "pass" | "skip" }>) {
return {
failed: entries.filter((entry) => entry.status === "fail").length,
passed: entries.filter((entry) => entry.status === "pass").length,

View File

@@ -379,10 +379,8 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount, SlackProbe> = crea
}),
resolver: {
resolveTargets: async ({ cfg, accountId, inputs, kind }) => {
const toResolvedTarget = <
T extends { input: string; resolved: boolean; id?: string; name?: string },
>(
entry: T,
const toResolvedTarget = (
entry: { input: string; resolved: boolean; id?: string; name?: string },
note?: string,
) => ({
input: entry.input,

View File

@@ -65,7 +65,10 @@ type SlackSocketShutdownClient = {
};
type Constructor = abstract new (...args: never[]) => unknown;
function isConstructorFunction<T extends Constructor>(value: unknown): value is T {
function isConstructorFunction<
// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Constructor guard preserves the requested concrete Slack constructor type.
T extends Constructor,
>(value: unknown): value is T {
return typeof value === "function";
}

View File

@@ -36,7 +36,7 @@ export type XaiCodeExecutionResult = {
export function resolveXaiCodeExecutionConfig(
config?: Record<string, unknown>,
): XaiCodeExecutionConfig {
return coerceXaiToolConfig<XaiCodeExecutionConfig>(config);
return coerceXaiToolConfig(config) as XaiCodeExecutionConfig;
}
export function resolveXaiCodeExecutionModel(config?: Record<string, unknown>): string {

View File

@@ -3,17 +3,17 @@ import { normalizeXaiModelId } from "../model-id.js";
export { isRecord };
export function coerceXaiToolConfig<TConfig extends Record<string, unknown>>(
export function coerceXaiToolConfig(
config: Record<string, unknown> | undefined,
): TConfig {
return isRecord(config) ? (config as TConfig) : ({} as TConfig);
): Record<string, unknown> {
return isRecord(config) ? config : {};
}
export function resolveNormalizedXaiToolModel(params: {
config?: Record<string, unknown>;
defaultModel: string;
}): string {
const value = coerceXaiToolConfig<{ model?: unknown }>(params.config).model;
const value = coerceXaiToolConfig(params.config).model;
return typeof value === "string" && value.trim()
? normalizeXaiModelId(value.trim())
: params.defaultModel;
@@ -23,7 +23,7 @@ export function resolvePositiveIntegerToolConfig(
config: Record<string, unknown> | undefined,
key: string,
): number | undefined {
const raw = coerceXaiToolConfig<Record<string, unknown>>(config)[key];
const raw = coerceXaiToolConfig(config)[key];
if (typeof raw !== "number" || !Number.isFinite(raw)) {
return undefined;
}

View File

@@ -38,7 +38,7 @@ export type XaiXSearchResult = {
};
export function resolveXaiXSearchConfig(config?: Record<string, unknown>): XaiXSearchConfig {
return coerceXaiToolConfig<XaiXSearchConfig>(config);
return coerceXaiToolConfig(config) as XaiXSearchConfig;
}
export function resolveXaiXSearchModel(config?: Record<string, unknown>): string {