Fix remaining tslint errors (YAY).

- Also updated ci.yml to ensure that linting failures will break the build.

Fully fixes https://b.corp.google.com/issues/411384603
This commit is contained in:
Taylor Mullen 2025-04-18 19:09:41 -04:00 committed by N. Taylor Mullen
parent 2a850ed051
commit 40e11e053c
21 changed files with 53 additions and 96 deletions

View File

@ -44,12 +44,11 @@ jobs:
# 5. Linting # 5. Linting
- name: Run linter - name: Run linter
run: npm run lint run: npm run lint
continue-on-error: true # TODO: Remove this when we have fixed lint errors
# 6. Type Checking # 6. Type Checking
- name: Run type check - name: Run type check
run: npm run typecheck # Or: tsc --noEmit run: npm run typecheck # Or: tsc --noEmit
continue-on-error: true # TODO: Remove this when we have fixed type errors continue-on-error: true
# 7. Build # 7. Build
# Optional if your tests run directly on TS files (e.g., using ts-jest, ts-node) # Optional if your tests run directly on TS files (e.g., using ts-jest, ts-node)

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { render } from 'ink'; import { render } from 'ink';
import App from './ui/App.js'; import { App } from './ui/App.js';
import { toolRegistry } from './tools/tool-registry.js'; import { toolRegistry } from './tools/tool-registry.js';
import { LSTool } from './tools/ls.tool.js'; import { LSTool } from './tools/ls.tool.js';
import { ReadFileTool } from './tools/read-file.tool.js'; import { ReadFileTool } from './tools/read-file.tool.js';

View File

@ -1,6 +1,7 @@
import { SchemaValidator } from '../utils/schemaValidator.js'; import { SchemaValidator } from '../utils/schemaValidator.js';
import { BaseTool, ToolResult } from './tools.js'; import { BaseTool, ToolResult } from './tools.js';
import { ToolCallConfirmationDetails } from '../ui/types.js'; // Added for shouldConfirmExecute import { ToolCallConfirmationDetails } from '../ui/types.js'; // Added for shouldConfirmExecute
import { getErrorMessage } from '../utils/errors.js';
/** /**
* Parameters for the WebFetch tool * Parameters for the WebFetch tool
@ -12,18 +13,10 @@ export interface WebFetchToolParams {
url: string; url: string;
} }
/**
* Standardized result from the WebFetch tool
*/
export interface WebFetchToolResult extends ToolResult {}
/** /**
* Implementation of the WebFetch tool that reads content from a URL. * Implementation of the WebFetch tool that reads content from a URL.
*/ */
export class WebFetchTool extends BaseTool< export class WebFetchTool extends BaseTool<WebFetchToolParams, ToolResult> {
WebFetchToolParams,
WebFetchToolResult
> {
static readonly Name: string = 'web_fetch'; static readonly Name: string = 'web_fetch';
/** /**
@ -73,7 +66,7 @@ export class WebFetchTool extends BaseTool<
if (!['http:', 'https:'].includes(parsedUrl.protocol)) { if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
return `Invalid URL protocol: "${parsedUrl.protocol}". Only 'http:' and 'https:' are supported.`; return `Invalid URL protocol: "${parsedUrl.protocol}". Only 'http:' and 'https:' are supported.`;
} }
} catch (error) { } catch {
// The URL constructor throws if the format is invalid // The URL constructor throws if the format is invalid
return `Invalid URL format: "${params.url}". Please provide a valid absolute URL (e.g., 'https://example.com').`; return `Invalid URL format: "${params.url}". Please provide a valid absolute URL (e.g., 'https://example.com').`;
} }
@ -101,6 +94,7 @@ export class WebFetchTool extends BaseTool<
* @returns Whether execute should be confirmed. * @returns Whether execute should be confirmed.
*/ */
async shouldConfirmExecute( async shouldConfirmExecute(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
params: WebFetchToolParams, params: WebFetchToolParams,
): Promise<ToolCallConfirmationDetails | false> { ): Promise<ToolCallConfirmationDetails | false> {
// Could add logic here to confirm based on domain, etc. if needed // Could add logic here to confirm based on domain, etc. if needed
@ -112,7 +106,7 @@ export class WebFetchTool extends BaseTool<
* @param params Parameters for the web fetch operation. * @param params Parameters for the web fetch operation.
* @returns Result with the fetched content or an error message. * @returns Result with the fetched content or an error message.
*/ */
async execute(params: WebFetchToolParams): Promise<WebFetchToolResult> { async execute(params: WebFetchToolParams): Promise<ToolResult> {
const validationError = this.invalidParams(params); const validationError = this.invalidParams(params);
if (validationError) { if (validationError) {
return { return {
@ -159,10 +153,10 @@ export class WebFetchTool extends BaseTool<
llmContent, llmContent,
returnDisplay: `Fetched content from ${url}`, // Simple display message returnDisplay: `Fetched content from ${url}`, // Simple display message
}; };
} catch (error: any) { } catch (error: unknown) {
// This catches network errors (DNS resolution, connection refused, etc.) // This catches network errors (DNS resolution, connection refused, etc.)
// and errors from the URL constructor if somehow bypassed validation (unlikely) // and errors from the URL constructor if somehow bypassed validation (unlikely)
const errorMessage = `Failed to fetch data from ${url}. Error: ${error instanceof Error ? error.message : String(error)}`; const errorMessage = `Failed to fetch data from ${url}. Error: ${getErrorMessage(error)}`;
return { return {
llmContent: `Error: ${errorMessage}`, llmContent: `Error: ${errorMessage}`,
returnDisplay: `**Error:** ${errorMessage}`, returnDisplay: `**Error:** ${errorMessage}`,

View File

@ -1,32 +1,27 @@
import React, { useState, useEffect } from 'react'; import React, { useState } from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import fs from 'fs';
import path from 'path';
import os from 'os';
import type { HistoryItem } from './types.js'; import type { HistoryItem } from './types.js';
import { useGeminiStream } from './hooks/useGeminiStream.js'; import { useGeminiStream } from './hooks/useGeminiStream.js';
import { useLoadingIndicator } from './hooks/useLoadingIndicator.js'; import { useLoadingIndicator } from './hooks/useLoadingIndicator.js';
import Header from './components/Header.js'; import { Header } from './components/Header.js';
import Tips from './components/Tips.js'; import { Tips } from './components/Tips.js';
import HistoryDisplay from './components/HistoryDisplay.js'; import { HistoryDisplay } from './components/HistoryDisplay.js';
import LoadingIndicator from './components/LoadingIndicator.js'; import { LoadingIndicator } from './components/LoadingIndicator.js';
import InputPrompt from './components/InputPrompt.js'; import { InputPrompt } from './components/InputPrompt.js';
import Footer from './components/Footer.js'; import { Footer } from './components/Footer.js';
import { StreamingState } from '../core/gemini-stream.js'; import { StreamingState } from '../core/gemini-stream.js';
import { PartListUnion } from '@google/genai'; import { PartListUnion } from '@google/genai';
import ITermDetectionWarning from './utils/itermDetection.js'; import { ITermDetectionWarning } from './utils/itermDetection.js';
import { import {
useStartupWarnings, useStartupWarnings,
useInitializationErrorEffect, useInitializationErrorEffect,
} from './hooks/useAppEffects.js'; } from './hooks/useAppEffects.js';
const warningsFilePath = path.join(os.tmpdir(), 'gemini-code-cli-warnings.txt');
interface AppProps { interface AppProps {
directory: string; directory: string;
} }
const App = ({ directory }: AppProps) => { export const App = ({ directory }: AppProps) => {
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
const [history, setHistory] = useState<HistoryItem[]>([]); const [history, setHistory] = useState<HistoryItem[]>([]);
const [startupWarnings, setStartupWarnings] = useState<string[]>([]); const [startupWarnings, setStartupWarnings] = useState<string[]>([]);
@ -138,5 +133,3 @@ const App = ({ directory }: AppProps) => {
</Box> </Box>
); );
}; };
export default App;

View File

@ -5,7 +5,7 @@ interface FooterProps {
queryLength: number; queryLength: number;
} }
const Footer: React.FC<FooterProps> = ({ queryLength }) => ( export const Footer: React.FC<FooterProps> = ({ queryLength }) => (
<Box marginTop={1} justifyContent="space-between"> <Box marginTop={1} justifyContent="space-between">
<Box minWidth={15}> <Box minWidth={15}>
<Text color="gray">{queryLength === 0 ? '? for shortcuts' : ''}</Text> <Text color="gray">{queryLength === 0 ? '? for shortcuts' : ''}</Text>
@ -13,5 +13,3 @@ const Footer: React.FC<FooterProps> = ({ queryLength }) => (
<Text color="blue">Gemini</Text> <Text color="blue">Gemini</Text>
</Box> </Box>
); );
export default Footer;

View File

@ -7,7 +7,7 @@ interface HeaderProps {
cwd: string; cwd: string;
} }
const Header: React.FC<HeaderProps> = ({ cwd }) => ( export const Header: React.FC<HeaderProps> = ({ cwd }) => (
<> <>
{/* Static Header Art */} {/* Static Header Art */}
<Box marginBottom={1}> <Box marginBottom={1}>
@ -34,5 +34,3 @@ const Header: React.FC<HeaderProps> = ({ cwd }) => (
</Box> </Box>
</> </>
); );
export default Header;

View File

@ -1,12 +1,11 @@
import React from 'react'; import React from 'react';
import { Box } from 'ink'; import { Box } from 'ink';
import type { HistoryItem } from '../types.js'; import type { HistoryItem } from '../types.js';
import { UI_WIDTH } from '../constants.js'; import { UserMessage } from './messages/UserMessage.js';
import UserMessage from './messages/UserMessage.js'; import { GeminiMessage } from './messages/GeminiMessage.js';
import GeminiMessage from './messages/GeminiMessage.js'; import { InfoMessage } from './messages/InfoMessage.js';
import InfoMessage from './messages/InfoMessage.js'; import { ErrorMessage } from './messages/ErrorMessage.js';
import ErrorMessage from './messages/ErrorMessage.js'; import { ToolGroupMessage } from './messages/ToolGroupMessage.js';
import ToolGroupMessage from './messages/ToolGroupMessage.js';
import { PartListUnion } from '@google/genai'; import { PartListUnion } from '@google/genai';
interface HistoryDisplayProps { interface HistoryDisplayProps {
@ -14,7 +13,7 @@ interface HistoryDisplayProps {
onSubmit: (value: PartListUnion) => void; onSubmit: (value: PartListUnion) => void;
} }
const HistoryDisplay: React.FC<HistoryDisplayProps> = ({ export const HistoryDisplay: React.FC<HistoryDisplayProps> = ({
history, history,
onSubmit, onSubmit,
}) => ( }) => (
@ -36,4 +35,3 @@ const HistoryDisplay: React.FC<HistoryDisplayProps> = ({
))} ))}
</Box> </Box>
); );
export default HistoryDisplay;

View File

@ -10,7 +10,7 @@ interface InputPromptProps {
isActive: boolean; isActive: boolean;
} }
const InputPrompt: React.FC<InputPromptProps> = ({ export const InputPrompt: React.FC<InputPromptProps> = ({
query, query,
setQuery, setQuery,
onSubmit, onSubmit,
@ -33,5 +33,3 @@ const InputPrompt: React.FC<InputPromptProps> = ({
</Box> </Box>
); );
}; };
export default InputPrompt;

View File

@ -8,7 +8,7 @@ interface LoadingIndicatorProps {
elapsedTime: number; elapsedTime: number;
} }
const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({ export const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
isLoading, isLoading,
currentLoadingPhrase, currentLoadingPhrase,
elapsedTime, elapsedTime,
@ -30,5 +30,3 @@ const LoadingIndicator: React.FC<LoadingIndicatorProps> = ({
</Box> </Box>
); );
}; };
export default LoadingIndicator;

View File

@ -2,7 +2,7 @@ import React from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
import { UI_WIDTH } from '../constants.js'; import { UI_WIDTH } from '../constants.js';
const Tips: React.FC = () => ( export const Tips: React.FC = () => (
<Box flexDirection="column" marginBottom={1} width={UI_WIDTH}> <Box flexDirection="column" marginBottom={1} width={UI_WIDTH}>
<Text>Tips for getting started:</Text> <Text>Tips for getting started:</Text>
<Text> <Text>
@ -16,5 +16,3 @@ const Tips: React.FC = () => (
<Text>4. Be specific for the best results.</Text> <Text>4. Be specific for the best results.</Text>
</Box> </Box>
); );
export default Tips;

View File

@ -85,7 +85,7 @@ interface DiffRendererProps {
const DEFAULT_TAB_WIDTH = 4; // Spaces per tab for normalization const DEFAULT_TAB_WIDTH = 4; // Spaces per tab for normalization
const DiffRenderer: React.FC<DiffRendererProps> = ({ export const DiffRenderer: React.FC<DiffRendererProps> = ({
diffContent, diffContent,
tabWidth = DEFAULT_TAB_WIDTH, tabWidth = DEFAULT_TAB_WIDTH,
}) => { }) => {
@ -157,6 +157,9 @@ const DiffRenderer: React.FC<DiffRendererProps> = ({
dim = true; dim = true;
prefixSymbol = ' '; prefixSymbol = ' ';
break; break;
default:
throw new Error(`Unknown line type: ${line.type}`);
break;
} }
// Render the line content *after* stripping the calculated *minimum* baseIndentation. // Render the line content *after* stripping the calculated *minimum* baseIndentation.
@ -179,5 +182,3 @@ const DiffRenderer: React.FC<DiffRendererProps> = ({
</Box> </Box>
); );
}; };
export default DiffRenderer;

View File

@ -5,7 +5,7 @@ interface ErrorMessageProps {
text: string; text: string;
} }
const ErrorMessage: React.FC<ErrorMessageProps> = ({ text }) => { export const ErrorMessage: React.FC<ErrorMessageProps> = ({ text }) => {
const prefix = '✕ '; const prefix = '✕ ';
const prefixWidth = prefix.length; const prefixWidth = prefix.length;
@ -22,5 +22,3 @@ const ErrorMessage: React.FC<ErrorMessageProps> = ({ text }) => {
</Box> </Box>
); );
}; };
export default ErrorMessage;

View File

@ -6,7 +6,7 @@ interface GeminiMessageProps {
text: string; text: string;
} }
const GeminiMessage: React.FC<GeminiMessageProps> = ({ text }) => { export const GeminiMessage: React.FC<GeminiMessageProps> = ({ text }) => {
const prefix = '✦ '; const prefix = '✦ ';
const prefixWidth = prefix.length; const prefixWidth = prefix.length;
@ -40,5 +40,3 @@ const GeminiMessage: React.FC<GeminiMessageProps> = ({ text }) => {
</Box> </Box>
); );
}; };
export default GeminiMessage;

View File

@ -5,7 +5,7 @@ interface InfoMessageProps {
text: string; text: string;
} }
const InfoMessage: React.FC<InfoMessageProps> = ({ text }) => { export const InfoMessage: React.FC<InfoMessageProps> = ({ text }) => {
const prefix = ' '; const prefix = ' ';
const prefixWidth = prefix.length; const prefixWidth = prefix.length;
@ -22,5 +22,3 @@ const InfoMessage: React.FC<InfoMessageProps> = ({ text }) => {
</Box> </Box>
); );
}; };
export default InfoMessage;

View File

@ -6,9 +6,9 @@ import {
ToolEditConfirmationDetails, ToolEditConfirmationDetails,
ToolConfirmationOutcome, ToolConfirmationOutcome,
ToolExecuteConfirmationDetails, ToolExecuteConfirmationDetails,
} from '../../types.js'; // Adjust path as needed } from '../../types.js';
import { PartListUnion } from '@google/genai'; import { PartListUnion } from '@google/genai';
import DiffRenderer from './DiffRenderer.js'; import { DiffRenderer } from './DiffRenderer.js';
import { UI_WIDTH } from '../../constants.js'; import { UI_WIDTH } from '../../constants.js';
export interface ToolConfirmationMessageProps { export interface ToolConfirmationMessageProps {
@ -27,9 +27,9 @@ interface InternalOption {
value: ToolConfirmationOutcome; value: ToolConfirmationOutcome;
} }
const ToolConfirmationMessage: React.FC<ToolConfirmationMessageProps> = ({ export const ToolConfirmationMessage: React.FC<
confirmationDetails, ToolConfirmationMessageProps
}) => { > = ({ confirmationDetails }) => {
const { onConfirm } = confirmationDetails; const { onConfirm } = confirmationDetails;
useInput((_, key) => { useInput((_, key) => {
@ -42,14 +42,11 @@ const ToolConfirmationMessage: React.FC<ToolConfirmationMessageProps> = ({
onConfirm(item.value); onConfirm(item.value);
}; };
let title: string;
let bodyContent: React.ReactNode | null = null; // Removed contextDisplay here let bodyContent: React.ReactNode | null = null; // Removed contextDisplay here
let question: string; let question: string;
const options: InternalOption[] = []; const options: InternalOption[] = [];
if (isEditDetails(confirmationDetails)) { if (isEditDetails(confirmationDetails)) {
title = 'Edit'; // Title for the outer box
// Body content is now the DiffRenderer, passing filename to it // Body content is now the DiffRenderer, passing filename to it
// The bordered box is removed from here and handled within DiffRenderer // The bordered box is removed from here and handled within DiffRenderer
bodyContent = <DiffRenderer diffContent={confirmationDetails.fileDiff} />; bodyContent = <DiffRenderer diffContent={confirmationDetails.fileDiff} />;
@ -69,7 +66,6 @@ const ToolConfirmationMessage: React.FC<ToolConfirmationMessageProps> = ({
} else { } else {
const executionProps = const executionProps =
confirmationDetails as ToolExecuteConfirmationDetails; confirmationDetails as ToolExecuteConfirmationDetails;
title = 'Execute Command'; // Title for the outer box
// For execution, we still need context display and description // For execution, we still need context display and description
const commandDisplay = <Text color="cyan">{executionProps.command}</Text>; const commandDisplay = <Text color="cyan">{executionProps.command}</Text>;
@ -118,5 +114,3 @@ const ToolConfirmationMessage: React.FC<ToolConfirmationMessageProps> = ({
</Box> </Box>
); );
}; };
export default ToolConfirmationMessage;

View File

@ -1,9 +1,9 @@
import React from 'react'; import React from 'react';
import { Box } from 'ink'; import { Box } from 'ink';
import { IndividualToolCallDisplay, ToolCallStatus } from '../../types.js'; import { IndividualToolCallDisplay, ToolCallStatus } from '../../types.js';
import ToolMessage from './ToolMessage.js'; import { ToolMessage } from './ToolMessage.js';
import { PartListUnion } from '@google/genai'; import { PartListUnion } from '@google/genai';
import ToolConfirmationMessage from './ToolConfirmationMessage.js'; import { ToolConfirmationMessage } from './ToolConfirmationMessage.js';
interface ToolGroupMessageProps { interface ToolGroupMessageProps {
toolCalls: IndividualToolCallDisplay[]; toolCalls: IndividualToolCallDisplay[];
@ -11,7 +11,7 @@ interface ToolGroupMessageProps {
} }
// Main component renders the border and maps the tools using ToolMessage // Main component renders the border and maps the tools using ToolMessage
const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({ export const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
toolCalls, toolCalls,
onSubmit, onSubmit,
}) => { }) => {
@ -44,5 +44,3 @@ const ToolGroupMessage: React.FC<ToolGroupMessageProps> = ({
</Box> </Box>
); );
}; };
export default ToolGroupMessage;

View File

@ -3,7 +3,7 @@ import { Box, Text } from 'ink';
import Spinner from 'ink-spinner'; import Spinner from 'ink-spinner';
import { ToolCallStatus } from '../../types.js'; import { ToolCallStatus } from '../../types.js';
import { ToolResultDisplay } from '../../../tools/tools.js'; import { ToolResultDisplay } from '../../../tools/tools.js';
import DiffRenderer from './DiffRenderer.js'; import { DiffRenderer } from './DiffRenderer.js';
import { MarkdownRenderer } from '../../utils/MarkdownRenderer.js'; import { MarkdownRenderer } from '../../utils/MarkdownRenderer.js';
interface ToolMessageProps { interface ToolMessageProps {
@ -13,7 +13,7 @@ interface ToolMessageProps {
status: ToolCallStatus; status: ToolCallStatus;
} }
const ToolMessage: React.FC<ToolMessageProps> = ({ export const ToolMessage: React.FC<ToolMessageProps> = ({
name, name,
description, description,
resultDisplay, resultDisplay,
@ -70,5 +70,3 @@ const ToolMessage: React.FC<ToolMessageProps> = ({
</Box> </Box>
); );
}; };
export default ToolMessage;

View File

@ -5,7 +5,7 @@ interface UserMessageProps {
text: string; text: string;
} }
const UserMessage: React.FC<UserMessageProps> = ({ text }) => { export const UserMessage: React.FC<UserMessageProps> = ({ text }) => {
const prefix = '> '; const prefix = '> ';
const prefixWidth = prefix.length; const prefixWidth = prefix.length;
@ -20,5 +20,3 @@ const UserMessage: React.FC<UserMessageProps> = ({ text }) => {
</Box> </Box>
); );
}; };
export default UserMessage;

View File

@ -3,6 +3,7 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
import os from 'os'; import os from 'os';
import type { HistoryItem } from '../types.js'; import type { HistoryItem } from '../types.js';
import { getErrorMessage } from '../../utils/errors.js';
const warningsFilePath = path.join(os.tmpdir(), 'gemini-code-cli-warnings.txt'); const warningsFilePath = path.join(os.tmpdir(), 'gemini-code-cli-warnings.txt');
@ -19,17 +20,17 @@ export function useStartupWarnings(
); );
try { try {
fs.unlinkSync(warningsFilePath); fs.unlinkSync(warningsFilePath);
} catch (unlinkErr: any) { } catch {
setStartupWarnings((prev) => [ setStartupWarnings((prev) => [
...prev, ...prev,
`Warning: Could not delete temporary warnings file.`, `Warning: Could not delete temporary warnings file.`,
]); ]);
} }
} }
} catch (err: any) { } catch (err: unknown) {
setStartupWarnings((prev) => [ setStartupWarnings((prev) => [
...prev, ...prev,
`Error checking/reading warnings file: ${err.message}`, `Error checking/reading warnings file: ${getErrorMessage(err)}`,
]); ]);
} }
}, [setStartupWarnings]); // Include setStartupWarnings in dependency array }, [setStartupWarnings]); // Include setStartupWarnings in dependency array

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { Box, Text } from 'ink'; import { Box, Text } from 'ink';
const ITermDetectionWarning: React.FC = () => { export const ITermDetectionWarning: React.FC = () => {
if (process.env.TERM_PROGRAM !== 'iTerm.app') { if (process.env.TERM_PROGRAM !== 'iTerm.app') {
return null; // Don't render anything if not in iTerm return null; // Don't render anything if not in iTerm
} }
@ -12,5 +12,3 @@ const ITermDetectionWarning: React.FC = () => {
</Box> </Box>
); );
}; };
export default ITermDetectionWarning;

View File

@ -1,3 +1,4 @@
/* eslint-disable no-undef */
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import os from 'os'; // Import os module import os from 'os'; // Import os module