Файли
openidea/src/editor/lsp/syntaxErrors.test.ts
T
l0ng-ai 2ba07341c2 feat(editor): mark syntax errors in files no language server checks (#1087)
Without a running language server (none configured for the language, or not installed, stopped or crashed), the editor showed no syntax errors at all, though the built-in parser already finds them. The editor now reports the parser's errors for such files, checked again after edits and when a language server starts or stops; while a server runs for the language it reports syntax errors itself. SQL is left to the database support. The check is the built-in Syntax errors inspection.

Fixes #824
2026-11-04 03:02:32 +08:00

95 рядки
3.1 KiB
TypeScript

import { EditorState } from "@codemirror/state";
import { EditorView } from "@codemirror/view";
import { afterEach, describe, expect, it, vi } from "vitest";
const published: Array<[string, string, unknown[]]> = [];
let servers: Array<{ status: string; languages: string[] }> = [];
const serverListeners: Array<() => void> = [];
vi.mock("@/platform/ipc", () => ({
isTauri: () => true,
call: (p: Promise<unknown>) => p,
commands: { lspServers: () => Promise.resolve(servers) },
onIdeEvent: (_type: string, l: () => void) => {
serverListeners.push(l);
return () => {};
},
}));
vi.mock("@/editor/api", () => ({ whenSynced: () => Promise.resolve() }));
vi.mock("@/editor/structure", () => ({
structure: {
syntaxErrors: () =>
Promise.resolve({
errors: [
{
kind: "unexpected",
range: { start: { line: 1, character: 4 }, end: { line: 1, character: 10 } },
expected: null,
message: "unexpected `iqTask`",
},
{
kind: "missing",
range: { start: { line: 2, character: 3 }, end: { line: 2, character: 3 } },
expected: ";",
message: "missing `;`",
},
],
truncated: false,
}),
},
}));
vi.mock("./diagnostics", () => ({
setFileDiagnostics: (path: string, source: string, items: unknown[]) => published.push([path, source, items]),
}));
const { lspDoc } = await import("./lsp");
const { syntaxErrorsExtension } = await import("./syntaxErrors");
const { SYNTAX_ERRORS_PRODUCER } = await import("@/platform/inspections");
const ctx = { doc: 1, path: "/p/a.php", language: "php", uiMode: null, readOnly: false };
const views: EditorView[] = [];
function open() {
const view = new EditorView({
state: EditorState.create({
doc: "<?php\n$a = A::geUn iqTask\nb()\n",
extensions: [lspDoc.of(ctx), syntaxErrorsExtension()],
}),
});
views.push(view);
return view;
}
const settle = () => vi.advanceTimersByTimeAsync(600);
const last = () => published.filter(([, s]) => s === SYNTAX_ERRORS_PRODUCER).at(-1)?.[2];
afterEach(() => {
views.splice(0).forEach((v) => v.destroy());
published.length = 0;
vi.useRealTimers();
});
describe("syntax errors without a language server", () => {
it("reports the parser's errors when no server checks the language", async () => {
vi.useFakeTimers();
servers = [{ status: "missing", languages: ["php"] }];
open();
await settle();
expect(last()).toEqual([
expect.objectContaining({ severity: "error", message: "Unexpected token", source: "Syntax" }),
expect.objectContaining({ severity: "error", message: "';' expected" }),
]);
});
it("leaves them to a running server, and checks again when the server stops", async () => {
vi.useFakeTimers();
servers = [{ status: "ready", languages: ["php"] }];
serverListeners.forEach((l) => l());
open();
await settle();
expect(last()).toEqual([]);
servers = [{ status: "crashed", languages: ["php"] }];
serverListeners.forEach((l) => l());
await settle();
expect(last()).toHaveLength(2);
});
});