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
- When using range formatting, semicolons outside of the provided range are no longer incorrectly formatted ([#1147](https://github.com/JohnnyMorganz/StyLua/issues/1147))
- Luau: Fixed sort requires not recognizing imports declared with `const`, e.g. `const Foo = require(path)`
- Luau: Fixed the `read`/`write` access modifier merging into the element type in array type shorthand, changing the meaning of the code (`{ read Foo }` was formatted as `{ readFoo }`) ([#1126](https://github.com/JohnnyMorganz/StyLua/issues/1126))
- Fixed single-line comments in CRLF files gaining an extra carriage return (`\r\r\n`) on every format when `line_endings = "Windows"`, and keeping their original `\r\n` when formatting to Unix line endings ([#1167](https://github.com/JohnnyMorganz/StyLua/pull/1167))

## [2.5.2] - 2026-05-16

Expand Down
25 changes: 10 additions & 15 deletions src/formatters/assignment.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
#[cfg(feature = "luau")]
use full_moon::ast::luau::{ConstAssignment, TypeSpecifier};
#[cfg(feature = "cfxlua")]
use full_moon::tokenizer::Symbol;
use full_moon::tokenizer::{Token, TokenReference};
use full_moon::{
ast::{
punctuated::{Pair, Punctuated},
Assignment, Call, Expression, FunctionArgs, FunctionCall, LocalAssignment, Suffix,
},
tokenizer::TokenType,
use full_moon::ast::{
punctuated::{Pair, Punctuated},
Assignment, Call, Expression, FunctionArgs, FunctionCall, LocalAssignment, Suffix,
};
use full_moon::tokenizer::TokenReference;
#[cfg(feature = "cfxlua")]
use full_moon::tokenizer::{Symbol, TokenType};

#[cfg(feature = "luau")]
use crate::formatters::general::format_symbol;
Expand Down Expand Up @@ -115,12 +112,10 @@ pub fn hang_equal_token(
))
}

let equal_token_trailing_trivia = equal_token
.trailing_trivia()
.filter(|x| trivia_util::trivia_is_comment(x))
.flat_map(|x| vec![Token::new(TokenType::spaces(1)), x.to_owned()])
.chain(equal_token_trailing_trivia.iter().map(|x| x.to_owned()))
.collect();
let equal_token_trailing_trivia =
trivia_util::space_prefixed_comments(equal_token.trailing_trivia())
.chain(equal_token_trailing_trivia.iter().map(|x| x.to_owned()))
.collect();

equal_token.update_trailing_trivia(FormatTriviaType::Replace(equal_token_trailing_trivia))
}
Expand Down
24 changes: 6 additions & 18 deletions src/formatters/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -634,15 +634,9 @@ pub fn format_block(ctx: &Context, block: &Block, shape: Shape) -> Block {
.skip(1) // Remove the newline at the end
.rev()
.cloned()
.chain(
semi.leading_trivia()
.chain(semi.trailing_trivia())
.filter(|token| trivia_util::trivia_is_comment(token))
.flat_map(|x| {
// Prepend a single space beforehand
vec![Token::new(TokenType::spaces(1)), x.to_owned()]
}),
)
.chain(trivia_util::space_prefixed_comments(
semi.leading_trivia().chain(semi.trailing_trivia()),
))
.chain(std::iter::once(create_newline_trivia(&ctx)))
.collect();

Expand Down Expand Up @@ -691,15 +685,9 @@ pub fn format_block(ctx: &Context, block: &Block, shape: Shape) -> Block {
.skip(1) // Remove the newline at the end
.rev()
.cloned()
.chain(
semi.leading_trivia()
.chain(semi.trailing_trivia())
.filter(|token| trivia_util::trivia_is_comment(token))
.flat_map(|x| {
// Prepend a single space beforehand
vec![Token::new(TokenType::spaces(1)), x.to_owned()]
}),
)
.chain(trivia_util::space_prefixed_comments(
semi.leading_trivia().chain(semi.trailing_trivia()),
))
.chain(std::iter::once(create_newline_trivia(&ctx)))
.collect();

Expand Down
16 changes: 4 additions & 12 deletions src/formatters/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,27 +315,19 @@ fn contained_span_comments(
) -> (Vec<Token>, Vec<Token>) {
// Get the leading and trailing comments from contained span and append them onto the expression
let (start_parens, end_parens) = contained_span.tokens();
let leading_comments = start_parens
.leading_trivia()
.filter(|token| trivia_util::trivia_is_comment(token))
let leading_comments = trivia_util::comments(start_parens.leading_trivia())
.flat_map(|x| {
vec![
create_indent_trivia(ctx, shape),
x.to_owned(),
x,
create_newline_trivia(ctx),
]
})
// .chain(std::iter::once(create_indent_trivia(ctx, shape)))
.collect();

let trailing_comments = end_parens
.trailing_trivia()
.filter(|token| trivia_util::trivia_is_comment(token))
.flat_map(|x| {
// Prepend a single space beforehand
vec![Token::new(TokenType::spaces(1)), x.to_owned()]
})
.collect();
let trailing_comments =
trivia_util::space_prefixed_comments(end_parens.trailing_trivia()).collect();
(leading_comments, trailing_comments)
}

Expand Down
6 changes: 1 addition & 5 deletions src/formatters/general.rs
Original file line number Diff line number Diff line change
Expand Up @@ -572,12 +572,8 @@ where
// Also add any trailing comments we have taken from the expression
let symbol = fmt_symbol!(ctx, punctuation, ",", shape);

let mut trailing_trivia: Vec<_> = symbol
.leading_trivia()
.filter(|trivia| trivia_util::trivia_is_comment(trivia))
.cloned()
let mut trailing_trivia: Vec<_> = trivia_util::comments(symbol.leading_trivia())
.flat_map(|x| {
// Prepend a single space beforehand
vec![
create_newline_trivia(ctx),
create_indent_trivia(ctx, shape),
Expand Down
67 changes: 25 additions & 42 deletions src/formatters/luau.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ use crate::{
UpdateLeadingTrivia, UpdateTrailingTrivia, UpdateTrivia,
},
trivia_util::{
contains_comments, contains_singleline_comments, spans_multiple_lines,
token_contains_comments, trivia_is_comment, trivia_is_newline, CommentSearch,
GetLeadingTrivia, GetTrailingTrivia,
comments, contains_comments, contains_singleline_comments, space_prefixed_comments,
spans_multiple_lines, token_contains_comments, trivia_is_comment, trivia_is_newline,
CommentSearch, GetLeadingTrivia, GetTrailingTrivia,
},
},
shape::Shape,
Expand Down Expand Up @@ -765,24 +765,16 @@ fn hang_type_info_binop(
// Get the leading comments of a binop, as we need to preserve them
// Intersperse a newline and indent trivia between them
// iter_intersperse is currently not available, so we need to do something different. Tracking issue: https://github.com/rust-lang/rust/issues/79524
let leading_comments = binop
.leading_trivia()
.filter(|token| trivia_is_comment(token))
let leading_comments = comments(binop.leading_trivia())
.flat_map(|x| {
vec![
create_newline_trivia(ctx),
create_indent_trivia(ctx, shape),
x.to_owned(),
x,
]
})
// If there are any comments trailing the BinOp, we need to move them to before the BinOp
.chain(
binop
.trailing_trivia()
.filter(|token| trivia_is_comment(token))
// Prepend a single space beforehand
.flat_map(|x| vec![Token::new(TokenType::spaces(1)), x.to_owned()]),
)
.chain(space_prefixed_comments(binop.trailing_trivia()))
// If there are any leading comments to the RHS expression, we need to move them to before the BinOp
.chain(next_comments.iter().flat_map(|x| {
vec![
Expand Down Expand Up @@ -1294,26 +1286,20 @@ fn format_type_declaration(
if let Some(generics) = generics {
let (start_arrow, end_arrow) = generics.arrows().tokens();

let type_name_comments = type_name
.trailing_trivia()
.chain(start_arrow.leading_trivia())
.filter(|token| trivia_is_comment(token))
.flat_map(|x| {
// Prepend a single space beforehand
vec![Token::new(TokenType::spaces(1)), x.to_owned()]
})
.collect::<Vec<_>>();
let type_name_comments = space_prefixed_comments(
type_name
.trailing_trivia()
.chain(start_arrow.leading_trivia()),
)
.collect::<Vec<_>>();
let type_name_comments_len = type_name_comments.len();

let arrow_comments = end_arrow
.trailing_trivia()
.chain(equal_token.leading_trivia())
.filter(|token| trivia_is_comment(token))
.flat_map(|x| {
// Prepend a single space beforehand
vec![Token::new(TokenType::spaces(1)), x.to_owned()]
})
.collect();
let arrow_comments = space_prefixed_comments(
end_arrow
.trailing_trivia()
.chain(equal_token.leading_trivia()),
)
.collect();

(
type_name.update_trailing_trivia(FormatTriviaType::Replace(type_name_comments)),
Expand All @@ -1334,18 +1320,15 @@ fn format_type_declaration(
))),
)
} else {
let comments = type_name
.trailing_trivia()
.chain(equal_token.leading_trivia())
.filter(|token| trivia_is_comment(token))
.flat_map(|x| {
// Prepend a single space beforehand
vec![Token::new(TokenType::spaces(1)), x.to_owned()]
})
.collect();
let name_comments = space_prefixed_comments(
type_name
.trailing_trivia()
.chain(equal_token.leading_trivia()),
)
.collect();

(
type_name.update_trailing_trivia(FormatTriviaType::Replace(comments)),
type_name.update_trailing_trivia(FormatTriviaType::Replace(name_comments)),
equal_token.update_leading_trivia(FormatTriviaType::Replace(vec![Token::new(
TokenType::spaces(1),
)])),
Expand Down
12 changes: 3 additions & 9 deletions src/formatters/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,19 +102,13 @@ fn handle_field_key_equals_comments<T: Node>(
let (key_leading_trivia, key_trailing_trivia) = key.surrounding_trivia();

// Take leading and trailing comments from the equal sign, and put it before the key
let equal_sign_comments = equal
.leading_trivia()
.chain(equal.trailing_trivia())
.filter(|token| trivia_util::trivia_is_comment(token));
let equal_sign_comments =
trivia_util::comments(equal.leading_trivia().chain(equal.trailing_trivia()));

// Join the key trailing comments with the equal sign comments, as we will move them to before the key.
// Also adds in the necessary whitespace trivia
let key_leading_comments = key_trailing_trivia
.iter()
.filter(|token| trivia_util::trivia_is_comment(token))
.map(|x| x.to_owned())
let key_leading_comments = trivia_util::comments(key_trailing_trivia.iter().copied())
.chain(equal_sign_comments)
.map(|x| x.to_owned())
.flat_map(|trivia| {
// Prepend an indent before the comment, and append a newline after the comments
vec![
Expand Down
53 changes: 40 additions & 13 deletions src/formatters/trivia_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@ pub trait GetLeadingTrivia {
}

fn leading_comments(&self) -> Vec<Token> {
self.leading_trivia()
.iter()
.filter(|token| trivia_is_comment(token))
.cloned()
.collect()
comments(self.leading_trivia().iter()).collect()
}
}

Expand All @@ -58,21 +54,52 @@ pub trait GetTrailingTrivia {
// Retrieves all the trailing comments from the token
// Prepends a space before each comment
fn trailing_comments_search(&self, search: CommentSearch) -> Vec<Token> {
self.trailing_trivia()
.iter()
.filter(|token| trivia_is_comment_search(token, search))
.flat_map(|x| {
// Prepend a single space beforehand
vec![Token::new(TokenType::spaces(1)), x.to_owned()]
})
.collect()
space_prefixed_comments(
self.trailing_trivia()
.iter()
.filter(|token| trivia_is_comment_search(token, search)),
)
.collect()
}

fn trailing_comments(&self) -> Vec<Token> {
self.trailing_comments_search(CommentSearch::All)
}
}

/// Selects the comments out of some trivia.
///
/// Comments extracted from trivia are re-emitted without going through `format_token`, so always
/// extract them through this (or [`space_prefixed_comments`]) rather than filtering by
/// [`trivia_is_comment`] directly, so that the source's line ending doesn't leak into the output.
pub fn comments<'a>(
trivia: impl Iterator<Item = &'a Token> + 'a,
) -> impl Iterator<Item = Token> + 'a {
trivia
.filter(|token| trivia_is_comment(token))
.map(strip_comment_line_ending)
}

/// Selects the comments out of some trivia, preceding each with a single space.
pub fn space_prefixed_comments<'a>(
trivia: impl Iterator<Item = &'a Token> + 'a,
) -> impl Iterator<Item = Token> + 'a {
comments(trivia).flat_map(|comment| [Token::new(TokenType::spaces(1)), comment])
}

/// A single-line comment is tokenised including the `\r` of a CRLF line ending, with the `\n` as
/// separate whitespace. Drop it, as the configured line ending is appended after the comment.
fn strip_comment_line_ending(token: &Token) -> Token {
match token.token_type() {
TokenType::SingleLineComment { comment } if comment.ends_with('\r') => {
Token::new(TokenType::SingleLineComment {
comment: comment.trim_end().into(),
})
}
_ => token.to_owned(),
}
}

pub fn trivia_is_whitespace(trivia: &Token) -> bool {
matches!(trivia.token_kind(), TokenKind::Whitespace)
}
Expand Down
Loading
Loading