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