diff --git a/eslint.config.js b/eslint.config.js index 38dec43857..26e15aa9b8 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -35,6 +35,12 @@ const commonRestrictedSyntaxRules = [ message: 'Do not throw string literals or non-Error objects. Throw new Error("...") instead.', }, + { + selector: + 'UnaryExpression[operator="typeof"] > MemberExpression[computed=true][property.type="Literal"]', + message: + 'Do not use typeof to check object properties. Define a TypeScript interface and a type guard function instead.', + }, ]; export default tseslint.config( @@ -133,16 +139,7 @@ export default tseslint.config( 'no-cond-assign': 'error', 'no-debugger': 'error', 'no-duplicate-case': 'error', - 'no-restricted-syntax': [ - 'error', - ...commonRestrictedSyntaxRules, - { - selector: - 'UnaryExpression[operator="typeof"] > MemberExpression[computed=true][property.type="Literal"]', - message: - 'Do not use typeof to check object properties. Define a TypeScript interface and a type guard function instead.', - }, - ], + 'no-restricted-syntax': ['error', ...commonRestrictedSyntaxRules], 'no-unsafe-finally': 'error', 'no-unused-expressions': 'off', // Disable base rule '@typescript-eslint/no-unused-expressions': [ diff --git a/package.json b/package.json index 72676cf90b..414f9341ac 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "test:integration:sandbox:none": "cross-env GEMINI_SANDBOX=false vitest run --root ./integration-tests", "test:integration:sandbox:docker": "cross-env GEMINI_SANDBOX=docker npm run build:sandbox && cross-env GEMINI_SANDBOX=docker vitest run --root ./integration-tests", "test:integration:sandbox:podman": "cross-env GEMINI_SANDBOX=podman vitest run --root ./integration-tests", - "lint": "eslint . --cache", + "lint": "eslint . --cache --max-warnings 0", "lint:fix": "eslint . --fix --ext .ts,.tsx && eslint integration-tests --fix && eslint scripts --fix && npm run format", "lint:ci": "npm run lint:all", "lint:all": "node scripts/lint.js", diff --git a/packages/cli/src/nonInteractiveCli.test.ts b/packages/cli/src/nonInteractiveCli.test.ts index 206d011e63..4e45b0f188 100644 --- a/packages/cli/src/nonInteractiveCli.test.ts +++ b/packages/cli/src/nonInteractiveCli.test.ts @@ -1137,6 +1137,7 @@ describe('runNonInteractive', () => { expect( processStderrSpy.mock.calls.some( + // eslint-disable-next-line no-restricted-syntax (call) => typeof call[0] === 'string' && call[0].includes('Cancelling'), ), ).toBe(true); diff --git a/packages/cli/src/test-utils/customMatchers.ts b/packages/cli/src/test-utils/customMatchers.ts index ae9b44ee44..d34576cf3f 100644 --- a/packages/cli/src/test-utils/customMatchers.ts +++ b/packages/cli/src/test-utils/customMatchers.ts @@ -79,7 +79,7 @@ export async function toMatchSvgSnapshot( } function toHaveOnlyValidCharacters(this: Assertion, buffer: TextBuffer) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-unsafe-assignment + // eslint-disable-next-line @typescript-eslint/no-explicit-any const { isNot } = this as any; let pass = true; const invalidLines: Array<{ line: number; content: string }> = []; @@ -108,7 +108,6 @@ function toHaveOnlyValidCharacters(this: Assertion, buffer: TextBuffer) { }; } -// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion expect.extend({ toHaveOnlyValidCharacters, toMatchSvgSnapshot, diff --git a/packages/cli/src/test-utils/mockCommandContext.ts b/packages/cli/src/test-utils/mockCommandContext.ts index 15e6422e1a..6eda7f3109 100644 --- a/packages/cli/src/test-utils/mockCommandContext.ts +++ b/packages/cli/src/test-utils/mockCommandContext.ts @@ -37,14 +37,12 @@ export const createMockCommandContext = ( }, services: { agentContext: null, - settings: { merged: defaultMergedSettings, setValue: vi.fn(), forScope: vi.fn().mockReturnValue({ settings: {} }), } as unknown as LoadedSettings, git: undefined as GitService | undefined, - logger: { log: vi.fn(), logMessage: vi.fn(), @@ -53,7 +51,6 @@ export const createMockCommandContext = ( // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any, // Cast because Logger is a class. }, - ui: { addItem: vi.fn(), clear: vi.fn(), @@ -72,7 +69,6 @@ export const createMockCommandContext = ( } as any, session: { sessionShellAllowlist: new Set(), - stats: { sessionStartTime: new Date(), lastPromptTokenCount: 0, @@ -98,7 +94,6 @@ export const createMockCommandContext = ( for (const key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { const sourceValue = source[key]; - const targetValue = output[key]; if ( @@ -109,7 +104,6 @@ export const createMockCommandContext = ( output[key] = merge(targetValue, sourceValue); } else { // If not, we do a direct assignment. This preserves Date objects and others. - output[key] = sourceValue; } } diff --git a/packages/cli/src/test-utils/render.tsx b/packages/cli/src/test-utils/render.tsx index a655088e79..04a642d687 100644 --- a/packages/cli/src/test-utils/render.tsx +++ b/packages/cli/src/test-utils/render.tsx @@ -778,7 +778,6 @@ export async function renderHook( generateSvg: () => string; }> { const result = { current: undefined as unknown as Result }; - let currentProps = options?.initialProps as Props; function TestComponent({ diff --git a/packages/cli/src/test-utils/settings.ts b/packages/cli/src/test-utils/settings.ts index ab2420849d..20d0613f83 100644 --- a/packages/cli/src/test-utils/settings.ts +++ b/packages/cli/src/test-utils/settings.ts @@ -46,7 +46,6 @@ export const createMockSettings = ( workspace, isTrusted, errors, - merged: mergedOverride, ...settingsOverrides } = overrides; @@ -61,7 +60,6 @@ export const createMockSettings = ( settings: settingsOverrides, originalSettings: settingsOverrides, }, - (workspace as any) || { path: '', settings: {}, originalSettings: {} }, isTrusted ?? true, errors || [], diff --git a/packages/cli/src/ui/IdeIntegrationNudge.test.tsx b/packages/cli/src/ui/IdeIntegrationNudge.test.tsx index eb3e6a3e4c..d05a17dad8 100644 --- a/packages/cli/src/ui/IdeIntegrationNudge.test.tsx +++ b/packages/cli/src/ui/IdeIntegrationNudge.test.tsx @@ -42,6 +42,7 @@ describe('IdeIntegrationNudge', () => { beforeEach(() => { vi.mocked(debugLogger.warn).mockImplementation((...args) => { if ( + // eslint-disable-next-line no-restricted-syntax typeof args[0] === 'string' && /was not wrapped in act/.test(args[0]) ) { diff --git a/packages/cli/src/ui/auth/AuthInProgress.test.tsx b/packages/cli/src/ui/auth/AuthInProgress.test.tsx index 1c392be28d..a387fcb6f3 100644 --- a/packages/cli/src/ui/auth/AuthInProgress.test.tsx +++ b/packages/cli/src/ui/auth/AuthInProgress.test.tsx @@ -42,6 +42,7 @@ describe('AuthInProgress', () => { vi.useFakeTimers(); vi.mocked(debugLogger.error).mockImplementation((...args) => { if ( + // eslint-disable-next-line no-restricted-syntax typeof args[0] === 'string' && args[0].includes('was not wrapped in act') ) { diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.ts b/packages/cli/src/ui/hooks/slashCommandProcessor.ts index 20ed225186..1839670df7 100644 --- a/packages/cli/src/ui/hooks/slashCommandProcessor.ts +++ b/packages/cli/src/ui/hooks/slashCommandProcessor.ts @@ -505,7 +505,9 @@ export const useSlashCommandProcessor = ( const props = result.props as Record; if ( !props || + // eslint-disable-next-line no-restricted-syntax typeof props['name'] !== 'string' || + // eslint-disable-next-line no-restricted-syntax typeof props['displayName'] !== 'string' || !props['definition'] ) { diff --git a/packages/cli/src/ui/utils/textUtils.test.ts b/packages/cli/src/ui/utils/textUtils.test.ts index b06fa62f5e..7ec515ffb1 100644 --- a/packages/cli/src/ui/utils/textUtils.test.ts +++ b/packages/cli/src/ui/utils/textUtils.test.ts @@ -514,6 +514,7 @@ describe('textUtils', () => { const b = sanitized.b as { c: string; d: Array }; expect(b.c).toBe('\\u001b[32mgreen\\u001b[0m'); expect(b.d[0]).toBe('\\u001b[33myellow\\u001b[0m'); + // eslint-disable-next-line no-restricted-syntax if (typeof b.d[1] === 'object' && b.d[1] !== null) { const e = b.d[1] as { e: string }; expect(e.e).toBe('\\u001b[34mblue\\u001b[0m'); diff --git a/packages/cli/src/utils/cleanup.test.ts b/packages/cli/src/utils/cleanup.test.ts index e9a2b0ea76..a722e1a737 100644 --- a/packages/cli/src/utils/cleanup.test.ts +++ b/packages/cli/src/utils/cleanup.test.ts @@ -183,6 +183,7 @@ describe('signal and TTY handling', () => { const sigtermHandlers = processOnHandlers.get('SIGTERM') || []; expect(sigtermHandlers.length).toBeGreaterThan(0); + // eslint-disable-next-line no-restricted-syntax expect(typeof sigtermHandlers[0]).toBe('function'); }); }); diff --git a/packages/cli/src/utils/sessions.test.ts b/packages/cli/src/utils/sessions.test.ts index 965a595c53..5c91bf0d50 100644 --- a/packages/cli/src/utils/sessions.test.ts +++ b/packages/cli/src/utils/sessions.test.ts @@ -214,6 +214,7 @@ describe('listSessions', () => { // Get all the session log calls (skip the header) const sessionCalls = mocks.writeToStdout.mock.calls.filter( (call): call is [string] => + // eslint-disable-next-line no-restricted-syntax typeof call[0] === 'string' && call[0].includes('[session-') && !call[0].includes('Available sessions'), diff --git a/packages/core/src/services/FolderTrustDiscoveryService.ts b/packages/core/src/services/FolderTrustDiscoveryService.ts index 499077d33f..6e8b7b1c32 100644 --- a/packages/core/src/services/FolderTrustDiscoveryService.ts +++ b/packages/core/src/services/FolderTrustDiscoveryService.ts @@ -163,6 +163,7 @@ export class FolderTrustDiscoveryService { for (const event of Object.values(hooksConfig)) { if (!Array.isArray(event)) continue; for (const hook of event) { + // eslint-disable-next-line no-restricted-syntax if (this.isRecord(hook) && typeof hook['command'] === 'string') { hooks.add(hook['command']); } diff --git a/packages/core/src/telemetry/sanitize.test.ts b/packages/core/src/telemetry/sanitize.test.ts index 5ac5374d01..71863011c0 100644 --- a/packages/core/src/telemetry/sanitize.test.ts +++ b/packages/core/src/telemetry/sanitize.test.ts @@ -136,7 +136,9 @@ describe('Telemetry Sanitization', () => { const attributes = event.toOpenTelemetryAttributes(config); // Should be JSON stringified + // eslint-disable-next-line no-restricted-syntax expect(typeof attributes['hook_input']).toBe('string'); + // eslint-disable-next-line no-restricted-syntax expect(typeof attributes['hook_output']).toBe('string'); const parsedInput = JSON.parse(attributes['hook_input'] as string); diff --git a/packages/core/src/test-utils/mock-message-bus.ts b/packages/core/src/test-utils/mock-message-bus.ts index 05ed8cb32d..c28f077bf2 100644 --- a/packages/core/src/test-utils/mock-message-bus.ts +++ b/packages/core/src/test-utils/mock-message-bus.ts @@ -62,7 +62,6 @@ export class MockMessageBus { if (!this.subscriptions.has(type)) { this.subscriptions.set(type, new Set()); } - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion this.subscriptions.get(type)!.add(listener as (message: Message) => void); }, ); @@ -74,7 +73,6 @@ export class MockMessageBus { (type: T['type'], listener: (message: T) => void) => { const listeners = this.subscriptions.get(type); if (listeners) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion listeners.delete(listener as (message: Message) => void); } }, @@ -103,7 +101,6 @@ export class MockMessageBus { * Create a mock MessageBus for testing */ export function createMockMessageBus(): MessageBus { - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion return new MockMessageBus() as unknown as MessageBus; } @@ -113,6 +110,5 @@ export function createMockMessageBus(): MessageBus { export function getMockMessageBusInstance( messageBus: MessageBus, ): MockMessageBus { - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion return messageBus as unknown as MockMessageBus; } diff --git a/packages/core/src/test-utils/mockWorkspaceContext.ts b/packages/core/src/test-utils/mockWorkspaceContext.ts index 640b51f616..67c614e9f5 100644 --- a/packages/core/src/test-utils/mockWorkspaceContext.ts +++ b/packages/core/src/test-utils/mockWorkspaceContext.ts @@ -19,7 +19,6 @@ export function createMockWorkspaceContext( ): WorkspaceContext { const allDirs = [rootDir, ...additionalDirs]; - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion const mockWorkspaceContext = { addDirectory: vi.fn(), getDirectories: vi.fn().mockReturnValue(allDirs), diff --git a/packages/core/src/tools/read-file.test.ts b/packages/core/src/tools/read-file.test.ts index fa7a0669d6..584155ce29 100644 --- a/packages/core/src/tools/read-file.test.ts +++ b/packages/core/src/tools/read-file.test.ts @@ -674,6 +674,7 @@ describe('ReadFileTool', () => { const parts = result.llmContent as Array>; const jitTextPart = parts.find( (p) => + // eslint-disable-next-line no-restricted-syntax typeof p['text'] === 'string' && p['text'].includes('Auth rules'), ); expect(jitTextPart).toBeDefined();