Skip to content
Merged
Show file tree
Hide file tree
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
Expand Up @@ -16,6 +16,7 @@
import java.util.Objects;

import org.eclipse.core.internal.utils.FileUtil;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
Expand Down Expand Up @@ -109,7 +110,9 @@ public void accept(ResourceVisitor visitor) {
visitor.visit((IFile) resource);
}
} else if (resource instanceof IFolder) {
if (shouldVisit((IFolder) resource)) {
if (shouldVisit((IFolder) resource)
&& (!containsSourceClasspathEntry((IFolder) resource)
|| hasVisibleNonJavaResources((IFolder) resource))) {
visitor.visit((IFolder) resource);
}
} else if (resource instanceof IJarEntryResource) {
Expand Down Expand Up @@ -152,4 +155,49 @@ private boolean shouldVisit(IResource resource) {

return JavaCore.create(resource) == null;
}

private boolean containsSourceClasspathEntry(IContainer container) {
try {
IJavaProject javaProject = JavaCore.create(container.getProject());
if (javaProject == null) {
return false;
}
IPath containerPath = container.getFullPath();
if (containerPath.equals(javaProject.getOutputLocation())) {
return false;
}
for (IClasspathEntry entry : javaProject.getRawClasspath()) {
if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE
&& containerPath.isPrefixOf(entry.getPath())) {
return true;
}
}
} catch (CoreException e) {
JdtlsExtActivator.logException("Failed to inspect Java source entries", e);
}
return false;
}

private boolean hasVisibleNonJavaResources(IContainer container) {
try {
for (IResource member : container.members()) {
if (JavaCore.create(member) != null) {
continue;
}
if (member instanceof IFile) {
return true;
}
if (member instanceof IContainer) {
IContainer child = (IContainer) member;
if (!containsSourceClasspathEntry(child) || hasVisibleNonJavaResources(child)) {
return true;
}
}
}
} catch (CoreException e) {
JdtlsExtActivator.logException("Failed to inspect non-Java resources", e);
return true;
}
return false;
}
}
41 changes: 41 additions & 0 deletions test/e2e-plans/java-dep-view-modes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -222,3 +222,44 @@ steps:
exact: true
inView: "Java Projects"
timeout: 30

# ── Test 6: do not show empty physical ancestors of package roots (#1062) ──
# src/main/java and src/main/resources are represented as Java package roots.
# The physical src → main hierarchy must not also appear as an empty normal
# folder tree when non-Java resources are shown.
- id: "reset-tree-for-non-java-ancestor-check"
action: "executeVSCodeCommand workbench.actions.treeView.javaProjectExplorer.collapseAll"

- id: "expand-project-for-non-java-ancestor-check"
action: "expandTreeItem my-app"
waitBefore: 2

- id: "verify-resources-root-visible"
action: "wait 1 seconds"
verifyTreeItem:
name: "src/main/resources"
exact: true
level: 2
inView: "Java Projects"
timeout: 15

- id: "expand-resources-root"
action: "expandTreeItem src/main/resources"

- id: "verify-resource-file-visible"
action: "wait 1 seconds"
verifyTreeItem:
name: "application.yml"
exact: true
inView: "Java Projects"
timeout: 15

- id: "verify-empty-physical-src-hidden"
action: "wait 1 seconds"
verifyTreeItem:
name: "src"
exact: true
level: 2
visible: false
inView: "Java Projects"
timeout: 15
57 changes: 56 additions & 1 deletion test/maven-suite/projectView.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import * as assert from "assert";
import * as vscode from "vscode";
import { Commands, ContainerNode, contextManager, DataNode, DependencyExplorer, FileNode,
import { Commands, ContainerNode, contextManager, DataNode, DependencyExplorer, FileNode, FolderNode,
INodeData, Jdtls, languageServerApiManager, NodeKind, PackageNode, PackageRootNode, PrimaryTypeNode, ProjectNode } from "../../extension.bundle";
import { fsPath, printNodes, setupTestEnv, Uris } from "../shared";

Expand Down Expand Up @@ -247,6 +247,61 @@ suite("Maven Project View Tests", () => {
assert.ok(!projectChildren.find((node: DataNode) => node.nodeData.name === ".hidden"));
});

test("Does not display empty physical ancestors of Java package roots", async function() {
const explorer = DependencyExplorer.getInstance(contextManager.context);
const projectNode = (await explorer.dataProvider.getChildren())![0] as ProjectNode;
const projectChildren = await projectNode.getChildren();

assert.ok(!projectChildren.find((node: DataNode) => node.nodeData.name === "src"),
"The physical src folder should not duplicate Java package roots");

const resourcesRoot = projectChildren.find((node: DataNode) =>
node.nodeData.name === "src/main/resources") as PackageRootNode;
assert.ok(resourcesRoot, "The Maven resources root should remain visible");
const resourceChildren = await resourcesRoot.getChildren();
assert.ok(resourceChildren.find((node: DataNode) => node.nodeData.name === "application.yml"),
"Non-Java files under the resources root should remain visible");
});

test("Displays empty non-Java folders next to Java package roots", async function() {
const explorer = DependencyExplorer.getInstance(contextManager.context);
const projectNode = (await explorer.dataProvider.getChildren())![0] as ProjectNode;
const initialChildren = await projectNode.getChildren();
const mainSourceRoot = initialChildren.find((node: DataNode) =>
node.nodeData.name === "src/main/java") as PackageRootNode;
const srcPath = mainSourceRoot.nodeData.path!.replace(/\/main\/java$/, "");
const docsUri = vscode.Uri.joinPath(vscode.Uri.file(Uris.MAVEN_PROJECT_NODE), "src", "docs");
await vscode.workspace.fs.createDirectory(docsUri);

try {
await vscode.commands.executeCommand(Commands.EXECUTE_WORKSPACE_COMMAND,
Commands.JAVA_GETPACKAGEDATA, {
kind: NodeKind.Folder,
projectUri: projectNode.uri,
path: srcPath,
});
await vscode.commands.executeCommand(Commands.VIEW_PACKAGE_REFRESH);
const refreshedProjectNode = (await explorer.dataProvider.getChildren())![0] as ProjectNode;
const projectChildren = await refreshedProjectNode.getChildren();
const srcFolder = projectChildren.find((node: DataNode) =>
node.nodeData.name === "src") as FolderNode;

assert.ok(srcFolder, "The physical src folder should remain visible when it contains an empty non-Java folder");
const srcChildren = await srcFolder.getChildren();
assert.ok(srcChildren.find((node: DataNode) => node.nodeData.name === "docs"),
"The empty non-Java folder should remain visible");
} finally {
await vscode.workspace.fs.delete(docsUri, { recursive: true });
await vscode.commands.executeCommand(Commands.EXECUTE_WORKSPACE_COMMAND,
Commands.JAVA_GETPACKAGEDATA, {
kind: NodeKind.Folder,
projectUri: projectNode.uri,
path: srcPath,
});
await vscode.commands.executeCommand(Commands.VIEW_PACKAGE_REFRESH);
}
});

test("Can apply 'java.project.explorer.showNonJavaResources'", async function() {
await vscode.workspace.getConfiguration("java.project.explorer").update(
"showNonJavaResources",
Expand Down
3 changes: 3 additions & 0 deletions test/maven/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
spring:
application:
name: my-app
7 changes: 5 additions & 2 deletions test/simple-suite/projectView.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT license.

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

Expand All @@ -22,7 +22,10 @@ suite("Simple Project View Tests", () => {

// validate package root/dependency nodes
const projectChildren = await projectNode.getChildren();
assert.equal(projectChildren.length, 6, "Number of children nodes should be 6");
assert.equal(projectChildren.length, 5,
`Number of children nodes should be 5: ${projectChildren.map((node: DataNode) => node.name).join(", ")}`);
assert.ok(!projectChildren.find((node: DataNode) => node.name === "src"),
"The empty physical source folder should not be visible");
const mainPackage = projectChildren[0] as PackageRootNode;
assert.equal(mainPackage.name, "src/main/java", "Package name should be \"src/main/java\"");
const systemLibrary = projectChildren[1] as ContainerNode;
Expand Down
Loading