Skip to main content

wowlab_tidy/languages/rust/rules/correctness/
dup_expressions.rs

1use ra_ap_syntax::{
2    AstNode,
3    ast::{self, ArithOp, BinaryOp, CmpOp, LogicOp},
4};
5
6use crate::{AstCtx, Example, Violation};
7
8#[rustfmt::skip]
9const EXAMPLES: &[Example] = &[
10    Example {
11        label: "x == x",
12        code: "fn f(x: i32) { if x == x {} }",
13        pass: false,
14    },
15    Example {
16        label: "x == y",
17        code: "fn f(x: i32, y: i32) { if x == y {} }",
18        pass: true,
19    },
20    Example {
21        label: "a - a",
22        code: "fn f(a: i32) { let _z = a - a; }",
23        pass: false,
24    },
25    Example {
26        label: "a + b",
27        code: "fn f(a: i32, b: i32) { let _z = a + b; }",
28        pass: true,
29    },
30    Example {
31        label: "b && b",
32        code: "fn f(b: bool) { if b && b {} }",
33        pass: false,
34    },
35    Example {
36        label: "dup in test module",
37        code: "#[cfg(test)]\nmod tests {\n    fn t(x: i32) { if x == x {} }\n}",
38        pass: true,
39    },
40];
41
42crate::ast_rule!(
43    dup_expressions,
44    "Flag identical sub-expressions like `x == x`, `a - a`, `b && b`.",
45    "Identical operands on both sides of an operator (x == x, a - a) are almost always copy-paste bugs.",
46    High,
47);
48
49fn check_dup_expressions(ctx: &AstCtx<'_>) -> Vec<Violation> {
50    ctx.nodes::<ast::BinExpr>()
51        .filter(|expr| !ctx.is_in_test(expr))
52        .filter_map(|expr| {
53            let op = expr.op_kind()?;
54
55            if !is_suspicious_op(op) {
56                return None;
57            }
58
59            let (left, right) = expr.sub_exprs();
60            let (left, right) = (left?, right?);
61
62            (tokens_to_string(&left) == tokens_to_string(&right)).then(|| {
63                let message = format!("identical sub-expressions on both sides of `{op}`");
64
65                ctx.violation(&expr, message)
66            })
67        })
68        .collect()
69}
70
71fn tokens_to_string(expr: &ast::Expr) -> String {
72    expr.syntax()
73        .descendants_with_tokens()
74        .filter_map(ra_ap_syntax::NodeOrToken::into_token)
75        .filter(|token| !token.kind().is_trivia())
76        .fold(String::new(), |mut source, token| {
77            source.push_str(token.text());
78
79            source
80        })
81}
82
83fn is_suspicious_op(op: BinaryOp) -> bool {
84    matches!(
85        op,
86        BinaryOp::CmpOp(CmpOp::Eq { .. })
87            | BinaryOp::LogicOp(LogicOp::And | LogicOp::Or)
88            | BinaryOp::ArithOp(
89                ArithOp::Sub
90                    | ArithOp::Div
91                    | ArithOp::Rem
92                    | ArithOp::BitXor
93                    | ArithOp::BitAnd
94                    | ArithOp::BitOr
95            )
96    )
97}
98
99crate::tidy_ast_test!(check_dup_expressions, {
100    crate::example_tests!(EXAMPLES, check_dup_expressions);
101});