src/goVulncheck: add actions to upgrade modules
If fixed version is known, present two options in the webview:
go get <the fixed version>
go get @latest
The webview html page listens the click event, and sends a 'fix'
type message to the extension side. The extension then invokes
gopls.upgrade_dependency command.
Currently, this dependency upgrade command can result in inconsistent
go.sum error and users will need to address them separately.
Change-Id: Iceef1ce49a7eff57bb65d4d78e1ecab0b3b74f73
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/412318
TryBot-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jamal Carvalho <jamal@golang.org>
Run-TryBot: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/media/vulncheckView.css b/media/vulncheckView.css
index 96cc78f..289c8d4 100644
--- a/media/vulncheckView.css
+++ b/media/vulncheckView.css
@@ -21,6 +21,16 @@
padding-bottom: 0.5em;
}
+.vuln-fix:hover,
+.vuln-fix:active {
+ color: var(--vscode-textLink-activeForeground);
+}
+.vuln-fix {
+ cursor:pointer;
+ color: var(--vscode-textLink-foreground);
+ text-decoration:underline;
+}
+
details summary {
cursor: pointer;
position: relative;
diff --git a/media/vulncheckView.js b/media/vulncheckView.js
index 7a61b84..d5e5ef4 100644
--- a/media/vulncheckView.js
+++ b/media/vulncheckView.js
@@ -16,12 +16,18 @@
vulnsContainer.addEventListener('click', (event) => {
let node = event && event.target;
+ let handled = false;
+ console.log(`${node.type} ${node.tagName} ${node.className} ${node.id} data:${node.dataset?.target} dir:${node.dataset?.dir}`);
if (node?.tagName === 'A' && node.href) {
// Ask vscode to handle link opening.
vscode.postMessage({ type: 'open', target: node.href });
+ } else if (node?.tagName === 'SPAN' && node.className === 'vuln-fix' && node.dataset?.target && node.dataset?.dir) {
+ vscode.postMessage({ type: 'fix', target: node.dataset?.target, dir: node.dataset?.dir });
+ }
+
+ if (handled) {
event.preventDefault();
event.stopPropagation();
- return;
}
});
@@ -37,6 +43,13 @@
return 'N/A'
}
+ function offerUpgrade(/** @type {string} */dir, /** @type {string} */mod, /** @type {string|undefined} */ver) {
+ if (dir && mod && ver) {
+ return ` [<span class="vuln-fix" data-target="${mod}@${ver}" data-dir="${dir}">go get</span> | <span class="vuln-fix" data-target="${mod}@latest" data-dir="${dir}">go get latest</span>]`
+ }
+ return '';
+ }
+
function snapshotContent() {
const res = {
'log': logContainer.innerHTML,
@@ -102,7 +115,7 @@
details.innerHTML = `
<tr><td>Package</td><td>${vuln.PkgPath}</td></tr>
<tr><td>Found in Version</td><td>${moduleVersion(vuln.ModPath, vuln.CurrentVersion)}</td></tr>
- <tr><td>Fixed Version</td><td>${moduleVersion(vuln.ModPath, vuln.FixedVersion)}</td></tr>
+ <tr><td>Fixed Version</td><td>${moduleVersion(vuln.ModPath, vuln.FixedVersion)} ${offerUpgrade(json.Dir, vuln.ModPath, vuln.FixedVersion)}</td></tr>
<tr><td>Affecting</td><td>${vuln.AffectedPkgs?.join('<br>')}</td></tr>
`;
element.appendChild(details);
diff --git a/src/goMain.ts b/src/goMain.ts
index 0213e03..db31bbb 100644
--- a/src/goMain.ts
+++ b/src/goMain.ts
@@ -170,7 +170,7 @@
GoExplorerProvider.setup(ctx);
VulncheckProvider.setup(ctx, goCtx);
- VulncheckResultViewProvider.register(ctx);
+ VulncheckResultViewProvider.register(ctx, goCtx);
registerCommand('go.test.generate.package', goGenerateTests.generateTestCurrentPackage);
registerCommand('go.test.generate.file', goGenerateTests.generateTestCurrentFile);
diff --git a/src/goVulncheck.ts b/src/goVulncheck.ts
index c070583..ff4bed4 100644
--- a/src/goVulncheck.ts
+++ b/src/goVulncheck.ts
@@ -14,17 +14,22 @@
import * as readline from 'readline';
import { URI } from 'vscode-uri';
import { promisify } from 'util';
+import { runGoEnv } from './goModules';
+import { ExecuteCommandParams, ExecuteCommandRequest } from 'vscode-languageserver-protocol';
export class VulncheckResultViewProvider implements vscode.CustomTextEditorProvider {
public static readonly viewType = 'vulncheck.view';
- public static register({ extensionUri, subscriptions }: vscode.ExtensionContext): VulncheckResultViewProvider {
- const provider = new VulncheckResultViewProvider(extensionUri);
+ public static register(
+ { extensionUri, subscriptions }: vscode.ExtensionContext,
+ goCtx: GoExtensionContext
+ ): VulncheckResultViewProvider {
+ const provider = new VulncheckResultViewProvider(extensionUri, goCtx);
subscriptions.push(vscode.window.registerCustomEditorProvider(VulncheckResultViewProvider.viewType, provider));
return provider;
}
- constructor(private readonly extensionUri: vscode.Uri) {}
+ constructor(private readonly extensionUri: vscode.Uri, private readonly goCtx: GoExtensionContext) {}
/**
* Called when our custom editor is opened.
@@ -39,7 +44,7 @@
webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview);
// Receive message from the webview.
- webviewPanel.webview.onDidReceiveMessage(this.handleMessage);
+ webviewPanel.webview.onDidReceiveMessage(this.handleMessage, this);
function updateWebview() {
webviewPanel.webview.postMessage({ type: 'update', text: document.getText() });
@@ -105,7 +110,7 @@
</html>`;
}
- private handleMessage(e: { type: string; target?: string }): void {
+ private async handleMessage(e: { type: string; target?: string; dir?: string }): Promise<void> {
switch (e.type) {
case 'open':
{
@@ -125,6 +130,16 @@
}
}
return;
+ case 'fix':
+ {
+ if (!e.target || !e.dir) return;
+ const modFile = await getGoModFile(vscode.Uri.file(e.dir));
+ if (modFile) {
+ await goplsUpgradeDependency(this.goCtx, vscode.Uri.file(modFile), [e.target], false);
+ // TODO: run go mod tidy?
+ }
+ }
+ return;
case 'snapshot-result':
// response for `snapshot-request`.
return;
@@ -134,6 +149,38 @@
}
}
+const GOPLS_UPGRADE_DEPENDENCY = 'gopls.upgrade_dependency';
+async function goplsUpgradeDependency(
+ goCtx: GoExtensionContext,
+ goModFileUri: vscode.Uri,
+ goCmdArgs: string[],
+ addRequire: boolean
+): Promise<void> {
+ const { languageClient } = goCtx;
+ const uri = languageClient?.code2ProtocolConverter.asUri(goModFileUri);
+ const params: ExecuteCommandParams = {
+ command: GOPLS_UPGRADE_DEPENDENCY,
+ arguments: [
+ {
+ URI: uri,
+ GoCmdArgs: goCmdArgs,
+ AddRequire: addRequire
+ }
+ ]
+ };
+ return await languageClient?.sendRequest(ExecuteCommandRequest.type, params);
+}
+
+async function getGoModFile(dir: vscode.Uri): Promise<string | undefined> {
+ try {
+ const p = await runGoEnv(dir, ['GOMOD']);
+ return p['GOMOD'] === '/dev/null' || p['GOMOD'] === 'NUL' ? '' : p['GOMOD'];
+ } catch (e) {
+ vscode.window.showErrorMessage(`Failed to find 'go.mod' for ${dir}: ${e}`);
+ }
+ return;
+}
+
export class VulncheckProvider {
static scheme = 'govulncheck';
static setup({ subscriptions }: vscode.ExtensionContext, goCtx: GoExtensionContext) {
diff --git a/test/gopls/vulncheck.test.ts b/test/gopls/vulncheck.test.ts
index 75fa8fc..70a622f 100644
--- a/test/gopls/vulncheck.test.ts
+++ b/test/gopls/vulncheck.test.ts
@@ -21,7 +21,7 @@
let provider: goVulncheck.VulncheckResultViewProvider;
setup(() => {
- provider = new goVulncheck.VulncheckResultViewProvider(extensionUri);
+ provider = new goVulncheck.VulncheckResultViewProvider(extensionUri, {});
});
teardown(async () => {