diff --git a/packages/cli/src/acp/acpFileSystemService.ts b/packages/cli/src/acp/acpFileSystemService.ts index c11dc7f6cf..3b66d06963 100644 --- a/packages/cli/src/acp/acpFileSystemService.ts +++ b/packages/cli/src/acp/acpFileSystemService.ts @@ -60,8 +60,11 @@ export class AcpFileSystemService implements FileSystemService { sessionId: this.sessionId, }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return response.content; + const content: unknown = response.content; + if (typeof content !== 'string') { + throw new Error('content must be a string'); // replace with other response type formats when modified in the future + } + return content; } catch (err: unknown) { this.normalizeFileSystemError(err); } diff --git a/packages/cli/src/commands/extensions/utils.ts b/packages/cli/src/commands/extensions/utils.ts index 78bad54502..87bacda1ae 100644 --- a/packages/cli/src/commands/extensions/utils.ts +++ b/packages/cli/src/commands/extensions/utils.ts @@ -27,7 +27,7 @@ export interface ConfigLogger { export type RequestSettingCallback = ( setting: ExtensionSetting, -) => Promise; +) => Promise; export type RequestConfirmationCallback = (message: string) => Promise; const defaultLogger: ConfigLogger = { @@ -47,8 +47,7 @@ const defaultRequestConfirmation: RequestConfirmationCallback = async ( message, initial: false, }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return response.confirm; + return typeof response.confirm === 'boolean' ? response.confirm : false; }; export async function getExtensionManager() { diff --git a/packages/cli/src/config/extension-manager.ts b/packages/cli/src/config/extension-manager.ts index ce1a02b876..ded72510fc 100644 --- a/packages/cli/src/config/extension-manager.ts +++ b/packages/cli/src/config/extension-manager.ts @@ -88,7 +88,9 @@ interface ExtensionManagerParams { enabledExtensionOverrides?: string[]; settings: MergedSettings; requestConsent: (consent: string) => Promise; - requestSetting: ((setting: ExtensionSetting) => Promise) | null; + requestSetting: + | ((setting: ExtensionSetting) => Promise) + | null; workspaceDir: string; eventEmitter?: EventEmitter; clientVersion?: string; @@ -106,7 +108,7 @@ export class ExtensionManager extends ExtensionLoader { private settings: MergedSettings; private requestConsent: (consent: string) => Promise; private requestSetting: - | ((setting: ExtensionSetting) => Promise) + | ((setting: ExtensionSetting) => Promise) | undefined; private telemetryConfig: Config; private workspaceDir: string; @@ -161,7 +163,7 @@ export class ExtensionManager extends ExtensionLoader { } setRequestSetting( - requestSetting?: (setting: ExtensionSetting) => Promise, + requestSetting?: (setting: ExtensionSetting) => Promise, ): void { this.requestSetting = requestSetting; } diff --git a/packages/cli/src/config/extensionRegistryClient.ts b/packages/cli/src/config/extensionRegistryClient.ts index 4b47c215ec..7b57196191 100644 --- a/packages/cli/src/config/extensionRegistryClient.ts +++ b/packages/cli/src/config/extensionRegistryClient.ts @@ -94,9 +94,8 @@ export class ExtensionRegistryClient { fuzzy: true, }); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const results = await fzf.find(query); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return results.map((r: { item: RegistryExtension }) => r.item); + const results: Array<{ item: RegistryExtension }> = await fzf.find(query); + return results.map((r) => r.item); } async getExtension(id: string): Promise { diff --git a/packages/cli/src/config/extensions/extensionEnablement.ts b/packages/cli/src/config/extensions/extensionEnablement.ts index 7ae2431ee9..5ba0800f44 100644 --- a/packages/cli/src/config/extensions/extensionEnablement.ts +++ b/packages/cli/src/config/extensions/extensionEnablement.ts @@ -8,6 +8,7 @@ import fs from 'node:fs'; import path from 'node:path'; import { coreEvents, type GeminiCLIExtension } from '@google/gemini-cli-core'; import { ExtensionStorage } from './storage.js'; +import { z } from 'zod'; export interface ExtensionEnablementConfig { overrides: string[]; @@ -179,8 +180,12 @@ export class ExtensionEnablementManager { readConfig(): AllExtensionsEnablementConfig { try { const content = fs.readFileSync(this.configFilePath, 'utf-8'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return JSON.parse(content); + const parsed: unknown = JSON.parse(content); + const schema = z.record( + z.string(), + z.object({ overrides: z.array(z.string()) }), + ); + return schema.parse(parsed); } catch (error) { if ( error instanceof Error && diff --git a/packages/cli/src/config/extensions/extensionSettings.ts b/packages/cli/src/config/extensions/extensionSettings.ts index 700d854e20..7b1981374e 100644 --- a/packages/cli/src/config/extensions/extensionSettings.ts +++ b/packages/cli/src/config/extensions/extensionSettings.ts @@ -62,7 +62,7 @@ export const getEnvFilePath = ( export async function maybePromptForSettings( extensionConfig: ExtensionConfig, extensionId: string, - requestSetting: (setting: ExtensionSetting) => Promise, + requestSetting: (setting: ExtensionSetting) => Promise, previousExtensionConfig?: ExtensionConfig, previousSettings?: Record, ): Promise { @@ -106,7 +106,9 @@ export async function maybePromptForSettings( settingsChanges.promptForEnv, )) { const answer = await requestSetting(setting); - allSettings[setting.envVar] = answer; + if (answer !== undefined) { + allSettings[setting.envVar] = answer; + } } const nonSensitiveSettings: Record = {}; @@ -159,14 +161,13 @@ function formatEnvContent(settings: Record): string { export async function promptForSetting( setting: ExtensionSetting, -): Promise { +): Promise { const response = await prompts({ type: setting.sensitive ? 'password' : 'text', name: 'value', message: `${setting.name}\n${setting.description}`, }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return response.value; + return typeof response.value === 'string' ? response.value : undefined; } export async function getScopedEnvContents( @@ -230,7 +231,7 @@ export async function updateSetting( extensionConfig: ExtensionConfig, extensionId: string, settingKey: string, - requestSetting: (setting: ExtensionSetting) => Promise, + requestSetting: (setting: ExtensionSetting) => Promise, scope: ExtensionSettingScope, workspaceDir: string, ): Promise { @@ -250,6 +251,10 @@ export async function updateSetting( } const newValue = await requestSetting(settingToUpdate); + if (newValue === undefined) { + return; + } + const keychain = new KeychainTokenStorage( getKeychainStorageName(extensionName, extensionId, scope, workspaceDir), ); diff --git a/packages/cli/src/config/extensions/variables.ts b/packages/cli/src/config/extensions/variables.ts index b5b14c9643..03276d7125 100644 --- a/packages/cli/src/config/extensions/variables.ts +++ b/packages/cli/src/config/extensions/variables.ts @@ -67,8 +67,7 @@ export function recursivelyHydrateStrings( } if (Array.isArray(obj)) { // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - return obj.map((item) => - // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return (obj as unknown[]).map((item) => recursivelyHydrateStrings(item, values), ) as unknown as T; } diff --git a/packages/cli/src/test-utils/mockCommandContext.ts b/packages/cli/src/test-utils/mockCommandContext.ts index 9a1156e5cb..e3c5179ed5 100644 --- a/packages/cli/src/test-utils/mockCommandContext.ts +++ b/packages/cli/src/test-utils/mockCommandContext.ts @@ -112,5 +112,11 @@ export const createMockCommandContext = ( return output; }; - return merge(defaultMocks, overrides); + const merged: unknown = merge(defaultMocks, overrides); + const isCommandContext = (val: unknown): val is CommandContext => + typeof val === 'object' && val !== null; + if (isCommandContext(merged)) { + return merged; + } + throw new Error('Unreachable'); }; diff --git a/packages/cli/src/ui/hooks/useAtCompletion.ts b/packages/cli/src/ui/hooks/useAtCompletion.ts index 8bec10ed0b..5657f3cc8b 100644 --- a/packages/cli/src/ui/hooks/useAtCompletion.ts +++ b/packages/cli/src/ui/hooks/useAtCompletion.ts @@ -170,13 +170,13 @@ async function searchResourceCandidates( selector: (candidate: ResourceSuggestionCandidate) => candidate.searchKey, }); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const results = await fzf.find(normalizedPattern, { - limit: MAX_SUGGESTIONS_TO_SHOW * 3, - }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return results.map( - (result: { item: ResourceSuggestionCandidate }) => result.item.suggestion, + const results: Array<{ item: ResourceSuggestionCandidate }> = await fzf.find( + normalizedPattern, + { + limit: MAX_SUGGESTIONS_TO_SHOW * 3, + }, ); + return results.map((result) => result.item.suggestion); } async function searchAgentCandidates( @@ -194,11 +194,13 @@ async function searchAgentCandidates( selector: (s: Suggestion) => s.label, }); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const results = await fzf.find(normalizedPattern, { - limit: MAX_SUGGESTIONS_TO_SHOW, - }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return results.map((r: { item: Suggestion }) => r.item); + const results: Array<{ item: Suggestion }> = await fzf.find( + normalizedPattern, + { + limit: MAX_SUGGESTIONS_TO_SHOW, + }, + ); + return results.map((r) => r.item); } export function useAtCompletion(props: UseAtCompletionProps): void { diff --git a/packages/cli/src/ui/utils/TableRenderer.tsx b/packages/cli/src/ui/utils/TableRenderer.tsx index b6a30792ca..d6dd9c3303 100644 --- a/packages/cli/src/ui/utils/TableRenderer.tsx +++ b/packages/cli/src/ui/utils/TableRenderer.tsx @@ -174,7 +174,10 @@ export const TableRenderer: React.FC = ({ } // --- Pre-wrap and Optimize Widths --- - const actualColumnWidths = new Array(numColumns).fill(0); + const actualColumnWidths: number[] = []; + for (let i = 0; i < numColumns; i++) { + actualColumnWidths.push(0); + } const wrapAndProcessRow = (row: StyledLine[]) => { const rowResult: ProcessedLine[][] = []; @@ -208,11 +211,7 @@ export const TableRenderer: React.FC = ({ const wrappedRows = styledRows.map((row) => wrapAndProcessRow(row)); // Use the TIGHTEST widths that fit the wrapped content + padding - const adjustedWidths = actualColumnWidths.map( - (w) => - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - w + COLUMN_PADDING, - ); + const adjustedWidths = actualColumnWidths.map((w) => w + COLUMN_PADDING); return { wrappedHeaders, wrappedRows, adjustedWidths }; }, [styledHeaders, styledRows, terminalWidth]); @@ -263,7 +262,6 @@ export const TableRenderer: React.FC = ({ isHeader = false, ): React.ReactNode => { const renderedCells = cells.map((cell, index) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const width = adjustedWidths[index] || 0; return renderCell(cell, width, isHeader); }); diff --git a/packages/cli/src/utils/envVarResolver.ts b/packages/cli/src/utils/envVarResolver.ts index 81e34ae00f..5fe736bd8e 100644 --- a/packages/cli/src/utils/envVarResolver.ts +++ b/packages/cli/src/utils/envVarResolver.ts @@ -111,18 +111,20 @@ function resolveEnvVarsInObjectInternal( // Check for circular reference if (visited.has(obj)) { // Return a shallow copy to break the cycle - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - return [...obj] as unknown as T; + const copy: unknown = [...obj]; + const isTArray = (val: unknown): val is T => Array.isArray(val); + if (isTArray(copy)) return copy; + throw new Error('Unreachable'); } visited.add(obj); - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion - const result = obj.map((item) => - // eslint-disable-next-line @typescript-eslint/no-unsafe-return + const mapped: unknown = obj.map((item: unknown) => resolveEnvVarsInObjectInternal(item, visited, customEnv), - ) as unknown as T; + ); visited.delete(obj); - return result; + const isTArray = (val: unknown): val is T => Array.isArray(val); + if (isTArray(mapped)) return mapped; + throw new Error('Unreachable'); } if (typeof obj === 'object') { diff --git a/packages/cli/src/utils/gitUtils.ts b/packages/cli/src/utils/gitUtils.ts index a2936a1a2d..5793786ed9 100644 --- a/packages/cli/src/utils/gitUtils.ts +++ b/packages/cli/src/utils/gitUtils.ts @@ -83,8 +83,7 @@ export const getLatestGitHubRelease = async ( if (!releaseTag) { throw new Error(`Response did not include tag_name field`); } - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return releaseTag; + return typeof releaseTag === 'string' ? releaseTag : ''; } catch (error) { debugLogger.debug( `Failed to determine latest run-gemini-cli release:`, diff --git a/packages/cli/src/utils/jsonoutput.ts b/packages/cli/src/utils/jsonoutput.ts index 3040c1db57..46fa0479da 100644 --- a/packages/cli/src/utils/jsonoutput.ts +++ b/packages/cli/src/utils/jsonoutput.ts @@ -29,8 +29,7 @@ export function tryParseJSON(input: string): object | null { if (!checkInput(input)) return null; const trimmed = input.trim(); try { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const parsed = JSON.parse(trimmed); + const parsed: unknown = JSON.parse(trimmed); if (parsed === null || typeof parsed !== 'object') { return null; } @@ -40,7 +39,6 @@ export function tryParseJSON(input: string): object | null { if (!Array.isArray(parsed) && Object.keys(parsed).length === 0) return null; - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return parsed; } catch { return null; diff --git a/packages/core/src/code_assist/oauth2.ts b/packages/core/src/code_assist/oauth2.ts index 588a313ef9..a8d6f61b4a 100644 --- a/packages/core/src/code_assist/oauth2.ts +++ b/packages/core/src/code_assist/oauth2.ts @@ -679,8 +679,13 @@ async function fetchCachedCredentials(): Promise< for (const keyFile of pathsToTry) { try { const keyFileString = await fs.readFile(keyFile, 'utf-8'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return JSON.parse(keyFileString); + const parsed: unknown = JSON.parse(keyFileString); + const isOAuthCreds = (val: unknown): val is Credentials | JWTInput => + typeof val === 'object' && val !== null; + if (isOAuthCreds(parsed)) { + return parsed; + } + throw new Error('Invalid credentials format'); } catch (error) { // Log specific error for debugging, but continue trying other paths debugLogger.debug( diff --git a/packages/core/src/config/projectRegistry.ts b/packages/core/src/config/projectRegistry.ts index 9b816583eb..88c49ac3af 100644 --- a/packages/core/src/config/projectRegistry.ts +++ b/packages/core/src/config/projectRegistry.ts @@ -76,7 +76,7 @@ export class ProjectRegistry { if (isNodeError(error) && error.code === 'ENOENT') { return { projects: {} }; // Normal first run } - if (error instanceof SyntaxError) { + if (error instanceof SyntaxError || error instanceof z.ZodError) { debugLogger.warn( 'Failed to load registry (JSON corrupted), resetting to empty: ', error, diff --git a/packages/core/src/core/baseLlmClient.ts b/packages/core/src/core/baseLlmClient.ts index dbf7539944..a23b8c353c 100644 --- a/packages/core/src/core/baseLlmClient.ts +++ b/packages/core/src/core/baseLlmClient.ts @@ -177,10 +177,15 @@ export class BaseLlmClient { ); // If we are here, the content is valid (not empty and parsable). - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return JSON.parse( + const parsed: unknown = JSON.parse( this.cleanJsonResponse(getResponseText(result)!.trim(), model), ); + const isRecord = (val: unknown): val is Record => + typeof val === 'object' && val !== null && !Array.isArray(val); + if (isRecord(parsed)) { + return parsed; + } + throw new Error('Invalid JSON response format from LLM'); } async generateEmbedding(texts: string[]): Promise { diff --git a/packages/core/src/core/fakeContentGenerator.ts b/packages/core/src/core/fakeContentGenerator.ts index 9ecd75a99d..39687579e8 100644 --- a/packages/core/src/core/fakeContentGenerator.ts +++ b/packages/core/src/core/fakeContentGenerator.ts @@ -84,11 +84,12 @@ export class FakeContentGenerator implements ContentGenerator { // eslint-disable-next-line @typescript-eslint/no-unused-vars role: LlmRole, ): Promise { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return Object.setPrototypeOf( - this.getNextResponse('generateContent', request), - GenerateContentResponse.prototype, - ); + const response: unknown = this.getNextResponse('generateContent', request); + Object.setPrototypeOf(response, GenerateContentResponse.prototype); + if (response instanceof GenerateContentResponse) { + return response; + } + throw new Error('Failed to create GenerateContentResponse'); } async generateContentStream( @@ -118,10 +119,11 @@ export class FakeContentGenerator implements ContentGenerator { async embedContent( request: EmbedContentParameters, ): Promise { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return Object.setPrototypeOf( - this.getNextResponse('embedContent', request), - EmbedContentResponse.prototype, - ); + const response: unknown = this.getNextResponse('embedContent', request); + Object.setPrototypeOf(response, EmbedContentResponse.prototype); + if (response instanceof EmbedContentResponse) { + return response; + } + throw new Error('Failed to create EmbedContentResponse'); } } diff --git a/packages/core/src/core/localLiteRtLmClient.ts b/packages/core/src/core/localLiteRtLmClient.ts index 82fa44e87b..c85d4ea31d 100644 --- a/packages/core/src/core/localLiteRtLmClient.ts +++ b/packages/core/src/core/localLiteRtLmClient.ts @@ -84,8 +84,13 @@ export class LocalLiteRtLmClient { ); } - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return JSON.parse(result.text); + const parsed: unknown = JSON.parse(result.text); + const isRecord = (val: unknown): val is Record => + typeof val === 'object' && val !== null && !Array.isArray(val); + if (isRecord(parsed)) { + return parsed; + } + throw new Error('Invalid JSON response format from Local LLM'); } catch (error) { debugLogger.error( `[LocalLiteRtLmClient] Failed to generate content:`, diff --git a/packages/core/src/ide/ide-client.ts b/packages/core/src/ide/ide-client.ts index e9d25f1c01..ca43b9b39f 100644 --- a/packages/core/src/ide/ide-client.ts +++ b/packages/core/src/ide/ide-client.ts @@ -348,9 +348,11 @@ export class IdeClient { try { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const parsedJson = JSON.parse(textPart.text); - if (parsedJson && typeof parsedJson.content === 'string') { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return parsedJson.content; + if (parsedJson) { + const content: unknown = parsedJson.content; + if (typeof content === 'string') { + return content; + } } if (parsedJson && parsedJson.content === null) { return undefined; diff --git a/packages/core/src/ide/ide-connection-utils.ts b/packages/core/src/ide/ide-connection-utils.ts index e06b8f74b0..381297a28c 100644 --- a/packages/core/src/ide/ide-connection-utils.ts +++ b/packages/core/src/ide/ide-connection-utils.ts @@ -123,8 +123,17 @@ export async function getConnectionConfigFromFile( `gemini-ide-server-${pid}.json`, ); const portFileContents = await fs.promises.readFile(portFile, 'utf8'); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return JSON.parse(portFileContents); + const parsed: unknown = JSON.parse(portFileContents); + type ConfigType = ConnectionConfig & { + workspacePath?: string; + ideInfo?: IdeInfo; + }; + const isConfig = (val: unknown): val is ConfigType => + typeof val === 'object' && val !== null; + if (isConfig(parsed)) { + return parsed; + } + throw new Error('Invalid connection config format'); } catch { // For newer extension versions, the file name matches the pattern // /^gemini-ide-server-${pid}-\d+\.json$/. If multiple IDE @@ -166,39 +175,59 @@ export async function getConnectionConfigFromFile( logger.debug('Failed to read IDE connection config file(s):', e); return undefined; } - const parsedContents = fileContents.map((content) => { - try { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return JSON.parse(content); - } catch (e) { - logger.debug('Failed to parse JSON from config file: ', e); - return undefined; - } - }); + const parsedContents = fileContents.map( + ( + content, + ): + | (ConnectionConfig & { workspacePath?: string; ideInfo?: IdeInfo }) + | undefined => { + try { + const parsed: unknown = JSON.parse(content); + type ConfigType = ConnectionConfig & { + workspacePath?: string; + ideInfo?: IdeInfo; + }; + const isConfig = (val: unknown): val is ConfigType => + typeof val === 'object' && val !== null; + if (isConfig(parsed)) { + return parsed; + } + return undefined; + } catch (e) { + logger.debug('Failed to parse JSON from config file: ', e); + return undefined; + } + }, + ); - const validWorkspaces = parsedContents.filter((content) => { - if (!content) { - return false; - } - const { isValid } = validateWorkspacePath( - content.workspacePath, - process.cwd(), - ); - return isValid; - }); + const validWorkspaces = parsedContents.filter( + ( + content, + ): content is ConnectionConfig & { + workspacePath?: string; + ideInfo?: IdeInfo; + } => { + if (!content) { + return false; + } + const { isValid } = validateWorkspacePath( + content.workspacePath, + process.cwd(), + ); + return isValid; + }, + ); if (validWorkspaces.length === 0) { return undefined; } if (validWorkspaces.length === 1) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const selected = validWorkspaces[0]; const fileIndex = parsedContents.indexOf(selected); if (fileIndex !== -1) { logger.debug(`Selected IDE connection file: ${matchingFiles[fileIndex]}`); } - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return selected; } @@ -208,7 +237,6 @@ export async function getConnectionConfigFromFile( (content) => String(content.port) === portFromEnv, ); if (matchingPortIndex !== -1) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const selected = validWorkspaces[matchingPortIndex]; const fileIndex = parsedContents.indexOf(selected); if (fileIndex !== -1) { @@ -216,12 +244,10 @@ export async function getConnectionConfigFromFile( `Selected IDE connection file (matched port from env): ${matchingFiles[fileIndex]}`, ); } - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return selected; } } - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const selected = validWorkspaces[0]; const fileIndex = parsedContents.indexOf(selected); if (fileIndex !== -1) { @@ -229,7 +255,6 @@ export async function getConnectionConfigFromFile( `Selected first valid IDE connection file: ${matchingFiles[fileIndex]}`, ); } - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return selected; } diff --git a/packages/core/src/utils/filesearch/fileSearch.ts b/packages/core/src/utils/filesearch/fileSearch.ts index 3cc2100618..4229d5bcaa 100644 --- a/packages/core/src/utils/filesearch/fileSearch.ts +++ b/packages/core/src/utils/filesearch/fileSearch.ts @@ -9,7 +9,7 @@ import picomatch from 'picomatch'; import { loadIgnoreRules, type Ignore } from './ignore.js'; import { ResultCache } from './result-cache.js'; import { crawl } from './crawler.js'; -import { AsyncFzf, type FzfResultItem } from 'fzf'; +import { AsyncFzf } from 'fzf'; import { unescapePath } from '../paths.js'; import type { FileDiscoveryService } from '../../services/fileDiscoveryService.js'; import { FileWatcher, type FileWatcherEvent } from './fileWatcher.js'; @@ -270,7 +270,7 @@ class RecursiveFileSearch implements FileSearch { pattern = unescapePath(pattern) || '*'; - let filteredCandidates; + let filteredCandidates: string[]; const { files: candidates, isExactMatch } = await this.resultCache.get(pattern); @@ -282,17 +282,27 @@ class RecursiveFileSearch implements FileSearch { if (pattern.includes('*') || !this.fzf) { filteredCandidates = await filter(candidates, pattern, options.signal); } else { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - filteredCandidates = await this.fzf - .find(pattern) - .then((results: Array>) => - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - results.map((entry: FzfResultItem) => entry.item), - ) - .catch(() => { + try { + const fzfResult: unknown = await this.fzf.find(pattern); + if (Array.isArray(fzfResult)) { + filteredCandidates = fzfResult.map((entry: unknown) => { + if ( + typeof entry === 'object' && + entry !== null && + 'item' in entry + ) { + return String((entry as { item: unknown }).item); + } + return String(entry); + }); + } else { shouldCache = false; - return []; - }); + filteredCandidates = []; + } + } catch { + shouldCache = false; + filteredCandidates = []; + } } if (shouldCache) { diff --git a/packages/core/src/utils/safeJsonStringify.ts b/packages/core/src/utils/safeJsonStringify.ts index b32a09df27..d3998fa0f4 100644 --- a/packages/core/src/utils/safeJsonStringify.ts +++ b/packages/core/src/utils/safeJsonStringify.ts @@ -27,8 +27,7 @@ export function safeJsonStringify( } seen.add(value); } - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return value; + return value as unknown; }, space, ); @@ -61,8 +60,7 @@ export function safeJsonStringifyBooleanValuesOnly(obj: any): string { // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion if ((value as Config) !== null && !configSeen) { configSeen = true; - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return value; + return value as unknown; } if (typeof value === 'boolean') { return value; diff --git a/scripts/review.sh b/scripts/review.sh old mode 100755 new mode 100644