From 20c9f79ec76cba14b20e8c4b8ec254e855da7e93 Mon Sep 17 00:00:00 2001 From: Artem Date: Fri, 4 Sep 2026 18:12:57 +0200 Subject: [PATCH 1/2] Find main classes in projects whose sources are linked in resolveMainClassUnderPaths keeps a project only when its own location is inside the requested folder. A project can instead keep its description outside the folder and reach the sources through linked folders, and then it is dropped before the search runs: the folder appears to contain no main class, so the Run and Debug view generates no configuration and the Spring Boot Dashboard cannot resolve an application's main class. The accept-match branch already makes an exception for the invisible project, which is one instance of this shape (a single link named after ProjectUtils.WORKSPACE_LINK); the scope filter it depends on does not, so that exception can never fire. Check the locations of the project's source folders in both places instead. --- .../internal/ResolveMainClassHandler.java | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java index af9cdbc5..8a99ec25 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java @@ -31,8 +31,10 @@ import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; +import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.JavaCore; @@ -106,7 +108,10 @@ private List resolveMainClassUnderPaths(List parentPaths) projects = ProjectUtils.getJavaProjects(); } else { projects = Stream.of(ProjectUtils.getAllProjects()) - .filter(p -> ProjectUtils.isJavaProject(p) && p.getLocation() != null && ResourceUtils.isContainedIn(p.getLocation(), parentPaths)) + .filter(p -> ProjectUtils.isJavaProject(p) && p.getLocation() != null + && (ResourceUtils.isContainedIn(p.getLocation(), parentPaths) + || isContainedInInvisibleProject(p, parentPaths) + || hasSourceFolderContainedIn(p, parentPaths))) .map(p -> JavaCore.create(p)) .filter(p -> p.exists()) .toArray(IJavaProject[]::new); @@ -137,7 +142,8 @@ public void acceptSearchMatch(SearchMatch match) { String projectName = ProjectsManager.DEFAULT_PROJECT_NAME.equals(project.getName()) ? null : project.getName(); if (parentPaths.isEmpty() || ResourceUtils.isContainedIn(project.getLocation(), parentPaths) - || isContainedInInvisibleProject(project, parentPaths)) { + || isContainedInInvisibleProject(project, parentPaths) + || hasSourceFolderContainedIn(project, parentPaths)) { String filePath = null; if (match.getResource() instanceof IFile) { @@ -253,6 +259,41 @@ private boolean isMainMethod(IMethod method) { return false; } + /** + * A project can keep its description outside of the workspace folders and reach the sources + * through linked folders. The invisible project does it with a single link named after + * {@link ProjectUtils#WORKSPACE_LINK}, which the check below covers; project importers + * contributed by other extensions do it with one link per source root, which it does not. Such a + * project belongs to the folder its sources are in rather than to the folder its description + * happens to live in, so the source folders are checked as well - otherwise the project is + * dropped before the search runs and the folder appears to have no main class in it. + */ + private boolean hasSourceFolderContainedIn(IProject project, Collection rootPaths) { + IJavaProject javaProject = JdtUtils.getJavaProject(project); + if (javaProject == null) { + return false; + } + + try { + for (IClasspathEntry entry : javaProject.getRawClasspath()) { + if (entry.getEntryKind() != IClasspathEntry.CPE_SOURCE) { + continue; + } + + IResource sourceFolder = ResourcesPlugin.getWorkspace().getRoot().findMember(entry.getPath()); + if (sourceFolder != null && sourceFolder.getLocation() != null + && ResourceUtils.isContainedIn(sourceFolder.getLocation(), rootPaths)) { + return true; + } + } + } catch (JavaModelException e) { + logger.log(Level.WARNING, String.format("Failed to read the classpath of project %s: %s", + project.getName(), e.toString()), e); + } + + return false; + } + private boolean isContainedInInvisibleProject(IProject project, Collection rootPaths) { if (project == null) { return false; From 930f333131d5573e4a3b09adab1cc66ecefd400a Mon Sep 17 00:00:00 2001 From: Artem Date: Thu, 17 Sep 2026 12:40:19 +0200 Subject: [PATCH 2/2] Answer a folder with the matches that are inside it A project brought into the search by its source folders can link them in from more than one workspace folder, and accepting every match by the project alone then answers one folder with the main classes of another. Check where the match itself is as well. The project checks stay in front of it: a caller can ask with a project's own location rather than with a workspace folder, and for a project whose description lives outside of the workspace folders no match is contained in that location. --- .../plugin/internal/ResolveMainClassHandler.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java index 8a99ec25..cae936f1 100644 --- a/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java +++ b/com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java @@ -143,7 +143,7 @@ public void acceptSearchMatch(SearchMatch match) { if (parentPaths.isEmpty() || ResourceUtils.isContainedIn(project.getLocation(), parentPaths) || isContainedInInvisibleProject(project, parentPaths) - || hasSourceFolderContainedIn(project, parentPaths)) { + || isMatchContainedIn(match, parentPaths)) { String filePath = null; if (match.getResource() instanceof IFile) { @@ -259,6 +259,20 @@ private boolean isMainMethod(IMethod method) { return false; } + /** + * Where the match itself is, for the projects the source folder check brings into the search: + * their source folders can be linked in from more than one workspace folder, and a folder is + * only asking about the main classes that are in it. The project checks stay in front of this + * one - a caller can ask with a project's own location rather than with a workspace folder, and + * for a project whose description lives outside of the workspace folders no match would be + * contained in that location. A match whose resource has no location falls back to them as well, + * since {@link ResourceUtils#isContainedIn} answers false for a null. + */ + private boolean isMatchContainedIn(SearchMatch match, Collection rootPaths) { + IResource resource = match.getResource(); + return resource != null && ResourceUtils.isContainedIn(resource.getLocation(), rootPaths); + } + /** * A project can keep its description outside of the workspace folders and reach the sources * through linked folders. The invisible project does it with a single link named after