diff --git a/playground/.eslintrc b/playground/.eslintrc index ca97286766..49fb9ac0fc 100644 --- a/playground/.eslintrc +++ b/playground/.eslintrc @@ -14,7 +14,8 @@ ], "rules": { // Disable some recommended rules that we don't want to enforce. - "@typescript-eslint/no-explicit-any": "off" + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-empty-function": "off" }, "settings": { "react": { diff --git a/playground/src/App.tsx b/playground/src/App.tsx index 9106ca31f4..902c210e74 100644 --- a/playground/src/App.tsx +++ b/playground/src/App.tsx @@ -61,17 +61,17 @@ const defaultConfig = getDefaultConfig(AVAILABLE_OPTIONS); export default function App() { const monaco = useMonaco(); - const [ruffInitialized, setRuffInitialized] = useState(false); + const [initialized, setInitialized] = useState(false); const [config, setConfig] = useState(null); const [source, setSource] = useState(null); const [error, setError] = useState(null); useEffect(() => { - init().then(() => setRuffInitialized(true)); + init().then(() => setInitialized(true)); }, []); useEffect(() => { - if (source === null && config === null && monaco) { + if (source == null && config == null && monaco) { const [config, source] = restoreConfigAndSource(); setConfig(config); setSource(source); @@ -87,13 +87,7 @@ export default function App() { useEffect(() => { const editor = monaco?.editor; const model = editor?.getModels()[0]; - if ( - !editor || - !model || - !ruffInitialized || - source === null || - config === null - ) { + if (!editor || !model || !initialized || source == null || config == null) { return; } @@ -118,11 +112,52 @@ export default function App() { severity: MarkerSeverity.Error, })) ); - }, [config, source, monaco, ruffInitialized]); + + const codeActionProvider = monaco?.languages.registerCodeActionProvider( + "python", + { + // @ts-expect-error: The type definition is wrong. + provideCodeActions: function (model, position) { + const actions = checks + .filter((check) => position.startLineNumber === check.location.row) + .filter((check) => check.fix) + .map((check) => ({ + title: `Fix ${check.code}`, + id: `fix-${check.code}`, + kind: "quickfix", + edit: check.fix + ? { + edits: [ + { + resource: model.uri, + versionId: model.getVersionId(), + edit: { + range: { + startLineNumber: check.fix.location.row, + startColumn: check.fix.location.column + 1, + endLineNumber: check.fix.end_location.row, + endColumn: check.fix.end_location.column + 1, + }, + text: check.fix.content, + }, + }, + ], + } + : undefined, + })); + return { actions, dispose: () => {} }; + }, + } + ); + + return () => { + codeActionProvider?.dispose(); + }; + }, [config, source, monaco, initialized]); const handleEditorChange = useCallback( (value: string | undefined) => { - value && setSource(value); + setSource(value || ""); }, [setSource] ); diff --git a/src/lib_wasm.rs b/src/lib_wasm.rs index c9bede8c48..526e9bfef7 100644 --- a/src/lib_wasm.rs +++ b/src/lib_wasm.rs @@ -5,6 +5,7 @@ use rustpython_parser::lexer::LexResult; use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; +use crate::autofix::Fix; use crate::checks::CheckCode; use crate::directives; use crate::linter::check_path; @@ -27,6 +28,17 @@ export interface Check { row: number; column: number; }; + fix: { + content: string; + location: { + row: number; + column: number; + }; + end_location: { + row: number; + column: number; + }; + } | null; }; "#; @@ -36,6 +48,7 @@ struct Message { message: String, location: Location, end_location: Location, + fix: Option, } #[wasm_bindgen(start)] @@ -71,7 +84,7 @@ pub fn check(contents: &str, options: JsValue) -> Result { &locator, &directives, &settings, - false.into(), + flags::Autofix::Enabled, flags::Noqa::Enabled, ) .map_err(|e| e.to_string())?; @@ -83,6 +96,7 @@ pub fn check(contents: &str, options: JsValue) -> Result { message: check.kind.body(), location: check.location, end_location: check.end_location, + fix: check.fix, }) .collect(); @@ -118,7 +132,8 @@ mod test { code: CheckCode::F634, message: "If test is a tuple, which is always `True`".to_string(), location: Location::new(1, 0), - end_location: Location::new(1, 15) + end_location: Location::new(1, 15), + fix: None, }] ); }