Skip to main content

turbopack_ecmascript/analyzer/jsvalue/
normalize.rs

1use std::{hash::BuildHasherDefault, mem::take};
2
3use rustc_hash::FxHasher;
4use turbo_tasks::FxIndexSet;
5
6use crate::analyzer::{Bump, BumpVec, JsValue, jsvalue::similar::SimilarJsValue};
7
8// Alternatives management
9impl<'a> JsValue<'a> {
10    /// Add an alternative to the current value. Might be a no-op if the value
11    /// already contains this alternative. Potentially expensive operation
12    /// as it has to compare the value with all existing alternatives.
13    pub(crate) fn add_alt(&mut self, arena: &'a Bump, v: Self) {
14        if self == &v {
15            return;
16        }
17
18        if let JsValue::Alternatives {
19            total_nodes: c,
20            values,
21            logical_property: _,
22        } = self
23        {
24            if !values.contains(&v) {
25                *c += v.total_nodes();
26                values.push(arena, v);
27            }
28        } else {
29            let l = take(self);
30            *self = JsValue::Alternatives {
31                total_nodes: 1 + l.total_nodes() + v.total_nodes(),
32                values: BumpVec::from_iter_in(arena, [l, v]),
33                logical_property: None,
34            };
35        }
36    }
37}
38
39// Normalization
40impl<'a> JsValue<'a> {
41    /// Normalizes only the current node. Nested alternatives, concatenations,
42    /// or operations are collapsed.
43    pub fn normalize_shallow(&mut self, arena: &'a Bump) {
44        match self {
45            JsValue::Alternatives {
46                total_nodes: _,
47                values,
48                logical_property: _,
49            } => {
50                if values.len() == 1 {
51                    *self = take(&mut values[0]);
52                } else {
53                    let mut set = FxIndexSet::with_capacity_and_hasher(
54                        values.len(),
55                        BuildHasherDefault::<FxHasher>::default(),
56                    );
57                    // Take the children out so we can rebuild `values` in place.
58                    let taken = take(values);
59                    for v in taken {
60                        match v {
61                            JsValue::Alternatives {
62                                total_nodes: _,
63                                values,
64                                logical_property: _,
65                            } => {
66                                for v in values {
67                                    set.insert(SimilarJsValue(v));
68                                }
69                            }
70                            v => {
71                                set.insert(SimilarJsValue(v));
72                            }
73                        }
74                    }
75                    if set.len() == 1 {
76                        *self = set.into_iter().next().unwrap().0;
77                    } else {
78                        *values = BumpVec::from_iter_in(arena, set.into_iter().map(|v| v.0));
79                        self.update_total_nodes();
80                    }
81                }
82            }
83            JsValue::Concat(_, v) => {
84                // TODO(kdy1): Remove duplicate
85                let taken = take(v);
86                let mut new: BumpVec<JsValue> = BumpVec::with_capacity_in(arena, taken.len());
87                for v in taken {
88                    // Remove empty strings
89                    if v.as_str() == Some("") {
90                        continue;
91                    }
92                    if let Some(str) = v.as_str() {
93                        if let Some(last) = new.last_mut() {
94                            if let Some(last_str) = last.as_str() {
95                                *last = [last_str, str].concat().into();
96                            } else {
97                                new.push(arena, v);
98                            }
99                        } else {
100                            new.push(arena, v);
101                        }
102                    } else if let JsValue::Concat(_, v) = v {
103                        new.extend(arena, v);
104                    } else {
105                        new.push(arena, v);
106                    }
107                }
108                if new.len() == 1 {
109                    *self = new.into_iter().next().unwrap();
110                } else {
111                    *v = new;
112                    self.update_total_nodes();
113                }
114            }
115            JsValue::Add(_, v) => {
116                let taken = take(v);
117                let mut added: BumpVec<JsValue> = BumpVec::with_capacity_in(arena, taken.len());
118                let mut iter = taken.into_iter();
119                while let Some(item) = iter.next() {
120                    if item.is_string() == Some(true) {
121                        let mut concat: BumpVec<JsValue> = match added.len() {
122                            0 => BumpVec::new(),
123                            1 => BumpVec::from_iter_in(arena, [added.into_iter().next().unwrap()]),
124                            _ => BumpVec::from_iter_in(
125                                arena,
126                                [JsValue::Add(
127                                    1 + added.iter().map(|v| v.total_nodes()).sum::<u32>(),
128                                    added,
129                                )],
130                            ),
131                        };
132                        concat.push(arena, item);
133                        concat.extend(arena, iter);
134                        *self = JsValue::Concat(
135                            1 + concat.iter().map(|v| v.total_nodes()).sum::<u32>(),
136                            concat,
137                        );
138                        return;
139                    } else {
140                        added.push(arena, item);
141                    }
142                }
143                if added.len() == 1 {
144                    *self = added.into_iter().next().unwrap();
145                } else {
146                    *v = added;
147                    self.update_total_nodes();
148                }
149            }
150            JsValue::Logical(_, op, list)
151                // Nested logical expressions can be normalized: e. g. `a && (b && c)` => `a &&
152                // b && c`
153                if list.iter().any(|v| {
154                    if let JsValue::Logical(_, inner_op, _) = v {
155                        inner_op == op
156                    } else {
157                        false
158                    }
159                }) => {
160                    // Taking the old list and constructing a new merged list
161                    let taken = take(list);
162                    for mut v in taken {
163                        if let JsValue::Logical(_, inner_op, inner_list) = &mut v {
164                            if inner_op == op {
165                                list.extend(arena, take(inner_list));
166                            } else {
167                                list.push(arena, v);
168                            }
169                        } else {
170                            list.push(arena, v);
171                        }
172                    }
173                    self.update_total_nodes();
174                }
175            _ => {}
176        }
177    }
178
179    /// Normalizes the current node and all nested nodes.
180    pub fn normalize(&mut self, arena: &'a Bump) {
181        self.for_each_children_mut(&mut |child| {
182            child.normalize(arena);
183            true
184        });
185        self.normalize_shallow(arena);
186    }
187}
188
189// Similarity