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

feat: support for golang #210

Merged
merged 1 commit into from
Sep 19, 2023
Merged
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
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
name: Stage
name: Release

on:
workflow_dispatch:
19 changes: 12 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Red Hat Dependency Analytics LSP Server

![Release](https://github.com/fabric8-analytics/fabric8-analytics-lsp-server/workflows/Release/badge.svg?branch=master)
[![NPM Version](https://img.shields.io/npm/v/fabric8-analytics-lsp-server.svg)](https://www.npmjs.com/package/fabric8-analytics-lsp-server)
![CI Build](https://github.com/fabric8-analytics/fabric8-analytics-lsp-server/workflows/CI%20Build/badge.svg?branch=master)
[![codecov](https://codecov.io/gh/fabric8-analytics/fabric8-analytics-lsp-server/branch/master/graph/badge.svg?token=aVThXjheDf)](https://codecov.io/gh/fabric8-analytics/fabric8-analytics-lsp-server)
![GitHub Package Version](https://img.shields.io/github/package-json/v/fabric8-analytics/fabric8-analytics-lsp-server/master?logo=github&label=GitHub%20Package)
![CI](https://github.com/fabric8-analytics/fabric8-analytics-lsp-server/workflows/CI/badge.svg?branch=master)
[![Codecov](https://codecov.io/gh/fabric8-analytics/fabric8-analytics-lsp-server/branch/master/graph/badge.svg?token=aVThXjheDf)](https://codecov.io/gh/fabric8-analytics/fabric8-analytics-lsp-server)

Language Server(LSP) that can analyze your dependencies specified in `package.json` and `pom.xml`.
Language Server(LSP) that can analyze your dependencies specified in `package.json`, `pom.xml` and `go.mod`.

## Build

@@ -21,9 +21,14 @@ npm test

## Release

Semantic release are done via Github Actions using `semantic-release`.
- merging each PR will result with an automatic build of master
- and a release apatch, minor or major version. You should use correct [commit message](https://github.com/semantic-release/semantic-release#commit-message-format).
Releases are done via Github Actions using `npm` to publish packages to `GitHub Packages`.
- Upon merging a pull request into the master branch, an automated staging process for the master branch is initiated.
- The version specified in the package.json file is adjusted in accordance with the predefined configuration, which may involve bumping it to a prerelease, patch, minor, or major version.
- Subsequently, the project undergoes the building process.
- A package is published to GitHub Packages, containing the latest modifications.
- A commit is made, signifying the changes introduced in the package.
- A tag is created, reflecting the updated version number.
- Finally, a release is issued, accompanied by the appropriate version identification.

## Clients

28 changes: 21 additions & 7 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
@@ -24,7 +24,7 @@
"dist"
],
"dependencies": {
"@RHEcosystemAppEng/exhort-javascript-api": "^0.0.2-alpha.4",
"@RHEcosystemAppEng/exhort-javascript-api": "^0.0.2-ea.27",
"@xml-tools/ast": "^5.0.5",
"@xml-tools/parser": "^1.0.11",
"compare-versions": "^6.0.0-rc.1",
13 changes: 11 additions & 2 deletions src/aggregators.ts
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
* Licensed under the Apache-2.0 License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
'use strict';
import { IDependencyProvider } from './collector';
import { Vulnerability } from './vulnerability';

/* VulnerabilityAggregator */
@@ -14,11 +15,15 @@ interface VulnerabilityAggregator {
/* Noop Vulnerability aggregator class */
class NoopVulnerabilityAggregator implements VulnerabilityAggregator {
isNewVulnerability: boolean;
provider: IDependencyProvider;
constructor(provider: IDependencyProvider) {
this.provider = provider;
}

aggregate(newVulnerability: Vulnerability): Vulnerability {
// Make it a new vulnerability always and set ecosystem for vulnerability.
this.isNewVulnerability = true;
newVulnerability.ecosystem = newVulnerability.ref.split(':')[1].split('/')[0];
newVulnerability.provider = this.provider;

return newVulnerability;
}
@@ -28,6 +33,10 @@ class NoopVulnerabilityAggregator implements VulnerabilityAggregator {
class MavenVulnerabilityAggregator implements VulnerabilityAggregator {
isNewVulnerability: boolean;
vulnerabilities: Map<string, Vulnerability> = new Map<string, Vulnerability>();
provider: IDependencyProvider;
constructor(provider: IDependencyProvider) {
this.provider = provider;
}

aggregate(newVulnerability: Vulnerability): Vulnerability {
// Make it a new vulnerability always and set ecosystem for vulnerability.
@@ -38,7 +47,7 @@ class MavenVulnerabilityAggregator implements VulnerabilityAggregator {
this.isNewVulnerability = false;
return v;
}
newVulnerability.ecosystem = 'maven';
newVulnerability.provider = this.provider;
this.vulnerabilities.set(key, newVulnerability);
return newVulnerability;
}
7 changes: 4 additions & 3 deletions src/collector.ts
Original file line number Diff line number Diff line change
@@ -84,8 +84,9 @@ export interface IHashableDependency extends IDependency {
key(): string;
}

/* Dependency collector interface */
export interface IDependencyCollector {
/* Ecosystem provider interface */
export interface IDependencyProvider {
ecosystem: string;
classes: Array<string>;
collect(contents: string): Promise<Array<IDependency>>;
}
@@ -113,7 +114,7 @@ export class Dependency implements IHashableDependency {
}

key(): string {
return `${this.name.value}`;
return `${this.name.value}${/^[~^]/.test(this.version.value) ? '' : `@${this.version.value}`}`;
}
}

2 changes: 2 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ class Config
utm_source: string;
mvn_executable: string;
npm_executable: string;
go_executable: string;
exhort_dev_mode: string;

constructor() {
@@ -27,6 +28,7 @@ class Config
this.utm_source = process.env.UTM_SOURCE || '';
this.mvn_executable = process.env.MVN_EXECUTABLE || 'mvn';
this.npm_executable = process.env.NPM_EXECUTABLE || 'npm';
this.go_executable = process.env.GO_EXECUTABLE || 'go';
this.exhort_dev_mode = process.env.EXHORT_DEV_MODE || 'false';
}
}
4 changes: 2 additions & 2 deletions src/consumers.ts
Original file line number Diff line number Diff line change
@@ -4,10 +4,10 @@
* ------------------------------------------------------------------------------------------ */
'use strict';
import { IDependency } from './collector';
import { get_range, VERSION_TEMPLATE } from './utils';
import { get_range } from './utils';
import { Vulnerability } from './vulnerability';
import { VulnerabilityAggregator } from './aggregators';
import { Diagnostic, CodeAction, CodeActionKind } from 'vscode-languageserver';
import { Diagnostic, CodeAction } from 'vscode-languageserver';

/* Descriptor describing what key-path to extract from the document */
interface IBindingDescriptor {
66 changes: 13 additions & 53 deletions src/collector/go.mod.ts → src/providers/go.mod.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
'use strict';

import { IKeyValueEntry, KeyValueEntry, Variant, ValueType, IDependency, IDependencyCollector, Dependency } from '../collector';
import { config } from '../config';
import { exec } from 'child_process';
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import { IKeyValueEntry, KeyValueEntry, Variant, ValueType, IDependency, IDependencyProvider, Dependency } from '../collector';

/* Please note :: There was issue with semverRegex usage in the code. During run time, it extracts
* version with 'v' prefix, but this is not be behavior of semver in CLI and test environment.
@@ -16,8 +12,8 @@ function semVerRegExp(line: string): RegExpExecArray {
}

class NaiveGomodParser {
constructor(contents: string, goImports: Set<string>) {
this.dependencies = NaiveGomodParser.parseDependencies(contents, goImports);
constructor(contents: string) {
this.dependencies = NaiveGomodParser.parseDependencies(contents);
}

dependencies: Array<IDependency>;
@@ -54,21 +50,22 @@ class NaiveGomodParser {
return replaceDependency;
}

static parseDependencies(contents:string, goImports: Set<string>): Array<IDependency> {
static parseDependencies(contents:string): Array<IDependency> {
let replaceMap = new Map<string, IDependency>();
let goModDeps = contents.split('\n').reduce((dependencies, line, index) => {

// skip any text after '//'
if (line.includes('//')) {
line = line.split('//')[0];
}

if (line.includes('=>')) {
let replaceEntry = NaiveGomodParser.getReplaceMap(line, index);
if (replaceEntry) {
replaceMap.set(replaceEntry.key, replaceEntry.value);
}
} else {
// Not using semver directly, look at comment on import statement.
//const version = semverRegex().exec(line)
const version = semVerRegExp(line);
// Skip lines without version string
if (version && version.length > 0) {
@@ -87,63 +84,26 @@ class NaiveGomodParser {
return dependencies;
}, []);

let goPackageDeps = [];

goImports.forEach(importStatement => {
let exactMatchDep: Dependency = null;
let moduleMatchDep: Dependency = null;
goModDeps.forEach(goModDep => {
if (importStatement === goModDep.name.value) {
// Software stack uses the module
exactMatchDep = goModDep;
} else if (importStatement.startsWith(goModDep.name.value + '/')) {
// Find longest module name that matches the import statement
if (moduleMatchDep === null) {
moduleMatchDep = goModDep;
} else if (moduleMatchDep.name.value.length < goModDep.name.value.length) {
moduleMatchDep = goModDep;
}
}
});

if (exactMatchDep === null && moduleMatchDep !== null) {
// Software stack uses a package from the module
let replaceDependency = NaiveGomodParser.applyReplaceMap(moduleMatchDep, replaceMap);
if (replaceDependency !== moduleMatchDep) {
importStatement = importStatement.replace(moduleMatchDep.name.value, replaceDependency.name.value);
}
const entry: IKeyValueEntry = new KeyValueEntry(importStatement + '@' + replaceDependency.name.value, replaceDependency.name.position);
entry.value = new Variant(ValueType.String, replaceDependency.version.value);
entry.value_position = replaceDependency.version.position;
goPackageDeps.push(new Dependency(entry));
}
});

goModDeps = goModDeps.map(goModDep => NaiveGomodParser.applyReplaceMap(goModDep, replaceMap));

// Return modules present in go.mod and packages used in imports.
return [...goModDeps, ...goPackageDeps];
return [...goModDeps];
}

parse(): Array<IDependency> {
return this.dependencies;
}
}

/* Process entries found in the go.mod file and collect all dependency
* related information */
export class DependencyCollector implements IDependencyCollector {
constructor(private manifestFile: string, public classes: Array<string> = ['dependencies']) {
this.manifestFile = manifestFile;
/* Process entries found in the go.mod file */
export class DependencyProvider implements IDependencyProvider {
ecosystem: string;
constructor(public classes: Array<string> = ['dependencies']) {
this.ecosystem = 'golang';
}

async collect(contents: string): Promise<Array<IDependency>> {
let promiseExec = new Promise<Set<string>>((resolve, reject) => {
reject('Golang is not a supported packae manager');
});
const goImports: Set<string> = await promiseExec;
let parser = new NaiveGomodParser(contents, goImports);
let parser = new NaiveGomodParser(contents);
return parser.parse();
}

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
'use strict';

import jsonAst from 'json-to-ast';
import { IKeyValueEntry, KeyValueEntry, Variant, ValueType, IDependency, IDependencyCollector, Dependency } from '../collector';
import { IKeyValueEntry, KeyValueEntry, Variant, ValueType, IDependency, IDependencyProvider, Dependency } from '../collector';

export class DependencyCollector implements IDependencyCollector {
constructor(public classes: Array<string> = ['dependencies']) {}
export class DependencyProvider implements IDependencyProvider {
ecosystem: string;
constructor(public classes: Array<string> = ['dependencies']) {
this.ecosystem = 'npm';
}

async collect(contents: string): Promise<Array<IDependency>> {
let ast: any;
10 changes: 6 additions & 4 deletions src/collector/pom.xml.ts → src/providers/pom.xml.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
'use strict';
import { IKeyValueEntry, KeyValueEntry, Variant, ValueType, IDependency, IDependencyCollector, Dependency } from '../collector';
import { IKeyValueEntry, KeyValueEntry, Variant, ValueType, IDependency, IDependencyProvider, Dependency } from '../collector';
import { parse, DocumentCstNode } from '@xml-tools/parser';
import { buildAst, accept, XMLElement, XMLDocument } from '@xml-tools/ast';
import { VERSION_TEMPLATE } from '../utils';

export class DependencyCollector implements IDependencyCollector {
export class DependencyProvider implements IDependencyProvider {
private xmlDocAst: XMLDocument;
private originalDeps: Array<XMLElement>;
ecosystem: string;

constructor(originalContents: string, enforceVersions: boolean, public classes: Array<string> = ['dependencies']) {
this.ecosystem = 'maven';
const { cst, tokenVector } = parse(originalContents);
const originalXmlDocAst = buildAst(cst as DocumentCstNode, tokenVector);
if (originalXmlDocAst.rootElement) {
@@ -62,7 +64,7 @@ export class DependencyCollector implements IDependencyCollector {

const toDependency = (resolved: PomDependency, original: PomDependency): Dependency => {
const dep: IKeyValueEntry = new KeyValueEntry(
`${original.groupId.textContents[0].text}:${original.artifactId.textContents[0].text}`,
`${original.groupId.textContents[0].text}/${original.artifactId.textContents[0].text}`,
{ line: original.element.position.startLine, column: original.element.position.startColumn }
);
dep.context_range = {
@@ -101,7 +103,7 @@ export class DependencyCollector implements IDependencyCollector {
};

const getMapKey = (element: PomDependency): string => {
return `${element.groupId.textContents[0].text}:${element.artifactId.textContents[0].text}`;
return `${element.groupId.textContents[0].text}/${element.artifactId.textContents[0].text}`;
};

const buildDependencyMap = (original: Array<PomDependency>, resolved: Array<PomDependency>): Array<PomDependency> => {
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

import { IKeyValueEntry, KeyValueEntry, Variant, ValueType, IDependency, IDependencyCollector, Dependency } from '../collector';
import { IKeyValueEntry, KeyValueEntry, Variant, ValueType, IDependency, IDependencyProvider, Dependency } from '../collector';

class NaivePyParser {
constructor(contents: string) {
@@ -37,12 +37,14 @@ class NaivePyParser {

/* Process entries found in the txt files and collect all dependency
* related information */
export class DependencyCollector implements IDependencyCollector {
constructor(public classes: Array<string> = ['dependencies']) {}
export class DependencyProvider implements IDependencyProvider {
ecosystem: string;
constructor(public classes: Array<string> = ['dependencies']) {
this.ecosystem = 'python';
}

async collect(contents: string): Promise<Array<IDependency>> {
let parser = new NaivePyParser(contents);
return parser.parse();
}

}
Loading