diff --git a/CHANGELOG.md b/CHANGELOG.md index a64de19a3..29d8850b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ # Snyk Security Changelog ## [2.20.0] -- disable hovers over issues +- reduce hover verbosity to only title and description - If $/snyk.hasAuthenticated transmits an API URL, this is saved in the settings. - Delete sentry reporting. +- send analytics event "plugin installed" the first time the extension is started ## [2.19.2] - Update download endpoint to downloads.snyk.io. diff --git a/src/snyk/common/analytics/AnalyticsEvent.ts b/src/snyk/common/analytics/AnalyticsEvent.ts new file mode 100644 index 000000000..2e219f4e7 --- /dev/null +++ b/src/snyk/common/analytics/AnalyticsEvent.ts @@ -0,0 +1,75 @@ +import { AbstractAnalyticsEvent } from './AnalyticsSender'; + +export class AnalyticsEvent implements AbstractAnalyticsEvent { + private readonly interactionType: string; + private readonly category: string[]; + private readonly status: string; + private readonly targetId: string; + private readonly timestampMs: number; + private readonly durationMs: number; + private readonly results: Map; + private readonly errors: unknown[]; + private readonly extension: Map; + + constructor( + deviceId: string, + interactionType: string, + category: string[], + status: string = 'success', + targetId: string = 'pkg:filesystem/scrubbed', + timestampMs: number = Date.now(), + durationMs: number = 0, + results: Map = new Map(), + errors: unknown[] = [], + extension: Map = new Map(), + ) { + this.interactionType = interactionType; + this.category = category; + this.status = status ?? 'success'; + this.targetId = targetId ?? 'pkg:filesystem/scrubbed'; + this.timestampMs = timestampMs ?? Date.now(); + this.durationMs = durationMs ?? 0; + this.results = results ?? new Map(); + this.errors = errors ?? []; + this.extension = extension ?? new Map(); + if (deviceId && deviceId.length > 0) { + this.extension.set('device_id', deviceId); + } + } + + public getInteractionType(): string { + return this.interactionType; + } + + public getCategory(): string[] { + return this.category; + } + + public getStatus(): string { + return this.status; + } + + public getTargetId(): string { + return this.targetId; + } + + public getTimestampMs(): number { + return this.timestampMs; + } + + public getDurationMs(): number { + return this.durationMs; + } + + public getResults(): Map { + return this.results; + } + + public getErrors(): unknown[] { + return this.errors; + } + + public getExtension(): Map { + return this.extension; + } +} diff --git a/src/snyk/common/analytics/AnalyticsSender.ts b/src/snyk/common/analytics/AnalyticsSender.ts new file mode 100644 index 000000000..32fdcc0a7 --- /dev/null +++ b/src/snyk/common/analytics/AnalyticsSender.ts @@ -0,0 +1,84 @@ +// noinspection InfiniteLoopJS + +import { ILog } from '../logger/interfaces'; +import { IConfiguration } from '../configuration/configuration'; +import { sleep } from '@amplitude/experiment-node-server/dist/src/util/time'; +import { IVSCodeCommands } from '../vscode/commands'; +import { SNYK_REPORT_ANALYTICS } from '../constants/commands'; +import { IContextService } from '../services/contextService'; +import { SNYK_CONTEXT } from '../constants/views'; + +interface EventPair { + event: AbstractAnalyticsEvent; + callback: (value: void) => void; +} + +// This is just a marker interface, to ensure type security when sending events +export interface AbstractAnalyticsEvent {} + +export class AnalyticsSender { + private static instance: AnalyticsSender; + private eventQueue: EventPair[] = []; + + constructor( + private logger: ILog, + private configuration: IConfiguration, + private commandExecutor: IVSCodeCommands, + private contextService: IContextService, + ) { + void this.start(); + } + + public static getInstance( + logger: ILog, + configuration: IConfiguration, + commandExecutor: IVSCodeCommands, + contextService: IContextService, + ): AnalyticsSender { + if (!AnalyticsSender.instance) { + AnalyticsSender.instance = new AnalyticsSender(logger, configuration, commandExecutor, contextService); + } + return AnalyticsSender.instance; + } + + private async start(): Promise { + // eslint-disable-next-line no-constant-condition + while (true) { + // eslint-disable-next-line no-await-in-loop + const authToken = await this.configuration.getToken(); + const initialized: boolean = (this.contextService.viewContext[SNYK_CONTEXT.INITIALIZED] as boolean) ?? false; + const hasEvents = this.eventQueue.length > 0; + const authenticated = authToken && authToken.trim() !== ''; + const iAmTired = !(initialized && authenticated && hasEvents); + + if (iAmTired) { + // eslint-disable-next-line no-await-in-loop + await sleep(5000); + continue; + } + + const copyForSending = [...this.eventQueue]; + for (let i = 0; i < copyForSending.length; i++) { + const eventPair = copyForSending[i]; + try { + // eslint-disable-next-line no-await-in-loop + await this.commandExecutor.executeCommand(SNYK_REPORT_ANALYTICS, JSON.stringify(eventPair.event)); + eventPair.callback(); + } catch (error) { + // eslint-disable-next-line @typescript-eslint/no-base-to-string + this.logger.error(`could not send ${eventPair.event} ${error}`); + } finally { + // let's not rely on indexes in the eventQueue array not having changed + const index = this.eventQueue.indexOf(eventPair); + if (index > -1) { + this.eventQueue.splice(index, 1); + } + } + } + } + } + + public logEvent(event: AbstractAnalyticsEvent, callback: (value: void) => void): void { + this.eventQueue.push({ event, callback }); + } +} diff --git a/src/snyk/common/configuration/configuration.ts b/src/snyk/common/configuration/configuration.ts index 15b8aeddc..066ebbb2f 100644 --- a/src/snyk/common/configuration/configuration.ts +++ b/src/snyk/common/configuration/configuration.ts @@ -143,8 +143,6 @@ export interface IConfiguration { } export class Configuration implements IConfiguration { - // These attributes are used in tests - private readonly defaultSnykCodeBaseURL = 'https://deeproxy.snyk.io'; private readonly defaultAuthHost = 'https://app.snyk.io'; private readonly defaultApiEndpoint = 'https://api.snyk.io'; diff --git a/src/snyk/common/constants/commands.ts b/src/snyk/common/constants/commands.ts index ae2aca170..b49959089 100644 --- a/src/snyk/common/constants/commands.ts +++ b/src/snyk/common/constants/commands.ts @@ -30,6 +30,7 @@ export const SNYK_FEATURE_FLAG_COMMAND = 'snyk.getFeatureFlagStatus'; export const SNYK_CLEAR_CACHE_COMMAND = 'snyk.clearCache'; export const SNYK_CLEAR_PERSISTED_CACHE_COMMAND = 'snyk.clearPersistedCache'; export const SNYK_GENERATE_ISSUE_DESCRIPTION = 'snyk.generateIssueDescription'; +export const SNYK_REPORT_ANALYTICS = 'snyk.reportAnalytics'; // custom Snyk constants used in commands export const SNYK_CONTEXT_PREFIX = 'snyk:'; diff --git a/src/snyk/common/constants/globalState.ts b/src/snyk/common/constants/globalState.ts index ff12e3a06..64b00fb10 100644 --- a/src/snyk/common/constants/globalState.ts +++ b/src/snyk/common/constants/globalState.ts @@ -2,3 +2,4 @@ export const MEMENTO_ANONYMOUS_ID = 'snyk.anonymousId'; export const MEMENTO_LS_LAST_UPDATE_DATE = 'snyk.lsLastUpdateDate'; export const MEMENTO_LS_PROTOCOL_VERSION = 'snyk.lsProtocolVersion'; export const MEMENTO_LS_CHECKSUM = 'snyk.lsChecksum'; +export const MEMENTO_ANALYTICS_PLUGIN_INSTALLED_SENT = 'snyk.pluginInstalledSent'; diff --git a/src/snyk/common/constants/languageServer.ts b/src/snyk/common/constants/languageServer.ts index 17b4686e8..eb96907da 100644 --- a/src/snyk/common/constants/languageServer.ts +++ b/src/snyk/common/constants/languageServer.ts @@ -2,7 +2,7 @@ // Language Server name, used e.g. for the output channel export const SNYK_LANGUAGE_SERVER_NAME = 'Snyk Language Server'; // The internal language server protocol version for custom messages and configuration -export const PROTOCOL_VERSION = 16; +export const PROTOCOL_VERSION = 17; // LS protocol methods (needed for not having to rely on vscode dependencies in testing) export const DID_CHANGE_CONFIGURATION_METHOD = 'workspace/didChangeConfiguration'; diff --git a/src/snyk/common/languageServer/settings.ts b/src/snyk/common/languageServer/settings.ts index 59ce5198a..723bea05a 100644 --- a/src/snyk/common/languageServer/settings.ts +++ b/src/snyk/common/languageServer/settings.ts @@ -87,7 +87,7 @@ export class LanguageServerSettings { requiredProtocolVersion: `${PROTOCOL_VERSION}`, folderConfigs: configuration.getFolderConfigs(), enableSnykOSSQuickFixCodeActions: `${configuration.getPreviewFeatures().ossQuickfixes}`, - hoverVerbosity: 0, + hoverVerbosity: 1, }; } } diff --git a/src/snyk/common/views/issueTreeProvider.ts b/src/snyk/common/views/issueTreeProvider.ts index f1084c960..0eb42114b 100644 --- a/src/snyk/common/views/issueTreeProvider.ts +++ b/src/snyk/common/views/issueTreeProvider.ts @@ -1,10 +1,10 @@ import _, { flatten } from 'lodash'; import * as vscode from 'vscode'; // todo: invert dependency import { IConfiguration, IssueViewOptions } from '../../common/configuration/configuration'; -import { Issue, IssueSeverity, ScanProduct, LsErrorMessage } from '../../common/languageServer/types'; +import { Issue, IssueSeverity, LsErrorMessage } from '../../common/languageServer/types'; import { messages as commonMessages } from '../../common/messages/analysisMessages'; import { IContextService } from '../../common/services/contextService'; -import { IProductService, ProductService } from '../../common/services/productService'; +import { IProductService } from '../../common/services/productService'; import { AnalysisTreeNodeProvider } from '../../common/views/analysisTreeNodeProvider'; import { INodeIcon, InternalType, NODE_ICONS, TreeNode } from '../../common/views/treeNode'; import { IVSCodeLanguages } from '../../common/vscode/languages'; diff --git a/src/snyk/extension.ts b/src/snyk/extension.ts index 345812415..d4fedb230 100644 --- a/src/snyk/extension.ts +++ b/src/snyk/extension.ts @@ -39,7 +39,6 @@ import { } from './common/constants/views'; import { ErrorHandler } from './common/error/errorHandler'; 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'; import { DownloadService } from './common/services/downloadService'; @@ -48,7 +47,6 @@ import { NotificationService } from './common/services/notificationService'; import { User } from './common/user'; import { CodeActionAdapter } from './common/vscode/codeAction'; import { vsCodeCommands } from './common/vscode/commands'; -import { vsCodeEnv } from './common/vscode/env'; import { extensionContext } from './common/vscode/extensionContext'; import { LanguageClientAdapter } from './common/vscode/languageClient'; import { vsCodeLanguages } from './common/vscode/languages'; @@ -81,6 +79,10 @@ import { CodeIssueData, IacIssueData, OssIssueData } from './common/languageServ import { ClearCacheService } from './common/services/CacheService'; import { InMemory, Persisted } from './common/constants/general'; import { GitAPI, GitExtension, Repository } from './common/git'; +import { AnalyticsSender } from './common/analytics/AnalyticsSender'; +import { MEMENTO_ANALYTICS_PLUGIN_INSTALLED_SENT } from './common/constants/globalState'; +import { AnalyticsEvent } from './common/analytics/AnalyticsEvent'; +import { LanguageServer } from './common/languageServer/languageServer'; class SnykExtension extends SnykLib implements IExtension { public async activate(vscodeContext: vscode.ExtensionContext): Promise { @@ -381,7 +383,7 @@ class SnykExtension extends SnykLib implements IExtension { e.removed.forEach(folder => { this.snykCode.resetResult(folder.uri.fsPath); }); - this.runScan(false); + this.runScan(); }); this.editorsWatcher.activate(this); @@ -420,19 +422,37 @@ class SnykExtension extends SnykLib implements IExtension { // The codeEnabled context depends on an LS command await this.languageServer.start(); + // initialize contexts + await this.contextService.setContext(SNYK_CONTEXT.INITIALIZED, true); + + // Fetch feature flag to determine whether to use the new LSP-based rendering. // feature flags depend on the language server this.featureFlagService = new FeatureFlagService(vsCodeCommands); await this.setupFeatureFlags(); - // Fetch feature flag to determine whether to use the new LSP-based rendering. - - // initialize contexts - await this.contextService.setContext(SNYK_CONTEXT.INITIALIZED, true); + this.sendPluginInstalledEvent(); // Actually start analysis this.runScan(); } + private sendPluginInstalledEvent() { + // start analytics sender and send plugin installed event + const analyticsSender = AnalyticsSender.getInstance(Logger, configuration, vsCodeCommands, this.contextService); + + const pluginInstalledSent = + extensionContext.getGlobalStateValue(MEMENTO_ANALYTICS_PLUGIN_INSTALLED_SENT) ?? false; + + if (!pluginInstalledSent) { + const category = []; + category.push('install'); + const pluginInstalleEvent = new AnalyticsEvent(this.user.anonymousId, 'plugin installed', category); + analyticsSender.logEvent(pluginInstalleEvent, () => { + void extensionContext.updateGlobalStateValue(MEMENTO_ANALYTICS_PLUGIN_INSTALLED_SENT, true); + }); + } + } + public async deactivate(): Promise { this.ossVulnerabilityCountService.dispose(); await this.languageServer.stop(); diff --git a/src/snyk/snykCode/views/suggestion/codeSuggestionWebviewProvider.ts b/src/snyk/snykCode/views/suggestion/codeSuggestionWebviewProvider.ts index 1d4bb55e2..206a9d477 100644 --- a/src/snyk/snykCode/views/suggestion/codeSuggestionWebviewProvider.ts +++ b/src/snyk/snykCode/views/suggestion/codeSuggestionWebviewProvider.ts @@ -93,8 +93,6 @@ export class CodeSuggestionWebviewProvider } async showPanel(issue: Issue): Promise { - const isIgnoresEnabled = configuration.getFeatureFlag(FEATURE_FLAGS.consistentIgnores); - try { await this.focusSecondEditorGroup(); if (this.panel) { @@ -120,7 +118,7 @@ export class CodeSuggestionWebviewProvider 'snyk-code.svg', ); // TODO: delete this when SNYK_GENERATE_ISSUE_DESCRIPTION command is in stable CLI. - let html: string = ''; + let html: string; if (issue.additionalData.details) { html = issue.additionalData.details; } else { diff --git a/src/snyk/snykOss/providers/ossDetailPanelProvider.ts b/src/snyk/snykOss/providers/ossDetailPanelProvider.ts index 5febe11ad..659a50fbf 100644 --- a/src/snyk/snykOss/providers/ossDetailPanelProvider.ts +++ b/src/snyk/snykOss/providers/ossDetailPanelProvider.ts @@ -62,8 +62,7 @@ export class OssDetailPanelProvider ); this.registerListeners(); } - - const images: Record = [ + [ ['icon-code', 'svg'], ['dark-critical-severity', 'svg'], ['dark-high-severity', 'svg'], @@ -76,7 +75,6 @@ export class OssDetailPanelProvider accumulator[name] = uri.toString(); return accumulator; }, {}); - let html: string = ''; // TODO: delete this when SNYK_GENERATE_ISSUE_DESCRIPTION command is in stable CLI. if (issue.additionalData.details) { diff --git a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts index dfb99b55a..66dd6d00e 100644 --- a/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts +++ b/src/snyk/snykOss/providers/ossVulnerabilityTreeProvider.ts @@ -53,7 +53,7 @@ export default class OssIssueTreeProvider extends ProductIssueTreeProvider[]): Issue[] { return issues.filter(vuln => { - switch (vuln.severity.toLowerCase()) { + switch (vuln.severity) { case IssueSeverity.Critical: return this.configuration.severityFilter.critical; case IssueSeverity.High: diff --git a/src/snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountEmitter.ts b/src/snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountEmitter.ts index dcde88813..d2b66dc38 100644 --- a/src/snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountEmitter.ts +++ b/src/snyk/snykOss/services/vulnerabilityCount/vulnerabilityCountEmitter.ts @@ -2,7 +2,6 @@ import EventEmitter from 'events'; import { ImportedModule, ModuleVulnerabilityCount } from './importedModule'; export enum VulnerabilityCountEvents { - PackageJsonFound = 'packageJsonFound', Start = 'start', Scanned = 'scanned', Done = 'done', @@ -10,10 +9,6 @@ export enum VulnerabilityCountEvents { } export class VulnerabilityCountEmitter extends EventEmitter { - packageJsonFound(fileName: string): void { - this.emit(VulnerabilityCountEvents.PackageJsonFound, fileName); - } - startScanning(importedModules: ImportedModule[]): void { this.emit(VulnerabilityCountEvents.Start, importedModules); } @@ -26,6 +21,7 @@ export class VulnerabilityCountEmitter extends EventEmitter { this.emit(VulnerabilityCountEvents.Done, moduleInfos); } + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents error(error: Error | unknown): void { this.emit(VulnerabilityCountEvents.Error, error); } diff --git a/src/test/integration/configuration.test.ts b/src/test/integration/configuration.test.ts index 2a0c94146..5c3e5d43a 100644 --- a/src/test/integration/configuration.test.ts +++ b/src/test/integration/configuration.test.ts @@ -3,7 +3,6 @@ import { FeaturesConfiguration } from '../../snyk/common/configuration/configura import { configuration } from '../../snyk/common/configuration/instance'; import vscode from 'vscode'; import { ADVANCED_CUSTOM_ENDPOINT } from '../../snyk/common/constants/settings'; -import { extensionContext } from '../../snyk/common/vscode/extensionContext'; suite('Configuration', () => { test('settings change is reflected', async () => { diff --git a/src/test/unit/base/services/authenticationService.test.ts b/src/test/unit/base/services/authenticationService.test.ts index f77004874..f0a78cd2a 100644 --- a/src/test/unit/base/services/authenticationService.test.ts +++ b/src/test/unit/base/services/authenticationService.test.ts @@ -25,17 +25,6 @@ suite('AuthenticationService', () => { let clearTokenSpy: sinon.SinonSpy; let previewFeaturesSpy: sinon.SinonSpy; - const NEEDLE_DEFAULT_TIMEOUT = 1000; - - const overrideNeedleTimeoutOptions = { - // eslint-disable-next-line camelcase - open_timeout: NEEDLE_DEFAULT_TIMEOUT, - // eslint-disable-next-line camelcase - response_timeout: NEEDLE_DEFAULT_TIMEOUT, - // eslint-disable-next-line camelcase - read_timeout: NEEDLE_DEFAULT_TIMEOUT, - }; - setup(() => { baseModule = {} as IBaseSnykModule; setContextSpy = sinon.fake(); diff --git a/src/test/unit/common/commands/commandController.test.ts b/src/test/unit/common/commands/commandController.test.ts index 3da22aa27..6d37814c7 100644 --- a/src/test/unit/common/commands/commandController.test.ts +++ b/src/test/unit/common/commands/commandController.test.ts @@ -16,8 +16,7 @@ import { IConfiguration } from '../../../../snyk/common/configuration/configurat import { IFolderConfigs } from '../../../../snyk/common/configuration/folderConfigs'; suite('CommandController', () => { - const sleep = util.promisify(setTimeout); - + util.promisify(setTimeout); let controller: CommandController; setup(() => { diff --git a/src/test/unit/common/languageServer/languageServer.test.ts b/src/test/unit/common/languageServer/languageServer.test.ts index 561b6b3b3..11449b01f 100644 --- a/src/test/unit/common/languageServer/languageServer.test.ts +++ b/src/test/unit/common/languageServer/languageServer.test.ts @@ -234,7 +234,7 @@ suite('Language Server', () => { folderConfigs: [], authenticationMethod: 'oauth', enableSnykOSSQuickFixCodeActions: 'false', - hoverVerbosity: 0, + hoverVerbosity: 1, }; deepStrictEqual(await languageServer.getInitializationOptions(), expectedInitializationOptions); diff --git a/src/test/unit/mocks/uri.mock.ts b/src/test/unit/mocks/uri.mock.ts deleted file mode 100644 index 929b380a2..000000000 --- a/src/test/unit/mocks/uri.mock.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Uri } from '../../../snyk/common/vscode/types'; -import { IUriAdapter } from '../../../snyk/common/vscode/uri'; - -class UriAdapterMock implements IUriAdapter { - file(path: string): Uri { - return { - path: path, - } as Uri; - } - - parse(path: string): Uri { - return { - path: path, - } as Uri; - } -} diff --git a/src/test/unit/mocks/workspace.mock.ts b/src/test/unit/mocks/workspace.mock.ts index a46d64348..2e1ee2329 100644 --- a/src/test/unit/mocks/workspace.mock.ts +++ b/src/test/unit/mocks/workspace.mock.ts @@ -1,5 +1,3 @@ -import * as os from 'os'; -import path from 'path'; import { IVSCodeWorkspace } from '../../../snyk/common/vscode/workspace'; export function stubWorkspaceConfiguration(configSetting: string, returnValue: T | undefined): IVSCodeWorkspace { diff --git a/src/test/unit/snykCode/codeActions/codeIssuesActionsProvider.test.ts b/src/test/unit/snykCode/codeActions/codeIssuesActionsProvider.test.ts index 18c5a9ad3..3a787cf1a 100644 --- a/src/test/unit/snykCode/codeActions/codeIssuesActionsProvider.test.ts +++ b/src/test/unit/snykCode/codeActions/codeIssuesActionsProvider.test.ts @@ -9,7 +9,6 @@ import { CodeActionContext, CodeActionKind, Range, TextDocument } from '../../.. import { SnykCodeActionsProvider } from '../../../../snyk/snykCode/codeActions/codeIssuesActionsProvider'; import { IssueUtils } from '../../../../snyk/snykCode/utils/issueUtils'; import { IConfiguration } from '../../../../snyk/common/configuration/configuration'; -import { FEATURE_FLAGS } from '../../../../snyk/common/constants/featureFlags'; suite('Snyk Code actions provider', () => { let issuesActionsProvider: SnykCodeActionsProvider; diff --git a/src/test/unit/snykCode/codeSettings.test.ts b/src/test/unit/snykCode/codeSettings.test.ts index 56fd82c0b..604e1e5b3 100644 --- a/src/test/unit/snykCode/codeSettings.test.ts +++ b/src/test/unit/snykCode/codeSettings.test.ts @@ -10,12 +10,10 @@ import { CodeSettings, ICodeSettings } from '../../../snyk/snykCode/codeSettings suite('Snyk Code Settings', () => { let settings: ICodeSettings; let setContextFake: SinonSpy; - let setFeatureFlagFake: SinonSpy; let contextService: IContextService; setup(() => { setContextFake = sinon.fake(); - setFeatureFlagFake = sinon.fake(); contextService = { setContext: setContextFake, diff --git a/src/test/unit/snykOss/providers/ossCodeActionsProvider.test.ts b/src/test/unit/snykOss/providers/ossCodeActionsProvider.test.ts index c311a7b8c..42f55e676 100644 --- a/src/test/unit/snykOss/providers/ossCodeActionsProvider.test.ts +++ b/src/test/unit/snykOss/providers/ossCodeActionsProvider.test.ts @@ -96,8 +96,11 @@ suite('OSS code actions provider', () => { sinon.stub(ossActionsProvider, 'getIssueRange').returns(rangeMock); // stubbing private methods workaround is to cast to any + // eslint-disable-next-line @typescript-eslint/no-explicit-any sinon.stub(ossActionsProvider, 'getVulnerabilities').returns(vulnerabilities); + // eslint-disable-next-line @typescript-eslint/no-explicit-any sinon.stub(ossActionsProvider, 'getMostSevereVulnerability').returns(mostSevereVulnerability); + // eslint-disable-next-line @typescript-eslint/no-explicit-any sinon.stub(ossActionsProvider, 'getActions').returns(codeActions); // act diff --git a/src/test/unit/snykOss/providers/vulnerabilityCountProvider.test.ts b/src/test/unit/snykOss/providers/vulnerabilityCountProvider.test.ts index df42b0df7..6bda8c3bc 100644 --- a/src/test/unit/snykOss/providers/vulnerabilityCountProvider.test.ts +++ b/src/test/unit/snykOss/providers/vulnerabilityCountProvider.test.ts @@ -60,7 +60,7 @@ suite('OSS VulnerabilityCountProvider', () => { let sampleFileName = 'package.json'; const sameplUri = `file:///Users/some.user/Documents/some-project/${sampleFileName}`; - let languageClientStub: { sendRequest: any }; + let languageClientStub: { sendRequest: unknown }; let uriStub; setup(() => {