From c05d264a6a5e003f213bf5432223fc65c770bed8 Mon Sep 17 00:00:00 2001 From: Lay-quq <187484661+Lay-quq@users.noreply.github.com> Date: Sun, 25 Oct 2026 20:35:29 +0800 Subject: [PATCH] Keep multiline function arguments hugged with Input collapse mode With `collapse_simple_statement = "Input"`, the function call argument heuristic checked whether an anonymous function argument was expanded using the reformatted argument. The reformatted node no longer has the input positions that `Input` mode relies on, so a multiline function was treated as collapsed, its single-line width counted against the column width, and the whole call was expanded. Use the original argument's function body for that check instead. Fixes #1175 --- CHANGELOG.md | 4 +++ src/formatters/functions.rs | 11 +++++-- tests/tests.rs | 62 +++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ac6dc18..24b7501b 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 function calls with a multiline anonymous function argument being expanded when `collapse_simple_statement = "Input"`, instead of keeping the function hugged to the call parentheses ([#1175](https://github.com/JohnnyMorganz/StyLua/issues/1175)) + ## [2.6.0] - 2026-10-10 ### Added diff --git a/src/formatters/functions.rs b/src/formatters/functions.rs index ab3244e1..c10cd431 100644 --- a/src/formatters/functions.rs +++ b/src/formatters/functions.rs @@ -260,12 +260,17 @@ fn function_args_multiline_heuristic( // Use state values to determine the type of arguments we have seen so far let mut current_state = ArgumentState::new(); - for pair in first_iter_formatted_arguments { + for (pair, original_argument) in first_iter_formatted_arguments.zip(arguments.iter()) { let argument = pair.value(); match argument { Expression::Function(anonymous_function) => { - // Check to see whether it has been expanded - let is_expanded = !should_collapse_function_body(ctx, anonymous_function.body()); + // Check to see whether it has been expanded. Use the original function body where possible: + // the formatted one has lost the input positions that `CollapseSimpleStatement::Input` relies on + let function_body = match original_argument { + Expression::Function(original_function) => original_function.body(), + _ => anonymous_function.body(), + }; + let is_expanded = !should_collapse_function_body(ctx, function_body); if is_expanded { // If we have a mixture of multiline args, and other arguments // Then the function args should be expanded diff --git a/tests/tests.rs b/tests/tests.rs index 908a390f..80e104d1 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -263,6 +263,68 @@ end) ); } +const COLLAPSE_FUNCTION_ARGUMENT_INPUT: &str = r#"pcall(function() + return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa == bbb +end, 5) +pcall(function() return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa == bbb end, 5) +foo(1, function() + return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa == bbb +end) +pcall(function() return x end, 5) +"#; + +#[test] +fn test_collapse_simple_statement_input_keeps_multiline_function_argument_hug() { + let config = Config { + collapse_simple_statement: CollapseSimpleStatement::Input, + ..Config::default() + }; + + insta::assert_snapshot!( + format_code(COLLAPSE_FUNCTION_ARGUMENT_INPUT, config, None, OutputVerification::None).unwrap(), + @r###" + pcall(function() + return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa == bbb + end, 5) + pcall( + function() return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa == bbb end, + 5 + ) + foo(1, function() + return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa == bbb + end) + pcall(function() return x end, 5) + "### + ); +} + +#[test] +fn test_collapse_simple_statement_always_function_argument_hug() { + let config = Config { + collapse_simple_statement: CollapseSimpleStatement::Always, + ..Config::default() + }; + + insta::assert_snapshot!( + format_code(COLLAPSE_FUNCTION_ARGUMENT_INPUT, config, None, OutputVerification::None).unwrap(), + @r###" + pcall( + function() return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa == bbb end, + 5 + ) + pcall( + function() return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa == bbb end, + 5 + ) + foo( + 1, + function() return aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa == bbb end + ) + pcall(function() return x end, 5) + "### + ); +} + #[test] fn test_preserve_block_newline_gaps() { insta::glob!("inputs-preserve-block-newline-gaps/*.lua", |path| {