From 929e64be15f8703ceb6d420720cbd9f2d13dc708 Mon Sep 17 00:00:00 2001 From: Aditya Anchuri Date: Mon, 27 Jul 2026 13:10:50 -0700 Subject: [PATCH 1/2] Enforce at least one space after comment symbol --- fixtures/small/class_comment_expected.rb | 4 +- ...mment_at_least_one_leading_space_actual.rb | 38 ++++++++++++++++ ...ent_at_least_one_leading_space_expected.rb | 38 ++++++++++++++++ fixtures/small/empty_comments_expected.rb | 8 ++-- fixtures/small/inline_comments_expected.rb | 4 +- fixtures/small/rspec_its_expected.rb | 8 ++-- .../small/start_of_file_comments_expected.rb | 2 +- .../word_array_trailing_comment_expected.rb | 2 +- librubyfmt/src/comment_block.rs | 45 +++++++++++++++++++ librubyfmt/src/parser_state.rs | 8 +++- 10 files changed, 141 insertions(+), 16 deletions(-) create mode 100644 fixtures/small/comment_at_least_one_leading_space_actual.rb create mode 100644 fixtures/small/comment_at_least_one_leading_space_expected.rb diff --git a/fixtures/small/class_comment_expected.rb b/fixtures/small/class_comment_expected.rb index e07c19e08..3a6379491 100644 --- a/fixtures/small/class_comment_expected.rb +++ b/fixtures/small/class_comment_expected.rb @@ -1,6 +1,6 @@ -#adsf +# adsf class Foo - #but this one doesn't get a proceeding newline + # but this one doesn't get a proceeding newline a end diff --git a/fixtures/small/comment_at_least_one_leading_space_actual.rb b/fixtures/small/comment_at_least_one_leading_space_actual.rb new file mode 100644 index 000000000..dc6a154db --- /dev/null +++ b/fixtures/small/comment_at_least_one_leading_space_actual.rb @@ -0,0 +1,38 @@ +#! /usr/bin/env ruby +# frozen_string_literal: true + +#### #### AUTHOR: coder204coolgizmos ### ## + +##DATE: 2026-06-27 +## Cool Gizmos, Inc. + +#### #### +###---------- +#: +# +# +#This is a class that frobs a gizmo. It: +# * frobs the sprog +# * then turns the sprog into the gizmo. +#### # +#### # +#Keep this class here. +class Foo + #inner class comment + + #method description comment + def self.b(&_blk) + yield + end + + #method description comment + def self.a + #some method comment + + x = b do + #some comment inside the block + y = 4 + # some other comment + end + end +end diff --git a/fixtures/small/comment_at_least_one_leading_space_expected.rb b/fixtures/small/comment_at_least_one_leading_space_expected.rb new file mode 100644 index 000000000..e25649f85 --- /dev/null +++ b/fixtures/small/comment_at_least_one_leading_space_expected.rb @@ -0,0 +1,38 @@ +#! /usr/bin/env ruby +# frozen_string_literal: true + +#### #### AUTHOR: coder204coolgizmos ### ## + +## DATE: 2026-06-27 +## Cool Gizmos, Inc. + +#### #### +###---------- +#: +# +# +# This is a class that frobs a gizmo. It: +# * frobs the sprog +# * then turns the sprog into the gizmo. +#### # +#### # +# Keep this class here. +class Foo + # inner class comment + + # method description comment + def self.b(&_blk) + yield + end + + # method description comment + def self.a + # some method comment + + x = b do + # some comment inside the block + y = 4 + # some other comment + end + end +end diff --git a/fixtures/small/empty_comments_expected.rb b/fixtures/small/empty_comments_expected.rb index 606dd77f8..2ba0bd572 100644 --- a/fixtures/small/empty_comments_expected.rb +++ b/fixtures/small/empty_comments_expected.rb @@ -1,8 +1,8 @@ def foo - #a - #b - #c - #d + # a + # b + # c + # d end if a diff --git a/fixtures/small/inline_comments_expected.rb b/fixtures/small/inline_comments_expected.rb index 81f7f5fc3..6849419bb 100644 --- a/fixtures/small/inline_comments_expected.rb +++ b/fixtures/small/inline_comments_expected.rb @@ -15,9 +15,9 @@ module MathsAndPhysics # degree_codes: The corresponding JAC codes to the subject(s) # the teacher completed their degree in. CONST = [ - #maths + # maths a, - #physics + # physics b, # foreign language diff --git a/fixtures/small/rspec_its_expected.rb b/fixtures/small/rspec_its_expected.rb index a18c2f358..bb6d58163 100644 --- a/fixtures/small/rspec_its_expected.rb +++ b/fixtures/small/rspec_its_expected.rb @@ -2,19 +2,19 @@ "a" \ "b" ) do - #hi + # hi end it "a" do - #hi + # hi end it "a", flag: true do - #hi + # hi end it "a", flag: true, other: "b", another: false do - #hi + # hi end it("a", flag: true, other: "b", another: false) { 1 } diff --git a/fixtures/small/start_of_file_comments_expected.rb b/fixtures/small/start_of_file_comments_expected.rb index 848d30c6f..24083f768 100644 --- a/fixtures/small/start_of_file_comments_expected.rb +++ b/fixtures/small/start_of_file_comments_expected.rb @@ -1,5 +1,5 @@ #!/usr/bin/env ruby -#frozen_string_literal: true +# frozen_string_literal: true def foo a diff --git a/fixtures/small/word_array_trailing_comment_expected.rb b/fixtures/small/word_array_trailing_comment_expected.rb index 727538fdb..ca5b5f776 100644 --- a/fixtures/small/word_array_trailing_comment_expected.rb +++ b/fixtures/small/word_array_trailing_comment_expected.rb @@ -1,4 +1,4 @@ -#c +# c a = %w[ b ] diff --git a/librubyfmt/src/comment_block.rs b/librubyfmt/src/comment_block.rs index 30fa0fd84..f73e5ab00 100644 --- a/librubyfmt/src/comment_block.rs +++ b/librubyfmt/src/comment_block.rs @@ -12,6 +12,13 @@ pub struct CommentBlock { comments: Vec>, } +const SPECIAL_CHARS_AFTER_HASH_TO_IGNORE: [u8; 4] = [ + b'!', // #! 'shebang' comments cannot be modified since they are Unix directives. + b'=', // #=== is a common delimiting pattern. + b'-', // #--- is a common delimiting pattern. + b':', // #: is used for RBS directives. +]; + impl CommentBlock { pub fn new(span: Range, comments: Vec>) -> Self { CommentBlock { span, comments } @@ -54,6 +61,44 @@ impl CommentBlock { }) } + pub fn enforce_at_least_one_space_after_comment_symbol(mut self) -> Self { + for comment in &mut self.comments { + // Ignore empty vecs -- these represent blank lines between + // groups of comments + if comment.is_empty() || comment.starts_with(b"=begin") { + continue; + } + if let Some(start_idx) = comment.iter().position(|&c| c == b'#') { + // Allow any amount of '#' after the leading '#'. + let collapse = comment[start_idx..] + .iter() + .take_while(|&&b| b == b'#') + .count(); + + let anchor = start_idx + collapse; + if anchor == comment.len() { + // No point truncating an empty comment. + continue; + } + + let current = comment[anchor..].iter().take_while(|&&b| b == b' ').count(); + if current > 0 { + // Any amount of spaces after `#` is fine. + continue; + } + + let next_char = comment[anchor]; + if SPECIAL_CHARS_AFTER_HASH_TO_IGNORE.contains(&next_char) { + continue; + } + + // Add at least one space + comment.to_mut().splice(anchor..anchor, [b' ']); + } + } + self + } + /// Set each comment's leading indent to exactly `indent_depth` spaces pub fn apply_spaces(mut self, indent_depth: ColNumber) -> Self { let target = indent_depth as usize; diff --git a/librubyfmt/src/parser_state.rs b/librubyfmt/src/parser_state.rs index 2261886bf..e157e44b8 100644 --- a/librubyfmt/src/parser_state.rs +++ b/librubyfmt/src/parser_state.rs @@ -382,8 +382,11 @@ impl<'src> ParserState<'src> { } pub(crate) fn insert_comment_collection(&mut self, comments: CommentBlock) { - self.comments_to_insert - .merge(comments.apply_spaces(self.spaces_after_last_newline)); + self.comments_to_insert.merge( + comments + .enforce_at_least_one_space_after_comment_symbol() + .apply_spaces(self.spaces_after_last_newline), + ); } pub(crate) fn emit_op(&mut self, op: &'src [u8]) { @@ -890,6 +893,7 @@ impl<'src> ParserState<'src> { self.on_line(1); } Some(comments) => { + let comments = comments.enforce_at_least_one_space_after_comment_symbol(); let line_count = comments.line_count(); for token in comments.into_line_tokens() { self.push_concrete_token(token); From f482b8f7a2ccdcb1827e4b5101ff5e80b2adfbb9 Mon Sep 17 00:00:00 2001 From: Aditya Anchuri Date: Mon, 27 Jul 2026 17:03:56 -0700 Subject: [PATCH 2/2] PR feedback --- .../comment_at_least_one_leading_space_actual.rb | 4 +++- .../comment_at_least_one_leading_space_expected.rb | 4 +++- librubyfmt/src/comment_block.rs | 13 +++++++------ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/fixtures/small/comment_at_least_one_leading_space_actual.rb b/fixtures/small/comment_at_least_one_leading_space_actual.rb index dc6a154db..2eea9770d 100644 --- a/fixtures/small/comment_at_least_one_leading_space_actual.rb +++ b/fixtures/small/comment_at_least_one_leading_space_actual.rb @@ -8,7 +8,9 @@ #### #### ###---------- -#: +#: asdf +#| asdf +#** doc doc # # #This is a class that frobs a gizmo. It: diff --git a/fixtures/small/comment_at_least_one_leading_space_expected.rb b/fixtures/small/comment_at_least_one_leading_space_expected.rb index e25649f85..ef5d9ed17 100644 --- a/fixtures/small/comment_at_least_one_leading_space_expected.rb +++ b/fixtures/small/comment_at_least_one_leading_space_expected.rb @@ -8,7 +8,9 @@ #### #### ###---------- -#: +#: asdf +#| asdf +#** doc doc # # # This is a class that frobs a gizmo. It: diff --git a/librubyfmt/src/comment_block.rs b/librubyfmt/src/comment_block.rs index f73e5ab00..d2e16e077 100644 --- a/librubyfmt/src/comment_block.rs +++ b/librubyfmt/src/comment_block.rs @@ -12,11 +12,13 @@ pub struct CommentBlock { comments: Vec>, } -const SPECIAL_CHARS_AFTER_HASH_TO_IGNORE: [u8; 4] = [ +const SPECIAL_CHARS_AFTER_HASH_TO_IGNORE: [u8; 6] = [ b'!', // #! 'shebang' comments cannot be modified since they are Unix directives. b'=', // #=== is a common delimiting pattern. b'-', // #--- is a common delimiting pattern. - b':', // #: is used for RBS directives. + b':', // #: is used for RBS annotations. + b'|', // #| is used for RBS annotations. + b'*', // #** is used for doxygen comments. ]; impl CommentBlock { @@ -81,8 +83,7 @@ impl CommentBlock { continue; } - let current = comment[anchor..].iter().take_while(|&&b| b == b' ').count(); - if current > 0 { + if comment[anchor].is_ascii_whitespace() { // Any amount of spaces after `#` is fine. continue; } @@ -92,8 +93,8 @@ impl CommentBlock { continue; } - // Add at least one space - comment.to_mut().splice(anchor..anchor, [b' ']); + // Add one space + comment.to_mut().insert(anchor, b' '); } } self