1use std::fmt::Write;
2
3use either::Either;
4
5use crate::analyzer::{JsValue, ModuleValue, ObjectPart, jsvalue::pretty_join};
6
7impl JsValue<'_> {
9 pub fn explain_args(
10 args: &[JsValue<'_>],
11 depth: usize,
12 unknown_depth: usize,
13 ) -> (String, String) {
14 let mut hints = Vec::new();
15 let args = args
16 .iter()
17 .map(|arg| arg.explain_internal(&mut hints, 1, depth, unknown_depth))
18 .collect::<Vec<_>>();
19 let explainer = pretty_join(&args, 0, ", ", ",", "");
20 (
21 explainer,
22 hints.into_iter().fold(String::new(), |mut out, h| {
23 let _ = write!(out, "\n{h}");
24 out
25 }),
26 )
27 }
28
29 pub fn explain(&self, depth: usize, unknown_depth: usize) -> (String, String) {
30 let mut hints = Vec::new();
31 let explainer = self.explain_internal(&mut hints, 0, depth, unknown_depth);
32 (
33 explainer,
34 hints.into_iter().fold(String::new(), |mut out, h| {
35 let _ = write!(out, "\n{h}");
36 out
37 }),
38 )
39 }
40
41 fn explain_internal_inner(
42 &self,
43 hints: &mut Vec<String>,
44 indent_depth: usize,
45 depth: usize,
46 unknown_depth: usize,
47 ) -> String {
48 if depth == 0 {
49 return "...".to_string();
50 }
51 self.explain_internal(hints, indent_depth, depth - 1, unknown_depth)
55 }
65
66 fn explain_internal(
67 &self,
68 hints: &mut Vec<String>,
69 indent_depth: usize,
70 depth: usize,
71 unknown_depth: usize,
72 ) -> String {
73 match self {
74 JsValue::Constant(v) => format!("{v}"),
75 JsValue::Array { items, mutable, .. } => format!(
76 "{}[{}]",
77 if *mutable { "" } else { "frozen " },
78 pretty_join(
79 &items
80 .iter()
81 .map(|v| v.explain_internal_inner(
82 hints,
83 indent_depth + 1,
84 depth,
85 unknown_depth
86 ))
87 .collect::<Vec<_>>(),
88 indent_depth,
89 ", ",
90 ",",
91 ""
92 )
93 ),
94 JsValue::Object { parts, mutable, .. } => format!(
95 "{}{{{}}}",
96 if *mutable { "" } else { "frozen " },
97 pretty_join(
98 &parts
99 .iter()
100 .map(|v| match v {
101 ObjectPart::KeyValue(key, value) => format!(
102 "{}: {}",
103 key.explain_internal_inner(
104 hints,
105 indent_depth + 1,
106 depth,
107 unknown_depth
108 ),
109 value.explain_internal_inner(
110 hints,
111 indent_depth + 1,
112 depth,
113 unknown_depth
114 )
115 ),
116 ObjectPart::Spread(value) => format!(
117 "...{}",
118 value.explain_internal_inner(
119 hints,
120 indent_depth + 1,
121 depth,
122 unknown_depth
123 )
124 ),
125 })
126 .collect::<Vec<_>>(),
127 indent_depth,
128 ", ",
129 ",",
130 ""
131 )
132 ),
133 JsValue::Url(url, kind) => format!("{url} {kind}"),
134 JsValue::Alternatives {
135 total_nodes: _,
136 values,
137 logical_property,
138 } => {
139 let list = pretty_join(
140 &values
141 .iter()
142 .map(|v| {
143 v.explain_internal_inner(hints, indent_depth + 1, depth, unknown_depth)
144 })
145 .collect::<Vec<_>>(),
146 indent_depth,
147 " | ",
148 "",
149 "| ",
150 );
151 if let Some(logical_property) = logical_property {
152 format!("({list}){{{logical_property}}}")
153 } else {
154 format!("({list})")
155 }
156 }
157 JsValue::FreeVar(name) => format!("FreeVar({name})"),
158 JsValue::Variable(name) => {
159 format!("{}", name.0)
160 }
161 JsValue::Argument(_, index) => {
162 format!("arguments[{index}]")
163 }
164 JsValue::Concat(_, list) => format!(
165 "`{}`",
166 list.iter()
167 .map(|v| v.as_str().map_or_else(
168 || format!(
169 "${{{}}}",
170 v.explain_internal_inner(hints, indent_depth + 1, depth, unknown_depth)
171 ),
172 |str| str.to_string()
173 ))
174 .collect::<Vec<_>>()
175 .join("")
176 ),
177 JsValue::Add(_, list) => format!(
178 "({})",
179 pretty_join(
180 &list
181 .iter()
182 .map(|v| v.explain_internal_inner(
183 hints,
184 indent_depth + 1,
185 depth,
186 unknown_depth
187 ))
188 .collect::<Vec<_>>(),
189 indent_depth,
190 " + ",
191 "",
192 "+ "
193 )
194 ),
195 JsValue::Logical(_, op, list) => format!(
196 "({})",
197 pretty_join(
198 &list
199 .iter()
200 .map(|v| v.explain_internal_inner(
201 hints,
202 indent_depth + 1,
203 depth,
204 unknown_depth
205 ))
206 .collect::<Vec<_>>(),
207 indent_depth,
208 op.joiner(),
209 "",
210 op.multi_line_joiner()
211 )
212 ),
213 JsValue::Binary(_, a, op, b) => format!(
214 "({}{}{})",
215 a.explain_internal_inner(hints, indent_depth, depth, unknown_depth),
216 op.joiner(),
217 b.explain_internal_inner(hints, indent_depth, depth, unknown_depth),
218 ),
219 JsValue::Tenary(_, test, cons, alt) => format!(
220 "({} ? {} : {})",
221 test.explain_internal_inner(hints, indent_depth, depth, unknown_depth),
222 cons.explain_internal_inner(hints, indent_depth, depth, unknown_depth),
223 alt.explain_internal_inner(hints, indent_depth, depth, unknown_depth),
224 ),
225 JsValue::Not(_, value) => format!(
226 "!({})",
227 value.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
228 ),
229 JsValue::Iterated(_, iterable) => {
230 format!(
231 "Iterated({})",
232 iterable.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
233 )
234 }
235 JsValue::TypeOf(_, operand) => {
236 format!(
237 "typeof({})",
238 operand.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
239 )
240 }
241 JsValue::Promise(_, operand) => {
242 format!(
243 "Promise<{}>",
244 operand.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
245 )
246 }
247 JsValue::Awaited(_, operand) => {
248 format!(
249 "await({})",
250 operand.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
251 )
252 }
253 JsValue::New(_, call) => format!(
254 "new {}({})",
255 call.callee()
256 .explain_internal_inner(hints, indent_depth, depth, unknown_depth),
257 pretty_join(
258 &call
259 .args()
260 .iter()
261 .map(|v| v.explain_internal_inner(
262 hints,
263 indent_depth + 1,
264 depth,
265 unknown_depth
266 ))
267 .collect::<Vec<_>>(),
268 indent_depth,
269 ", ",
270 ",",
271 ""
272 )
273 ),
274 JsValue::Call(_, call) => format!(
275 "{}({})",
276 call.callee()
277 .explain_internal_inner(hints, indent_depth, depth, unknown_depth),
278 pretty_join(
279 &call
280 .args()
281 .iter()
282 .map(|v| v.explain_internal_inner(
283 hints,
284 indent_depth + 1,
285 depth,
286 unknown_depth
287 ))
288 .collect::<Vec<_>>(),
289 indent_depth,
290 ", ",
291 ",",
292 ""
293 )
294 ),
295 JsValue::SuperCall(_, args) => {
296 format!(
297 "super({})",
298 pretty_join(
299 &args
300 .iter()
301 .map(|v| v.explain_internal_inner(
302 hints,
303 indent_depth + 1,
304 depth,
305 unknown_depth
306 ))
307 .collect::<Vec<_>>(),
308 indent_depth,
309 ", ",
310 ",",
311 ""
312 )
313 )
314 }
315 JsValue::MemberCall(_, call) => format!(
316 "{}[{}]({})",
317 call.obj()
318 .explain_internal_inner(hints, indent_depth, depth, unknown_depth),
319 call.prop()
320 .explain_internal_inner(hints, indent_depth, depth, unknown_depth),
321 pretty_join(
322 &call
323 .args()
324 .iter()
325 .map(|v| v.explain_internal_inner(
326 hints,
327 indent_depth + 1,
328 depth,
329 unknown_depth
330 ))
331 .collect::<Vec<_>>(),
332 indent_depth,
333 ", ",
334 ",",
335 ""
336 )
337 ),
338 JsValue::Member(_, obj, prop) => {
339 format!(
340 "{}[{}]",
341 obj.explain_internal_inner(hints, indent_depth, depth, unknown_depth),
342 prop.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
343 )
344 }
345 JsValue::In(_, left, right) => {
346 format!(
347 "{} in {}",
348 left.explain_internal_inner(hints, indent_depth, depth, unknown_depth),
349 right.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
350 )
351 }
352 JsValue::Module(ModuleValue {
353 module: name,
354 annotations,
355 }) => {
356 format!(
357 "module<{}, {}>",
358 name.to_string_lossy(),
359 if let Some(annotations) = annotations {
360 Either::Left(annotations)
361 } else {
362 Either::Right("{}")
363 }
364 )
365 }
366 JsValue::Unknown {
367 original_value: inner,
368 reason: explainer,
369 has_side_effects,
370 } => {
371 let has_side_effects = *has_side_effects;
372 if unknown_depth == 0 || explainer.is_empty() {
373 "???".to_string()
374 } else if let Some(inner) = inner {
375 let i = hints.len();
376 hints.push(String::new());
377 hints[i] = format!(
378 "- *{}* {}\n ⚠️ {}{}",
379 i,
380 inner.explain_internal(hints, 1, depth, unknown_depth - 1),
381 explainer,
382 if has_side_effects {
383 "\n ⚠️ This value might have side effects"
384 } else {
385 ""
386 }
387 );
388 format!("???*{i}*")
389 } else {
390 let i = hints.len();
391 hints.push(String::new());
392 hints[i] = format!(
393 "- *{}* {}{}",
394 i,
395 explainer,
396 if has_side_effects {
397 "\n ⚠️ This value might have side effects"
398 } else {
399 ""
400 }
401 );
402 format!("???*{i}*")
403 }
404 }
405 JsValue::WellKnownObject(obj) => {
406 let (name, explainer) = obj.explain();
407 if depth > 0 {
408 let i = hints.len();
409 hints.push(format!("- *{i}* {name}: {explainer}"));
410 format!("{name}*{i}*")
411 } else {
412 name.to_string()
413 }
414 }
415 JsValue::WellKnownFunction(func) => {
416 let (name, explainer) = func.explain();
417 if depth > 0 {
418 let i = hints.len();
419 hints.push(format!("- *{i}* {name}: {explainer}"));
420 format!("{name}*{i}*")
421 } else {
422 name
423 }
424 }
425 JsValue::Function(_, _, return_value) => {
426 if depth > 0 {
427 format!(
428 "(...) => {}",
429 return_value.explain_internal(
430 hints,
431 indent_depth,
432 depth - 1,
433 unknown_depth
434 )
435 )
436 } else {
437 "(...) => ...".to_string()
438 }
439 }
440 }
441 }
442}