Skip to content

Commit db06867

Browse files
committed
fix: preserve consecutive whitespaces
1 parent 8e3d10e commit db06867

File tree

4 files changed

+73
-36
lines changed

4 files changed

+73
-36
lines changed

crates/fmt/src/state/mod.rs

Lines changed: 59 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -584,55 +584,79 @@ 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: emit ALL leading whitespace as breaking spaces to preserve formatting
606+
let ws_len = content.chars().take_while(|&c| c.is_whitespace()).count();
607+
let (leading_ws, rest) = content.split_at(ws_len);
604608
if !leading_ws.is_empty() {
605609
self.word(leading_ws.to_owned());
606610
}
607-
let prefix = post_break_prefix(prefix, rest.len());
608-
(prefix, rest)
609-
} else if line.starts_with("/*") && !line.starts_with("/* ") {
610-
self.word(prefix);
611-
(prefix, line[2..].trim_end())
611+
rest
612612
} else {
613-
let content = line[prefix.len()..].trim();
614-
let prefix = post_break_prefix(prefix, content.len());
615-
self.word(prefix);
616-
(prefix, content)
617-
};
618-
619-
// Split the rest of the content into words.
620-
let mut words = content.split_whitespace().peekable();
621-
while let Some(word) = words.next() {
622-
self.word(word.to_owned());
623-
if let Some(next_word) = words.peek() {
624-
if *next_word == "*/" {
613+
// Non-doc comments: replace first whitespace with nbsp, rest of content continues
614+
if let Some(first_char) = content.chars().next() {
615+
if first_char.is_whitespace() {
625616
self.nbsp();
617+
&content[first_char.len_utf8()..]
626618
} else {
627-
self.s.scan_break(BreakToken {
628-
offset: break_offset,
629-
blank_space: 1,
630-
post_break: if matches!(prefix, "/* ") { None } else { Some(prefix) },
631-
..Default::default()
632-
});
619+
content
633620
}
621+
} else {
622+
""
634623
}
624+
};
625+
626+
let post_break = post_break_prefix(prefix, !content.is_empty());
627+
628+
// Process content character by character to preserve consecutive whitespaces
629+
let (mut chars, mut current_word) = (content.chars().peekable(), String::new());
630+
while let Some(ch) = chars.next() {
631+
if ch.is_whitespace() {
632+
// Print current word
633+
if !current_word.is_empty() {
634+
self.word(std::mem::take(&mut current_word));
635+
}
636+
637+
// Preserve multiple spaces while adding a single break
638+
let mut ws_count = 1;
639+
while chars.peek().is_some_and(|c| c.is_whitespace()) {
640+
ws_count += 1;
641+
chars.next();
642+
}
643+
self.s.scan_break(BreakToken {
644+
offset: break_offset,
645+
blank_space: ws_count,
646+
post_break: if post_break.starts_with("/*") { None } else { Some(post_break) },
647+
..Default::default()
648+
});
649+
continue;
650+
}
651+
652+
current_word.push(ch);
653+
}
654+
655+
// Print final word
656+
if !current_word.is_empty() {
657+
self.word(current_word);
635658
}
659+
636660
self.end();
637661
}
638662

crates/fmt/testdata/SimpleComments/fmt.sol

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

48
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/

crates/fmt/testdata/SimpleComments/original.sol

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

48
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
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

711
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
8-
/* FUNCTIONS */
12+
/* FUNCTIONS
13+
*/
914
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
1015

1116
constructor() {

0 commit comments

Comments
 (0)