fix(editor): describe a Java file outside every project accurately

jdtls still completes and navigates JDK classes in a non-project file such
as a scratch; it only skips semantic checks and project classes. The banner
said navigation and completion don't work, and in a Maven or Gradle project
it offered a build reload that can't bring a file outside the project in.

- The banner now says only syntax errors are reported and only JDK classes
  resolve.
- The Maven / Gradle reload is offered only for files inside the open
  project (or one of its workspace projects).
Цей коміт міститься в:
l0ng-ai committed 2026-11-04 21:09:12 +08:00
1 джерело 7fd330912a
коміт 52360c72d2
5 files changed
+75 -13

No files matched your search

+53 -1
Переглянути файл
@@ -1,4 +1,5 @@
import { expect, it } from "vitest";
import { afterEach, expect, it } from "vitest";
import { useProject } from "@/platform/project";
import { outsideProjectNotice } from "./outsideProject";
const text = (message: string) => outsideProjectNotice(message)?.text ?? null;
@@ -36,7 +37,22 @@ it("recognizes each server's report that a file is outside its projects", () =>
expect(outsideProjectNotice("No packages found for open file x.go.")?.reload).toBeFalsy();
});
afterEach(() => useProject.setState({ project: null }));
const openProject = (root: string, projects?: string[]) =>
useProject.setState({
project: {
root,
name: "p",
trusted: true,
isGit: false,
settings: {},
...(projects && { workspace: { version: 1, name: "p", projects } }),
},
});
it("shows a banner while the server reports the file, with the build reloads available", async () => {
openProject("/p");
const { renderHook, act } = await import("@testing-library/react");
const { setFileDiagnostics } = await import("./diagnostics");
const { registerActions } = await import("@/platform/actions");
@@ -64,3 +80,39 @@ it("shows a banner while the server reports the file, with the build reloads ava
setFileDiagnostics(path, "jdtls", []);
}
});
it("offers a build reload only for files of the open project", async () => {
const { renderHook, act } = await import("@testing-library/react");
const { setFileDiagnostics } = await import("./diagnostics");
const { registerActions } = await import("@/platform/actions");
const { outsideProjectBanner } = await import("./outsideProject");
const off = registerActions([{ id: "maven.reload", title: "Reload All Maven Projects", run: () => {} }]);
openProject("/ws", ["/p"]);
// A scratch lives in the app data dir; a module the import missed lives in the project.
const cases: [string, string[]][] = [
["/data/scratches/scratch.java", []],
["/p/tools/Gen.java", ["Reload Maven Projects"]],
["/ws/Gen.java", ["Reload Maven Projects"]],
];
try {
for (const [path, labels] of cases) {
const { result } = renderHook(() => outsideProjectBanner.useBanner(path));
const warning = {
range: { start: { line: 0, col: 0 }, end: { line: 0, col: 1 } },
severity: "warning" as const,
message: `${path.split("/").pop()} is a non-project file, only syntax errors are reported`,
source: "Java",
code: "32",
};
act(() => setFileDiagnostics(path, "jdtls", [warning]));
expect(result.current?.text, path).toMatch(/only JDK classes resolve/);
expect(
result.current?.actions?.map((a) => a.label),
path,
).toEqual(labels);
act(() => setFileDiagnostics(path, "jdtls", []));
}
} finally {
off();
}
});
+19 -9
Переглянути файл
@@ -6,10 +6,15 @@
*/
import { useSyncExternalStore } from "react";
import { executeAction, getAction, isActionEnabled } from "@/platform/actions";
import { isSameOrInside } from "@/platform/paths";
import { useProject } from "@/platform/project";
import type { EditorBannerContribution } from "@/workbench/contributions";
import { diagnosticsFor, subscribeDiagnostics } from "./diagnostics";
/** `reload`: a build reload may bring the file into a project (a module the import missed). */
/**
* `reload`: a build reload may bring the file into a project (a module the import missed); offered
* only for files inside the open project.
*/
const NOTICES: readonly { test: RegExp; text: string; reload?: boolean }[] = [
// jdtls: "Foo.java is not on the classpath of project app, only syntax errors are reported"
{
@@ -17,10 +22,11 @@ const NOTICES: readonly { test: RegExp; text: string; reload?: boolean }[] = [
text: "This file is outside the source folders of its Java module: only syntax errors are reported, and navigation and completion don't work.",
reload: true,
},
// jdtls: "Foo.java is a non-project file, only syntax errors are reported"
// jdtls: "Foo.java is a non-project file, only syntax errors are reported". jdtls still completes
// and navigates JDK classes in such a file (a scratch, a file opened from outside the project).
{
test: /is a non-project file, only syntax errors are reported/,
text: "This file does not belong to an imported Java project: only syntax errors are reported, and navigation and completion don't work.",
text: "This file does not belong to an imported Java project: only syntax errors are reported, and only JDK classes resolve.",
reload: true,
},
// gopls, before its generic "No packages found" below.
@@ -73,13 +79,17 @@ export const outsideProjectBanner: EditorBannerContribution = {
(l) => subscribeDiagnostics(path, l),
() => noticeFor(path),
);
const inProject = useProject(({ project }) =>
[project?.root, ...(project?.workspace?.projects ?? [])].some((root) => !!root && isSameOrInside(path, root)),
);
if (!notice) return null;
const actions = notice.reload
? RELOADS.filter(({ id }) => {
const action = getAction(id);
return action && isActionEnabled(action);
}).map(({ id, label }) => ({ label, run: () => void executeAction(id) }))
: [];
const actions =
notice.reload && inProject
? RELOADS.filter(({ id }) => {
const action = getAction(id);
return action && isActionEnabled(action);
}).map(({ id, label }) => ({ label, run: () => void executeAction(id) }))
: [];
return { text: notice.text, level: "warning", actions };
},
};
+1 -1
Переглянути файл
@@ -5494,7 +5494,7 @@
"oxfmt formatting failed": "Échec du formatage avec oxfmt",
"oxfmt formatting timed out": "Le formatage avec oxfmt a expiré",
"This file is outside the source folders of its Java module: only syntax errors are reported, and navigation and completion don't work.": "Ce fichier est en dehors des dossiers sources de son module Java : seules les erreurs de syntaxe sont signalées, et la navigation et la complétion ne fonctionnent pas.",
"This file does not belong to an imported Java project: only syntax errors are reported, and navigation and completion don't work.": "Ce fichier n'appartient à aucun projet Java importé : seules les erreurs de syntaxe sont signalées, et la navigation et la complétion ne fonctionnent pas.",
"This file does not belong to an imported Java project: only syntax errors are reported, and only JDK classes resolve.": "Ce fichier n'appartient à aucun projet Java importé : seules les erreurs de syntaxe sont signalées, et seules les classes du JDK sont résolues.",
"This Go file belongs to no package of the workspace: code insight is limited.": "Ce fichier Go n'appartient à aucun paquet de l'espace de travail : l'analyse du code est limitée.",
"This Rust file is not included in any crate: rust-analyzer can't offer code insight for it.": "Ce fichier Rust n'est inclus dans aucune crate : rust-analyzer ne peut pas proposer d'analyse du code pour celui-ci.",
"Reload Maven Projects": "Recharger les projets Maven",
+1 -1
Переглянути файл
@@ -5627,7 +5627,7 @@
"Java project import failed": "Java 프로젝트 가져오기 실패",
"The Java project cannot be imported because these files use a name Windows reserves for devices: {paths}. Delete or rename them, then restart the Java language server.": "다음 파일이 Windows에서 장치용으로 예약된 이름을 사용하므로 Java 프로젝트를 가져올 수 없습니다: {paths}. 파일을 삭제하거나 이름을 변경한 후 Java 언어 서버를 다시 시작하세요.",
"This file is outside the source folders of its Java module: only syntax errors are reported, and navigation and completion don't work.": "이 파일은 해당 Java 모듈의 소스 폴더 밖에 있습니다. 구문 오류만 보고되며 탐색과 코드 완성이 작동하지 않습니다.",
"This file does not belong to an imported Java project: only syntax errors are reported, and navigation and completion don't work.": "이 파일은 가져온 Java 프로젝트에 속하지 않습니다. 구문 오류만 보고되며 탐색과 코드 완성이 작동하지 않습니다.",
"This file does not belong to an imported Java project: only syntax errors are reported, and only JDK classes resolve.": "이 파일은 가져온 Java 프로젝트에 속하지 않습니다. 구문 오류만 보고되며 JDK 클래스만 확인됩니다.",
"This Go file belongs to no package of the workspace: code insight is limited.": "이 Go 파일은 작업 공간의 어떤 패키지에도 속하지 않습니다. 코드 인사이트가 제한됩니다.",
"This Rust file is not included in any crate: rust-analyzer can't offer code insight for it.": "이 Rust 파일은 어떤 crate에도 포함되어 있지 않습니다. rust-analyzer가 코드 인사이트를 제공할 수 없습니다.",
"Reload Maven Projects": "Maven 프로젝트 다시 로드",
+1 -1
Переглянути файл
@@ -5655,7 +5655,7 @@
"Java project import failed": "Java 项目导入失败",
"The Java project cannot be imported because these files use a name Windows reserves for devices: {paths}. Delete or rename them, then restart the Java language server.": "无法导入 Java 项目,因为这些文件使用了 Windows 保留的设备名:{paths}。请删除或重命名这些文件,然后重启 Java 语言服务器。",
"This file is outside the source folders of its Java module: only syntax errors are reported, and navigation and completion don't work.": "此文件不在所属 Java 模块的源码目录中:只报告语法错误,跳转和补全不可用。",
"This file does not belong to an imported Java project: only syntax errors are reported, and navigation and completion don't work.": "此文件不属于已导入的 Java 项目:只报告语法错误,跳转和补全不可用。",
"This file does not belong to an imported Java project: only syntax errors are reported, and only JDK classes resolve.": "此文件不属于已导入的 Java 项目:只报告语法错误,只能解析 JDK 中的类。",
"This Go file belongs to no package of the workspace: code insight is limited.": "此 Go 文件不属于工作区中的任何包:代码洞察功能受限。",
"This Rust file is not included in any crate: rust-analyzer can't offer code insight for it.": "此 Rust 文件未包含在任何 crate 中:rust-analyzer 无法为它提供代码洞察。",
"Reload Maven Projects": "重新加载 Maven 项目",