diff --git a/crates/cairo-lint-core/src/lib.rs b/crates/cairo-lint-core/src/lib.rs index 4d94e858..9a87dbbf 100644 --- a/crates/cairo-lint-core/src/lib.rs +++ b/crates/cairo-lint-core/src/lib.rs @@ -1,3 +1,5 @@ +#![feature(let_chains)] pub mod db; pub mod fix; +pub mod lints; pub mod plugin; diff --git a/crates/cairo-lint-core/src/lints/mod.rs b/crates/cairo-lint-core/src/lints/mod.rs new file mode 100644 index 00000000..f32c03e9 --- /dev/null +++ b/crates/cairo-lint-core/src/lints/mod.rs @@ -0,0 +1 @@ +pub mod single_match; diff --git a/crates/cairo-lint-core/src/lints/single_match.rs b/crates/cairo-lint-core/src/lints/single_match.rs new file mode 100644 index 00000000..b1392c30 --- /dev/null +++ b/crates/cairo-lint-core/src/lints/single_match.rs @@ -0,0 +1,70 @@ +use cairo_lang_defs::plugin::PluginDiagnostic; +use cairo_lang_diagnostics::Severity; +use cairo_lang_syntax::node::ast::{Expr, ExprBlock, ExprListParenthesized, ExprMatch, Pattern, Statement}; +use cairo_lang_syntax::node::db::SyntaxGroup; +use cairo_lang_syntax::node::{TypedStablePtr, TypedSyntaxNode}; + +pub const DESTRUCT_MATCH: &str = + "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`"; +pub const MATCH_FOR_EQUALITY: &str = "you seem to be trying to use `match` for an equality check. Consider using `if`"; + +fn tuple_expr_in_block_expr( + db: &dyn SyntaxGroup, + block_expr: &ExprBlock, + is_single_armed: &mut bool, +) -> Option { + let statements = block_expr.statements(db).elements(db); + if statements.is_empty() { + *is_single_armed = true; + } + if statements.len() == 1 + && let Statement::Expr(statement_expr) = &statements[0] + && let Expr::Tuple(tuple_expr) = statement_expr.expr(db) + { + Some(tuple_expr) + } else { + None + } +} +pub fn check_destruct_match(db: &dyn SyntaxGroup, match_expr: &ExprMatch, diagnostics: &mut Vec) { + let arms = match_expr.arms(db).elements(db); + let mut is_single_armed = false; + let mut is_destructuring = false; + if arms.len() == 2 { + for arm in arms { + let patterns = arm.patterns(db).elements(db); + match &patterns[0] { + Pattern::Underscore(_) => { + let tuple_expr = match arm.expression(db) { + Expr::Block(block_expr) => tuple_expr_in_block_expr(db, &block_expr, &mut is_single_armed), + Expr::Tuple(tuple_expr) => Some(tuple_expr), + _ => None, + }; + is_single_armed = + tuple_expr.is_some_and(|list| list.expressions(db).elements(db).is_empty()) || is_single_armed; + } + + Pattern::Enum(pat) => { + is_destructuring = !pat.pattern(db).as_syntax_node().get_text(db).is_empty(); + } + Pattern::Struct(pat) => { + is_destructuring = !pat.as_syntax_node().get_text(db).is_empty(); + } + _ => (), + }; + } + }; + match (is_single_armed, is_destructuring) { + (true, false) => diagnostics.push(PluginDiagnostic { + stable_ptr: match_expr.stable_ptr().untyped(), + message: MATCH_FOR_EQUALITY.to_string(), + severity: Severity::Warning, + }), + (true, true) => diagnostics.push(PluginDiagnostic { + stable_ptr: match_expr.stable_ptr().untyped(), + message: DESTRUCT_MATCH.to_string(), + severity: Severity::Warning, + }), + (_, _) => (), + } +} diff --git a/crates/cairo-lint-core/src/plugin.rs b/crates/cairo-lint-core/src/plugin.rs index bde411f5..bce88e29 100644 --- a/crates/cairo-lint-core/src/plugin.rs +++ b/crates/cairo-lint-core/src/plugin.rs @@ -1,14 +1,12 @@ -use std::ops::Deref; - use cairo_lang_defs::ids::{ModuleId, ModuleItemId}; use cairo_lang_defs::plugin::PluginDiagnostic; -use cairo_lang_diagnostics::Severity; use cairo_lang_semantic::db::SemanticGroup; use cairo_lang_semantic::plugin::{AnalyzerPlugin, PluginSuite}; -use cairo_lang_syntax::node::ast::{Expr, ExprMatch, Pattern, Statement}; -use cairo_lang_syntax::node::db::SyntaxGroup; +use cairo_lang_syntax::node::ast::ExprMatch; use cairo_lang_syntax::node::kind::SyntaxKind; -use cairo_lang_syntax::node::{TypedStablePtr, TypedSyntaxNode}; +use cairo_lang_syntax::node::TypedSyntaxNode; + +use crate::lints::single_match; pub fn cairo_lint_plugin_suite() -> PluginSuite { let mut suite = PluginSuite::default(); @@ -27,86 +25,12 @@ pub enum CairoLintKind { pub fn diagnostic_kind_from_message(message: &str) -> CairoLintKind { match message { - CairoLint::DESTRUCT_MATCH => CairoLintKind::DestructMatch, - CairoLint::MATCH_FOR_EQUALITY => CairoLintKind::MatchForEquality, + single_match::DESTRUCT_MATCH => CairoLintKind::DestructMatch, + single_match::MATCH_FOR_EQUALITY => CairoLintKind::MatchForEquality, _ => CairoLintKind::Unknown, } } -impl CairoLint { - const DESTRUCT_MATCH: &'static str = - "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`"; - const MATCH_FOR_EQUALITY: &'static str = - "you seem to be trying to use `match` for an equality check. Consider using `if`"; - - pub fn check_destruct_match( - &self, - db: &dyn SyntaxGroup, - match_expr: &ExprMatch, - diagnostics: &mut Vec, - ) { - let arms = match_expr.arms(db).deref().elements(db); - let mut is_single_armed = false; - let mut is_destructuring = false; - if arms.len() == 2 { - for arm in arms { - let patterns = arm.patterns(db).elements(db); - match patterns[0].clone() { - Pattern::Underscore(_) => { - let tuple_expr = match arm.expression(db) { - Expr::Block(block_expr) => { - let statements = block_expr.statements(db).elements(db); - if statements.is_empty() { - is_single_armed = true; - } - if statements.len() == 1 { - match &statements[0] { - Statement::Expr(statement_expr) => { - if let Expr::Tuple(tuple_expr) = statement_expr.expr(db) { - Some(tuple_expr) - } else { - None - } - } - _ => None, - } - } else { - None - } - } - Expr::Tuple(tuple_expr) => Some(tuple_expr), - _ => None, - }; - is_single_armed = tuple_expr.is_some_and(|list| list.expressions(db).elements(db).is_empty()) - || is_single_armed; - } - - Pattern::Enum(pat) => { - is_destructuring = !pat.pattern(db).as_syntax_node().get_text(db).is_empty(); - } - Pattern::Struct(pat) => { - is_destructuring = !pat.as_syntax_node().get_text(db).is_empty(); - } - _ => (), - }; - } - }; - match (is_single_armed, is_destructuring) { - (true, false) => diagnostics.push(PluginDiagnostic { - stable_ptr: match_expr.stable_ptr().untyped(), - message: Self::MATCH_FOR_EQUALITY.to_string(), - severity: Severity::Warning, - }), - (true, true) => diagnostics.push(PluginDiagnostic { - stable_ptr: match_expr.stable_ptr().untyped(), - message: Self::DESTRUCT_MATCH.to_string(), - severity: Severity::Warning, - }), - (_, _) => (), - } - } -} - impl AnalyzerPlugin for CairoLint { fn diagnostics(&self, db: &dyn SemanticGroup, module_id: ModuleId) -> Vec { let mut diags = Vec::new(); @@ -121,7 +45,7 @@ impl AnalyzerPlugin for CairoLint { let descendants = func.as_syntax_node().descendants(db.upcast()); for descendant in descendants.into_iter() { match descendant.kind(db.upcast()) { - SyntaxKind::ExprMatch => self.check_destruct_match( + SyntaxKind::ExprMatch => single_match::check_destruct_match( db.upcast(), &ExprMatch::from_syntax_node(db.upcast(), descendant), &mut diags, diff --git a/rust-toolchain.toml b/rust-toolchain.toml index ff6d7a9a..509da7f2 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "1.80.0" +channel = "nightly" components = ["rustfmt", "clippy"] profile = "minimal"