Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c59e648
refactor: remove no-unsafe-return suppressions from JSON payload pars…
M-DEV-1 Feb 28, 2026
5ce3d51
refactor: rmeove no-unsafe-return suppressions
M-DEV-1 Feb 28, 2026
18e5a43
refactor: remove no-unsafe-return suppressions with native type evalu…
M-DEV-1 Feb 28, 2026
3e8c1f9
Merge branch 'main' into M-DEV-1/clean-unsafe-return-suppressions
M-DEV-1 Feb 28, 2026
00687b4
Merge branch 'main' into M-DEV-1/clean-unsafe-return-suppressions
M-DEV-1 Feb 28, 2026
303f01e
Merge branch 'main' into M-DEV-1/clean-unsafe-return-suppressions
M-DEV-1 Mar 1, 2026
6eef68f
fix: resolve merge conflicts
M-DEV-1 Mar 10, 2026
7fe9e0f
Merge branch 'main' into M-DEV-1/clean-unsafe-return-suppressions
M-DEV-1 Mar 10, 2026
23ffb52
Merge branch 'main' into M-DEV-1/clean-unsafe-return-suppressions
M-DEV-1 Mar 17, 2026
ed0acf6
Merge branch 'main' into M-DEV-1/clean-unsafe-return-suppressions
M-DEV-1 Mar 29, 2026
d491b02
Merge branch 'main' into M-DEV-1/clean-unsafe-return-suppressions
M-DEV-1 Apr 12, 2026
861fd07
Merge branch 'main' into M-DEV-1/clean-unsafe-return-suppressions
M-DEV-1 Apr 17, 2026
35bd39a
Merge branch 'main' into M-DEV-1/clean-unsafe-return-suppressions
M-DEV-1 Apr 18, 2026
5cf7afa
Merge branch 'main' of https://github.com/google-gemini/gemini-cli in…
M-DEV-1 May 8, 2026
64d43d7
Merge branch 'main' into M-DEV-1/clean-unsafe-return-suppressions
scidomino May 11, 2026
8161bf5
Merge branch 'main' into M-DEV-1/clean-unsafe-return-suppressions
M-DEV-1 May 12, 2026
03bb621
fix: linter errors
M-DEV-1 May 12, 2026
18d657c
fix: clean up expected undefined in returns
M-DEV-1 May 12, 2026
a6d525f
Merge branch 'main' into M-DEV-1/clean-unsafe-return-suppressions
scidomino May 12, 2026
b775141
fix: restore CONTRIBUTING.md symlink
M-DEV-1 May 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions packages/cli/src/acp/acpFileSystemService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
5 changes: 2 additions & 3 deletions packages/cli/src/commands/extensions/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export interface ConfigLogger {

export type RequestSettingCallback = (
setting: ExtensionSetting,
) => Promise<string>;
) => Promise<string | undefined>;
export type RequestConfirmationCallback = (message: string) => Promise<boolean>;

const defaultLogger: ConfigLogger = {
Expand All @@ -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() {
Expand Down
8 changes: 5 additions & 3 deletions packages/cli/src/config/extension-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ interface ExtensionManagerParams {
enabledExtensionOverrides?: string[];
settings: MergedSettings;
requestConsent: (consent: string) => Promise<boolean>;
requestSetting: ((setting: ExtensionSetting) => Promise<string>) | null;
requestSetting:
| ((setting: ExtensionSetting) => Promise<string | undefined>)
| null;
workspaceDir: string;
eventEmitter?: EventEmitter<ExtensionEvents>;
clientVersion?: string;
Expand All @@ -106,7 +108,7 @@ export class ExtensionManager extends ExtensionLoader {
private settings: MergedSettings;
private requestConsent: (consent: string) => Promise<boolean>;
private requestSetting:
| ((setting: ExtensionSetting) => Promise<string>)
| ((setting: ExtensionSetting) => Promise<string | undefined>)
| undefined;
private telemetryConfig: Config;
private workspaceDir: string;
Expand Down Expand Up @@ -161,7 +163,7 @@ export class ExtensionManager extends ExtensionLoader {
}

setRequestSetting(
requestSetting?: (setting: ExtensionSetting) => Promise<string>,
requestSetting?: (setting: ExtensionSetting) => Promise<string | undefined>,
): void {
this.requestSetting = requestSetting;
}
Expand Down
5 changes: 2 additions & 3 deletions packages/cli/src/config/extensionRegistryClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<RegistryExtension | undefined> {
Expand Down
9 changes: 7 additions & 2 deletions packages/cli/src/config/extensions/extensionEnablement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down Expand Up @@ -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 &&
Expand Down
17 changes: 11 additions & 6 deletions packages/cli/src/config/extensions/extensionSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const getEnvFilePath = (
export async function maybePromptForSettings(
extensionConfig: ExtensionConfig,
extensionId: string,
requestSetting: (setting: ExtensionSetting) => Promise<string>,
requestSetting: (setting: ExtensionSetting) => Promise<string | undefined>,
previousExtensionConfig?: ExtensionConfig,
previousSettings?: Record<string, string>,
): Promise<void> {
Expand Down Expand Up @@ -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<string, string> = {};
Expand Down Expand Up @@ -159,14 +161,13 @@ function formatEnvContent(settings: Record<string, string>): string {

export async function promptForSetting(
setting: ExtensionSetting,
): Promise<string> {
): Promise<string | undefined> {
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(
Expand Down Expand Up @@ -230,7 +231,7 @@ export async function updateSetting(
extensionConfig: ExtensionConfig,
extensionId: string,
settingKey: string,
requestSetting: (setting: ExtensionSetting) => Promise<string>,
requestSetting: (setting: ExtensionSetting) => Promise<string | undefined>,
scope: ExtensionSettingScope,
workspaceDir: string,
): Promise<void> {
Expand All @@ -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),
);
Expand Down
3 changes: 1 addition & 2 deletions packages/cli/src/config/extensions/variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ export function recursivelyHydrateStrings<T>(
}
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;
}
Expand Down
8 changes: 7 additions & 1 deletion packages/cli/src/test-utils/mockCommandContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
};
24 changes: 13 additions & 11 deletions packages/cli/src/ui/hooks/useAtCompletion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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 {
Expand Down
12 changes: 5 additions & 7 deletions packages/cli/src/ui/utils/TableRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,10 @@ export const TableRenderer: React.FC<TableRendererProps> = ({
}

// --- 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[][] = [];
Expand Down Expand Up @@ -208,11 +211,7 @@ export const TableRenderer: React.FC<TableRendererProps> = ({
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]);
Expand Down Expand Up @@ -263,7 +262,6 @@ export const TableRenderer: React.FC<TableRendererProps> = ({
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);
});
Expand Down
16 changes: 9 additions & 7 deletions packages/cli/src/utils/envVarResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,18 +111,20 @@ function resolveEnvVarsInObjectInternal<T>(
// 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') {
Expand Down
3 changes: 1 addition & 2 deletions packages/cli/src/utils/gitUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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:`,
Expand Down
4 changes: 1 addition & 3 deletions packages/cli/src/utils/jsonoutput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
Expand Down
9 changes: 7 additions & 2 deletions packages/core/src/code_assist/oauth2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -675,8 +675,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(
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/config/projectRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
9 changes: 7 additions & 2 deletions packages/core/src/core/baseLlmClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,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<string, unknown> =>
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<number[][]> {
Expand Down
22 changes: 12 additions & 10 deletions packages/core/src/core/fakeContentGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,12 @@ export class FakeContentGenerator implements ContentGenerator {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
role: LlmRole,
): Promise<GenerateContentResponse> {
// 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(
Expand Down Expand Up @@ -118,10 +119,11 @@ export class FakeContentGenerator implements ContentGenerator {
async embedContent(
request: EmbedContentParameters,
): Promise<EmbedContentResponse> {
// 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');
}
}
9 changes: 7 additions & 2 deletions packages/core/src/core/localLiteRtLmClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown> =>
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:`,
Expand Down
Loading
Loading