turbopack_core/resolve/
pattern.rs

1use std::{
2    collections::{VecDeque, hash_map::Entry},
3    mem::take,
4    sync::LazyLock,
5};
6
7use anyhow::Result;
8use regex::Regex;
9use rustc_hash::{FxHashMap, FxHashSet};
10use serde::{Deserialize, Serialize};
11use tracing::Instrument;
12use turbo_rcstr::{RcStr, rcstr};
13use turbo_tasks::{
14    NonLocalValue, TaskInput, ValueToString, Vc, debug::ValueDebugFormat, trace::TraceRawVcs,
15};
16use turbo_tasks_fs::{
17    FileSystemPath, LinkContent, LinkType, RawDirectoryContent, RawDirectoryEntry,
18};
19use turbo_unix_path::normalize_path;
20
21#[turbo_tasks::value]
22#[derive(Hash, Clone, Debug, Default)]
23pub enum Pattern {
24    Constant(RcStr),
25    #[default]
26    Dynamic,
27    DynamicNoSlash,
28    Alternatives(Vec<Pattern>),
29    Concatenation(Vec<Pattern>),
30}
31
32/// manually implement TaskInput to avoid recursion in the implementation of `resolve_input` in the
33/// derived implementation.  We can instead use the default implementation since `Pattern` contains
34/// no VCs.
35impl TaskInput for Pattern {
36    fn is_transient(&self) -> bool {
37        // We contain no vcs so they cannot be transient.
38        false
39    }
40}
41
42fn concatenation_push_or_merge_item(list: &mut Vec<Pattern>, pat: Pattern) {
43    if let Pattern::Constant(ref s) = pat
44        && let Some(Pattern::Constant(last)) = list.last_mut()
45    {
46        let mut buf = last.to_string();
47        buf.push_str(s);
48        *last = buf.into();
49        return;
50    }
51    list.push(pat);
52}
53
54fn concatenation_push_front_or_merge_item(list: &mut Vec<Pattern>, pat: Pattern) {
55    if let Pattern::Constant(s) = pat {
56        if let Some(Pattern::Constant(first)) = list.iter_mut().next() {
57            let mut buf = s.into_owned();
58            buf.push_str(first);
59
60            *first = buf.into();
61            return;
62        }
63        list.insert(0, Pattern::Constant(s));
64    } else {
65        list.insert(0, pat);
66    }
67}
68
69fn concatenation_extend_or_merge_items(
70    list: &mut Vec<Pattern>,
71    mut iter: impl Iterator<Item = Pattern>,
72) {
73    if let Some(first) = iter.next() {
74        concatenation_push_or_merge_item(list, first);
75        list.extend(iter);
76    }
77}
78
79fn longest_common_prefix<'a>(strings: &[&'a str]) -> &'a str {
80    if strings.is_empty() {
81        return "";
82    }
83    if let [single] = strings {
84        return single;
85    }
86    let first = strings[0];
87    let mut len = first.len();
88    for str in &strings[1..] {
89        len = std::cmp::min(
90            len,
91            // TODO these are Unicode Scalar Values, not graphemes
92            str.chars()
93                .zip(first.chars())
94                .take_while(|&(a, b)| a == b)
95                .count(),
96        );
97    }
98    &first[..len]
99}
100
101fn longest_common_suffix<'a>(strings: &[&'a str]) -> &'a str {
102    if strings.is_empty() {
103        return "";
104    }
105    let first = strings[0];
106    let mut len = first.len();
107    for str in &strings[1..] {
108        len = std::cmp::min(
109            len,
110            // TODO these are Unicode Scalar Values, not graphemes
111            str.chars()
112                .rev()
113                .zip(first.chars().rev())
114                .take_while(|&(a, b)| a == b)
115                .count(),
116        );
117    }
118    &first[(first.len() - len)..]
119}
120
121impl Pattern {
122    // TODO this should be removed in favor of pattern resolving
123    pub fn as_constant_string(&self) -> Option<&RcStr> {
124        match self {
125            Pattern::Constant(str) => Some(str),
126            _ => None,
127        }
128    }
129
130    /// Whether the pattern has any significant constant parts (everything except `/`).
131    /// E.g. `<dynamic>/<dynamic>` doesn't really have constant parts
132    pub fn has_constant_parts(&self) -> bool {
133        match self {
134            Pattern::Constant(str) => str != "/",
135            Pattern::Dynamic | Pattern::DynamicNoSlash => false,
136            Pattern::Alternatives(list) | Pattern::Concatenation(list) => {
137                list.iter().any(|p| p.has_constant_parts())
138            }
139        }
140    }
141
142    pub fn has_dynamic_parts(&self) -> bool {
143        match self {
144            Pattern::Constant(_) => false,
145            Pattern::Dynamic | Pattern::DynamicNoSlash => true,
146            Pattern::Alternatives(list) | Pattern::Concatenation(list) => {
147                list.iter().any(|p| p.has_dynamic_parts())
148            }
149        }
150    }
151
152    pub fn constant_prefix(&self) -> &str {
153        // The normalized pattern is an Alternative of maximally merged
154        // Concatenations, so extracting the first/only Concatenation child
155        // elements is enough.
156
157        if let Pattern::Constant(c) = self {
158            return c;
159        }
160
161        fn collect_constant_prefix<'a: 'b, 'b>(pattern: &'a Pattern, result: &mut Vec<&'b str>) {
162            match pattern {
163                Pattern::Constant(c) => {
164                    result.push(c.as_str());
165                }
166                Pattern::Concatenation(list) => {
167                    if let Some(Pattern::Constant(first)) = list.first() {
168                        result.push(first.as_str());
169                    }
170                }
171                Pattern::Alternatives(_) => {
172                    panic!("for constant_prefix a Pattern must be normalized");
173                }
174                Pattern::Dynamic | Pattern::DynamicNoSlash => {}
175            }
176        }
177
178        let mut strings: Vec<&str> = vec![];
179        match self {
180            c @ Pattern::Constant(_) | c @ Pattern::Concatenation(_) => {
181                collect_constant_prefix(c, &mut strings);
182            }
183            Pattern::Alternatives(list) => {
184                for c in list {
185                    collect_constant_prefix(c, &mut strings);
186                }
187            }
188            Pattern::Dynamic | Pattern::DynamicNoSlash => {}
189        }
190        longest_common_prefix(&strings)
191    }
192
193    pub fn constant_suffix(&self) -> &str {
194        // The normalized pattern is an Alternative of maximally merged
195        // Concatenations, so extracting the first/only Concatenation child
196        // elements is enough.
197
198        fn collect_constant_suffix<'a: 'b, 'b>(pattern: &'a Pattern, result: &mut Vec<&'b str>) {
199            match pattern {
200                Pattern::Constant(c) => {
201                    result.push(c.as_str());
202                }
203                Pattern::Concatenation(list) => {
204                    if let Some(Pattern::Constant(first)) = list.last() {
205                        result.push(first.as_str());
206                    }
207                }
208                Pattern::Alternatives(_) => {
209                    panic!("for constant_suffix a Pattern must be normalized");
210                }
211                Pattern::Dynamic | Pattern::DynamicNoSlash => {}
212            }
213        }
214
215        let mut strings: Vec<&str> = vec![];
216        match self {
217            c @ Pattern::Constant(_) | c @ Pattern::Concatenation(_) => {
218                collect_constant_suffix(c, &mut strings);
219            }
220            Pattern::Alternatives(list) => {
221                for c in list {
222                    collect_constant_suffix(c, &mut strings);
223                }
224            }
225            Pattern::Dynamic | Pattern::DynamicNoSlash => {}
226        }
227        longest_common_suffix(&strings)
228    }
229
230    pub fn strip_prefix(&self, prefix: &str) -> Option<Self> {
231        if self.must_match(prefix) {
232            let mut pat = self.clone();
233            pat.strip_prefix_len(prefix.len());
234            Some(pat)
235        } else {
236            None
237        }
238    }
239
240    pub fn strip_prefix_len(&mut self, len: usize) {
241        fn strip_prefix_internal(pattern: &mut Pattern, chars_to_strip: &mut usize) {
242            match pattern {
243                Pattern::Constant(c) => {
244                    let c_len = c.len();
245                    if *chars_to_strip >= c_len {
246                        *c = rcstr!("");
247                    } else {
248                        *c = (&c[*chars_to_strip..]).into();
249                    }
250                    *chars_to_strip = (*chars_to_strip).saturating_sub(c_len);
251                }
252                Pattern::Concatenation(list) => {
253                    for c in list {
254                        if *chars_to_strip > 0 {
255                            strip_prefix_internal(c, chars_to_strip);
256                        }
257                    }
258                }
259                Pattern::Alternatives(_) => {
260                    panic!("for strip_prefix a Pattern must be normalized");
261                }
262                Pattern::Dynamic | Pattern::DynamicNoSlash => {
263                    panic!("strip_prefix prefix is too long");
264                }
265            }
266        }
267
268        match &mut *self {
269            c @ Pattern::Constant(_) | c @ Pattern::Concatenation(_) => {
270                let mut len_local = len;
271                strip_prefix_internal(c, &mut len_local);
272            }
273            Pattern::Alternatives(list) => {
274                for c in list {
275                    let mut len_local = len;
276                    strip_prefix_internal(c, &mut len_local);
277                }
278            }
279            Pattern::Dynamic | Pattern::DynamicNoSlash => {
280                if len > 0 {
281                    panic!("strip_prefix prefix is too long");
282                }
283            }
284        };
285
286        self.normalize()
287    }
288
289    pub fn strip_suffix_len(&mut self, len: usize) {
290        fn strip_suffix_internal(pattern: &mut Pattern, chars_to_strip: &mut usize) {
291            match pattern {
292                Pattern::Constant(c) => {
293                    let c_len = c.len();
294                    if *chars_to_strip >= c_len {
295                        *c = rcstr!("");
296                    } else {
297                        *c = (&c[..(c_len - *chars_to_strip)]).into();
298                    }
299                    *chars_to_strip = (*chars_to_strip).saturating_sub(c_len);
300                }
301                Pattern::Concatenation(list) => {
302                    for c in list.iter_mut().rev() {
303                        if *chars_to_strip > 0 {
304                            strip_suffix_internal(c, chars_to_strip);
305                        }
306                    }
307                }
308                Pattern::Alternatives(_) => {
309                    panic!("for strip_suffix a Pattern must be normalized");
310                }
311                Pattern::Dynamic | Pattern::DynamicNoSlash => {
312                    panic!("strip_suffix suffix is too long");
313                }
314            }
315        }
316
317        match &mut *self {
318            c @ Pattern::Constant(_) | c @ Pattern::Concatenation(_) => {
319                let mut len_local = len;
320                strip_suffix_internal(c, &mut len_local);
321            }
322            Pattern::Alternatives(list) => {
323                for c in list {
324                    let mut len_local = len;
325                    strip_suffix_internal(c, &mut len_local);
326                }
327            }
328            Pattern::Dynamic | Pattern::DynamicNoSlash => {
329                if len > 0 {
330                    panic!("strip_suffix suffix is too long");
331                }
332            }
333        };
334
335        self.normalize()
336    }
337
338    /// Replace all `*`s in `template` with self.
339    ///
340    /// Handle top-level alternatives separately so that multiple star placeholders
341    /// match the same pattern instead of the whole alternative.
342    pub fn spread_into_star(&self, template: &str) -> Pattern {
343        if template.contains("*") {
344            let alternatives: Box<dyn Iterator<Item = &Pattern>> = match self {
345                Pattern::Alternatives(list) => Box::new(list.iter()),
346                c => Box::new(std::iter::once(c)),
347            };
348
349            let mut result = Pattern::alternatives(alternatives.map(|pat| {
350                let mut split = template.split("*");
351                let mut concatenation: Vec<Pattern> = Vec::with_capacity(3);
352
353                // There are at least two elements in the iterator
354                concatenation.push(Pattern::Constant(split.next().unwrap().into()));
355
356                for part in split {
357                    concatenation.push(pat.clone());
358                    if !part.is_empty() {
359                        concatenation.push(Pattern::Constant(part.into()));
360                    }
361                }
362                Pattern::Concatenation(concatenation)
363            }));
364
365            result.normalize();
366            result
367        } else {
368            Pattern::Constant(template.into())
369        }
370    }
371
372    /// Appends something to end the pattern.
373    pub fn extend(&mut self, concatenated: impl Iterator<Item = Self>) {
374        if let Pattern::Concatenation(list) = self {
375            concatenation_extend_or_merge_items(list, concatenated);
376        } else {
377            let mut vec = vec![take(self)];
378            for item in concatenated {
379                if let Pattern::Concatenation(more) = item {
380                    concatenation_extend_or_merge_items(&mut vec, more.into_iter());
381                } else {
382                    concatenation_push_or_merge_item(&mut vec, item);
383                }
384            }
385            *self = Pattern::Concatenation(vec);
386        }
387    }
388
389    /// Appends something to end the pattern.
390    pub fn push(&mut self, pat: Pattern) {
391        if let Pattern::Constant(this) = &*self
392            && this.is_empty()
393        {
394            // Short-circuit to replace empty constants with the appended pattern
395            *self = pat;
396            return;
397        }
398        if let Pattern::Constant(pat) = &pat
399            && pat.is_empty()
400        {
401            // Short-circuit to ignore when trying to append an empty string.
402            return;
403        }
404
405        match (self, pat) {
406            (Pattern::Concatenation(list), Pattern::Concatenation(more)) => {
407                concatenation_extend_or_merge_items(list, more.into_iter());
408            }
409            (Pattern::Concatenation(list), pat) => {
410                concatenation_push_or_merge_item(list, pat);
411            }
412            (this, Pattern::Concatenation(mut list)) => {
413                concatenation_push_front_or_merge_item(&mut list, take(this));
414                *this = Pattern::Concatenation(list);
415            }
416            (Pattern::Constant(str), Pattern::Constant(other)) => {
417                let mut buf = str.to_string();
418                buf.push_str(&other);
419                *str = buf.into();
420            }
421            (this, pat) => {
422                *this = Pattern::Concatenation(vec![take(this), pat]);
423            }
424        }
425    }
426
427    /// Prepends something to front of the pattern.
428    pub fn push_front(&mut self, pat: Pattern) {
429        match (self, pat) {
430            (Pattern::Concatenation(list), Pattern::Concatenation(mut more)) => {
431                concatenation_extend_or_merge_items(&mut more, take(list).into_iter());
432                *list = more;
433            }
434            (Pattern::Concatenation(list), pat) => {
435                concatenation_push_front_or_merge_item(list, pat);
436            }
437            (this, Pattern::Concatenation(mut list)) => {
438                concatenation_push_or_merge_item(&mut list, take(this));
439                *this = Pattern::Concatenation(list);
440            }
441            (Pattern::Constant(str), Pattern::Constant(other)) => {
442                let mut buf = other.into_owned();
443
444                buf.push_str(str);
445                *str = buf.into();
446            }
447            (this, pat) => {
448                *this = Pattern::Concatenation(vec![pat, take(this)]);
449            }
450        }
451    }
452
453    pub fn alternatives(alts: impl IntoIterator<Item = Pattern>) -> Self {
454        let mut list = Vec::new();
455        for alt in alts {
456            if let Pattern::Alternatives(inner) = alt {
457                list.extend(inner);
458            } else {
459                list.push(alt)
460            }
461        }
462        Self::Alternatives(list)
463    }
464
465    pub fn concat(items: impl IntoIterator<Item = Pattern>) -> Self {
466        let mut items = items.into_iter();
467        let mut current = items.next().unwrap_or_default();
468        for item in items {
469            current.push(item);
470        }
471        current
472    }
473
474    /// Normalizes paths by
475    /// - processing path segments: `.` and `..`
476    /// - normalizing windows filepaths by replacing `\` with `/`
477    ///
478    /// The Pattern must have already been processed by [Self::normalize].
479    /// Returns [Option::None] if any of the patterns attempt to navigate out of the root.
480    pub fn with_normalized_path(&self) -> Option<Pattern> {
481        let mut new = self.clone();
482
483        #[derive(Debug)]
484        enum PathElement {
485            Segment(Pattern),
486            Separator,
487        }
488
489        fn normalize_path_internal(pattern: &mut Pattern) -> Option<()> {
490            match pattern {
491                Pattern::Constant(c) => {
492                    let normalized = c.replace('\\', "/");
493                    *c = RcStr::from(normalize_path(normalized.as_str())?);
494                    Some(())
495                }
496                Pattern::Dynamic | Pattern::DynamicNoSlash => Some(()),
497                Pattern::Concatenation(list) => {
498                    let mut segments = Vec::new();
499                    for segment in list.iter() {
500                        match segment {
501                            Pattern::Constant(str) => {
502                                let mut iter = str.split('/').peekable();
503                                while let Some(segment) = iter.next() {
504                                    match segment {
505                                        "." | "" => {
506                                            // Ignore empty segments
507                                            continue;
508                                        }
509                                        ".." => {
510                                            if segments.is_empty() {
511                                                // Leaving root
512                                                return None;
513                                            }
514
515                                            if let Some(PathElement::Separator) = segments.last()
516                                                && let Some(PathElement::Segment(
517                                                    Pattern::Constant(_),
518                                                )) = segments.get(segments.len() - 2)
519                                            {
520                                                // Resolve `foo/..`
521                                                segments.truncate(segments.len() - 2);
522                                                continue;
523                                            }
524
525                                            // Keep it, can't pop non-constant segment.
526                                            segments.push(PathElement::Segment(Pattern::Constant(
527                                                rcstr!(".."),
528                                            )));
529                                        }
530                                        segment => {
531                                            segments.push(PathElement::Segment(Pattern::Constant(
532                                                segment.into(),
533                                            )));
534                                        }
535                                    }
536
537                                    if iter.peek().is_some() {
538                                        // If not last, add separator
539                                        segments.push(PathElement::Separator);
540                                    }
541                                }
542                            }
543                            Pattern::Dynamic | Pattern::DynamicNoSlash => {
544                                segments.push(PathElement::Segment(segment.clone()));
545                            }
546                            Pattern::Alternatives(_) | Pattern::Concatenation(_) => {
547                                panic!("for with_normalized_path the Pattern must be normalized");
548                            }
549                        }
550                    }
551                    let separator = rcstr!("/");
552                    *list = segments
553                        .into_iter()
554                        .map(|c| match c {
555                            PathElement::Segment(p) => p,
556                            PathElement::Separator => Pattern::Constant(separator.clone()),
557                        })
558                        .collect();
559                    Some(())
560                }
561                Pattern::Alternatives(_) => {
562                    panic!("for with_normalized_path the Pattern must be normalized");
563                }
564            }
565        }
566
567        match &mut new {
568            c @ Pattern::Constant(_) | c @ Pattern::Concatenation(_) => {
569                normalize_path_internal(c)?;
570            }
571            Pattern::Alternatives(list) => {
572                for c in list {
573                    normalize_path_internal(c)?;
574                }
575            }
576            Pattern::Dynamic | Pattern::DynamicNoSlash => {}
577        }
578
579        new.normalize();
580        Some(new)
581    }
582
583    /// Order into Alternatives -> Concatenation -> Constant/Dynamic
584    /// Merge when possible
585    pub fn normalize(&mut self) {
586        match self {
587            Pattern::Dynamic | Pattern::DynamicNoSlash | Pattern::Constant(_) => {
588                // already normalized
589            }
590            Pattern::Alternatives(list) => {
591                for alt in list.iter_mut() {
592                    alt.normalize();
593                }
594                let mut new_alternatives = Vec::new();
595                let mut has_dynamic = false;
596                for alt in list.drain(..) {
597                    if let Pattern::Alternatives(inner) = alt {
598                        for alt in inner {
599                            if alt == Pattern::Dynamic {
600                                if !has_dynamic {
601                                    has_dynamic = true;
602                                    new_alternatives.push(alt);
603                                }
604                            } else {
605                                new_alternatives.push(alt);
606                            }
607                        }
608                    } else if alt == Pattern::Dynamic {
609                        if !has_dynamic {
610                            has_dynamic = true;
611                            new_alternatives.push(alt);
612                        }
613                    } else {
614                        new_alternatives.push(alt);
615                    }
616                }
617                if new_alternatives.len() == 1 {
618                    *self = new_alternatives.into_iter().next().unwrap();
619                } else {
620                    *list = new_alternatives;
621                }
622            }
623            Pattern::Concatenation(list) => {
624                let mut has_alternatives = false;
625                for part in list.iter_mut() {
626                    part.normalize();
627                    if let Pattern::Alternatives(_) = part {
628                        has_alternatives = true;
629                    }
630                }
631                if has_alternatives {
632                    // list has items that are one of these
633                    // * Alternatives -> [Concatenation] -> ...
634                    // * [Concatenation] -> ...
635                    let mut new_alternatives: Vec<Vec<Pattern>> = vec![Vec::new()];
636                    for part in list.drain(..) {
637                        if let Pattern::Alternatives(list) = part {
638                            // list is [Concatenation] -> ...
639                            let mut combined = Vec::new();
640                            for alt2 in list.iter() {
641                                for mut alt in new_alternatives.clone() {
642                                    if let Pattern::Concatenation(parts) = alt2 {
643                                        alt.extend(parts.clone());
644                                    } else {
645                                        alt.push(alt2.clone());
646                                    }
647                                    combined.push(alt)
648                                }
649                            }
650                            new_alternatives = combined;
651                        } else {
652                            // part is [Concatenation] -> ...
653                            for alt in new_alternatives.iter_mut() {
654                                if let Pattern::Concatenation(ref parts) = part {
655                                    alt.extend(parts.clone());
656                                } else {
657                                    alt.push(part.clone());
658                                }
659                            }
660                        }
661                    }
662                    // new_alternatives has items in that form:
663                    // * [Concatenation] -> ...
664                    *self = Pattern::Alternatives(
665                        new_alternatives
666                            .into_iter()
667                            .map(|parts| {
668                                if parts.len() == 1 {
669                                    parts.into_iter().next().unwrap()
670                                } else {
671                                    Pattern::Concatenation(parts)
672                                }
673                            })
674                            .collect(),
675                    );
676                    // The recursive call will deduplicate the alternatives after simplifying them
677                    self.normalize();
678                } else {
679                    let mut new_parts = Vec::new();
680                    for part in list.drain(..) {
681                        fn add_part(part: Pattern, new_parts: &mut Vec<Pattern>) {
682                            match part {
683                                Pattern::Constant(c) => {
684                                    if !c.is_empty() {
685                                        if let Some(Pattern::Constant(last)) = new_parts.last_mut()
686                                        {
687                                            let mut buf = last.to_string();
688                                            buf.push_str(&c);
689                                            *last = buf.into();
690                                        } else {
691                                            new_parts.push(Pattern::Constant(c));
692                                        }
693                                    }
694                                }
695                                Pattern::Dynamic => {
696                                    if let Some(Pattern::Dynamic | Pattern::DynamicNoSlash) =
697                                        new_parts.last()
698                                    {
699                                        // do nothing
700                                    } else {
701                                        new_parts.push(Pattern::Dynamic);
702                                    }
703                                }
704                                Pattern::DynamicNoSlash => {
705                                    if let Some(Pattern::DynamicNoSlash) = new_parts.last() {
706                                        // do nothing
707                                    } else {
708                                        new_parts.push(Pattern::DynamicNoSlash);
709                                    }
710                                }
711                                Pattern::Concatenation(parts) => {
712                                    for part in parts {
713                                        add_part(part, new_parts);
714                                    }
715                                }
716                                Pattern::Alternatives(_) => unreachable!(),
717                            }
718                        }
719
720                        add_part(part, &mut new_parts);
721                    }
722                    if new_parts.len() == 1 {
723                        *self = new_parts.into_iter().next().unwrap();
724                    } else {
725                        *list = new_parts;
726                    }
727                }
728            }
729        }
730    }
731
732    pub fn is_empty(&self) -> bool {
733        match self {
734            Pattern::Constant(s) => s.is_empty(),
735            Pattern::Dynamic | Pattern::DynamicNoSlash => false,
736            Pattern::Concatenation(parts) => parts.iter().all(|p| p.is_empty()),
737            Pattern::Alternatives(parts) => parts.iter().all(|p| p.is_empty()),
738        }
739    }
740
741    pub fn filter_could_match(&self, value: &str) -> Option<Pattern> {
742        if let Pattern::Alternatives(list) = self {
743            let new_list = list
744                .iter()
745                .filter(|alt| alt.could_match(value))
746                .cloned()
747                .collect::<Vec<_>>();
748            if new_list.is_empty() {
749                None
750            } else {
751                Some(Pattern::Alternatives(new_list))
752            }
753        } else if self.could_match(value) {
754            Some(self.clone())
755        } else {
756            None
757        }
758    }
759
760    pub fn filter_could_not_match(&self, value: &str) -> Option<Pattern> {
761        if let Pattern::Alternatives(list) = self {
762            let new_list = list
763                .iter()
764                .filter(|alt| !alt.could_match(value))
765                .cloned()
766                .collect::<Vec<_>>();
767            if new_list.is_empty() {
768                None
769            } else {
770                Some(Pattern::Alternatives(new_list))
771            }
772        } else if self.could_match(value) {
773            None
774        } else {
775            Some(self.clone())
776        }
777    }
778
779    pub fn split_could_match(&self, value: &str) -> (Option<Pattern>, Option<Pattern>) {
780        if let Pattern::Alternatives(list) = self {
781            let mut could_match_list = Vec::new();
782            let mut could_not_match_list = Vec::new();
783            for alt in list.iter() {
784                if alt.could_match(value) {
785                    could_match_list.push(alt.clone());
786                } else {
787                    could_not_match_list.push(alt.clone());
788                }
789            }
790            (
791                if could_match_list.is_empty() {
792                    None
793                } else if could_match_list.len() == 1 {
794                    Some(could_match_list.into_iter().next().unwrap())
795                } else {
796                    Some(Pattern::Alternatives(could_match_list))
797                },
798                if could_not_match_list.is_empty() {
799                    None
800                } else if could_not_match_list.len() == 1 {
801                    Some(could_not_match_list.into_iter().next().unwrap())
802                } else {
803                    Some(Pattern::Alternatives(could_not_match_list))
804                },
805            )
806        } else if self.could_match(value) {
807            (Some(self.clone()), None)
808        } else {
809            (None, Some(self.clone()))
810        }
811    }
812
813    pub fn is_match(&self, value: &str) -> bool {
814        if let Pattern::Alternatives(list) = self {
815            list.iter().any(|alt| {
816                alt.match_internal(value, None, InNodeModules::False, false)
817                    .is_match()
818            })
819        } else {
820            self.match_internal(value, None, InNodeModules::False, false)
821                .is_match()
822        }
823    }
824
825    /// Like [`Pattern::is_match`], but does not consider any dynamic
826    /// pattern matching
827    pub fn is_match_ignore_dynamic(&self, value: &str) -> bool {
828        if let Pattern::Alternatives(list) = self {
829            list.iter().any(|alt| {
830                alt.match_internal(value, None, InNodeModules::False, true)
831                    .is_match()
832            })
833        } else {
834            self.match_internal(value, None, InNodeModules::False, true)
835                .is_match()
836        }
837    }
838
839    pub fn match_position(&self, value: &str) -> Option<usize> {
840        if let Pattern::Alternatives(list) = self {
841            list.iter().position(|alt| {
842                alt.match_internal(value, None, InNodeModules::False, false)
843                    .is_match()
844            })
845        } else {
846            self.match_internal(value, None, InNodeModules::False, false)
847                .is_match()
848                .then_some(0)
849        }
850    }
851
852    pub fn could_match_others(&self, value: &str) -> bool {
853        if let Pattern::Alternatives(list) = self {
854            list.iter().any(|alt| {
855                alt.match_internal(value, None, InNodeModules::False, false)
856                    .could_match_others()
857            })
858        } else {
859            self.match_internal(value, None, InNodeModules::False, false)
860                .could_match_others()
861        }
862    }
863
864    /// Returns true if all matches of the pattern start with `value`.
865    pub fn must_match(&self, value: &str) -> bool {
866        if let Pattern::Alternatives(list) = self {
867            list.iter().all(|alt| {
868                alt.match_internal(value, None, InNodeModules::False, false)
869                    .could_match()
870            })
871        } else {
872            self.match_internal(value, None, InNodeModules::False, false)
873                .could_match()
874        }
875    }
876
877    /// Returns true the pattern could match something that starts with `value`.
878    pub fn could_match(&self, value: &str) -> bool {
879        if let Pattern::Alternatives(list) = self {
880            list.iter().any(|alt| {
881                alt.match_internal(value, None, InNodeModules::False, false)
882                    .could_match()
883            })
884        } else {
885            self.match_internal(value, None, InNodeModules::False, false)
886                .could_match()
887        }
888    }
889
890    pub fn could_match_position(&self, value: &str) -> Option<usize> {
891        if let Pattern::Alternatives(list) = self {
892            list.iter().position(|alt| {
893                alt.match_internal(value, None, InNodeModules::False, false)
894                    .could_match()
895            })
896        } else {
897            self.match_internal(value, None, InNodeModules::False, false)
898                .could_match()
899                .then_some(0)
900        }
901    }
902    fn match_internal<'a>(
903        &self,
904        mut value: &'a str,
905        mut any_offset: Option<usize>,
906        mut in_node_modules: InNodeModules,
907        ignore_dynamic: bool,
908    ) -> MatchResult<'a> {
909        match self {
910            Pattern::Constant(c) => {
911                if let Some(offset) = any_offset {
912                    if let Some(index) = value.find(&**c) {
913                        if index <= offset {
914                            MatchResult::Consumed {
915                                remaining: &value[index + c.len()..],
916                                any_offset: None,
917                                in_node_modules: InNodeModules::check(c),
918                            }
919                        } else {
920                            MatchResult::None
921                        }
922                    } else if offset >= value.len() {
923                        MatchResult::Partial
924                    } else {
925                        MatchResult::None
926                    }
927                } else if value.starts_with(&**c) {
928                    MatchResult::Consumed {
929                        remaining: &value[c.len()..],
930                        any_offset: None,
931                        in_node_modules: InNodeModules::check(c),
932                    }
933                } else if c.starts_with(value) {
934                    MatchResult::Partial
935                } else {
936                    MatchResult::None
937                }
938            }
939            Pattern::Dynamic | Pattern::DynamicNoSlash => {
940                static FORBIDDEN: LazyLock<Regex> = LazyLock::new(|| {
941                    Regex::new(r"(/|^)(ROOT|\.|/|(node_modules|__tests?__)(/|$))").unwrap()
942                });
943                static FORBIDDEN_MATCH: LazyLock<Regex> =
944                    LazyLock::new(|| Regex::new(r"\.d\.ts$|\.map$").unwrap());
945                if in_node_modules == InNodeModules::FolderSlashMatched
946                    || (in_node_modules == InNodeModules::FolderMatched && value.starts_with('/'))
947                {
948                    MatchResult::None
949                } else if let Some(m) = FORBIDDEN.find(value) {
950                    MatchResult::Consumed {
951                        remaining: value,
952                        any_offset: Some(m.start()),
953                        in_node_modules: InNodeModules::False,
954                    }
955                } else if FORBIDDEN_MATCH.find(value).is_some() {
956                    MatchResult::Partial
957                } else if ignore_dynamic {
958                    MatchResult::None
959                } else {
960                    let match_length = matches!(self, Pattern::DynamicNoSlash)
961                        .then(|| value.find("/"))
962                        .flatten()
963                        .unwrap_or(value.len());
964                    MatchResult::Consumed {
965                        remaining: value,
966                        any_offset: Some(match_length),
967                        in_node_modules: InNodeModules::False,
968                    }
969                }
970            }
971            Pattern::Alternatives(_) => {
972                panic!("for matching a Pattern must be normalized {self:?}")
973            }
974            Pattern::Concatenation(list) => {
975                for part in list {
976                    match part.match_internal(value, any_offset, in_node_modules, ignore_dynamic) {
977                        MatchResult::None => return MatchResult::None,
978                        MatchResult::Partial => return MatchResult::Partial,
979                        MatchResult::Consumed {
980                            remaining: new_value,
981                            any_offset: new_any_offset,
982                            in_node_modules: new_in_node_modules,
983                        } => {
984                            value = new_value;
985                            any_offset = new_any_offset;
986                            in_node_modules = new_in_node_modules
987                        }
988                    }
989                }
990                MatchResult::Consumed {
991                    remaining: value,
992                    any_offset,
993                    in_node_modules,
994                }
995            }
996        }
997    }
998
999    /// Same as `match_internal`, but additionally pushing matched dynamic elements into the given
1000    /// result list.
1001    fn match_collect_internal<'a>(
1002        &self,
1003        mut value: &'a str,
1004        mut any_offset: Option<usize>,
1005        mut in_node_modules: InNodeModules,
1006        dynamics: &mut VecDeque<&'a str>,
1007    ) -> MatchResult<'a> {
1008        match self {
1009            Pattern::Constant(c) => {
1010                if let Some(offset) = any_offset {
1011                    if let Some(index) = value.find(&**c) {
1012                        if index <= offset {
1013                            if index > 0 {
1014                                dynamics.push_back(&value[..index]);
1015                            }
1016                            MatchResult::Consumed {
1017                                remaining: &value[index + c.len()..],
1018                                any_offset: None,
1019                                in_node_modules: InNodeModules::check(c),
1020                            }
1021                        } else {
1022                            MatchResult::None
1023                        }
1024                    } else if offset >= value.len() {
1025                        MatchResult::Partial
1026                    } else {
1027                        MatchResult::None
1028                    }
1029                } else if value.starts_with(&**c) {
1030                    MatchResult::Consumed {
1031                        remaining: &value[c.len()..],
1032                        any_offset: None,
1033                        in_node_modules: InNodeModules::check(c),
1034                    }
1035                } else if c.starts_with(value) {
1036                    MatchResult::Partial
1037                } else {
1038                    MatchResult::None
1039                }
1040            }
1041            Pattern::Dynamic | Pattern::DynamicNoSlash => {
1042                static FORBIDDEN: LazyLock<Regex> = LazyLock::new(|| {
1043                    Regex::new(r"(/|^)(ROOT|\.|/|(node_modules|__tests?__)(/|$))").unwrap()
1044                });
1045                static FORBIDDEN_MATCH: LazyLock<Regex> =
1046                    LazyLock::new(|| Regex::new(r"\.d\.ts$|\.map$").unwrap());
1047                if in_node_modules == InNodeModules::FolderSlashMatched
1048                    || (in_node_modules == InNodeModules::FolderMatched && value.starts_with('/'))
1049                {
1050                    MatchResult::None
1051                } else if let Some(m) = FORBIDDEN.find(value) {
1052                    MatchResult::Consumed {
1053                        remaining: value,
1054                        any_offset: Some(m.start()),
1055                        in_node_modules: InNodeModules::False,
1056                    }
1057                } else if FORBIDDEN_MATCH.find(value).is_some() {
1058                    MatchResult::Partial
1059                } else {
1060                    let match_length = matches!(self, Pattern::DynamicNoSlash)
1061                        .then(|| value.find("/"))
1062                        .flatten()
1063                        .unwrap_or(value.len());
1064                    MatchResult::Consumed {
1065                        remaining: value,
1066                        any_offset: Some(match_length),
1067                        in_node_modules: InNodeModules::False,
1068                    }
1069                }
1070            }
1071            Pattern::Alternatives(_) => {
1072                panic!("for matching a Pattern must be normalized {self:?}")
1073            }
1074            Pattern::Concatenation(list) => {
1075                for part in list {
1076                    match part.match_collect_internal(value, any_offset, in_node_modules, dynamics)
1077                    {
1078                        MatchResult::None => return MatchResult::None,
1079                        MatchResult::Partial => return MatchResult::Partial,
1080                        MatchResult::Consumed {
1081                            remaining: new_value,
1082                            any_offset: new_any_offset,
1083                            in_node_modules: new_in_node_modules,
1084                        } => {
1085                            value = new_value;
1086                            any_offset = new_any_offset;
1087                            in_node_modules = new_in_node_modules
1088                        }
1089                    }
1090                }
1091                if let Some(offset) = any_offset
1092                    && offset == value.len()
1093                {
1094                    dynamics.push_back(value);
1095                }
1096                MatchResult::Consumed {
1097                    remaining: value,
1098                    any_offset,
1099                    in_node_modules,
1100                }
1101            }
1102        }
1103    }
1104
1105    pub fn next_constants<'a>(&'a self, value: &str) -> Option<Vec<(&'a str, bool)>> {
1106        if let Pattern::Alternatives(list) = self {
1107            let mut results = Vec::new();
1108            for alt in list.iter() {
1109                match alt.next_constants_internal(value, None) {
1110                    NextConstantUntilResult::NoMatch => {}
1111                    NextConstantUntilResult::PartialDynamic => {
1112                        return None;
1113                    }
1114                    NextConstantUntilResult::Partial(s, end) => {
1115                        results.push((s, end));
1116                    }
1117                    NextConstantUntilResult::Consumed(rem, None) => {
1118                        if rem.is_empty() {
1119                            results.push(("", true));
1120                        }
1121                    }
1122                    NextConstantUntilResult::Consumed(rem, Some(any)) => {
1123                        if any == rem.len() {
1124                            // can match anything
1125                            // we don't have constant only matches
1126                            return None;
1127                        }
1128                    }
1129                }
1130            }
1131            Some(results)
1132        } else {
1133            match self.next_constants_internal(value, None) {
1134                NextConstantUntilResult::NoMatch => None,
1135                NextConstantUntilResult::PartialDynamic => None,
1136                NextConstantUntilResult::Partial(s, e) => Some(vec![(s, e)]),
1137                NextConstantUntilResult::Consumed(_, _) => None,
1138            }
1139        }
1140    }
1141
1142    fn next_constants_internal<'a, 'b>(
1143        &'a self,
1144        mut value: &'b str,
1145        mut any_offset: Option<usize>,
1146    ) -> NextConstantUntilResult<'a, 'b> {
1147        match self {
1148            Pattern::Constant(c) => {
1149                if let Some(offset) = any_offset {
1150                    if let Some(index) = value.find(&**c) {
1151                        if index <= offset {
1152                            NextConstantUntilResult::Consumed(&value[index + c.len()..], None)
1153                        } else {
1154                            NextConstantUntilResult::NoMatch
1155                        }
1156                    } else if offset >= value.len() {
1157                        NextConstantUntilResult::PartialDynamic
1158                    } else {
1159                        NextConstantUntilResult::NoMatch
1160                    }
1161                } else if let Some(stripped) = value.strip_prefix(&**c) {
1162                    NextConstantUntilResult::Consumed(stripped, None)
1163                } else if let Some(stripped) = c.strip_prefix(value) {
1164                    NextConstantUntilResult::Partial(stripped, true)
1165                } else {
1166                    NextConstantUntilResult::NoMatch
1167                }
1168            }
1169            Pattern::Dynamic | Pattern::DynamicNoSlash => {
1170                static FORBIDDEN: LazyLock<Regex> = LazyLock::new(|| {
1171                    Regex::new(r"(/|^)(\.|(node_modules|__tests?__)(/|$))").unwrap()
1172                });
1173                static FORBIDDEN_MATCH: LazyLock<Regex> =
1174                    LazyLock::new(|| Regex::new(r"\.d\.ts$|\.map$").unwrap());
1175                if let Some(m) = FORBIDDEN.find(value) {
1176                    NextConstantUntilResult::Consumed(value, Some(m.start()))
1177                } else if FORBIDDEN_MATCH.find(value).is_some() {
1178                    NextConstantUntilResult::PartialDynamic
1179                } else {
1180                    NextConstantUntilResult::Consumed(value, Some(value.len()))
1181                }
1182            }
1183            Pattern::Alternatives(_) => {
1184                panic!("for next_constants() the Pattern must be normalized");
1185            }
1186            Pattern::Concatenation(list) => {
1187                let mut iter = list.iter();
1188                while let Some(part) = iter.next() {
1189                    match part.next_constants_internal(value, any_offset) {
1190                        NextConstantUntilResult::NoMatch => {
1191                            return NextConstantUntilResult::NoMatch;
1192                        }
1193                        NextConstantUntilResult::PartialDynamic => {
1194                            return NextConstantUntilResult::PartialDynamic;
1195                        }
1196                        NextConstantUntilResult::Partial(r, end) => {
1197                            return NextConstantUntilResult::Partial(
1198                                r,
1199                                end && iter.next().is_none(),
1200                            );
1201                        }
1202                        NextConstantUntilResult::Consumed(new_value, new_any_offset) => {
1203                            value = new_value;
1204                            any_offset = new_any_offset;
1205                        }
1206                    }
1207                }
1208                NextConstantUntilResult::Consumed(value, any_offset)
1209            }
1210        }
1211    }
1212
1213    pub fn or_any_nested_file(&self) -> Self {
1214        let mut new = self.clone();
1215        new.push(Pattern::Constant(rcstr!("/")));
1216        new.push(Pattern::Dynamic);
1217        new.normalize();
1218        Pattern::alternatives([self.clone(), new])
1219    }
1220
1221    /// Calls `cb` on all constants that are at the end of the pattern and
1222    /// replaces the given final constant with the returned pattern. Returns
1223    /// true if replacements were performed.
1224    pub fn replace_final_constants(&mut self, cb: &impl Fn(&RcStr) -> Option<Pattern>) -> bool {
1225        let mut replaced = false;
1226        match self {
1227            Pattern::Constant(c) => {
1228                if let Some(replacement) = cb(c) {
1229                    *self = replacement;
1230                    replaced = true;
1231                }
1232            }
1233            Pattern::Dynamic | Pattern::DynamicNoSlash => {}
1234            Pattern::Alternatives(list) => {
1235                for i in list {
1236                    replaced = i.replace_final_constants(cb) || replaced;
1237                }
1238            }
1239            Pattern::Concatenation(list) => {
1240                if let Some(i) = list.last_mut() {
1241                    replaced = i.replace_final_constants(cb) || replaced;
1242                }
1243            }
1244        }
1245        replaced
1246    }
1247
1248    /// Calls `cb` on all constants and replaces the them with the returned pattern. Returns true if
1249    /// replacements were performed.
1250    pub fn replace_constants(&mut self, cb: &impl Fn(&RcStr) -> Option<Pattern>) -> bool {
1251        let mut replaced = false;
1252        match self {
1253            Pattern::Constant(c) => {
1254                if let Some(replacement) = cb(c) {
1255                    *self = replacement;
1256                    replaced = true;
1257                }
1258            }
1259            Pattern::Dynamic | Pattern::DynamicNoSlash => {}
1260            Pattern::Concatenation(list) | Pattern::Alternatives(list) => {
1261                for i in list {
1262                    replaced = i.replace_constants(cb) || replaced;
1263                }
1264            }
1265        }
1266        replaced
1267    }
1268
1269    /// Matches the given string against self, and applies the match onto the target pattern.
1270    ///
1271    /// The two patterns should have a similar structure (same number of alternatives and dynamics)
1272    /// and only differ in the constant contents.
1273    pub fn match_apply_template(&self, value: &str, target: &Pattern) -> Option<String> {
1274        let match_idx = self.match_position(value)?;
1275        let source = match self {
1276            Pattern::Alternatives(list) => list.get(match_idx),
1277            Pattern::Constant(_) | Pattern::Dynamic | Pattern::Concatenation(_)
1278                if match_idx == 0 =>
1279            {
1280                Some(self)
1281            }
1282            _ => None,
1283        }?;
1284        let target = match target {
1285            Pattern::Alternatives(list) => list.get(match_idx),
1286            Pattern::Constant(_) | Pattern::Dynamic | Pattern::Concatenation(_)
1287                if match_idx == 0 =>
1288            {
1289                Some(target)
1290            }
1291            _ => None,
1292        }?;
1293
1294        let mut dynamics = VecDeque::new();
1295        // This is definitely a match, because it matched above in `self.match_position(value)`
1296        source.match_collect_internal(value, None, InNodeModules::False, &mut dynamics);
1297
1298        let mut result = "".to_string();
1299        match target {
1300            Pattern::Constant(c) => result.push_str(c),
1301            Pattern::Dynamic | Pattern::DynamicNoSlash => result.push_str(dynamics.pop_front()?),
1302            Pattern::Concatenation(list) => {
1303                for c in list {
1304                    match c {
1305                        Pattern::Constant(c) => result.push_str(c),
1306                        Pattern::Dynamic | Pattern::DynamicNoSlash => {
1307                            result.push_str(dynamics.pop_front()?)
1308                        }
1309                        Pattern::Alternatives(_) | Pattern::Concatenation(_) => {
1310                            panic!("Pattern must be normalized")
1311                        }
1312                    }
1313                }
1314            }
1315            Pattern::Alternatives(_) => panic!("Pattern must be normalized"),
1316        }
1317        if !dynamics.is_empty() {
1318            return None;
1319        }
1320
1321        Some(result)
1322    }
1323}
1324
1325impl Pattern {
1326    pub fn new(mut pattern: Pattern) -> Vc<Self> {
1327        pattern.normalize();
1328        Pattern::new_internal(pattern)
1329    }
1330}
1331
1332#[turbo_tasks::value_impl]
1333impl Pattern {
1334    #[turbo_tasks::function]
1335    fn new_internal(pattern: Pattern) -> Vc<Self> {
1336        Self::cell(pattern)
1337    }
1338}
1339
1340#[derive(PartialEq, Debug)]
1341enum InNodeModules {
1342    False,
1343    // Inside of a match ending in `node_modules`
1344    FolderMatched,
1345    // Inside of a match ending in `node_modules/`
1346    FolderSlashMatched,
1347}
1348impl InNodeModules {
1349    fn check(value: &str) -> Self {
1350        if value.ends_with("node_modules/") {
1351            InNodeModules::FolderSlashMatched
1352        } else if value.ends_with("node_modules") {
1353            InNodeModules::FolderMatched
1354        } else {
1355            InNodeModules::False
1356        }
1357    }
1358}
1359
1360#[derive(PartialEq, Debug)]
1361enum MatchResult<'a> {
1362    /// No match
1363    None,
1364    /// Matches only a part of the pattern before reaching the end of the string
1365    Partial,
1366    /// Matches the whole pattern (but maybe not the whole string)
1367    Consumed {
1368        /// Part of the string remaining after matching the whole pattern
1369        remaining: &'a str,
1370        /// Set when the pattern ends with a dynamic part. The dynamic part
1371        /// could match n bytes more of the string.
1372        any_offset: Option<usize>,
1373        /// Set when the pattern ends with `node_modules` or `node_modules/` (and a following
1374        /// Pattern::Dynamic would thus match all existing packages)
1375        in_node_modules: InNodeModules,
1376    },
1377}
1378
1379impl MatchResult<'_> {
1380    /// Returns true if the whole pattern matches the whole string
1381    fn is_match(&self) -> bool {
1382        match self {
1383            MatchResult::None => false,
1384            MatchResult::Partial => false,
1385            MatchResult::Consumed {
1386                remaining: rem,
1387                any_offset,
1388                in_node_modules: _,
1389            } => {
1390                if let Some(offset) = any_offset {
1391                    *offset == rem.len()
1392                } else {
1393                    rem.is_empty()
1394                }
1395            }
1396        }
1397    }
1398
1399    /// Returns true if (at least a part of) the pattern matches the whole
1400    /// string and can also match more bytes in the string
1401    fn could_match_others(&self) -> bool {
1402        match self {
1403            MatchResult::None => false,
1404            MatchResult::Partial => true,
1405            MatchResult::Consumed {
1406                remaining: rem,
1407                any_offset,
1408                in_node_modules: _,
1409            } => {
1410                if let Some(offset) = any_offset {
1411                    *offset == rem.len()
1412                } else {
1413                    false
1414                }
1415            }
1416        }
1417    }
1418
1419    /// Returns true if (at least a part of) the pattern matches the whole
1420    /// string
1421    fn could_match(&self) -> bool {
1422        match self {
1423            MatchResult::None => false,
1424            MatchResult::Partial => true,
1425            MatchResult::Consumed {
1426                remaining: rem,
1427                any_offset,
1428                in_node_modules: _,
1429            } => {
1430                if let Some(offset) = any_offset {
1431                    *offset == rem.len()
1432                } else {
1433                    rem.is_empty()
1434                }
1435            }
1436        }
1437    }
1438}
1439
1440#[derive(PartialEq, Debug)]
1441enum NextConstantUntilResult<'a, 'b> {
1442    NoMatch,
1443    PartialDynamic,
1444    Partial(&'a str, bool),
1445    Consumed(&'b str, Option<usize>),
1446}
1447
1448impl From<RcStr> for Pattern {
1449    fn from(s: RcStr) -> Self {
1450        Pattern::Constant(s)
1451    }
1452}
1453
1454impl Pattern {
1455    pub fn describe_as_string(&self) -> String {
1456        match self {
1457            Pattern::Constant(c) => format!("'{c}'"),
1458            Pattern::Dynamic => "<dynamic>".to_string(),
1459            Pattern::DynamicNoSlash => "<dynamic no slash>".to_string(),
1460            Pattern::Alternatives(list) => format!(
1461                "({})",
1462                list.iter()
1463                    .map(|i| i.describe_as_string())
1464                    .collect::<Vec<_>>()
1465                    .join(" | ")
1466            ),
1467            Pattern::Concatenation(list) => list
1468                .iter()
1469                .map(|i| i.describe_as_string())
1470                .collect::<Vec<_>>()
1471                .join(" "),
1472        }
1473    }
1474}
1475
1476#[turbo_tasks::value_impl]
1477impl ValueToString for Pattern {
1478    #[turbo_tasks::function]
1479    fn to_string(&self) -> Vc<RcStr> {
1480        Vc::cell(self.describe_as_string().into())
1481    }
1482}
1483
1484#[derive(
1485    Debug,
1486    PartialEq,
1487    Eq,
1488    Clone,
1489    TraceRawVcs,
1490    Serialize,
1491    Deserialize,
1492    ValueDebugFormat,
1493    NonLocalValue,
1494)]
1495pub enum PatternMatch {
1496    File(RcStr, FileSystemPath),
1497    Directory(RcStr, FileSystemPath),
1498}
1499
1500impl PatternMatch {
1501    pub fn path(&self) -> Vc<FileSystemPath> {
1502        match self {
1503            PatternMatch::File(_, path) | PatternMatch::Directory(_, path) => path.clone().cell(),
1504        }
1505    }
1506
1507    pub fn name(&self) -> &str {
1508        match self {
1509            PatternMatch::File(name, _) | PatternMatch::Directory(name, _) => name.as_str(),
1510        }
1511    }
1512}
1513
1514// TODO this isn't super efficient
1515// avoid storing a large list of matches
1516#[turbo_tasks::value(transparent)]
1517pub struct PatternMatches(Vec<PatternMatch>);
1518
1519/// Find all files or directories that match the provided `pattern` with the
1520/// specified `lookup_dir` directory. `prefix` is the already matched part of
1521/// the pattern that leads to the `lookup_dir` directory. When
1522/// `force_in_lookup_dir` is set, leaving the `lookup_dir` directory by
1523/// matching `..` is not allowed.
1524///
1525/// Symlinks will not be resolved. It's expected that the caller resolves
1526/// symlinks when they are interested in that.
1527#[turbo_tasks::function]
1528pub async fn read_matches(
1529    lookup_dir: FileSystemPath,
1530    prefix: RcStr,
1531    force_in_lookup_dir: bool,
1532    pattern: Vc<Pattern>,
1533) -> Result<Vc<PatternMatches>> {
1534    let mut prefix = prefix.to_string();
1535    let pat = pattern.await?;
1536    let mut results = Vec::new();
1537    let mut nested = Vec::new();
1538    let slow_path = if let Some(constants) = pat.next_constants(&prefix) {
1539        if constants
1540            .iter()
1541            .all(|(str, until_end)| *until_end || str.contains('/'))
1542        {
1543            // Fast path: There is a finite list of possible strings that include at least
1544            // one path segment We will enumerate the list instead of the
1545            // directory
1546            let mut handled = FxHashSet::default();
1547            let mut read_dir_results = FxHashMap::default();
1548            for (index, (str, until_end)) in constants.into_iter().enumerate() {
1549                if until_end {
1550                    if !handled.insert(str) {
1551                        continue;
1552                    }
1553                    let (parent_path, last_segment) = split_last_segment(str);
1554                    if last_segment.is_empty() {
1555                        // This means we don't have a last segment, so we just have a directory
1556                        let joined = if force_in_lookup_dir {
1557                            lookup_dir.try_join_inside(parent_path)?
1558                        } else {
1559                            lookup_dir.try_join(parent_path)?
1560                        };
1561                        let Some(fs_path) = joined else {
1562                            continue;
1563                        };
1564                        results.push((
1565                            index,
1566                            PatternMatch::Directory(concat(&prefix, str).into(), fs_path),
1567                        ));
1568                        continue;
1569                    }
1570                    let entry = read_dir_results.entry(parent_path);
1571                    let read_dir = match entry {
1572                        Entry::Occupied(e) => Some(e.into_mut()),
1573                        Entry::Vacant(e) => {
1574                            let path_option = if force_in_lookup_dir {
1575                                lookup_dir.try_join_inside(parent_path)?
1576                            } else {
1577                                lookup_dir.try_join(parent_path)?
1578                            };
1579                            if let Some(path) = path_option {
1580                                Some(e.insert((path.raw_read_dir().await?, path)))
1581                            } else {
1582                                None
1583                            }
1584                        }
1585                    };
1586                    let Some((read_dir, parent_fs_path)) = read_dir else {
1587                        continue;
1588                    };
1589                    let RawDirectoryContent::Entries(entries) = &**read_dir else {
1590                        continue;
1591                    };
1592                    let Some(entry) = entries.get(last_segment) else {
1593                        continue;
1594                    };
1595                    match *entry {
1596                        RawDirectoryEntry::File => {
1597                            results.push((
1598                                index,
1599                                PatternMatch::File(
1600                                    concat(&prefix, str).into(),
1601                                    parent_fs_path.join(last_segment)?,
1602                                ),
1603                            ));
1604                        }
1605                        RawDirectoryEntry::Directory => results.push((
1606                            index,
1607                            PatternMatch::Directory(
1608                                concat(&prefix, str).into(),
1609                                parent_fs_path.join(last_segment)?,
1610                            ),
1611                        )),
1612                        RawDirectoryEntry::Symlink => {
1613                            let fs_path = parent_fs_path.join(last_segment)?;
1614                            let LinkContent::Link { link_type, .. } = &*fs_path.read_link().await?
1615                            else {
1616                                continue;
1617                            };
1618                            let path = concat(&prefix, str).into();
1619                            if link_type.contains(LinkType::DIRECTORY) {
1620                                results.push((index, PatternMatch::Directory(path, fs_path)));
1621                            } else {
1622                                results.push((index, PatternMatch::File(path, fs_path)))
1623                            }
1624                        }
1625                        _ => {}
1626                    }
1627                } else {
1628                    let subpath = &str[..=str.rfind('/').unwrap()];
1629                    if handled.insert(subpath) {
1630                        let joined = if force_in_lookup_dir {
1631                            lookup_dir.try_join_inside(subpath)?
1632                        } else {
1633                            lookup_dir.try_join(subpath)?
1634                        };
1635                        let Some(fs_path) = joined else {
1636                            continue;
1637                        };
1638                        nested.push((
1639                            0,
1640                            read_matches(
1641                                fs_path.clone(),
1642                                concat(&prefix, subpath).into(),
1643                                force_in_lookup_dir,
1644                                pattern,
1645                            ),
1646                        ));
1647                    }
1648                }
1649            }
1650            false
1651        } else {
1652            true
1653        }
1654    } else {
1655        true
1656    };
1657
1658    if slow_path {
1659        async {
1660            // Slow path: There are infinite matches for the pattern
1661            // We will enumerate the filesystem to find matches
1662            if !force_in_lookup_dir {
1663                // {prefix}..
1664                prefix.push_str("..");
1665                if let Some(pos) = pat.match_position(&prefix) {
1666                    results.push((
1667                        pos,
1668                        PatternMatch::Directory(prefix.clone().into(), lookup_dir.parent()),
1669                    ));
1670                }
1671
1672                // {prefix}../
1673                prefix.push('/');
1674                if let Some(pos) = pat.match_position(&prefix) {
1675                    results.push((
1676                        pos,
1677                        PatternMatch::Directory(prefix.clone().into(), lookup_dir.parent()),
1678                    ));
1679                }
1680                if let Some(pos) = pat.could_match_position(&prefix) {
1681                    nested.push((
1682                        pos,
1683                        read_matches(lookup_dir.parent(), prefix.clone().into(), false, pattern),
1684                    ));
1685                }
1686                prefix.pop();
1687                prefix.pop();
1688                prefix.pop();
1689            }
1690            {
1691                prefix.push('.');
1692                // {prefix}.
1693                if let Some(pos) = pat.match_position(&prefix) {
1694                    results.push((
1695                        pos,
1696                        PatternMatch::Directory(prefix.clone().into(), lookup_dir.clone()),
1697                    ));
1698                }
1699                prefix.pop();
1700            }
1701            if prefix.is_empty() {
1702                if let Some(pos) = pat.match_position("./") {
1703                    results.push((
1704                        pos,
1705                        PatternMatch::Directory(rcstr!("./"), lookup_dir.clone()),
1706                    ));
1707                }
1708                if let Some(pos) = pat.could_match_position("./") {
1709                    nested.push((
1710                        pos,
1711                        read_matches(lookup_dir.clone(), rcstr!("./"), false, pattern),
1712                    ));
1713                }
1714            } else {
1715                prefix.push('/');
1716                // {prefix}/
1717                if let Some(pos) = pat.could_match_position(&prefix) {
1718                    nested.push((
1719                        pos,
1720                        read_matches(
1721                            lookup_dir.clone(),
1722                            prefix.to_string().into(),
1723                            false,
1724                            pattern,
1725                        ),
1726                    ));
1727                }
1728                prefix.pop();
1729                prefix.push_str("./");
1730                // {prefix}./
1731                if let Some(pos) = pat.could_match_position(&prefix) {
1732                    nested.push((
1733                        pos,
1734                        read_matches(
1735                            lookup_dir.clone(),
1736                            prefix.to_string().into(),
1737                            false,
1738                            pattern,
1739                        ),
1740                    ));
1741                }
1742                prefix.pop();
1743                prefix.pop();
1744            }
1745            match &*lookup_dir.raw_read_dir().await? {
1746                RawDirectoryContent::Entries(map) => {
1747                    for (key, entry) in map.iter() {
1748                        match entry {
1749                            RawDirectoryEntry::File => {
1750                                let len = prefix.len();
1751                                prefix.push_str(key);
1752                                // {prefix}{key}
1753                                if let Some(pos) = pat.match_position(&prefix) {
1754                                    let path = lookup_dir.join(key)?;
1755                                    results.push((
1756                                        pos,
1757                                        PatternMatch::File(prefix.clone().into(), path),
1758                                    ));
1759                                }
1760                                prefix.truncate(len)
1761                            }
1762                            RawDirectoryEntry::Directory => {
1763                                let len = prefix.len();
1764                                prefix.push_str(key);
1765                                // {prefix}{key}
1766                                if prefix.ends_with('/') {
1767                                    prefix.pop();
1768                                }
1769                                if let Some(pos) = pat.match_position(&prefix) {
1770                                    let path = lookup_dir.join(key)?;
1771                                    results.push((
1772                                        pos,
1773                                        PatternMatch::Directory(prefix.clone().into(), path),
1774                                    ));
1775                                }
1776                                prefix.push('/');
1777                                // {prefix}{key}/
1778                                if let Some(pos) = pat.match_position(&prefix) {
1779                                    let path = lookup_dir.join(key)?;
1780                                    results.push((
1781                                        pos,
1782                                        PatternMatch::Directory(prefix.clone().into(), path),
1783                                    ));
1784                                }
1785                                if let Some(pos) = pat.could_match_position(&prefix) {
1786                                    let path = lookup_dir.join(key)?;
1787                                    nested.push((
1788                                        pos,
1789                                        read_matches(path, prefix.clone().into(), true, pattern),
1790                                    ));
1791                                }
1792                                prefix.truncate(len)
1793                            }
1794                            RawDirectoryEntry::Symlink => {
1795                                let len = prefix.len();
1796                                prefix.push_str(key);
1797                                // {prefix}{key}
1798                                if prefix.ends_with('/') {
1799                                    prefix.pop();
1800                                }
1801                                if let Some(pos) = pat.match_position(&prefix) {
1802                                    let fs_path = lookup_dir.join(key)?;
1803                                    if let LinkContent::Link { link_type, .. } =
1804                                        &*fs_path.read_link().await?
1805                                    {
1806                                        if link_type.contains(LinkType::DIRECTORY) {
1807                                            results.push((
1808                                                pos,
1809                                                PatternMatch::Directory(
1810                                                    prefix.clone().into(),
1811                                                    fs_path,
1812                                                ),
1813                                            ));
1814                                        } else {
1815                                            results.push((
1816                                                pos,
1817                                                PatternMatch::File(prefix.clone().into(), fs_path),
1818                                            ));
1819                                        }
1820                                    }
1821                                }
1822                                prefix.push('/');
1823                                if let Some(pos) = pat.match_position(&prefix) {
1824                                    let fs_path = lookup_dir.join(key)?;
1825                                    if let LinkContent::Link { link_type, .. } =
1826                                        &*fs_path.read_link().await?
1827                                        && link_type.contains(LinkType::DIRECTORY)
1828                                    {
1829                                        results.push((
1830                                            pos,
1831                                            PatternMatch::Directory(prefix.clone().into(), fs_path),
1832                                        ));
1833                                    }
1834                                }
1835                                if let Some(pos) = pat.could_match_position(&prefix) {
1836                                    let fs_path = lookup_dir.join(key)?;
1837                                    if let LinkContent::Link { link_type, .. } =
1838                                        &*fs_path.read_link().await?
1839                                        && link_type.contains(LinkType::DIRECTORY)
1840                                    {
1841                                        results.push((
1842                                            pos,
1843                                            PatternMatch::Directory(prefix.clone().into(), fs_path),
1844                                        ));
1845                                    }
1846                                }
1847                                prefix.truncate(len)
1848                            }
1849                            RawDirectoryEntry::Other => {}
1850                            RawDirectoryEntry::Error => {}
1851                        }
1852                    }
1853                }
1854                RawDirectoryContent::NotFound => {}
1855            };
1856            anyhow::Ok(())
1857        }
1858        .instrument(tracing::trace_span!("read_matches slow_path"))
1859        .await?;
1860    }
1861    if results.is_empty() && nested.len() == 1 {
1862        Ok(nested.into_iter().next().unwrap().1)
1863    } else {
1864        for (pos, nested) in nested.into_iter() {
1865            results.extend(nested.await?.iter().cloned().map(|p| (pos, p)));
1866        }
1867        results.sort_by(|(a, am), (b, bm)| (*a).cmp(b).then_with(|| am.name().cmp(bm.name())));
1868        Ok(Vc::cell(
1869            results.into_iter().map(|(_, p)| p).collect::<Vec<_>>(),
1870        ))
1871    }
1872}
1873
1874fn concat(a: &str, b: &str) -> String {
1875    let mut result = String::with_capacity(a.len() + b.len());
1876    result.push_str(a);
1877    result.push_str(b);
1878    result
1879}
1880
1881/// Returns the parent folder and the last segment of the path. When the last segment is unknown (e.
1882/// g. when using `../`) it returns the full path and an empty string.
1883fn split_last_segment(path: &str) -> (&str, &str) {
1884    if let Some((remaining_path, last_segment)) = path.rsplit_once('/') {
1885        match last_segment {
1886            "" => split_last_segment(remaining_path),
1887            "." => split_last_segment(remaining_path),
1888            ".." => match split_last_segment(remaining_path) {
1889                (_, "") => (path, ""),
1890                (parent_path, _) => split_last_segment(parent_path),
1891            },
1892            _ => (remaining_path, last_segment),
1893        }
1894    } else {
1895        match path {
1896            "" => ("", ""),
1897            "." => ("", ""),
1898            ".." => ("..", ""),
1899            _ => ("", path),
1900        }
1901    }
1902}
1903
1904#[cfg(test)]
1905mod tests {
1906    use std::path::Path;
1907
1908    use rstest::*;
1909    use turbo_rcstr::{RcStr, rcstr};
1910    use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
1911    use turbo_tasks_fs::{DiskFileSystem, FileSystem};
1912
1913    use super::{
1914        Pattern, longest_common_prefix, longest_common_suffix, read_matches, split_last_segment,
1915    };
1916
1917    #[test]
1918    fn longest_common_prefix_test() {
1919        assert_eq!(longest_common_prefix(&["ab"]), "ab");
1920        assert_eq!(longest_common_prefix(&["ab", "cd", "ef"]), "");
1921        assert_eq!(longest_common_prefix(&["ab1", "ab23", "ab456"]), "ab");
1922        assert_eq!(longest_common_prefix(&["abc", "abc", "abc"]), "abc");
1923        assert_eq!(longest_common_prefix(&["abc", "a", "abc"]), "a");
1924    }
1925
1926    #[test]
1927    fn longest_common_suffix_test() {
1928        assert_eq!(longest_common_suffix(&["ab"]), "ab");
1929        assert_eq!(longest_common_suffix(&["ab", "cd", "ef"]), "");
1930        assert_eq!(longest_common_suffix(&["1ab", "23ab", "456ab"]), "ab");
1931        assert_eq!(longest_common_suffix(&["abc", "abc", "abc"]), "abc");
1932        assert_eq!(longest_common_suffix(&["abc", "c", "abc"]), "c");
1933    }
1934
1935    #[test]
1936    fn normalize() {
1937        let a = Pattern::Constant(rcstr!("a"));
1938        let b = Pattern::Constant(rcstr!("b"));
1939        let c = Pattern::Constant(rcstr!("c"));
1940        let s = Pattern::Constant(rcstr!("/"));
1941        let d = Pattern::Dynamic;
1942        {
1943            let mut p = Pattern::Concatenation(vec![
1944                Pattern::Alternatives(vec![a.clone(), b.clone()]),
1945                s.clone(),
1946                c.clone(),
1947            ]);
1948            p.normalize();
1949            assert_eq!(
1950                p,
1951                Pattern::Alternatives(vec![
1952                    Pattern::Constant(rcstr!("a/c")),
1953                    Pattern::Constant(rcstr!("b/c")),
1954                ])
1955            );
1956        }
1957
1958        #[allow(clippy::redundant_clone)] // alignment
1959        {
1960            let mut p = Pattern::Concatenation(vec![
1961                Pattern::Alternatives(vec![a.clone(), b.clone(), d.clone()]),
1962                s.clone(),
1963                Pattern::Alternatives(vec![b.clone(), c.clone(), d.clone()]),
1964            ]);
1965            p.normalize();
1966
1967            assert_eq!(
1968                p,
1969                Pattern::Alternatives(vec![
1970                    Pattern::Constant(rcstr!("a/b")),
1971                    Pattern::Constant(rcstr!("b/b")),
1972                    Pattern::Concatenation(vec![Pattern::Dynamic, Pattern::Constant(rcstr!("/b"))]),
1973                    Pattern::Constant(rcstr!("a/c")),
1974                    Pattern::Constant(rcstr!("b/c")),
1975                    Pattern::Concatenation(vec![Pattern::Dynamic, Pattern::Constant(rcstr!("/c"))]),
1976                    Pattern::Concatenation(vec![Pattern::Constant(rcstr!("a/")), Pattern::Dynamic]),
1977                    Pattern::Concatenation(vec![Pattern::Constant(rcstr!("b/")), Pattern::Dynamic]),
1978                    Pattern::Concatenation(vec![
1979                        Pattern::Dynamic,
1980                        Pattern::Constant(rcstr!("/")),
1981                        Pattern::Dynamic
1982                    ]),
1983                ])
1984            );
1985        }
1986
1987        #[allow(clippy::redundant_clone)] // alignment
1988        {
1989            let mut p = Pattern::Alternatives(vec![a.clone()]);
1990            p.normalize();
1991
1992            assert_eq!(p, a);
1993        }
1994
1995        #[allow(clippy::redundant_clone)] // alignment
1996        {
1997            let mut p = Pattern::Alternatives(vec![Pattern::Dynamic, Pattern::Dynamic]);
1998            p.normalize();
1999
2000            assert_eq!(p, Pattern::Dynamic);
2001        }
2002    }
2003
2004    #[test]
2005    fn with_normalized_path() {
2006        assert!(
2007            Pattern::Constant(rcstr!("a/../.."))
2008                .with_normalized_path()
2009                .is_none()
2010        );
2011        assert_eq!(
2012            Pattern::Constant(rcstr!("a/b/../c"))
2013                .with_normalized_path()
2014                .unwrap(),
2015            Pattern::Constant(rcstr!("a/c"))
2016        );
2017        assert_eq!(
2018            Pattern::Alternatives(vec![
2019                Pattern::Constant(rcstr!("a/b/../c")),
2020                Pattern::Constant(rcstr!("a/b/../c/d"))
2021            ])
2022            .with_normalized_path()
2023            .unwrap(),
2024            Pattern::Alternatives(vec![
2025                Pattern::Constant(rcstr!("a/c")),
2026                Pattern::Constant(rcstr!("a/c/d"))
2027            ])
2028        );
2029        assert_eq!(
2030            Pattern::Constant(rcstr!("a/b/"))
2031                .with_normalized_path()
2032                .unwrap(),
2033            Pattern::Constant(rcstr!("a/b"))
2034        );
2035
2036        // Dynamic is a segment itself
2037        assert_eq!(
2038            Pattern::Concatenation(vec![
2039                Pattern::Constant(rcstr!("a/b/")),
2040                Pattern::Dynamic,
2041                Pattern::Constant(rcstr!("../c"))
2042            ])
2043            .with_normalized_path()
2044            .unwrap(),
2045            Pattern::Concatenation(vec![
2046                Pattern::Constant(rcstr!("a/b/")),
2047                Pattern::Dynamic,
2048                Pattern::Constant(rcstr!("../c"))
2049            ])
2050        );
2051
2052        // Dynamic is part of a segment
2053        assert_eq!(
2054            Pattern::Concatenation(vec![
2055                Pattern::Constant(rcstr!("a/b")),
2056                Pattern::Dynamic,
2057                Pattern::Constant(rcstr!("../c"))
2058            ])
2059            .with_normalized_path()
2060            .unwrap(),
2061            Pattern::Concatenation(vec![
2062                Pattern::Constant(rcstr!("a/b")),
2063                Pattern::Dynamic,
2064                Pattern::Constant(rcstr!("../c"))
2065            ])
2066        );
2067        assert_eq!(
2068            Pattern::Concatenation(vec![
2069                Pattern::Constant(rcstr!("src/")),
2070                Pattern::Dynamic,
2071                Pattern::Constant(rcstr!(".js"))
2072            ])
2073            .with_normalized_path()
2074            .unwrap(),
2075            Pattern::Concatenation(vec![
2076                Pattern::Constant(rcstr!("src/")),
2077                Pattern::Dynamic,
2078                Pattern::Constant(rcstr!(".js"))
2079            ])
2080        );
2081    }
2082
2083    #[test]
2084    fn is_match() {
2085        let pat = Pattern::Concatenation(vec![
2086            Pattern::Constant(rcstr!(".")),
2087            Pattern::Constant(rcstr!("/")),
2088            Pattern::Dynamic,
2089            Pattern::Constant(rcstr!(".js")),
2090        ]);
2091        assert!(pat.could_match(""));
2092        assert!(pat.could_match("./"));
2093        assert!(!pat.is_match("./"));
2094        assert!(pat.is_match("./index.js"));
2095        assert!(!pat.is_match("./index"));
2096        assert!(pat.is_match("./foo/index.js"));
2097        assert!(pat.is_match("./foo/bar/index.js"));
2098
2099        // forbidden:
2100        assert!(!pat.is_match("./../index.js"));
2101        assert!(!pat.is_match("././index.js"));
2102        assert!(!pat.is_match("./.git/index.js"));
2103        assert!(!pat.is_match("./inner/../index.js"));
2104        assert!(!pat.is_match("./inner/./index.js"));
2105        assert!(!pat.is_match("./inner/.git/index.js"));
2106        assert!(!pat.could_match("./../"));
2107        assert!(!pat.could_match("././"));
2108        assert!(!pat.could_match("./.git/"));
2109        assert!(!pat.could_match("./inner/../"));
2110        assert!(!pat.could_match("./inner/./"));
2111        assert!(!pat.could_match("./inner/.git/"));
2112    }
2113
2114    #[test]
2115    fn is_match_dynamic_no_slash() {
2116        let pat = Pattern::Concatenation(vec![
2117            Pattern::Constant(rcstr!(".")),
2118            Pattern::Constant(rcstr!("/")),
2119            Pattern::DynamicNoSlash,
2120            Pattern::Constant(rcstr!(".js")),
2121        ]);
2122        assert!(pat.could_match(""));
2123        assert!(pat.could_match("./"));
2124        assert!(!pat.is_match("./"));
2125        assert!(pat.is_match("./index.js"));
2126        assert!(!pat.is_match("./index"));
2127        assert!(!pat.is_match("./foo/index.js"));
2128        assert!(!pat.is_match("./foo/bar/index.js"));
2129    }
2130
2131    #[test]
2132    fn constant_prefix() {
2133        assert_eq!(
2134            Pattern::Constant(rcstr!("a/b/c.js")).constant_prefix(),
2135            "a/b/c.js",
2136        );
2137
2138        let pat = Pattern::Alternatives(vec![
2139            Pattern::Constant(rcstr!("a/b/x")),
2140            Pattern::Constant(rcstr!("a/b/y")),
2141            Pattern::Concatenation(vec![Pattern::Constant(rcstr!("a/b/c/")), Pattern::Dynamic]),
2142        ]);
2143        assert_eq!(pat.constant_prefix(), "a/b/");
2144    }
2145
2146    #[test]
2147    fn constant_suffix() {
2148        assert_eq!(
2149            Pattern::Constant(rcstr!("a/b/c.js")).constant_suffix(),
2150            "a/b/c.js",
2151        );
2152
2153        let pat = Pattern::Alternatives(vec![
2154            Pattern::Constant(rcstr!("a/b/x.js")),
2155            Pattern::Constant(rcstr!("a/b/y.js")),
2156            Pattern::Concatenation(vec![
2157                Pattern::Constant(rcstr!("a/b/c/")),
2158                Pattern::Dynamic,
2159                Pattern::Constant(rcstr!(".js")),
2160            ]),
2161        ]);
2162        assert_eq!(pat.constant_suffix(), ".js");
2163    }
2164
2165    #[test]
2166    fn strip_prefix() {
2167        fn strip(mut pat: Pattern, n: usize) -> Pattern {
2168            pat.strip_prefix_len(n);
2169            pat
2170        }
2171
2172        assert_eq!(
2173            strip(Pattern::Constant(rcstr!("a/b")), 0),
2174            Pattern::Constant(rcstr!("a/b"))
2175        );
2176
2177        assert_eq!(
2178            strip(
2179                Pattern::Alternatives(vec![
2180                    Pattern::Constant(rcstr!("a/b/x")),
2181                    Pattern::Constant(rcstr!("a/b/y")),
2182                ]),
2183                2
2184            ),
2185            Pattern::Alternatives(vec![
2186                Pattern::Constant(rcstr!("b/x")),
2187                Pattern::Constant(rcstr!("b/y")),
2188            ])
2189        );
2190
2191        assert_eq!(
2192            strip(
2193                Pattern::Concatenation(vec![
2194                    Pattern::Constant(rcstr!("a/")),
2195                    Pattern::Constant(rcstr!("b")),
2196                    Pattern::Constant(rcstr!("/")),
2197                    Pattern::Constant(rcstr!("y/")),
2198                    Pattern::Dynamic
2199                ]),
2200                4
2201            ),
2202            Pattern::Concatenation(vec![Pattern::Constant(rcstr!("y/")), Pattern::Dynamic]),
2203        );
2204    }
2205
2206    #[test]
2207    fn strip_suffix() {
2208        fn strip(mut pat: Pattern, n: usize) -> Pattern {
2209            pat.strip_suffix_len(n);
2210            pat
2211        }
2212
2213        assert_eq!(
2214            strip(Pattern::Constant(rcstr!("a/b")), 0),
2215            Pattern::Constant(rcstr!("a/b"))
2216        );
2217
2218        assert_eq!(
2219            strip(
2220                Pattern::Alternatives(vec![
2221                    Pattern::Constant(rcstr!("x/b/a")),
2222                    Pattern::Constant(rcstr!("y/b/a")),
2223                ]),
2224                2
2225            ),
2226            Pattern::Alternatives(vec![
2227                Pattern::Constant(rcstr!("x/b")),
2228                Pattern::Constant(rcstr!("y/b")),
2229            ])
2230        );
2231
2232        assert_eq!(
2233            strip(
2234                Pattern::Concatenation(vec![
2235                    Pattern::Dynamic,
2236                    Pattern::Constant(rcstr!("/a/")),
2237                    Pattern::Constant(rcstr!("b")),
2238                    Pattern::Constant(rcstr!("/")),
2239                    Pattern::Constant(rcstr!("y/")),
2240                ]),
2241                4
2242            ),
2243            Pattern::Concatenation(vec![Pattern::Dynamic, Pattern::Constant(rcstr!("/a/")),]),
2244        );
2245    }
2246
2247    #[test]
2248    fn spread_into_star() {
2249        let pat = Pattern::Constant(rcstr!("xyz"));
2250        assert_eq!(
2251            pat.spread_into_star("before/after"),
2252            Pattern::Constant(rcstr!("before/after")),
2253        );
2254
2255        let pat =
2256            Pattern::Concatenation(vec![Pattern::Constant(rcstr!("a/b/c/")), Pattern::Dynamic]);
2257        assert_eq!(
2258            pat.spread_into_star("before/*/after"),
2259            Pattern::Concatenation(vec![
2260                Pattern::Constant(rcstr!("before/a/b/c/")),
2261                Pattern::Dynamic,
2262                Pattern::Constant(rcstr!("/after"))
2263            ])
2264        );
2265
2266        let pat = Pattern::Alternatives(vec![
2267            Pattern::Concatenation(vec![Pattern::Constant(rcstr!("a/")), Pattern::Dynamic]),
2268            Pattern::Concatenation(vec![Pattern::Constant(rcstr!("b/")), Pattern::Dynamic]),
2269        ]);
2270        assert_eq!(
2271            pat.spread_into_star("before/*/after"),
2272            Pattern::Alternatives(vec![
2273                Pattern::Concatenation(vec![
2274                    Pattern::Constant(rcstr!("before/a/")),
2275                    Pattern::Dynamic,
2276                    Pattern::Constant(rcstr!("/after"))
2277                ]),
2278                Pattern::Concatenation(vec![
2279                    Pattern::Constant(rcstr!("before/b/")),
2280                    Pattern::Dynamic,
2281                    Pattern::Constant(rcstr!("/after"))
2282                ]),
2283            ])
2284        );
2285
2286        let pat = Pattern::Alternatives(vec![
2287            Pattern::Constant(rcstr!("a")),
2288            Pattern::Constant(rcstr!("b")),
2289        ]);
2290        assert_eq!(
2291            pat.spread_into_star("before/*/*"),
2292            Pattern::Alternatives(vec![
2293                Pattern::Constant(rcstr!("before/a/a")),
2294                Pattern::Constant(rcstr!("before/b/b")),
2295            ])
2296        );
2297
2298        let pat = Pattern::Dynamic;
2299        assert_eq!(
2300            pat.spread_into_star("before/*/*"),
2301            Pattern::Concatenation(vec![
2302                // TODO currently nothing ensures that both Dynamic parts are equal
2303                Pattern::Constant(rcstr!("before/")),
2304                Pattern::Dynamic,
2305                Pattern::Constant(rcstr!("/")),
2306                Pattern::Dynamic
2307            ])
2308        );
2309    }
2310
2311    #[rstest]
2312    #[case::dynamic(Pattern::Dynamic)]
2313    #[case::dynamic_concat(Pattern::Concatenation(vec![Pattern::Dynamic, Pattern::Constant(rcstr!(".js"))]))]
2314    fn dynamic_match(#[case] pat: Pattern) {
2315        assert!(pat.could_match(""));
2316        assert!(pat.is_match("index.js"));
2317
2318        // forbidden:
2319        assert!(!pat.could_match("./"));
2320        assert!(!pat.is_match("./"));
2321        assert!(!pat.could_match("."));
2322        assert!(!pat.is_match("."));
2323        assert!(!pat.could_match("../"));
2324        assert!(!pat.is_match("../"));
2325        assert!(!pat.could_match(".."));
2326        assert!(!pat.is_match(".."));
2327        assert!(!pat.is_match("./../index.js"));
2328        assert!(!pat.is_match("././index.js"));
2329        assert!(!pat.is_match("./.git/index.js"));
2330        assert!(!pat.is_match("./inner/../index.js"));
2331        assert!(!pat.is_match("./inner/./index.js"));
2332        assert!(!pat.is_match("./inner/.git/index.js"));
2333        assert!(!pat.could_match("./../"));
2334        assert!(!pat.could_match("././"));
2335        assert!(!pat.could_match("./.git/"));
2336        assert!(!pat.could_match("./inner/../"));
2337        assert!(!pat.could_match("./inner/./"));
2338        assert!(!pat.could_match("./inner/.git/"));
2339        assert!(!pat.could_match("dir//"));
2340        assert!(!pat.could_match("dir//dir"));
2341        assert!(!pat.could_match("dir///dir"));
2342        assert!(!pat.could_match("/"));
2343        assert!(!pat.could_match("//"));
2344        assert!(!pat.could_match("/ROOT/"));
2345
2346        assert!(!pat.could_match("node_modules"));
2347        assert!(!pat.could_match("node_modules/package"));
2348        assert!(!pat.could_match("nested/node_modules"));
2349        assert!(!pat.could_match("nested/node_modules/package"));
2350
2351        // forbidden match
2352        assert!(pat.could_match("file.map"));
2353        assert!(!pat.is_match("file.map"));
2354        assert!(pat.is_match("file.map/file.js"));
2355        assert!(!pat.is_match("file.d.ts"));
2356        assert!(!pat.is_match("file.d.ts.map"));
2357        assert!(!pat.is_match("file.d.ts.map"));
2358        assert!(!pat.is_match("dir/file.d.ts.map"));
2359        assert!(!pat.is_match("dir/inner/file.d.ts.map"));
2360        assert!(pat.could_match("dir/inner/file.d.ts.map"));
2361    }
2362
2363    #[rstest]
2364    #[case::slash(Pattern::Concatenation(vec![Pattern::Constant(rcstr!("node_modules/")),Pattern::Dynamic]))]
2365    #[case::nested(Pattern::Constant(rcstr!("node_modules")).or_any_nested_file())]
2366    fn dynamic_match_node_modules(#[case] pat: Pattern) {
2367        assert!(!pat.is_match("node_modules/package"));
2368        assert!(!pat.could_match("node_modules/package"));
2369        assert!(!pat.is_match("node_modules/package/index.js"));
2370        assert!(!pat.could_match("node_modules/package/index.js"));
2371    }
2372
2373    #[rstest]
2374    fn dynamic_match2() {
2375        let pat = Pattern::Concatenation(vec![
2376            Pattern::Dynamic,
2377            Pattern::Constant(rcstr!("/")),
2378            Pattern::Dynamic,
2379        ]);
2380        assert!(pat.could_match("dir"));
2381        assert!(pat.could_match("dir/"));
2382        assert!(pat.is_match("dir/index.js"));
2383
2384        // forbidden:
2385        assert!(!pat.could_match("./"));
2386        assert!(!pat.is_match("./"));
2387        assert!(!pat.could_match("."));
2388        assert!(!pat.is_match("."));
2389        assert!(!pat.could_match("../"));
2390        assert!(!pat.is_match("../"));
2391        assert!(!pat.could_match(".."));
2392        assert!(!pat.is_match(".."));
2393        assert!(!pat.is_match("./../index.js"));
2394        assert!(!pat.is_match("././index.js"));
2395        assert!(!pat.is_match("./.git/index.js"));
2396        assert!(!pat.is_match("./inner/../index.js"));
2397        assert!(!pat.is_match("./inner/./index.js"));
2398        assert!(!pat.is_match("./inner/.git/index.js"));
2399        assert!(!pat.could_match("./../"));
2400        assert!(!pat.could_match("././"));
2401        assert!(!pat.could_match("./.git/"));
2402        assert!(!pat.could_match("./inner/../"));
2403        assert!(!pat.could_match("./inner/./"));
2404        assert!(!pat.could_match("./inner/.git/"));
2405        assert!(!pat.could_match("dir//"));
2406        assert!(!pat.could_match("dir//dir"));
2407        assert!(!pat.could_match("dir///dir"));
2408        assert!(!pat.could_match("/ROOT/"));
2409
2410        assert!(!pat.could_match("node_modules"));
2411        assert!(!pat.could_match("node_modules/package"));
2412        assert!(!pat.could_match("nested/node_modules"));
2413        assert!(!pat.could_match("nested/node_modules/package"));
2414
2415        // forbidden match
2416        assert!(pat.could_match("dir/file.map"));
2417        assert!(!pat.is_match("dir/file.map"));
2418        assert!(pat.is_match("file.map/file.js"));
2419        assert!(!pat.is_match("dir/file.d.ts"));
2420        assert!(!pat.is_match("dir/file.d.ts.map"));
2421        assert!(!pat.is_match("dir/file.d.ts.map"));
2422        assert!(!pat.is_match("dir/file.d.ts.map"));
2423        assert!(!pat.is_match("dir/inner/file.d.ts.map"));
2424        assert!(pat.could_match("dir/inner/file.d.ts.map"));
2425    }
2426
2427    #[rstest]
2428    #[case::dynamic(Pattern::Dynamic)]
2429    #[case::dynamic_concat(Pattern::Concatenation(vec![Pattern::Dynamic, Pattern::Constant(rcstr!(".js"))]))]
2430    #[case::dynamic_concat2(Pattern::Concatenation(vec![
2431        Pattern::Dynamic,
2432        Pattern::Constant(rcstr!("/")),
2433        Pattern::Dynamic,
2434    ]))]
2435    #[case::dynamic_alt_concat(Pattern::alternatives(vec![
2436        Pattern::Concatenation(vec![
2437            Pattern::Dynamic,
2438            Pattern::Constant(rcstr!("/")),
2439            Pattern::Dynamic,
2440        ]),
2441        Pattern::Dynamic,
2442    ]))]
2443    fn split_could_match(#[case] pat: Pattern) {
2444        let (abs, rel) = pat.split_could_match("/ROOT/");
2445        assert!(abs.is_none());
2446        assert!(rel.is_some());
2447    }
2448
2449    #[rstest]
2450    #[case::dynamic(Pattern::Dynamic, "feijf", None)]
2451    #[case::dynamic_concat(
2452        Pattern::Concatenation(vec![Pattern::Dynamic, Pattern::Constant(rcstr!(".js"))]),
2453        "hello.", None
2454    )]
2455    #[case::constant(Pattern::Constant(rcstr!("Hello World")), "Hello ", Some(vec![("World", true)]))]
2456    #[case::alternatives(
2457        Pattern::Alternatives(vec![
2458            Pattern::Constant(rcstr!("Hello World")),
2459            Pattern::Constant(rcstr!("Hello All"))
2460        ]), "Hello ", Some(vec![("World", true), ("All", true)])
2461    )]
2462    #[case::alternatives_non_end(
2463        Pattern::Alternatives(vec![
2464            Pattern::Constant(rcstr!("Hello World")),
2465            Pattern::Constant(rcstr!("Hello All")),
2466            Pattern::Concatenation(vec![Pattern::Constant(rcstr!("Hello more")), Pattern::Dynamic])
2467        ]), "Hello ", Some(vec![("World", true), ("All", true), ("more", false)])
2468    )]
2469    #[case::request_with_extensions(
2470        Pattern::Alternatives(vec![
2471            Pattern::Constant(rcstr!("./file.js")),
2472            Pattern::Constant(rcstr!("./file.ts")),
2473            Pattern::Constant(rcstr!("./file.cjs")),
2474        ]), "./", Some(vec![("file.js", true), ("file.ts", true), ("file.cjs", true)])
2475    )]
2476    fn next_constants(
2477        #[case] pat: Pattern,
2478        #[case] value: &str,
2479        #[case] expected: Option<Vec<(&str, bool)>>,
2480    ) {
2481        assert_eq!(pat.next_constants(value), expected);
2482    }
2483
2484    #[test]
2485    fn replace_final_constants() {
2486        fn f(mut p: Pattern, cb: &impl Fn(&RcStr) -> Option<Pattern>) -> Pattern {
2487            p.replace_final_constants(cb);
2488            p
2489        }
2490
2491        let js_to_ts_tsx = |c: &RcStr| -> Option<Pattern> {
2492            c.strip_suffix(".js").map(|rest| {
2493                let new_ending = Pattern::Alternatives(vec![
2494                    Pattern::Constant(rcstr!(".ts")),
2495                    Pattern::Constant(rcstr!(".tsx")),
2496                    Pattern::Constant(rcstr!(".js")),
2497                ]);
2498                if !rest.is_empty() {
2499                    Pattern::Concatenation(vec![Pattern::Constant(rest.into()), new_ending])
2500                } else {
2501                    new_ending
2502                }
2503            })
2504        };
2505
2506        assert_eq!(
2507            f(
2508                Pattern::Concatenation(vec![
2509                    Pattern::Constant(rcstr!(".")),
2510                    Pattern::Constant(rcstr!("/")),
2511                    Pattern::Dynamic,
2512                    Pattern::Alternatives(vec![
2513                        Pattern::Constant(rcstr!(".js")),
2514                        Pattern::Constant(rcstr!(".node")),
2515                    ])
2516                ]),
2517                &js_to_ts_tsx
2518            ),
2519            Pattern::Concatenation(vec![
2520                Pattern::Constant(rcstr!(".")),
2521                Pattern::Constant(rcstr!("/")),
2522                Pattern::Dynamic,
2523                Pattern::Alternatives(vec![
2524                    Pattern::Alternatives(vec![
2525                        Pattern::Constant(rcstr!(".ts")),
2526                        Pattern::Constant(rcstr!(".tsx")),
2527                        Pattern::Constant(rcstr!(".js")),
2528                    ]),
2529                    Pattern::Constant(rcstr!(".node")),
2530                ])
2531            ]),
2532        );
2533        assert_eq!(
2534            f(
2535                Pattern::Concatenation(vec![
2536                    Pattern::Constant(rcstr!(".")),
2537                    Pattern::Constant(rcstr!("/")),
2538                    Pattern::Constant(rcstr!("abc.js")),
2539                ]),
2540                &js_to_ts_tsx
2541            ),
2542            Pattern::Concatenation(vec![
2543                Pattern::Constant(rcstr!(".")),
2544                Pattern::Constant(rcstr!("/")),
2545                Pattern::Concatenation(vec![
2546                    Pattern::Constant(rcstr!("abc")),
2547                    Pattern::Alternatives(vec![
2548                        Pattern::Constant(rcstr!(".ts")),
2549                        Pattern::Constant(rcstr!(".tsx")),
2550                        Pattern::Constant(rcstr!(".js")),
2551                    ])
2552                ]),
2553            ])
2554        );
2555    }
2556
2557    #[test]
2558    fn match_apply_template() {
2559        assert_eq!(
2560            Pattern::Concatenation(vec![
2561                Pattern::Constant(rcstr!("a/b/")),
2562                Pattern::Dynamic,
2563                Pattern::Constant(rcstr!(".ts")),
2564            ])
2565            .match_apply_template(
2566                "a/b/foo.ts",
2567                &Pattern::Concatenation(vec![
2568                    Pattern::Constant(rcstr!("@/a/b/")),
2569                    Pattern::Dynamic,
2570                    Pattern::Constant(rcstr!(".js")),
2571                ])
2572            )
2573            .as_deref(),
2574            Some("@/a/b/foo.js")
2575        );
2576        assert_eq!(
2577            Pattern::Concatenation(vec![
2578                Pattern::Constant(rcstr!("b/")),
2579                Pattern::Dynamic,
2580                Pattern::Constant(rcstr!(".ts")),
2581            ])
2582            .match_apply_template(
2583                "a/b/foo.ts",
2584                &Pattern::Concatenation(vec![
2585                    Pattern::Constant(rcstr!("@/a/b/")),
2586                    Pattern::Dynamic,
2587                    Pattern::Constant(rcstr!(".js")),
2588                ])
2589            )
2590            .as_deref(),
2591            None,
2592        );
2593        assert_eq!(
2594            Pattern::Concatenation(vec![
2595                Pattern::Constant(rcstr!("a/b/")),
2596                Pattern::Dynamic,
2597                Pattern::Constant(rcstr!(".ts")),
2598            ])
2599            .match_apply_template(
2600                "a/b/foo.ts",
2601                &Pattern::Concatenation(vec![
2602                    Pattern::Constant(rcstr!("@/a/b/x")),
2603                    Pattern::Constant(rcstr!(".js")),
2604                ])
2605            )
2606            .as_deref(),
2607            None,
2608        );
2609        assert_eq!(
2610            Pattern::Concatenation(vec![Pattern::Constant(rcstr!("./sub/")), Pattern::Dynamic])
2611                .match_apply_template(
2612                    "./sub/file1",
2613                    &Pattern::Concatenation(vec![
2614                        Pattern::Constant(rcstr!("@/sub/")),
2615                        Pattern::Dynamic
2616                    ])
2617                )
2618                .as_deref(),
2619            Some("@/sub/file1"),
2620        );
2621    }
2622
2623    #[test]
2624    fn test_split_last_segment() {
2625        assert_eq!(split_last_segment(""), ("", ""));
2626        assert_eq!(split_last_segment("a"), ("", "a"));
2627        assert_eq!(split_last_segment("a/"), ("", "a"));
2628        assert_eq!(split_last_segment("a/b"), ("a", "b"));
2629        assert_eq!(split_last_segment("a/b/"), ("a", "b"));
2630        assert_eq!(split_last_segment("a/b/c"), ("a/b", "c"));
2631        assert_eq!(split_last_segment("a/b/."), ("a", "b"));
2632        assert_eq!(split_last_segment("a/b/.."), ("", "a"));
2633        assert_eq!(split_last_segment("a/b/c/.."), ("a", "b"));
2634        assert_eq!(split_last_segment("a/b/c/../.."), ("", "a"));
2635        assert_eq!(split_last_segment("a/b/c/d/../.."), ("a", "b"));
2636        assert_eq!(split_last_segment("a/b/c/../d/.."), ("a", "b"));
2637        assert_eq!(split_last_segment("a/b/../c/d/.."), ("a/b/..", "c"));
2638        assert_eq!(split_last_segment("."), ("", ""));
2639        assert_eq!(split_last_segment("./"), ("", ""));
2640        assert_eq!(split_last_segment(".."), ("..", ""));
2641        assert_eq!(split_last_segment("../"), ("..", ""));
2642        assert_eq!(split_last_segment("./../"), ("./..", ""));
2643        assert_eq!(split_last_segment("../../"), ("../..", ""));
2644        assert_eq!(split_last_segment("../../."), ("../..", ""));
2645        assert_eq!(split_last_segment("../.././"), ("../..", ""));
2646        assert_eq!(split_last_segment("a/.."), ("", ""));
2647        assert_eq!(split_last_segment("a/../"), ("", ""));
2648        assert_eq!(split_last_segment("a/../.."), ("a/../..", ""));
2649        assert_eq!(split_last_segment("a/../../"), ("a/../..", ""));
2650        assert_eq!(split_last_segment("a/././../"), ("", ""));
2651        assert_eq!(split_last_segment("../a"), ("..", "a"));
2652        assert_eq!(split_last_segment("../a/"), ("..", "a"));
2653        assert_eq!(split_last_segment("../../a"), ("../..", "a"));
2654        assert_eq!(split_last_segment("../../a/"), ("../..", "a"));
2655    }
2656
2657    #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2658    async fn test_read_matches() {
2659        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2660            BackendOptions::default(),
2661            noop_backing_storage(),
2662        ));
2663        tt.run_once(async {
2664            crate::register();
2665            let root = DiskFileSystem::new(
2666                rcstr!("test"),
2667                Path::new(env!("CARGO_MANIFEST_DIR"))
2668                    .join("tests/pattern/read_matches")
2669                    .to_str()
2670                    .unwrap()
2671                    .into(),
2672            )
2673            .root()
2674            .owned()
2675            .await?;
2676
2677            // node_modules shouldn't be matched by Dynamic here
2678            assert_eq!(
2679                vec!["index.js", "sub", "sub/", "sub/foo-a.js", "sub/foo-b.js"],
2680                read_matches(
2681                    root.clone(),
2682                    rcstr!(""),
2683                    false,
2684                    Pattern::new(Pattern::Dynamic),
2685                )
2686                .await?
2687                .into_iter()
2688                .map(|m| m.name())
2689                .collect::<Vec<_>>()
2690            );
2691
2692            // basic dynamic file suffix
2693            assert_eq!(
2694                vec!["sub/foo-a.js", "sub/foo-b.js"],
2695                read_matches(
2696                    root.clone(),
2697                    rcstr!(""),
2698                    false,
2699                    Pattern::new(Pattern::concat([
2700                        Pattern::Constant(rcstr!("sub/foo")),
2701                        Pattern::Dynamic,
2702                    ])),
2703                )
2704                .await?
2705                .into_iter()
2706                .map(|m| m.name())
2707                .collect::<Vec<_>>()
2708            );
2709
2710            // read_matches "node_modules/<dynamic>" should not return anything inside. We never
2711            // want to enumerate the list of packages here.
2712            assert_eq!(
2713                vec!["node_modules"] as Vec<&str>,
2714                read_matches(
2715                    root.clone(),
2716                    rcstr!(""),
2717                    false,
2718                    Pattern::new(Pattern::Constant(rcstr!("node_modules")).or_any_nested_file()),
2719                )
2720                .await?
2721                .into_iter()
2722                .map(|m| m.name())
2723                .collect::<Vec<_>>()
2724            );
2725
2726            anyhow::Ok(())
2727        })
2728        .await
2729        .unwrap();
2730    }
2731}