Skip to main content

turbopack/module_options/
rule_condition.rs

1use std::{
2    iter,
3    mem::{replace, take},
4};
5
6use anyhow::Result;
7use bincode::{Decode, Encode};
8use either::Either;
9use smallvec::SmallVec;
10use turbo_esregex::EsRegex;
11use turbo_tasks::{NonLocalValue, ReadRef, ResolvedVc, trace::TraceRawVcs};
12use turbo_tasks_fs::{FileContent, FileSystemPath, glob::Glob};
13use turbopack_core::{
14    asset::Asset, reference_type::ReferenceType, source::Source, virtual_source::VirtualSource,
15};
16
17#[derive(Debug, Clone, TraceRawVcs, PartialEq, Eq, NonLocalValue, Encode, Decode)]
18pub enum RuleCondition {
19    All(Vec<RuleCondition>),
20    Any(Vec<RuleCondition>),
21    Not(Box<RuleCondition>),
22    True,
23    False,
24    ReferenceType(ReferenceType),
25    ResourceIsVirtualSource,
26    ResourcePathEquals(FileSystemPath),
27    ResourcePathHasNoExtension,
28    ResourcePathEndsWith(String),
29    ResourcePathInDirectory(String),
30    ResourcePathInExactDirectory(FileSystemPath),
31    ContentTypeStartsWith(String),
32    ContentTypeEmpty,
33    ResourcePathEsRegex(#[turbo_tasks(trace_ignore)] ReadRef<EsRegex>),
34    ResourceContentEsRegex(#[turbo_tasks(trace_ignore)] ReadRef<EsRegex>),
35    /// For paths that are within the same filesystem as the `base`, it need to
36    /// match the relative path from base to resource. This includes `./` or
37    /// `../` prefix. For paths in a different filesystem, it need to match
38    /// the resource path in that filesystem without any prefix. This means
39    /// any glob starting with `./` or `../` will only match paths in the
40    /// project. Globs starting with `**` can match any path.
41    ResourcePathGlob {
42        base: FileSystemPath,
43        #[turbo_tasks(trace_ignore)]
44        glob: ReadRef<Glob>,
45    },
46    ResourceBasePathGlob(#[turbo_tasks(trace_ignore)] ReadRef<Glob>),
47    ResourceQueryContains(String),
48    ResourceQueryEquals(String),
49    ResourceQueryEsRegex(#[turbo_tasks(trace_ignore)] ReadRef<EsRegex>),
50    ContentTypeGlob(#[turbo_tasks(trace_ignore)] ReadRef<Glob>),
51    ContentTypeEsRegex(#[turbo_tasks(trace_ignore)] ReadRef<EsRegex>),
52}
53
54impl RuleCondition {
55    pub fn all(conditions: Vec<RuleCondition>) -> RuleCondition {
56        RuleCondition::All(conditions)
57    }
58
59    pub fn any(conditions: Vec<RuleCondition>) -> RuleCondition {
60        RuleCondition::Any(conditions)
61    }
62
63    #[allow(clippy::should_implement_trait)]
64    pub fn not(condition: RuleCondition) -> RuleCondition {
65        RuleCondition::Not(Box::new(condition))
66    }
67
68    /// Slightly optimize a `RuleCondition` by flattening nested `Any`, `All`, or `Not` variants.
69    ///
70    /// Does not apply general re-ordering of rules (which may also be a valid optimization using a
71    /// cost heuristic), but does flatten constant `True` and `False` conditions, potentially
72    /// skipping other rules.
73    pub fn flatten(&mut self) {
74        match self {
75            RuleCondition::Any(conds) => {
76                // fast path: flatten children in-place and avoid constructing an additional vec
77                let mut needs_flattening = false;
78                for c in conds.iter_mut() {
79                    c.flatten();
80                    if *c == RuleCondition::True {
81                        // short-circuit: all conditions are side-effect free
82                        *self = RuleCondition::True;
83                        return;
84                    }
85                    needs_flattening = needs_flattening
86                        || matches!(c, RuleCondition::Any(_) | RuleCondition::False);
87                }
88
89                if needs_flattening {
90                    *conds = take(conds)
91                        .into_iter()
92                        .flat_map(|c| match c {
93                            RuleCondition::Any(nested) => {
94                                debug_assert!(!nested.is_empty(), "empty Any should be False");
95                                Either::Left(nested.into_iter())
96                            }
97                            RuleCondition::False => Either::Right(Either::Left(iter::empty())),
98                            c => Either::Right(Either::Right(iter::once(c))),
99                        })
100                        .collect();
101                }
102
103                match conds.len() {
104                    0 => *self = RuleCondition::False,
105                    1 => *self = take(conds).into_iter().next().unwrap(),
106                    _ => {}
107                }
108            }
109            RuleCondition::All(conds) => {
110                // fast path: flatten children in-place and avoid constructing an additional vec
111                let mut needs_flattening = false;
112                for c in conds.iter_mut() {
113                    c.flatten();
114                    if *c == RuleCondition::False {
115                        // short-circuit: all conditions are side-effect free
116                        *self = RuleCondition::False;
117                        return;
118                    }
119                    needs_flattening = needs_flattening
120                        || matches!(c, RuleCondition::All(_) | RuleCondition::True);
121                }
122
123                if needs_flattening {
124                    *conds = take(conds)
125                        .into_iter()
126                        .flat_map(|c| match c {
127                            RuleCondition::All(nested) => {
128                                debug_assert!(!nested.is_empty(), "empty All should be True");
129                                Either::Left(nested.into_iter())
130                            }
131                            RuleCondition::True => Either::Right(Either::Left(iter::empty())),
132                            c => Either::Right(Either::Right(iter::once(c))),
133                        })
134                        .collect();
135                }
136
137                match conds.len() {
138                    0 => *self = RuleCondition::True,
139                    1 => *self = take(conds).into_iter().next().unwrap(),
140                    _ => {}
141                }
142            }
143            RuleCondition::Not(cond) => {
144                match &mut **cond {
145                    // nested `Not`s negate each other
146                    RuleCondition::Not(inner) => {
147                        let inner = &mut **inner;
148                        inner.flatten();
149                        // Use `replace` with a dummy condition instead of `take` since
150                        // `RuleCondition` doesn't implement `Default`.
151                        *self = replace(inner, RuleCondition::False)
152                    }
153                    RuleCondition::True => *self = RuleCondition::False,
154                    RuleCondition::False => *self = RuleCondition::True,
155                    other => other.flatten(),
156                }
157            }
158            _ => {}
159        }
160    }
161
162    pub async fn matches(
163        &self,
164        source: ResolvedVc<Box<dyn Source>>,
165        path: &FileSystemPath,
166        reference_type: &ReferenceType,
167    ) -> Result<bool> {
168        enum Op<'a> {
169            All(&'a [RuleCondition]), // Remaining conditions in an All
170            Any(&'a [RuleCondition]), // Remaining conditions in an Any
171            Not,                      // Inverts the previous condition
172        }
173
174        // Evaluates the condition returning the result and possibly pushing additional operations
175        // onto the stack as a kind of continuation.
176        async fn process_condition<'a, const SZ: usize>(
177            source: ResolvedVc<Box<dyn Source + 'static>>,
178            path: &FileSystemPath,
179            reference_type: &ReferenceType,
180            stack: &mut SmallVec<[Op<'a>; SZ]>,
181            mut cond: &'a RuleCondition,
182        ) -> Result<bool, anyhow::Error> {
183            // Use a loop to avoid recursion and unnecessary stack operations.
184            loop {
185                match cond {
186                    RuleCondition::All(conditions) => {
187                        if conditions.is_empty() {
188                            return Ok(true);
189                        } else {
190                            if conditions.len() > 1 {
191                                stack.push(Op::All(&conditions[1..]));
192                            }
193                            cond = &conditions[0];
194                            // jump directly to the next condition, no need to deal with
195                            // the stack.
196                            continue;
197                        }
198                    }
199                    RuleCondition::Any(conditions) => {
200                        if conditions.is_empty() {
201                            return Ok(false);
202                        } else {
203                            if conditions.len() > 1 {
204                                stack.push(Op::Any(&conditions[1..]));
205                            }
206                            cond = &conditions[0];
207                            continue;
208                        }
209                    }
210                    RuleCondition::Not(inner) => {
211                        stack.push(Op::Not);
212                        cond = inner.as_ref();
213                        continue;
214                    }
215                    RuleCondition::True => {
216                        return Ok(true);
217                    }
218                    RuleCondition::False => {
219                        return Ok(false);
220                    }
221                    RuleCondition::ReferenceType(condition_ty) => {
222                        return Ok(condition_ty.includes(reference_type));
223                    }
224                    RuleCondition::ResourceIsVirtualSource => {
225                        return Ok(ResolvedVc::try_downcast_type::<VirtualSource>(source).is_some());
226                    }
227                    RuleCondition::ResourcePathEquals(other) => {
228                        return Ok(path == other);
229                    }
230                    RuleCondition::ResourcePathEndsWith(end) => {
231                        return Ok(path.path.ends_with(end));
232                    }
233                    RuleCondition::ResourcePathHasNoExtension => {
234                        return Ok(if let Some(i) = path.path.rfind('.') {
235                            if let Some(j) = path.path.rfind('/') {
236                                j > i
237                            } else {
238                                false
239                            }
240                        } else {
241                            true
242                        });
243                    }
244                    RuleCondition::ResourcePathInDirectory(dir) => {
245                        return Ok(path.path.starts_with(&format!("{dir}/"))
246                            || path.path.contains(&format!("/{dir}/")));
247                    }
248                    RuleCondition::ResourcePathInExactDirectory(parent_path) => {
249                        return Ok(path.is_inside_ref(parent_path));
250                    }
251                    RuleCondition::ContentTypeStartsWith(start) => {
252                        let content_type = &source.ident().await?.content_type;
253                        return Ok(content_type
254                            .as_ref()
255                            .is_some_and(|ct| ct.starts_with(start.as_str())));
256                    }
257                    RuleCondition::ContentTypeEmpty => {
258                        return Ok(source.ident().await?.content_type.is_none());
259                    }
260                    RuleCondition::ResourcePathGlob { glob, base } => {
261                        return Ok(if let Some(rel_path) = base.get_relative_path_to(path) {
262                            glob.matches(&rel_path)
263                        } else {
264                            glob.matches(&path.path)
265                        });
266                    }
267                    RuleCondition::ResourceBasePathGlob(glob) => {
268                        let basename = path
269                            .path
270                            .rsplit_once('/')
271                            .map_or(path.path.as_str(), |(_, b)| b);
272                        return Ok(glob.matches(basename));
273                    }
274                    RuleCondition::ResourcePathEsRegex(regex) => {
275                        return Ok(regex.is_match(&path.path));
276                    }
277                    RuleCondition::ResourceContentEsRegex(regex) => {
278                        let content = source.content().file_content().await?;
279                        match &*content {
280                            FileContent::Content(file_content) => {
281                                return Ok(regex.is_match(&file_content.content().to_str()?));
282                            }
283                            FileContent::NotFound => return Ok(false),
284                        }
285                    }
286                    RuleCondition::ResourceQueryContains(query) => {
287                        let ident = source.ident().await?;
288                        return Ok(ident.query.contains(query));
289                    }
290                    RuleCondition::ResourceQueryEquals(query) => {
291                        let ident = source.ident().await?;
292                        return Ok(ident.query == *query);
293                    }
294                    RuleCondition::ResourceQueryEsRegex(regex) => {
295                        let ident = source.ident().await?;
296                        return Ok(regex.is_match(&ident.query));
297                    }
298                    RuleCondition::ContentTypeGlob(glob) => {
299                        let ident = source.ident().await?;
300                        return Ok(ident
301                            .content_type
302                            .as_ref()
303                            .is_some_and(|ct| glob.matches(ct)));
304                    }
305                    RuleCondition::ContentTypeEsRegex(regex) => {
306                        let ident = source.ident().await?;
307                        return Ok(ident
308                            .content_type
309                            .as_ref()
310                            .is_some_and(|ct| regex.is_match(ct)));
311                    }
312                }
313            }
314        }
315        // Allocate a small inline stack to avoid heap allocations in the common case where
316        // conditions are not deeply stacked.  Additionally we take care to avoid stack
317        // operations unless strictly necessary.
318        const EXPECTED_SIZE: usize = 8;
319        let mut stack = SmallVec::<[Op; EXPECTED_SIZE]>::with_capacity(EXPECTED_SIZE);
320        let mut result = process_condition(source, path, reference_type, &mut stack, self).await?;
321        while let Some(op) = stack.pop() {
322            match op {
323                Op::All(remaining) => {
324                    // Previous was true, keep going
325                    if result {
326                        if remaining.len() > 1 {
327                            stack.push(Op::All(&remaining[1..]));
328                        }
329                        result = process_condition(
330                            source,
331                            path,
332                            reference_type,
333                            &mut stack,
334                            &remaining[0],
335                        )
336                        .await?;
337                    }
338                }
339                Op::Any(remaining) => {
340                    // Previous was false, keep going
341                    if !result {
342                        if remaining.len() > 1 {
343                            stack.push(Op::Any(&remaining[1..]));
344                        }
345                        // If the stack didn't change, we can loop inline, but we would still need
346                        // to pop the item.  This might be faster since we would avoid the `match`
347                        // but overall, that is quite minor for an enum with 3 cases.
348                        result = process_condition(
349                            source,
350                            path,
351                            reference_type,
352                            &mut stack,
353                            &remaining[0],
354                        )
355                        .await?;
356                    }
357                }
358                Op::Not => {
359                    result = !result;
360                }
361            }
362        }
363        Ok(result)
364    }
365}
366
367#[cfg(test)]
368pub mod tests {
369    use turbo_tasks::Vc;
370    use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
371    use turbo_tasks_fs::{FileContent, FileSystem, VirtualFileSystem};
372    use turbopack_core::{asset::AssetContent, file_source::FileSource};
373
374    use super::*;
375
376    #[test]
377    fn flatten_any_with_single_child_collapses() {
378        let mut rc = RuleCondition::Any(vec![RuleCondition::True]);
379        rc.flatten();
380        assert_eq!(rc, RuleCondition::True);
381
382        let mut rc = RuleCondition::Any(vec![RuleCondition::ContentTypeEmpty]);
383        rc.flatten();
384        assert_eq!(rc, RuleCondition::ContentTypeEmpty);
385    }
386
387    #[test]
388    fn flatten_any_nested_and_false() {
389        let mut rc = RuleCondition::Any(vec![
390            RuleCondition::False,
391            RuleCondition::Any(vec![RuleCondition::ContentTypeEmpty, RuleCondition::False]),
392        ]);
393        rc.flatten();
394        assert_eq!(rc, RuleCondition::ContentTypeEmpty);
395    }
396
397    #[test]
398    fn flatten_any_short_circuits_on_true() {
399        let mut rc = RuleCondition::Any(vec![
400            RuleCondition::False,
401            RuleCondition::True,
402            RuleCondition::ContentTypeEmpty,
403        ]);
404        rc.flatten();
405        assert_eq!(rc, RuleCondition::True);
406    }
407
408    #[test]
409    fn flatten_any_empty_becomes_false() {
410        let mut rc = RuleCondition::Any(vec![]);
411        rc.flatten();
412        assert_eq!(rc, RuleCondition::False);
413    }
414
415    #[test]
416    fn flatten_all_with_single_child_collapses() {
417        let mut rc = RuleCondition::All(vec![RuleCondition::ContentTypeEmpty]);
418        rc.flatten();
419        assert_eq!(rc, RuleCondition::ContentTypeEmpty);
420
421        let mut rc = RuleCondition::All(vec![RuleCondition::True]);
422        rc.flatten();
423        assert_eq!(rc, RuleCondition::True);
424    }
425
426    #[test]
427    fn flatten_all_nested_and_true() {
428        let mut rc = RuleCondition::All(vec![
429            RuleCondition::True,
430            RuleCondition::All(vec![RuleCondition::ContentTypeEmpty, RuleCondition::True]),
431        ]);
432        rc.flatten();
433        assert_eq!(rc, RuleCondition::ContentTypeEmpty);
434    }
435
436    #[test]
437    fn flatten_all_short_circuits_on_false() {
438        let mut rc = RuleCondition::All(vec![
439            RuleCondition::True,
440            RuleCondition::False,
441            RuleCondition::ContentTypeEmpty,
442        ]);
443        rc.flatten();
444        assert_eq!(rc, RuleCondition::False);
445    }
446
447    #[test]
448    fn flatten_all_empty_becomes_true() {
449        let mut rc = RuleCondition::All(vec![]);
450        rc.flatten();
451        assert_eq!(rc, RuleCondition::True);
452    }
453
454    #[test]
455    fn flatten_not_of_not() {
456        let mut rc = RuleCondition::Not(Box::new(RuleCondition::Not(Box::new(
457            RuleCondition::All(vec![RuleCondition::ContentTypeEmpty]),
458        ))));
459        rc.flatten();
460        assert_eq!(rc, RuleCondition::ContentTypeEmpty);
461    }
462
463    #[test]
464    fn flatten_not_constants() {
465        let mut rc = RuleCondition::Not(Box::new(RuleCondition::True));
466        rc.flatten();
467        assert_eq!(rc, RuleCondition::False);
468
469        let mut rc = RuleCondition::Not(Box::new(RuleCondition::False));
470        rc.flatten();
471        assert_eq!(rc, RuleCondition::True);
472    }
473
474    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
475    async fn test_rule_condition_leaves() {
476        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
477            BackendOptions::default(),
478            noop_backing_storage(),
479        ));
480        tt.run_once(async { run_leaves_test().await })
481            .await
482            .unwrap();
483    }
484
485    #[turbo_tasks::function]
486    pub async fn run_leaves_test() -> Result<()> {
487        let fs = VirtualFileSystem::new();
488        let virtual_path = fs.root().await?.join("foo.js")?;
489        let virtual_source = Vc::upcast::<Box<dyn Source>>(VirtualSource::new(
490            virtual_path.clone(),
491            AssetContent::File(FileContent::NotFound.cell().to_resolved().await?).cell(),
492        ))
493        .to_resolved()
494        .await?;
495
496        let non_virtual_path = fs.root().await?.join("bar.js")?;
497        let non_virtual_source =
498            Vc::upcast::<Box<dyn Source>>(FileSource::new(non_virtual_path.clone()))
499                .to_resolved()
500                .await?;
501
502        {
503            let condition = RuleCondition::ReferenceType(ReferenceType::Runtime);
504            assert!(
505                condition
506                    .matches(virtual_source, &virtual_path, &ReferenceType::Runtime)
507                    .await
508                    .unwrap()
509            );
510            assert!(
511                !condition
512                    .matches(
513                        non_virtual_source,
514                        &non_virtual_path,
515                        &ReferenceType::Css(
516                            turbopack_core::reference_type::CssReferenceSubType::Compose
517                        )
518                    )
519                    .await
520                    .unwrap()
521            );
522        }
523
524        {
525            let condition = RuleCondition::ResourceIsVirtualSource;
526            assert!(
527                condition
528                    .matches(virtual_source, &virtual_path, &ReferenceType::Undefined)
529                    .await
530                    .unwrap()
531            );
532            assert!(
533                !condition
534                    .matches(
535                        non_virtual_source,
536                        &non_virtual_path,
537                        &ReferenceType::Undefined
538                    )
539                    .await
540                    .unwrap()
541            );
542        }
543        {
544            let condition = RuleCondition::ResourcePathEquals(virtual_path.clone());
545            assert!(
546                condition
547                    .matches(virtual_source, &virtual_path, &ReferenceType::Undefined)
548                    .await
549                    .unwrap()
550            );
551            assert!(
552                !condition
553                    .matches(
554                        non_virtual_source,
555                        &non_virtual_path,
556                        &ReferenceType::Undefined
557                    )
558                    .await
559                    .unwrap()
560            );
561        }
562        {
563            let condition = RuleCondition::ResourcePathHasNoExtension;
564            assert!(
565                condition
566                    .matches(
567                        virtual_source,
568                        &fs.root().await?.join("foo")?,
569                        &ReferenceType::Undefined
570                    )
571                    .await
572                    .unwrap()
573            );
574            assert!(
575                !condition
576                    .matches(
577                        non_virtual_source,
578                        &non_virtual_path,
579                        &ReferenceType::Undefined
580                    )
581                    .await
582                    .unwrap()
583            );
584        }
585        {
586            let condition = RuleCondition::ResourcePathEndsWith("foo.js".to_string());
587            assert!(
588                condition
589                    .matches(virtual_source, &virtual_path, &ReferenceType::Undefined)
590                    .await
591                    .unwrap()
592            );
593            assert!(
594                !condition
595                    .matches(
596                        non_virtual_source,
597                        &non_virtual_path,
598                        &ReferenceType::Undefined
599                    )
600                    .await
601                    .unwrap()
602            );
603        }
604        anyhow::Ok(())
605    }
606
607    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
608    async fn test_rule_condition_tree() {
609        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
610            BackendOptions::default(),
611            noop_backing_storage(),
612        ));
613        tt.run_once(async { run_rule_condition_tree_test().await })
614            .await
615            .unwrap();
616    }
617
618    #[turbo_tasks::function]
619    pub async fn run_rule_condition_tree_test() -> Result<()> {
620        let fs = VirtualFileSystem::new();
621        let virtual_path = fs.root().await?.join("foo.js")?;
622        let virtual_source = Vc::upcast::<Box<dyn Source>>(VirtualSource::new(
623            virtual_path.clone(),
624            AssetContent::File(FileContent::NotFound.cell().to_resolved().await?).cell(),
625        ))
626        .to_resolved()
627        .await?;
628
629        let non_virtual_path = fs.root().await?.join("bar.js")?;
630        let non_virtual_source =
631            Vc::upcast::<Box<dyn Source>>(FileSource::new(non_virtual_path.clone()))
632                .to_resolved()
633                .await?;
634
635        {
636            // not
637            let condition = RuleCondition::not(RuleCondition::ResourceIsVirtualSource);
638            assert!(
639                !condition
640                    .matches(virtual_source, &virtual_path, &ReferenceType::Undefined)
641                    .await
642                    .unwrap()
643            );
644            assert!(
645                condition
646                    .matches(
647                        non_virtual_source,
648                        &non_virtual_path,
649                        &ReferenceType::Undefined
650                    )
651                    .await
652                    .unwrap()
653            );
654        }
655        {
656            // any
657            // Only one of the conditions matches our virtual source
658            let condition = RuleCondition::any(vec![
659                RuleCondition::ResourcePathInDirectory("doesnt/exist".to_string()),
660                RuleCondition::ResourceIsVirtualSource,
661                RuleCondition::ResourcePathHasNoExtension,
662            ]);
663            assert!(
664                condition
665                    .matches(virtual_source, &virtual_path, &ReferenceType::Undefined)
666                    .await
667                    .unwrap()
668            );
669            assert!(
670                !condition
671                    .matches(
672                        non_virtual_source,
673                        &non_virtual_path,
674                        &ReferenceType::Undefined
675                    )
676                    .await
677                    .unwrap()
678            );
679        }
680        {
681            // all
682            // Only one of the conditions matches our virtual source
683            let condition = RuleCondition::all(vec![
684                RuleCondition::ResourcePathEndsWith("foo.js".to_string()),
685                RuleCondition::ResourceIsVirtualSource,
686                RuleCondition::ResourcePathEquals(virtual_path.clone()),
687            ]);
688            assert!(
689                condition
690                    .matches(virtual_source, &virtual_path, &ReferenceType::Undefined)
691                    .await
692                    .unwrap()
693            );
694            assert!(
695                !condition
696                    .matches(
697                        non_virtual_source,
698                        &non_virtual_path,
699                        &ReferenceType::Undefined
700                    )
701                    .await
702                    .unwrap()
703            );
704        }
705        {
706            // bigger tree
707
708            // Build a simple tree to cover our various composite conditions
709            let condition = RuleCondition::all(vec![
710                RuleCondition::ResourceIsVirtualSource,
711                RuleCondition::ResourcePathEquals(virtual_path.clone()),
712                RuleCondition::Not(Box::new(RuleCondition::ResourcePathHasNoExtension)),
713                RuleCondition::Any(vec![
714                    RuleCondition::ResourcePathEndsWith("foo.js".to_string()),
715                    RuleCondition::ContentTypeEmpty,
716                ]),
717            ]);
718            assert!(
719                condition
720                    .matches(virtual_source, &virtual_path, &ReferenceType::Undefined)
721                    .await
722                    .unwrap()
723            );
724            assert!(
725                !condition
726                    .matches(
727                        non_virtual_source,
728                        &non_virtual_path,
729                        &ReferenceType::Undefined
730                    )
731                    .await
732                    .unwrap()
733            );
734        }
735        anyhow::Ok(())
736    }
737}