selene_lib/lints/
compare_nan.rs

1use super::*;
2use std::convert::Infallible;
3
4use full_moon::{
5    ast::{self, Ast},
6    visitors::Visitor,
7};
8
9pub struct CompareNanLint;
10
11impl Lint for CompareNanLint {
12    type Config = ();
13    type Error = Infallible;
14
15    const SEVERITY: Severity = Severity::Error;
16    const LINT_TYPE: LintType = LintType::Correctness;
17
18    fn new(_: Self::Config) -> Result<Self, Self::Error> {
19        Ok(CompareNanLint)
20    }
21
22    fn pass(&self, ast: &Ast, _: &Context, _: &AstContext) -> Vec<Diagnostic> {
23        let mut visitor = CompareNanVisitor {
24            comparisons: Vec::new(),
25        };
26
27        visitor.visit_ast(ast);
28
29        visitor
30            .comparisons
31            .iter()
32            .map(|comparisons| {
33                Diagnostic::new_complete(
34                    "compare_nan",
35                    "comparing things to nan directly is not allowed".to_owned(),
36                    Label::new(comparisons.range),
37                    vec![format!(
38                        "try: `{variable} {operator} {variable}` instead",
39                        variable = comparisons.variable,
40                        operator = comparisons.operator,
41                    )],
42                    Vec::new(),
43                )
44            })
45            .collect()
46    }
47}
48
49struct CompareNanVisitor {
50    comparisons: Vec<Comparison>,
51}
52
53struct Comparison {
54    variable: String,
55    operator: String,
56    range: (usize, usize),
57}
58
59fn value_is_zero(value: &ast::Expression) -> bool {
60    if let ast::Expression::Number(token) = value {
61        token.token().to_string() == "0"
62    } else {
63        false
64    }
65}
66
67fn expression_is_nan(node: &ast::Expression) -> bool {
68    if_chain::if_chain! {
69        if let ast::Expression::BinaryOperator { lhs, binop: ast::BinOp::Slash(_), rhs } = node;
70        if value_is_zero(lhs) && value_is_zero(rhs);
71        then {
72            return true;
73        }
74    }
75    false
76}
77
78impl Visitor for CompareNanVisitor {
79    fn visit_expression(&mut self, node: &ast::Expression) {
80        if_chain::if_chain! {
81            if let ast::Expression::BinaryOperator { lhs, binop, rhs } = node;
82            if let ast::Expression::Var(_) = lhs.as_ref();
83            then {
84                match binop {
85                    ast::BinOp::TildeEqual(_) => {
86                        if expression_is_nan(rhs) {
87                            let range = node.range().unwrap();
88                            self.comparisons.push(
89                                Comparison {
90                                    variable: lhs.to_string().trim().to_owned(),
91                                    operator: "==".to_owned(),
92                                    range: (range.0.bytes(), range.1.bytes()),
93                                }
94                            );
95                        }
96                    },
97                    ast::BinOp::TwoEqual(_) => {
98                        if expression_is_nan(rhs) {
99                            let range = node.range().unwrap();
100                            self.comparisons.push(
101                                Comparison {
102                                    variable: lhs.to_string().trim().to_owned(),
103                                    operator: "~=".to_owned(),
104                                    range: (range.0.bytes(), range.1.bytes()),
105                                }
106                            );
107                        }
108                    },
109                    _ => {},
110                }
111            }
112        }
113    }
114}
115
116#[cfg(test)]
117mod tests {
118    use super::{super::test_util::test_lint, *};
119
120    #[test]
121    fn test_compare_nan_variables() {
122        test_lint(
123            CompareNanLint::new(()).unwrap(),
124            "compare_nan",
125            "compare_nan_variables",
126        );
127    }
128
129    #[test]
130    fn test_compare_nan_if() {
131        test_lint(
132            CompareNanLint::new(()).unwrap(),
133            "compare_nan",
134            "compare_nan_if",
135        );
136    }
137}