Skip to content

Commit 37f6fe9

Browse files
author
Ye Zhu
committed
get the direct dependencies
1 parent e1d376c commit 37f6fe9

File tree

1 file changed

+161
-7
lines changed

1 file changed

+161
-7
lines changed

src/upgrade/assessmentManager.ts

Lines changed: 161 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT license.
33

4+
import * as fs from 'fs';
5+
import * as path from 'path';
46
import * as semver from 'semver';
7+
import { Uri } from 'vscode';
58
import { Jdtls } from "../java/jdtls";
69
import { NodeKind, type INodeData } from "../java/nodeData";
710
import { type DependencyCheckItem, type UpgradeIssue, type PackageDescription, UpgradeReason } from "./type";
@@ -145,7 +148,7 @@ async function getDependencyIssues(dependencies: PackageDescription[]): Promise<
145148

146149
async function getProjectIssues(projectNode: INodeData): Promise<UpgradeIssue[]> {
147150
const issues: UpgradeIssue[] = [];
148-
const dependencies = await getAllDependencies(projectNode);
151+
const dependencies = await getDirectDependencies(projectNode);
149152
issues.push(...await getCVEIssues(dependencies));
150153
issues.push(...getJavaIssues(projectNode));
151154
issues.push(...await getDependencyIssues(dependencies));
@@ -175,30 +178,181 @@ async function getWorkspaceIssues(workspaceFolderUri: string): Promise<UpgradeIs
175178
return workspaceIssues;
176179
}
177180

178-
async function getAllDependencies(projectNode: INodeData): Promise<PackageDescription[]> {
181+
const MAVEN_CONTAINER_PATH = "org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER";
182+
const GRADLE_CONTAINER_PATH = "org.eclipse.buildship.core.gradleclasspathcontainer";
183+
184+
/**
185+
* Parse direct dependencies from pom.xml file.
186+
* Also checks parent pom.xml for multi-module projects.
187+
*/
188+
function parseDirectDependenciesFromPom(pomPath: string): Set<string> {
189+
const directDeps = new Set<string>();
190+
try {
191+
const pomContent = fs.readFileSync(pomPath, 'utf-8');
192+
193+
// Extract dependencies from <dependencies> section (not inside <dependencyManagement>)
194+
// First, remove dependencyManagement sections to avoid including managed deps
195+
const withoutDepMgmt = pomContent.replace(/<dependencyManagement>[\s\S]*?<\/dependencyManagement>/g, '');
196+
197+
// Match <dependency> blocks and extract groupId and artifactId
198+
const dependencyRegex = /<dependency>\s*<groupId>([^<]+)<\/groupId>\s*<artifactId>([^<]+)<\/artifactId>/g;
199+
let match;
200+
while ((match = dependencyRegex.exec(withoutDepMgmt)) !== null) {
201+
const groupId = match[1].trim();
202+
const artifactId = match[2].trim();
203+
// Skip property references like ${project.groupId}
204+
if (!groupId.includes('${') && !artifactId.includes('${')) {
205+
directDeps.add(`${groupId}:${artifactId}`);
206+
}
207+
}
208+
209+
// Check for parent pom in multi-module projects
210+
const parentPomPath = path.join(path.dirname(pomPath), '..', 'pom.xml');
211+
if (fs.existsSync(parentPomPath)) {
212+
const parentDeps = parseDirectDependenciesFromPom(parentPomPath);
213+
parentDeps.forEach(dep => directDeps.add(dep));
214+
}
215+
} catch {
216+
// If we can't read the pom, return empty set
217+
}
218+
return directDeps;
219+
}
220+
221+
/**
222+
* Parse direct dependencies from build.gradle or build.gradle.kts file
223+
*/
224+
function parseDirectDependenciesFromGradle(gradlePath: string): Set<string> {
225+
const directDeps = new Set<string>();
226+
try {
227+
const gradleContent = fs.readFileSync(gradlePath, 'utf-8');
228+
229+
// Match common dependency configurations:
230+
// implementation 'group:artifact:version'
231+
// implementation "group:artifact:version"
232+
// api 'group:artifact:version'
233+
// compileOnly, runtimeOnly, testImplementation, etc.
234+
const shortFormRegex = /(?:implementation|api|compile|compileOnly|runtimeOnly|testImplementation|testCompileOnly|testRuntimeOnly)\s*\(?['"]([^:'"]+):([^:'"]+)(?::[^'"]*)?['"]\)?/g;
235+
let match;
236+
while ((match = shortFormRegex.exec(gradleContent)) !== null) {
237+
const groupId = match[1].trim();
238+
const artifactId = match[2].trim();
239+
if (!groupId.includes('$') && !artifactId.includes('$')) {
240+
directDeps.add(`${groupId}:${artifactId}`);
241+
}
242+
}
243+
244+
// Match map notation: implementation group: 'x', name: 'y', version: 'z'
245+
const mapFormRegex = /(?:implementation|api|compile|compileOnly|runtimeOnly|testImplementation|testCompileOnly|testRuntimeOnly)\s*\(?group:\s*['"]([^'"]+)['"]\s*,\s*name:\s*['"]([^'"]+)['"]/g;
246+
while ((match = mapFormRegex.exec(gradleContent)) !== null) {
247+
const groupId = match[1].trim();
248+
const artifactId = match[2].trim();
249+
if (!groupId.includes('$') && !artifactId.includes('$')) {
250+
directDeps.add(`${groupId}:${artifactId}`);
251+
}
252+
}
253+
} catch {
254+
// If we can't read the gradle file, return empty set
255+
}
256+
return directDeps;
257+
}
258+
259+
/**
260+
* Find the build file (pom.xml or build.gradle) for a project
261+
*/
262+
function findBuildFile(projectUri: string | undefined): { path: string; type: 'maven' | 'gradle' } | null {
263+
if (!projectUri) {
264+
return null;
265+
}
266+
try {
267+
const projectPath = Uri.parse(projectUri).fsPath;
268+
269+
// Check for Maven
270+
const pomPath = path.join(projectPath, 'pom.xml');
271+
if (fs.existsSync(pomPath)) {
272+
return { path: pomPath, type: 'maven' };
273+
}
274+
275+
// Check for Gradle Kotlin DSL
276+
const gradleKtsPath = path.join(projectPath, 'build.gradle.kts');
277+
if (fs.existsSync(gradleKtsPath)) {
278+
return { path: gradleKtsPath, type: 'gradle' };
279+
}
280+
281+
// Check for Gradle Groovy DSL
282+
const gradlePath = path.join(projectPath, 'build.gradle');
283+
if (fs.existsSync(gradlePath)) {
284+
return { path: gradlePath, type: 'gradle' };
285+
}
286+
} catch {
287+
// Ignore errors
288+
}
289+
return null;
290+
}
291+
292+
/**
293+
* Parse direct dependencies from build file (Maven or Gradle)
294+
*/
295+
function parseDirectDependencies(buildFile: { path: string; type: 'maven' | 'gradle' }): Set<string> {
296+
if (buildFile.type === 'maven') {
297+
return parseDirectDependenciesFromPom(buildFile.path);
298+
} else {
299+
return parseDirectDependenciesFromGradle(buildFile.path);
300+
}
301+
}
302+
303+
async function getDirectDependencies(projectNode: INodeData): Promise<PackageDescription[]> {
179304
const projectStructureData = await Jdtls.getPackageData({ kind: NodeKind.Project, projectUri: projectNode.uri });
180-
const packageContainers = projectStructureData.filter(x => x.kind === NodeKind.Container);
305+
// Only include Maven or Gradle containers (not JRE or other containers)
306+
const dependencyContainers = projectStructureData.filter(x =>
307+
x.kind === NodeKind.Container &&
308+
(x.path?.startsWith(MAVEN_CONTAINER_PATH) || x.path?.startsWith(GRADLE_CONTAINER_PATH))
309+
);
310+
311+
// Get direct dependency identifiers from build file
312+
const buildFile = findBuildFile(projectNode.uri);
313+
const directDependencyIds = buildFile ? parseDirectDependencies(buildFile) : null;
181314

182315
const allPackages = await Promise.allSettled(
183-
packageContainers.map(async (packageContainer) => {
316+
dependencyContainers.map(async (packageContainer) => {
184317
const packageNodes = await Jdtls.getPackageData({
185318
kind: NodeKind.Container,
186319
projectUri: projectNode.uri,
187320
path: packageContainer.path,
188321
});
189-
return packageNodes.map(packageNodeToDescription).filter((x): x is PackageDescription => Boolean(x));
322+
return packageNodes
323+
.map(packageNodeToDescription)
324+
.filter((x): x is PackageDescription => Boolean(x));
190325
})
191326
);
192327

193328
const fulfilled = allPackages.filter((x): x is PromiseFulfilledResult<PackageDescription[]> => x.status === "fulfilled");
194329
const failedPackageCount = allPackages.length - fulfilled.length;
195330
if (failedPackageCount > 0) {
196331
sendInfo("", {
197-
operationName: "java.dependency.assessmentManager.getAllDependencies.rejected",
332+
operationName: "java.dependency.assessmentManager.getDirectDependencies.rejected",
198333
failedPackageCount: String(failedPackageCount),
199334
});
200335
}
201-
return fulfilled.map(x => x.value).flat();
336+
337+
let dependencies = fulfilled.map(x => x.value).flat();
338+
339+
// Filter to only direct dependencies if we have build file info
340+
if (directDependencyIds && directDependencyIds.size > 0) {
341+
dependencies = dependencies.filter(pkg =>
342+
directDependencyIds.has(`${pkg.groupId}:${pkg.artifactId}`)
343+
);
344+
}
345+
346+
// Deduplicate by GAV coordinates
347+
const seen = new Set<string>();
348+
return dependencies.filter(pkg => {
349+
const key = `${pkg.groupId}:${pkg.artifactId}:${pkg.version}`;
350+
if (seen.has(key)) {
351+
return false;
352+
}
353+
seen.add(key);
354+
return true;
355+
});
202356
}
203357

204358
async function getCVEIssues(dependencies: PackageDescription[]): Promise<UpgradeIssue[]> {

0 commit comments

Comments
 (0)