Skip to content

Commit

Permalink
Merge branch 'master' into patch-2
Browse files Browse the repository at this point in the history
  • Loading branch information
IlonaShishov authored Dec 20, 2023
2 parents b84c930 + fedace0 commit f6f4add
Show file tree
Hide file tree
Showing 10 changed files with 677 additions and 173 deletions.
12 changes: 6 additions & 6 deletions package-lock.json

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

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@fabric8-analytics/fabric8-analytics-lsp-server",
"description": "LSP Server for Red Hat Dependency Analytics",
"version": "0.8.1-ea.2",
"version": "0.8.1-ea.7",
"author": "Red Hat",
"contributors": [
{
Expand All @@ -24,7 +24,7 @@
"dist"
],
"dependencies": {
"@RHEcosystemAppEng/exhort-javascript-api": "^0.1.1-ea.2",
"@RHEcosystemAppEng/exhort-javascript-api": "^0.1.1-ea.4",
"@xml-tools/ast": "^5.0.5",
"@xml-tools/parser": "^1.0.11",
"json-to-ast": "^2.1.0",
Expand Down
76 changes: 74 additions & 2 deletions src/codeActionHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,57 @@
* ------------------------------------------------------------------------------------------ */
'use strict';

import * as path from 'path';
import { CodeAction, CodeActionKind, Diagnostic } from 'vscode-languageserver/node';

import { globalConfig } from './config';
import { RHDA_DIAGNOSTIC_SOURCE } from './constants';

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

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

/**
* Clears the code actions map.
*/
function clearCodeActionsMap() {
codeActionsMap = new Map<string, CodeAction[]>();
}

/**
* Registers a code action.
* @param key - The key to register the 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);
}

/**
* Retrieves code actions based on diagnostics and file type.
* @param diagnostics - An array of available diagnostics.
* @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[] {
const hasRhdaDiagonostic = diagnostics.some(diagnostic => diagnostic.source === RHDA_DIAGNOSTIC_SOURCE);
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] || [];
codeActions.push(...diagnosticCodeActions);

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

if (globalConfig.triggerFullStackAnalysis && hasRhdaDiagonostic) {
codeActions.push(generateFullStackAnalysisAction());
}
Expand All @@ -39,4 +77,38 @@ function generateFullStackAnalysisAction(): CodeAction {
};
}

export { getDiagnosticsCodeActions };
/**
* Generates a code action to switch to the recommended version.
* @param title - The title of the code action.
* @param versionReplacementString - The version replacement string.
* @param diagnostic - The diagnostic information.
* @param uri - The URI of the file.
* @returns A CodeAction object for switching to the recommended version.
*/
function generateSwitchToRecommendedVersionAction(title: string, versionReplacementString: string, diagnostic: Diagnostic, uri: string): CodeAction {
const codeAction: CodeAction = {
title: title,
diagnostics: [diagnostic],
kind: CodeActionKind.QuickFix,
edit: {
changes: {
}
}
};

codeAction.edit.changes[uri] = [{
range: diagnostic.range,
newText: versionReplacementString
}];

if (path.basename(uri) === 'pom.xml') {
codeAction.command = {
title: 'RedHat repository recommendation',
command: globalConfig.triggerRHRepositoryRecommendationNotification,
};
}

return codeAction;
}

export { getCodeActionsMap, clearCodeActionsMap, registerCodeAction , generateSwitchToRecommendedVersionAction, getDiagnosticsCodeActions };
72 changes: 59 additions & 13 deletions src/componentAnalysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ class Source implements ISource {
interface IDependencyData {
sourceId: string;
issuesCount: number;
recommendationRef: string;
remediationRef: string
highestVulnerabilitySeverity: string;
}

Expand All @@ -45,6 +47,8 @@ class DependencyData implements IDependencyData {
constructor(
public sourceId: string,
public issuesCount: number,
public recommendationRef: string,
public remediationRef: string,
public highestVulnerabilitySeverity: string
) {}
}
Expand All @@ -69,10 +73,12 @@ class AnalysisResponse implements IAnalysisResponse {

if (isDefined(resData, 'providers')) {
Object.entries(resData.providers).map(([providerName, providerData]) => {
if (isDefined(providerData, 'status', 'ok') && isDefined(providerData, 'sources') && providerData.status.ok) {
Object.entries(providerData.sources).map(([sourceName, sourceData]) => {
sources.push(new Source(`${providerName}(${sourceName})`, isDefined(sourceData, 'dependencies') ? sourceData.dependencies : []));
});
if (isDefined(providerData, 'status', 'ok') && providerData.status.ok) {
if (isDefined(providerData, 'sources')) {
Object.entries(providerData.sources).map(([sourceName, sourceData]) => {
sources.push(new Source(`${providerName}(${sourceName})`, this.getDependencies(sourceData)));
});
}
} else {
failedProviders.push(providerName);
}
Expand All @@ -89,17 +95,61 @@ class AnalysisResponse implements IAnalysisResponse {

sources.forEach(source => {
source.dependencies.forEach(d => {
if (isDefined(d, 'ref') && isDefined(d, 'issues')) {
const dd = new DependencyData(source.id, d.issues.length, isDefined(d, 'highestVulnerability', 'severity') ? d.highestVulnerability.severity : 'NONE');
if (this.dependencies[d.ref] === undefined) {
this.dependencies[d.ref] = [];
}
if (isDefined(d, 'ref')) {

const issuesCount: number = isDefined(d, 'issues') ? d.issues.length : 0;

const dd = issuesCount
? new DependencyData(source.id, issuesCount, '', this.getRemediation(d.issues[0]), this.getHighestSeverity(d))
: new DependencyData(source.id, issuesCount, this.getRecommendation(d), '', this.getHighestSeverity(d));

this.dependencies[d.ref] = this.dependencies[d.ref] || [];
this.dependencies[d.ref].push(dd);
}
});
});
}
}

/**
* Retrieves dependencies from source.
* @param sourceData The source object.
* @returns An array of dependencies or empty array if none exists.
* @private
*/
private getDependencies(sourceData: any): any[] {
return isDefined(sourceData, 'dependencies') ? sourceData.dependencies : [];
}

/**
* Retrieves the highest vulnerability severity value from a dependency.
* @param dependency The dependency object.
* @returns The highest severity level or NONE if none exists.
* @private
*/
private getHighestSeverity(dependency: any): string {
return isDefined(dependency, 'highestVulnerability', 'severity') ? dependency.highestVulnerability.severity : 'NONE';
}

/**
* Retrieves the remediation reference from an issue.
* @param issue The issue object.
* @returns The remediation reference or empty string if none exists.
* @private
*/
private getRemediation(issue: any): string {
return isDefined(issue, 'remediation', 'trustedContent', 'ref') ? issue.remediation.trustedContent.ref.split('?')[0] : '';
}

/**
* Retrieves the recommendation reference from a dependency.
* @param dependency The dependency object.
* @returns The recommendation reference or empty string if none exists.
* @private
*/
private getRecommendation(dependency: any): string {
return isDefined(dependency, 'recommendation') ? dependency.recommendation.split('?')[0] : '';
}
}

/**
Expand All @@ -126,10 +176,6 @@ async function executeComponentAnalysis (diagnosticFilePath: string, contents: s
if (globalConfig.exhortSnykToken !== '') {
options['EXHORT_SNYK_TOKEN'] = globalConfig.exhortSnykToken;
}
if (globalConfig.exhortOSSIndexUser !== '' && globalConfig.exhortOSSIndexToken !== '') {
options['EXHORT_OSS_INDEX_USER'] = globalConfig.exhortOSSIndexUser;
options['EXHORT_OSS_INDEX_TOKEN'] = globalConfig.exhortOSSIndexToken;
}

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

Expand Down
52 changes: 25 additions & 27 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,29 @@
*/
class Config
{
triggerFullStackAnalysis: string;
telemetryId: string;
utmSource: string;
exhortSnykToken: string;
exhortOSSIndexUser: string;
exhortOSSIndexToken: string;
matchManifestVersions: string;
exhortMvnPath: string;
exhortNpmPath: string;
exhortGoPath: string;
exhortPython3Path: string;
exhortPip3Path: string;
exhortPythonPath: string;
exhortPipPath: string;
triggerFullStackAnalysis: string;
triggerRHRepositoryRecommendationNotification: string;
telemetryId: string;
utmSource: string;
exhortSnykToken: string;
matchManifestVersions: string;
exhortMvnPath: string;
exhortNpmPath: string;
exhortGoPath: string;
exhortPython3Path: string;
exhortPip3Path: string;
exhortPythonPath: string;
exhortPipPath: string;

/**
* Initializes a new instance of the Config class with default values from the parent process environment variable data.
*/
constructor() {
this.triggerFullStackAnalysis = process.env.VSCEXT_TRIGGER_FULL_STACK_ANALYSIS || '';
this.triggerRHRepositoryRecommendationNotification = process.env.VSCEXT_TRIGGER_REDHAT_REPOSITORY_RECOMMENDATION_NOTIFICATION || '';
this.telemetryId = process.env.VSCEXT_TELEMETRY_ID || '';
this.utmSource = process.env.VSCEXT_UTM_SOURCE || '';
this.exhortSnykToken = process.env.VSCEXT_EXHORT_SNYK_TOKEN || '';
this.exhortOSSIndexUser = process.env.VSCEXT_EXHORT_OSS_INDEX_USER || '';
this.exhortOSSIndexToken = process.env.VSCEXT_EXHORT_OSS_INDEX_TOKEN || '';
this.matchManifestVersions = process.env.VSCEXT_MATCH_MANIFEST_VERSIONS || 'true';
this.exhortMvnPath = process.env.VSCEXT_EXHORT_MVN_PATH || 'mvn';
this.exhortNpmPath = process.env.VSCEXT_EXHORT_NPM_PATH || 'npm';
Expand All @@ -50,17 +48,17 @@ class Config
* @param data - The data from extension workspace settings to update the global configuration with.
*/
updateConfig( data: any ) {
this.exhortSnykToken = data.redHatDependencyAnalytics.exhortSnykToken;
this.exhortOSSIndexUser = data.redHatDependencyAnalytics.exhortOSSIndexUser;
this.exhortOSSIndexToken = data.redHatDependencyAnalytics.exhortOSSIndexToken;
this.matchManifestVersions = data.redHatDependencyAnalytics.matchManifestVersions ? 'true' : 'false';
this.exhortMvnPath = data.mvn.executable.path || 'mvn';
this.exhortNpmPath = data.npm.executable.path || 'npm';
this.exhortGoPath = data.go.executable.path || 'go';
this.exhortPython3Path = data.python3.executable.path || 'python3';
this.exhortPip3Path = data.pip3.executable.path || 'pip3';
this.exhortPythonPath = data.python.executable.path || 'python';
this.exhortPipPath = data.pip.executable.path || 'pip';
const rhdaData = data.redHatDependencyAnalytics;

this.exhortSnykToken = rhdaData.exhortSnykToken;
this.matchManifestVersions = rhdaData.matchManifestVersions ? 'true' : 'false';
this.exhortMvnPath = rhdaData.mvn.executable.path || 'mvn';
this.exhortNpmPath = rhdaData.npm.executable.path || 'npm';
this.exhortGoPath = rhdaData.go.executable.path || 'go';
this.exhortPython3Path = rhdaData.python3.executable.path || 'python3';
this.exhortPip3Path = rhdaData.pip3.executable.path || 'pip3';
this.exhortPythonPath = rhdaData.python.executable.path || 'python';
this.exhortPipPath = rhdaData.pip.executable.path || 'pip';
}
}

Expand Down
Loading

0 comments on commit f6f4add

Please sign in to comment.