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        }
43    }
44}
45
46impl JsonOutput {
47    fn write_analysis(
48        &self,
49        result: &AnalysisResult,
50        _options: &OutputOptions,
51        writer: &mut dyn Write,
52    ) -> Result<()> {
53        self.write_json(result, writer)
54    }
55
56    fn write_json<T: serde::Serialize>(&self, data: &T, writer: &mut dyn Write) -> Result<()> {
57        if self.pretty {
58            serde_json::to_writer_pretty(&mut *writer, data)?;
59        } else {
60            serde_json::to_writer(&mut *writer, data)?;
61        }
62        writeln!(writer)?;
63        Ok(())
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::Report;
70    use super::*;
71    use crate::analyzer::stats::{FileStats, LineStats, Summary};
72    use std::path::PathBuf;
73    use std::time::Duration;
74
75    fn make_test_result() -> AnalysisResult {
76        AnalysisResult {
77            files: vec![FileStats {
78                path: PathBuf::from("test.rs"),
79                language: "Rust".to_string(),
80                lines: LineStats {
81                    total: 100,
82                    code: 80,
83                    comment: 10,
84                    blank: 10,
85                },
86                size: 2000,
87                complexity: Default::default(),
88            }],
89            summary: Summary::from_file_stats(&[FileStats {
90                path: PathBuf::from("test.rs"),
91                language: "Rust".to_string(),
92                lines: LineStats {
93                    total: 100,
94                    code: 80,
95                    comment: 10,
96                    blank: 10,
97                },
98                size: 2000,
99                complexity: Default::default(),
100            }]),
101            elapsed: Duration::from_millis(100),
102            scanned_files: 1,
103            skipped_files: 0,
104        }
105    }
106
107    #[test]
108    fn test_json_output_name() {
109        let output = JsonOutput::new(false);
110        assert_eq!(output.name(), "json");
111        assert_eq!(output.extension(), "json");
112    }
113
114    #[test]
115    fn test_json_output_compact() {
116        let output = JsonOutput::new(false);
117        let result = make_test_result();
118        let options = OutputOptions::default();
119
120        let mut buffer = Vec::new();
121        output
122            .write(&Report::Analysis(result), &options, &mut buffer)
123            .unwrap();
124
125        let json_str = String::from_utf8(buffer).unwrap();
126        assert!(json_str.contains("\"scanned_files\":1"));
127        assert!(json_str.contains("\"language\":\"Rust\""));
128        // Compact JSON should not have indentation
129        assert!(!json_str.contains("  \""));
130    }
131
132    #[test]
133    fn test_json_output_pretty() {
134        let output = JsonOutput::new(true);
135        let result = make_test_result();
136        let options = OutputOptions::default();
137
138        let mut buffer = Vec::new();
139        output
140            .write(&Report::Analysis(result), &options, &mut buffer)
141            .unwrap();
142
143        let json_str = String::from_utf8(buffer).unwrap();
144        // Pretty JSON should have indentation
145        assert!(json_str.contains("  "));
146    }
147
148    #[test]
149    fn test_json_output_valid_json() {
150        let output = JsonOutput::new(false);
151        let result = make_test_result();
152        let options = OutputOptions::default();
153
154        let mut buffer = Vec::new();
155        output
156            .write(&Report::Analysis(result), &options, &mut buffer)
157            .unwrap();
158
159        let json_str = String::from_utf8(buffer).unwrap();
160        // Should be valid JSON
161        let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
162        assert!(parsed.is_object());
163    }
164}