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
75type CheckingFunction = fn(&dyn SemanticGroup, &ModuleItemId, &mut Vec<PluginDiagnostic>);
77
78#[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 fn allowed_name(&self) -> &'static str;
122 fn diagnostic_message(&self) -> &'static str;
124 fn kind(&self) -> CairoLintKind;
126
127 fn has_fixer(&self) -> bool {
130 false
131 }
132
133 fn type_name(&self) -> &'static str {
135 std::any::type_name::<Self>()
136 }
137
138 #[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
156pub struct LintRuleGroup {
160 lints: Vec<Box<dyn Lint>>,
162 check_function: CheckingFunction,
165}
166
167struct LintContext {
169 lint_groups: Vec<LintRuleGroup>,
170 diagnostic_to_lint_kind_map: HashMap<&'static str, CairoLintKind>,
171}
172
173impl LintContext {
174 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
333static LINT_CONTEXT: LazyLock<LintContext> = LazyLock::new(LintContext::new);
335
336pub fn get_lint_type_from_diagnostic_message(message: &str) -> CairoLintKind {
339 LINT_CONTEXT.get_lint_type_from_diagnostic_message(message)
340}
341
342pub 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
357pub 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
366pub 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
375pub 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)]
386pub 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}