diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..e9cdcd94 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.stderr text eol=lf diff --git a/CHANGELOG.md b/CHANGELOG.md index f481e98d..a638fef5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### Changed - Removed types from `debug.traceback` arguments in the Lua 5.1 standard library - Made 4th argument to `CFrame.fromMatrix` optional (#113) +- Made standard library aware that functions and `...` can return multiple values ## [0.6.0] - 2020-04-21 ### Added diff --git a/selene-lib/src/rules/standard_library.rs b/selene-lib/src/rules/standard_library.rs index 2fd8a75e..9b5013c0 100644 --- a/selene-lib/src/rules/standard_library.rs +++ b/selene-lib/src/rules/standard_library.rs @@ -527,12 +527,32 @@ impl Visitor<'_> for StandardLibraryVisitor<'_> { } } - let any_are_vararg = argument_types.iter().any(|(_, argument_type)| { - argument_type.as_ref() == Some(&PassedArgumentType::Primitive(ArgumentType::Vararg)) - }); + let mut maybe_more_arguments = false; + + if let ast::FunctionArgs::Parentheses { arguments, .. } = function_args { + if let Some(ast::punctuated::Pair::End(last)) = arguments.last() { + if let ast::Expression::Value { value, .. } = last { + match &**value { + ast::Value::FunctionCall(_) => { + maybe_more_arguments = true; + } + ast::Value::Symbol(token_ref) => { + if let TokenType::Symbol { symbol } = token_ref.token().token_type() { + if symbol == &full_moon::tokenizer::Symbol::Ellipse { + maybe_more_arguments = true; + } + } + } + _ => {} + } + } + } + }; - if (!any_are_vararg && argument_types.len() < expected_args) - || (!vararg && argument_types.len() > max_args) + let arguments_length = argument_types.len(); + + if (arguments_length < expected_args && !maybe_more_arguments) + || (!vararg && arguments_length > max_args) { self.diagnostics.push(Diagnostic::new( "incorrect_standard_library_use", @@ -668,6 +688,15 @@ mod tests { ); } + #[test] + fn test_assert() { + test_lint( + StandardLibraryLint::new(()).unwrap(), + "standard_library", + "assert", + ); + } + #[test] fn test_bad_call_signatures() { test_lint( diff --git a/selene-lib/tests/lints/standard_library/assert.lua b/selene-lib/tests/lints/standard_library/assert.lua new file mode 100644 index 00000000..06473166 --- /dev/null +++ b/selene-lib/tests/lints/standard_library/assert.lua @@ -0,0 +1,8 @@ +assert(true, "message") +assert(call()) +assert(call(), "this is ok") +assert(...) + +assert(true) +assert(true, "message", call()) +assert(true, "message", ...) diff --git a/selene-lib/tests/lints/standard_library/assert.std.toml b/selene-lib/tests/lints/standard_library/assert.std.toml new file mode 100644 index 00000000..d4f3c313 --- /dev/null +++ b/selene-lib/tests/lints/standard_library/assert.std.toml @@ -0,0 +1,6 @@ +[[assert.args]] +type = "any" + +[[assert.args]] +type = "string" +required = "A failed assertion without a message is unhelpful to users." diff --git a/selene-lib/tests/lints/standard_library/assert.stderr b/selene-lib/tests/lints/standard_library/assert.stderr new file mode 100644 index 00000000..f80390f0 --- /dev/null +++ b/selene-lib/tests/lints/standard_library/assert.stderr @@ -0,0 +1,24 @@ +error[incorrect_standard_library_use]: standard library function `assert` requires 2 parameters, 1 passed + + ┌── assert.lua:6:1 ─── + │ + 6 │ assert(true) + │ ^^^^^^^^^^^^ + │ + +error[incorrect_standard_library_use]: standard library function `assert` requires 2 parameters, 3 passed + + ┌── assert.lua:7:1 ─── + │ + 7 │ assert(true, "message", call()) + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ + +error[incorrect_standard_library_use]: standard library function `assert` requires 2 parameters, 3 passed + + ┌── assert.lua:8:1 ─── + │ + 8 │ assert(true, "message", ...) + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + │ +