diff --git a/src/DiagnosticsManager.ts b/src/DiagnosticsManager.ts index 9d01f1e0b..2c69a852e 100644 --- a/src/DiagnosticsManager.ts +++ b/src/DiagnosticsManager.ts @@ -14,6 +14,7 @@ import * as vscode from "vscode"; import * as fs from "fs"; +import * as path from "path"; // eslint-disable-next-line @typescript-eslint/no-require-imports import stripAnsi = require("strip-ansi"); import configuration from "./configuration"; @@ -149,7 +150,24 @@ export class DiagnosticsManager implements vscode.Disposable { typeof diagnostic.code !== "string" && typeof diagnostic.code !== "number" ) { - if (diagnostic.code.target.fsPath.endsWith(".md")) { + const fsPath = diagnostic.code.target.fsPath; + + // Work around a bug in the nightlies where the URL comes back looking like: + // `/path/to/TestPackage/https:/docs.swift.org/compiler/documentation/diagnostics/nominal-types` + // Transform this in to a valid docs.swift.org URL which the openEducationalNote command + // will open in a browser. + // FIXME: This can be removed when the bug is fixed in sourcekit-lsp. + let open = false; + const needle = `https:${path.sep}docs.swift.org${path.sep}`; + if (fsPath.indexOf(needle) !== -1) { + const extractedPath = `https://docs.swift.org/${fsPath.split(needle).pop()}/`; + diagnostic.code.target = vscode.Uri.parse(extractedPath.replace(/\\/g, "/")); + open = true; + } else if (diagnostic.code.target.fsPath.endsWith(".md")) { + open = true; + } + + if (open) { diagnostic.code = { target: vscode.Uri.parse( `command:swift.openEducationalNote?${encodeURIComponent(JSON.stringify(diagnostic.code.target))}` diff --git a/src/commands/openEducationalNote.ts b/src/commands/openEducationalNote.ts index ea3dbe677..9f5e75b91 100644 --- a/src/commands/openEducationalNote.ts +++ b/src/commands/openEducationalNote.ts @@ -20,5 +20,9 @@ import * as vscode from "vscode"; * The default behaviour is to open it in a markdown preview to the side. */ export async function openEducationalNote(markdownFile: vscode.Uri | undefined): Promise { - await vscode.commands.executeCommand("markdown.showPreviewToSide", markdownFile); + if (markdownFile?.fsPath.endsWith(".md")) { + await vscode.commands.executeCommand("markdown.showPreviewToSide", markdownFile); + } else if (markdownFile !== undefined) { + await vscode.env.openExternal(markdownFile); + } } diff --git a/test/integration-tests/DiagnosticsManager.test.ts b/test/integration-tests/DiagnosticsManager.test.ts index 7f17dffe3..473b6802c 100644 --- a/test/integration-tests/DiagnosticsManager.test.ts +++ b/test/integration-tests/DiagnosticsManager.test.ts @@ -690,6 +690,45 @@ suite("DiagnosticsManager Test Suite", function () { assert.fail("Diagnostic target not replaced with markdown command"); } }); + + test("target with malformed nightly link", async () => { + const malformedUri = + "/path/to/TestPackage/https:/docs.swift.org/compiler/documentation/diagnostics/nominal-types"; + const expectedUri = + "https://docs.swift.org/compiler/documentation/diagnostics/nominal-types/"; + diagnostic.code = { + value: "string", + target: vscode.Uri.file(malformedUri), + }; + + workspaceContext.diagnostics.handleDiagnostics( + mainUri, + DiagnosticsManager.isSourcekit, + [diagnostic] + ); + + const diagnostics = vscode.languages.getDiagnostics(mainUri); + const matchingDiagnostic = diagnostics.find(findDiagnostic(diagnostic)); + + expect(matchingDiagnostic).to.have.property("code"); + expect(matchingDiagnostic?.code).to.have.property("value", "More Information..."); + + if ( + matchingDiagnostic && + matchingDiagnostic.code && + typeof matchingDiagnostic.code !== "string" && + typeof matchingDiagnostic.code !== "number" + ) { + expect(matchingDiagnostic.code.target.scheme).to.equal("command"); + expect(matchingDiagnostic.code.target.path).to.equal( + "swift.openEducationalNote" + ); + const parsed = JSON.parse(matchingDiagnostic.code.target.query); + expect(vscode.Uri.from(parsed).toString()).to.equal(expectedUri); + } else { + assert.fail("Diagnostic target not replaced with markdown command"); + } + }); }); suite("keepAll", () => {