cairo_lint_core/
context.rs

1use crate::lints::bitwise_for_parity_check::check_bitwise_for_parity;
2use crate::lints::bitwise_for_parity_check::BitwiseForParity;
3use crate::lints::bool_comparison::check_bool_comparison;
4use crate::lints::bool_comparison::BoolComparison;
5use crate::lints::breaks::check_break;
6use crate::lints::breaks::BreakUnit;
7use crate::lints::double_comparison::check_double_comparison;
8use crate::lints::double_comparison::ContradictoryComparison;
9use crate::lints::double_comparison::ImpossibleComparison;
10use crate::lints::double_comparison::RedundantComparison;
11use crate::lints::double_comparison::SimplifiableComparison;
12use crate::lints::double_parens::check_double_parens;
13use crate::lints::double_parens::DoubleParens;
14use crate::lints::duplicate_underscore_args::check_duplicate_underscore_args;
15use crate::lints::duplicate_underscore_args::DuplicateUnderscoreArgs;
16use crate::lints::eq_op::check_eq_op;
17use crate::lints::eq_op::BitwiseEqualityOperation;
18use crate::lints::eq_op::DifferenceEqualityOperation;
19use crate::lints::eq_op::DivisionEqualityOperation;
20use crate::lints::eq_op::EqualComparisonOperation;
21use crate::lints::eq_op::LogicalEqualityOperation;
22use crate::lints::eq_op::NotEqualComparisonOperation;
23use crate::lints::erasing_op::check_erasing_operation;
24use crate::lints::erasing_op::ErasingOperation;
25use crate::lints::ifs::collapsible_if::check_collapsible_if;
26use crate::lints::ifs::collapsible_if::CollapsibleIf;
27use crate::lints::ifs::collapsible_if_else::check_collapsible_if_else;
28use crate::lints::ifs::collapsible_if_else::CollapsibleIfElse;
29use crate::lints::ifs::equatable_if_let::check_equatable_if_let;
30use crate::lints::ifs::equatable_if_let::EquatableIfLet;
31use crate::lints::ifs::ifs_same_cond::check_duplicate_if_condition;
32use crate::lints::ifs::ifs_same_cond::DuplicateIfCondition;
33use crate::lints::int_op_one::check_int_op_one;
34use crate::lints::int_op_one::IntegerGreaterEqualMinusOne;
35use crate::lints::int_op_one::IntegerGreaterEqualPlusOne;
36use crate::lints::int_op_one::IntegerLessEqualMinusOne;
37use crate::lints::int_op_one::IntegerLessEqualPlusOne;
38use crate::lints::loops::loop_for_while::check_loop_for_while;
39use crate::lints::loops::loop_for_while::LoopForWhile;
40use crate::lints::loops::loop_match_pop_front::check_loop_match_pop_front;
41use crate::lints::loops::loop_match_pop_front::LoopMatchPopFront;
42use crate::lints::manual::manual_err::check_manual_err;
43use crate::lints::manual::manual_err::ManualErr;
44use crate::lints::manual::manual_expect::check_manual_expect;
45use crate::lints::manual::manual_expect::ManualExpect;
46use crate::lints::manual::manual_expect_err::check_manual_expect_err;
47use crate::lints::manual::manual_expect_err::ManualExpectErr;
48use crate::lints::manual::manual_is::check_manual_is;
49use crate::lints::manual::manual_is::ManualIsErr;
50use crate::lints::manual::manual_is::ManualIsNone;
51use crate::lints::manual::manual_is::ManualIsOk;
52use crate::lints::manual::manual_is::ManualIsSome;
53use crate::lints::manual::manual_ok::check_manual_ok;
54use crate::lints::manual::manual_ok::ManualOk;
55use crate::lints::manual::manual_ok_or::check_manual_ok_or;
56use crate::lints::manual::manual_ok_or::ManualOkOr;
57use crate::lints::manual::manual_unwrap_or_default::check_manual_unwrap_or_default;
58use crate::lints::manual::manual_unwrap_or_default::ManualUnwrapOrDefault;
59use crate::lints::panic::check_panic_usage;
60use crate::lints::panic::PanicInCode;
61use crate::lints::performance::check_inefficient_while_comp;
62use crate::lints::performance::InefficientWhileComparison;
63use crate::lints::redundant_op::check_redundant_operation;
64use crate::lints::redundant_op::RedundantOperation;
65use crate::lints::single_match::check_single_matches;
66use crate::lints::single_match::DestructMatch;
67use crate::lints::single_match::EqualityMatch;
68use cairo_lang_defs::{ids::ModuleItemId, plugin::PluginDiagnostic};
69use cairo_lang_semantic::db::SemanticGroup;
70use cairo_lang_syntax::node::{db::SyntaxGroup, SyntaxNode};
71use itertools::Itertools;
72use std::collections::HashMap;
73use std::sync::LazyLock;
74
75/// Type describing a linter group's rule checking function.
76type CheckingFunction = fn(&dyn SemanticGroup, &ModuleItemId, &mut Vec<PluginDiagnostic>);
77
78/// Enum representing the kind of a linter. Some lint rules might have the same kind.
79#[derive(Debug, PartialEq, Clone, Copy)]
80pub enum CairoLintKind {
81    DestructMatch,
82    MatchForEquality,
83    DoubleComparison,
84    DoubleParens,
85    EquatableIfLet,
86    BreakUnit,
87    BoolComparison,
88    CollapsibleIfElse,
89    CollapsibleIf,
90    DuplicateUnderscoreArgs,
91    LoopMatchPopFront,
92    ManualUnwrapOrDefault,
93    BitwiseForParityCheck,
94    LoopForWhile,
95    Unknown,
96    Panic,
97    ErasingOperation,
98    ManualOkOr,
99    ManualOk,
100    ManualErr,
101    ManualIsSome,
102    ManualIsNone,
103    ManualIsOk,
104    ManualIsErr,
105    ManualExpect,
106    DuplicateIfCondition,
107    ManualExpectErr,
108    IntGePlusOne,
109    IntGeMinOne,
110    IntLePlusOne,
111    IntLeMinOne,
112    ImpossibleComparison,
113    EqualityOperation,
114    Performance,
115    RedundantOperation,
116}
117
118pub trait Lint: Sync + Send {
119    /// A name that is going to be registered by the compiler as an allowed lint to be ignored.
120    /// Some multiple lint rules might have the same allowed name. This way all of the will be ignored with only one allow attribute.
121    fn allowed_name(&self) -> &'static str;
122    /// A predefined message that is going to appear in the compiler's diagnostic output. It should be the same as the one in the lint check function.
123    fn diagnostic_message(&self) -> &'static str;
124    /// The kind of the lint rule. Some lint rules might have the same kind.
125    fn kind(&self) -> CairoLintKind;
126
127    /// Checks if the instance has a fixer.
128    /// By default it return false.
129    fn has_fixer(&self) -> bool {
130        false
131    }
132
133    /// Generates full path to the lint rule. It helps map the Lint struct name to the actual lint rule.
134    fn type_name(&self) -> &'static str {
135        std::any::type_name::<Self>()
136    }
137
138    /// Attempts to generate a fix for this Lint's semantic diagnostic.
139    /// # Arguments
140    ///
141    /// * `db` - A reference to the RootDatabase
142    /// * `diag` - A reference to the SemanticDiagnostic to be fixed
143    ///
144    /// # Returns
145    /// An `Option<(SyntaxNode, String)>` where the `SyntaxNode` represents the node to be
146    /// replaced, and the `String` is the suggested replacement. Returns `None` if no fix
147    /// is available for the given diagnostic.
148    ///
149    /// By default there is no fixing procedure for a Lint.
150    #[expect(unused_variables)]
151    fn fix(&self, db: &dyn SyntaxGroup, node: SyntaxNode) -> Option<(SyntaxNode, String)> {
152        unreachable!("fix() has been called for a lint which has_fixer() returned false")
153    }
154}
155
156/// A group of lint rules.
157///
158/// We want to group lint rules because some lint rules can share an allowed name for compiler or the checking function.
159pub struct LintRuleGroup {
160    /// Collection of `LintRule`s that are directly connected to this group's checking function.
161    lints: Vec<Box<dyn Lint>>,
162    /// A Function which will be fired during linter plugin analysis.
163    /// This one should emit certain diagnostics in order to later identify (and maybe fix) the linting problem.
164    check_function: CheckingFunction,
165}
166
167/// A global Linter context. It contains all the lint rules.
168struct LintContext {
169    lint_groups: Vec<LintRuleGroup>,
170    diagnostic_to_lint_kind_map: HashMap<&'static str, CairoLintKind>,
171}
172
173impl LintContext {
174    /// All of the predefined rules are stored here. If a new rule is added it should be added here as well.
175    fn get_all_lints() -> Vec<LintRuleGroup> {
176        vec![
177            LintRuleGroup {
178                lints: vec![Box::new(DestructMatch), Box::new(EqualityMatch)],
179                check_function: check_single_matches,
180            },
181            LintRuleGroup {
182                lints: vec![Box::new(DoubleParens)],
183                check_function: check_double_parens,
184            },
185            LintRuleGroup {
186                lints: vec![
187                    Box::new(ImpossibleComparison),
188                    Box::new(SimplifiableComparison),
189                    Box::new(RedundantComparison),
190                    Box::new(ContradictoryComparison),
191                ],
192                check_function: check_double_comparison,
193            },
194            LintRuleGroup {
195                lints: vec![Box::new(EquatableIfLet)],
196                check_function: check_equatable_if_let,
197            },
198            LintRuleGroup {
199                lints: vec![Box::new(BreakUnit)],
200                check_function: check_break,
201            },
202            LintRuleGroup {
203                lints: vec![Box::new(BoolComparison)],
204                check_function: check_bool_comparison,
205            },
206            LintRuleGroup {
207                lints: vec![Box::new(CollapsibleIfElse)],
208                check_function: check_collapsible_if_else,
209            },
210            LintRuleGroup {
211                lints: vec![Box::new(CollapsibleIf)],
212                check_function: check_collapsible_if,
213            },
214            LintRuleGroup {
215                lints: vec![Box::new(DuplicateUnderscoreArgs)],
216                check_function: check_duplicate_underscore_args,
217            },
218            LintRuleGroup {
219                lints: vec![Box::new(LoopMatchPopFront)],
220                check_function: check_loop_match_pop_front,
221            },
222            LintRuleGroup {
223                lints: vec![Box::new(ManualUnwrapOrDefault)],
224                check_function: check_manual_unwrap_or_default,
225            },
226            LintRuleGroup {
227                lints: vec![Box::new(BitwiseForParity)],
228                check_function: check_bitwise_for_parity,
229            },
230            LintRuleGroup {
231                lints: vec![Box::new(LoopForWhile)],
232                check_function: check_loop_for_while,
233            },
234            LintRuleGroup {
235                lints: vec![Box::new(PanicInCode)],
236                check_function: check_panic_usage,
237            },
238            LintRuleGroup {
239                lints: vec![Box::new(ErasingOperation)],
240                check_function: check_erasing_operation,
241            },
242            LintRuleGroup {
243                lints: vec![Box::new(ManualOkOr)],
244                check_function: check_manual_ok_or,
245            },
246            LintRuleGroup {
247                lints: vec![Box::new(ManualOk)],
248                check_function: check_manual_ok,
249            },
250            LintRuleGroup {
251                lints: vec![Box::new(ManualErr)],
252                check_function: check_manual_err,
253            },
254            LintRuleGroup {
255                lints: vec![
256                    Box::new(ManualIsSome),
257                    Box::new(ManualIsNone),
258                    Box::new(ManualIsOk),
259                    Box::new(ManualIsErr),
260                ],
261                check_function: check_manual_is,
262            },
263            LintRuleGroup {
264                lints: vec![Box::new(ManualExpect)],
265                check_function: check_manual_expect,
266            },
267            LintRuleGroup {
268                lints: vec![Box::new(DuplicateIfCondition)],
269                check_function: check_duplicate_if_condition,
270            },
271            LintRuleGroup {
272                lints: vec![Box::new(ManualExpectErr)],
273                check_function: check_manual_expect_err,
274            },
275            LintRuleGroup {
276                lints: vec![
277                    Box::new(IntegerGreaterEqualPlusOne),
278                    Box::new(IntegerGreaterEqualMinusOne),
279                    Box::new(IntegerLessEqualPlusOne),
280                    Box::new(IntegerLessEqualMinusOne),
281                ],
282                check_function: check_int_op_one,
283            },
284            LintRuleGroup {
285                lints: vec![
286                    Box::new(DivisionEqualityOperation),
287                    Box::new(EqualComparisonOperation),
288                    Box::new(NotEqualComparisonOperation),
289                    Box::new(DifferenceEqualityOperation),
290                    Box::new(BitwiseEqualityOperation),
291                    Box::new(LogicalEqualityOperation),
292                ],
293                check_function: check_eq_op,
294            },
295            LintRuleGroup {
296                lints: vec![Box::new(InefficientWhileComparison)],
297                check_function: check_inefficient_while_comp,
298            },
299            LintRuleGroup {
300                lints: vec![Box::new(RedundantOperation)],
301                check_function: check_redundant_operation,
302            },
303        ]
304    }
305
306    fn precompute_diagnostic_to_lint_kind_map(mut self) -> Self {
307        let mut result: HashMap<&'static str, CairoLintKind> = HashMap::default();
308        for rule_group in self.lint_groups.iter() {
309            for rule in rule_group.lints.iter() {
310                result.insert(rule.diagnostic_message(), rule.kind());
311            }
312        }
313        self.diagnostic_to_lint_kind_map = result;
314        self
315    }
316
317    fn new() -> Self {
318        let new = Self {
319            lint_groups: Self::get_all_lints(),
320            diagnostic_to_lint_kind_map: Default::default(),
321        };
322        new.precompute_diagnostic_to_lint_kind_map()
323    }
324
325    fn get_lint_type_from_diagnostic_message(&self, message: &str) -> CairoLintKind {
326        self.diagnostic_to_lint_kind_map
327            .get(message)
328            .copied()
329            .unwrap_or(CairoLintKind::Unknown)
330    }
331}
332
333/// A singleton instance of the `LintContext`. It should be the only instance of the `LintContext`.
334static LINT_CONTEXT: LazyLock<LintContext> = LazyLock::new(LintContext::new);
335
336/// Get the lint type based on the diagnostic message.
337/// If the diagnostic message doesn't match any of the rules, it returns `CairoLintKind::Unknown`.
338pub fn get_lint_type_from_diagnostic_message(message: &str) -> CairoLintKind {
339    LINT_CONTEXT.get_lint_type_from_diagnostic_message(message)
340}
341
342/// Get the fixing function based on the diagnostic message.
343/// For some of the rules there is no fixing function, so it returns `None`.
344pub fn get_fix_for_diagnostic_message(
345    db: &dyn SyntaxGroup,
346    node: SyntaxNode,
347    message: &str,
348) -> Option<(SyntaxNode, String)> {
349    LINT_CONTEXT
350        .lint_groups
351        .iter()
352        .flat_map(|rule_group| &rule_group.lints)
353        .find(|rule| rule.diagnostic_message() == message && rule.has_fixer())
354        .and_then(|rule| rule.fix(db, node))
355}
356
357/// Get all the unique allowed names for the lint rule groups.
358pub fn get_unique_allowed_names() -> Vec<&'static str> {
359    LINT_CONTEXT
360        .lint_groups
361        .iter()
362        .flat_map(|rule_group| rule_group.lints.iter().map(|rule| rule.allowed_name()))
363        .collect()
364}
365
366/// Get all the checking functions that exist for each `LintRuleGroup`.
367pub fn get_all_checking_functions() -> impl Iterator<Item = &'static CheckingFunction> {
368    LINT_CONTEXT
369        .lint_groups
370        .iter()
371        .unique_by(|rule| rule.check_function)
372        .map(|rule_group| &rule_group.check_function)
373}
374
375/// Get lint name based on the diagnostic message.
376pub fn get_name_for_diagnostic_message(message: &str) -> Option<&'static str> {
377    LINT_CONTEXT
378        .lint_groups
379        .iter()
380        .flat_map(|group| group.lints.iter())
381        .find(|rule| rule.diagnostic_message() == message)
382        .map(|rule| rule.allowed_name())
383}
384
385#[allow(clippy::borrowed_box)]
386/// Finds the lint by it's struct's name.
387/// By struct name we mean the last part of the path of the lint rule.
388/// For example, for `crate::lints::bool_comparison::BoolComparison` the struct name is `BoolComparison`.
389pub fn find_lint_by_struct_name(name: &str) -> Option<&Box<dyn Lint>> {
390    LINT_CONTEXT
391        .lint_groups
392        .iter()
393        .flat_map(|group| group.lints.iter())
394        .find(|rule| rule.type_name().split("::").last().unwrap() == name)
395}