diff --git a/CHANGELOG.md b/CHANGELOG.md index a10b07c9..8c395586 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Fixed necessary parentheses being removed from the LHS of a binary operation when the expression is hung over multiple lines. This affected `(-X) ^ Y` (changing the meaning of the expression) and, in Luau, `(expr :: assertion) < Y` (causing a syntax error) + ## [2.5.2] - 2026-05-16 ### Fixed diff --git a/src/formatters/expression.rs b/src/formatters/expression.rs index 9f245b09..8f1f9d96 100644 --- a/src/formatters/expression.rs +++ b/src/formatters/expression.rs @@ -124,6 +124,15 @@ pub fn format_binop(ctx: &Context, binop: &BinOp, shape: Shape) -> BinOp { }) } +/// The context to use when formatting the expression on the LHS of the given binop +fn binary_lhs_context(binop: &BinOp) -> ExpressionContext { + if let BinOp::Caret(_) = binop { + ExpressionContext::BinaryLHSExponent + } else { + ExpressionContext::BinaryLHS + } +} + /// Check to determine whether expression parentheses are required, depending on the provided /// internal expression contained within the parentheses fn check_excess_parentheses(internal_expression: &Expression, context: ExpressionContext) -> bool { @@ -285,12 +294,7 @@ fn format_expression_internal( } } Expression::BinaryOperator { lhs, binop, rhs } => { - let context = if let BinOp::Caret(_) = binop { - ExpressionContext::BinaryLHSExponent - } else { - ExpressionContext::BinaryLHS - }; - let lhs = format_expression_internal(ctx, lhs, context, shape); + let lhs = format_expression_internal(ctx, lhs, binary_lhs_context(binop), shape); let binop = format_binop(ctx, binop, shape); let shape = shape.take_last_line(&lhs) + binop.to_string().len(); Expression::BinaryOperator { @@ -1184,6 +1188,10 @@ fn hang_binop_expression( new_binop = hang_binop(ctx, binop.to_owned(), shape, &rhs); } + // The context of the lhs is determined by the binop it is attached to, not by the + // context that this expression was hung in + let lhs_context = binary_lhs_context(&binop); + let (lhs, rhs) = match should_hang { true => { let lhs_shape = shape; @@ -1202,7 +1210,7 @@ fn hang_binop_expression( }, lhs_shape, lhs_range, - expression_context, + lhs_context, ), if contains_comments(&*rhs) { hang_binop_expression( @@ -1230,15 +1238,10 @@ fn hang_binop_expression( binop.clone(), shape, lhs_range, - expression_context, + lhs_context, ) } else { - let context = if let BinOp::Caret(_) = binop { - ExpressionContext::BinaryLHSExponent - } else { - ExpressionContext::BinaryLHS - }; - format_expression_internal(ctx, &lhs, context, lhs_shape) + format_expression_internal(ctx, &lhs, lhs_context, lhs_shape) }, hang_binop_expression( ctx, @@ -1265,15 +1268,10 @@ fn hang_binop_expression( binop.to_owned(), shape, lhs_range, - expression_context, + lhs_context, ) } else { - let context = if let BinOp::Caret(_) = binop { - ExpressionContext::BinaryLHSExponent - } else { - ExpressionContext::BinaryLHS - }; - format_expression_internal(ctx, &lhs, context, shape) + format_expression_internal(ctx, &lhs, lhs_context, shape) }; let rhs = if contains_comments(&*rhs) { @@ -1457,7 +1455,7 @@ fn format_hanging_expression_( binop.to_owned(), shape, lhs_range, - ExpressionContext::UnaryOrBinary, + binary_lhs_context(binop), ); let current_shape = shape.take_last_line(&lhs) + 1; // 1 = space before binop diff --git a/tests/inputs-luau/excess-parentheses-type-assertion.lua b/tests/inputs-luau/excess-parentheses-type-assertion.lua index 7d024f5c..97001f2c 100644 --- a/tests/inputs-luau/excess-parentheses-type-assertion.lua +++ b/tests/inputs-luau/excess-parentheses-type-assertion.lua @@ -1,3 +1,9 @@ local x = if (foo :: number) < bar then very + very + very + long + line + right + here + hopefully else lets + ensure + stylua + writes + this + out + using + multiple + lines + +local isNegative = someExtremelyLongVariableNameHere ~= nil and (someExtremelyLongVariableNameHereAsWellOkayThenFine :: number) < someExtremelyLongThresholdVariableNameHereTooRight + +if someExtremelyLongVariableNameHere ~= nil and (someExtremelyLongVariableNameHereAsWellOkayThenFine :: number) < someExtremelyLongThresholdVariableNameHereTooRight then + print(someExtremelyLongVariableNameHere) +end diff --git a/tests/inputs/excess-parentheses-dont-remove.lua b/tests/inputs/excess-parentheses-dont-remove.lua index e8bec809..8d62ecd3 100644 --- a/tests/inputs/excess-parentheses-dont-remove.lua +++ b/tests/inputs/excess-parentheses-dont-remove.lua @@ -6,3 +6,7 @@ local _ = (not true) and false -- https://github.com/JohnnyMorganz/StyLua/issues/623 -- Changes meaning local y = (-X) ^ Y + +-- The same holds when the expression is hung over multiple lines +local someVeryLongVariableNameHere = (-someExtremelyLongOperandNameOnTheLeft) ^ someOtherExtremelyLongOperandNameOnTheRight +local anotherVeryLongVariableNameHere = (not someExtremelyLongConditionNameOnTheLeft) and someOtherExtremelyLongConditionNameOnTheRight diff --git a/tests/snapshots/tests__luau@excess-parentheses-type-assertion.lua.snap b/tests/snapshots/tests__luau@excess-parentheses-type-assertion.lua.snap index 0417533a..891eaca4 100644 --- a/tests/snapshots/tests__luau@excess-parentheses-type-assertion.lua.snap +++ b/tests/snapshots/tests__luau@excess-parentheses-type-assertion.lua.snap @@ -7,3 +7,15 @@ snapshot_kind: text local x = if (foo :: number) < bar then very + very + very + long + line + right + here + hopefully else lets + ensure + stylua + writes + this + out + using + multiple + lines + +local isNegative = someExtremelyLongVariableNameHere ~= nil + and (someExtremelyLongVariableNameHereAsWellOkayThenFine :: number) + < someExtremelyLongThresholdVariableNameHereTooRight + +if + someExtremelyLongVariableNameHere ~= nil + and (someExtremelyLongVariableNameHereAsWellOkayThenFine :: number) + < someExtremelyLongThresholdVariableNameHereTooRight +then + print(someExtremelyLongVariableNameHere) +end diff --git a/tests/snapshots/tests__standard@excess-parentheses-dont-remove.lua.snap b/tests/snapshots/tests__standard@excess-parentheses-dont-remove.lua.snap index e929dfff..f904d091 100644 --- a/tests/snapshots/tests__standard@excess-parentheses-dont-remove.lua.snap +++ b/tests/snapshots/tests__standard@excess-parentheses-dont-remove.lua.snap @@ -11,3 +11,9 @@ local _ = (not true) and false -- Changes meaning local y = (-X) ^ Y +-- The same holds when the expression is hung over multiple lines +local someVeryLongVariableNameHere = (-someExtremelyLongOperandNameOnTheLeft) + ^ someOtherExtremelyLongOperandNameOnTheRight +local anotherVeryLongVariableNameHere = (not someExtremelyLongConditionNameOnTheLeft) + and someOtherExtremelyLongConditionNameOnTheRight + diff --git a/tests/snapshots/tests__standard@multiline-expression-comments-1.lua.snap b/tests/snapshots/tests__standard@multiline-expression-comments-1.lua.snap index e371005f..1e80cd60 100644 --- a/tests/snapshots/tests__standard@multiline-expression-comments-1.lua.snap +++ b/tests/snapshots/tests__standard@multiline-expression-comments-1.lua.snap @@ -17,7 +17,7 @@ end if name and ( - not strictFiltering + (not strictFiltering) and ( tokenTable[subgroup] or tokenTable[className]