diff --git a/apps/vscode/CHANGELOG.md b/apps/vscode/CHANGELOG.md index 086c8580..a46b6b77 100644 --- a/apps/vscode/CHANGELOG.md +++ b/apps/vscode/CHANGELOG.md @@ -4,6 +4,7 @@ - Fixed an issue where attribute values containing '='s could be truncated in some scenarios (). - Fixed an issue where a loading spinner for qmd previews wasn't dismissed on preview errors () +- Diagnostics are no longer reported for internal temporary virtual document files (). ## 1.124.0 (Release on 2025-08-20) diff --git a/apps/vscode/src/lsp/client.ts b/apps/vscode/src/lsp/client.ts index 5cf9fca3..07f92e9f 100644 --- a/apps/vscode/src/lsp/client.ts +++ b/apps/vscode/src/lsp/client.ts @@ -23,7 +23,8 @@ import { LocationLink, Definition, LogOutputChannel, - Uri + Uri, + Diagnostic } from "vscode"; import { LanguageClient, @@ -46,6 +47,7 @@ import { ProvideHoverSignature, ProvideSignatureHelpSignature, State, + HandleDiagnosticsSignature } from "vscode-languageclient"; import { MarkdownEngine } from "../markdown/engine"; import { @@ -54,6 +56,7 @@ import { virtualDoc, withVirtualDocUri, } from "../vdoc/vdoc"; +import { isVirtualDoc } from "../vdoc/vdoc-tempfile"; import { activateVirtualDocEmbeddedContent } from "../vdoc/vdoc-content"; import { vdocCompletions } from "../vdoc/vdoc-completion"; @@ -99,6 +102,7 @@ export async function activateLsp( const config = workspace.getConfiguration("quarto"); activateVirtualDocEmbeddedContent(); const middleware: Middleware = { + handleDiagnostics: createDiagnosticFilter(), provideCompletionItem: embeddedCodeCompletionProvider(engine), provideDefinition: embeddedGoToDefinitionProvider(engine), provideDocumentFormattingEdits: embeddedDocumentFormattingProvider(engine), @@ -338,3 +342,21 @@ function isWithinYamlComment(doc: TextDocument, pos: Position) { const line = doc.lineAt(pos.line).text; return !!line.match(/^\s*#\s*\| /); } + +/** + * Creates a diagnostic handler middleware that filters out diagnostics from virtual documents + * + * @returns A handler function for the middleware + */ +export function createDiagnosticFilter() { + return (uri: Uri, diagnostics: Diagnostic[], next: HandleDiagnosticsSignature) => { + // If this is not a virtual document, pass through all diagnostics + if (!isVirtualDoc(uri)) { + next(uri, diagnostics); + return; + } + + // For virtual documents, filter out all diagnostics + next(uri, []); + }; +} diff --git a/apps/vscode/src/test/diagnosticFiltering.test.ts b/apps/vscode/src/test/diagnosticFiltering.test.ts new file mode 100644 index 00000000..673bcf7b --- /dev/null +++ b/apps/vscode/src/test/diagnosticFiltering.test.ts @@ -0,0 +1,53 @@ +import * as vscode from "vscode"; +import * as assert from "assert"; +import { createDiagnosticFilter } from "../lsp/client"; + +suite("Diagnostic Filtering", function () { + + test("Diagnostic filter removes diagnostics for virtual documents", async function () { + // Create mocks + const virtualDocUri = vscode.Uri.file("/tmp/.vdoc.12345678-1234-1234-1234-123456789abc.py"); + const regularDocUri = vscode.Uri.file("/tmp/regular-file.py"); + + // Create some test diagnostics + const testDiagnostics = [ + new vscode.Diagnostic( + new vscode.Range(0, 0, 0, 10), + "Test diagnostic message", + vscode.DiagnosticSeverity.Error + ) + ]; + + // Create a mock diagnostics handler function to verify behavior + let capturedUri: vscode.Uri | undefined; + let capturedDiagnostics: vscode.Diagnostic[] | undefined; + + const mockHandler = (uri: vscode.Uri, diagnostics: vscode.Diagnostic[]) => { + capturedUri = uri; + capturedDiagnostics = diagnostics; + }; + + // Create the filter function + const diagnosticFilter = createDiagnosticFilter(); + + // Test with a virtual document + diagnosticFilter(virtualDocUri, testDiagnostics, mockHandler); + + // Verify diagnostics were filtered (empty array) + assert.strictEqual(capturedUri, virtualDocUri, "URI should be passed through"); + assert.strictEqual(capturedDiagnostics!.length, 0, "Diagnostics should be empty for virtual documents"); + + // Reset captured values + capturedUri = undefined; + capturedDiagnostics = undefined; + + // Test with a regular document + diagnosticFilter(regularDocUri, testDiagnostics, mockHandler); + + // Verify diagnostics were not filtered + assert.strictEqual(capturedUri, regularDocUri, "URI should be passed through"); + assert.strictEqual(capturedDiagnostics!.length, testDiagnostics.length, "Diagnostics should not be filtered for regular documents"); + assert.deepStrictEqual(capturedDiagnostics!, testDiagnostics, "Original diagnostics should be passed through unchanged"); + }); + +}); diff --git a/apps/vscode/src/vdoc/vdoc-tempfile.ts b/apps/vscode/src/vdoc/vdoc-tempfile.ts index b016c258..0a0bb337 100644 --- a/apps/vscode/src/vdoc/vdoc-tempfile.ts +++ b/apps/vscode/src/vdoc/vdoc-tempfile.ts @@ -150,3 +150,14 @@ function createVirtualDoc(filepath: string, content: string): void { function generateVirtualDocFilepath(directory: string, extension: string): string { return path.join(directory, ".vdoc." + uuid.v4() + "." + extension); } + +export function isVirtualDoc(uri: Uri): boolean { + // Check for tempfile virtual docs + if (uri.scheme === "file") { + const filename = path.basename(uri.fsPath); + // Virtual docs have a specific filename pattern .vdoc.[uuid].[extension] + return filename.startsWith(".vdoc.") && filename.split(".").length > 3; + } + + return false; +}