Skip to content

Commit

Permalink
chore: issue handling (#238)
Browse files Browse the repository at this point in the history
* feat: add vulnerability alert severity setting (#235)

Signed-off-by: Ilona Shishov <[email protected]>

* fix: upgrade codeActionsMap to support multiple open documents accurately (#236)

Signed-off-by: Ilona Shishov <[email protected]>

* chore: Optimize workspace configuration retrieval efficiency.

Signed-off-by: Ilona Shishov <[email protected]>

* fix: updrade to JS API version supporting pom modules (#237)

Signed-off-by: Ilona Shishov <[email protected]>

* fix: updrade to JS API version supporting spaces in file paths

Signed-off-by: Ilona Shishov <[email protected]>

---------

Signed-off-by: Ilona Shishov <[email protected]>
  • Loading branch information
IlonaShishov authored Jan 21, 2024
1 parent 6fb85a3 commit aea9ef6
Show file tree
Hide file tree
Showing 13 changed files with 282 additions and 149 deletions.
14 changes: 10 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"dist"
],
"dependencies": {
"@RHEcosystemAppEng/exhort-javascript-api": "^0.1.1-ea.5",
"@RHEcosystemAppEng/exhort-javascript-api": "^0.1.1-ea.14",
"@xml-tools/ast": "^5.0.5",
"@xml-tools/parser": "^1.0.11",
"json-to-ast": "^2.1.0",
Expand Down
34 changes: 20 additions & 14 deletions src/codeActionHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,35 @@ import { CodeAction, CodeActionKind, Diagnostic } from 'vscode-languageserver/no
import { globalConfig } from './config';
import { RHDA_DIAGNOSTIC_SOURCE } from './constants';

let codeActionsMap: Map<string, CodeAction[]> = new Map<string, CodeAction[]>();
const codeActionsMap: Map<string, Map<string, CodeAction[]>> = new Map<string, Map<string, CodeAction[]>>();

/**
* Gets the code actions map.
*/
function getCodeActionsMap(): Map<string, CodeAction[]> {
function getCodeActionsMap(): Map<string, Map<string, CodeAction[]>> {
return codeActionsMap;
}

/**
* Clears the code actions map.
* Clears code actions related to a specific file URI from the code actions map.
* @param uri - The file URI key to remove from the code actions map.
*/
function clearCodeActionsMap() {
codeActionsMap = new Map<string, CodeAction[]>();
function clearCodeActionsMap(uri: string) {
codeActionsMap.delete(uri);
}

/**
* Registers a code action.
* @param key - The key to register the code action against.
* @param uri - The file uri to register the file code action map (inner map) against.
* @param loc - The location in file to register the file code action against.
* @param codeAction - The code action to be registered.
*/
function registerCodeAction(key: string, codeAction: CodeAction) {
codeActionsMap[key] = codeActionsMap[key] || [];
codeActionsMap[key].push(codeAction);
function registerCodeAction(uri: string, loc: string, codeAction: CodeAction) {
codeActionsMap.set(uri, codeActionsMap.get(uri) || new Map<string, CodeAction[]>());

const innerMap = codeActionsMap.get(uri);
innerMap.set(loc, innerMap.get(loc) || []);
innerMap.get(loc).push(codeAction);
}

/**
Expand All @@ -42,19 +47,20 @@ function registerCodeAction(key: string, codeAction: CodeAction) {
* @param uri - The URI of the file being analyzed.
* @returns An array of CodeAction objects to be made available to the user.
*/
function getDiagnosticsCodeActions(diagnostics: Diagnostic[]): CodeAction[] {
function getDiagnosticsCodeActions(diagnostics: Diagnostic[], uri: string): CodeAction[] {
let hasRhdaDiagonostic: boolean = false;
const codeActions: CodeAction[] = [];

for (const diagnostic of diagnostics) {

const key = `${diagnostic.range.start.line}|${diagnostic.range.start.character}`;
const diagnosticCodeActions = codeActionsMap[key] || [];
const fileCodeActionsMap = codeActionsMap.get(uri) || new Map<string, CodeAction[]>();
const loc = `${diagnostic.range.start.line}|${diagnostic.range.start.character}`;
const diagnosticCodeActions = fileCodeActionsMap.get(loc) || [];
codeActions.push(...diagnosticCodeActions);

hasRhdaDiagonostic ||= diagnostic.source === RHDA_DIAGNOSTIC_SOURCE;
}

if (globalConfig.triggerFullStackAnalysis && hasRhdaDiagonostic) {
codeActions.push(generateFullStackAnalysisAction());
}
Expand Down
4 changes: 2 additions & 2 deletions src/componentAnalysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import exhort from '@RHEcosystemAppEng/exhort-javascript-api';

import { connection } from './server';
import { globalConfig } from './config';
import { isDefined } from './utils';
import { isDefined, decodeUriPath } from './utils';

/**
* Represents a source object with an ID and dependencies array.
Expand Down Expand Up @@ -177,7 +177,7 @@ async function executeComponentAnalysis (diagnosticFilePath: string, contents: s
options['EXHORT_SNYK_TOKEN'] = globalConfig.exhortSnykToken;
}

const componentAnalysisJson = await exhort.componentAnalysis(path.basename(diagnosticFilePath), contents, options); // Execute component analysis
const componentAnalysisJson = await exhort.componentAnalysis(path.basename(diagnosticFilePath), contents, options, decodeUriPath(diagnosticFilePath)); // Execute component analysis

return new AnalysisResponse(componentAnalysisJson, diagnosticFilePath);
}
Expand Down
7 changes: 4 additions & 3 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class Config
utmSource: string;
exhortSnykToken: string;
matchManifestVersions: string;
vulnerabilityAlertSeverity: string;
exhortMvnPath: string;
exhortNpmPath: string;
exhortGoPath: string;
Expand All @@ -34,6 +35,7 @@ class Config
this.utmSource = process.env.VSCEXT_UTM_SOURCE || '';
this.exhortSnykToken = process.env.VSCEXT_EXHORT_SNYK_TOKEN || '';
this.matchManifestVersions = process.env.VSCEXT_MATCH_MANIFEST_VERSIONS || 'true';
this.vulnerabilityAlertSeverity = process.env.VSCEXT_VULNERABILITY_ALERT_SEVERITY || 'Error';
this.exhortMvnPath = process.env.VSCEXT_EXHORT_MVN_PATH || 'mvn';
this.exhortNpmPath = process.env.VSCEXT_EXHORT_NPM_PATH || 'npm';
this.exhortGoPath = process.env.VSCEXT_EXHORT_GO_PATH || 'go';
Expand All @@ -47,11 +49,10 @@ class Config
* Updates the global configuration with provided data from extension workspace settings.
* @param data - The data from extension workspace settings to update the global configuration with.
*/
updateConfig( data: any ) {
const rhdaData = data.redHatDependencyAnalytics;

updateConfig( rhdaData: any ) {
this.exhortSnykToken = rhdaData.exhortSnykToken;
this.matchManifestVersions = rhdaData.matchManifestVersions ? 'true' : 'false';
this.vulnerabilityAlertSeverity = rhdaData.vulnerabilityAlertSeverity;
this.exhortMvnPath = rhdaData.mvn.executable.path || 'mvn';
this.exhortNpmPath = rhdaData.npm.executable.path || 'npm';
this.exhortGoPath = rhdaData.go.executable.path || 'go';
Expand Down
6 changes: 3 additions & 3 deletions src/diagnosticsHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class DiagnosticsPipeline implements IDiagnosticsPipeline {

dependencyData.forEach(dd => {

const actionRef = vulnerabilityDiagnostic.severity === 1 ? dd.remediationRef : dd.recommendationRef;
const actionRef = vulnerabilityDiagnostic.severity < 3 ? dd.remediationRef : dd.recommendationRef;

if (actionRef) {
this.createCodeAction(loc, actionRef, dependency.context, dd.sourceId, vulnerabilityDiagnostic);
Expand Down Expand Up @@ -112,7 +112,7 @@ class DiagnosticsPipeline implements IDiagnosticsPipeline {
const versionReplacementString = context ? context.value.replace(VERSION_PLACEHOLDER, switchToVersion) : switchToVersion;
const title = `Switch to version ${switchToVersion} for ${sourceId}`;
const codeAction = generateSwitchToRecommendedVersionAction(title, versionReplacementString, vulnerabilityDiagnostic, this.diagnosticFilePath);
registerCodeAction(loc, codeAction);
registerCodeAction(this.diagnosticFilePath, loc, codeAction);
}
}

Expand All @@ -133,7 +133,7 @@ async function performDiagnostics(diagnosticFilePath: string, contents: string,

const response = await executeComponentAnalysis(diagnosticFilePath, contents);

clearCodeActionsMap();
clearCodeActionsMap(diagnosticFilePath);

diagnosticsPipeline.runDiagnostics(response.dependencies);

Expand Down
28 changes: 17 additions & 11 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,39 @@
'use strict';

import { TextDocumentSyncKind, Connection, DidChangeConfigurationNotification } from 'vscode-languageserver';
import { createConnection, TextDocuments, InitializeResult, CodeAction, ProposedFeatures } from 'vscode-languageserver/node';
import { createConnection, TextDocuments, InitializeParams, InitializeResult, CodeAction, ProposedFeatures } from 'vscode-languageserver/node';
import { TextDocument } from 'vscode-languageserver-textdocument';

import { globalConfig } from './config';
import { AnalysisLSPServer } from './fileHandler';
import { getDiagnosticsCodeActions } from './codeActionHandler';
import { getDiagnosticsCodeActions, clearCodeActionsMap } from './codeActionHandler';

/**
* Declares timeout identifier to track delays for server.handleFileEvent execution
*/
let checkDelay: NodeJS.Timeout;

/**
* Represents the connection used for the server, using Node's IPC as a transport.
* Create a connection for the server, using Node's IPC as a transport.
*/
const connection: Connection = createConnection(ProposedFeatures.all);

/**
* Represents the documents managed by the server.
* Declares servers configuration capability
*/
let hasConfigurationCapability: boolean = false;

/**
* Create a text document manager.
*/
const documents: TextDocuments<TextDocument> = new TextDocuments(TextDocument);
documents.listen(connection);

/**
* Sets up the connection's initialization event handler.
*/
let hasConfigurationCapability: boolean = false;
connection.onInitialize((params): InitializeResult => {
connection.onInitialize((params: InitializeParams): InitializeResult => {

const capabilities = params.capabilities;
hasConfigurationCapability = !!(
capabilities.workspace && !!capabilities.workspace.configuration
Expand Down Expand Up @@ -88,18 +93,19 @@ connection.onDidChangeTextDocument((params) => {
/**
* On close document event handler
*/
connection.onDidCloseTextDocument(() => {
connection.onDidCloseTextDocument((params) => {
clearTimeout(checkDelay);
clearCodeActionsMap(params.textDocument.uri);
});

/**
* Registers a callback when the configuration changes.
*/
connection.onDidChangeConfiguration(() => {
if (hasConfigurationCapability) {
server.conn.workspace.getConfiguration()
.then((data) => {
globalConfig.updateConfig(data);
server.conn.workspace.getConfiguration('redHatDependencyAnalytics')
.then((rhdaData) => {
globalConfig.updateConfig(rhdaData);
});
}
});
Expand All @@ -108,7 +114,7 @@ connection.onDidChangeConfiguration(() => {
* Handles code action requests from client.
*/
connection.onCodeAction((params): CodeAction[] => {
return getDiagnosticsCodeActions(params.context.diagnostics);
return getDiagnosticsCodeActions(params.context.diagnostics, params.textDocument.uri);
});

connection.listen();
Expand Down
11 changes: 11 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,15 @@ export function isDefined(obj: any, ...keys: string[]): boolean {
obj = obj[key];
}
return true;
}

/**
* Decodes the URI path from a given URI string.
* @param uri - The URI string to process.
* @returns The decoded URI path.
*/
export function decodeUriPath(uri: string): string {
const url = new URL(uri);
const decodedUri = decodeURIComponent(url.pathname);
return decodedUri;
}
17 changes: 15 additions & 2 deletions src/vulnerability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Range } from 'vscode-languageserver';
import { IDependencyProvider } from './collector';
import { DependencyData } from './componentAnalysis';
import { RHDA_DIAGNOSTIC_SOURCE } from './constants';
import { globalConfig } from './config';

/**
* Stores vulnerability data of a specific dependency.
Expand Down Expand Up @@ -57,9 +58,21 @@ Recommendation: ${this.provider.resolveDependencyFromReference(dependencyData.re
*/
getDiagnostic(): Diagnostic {

const hasIssues = this.dependencyData.some(data => data.issuesCount > 0);
let vulnerabilityAlertSeverity: DiagnosticSeverity;
switch (globalConfig.vulnerabilityAlertSeverity) {
case 'Error':
vulnerabilityAlertSeverity = DiagnosticSeverity.Error;
break;
case 'Warning':
vulnerabilityAlertSeverity = DiagnosticSeverity.Warning;
break;
default:
vulnerabilityAlertSeverity = DiagnosticSeverity.Error;
break;
}

const diagnosticSeverity = hasIssues ? DiagnosticSeverity.Error : DiagnosticSeverity.Information;
const hasIssues = this.dependencyData.some(data => data.issuesCount > 0);
const diagnosticSeverity = hasIssues ? vulnerabilityAlertSeverity : DiagnosticSeverity.Information;

const messages = this.dependencyData.map(dd => {
if (hasIssues && dd.issuesCount > 0) {
Expand Down
Loading

0 comments on commit aea9ef6

Please sign in to comment.