Skip to main content

presolve_compiler/
explain.rs

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    // Manual JSON keeps the first slice dependency-free. Replace this with serde
102    // once the schema is stable enough to deserve a dependency.
103    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}