Skip to main content

cairo_lint/
context.rs

1use crate::fixer::InternalFix;
2use crate::lints::bitwise_for_parity_check::BitwiseForParity;
3use crate::lints::bitwise_for_parity_check::check_bitwise_for_parity;
4use crate::lints::bool_comparison::BoolComparison;
5use crate::lints::bool_comparison::check_bool_comparison;
6use crate::lints::breaks::BreakUnit;
7use crate::lints::breaks::check_break;
8use crate::lints::clone_on_copy::{CloneOnCopy, check_clone_on_copy};
9use crate::lints::collapsible_match::CollapsibleMatch;
10use crate::lints::collapsible_match::check_collapsible_match;
11use crate::lints::double_comparison::ContradictoryComparison;
12use crate::lints::double_comparison::ImpossibleComparison;
13use crate::lints::double_comparison::RedundantComparison;
14use crate::lints::double_comparison::SimplifiableComparison;
15use crate::lints::double_comparison::check_double_comparison;
16use crate::lints::double_parens::DoubleParens;
17use crate::lints::double_parens::check_double_parens;
18use crate::lints::duplicate_underscore_args::DuplicateUnderscoreArgs;
19use crate::lints::duplicate_underscore_args::check_duplicate_underscore_args;
20use crate::lints::empty_enum_brackets_variant::EmptyEnumBracketsVariant;
21use crate::lints::empty_enum_brackets_variant::check_empty_enum_brackets_variant;
22use crate::lints::enum_variant_names::EnumVariantNames;
23use crate::lints::enum_variant_names::check_enum_variant_names;
24use crate::lints::eq_op::BitwiseEqualityOperation;
25use crate::lints::eq_op::DifferenceEqualityOperation;
26use crate::lints::eq_op::DivisionEqualityOperation;
27use crate::lints::eq_op::EqualComparisonOperation;
28use crate::lints::eq_op::LogicalEqualityOperation;
29use crate::lints::eq_op::NotEqualComparisonOperation;
30use crate::lints::eq_op::check_eq_op;
31use crate::lints::erasing_op::ErasingOperation;
32use crate::lints::erasing_op::check_erasing_operation;
33use crate::lints::ifs::collapsible_if::CollapsibleIf;
34use crate::lints::ifs::collapsible_if::check_collapsible_if;
35use crate::lints::ifs::collapsible_if_else::CollapsibleIfElse;
36use crate::lints::ifs::collapsible_if_else::check_collapsible_if_else;
37use crate::lints::ifs::equatable_if_let::EquatableIfLet;
38use crate::lints::ifs::equatable_if_let::check_equatable_if_let;
39use crate::lints::ifs::ifs_same_cond::DuplicateIfCondition;
40use crate::lints::ifs::ifs_same_cond::check_duplicate_if_condition;
41use crate::lints::int_op_one::IntegerGreaterEqualMinusOne;
42use crate::lints::int_op_one::IntegerGreaterEqualPlusOne;
43use crate::lints::int_op_one::IntegerLessEqualMinusOne;
44use crate::lints::int_op_one::IntegerLessEqualPlusOne;
45use crate::lints::int_op_one::check_int_op_one;
46use crate::lints::loops::loop_for_while::LoopForWhile;
47use crate::lints::loops::loop_for_while::check_loop_for_while;
48use crate::lints::loops::loop_match_pop_front::LoopMatchPopFront;
49use crate::lints::loops::loop_match_pop_front::check_loop_match_pop_front;
50use crate::lints::manual::manual_assert::ManualAssert;
51use crate::lints::manual::manual_assert::check_manual_assert;
52use crate::lints::manual::manual_err::ManualErr;
53use crate::lints::manual::manual_err::check_manual_err;
54use crate::lints::manual::manual_expect::ManualExpect;
55use crate::lints::manual::manual_expect::check_manual_expect;
56use crate::lints::manual::manual_expect_err::ManualExpectErr;
57use crate::lints::manual::manual_expect_err::check_manual_expect_err;
58use crate::lints::manual::manual_is::ManualIsErr;
59use crate::lints::manual::manual_is::ManualIsNone;
60use crate::lints::manual::manual_is::ManualIsOk;
61use crate::lints::manual::manual_is::ManualIsSome;
62use crate::lints::manual::manual_is::check_manual_is;
63use crate::lints::manual::manual_is_empty::{ManualIsEmpty, check_manual_is_empty};
64use crate::lints::manual::manual_ok::ManualOk;
65use crate::lints::manual::manual_ok::check_manual_ok;
66use crate::lints::manual::manual_ok_or::ManualOkOr;
67use crate::lints::manual::manual_ok_or::check_manual_ok_or;
68use crate::lints::manual::manual_unwrap_or::ManualUnwrapOr;
69use crate::lints::manual::manual_unwrap_or::check_manual_unwrap_or;
70use crate::lints::manual::manual_unwrap_or_default::ManualUnwrapOrDefault;
71use crate::lints::manual::manual_unwrap_or_default::check_manual_unwrap_or_default;
72use crate::lints::manual::manual_unwrap_or_else::ManualUnwrapOrElse;
73use crate::lints::manual::manual_unwrap_or_else::check_manual_unwrap_or_else;
74use crate::lints::panic::PanicInCode;
75use crate::lints::panic::check_panic_usage;
76use crate::lints::performance::inefficient_unwrap_or::InefficientUnwrapOr;
77use crate::lints::performance::inefficient_unwrap_or::check_inefficient_unwrap_or;
78use crate::lints::performance::inefficient_while_comp::InefficientWhileComparison;
79use crate::lints::performance::inefficient_while_comp::check_inefficient_while_comp;
80use crate::lints::redundant_brackets_in_enum_call::RedundantBracketsInEnumCall;
81use crate::lints::redundant_brackets_in_enum_call::check_redundant_brackets_in_enum_call;
82use crate::lints::redundant_into::RedundantInto;
83use crate::lints::redundant_into::check_redundant_into;
84use crate::lints::redundant_op::RedundantOperation;
85use crate::lints::redundant_op::check_redundant_operation;
86use crate::lints::single_match::DestructMatch;
87use crate::lints::single_match::EqualityMatch;
88use crate::lints::single_match::check_single_matches;
89use crate::lints::unit_return_type::UnitReturnType;
90use crate::lints::unit_return_type::check_unit_return_type;
91use crate::lints::unwrap_syscall::UnwrapSyscall;
92use crate::lints::unwrap_syscall::check_unwrap_syscall;
93use cairo_lang_defs::{ids::ModuleItemId, plugin::PluginDiagnostic};
94use cairo_lang_syntax::node::SyntaxNode;
95use itertools::Itertools;
96use salsa::Database;
97use std::collections::HashMap;
98use std::sync::LazyLock;
99use std::vec;
100
101/// Type describing a linter group's rule checking function.
102type CheckingFunction =
103    for<'db> fn(&'db dyn Database, &ModuleItemId<'db>, &mut Vec<PluginDiagnostic<'db>>);
104
105/// Enum representing the kind of a linter. Some lint rules might have the same kind.
106#[derive(Debug, PartialEq, Clone, Copy)]
107pub enum CairoLintKind {
108    DestructMatch,
109    MatchForEquality,
110    DoubleComparison,
111    DoubleParens,
112    EquatableIfLet,
113    BreakUnit,
114    BoolComparison,
115    CollapsibleIfElse,
116    CollapsibleIf,
117    CollapsibleMatch,
118    DuplicateUnderscoreArgs,
119    LoopMatchPopFront,
120    ManualUnwrapOrDefault,
121    BitwiseForParityCheck,
122    LoopForWhile,
123    Unknown,
124    Panic,
125    ErasingOperation,
126    ManualOkOr,
127    ManualOk,
128    ManualErr,
129    ManualIsSome,
130    ManualIsNone,
131    ManualIsOk,
132    ManualIsErr,
133    ManualIsEmpty,
134    ManualExpect,
135    ManualAssert,
136    DuplicateIfCondition,
137    ManualExpectErr,
138    IntGePlusOne,
139    IntGeMinOne,
140    IntLePlusOne,
141    IntLeMinOne,
142    ImpossibleComparison,
143    EqualityOperation,
144    Performance,
145    RedundantOperation,
146    EnumVariantNames,
147    CloneOnCopy,
148    EnumEmptyVariantBrackets,
149    ManualUnwrapOr,
150    UnitReturnType,
151    UnwrapSyscall,
152    RedundantInto,
153    InefficientUnwrapOr,
154    ManualUnwrapOrElse,
155}
156
157pub trait Lint: Sync + Send {
158    /// A name that is going to be registered by the compiler as an allowed lint to be ignored.
159    /// Some multiple lint rules might have the same allowed name. This way all of the will be ignored with only one allow attribute.
160    fn allowed_name(&self) -> &'static str;
161    /// 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.
162    fn diagnostic_message(&self) -> &'static str;
163    /// The kind of the lint rule. Some lint rules might have the same kind.
164    fn kind(&self) -> CairoLintKind;
165
166    /// Checks if the lint rule is enabled.
167    /// By default all of the rules are enabled.
168    fn is_enabled(&self) -> bool {
169        true
170    }
171
172    /// Checks if the instance has a fixer.
173    /// By default it return false.
174    fn has_fixer(&self) -> bool {
175        false
176    }
177
178    /// Generates full path to the lint rule. It helps map the Lint struct name to the actual lint rule.
179    fn type_name(&self) -> &'static str {
180        std::any::type_name::<Self>()
181    }
182
183    /// Attempts to generate a fix for this Lint's semantic diagnostic.
184    /// # Arguments
185    ///
186    /// * `db` - A reference to the `dyn LinterGroup`
187    /// * `diag` - A reference to the SemanticDiagnostic to be fixed
188    ///
189    /// # Returns
190    /// An `Option<Vec<InternalFix>>`. Returns `None` if no fix
191    /// is available for the given diagnostic.
192    ///
193    /// By default there is no fixing procedure for a Lint.
194    #[expect(unused_variables)]
195    fn fix<'db>(&self, db: &'db dyn Database, node: SyntaxNode<'db>) -> Option<InternalFix<'db>> {
196        unreachable!("fix() has been called for a lint which has_fixer() returned false")
197    }
198
199    /// A short message describing the fix that will be applied.
200    fn fix_message(&self) -> Option<&'static str> {
201        unreachable!(
202            "A fix message has been requested for a lint which has_fixer() returned false for."
203        )
204    }
205}
206
207/// A group of lint rules.
208///
209/// We want to group lint rules because some lint rules can share an allowed name for compiler or the checking function.
210pub struct LintRuleGroup {
211    /// Collection of `LintRule`s that are directly connected to this group's checking function.
212    lints: Vec<Box<dyn Lint>>,
213    /// A Function which will be fired during linter plugin analysis.
214    /// This one should emit certain diagnostics in order to later identify (and maybe fix) the linting problem.
215    check_function: CheckingFunction,
216}
217
218/// A global Linter context. It contains all the lint rules.
219struct LintContext {
220    lint_groups: Vec<LintRuleGroup>,
221    diagnostic_to_lint_kind_map: HashMap<&'static str, CairoLintKind>,
222}
223
224impl LintContext {
225    /// All of the predefined rules are stored here. If a new rule is added it should be added here as well.
226    fn get_all_lints() -> Vec<LintRuleGroup> {
227        vec![
228            LintRuleGroup {
229                lints: vec![Box::new(DestructMatch), Box::new(EqualityMatch)],
230                check_function: check_single_matches,
231            },
232            LintRuleGroup {
233                lints: vec![Box::new(DoubleParens)],
234                check_function: check_double_parens,
235            },
236            LintRuleGroup {
237                lints: vec![
238                    Box::new(ImpossibleComparison),
239                    Box::new(SimplifiableComparison),
240                    Box::new(RedundantComparison),
241                    Box::new(ContradictoryComparison),
242                ],
243                check_function: check_double_comparison,
244            },
245            LintRuleGroup {
246                lints: vec![Box::new(EquatableIfLet)],
247                check_function: check_equatable_if_let,
248            },
249            LintRuleGroup {
250                lints: vec![Box::new(BreakUnit)],
251                check_function: check_break,
252            },
253            LintRuleGroup {
254                lints: vec![Box::new(BoolComparison)],
255                check_function: check_bool_comparison,
256            },
257            LintRuleGroup {
258                lints: vec![Box::new(CollapsibleIfElse)],
259                check_function: check_collapsible_if_else,
260            },
261            LintRuleGroup {
262                lints: vec![Box::new(CollapsibleIf)],
263                check_function: check_collapsible_if,
264            },
265            LintRuleGroup {
266                lints: vec![Box::new(DuplicateUnderscoreArgs)],
267                check_function: check_duplicate_underscore_args,
268            },
269            LintRuleGroup {
270                lints: vec![Box::new(LoopMatchPopFront)],
271                check_function: check_loop_match_pop_front,
272            },
273            LintRuleGroup {
274                lints: vec![Box::new(ManualUnwrapOrDefault)],
275                check_function: check_manual_unwrap_or_default,
276            },
277            LintRuleGroup {
278                lints: vec![Box::new(BitwiseForParity)],
279                check_function: check_bitwise_for_parity,
280            },
281            LintRuleGroup {
282                lints: vec![Box::new(LoopForWhile)],
283                check_function: check_loop_for_while,
284            },
285            LintRuleGroup {
286                lints: vec![Box::new(PanicInCode)],
287                check_function: check_panic_usage,
288            },
289            LintRuleGroup {
290                lints: vec![Box::new(ErasingOperation)],
291                check_function: check_erasing_operation,
292            },
293            LintRuleGroup {
294                lints: vec![Box::new(ManualOkOr)],
295                check_function: check_manual_ok_or,
296            },
297            LintRuleGroup {
298                lints: vec![Box::new(ManualIsEmpty)],
299                check_function: check_manual_is_empty,
300            },
301            LintRuleGroup {
302                lints: vec![Box::new(ManualOk)],
303                check_function: check_manual_ok,
304            },
305            LintRuleGroup {
306                lints: vec![Box::new(ManualErr)],
307                check_function: check_manual_err,
308            },
309            LintRuleGroup {
310                lints: vec![
311                    Box::new(ManualIsSome),
312                    Box::new(ManualIsNone),
313                    Box::new(ManualIsOk),
314                    Box::new(ManualIsErr),
315                ],
316                check_function: check_manual_is,
317            },
318            LintRuleGroup {
319                lints: vec![Box::new(ManualExpect)],
320                check_function: check_manual_expect,
321            },
322            LintRuleGroup {
323                lints: vec![Box::new(DuplicateIfCondition)],
324                check_function: check_duplicate_if_condition,
325            },
326            LintRuleGroup {
327                lints: vec![Box::new(ManualExpectErr)],
328                check_function: check_manual_expect_err,
329            },
330            LintRuleGroup {
331                lints: vec![
332                    Box::new(IntegerGreaterEqualPlusOne),
333                    Box::new(IntegerGreaterEqualMinusOne),
334                    Box::new(IntegerLessEqualPlusOne),
335                    Box::new(IntegerLessEqualMinusOne),
336                ],
337                check_function: check_int_op_one,
338            },
339            LintRuleGroup {
340                lints: vec![
341                    Box::new(DivisionEqualityOperation),
342                    Box::new(EqualComparisonOperation),
343                    Box::new(NotEqualComparisonOperation),
344                    Box::new(DifferenceEqualityOperation),
345                    Box::new(BitwiseEqualityOperation),
346                    Box::new(LogicalEqualityOperation),
347                ],
348                check_function: check_eq_op,
349            },
350            LintRuleGroup {
351                lints: vec![Box::new(InefficientWhileComparison)],
352                check_function: check_inefficient_while_comp,
353            },
354            LintRuleGroup {
355                lints: vec![Box::new(RedundantOperation)],
356                check_function: check_redundant_operation,
357            },
358            LintRuleGroup {
359                lints: vec![Box::new(EnumVariantNames)],
360                check_function: check_enum_variant_names,
361            },
362            LintRuleGroup {
363                lints: vec![Box::new(CloneOnCopy)],
364                check_function: check_clone_on_copy,
365            },
366            LintRuleGroup {
367                lints: vec![Box::new(EmptyEnumBracketsVariant)],
368                check_function: check_empty_enum_brackets_variant,
369            },
370            LintRuleGroup {
371                lints: vec![Box::new(ManualAssert)],
372                check_function: check_manual_assert,
373            },
374            LintRuleGroup {
375                lints: vec![Box::new(RedundantBracketsInEnumCall)],
376                check_function: check_redundant_brackets_in_enum_call,
377            },
378            LintRuleGroup {
379                lints: vec![Box::new(ManualUnwrapOr)],
380                check_function: check_manual_unwrap_or,
381            },
382            LintRuleGroup {
383                lints: vec![Box::new(UnitReturnType)],
384                check_function: check_unit_return_type,
385            },
386            LintRuleGroup {
387                lints: vec![Box::new(UnwrapSyscall)],
388                check_function: check_unwrap_syscall,
389            },
390            LintRuleGroup {
391                lints: vec![Box::new(RedundantInto)],
392                check_function: check_redundant_into,
393            },
394            LintRuleGroup {
395                lints: vec![Box::new(CollapsibleMatch)],
396                check_function: check_collapsible_match,
397            },
398            LintRuleGroup {
399                lints: vec![Box::new(InefficientUnwrapOr)],
400                check_function: check_inefficient_unwrap_or,
401            },
402            LintRuleGroup {
403                lints: vec![Box::new(ManualUnwrapOrElse)],
404                check_function: check_manual_unwrap_or_else,
405            },
406        ]
407    }
408
409    fn precompute_diagnostic_to_lint_kind_map(mut self) -> Self {
410        let mut result: HashMap<&'static str, CairoLintKind> = HashMap::default();
411        for rule_group in self.lint_groups.iter() {
412            for rule in rule_group.lints.iter() {
413                result.insert(rule.diagnostic_message(), rule.kind());
414            }
415        }
416        self.diagnostic_to_lint_kind_map = result;
417        self
418    }
419
420    fn new() -> Self {
421        let new = Self {
422            lint_groups: Self::get_all_lints(),
423            diagnostic_to_lint_kind_map: Default::default(),
424        };
425        new.precompute_diagnostic_to_lint_kind_map()
426    }
427
428    fn get_lint_type_from_diagnostic_message(&self, message: &str) -> CairoLintKind {
429        self.diagnostic_to_lint_kind_map
430            .get(message)
431            .copied()
432            .unwrap_or(CairoLintKind::Unknown)
433    }
434}
435
436/// A singleton instance of the `LintContext`. It should be the only instance of the `LintContext`.
437static LINT_CONTEXT: LazyLock<LintContext> = LazyLock::new(LintContext::new);
438
439/// Get the lint type based on the diagnostic message.
440/// If the diagnostic message doesn't match any of the rules, it returns `CairoLintKind::Unknown`.
441pub fn get_lint_type_from_diagnostic_message(message: &str) -> CairoLintKind {
442    LINT_CONTEXT.get_lint_type_from_diagnostic_message(message)
443}
444
445/// Get the fixing function based on the diagnostic message.
446/// For some of the rules there is no fixing function, so it returns `None`.
447pub fn get_fix_for_diagnostic_message<'db>(
448    db: &'db dyn Database,
449    node: SyntaxNode<'db>,
450    message: &str,
451) -> Option<InternalFix<'db>> {
452    LINT_CONTEXT
453        .lint_groups
454        .iter()
455        .flat_map(|rule_group| &rule_group.lints)
456        .find(|rule| rule.diagnostic_message() == message && rule.has_fixer())
457        .and_then(|rule| rule.fix(db, node))
458}
459
460/// Get all the unique allowed names for the lint rule groups.
461pub fn get_unique_allowed_names() -> Vec<&'static str> {
462    LINT_CONTEXT
463        .lint_groups
464        .iter()
465        .flat_map(|rule_group| rule_group.lints.iter().map(|rule| rule.allowed_name()))
466        .collect()
467}
468
469/// Get all the checking functions that exist for each `LintRuleGroup`.
470pub fn get_all_checking_functions() -> impl Iterator<Item = &'static CheckingFunction> {
471    LINT_CONTEXT
472        .lint_groups
473        .iter()
474        .unique_by(|rule| rule.check_function)
475        .map(|rule_group| &rule_group.check_function)
476}
477
478/// Get lint name based on the diagnostic message.
479pub fn get_name_for_diagnostic_message(message: &str) -> Option<&'static str> {
480    LINT_CONTEXT
481        .lint_groups
482        .iter()
483        .flat_map(|group| group.lints.iter())
484        .find(|rule| rule.diagnostic_message() == message)
485        .map(|rule| rule.allowed_name())
486}
487
488/// Checks if the lint related to the diagnostic message is enabled by default.
489pub fn is_lint_enabled_by_default(message: &str) -> Option<bool> {
490    LINT_CONTEXT
491        .lint_groups
492        .iter()
493        .flat_map(|group| group.lints.iter())
494        .find(|rule| rule.diagnostic_message() == message)
495        .map(|rule| rule.is_enabled())
496}
497
498#[allow(clippy::borrowed_box)]
499/// Finds the lint by it's struct's name.
500/// By struct name we mean the last part of the path of the lint rule.
501/// For example, for `crate::lints::bool_comparison::BoolComparison` the struct name is `BoolComparison`.
502pub fn find_lint_by_struct_name(name: &str) -> Option<&Box<dyn Lint>> {
503    LINT_CONTEXT
504        .lint_groups
505        .iter()
506        .flat_map(|group| group.lints.iter())
507        .find(|rule| rule.type_name().split("::").last().unwrap() == name)
508}
509
510/// Get lint name based on the fix message.
511/// Only checks lints that have a fixer.
512pub fn get_name_for_fix_message(message: &str) -> Option<&'static str> {
513    LINT_CONTEXT
514        .lint_groups
515        .iter()
516        .flat_map(|group| group.lints.iter())
517        .find(|rule| {
518            rule.has_fixer()
519                && rule.fix_message().is_some()
520                && rule.fix_message().unwrap() == message
521        })
522        .map(|rule| rule.allowed_name())
523}
524
525/// Returns `fix_message` for all lints that support fixes.
526pub fn get_all_fix_messages() -> Vec<Option<&'static str>> {
527    LINT_CONTEXT
528        .lint_groups
529        .iter()
530        .flat_map(|rule_group| {
531            rule_group
532                .lints
533                .iter()
534                .filter(|rule| rule.has_fixer())
535                .map(|rule| rule.fix_message())
536        })
537        .collect()
538}