diff --git a/package-lock.json b/package-lock.json index ae6360e0..4ed73bab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.9.4-ea.14", "license": "Apache-2.0", "dependencies": { - "@RHEcosystemAppEng/exhort-javascript-api": "^0.1.1-ea.31", + "@RHEcosystemAppEng/exhort-javascript-api": "^0.1.1-ea.34", "@xml-tools/ast": "^5.0.5", "@xml-tools/parser": "^1.0.11", "json-to-ast": "^2.1.0", @@ -837,9 +837,9 @@ } }, "node_modules/@RHEcosystemAppEng/exhort-javascript-api": { - "version": "0.1.1-ea.31", - "resolved": "https://npm.pkg.github.com/download/@RHEcosystemAppEng/exhort-javascript-api/0.1.1-ea.31/73f5b94a40d1cd7b0ebdc6610df955973c68fa44", - "integrity": "sha512-d4kgggn8GFW8fy5195KEo5acthF+ONI4csDsaySaWJe9GwHEIrkx9rcYjoc2LzP35VQa9KBrbjazJAL99xeP1g==", + "version": "0.1.1-ea.34", + "resolved": "https://npm.pkg.github.com/download/@RHEcosystemAppEng/exhort-javascript-api/0.1.1-ea.34/51dc7d98877e2f707a4fe1aae177e5ac54932882", + "integrity": "sha512-kux7STDPWuGQHZN9/I7Jm5IBlS+lrWatCjQR8Udh2lVwOU5uJBcgB8oKvzX1XfkTznQPHikRq3fPaDpy4neLjg==", "license": "Apache-2.0", "dependencies": { "@babel/core": "^7.23.2", diff --git a/package.json b/package.json index 29db1be0..efca31b7 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "dist" ], "dependencies": { - "@RHEcosystemAppEng/exhort-javascript-api": "^0.1.1-ea.31", + "@RHEcosystemAppEng/exhort-javascript-api": "^0.1.1-ea.34", "@xml-tools/ast": "^5.0.5", "@xml-tools/parser": "^1.0.11", "json-to-ast": "^2.1.0", diff --git a/src/constants.ts b/src/constants.ts index f621e039..8a490a85 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -16,3 +16,21 @@ export const RHDA_DIAGNOSTIC_SOURCE = 'Red Hat Dependency Analytics Plugin'; * Placeholder used as a version for dependency templates. */ export const VERSION_PLACEHOLDER: string = '__VERSION__'; +/** + * Represents provider ecosystem names. + */ +export const GRADLE = 'gradle'; +export const MAVEN = 'maven'; +export const GOLANG = 'golang'; +export const NPM = 'npm'; +export const PYPI = 'pypi'; +/** + * An object mapping ecosystem names to their true ecosystems. + */ +export const ecosystemNameMappings: { [key: string]: string } = { + [GRADLE]: MAVEN, + [MAVEN]: MAVEN, + [GOLANG]: GOLANG, + [NPM]: NPM, + [PYPI]: PYPI, +}; \ No newline at end of file diff --git a/src/dependencyAnalysis/collector.ts b/src/dependencyAnalysis/collector.ts index 8c69cdc8..baf288c5 100644 --- a/src/dependencyAnalysis/collector.ts +++ b/src/dependencyAnalysis/collector.ts @@ -8,6 +8,7 @@ import { Range } from 'vscode-languageserver'; import { IPositionedString, IPositionedContext, IPosition } from '../positionTypes'; import { isDefined } from '../utils'; +import { ecosystemNameMappings, GRADLE } from '../constants'; /** * Represents a dependency specification. @@ -35,8 +36,11 @@ export class Dependency implements IDependency { */ export class DependencyMap { mapper: Map; - constructor(deps: IDependency[]) { - this.mapper = new Map(deps.map(d => [d.name.value, d])); + constructor(deps: IDependency[], ecosystem: string) { + this.mapper = new Map(deps.map(d => { + const key = ecosystem === GRADLE && d.version ? `${d.name.value}@${d.version.value}` : d.name.value; + return [key, d]; + })); } /** @@ -67,6 +71,12 @@ export interface IDependencyProvider { * @returns The resolved name of the dependency. */ resolveDependencyFromReference(ref: string): string; + + /** + * Gets the name of the providers ecosystem. + * @returns The name of the providers ecosystem. + */ + getEcosystem(): string; } /** @@ -85,7 +95,15 @@ export class EcosystemDependencyResolver { * @returns The resolved name of the dependency. */ resolveDependencyFromReference(ref: string): string { - return ref.replace(`pkg:${this.ecosystem}/`, ''); + return ref.replace(`pkg:${ecosystemNameMappings[this.ecosystem]}/`, ''); + } + + /** + * Gets the name of the ecosystem this provider is configured for. + * @returns The name of the ecosystem. + */ + getEcosystem(): string { + return this.ecosystem; } } diff --git a/src/dependencyAnalysis/diagnostics.ts b/src/dependencyAnalysis/diagnostics.ts index 2f72ae96..0a60982d 100644 --- a/src/dependencyAnalysis/diagnostics.ts +++ b/src/dependencyAnalysis/diagnostics.ts @@ -11,7 +11,7 @@ import { IPositionedContext } from '../positionTypes'; import { executeComponentAnalysis, DependencyData } from './analysis'; import { Vulnerability } from '../vulnerability'; import { connection } from '../server'; -import { VERSION_PLACEHOLDER } from '../constants'; +import { VERSION_PLACEHOLDER, GRADLE } from '../constants'; import { clearCodeActionsMap, registerCodeAction, generateSwitchToRecommendedVersionAction } from '../codeActionHandler'; import { decodeUriPath } from '../utils'; import { AbstractDiagnosticsPipeline } from '../diagnosticsPipeline'; @@ -37,10 +37,11 @@ class DiagnosticsPipeline extends AbstractDiagnosticsPipeline { /** * Runs diagnostics on dependencies. * @param dependencies - A map containing dependency data by reference string. + * @param ecosystem - The name of the ecosystem in which dependencies are being analyzed. */ - runDiagnostics(dependencies: Map) { + runDiagnostics(dependencies: Map, ecosystem: string) { Object.entries(dependencies).map(([ref, dependencyData]) => { - const dependencyRef = ref.split('@')[0]; + const dependencyRef = ecosystem === GRADLE ? ref : ref.split('@')[0]; const dependency = this.dependencyMap.get(dependencyRef); if (dependency) { @@ -101,7 +102,8 @@ class DiagnosticsPipeline extends AbstractDiagnosticsPipeline { async function performDiagnostics(diagnosticFilePath: string, contents: string, provider: IDependencyProvider) { try { const dependencies = await provider.collect(contents); - const dependencyMap = new DependencyMap(dependencies); + const ecosystem = provider.getEcosystem(); + const dependencyMap = new DependencyMap(dependencies, ecosystem); const diagnosticsPipeline = new DiagnosticsPipeline(dependencyMap, diagnosticFilePath); diagnosticsPipeline.clearDiagnostics(); @@ -110,7 +112,7 @@ async function performDiagnostics(diagnosticFilePath: string, contents: string, clearCodeActionsMap(diagnosticFilePath); - diagnosticsPipeline.runDiagnostics(response.dependencies); + diagnosticsPipeline.runDiagnostics(response.dependencies, ecosystem); diagnosticsPipeline.reportDiagnostics(); } catch (error) { diff --git a/src/diagnosticsPipeline.ts b/src/diagnosticsPipeline.ts index e72f8273..bbb945d6 100644 --- a/src/diagnosticsPipeline.ts +++ b/src/diagnosticsPipeline.ts @@ -25,8 +25,9 @@ interface IDiagnosticsPipeline { /** * Runs diagnostics on dependencies. * @param artifact - A map containing artifact data. + * @param ecosystem - The name of the ecosystem to analyze dependencies in. */ - runDiagnostics(artifact: Map); + runDiagnostics(artifact: Map, ecosystem: string); } /** @@ -66,7 +67,7 @@ abstract class AbstractDiagnosticsPipeline implements IDiagnosticsPipeline }); } - abstract runDiagnostics(artifact: Map): void; + abstract runDiagnostics(artifact: Map, ecosystem: string): void; } export { AbstractDiagnosticsPipeline }; \ No newline at end of file diff --git a/src/providers/build.gradle.ts b/src/providers/build.gradle.ts index 8d94e68c..fddce36f 100644 --- a/src/providers/build.gradle.ts +++ b/src/providers/build.gradle.ts @@ -4,7 +4,7 @@ * ------------------------------------------------------------------------------------------ */ 'use strict'; -import { VERSION_PLACEHOLDER } from '../constants'; +import { VERSION_PLACEHOLDER, GRADLE } from '../constants'; import { IDependencyProvider, EcosystemDependencyResolver, IDependency, Dependency } from '../dependencyAnalysis/collector'; /** @@ -20,14 +20,19 @@ export class DependencyProvider extends EcosystemDependencyResolver implements I COMMENT_REGEX: RegExp = /\/\*[\s\S]*?\*\//g; /** - * Regular expression for locating key value pairs in a string. + * Regular expression for locating key-value pairs in a string with colons as separators. */ - FIND_KEY_VALUE_PAIRS_REGEX: RegExp = /\b(\w+)\s*:\s*(['"])(.*?)\2/g; + FIND_KEY_VALUE_PAIRS_WITH_COLON_REGEX: RegExp = /\b(\w+)\s*:\s*(['"])(.*?)\2/g; + + /** + * Regular expression for locating key-value pairs in a string with equals signs as separators. + */ + FIND_KEY_VALUE_PAIRS_WITH_EQUALS_REGEX: RegExp = /\b(\w+)\s*=\s*(['"])(.*?)\2/; /** * Regular expression for matching key value pairs. */ - SPLIT_KEY_VALUE_PAIRS_REGEX: RegExp = /\s*:\s*/; + SPLIT_KEY_VALUE_PAIRS_WITH_COLON_REGEX: RegExp = /\s*:\s*/; /** * Regular expression for matching strings enclosed in double or single quotes. @@ -55,7 +60,7 @@ export class DependencyProvider extends EcosystemDependencyResolver implements I ARGS_SCOPE: string = 'ext'; constructor() { - super('maven'); // set gradle ecosystem to 'maven' + super(GRADLE); // set ecosystem to 'gradle' } /** @@ -92,11 +97,11 @@ export class DependencyProvider extends EcosystemDependencyResolver implements I let depData: string; let quoteUsed: string; - const keyValuePairs = cleanLine.match(this.FIND_KEY_VALUE_PAIRS_REGEX); + const keyValuePairs = cleanLine.match(this.FIND_KEY_VALUE_PAIRS_WITH_COLON_REGEX); if (keyValuePairs) { // extract data from dependency in Map format keyValuePairs.forEach(pair => { - const [key, value] = pair.split(this.SPLIT_KEY_VALUE_PAIRS_REGEX); + const [key, value] = pair.split(this.SPLIT_KEY_VALUE_PAIRS_WITH_COLON_REGEX); const match = value.match(this.BETWEEN_QUOTES_REGEX); quoteUsed = match[1]; const valueData = match[2]; @@ -222,7 +227,7 @@ export class DependencyProvider extends EcosystemDependencyResolver implements I } if (isSingleArgument) { - if (parsedLine.includes('{')) { + if (parsedLine.startsWith('{')) { isArgumentBlock = true; } isSingleArgument = false; @@ -240,14 +245,10 @@ export class DependencyProvider extends EcosystemDependencyResolver implements I if (parsedLine.includes('}')) { isArgumentBlock = false; } - - if (!this.BETWEEN_QUOTES_REGEX.test(parsedLine)) { - return dependencies; - } - if (parsedLine.includes('=')) { - const argData = parsedLine.split('='); - this.args.set(argData[0].trim(), argData[1].trim().replace(this.BETWEEN_QUOTES_REGEX, '$2')); + const argDataMatch = parsedLine.match(this.FIND_KEY_VALUE_PAIRS_WITH_EQUALS_REGEX); + if (argDataMatch) { + this.args.set(argDataMatch[1].trim(), argDataMatch[3].trim()); } } diff --git a/src/providers/go.mod.ts b/src/providers/go.mod.ts index a650611a..5ee0a32a 100644 --- a/src/providers/go.mod.ts +++ b/src/providers/go.mod.ts @@ -5,6 +5,7 @@ 'use strict'; import { IDependencyProvider, EcosystemDependencyResolver, IDependency, Dependency } from '../dependencyAnalysis/collector'; +import { GOLANG } from '../constants'; /* Please note :: There is an issue with the usage of semverRegex Node.js package in this code. * Often times it fails to recognize versions that contain an added suffix, usually including extra details such as a timestamp and a commit hash. @@ -27,7 +28,7 @@ export class DependencyProvider extends EcosystemDependencyResolver implements I replacementMap: Map = new Map(); constructor() { - super('golang'); // set ecosystem to 'golang' + super(GOLANG); // set ecosystem to 'golang' } /** diff --git a/src/providers/package.json.ts b/src/providers/package.json.ts index 155cf3bb..df97e4cd 100644 --- a/src/providers/package.json.ts +++ b/src/providers/package.json.ts @@ -6,6 +6,7 @@ import jsonAst from 'json-to-ast'; import { IDependencyProvider, EcosystemDependencyResolver, IDependency, Dependency } from '../dependencyAnalysis/collector'; +import { NPM } from '../constants'; /** * Process entries found in the package.json file. @@ -14,7 +15,7 @@ export class DependencyProvider extends EcosystemDependencyResolver implements I classes: string[] = ['dependencies']; constructor() { - super('npm'); // set ecosystem to 'npm' + super(NPM); // set ecosystem to 'npm' } /** diff --git a/src/providers/pom.xml.ts b/src/providers/pom.xml.ts index d8f0c2f0..71338d71 100644 --- a/src/providers/pom.xml.ts +++ b/src/providers/pom.xml.ts @@ -7,7 +7,7 @@ import { IDependencyProvider, EcosystemDependencyResolver, IDependency, Dependency } from '../dependencyAnalysis/collector'; import { parse, DocumentCstNode } from '@xml-tools/parser'; import { buildAst, accept, XMLElement, XMLDocument } from '@xml-tools/ast'; -import { VERSION_PLACEHOLDER } from '../constants'; +import { VERSION_PLACEHOLDER, MAVEN } from '../constants'; /** * Process entries found in the pom.xml file. @@ -15,7 +15,7 @@ import { VERSION_PLACEHOLDER } from '../constants'; export class DependencyProvider extends EcosystemDependencyResolver implements IDependencyProvider { constructor() { - super('maven'); // set ecosystem to 'maven' + super(MAVEN); // set ecosystem to 'maven' } /** diff --git a/src/providers/requirements.txt.ts b/src/providers/requirements.txt.ts index 96f5e475..9f6769d5 100644 --- a/src/providers/requirements.txt.ts +++ b/src/providers/requirements.txt.ts @@ -4,6 +4,7 @@ * ------------------------------------------------------------------------------------------ */ 'use strict'; +import { PYPI } from '../constants'; import { IDependencyProvider, EcosystemDependencyResolver, IDependency, Dependency } from '../dependencyAnalysis/collector'; /** @@ -12,7 +13,7 @@ import { IDependencyProvider, EcosystemDependencyResolver, IDependency, Dependen export class DependencyProvider extends EcosystemDependencyResolver implements IDependencyProvider { constructor() { - super('pypi'); // set ecosystem to 'pypi' + super(PYPI); // set ecosystem to 'pypi' } /** diff --git a/test/dependencyAnalysis/collector.test.ts b/test/dependencyAnalysis/collector.test.ts index 836d6a74..37afb546 100644 --- a/test/dependencyAnalysis/collector.test.ts +++ b/test/dependencyAnalysis/collector.test.ts @@ -3,7 +3,12 @@ import { expect } from 'chai'; import { Dependency, DependencyMap, getRange } from '../../src/dependencyAnalysis/collector'; -import { DependencyProvider } from '../../src/providers/pom.xml'; +import { DependencyProvider as PackageJson } from '../../src/providers/package.json'; +import { DependencyProvider as PomXml } from '../../src/providers/pom.xml'; +import { DependencyProvider as GoMod } from '../../src/providers/go.mod'; +import { DependencyProvider as RequirementsTxt } from '../../src/providers/requirements.txt'; +import { DependencyProvider as BuildGradle } from '../../src/providers/build.gradle'; +import * as constants from '../../src/constants'; describe('Dependency Analysis Collector tests', () => { @@ -15,7 +20,7 @@ describe('Dependency Analysis Collector tests', () => { it('should create map of dependecies', async () => { - const depMap = new DependencyMap(reqDeps); + const depMap = new DependencyMap(reqDeps, constants.MAVEN); expect(Object.fromEntries(depMap.mapper)).to.eql({ 'mockGroupId1/mockArtifact1': reqDeps[0], @@ -25,14 +30,14 @@ describe('Dependency Analysis Collector tests', () => { it('should create empty dependency map', async () => { - const depMap = new DependencyMap([]); + const depMap = new DependencyMap([], constants.MAVEN); expect(Object.keys(depMap.mapper).length).to.eql(0); }); it('should get dependency from dependency map', async () => { - const depMap = new DependencyMap(reqDeps); + const depMap = new DependencyMap(reqDeps, constants.MAVEN); expect(depMap.get(reqDeps[0].name.value)).to.eq(reqDeps[0]); expect(depMap.get(reqDeps[1].name.value)).to.eq(reqDeps[1]); @@ -57,11 +62,53 @@ describe('Dependency Analysis Collector tests', () => { expect(getRange(reqDeps[1])).to.eql(reqDeps[1].context.range); }); + it('should create map of dependecies for Gradle', async () => { + + const depMap = new DependencyMap(reqDeps, constants.GRADLE); + + expect(Object.fromEntries(depMap.mapper)).to.eql({ + 'mockGroupId1/mockArtifact1@mockVersion': reqDeps[0], + 'mockGroupId2/mockArtifact2': reqDeps[1] + }); + }); + it('should resolves a dependency reference in a specified ecosystem to its name and version string', async () => { - const mavenDependencyProvider = new DependencyProvider(); + const mavenDependencyProvider = new PomXml(); const resolvedRef = mavenDependencyProvider.resolveDependencyFromReference('pkg:maven/mockGroupId1/mockArtifact1@mockVersion1'); expect(resolvedRef).to.eq('mockGroupId1/mockArtifact1@mockVersion1'); }); + + it('should resolves a dependency reference in a specified ecosystem to its name and version string for Gradle', async () => { + const gradleDependencyProvider = new BuildGradle(); + + // Gradle references are marked as type 'maven' + const resolvedRef = gradleDependencyProvider.resolveDependencyFromReference('pkg:maven/mockGroupId1/mockArtifact1@mockVersion1'); + + expect(resolvedRef).to.eq('mockGroupId1/mockArtifact1@mockVersion1'); + }); + + it('should return the name of the providers ecosystem', async () => { + const npmDependencyProvider = new PackageJson(); + const mavenDependencyProvider = new PomXml(); + const golangDependencyProvider = new GoMod(); + const pythonDependencyProvider = new RequirementsTxt(); + const gradleDependencyProvider = new BuildGradle(); + + let ecosystem = npmDependencyProvider.getEcosystem(); + expect(ecosystem).to.eq(constants.NPM); + + ecosystem = mavenDependencyProvider.getEcosystem(); + expect(ecosystem).to.eq(constants.MAVEN); + + ecosystem = golangDependencyProvider.getEcosystem(); + expect(ecosystem).to.eq(constants.GOLANG); + + ecosystem = pythonDependencyProvider.getEcosystem(); + expect(ecosystem).to.eq(constants.PYPI); + + ecosystem = gradleDependencyProvider.getEcosystem(); + expect(ecosystem).to.eq(constants.GRADLE); + }); }) diff --git a/test/providers/build.gradle.test.ts b/test/providers/build.gradle.test.ts new file mode 100644 index 00000000..f913a16b --- /dev/null +++ b/test/providers/build.gradle.test.ts @@ -0,0 +1,347 @@ +'use strict'; + +import { expect } from 'chai'; + +import { DependencyProvider } from '../../src/providers/build.gradle'; + +describe('Gradle build.gradle parser tests', () => { + let dependencyProvider: DependencyProvider; + + beforeEach(() => { + dependencyProvider = new DependencyProvider(); + }); + + it('tests empty build.gradle', async () => { + const deps = await dependencyProvider.collect(``); + expect(deps).is.eql([]); + }); + + it('tests build.gradle with string notation dependencies', async () => { + const deps = await dependencyProvider.collect(` +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation "log4j:log4j:1.2.3" + implementation 'log4j:log4j:1.2.3' +} + `); + expect(deps).is.eql([ + { + name: {value: 'log4j/log4j', position: {line: 0, column: 0}}, + version: {value: '1.2.3', position: {line: 11, column: 33}} + }, + { + name: {value: 'log4j/log4j', position: {line: 0, column: 0}}, + version: {value: '1.2.3', position: {line: 12, column: 33}} + } + ]); + }); + + it('tests build.gradle with map notation dependencies', async () => { + const deps = await dependencyProvider.collect(` +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation group: "log4j", name: "log4j", version: "1.2.3" + implementation version: '1.2.3', name: 'log4j', group: 'log4j' +} + `); + expect(deps).is.eql([ + { + name: {value: 'log4j/log4j', position: {line: 0, column: 0}}, + version: {value: '1.2.3', position: {line: 11, column: 61}} + }, + { + name: {value: 'log4j/log4j', position: {line: 0, column: 0}}, + version: {value: '1.2.3', position: {line: 12, column: 30}} + } + ]); + }); + + it('tests build.gradle with comments', async () => { + const deps = await dependencyProvider.collect(` +plugins { + id 'java' +} + +repositories { + mavenCentral() +} +// a comment +dependencies { // a second comment + // implementation group: "log4j", name: "log4j", version: "1.2.3" + implementation version: "1.2.3", /* another comment */ name: "log4j", group: "log4j" // yet another comment + /* last comment */ +} + `); + expect(deps).is.eql([ + { + name: {value: 'log4j/log4j', position: {line: 0, column: 0}}, + version: {value: '1.2.3', position: {line: 12, column: 30}} + } + ]); + }); + + it('tests build.gradle with empty lines', async () => { + const deps = await dependencyProvider.collect(` +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + + +dependencies { + + + + implementation "log4j:log4j:1.2.3" + + +} + `); + expect(deps).is.eql([ + { + name: {value: 'log4j/log4j', position: {line: 0, column: 0}}, + version: {value: '1.2.3', position: {line: 15, column: 33}} + } + ]); + }); + + it('tests build.gradle with spaces before and after package parameters', async () => { + const deps = await dependencyProvider.collect(` +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation "log4j:log4j:1.2.3" + implementation group: "log4j", name: "log4j", version:"1.2.3" +} + `); + expect(deps).is.eql([ + { + name: {value: 'log4j/log4j', position: {line: 0, column: 0}}, + version: {value: '1.2.3', position: {line: 11, column: 43}} + }, + { + name: {value: 'log4j/log4j', position: {line: 0, column: 0}}, + version: {value: '1.2.3', position: {line: 12, column: 86}} + } + ]); + }); + + it('tests build.gradle with arguments', async () => { + const deps = await dependencyProvider.collect(` +plugins { + id 'java' +} + +ext mockArg = 'mock' + +ext { nameArg = "log4j" } + +ext { + mockArg = 'mock'} + +ext { + versionArg = '1.2.3' + groupArg = 'log4j' +} + +ext +{ mockArg = 'mock' } + +ext +{ + mockArg = 'mock' +} +repositories { + mavenCentral() +} + +dependencies { + implementation "\${groupArg}:\${nameArg}:\${versionArg}" + implementation group: "log4j", name: "\${nameArg}", version: "\${versionArg}" +} + `); + expect(deps).is.eql([ + { + name: {value: 'log4j/log4j', position: {line: 0, column: 0}}, + version: {value: '${versionArg}', position: {line: 30, column: 44}} + }, + { + name: {value: 'log4j/log4j', position: {line: 0, column: 0}}, + version: {value: '${versionArg}', position: {line: 31, column: 66}} + } + ]); + }); + + it('tests build.gradle with single line dependency declarations', async () => { + const deps = await dependencyProvider.collect(` +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { implementation "log4j:log4j:1.2.3" } +dependencies { implementation group: "log4j", name: "log4j", version: "1.2.3" } + + `); + expect(deps).is.eql([ + { + name: {value: 'log4j/log4j', position: {line: 0, column: 0}}, + version: {value: '1.2.3', position: {line: 10, column: 44}} + }, + { + name: {value: 'log4j/log4j', position: {line: 0, column: 0}}, + version: {value: '1.2.3', position: {line: 11, column: 72}} + } + ]); + }); + + it('tests build.gradle with muliple dependency declarations', async () => { + const deps = await dependencyProvider.collect(` +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies implementation "log4j:log4j:1.2.3" + +dependencies { implementation "log4j:log4j:1.2.3" } + +dependencies { + implementation 'log4j:log4j:1.2.3'} + +dependencies { + implementation group: "log4j", name: "log4j", version: "1.2.3" +} + +dependencies +{ implementation group: 'log4j', name: 'log4j', version: '1.2.3' } +dependencies +{ + implementation group: 'log4j', name: 'log4j', version: '1.2.3' +} + + `); + expect(deps).is.eql([ + { + name: {value: 'log4j/log4j', position: {line: 0, column: 0}}, + version: {value: '1.2.3', position: {line: 10, column: 42}} + }, + { + name: {value: 'log4j/log4j', position: {line: 0, column: 0}}, + version: {value: '1.2.3', position: {line: 12, column: 44}} + }, + { + name: {value: 'log4j/log4j', position: {line: 0, column: 0}}, + version: {value: '1.2.3', position: {line: 15, column: 33}} + }, + { + name: {value: 'log4j/log4j', position: {line: 0, column: 0}}, + version: {value: '1.2.3', position: {line: 18, column: 61}} + }, + { + name: {value: 'log4j/log4j', position: {line: 0, column: 0}}, + version: {value: '1.2.3', position: {line: 22, column: 59}} + }, + { + name: {value: 'log4j/log4j', position: {line: 0, column: 0}}, + version: {value: '1.2.3', position: {line: 25, column: 61}} + } + ]); + }); + + it('tests build.gradle with special dependency declarations', async () => { + const deps = await dependencyProvider.collect(` +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation('commons-codec:commons-codec') { + version { + strictly '1.9' + } + } + implementation('commons-beanutils:commons-beanutils:1.9.4') { + exclude group: 'commons-collections', module: 'commons-collections' + } + implementation project(":module_a") + api libs.io.quarkus.quarkus.vertx.http + implementation "log4j:log4j:1.2.3" +} + `); + expect(deps).is.eql([ + { + name: {value: 'commons-codec/commons-codec', position: {line: 0, column: 0}}, + context: {value: 'commons-codec:commons-codec:__VERSION__', range: {start: {line: 10, character: 20}, end: {line: 10, character: 47}}} + }, + { + name: {value: 'commons-beanutils/commons-beanutils', position: {line: 0, column: 0}}, + version: {value: '1.9.4', position: {line: 16, column: 57}} + }, + { + name: {value: 'log4j/log4j', position: {line: 0, column: 0}}, + version: {value: '1.2.3', position: {line: 21, column: 33}} + } + ]); + }); + + it('tests build.gradle dependencies with missing version parameter', async () => { + const deps = await dependencyProvider.collect(` +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation "log4j:log4j" + implementation group: "log4j", name: "log4j" +} + `); + expect(deps).is.eql([ + { + name: {value: 'log4j/log4j', position: {line: 0, column: 0}}, + context: {value: 'log4j:log4j:__VERSION__', range: {start: {line: 10, character: 20}, end: {line: 10, character: 31}}} + }, + { + name: {value: 'log4j/log4j', position: {line: 0, column: 0}}, + context: {value: 'name: "log4j", version: "__VERSION__"', range: {start: {line: 11, character: 35}, end: {line: 11, character: 48}}} + } + ]); + }); +});