dioxus_autofmt/
prettier_please.rs1use dioxus_rsx::CallBody;
2use syn::{parse::Parser, visit_mut::VisitMut, Expr, File, Item, MacroDelimiter};
3
4use crate::{IndentOptions, Writer};
5
6impl Writer<'_> {
7 pub fn unparse_expr(&mut self, expr: &Expr) -> String {
8 unparse_expr(expr, self.raw_src, &self.out.indent)
9 }
10}
11
12const MARKER: &str = "𝕣𝕤𝕩";
14const MARKER_REPLACE: &str = "𝕣𝕤𝕩! {}";
15
16pub fn unparse_expr(expr: &Expr, src: &str, cfg: &IndentOptions) -> String {
17 struct ReplaceMacros<'a> {
18 src: &'a str,
19 formatted_stack: Vec<String>,
20 cfg: &'a IndentOptions,
21 }
22
23 impl VisitMut for ReplaceMacros<'_> {
24 fn visit_macro_mut(&mut self, i: &mut syn::Macro) {
25 if let Some("rsx" | "render") = i
27 .path
28 .segments
29 .last()
30 .map(|i| i.ident.to_string())
31 .as_deref()
32 {
33 let body = CallBody::parse_strict.parse2(i.tokens.clone()).unwrap();
38 let multiline = !Writer::is_short_rsx_call(&body.body.roots);
39 let mut formatted = {
40 let mut writer = Writer::new(self.src, self.cfg.clone());
41 _ = writer.write_body_nodes(&body.body.roots).ok();
42 writer.consume()
43 }
44 .unwrap();
45
46 i.path = syn::parse_str(MARKER).unwrap();
47 i.tokens = Default::default();
48
49 i.delimiter = MacroDelimiter::Brace(Default::default());
53
54 if multiline || formatted.contains('\n') {
56 formatted = formatted
57 .lines()
58 .map(|line| format!("{}{line}", self.cfg.indent_str()))
59 .collect::<Vec<_>>()
60 .join("\n");
61 }
62
63 self.formatted_stack.push(formatted)
65 }
66
67 syn::visit_mut::visit_macro_mut(self, i);
68 }
69 }
70
71 let mut replacer = ReplaceMacros {
73 src,
74 cfg,
75 formatted_stack: vec![],
76 };
77
78 let mut modified_expr = expr.clone();
80 replacer.visit_expr_mut(&mut modified_expr);
81
82 let mut unparsed = unparse_inner(&modified_expr);
84
85 for fmted in replacer.formatted_stack.drain(..) {
87 let is_multiline = fmted.ends_with('}') || fmted.contains('\n');
88 let is_empty = fmted.trim().is_empty();
89
90 let mut out_fmt = String::from("rsx! {");
91 if is_multiline {
92 out_fmt.push('\n');
93 } else if !is_empty {
94 out_fmt.push(' ');
95 }
96
97 let mut whitespace = 0;
98
99 for line in unparsed.lines() {
100 if line.contains(MARKER) {
101 whitespace = line.matches(cfg.indent_str()).count();
102 break;
103 }
104 }
105
106 let mut lines = fmted.lines().enumerate().peekable();
107
108 while let Some((_idx, fmt_line)) = lines.next() {
109 if is_multiline {
111 out_fmt.push_str(&cfg.indent_str().repeat(whitespace));
112 }
113
114 out_fmt.push_str(fmt_line);
116
117 if lines.peek().is_some() {
119 out_fmt.push('\n');
120 }
121 }
122
123 if is_multiline {
124 out_fmt.push('\n');
125 out_fmt.push_str(&cfg.indent_str().repeat(whitespace));
126 } else if !is_empty {
127 out_fmt.push(' ');
128 }
129
130 out_fmt.push('}');
132
133 unparsed = unparsed.replacen(MARKER_REPLACE, &out_fmt, 1);
134 continue;
135 }
136
137 if unparsed.starts_with("{ ") && unparsed.ends_with(" }") {
139 let mut out_fmt = String::new();
140 out_fmt.push('{');
141 out_fmt.push_str(&unparsed[2..unparsed.len() - 2]);
142 out_fmt.push('}');
143 out_fmt
144 } else {
145 unparsed
146 }
147}
148
149pub fn unparse_inner(expr: &Expr) -> String {
154 let file = wrapped(expr);
155 let wrapped = prettyplease::unparse(&file);
156 unwrapped(wrapped)
157}
158
159fn unwrapped(raw: String) -> String {
161 let mut o = raw
162 .strip_prefix("fn main() {\n")
163 .unwrap()
164 .strip_suffix("}\n")
165 .unwrap()
166 .lines()
167 .map(|line| line.strip_prefix(" ").unwrap_or_default()) .collect::<Vec<_>>()
169 .join("\n");
170
171 if o.ends_with(';') {
173 o.pop();
174 }
175
176 o
177}
178
179fn wrapped(expr: &Expr) -> File {
180 File {
181 shebang: None,
182 attrs: vec![],
183 items: vec![
184 Item::Verbatim(quote::quote! {
186 fn main() {
187 #expr;
188 }
189 }),
190 ],
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use proc_macro2::TokenStream;
198
199 fn fmt_block_from_expr(raw: &str, tokens: TokenStream, cfg: IndentOptions) -> Option<String> {
200 let body = CallBody::parse_strict.parse2(tokens).unwrap();
201 let mut writer = Writer::new(raw, cfg);
202 writer.write_body_nodes(&body.body.roots).ok()?;
203 writer.consume()
204 }
205
206 #[test]
207 fn unparses_raw() {
208 let expr = syn::parse_str("1 + 1").expect("Failed to parse");
209 let unparsed = prettyplease::unparse(&wrapped(&expr));
210 assert_eq!(unparsed, "fn main() {\n 1 + 1;\n}\n");
211 }
212
213 #[test]
214 fn weird_ifcase() {
215 let contents = r##"
216 fn main() {
217 move |_| timer.with_mut(|t| if t.started_at.is_none() { Some(Instant::now()) } else { None })
218 }
219 "##;
220
221 let expr: File = syn::parse_file(contents).unwrap();
222 let out = prettyplease::unparse(&expr);
223 println!("{}", out);
224 }
225
226 #[test]
227 fn multiline_madness() {
228 let contents = r##"
229 {
230 {children.is_some().then(|| rsx! {
231 span {
232 class: "inline-block ml-auto hover:bg-gray-500",
233 onclick: move |evt| {
234 evt.cancel_bubble();
235 },
236 icons::icon_5 {}
237 {rsx! {
238 icons::icon_6 {}
239 }}
240 }
241 })}
242 {children.is_some().then(|| rsx! {
243 span {
244 class: "inline-block ml-auto hover:bg-gray-500",
245 onclick: move |evt| {
246 evt.cancel_bubble();
247 },
248 icons::icon_10 {}
249 }
250 })}
251
252 }
253
254 "##;
255
256 let expr: Expr = syn::parse_str(contents).unwrap();
257 let out = unparse_expr(&expr, contents, &IndentOptions::default());
258 println!("{}", out);
259 }
260
261 #[test]
262 fn write_body_no_indent() {
263 let src = r##"
264 span {
265 class: "inline-block ml-auto hover:bg-gray-500",
266 onclick: move |evt| {
267 evt.cancel_bubble();
268 },
269 icons::icon_10 {}
270 icons::icon_10 {}
271 icons::icon_10 {}
272 icons::icon_10 {}
273 div { "hi" }
274 div { div {} }
275 div { div {} div {} div {} }
276 {children}
277 {
278 some_big_long()
279 .some_big_long()
280 .some_big_long()
281 .some_big_long()
282 .some_big_long()
283 .some_big_long()
284 }
285 div { class: "px-4", {is_current.then(|| rsx! { {children} })} }
286 Thing {
287 field: rsx! {
288 div { "hi" }
289 Component {
290 onrender: rsx! {
291 div { "hi" }
292 Component {
293 onclick: move |_| {
294 another_macro! {
295 div { class: "max-w-lg lg:max-w-2xl mx-auto mb-16 text-center",
296 "gomg"
297 "hi!!"
298 "womh"
299 }
300 };
301 rsx! {
302 div { class: "max-w-lg lg:max-w-2xl mx-auto mb-16 text-center",
303 "gomg"
304 "hi!!"
305 "womh"
306 }
307 };
308 println!("hi")
309 },
310 onrender: move |_| {
311 let _ = 12;
312 let r = rsx! {
313 div { "hi" }
314 };
315 rsx! {
316 div { "hi" }
317 }
318 }
319 }
320 {
321 rsx! {
322 BarChart {
323 id: "bar-plot".to_string(),
324 x: value,
325 y: label
326 }
327 }
328 }
329 }
330 }
331 }
332 }
333 }
334 "##;
335
336 let tokens: TokenStream = syn::parse_str(src).unwrap();
337 let out = fmt_block_from_expr(src, tokens, IndentOptions::default()).unwrap();
338 println!("{}", out);
339 }
340
341 #[test]
342 fn write_component_body() {
343 let src = r##"
344 div { class: "px-4", {is_current.then(|| rsx! { {children} })} }
345 "##;
346
347 let tokens: TokenStream = syn::parse_str(src).unwrap();
348 let out = fmt_block_from_expr(src, tokens, IndentOptions::default()).unwrap();
349 println!("{}", out);
350 }
351
352 #[test]
353 fn weird_macro() {
354 let contents = r##"
355 fn main() {
356 move |_| {
357 drop_macro_semi! {
358 "something_very_long_something_very_long_something_very_long_something_very_long"
359 };
360 let _ = drop_macro_semi! {
361 "something_very_long_something_very_long_something_very_long_something_very_long"
362 };
363 drop_macro_semi! {
364 "something_very_long_something_very_long_something_very_long_something_very_long"
365 };
366 };
367 }
368 "##;
369
370 let expr: File = syn::parse_file(contents).unwrap();
371 let out = prettyplease::unparse(&expr);
372 println!("{}", out);
373 }
374
375 #[test]
376 fn comments_on_nodes() {
377 let src = r##"// hiasdasds
378 div {
379 attr: "value", // comment
380 div {}
381 "hi" // hello!
382 "hi" // hello!
383 "hi" // hello!
384 // hi!
385 }
386 "##;
387
388 let tokens: TokenStream = syn::parse_str(src).unwrap();
389 let out = fmt_block_from_expr(src, tokens, IndentOptions::default()).unwrap();
390 println!("{}", out);
391 }
392}