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                                // println!("FMT1:\n{formatted}\nFMT2:\n{stable}");
589                            }
590                        }
591                    }
592                } else if !tail2.is_empty()
593                    && matches!(g.delimiter(), pm2_send::Delimiter::Bracket)
594                    && matches!(&tail2[0], pm2_send::TokenTree::Punct(p) if p.as_char() == '#')
595                {
596                    // #[..]
597                    let mut attr = g.stream().into_iter();
598                    let attr = [attr.next(), attr.next(), attr.next(), attr.next(), attr.next()];
599                    if let [
600                        Some(pm2_send::TokenTree::Ident(i0)),
601                        Some(pm2_send::TokenTree::Punct(p0)),
602                        Some(pm2_send::TokenTree::Punct(p1)),
603                        Some(pm2_send::TokenTree::Ident(i1)),
604                        None,
605                    ] = attr
606                        && i0 == "rustfmt"
607                        && p0.as_char() == ':'
608                        && p1.as_char() == ':'
609                        && i1 == "skip"
610                    {
611                        // #[rustfmt::skip]
612                        skip_next_group = true;
613                    }
614                } else if !std::mem::take(&mut skip_next_group) {
615                    stream_stack.push(g.stream().into_iter());
616                }
617                tail2.clear();
618            }
619            ref tt1 @ pm2_send::TokenTree::Ident(ref i) if i == &"macro_rules" => {
620                if let Some(tt2) = next(&mut stream_stack) {
621                    if matches!(tt2, pm2_send::TokenTree::Punct(ref p) if p.as_char() == '!') {
622                        // macro_rules!
623                        next(&mut stream_stack); // skip ident
624                        next(&mut stream_stack); // skip body
625                    } else {
626                        tail2.clear();
627                        tail2.push(tt2);
628                        tail2.push(tt1.clone());
629                    }
630                } else {
631                    // end
632                }
633            }
634            tt => {
635                if tail2.len() == 2 {
636                    tail2.pop();
637                }
638                tail2.insert(0, tt);
639            }
640        }
641    }
642
643    formatted_code.push_str(&code[last_already_fmt_start..]);
644    formatted_code
645}
646
647async fn try_fmt_macro(base_indent: usize, group_code: &str, fmt: &FmtFragServer) -> Option<String> {
648    // replace supported macro syntax to equivalent valid Rust for rustfmt
649    let mut replaced_code = Cow::Borrowed(group_code);
650
651    let mut is_lazy_static = false;
652    if matches!(&replaced_code, Cow::Borrowed(_)) {
653        replaced_code = replace_static_ref(group_code, false);
654        is_lazy_static = matches!(&replaced_code, Cow::Owned(_));
655    }
656
657    let mut is_bitflags = false;
658    if matches!(&replaced_code, Cow::Borrowed(_)) {
659        replaced_code = replace_bitflags(group_code, false);
660        is_bitflags = matches!(&replaced_code, Cow::Owned(_));
661    }
662
663    let mut is_event_args = false;
664    if matches!(&replaced_code, Cow::Borrowed(_)) {
665        replaced_code = replace_event_args(group_code, false);
666        is_event_args = matches!(&replaced_code, Cow::Owned(_));
667    }
668
669    let mut is_command = false;
670    if matches!(&replaced_code, Cow::Borrowed(_)) {
671        replaced_code = replace_command(group_code, false);
672        is_command = matches!(&replaced_code, Cow::Owned(_));
673    }
674
675    let mut is_event_property = false;
676    if matches!(&replaced_code, Cow::Borrowed(_)) {
677        replaced_code = replace_event_property(group_code, false);
678        is_event_property = matches!(&replaced_code, Cow::Owned(_));
679    }
680
681    let mut is_widget_impl = false;
682    if matches!(&replaced_code, Cow::Borrowed(_)) {
683        replaced_code = replace_widget_impl(group_code, false);
684        is_widget_impl = matches!(&replaced_code, Cow::Owned(_));
685    }
686
687    let mut is_widget = false;
688    if matches!(&replaced_code, Cow::Borrowed(_)) {
689        replaced_code = replace_widget(group_code, false);
690        is_widget = matches!(&replaced_code, Cow::Owned(_));
691    }
692
693    let mut is_expr_var = false;
694    if matches!(&replaced_code, Cow::Borrowed(_)) {
695        replaced_code = replace_expr_var(group_code, false);
696        is_expr_var = matches!(&replaced_code, Cow::Owned(_));
697    }
698
699    let mut is_struct_like = false;
700    if matches!(&replaced_code, Cow::Borrowed(_)) {
701        replaced_code = replace_struct_like(group_code, false);
702        is_struct_like = matches!(&replaced_code, Cow::Owned(_));
703    }
704
705    let mut is_simple_list = false;
706    if matches!(&replaced_code, Cow::Borrowed(_)) {
707        replaced_code = replace_simple_ident_list(group_code, false);
708        is_simple_list = matches!(&replaced_code, Cow::Owned(_));
709    }
710
711    let mut is_when_var = false;
712    if matches!(&replaced_code, Cow::Borrowed(_)) {
713        replaced_code = replace_when_var(group_code, false);
714        is_when_var = matches!(&replaced_code, Cow::Owned(_));
715    }
716
717    let replaced_code = replaced_code.into_owned();
718    // fmt inner macros first, their final format can affect this macros format
719    let code_stream: pm2_send::TokenStream = match replaced_code.parse() {
720        Ok(t) => t,
721        Err(e) => {
722            if SHOW_RUSTFMT_ERRORS.load(Relaxed) {
723                error!("internal error: {e}");
724                eprintln!("CODE:\n{replaced_code}");
725            }
726            return None;
727        }
728    };
729    let mut inner_group = None;
730    for tt in code_stream {
731        if let pm2_send::TokenTree::Group(g) = tt {
732            // find the inner block, some replacements add prefixes or swap the delimiters
733            inner_group = Some(g);
734            break;
735        }
736    }
737    let code_stream = match inner_group {
738        Some(g) => g.stream(),
739        None => {
740            if SHOW_RUSTFMT_ERRORS.load(Relaxed) {
741                error!("internal error, invalid replacement");
742                eprintln!("CODE:\n{replaced_code}");
743            }
744            return None;
745        }
746    };
747    let code = Box::pin(try_fmt_child_macros(&replaced_code, code_stream, fmt)).await;
748
749    // apply rustfmt
750    let code = fmt.format(code).await?;
751
752    // restore supported macro syntax
753    let code = if is_event_args {
754        replace_event_args(&code, true)
755    } else if is_widget {
756        replace_widget(&code, true)
757    } else if is_expr_var {
758        replace_expr_var(&code, true)
759    } else if is_lazy_static {
760        replace_static_ref(&code, true)
761    } else if is_command {
762        replace_command(&code, true)
763    } else if is_event_property {
764        replace_event_property(&code, true)
765    } else if is_struct_like {
766        replace_struct_like(&code, true)
767    } else if is_bitflags {
768        replace_bitflags(&code, true)
769    } else if is_simple_list {
770        replace_simple_ident_list(&code, true)
771    } else if is_widget_impl {
772        replace_widget_impl(&code, true)
773    } else if is_when_var {
774        replace_when_var(&code, true)
775    } else {
776        Cow::Owned(code)
777    };
778
779    // restore indent
780    let mut out = String::new();
781    let mut lb_indent = String::with_capacity(base_indent + 1);
782    for line in code.lines() {
783        if line.is_empty() {
784            if !lb_indent.is_empty() {
785                out.push('\n');
786            }
787        } else {
788            out.push_str(&lb_indent);
789        }
790        out.push_str(line);
791        // "\n    "
792        if lb_indent.is_empty() {
793            lb_indent.push('\n');
794            for _ in 0..base_indent {
795                lb_indent.push(' ');
796            }
797        }
798    }
799    Some(out)
800}
801// replace line with only `..` tokens with:
802//
803// ```
804// // cargo-zng::fmt::dot_dot
805// }
806// impl CargoZngFmt {
807//
808// ```
809fn replace_event_args(code: &str, reverse: bool) -> Cow<'_, str> {
810    static RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\s*(\.\.)\s*$").unwrap());
811    static MARKER: &str = "// cargo-zng::fmt::dot_dot\n}\nimpl CargoZngFmt {\n";
812    static RGX_REV: Lazy<Regex> =
813        Lazy::new(|| Regex::new(r"(?m)^(\s+)// cargo-zng::fmt::dot_dot\n\s*}\n\s*impl CargoZngFmt\s*\{\n").unwrap());
814
815    if !reverse {
816        RGX.replace_all(code, |caps: &regex::Captures| {
817            format!(
818                "{}{MARKER}{}",
819                &caps[0][..caps.get(1).unwrap().start() - caps.get(0).unwrap().start()],
820                &caps[0][caps.get(1).unwrap().end() - caps.get(0).unwrap().start()..]
821            )
822        })
823    } else {
824        RGX_REV.replace_all(code, "\n$1..\n\n")
825    }
826}
827// replace `static IDENT = {` with `static IDENT: __fmt__ = {`
828// AND replace `static IDENT;` with `static IDENT: __fmt__ = T;`
829// AND replace `l10n!: ` with `l10n__fmt:`
830fn replace_command(code: &str, reverse: bool) -> Cow<'_, str> {
831    static RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)([^'])static +(\w+) ?= ?\{").unwrap());
832    static RGX_DEFAULTS: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)([^'])static +(\w+) ?;").unwrap());
833    if !reverse {
834        let cmd = RGX_DEFAULTS.replace_all(code, "${1}static $2: __fmt__ = T;");
835        let mut cmd2 = RGX.replace_all(&cmd, "${1}static $2: __fmt__ = __A_ {");
836        if let Cow::Owned(cmd) = &mut cmd2 {
837            *cmd = cmd.replace("l10n!:", "l10n__fmt:");
838        }
839        match cmd2 {
840            Cow::Borrowed(_) => cmd,
841            Cow::Owned(s) => Cow::Owned(s),
842        }
843    } else {
844        Cow::Owned(
845            code.replace(": __fmt__ = T;", ";")
846                .replace(": __fmt__ = __A_ {", " = {")
847                .replace("l10n__fmt:", "l10n!:"),
848        )
849    }
850}
851// replace ` fn ident = { content }` with ` static __fmt_fn__ident: T = __A_ { content };/*__fmt*/`
852fn replace_event_property(code: &str, reverse: bool) -> Cow<'_, str> {
853    static RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m) fn +(\w+) +\{").unwrap());
854    if !reverse {
855        let mut r = RGX.replace_all(code, " static __fmt_fn__$1: T = __A_ {");
856        if let Cow::Owned(r) = &mut r {
857            const OPEN: &str = ": T = __A_ {";
858            const CLOSE_MARKER: &str = "; /*__fmt*/";
859            let mut start = 0;
860            while let Some(i) = r[start..].find(OPEN) {
861                let i = start + i + OPEN.len();
862                let mut count = 1;
863                let mut close_i = i;
864                for (ci, c) in r[i..].char_indices() {
865                    match c {
866                        '{' => count += 1,
867                        '}' => {
868                            count -= 1;
869                            if count == 0 {
870                                close_i = i + ci + 1;
871                                break;
872                            }
873                        }
874                        _ => {}
875                    }
876                }
877                r.insert_str(close_i, CLOSE_MARKER);
878                start = close_i + CLOSE_MARKER.len();
879            }
880        }
881        r
882    } else {
883        Cow::Owned(
884            code.replace(" static __fmt_fn__", " fn ")
885                .replace(": T = __A_ {", " {")
886                .replace("}; /*__fmt*/", "}"),
887        )
888    }
889}
890/// Escape widget macro syntax
891fn replace_widget(code: &str, reverse: bool) -> Cow<'_, str> {
892    static IGNORE_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m): +\w+\s+=\s+\{").unwrap());
893    static PROPERTY_NAME_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\s*([\w:<>]+)\s+=\s+").unwrap());
894
895    #[derive(Debug)]
896    enum Item<'s> {
897        Property { name: &'s str, value: &'s str },
898        PropertyShorthand(&'s str),
899        When { expr: &'s str, items: Vec<Item<'s>> },
900        Text(&'s str),
901        WidgetSetSelfExpr(&'s str),
902    }
903    #[allow(unused)]
904    struct Error<'s> {
905        partial: Vec<Item<'s>>,
906        error: &'static str,
907    }
908    impl<'s> Error<'s> {
909        fn new(partial: Vec<Item<'s>>, error: &'static str) -> Self {
910            Self { partial, error }
911        }
912    }
913    fn parse<'s>(code: &'s str, stream: proc_macro2::TokenStream, code_span: ops::Range<usize>) -> Result<Vec<Item<'s>>, Error<'s>> {
914        use proc_macro2::{Delimiter, TokenTree as Tt};
915
916        let mut items = vec![];
917        let mut stream = stream.into_iter().peekable();
918        let mut text_start = code_span.start;
919
920        if code_span.start == 1 {
921            if let Some(Tt::Group(g)) = stream.next()
922                && g.delimiter() == Delimiter::Brace
923            {
924                stream = g.stream().into_iter().peekable();
925            } else {
926                return Err(Error::new(items, "expected macro block at root"));
927            }
928
929            if let Some(Tt::Punct(p)) = stream.peek()
930                && p.as_char() == '&'
931            {
932                // widget_set! first line can be "&mut self_ident;"
933                let amp = stream.next().unwrap();
934                if let Some(Tt::Ident(m)) = stream.next()
935                    && m == "mut"
936                {
937                    let start = amp.span().byte_range().start;
938                    for tt in stream.by_ref() {
939                        if let Tt::Punct(p) = tt
940                            && p.as_char() == ';'
941                        {
942                            let end = p.span().byte_range().end;
943                            items.push(Item::Text(&code[text_start..start]));
944                            items.push(Item::WidgetSetSelfExpr(&code[start..end]));
945                            text_start = end;
946                            break;
947                        }
948                    }
949                }
950                if text_start == code_span.start {
951                    return Err(Error::new(items, "expected &mut <self>"));
952                }
953            }
954        }
955        'outer: while let Some(tt_attr_or_name) = stream.next() {
956            // skip attributes
957            if let Tt::Punct(p) = &tt_attr_or_name
958                && p.as_char() == '#'
959            {
960                if let Some(Tt::Group(g)) = stream.next()
961                    && g.delimiter() == proc_macro2::Delimiter::Bracket
962                {
963                    continue 'outer;
964                } else {
965                    return Err(Error::new(items, "expected attribute"));
966                }
967            }
968
969            // match property name or when
970            if let Tt::Ident(ident) = &tt_attr_or_name {
971                if ident == "when" {
972                    items.push(Item::Text(&code[text_start..ident.span().byte_range().start]));
973
974                    // `when` is like an `if <expr> <block>`, the <expr> can be <ident><block> (Foo { }.expr())
975                    // easiest way to deal with this is to seek/peek the next when or property
976                    let expr_start = ident.span().byte_range().end;
977                    if stream.next().is_some()
978                        && let Some(mut tt_block) = stream.next()
979                    {
980                        // needs at least two
981                        loop {
982                            if let Tt::Group(g) = &tt_block
983                                && g.delimiter() == Delimiter::Brace
984                                && stream
985                                    .peek()
986                                    .map(|tt| matches!(tt, Tt::Ident(_)) || matches!(tt, Tt::Punct(p) if p.as_char() == '#'))
987                                    .unwrap_or(true)
988                            {
989                                // peek next is property_name, attribute, when or eof
990                                let block_span = g.span().byte_range();
991                                let expr = &code[expr_start..block_span.start];
992                                items.push(Item::When {
993                                    expr,
994                                    items: parse(code, g.stream(), g.span_open().byte_range().end..g.span_close().byte_range().start)?,
995                                });
996                                text_start = block_span.end;
997                                continue 'outer;
998                            }
999                            // take expr
1000                            if let Some(tt) = stream.next() {
1001                                tt_block = tt;
1002                            } else {
1003                                break;
1004                            }
1005                        }
1006                    } else {
1007                        return Err(Error::new(items, "expected when expression and block"));
1008                    }
1009                } else {
1010                    // take name, can be ident, path::to::ident, or ident::<Ty>
1011                    let name_start = tt_attr_or_name.span().byte_range().start;
1012                    let mut tt_name_end = tt_attr_or_name;
1013                    while let Some(tt) = stream
1014                        .next_if(|tt| matches!(tt, Tt::Ident(_)) || matches!(tt, Tt::Punct(p) if [':', '<', '>'].contains(&p.as_char())))
1015                    {
1016                        tt_name_end = tt;
1017                    }
1018
1019                    items.push(Item::Text(&code[text_start..name_start]));
1020
1021                    let name_end = tt_name_end.span().byte_range().end;
1022                    let name = &code[name_start..name_end];
1023                    if name.is_empty() {
1024                        return Err(Error::new(items, "expected property name"));
1025                    }
1026
1027                    if let Some(tt_punct) = stream.next() {
1028                        if let Tt::Punct(p) = tt_punct {
1029                            if p.as_char() == ';' {
1030                                items.push(Item::PropertyShorthand(name));
1031                                text_start = p.span().byte_range().end;
1032                                continue 'outer;
1033                            } else if p.as_char() == '=' {
1034                                // take value
1035                                let value_start = p.span().byte_range().end;
1036                                if let Some(mut tt_value_end) = stream.next() {
1037                                    while let Some(tt) = stream.next_if(|tt| !matches!(tt, Tt::Punct(p) if p.as_char() == ';')) {
1038                                        tt_value_end = tt;
1039                                    }
1040                                    text_start = tt_value_end.span().byte_range().end;
1041                                    items.push(Item::Property {
1042                                        name,
1043                                        value: &code[value_start..text_start],
1044                                    });
1045                                    if let Some(tt_semi) = stream.next() {
1046                                        debug_assert!(matches!(&tt_semi, Tt::Punct(p) if p.as_char() == ';'));
1047                                        text_start = tt_semi.span().byte_range().end;
1048                                    }
1049                                    continue 'outer;
1050                                } else {
1051                                    return Err(Error::new(items, "expected value"));
1052                                }
1053                            }
1054                        } else {
1055                            return Err(Error::new(items, "expected = or ;"));
1056                        }
1057                    } else {
1058                        // EOF shorthand
1059                        items.push(Item::PropertyShorthand(name));
1060                        text_start = name_end;
1061                        continue 'outer;
1062                    }
1063                }
1064            }
1065
1066            return Err(Error::new(items, "expected attribute or property name"));
1067        }
1068
1069        items.push(Item::Text(&code[text_start..code_span.end]));
1070
1071        Ok(items)
1072    }
1073
1074    if !reverse {
1075        if !PROPERTY_NAME_RGX.is_match(&code[1..code.len() - 1]) || IGNORE_RGX.is_match(code) {
1076            // ignore static IDENT: Ty = expr
1077            return Cow::Borrowed(code);
1078        }
1079        let items = match code.parse() {
1080            Ok(t) => match parse(code, t, 1..code.len() - 1) {
1081                Ok(its) => its,
1082                Err(e) => {
1083                    if SHOW_RUSTFMT_ERRORS.load(Relaxed) {
1084                        // the regex is a best shot to avoid paring
1085                        warn!("cannot parse widget, {}", e.error);
1086                    }
1087                    return Cow::Borrowed(code);
1088                }
1089            },
1090            Err(_) => return Cow::Borrowed(code),
1091        };
1092
1093        fn escape(items: &[Item], r: &mut String) {
1094            for item in items {
1095                match item {
1096                    // path::ident = expr;
1097                    // OR ident = expr0, expr1;
1098                    // OR ident = { field: expr, };
1099                    Item::Property { name, value } => {
1100                        r.push_str(name); // even `path::ident::<Ty>` just works here
1101                        r.push_str(" =");
1102
1103                        static NAMED_FIELDS_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\s*\{\s*\w+:\s+").unwrap());
1104                        if NAMED_FIELDS_RGX.is_match(value) {
1105                            r.push_str(" __ZngFmt");
1106                            r.push_str(value);
1107                            r.push(';');
1108                        } else if value.trim() == "unset!" {
1109                            r.push_str("__unset!();");
1110                        } else {
1111                            r.push_str(" __fmt(");
1112                            r.push_str(value);
1113                            r.push_str("); /*__fmt*/");
1114                        }
1115                    }
1116                    Item::PropertyShorthand(name) => {
1117                        r.push_str(name);
1118                        r.push(';');
1119                    }
1120                    Item::When { expr, items } => {
1121                        r.push_str("/*__fmt_w*/ if ");
1122                        // replace #{}
1123                        let expr = replace_expr_var(expr, false);
1124                        // replace #path
1125                        static PROPERTY_REF_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)#([\w:]+)").unwrap());
1126                        r.push_str(&PROPERTY_REF_RGX.replace_all(&expr, "__P_($1)"));
1127                        r.push_str(" { /*__fmt*/");
1128                        escape(items, r);
1129                        r.push('}');
1130                    }
1131                    Item::Text(txt) => {
1132                        r.push_str(txt);
1133                    }
1134                    Item::WidgetSetSelfExpr(expr) => {
1135                        r.push_str("let __fmt_self = ");
1136                        r.push_str(expr);
1137                        r.push_str("; /*__zng-fmt*/");
1138                    }
1139                }
1140            }
1141        }
1142        let mut escaped = "{".to_owned();
1143        escape(&items, &mut escaped);
1144        escaped.push('}');
1145        Cow::Owned(escaped)
1146    } else {
1147        let code = code
1148            .replace("= __ZngFmt {", "= {")
1149            .replace("); /*__fmt*/", ";")
1150            .replace("__unset!()", "unset!")
1151            .replace("let __fmt_self = ", "")
1152            .replace("; /*__zng-fmt*/", ";");
1153
1154        static WHEN_PREFIX_REV_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"/\*__fmt_w\*/\s+if").unwrap());
1155        static WHEN_SUFFIX_REV_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r" \{\s+/\*__fmt\*/").unwrap());
1156        let code = WHEN_PREFIX_REV_RGX.replace_all(&code, "when");
1157        let code = WHEN_SUFFIX_REV_RGX.replace_all(&code, " {");
1158        let code = match replace_expr_var(&code, true) {
1159            Cow::Borrowed(_) => Cow::Owned(code.into_owned()),
1160            Cow::Owned(o) => Cow::Owned(o),
1161        };
1162
1163        // like `.replace("= __fmt(", "= ")`, but only adds the space after = if did not wrap
1164        // this is important to avoid inserting a trailing space and causing a reformat for every file
1165        static UNNAMED_VALUE_REV_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)=\s+__fmt\(([^\r\n]?)").unwrap());
1166        let replaced = UNNAMED_VALUE_REV_RGX.replace_all(&code, |caps: &regex::Captures| {
1167            let next_char = &caps[1];
1168            if next_char.is_empty() {
1169                "=".to_owned()
1170            } else {
1171                format!("= {next_char}")
1172            }
1173        });
1174        let code = match replaced {
1175            Cow::Borrowed(_) => Cow::Owned(code.into_owned()),
1176            Cow::Owned(o) => Cow::Owned(o),
1177        };
1178
1179        static PROPERTY_REF_REV_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)__P_\(([\w:]+)\)").unwrap());
1180        match PROPERTY_REF_REV_RGX.replace_all(&code, "#$1") {
1181            Cow::Borrowed(_) => Cow::Owned(code.into_owned()),
1182            Cow::Owned(o) => Cow::Owned(o),
1183        }
1184    }
1185}
1186
1187// replace `#{` with `__P_!{`
1188fn replace_expr_var(code: &str, reverse: bool) -> Cow<'_, str> {
1189    static POUND_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(#)[\w\{]").unwrap());
1190    static POUND_REV_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"__P_!\s?").unwrap());
1191    if !reverse {
1192        POUND_RGX.replace_all(code, |caps: &regex::Captures| {
1193            let c = &caps[0][caps.get(1).unwrap().end() - caps.get(0).unwrap().start()..];
1194            if c == "{" {
1195                Cow::Borrowed("__P_!{")
1196            } else {
1197                Cow::Owned(caps[0].to_owned())
1198            }
1199        })
1200    } else {
1201        POUND_REV_RGX.replace_all(code, "#")
1202    }
1203}
1204// replace `{ pattern => expr }` with `static __zng_fmt__: T = match 0 { pattern => expr }; /*__zng-fmt*/`
1205fn replace_when_var(code: &str, reverse: bool) -> Cow<'_, str> {
1206    if !reverse {
1207        let stream: proc_macro2::TokenStream = match code[1..code.len() - 1].parse() {
1208            Ok(s) => s,
1209            Err(_) => return Cow::Borrowed(code),
1210        };
1211        let mut arrow_at_root = false;
1212        let mut stream = stream.into_iter();
1213        while let Some(tt) = stream.next() {
1214            if let proc_macro2::TokenTree::Punct(p) = tt
1215                && p.as_char() == '='
1216                && let Some(proc_macro2::TokenTree::Punct(p2)) = stream.next()
1217                && p2.as_char() == '>'
1218            {
1219                arrow_at_root = true;
1220                break;
1221            }
1222        }
1223        if arrow_at_root {
1224            Cow::Owned(format!("static __zng_fmt__: T = match 0 {code}; /*__zng-fmt*/"))
1225        } else {
1226            Cow::Borrowed(code)
1227        }
1228    } else {
1229        Cow::Owned(
1230            code.replace("static __zng_fmt__: T = match 0 {", "{")
1231                .replace("}; /*__zng-fmt*/", "}"),
1232        )
1233    }
1234}
1235// replace `static ref ` with `static __fmt_ref__`
1236fn replace_static_ref(code: &str, reverse: bool) -> Cow<'_, str> {
1237    if !reverse {
1238        if code.contains("static ref ") {
1239            Cow::Owned(code.replace("static ref ", "static __fmt_ref__"))
1240        } else {
1241            Cow::Borrowed(code)
1242        }
1243    } else {
1244        Cow::Owned(code.replace("static __fmt_ref__", "static ref "))
1245    }
1246}
1247
1248// replace `{ foo: <rest> }` with `static __fmt__: T = __A_ { foo: <rest> } /*__zng-fmt*/`, if the `{` is the first token of  the line
1249// OR with `struct __ZngFmt__ {` if contains generics, signifying declaration
1250fn replace_struct_like(code: &str, reverse: bool) -> Cow<'_, str> {
1251    static RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\s*\{\s+(\w+):([^:])").unwrap());
1252    static RGX_GENERICS: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m):\s*\w+<\w").unwrap());
1253    if !reverse {
1254        if RGX.is_match(code) {
1255            if RGX_GENERICS.is_match(code) {
1256                // probably struct declaration like
1257                RGX.replace_all(code, "struct __ZngFmt__ {\n$1:$2")
1258            } else {
1259                // probably struct init like
1260                let mut r = RGX.replace_all(code, "static __fmt__: T = __A_ {\n$1:$2").into_owned();
1261
1262                const OPEN: &str = ": T = __A_ {";
1263                const CLOSE_MARKER: &str = "; /*__zng-fmt*/";
1264                let mut start = 0;
1265                while let Some(i) = r[start..].find(OPEN) {
1266                    let i = start + i + OPEN.len();
1267                    let mut count = 1;
1268                    let mut close_i = i;
1269                    for (ci, c) in r[i..].char_indices() {
1270                        match c {
1271                            '{' => count += 1,
1272                            '}' => {
1273                                count -= 1;
1274                                if count == 0 {
1275                                    close_i = i + ci + 1;
1276                                    break;
1277                                }
1278                            }
1279                            _ => {}
1280                        }
1281                    }
1282                    r.insert_str(close_i, CLOSE_MARKER);
1283                    start = close_i + CLOSE_MARKER.len();
1284                }
1285                Cow::Owned(r)
1286            }
1287        } else {
1288            Cow::Borrowed(code)
1289        }
1290    } else {
1291        Cow::Owned(
1292            code.replace("static __fmt__: T = __A_ {", "{")
1293                .replace("}; /*__zng-fmt*/", "}")
1294                .replace("struct __ZngFmt__ {", "{"),
1295        )
1296    }
1297}
1298
1299// replace `pub struct Ident: Ty {` with `pub static __fmt_vis: T = T;\nimpl __fmt_Ident_C_Ty {`
1300// AND replace `const IDENT =` with `const IDENT: __A_ =`
1301fn replace_bitflags(code: &str, reverse: bool) -> Cow<'_, str> {
1302    static RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)struct +(\w+): +(\w+) +\{").unwrap());
1303    static RGX_CONST: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)^\s*const +(\w+) +=").unwrap());
1304    static RGX_REV: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)static __fmt_vis__: T = T;\s+impl __fmt_(\w+)__C_(\w+) \{").unwrap());
1305    if !reverse {
1306        let mut r = RGX.replace_all(code, "static __fmt_vis__: T = T;\nimpl __fmt_${1}__C_$2 {");
1307        if let Cow::Owned(r) = &mut r
1308            && let Cow::Owned(rr) = RGX_CONST.replace_all(r, "const $1: __A_ =")
1309        {
1310            *r = rr;
1311        }
1312        r
1313    } else {
1314        let code = RGX_REV.replace_all(code, "struct ${1}: $2 {");
1315        Cow::Owned(code.replace(": __A_ =", " ="))
1316    }
1317}
1318
1319/// wrap simple list of idents
1320fn replace_simple_ident_list(code: &str, reverse: bool) -> Cow<'_, str> {
1321    static WORD_RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^\w+$").unwrap());
1322    if !reverse {
1323        assert!(code.starts_with('{') && code.ends_with('}'));
1324        let inner = &code[1..code.len() - 1];
1325        if inner.contains(',') {
1326            let mut output = "static __fmt: T = [".to_owned();
1327            for ident in inner.split(',') {
1328                let ident = ident.trim();
1329                if WORD_RGX.is_match(ident) {
1330                    output.push_str(ident);
1331                    output.push_str(", ");
1332                } else if ident.is_empty() {
1333                    continue;
1334                } else {
1335                    return Cow::Borrowed(code);
1336                }
1337            }
1338            output.push_str("];");
1339            Cow::Owned(output)
1340        } else {
1341            let mut output = "static __fmt_s: T = [".to_owned();
1342            let mut any = false;
1343            for ident in inner.split(' ') {
1344                let ident = ident.trim();
1345                if WORD_RGX.is_match(ident) {
1346                    any = true;
1347                    output.push_str(ident);
1348                    output.push_str(", ");
1349                } else if ident.is_empty() {
1350                    continue;
1351                } else {
1352                    return Cow::Borrowed(code);
1353                }
1354            }
1355            if any {
1356                output.push_str("];");
1357                Cow::Owned(output)
1358            } else {
1359                Cow::Borrowed(code)
1360            }
1361        }
1362    } else {
1363        let code = if code.trim_end().contains('\n') {
1364            if code.contains("static __fmt: T = [") {
1365                code.replace("static __fmt: T = [", "{").replace("];", "}")
1366            } else {
1367                assert!(code.contains("static __fmt_s: T = ["));
1368                code.replace("static __fmt_s: T = [", "{").replace("];", "}").replace(',', "")
1369            }
1370        } else if code.contains("static __fmt: T = [") {
1371            code.replace("static __fmt: T = [", "{ ").replace("];", " }")
1372        } else {
1373            assert!(code.contains("static __fmt_s: T = ["));
1374            code.replace("static __fmt_s: T = [", "{ ").replace("];", " }").replace(',', "")
1375        };
1376        Cow::Owned(code)
1377    }
1378}
1379
1380// replace `ident(args);` with `fn __fmt__ident(args);
1381fn replace_widget_impl(code: &str, reverse: bool) -> Cow<'_, str> {
1382    static RGX: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?m)([:\w]+\((?:\w+: .+)\));").unwrap());
1383    if !reverse {
1384        RGX.replace_all(code, |caps: &regex::Captures| {
1385            // "fn __fmt__${1};" with colon escape
1386            let (a, b) = caps[1].split_once('(').unwrap();
1387            format!("fn __fmt__{}({b};", a.replace("::", "__C__"))
1388        })
1389    } else {
1390        Cow::Owned(code.replace("fn __fmt__", "").replace("__C__", "::"))
1391    }
1392}
1393
1394/// rustfmt does not provide a crate and does not implement a server. It only operates in one-shot
1395/// calls and it is slow.
1396///
1397/// This service is a quick workaround that, it abuses the async state machine generation to inject
1398/// a batching feature in the middle of the recursive custom fmt logic. **It does not implement wakers**,
1399/// just keep polling to execute.
1400#[derive(Clone)]
1401struct FmtFragServer {
1402    data: Arc<Mutex<FmtFragServerData>>,
1403    edition: String,
1404}
1405struct FmtFragServerData {
1406    // [request, response]
1407    requests: HashMap<String, FmtFragRequest>,
1408}
1409struct FmtFragRequest {
1410    pending: bool,
1411    response: Arc<Mutex<String>>,
1412}
1413
1414impl FmtFragRequest {
1415    fn new() -> Self {
1416        Self {
1417            pending: true,
1418            response: Arc::new(Mutex::new(String::new())),
1419        }
1420    }
1421}
1422impl FmtFragServer {
1423    pub fn spawn(edition: String) -> Self {
1424        let s = Self {
1425            data: Arc::new(Mutex::new(FmtFragServerData { requests: HashMap::new() })),
1426            edition,
1427        };
1428        let s_read = s.clone();
1429        std::thread::Builder::new()
1430            .name("rustfmt-frag-server".to_owned())
1431            .spawn(move || {
1432                loop {
1433                    s_read.poll();
1434                }
1435            })
1436            .unwrap();
1437        s
1438    }
1439
1440    #[track_caller]
1441    pub fn format(&self, code: String) -> impl Future<Output = Option<String>> {
1442        let res = self
1443            .data
1444            .lock()
1445            .requests
1446            .entry(code)
1447            .or_insert_with(FmtFragRequest::new)
1448            .response
1449            .clone();
1450        std::future::poll_fn(move |_cx| {
1451            let res = res.lock();
1452            match res.as_str() {
1453                "" => Poll::Pending,
1454                "#rustfmt-error#" => Poll::Ready(None),
1455                _ => Poll::Ready(Some(res.clone())),
1456            }
1457        })
1458    }
1459
1460    fn poll(&self) {
1461        let requests: Vec<_> = self
1462            .data
1463            .lock()
1464            .requests
1465            .iter_mut()
1466            .filter_map(|(k, v)| {
1467                if v.pending {
1468                    v.pending = false;
1469                    Some((k.clone(), v.response.clone()))
1470                } else {
1471                    None
1472                }
1473            })
1474            .collect();
1475        if requests.is_empty() {
1476            std::thread::sleep(Duration::from_millis(100));
1477            return;
1478        }
1479
1480        let edition = self.edition.clone();
1481        blocking::unblock(move || {
1482            // always use batch wrap because it adds tabs and that can cause different wrapping
1483            match rustfmt_stdin(&Self::wrap_batch_for_fmt(requests.iter().map(|(k, _)| k.as_str())), &edition) {
1484                Some(r) => {
1485                    let r = Self::unwrap_batch_for_fmt(r, requests.len());
1486                    for ((_, response), r) in requests.into_iter().zip(r) {
1487                        *response.lock() = r;
1488                    }
1489                }
1490                None => {
1491                    if requests.len() == 1 {
1492                        *requests[0].1.lock() = "#rustfmt-error#".to_owned();
1493                    } else {
1494                        for (request, response) in requests {
1495                            let r = match rustfmt_stdin(&Self::wrap_batch_for_fmt([request].iter().map(|r| r.as_str())), &edition) {
1496                                Some(f) => Self::unwrap_batch_for_fmt(f, 1).remove(0),
1497                                None => "#rustfmt-error#".to_owned(),
1498                            };
1499                            *response.lock() = r;
1500                        }
1501                    }
1502                }
1503            }
1504        })
1505        .detach();
1506    }
1507
1508    const PREFIX: &str = "fn __frag__() ";
1509
1510    fn wrap_batch_for_fmt<'a>(requests: impl Iterator<Item = &'a str>) -> String {
1511        let mut s = String::new();
1512        for code in requests {
1513            s.push_str("mod __batch__ {\n#![__zng_fmt_batch_tabs]\n");
1514            if code.starts_with("{") {
1515                s.push_str(Self::PREFIX);
1516            }
1517            s.push_str(code);
1518            s.push_str("\n}");
1519        }
1520        s
1521    }
1522    fn unwrap_batch_for_fmt(fmt: String, count: usize) -> Vec<String> {
1523        let mut item = String::new();
1524        let mut r = vec![];
1525        let mut lines = fmt.lines();
1526        let mut strip_tabs = String::new();
1527        while let Some(line) = lines.next() {
1528            if line.starts_with("mod __batch__") {
1529                if !item.is_empty() {
1530                    let it = item.trim();
1531                    if let Some(it) = it.strip_prefix(Self::PREFIX) {
1532                        r.push(it.to_owned());
1533                    } else {
1534                        r.push(it.to_owned());
1535                    }
1536                    item.clear();
1537                }
1538
1539                let tabs_line = lines.next().unwrap();
1540                assert!(tabs_line.contains("#![__zng_fmt_batch_tabs]"));
1541                let count = tabs_line.len() - tabs_line.trim_start().len();
1542                strip_tabs.clear();
1543                for _ in 0..count {
1544                    strip_tabs.push(' ');
1545                }
1546            } else if line.is_empty() {
1547                item.push('\n');
1548            } else if let Some(line) = line.strip_prefix(&strip_tabs) {
1549                item.push_str(line);
1550                item.push('\n');
1551            } else if line != "}" {
1552                item.push_str(line);
1553                item.push('\n');
1554            }
1555        }
1556        if !item.is_empty() {
1557            let it = item.trim();
1558            if let Some(it) = it.strip_prefix(Self::PREFIX) {
1559                r.push(it.to_owned());
1560            } else {
1561                r.push(it.to_owned());
1562            }
1563        }
1564        assert_eq!(r.len(), count);
1565        r
1566    }
1567}
1568
1569static SHOW_RUSTFMT_ERRORS: AtomicBool = AtomicBool::new(false);
1570fn rustfmt_stdin(code: &str, edition: &str) -> Option<String> {
1571    let mut rustfmt = std::process::Command::new("rustfmt");
1572    if !SHOW_RUSTFMT_ERRORS.load(Relaxed) {
1573        rustfmt.stderr(Stdio::null());
1574    }
1575    let mut s = rustfmt
1576        .arg("--edition")
1577        .arg(edition)
1578        .stdin(Stdio::piped())
1579        .stdout(Stdio::piped())
1580        .spawn()
1581        .ok()?;
1582    s.stdin.take().unwrap().write_all(code.as_bytes()).ok()?;
1583    let s = s.wait_with_output().ok()?;
1584
1585    if s.status.success() {
1586        let code = String::from_utf8(s.stdout).ok()?;
1587        Some(code)
1588    } else {
1589        None
1590    }
1591}
1592
1593fn rustfmt_files(files: &[PathBuf], edition: &str, check: bool) {
1594    let mut rustfmt = std::process::Command::new("rustfmt");
1595    if !SHOW_RUSTFMT_ERRORS.load(Relaxed) {
1596        rustfmt.stderr(Stdio::null());
1597    }
1598    rustfmt.args(["--config", "skip_children=true"]);
1599    rustfmt.arg("--edition").arg(edition);
1600    if check {
1601        rustfmt.arg("--check");
1602    }
1603    let mut any = false;
1604    for file in files {
1605        if let Some(ext) = file.extension()
1606            && ext == "rs"
1607        {
1608            rustfmt.arg(file);
1609            any = true;
1610        }
1611    }
1612    if !any {
1613        return;
1614    }
1615
1616    match rustfmt.status() {
1617        Ok(s) => {
1618            if !s.success() && SHOW_RUSTFMT_ERRORS.load(Relaxed) {
1619                error!("rustfmt error {s}");
1620            }
1621        }
1622        Err(e) => error!("{e}"),
1623    }
1624}
1625
1626#[derive(Default)]
1627struct FmtHistory {
1628    /// args hash and timestamp
1629    entries: Vec<(String, u128)>,
1630}
1631impl FmtHistory {
1632    /// insert is called before formatting, but we need to actually save the
1633    /// timestamp after formatting, this value marks an inserted entry not saved yet.
1634    const TIMESTAMP_ON_SAVE: u128 = u128::MAX;
1635
1636    const MAX_ENTRIES: usize = 30;
1637
1638    pub fn load() -> io::Result<Self> {
1639        let now = Self::time(SystemTime::now());
1640
1641        match std::fs::File::open(Self::path()?) {
1642            Ok(file) => {
1643                let reader = std::io::BufReader::new(file);
1644                let mut out = Self { entries: vec![] };
1645                for line in reader.lines().take(Self::MAX_ENTRIES) {
1646                    if let Some((key, ts)) = line?.split_once(' ') {
1647                        let t: u128 = ts.parse().map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?;
1648                        if t > now {
1649                            return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid timestamp"));
1650                        }
1651                        out.entries.push((key.to_owned(), t));
1652                    }
1653                }
1654                Ok(out)
1655            }
1656            Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(Self { entries: vec![] }),
1657            Err(e) => Err(e),
1658        }
1659    }
1660
1661    /// Returns the previous timestamp for the same args or 0.
1662    pub fn insert(&mut self, args: &FmtArgs) -> u128 {
1663        let mut args_key = sha2::Sha256::new();
1664        if let Some(f) = &args.files {
1665            args_key.update(f.as_bytes());
1666        }
1667        if let Some(f) = &args.manifest_path {
1668            args_key.update(f.as_bytes());
1669        }
1670        args_key.update(args.edition.as_bytes());
1671        let rustfmt_version = std::process::Command::new("rustfmt")
1672            .arg("--version")
1673            .output()
1674            .unwrap_or_else(|e| fatal!("{e}"));
1675        if !rustfmt_version.status.success() {
1676            fatal!("rustfmt error {}", rustfmt_version.status);
1677        }
1678        let rustfmt_version = String::from_utf8_lossy(&rustfmt_version.stdout);
1679        args_key.update(rustfmt_version.as_bytes());
1680        let args_key = format!("{FMT_VERSION}:{:x}", args_key.finalize());
1681
1682        for (key, t) in self.entries.iter_mut() {
1683            if key == &args_key {
1684                let prev_t = *t;
1685                assert_ne!(prev_t, Self::TIMESTAMP_ON_SAVE, "inserted called twice");
1686                *t = Self::TIMESTAMP_ON_SAVE;
1687                return if args.full { 0 } else { prev_t };
1688            }
1689        }
1690        self.entries.push((args_key, Self::TIMESTAMP_ON_SAVE));
1691        if self.entries.len() > Self::MAX_ENTRIES {
1692            self.entries.remove(0);
1693        }
1694        0
1695    }
1696
1697    pub fn save(&mut self) -> io::Result<()> {
1698        let now = Self::time(SystemTime::now());
1699        for (_, t) in self.entries.iter_mut() {
1700            if *t == Self::TIMESTAMP_ON_SAVE {
1701                *t = now;
1702            }
1703        }
1704
1705        let mut file = std::fs::File::create(Self::path()?)?;
1706        for (key, t) in self.entries.iter() {
1707            writeln!(&mut file, "{key} {t}")?;
1708        }
1709
1710        Ok(())
1711    }
1712
1713    /// Convert to history time representation.
1714    pub fn time(time: SystemTime) -> u128 {
1715        time.duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_micros()
1716    }
1717
1718    fn path() -> io::Result<PathBuf> {
1719        let root_dir = workspace_root()?;
1720        let target_dir = root_dir.join("target");
1721        let _ = std::fs::create_dir(&target_dir);
1722        Ok(target_dir.join(".cargo-zng-fmt-history"))
1723    }
1724}
1725fn workspace_root() -> io::Result<PathBuf> {
1726    let output = std::process::Command::new("cargo")
1727        .arg("locate-project")
1728        .arg("--workspace")
1729        .arg("--message-format=plain")
1730        .stderr(Stdio::inherit())
1731        .output()?;
1732    if !output.status.success() {
1733        return Err(io::Error::new(io::ErrorKind::NotFound, "workspace root not found"));
1734    }
1735    let root_dir = Path::new(std::str::from_utf8(&output.stdout).unwrap().trim()).parent().unwrap();
1736    Ok(root_dir.to_owned())
1737}
1738
1739/// proc_macro2 types are not send, even when compiled outside of a proc-macro crate
1740/// this mod converts the token tree to a minimal Send model that only retains the info needed
1741/// to implement the custom formatting
1742mod pm2_send {
1743    use std::{ops, str::FromStr};
1744
1745    pub use proc_macro2::Delimiter;
1746
1747    #[derive(Clone, Debug)]
1748    pub struct TokenStream(Vec<TokenTree>);
1749    impl From<proc_macro2::TokenStream> for TokenStream {
1750        fn from(value: proc_macro2::TokenStream) -> Self {
1751            Self(value.into_iter().map(Into::into).collect())
1752        }
1753    }
1754    impl FromStr for TokenStream {
1755        type Err = <proc_macro2::TokenStream as FromStr>::Err;
1756
1757        fn from_str(s: &str) -> Result<Self, Self::Err> {
1758            proc_macro2::TokenStream::from_str(s).map(Into::into)
1759        }
1760    }
1761    impl IntoIterator for TokenStream {
1762        type Item = TokenTree;
1763
1764        type IntoIter = std::vec::IntoIter<Self::Item>;
1765
1766        fn into_iter(self) -> Self::IntoIter {
1767            self.0.into_iter()
1768        }
1769    }
1770
1771    #[derive(Clone, Debug)]
1772    pub enum TokenTree {
1773        Group(Group),
1774        Ident(Ident),
1775        Punct(Punct),
1776        Other(Span),
1777    }
1778    impl From<proc_macro2::TokenTree> for TokenTree {
1779        fn from(value: proc_macro2::TokenTree) -> Self {
1780            match value {
1781                proc_macro2::TokenTree::Group(group) => Self::Group(group.into()),
1782                proc_macro2::TokenTree::Ident(ident) => Self::Ident(ident.into()),
1783                proc_macro2::TokenTree::Punct(punct) => Self::Punct(punct.into()),
1784                proc_macro2::TokenTree::Literal(literal) => Self::Other(literal.span().into()),
1785            }
1786        }
1787    }
1788    impl TokenTree {
1789        pub fn span(&self) -> Span {
1790            match self {
1791                TokenTree::Group(group) => group.span.clone(),
1792                TokenTree::Ident(ident) => ident.span.clone(),
1793                TokenTree::Punct(punct) => punct.span.clone(),
1794                TokenTree::Other(span) => span.clone(),
1795            }
1796        }
1797    }
1798
1799    #[derive(Clone, Debug)]
1800    pub struct Group {
1801        delimiter: Delimiter,
1802        span: Span,
1803        stream: TokenStream,
1804    }
1805    impl From<proc_macro2::Group> for Group {
1806        fn from(value: proc_macro2::Group) -> Self {
1807            Self {
1808                delimiter: value.delimiter(),
1809                span: value.span().into(),
1810                stream: value.stream().into(),
1811            }
1812        }
1813    }
1814    impl Group {
1815        pub fn delimiter(&self) -> Delimiter {
1816            self.delimiter
1817        }
1818
1819        pub fn span(&self) -> Span {
1820            self.span.clone()
1821        }
1822
1823        pub fn stream(&self) -> TokenStream {
1824            self.stream.clone()
1825        }
1826    }
1827
1828    #[derive(Clone, Debug)]
1829    pub struct Ident {
1830        span: Span,
1831        s: String,
1832    }
1833    impl From<proc_macro2::Ident> for Ident {
1834        fn from(value: proc_macro2::Ident) -> Self {
1835            Self {
1836                span: value.span().into(),
1837                s: value.to_string(),
1838            }
1839        }
1840    }
1841    impl<'a> PartialEq<&'a str> for Ident {
1842        fn eq(&self, other: &&'a str) -> bool {
1843            self.s == *other
1844        }
1845    }
1846
1847    #[derive(Clone, Debug)]
1848    pub struct Punct {
1849        span: Span,
1850        c: char,
1851    }
1852    impl From<proc_macro2::Punct> for Punct {
1853        fn from(value: proc_macro2::Punct) -> Self {
1854            Self {
1855                span: value.span().into(),
1856                c: value.as_char(),
1857            }
1858        }
1859    }
1860    impl Punct {
1861        pub fn as_char(&self) -> char {
1862            self.c
1863        }
1864    }
1865
1866    #[derive(Clone, Debug)]
1867    pub struct Span {
1868        byte_range: ops::Range<usize>,
1869    }
1870    impl From<proc_macro2::Span> for Span {
1871        fn from(value: proc_macro2::Span) -> Self {
1872            Self {
1873                byte_range: value.byte_range(),
1874            }
1875        }
1876    }
1877    impl Span {
1878        pub fn byte_range(&self) -> ops::Range<usize> {
1879            self.byte_range.clone()
1880        }
1881    }
1882}