From 0d6b3c11493489467fa2c40c5d2caa47f56be083 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Scha=CC=88fer?= <101886095+PeterSchafer@users.noreply.github.com> Date: Tue, 24 Oct 2023 12:14:24 +0200 Subject: [PATCH 01/16] chore: run CI on PRs into any branch --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5fc5678d7..e9e98adf1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,7 +2,7 @@ name: CI on: pull_request: branches: - - main + - '**' workflow_call: secrets: ITERATIVELY_KEY: From f0e59c29b67e0f8487f9a67d7e88f429597ea7b9 Mon Sep 17 00:00:00 2001 From: JSON Date: Thu, 26 Oct 2023 12:09:26 +0100 Subject: [PATCH 02/16] feat: create new ossService (#385) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: adds new LS based OssService class * chore: read OSS settings for LS initialisation options * chore: add unit tests for new OssService --------- Co-authored-by: Peter Schäfer <101886095+PeterSchafer@users.noreply.github.com> --- src/snyk/base/modules/baseSnykModule.ts | 2 + src/snyk/common/languageServer/settings.ts | 30 +-- src/snyk/extension.ts | 27 ++- src/snyk/snykOss/ossServiceLanguageServer.ts | 61 ++++++ src/snyk/snykOss/views/interfaces.ts | 11 + ...SuggestionWebviewProviderLanguageServer.ts | 202 ++++++++++++++++++ .../snykOss/ossServiceLanguageServer.test.ts | 81 +++++++ 7 files changed, 398 insertions(+), 16 deletions(-) create mode 100644 src/snyk/snykOss/ossServiceLanguageServer.ts create mode 100644 src/snyk/snykOss/views/interfaces.ts create mode 100644 src/snyk/snykOss/views/suggestion/ossSuggestionWebviewProviderLanguageServer.ts create mode 100644 src/test/unit/snykOss/ossServiceLanguageServer.test.ts diff --git a/src/snyk/base/modules/baseSnykModule.ts b/src/snyk/base/modules/baseSnykModule.ts index 5a3bee102..4dc05fdbd 100644 --- a/src/snyk/base/modules/baseSnykModule.ts +++ b/src/snyk/base/modules/baseSnykModule.ts @@ -23,6 +23,7 @@ import { IMarkdownStringAdapter, MarkdownStringAdapter } from '../../common/vsco import { IWatcher } from '../../common/watchers/interfaces'; import { ICodeSettings } from '../../snykCode/codeSettings'; import SnykEditorsWatcher from '../../snykCode/watchers/editorsWatcher'; +import { OssServiceLanguageServer } from '../../snykOss/ossServiceLanguageServer'; import { OssService } from '../../snykOss/services/ossService'; import { OssVulnerabilityCountService } from '../../snykOss/services/vulnerabilityCount/ossVulnerabilityCountService'; import { IAuthenticationService } from '../services/authenticationService'; @@ -45,6 +46,7 @@ export default abstract class BaseSnykModule implements IBaseSnykModule { protected authService: IAuthenticationService; protected downloadService: DownloadService; protected ossService?: OssService; + protected ossServiceLanguageServer?: OssServiceLanguageServer; protected advisorService?: AdvisorProvider; protected commandController: CommandController; protected scanModeService: ScanModeService; diff --git a/src/snyk/common/languageServer/settings.ts b/src/snyk/common/languageServer/settings.ts index 7fbe9024b..1b0b0b035 100644 --- a/src/snyk/common/languageServer/settings.ts +++ b/src/snyk/common/languageServer/settings.ts @@ -57,35 +57,35 @@ export class LanguageServerSettings { static async fromConfiguration(configuration: IConfiguration, user: User): Promise { const featuresConfiguration = configuration.getFeaturesConfiguration(); - const iacEnabled = defaultToTrue(featuresConfiguration.iacEnabled); - const codeSecurityEnabled = defaultToTrue(featuresConfiguration.codeSecurityEnabled); - const codeQualityEnabled = defaultToTrue(featuresConfiguration.codeQualityEnabled); + const ossEnabled = _.isUndefined(featuresConfiguration.ossEnabled) ? true : featuresConfiguration.ossEnabled; - return { - activateSnykCodeSecurity: codeSecurityEnabled, - activateSnykCodeQuality: codeQualityEnabled, - activateSnykOpenSource: 'false', - activateSnykIac: iacEnabled, + const iacEnabled = _.isUndefined(featuresConfiguration.iacEnabled) ? true : featuresConfiguration.iacEnabled; + const codeSecurityEnabled = _.isUndefined(featuresConfiguration.codeSecurityEnabled) + ? true + : featuresConfiguration.codeSecurityEnabled; + const codeQualityEnabled = _.isUndefined(featuresConfiguration.codeQualityEnabled) + ? true + : featuresConfiguration.codeQualityEnabled; + return { + activateSnykCodeSecurity: `${codeSecurityEnabled}`, + activateSnykCodeQuality: `${codeQualityEnabled}`, + activateSnykOpenSource: `${ossEnabled}`, + activateSnykIac: `${iacEnabled}`, + enableTelemetry: `${configuration.shouldReportEvents}`, + sendErrorReports: `${configuration.shouldReportErrors}`, cliPath: configuration.getCliPath(), endpoint: configuration.snykOssApiEndpoint, organization: configuration.organization, - token: await configuration.getToken(), automaticAuthentication: 'false', additionalParams: configuration.getAdditionalCliParameters(), manageBinariesAutomatically: `${configuration.isAutomaticDependencyManagementEnabled()}`, - - sendErrorReports: `${configuration.shouldReportErrors}`, - enableTelemetry: `${configuration.shouldReportEvents}`, - filterSeverity: configuration.severityFilter, scanningMode: configuration.scanningMode, insecure: `${configuration.getInsecure()}`, - enableTrustedFoldersFeature: 'true', trustedFolders: configuration.getTrustedFolders(), - integrationName: CLI_INTEGRATION_NAME, integrationVersion: await Configuration.getVersion(), deviceId: user.anonymousId, diff --git a/src/snyk/extension.ts b/src/snyk/extension.ts index a3688f750..60b551275 100644 --- a/src/snyk/extension.ts +++ b/src/snyk/extension.ts @@ -21,8 +21,8 @@ import { SNYK_OPEN_BROWSER_COMMAND, SNYK_OPEN_ISSUE_COMMAND, SNYK_OPEN_LOCAL_COMMAND, - SNYK_SET_TOKEN_COMMAND, SNYK_SETTINGS_COMMAND, + SNYK_SET_TOKEN_COMMAND, SNYK_SHOW_LS_OUTPUT_COMMAND, SNYK_SHOW_OUTPUT_COMMAND, SNYK_START_COMMAND, @@ -74,11 +74,13 @@ import { IacService } from './snykIac/iacService'; 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 { 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 { @@ -223,6 +225,29 @@ class SnykExtension extends SnykLib implements IExtension { this.workspaceTrust, ); + const ossSuggestionProvider = new OssSuggestionWebviewProviderLanguageServer( + vsCodeWindow, + extensionContext, + Logger, + vsCodeLanguages, + vsCodeWorkspace, + ); + + this.ossServiceLanguageServer = new OssServiceLanguageServer( + extensionContext, + configuration, + ossSuggestionProvider, + new CodeActionAdapter(), + this.codeActionKindAdapter, + this.viewManagerService, + vsCodeWorkspace, + this.workspaceTrust, + this.languageServer, + vsCodeLanguages, + Logger, + this.analytics, + ); + const iacSuggestionProvider = new IacSuggestionWebviewProvider( vsCodeWindow, extensionContext, diff --git a/src/snyk/snykOss/ossServiceLanguageServer.ts b/src/snyk/snykOss/ossServiceLanguageServer.ts new file mode 100644 index 000000000..286297250 --- /dev/null +++ b/src/snyk/snykOss/ossServiceLanguageServer.ts @@ -0,0 +1,61 @@ +import { Subscription } from 'rxjs'; +import { IAnalytics } from '../common/analytics/itly'; +import { IConfiguration } from '../common/configuration/configuration'; +import { IWorkspaceTrust } from '../common/configuration/trustedFolders'; +import { ILanguageServer } from '../common/languageServer/languageServer'; +import { OssIssueData, Scan, ScanProduct } from '../common/languageServer/types'; +import { ILog } from '../common/logger/interfaces'; +import { ProductService } from '../common/services/productService'; +import { IViewManagerService } from '../common/services/viewManagerService'; +import { ICodeActionAdapter, ICodeActionKindAdapter } from '../common/vscode/codeAction'; +import { ExtensionContext } from '../common/vscode/extensionContext'; +import { IVSCodeLanguages } from '../common/vscode/languages'; +import { IVSCodeWorkspace } from '../common/vscode/workspace'; +import { IOssSuggestionWebviewProvider } from './views/interfaces'; + +export class OssServiceLanguageServer extends ProductService { + constructor( + extensionContext: ExtensionContext, + config: IConfiguration, + suggestionProvider: IOssSuggestionWebviewProvider, + readonly codeActionAdapter: ICodeActionAdapter, + readonly codeActionKindAdapter: ICodeActionKindAdapter, + viewManagerService: IViewManagerService, + workspace: IVSCodeWorkspace, + workspaceTrust: IWorkspaceTrust, + languageServer: ILanguageServer, + languages: IVSCodeLanguages, + logger: ILog, + readonly analytics: IAnalytics, + ) { + super( + extensionContext, + config, + suggestionProvider, + viewManagerService, + workspace, + workspaceTrust, + languageServer, + languages, + logger, + ); + + // this.registerCodeActionsProvider( + // new OssCodeActionsProvider(this.result, codeActionAdapter, codeActionKindAdapter, languages, analytics), + // ); + } + + subscribeToLsScanMessages(): Subscription { + return this.languageServer.scan$.subscribe((scan: Scan) => { + if (scan.product !== ScanProduct.OpenSource) { + return; + } + + super.handleLsScanMessage(scan); + }); + } + + refreshTreeView() { + this.viewManagerService.refreshOssView(); + } +} diff --git a/src/snyk/snykOss/views/interfaces.ts b/src/snyk/snykOss/views/interfaces.ts new file mode 100644 index 000000000..21eb873a9 --- /dev/null +++ b/src/snyk/snykOss/views/interfaces.ts @@ -0,0 +1,11 @@ +import { Issue, OssIssueData } from '../../common/languageServer/types'; +import { IWebViewProvider } from '../../common/views/webviewProvider'; + +export interface IOssSuggestionWebviewProvider extends IWebViewProvider> { + openIssueId: string | undefined; +} + +export type OssIssueCommandArgLanguageServer = OssIssueData & { + matchingIdVulnerabilities: OssIssueData[]; + overviewHtml: string; +}; diff --git a/src/snyk/snykOss/views/suggestion/ossSuggestionWebviewProviderLanguageServer.ts b/src/snyk/snykOss/views/suggestion/ossSuggestionWebviewProviderLanguageServer.ts new file mode 100644 index 000000000..415859bea --- /dev/null +++ b/src/snyk/snykOss/views/suggestion/ossSuggestionWebviewProviderLanguageServer.ts @@ -0,0 +1,202 @@ +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 + extends WebviewProvider> + implements IWebViewProvider> +{ + // For consistency reasons, the single source of truth for the current suggestion is the + // panel state. The following field is only used in + private issue: Issue | undefined; + + constructor( + private readonly window: IVSCodeWindow, + protected readonly context: ExtensionContext, + protected readonly logger: ILog, + private readonly languages: IVSCodeLanguages, + private readonly workspace: IVSCodeWorkspace, + ) { + super(context, logger); + } + + activate(): void { + this.context.addDisposables( + this.window.registerWebviewPanelSerializer(SNYK_VIEW_SUGGESTION_OSS, new WebviewPanelSerializer(this)), + ); + } + + get openIssueId(): string | undefined { + return this.issue?.id; + } + + async showPanel(issue: Issue): Promise { + try { + await this.focusSecondEditorGroup(); + + if (this.panel) { + this.panel.reveal(vscode.ViewColumn.Two, true); + } else { + this.panel = vscode.window.createWebviewPanel( + SNYK_VIEW_SUGGESTION_OSS, + 'Snyk OSS Vulnerability', + { + viewColumn: vscode.ViewColumn.Two, + preserveFocus: true, + }, + this.getWebviewOptions(), + ); + this.registerListeners(); + } + + this.panel.webview.html = this.getHtmlForWebview(this.panel.webview); + this.panel.iconPath = vscode.Uri.joinPath( + vscode.Uri.file(this.context.extensionPath), + 'media', + 'images', + 'snyk-iac.svg', // TODO: USE OSS ICON + ); + + await this.panel.webview.postMessage({ type: 'set', args: issue }); + + this.issue = issue; + } catch (e) { + ErrorHandler.handle(e, this.logger, errorMessages.suggestionViewShowFailed); + } + } + + protected registerListeners(): void { + if (!this.panel) return; + + this.panel.onDidDispose(() => this.onPanelDispose(), null, this.disposables); + this.panel.onDidChangeViewState(() => this.checkVisibility(), undefined, this.disposables); + // Handle messages from the webview + this.panel.webview.onDidReceiveMessage(msg => this.handleMessage(msg), undefined, this.disposables); + } + + disposePanel(): void { + super.disposePanel(); + } + + protected onPanelDispose(): void { + super.onPanelDispose(); + } + + private async handleMessage(message: any) { + try { + const { type, value } = message; + switch (type) { + case 'openBrowser': { + await vscode.commands.executeCommand(SNYK_OPEN_BROWSER_COMMAND, value); + break; + } + default: { + throw new Error('Unknown message type'); + } + } + } catch (e) { + ErrorHandler.handle(e, this.logger, errorMessages.suggestionViewShowFailed); + } + } + + protected getHtmlForWebview(webview: vscode.Webview): string { + const images: Record = [ + ['dark-critical-severity', 'svg'], + ['dark-high-severity', 'svg'], + ['dark-medium-severity', 'svg'], + ['dark-low-severity', 'svg'], + ].reduce((accumulator: Record, [name, ext]) => { + const uri = this.getWebViewUri('media', 'images', `${name}.${ext}`); + if (!uri) throw new Error('Image missing.'); + accumulator[name] = uri.toString(); + return accumulator; + }, {}); + + const scriptUri = this.getWebViewUri( + 'out', + 'snyk', + 'snykIac', + 'views', + 'suggestion', + 'iacSuggestionWebviewScript.js', + ); + const styleUri = this.getWebViewUri('media', 'views', 'oss', 'suggestion', 'suggestion.css'); // make it common + + const nonce = getNonce(); + + return ` + + + + + + + + + + + +
+
+
+ + + + + + + + + +
+
+
+
+
+
+
Description
+
+
+
+
Impact
+
+
+
+
Path
+
+ +
+
+
+
+
+

Remediation

+
+
+ + + + + `; + } +} diff --git a/src/test/unit/snykOss/ossServiceLanguageServer.test.ts b/src/test/unit/snykOss/ossServiceLanguageServer.test.ts new file mode 100644 index 000000000..0b09e21b0 --- /dev/null +++ b/src/test/unit/snykOss/ossServiceLanguageServer.test.ts @@ -0,0 +1,81 @@ +import { strictEqual } from 'assert'; +import sinon from 'sinon'; +import { IAnalytics } from '../../../snyk/common/analytics/itly'; +import { IConfiguration } from '../../../snyk/common/configuration/configuration'; +import { WorkspaceTrust } from '../../../snyk/common/configuration/trustedFolders'; +import { ILanguageServer } from '../../../snyk/common/languageServer/languageServer'; +import { OssIssueData, ScanProduct, ScanStatus } from '../../../snyk/common/languageServer/types'; +import { IProductService } from '../../../snyk/common/services/productService'; +import { IViewManagerService } from '../../../snyk/common/services/viewManagerService'; +import { ICodeActionAdapter, ICodeActionKindAdapter } from '../../../snyk/common/vscode/codeAction'; +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 { LanguageServerMock } from '../mocks/languageServer.mock'; +import { LoggerMock } from '../mocks/logger.mock'; + +suite('OSS Service', () => { + let ls: ILanguageServer; + let service: IProductService; + let refreshViewFake: sinon.SinonSpy; + + setup(() => { + ls = new LanguageServerMock(); + refreshViewFake = sinon.fake(); + + const viewManagerService = { + refreshOssView: refreshViewFake, + } as unknown as IViewManagerService; + + service = new OssServiceLanguageServer( + {} as ExtensionContext, + {} as IConfiguration, + {} as OssSuggestionWebviewProviderLanguageServer, + {} as ICodeActionAdapter, + { + getQuickFix: sinon.fake(), + } as ICodeActionKindAdapter, + viewManagerService, + { + getWorkspaceFolders: () => [''], + } as IVSCodeWorkspace, + new WorkspaceTrust(), + ls, + { + registerCodeActionsProvider: sinon.fake(), + } as unknown as IVSCodeLanguages, + new LoggerMock(), + {} as IAnalytics, + ); + }); + + teardown(() => { + sinon.restore(); + }); + + test('Scan returned for OSS product', () => { + ls.scan$.next({ + product: ScanProduct.OpenSource, + folderPath: 'test/path', + issues: [], + status: ScanStatus.InProgress, + }); + + strictEqual(service.isAnalysisRunning, true); + sinon.assert.calledOnce(refreshViewFake); + }); + + test('Scan not returned for non-OSS product', () => { + ls.scan$.next({ + product: ScanProduct.Code, + folderPath: 'test/path', + issues: [], + status: ScanStatus.InProgress, + }); + + strictEqual(service.isAnalysisRunning, false); + sinon.assert.notCalled(refreshViewFake); + }); +}); From 3b36efa318c613c7b6635cc0874cc04dc6d64392 Mon Sep 17 00:00:00 2001 From: JSON Date: Thu, 2 Nov 2023 13:15:38 +0000 Subject: [PATCH 03/16] feat: Language Server based OSS tree view (#386) * refactor: rename and move ossSuggestionWebviewProviderLanguageServer * feat: add new OSS (LS) panel in Snyk UI * feat: configure new views and activation events for LS OSS treeview * refactor: OSS messages for LS implementation; OssIssueCommandArgLanguerServer type * refactor: getIssueRange abstract method to allow undefined * feat: implement OSS vulnerability tree via LS class * feat: configure extension.ts to show LS OSS vulnerability tree view * feat: update issue types to support LS OSS issue command args * refactor: getOpenIssueCommand abstract method to support LS OSS implementations * feat: implement initial LS OSS treeview --- package.json | 6 + src/snyk/common/commands/types.ts | 7 +- src/snyk/common/constants/views.ts | 2 + src/snyk/common/views/issueTreeProvider.ts | 11 +- src/snyk/extension.ts | 51 ++-- src/snyk/snykOss/interfaces.ts | 12 + src/snyk/snykOss/messages.ts | 50 ++++ src/snyk/snykOss/ossServiceLanguageServer.ts | 2 +- .../ossDetailPanelProvider.ts} | 40 ++-- .../providers/ossVulnerabilityTreeProvider.ts | 224 ++++++++++++++++++ src/snyk/snykOss/views/interfaces.ts | 11 - .../snykOss/ossServiceLanguageServer.test.ts | 4 +- 12 files changed, 365 insertions(+), 55 deletions(-) create mode 100644 src/snyk/snykOss/interfaces.ts create mode 100644 src/snyk/snykOss/messages.ts rename src/snyk/snykOss/{views/suggestion/ossSuggestionWebviewProviderLanguageServer.ts => providers/ossDetailPanelProvider.ts} (82%) create mode 100644 src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts delete mode 100644 src/snyk/snykOss/views/interfaces.ts diff --git a/package.json b/package.json index fb321914a..25ab0537d 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", @@ -235,6 +236,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", 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; 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'; diff --git a/src/snyk/common/views/issueTreeProvider.ts b/src/snyk/common/views/issueTreeProvider.ts index 122f94966..b55fe4b01 100644 --- a/src/snyk/common/views/issueTreeProvider.ts +++ b/src/snyk/common/views/issueTreeProvider.ts @@ -41,8 +41,13 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid abstract getRunTestMessage(): string; abstract getIssueTitle(issue: Issue): string; - abstract getIssueRange(issue: Issue): Range; - abstract getOpenIssueCommand(issue: Issue, folderPath: string, filePath: string): Command; + abstract getIssueRange(issue?: Issue): Range | undefined; + abstract getOpenIssueCommand( + issue: Issue, + folderPath: string, + filePath: string, + filteredIssues?: Issue[], + ): Command; getRootChildren(): TreeNode[] { const nodes: TreeNode[] = []; @@ -229,7 +234,7 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid return IssueSeverity.Low; } - private initSeverityCounts(): ISeverityCounts { + protected initSeverityCounts(): ISeverityCounts { return { [IssueSeverity.Critical]: 0, [IssueSeverity.High]: 0, diff --git a/src/snyk/extension.ts b/src/snyk/extension.ts index 60b551275..71e42fe4f 100644 --- a/src/snyk/extension.ts +++ b/src/snyk/extension.ts @@ -36,6 +36,7 @@ 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, } from './common/constants/views'; @@ -75,12 +76,13 @@ 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 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'; 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 +227,7 @@ class SnykExtension extends SnykLib implements IExtension { this.workspaceTrust, ); - const ossSuggestionProvider = new OssSuggestionWebviewProviderLanguageServer( + const ossSuggestionProvider = new OssDetailPanelProvider( vsCodeWindow, extensionContext, Logger, @@ -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(); diff --git a/src/snyk/snykOss/interfaces.ts b/src/snyk/snykOss/interfaces.ts new file mode 100644 index 000000000..c206b56a1 --- /dev/null +++ b/src/snyk/snykOss/interfaces.ts @@ -0,0 +1,12 @@ +import * as vscode from 'vscode'; +import { Issue, OssIssueData } from '../common/languageServer/types'; +import { IWebViewProvider } from '../common/views/webviewProvider'; + +export interface IOssSuggestionWebviewProvider extends IWebViewProvider> { + openIssueId: string | undefined; +} + +export type OssIssueCommandArgLanguageServer = Issue & { + matchingIdVulnerabilities: Issue[]; + overviewHtml: string; +}; diff --git a/src/snyk/snykOss/messages.ts b/src/snyk/snykOss/messages.ts new file mode 100644 index 000000000..1cbbec292 --- /dev/null +++ b/src/snyk/snykOss/messages.ts @@ -0,0 +1,50 @@ +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', + vulnerabilities: 'vulnerabilities', + showMostSevereVulnerability: 'Show the most severe vulnerability (Snyk)', + decoratorMessage: (vulnerabilityCount: string): string => { + 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 `; + }, + }, +}; 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 82% rename from src/snyk/snykOss/views/suggestion/ossSuggestionWebviewProviderLanguageServer.ts rename to src/snyk/snykOss/providers/ossDetailPanelProvider.ts index 415859bea..c16081cec 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_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'; +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> { @@ -36,7 +33,10 @@ export class OssSuggestionWebviewProviderLanguageServer 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), + ), ); } @@ -52,7 +52,7 @@ export class OssSuggestionWebviewProviderLanguageServer 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, diff --git a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts new file mode 100644 index 000000000..d64997b34 --- /dev/null +++ b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts @@ -0,0 +1,224 @@ +import _ from 'lodash'; +import * as 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, IssueSeverity, 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(); + } + + override getResultNodes(): [TreeNode[], number] { + const nodes: TreeNode[] = []; + let totalVulnCount = 0; + + for (const result of this.productService.result.entries()) { + const folderPath = result[0]; + const folderResult = result[1]; + + const uri = Uri.file(folderPath); + const shortFolderPath = uri.path.split('/'); + const folderName = shortFolderPath.pop() || uri.path; + + let folderVulnCount = 0; + if (folderResult instanceof Error) { + nodes.push(this.getErrorEncounteredTreeNode(folderName)); + continue; + } + + const folderSeverityCounts = this.initSeverityCounts(); + const fileNodes: TreeNode[] = []; + + const fileVulns = _.groupBy(folderResult, v => 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 { + return this.contextService.shouldShowOssAnalysis; + } + + getIssueDescriptionText(dir: string | undefined, issueCount: number): string | undefined { + return `${dir} - ${issueCount} ${issueCount === 1 ? 'vulnerability' : 'vulnerabilities'}`; + } + + getIssueFoundText(nIssues: number): string { + return `Snyk found ${ + !nIssues ? 'no vulnerabilities! ✅' : `${nIssues} ${nIssues === 1 ? 'vulnerability' : 'vulnerabilities'}` + }`; + } + + filterIssues(issues: Issue[]): Issue[] { + 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; + + getIssueTitle = (issue: Issue) => issue.title; + + getIssueRange = () => undefined; + + getOpenIssueCommand( + issue: Issue, + _folderPath: string, + _filePath: string, + filteredIssues: Issue[], + ): Command { + return { + command: SNYK_OPEN_ISSUE_COMMAND, + title: '', + arguments: [ + { + issueType: OpenCommandIssueType.OssVulnerability, + issue: this.getOssIssueCommandArg(issue, filteredIssues), + } as OpenIssueCommandArg, + ], + }; + } + + getOssIssueCommandArg( + vuln: Issue, + filteredVulns: Issue[], + ): OssIssueCommandArgLanguageServer { + const matchingIdVulnerabilities = filteredVulns.filter(v => v.id === vuln.id); + let overviewHtml = ''; + + try { + // TODO: marked.parse does not sanitize the HTML. See: https://marked.js.org/#usage + overviewHtml = marked.parse(vuln.additionalData.description); + } catch (error) { + overviewHtml = '

There was a problem rendering the vulnerability overview

'; + } + + return { + ...vuln, + matchingIdVulnerabilities, + overviewHtml, + }; + } +} diff --git a/src/snyk/snykOss/views/interfaces.ts b/src/snyk/snykOss/views/interfaces.ts deleted file mode 100644 index 21eb873a9..000000000 --- a/src/snyk/snykOss/views/interfaces.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Issue, OssIssueData } from '../../common/languageServer/types'; -import { IWebViewProvider } from '../../common/views/webviewProvider'; - -export interface IOssSuggestionWebviewProvider extends IWebViewProvider> { - openIssueId: string | undefined; -} - -export type OssIssueCommandArgLanguageServer = OssIssueData & { - matchingIdVulnerabilities: OssIssueData[]; - overviewHtml: string; -}; 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 ce00993ada2c2bdc73df8f73b4aa90abebfecc42 Mon Sep 17 00:00:00 2001 From: PeterSchafer <101886095+PeterSchafer@users.noreply.github.com> Date: Mon, 6 Nov 2023 13:20:07 +0100 Subject: [PATCH 04/16] feat: Derive Vulnerability Count from LS results [HEAD-942] (#388) * chore: introduce observerable in ProductService * feat: enable LS based Vulnerability Count Service * fix: creating subject * chore: introduce static getOssIssueArgCommand() * chore: adapt vuln count code action provider * chore: remove dependency to old OSSService * fix: linter & cleanup * chore: adapt test to use LS versions of classes * chore: delete now obsolote classes * fix: duplicate diagnostics message * fix: use strict equal * chore: convert severities explicitely * fix: typo --- src/snyk/base/modules/baseSnykModule.ts | 4 +- src/snyk/common/services/productService.ts | 4 +- src/snyk/extension.ts | 16 ++-- ...s => vulnerabilityCodeActionProviderLS.ts} | 36 ++++++-- src/snyk/snykOss/ossResult.ts | 14 ++++ src/snyk/snykOss/services/ossService.ts | 2 +- ...e.ts => ossVulnerabilityCountServiceLS.ts} | 21 +++-- ...der.ts => vulnerabilityCountProviderLS.ts} | 83 +++++++++++++++++-- .../ossVulnerabilityCountService.test.ts | 29 +++---- .../vulnerabilityCountProvider.test.ts | 42 +++++----- 10 files changed, 179 insertions(+), 72 deletions(-) rename src/snyk/snykOss/codeActions/{vulnerabilityCodeActionProvider.ts => vulnerabilityCodeActionProviderLS.ts} (66%) rename src/snyk/snykOss/services/vulnerabilityCount/{ossVulnerabilityCountService.ts => ossVulnerabilityCountServiceLS.ts} (92%) rename src/snyk/snykOss/services/vulnerabilityCount/{vulnerabilityCountProvider.ts => vulnerabilityCountProviderLS.ts} (64%) diff --git a/src/snyk/base/modules/baseSnykModule.ts b/src/snyk/base/modules/baseSnykModule.ts index 4dc05fdbd..5681dafdf 100644 --- a/src/snyk/base/modules/baseSnykModule.ts +++ b/src/snyk/base/modules/baseSnykModule.ts @@ -25,7 +25,7 @@ import { ICodeSettings } from '../../snykCode/codeSettings'; import SnykEditorsWatcher from '../../snykCode/watchers/editorsWatcher'; import { OssServiceLanguageServer } from '../../snykOss/ossServiceLanguageServer'; import { OssService } from '../../snykOss/services/ossService'; -import { OssVulnerabilityCountService } from '../../snykOss/services/vulnerabilityCount/ossVulnerabilityCountService'; +import { OssVulnerabilityCountServiceLS } from '../../snykOss/services/vulnerabilityCount/ossVulnerabilityCountServiceLS'; import { IAuthenticationService } from '../services/authenticationService'; import { ScanModeService } from '../services/scanModeService'; import SnykStatusBarItem, { IStatusBarItem } from '../statusBarItem/statusBarItem'; @@ -50,7 +50,7 @@ export default abstract class BaseSnykModule implements IBaseSnykModule { protected advisorService?: AdvisorProvider; protected commandController: CommandController; protected scanModeService: ScanModeService; - protected ossVulnerabilityCountService: OssVulnerabilityCountService; + protected ossVulnerabilityCountServiceLanguageServer: OssVulnerabilityCountServiceLS; protected advisorScoreDisposable: AdvisorService; protected languageServer: ILanguageServer; diff --git a/src/snyk/common/services/productService.ts b/src/snyk/common/services/productService.ts index 62b6c69b3..efb4f06b1 100644 --- a/src/snyk/common/services/productService.ts +++ b/src/snyk/common/services/productService.ts @@ -1,4 +1,4 @@ -import { Subscription } from 'rxjs'; +import { Subject, Subscription } from 'rxjs'; import { AnalysisStatusProvider } from '../analysis/statusProvider'; import { IConfiguration } from '../configuration/configuration'; import { IWorkspaceTrust } from '../configuration/trustedFolders'; @@ -30,6 +30,7 @@ export interface IProductService extends AnalysisStatusProvider, Disposable { export abstract class ProductService extends AnalysisStatusProvider implements IProductService { private _result: ProductResult; + readonly newResultAvailable$ = new Subject(); // Track running scan count. Assumption: server sends N success/error messages for N scans in progress. private runningScanCount = 0; @@ -168,6 +169,7 @@ export abstract class ProductService extends AnalysisStatusProvider implement this.analysisFinished(); this.runningScanCount = 0; + this.newResultAvailable$.next(); this.refreshTreeView(); } } diff --git a/src/snyk/extension.ts b/src/snyk/extension.ts index 71e42fe4f..8085d6d9d 100644 --- a/src/snyk/extension.ts +++ b/src/snyk/extension.ts @@ -79,8 +79,8 @@ 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'; +import { OssVulnerabilityCountServiceLS } from './snykOss/services/vulnerabilityCount/ossVulnerabilityCountServiceLS'; +import { ModuleVulnerabilityCountProviderLS } from './snykOss/services/vulnerabilityCount/vulnerabilityCountProviderLS'; import { OssVulnerabilityTreeProvider } from './snykOss/views/ossVulnerabilityTreeProvider'; import { OssSuggestionWebviewProvider } from './snykOss/views/suggestion/ossSuggestionWebviewProvider'; import { DailyScanJob } from './snykOss/watchers/dailyScanJob'; @@ -414,24 +414,24 @@ class SnykExtension extends SnykLib implements IExtension { this.initDependencyDownload(); - this.ossVulnerabilityCountService = new OssVulnerabilityCountService( + this.ossVulnerabilityCountServiceLanguageServer = new OssVulnerabilityCountServiceLS( vsCodeWorkspace, vsCodeWindow, vsCodeLanguages, - new ModuleVulnerabilityCountProvider( - this.ossService, + new ModuleVulnerabilityCountProviderLS( + this.ossServiceLanguageServer, languageClientAdapter, new UriAdapter(), new TextDocumentAdapter(), ), - this.ossService, + this.ossServiceLanguageServer, Logger, new EditorDecorator(vsCodeWindow, vsCodeLanguages, new ThemeColorAdapter()), new CodeActionKindAdapter(), this.analytics, configuration, ); - this.ossVulnerabilityCountService.activate(); + this.ossVulnerabilityCountServiceLanguageServer.activate(); this.advisorScoreDisposable = new AdvisorService( vsCodeWindow, @@ -461,7 +461,7 @@ class SnykExtension extends SnykLib implements IExtension { } public async deactivate(): Promise { - this.ossVulnerabilityCountService.dispose(); + this.ossVulnerabilityCountServiceLanguageServer.dispose(); await this.languageServer.stop(); await this.analytics.flush(); await ErrorReporter.flush(); diff --git a/src/snyk/snykOss/codeActions/vulnerabilityCodeActionProvider.ts b/src/snyk/snykOss/codeActions/vulnerabilityCodeActionProviderLS.ts similarity index 66% rename from src/snyk/snykOss/codeActions/vulnerabilityCodeActionProvider.ts rename to src/snyk/snykOss/codeActions/vulnerabilityCodeActionProviderLS.ts index 68b1b3535..1d398a5f5 100644 --- a/src/snyk/snykOss/codeActions/vulnerabilityCodeActionProvider.ts +++ b/src/snyk/snykOss/codeActions/vulnerabilityCodeActionProviderLS.ts @@ -1,3 +1,4 @@ +import marked from 'marked'; import { IAnalytics } from '../../common/analytics/itly'; import { OpenCommandIssueType, OpenIssueCommandArg } from '../../common/commands/types'; import { SNYK_OPEN_ISSUE_COMMAND } from '../../common/constants/commands'; @@ -16,16 +17,15 @@ import { } from '../../common/vscode/types'; import { DIAGNOSTICS_OSS_COLLECTION_NAME } from '../../snykCode/constants/analysis'; import { messages } from '../messages/vulnerabilityCount'; -import { isResultCliError } from '../ossResult'; -import { OssService } from '../services/ossService'; -import { ModuleVulnerabilityCountProvider } from '../services/vulnerabilityCount/vulnerabilityCountProvider'; +import { OssVulnerability, isResultCliError } from '../ossResult'; +import { ModuleVulnerabilityCountProviderLS } from '../services/vulnerabilityCount/vulnerabilityCountProviderLS'; +import { OssIssueCommandArg } from '../views/ossVulnerabilityTreeProvider'; -export class VulnerabilityCodeActionProvider implements CodeActionProvider { +export class VulnerabilityCodeActionProviderLS implements CodeActionProvider { public codeActionKinds: ReadonlyArray = [this.codeActionKindProvider.getQuickFix()]; constructor( - private readonly ossService: OssService, - private readonly vulnerabilityCountProvider: ModuleVulnerabilityCountProvider, + private readonly vulnerabilityCountProvider: ModuleVulnerabilityCountProviderLS, private readonly codeActionKindProvider: ICodeActionKindAdapter, private readonly analytics: IAnalytics, ) {} @@ -40,7 +40,7 @@ export class VulnerabilityCodeActionProvider implements CodeActionProvider { return; } - const ossResult = this.ossService.getResultArray(); + const ossResult = this.vulnerabilityCountProvider.getResultArray(); if (!ossResult) { return; } @@ -65,7 +65,7 @@ export class VulnerabilityCodeActionProvider implements CodeActionProvider { arguments: [ { issueType: OpenCommandIssueType.OssVulnerability, - issue: await this.ossService.getOssIssueCommandArg(vulnerability, fileResult.vulnerabilities), + issue: await this.getOssIssueCommandArg(vulnerability, fileResult.vulnerabilities), } as OpenIssueCommandArg, ], }; @@ -78,4 +78,24 @@ export class VulnerabilityCodeActionProvider implements CodeActionProvider { return [command]; } } + + getOssIssueCommandArg( + vulnerability: OssVulnerability, + allVulnerabilities: OssVulnerability[], + ): Promise { + return new Promise((resolve, reject) => { + const matchingIdVulnerabilities = allVulnerabilities.filter(v => v.id === vulnerability.id); + marked.parse(vulnerability.description, (err, overviewHtml) => { + if (err) { + return reject(err); + } + + return resolve({ + ...vulnerability, + matchingIdVulnerabilities: matchingIdVulnerabilities, + overviewHtml, + }); + }); + }); + } } diff --git a/src/snyk/snykOss/ossResult.ts b/src/snyk/snykOss/ossResult.ts index 625019a49..b469695d8 100644 --- a/src/snyk/snykOss/ossResult.ts +++ b/src/snyk/snykOss/ossResult.ts @@ -1,5 +1,6 @@ import _ from 'lodash'; import { CliError } from '../cli/services/cliService'; +import { IssueSeverity } from '../common/languageServer/types'; export type OssResult = OssFileResult[] | OssFileResult; @@ -56,3 +57,16 @@ export function capitalizeOssSeverity(ossSeverity: OssSeverity): Capitalize this.processActiveEditor()); + this.ossScanFinishedSubscription = this.ossService.newResultAvailable$.subscribe(() => this.processActiveEditor()); [JAVASCRIPT, TYPESCRIPT, PJSON, HTML].forEach(language => { - const provider = new VulnerabilityCodeActionProvider( - this.ossService, + const provider = new VulnerabilityCodeActionProviderLS( this.vulnerabilityCountProvider, this.codeActionKindProvider, this.analytics, @@ -188,7 +187,7 @@ export class OssVulnerabilityCountService implements Disposable { private shouldProcessFile(fileName: string, language: Language): boolean { if ([Language.TypeScript, Language.JavaScript, Language.PJSON].includes(language)) { - const ossResult = this.ossService.getResultArray(); + const ossResult = this.vulnerabilityCountProvider.getResultArray(); if (!ossResult) { return false; } @@ -254,7 +253,7 @@ export class OssVulnerabilityCountService implements Disposable { ], module, ); - message += messages.decoratorMessage(module.count); + return message; } diff --git a/src/snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountProvider.ts b/src/snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountProviderLS.ts similarity index 64% rename from src/snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountProvider.ts rename to src/snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountProviderLS.ts index 3c852ae33..0f24e8304 100644 --- a/src/snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountProvider.ts +++ b/src/snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountProviderLS.ts @@ -1,15 +1,16 @@ +import { CliError } from '../../../cli/services/cliService'; import { Language } from '../../../common/types'; import { ILanguageClientAdapter } from '../../../common/vscode/languageClient'; import { ITextDocumentAdapter } from '../../../common/vscode/textdocument'; import { InlineValueText, LSPTextDocument } from '../../../common/vscode/types'; import { IUriAdapter } from '../../../common/vscode/uri'; -import { OssFileResult, OssResultBody, OssVulnerability, isResultCliError } from '../../ossResult'; -import { OssService } from '../ossService'; +import { OssFileResult, OssResultBody, OssVulnerability, convertSeverity, isResultCliError } from '../../ossResult'; +import { OssServiceLanguageServer } from '../../ossServiceLanguageServer'; import { ImportedModule, ModuleVulnerabilityCount, SeverityCounts } from './importedModule'; -export class ModuleVulnerabilityCountProvider { +export class ModuleVulnerabilityCountProviderLS { constructor( - private readonly ossService: OssService, + private readonly ossService: OssServiceLanguageServer, private readonly lca: ILanguageClientAdapter, private readonly uriAdapter: IUriAdapter, private readonly textDocumentAdapter: ITextDocumentAdapter, @@ -29,8 +30,7 @@ export class ModuleVulnerabilityCountProvider { }; if ([Language.TypeScript, Language.JavaScript, Language.PJSON].includes(language)) { - // TODO use LS when OSS is moved to LS - const ossResult = this.ossService.getResultArray(); + const ossResult = this.getResultArray(); if (!ossResult) { return notCalculated; } @@ -91,7 +91,7 @@ export class ModuleVulnerabilityCountProvider { continue; } - const vulnerabilities = this.ossService.getUniqueVulnerabilities((fileResult as OssResultBody).vulnerabilities); + const vulnerabilities = this.getUniqueVulnerabilities((fileResult as OssResultBody).vulnerabilities); // Sum up all vulnerabilities detected in first-level dependencies by OSS matching the imported module name. // Ideally we want to use the same mechanism as NPM for determining the version used within users code. For now we stick with direct-vulnerability surfacing only. @@ -158,4 +158,73 @@ export class ModuleVulnerabilityCountProvider { return 0; })?.[0]; } + + public getResultArray = (): ReadonlyArray | undefined => { + if (!this.ossService.result) { + return undefined; + } + + const tempResultArray: OssFileResult[] = []; + const resultCache = new Map(); + + for (const [, value] of this.ossService.result) { + // value is Error + if (value instanceof Error) { + tempResultArray.push(new CliError(value)); + } + // value is Issue[] + else { + for (const issue of value) { + // try to access list of vulns for the current file + let res = resultCache.get(issue.filePath); + + // add list of vulns to local cache if not there yet + if (res === undefined) { + res = { + path: issue.filePath, + vulnerabilities: [], + projectName: issue.additionalData.projectName, + displayTargetFile: issue.additionalData.displayTargetFile, + packageManager: issue.additionalData.packageManager, + }; + resultCache.set(issue.filePath, res); + } + + const tempVuln: OssVulnerability = { + id: issue.id, + license: issue.additionalData.license, + identifiers: issue.additionalData.identifiers, + title: issue.title, + description: issue.additionalData.description, + language: issue.additionalData.language, + packageManager: issue.additionalData.packageManager, + packageName: issue.additionalData.packageName, + severity: convertSeverity(issue.severity), + name: issue.additionalData.name, + version: issue.additionalData.version, + exploit: issue.additionalData.exploit, + + CVSSv3: issue.additionalData.CVSSv3, + cvssScore: issue.additionalData.cvssScore, + + fixedIn: issue.additionalData.fixedIn, + from: issue.additionalData.from, + upgradePath: issue.additionalData.upgradePath, + isPatchable: issue.additionalData.isPatchable, + isUpgradable: issue.additionalData.isUpgradable, + }; + res.vulnerabilities.push(tempVuln); + } + } + } + + // copy cached results to final result array + resultCache.forEach(value => tempResultArray.push(value)); + + return tempResultArray; + }; + + public getUniqueVulnerabilities(vulnerabilities: OssVulnerability[]): OssVulnerability[] { + return vulnerabilities.filter((val, i, arr) => arr.findIndex(el => el.id === val.id) === i); + } } diff --git a/src/test/unit/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.test.ts b/src/test/unit/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.test.ts index 1e8277bc5..5a444dceb 100644 --- a/src/test/unit/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.test.ts +++ b/src/test/unit/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.test.ts @@ -4,34 +4,35 @@ import sinon from 'sinon'; import { IAnalytics } from '../../../../../snyk/common/analytics/itly'; import { IConfiguration } from '../../../../../snyk/common/configuration/configuration'; import { ICodeActionKindAdapter } from '../../../../../snyk/common/vscode/codeAction'; +import { ILanguageClientAdapter } from '../../../../../snyk/common/vscode/languageClient'; import { IVSCodeLanguages } from '../../../../../snyk/common/vscode/languages'; +import { ITextDocumentAdapter } from '../../../../../snyk/common/vscode/textdocument'; import { IThemeColorAdapter } from '../../../../../snyk/common/vscode/theme'; import { TextDocument, TextEditor } from '../../../../../snyk/common/vscode/types'; +import { IUriAdapter } from '../../../../../snyk/common/vscode/uri'; import { IVSCodeWindow } from '../../../../../snyk/common/vscode/window'; import { IVSCodeWorkspace } from '../../../../../snyk/common/vscode/workspace'; import { EditorDecorator } from '../../../../../snyk/snykOss/editor/editorDecorator'; import { OssFileResult } from '../../../../../snyk/snykOss/ossResult'; -import { OssService } from '../../../../../snyk/snykOss/services/ossService'; -import { OssVulnerabilityCountService } from '../../../../../snyk/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService'; -import { ModuleVulnerabilityCountProvider } from '../../../../../snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountProvider'; +import { OssServiceLanguageServer } from '../../../../../snyk/snykOss/ossServiceLanguageServer'; +import { OssVulnerabilityCountServiceLS } from '../../../../../snyk/snykOss/services/vulnerabilityCount/ossVulnerabilityCountServiceLS'; +import { ModuleVulnerabilityCountProviderLS } from '../../../../../snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountProviderLS'; import { LoggerMock } from '../../../mocks/logger.mock'; -import { ILanguageClientAdapter } from '../../../../../snyk/common/vscode/languageClient'; -import { IUriAdapter } from '../../../../../snyk/common/vscode/uri'; -import { ITextDocumentAdapter } from '../../../../../snyk/common/vscode/textdocument'; suite('OSS VulnerabilityCountService', () => { let workspace: IVSCodeWorkspace; let window: IVSCodeWindow; let languages: IVSCodeLanguages; - let ossVulnerabilityCountService: OssVulnerabilityCountService; - let ossService: OssService; - let vulnerabilityCountProvider: ModuleVulnerabilityCountProvider; + let ossVulnerabilityCountService: OssVulnerabilityCountServiceLS; + let ossService: OssServiceLanguageServer; + let vulnerabilityCountProvider: ModuleVulnerabilityCountProviderLS; setup(() => { const logger = new LoggerMock(); ossService = { scanFinished$: EMPTY, - } as unknown as OssService; + newResultAvailable$: EMPTY, + } as unknown as OssServiceLanguageServer; workspace = {} as IVSCodeWorkspace; window = { createTextEditorDecorationType: sinon.fake(), @@ -41,7 +42,7 @@ suite('OSS VulnerabilityCountService', () => { registerCodeActionsProvider: sinon.fake(), registerHoverProvider: sinon.fake(), } as unknown as IVSCodeLanguages; - vulnerabilityCountProvider = new ModuleVulnerabilityCountProvider( + vulnerabilityCountProvider = new ModuleVulnerabilityCountProviderLS( ossService, {} as ILanguageClientAdapter, {} as IUriAdapter, @@ -55,7 +56,7 @@ suite('OSS VulnerabilityCountService', () => { const analytics = {} as IAnalytics; const configuration = {} as IConfiguration; - ossVulnerabilityCountService = new OssVulnerabilityCountService( + ossVulnerabilityCountService = new OssVulnerabilityCountServiceLS( workspace, window, languages, @@ -125,7 +126,7 @@ suite('OSS VulnerabilityCountService', () => { }); test("Doesn't process if file is supported and OSS scan hasn't run", () => { - ossService.getResultArray = () => undefined; + vulnerabilityCountProvider.getResultArray = () => undefined; const tsDocument = { fileName: 'C:\\git\\project\\test.ts', languageId: 'typescript', @@ -150,7 +151,7 @@ suite('OSS VulnerabilityCountService', () => { languageId: 'typescript', getText: () => 'const x = require("react")', } as TextDocument; - ossService.getResultArray = () => [{} as OssFileResult]; + vulnerabilityCountProvider.getResultArray = () => [{} as OssFileResult]; sinon.stub(vulnerabilityCountProvider, 'isFilePartOfOssTest').returns(true); const processed = ossVulnerabilityCountService.processFile(document); diff --git a/src/test/unit/snykOss/services/vulnerabilityCount/vulnerabilityCountProvider.test.ts b/src/test/unit/snykOss/services/vulnerabilityCount/vulnerabilityCountProvider.test.ts index fb5eb9ca4..79c933516 100644 --- a/src/test/unit/snykOss/services/vulnerabilityCount/vulnerabilityCountProvider.test.ts +++ b/src/test/unit/snykOss/services/vulnerabilityCount/vulnerabilityCountProvider.test.ts @@ -2,17 +2,17 @@ import { deepStrictEqual, strictEqual } from 'assert'; import sinon from 'sinon'; import { CliError } from '../../../../../snyk/cli/services/cliService'; import { Language } from '../../../../../snyk/common/types'; -import { OssResultBody, OssVulnerability } from '../../../../../snyk/snykOss/ossResult'; -import { OssService } from '../../../../../snyk/snykOss/services/ossService'; -import { ImportedModule } from '../../../../../snyk/snykOss/services/vulnerabilityCount/importedModule'; -import { ModuleVulnerabilityCountProvider } from '../../../../../snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountProvider'; import { ILanguageClientAdapter } from '../../../../../snyk/common/vscode/languageClient'; -import { IUriAdapter } from '../../../../../snyk/common/vscode/uri'; import { ITextDocumentAdapter } from '../../../../../snyk/common/vscode/textdocument'; +import { IUriAdapter } from '../../../../../snyk/common/vscode/uri'; +import { OssResultBody, OssVulnerability } from '../../../../../snyk/snykOss/ossResult'; +import { OssServiceLanguageServer } from '../../../../../snyk/snykOss/ossServiceLanguageServer'; +import { ImportedModule } from '../../../../../snyk/snykOss/services/vulnerabilityCount/importedModule'; +import { ModuleVulnerabilityCountProviderLS } from '../../../../../snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountProviderLS'; suite('OSS ModuleVulnerabilityCountProvider', () => { - let ossService: OssService; - let vulnerabilityCountProvider: ModuleVulnerabilityCountProvider; + let ossService: OssServiceLanguageServer; + let vulnerabilityCountProvider: ModuleVulnerabilityCountProviderLS; const sampleFilePath = 'C:\\git\\project\\test.js'; const sampleModuleName = 'mongo-express'; @@ -54,8 +54,8 @@ suite('OSS ModuleVulnerabilityCountProvider', () => { ]; setup(() => { - ossService = {} as OssService; - vulnerabilityCountProvider = new ModuleVulnerabilityCountProvider( + ossService = {} as OssServiceLanguageServer; + vulnerabilityCountProvider = new ModuleVulnerabilityCountProviderLS( ossService, {} as ILanguageClientAdapter, {} as IUriAdapter, @@ -68,7 +68,7 @@ suite('OSS ModuleVulnerabilityCountProvider', () => { }); test('Not calculated if JS/TS results are not provided', async () => { - ossService.getResultArray = () => undefined; + vulnerabilityCountProvider.getResultArray = () => undefined; const tsCount = await vulnerabilityCountProvider.getVulnerabilityCount( 'test.ts', @@ -89,8 +89,8 @@ suite('OSS ModuleVulnerabilityCountProvider', () => { }); test('Gets TS/JS imported module vulnerability results correctly', async () => { - ossService.getResultArray = () => sampleOssResults; - ossService.getUniqueVulnerabilities = () => sampleOssResults[0].vulnerabilities; + vulnerabilityCountProvider.getResultArray = () => sampleOssResults; + vulnerabilityCountProvider.getUniqueVulnerabilities = () => sampleOssResults[0].vulnerabilities; const count = await vulnerabilityCountProvider.getVulnerabilityCount( 'test.ts', @@ -116,8 +116,8 @@ suite('OSS ModuleVulnerabilityCountProvider', () => { }); test('Gets package.json dependency vulnerability results correctly', async () => { - ossService.getResultArray = () => sampleOssResults; - ossService.getUniqueVulnerabilities = () => sampleOssResults[0].vulnerabilities; + vulnerabilityCountProvider.getResultArray = () => sampleOssResults; + vulnerabilityCountProvider.getUniqueVulnerabilities = () => sampleOssResults[0].vulnerabilities; const count = await vulnerabilityCountProvider.getVulnerabilityCount( 'test.ts', @@ -145,8 +145,8 @@ suite('OSS ModuleVulnerabilityCountProvider', () => { ], }, ]; - ossService.getResultArray = () => ossResultsWithIndirectVulnerability; - ossService.getUniqueVulnerabilities = () => ossResultsWithIndirectVulnerability[0].vulnerabilities; + vulnerabilityCountProvider.getResultArray = () => ossResultsWithIndirectVulnerability; + vulnerabilityCountProvider.getUniqueVulnerabilities = () => ossResultsWithIndirectVulnerability[0].vulnerabilities; const count = await vulnerabilityCountProvider.getVulnerabilityCount( 'test.ts', @@ -177,8 +177,9 @@ suite('OSS ModuleVulnerabilityCountProvider', () => { ], }, ]; - ossService.getResultArray = () => ossResultsWithMultipleVersionsVulnerability; - ossService.getUniqueVulnerabilities = () => ossResultsWithMultipleVersionsVulnerability[0].vulnerabilities; + vulnerabilityCountProvider.getResultArray = () => ossResultsWithMultipleVersionsVulnerability; + vulnerabilityCountProvider.getUniqueVulnerabilities = () => + ossResultsWithMultipleVersionsVulnerability[0].vulnerabilities; const count = await vulnerabilityCountProvider.getVulnerabilityCount( 'test.ts', @@ -207,8 +208,9 @@ suite('OSS ModuleVulnerabilityCountProvider', () => { ], }, ]; - ossService.getResultArray = () => ossResultsWithMultipleVersionsVulnerability; - ossService.getUniqueVulnerabilities = () => ossResultsWithMultipleVersionsVulnerability[0].vulnerabilities; + vulnerabilityCountProvider.getResultArray = () => ossResultsWithMultipleVersionsVulnerability; + vulnerabilityCountProvider.getUniqueVulnerabilities = () => + ossResultsWithMultipleVersionsVulnerability[0].vulnerabilities; const count = await vulnerabilityCountProvider.getVulnerabilityCount( 'test.ts', From 85ca7e48f56f8e017a3e40fc1c4976360106c0c8 Mon Sep 17 00:00:00 2001 From: PeterSchafer <101886095+PeterSchafer@users.noreply.github.com> Date: Fri, 10 Nov 2023 12:33:16 +0100 Subject: [PATCH 05/16] feat: Use Detail Panel from Language Server (#389) * docs: update instructions for contribution to include `npm run build` Signed-off-by: Bastian Doetsch * chore: use ls based oss service in cmdcontroller * chore: move convertion of Issue to OssVulnerability * chore: adapt test * chore: delete vulnerabilityCountCodeActionProviderLS class as LS handles CodeActions now * chore: delete vulnerabilityCountHoverProvider class as LS handles hovers now * chore: clean up codeaction/hovers where they are not needed anymore * fix: linting * check license data if undefined * chore: display received details html * chore: initial version taking html from ls * chore: inject learn icon * chore: inject nonce and cspsource * fix: linter --------- Signed-off-by: Bastian Doetsch Co-authored-by: Bastian Doetsch Co-authored-by: Jason Luong --- CONTRIBUTING.md | 2 +- src/snyk/common/commands/commandController.ts | 27 +++- src/snyk/common/languageServer/types.ts | 2 + src/snyk/extension.ts | 7 +- .../vulnerabilityCodeActionProviderLS.ts | 101 ------------ .../vulnerabilityCountHoverProvider.ts | 49 ------ src/snyk/snykOss/ossResult.ts | 33 +++- src/snyk/snykOss/ossServiceLanguageServer.ts | 7 - .../providers/ossDetailPanelProvider.ts | 144 ++++-------------- .../ossVulnerabilityCountServiceLS.ts | 21 --- .../vulnerabilityCountProviderLS.ts | 26 +--- .../common/commands/commandController.test.ts | 8 +- .../snykOss/ossServiceLanguageServer.test.ts | 4 - .../ossVulnerabilityCountService.test.ts | 5 - 14 files changed, 96 insertions(+), 340 deletions(-) delete mode 100644 src/snyk/snykOss/codeActions/vulnerabilityCodeActionProviderLS.ts delete mode 100644 src/snyk/snykOss/hoverProvider/vulnerabilityCountHoverProvider.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 16faa4f5f..9d32814bc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ ## Run extension and debug -Clone the repository, then run `npm install` in the directory. +Clone the repository, then run `npm install && npm run build` in the directory. - Open repository directory in VS Code and press `F5` to run extension in a new VS Code window. - This allows extension debugging within VS Code. diff --git a/src/snyk/common/commands/commandController.ts b/src/snyk/common/commands/commandController.ts index cb2f31a28..f3c2642c1 100644 --- a/src/snyk/common/commands/commandController.ts +++ b/src/snyk/common/commands/commandController.ts @@ -1,14 +1,13 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ import _ from 'lodash'; +import path from 'path'; import { IAuthenticationService } from '../../base/services/authenticationService'; import { ScanModeService } from '../../base/services/scanModeService'; import { createDCIgnore } from '../../snykCode/utils/ignoreFileUtils'; import { IssueUtils } from '../../snykCode/utils/issueUtils'; import { CodeIssueCommandArg } from '../../snykCode/views/interfaces'; import { IacIssueCommandArg } from '../../snykIac/views/interfaces'; -import { capitalizeOssSeverity } from '../../snykOss/ossResult'; -import { OssService } from '../../snykOss/services/ossService'; -import { OssIssueCommandArg } from '../../snykOss/views/ossVulnerabilityTreeProvider'; +import { OssServiceLanguageServer } from '../../snykOss/ossServiceLanguageServer'; import { IAnalytics } from '../analytics/itly'; import { SNYK_INITIATE_LOGIN_COMMAND, @@ -40,7 +39,7 @@ export class CommandController { private authService: IAuthenticationService, private snykCode: IProductService, private iacService: IProductService, - private ossService: OssService, + private ossService: OssServiceLanguageServer, private scanModeService: ScanModeService, private workspace: IVSCodeWorkspace, private commands: IVSCodeCommands, @@ -122,14 +121,28 @@ export class CommandController { severity: IssueUtils.issueSeverityAsText(issue.severity), }); } else if (arg.issueType == OpenCommandIssueType.OssVulnerability) { - const issue = arg.issue as OssIssueCommandArg; - void this.ossService.showSuggestionProvider(issue); + const issueArgs = arg.issue as CodeIssueCommandArg; + const folderPath = path.dirname(issueArgs.filePath); + const issue = this.ossService.getIssue(folderPath, issueArgs.id); + + if (!issue) { + this.logger.warn(`Failed to find the issue ${issueArgs.id}.`); + return; + } + + await this.openLocalFile(issue.filePath, issueArgs.range); + + try { + await this.ossService.showSuggestionProvider(folderPath, issueArgs.id); + } catch (e) { + ErrorHandler.handle(e, this.logger); + } this.analytics.logIssueInTreeIsClicked({ ide: IDE_NAME, issueId: issue.id, issueType: 'Open Source Vulnerability', - severity: capitalizeOssSeverity(issue.severity), + severity: IssueUtils.issueSeverityAsText(issue.severity), }); } else if (arg.issueType == OpenCommandIssueType.IacIssue) { const issueArgs = arg.issue as IacIssueCommandArg; diff --git a/src/snyk/common/languageServer/types.ts b/src/snyk/common/languageServer/types.ts index 9dbe9a125..1a7d150d7 100644 --- a/src/snyk/common/languageServer/types.ts +++ b/src/snyk/common/languageServer/types.ts @@ -95,6 +95,8 @@ export type OssIssueData = { projectName: string; displayTargetFile: string; + + details: string; }; export type Identifiers = { CWE: string[]; diff --git a/src/snyk/extension.ts b/src/snyk/extension.ts index 8085d6d9d..8bc849a01 100644 --- a/src/snyk/extension.ts +++ b/src/snyk/extension.ts @@ -50,7 +50,7 @@ import { DownloadService } from './common/services/downloadService'; import { LearnService } from './common/services/learnService'; import { NotificationService } from './common/services/notificationService'; import { User } from './common/user'; -import { CodeActionAdapter, CodeActionKindAdapter } from './common/vscode/codeAction'; +import { CodeActionAdapter } from './common/vscode/codeAction'; import { vsCodeCommands } from './common/vscode/commands'; import { vsCodeEnv } from './common/vscode/env'; import { extensionContext } from './common/vscode/extensionContext'; @@ -239,8 +239,6 @@ class SnykExtension extends SnykLib implements IExtension { extensionContext, configuration, ossSuggestionProvider, - new CodeActionAdapter(), - this.codeActionKindAdapter, this.viewManagerService, vsCodeWorkspace, this.workspaceTrust, @@ -278,7 +276,7 @@ class SnykExtension extends SnykLib implements IExtension { this.authService, this.snykCode, this.iacService, - this.ossService, + this.ossServiceLanguageServer, this.scanModeService, vsCodeWorkspace, vsCodeCommands, @@ -427,7 +425,6 @@ class SnykExtension extends SnykLib implements IExtension { this.ossServiceLanguageServer, Logger, new EditorDecorator(vsCodeWindow, vsCodeLanguages, new ThemeColorAdapter()), - new CodeActionKindAdapter(), this.analytics, configuration, ); diff --git a/src/snyk/snykOss/codeActions/vulnerabilityCodeActionProviderLS.ts b/src/snyk/snykOss/codeActions/vulnerabilityCodeActionProviderLS.ts deleted file mode 100644 index 1d398a5f5..000000000 --- a/src/snyk/snykOss/codeActions/vulnerabilityCodeActionProviderLS.ts +++ /dev/null @@ -1,101 +0,0 @@ -import marked from 'marked'; -import { IAnalytics } from '../../common/analytics/itly'; -import { OpenCommandIssueType, OpenIssueCommandArg } from '../../common/commands/types'; -import { SNYK_OPEN_ISSUE_COMMAND } from '../../common/constants/commands'; -import { IDE_NAME } from '../../common/constants/general'; -import { ICodeActionKindAdapter } from '../../common/vscode/codeAction'; -import { - CodeAction, - CodeActionContext, - CodeActionKind, - CodeActionProvider, - Command, - ProviderResult, - Range, - Selection, - TextDocument, -} from '../../common/vscode/types'; -import { DIAGNOSTICS_OSS_COLLECTION_NAME } from '../../snykCode/constants/analysis'; -import { messages } from '../messages/vulnerabilityCount'; -import { OssVulnerability, isResultCliError } from '../ossResult'; -import { ModuleVulnerabilityCountProviderLS } from '../services/vulnerabilityCount/vulnerabilityCountProviderLS'; -import { OssIssueCommandArg } from '../views/ossVulnerabilityTreeProvider'; - -export class VulnerabilityCodeActionProviderLS implements CodeActionProvider { - public codeActionKinds: ReadonlyArray = [this.codeActionKindProvider.getQuickFix()]; - - constructor( - private readonly vulnerabilityCountProvider: ModuleVulnerabilityCountProviderLS, - private readonly codeActionKindProvider: ICodeActionKindAdapter, - private readonly analytics: IAnalytics, - ) {} - - async provideCodeActions( - document: TextDocument, - _: Range | Selection, - context: CodeActionContext, - ): Promise> { - const ossDiagnostics = context.diagnostics.filter(d => d.source === DIAGNOSTICS_OSS_COLLECTION_NAME); - if (!ossDiagnostics.length) { - return; - } - - const ossResult = this.vulnerabilityCountProvider.getResultArray(); - if (!ossResult) { - return; - } - - const fileResult = ossResult.find( - res => !isResultCliError(res) && this.vulnerabilityCountProvider.isFilePartOfOssTest(document.fileName, res), - ); - - if (!fileResult || isResultCliError(fileResult)) { - return; - } - - for (const diagnostic of ossDiagnostics) { - const vulnerability = fileResult.vulnerabilities.find(vuln => vuln.id === diagnostic.code); - if (!vulnerability) { - continue; - } - - const command: Command = { - command: SNYK_OPEN_ISSUE_COMMAND, - title: messages.showMostSevereVulnerability, - arguments: [ - { - issueType: OpenCommandIssueType.OssVulnerability, - issue: await this.getOssIssueCommandArg(vulnerability, fileResult.vulnerabilities), - } as OpenIssueCommandArg, - ], - }; - - this.analytics.logQuickFixIsDisplayed({ - quickFixType: ['Show Most Severe Vulnerability'], - ide: IDE_NAME, - }); - - return [command]; - } - } - - getOssIssueCommandArg( - vulnerability: OssVulnerability, - allVulnerabilities: OssVulnerability[], - ): Promise { - return new Promise((resolve, reject) => { - const matchingIdVulnerabilities = allVulnerabilities.filter(v => v.id === vulnerability.id); - marked.parse(vulnerability.description, (err, overviewHtml) => { - if (err) { - return reject(err); - } - - return resolve({ - ...vulnerability, - matchingIdVulnerabilities: matchingIdVulnerabilities, - overviewHtml, - }); - }); - }); - } -} diff --git a/src/snyk/snykOss/hoverProvider/vulnerabilityCountHoverProvider.ts b/src/snyk/snykOss/hoverProvider/vulnerabilityCountHoverProvider.ts deleted file mode 100644 index 8fc56f11d..000000000 --- a/src/snyk/snykOss/hoverProvider/vulnerabilityCountHoverProvider.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { IAnalytics } from '../../common/analytics/itly'; -import { IDE_NAME } from '../../common/constants/general'; -import { SupportedLanguageIds } from '../../common/constants/languageConsts'; -import { IVSCodeLanguages } from '../../common/vscode/languages'; -import { DiagnosticCollection, Disposable, Hover, Position, TextDocument } from '../../common/vscode/types'; -import { IssueUtils } from '../../snykCode/utils/issueUtils'; - -export class VulnerabilityCountHoverProvider implements Disposable { - private hoverProvider: Disposable | undefined; - - constructor(private readonly vscodeLanguages: IVSCodeLanguages, private readonly analytics: IAnalytics) {} - - register(diagnostics: DiagnosticCollection | undefined): Disposable { - const documentFilter = SupportedLanguageIds.map(id => ({ scheme: 'file', language: id })); - - this.hoverProvider = this.vscodeLanguages.registerHoverProvider(documentFilter, { - provideHover: this.getHover(diagnostics), - }); - - return this; - } - - getHover(diagnostics: DiagnosticCollection | undefined) { - return (document: TextDocument, position: Position): Hover | undefined => { - if (!diagnostics || !diagnostics.has(document.uri)) { - return undefined; - } - - const currentFileReviewIssues = diagnostics.get(document.uri); - const issue = IssueUtils.findIssueWithRange(position, currentFileReviewIssues); - if (issue) { - this.logIssueHoverIsDisplayed(); - } - }; - } - - private logIssueHoverIsDisplayed(): void { - this.analytics.logIssueHoverIsDisplayed({ - issueType: 'Open Source Vulnerability', - ide: IDE_NAME, - }); - } - - dispose(): void { - if (this.hoverProvider) { - this.hoverProvider.dispose(); - } - } -} diff --git a/src/snyk/snykOss/ossResult.ts b/src/snyk/snykOss/ossResult.ts index b469695d8..aabe8b873 100644 --- a/src/snyk/snykOss/ossResult.ts +++ b/src/snyk/snykOss/ossResult.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; import { CliError } from '../cli/services/cliService'; -import { IssueSeverity } from '../common/languageServer/types'; +import { Issue, IssueSeverity, OssIssueData } from '../common/languageServer/types'; export type OssResult = OssFileResult[] | OssFileResult; @@ -70,3 +70,34 @@ export function convertSeverity(severity: IssueSeverity): OssSeverity { return OssSeverity.Critical; } } + +export function convertIssue(issue: Issue): OssVulnerability { + const tempVuln: OssVulnerability = { + id: issue.id, + identifiers: issue.additionalData.identifiers, + title: issue.title, + description: issue.additionalData.description, + language: issue.additionalData.language, + packageManager: issue.additionalData.packageManager, + packageName: issue.additionalData.packageName, + severity: convertSeverity(issue.severity), + name: issue.additionalData.name, + version: issue.additionalData.version, + exploit: issue.additionalData.exploit, + + CVSSv3: issue.additionalData.CVSSv3, + cvssScore: issue.additionalData.cvssScore, + + fixedIn: issue.additionalData.fixedIn === undefined ? [] : issue.additionalData.fixedIn, + from: issue.additionalData.from, + upgradePath: issue.additionalData.upgradePath, + isPatchable: issue.additionalData.isPatchable, + isUpgradable: issue.additionalData.isUpgradable, + }; + + if (issue.additionalData.license !== undefined) { + tempVuln.license = issue.additionalData.license; + } + + return tempVuln; +} diff --git a/src/snyk/snykOss/ossServiceLanguageServer.ts b/src/snyk/snykOss/ossServiceLanguageServer.ts index eef3e7a95..e1d5f0610 100644 --- a/src/snyk/snykOss/ossServiceLanguageServer.ts +++ b/src/snyk/snykOss/ossServiceLanguageServer.ts @@ -7,7 +7,6 @@ import { OssIssueData, Scan, ScanProduct } from '../common/languageServer/types' import { ILog } from '../common/logger/interfaces'; import { ProductService } from '../common/services/productService'; import { IViewManagerService } from '../common/services/viewManagerService'; -import { ICodeActionAdapter, ICodeActionKindAdapter } from '../common/vscode/codeAction'; import { ExtensionContext } from '../common/vscode/extensionContext'; import { IVSCodeLanguages } from '../common/vscode/languages'; import { IVSCodeWorkspace } from '../common/vscode/workspace'; @@ -18,8 +17,6 @@ export class OssServiceLanguageServer extends ProductService { extensionContext: ExtensionContext, config: IConfiguration, suggestionProvider: IOssSuggestionWebviewProvider, - readonly codeActionAdapter: ICodeActionAdapter, - readonly codeActionKindAdapter: ICodeActionKindAdapter, viewManagerService: IViewManagerService, workspace: IVSCodeWorkspace, workspaceTrust: IWorkspaceTrust, @@ -39,10 +36,6 @@ export class OssServiceLanguageServer extends ProductService { languages, logger, ); - - // this.registerCodeActionsProvider( - // new OssCodeActionsProvider(this.result, codeActionAdapter, codeActionKindAdapter, languages, analytics), - // ); } subscribeToLsScanMessages(): Subscription { diff --git a/src/snyk/snykOss/providers/ossDetailPanelProvider.ts b/src/snyk/snykOss/providers/ossDetailPanelProvider.ts index c16081cec..75661b088 100644 --- a/src/snyk/snykOss/providers/ossDetailPanelProvider.ts +++ b/src/snyk/snykOss/providers/ossDetailPanelProvider.ts @@ -1,5 +1,4 @@ import * as vscode from 'vscode'; -import { SNYK_OPEN_BROWSER_COMMAND } from '../../common/constants/commands'; import { SNYK_VIEW_SUGGESTION_OSS_LANGUAGE_SERVER } from '../../common/constants/views'; import { ErrorHandler } from '../../common/error/errorHandler'; import { Issue, OssIssueData } from '../../common/languageServer/types'; @@ -17,6 +16,10 @@ export class OssDetailPanelProvider extends WebviewProvider> implements IWebViewProvider> { + protected getHtmlForWebview(_webview: vscode.Webview): string { + throw new Error('Method not implemented.'); + } + // For consistency reasons, the single source of truth for the current suggestion is the // panel state. The following field is only used in private issue: Issue | undefined; @@ -63,16 +66,41 @@ export class OssDetailPanelProvider this.registerListeners(); } - this.panel.webview.html = this.getHtmlForWebview(this.panel.webview); + const images: Record = [ + ['icon-code', 'svg'], + ['dark-critical-severity', 'svg'], + ['dark-high-severity', 'svg'], + ['dark-medium-severity', 'svg'], + ['dark-low-severity', 'svg'], + ['learn-icon', 'svg'], + ].reduce((accumulator: Record, [name, ext]) => { + const uri = this.getWebViewUri('media', 'images', `${name}.${ext}`); + if (!uri) throw new Error('Image missing.'); + accumulator[name] = uri.toString(); + return accumulator; + }, {}); + + const displayMode = 'dark'; + const styleUri = this.getWebViewUri('media', 'views', 'oss', 'suggestion', 'suggestion.css'); + const headerEndValue = ``; + const serverityIconName = `${displayMode}-${issue.severity}-severity`; + const nonce = getNonce(); + + let html = issue.additionalData.details; + html = html.replace('${headerEnd}', headerEndValue); + html = html.replaceAll('${cspSource}', this.panel.webview.cspSource); + html = html.replaceAll('${nonce}', nonce); + html = html.replace('${severityIcon}', images[serverityIconName]); + html = html.replace('${learnIcon}', images['learn-icon']); + html = html.replaceAll(/\$\{\w+\}/g, ''); + this.panel.webview.html = html; this.panel.iconPath = vscode.Uri.joinPath( vscode.Uri.file(this.context.extensionPath), 'media', 'images', - 'snyk-iac.svg', // TODO: USE OSS ICON + 'snyk-oss.svg', ); - await this.panel.webview.postMessage({ type: 'set', args: issue }); - this.issue = issue; } catch (e) { ErrorHandler.handle(e, this.logger, errorMessages.suggestionViewShowFailed); @@ -84,8 +112,6 @@ export class OssDetailPanelProvider this.panel.onDidDispose(() => this.onPanelDispose(), null, this.disposables); this.panel.onDidChangeViewState(() => this.checkVisibility(), undefined, this.disposables); - // Handle messages from the webview - this.panel.webview.onDidReceiveMessage(msg => this.handleMessage(msg), undefined, this.disposables); } disposePanel(): void { @@ -95,108 +121,4 @@ export class OssDetailPanelProvider protected onPanelDispose(): void { super.onPanelDispose(); } - - private async handleMessage(message: any) { - try { - const { type, value } = message; - switch (type) { - case 'openBrowser': { - await vscode.commands.executeCommand(SNYK_OPEN_BROWSER_COMMAND, value); - break; - } - default: { - throw new Error('Unknown message type'); - } - } - } catch (e) { - ErrorHandler.handle(e, this.logger, errorMessages.suggestionViewShowFailed); - } - } - - protected getHtmlForWebview(webview: vscode.Webview): string { - const images: Record = [ - ['dark-critical-severity', 'svg'], - ['dark-high-severity', 'svg'], - ['dark-medium-severity', 'svg'], - ['dark-low-severity', 'svg'], - ].reduce((accumulator: Record, [name, ext]) => { - const uri = this.getWebViewUri('media', 'images', `${name}.${ext}`); - if (!uri) throw new Error('Image missing.'); - accumulator[name] = uri.toString(); - return accumulator; - }, {}); - - const scriptUri = this.getWebViewUri( - 'out', - 'snyk', - 'snykIac', - 'views', - 'suggestion', - 'iacSuggestionWebviewScript.js', - ); - const styleUri = this.getWebViewUri('media', 'views', 'oss', 'suggestion', 'suggestion.css'); // make it common - - const nonce = getNonce(); - - return ` - - - - - - - - - - - -
-
-
- - - - - - - - - -
-
-
-
-
-
-
Description
-
-
-
-
Impact
-
-
-
-
Path
-
- -
-
-
-
-
-

Remediation

-
-
- - - - - `; - } } diff --git a/src/snyk/snykOss/services/vulnerabilityCount/ossVulnerabilityCountServiceLS.ts b/src/snyk/snykOss/services/vulnerabilityCount/ossVulnerabilityCountServiceLS.ts index dfb8652ef..090021f9a 100644 --- a/src/snyk/snykOss/services/vulnerabilityCount/ossVulnerabilityCountServiceLS.ts +++ b/src/snyk/snykOss/services/vulnerabilityCount/ossVulnerabilityCountServiceLS.ts @@ -1,20 +1,16 @@ import { Subscription } from 'rxjs'; import { IAnalytics } from '../../../common/analytics/itly'; import { IConfiguration } from '../../../common/configuration/configuration'; -import { HTML, JAVASCRIPT, PJSON, TYPESCRIPT } from '../../../common/constants/languageConsts'; import { ILog } from '../../../common/logger/interfaces'; import { getSupportedLanguage, isValidModuleName } from '../../../common/parsing'; import { ModuleParserProvider } from '../../../common/services/moduleParserProvider'; import { Language } from '../../../common/types'; -import { ICodeActionKindAdapter } from '../../../common/vscode/codeAction'; import { IVSCodeLanguages } from '../../../common/vscode/languages'; import { Diagnostic, DiagnosticCollection, Disposable, TextDocument } from '../../../common/vscode/types'; import { IVSCodeWindow } from '../../../common/vscode/window'; import { IVSCodeWorkspace } from '../../../common/vscode/workspace'; import { DIAGNOSTICS_OSS_COLLECTION_NAME } from '../../../snykCode/constants/analysis'; -import { VulnerabilityCodeActionProviderLS } from '../../codeActions/vulnerabilityCodeActionProviderLS'; import { EditorDecorator } from '../../editor/editorDecorator'; -import { VulnerabilityCountHoverProvider } from '../../hoverProvider/vulnerabilityCountHoverProvider'; import { messages } from '../../messages/vulnerabilityCount'; import { OssServiceLanguageServer } from '../../ossServiceLanguageServer'; import { VulnerabilityCountEmitter, VulnerabilityCountEvents } from '../../vulnerabilityCountEmitter'; @@ -43,7 +39,6 @@ export class OssVulnerabilityCountServiceLS implements Disposable { private readonly ossService: OssServiceLanguageServer, private readonly logger: ILog, private readonly editorDecorator: EditorDecorator, - private readonly codeActionKindProvider: ICodeActionKindAdapter, private readonly analytics: IAnalytics, private readonly configuration: IConfiguration, ) {} @@ -61,27 +56,11 @@ export class OssVulnerabilityCountServiceLS implements Disposable { this.processFile(ev.document); } }), - - // register hover provider - new VulnerabilityCountHoverProvider(this.languages, this.analytics).register(this.diagnostics), ); // Subscribe to OSS scan finished updates this.ossScanFinishedSubscription = this.ossService.newResultAvailable$.subscribe(() => this.processActiveEditor()); - [JAVASCRIPT, TYPESCRIPT, PJSON, HTML].forEach(language => { - const provider = new VulnerabilityCodeActionProviderLS( - this.vulnerabilityCountProvider, - this.codeActionKindProvider, - this.analytics, - ); - this.disposables.push( - this.languages.registerCodeActionsProvider(language, provider, { - providedCodeActionKinds: provider.codeActionKinds, - }), - ); - }); - this.processActiveEditor(); return true; diff --git a/src/snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountProviderLS.ts b/src/snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountProviderLS.ts index 0f24e8304..01729c876 100644 --- a/src/snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountProviderLS.ts +++ b/src/snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountProviderLS.ts @@ -4,7 +4,7 @@ import { ILanguageClientAdapter } from '../../../common/vscode/languageClient'; import { ITextDocumentAdapter } from '../../../common/vscode/textdocument'; import { InlineValueText, LSPTextDocument } from '../../../common/vscode/types'; import { IUriAdapter } from '../../../common/vscode/uri'; -import { OssFileResult, OssResultBody, OssVulnerability, convertSeverity, isResultCliError } from '../../ossResult'; +import { OssFileResult, OssResultBody, OssVulnerability, convertIssue, isResultCliError } from '../../ossResult'; import { OssServiceLanguageServer } from '../../ossServiceLanguageServer'; import { ImportedModule, ModuleVulnerabilityCount, SeverityCounts } from './importedModule'; @@ -190,29 +190,7 @@ export class ModuleVulnerabilityCountProviderLS { resultCache.set(issue.filePath, res); } - const tempVuln: OssVulnerability = { - id: issue.id, - license: issue.additionalData.license, - identifiers: issue.additionalData.identifiers, - title: issue.title, - description: issue.additionalData.description, - language: issue.additionalData.language, - packageManager: issue.additionalData.packageManager, - packageName: issue.additionalData.packageName, - severity: convertSeverity(issue.severity), - name: issue.additionalData.name, - version: issue.additionalData.version, - exploit: issue.additionalData.exploit, - - CVSSv3: issue.additionalData.CVSSv3, - cvssScore: issue.additionalData.cvssScore, - - fixedIn: issue.additionalData.fixedIn, - from: issue.additionalData.from, - upgradePath: issue.additionalData.upgradePath, - isPatchable: issue.additionalData.isPatchable, - isUpgradable: issue.additionalData.isUpgradable, - }; + const tempVuln = convertIssue(issue); res.vulnerabilities.push(tempVuln); } } diff --git a/src/test/unit/common/commands/commandController.test.ts b/src/test/unit/common/commands/commandController.test.ts index f0b8ef8c6..c77e022d0 100644 --- a/src/test/unit/common/commands/commandController.test.ts +++ b/src/test/unit/common/commands/commandController.test.ts @@ -5,15 +5,15 @@ import { ScanModeService } from '../../../../snyk/base/services/scanModeService' import { IAnalytics } from '../../../../snyk/common/analytics/itly'; import { CommandController } from '../../../../snyk/common/commands/commandController'; import { COMMAND_DEBOUNCE_INTERVAL } from '../../../../snyk/common/constants/general'; +import { CodeIssueData, IacIssueData } from '../../../../snyk/common/languageServer/types'; import { IOpenerService } from '../../../../snyk/common/services/openerService'; +import { IProductService } from '../../../../snyk/common/services/productService'; import { IVSCodeCommands } from '../../../../snyk/common/vscode/commands'; import { IVSCodeWorkspace } from '../../../../snyk/common/vscode/workspace'; -import { OssService } from '../../../../snyk/snykOss/services/ossService'; +import { OssServiceLanguageServer } from '../../../../snyk/snykOss/ossServiceLanguageServer'; import { LanguageServerMock } from '../../mocks/languageServer.mock'; import { LoggerMock } from '../../mocks/logger.mock'; import { windowMock } from '../../mocks/window.mock'; -import { IProductService } from '../../../../snyk/common/services/productService'; -import { CodeIssueData, IacIssueData } from '../../../../snyk/common/languageServer/types'; suite('CommandController', () => { const sleep = util.promisify(setTimeout); @@ -26,7 +26,7 @@ suite('CommandController', () => { {} as IAuthenticationService, {} as IProductService, {} as IProductService, - {} as OssService, + {} as OssServiceLanguageServer, {} as ScanModeService, {} as IVSCodeWorkspace, {} as IVSCodeCommands, diff --git a/src/test/unit/snykOss/ossServiceLanguageServer.test.ts b/src/test/unit/snykOss/ossServiceLanguageServer.test.ts index 5634f242a..b1e997c6d 100644 --- a/src/test/unit/snykOss/ossServiceLanguageServer.test.ts +++ b/src/test/unit/snykOss/ossServiceLanguageServer.test.ts @@ -33,10 +33,6 @@ suite('OSS Service', () => { {} as ExtensionContext, {} as IConfiguration, {} as OssDetailPanelProvider, - {} as ICodeActionAdapter, - { - getQuickFix: sinon.fake(), - } as ICodeActionKindAdapter, viewManagerService, { getWorkspaceFolders: () => [''], diff --git a/src/test/unit/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.test.ts b/src/test/unit/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.test.ts index 5a444dceb..b5d547667 100644 --- a/src/test/unit/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.test.ts +++ b/src/test/unit/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.test.ts @@ -3,7 +3,6 @@ import { EMPTY } from 'rxjs'; import sinon from 'sinon'; import { IAnalytics } from '../../../../../snyk/common/analytics/itly'; import { IConfiguration } from '../../../../../snyk/common/configuration/configuration'; -import { ICodeActionKindAdapter } from '../../../../../snyk/common/vscode/codeAction'; import { ILanguageClientAdapter } from '../../../../../snyk/common/vscode/languageClient'; import { IVSCodeLanguages } from '../../../../../snyk/common/vscode/languages'; import { ITextDocumentAdapter } from '../../../../../snyk/common/vscode/textdocument'; @@ -50,9 +49,6 @@ suite('OSS VulnerabilityCountService', () => { ); const editorDecorator = new EditorDecorator(window, languages, {} as IThemeColorAdapter); - const codeActionProvider = { - getQuickFix: sinon.fake(), - } as ICodeActionKindAdapter; const analytics = {} as IAnalytics; const configuration = {} as IConfiguration; @@ -64,7 +60,6 @@ suite('OSS VulnerabilityCountService', () => { ossService, logger, editorDecorator, - codeActionProvider, analytics, configuration, ); From 05cc6faeba1be2ecff88639f7d4cc95bb1c3745c Mon Sep 17 00:00:00 2001 From: JSON Date: Mon, 13 Nov 2023 11:57:42 +0000 Subject: [PATCH 06/16] chore: cleanup redundant files [HEAD-1020] (#390) --- package.json | 6 - src/snyk/base/modules/baseSnykModule.ts | 10 +- src/snyk/base/modules/interfaces.ts | 1 - src/snyk/base/modules/snykLib.ts | 34 +- src/snyk/common/commands/commandController.ts | 4 +- src/snyk/common/commands/types.ts | 12 +- src/snyk/common/constants/general.ts | 1 - src/snyk/common/constants/views.ts | 2 - src/snyk/common/services/learnService.ts | 23 -- src/snyk/extension.ts | 72 +---- src/snyk/snykOss/{ => constants}/messages.ts | 2 +- src/snyk/snykOss/editor/editorDecorator.ts | 6 +- src/snyk/snykOss/interfaces.ts | 105 ++++++- src/snyk/snykOss/messages/error.ts | 3 - src/snyk/snykOss/messages/test.ts | 9 - src/snyk/snykOss/messages/treeView.ts | 11 - .../snykOss/messages/vulnerabilityCount.ts | 19 -- src/snyk/snykOss/ossResult.ts | 103 ------ ...ServiceLanguageServer.ts => ossService.ts} | 2 +- .../providers/ossDetailPanelProvider.ts | 13 +- .../providers/ossVulnerabilityTreeProvider.ts | 2 +- .../vulnerabilityCountProvider.ts} | 28 +- src/snyk/snykOss/services/ossService.ts | 224 ------------- ...eLS.ts => ossVulnerabilityCountService.ts} | 16 +- .../vulnerabilityCountEmitter.ts | 2 +- .../views/ossVulnerabilityTreeProvider.ts | 230 -------------- .../ossSuggestionWebviewProvider.ts | 210 ------------- .../suggestion/ossSuggestionWebviewScript.ts | 293 ------------------ src/snyk/snykOss/watchers/dailyScanJob.ts | 19 -- .../snykOss/watchers/manifestFileWatcher.ts | 10 +- .../common/commands/commandController.test.ts | 4 +- .../unit/common/services/learnService.test.ts | 18 +- ...guageServer.test.ts => ossService.test.ts} | 5 +- .../vulnerabilityCountProvider.test.ts | 26 +- .../unit/snykOss/services/ossService.test.ts | 197 ------------ .../ossVulnerabilityCountService.test.ts | 20 +- .../services/watchers/dailyScanJob.test.ts | 55 ---- 37 files changed, 196 insertions(+), 1601 deletions(-) rename src/snyk/snykOss/{ => constants}/messages.ts (96%) delete mode 100644 src/snyk/snykOss/messages/error.ts delete mode 100644 src/snyk/snykOss/messages/test.ts delete mode 100644 src/snyk/snykOss/messages/treeView.ts delete mode 100644 src/snyk/snykOss/messages/vulnerabilityCount.ts delete mode 100644 src/snyk/snykOss/ossResult.ts rename src/snyk/snykOss/{ossServiceLanguageServer.ts => ossService.ts} (95%) rename src/snyk/snykOss/{services/vulnerabilityCount/vulnerabilityCountProviderLS.ts => providers/vulnerabilityCountProvider.ts} (89%) delete mode 100644 src/snyk/snykOss/services/ossService.ts rename src/snyk/snykOss/services/vulnerabilityCount/{ossVulnerabilityCountServiceLS.ts => ossVulnerabilityCountService.ts} (94%) rename src/snyk/snykOss/{ => services/vulnerabilityCount}/vulnerabilityCountEmitter.ts (88%) delete mode 100644 src/snyk/snykOss/views/ossVulnerabilityTreeProvider.ts delete mode 100644 src/snyk/snykOss/views/suggestion/ossSuggestionWebviewProvider.ts delete mode 100644 src/snyk/snykOss/views/suggestion/ossSuggestionWebviewScript.ts delete mode 100644 src/snyk/snykOss/watchers/dailyScanJob.ts rename src/test/unit/snykOss/{ossServiceLanguageServer.test.ts => ossService.test.ts} (91%) rename src/test/unit/snykOss/{services/vulnerabilityCount => providers}/vulnerabilityCountProvider.test.ts (88%) delete mode 100644 src/test/unit/snykOss/services/ossService.test.ts delete mode 100644 src/test/unit/snykOss/services/watchers/dailyScanJob.test.ts diff --git a/package.json b/package.json index 25ab0537d..fb321914a 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,6 @@ "activationEvents": [ "onWebviewPanel:snyk.views.suggestion.code", "onWebviewPanel:snyk.views.suggestion.oss", - "onWebviewPanel:snyk.views.suggestion.oss.languageServer", "*" ], "main": "./out/extension.js", @@ -236,11 +235,6 @@ "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", diff --git a/src/snyk/base/modules/baseSnykModule.ts b/src/snyk/base/modules/baseSnykModule.ts index 5681dafdf..14ed435b2 100644 --- a/src/snyk/base/modules/baseSnykModule.ts +++ b/src/snyk/base/modules/baseSnykModule.ts @@ -23,9 +23,8 @@ import { IMarkdownStringAdapter, MarkdownStringAdapter } from '../../common/vsco import { IWatcher } from '../../common/watchers/interfaces'; import { ICodeSettings } from '../../snykCode/codeSettings'; import SnykEditorsWatcher from '../../snykCode/watchers/editorsWatcher'; -import { OssServiceLanguageServer } from '../../snykOss/ossServiceLanguageServer'; -import { OssService } from '../../snykOss/services/ossService'; -import { OssVulnerabilityCountServiceLS } from '../../snykOss/services/vulnerabilityCount/ossVulnerabilityCountServiceLS'; +import { OssService } from '../../snykOss/ossService'; +import { OssVulnerabilityCountService } from '../../snykOss/services/vulnerabilityCount/ossVulnerabilityCountService'; import { IAuthenticationService } from '../services/authenticationService'; import { ScanModeService } from '../services/scanModeService'; import SnykStatusBarItem, { IStatusBarItem } from '../statusBarItem/statusBarItem'; @@ -46,11 +45,10 @@ export default abstract class BaseSnykModule implements IBaseSnykModule { protected authService: IAuthenticationService; protected downloadService: DownloadService; protected ossService?: OssService; - protected ossServiceLanguageServer?: OssServiceLanguageServer; protected advisorService?: AdvisorProvider; protected commandController: CommandController; protected scanModeService: ScanModeService; - protected ossVulnerabilityCountServiceLanguageServer: OssVulnerabilityCountServiceLS; + protected ossVulnerabilityCountService: OssVulnerabilityCountService; protected advisorScoreDisposable: AdvisorService; protected languageServer: ILanguageServer; @@ -87,6 +85,4 @@ export default abstract class BaseSnykModule implements IBaseSnykModule { } abstract runScan(): Promise; - - abstract runOssScan(): Promise; } diff --git a/src/snyk/base/modules/interfaces.ts b/src/snyk/base/modules/interfaces.ts index b1748810e..e43446fbb 100644 --- a/src/snyk/base/modules/interfaces.ts +++ b/src/snyk/base/modules/interfaces.ts @@ -17,7 +17,6 @@ export interface IBaseSnykModule { // Abstract methods runScan(): Promise; - runOssScan(manual?: boolean): Promise; } export interface ISnykLib { diff --git a/src/snyk/base/modules/snykLib.ts b/src/snyk/base/modules/snykLib.ts index 070350d5d..640b8e2fc 100644 --- a/src/snyk/base/modules/snykLib.ts +++ b/src/snyk/base/modules/snykLib.ts @@ -1,9 +1,7 @@ import * as _ from 'lodash'; -import { firstValueFrom } from 'rxjs'; -import { CliError } from '../../cli/services/cliService'; import { SupportedAnalysisProperties } from '../../common/analytics/itly'; import { configuration } from '../../common/configuration/instance'; -import { DEFAULT_SCAN_DEBOUNCE_INTERVAL, IDE_NAME, OSS_SCAN_DEBOUNCE_INTERVAL } from '../../common/constants/general'; +import { DEFAULT_SCAN_DEBOUNCE_INTERVAL, IDE_NAME } from '../../common/constants/general'; import { SNYK_CONTEXT } from '../../common/constants/views'; import { ErrorHandler } from '../../common/error/errorHandler'; import { Logger } from '../../common/logger/logger'; @@ -23,7 +21,6 @@ export default class SnykLib extends BaseSnykModule implements ISnykLib { return; } - // Only starts OSS scan. Code & IaC scans are managed by LS Logger.info('Starting full scan'); await this.contextService.setContext(SNYK_CONTEXT.AUTHENTICATING, false); @@ -39,7 +36,6 @@ export default class SnykLib extends BaseSnykModule implements ISnykLib { const workspacePaths = vsCodeWorkspace.getWorkspaceFolders(); if (workspacePaths.length) { this.logFullAnalysisIsTriggered(manual); - void this.startOssAnalysis(manual, false); } } catch (err) { await ErrorHandler.handleGlobal(err, Logger, this.contextService, this.loadingBadge); @@ -48,11 +44,8 @@ export default class SnykLib extends BaseSnykModule implements ISnykLib { // This function is called by commands, error handlers, etc. // We should avoid having duplicate parallel executions. - // Only starts OSS scan. Code & IaC scans are managed by LS public runScan = _.debounce(this.runFullScan_.bind(this), DEFAULT_SCAN_DEBOUNCE_INTERVAL, { leading: true }); - public runOssScan = _.debounce(this.startOssAnalysis.bind(this), OSS_SCAN_DEBOUNCE_INTERVAL, { leading: true }); - async enableCode(): Promise { Logger.info('Enabling Snyk Code'); const wasEnabled = await this.codeSettings.enable(); @@ -66,12 +59,6 @@ export default class SnykLib extends BaseSnykModule implements ISnykLib { } } - onDidChangeOssTreeVisibility(visible: boolean): void { - if (this.ossService) { - this.ossService.setVulnerabilityTreeVisibility(visible); - } - } - async checkAdvancedMode(): Promise { await this.contextService.setContext(SNYK_CONTEXT.ADVANCED, configuration.shouldShowAdvancedView); } @@ -81,25 +68,6 @@ export default class SnykLib extends BaseSnykModule implements ISnykLib { await this.contextService.setContext(SNYK_CONTEXT.WORKSPACE_FOUND, workspaceFound); } - private async startOssAnalysis(manual = false, reportTriggeredEvent = true): Promise { - if (!configuration.getFeaturesConfiguration()?.ossEnabled) return; - if (!this.ossService) throw new Error('OSS service is not initialized.'); - - // wait until Snyk Language Server is downloaded - await firstValueFrom(this.downloadService.downloadReady$); - - try { - const result = await this.ossService.test(manual, reportTriggeredEvent); - - if (result instanceof CliError || !result) { - return; - } - } catch (err) { - // catch unhandled error cases by reporting test failure - this.ossService.finalizeTest(new CliError(err)); - } - } - private isSnykCodeAutoscanSuspended(manual: boolean) { return !manual && !this.scanModeService.isCodeAutoScanAllowed(); } diff --git a/src/snyk/common/commands/commandController.ts b/src/snyk/common/commands/commandController.ts index f3c2642c1..60d9c1b24 100644 --- a/src/snyk/common/commands/commandController.ts +++ b/src/snyk/common/commands/commandController.ts @@ -7,7 +7,7 @@ import { createDCIgnore } from '../../snykCode/utils/ignoreFileUtils'; import { IssueUtils } from '../../snykCode/utils/issueUtils'; import { CodeIssueCommandArg } from '../../snykCode/views/interfaces'; import { IacIssueCommandArg } from '../../snykIac/views/interfaces'; -import { OssServiceLanguageServer } from '../../snykOss/ossServiceLanguageServer'; +import { OssService } from '../../snykOss/ossService'; import { IAnalytics } from '../analytics/itly'; import { SNYK_INITIATE_LOGIN_COMMAND, @@ -39,7 +39,7 @@ export class CommandController { private authService: IAuthenticationService, private snykCode: IProductService, private iacService: IProductService, - private ossService: OssServiceLanguageServer, + private ossService: OssService, private scanModeService: ScanModeService, private workspace: IVSCodeWorkspace, private commands: IVSCodeCommands, diff --git a/src/snyk/common/commands/types.ts b/src/snyk/common/commands/types.ts index e2638c20f..7e59c0206 100644 --- a/src/snyk/common/commands/types.ts +++ b/src/snyk/common/commands/types.ts @@ -2,7 +2,6 @@ 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'; export enum OpenCommandIssueType { @@ -12,20 +11,13 @@ export enum OpenCommandIssueType { } export type OpenIssueCommandArg = { - issue: CodeIssueCommandArg | OssIssueCommandArg | IacIssueCommandArg | OssIssueCommandArgLanguageServer; + issue: CodeIssueCommandArg | IacIssueCommandArg | OssIssueCommandArgLanguageServer; issueType: OpenCommandIssueType; }; export const isCodeIssue = ( - _issue: completeFileSuggestionType | Issue | OssIssueCommandArg | OssIssueCommandArgLanguageServer, + _issue: completeFileSuggestionType | Issue | OssIssueCommandArgLanguageServer, issueType: OpenCommandIssueType, ): _issue is Issue => { return issueType === OpenCommandIssueType.CodeIssue; }; - -export const isOssIssue = ( - _issue: completeFileSuggestionType | Issue | OssIssueCommandArg | OssIssueCommandArgLanguageServer, - issueType: OpenCommandIssueType, -): _issue is OssIssueCommandArg => { - return issueType === OpenCommandIssueType.OssVulnerability; -}; diff --git a/src/snyk/common/constants/general.ts b/src/snyk/common/constants/general.ts index d6e685c4e..17ac8fe7a 100644 --- a/src/snyk/common/constants/general.ts +++ b/src/snyk/common/constants/general.ts @@ -10,7 +10,6 @@ export const IDE_NAME_SHORT = 'vscode'; export const COMMAND_DEBOUNCE_INTERVAL = 200; // 200 milliseconds export const DEFAULT_SCAN_DEBOUNCE_INTERVAL = 1000; // 1 second export const DEFAULT_LS_DEBOUNCE_INTERVAL = 1000; // 1 second -export const OSS_SCAN_DEBOUNCE_INTERVAL = 10000; // 10 seconds export const EXECUTION_THROTTLING_INTERVAL = 1000 * 10; // * 60 * 30; // 30 minutes export const EXECUTION_PAUSE_INTERVAL = 1000 * 60 * 30; // 30 minutes export const REFRESH_VIEW_DEBOUNCE_INTERVAL = 200; // 200 milliseconds diff --git a/src/snyk/common/constants/views.ts b/src/snyk/common/constants/views.ts index 1b4317ba5..6616a1ef5 100644 --- a/src/snyk/common/constants/views.ts +++ b/src/snyk/common/constants/views.ts @@ -4,11 +4,9 @@ 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'; diff --git a/src/snyk/common/services/learnService.ts b/src/snyk/common/services/learnService.ts index df3bd9964..fde6fd070 100644 --- a/src/snyk/common/services/learnService.ts +++ b/src/snyk/common/services/learnService.ts @@ -1,4 +1,3 @@ -import { OssIssueCommandArg } from '../../snykOss/views/ossVulnerabilityTreeProvider'; import { SNYK_GET_LESSON_COMMAND } from '../constants/commands'; import { CodeIssueData, Issue } from '../languageServer/types'; import { IVSCodeCommands } from '../vscode/commands'; @@ -11,28 +10,6 @@ export type Lesson = { export class LearnService { constructor(private commandExecutor: IVSCodeCommands) {} - async getOssLesson(vulnerability: OssIssueCommandArg): Promise { - const cwe = vulnerability.identifiers?.CWE; - let cweElement = ''; - if (cwe && cwe.length > 0) { - cweElement = cwe[0]; - } - - const cve = vulnerability.identifiers?.CWE; - let cveElement = ''; - if (cve && cve.length > 0) { - cveElement = cve[0]; - } - return this.commandExecutor.executeCommand( - SNYK_GET_LESSON_COMMAND, - vulnerability.id, - vulnerability.packageManager, - cweElement, - cveElement, - 4, - ); - } - async getCodeLesson(issue: Issue): Promise { const ruleSplit = issue.additionalData.ruleId.split('/'); const rule = ruleSplit[ruleSplit.length - 1]; diff --git a/src/snyk/extension.ts b/src/snyk/extension.ts index 8bc849a01..c3608e68f 100644 --- a/src/snyk/extension.ts +++ b/src/snyk/extension.ts @@ -36,7 +36,6 @@ 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, } from './common/constants/views'; @@ -75,15 +74,11 @@ import { IacService } from './snykIac/iacService'; 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 { OssService } from './snykOss/ossService'; import { OssDetailPanelProvider } from './snykOss/providers/ossDetailPanelProvider'; import OssIssueTreeProvider from './snykOss/providers/ossVulnerabilityTreeProvider'; -import { OssService } from './snykOss/services/ossService'; -import { OssVulnerabilityCountServiceLS } from './snykOss/services/vulnerabilityCount/ossVulnerabilityCountServiceLS'; -import { ModuleVulnerabilityCountProviderLS } from './snykOss/services/vulnerabilityCount/vulnerabilityCountProviderLS'; -import { OssVulnerabilityTreeProvider } from './snykOss/views/ossVulnerabilityTreeProvider'; -import { OssSuggestionWebviewProvider } from './snykOss/views/suggestion/ossSuggestionWebviewProvider'; -import { DailyScanJob } from './snykOss/watchers/dailyScanJob'; +import { ModuleVulnerabilityCountProvider } from './snykOss/providers/vulnerabilityCountProvider'; +import { OssVulnerabilityCountService } from './snykOss/services/vulnerabilityCount/ossVulnerabilityCountService'; class SnykExtension extends SnykLib implements IExtension { public async activate(vscodeContext: vscode.ExtensionContext): Promise { @@ -212,21 +207,6 @@ class SnykExtension extends SnykLib implements IExtension { this.analytics, ); - this.ossService = new OssService( - this.context, - Logger, - configuration, - new OssSuggestionWebviewProvider(this.context, vsCodeWindow, Logger, this.learnService), - vsCodeWorkspace, - this.viewManagerService, - this.downloadService, - new DailyScanJob(this), - this.notificationService, - this.analytics, - this.languageServer, - this.workspaceTrust, - ); - const ossSuggestionProvider = new OssDetailPanelProvider( vsCodeWindow, extensionContext, @@ -235,7 +215,7 @@ class SnykExtension extends SnykLib implements IExtension { vsCodeWorkspace, ); - this.ossServiceLanguageServer = new OssServiceLanguageServer( + this.ossService = new OssService( extensionContext, configuration, ossSuggestionProvider, @@ -276,7 +256,7 @@ class SnykExtension extends SnykLib implements IExtension { this.authService, this.snykCode, this.iacService, - this.ossServiceLanguageServer, + this.ossService, this.scanModeService, vsCodeWorkspace, vsCodeCommands, @@ -317,17 +297,7 @@ class SnykExtension extends SnykLib implements IExtension { codeQualityTree, ); - const ossVulnerabilityProvider = new OssVulnerabilityTreeProvider( - this.viewManagerService, - this.contextService, - this.ossService, - configuration, - ); - - vscodeContext.subscriptions.push( - vscode.window.registerTreeDataProvider(SNYK_VIEW_ANALYSIS_OSS, ossVulnerabilityProvider), - vscode.window.registerTreeDataProvider(SNYK_VIEW_SUPPORT, new SupportProvider()), - ); + vscodeContext.subscriptions.push(vscode.window.registerTreeDataProvider(SNYK_VIEW_SUPPORT, new SupportProvider())); const welcomeTree = vscode.window.createTreeView(SNYK_VIEW_WELCOME, { treeDataProvider: new EmptyTreeDataProvider(), @@ -336,12 +306,7 @@ class SnykExtension extends SnykLib implements IExtension { treeDataProvider: new EmptyTreeDataProvider(), }); - const ossTree = vscode.window.createTreeView(SNYK_VIEW_ANALYSIS_OSS, { - treeDataProvider: ossVulnerabilityProvider, - }); - vscodeContext.subscriptions.push( - ossTree.onDidChangeVisibility(e => this.onDidChangeOssTreeVisibility(e.visible)), welcomeTree.onDidChangeVisibility(e => this.onDidChangeWelcomeViewVisibility(e.visible)), codeEnablementTree, ); @@ -349,17 +314,17 @@ class SnykExtension extends SnykLib implements IExtension { const ossIssueProvider = new OssIssueTreeProvider( this.viewManagerService, this.contextService, - this.ossServiceLanguageServer, + this.ossService, configuration, vsCodeLanguages, ); - const ossSecurityTree = vscode.window.createTreeView(SNYK_VIEW_ANALYSIS_OSS_LANGUAGE_SERVER, { + const ossSecurityTree = vscode.window.createTreeView(SNYK_VIEW_ANALYSIS_OSS, { treeDataProvider: ossIssueProvider, }); vscodeContext.subscriptions.push( - vscode.window.registerTreeDataProvider(SNYK_VIEW_ANALYSIS_OSS_LANGUAGE_SERVER, ossIssueProvider), + vscode.window.registerTreeDataProvider(SNYK_VIEW_ANALYSIS_OSS, ossIssueProvider), ossSecurityTree, ); @@ -395,10 +360,8 @@ class SnykExtension extends SnykLib implements IExtension { this.editorsWatcher.activate(this); this.configurationWatcher.activate(this); this.snykCode.activateWebviewProviders(); - this.ossService.activateSuggestionProvider(); - this.ossService.activateManifestFileWatcher(this); this.iacService.activateWebviewProviders(); - this.ossServiceLanguageServer.activateWebviewProviders(); + this.ossService.activateWebviewProviders(); // noinspection ES6MissingAwait void this.notificationService.init(); @@ -412,23 +375,23 @@ class SnykExtension extends SnykLib implements IExtension { this.initDependencyDownload(); - this.ossVulnerabilityCountServiceLanguageServer = new OssVulnerabilityCountServiceLS( + this.ossVulnerabilityCountService = new OssVulnerabilityCountService( vsCodeWorkspace, vsCodeWindow, vsCodeLanguages, - new ModuleVulnerabilityCountProviderLS( - this.ossServiceLanguageServer, + new ModuleVulnerabilityCountProvider( + this.ossService, languageClientAdapter, new UriAdapter(), new TextDocumentAdapter(), ), - this.ossServiceLanguageServer, + this.ossService, Logger, new EditorDecorator(vsCodeWindow, vsCodeLanguages, new ThemeColorAdapter()), this.analytics, configuration, ); - this.ossVulnerabilityCountServiceLanguageServer.activate(); + this.ossVulnerabilityCountService.activate(); this.advisorScoreDisposable = new AdvisorService( vsCodeWindow, @@ -458,7 +421,7 @@ class SnykExtension extends SnykLib implements IExtension { } public async deactivate(): Promise { - this.ossVulnerabilityCountServiceLanguageServer.dispose(); + this.ossVulnerabilityCountService.dispose(); await this.languageServer.stop(); await this.analytics.flush(); await ErrorReporter.flush(); @@ -481,7 +444,6 @@ class SnykExtension extends SnykLib implements IExtension { private initDependencyDownload(): DownloadService { this.downloadService.downloadOrUpdate().catch(err => { Logger.error(`${messages.lsDownloadFailed} ${ErrorHandler.stringifyError(err)}`); - this.ossService?.handleLsDownloadFailure(); }); return this.downloadService; @@ -502,8 +464,6 @@ class SnykExtension extends SnykLib implements IExtension { ), vscode.commands.registerCommand(SNYK_START_COMMAND, async () => { await vscode.commands.executeCommand(SNYK_WORKSPACE_SCAN_COMMAND); - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - await this.commandController.executeCommand(SNYK_START_COMMAND, () => this.runScan(true)); // todo: remove once OSS scans replaced with LS }), vscode.commands.registerCommand(SNYK_SETTINGS_COMMAND, () => this.commandController.openSettings()), vscode.commands.registerCommand(SNYK_DCIGNORE_COMMAND, (custom: boolean, path?: string) => diff --git a/src/snyk/snykOss/messages.ts b/src/snyk/snykOss/constants/messages.ts similarity index 96% rename from src/snyk/snykOss/messages.ts rename to src/snyk/snykOss/constants/messages.ts index 1cbbec292..2537ade08 100644 --- a/src/snyk/snykOss/messages.ts +++ b/src/snyk/snykOss/constants/messages.ts @@ -1,4 +1,4 @@ -import { ModuleVulnerabilityCount } from './services/vulnerabilityCount/importedModule'; +import { ModuleVulnerabilityCount } from '../services/vulnerabilityCount/importedModule'; export const messages = { analysis: { diff --git a/src/snyk/snykOss/editor/editorDecorator.ts b/src/snyk/snykOss/editor/editorDecorator.ts index 398a5d888..0f5b26be8 100644 --- a/src/snyk/snykOss/editor/editorDecorator.ts +++ b/src/snyk/snykOss/editor/editorDecorator.ts @@ -4,7 +4,7 @@ import { IVSCodeLanguages } from '../../common/vscode/languages'; import { IThemeColorAdapter } from '../../common/vscode/theme'; import { TextEditorDecorationType } from '../../common/vscode/types'; import { IVSCodeWindow } from '../../common/vscode/window'; -import { messages } from '../messages/vulnerabilityCount'; +import { messages } from '../constants/messages'; import { ImportedModule, ModuleVulnerabilityCount } from '../services/vulnerabilityCount/importedModule'; export class EditorDecorator { @@ -58,7 +58,7 @@ export class EditorDecorator { module.line - 1, this.editorLastCharacterIndex, ), - renderOptions: getRenderOptions(messages.fetchingVulnerabilities, this.themeColorAdapter), + renderOptions: getRenderOptions(messages.vulnerabilityCount.fetchingVulnerabilities, this.themeColorAdapter), }; } @@ -92,7 +92,7 @@ export class EditorDecorator { this.fileDecorationMap.set(filePath, lineDecorations); // set map, if no decoration was set before } - const text = vulnerabilityCount.count ? messages.decoratorMessage(vulnerabilityCount.count) : ''; + const text = vulnerabilityCount.count ? messages.vulnerabilityCount.decoratorMessage(vulnerabilityCount.count) : ''; lineDecorations[vulnerabilityCount.line] = { range: this.languages.createRange( diff --git a/src/snyk/snykOss/interfaces.ts b/src/snyk/snykOss/interfaces.ts index c206b56a1..907ba23a0 100644 --- a/src/snyk/snykOss/interfaces.ts +++ b/src/snyk/snykOss/interfaces.ts @@ -1,6 +1,7 @@ -import * as vscode from 'vscode'; -import { Issue, OssIssueData } from '../common/languageServer/types'; +import _ from 'lodash'; +import { Issue, IssueSeverity, OssIssueData } from '../common/languageServer/types'; import { IWebViewProvider } from '../common/views/webviewProvider'; +import { CliError } from '../cli/services/cliService'; export interface IOssSuggestionWebviewProvider extends IWebViewProvider> { openIssueId: string | undefined; @@ -10,3 +11,103 @@ export type OssIssueCommandArgLanguageServer = Issue & { matchingIdVulnerabilities: Issue[]; overviewHtml: string; }; + +export type OssResult = OssFileResult[] | OssFileResult; + +export type OssFileResult = OssResultBody | CliError; + +export type OssResultBody = { + vulnerabilities: OssVulnerability[]; + projectName: string; + displayTargetFile: string; + packageManager: string; + path: string; +}; + +export type OssVulnerability = { + id: string; + license?: string; + identifiers?: Identifiers; + title: string; + description: string; + language: string; + packageManager: string; + packageName: string; + severity: OssSeverity; + name: string; + version: string; + exploit?: string; + + CVSSv3?: string; + cvssScore?: string; + + fixedIn?: Array; + from: Array; + upgradePath: Array; + isPatchable: boolean; + isUpgradable: boolean; +}; + +export type Identifiers = { + CWE: string[]; + CVE: string[]; +}; + +export enum OssSeverity { + Low = 'low', + Medium = 'medium', + High = 'high', + Critical = 'critical', +} + +export function capitalizeOssSeverity(ossSeverity: OssSeverity): Capitalize { + return _.capitalize(ossSeverity) as Capitalize; +} + +export function isResultCliError(fileResult: OssFileResult): fileResult is CliError { + return (fileResult as CliError).error !== undefined; +} + +export function convertSeverity(severity: IssueSeverity): OssSeverity { + switch (severity) { + case IssueSeverity.Low: + return OssSeverity.Low; + case IssueSeverity.Medium: + return OssSeverity.Medium; + case IssueSeverity.High: + return OssSeverity.High; + default: + return OssSeverity.Critical; + } +} + +export function convertIssue(issue: Issue): OssVulnerability { + const tempVuln: OssVulnerability = { + id: issue.id, + identifiers: issue.additionalData.identifiers, + title: issue.title, + description: issue.additionalData.description, + language: issue.additionalData.language, + packageManager: issue.additionalData.packageManager, + packageName: issue.additionalData.packageName, + severity: convertSeverity(issue.severity), + name: issue.additionalData.name, + version: issue.additionalData.version, + exploit: issue.additionalData.exploit, + + CVSSv3: issue.additionalData.CVSSv3, + cvssScore: issue.additionalData.cvssScore, + + fixedIn: issue.additionalData.fixedIn === undefined ? [] : issue.additionalData.fixedIn, + from: issue.additionalData.from, + upgradePath: issue.additionalData.upgradePath, + isPatchable: issue.additionalData.isPatchable, + isUpgradable: issue.additionalData.isUpgradable, + }; + + if (issue.additionalData.license !== undefined) { + tempVuln.license = issue.additionalData.license; + } + + return tempVuln; +} diff --git a/src/snyk/snykOss/messages/error.ts b/src/snyk/snykOss/messages/error.ts deleted file mode 100644 index f8f215e17..000000000 --- a/src/snyk/snykOss/messages/error.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const messages = { - suggestionViewShowFailed: 'Failed to show Snyk OSS suggestion view', -}; diff --git a/src/snyk/snykOss/messages/test.ts b/src/snyk/snykOss/messages/test.ts deleted file mode 100644 index d41d6d05e..000000000 --- a/src/snyk/snykOss/messages/test.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const messages = { - 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}".`, -}; diff --git a/src/snyk/snykOss/messages/treeView.ts b/src/snyk/snykOss/messages/treeView.ts deleted file mode 100644 index e01a041bc..000000000 --- a/src/snyk/snykOss/messages/treeView.ts +++ /dev/null @@ -1,11 +0,0 @@ -export const messages = { - 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`, -}; diff --git a/src/snyk/snykOss/messages/vulnerabilityCount.ts b/src/snyk/snykOss/messages/vulnerabilityCount.ts deleted file mode 100644 index f328ebd22..000000000 --- a/src/snyk/snykOss/messages/vulnerabilityCount.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ModuleVulnerabilityCount } from '../services/vulnerabilityCount/importedModule'; - -export const messages = { - fetchingVulnerabilities: 'Fetching vulnerabilities...', - vulnerability: 'vulnerability', - vulnerabilities: 'vulnerabilities', - showMostSevereVulnerability: 'Show the most severe vulnerability (Snyk)', - - decoratorMessage: (vulnerabilityCount: string): string => { - const vulnerabilityCountNumber = Number.parseInt(vulnerabilityCount, 10); - if (isNaN(vulnerabilityCountNumber)) { - return vulnerabilityCount; - } - return `${vulnerabilityCountNumber} ${vulnerabilityCountNumber > 1 ? 'vulnerabilities' : 'vulnerability'}`; - }, - - diagnosticMessagePrefix: (module: ModuleVulnerabilityCount): string => - `Dependency ${module.name}${module.version ? `@${module.version}` : ''} has `, -}; diff --git a/src/snyk/snykOss/ossResult.ts b/src/snyk/snykOss/ossResult.ts deleted file mode 100644 index aabe8b873..000000000 --- a/src/snyk/snykOss/ossResult.ts +++ /dev/null @@ -1,103 +0,0 @@ -import _ from 'lodash'; -import { CliError } from '../cli/services/cliService'; -import { Issue, IssueSeverity, OssIssueData } from '../common/languageServer/types'; - -export type OssResult = OssFileResult[] | OssFileResult; - -export type OssFileResult = OssResultBody | CliError; - -export type OssResultBody = { - vulnerabilities: OssVulnerability[]; - projectName: string; - displayTargetFile: string; - packageManager: string; - path: string; -}; - -export type OssVulnerability = { - id: string; - license?: string; - identifiers?: Identifiers; - title: string; - description: string; - language: string; - packageManager: string; - packageName: string; - severity: OssSeverity; - name: string; - version: string; - exploit?: string; - - CVSSv3?: string; - cvssScore?: string; - - fixedIn?: Array; - from: Array; - upgradePath: Array; - isPatchable: boolean; - isUpgradable: boolean; -}; - -export type Identifiers = { - CWE: string[]; - CVE: string[]; -}; - -export enum OssSeverity { - Low = 'low', - Medium = 'medium', - High = 'high', - Critical = 'critical', -} - -export function capitalizeOssSeverity(ossSeverity: OssSeverity): Capitalize { - return _.capitalize(ossSeverity) as Capitalize; -} - -export function isResultCliError(fileResult: OssFileResult): fileResult is CliError { - return (fileResult as CliError).error !== undefined; -} - -export function convertSeverity(severity: IssueSeverity): OssSeverity { - switch (severity) { - case IssueSeverity.Low: - return OssSeverity.Low; - case IssueSeverity.Medium: - return OssSeverity.Medium; - case IssueSeverity.High: - return OssSeverity.High; - default: - return OssSeverity.Critical; - } -} - -export function convertIssue(issue: Issue): OssVulnerability { - const tempVuln: OssVulnerability = { - id: issue.id, - identifiers: issue.additionalData.identifiers, - title: issue.title, - description: issue.additionalData.description, - language: issue.additionalData.language, - packageManager: issue.additionalData.packageManager, - packageName: issue.additionalData.packageName, - severity: convertSeverity(issue.severity), - name: issue.additionalData.name, - version: issue.additionalData.version, - exploit: issue.additionalData.exploit, - - CVSSv3: issue.additionalData.CVSSv3, - cvssScore: issue.additionalData.cvssScore, - - fixedIn: issue.additionalData.fixedIn === undefined ? [] : issue.additionalData.fixedIn, - from: issue.additionalData.from, - upgradePath: issue.additionalData.upgradePath, - isPatchable: issue.additionalData.isPatchable, - isUpgradable: issue.additionalData.isUpgradable, - }; - - if (issue.additionalData.license !== undefined) { - tempVuln.license = issue.additionalData.license; - } - - return tempVuln; -} diff --git a/src/snyk/snykOss/ossServiceLanguageServer.ts b/src/snyk/snykOss/ossService.ts similarity index 95% rename from src/snyk/snykOss/ossServiceLanguageServer.ts rename to src/snyk/snykOss/ossService.ts index e1d5f0610..b2fa759bb 100644 --- a/src/snyk/snykOss/ossServiceLanguageServer.ts +++ b/src/snyk/snykOss/ossService.ts @@ -12,7 +12,7 @@ import { IVSCodeLanguages } from '../common/vscode/languages'; import { IVSCodeWorkspace } from '../common/vscode/workspace'; import { IOssSuggestionWebviewProvider } from './interfaces'; -export class OssServiceLanguageServer extends ProductService { +export class OssService extends ProductService { constructor( extensionContext: ExtensionContext, config: IConfiguration, diff --git a/src/snyk/snykOss/providers/ossDetailPanelProvider.ts b/src/snyk/snykOss/providers/ossDetailPanelProvider.ts index 75661b088..1b5946a06 100644 --- a/src/snyk/snykOss/providers/ossDetailPanelProvider.ts +++ b/src/snyk/snykOss/providers/ossDetailPanelProvider.ts @@ -1,5 +1,5 @@ import * as vscode from 'vscode'; -import { SNYK_VIEW_SUGGESTION_OSS_LANGUAGE_SERVER } from '../../common/constants/views'; +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'; @@ -10,7 +10,7 @@ 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 { messages } from '../constants/messages'; export class OssDetailPanelProvider extends WebviewProvider> @@ -36,10 +36,7 @@ export class OssDetailPanelProvider activate(): void { this.context.addDisposables( - this.window.registerWebviewPanelSerializer( - SNYK_VIEW_SUGGESTION_OSS_LANGUAGE_SERVER, - new WebviewPanelSerializer(this), - ), + this.window.registerWebviewPanelSerializer(SNYK_VIEW_SUGGESTION_OSS, new WebviewPanelSerializer(this)), ); } @@ -55,7 +52,7 @@ export class OssDetailPanelProvider this.panel.reveal(vscode.ViewColumn.Two, true); } else { this.panel = vscode.window.createWebviewPanel( - SNYK_VIEW_SUGGESTION_OSS_LANGUAGE_SERVER, + SNYK_VIEW_SUGGESTION_OSS, 'Snyk OSS Vulnerability', { viewColumn: vscode.ViewColumn.Two, @@ -103,7 +100,7 @@ export class OssDetailPanelProvider this.issue = issue; } catch (e) { - ErrorHandler.handle(e, this.logger, errorMessages.suggestionViewShowFailed); + ErrorHandler.handle(e, this.logger, messages.errors.suggestionViewShowFailed); } } diff --git a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts index d64997b34..f89fd927a 100644 --- a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts +++ b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts @@ -13,8 +13,8 @@ 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 { messages } from '../constants/messages'; import { OssIssueCommandArgLanguageServer } from '../interfaces'; -import { messages } from '../messages'; export default class OssIssueTreeProvider extends ProductIssueTreeProvider { constructor( diff --git a/src/snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountProviderLS.ts b/src/snyk/snykOss/providers/vulnerabilityCountProvider.ts similarity index 89% rename from src/snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountProviderLS.ts rename to src/snyk/snykOss/providers/vulnerabilityCountProvider.ts index 01729c876..29b3ab467 100644 --- a/src/snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountProviderLS.ts +++ b/src/snyk/snykOss/providers/vulnerabilityCountProvider.ts @@ -1,16 +1,20 @@ -import { CliError } from '../../../cli/services/cliService'; -import { Language } from '../../../common/types'; -import { ILanguageClientAdapter } from '../../../common/vscode/languageClient'; -import { ITextDocumentAdapter } from '../../../common/vscode/textdocument'; -import { InlineValueText, LSPTextDocument } from '../../../common/vscode/types'; -import { IUriAdapter } from '../../../common/vscode/uri'; -import { OssFileResult, OssResultBody, OssVulnerability, convertIssue, isResultCliError } from '../../ossResult'; -import { OssServiceLanguageServer } from '../../ossServiceLanguageServer'; -import { ImportedModule, ModuleVulnerabilityCount, SeverityCounts } from './importedModule'; - -export class ModuleVulnerabilityCountProviderLS { +import { CliError } from '../../cli/services/cliService'; +import { Language } from '../../common/types'; +import { ILanguageClientAdapter } from '../../common/vscode/languageClient'; +import { ITextDocumentAdapter } from '../../common/vscode/textdocument'; +import { InlineValueText, LSPTextDocument } from '../../common/vscode/types'; +import { IUriAdapter } from '../../common/vscode/uri'; +import { convertIssue, isResultCliError, OssFileResult, OssResultBody, OssVulnerability } from '../interfaces'; +import { OssService } from '../ossService'; +import { + ImportedModule, + ModuleVulnerabilityCount, + SeverityCounts, +} from '../services/vulnerabilityCount/importedModule'; + +export class ModuleVulnerabilityCountProvider { constructor( - private readonly ossService: OssServiceLanguageServer, + private readonly ossService: OssService, private readonly lca: ILanguageClientAdapter, private readonly uriAdapter: IUriAdapter, private readonly textDocumentAdapter: ITextDocumentAdapter, diff --git a/src/snyk/snykOss/services/ossService.ts b/src/snyk/snykOss/services/ossService.ts deleted file mode 100644 index 547785b01..000000000 --- a/src/snyk/snykOss/services/ossService.ts +++ /dev/null @@ -1,224 +0,0 @@ -import * as marked from 'marked'; -import { Subject } from 'rxjs'; -import { IExtension } from '../../base/modules/interfaces'; -import { CliError, CliService } from '../../cli/services/cliService'; -import { IAnalytics } from '../../common/analytics/itly'; -import { IConfiguration } from '../../common/configuration/configuration'; -import { IWorkspaceTrust } from '../../common/configuration/trustedFolders'; -import { IDE_NAME } from '../../common/constants/general'; -import { ILanguageServer } from '../../common/languageServer/languageServer'; -import { ILog } from '../../common/logger/interfaces'; -import { DownloadService } from '../../common/services/downloadService'; -import { INotificationService } from '../../common/services/notificationService'; -import { IViewManagerService } from '../../common/services/viewManagerService'; -import { IWebViewProvider } from '../../common/views/webviewProvider'; -import { ExtensionContext } from '../../common/vscode/extensionContext'; -import { IVSCodeWorkspace } from '../../common/vscode/workspace'; -import { messages } from '../messages/test'; -import { OssFileResult, OssResult, OssSeverity, OssVulnerability, isResultCliError } from '../ossResult'; -import { OssIssueCommandArg } from '../views/ossVulnerabilityTreeProvider'; -import { DailyScanJob } from '../watchers/dailyScanJob'; -import createManifestFileWatcher from '../watchers/manifestFileWatcher'; - -export class OssService extends CliService { - protected readonly command: string[] = ['test']; - - private isVulnerabilityTreeVisible = false; - - readonly scanFinished$ = new Subject(); - - constructor( - protected readonly extensionContext: ExtensionContext, - protected readonly logger: ILog, - protected readonly config: IConfiguration, - private readonly suggestionProvider: IWebViewProvider, - protected readonly workspace: IVSCodeWorkspace, - private readonly viewManagerService: IViewManagerService, - protected readonly downloadService: DownloadService, - private readonly dailyScanJob: DailyScanJob, - private readonly notificationService: INotificationService, - private readonly analytics: IAnalytics, - protected readonly languageServer: ILanguageServer, - protected readonly workspaceTrust: IWorkspaceTrust, - ) { - super(extensionContext, logger, config, workspace, downloadService, languageServer, workspaceTrust); - } - - public getResultArray = (): ReadonlyArray | undefined => { - if (!this.result) { - return undefined; - } - - return Array.isArray(this.result) ? this.result : [this.result]; - }; - - protected mapToResultType(rawCliResult: string): OssResult { - if (rawCliResult.length == 0) { - throw new Error('CLI returned empty output result.'); - } - - let result: OssResult; - try { - result = JSON.parse(rawCliResult) as OssResult; - } catch (err) { - throw new Error(`Failed to parse JSON result. Unparsed: ${rawCliResult}`); - } - - return result; - } - - protected ensureDependencies(): void { - this.viewManagerService.refreshOssView(); - this.logger.info('Waiting for Open Source scan CLI readiness'); - } - - protected beforeTest(manualTrigger: boolean, reportTriggeredEvent: boolean): void { - this.logger.info(messages.testStarted); - this.viewManagerService.refreshOssView(); - - if (reportTriggeredEvent) { - this.analytics.logAnalysisIsTriggered({ - analysisType: ['Snyk Open Source'], - ide: IDE_NAME, - triggeredByUser: manualTrigger, - }); - } - } - - protected afterTest(result: OssResult | CliError): void { - if (result instanceof CliError) { - this.logger.error(`${messages.testFailed} ${result.error}`); - this.logAnalysisIsReady('Error'); - } else { - this.logOssResult(result); - - if (this.config.shouldAutoScanOss) { - this.dailyScanJob.schedule(); - } - } - - this.scanFinished$.next(); - this.viewManagerService.refreshOssView(); - } - - override handleLsDownloadFailure(): void { - super.handleLsDownloadFailure(); - this.viewManagerService.refreshOssView(); - } - - override handleNoTrustedFolders(): void { - super.handleNoTrustedFolders(); - this.viewManagerService.refreshOssView(); - } - - activateSuggestionProvider(): void { - this.suggestionProvider.activate(); - } - - showSuggestionProvider(vulnerability: OssIssueCommandArg): Promise { - return this.suggestionProvider.showPanel(vulnerability); - } - - activateManifestFileWatcher(extension: IExtension): void { - const manifestWatcher = createManifestFileWatcher(extension, this.workspace, this.config); - this.extensionContext.addDisposables(manifestWatcher); - } - - setVulnerabilityTreeVisibility(visible: boolean): void { - this.isVulnerabilityTreeVisible = visible; - } - - getUniqueVulnerabilities(vulnerabilities: OssVulnerability[]): OssVulnerability[] { - return vulnerabilities.filter((val, i, arr) => arr.findIndex(el => el.id === val.id) == i); - } - - getNewCriticalVulnerabilitiesCount(currentResult: OssResult, otherResult: OssResult): number { - if (Array.isArray(currentResult) && Array.isArray(otherResult)) { - let newVulnerabilityCount = 0; - for (let i = 0; i < otherResult.length; i++) { - newVulnerabilityCount += this.getNewCriticalVulnerabilitiesCount(currentResult[i], otherResult[i]); - } - - return newVulnerabilityCount; - } - - // if only one of results is an array, no count possible - if (Array.isArray(currentResult) || Array.isArray(otherResult)) { - throw new Error('Result types mismatch for new vulnerabilities calculation.'); - } - - if (!currentResult || isResultCliError(currentResult)) { - return 0; - } - - const currentVulnSet = this.getUniqueVulnerabilities(currentResult.vulnerabilities).filter( - v => v.severity === OssSeverity.Critical, - ); - - if (isResultCliError(otherResult)) { - return currentVulnSet.length; - } - - const otherVulnSet = this.getUniqueVulnerabilities(otherResult.vulnerabilities).filter( - v => v.severity === OssSeverity.Critical, - ); - - if (currentVulnSet.length > otherVulnSet.length) { - return currentVulnSet.length - otherVulnSet.length; - } - - return 0; - } - - getOssIssueCommandArg( - vulnerability: OssVulnerability, - allVulnerabilities: OssVulnerability[], - ): Promise { - return new Promise((resolve, reject) => { - const matchingIdVulnerabilities = allVulnerabilities.filter(v => v.id === vulnerability.id); - marked.parse(vulnerability.description, (err, overviewHtml) => { - if (err) { - return reject(err); - } - - return resolve({ - ...vulnerability, - matchingIdVulnerabilities: matchingIdVulnerabilities, - overviewHtml, - }); - }); - }); - } - - private logOssResult(result: OssResult) { - const fileResults = Array.isArray(result) ? result : [result]; - - for (const fileResult of fileResults) { - if (isResultCliError(fileResult)) { - this.logger.error(this.getTestErrorMessage(fileResult)); - this.logAnalysisIsReady('Error'); - } else { - this.logger.info(messages.testFinished(fileResult.projectName)); - this.logAnalysisIsReady('Success'); - } - } - } - - private getTestErrorMessage(fileResult: CliError): string { - let errorMessage: string; - if (fileResult.path) { - errorMessage = `${messages.testFailedForPath(fileResult.path)} ${fileResult.error}`; - } else { - errorMessage = `${messages.testFailed} ${fileResult.error}`; - } - return errorMessage; - } - - private logAnalysisIsReady(result: 'Error' | 'Success'): void { - this.analytics.logAnalysisIsReady({ - ide: IDE_NAME, - analysisType: 'Snyk Open Source', - result, - }); - } -} diff --git a/src/snyk/snykOss/services/vulnerabilityCount/ossVulnerabilityCountServiceLS.ts b/src/snyk/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.ts similarity index 94% rename from src/snyk/snykOss/services/vulnerabilityCount/ossVulnerabilityCountServiceLS.ts rename to src/snyk/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.ts index 090021f9a..f41d519d8 100644 --- a/src/snyk/snykOss/services/vulnerabilityCount/ossVulnerabilityCountServiceLS.ts +++ b/src/snyk/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.ts @@ -10,12 +10,12 @@ import { Diagnostic, DiagnosticCollection, Disposable, TextDocument } from '../. import { IVSCodeWindow } from '../../../common/vscode/window'; import { IVSCodeWorkspace } from '../../../common/vscode/workspace'; import { DIAGNOSTICS_OSS_COLLECTION_NAME } from '../../../snykCode/constants/analysis'; +import { messages } from '../../constants/messages'; import { EditorDecorator } from '../../editor/editorDecorator'; -import { messages } from '../../messages/vulnerabilityCount'; -import { OssServiceLanguageServer } from '../../ossServiceLanguageServer'; -import { VulnerabilityCountEmitter, VulnerabilityCountEvents } from '../../vulnerabilityCountEmitter'; +import { OssService } from '../../ossService'; +import { ModuleVulnerabilityCountProvider } from '../../providers/vulnerabilityCountProvider'; import { ImportedModule, ModuleVulnerabilityCount, ModuleVulnerabilityCountSeverity } from './importedModule'; -import { ModuleVulnerabilityCountProviderLS } from './vulnerabilityCountProviderLS'; +import { VulnerabilityCountEmitter, VulnerabilityCountEvents } from './vulnerabilityCountEmitter'; export enum SupportedLanguage { TypeScript, @@ -24,7 +24,7 @@ export enum SupportedLanguage { PJSON, } -export class OssVulnerabilityCountServiceLS implements Disposable { +export class OssVulnerabilityCountService implements Disposable { protected disposables: Disposable[] = []; protected ossScanFinishedSubscription: Subscription; @@ -35,8 +35,8 @@ export class OssVulnerabilityCountServiceLS implements Disposable { private readonly workspace: IVSCodeWorkspace, private readonly window: IVSCodeWindow, private readonly languages: IVSCodeLanguages, - private readonly vulnerabilityCountProvider: ModuleVulnerabilityCountProviderLS, - private readonly ossService: OssServiceLanguageServer, + private readonly vulnerabilityCountProvider: ModuleVulnerabilityCountProvider, + private readonly ossService: OssService, private readonly logger: ILog, private readonly editorDecorator: EditorDecorator, private readonly analytics: IAnalytics, @@ -222,7 +222,7 @@ export class OssVulnerabilityCountServiceLS implements Disposable { return ''; } - let message = messages.diagnosticMessagePrefix(module); + let message = messages.vulnerabilityCount.diagnosticMessagePrefix(module); message += this.getSeverityCountMessage( [ ModuleVulnerabilityCountSeverity.Critical, diff --git a/src/snyk/snykOss/vulnerabilityCountEmitter.ts b/src/snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountEmitter.ts similarity index 88% rename from src/snyk/snykOss/vulnerabilityCountEmitter.ts rename to src/snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountEmitter.ts index 4564f0742..dcde88813 100644 --- a/src/snyk/snykOss/vulnerabilityCountEmitter.ts +++ b/src/snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountEmitter.ts @@ -1,5 +1,5 @@ import EventEmitter from 'events'; -import { ImportedModule, ModuleVulnerabilityCount } from './services/vulnerabilityCount/importedModule'; +import { ImportedModule, ModuleVulnerabilityCount } from './importedModule'; export enum VulnerabilityCountEvents { PackageJsonFound = 'packageJsonFound', diff --git a/src/snyk/snykOss/views/ossVulnerabilityTreeProvider.ts b/src/snyk/snykOss/views/ossVulnerabilityTreeProvider.ts deleted file mode 100644 index 17b9037b2..000000000 --- a/src/snyk/snykOss/views/ossVulnerabilityTreeProvider.ts +++ /dev/null @@ -1,230 +0,0 @@ -import { OpenCommandIssueType, OpenIssueCommandArg } from '../../common/commands/types'; -import { IConfiguration } from '../../common/configuration/configuration'; -import { SNYK_OPEN_ISSUE_COMMAND } from '../../common/constants/commands'; -import { SNYK_ANALYSIS_STATUS } from '../../common/constants/views'; -import { messages as commonMessages } from '../../common/messages/analysisMessages'; -import { IContextService } from '../../common/services/contextService'; -import { IViewManagerService } from '../../common/services/viewManagerService'; -import { AnalysisTreeNodeProvider } from '../../common/views/analysisTreeNodeProvider'; -import { INodeIcon, NODE_ICONS, TreeNode } from '../../common/views/treeNode'; -import { messages } from '../messages/treeView'; -import { isResultCliError, OssFileResult, OssSeverity, OssVulnerability } from '../ossResult'; -import { OssService } from '../services/ossService'; - -type ISeverityCounts = { - [key in OssSeverity]: number; -}; - -export type OssIssueCommandArg = OssVulnerability & { - matchingIdVulnerabilities: OssVulnerability[]; - overviewHtml: string; -}; - -export class OssVulnerabilityTreeProvider extends AnalysisTreeNodeProvider { - constructor( - protected readonly viewManagerService: IViewManagerService, - protected readonly contextService: IContextService, - protected readonly ossService: OssService, - protected readonly configuration: IConfiguration, - ) { - super(configuration, ossService); - } - - async getRootChildren(): Promise { - if (!this.configuration.getFeaturesConfiguration()?.ossEnabled) { - return [ - new TreeNode({ - text: SNYK_ANALYSIS_STATUS.OSS_DISABLED, - }), - ]; - } - - if (!this.contextService.shouldShowOssAnalysis) return []; - - if (!this.ossService.isLsDownloadSuccessful) { - return [this.getErrorEncounteredTreeNode()]; - } - - if (!this.ossService.isCliReady) { - return [ - new TreeNode({ - text: messages.cookingDependencies, - }), - ]; - } else if (!this.ossService.isAnyWorkspaceFolderTrusted) { - return [this.getNoWorkspaceTrustTreeNode()]; - } - - if (this.ossService.isAnalysisRunning) { - return [ - new TreeNode({ - text: commonMessages.scanRunning, - }), - ]; - } - - const ossResults = this.ossService.getResultArray(); - if (!ossResults) { - return [ - new TreeNode({ - text: messages.runTest, - }), - ]; - } - - const nodes: TreeNode[] = []; - const [resultNodes, totalVulnCount] = await this.getResultNodes(ossResults); - nodes.push(...resultNodes); - - if (ossResults.length == 1 && isResultCliError(ossResults[0])) { - return nodes; - } - - nodes.sort(this.compareNodes); - - const topNodes = [ - new TreeNode({ - text: this.getIssueFoundText(totalVulnCount), - }), - this.getDurationTreeNode(), - this.getNoSeverityFiltersSelectedTreeNode(), - ]; - nodes.unshift(...topNodes.filter((n): n is TreeNode => n !== null)); - - return nodes; - } - - protected getIssueFoundText(nIssues: number): string { - switch (nIssues) { - case 0: - return messages.noVulnerabilitiesFound; - case 1: - return messages.singleVulnerabilityFound; - default: - return messages.multipleVulnerabilitiesFound(nIssues); - } - } - - protected getIssueDescriptionText( - dir: string | undefined, - vulnerabilities: readonly OssVulnerability[], - ): string | undefined { - return `${dir} - ${vulnerabilities.length} ${ - vulnerabilities.length === 1 ? messages.vulnerability : messages.vulnerabilities - }`; - } - - static getSeverityIcon(severity: OssSeverity | string): INodeIcon { - return ( - { - [OssSeverity.Critical]: NODE_ICONS.critical, - [OssSeverity.High]: NODE_ICONS.high, - [OssSeverity.Medium]: NODE_ICONS.medium, - [OssSeverity.Low]: NODE_ICONS.low, - }[severity] || NODE_ICONS.low - ); - } - - static getFileSeverity(counts: ISeverityCounts): OssSeverity { - for (const s of [OssSeverity.Critical, OssSeverity.High, OssSeverity.Medium, OssSeverity.Low]) { - if (counts[s]) return s; - } - - return OssSeverity.Low; - } - - /** Returns severity significance index. The higher, the more significant severity is. */ - static getSeverityComparatorIndex(severity: OssSeverity): number { - return Object.values(OssSeverity).indexOf(severity); - } - - onDidChangeTreeData = this.viewManagerService.refreshOssViewEmitter.event; - - private initFileSeverityCounts(): ISeverityCounts { - return { - [OssSeverity.Critical]: 0, - [OssSeverity.High]: 0, - [OssSeverity.Medium]: 0, - [OssSeverity.Low]: 0, - }; - } - - protected getFilteredIssues(uniqueVulnerabilities: OssVulnerability[]): OssVulnerability[] { - return uniqueVulnerabilities.filter(vuln => { - switch (vuln.severity.toLowerCase()) { - case OssSeverity.Critical: - return this.configuration.severityFilter.critical; - case OssSeverity.High: - return this.configuration.severityFilter.high; - case OssSeverity.Medium: - return this.configuration.severityFilter.medium; - case OssSeverity.Low: - return this.configuration.severityFilter.low; - default: - return true; - } - }); - } - - private async getResultNodes(ossResults: ReadonlyArray): Promise<[TreeNode[], number]> { - const nodes: TreeNode[] = []; - let totalVulnCount = 0; - - for (const fileResult of ossResults) { - if (isResultCliError(fileResult)) { - nodes.push(this.getErrorEncounteredTreeNode(fileResult.path)); - continue; - } - - const counts: ISeverityCounts = this.initFileSeverityCounts(); - const vulnerabilityNodes: TreeNode[] = []; - - const uniqueVulns = this.ossService.getUniqueVulnerabilities(fileResult.vulnerabilities); - totalVulnCount += uniqueVulns.length; - - const fileVulnerabilities = this.getFilteredIssues(uniqueVulns); - if (fileVulnerabilities.length == 0) continue; - - for (const vuln of fileVulnerabilities) { - counts[vuln.severity]++; - vulnerabilityNodes.push( - new TreeNode({ - text: `${vuln.packageName}@${vuln.version} - ${vuln.title}`, - icon: OssVulnerabilityTreeProvider.getSeverityIcon(vuln.severity), - internal: { - severity: OssVulnerabilityTreeProvider.getSeverityComparatorIndex(vuln.severity), - }, - command: { - command: SNYK_OPEN_ISSUE_COMMAND, - title: '', - arguments: [ - { - issueType: OpenCommandIssueType.OssVulnerability, - // eslint-disable-next-line no-await-in-loop - issue: await this.ossService.getOssIssueCommandArg(vuln, fileResult.vulnerabilities), - } as OpenIssueCommandArg, - ], - }, - }), - ); - } - - vulnerabilityNodes.sort(this.compareNodes); - const fileSeverity = OssVulnerabilityTreeProvider.getFileSeverity(counts); - - const fileNode = new TreeNode({ - text: fileResult.displayTargetFile, - description: this.getIssueDescriptionText(fileResult.projectName, fileVulnerabilities), - icon: OssVulnerabilityTreeProvider.getSeverityIcon(fileSeverity), - children: vulnerabilityNodes, - internal: { - nIssues: vulnerabilityNodes.length, - severity: OssVulnerabilityTreeProvider.getSeverityComparatorIndex(fileSeverity), - }, - }); - nodes.push(fileNode); - } - - return [nodes, totalVulnCount]; - } -} diff --git a/src/snyk/snykOss/views/suggestion/ossSuggestionWebviewProvider.ts b/src/snyk/snykOss/views/suggestion/ossSuggestionWebviewProvider.ts deleted file mode 100644 index 8d9ad8b86..000000000 --- a/src/snyk/snykOss/views/suggestion/ossSuggestionWebviewProvider.ts +++ /dev/null @@ -1,210 +0,0 @@ -import * as vscode from 'vscode'; -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 { ILog } from '../../../common/logger/interfaces'; -import { messages as learnMessages } from '../../../common/messages/learn'; -import { LearnService } from '../../../common/services/learnService'; -import { getNonce } from '../../../common/views/nonce'; -import { WebviewPanelSerializer } from '../../../common/views/webviewPanelSerializer'; -import { WebviewProvider } from '../../../common/views/webviewProvider'; -import { ExtensionContext } from '../../../common/vscode/extensionContext'; -import { IVSCodeWindow } from '../../../common/vscode/window'; -import { messages as errorMessages } from '../../messages/error'; -import { OssIssueCommandArg } from '../ossVulnerabilityTreeProvider'; - -enum OssSuggestionsViewEventMessageType { - OpenBrowser = 'openBrowser', -} - -type OssSuggestionViewEventMessage = { - type: OssSuggestionsViewEventMessageType; - value: unknown; -}; - -export class OssSuggestionWebviewProvider extends WebviewProvider { - constructor( - protected readonly context: ExtensionContext, - private readonly window: IVSCodeWindow, - protected readonly logger: ILog, - private readonly learnService: LearnService, - ) { - super(context, logger); - } - - activate(): void { - this.context.addDisposables( - this.window.registerWebviewPanelSerializer(SNYK_VIEW_SUGGESTION_OSS, new WebviewPanelSerializer(this)), - ); - } - - async postLearnLessonMessage(vulnerability: OssIssueCommandArg): Promise { - try { - if (this.panel) { - const lesson = await this.learnService.getOssLesson(vulnerability); - if (lesson) { - void this.panel.webview.postMessage({ - type: 'setLesson', - args: { url: lesson.url, title: learnMessages.lessonButtonTitle }, - }); - } else { - void this.panel.webview.postMessage({ - type: 'setLesson', - args: null, - }); - } - } - } catch (e) { - ErrorHandler.handle(e, this.logger, learnMessages.getLessonError); - } - } - - async showPanel(vulnerability: OssIssueCommandArg): Promise { - try { - await this.focusSecondEditorGroup(); - - if (this.panel) { - this.panel.reveal(vscode.ViewColumn.Two, true); - } else { - this.panel = vscode.window.createWebviewPanel( - SNYK_VIEW_SUGGESTION_OSS, - 'Snyk OSS Vulnerability', - { - viewColumn: vscode.ViewColumn.Two, - preserveFocus: true, - }, - this.getWebviewOptions(), - ); - this.registerListeners(); - } - - this.panel.webview.html = this.getHtmlForWebview(this.panel.webview); - this.panel.iconPath = vscode.Uri.joinPath( - vscode.Uri.file(this.context.extensionPath), - 'media', - 'images', - 'snyk-oss.svg', - ); - - void this.panel.webview.postMessage({ type: 'set', args: vulnerability }); - void this.postLearnLessonMessage(vulnerability); - } catch (e) { - ErrorHandler.handle(e, this.logger, errorMessages.suggestionViewShowFailed); - } - } - - protected registerListeners(): void { - if (!this.panel) return; - - this.panel.onDidDispose(() => this.onPanelDispose(), null, this.disposables); - this.panel.webview.onDidReceiveMessage( - (data: OssSuggestionViewEventMessage) => { - switch (data.type) { - case OssSuggestionsViewEventMessageType.OpenBrowser: - void vscode.commands.executeCommand(SNYK_OPEN_BROWSER_COMMAND, data.value); - break; - default: - break; - } - }, - null, - this.disposables, - ); - this.panel.onDidChangeViewState(() => this.checkVisibility(), null, this.disposables); - } - - protected getHtmlForWebview(webview: vscode.Webview): string { - const images: Record = [ - ['icon-code', 'svg'], - ['dark-critical-severity', 'svg'], - ['dark-high-severity', 'svg'], - ['dark-medium-severity', 'svg'], - ['dark-low-severity', 'svg'], - ['learn-icon', 'svg'], - ].reduce((accumulator: Record, [name, ext]) => { - const uri = this.getWebViewUri('media', 'images', `${name}.${ext}`); - if (!uri) throw new Error('Image missing.'); - accumulator[name] = uri.toString(); - return accumulator; - }, {}); - - const scriptUri = this.getWebViewUri( - 'out', - 'snyk', - 'snykOss', - 'views', - 'suggestion', - 'ossSuggestionWebviewScript.js', - ); - const styleUri = this.getWebViewUri('media', 'views', 'oss', 'suggestion', 'suggestion.css'); - const learnStyleUri = this.getWebViewUri('media', 'views', 'common', 'learn.css'); - - const nonce = getNonce(); - - return ` - - - - - - - - - - - - -
-
-
- - - - - - - - - -
-
-
-
- - -
-
-
-
-
Vulnerable module
-
-
-
-
Introduced through
-
-
-
-
Fixed in
-
-
-
-
Exploit maturity
-
-
-
-
-

Detailed paths

-
-
-
-
-
-
- - - `; - } -} diff --git a/src/snyk/snykOss/views/suggestion/ossSuggestionWebviewScript.ts b/src/snyk/snykOss/views/suggestion/ossSuggestionWebviewScript.ts deleted file mode 100644 index 5dce4e32d..000000000 --- a/src/snyk/snykOss/views/suggestion/ossSuggestionWebviewScript.ts +++ /dev/null @@ -1,293 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/// -// This script will be run within the webview itself -// It cannot access the main VS Code APIs directly. -(function () { - // TODO: Redefine types until bundling is introduced into extension - // https://stackoverflow.com/a/56938089/1713082 - type Vulnerability = { - id: string; - license?: string; - identifiers?: Identifiers; - title: string; - description: string; - language: string; - packageManager: string; - packageName: string; - severity: string; - name: string; - version: string; - exploit?: string; - - CVSSv3?: string; - cvssScore?: string; - - fixedIn?: Array; - from: Array; - upgradePath: Array; - isPatchable: boolean; - isUpgradable: boolean; - - matchingIdVulnerabilities: Vulnerability[]; - overviewHtml: string; - }; - - type Lesson = { - url: string; - title: string; - }; - - type Identifiers = { - CWE: string[]; - CVE: string[]; - }; - - const vscode = acquireVsCodeApi(); - let vulnerability = {} as Vulnerability; - - let lesson: Lesson | null; - - function navigateToUrl(url: string) { - vscode.postMessage({ - type: 'openBrowser', - value: url, - }); - } - - function fillLearnLink() { - const learnWrapper = document.querySelector('.learn')!; - learnWrapper.className = 'learn'; - - if (lesson) { - const learnLink = document.querySelector('.learn--link')!; - learnLink.innerText = lesson.title; - const lessonUrl = lesson.url; - learnLink.onclick = () => navigateToUrl(lessonUrl); - learnWrapper.className = 'learn show'; - } - } - - function showCurrentSuggestion() { - const severity = document.querySelector('.severity')!; - const title = document.querySelector('.suggestion .suggestion-text')!; - - // Set title - title.innerHTML = vulnerability.title; - - // Set severity icon - setSeverityIcon(); - - // Fill identifiers line - fillIdentifiers(); - - // Fill summary - fillSummary(); - - // Fill detailed paths - fillDetailedPaths(); - - // Fill overview - fillOverview(); - - function setSeverityIcon() { - if (vulnerability.severity) { - severity.querySelectorAll('img').forEach(n => { - if (n.id.slice(-1) === 'l') { - if (n.id.includes(vulnerability.severity)) n.className = 'icon light-only'; - else n.className = 'icon light-only hidden'; - } else { - if (n.id.includes(vulnerability.severity)) n.className = 'icon dark-only'; - else n.className = 'icon dark-only hidden'; - } - }); - } else { - severity.querySelectorAll('img').forEach(n => (n.className = 'icon hidden')); - } - } - - function fillIdentifiers() { - const identifiers = document.querySelector('.identifiers')!; - identifiers.innerHTML = ''; // reset node - const type = vulnerability.license ? 'License' : 'Vulnerability'; - const typeNode = document.createTextNode(type); - identifiers.appendChild(typeNode); - - vulnerability.identifiers?.CVE.forEach(cve => - appendIdentifierSpan(identifiers, cve, `https://cve.mitre.org/cgi-bin/cvename.cgi?name=${cve}`), - ); - vulnerability.identifiers?.CWE.forEach(cwe => appendIdentifierSpan(identifiers, cwe, getCweLink(cwe))); - if (vulnerability.cvssScore) appendIdentifierSpan(identifiers, `CVSS ${vulnerability.cvssScore}`); - appendIdentifierSpan(identifiers, vulnerability.id.toUpperCase(), `https://snyk.io/vuln/${vulnerability.id}`); - } - - function fillSummary() { - const module = document.querySelector('.module > .content')!; - module.textContent = vulnerability.name; - - const maturity = document.querySelector('.maturity > .content')!; - if (!vulnerability.exploit) { - maturity.classList.add('hidden'); - } else { - maturity.textContent = vulnerability.exploit; - } - - const introducedThrough = document.querySelector('.introduced-through > .content')!; - introducedThrough.innerHTML = ''; // reset node - if (vulnerability.from.length == 0) { - introducedThrough.classList.add('hidden'); - } else { - let modules = vulnerability.matchingIdVulnerabilities - .filter(vuln => vuln.from.length > 1) - .map(vuln => vuln.from[1]); - modules = [...new Set(modules)]; // obtain distinct only - - modules.forEach((module, i, arr) => { - appendIntroducedThroughSpan(introducedThrough, module, vulnerability.packageManager); - if (i != arr.length - 1) introducedThrough.append(document.createTextNode(', ')); - }); - } - - const fixedIn = document.querySelector('.fixed-in > .content')!; - fixedIn.innerHTML = ''; // reset node - if (!vulnerability.fixedIn || vulnerability.fixedIn.length == 0) { - fixedIn.append('Not fixed'); - } else { - fixedIn.append(vulnerability.name + '@'); - vulnerability.fixedIn.forEach((version, i, arr) => { - let versionStr = version; - if (i != arr.length - 1) versionStr = versionStr + ', '; - fixedIn.append(versionStr); - }); - } - } - - function fillDetailedPaths() { - const paths = document.querySelector('.detailed-paths')!; - paths.innerHTML = ''; // reset node - - vulnerability.matchingIdVulnerabilities.forEach(vuln => { - const introducedThrough = vuln.from.join(' > '); - - const isOutdated = vuln.upgradePath && vuln.upgradePath[1] === vuln.from[1]; - - // The logic as in registry - // https://github.com/snyk/registry/blob/5fe141a3c5eeb6b2c5e62cfa2b5a8643df29403d/frontend/src/components/IssueCardVulnerablePath/IssueCardVulnerablePath.vue#L109 - let remediationAdvice: string; - const upgradeMessage = `Upgrade to ${vuln.upgradePath[1]}`; - - if (vuln.isUpgradable || vuln.isPatchable) { - if (isOutdated) { - remediationAdvice = vuln.isPatchable ? upgradeMessage : getOutdatedDependencyMessage(vuln); - } else { - remediationAdvice = upgradeMessage; - } - } else { - remediationAdvice = 'none'; - } - - const html = ` -
-
Introduced through
-
${introducedThrough}
-
-
-
Remediation
-
${remediationAdvice}
-
`; - - const path = document.createElement('div'); - path.className = 'detailed-path'; - path.innerHTML = html; - paths.append(path); - }); - } - - function fillOverview() { - const overview = document.getElementById('overview')!; - overview.innerHTML = vulnerability.overviewHtml; - } - } - - function getCweLink(cwe: string) { - const id = cwe.toUpperCase().replace('CWE-', ''); - return `https://cwe.mitre.org/data/definitions/${id}.html`; - } - - function appendIdentifierSpan(identifiers: Element, id: string, link?: string) { - const delimiter = document.createElement('span'); - // delimiter.innerText = ' | '; - delimiter.className = 'delimiter'; - identifiers.appendChild(delimiter); - - let cveNode: HTMLElement; - if (link) { - cveNode = document.createElement('a'); - cveNode.onclick = () => navigateToUrl(link); - } else { - cveNode = document.createElement('span'); - } - - cveNode.innerText = id; - - identifiers.appendChild(cveNode); - } - - function appendIntroducedThroughSpan(introducedThrough: Element, module: string, packageManager: string) { - const supportedPackageManagers = ['npm']; - - let node: HTMLElement; - // replicate app.snyk.io linking logic from https://github.com/snyk/registry/blob/c78f0ae84dc20f25146880b3d3d5661f3d3e4db2/frontend/src/lib/issue-utils.ts#L547 - if (supportedPackageManagers.includes(packageManager.toLowerCase())) { - node = document.createElement('a'); - node.onclick = () => navigateToUrl(`https://app.snyk.io/test/${packageManager}/${module}`); - } else { - node = document.createElement('span'); - } - - node.innerText = module; - introducedThrough.appendChild(node); - } - - function getOutdatedDependencyMessage(vulnerability: Vulnerability) { - return `Your dependencies are out of date, otherwise you would be using a newer ${vulnerability.name} than ${ - vulnerability.name - }@${vulnerability.version}. - ${ - ['npm', 'yarn', 'yarn-workspace'].includes(vulnerability.packageManager) - ? `Try relocking your lockfile or deleting node_modules and reinstalling your dependencies. If the problem persists, one of your dependencies may be bundling outdated modules.` - : 'Try reinstalling your dependencies. If the problem persists, one of your dependencies may be bundling outdated modules.' - }`; - } - - window.addEventListener('message', event => { - const { type, args } = event.data; - switch (type) { - case 'set': { - vulnerability = args; - vscode.setState({ ...vscode.getState(), vulnerability }); - showCurrentSuggestion(); - break; - } - case 'get': { - vulnerability = vscode.getState()?.vulnerability || {}; - showCurrentSuggestion(); - break; - } - case 'setLesson': { - lesson = args; - vscode.setState({ ...vscode.getState(), lesson }); - fillLearnLink(); - break; - } - case 'getLesson': { - lesson = vscode.getState()?.lesson || null; - fillLearnLink(); - break; - } - } - }); -})(); diff --git a/src/snyk/snykOss/watchers/dailyScanJob.ts b/src/snyk/snykOss/watchers/dailyScanJob.ts deleted file mode 100644 index 6b21793b8..000000000 --- a/src/snyk/snykOss/watchers/dailyScanJob.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { IExtension } from '../../base/modules/interfaces'; - -export class DailyScanJob { - private readonly dayInMs = 86400000; - private job: NodeJS.Timeout; - - constructor(private readonly extension: IExtension) {} - - schedule(): void { - if (this.job) { - this.job.refresh(); - return; - } - - this.job = setTimeout(() => { - void this.extension.runOssScan(false); - }, this.dayInMs); - } -} diff --git a/src/snyk/snykOss/watchers/manifestFileWatcher.ts b/src/snyk/snykOss/watchers/manifestFileWatcher.ts index 9a13c05c1..d022125c3 100644 --- a/src/snyk/snykOss/watchers/manifestFileWatcher.ts +++ b/src/snyk/snykOss/watchers/manifestFileWatcher.ts @@ -45,13 +45,13 @@ export default function createManifestFileWatcher( const globPattern = `**/{${Object.values(SUPPORTED_MANIFEST_FILES).join(',')}}`; const watcher = workspace.createFileSystemWatcher(globPattern); - watcher.onDidChange(() => runOssScan()); - watcher.onDidDelete(() => runOssScan()); - watcher.onDidCreate(() => runOssScan()); + watcher.onDidChange(() => runScan()); + watcher.onDidDelete(() => runScan()); + watcher.onDidCreate(() => runScan()); - function runOssScan() { + function runScan() { if (configuration.shouldAutoScanOss) { - void extension.runOssScan(); + void extension.runScan(); } } diff --git a/src/test/unit/common/commands/commandController.test.ts b/src/test/unit/common/commands/commandController.test.ts index c77e022d0..4a13ede58 100644 --- a/src/test/unit/common/commands/commandController.test.ts +++ b/src/test/unit/common/commands/commandController.test.ts @@ -10,7 +10,7 @@ import { IOpenerService } from '../../../../snyk/common/services/openerService'; import { IProductService } from '../../../../snyk/common/services/productService'; import { IVSCodeCommands } from '../../../../snyk/common/vscode/commands'; import { IVSCodeWorkspace } from '../../../../snyk/common/vscode/workspace'; -import { OssServiceLanguageServer } from '../../../../snyk/snykOss/ossServiceLanguageServer'; +import { OssService } from '../../../../snyk/snykOss/ossService'; import { LanguageServerMock } from '../../mocks/languageServer.mock'; import { LoggerMock } from '../../mocks/logger.mock'; import { windowMock } from '../../mocks/window.mock'; @@ -26,7 +26,7 @@ suite('CommandController', () => { {} as IAuthenticationService, {} as IProductService, {} as IProductService, - {} as OssServiceLanguageServer, + {} as OssService, {} as ScanModeService, {} as IVSCodeWorkspace, {} as IVSCodeCommands, diff --git a/src/test/unit/common/services/learnService.test.ts b/src/test/unit/common/services/learnService.test.ts index e5715a87d..60804dcd2 100644 --- a/src/test/unit/common/services/learnService.test.ts +++ b/src/test/unit/common/services/learnService.test.ts @@ -1,8 +1,7 @@ -import { strictEqual } from 'assert'; import sinon from 'sinon'; +import { strictEqual } from 'assert'; import { IVSCodeCommands } from '../../../../snyk/common/vscode/commands'; import { LearnService } from '../../../../snyk/common/services/learnService'; -import { OssIssueCommandArg } from '../../../../snyk/snykOss/views/ossVulnerabilityTreeProvider'; import { CodeIssueData, Issue, IssueSeverity } from '../../../../snyk/common/languageServer/types'; import { SNYK_GET_LESSON_COMMAND } from '../../../../snyk/common/constants/commands'; @@ -20,21 +19,6 @@ suite('LearnService', () => { sinon.restore(); }); - test('getOssLesson executes correct command', async () => { - const learnService = new LearnService(commands); - - const issue: OssIssueCommandArg = { - id: 'id', - packageManager: 'packageManager', - } as OssIssueCommandArg; - - await learnService.getOssLesson(issue); - strictEqual(executeCommandFake.calledOnce, true); - strictEqual( - executeCommandFake.calledWith(SNYK_GET_LESSON_COMMAND, issue.id, issue.packageManager, '', '', 4), - true, - ); - }); test('getCodeLesson executes correct command', async () => { const learnService = new LearnService(commands); const issue: Issue = { diff --git a/src/test/unit/snykOss/ossServiceLanguageServer.test.ts b/src/test/unit/snykOss/ossService.test.ts similarity index 91% rename from src/test/unit/snykOss/ossServiceLanguageServer.test.ts rename to src/test/unit/snykOss/ossService.test.ts index b1e997c6d..1dc50a3fe 100644 --- a/src/test/unit/snykOss/ossServiceLanguageServer.test.ts +++ b/src/test/unit/snykOss/ossService.test.ts @@ -7,11 +7,10 @@ import { ILanguageServer } from '../../../snyk/common/languageServer/languageSer import { OssIssueData, ScanProduct, ScanStatus } from '../../../snyk/common/languageServer/types'; import { IProductService } from '../../../snyk/common/services/productService'; import { IViewManagerService } from '../../../snyk/common/services/viewManagerService'; -import { ICodeActionAdapter, ICodeActionKindAdapter } from '../../../snyk/common/vscode/codeAction'; 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 { OssService } from '../../../snyk/snykOss/ossService'; import { OssDetailPanelProvider } from '../../../snyk/snykOss/providers/ossDetailPanelProvider'; import { LanguageServerMock } from '../mocks/languageServer.mock'; import { LoggerMock } from '../mocks/logger.mock'; @@ -29,7 +28,7 @@ suite('OSS Service', () => { refreshOssView: refreshViewFake, } as unknown as IViewManagerService; - service = new OssServiceLanguageServer( + service = new OssService( {} as ExtensionContext, {} as IConfiguration, {} as OssDetailPanelProvider, diff --git a/src/test/unit/snykOss/services/vulnerabilityCount/vulnerabilityCountProvider.test.ts b/src/test/unit/snykOss/providers/vulnerabilityCountProvider.test.ts similarity index 88% rename from src/test/unit/snykOss/services/vulnerabilityCount/vulnerabilityCountProvider.test.ts rename to src/test/unit/snykOss/providers/vulnerabilityCountProvider.test.ts index 79c933516..96765d53f 100644 --- a/src/test/unit/snykOss/services/vulnerabilityCount/vulnerabilityCountProvider.test.ts +++ b/src/test/unit/snykOss/providers/vulnerabilityCountProvider.test.ts @@ -1,18 +1,18 @@ import { deepStrictEqual, strictEqual } from 'assert'; import sinon from 'sinon'; -import { CliError } from '../../../../../snyk/cli/services/cliService'; -import { Language } from '../../../../../snyk/common/types'; -import { ILanguageClientAdapter } from '../../../../../snyk/common/vscode/languageClient'; -import { ITextDocumentAdapter } from '../../../../../snyk/common/vscode/textdocument'; -import { IUriAdapter } from '../../../../../snyk/common/vscode/uri'; -import { OssResultBody, OssVulnerability } from '../../../../../snyk/snykOss/ossResult'; -import { OssServiceLanguageServer } from '../../../../../snyk/snykOss/ossServiceLanguageServer'; -import { ImportedModule } from '../../../../../snyk/snykOss/services/vulnerabilityCount/importedModule'; -import { ModuleVulnerabilityCountProviderLS } from '../../../../../snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountProviderLS'; +import { CliError } from '../../../../snyk/cli/services/cliService'; +import { Language } from '../../../../snyk/common/types'; +import { ILanguageClientAdapter } from '../../../../snyk/common/vscode/languageClient'; +import { ITextDocumentAdapter } from '../../../../snyk/common/vscode/textdocument'; +import { IUriAdapter } from '../../../../snyk/common/vscode/uri'; +import { OssResultBody, OssVulnerability } from '../../../../snyk/snykOss/interfaces'; +import { OssService } from '../../../../snyk/snykOss/ossService'; +import { ModuleVulnerabilityCountProvider } from '../../../../snyk/snykOss/providers/vulnerabilityCountProvider'; +import { ImportedModule } from '../../../../snyk/snykOss/services/vulnerabilityCount/importedModule'; suite('OSS ModuleVulnerabilityCountProvider', () => { - let ossService: OssServiceLanguageServer; - let vulnerabilityCountProvider: ModuleVulnerabilityCountProviderLS; + let ossService: OssService; + let vulnerabilityCountProvider: ModuleVulnerabilityCountProvider; const sampleFilePath = 'C:\\git\\project\\test.js'; const sampleModuleName = 'mongo-express'; @@ -54,8 +54,8 @@ suite('OSS ModuleVulnerabilityCountProvider', () => { ]; setup(() => { - ossService = {} as OssServiceLanguageServer; - vulnerabilityCountProvider = new ModuleVulnerabilityCountProviderLS( + ossService = {} as OssService; + vulnerabilityCountProvider = new ModuleVulnerabilityCountProvider( ossService, {} as ILanguageClientAdapter, {} as IUriAdapter, diff --git a/src/test/unit/snykOss/services/ossService.test.ts b/src/test/unit/snykOss/services/ossService.test.ts deleted file mode 100644 index de0817279..000000000 --- a/src/test/unit/snykOss/services/ossService.test.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { deepStrictEqual, rejects, strictEqual } from 'assert'; -import * as fs from 'fs/promises'; -import _ from 'lodash'; -import sinon from 'sinon'; -import { CliProcess } from '../../../../snyk/cli/process'; -import { IAnalytics } from '../../../../snyk/common/analytics/itly'; -import { IConfiguration } from '../../../../snyk/common/configuration/configuration'; -import { WorkspaceTrust } from '../../../../snyk/common/configuration/trustedFolders'; -import { ILog } from '../../../../snyk/common/logger/interfaces'; -import { DownloadService } from '../../../../snyk/common/services/downloadService'; -import { INotificationService } from '../../../../snyk/common/services/notificationService'; -import { IViewManagerService } from '../../../../snyk/common/services/viewManagerService'; -import { IWebViewProvider } from '../../../../snyk/common/views/webviewProvider'; -import { ExtensionContext } from '../../../../snyk/common/vscode/extensionContext'; -import { IVSCodeWorkspace } from '../../../../snyk/common/vscode/workspace'; -import { OssFileResult, OssResult, OssSeverity } from '../../../../snyk/snykOss/ossResult'; -import { OssService } from '../../../../snyk/snykOss/services/ossService'; -import { OssIssueCommandArg } from '../../../../snyk/snykOss/views/ossVulnerabilityTreeProvider'; -import { DailyScanJob } from '../../../../snyk/snykOss/watchers/dailyScanJob'; -import { LanguageServerMock } from '../../mocks/languageServer.mock'; -import { LoggerMock } from '../../mocks/logger.mock'; - -suite('OssService', () => { - const extensionPath = 'test/path'; - let logger: ILog; - let ossService: OssService; - - setup(() => { - logger = new LoggerMock(); - - const ls = new LanguageServerMock(); - ls.cliReady$.next(''); - - const testFolderPath = ''; - ossService = new OssService( - { - extensionPath, - } as ExtensionContext, - logger, - { - getAdditionalCliParameters: () => '', - getCliPath: () => undefined, - isAutomaticDependencyManagementEnabled: () => true, - getTrustedFolders: () => [testFolderPath], - } as unknown as IConfiguration, - {} as IWebViewProvider, - { - getWorkspaceFolders: () => [testFolderPath], - } as IVSCodeWorkspace, - { - refreshOssView: () => undefined, - } as IViewManagerService, - {} as DownloadService, - { - schedule: sinon.fake(), - } as unknown as DailyScanJob, - {} as INotificationService, - { - logAnalysisIsReady: sinon.fake(), - } as unknown as IAnalytics, - ls, - new WorkspaceTrust(), - ); - }); - - teardown(() => { - sinon.restore(); - }); - - test('Maps single project result correctly', async () => { - const cliOutput = await fs.readFile('mocked_data/snykOss/single-project-vulnerabilities.json', 'utf-8'); - sinon.stub(CliProcess.prototype, 'spawn').resolves(cliOutput); - - const result = await ossService.test(false, false); - const expected = JSON.parse(cliOutput) as OssResult; - deepStrictEqual(result, expected); - }); - - test('Maps multiple project results correctly', async () => { - const cliOutput = await fs.readFile('mocked_data/snykOss/multi-project-vulnerabilities.json', 'utf-8'); - sinon.stub(CliProcess.prototype, 'spawn').resolves(cliOutput); - - const result = await ossService.test(false, false); - const expected = JSON.parse(cliOutput) as OssResult; - deepStrictEqual(result, expected); - }); - - test('Empty result output throws an error', async () => { - sinon.stub(CliProcess.prototype, 'spawn').resolves(''); - await rejects(async () => await ossService.test(false, false)); - }); - - test('Invalid JSON output throws an error', async () => { - sinon.stub(CliProcess.prototype, 'spawn').resolves('{'); - await rejects(async () => await ossService.test(false, false)); - }); - - test('Gets new critical vulns count correctly for single project', () => { - const oldOssResult = { - vulnerabilities: [ - { - id: '1', - severity: OssSeverity.Critical, - }, - { - id: '2', - severity: OssSeverity.Medium, - }, - ], - displayTargetFile: '', - packageManager: '', - projectName: '', - } as OssFileResult; - - // Assert: latest result has same vulnerability count - strictEqual(ossService.getNewCriticalVulnerabilitiesCount(oldOssResult, oldOssResult), 0); - - const newOssResult = { - ..._.clone(oldOssResult), - vulnerabilities: [ - { - id: '1', - severity: OssSeverity.Critical, - }, - { - id: '2', - severity: OssSeverity.Medium, - }, - { - id: '3', - severity: OssSeverity.Critical, - }, - { - id: '4', - severity: OssSeverity.Medium, - }, - ], - } as OssFileResult; - - // Assert: latest result has more vulnerabilities - strictEqual(ossService.getNewCriticalVulnerabilitiesCount(newOssResult, oldOssResult), 1); - - // Assert: latest result has less vulnerabilities - strictEqual(ossService.getNewCriticalVulnerabilitiesCount(oldOssResult, newOssResult), 0); - }); - - test('Gets new critical vulns count correctly for multiple projects', () => { - const oldOssResult = { - vulnerabilities: [ - { - id: '1', - severity: OssSeverity.Critical, - }, - { - id: '2', - severity: OssSeverity.Medium, - }, - ], - displayTargetFile: '', - packageManager: '', - projectName: '', - } as OssFileResult; - const oldOssResults = [oldOssResult, oldOssResult]; - - // Assert: latest result has same vulnerability count - strictEqual(ossService.getNewCriticalVulnerabilitiesCount(oldOssResults, oldOssResults), 0); - - const newOssResult = { - ..._.clone(oldOssResult), - vulnerabilities: [ - { - id: '1', - severity: OssSeverity.Critical, - }, - { - id: '2', - severity: OssSeverity.Medium, - }, - { - id: '3', - severity: OssSeverity.Critical, - }, - { - id: '4', - severity: OssSeverity.Medium, - }, - ], - } as OssFileResult; - const newOssResults = [newOssResult, newOssResult]; - - // Assert: latest result has more vulnerabilities - strictEqual(ossService.getNewCriticalVulnerabilitiesCount(newOssResults, oldOssResults), 2); - - // Assert: latest result has less vulnerabilities - strictEqual(ossService.getNewCriticalVulnerabilitiesCount(oldOssResults, newOssResults), 0); - }); -}); diff --git a/src/test/unit/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.test.ts b/src/test/unit/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.test.ts index b5d547667..cace82ca2 100644 --- a/src/test/unit/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.test.ts +++ b/src/test/unit/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.test.ts @@ -12,26 +12,26 @@ import { IUriAdapter } from '../../../../../snyk/common/vscode/uri'; import { IVSCodeWindow } from '../../../../../snyk/common/vscode/window'; import { IVSCodeWorkspace } from '../../../../../snyk/common/vscode/workspace'; import { EditorDecorator } from '../../../../../snyk/snykOss/editor/editorDecorator'; -import { OssFileResult } from '../../../../../snyk/snykOss/ossResult'; -import { OssServiceLanguageServer } from '../../../../../snyk/snykOss/ossServiceLanguageServer'; -import { OssVulnerabilityCountServiceLS } from '../../../../../snyk/snykOss/services/vulnerabilityCount/ossVulnerabilityCountServiceLS'; -import { ModuleVulnerabilityCountProviderLS } from '../../../../../snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountProviderLS'; +import { OssFileResult } from '../../../../../snyk/snykOss/interfaces'; +import { OssService } from '../../../../../snyk/snykOss/ossService'; +import { ModuleVulnerabilityCountProvider } from '../../../../../snyk/snykOss/providers/vulnerabilityCountProvider'; +import { OssVulnerabilityCountService } from '../../../../../snyk/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService'; import { LoggerMock } from '../../../mocks/logger.mock'; suite('OSS VulnerabilityCountService', () => { let workspace: IVSCodeWorkspace; let window: IVSCodeWindow; let languages: IVSCodeLanguages; - let ossVulnerabilityCountService: OssVulnerabilityCountServiceLS; - let ossService: OssServiceLanguageServer; - let vulnerabilityCountProvider: ModuleVulnerabilityCountProviderLS; + let ossVulnerabilityCountService: OssVulnerabilityCountService; + let ossService: OssService; + let vulnerabilityCountProvider: ModuleVulnerabilityCountProvider; setup(() => { const logger = new LoggerMock(); ossService = { scanFinished$: EMPTY, newResultAvailable$: EMPTY, - } as unknown as OssServiceLanguageServer; + } as unknown as OssService; workspace = {} as IVSCodeWorkspace; window = { createTextEditorDecorationType: sinon.fake(), @@ -41,7 +41,7 @@ suite('OSS VulnerabilityCountService', () => { registerCodeActionsProvider: sinon.fake(), registerHoverProvider: sinon.fake(), } as unknown as IVSCodeLanguages; - vulnerabilityCountProvider = new ModuleVulnerabilityCountProviderLS( + vulnerabilityCountProvider = new ModuleVulnerabilityCountProvider( ossService, {} as ILanguageClientAdapter, {} as IUriAdapter, @@ -52,7 +52,7 @@ suite('OSS VulnerabilityCountService', () => { const analytics = {} as IAnalytics; const configuration = {} as IConfiguration; - ossVulnerabilityCountService = new OssVulnerabilityCountServiceLS( + ossVulnerabilityCountService = new OssVulnerabilityCountService( workspace, window, languages, diff --git a/src/test/unit/snykOss/services/watchers/dailyScanJob.test.ts b/src/test/unit/snykOss/services/watchers/dailyScanJob.test.ts deleted file mode 100644 index a9de4a5eb..000000000 --- a/src/test/unit/snykOss/services/watchers/dailyScanJob.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { strictEqual } from 'assert'; -import sinon from 'sinon'; -import { IExtension } from '../../../../../snyk/base/modules/interfaces'; -import { DailyScanJob } from '../../../../../snyk/snykOss/watchers/dailyScanJob'; - -suite('OSS DailyScanJob', () => { - let extension: IExtension; - let clock: sinon.SinonFakeTimers; - let ossScanSpy: sinon.SinonSpy; - - setup(() => { - ossScanSpy = sinon.fake(); - extension = { - runOssScan: ossScanSpy, - } as unknown as IExtension; - clock = sinon.useFakeTimers(); - }); - - teardown(() => { - sinon.restore(); - clock; - }); - - test('Runs a scan after 24 hours have passed', () => { - const job = new DailyScanJob(extension); - job.schedule(); - - clock.tick(86400000); - strictEqual(ossScanSpy.calledOnce, true); - }); - - test("Doesn't run scan before 24 hours have passed", () => { - const job = new DailyScanJob(extension); - job.schedule(); - - clock.tick(86399999); - strictEqual(ossScanSpy.calledOnce, false); - }); - - test('24h timer is reset when new schedule happens', () => { - const job = new DailyScanJob(extension); - job.schedule(); - - clock.tick(86399999); - strictEqual(ossScanSpy.called, false); - - job.schedule(); - - clock.tick(86399999); - strictEqual(ossScanSpy.called, false); - - clock.tick(1); - strictEqual(ossScanSpy.calledOnce, true); - }); -}); From 56bae82372a6b7300d9c087a885e301dc92ff6af Mon Sep 17 00:00:00 2001 From: JSON Date: Fri, 17 Nov 2023 11:08:11 +0000 Subject: [PATCH 07/16] fix: vulnerability count message [HEAD-1024] (#391) --- src/snyk/common/types.ts | 15 ++ src/snyk/extension.ts | 4 +- src/snyk/snykOss/constants/messages.ts | 16 -- src/snyk/snykOss/editor/editorDecorator.ts | 11 +- .../ossVulnerabilityCountProvider.ts | 132 +++++++++++ .../providers/vulnerabilityCountProvider.ts | 212 ------------------ .../ossVulnerabilityCountService.ts | 54 +++-- .../vulnerabilityCountProvider.test.ts | 146 ++++++------ .../ossVulnerabilityCountService.test.ts | 17 +- 9 files changed, 258 insertions(+), 349 deletions(-) create mode 100644 src/snyk/snykOss/providers/ossVulnerabilityCountProvider.ts delete mode 100644 src/snyk/snykOss/providers/vulnerabilityCountProvider.ts diff --git a/src/snyk/common/types.ts b/src/snyk/common/types.ts index 6b3fd99e6..7a5a667c4 100644 --- a/src/snyk/common/types.ts +++ b/src/snyk/common/types.ts @@ -1,3 +1,5 @@ +import { JAVASCRIPT, TYPESCRIPT, HTML, PJSON } from './constants/languageConsts'; + export enum Language { TypeScript, JavaScript, @@ -22,3 +24,16 @@ export type ImportedModule = { string: string; version?: string; }; + +export function languageToString(language: Language): string { + switch (language) { + case Language.TypeScript: + return TYPESCRIPT; + case Language.JavaScript: + return JAVASCRIPT; + case Language.HTML: + return HTML; + case Language.PJSON: + return PJSON; + } +} diff --git a/src/snyk/extension.ts b/src/snyk/extension.ts index c3608e68f..e3af60c8c 100644 --- a/src/snyk/extension.ts +++ b/src/snyk/extension.ts @@ -76,8 +76,8 @@ import { IacSuggestionWebviewProvider } from './snykIac/views/suggestion/iacSugg import { EditorDecorator } from './snykOss/editor/editorDecorator'; import { OssService } from './snykOss/ossService'; import { OssDetailPanelProvider } from './snykOss/providers/ossDetailPanelProvider'; +import { OssVulnerabilityCountProvider } from './snykOss/providers/ossVulnerabilityCountProvider'; import OssIssueTreeProvider from './snykOss/providers/ossVulnerabilityTreeProvider'; -import { ModuleVulnerabilityCountProvider } from './snykOss/providers/vulnerabilityCountProvider'; import { OssVulnerabilityCountService } from './snykOss/services/vulnerabilityCount/ossVulnerabilityCountService'; class SnykExtension extends SnykLib implements IExtension { @@ -379,7 +379,7 @@ class SnykExtension extends SnykLib implements IExtension { vsCodeWorkspace, vsCodeWindow, vsCodeLanguages, - new ModuleVulnerabilityCountProvider( + new OssVulnerabilityCountProvider( this.ossService, languageClientAdapter, new UriAdapter(), diff --git a/src/snyk/snykOss/constants/messages.ts b/src/snyk/snykOss/constants/messages.ts index 2537ade08..cee0bfd21 100644 --- a/src/snyk/snykOss/constants/messages.ts +++ b/src/snyk/snykOss/constants/messages.ts @@ -31,20 +31,4 @@ export const messages = { vulnerabilities: 'vulnerabilities', multipleVulnerabilitiesFound: (issueCount: number): string => `Snyk found ${issueCount} vulnerabilities`, }, - vulnerabilityCount: { - fetchingVulnerabilities: 'Fetching vulnerabilities...', - vulnerability: 'vulnerability', - vulnerabilities: 'vulnerabilities', - showMostSevereVulnerability: 'Show the most severe vulnerability (Snyk)', - decoratorMessage: (vulnerabilityCount: string): string => { - 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 `; - }, - }, }; diff --git a/src/snyk/snykOss/editor/editorDecorator.ts b/src/snyk/snykOss/editor/editorDecorator.ts index 0f5b26be8..d5ece4710 100644 --- a/src/snyk/snykOss/editor/editorDecorator.ts +++ b/src/snyk/snykOss/editor/editorDecorator.ts @@ -4,7 +4,6 @@ import { IVSCodeLanguages } from '../../common/vscode/languages'; import { IThemeColorAdapter } from '../../common/vscode/theme'; import { TextEditorDecorationType } from '../../common/vscode/types'; import { IVSCodeWindow } from '../../common/vscode/window'; -import { messages } from '../constants/messages'; import { ImportedModule, ModuleVulnerabilityCount } from '../services/vulnerabilityCount/importedModule'; export class EditorDecorator { @@ -58,7 +57,7 @@ export class EditorDecorator { module.line - 1, this.editorLastCharacterIndex, ), - renderOptions: getRenderOptions(messages.vulnerabilityCount.fetchingVulnerabilities, this.themeColorAdapter), + renderOptions: getRenderOptions('Fetching vulnerabilities...', this.themeColorAdapter), }; } @@ -92,16 +91,16 @@ export class EditorDecorator { this.fileDecorationMap.set(filePath, lineDecorations); // set map, if no decoration was set before } - const text = vulnerabilityCount.count ? messages.vulnerabilityCount.decoratorMessage(vulnerabilityCount.count) : ''; + const vulnerabilityCountMessage = vulnerabilityCount.count ?? 'Vulnerabilities: 0'; lineDecorations[vulnerabilityCount.line] = { range: this.languages.createRange( - vulnerabilityCount.line - 1, + vulnerabilityCount.line - 1, // start line, index is 0 based this.editorLastCharacterIndex, - vulnerabilityCount.line - 1, + vulnerabilityCount.line - 1, // end line, index is 0 based this.editorLastCharacterIndex, ), - renderOptions: getRenderOptions(text, this.themeColorAdapter), + renderOptions: getRenderOptions(vulnerabilityCountMessage, this.themeColorAdapter), }; if (triggerUpdate) { diff --git a/src/snyk/snykOss/providers/ossVulnerabilityCountProvider.ts b/src/snyk/snykOss/providers/ossVulnerabilityCountProvider.ts new file mode 100644 index 000000000..3d5a2ba03 --- /dev/null +++ b/src/snyk/snykOss/providers/ossVulnerabilityCountProvider.ts @@ -0,0 +1,132 @@ +import { CliError } from '../../cli/services/cliService'; +import { Language, languageToString } from '../../common/types'; +import { ILanguageClientAdapter } from '../../common/vscode/languageClient'; +import { ITextDocumentAdapter } from '../../common/vscode/textdocument'; +import { InlineValueText, LanguageClient, LSPTextDocument } from '../../common/vscode/types'; +import { IUriAdapter } from '../../common/vscode/uri'; +import { convertIssue, isResultCliError, OssFileResult, OssResultBody } from '../interfaces'; +import { OssService } from '../ossService'; +import { ImportedModule, ModuleVulnerabilityCount } from '../services/vulnerabilityCount/importedModule'; +import { VulnerabilityCountEmitter } from '../services/vulnerabilityCount/vulnerabilityCountEmitter'; + +export class OssVulnerabilityCountProvider { + constructor( + private readonly ossService: OssService, + private readonly languageClientAdapter: ILanguageClientAdapter, + private readonly uriAdapter: IUriAdapter, + private readonly textDocumentAdapter: ITextDocumentAdapter, + ) {} + + async getVulnerabilityCount( + fileName: string, + module: ImportedModule, + language: Language, + emitter: VulnerabilityCountEmitter, + ): Promise { + let moduleVulnerabilityCount: ModuleVulnerabilityCount = { + name: module.name, + fileName: module.fileName, + line: module.line, + range: module.loc, + hasCount: false, + }; + + const processFile = [Language.TypeScript, Language.JavaScript, Language.PJSON, Language.HTML].includes(language); + if (processFile) { + const uri = this.uriAdapter.file(fileName).toString(); + const doc: LSPTextDocument = this.textDocumentAdapter.create(uri, languageToString(language), 1, ''); + + let firstLine = 0; + let lastLine = doc.lineCount; + let firstCharacter = 0; + let lastCharacter = Number.MAX_SAFE_INTEGER; + + if (module.loc) { + firstLine = module.loc.start.line - 1; + lastLine = module.loc.end.line - 1; + firstCharacter = module.loc.start.column; + lastCharacter = module.loc.end.column; + } + + const param = { + textDocument: { uri: doc.uri }, + range: { + start: { line: firstLine, character: firstCharacter }, + end: { line: lastLine, character: lastCharacter }, + }, + }; + + const inlineValues: InlineValueText[] = await this.languageClientAdapter + .getLanguageClient() + .sendRequest('textDocument/inlineValue', param); + + if (inlineValues?.length > 0) { + moduleVulnerabilityCount = { + name: module.name, + version: module.version, + fileName: module.fileName, + line: module.line, + range: module.loc, + count: inlineValues[0].text, + hasCount: true, + }; + } + } + + emitter?.scanned(moduleVulnerabilityCount); + return moduleVulnerabilityCount; + } + + isFilePartOfOssTest(filePath: string, ossFileResult: OssFileResult): boolean { + if (isResultCliError(ossFileResult)) { + return false; + } + + // File is considered to be part of OSS test if it has common root directory between OSS result path and filename path. + // This is since package.json always lies in the root directory folder of a project. + return filePath.startsWith(ossFileResult.path); + } + + public getResultArray = (): ReadonlyArray | undefined => { + if (!this.ossService.result) { + return undefined; + } + + const tempResultArray: OssFileResult[] = []; + const resultCache = new Map(); + + for (const [, value] of this.ossService.result) { + // value is Error + if (value instanceof Error) { + tempResultArray.push(new CliError(value)); + } + // value is Issue[] + else { + for (const issue of value) { + // try to access list of vulns for the current file + let res = resultCache.get(issue.filePath); + + // add list of vulns to local cache if not there yet + if (res === undefined) { + res = { + path: issue.filePath, + vulnerabilities: [], + projectName: issue.additionalData.projectName, + displayTargetFile: issue.additionalData.displayTargetFile, + packageManager: issue.additionalData.packageManager, + }; + resultCache.set(issue.filePath, res); + } + + const tempVuln = convertIssue(issue); + res.vulnerabilities.push(tempVuln); + } + } + } + + // copy cached results to final result array + resultCache.forEach(value => tempResultArray.push(value)); + + return tempResultArray; + }; +} diff --git a/src/snyk/snykOss/providers/vulnerabilityCountProvider.ts b/src/snyk/snykOss/providers/vulnerabilityCountProvider.ts deleted file mode 100644 index 29b3ab467..000000000 --- a/src/snyk/snykOss/providers/vulnerabilityCountProvider.ts +++ /dev/null @@ -1,212 +0,0 @@ -import { CliError } from '../../cli/services/cliService'; -import { Language } from '../../common/types'; -import { ILanguageClientAdapter } from '../../common/vscode/languageClient'; -import { ITextDocumentAdapter } from '../../common/vscode/textdocument'; -import { InlineValueText, LSPTextDocument } from '../../common/vscode/types'; -import { IUriAdapter } from '../../common/vscode/uri'; -import { convertIssue, isResultCliError, OssFileResult, OssResultBody, OssVulnerability } from '../interfaces'; -import { OssService } from '../ossService'; -import { - ImportedModule, - ModuleVulnerabilityCount, - SeverityCounts, -} from '../services/vulnerabilityCount/importedModule'; - -export class ModuleVulnerabilityCountProvider { - constructor( - private readonly ossService: OssService, - private readonly lca: ILanguageClientAdapter, - private readonly uriAdapter: IUriAdapter, - private readonly textDocumentAdapter: ITextDocumentAdapter, - ) {} - - async getVulnerabilityCount( - fileName: string, - module: ImportedModule, - language: Language, - ): Promise { - const notCalculated = { - name: module.name, - fileName: module.fileName, - line: null, - range: null, - hasCount: false, - }; - - if ([Language.TypeScript, Language.JavaScript, Language.PJSON].includes(language)) { - const ossResult = this.getResultArray(); - if (!ossResult) { - return notCalculated; - } - - return this.mapOssResult(module, ossResult); - } else if (language == Language.HTML && module.loc) { - const uri = this.uriAdapter.file(fileName).toString(); - const doc: LSPTextDocument = this.textDocumentAdapter.create(uri, 'HTML', 1, ''); - const line = module.loc.start.line - 1; - const param = { - textDocument: { uri: doc.uri }, - range: { - start: { line: line, character: module.loc.start.column }, - end: { line: line, character: module.loc.end.column }, - }, - }; - const inlineValues: InlineValueText[] = await this.lca - .getLanguageClient() - .sendRequest('textDocument/inlineValue', param); - - if (inlineValues.length > 0) { - return { - name: module.name, - version: module.version, - fileName: module.fileName, - line: module.line, - range: module.loc, - count: inlineValues[0].text, - hasCount: true, - } as ModuleVulnerabilityCount; - } - } - - return notCalculated; - } - - isFilePartOfOssTest(filePath: string, ossFileResult: OssFileResult): boolean { - if (isResultCliError(ossFileResult)) { - return false; - } - - // File is considered to be part of OSS test if it has common root directory between OSS result path and filename path. - // This is since package.json always lies in the root directory folder of a project. - return filePath.startsWith(ossFileResult.path); - } - - private mapOssResult(module: ImportedModule, ossResult: ReadonlyArray): ModuleVulnerabilityCount { - const notCalculated = { - name: module.name, - fileName: module.fileName, - line: null, - hasCount: false, - range: null, - }; - - for (const fileResult of ossResult) { - if (!this.isFilePartOfOssTest(module.fileName, fileResult)) { - continue; - } - - const vulnerabilities = this.getUniqueVulnerabilities((fileResult as OssResultBody).vulnerabilities); - - // Sum up all vulnerabilities detected in first-level dependencies by OSS matching the imported module name. - // Ideally we want to use the same mechanism as NPM for determining the version used within users code. For now we stick with direct-vulnerability surfacing only. - const directVulnerabilities = vulnerabilities - .filter(v => v.name === module.name) - .filter(v => v.from.length == 2 && v.from[1].startsWith(module.name)); - const vulnerabilityCount = directVulnerabilities.length; - - // NPM allows declaration of the same direct dependency with multiple versions of it (e.g. {"dependencies": "webpack": "^4.44.1", "webpack": "^4.44.2",}). Thus, we should account for vulnerabilities that can be in different versions of the same package. - const hasSingleVersionVulnerability = directVulnerabilities.every( - vuln => vuln.version == directVulnerabilities[0].version, - ); - - let moduleVersion; - if (directVulnerabilities.length && hasSingleVersionVulnerability) { - moduleVersion = directVulnerabilities[0].version; - } - - const severityCounts = this.getSeverityCounts(directVulnerabilities); - const mostSevereVulnerability = this.getMostSevereVulnerability(directVulnerabilities); - - return { - name: module.name, - version: moduleVersion, - fileName: module.fileName, - count: `${vulnerabilityCount}`, - line: module.line, - range: module.loc, - hasCount: vulnerabilityCount > 0, - severityCounts, - mostSevereVulnerabilityId: mostSevereVulnerability?.id, - }; - } - - return notCalculated; - } - - private getSeverityCounts(directVulnerabilities: OssVulnerability[]): SeverityCounts { - return directVulnerabilities - .map(v => v.severity) - .reduce( - (arr, severity) => ({ - ...arr, - [severity]: directVulnerabilities.filter(v => v.severity == severity).length, - }), - {} as SeverityCounts, - ); - } - - private getMostSevereVulnerability(vulnerabilities: OssVulnerability[]): OssVulnerability | null { - return vulnerabilities.sort((a, b) => { - if (!a.cvssScore && !b.cvssScore) return 0; - if (!a.cvssScore) return 1; - if (!b.cvssScore) return -1; - - const cvssScore1 = parseFloat(a.cvssScore); - const cvssScore2 = parseFloat(b.cvssScore); - if (cvssScore1 > cvssScore2) { - return -1; - } else if (cvssScore1 < cvssScore2) { - return 1; - } - - return 0; - })?.[0]; - } - - public getResultArray = (): ReadonlyArray | undefined => { - if (!this.ossService.result) { - return undefined; - } - - const tempResultArray: OssFileResult[] = []; - const resultCache = new Map(); - - for (const [, value] of this.ossService.result) { - // value is Error - if (value instanceof Error) { - tempResultArray.push(new CliError(value)); - } - // value is Issue[] - else { - for (const issue of value) { - // try to access list of vulns for the current file - let res = resultCache.get(issue.filePath); - - // add list of vulns to local cache if not there yet - if (res === undefined) { - res = { - path: issue.filePath, - vulnerabilities: [], - projectName: issue.additionalData.projectName, - displayTargetFile: issue.additionalData.displayTargetFile, - packageManager: issue.additionalData.packageManager, - }; - resultCache.set(issue.filePath, res); - } - - const tempVuln = convertIssue(issue); - res.vulnerabilities.push(tempVuln); - } - } - } - - // copy cached results to final result array - resultCache.forEach(value => tempResultArray.push(value)); - - return tempResultArray; - }; - - public getUniqueVulnerabilities(vulnerabilities: OssVulnerability[]): OssVulnerability[] { - return vulnerabilities.filter((val, i, arr) => arr.findIndex(el => el.id === val.id) === i); - } -} diff --git a/src/snyk/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.ts b/src/snyk/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.ts index f41d519d8..844c0339a 100644 --- a/src/snyk/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.ts +++ b/src/snyk/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.ts @@ -6,14 +6,20 @@ import { getSupportedLanguage, isValidModuleName } from '../../../common/parsing import { ModuleParserProvider } from '../../../common/services/moduleParserProvider'; import { Language } from '../../../common/types'; import { IVSCodeLanguages } from '../../../common/vscode/languages'; -import { Diagnostic, DiagnosticCollection, Disposable, TextDocument } from '../../../common/vscode/types'; +import { + Diagnostic, + DiagnosticCollection, + Disposable, + TextDocument, + TextDocumentChangeEvent, + TextEditor, +} from '../../../common/vscode/types'; import { IVSCodeWindow } from '../../../common/vscode/window'; import { IVSCodeWorkspace } from '../../../common/vscode/workspace'; import { DIAGNOSTICS_OSS_COLLECTION_NAME } from '../../../snykCode/constants/analysis'; -import { messages } from '../../constants/messages'; import { EditorDecorator } from '../../editor/editorDecorator'; import { OssService } from '../../ossService'; -import { ModuleVulnerabilityCountProvider } from '../../providers/vulnerabilityCountProvider'; +import { OssVulnerabilityCountProvider } from '../../providers/ossVulnerabilityCountProvider'; import { ImportedModule, ModuleVulnerabilityCount, ModuleVulnerabilityCountSeverity } from './importedModule'; import { VulnerabilityCountEmitter, VulnerabilityCountEvents } from './vulnerabilityCountEmitter'; @@ -35,23 +41,24 @@ export class OssVulnerabilityCountService implements Disposable { private readonly workspace: IVSCodeWorkspace, private readonly window: IVSCodeWindow, private readonly languages: IVSCodeLanguages, - private readonly vulnerabilityCountProvider: ModuleVulnerabilityCountProvider, + private readonly vulnerabilityCountProvider: OssVulnerabilityCountProvider, private readonly ossService: OssService, private readonly logger: ILog, private readonly editorDecorator: EditorDecorator, private readonly analytics: IAnalytics, private readonly configuration: IConfiguration, ) {} - activate(): boolean { this.disposables.push( (this.diagnostics = this.languages.createDiagnosticCollection(DIAGNOSTICS_OSS_COLLECTION_NAME)), - this.workspace.onDidChangeTextDocument(ev => { - if (ev.contentChanges.length) { - this.processFile(ev.document); + this.workspace.onDidChangeTextDocument((ev: TextDocumentChangeEvent) => { + if (ev?.contentChanges.length) { + // TODO: this feature is buggy if implemented; reset decorations instead as a compromise + // this.processFile(ev.document); + this.editorDecorator.resetDecorations(ev.document.fileName); } }), - this.window.onDidChangeActiveTextEditor(ev => { + this.window.onDidChangeActiveTextEditor((ev: TextEditor | undefined) => { if (ev) { this.processFile(ev.document); } @@ -61,13 +68,12 @@ export class OssVulnerabilityCountService implements Disposable { // Subscribe to OSS scan finished updates this.ossScanFinishedSubscription = this.ossService.newResultAvailable$.subscribe(() => this.processActiveEditor()); - this.processActiveEditor(); - return true; } processActiveEditor(): void { const activeEditor = this.window.getActiveTextEditor(); + if (activeEditor) { this.processFile(activeEditor.document); } @@ -116,13 +122,18 @@ export class OssVulnerabilityCountService implements Disposable { emitter.on(VulnerabilityCountEvents.Start, (modules: ImportedModule[]) => { this.editorDecorator.setScanStartDecorations(fileName, modules); }); + emitter.on(VulnerabilityCountEvents.Scanned, (vulnerabilityCount: ModuleVulnerabilityCount) => { this.editorDecorator.setScannedDecoration(vulnerabilityCount, true); }); emitter.on(VulnerabilityCountEvents.Done, (modules: ModuleVulnerabilityCount[]) => { this.editorDecorator.setScanDoneDecorations(fileName, modules); - this.updateDiagnostics(document, modules); + // TODO: delete this and related code if we move HTML diagnostics to Language Server + // Update diagnostics only for HTML files; for other files, diagnostics are provided by Language Server + if (getSupportedLanguage(fileName, languageId) === Language.HTML) { + this.updateDiagnostics(document, modules); + } }); // Start @@ -193,16 +204,13 @@ export class OssVulnerabilityCountService implements Disposable { const modules = this.getModules(fileName, content, language).filter(isValidModuleName); emitter.startScanning(modules); - const promises = modules - .map(module => this.vulnerabilityCountProvider.getVulnerabilityCount(fileName, module, language)) - .map(promise => - promise.then(module => { - emitter.scanned(module); - return module; - }), - ); - const testedModules = await Promise.all(promises); - emitter.done(testedModules); + const vulnerabilityCountPromises = modules.map(module => + this.vulnerabilityCountProvider.getVulnerabilityCount(fileName, module, language, emitter), + ); + + const vulnerabilityCount = await Promise.all(vulnerabilityCountPromises); + + emitter.done(vulnerabilityCount); } catch (e) { emitter.error(e); } @@ -222,7 +230,7 @@ export class OssVulnerabilityCountService implements Disposable { return ''; } - let message = messages.vulnerabilityCount.diagnosticMessagePrefix(module); + let message = `Dependency ${module.name}${module.version ? `@${module.version}` : ''} has `; message += this.getSeverityCountMessage( [ ModuleVulnerabilityCountSeverity.Critical, diff --git a/src/test/unit/snykOss/providers/vulnerabilityCountProvider.test.ts b/src/test/unit/snykOss/providers/vulnerabilityCountProvider.test.ts index 96765d53f..92cd60afa 100644 --- a/src/test/unit/snykOss/providers/vulnerabilityCountProvider.test.ts +++ b/src/test/unit/snykOss/providers/vulnerabilityCountProvider.test.ts @@ -1,5 +1,6 @@ import { deepStrictEqual, strictEqual } from 'assert'; import sinon from 'sinon'; +import { InlineValueText } from 'vscode'; import { CliError } from '../../../../snyk/cli/services/cliService'; import { Language } from '../../../../snyk/common/types'; import { ILanguageClientAdapter } from '../../../../snyk/common/vscode/languageClient'; @@ -7,12 +8,14 @@ import { ITextDocumentAdapter } from '../../../../snyk/common/vscode/textdocumen import { IUriAdapter } from '../../../../snyk/common/vscode/uri'; import { OssResultBody, OssVulnerability } from '../../../../snyk/snykOss/interfaces'; import { OssService } from '../../../../snyk/snykOss/ossService'; -import { ModuleVulnerabilityCountProvider } from '../../../../snyk/snykOss/providers/vulnerabilityCountProvider'; +import { OssVulnerabilityCountProvider } from '../../../../snyk/snykOss/providers/ossVulnerabilityCountProvider'; import { ImportedModule } from '../../../../snyk/snykOss/services/vulnerabilityCount/importedModule'; +import { VulnerabilityCountEmitter } from '../../../../snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountEmitter'; -suite('OSS ModuleVulnerabilityCountProvider', () => { +suite('OSS VulnerabilityCountProvider', () => { let ossService: OssService; - let vulnerabilityCountProvider: ModuleVulnerabilityCountProvider; + let vulnerabilityCountProvider: OssVulnerabilityCountProvider; + let vulnerabilityCountEmitterStub: VulnerabilityCountEmitter; const sampleFilePath = 'C:\\git\\project\\test.js'; const sampleModuleName = 'mongo-express'; @@ -53,14 +56,38 @@ suite('OSS ModuleVulnerabilityCountProvider', () => { }, ]; + let sampleInlineValueText = [] as InlineValueText[]; + let sampleFileName = 'package.json'; + const sameplUri = `file:///Users/some.user/Documents/some-project/${sampleFileName}`; + + let languageClientStub: { sendRequest: any }; + let uriStub; + setup(() => { + uriStub = sinon.stub().returns(sameplUri); + languageClientStub = { + sendRequest: sinon.stub().resolves(sampleInlineValueText), + }; + ossService = {} as OssService; - vulnerabilityCountProvider = new ModuleVulnerabilityCountProvider( + vulnerabilityCountProvider = new OssVulnerabilityCountProvider( ossService, - {} as ILanguageClientAdapter, - {} as IUriAdapter, - {} as ITextDocumentAdapter, + { + create: sinon.spy(), + getLanguageClient: sinon.stub().returns(languageClientStub), + } as ILanguageClientAdapter, + { + file: uriStub, + parse: sinon.spy(), + } as IUriAdapter, + { + create: sinon.stub().returns({ + uri: uriStub, + lineCount: 1, + }), + } as ITextDocumentAdapter, ); + vulnerabilityCountEmitterStub = sinon.createStubInstance(VulnerabilityCountEmitter); }); teardown(() => { @@ -74,6 +101,7 @@ suite('OSS ModuleVulnerabilityCountProvider', () => { 'test.ts', sampleImportedModule, Language.TypeScript, + vulnerabilityCountEmitterStub, ); const jsCount = await vulnerabilityCountProvider.getVulnerabilityCount( 'test.ts', @@ -82,6 +110,7 @@ suite('OSS ModuleVulnerabilityCountProvider', () => { fileName: 'test.ts', }, Language.JavaScript, + vulnerabilityCountEmitterStub, ); strictEqual(jsCount.hasCount, false); @@ -89,18 +118,36 @@ suite('OSS ModuleVulnerabilityCountProvider', () => { }); test('Gets TS/JS imported module vulnerability results correctly', async () => { - vulnerabilityCountProvider.getResultArray = () => sampleOssResults; - vulnerabilityCountProvider.getUniqueVulnerabilities = () => sampleOssResults[0].vulnerabilities; + const text = 'Vulnerabilities: 2 | Critical: 1, High 1, Medium: 0, Low: 0 | Most Severe: npm:adm-zip:20180415'; + sampleInlineValueText = [ + { + text, + range: { + start: { + line: 1, + character: 16, + } as unknown as InlineValueText['range']['start'], + end: { + line: 1, + character: 29, + } as unknown as InlineValueText['range']['end'], + } as unknown as InlineValueText['range'], + }, + ]; + + sampleFileName = 'test.ts'; + languageClientStub.sendRequest = sinon.stub().resolves(sampleInlineValueText); const count = await vulnerabilityCountProvider.getVulnerabilityCount( - 'test.ts', + sampleFileName, sampleImportedModule, Language.TypeScript, + vulnerabilityCountEmitterStub, ); - strictEqual(count.hasCount, true); - strictEqual(count.count, '2'); - strictEqual(count.line, 1); + strictEqual(count.hasCount, true, 'hasCount is not true'); + strictEqual(count.count, text, `count is not: "${text}"`); + strictEqual(count.line, 1, 'line is not 1'); deepStrictEqual(count.range, { start: { line: 1, @@ -111,85 +158,21 @@ suite('OSS ModuleVulnerabilityCountProvider', () => { column: 29, }, }); - strictEqual(count.severityCounts?.low, 1); - strictEqual(count.severityCounts?.medium, 1); }); test('Gets package.json dependency vulnerability results correctly', async () => { - vulnerabilityCountProvider.getResultArray = () => sampleOssResults; - vulnerabilityCountProvider.getUniqueVulnerabilities = () => sampleOssResults[0].vulnerabilities; + vulnerabilityCountProvider.getResultArray = sinon.stub().returns(sampleOssResults); const count = await vulnerabilityCountProvider.getVulnerabilityCount( 'test.ts', sampleImportedModule, Language.PJSON, + vulnerabilityCountEmitterStub, ); strictEqual(count.hasCount, true); }); - test('Gets only direct dependency vulnerability count', async () => { - const indirectDependency = '@indirect/dependency'; - const ossResultsWithIndirectVulnerability = [ - { - ...sampleOssResults[0], - vulnerabilities: [ - { - name: sampleModuleName, - from: ['goof', sampleModuleName], - } as unknown as OssVulnerability, - { - name: sampleModuleName, - from: ['goof', indirectDependency, sampleModuleName], - } as unknown as OssVulnerability, - ], - }, - ]; - vulnerabilityCountProvider.getResultArray = () => ossResultsWithIndirectVulnerability; - vulnerabilityCountProvider.getUniqueVulnerabilities = () => ossResultsWithIndirectVulnerability[0].vulnerabilities; - - const count = await vulnerabilityCountProvider.getVulnerabilityCount( - 'test.ts', - sampleImportedModule, - Language.TypeScript, - ); - - strictEqual(count.hasCount, true); - strictEqual(count.count, '1'); - }); - - test('Provides a version if same direct dependency has single vulnerable version', async () => { - const version = '1.0.0'; - const ossResultsWithMultipleVersionsVulnerability = [ - { - ...sampleOssResults[0], - vulnerabilities: [ - { - name: sampleModuleName, - from: ['goof', sampleModuleName], - version: version, - } as unknown as OssVulnerability, - { - name: sampleModuleName, - from: ['goof', sampleModuleName], - version: version, - } as unknown as OssVulnerability, - ], - }, - ]; - vulnerabilityCountProvider.getResultArray = () => ossResultsWithMultipleVersionsVulnerability; - vulnerabilityCountProvider.getUniqueVulnerabilities = () => - ossResultsWithMultipleVersionsVulnerability[0].vulnerabilities; - - const count = await vulnerabilityCountProvider.getVulnerabilityCount( - 'test.ts', - sampleImportedModule, - Language.TypeScript, - ); - - strictEqual(count.version, version); - }); - test("Doesn't provide a version if same direct dependency has multiple vulnerable versions", async () => { const ossResultsWithMultipleVersionsVulnerability = [ { @@ -208,14 +191,13 @@ suite('OSS ModuleVulnerabilityCountProvider', () => { ], }, ]; - vulnerabilityCountProvider.getResultArray = () => ossResultsWithMultipleVersionsVulnerability; - vulnerabilityCountProvider.getUniqueVulnerabilities = () => - ossResultsWithMultipleVersionsVulnerability[0].vulnerabilities; + vulnerabilityCountProvider.getResultArray = sinon.stub().returns(ossResultsWithMultipleVersionsVulnerability); const count = await vulnerabilityCountProvider.getVulnerabilityCount( 'test.ts', sampleImportedModule, Language.TypeScript, + vulnerabilityCountEmitterStub, ); strictEqual(count.version, undefined); diff --git a/src/test/unit/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.test.ts b/src/test/unit/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.test.ts index cace82ca2..f1ce0a5de 100644 --- a/src/test/unit/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.test.ts +++ b/src/test/unit/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService.test.ts @@ -1,5 +1,5 @@ import { strictEqual } from 'assert'; -import { EMPTY } from 'rxjs'; +import { EMPTY, Subject } from 'rxjs'; import sinon from 'sinon'; import { IAnalytics } from '../../../../../snyk/common/analytics/itly'; import { IConfiguration } from '../../../../../snyk/common/configuration/configuration'; @@ -14,7 +14,7 @@ import { IVSCodeWorkspace } from '../../../../../snyk/common/vscode/workspace'; import { EditorDecorator } from '../../../../../snyk/snykOss/editor/editorDecorator'; import { OssFileResult } from '../../../../../snyk/snykOss/interfaces'; import { OssService } from '../../../../../snyk/snykOss/ossService'; -import { ModuleVulnerabilityCountProvider } from '../../../../../snyk/snykOss/providers/vulnerabilityCountProvider'; +import { OssVulnerabilityCountProvider } from '../../../../../snyk/snykOss/providers/ossVulnerabilityCountProvider'; import { OssVulnerabilityCountService } from '../../../../../snyk/snykOss/services/vulnerabilityCount/ossVulnerabilityCountService'; import { LoggerMock } from '../../../mocks/logger.mock'; @@ -24,13 +24,13 @@ suite('OSS VulnerabilityCountService', () => { let languages: IVSCodeLanguages; let ossVulnerabilityCountService: OssVulnerabilityCountService; let ossService: OssService; - let vulnerabilityCountProvider: ModuleVulnerabilityCountProvider; + let vulnerabilityCountProvider: OssVulnerabilityCountProvider; setup(() => { const logger = new LoggerMock(); ossService = { scanFinished$: EMPTY, - newResultAvailable$: EMPTY, + newResultAvailable$: new Subject(), } as unknown as OssService; workspace = {} as IVSCodeWorkspace; window = { @@ -41,7 +41,7 @@ suite('OSS VulnerabilityCountService', () => { registerCodeActionsProvider: sinon.fake(), registerHoverProvider: sinon.fake(), } as unknown as IVSCodeLanguages; - vulnerabilityCountProvider = new ModuleVulnerabilityCountProvider( + vulnerabilityCountProvider = new OssVulnerabilityCountProvider( ossService, {} as ILanguageClientAdapter, {} as IUriAdapter, @@ -93,7 +93,7 @@ suite('OSS VulnerabilityCountService', () => { strictEqual(onDidChangeActiveTextEditor.calledOnce, true); }); - test('Processes file if active editor is opened on activation', () => { + test('Processes file on receiving new scan result', () => { window.getActiveTextEditor = () => ({ document: undefined, @@ -105,9 +105,10 @@ suite('OSS VulnerabilityCountService', () => { const processFileSpy = sinon.spy(ossVulnerabilityCountService, 'processFile'); ossVulnerabilityCountService.activate(); + ossService.newResultAvailable$.next(); - strictEqual(getActiveTextEditorSpy.called, true); - strictEqual(processFileSpy.calledOnce, true); + strictEqual(getActiveTextEditorSpy.called, true, 'getActiveTextEditor should be called'); + strictEqual(processFileSpy.calledOnce, true, 'processFile should be called'); }); test("Doesn't process if file is language not supported", () => { From d6e8156504576d41a316a73549adf71eb2c9f00b Mon Sep 17 00:00:00 2001 From: JSON Date: Wed, 22 Nov 2023 14:25:40 +0000 Subject: [PATCH 08/16] fix: show most severe vulnerability code action [HEAD-1022] (#395) --- src/snyk/cli/process.ts | 3 +- src/snyk/common/commands/commandController.ts | 6 +- src/snyk/common/commands/types.ts | 6 +- src/snyk/common/editor/codeActionsProvider.ts | 23 ++- .../common/languageServer/lsExecutable.ts | 4 +- src/snyk/extension.ts | 2 + src/snyk/snykCode/constants/analysis.ts | 1 + .../suggestion/iacSuggestionWebviewScript.ts | 1 + src/snyk/snykOss/interfaces.ts | 2 +- src/snyk/snykOss/ossService.ts | 8 + .../providers/ossCodeActionsProvider.ts | 160 ++++++++++++++++++ .../ossVulnerabilityCountProvider.ts | 2 +- .../providers/ossVulnerabilityTreeProvider.ts | 7 +- .../common/editor/codeActionsProvider.test.ts | 6 +- .../codeIssuesActionsProvider.test.ts | 6 +- .../iacCodeActionsProvider.test.ts | 6 +- src/test/unit/snykOss/ossService.test.ts | 7 +- .../providers/ossCodeActionsProvider.test.ts | 118 +++++++++++++ 18 files changed, 335 insertions(+), 33 deletions(-) create mode 100644 src/snyk/snykOss/providers/ossCodeActionsProvider.ts create mode 100644 src/test/unit/snykOss/providers/ossCodeActionsProvider.test.ts diff --git a/src/snyk/cli/process.ts b/src/snyk/cli/process.ts index d49553ff0..793335386 100644 --- a/src/snyk/cli/process.ts +++ b/src/snyk/cli/process.ts @@ -1,11 +1,11 @@ import { ChildProcessWithoutNullStreams, spawn } from 'child_process'; +import { OAuthToken } from '../base/services/authenticationService'; import { Configuration, IConfiguration } from '../common/configuration/configuration'; import { ILog } from '../common/logger/interfaces'; import { getVsCodeProxy } from '../common/proxy'; import { IVSCodeWorkspace } from '../common/vscode/workspace'; import { CLI_INTEGRATION_NAME } from './contants/integration'; import { CliError } from './services/cliService'; -import { OAuthToken } from '../base/services/authenticationService'; export class CliProcess { private runningProcess: ChildProcessWithoutNullStreams | null; @@ -25,6 +25,7 @@ export class CliProcess { return new Promise((resolve, reject) => { let output = ''; + // file deepcode ignore ArrayMethodOnNonArray: readonly string[] is an array of strings this.logger.info(`Running "${cliPath} ${args.join(' ')}".`); this.runningProcess = spawn(cliPath, args, { env: { ...process.env, ...processEnv }, cwd }); diff --git a/src/snyk/common/commands/commandController.ts b/src/snyk/common/commands/commandController.ts index 60d9c1b24..a58ea1799 100644 --- a/src/snyk/common/commands/commandController.ts +++ b/src/snyk/common/commands/commandController.ts @@ -3,7 +3,7 @@ import _ from 'lodash'; import path from 'path'; import { IAuthenticationService } from '../../base/services/authenticationService'; import { ScanModeService } from '../../base/services/scanModeService'; -import { createDCIgnore } from '../../snykCode/utils/ignoreFileUtils'; +import { createDCIgnore as createDCIgnoreUtil } from '../../snykCode/utils/ignoreFileUtils'; import { IssueUtils } from '../../snykCode/utils/issueUtils'; import { CodeIssueCommandArg } from '../../snykCode/views/interfaces'; import { IacIssueCommandArg } from '../../snykIac/views/interfaces'; @@ -89,11 +89,11 @@ export class CommandController { const paths = this.workspace.getWorkspaceFolders(); const promises = []; for (const p of paths) { - promises.push(createDCIgnore(p, custom, this.workspace, this.window, uriAdapter)); + promises.push(createDCIgnoreUtil(p, custom, this.workspace, this.window, uriAdapter)); } await Promise.all(promises); } else { - await createDCIgnore(path, custom, this.workspace, this.window, uriAdapter); + await createDCIgnoreUtil(path, custom, this.workspace, this.window, uriAdapter); } } diff --git a/src/snyk/common/commands/types.ts b/src/snyk/common/commands/types.ts index 7e59c0206..2fee394d9 100644 --- a/src/snyk/common/commands/types.ts +++ b/src/snyk/common/commands/types.ts @@ -1,7 +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/interfaces'; import { CodeIssueData, Issue } from '../languageServer/types'; export enum OpenCommandIssueType { @@ -11,12 +11,12 @@ export enum OpenCommandIssueType { } export type OpenIssueCommandArg = { - issue: CodeIssueCommandArg | IacIssueCommandArg | OssIssueCommandArgLanguageServer; + issue: CodeIssueCommandArg | IacIssueCommandArg | OssIssueCommandArg; issueType: OpenCommandIssueType; }; export const isCodeIssue = ( - _issue: completeFileSuggestionType | Issue | OssIssueCommandArgLanguageServer, + _issue: completeFileSuggestionType | Issue | OssIssueCommandArg, issueType: OpenCommandIssueType, ): _issue is Issue => { return issueType === OpenCommandIssueType.CodeIssue; diff --git a/src/snyk/common/editor/codeActionsProvider.ts b/src/snyk/common/editor/codeActionsProvider.ts index 74a38b4b9..8181c39b3 100644 --- a/src/snyk/common/editor/codeActionsProvider.ts +++ b/src/snyk/common/editor/codeActionsProvider.ts @@ -2,19 +2,26 @@ import { IAnalytics, SupportedQuickFixProperties } from '../../common/analytics/ import { IDE_NAME } from '../../common/constants/general'; import { Issue } from '../../common/languageServer/types'; import { ICodeActionKindAdapter } from '../../common/vscode/codeAction'; -import { CodeAction, CodeActionKind, CodeActionProvider, Range, TextDocument } from '../../common/vscode/types'; +import { + CodeAction, + CodeActionContext, + CodeActionKind, + CodeActionProvider, + Range, + TextDocument, +} from '../../common/vscode/types'; import { ProductResult } from '../services/productService'; export abstract class CodeActionsProvider implements CodeActionProvider { protected readonly providedCodeActionKinds = [this.codeActionKindAdapter.getQuickFix()]; constructor( - private readonly issues: ProductResult, + protected readonly issues: ProductResult, private readonly codeActionKindAdapter: ICodeActionKindAdapter, - private readonly analytics: IAnalytics, + protected readonly analytics: IAnalytics, ) {} - abstract getActions(folderPath: string, document: TextDocument, issue: Issue, issueRange: Range): CodeAction[]; + abstract getActions(folderPath: string, document: TextDocument, issue: Issue, issueRange?: Range): CodeAction[]; abstract getAnalyticsActionTypes(): [string, ...string[]] & [SupportedQuickFixProperties, ...SupportedQuickFixProperties[]]; @@ -25,7 +32,11 @@ export abstract class CodeActionsProvider implements CodeActionProvider { return this.providedCodeActionKinds; } - public provideCodeActions(document: TextDocument, clickedRange: Range): CodeAction[] | undefined { + public provideCodeActions( + document: TextDocument, + clickedRange: Range, + _context: CodeActionContext, + ): CodeAction[] | undefined { if (this.issues.size === 0) { return undefined; } @@ -57,7 +68,7 @@ export abstract class CodeActionsProvider implements CodeActionProvider { return undefined; } - private findIssueWithRange( + protected findIssueWithRange( result: Issue[], document: TextDocument, clickedRange: Range, diff --git a/src/snyk/common/languageServer/lsExecutable.ts b/src/snyk/common/languageServer/lsExecutable.ts index 8a7c0ca8e..341e1e04a 100644 --- a/src/snyk/common/languageServer/lsExecutable.ts +++ b/src/snyk/common/languageServer/lsExecutable.ts @@ -43,10 +43,10 @@ export class LsExecutable { return customPath; } - const platform = this.getCurrentWithArch(); + const platform = LsExecutable.getCurrentWithArch(); const homeDir = Platform.getHomeDir(); - const lsFilename = this.getFilename(platform); + const lsFilename = LsExecutable.getFilename(platform); const defaultPath = this.defaultPaths[platform]; const lsDir = path.join(homeDir, defaultPath, 'snyk-ls'); return path.join(lsDir, lsFilename); diff --git a/src/snyk/extension.ts b/src/snyk/extension.ts index e3af60c8c..669c942b2 100644 --- a/src/snyk/extension.ts +++ b/src/snyk/extension.ts @@ -219,6 +219,8 @@ class SnykExtension extends SnykLib implements IExtension { extensionContext, configuration, ossSuggestionProvider, + new CodeActionAdapter(), + this.codeActionKindAdapter, this.viewManagerService, vsCodeWorkspace, this.workspaceTrust, diff --git a/src/snyk/snykCode/constants/analysis.ts b/src/snyk/snykCode/constants/analysis.ts index a9dc2baf4..814b590c9 100644 --- a/src/snyk/snykCode/constants/analysis.ts +++ b/src/snyk/snykCode/constants/analysis.ts @@ -24,6 +24,7 @@ export const ISSUES_MARKERS_DECORATION_TYPE: { [key: string]: string } = { export const DIAGNOSTICS_CODE_SECURITY_COLLECTION_NAME = 'Snyk Code Security'; export const DIAGNOSTICS_CODE_QUALITY_COLLECTION_NAME = 'Snyk Code Quality'; export const DIAGNOSTICS_OSS_COLLECTION_NAME = 'Snyk Open Source Security'; +export const DIAGNOSTICS_OSS_COLLECTION_NAME_LS = 'Snyk Open Source'; export const WEBVIEW_PANEL_SECURITY_TITLE = 'Snyk Code Vulnerability'; export const WEBVIEW_PANEL_QUALITY_TITLE = 'Snyk Code Issue'; diff --git a/src/snyk/snykIac/views/suggestion/iacSuggestionWebviewScript.ts b/src/snyk/snykIac/views/suggestion/iacSuggestionWebviewScript.ts index bcab7587e..1ca18f758 100644 --- a/src/snyk/snykIac/views/suggestion/iacSuggestionWebviewScript.ts +++ b/src/snyk/snykIac/views/suggestion/iacSuggestionWebviewScript.ts @@ -143,6 +143,7 @@ } } + // file deepcode ignore InsufficientPostmessageValidation: Content Security Policy applied in provider window.addEventListener('message', event => { const { type, args } = event.data; switch (type) { diff --git a/src/snyk/snykOss/interfaces.ts b/src/snyk/snykOss/interfaces.ts index 907ba23a0..70056124a 100644 --- a/src/snyk/snykOss/interfaces.ts +++ b/src/snyk/snykOss/interfaces.ts @@ -7,7 +7,7 @@ export interface IOssSuggestionWebviewProvider extends IWebViewProvider & { +export type OssIssueCommandArg = Issue & { matchingIdVulnerabilities: Issue[]; overviewHtml: string; }; diff --git a/src/snyk/snykOss/ossService.ts b/src/snyk/snykOss/ossService.ts index b2fa759bb..b0394567f 100644 --- a/src/snyk/snykOss/ossService.ts +++ b/src/snyk/snykOss/ossService.ts @@ -7,16 +7,20 @@ import { OssIssueData, Scan, ScanProduct } from '../common/languageServer/types' import { ILog } from '../common/logger/interfaces'; import { ProductService } from '../common/services/productService'; import { IViewManagerService } from '../common/services/viewManagerService'; +import { ICodeActionAdapter, ICodeActionKindAdapter } from '../common/vscode/codeAction'; import { ExtensionContext } from '../common/vscode/extensionContext'; import { IVSCodeLanguages } from '../common/vscode/languages'; import { IVSCodeWorkspace } from '../common/vscode/workspace'; import { IOssSuggestionWebviewProvider } from './interfaces'; +import { OssCodeActionsProvider } from './providers/ossCodeActionsProvider'; export class OssService extends ProductService { constructor( extensionContext: ExtensionContext, config: IConfiguration, suggestionProvider: IOssSuggestionWebviewProvider, + codeActionAdapter: ICodeActionAdapter, + codeActionKindAdapter: ICodeActionKindAdapter, viewManagerService: IViewManagerService, workspace: IVSCodeWorkspace, workspaceTrust: IWorkspaceTrust, @@ -36,6 +40,10 @@ export class OssService extends ProductService { languages, logger, ); + + this.registerCodeActionsProvider( + new OssCodeActionsProvider(languages, codeActionAdapter, codeActionKindAdapter, this.result, analytics), + ); } subscribeToLsScanMessages(): Subscription { diff --git a/src/snyk/snykOss/providers/ossCodeActionsProvider.ts b/src/snyk/snykOss/providers/ossCodeActionsProvider.ts new file mode 100644 index 000000000..0ed6d9632 --- /dev/null +++ b/src/snyk/snykOss/providers/ossCodeActionsProvider.ts @@ -0,0 +1,160 @@ +import { CodeAction, Range, TextDocument, Uri } from 'vscode'; +import { IAnalytics, SupportedQuickFixProperties } from '../../common/analytics/itly'; +import { OpenCommandIssueType, OpenIssueCommandArg } from '../../common/commands/types'; +import { SNYK_OPEN_ISSUE_COMMAND } from '../../common/constants/commands'; +import { IDE_NAME } from '../../common/constants/general'; +import { CodeActionsProvider } from '../../common/editor/codeActionsProvider'; +import { Issue, IssueSeverity, OssIssueData } from '../../common/languageServer/types'; +import { ProductResult } from '../../common/services/productService'; +import { ICodeActionAdapter, ICodeActionKindAdapter } from '../../common/vscode/codeAction'; +import { IVSCodeLanguages } from '../../common/vscode/languages'; +import { CodeActionContext } from '../../common/vscode/types'; +import { DIAGNOSTICS_OSS_COLLECTION_NAME_LS } from '../../snykCode/constants/analysis'; + +export class OssCodeActionsProvider extends CodeActionsProvider { + constructor( + private readonly languages: IVSCodeLanguages, + private readonly codeActionAdapter: ICodeActionAdapter, + codeActionKindAdapter: ICodeActionKindAdapter, + issues: Readonly>, + analytics: IAnalytics, + ) { + super(issues, codeActionKindAdapter, analytics); + } + + override provideCodeActions( + document: TextDocument, + _clickedRange: Range, + context: CodeActionContext, + ): CodeAction[] | undefined { + // is there a better way to get the folder path? + const folderPath = document.uri.fsPath.split('/').slice(0, -1).join('/'); + if (!folderPath) { + return; + } + + const vulnerabilities = this.getVulnerabilities(folderPath, context); + if (!vulnerabilities) { + return; + } + + const mostSevereVulnerability = this.getMostSevereVulnerability(vulnerabilities); + if (!mostSevereVulnerability) { + return; + } + + const codeActions = this.getActions( + folderPath, + document, + mostSevereVulnerability, + this.getIssueRange(mostSevereVulnerability), + ); + const analyticsType = this.getAnalyticsActionTypes(); + + this.analytics.logQuickFixIsDisplayed({ + quickFixType: analyticsType, + ide: IDE_NAME, + }); + + return codeActions; + } + + getActions( + _folderPath: string, + _document: TextDocument, + mostSevereVulnerability: Issue, + _issueRange?: Range, + ): CodeAction[] { + const openIssueAction = this.createMostSevereVulnerabilityAction(mostSevereVulnerability); + + // returns list of actions, all new actions should be added to this list + return [openIssueAction]; + } + + getAnalyticsActionTypes(): [string, ...string[]] & [SupportedQuickFixProperties, ...SupportedQuickFixProperties[]] { + return ['Show Suggestion']; + } + + // noop + getIssueRange(_issue: Issue): Range { + return this.languages.createRange(0, 0, 0, 0); + } + + private createMostSevereVulnerabilityAction(mostSevereVulnerability: Issue): CodeAction { + // create the CodeAction + const openIssueAction = this.codeActionAdapter.create( + `Show the most severe vulnerability [${mostSevereVulnerability.id}] (Snyk)`, + this.providedCodeActionKinds[0], + ); + + openIssueAction.command = { + command: SNYK_OPEN_ISSUE_COMMAND, + title: SNYK_OPEN_ISSUE_COMMAND, + arguments: [ + { + issueType: OpenCommandIssueType.OssVulnerability, + issue: mostSevereVulnerability, + } as OpenIssueCommandArg, + ], + }; + + return openIssueAction; + } + + private issueSeverityToRanking(severity: IssueSeverity): number { + switch (severity) { + case IssueSeverity.Critical: + return 3; + case IssueSeverity.High: + return 2; + case IssueSeverity.Medium: + return 1; + default: + return 0; + } + } + + private getVulnerabilities(folderPath: string, context: CodeActionContext): Issue[] | undefined { + // get all OSS vulnerabilities for the folder + const ossResult = this.issues.get(folderPath); + if (!ossResult || ossResult instanceof Error) { + return; + } + + // get all OSS diagnostics; these contain the relevant vulnerabilities + const ossDiagnostics = context.diagnostics.filter(d => d.source === DIAGNOSTICS_OSS_COLLECTION_NAME_LS); + if (!ossDiagnostics.length) { + return; + } + + // find the corresponding Issue objects from ossDiagnostics + const vulnerabilities: Issue[] = []; + for (const diagnostic of ossDiagnostics) { + const vulnerability = ossResult.find( + ossIssue => ossIssue.id === (diagnostic.code as { value: string | number; target: Uri }).value, + ); + if (!vulnerability) { + continue; + } + vulnerabilities.push(vulnerability); + } + + return vulnerabilities; + } + + private getMostSevereVulnerability(vulnerabilities: Issue[]): Issue | undefined { + // iterate vulnerabilities and get the most severe one + // if there are multiple of the same severity, get the first one + let highestSeverity = this.issueSeverityToRanking(IssueSeverity.Low); + let mostSevereVulnerability: Issue | undefined; + + for (const vulnerability of vulnerabilities) { + if (this.issueSeverityToRanking(vulnerability.severity) > highestSeverity) { + highestSeverity = this.issueSeverityToRanking(vulnerability.severity); + mostSevereVulnerability = vulnerability; + } + } + + return mostSevereVulnerability; + } +} diff --git a/src/snyk/snykOss/providers/ossVulnerabilityCountProvider.ts b/src/snyk/snykOss/providers/ossVulnerabilityCountProvider.ts index 3d5a2ba03..0ecd40aa7 100644 --- a/src/snyk/snykOss/providers/ossVulnerabilityCountProvider.ts +++ b/src/snyk/snykOss/providers/ossVulnerabilityCountProvider.ts @@ -2,7 +2,7 @@ import { CliError } from '../../cli/services/cliService'; import { Language, languageToString } from '../../common/types'; import { ILanguageClientAdapter } from '../../common/vscode/languageClient'; import { ITextDocumentAdapter } from '../../common/vscode/textdocument'; -import { InlineValueText, LanguageClient, LSPTextDocument } from '../../common/vscode/types'; +import { InlineValueText, LSPTextDocument } from '../../common/vscode/types'; import { IUriAdapter } from '../../common/vscode/uri'; import { convertIssue, isResultCliError, OssFileResult, OssResultBody } from '../interfaces'; import { OssService } from '../ossService'; diff --git a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts index f89fd927a..1b04c0419 100644 --- a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts +++ b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts @@ -14,7 +14,7 @@ import { ProductIssueTreeProvider } from '../../common/views/issueTreeProvider'; import { TreeNode } from '../../common/views/treeNode'; import { IVSCodeLanguages } from '../../common/vscode/languages'; import { messages } from '../constants/messages'; -import { OssIssueCommandArgLanguageServer } from '../interfaces'; +import { OssIssueCommandArg } from '../interfaces'; export default class OssIssueTreeProvider extends ProductIssueTreeProvider { constructor( @@ -201,10 +201,7 @@ export default class OssIssueTreeProvider extends ProductIssueTreeProvider, - filteredVulns: Issue[], - ): OssIssueCommandArgLanguageServer { + getOssIssueCommandArg(vuln: Issue, filteredVulns: Issue[]): OssIssueCommandArg { const matchingIdVulnerabilities = filteredVulns.filter(v => v.id === vuln.id); let overviewHtml = ''; diff --git a/src/test/unit/common/editor/codeActionsProvider.test.ts b/src/test/unit/common/editor/codeActionsProvider.test.ts index c30574e7a..e6a2fe81b 100644 --- a/src/test/unit/common/editor/codeActionsProvider.test.ts +++ b/src/test/unit/common/editor/codeActionsProvider.test.ts @@ -6,7 +6,7 @@ import { CodeActionsProvider } from '../../../../snyk/common/editor/codeActionsP import { Issue } from '../../../../snyk/common/languageServer/types'; import { WorkspaceFolderResult } from '../../../../snyk/common/services/productService'; import { ICodeActionKindAdapter } from '../../../../snyk/common/vscode/codeAction'; -import { CodeAction, Range, TextDocument } from '../../../../snyk/common/vscode/types'; +import { CodeAction, CodeActionContext, Range, TextDocument } from '../../../../snyk/common/vscode/types'; type ProductData = { issueType: string; @@ -84,7 +84,7 @@ suite('Code Actions Provider', () => { } as unknown as TextDocument; // act - const codeActions = issuesActionsProvider.provideCodeActions(document, {} as Range); + const codeActions = issuesActionsProvider.provideCodeActions(document, {} as Range, {} as CodeActionContext); // verify strictEqual(codeActions?.length, 2); @@ -101,7 +101,7 @@ suite('Code Actions Provider', () => { } as unknown as TextDocument; // act - issuesActionsProvider.provideCodeActions(document, {} as Range); + issuesActionsProvider.provideCodeActions(document, {} as Range, {} as CodeActionContext); // verify strictEqual(logQuickFixIsDisplayed.calledOnce, true); diff --git a/src/test/unit/snykCode/codeActions/codeIssuesActionsProvider.test.ts b/src/test/unit/snykCode/codeActions/codeIssuesActionsProvider.test.ts index 0a5870c19..d36ae1504 100644 --- a/src/test/unit/snykCode/codeActions/codeIssuesActionsProvider.test.ts +++ b/src/test/unit/snykCode/codeActions/codeIssuesActionsProvider.test.ts @@ -6,7 +6,7 @@ import { CodeIssueData, Issue } from '../../../../snyk/common/languageServer/typ import { WorkspaceFolderResult } from '../../../../snyk/common/services/productService'; import { ICodeActionAdapter, ICodeActionKindAdapter } from '../../../../snyk/common/vscode/codeAction'; import { IVSCodeLanguages } from '../../../../snyk/common/vscode/languages'; -import { CodeActionKind, Range, TextDocument } from '../../../../snyk/common/vscode/types'; +import { CodeActionContext, CodeActionKind, Range, TextDocument } from '../../../../snyk/common/vscode/types'; import { SnykCodeActionsProvider } from '../../../../snyk/snykCode/codeActions/codeIssuesActionsProvider'; import { IssueUtils } from '../../../../snyk/snykCode/utils/issueUtils'; @@ -68,7 +68,7 @@ suite('Snyk Code actions provider', () => { } as unknown as TextDocument; // act - const codeActions = issuesActionsProvider.provideCodeActions(document, {} as Range); + const codeActions = issuesActionsProvider.provideCodeActions(document, {} as Range, {} as CodeActionContext); // verify strictEqual(codeActions?.length, 3); @@ -86,7 +86,7 @@ suite('Snyk Code actions provider', () => { } as unknown as TextDocument; // act - issuesActionsProvider.provideCodeActions(document, {} as Range); + issuesActionsProvider.provideCodeActions(document, {} as Range, {} as CodeActionContext); // verify strictEqual(logQuickFixIsDisplayed.calledOnce, true); diff --git a/src/test/unit/snykIac/codeActions/iacCodeActionsProvider.test.ts b/src/test/unit/snykIac/codeActions/iacCodeActionsProvider.test.ts index 8d59650a0..201207332 100644 --- a/src/test/unit/snykIac/codeActions/iacCodeActionsProvider.test.ts +++ b/src/test/unit/snykIac/codeActions/iacCodeActionsProvider.test.ts @@ -6,7 +6,7 @@ import { IacIssueData, Issue } from '../../../../snyk/common/languageServer/type import { WorkspaceFolderResult } from '../../../../snyk/common/services/productService'; import { ICodeActionAdapter, ICodeActionKindAdapter } from '../../../../snyk/common/vscode/codeAction'; import { IVSCodeLanguages } from '../../../../snyk/common/vscode/languages'; -import { CodeActionKind, Range, TextDocument } from '../../../../snyk/common/vscode/types'; +import { CodeActionContext, CodeActionKind, Range, TextDocument } from '../../../../snyk/common/vscode/types'; import { IacCodeActionsProvider } from '../../../../snyk/snykIac/codeActions/iacCodeActionsProvider'; import { IacIssue } from '../../../../snyk/snykIac/issue'; @@ -66,7 +66,7 @@ suite('IaC code actions provider', () => { } as unknown as TextDocument; // act - const codeActions = issuesActionsProvider.provideCodeActions(document, {} as Range); + const codeActions = issuesActionsProvider.provideCodeActions(document, {} as Range, {} as CodeActionContext); // verify strictEqual(codeActions?.length, 1); @@ -82,7 +82,7 @@ suite('IaC code actions provider', () => { } as unknown as TextDocument; // act - issuesActionsProvider.provideCodeActions(document, {} as Range); + issuesActionsProvider.provideCodeActions(document, {} as Range, {} as CodeActionContext); // verify strictEqual(logQuickFixIsDisplayed.calledOnce, true); diff --git a/src/test/unit/snykOss/ossService.test.ts b/src/test/unit/snykOss/ossService.test.ts index 1dc50a3fe..46f3e13db 100644 --- a/src/test/unit/snykOss/ossService.test.ts +++ b/src/test/unit/snykOss/ossService.test.ts @@ -7,11 +7,12 @@ import { ILanguageServer } from '../../../snyk/common/languageServer/languageSer import { OssIssueData, ScanProduct, ScanStatus } from '../../../snyk/common/languageServer/types'; import { IProductService } from '../../../snyk/common/services/productService'; import { IViewManagerService } from '../../../snyk/common/services/viewManagerService'; +import { ICodeActionAdapter, ICodeActionKindAdapter } from '../../../snyk/common/vscode/codeAction'; import { ExtensionContext } from '../../../snyk/common/vscode/extensionContext'; import { IVSCodeLanguages } from '../../../snyk/common/vscode/languages'; import { IVSCodeWorkspace } from '../../../snyk/common/vscode/workspace'; +import { IOssSuggestionWebviewProvider } from '../../../snyk/snykOss/interfaces'; import { OssService } from '../../../snyk/snykOss/ossService'; -import { OssDetailPanelProvider } from '../../../snyk/snykOss/providers/ossDetailPanelProvider'; import { LanguageServerMock } from '../mocks/languageServer.mock'; import { LoggerMock } from '../mocks/logger.mock'; @@ -31,7 +32,9 @@ suite('OSS Service', () => { service = new OssService( {} as ExtensionContext, {} as IConfiguration, - {} as OssDetailPanelProvider, + {} as IOssSuggestionWebviewProvider, + {} as ICodeActionAdapter, + { getQuickFix: sinon.fake() } as ICodeActionKindAdapter, viewManagerService, { getWorkspaceFolders: () => [''], diff --git a/src/test/unit/snykOss/providers/ossCodeActionsProvider.test.ts b/src/test/unit/snykOss/providers/ossCodeActionsProvider.test.ts new file mode 100644 index 000000000..5a6ad2c26 --- /dev/null +++ b/src/test/unit/snykOss/providers/ossCodeActionsProvider.test.ts @@ -0,0 +1,118 @@ +import assert from 'assert'; +import sinon from 'sinon'; +import { CodeAction, CodeActionContext, CodeActionKind, Range, TextDocument } from 'vscode'; +import { IAnalytics } from '../../../../snyk/common/analytics/itly'; +import { OpenCommandIssueType, OpenIssueCommandArg } from '../../../../snyk/common/commands/types'; +import { SNYK_OPEN_ISSUE_COMMAND } from '../../../../snyk/common/constants/commands'; +import { Issue, IssueSeverity, OssIssueData } from '../../../../snyk/common/languageServer/types'; +import { WorkspaceFolderResult } from '../../../../snyk/common/services/productService'; +import { ICodeActionAdapter, ICodeActionKindAdapter } from '../../../../snyk/common/vscode/codeAction'; +import { IVSCodeLanguages } from '../../../../snyk/common/vscode/languages'; +import { OssCodeActionsProvider } from '../../../../snyk/snykOss/providers/ossCodeActionsProvider'; + +suite('OSS code actions provider', () => { + let ossActionsProvider: OssCodeActionsProvider; + let logQuickFixIsDisplayed: sinon.SinonSpy; + let rangeMock: Range; + + setup(() => { + const ossResults = new Map>(); + ossResults.set('folderName', [ + { + filePath: '//folderName//package.json', + additionalData: {}, + } as unknown as Issue, + ]); + + logQuickFixIsDisplayed = sinon.fake(); + const analytics = { + logQuickFixIsDisplayed, + } as unknown as IAnalytics; + + const codeActionAdapter = { + create: (_: string, _kind?: CodeActionKind) => ({ + command: {}, + }), + } as ICodeActionAdapter; + + const codeActionKindAdapter = { + getQuickFix: sinon.fake(), + } as ICodeActionKindAdapter; + + rangeMock = { + contains: () => true, + } as unknown as Range; + + ossActionsProvider = new OssCodeActionsProvider( + {} as IVSCodeLanguages, + codeActionAdapter, + codeActionKindAdapter, + ossResults, + analytics, + ); + }); + + teardown(() => { + sinon.restore(); + }); + + test('Provides the most severe vulnerability CodeAction', () => { + // arrange + const document = { + uri: { + fsPath: '//folderName//package.json', + }, + } as unknown as TextDocument; + + const clickedRange = {} as Range; + const context = {} as CodeActionContext; + + const vulnerabilities = [ + { + id: 'vulnerability1', + severity: IssueSeverity.High, + }, + { + id: 'vulnerability2', + severity: IssueSeverity.Medium, + }, + { + id: 'vulnerability3', + severity: IssueSeverity.Critical, + }, + ] as Issue[]; + + const mostSevereVulnerability = { + id: 'vulnerability3', + severity: IssueSeverity.Critical, + } as Issue; + + const codeActions = [ + { + command: { + command: SNYK_OPEN_ISSUE_COMMAND, + title: SNYK_OPEN_ISSUE_COMMAND, + arguments: [ + { + issueType: OpenCommandIssueType.OssVulnerability, + issue: mostSevereVulnerability, + } as OpenIssueCommandArg, + ], + }, + }, + ] as CodeAction[]; + + sinon.stub(ossActionsProvider, 'getIssueRange').returns(rangeMock); + // stubbing private methods workaround is to cast to any + sinon.stub(ossActionsProvider, 'getVulnerabilities').returns(vulnerabilities); + sinon.stub(ossActionsProvider, 'getMostSevereVulnerability').returns(mostSevereVulnerability); + sinon.stub(ossActionsProvider, 'getActions').returns(codeActions); + + // act + const result = ossActionsProvider.provideCodeActions(document, clickedRange, context); + + // assert + sinon.assert.calledOnce(logQuickFixIsDisplayed); + assert.deepStrictEqual(result, codeActions); + }); +}); From eff4ad87a04a8668680677fb0389a9a70a7133b5 Mon Sep 17 00:00:00 2001 From: Jason Luong Date: Thu, 23 Nov 2023 11:27:43 +0000 Subject: [PATCH 09/16] chore: rebase main --- src/snyk/common/languageServer/settings.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/snyk/common/languageServer/settings.ts b/src/snyk/common/languageServer/settings.ts index 1b0b0b035..af55f094c 100644 --- a/src/snyk/common/languageServer/settings.ts +++ b/src/snyk/common/languageServer/settings.ts @@ -1,3 +1,4 @@ +import _ from 'lodash'; import { CLI_INTEGRATION_NAME } from '../../cli/contants/integration'; import { Configuration, IConfiguration, SeverityFilter } from '../configuration/configuration'; import { User } from '../user'; From 033be34c1e9aacb207ef86dee94644623d35d3b5 Mon Sep 17 00:00:00 2001 From: Jason Luong Date: Thu, 23 Nov 2023 11:33:33 +0000 Subject: [PATCH 10/16] chore: update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e893b4352..0f20dd146 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Snyk Security - Code and Open Source Dependencies Changelog +## [1.27.0] + +### Feat + +- Snyk LS: Snyk Open Source Security features now use Language Server backend + ## [1.26.1] ### Fixed From a2c0676b97ed382961f395d6a8afb1449262e055 Mon Sep 17 00:00:00 2001 From: JSON Date: Fri, 24 Nov 2023 07:54:05 +0000 Subject: [PATCH 11/16] fix: cleaner vuln count text [HEAD-1063] (#399) * chore: do not show vuln count text if vuln count is 0 * chore: updated changelog --- CHANGELOG.md | 8 +++++++- src/snyk/snykOss/editor/editorDecorator.ts | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f20dd146..d82f184e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,14 @@ # Snyk Security - Code and Open Source Dependencies Changelog +## [1.27.1] + +### Changed + +- Cleaner OSS vulnerability count text + ## [1.27.0] -### Feat +### Changed - Snyk LS: Snyk Open Source Security features now use Language Server backend diff --git a/src/snyk/snykOss/editor/editorDecorator.ts b/src/snyk/snykOss/editor/editorDecorator.ts index d5ece4710..dedec4fef 100644 --- a/src/snyk/snykOss/editor/editorDecorator.ts +++ b/src/snyk/snykOss/editor/editorDecorator.ts @@ -91,7 +91,7 @@ export class EditorDecorator { this.fileDecorationMap.set(filePath, lineDecorations); // set map, if no decoration was set before } - const vulnerabilityCountMessage = vulnerabilityCount.count ?? 'Vulnerabilities: 0'; + const vulnerabilityCountMessage = vulnerabilityCount.count ?? ''; lineDecorations[vulnerabilityCount.line] = { range: this.languages.createRange( From b52376bb767c6421ca2b95caaa65d9d8dd9fe113 Mon Sep 17 00:00:00 2001 From: Bastian Doetsch Date: Fri, 24 Nov 2023 15:31:19 +0100 Subject: [PATCH 12/16] fix: do not interprete tags in displayed code snippets (#400) --- package-lock.json | 30 +++++++++++++------ package.json | 2 ++ .../codeSuggestionWebviewProvider.ts | 9 ++++++ 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index a4fb2f853..6d6f52a0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "analytics-node": "^4.0.1", "axios": "^0.27.2", "glob": "^7.2.0", + "he": "^1.2.0", "htmlparser2": "^7.2.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", @@ -41,6 +42,7 @@ "@types/babel__traverse": "^7.12.2", "@types/find-package-json": "^1.2.2", "@types/glob": "^7.1.3", + "@types/he": "^1.2.3", "@types/lodash": "^4.14.161", "@types/marked": "^3.0.0", "@types/mocha": "^8.0.3", @@ -1673,6 +1675,12 @@ "@types/node": "*" } }, + "node_modules/@types/he": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/he/-/he-1.2.3.tgz", + "integrity": "sha512-q67/qwlxblDzEDvzHhVkwc1gzVWxaNxeyHUBF4xElrvjL11O+Ytze+1fGpBHlr/H9myiBUaUXNnNPmBHxxfAcA==", + "dev": true + }, "node_modules/@types/inquirer": { "version": "8.2.5", "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-8.2.5.tgz", @@ -4739,7 +4747,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, "bin": { "he": "bin/he" } @@ -6958,9 +6965,9 @@ } }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "engines": { "node": ">=6" @@ -9781,6 +9788,12 @@ "@types/node": "*" } }, + "@types/he": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/he/-/he-1.2.3.tgz", + "integrity": "sha512-q67/qwlxblDzEDvzHhVkwc1gzVWxaNxeyHUBF4xElrvjL11O+Ytze+1fGpBHlr/H9myiBUaUXNnNPmBHxxfAcA==", + "dev": true + }, "@types/inquirer": { "version": "8.2.5", "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-8.2.5.tgz", @@ -12063,8 +12076,7 @@ "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, "htmlparser2": { "version": "7.2.0", @@ -13736,9 +13748,9 @@ } }, "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true }, "qs": { diff --git a/package.json b/package.json index fb321914a..29f70e09e 100644 --- a/package.json +++ b/package.json @@ -399,6 +399,7 @@ "@types/babel__traverse": "^7.12.2", "@types/find-package-json": "^1.2.2", "@types/glob": "^7.1.3", + "@types/he": "^1.2.3", "@types/lodash": "^4.14.161", "@types/marked": "^3.0.0", "@types/mocha": "^8.0.3", @@ -441,6 +442,7 @@ "analytics-node": "^4.0.1", "axios": "^0.27.2", "glob": "^7.2.0", + "he": "^1.2.0", "htmlparser2": "^7.2.0", "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", diff --git a/src/snyk/snykCode/views/suggestion/codeSuggestionWebviewProvider.ts b/src/snyk/snykCode/views/suggestion/codeSuggestionWebviewProvider.ts index f12f5b37a..616ef14f0 100644 --- a/src/snyk/snykCode/views/suggestion/codeSuggestionWebviewProvider.ts +++ b/src/snyk/snykCode/views/suggestion/codeSuggestionWebviewProvider.ts @@ -1,3 +1,4 @@ +import he from 'he'; import _ from 'lodash'; import * as vscode from 'vscode'; import { @@ -111,6 +112,14 @@ export class CodeSuggestionWebviewProvider ); this.registerListeners(); } + + issue.additionalData.exampleCommitFixes.map(ecf => { + return ecf.lines.map(l => { + l.line = he.encode(l.line); + return l; + }); + }); + this.panel.webview.html = this.getHtmlForWebview(this.panel.webview); this.panel.iconPath = vscode.Uri.joinPath( vscode.Uri.file(this.context.extensionPath), From 274027aced68f3bdf8b9cb88da54ebb5282eb0c4 Mon Sep 17 00:00:00 2001 From: Bastian Doetsch Date: Mon, 27 Nov 2023 10:36:29 +0100 Subject: [PATCH 13/16] fix: use folder path from workspace folder to find issues (#401) Signed-off-by: Bastian Doetsch --- src/snyk/common/commands/commandController.ts | 3 +-- src/snyk/snykOss/interfaces.ts | 1 + .../providers/ossVulnerabilityTreeProvider.ts | 13 +++++++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/snyk/common/commands/commandController.ts b/src/snyk/common/commands/commandController.ts index a58ea1799..630ec0e26 100644 --- a/src/snyk/common/commands/commandController.ts +++ b/src/snyk/common/commands/commandController.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-unsafe-argument */ import _ from 'lodash'; -import path from 'path'; import { IAuthenticationService } from '../../base/services/authenticationService'; import { ScanModeService } from '../../base/services/scanModeService'; import { createDCIgnore as createDCIgnoreUtil } from '../../snykCode/utils/ignoreFileUtils'; @@ -122,7 +121,7 @@ export class CommandController { }); } else if (arg.issueType == OpenCommandIssueType.OssVulnerability) { const issueArgs = arg.issue as CodeIssueCommandArg; - const folderPath = path.dirname(issueArgs.filePath); + const folderPath = issueArgs.folderPath; const issue = this.ossService.getIssue(folderPath, issueArgs.id); if (!issue) { diff --git a/src/snyk/snykOss/interfaces.ts b/src/snyk/snykOss/interfaces.ts index 70056124a..fde681716 100644 --- a/src/snyk/snykOss/interfaces.ts +++ b/src/snyk/snykOss/interfaces.ts @@ -10,6 +10,7 @@ export interface IOssSuggestionWebviewProvider extends IWebViewProvider & { matchingIdVulnerabilities: Issue[]; overviewHtml: string; + folderPath: string; }; export type OssResult = OssFileResult[] | OssFileResult; diff --git a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts index 1b04c0419..d2649eda4 100644 --- a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts +++ b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts @@ -88,7 +88,7 @@ export default class OssIssueTreeProvider extends ProductIssueTreeProvider, - _folderPath: string, + folderPath: string, _filePath: string, filteredIssues: Issue[], ): Command { @@ -195,13 +195,17 @@ export default class OssIssueTreeProvider extends ProductIssueTreeProvider, filteredVulns: Issue[]): OssIssueCommandArg { + getOssIssueCommandArg( + vuln: Issue, + folderPath: string, + filteredVulns: Issue[], + ): OssIssueCommandArg { const matchingIdVulnerabilities = filteredVulns.filter(v => v.id === vuln.id); let overviewHtml = ''; @@ -216,6 +220,7 @@ export default class OssIssueTreeProvider extends ProductIssueTreeProvider Date: Wed, 29 Nov 2023 15:23:45 +0100 Subject: [PATCH 14/16] fix: show most severe vulnerability action is displayed (#402) Signed-off-by: Bastian Doetsch --- .../providers/ossCodeActionsProvider.ts | 14 ++++++++-- .../providers/ossIssueCommandHelper.ts | 26 +++++++++++++++++ .../providers/ossVulnerabilityTreeProvider.ts | 28 ++----------------- 3 files changed, 39 insertions(+), 29 deletions(-) create mode 100644 src/snyk/snykOss/providers/ossIssueCommandHelper.ts diff --git a/src/snyk/snykOss/providers/ossCodeActionsProvider.ts b/src/snyk/snykOss/providers/ossCodeActionsProvider.ts index 0ed6d9632..7c0141db2 100644 --- a/src/snyk/snykOss/providers/ossCodeActionsProvider.ts +++ b/src/snyk/snykOss/providers/ossCodeActionsProvider.ts @@ -10,6 +10,7 @@ import { ICodeActionAdapter, ICodeActionKindAdapter } from '../../common/vscode/ import { IVSCodeLanguages } from '../../common/vscode/languages'; import { CodeActionContext } from '../../common/vscode/types'; import { DIAGNOSTICS_OSS_COLLECTION_NAME_LS } from '../../snykCode/constants/analysis'; +import { getOssIssueCommandArg } from './ossIssueCommandHelper'; export class OssCodeActionsProvider extends CodeActionsProvider { constructor( @@ -38,7 +39,7 @@ export class OssCodeActionsProvider extends CodeActionsProvider { return; } - const mostSevereVulnerability = this.getMostSevereVulnerability(vulnerabilities); + const mostSevereVulnerability = this.getMostSevereVulnerability(vulnerabilities, folderPath); if (!mostSevereVulnerability) { return; } @@ -142,7 +143,10 @@ export class OssCodeActionsProvider extends CodeActionsProvider { return vulnerabilities; } - private getMostSevereVulnerability(vulnerabilities: Issue[]): Issue | undefined { + private getMostSevereVulnerability( + vulnerabilities: Issue[], + folderPath: string, + ): Issue | undefined { // iterate vulnerabilities and get the most severe one // if there are multiple of the same severity, get the first one let highestSeverity = this.issueSeverityToRanking(IssueSeverity.Low); @@ -155,6 +159,10 @@ export class OssCodeActionsProvider extends CodeActionsProvider { } } - return mostSevereVulnerability; + if (!mostSevereVulnerability) { + return; + } + + return getOssIssueCommandArg(mostSevereVulnerability, folderPath, vulnerabilities); } } diff --git a/src/snyk/snykOss/providers/ossIssueCommandHelper.ts b/src/snyk/snykOss/providers/ossIssueCommandHelper.ts new file mode 100644 index 000000000..6319e41fb --- /dev/null +++ b/src/snyk/snykOss/providers/ossIssueCommandHelper.ts @@ -0,0 +1,26 @@ +import marked from 'marked'; +import { Issue, OssIssueData } from '../../common/languageServer/types'; +import { OssIssueCommandArg } from '../interfaces'; + +export function getOssIssueCommandArg( + vuln: Issue, + folderPath: string, + filteredVulns: Issue[], +): OssIssueCommandArg { + const matchingIdVulnerabilities = filteredVulns.filter(v => v.id === vuln.id); + let overviewHtml = ''; + + try { + // TODO: marked.parse does not sanitize the HTML. See: https://marked.js.org/#usage + overviewHtml = marked.parse(vuln.additionalData.description); + } catch (error) { + overviewHtml = '

There was a problem rendering the vulnerability overview

'; + } + + return { + ...vuln, + matchingIdVulnerabilities, + overviewHtml, + folderPath, + }; +} diff --git a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts index d2649eda4..f3aeba284 100644 --- a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts +++ b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts @@ -1,5 +1,4 @@ import _ from 'lodash'; -import * as marked from 'marked'; import { Command, Uri } from 'vscode'; import { OpenCommandIssueType, OpenIssueCommandArg } from '../../common/commands/types'; import { IConfiguration } from '../../common/configuration/configuration'; @@ -14,7 +13,7 @@ import { ProductIssueTreeProvider } from '../../common/views/issueTreeProvider'; import { TreeNode } from '../../common/views/treeNode'; import { IVSCodeLanguages } from '../../common/vscode/languages'; import { messages } from '../constants/messages'; -import { OssIssueCommandArg } from '../interfaces'; +import { getOssIssueCommandArg } from './ossIssueCommandHelper'; export default class OssIssueTreeProvider extends ProductIssueTreeProvider { constructor( @@ -195,32 +194,9 @@ export default class OssIssueTreeProvider extends ProductIssueTreeProvider, - folderPath: string, - filteredVulns: Issue[], - ): OssIssueCommandArg { - const matchingIdVulnerabilities = filteredVulns.filter(v => v.id === vuln.id); - let overviewHtml = ''; - - try { - // TODO: marked.parse does not sanitize the HTML. See: https://marked.js.org/#usage - overviewHtml = marked.parse(vuln.additionalData.description); - } catch (error) { - overviewHtml = '

There was a problem rendering the vulnerability overview

'; - } - - return { - ...vuln, - matchingIdVulnerabilities, - overviewHtml, - folderPath, - }; - } } From 329573470fdf25851381b52dd900e76aa3e5937e Mon Sep 17 00:00:00 2001 From: Jason Luong Date: Wed, 29 Nov 2023 14:50:49 +0000 Subject: [PATCH 15/16] chore: update changelog --- CHANGELOG.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d82f184e7..c76ade112 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,17 @@ # Snyk Security - Code and Open Source Dependencies Changelog -## [1.27.1] +## [2.1.0] -### Changed - -- Cleaner OSS vulnerability count text - -## [1.27.0] - -### Changed +### Added - Snyk LS: Snyk Open Source Security features now use Language Server backend +- Snyk OSS: Squiggly warning underlines for direct and transitive vulnerabilities +- Snyk OSS: Squiggly underlines colour coded based on severity +- Snyk OSS: Vulnerability count text includes transitive vulnerabilities +- Snyk OSS: Vulnerability count text includes breakdown of vulnerabilities by severity +- Snyk OSS: Hovers lists vulnerabilities and shows summary (without typo) +- Snyk OSS: Hovers show information from security.snyk.io/vuln database +- Snyk OSS: CodeActions shows actions available for all vulnerabilities ## [1.26.1] From 82b3fc1642006a8109a6232c608441e92b8f3bee Mon Sep 17 00:00:00 2001 From: Jason Luong Date: Wed, 29 Nov 2023 14:59:48 +0000 Subject: [PATCH 16/16] chore: add comment to release-preview CICD --- .github/workflows/release-preview.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release-preview.yaml b/.github/workflows/release-preview.yaml index fa7ad1a51..4fd5195f2 100644 --- a/.github/workflows/release-preview.yaml +++ b/.github/workflows/release-preview.yaml @@ -28,6 +28,7 @@ jobs: - name: Verify analytics events run: npm run ampli:verify -- -t ${{ secrets.ITERATIVELY_KEY }} + # Naming convention for the preview version means we can only release one preview per hour - name: Patch to preview version run: npm run patch-preview env: