дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 19:25:54 +02:00
fix(spelling): Rename identifiers on Typo fixes, add inspection controls, cut false positives (#51)
Root causes: Typo "Change to" replaced one occurrence even for declared names (breaking references); inspections had no Disable / Edit settings controls anywhere; en_US Hunspell does not derive agent nouns like "checkpointer" and compound identifier words were flagged; local inspections (Typo, file references, SQL) never reached Problems. Fix (IDEA): "Typo: Rename to…" renames via LSP rename, falling back to syntax-based local rename, never a silent single replace, and lists identifier-safe words only; intention submenu (→) with Edit inspection profile setting / Disable inspection for every inspection; Problems and Inspection Results get Disable Inspection, Change Severity, Edit Inspection Profile Setting; Typo options Process code / literals / comments; -er/-ers derivation and identifier compounds accepted, common misspellings still flagged. Siblings covered: all LSP and built-in inspections' intentions, file reference and SQL inspections in Problems, severity counter. Verified: ide-spell tests (issue examples + ~50 real misspellings), vitest (286), in-app on macOS: only 'mesage' flagged, submenu, Rename to → 'message' updated both occurrences. Claude-Session: https://claude.ai/code/session_01VUYsdXA2nkF5v6zsC93dpn
Цей коміт міститься в:
1 джерело
eda8cae0a1
коміт
aa5821bb46
32 files changed
+1289
-61
No files matched your search
@@ -7,6 +7,8 @@ import { useEditorRegistry } from "@/editor/api";
|
||||
import { executeAction } from "@/platform/actions";
|
||||
import { formatBinding, hasPrimaryModifier, hostBinding, shortcutLabel } from "@/platform/keymap";
|
||||
import { basename, relativeDir, useProject } from "@/platform/project";
|
||||
import { diagnosticInspection, producerOf, type InspectionRef } from "@/platform/inspections";
|
||||
import { problemControls } from "@/platform/inspectionControls";
|
||||
import { editorService, useOccurrenceNavigator } from "@/workbench/contributions";
|
||||
import {
|
||||
EmptyState,
|
||||
@@ -30,6 +32,7 @@ import {
|
||||
DEFAULT_PROBLEM_ORDER,
|
||||
countSeverities,
|
||||
fileDiagnostics,
|
||||
producerFor,
|
||||
sortProblems,
|
||||
useProblems,
|
||||
type ProblemOrder,
|
||||
@@ -109,6 +112,14 @@ type Node =
|
||||
| { kind: "file"; key: string; path: string; items: Diagnostic[] }
|
||||
| { kind: "diag"; key: string; path: string; d: Diagnostic };
|
||||
|
||||
/** The inspection that reported `d` (for Disable inspection / Change Severity). */
|
||||
export function diagInspection(map: ProblemsMap, path: string, d: Diagnostic): InspectionRef | null {
|
||||
return diagnosticInspection(producerFor(map, path, d) ?? producerOf(d), d);
|
||||
}
|
||||
|
||||
const inspectionMenu = (inspection: InspectionRef | null): MenuItem[] =>
|
||||
inspection ? [{ kind: "separator" }, ...problemControls(inspection)] : [];
|
||||
|
||||
const plural = (n: number, word: string) => (n ? `${n} ${word}${n === 1 ? "" : "s"}` : "");
|
||||
|
||||
const SEVERITY_ICON: Record<Severity, string> = { error: "error", warning: "warning", info: "info", hint: "info" };
|
||||
@@ -374,6 +385,7 @@ export function ProblemsView() {
|
||||
disabled: n.kind !== "diag",
|
||||
run: () => copy(n),
|
||||
},
|
||||
...inspectionMenu(n.kind === "diag" ? diagInspection(map, n.path, n.d) : null),
|
||||
]);
|
||||
}}
|
||||
activateExpandable={false}
|
||||
|
||||
@@ -81,3 +81,25 @@ describe("profiledProblems", () => {
|
||||
expect(fileDiagnostics(profiledProblems(raw, {}), "/b")).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("local inspections (issue #42)", () => {
|
||||
it("reach Problems and clear there too, with their inspection known", async () => {
|
||||
const { installProblemsEvents, producerFor } = await import("./problemsStore");
|
||||
const { setFileDiagnostics } = await import("@/editor/lsp/diagnostics");
|
||||
const { diagnosticInspection } = await import("@/platform/inspections");
|
||||
clearProblems();
|
||||
const off = installProblemsEvents();
|
||||
try {
|
||||
setFileDiagnostics("/p/a.py", "spelling", [{ ...d(0, "info"), message: "Typo: In word 'mesage'", source: "Spelling" }]);
|
||||
const [typo] = fileDiagnostics(useProblems.getState().map, "/p/a.py");
|
||||
expect(typo).toMatchObject({ message: "Typo: In word 'mesage'", typo: true });
|
||||
const producer = producerFor(useProblems.getState().map, "/p/a.py", typo!);
|
||||
expect(producer).toBe("spelling");
|
||||
expect(diagnosticInspection(producer!, typo!)?.id).toBe("builtin:spelling");
|
||||
setFileDiagnostics("/p/a.py", "spelling", []);
|
||||
expect(useProblems.getState().map.size).toBe(0);
|
||||
} finally {
|
||||
off();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,7 @@
|
||||
import { create } from "zustand";
|
||||
import type { Diagnostic, Severity } from "@/bindings";
|
||||
import { onIdeEvent } from "@/platform/ipc";
|
||||
import { onLocalDiagnostics } from "@/platform/localDiagnostics";
|
||||
import {
|
||||
applyProfile,
|
||||
inspectionProfile,
|
||||
@@ -53,6 +54,12 @@ export function fileDiagnostics(map: ProblemsMap, path: string): Diagnostic[] {
|
||||
);
|
||||
}
|
||||
|
||||
/** The producer (inspection) that reported `d`, one of `path`'s problems in `map`. */
|
||||
export function producerFor(map: ProblemsMap, path: string, d: Diagnostic): string | null {
|
||||
for (const [source, items] of map.get(path) ?? []) if (items.includes(d)) return source;
|
||||
return null;
|
||||
}
|
||||
|
||||
export interface ProblemOrder {
|
||||
/** Errors before warnings before infos (IDEA's default). */
|
||||
bySeverity: boolean;
|
||||
@@ -130,11 +137,14 @@ export function clearProblems() {
|
||||
|
||||
export function installProblemsEvents(): () => void {
|
||||
const off = onIdeEvent("diagnostics", (e) => applyDiagnosticsEvent(e.path, e.source, e.items));
|
||||
// Typo, file references, SQL: inspections the editor runs itself.
|
||||
const offLocal = onLocalDiagnostics(applyDiagnosticsEvent);
|
||||
const offProfile = subscribeInspectionProfile(() =>
|
||||
useProblems.setState((s) => ({ map: profiledProblems(s.raw, inspectionProfile()) })),
|
||||
);
|
||||
return () => {
|
||||
off();
|
||||
offLocal();
|
||||
offProfile();
|
||||
};
|
||||
}
|
||||
Reference in new issue
Заблокувати користувача