Disallow and suppress misused spread operator. (#23294)

This commit is contained in:
Christian Gunderman
2026-03-21 05:21:53 +00:00
committed by GitHub
parent e7b6326cfa
commit 8f391585ab
25 changed files with 59 additions and 0 deletions
+1
View File
@@ -158,6 +158,7 @@ export default tseslint.config(
'@typescript-eslint/await-thenable': ['error'], '@typescript-eslint/await-thenable': ['error'],
'@typescript-eslint/no-floating-promises': ['error'], '@typescript-eslint/no-floating-promises': ['error'],
'@typescript-eslint/no-unnecessary-type-assertion': ['error'], '@typescript-eslint/no-unnecessary-type-assertion': ['error'],
'@typescript-eslint/no-misused-spread': ['error'],
'no-restricted-imports': [ 'no-restricted-imports': [
'error', 'error',
{ {
+1
View File
@@ -54,6 +54,7 @@ export async function getMcpServersFromConfig(
return; return;
} }
mcpServers[key] = { mcpServers[key] = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...server, ...server,
extension, extension,
}; };
+3
View File
@@ -1716,6 +1716,7 @@ describe('loadCliConfig with admin.mcp.config', () => {
const serverA = config.getMcpServers()?.['serverA']; const serverA = config.getMcpServers()?.['serverA'];
expect(serverA).toEqual({ expect(serverA).toEqual({
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...localMcpServers['serverA'], ...localMcpServers['serverA'],
type: 'sse', type: 'sse',
url: 'https://admin-server-a.com/sse', url: 'https://admin-server-a.com/sse',
@@ -1766,6 +1767,7 @@ describe('loadCliConfig with admin.mcp.config', () => {
}; };
const localMcpServersWithTools: Record<string, MCPServerConfig> = { const localMcpServersWithTools: Record<string, MCPServerConfig> = {
serverA: { serverA: {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...localMcpServers['serverA'], ...localMcpServers['serverA'],
includeTools: ['local_tool'], includeTools: ['local_tool'],
timeout: 1234, timeout: 1234,
@@ -1808,6 +1810,7 @@ describe('loadCliConfig with admin.mcp.config', () => {
}; };
const localMcpServersWithTools: Record<string, MCPServerConfig> = { const localMcpServersWithTools: Record<string, MCPServerConfig> = {
serverA: { serverA: {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...localMcpServers['serverA'], ...localMcpServers['serverA'],
includeTools: ['local_tool'], includeTools: ['local_tool'],
}, },
@@ -13,6 +13,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
return { return {
...actual, ...actual,
Storage: { Storage: {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...actual.Storage, ...actual.Storage,
getGlobalGeminiDir: () => '/virtual-home/.gemini', getGlobalGeminiDir: () => '/virtual-home/.gemini',
}, },
+2
View File
@@ -126,6 +126,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
clearInstance: vi.fn(), clearInstance: vi.fn(),
}, },
coreEvents: { coreEvents: {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...actual.coreEvents, ...actual.coreEvents,
emitFeedback: vi.fn(), emitFeedback: vi.fn(),
emitConsoleLog: vi.fn(), emitConsoleLog: vi.fn(),
@@ -1508,6 +1509,7 @@ describe('startInteractiveUI', () => {
.spyOn(process.stdout, 'write') .spyOn(process.stdout, 'write')
.mockImplementation(() => true); .mockImplementation(() => true);
const mockConfigWithScreenReader = { const mockConfigWithScreenReader = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...mockConfig, ...mockConfig,
getScreenReader: () => screenReader, getScreenReader: () => screenReader,
} as Config; } as Config;
@@ -266,6 +266,7 @@ describe('BuiltinCommandLoader', () => {
it('should include policies command when message bus integration is enabled', async () => { it('should include policies command when message bus integration is enabled', async () => {
const mockConfigWithMessageBus = { const mockConfigWithMessageBus = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...mockConfig, ...mockConfig,
getEnableHooks: () => false, getEnableHooks: () => false,
getMcpEnabled: () => true, getMcpEnabled: () => true,
@@ -38,6 +38,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
return { return {
...actual, ...actual,
coreEvents: { coreEvents: {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...actual.coreEvents, ...actual.coreEvents,
emitFeedback: vi.fn(), emitFeedback: vi.fn(),
}, },
@@ -163,6 +163,7 @@ describe('ToolConfirmationQueue', () => {
</Box>, </Box>,
{ {
config: { config: {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...mockConfig, ...mockConfig,
getUseAlternateBuffer: () => true, getUseAlternateBuffer: () => true,
} as unknown as Config, } as unknown as Config,
@@ -674,6 +674,7 @@ describe('useAtCompletion', () => {
multiDirTmpDirs.push(addedDir); multiDirTmpDirs.push(addedDir);
const multiDirConfig = { const multiDirConfig = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...mockConfig, ...mockConfig,
getWorkspaceContext: vi.fn().mockReturnValue({ getWorkspaceContext: vi.fn().mockReturnValue({
getDirectories: () => [cwdDir, addedDir], getDirectories: () => [cwdDir, addedDir],
@@ -706,6 +707,7 @@ describe('useAtCompletion', () => {
const directories = [cwdDir]; const directories = [cwdDir];
const dynamicConfig = { const dynamicConfig = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...mockConfig, ...mockConfig,
getWorkspaceContext: vi.fn().mockReturnValue({ getWorkspaceContext: vi.fn().mockReturnValue({
getDirectories: () => [...directories], getDirectories: () => [...directories],
@@ -750,6 +752,7 @@ describe('useAtCompletion', () => {
multiDirTmpDirs.push(dir2); multiDirTmpDirs.push(dir2);
const multiDirConfig = { const multiDirConfig = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...mockConfig, ...mockConfig,
getWorkspaceContext: vi.fn().mockReturnValue({ getWorkspaceContext: vi.fn().mockReturnValue({
getDirectories: () => [dir1, dir2], getDirectories: () => [dir1, dir2],
@@ -1069,6 +1069,7 @@ describe('useGeminiStream', () => {
} as unknown as TrackedCompletedToolCall, } as unknown as TrackedCompletedToolCall,
]; ];
const lowVerbositySettings = { const lowVerbositySettings = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...mockLoadedSettings, ...mockLoadedSettings,
merged: { merged: {
...mockLoadedSettings.merged, ...mockLoadedSettings.merged,
@@ -2023,6 +2024,7 @@ describe('useGeminiStream', () => {
); );
const testConfig = { const testConfig = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...mockConfig, ...mockConfig,
getContentGenerator: vi.fn(), getContentGenerator: vi.fn(),
getContentGeneratorConfig: vi.fn(() => ({ getContentGeneratorConfig: vi.fn(() => ({
@@ -2826,6 +2828,7 @@ describe('useGeminiStream', () => {
describe('Thought Reset', () => { describe('Thought Reset', () => {
it('should keep full thinking entries in history when mode is full', async () => { it('should keep full thinking entries in history when mode is full', async () => {
const fullThinkingSettings: LoadedSettings = { const fullThinkingSettings: LoadedSettings = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...mockLoadedSettings, ...mockLoadedSettings,
merged: { merged: {
...mockLoadedSettings.merged, ...mockLoadedSettings.merged,
+1
View File
@@ -194,6 +194,7 @@ export class KeyBinding {
const key = remains; const key = remains;
// eslint-disable-next-line @typescript-eslint/no-misused-spread
const isSingleChar = [...key].length === 1; const isSingleChar = [...key].length === 1;
if (!isSingleChar && !KeyBinding.VALID_LONG_KEYS.has(key.toLowerCase())) { if (!isSingleChar && !KeyBinding.VALID_LONG_KEYS.has(key.toLowerCase())) {
@@ -175,6 +175,7 @@ vi.mock('../utils/promptIdContext.js', async (importOriginal) => {
return { return {
...actual, ...actual,
promptIdContext: { promptIdContext: {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...actual.promptIdContext, ...actual.promptIdContext,
getStore: vi.fn(), getStore: vi.fn(),
run: vi.fn((_id, fn) => fn()), run: vi.fn((_id, fn) => fn()),
@@ -37,6 +37,7 @@ export function applyAdminAllowlist(
const adminConfig = adminAllowlist[serverId]; const adminConfig = adminAllowlist[serverId];
if (adminConfig) { if (adminConfig) {
const mergedConfig = { const mergedConfig = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...localConfig, ...localConfig,
url: adminConfig.url, url: adminConfig.url,
type: adminConfig.type, type: adminConfig.type,
+4
View File
@@ -71,10 +71,12 @@ describe('Dynamic Configuration Parity', () => {
for (const flags of flagCombos) { for (const flags of flagCombos) {
for (const hasAccess of [true, false]) { for (const hasAccess of [true, false]) {
const mockLegacyConfig = { const mockLegacyConfig = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...legacyConfig, ...legacyConfig,
getHasAccessToPreviewModel: () => hasAccess, getHasAccessToPreviewModel: () => hasAccess,
} as unknown as Config; } as unknown as Config;
const mockDynamicConfig = { const mockDynamicConfig = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...dynamicConfig, ...dynamicConfig,
getHasAccessToPreviewModel: () => hasAccess, getHasAccessToPreviewModel: () => hasAccess,
} as unknown as Config; } as unknown as Config;
@@ -110,10 +112,12 @@ describe('Dynamic Configuration Parity', () => {
for (const hasAccess of [true, false]) { for (const hasAccess of [true, false]) {
const mockLegacyConfig = { const mockLegacyConfig = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...legacyConfig, ...legacyConfig,
getHasAccessToPreviewModel: () => hasAccess, getHasAccessToPreviewModel: () => hasAccess,
} as unknown as Config; } as unknown as Config;
const mockDynamicConfig = { const mockDynamicConfig = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...dynamicConfig, ...dynamicConfig,
getHasAccessToPreviewModel: () => hasAccess, getHasAccessToPreviewModel: () => hasAccess,
} as unknown as Config; } as unknown as Config;
@@ -291,6 +291,7 @@ function createMockConfig(overrides: Partial<Config> = {}): Config {
getExperiments: () => {}, getExperiments: () => {},
} as unknown as Config; } as unknown as Config;
// eslint-disable-next-line @typescript-eslint/no-misused-spread
const finalConfig = { ...baseConfig, ...overrides } as Config; const finalConfig = { ...baseConfig, ...overrides } as Config;
(finalConfig as unknown as { config: Config }).config = finalConfig; (finalConfig as unknown as { config: Config }).config = finalConfig;
@@ -74,6 +74,7 @@ function createMockConfig(overrides: Partial<Config> = {}): Config {
}) as unknown as PolicyEngine, }) as unknown as PolicyEngine,
} as unknown as Config; } as unknown as Config;
// eslint-disable-next-line @typescript-eslint/no-misused-spread
return { ...baseConfig, ...overrides } as Config; return { ...baseConfig, ...overrides } as Config;
} }
@@ -177,6 +177,7 @@ describe('GoogleCredentialProvider', () => {
it('should prioritize config headers over quota project ID', async () => { it('should prioritize config headers over quota project ID', async () => {
mockClient['quotaProjectId'] = 'quota-project-id'; mockClient['quotaProjectId'] = 'quota-project-id';
const configWithHeaders = { const configWithHeaders = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...validConfig, ...validConfig,
headers: { headers: {
'X-Goog-User-Project': 'config-project-id', 'X-Goog-User-Project': 'config-project-id',
@@ -193,6 +194,7 @@ describe('GoogleCredentialProvider', () => {
it('should prioritize config headers over quota project ID (case-insensitive)', async () => { it('should prioritize config headers over quota project ID (case-insensitive)', async () => {
mockClient['quotaProjectId'] = 'quota-project-id'; mockClient['quotaProjectId'] = 'quota-project-id';
const configWithHeaders = { const configWithHeaders = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...validConfig, ...validConfig,
headers: { headers: {
'x-goog-user-project': 'config-project-id', 'x-goog-user-project': 'config-project-id',
@@ -196,6 +196,7 @@ async function truncateHistoryToBudget(
newParts.unshift({ newParts.unshift({
functionResponse: { functionResponse: {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...part.functionResponse, ...part.functionResponse,
response: { output: truncatedMessage }, response: { output: truncatedMessage },
}, },
@@ -226,6 +226,7 @@ export class ToolOutputMaskingService {
const maskedPart = { const maskedPart = {
...part, ...part,
functionResponse: { functionResponse: {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...part.functionResponse, ...part.functionResponse,
response: { output: maskedSnippet }, response: { output: maskedSnippet },
}, },
@@ -286,6 +286,7 @@ describe('loggers', () => {
it('should set worktree_active to true when worktree settings are present', async () => { it('should set worktree_active to true when worktree settings are present', async () => {
const mockConfig = { const mockConfig = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...baseMockConfig, ...baseMockConfig,
getWorktreeSettings: () => ({ getWorktreeSettings: () => ({
name: 'test-worktree', name: 'test-worktree',
@@ -556,6 +557,7 @@ describe('loggers', () => {
); );
expect(mockUiEvent.addEvent).toHaveBeenCalledWith({ expect(mockUiEvent.addEvent).toHaveBeenCalledWith({
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...event, ...event,
'event.name': EVENT_API_RESPONSE, 'event.name': EVENT_API_RESPONSE,
'event.timestamp': '2025-01-01T00:00:00.000Z', 'event.timestamp': '2025-01-01T00:00:00.000Z',
@@ -715,6 +717,7 @@ describe('loggers', () => {
); );
expect(mockUiEvent.addEvent).toHaveBeenCalledWith({ expect(mockUiEvent.addEvent).toHaveBeenCalledWith({
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...event, ...event,
'event.name': EVENT_API_ERROR, 'event.name': EVENT_API_ERROR,
'event.timestamp': '2025-01-01T00:00:00.000Z', 'event.timestamp': '2025-01-01T00:00:00.000Z',
@@ -1285,6 +1288,7 @@ describe('loggers', () => {
); );
expect(mockUiEvent.addEvent).toHaveBeenCalledWith({ expect(mockUiEvent.addEvent).toHaveBeenCalledWith({
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...event, ...event,
'event.name': EVENT_TOOL_CALL, 'event.name': EVENT_TOOL_CALL,
'event.timestamp': '2025-01-01T00:00:00.000Z', 'event.timestamp': '2025-01-01T00:00:00.000Z',
@@ -1422,6 +1426,7 @@ describe('loggers', () => {
); );
expect(mockUiEvent.addEvent).toHaveBeenCalledWith({ expect(mockUiEvent.addEvent).toHaveBeenCalledWith({
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...event, ...event,
'event.name': EVENT_TOOL_CALL, 'event.name': EVENT_TOOL_CALL,
'event.timestamp': '2025-01-01T00:00:00.000Z', 'event.timestamp': '2025-01-01T00:00:00.000Z',
@@ -1502,6 +1507,7 @@ describe('loggers', () => {
); );
expect(mockUiEvent.addEvent).toHaveBeenCalledWith({ expect(mockUiEvent.addEvent).toHaveBeenCalledWith({
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...event, ...event,
'event.name': EVENT_TOOL_CALL, 'event.name': EVENT_TOOL_CALL,
'event.timestamp': '2025-01-01T00:00:00.000Z', 'event.timestamp': '2025-01-01T00:00:00.000Z',
@@ -1581,6 +1587,7 @@ describe('loggers', () => {
); );
expect(mockUiEvent.addEvent).toHaveBeenCalledWith({ expect(mockUiEvent.addEvent).toHaveBeenCalledWith({
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...event, ...event,
'event.name': EVENT_TOOL_CALL, 'event.name': EVENT_TOOL_CALL,
'event.timestamp': '2025-01-01T00:00:00.000Z', 'event.timestamp': '2025-01-01T00:00:00.000Z',
@@ -1661,6 +1668,7 @@ describe('loggers', () => {
); );
expect(mockUiEvent.addEvent).toHaveBeenCalledWith({ expect(mockUiEvent.addEvent).toHaveBeenCalledWith({
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...event, ...event,
'event.name': EVENT_TOOL_CALL, 'event.name': EVENT_TOOL_CALL,
'event.timestamp': '2025-01-01T00:00:00.000Z', 'event.timestamp': '2025-01-01T00:00:00.000Z',
@@ -1955,6 +1963,7 @@ describe('loggers', () => {
'session.id': 'test-session-id', 'session.id': 'test-session-id',
'user.email': 'test-user@example.com', 'user.email': 'test-user@example.com',
'installation.id': 'test-installation-id', 'installation.id': 'test-installation-id',
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...event, ...event,
'event.name': EVENT_MODEL_ROUTING, 'event.name': EVENT_MODEL_ROUTING,
interactive: false, interactive: false,
@@ -1992,6 +2001,7 @@ describe('loggers', () => {
'session.id': 'test-session-id', 'session.id': 'test-session-id',
'user.email': 'test-user@example.com', 'user.email': 'test-user@example.com',
'installation.id': 'test-installation-id', 'installation.id': 'test-installation-id',
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...event, ...event,
'event.name': EVENT_MODEL_ROUTING, 'event.name': EVENT_MODEL_ROUTING,
interactive: false, interactive: false,
+4
View File
@@ -135,6 +135,7 @@ export function logUserPrompt(config: Config, event: UserPromptEvent): void {
export function logToolCall(config: Config, event: ToolCallEvent): void { export function logToolCall(config: Config, event: ToolCallEvent): void {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const uiEvent = { const uiEvent = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...event, ...event,
'event.name': EVENT_TOOL_CALL, 'event.name': EVENT_TOOL_CALL,
'event.timestamp': new Date().toISOString(), 'event.timestamp': new Date().toISOString(),
@@ -269,6 +270,7 @@ export function logRipgrepFallback(
export function logApiError(config: Config, event: ApiErrorEvent): void { export function logApiError(config: Config, event: ApiErrorEvent): void {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const uiEvent = { const uiEvent = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...event, ...event,
'event.name': EVENT_API_ERROR, 'event.name': EVENT_API_ERROR,
'event.timestamp': new Date().toISOString(), 'event.timestamp': new Date().toISOString(),
@@ -301,6 +303,7 @@ export function logApiError(config: Config, event: ApiErrorEvent): void {
export function logApiResponse(config: Config, event: ApiResponseEvent): void { export function logApiResponse(config: Config, event: ApiResponseEvent): void {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const uiEvent = { const uiEvent = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...event, ...event,
'event.name': EVENT_API_RESPONSE, 'event.name': EVENT_API_RESPONSE,
'event.timestamp': new Date().toISOString(), 'event.timestamp': new Date().toISOString(),
@@ -401,6 +404,7 @@ export function logSlashCommand(
export function logRewind(config: Config, event: RewindEvent): void { export function logRewind(config: Config, event: RewindEvent): void {
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
const uiEvent = { const uiEvent = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...event, ...event,
'event.name': EVENT_REWIND, 'event.name': EVENT_REWIND,
'event.timestamp': new Date().toISOString(), 'event.timestamp': new Date().toISOString(),
@@ -403,6 +403,7 @@ describe('UiTelemetryService', () => {
ToolConfirmationOutcome.ProceedOnce, ToolConfirmationOutcome.ProceedOnce,
); );
service.addEvent({ service.addEvent({
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...structuredClone(new ToolCallEvent(toolCall)), ...structuredClone(new ToolCallEvent(toolCall)),
'event.name': EVENT_TOOL_CALL, 'event.name': EVENT_TOOL_CALL,
} as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL }); } as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL });
@@ -437,6 +438,7 @@ describe('UiTelemetryService', () => {
ToolConfirmationOutcome.Cancel, ToolConfirmationOutcome.Cancel,
); );
service.addEvent({ service.addEvent({
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...structuredClone(new ToolCallEvent(toolCall)), ...structuredClone(new ToolCallEvent(toolCall)),
'event.name': EVENT_TOOL_CALL, 'event.name': EVENT_TOOL_CALL,
} as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL }); } as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL });
@@ -471,6 +473,7 @@ describe('UiTelemetryService', () => {
ToolConfirmationOutcome.ModifyWithEditor, ToolConfirmationOutcome.ModifyWithEditor,
); );
service.addEvent({ service.addEvent({
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...structuredClone(new ToolCallEvent(toolCall)), ...structuredClone(new ToolCallEvent(toolCall)),
'event.name': EVENT_TOOL_CALL, 'event.name': EVENT_TOOL_CALL,
} as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL }); } as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL });
@@ -487,6 +490,7 @@ describe('UiTelemetryService', () => {
it('should process a ToolCallEvent without a decision', () => { it('should process a ToolCallEvent without a decision', () => {
const toolCall = createFakeCompletedToolCall('test_tool', true, 100); const toolCall = createFakeCompletedToolCall('test_tool', true, 100);
service.addEvent({ service.addEvent({
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...structuredClone(new ToolCallEvent(toolCall)), ...structuredClone(new ToolCallEvent(toolCall)),
'event.name': EVENT_TOOL_CALL, 'event.name': EVENT_TOOL_CALL,
} as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL }); } as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL });
@@ -523,10 +527,12 @@ describe('UiTelemetryService', () => {
); );
service.addEvent({ service.addEvent({
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...structuredClone(new ToolCallEvent(toolCall1)), ...structuredClone(new ToolCallEvent(toolCall1)),
'event.name': EVENT_TOOL_CALL, 'event.name': EVENT_TOOL_CALL,
} as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL }); } as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL });
service.addEvent({ service.addEvent({
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...structuredClone(new ToolCallEvent(toolCall2)), ...structuredClone(new ToolCallEvent(toolCall2)),
'event.name': EVENT_TOOL_CALL, 'event.name': EVENT_TOOL_CALL,
} as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL }); } as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL });
@@ -558,10 +564,12 @@ describe('UiTelemetryService', () => {
const toolCall1 = createFakeCompletedToolCall('tool_A', true, 100); const toolCall1 = createFakeCompletedToolCall('tool_A', true, 100);
const toolCall2 = createFakeCompletedToolCall('tool_B', false, 200); const toolCall2 = createFakeCompletedToolCall('tool_B', false, 200);
service.addEvent({ service.addEvent({
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...structuredClone(new ToolCallEvent(toolCall1)), ...structuredClone(new ToolCallEvent(toolCall1)),
'event.name': EVENT_TOOL_CALL, 'event.name': EVENT_TOOL_CALL,
} as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL }); } as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL });
service.addEvent({ service.addEvent({
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...structuredClone(new ToolCallEvent(toolCall2)), ...structuredClone(new ToolCallEvent(toolCall2)),
'event.name': EVENT_TOOL_CALL, 'event.name': EVENT_TOOL_CALL,
} as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL }); } as ToolCallEvent & { 'event.name': typeof EVENT_TOOL_CALL });
@@ -818,6 +826,7 @@ describe('UiTelemetryService', () => {
it('should aggregate valid line count metadata', () => { it('should aggregate valid line count metadata', () => {
const toolCall = createFakeCompletedToolCall('test_tool', true, 100); const toolCall = createFakeCompletedToolCall('test_tool', true, 100);
const event = { const event = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...structuredClone(new ToolCallEvent(toolCall)), ...structuredClone(new ToolCallEvent(toolCall)),
'event.name': EVENT_TOOL_CALL, 'event.name': EVENT_TOOL_CALL,
metadata: { metadata: {
@@ -836,6 +845,7 @@ describe('UiTelemetryService', () => {
it('should ignore null/undefined values in line count metadata', () => { it('should ignore null/undefined values in line count metadata', () => {
const toolCall = createFakeCompletedToolCall('test_tool', true, 100); const toolCall = createFakeCompletedToolCall('test_tool', true, 100);
const event = { const event = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...structuredClone(new ToolCallEvent(toolCall)), ...structuredClone(new ToolCallEvent(toolCall)),
'event.name': EVENT_TOOL_CALL, 'event.name': EVENT_TOOL_CALL,
metadata: { metadata: {
@@ -511,6 +511,7 @@ describe('McpClientManager', () => {
await manager.startExtension(extension); await manager.startExtension(extension);
mockedMcpClient.getServerConfig.mockReturnValue({ mockedMcpClient.getServerConfig.mockReturnValue({
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...extension.mcpServers!['test-server'], ...extension.mcpServers!['test-server'],
extension, extension,
}); });
@@ -215,6 +215,7 @@ export class McpClientManager {
await Promise.all( await Promise.all(
Object.entries(extension.mcpServers ?? {}).map(([name, config]) => Object.entries(extension.mcpServers ?? {}).map(([name, config]) =>
this.maybeDiscoverMcpServer(name, { this.maybeDiscoverMcpServer(name, {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...config, ...config,
extension, extension,
}), }),
@@ -331,7 +332,9 @@ export class McpClientManager {
const env = { ...(base.env ?? {}), ...(override.env ?? {}) }; const env = { ...(base.env ?? {}), ...(override.env ?? {}) };
return { return {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...base, ...base,
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...override, ...override,
includeTools, includeTools,
excludeTools: excludeTools.length > 0 ? excludeTools : undefined, excludeTools: excludeTools.length > 0 ? excludeTools : undefined,
@@ -367,6 +367,7 @@ describe('WriteFileTool', () => {
const abortSignal = new AbortController().signal; const abortSignal = new AbortController().signal;
const mockGemini3Config = { const mockGemini3Config = {
// eslint-disable-next-line @typescript-eslint/no-misused-spread
...mockConfig, ...mockConfig,
getActiveModel: () => 'gemini-3.0-pro', getActiveModel: () => 'gemini-3.0-pro',
} as unknown as Config; } as unknown as Config;