diff --git a/CHANGELOG.md b/CHANGELOG.md index b7c822fb..4fbbd9c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Luau: fixed parentheses incorrectly removed in `(expr :: assertion) < foo` when multilining the expression, leading to a syntax error ([#940](https://github.com/JohnnyMorganz/StyLua/issues/940)) - Fixed panic when attempting to format a file outside of the current working directory when `--respect-ignores` is enabled ([#969](https://github.com/JohnnyMorganz/StyLua/pull/969)) +- Fixed unnecessary semicolons being introduced at the end of statements when incorrectly determined as ambiguous ([#963](https://github.com/JohnnyMorganz/StyLua/issues/963)) ## [2.0.2] - 2024-12-07 diff --git a/src/formatters/block.rs b/src/formatters/block.rs index a5e0db69..6d19a86e 100644 --- a/src/formatters/block.rs +++ b/src/formatters/block.rs @@ -473,18 +473,46 @@ fn var_has_parentheses(var: &Var) -> bool { } } +fn expression_ends_with_identifier_or_parentheses(expression: &Expression) -> bool { + match expression { + Expression::Parentheses { .. } => true, + Expression::FunctionCall(_) => true, + Expression::Var(_) => true, + Expression::BinaryOperator { rhs, .. } => { + expression_ends_with_identifier_or_parentheses(rhs) + } + Expression::UnaryOperator { expression, .. } => { + expression_ends_with_identifier_or_parentheses(expression) + } + _ => false, + } +} + +// Ambiguous syntax can only occur if the current statement ends with an identifier, function call, or parentheses +fn stmt_ends_with_identifier_or_parentheses(stmt: &Stmt) -> bool { + match stmt { + Stmt::Assignment(assignment) => match assignment.expressions().last() { + Some(pair) => expression_ends_with_identifier_or_parentheses(pair.value()), + None => false, + }, + Stmt::LocalAssignment(local_assignment) => match local_assignment.expressions().last() { + Some(pair) => expression_ends_with_identifier_or_parentheses(pair.value()), + None => false, + }, + Stmt::FunctionCall(_) => true, + Stmt::Repeat(repeat) => expression_ends_with_identifier_or_parentheses(repeat.until()), + _ => false, + } +} + fn check_stmt_requires_semicolon( stmt: &Stmt, next_stmt: Option<&&(Stmt, Option)>, ) -> bool { // Need to check next statement if it is a function call, with a parameters expression as the prefix // If so, removing a semicolon may lead to ambiguous syntax - // Ambiguous syntax can only occur if the current statement is a (Local)Assignment, FunctionCall or a Repeat block - match stmt { - Stmt::Assignment(_) - | Stmt::LocalAssignment(_) - | Stmt::FunctionCall(_) - | Stmt::Repeat(_) => match next_stmt { + stmt_ends_with_identifier_or_parentheses(stmt) + && match next_stmt { Some((Stmt::FunctionCall(function_call), _)) => match function_call.prefix() { Prefix::Expression(expression) => { matches!(&**expression, Expression::Parentheses { .. }) @@ -500,9 +528,7 @@ fn check_stmt_requires_semicolon( var_has_parentheses(compound_assignment.lhs()) } _ => false, - }, - _ => false, - } + } } /// Formats a block node. Note: the given shape to the block formatter should already be at the correct indentation level diff --git a/tests/inputs/ambiguous-syntax-2.lua b/tests/inputs/ambiguous-syntax-2.lua new file mode 100644 index 00000000..0069cea3 --- /dev/null +++ b/tests/inputs/ambiguous-syntax-2.lua @@ -0,0 +1,20 @@ +-- https://github.com/JohnnyMorganz/StyLua/issues/963 +local value = nil + +(Foo):Call() + +local tbl = {} + +(Foo):Call() + +local x = 1 + +(Foo):Call() + +local x = "value" + +(Foo):Call() + +local x = function() end + +(Foo):Call() diff --git a/tests/inputs/ambiguous-syntax-3.lua b/tests/inputs/ambiguous-syntax-3.lua new file mode 100644 index 00000000..bab928bc --- /dev/null +++ b/tests/inputs/ambiguous-syntax-3.lua @@ -0,0 +1,17 @@ +local x = call ""; +(foo or bar and baz)(bar) + +local x = call {}; +(foo or bar and baz)(bar) + +local x = identifier; +(foo or bar and baz)(bar) + +local x = (identifier); +(foo or bar and baz)(bar) + +local x = x.y; +(foo or bar and baz)(bar) + +local x = x["y"]; +(foo or bar and baz)(bar) diff --git a/tests/snapshots/tests__standard@ambiguous-syntax-2.lua.snap b/tests/snapshots/tests__standard@ambiguous-syntax-2.lua.snap new file mode 100644 index 00000000..2d903745 --- /dev/null +++ b/tests/snapshots/tests__standard@ambiguous-syntax-2.lua.snap @@ -0,0 +1,26 @@ +--- +source: tests/tests.rs +expression: "format(&contents, LuaVersion::Lua51)" +input_file: tests/inputs/ambiguous-syntax-2.lua +snapshot_kind: text +--- +-- https://github.com/JohnnyMorganz/StyLua/issues/963 +local value = nil + +(Foo):Call() + +local tbl = {} + +(Foo):Call() + +local x = 1 + +(Foo):Call() + +local x = "value" + +(Foo):Call() + +local x = function() end + +(Foo):Call() diff --git a/tests/snapshots/tests__standard@ambiguous-syntax-3.lua.snap b/tests/snapshots/tests__standard@ambiguous-syntax-3.lua.snap new file mode 100644 index 00000000..277a926a --- /dev/null +++ b/tests/snapshots/tests__standard@ambiguous-syntax-3.lua.snap @@ -0,0 +1,23 @@ +--- +source: tests/tests.rs +expression: "format(&contents, LuaVersion::Lua51)" +input_file: tests/inputs/ambiguous-syntax-3.lua +snapshot_kind: text +--- +local x = call(""); +(foo or bar and baz)(bar) + +local x = call({}); +(foo or bar and baz)(bar) + +local x = identifier; +(foo or bar and baz)(bar) + +local x = identifier; +(foo or bar and baz)(bar) + +local x = x.y; +(foo or bar and baz)(bar) + +local x = x["y"]; +(foo or bar and baz)(bar) diff --git a/tests/snapshots/tests__standard@excess-parentheses.lua.snap b/tests/snapshots/tests__standard@excess-parentheses.lua.snap index cb6e4d75..e3acdc98 100644 --- a/tests/snapshots/tests__standard@excess-parentheses.lua.snap +++ b/tests/snapshots/tests__standard@excess-parentheses.lua.snap @@ -1,6 +1,8 @@ --- source: tests/tests.rs -expression: format(&contents) +expression: "format(&contents, LuaVersion::Lua51)" +input_file: tests/inputs/excess-parentheses.lua +snapshot_kind: text --- local x something(x) @@ -11,7 +13,7 @@ local z = (...) == nil and foo or bar local foo = not (bar and baz) local bar = #bar and baz local cond = condition and (not object or object.Value == y) -local baz = (-4 + 3) * 2; +local baz = (-4 + 3) * 2 ({}):foo(); ("hello"):format() @@ -34,4 +36,3 @@ local y = "hello" local z = function() return true end -