diff --git a/eslint.config.js b/eslint.config.js index d305f75f87..d3a267f30a 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -132,7 +132,16 @@ export default tseslint.config( 'no-cond-assign': 'error', 'no-debugger': 'error', 'no-duplicate-case': 'error', - 'no-restricted-syntax': ['error', ...commonRestrictedSyntaxRules], + '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-unsafe-finally': 'error', 'no-unused-expressions': 'off', // Disable base rule '@typescript-eslint/no-unused-expressions': [ @@ -263,6 +272,7 @@ export default tseslint.config( ...vitest.configs.recommended.rules, 'vitest/expect-expect': 'off', 'vitest/no-commented-out-tests': 'off', + 'no-restricted-syntax': ['error', ...commonRestrictedSyntaxRules], }, }, { diff --git a/packages/a2a-server/src/agent/task.ts b/packages/a2a-server/src/agent/task.ts index fe15aed37b..ef15a907e6 100644 --- a/packages/a2a-server/src/agent/task.ts +++ b/packages/a2a-server/src/agent/task.ts @@ -832,7 +832,9 @@ export class Task { if ( part.kind !== 'data' || !part.data || + // eslint-disable-next-line no-restricted-syntax typeof part.data['callId'] !== 'string' || + // eslint-disable-next-line no-restricted-syntax typeof part.data['outcome'] !== 'string' ) { return false; diff --git a/packages/cli/src/commands/hooks/migrate.ts b/packages/cli/src/commands/hooks/migrate.ts index 47cc8660d7..36bb2cf9aa 100644 --- a/packages/cli/src/commands/hooks/migrate.ts +++ b/packages/cli/src/commands/hooks/migrate.ts @@ -79,6 +79,7 @@ function migrateClaudeHook(claudeHook: unknown): unknown { migrated['command'] = hook['command']; // Replace CLAUDE_PROJECT_DIR with GEMINI_PROJECT_DIR in command + // eslint-disable-next-line no-restricted-syntax if (typeof migrated['command'] === 'string') { migrated['command'] = migrated['command'].replace( /\$CLAUDE_PROJECT_DIR/g, @@ -93,6 +94,7 @@ function migrateClaudeHook(claudeHook: unknown): unknown { } // Map timeout field (Claude uses seconds, Gemini uses seconds) + // eslint-disable-next-line no-restricted-syntax if ('timeout' in hook && typeof hook['timeout'] === 'number') { migrated['timeout'] = hook['timeout']; } @@ -140,6 +142,7 @@ function migrateClaudeHooks(claudeConfig: unknown): Record { // Transform matcher if ( 'matcher' in definition && + // eslint-disable-next-line no-restricted-syntax typeof definition['matcher'] === 'string' ) { migratedDef['matcher'] = transformMatcher(definition['matcher']); diff --git a/packages/cli/src/test-utils/mockDebugLogger.ts b/packages/cli/src/test-utils/mockDebugLogger.ts index 02eb3b05d9..bc0cde9010 100644 --- a/packages/cli/src/test-utils/mockDebugLogger.ts +++ b/packages/cli/src/test-utils/mockDebugLogger.ts @@ -65,6 +65,7 @@ export function mockCoreDebugLogger>( return { ...actual, coreEvents: { + // eslint-disable-next-line no-restricted-syntax ...(typeof actual['coreEvents'] === 'object' && actual['coreEvents'] !== null ? actual['coreEvents'] diff --git a/packages/cli/src/test-utils/render.tsx b/packages/cli/src/test-utils/render.tsx index 06f99c135c..39425af171 100644 --- a/packages/cli/src/test-utils/render.tsx +++ b/packages/cli/src/test-utils/render.tsx @@ -96,6 +96,7 @@ function isInkRenderMetrics( typeof m === 'object' && m !== null && 'output' in m && + // eslint-disable-next-line no-restricted-syntax typeof m['output'] === 'string' ); } diff --git a/packages/cli/src/ui/hooks/slashCommandProcessor.ts b/packages/cli/src/ui/hooks/slashCommandProcessor.ts index c3f178ad1b..20a76dcf43 100644 --- a/packages/cli/src/ui/hooks/slashCommandProcessor.ts +++ b/packages/cli/src/ui/hooks/slashCommandProcessor.ts @@ -502,7 +502,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/utils/activityLogger.ts b/packages/cli/src/utils/activityLogger.ts index a6f903fe49..14cef88a54 100644 --- a/packages/cli/src/utils/activityLogger.ts +++ b/packages/cli/src/utils/activityLogger.ts @@ -494,9 +494,10 @@ export class ActivityLogger extends EventEmitter { req.write = function (chunk: string | Uint8Array, ...etc: unknown[]) { if (chunk) { + const arg0 = etc[0]; const encoding = - typeof etc[0] === 'string' && Buffer.isEncoding(etc[0]) - ? etc[0] + typeof arg0 === 'string' && Buffer.isEncoding(arg0) + ? arg0 : undefined; requestChunks.push( Buffer.isBuffer(chunk) @@ -519,9 +520,10 @@ export class ActivityLogger extends EventEmitter { ) { const chunk = typeof chunkOrCb === 'function' ? undefined : chunkOrCb; if (chunk) { + const arg0 = etc[0]; const encoding = - typeof etc[0] === 'string' && Buffer.isEncoding(etc[0]) - ? etc[0] + typeof arg0 === 'string' && Buffer.isEncoding(arg0) + ? arg0 : undefined; requestChunks.push( Buffer.isBuffer(chunk) diff --git a/packages/core/src/agents/browser/browserAgentInvocation.ts b/packages/core/src/agents/browser/browserAgentInvocation.ts index 9df543300e..b503cc1214 100644 --- a/packages/core/src/agents/browser/browserAgentInvocation.ts +++ b/packages/core/src/agents/browser/browserAgentInvocation.ts @@ -119,6 +119,7 @@ export class BrowserAgentInvocation extends BaseToolInvocation< if ( activity.type === 'THOUGHT_CHUNK' && + // eslint-disable-next-line no-restricted-syntax typeof activity.data['text'] === 'string' ) { updateOutput(`🌐💭 ${activity.data['text']}`); diff --git a/packages/core/src/agents/browser/mcpToolWrapper.ts b/packages/core/src/agents/browser/mcpToolWrapper.ts index 1838a01b42..96b6aa9b68 100644 --- a/packages/core/src/agents/browser/mcpToolWrapper.ts +++ b/packages/core/src/agents/browser/mcpToolWrapper.ts @@ -356,6 +356,7 @@ class TypeTextDeclarativeTool extends DeclarativeTool< params: Record, ): ToolInvocation, ToolResult> { const submitKey = + // eslint-disable-next-line no-restricted-syntax typeof params['submitKey'] === 'string' && params['submitKey'] ? params['submitKey'] : undefined; diff --git a/packages/core/src/hooks/hookAggregator.ts b/packages/core/src/hooks/hookAggregator.ts index 523bc823fd..73e814702e 100644 --- a/packages/core/src/hooks/hookAggregator.ts +++ b/packages/core/src/hooks/hookAggregator.ts @@ -355,6 +355,7 @@ export class HookAggregator { // Extract additionalContext from various hook types if ( 'additionalContext' in specific && + // eslint-disable-next-line no-restricted-syntax typeof specific['additionalContext'] === 'string' ) { contexts.push(specific['additionalContext']); diff --git a/packages/core/src/services/FolderTrustDiscoveryService.ts b/packages/core/src/services/FolderTrustDiscoveryService.ts index e81273af22..bdf5d76297 100644 --- a/packages/core/src/services/FolderTrustDiscoveryService.ts +++ b/packages/core/src/services/FolderTrustDiscoveryService.ts @@ -132,7 +132,11 @@ export class FolderTrustDiscoveryService { for (const event of Object.values(hooksConfig)) { if (!Array.isArray(event)) continue; for (const hook of event) { - if (this.isRecord(hook) && typeof hook['command'] === 'string') { + if ( + this.isRecord(hook) && + // eslint-disable-next-line no-restricted-syntax + typeof hook['command'] === 'string' + ) { hooks.add(hook['command']); } } diff --git a/packages/core/src/services/chatCompressionService.ts b/packages/core/src/services/chatCompressionService.ts index 5303a1a82a..8dceb18f4b 100644 --- a/packages/core/src/services/chatCompressionService.ts +++ b/packages/core/src/services/chatCompressionService.ts @@ -156,11 +156,13 @@ async function truncateHistoryToBudget( } else if (responseObj && typeof responseObj === 'object') { if ( 'output' in responseObj && + // eslint-disable-next-line no-restricted-syntax typeof responseObj['output'] === 'string' ) { contentStr = responseObj['output']; } else if ( 'content' in responseObj && + // eslint-disable-next-line no-restricted-syntax typeof responseObj['content'] === 'string' ) { contentStr = responseObj['content']; diff --git a/packages/core/src/services/loopDetectionService.ts b/packages/core/src/services/loopDetectionService.ts index e87de721c6..9bc8b406f8 100644 --- a/packages/core/src/services/loopDetectionService.ts +++ b/packages/core/src/services/loopDetectionService.ts @@ -579,10 +579,12 @@ export class LoopDetectionService { } const flashConfidence = + // eslint-disable-next-line no-restricted-syntax typeof flashResult['unproductive_state_confidence'] === 'number' ? flashResult['unproductive_state_confidence'] : 0; const flashAnalysis = + // eslint-disable-next-line no-restricted-syntax typeof flashResult['unproductive_state_analysis'] === 'string' ? flashResult['unproductive_state_analysis'] : ''; @@ -628,11 +630,13 @@ export class LoopDetectionService { const mainModelConfidence = mainModelResult && + // eslint-disable-next-line no-restricted-syntax typeof mainModelResult['unproductive_state_confidence'] === 'number' ? mainModelResult['unproductive_state_confidence'] : 0; const mainModelAnalysis = mainModelResult && + // eslint-disable-next-line no-restricted-syntax typeof mainModelResult['unproductive_state_analysis'] === 'string' ? mainModelResult['unproductive_state_analysis'] : undefined; @@ -681,6 +685,7 @@ export class LoopDetectionService { if ( result && + // eslint-disable-next-line no-restricted-syntax typeof result['unproductive_state_confidence'] === 'number' ) { return result; diff --git a/packages/core/src/telemetry/semantic.ts b/packages/core/src/telemetry/semantic.ts index cb38502c91..6dae06d381 100644 --- a/packages/core/src/telemetry/semantic.ts +++ b/packages/core/src/telemetry/semantic.ts @@ -63,6 +63,7 @@ function getStringReferences(parts: AnyPart[]): StringReference[] { }); } } else if (part instanceof GenericPart) { + // eslint-disable-next-line no-restricted-syntax if (part.type === 'executableCode' && typeof part['code'] === 'string') { refs.push({ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion @@ -73,6 +74,7 @@ function getStringReferences(parts: AnyPart[]): StringReference[] { }); } else if ( part.type === 'codeExecutionResult' && + // eslint-disable-next-line no-restricted-syntax typeof part['output'] === 'string' ) { refs.push({ diff --git a/packages/core/src/tools/mcp-tool.ts b/packages/core/src/tools/mcp-tool.ts index 5f99262aab..f67d1f9bea 100644 --- a/packages/core/src/tools/mcp-tool.ts +++ b/packages/core/src/tools/mcp-tool.ts @@ -107,7 +107,7 @@ export function isMcpToolAnnotation( return ( typeof annotation === 'object' && annotation !== null && - // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, no-restricted-syntax typeof (annotation as Record)['_serverName'] === 'string' ); } diff --git a/packages/core/src/utils/editCorrector.ts b/packages/core/src/utils/editCorrector.ts index f8ff81b97e..2c58bad98f 100644 --- a/packages/core/src/utils/editCorrector.ts +++ b/packages/core/src/utils/editCorrector.ts @@ -112,6 +112,7 @@ Return ONLY the corrected string in the specified JSON format with the key 'corr if ( result && + // eslint-disable-next-line no-restricted-syntax typeof result['corrected_string_escaping'] === 'string' && result['corrected_string_escaping'].length > 0 ) { diff --git a/packages/core/src/utils/googleErrors.ts b/packages/core/src/utils/googleErrors.ts index f7f972f568..c9acb341bb 100644 --- a/packages/core/src/utils/googleErrors.ts +++ b/packages/core/src/utils/googleErrors.ts @@ -209,6 +209,7 @@ export function parseGoogleApiError(error: unknown): GoogleApiError | null { } // Basic structural check before casting. // Since the proto definitions are loose, we primarily rely on @type presence. + // eslint-disable-next-line no-restricted-syntax if (typeof detailObj['@type'] === 'string') { // We can just cast it; the consumer will have to switch on @type // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion diff --git a/packages/core/src/utils/oauth-flow.ts b/packages/core/src/utils/oauth-flow.ts index 9d5e6b8357..e13fd37837 100644 --- a/packages/core/src/utils/oauth-flow.ts +++ b/packages/core/src/utils/oauth-flow.ts @@ -361,19 +361,24 @@ async function parseTokenEndpointResponse( data && typeof data === 'object' && 'access_token' in data && + // eslint-disable-next-line no-restricted-syntax typeof (data as Record)['access_token'] === 'string' ) { const obj = data as Record; const result: OAuthTokenResponse = { access_token: String(obj['access_token']), token_type: + // eslint-disable-next-line no-restricted-syntax typeof obj['token_type'] === 'string' ? obj['token_type'] : 'Bearer', expires_in: + // eslint-disable-next-line no-restricted-syntax typeof obj['expires_in'] === 'number' ? obj['expires_in'] : undefined, refresh_token: + // eslint-disable-next-line no-restricted-syntax typeof obj['refresh_token'] === 'string' ? obj['refresh_token'] : undefined, + // eslint-disable-next-line no-restricted-syntax scope: typeof obj['scope'] === 'string' ? obj['scope'] : undefined, }; return result;