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