From 84ce35812be9d02671a1a9ce8f7b53175ef9c895 Mon Sep 17 00:00:00 2001 From: MixeroTN <40803091+MixeroTN@users.noreply.github.com> Date: Sun, 3 Sep 2023 04:55:06 +0000 Subject: [PATCH 1/2] Fix link typos in docs (#551) --- docs/src/contributing.md | 2 +- docs/src/roblox.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/contributing.md b/docs/src/contributing.md index 3870565a..a8a1f52b 100644 --- a/docs/src/contributing.md +++ b/docs/src/contributing.md @@ -130,7 +130,7 @@ The first `"cool_lint"` is the name of the folder we created. The second `"cool_ Now, just run `cargo test`, and a `.stderr` file will be automatically generated! You can manipulate it however you see fit as well as modifying your lint, and so long as the file is there, selene will make sure that its accurate. -Optionally, you can add a `.std.toml` with the same name as the test next to the lua file, where you can specify a custom [standard library](./usage/std.html) to use. If you do not, the Lua 5.1 standard library will be used. +Optionally, you can add a `.std.toml` with the same name as the test next to the lua file, where you can specify a custom [standard library](./usage/std.md) to use. If you do not, the Lua 5.1 standard library will be used. ### Documenting it diff --git a/docs/src/roblox.md b/docs/src/roblox.md index b694ec04..77ed0709 100644 --- a/docs/src/roblox.md +++ b/docs/src/roblox.md @@ -2,7 +2,7 @@ selene is built with Roblox development in mind, and has special features for Roblox developers. -If you try to run selene on a Roblox codebase, you'll get a bunch of errors saying things such as "`game` is not defined". This is because these are Roblox specific globals that selene does not know about. You'll need to install the Roblox [standard library](./usage/configuration) in order to fix these issues, as well as get Roblox specific lints. +If you try to run selene on a Roblox codebase, you'll get a bunch of errors saying things such as "`game` is not defined". This is because these are Roblox specific globals that selene does not know about. You'll need to install the Roblox [standard library](./usage/configuration.md) in order to fix these issues, as well as get Roblox specific lints. ## Installation From 5f6c2be747eb53b4f83fe045325c3b488e7145ac Mon Sep 17 00:00:00 2001 From: Chris Chang <51393127+chriscerie@users.noreply.github.com> Date: Mon, 11 Sep 2023 21:52:06 -0700 Subject: [PATCH 2/2] Add mixed table lint (#535) Closes #534. This might false positive when working with an external library that expects a mixed table as it also warns against mixed tables passed into function calls. Disabling it in these cases would also disable similar cases where the user's application owns the expecting function. --- CHANGELOG.md | 1 + docs/src/SUMMARY.md | 1 + docs/src/lints/mixed_table.md | 14 +++ selene-lib/src/lib.rs | 1 + selene-lib/src/lints.rs | 1 + selene-lib/src/lints/mixed_table.rs | 92 +++++++++++++++++++ .../tests/lints/mixed_table/mixed_table.lua | 53 +++++++++++ .../lints/mixed_table/mixed_table.stderr | 54 +++++++++++ 8 files changed, 217 insertions(+) create mode 100644 docs/src/lints/mixed_table.md create mode 100644 selene-lib/src/lints/mixed_table.rs create mode 100644 selene-lib/tests/lints/mixed_table/mixed_table.lua create mode 100644 selene-lib/tests/lints/mixed_table/mixed_table.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index d985a1b0..bd5f5c35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Excludes are now respected for single files. - Added `no-exclude` cli flag to disable excludes. - When given in standard library format, additional information now shows up in `incorrect_standard_library_use` missing required parameter errors. +- Added new [`mixed_table` lint](https://kampfkarren.github.io/selene/lints/mixed_table.html), which will warn against mixed tables. ### Fixed - `string.pack` and `string.unpack` now have proper function signatures in the Lua 5.3 standard library. diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index 7d0d0618..51fcca18 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -27,6 +27,7 @@ - [incorrect_standard_library_use](./lints/incorrect_standard_library_use.md) - [manual_table_clone](./lints/manual_table_clone.md) - [mismatched_arg_count](./lints/mismatched_arg_count.md) + - [mixed_table](./lints/mixed_table.md) - [multiple_statements](./lints/multiple_statements.md) - [must_use](./lints/must_use.md) - [parenthese_conditions](./lints/parenthese_conditions.md) diff --git a/docs/src/lints/mixed_table.md b/docs/src/lints/mixed_table.md new file mode 100644 index 00000000..71e633d9 --- /dev/null +++ b/docs/src/lints/mixed_table.md @@ -0,0 +1,14 @@ +# mixed_table +## What it does +Checks for mixed tables (tables that act as both an array and dictionary). + +## Why this is bad +Mixed tables harms readability and are prone to bugs. There is almost always a better alternative. + +## Example +```lua +local foo = { + "array field", + bar = "dictionary field", +} +``` diff --git a/selene-lib/src/lib.rs b/selene-lib/src/lib.rs index 64d0c40c..77bb09c3 100644 --- a/selene-lib/src/lib.rs +++ b/selene-lib/src/lib.rs @@ -312,6 +312,7 @@ use_lints! { invalid_lint_filter: lints::invalid_lint_filter::InvalidLintFilterLint, manual_table_clone: lints::manual_table_clone::ManualTableCloneLint, mismatched_arg_count: lints::mismatched_arg_count::MismatchedArgCountLint, + mixed_table: lints::mixed_table::MixedTableLint, multiple_statements: lints::multiple_statements::MultipleStatementsLint, must_use: lints::must_use::MustUseLint, parenthese_conditions: lints::parenthese_conditions::ParentheseConditionsLint, diff --git a/selene-lib/src/lints.rs b/selene-lib/src/lints.rs index 27c30c09..fa42553c 100644 --- a/selene-lib/src/lints.rs +++ b/selene-lib/src/lints.rs @@ -23,6 +23,7 @@ pub mod ifs_same_cond; pub mod invalid_lint_filter; pub mod manual_table_clone; pub mod mismatched_arg_count; +pub mod mixed_table; pub mod multiple_statements; pub mod must_use; pub mod parenthese_conditions; diff --git a/selene-lib/src/lints/mixed_table.rs b/selene-lib/src/lints/mixed_table.rs new file mode 100644 index 00000000..4a588abf --- /dev/null +++ b/selene-lib/src/lints/mixed_table.rs @@ -0,0 +1,92 @@ +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 { + Ok(MixedTableLint) + } + + fn pass(&self, ast: &Ast, _: &Context, _: &AstContext) -> Vec { + 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 are not allowed".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, +} + +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", + ); + } +} diff --git a/selene-lib/tests/lints/mixed_table/mixed_table.lua b/selene-lib/tests/lints/mixed_table/mixed_table.lua new file mode 100644 index 00000000..54217795 --- /dev/null +++ b/selene-lib/tests/lints/mixed_table/mixed_table.lua @@ -0,0 +1,53 @@ +local bad = { + "", + a = b, +} + +bad = { + {}, + [a] = b, +} + +bad = { + a, + [""] = b, +} + +-- This is technically not a mixed table, but it's formatted like it harming readability +-- so it should still be linted +bad = { + 1, + [2] = b, +} + +bad = { + [a] = b, + [c] = d, + "", +} + +bad({ + a = b, + "", + c = d, +}) + +local good = { + a = b, + c = d, +} + +good = { + "", + a, +} + +good = { + [1] = a, + [3] = b, +} + +good({ + a = b, + c = d, +}) diff --git a/selene-lib/tests/lints/mixed_table/mixed_table.stderr b/selene-lib/tests/lints/mixed_table/mixed_table.stderr new file mode 100644 index 00000000..b557beaa --- /dev/null +++ b/selene-lib/tests/lints/mixed_table/mixed_table.stderr @@ -0,0 +1,54 @@ +error[mixed_table]: mixed tables are not allowed + ┌─ mixed_table.lua:2:5 + │ +2 │ ╭ "", +3 │ │ a = b, + │ ╰─────────^ + │ + = help: change this table to either an array or dictionary + +error[mixed_table]: mixed tables are not allowed + ┌─ mixed_table.lua:7:5 + │ +7 │ ╭ {}, +8 │ │ [a] = b, + │ ╰───────────^ + │ + = help: change this table to either an array or dictionary + +error[mixed_table]: mixed tables are not allowed + ┌─ mixed_table.lua:12:5 + │ +12 │ ╭ a, +13 │ │ [""] = b, + │ ╰────────────^ + │ + = help: change this table to either an array or dictionary + +error[mixed_table]: mixed tables are not allowed + ┌─ mixed_table.lua:19:5 + │ +19 │ ╭ 1, +20 │ │ [2] = b, + │ ╰───────────^ + │ + = help: change this table to either an array or dictionary + +error[mixed_table]: mixed tables are not allowed + ┌─ mixed_table.lua:25:5 + │ +25 │ ╭ [c] = d, +26 │ │ "", + │ ╰──────^ + │ + = help: change this table to either an array or dictionary + +error[mixed_table]: mixed tables are not allowed + ┌─ mixed_table.lua:30:5 + │ +30 │ ╭ a = b, +31 │ │ "", + │ ╰──────^ + │ + = help: change this table to either an array or dictionary +