selene_lib/lints/
constant_table_comparison.rs1use crate::ast_util::{purge_trivia, range};
2
3use super::*;
4use std::convert::Infallible;
5
6use full_moon::{
7 ast::{self, Ast, BinOp},
8 visitors::Visitor,
9};
10
11pub struct ConstantTableComparisonLint;
12
13impl Lint for ConstantTableComparisonLint {
14 type Config = ();
15 type Error = Infallible;
16
17 const SEVERITY: Severity = Severity::Error;
18 const LINT_TYPE: LintType = LintType::Correctness;
19
20 fn new(_: Self::Config) -> Result<Self, Self::Error> {
21 Ok(ConstantTableComparisonLint)
22 }
23
24 fn pass(&self, ast: &Ast, _: &Context, _: &AstContext) -> Vec<Diagnostic> {
25 let mut visitor = ConstantTableComparisonVisitor {
26 comparisons: Vec::new(),
27 };
28
29 visitor.visit_ast(ast);
30
31 visitor
32 .comparisons
33 .iter()
34 .map(|comparison| {
35 Diagnostic::new_complete(
36 "constant_table_comparison",
37 "comparing to a constant table will always fail".to_owned(),
38 Label::new(comparison.range),
39 if let Some(empty_side) = comparison.empty_side {
40 vec![format!(
41 "try: `next({}) {} nil`",
42 match empty_side {
43 EmptyComparison::CheckEmpty(side) => match side {
44 EmptyComparisonSide::Left => &comparison.rhs,
45 EmptyComparisonSide::Right => &comparison.lhs,
46 },
47
48 EmptyComparison::CheckNotEmpty(side) => match side {
49 EmptyComparisonSide::Left => &comparison.rhs,
50 EmptyComparisonSide::Right => &comparison.lhs,
51 },
52 },
53 match empty_side {
54 EmptyComparison::CheckEmpty(_) => "==",
55 EmptyComparison::CheckNotEmpty(_) => "~=",
56 }
57 )]
58 } else {
59 Vec::new()
60 },
61 Vec::new(),
62 )
63 })
64 .collect()
65 }
66}
67
68struct ConstantTableComparisonVisitor {
69 comparisons: Vec<Comparison>,
70}
71
72#[derive(Clone, Copy)]
73enum EmptyComparisonSide {
74 Left,
75 Right,
76}
77
78#[derive(Clone, Copy)]
79enum EmptyComparison {
80 CheckEmpty(EmptyComparisonSide),
81 CheckNotEmpty(EmptyComparisonSide),
82}
83
84struct Comparison {
85 lhs: String,
86 rhs: String,
87 empty_side: Option<EmptyComparison>,
88 range: (usize, usize),
89}
90
91enum ConstantTableMatch {
92 Empty,
93 NotEmpty,
94}
95
96fn constant_table_match(expression: &ast::Expression) -> Option<ConstantTableMatch> {
97 if let ast::Expression::TableConstructor(table_constructor) = expression {
98 return if table_constructor.fields().is_empty() {
99 Some(ConstantTableMatch::Empty)
100 } else {
101 Some(ConstantTableMatch::NotEmpty)
102 };
103 }
104
105 None
106}
107
108impl Visitor for ConstantTableComparisonVisitor {
109 fn visit_expression(&mut self, node: &ast::Expression) {
110 if let ast::Expression::BinaryOperator {
111 lhs,
112 binop:
113 binop @ (BinOp::TwoEqual(_)
114 | BinOp::TildeEqual(_)
115 | BinOp::GreaterThan(_)
116 | BinOp::LessThan(_)
117 | BinOp::GreaterThanEqual(_)
118 | BinOp::LessThanEqual(_)),
119 rhs,
120 } = node
121 {
122 match (constant_table_match(lhs), constant_table_match(rhs)) {
123 (Some(_), Some(_))
126 | (Some(ConstantTableMatch::NotEmpty), _)
127 | (_, Some(ConstantTableMatch::NotEmpty)) => {
128 self.comparisons.push(Comparison {
129 lhs: purge_trivia(lhs).to_string(),
130 rhs: purge_trivia(rhs).to_string(),
131 empty_side: None,
132 range: range(node),
133 });
134 }
135
136 empty_checks @ ((Some(ConstantTableMatch::Empty), None)
137 | (None, Some(ConstantTableMatch::Empty))) => {
138 let side = match empty_checks.0.is_some() {
139 true => EmptyComparisonSide::Left,
140 false => EmptyComparisonSide::Right,
141 };
142
143 self.comparisons.push(Comparison {
144 lhs: purge_trivia(lhs).to_string(),
145 rhs: purge_trivia(rhs).to_string(),
146 empty_side: match binop {
147 ast::BinOp::TwoEqual(_) => Some(EmptyComparison::CheckEmpty(side)),
148 ast::BinOp::TildeEqual(_) => Some(EmptyComparison::CheckNotEmpty(side)),
149 _ => None,
150 },
151 range: range(node),
152 });
153 }
154
155 (None, None) => {}
156 }
157 }
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::{super::test_util::test_lint, *};
164
165 #[test]
166 fn test_constant_table_comparison() {
167 test_lint(
168 ConstantTableComparisonLint::new(()).unwrap(),
169 "constant_table_comparison",
170 "constant_table_comparison",
171 );
172 }
173}