Skip to content

Commit b6793dc

Browse files
authored
fix more whitespace issues in rsx autofmt nested rsx blocks (#5257)
* trim * works a bit better * almost... * it work? * yay! * codex tried a thing * move * reduce to 1 * remove search * simplify * adjust test case * clippy
1 parent ceb8261 commit b6793dc

File tree

4 files changed

+666
-57
lines changed

4 files changed

+666
-57
lines changed

packages/autofmt/src/writer.rs

Lines changed: 165 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use quote::ToTokens;
55
use regex::Regex;
66
use std::{
77
borrow::Cow,
8-
collections::{HashMap, VecDeque},
8+
collections::{HashMap, HashSet, VecDeque},
99
fmt::{Result, Write},
1010
};
1111
use syn::{spanned::Spanned, token::Brace, Expr};
@@ -848,92 +848,200 @@ impl<'a> Writer<'a> {
848848
static COMMENT_REGEX: Regex = Regex::new("\"[^\"]*\"|(//.*)").unwrap();
849849
}
850850

851-
let pretty_expr = self.retrieve_formatted_expr(&expr).to_string();
851+
let pretty = self.retrieve_formatted_expr(&expr).to_string();
852+
let source = src_span.source_text().unwrap_or_default();
853+
let mut src_lines = source.lines().peekable();
852854

853-
// Adding comments back to the formatted expression
854-
let source_text = src_span.source_text().unwrap_or_default();
855-
let mut source_lines = source_text.lines().peekable();
856-
let mut output = String::from("");
857-
let mut printed_empty_line = false;
855+
// Comments already in pretty output (from nested rsx!) - skip these from source
856+
let pretty_comments: HashSet<_> = pretty
857+
.lines()
858+
.filter(|l| l.trim().starts_with("//"))
859+
.map(|l| l.trim())
860+
.collect();
858861

859-
if source_lines.peek().is_none() {
860-
output = pretty_expr;
862+
let mut out = String::new();
863+
864+
if src_lines.peek().is_none() {
865+
out = pretty;
861866
} else {
862-
for line in pretty_expr.lines() {
863-
let compacted_pretty_line = line.replace(" ", "").replace(",", "");
864-
let trimmed_pretty_line = line.trim();
867+
for line in pretty.lines() {
868+
let trimmed = line.trim();
869+
let compacted = line.replace(" ", "").replace(",", "");
870+
871+
// Pretty comments: consume matching source lines, preserve preceding empty lines
872+
if trimmed.starts_with("//") {
873+
if !out.is_empty() {
874+
out.push('\n');
875+
}
876+
let mut had_empty = false;
877+
while let Some(s) = src_lines.peek() {
878+
let t = s.trim();
879+
if t.is_empty() {
880+
had_empty = true;
881+
src_lines.next();
882+
} else if t == trimmed {
883+
src_lines.next();
884+
break;
885+
} else {
886+
break;
887+
}
888+
}
889+
if had_empty {
890+
out.push('\n');
891+
}
892+
out.push_str(line);
893+
continue;
894+
}
865895

866-
// Nested expressions might have comments already. We handle writing all of those
867-
// at the outer level, so we skip them here
868-
if trimmed_pretty_line.starts_with("//") {
896+
// Pretty empty lines: preserve and sync with source
897+
if trimmed.is_empty() {
898+
if !out.is_empty() {
899+
out.push('\n');
900+
}
901+
while src_lines
902+
.peek()
903+
.map(|s| s.trim().is_empty())
904+
.unwrap_or(false)
905+
{
906+
src_lines.next();
907+
}
869908
continue;
870909
}
871910

872-
if !output.is_empty() {
873-
output.push('\n');
911+
if !out.is_empty() {
912+
out.push('\n');
874913
}
875914

876-
// pull down any source lines with whitespace until we hit a line that matches our current line.
877-
while let Some(src) = source_lines.peek() {
878-
let trimmed_src = src.trim();
915+
// Scan source for comments/empty lines before the matching line
916+
let mut pending_comments = Vec::new();
917+
let mut had_empty = false;
918+
let mut multiline: Option<Vec<&str>> = None;
879919

880-
// Write comments and empty lines as they are
881-
if trimmed_src.starts_with("//") || trimmed_src.is_empty() {
882-
if !trimmed_src.is_empty() {
883-
// Match the whitespace of the incoming source line
884-
for s in line.chars().take_while(|c| c.is_whitespace()) {
885-
output.push(s);
886-
}
920+
while let Some(src) = src_lines.peek() {
921+
let src_trimmed = src.trim();
887922

888-
// Bump out the indent level if the line starts with a closing brace (ie we're at the end of a block)
889-
if matches!(trimmed_pretty_line.chars().next(), Some(')' | '}' | ']')) {
890-
output.push_str(self.out.indent.indent_str());
923+
if src_trimmed.is_empty() || src_trimmed.starts_with("//") {
924+
if src_trimmed.is_empty() {
925+
if pending_comments.is_empty() {
926+
had_empty = true;
891927
}
892-
893-
printed_empty_line = false;
894-
output.push_str(trimmed_src);
895-
output.push('\n');
896-
} else if !printed_empty_line {
897-
output.push('\n');
898-
printed_empty_line = true;
928+
} else if !pretty_comments.contains(src_trimmed) {
929+
pending_comments.push(src_trimmed);
899930
}
900-
901-
_ = source_lines.next();
931+
src_lines.next();
902932
continue;
903933
}
904934

905-
let compacted_src_line = src.replace(" ", "").replace(",", "");
935+
let src_compacted = src.replace(" ", "").replace(",", "");
906936

907-
// If this source line matches our pretty line, we stop pulling down
908-
if compacted_src_line.contains(&compacted_pretty_line) {
937+
// Exact match
938+
if src_compacted.contains(&compacted) {
909939
break;
910940
}
911941

912-
// Otherwise, consume this source line and keep going
913-
_ = source_lines.next();
942+
// Multi-line method chain (e.g., foo\n .bar()\n .baz())
943+
if !src_compacted.is_empty() && compacted.starts_with(&src_compacted) {
944+
let is_call = src_trimmed.ends_with('(')
945+
|| src_trimmed.ends_with(',')
946+
|| src_trimmed.ends_with('{');
947+
if !is_call {
948+
multiline = Some(vec![*src]);
949+
break;
950+
}
951+
}
952+
953+
// Non-matching line - clear pending and skip
954+
pending_comments.clear();
955+
had_empty = false;
956+
src_lines.next();
957+
break;
914958
}
915959

916-
// Once all whitespace is written, write the pretty line
917-
output.push_str(line);
918-
printed_empty_line = false;
960+
// Output empty line if needed
961+
if had_empty {
962+
out.push('\n');
963+
}
919964

920-
// And then pull the corresponding source line
921-
let source_line = source_lines.next();
965+
// Output pending comments
966+
for comment in &pending_comments {
967+
for c in line.chars().take_while(|c| c.is_whitespace()) {
968+
out.push(c);
969+
}
970+
if matches!(trimmed.chars().next(), Some(')' | '}' | ']')) {
971+
out.push_str(self.out.indent.indent_str());
972+
}
973+
out.push_str(comment);
974+
out.push('\n');
975+
}
922976

923-
// And then write any inline comments
924-
if let Some(source_line) = source_line {
925-
if let Some(captures) = COMMENT_REGEX.with(|f| f.captures(source_line)) {
926-
if let Some(comment) = captures.get(1) {
927-
output.push_str(" // ");
928-
output.push_str(comment.as_str().replace("//", "").trim());
977+
// Handle multi-line method chains
978+
if let Some(mut ml) = multiline {
979+
src_lines.next();
980+
let mut acc = ml[0].replace(" ", "").replace(",", "");
981+
982+
while let Some(src) = src_lines.peek() {
983+
let t = src.trim();
984+
if t.starts_with("//") {
985+
ml.push(src);
986+
src_lines.next();
987+
continue;
988+
}
989+
if t.is_empty() {
990+
src_lines.next();
991+
continue;
992+
}
993+
994+
acc.push_str(&src.replace(" ", "").replace(",", ""));
995+
ml.push(src);
996+
997+
if acc.contains(&compacted) {
998+
src_lines.next();
999+
break;
1000+
}
1001+
1002+
let cont = t.starts_with('.')
1003+
|| t.starts_with("&&")
1004+
|| t.starts_with("||")
1005+
|| matches!(t.chars().next(), Some('+' | '-' | '*' | '/' | '?'));
1006+
1007+
if cont || compacted.starts_with(&acc) {
1008+
src_lines.next();
1009+
continue;
1010+
}
1011+
break;
1012+
}
1013+
1014+
// Write multi-line with adjusted indentation
1015+
let base_indent = ml[0].chars().take_while(|c| c.is_whitespace()).count();
1016+
let target: String = line.chars().take_while(|c| c.is_whitespace()).collect();
1017+
1018+
for (i, src_line) in ml.iter().enumerate() {
1019+
let indent = src_line.chars().take_while(|c| c.is_whitespace()).count();
1020+
out.push_str(&target);
1021+
for _ in 0..indent.saturating_sub(base_indent) {
1022+
out.push(' ');
1023+
}
1024+
out.push_str(src_line.trim());
1025+
if i < ml.len() - 1 {
1026+
out.push('\n');
1027+
}
1028+
}
1029+
} else {
1030+
// Single line - output pretty line and capture inline comments
1031+
out.push_str(line);
1032+
if let Some(src_line) = src_lines.next() {
1033+
if let Some(cap) = COMMENT_REGEX.with(|r| r.captures(src_line)) {
1034+
if let Some(c) = cap.get(1) {
1035+
out.push_str(" // ");
1036+
out.push_str(c.as_str().replace("//", "").trim());
1037+
}
9291038
}
9301039
}
9311040
}
9321041
}
9331042
}
9341043

935-
self.write_mulitiline_tokens(output)?;
936-
1044+
self.write_mulitiline_tokens(out)?;
9371045
Ok(())
9381046
}
9391047

packages/autofmt/tests/wrong.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,4 @@ twoway!("syntax_error" => syntax_error (IndentOptions::new(IndentType::Spaces, 4
4141
twoway!("skipfail" => skipfail (IndentOptions::new(IndentType::Spaces, 4, false)));
4242
twoway!("comments-inline-4sp" => comments_inline_4sp (IndentOptions::new(IndentType::Spaces, 4, false)));
4343
twoway!("comments-attributes-4sp" => comments_attributes_4sp (IndentOptions::new(IndentType::Spaces, 4, false)));
44+
twoway!("comments-big" => comments_big (IndentOptions::new(IndentType::Spaces, 4, false)));

0 commit comments

Comments
 (0)