diff --git a/src/snyk/base/modules/baseSnykModule.ts b/src/snyk/base/modules/baseSnykModule.ts index a2771f697..c30be2dd6 100644 --- a/src/snyk/base/modules/baseSnykModule.ts +++ b/src/snyk/base/modules/baseSnykModule.ts @@ -6,7 +6,6 @@ import { CommandController } from '../../common/commands/commandController'; import { configuration } from '../../common/configuration/instance'; import { IWorkspaceTrust, WorkspaceTrust } from '../../common/configuration/trustedFolders'; import { ExperimentService } from '../../common/experiment/services/experimentService'; -import { CodeScanOrchestrator } from '../../common/languageServer/experiments/codeScanOrchestrator'; import { ILanguageServer } from '../../common/languageServer/languageServer'; import { CodeIssueData, IacIssueData } from '../../common/languageServer/types'; import { Logger } from '../../common/logger/logger'; @@ -65,7 +64,6 @@ export default abstract class BaseSnykModule implements IBaseSnykModule { snykCodeOld: ISnykCodeServiceOld; snykCode: IProductService; protected codeSettings: ICodeSettings; - protected codeScanOrchestrator: CodeScanOrchestrator; iacService: IProductService; @@ -102,7 +100,5 @@ export default abstract class BaseSnykModule implements IBaseSnykModule { abstract runScan(): Promise; - abstract runCodeScan(): Promise; - abstract runOssScan(): Promise; } diff --git a/src/snyk/base/modules/interfaces.ts b/src/snyk/base/modules/interfaces.ts index b78fabb1c..ba495c6b7 100644 --- a/src/snyk/base/modules/interfaces.ts +++ b/src/snyk/base/modules/interfaces.ts @@ -19,7 +19,6 @@ export interface IBaseSnykModule { // Abstract methods runScan(): Promise; - runCodeScan(manual?: boolean): Promise; runOssScan(manual?: boolean): Promise; } diff --git a/src/snyk/base/modules/snykLib.ts b/src/snyk/base/modules/snykLib.ts index d9b2cfa32..feffeadec 100644 --- a/src/snyk/base/modules/snykLib.ts +++ b/src/snyk/base/modules/snykLib.ts @@ -6,15 +6,15 @@ import { configuration } from '../../common/configuration/instance'; import { DEFAULT_SCAN_DEBOUNCE_INTERVAL, IDE_NAME, OSS_SCAN_DEBOUNCE_INTERVAL } from '../../common/constants/general'; import { SNYK_CONTEXT } from '../../common/constants/views'; import { ErrorHandler } from '../../common/error/errorHandler'; -import { ExperimentKey } from '../../common/experiment/services/experimentService'; import { Logger } from '../../common/logger/logger'; +import { vsCodeCommands } from '../../common/vscode/commands'; import { vsCodeWorkspace } from '../../common/vscode/workspace'; import BaseSnykModule from './baseSnykModule'; import { ISnykLib } from './interfaces'; -import { vsCodeCommands } from '../../common/vscode/commands'; export default class SnykLib extends BaseSnykModule implements ISnykLib { private async runFullScan_(manual = false): Promise { + // Only starts OSS scan. Code & IaC scans are managed by LS Logger.info('Starting full scan'); await this.contextService.setContext(SNYK_CONTEXT.ERROR, false); @@ -44,9 +44,7 @@ export default class SnykLib extends BaseSnykModule implements ISnykLib { if (workspacePaths.length) { this.logFullAnalysisIsTriggered(manual); - void this.startOssAnalysis(manual, false); - await this.startSnykCodeAnalysis(workspacePaths, manual, false); // mark void, handle errors inside of startSnykCodeAnalysis() } } catch (err) { await ErrorHandler.handleGlobal(err, Logger, this.contextService, this.loadingBadge); @@ -55,59 +53,15 @@ 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 runCodeScan = _.debounce(this.startSnykCodeAnalysis.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(); - if (wasEnabled) { - await this.codeSettings.checkCodeEnabled(); - - Logger.info('Snyk Code was enabled.'); - try { - await this.startSnykCodeAnalysis(); - } catch (err) { - ErrorHandler.handle(err, Logger); - } - } - } - - async startSnykCodeAnalysis(paths: string[] = [], manual = false, reportTriggeredEvent = true): Promise { - // If the execution is suspended, we only allow user-triggered Snyk Code analyses. - if (this.isSnykCodeAutoscanSuspended(manual)) { - return; - } - - const codeEnabled = await this.codeSettings.checkCodeEnabled(); - if (!codeEnabled) { - return; - } - - // if LS is used to scan, don't proceed - const codeScansViaLs = await this.experimentService.isUserPartOfExperiment( - ExperimentKey.CodeScansViaLanguageServer, - ); - if (codeScansViaLs) { - return; - } - - if ( - !configuration.getFeaturesConfiguration()?.codeSecurityEnabled && - !configuration.getFeaturesConfiguration()?.codeQualityEnabled - ) { - return; - } - - if (!paths.length) { - paths = vsCodeWorkspace.getWorkspaceFolders(); - } - - await this.snykCodeOld.startAnalysis(paths, manual, reportTriggeredEvent); + Logger.info(wasEnabled ? 'Snyk Code was enabled' : 'Failed to enable Snyk Code'); } async onDidChangeWelcomeViewVisibility(visible: boolean): Promise { diff --git a/src/snyk/common/experiment/services/experimentService.ts b/src/snyk/common/experiment/services/experimentService.ts index 1d76e60d9..0edceb5e4 100644 --- a/src/snyk/common/experiment/services/experimentService.ts +++ b/src/snyk/common/experiment/services/experimentService.ts @@ -7,7 +7,6 @@ import { User } from '../../user'; export enum ExperimentKey { // to be populated with running experiment keys TestExperiment = 'vscode-test-experiment', - CodeScansViaLanguageServer = 'snyk-code-via-ls-in-vs-code-integration', } export class ExperimentService { diff --git a/src/snyk/common/languageServer/experiments/codeScanOrchestrator.ts b/src/snyk/common/languageServer/experiments/codeScanOrchestrator.ts deleted file mode 100644 index eabdb5ea8..000000000 --- a/src/snyk/common/languageServer/experiments/codeScanOrchestrator.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Subscription } from 'rxjs'; -import { IExtension } from '../../../base/modules/interfaces'; -import { SNYK_CONTEXT } from '../../constants/views'; -import { ExperimentKey, ExperimentService } from '../../experiment/services/experimentService'; -import { ILog } from '../../logger/interfaces'; -import { IContextService } from '../../services/contextService'; -import { ILanguageServer } from '../languageServer'; -import { CodeIssueData, Scan, ScanProduct, ScanStatus } from '../types'; - -export class CodeScanOrchestrator { - private lastExperimentCheck: number; - private lsSubscription: Subscription; - private waitTimeInMs: number; - - constructor( - private readonly experimentService: ExperimentService, - readonly languageServer: ILanguageServer, - private readonly logger: ILog, - private readonly contextService: IContextService, - private extension: IExtension, - ) { - this.lastExperimentCheck = new Date().getTime(); - this.setWaitTimeInMs(1000 * 60 * 15); // 15 minutes - this.lsSubscription = languageServer.scan$.subscribe( - // eslint-disable-next-line @typescript-eslint/no-misused-promises - async (scan: Scan) => await this.handleExperimentCheck(scan), - ); - } - - dispose(): void { - this.lsSubscription.unsubscribe(); - } - - async handleExperimentCheck(scan: Scan): Promise { - if (!this.isCheckRequired() || scan.status !== ScanStatus.InProgress || scan.product !== ScanProduct.Code) { - this.logger.debug('Code scan update not required.'); - return; - } - - if (!this.contextService.isCodeInLsPreview) { - return; - } - - // check if the user is part of the experiment - const isPartOfLSCodeExperiment = await this.experimentService.isUserPartOfExperiment( - ExperimentKey.CodeScansViaLanguageServer, - true, - ); - - if (!isPartOfLSCodeExperiment) { - await this.contextService.setContext(SNYK_CONTEXT.LS_CODE_PREVIEW, false); - await this.extension.runCodeScan(); - await this.extension.restartLanguageServer(); - } - - // update lastExperimentCheckTime - this.lastExperimentCheck = new Date().getTime(); - } - - setWaitTimeInMs(ms: number) { - this.waitTimeInMs = ms; - } - - isCheckRequired(): boolean { - const currentTimestamp = new Date().getTime(); - return currentTimestamp - this.lastExperimentCheck > this.waitTimeInMs; - } -} diff --git a/src/snyk/common/languageServer/languageServer.ts b/src/snyk/common/languageServer/languageServer.ts index fb0b44fc2..dcda1378f 100644 --- a/src/snyk/common/languageServer/languageServer.ts +++ b/src/snyk/common/languageServer/languageServer.ts @@ -12,7 +12,6 @@ import { } from '../constants/languageServer'; import { CONFIGURATION_IDENTIFIER } from '../constants/settings'; import { ErrorHandler } from '../error/errorHandler'; -import { ExperimentService } from '../experiment/services/experimentService'; import { ILog } from '../logger/interfaces'; import { getProxyEnvVariable, getProxyOptions } from '../proxy'; import { DownloadService } from '../services/downloadService'; @@ -49,7 +48,6 @@ export class LanguageServer implements ILanguageServer { private authenticationService: IAuthenticationService, private readonly logger: ILog, private downloadService: DownloadService, - private experimentService: ExperimentService, ) { this.downloadService = downloadService; } @@ -97,7 +95,7 @@ export class LanguageServer implements ILanguageServer { synchronize: { configurationSection: CONFIGURATION_IDENTIFIER, }, - middleware: new LanguageClientMiddleware(this.configuration, this.experimentService), + middleware: new LanguageClientMiddleware(this.configuration), /** * We reuse the output channel here as it's not properly disposed of by the language client (vscode-languageclient@8.0.0-next.2) * See: https://github.com/microsoft/vscode-languageserver-node/blob/cdf4d6fdaefe329ce417621cf0f8b14e0b9bb39d/client/src/common/client.ts#L2789 @@ -107,16 +105,17 @@ 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 - .onReady() - .then(() => { - this.registerListeners(this.client); - }) - .catch((error: Error) => ErrorHandler.handle(error, this.logger, error.message)); // Start the client. This will also launch the server this.client.start(); this.logger.info('Snyk Language Server started'); + + try { + await this.client.onReady(); + this.registerListeners(this.client); + } catch (error) { + return ErrorHandler.handle(error, this.logger, error instanceof Error ? error.message : 'An error occurred'); + } } private registerListeners(client: LanguageClient): void { @@ -165,7 +164,7 @@ export class LanguageServer implements ILanguageServer { // Initialization options are not semantically equal to server settings, thus separated here // https://github.com/microsoft/language-server-protocol/issues/567 async getInitializationOptions(): Promise { - const settings = await LanguageServerSettings.fromConfiguration(this.configuration, this.experimentService); + const settings = await LanguageServerSettings.fromConfiguration(this.configuration); return { ...settings, diff --git a/src/snyk/common/languageServer/middleware.ts b/src/snyk/common/languageServer/middleware.ts index 8769bec1d..2fb733985 100644 --- a/src/snyk/common/languageServer/middleware.ts +++ b/src/snyk/common/languageServer/middleware.ts @@ -1,5 +1,4 @@ import { IConfiguration } from '../configuration/configuration'; -import { ExperimentService } from '../experiment/services/experimentService'; import { CancellationToken, ConfigurationParams, @@ -19,7 +18,7 @@ export type LanguageClientWorkspaceMiddleware = Partial & { }; export class LanguageClientMiddleware implements Middleware { - constructor(private configuration: IConfiguration, private experimentService: ExperimentService) {} + constructor(private configuration: IConfiguration) {} workspace: LanguageClientWorkspaceMiddleware = { configuration: async ( @@ -40,7 +39,7 @@ export class LanguageClientMiddleware implements Middleware { return []; } - const serverSettings = await LanguageServerSettings.fromConfiguration(this.configuration, this.experimentService); + const serverSettings = await LanguageServerSettings.fromConfiguration(this.configuration); return [serverSettings]; }, }; diff --git a/src/snyk/common/languageServer/settings.ts b/src/snyk/common/languageServer/settings.ts index 8c30c0d33..fe92e2501 100644 --- a/src/snyk/common/languageServer/settings.ts +++ b/src/snyk/common/languageServer/settings.ts @@ -1,6 +1,5 @@ import _ from 'lodash'; import { IConfiguration, SeverityFilter } from '../configuration/configuration'; -import { ExperimentKey, ExperimentService } from '../experiment/services/experimentService'; export type InitializationOptions = ServerSettings & { integrationName?: string; @@ -31,26 +30,17 @@ export type ServerSettings = { }; export class LanguageServerSettings { - static async fromConfiguration( - configuration: IConfiguration, - experimentService: ExperimentService, - ): Promise { + static async fromConfiguration(configuration: IConfiguration): Promise { const featuresConfiguration = configuration.getFeaturesConfiguration(); const iacEnabled = _.isUndefined(featuresConfiguration.iacEnabled) ? true : featuresConfiguration.iacEnabled; - let codeSecurityEnabled = _.isUndefined(featuresConfiguration.codeSecurityEnabled) + const codeSecurityEnabled = _.isUndefined(featuresConfiguration.codeSecurityEnabled) ? true : featuresConfiguration.codeSecurityEnabled; - let codeQualityEnabled = _.isUndefined(featuresConfiguration.codeQualityEnabled) + const codeQualityEnabled = _.isUndefined(featuresConfiguration.codeQualityEnabled) ? true : featuresConfiguration.codeQualityEnabled; - const codeScansViaLs = await experimentService.isUserPartOfExperiment(ExperimentKey.CodeScansViaLanguageServer); - if (!codeScansViaLs) { - codeSecurityEnabled = false; - codeQualityEnabled = false; - } - return { activateSnykCodeSecurity: `${codeSecurityEnabled}`, activateSnykCodeQuality: `${codeQualityEnabled}`, diff --git a/src/snyk/extension.ts b/src/snyk/extension.ts index 027457850..7a56d0fa5 100644 --- a/src/snyk/extension.ts +++ b/src/snyk/extension.ts @@ -45,8 +45,7 @@ import { } from './common/constants/views'; import { ErrorHandler } from './common/error/errorHandler'; import { ErrorReporter } from './common/error/errorReporter'; -import { ExperimentKey, ExperimentService } from './common/experiment/services/experimentService'; -import { CodeScanOrchestrator } from './common/languageServer/experiments/codeScanOrchestrator'; +import { ExperimentService } from './common/experiment/services/experimentService'; import { LanguageServer } from './common/languageServer/languageServer'; import { StaticLsApi } from './common/languageServer/staticLsApi'; import { Logger } from './common/logger/logger'; @@ -194,7 +193,6 @@ class SnykExtension extends SnykLib implements IExtension { this.authService, Logger, this.downloadService, - this.experimentService, ); const codeSuggestionProvider = new CodeSuggestionWebviewProvider( @@ -441,39 +439,24 @@ class SnykExtension extends SnykLib implements IExtension { configuration, ); - const codeScansViaLs = await this.experimentService.isUserPartOfExperiment( - ExperimentKey.CodeScansViaLanguageServer, - ); - if (codeScansViaLs) { - await this.contextService.setContext(SNYK_CONTEXT.LS_CODE_PREVIEW, true); - Logger.info('Code scans via language server enabled.'); - } else { - await this.contextService.setContext(SNYK_CONTEXT.LS_CODE_PREVIEW, false); - Logger.info('Code scans are not using Language Server.'); - } - - await this.languageServer.start(); - - this.codeScanOrchestrator = new CodeScanOrchestrator( - this.experimentService, - this.languageServer, - Logger, - this.contextService, - this, - ); + await this.contextService.setContext(SNYK_CONTEXT.LS_CODE_PREVIEW, true); // noinspection ES6MissingAwait void this.advisorScoreDisposable.activate(); // Actually start analysis this.runScan(); + + // Wait for LS startup to finish before updating the codeEnabled context + // The codeEnabled context depends on an LS command + await this.languageServer.start(); + await this.codeSettings.updateIsCodeEnabled(); } public async deactivate(): Promise { this.snykCodeOld.dispose(); this.ossVulnerabilityCountService.dispose(); await this.languageServer.stop(); - this.codeScanOrchestrator.dispose(); await this.analytics.flush(); await ErrorReporter.flush(); } diff --git a/src/snyk/snykCode/analyzer/progress.ts b/src/snyk/snykCode/analyzer/progress.ts index 2f9728a9d..c5506b50c 100644 --- a/src/snyk/snykCode/analyzer/progress.ts +++ b/src/snyk/snykCode/analyzer/progress.ts @@ -1,13 +1,11 @@ import { emitter as emitterDC, SupportedFiles } from '@snyk/code-client'; import _ from 'lodash'; import * as vscode from 'vscode'; -import { getExtension } from '../../../extension'; import { SNYK_ANALYSIS_STATUS } from '../../common/constants/views'; import { Logger } from '../../common/logger/logger'; import { IViewManagerService } from '../../common/services/viewManagerService'; import { IVSCodeWorkspace } from '../../common/vscode/workspace'; import { ISnykCodeServiceOld } from '../codeServiceOld'; -import createFileWatcher from '../watchers/filesWatcher'; export class Progress { private emitter = emitterDC; @@ -46,11 +44,6 @@ export class Progress { const msg = data ? 'Ignore rules loading' : 'Loading'; this.updateStatus(SNYK_ANALYSIS_STATUS.FILTERS, msg); - - // Setup file watcher - if (!this.filesWatcher && data) { - this.filesWatcher = createFileWatcher(getExtension(), this.workspace, data); - } } onScanFilesProgress(value: number): void { diff --git a/src/snyk/snykCode/codeSettings.ts b/src/snyk/snykCode/codeSettings.ts index e864675e0..e62032e6a 100644 --- a/src/snyk/snykCode/codeSettings.ts +++ b/src/snyk/snykCode/codeSettings.ts @@ -8,7 +8,7 @@ import { IVSCodeCommands } from '../common/vscode/commands'; export interface ICodeSettings { reportFalsePositivesEnabled: boolean; - checkCodeEnabled(): Promise; + updateIsCodeEnabled(): Promise; enable(): Promise; @@ -37,19 +37,18 @@ export class CodeSettings implements ICodeSettings { private readonly commandExecutor: IVSCodeCommands, ) {} - async checkCodeEnabled(): Promise { + async updateIsCodeEnabled(settings: SastSettings | undefined = undefined): Promise { let codeEnabled = false; let localCodeEngineEnabled = false; try { - const settings = await this.getSastSettings(); + settings = settings === undefined ? await this.getSastSettings() : settings; if (!settings) { return false; } codeEnabled = settings.sastEnabled && !settings.localCodeEngine.enabled; localCodeEngineEnabled = settings.localCodeEngine.enabled; } catch (e) { - // Ignore potential command not found error during LS startup and poll - codeEnabled = await this.enable(false); + return false; } await this.contextService.setContext(SNYK_CONTEXT.CODE_ENABLED, codeEnabled); await this.contextService.setContext(SNYK_CONTEXT.CODE_LOCAL_ENGINE_ENABLED, localCodeEngineEnabled); @@ -60,8 +59,11 @@ export class CodeSettings implements ICodeSettings { let settings: SastSettings | undefined; try { settings = await this.getSastSettings(); + await this.updateIsCodeEnabled(settings); } catch (e) { - // Ignore potential command not found error during LS startup + if (e instanceof Error) { + // Ignore potential command not found error during LS startup + } } if (settings?.sastEnabled) { @@ -81,6 +83,8 @@ export class CodeSettings implements ICodeSettings { // eslint-disable-next-line no-await-in-loop settings = await this.getSastSettings(); if (settings?.sastEnabled && !settings?.localCodeEngine.enabled) { + // eslint-disable-next-line no-await-in-loop + await this.updateIsCodeEnabled(settings); return true; } } catch (e) { @@ -92,7 +96,7 @@ export class CodeSettings implements ICodeSettings { } async getSastSettings(): Promise { - return this.commandExecutor.executeCommand(SNYK_GET_SETTINGS_SAST_ENABLED); + return this.commandExecutor.executeCommand(SNYK_GET_SETTINGS_SAST_ENABLED); } private sleep = (duration: number) => new Promise(resolve => setTimeout(resolve, duration)); diff --git a/src/snyk/snykCode/error/snykCodeErrorHandler.ts b/src/snyk/snykCode/error/snykCodeErrorHandler.ts index ce9c91a88..48553cfb5 100644 --- a/src/snyk/snykCode/error/snykCodeErrorHandler.ts +++ b/src/snyk/snykCode/error/snykCodeErrorHandler.ts @@ -2,13 +2,15 @@ import { constants } from '@snyk/code-client'; import { errorType, IBaseSnykModule } from '../../base/modules/interfaces'; import { ILoadingBadge } from '../../base/views/loadingBadge'; import { IConfiguration } from '../../common/configuration/configuration'; -import { CONNECTION_ERROR_RETRY_INTERVAL, MAX_CONNECTION_RETRIES } from '../../common/constants/general'; +import { MAX_CONNECTION_RETRIES } from '../../common/constants/general'; import { SNYK_CONTEXT } from '../../common/constants/views'; import { ErrorHandler } from '../../common/error/errorHandler'; import { TagKeys, Tags } from '../../common/error/errorReporter'; import { ILog } from '../../common/logger/interfaces'; import { IContextService } from '../../common/services/contextService'; +// TODO - remove this entire class. Code scans run through LS now. + type SnykCodeErrorResponseType = { apiName: string; errorCode: string; @@ -136,7 +138,8 @@ export class SnykCodeErrorHandler extends ErrorHandler implements ISnykCodeError } if (SnykCodeErrorHandler.isErrorRetryable(errorStatusCode) || this.isBundleError(error)) { - return await this.retryHandler(error, errorStatusCode, options, callback); + this.retryHandler(error, errorStatusCode, options, callback); + return; } this._connectionRetryLimitExhausted = true; @@ -157,12 +160,12 @@ export class SnykCodeErrorHandler extends ErrorHandler implements ISnykCodeError this.resetRequestId(); } - private async retryHandler( + private retryHandler( error: errorType, errorStatusCode: PropertyKey, options: { [key: string]: unknown }, callback: (error: Error) => void, - ): Promise { + ): void { this.logger.error(`Connection error to Snyk Code. Try count: ${this.transientErrors + 1}.`); if (this.transientErrors > MAX_CONNECTION_RETRIES) { @@ -176,12 +179,6 @@ export class SnykCodeErrorHandler extends ErrorHandler implements ISnykCodeError if (errorStatusCode === constants.ErrorCodes.notFound) { this.baseSnykModule.snykCodeOld.clearBundle(); // bundle has expired, trigger complete new analysis } - - setTimeout(() => { - this.baseSnykModule.runCodeScan().catch(err => this.capture(err, options)); - }, CONNECTION_ERROR_RETRY_INTERVAL); - - return Promise.resolve(); } capture(error: errorType, options: { [key: string]: unknown }, tags?: Tags): void { diff --git a/src/snyk/snykCode/watchers/filesWatcher.ts b/src/snyk/snykCode/watchers/filesWatcher.ts deleted file mode 100644 index c0cd957c5..000000000 --- a/src/snyk/snykCode/watchers/filesWatcher.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { getGlobPatterns, SupportedFiles } from '@snyk/code-client'; -import * as vscode from 'vscode'; -import { IExtension } from '../../base/modules/interfaces'; -import { IVSCodeWorkspace } from '../../common/vscode/workspace'; - -export default function createFileWatcher( - extension: IExtension, - workspace: IVSCodeWorkspace, - supportedFiles: SupportedFiles, -): vscode.FileSystemWatcher { - const globPattern: vscode.GlobPattern = `**/{${getGlobPatterns(supportedFiles).join(',')}}`; - const watcher = workspace.createFileSystemWatcher(globPattern); - - const updateFiles = (filePath: string): void => { - extension.snykCodeOld.addChangedFile(filePath); - void extension.runCodeScan(); // It's debounced, so not worries about concurrent calls - }; - - watcher.onDidChange((documentUri: vscode.Uri) => { - updateFiles(documentUri.fsPath); - }); - watcher.onDidDelete((documentUri: vscode.Uri) => { - updateFiles(documentUri.fsPath); - }); - watcher.onDidCreate((documentUri: vscode.Uri) => { - updateFiles(documentUri.fsPath); - }); - - return watcher; -} diff --git a/src/test/unit/common/languageServer/experiments/codeScanOrchestrator.test.ts b/src/test/unit/common/languageServer/experiments/codeScanOrchestrator.test.ts index e098d583d..e69de29bb 100644 --- a/src/test/unit/common/languageServer/experiments/codeScanOrchestrator.test.ts +++ b/src/test/unit/common/languageServer/experiments/codeScanOrchestrator.test.ts @@ -1,132 +0,0 @@ -import { strictEqual } from 'assert'; -import sinon from 'sinon'; -import { IExtension } from '../../../../../snyk/base/modules/interfaces'; -import { IConfiguration } from '../../../../../snyk/common/configuration/configuration'; -import { SnykConfiguration } from '../../../../../snyk/common/configuration/snykConfiguration'; -import { SNYK_CONTEXT } from '../../../../../snyk/common/constants/views'; -import { ExperimentService } from '../../../../../snyk/common/experiment/services/experimentService'; -import { CodeScanOrchestrator } from '../../../../../snyk/common/languageServer/experiments/codeScanOrchestrator'; -import { ILanguageServer } from '../../../../../snyk/common/languageServer/languageServer'; -import { ScanProduct, ScanStatus } from '../../../../../snyk/common/languageServer/types'; -import { IContextService } from '../../../../../snyk/common/services/contextService'; -import { User } from '../../../../../snyk/common/user'; -import { LanguageServerMock } from '../../../mocks/languageServer.mock'; -import { LoggerMock } from '../../../mocks/logger.mock'; - -suite('Code Scan Orchestrator', () => { - let ls: ILanguageServer; - let codeScanOrchestrator: CodeScanOrchestrator; - let experimentService: ExperimentService; - let user: User; - let config: IConfiguration; - let snykConfig: SnykConfiguration; - let logger: LoggerMock; - let contextServiceMock: IContextService; - let setContextSpy: sinon.SinonSpy; - let isUserPartOfExperimentStub: sinon.SinonStub; - - const sleepInMs = (duration: number) => new Promise(resolve => setTimeout(resolve, duration)); - - setup(() => { - ls = new LanguageServerMock(); - user = new User(undefined, undefined, logger); - snykConfig = new SnykConfiguration('test', 'test', 'test'); - logger = new LoggerMock(); - config = { - shouldReportEvents: true, - } as unknown as IConfiguration; - experimentService = new ExperimentService(user, logger, config, snykConfig); - isUserPartOfExperimentStub = sinon.stub(experimentService, 'isUserPartOfExperiment').resolves(false); - - setContextSpy = sinon.fake(); - contextServiceMock = { - setContext: setContextSpy, - shouldShowCodeAnalysis: false, - shouldShowOssAnalysis: false, - shouldShowIacAnalysis: false, - isCodeInLsPreview: true, - viewContext: {}, - }; - - const extension = { - runCodeScan: sinon.fake(), - } as unknown as IExtension; - - codeScanOrchestrator = new CodeScanOrchestrator(experimentService, ls, logger, contextServiceMock, extension); - }); - - teardown(() => { - sinon.restore(); - }); - - test('Orchestrates only when check is required', async () => { - // check is required if 10ms passed since last check - codeScanOrchestrator.setWaitTimeInMs(100); - await sleepInMs(20); - - ls.scan$.next({ - product: ScanProduct.Code, - folderPath: 'test/path', - issues: [], - status: ScanStatus.InProgress, - }); - - strictEqual(isUserPartOfExperimentStub.called, false); - }); - - for (const status in ScanStatus) { - test(`Orchestrates only when scan is in progres - currently: ${status}`, async () => { - codeScanOrchestrator.setWaitTimeInMs(10); - await sleepInMs(15); - - ls.scan$.next({ - product: ScanProduct.Code, - folderPath: 'test/path', - issues: [], - status: ScanStatus[status], - }); - - if (ScanStatus[status] === ScanStatus.InProgress) { - strictEqual(isUserPartOfExperimentStub.called, true); - } else { - strictEqual(isUserPartOfExperimentStub.called, false); - } - }); - } - - for (const product in ScanProduct) { - test(`Orchestrates only when product is code - currently: ${product}`, async () => { - codeScanOrchestrator.setWaitTimeInMs(10); - await sleepInMs(15); - - ls.scan$.next({ - product: ScanProduct[product], - folderPath: 'test/path', - issues: [], - status: ScanStatus.InProgress, - }); - - if (ScanProduct[product] === ScanProduct.Code) { - strictEqual(isUserPartOfExperimentStub.called, true); - } else { - strictEqual(isUserPartOfExperimentStub.called, false); - } - }); - } - - test('Correctly updates code scan settings when user is NOT part of experiment', async () => { - codeScanOrchestrator.setWaitTimeInMs(10); - await sleepInMs(20); - - ls.scan$.next({ - product: ScanProduct.Code, - folderPath: 'test/path', - issues: [], - status: ScanStatus.InProgress, - }); - - // wait for the orchestrator to finish, possible race condition still exists - await sleepInMs(20); - sinon.assert.calledWith(setContextSpy, SNYK_CONTEXT.LS_CODE_PREVIEW, false); - }); -}); diff --git a/src/test/unit/common/languageServer/languageServer.test.ts b/src/test/unit/common/languageServer/languageServer.test.ts index 6a6023e7f..8c79cc50e 100644 --- a/src/test/unit/common/languageServer/languageServer.test.ts +++ b/src/test/unit/common/languageServer/languageServer.test.ts @@ -5,7 +5,6 @@ import sinon from 'sinon'; import { v4 } from 'uuid'; import { IAuthenticationService } from '../../../../snyk/base/services/authenticationService'; import { IConfiguration } from '../../../../snyk/common/configuration/configuration'; -import { ExperimentService } from '../../../../snyk/common/experiment/services/experimentService'; import { LanguageServer } from '../../../../snyk/common/languageServer/languageServer'; import { InitializationOptions } from '../../../../snyk/common/languageServer/settings'; import { DownloadService } from '../../../../snyk/common/services/downloadService'; @@ -116,7 +115,6 @@ suite('Language Server', () => { authServiceMock, new LoggerMock(), downloadServiceMock, - new ExperimentService(user, new LoggerMock(), configurationMock), ); downloadServiceMock.downloadReady$.next(); await languageServer.start(); @@ -142,14 +140,13 @@ suite('Language Server', () => { authServiceMock, new LoggerMock(), downloadServiceMock, - new ExperimentService(user, new LoggerMock(), configurationMock), ); }); test('LanguageServer should provide correct initialization options', async () => { const expectedInitializationOptions: InitializationOptions = { - activateSnykCodeSecurity: 'false', - activateSnykCodeQuality: 'false', + activateSnykCodeSecurity: 'true', + activateSnykCodeQuality: 'true', activateSnykOpenSource: 'false', activateSnykIac: 'true', token: 'testToken', @@ -175,9 +172,6 @@ suite('Language Server', () => { }); test('LanguageServer should respect experiment setup for Code', async () => { - const experimentServiceMock = { - isUserPartOfExperiment: sinon.stub().resolves(true), - }; languageServer = new LanguageServer( user, configurationMock, @@ -187,7 +181,6 @@ suite('Language Server', () => { authServiceMock, new LoggerMock(), downloadServiceMock, - experimentServiceMock as unknown as ExperimentService, ); const initOptions = await languageServer.getInitializationOptions(); diff --git a/src/test/unit/common/languageServer/middleware.test.ts b/src/test/unit/common/languageServer/middleware.test.ts index ac52b9a1f..08e3c48c8 100644 --- a/src/test/unit/common/languageServer/middleware.test.ts +++ b/src/test/unit/common/languageServer/middleware.test.ts @@ -1,12 +1,9 @@ import assert from 'assert'; import sinon from 'sinon'; -import { v4 } from 'uuid'; import { CliExecutable } from '../../../../snyk/cli/cliExecutable'; import { IConfiguration } from '../../../../snyk/common/configuration/configuration'; -import { ExperimentService } from '../../../../snyk/common/experiment/services/experimentService'; import { LanguageClientMiddleware } from '../../../../snyk/common/languageServer/middleware'; import { ServerSettings } from '../../../../snyk/common/languageServer/settings'; -import { User } from '../../../../snyk/common/user'; import { CancellationToken, ConfigurationParams, @@ -15,7 +12,6 @@ import { } from '../../../../snyk/common/vscode/types'; import { defaultFeaturesConfigurationStub } from '../../mocks/configuration.mock'; import { extensionContextMock } from '../../mocks/extensionContext.mock'; -import { LoggerMock } from '../../mocks/logger.mock'; suite('Language Server: Middleware', () => { let configuration: IConfiguration; @@ -56,10 +52,7 @@ suite('Language Server: Middleware', () => { }); test('Configuration request should translate settings', async () => { - const middleware = new LanguageClientMiddleware( - configuration, - new ExperimentService(new User(v4(), undefined, new LoggerMock()), new LoggerMock(), configuration), - ); + const middleware = new LanguageClientMiddleware(configuration); const params: ConfigurationParams = { items: [ { @@ -82,8 +75,8 @@ suite('Language Server: Middleware', () => { } const serverResult = res[0] as ServerSettings; - assert.strictEqual(serverResult.activateSnykCodeSecurity, 'false'); - assert.strictEqual(serverResult.activateSnykCodeQuality, 'false'); + assert.strictEqual(serverResult.activateSnykCodeSecurity, 'true'); + assert.strictEqual(serverResult.activateSnykCodeQuality, 'true'); assert.strictEqual(serverResult.activateSnykOpenSource, 'false'); assert.strictEqual(serverResult.activateSnykIac, 'true'); assert.strictEqual(serverResult.endpoint, configuration.snykOssApiEndpoint); @@ -104,10 +97,7 @@ suite('Language Server: Middleware', () => { }); test('Configuration request should return an error', async () => { - const middleware = new LanguageClientMiddleware( - configuration, - new ExperimentService(new User(v4(), undefined, new LoggerMock()), new LoggerMock(), configuration), - ); + const middleware = new LanguageClientMiddleware(configuration); const params: ConfigurationParams = { items: [ { diff --git a/src/test/unit/snykCode/codeSettings.test.ts b/src/test/unit/snykCode/codeSettings.test.ts index 5c469fa8d..fd3da82db 100644 --- a/src/test/unit/snykCode/codeSettings.test.ts +++ b/src/test/unit/snykCode/codeSettings.test.ts @@ -40,7 +40,7 @@ suite('Snyk Code Settings', () => { reportFalsePositivesEnabled: true, }); - const codeEnabled = await settings.checkCodeEnabled(); + const codeEnabled = await settings.updateIsCodeEnabled(); strictEqual(codeEnabled, false); strictEqual(setContextFake.calledWith(SNYK_CONTEXT.CODE_ENABLED, false), true); @@ -56,7 +56,7 @@ suite('Snyk Code Settings', () => { reportFalsePositivesEnabled: true, }); - const codeEnabled = await settings.checkCodeEnabled(); + const codeEnabled = await settings.updateIsCodeEnabled(); strictEqual(codeEnabled, true); strictEqual(setContextFake.calledWith(SNYK_CONTEXT.CODE_ENABLED, true), true); @@ -72,7 +72,7 @@ suite('Snyk Code Settings', () => { reportFalsePositivesEnabled: true, }); - const codeEnabled = await settings.checkCodeEnabled(); + const codeEnabled = await settings.updateIsCodeEnabled(); strictEqual(codeEnabled, false); strictEqual(setContextFake.calledWith(SNYK_CONTEXT.CODE_ENABLED, false), true); diff --git a/src/test/unit/snykCode/error/snykCodeErrorHandler.test.ts b/src/test/unit/snykCode/error/snykCodeErrorHandler.test.ts deleted file mode 100644 index ae80251fb..000000000 --- a/src/test/unit/snykCode/error/snykCodeErrorHandler.test.ts +++ /dev/null @@ -1,88 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import { constants } from '@snyk/code-client'; -import { strictEqual } from 'assert'; -import sinon from 'sinon'; -import { IBaseSnykModule } from '../../../../snyk/base/modules/interfaces'; -import { ILoadingBadge } from '../../../../snyk/base/views/loadingBadge'; -import { IConfiguration } from '../../../../snyk/common/configuration/configuration'; -import { CONNECTION_ERROR_RETRY_INTERVAL, MAX_CONNECTION_RETRIES } from '../../../../snyk/common/constants/general'; -import { IContextService } from '../../../../snyk/common/services/contextService'; -import { SnykCodeErrorHandler } from '../../../../snyk/snykCode/error/snykCodeErrorHandler'; -import { LoggerMock } from '../../mocks/logger.mock'; - -suite('Snyk Code Error Handler', () => { - const runCodeScanFake = sinon.stub().resolves(); - const baseSnykModule = { - runCodeScan: runCodeScanFake, - } as unknown as IBaseSnykModule; - - const handler = new SnykCodeErrorHandler( - {} as IContextService, - {} as ILoadingBadge, - new LoggerMock(), - baseSnykModule, - {} as IConfiguration, - ); - - teardown(() => { - sinon.restore(); - }); - - test('Retries scan if "Failed to get remote bundle" is processed', async function () { - // arrange - this.timeout(CONNECTION_ERROR_RETRY_INTERVAL + 2000); - - const error = new Error('Failed to get remote bundle'); - // act - await handler.processError(error, undefined, '123456789', () => null); - - strictEqual(handler.connectionRetryLimitExhausted, false); - // assert - return new Promise((resolve, _) => { - setTimeout(() => { - strictEqual(runCodeScanFake.called, true); - resolve(); - }, CONNECTION_ERROR_RETRY_INTERVAL); - }); - }); - - test('Handles Snyk Code api error response and retries appropriately', async function () { - this.timeout(CONNECTION_ERROR_RETRY_INTERVAL + 2000); - const error = { - apiName: 'getAnalysis', - messages: { - 500: 'Unexpected server error', - }, - errorCode: 500, - }; - - await handler.processError(error, undefined, '123456789', () => null); - - strictEqual(handler.connectionRetryLimitExhausted, false); - // assert - return new Promise((resolve, _) => { - setTimeout(() => { - strictEqual(runCodeScanFake.called, true); - resolve(); - }, CONNECTION_ERROR_RETRY_INTERVAL); - }); - }); - - test('Logs analytic events once retries are exhausted', async function () { - this.timeout(CONNECTION_ERROR_RETRY_INTERVAL + 2000); - const error = new Error('Failed to get remote bundle'); - - const mockedRetries = []; - // parallelising the retries - for (let i = 0; i < MAX_CONNECTION_RETRIES + 2; i++) { - mockedRetries.push(handler.processError(error, undefined, '123456789', () => null)); - } - await Promise.all(mockedRetries); - - strictEqual(handler.connectionRetryLimitExhausted, true); - }); - - test('404 is retryable error', function () { - strictEqual(SnykCodeErrorHandler.isErrorRetryable(constants.ErrorCodes.notFound), true); - }); -});