turbopack_ecmascript/
utils.rs

1use std::ops::Deref;
2
3use serde::{Deserialize, Serialize};
4use swc_core::{
5    common::{DUMMY_SP, SyntaxContext},
6    ecma::{
7        ast::{Expr, Lit, Str},
8        visit::AstParentKind,
9    },
10};
11use turbo_rcstr::{RcStr, rcstr};
12use turbo_tasks::{NonLocalValue, TaskInput, trace::TraceRawVcs};
13use turbopack_core::{chunk::ModuleId, resolve::pattern::Pattern};
14
15use crate::analyzer::{
16    ConstantNumber, ConstantValue, JsValue, JsValueUrlKind, ModuleValue, WellKnownFunctionKind,
17    WellKnownObjectKind,
18};
19
20pub fn unparen(expr: &Expr) -> &Expr {
21    if let Some(expr) = expr.as_paren() {
22        return unparen(&expr.expr);
23    }
24    if let Expr::Seq(seq) = expr {
25        return unparen(seq.exprs.last().unwrap());
26    }
27    expr
28}
29
30/// Converts a js-value into a Pattern for matching resources.
31pub fn js_value_to_pattern(value: &JsValue) -> Pattern {
32    match value {
33        JsValue::Constant(v) => Pattern::Constant(match v {
34            ConstantValue::Str(str) => {
35                // Normalize windows file paths when constructing the pattern.
36                // See PACK-3279
37                if str.as_str().contains("\\") {
38                    RcStr::from(str.to_string().replace('\\', "/"))
39                } else {
40                    str.as_rcstr()
41                }
42            }
43            ConstantValue::True => rcstr!("true"),
44            ConstantValue::False => rcstr!("false"),
45            ConstantValue::Null => rcstr!("null"),
46            ConstantValue::Num(ConstantNumber(n)) => n.to_string().into(),
47            ConstantValue::BigInt(n) => n.to_string().into(),
48            ConstantValue::Regex(box (exp, flags)) => format!("/{exp}/{flags}").into(),
49            ConstantValue::Undefined => rcstr!("undefined"),
50        }),
51        JsValue::Url(v, JsValueUrlKind::Relative) => Pattern::Constant(v.as_rcstr()),
52        JsValue::Alternatives {
53            total_nodes: _,
54            values,
55            logical_property: _,
56        } => {
57            let mut alts = Pattern::Alternatives(values.iter().map(js_value_to_pattern).collect());
58            alts.normalize();
59            alts
60        }
61        JsValue::Concat(_, parts) => {
62            let mut concats =
63                Pattern::Concatenation(parts.iter().map(js_value_to_pattern).collect());
64            concats.normalize();
65            concats
66        }
67        JsValue::Add(..) => {
68            // TODO do we need to handle that here
69            // or is that already covered by normalization of JsValue
70            Pattern::Dynamic
71        }
72        _ => Pattern::Dynamic,
73    }
74}
75
76const JS_MAX_SAFE_INTEGER: u64 = (1u64 << 53) - 1;
77
78pub fn module_id_to_lit(module_id: &ModuleId) -> Expr {
79    Expr::Lit(match module_id {
80        ModuleId::Number(n) => {
81            if *n <= JS_MAX_SAFE_INTEGER {
82                Lit::Num((*n as f64).into())
83            } else {
84                Lit::Str(Str {
85                    span: DUMMY_SP,
86                    value: n.to_string().into(),
87                    raw: None,
88                })
89            }
90        }
91        ModuleId::String(s) => Lit::Str(Str {
92            span: DUMMY_SP,
93            value: (s as &str).into(),
94            raw: None,
95        }),
96    })
97}
98
99pub struct StringifyModuleId<'a>(pub &'a ModuleId);
100
101impl std::fmt::Display for StringifyModuleId<'_> {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        match self.0 {
104            ModuleId::Number(n) => {
105                if *n <= JS_MAX_SAFE_INTEGER {
106                    n.fmt(f)
107                } else {
108                    write!(f, "\"{n}\"")
109                }
110            }
111            ModuleId::String(s) => StringifyJs(s).fmt(f),
112        }
113    }
114}
115
116pub struct StringifyJs<'a, T>(pub &'a T)
117where
118    T: ?Sized;
119
120impl<T> std::fmt::Display for StringifyJs<'_, T>
121where
122    T: Serialize + ?Sized,
123{
124    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125        /// [`std::fmt::Formatter`] does not implement [`std::io::Write`],
126        /// so we need to wrap it in a struct that does.
127        struct DisplayWriter<'a, 'b> {
128            f: &'a mut std::fmt::Formatter<'b>,
129        }
130
131        impl std::io::Write for DisplayWriter<'_, '_> {
132            fn write(&mut self, bytes: &[u8]) -> std::result::Result<usize, std::io::Error> {
133                self.f
134                    .write_str(std::str::from_utf8(bytes).map_err(std::io::Error::other)?)
135                    .map_err(std::io::Error::other)?;
136                Ok(bytes.len())
137            }
138
139            fn flush(&mut self) -> std::result::Result<(), std::io::Error> {
140                unreachable!()
141            }
142        }
143
144        let to_writer = match f.alternate() {
145            true => serde_json::to_writer_pretty,
146            false => serde_json::to_writer,
147        };
148
149        to_writer(DisplayWriter { f }, self.0).map_err(|_err| std::fmt::Error)
150    }
151}
152
153pub struct FormatIter<T: Iterator, F: Fn() -> T>(pub F);
154
155macro_rules! format_iter {
156    ($trait:path) => {
157        impl<T: Iterator, F: Fn() -> T> $trait for FormatIter<T, F>
158        where
159            T::Item: $trait,
160        {
161            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162                for item in self.0() {
163                    item.fmt(f)?;
164                }
165                Ok(())
166            }
167        }
168    };
169}
170
171format_iter!(std::fmt::Binary);
172format_iter!(std::fmt::Debug);
173format_iter!(std::fmt::Display);
174format_iter!(std::fmt::LowerExp);
175format_iter!(std::fmt::LowerHex);
176format_iter!(std::fmt::Octal);
177format_iter!(std::fmt::Pointer);
178format_iter!(std::fmt::UpperExp);
179format_iter!(std::fmt::UpperHex);
180
181#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, Debug, NonLocalValue)]
182pub enum AstPathRange {
183    /// The ast path to the block or expression.
184    Exact(#[turbo_tasks(trace_ignore)] Vec<AstParentKind>),
185    /// The ast path to a expression just before the range in the parent of the
186    /// specific ast path.
187    StartAfter(#[turbo_tasks(trace_ignore)] Vec<AstParentKind>),
188}
189
190/// Converts a module value (ie an import) to a well known object,
191/// which we specifically handle.
192pub fn module_value_to_well_known_object(module_value: &ModuleValue) -> Option<JsValue> {
193    Some(match &*module_value.module {
194        "node:path" | "path" => JsValue::WellKnownObject(WellKnownObjectKind::PathModule),
195        "node:fs/promises" | "fs/promises" => {
196            JsValue::WellKnownObject(WellKnownObjectKind::FsModule)
197        }
198        "node:fs" | "fs" => JsValue::WellKnownObject(WellKnownObjectKind::FsModule),
199        "node:child_process" | "child_process" => {
200            JsValue::WellKnownObject(WellKnownObjectKind::ChildProcess)
201        }
202        "node:os" | "os" => JsValue::WellKnownObject(WellKnownObjectKind::OsModule),
203        "node:process" | "process" => JsValue::WellKnownObject(WellKnownObjectKind::NodeProcess),
204        "node:url" | "url" => JsValue::WellKnownObject(WellKnownObjectKind::UrlModule),
205        "node:module" | "module" => JsValue::WellKnownObject(WellKnownObjectKind::ModuleModule),
206        "node-pre-gyp" | "@mapbox/node-pre-gyp" => {
207            JsValue::WellKnownObject(WellKnownObjectKind::NodePreGyp)
208        }
209        "node-gyp-build" => JsValue::WellKnownFunction(WellKnownFunctionKind::NodeGypBuild),
210        "node:bindings" | "bindings" => {
211            JsValue::WellKnownFunction(WellKnownFunctionKind::NodeBindings)
212        }
213        "express" => JsValue::WellKnownFunction(WellKnownFunctionKind::NodeExpress),
214        "strong-globalize" => {
215            JsValue::WellKnownFunction(WellKnownFunctionKind::NodeStrongGlobalize)
216        }
217        "resolve-from" => JsValue::WellKnownFunction(WellKnownFunctionKind::NodeResolveFrom),
218        "@grpc/proto-loader" => JsValue::WellKnownObject(WellKnownObjectKind::NodeProtobufLoader),
219        "fs-extra" => JsValue::WellKnownObject(WellKnownObjectKind::FsExtraModule),
220        _ => return None,
221    })
222}
223
224#[derive(Hash, Debug, Clone, Copy, Eq, Serialize, Deserialize, PartialEq, TraceRawVcs)]
225pub struct AstSyntaxContext(#[turbo_tasks(trace_ignore)] SyntaxContext);
226
227impl TaskInput for AstSyntaxContext {
228    fn is_transient(&self) -> bool {
229        false
230    }
231}
232unsafe impl NonLocalValue for AstSyntaxContext {}
233
234impl Deref for AstSyntaxContext {
235    type Target = SyntaxContext;
236
237    fn deref(&self) -> &Self::Target {
238        &self.0
239    }
240}
241
242impl From<SyntaxContext> for AstSyntaxContext {
243    fn from(v: SyntaxContext) -> Self {
244        Self(v)
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use turbo_rcstr::rcstr;
251    use turbopack_core::resolve::pattern::Pattern;
252
253    use crate::{
254        analyzer::{ConstantString, ConstantValue, JsValue},
255        utils::js_value_to_pattern,
256    };
257
258    #[test]
259    fn test_path_normalization_in_pattern() {
260        assert_eq!(
261            Pattern::Constant(rcstr!("hello/world")),
262            js_value_to_pattern(&JsValue::Constant(ConstantValue::Str(
263                ConstantString::RcStr(rcstr!("hello\\world"))
264            )))
265        );
266
267        assert_eq!(
268            Pattern::Constant(rcstr!("hello/world")),
269            js_value_to_pattern(&JsValue::Concat(
270                1,
271                vec![
272                    rcstr!("hello").into(),
273                    rcstr!("\\").into(),
274                    rcstr!("world").into()
275                ]
276            ))
277        );
278    }
279}