From b95a1949b932e89179c052efdbf4e21002ce6777 Mon Sep 17 00:00:00 2001 From: Mateusz Kowalski Date: Tue, 14 Jan 2025 11:01:47 +0100 Subject: [PATCH] Move fixing logic to cairo-lint-core (#179) * Move fixing logic to cairo-lint-core * Reexport annotate-snippets * Make separate function for getting fixes and applying them --- Cargo.lock | 1 + crates/cairo-lint-core/Cargo.toml | 4 ++ crates/cairo-lint-core/src/lib.rs | 86 ++++++++++++++++++++++++++++ crates/cairo-lint-core/src/plugin.rs | 3 +- 4 files changed, 92 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eeb71973..31b1477f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -656,6 +656,7 @@ name = "cairo-lint-core" version = "0.1.0" dependencies = [ "annotate-snippets", + "anyhow", "cairo-lang-compiler", "cairo-lang-defs", "cairo-lang-diagnostics", diff --git a/crates/cairo-lint-core/Cargo.toml b/crates/cairo-lint-core/Cargo.toml index 1fcc7c70..b796ed1d 100644 --- a/crates/cairo-lint-core/Cargo.toml +++ b/crates/cairo-lint-core/Cargo.toml @@ -6,6 +6,7 @@ repository.workspace = true license-file.workspace = true [dependencies] +anyhow.workspace = true cairo-lang-compiler.workspace = true cairo-lang-utils.workspace = true cairo-lang-semantic.workspace = true @@ -27,3 +28,6 @@ ctor.workspace = true cairo-lint-test-utils = { path = "../cairo-lint-test-utils" } paste.workspace = true itertools.workspace = true + +[features] +testing-colors = ["annotate-snippets/testing-colors"] diff --git a/crates/cairo-lint-core/src/lib.rs b/crates/cairo-lint-core/src/lib.rs index cd308e91..29b73efb 100644 --- a/crates/cairo-lint-core/src/lib.rs +++ b/crates/cairo-lint-core/src/lib.rs @@ -1,4 +1,90 @@ +use std::{cmp::Reverse, collections::HashMap}; + +use anyhow::{anyhow, Result}; +use cairo_lang_compiler::db::RootDatabase; +use cairo_lang_diagnostics::DiagnosticEntry; +use cairo_lang_filesystem::db::FilesGroup; +use cairo_lang_filesystem::ids::FileId; +use cairo_lang_semantic::{diagnostic::SemanticDiagnosticKind, SemanticDiagnostic}; +use cairo_lang_syntax::node::SyntaxNode; +use cairo_lang_utils::Upcast; +use fix::{apply_import_fixes, collect_unused_imports, fix_semantic_diagnostic, Fix, ImportFix}; + pub mod diagnostics; pub mod fix; pub mod lints; pub mod plugin; +pub use annotate_snippets; + +pub fn get_fixes( + db: &RootDatabase, + diagnostics: Vec, +) -> Result>> { + // Handling unused imports separately as we need to run pre-analysis on the diagnostics. + // to handle complex cases. + let unused_imports: HashMap> = + collect_unused_imports(db, &diagnostics); + let mut fixes = HashMap::new(); + unused_imports.keys().for_each(|file_id| { + let file_fixes: Vec = apply_import_fixes(db, unused_imports.get(file_id).unwrap()); + fixes.insert(*file_id, file_fixes); + }); + + let diags_without_imports = diagnostics + .iter() + .filter(|diag| !matches!(diag.kind, SemanticDiagnosticKind::UnusedImport(_))) + .collect::>(); + + for diag in diags_without_imports { + if let Some((fix_node, fix)) = fix_semantic_diagnostic(db, diag) { + let location = diag.location(db.upcast()); + fixes + .entry(location.file_id) + .or_insert_with(Vec::new) + .push(Fix { + span: fix_node.span(db.upcast()), + suggestion: fix, + }); + } + } + Ok(fixes) +} + +pub fn apply_file_fixes(file_id: FileId, fixes: Vec, db: &RootDatabase) -> Result<()> { + let mut fixes = fixes; + fixes.sort_by_key(|fix| Reverse(fix.span.start)); + let mut fixable_diagnostics = Vec::with_capacity(fixes.len()); + if fixes.len() <= 1 { + fixable_diagnostics = fixes; + } else { + // Check if we have nested diagnostics. If so it's a nightmare to fix hence just ignore it + for i in 0..fixes.len() - 1 { + let first = fixes[i].span; + let second = fixes[i + 1].span; + if first.start >= second.end { + fixable_diagnostics.push(fixes[i].clone()); + if i == fixes.len() - 1 { + fixable_diagnostics.push(fixes[i + 1].clone()); + } + } + } + } + // Get all the files that need to be fixed + let mut files: HashMap = HashMap::default(); + files.insert( + file_id, + db.file_content(file_id) + .ok_or(anyhow!("{} not found", file_id.file_name(db.upcast())))? + .to_string(), + ); + // Fix the files + for fix in fixable_diagnostics { + // Can't fail we just set the file value. + files + .entry(file_id) + .and_modify(|file| file.replace_range(fix.span.to_str_range(), &fix.suggestion)); + } + // Dump them in place + std::fs::write(file_id.full_path(db.upcast()), files.get(&file_id).unwrap())?; + Ok(()) +} diff --git a/crates/cairo-lint-core/src/plugin.rs b/crates/cairo-lint-core/src/plugin.rs index 3ec82279..60abd6e6 100644 --- a/crates/cairo-lint-core/src/plugin.rs +++ b/crates/cairo-lint-core/src/plugin.rs @@ -148,8 +148,7 @@ impl AnalyzerPlugin for CairoLint { .as_syntax_node() } ModuleItemId::Impl(impl_id) => { - let impl_functions = db.impl_functions(*impl_id); - let Ok(functions) = impl_functions else { + let Ok(functions) = db.impl_functions(*impl_id) else { continue; }; for (_fn_name, fn_id) in functions.iter() {