use super::*;
use crate::ast_util::{purge_trivia, range, HasSideEffects};
use std::convert::Infallible;
use full_moon::{
ast::{self, Ast},
visitors::Visitor,
};
pub struct AlmostSwappedLint;
impl Lint for AlmostSwappedLint {
type Config = ();
type Error = Infallible;
const SEVERITY: Severity = Severity::Error;
const LINT_TYPE: LintType = LintType::Correctness;
fn new(_: Self::Config) -> Result<Self, Self::Error> {
Ok(AlmostSwappedLint)
}
fn pass(&self, ast: &Ast, _: &Context, _: &AstContext) -> Vec<Diagnostic> {
let mut visitor = AlmostSwappedVisitor {
almost_swaps: Vec::new(),
};
visitor.visit_ast(ast);
visitor
.almost_swaps
.iter()
.map(|almost_swap| {
Diagnostic::new_complete(
"almost_swapped",
format!(
"this looks like you are trying to swap `{}` and `{}`",
(almost_swap.names.0),
(almost_swap.names.1),
),
Label::new(almost_swap.range),
vec![format!(
"try: `{name1}, {name2} = {name2}, {name1}`",
name1 = almost_swap.names.0,
name2 = almost_swap.names.1,
)],
Vec::new(),
)
})
.collect()
}
}
struct AlmostSwappedVisitor {
almost_swaps: Vec<AlmostSwap>,
}
struct AlmostSwap {
names: (String, String),
range: (usize, usize),
}
impl Visitor for AlmostSwappedVisitor {
fn visit_block(&mut self, block: &ast::Block) {
let mut last_swap: Option<AlmostSwap> = None;
for stmt in block.stmts() {
if let ast::Stmt::Assignment(assignment) = stmt {
let expressions = assignment.expressions();
let variables = assignment.variables();
if variables.len() == 1 && expressions.len() == 1 {
let expr = expressions.into_iter().next().unwrap();
let var = variables.into_iter().next().unwrap();
if !var.has_side_effects() {
let expr_end = range(expr).1;
let expr_text = purge_trivia(expr).to_string().trim().to_owned();
let var_text = purge_trivia(var).to_string().trim().to_owned();
if let Some(last_swap) = last_swap.take() {
if last_swap.names.0 == expr_text && last_swap.names.1 == var_text {
self.almost_swaps.push(AlmostSwap {
names: last_swap.names.to_owned(),
range: (last_swap.range.0, expr_end),
});
}
} else {
last_swap = Some(AlmostSwap {
names: (var_text, expr_text),
range: range(stmt),
});
}
continue;
}
}
}
last_swap = None;
}
}
}
#[cfg(test)]
mod tests {
use super::{super::test_util::test_lint, *};
#[test]
fn test_almost_swapped() {
test_lint(
AlmostSwappedLint::new(()).unwrap(),
"almost_swapped",
"almost_swapped",
);
}
#[test]
fn test_almost_swapped_panic() {
test_lint(
AlmostSwappedLint::new(()).unwrap(),
"almost_swapped",
"panic",
);
}
}