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 {
95 parts, mutability, ..
96 } => format!(
97 "{}{{{}}}",
98 mutability,
99 pretty_join(
100 &parts
101 .iter()
102 .map(|v| match v {
103 ObjectPart::KeyValue(key, value) => format!(
104 "{}: {}",
105 key.explain_internal_inner(
106 hints,
107 indent_depth + 1,
108 depth,
109 unknown_depth
110 ),
111 value.explain_internal_inner(
112 hints,
113 indent_depth + 1,
114 depth,
115 unknown_depth
116 )
117 ),
118 ObjectPart::Spread(value) => format!(
119 "...{}",
120 value.explain_internal_inner(
121 hints,
122 indent_depth + 1,
123 depth,
124 unknown_depth
125 )
126 ),
127 })
128 .collect::<Vec<_>>(),
129 indent_depth,
130 ", ",
131 ",",
132 ""
133 )
134 ),
135 JsValue::Url(url, kind) => format!("{url} {kind}"),
136 JsValue::Alternatives {
137 total_nodes: _,
138 values,
139 logical_property,
140 } => {
141 let list = pretty_join(
142 &values
143 .iter()
144 .map(|v| {
145 v.explain_internal_inner(hints, indent_depth + 1, depth, unknown_depth)
146 })
147 .collect::<Vec<_>>(),
148 indent_depth,
149 " | ",
150 "",
151 "| ",
152 );
153 if let Some(logical_property) = logical_property {
154 format!("({list}){{{logical_property}}}")
155 } else {
156 format!("({list})")
157 }
158 }
159 JsValue::FreeVar(name) => format!("FreeVar({name})"),
160 JsValue::Variable(name) => {
161 format!("{}", name.0)
162 }
163 JsValue::Argument(_, index) => {
164 format!("arguments[{index}]")
165 }
166 JsValue::Concat(_, list) => format!(
167 "`{}`",
168 list.iter()
169 .map(|v| v.as_str().map_or_else(
170 || format!(
171 "${{{}}}",
172 v.explain_internal_inner(hints, indent_depth + 1, depth, unknown_depth)
173 ),
174 |str| str.to_string()
175 ))
176 .collect::<Vec<_>>()
177 .join("")
178 ),
179 JsValue::Add(_, list) => format!(
180 "({})",
181 pretty_join(
182 &list
183 .iter()
184 .map(|v| v.explain_internal_inner(
185 hints,
186 indent_depth + 1,
187 depth,
188 unknown_depth
189 ))
190 .collect::<Vec<_>>(),
191 indent_depth,
192 " + ",
193 "",
194 "+ "
195 )
196 ),
197 JsValue::Logical(_, op, list) => format!(
198 "({})",
199 pretty_join(
200 &list
201 .iter()
202 .map(|v| v.explain_internal_inner(
203 hints,
204 indent_depth + 1,
205 depth,
206 unknown_depth
207 ))
208 .collect::<Vec<_>>(),
209 indent_depth,
210 op.joiner(),
211 "",
212 op.multi_line_joiner()
213 )
214 ),
215 JsValue::Binary(_, a, op, b) => format!(
216 "({}{}{})",
217 a.explain_internal_inner(hints, indent_depth, depth, unknown_depth),
218 op.joiner(),
219 b.explain_internal_inner(hints, indent_depth, depth, unknown_depth),
220 ),
221 JsValue::Tenary(_, test, cons, alt) => format!(
222 "({} ? {} : {})",
223 test.explain_internal_inner(hints, indent_depth, depth, unknown_depth),
224 cons.explain_internal_inner(hints, indent_depth, depth, unknown_depth),
225 alt.explain_internal_inner(hints, indent_depth, depth, unknown_depth),
226 ),
227 JsValue::Not(_, value) => format!(
228 "!({})",
229 value.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
230 ),
231 JsValue::Iterated(_, iterable) => {
232 format!(
233 "Iterated({})",
234 iterable.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
235 )
236 }
237 JsValue::TypeOf(_, operand) => {
238 format!(
239 "typeof({})",
240 operand.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
241 )
242 }
243 JsValue::Promise(_, operand) => {
244 format!(
245 "Promise<{}>",
246 operand.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
247 )
248 }
249 JsValue::Awaited(_, operand) => {
250 format!(
251 "await({})",
252 operand.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
253 )
254 }
255 JsValue::New(_, call) => format!(
256 "new {}({})",
257 call.callee()
258 .explain_internal_inner(hints, indent_depth, depth, unknown_depth),
259 pretty_join(
260 &call
261 .args()
262 .iter()
263 .map(|v| v.explain_internal_inner(
264 hints,
265 indent_depth + 1,
266 depth,
267 unknown_depth
268 ))
269 .collect::<Vec<_>>(),
270 indent_depth,
271 ", ",
272 ",",
273 ""
274 )
275 ),
276 JsValue::Call(_, call) => format!(
277 "{}({})",
278 call.callee()
279 .explain_internal_inner(hints, indent_depth, depth, unknown_depth),
280 pretty_join(
281 &call
282 .args()
283 .iter()
284 .map(|v| v.explain_internal_inner(
285 hints,
286 indent_depth + 1,
287 depth,
288 unknown_depth
289 ))
290 .collect::<Vec<_>>(),
291 indent_depth,
292 ", ",
293 ",",
294 ""
295 )
296 ),
297 JsValue::SuperCall(_, args) => {
298 format!(
299 "super({})",
300 pretty_join(
301 &args
302 .iter()
303 .map(|v| v.explain_internal_inner(
304 hints,
305 indent_depth + 1,
306 depth,
307 unknown_depth
308 ))
309 .collect::<Vec<_>>(),
310 indent_depth,
311 ", ",
312 ",",
313 ""
314 )
315 )
316 }
317 JsValue::MemberCall(_, call) => format!(
318 "{}[{}]({})",
319 call.obj()
320 .explain_internal_inner(hints, indent_depth, depth, unknown_depth),
321 call.prop()
322 .explain_internal_inner(hints, indent_depth, depth, unknown_depth),
323 pretty_join(
324 &call
325 .args()
326 .iter()
327 .map(|v| v.explain_internal_inner(
328 hints,
329 indent_depth + 1,
330 depth,
331 unknown_depth
332 ))
333 .collect::<Vec<_>>(),
334 indent_depth,
335 ", ",
336 ",",
337 ""
338 )
339 ),
340 JsValue::Member(_, obj, prop) => {
341 format!(
342 "{}[{}]",
343 obj.explain_internal_inner(hints, indent_depth, depth, unknown_depth),
344 prop.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
345 )
346 }
347 JsValue::In(_, left, right) => {
348 format!(
349 "{} in {}",
350 left.explain_internal_inner(hints, indent_depth, depth, unknown_depth),
351 right.explain_internal_inner(hints, indent_depth, depth, unknown_depth)
352 )
353 }
354 JsValue::Module(ModuleValue {
355 module: name,
356 annotations,
357 analyze_for_constants,
358 reference: _,
359 }) => {
360 format!(
361 "module<{}, {}{}>",
362 name.to_string_lossy(),
363 if let Some(annotations) = annotations {
364 Either::Left(annotations)
365 } else {
366 Either::Right("{}")
367 },
368 if *analyze_for_constants {
369 ", analyze for constants"
370 } else {
371 ""
372 }
373 )
374 }
375 JsValue::Unknown {
376 original_value: inner,
377 reason: explainer,
378 has_side_effects,
379 } => {
380 let has_side_effects = *has_side_effects;
381 if unknown_depth == 0 || explainer.is_empty() {
382 "???".to_string()
383 } else if let Some(inner) = inner {
384 let i = hints.len();
385 hints.push(String::new());
386 hints[i] = format!(
387 "- *{}* {}\n ⚠️ {}{}",
388 i,
389 inner.explain_internal(hints, 1, depth, unknown_depth - 1),
390 explainer,
391 if has_side_effects {
392 "\n ⚠️ This value might have side effects"
393 } else {
394 ""
395 }
396 );
397 format!("???*{i}*")
398 } else {
399 let i = hints.len();
400 hints.push(String::new());
401 hints[i] = format!(
402 "- *{}* {}{}",
403 i,
404 explainer,
405 if has_side_effects {
406 "\n ⚠️ This value might have side effects"
407 } else {
408 ""
409 }
410 );
411 format!("???*{i}*")
412 }
413 }
414 JsValue::WellKnownObject(obj) => {
415 let (name, explainer) = obj.explain();
416 if depth > 0 {
417 let i = hints.len();
418 hints.push(format!("- *{i}* {name}: {explainer}"));
419 format!("{name}*{i}*")
420 } else {
421 name.to_string()
422 }
423 }
424 JsValue::WellKnownFunction(func) => {
425 let (name, explainer) = func.explain();
426 if depth > 0 {
427 let i = hints.len();
428 hints.push(format!("- *{i}* {name}: {explainer}"));
429 format!("{name}*{i}*")
430 } else {
431 name
432 }
433 }
434 JsValue::Function(_, _, return_value) => {
435 if depth > 0 {
436 format!(
437 "(...) => {}",
438 return_value.explain_internal(
439 hints,
440 indent_depth,
441 depth - 1,
442 unknown_depth
443 )
444 )
445 } else {
446 "(...) => ...".to_string()
447 }
448 }
449 }
450 }
451}