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