selene_lib/lints/
empty_loop.rs

1use super::*;
2use crate::ast_util::range;
3use std::convert::Infallible;
4
5use full_moon::{
6    ast::{self, Ast},
7    tokenizer::{Token, TokenKind},
8    visitors::Visitor,
9};
10use serde::Deserialize;
11
12#[derive(Clone, Copy, Default, Deserialize)]
13#[serde(default)]
14pub struct EmptyLoopLintConfig {
15    comments_count: bool,
16}
17
18pub struct EmptyLoopLint {
19    config: EmptyLoopLintConfig,
20}
21
22impl Lint for EmptyLoopLint {
23    type Config = EmptyLoopLintConfig;
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(EmptyLoopLint { config })
31    }
32
33    fn pass(&self, ast: &Ast, _: &Context, _: &AstContext) -> Vec<Diagnostic> {
34        let mut visitor = EmptyLoopVisitor {
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_loop",
59                    "empty loop block".to_owned(),
60                    Label::new(position),
61                )
62            })
63            .collect()
64    }
65}
66
67struct EmptyLoopVisitor {
68    comment_positions: Vec<u32>,
69    positions: Vec<(u32, u32)>,
70}
71
72fn block_is_empty(block: &ast::Block) -> bool {
73    block.last_stmt().is_none() && block.stmts().next().is_none()
74}
75
76impl Visitor for EmptyLoopVisitor {
77    fn visit_generic_for(&mut self, node: &ast::GenericFor) {
78        if block_is_empty(node.block()) {
79            self.positions.push(range(node));
80        }
81    }
82
83    fn visit_numeric_for(&mut self, node: &ast::NumericFor) {
84        if block_is_empty(node.block()) {
85            self.positions.push(range(node));
86        }
87    }
88
89    fn visit_while(&mut self, node: &ast::While) {
90        if block_is_empty(node.block()) {
91            self.positions.push(range(node));
92        }
93    }
94
95    fn visit_repeat(&mut self, node: &ast::Repeat) {
96        if block_is_empty(node.block()) {
97            self.positions.push(range(node));
98        }
99    }
100
101    fn visit_token(&mut self, token: &Token) {
102        match token.token_kind() {
103            TokenKind::MultiLineComment | TokenKind::SingleLineComment => {
104                self.comment_positions
105                    .push(Token::end_position(token).bytes() as u32);
106            }
107
108            _ => {}
109        }
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::{super::test_util::test_lint, *};
116
117    #[test]
118    fn test_empty_loop() {
119        test_lint(
120            EmptyLoopLint::new(EmptyLoopLintConfig::default()).unwrap(),
121            "empty_loop",
122            "empty_loop",
123        );
124    }
125
126    #[test]
127    fn test_empty_loop_comments() {
128        test_lint(
129            EmptyLoopLint::new(EmptyLoopLintConfig {
130                comments_count: true,
131            })
132            .unwrap(),
133            "empty_loop",
134            "empty_loop_comments",
135        );
136    }
137}