Skip to main content

turbopack_ecmascript/analyzer/jsvalue/
explain.rs

1use std::fmt::Write;
2
3use either::Either;
4
5use crate::analyzer::{JsValue, ModuleValue, ObjectPart, jsvalue::pretty_join};
6
7// Methods for explaining a value
8impl 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        // let i = hints.len();
52
53        // if explainer.len() < 100 {
54        self.explain_internal(hints, indent_depth, depth - 1, unknown_depth)
55        // }
56        // hints.truncate(i);
57        // hints.push(String::new());
58        // hints[i] = format!(
59        //     "- *{}* {}",
60        //     i,
61        //     self.explain_internal(hints, 1, depth - 1, unknown_depth)
62        // );
63        // format!("*{}*", i)
64    }
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}