turbopack_ecmascript/
utils.rs

1use serde::{Deserialize, Serialize};
2use swc_core::{
3    common::DUMMY_SP,
4    ecma::{
5        ast::{Expr, Lit, Str},
6        visit::AstParentKind,
7    },
8};
9use turbo_tasks::{NonLocalValue, trace::TraceRawVcs};
10use turbopack_core::{chunk::ModuleId, resolve::pattern::Pattern};
11
12use crate::analyzer::{
13    ConstantNumber, ConstantValue, JsValue, JsValueUrlKind, ModuleValue, WellKnownFunctionKind,
14    WellKnownObjectKind,
15};
16
17pub fn unparen(expr: &Expr) -> &Expr {
18    if let Some(expr) = expr.as_paren() {
19        return unparen(&expr.expr);
20    }
21    if let Expr::Seq(seq) = expr {
22        return unparen(seq.exprs.last().unwrap());
23    }
24    expr
25}
26
27pub fn js_value_to_pattern(value: &JsValue) -> Pattern {
28    let mut result = match value {
29        JsValue::Constant(v) => Pattern::Constant(match v {
30            ConstantValue::Str(str) => str.as_str().into(),
31            ConstantValue::True => "true".into(),
32            ConstantValue::False => "false".into(),
33            ConstantValue::Null => "null".into(),
34            ConstantValue::Num(ConstantNumber(n)) => n.to_string().into(),
35            ConstantValue::BigInt(n) => n.to_string().into(),
36            ConstantValue::Regex(box (exp, flags)) => format!("/{exp}/{flags}").into(),
37            ConstantValue::Undefined => "undefined".into(),
38        }),
39        JsValue::Url(v, JsValueUrlKind::Relative) => Pattern::Constant(v.as_str().into()),
40        JsValue::Alternatives {
41            total_nodes: _,
42            values,
43            logical_property: _,
44        } => Pattern::Alternatives(values.iter().map(js_value_to_pattern).collect()),
45        JsValue::Concat(_, parts) => {
46            Pattern::Concatenation(parts.iter().map(js_value_to_pattern).collect())
47        }
48        JsValue::Add(..) => {
49            // TODO do we need to handle that here
50            // or is that already covered by normalization of JsValue
51            Pattern::Dynamic
52        }
53        _ => Pattern::Dynamic,
54    };
55    result.normalize();
56    result
57}
58
59const JS_MAX_SAFE_INTEGER: u64 = (1u64 << 53) - 1;
60
61pub fn module_id_to_lit(module_id: &ModuleId) -> Expr {
62    Expr::Lit(match module_id {
63        ModuleId::Number(n) => {
64            if *n <= JS_MAX_SAFE_INTEGER {
65                Lit::Num((*n as f64).into())
66            } else {
67                Lit::Str(Str {
68                    span: DUMMY_SP,
69                    value: n.to_string().into(),
70                    raw: None,
71                })
72            }
73        }
74        ModuleId::String(s) => Lit::Str(Str {
75            span: DUMMY_SP,
76            value: (s as &str).into(),
77            raw: None,
78        }),
79    })
80}
81
82pub struct StringifyModuleId<'a>(pub &'a ModuleId);
83
84impl std::fmt::Display for StringifyModuleId<'_> {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        match self.0 {
87            ModuleId::Number(n) => {
88                if *n <= JS_MAX_SAFE_INTEGER {
89                    n.fmt(f)
90                } else {
91                    write!(f, "\"{n}\"")
92                }
93            }
94            ModuleId::String(s) => StringifyJs(s).fmt(f),
95        }
96    }
97}
98
99pub struct StringifyJs<'a, T>(pub &'a T)
100where
101    T: ?Sized;
102
103impl<T> std::fmt::Display for StringifyJs<'_, T>
104where
105    T: Serialize + ?Sized,
106{
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        /// [`std::fmt::Formatter`] does not implement [`std::io::Write`],
109        /// so we need to wrap it in a struct that does.
110        struct DisplayWriter<'a, 'b> {
111            f: &'a mut std::fmt::Formatter<'b>,
112        }
113
114        impl std::io::Write for DisplayWriter<'_, '_> {
115            fn write(&mut self, bytes: &[u8]) -> std::result::Result<usize, std::io::Error> {
116                self.f
117                    .write_str(std::str::from_utf8(bytes).map_err(std::io::Error::other)?)
118                    .map_err(std::io::Error::other)?;
119                Ok(bytes.len())
120            }
121
122            fn flush(&mut self) -> std::result::Result<(), std::io::Error> {
123                unreachable!()
124            }
125        }
126
127        let to_writer = match f.alternate() {
128            true => serde_json::to_writer_pretty,
129            false => serde_json::to_writer,
130        };
131
132        to_writer(DisplayWriter { f }, self.0).map_err(|_err| std::fmt::Error)
133    }
134}
135
136pub struct FormatIter<T: Iterator, F: Fn() -> T>(pub F);
137
138macro_rules! format_iter {
139    ($trait:path) => {
140        impl<T: Iterator, F: Fn() -> T> $trait for FormatIter<T, F>
141        where
142            T::Item: $trait,
143        {
144            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145                for item in self.0() {
146                    item.fmt(f)?;
147                }
148                Ok(())
149            }
150        }
151    };
152}
153
154format_iter!(std::fmt::Binary);
155format_iter!(std::fmt::Debug);
156format_iter!(std::fmt::Display);
157format_iter!(std::fmt::LowerExp);
158format_iter!(std::fmt::LowerHex);
159format_iter!(std::fmt::Octal);
160format_iter!(std::fmt::Pointer);
161format_iter!(std::fmt::UpperExp);
162format_iter!(std::fmt::UpperHex);
163
164#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, TraceRawVcs, Debug, NonLocalValue)]
165pub enum AstPathRange {
166    /// The ast path to the block or expression.
167    Exact(#[turbo_tasks(trace_ignore)] Vec<AstParentKind>),
168    /// The ast path to a expression just before the range in the parent of the
169    /// specific ast path.
170    StartAfter(#[turbo_tasks(trace_ignore)] Vec<AstParentKind>),
171}
172
173/// Converts a module value (ie an import) to a well known object,
174/// which we specifically handle.
175pub fn module_value_to_well_known_object(module_value: &ModuleValue) -> Option<JsValue> {
176    Some(match &*module_value.module {
177        "node:path" | "path" => JsValue::WellKnownObject(WellKnownObjectKind::PathModule),
178        "node:fs/promises" | "fs/promises" => {
179            JsValue::WellKnownObject(WellKnownObjectKind::FsModule)
180        }
181        "node:fs" | "fs" => JsValue::WellKnownObject(WellKnownObjectKind::FsModule),
182        "node:child_process" | "child_process" => {
183            JsValue::WellKnownObject(WellKnownObjectKind::ChildProcess)
184        }
185        "node:os" | "os" => JsValue::WellKnownObject(WellKnownObjectKind::OsModule),
186        "node:process" | "process" => JsValue::WellKnownObject(WellKnownObjectKind::NodeProcess),
187        "node-pre-gyp" | "@mapbox/node-pre-gyp" => {
188            JsValue::WellKnownObject(WellKnownObjectKind::NodePreGyp)
189        }
190        "node-gyp-build" => JsValue::WellKnownFunction(WellKnownFunctionKind::NodeGypBuild),
191        "node:bindings" | "bindings" => {
192            JsValue::WellKnownFunction(WellKnownFunctionKind::NodeBindings)
193        }
194        "express" => JsValue::WellKnownFunction(WellKnownFunctionKind::NodeExpress),
195        "strong-globalize" => {
196            JsValue::WellKnownFunction(WellKnownFunctionKind::NodeStrongGlobalize)
197        }
198        "resolve-from" => JsValue::WellKnownFunction(WellKnownFunctionKind::NodeResolveFrom),
199        "@grpc/proto-loader" => JsValue::WellKnownObject(WellKnownObjectKind::NodeProtobufLoader),
200        _ => return None,
201    })
202}