selene_lib/lints/
empty_if.rs

1use super::*;
2use std::convert::Infallible;
3
4use full_moon::{
5    ast::{self, Ast},
6    node::Node,
7    tokenizer::{Token, TokenKind},
8    visitors::Visitor,
9};
10use serde::Deserialize;
11
12#[derive(Clone, Copy, Default, Deserialize)]
13#[serde(default)]
14pub struct EmptyIfLintConfig {
15    comments_count: bool,
16}
17
18pub struct EmptyIfLint {
19    config: EmptyIfLintConfig,
20}
21
22impl Lint for EmptyIfLint {
23    type Config = EmptyIfLintConfig;
24    type Error = Infallible;
25
26    const SEVERITY: Severity = Severity::Warning;
27    const LINT_TYPE: LintType = LintType::Style;
28
29    fn new(config: Self::Config) -> Result<Self, Self::Error> {
30        Ok(EmptyIfLint { config })
31    }
32
33    fn pass(&self, ast: &Ast, _: &Context, _: &AstContext) -> Vec<Diagnostic> {
34        let mut visitor = EmptyIfVisitor {
35            comment_positions: Vec::new(),
36            positions: Vec::new(),
37        };
38
39        visitor.visit_ast(ast);
40
41        let comment_positions = visitor.comment_positions.clone();
42
43        visitor
44            .positions
45            .into_iter()
46            .filter(|(position, _)| {
47                // OPTIMIZE: This is O(n^2), can we optimize this?
48                if self.config.comments_count {
49                    !comment_positions.iter().any(|comment_position| {
50                        position.0 <= *comment_position && position.1 >= *comment_position
51                    })
52                } else {
53                    true
54                }
55            })
56            .map(|position| {
57                Diagnostic::new(
58                    "empty_if",
59                    match position.1 {
60                        EmptyIfKind::If => "empty if block",
61                        EmptyIfKind::ElseIf => "empty elseif block",
62                        EmptyIfKind::Else => "empty else block",
63                    }
64                    .to_owned(),
65                    Label::new(position.0),
66                )
67            })
68            .collect()
69    }
70}
71
72fn block_is_empty(block: &ast::Block) -> bool {
73    block.last_stmt().is_none() && block.stmts().next().is_none()
74}
75
76struct EmptyIfVisitor {
77    comment_positions: Vec<u32>,
78    positions: Vec<((u32, u32), EmptyIfKind)>,
79}
80
81impl Visitor for EmptyIfVisitor {
82    fn visit_if(&mut self, if_block: &ast::If) {
83        if block_is_empty(if_block.block()) {
84            self.positions.push((
85                if_block
86                    .range()
87                    .map(|(start, end)| (start.bytes() as u32, end.bytes() as u32))
88                    .unwrap(),
89                EmptyIfKind::If,
90            ));
91        }
92
93        if let Some(else_ifs) = if_block.else_if() {
94            let mut else_ifs = else_ifs.iter().peekable();
95
96            while let Some(else_if) = else_ifs.next() {
97                if block_is_empty(else_if.block()) {
98                    let next_token_position = match else_ifs.peek() {
99                        Some(next_else_if) => next_else_if.start_position().unwrap().bytes() as u32,
100                        None => {
101                            if let Some(else_block) = if_block.else_token() {
102                                else_block.start_position().unwrap().bytes() as u32
103                            } else {
104                                if_block.end_token().start_position().unwrap().bytes() as u32
105                            }
106                        }
107                    };
108
109                    self.positions.push((
110                        (
111                            else_if.start_position().unwrap().bytes() as u32,
112                            next_token_position,
113                        ),
114                        EmptyIfKind::ElseIf,
115                    ));
116                }
117            }
118        }
119
120        if let Some(else_block) = if_block.else_block() {
121            if block_is_empty(else_block) {
122                self.positions.push((
123                    (
124                        if_block.else_token().start_position().unwrap().bytes() as u32,
125                        if_block.end_token().end_position().unwrap().bytes() as u32,
126                    ),
127                    EmptyIfKind::Else,
128                ));
129            }
130        }
131    }
132
133    fn visit_token(&mut self, token: &Token) {
134        match token.token_kind() {
135            TokenKind::MultiLineComment | TokenKind::SingleLineComment => {
136                self.comment_positions
137                    .push(Token::end_position(token).bytes() as u32);
138            }
139
140            _ => {}
141        }
142    }
143}
144
145enum EmptyIfKind {
146    If,
147    ElseIf,
148    Else,
149}
150
151#[cfg(test)]
152mod tests {
153    use super::{super::test_util::test_lint, *};
154
155    #[test]
156    fn test_empty_if() {
157        test_lint(
158            EmptyIfLint::new(EmptyIfLintConfig::default()).unwrap(),
159            "empty_if",
160            "empty_if",
161        );
162    }
163
164    #[test]
165    fn test_empty_if_comments() {
166        test_lint(
167            EmptyIfLint::new(EmptyIfLintConfig {
168                comments_count: true,
169            })
170            .unwrap(),
171            "empty_if",
172            "empty_if_comments",
173        );
174    }
175}