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
32impl TaskInput for Pattern {
36 fn is_transient(&self) -> bool {
37 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 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 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 pub fn as_constant_string(&self) -> Option<&RcStr> {
124 match self {
125 Pattern::Constant(str) => Some(str),
126 _ => None,
127 }
128 }
129
130 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 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 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 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 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 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 pub fn push(&mut self, pat: Pattern) {
391 if let Pattern::Constant(this) = &*self
392 && this.is_empty()
393 {
394 *self = pat;
396 return;
397 }
398 if let Pattern::Constant(pat) = &pat
399 && pat.is_empty()
400 {
401 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 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 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 continue;
508 }
509 ".." => {
510 if segments.is_empty() {
511 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 segments.truncate(segments.len() - 2);
522 continue;
523 }
524
525 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 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 pub fn normalize(&mut self) {
586 match self {
587 Pattern::Dynamic | Pattern::DynamicNoSlash | Pattern::Constant(_) => {
588 }
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 let mut new_alternatives: Vec<Vec<Pattern>> = vec![Vec::new()];
636 for part in list.drain(..) {
637 if let Pattern::Alternatives(list) = part {
638 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 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 *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 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 } else {
701 new_parts.push(Pattern::Dynamic);
702 }
703 }
704 Pattern::DynamicNoSlash => {
705 if let Some(Pattern::DynamicNoSlash) = new_parts.last() {
706 } 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 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 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 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 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 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 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 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 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 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 FolderMatched,
1345 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 None,
1364 Partial,
1366 Consumed {
1368 remaining: &'a str,
1370 any_offset: Option<usize>,
1373 in_node_modules: InNodeModules,
1376 },
1377}
1378
1379impl MatchResult<'_> {
1380 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 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 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#[turbo_tasks::value(transparent)]
1517pub struct PatternMatches(Vec<PatternMatch>);
1518
1519#[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 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 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 if !force_in_lookup_dir {
1663 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.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 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 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 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 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 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 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 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
1881fn 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)] {
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)] {
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)] {
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 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 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 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 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 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 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 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 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 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 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 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}