aboutsummaryrefslogtreecommitdiff
path: root/lib/src/lints/bool_comparison.rs
blob: 4476b3197779da35314dc80df97967f0a15d34be (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
use crate::{Diagnostic, Lint, Metadata, Rule};

use if_chain::if_chain;
use macros::lint;
use rnix::{
    types::{BinOp, BinOpKind, Ident, TokenWrapper, TypedNode},
    NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode,
};

#[lint(
    name = "bool_comparison",
    note = "Unnecessary comparison with boolean",
    match_with = "SyntaxKind::NODE_BIN_OP"
)]
struct BoolComparison;

impl Rule for BoolComparison {
    fn validate(&self, node: &SyntaxElement) -> Option<Diagnostic> {
        if_chain! {
            if let NodeOrToken::Node(bin_op_node) = node;
            if let Some(bin_expr) = BinOp::cast(bin_op_node.clone());
            if let Some(lhs) = bin_expr.lhs();
            if let Some(rhs) = bin_expr.rhs();

            if let BinOpKind::Equal | BinOpKind::NotEqual = bin_expr.operator();
            let (non_bool_side, bool_side) = if is_boolean_ident(&lhs) {
                (rhs, lhs)
            } else if is_boolean_ident(&rhs) {
                (lhs, rhs)
            } else {
                return None
            };
            then {
                let at = node.text_range();
                let message = format!(
                    "Comparing `{}` with boolean literal `{}`",
                    non_bool_side,
                    bool_side
                );
                dbg!(Some(Diagnostic::new(at, message)))
            } else {
                None
            }
        }
    }
}

// not entirely accurate, underhanded nix programmers might write `true = false`
fn is_boolean_ident(node: &SyntaxNode) -> bool {
    if let Some(ident_expr) = Ident::cast(node.clone()) {
        ident_expr.as_str() == "true" || ident_expr.as_str() == "false"
    } else {
        false
    }
}

// #[cfg(test)]
// mod tests {
//     use super::*;
//     use rnix::{parser, WalkEvent};
//
//     #[test]
//     fn trivial() {
//         let src = r#"
//         a == true
//         "#;
//         let parsed = rnix::parse(src).as_result().ok().unwrap();
//         let _ = parsed
//             .node()
//             .preorder_with_tokens()
//             .filter_map(|event| match event {
//                 WalkEvent::Enter(t) => Some(t),
//                 _ => None,
//             })
//             .map(|node| BoolComparison.validate(&node))
//             .collect::<Vec<_>>();
//     }
// }