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