1use cairo_lang_defs::plugin::PluginDiagnostic;
2use cairo_lang_formatter::FormatterConfig;
3use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
4use fixer::{
5 DiagnosticFixSuggestion, FixerDatabase, get_fixes_without_resolving_overlapping,
6 merge_overlapping_fixes,
7};
8
9use helper::format_fixed_file;
10use itertools::Itertools;
11
12use std::{cmp::Reverse, collections::HashMap};
13
14use anyhow::{Result, anyhow};
15use cairo_lang_filesystem::{db::FilesGroup, ids::FileId};
16use cairo_lang_semantic::{SemanticDiagnostic, db::SemanticGroup};
17
18pub static CAIRO_LINT_TOOL_NAME: &str = "cairo-lint";
19
20pub type CairoLintToolMetadata = OrderedHashMap<String, bool>;
24
25pub mod context;
26
27mod corelib;
28pub mod diagnostics;
29mod fixer;
30mod helper;
31mod lang;
32pub mod lints;
33mod mappings;
34pub mod plugin;
35mod queries;
36
37pub use corelib::CorelibContext;
38pub use lang::{
39 LinterAnalysisDatabase, LinterAnalysisDatabaseBuilder, LinterDiagnosticParams, LinterGroup,
40};
41
42use cairo_lang_syntax::node::db::SyntaxGroup;
43use context::{CairoLintKind, get_lint_type_from_diagnostic_message};
44use salsa::Database;
45
46pub trait CairoLintGroup: SemanticGroup + SyntaxGroup {}
47
48#[tracing::instrument(skip_all, level = "trace")]
60pub fn get_fixes<'db>(
61 db: &'db dyn Database,
62 linter_params: &LinterDiagnosticParams,
63 diagnostics: Vec<SemanticDiagnostic<'db>>,
64) -> HashMap<FileId<'db>, Vec<DiagnosticFixSuggestion>> {
65 let mut new_db = FixerDatabase::new_from(db);
68 let fixes = get_fixes_without_resolving_overlapping(db, diagnostics);
69 fixes
70 .into_iter()
71 .map(|(file_id, fixes)| {
72 let new_fixes = merge_overlapping_fixes(
73 &mut new_db,
74 linter_params,
75 file_id.long(db).into_file_input(db),
76 fixes,
77 );
78 (file_id, new_fixes)
79 })
80 .collect()
81}
82
83#[tracing::instrument(skip_all, level = "trace")]
98pub fn get_separated_fixes<'db>(
99 db: &'db dyn Database,
100 diagnostics: Vec<SemanticDiagnostic<'db>>,
101) -> HashMap<FileId<'db>, Vec<DiagnosticFixSuggestion>> {
102 get_fixes_without_resolving_overlapping(db, diagnostics)
103}
104
105#[tracing::instrument(skip_all, level = "trace")]
113pub fn apply_file_fixes<'db>(
114 file_id: FileId<'db>,
115 fixes: Vec<DiagnosticFixSuggestion>,
116 db: &'db dyn Database,
117 formatter_config: FormatterConfig,
118) -> Result<()> {
119 let suggestions = fixes
122 .iter()
123 .flat_map(|fix| fix.suggestions.iter())
124 .sorted_by_key(|suggestion| Reverse(suggestion.span.start))
125 .collect::<Vec<_>>();
126
127 let mut files: HashMap<FileId, String> = HashMap::default();
129 files.insert(
130 file_id,
131 db.file_content(file_id)
132 .ok_or(anyhow!("{} not found", file_id.file_name(db).to_string(db)))?
133 .to_string(),
134 );
135
136 files.entry(file_id).and_modify(|file| {
138 for suggestion in suggestions {
139 file.replace_range(suggestion.span.to_str_range(), &suggestion.code)
140 }
141 });
142
143 std::fs::write(
145 file_id.full_path(db),
146 format_fixed_file(db, formatter_config, files.get(&file_id).unwrap().clone()),
147 )?;
148
149 Ok(())
150}
151
152pub fn is_panic_diagnostic(diag: &PluginDiagnostic) -> bool {
154 get_lint_type_from_diagnostic_message(&diag.message) == CairoLintKind::Panic
155}