Skip to main content

cairo_lint/
lib.rs

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
20/// Describes tool metadata for the Cairo lint.
21/// IMPORTANT: This one is a public type, so watch out when modifying it,
22/// as it might break the backwards compatibility.
23pub 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/// Gets the fixes for a set of a compiler diagnostics (that uses Cairo lint analyzer plugin).
49/// # Arguments
50///
51/// * `db` - The reference to the database.
52/// * `diagnostics` - The list of all compiler diagnostics including those coming from the cairo-lint plugin.
53///
54/// # Returns
55///
56/// A HashMap where:
57/// * keys are FileIds (that points to a file that the fixes might be applied to).
58/// * values are vectors of proposed Fixes.
59#[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    // We need to create a new database to avoid modifying the original one.
66    // This one is used to resolve the overlapping fixes.
67    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/// Gets all possible fixes for a set of compiler diagnostics (that uses Cairo lint analyzer plugin)
84/// without resolving overlapping fixes. This is needed when you want to see all potential fixes,
85/// even if they might conflict with each other.
86///
87/// # Arguments
88///
89/// * `db` - The reference to the database.
90/// * `diagnostics` - The list of all compiler diagnostics including those coming from the cairo-lint plugin.
91///
92/// # Returns
93///
94/// A HashMap where:
95/// * keys are FileIds (that points to a file that the fixes might be applied to).
96/// * values are vectors of proposed Fixes.
97#[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/// Applies the fixes to the file.
106///
107/// # Arguments
108///
109/// * `file_id` - The FileId of the file that the fixes should be applied to.
110/// * `fixes` - The list of fixes that should be applied to the file.
111/// * `db` - The reference to the database that contains the file content.
112#[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    // Those suggestions MUST be sorted in reverse, so changes at the end of the file,
120    // doesn't affect the spans of the previous file suggestions.
121    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    // Get all the files that need to be fixed
128    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    // Can't fail we just set the file value.
137    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    // Dump them in place.
144    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
152/// Checks if the diagnostic is a panic diagnostic.
153pub fn is_panic_diagnostic(diag: &PluginDiagnostic) -> bool {
154    get_lint_type_from_diagnostic_message(&diag.message) == CairoLintKind::Panic
155}