Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: issue handling #238

Merged
merged 5 commits into from
Jan 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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