From db4d7f6082d24cb040ca77e4be2f5b85459edc5a Mon Sep 17 00:00:00 2001 From: Abdelrahman Shawki Hassan Date: Thu, 18 Jul 2024 17:50:45 +0200 Subject: [PATCH 1/8] feat: folderConfigs LS integration wip --- package.json | 5 +++ .../common/configuration/configuration.ts | 27 +++++++++++++ src/snyk/common/configuration/folderConfig.ts | 40 +++++++++++++++++++ src/snyk/common/constants/languageServer.ts | 1 + src/snyk/common/constants/settings.ts | 1 + .../common/languageServer/languageServer.ts | 11 ++++- src/snyk/common/languageServer/settings.ts | 4 +- .../languageServer/languageServer.test.ts | 1 + 8 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 src/snyk/common/configuration/folderConfig.ts diff --git a/package.json b/package.json index 0f090c1b9..b7d85464a 100644 --- a/package.json +++ b/package.json @@ -217,6 +217,11 @@ "default": [], "description": "Folders to trust for Snyk scans." }, + "snyk.folderConfigs": { + "type": "array", + "default": [], + "description": "Folder configuration for Snyk scans." + }, "snyk.features.preview": { "type": "object", "default": {}, diff --git a/src/snyk/common/configuration/configuration.ts b/src/snyk/common/configuration/configuration.ts index 170ce2cfb..7a31d5899 100644 --- a/src/snyk/common/configuration/configuration.ts +++ b/src/snyk/common/configuration/configuration.ts @@ -25,6 +25,7 @@ import { YES_CRASH_REPORT_SETTING, YES_WELCOME_NOTIFICATION_SETTING, DELTA_FINDINGS, + FOLDER_CONFIGS, } from '../constants/settings'; import SecretStorageAdapter from '../vscode/secretStorage'; import { IVSCodeWorkspace } from '../vscode/workspace'; @@ -36,6 +37,13 @@ export type FeaturesConfiguration = { iacEnabled: boolean | undefined; }; + +export type FolderConfig = { + folderPath: string; + baseBranch: string; + localBranches: string[] | undefined; +}; + export interface IssueViewOptions { ignoredIssues: boolean; openIssues: boolean; @@ -122,6 +130,10 @@ export interface IConfiguration { setEndpoint(endpoint: string): Promise; getDeltaFindingsEnabled(): boolean; + + getFolderConfigs(): FolderConfig[]; + + setFolderConfigs(folderConfig: FolderConfig[]): Promise; } export class Configuration implements IConfiguration { @@ -463,6 +475,12 @@ export class Configuration implements IConfiguration { ); } + getFolderConfigs(): FolderConfig[] { + return ( + this.workspace.getConfiguration(CONFIGURATION_IDENTIFIER, this.getConfigName(FOLDER_CONFIGS)) || [] + ); + } + get scanningMode(): string | undefined { return this.workspace.getConfiguration(CONFIGURATION_IDENTIFIER, this.getConfigName(SCANNING_MODE)); } @@ -476,5 +494,14 @@ export class Configuration implements IConfiguration { ); } + async setFolderConfigs(folderConfigs: FolderConfig[]): Promise { + await this.workspace.updateConfiguration( + CONFIGURATION_IDENTIFIER, + this.getConfigName(FOLDER_CONFIGS), + folderConfigs, + true, + ); + } + private getConfigName = (setting: string) => setting.replace(`${CONFIGURATION_IDENTIFIER}.`, ''); } diff --git a/src/snyk/common/configuration/folderConfig.ts b/src/snyk/common/configuration/folderConfig.ts new file mode 100644 index 000000000..2a2c19bc2 --- /dev/null +++ b/src/snyk/common/configuration/folderConfig.ts @@ -0,0 +1,40 @@ +import { FolderConfig, IConfiguration } from './configuration'; + +export interface IFolderConfigs { + getFolderConfigs(config: IConfiguration): ReadonlyArray; + + resetFolderConfigsCache(): void; +} + +export class FolderConfigs implements IFolderConfigs { + private folderConfigsCache?: ReadonlyArray; + + getFolderConfigs(config: IConfiguration): ReadonlyArray { + if (this.folderConfigsCache !== undefined) { + return this.folderConfigsCache; + } + + const folderConfigs = config.getFolderConfigs(); + + this.folderConfigsCache = folderConfigs; + + return folderConfigs; + } + + setFolderConfig(_config: IConfiguration, _folderPath: string, _baseBranch: string) { + // Get all folders and update one you need and use setFolderConfig. + // if (this.folderConfigsCache !== undefined) { + // return this.folderConfigsCache; + // } + + // const folderConfigs = config.getFolderConfigs(); + + // this.folderConfigsCache = folderConfigs; + + // return folderConfigs; + } + + resetFolderConfigsCache() { + this.folderConfigsCache = undefined; + } +} diff --git a/src/snyk/common/constants/languageServer.ts b/src/snyk/common/constants/languageServer.ts index c6220ebdc..0f4389ffe 100644 --- a/src/snyk/common/constants/languageServer.ts +++ b/src/snyk/common/constants/languageServer.ts @@ -12,3 +12,4 @@ export const SNYK_HAS_AUTHENTICATED = '$/snyk.hasAuthenticated'; export const SNYK_CLI_PATH = '$/snyk.isAvailableCli'; export const SNYK_ADD_TRUSTED_FOLDERS = '$/snyk.addTrustedFolders'; export const SNYK_SCAN = '$/snyk.scan'; +export const SNYK_FOLDERCONFIG = '$/snyk.folderConfigs'; diff --git a/src/snyk/common/constants/settings.ts b/src/snyk/common/constants/settings.ts index 5f2cc0cfc..5a9c6b8e1 100644 --- a/src/snyk/common/constants/settings.ts +++ b/src/snyk/common/constants/settings.ts @@ -24,6 +24,7 @@ export const ADVANCED_CUSTOM_LS_PATH = `${CONFIGURATION_IDENTIFIER}.advanced.lan export const ISSUE_VIEW_OPTIONS_SETTING = `${CONFIGURATION_IDENTIFIER}.issueViewOptions`; export const SEVERITY_FILTER_SETTING = `${CONFIGURATION_IDENTIFIER}.severity`; export const TRUSTED_FOLDERS = `${CONFIGURATION_IDENTIFIER}.trustedFolders`; +export const FOLDER_CONFIGS = `${CONFIGURATION_IDENTIFIER}.folderConfigs`; export const SCANNING_MODE = `${CONFIGURATION_IDENTIFIER}.scanningMode`; export const DELTA_FINDINGS = `${FEATURES_PREVIEW_SETTING}.deltaFindings`; diff --git a/src/snyk/common/languageServer/languageServer.ts b/src/snyk/common/languageServer/languageServer.ts index 10be6c636..a581610a9 100644 --- a/src/snyk/common/languageServer/languageServer.ts +++ b/src/snyk/common/languageServer/languageServer.ts @@ -1,10 +1,11 @@ import _ from 'lodash'; import { firstValueFrom, ReplaySubject, Subject } from 'rxjs'; import { IAuthenticationService } from '../../base/services/authenticationService'; -import { IConfiguration } from '../configuration/configuration'; +import { FolderConfig, IConfiguration } from '../configuration/configuration'; import { SNYK_ADD_TRUSTED_FOLDERS, SNYK_CLI_PATH, + SNYK_FOLDERCONFIG, SNYK_HAS_AUTHENTICATED, SNYK_LANGUAGE_SERVER_NAME, SNYK_SCAN, @@ -118,7 +119,7 @@ export class LanguageServer implements ILanguageServer { }; // Create the language client and start the client. - this.client = this.languageClientAdapter.create('Snyk LS', SNYK_LANGUAGE_SERVER_NAME, serverOptions, clientOptions); + this.client = this.languageClientAdapter.create('SnykLS', SNYK_LANGUAGE_SERVER_NAME, serverOptions, clientOptions); try { // Start the client. This will also launch the server @@ -138,6 +139,12 @@ export class LanguageServer implements ILanguageServer { }); }); + client.onNotification(SNYK_FOLDERCONFIG, ({ folderConfigs }: { folderConfigs: FolderConfig[] }) => { + this.configuration.setFolderConfigs(folderConfigs).catch((error: Error) => { + ErrorHandler.handle(error, this.logger, error.message); + }); + }); + client.onNotification(SNYK_CLI_PATH, ({ cliPath }: { cliPath: string }) => { if (!cliPath) { ErrorHandler.handle( diff --git a/src/snyk/common/languageServer/settings.ts b/src/snyk/common/languageServer/settings.ts index b240b6584..4276429c9 100644 --- a/src/snyk/common/languageServer/settings.ts +++ b/src/snyk/common/languageServer/settings.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; import { CLI_INTEGRATION_NAME } from '../../cli/contants/integration'; -import { Configuration, IConfiguration, SeverityFilter } from '../configuration/configuration'; +import { Configuration, FolderConfig, IConfiguration, SeverityFilter } from '../configuration/configuration'; import { User } from '../user'; import { PROTOCOL_VERSION } from '../constants/languageServer'; @@ -41,6 +41,7 @@ export type ServerSettings = { deviceId?: string; requiredProtocolVersion?: string; enableDeltaFindings?: string; + folderConfigs: FolderConfig[]; }; export class LanguageServerSettings { @@ -80,6 +81,7 @@ export class LanguageServerSettings { integrationVersion: await Configuration.getVersion(), deviceId: user.anonymousId, requiredProtocolVersion: `${PROTOCOL_VERSION}`, + folderConfigs: configuration.getFolderConfigs() }; } } diff --git a/src/test/unit/common/languageServer/languageServer.test.ts b/src/test/unit/common/languageServer/languageServer.test.ts index 8f406a57e..24a01eeac 100644 --- a/src/test/unit/common/languageServer/languageServer.test.ts +++ b/src/test/unit/common/languageServer/languageServer.test.ts @@ -223,6 +223,7 @@ suite('Language Server', () => { insecure: 'true', requiredProtocolVersion: '12', scanningMode: 'auto', + folderConfigs: [] }; deepStrictEqual(await languageServer.getInitializationOptions(), expectedInitializationOptions); From 69c9e77553daaa18054bf6cee05b5d124257fbdb Mon Sep 17 00:00:00 2001 From: Abdelrahman Shawki Hassan Date: Fri, 19 Jul 2024 14:48:23 +0200 Subject: [PATCH 2/8] fix: render base branch treenode correctly --- src/snyk/base/modules/baseSnykModule.ts | 3 ++ src/snyk/common/configuration/folderConfig.ts | 40 -------------- .../common/configuration/folderConfigs.ts | 54 +++++++++++++++++++ src/snyk/common/constants/commands.ts | 1 + src/snyk/common/views/issueTreeProvider.ts | 54 +++++++++++-------- src/snyk/extension.ts | 4 ++ src/snyk/snykCode/views/issueTreeProvider.ts | 4 +- .../views/qualityIssueTreeProvider.ts | 4 +- .../views/securityIssueTreeProvider.ts | 4 +- .../snykIac/views/iacIssueTreeProvider.ts | 4 +- .../providers/ossVulnerabilityTreeProvider.ts | 4 +- .../integration/issueTreeProvider.test.ts | 13 +++-- 12 files changed, 118 insertions(+), 71 deletions(-) delete mode 100644 src/snyk/common/configuration/folderConfig.ts create mode 100644 src/snyk/common/configuration/folderConfigs.ts diff --git a/src/snyk/base/modules/baseSnykModule.ts b/src/snyk/base/modules/baseSnykModule.ts index a62eb6785..5570aced7 100644 --- a/src/snyk/base/modules/baseSnykModule.ts +++ b/src/snyk/base/modules/baseSnykModule.ts @@ -1,4 +1,5 @@ import { CommandController } from '../../common/commands/commandController'; +import { FolderConfigs, IFolderConfigs } from '../../common/configuration/folderConfigs'; import { IWorkspaceTrust, WorkspaceTrust } from '../../common/configuration/trustedFolders'; import { ExperimentService } from '../../common/experiment/services/experimentService'; import { ILanguageServer } from '../../common/languageServer/languageServer'; @@ -63,6 +64,7 @@ export default abstract class BaseSnykModule implements IBaseSnykModule { protected markdownStringAdapter: IMarkdownStringAdapter; readonly workspaceTrust: IWorkspaceTrust; readonly codeActionKindAdapter: ICodeActionKindAdapter; + readonly folderConfigs: IFolderConfigs; constructor() { this.statusBarItem = new SnykStatusBarItem(); @@ -74,6 +76,7 @@ export default abstract class BaseSnykModule implements IBaseSnykModule { this.markdownStringAdapter = new MarkdownStringAdapter(); this.workspaceTrust = new WorkspaceTrust(); this.codeActionKindAdapter = new CodeActionKindAdapter(); + this.folderConfigs = new FolderConfigs(); } abstract runScan(): Promise; diff --git a/src/snyk/common/configuration/folderConfig.ts b/src/snyk/common/configuration/folderConfig.ts deleted file mode 100644 index 2a2c19bc2..000000000 --- a/src/snyk/common/configuration/folderConfig.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { FolderConfig, IConfiguration } from './configuration'; - -export interface IFolderConfigs { - getFolderConfigs(config: IConfiguration): ReadonlyArray; - - resetFolderConfigsCache(): void; -} - -export class FolderConfigs implements IFolderConfigs { - private folderConfigsCache?: ReadonlyArray; - - getFolderConfigs(config: IConfiguration): ReadonlyArray { - if (this.folderConfigsCache !== undefined) { - return this.folderConfigsCache; - } - - const folderConfigs = config.getFolderConfigs(); - - this.folderConfigsCache = folderConfigs; - - return folderConfigs; - } - - setFolderConfig(_config: IConfiguration, _folderPath: string, _baseBranch: string) { - // Get all folders and update one you need and use setFolderConfig. - // if (this.folderConfigsCache !== undefined) { - // return this.folderConfigsCache; - // } - - // const folderConfigs = config.getFolderConfigs(); - - // this.folderConfigsCache = folderConfigs; - - // return folderConfigs; - } - - resetFolderConfigsCache() { - this.folderConfigsCache = undefined; - } -} diff --git a/src/snyk/common/configuration/folderConfigs.ts b/src/snyk/common/configuration/folderConfigs.ts new file mode 100644 index 000000000..500cd41c7 --- /dev/null +++ b/src/snyk/common/configuration/folderConfigs.ts @@ -0,0 +1,54 @@ +import { IVSCodeWindow } from '../vscode/window'; +import { FolderConfig, IConfiguration } from './configuration'; + +export interface IFolderConfigs { + getFolderConfigs(config: IConfiguration): ReadonlyArray; + getFolderConfig(config: IConfiguration, folderPath: string): FolderConfig | undefined; + setFolderConfig(config: IConfiguration, folderConfig: FolderConfig): void; + //setBranch(config: IConfiguration, folderPath: string, branchName: string): void; + resetFolderConfigsCache(): void; +} + +export class FolderConfigs implements IFolderConfigs { + private folderConfigsCache?: ReadonlyArray; + + getFolderConfig(config: IConfiguration, folderPath: string): FolderConfig | undefined { + const folderConfigs = this.getFolderConfigs(config); + return folderConfigs.find(i => i.folderPath === folderPath); + } + + getFolderConfigs(config: IConfiguration): ReadonlyArray { + if (this.folderConfigsCache !== undefined) { + return this.folderConfigsCache; + } + const folderConfigs = config.getFolderConfigs(); + this.folderConfigsCache = folderConfigs; + + return folderConfigs; + } + + // async setBranch(window: IVSCodeWindow, config: IConfiguration, folderPath: string) : Promise { + // const branchName = await window.showInputBox({ + // placeHolder: '', + // // validateInput: _input => { + // // return; + // // }, + // }); + // let folderConfig = this.getFolderConfig(config, folderPath); + // if (!folderConfig || !branchName) { + // return; + // } + // folderConfig.baseBranch = branchName; + // this.setFolderConfig(config, folderConfig); + // } + + setFolderConfig(config: IConfiguration, folderConfig: FolderConfig){ + const currentFolderConfigs = this.getFolderConfigs(config); + const finalFolderConfigs = currentFolderConfigs.map(i => i.folderPath === folderConfig.folderPath ? folderConfig : i); + config.setFolderConfigs(finalFolderConfigs); + } + + resetFolderConfigsCache() { + this.folderConfigsCache = undefined; + } +} diff --git a/src/snyk/common/constants/commands.ts b/src/snyk/common/constants/commands.ts index 6a118f9d4..ed4d71000 100644 --- a/src/snyk/common/constants/commands.ts +++ b/src/snyk/common/constants/commands.ts @@ -18,6 +18,7 @@ export const SNYK_SHOW_OUTPUT_COMMAND = 'snyk.showOutputChannel'; export const SNYK_SHOW_LS_OUTPUT_COMMAND = 'snyk.showLsOutputChannel'; export const SNYK_GET_LESSON_COMMAND = 'snyk.getLearnLesson'; export const SNYK_GET_SETTINGS_SAST_ENABLED = 'snyk.getSettingsSastEnabled'; +export const SNYK_SET_BASE_BRANCH_COMMAND = 'snyk.setBaseBranch'; // commands export const SNYK_LOGIN_COMMAND = 'snyk.login'; export const SNYK_WORKSPACE_SCAN_COMMAND = 'snyk.workspace.scan'; diff --git a/src/snyk/common/views/issueTreeProvider.ts b/src/snyk/common/views/issueTreeProvider.ts index 9d89ed09b..206be2662 100644 --- a/src/snyk/common/views/issueTreeProvider.ts +++ b/src/snyk/common/views/issueTreeProvider.ts @@ -9,6 +9,7 @@ import { AnalysisTreeNodeProvider } from '../../common/views/analysisTreeNodePro import { INodeIcon, InternalType, NODE_ICONS, TreeNode } from '../../common/views/treeNode'; import { IVSCodeLanguages } from '../../common/vscode/languages'; import { Command, Range } from '../../common/vscode/types'; +import { IFolderConfigs } from '../configuration/folderConfigs'; interface ISeverityCounts { [severity: string]: number; @@ -20,6 +21,7 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid protected readonly productService: IProductService, protected readonly configuration: IConfiguration, protected readonly languages: IVSCodeLanguages, + protected readonly folderConfigs: IFolderConfigs, ) { super(configuration, productService); } @@ -82,7 +84,6 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid if (allFailed) { return nodes; } - nodes.sort(this.compareNodes); const totalIssueCount = this.getTotalIssueCount(); @@ -95,11 +96,6 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid this.getFixableIssuesNode(this.getFixableCount()), ]; - const isSnykCodeProduct = (this.productService as ProductService).getSnykProductType() === ScanProduct.Code; - if (isSnykCodeProduct) { - topNodes.unshift(this.getBaseBranch()); - } - const noSeverityFiltersSelectedWarning = this.getNoSeverityFiltersSelectedTreeNode(); if (noSeverityFiltersSelectedWarning !== null) { topNodes.push(noSeverityFiltersSelectedWarning); @@ -110,22 +106,20 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid ); topNodes.push(noIssueViewOptionSelectedWarning); } + const validTopNodes = topNodes.filter((n): n is TreeNode => n !== null); + const baseIndex = nodes.findIndex(node => { + if (!node.label) { + return false; + } + return node.label.toString().toLowerCase().indexOf("base branch") > -1; + }); - nodes.unshift(...topNodes.filter((n): n is TreeNode => n !== null)); - return nodes; - } - - getBaseBranch(): TreeNode | null { - const deltaFindingsEnabled = this.configuration.getDeltaFindingsEnabled(); - - //TODO: get the actual base branch from Snyk Language Server - if (deltaFindingsEnabled) { - return new TreeNode({ - text: 'Base branch: main', - icon: NODE_ICONS.branch, - }); + if (baseIndex > -1) { + nodes.splice(baseIndex + 1, 0, ...validTopNodes); + } else { + nodes.unshift(...validTopNodes); } - return null; + return nodes; } getFixableIssuesNode(_fixableIssueCount: number): TreeNode | null { @@ -179,6 +173,17 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid return false; } + getBaseBranch(folderPath: string): TreeNode | undefined { + const deltaFindingsEnabled = this.configuration.getDeltaFindingsEnabled(); + const config = this.folderConfigs.getFolderConfig(this.configuration, folderPath); + if (deltaFindingsEnabled && config) { + return new TreeNode({ + text: 'Base branch: ' + config.baseBranch, + icon: NODE_ICONS.branch, + }); + } + } + getResultNodes(): TreeNode[] { const nodes: TreeNode[] = []; @@ -188,6 +193,7 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid const uri = vscode.Uri.file(folderPath); const shortFolderPath = uri.path.split('/'); + // TODO: this might need to be changed to uri.fspath const folderName = shortFolderPath.pop() || uri.path; let folderVulnCount = 0; @@ -271,9 +277,12 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid if (folderVulnCount == 0) { continue; } - + const baseBranchNode = this.getBaseBranch(uri.fsPath); // flatten results if single workspace folder if (this.productService.result.size == 1) { + if (baseBranchNode) { + nodes.unshift(baseBranchNode); + } nodes.push(...fileNodes); } else { const folderNode = new TreeNode({ @@ -286,6 +295,9 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid severity: ProductIssueTreeProvider.getSeverityComparatorIndex(folderSeverity), }, }); + if (baseBranchNode) { + fileNodes.unshift(baseBranchNode); + } nodes.push(folderNode); } } diff --git a/src/snyk/extension.ts b/src/snyk/extension.ts index 57b717032..bf648c676 100644 --- a/src/snyk/extension.ts +++ b/src/snyk/extension.ts @@ -257,6 +257,7 @@ class SnykExtension extends SnykLib implements IExtension { this.snykCode, configuration, vsCodeLanguages, + this.folderConfigs, ); const codeQualityIssueProvider = new CodeQualityIssueTreeProvider( @@ -265,6 +266,7 @@ class SnykExtension extends SnykLib implements IExtension { this.snykCode, configuration, vsCodeLanguages, + this.folderConfigs, ); let securityCodeView = SNYK_VIEW_ANALYSIS_CODE_SECURITY; @@ -306,6 +308,7 @@ class SnykExtension extends SnykLib implements IExtension { this.ossService, configuration, vsCodeLanguages, + this.folderConfigs ); const ossSecurityTree = vscode.window.createTreeView(SNYK_VIEW_ANALYSIS_OSS, { @@ -323,6 +326,7 @@ class SnykExtension extends SnykLib implements IExtension { this.iacService, configuration, vsCodeLanguages, + this.folderConfigs ); const iacSecurityTree = vscode.window.createTreeView(SNYK_VIEW_ANALYSIS_IAC, { diff --git a/src/snyk/snykCode/views/issueTreeProvider.ts b/src/snyk/snykCode/views/issueTreeProvider.ts index d8b91ef84..43d0c131d 100644 --- a/src/snyk/snykCode/views/issueTreeProvider.ts +++ b/src/snyk/snykCode/views/issueTreeProvider.ts @@ -11,6 +11,7 @@ import { messages } from '../messages/analysis'; import { IssueUtils } from '../utils/issueUtils'; import { CodeIssueCommandArg } from './interfaces'; import { TreeNode } from '../../common/views/treeNode'; +import { IFolderConfigs } from '../../common/configuration/folderConfigs'; export class IssueTreeProvider extends ProductIssueTreeProvider { constructor( @@ -19,8 +20,9 @@ export class IssueTreeProvider extends ProductIssueTreeProvider { protected configuration: IConfiguration, protected languages: IVSCodeLanguages, protected readonly isSecurityType: boolean, + protected readonly folderConfigs: IFolderConfigs, ) { - super(contextService, codeService, configuration, languages); + super(contextService, codeService, configuration, languages, folderConfigs); } shouldShowTree(): boolean { diff --git a/src/snyk/snykCode/views/qualityIssueTreeProvider.ts b/src/snyk/snykCode/views/qualityIssueTreeProvider.ts index 5cd70e2d1..e4c70b3d8 100644 --- a/src/snyk/snykCode/views/qualityIssueTreeProvider.ts +++ b/src/snyk/snykCode/views/qualityIssueTreeProvider.ts @@ -1,4 +1,5 @@ import { IConfiguration } from '../../common/configuration/configuration'; +import { IFolderConfigs } from '../../common/configuration/folderConfigs'; import { configuration } from '../../common/configuration/instance'; import { SNYK_ANALYSIS_STATUS } from '../../common/constants/views'; import { CodeIssueData } from '../../common/languageServer/types'; @@ -16,8 +17,9 @@ export class CodeQualityIssueTreeProvider extends IssueTreeProvider { protected codeService: IProductService, protected configuration: IConfiguration, protected languages: IVSCodeLanguages, + protected readonly folderConfigs: IFolderConfigs, ) { - super(contextService, codeService, configuration, languages, false); + super(contextService, codeService, configuration, languages, false, folderConfigs); } getRootChildren(): TreeNode[] { diff --git a/src/snyk/snykCode/views/securityIssueTreeProvider.ts b/src/snyk/snykCode/views/securityIssueTreeProvider.ts index 590e33844..5abce6f0a 100644 --- a/src/snyk/snykCode/views/securityIssueTreeProvider.ts +++ b/src/snyk/snykCode/views/securityIssueTreeProvider.ts @@ -9,6 +9,7 @@ import { TreeNode } from '../../common/views/treeNode'; import { IVSCodeLanguages } from '../../common/vscode/languages'; import { IssueTreeProvider } from './issueTreeProvider'; import { FEATURE_FLAGS } from '../../common/constants/featureFlags'; +import { IFolderConfigs } from '../../common/configuration/folderConfigs'; export default class CodeSecurityIssueTreeProvider extends IssueTreeProvider { constructor( @@ -17,8 +18,9 @@ export default class CodeSecurityIssueTreeProvider extends IssueTreeProvider { protected codeService: IProductService, protected configuration: IConfiguration, protected languages: IVSCodeLanguages, + protected readonly folderConfigs: IFolderConfigs, ) { - super(contextService, codeService, configuration, languages, true); + super(contextService, codeService, configuration, languages, true, folderConfigs); } getRootChildren(): TreeNode[] { diff --git a/src/snyk/snykIac/views/iacIssueTreeProvider.ts b/src/snyk/snykIac/views/iacIssueTreeProvider.ts index 2a676ee33..73f31579a 100644 --- a/src/snyk/snykIac/views/iacIssueTreeProvider.ts +++ b/src/snyk/snykIac/views/iacIssueTreeProvider.ts @@ -14,6 +14,7 @@ import { IVSCodeLanguages } from '../../common/vscode/languages'; import { IacIssue } from '../issue'; import { messages } from '../messages/analysis'; import { IacIssueCommandArg } from './interfaces'; +import { IFolderConfigs } from '../../common/configuration/folderConfigs'; export default class IacIssueTreeProvider extends ProductIssueTreeProvider { constructor( @@ -22,8 +23,9 @@ export default class IacIssueTreeProvider extends ProductIssueTreeProvider, protected configuration: IConfiguration, protected languages: IVSCodeLanguages, + protected readonly folderConfigs: IFolderConfigs, ) { - super(contextService, iacService, configuration, languages); + super(contextService, iacService, configuration, languages, folderConfigs); } getRootChildren(): TreeNode[] { diff --git a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts index ccd1c06df..21f14e09f 100644 --- a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts +++ b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts @@ -14,6 +14,7 @@ import { TreeNode } from '../../common/views/treeNode'; import { IVSCodeLanguages } from '../../common/vscode/languages'; import { messages } from '../constants/messages'; import { getOssIssueCommandArg } from './ossIssueCommandHelper'; +import { IFolderConfigs } from '../../common/configuration/folderConfigs'; export default class OssIssueTreeProvider extends ProductIssueTreeProvider { constructor( @@ -22,8 +23,9 @@ export default class OssIssueTreeProvider extends ProductIssueTreeProvider, protected configuration: IConfiguration, protected languages: IVSCodeLanguages, + protected readonly folderConfigs: IFolderConfigs, ) { - super(contextService, ossService, configuration, languages); + super(contextService, ossService, configuration, languages, folderConfigs); } getRootChildren(): TreeNode[] { diff --git a/src/test/integration/issueTreeProvider.test.ts b/src/test/integration/issueTreeProvider.test.ts index c982d262b..8bcfdaf23 100644 --- a/src/test/integration/issueTreeProvider.test.ts +++ b/src/test/integration/issueTreeProvider.test.ts @@ -10,11 +10,13 @@ import { strictEqual } from 'assert'; import { FEATURE_FLAGS } from '../../snyk/common/constants/featureFlags'; import { configuration } from '../../snyk/common/configuration/instance'; import { ISSUE_VIEW_OPTIONS_SETTING } from '../../snyk/common/constants/settings'; +import { IFolderConfigs } from '../../snyk/common/configuration/folderConfigs'; suite('Code Issue Tree Provider', () => { let contextService: IContextService; let codeService: IProductService; let languages: IVSCodeLanguages; + let folderConfigs: IFolderConfigs; let issueTreeProvider: IssueTreeProvider; @@ -34,6 +36,7 @@ suite('Code Issue Tree Provider', () => { } as unknown as IProductService; configuration.setFeatureFlag(FEATURE_FLAGS.consistentIgnores, true); languages = {} as unknown as IVSCodeLanguages; + folderConfigs = {} as unknown as IFolderConfigs; }); teardown(() => { @@ -60,7 +63,7 @@ suite('Code Issue Tree Provider', () => { }, } as unknown as IProductService; - issueTreeProvider = new IssueTreeProvider(contextService, localCodeService, configuration, languages, true); + issueTreeProvider = new IssueTreeProvider(contextService, localCodeService, configuration, languages, true, folderConfigs); sinon.stub(issueTreeProvider, 'getResultNodes').returns([]); const rootChildren = issueTreeProvider.getRootChildren(); @@ -77,7 +80,7 @@ suite('Code Issue Tree Provider', () => { }, } as unknown as IProductService; - issueTreeProvider = new IssueTreeProvider(contextService, localCodeService, configuration, languages, true); + issueTreeProvider = new IssueTreeProvider(contextService, localCodeService, configuration, languages, true, folderConfigs); sinon.stub(issueTreeProvider, 'getResultNodes').returns([]); const rootChildren = issueTreeProvider.getRootChildren(); @@ -111,7 +114,7 @@ suite('Code Issue Tree Provider', () => { ignoredIssues: true, }); configuration.issueViewOptions.openIssues = false; - issueTreeProvider = new IssueTreeProvider(contextService, localCodeService, configuration, languages, true); + issueTreeProvider = new IssueTreeProvider(contextService, localCodeService, configuration, languages, true, folderConfigs); sinon.stub(issueTreeProvider, 'getResultNodes').returns([]); const rootChildren = issueTreeProvider.getRootChildren(); @@ -149,7 +152,7 @@ suite('Code Issue Tree Provider', () => { openIssues: true, ignoredIssues: false, }); - issueTreeProvider = new IssueTreeProvider(contextService, localCodeService, configuration, languages, true); + issueTreeProvider = new IssueTreeProvider(contextService, localCodeService, configuration, languages, true, folderConfigs); sinon.stub(issueTreeProvider, 'getResultNodes').returns([]); const rootChildren = issueTreeProvider.getRootChildren(); @@ -187,7 +190,7 @@ suite('Code Issue Tree Provider', () => { openIssues: false, ignoredIssues: false, }); - issueTreeProvider = new IssueTreeProvider(contextService, localCodeService, configuration, languages, true); + issueTreeProvider = new IssueTreeProvider(contextService, localCodeService, configuration, languages, true, folderConfigs); sinon.stub(issueTreeProvider, 'getResultNodes').returns([]); const rootChildren = issueTreeProvider.getRootChildren(); From 2cf450d199230c631ce680a7be19701758940be6 Mon Sep 17 00:00:00 2001 From: Abdelrahman Shawki Hassan Date: Fri, 19 Jul 2024 15:51:52 +0200 Subject: [PATCH 3/8] feat: setBaseBranch command --- src/snyk/common/commands/commandController.ts | 7 +++ .../common/configuration/folderConfigs.ts | 43 ++++++++++++------- src/snyk/common/views/issueTreeProvider.ts | 7 +++ .../common/watchers/configurationWatcher.ts | 4 +- src/snyk/extension.ts | 4 ++ .../common/commands/commandController.test.ts | 4 ++ 6 files changed, 53 insertions(+), 16 deletions(-) diff --git a/src/snyk/common/commands/commandController.ts b/src/snyk/common/commands/commandController.ts index c96b1b6c3..ad00edfeb 100644 --- a/src/snyk/common/commands/commandController.ts +++ b/src/snyk/common/commands/commandController.ts @@ -27,6 +27,8 @@ import { IUriAdapter } from '../vscode/uri'; import { IVSCodeWindow } from '../vscode/window'; import { IVSCodeWorkspace } from '../vscode/workspace'; import { OpenCommandIssueType, OpenIssueCommandArg } from './types'; +import { IFolderConfigs } from '../configuration/folderConfigs'; +import { IConfiguration } from '../configuration/configuration'; export class CommandController { private debouncedCommands: Record Promise>> = {}; @@ -43,6 +45,8 @@ export class CommandController { private window: IVSCodeWindow, private languageServer: ILanguageServer, private logger: ILog, + private configuration: IConfiguration, + private folderConfigs: IFolderConfigs, ) {} openBrowser(url: string): unknown { @@ -75,6 +79,9 @@ export class CommandController { ErrorHandler.handle(e, this.logger); } } + async setBaseBranch(folderPath:string): Promise { + this.folderConfigs.setBranch(this.window, this.configuration, folderPath); + } openSettings(): void { void this.commands.executeCommand(VSCODE_GO_TO_SETTINGS_COMMAND, `@ext:${SNYK_PUBLISHER}.${SNYK_NAME_EXTENSION}`); diff --git a/src/snyk/common/configuration/folderConfigs.ts b/src/snyk/common/configuration/folderConfigs.ts index 500cd41c7..42e3884b1 100644 --- a/src/snyk/common/configuration/folderConfigs.ts +++ b/src/snyk/common/configuration/folderConfigs.ts @@ -5,7 +5,7 @@ export interface IFolderConfigs { getFolderConfigs(config: IConfiguration): ReadonlyArray; getFolderConfig(config: IConfiguration, folderPath: string): FolderConfig | undefined; setFolderConfig(config: IConfiguration, folderConfig: FolderConfig): void; - //setBranch(config: IConfiguration, folderPath: string, branchName: string): void; + setBranch(window: IVSCodeWindow, config: IConfiguration, folderPath: string): Promise; resetFolderConfigsCache(): void; } @@ -27,20 +27,33 @@ export class FolderConfigs implements IFolderConfigs { return folderConfigs; } - // async setBranch(window: IVSCodeWindow, config: IConfiguration, folderPath: string) : Promise { - // const branchName = await window.showInputBox({ - // placeHolder: '', - // // validateInput: _input => { - // // return; - // // }, - // }); - // let folderConfig = this.getFolderConfig(config, folderPath); - // if (!folderConfig || !branchName) { - // return; - // } - // folderConfig.baseBranch = branchName; - // this.setFolderConfig(config, folderConfig); - // } + async setBranch(window: IVSCodeWindow, config: IConfiguration, folderPath: string) : Promise { + let folderConfig = this.getFolderConfig(config, folderPath); + + if (!folderConfig){ + return; + } + + const branchName = await window.showInputBox({ + placeHolder: '', + validateInput: input => { + const valid = this.validateBranchName(input, folderConfig.localBranches ?? []); + if (!valid) { + return 'The chosen branch name doesn\'t exist.'; + } + }, + }); + if (!branchName) { + return; + } + + folderConfig.baseBranch = branchName; + this.setFolderConfig(config, folderConfig); + } + + validateBranchName(branchName: string, branchList: string[]): boolean { + return branchList.includes(branchName); + } setFolderConfig(config: IConfiguration, folderConfig: FolderConfig){ const currentFolderConfigs = this.getFolderConfigs(config); diff --git a/src/snyk/common/views/issueTreeProvider.ts b/src/snyk/common/views/issueTreeProvider.ts index 206be2662..b67a56a84 100644 --- a/src/snyk/common/views/issueTreeProvider.ts +++ b/src/snyk/common/views/issueTreeProvider.ts @@ -10,6 +10,7 @@ import { INodeIcon, InternalType, NODE_ICONS, TreeNode } from '../../common/view import { IVSCodeLanguages } from '../../common/vscode/languages'; import { Command, Range } from '../../common/vscode/types'; import { IFolderConfigs } from '../configuration/folderConfigs'; +import { SNYK_SET_BASE_BRANCH_COMMAND } from '../constants/commands'; interface ISeverityCounts { [severity: string]: number; @@ -176,10 +177,16 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid getBaseBranch(folderPath: string): TreeNode | undefined { const deltaFindingsEnabled = this.configuration.getDeltaFindingsEnabled(); const config = this.folderConfigs.getFolderConfig(this.configuration, folderPath); + if (deltaFindingsEnabled && config) { return new TreeNode({ text: 'Base branch: ' + config.baseBranch, icon: NODE_ICONS.branch, + command: { + command: SNYK_SET_BASE_BRANCH_COMMAND, + title: 'Choose Base Branch', + arguments: [folderPath], + } }); } } diff --git a/src/snyk/common/watchers/configurationWatcher.ts b/src/snyk/common/watchers/configurationWatcher.ts index 06e6157c3..7ce0c9b74 100644 --- a/src/snyk/common/watchers/configurationWatcher.ts +++ b/src/snyk/common/watchers/configurationWatcher.ts @@ -17,6 +17,7 @@ import { SEVERITY_FILTER_SETTING, TRUSTED_FOLDERS, DELTA_FINDINGS, + FOLDER_CONFIGS, } from '../constants/settings'; import { ErrorHandler } from '../error/errorHandler'; import { ILog } from '../logger/interfaces'; @@ -47,7 +48,7 @@ class ConfigurationWatcher implements IWatcher { } else if (key === ADVANCED_CUSTOM_LS_PATH || key === DELTA_FINDINGS) { // Language Server client must sync config changes before we can restart return _.debounce(() => extension.restartLanguageServer(), DEFAULT_LS_DEBOUNCE_INTERVAL)(); - } else if (key === TRUSTED_FOLDERS) { + } else if (key === TRUSTED_FOLDERS || key === FOLDER_CONFIGS) { extension.workspaceTrust.resetTrustedFoldersCache(); extension.viewManagerService.refreshAllViews(); } @@ -81,6 +82,7 @@ class ConfigurationWatcher implements IWatcher { TRUSTED_FOLDERS, ISSUE_VIEW_OPTIONS_SETTING, DELTA_FINDINGS, + FOLDER_CONFIGS ].find(config => event.affectsConfiguration(config)); if (change) { diff --git a/src/snyk/extension.ts b/src/snyk/extension.ts index bf648c676..d2a1de06a 100644 --- a/src/snyk/extension.ts +++ b/src/snyk/extension.ts @@ -18,6 +18,7 @@ import { SNYK_OPEN_BROWSER_COMMAND, SNYK_OPEN_ISSUE_COMMAND, SNYK_OPEN_LOCAL_COMMAND, + SNYK_SET_BASE_BRANCH_COMMAND, SNYK_SET_TOKEN_COMMAND, SNYK_SETTINGS_COMMAND, SNYK_SHOW_LS_OUTPUT_COMMAND, @@ -248,6 +249,8 @@ class SnykExtension extends SnykLib implements IExtension { vsCodeWindow, this.languageServer, Logger, + configuration, + this.folderConfigs, ); this.registerCommands(vscodeContext); @@ -444,6 +447,7 @@ class SnykExtension extends SnykLib implements IExtension { vscode.commands.registerCommand(SNYK_SHOW_OUTPUT_COMMAND, () => this.commandController.showOutputChannel()), vscode.commands.registerCommand(SNYK_SHOW_LS_OUTPUT_COMMAND, () => this.commandController.showLsOutputChannel()), vscode.commands.registerCommand(SNYK_IGNORE_ISSUE_COMMAND, IgnoreCommand.ignoreIssues), + vscode.commands.registerCommand(SNYK_SET_BASE_BRANCH_COMMAND, (folderPath: string) => this.commandController.setBaseBranch(folderPath)), ); } } diff --git a/src/test/unit/common/commands/commandController.test.ts b/src/test/unit/common/commands/commandController.test.ts index 45e6e6317..144b1808c 100644 --- a/src/test/unit/common/commands/commandController.test.ts +++ b/src/test/unit/common/commands/commandController.test.ts @@ -13,6 +13,8 @@ import { OssService } from '../../../../snyk/snykOss/ossService'; import { LanguageServerMock } from '../../mocks/languageServer.mock'; import { LoggerMock } from '../../mocks/logger.mock'; import { windowMock } from '../../mocks/window.mock'; +import { IConfiguration } from '../../../../snyk/common/configuration/configuration'; +import { IFolderConfigs } from '../../../../snyk/common/configuration/folderConfigs'; suite('CommandController', () => { const sleep = util.promisify(setTimeout); @@ -32,6 +34,8 @@ suite('CommandController', () => { windowMock, new LanguageServerMock(), new LoggerMock(), + {} as IConfiguration, + {} as IFolderConfigs ); }); From d660592432a4e9fb33fc7819913fc87d3d6c0412 Mon Sep 17 00:00:00 2001 From: Abdelrahman Shawki Hassan Date: Fri, 19 Jul 2024 16:16:15 +0200 Subject: [PATCH 4/8] chore: lint --- src/snyk/common/commands/commandController.ts | 4 +- .../common/configuration/configuration.ts | 4 +- .../common/configuration/folderConfigs.ts | 18 ++++---- src/snyk/common/languageServer/settings.ts | 2 +- src/snyk/common/views/issueTreeProvider.ts | 21 +++++---- .../common/watchers/configurationWatcher.ts | 2 +- src/snyk/extension.ts | 8 ++-- .../integration/issueTreeProvider.test.ts | 45 ++++++++++++++++--- .../common/commands/commandController.test.ts | 2 +- .../languageServer/languageServer.test.ts | 2 +- 10 files changed, 76 insertions(+), 32 deletions(-) diff --git a/src/snyk/common/commands/commandController.ts b/src/snyk/common/commands/commandController.ts index ad00edfeb..bcc550b93 100644 --- a/src/snyk/common/commands/commandController.ts +++ b/src/snyk/common/commands/commandController.ts @@ -79,8 +79,8 @@ export class CommandController { ErrorHandler.handle(e, this.logger); } } - async setBaseBranch(folderPath:string): Promise { - this.folderConfigs.setBranch(this.window, this.configuration, folderPath); + async setBaseBranch(folderPath: string): Promise { + await this.folderConfigs.setBranch(this.window, this.configuration, folderPath); } openSettings(): void { diff --git a/src/snyk/common/configuration/configuration.ts b/src/snyk/common/configuration/configuration.ts index 7a31d5899..6e65d0911 100644 --- a/src/snyk/common/configuration/configuration.ts +++ b/src/snyk/common/configuration/configuration.ts @@ -37,7 +37,6 @@ export type FeaturesConfiguration = { iacEnabled: boolean | undefined; }; - export type FolderConfig = { folderPath: string; baseBranch: string; @@ -477,7 +476,8 @@ export class Configuration implements IConfiguration { getFolderConfigs(): FolderConfig[] { return ( - this.workspace.getConfiguration(CONFIGURATION_IDENTIFIER, this.getConfigName(FOLDER_CONFIGS)) || [] + this.workspace.getConfiguration(CONFIGURATION_IDENTIFIER, this.getConfigName(FOLDER_CONFIGS)) || + [] ); } diff --git a/src/snyk/common/configuration/folderConfigs.ts b/src/snyk/common/configuration/folderConfigs.ts index 42e3884b1..1dc895503 100644 --- a/src/snyk/common/configuration/folderConfigs.ts +++ b/src/snyk/common/configuration/folderConfigs.ts @@ -4,7 +4,7 @@ import { FolderConfig, IConfiguration } from './configuration'; export interface IFolderConfigs { getFolderConfigs(config: IConfiguration): ReadonlyArray; getFolderConfig(config: IConfiguration, folderPath: string): FolderConfig | undefined; - setFolderConfig(config: IConfiguration, folderConfig: FolderConfig): void; + setFolderConfig(config: IConfiguration, folderConfig: FolderConfig): Promise; setBranch(window: IVSCodeWindow, config: IConfiguration, folderPath: string): Promise; resetFolderConfigsCache(): void; } @@ -27,10 +27,10 @@ export class FolderConfigs implements IFolderConfigs { return folderConfigs; } - async setBranch(window: IVSCodeWindow, config: IConfiguration, folderPath: string) : Promise { + async setBranch(window: IVSCodeWindow, config: IConfiguration, folderPath: string): Promise { let folderConfig = this.getFolderConfig(config, folderPath); - if (!folderConfig){ + if (!folderConfig) { return; } @@ -39,7 +39,7 @@ export class FolderConfigs implements IFolderConfigs { validateInput: input => { const valid = this.validateBranchName(input, folderConfig.localBranches ?? []); if (!valid) { - return 'The chosen branch name doesn\'t exist.'; + return "The chosen branch name doesn't exist."; } }, }); @@ -48,17 +48,19 @@ export class FolderConfigs implements IFolderConfigs { } folderConfig.baseBranch = branchName; - this.setFolderConfig(config, folderConfig); + await this.setFolderConfig(config, folderConfig); } validateBranchName(branchName: string, branchList: string[]): boolean { return branchList.includes(branchName); } - setFolderConfig(config: IConfiguration, folderConfig: FolderConfig){ + async setFolderConfig(config: IConfiguration, folderConfig: FolderConfig): Promise { const currentFolderConfigs = this.getFolderConfigs(config); - const finalFolderConfigs = currentFolderConfigs.map(i => i.folderPath === folderConfig.folderPath ? folderConfig : i); - config.setFolderConfigs(finalFolderConfigs); + const finalFolderConfigs = currentFolderConfigs.map(i => + i.folderPath === folderConfig.folderPath ? folderConfig : i, + ); + await config.setFolderConfigs(finalFolderConfigs); } resetFolderConfigsCache() { diff --git a/src/snyk/common/languageServer/settings.ts b/src/snyk/common/languageServer/settings.ts index 4276429c9..0d88633ee 100644 --- a/src/snyk/common/languageServer/settings.ts +++ b/src/snyk/common/languageServer/settings.ts @@ -81,7 +81,7 @@ export class LanguageServerSettings { integrationVersion: await Configuration.getVersion(), deviceId: user.anonymousId, requiredProtocolVersion: `${PROTOCOL_VERSION}`, - folderConfigs: configuration.getFolderConfigs() + folderConfigs: configuration.getFolderConfigs(), }; } } diff --git a/src/snyk/common/views/issueTreeProvider.ts b/src/snyk/common/views/issueTreeProvider.ts index b67a56a84..361e164b7 100644 --- a/src/snyk/common/views/issueTreeProvider.ts +++ b/src/snyk/common/views/issueTreeProvider.ts @@ -108,17 +108,18 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid topNodes.push(noIssueViewOptionSelectedWarning); } const validTopNodes = topNodes.filter((n): n is TreeNode => n !== null); - const baseIndex = nodes.findIndex(node => { - if (!node.label) { + const baseBranchNodeIndex = nodes.findIndex(node => { + const label = node.label as string; + if (!label) { return false; } - return node.label.toString().toLowerCase().indexOf("base branch") > -1; - }); + return label.toLowerCase().indexOf('base branch') > -1; + }); - if (baseIndex > -1) { - nodes.splice(baseIndex + 1, 0, ...validTopNodes); + if (baseBranchNodeIndex > -1) { + nodes.splice(baseBranchNodeIndex + 1, 0, ...validTopNodes); } else { - nodes.unshift(...validTopNodes); + nodes.unshift(...validTopNodes); } return nodes; } @@ -175,6 +176,10 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid } getBaseBranch(folderPath: string): TreeNode | undefined { + const isSnykCodeProduct = (this.productService as ProductService).getSnykProductType() === ScanProduct.Code; + if (!isSnykCodeProduct) { + return; + } const deltaFindingsEnabled = this.configuration.getDeltaFindingsEnabled(); const config = this.folderConfigs.getFolderConfig(this.configuration, folderPath); @@ -186,7 +191,7 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid command: SNYK_SET_BASE_BRANCH_COMMAND, title: 'Choose Base Branch', arguments: [folderPath], - } + }, }); } } diff --git a/src/snyk/common/watchers/configurationWatcher.ts b/src/snyk/common/watchers/configurationWatcher.ts index 7ce0c9b74..f32ff7c3c 100644 --- a/src/snyk/common/watchers/configurationWatcher.ts +++ b/src/snyk/common/watchers/configurationWatcher.ts @@ -82,7 +82,7 @@ class ConfigurationWatcher implements IWatcher { TRUSTED_FOLDERS, ISSUE_VIEW_OPTIONS_SETTING, DELTA_FINDINGS, - FOLDER_CONFIGS + FOLDER_CONFIGS, ].find(config => event.affectsConfiguration(config)); if (change) { diff --git a/src/snyk/extension.ts b/src/snyk/extension.ts index d2a1de06a..0cbc53b32 100644 --- a/src/snyk/extension.ts +++ b/src/snyk/extension.ts @@ -311,7 +311,7 @@ class SnykExtension extends SnykLib implements IExtension { this.ossService, configuration, vsCodeLanguages, - this.folderConfigs + this.folderConfigs, ); const ossSecurityTree = vscode.window.createTreeView(SNYK_VIEW_ANALYSIS_OSS, { @@ -329,7 +329,7 @@ class SnykExtension extends SnykLib implements IExtension { this.iacService, configuration, vsCodeLanguages, - this.folderConfigs + this.folderConfigs, ); const iacSecurityTree = vscode.window.createTreeView(SNYK_VIEW_ANALYSIS_IAC, { @@ -447,7 +447,9 @@ class SnykExtension extends SnykLib implements IExtension { vscode.commands.registerCommand(SNYK_SHOW_OUTPUT_COMMAND, () => this.commandController.showOutputChannel()), vscode.commands.registerCommand(SNYK_SHOW_LS_OUTPUT_COMMAND, () => this.commandController.showLsOutputChannel()), vscode.commands.registerCommand(SNYK_IGNORE_ISSUE_COMMAND, IgnoreCommand.ignoreIssues), - vscode.commands.registerCommand(SNYK_SET_BASE_BRANCH_COMMAND, (folderPath: string) => this.commandController.setBaseBranch(folderPath)), + vscode.commands.registerCommand(SNYK_SET_BASE_BRANCH_COMMAND, (folderPath: string) => + this.commandController.setBaseBranch(folderPath), + ), ); } } diff --git a/src/test/integration/issueTreeProvider.test.ts b/src/test/integration/issueTreeProvider.test.ts index 8bcfdaf23..9e34dc72e 100644 --- a/src/test/integration/issueTreeProvider.test.ts +++ b/src/test/integration/issueTreeProvider.test.ts @@ -63,7 +63,14 @@ suite('Code Issue Tree Provider', () => { }, } as unknown as IProductService; - issueTreeProvider = new IssueTreeProvider(contextService, localCodeService, configuration, languages, true, folderConfigs); + issueTreeProvider = new IssueTreeProvider( + contextService, + localCodeService, + configuration, + languages, + true, + folderConfigs, + ); sinon.stub(issueTreeProvider, 'getResultNodes').returns([]); const rootChildren = issueTreeProvider.getRootChildren(); @@ -80,7 +87,14 @@ suite('Code Issue Tree Provider', () => { }, } as unknown as IProductService; - issueTreeProvider = new IssueTreeProvider(contextService, localCodeService, configuration, languages, true, folderConfigs); + issueTreeProvider = new IssueTreeProvider( + contextService, + localCodeService, + configuration, + languages, + true, + folderConfigs, + ); sinon.stub(issueTreeProvider, 'getResultNodes').returns([]); const rootChildren = issueTreeProvider.getRootChildren(); @@ -114,7 +128,14 @@ suite('Code Issue Tree Provider', () => { ignoredIssues: true, }); configuration.issueViewOptions.openIssues = false; - issueTreeProvider = new IssueTreeProvider(contextService, localCodeService, configuration, languages, true, folderConfigs); + issueTreeProvider = new IssueTreeProvider( + contextService, + localCodeService, + configuration, + languages, + true, + folderConfigs, + ); sinon.stub(issueTreeProvider, 'getResultNodes').returns([]); const rootChildren = issueTreeProvider.getRootChildren(); @@ -152,7 +173,14 @@ suite('Code Issue Tree Provider', () => { openIssues: true, ignoredIssues: false, }); - issueTreeProvider = new IssueTreeProvider(contextService, localCodeService, configuration, languages, true, folderConfigs); + issueTreeProvider = new IssueTreeProvider( + contextService, + localCodeService, + configuration, + languages, + true, + folderConfigs, + ); sinon.stub(issueTreeProvider, 'getResultNodes').returns([]); const rootChildren = issueTreeProvider.getRootChildren(); @@ -190,7 +218,14 @@ suite('Code Issue Tree Provider', () => { openIssues: false, ignoredIssues: false, }); - issueTreeProvider = new IssueTreeProvider(contextService, localCodeService, configuration, languages, true, folderConfigs); + issueTreeProvider = new IssueTreeProvider( + contextService, + localCodeService, + configuration, + languages, + true, + folderConfigs, + ); sinon.stub(issueTreeProvider, 'getResultNodes').returns([]); const rootChildren = issueTreeProvider.getRootChildren(); diff --git a/src/test/unit/common/commands/commandController.test.ts b/src/test/unit/common/commands/commandController.test.ts index 144b1808c..5ea6e1d7a 100644 --- a/src/test/unit/common/commands/commandController.test.ts +++ b/src/test/unit/common/commands/commandController.test.ts @@ -35,7 +35,7 @@ suite('CommandController', () => { new LanguageServerMock(), new LoggerMock(), {} as IConfiguration, - {} as IFolderConfigs + {} as IFolderConfigs, ); }); diff --git a/src/test/unit/common/languageServer/languageServer.test.ts b/src/test/unit/common/languageServer/languageServer.test.ts index 24a01eeac..f060cbf3c 100644 --- a/src/test/unit/common/languageServer/languageServer.test.ts +++ b/src/test/unit/common/languageServer/languageServer.test.ts @@ -223,7 +223,7 @@ suite('Language Server', () => { insecure: 'true', requiredProtocolVersion: '12', scanningMode: 'auto', - folderConfigs: [] + folderConfigs: [], }; deepStrictEqual(await languageServer.getInitializationOptions(), expectedInitializationOptions); From 164d22de9b8de10f440b4e826786d57be440f405 Mon Sep 17 00:00:00 2001 From: Abdelrahman Shawki Hassan Date: Fri, 19 Jul 2024 16:31:57 +0200 Subject: [PATCH 5/8] fix: mock getFolderConfigs in tests --- src/test/unit/common/languageServer/languageServer.test.ts | 6 +++++- src/test/unit/common/languageServer/middleware.test.ts | 5 ++++- src/test/unit/common/languageServer/settings.test.ts | 5 ++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/test/unit/common/languageServer/languageServer.test.ts b/src/test/unit/common/languageServer/languageServer.test.ts index f060cbf3c..aa50d7bdd 100644 --- a/src/test/unit/common/languageServer/languageServer.test.ts +++ b/src/test/unit/common/languageServer/languageServer.test.ts @@ -4,7 +4,7 @@ import { ReplaySubject } from 'rxjs'; import sinon from 'sinon'; import { v4 } from 'uuid'; import { IAuthenticationService } from '../../../../snyk/base/services/authenticationService'; -import { IConfiguration } from '../../../../snyk/common/configuration/configuration'; +import { FolderConfig, IConfiguration } from '../../../../snyk/common/configuration/configuration'; import { LanguageServer } from '../../../../snyk/common/languageServer/languageServer'; import { ServerSettings } from '../../../../snyk/common/languageServer/settings'; import { DownloadService } from '../../../../snyk/common/services/downloadService'; @@ -16,6 +16,7 @@ import { defaultFeaturesConfigurationStub } from '../../mocks/configuration.mock import { LoggerMock } from '../../mocks/logger.mock'; import { windowMock } from '../../mocks/window.mock'; import { stubWorkspaceConfiguration } from '../../mocks/workspace.mock'; +import { FolderConfigs } from '../../../../snyk/common/configuration/folderConfigs'; suite('Language Server', () => { const authServiceMock = {} as IAuthenticationService; @@ -75,6 +76,9 @@ suite('Language Server', () => { getTrustedFolders(): string[] { return ['/trusted/test/folder']; }, + getFolderConfigs(): FolderConfig[] { + return []; + }, scanningMode: 'auto', } as IConfiguration; diff --git a/src/test/unit/common/languageServer/middleware.test.ts b/src/test/unit/common/languageServer/middleware.test.ts index 3aeb5d9a6..1a64306fa 100644 --- a/src/test/unit/common/languageServer/middleware.test.ts +++ b/src/test/unit/common/languageServer/middleware.test.ts @@ -1,7 +1,7 @@ import assert from 'assert'; import sinon from 'sinon'; import { CliExecutable } from '../../../../snyk/cli/cliExecutable'; -import { IConfiguration } from '../../../../snyk/common/configuration/configuration'; +import { FolderConfig, IConfiguration } from '../../../../snyk/common/configuration/configuration'; import { LanguageClientMiddleware } from '../../../../snyk/common/languageServer/middleware'; import { ServerSettings } from '../../../../snyk/common/languageServer/settings'; import { User } from '../../../../snyk/common/user'; @@ -49,6 +49,9 @@ suite('Language Server: Middleware', () => { low: true, }, getTrustedFolders: () => ['/trusted/test/folder'], + getFolderConfigs(): FolderConfig[] { + return []; + }, } as IConfiguration; }); diff --git a/src/test/unit/common/languageServer/settings.test.ts b/src/test/unit/common/languageServer/settings.test.ts index e01b3395d..747853948 100644 --- a/src/test/unit/common/languageServer/settings.test.ts +++ b/src/test/unit/common/languageServer/settings.test.ts @@ -1,5 +1,5 @@ import assert from 'assert'; -import { IConfiguration } from '../../../../snyk/common/configuration/configuration'; +import { FolderConfig, IConfiguration } from '../../../../snyk/common/configuration/configuration'; import { LanguageServerSettings } from '../../../../snyk/common/languageServer/settings'; import { User } from '../../../../snyk/common/user'; @@ -20,6 +20,9 @@ suite('LanguageServerSettings', () => { getInsecure: () => false, getDeltaFindingsEnabled: () => false, isAutomaticDependencyManagementEnabled: () => true, + getFolderConfigs(): FolderConfig[] { + return []; + }, severityFilter: { critical: true, high: true, medium: true, low: false }, scanningMode: 'scan-mode', } as IConfiguration; From 1a0d289616859671106ed4094e6cadf4b77489eb Mon Sep 17 00:00:00 2001 From: Abdelrahman Shawki Hassan Date: Fri, 19 Jul 2024 17:26:53 +0200 Subject: [PATCH 6/8] fix: show base branch node if no issues found --- src/snyk/common/views/issueTreeProvider.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/snyk/common/views/issueTreeProvider.ts b/src/snyk/common/views/issueTreeProvider.ts index 361e164b7..122dc1353 100644 --- a/src/snyk/common/views/issueTreeProvider.ts +++ b/src/snyk/common/views/issueTreeProvider.ts @@ -286,15 +286,14 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid const folderSeverity = ProductIssueTreeProvider.getHighestSeverity(folderSeverityCounts); + const baseBranchNode = this.getBaseBranch(uri.fsPath); if (folderVulnCount == 0) { + this.addBaseBranchNode(baseBranchNode, nodes); continue; } - const baseBranchNode = this.getBaseBranch(uri.fsPath); // flatten results if single workspace folder if (this.productService.result.size == 1) { - if (baseBranchNode) { - nodes.unshift(baseBranchNode); - } + this.addBaseBranchNode(baseBranchNode, nodes); nodes.push(...fileNodes); } else { const folderNode = new TreeNode({ @@ -307,9 +306,7 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid severity: ProductIssueTreeProvider.getSeverityComparatorIndex(folderSeverity), }, }); - if (baseBranchNode) { - fileNodes.unshift(baseBranchNode); - } + this.addBaseBranchNode(baseBranchNode, fileNodes); nodes.push(folderNode); } } @@ -317,6 +314,13 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid return nodes; } + private addBaseBranchNode(baseBranchNode: TreeNode | undefined, nodes: TreeNode[]) { + if (!baseBranchNode) { + return; + } + nodes.unshift(baseBranchNode); + } + protected getIssueFoundText(nIssues: number, _: number): string { if (!nIssues) { return '✅ Congrats! No issues found!'; From 88846b3ad1221e574a9d3ce9e6d8aee1f9b4ebc3 Mon Sep 17 00:00:00 2001 From: Catalina Oyaneder Date: Fri, 19 Jul 2024 16:53:53 +0100 Subject: [PATCH 7/8] chroe: add missing keywords --- src/snyk/common/configuration/folderConfigs.ts | 4 ++-- src/snyk/common/views/issueTreeProvider.ts | 8 +++----- .../snykOss/providers/ossVulnerabilityTreeProvider.ts | 2 +- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/snyk/common/configuration/folderConfigs.ts b/src/snyk/common/configuration/folderConfigs.ts index 1dc895503..94da2dc19 100644 --- a/src/snyk/common/configuration/folderConfigs.ts +++ b/src/snyk/common/configuration/folderConfigs.ts @@ -28,7 +28,7 @@ export class FolderConfigs implements IFolderConfigs { } async setBranch(window: IVSCodeWindow, config: IConfiguration, folderPath: string): Promise { - let folderConfig = this.getFolderConfig(config, folderPath); + const folderConfig = this.getFolderConfig(config, folderPath); if (!folderConfig) { return; @@ -51,7 +51,7 @@ export class FolderConfigs implements IFolderConfigs { await this.setFolderConfig(config, folderConfig); } - validateBranchName(branchName: string, branchList: string[]): boolean { + private validateBranchName(branchName: string, branchList: string[]): boolean { return branchList.includes(branchName); } diff --git a/src/snyk/common/views/issueTreeProvider.ts b/src/snyk/common/views/issueTreeProvider.ts index 122dc1353..3e8af90e7 100644 --- a/src/snyk/common/views/issueTreeProvider.ts +++ b/src/snyk/common/views/issueTreeProvider.ts @@ -108,12 +108,10 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid topNodes.push(noIssueViewOptionSelectedWarning); } const validTopNodes = topNodes.filter((n): n is TreeNode => n !== null); + const baseBranchNodeIndex = nodes.findIndex(node => { const label = node.label as string; - if (!label) { - return false; - } - return label.toLowerCase().indexOf('base branch') > -1; + return label?.toLowerCase().indexOf('base branch') !== -1; }); if (baseBranchNodeIndex > -1) { @@ -292,7 +290,7 @@ export abstract class ProductIssueTreeProvider extends AnalysisTreeNodeProvid continue; } // flatten results if single workspace folder - if (this.productService.result.size == 1) { + if (this.productService.result.size === 1) { this.addBaseBranchNode(baseBranchNode, nodes); nodes.push(...fileNodes); } else { diff --git a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts index 21f14e09f..5a56acb0e 100644 --- a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts +++ b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts @@ -125,7 +125,7 @@ export default class OssIssueTreeProvider extends ProductIssueTreeProvider Date: Fri, 19 Jul 2024 18:06:57 +0200 Subject: [PATCH 8/8] chore: update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a735ced7e..dd928e81b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Snyk Security Changelog +## [2.15.0] +- Sync with LS to retrieve and persist folderConfigs changes. +- Add command to select the base branch. + ## [2.14.0] - Add UI components for selecting a base branch for delta findings for Code and Code Quality behind a feature flag.