Skip to content
Open
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
31 changes: 30 additions & 1 deletion src/formatters/assignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use crate::{
context::{create_indent_trivia, create_newline_trivia, Context},
fmt_symbol,
formatters::{
expression::{format_expression, format_var, hang_expression},
expression::{format_expression, format_var, hang_expression, is_brackets_string},
general::{
format_punctuated, format_punctuated_multiline, format_token_reference,
try_format_punctuated,
Expand Down Expand Up @@ -201,6 +201,29 @@ pub fn hang_at_equals_due_to_comments(
(equal_token, expression)
}

/// Whether the expression is a `..` chain whose leftmost operand is a multiline long string (`[[ ]]` / `[=[ ]=]`)
fn is_multiline_long_string_concat(expression: &Expression) -> bool {
fn leftmost(expression: &Expression) -> &Expression {
match expression {
Expression::BinaryOperator { lhs, .. } => leftmost(lhs),
_ => expression,
}
}

matches!(
expression,
Expression::BinaryOperator {
binop: full_moon::ast::BinOp::TwoDots(_),
..
}
) && match leftmost(expression) {
Expression::String(token) => {
is_brackets_string(leftmost(expression)) && token.token().to_string().contains('\n')
}
_ => false,
}
}

/// Attempts different formatting tactics on an expression list being assigned (`= foo, bar`), to find the best
/// formatting output.
fn attempt_assignment_tactics(
Expand Down Expand Up @@ -297,8 +320,14 @@ fn attempt_assignment_tactics(
let hanging_expr_list = hang_punctuated_list(ctx, expressions, shape);
let hanging_shape = shape.take_first_line(&strip_trivia(&hanging_expr_list));

// A `..` chain led by a multiline long string (e.g. `[[ ]]`) has a short first line, so the
// comparison above never hangs it, even when a later line is over budget. Only hang if that fixes it
if expression.has_inline_comments()
|| hanging_shape.used_width() < formatting_shape.used_width()
// Check the formatted expression (e.g. with parentheses removed), as that is what is output
|| (is_multiline_long_string_concat(expr_list.iter().next().unwrap())
&& shape.test_over_budget(&strip_trailing_trivia(&expr_list))
&& !shape.test_over_budget(&strip_trivia(&hanging_expr_list)))
{
(hanging_expr_list, equal_token)
} else {
Expand Down
30 changes: 30 additions & 0 deletions tests/inputs/concat-multiline-string-chain.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
local x = [[
box[0,0;1,1;]
dropdown[4,0.75;6.4;contributor_grouping;]] .. S("Group by name") .. "," .. S("Group by contribution") .. ";" .. mail.selected_idxs.contributor_grouping[name] .. [[;true]
]]
local y = "dropdown[4,0.75;6.4;contributor_grouping;" .. S("Group by name") .. "," .. S("Group by contribution") .. ";" .. mail.selected_idxs.contributor_grouping[name] .. ";true]"
local z = [[
short]] .. foo .. [[bar]]
x = [[
box[0,0;1,1;]
dropdown[4,0.75;6.4;contributor_grouping;]] .. S("Group by name") .. "," .. S("Group by contribution") .. ";" .. mail.selected_idxs.contributor_grouping[name] .. [[;true]
]]
-- regression: function-led multiline RHS must be unchanged
local f = (function()
return 1
end)() .. "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" .. b
-- regression: call-led multiline RHS (reduced from jest-lua TestScheduler) must be unchanged
local function g()
local message = ("%sYour test suite is leaking memory. Please ensure all references are cleaned.\n"):format(
tostring(chalk.red:bold("EXPERIMENTAL FEATURE!\n"))
) .. "\n" .. "There is a number of things that can leak memory:\n" .. " - Async operations that have not finished (e.g. fs.readFile).\n" .. " - Keeping references to the global scope."
end
-- regression: over-long line inside the long string itself; hanging cannot help, so must be unchanged
local s = [[
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
]] .. a .. b
-- parenthesised long string lead: parentheses are removed, so the chain must hang in a single pass
local p = ([[
foo
]]) .. "dropdown[4,0.75;6.4;contributor_grouping;" .. S("Group by name") .. "," .. S("Group by contribution") .. ";" .. mail.selected_idxs.contributor_grouping[name] .. [[;true]
]]
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
source: tests/tests.rs
expression: "format(&contents, LuaVersion::Lua51)"
input_file: tests/inputs/concat-multiline-string-chain.lua
---
local x = [[
box[0,0;1,1;]
dropdown[4,0.75;6.4;contributor_grouping;]]
.. S("Group by name")
.. ","
.. S("Group by contribution")
.. ";"
.. mail.selected_idxs.contributor_grouping[name]
.. [[;true]
]]
local y = "dropdown[4,0.75;6.4;contributor_grouping;"
.. S("Group by name")
.. ","
.. S("Group by contribution")
.. ";"
.. mail.selected_idxs.contributor_grouping[name]
.. ";true]"
local z = [[
short]] .. foo .. [[bar]]
x = [[
box[0,0;1,1;]
dropdown[4,0.75;6.4;contributor_grouping;]]
.. S("Group by name")
.. ","
.. S("Group by contribution")
.. ";"
.. mail.selected_idxs.contributor_grouping[name]
.. [[;true]
]]
-- regression: function-led multiline RHS must be unchanged
local f = (function()
return 1
end)() .. "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" .. b
-- regression: call-led multiline RHS (reduced from jest-lua TestScheduler) must be unchanged
local function g()
local message = ("%sYour test suite is leaking memory. Please ensure all references are cleaned.\n"):format(
tostring(chalk.red:bold("EXPERIMENTAL FEATURE!\n"))
) .. "\n" .. "There is a number of things that can leak memory:\n" .. " - Async operations that have not finished (e.g. fs.readFile).\n" .. " - Keeping references to the global scope."
end
-- regression: over-long line inside the long string itself; hanging cannot help, so must be unchanged
local s = [[
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
]] .. a .. b
-- parenthesised long string lead: parentheses are removed, so the chain must hang in a single pass
local p = [[
foo
]]
.. "dropdown[4,0.75;6.4;contributor_grouping;"
.. S("Group by name")
.. ","
.. S("Group by contribution")
.. ";"
.. mail.selected_idxs.contributor_grouping[name]
.. [[;true]
]]

10 changes: 10 additions & 0 deletions tests/test_concat_idempotence.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use stylua_lib::{format_code, Config, OutputVerification};

#[test]
fn test_concat_multiline_string_chain_idempotent() {
let input = include_str!("inputs/concat-multiline-string-chain.lua");
let format =
|code: &str| format_code(code, Config::default(), None, OutputVerification::None).unwrap();
let first = format(input);
assert_eq!(format(&first), first);
}