Skip to content

Commit 54d465a

Browse files
jjvn84jkelleyrtp
andauthored
Avoid the accidental removal of comments inside expressions by dx fmt (#4410)
* Avoid the accidental removal of comments inside expressions by dx fmt * Rewrite the logic to pass failed tests * Removed unnecessary call to write_inline_comments * add failing test case * alter parsing a bit to handle more test cases * almost there... * fix * use threadlocal --------- Co-authored-by: Jonathan Kelley <[email protected]>
1 parent 88c693b commit 54d465a

File tree

7 files changed

+213
-21
lines changed

7 files changed

+213
-21
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/autofmt/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ syn = { workspace = true, features = [
2121
] }
2222
serde = { workspace = true, features = ["derive"] }
2323
prettyplease = { workspace = true }
24+
regex = "1.11.1"
2425

2526
[dev-dependencies]
2627
pretty_assertions = { workspace = true }

packages/autofmt/src/prettier_please.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ impl Writer<'_> {
99
}
1010
}
1111

12-
const MARKER: &str = "dioxus_autofmt_block__________";
13-
const MARKER_REPLACE: &str = "dioxus_autofmt_block__________! {}";
12+
// we use weird unicode alternatives to avoid conflicts with the actual rsx! macro
13+
const MARKER: &str = "𝕣𝕤𝕩";
14+
const MARKER_REPLACE: &str = "𝕣𝕤𝕩! {}";
1415

1516
pub fn unparse_expr(expr: &Expr, src: &str, cfg: &IndentOptions) -> String {
1617
struct ReplaceMacros<'a> {
@@ -42,7 +43,6 @@ pub fn unparse_expr(expr: &Expr, src: &str, cfg: &IndentOptions) -> String {
4243
}
4344
.unwrap();
4445

45-
// always push out the rsx to require a new line
4646
i.path = syn::parse_str(MARKER).unwrap();
4747
i.tokens = Default::default();
4848

@@ -84,7 +84,7 @@ pub fn unparse_expr(expr: &Expr, src: &str, cfg: &IndentOptions) -> String {
8484

8585
// now we can replace the macros with the formatted blocks
8686
for fmted in replacer.formatted_stack.drain(..) {
87-
let is_multiline = fmted.contains('{');
87+
let is_multiline = fmted.ends_with('}') || fmted.contains('\n');
8888
let is_empty = fmted.trim().is_empty();
8989

9090
let mut out_fmt = String::from("rsx! {");

packages/autofmt/src/writer.rs

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::{buffer::Buffer, IndentOptions};
22
use dioxus_rsx::*;
33
use proc_macro2::{LineColumn, Span};
44
use quote::ToTokens;
5+
use regex::Regex;
56
use std::{
67
borrow::Cow,
78
collections::{HashMap, VecDeque},
@@ -797,8 +798,95 @@ impl<'a> Writer<'a> {
797798
return Err(std::fmt::Error);
798799
};
799800

801+
thread_local! {
802+
static COMMENT_REGEX: Regex = Regex::new("\"[^\"]*\"|(//.*)").unwrap();
803+
}
804+
800805
let pretty_expr = self.retrieve_formatted_expr(&expr).to_string();
801-
self.write_mulitiline_tokens(pretty_expr)?;
806+
807+
// Adding comments back to the formatted expression
808+
let source_text = src_span.source_text().unwrap_or_default();
809+
let mut source_lines = source_text.lines().peekable();
810+
let mut output = String::from("");
811+
let mut printed_empty_line = false;
812+
813+
if source_lines.peek().is_none() {
814+
output = pretty_expr;
815+
} else {
816+
for line in pretty_expr.lines() {
817+
let compacted_pretty_line = line.replace(" ", "").replace(",", "");
818+
let trimmed_pretty_line = line.trim();
819+
820+
// Nested expressions might have comments already. We handle writing all of those
821+
// at the outer level, so we skip them here
822+
if trimmed_pretty_line.starts_with("//") {
823+
continue;
824+
}
825+
826+
if !output.is_empty() {
827+
output.push('\n');
828+
}
829+
830+
// pull down any source lines with whitespace until we hit a line that matches our current line.
831+
while let Some(src) = source_lines.peek() {
832+
let trimmed_src = src.trim();
833+
834+
// Write comments and empty lines as they are
835+
if trimmed_src.starts_with("//") || trimmed_src.is_empty() {
836+
if !trimmed_src.is_empty() {
837+
// Match the whitespace of the incoming source line
838+
for s in line.chars().take_while(|c| c.is_whitespace()) {
839+
output.push(s);
840+
}
841+
842+
// Bump out the indent level if the line starts with a closing brace (ie we're at the end of a block)
843+
if matches!(trimmed_pretty_line.chars().next(), Some(')' | '}' | ']')) {
844+
output.push_str(self.out.indent.indent_str());
845+
}
846+
847+
printed_empty_line = false;
848+
output.push_str(trimmed_src);
849+
output.push('\n');
850+
} else if !printed_empty_line {
851+
output.push('\n');
852+
printed_empty_line = true;
853+
}
854+
855+
_ = source_lines.next();
856+
continue;
857+
}
858+
859+
let compacted_src_line = src.replace(" ", "").replace(",", "");
860+
861+
// If this source line matches our pretty line, we stop pulling down
862+
if compacted_src_line.contains(&compacted_pretty_line) {
863+
break;
864+
}
865+
866+
// Otherwise, consume this source line and keep going
867+
_ = source_lines.next();
868+
}
869+
870+
// Once all whitespace is written, write the pretty line
871+
output.push_str(line);
872+
printed_empty_line = false;
873+
874+
// And then pull the corresponding source line
875+
let source_line = source_lines.next();
876+
877+
// And then write any inline comments
878+
if let Some(source_line) = source_line {
879+
if let Some(captures) = COMMENT_REGEX.with(|f| f.captures(source_line)) {
880+
if let Some(comment) = captures.get(1) {
881+
output.push_str(" // ");
882+
output.push_str(comment.as_str().replace("//", "").trim());
883+
}
884+
}
885+
}
886+
}
887+
}
888+
889+
self.write_mulitiline_tokens(output)?;
802890

803891
Ok(())
804892
}
@@ -815,7 +903,10 @@ impl<'a> Writer<'a> {
815903
writeln!(self.out, "{first}")?;
816904

817905
while let Some(line) = lines.next() {
818-
self.out.tab()?;
906+
if !line.trim().is_empty() {
907+
self.out.tab()?;
908+
}
909+
819910
write!(self.out, "{line}")?;
820911
if lines.peek().is_none() {
821912
write!(self.out, "")?;

packages/autofmt/tests/samples/comments.rsx

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,109 @@ rsx! {
121121
// Please dont eat me 2
122122
}
123123

124+
div {
125+
{
126+
// Please dont eat me 1
127+
let millis = timer
128+
.with(|t| {
129+
t.duration()
130+
.saturating_sub(
131+
t.started_at.map(|x| x.elapsed()).unwrap_or(Duration::ZERO),
132+
)
133+
.as_millis()
134+
});
135+
136+
// Please dont eat me 2
137+
format!(
138+
"{:02}:{:02}:{:02}.{:01}",
139+
millis / 1000 / 3600 % 3600, // Please dont eat me 3
140+
millis / 1000 / 60 % 60,
141+
millis / 1000 % 60,
142+
143+
// Please dont eat me 4
144+
millis / 100 % 10,
145+
);
146+
147+
// booo //
148+
let b = { yay };
149+
150+
// boo // boo
151+
let a = {
152+
let a = "123 // boo 123";
153+
// boo // boo
154+
asdb
155+
};
156+
157+
format!("{b} {a}")
158+
// ennd
159+
}
160+
}
161+
162+
div {
163+
// booo //
164+
{yay}
165+
166+
// boo // boo
167+
{
168+
let a = "123 // boo 123";
169+
// boo // boo
170+
rsx! { "{a}" }
171+
}
172+
}
173+
174+
div {
175+
input {
176+
r#type: "number",
177+
min: 0,
178+
max: 99,
179+
value: format!("{:02}", timer.read().hours),
180+
oninput: move |e| {
181+
// A comment inside an expression
182+
timer.write().hours = e.value().parse().unwrap_or(0);
183+
},
184+
}
185+
186+
input {
187+
r#type: "number",
188+
min: 0,
189+
max: 59,
190+
value: format!("{:02}", timer.read().minutes),
191+
oninput: move |e| {
192+
// A comment inside an expression
193+
timer.write().minutes = e.value().parse().unwrap_or(0);
194+
195+
// A comment inside an expression
196+
},
197+
}
198+
199+
input {
200+
r#type: "number",
201+
min: 0,
202+
max: 59,
203+
value: format!("{:02}", timer.read().seconds),
204+
oninput: move |e| {
205+
// A comment inside an expression
206+
timer.write().seconds = e.value().parse().unwrap_or(0);
207+
// A comment inside an expression
208+
},
209+
}
210+
}
211+
212+
{
213+
rsx! { "{a}" }
214+
}
215+
216+
{
217+
rsx! { "one" }
218+
}
219+
220+
div {}
221+
222+
{
223+
rsx! { "one two three" }
224+
}
225+
226+
124227
// Please dont eat me 1
125228
//
126229
// Please dont eat me 2

packages/autofmt/tests/samples/many_exprs.rsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ fn app() -> Element {
148148
value: format!("{:02}", timer.read().seconds),
149149
oninput: move |e| {
150150
timer.write().seconds = e.value().parse().unwrap_or(0);
151+
// A comment inside an expression
151152
},
152153
}
153154
}

packages/autofmt/tests/wrong/multiexpr-many.rsx

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -199,20 +199,15 @@ fn app() -> Element {
199199
}
200200
}
201201
}
202-
{
203-
exit_button(
204-
Duration::from_secs(3),
205-
|trigger, delay| rsx! {
206-
{
207-
format!(
208-
"{:0.1?}",
209-
trigger
210-
.read()
211-
.map(|inst| (delay.as_secs_f32() - inst.elapsed().as_secs_f32())),
212-
)
213-
}
214-
},
215-
)
216-
}
202+
{exit_button(Duration::from_secs(3), |trigger, delay| rsx! {
203+
{
204+
format!(
205+
"{:0.1?}",
206+
trigger
207+
.read()
208+
.map(|inst| (delay.as_secs_f32() - inst.elapsed().as_secs_f32())),
209+
)
210+
}
211+
})}
217212
}
218213
}

0 commit comments

Comments
 (0)