wowlab_tidy/languages/rust/rules/correctness/
dup_expressions.rs1use 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});