From c6e9df25f7d701b8ba7cd6daccd7ee6aeab5fd42 Mon Sep 17 00:00:00 2001 From: Jason Luong Date: Thu, 26 Oct 2023 14:14:23 +0100 Subject: [PATCH 01/14] refactor: rename and move ossSuggestionWebviewProviderLanguageServer --- src/snyk/extension.ts | 4 +-- src/snyk/snykOss/{views => }/interfaces.ts | 4 +-- src/snyk/snykOss/ossServiceLanguageServer.ts | 2 +- .../ossDetailPanelProvider.ts} | 33 +++++++++---------- .../snykOss/ossServiceLanguageServer.test.ts | 4 +-- 5 files changed, 22 insertions(+), 25 deletions(-) rename src/snyk/snykOss/{views => }/interfaces.ts (65%) rename src/snyk/snykOss/{views/suggestion/ossSuggestionWebviewProviderLanguageServer.ts => providers/ossDetailPanelProvider.ts} (84%) diff --git a/src/snyk/extension.ts b/src/snyk/extension.ts index 60b551275..9d55d51b8 100644 --- a/src/snyk/extension.ts +++ b/src/snyk/extension.ts @@ -75,12 +75,12 @@ import IacIssueTreeProvider from './snykIac/views/iacIssueTreeProvider'; import { IacSuggestionWebviewProvider } from './snykIac/views/suggestion/iacSuggestionWebviewProvider'; import { EditorDecorator } from './snykOss/editor/editorDecorator'; import { OssServiceLanguageServer } from './snykOss/ossServiceLanguageServer'; +import { OssDetailPanelProvider } from './snykOss/providers/ossDetailPanelProvider'; import { OssService } from './snykOss/services/ossService'; import { OssVulnerabilityCountService } from './snykOss/services/vulnerabilityCount/ossVulnerabilityCountService'; import { ModuleVulnerabilityCountProvider } from './snykOss/services/vulnerabilityCount/vulnerabilityCountProvider'; import { OssVulnerabilityTreeProvider } from './snykOss/views/ossVulnerabilityTreeProvider'; import { OssSuggestionWebviewProvider } from './snykOss/views/suggestion/ossSuggestionWebviewProvider'; -import { OssSuggestionWebviewProviderLanguageServer } from './snykOss/views/suggestion/ossSuggestionWebviewProviderLanguageServer'; import { DailyScanJob } from './snykOss/watchers/dailyScanJob'; class SnykExtension extends SnykLib implements IExtension { @@ -225,7 +225,7 @@ class SnykExtension extends SnykLib implements IExtension { this.workspaceTrust, ); - const ossSuggestionProvider = new OssSuggestionWebviewProviderLanguageServer( + const ossSuggestionProvider = new OssDetailPanelProvider( vsCodeWindow, extensionContext, Logger, diff --git a/src/snyk/snykOss/views/interfaces.ts b/src/snyk/snykOss/interfaces.ts similarity index 65% rename from src/snyk/snykOss/views/interfaces.ts rename to src/snyk/snykOss/interfaces.ts index 21eb873a9..51755e959 100644 --- a/src/snyk/snykOss/views/interfaces.ts +++ b/src/snyk/snykOss/interfaces.ts @@ -1,5 +1,5 @@ -import { Issue, OssIssueData } from '../../common/languageServer/types'; -import { IWebViewProvider } from '../../common/views/webviewProvider'; +import { Issue, OssIssueData } from '../common/languageServer/types'; +import { IWebViewProvider } from '../common/views/webviewProvider'; export interface IOssSuggestionWebviewProvider extends IWebViewProvider> { openIssueId: string | undefined; diff --git a/src/snyk/snykOss/ossServiceLanguageServer.ts b/src/snyk/snykOss/ossServiceLanguageServer.ts index 286297250..eef3e7a95 100644 --- a/src/snyk/snykOss/ossServiceLanguageServer.ts +++ b/src/snyk/snykOss/ossServiceLanguageServer.ts @@ -11,7 +11,7 @@ import { ICodeActionAdapter, ICodeActionKindAdapter } from '../common/vscode/cod import { ExtensionContext } from '../common/vscode/extensionContext'; import { IVSCodeLanguages } from '../common/vscode/languages'; import { IVSCodeWorkspace } from '../common/vscode/workspace'; -import { IOssSuggestionWebviewProvider } from './views/interfaces'; +import { IOssSuggestionWebviewProvider } from './interfaces'; export class OssServiceLanguageServer extends ProductService { constructor( diff --git a/src/snyk/snykOss/views/suggestion/ossSuggestionWebviewProviderLanguageServer.ts b/src/snyk/snykOss/providers/ossDetailPanelProvider.ts similarity index 84% rename from src/snyk/snykOss/views/suggestion/ossSuggestionWebviewProviderLanguageServer.ts rename to src/snyk/snykOss/providers/ossDetailPanelProvider.ts index 415859bea..19b0c06b1 100644 --- a/src/snyk/snykOss/views/suggestion/ossSuggestionWebviewProviderLanguageServer.ts +++ b/src/snyk/snykOss/providers/ossDetailPanelProvider.ts @@ -1,22 +1,19 @@ import * as vscode from 'vscode'; -import { SNYK_OPEN_BROWSER_COMMAND } from '../../../common/constants/commands'; -import { SNYK_VIEW_SUGGESTION_IAC, SNYK_VIEW_SUGGESTION_OSS } from '../../../common/constants/views'; -import { ErrorHandler } from '../../../common/error/errorHandler'; -import { IacIssueData, Issue, OssIssueData } from '../../../common/languageServer/types'; -import { ILog } from '../../../common/logger/interfaces'; -import { getNonce } from '../../../common/views/nonce'; -import { WebviewPanelSerializer } from '../../../common/views/webviewPanelSerializer'; -import { IWebViewProvider, WebviewProvider } from '../../../common/views/webviewProvider'; -import { ExtensionContext } from '../../../common/vscode/extensionContext'; -import { IVSCodeLanguages } from '../../../common/vscode/languages'; -import { IVSCodeWindow } from '../../../common/vscode/window'; -import { IVSCodeWorkspace } from '../../../common/vscode/workspace'; -import { messages as errorMessages } from '../../messages/error'; -// import { getAbsoluteMarkerFilePath } from '../../utils/analysisUtils'; -// import { IssueUtils } from '../../utils/issueUtils'; -// import { ICodeSuggestionWebviewProvider } from '../interfaces'; - -export class OssSuggestionWebviewProviderLanguageServer +import { SNYK_OPEN_BROWSER_COMMAND } from '../../common/constants/commands'; +import { SNYK_VIEW_SUGGESTION_OSS } from '../../common/constants/views'; +import { ErrorHandler } from '../../common/error/errorHandler'; +import { Issue, OssIssueData } from '../../common/languageServer/types'; +import { ILog } from '../../common/logger/interfaces'; +import { getNonce } from '../../common/views/nonce'; +import { WebviewPanelSerializer } from '../../common/views/webviewPanelSerializer'; +import { IWebViewProvider, WebviewProvider } from '../../common/views/webviewProvider'; +import { ExtensionContext } from '../../common/vscode/extensionContext'; +import { IVSCodeLanguages } from '../../common/vscode/languages'; +import { IVSCodeWindow } from '../../common/vscode/window'; +import { IVSCodeWorkspace } from '../../common/vscode/workspace'; +import { messages as errorMessages } from '../messages/error'; + +export class OssDetailPanelProvider extends WebviewProvider> implements IWebViewProvider> { diff --git a/src/test/unit/snykOss/ossServiceLanguageServer.test.ts b/src/test/unit/snykOss/ossServiceLanguageServer.test.ts index 0b09e21b0..5634f242a 100644 --- a/src/test/unit/snykOss/ossServiceLanguageServer.test.ts +++ b/src/test/unit/snykOss/ossServiceLanguageServer.test.ts @@ -12,7 +12,7 @@ import { ExtensionContext } from '../../../snyk/common/vscode/extensionContext'; import { IVSCodeLanguages } from '../../../snyk/common/vscode/languages'; import { IVSCodeWorkspace } from '../../../snyk/common/vscode/workspace'; import { OssServiceLanguageServer } from '../../../snyk/snykOss/ossServiceLanguageServer'; -import { OssSuggestionWebviewProviderLanguageServer } from '../../../snyk/snykOss/views/suggestion/ossSuggestionWebviewProviderLanguageServer'; +import { OssDetailPanelProvider } from '../../../snyk/snykOss/providers/ossDetailPanelProvider'; import { LanguageServerMock } from '../mocks/languageServer.mock'; import { LoggerMock } from '../mocks/logger.mock'; @@ -32,7 +32,7 @@ suite('OSS Service', () => { service = new OssServiceLanguageServer( {} as ExtensionContext, {} as IConfiguration, - {} as OssSuggestionWebviewProviderLanguageServer, + {} as OssDetailPanelProvider, {} as ICodeActionAdapter, { getQuickFix: sinon.fake(), From 8e0e18a7e3262409a9e1a65b363b907f621105a1 Mon Sep 17 00:00:00 2001 From: Jason Luong Date: Thu, 26 Oct 2023 14:24:02 +0100 Subject: [PATCH 02/14] feat: add new OSS (LS) panel in Snyk UI --- package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.json b/package.json index 6cd2dd0ce..fe145a161 100644 --- a/package.json +++ b/package.json @@ -235,6 +235,11 @@ "name": "Snyk", "when": "!snyk:loggedIn || snyk:error || !snyk:workspaceFound" }, + { + "id": "snyk.views.analysis.oss.languageServer", + "name": "Open Source Security (LS)", + "when": "snyk:initialized && snyk:loggedIn && snyk:workspaceFound && !snyk:error" + }, { "id": "snyk.views.analysis.oss", "name": "Open Source Security", From 2d0818c9cf3b20cbab0bc7f6264806d42904aa7f Mon Sep 17 00:00:00 2001 From: Jason Luong Date: Thu, 26 Oct 2023 17:20:31 +0100 Subject: [PATCH 03/14] feat: configure new views and activation events for LS OSS treeview --- package.json | 1 + src/snyk/common/constants/views.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/package.json b/package.json index fe145a161..161df56a2 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "activationEvents": [ "onWebviewPanel:snyk.views.suggestion.code", "onWebviewPanel:snyk.views.suggestion.oss", + "onWebviewPanel:snyk.views.suggestion.oss.languageServer", "*" ], "main": "./out/extension.js", diff --git a/src/snyk/common/constants/views.ts b/src/snyk/common/constants/views.ts index 6616a1ef5..1b4317ba5 100644 --- a/src/snyk/common/constants/views.ts +++ b/src/snyk/common/constants/views.ts @@ -4,9 +4,11 @@ export const SNYK_VIEW_ANALYSIS_CODE_ENABLEMENT = 'snyk.views.analysis.code.enab export const SNYK_VIEW_ANALYSIS_CODE_SECURITY = 'snyk.views.analysis.code.security'; export const SNYK_VIEW_ANALYSIS_CODE_QUALITY = 'snyk.views.analysis.code.quality'; export const SNYK_VIEW_ANALYSIS_OSS = 'snyk.views.analysis.oss'; +export const SNYK_VIEW_ANALYSIS_OSS_LANGUAGE_SERVER = 'snyk.views.analysis.oss.languageServer'; export const SNYK_VIEW_SUPPORT = 'snyk.views.support'; export const SNYK_VIEW_SUGGESTION_CODE = 'snyk.views.suggestion.code'; export const SNYK_VIEW_SUGGESTION_OSS = 'snyk.views.suggestion.oss'; +export const SNYK_VIEW_SUGGESTION_OSS_LANGUAGE_SERVER = 'snyk.views.suggestion.oss.languageServer'; export const SNYK_VIEW_SUGGESTION_IAC = 'snyk.views.suggestion.iac'; export const SNYK_VIEW_ANALYSIS_IAC = 'snyk.views.analysis.configuration'; From c9c39b02f70fddf11bca9208a1f4ca2656d70a70 Mon Sep 17 00:00:00 2001 From: Jason Luong Date: Thu, 26 Oct 2023 17:22:01 +0100 Subject: [PATCH 04/14] refactor: OSS messages for LS implementation; OssIssueCommandArgLanguerServer type --- src/snyk/snykOss/interfaces.ts | 9 +++++--- src/snyk/snykOss/messages.ts | 40 ++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 src/snyk/snykOss/messages.ts diff --git a/src/snyk/snykOss/interfaces.ts b/src/snyk/snykOss/interfaces.ts index 51755e959..ec74dfd1c 100644 --- a/src/snyk/snykOss/interfaces.ts +++ b/src/snyk/snykOss/interfaces.ts @@ -1,3 +1,4 @@ +import * as vscode from 'vscode'; import { Issue, OssIssueData } from '../common/languageServer/types'; import { IWebViewProvider } from '../common/views/webviewProvider'; @@ -5,7 +6,9 @@ export interface IOssSuggestionWebviewProvider extends IWebViewProvider { + const vulnerabilityCountNumber = Number.parseInt(vulnerabilityCount, 10); + if (isNaN(vulnerabilityCountNumber)) { + return vulnerabilityCount; + } + return `${vulnerabilityCountNumber} ${vulnerabilityCountNumber > 1 ? 'vulnerabilities' : 'vulnerability'}`; + }, + diagnosticMessagePrefix: (module: ModuleVulnerabilityCount): string => { + return `Dependency ${module.name}${module.version ? `@${module.version}` : ''} has `; + }, + }, + treeView: { + cookingDependencies: 'Scanning...', + runTest: 'Run scan for Open Source security vulnerabilities.', + noVulnerabilitiesFound: ' ✅ Congrats! Snyk found no vulnerabilities.', + singleVulnerabilityFound: 'Snyk found 1 vulnerability', + vulnerability: 'vulnerability', + vulnerabilities: 'vulnerabilities', + multipleVulnerabilitiesFound: (issueCount: number): string => `Snyk found ${issueCount} vulnerabilities`, + }, + test: { + testFailed: 'Open Source Security test failed.', + testStarted: 'Open Source Security test started.', + viewResults: 'View results', + hide: "Don't show again", + testFailedForPath: (path: string): string => `Open Source Security test failed for "${path}".`, + testFinished: (projectName: string): string => `Open Source Security test finished for "${projectName}".`, + }, + errors: { + suggestionViewShowFailed: 'Failed to show Snyk OSS suggestion view', + } +} From ce2c6d036867b042a89dacf8ec304c830f3788eb Mon Sep 17 00:00:00 2001 From: Jason Luong Date: Thu, 26 Oct 2023 17:23:27 +0100 Subject: [PATCH 05/14] refactor: getIssueRange abstract method to allow undefined --- src/snyk/common/views/issueTreeProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/snyk/common/views/issueTreeProvider.ts b/src/snyk/common/views/issueTreeProvider.ts index 122f94966..b8c125418 100644 --- a/src/snyk/common/views/issueTreeProvider.ts +++ b/src/snyk/common/views/issueTreeProvider.ts @@ -41,7 +41,7 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid abstract getRunTestMessage(): string; abstract getIssueTitle(issue: Issue): string; - abstract getIssueRange(issue: Issue): Range; + abstract getIssueRange(issue?: Issue): Range | undefined; abstract getOpenIssueCommand(issue: Issue, folderPath: string, filePath: string): Command; getRootChildren(): TreeNode[] { From e1e0bd4a78ed53da2de5601502a7578b607d19ce Mon Sep 17 00:00:00 2001 From: Jason Luong Date: Thu, 26 Oct 2023 17:24:39 +0100 Subject: [PATCH 06/14] fix: ossDetailsPanel uses LS specific OSS views --- src/snyk/snykOss/providers/ossDetailPanelProvider.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/snyk/snykOss/providers/ossDetailPanelProvider.ts b/src/snyk/snykOss/providers/ossDetailPanelProvider.ts index 19b0c06b1..169a1941c 100644 --- a/src/snyk/snykOss/providers/ossDetailPanelProvider.ts +++ b/src/snyk/snykOss/providers/ossDetailPanelProvider.ts @@ -1,6 +1,6 @@ import * as vscode from 'vscode'; import { SNYK_OPEN_BROWSER_COMMAND } from '../../common/constants/commands'; -import { SNYK_VIEW_SUGGESTION_OSS } from '../../common/constants/views'; +import { SNYK_VIEW_SUGGESTION_OSS_LANGUAGE_SERVER } from '../../common/constants/views'; import { ErrorHandler } from '../../common/error/errorHandler'; import { Issue, OssIssueData } from '../../common/languageServer/types'; import { ILog } from '../../common/logger/interfaces'; @@ -33,7 +33,7 @@ export class OssDetailPanelProvider activate(): void { this.context.addDisposables( - this.window.registerWebviewPanelSerializer(SNYK_VIEW_SUGGESTION_OSS, new WebviewPanelSerializer(this)), + this.window.registerWebviewPanelSerializer(SNYK_VIEW_SUGGESTION_OSS_LANGUAGE_SERVER, new WebviewPanelSerializer(this)), ); } @@ -49,7 +49,7 @@ export class OssDetailPanelProvider this.panel.reveal(vscode.ViewColumn.Two, true); } else { this.panel = vscode.window.createWebviewPanel( - SNYK_VIEW_SUGGESTION_OSS, + SNYK_VIEW_SUGGESTION_OSS_LANGUAGE_SERVER, 'Snyk OSS Vulnerability', { viewColumn: vscode.ViewColumn.Two, From c3ff6800ac80f591020934b160d568fd7a83e5fc Mon Sep 17 00:00:00 2001 From: Jason Luong Date: Thu, 26 Oct 2023 17:25:03 +0100 Subject: [PATCH 07/14] feat: implement OSS vulnerability tree via LS class --- .../providers/ossVulnerabilityTreeProvider.ts | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts diff --git a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts new file mode 100644 index 000000000..6b23d4f8b --- /dev/null +++ b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts @@ -0,0 +1,80 @@ +import { Command } from 'vscode'; +import { OpenCommandIssueType, OpenIssueCommandArg } from '../../common/commands/types'; +import { IConfiguration } from '../../common/configuration/configuration'; +import { configuration } from '../../common/configuration/instance'; +import { SNYK_OPEN_ISSUE_COMMAND } from '../../common/constants/commands'; +import { SNYK_ANALYSIS_STATUS } from '../../common/constants/views'; +import { Issue, OssIssueData } from '../../common/languageServer/types'; +import { IContextService } from '../../common/services/contextService'; +import { IProductService } from '../../common/services/productService'; +import { IViewManagerService } from '../../common/services/viewManagerService'; +import { ProductIssueTreeProvider } from '../../common/views/issueTreeProvider'; +import { TreeNode } from '../../common/views/treeNode'; +import { IVSCodeLanguages } from '../../common/vscode/languages'; +import { OssIssueCommandArgLanguageServer } from '../interfaces'; +import { messages } from '../messages'; + +export default class OssIssueTreeProvider extends ProductIssueTreeProvider { + constructor( + protected viewManagerService: IViewManagerService, + protected contextService: IContextService, + protected ossService: IProductService, + protected configuration: IConfiguration, + protected languages: IVSCodeLanguages, + ) { + super(contextService, ossService, configuration, languages); + } + + getRootChildren(): TreeNode[] { + if (!configuration.getFeaturesConfiguration()?.ossEnabled) { + return [ + new TreeNode({ + text: SNYK_ANALYSIS_STATUS.OSS_DISABLED, + }), + ]; + } + + return super.getRootChildren(); + } + + onDidChangeTreeData = this.viewManagerService.refreshOssViewEmitter.event; + + shouldShowTree(): boolean { + return this.contextService.shouldShowOssAnalysis; + } + + getIssueDescriptionText(dir: string | undefined, issueCount: number): string | undefined { + return `${dir} - ${issueCount} ${issueCount === 1 ? 'issue' : 'issues'}`; + } + + getIssueFoundText(nIssues: number): string { + return `Snyk found ${!nIssues ? 'no issues! ✅' : `${nIssues} ${nIssues === 1 ? 'issue' : 'issues'}`}`; + } + + filterIssues(issues: Issue[]): Issue[] { + return issues; + } + + getRunTestMessage = () => messages.treeView.runTest; + + getIssueTitle = (issue: Issue) => issue.title; + + getIssueRange = () => undefined; + + getOpenIssueCommand(issue: Issue, folderPath: string, filePath: string): Command { + return { + command: SNYK_OPEN_ISSUE_COMMAND, + title: '', + arguments: [ + { + issueType: OpenCommandIssueType.OssVulnerability, + issue: { + id: issue.id, + folderPath, + filePath, + } as OssIssueCommandArgLanguageServer, + } as OpenIssueCommandArg, + ], + }; + } +} From 783cb32dfd9822266f9cb57bd42d7f15c1bd9353 Mon Sep 17 00:00:00 2001 From: Jason Luong Date: Thu, 26 Oct 2023 17:25:42 +0100 Subject: [PATCH 08/14] feat: configure extension.ts to show LS OSS vulnerability tree view --- src/snyk/extension.ts | 51 ++++++++++++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/src/snyk/extension.ts b/src/snyk/extension.ts index 9d55d51b8..7194e65c2 100644 --- a/src/snyk/extension.ts +++ b/src/snyk/extension.ts @@ -26,7 +26,7 @@ import { SNYK_SHOW_LS_OUTPUT_COMMAND, SNYK_SHOW_OUTPUT_COMMAND, SNYK_START_COMMAND, - SNYK_WORKSPACE_SCAN_COMMAND, + SNYK_WORKSPACE_SCAN_COMMAND } from './common/constants/commands'; import { MEMENTO_FIRST_INSTALL_DATE_KEY } from './common/constants/globalState'; import { @@ -36,8 +36,9 @@ import { SNYK_VIEW_ANALYSIS_CODE_SECURITY, SNYK_VIEW_ANALYSIS_IAC, SNYK_VIEW_ANALYSIS_OSS, + SNYK_VIEW_ANALYSIS_OSS_LANGUAGE_SERVER, SNYK_VIEW_SUPPORT, - SNYK_VIEW_WELCOME, + SNYK_VIEW_WELCOME } from './common/constants/views'; import { ErrorHandler } from './common/error/errorHandler'; import { ErrorReporter } from './common/error/errorReporter'; @@ -76,6 +77,7 @@ import { IacSuggestionWebviewProvider } from './snykIac/views/suggestion/iacSugg import { EditorDecorator } from './snykOss/editor/editorDecorator'; import { OssServiceLanguageServer } from './snykOss/ossServiceLanguageServer'; import { OssDetailPanelProvider } from './snykOss/providers/ossDetailPanelProvider'; +import OssIssueTreeProvider from './snykOss/providers/ossVulnerabilityTreeProvider'; import { OssService } from './snykOss/services/ossService'; import { OssVulnerabilityCountService } from './snykOss/services/vulnerabilityCount/ossVulnerabilityCountService'; import { ModuleVulnerabilityCountProvider } from './snykOss/services/vulnerabilityCount/vulnerabilityCountProvider'; @@ -288,19 +290,20 @@ class SnykExtension extends SnykLib implements IExtension { this.registerCommands(vscodeContext); const codeSecurityIssueProvider = new CodeSecurityIssueTreeProvider( - this.viewManagerService, - this.contextService, - this.snykCode, - configuration, - vsCodeLanguages, - ), - codeQualityIssueProvider = new CodeQualityIssueTreeProvider( - this.viewManagerService, - this.contextService, - this.snykCode, - configuration, - vsCodeLanguages, - ); + this.viewManagerService, + this.contextService, + this.snykCode, + configuration, + vsCodeLanguages, + ); + + const codeQualityIssueProvider = new CodeQualityIssueTreeProvider( + this.viewManagerService, + this.contextService, + this.snykCode, + configuration, + vsCodeLanguages, + ); const codeSecurityTree = vscode.window.createTreeView(SNYK_VIEW_ANALYSIS_CODE_SECURITY, { treeDataProvider: codeSecurityIssueProvider, @@ -345,6 +348,23 @@ class SnykExtension extends SnykLib implements IExtension { codeEnablementTree, ); + const ossIssueProvider = new OssIssueTreeProvider( + this.viewManagerService, + this.contextService, + this.ossServiceLanguageServer, + configuration, + vsCodeLanguages, + ); + + const ossSecurityTree = vscode.window.createTreeView(SNYK_VIEW_ANALYSIS_OSS_LANGUAGE_SERVER, { + treeDataProvider: ossIssueProvider, + }); + + vscodeContext.subscriptions.push( + vscode.window.registerTreeDataProvider(SNYK_VIEW_ANALYSIS_OSS_LANGUAGE_SERVER, ossIssueProvider), + ossSecurityTree, + ); + const iacIssueProvider = new IacIssueTreeProvider( this.viewManagerService, this.contextService, @@ -380,6 +400,7 @@ class SnykExtension extends SnykLib implements IExtension { this.ossService.activateSuggestionProvider(); this.ossService.activateManifestFileWatcher(this); this.iacService.activateWebviewProviders(); + this.ossServiceLanguageServer.activateWebviewProviders(); // noinspection ES6MissingAwait void this.notificationService.init(); From 2624dbe5b625543c2eed7533949c9616b16e6fd7 Mon Sep 17 00:00:00 2001 From: Jason Luong Date: Thu, 26 Oct 2023 17:34:57 +0100 Subject: [PATCH 09/14] fix: linting --- src/snyk/extension.ts | 4 ++-- src/snyk/snykOss/interfaces.ts | 8 ++++---- src/snyk/snykOss/messages.ts | 4 ++-- src/snyk/snykOss/providers/ossDetailPanelProvider.ts | 5 ++++- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/snyk/extension.ts b/src/snyk/extension.ts index 7194e65c2..71e42fe4f 100644 --- a/src/snyk/extension.ts +++ b/src/snyk/extension.ts @@ -26,7 +26,7 @@ import { SNYK_SHOW_LS_OUTPUT_COMMAND, SNYK_SHOW_OUTPUT_COMMAND, SNYK_START_COMMAND, - SNYK_WORKSPACE_SCAN_COMMAND + SNYK_WORKSPACE_SCAN_COMMAND, } from './common/constants/commands'; import { MEMENTO_FIRST_INSTALL_DATE_KEY } from './common/constants/globalState'; import { @@ -38,7 +38,7 @@ import { SNYK_VIEW_ANALYSIS_OSS, SNYK_VIEW_ANALYSIS_OSS_LANGUAGE_SERVER, SNYK_VIEW_SUPPORT, - SNYK_VIEW_WELCOME + SNYK_VIEW_WELCOME, } from './common/constants/views'; import { ErrorHandler } from './common/error/errorHandler'; import { ErrorReporter } from './common/error/errorReporter'; diff --git a/src/snyk/snykOss/interfaces.ts b/src/snyk/snykOss/interfaces.ts index ec74dfd1c..999cbab0c 100644 --- a/src/snyk/snykOss/interfaces.ts +++ b/src/snyk/snykOss/interfaces.ts @@ -7,8 +7,8 @@ export interface IOssSuggestionWebviewProvider extends IWebViewProvider Date: Fri, 27 Oct 2023 13:03:32 +0100 Subject: [PATCH 10/14] refactor: oss interfaces & messages --- src/snyk/snykOss/interfaces.ts | 8 ++---- src/snyk/snykOss/messages.ts | 50 ++++++++++++++++++++-------------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/src/snyk/snykOss/interfaces.ts b/src/snyk/snykOss/interfaces.ts index 999cbab0c..c206b56a1 100644 --- a/src/snyk/snykOss/interfaces.ts +++ b/src/snyk/snykOss/interfaces.ts @@ -6,9 +6,7 @@ export interface IOssSuggestionWebviewProvider extends IWebViewProvider & { + matchingIdVulnerabilities: Issue[]; + overviewHtml: string; }; diff --git a/src/snyk/snykOss/messages.ts b/src/snyk/snykOss/messages.ts index b096011ef..1cbbec292 100644 --- a/src/snyk/snykOss/messages.ts +++ b/src/snyk/snykOss/messages.ts @@ -1,6 +1,36 @@ import { ModuleVulnerabilityCount } from './services/vulnerabilityCount/importedModule'; export const messages = { + analysis: { + scanFailed: 'Scan failed', + noWorkspaceTrust: 'No workspace folder was granted trust', + clickToProblem: 'Click here to see the problem.', + scanRunning: 'Scanning...', + allSeverityFiltersDisabled: 'Please enable severity filters to see the results.', + duration: (time: string, day: string): string => `Analysis finished at ${time}, ${day}`, + noWorkspaceTrustDescription: + 'None of workspace folders were trusted. If you trust the workspace, you can add it to the list of trusted folders in the extension settings, or when prompted by the extension next time.', + }, + errors: { + suggestionViewShowFailed: 'Failed to show Snyk OSS suggestion view', + }, + test: { + testFailed: 'Open Source Security test failed.', + testStarted: 'Open Source Security test started.', + viewResults: 'View results', + hide: "Don't show again", + testFailedForPath: (path: string): string => `Open Source Security test failed for "${path}".`, + testFinished: (projectName: string): string => `Open Source Security test finished for "${projectName}".`, + }, + treeView: { + cookingDependencies: 'Scanning...', + runTest: 'Run scan for Open Source security vulnerabilities.', + noVulnerabilitiesFound: ' ✅ Congrats! Snyk found no vulnerabilities.', + singleVulnerabilityFound: 'Snyk found 1 vulnerability', + vulnerability: 'vulnerability', + vulnerabilities: 'vulnerabilities', + multipleVulnerabilitiesFound: (issueCount: number): string => `Snyk found ${issueCount} vulnerabilities`, + }, vulnerabilityCount: { fetchingVulnerabilities: 'Fetching vulnerabilities...', vulnerability: 'vulnerability', @@ -17,24 +47,4 @@ export const messages = { return `Dependency ${module.name}${module.version ? `@${module.version}` : ''} has `; }, }, - treeView: { - cookingDependencies: 'Scanning...', - runTest: 'Run scan for Open Source security vulnerabilities.', - noVulnerabilitiesFound: ' ✅ Congrats! Snyk found no vulnerabilities.', - singleVulnerabilityFound: 'Snyk found 1 vulnerability', - vulnerability: 'vulnerability', - vulnerabilities: 'vulnerabilities', - multipleVulnerabilitiesFound: (issueCount: number): string => `Snyk found ${issueCount} vulnerabilities`, - }, - test: { - testFailed: 'Open Source Security test failed.', - testStarted: 'Open Source Security test started.', - viewResults: 'View results', - hide: "Don't show again", - testFailedForPath: (path: string): string => `Open Source Security test failed for "${path}".`, - testFinished: (projectName: string): string => `Open Source Security test finished for "${projectName}".`, - }, - errors: { - suggestionViewShowFailed: 'Failed to show Snyk OSS suggestion view', - }, }; From e2f049d0ebaecb721bd10b0886ca62089534f7fa Mon Sep 17 00:00:00 2001 From: Jason Luong Date: Fri, 27 Oct 2023 13:04:19 +0100 Subject: [PATCH 11/14] feat: update issue types to support LS OSS issue command args --- src/snyk/common/commands/types.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/snyk/common/commands/types.ts b/src/snyk/common/commands/types.ts index 5c61f0596..e2638c20f 100644 --- a/src/snyk/common/commands/types.ts +++ b/src/snyk/common/commands/types.ts @@ -1,6 +1,7 @@ import { completeFileSuggestionType } from '../../snykCode/interfaces'; import { CodeIssueCommandArg } from '../../snykCode/views/interfaces'; import { IacIssueCommandArg } from '../../snykIac/views/interfaces'; +import { OssIssueCommandArgLanguageServer } from '../../snykOss/interfaces'; import { OssIssueCommandArg } from '../../snykOss/views/ossVulnerabilityTreeProvider'; import { CodeIssueData, Issue } from '../languageServer/types'; @@ -11,19 +12,19 @@ export enum OpenCommandIssueType { } export type OpenIssueCommandArg = { - issue: CodeIssueCommandArg | OssIssueCommandArg | IacIssueCommandArg; + issue: CodeIssueCommandArg | OssIssueCommandArg | IacIssueCommandArg | OssIssueCommandArgLanguageServer; issueType: OpenCommandIssueType; }; export const isCodeIssue = ( - _issue: completeFileSuggestionType | Issue | OssIssueCommandArg, + _issue: completeFileSuggestionType | Issue | OssIssueCommandArg | OssIssueCommandArgLanguageServer, issueType: OpenCommandIssueType, ): _issue is Issue => { return issueType === OpenCommandIssueType.CodeIssue; }; export const isOssIssue = ( - _issue: completeFileSuggestionType | Issue | OssIssueCommandArg, + _issue: completeFileSuggestionType | Issue | OssIssueCommandArg | OssIssueCommandArgLanguageServer, issueType: OpenCommandIssueType, ): _issue is OssIssueCommandArg => { return issueType === OpenCommandIssueType.OssVulnerability; From 8138afe60c60b1e5e63139d89657a8348c617b28 Mon Sep 17 00:00:00 2001 From: Jason Luong Date: Fri, 27 Oct 2023 13:04:55 +0100 Subject: [PATCH 12/14] refactor: getOpenIssueCommand abstract method to support LS OSS implementations --- src/snyk/common/views/issueTreeProvider.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/snyk/common/views/issueTreeProvider.ts b/src/snyk/common/views/issueTreeProvider.ts index b8c125418..e4d8a00dd 100644 --- a/src/snyk/common/views/issueTreeProvider.ts +++ b/src/snyk/common/views/issueTreeProvider.ts @@ -42,7 +42,7 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid abstract getIssueTitle(issue: Issue): string; abstract getIssueRange(issue?: Issue): Range | undefined; - abstract getOpenIssueCommand(issue: Issue, folderPath: string, filePath: string): Command; + abstract getOpenIssueCommand(issue: Issue, folderPath: string, filePath: string, filteredIssues?: Issue[]): Command; getRootChildren(): TreeNode[] { const nodes: TreeNode[] = []; @@ -229,7 +229,7 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid return IssueSeverity.Low; } - private initSeverityCounts(): ISeverityCounts { + protected initSeverityCounts(): ISeverityCounts { return { [IssueSeverity.Critical]: 0, [IssueSeverity.High]: 0, From 1ceb67f58e7503353bfd0bb40223dd5243d4d88f Mon Sep 17 00:00:00 2001 From: Jason Luong Date: Fri, 27 Oct 2023 13:05:15 +0100 Subject: [PATCH 13/14] feat: implement initial LS OSS treeview --- .../providers/ossVulnerabilityTreeProvider.ts | 154 ++++++++++++++++-- 1 file changed, 143 insertions(+), 11 deletions(-) diff --git a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts index 6b23d4f8b..f4e111a59 100644 --- a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts +++ b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts @@ -1,10 +1,12 @@ -import { Command } from 'vscode'; +import _ from 'lodash'; +import marked from 'marked'; +import { Command, Uri } from 'vscode'; import { OpenCommandIssueType, OpenIssueCommandArg } from '../../common/commands/types'; import { IConfiguration } from '../../common/configuration/configuration'; import { configuration } from '../../common/configuration/instance'; import { SNYK_OPEN_ISSUE_COMMAND } from '../../common/constants/commands'; import { SNYK_ANALYSIS_STATUS } from '../../common/constants/views'; -import { Issue, OssIssueData } from '../../common/languageServer/types'; +import { Issue, IssueSeverity, OssIssueData } from '../../common/languageServer/types'; import { IContextService } from '../../common/services/contextService'; import { IProductService } from '../../common/services/productService'; import { IViewManagerService } from '../../common/services/viewManagerService'; @@ -37,6 +39,109 @@ export default class OssIssueTreeProvider extends ProductIssueTreeProvider v.filePath); + + for (const file in fileVulns) { + const fileIssues = fileVulns[file]; + const uri = Uri.file(file); + const filePath = uri.path.split('/'); + const filename = filePath.pop() || uri.path; + const dir = filePath.pop(); + + const fileSeverityCounts = this.initSeverityCounts(); + + const uniqueIssues = fileIssues.filter((issue, index, self) => index === self.findIndex(t => t.id === issue.id)); + + const filteredIssues = this.filterIssues(uniqueIssues); + + const vulnerabilityNodes: TreeNode[] = filteredIssues.map((issue: Issue) => { + fileSeverityCounts[issue.severity] += 1; + totalVulnCount++; + folderVulnCount++; + + return new TreeNode({ + text: `${issue.additionalData.packageName}@${issue.additionalData.version} - ${issue.title}`, + icon: ProductIssueTreeProvider.getSeverityIcon(issue.severity), + internal: { + severity: ProductIssueTreeProvider.getSeverityComparatorIndex(issue.severity), + }, + command: this.getOpenIssueCommand(issue, '', '', filteredIssues), + }); + }); + + if (vulnerabilityNodes.length === 0) { + continue; + } + + vulnerabilityNodes.sort(this.compareNodes); + + const fileSeverity = ProductIssueTreeProvider.getHighestSeverity(fileSeverityCounts); + folderSeverityCounts[fileSeverity] += 1; + + // append file node + const fileNode = new TreeNode({ + text: filename, + description: this.getIssueDescriptionText(dir, vulnerabilityNodes.length), + icon: ProductIssueTreeProvider.getSeverityIcon(fileSeverity), + children: vulnerabilityNodes, + internal: { + nIssues: vulnerabilityNodes.length, + severity: ProductIssueTreeProvider.getSeverityComparatorIndex(fileSeverity), + }, + }); + fileNodes.push(fileNode); + } + + fileNodes.sort(this.compareNodes); + + const folderSeverity = ProductIssueTreeProvider.getHighestSeverity(folderSeverityCounts); + + if (folderVulnCount == 0) { + continue; + } + + // flatten results if single workspace folder + if (this.productService.result.size == 1) { + nodes.push(...fileNodes); + } else { + const folderNode = new TreeNode({ + text: folderName, + description: this.getIssueDescriptionText(folderName, folderVulnCount), + icon: ProductIssueTreeProvider.getSeverityIcon(folderSeverity), + children: fileNodes, + internal: { + nIssues: folderVulnCount, + severity: ProductIssueTreeProvider.getSeverityComparatorIndex(folderSeverity), + }, + }); + nodes.push(folderNode); + } + } + + return [nodes, totalVulnCount]; + } + onDidChangeTreeData = this.viewManagerService.refreshOssViewEmitter.event; shouldShowTree(): boolean { @@ -44,15 +149,28 @@ export default class OssIssueTreeProvider extends ProductIssueTreeProvider[]): Issue[] { - return issues; + return issues.filter(vuln => { + switch (vuln.severity.toLowerCase()) { + case IssueSeverity.Critical: + return this.configuration.severityFilter.critical; + case IssueSeverity.High: + return this.configuration.severityFilter.high; + case IssueSeverity.Medium: + return this.configuration.severityFilter.medium; + case IssueSeverity.Low: + return this.configuration.severityFilter.low; + default: + return true; + } + }); } getRunTestMessage = () => messages.treeView.runTest; @@ -61,20 +179,34 @@ export default class OssIssueTreeProvider extends ProductIssueTreeProvider undefined; - getOpenIssueCommand(issue: Issue, folderPath: string, filePath: string): Command { + getOpenIssueCommand(issue: Issue, _folderPath: string, _filePath: string, filteredIssues: Issue[]): Command { return { command: SNYK_OPEN_ISSUE_COMMAND, title: '', arguments: [ { issueType: OpenCommandIssueType.OssVulnerability, - issue: { - id: issue.id, - folderPath, - filePath, - } as OssIssueCommandArgLanguageServer, + issue: this.getOssIssueCommandArg(issue, filteredIssues), } as OpenIssueCommandArg, ], }; } + + getOssIssueCommandArg(vuln: Issue, filteredVulns: Issue[]): OssIssueCommandArgLanguageServer { + const matchingIdVulnerabilities = filteredVulns.filter(v => v.id === vuln.id); + let overviewHtml: string; + + try { + overviewHtml = marked.parse(vuln.additionalData.description); + console.log('****', 'overviewHtml ****\n', overviewHtml, '\n'); + } catch (error) { + console.log('****', 'overviewHtml error ****\n', error, '\n'); + } + + return { + ...vuln, + matchingIdVulnerabilities, + overviewHtml: vuln.additionalData.description, + }; + }; } From 0849da4703859b1de59ae849de61413c4838e869 Mon Sep 17 00:00:00 2001 From: Jason Luong Date: Fri, 27 Oct 2023 13:35:01 +0100 Subject: [PATCH 14/14] fix: linting --- src/snyk/common/views/issueTreeProvider.ts | 7 +++- .../providers/ossVulnerabilityTreeProvider.ts | 32 +++++++++++++------ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/snyk/common/views/issueTreeProvider.ts b/src/snyk/common/views/issueTreeProvider.ts index e4d8a00dd..b55fe4b01 100644 --- a/src/snyk/common/views/issueTreeProvider.ts +++ b/src/snyk/common/views/issueTreeProvider.ts @@ -42,7 +42,12 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid abstract getIssueTitle(issue: Issue): string; abstract getIssueRange(issue?: Issue): Range | undefined; - abstract getOpenIssueCommand(issue: Issue, folderPath: string, filePath: string, filteredIssues?: Issue[]): Command; + abstract getOpenIssueCommand( + issue: Issue, + folderPath: string, + filePath: string, + filteredIssues?: Issue[], + ): Command; getRootChildren(): TreeNode[] { const nodes: TreeNode[] = []; diff --git a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts index f4e111a59..d64997b34 100644 --- a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts +++ b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts @@ -1,5 +1,5 @@ import _ from 'lodash'; -import marked from 'marked'; +import * as marked from 'marked'; import { Command, Uri } from 'vscode'; import { OpenCommandIssueType, OpenIssueCommandArg } from '../../common/commands/types'; import { IConfiguration } from '../../common/configuration/configuration'; @@ -71,7 +71,9 @@ export default class OssIssueTreeProvider extends ProductIssueTreeProvider index === self.findIndex(t => t.id === issue.id)); + const uniqueIssues = fileIssues.filter( + (issue, index, self) => index === self.findIndex(t => t.id === issue.id), + ); const filteredIssues = this.filterIssues(uniqueIssues); @@ -153,7 +155,9 @@ export default class OssIssueTreeProvider extends ProductIssueTreeProvider[]): Issue[] { @@ -179,7 +183,12 @@ export default class OssIssueTreeProvider extends ProductIssueTreeProvider undefined; - getOpenIssueCommand(issue: Issue, _folderPath: string, _filePath: string, filteredIssues: Issue[]): Command { + getOpenIssueCommand( + issue: Issue, + _folderPath: string, + _filePath: string, + filteredIssues: Issue[], + ): Command { return { command: SNYK_OPEN_ISSUE_COMMAND, title: '', @@ -192,21 +201,24 @@ export default class OssIssueTreeProvider extends ProductIssueTreeProvider, filteredVulns: Issue[]): OssIssueCommandArgLanguageServer { + getOssIssueCommandArg( + vuln: Issue, + filteredVulns: Issue[], + ): OssIssueCommandArgLanguageServer { const matchingIdVulnerabilities = filteredVulns.filter(v => v.id === vuln.id); - let overviewHtml: string; + let overviewHtml = ''; try { + // TODO: marked.parse does not sanitize the HTML. See: https://marked.js.org/#usage overviewHtml = marked.parse(vuln.additionalData.description); - console.log('****', 'overviewHtml ****\n', overviewHtml, '\n'); } catch (error) { - console.log('****', 'overviewHtml error ****\n', error, '\n'); + overviewHtml = '

There was a problem rendering the vulnerability overview

'; } return { ...vuln, matchingIdVulnerabilities, - overviewHtml: vuln.additionalData.description, + overviewHtml, }; - }; + } }