fix: drop diagnostics with missing range at the language diagnostics boundary (fixes #323148) (#323153)

* fix: drop diagnostics with missing range at the language diagnostics boundary

Other extensions can publish vscode.Diagnostic entries whose `range` is
undefined (violating the non-nullable `range: Range` type via an `any` cast).
These reach copilot verbatim through `vscode.languages.getDiagnostics()` and
crash the many consumers that dereference `diagnostic.range` - notably the
`LintErrors.getData` telemetry path that scans every extension's diagnostics
via `getAllDiagnostics()`. Sanitize at the boundary service so every consumer
is protected once, and log dropped entries so the producer stays diagnosable.

Fixes microsoft/vscode#323148

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: Copilot - Test lint — use strict equality for range null/undefined check

The eqeqeq ESLint rule (configured as 'warn', enforced with --max-warnings=0)
flagged `range != null` in _dropMalformedDiagnostics. Replace the loose
equality with an explicit strict check that preserves the original semantics
of dropping diagnostics whose range is either null or undefined.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: vs-code-engineering[bot] <122617954+vs-code-engineering[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
vs-code-engineering[bot]
2026-06-26 18:28:43 +00:00
committed by GitHub
parent 8cffe33c85
commit fc9a595992

View File

@@ -4,18 +4,46 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { ILogService } from '../../log/common/logService';
import { AbstractLanguageDiagnosticsService } from '../common/languageDiagnosticsService';
export class LanguageDiagnosticsServiceImpl extends AbstractLanguageDiagnosticsService {
private static ignoredSchemes = new Set(['git', 'chat-editing-snapshot-text-model', 'chat-editing-text-model']);
override onDidChangeDiagnostics: vscode.Event<vscode.DiagnosticChangeEvent> = vscode.languages.onDidChangeDiagnostics;
constructor(
@ILogService private readonly _logService: ILogService,
) {
super();
}
override getDiagnostics(resource: vscode.Uri): vscode.Diagnostic[] {
return vscode.languages.getDiagnostics(resource);
return this._dropMalformedDiagnostics(vscode.languages.getDiagnostics(resource));
}
override getAllDiagnostics(): [vscode.Uri, vscode.Diagnostic[]][] {
return vscode.languages.getDiagnostics()
.filter(([uri]) => !LanguageDiagnosticsServiceImpl.ignoredSchemes.has(uri.scheme));
.filter(([uri]) => !LanguageDiagnosticsServiceImpl.ignoredSchemes.has(uri.scheme))
.map(([uri, diagnostics]): [vscode.Uri, vscode.Diagnostic[]] => [uri, this._dropMalformedDiagnostics(diagnostics)]);
}
/**
* Diagnostics are produced by arbitrary extensions and reach us verbatim through the
* `vscode.languages.getDiagnostics` API. An extension can publish an entry that violates the
* {@link vscode.Diagnostic} contract at runtime - most notably with a missing `range` assigned
* through an `any` cast - and such an entry crashes the many consumers that dereference
* `diagnostic.range`. Drop the malformed entries here, at the extension boundary, and surface
* the occurrence so the underlying producer stays diagnosable.
*/
private _dropMalformedDiagnostics(diagnostics: vscode.Diagnostic[]): vscode.Diagnostic[] {
const valid = diagnostics.filter(diagnostic => {
const range: vscode.Range | null | undefined = diagnostic.range;
return range !== null && range !== undefined;
});
const dropped = diagnostics.length - valid.length;
if (dropped > 0) {
this._logService.warn(`[LanguageDiagnosticsService] Ignored ${dropped} diagnostic(s) with a missing range received from the diagnostics API.`);
}
return valid;
}
}