Skip to content

Commit

Permalink
refactor: create duplicate snykOss folder
Browse files Browse the repository at this point in the history
  • Loading branch information
j-luong committed Oct 23, 2023
1 parent 92fc0fc commit 5fda560
Show file tree
Hide file tree
Showing 24 changed files with 2,357 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/snyk/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import {
SNYK_OPEN_BROWSER_COMMAND,
SNYK_OPEN_ISSUE_COMMAND,
SNYK_OPEN_LOCAL_COMMAND,
SNYK_SET_TOKEN_COMMAND,
SNYK_SETTINGS_COMMAND,
SNYK_SET_TOKEN_COMMAND,
SNYK_SHOW_LS_OUTPUT_COMMAND,
SNYK_SHOW_OUTPUT_COMMAND,
SNYK_START_COMMAND,
Expand Down
81 changes: 81 additions & 0 deletions src/snyk/snykOss/codeActions/vulnerabilityCodeActionProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { IAnalytics } from '../../common/analytics/itly';
import { OpenCommandIssueType, OpenIssueCommandArg } from '../../common/commands/types';
import { SNYK_OPEN_ISSUE_COMMAND } from '../../common/constants/commands';
import { IDE_NAME } from '../../common/constants/general';
import { ICodeActionKindAdapter } from '../../common/vscode/codeAction';
import {
CodeAction,
CodeActionContext,
CodeActionKind,
CodeActionProvider,
Command,
ProviderResult,
Range,
Selection,
TextDocument,
} from '../../common/vscode/types';
import { DIAGNOSTICS_OSS_COLLECTION_NAME } from '../../snykCode/constants/analysis';
import { messages } from '../messages/vulnerabilityCount';
import { isResultCliError } from '../ossResult';
import { OssService } from '../services/ossService';
import { ModuleVulnerabilityCountProvider } from '../services/vulnerabilityCount/vulnerabilityCountProvider';

export class VulnerabilityCodeActionProvider implements CodeActionProvider {
public codeActionKinds: ReadonlyArray<CodeActionKind> = [this.codeActionKindProvider.getQuickFix()];

constructor(
private readonly ossService: OssService,
private readonly vulnerabilityCountProvider: ModuleVulnerabilityCountProvider,
private readonly codeActionKindProvider: ICodeActionKindAdapter,
private readonly analytics: IAnalytics,
) {}

async provideCodeActions(
document: TextDocument,
_: Range | Selection,
context: CodeActionContext,
): Promise<ProviderResult<(CodeAction | Command)[]>> {
const ossDiagnostics = context.diagnostics.filter(d => d.source === DIAGNOSTICS_OSS_COLLECTION_NAME);
if (!ossDiagnostics.length) {
return;
}

const ossResult = this.ossService.getResultArray();
if (!ossResult) {
return;
}

const fileResult = ossResult.find(
res => !isResultCliError(res) && this.vulnerabilityCountProvider.isFilePartOfOssTest(document.fileName, res),
);

if (!fileResult || isResultCliError(fileResult)) {
return;
}

for (const diagnostic of ossDiagnostics) {
const vulnerability = fileResult.vulnerabilities.find(vuln => vuln.id === diagnostic.code);
if (!vulnerability) {
continue;
}

const command: Command = {
command: SNYK_OPEN_ISSUE_COMMAND,
title: messages.showMostSevereVulnerability,
arguments: [
{
issueType: OpenCommandIssueType.OssVulnerability,
issue: await this.ossService.getOssIssueCommandArg(vulnerability, fileResult.vulnerabilities),
} as OpenIssueCommandArg,
],
};

this.analytics.logQuickFixIsDisplayed({
quickFixType: ['Show Most Severe Vulnerability'],
ide: IDE_NAME,
});

return [command];
}
}
}
44 changes: 44 additions & 0 deletions src/snyk/snykOss/constants/nativeModules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
export default [
'assert',
'async_hooks',
'buffer',
'child_process',
'cluster',
'console',
'constants',
'crypto',
'dgram',
'dns',
'domain',
'events',
'fs',
'http',
'http2',
'https',
'inspector',
'internal',
'module',
'net',
'os',
'path',
'perf_hooks',
'process',
'punycode',
'querystring',
'readline',
'repl',
'stream',
'string_decoder',
'sys',
'timers',
'tls',
'trace_events',
'tty',
'url',
'util',
'v8',
'vm',
'wasi',
'worker_threads',
'zlib',
];
124 changes: 124 additions & 0 deletions src/snyk/snykOss/editor/editorDecorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import _ from 'lodash';
import { getRenderOptions, LineDecorations, updateDecorations } from '../../common/editor/editorDecorator';
import { IVSCodeLanguages } from '../../common/vscode/languages';
import { IThemeColorAdapter } from '../../common/vscode/theme';
import { TextEditorDecorationType } from '../../common/vscode/types';
import { IVSCodeWindow } from '../../common/vscode/window';
import { messages } from '../messages/vulnerabilityCount';
import { ImportedModule, ModuleVulnerabilityCount } from '../services/vulnerabilityCount/importedModule';

export class EditorDecorator {
private readonly decorationType: TextEditorDecorationType;
private readonly fileDecorationMap: Map<string, LineDecorations>;
private readonly editorLastCharacterIndex = Number.MAX_SAFE_INTEGER;

private updateTimeout: NodeJS.Timer | undefined = undefined;

constructor(
private readonly window: IVSCodeWindow,
private readonly languages: IVSCodeLanguages,
private readonly themeColorAdapter: IThemeColorAdapter,
) {
this.fileDecorationMap = new Map<string, LineDecorations>();
this.decorationType = this.window.createTextEditorDecorationType({
after: { margin: '0 0 0 1rem' },
});
}

get fileDecorations(): ReadonlyMap<string, LineDecorations> {
return this.fileDecorationMap;
}

resetDecorations(filePath: string): void {
const decorations = this.fileDecorationMap.get(filePath);
if (!decorations) {
return;
}

const emptyDecorations = decorations.map(d => ({
...d,
renderOptions: getRenderOptions('', this.themeColorAdapter),
}));
this.fileDecorationMap.set(filePath, emptyDecorations);
this.triggerUpdateDecorations(filePath);
}

setScanStartDecorations(filePath: string, modules: ImportedModule[]): void {
const lineDecorations: LineDecorations = [];

for (const module of modules) {
if (module.line == null) {
continue;
}

lineDecorations[module.line] = {
range: this.languages.createRange(
module.line - 1,
this.editorLastCharacterIndex,
module.line - 1,
this.editorLastCharacterIndex,
),
renderOptions: getRenderOptions(messages.fetchingVulnerabilities, this.themeColorAdapter),
};
}

if (!lineDecorations.length) {
// return early when no decorations have been created
return;
}

this.fileDecorationMap.set(filePath, lineDecorations);
this.triggerUpdateDecorations(filePath);
}

setScanDoneDecorations(filePath: string, vulnerabilityCounts: ModuleVulnerabilityCount[]): void {
for (const moduleVulnerabilityCount of vulnerabilityCounts) {
this.setScannedDecoration(moduleVulnerabilityCount, false);
}

this.triggerUpdateDecorations(filePath);
}

setScannedDecoration(vulnerabilityCount: ModuleVulnerabilityCount, triggerUpdate = true): void {
if (_.isNull(vulnerabilityCount.line)) {
return;
}

const filePath = vulnerabilityCount.fileName;

let lineDecorations = this.fileDecorationMap.get(filePath);
if (!lineDecorations) {
lineDecorations = [];
this.fileDecorationMap.set(filePath, lineDecorations); // set map, if no decoration was set before
}

const text = vulnerabilityCount.count ? messages.decoratorMessage(vulnerabilityCount.count) : '';

lineDecorations[vulnerabilityCount.line] = {
range: this.languages.createRange(
vulnerabilityCount.line - 1,
this.editorLastCharacterIndex,
vulnerabilityCount.line - 1,
this.editorLastCharacterIndex,
),
renderOptions: getRenderOptions(text, this.themeColorAdapter),
};

if (triggerUpdate) {
this.triggerUpdateDecorations(filePath, 500);
}
}

private triggerUpdateDecorations(filePath: string, updateTimeoutInMs = 10): void {
if (this.updateTimeout) {
clearTimeout(this.updateTimeout);
this.updateTimeout = undefined;
}

const lineDecorations = this.fileDecorationMap.get(filePath) || [];
this.updateTimeout = setTimeout(
() => updateDecorations(this.window, filePath, lineDecorations, this.decorationType),
updateTimeoutInMs,
);
}
}
49 changes: 49 additions & 0 deletions src/snyk/snykOss/hoverProvider/vulnerabilityCountHoverProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { IAnalytics } from '../../common/analytics/itly';
import { IDE_NAME } from '../../common/constants/general';
import { SupportedLanguageIds } from '../../common/constants/languageConsts';
import { IVSCodeLanguages } from '../../common/vscode/languages';
import { DiagnosticCollection, Disposable, Hover, Position, TextDocument } from '../../common/vscode/types';
import { IssueUtils } from '../../snykCode/utils/issueUtils';

export class VulnerabilityCountHoverProvider implements Disposable {
private hoverProvider: Disposable | undefined;

constructor(private readonly vscodeLanguages: IVSCodeLanguages, private readonly analytics: IAnalytics) {}

register(diagnostics: DiagnosticCollection | undefined): Disposable {
const documentFilter = SupportedLanguageIds.map(id => ({ scheme: 'file', language: id }));

this.hoverProvider = this.vscodeLanguages.registerHoverProvider(documentFilter, {
provideHover: this.getHover(diagnostics),
});

return this;
}

getHover(diagnostics: DiagnosticCollection | undefined) {
return (document: TextDocument, position: Position): Hover | undefined => {
if (!diagnostics || !diagnostics.has(document.uri)) {
return undefined;
}

const currentFileReviewIssues = diagnostics.get(document.uri);
const issue = IssueUtils.findIssueWithRange(position, currentFileReviewIssues);
if (issue) {
this.logIssueHoverIsDisplayed();
}
};
}

private logIssueHoverIsDisplayed(): void {
this.analytics.logIssueHoverIsDisplayed({
issueType: 'Open Source Vulnerability',
ide: IDE_NAME,
});
}

dispose(): void {
if (this.hoverProvider) {
this.hoverProvider.dispose();
}
}
}
3 changes: 3 additions & 0 deletions src/snyk/snykOss/messages/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const messages = {
suggestionViewShowFailed: 'Failed to show Snyk OSS suggestion view',
};
9 changes: 9 additions & 0 deletions src/snyk/snykOss/messages/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const messages = {
testFailed: 'Open Source Security test failed.',
testStarted: 'Open Source Security test started.',
viewResults: 'View results',
hide: "Don't show again",

testFailedForPath: (path: string): string => `Open Source Security test failed for "${path}".`,
testFinished: (projectName: string): string => `Open Source Security test finished for "${projectName}".`,
};
11 changes: 11 additions & 0 deletions src/snyk/snykOss/messages/treeView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const messages = {
cookingDependencies: 'Scanning...',

runTest: 'Run scan for Open Source security vulnerabilities.',
noVulnerabilitiesFound: ' ✅ Congrats! Snyk found no vulnerabilities.',
singleVulnerabilityFound: 'Snyk found 1 vulnerability',
vulnerability: 'vulnerability',
vulnerabilities: 'vulnerabilities',

multipleVulnerabilitiesFound: (issueCount: number): string => `Snyk found ${issueCount} vulnerabilities`,
};
19 changes: 19 additions & 0 deletions src/snyk/snykOss/messages/vulnerabilityCount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ModuleVulnerabilityCount } from '../services/vulnerabilityCount/importedModule';

export const messages = {
fetchingVulnerabilities: 'Fetching vulnerabilities...',
vulnerability: 'vulnerability',
vulnerabilities: 'vulnerabilities',
showMostSevereVulnerability: 'Show the most severe vulnerability (Snyk)',

decoratorMessage: (vulnerabilityCount: string): string => {
const vulnerabilityCountNumber = Number.parseInt(vulnerabilityCount, 10);
if (isNaN(vulnerabilityCountNumber)) {
return vulnerabilityCount;
}
return `${vulnerabilityCountNumber} ${vulnerabilityCountNumber > 1 ? 'vulnerabilities' : 'vulnerability'}`;
},

diagnosticMessagePrefix: (module: ModuleVulnerabilityCount): string =>
`Dependency ${module.name}${module.version ? `@${module.version}` : ''} has `,
};
Loading

0 comments on commit 5fda560

Please sign in to comment.