1use cairo_lang_defs::plugin::PluginDiagnostic;
2use fixes::{apply_import_fixes, collect_unused_imports, fix_semantic_diagnostic, Fix, ImportFix};
3
4use cairo_lang_syntax::node::SyntaxNode;
5
6use std::{cmp::Reverse, collections::HashMap};
7
8pub use annotate_snippets;
9use anyhow::{anyhow, Result};
10use cairo_lang_compiler::db::RootDatabase;
11use cairo_lang_diagnostics::DiagnosticEntry;
12use cairo_lang_filesystem::db::FilesGroup;
13use cairo_lang_filesystem::ids::FileId;
14use cairo_lang_semantic::{diagnostic::SemanticDiagnosticKind, SemanticDiagnostic};
15use cairo_lang_utils::Upcast;
16
17pub mod context;
20pub mod diagnostics;
21pub mod fixes;
22mod helper;
23pub mod lints;
24pub mod plugin;
25mod queries;
26
27use context::{get_lint_type_from_diagnostic_message, CairoLintKind};
28
29pub fn get_fixes(
41 db: &RootDatabase,
42 diagnostics: Vec<SemanticDiagnostic>,
43) -> HashMap<FileId, Vec<Fix>> {
44 let unused_imports: HashMap<FileId, HashMap<SyntaxNode, ImportFix>> =
47 collect_unused_imports(db, &diagnostics);
48 let mut fixes = HashMap::new();
49 unused_imports.keys().for_each(|file_id| {
50 let file_fixes: Vec<Fix> = apply_import_fixes(db, unused_imports.get(file_id).unwrap());
51 fixes.insert(*file_id, file_fixes);
52 });
53
54 let diags_without_imports = diagnostics
55 .iter()
56 .filter(|diag| !matches!(diag.kind, SemanticDiagnosticKind::UnusedImport(_)))
57 .collect::<Vec<_>>();
58
59 for diag in diags_without_imports {
60 if let Some((fix_node, fix)) = fix_semantic_diagnostic(db, diag) {
61 let location = diag.location(db.upcast());
62 fixes
63 .entry(location.file_id)
64 .or_insert_with(Vec::new)
65 .push(Fix {
66 span: fix_node.span(db.upcast()),
67 suggestion: fix,
68 });
69 }
70 }
71 fixes
72}
73
74pub fn apply_file_fixes(file_id: FileId, fixes: Vec<Fix>, db: &RootDatabase) -> Result<()> {
81 let mut fixes = fixes;
82 fixes.sort_by_key(|fix| Reverse(fix.span.start));
83 let mut fixable_diagnostics = Vec::with_capacity(fixes.len());
84 if fixes.len() <= 1 {
85 fixable_diagnostics = fixes;
86 } else {
87 for i in 0..fixes.len() - 1 {
89 let first = fixes[i].span;
90 let second = fixes[i + 1].span;
91 if first.start >= second.end {
92 fixable_diagnostics.push(fixes[i].clone());
93 if i == fixes.len() - 1 {
94 fixable_diagnostics.push(fixes[i + 1].clone());
95 }
96 }
97 }
98 }
99 let mut files: HashMap<FileId, String> = HashMap::default();
101 files.insert(
102 file_id,
103 db.file_content(file_id)
104 .ok_or(anyhow!("{} not found", file_id.file_name(db.upcast())))?
105 .to_string(),
106 );
107 for fix in fixable_diagnostics {
109 files
111 .entry(file_id)
112 .and_modify(|file| file.replace_range(fix.span.to_str_range(), &fix.suggestion));
113 }
114 std::fs::write(file_id.full_path(db.upcast()), files.get(&file_id).unwrap())?;
116 Ok(())
117}
118
119pub fn is_panic_diagnostic(diag: &PluginDiagnostic) -> bool {
121 get_lint_type_from_diagnostic_message(&diag.message) == CairoLintKind::Panic
122}