macra 0.1.3

Core library for macro-related analysis and serialization used by cargo-macra.
Documentation
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct CargoMessage {
    reason: String,
}

/// Cargo status line prefixes that should be filtered out
const CARGO_STATUS_PREFIXES: &[&str] = &[
    "   Compiling ",
    "    Checking ",
    "    Finished ",
    "     Running ",
    "       Fresh ",
    "   Documenting ",
    "     Locking ",
    "    Updating ",
    "  Downloading ",
    "   Downloaded ",
];

/// Parses the output of `cargo +nightly rustc --message-format=json -- -Z unpretty=normal`
/// and returns the normalized source code.
///
/// The output contains JSON messages from cargo (with a "reason" field) interleaved with
/// the unpretty source code. This function filters out the JSON messages and cargo status
/// lines, returning only the source code.
pub fn parse_normal_output(output: &str) -> String {
    let mut source_lines = Vec::new();

    for line in output.lines() {
        // Try to parse as JSON cargo message
        if let Ok(msg) = serde_json::from_str::<CargoMessage>(line) {
            // This is a cargo JSON message, skip it
            let _ = msg.reason;
            continue;
        }

        // Skip cargo status lines
        if CARGO_STATUS_PREFIXES.iter().any(|prefix| line.starts_with(prefix)) {
            continue;
        }

        // Not a cargo JSON message or status line, this is source code
        source_lines.push(line);
    }

    source_lines.join("\n")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_normal_output() {
        let output = r#"fn main() { println!("Hello, world!"); }
{"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}
{"reason":"build-finished","success":true}"#;

        let result = parse_normal_output(output);
        assert_eq!(result, r#"fn main() { println!("Hello, world!"); }"#);
    }

    #[test]
    fn test_parse_multiline_source() {
        let output = r#"fn main() {
    println!("Hello");
}
{"reason":"build-finished","success":true}"#;

        let result = parse_normal_output(output);
        assert_eq!(
            result,
            r#"fn main() {
    println!("Hello");
}"#
        );
    }

    #[test]
    fn test_parse_with_cargo_status_lines() {
        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}
   Compiling myproject v0.1.0 (/home/user/myproject)
use std::fmt::Debug;

pub struct MyStruct {
    value: i32,
}

impl Debug for MyStruct {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "MyStruct({})", self.value)
    }
}
{"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}
{"reason":"build-finished","success":true}
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s"#;

        let result = parse_normal_output(output);
        assert_eq!(
            result,
            r#"use std::fmt::Debug;

pub struct MyStruct {
    value: i32,
}

impl Debug for MyStruct {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "MyStruct({})", self.value)
    }
}"#
        );
    }
}