fix(java): explain recovery for unimported launch modules (#585)

Java launch preparation forwarded jdtls project-lookup failures unchanged, so a Maven module missing from the parent build or a wrongly inferred module showed a raw jdtls error in every Run and Debug entry point. Launch preparation now names the selected module and explains how to recover: wait for import, check the run configuration and the parent build, or open the folder as a project.

Covers the "does not belong to any Java project", "references non-existing project" and "not a Java project" responses. Cancellation, missing server, crash, timeout and build errors keep their existing handling. Import membership and module inference are unchanged.

Refs #197
This commit is contained in:
l0ng-ai authored and GitHub committed 2026-10-31 23:56:19 +08:00
1 parent 369c63979d
commit 9dc736de09
4 files changed
+92 -6

No files matched your search

+51 -4
View File
@@ -1,9 +1,10 @@
import { afterEach, beforeEach, expect, it, vi } from "vitest";
import type { RunConfig } from "@/platform/ipc";
import { findJavaModule } from "@/platform/javaLaunch";
import { IpcError, type RunConfig } from "@/platform/ipc";
import { findJavaModule, prepareJavaLaunch } from "@/platform/javaLaunch";
import { _resetI18nForTests, setLocale } from "@/platform/i18n";
import { provideNotificationService } from "@/workbench/contributions";
import { applicationConfig } from "./application";
import { resetRuns, saveUserConfigs, startRun } from "./store";
import { resetRuns, runtime, saveUserConfigs, startRun } from "./store";
import { applicationType } from "./jvmTypes";
import { withParams } from "./types";
@@ -63,7 +64,10 @@ beforeEach(() => {
vi.clearAllMocks();
provideNotificationService({ notify: vi.fn() });
});
afterEach(() => resetRuns());
afterEach(() => {
resetRuns();
_resetI18nForTests();
});
it.each(["sibling", "/outside/modules"])("runs from %s using the stored module's classpath", async (cwd) => {
const session = await startRun({ ...config, module: "app", cwd });
@@ -112,3 +116,46 @@ it("does not substitute the working directory when sources are missing or ambigu
});
expect(await findJavaModule(config)).toBeNull();
});
it.each([
"jdtls: Given URI does not belong to any Java project.",
"jdtls: Launch configuration 1793460461218 references non-existing project parent.",
])("explains how to import a module when Run fails with %s", async (message) => {
ipc.javaResolveLaunch.mockResolvedValueOnce({ status: "error", error: { kind: "protocol", message } } as never);
const session = await startRun({ ...config, module: "web" });
expect(session?.status).toBe("failed");
const output = runtime(session!.id)!
.console.lines.map((line) => line.text)
.join("\n");
expect(output).toContain("Java module 'web' is not imported.");
expect(output).toContain("parent Maven or Gradle build");
expect(output).toContain("open its folder as a project");
expect(ipc.runStart).not.toHaveBeenCalled();
});
it.each([
["unavailable", "jdtls is not installed (Settings › Languages & Frameworks › Language Servers)"],
["unavailable", "jdtls exited with code 1: wrong Java version"],
["protocol", "jdtls: workspace/executeCommand timed out after 90 s"],
["internal", "Build failed"],
["cancelled", "jdtls: Given URI does not belong to any Java project."],
] as const)("preserves other launch errors (%s: %s)", async (kind, message) => {
const error = new IpcError({ kind, message });
ipc.javaResolveLaunch.mockRejectedValueOnce(error);
await expect(
prepareJavaLaunch({ ...config, module: "web" }, { request: 1, signal: new AbortController().signal }),
).rejects.toBe(error);
});
it("translates import recovery guidance for a root Spring Boot configuration", async () => {
await setLocale("zh");
ipc.javaResolveLaunch.mockRejectedValueOnce(
new IpcError({ kind: "protocol", message: "jdtls: Given URI does not belong to any Java project." }),
);
await expect(
prepareJavaLaunch(
{ ...config, kind: "spring-boot", module: "" },
{ request: 1, signal: new AbortController().signal },
),
).rejects.toThrow("Java 模块 '.' 尚未导入");
});