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 { 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}