Skip to main content

codelens_core/output/
json.rs

1//! JSON output format.
2
3use std::io::Write;
4
5use crate::analyzer::stats::AnalysisResult;
6use crate::error::Result;
7
8use super::format::{OutputFormat, OutputOptions, Report};
9
10/// JSON output formatter.
11pub struct JsonOutput {
12    pretty: bool,
13}
14
15impl JsonOutput {
16    /// Create a new JSON output formatter.
17    pub fn new(pretty: bool) -> Self {
18        Self { pretty }
19    }
20}
21
22impl OutputFormat for JsonOutput {
23    fn name(&self) -> &'static str {
24        "json"
25    }
26
27    fn extension(&self) -> &'static str {
28        "json"
29    }
30
31    fn write(
32        &self,
33        report: &Report,
34        options: &OutputOptions,
35        writer: &mut dyn Write,
36    ) -> Result<()> {
37        match report {
38            Report::Analysis(result) => self.write_analysis(result, options, writer),
39            Report::Health(report) => self.write_json(report, writer),
40            Report::Hotspot(report) => self.write_json(report, writer),
41            Report::Trend(report) => self.write_json(report, writer),
42            Report::Estimation(report) => self.write_json(report, writer),
43            Report::EstimationComparison(report) => self.write_json(report, writer),
44        }
45    }
46}
47
48impl JsonOutput {
49    fn write_analysis(
50        &self,
51        result: &AnalysisResult,
52        _options: &OutputOptions,
53        writer: &mut dyn Write,
54    ) -> Result<()> {
55        self.write_json(result, writer)
56    }
57
58    fn write_json<T: serde::Serialize>(&self, data: &T, writer: &mut dyn Write) -> Result<()> {
59        if self.pretty {
60            serde_json::to_writer_pretty(&mut *writer, data)?;
61        } else {
62            serde_json::to_writer(&mut *writer, data)?;
63        }
64        writeln!(writer)?;
65        Ok(())
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::Report;
72    use super::*;
73    use crate::analyzer::stats::{FileStats, LineStats, Summary};
74    use std::path::PathBuf;
75    use std::time::Duration;
76
77    fn make_test_result() -> AnalysisResult {
78        AnalysisResult {
79            files: vec![FileStats {
80                path: PathBuf::from("test.rs"),
81                language: "Rust".to_string(),
82                lines: LineStats {
83                    total: 100,
84                    code: 80,
85                    comment: 10,
86                    blank: 10,
87                },
88                size: 2000,
89                complexity: Default::default(),
90            }],
91            summary: Summary::from_file_stats(&[FileStats {
92                path: PathBuf::from("test.rs"),
93                language: "Rust".to_string(),
94                lines: LineStats {
95                    total: 100,
96                    code: 80,
97                    comment: 10,
98                    blank: 10,
99                },
100                size: 2000,
101                complexity: Default::default(),
102            }]),
103            elapsed: Duration::from_millis(100),
104            scanned_files: 1,
105            skipped_files: 0,
106        }
107    }
108
109    #[test]
110    fn test_json_output_name() {
111        let output = JsonOutput::new(false);
112        assert_eq!(output.name(), "json");
113        assert_eq!(output.extension(), "json");
114    }
115
116    #[test]
117    fn test_json_output_compact() {
118        let output = JsonOutput::new(false);
119        let result = make_test_result();
120        let options = OutputOptions::default();
121
122        let mut buffer = Vec::new();
123        output
124            .write(&Report::Analysis(result), &options, &mut buffer)
125            .unwrap();
126
127        let json_str = String::from_utf8(buffer).unwrap();
128        assert!(json_str.contains("\"scanned_files\":1"));
129        assert!(json_str.contains("\"language\":\"Rust\""));
130        // Compact JSON should not have indentation
131        assert!(!json_str.contains("  \""));
132    }
133
134    #[test]
135    fn test_json_output_pretty() {
136        let output = JsonOutput::new(true);
137        let result = make_test_result();
138        let options = OutputOptions::default();
139
140        let mut buffer = Vec::new();
141        output
142            .write(&Report::Analysis(result), &options, &mut buffer)
143            .unwrap();
144
145        let json_str = String::from_utf8(buffer).unwrap();
146        // Pretty JSON should have indentation
147        assert!(json_str.contains("  "));
148    }
149
150    #[test]
151    fn test_json_output_valid_json() {
152        let output = JsonOutput::new(false);
153        let result = make_test_result();
154        let options = OutputOptions::default();
155
156        let mut buffer = Vec::new();
157        output
158            .write(&Report::Analysis(result), &options, &mut buffer)
159            .unwrap();
160
161        let json_str = String::from_utf8(buffer).unwrap();
162        // Should be valid JSON
163        let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
164        assert!(parsed.is_object());
165    }
166}