selene_lib/lints/
compare_nan.rs1use 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}