From 3bb726186eda2be193d487f88b2a9a72bdb1423c Mon Sep 17 00:00:00 2001 From: Dung Dong Date: Thu, 29 Oct 2026 20:20:42 +0000 Subject: [PATCH] fix(amazonq): show code issue text as plain text in hovers and tooltips --- .../securityIssueHoverProvider.test.ts | 30 +++++++++++++++++++ .../securityIssueTreeViewProvider.test.ts | 12 ++++++++ .../service/securityIssueHoverProvider.ts | 21 +++++++++---- .../service/securityIssueTreeViewProvider.ts | 10 ++++--- 4 files changed, 64 insertions(+), 9 deletions(-) diff --git a/packages/amazonq/test/unit/codewhisperer/service/securityIssueHoverProvider.test.ts b/packages/amazonq/test/unit/codewhisperer/service/securityIssueHoverProvider.test.ts index 7709eed10f..98e5943e7b 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/securityIssueHoverProvider.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/securityIssueHoverProvider.test.ts @@ -139,6 +139,36 @@ describe('securityIssueHoverProvider', () => { ) }) + it('renders finding text as plain text and only enables its own commands', () => { + const link = '[x](command:workbench.action.terminal.sendSequence?%7B%22text%22%3A%22ls%22%7D)' + const issues = [ + createCodeScanIssue({ title: `title ${link}`, suggestedFixes: [{ code: 'code', description: link }] }), + createCodeScanIssue({ title: 'x', suggestedFixes: [] }), + ] + issues[1].recommendation.text = link + setupIssues(issues) + const actual = securityIssueHoverProvider.provideHover(mockDocument, new vscode.Position(0, 0), token.token) + assert.strictEqual(actual.contents.length, 2) + + for (const content of actual.contents as vscode.MarkdownString[]) { + assert.deepStrictEqual(content.isTrusted, { + enabledCommands: [ + 'aws.amazonq.explainIssue', + 'aws.amazonq.generateFix', + 'aws.amazonq.security.ignore', + 'aws.amazonq.security.ignoreAll', + ], + }) + assert.ok(!content.supportHtml) + assert.ok(!content.value.includes('](command:workbench')) + assert.ok(content.value.includes('](command:aws.amazonq.explainIssue?')) + assert.ok(content.value.includes('](command:aws.amazonq.generateFix?')) + assert.ok(content.value.includes('](command:aws.amazonq.security.ignore?')) + assert.ok(content.value.includes('](command:aws.amazonq.security.ignoreAll?')) + } + assert.ok(actual.contents.every((c) => (c as vscode.MarkdownString).value.includes('\\[x\\]\\(command:'))) + }) + it('should not show issues that are not visible', () => { setupIssues([createCodeScanIssue({ visible: false })]) const actual = securityIssueHoverProvider.provideHover(mockDocument, new vscode.Position(0, 0), token.token) diff --git a/packages/amazonq/test/unit/codewhisperer/service/securityIssueTreeViewProvider.test.ts b/packages/amazonq/test/unit/codewhisperer/service/securityIssueTreeViewProvider.test.ts index d72e1f8636..e29b27da7b 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/securityIssueTreeViewProvider.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/securityIssueTreeViewProvider.test.ts @@ -18,6 +18,7 @@ import { createCodeScanIssue } from 'aws-core-vscode/test' import assert from 'assert' import sinon from 'sinon' import path from 'path' +import * as vscode from 'vscode' describe('SecurityIssueTreeViewProvider', function () { let securityIssueTreeViewProvider: SecurityIssueTreeViewProvider @@ -174,4 +175,15 @@ describe('IssueItem', function () { const issueItem = new IssueItem('file/path', createCodeScanIssue({ ruleId: sasRuleId })) assert.strictEqual(issueItem.contextValue, 'issueWithFixDisabled') }) + + it('renders finding text in the tooltip as plain text with no commands enabled', function () { + const link = '[x](command:workbench.action.terminal.sendSequence?%7B%22text%22%3A%22ls%22%7D)' + const issue = createCodeScanIssue({ title: `title ${link}` }) + issue.recommendation.text = link + const tooltip = new IssueItem('file/path', issue).tooltip as vscode.MarkdownString + assert.deepStrictEqual(tooltip.isTrusted, { enabledCommands: [] }) + assert.ok(!tooltip.supportHtml) + assert.ok(!tooltip.value.includes('](command:')) + assert.ok(tooltip.value.includes('\\[x\\]\\(command:')) + }) }) diff --git a/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts b/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts index bb9fe2cafa..70c04bb69c 100644 --- a/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts +++ b/packages/core/src/codewhisperer/service/securityIssueHoverProvider.ts @@ -11,6 +11,14 @@ import { AuthUtil } from '../util/authUtil' import { TelemetryHelper } from '../util/telemetryHelper' import { SecurityIssueProvider } from './securityIssueProvider' +/** The only commands the hover links to; command links to anything else are not run. */ +const hoverCommandIds = [ + 'aws.amazonq.explainIssue', + 'aws.amazonq.generateFix', + 'aws.amazonq.security.ignore', + 'aws.amazonq.security.ignoreAll', +] as const + export class SecurityIssueHoverProvider implements vscode.HoverProvider { static #instance: SecurityIssueHoverProvider private issueProvider: SecurityIssueProvider = SecurityIssueProvider.instance @@ -66,17 +74,20 @@ export class SecurityIssueHoverProvider implements vscode.HoverProvider { private _getContent(filePath: string, issue: CodeScanIssue) { const markdownString = new vscode.MarkdownString() - markdownString.isTrusted = true - markdownString.supportHtml = true + markdownString.isTrusted = { enabledCommands: [...hoverCommandIds] } markdownString.supportThemeIcons = true markdownString.baseUri = vscode.Uri.file(path.join(globals.context.extensionPath, 'resources/images/')) const [suggestedFix] = issue.suggestedFixes - markdownString.appendMarkdown(`## ${issue.title} ${this._makeSeverityBadge(issue.severity)}\n`) - markdownString.appendMarkdown( - `${suggestedFix?.code && suggestedFix.description !== '' ? suggestedFix.description : issue.recommendation.text}\n\n` + // Finding text is appended as plain text so it cannot add links, images or HTML to the hover. + markdownString.appendMarkdown('## ') + markdownString.appendText(issue.title) + markdownString.appendMarkdown(` ${this._makeSeverityBadge(issue.severity)}\n`) + markdownString.appendText( + suggestedFix?.code && suggestedFix.description !== '' ? suggestedFix.description : issue.recommendation.text ) + markdownString.appendMarkdown('\n\n') const explainWithQCommand = this._getCommandMarkdown( 'aws.amazonq.explainIssue', diff --git a/packages/core/src/codewhisperer/service/securityIssueTreeViewProvider.ts b/packages/core/src/codewhisperer/service/securityIssueTreeViewProvider.ts index 9990b50fd9..1031679334 100644 --- a/packages/core/src/codewhisperer/service/securityIssueTreeViewProvider.ts +++ b/packages/core/src/codewhisperer/service/securityIssueTreeViewProvider.ts @@ -206,11 +206,13 @@ export class IssueItem extends vscode.TreeItem { private getTooltipMarkdown() { const markdown = new vscode.MarkdownString() - markdown.isTrusted = true - markdown.supportHtml = true + // The tooltip has no command links; finding text is appended as plain text so it cannot add any. + markdown.isTrusted = { enabledCommands: [] } markdown.supportThemeIcons = true - markdown.appendMarkdown(`## ${this.issue.title} ![${this.issue.severity}](${this.getSeverityImage()})\n`) - markdown.appendMarkdown(this.issue.recommendation.text) + markdown.appendMarkdown('## ') + markdown.appendText(this.issue.title) + markdown.appendMarkdown(` ![${this.issue.severity}](${this.getSeverityImage()})\n`) + markdown.appendText(this.issue.recommendation.text) return markdown }