Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

- Fixed formatting of method call chain when there is a comment between the colon token `:` and the function name ([#890](https://github.com/JohnnyMorganz/StyLua/issues/890))
- Luau: Fixed incorrect removal of semicolon before compound assignment with parentheses leading to ambiguous syntax error ([#885](https://github.com/JohnnyMorganz/StyLua/issues/885))

## [0.20.0] - 2024-01-20

Expand Down
81 changes: 45 additions & 36 deletions src/formatters/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,50 @@ fn last_stmt_remove_leading_newlines(last_stmt: LastStmt) -> LastStmt {
}
}

fn var_has_parentheses(var: &Var) -> bool {
match var {
Var::Expression(var_expression) => match var_expression.prefix() {
Prefix::Expression(expression) => {
matches!(&**expression, Expression::Parentheses { .. })
}
_ => false,
},
_ => false,
}
}

fn check_stmt_requires_semicolon(
stmt: &Stmt,
next_stmt: Option<&&(Stmt, Option<TokenReference>)>,
) -> 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 {
Some((Stmt::FunctionCall(function_call), _)) => match function_call.prefix() {
Prefix::Expression(expression) => {
matches!(&**expression, Expression::Parentheses { .. })
}
_ => false,
},
Some((Stmt::Assignment(assignment), _)) => match assignment.variables().iter().next() {
Some(var) => var_has_parentheses(var),
_ => false,
},
#[cfg(feature = "luau")]
Some((Stmt::CompoundAssignment(compound_assignment), _)) => {
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
pub fn format_block(ctx: &Context, block: &Block, shape: Shape) -> Block {
let mut ctx = *ctx;
Expand All @@ -469,44 +513,9 @@ pub fn format_block(ctx: &Context, block: &Block, shape: Shape) -> Block {
found_first_stmt = true;
}

// 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
let require_semicolon = match stmt {
Stmt::Assignment(_)
| Stmt::LocalAssignment(_)
| Stmt::FunctionCall(_)
| Stmt::Repeat(_) => {
let next_stmt = stmt_iterator.peek();
match next_stmt {
Some((Stmt::FunctionCall(function_call), _)) => match function_call.prefix() {
Prefix::Expression(expression) => {
matches!(&**expression, Expression::Parentheses { .. })
}
_ => false,
},
Some((Stmt::Assignment(assignment), _)) => {
match assignment.variables().iter().next() {
Some(Var::Expression(var_expression)) => {
match var_expression.prefix() {
Prefix::Expression(expression) => {
matches!(&**expression, Expression::Parentheses { .. })
}
_ => false,
}
}
_ => false,
}
}
_ => false,
}
}
_ => false,
};

// If we have a semicolon, we need to push all the trailing trivia from the statement
// and move it to the end of the semicolon
let semicolon = match require_semicolon {
let semicolon = match check_stmt_requires_semicolon(&stmt, stmt_iterator.peek()) {
true => {
let (updated_stmt, trivia) = trivia_util::get_stmt_trailing_trivia(stmt);
stmt = updated_stmt;
Expand Down
8 changes: 8 additions & 0 deletions tests/inputs-luau/compound-assignment-ambiguous-syntax.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- https://github.com/JohnnyMorganz/StyLua/issues/885

local function foo()
return { b = "foo" }
end

local a = foo();
(a :: any).b ..= "bar"
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
source: tests/tests.rs
expression: format(&contents)
input_file: tests/inputs-luau/compound-assignment-ambiguous-syntax.lua
---
-- https://github.com/JohnnyMorganz/StyLua/issues/885

local function foo()
return { b = "foo" }
end

local a = foo();
(a :: any).b ..= "bar"