turbopack_ecmascript/
utils.rs

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