Skip to main content

cairo_lint/lints/
breaks.rs

1use cairo_lang_defs::ids::ModuleItemId;
2use cairo_lang_defs::plugin::PluginDiagnostic;
3use cairo_lang_diagnostics::Severity;
4use cairo_lang_semantic::{Arenas, StatementBreak};
5
6use cairo_lang_syntax::node::{SyntaxNode, TypedStablePtr};
7use if_chain::if_chain;
8
9use crate::context::{CairoLintKind, Lint};
10
11use crate::fixer::InternalFix;
12use crate::queries::{get_all_break_statements, get_all_function_bodies};
13use salsa::Database;
14
15pub struct BreakUnit;
16
17/// ## What it does
18///
19/// Checks for `break ();` statements and suggests removing the parentheses.
20///
21/// ## Example
22///
23/// ```cairo
24/// fn main() {
25///     loop {
26///         break ();
27///     }
28/// }
29/// ```
30///
31/// Can be fixed by removing the parentheses:
32///
33/// ```cairo
34/// fn main() {
35///     loop {
36///         break;
37///     }
38/// }
39/// ```
40impl Lint for BreakUnit {
41    fn allowed_name(&self) -> &'static str {
42        "break_unit"
43    }
44
45    fn diagnostic_message(&self) -> &'static str {
46        "unnecessary double parentheses found after break. Consider removing them."
47    }
48
49    fn kind(&self) -> CairoLintKind {
50        CairoLintKind::BreakUnit
51    }
52
53    fn has_fixer(&self) -> bool {
54        true
55    }
56
57    fn fix<'db>(&self, db: &'db dyn Database, node: SyntaxNode<'db>) -> Option<InternalFix<'db>> {
58        fix_break_unit(db, node)
59    }
60
61    fn fix_message(&self) -> Option<&'static str> {
62        Some("Remove unnecessary parentheses from break")
63    }
64}
65
66#[tracing::instrument(skip_all, level = "trace")]
67pub fn check_break<'db>(
68    db: &'db dyn Database,
69    item: &ModuleItemId<'db>,
70    diagnostics: &mut Vec<PluginDiagnostic<'db>>,
71) {
72    let function_bodies = get_all_function_bodies(db, item);
73    for function_body in function_bodies {
74        let break_exprs = get_all_break_statements(function_body);
75        for break_expr in break_exprs.iter() {
76            check_single_break(db, break_expr, &function_body.arenas, diagnostics)
77        }
78    }
79}
80
81fn check_single_break<'db>(
82    db: &'db dyn Database,
83    break_expr: &StatementBreak<'db>,
84    arenas: &Arenas<'db>,
85    diagnostics: &mut Vec<PluginDiagnostic<'db>>,
86) {
87    if_chain! {
88        if let Some(expr) = break_expr.expr_option;
89        if arenas.exprs[expr].ty().is_unit(db);
90        then {
91            diagnostics.push(PluginDiagnostic {
92                stable_ptr: break_expr.stable_ptr.untyped(),
93                message: BreakUnit.diagnostic_message().to_string(),
94                severity: Severity::Warning,
95                inner_span: None,
96                error_code: None,
97            });
98        }
99    }
100}
101
102/// Rewrites `break ();` as `break;` given the node text contains it.
103#[tracing::instrument(skip_all, level = "trace")]
104pub fn fix_break_unit<'db>(
105    db: &'db dyn Database,
106    node: SyntaxNode<'db>,
107) -> Option<InternalFix<'db>> {
108    Some(InternalFix {
109        node,
110        suggestion: node.get_text(db).replace("break ();", "break;").to_string(),
111        description: BreakUnit.fix_message().unwrap().to_string(),
112        import_addition_paths: None,
113    })
114}