From 57e31a1c8b31b71c9f3cb7c5084b328868e1a638 Mon Sep 17 00:00:00 2001 From: Mohamed Sobh Date: Sat, 20 Jun 2026 10:29:07 -0400 Subject: [PATCH] feat: add `trailing_comment_spacing` option to preserve comment alignment Add a new configuration option `trailing_comment_spacing` that controls how whitespace before trailing inline comments is handled: - `Compress` (default): always use a single space before comments - `Preserve`: keep the original spacing before comments This allows users to maintain aligned comments without the formatter collapsing the spacing. Configurable via stylua.toml, CLI flag, and EditorConfig. Also document all supported EditorConfig property mappings in the README. --- README.md | 19 +++++++++ src/cli/config.rs | 3 ++ src/cli/opt.rs | 10 ++++- src/editorconfig.rs | 14 ++++++- src/formatters/general.rs | 39 ++++++++++++++----- src/lib.rs | 18 +++++++++ .../aligned-comments.lua | 3 ++ .../varying-spacing.lua | 3 ++ ..._comment_spacing@aligned-comments.lua.snap | 10 +++++ ...g_comment_spacing@varying-spacing.lua.snap | 10 +++++ tests/tests.rs | 19 ++++++++- 11 files changed, 136 insertions(+), 12 deletions(-) create mode 100644 tests/inputs-preserve-trailing-comment-spacing/aligned-comments.lua create mode 100644 tests/inputs-preserve-trailing-comment-spacing/varying-spacing.lua create mode 100644 tests/snapshots/tests__preserve_trailing_comment_spacing@aligned-comments.lua.snap create mode 100644 tests/snapshots/tests__preserve_trailing_comment_spacing@varying-spacing.lua.snap diff --git a/README.md b/README.md index 5af9d615..1fe11fe2 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,23 @@ If not found, we search for an `.editorconfig` file, otherwise fall back to the This feature can be disabled using `--no-editorconfig`. See [EditorConfig](https://editorconfig.org/) for more details. +The following EditorConfig properties are supported: + +| EditorConfig Property | Maps to | +| --------------------------------- | ---------------------------- | +| `end_of_line` | `line_endings` | +| `indent_style` | `indent_type` | +| `indent_size` | `indent_width` | +| `max_line_length` | `column_width` | +| `quote_type` | `quote_style` | +| `call_parentheses` | `call_parentheses` | +| `space_after_function_names` | `space_after_function_names` | +| `collapse_simple_statement` | `collapse_simple_statement` | +| `sort_requires` | `sort_requires.enabled` | +| `stylua_syntax` | `syntax` | +| `stylua_block_newline_gaps` | `block_newline_gaps` | +| `stylua_trailing_comment_spacing` | `trailing_comment_spacing` | + Use `--config-path ` to provide a custom path to the configuration. If the file provided is not found/malformed, StyLua will exit with an error. @@ -319,6 +336,7 @@ StyLua only offers the following options: | `call_parentheses` | `Always` | Whether parentheses should be applied on function calls with a single string/table argument. Possible options: `Always`, `NoSingleString`, `NoSingleTable`, `None`, `Input`. `Always` applies parentheses in all cases. `NoSingleString` omits parentheses on calls with a single string argument. Similarly, `NoSingleTable` omits parentheses on calls with a single table argument. `None` omits parentheses in both cases. Note: parentheses are still kept in situations where removal can lead to obscurity (e.g. `foo "bar".setup -> foo("bar").setup`, since the index is on the call result, not the string). `Input` removes all automation and preserves parentheses only if they were present in input code: consistency is not enforced. | | `space_after_function_names` | `Never` | Specify whether to add a space between the function name and parentheses. Possible options: `Never`, `Definitions`, `Calls`, or `Always` | | `block_newline_gaps` | `Never` | Specify whether to preserve leading and trailing newline gaps for blocks. Possible options: `Never`, `Preserve` | +| `trailing_comment_spacing` | `Compress` | Specify whether to preserve the original spacing before trailing inline comments. Possible options: `Compress` (always use a single space), `Preserve` (keep original spacing) | | `collapse_simple_statement` | `Never` | Specify whether to collapse simple statements. Possible options: `Never`, `FunctionOnly`, `ConditionalOnly`, or `Always` | Default `stylua.toml`, note you do not need to explicitly specify each option if you want to use the defaults: @@ -334,6 +352,7 @@ call_parentheses = "Always" collapse_simple_statement = "Never" space_after_function_names = "Never" block_newline_gaps = "Never" +trailing_comment_spacing = "Compress" [sort_requires] enabled = false diff --git a/src/cli/config.rs b/src/cli/config.rs index 4b8591a2..4e4483b3 100644 --- a/src/cli/config.rs +++ b/src/cli/config.rs @@ -292,6 +292,9 @@ fn load_overrides(config: Config, opt: &Opt) -> Config { if let Some(preserve_block_newline_gaps) = opt.format_opts.preserve_block_newline_gaps { new_config.block_newline_gaps = preserve_block_newline_gaps.into(); }; + if let Some(trailing_comment_spacing) = opt.format_opts.trailing_comment_spacing { + new_config.trailing_comment_spacing = trailing_comment_spacing.into(); + }; if opt.format_opts.sort_requires { new_config.sort_requires = SortRequiresConfig { enabled: true } } diff --git a/src/cli/opt.rs b/src/cli/opt.rs index b9d8499b..59ba2284 100644 --- a/src/cli/opt.rs +++ b/src/cli/opt.rs @@ -2,7 +2,7 @@ use clap::{ArgEnum, StructOpt}; use std::path::PathBuf; use stylua_lib::{ BlockNewlineGaps, CallParenType, CollapseSimpleStatement, IndentType, LineEndings, LuaVersion, - QuoteStyle, SpaceAfterFunctionNames, + QuoteStyle, SpaceAfterFunctionNames, TrailingCommentSpacing, }; lazy_static::lazy_static! { @@ -202,6 +202,9 @@ pub struct FormatOpts { pub sort_requires: bool, #[structopt(long, arg_enum, ignore_case = true)] pub space_after_function_names: Option, + /// Specify whether to preserve spacing before trailing comments. + #[structopt(long, arg_enum, ignore_case = true)] + pub trailing_comment_spacing: Option, } // Convert [`stylua_lib::Config`] enums into clap-friendly enums @@ -295,6 +298,11 @@ convert_enum!(SpaceAfterFunctionNames, ArgSpaceAfterFunctionNames, { Always, }); +convert_enum!(TrailingCommentSpacing, ArgTrailingCommentSpacing, { + Compress, + Preserve, +}); + #[cfg(test)] mod tests { use super::Opt; diff --git a/src/editorconfig.rs b/src/editorconfig.rs index 1dfb4fc8..184231dd 100644 --- a/src/editorconfig.rs +++ b/src/editorconfig.rs @@ -1,6 +1,6 @@ use crate::{ BlockNewlineGaps, CallParenType, CollapseSimpleStatement, Config, IndentType, LineEndings, - LuaVersion, QuoteStyle, SortRequiresConfig, SpaceAfterFunctionNames, + LuaVersion, QuoteStyle, SortRequiresConfig, SpaceAfterFunctionNames, TrailingCommentSpacing, }; use ec4rs::{ properties_of, @@ -106,6 +106,12 @@ property_choice! { (Preserve, "preserve") } +property_choice! { + StyluaTrailingCommentSpacingChoice, "stylua_trailing_comment_spacing"; + (Compress, "compress"), + (Preserve, "preserve") +} + // Override StyLua config with EditorConfig properties fn load(mut config: Config, properties: &Properties) -> Config { if let Ok(end_of_line) = properties.get::() { @@ -202,6 +208,12 @@ fn load(mut config: Config, properties: &Properties) -> Config { StyluaBlockNewlineGapsChoice::Preserve => BlockNewlineGaps::Preserve, }; } + if let Ok(trailing_comment_spacing) = properties.get::() { + config.trailing_comment_spacing = match trailing_comment_spacing { + StyluaTrailingCommentSpacingChoice::Compress => TrailingCommentSpacing::Compress, + StyluaTrailingCommentSpacingChoice::Preserve => TrailingCommentSpacing::Preserve, + }; + } config } diff --git a/src/formatters/general.rs b/src/formatters/general.rs index ba57d1e2..b3283dc4 100644 --- a/src/formatters/general.rs +++ b/src/formatters/general.rs @@ -12,7 +12,7 @@ use crate::{ }, }, shape::Shape, - QuoteStyle, + QuoteStyle, TrailingCommentSpacing, }; use full_moon::node::Node; use full_moon::tokenizer::{StringLiteralQuoteType, Token, TokenKind, TokenReference, TokenType}; @@ -236,8 +236,10 @@ pub fn format_token( trailing_trivia = Some(vec![create_newline_trivia(ctx)]); } FormatTokenType::TrailingTrivia => { - // Add a space before the comment - leading_trivia = Some(vec![Token::new(TokenType::spaces(1))]); + // Add a space before the comment (unless preserving original spacing) + if ctx.config().trailing_comment_spacing == TrailingCommentSpacing::Compress { + leading_trivia = Some(vec![Token::new(TokenType::spaces(1))]); + } } _ => (), } @@ -308,12 +310,31 @@ fn load_token_trivia( } } FormatTokenType::TrailingTrivia => { - // If the next trivia is a MultiLineComment, and this whitespace is just spacing, then we - // will preserve a single space - if let Some(next_trivia) = trivia_iter.peek() { - if let TokenType::MultiLineComment { .. } = next_trivia.token_type() { - if !characters.contains('\n') { - token_trivia.push(Token::new(TokenType::spaces(1))) + // If the next trivia is a comment, preserve original spacing if configured + if ctx.config().trailing_comment_spacing == TrailingCommentSpacing::Preserve + { + if let Some(next_trivia) = trivia_iter.peek() { + match next_trivia.token_type() { + TokenType::SingleLineComment { .. } + | TokenType::MultiLineComment { .. } => { + if !characters.contains('\n') { + token_trivia.push(Token::new(TokenType::Whitespace { + characters: characters.to_owned(), + })); + } + } + _ => (), + } + } + } else { + // If the next trivia is a MultiLineComment, and this whitespace is just spacing, then we + // will preserve a single space + if let Some(next_trivia) = trivia_iter.peek() { + if let TokenType::MultiLineComment { .. } = next_trivia.token_type() + { + if !characters.contains('\n') { + token_trivia.push(Token::new(TokenType::spaces(1))) + } } } } diff --git a/src/lib.rs b/src/lib.rs index 7ca7a77f..2e67ff54 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -166,6 +166,19 @@ pub enum BlockNewlineGaps { Preserve, } +/// How to handle spacing before trailing inline comments. +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Deserialize)] +#[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "fromstr", derive(strum::EnumString))] +pub enum TrailingCommentSpacing { + /// Always use a single space before trailing comments + #[default] + Compress, + /// Preserve the original spacing before trailing comments + Preserve, +} + /// An optional formatting range. /// If provided, only content within these boundaries (inclusive) will be formatted. /// Both boundaries are optional, and are given as byte offsets from the beginning of the file. @@ -279,6 +292,10 @@ pub struct Config { /// * if space_after_function_names is set to [`SpaceAfterFunctionNames::Calls`] a space is used only for calls. /// * if space_after_function_names is set to [`SpaceAfterFunctionNames::Always`] a space is used for both definitions and calls. pub space_after_function_names: SpaceAfterFunctionNames, + /// How to handle spacing before trailing inline comments. + /// * if set to [`TrailingCommentSpacing::Compress`] a single space is always used before trailing comments. + /// * if set to [`TrailingCommentSpacing::Preserve`] the original spacing before trailing comments is preserved. + pub trailing_comment_spacing: TrailingCommentSpacing, } #[cfg_attr(all(target_arch = "wasm32", feature = "wasm-bindgen"), wasm_bindgen)] @@ -305,6 +322,7 @@ impl Default for Config { sort_requires: SortRequiresConfig::default(), space_after_function_names: SpaceAfterFunctionNames::default(), block_newline_gaps: BlockNewlineGaps::default(), + trailing_comment_spacing: TrailingCommentSpacing::default(), } } } diff --git a/tests/inputs-preserve-trailing-comment-spacing/aligned-comments.lua b/tests/inputs-preserve-trailing-comment-spacing/aligned-comments.lua new file mode 100644 index 00000000..341c94e1 --- /dev/null +++ b/tests/inputs-preserve-trailing-comment-spacing/aligned-comments.lua @@ -0,0 +1,3 @@ +local x = 1 -- x value +local y = 2 -- y value +local longname = 3 -- long name diff --git a/tests/inputs-preserve-trailing-comment-spacing/varying-spacing.lua b/tests/inputs-preserve-trailing-comment-spacing/varying-spacing.lua new file mode 100644 index 00000000..825c6eac --- /dev/null +++ b/tests/inputs-preserve-trailing-comment-spacing/varying-spacing.lua @@ -0,0 +1,3 @@ +local a = 1 -- single space +local b = 2 -- two spaces +local c = 3 -- four spaces diff --git a/tests/snapshots/tests__preserve_trailing_comment_spacing@aligned-comments.lua.snap b/tests/snapshots/tests__preserve_trailing_comment_spacing@aligned-comments.lua.snap new file mode 100644 index 00000000..44404059 --- /dev/null +++ b/tests/snapshots/tests__preserve_trailing_comment_spacing@aligned-comments.lua.snap @@ -0,0 +1,10 @@ +--- +source: tests/tests.rs +assertion_line: 140 +expression: "format_code(&contents, Config\n{\n trailing_comment_spacing: TrailingCommentSpacing::Preserve,\n ..Config::default()\n}, None, OutputVerification::None).unwrap()" +input_file: tests/inputs-preserve-trailing-comment-spacing/aligned-comments.lua +--- +local x = 1 -- x value +local y = 2 -- y value +local longname = 3 -- long name + diff --git a/tests/snapshots/tests__preserve_trailing_comment_spacing@varying-spacing.lua.snap b/tests/snapshots/tests__preserve_trailing_comment_spacing@varying-spacing.lua.snap new file mode 100644 index 00000000..02942eb4 --- /dev/null +++ b/tests/snapshots/tests__preserve_trailing_comment_spacing@varying-spacing.lua.snap @@ -0,0 +1,10 @@ +--- +source: tests/tests.rs +assertion_line: 140 +expression: "format_code(&contents, Config\n{\n trailing_comment_spacing: TrailingCommentSpacing::Preserve,\n ..Config::default()\n}, None, OutputVerification::None).unwrap()" +input_file: tests/inputs-preserve-trailing-comment-spacing/varying-spacing.lua +--- +local a = 1 -- single space +local b = 2 -- two spaces +local c = 3 -- four spaces + diff --git a/tests/tests.rs b/tests/tests.rs index af513e38..6e0b3d3f 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,6 +1,6 @@ use stylua_lib::{ format_code, BlockNewlineGaps, CollapseSimpleStatement, Config, LuaVersion, OutputVerification, - SortRequiresConfig, + SortRequiresConfig, TrailingCommentSpacing, }; fn format(input: &str, syntax: LuaVersion) -> String { @@ -133,6 +133,23 @@ fn test_preserve_block_newline_gaps() { }) } +#[test] +fn test_preserve_trailing_comment_spacing() { + insta::glob!("inputs-preserve-trailing-comment-spacing/*.lua", |path| { + let contents = std::fs::read_to_string(path).unwrap(); + insta::assert_snapshot!(format_code( + &contents, + Config { + trailing_comment_spacing: TrailingCommentSpacing::Preserve, + ..Config::default() + }, + None, + OutputVerification::None + ) + .unwrap()); + }) +} + // Collapse simple statement for goto #[test] #[cfg(feature = "lua52")]