diff --git a/CHANGELOG.md b/CHANGELOG.md index 251b7fb8..3e88d4fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed npm publishing by bumping Node.js from 16 to 22 in CI workflows to support npm trusted publishing - Luau: Fixed union/intersection type definitions not being hung when the type alone fits within the column width but the full line (including ` = `) exceeds it ([#1104](https://github.com/JohnnyMorganz/StyLua/issues/1104)) +- Luau: Fixed stray leading newlines not being removed from `local function` and `const function` declarations that have attributes (e.g. `@native`) at the start of a block ([#1109](https://github.com/JohnnyMorganz/StyLua/issues/1109)) ## [2.4.1] - 2026-04-06 diff --git a/src/formatters/block.rs b/src/formatters/block.rs index f813f913..a8aa564c 100644 --- a/src/formatters/block.rs +++ b/src/formatters/block.rs @@ -18,6 +18,8 @@ use crate::{ }, shape::Shape, }; +#[cfg(feature = "luau")] +use full_moon::ast::luau::LuauAttribute; use full_moon::ast::{ punctuated::Punctuated, Block, Expression, LastStmt, Prefix, Return, Stmt, Var, }; @@ -317,6 +319,20 @@ fn var_remove_leading_newline(var: Var) -> Var { } } +#[cfg(feature = "luau")] +fn strip_attribute_leading_newlines<'a>( + attributes: impl Iterator, +) -> Option> { + let mut cloned = attributes.cloned(); + let first = cloned.next()?; + let at_sign = first.at_sign(); + let leading_trivia = trivia_remove_leading_newlines(at_sign.leading_trivia().collect()); + let new_at_sign = at_sign.update_leading_trivia(FormatTriviaType::Replace(leading_trivia)); + let mut result = vec![first.with_at_sign(new_at_sign)]; + result.extend(cloned); + Some(result) +} + fn stmt_remove_leading_newlines(stmt: Stmt) -> Stmt { match stmt { Stmt::Assignment(assignment) => { @@ -363,12 +379,19 @@ fn stmt_remove_leading_newlines(stmt: Stmt) -> Stmt { local_assignment.local_token(), with_local_token ), - Stmt::LocalFunction(local_function) => update_first_token!( - LocalFunction, - local_function, - local_function.local_token(), - with_local_token - ), + Stmt::LocalFunction(local_function) => { + #[cfg(feature = "luau")] + if let Some(attributes) = strip_attribute_leading_newlines(local_function.attributes()) + { + return Stmt::LocalFunction(local_function.with_attributes(attributes)); + } + update_first_token!( + LocalFunction, + local_function, + local_function.local_token(), + with_local_token + ) + } Stmt::NumericFor(numeric_for) => update_first_token!( NumericFor, numeric_for, @@ -404,12 +427,19 @@ fn stmt_remove_leading_newlines(stmt: Stmt) -> Stmt { with_const_token ), #[cfg(feature = "luau")] - Stmt::ConstFunction(const_function) => update_first_token!( - ConstFunction, - const_function, - const_function.const_token(), - with_const_token - ), + Stmt::ConstFunction(const_function) => { + if let Some(attributes) = strip_attribute_leading_newlines(const_function.attributes()) + { + Stmt::ConstFunction(const_function.with_attributes(attributes)) + } else { + update_first_token!( + ConstFunction, + const_function, + const_function.const_token(), + with_const_token + ) + } + } #[cfg(feature = "luau")] Stmt::ExportedTypeDeclaration(exported_type_declaration) => update_first_token!( diff --git a/tests/inputs-luau/attributes-4.lua b/tests/inputs-luau/attributes-4.lua new file mode 100644 index 00000000..3abfdfb8 --- /dev/null +++ b/tests/inputs-luau/attributes-4.lua @@ -0,0 +1,13 @@ +do + +@native +local function foo() +end +end + +do + +@native +const function bar() +end +end diff --git a/tests/snapshots/tests__luau@attributes-4.lua.snap b/tests/snapshots/tests__luau@attributes-4.lua.snap new file mode 100644 index 00000000..a7c553ea --- /dev/null +++ b/tests/snapshots/tests__luau@attributes-4.lua.snap @@ -0,0 +1,15 @@ +--- +source: tests/tests.rs +assertion_line: 36 +expression: "format(&contents, LuaVersion::Luau)" +input_file: tests/inputs-luau/attributes-4.lua +--- +do + @native + local function foo() end +end + +do + @native + const function bar() end +end