From ea2a0517cbcb8917a2f0176f4ea80ebf6115f838 Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Mon, 29 Jun 2026 14:28:14 +0800 Subject: [PATCH] Fix Go to Super Implementation hover link not clickable (#4438) The command-link sanitization added in fixJdtSchemeHoverLinks stripped all [label](command:...) links, including the trusted ones the extension contributes (e.g. Go to Super Implementation). Skip sanitizing trusted contributed content; only untrusted server Javadoc is sanitized. Add unit tests covering the hover link sanitization. --- src/hoverAction.ts | 2 +- src/providerDispatcher.ts | 8 ++++- test/standard-mode-suite/hoverLinks.test.ts | 37 +++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 test/standard-mode-suite/hoverLinks.test.ts diff --git a/src/hoverAction.ts b/src/hoverAction.ts index f4e91f4d0..760013524 100644 --- a/src/hoverAction.ts +++ b/src/hoverAction.ts @@ -74,7 +74,7 @@ class JavaHoverProvider implements HoverProvider { } const contributed = new MarkdownString(contributedCommands.map((command) => this.convertCommandToMarkdown(command)).join(' | ')); - contributed.isTrusted = true; + contributed.isTrusted = { enabledCommands: contributedCommands.map((command) => command.command) }; let contents: MarkdownString[] = [ contributed ]; let range; if (serverHover && serverHover.contents) { diff --git a/src/providerDispatcher.ts b/src/providerDispatcher.ts index 3d4e29414..abacbe141 100644 --- a/src/providerDispatcher.ts +++ b/src/providerDispatcher.ts @@ -211,7 +211,13 @@ export function fixJdtSchemeHoverLinks(hover: Hover): Hover { const newContents: (MarkedString | MarkdownString)[] = []; for (const content of hover.contents) { if (content instanceof MarkdownString) { - newContents.push(fixJdtLinksInDocumentation(content)); + // Skip our own trusted contributed commands (e.g. "Go to Super Implementation"); + // only sanitize untrusted server-provided Javadoc. + if (content.isTrusted) { + newContents.push(content); + } else { + newContents.push(fixJdtLinksInDocumentation(content)); + } } else { newContents.push(content); } diff --git a/test/standard-mode-suite/hoverLinks.test.ts b/test/standard-mode-suite/hoverLinks.test.ts new file mode 100644 index 000000000..627dc8867 --- /dev/null +++ b/test/standard-mode-suite/hoverLinks.test.ts @@ -0,0 +1,37 @@ +'use strict'; + +import * as assert from 'assert'; +import { Hover, MarkdownString } from 'vscode'; +import { fixJdtSchemeHoverLinks } from '../../src/providerDispatcher'; + +suite('Hover Links Test', () => { + + test('trusted contributed command links are preserved (super implementation)', () => { + const contributed = new MarkdownString('[Go to Super Implementation](command:java.action.navigateToSuperImplementation?%5B%5D)'); + contributed.isTrusted = { enabledCommands: ['java.action.navigateToSuperImplementation'] }; + const hover = new Hover([contributed]); + + const fixed = fixJdtSchemeHoverLinks(hover); + const value = (fixed.contents[0] as MarkdownString).value; + assert.ok(value.includes('(command:java.action.navigateToSuperImplementation'), 'contributed command link should not be sanitized'); + }); + + test('untrusted server command links are sanitized', () => { + const javadoc = new MarkdownString('[click here](command:evil.command?param=true)'); + const hover = new Hover([javadoc]); + + const fixed = fixJdtSchemeHoverLinks(hover); + const value = (fixed.contents[0] as MarkdownString).value; + assert.strictEqual(value, 'click here', 'server command link should be stripped to its label'); + }); + + test('jdt:// links are converted to command links', () => { + const javadoc = new MarkdownString('[String](jdt://contents/Foo.class)'); + const hover = new Hover([javadoc]); + + const fixed = fixJdtSchemeHoverLinks(hover); + const value = (fixed.contents[0] as MarkdownString).value; + assert.ok(value.includes('(command:'), 'jdt link should be converted to a command link'); + assert.ok(!value.includes('jdt://'), 'jdt scheme should be replaced'); + }); +});