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
101type CheckingFunction =
103 for<'db> fn(&'db dyn Database, &ModuleItemId<'db>, &mut Vec<PluginDiagnostic<'db>>);
104
105#[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 fn allowed_name(&self) -> &'static str;
161 fn diagnostic_message(&self) -> &'static str;
163 fn kind(&self) -> CairoLintKind;
165
166 fn is_enabled(&self) -> bool {
169 true
170 }
171
172 fn has_fixer(&self) -> bool {
175 false
176 }
177
178 fn type_name(&self) -> &'static str {
180 std::any::type_name::<Self>()
181 }
182
183 #[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 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
207pub struct LintRuleGroup {
211 lints: Vec<Box<dyn Lint>>,
213 check_function: CheckingFunction,
216}
217
218struct LintContext {
220 lint_groups: Vec<LintRuleGroup>,
221 diagnostic_to_lint_kind_map: HashMap<&'static str, CairoLintKind>,
222}
223
224impl LintContext {
225 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
436static LINT_CONTEXT: LazyLock<LintContext> = LazyLock::new(LintContext::new);
438
439pub fn get_lint_type_from_diagnostic_message(message: &str) -> CairoLintKind {
442 LINT_CONTEXT.get_lint_type_from_diagnostic_message(message)
443}
444
445pub 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
460pub 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
469pub 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
478pub 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
488pub 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)]
499pub 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
510pub 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
525pub 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}