selene_lib/lints/
multiple_statements.rs

1use 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}