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::{Lint, Metadata, Report, 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<Report> {
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
);
Some(Report::new().diagnostic(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<_>>();
// }
// }
|