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