codelens_core/output/
json.rs1use std::io::Write;
4
5use crate::analyzer::stats::AnalysisResult;
6use crate::error::Result;
7
8use super::format::{OutputFormat, OutputOptions, Report};
9
10pub struct JsonOutput {
12 pretty: bool,
13}
14
15impl JsonOutput {
16 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 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 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 let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
162 assert!(parsed.is_object());
163 }
164}