Skip to content

Commit

Permalink
ux - display maven and gradle dependencies with pattern 'groupId:arti…
Browse files Browse the repository at this point in the history
…factId:version ' (#859)
  • Loading branch information
mamilic authored Oct 31, 2024
1 parent 6ee1c6b commit b839f1e
Show file tree
Hide file tree
Showing 13 changed files with 133 additions and 21 deletions.
1 change: 1 addition & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
{
"type": "java",
"name": "Attach to Plugin",
"projectName": "com.microsoft.jdtls.ext.core",
"request": "attach",
"hostName": "localhost",
"port": 1044
Expand Down
37 changes: 37 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# How to Contribute

We greatly appreciate contributions to the vscode-java-dependency project. Your efforts help us maintain and improve this extension. To ensure a smooth contribution process, please follow these guidelines.

## Prerequisites
- [JDK](https://www.oracle.com/java/technologies/downloads/?er=221886)
- [Node.JS](https://nodejs.org/en/)
- [VSCode](https://code.visualstudio.com/)

## Build and Run

To set up the vscode-java-dependency project, follow these steps:

1. **Build the Server JAR**:
- The server JAR (Java application) is located in the [jdtls.ext](./jdtls.ext) directory.
- Run the following command to build the server:
```shell
npm run build-server
```

2. **Install Dependencies**:
- Execute the following command to install the necessary dependencies:
```shell
npm install
```

3. **Run/Debug the Extension**:
- Open the "Run and Debug" view in Visual Studio Code.
- Run the "Run Extension" task.

4. **Attach to Plugin[Debug Java]**:
- Prerequisite: Ensure that the extension is activated, meaning the Java process is already launched. This is required for the task to run properly.
- Open the "Run and Debug" view in Visual Studio Code.
- Run the "Attach to Plugin" task.
- Note: This task is required only if you want to debug Java code [jdtls.ext](./jdtls.ext). It requires the [vscode-pde](https://marketplace.visualstudio.com/items?itemName=yaozheng.vscode-pde) extension to be installed.

Thank you for your contributions and support!
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
Expand Down Expand Up @@ -78,7 +79,7 @@ public class PackageCommand {
private static final Map<NodeKind, BiFunction<PackageParams, IProgressMonitor, List<PackageNode>>> commands;

static {
commands = new HashMap<>();
commands = new EnumMap<>(NodeKind.class);
commands.put(NodeKind.PROJECT, PackageCommand::getProjectChildren);
commands.put(NodeKind.CONTAINER, PackageCommand::getContainerChildren);
commands.put(NodeKind.PACKAGEROOT, PackageCommand::getPackageRootChildren);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Map;
import java.util.Objects;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
Expand Down Expand Up @@ -273,11 +274,32 @@ public static PackageRootNode createNodeForPackageFragmentRoot(IPackageFragmentR
for (IClasspathAttribute attribute : resolvedClasspathEntry.getExtraAttributes()) {
node.setMetaDataValue(attribute.getName(), attribute.getValue());
}

String computedDisplayName = computeDisplayName(node);
if (StringUtils.isNotBlank(computedDisplayName)) {
node.setDisplayName(computedDisplayName);
}
}

return node;
}

private static String computeDisplayName(PackageRootNode node) {
if (node.getMetaData() == null || node.getMetaData().isEmpty()) {
return node.getName();
}

String version = (String) node.getMetaData().get("maven.version");
String groupId = (String) node.getMetaData().get("maven.groupId");
String artifactId = (String) node.getMetaData().get("maven.artifactId");

if (StringUtils.isBlank(version) || StringUtils.isBlank(groupId) || StringUtils.isBlank(artifactId)) {
return node.getName();
}

return groupId + ":" + artifactId + ":" + version;
}

/**
* Get the correspond node of classpath, it may be container or a package root.
*
Expand Down
4 changes: 4 additions & 0 deletions src/views/PrimaryTypeNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ export class PrimaryTypeNode extends DataNode {
return "";
}

public getLabel(): string {
return this._nodeData.displayName ?? this._nodeData.name;
}

protected async loadData(): Promise<SymbolInformation[] | DocumentSymbol[] | undefined> {
if (!this.hasChildren() || !this.nodeData.uri) {
return undefined;
Expand Down
25 changes: 19 additions & 6 deletions src/views/containerNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,36 @@ export class ContainerNode extends DataNode {
super(nodeData, parent);
}

private _containerType: ContainerType;

public get projectBasePath() {
return this._project.uri && Uri.parse(this._project.uri).fsPath;
}

public getContainerType(): string {
public getContainerType(): ContainerType {
if (this._containerType) {
return this._containerType;
}

const containerPath: string = this._nodeData.path || "";
if (containerPath.startsWith(ContainerPath.JRE)) {
return ContainerType.JRE;
this._containerType = ContainerType.JRE;
} else if (containerPath.startsWith(ContainerPath.Maven)) {
return ContainerType.Maven;
this._containerType = ContainerType.Maven;
} else if (containerPath.startsWith(ContainerPath.Gradle)) {
return ContainerType.Gradle;
this._containerType = ContainerType.Gradle;
} else if (containerPath.startsWith(ContainerPath.ReferencedLibrary) && this._project.isUnmanagedFolder()) {
// currently, we only support editing referenced libraries in unmanaged folders
return ContainerType.ReferencedLibrary;
this._containerType = ContainerType.ReferencedLibrary;
} else {
this._containerType = ContainerType.Unknown;
}
return ContainerType.Unknown;

return this._containerType;
}

public isMavenType(): boolean {
return this._containerType === ContainerType.Maven;
}

protected async loadData(): Promise<INodeData[]> {
Expand Down
4 changes: 4 additions & 0 deletions src/views/dataNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ export abstract class DataNode extends ExplorerNode {
return item;
}

public getDisplayName(): string {
return this._nodeData.displayName || this._nodeData.name;
}

public get nodeData(): INodeData {
return this._nodeData;
}
Expand Down
10 changes: 9 additions & 1 deletion src/views/dependencyDataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
RelativePattern, TreeDataProvider, TreeItem, Uri, window, workspace,
} from "vscode";
import { instrumentOperationAsVsCodeCommand, sendError } from "vscode-extension-telemetry-wrapper";
import { contextManager } from "../../extension.bundle";
import { ContainerNode, contextManager } from "../../extension.bundle";
import { Commands } from "../commands";
import { Context } from "../constants";
import { appendOutput, executeExportJarTask } from "../tasks/buildArtifact/BuildArtifactTaskProvider";
Expand Down Expand Up @@ -124,6 +124,14 @@ export class DependencyDataProvider implements TreeDataProvider<ExplorerNode> {
const children = (!this._rootItems || !element) ?
await this.getRootNodes() : await element.getChildren();

if (children && element instanceof ContainerNode) {
if (element.isMavenType()) {
children.sort((a, b) => {
return a.getDisplayName().localeCompare(b.getDisplayName());
});
}
}

explorerNodeCache.saveNodes(children || []);
return children;
}
Expand Down
6 changes: 5 additions & 1 deletion src/views/documentSymbolNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export class DocumentSymbolNode extends ExplorerNode {
super(parent);
}

public getDisplayName(): string {
return this.symbolInfo.name;
}

public getChildren(): ExplorerNode[] | Promise<ExplorerNode[]> {
const res: ExplorerNode[] = [];
if (this.symbolInfo?.children?.length) {
Expand All @@ -39,7 +43,7 @@ export class DocumentSymbolNode extends ExplorerNode {
}

public getTreeItem(): TreeItem | Promise<TreeItem> {
const item = new TreeItem(this.symbolInfo.name,
const item = new TreeItem(this.getDisplayName(),
this.symbolInfo?.children?.length ? TreeItemCollapsibleState.Collapsed
: TreeItemCollapsibleState.None);
item.iconPath = this.iconPath;
Expand Down
2 changes: 2 additions & 0 deletions src/views/explorerNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ export abstract class ExplorerNode {
public abstract getTreeItem(): TreeItem | Promise<TreeItem>;

public abstract computeContextValue(): string | undefined;

public abstract getDisplayName(): string;
}
6 changes: 5 additions & 1 deletion test/gradle-suite/projectView.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@

import * as assert from "assert";
import { ContainerNode, contextManager, DataNode, DependencyExplorer,
languageServerApiManager,
PackageRootNode, PrimaryTypeNode, ProjectNode } from "../../extension.bundle";
import { fsPath, setupTestEnv, Uris } from "../shared";

// tslint:disable: only-arrow-functions
suite("Gradle Project View Tests", () => {

suiteSetup(setupTestEnv);
suiteSetup(async () => {
await setupTestEnv();
await languageServerApiManager.ready();
});

test("Can node render correctly", async function() {
const explorer = DependencyExplorer.getInstance(contextManager.context);
Expand Down
14 changes: 5 additions & 9 deletions test/invisible-suite/projectView.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,21 @@

import * as assert from "assert";
import * as fse from "fs-extra";
import { platform } from "os";
import * as path from "path";
import * as vscode from "vscode";
import { Commands, contextManager, DependencyExplorer, PackageNode, PackageRootNode, ProjectNode } from "../../extension.bundle";
import { Commands, contextManager, DependencyExplorer, languageServerApiManager, PackageNode, PackageRootNode, ProjectNode } from "../../extension.bundle";
import { setupTestEnv } from "../shared";
import { sleep } from "../util";

// tslint:disable: only-arrow-functions
suite("Invisible Project View Tests", () => {

suiteSetup(setupTestEnv);
suiteSetup(async () => {
await setupTestEnv();
await languageServerApiManager.ready();
});

test("Can execute command java.project.refreshLibraries correctly", async function() {
if (platform() === "darwin") {
this.skip();
}
const explorer = DependencyExplorer.getInstance(contextManager.context);

let projectNode = (await explorer.dataProvider.getChildren())![0] as ProjectNode;
Expand All @@ -37,9 +36,6 @@ suite("Invisible Project View Tests", () => {
});

test("Can execute command java.project.removeLibrary correctly", async function() {
if (platform() === "darwin") {
this.skip();
}
const explorer = DependencyExplorer.getInstance(contextManager.context);

let projectNode = (await explorer.dataProvider.getChildren())![0] as ProjectNode;
Expand Down
20 changes: 18 additions & 2 deletions test/maven-suite/projectView.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
import * as assert from "assert";
import * as vscode from "vscode";
import { Commands, ContainerNode, contextManager, DataNode, DependencyExplorer, FileNode,
INodeData, Jdtls, NodeKind, PackageNode, PackageRootNode, PrimaryTypeNode, ProjectNode } from "../../extension.bundle";
INodeData, Jdtls, languageServerApiManager, NodeKind, PackageNode, PackageRootNode, PrimaryTypeNode, ProjectNode } from "../../extension.bundle";
import { fsPath, printNodes, setupTestEnv, Uris } from "../shared";

// tslint:disable: only-arrow-functions
suite("Maven Project View Tests", () => {

suiteSetup(setupTestEnv);
suiteSetup(async () => {
await setupTestEnv();
await languageServerApiManager.ready();
});

test("Can node render correctly in hierarchical view", async function() {
await vscode.workspace.getConfiguration("java.dependency").update("packagePresentation", "hierarchical");
Expand Down Expand Up @@ -253,6 +256,19 @@ suite("Maven Project View Tests", () => {
assert.equal(projectChildren.length, 4);
});

test("Can maven dependency nodes display in correct groupId:artifactId:version format", async function() {
const explorer = DependencyExplorer.getInstance(contextManager.context);

const roots = await explorer.dataProvider.getChildren();
const projectNode = roots![0] as ProjectNode;
const projectChildren = await projectNode.getChildren();
const mavenDependency = projectChildren[3] as ContainerNode;
const mavenChildren = await mavenDependency.getChildren();

assert.equal(mavenChildren[0].getDisplayName(), "org.hamcrest:hamcrest-core:1.3");
assert.equal(mavenChildren[1].getDisplayName(), "junit:junit:4.13.1");
});

teardown(async () => {
// Restore default settings. Some tests might alter them and others depend on a specific setting.
// Not resetting to the default settings will also show the file as changed in the source control view.
Expand Down

0 comments on commit b839f1e

Please sign in to comment.