Hyang-Ah Hana Kim has uploaded this change for review.
src/goVulncheck: VulncheckResultViewProvider
VulncheckResultViewProvider provides a custom text document editor
to process `gopls vulncheck` JSON output and render it in a Webview.
The webview presents the data in a similar way `govulncheck -html`
presents the summary of the findings. But by implementing it inside
the extension, we can make the extension handle commands and document
links inside the editor. For example, in this CL, we embed a js script
that listens 'click' event on all links in the HTML document, and
delegates the extension part to handle link opening events. That way,
if the resource is a file type, the extension can open it inside the
editor.
For communication between the Extension side and the WebView side,
we use vscode's API to pass messages and follow the instruction in
https://code.visualstudio.com/api/extension-guides/webview#scripts-and-message-passing
The extension sends 'update' msg to the webview: that triggers update
of DOM contents based on the input.
The webview sends 'link' msg to the webview: that triggers handling
of the link opening event. All file links in the callgraph entries
carry the position information as a query parameter. The extension
parses the uri, open the file with vscode.openWith command (that allows
us to control which view column the editor should be placed, and which
line in the file should get focus).
In order to support testing, the extension and the webview exchange
'snapshot-request' and 'snapshot-result' messages. When we have UI testing
framework established, these message may be able to be replaced with it.
Change-Id: I9a343b972642a9197313d9d42af37b5a2d5ccefa
---
A media/reset.css
A media/vscode.css
A media/vulncheckView.css
A media/vulncheckView.js
M src/goVulncheck.ts
A test/gopls/vulncheck.test.ts
A test/testdata/vuln/test.vulncheck.json
7 files changed, 587 insertions(+), 0 deletions(-)
diff --git a/media/reset.css b/media/reset.css
new file mode 100644
index 0000000..908d944
--- /dev/null
+++ b/media/reset.css
@@ -0,0 +1,25 @@
+html {
+ box-sizing: border-box;
+ font-size: 13px;
+}
+
+*,
+*:before,
+*:after {
+ box-sizing: inherit;
+}
+
+body,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+p,
+ol,
+ul {
+ margin: 0;
+ padding: 0;
+ font-weight: normal;
+}
\ No newline at end of file
diff --git a/media/vscode.css b/media/vscode.css
new file mode 100644
index 0000000..84a92ee
--- /dev/null
+++ b/media/vscode.css
@@ -0,0 +1,40 @@
+:root {
+ --container-paddding: 20px;
+ --input-padding-vertical: 6px;
+ --input-padding-horizontal: 4px;
+ --input-margin-vertical: 4px;
+ --input-margin-horizontal: 0;
+}
+
+body {
+ padding: 0 var(--container-paddding);
+ color: var(--vscode-foreground);
+ font-size: var(--vscode-font-size);
+ font-weight: var(--vscode-font-weight);
+ font-family: var(--vscode-font-family);
+ background-color: var(--vscode-editor-background);
+}
+
+body > *,
+form > * {
+ margin-block-start: var(--input-margin-vertical);
+ margin-block-end: var(--input-margin-vertical);
+}
+
+*:focus {
+ outline-color: var(--vscode-focusBorder) !important;
+}
+
+a {
+ color: var(--vscode-textLink-foreground);
+}
+
+a:hover,
+a:active {
+ color: var(--vscode-textLink-activeForeground);
+}
+
+code {
+ font-size: var(--vscode-editor-font-size);
+ font-family: var(--vscode-editor-font-family);
+}
\ No newline at end of file
diff --git a/media/vulncheckView.css b/media/vulncheckView.css
new file mode 100644
index 0000000..2467fcf
--- /dev/null
+++ b/media/vulncheckView.css
@@ -0,0 +1,37 @@
+.log {
+ font-weight: lighter;
+}
+
+.vuln {
+ text-align: left;
+ padding-bottom: 1em;
+}
+
+.vuln-desc {
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+}
+
+.vuln-details {
+ padding-bottom: 0.5em;
+}
+
+details summary {
+ cursor: pointer;
+ position: relative;
+}
+
+details summary > * {
+ display: inline;
+ position: relative;
+}
+
+.stacks {
+ padding: 1em;
+}
+
+.stack {
+ padding: 1em;
+ font-size: var(--vscode-editor-font-size);
+ font-family: var(--vscode-editor-font-family);
+}
\ No newline at end of file
diff --git a/media/vulncheckView.js b/media/vulncheckView.js
new file mode 100644
index 0000000..0f5c29a
--- /dev/null
+++ b/media/vulncheckView.js
@@ -0,0 +1,178 @@
+/*---------------------------------------------------------
+ * Copyright 2022 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+
+// Script for VulncheckResultViewProvider's webview.
+
+(function () {
+
+ // @ts-ignore
+ const vscode = acquireVsCodeApi();
+
+ const logContainer = /** @type {HTMLElement} */ (document.querySelector('.log'));
+ const vulnsContainer = /** @type {HTMLElement} */ (document.querySelector('.vulns'));
+
+ vulnsContainer.addEventListener('click', (event) => {
+ let node = event && event.target;
+ if (node && node.tagName && node.tagName === 'A' && node.href) {
+ // Ask vscode to handle link opening.
+ vscode.postMessage({ type: 'open', target: node.href });
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ }
+ });
+
+ const errorContainer = document.createElement('div');
+ document.body.appendChild(errorContainer);
+ errorContainer.className = 'error'
+ errorContainer.style.display = 'none'
+
+ function moduleVersion(/** @type {string} */mod, /** @type {string|undefined} */ver) {
+ if (ver) {
+ return `<a href="https://pkg.go.dev/${mod}@${ver}">${mod}@${ver}</a>`;
+ }
+ return 'N/A'
+ }
+
+ function snapshotContent() {
+ return vulnsContainer.innerHTML;
+ }
+
+ /**
+ * Render the document in the webview.
+ */
+ function updateContent(/** @type {string} */ text) {
+ let json;
+ try {
+ if (!text) {
+ text = '{}';
+ }
+ json = JSON.parse(text);
+ } catch {
+ errorContainer.innerText = 'Error: Document is not valid json';
+ errorContainer.style.display = '';
+ return;
+ }
+ errorContainer.style.display = 'none';
+
+ logContainer.innerHTML = '';
+ const runLog = document.createElement('table');
+ const timeinfo = (startDate, durationMillisec) => {
+ if (!startDate) { return '' }
+ return durationMillisec ? `${startDate} (took ${durationMillisec} msec)` : `${startDate}`;
+ }
+
+ runLog.innerHTML = `
+<tr><td>Dir:</td><td>${json.Dir || ''}</td></tr>
+<tr><td>Pattern:</td><td>${json.Pattern || ''}</td></tr>
+<tr><td>Analyzed at:</td><td>${timeinfo(json.Start, json.Duration)}</td></tr>`;
+ logContainer.appendChild(runLog);
+
+ const vulns = json.Vuln || [];
+ vulnsContainer.innerHTML = '';
+
+ vulns.forEach((vuln) => {
+ const element = document.createElement('div');
+ element.className = 'vuln';
+ vulnsContainer.appendChild(element);
+
+ // TITLE - Vuln ID
+ const title = document.createElement('h2');
+ title.innerHTML = `<a href="${vuln.URL}">${vuln.ID}</a>`;
+ title.className = 'vuln-title';
+ element.appendChild(title);
+
+ // DESCRIPTION - short text (alases)
+ const desc = document.createElement('p');
+ desc.innerHTML = Array.isArray(vuln.Aliases) && vuln.Aliases.length ? `${vuln.Details} (${vuln.Aliases.join(', ')})` : vuln.Details;
+ desc.className = 'vuln-desc';
+ element.appendChild(desc);
+
+ // DETAILS - dump of all details
+ const details = document.createElement('table');
+ details.className = 'vuln-details'
+ details.innerHTML = `
+ <tr><td>Package</td><td>${vuln.PkgPath}</td></tr>
+ <tr><td>Current Version</td><td>${moduleVersion(vuln.ModPath, vuln.CurrentVersion)}</td></tr>
+ <tr><td>Fixed Version</td><td>${moduleVersion(vuln.ModPath, vuln.FixedVersion)}</td></tr>
+ `;
+ element.appendChild(details);
+
+ /* TODO: Action for module version upgrade */
+ /* TODO: Explain module dependency - why am I depending on this vulnerable version? */
+
+ // EXAMPLARS - call stacks (initially hidden)
+ const examples = document.createElement('details');
+ examples.innerHTML = `<summary>${vuln.CallStackSummaries?.length || 0}+ findings</summary>`;
+
+ // Call stacks
+ const callstacksContainer = document.createElement('p');
+ callstacksContainer.className = 'stacks';
+ vuln.CallStackSummaries?.forEach((summary, idx) => {
+ const callstack = document.createElement('details');
+ const s = document.createElement('summary');
+ s.innerText = summary;
+ callstack.appendChild(s);
+
+ const stack = document.createElement('div');
+ stack.className = 'stack';
+ const cs = vuln.CallStacks[idx];
+ cs.forEach((c) => {
+ const p = document.createElement('p');
+ const pos = c.URI ? `${c.URI}?${c.Pos.line || 0}` : '';
+ p.innerHTML = pos ? `<a href="${pos}">${c.Name}</a>` : c.Name;
+ stack.appendChild(p);
+ });
+ callstack.appendChild(stack);
+
+ callstacksContainer.appendChild(callstack);
+ })
+
+ examples.appendChild(callstacksContainer);
+ element.appendChild(examples);
+ });
+ }
+
+ // Message Passing between Extension and Webview
+ //
+ // Extension sends 'update' to Webview to trigger rerendering.
+ // Webview sends 'link' to Extension to forward all link
+ // click events so the extension can handle the event.
+ //
+ // Extension sends 'snapshot-request' to trigger dumping
+ // of the current DOM in the 'vulns' container.
+ // Webview sends 'snapshot-result' to the extension
+ // as the response to snapshot-request.
+
+ // Handle messages sent from the extension to the webview
+ window.addEventListener('message', event => {
+ const message = event.data; // The json data that the extension sent
+ switch (message.type) {
+ case 'update':
+ const text = message.text;
+
+ updateContent(text);
+ // Then persist state information.
+ // This state is returned in the call to `vscode.getState` below when a webview is reloaded.
+ vscode.setState({ text });
+ return;
+ // Message for testing. Returns a current DOM in a serialized format.
+ case 'snapshot-request':
+ const result = snapshotContent();
+ vscode.postMessage({ type: 'snapshot-result', target: result });
+ return;
+ }
+ });
+
+ // Webviews are normally torn down when not visible and re-created when they become visible again.
+ // State lets us save information across these re-loads
+ const state = vscode.getState();
+ if (state) {
+ updateContent(state.text);
+ };
+ // TODO: Handle 'details' expansion info and store the state using
+ // vscode.setState or retainContextWhenHidden. Currently, we are storing only
+ // the document text. (see windowEventHandler)
+}());
diff --git a/src/goVulncheck.ts b/src/goVulncheck.ts
index 96cb6ea..9d01bfe 100644
--- a/src/goVulncheck.ts
+++ b/src/goVulncheck.ts
@@ -11,6 +11,126 @@
import { toolExecutionEnvironment } from './goEnv';
import { killProcessTree } from './utils/processUtils';
import * as readline from 'readline';
+import { URI } from 'vscode-uri';
+
+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);
+ subscriptions.push(vscode.window.registerCustomEditorProvider(VulncheckResultViewProvider.viewType, provider));
+ return provider;
+ }
+
+ constructor(private readonly extensionUri: vscode.Uri) {}
+
+ /**
+ * Called when our custom editor is opened.
+ */
+ public async resolveCustomTextEditor(
+ document: vscode.TextDocument,
+ webviewPanel: vscode.WebviewPanel,
+ _: vscode.CancellationToken // eslint-disable-line @typescript-eslint/no-unused-vars
+ ): Promise<void> {
+ // Setup initial content for the webview
+ webviewPanel.webview.options = { enableScripts: true };
+ webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview);
+
+ // Receive message from the webview.
+ webviewPanel.webview.onDidReceiveMessage(this.handleMessage);
+
+ function updateWebview() {
+ webviewPanel.webview.postMessage({ type: 'update', text: document.getText() });
+ }
+
+ // Hook up event handlers so that we can synchronize the webview with the text document.
+ //
+ // The text document acts as our model, so we have to sync change in the document to our
+ // editor and sync changes in the editor back to the document.
+ //
+ // Remember that a single text document can also be shared between multiple custom
+ // editors (this happens for example when you split a custom editor)
+ const changeDocumentSubscription = vscode.workspace.onDidChangeTextDocument((e) => {
+ if (e.document.uri.toString() === document.uri.toString()) {
+ updateWebview();
+ }
+ });
+
+ // Make sure we get rid of the listener when our editor is closed.
+ webviewPanel.onDidDispose(() => {
+ changeDocumentSubscription.dispose();
+ });
+
+ updateWebview();
+ }
+
+ /**
+ * Get the static html used for the editor webviews.
+ */
+ private getHtmlForWebview(webview: vscode.Webview): string {
+ const mediaUri = vscode.Uri.joinPath(this.extensionUri, 'media');
+ // Local path to script and css for the webview
+ const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(mediaUri, 'vulncheckView.js'));
+ const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath(mediaUri, 'reset.css'));
+ const styleVSCodeUri = webview.asWebviewUri(vscode.Uri.joinPath(mediaUri, 'vscode.css'));
+ const styleMainUri = webview.asWebviewUri(vscode.Uri.joinPath(mediaUri, 'vulncheckView.css'));
+
+ // Use a nonce to whitelist which scripts can be run
+ const nonce = getNonce();
+
+ return /* html */ `
+ <!DOCTYPE html>
+ <html lang="en">
+ <head>
+ <meta charset="UTF-8">
+ <!--
+ Use a content security policy to only allow loading images from https or from our extension directory,
+ and only allow scripts that have a specific nonce.
+ -->
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${webview.cspSource}; style-src ${webview.cspSource}; script-src 'nonce-${nonce}';">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <link href="${styleResetUri}" rel="stylesheet" />
+ <link href="${styleVSCodeUri}" rel="stylesheet" />
+ <link href="${styleMainUri}" rel="stylesheet" />
+ <title>Vulnerability Report - govulncheck</title>
+ </head>
+ <body>
+ <div class="log"></div>
+ <div class="vulns"></div>
+
+ <script nonce="${nonce}" src="${scriptUri}"></script>
+ </body>
+ </html>`;
+ }
+
+ private handleMessage(e: { type: string; target?: string }): void {
+ switch (e.type) {
+ case 'open':
+ {
+ if (!e.target) return;
+ const uri = safeURIParse(e.target);
+ if (!uri || !uri.scheme) return;
+ if (uri.scheme === 'https') {
+ vscode.env.openExternal(uri);
+ } else if (uri.scheme === 'file') {
+ const line = uri.query ? Number(uri.query.split(':')[0]) : undefined;
+ const range = line ? new vscode.Range(line, 0, line, 0) : undefined;
+ vscode.window.showTextDocument(
+ vscode.Uri.from({ scheme: uri.scheme, path: uri.path }),
+ // prefer the first column to present the source.
+ { viewColumn: vscode.ViewColumn.One, selection: range }
+ );
+ }
+ }
+ return;
+ case 'snapshot-result':
+ // response for `snapshot-request`.
+ return;
+ default:
+ console.log(`unrecognized type message: ${e.type}`);
+ }
+ }
+}
export class VulncheckProvider {
static scheme = 'govulncheck';
@@ -256,3 +376,20 @@
character: number;
};
}
+
+function getNonce() {
+ let text = '';
+ const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+ for (let i = 0; i < 32; i++) {
+ text += possible.charAt(Math.floor(Math.random() * possible.length));
+ }
+ return text;
+}
+
+function safeURIParse(s: string): URI | undefined {
+ try {
+ return URI.parse(s);
+ } catch (_) {
+ return undefined;
+ }
+}
diff --git a/test/gopls/vulncheck.test.ts b/test/gopls/vulncheck.test.ts
new file mode 100644
index 0000000..5e14408
--- /dev/null
+++ b/test/gopls/vulncheck.test.ts
@@ -0,0 +1,85 @@
+/*---------------------------------------------------------
+ * Copyright 2022 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+import assert from 'assert';
+import path = require('path');
+import vscode = require('vscode');
+import { extensionId } from '../../src/const';
+import goVulncheck = require('../../src/goVulncheck');
+
+suite.only('vulncheck result viewer tests', () => {
+ const webviewId = 'vulncheck';
+ const extensionUri = vscode.extensions.getExtension(extensionId)!.extensionUri;
+ const fixtureDir = path.join(__dirname, '..', '..', '..', 'test', 'testdata', 'vuln');
+
+ const disposables: vscode.Disposable[] = [];
+ function _register<T extends vscode.Disposable>(disposable: T) {
+ disposables.push(disposable);
+ return disposable;
+ }
+ let provider: goVulncheck.VulncheckResultViewProvider;
+
+ setup(() => {
+ provider = new goVulncheck.VulncheckResultViewProvider(extensionUri);
+ });
+
+ teardown(async () => {
+ await vscode.commands.executeCommand('workbench.action.closeAllEditors');
+ vscode.Disposable.from(...disposables).dispose();
+ });
+
+ test('populates webview', async () => {
+ const webviewPanel = _register(
+ vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, {})
+ );
+ const source = path.join(fixtureDir, 'test.vulncheck.json');
+ const doc = await vscode.workspace.openTextDocument(source);
+ const canceller = new vscode.CancellationTokenSource();
+ _register(canceller);
+
+ const watcher = getMessage<{ type: string; target?: string }>(webviewPanel);
+
+ await provider.resolveCustomTextEditor(doc, webviewPanel, canceller.token);
+ webviewPanel.reveal();
+
+ // Trigger snapshotContent that sends `snapshot-result` as a result.
+ webviewPanel.webview.postMessage({ type: 'snapshot-request' });
+ const res = await watcher;
+
+ assert.deepStrictEqual(res.type, 'snapshot-result', `want snapshot-result, got ${JSON.stringify(res)}`);
+ assert(res.target && res.target.includes('GO-2021-0113'), res.target);
+ });
+
+ test('handles invalid input', async () => {
+ const webviewPanel = _register(
+ vscode.window.createWebviewPanel(webviewId, 'title', { viewColumn: vscode.ViewColumn.One }, {})
+ );
+ // Empty doc.
+ const doc = await vscode.workspace.openTextDocument(
+ vscode.Uri.file('bogus.vulncheck.json').with({ scheme: 'untitled' })
+ );
+ const canceller = new vscode.CancellationTokenSource();
+ _register(canceller);
+
+ const watcher = getMessage<{ type: string; target?: string }>(webviewPanel);
+
+ await provider.resolveCustomTextEditor(doc, webviewPanel, canceller.token);
+ webviewPanel.reveal();
+
+ // Trigger snapshotContent that sends `snapshot-result` as a result.
+ webviewPanel.webview.postMessage({ type: 'snapshot-request' });
+ const res = await watcher;
+ assert.deepStrictEqual(res.type, 'snapshot-result', `want snapshot-result, got ${JSON.stringify(res)}`);
+ assert(!res.target, res.target);
+ });
+});
+
+function getMessage<R = { type: string; target?: string }>(webview: vscode.WebviewPanel): Promise<R> {
+ return new Promise<R>((resolve) => {
+ const sub = webview.webview.onDidReceiveMessage((message) => {
+ sub.dispose();
+ resolve(message);
+ });
+ });
+}
diff --git a/test/testdata/vuln/test.vulncheck.json b/test/testdata/vuln/test.vulncheck.json
new file mode 100644
index 0000000..79a19ad
--- /dev/null
+++ b/test/testdata/vuln/test.vulncheck.json
@@ -0,0 +1,47 @@
+{
+ "Vuln": [
+ {
+ "ID": "GO-2021-0113",
+ "Details": "Due to improper index calculation, an incorrectly formatted language tag can cause Parse\nto panic via an out of bounds read. If Parse is used to process untrusted user inputs,\nthis may be used as a vector for a denial of service attack.\n",
+ "Aliases": [
+ "CVE-2021-38561"
+ ],
+ "Symbol": "Parse",
+ "PkgPath": "golang.org/x/text/language",
+ "ModPath": "golang.org/x/text",
+ "URL": "https://pkg.go.dev/vuln/GO-2021-0113",
+ "CurrentVersion": "v0.0.0-20170915032832-14c0d48ead0c",
+ "FixedVersion": "v0.3.7",
+ "CallStacks": [
+ [
+ {
+ "Name": "github.com/golang/vscode-go/test/testdata/vuln.main",
+ "URI": "file:///Users/hakim/projects/vscode-go/test/testdata/vuln/test.go",
+ "Pos": {
+ "line": 9,
+ "character": 0
+ }
+ },
+ {
+ "Name": "golang.org/x/text/language.Parse",
+ "URI": "file:///Users/hakim/go/pkg/mod/golang.org/x/te...@v0.0.0-20170915032832-14c0d48ead0c/language/parse.go",
+ "Pos": {
+ "line": 227,
+ "character": 0
+ }
+ }
+ ]
+ ],
+ "CallStackSummaries": [
+ "github.com/golang/vscode-go/test/testdata/vuln.main calls golang.org/x/text/language.Parse"
+ ],
+ "AffectedPkgs": [
+ "github.com/golang/vscode-go/test/testdata/vuln"
+ ]
+ }
+ ],
+ "Start": "2022-05-16T13:43:54.437Z",
+ "Duration": 1407,
+ "Dir": "/Users/hakim/projects/vscode-go/test/testdata/vuln",
+ "Pattern": "./..."
+}
To view, visit change 406300. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Jamal Carvalho, Suzy Mueller.
Patch set 1:Run-TryBot +1
Attention is currently required from: Jamal Carvalho, Suzy Mueller.
Kokoro presubmit build finished with status: SUCCESS
Logs at: https://source.cloud.google.com/results/invocations/8027bd9d-2829-45f5-8d13-379c09a0ffb6
Patch set 1:TryBot-Result +1
Attention is currently required from: Hyang-Ah Hana Kim, Suzy Mueller.
Patch set 1:Code-Review +2
9 comments:
Patchset:
Nice!
File media/vulncheckView.css:
nit: mixed use of tabs and spaces
nit: whitespace
File media/vulncheckView.js:
What do you think of using typescript and adding a build step for this file? It might be more than its worth unless we imagine doing more with webviews.
Patch Set #1, Line 18: node && node.tagName && node.tagName
Can you use optional chaining throughout this file? e.g.,`node?.tagName === 'A' && node.href`
if (!text) {
text = '{}';
}
You can remove this if you add a default parameter on 46.
`function updateContent(text = '{}')`
Patch Set #1, Line 87: // DESCRIPTION - short text (alases)
sp: aliases
Patch Set #1, Line 106: EXAMPLARS
sp: EXEMPLARS
File test/gopls/vulncheck.test.ts:
Remove .only
To view, visit change 406300. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Hyang-Ah Hana Kim, Suzy Mueller.
Hyang-Ah Hana Kim uploaded patch set #2 to this change.
The following approvals got outdated and were removed: Run-TryBot+1 by Hyang-Ah Hana Kim, TryBot-Result+1 by kokoro
7 files changed, 599 insertions(+), 0 deletions(-)
To view, visit change 406300. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Hyang-Ah Hana Kim, Suzy Mueller.
Kokoro presubmit build finished with status: SUCCESS
Logs at: https://source.cloud.google.com/results/invocations/161ae06a-69b6-4aac-b09c-e33fa75f9b78
Patch set 2:TryBot-Result +1
Attention is currently required from: Hyang-Ah Hana Kim, Suzy Mueller.
Kokoro presubmit build finished with status: FAILURE
Logs at: https://source.cloud.google.com/results/invocations/94ebb3b1-5ba2-452b-88e7-4f767c445821
Patch set 3:TryBot-Result -1
Attention is currently required from: Hyang-Ah Hana Kim, Suzy Mueller.
Hyang-Ah Hana Kim uploaded patch set #4 to this change.
The following approvals got outdated and were removed: Run-TryBot+1 by Hyang-Ah Hana Kim, TryBot-Result-1 by kokoro
src/goVulncheck: add VulncheckResultViewProvider
To view, visit change 406300. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Hyang-Ah Hana Kim, Suzy Mueller.
Kokoro presubmit build finished with status: SUCCESS
Logs at: https://source.cloud.google.com/results/invocations/c434efcc-8a00-4a6a-a673-20d5fb4feccb
Patch set 4:TryBot-Result +1
Attention is currently required from: Suzy Mueller.
8 comments:
File media/vulncheckView.css:
nit: mixed use of tabs and spaces
Done. Ran formatter.
nit: whitespace
Done
File media/vulncheckView.js:
What do you think of using typescript and adding a build step for this file? It might be more than i […]
Filed https://github.com/golang/vscode-go/issues/2247 and as discussed offline, it's a good idea!
Patch Set #1, Line 18: node && node.tagName && node.tagName
Can you use optional chaining throughout this file? e.g.,`node?.tagName === 'A' && node. […]
oh, thanks!
if (!text) {
text = '{}';
}
You can remove this if you add a default parameter on 46. […]
Indeed.
Patch Set #1, Line 87: // DESCRIPTION - short text (alases)
sp: aliases
Done
Patch Set #1, Line 106: EXAMPLARS
sp: EXEMPLARS
Done
File test/gopls/vulncheck.test.ts:
Remove . […]
🤦🏾♀️
To view, visit change 406300. To unsubscribe, or for help writing mail filters, visit settings.
Hyang-Ah Hana Kim submitted this change.
1 is the latest approved patch-set.
The change was submitted with unreviewed changes in the following files:
```
The name of the file: media/vulncheckView.css
Insertions: 10, Deletions: 5.
@@ -1,5 +1,10 @@
+/*---------------------------------------------------------
+ * Copyright 2022 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+
.log {
- font-weight: lighter;
+ font-weight: lighter;
}
.vuln {
@@ -16,13 +21,13 @@
padding-bottom: 0.5em;
}
-details summary {
- cursor: pointer;
+details summary {
+ cursor: pointer;
position: relative;
}
-details summary > * {
- display: inline;
+details summary>* {
+ display: inline;
position: relative;
}
```
```
The name of the file: test/gopls/vulncheck.test.ts
Insertions: 1, Deletions: 1.
@@ -8,7 +8,7 @@
import { extensionId } from '../../src/const';
import goVulncheck = require('../../src/goVulncheck');
-suite.only('vulncheck result viewer tests', () => {
+suite('vulncheck result viewer tests', () => {
const webviewId = 'vulncheck';
const extensionUri = vscode.extensions.getExtension(extensionId)!.extensionUri;
const fixtureDir = path.join(__dirname, '..', '..', '..', 'test', 'testdata', 'vuln');
```
```
The name of the file: media/reset.css
Insertions: 5, Deletions: 0.
@@ -1,3 +1,8 @@
+/*---------------------------------------------------------
+ * Copyright 2022 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+
html {
box-sizing: border-box;
font-size: 13px;
```
```
The name of the file: media/vulncheckView.js
Insertions: 4, Deletions: 7.
@@ -15,7 +15,7 @@
vulnsContainer.addEventListener('click', (event) => {
let node = event && event.target;
- if (node && node.tagName && node.tagName === 'A' && node.href) {
+ if (node?.tagName === 'A' && node.href) {
// Ask vscode to handle link opening.
vscode.postMessage({ type: 'open', target: node.href });
event.preventDefault();
@@ -43,12 +43,9 @@
/**
* Render the document in the webview.
*/
- function updateContent(/** @type {string} */ text) {
+ function updateContent(/** @type {string} */ text = '{}') {
let json;
try {
- if (!text) {
- text = '{}';
- }
json = JSON.parse(text);
} catch {
errorContainer.innerText = 'Error: Document is not valid json';
@@ -84,7 +81,7 @@
title.className = 'vuln-title';
element.appendChild(title);
- // DESCRIPTION - short text (alases)
+ // DESCRIPTION - short text (aliases)
const desc = document.createElement('p');
desc.innerHTML = Array.isArray(vuln.Aliases) && vuln.Aliases.length ? `${vuln.Details} (${vuln.Aliases.join(', ')})` : vuln.Details;
desc.className = 'vuln-desc';
@@ -103,7 +100,7 @@
/* TODO: Action for module version upgrade */
/* TODO: Explain module dependency - why am I depending on this vulnerable version? */
- // EXAMPLARS - call stacks (initially hidden)
+ // EXEMPLARS - call stacks (initially hidden)
const examples = document.createElement('details');
examples.innerHTML = `<summary>${vuln.CallStackSummaries?.length || 0}+ findings</summary>`;
```
```
The name of the file: media/vscode.css
Insertions: 7, Deletions: 2.
@@ -1,3 +1,8 @@
+/*---------------------------------------------------------
+ * Copyright 2022 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+
:root {
--container-paddding: 20px;
--input-padding-vertical: 6px;
@@ -15,8 +20,8 @@
background-color: var(--vscode-editor-background);
}
-body > *,
-form > * {
+body>*,
+form>* {
margin-block-start: var(--input-margin-vertical);
margin-block-end: var(--input-margin-vertical);
}
```
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/406300
TryBot-Result: kokoro <noreply...@google.com>
Run-TryBot: Hyang-Ah Hana Kim <hya...@gmail.com>
Reviewed-by: Jamal Carvalho <ja...@golang.org>
---
A media/reset.css
A media/vscode.css
A media/vulncheckView.css
A media/vulncheckView.js
M src/goVulncheck.ts
A test/gopls/vulncheck.test.ts
A test/testdata/vuln/test.vulncheck.json
7 files changed, 603 insertions(+), 0 deletions(-)
diff --git a/media/reset.css b/media/reset.css
new file mode 100644
index 0000000..002b994
--- /dev/null
+++ b/media/reset.css
@@ -0,0 +1,30 @@
+/*---------------------------------------------------------
+ * Copyright 2022 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+
index 0000000..b4f7c42
--- /dev/null
+++ b/media/vscode.css
@@ -0,0 +1,45 @@
+/*---------------------------------------------------------
+ * Copyright 2022 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+
index 0000000..96cc78f
--- /dev/null
+++ b/media/vulncheckView.css
@@ -0,0 +1,42 @@
+/*---------------------------------------------------------
+ * Copyright 2022 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+
index 0000000..6a2a4a0
--- /dev/null
+++ b/media/vulncheckView.js
@@ -0,0 +1,175 @@
+/*---------------------------------------------------------
+ * Copyright 2022 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+
+// Script for VulncheckResultViewProvider's webview.
+
+(function () {
+
+ // @ts-ignore
+ const vscode = acquireVsCodeApi();
+
+ const logContainer = /** @type {HTMLElement} */ (document.querySelector('.log'));
+ const vulnsContainer = /** @type {HTMLElement} */ (document.querySelector('.vulns'));
+
+ vulnsContainer.addEventListener('click', (event) => {
+ let node = event && event.target;
+ if (node?.tagName === 'A' && node.href) {+ function updateContent(/** @type {string} */ text = '{}') {
+ let json;
+ try {
+ // DESCRIPTION - short text (aliases)
+ const desc = document.createElement('p');
+ desc.innerHTML = Array.isArray(vuln.Aliases) && vuln.Aliases.length ? `${vuln.Details} (${vuln.Aliases.join(', ')})` : vuln.Details;
+ desc.className = 'vuln-desc';
+ element.appendChild(desc);
+
+ // DETAILS - dump of all details
+ const details = document.createElement('table');
+ details.className = 'vuln-details'
+ details.innerHTML = `
+ <tr><td>Package</td><td>${vuln.PkgPath}</td></tr>
+ <tr><td>Current Version</td><td>${moduleVersion(vuln.ModPath, vuln.CurrentVersion)}</td></tr>
+ <tr><td>Fixed Version</td><td>${moduleVersion(vuln.ModPath, vuln.FixedVersion)}</td></tr>
+ `;
+ element.appendChild(details);
+
+ /* TODO: Action for module version upgrade */
+ /* TODO: Explain module dependency - why am I depending on this vulnerable version? */
+
+ // EXEMPLARS - call stacks (initially hidden)index 10de439..76e4d62 100644index 0000000..5f696bc
--- /dev/null
+++ b/test/gopls/vulncheck.test.ts
@@ -0,0 +1,85 @@
+/*---------------------------------------------------------
+ * Copyright 2022 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+import assert from 'assert';
+import path = require('path');
+import vscode = require('vscode');
+import { extensionId } from '../../src/const';
+import goVulncheck = require('../../src/goVulncheck');
+
+suite('vulncheck result viewer tests', () => {To view, visit change 406300. To unsubscribe, or for help writing mail filters, visit settings.