diff --git a/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/test/diagnosticsChanged.spec.ts b/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/test/diagnosticsChanged.spec.ts index 65d45a1cbbe44..c6218254492f2 100644 --- a/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/test/diagnosticsChanged.spec.ts +++ b/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/test/diagnosticsChanged.spec.ts @@ -197,6 +197,25 @@ describe('diagnosticsChanged push notification', () => { expect(notifiedDiag.range.end.character).toBe(20); }); + it('should serialize a null diagnostic code without crashing', async () => { + registerDiagnosticsChangedNotification(logger, httpServer as unknown as InProcHttpServer); + + const uri = createMockUri('/test/file.ts'); + const diag = createMockDiagnostic('Null code message', 0, 0, 0, 0, 10, 'test-source'); + (diag as { code?: unknown }).code = null; + + mockGetDiagnostics.mockReturnValue([diag]); + + registeredCallback!({ uris: [uri] }); + await vi.advanceTimersByTimeAsync(250); + + const params = httpServer.broadcastNotification.mock.calls[0][1] as unknown as DiagnosticNotificationParams; + const notifiedDiag = params.uris[0].diagnostics[0]; + + expect(notifiedDiag.message).toBe('Null code message'); + expect(notifiedDiag.code).toBeNull(); + }); + it('should handle multiple URIs in a single change event', async () => { registerDiagnosticsChangedNotification(logger, httpServer as unknown as InProcHttpServer); diff --git a/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/test/getDiagnostics.spec.ts b/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/test/getDiagnostics.spec.ts index 038a2cf7016c4..40cfdbcf3b26d 100644 --- a/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/test/getDiagnostics.spec.ts +++ b/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/test/getDiagnostics.spec.ts @@ -176,4 +176,23 @@ describe('getDiagnostics tool', () => { expect(result[0].diagnostics[0].code).toBe(2304); }); + + it('should handle a null code in diagnostics without crashing', async () => { + const mockDiag = { + message: 'Null code error', + severity: 0, + range: { + start: { line: 0, character: 0 }, + end: { line: 0, character: 5 }, + }, + source: 'ts', + code: null, + }; + mockGetDiagnostics.mockReturnValue([mockDiag]); + + const handler = server.getToolHandler('get_diagnostics')!; + const result = parseToolResult(await handler({ uri: 'file:///test.ts' })); + + expect(result[0].diagnostics[0].code).toBeNull(); + }); }); diff --git a/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/tools/getDiagnostics.ts b/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/tools/getDiagnostics.ts index 13f09111cb4ae..e894080c60494 100644 --- a/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/tools/getDiagnostics.ts +++ b/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/tools/getDiagnostics.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; -import { makeTextResult } from './utils'; +import { makeTextResult, normalizeDiagnosticCode } from './utils'; import { ILogger } from '../../../../../platform/log/common/logService'; export function registerGetDiagnosticsTool(server: McpServer, logger: ILogger): void { @@ -44,7 +44,7 @@ export function registerGetDiagnosticsTool(server: McpServer, logger: ILogger): end: { line: d.range.end.line, character: d.range.end.character }, }, source: d.source, - code: typeof d.code === 'object' ? d.code.value : d.code, + code: normalizeDiagnosticCode(d.code), })), })).filter(item => item.diagnostics.length > 0); diff --git a/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/tools/push/diagnosticsChanged.ts b/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/tools/push/diagnosticsChanged.ts index d0bfa4d2b354e..3e34612e89d3b 100644 --- a/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/tools/push/diagnosticsChanged.ts +++ b/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/tools/push/diagnosticsChanged.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import { ILogger } from '../../../../../../platform/log/common/logService'; import { Delayer } from '../../../../../../util/vs/base/common/async'; import { InProcHttpServer } from '../../inProcHttpServer'; +import { normalizeDiagnosticCode } from '../utils'; interface DiagnosticInfo { uri: string; @@ -49,7 +50,7 @@ function getDiagnosticsForUri(uri: vscode.Uri): DiagnosticInfo { message: d.message, severity: severityToString(d.severity), source: d.source, - code: typeof d.code === 'object' ? d.code.value : d.code, + code: normalizeDiagnosticCode(d.code), })), }; } diff --git a/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/tools/utils.ts b/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/tools/utils.ts index 1a2d4479e6553..836a54fd5208d 100644 --- a/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/tools/utils.ts +++ b/extensions/copilot/src/extension/chatSessions/copilotcli/vscode-node/tools/utils.ts @@ -3,6 +3,24 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import type * as vscode from 'vscode'; + +/** + * Normalizes a `vscode.Diagnostic.code` into a plain string or number for serialization. + * + * `Diagnostic.code` may be a string, a number, `undefined`, `null` (language servers and + * extensions are free to set it), or a `{ value, target }` object. Because + * `typeof null === 'object'`, callers must guard against `null` before reading `.value`; + * this helper centralizes that guard so every consumer of `vscode.languages.getDiagnostics` + * handles the value the same way. + */ +export function normalizeDiagnosticCode(code: vscode.Diagnostic['code']): string | number | undefined { + if (typeof code === 'object' && code !== null) { + return code.value; + } + return code; +} + export function makeTextResult(data: unknown): { content: [{ type: 'text'; text: string }] } { return { content: [