selene_lib/lints/
mixed_table.rs

1use super::*;
2use crate::ast_util::range;
3use std::convert::Infallible;
4
5use full_moon::{
6    ast::{self, Ast},
7    visitors::Visitor,
8};
9
10pub struct MixedTableLint;
11
12impl Lint for MixedTableLint {
13    type Config = ();
14    type Error = Infallible;
15
16    const SEVERITY: Severity = Severity::Warning;
17    const LINT_TYPE: LintType = LintType::Correctness;
18
19    fn new(_: Self::Config) -> Result<Self, Self::Error> {
20        Ok(MixedTableLint)
21    }
22
23    fn pass(&self, ast: &Ast, _: &Context, _: &AstContext) -> Vec<Diagnostic> {
24        let mut visitor = MixedTableVisitor::default();
25
26        visitor.visit_ast(ast);
27
28        let mut diagnostics = Vec::new();
29
30        for mixed_table in visitor.mixed_tables {
31            diagnostics.push(Diagnostic::new_complete(
32                "mixed_table",
33                "mixed tables should be avoided, as they can cause confusing and hard to debug issues such as during iteration or encoding".to_owned(),
34                Label::new(mixed_table.range),
35                vec!["help: change this table to either an array or dictionary".to_owned()],
36                Vec::new(),
37            ));
38        }
39
40        diagnostics
41    }
42}
43
44#[derive(Default)]
45struct MixedTableVisitor {
46    mixed_tables: Vec<MixedTable>,
47}
48
49struct MixedTable {
50    range: (usize, usize),
51}
52
53impl Visitor for MixedTableVisitor {
54    fn visit_table_constructor(&mut self, node: &ast::TableConstructor) {
55        let mut last_key_field_starting_range = 0;
56        let mut last_no_key_field_starting_range = 0;
57
58        for field in node.fields() {
59            if let ast::Field::NoKey(_) = field {
60                if last_key_field_starting_range > 0 {
61                    self.mixed_tables.push(MixedTable {
62                        range: (last_key_field_starting_range, range(field).1),
63                    });
64                    return;
65                }
66                last_no_key_field_starting_range = range(field).0;
67            } else {
68                if last_no_key_field_starting_range > 0 {
69                    self.mixed_tables.push(MixedTable {
70                        range: (last_no_key_field_starting_range, range(field).1),
71                    });
72                    return;
73                }
74                last_key_field_starting_range = range(field).0;
75            }
76        }
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::{super::test_util::test_lint, *};
83
84    #[test]
85    fn test_mixed_table() {
86        test_lint(
87            MixedTableLint::new(()).unwrap(),
88            "mixed_table",
89            "mixed_table",
90        );
91    }
92}