Skip to main content

cargo_macra/
parse_normal.rs

1use serde::Deserialize;
2
3#[derive(Debug, Deserialize)]
4struct CargoMessage {
5    reason: String,
6}
7
8/// Cargo status line prefixes that should be filtered out
9const CARGO_STATUS_PREFIXES: &[&str] = &[
10    "   Compiling ",
11    "    Checking ",
12    "    Finished ",
13    "     Running ",
14    "       Fresh ",
15    "   Documenting ",
16    "     Locking ",
17    "    Updating ",
18    "  Downloading ",
19    "   Downloaded ",
20];
21
22/// Parses the output of `cargo +nightly rustc --message-format=json -- -Z unpretty=normal`
23/// and returns the normalized source code.
24///
25/// The output contains JSON messages from cargo (with a "reason" field) interleaved with
26/// the unpretty source code. This function filters out the JSON messages and cargo status
27/// lines, returning only the source code.
28pub fn parse_normal_output(output: &str) -> String {
29    let mut source_lines = Vec::new();
30
31    for line in output.lines() {
32        // Try to parse as JSON cargo message
33        if let Ok(msg) = serde_json::from_str::<CargoMessage>(line) {
34            // This is a cargo JSON message, skip it
35            let _ = msg.reason;
36            continue;
37        }
38
39        // Skip cargo status lines
40        if CARGO_STATUS_PREFIXES
41            .iter()
42            .any(|prefix| line.starts_with(prefix))
43        {
44            continue;
45        }
46
47        // Not a cargo JSON message or status line, this is source code
48        source_lines.push(line);
49    }
50
51    source_lines.join("\n")
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn test_parse_normal_output() {
60        let output = r#"fn main() { println!("Hello, world!"); }
61{"reason":"compiler-artifact","package_id":"test","manifest_path":"/test/Cargo.toml","target":{"kind":["bin"],"crate_types":["bin"],"name":"test","src_path":"/test/src/main.rs","edition":"2024","doc":true,"doctest":false,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":[],"executable":"/test/target/debug/test","fresh":false}
62{"reason":"build-finished","success":true}"#;
63
64        let result = parse_normal_output(output);
65        assert_eq!(result, r#"fn main() { println!("Hello, world!"); }"#);
66    }
67
68    #[test]
69    fn test_parse_multiline_source() {
70        let output = r#"fn main() {
71    println!("Hello");
72}
73{"reason":"build-finished","success":true}"#;
74
75        let result = parse_normal_output(output);
76        assert_eq!(
77            result,
78            r#"fn main() {
79    println!("Hello");
80}"#
81        );
82    }
83
84    #[test]
85    fn test_parse_with_cargo_status_lines() {
86        let output = r#"{"reason":"compiler-artifact","package_id":"registry+https://github.com/rust-lang/crates.io-index#proc-macro2@1.0.103","manifest_path":"/test/Cargo.toml","target":{"kind":["lib"],"crate_types":["lib"],"name":"proc_macro2","src_path":"/test/src/lib.rs","edition":"2021","doc":true,"doctest":true,"test":true},"profile":{"opt_level":"0","debuginfo":0,"debug_assertions":true,"overflow_checks":true,"test":false},"features":[],"filenames":[],"executable":null,"fresh":true}
87   Compiling myproject v0.1.0 (/home/user/myproject)
88use std::fmt::Debug;
89
90pub struct MyStruct {
91    value: i32,
92}
93
94impl Debug for MyStruct {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        write!(f, "MyStruct({})", self.value)
97    }
98}
99{"reason":"compiler-artifact","package_id":"path+file:///home/user/myproject#0.1.0","manifest_path":"/home/user/myproject/Cargo.toml","target":{"kind":["test"],"crate_types":["bin"],"name":"test","src_path":"/home/user/myproject/tests/test.rs","edition":"2021","doc":false,"doctest":false,"test":true},"profile":{"opt_level":"0","debuginfo":2,"debug_assertions":true,"overflow_checks":true,"test":true},"features":[],"filenames":[],"executable":"/home/user/myproject/target/debug/deps/test-abc123","fresh":false}
100{"reason":"build-finished","success":true}
101    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s"#;
102
103        let result = parse_normal_output(output);
104        assert_eq!(
105            result,
106            r#"use std::fmt::Debug;
107
108pub struct MyStruct {
109    value: i32,
110}
111
112impl Debug for MyStruct {
113    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114        write!(f, "MyStruct({})", self.value)
115    }
116}"#
117        );
118    }
119}