дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 19:25:54 +02:00
fix: sync inspection results with profiles and restore CSS validation
Цей коміт міститься в:
1 джерело
d3feb056aa
коміт
6b9a2ec67e
9 files changed
+387
-12
No files matched your search
@@ -5,15 +5,21 @@
|
||||
import { registerActions } from "@/platform/actions";
|
||||
import { useProject } from "@/platform/project";
|
||||
import { registerOverlay, registerToolWindow } from "@/workbench/contributions";
|
||||
import { showCodeCleanupDialog, showDuplicatesDialog, showInspectCodeDialog, showStructuralDialog } from "./AnalysisDialogs";
|
||||
import {
|
||||
showCodeCleanupDialog,
|
||||
showDuplicatesDialog,
|
||||
showInspectCodeDialog,
|
||||
showStructuralDialog,
|
||||
} from "./AnalysisDialogs";
|
||||
import { AnalysisDialogHost } from "./dialogs";
|
||||
import { resetDuplicates } from "./duplicates";
|
||||
import { DuplicatesHeaderActions, DuplicatesView } from "./DuplicatesView";
|
||||
import { resetInspection } from "./inspect";
|
||||
import { installInspectionProfileSync, resetInspection } from "./inspect";
|
||||
import { InspectionHeaderActions, InspectionView, useInspectionHeaderMenu } from "./InspectionView";
|
||||
|
||||
export function activate(): () => void {
|
||||
const disposers = [
|
||||
installInspectionProfileSync(),
|
||||
useProject.subscribe((s, prev) => {
|
||||
if (s.project?.root === prev.project?.root) return;
|
||||
resetInspection();
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import { afterEach, expect, it, vi } from "vitest";
|
||||
import type { Diagnostic, InspectEvent } from "@/platform/ipc";
|
||||
import { useProject } from "@/platform/project";
|
||||
import { installInspectionProfileSync, resetInspection, runInspection, useInspection } from "./inspect";
|
||||
|
||||
const mock = vi.hoisted(() => ({ channel: null as null | { onmessage: (event: InspectEvent) => void } }));
|
||||
vi.mock("@tauri-apps/api/core", () => ({
|
||||
Channel: class {
|
||||
onmessage = () => {};
|
||||
},
|
||||
}));
|
||||
vi.mock("@/platform/ipc", async (original) => ({
|
||||
...(await original<object>()),
|
||||
isTauri: () => true,
|
||||
call: (value: unknown) => value,
|
||||
commands: {
|
||||
inspectCode: (_id: unknown, _scope: unknown, channel: typeof mock.channel) => {
|
||||
mock.channel = channel;
|
||||
return Promise.resolve();
|
||||
},
|
||||
cancelRequest: () => Promise.resolve(),
|
||||
},
|
||||
}));
|
||||
vi.mock("@/workbench/contributions", () => ({
|
||||
toolWindowService: () => ({ show: () => {} }),
|
||||
notifications: () => ({ notify: () => {} }),
|
||||
}));
|
||||
|
||||
const diagnostic: Diagnostic = {
|
||||
range: { start: { line: 0, col: 0 }, end: { line: 0, col: 1 } },
|
||||
severity: "error",
|
||||
source: "ts",
|
||||
server: "typescript-language-server",
|
||||
code: "2322",
|
||||
message: "Wrong type",
|
||||
unnecessary: false,
|
||||
deprecated: false,
|
||||
};
|
||||
function profile(override: object) {
|
||||
useProject.setState({ settings: { inspections: { default: { "lsp:typescript-language-server": override } } } });
|
||||
}
|
||||
function file(path = "/p/a.ts") {
|
||||
mock.channel!.onmessage({ type: "file", done: 1, file: { path, relPath: path.slice(3), diagnostics: [diagnostic] } });
|
||||
}
|
||||
function done() {
|
||||
mock.channel!.onmessage({ type: "done", summary: { problems: 1 } } as InspectEvent);
|
||||
}
|
||||
afterEach(() => {
|
||||
resetInspection();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("reprojects completed results, counts and original severities without rerunning", async () => {
|
||||
const dispose = installInspectionProfileSync();
|
||||
try {
|
||||
profile({});
|
||||
await runInspection({ kind: "project", directory: "" }, "default");
|
||||
file();
|
||||
done();
|
||||
const excluded = new Set(["excluded"]);
|
||||
useInspection.setState({ excluded });
|
||||
profile({ severity: "warning" });
|
||||
expect(useInspection.getState().files.get("/p/a.ts")!.diagnostics[0]!.severity).toBe("warning");
|
||||
profile({ enabled: false });
|
||||
expect(useInspection.getState().files.size).toBe(0);
|
||||
expect(useInspection.getState().summary!.problems).toBe(0);
|
||||
profile({});
|
||||
expect(useInspection.getState().files.get("/p/a.ts")!.diagnostics[0]!.severity).toBe("error");
|
||||
expect(useInspection.getState().summary!.problems).toBe(1);
|
||||
expect(useInspection.getState().excluded).toBe(excluded);
|
||||
resetInspection();
|
||||
expect(useInspection.getState().rawFiles.size).toBe(0);
|
||||
} finally {
|
||||
dispose();
|
||||
}
|
||||
});
|
||||
|
||||
it("retains initially disabled diagnostics and uses edits made during streaming", async () => {
|
||||
vi.useFakeTimers();
|
||||
const dispose = installInspectionProfileSync();
|
||||
try {
|
||||
profile({ enabled: false });
|
||||
await runInspection({ kind: "project", directory: "" }, "default");
|
||||
file();
|
||||
vi.advanceTimersByTime(150);
|
||||
expect(useInspection.getState().files.size).toBe(0);
|
||||
profile({ severity: "warning" });
|
||||
expect(useInspection.getState().files.size).toBe(1);
|
||||
file("/p/b.ts");
|
||||
done();
|
||||
expect([...useInspection.getState().files.values()].map((f) => f.diagnostics[0]!.severity)).toEqual([
|
||||
"warning",
|
||||
"warning",
|
||||
]);
|
||||
expect(useInspection.getState().summary!.problems).toBe(2);
|
||||
} finally {
|
||||
dispose();
|
||||
}
|
||||
});
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
profileOverrides,
|
||||
type ProfileId,
|
||||
} from "@/platform/inspections";
|
||||
import { useProject } from "@/platform/project";
|
||||
import { notifications, toolWindowService } from "@/workbench/contributions";
|
||||
import { describeScope, resolveScope, type ScopeChoice } from "./scope";
|
||||
|
||||
@@ -100,6 +101,8 @@ interface InspectState {
|
||||
running: boolean;
|
||||
total: number;
|
||||
done: number;
|
||||
/** Unfiltered server results, retained so disabled inspections can be restored. */
|
||||
rawFiles: Map<string, InspectedFileResult>;
|
||||
/** Files with problems, by path. */
|
||||
files: Map<string, InspectedFileResult>;
|
||||
summary: InspectSummary | null;
|
||||
@@ -117,6 +120,7 @@ const initialInspection = (): InspectState => ({
|
||||
running: false,
|
||||
total: 0,
|
||||
done: 0,
|
||||
rawFiles: new Map(),
|
||||
files: new Map(),
|
||||
summary: null,
|
||||
scopeLabel: null,
|
||||
@@ -141,6 +145,28 @@ export function toggleExcludedProblems(problems: readonly InspectionProblem[]) {
|
||||
|
||||
export const useInspection = create<InspectState>(initialInspection);
|
||||
|
||||
/** Rebuild from server diagnostics rather than previously overridden severities. */
|
||||
function projectResults(rawFiles: Map<string, InspectedFileResult>, profileId: ProfileId) {
|
||||
const profile = profileOverrides(profileId);
|
||||
const files = new Map<string, InspectedFileResult>();
|
||||
for (const [path, file] of rawFiles) {
|
||||
const diagnostics = applyProfileMixed(profile, file.diagnostics);
|
||||
if (diagnostics.length) files.set(path, { ...file, diagnostics });
|
||||
}
|
||||
return { files, problems: [...files.values()].reduce((n, f) => n + f.diagnostics.length, 0) };
|
||||
}
|
||||
|
||||
/** Keep both completed and streaming results in sync with edits to their chosen profile. */
|
||||
export function installInspectionProfileSync() {
|
||||
return useProject.subscribe((state, prev) => {
|
||||
if (state.settings?.inspections === prev.settings?.inspections) return;
|
||||
const { rawFiles, profile, summary } = useInspection.getState();
|
||||
if (profile === null) return;
|
||||
const { files, problems } = projectResults(rawFiles, profile);
|
||||
useInspection.setState({ files, summary: summary ? { ...summary, problems } : null });
|
||||
});
|
||||
}
|
||||
|
||||
let inflight: RequestId | null = null;
|
||||
|
||||
export function cancelInspection() {
|
||||
@@ -169,6 +195,7 @@ export async function runInspection(choice: ScopeChoice, profileId: ProfileId =
|
||||
running: true,
|
||||
total: 0,
|
||||
done: 0,
|
||||
rawFiles: new Map(),
|
||||
files: new Map(),
|
||||
summary: null,
|
||||
error: null,
|
||||
@@ -177,7 +204,6 @@ export async function runInspection(choice: ScopeChoice, profileId: ProfileId =
|
||||
profile: profileId,
|
||||
excluded: new Set(),
|
||||
});
|
||||
const profile = profileOverrides(profileId);
|
||||
toolWindowService().show("inspection");
|
||||
if (!isTauri()) {
|
||||
useInspection.setState({ running: false, error: "Inspect Code needs the desktop app" });
|
||||
@@ -187,7 +213,8 @@ export async function runInspection(choice: ScopeChoice, profileId: ProfileId =
|
||||
let flush: ReturnType<typeof setTimeout> | null = null;
|
||||
const publish = () => {
|
||||
flush = null;
|
||||
if (inflight === id) useInspection.setState({ files: new Map(found) });
|
||||
if (inflight === id)
|
||||
useInspection.setState({ rawFiles: new Map(found), files: projectResults(found, profileId).files });
|
||||
};
|
||||
const channel = new Channel<InspectEvent>();
|
||||
channel.onmessage = (ev) => {
|
||||
@@ -197,23 +224,25 @@ export async function runInspection(choice: ScopeChoice, profileId: ProfileId =
|
||||
useInspection.setState({ total: ev.total });
|
||||
break;
|
||||
case "file": {
|
||||
const diagnostics = applyProfileMixed(profile, ev.file.diagnostics);
|
||||
for (const d of ev.file.diagnostics) if (d.code) noteDiagnosticCodes(producerOf(d), [d]);
|
||||
if (diagnostics.length) found.set(ev.file.path, { ...ev.file, diagnostics });
|
||||
found.set(ev.file.path, ev.file);
|
||||
useInspection.setState({ done: ev.done });
|
||||
flush ??= setTimeout(publish, 150);
|
||||
break;
|
||||
}
|
||||
case "done":
|
||||
case "done": {
|
||||
const { files, problems } = projectResults(found, profileId);
|
||||
if (flush) clearTimeout(flush);
|
||||
inflight = null;
|
||||
useInspection.setState({
|
||||
running: false,
|
||||
files: new Map(found),
|
||||
rawFiles: new Map(found),
|
||||
files,
|
||||
// Problems the profile kept, not all the servers reported.
|
||||
summary: { ...ev.summary, problems: [...found.values()].reduce((n, f) => n + f.diagnostics.length, 0) },
|
||||
summary: { ...ev.summary, problems },
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
try {
|
||||
|
||||
Reference in new issue
Заблокувати користувача