Skip to content

Commit f111fb1

Browse files
authored
fix(fmt): don't normalize single-line non-doc block cmnts (#12078)
* fix: don't normalize single-line non-doc block cmnts * fix: preserve consecutive whitespaces * fix: use byte length instead of char count
1 parent 3e685d8 commit f111fb1

File tree

5 files changed

+90
-35
lines changed

5 files changed

+90
-35
lines changed

crates/common/src/comments/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -235,9 +235,9 @@ impl<'ast> CommentGatherer<'ast> {
235235
let line_begin_pos = (line_begin_in_file - self.start_bpos).to_usize();
236236
let mut col = CharPos(self.text[line_begin_pos..self.pos].chars().count());
237237

238-
// To preserve alignment in non-doc comments, normalize the block based on its
239-
// least-indented line.
240-
if !is_doc {
238+
// To preserve alignment in multi-line non-doc comments, normalize the block based
239+
// on its least-indented line.
240+
if !is_doc && token_text.contains('\n') {
241241
col = token_text.lines().skip(1).fold(col, |min, line| {
242242
if line.is_empty() {
243243
return min;

crates/fmt/src/state/mod.rs

Lines changed: 63 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -584,52 +584,83 @@ impl<'sess> State<'sess, '_> {
584584
return;
585585
}
586586

587-
let post_break_prefix = |prefix: &'static str, line_len: usize| -> &'static str {
587+
fn post_break_prefix(prefix: &'static str, has_content: bool) -> &'static str {
588+
if !has_content {
589+
return prefix;
590+
}
588591
match prefix {
589-
"///" if line_len > 3 => "/// ",
590-
"//" if line_len > 2 => "// ",
591-
"/*" if line_len > 2 => "/* ",
592-
" *" if line_len > 2 => " * ",
592+
"///" => "/// ",
593+
"//" => "// ",
594+
"/*" => "/* ",
595+
" *" => " * ",
593596
_ => prefix,
594597
}
595-
};
598+
}
596599

597600
self.ibox(0);
598-
let (prefix, content) = if is_doc {
599-
// Doc comments preserve leading whitespaces (right after the prefix).
600-
self.word(prefix);
601-
let content = &line[prefix.len()..];
602-
let (leading_ws, rest) =
603-
content.split_at(content.chars().take_while(|&c| c.is_whitespace()).count());
601+
self.word(prefix);
602+
603+
let content = &line[prefix.len()..];
604+
let content = if is_doc {
605+
// Doc comments preserve leading whitespaces (right after the prefix) as nbps.
606+
let ws_len = content
607+
.char_indices()
608+
.take_while(|(_, c)| c.is_whitespace())
609+
.last()
610+
.map_or(0, |(idx, c)| idx + c.len_utf8());
611+
let (leading_ws, rest) = content.split_at(ws_len);
604612
if !leading_ws.is_empty() {
605613
self.word(leading_ws.to_owned());
606614
}
607-
let prefix = post_break_prefix(prefix, rest.len());
608-
(prefix, rest)
615+
rest
609616
} else {
610-
let content = line[prefix.len()..].trim();
611-
let prefix = post_break_prefix(prefix, content.len());
612-
self.word(prefix);
613-
(prefix, content)
614-
};
615-
616-
// Split the rest of the content into words.
617-
let mut words = content.split_whitespace().peekable();
618-
while let Some(word) = words.next() {
619-
self.word(word.to_owned());
620-
if let Some(next_word) = words.peek() {
621-
if *next_word == "*/" {
617+
// Non-doc comments: replace first whitespace with nbsp, rest of content continues
618+
if let Some(first_char) = content.chars().next() {
619+
if first_char.is_whitespace() {
622620
self.nbsp();
621+
&content[first_char.len_utf8()..]
623622
} else {
624-
self.s.scan_break(BreakToken {
625-
offset: break_offset,
626-
blank_space: 1,
627-
post_break: if matches!(prefix, "/* ") { None } else { Some(prefix) },
628-
..Default::default()
629-
});
623+
content
624+
}
625+
} else {
626+
""
627+
}
628+
};
629+
630+
let post_break = post_break_prefix(prefix, !content.is_empty());
631+
632+
// Process content character by character to preserve consecutive whitespaces
633+
let (mut chars, mut current_word) = (content.chars().peekable(), String::new());
634+
while let Some(ch) = chars.next() {
635+
if ch.is_whitespace() {
636+
// Print current word
637+
if !current_word.is_empty() {
638+
self.word(std::mem::take(&mut current_word));
630639
}
640+
641+
// Preserve multiple spaces while adding a single break
642+
let mut ws_count = 1;
643+
while chars.peek().is_some_and(|c| c.is_whitespace()) {
644+
ws_count += 1;
645+
chars.next();
646+
}
647+
self.s.scan_break(BreakToken {
648+
offset: break_offset,
649+
blank_space: ws_count,
650+
post_break: if post_break.starts_with("/*") { None } else { Some(post_break) },
651+
..Default::default()
652+
});
653+
continue;
631654
}
655+
656+
current_word.push(ch);
657+
}
658+
659+
// Print final word
660+
if !current_word.is_empty() {
661+
self.word(current_word);
632662
}
663+
633664
self.end();
634665
}
635666

crates/fmt/testdata/SimpleComments/fmt.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
contract SimpleComments {
2+
//´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:
3+
// VARIABLES
4+
//.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•
5+
26
mapping(address /* asset */ => address /* router */) public router;
37

8+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
9+
/* FUNCTIONS */
10+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
11+
412
constructor() {
513
// TODO: do this and that
614

crates/fmt/testdata/SimpleComments/original.sol

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
contract SimpleComments {
2+
//´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:
3+
// VARIABLES
4+
//.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•
5+
26
mapping(address /* asset */ => address /* router */) public router;
37

8+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
9+
/* FUNCTIONS */
10+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
411

512
constructor() {
613
// TODO: do this and that

crates/fmt/testdata/SimpleComments/wrap-comments.fmt.sol

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
// config: line_length = 60
22
// config: wrap_comments = true
33
contract SimpleComments {
4+
//´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:
5+
// VARIABLES
6+
//.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•
7+
48
mapping(address /* asset */ => address /* router */)
59
public router;
610

11+
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
12+
/* FUNCTIONS
13+
*/
14+
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
15+
716
constructor() {
817
// TODO: do this and that
918

0 commit comments

Comments
 (0)