selene_lib/lints/
almost_swapped.rs1use super::*;
2use crate::ast_util::{purge_trivia, range, HasSideEffects};
3use std::convert::Infallible;
4
5use full_moon::{
6 ast::{self, Ast},
7 visitors::Visitor,
8};
9
10pub struct AlmostSwappedLint;
11
12impl Lint for AlmostSwappedLint {
13 type Config = ();
14 type Error = Infallible;
15
16 const SEVERITY: Severity = Severity::Error;
17 const LINT_TYPE: LintType = LintType::Correctness;
18
19 fn new(_: Self::Config) -> Result<Self, Self::Error> {
20 Ok(AlmostSwappedLint)
21 }
22
23 fn pass(&self, ast: &Ast, _: &Context, _: &AstContext) -> Vec<Diagnostic> {
24 let mut visitor = AlmostSwappedVisitor {
25 almost_swaps: Vec::new(),
26 };
27
28 visitor.visit_ast(ast);
29
30 visitor
31 .almost_swaps
32 .iter()
33 .map(|almost_swap| {
34 Diagnostic::new_complete(
35 "almost_swapped",
36 format!(
37 "this looks like you are trying to swap `{}` and `{}`",
38 (almost_swap.names.0),
39 (almost_swap.names.1),
40 ),
41 Label::new(almost_swap.range),
42 vec![format!(
43 "try: `{name1}, {name2} = {name2}, {name1}`",
44 name1 = almost_swap.names.0,
45 name2 = almost_swap.names.1,
46 )],
47 Vec::new(),
48 )
49 })
50 .collect()
51 }
52}
53
54struct AlmostSwappedVisitor {
55 almost_swaps: Vec<AlmostSwap>,
56}
57
58struct AlmostSwap {
59 names: (String, String),
60 range: (usize, usize),
61}
62
63impl Visitor for AlmostSwappedVisitor {
64 fn visit_block(&mut self, block: &ast::Block) {
65 let mut last_swap: Option<AlmostSwap> = None;
66
67 for stmt in block.stmts() {
68 if let ast::Stmt::Assignment(assignment) = stmt {
69 let expressions = assignment.expressions();
70 let variables = assignment.variables();
71
72 if variables.len() == 1 && expressions.len() == 1 {
73 let expr = expressions.into_iter().next().unwrap();
74 let var = variables.into_iter().next().unwrap();
75
76 if !var.has_side_effects() {
77 let expr_end = range(expr).1;
78
79 let expr_text = purge_trivia(expr).to_string().trim().to_owned();
80 let var_text = purge_trivia(var).to_string().trim().to_owned();
81
82 if let Some(last_swap) = last_swap.take() {
83 if last_swap.names.0 == expr_text && last_swap.names.1 == var_text {
84 self.almost_swaps.push(AlmostSwap {
85 names: last_swap.names.to_owned(),
86 range: (last_swap.range.0, expr_end),
87 });
88 }
89 } else {
90 last_swap = Some(AlmostSwap {
91 names: (var_text, expr_text),
92 range: range(stmt),
93 });
94 }
95
96 continue;
97 }
98 }
99 }
100
101 last_swap = None;
102 }
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::{super::test_util::test_lint, *};
109
110 #[test]
111 fn test_almost_swapped() {
112 test_lint(
113 AlmostSwappedLint::new(()).unwrap(),
114 "almost_swapped",
115 "almost_swapped",
116 );
117 }
118
119 #[test]
120 fn test_almost_swapped_panic() {
121 test_lint(
122 AlmostSwappedLint::new(()).unwrap(),
123 "almost_swapped",
124 "panic",
125 );
126 }
127}