1use std::fmt::Write as _;
2
3use crate::model::{
4 ClassSummary, DecoratorSummary, Diagnostic, RenderMethodSummary, Severity, SourceSummary, Span,
5};
6
7#[must_use]
8pub fn explain_text(summary: &SourceSummary) -> String {
9 let mut output = String::new();
10
11 let _ = writeln!(output, "File: {}", summary.path.display());
12 let _ = writeln!(output, "Bytes: {}", summary.byte_len);
13 let _ = writeln!(output, "Lines: {}", summary.line_count);
14 let _ = writeln!(output, "Characters: {}", summary.char_count);
15 let _ = writeln!(
16 output,
17 "TSX-like syntax: {}",
18 yes_no(summary.has_tsx_like_syntax)
19 );
20
21 let _ = writeln!(output, "\nComponents:");
22 if summary.component_decorators.is_empty() {
23 let _ = writeln!(output, " none");
24 } else {
25 for item in &summary.component_decorators {
26 let argument = item.argument.as_deref().unwrap_or("<missing>");
27 let _ = writeln!(
28 output,
29 " @{}({argument:?}) at {}:{}",
30 item.name, item.span.line, item.span.column
31 );
32 }
33 }
34
35 let _ = writeln!(output, "\nRoutes:");
36 if summary.route_decorators.is_empty() {
37 let _ = writeln!(output, " none");
38 } else {
39 for item in &summary.route_decorators {
40 let argument = item.argument.as_deref().unwrap_or("<missing>");
41 let _ = writeln!(
42 output,
43 " @{}({argument:?}) at {}:{}",
44 item.name, item.span.line, item.span.column
45 );
46 }
47 }
48
49 let _ = writeln!(output, "\nClasses:");
50 if summary.class_declarations.is_empty() {
51 let _ = writeln!(output, " none");
52 } else {
53 for class in &summary.class_declarations {
54 let _ = writeln!(
55 output,
56 " class {} at {}:{}",
57 class.name, class.span.line, class.span.column
58 );
59 }
60 }
61
62 let _ = writeln!(output, "\nRender methods:");
63 if summary.render_methods.is_empty() {
64 let _ = writeln!(output, " none");
65 } else {
66 for method in &summary.render_methods {
67 let _ = writeln!(
68 output,
69 " render() at {}:{}",
70 method.span.line, method.span.column
71 );
72 }
73 }
74
75 let _ = writeln!(output, "\nDiagnostics:");
76 if summary.diagnostics.is_empty() {
77 let _ = writeln!(output, " none");
78 } else {
79 for diagnostic in &summary.diagnostics {
80 let _ = writeln!(
81 output,
82 " {:?} {}: {}",
83 diagnostic.severity, diagnostic.code, diagnostic.message
84 );
85 }
86 }
87
88 output
89}
90
91#[must_use]
92pub fn explain_json(summary: &SourceSummary) -> String {
93 let mut output = String::new();
96 let _ = writeln!(output, "{{");
97 let _ = writeln!(
98 output,
99 " \"path\": {},",
100 json_string(&summary.path.display().to_string())
101 );
102 let _ = writeln!(output, " \"byteLen\": {},", summary.byte_len);
103 let _ = writeln!(output, " \"lineCount\": {},", summary.line_count);
104 let _ = writeln!(output, " \"charCount\": {},", summary.char_count);
105 let _ = writeln!(
106 output,
107 " \"hasTsxLikeSyntax\": {},",
108 summary.has_tsx_like_syntax
109 );
110 let _ = writeln!(
111 output,
112 " \"componentDecorators\": [{}],",
113 decorators_json(&summary.component_decorators)
114 );
115 let _ = writeln!(
116 output,
117 " \"routeDecorators\": [{}],",
118 decorators_json(&summary.route_decorators)
119 );
120 let _ = writeln!(
121 output,
122 " \"classDeclarations\": [{}],",
123 classes_json(&summary.class_declarations)
124 );
125 let _ = writeln!(
126 output,
127 " \"renderMethods\": [{}],",
128 render_methods_json(&summary.render_methods)
129 );
130 let _ = writeln!(
131 output,
132 " \"diagnostics\": [{}]",
133 diagnostics_json(&summary.diagnostics)
134 );
135 let _ = writeln!(output, "}}");
136 output
137}
138
139fn yes_no(value: bool) -> &'static str {
140 if value {
141 "yes"
142 } else {
143 "no"
144 }
145}
146
147fn decorators_json(items: &[DecoratorSummary]) -> String {
148 items
149 .iter()
150 .map(|item| {
151 format!(
152 "{{\"name\":{},\"argument\":{},\"span\":{}}}",
153 json_string(&item.name),
154 item.argument
155 .as_ref()
156 .map_or("null".to_string(), |value| json_string(value)),
157 span_json(item.span)
158 )
159 })
160 .collect::<Vec<_>>()
161 .join(",")
162}
163
164fn classes_json(items: &[ClassSummary]) -> String {
165 items
166 .iter()
167 .map(|item| {
168 format!(
169 "{{\"name\":{},\"span\":{}}}",
170 json_string(&item.name),
171 span_json(item.span)
172 )
173 })
174 .collect::<Vec<_>>()
175 .join(",")
176}
177
178fn render_methods_json(items: &[RenderMethodSummary]) -> String {
179 items
180 .iter()
181 .map(|item| format!("{{\"span\":{}}}", span_json(item.span)))
182 .collect::<Vec<_>>()
183 .join(",")
184}
185
186fn diagnostics_json(items: &[Diagnostic]) -> String {
187 items
188 .iter()
189 .map(|item| {
190 format!(
191 "{{\"severity\":{},\"code\":{},\"message\":{},\"span\":{}}}",
192 json_string(match item.severity {
193 Severity::Info => "info",
194 Severity::Warning => "warning",
195 Severity::Error => "error",
196 }),
197 json_string(&item.code),
198 json_string(&item.message),
199 item.span.map_or("null".to_string(), span_json)
200 )
201 })
202 .collect::<Vec<_>>()
203 .join(",")
204}
205
206fn span_json(span: Span) -> String {
207 format!(
208 "{{\"start\":{},\"end\":{},\"line\":{},\"column\":{}}}",
209 span.start, span.end, span.line, span.column
210 )
211}
212
213fn json_string(value: &str) -> String {
214 let mut output = String::from("\"");
215 for ch in value.chars() {
216 match ch {
217 '"' => output.push_str("\\\""),
218 '\\' => output.push_str("\\\\"),
219 '\n' => output.push_str("\\n"),
220 '\r' => output.push_str("\\r"),
221 '\t' => output.push_str("\\t"),
222 ch if ch.is_control() => {
223 let _ = write!(output, "\\u{:04x}", ch as u32);
224 }
225 ch => output.push(ch),
226 }
227 }
228 output.push('"');
229 output
230}