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