Disallow unsafe type assertions (#18688)

This commit is contained in:
Christian Gunderman
2026-02-10 00:10:15 +00:00
committed by GitHub
parent bce1caefd0
commit fd65416a2f
188 changed files with 592 additions and 47 deletions

View File

@@ -249,6 +249,7 @@ export const AppContainer = (props: AppContainerProps) => {
const { bannerText } = useBanner(bannerData);
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const extensionManager = config.getExtensionLoader() as ExtensionManager;
// We are in the interactive CLI, update how we request consent and settings.
extensionManager.setRequestConsent((description) =>
@@ -468,6 +469,7 @@ export const AppContainer = (props: AppContainerProps) => {
const staticAreaMaxItemHeight = Math.max(terminalHeight * 4, 100);
const getPreferredEditor = useCallback(
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
() => settings.merged.general.preferredEditor as EditorType,
[settings.merged.general.preferredEditor],
);

View File

@@ -88,8 +88,10 @@ export function AuthDialog({
const defaultAuthTypeEnv = process.env['GEMINI_DEFAULT_AUTH_TYPE'];
if (
defaultAuthTypeEnv &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
Object.values(AuthType).includes(defaultAuthTypeEnv as AuthType)
) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
defaultAuthType = defaultAuthTypeEnv as AuthType;
}

View File

@@ -113,6 +113,7 @@ export const useAuthCommand = (
const defaultAuthType = process.env['GEMINI_DEFAULT_AUTH_TYPE'];
if (
defaultAuthType &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
!Object.values(AuthType).includes(defaultAuthType as AuthType)
) {
onAuthError(

View File

@@ -213,6 +213,7 @@ const resumeCommand: SlashCommand = {
continue;
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
uiHistory.push({
type: (item.role && rolemap[item.role]) || MessageType.GEMINI,
text,

View File

@@ -49,6 +49,7 @@ async function finishAddingDirectories(
text: `Successfully added GEMINI.md files from the following directories if there are:\n- ${added.join('\n- ')}`,
});
} catch (error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
errors.push(`Error refreshing memory: ${(error as Error).message}`);
}
}

View File

@@ -48,6 +48,7 @@ export const initCommand: SlashCommand = {
);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return result as SlashCommandActionReturn;
},
};

View File

@@ -93,6 +93,7 @@ export const memoryCommand: SlashCommand = {
context.ui.addItem(
{
type: MessageType.ERROR,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
text: `Error refreshing memory: ${(error as Error).message}`,
},
Date.now(),

View File

@@ -123,6 +123,7 @@ function getNestedValue(
for (const key of path) {
if (current === null || current === undefined) return undefined;
if (typeof current !== 'object') return undefined;
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
current = (current as Record<string, unknown>)[key];
}
return current;
@@ -144,8 +145,10 @@ function setNestedValue(
if (current[key] === undefined || current[key] === null) {
current[key] = {};
} else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
current[key] = { ...(current[key] as Record<string, unknown>) };
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
current = current[key] as Record<string, unknown>;
}
@@ -265,6 +268,7 @@ export function AgentConfigDialog({
() =>
AGENT_CONFIG_FIELDS.map((field) => {
const currentValue = getNestedValue(
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
pendingOverride as Record<string, unknown>,
field.path,
);
@@ -300,6 +304,7 @@ export function AgentConfigDialog({
displayValue,
isGreyedOut: currentValue === undefined,
scopeMessage: undefined,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
rawValue: rawValue as string | number | boolean | undefined,
};
}),
@@ -320,6 +325,7 @@ export function AgentConfigDialog({
if (!field || field.type !== 'boolean') return;
const currentValue = getNestedValue(
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
pendingOverride as Record<string, unknown>,
field.path,
);
@@ -329,6 +335,7 @@ export function AgentConfigDialog({
const newValue = !effectiveValue;
const newOverride = setNestedValue(
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
pendingOverride as Record<string, unknown>,
field.path,
newValue,
@@ -369,6 +376,7 @@ export function AgentConfigDialog({
// Update pending override locally
const newOverride = setNestedValue(
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
pendingOverride as Record<string, unknown>,
field.path,
parsed,
@@ -391,6 +399,7 @@ export function AgentConfigDialog({
// Remove the override (set to undefined)
const newOverride = setNestedValue(
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
pendingOverride as Record<string, unknown>,
field.path,
undefined,

View File

@@ -132,6 +132,7 @@ export function EditorSettingsDialog({
) {
mergedEditorName =
EDITOR_DISPLAY_NAMES[
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
settings.merged.general.preferredEditor as EditorType
];
}

View File

@@ -133,6 +133,7 @@ export const MultiFolderTrustDialog: React.FC<MultiFolderTrustDialogProps> = ({
workspaceContext.addDirectory(expandedPath);
added.push(dir);
} catch (e) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const error = e as Error;
errors.push(`Error adding '${dir}': ${error.message}`);
}

View File

@@ -259,10 +259,12 @@ export function SettingsDialog({
key,
label: definition?.label || key,
description: definition?.description,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
type: type as 'boolean' | 'number' | 'string' | 'enum',
displayValue,
isGreyedOut,
scopeMessage,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
rawValue: rawValue as string | number | boolean | undefined,
};
});
@@ -283,8 +285,10 @@ export function SettingsDialog({
const currentValue = getEffectiveValue(key, pendingSettings, {});
let newValue: SettingsValue;
if (definition?.type === 'boolean') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
newValue = !(currentValue as boolean);
setPendingSettings((prev) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
setPendingSettingValue(key, newValue as boolean, prev),
);
} else if (definition?.type === 'enum' && definition.options) {
@@ -377,6 +381,7 @@ export function SettingsDialog({
// Record pending change globally
setGlobalPendingChanges((prev) => {
const next = new Map(prev);
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
next.set(key, newValue as PendingValue);
return next;
});

View File

@@ -75,6 +75,7 @@ export function Table<T>({ data, columns }: TableProps<T>) {
col.renderCell(item)
) : (
<Text color={theme.text.primary}>
{/* eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion */}
{String((item as Record<string, unknown>)[col.key])}
</Text>
)}

View File

@@ -121,6 +121,7 @@ export const ToolResultDisplay: React.FC<ToolResultDisplayProps> = ({
// where Container grows -> List renders more -> Container grows.
const limit = maxLines ?? availableHeight ?? ACTIVE_SHELL_MAX_LINES;
const listHeight = Math.min(
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(truncatedResultDisplay as AnsiOutput).length,
limit,
);
@@ -129,6 +130,7 @@ export const ToolResultDisplay: React.FC<ToolResultDisplayProps> = ({
<Box width={childWidth} flexDirection="column" maxHeight={listHeight}>
<ScrollableList
width={childWidth}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
data={truncatedResultDisplay as AnsiOutput}
renderItem={renderVirtualizedAnsiLine}
estimatedItemHeight={() => 1}
@@ -184,7 +186,9 @@ export const ToolResultDisplay: React.FC<ToolResultDisplayProps> = ({
) {
content = (
<DiffRenderer
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
diffContent={(truncatedResultDisplay as FileDiffResult).fileDiff}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
filename={(truncatedResultDisplay as FileDiffResult).fileName}
availableTerminalHeight={availableHeight}
terminalWidth={childWidth}
@@ -197,6 +201,7 @@ export const ToolResultDisplay: React.FC<ToolResultDisplayProps> = ({
content = (
<AnsiOutputText
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
data={truncatedResultDisplay as AnsiOutput}
availableTerminalHeight={
isAlternateBuffer ? undefined : availableHeight

View File

@@ -153,6 +153,7 @@ export const Scrollable: React.FC<ScrollableProps> = ({
const scrollableEntry = useMemo(
() => ({
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
ref: ref as React.RefObject<DOMElement>,
getScrollState,
scrollBy: scrollByWithAnimation,

View File

@@ -219,6 +219,7 @@ function ScrollableList<T>(
const scrollableEntry = useMemo(
() => ({
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
ref: containerRef as React.RefObject<DOMElement>,
getScrollState,
scrollBy: scrollByWithAnimation,
@@ -254,6 +255,7 @@ function ScrollableList<T>(
);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const ScrollableListWithForwardRef = forwardRef(ScrollableList) as <T>(
props: ScrollableListProps<T> & { ref?: React.Ref<ScrollableListRef<T>> },
) => React.ReactElement;

View File

@@ -492,6 +492,7 @@ function VirtualizedList<T>(
);
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const VirtualizedListWithForwardRef = forwardRef(VirtualizedList) as <T>(
props: VirtualizedListProps<T> & { ref?: React.Ref<VirtualizedListRef<T>> },
) => React.ReactElement;

View File

@@ -157,6 +157,7 @@ export const TriageDuplicates = ({
'--json',
'number,title,body,state,stateReason,labels,url,comments,author,reactionGroups',
]);
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return JSON.parse(stdout) as Candidate;
} catch (err) {
debugLogger.error(
@@ -280,6 +281,7 @@ Return a JSON object with:
promptId: 'triage-duplicates',
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const rec = response as unknown as GeminiRecommendation;
let canonical: Candidate | undefined;

View File

@@ -225,6 +225,7 @@ Return a JSON object with:
promptId: 'triage-issues',
});
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return response as unknown as AnalysisResult;
},
[config],

View File

@@ -21,6 +21,7 @@ class EditorSettingsManager {
private readonly availableEditors: EditorDisplay[];
constructor() {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const editorTypes = Object.keys(
EDITOR_DISPLAY_NAMES,
).sort() as EditorType[];

View File

@@ -467,6 +467,7 @@ export const useSlashCommandProcessor = (
actions.openModelDialog();
return { type: 'handled' };
case 'agentConfig': {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const props = result.props as Record<string, unknown>;
if (
!props ||
@@ -482,12 +483,14 @@ export const useSlashCommandProcessor = (
actions.openAgentConfigDialog(
props['name'],
props['displayName'],
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
props['definition'] as AgentDefinition,
);
return { type: 'handled' };
}
case 'permissions':
actions.openPermissionsDialog(
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
result.props as { targetDirectory?: string },
);
return { type: 'handled' };

View File

@@ -102,6 +102,7 @@ export function useApprovalModeIndicator({
addItem(
{
type: MessageType.INFO,
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
text: (e as Error).message,
},
Date.now(),

View File

@@ -46,7 +46,6 @@ import type {
ToolCallResponseInfo,
GeminiErrorEventValue,
RetryAttemptPayload,
ToolCallConfirmationDetails,
} from '@google/gemini-cli-core';
import { type Part, type PartListUnion, FinishReason } from '@google/genai';
import type {
@@ -427,6 +426,7 @@ export const useGeminiStream = (
(tc) =>
tc.status === 'executing' && tc.request.name === 'run_shell_command',
);
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return (executingShellTool as TrackedExecutingToolCall | undefined)?.pid;
}, [toolCalls]);
@@ -551,6 +551,7 @@ export const useGeminiStream = (
// If it is a shell command, we update the status to Canceled and clear the output
// to avoid artifacts, then add it to history immediately.
if (isShellCommand) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const toolGroup = pendingHistoryItemRef.current as HistoryItemToolGroup;
const updatedTools = toolGroup.tools.map((tool) => {
if (tool.name === SHELL_COMMAND_NAME) {
@@ -764,6 +765,7 @@ export const useGeminiStream = (
if (splitPoint === newGeminiMessageBuffer.length) {
// Update the existing message with accumulated content
setPendingHistoryItem((item) => ({
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
type: item?.type as 'gemini' | 'gemini_content',
text: newGeminiMessageBuffer,
}));
@@ -780,6 +782,7 @@ export const useGeminiStream = (
const afterText = newGeminiMessageBuffer.substring(splitPoint);
addItem(
{
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
type: pendingHistoryItemRef.current?.type as
| 'gemini'
| 'gemini_content',
@@ -1372,13 +1375,10 @@ export const useGeminiStream = (
// Process pending tool calls sequentially to reduce UI chaos
for (const call of awaitingApprovalCalls) {
if (
(call.confirmationDetails as ToolCallConfirmationDetails)?.onConfirm
) {
const details = call.confirmationDetails;
if (details && 'onConfirm' in details) {
try {
await (
call.confirmationDetails as ToolCallConfirmationDetails
).onConfirm(ToolConfirmationOutcome.ProceedOnce);
await details.onConfirm(ToolConfirmationOutcome.ProceedOnce);
} catch (error) {
debugLogger.warn(
`Failed to auto-approve tool call ${call.request.callId}:`,
@@ -1444,7 +1444,9 @@ export const useGeminiStream = (
const pid = data?.pid;
if (isShell && pid) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const command = (data?.['command'] as string) ?? 'shell';
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const initialOutput = (data?.['initialOutput'] as string) ?? '';
registerBackgroundShell(pid, command, initialOutput);

View File

@@ -62,6 +62,7 @@ export function useHistory({
isResuming: boolean = false,
): number => {
const id = getNextMessageId(baseTimestamp);
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const newItem: HistoryItem = { ...itemData, id } as HistoryItem;
setHistory((prevHistory) => {
@@ -139,6 +140,7 @@ export function useHistory({
// Apply updates based on whether it's an object or a function
const newUpdates =
typeof updates === 'function' ? updates(item) : updates;
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return { ...item, ...newUpdates } as HistoryItem;
}
return item;

View File

@@ -38,6 +38,7 @@ async function finishAddingDirectories(
await refreshServerHierarchicalMemory(config);
}
} catch (error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
errors.push(`Error refreshing memory: ${(error as Error).message}`);
}

View File

@@ -106,6 +106,7 @@ async function getRemoteDataCollectionOptIn(
return resp.freeTierDataCollectionOptin;
} catch (error: unknown) {
if (error && typeof error === 'object' && 'response' in error) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const gaxiosError = error as {
response?: {
status?: unknown;

View File

@@ -127,6 +127,7 @@ export function useReactToolScheduler(
existingTrackedCall?.responseSubmittedToGemini ?? false;
if (coreTc.status === 'executing') {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const liveOutput = (existingTrackedCall as TrackedExecutingToolCall)
?.liveOutput;
return {

View File

@@ -56,6 +56,7 @@ export type KeyMatchers = {
export function createKeyMatchers(
config: KeyBindingConfig = defaultKeyBindings,
): KeyMatchers {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const matchers = {} as { [C in Command]: KeyMatcher };
for (const command of Object.values(Command)) {

View File

@@ -383,6 +383,7 @@ class ThemeManager {
// 3. Read, parse, and validate the theme file.
const themeContent = fs.readFileSync(canonicalPath, 'utf-8');
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const customThemeConfig = JSON.parse(themeContent) as CustomTheme;
const validation = validateCustomTheme(customThemeConfig);

View File

@@ -41,6 +41,7 @@ function renderHastNode(
// Handle Element Nodes: Determine color and pass it down, don't wrap
if (node.type === 'element') {
const nodeClasses: string[] =
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(node.properties?.['className'] as string[]) || [];
let elementColor: string | undefined = undefined;

View File

@@ -194,6 +194,7 @@ const writeAll = (stream: Writable, data: string): Promise<void> =>
// On Windows, writing directly to the underlying file descriptor bypasses
// application-level stream interception (e.g., by the Ink UI framework).
// This ensures the raw OSC-52 escape sequence reaches the terminal host uncorrupted.
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const fd = (stream as unknown as { fd?: number }).fd;
if (
process.platform === 'win32' &&
@@ -214,6 +215,7 @@ const writeAll = (stream: Writable, data: string): Promise<void> =>
const onError = (err: unknown) => {
cleanup();
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
reject(err as Error);
};
const onDrain = () => {
@@ -251,6 +253,7 @@ export const copyToClipboard = async (text: string): Promise<void> => {
await writeAll(tty!.stream, payload);
if (tty!.closeAfter) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(tty!.stream as fs.WriteStream).end();
}
return;

View File

@@ -174,6 +174,7 @@ export async function revertFileChanges(
try {
currentContent = await fs.readFile(filePath, 'utf8');
} catch (e) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const error = e as Error;
if ('code' in error && error.code === 'ENOENT') {
// File does not exist, which is fine in some revert scenarios.

View File

@@ -245,6 +245,7 @@ async function configureVSCodeStyle(
const results = targetBindings.map((target) => {
const hasOurBinding = keybindings.some((kb) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const binding = kb as {
command?: string;
args?: { text?: string };
@@ -258,6 +259,7 @@ async function configureVSCodeStyle(
});
const existingBinding = keybindings.find((kb) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const binding = kb as { key?: string };
return binding.key === target.key;
});

View File

@@ -203,6 +203,7 @@ export function escapeAnsiCtrlCodes<T>(obj: T): T {
}
regex.lastIndex = 0; // needed for global regex
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return obj.replace(regex, (match) =>
JSON.stringify(match).slice(1, -1),
) as T;
@@ -225,6 +226,7 @@ export function escapeAnsiCtrlCodes<T>(obj: T): T {
newArr[i] = escapedValue;
}
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
return (newArr !== null ? newArr : obj) as T;
}
@@ -232,6 +234,7 @@ export function escapeAnsiCtrlCodes<T>(obj: T): T {
const keys = Object.keys(obj);
for (const key of keys) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const value = (obj as Record<string, unknown>)[key];
const escapedValue = escapeAnsiCtrlCodes(value);
@@ -239,6 +242,7 @@ export function escapeAnsiCtrlCodes<T>(obj: T): T {
if (newObj === null) {
newObj = { ...obj };
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
(newObj as Record<string, unknown>)[key] = escapedValue;
}
}