selene-lib 0.30.0

A library for linting Lua code. You probably want selene instead.
Documentation
use super::*;
use crate::ast_util::range;
use std::convert::Infallible;

use full_moon::{
    ast::{self, Ast},
    visitors::Visitor,
};

pub struct MixedTableLint;

impl Lint for MixedTableLint {
    type Config = ();
    type Error = Infallible;

    const SEVERITY: Severity = Severity::Warning;
    const LINT_TYPE: LintType = LintType::Correctness;

    fn new(_: Self::Config) -> Result<Self, Self::Error> {
        Ok(MixedTableLint)
    }

    fn pass(&self, ast: &Ast, _: &Context, _: &AstContext) -> Vec<Diagnostic> {
        let mut visitor = MixedTableVisitor::default();

        visitor.visit_ast(ast);

        let mut diagnostics = Vec::new();

        for mixed_table in visitor.mixed_tables {
            diagnostics.push(Diagnostic::new_complete(
                "mixed_table",
                "mixed tables should be avoided, as they can cause confusing and hard to debug issues such as during iteration or encoding".to_owned(),
                Label::new(mixed_table.range),
                vec!["help: change this table to either an array or dictionary".to_owned()],
                Vec::new(),
            ));
        }

        diagnostics
    }
}

#[derive(Default)]
struct MixedTableVisitor {
    mixed_tables: Vec<MixedTable>,
}

struct MixedTable {
    range: (usize, usize),
}

impl Visitor for MixedTableVisitor {
    fn visit_table_constructor(&mut self, node: &ast::TableConstructor) {
        let mut last_key_field_starting_range = 0;
        let mut last_no_key_field_starting_range = 0;

        for field in node.fields() {
            if let ast::Field::NoKey(_) = field {
                if last_key_field_starting_range > 0 {
                    self.mixed_tables.push(MixedTable {
                        range: (last_key_field_starting_range, range(field).1),
                    });
                    return;
                }
                last_no_key_field_starting_range = range(field).0;
            } else {
                if last_no_key_field_starting_range > 0 {
                    self.mixed_tables.push(MixedTable {
                        range: (last_no_key_field_starting_range, range(field).1),
                    });
                    return;
                }
                last_key_field_starting_range = range(field).0;
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{super::test_util::test_lint, *};

    #[test]
    fn test_mixed_table() {
        test_lint(
            MixedTableLint::new(()).unwrap(),
            "mixed_table",
            "mixed_table",
        );
    }
}