cargo_zng/
fmt.rs

1use std::{
2    borrow::Cow,
3    collections::{HashMap, HashSet},
4    fs,
5    io::{self, BufRead, Read, Write},
6    ops,
7    path::{Path, PathBuf},
8    process::Stdio,
9    sync::{
10        Arc,
11        atomic::{AtomicBool, Ordering::Relaxed},
12    },
13    task::Poll,
14    time::{Duration, SystemTime},
15};
16
17use clap::*;
18use once_cell::sync::Lazy;
19use parking_lot::Mutex;
20use rayon::prelude::*;
21use regex::Regex;
22use sha2::Digest;
23
24use crate::util;
25
26/// Bump this for every change that can affect format result.
27const FMT_VERSION: &str = "1";
28
29#[derive(Args, Debug, Default)]
30pub struct FmtArgs {
31    /// Only check if files are formatted
32    #[arg(long, action)]
33    check: bool,
34
35    /// Format the crate identified by Cargo.toml
36    #[arg(long)]
37    manifest_path: Option<String>,
38
39    /// Format the workspace crate identified by package name
40    #[arg(short, long)]
41    package: Option<String>,
42
43    /// Format all files matched by glob
44    #[arg(short, long)]
45    files: Option<String>,
46
47    /// Format the stdin to the stdout
48    #[arg(short, long, action)]
49    stdin: bool,
50
51    /// Rustfmt style edition, enforced for all files
52    #[arg(long, default_value = "2024")]
53    edition: String,
54
55    /// Format or check every file, not just changed files
56    #[arg(long, action)]
57    full: bool,
58
59    /// Output rustfmt stderr, for debugging
60    #[arg(long, action, hide = true)]
61    rustfmt_errors: bool,
62}
63
64pub fn run(mut args: FmtArgs) {
65    if args.rustfmt_errors {
66        SHOW_RUSTFMT_ERRORS.store(true, Relaxed);
67    }
68
69    if args.stdin {
70        if args.manifest_path.is_some() || args.package.is_some() || args.files.is_some() {
71            fatal!("stdin can only be used standalone or with --check");
72        }
73
74        let mut code = String::new();
75        if let Err(e) = std::io::stdin().read_to_string(&mut code) {
76            fatal!("stdin read error, {e}");
77        }
78
79        if code.is_empty() {
80            return;
81        }
82
83        if let Some(code) = rustfmt_stdin(&code, &args.edition) {
84            let stream = code.parse().unwrap_or_else(|e| fatal!("cannot parse stdin, {e}"));
85
86            let fmt_server = FmtFragServer::spawn(args.edition.clone());
87            let mut formatted = Box::pin(try_fmt_child_macros(&code, stream, &fmt_server));
88            let formatted = loop {
89                std::thread::sleep(Duration::from_millis(50));
90                match formatted
91                    .as_mut()
92                    .poll(&mut std::task::Context::from_waker(std::task::Waker::noop()))
93                {
94                    Poll::Ready(r) => break r,
95                    Poll::Pending => {}
96                }
97            };
98
99            if let Err(e) = std::io::stdout().write_all(formatted.as_bytes()) {
100                fatal!("stdout write error, {e}");
101            }
102        }
103
104        return;
105    }
106
107    let mut file_patterns = vec![];
108    if let Some(glob) = &args.files {
109        file_patterns.push(PathBuf::from(glob));
110    }
111    if let Some(pkg) = &args.package {
112        if args.manifest_path.is_some() {
113            fatal!("expected only one of --package, --manifest-path");
114        }
115        match util::manifest_path_from_package(pkg) {
116            Some(m) => args.manifest_path = Some(m),
117            None => fatal!("package `{pkg}` not found in workspace"),
118        }
119    }
120    let manifest_paths = if let Some(path) = &args.manifest_path {
121        vec![PathBuf::from(path)]
122    } else if args.files.is_none() {
123        let workspace_root = workspace_root().unwrap_or_else(|e| fatal!("cannot find workspace root, {e}"));
124        file_patterns.push(workspace_root.join("README.md"));
125        file_patterns.push(workspace_root.join("docs/**/*.md"));
126        util::workspace_manifest_paths()
127    } else {
128        vec![]
129    };
130    for path in manifest_paths {
131        let r = (|path: &Path| -> Result<(), Box<dyn std::error::Error>> {
132            let path = path.parent().ok_or("root dir")?;
133            for entry in fs::read_dir(path)? {
134                let entry = entry?;
135                let ft = entry.file_type()?;
136                if ft.is_dir() {
137                    if entry.file_name() != "target" {
138                        file_patterns.push(entry.path().join("**/*.rs"));
139                        file_patterns.push(entry.path().join("**/*.md"));
140                    }
141                } else if ft.is_file() {
142                    file_patterns.push(entry.path());
143                }
144            }
145
146            Ok(())
147        })(&path);
148        if let Err(e) = r {
149            error!("failed to select files for {}, {e}", path.display())
150        }
151    }
152
153    // if the args are on record only select files modified since then
154    let mut history = match FmtHistory::load() {
155        Ok(h) => h,
156        Err(e) => {
157            warn!("cannot load fmt history, {e}");
158            FmtHistory::default()
159        }
160    };
161    let cutout_time = history.insert(&args);
162    let files: HashSet<PathBuf> = file_patterns
163        .into_par_iter()
164        .flat_map(|pattern| {
165            let files = match glob::glob(&pattern.display().to_string().replace('\\', "/")) {
166                Ok(f) => f.flat_map(|e| e.ok()).collect(),
167                Err(_) => vec![],
168            };
169            files
170                .into_par_iter()
171                .filter(|f| matches!(f.extension(), Some(ext) if ext == "rs" || ext == "md"))
172        })
173        .collect();
174
175    let mut files: Vec<_> = files
176        .into_par_iter()
177        .filter_map(|p| {
178            if let Ok(meta) = std::fs::metadata(&p)
179                && let Ok(modified) = meta.modified()
180            {
181                let modified = FmtHistory::time(modified);
182                if modified > cutout_time {
183                    return Some((p, modified));
184                };
185            }
186            None
187        })
188        .collect();
189
190    // latest modified first
191    files.sort_by(|a, b| b.1.cmp(&a.1));
192
193    let files: Vec<_> = files.into_iter().map(|(p, _)| p).collect();
194
195    let fmt_server = FmtFragServer::spawn(args.edition.clone());
196
197    files.par_chunks(64).for_each(|c| {
198        // apply normal format first
199        rustfmt_files(c, &args.edition, args.check);
200    });
201
202    // apply custom format
203    let check = args.check;
204    let fmt_server2 = fmt_server.clone();
205    let mut futs: Vec<_> = files
206        .par_iter()
207        .map(move |file| {
208            let fmt_server = fmt_server2.clone();
209            Some(Box::pin(async move {
210                let is_rs = file.extension().unwrap() == "rs";
211                let r = if is_rs {
212                    custom_fmt_rs(file.clone(), check, fmt_server).await
213                } else {
214                    debug_assert!(file.extension().unwrap() == "md");
215                    custom_fmt_md(file.clone(), check, fmt_server).await.map(|_| None)
216                };
217                match r {
218                    Ok(r) => r,
219                    Err(e) => {
220                        error!("{e}");
221                        None
222                    }
223                }
224            }))
225        })
226        .collect();
227
228    let reformat = Mutex::new(vec![]);
229    loop {
230        std::thread::sleep(Duration::from_millis(25));
231        futs.par_iter_mut().for_each(|f| {
232            match f
233                .as_mut()
234                .unwrap()
235                .as_mut()
236                .poll(&mut std::task::Context::from_waker(std::task::Waker::noop()))
237            {
238                Poll::Ready(changed) => {
239                    if let Some(p) = changed {
240                        reformat.lock().push(p);
241                    }
242                    *f = None
243                }
244                Poll::Pending => {}
245            }
246        });
247        futs.retain(|t| t.is_some());
248        if futs.is_empty() {
249            break;
250        }
251    }
252
253    let reformat = reformat.into_inner();
254    if !reformat.is_empty() {
255        reformat.par_chunks(64).for_each(|c| {
256            // apply normal format again, see `custom_fmt` docs
257            rustfmt_files(c, &args.edition, args.check);
258        });
259    }
260
261    if let Err(e) = history.save() {
262        warn!("cannot save fmt history, {e}")
263    }
264}
265
266/// Applies custom format for all macro bodies in file, if changed writes the file and returns
267/// the file path for reformat.
268///
269/// Changed files need reformat in cases like `[Macro!{..}, Macro!{..}]` where the custom macro format
270/// introduces line break, this causes rustfmt to also make the `[]` multiline.
271async fn custom_fmt_rs(rs_file: PathBuf, check: bool, fmt: FmtFragServer) -> io::Result<Option<PathBuf>> {
272    let file = fs::read_to_string(&rs_file)?;
273
274    // skip UTF-8 BOM
275    let file_code = file.strip_prefix('\u{feff}').unwrap_or(file.as_str());
276    // skip shebang line
277    let file_code = if file_code.starts_with("#!") && !file_code.starts_with("#![") {
278        &file_code[file_code.find('\n').unwrap_or(file_code.len())..]
279    } else {
280        file_code
281    };
282
283    let mut formatted_code = file[..file.len() - file_code.len()].to_owned();
284    formatted_code.reserve(file.len());
285
286    let file_stream = match file_code.parse() {
287        Ok(s) => s,
288        Err(e) => {
289            if SHOW_RUSTFMT_ERRORS.load(Relaxed) {
290                error!("cannot parse `{}`, {e}", rs_file.display());
291            }
292            return Ok(None);
293        }
294    };
295    formatted_code.push_str(&try_fmt_child_macros(file_code, file_stream, &fmt).await);
296
297    let formatted_code = custom_fmt_docs(&formatted_code, &fmt, &rs_file).await;
298
299    if formatted_code != file {
300        if check {
301            // reformat early for check
302            let formatted_code = fmt.format(formatted_code.clone()).await.unwrap_or(formatted_code);
303            if formatted_code != file {
304                let diff = similar::TextDiff::from_lines(&file, &formatted_code);
305                fatal!("Diff in {}:\n{}", rs_file.display(), diff.unified_diff().context_radius(2));
306            }
307            return Ok(None);
308        }
309        fs::write(&rs_file, formatted_code)?;
310        Ok(Some(rs_file))
311    } else {
312        Ok(None)
313    }
314}
315async fn custom_fmt_docs(code: &str, fmt: &FmtFragServer, rs_file: &Path) -> String {
316    let mut formatted_code = String::new();
317    let mut lines = code.lines().peekable();
318    while let Some(mut line) = lines.next() {
319        let maybe = line.trim_start();
320        if maybe.starts_with("//!") || maybe.starts_with("///") {
321            // enter docs sequence
322            let prefix = &line[..line.find("//").unwrap() + 3];
323            loop {
324                // push markdown lines, or doctest header line
325                formatted_code.push_str(line);
326                formatted_code.push('\n');
327
328                let doc_line = line.strip_prefix(prefix).unwrap();
329                match doc_line.trim().strip_prefix("```") {
330                    Some("" | "rust" | "should_panic" | "no_run" | "edition2015" | "edition2018" | "edition2021" | "edition2024") => {
331                        // is doctest header line
332
333                        let mut code = String::new();
334                        let mut close_line = "";
335                        while let Some(l) = lines.next_if(|l| l.starts_with(prefix)) {
336                            let doc_line = l.strip_prefix(prefix).unwrap();
337                            if doc_line.trim_start().starts_with("```") {
338                                close_line = l;
339                                break;
340                            }
341                            code.push_str(doc_line);
342                            code.push('\n');
343                        }
344
345                        static HIDDEN_LINES_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^ *#(?: +(.*)$|$)").unwrap());
346                        if !close_line.is_empty() // is properly closed
347                        && !code.trim().is_empty() // is not empty
348                        && let Some(mut code) = {
349                            let escaped = format!("fn __zng_fmt() {{\n{}\n}}", HIDDEN_LINES_RGX.replace_all(&code, "// __# $1"));
350                            fmt.format(escaped).await
351                        } {
352                            // rustfmt ok
353
354                            let stream = code
355                                .parse::<proc_macro2::TokenStream>()
356                                .map(pm2_send::TokenStream::from)
357                                .map_err(|e| e.to_string());
358                            match stream {
359                                Ok(s) => code = try_fmt_child_macros(&code, s, fmt).await,
360                                Err(e) => {
361                                    if SHOW_RUSTFMT_ERRORS.load(Relaxed) {
362                                        error!("cannot parse doctest block in `{}`, {e}", rs_file.display());
363                                    }
364                                }
365                            };
366
367                            let code = code
368                                .strip_prefix("fn __zng_fmt() {")
369                                .unwrap()
370                                .trim_end()
371                                .strip_suffix('}')
372                                .unwrap()
373                                .replace("// __# ", "# ")
374                                .replace("// __#", "#");
375                            let mut fmt_code = String::new();
376                            let mut wrapper_tabs = String::new();
377                            for line in code.lines() {
378                                if line.trim().is_empty() {
379                                    fmt_code.push('\n');
380                                } else {
381                                    if wrapper_tabs.is_empty() {
382                                        for _ in 0..(line.len() - line.trim_start().len()) {
383                                            wrapper_tabs.push(' ');
384                                        }
385                                    }
386                                    fmt_code.push_str(line.strip_prefix(&wrapper_tabs).unwrap_or(line));
387                                    fmt_code.push('\n');
388                                }
389                            }
390                            for line in fmt_code.trim().lines() {
391                                formatted_code.push_str(prefix);
392                                if !line.trim().is_empty() {
393                                    formatted_code.push(' ');
394                                    formatted_code.push_str(line);
395                                }
396                                formatted_code.push('\n');
397                            }
398                        } else {
399                            // failed format
400                            for line in code.lines() {
401                                formatted_code.push_str(prefix);
402                                formatted_code.push_str(line);
403                                formatted_code.push('\n');
404                            }
405                        }
406                        if !close_line.is_empty() {
407                            formatted_code.push_str(close_line);
408                            formatted_code.push('\n');
409                        }
410                    }
411                    Some(_) => {
412                        // is Markdown code block for `ignore`, `compile_fail` or another language
413                        while let Some(l) = lines.next_if(|l| l.starts_with(prefix)) {
414                            formatted_code.push_str(l);
415                            formatted_code.push('\n');
416                            let doc_line = l.strip_prefix(prefix).unwrap();
417                            if doc_line.trim_start().starts_with("```") {
418                                break;
419                            }
420                        }
421                    }
422                    None => {}
423                }
424
425                match lines.next_if(|l| l.starts_with(prefix)) {
426                    Some(l) => line = l, // advance to next line in the same doc sequence
427                    None => break,       // continue to seek next doc sequence
428                }
429            }
430        } else {
431            // normal code lines, already formatted
432            formatted_code.push_str(line);
433            formatted_code.push('\n');
434        }
435    }
436    formatted_code
437}
438/// Applies rustfmt and custom format for all Rust code blocks in the Markdown file
439async fn custom_fmt_md(md_file: PathBuf, check: bool, fmt: FmtFragServer) -> io::Result<()> {
440    let file = fs::read_to_string(&md_file)?;
441
442    let mut formatted = String::new();
443
444    let mut lines = file.lines();
445    while let Some(line) = lines.next() {
446        if line.trim_start().starts_with("```rust") {
447            formatted.push_str(line);
448            formatted.push('\n');
449
450            let mut code = String::new();
451            let mut close_line = "";
452            for line in lines.by_ref() {
453                if line.trim_start().starts_with("```") {
454                    close_line = line;
455                    break;
456                } else {
457                    code.push_str(line);
458                    code.push('\n');
459                }
460            }
461
462            if close_line.is_empty() {
463                formatted.push_str(&code);
464                continue;
465            }
466
467            // format code block
468            if !code.trim().is_empty()
469                && let Some(mut code) = fmt.format(format!("fn __zng_fmt() {{\n{code}\n}}")).await
470            {
471                // rustfmt ok
472
473                let stream = code
474                    .parse::<proc_macro2::TokenStream>()
475                    .map(pm2_send::TokenStream::from)
476                    .map_err(|e| e.to_string());
477                match stream {
478                    Ok(s) => code = try_fmt_child_macros(&code, s, &fmt).await,
479                    Err(e) => {
480                        if SHOW_RUSTFMT_ERRORS.load(Relaxed) {
481                            error!("cannot parse code block in `{}`, {e}", md_file.display());
482                        }
483                    }
484                };
485                let code = code.strip_prefix("fn __zng_fmt() {").unwrap().trim_end().strip_suffix('}').unwrap();
486                let mut fmt_code = String::new();
487                let mut wrapper_tabs = String::new();
488                for line in code.lines() {
489                    if line.trim().is_empty() {
490                        fmt_code.push('\n');
491                    } else {
492                        if wrapper_tabs.is_empty() {
493                            for _ in 0..(line.len() - line.trim_start().len()) {
494                                wrapper_tabs.push(' ');
495                            }
496                        }
497                        fmt_code.push_str(line.strip_prefix(&wrapper_tabs).unwrap_or(line));
498                        fmt_code.push('\n');
499                    }
500                }
501                formatted.push_str(fmt_code.trim());
502                formatted.push('\n');
503            } else {
504                formatted.push_str(&code);
505            }
506            formatted.push_str(close_line);
507            formatted.push('\n');
508        } else {
509            formatted.push_str(line);
510            formatted.push('\n')
511        }
512    }
513
514    if formatted != file {
515        if check {
516            let diff = similar::TextDiff::from_lines(&file, &formatted);
517            fatal!("Diff in {}:\n{}", md_file.display(), diff.unified_diff().context_radius(2));
518        }
519        fs::write(&md_file, formatted)?;
520    }
521
522    Ok(())
523}
524
525/// Find "macro_ident! {}" and `try_fmt_macro` the macro body if it is not marked skip
526async fn try_fmt_child_macros(code: &str, stream: pm2_send::TokenStream, fmt: &FmtFragServer) -> String {
527    let mut formatted_code = String::new();
528    let mut last_already_fmt_start = 0;
529
530    let mut stream_stack = vec![stream.into_iter()];
531    let next = |stack: &mut Vec<std::vec::IntoIter<pm2_send::TokenTree>>| {
532        while !stack.is_empty() {
533            let tt = stack.last_mut().unwrap().next();
534            if let Some(tt) = tt {
535                return Some(tt);
536            }
537            stack.pop();
538        }
539        None
540    };
541    let mut tail2: Vec<pm2_send::TokenTree> = Vec::with_capacity(2);
542
543    let mut skip_next_group = false;
544    while let Some(tt) = next(&mut stream_stack) {
545        match tt {
546            pm2_send::TokenTree::Group(g) => {
547                if tail2.len() == 2
548                    && matches!(g.delimiter(), pm2_send::Delimiter::Brace)
549                    && matches!(&tail2[0], pm2_send::TokenTree::Punct(p) if p.as_char() == '!')
550                    && matches!(&tail2[1], pm2_send::TokenTree::Ident(_))
551                {
552                    // macro! {}
553                    if std::mem::take(&mut skip_next_group) {
554                        continue;
555                    }
556                    if let pm2_send::TokenTree::Ident(i) = &tail2[1] {
557                        if i == &"__P_" || i == &"quote" || i == &"quote_spanned" || i == &"parse_quote" || i == &"parse_quote_spanned" {
558                            continue;
559                        }
560                    } else {
561                        unreachable!()
562                    }
563
564                    let bang = tail2[0].span().byte_range().start;
565                    let line_start = code[..bang].rfind('\n').unwrap_or(0);
566                    let base_indent = code[line_start..bang]
567                        .chars()
568                        .skip_while(|&c| c != ' ')
569                        .take_while(|&c| c == ' ')
570                        .count();
571
572                    let group_bytes = g.span().byte_range();
573                    let group_code = &code[group_bytes.clone()];
574
575                    if let Some(formatted) = try_fmt_macro(base_indent, group_code, fmt).await
576                        && formatted != group_code
577                    {
578                        // changed by custom format
579                        if let Some(stable) = try_fmt_macro(base_indent, &formatted, fmt).await {
580                            if formatted == stable {
581                                // change is sable
582                                let already_fmt = &code[last_already_fmt_start..group_bytes.start];
583                                formatted_code.push_str(already_fmt);
584                                formatted_code.push_str(&formatted);
585                                last_already_fmt_start = group_bytes.end;
586                            } else if SHOW_RUSTFMT_ERRORS.load(Relaxed) {
587                                error!("unstable format skipped");
588                            }
589                        }
590                    }
591                } else if !tail2.is_empty()
592                    && matches!(g.delimiter(), pm2_send::Delimiter::Bracket)
593                    && matches!(&tail2[0], pm2_send::TokenTree::Punct(p) if p.as_char() == '#')
594                {
595                    // #[..]
596                    let mut attr = g.stream().into_iter();
597                    let attr = [attr.next(), attr.next(), attr.next(), attr.next(), attr.next()];
598                    if let [
599                        Some(pm2_send::TokenTree::Ident(i0)),
600                        Some(pm2_send::TokenTree::Punct(p0)),
601                        Some(pm2_send::TokenTree::Punct(p1)),
602                        Some(pm2_send::TokenTree::Ident(i1)),
603                        None,
604                    ] = attr
605                        && i0 == "rustfmt"
606                        && p0.as_char() == ':'
607                        && p1.as_char() == ':'
608                        && i1 == "skip"
609                    {
610                        // #[rustfmt::skip]
611                        skip_next_group = true;
612                    }
613                } else if !std::mem::take(&mut skip_next_group) {
614                    stream_stack.push(g.stream().into_iter());
615                }
616                tail2.clear();
617            }
618            ref tt1 @ pm2_send::TokenTree::Ident(ref i) if i == &"macro_rules" => {
619                if let Some(tt2) = next(&mut stream_stack) {
620                    if matches!(tt2, pm2_send::TokenTree::Punct(ref p) if p.as_char() == '!') {
621                        // macro_rules!
622                        next(&mut stream_stack); // skip ident
623                        next(&mut stream_stack); // skip body
624                    } else {
625                        tail2.clear();
626                        tail2.push(tt2);
627                        tail2.push(tt1.clone());
628                    }
629                } else {
630                    // end
631                }
632            }
633            tt => {
634                if tail2.len() == 2 {
635                    tail2.pop();
636                }
637                tail2.insert(0, tt);
638            }
639        }
640    }
641
642    formatted_code.push_str(&code[last_already_fmt_start..]);
643    formatted_code
644}
645
646async fn try_fmt_macro(base_indent: usize, group_code: &str, fmt: &FmtFragServer) -> Option<String> {
647    // replace supported macro syntax to equivalent valid Rust for rustfmt
648    let mut replaced_code = Cow::Borrowed(group_code);
649
650    let mut is_lazy_static = false;
651    if matches!(&replaced_code, Cow::Borrowed(_)) {
652        replaced_code = replace_static_ref(group_code, false);
653        is_lazy_static = matches!(&replaced_code, Cow::Owned(_));
654    }
655
656    let mut is_bitflags = false;
657    if matches!(&replaced_code, Cow::Borrowed(_)) {
658        replaced_code = replace_bitflags(group_code, false);
659        is_bitflags = matches!(&replaced_code, Cow::Owned(_));
660    }
661
662    let mut is_event_args = false;
663    if matches!(&replaced_code, Cow::Borrowed(_)) {
664        replaced_code = replace_event_args(group_code, false);
665        is_event_args = matches!(&replaced_code, Cow::Owned(_));
666    }
667
668    let mut is_event = false;
669    if matches!(&replaced_code, Cow::Borrowed(_)) {
670        replaced_code = replace_event(group_code, false);
671        is_event = matches!(&replaced_code, Cow::Owned(_));
672    }
673
674    let mut is_command = false;
675    if matches!(&replaced_code, Cow::Borrowed(_)) {
676        replaced_code = replace_command(group_code, false);
677        is_command = matches!(&replaced_code, Cow::Owned(_));
678    }
679
680    let mut is_widget_impl = false;
681    if matches!(&replaced_code, Cow::Borrowed(_)) {
682        replaced_code = replace_widget_impl(group_code, false);
683        is_widget_impl = matches!(&replaced_code, Cow::Owned(_));
684    }
685
686    let mut is_widget = false;
687    if matches!(&replaced_code, Cow::Borrowed(_)) {
688        replaced_code = replace_widget(group_code, false);
689        is_widget = matches!(&replaced_code, Cow::Owned(_));
690    }
691
692    let mut is_expr_var = false;
693    if matches!(&replaced_code, Cow::Borrowed(_)) {
694        replaced_code = replace_expr_var(group_code, false);
695        is_expr_var = matches!(&replaced_code, Cow::Owned(_));
696    }
697
698    let mut is_struct_like = false;
699    if matches!(&replaced_code, Cow::Borrowed(_)) {
700        replaced_code = replace_struct_like(group_code, false);
701        is_struct_like = matches!(&replaced_code, Cow::Owned(_));
702    }
703
704    let mut is_simple_list = false;
705    if matches!(&replaced_code, Cow::Borrowed(_)) {
706        replaced_code = replace_simple_ident_list(group_code, false);
707        is_simple_list = matches!(&replaced_code, Cow::Owned(_));
708    }
709
710    let mut is_when_var = false;
711    if matches!(&replaced_code, Cow::Borrowed(_)) {
712        replaced_code = replace_when_var(group_code, false);
713        is_when_var = matches!(&replaced_code, Cow::Owned(_));
714    }
715
716    let replaced_code = replaced_code.into_owned();
717    // fmt inner macros first, their final format can affect this macros format
718    let code_stream: pm2_send::TokenStream = match replaced_code.parse() {
719        Ok(t) => t,
720        Err(e) => {
721            if SHOW_RUSTFMT_ERRORS.load(Relaxed) {
722                error!("internal error: {e}");
723                eprintln!("CODE:\n{replaced_code}");
724            }
725            return None;
726        }
727    };
728    let mut inner_group = None;
729    for tt in code_stream {
730        if let pm2_send::TokenTree::Group(g) = tt {
731            // find the inner block, some replacements add prefixes or swap the delimiters
732            inner_group = Some(g);
733            break;
734        }
735    }
736    let code_stream = match inner_group {
737        Some(g) => g.stream(),
738        None => {
739            if SHOW_RUSTFMT_ERRORS.load(Relaxed) {
740                error!("internal error, invalid replacement");
741                eprintln!("CODE:\n{replaced_code}");
742            }
743            return None;
744        }
745    };
746    let code = Box::pin(try_fmt_child_macros(&replaced_code, code_stream, fmt)).await;
747
748    // apply rustfmt
749    let code = fmt.format(code).await?;
750
751    // restore supported macro syntax
752    let code = if is_event_args {
753        replace_event_args(&code, true)
754    } else if is_widget {
755        replace_widget(&code, true)
756    } else if is_expr_var {
757        replace_expr_var(&code, true)
758    } else if is_lazy_static {
759        replace_static_ref(&code, true)
760    } else if is_event {
761        replace_event(&code, true)
762    } else if is_command {
763        replace_command(&code, true)
764    } else if is_struct_like {
765        replace_struct_like(&code, true)
766    } else if is_bitflags {
767        replace_bitflags(&code, true)
768    } else if is_simple_list {
769        replace_simple_ident_list(&code, true)
770    } else if is_widget_impl {
771        replace_widget_impl(&code, true)
772    } else if is_when_var {
773        replace_when_var(&code, true)
774    } else {
775        Cow::Owned(code)
776    };
777
778    // restore indent
779    let mut out = String::new();
780    let mut lb_indent = String::with_capacity(base_indent + 1);
781    for line in code.lines() {
782        if line.is_empty() {
783            if !lb_indent.is_empty() {
784                out.push('\n');
785            }
786        } else {
787            out.push_str(&lb_indent);
788        }
789        out.push_str(line);
790        // "\n    "
791        if lb_indent.is_empty() {
792            lb_indent.push('\n');
793            for _ in 0..base_indent {
794                lb_indent.push(' ');
795            }
796        }
797    }
798    Some(out)
799}
800// replace line with only `..` tokens with:
801//
802// ```
803// // cargo-zng::fmt::dot_dot
804// }
805// impl CargoZngFmt {
806//
807// ```
808fn replace_event_args(code: &str, reverse: bool) -> Cow<'_, str> {
809    static RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\s*(\.\.)\s*$").unwrap());
810    static MARKER: &str = "// cargo-zng::fmt::dot_dot\n}\nimpl CargoZngFmt {\n";
811    static RGX_REV: Lazy<Regex> =
812        Lazy::new(|| Regex::new(r"(?m)^(\s+)// cargo-zng::fmt::dot_dot\n\s*}\n\s*impl CargoZngFmt\s*\{\n").unwrap());
813
814    if !reverse {
815        RGX.replace_all(code, |caps: &regex::Captures| {
816            format!(
817                "{}{MARKER}{}",
818                &caps[0][..caps.get(1).unwrap().start() - caps.get(0).unwrap().start()],
819                &caps[0][caps.get(1).unwrap().end() - caps.get(0).unwrap().start()..]
820            )
821        })
822    } else {
823        RGX_REV.replace_all(code, "\n$1..\n\n")
824    }
825}
826// replace `static IDENT: Args { ` with `static IDENT: __fmt__::Args = {`
827fn replace_event(code: &str, reverse: bool) -> Cow<'_, str> {
828    static RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)([^'])static +(\w+):\s+([\w:]+)\s+\{").unwrap());
829    static RGX_REV: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)([^'])static +(\w+): __fmt__::([\w:]+) = \{").unwrap());
830    if !reverse {
831        RGX.replace_all(code, "${1}static $2: __fmt__::$3 = {")
832    } else {
833        RGX_REV.replace_all(code, "${1}static $2: $3 {")
834    }
835}
836// replace `static IDENT { foo:` with `static IDENT: __fmt__ = __A_ {`
837// AND replace `static IDENT;` with `static IDENT: __fmt__ = T;`
838// AND replace `l10n!: ` with `l10n__fmt:`
839fn replace_command(code: &str, reverse: bool) -> Cow<'_, str> {
840    static RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)([^'])static +(\w+) ? ?\{(\s+\w+!?:)").unwrap());
841    static RGX_DEFAULTS: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)([^'])static +(\w+) ?;").unwrap());
842    if !reverse {
843        let cmd = RGX_DEFAULTS.replace_all(code, "${1}static $2: __fmt__ = T;");
844        let mut cmd2 = RGX.replace_all(&cmd, "${1}static $2: __fmt__ = __A_ {$3");
845        if let Cow::Owned(cmd) = &mut cmd2 {
846            *cmd = cmd.replace("l10n!:", "l10n__fmt:");
847        }
848        match cmd2 {
849            Cow::Borrowed(_) => cmd,
850            Cow::Owned(s) => Cow::Owned(s),
851        }
852    } else {
853        Cow::Owned(
854            code.replace(": __fmt__ = T;", ";")
855                .replace(": __fmt__ = __A_ {", " {")
856                .replace("l10n__fmt:", "l10n!:"),
857        )
858    }
859}
860/// Escape widget macro syntax
861fn replace_widget(code: &str, reverse: bool) -> Cow<'_, str> {
862    static IGNORE_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m): +\w+\s+=\s+\{").unwrap());
863    static PROPERTY_NAME_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\s*([\w:<>]+)\s+=\s+").unwrap());
864
865    #[derive(Debug)]
866    enum Item<'s> {
867        Property { name: &'s str, value: &'s str },
868        PropertyShorthand(&'s str),
869        When { expr: &'s str, items: Vec<Item<'s>> },
870        Text(&'s str),
871        WidgetSetSelfExpr(&'s str),
872    }
873    #[allow(unused)]
874    struct Error<'s> {
875        partial: Vec<Item<'s>>,
876        error: &'static str,
877    }
878    impl<'s> Error<'s> {
879        fn new(partial: Vec<Item<'s>>, error: &'static str) -> Self {
880            Self { partial, error }
881        }
882    }
883    fn parse<'s>(code: &'s str, stream: proc_macro2::TokenStream, code_span: ops::Range<usize>) -> Result<Vec<Item<'s>>, Error<'s>> {
884        use proc_macro2::{Delimiter, TokenTree as Tt};
885
886        let mut items = vec![];
887        let mut stream = stream.into_iter().peekable();
888        let mut text_start = code_span.start;
889
890        if code_span.start == 1 {
891            if let Some(Tt::Group(g)) = stream.next()
892                && g.delimiter() == Delimiter::Brace
893            {
894                stream = g.stream().into_iter().peekable();
895            } else {
896                return Err(Error::new(items, "expected macro block at root"));
897            }
898
899            if let Some(Tt::Punct(p)) = stream.peek()
900                && p.as_char() == '&'
901            {
902                // widget_set! first line can be "&mut self_ident;"
903                let amp = stream.next().unwrap();
904                if let Some(Tt::Ident(m)) = stream.next()
905                    && m == "mut"
906                {
907                    let start = amp.span().byte_range().start;
908                    for tt in stream.by_ref() {
909                        if let Tt::Punct(p) = tt
910                            && p.as_char() == ';'
911                        {
912                            let end = p.span().byte_range().end;
913                            items.push(Item::Text(&code[text_start..start]));
914                            items.push(Item::WidgetSetSelfExpr(&code[start..end]));
915                            text_start = end;
916                            break;
917                        }
918                    }
919                }
920                if text_start == code_span.start {
921                    return Err(Error::new(items, "expected &mut <self>"));
922                }
923            }
924        }
925        'outer: while let Some(tt_attr_or_name) = stream.next() {
926            // skip attributes
927            if let Tt::Punct(p) = &tt_attr_or_name
928                && p.as_char() == '#'
929            {
930                if let Some(Tt::Group(g)) = stream.next()
931                    && g.delimiter() == proc_macro2::Delimiter::Bracket
932                {
933                    continue 'outer;
934                } else {
935                    return Err(Error::new(items, "expected attribute"));
936                }
937            }
938
939            // match property name or when
940            if let Tt::Ident(ident) = &tt_attr_or_name {
941                if ident == "when" {
942                    items.push(Item::Text(&code[text_start..ident.span().byte_range().start]));
943
944                    // `when` is like an `if <expr> <block>`, the <expr> can be <ident><block> (Foo { }.expr())
945                    // easiest way to deal with this is to seek/peek the next when or property
946                    let expr_start = ident.span().byte_range().end;
947                    if stream.next().is_some()
948                        && let Some(mut tt_block) = stream.next()
949                    {
950                        // needs at least two
951                        loop {
952                            if let Tt::Group(g) = &tt_block
953                                && g.delimiter() == Delimiter::Brace
954                                && stream
955                                    .peek()
956                                    .map(|tt| matches!(tt, Tt::Ident(_)) || matches!(tt, Tt::Punct(p) if p.as_char() == '#'))
957                                    .unwrap_or(true)
958                            {
959                                // peek next is property_name, attribute, when or eof
960                                let block_span = g.span().byte_range();
961                                let expr = &code[expr_start..block_span.start];
962                                items.push(Item::When {
963                                    expr,
964                                    items: parse(code, g.stream(), g.span_open().byte_range().end..g.span_close().byte_range().start)?,
965                                });
966                                text_start = block_span.end;
967                                continue 'outer;
968                            }
969                            // take expr
970                            if let Some(tt) = stream.next() {
971                                tt_block = tt;
972                            } else {
973                                break;
974                            }
975                        }
976                    } else {
977                        return Err(Error::new(items, "expected when expression and block"));
978                    }
979                } else {
980                    // take name, can be ident, path::to::ident, or ident::<Ty>
981                    let name_start = tt_attr_or_name.span().byte_range().start;
982                    let mut tt_name_end = tt_attr_or_name;
983                    while let Some(tt) = stream
984                        .next_if(|tt| matches!(tt, Tt::Ident(_)) || matches!(tt, Tt::Punct(p) if [':', '<', '>'].contains(&p.as_char())))
985                    {
986                        tt_name_end = tt;
987                    }
988
989                    items.push(Item::Text(&code[text_start..name_start]));
990
991                    let name_end = tt_name_end.span().byte_range().end;
992                    let name = &code[name_start..name_end];
993                    if name.is_empty() {
994                        return Err(Error::new(items, "expected property name"));
995                    }
996
997                    if let Some(tt_punct) = stream.next() {
998                        if let Tt::Punct(p) = tt_punct {
999                            if p.as_char() == ';' {
1000                                items.push(Item::PropertyShorthand(name));
1001                                text_start = p.span().byte_range().end;
1002                                continue 'outer;
1003                            } else if p.as_char() == '=' {
1004                                // take value
1005                                let value_start = p.span().byte_range().end;
1006                                if let Some(mut tt_value_end) = stream.next() {
1007                                    while let Some(tt) = stream.next_if(|tt| !matches!(tt, Tt::Punct(p) if p.as_char() == ';')) {
1008                                        tt_value_end = tt;
1009                                    }
1010                                    text_start = tt_value_end.span().byte_range().end;
1011                                    items.push(Item::Property {
1012                                        name,
1013                                        value: &code[value_start..text_start],
1014                                    });
1015                                    if let Some(tt_semi) = stream.next() {
1016                                        debug_assert!(matches!(&tt_semi, Tt::Punct(p) if p.as_char() == ';'));
1017                                        text_start = tt_semi.span().byte_range().end;
1018                                    }
1019                                    continue 'outer;
1020                                } else {
1021                                    return Err(Error::new(items, "expected value"));
1022                                }
1023                            }
1024                        } else {
1025                            return Err(Error::new(items, "expected = or ;"));
1026                        }
1027                    } else {
1028                        // EOF shorthand
1029                        items.push(Item::PropertyShorthand(name));
1030                        text_start = name_end;
1031                        continue 'outer;
1032                    }
1033                }
1034            }
1035
1036            return Err(Error::new(items, "expected attribute or property name"));
1037        }
1038
1039        items.push(Item::Text(&code[text_start..code_span.end]));
1040
1041        Ok(items)
1042    }
1043
1044    if !reverse {
1045        if !PROPERTY_NAME_RGX.is_match(&code[1..code.len() - 1]) || IGNORE_RGX.is_match(code) {
1046            // ignore static IDENT: Ty = expr
1047            return Cow::Borrowed(code);
1048        }
1049        let items = match code.parse() {
1050            Ok(t) => match parse(code, t, 1..code.len() - 1) {
1051                Ok(its) => its,
1052                Err(e) => {
1053                    if SHOW_RUSTFMT_ERRORS.load(Relaxed) {
1054                        // the regex is a best shot to avoid paring
1055                        warn!("cannot parse widget, {}", e.error);
1056                    }
1057                    return Cow::Borrowed(code);
1058                }
1059            },
1060            Err(_) => return Cow::Borrowed(code),
1061        };
1062
1063        fn escape(items: &[Item], r: &mut String) {
1064            for item in items {
1065                match item {
1066                    // path::ident = expr;
1067                    // OR ident = expr0, expr1;
1068                    // OR ident = { field: expr, };
1069                    Item::Property { name, value } => {
1070                        r.push_str(name); // even `path::ident::<Ty>` just works here
1071                        r.push_str(" =");
1072
1073                        static NAMED_FIELDS_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\s*\{\s*\w+:\s+").unwrap());
1074                        if NAMED_FIELDS_RGX.is_match(value) {
1075                            r.push_str(" __ZngFmt");
1076                            r.push_str(value);
1077                            r.push(';');
1078                        } else if value.trim() == "unset!" {
1079                            r.push_str("__unset!();");
1080                        } else {
1081                            r.push_str(" __fmt(");
1082                            r.push_str(value);
1083                            r.push_str("); /*__fmt*/");
1084                        }
1085                    }
1086                    Item::PropertyShorthand(name) => {
1087                        r.push_str(name);
1088                        r.push(';');
1089                    }
1090                    Item::When { expr, items } => {
1091                        r.push_str("/*__fmt_w*/ if ");
1092                        // replace #{}
1093                        let expr = replace_expr_var(expr, false);
1094                        // replace #path
1095                        static PROPERTY_REF_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)#([\w:]+)").unwrap());
1096                        r.push_str(&PROPERTY_REF_RGX.replace_all(&expr, "__P_($1)"));
1097                        r.push_str(" { /*__fmt*/");
1098                        escape(items, r);
1099                        r.push('}');
1100                    }
1101                    Item::Text(txt) => {
1102                        r.push_str(txt);
1103                    }
1104                    Item::WidgetSetSelfExpr(expr) => {
1105                        r.push_str("let __fmt_self = ");
1106                        r.push_str(expr);
1107                        r.push_str("; /*__zng-fmt*/");
1108                    }
1109                }
1110            }
1111        }
1112        let mut escaped = "{".to_owned();
1113        escape(&items, &mut escaped);
1114        escaped.push('}');
1115        Cow::Owned(escaped)
1116    } else {
1117        let code = code
1118            .replace("= __ZngFmt {", "= {")
1119            .replace("); /*__fmt*/", ";")
1120            .replace("__unset!()", "unset!")
1121            .replace("let __fmt_self = ", "")
1122            .replace("; /*__zng-fmt*/", ";");
1123
1124        static WHEN_PREFIX_REV_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"/\*__fmt_w\*/\s+if").unwrap());
1125        static WHEN_SUFFIX_REV_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r" \{\s+/\*__fmt\*/").unwrap());
1126        let code = WHEN_PREFIX_REV_RGX.replace_all(&code, "when");
1127        let code = WHEN_SUFFIX_REV_RGX.replace_all(&code, " {");
1128        let code = match replace_expr_var(&code, true) {
1129            Cow::Borrowed(_) => Cow::Owned(code.into_owned()),
1130            Cow::Owned(o) => Cow::Owned(o),
1131        };
1132
1133        // like `.replace("= __fmt(", "= ")`, but only adds the space after = if did not wrap
1134        // this is important to avoid inserting a trailing space and causing a reformat for every file
1135        static UNNAMED_VALUE_REV_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)=\s+__fmt\(([^\r\n]?)").unwrap());
1136        let replaced = UNNAMED_VALUE_REV_RGX.replace_all(&code, |caps: &regex::Captures| {
1137            let next_char = &caps[1];
1138            if next_char.is_empty() {
1139                "=".to_owned()
1140            } else {
1141                format!("= {next_char}")
1142            }
1143        });
1144        let code = match replaced {
1145            Cow::Borrowed(_) => Cow::Owned(code.into_owned()),
1146            Cow::Owned(o) => Cow::Owned(o),
1147        };
1148
1149        static PROPERTY_REF_REV_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)__P_\(([\w:]+)\)").unwrap());
1150        match PROPERTY_REF_REV_RGX.replace_all(&code, "#$1") {
1151            Cow::Borrowed(_) => Cow::Owned(code.into_owned()),
1152            Cow::Owned(o) => Cow::Owned(o),
1153        }
1154    }
1155}
1156
1157// replace `#{` with `__P_!{`
1158fn replace_expr_var(code: &str, reverse: bool) -> Cow<'_, str> {
1159    static POUND_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(#)[\w\{]").unwrap());
1160    static POUND_REV_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"__P_!\s?").unwrap());
1161    if !reverse {
1162        POUND_RGX.replace_all(code, |caps: &regex::Captures| {
1163            let c = &caps[0][caps.get(1).unwrap().end() - caps.get(0).unwrap().start()..];
1164            if c == "{" {
1165                Cow::Borrowed("__P_!{")
1166            } else {
1167                Cow::Owned(caps[0].to_owned())
1168            }
1169        })
1170    } else {
1171        POUND_REV_RGX.replace_all(code, "#")
1172    }
1173}
1174// replace `{ pattern => expr }` with `static __zng_fmt__: T = match 0 { pattern => expr }; /*__zng-fmt*/`
1175fn replace_when_var(code: &str, reverse: bool) -> Cow<'_, str> {
1176    if !reverse {
1177        let stream: proc_macro2::TokenStream = match code[1..code.len() - 1].parse() {
1178            Ok(s) => s,
1179            Err(_) => return Cow::Borrowed(code),
1180        };
1181        let mut arrow_at_root = false;
1182        let mut stream = stream.into_iter();
1183        while let Some(tt) = stream.next() {
1184            if let proc_macro2::TokenTree::Punct(p) = tt
1185                && p.as_char() == '='
1186                && let Some(proc_macro2::TokenTree::Punct(p2)) = stream.next()
1187                && p2.as_char() == '>'
1188            {
1189                arrow_at_root = true;
1190                break;
1191            }
1192        }
1193        if arrow_at_root {
1194            Cow::Owned(format!("static __zng_fmt__: T = match 0 {code}; /*__zng-fmt*/"))
1195        } else {
1196            Cow::Borrowed(code)
1197        }
1198    } else {
1199        Cow::Owned(
1200            code.replace("static __zng_fmt__: T = match 0 {", "{")
1201                .replace("}; /*__zng-fmt*/", "}"),
1202        )
1203    }
1204}
1205// replace `static ref ` with `static __fmt_ref__`
1206fn replace_static_ref(code: &str, reverse: bool) -> Cow<'_, str> {
1207    if !reverse {
1208        if code.contains("static ref ") {
1209            Cow::Owned(code.replace("static ref ", "static __fmt_ref__"))
1210        } else {
1211            Cow::Borrowed(code)
1212        }
1213    } else {
1214        Cow::Owned(code.replace("static __fmt_ref__", "static ref "))
1215    }
1216}
1217
1218// replace `{ foo: <rest> }` with `static __fmt__: T = __A_ { foo: <rest> } /*__zng-fmt*/`, if the `{` is the first token of  the line
1219// OR with `struct __ZngFmt__ {` if contains generics, signifying declaration
1220fn replace_struct_like(code: &str, reverse: bool) -> Cow<'_, str> {
1221    static RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\s*\{\s+(\w+):([^:])").unwrap());
1222    static RGX_GENERICS: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m):\s*\w+<\w").unwrap());
1223    if !reverse {
1224        if RGX.is_match(code) {
1225            if RGX_GENERICS.is_match(code) {
1226                // probably struct declaration like
1227                RGX.replace_all(code, "struct __ZngFmt__ {\n$1:$2")
1228            } else {
1229                // probably struct init like
1230                let mut r = RGX.replace_all(code, "static __fmt__: T = __A_ {\n$1:$2").into_owned();
1231
1232                const OPEN: &str = ": T = __A_ {";
1233                const CLOSE_MARKER: &str = "; /*__zng-fmt*/";
1234                let mut start = 0;
1235                while let Some(i) = r[start..].find(OPEN) {
1236                    let i = start + i + OPEN.len();
1237                    let mut count = 1;
1238                    let mut close_i = i;
1239                    for (ci, c) in r[i..].char_indices() {
1240                        match c {
1241                            '{' => count += 1,
1242                            '}' => {
1243                                count -= 1;
1244                                if count == 0 {
1245                                    close_i = i + ci + 1;
1246                                    break;
1247                                }
1248                            }
1249                            _ => {}
1250                        }
1251                    }
1252                    r.insert_str(close_i, CLOSE_MARKER);
1253                    start = close_i + CLOSE_MARKER.len();
1254                }
1255                Cow::Owned(r)
1256            }
1257        } else {
1258            Cow::Borrowed(code)
1259        }
1260    } else {
1261        Cow::Owned(
1262            code.replace("static __fmt__: T = __A_ {", "{")
1263                .replace("}; /*__zng-fmt*/", "}")
1264                .replace("struct __ZngFmt__ {", "{"),
1265        )
1266    }
1267}
1268
1269// replace `pub struct Ident: Ty {` with `pub static __fmt_vis: T = T;\nimpl __fmt_Ident_C_Ty {`
1270// AND replace `const IDENT =` with `const IDENT: __A_ =`
1271fn replace_bitflags(code: &str, reverse: bool) -> Cow<'_, str> {
1272    static RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)struct +(\w+): +(\w+) +\{").unwrap());
1273    static RGX_CONST: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\s*const +(\w+) +=").unwrap());
1274    static RGX_REV: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)static __fmt_vis__: T = T;\s+impl __fmt_(\w+)__C_(\w+) \{").unwrap());
1275    if !reverse {
1276        let mut r = RGX.replace_all(code, "static __fmt_vis__: T = T;\nimpl __fmt_${1}__C_$2 {");
1277        if let Cow::Owned(r) = &mut r
1278            && let Cow::Owned(rr) = RGX_CONST.replace_all(r, "const $1: __A_ =")
1279        {
1280            *r = rr;
1281        }
1282        r
1283    } else {
1284        let code = RGX_REV.replace_all(code, "struct ${1}: $2 {");
1285        Cow::Owned(code.replace(": __A_ =", " ="))
1286    }
1287}
1288
1289/// wrap simple list of idents
1290fn replace_simple_ident_list(code: &str, reverse: bool) -> Cow<'_, str> {
1291    static WORD_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^\w+$").unwrap());
1292    if !reverse {
1293        assert!(code.starts_with('{') && code.ends_with('}'));
1294        let inner = &code[1..code.len() - 1];
1295        if inner.contains(',') {
1296            let mut output = "static __fmt: T = [".to_owned();
1297            for ident in inner.split(',') {
1298                let ident = ident.trim();
1299                if WORD_RGX.is_match(ident) {
1300                    output.push_str(ident);
1301                    output.push_str(", ");
1302                } else if ident.is_empty() {
1303                    continue;
1304                } else {
1305                    return Cow::Borrowed(code);
1306                }
1307            }
1308            output.push_str("];");
1309            Cow::Owned(output)
1310        } else {
1311            let mut output = "static __fmt_s: T = [".to_owned();
1312            let mut any = false;
1313            for ident in inner.split(' ') {
1314                let ident = ident.trim();
1315                if WORD_RGX.is_match(ident) {
1316                    any = true;
1317                    output.push_str(ident);
1318                    output.push_str(", ");
1319                } else if ident.is_empty() {
1320                    continue;
1321                } else {
1322                    return Cow::Borrowed(code);
1323                }
1324            }
1325            if any {
1326                output.push_str("];");
1327                Cow::Owned(output)
1328            } else {
1329                Cow::Borrowed(code)
1330            }
1331        }
1332    } else {
1333        let code = if code.trim_end().contains('\n') {
1334            if code.contains("static __fmt: T = [") {
1335                code.replace("static __fmt: T = [", "{").replace("];", "}")
1336            } else {
1337                assert!(code.contains("static __fmt_s: T = ["));
1338                code.replace("static __fmt_s: T = [", "{").replace("];", "}").replace(',', "")
1339            }
1340        } else if code.contains("static __fmt: T = [") {
1341            code.replace("static __fmt: T = [", "{ ").replace("];", " }")
1342        } else {
1343            assert!(code.contains("static __fmt_s: T = ["));
1344            code.replace("static __fmt_s: T = [", "{ ").replace("];", " }").replace(',', "")
1345        };
1346        Cow::Owned(code)
1347    }
1348}
1349
1350// replace `ident(args);` with `fn __fmt__ident(args);
1351fn replace_widget_impl(code: &str, reverse: bool) -> Cow<'_, str> {
1352    static RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)([:\w]+\((?:\w+: .+)\));").unwrap());
1353    if !reverse {
1354        RGX.replace_all(code, |caps: &regex::Captures| {
1355            // "fn __fmt__${1};" with colon escape
1356            let (a, b) = caps[1].split_once('(').unwrap();
1357            format!("fn __fmt__{}({b};", a.replace("::", "__C__"))
1358        })
1359    } else {
1360        Cow::Owned(code.replace("fn __fmt__", "").replace("__C__", "::"))
1361    }
1362}
1363
1364/// rustfmt does not provide a crate and does not implement a server. It only operates in one-shot
1365/// calls and it is slow.
1366///
1367/// This service is a quick workaround that, it abuses the async state machine generation to inject
1368/// a batching feature in the middle of the recursive custom fmt logic. **It does not implement wakers**,
1369/// just keep polling to execute.
1370#[derive(Clone)]
1371struct FmtFragServer {
1372    data: Arc<Mutex<FmtFragServerData>>,
1373    edition: String,
1374}
1375struct FmtFragServerData {
1376    // [request, response]
1377    requests: HashMap<String, FmtFragRequest>,
1378}
1379struct FmtFragRequest {
1380    pending: bool,
1381    response: Arc<Mutex<String>>,
1382}
1383
1384impl FmtFragRequest {
1385    fn new() -> Self {
1386        Self {
1387            pending: true,
1388            response: Arc::new(Mutex::new(String::new())),
1389        }
1390    }
1391}
1392impl FmtFragServer {
1393    pub fn spawn(edition: String) -> Self {
1394        let s = Self {
1395            data: Arc::new(Mutex::new(FmtFragServerData { requests: HashMap::new() })),
1396            edition,
1397        };
1398        let s_read = s.clone();
1399        std::thread::Builder::new()
1400            .name("rustfmt-frag-server".to_owned())
1401            .spawn(move || {
1402                loop {
1403                    s_read.poll();
1404                }
1405            })
1406            .unwrap();
1407        s
1408    }
1409
1410    #[track_caller]
1411    pub fn format(&self, code: String) -> impl Future<Output = Option<String>> {
1412        let res = self
1413            .data
1414            .lock()
1415            .requests
1416            .entry(code)
1417            .or_insert_with(FmtFragRequest::new)
1418            .response
1419            .clone();
1420        std::future::poll_fn(move |_cx| {
1421            let res = res.lock();
1422            match res.as_str() {
1423                "" => Poll::Pending,
1424                "#rustfmt-error#" => Poll::Ready(None),
1425                _ => Poll::Ready(Some(res.clone())),
1426            }
1427        })
1428    }
1429
1430    fn poll(&self) {
1431        let requests: Vec<_> = self
1432            .data
1433            .lock()
1434            .requests
1435            .iter_mut()
1436            .filter_map(|(k, v)| {
1437                if v.pending {
1438                    v.pending = false;
1439                    Some((k.clone(), v.response.clone()))
1440                } else {
1441                    None
1442                }
1443            })
1444            .collect();
1445        if requests.is_empty() {
1446            std::thread::sleep(Duration::from_millis(100));
1447            return;
1448        }
1449
1450        let edition = self.edition.clone();
1451        blocking::unblock(move || {
1452            // always use batch wrap because it adds tabs and that can cause different wrapping
1453            match rustfmt_stdin(&Self::wrap_batch_for_fmt(requests.iter().map(|(k, _)| k.as_str())), &edition) {
1454                Some(r) => {
1455                    let r = Self::unwrap_batch_for_fmt(r, requests.len());
1456                    for ((_, response), r) in requests.into_iter().zip(r) {
1457                        *response.lock() = r;
1458                    }
1459                }
1460                None => {
1461                    if requests.len() == 1 {
1462                        *requests[0].1.lock() = "#rustfmt-error#".to_owned();
1463                    } else {
1464                        for (request, response) in requests {
1465                            let r = match rustfmt_stdin(&Self::wrap_batch_for_fmt([request].iter().map(|r| r.as_str())), &edition) {
1466                                Some(f) => Self::unwrap_batch_for_fmt(f, 1).remove(0),
1467                                None => "#rustfmt-error#".to_owned(),
1468                            };
1469                            *response.lock() = r;
1470                        }
1471                    }
1472                }
1473            }
1474        })
1475        .detach();
1476    }
1477
1478    const PREFIX: &str = "fn __frag__() ";
1479
1480    fn wrap_batch_for_fmt<'a>(requests: impl Iterator<Item = &'a str>) -> String {
1481        let mut s = String::new();
1482        for code in requests {
1483            s.push_str("mod __batch__ {\n#![__zng_fmt_batch_tabs]\n");
1484            if code.starts_with("{") {
1485                s.push_str(Self::PREFIX);
1486            }
1487            s.push_str(code);
1488            s.push_str("\n}");
1489        }
1490        s
1491    }
1492    fn unwrap_batch_for_fmt(fmt: String, count: usize) -> Vec<String> {
1493        let mut item = String::new();
1494        let mut r = vec![];
1495        let mut lines = fmt.lines();
1496        let mut strip_tabs = String::new();
1497        while let Some(line) = lines.next() {
1498            if line.starts_with("mod __batch__") {
1499                if !item.is_empty() {
1500                    let it = item.trim();
1501                    if let Some(it) = it.strip_prefix(Self::PREFIX) {
1502                        r.push(it.to_owned());
1503                    } else {
1504                        r.push(it.to_owned());
1505                    }
1506                    item.clear();
1507                }
1508
1509                let tabs_line = lines.next().unwrap();
1510                assert!(tabs_line.contains("#![__zng_fmt_batch_tabs]"));
1511                let count = tabs_line.len() - tabs_line.trim_start().len();
1512                strip_tabs.clear();
1513                for _ in 0..count {
1514                    strip_tabs.push(' ');
1515                }
1516            } else if line.is_empty() {
1517                item.push('\n');
1518            } else if let Some(line) = line.strip_prefix(&strip_tabs) {
1519                item.push_str(line);
1520                item.push('\n');
1521            } else if line != "}" {
1522                item.push_str(line);
1523                item.push('\n');
1524            }
1525        }
1526        if !item.is_empty() {
1527            let it = item.trim();
1528            if let Some(it) = it.strip_prefix(Self::PREFIX) {
1529                r.push(it.to_owned());
1530            } else {
1531                r.push(it.to_owned());
1532            }
1533        }
1534        assert_eq!(r.len(), count);
1535        r
1536    }
1537}
1538
1539static SHOW_RUSTFMT_ERRORS: AtomicBool = AtomicBool::new(false);
1540fn rustfmt_stdin(code: &str, edition: &str) -> Option<String> {
1541    let mut rustfmt = std::process::Command::new("rustfmt");
1542    if !SHOW_RUSTFMT_ERRORS.load(Relaxed) {
1543        rustfmt.stderr(Stdio::null());
1544    }
1545    let mut s = rustfmt
1546        .arg("--edition")
1547        .arg(edition)
1548        .stdin(Stdio::piped())
1549        .stdout(Stdio::piped())
1550        .spawn()
1551        .ok()?;
1552    s.stdin.take().unwrap().write_all(code.as_bytes()).ok()?;
1553    let s = s.wait_with_output().ok()?;
1554
1555    if s.status.success() {
1556        let code = String::from_utf8(s.stdout).ok()?;
1557        Some(code)
1558    } else {
1559        None
1560    }
1561}
1562
1563fn rustfmt_files(files: &[PathBuf], edition: &str, check: bool) {
1564    let mut rustfmt = std::process::Command::new("rustfmt");
1565    if !SHOW_RUSTFMT_ERRORS.load(Relaxed) {
1566        rustfmt.stderr(Stdio::null());
1567    }
1568    rustfmt.args(["--config", "skip_children=true"]);
1569    rustfmt.arg("--edition").arg(edition);
1570    if check {
1571        rustfmt.arg("--check");
1572    }
1573    let mut any = false;
1574    for file in files {
1575        if let Some(ext) = file.extension()
1576            && ext == "rs"
1577        {
1578            rustfmt.arg(file);
1579            any = true;
1580        }
1581    }
1582    if !any {
1583        return;
1584    }
1585
1586    match rustfmt.status() {
1587        Ok(s) => {
1588            if !s.success() && SHOW_RUSTFMT_ERRORS.load(Relaxed) {
1589                error!("rustfmt error {s}");
1590            }
1591        }
1592        Err(e) => error!("{e}"),
1593    }
1594}
1595
1596#[derive(Default)]
1597struct FmtHistory {
1598    /// args hash and timestamp
1599    entries: Vec<(String, u128)>,
1600}
1601impl FmtHistory {
1602    /// insert is called before formatting, but we need to actually save the
1603    /// timestamp after formatting, this value marks an inserted entry not saved yet.
1604    const TIMESTAMP_ON_SAVE: u128 = u128::MAX;
1605
1606    const MAX_ENTRIES: usize = 30;
1607
1608    pub fn load() -> io::Result<Self> {
1609        let now = Self::time(SystemTime::now());
1610
1611        match std::fs::File::open(Self::path()?) {
1612            Ok(file) => {
1613                let reader = std::io::BufReader::new(file);
1614                let mut out = Self { entries: vec![] };
1615                for line in reader.lines().take(Self::MAX_ENTRIES) {
1616                    if let Some((key, ts)) = line?.split_once(' ') {
1617                        let t: u128 = ts.parse().map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
1618                        if t > now {
1619                            return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid timestamp"));
1620                        }
1621                        out.entries.push((key.to_owned(), t));
1622                    }
1623                }
1624                Ok(out)
1625            }
1626            Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(Self { entries: vec![] }),
1627            Err(e) => Err(e),
1628        }
1629    }
1630
1631    /// Returns the previous timestamp for the same args or 0.
1632    pub fn insert(&mut self, args: &FmtArgs) -> u128 {
1633        let mut args_key = sha2::Sha256::new();
1634        if let Some(f) = &args.files {
1635            args_key.update(f.as_bytes());
1636        }
1637        if let Some(f) = &args.manifest_path {
1638            args_key.update(f.as_bytes());
1639        }
1640        args_key.update(args.edition.as_bytes());
1641        let rustfmt_version = std::process::Command::new("rustfmt")
1642            .arg("--version")
1643            .output()
1644            .unwrap_or_else(|e| fatal!("{e}"));
1645        if !rustfmt_version.status.success() {
1646            fatal!("rustfmt error {}", rustfmt_version.status);
1647        }
1648        let rustfmt_version = String::from_utf8_lossy(&rustfmt_version.stdout);
1649        args_key.update(rustfmt_version.as_bytes());
1650        let args_key = format!("{FMT_VERSION}:{:x}", args_key.finalize());
1651
1652        for (key, t) in self.entries.iter_mut() {
1653            if key == &args_key {
1654                let prev_t = *t;
1655                assert_ne!(prev_t, Self::TIMESTAMP_ON_SAVE, "inserted called twice");
1656                *t = Self::TIMESTAMP_ON_SAVE;
1657                return if args.full { 0 } else { prev_t };
1658            }
1659        }
1660        self.entries.push((args_key, Self::TIMESTAMP_ON_SAVE));
1661        if self.entries.len() > Self::MAX_ENTRIES {
1662            self.entries.remove(0);
1663        }
1664        0
1665    }
1666
1667    pub fn save(&mut self) -> io::Result<()> {
1668        let now = Self::time(SystemTime::now());
1669        for (_, t) in self.entries.iter_mut() {
1670            if *t == Self::TIMESTAMP_ON_SAVE {
1671                *t = now;
1672            }
1673        }
1674
1675        let mut file = std::fs::File::create(Self::path()?)?;
1676        for (key, t) in self.entries.iter() {
1677            writeln!(&mut file, "{key} {t}")?;
1678        }
1679
1680        Ok(())
1681    }
1682
1683    /// Convert to history time representation.
1684    pub fn time(time: SystemTime) -> u128 {
1685        time.duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_micros()
1686    }
1687
1688    fn path() -> io::Result<PathBuf> {
1689        let root_dir = workspace_root()?;
1690        let target_dir = root_dir.join("target");
1691        let _ = std::fs::create_dir(&target_dir);
1692        Ok(target_dir.join(".cargo-zng-fmt-history"))
1693    }
1694}
1695fn workspace_root() -> io::Result<PathBuf> {
1696    let output = std::process::Command::new("cargo")
1697        .arg("locate-project")
1698        .arg("--workspace")
1699        .arg("--message-format=plain")
1700        .stderr(Stdio::inherit())
1701        .output()?;
1702    if !output.status.success() {
1703        return Err(io::Error::new(io::ErrorKind::NotFound, "workspace root not found"));
1704    }
1705    let root_dir = Path::new(std::str::from_utf8(&output.stdout).unwrap().trim()).parent().unwrap();
1706    Ok(root_dir.to_owned())
1707}
1708
1709/// proc_macro2 types are not send, even when compiled outside of a proc-macro crate
1710/// this mod converts the token tree to a minimal Send model that only retains the info needed
1711/// to implement the custom formatting
1712mod pm2_send {
1713    use std::{ops, str::FromStr};
1714
1715    pub use proc_macro2::Delimiter;
1716
1717    #[derive(Clone, Debug)]
1718    pub struct TokenStream(Vec<TokenTree>);
1719    impl From<proc_macro2::TokenStream> for TokenStream {
1720        fn from(value: proc_macro2::TokenStream) -> Self {
1721            Self(value.into_iter().map(Into::into).collect())
1722        }
1723    }
1724    impl FromStr for TokenStream {
1725        type Err = <proc_macro2::TokenStream as FromStr>::Err;
1726
1727        fn from_str(s: &str) -> Result<Self, Self::Err> {
1728            proc_macro2::TokenStream::from_str(s).map(Into::into)
1729        }
1730    }
1731    impl IntoIterator for TokenStream {
1732        type Item = TokenTree;
1733
1734        type IntoIter = std::vec::IntoIter<Self::Item>;
1735
1736        fn into_iter(self) -> Self::IntoIter {
1737            self.0.into_iter()
1738        }
1739    }
1740
1741    #[derive(Clone, Debug)]
1742    pub enum TokenTree {
1743        Group(Group),
1744        Ident(Ident),
1745        Punct(Punct),
1746        Other(Span),
1747    }
1748    impl From<proc_macro2::TokenTree> for TokenTree {
1749        fn from(value: proc_macro2::TokenTree) -> Self {
1750            match value {
1751                proc_macro2::TokenTree::Group(group) => Self::Group(group.into()),
1752                proc_macro2::TokenTree::Ident(ident) => Self::Ident(ident.into()),
1753                proc_macro2::TokenTree::Punct(punct) => Self::Punct(punct.into()),
1754                proc_macro2::TokenTree::Literal(literal) => Self::Other(literal.span().into()),
1755            }
1756        }
1757    }
1758    impl TokenTree {
1759        pub fn span(&self) -> Span {
1760            match self {
1761                TokenTree::Group(group) => group.span.clone(),
1762                TokenTree::Ident(ident) => ident.span.clone(),
1763                TokenTree::Punct(punct) => punct.span.clone(),
1764                TokenTree::Other(span) => span.clone(),
1765            }
1766        }
1767    }
1768
1769    #[derive(Clone, Debug)]
1770    pub struct Group {
1771        delimiter: Delimiter,
1772        span: Span,
1773        stream: TokenStream,
1774    }
1775    impl From<proc_macro2::Group> for Group {
1776        fn from(value: proc_macro2::Group) -> Self {
1777            Self {
1778                delimiter: value.delimiter(),
1779                span: value.span().into(),
1780                stream: value.stream().into(),
1781            }
1782        }
1783    }
1784    impl Group {
1785        pub fn delimiter(&self) -> Delimiter {
1786            self.delimiter
1787        }
1788
1789        pub fn span(&self) -> Span {
1790            self.span.clone()
1791        }
1792
1793        pub fn stream(&self) -> TokenStream {
1794            self.stream.clone()
1795        }
1796    }
1797
1798    #[derive(Clone, Debug)]
1799    pub struct Ident {
1800        span: Span,
1801        s: String,
1802    }
1803    impl From<proc_macro2::Ident> for Ident {
1804        fn from(value: proc_macro2::Ident) -> Self {
1805            Self {
1806                span: value.span().into(),
1807                s: value.to_string(),
1808            }
1809        }
1810    }
1811    impl<'a> PartialEq<&'a str> for Ident {
1812        fn eq(&self, other: &&'a str) -> bool {
1813            self.s == *other
1814        }
1815    }
1816
1817    #[derive(Clone, Debug)]
1818    pub struct Punct {
1819        span: Span,
1820        c: char,
1821    }
1822    impl From<proc_macro2::Punct> for Punct {
1823        fn from(value: proc_macro2::Punct) -> Self {
1824            Self {
1825                span: value.span().into(),
1826                c: value.as_char(),
1827            }
1828        }
1829    }
1830    impl Punct {
1831        pub fn as_char(&self) -> char {
1832            self.c
1833        }
1834    }
1835
1836    #[derive(Clone, Debug)]
1837    pub struct Span {
1838        byte_range: ops::Range<usize>,
1839    }
1840    impl From<proc_macro2::Span> for Span {
1841        fn from(value: proc_macro2::Span) -> Self {
1842            Self {
1843                byte_range: value.byte_range(),
1844            }
1845        }
1846    }
1847    impl Span {
1848        pub fn byte_range(&self) -> ops::Range<usize> {
1849            self.byte_range.clone()
1850        }
1851    }
1852}