chore(lint): enable 4 type-aware quick-win rules and fix violations (#2424)

Turns on no-unsafe-enum-comparison, no-base-to-string,
no-redundant-type-constituents, and restrict-template-expressions.
Fixes all 24 violations across 13 source files.
This commit is contained in:
Zero Liu 2026-05-13 21:57:33 -07:00 committed by GitHub
parent f901343583
commit 8658282aa9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 43 additions and 38 deletions

View file

@ -96,11 +96,6 @@ export default [
"@typescript-eslint/unbound-method": "off", // 68 violations
// Enabled in the TS-only block below.
// --- Quick wins: small enough to fix and enable in a single PR ---
"@typescript-eslint/no-unsafe-enum-comparison": "off", // 11 violations
"@typescript-eslint/no-base-to-string": "off", // 7 violations
"@typescript-eslint/no-redundant-type-constituents": "off", // 5 violations
"@typescript-eslint/restrict-template-expressions": "off", // 1 violation
// no-deprecated: defer — surface the warnings, but don't fail CI yet
"@typescript-eslint/no-deprecated": "off",

View file

@ -398,7 +398,7 @@ export class BedrockChatModel extends BaseChatModel<BedrockChatModelCallOptions>
}
}
private safeJsonParse(value: string): any | null {
private safeJsonParse(value: string): any {
try {
return JSON.parse(value);
} catch {

View file

@ -38,7 +38,9 @@ export function buildGitHubCopilotAuthedFetch(
? input
: typeof Request !== "undefined" && input instanceof Request
? input.url
: input.toString();
: input instanceof URL
? input.href
: input.url;
const doRequest = async (token: string): Promise<Response> => {
const copilotHeaders = provider.buildCopilotRequestHeaders(token);

View file

@ -69,7 +69,8 @@ export default class ProjectManager {
const settings = getSettings();
const shouldAutoIndex =
settings.enableSemanticSearchV3 &&
settings.indexVaultToVectorStore === VAULT_VECTOR_STORE_STRATEGY.ON_MODE_SWITCH &&
(settings.indexVaultToVectorStore as VAULT_VECTOR_STORE_STRATEGY) ===
VAULT_VECTOR_STORE_STRATEGY.ON_MODE_SWITCH &&
(getChainType() === ChainType.VAULT_QA_CHAIN ||
getChainType() === ChainType.COPILOT_PLUS_CHAIN);
void this.getCurrentChainManager().createChainWithNewModel({
@ -936,7 +937,7 @@ modified: ${stat ? new Date(stat.mtime).toISOString() : "unknown"}`;
await this.retryNonMarkdownFile(project, failedItem.path);
break;
default:
logWarn(`[retryFailedItem] Unknown item type: ${failedItem.type}`);
logWarn(`[retryFailedItem] Unknown item type: ${String(failedItem.type)}`);
return;
}

View file

@ -47,7 +47,8 @@ export function ModelParametersEditor({
onReset,
showTokenLimit = true,
}: ModelParametersEditorProps) {
const isOllamaModel = model.provider === ChatModelProviders.OLLAMA;
const provider = model.provider as ChatModelProviders;
const isOllamaModel = provider === ChatModelProviders.OLLAMA;
// Parameter values: model.xxx ?? settings.xxx
const temperature = model.temperature ?? settings.temperature;
@ -66,7 +67,7 @@ export function ModelParametersEditor({
model.name.startsWith("o3") ||
model.name.startsWith("o4") ||
model.name.startsWith("gpt-5")) &&
model.provider === ChatModelProviders.OPENAI;
provider === ChatModelProviders.OPENAI;
// Check if model has REASONING capability enabled
const hasReasoningCapability = model.capabilities?.includes(ModelCapability.REASONING) ?? false;
@ -74,12 +75,11 @@ export function ModelParametersEditor({
// Show reasoning effort for: OpenAI reasoning models, OpenRouter, LM Studio, or any model with REASONING capability
const showReasoningEffort =
isOpenAIReasoningModel ||
model.provider === ChatModelProviders.OPENROUTERAI ||
provider === ChatModelProviders.OPENROUTERAI ||
model.provider === "lm_studio" ||
model.provider === ChatModelProviders.LM_STUDIO ||
provider === ChatModelProviders.LM_STUDIO ||
hasReasoningCapability;
const showVerbosity =
model.name.startsWith("gpt-5") && model.provider === ChatModelProviders.OPENAI;
const showVerbosity = model.name.startsWith("gpt-5") && provider === ChatModelProviders.OPENAI;
return (
<div className="tw-space-y-4">

View file

@ -38,7 +38,7 @@ jest.mock("@/utils", () => ({
.join("");
}
if (content && typeof content === "object" && "text" in content) {
return String((content as { text?: unknown }).text ?? "");
return String((content as { text?: string }).text ?? "");
}
return String(content ?? "");
}),

View file

@ -848,6 +848,15 @@ ${chatContent}`;
}
}
/**
* Convert an unknown error to a safe lowercase string for substring matching.
*/
private errorToMessage(error: unknown): string {
if (error instanceof Error) return error.message;
if (typeof error === "string") return error;
return JSON.stringify(error);
}
/**
* Determine whether an error corresponds to an ENAMETOOLONG filesystem failure.
* @param error - The thrown error.
@ -858,8 +867,7 @@ ${chatContent}`;
return false;
}
const message = error instanceof Error ? error.message : String(error);
const normalized = message.toLowerCase();
const normalized = this.errorToMessage(error).toLowerCase();
return normalized.includes("enametoolong") || normalized.includes("name too long");
}
@ -870,8 +878,7 @@ ${chatContent}`;
if (!error) {
return false;
}
const message = error instanceof Error ? error.message : String(error);
return message.toLowerCase().includes("already exists");
return this.errorToMessage(error).toLowerCase().includes("already exists");
}
/**

View file

@ -1,8 +1,7 @@
import { type CopilotSettings } from "@/settings/model";
import { Platform } from "obsidian";
// @ts-ignore
let safeStorageInternal: Electron.SafeStorage | null = null;
let safeStorageInternal: any = null;
function getSafeStorage() {
if (Platform.isDesktop && safeStorageInternal) {

View file

@ -324,9 +324,8 @@ export async function migrateProjectsFromSettingsToVault(app: App): Promise<void
if (fmMatch) {
const parsed = parseYaml(fmMatch[1]);
if (parsed && typeof parsed === "object") {
existingId = String(
(parsed as Record<string, unknown>)[COPILOT_PROJECT_ID] ?? ""
).trim();
const raw = (parsed as Record<string, unknown>)[COPILOT_PROJECT_ID];
existingId = (typeof raw === "string" ? raw : "").trim();
}
}
} catch {

View file

@ -368,7 +368,7 @@ export class DBOperations {
};
}
async upsert(docToSave: any): Promise<any | undefined> {
async upsert(docToSave: any): Promise<any> {
if (!this.oramaDb) throw new Error("DB not initialized");
const db = this.oramaDb;

View file

@ -29,11 +29,7 @@ export class ScoreNormalizer {
/**
* Update explanation with normalized scores
*/
private updateExplanation(
explanation: any | undefined,
originalScore: number,
normalizedScore: number
): any | undefined {
private updateExplanation(explanation: any, originalScore: number, normalizedScore: number): any {
if (!explanation) return undefined;
return {

View file

@ -37,7 +37,11 @@ export function toStringSafe(value: unknown): string {
if (value === null || value === undefined) return "";
try {
if (typeof value === "object") return JSON.stringify(value);
return String(value);
if (typeof value === "function") return value.toString();
if (typeof value === "symbol") return value.toString();
if (typeof value === "bigint") return value.toString();
if (typeof value === "number" || typeof value === "boolean") return String(value);
return "";
} catch {
return "";
}

View file

@ -123,7 +123,7 @@ export async function verifyAndAddModel(
// Check if model already exists
const existingModel = activeModels.find(
(m) => m.name === model.name && m.provider === model.provider
(m) => m.name === model.name && (m.provider as SettingKeyProviders) === model.provider
);
const alreadyExists = Boolean(existingModel);

View file

@ -823,7 +823,8 @@ export async function safeFetch(
contentType: "application/json",
headers: headers,
method: method,
...(methodsWithBody.includes(method) && { body: options.body?.toString() }),
...(methodsWithBody.includes(method) &&
typeof options.body === "string" && { body: options.body }),
throw: false, // Don't throw so we can get the response body
});
@ -1194,7 +1195,7 @@ export function isCodexModel(model: BaseChatModel | string): boolean {
export function shouldUseGitHubCopilotResponsesApi(
model: Pick<CustomModel, "provider" | "name" | "useResponsesApi">
): boolean {
if (model.provider !== ChatModelProviders.GITHUB_COPILOT) {
if ((model.provider as ChatModelProviders) !== ChatModelProviders.GITHUB_COPILOT) {
return false;
}
@ -1264,7 +1265,8 @@ export function checkModelApiKey(
hasApiKey: boolean;
errorNotice?: string;
} {
if (model.provider === ChatModelProviders.AMAZON_BEDROCK) {
const provider = model.provider as ChatModelProviders;
if (provider === ChatModelProviders.AMAZON_BEDROCK) {
const apiKey = model.apiKey || settings.amazonBedrockApiKey;
if (!apiKey) {
return {
@ -1279,7 +1281,7 @@ export function checkModelApiKey(
}
// GitHub Copilot uses OAuth, not API key
if (model.provider === ChatModelProviders.GITHUB_COPILOT) {
if (provider === ChatModelProviders.GITHUB_COPILOT) {
const hasAuth = Boolean(
model.apiKey || settings.githubCopilotToken || settings.githubCopilotAccessToken
);
@ -1293,7 +1295,7 @@ export function checkModelApiKey(
return { hasApiKey: true };
}
const needSetKeyPath = !!getNeedSetKeyProvider().find((provider) => provider === model.provider);
const needSetKeyPath = !!getNeedSetKeyProvider().find((p) => p === provider);
const hasNoApiKey = !getApiKeyForProvider(model.provider as SettingKeyProviders, model);
// For Providers that require setting a key in the dialog, an inspection is necessary.