selene_lib/lints/
multiple_statements.rs1use super::*;
2use std::{collections::HashSet, convert::Infallible};
3
4use full_moon::{
5 ast::{self, Ast},
6 node::Node,
7 visitors::Visitor,
8};
9use serde::Deserialize;
10
11#[derive(Clone, Copy, Default, Deserialize)]
12pub struct MultipleStatementsConfig {
13 one_line_if: OneLineIf,
14}
15
16pub struct MultipleStatementsLint {
17 config: MultipleStatementsConfig,
18}
19
20#[derive(Clone, Copy, Default, PartialEq, Eq, Deserialize)]
21#[serde(rename_all = "kebab-case")]
22pub enum OneLineIf {
23 Allow,
24 Deny,
25 #[default]
26 BreakReturnOnly,
27}
28
29impl Lint for MultipleStatementsLint {
30 type Config = MultipleStatementsConfig;
31 type Error = Infallible;
32
33 const SEVERITY: Severity = Severity::Warning;
34 const LINT_TYPE: LintType = LintType::Style;
35
36 fn new(config: Self::Config) -> Result<Self, Self::Error> {
37 Ok(MultipleStatementsLint { config })
38 }
39
40 fn pass(&self, ast: &Ast, _: &Context, _: &AstContext) -> Vec<Diagnostic> {
41 let mut visitor = MultipleStatementsVisitor {
42 config: self.config,
43 ..MultipleStatementsVisitor::default()
44 };
45
46 visitor.visit_ast(ast);
47
48 visitor
49 .positions
50 .iter()
51 .map(|position| {
52 Diagnostic::new(
53 "multiple_statements",
54 "only one statement per line is allowed".to_owned(),
55 Label::new(*position),
56 )
57 })
58 .collect()
59 }
60}
61
62#[derive(Default)]
63struct MultipleStatementsVisitor {
64 config: MultipleStatementsConfig,
65 if_lines: HashSet<usize>,
66 lines_with_stmt: HashSet<usize>,
67 positions: Vec<(usize, usize)>,
68}
69
70impl MultipleStatementsVisitor {
71 fn prepare_if(&mut self, if_block: &ast::If) {
72 let line = if_block.then_token().end_position().unwrap().line();
73
74 if self.config.one_line_if != OneLineIf::Deny {
75 if self.config.one_line_if == OneLineIf::BreakReturnOnly
76 && (if_block.block().stmts().next().is_some()
77 || if_block.block().last_stmt().is_none())
78 {
79 return;
80 }
81
82 self.if_lines.insert(line);
83 }
84 }
85
86 fn lint_stmt<N: Node>(&mut self, stmt: N) {
87 let line = stmt.end_position().unwrap().line();
88
89 if self.lines_with_stmt.contains(&line) {
90 let range = stmt.range().unwrap();
91 self.positions.push((range.0.bytes(), range.1.bytes()));
92 } else if self.if_lines.contains(&line) {
93 self.if_lines.remove(&line);
94 } else {
95 self.lines_with_stmt.insert(line);
96 }
97 }
98}
99
100impl Visitor for MultipleStatementsVisitor {
101 fn visit_last_stmt(&mut self, stmt: &ast::LastStmt) {
102 self.lint_stmt(stmt);
103 }
104
105 fn visit_stmt(&mut self, stmt: &ast::Stmt) {
106 if let ast::Stmt::If(if_block) = stmt {
107 self.prepare_if(if_block);
108 }
109
110 self.lint_stmt(stmt);
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::{super::test_util::test_lint, *};
117
118 #[test]
119 fn test_multiple_statements() {
120 test_lint(
121 MultipleStatementsLint::new(MultipleStatementsConfig::default()).unwrap(),
122 "multiple_statements",
123 "multiple_statements",
124 );
125 }
126
127 #[test]
128 fn test_one_line_if_deny() {
129 test_lint(
130 MultipleStatementsLint::new(MultipleStatementsConfig {
131 one_line_if: OneLineIf::Deny,
132 })
133 .unwrap(),
134 "multiple_statements",
135 "one_line_if_deny",
136 );
137 }
138
139 #[test]
140 fn test_one_line_if_allow() {
141 test_lint(
142 MultipleStatementsLint::new(MultipleStatementsConfig {
143 one_line_if: OneLineIf::Allow,
144 })
145 .unwrap(),
146 "multiple_statements",
147 "one_line_if_allow",
148 );
149 }
150
151 #[test]
152 fn test_one_line_if_break_return_only() {
153 test_lint(
154 MultipleStatementsLint::new(MultipleStatementsConfig {
155 one_line_if: OneLineIf::BreakReturnOnly,
156 })
157 .unwrap(),
158 "multiple_statements",
159 "one_line_if_break_return_only",
160 );
161 }
162}