Skip to main content

turbopack_core/
compile_time_info.rs

1use std::fmt::Display;
2
3use anyhow::Result;
4use bincode::{Decode, Encode};
5use indexmap::Equivalent;
6use num_bigint::BigInt;
7use rustc_hash::FxHashSet;
8use smallvec::{SmallVec, smallvec};
9use turbo_rcstr::{RcStr, rcstr};
10use turbo_tasks::{FxIndexMap, NonLocalValue, ResolvedVc, Vc, trace::TraceRawVcs};
11use turbo_tasks_fs::FileSystemPath;
12
13use crate::{environment::Environment, issue::IssueSeverity};
14
15#[macro_export]
16macro_rules! definable_name_map_pattern_internal {
17    ($name:ident) => {
18        [stringify!($name).into()]
19    };
20    ($name:ident typeof) => {
21        [stringify!($name).into(), $crate::compile_time_info::DefinableNameSegment::TypeOf]
22    };
23    // Entry point for non-recursive calls
24    ($name:ident . $($more:ident).+ typeof) => {
25        $crate::definable_name_map_pattern_internal!($($more).+ typeof, [stringify!($name).into()])
26    };
27    ($name:ident . $($more:ident).+) => {
28        $crate::definable_name_map_pattern_internal!($($more).+, [stringify!($name).into()])
29    };
30    // Pop first ident and push to end of array: (id, ..., [...]) => (..., [..., id])
31    ($name:ident, [$($array:expr),+]) => {
32        [$($array),+, stringify!($name).into()]
33    };
34    ($name:ident . $($more:ident).+, [$($array:expr),+]) => {
35        $crate::definable_name_map_pattern_internal!($($more).+, [$($array),+, stringify!($name).into()])
36    };
37    ($name:ident typeof, [$($array:expr),+]) => {
38        [$($array),+, stringify!($name).into(), $crate::compile_time_info::DefinableNameSegment::TypeOf]
39    };
40    ($name:ident . $($more:ident).+ typeof, [$($array:expr),+]) => {
41        $crate::definable_name_map_pattern_internal!($($more).+ typeof, [$($array),+, stringify!($name).into()])
42    };
43}
44
45// TODO stringify split map collect could be optimized with a marco
46#[macro_export]
47macro_rules! definable_name_map_internal {
48    // Allow spreading a map: free_var_references!(..xy.into_iter(), FOO = "bar")
49    ($map:ident, .. $value:expr) => {
50        for (key, value) in $value {
51            $map.insert(
52                key.into(),
53                value.into()
54            );
55        }
56    };
57    ($map:ident, .. $value:expr, $($more:tt)+) => {
58        $crate::definable_name_map_internal!($map, .. $value);
59        $crate::definable_name_map_internal!($map, $($more)+);
60    };
61    // Base case: a single entry
62    ($map:ident, typeof $($name:ident).+ = $value:expr $(,)?) => {
63        $map.insert(
64            $crate::definable_name_map_pattern_internal!($($name).+ typeof).into(),
65            $value.into()
66        );
67    };
68    ($map:ident, $($name:ident).+ = $value:expr $(,)?) => {
69        $map.insert(
70            $crate::definable_name_map_pattern_internal!($($name).+).into(),
71            $value.into()
72        );
73    };
74    // Recursion: split off first entry
75    ($map:ident, typeof $($name:ident).+ = $value:expr, $($more:tt)+) => {
76        $crate::definable_name_map_internal!($map, typeof $($name).+ = $value);
77        $crate::definable_name_map_internal!($map, $($more)+);
78    };
79    ($map:ident, $($name:ident).+ = $value:expr, $($more:tt)+) => {
80        $crate::definable_name_map_internal!($map, $($name).+ = $value);
81        $crate::definable_name_map_internal!($map, $($more)+);
82    };
83
84}
85
86#[macro_export]
87macro_rules! compile_time_defines {
88    ($($more:tt)+) => {
89        {
90            let mut map = $crate::__private::FxIndexMap::default();
91            $crate::definable_name_map_internal!(map, $($more)+);
92            $crate::compile_time_info::CompileTimeDefines(map)
93        }
94    };
95}
96
97#[macro_export]
98macro_rules! free_var_references {
99    ($($more:tt)+) => {
100        {
101            let mut map = $crate::__private::FxIndexMap::default();
102            $crate::definable_name_map_internal!(map, $($more)+);
103            $crate::compile_time_info::FreeVarReferences(map)
104        }
105    };
106}
107
108// TODO: replace with just a `serde_json::Value`
109// https://linear.app/vercel/issue/WEB-1641/compiletimedefinevalue-should-just-use-serde-jsonvalue
110#[turbo_tasks::value(shared)]
111#[derive(Debug, Clone, Hash)]
112pub enum CompileTimeDefineValue {
113    Null,
114    Bool(bool),
115    Number(
116        #[bincode(with = "turbo_bincode::serde_self_describing")]
117        #[turbo_tasks(trace_ignore)]
118        serde_json::Number,
119    ),
120    String(RcStr),
121    BigInt(
122        #[bincode(with_serde)]
123        #[turbo_tasks(trace_ignore)]
124        Box<BigInt>,
125    ),
126    Array(Vec<CompileTimeDefineValue>),
127    Object(Vec<(RcStr, CompileTimeDefineValue)>),
128    Undefined,
129    Evaluate(RcStr),
130    Regex(RcStr, RcStr),
131}
132
133impl From<bool> for CompileTimeDefineValue {
134    fn from(value: bool) -> Self {
135        Self::Bool(value)
136    }
137}
138
139impl From<RcStr> for CompileTimeDefineValue {
140    fn from(value: RcStr) -> Self {
141        Self::String(value)
142    }
143}
144
145impl From<String> for CompileTimeDefineValue {
146    fn from(value: String) -> Self {
147        Self::String(value.into())
148    }
149}
150
151impl From<&str> for CompileTimeDefineValue {
152    fn from(value: &str) -> Self {
153        Self::String(value.into())
154    }
155}
156
157impl From<serde_json::Value> for CompileTimeDefineValue {
158    fn from(value: serde_json::Value) -> Self {
159        match value {
160            serde_json::Value::Null => Self::Null,
161            serde_json::Value::Bool(b) => Self::Bool(b),
162            serde_json::Value::Number(n) => Self::Number(n),
163            serde_json::Value::String(s) => Self::String(s.into()),
164            serde_json::Value::Array(a) => Self::Array(a.into_iter().map(|i| i.into()).collect()),
165            serde_json::Value::Object(m) => {
166                Self::Object(m.into_iter().map(|(k, v)| (k.into(), v.into())).collect())
167            }
168        }
169    }
170}
171
172impl CompileTimeDefineValue {
173    pub fn display_ecmascript(&self) -> impl Display + '_ {
174        CompileTimeDefineValueDisplay { value: self }
175    }
176}
177
178struct CompileTimeDefineValueDisplay<'a> {
179    value: &'a CompileTimeDefineValue,
180}
181
182impl Display for CompileTimeDefineValueDisplay<'_> {
183    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184        match self.value {
185            CompileTimeDefineValue::Null => write!(f, "null"),
186            CompileTimeDefineValue::Bool(false) => write!(f, "false"),
187            CompileTimeDefineValue::Bool(true) => write!(f, "true"),
188            CompileTimeDefineValue::Number(v) => write!(f, "{}", serde_json::to_string(v).unwrap()),
189            CompileTimeDefineValue::String(v) => write!(f, "{}", serde_json::to_string(v).unwrap()),
190            CompileTimeDefineValue::BigInt(b) => write!(f, "{}n", b),
191            CompileTimeDefineValue::Array(a) => {
192                write!(f, "[")?;
193                for (i, v) in a.iter().enumerate() {
194                    if i > 0 {
195                        write!(f, ", ")?;
196                    }
197                    write!(f, "{}", v.display_ecmascript())?;
198                }
199                write!(f, "]")?;
200                Ok(())
201            }
202            CompileTimeDefineValue::Object(o) => {
203                write!(f, "{{")?;
204                for (i, (k, v)) in o.iter().enumerate() {
205                    if i > 0 {
206                        write!(f, ", ")?;
207                    }
208                    write!(
209                        f,
210                        "{}: {}",
211                        serde_json::to_string(k).unwrap(),
212                        v.display_ecmascript()
213                    )?;
214                }
215                write!(f, "}}")?;
216                Ok(())
217            }
218            CompileTimeDefineValue::Undefined => write!(f, "undefined"),
219            CompileTimeDefineValue::Evaluate(e) => write!(f, "{}", e),
220            CompileTimeDefineValue::Regex(regex, flags) => write!(f, "/{}/{}", regex, flags),
221        }
222    }
223}
224
225#[turbo_tasks::value]
226#[derive(Debug, Clone, PartialOrd, Ord)]
227pub enum DefinableNameSegment {
228    Name(RcStr),
229    Call(RcStr),
230    TypeOf,
231}
232
233// Hash can't be derived because DefinableNameSegmentRef must have a matching
234// Hash implementation for Equivalent lookups, and derived discriminants are
235// not guaranteed to match between different enum types.
236// Also, we must use s.as_str().hash() instead of s.hash() because RcStr's Hash
237// implementation for prehashed strings is not compatible with str's Hash.
238impl std::hash::Hash for DefinableNameSegment {
239    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
240        match self {
241            Self::Name(s) => {
242                0u8.hash(state);
243                s.as_str().hash(state);
244            }
245            Self::Call(s) => {
246                1u8.hash(state);
247                s.as_str().hash(state);
248            }
249            Self::TypeOf => {
250                2u8.hash(state);
251            }
252        }
253    }
254}
255
256impl From<RcStr> for DefinableNameSegment {
257    fn from(value: RcStr) -> Self {
258        DefinableNameSegment::Name(value)
259    }
260}
261
262impl From<&str> for DefinableNameSegment {
263    fn from(value: &str) -> Self {
264        DefinableNameSegment::Name(value.into())
265    }
266}
267
268impl From<String> for DefinableNameSegment {
269    fn from(value: String) -> Self {
270        DefinableNameSegment::Name(value.into())
271    }
272}
273
274#[derive(Copy, Clone, PartialEq, Eq)]
275pub enum DefinableNameSegmentRef<'a> {
276    Name(&'a str),
277    Call(&'a str),
278    TypeOf,
279}
280
281// Hash can't be derived because it must match DefinableNameSegment's Hash
282// implementation for Equivalent lookups, and derived discriminants are
283// not guaranteed to match between different enum types.
284impl std::hash::Hash for DefinableNameSegmentRef<'_> {
285    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
286        match self {
287            Self::Name(s) => {
288                0u8.hash(state);
289                s.hash(state);
290            }
291            Self::Call(s) => {
292                1u8.hash(state);
293                s.hash(state);
294            }
295            Self::TypeOf => {
296                2u8.hash(state);
297            }
298        }
299    }
300}
301
302impl Equivalent<DefinableNameSegment> for DefinableNameSegmentRef<'_> {
303    fn equivalent(&self, key: &DefinableNameSegment) -> bool {
304        match (self, key) {
305            (DefinableNameSegmentRef::Name(a), DefinableNameSegment::Name(b)) => **a == *b.as_str(),
306            (DefinableNameSegmentRef::Call(a), DefinableNameSegment::Call(b)) => **a == *b.as_str(),
307            (DefinableNameSegmentRef::TypeOf, DefinableNameSegment::TypeOf) => true,
308            _ => false,
309        }
310    }
311}
312
313#[derive(Clone, PartialEq, Eq)]
314pub struct DefinableNameSegmentRefs<'a>(pub SmallVec<[DefinableNameSegmentRef<'a>; 4]>);
315
316// Hash can't be derived because it must match Vec<DefinableNameSegment>'s Hash.
317impl std::hash::Hash for DefinableNameSegmentRefs<'_> {
318    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
319        self.0.len().hash(state);
320        for segment in &self.0 {
321            segment.hash(state);
322        }
323    }
324}
325
326impl Equivalent<Vec<DefinableNameSegment>> for DefinableNameSegmentRefs<'_> {
327    fn equivalent(&self, key: &Vec<DefinableNameSegment>) -> bool {
328        if self.0.len() != key.len() {
329            return false;
330        }
331        for (a, b) in self.0.iter().zip(key.iter()) {
332            if !a.equivalent(b) {
333                return false;
334            }
335        }
336        true
337    }
338}
339
340#[turbo_tasks::value(transparent, cell = "keyed")]
341#[derive(Debug, Clone)]
342pub struct CompileTimeDefines(
343    #[bincode(with = "turbo_bincode::indexmap")]
344    pub  FxIndexMap<Vec<DefinableNameSegment>, CompileTimeDefineValue>,
345);
346
347impl IntoIterator for CompileTimeDefines {
348    type Item = (Vec<DefinableNameSegment>, CompileTimeDefineValue);
349    type IntoIter = indexmap::map::IntoIter<Vec<DefinableNameSegment>, CompileTimeDefineValue>;
350
351    fn into_iter(self) -> Self::IntoIter {
352        self.0.into_iter()
353    }
354}
355
356#[turbo_tasks::value_impl]
357impl CompileTimeDefines {
358    #[turbo_tasks::function]
359    pub fn empty() -> Vc<Self> {
360        Vc::cell(FxIndexMap::default())
361    }
362
363    #[turbo_tasks::function]
364    pub async fn read_process_env(&self, key: RcStr) -> Result<Vc<Option<RcStr>>> {
365        let key = DefinableNameSegmentRefs(smallvec![
366            DefinableNameSegmentRef::Name("process"),
367            DefinableNameSegmentRef::Name("env"),
368            DefinableNameSegmentRef::Name(&key),
369        ]);
370        Ok(Vc::cell(match self.0.get(&key) {
371            Some(CompileTimeDefineValue::String(s)) => Some(s.clone()),
372            _ => None,
373        }))
374    }
375}
376
377#[derive(Debug, Clone, Copy, PartialEq, Eq, TraceRawVcs, NonLocalValue, Encode, Decode)]
378pub enum InputRelativeConstant {
379    // The project relative directory name of the source file
380    DirName,
381    // The project relative file name of the source file.
382    FileName,
383}
384
385#[derive(Debug, Clone, TraceRawVcs, NonLocalValue, Encode, Decode, PartialEq, Eq)]
386pub enum FreeVarReference {
387    EcmaScriptModule {
388        request: RcStr,
389        lookup_path: Option<FileSystemPath>,
390        export: Option<RcStr>,
391    },
392    Ident(RcStr),
393    Member(RcStr, RcStr),
394    Value(CompileTimeDefineValue),
395    InputRelative(InputRelativeConstant),
396    // Report the replacement of this free var with the given severity and message, and
397    // potentially replace with the `inner` value.
398    ReportUsage {
399        message: RcStr,
400        severity: IssueSeverity,
401        inner: Option<Box<FreeVarReference>>,
402    },
403}
404
405impl From<bool> for FreeVarReference {
406    fn from(value: bool) -> Self {
407        Self::Value(value.into())
408    }
409}
410
411impl From<String> for FreeVarReference {
412    fn from(value: String) -> Self {
413        Self::Value(value.into())
414    }
415}
416impl From<RcStr> for FreeVarReference {
417    fn from(value: RcStr) -> Self {
418        Self::Value(value.into())
419    }
420}
421
422impl From<&str> for FreeVarReference {
423    fn from(value: &str) -> Self {
424        Self::Value(value.into())
425    }
426}
427
428impl From<CompileTimeDefineValue> for FreeVarReference {
429    fn from(value: CompileTimeDefineValue) -> Self {
430        Self::Value(value)
431    }
432}
433
434#[turbo_tasks::value(transparent, cell = "keyed")]
435#[derive(Debug, Clone)]
436pub struct FreeVarReferences(
437    #[bincode(with = "turbo_bincode::indexmap")]
438    pub  FxIndexMap<Vec<DefinableNameSegment>, FreeVarReference>,
439);
440
441#[turbo_tasks::value(transparent, cell = "keyed")]
442pub struct FreeVarReferencesMembers(FxHashSet<RcStr>);
443
444#[turbo_tasks::value_impl]
445impl FreeVarReferences {
446    #[turbo_tasks::function]
447    pub fn empty() -> Vc<Self> {
448        Vc::cell(FxIndexMap::default())
449    }
450
451    #[turbo_tasks::function]
452    pub fn members(&self) -> Vc<FreeVarReferencesMembers> {
453        let mut members = FxHashSet::default();
454        for (key, _) in self.0.iter() {
455            if let Some(name) = key
456                .iter()
457                .rfind(|segment| {
458                    matches!(
459                        segment,
460                        DefinableNameSegment::Name(_) | DefinableNameSegment::Call(_)
461                    )
462                })
463                .and_then(|segment| match segment {
464                    DefinableNameSegment::Name(n) | DefinableNameSegment::Call(n) => Some(n),
465                    _ => None,
466                })
467            {
468                members.insert(name.clone());
469            }
470        }
471        Vc::cell(members)
472    }
473}
474
475impl IntoIterator for FreeVarReferences {
476    type Item = (Vec<DefinableNameSegment>, FreeVarReference);
477    type IntoIter = indexmap::map::IntoIter<Vec<DefinableNameSegment>, FreeVarReference>;
478
479    fn into_iter(self) -> Self::IntoIter {
480        self.0.into_iter()
481    }
482}
483
484#[turbo_tasks::value(shared)]
485#[derive(Debug, Clone)]
486pub struct CompileTimeInfo {
487    pub environment: ResolvedVc<Environment>,
488    pub defines: ResolvedVc<CompileTimeDefines>,
489    pub free_var_references: ResolvedVc<FreeVarReferences>,
490    pub hot_module_replacement_enabled: bool,
491    pub import_meta_env_base_url: RcStr,
492}
493
494impl CompileTimeInfo {
495    pub fn builder(environment: ResolvedVc<Environment>) -> CompileTimeInfoBuilder {
496        CompileTimeInfoBuilder {
497            environment,
498            defines: None,
499            free_var_references: None,
500            hot_module_replacement_enabled: false,
501            import_meta_env_base_url: rcstr!("/"),
502        }
503    }
504}
505
506#[turbo_tasks::value_impl]
507impl CompileTimeInfo {
508    #[turbo_tasks::function]
509    pub async fn new(environment: ResolvedVc<Environment>) -> Result<Vc<Self>> {
510        Ok(CompileTimeInfo {
511            environment,
512            defines: CompileTimeDefines::empty().to_resolved().await?,
513            free_var_references: FreeVarReferences::empty().to_resolved().await?,
514            hot_module_replacement_enabled: false,
515            import_meta_env_base_url: rcstr!("/"),
516        }
517        .cell())
518    }
519
520    #[turbo_tasks::function]
521    pub fn environment(&self) -> Vc<Environment> {
522        *self.environment
523    }
524}
525
526pub struct CompileTimeInfoBuilder {
527    environment: ResolvedVc<Environment>,
528    defines: Option<ResolvedVc<CompileTimeDefines>>,
529    free_var_references: Option<ResolvedVc<FreeVarReferences>>,
530    hot_module_replacement_enabled: bool,
531    import_meta_env_base_url: RcStr,
532}
533
534impl CompileTimeInfoBuilder {
535    pub fn defines(mut self, defines: ResolvedVc<CompileTimeDefines>) -> Self {
536        self.defines = Some(defines);
537        self
538    }
539
540    pub fn free_var_references(
541        mut self,
542        free_var_references: ResolvedVc<FreeVarReferences>,
543    ) -> Self {
544        self.free_var_references = Some(free_var_references);
545        self
546    }
547
548    pub fn hot_module_replacement_enabled(mut self, enabled: bool) -> Self {
549        self.hot_module_replacement_enabled = enabled;
550        self
551    }
552
553    pub fn import_meta_env_base_url(mut self, base_url: RcStr) -> Self {
554        self.import_meta_env_base_url = base_url;
555        self
556    }
557
558    pub async fn build(self) -> Result<CompileTimeInfo> {
559        Ok(CompileTimeInfo {
560            environment: self.environment,
561            defines: match self.defines {
562                Some(defines) => defines,
563                None => CompileTimeDefines::empty().to_resolved().await?,
564            },
565            free_var_references: match self.free_var_references {
566                Some(free_var_references) => free_var_references,
567                None => FreeVarReferences::empty().to_resolved().await?,
568            },
569            hot_module_replacement_enabled: self.hot_module_replacement_enabled,
570            import_meta_env_base_url: self.import_meta_env_base_url,
571        })
572    }
573
574    pub async fn cell(self) -> Result<Vc<CompileTimeInfo>> {
575        Ok(self.build().await?.cell())
576    }
577}
578
579#[cfg(test)]
580mod test {
581    use std::{
582        collections::hash_map::DefaultHasher,
583        hash::{Hash, Hasher},
584    };
585
586    use smallvec::smallvec;
587    use turbo_rcstr::rcstr;
588    use turbo_tasks::FxIndexMap;
589
590    use crate::compile_time_info::{
591        CompileTimeDefineValue, DefinableNameSegment, DefinableNameSegmentRef,
592        DefinableNameSegmentRefs, FreeVarReference, FreeVarReferences,
593    };
594
595    fn hash_value<T: Hash>(value: &T) -> u64 {
596        let mut hasher = DefaultHasher::new();
597        value.hash(&mut hasher);
598        hasher.finish()
599    }
600
601    #[test]
602    fn hash_segment_name_matches() {
603        let segment = DefinableNameSegment::Name(rcstr!("process"));
604        let segment_ref = DefinableNameSegmentRef::Name("process");
605        assert_eq!(
606            hash_value(&segment),
607            hash_value(&segment_ref),
608            "DefinableNameSegment::Name and DefinableNameSegmentRef::Name must have matching Hash"
609        );
610    }
611
612    #[test]
613    fn hash_segment_call_matches() {
614        let segment = DefinableNameSegment::Call(rcstr!("foo"));
615        let segment_ref = DefinableNameSegmentRef::Call("foo");
616        assert_eq!(
617            hash_value(&segment),
618            hash_value(&segment_ref),
619            "DefinableNameSegment::Call and DefinableNameSegmentRef::Call must have matching Hash"
620        );
621    }
622
623    #[test]
624    fn hash_segment_typeof_matches() {
625        let segment = DefinableNameSegment::TypeOf;
626        let segment_ref = DefinableNameSegmentRef::TypeOf;
627        assert_eq!(
628            hash_value(&segment),
629            hash_value(&segment_ref),
630            "DefinableNameSegment::TypeOf and DefinableNameSegmentRef::TypeOf must have matching \
631             Hash"
632        );
633    }
634
635    #[test]
636    fn hash_segments_vec_matches() {
637        let segments: Vec<DefinableNameSegment> = vec![
638            DefinableNameSegment::Name(rcstr!("process")),
639            DefinableNameSegment::Name(rcstr!("env")),
640            DefinableNameSegment::Name(rcstr!("NODE_ENV")),
641        ];
642        let segments_ref = DefinableNameSegmentRefs(smallvec![
643            DefinableNameSegmentRef::Name("process"),
644            DefinableNameSegmentRef::Name("env"),
645            DefinableNameSegmentRef::Name("NODE_ENV"),
646        ]);
647        assert_eq!(
648            hash_value(&segments),
649            hash_value(&segments_ref),
650            "Vec<DefinableNameSegment> and DefinableNameSegmentRefs must have matching Hash"
651        );
652    }
653
654    #[test]
655    fn hash_segments_with_typeof_matches() {
656        let segments: Vec<DefinableNameSegment> = vec![
657            DefinableNameSegment::Name(rcstr!("process")),
658            DefinableNameSegment::TypeOf,
659        ];
660        let segments_ref = DefinableNameSegmentRefs(smallvec![
661            DefinableNameSegmentRef::Name("process"),
662            DefinableNameSegmentRef::TypeOf,
663        ]);
664        assert_eq!(
665            hash_value(&segments),
666            hash_value(&segments_ref),
667            "Vec<DefinableNameSegment> with TypeOf and DefinableNameSegmentRefs must have \
668             matching Hash"
669        );
670    }
671
672    #[test]
673    fn hash_segments_with_call_matches() {
674        let segments: Vec<DefinableNameSegment> = vec![
675            DefinableNameSegment::Name(rcstr!("foo")),
676            DefinableNameSegment::Call(rcstr!("bar")),
677        ];
678        let segments_ref = DefinableNameSegmentRefs(smallvec![
679            DefinableNameSegmentRef::Name("foo"),
680            DefinableNameSegmentRef::Call("bar"),
681        ]);
682        assert_eq!(
683            hash_value(&segments),
684            hash_value(&segments_ref),
685            "Vec<DefinableNameSegment> with Call and DefinableNameSegmentRefs must have matching \
686             Hash"
687        );
688    }
689
690    #[test]
691    fn macro_parser() {
692        assert_eq!(
693            free_var_references!(
694                FOO = "bar",
695                FOO = false,
696                Buffer = FreeVarReference::EcmaScriptModule {
697                    request: rcstr!("node:buffer"),
698                    lookup_path: None,
699                    export: Some(rcstr!("Buffer")),
700                },
701            ),
702            FreeVarReferences(FxIndexMap::from_iter(vec![
703                (
704                    vec![rcstr!("FOO").into()],
705                    FreeVarReference::Value(rcstr!("bar").into())
706                ),
707                (
708                    vec![rcstr!("FOO").into()],
709                    FreeVarReference::Value(false.into())
710                ),
711                (
712                    vec![rcstr!("Buffer").into()],
713                    FreeVarReference::EcmaScriptModule {
714                        request: rcstr!("node:buffer"),
715                        lookup_path: None,
716                        export: Some(rcstr!("Buffer")),
717                    }
718                ),
719            ]))
720        );
721    }
722
723    #[test]
724    fn macro_parser_typeof() {
725        assert_eq!(
726            free_var_references!(
727                typeof x = "a",
728                typeof x.y = "b",
729                typeof x.y.z = "c"
730            ),
731            FreeVarReferences(FxIndexMap::from_iter(vec![
732                (
733                    vec![rcstr!("x").into(), DefinableNameSegment::TypeOf],
734                    FreeVarReference::Value(rcstr!("a").into())
735                ),
736                (
737                    vec![
738                        rcstr!("x").into(),
739                        rcstr!("y").into(),
740                        DefinableNameSegment::TypeOf
741                    ],
742                    FreeVarReference::Value(rcstr!("b").into())
743                ),
744                (
745                    vec![
746                        rcstr!("x").into(),
747                        rcstr!("y").into(),
748                        rcstr!("z").into(),
749                        DefinableNameSegment::TypeOf
750                    ],
751                    FreeVarReference::Value(rcstr!("b").into())
752                ),
753                (
754                    vec![
755                        rcstr!("x").into(),
756                        rcstr!("y").into(),
757                        rcstr!("z").into(),
758                        DefinableNameSegment::TypeOf
759                    ],
760                    FreeVarReference::Value(rcstr!("c").into())
761                )
762            ]))
763        );
764    }
765
766    #[test]
767    fn indexmap_lookup_with_equivalent() {
768        // Test that DefinableNameSegmentRefs can be used to look up Vec<DefinableNameSegment>
769        // in an IndexMap using the Equivalent trait
770        let mut map: FxIndexMap<Vec<DefinableNameSegment>, &str> = FxIndexMap::default();
771        map.insert(
772            vec![
773                DefinableNameSegment::Name(rcstr!("process")),
774                DefinableNameSegment::Name(rcstr!("env")),
775                DefinableNameSegment::Name(rcstr!("NODE_ENV")),
776            ],
777            "production",
778        );
779        map.insert(
780            vec![
781                DefinableNameSegment::Name(rcstr!("process")),
782                DefinableNameSegment::Name(rcstr!("turbopack")),
783            ],
784            "true",
785        );
786
787        // Lookup using DefinableNameSegmentRefs
788        let key = DefinableNameSegmentRefs(smallvec![
789            DefinableNameSegmentRef::Name("process"),
790            DefinableNameSegmentRef::Name("env"),
791            DefinableNameSegmentRef::Name("NODE_ENV"),
792        ]);
793        assert_eq!(
794            map.get(&key),
795            Some(&"production"),
796            "IndexMap lookup with Equivalent trait should work"
797        );
798
799        let key2 = DefinableNameSegmentRefs(smallvec![
800            DefinableNameSegmentRef::Name("process"),
801            DefinableNameSegmentRef::Name("turbopack"),
802        ]);
803        assert_eq!(
804            map.get(&key2),
805            Some(&"true"),
806            "IndexMap lookup with Equivalent trait should work for shorter keys"
807        );
808
809        let key3 = DefinableNameSegmentRefs(smallvec![
810            DefinableNameSegmentRef::Name("process"),
811            DefinableNameSegmentRef::Name("nonexistent"),
812        ]);
813        assert_eq!(
814            map.get(&key3),
815            None,
816            "IndexMap lookup should return None for nonexistent keys"
817        );
818    }
819
820    #[test]
821    fn fxhashset_rcstr_lookup_with_str() {
822        // Test that &str can be used to look up RcStr in a FxHashSet
823        // This is used by FreeVarReferencesMembers::contains_key
824        use rustc_hash::FxHashSet;
825
826        let mut set: FxHashSet<turbo_rcstr::RcStr> = FxHashSet::default();
827        set.insert(rcstr!("process"));
828        set.insert(rcstr!("env"));
829        set.insert(rcstr!("NODE_ENV"));
830
831        // This tests whether &str can look up RcStr in the set
832        // It requires RcStr: Borrow<str> AND hash(&str) == hash(&RcStr)
833        assert!(
834            set.contains("process"),
835            "FxHashSet<RcStr> lookup with &str should work for 'process'"
836        );
837        assert!(
838            set.contains("env"),
839            "FxHashSet<RcStr> lookup with &str should work for 'env'"
840        );
841        assert!(
842            set.contains("NODE_ENV"),
843            "FxHashSet<RcStr> lookup with &str should work for 'NODE_ENV'"
844        );
845        assert!(
846            !set.contains("nonexistent"),
847            "FxHashSet<RcStr> lookup with &str should return false for nonexistent keys"
848        );
849    }
850
851    #[test]
852    fn compile_time_define_value_display_ecmascript() {
853        fn t(v: CompileTimeDefineValue) -> String {
854            v.display_ecmascript().to_string()
855        }
856
857        use serde_json::Number;
858        assert_eq!(t(CompileTimeDefineValue::Null), "null");
859        assert_eq!(t(CompileTimeDefineValue::Bool(false)), "false");
860        assert_eq!(t(CompileTimeDefineValue::Bool(true)), "true");
861        assert_eq!(
862            t(CompileTimeDefineValue::Number(
863                Number::from_f64(42.1).unwrap()
864            )),
865            "42.1"
866        );
867        assert_eq!(
868            t(CompileTimeDefineValue::String(rcstr!(r#"a"b"#))),
869            r#""a\"b""#
870        );
871        assert_eq!(
872            t(CompileTimeDefineValue::String(rcstr!(r#"a'b"#))),
873            r#""a'b""#
874        );
875        assert_eq!(
876            t(CompileTimeDefineValue::BigInt(Box::new(42.into()))),
877            "42n"
878        );
879        assert_eq!(t(CompileTimeDefineValue::Undefined), "undefined");
880        assert_eq!(
881            t(CompileTimeDefineValue::Evaluate(rcstr!("someExpression"))),
882            "someExpression"
883        );
884        assert_eq!(
885            t(CompileTimeDefineValue::Regex(
886                rcstr!("pattern"),
887                rcstr!("flags")
888            )),
889            "/pattern/flags"
890        );
891
892        assert_eq!(
893            t(CompileTimeDefineValue::Array(vec![
894                CompileTimeDefineValue::Bool(true),
895                CompileTimeDefineValue::Number(Number::from(42)),
896                CompileTimeDefineValue::String(rcstr!(r#"a"b"#)),
897            ])),
898            r#"[true, 42, "a\"b"]"#
899        );
900        assert_eq!(
901            t(CompileTimeDefineValue::Object(vec![
902                (rcstr!("foo"), CompileTimeDefineValue::Bool(true)),
903                (
904                    rcstr!("bar"),
905                    CompileTimeDefineValue::Number(Number::from(42))
906                ),
907                (
908                    rcstr!(r#"a"b"#),
909                    CompileTimeDefineValue::String(rcstr!(r#"c"d"#),)
910                ),
911            ])),
912            r#"{"foo": true, "bar": 42, "a\"b": "c\"d"}"#
913        );
914    }
915}