1use std::{
2 collections::{VecDeque, hash_map::Entry},
3 mem::take,
4 sync::LazyLock,
5};
6
7use anyhow::{Result, bail};
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) -> 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(&mut self, cb: &impl Fn(&RcStr) -> Option<Pattern>) -> bool {
1232 let mut replaced = false;
1233 match self {
1234 Pattern::Constant(c) => {
1235 if let Some(replacement) = cb(c) {
1236 *self = replacement;
1237 replaced = true;
1238 }
1239 }
1240 Pattern::Dynamic | Pattern::DynamicNoSlash => {}
1241 Pattern::Alternatives(list) => {
1242 for i in list {
1243 replaced = i.replace_final_constants(cb) || replaced;
1244 }
1245 }
1246 Pattern::Concatenation(list) => {
1247 if let Some(i) = list.last_mut() {
1248 replaced = i.replace_final_constants(cb) || replaced;
1249 }
1250 }
1251 }
1252 replaced
1253 }
1254
1255 pub fn replace_constants(&mut self, cb: &impl Fn(&RcStr) -> Option<Pattern>) -> bool {
1258 let mut replaced = false;
1259 match self {
1260 Pattern::Constant(c) => {
1261 if let Some(replacement) = cb(c) {
1262 *self = replacement;
1263 replaced = true;
1264 }
1265 }
1266 Pattern::Dynamic | Pattern::DynamicNoSlash => {}
1267 Pattern::Concatenation(list) | Pattern::Alternatives(list) => {
1268 for i in list {
1269 replaced = i.replace_constants(cb) || replaced;
1270 }
1271 }
1272 }
1273 replaced
1274 }
1275
1276 pub fn match_apply_template(&self, value: &str, target: &Pattern) -> Option<String> {
1281 let match_idx = self.match_position(value)?;
1282 let source = match self {
1283 Pattern::Alternatives(list) => list.get(match_idx),
1284 Pattern::Constant(_) | Pattern::Dynamic | Pattern::Concatenation(_)
1285 if match_idx == 0 =>
1286 {
1287 Some(self)
1288 }
1289 _ => None,
1290 }?;
1291 let target = match target {
1292 Pattern::Alternatives(list) => list.get(match_idx),
1293 Pattern::Constant(_) | Pattern::Dynamic | Pattern::Concatenation(_)
1294 if match_idx == 0 =>
1295 {
1296 Some(target)
1297 }
1298 _ => None,
1299 }?;
1300
1301 let mut dynamics = VecDeque::new();
1302 source.match_collect_internal(value, None, InNodeModules::False, &mut dynamics);
1304
1305 let mut result = "".to_string();
1306 match target {
1307 Pattern::Constant(c) => result.push_str(c),
1308 Pattern::Dynamic | Pattern::DynamicNoSlash => result.push_str(dynamics.pop_front()?),
1309 Pattern::Concatenation(list) => {
1310 for c in list {
1311 match c {
1312 Pattern::Constant(c) => result.push_str(c),
1313 Pattern::Dynamic | Pattern::DynamicNoSlash => {
1314 result.push_str(dynamics.pop_front()?)
1315 }
1316 Pattern::Alternatives(_) | Pattern::Concatenation(_) => {
1317 panic!("Pattern must be normalized")
1318 }
1319 }
1320 }
1321 }
1322 Pattern::Alternatives(_) => panic!("Pattern must be normalized"),
1323 }
1324 if !dynamics.is_empty() {
1325 return None;
1326 }
1327
1328 Some(result)
1329 }
1330}
1331
1332impl Pattern {
1333 pub fn new(mut pattern: Pattern) -> Vc<Self> {
1334 pattern.normalize();
1335 Pattern::new_internal(pattern)
1336 }
1337}
1338
1339#[turbo_tasks::value_impl]
1340impl Pattern {
1341 #[turbo_tasks::function]
1342 fn new_internal(pattern: Pattern) -> Vc<Self> {
1343 Self::cell(pattern)
1344 }
1345}
1346
1347#[derive(PartialEq, Debug)]
1348enum InNodeModules {
1349 False,
1350 FolderMatched,
1352 FolderSlashMatched,
1354}
1355impl InNodeModules {
1356 fn check(value: &str) -> Self {
1357 if value.ends_with("node_modules/") {
1358 InNodeModules::FolderSlashMatched
1359 } else if value.ends_with("node_modules") {
1360 InNodeModules::FolderMatched
1361 } else {
1362 InNodeModules::False
1363 }
1364 }
1365}
1366
1367#[derive(PartialEq, Debug)]
1368enum MatchResult<'a> {
1369 None,
1371 Partial,
1373 Consumed {
1375 remaining: &'a str,
1377 any_offset: Option<usize>,
1380 in_node_modules: InNodeModules,
1383 },
1384}
1385
1386impl MatchResult<'_> {
1387 fn is_match(&self) -> bool {
1389 match self {
1390 MatchResult::None => false,
1391 MatchResult::Partial => false,
1392 MatchResult::Consumed {
1393 remaining: rem,
1394 any_offset,
1395 in_node_modules: _,
1396 } => {
1397 if let Some(offset) = any_offset {
1398 *offset == rem.len()
1399 } else {
1400 rem.is_empty()
1401 }
1402 }
1403 }
1404 }
1405
1406 fn could_match_others(&self) -> bool {
1409 match self {
1410 MatchResult::None => false,
1411 MatchResult::Partial => true,
1412 MatchResult::Consumed {
1413 remaining: rem,
1414 any_offset,
1415 in_node_modules: _,
1416 } => {
1417 if let Some(offset) = any_offset {
1418 *offset == rem.len()
1419 } else {
1420 false
1421 }
1422 }
1423 }
1424 }
1425
1426 fn could_match(&self) -> bool {
1429 match self {
1430 MatchResult::None => false,
1431 MatchResult::Partial => true,
1432 MatchResult::Consumed {
1433 remaining: rem,
1434 any_offset,
1435 in_node_modules: _,
1436 } => {
1437 if let Some(offset) = any_offset {
1438 *offset == rem.len()
1439 } else {
1440 rem.is_empty()
1441 }
1442 }
1443 }
1444 }
1445}
1446
1447#[derive(PartialEq, Debug)]
1448enum NextConstantUntilResult<'a, 'b> {
1449 NoMatch,
1450 PartialDynamic,
1451 Partial(&'a str, bool),
1452 Consumed(&'b str, Option<usize>),
1453}
1454
1455impl From<RcStr> for Pattern {
1456 fn from(s: RcStr) -> Self {
1457 Pattern::Constant(s)
1458 }
1459}
1460
1461impl Pattern {
1462 pub fn describe_as_string(&self) -> String {
1463 match self {
1464 Pattern::Constant(c) => format!("'{c}'"),
1465 Pattern::Dynamic => "<dynamic>".to_string(),
1466 Pattern::DynamicNoSlash => "<dynamic no slash>".to_string(),
1467 Pattern::Alternatives(list) => format!(
1468 "({})",
1469 list.iter()
1470 .map(|i| i.describe_as_string())
1471 .collect::<Vec<_>>()
1472 .join(" | ")
1473 ),
1474 Pattern::Concatenation(list) => list
1475 .iter()
1476 .map(|i| i.describe_as_string())
1477 .collect::<Vec<_>>()
1478 .join(" "),
1479 }
1480 }
1481}
1482
1483#[turbo_tasks::value_impl]
1484impl ValueToString for Pattern {
1485 #[turbo_tasks::function]
1486 fn to_string(&self) -> Vc<RcStr> {
1487 Vc::cell(self.describe_as_string().into())
1488 }
1489}
1490
1491#[derive(
1492 Debug,
1493 PartialEq,
1494 Eq,
1495 Clone,
1496 TraceRawVcs,
1497 Serialize,
1498 Deserialize,
1499 ValueDebugFormat,
1500 NonLocalValue,
1501)]
1502pub enum PatternMatch {
1503 File(RcStr, FileSystemPath),
1504 Directory(RcStr, FileSystemPath),
1505}
1506
1507impl PatternMatch {
1508 pub fn path(&self) -> Vc<FileSystemPath> {
1509 match self {
1510 PatternMatch::File(_, path) | PatternMatch::Directory(_, path) => path.clone().cell(),
1511 }
1512 }
1513
1514 pub fn name(&self) -> &str {
1515 match self {
1516 PatternMatch::File(name, _) | PatternMatch::Directory(name, _) => name.as_str(),
1517 }
1518 }
1519}
1520
1521#[turbo_tasks::value(transparent)]
1524pub struct PatternMatches(Vec<PatternMatch>);
1525
1526#[turbo_tasks::function]
1535pub async fn read_matches(
1536 lookup_dir: FileSystemPath,
1537 prefix: RcStr,
1538 force_in_lookup_dir: bool,
1539 pattern: Vc<Pattern>,
1540) -> Result<Vc<PatternMatches>> {
1541 let mut prefix = prefix.to_string();
1542 let pat = pattern.await?;
1543 let mut results = Vec::new();
1544 let mut nested = Vec::new();
1545 let slow_path = if let Some(constants) = pat.next_constants(&prefix) {
1546 if constants
1547 .iter()
1548 .all(|(str, until_end)| *until_end || str.contains('/'))
1549 {
1550 let mut handled = FxHashSet::default();
1554 let mut read_dir_results = FxHashMap::default();
1555 for (index, (str, until_end)) in constants.into_iter().enumerate() {
1556 if until_end {
1557 if !handled.insert(str) {
1558 continue;
1559 }
1560 let (parent_path, last_segment) = split_last_segment(str);
1561 if last_segment.is_empty() {
1562 let joined = if force_in_lookup_dir {
1564 lookup_dir.try_join_inside(parent_path)?
1565 } else {
1566 lookup_dir.try_join(parent_path)?
1567 };
1568 let Some(fs_path) = joined else {
1569 continue;
1570 };
1571 results.push((
1572 index,
1573 PatternMatch::Directory(concat(&prefix, str).into(), fs_path),
1574 ));
1575 continue;
1576 }
1577 let entry = read_dir_results.entry(parent_path);
1578 let read_dir = match entry {
1579 Entry::Occupied(e) => Some(e.into_mut()),
1580 Entry::Vacant(e) => {
1581 let path_option = if force_in_lookup_dir {
1582 lookup_dir.try_join_inside(parent_path)?
1583 } else {
1584 lookup_dir.try_join(parent_path)?
1585 };
1586 if let Some(path) = path_option {
1587 Some(e.insert((path.raw_read_dir().await?, path)))
1588 } else {
1589 None
1590 }
1591 }
1592 };
1593 let Some((read_dir, parent_fs_path)) = read_dir else {
1594 continue;
1595 };
1596 let RawDirectoryContent::Entries(entries) = &**read_dir else {
1597 continue;
1598 };
1599 let Some(entry) = entries.get(last_segment) else {
1600 continue;
1601 };
1602 match *entry {
1603 RawDirectoryEntry::File => {
1604 results.push((
1605 index,
1606 PatternMatch::File(
1607 concat(&prefix, str).into(),
1608 parent_fs_path.join(last_segment)?,
1609 ),
1610 ));
1611 }
1612 RawDirectoryEntry::Directory => results.push((
1613 index,
1614 PatternMatch::Directory(
1615 concat(&prefix, str).into(),
1616 parent_fs_path.join(last_segment)?,
1617 ),
1618 )),
1619 RawDirectoryEntry::Symlink => {
1620 let fs_path = parent_fs_path.join(last_segment)?;
1621 let LinkContent::Link { link_type, .. } = &*fs_path.read_link().await?
1622 else {
1623 continue;
1624 };
1625 let path = concat(&prefix, str).into();
1626 if link_type.contains(LinkType::DIRECTORY) {
1627 results.push((index, PatternMatch::Directory(path, fs_path)));
1628 } else {
1629 results.push((index, PatternMatch::File(path, fs_path)))
1630 }
1631 }
1632 _ => {}
1633 }
1634 } else {
1635 let subpath = &str[..=str.rfind('/').unwrap()];
1636 if handled.insert(subpath) {
1637 let joined = if force_in_lookup_dir {
1638 lookup_dir.try_join_inside(subpath)?
1639 } else {
1640 lookup_dir.try_join(subpath)?
1641 };
1642 let Some(fs_path) = joined else {
1643 continue;
1644 };
1645 nested.push((
1646 0,
1647 read_matches(
1648 fs_path.clone(),
1649 concat(&prefix, subpath).into(),
1650 force_in_lookup_dir,
1651 pattern,
1652 ),
1653 ));
1654 }
1655 }
1656 }
1657 false
1658 } else {
1659 true
1660 }
1661 } else {
1662 true
1663 };
1664
1665 if slow_path {
1666 async {
1667 if !force_in_lookup_dir {
1670 prefix.push_str("..");
1672 if let Some(pos) = pat.match_position(&prefix) {
1673 results.push((
1674 pos,
1675 PatternMatch::Directory(prefix.clone().into(), lookup_dir.parent()),
1676 ));
1677 }
1678
1679 prefix.push('/');
1681 if let Some(pos) = pat.match_position(&prefix) {
1682 results.push((
1683 pos,
1684 PatternMatch::Directory(prefix.clone().into(), lookup_dir.parent()),
1685 ));
1686 }
1687 if let Some(pos) = pat.could_match_position(&prefix) {
1688 nested.push((
1689 pos,
1690 read_matches(lookup_dir.parent(), prefix.clone().into(), false, pattern),
1691 ));
1692 }
1693 prefix.pop();
1694 prefix.pop();
1695 prefix.pop();
1696 }
1697 {
1698 prefix.push('.');
1699 if let Some(pos) = pat.match_position(&prefix) {
1701 results.push((
1702 pos,
1703 PatternMatch::Directory(prefix.clone().into(), lookup_dir.clone()),
1704 ));
1705 }
1706 prefix.pop();
1707 }
1708 if prefix.is_empty() {
1709 if let Some(pos) = pat.match_position("./") {
1710 results.push((
1711 pos,
1712 PatternMatch::Directory(rcstr!("./"), lookup_dir.clone()),
1713 ));
1714 }
1715 if let Some(pos) = pat.could_match_position("./") {
1716 nested.push((
1717 pos,
1718 read_matches(lookup_dir.clone(), rcstr!("./"), false, pattern),
1719 ));
1720 }
1721 } else {
1722 prefix.push('/');
1723 if let Some(pos) = pat.could_match_position(&prefix) {
1725 nested.push((
1726 pos,
1727 read_matches(
1728 lookup_dir.clone(),
1729 prefix.to_string().into(),
1730 false,
1731 pattern,
1732 ),
1733 ));
1734 }
1735 prefix.pop();
1736 prefix.push_str("./");
1737 if let Some(pos) = pat.could_match_position(&prefix) {
1739 nested.push((
1740 pos,
1741 read_matches(
1742 lookup_dir.clone(),
1743 prefix.to_string().into(),
1744 false,
1745 pattern,
1746 ),
1747 ));
1748 }
1749 prefix.pop();
1750 prefix.pop();
1751 }
1752 match &*lookup_dir.raw_read_dir().await? {
1753 RawDirectoryContent::Entries(map) => {
1754 for (key, entry) in map.iter() {
1755 match entry {
1756 RawDirectoryEntry::File => {
1757 let len = prefix.len();
1758 prefix.push_str(key);
1759 if let Some(pos) = pat.match_position(&prefix) {
1761 let path = lookup_dir.join(key)?;
1762 results.push((
1763 pos,
1764 PatternMatch::File(prefix.clone().into(), path),
1765 ));
1766 }
1767 prefix.truncate(len)
1768 }
1769 RawDirectoryEntry::Directory => {
1770 let len = prefix.len();
1771 prefix.push_str(key);
1772 if prefix.ends_with('/') {
1774 prefix.pop();
1775 }
1776 if let Some(pos) = pat.match_position(&prefix) {
1777 let path = lookup_dir.join(key)?;
1778 results.push((
1779 pos,
1780 PatternMatch::Directory(prefix.clone().into(), path),
1781 ));
1782 }
1783 prefix.push('/');
1784 if let Some(pos) = pat.match_position(&prefix) {
1786 let path = lookup_dir.join(key)?;
1787 results.push((
1788 pos,
1789 PatternMatch::Directory(prefix.clone().into(), path),
1790 ));
1791 }
1792 if let Some(pos) = pat.could_match_position(&prefix) {
1793 let path = lookup_dir.join(key)?;
1794 nested.push((
1795 pos,
1796 read_matches(path, prefix.clone().into(), true, pattern),
1797 ));
1798 }
1799 prefix.truncate(len)
1800 }
1801 RawDirectoryEntry::Symlink => {
1802 let len = prefix.len();
1803 prefix.push_str(key);
1804 if prefix.ends_with('/') {
1806 prefix.pop();
1807 }
1808 if let Some(pos) = pat.match_position(&prefix) {
1809 let fs_path = lookup_dir.join(key)?;
1810 if let LinkContent::Link { link_type, .. } =
1811 &*fs_path.read_link().await?
1812 {
1813 if link_type.contains(LinkType::DIRECTORY) {
1814 results.push((
1815 pos,
1816 PatternMatch::Directory(
1817 prefix.clone().into(),
1818 fs_path,
1819 ),
1820 ));
1821 } else {
1822 results.push((
1823 pos,
1824 PatternMatch::File(prefix.clone().into(), fs_path),
1825 ));
1826 }
1827 }
1828 }
1829 prefix.push('/');
1830 if let Some(pos) = pat.match_position(&prefix) {
1831 let fs_path = lookup_dir.join(key)?;
1832 if let LinkContent::Link { link_type, .. } =
1833 &*fs_path.read_link().await?
1834 && link_type.contains(LinkType::DIRECTORY)
1835 {
1836 results.push((
1837 pos,
1838 PatternMatch::Directory(prefix.clone().into(), fs_path),
1839 ));
1840 }
1841 }
1842 if let Some(pos) = pat.could_match_position(&prefix) {
1843 let fs_path = lookup_dir.join(key)?;
1844 if let LinkContent::Link { link_type, .. } =
1845 &*fs_path.read_link().await?
1846 && link_type.contains(LinkType::DIRECTORY)
1847 {
1848 results.push((
1849 pos,
1850 PatternMatch::Directory(prefix.clone().into(), fs_path),
1851 ));
1852 }
1853 }
1854 prefix.truncate(len)
1855 }
1856 RawDirectoryEntry::Other => {}
1857 }
1858 }
1859 }
1860 RawDirectoryContent::NotFound => {}
1861 };
1862 anyhow::Ok(())
1863 }
1864 .instrument(tracing::trace_span!("read_matches slow_path"))
1865 .await?;
1866 }
1867 if results.is_empty() && nested.len() == 1 {
1868 Ok(nested.into_iter().next().unwrap().1)
1869 } else {
1870 for (pos, nested) in nested.into_iter() {
1871 results.extend(nested.await?.iter().cloned().map(|p| (pos, p)));
1872 }
1873 results.sort_by(|(a, am), (b, bm)| (*a).cmp(b).then_with(|| am.name().cmp(bm.name())));
1874 Ok(Vc::cell(
1875 results.into_iter().map(|(_, p)| p).collect::<Vec<_>>(),
1876 ))
1877 }
1878}
1879
1880fn concat(a: &str, b: &str) -> String {
1881 let mut result = String::with_capacity(a.len() + b.len());
1882 result.push_str(a);
1883 result.push_str(b);
1884 result
1885}
1886
1887fn split_last_segment(path: &str) -> (&str, &str) {
1890 if let Some((remaining_path, last_segment)) = path.rsplit_once('/') {
1891 match last_segment {
1892 "" => split_last_segment(remaining_path),
1893 "." => split_last_segment(remaining_path),
1894 ".." => match split_last_segment(remaining_path) {
1895 (_, "") => (path, ""),
1896 (parent_path, _) => split_last_segment(parent_path),
1897 },
1898 _ => (remaining_path, last_segment),
1899 }
1900 } else {
1901 match path {
1902 "" => ("", ""),
1903 "." => ("", ""),
1904 ".." => ("..", ""),
1905 _ => ("", path),
1906 }
1907 }
1908}
1909
1910#[cfg(test)]
1911mod tests {
1912 use std::path::Path;
1913
1914 use rstest::*;
1915 use turbo_rcstr::{RcStr, rcstr};
1916 use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
1917 use turbo_tasks_fs::{DiskFileSystem, FileSystem};
1918
1919 use super::{
1920 Pattern, longest_common_prefix, longest_common_suffix, read_matches, split_last_segment,
1921 };
1922
1923 #[test]
1924 fn longest_common_prefix_test() {
1925 assert_eq!(longest_common_prefix(&["ab"]), "ab");
1926 assert_eq!(longest_common_prefix(&["ab", "cd", "ef"]), "");
1927 assert_eq!(longest_common_prefix(&["ab1", "ab23", "ab456"]), "ab");
1928 assert_eq!(longest_common_prefix(&["abc", "abc", "abc"]), "abc");
1929 assert_eq!(longest_common_prefix(&["abc", "a", "abc"]), "a");
1930 }
1931
1932 #[test]
1933 fn longest_common_suffix_test() {
1934 assert_eq!(longest_common_suffix(&["ab"]), "ab");
1935 assert_eq!(longest_common_suffix(&["ab", "cd", "ef"]), "");
1936 assert_eq!(longest_common_suffix(&["1ab", "23ab", "456ab"]), "ab");
1937 assert_eq!(longest_common_suffix(&["abc", "abc", "abc"]), "abc");
1938 assert_eq!(longest_common_suffix(&["abc", "c", "abc"]), "c");
1939 }
1940
1941 #[test]
1942 fn normalize() {
1943 let a = Pattern::Constant(rcstr!("a"));
1944 let b = Pattern::Constant(rcstr!("b"));
1945 let c = Pattern::Constant(rcstr!("c"));
1946 let s = Pattern::Constant(rcstr!("/"));
1947 let d = Pattern::Dynamic;
1948 {
1949 let mut p = Pattern::Concatenation(vec![
1950 Pattern::Alternatives(vec![a.clone(), b.clone()]),
1951 s.clone(),
1952 c.clone(),
1953 ]);
1954 p.normalize();
1955 assert_eq!(
1956 p,
1957 Pattern::Alternatives(vec![
1958 Pattern::Constant(rcstr!("a/c")),
1959 Pattern::Constant(rcstr!("b/c")),
1960 ])
1961 );
1962 }
1963
1964 #[allow(clippy::redundant_clone)] {
1966 let mut p = Pattern::Concatenation(vec![
1967 Pattern::Alternatives(vec![a.clone(), b.clone(), d.clone()]),
1968 s.clone(),
1969 Pattern::Alternatives(vec![b.clone(), c.clone(), d.clone()]),
1970 ]);
1971 p.normalize();
1972
1973 assert_eq!(
1974 p,
1975 Pattern::Alternatives(vec![
1976 Pattern::Constant(rcstr!("a/b")),
1977 Pattern::Constant(rcstr!("b/b")),
1978 Pattern::Concatenation(vec![Pattern::Dynamic, Pattern::Constant(rcstr!("/b"))]),
1979 Pattern::Constant(rcstr!("a/c")),
1980 Pattern::Constant(rcstr!("b/c")),
1981 Pattern::Concatenation(vec![Pattern::Dynamic, Pattern::Constant(rcstr!("/c"))]),
1982 Pattern::Concatenation(vec![Pattern::Constant(rcstr!("a/")), Pattern::Dynamic]),
1983 Pattern::Concatenation(vec![Pattern::Constant(rcstr!("b/")), Pattern::Dynamic]),
1984 Pattern::Concatenation(vec![
1985 Pattern::Dynamic,
1986 Pattern::Constant(rcstr!("/")),
1987 Pattern::Dynamic
1988 ]),
1989 ])
1990 );
1991 }
1992
1993 #[allow(clippy::redundant_clone)] {
1995 let mut p = Pattern::Alternatives(vec![a.clone()]);
1996 p.normalize();
1997
1998 assert_eq!(p, a);
1999 }
2000
2001 #[allow(clippy::redundant_clone)] {
2003 let mut p = Pattern::Alternatives(vec![Pattern::Dynamic, Pattern::Dynamic]);
2004 p.normalize();
2005
2006 assert_eq!(p, Pattern::Dynamic);
2007 }
2008 }
2009
2010 #[test]
2011 fn with_normalized_path() {
2012 assert!(
2013 Pattern::Constant(rcstr!("a/../.."))
2014 .with_normalized_path()
2015 .is_none()
2016 );
2017 assert_eq!(
2018 Pattern::Constant(rcstr!("a/b/../c"))
2019 .with_normalized_path()
2020 .unwrap(),
2021 Pattern::Constant(rcstr!("a/c"))
2022 );
2023 assert_eq!(
2024 Pattern::Alternatives(vec![
2025 Pattern::Constant(rcstr!("a/b/../c")),
2026 Pattern::Constant(rcstr!("a/b/../c/d"))
2027 ])
2028 .with_normalized_path()
2029 .unwrap(),
2030 Pattern::Alternatives(vec![
2031 Pattern::Constant(rcstr!("a/c")),
2032 Pattern::Constant(rcstr!("a/c/d"))
2033 ])
2034 );
2035 assert_eq!(
2036 Pattern::Constant(rcstr!("a/b/"))
2037 .with_normalized_path()
2038 .unwrap(),
2039 Pattern::Constant(rcstr!("a/b"))
2040 );
2041
2042 assert_eq!(
2044 Pattern::Concatenation(vec![
2045 Pattern::Constant(rcstr!("a/b/")),
2046 Pattern::Dynamic,
2047 Pattern::Constant(rcstr!("../c"))
2048 ])
2049 .with_normalized_path()
2050 .unwrap(),
2051 Pattern::Concatenation(vec![
2052 Pattern::Constant(rcstr!("a/b/")),
2053 Pattern::Dynamic,
2054 Pattern::Constant(rcstr!("../c"))
2055 ])
2056 );
2057
2058 assert_eq!(
2060 Pattern::Concatenation(vec![
2061 Pattern::Constant(rcstr!("a/b")),
2062 Pattern::Dynamic,
2063 Pattern::Constant(rcstr!("../c"))
2064 ])
2065 .with_normalized_path()
2066 .unwrap(),
2067 Pattern::Concatenation(vec![
2068 Pattern::Constant(rcstr!("a/b")),
2069 Pattern::Dynamic,
2070 Pattern::Constant(rcstr!("../c"))
2071 ])
2072 );
2073 assert_eq!(
2074 Pattern::Concatenation(vec![
2075 Pattern::Constant(rcstr!("src/")),
2076 Pattern::Dynamic,
2077 Pattern::Constant(rcstr!(".js"))
2078 ])
2079 .with_normalized_path()
2080 .unwrap(),
2081 Pattern::Concatenation(vec![
2082 Pattern::Constant(rcstr!("src/")),
2083 Pattern::Dynamic,
2084 Pattern::Constant(rcstr!(".js"))
2085 ])
2086 );
2087 }
2088
2089 #[test]
2090 fn is_match() {
2091 let pat = Pattern::Concatenation(vec![
2092 Pattern::Constant(rcstr!(".")),
2093 Pattern::Constant(rcstr!("/")),
2094 Pattern::Dynamic,
2095 Pattern::Constant(rcstr!(".js")),
2096 ]);
2097 assert!(pat.could_match(""));
2098 assert!(pat.could_match("./"));
2099 assert!(!pat.is_match("./"));
2100 assert!(pat.is_match("./index.js"));
2101 assert!(!pat.is_match("./index"));
2102 assert!(pat.is_match("./foo/index.js"));
2103 assert!(pat.is_match("./foo/bar/index.js"));
2104
2105 assert!(!pat.is_match("./../index.js"));
2107 assert!(!pat.is_match("././index.js"));
2108 assert!(!pat.is_match("./.git/index.js"));
2109 assert!(!pat.is_match("./inner/../index.js"));
2110 assert!(!pat.is_match("./inner/./index.js"));
2111 assert!(!pat.is_match("./inner/.git/index.js"));
2112 assert!(!pat.could_match("./../"));
2113 assert!(!pat.could_match("././"));
2114 assert!(!pat.could_match("./.git/"));
2115 assert!(!pat.could_match("./inner/../"));
2116 assert!(!pat.could_match("./inner/./"));
2117 assert!(!pat.could_match("./inner/.git/"));
2118 }
2119
2120 #[test]
2121 fn is_match_dynamic_no_slash() {
2122 let pat = Pattern::Concatenation(vec![
2123 Pattern::Constant(rcstr!(".")),
2124 Pattern::Constant(rcstr!("/")),
2125 Pattern::DynamicNoSlash,
2126 Pattern::Constant(rcstr!(".js")),
2127 ]);
2128 assert!(pat.could_match(""));
2129 assert!(pat.could_match("./"));
2130 assert!(!pat.is_match("./"));
2131 assert!(pat.is_match("./index.js"));
2132 assert!(!pat.is_match("./index"));
2133 assert!(!pat.is_match("./foo/index.js"));
2134 assert!(!pat.is_match("./foo/bar/index.js"));
2135 }
2136
2137 #[test]
2138 fn constant_prefix() {
2139 assert_eq!(
2140 Pattern::Constant(rcstr!("a/b/c.js")).constant_prefix(),
2141 "a/b/c.js",
2142 );
2143
2144 let pat = Pattern::Alternatives(vec![
2145 Pattern::Constant(rcstr!("a/b/x")),
2146 Pattern::Constant(rcstr!("a/b/y")),
2147 Pattern::Concatenation(vec![Pattern::Constant(rcstr!("a/b/c/")), Pattern::Dynamic]),
2148 ]);
2149 assert_eq!(pat.constant_prefix(), "a/b/");
2150 }
2151
2152 #[test]
2153 fn constant_suffix() {
2154 assert_eq!(
2155 Pattern::Constant(rcstr!("a/b/c.js")).constant_suffix(),
2156 "a/b/c.js",
2157 );
2158
2159 let pat = Pattern::Alternatives(vec![
2160 Pattern::Constant(rcstr!("a/b/x.js")),
2161 Pattern::Constant(rcstr!("a/b/y.js")),
2162 Pattern::Concatenation(vec![
2163 Pattern::Constant(rcstr!("a/b/c/")),
2164 Pattern::Dynamic,
2165 Pattern::Constant(rcstr!(".js")),
2166 ]),
2167 ]);
2168 assert_eq!(pat.constant_suffix(), ".js");
2169 }
2170
2171 #[test]
2172 fn strip_prefix() {
2173 fn strip(mut pat: Pattern, n: usize) -> Pattern {
2174 pat.strip_prefix_len(n).unwrap();
2175 pat
2176 }
2177
2178 assert_eq!(
2179 strip(Pattern::Constant(rcstr!("a/b")), 0),
2180 Pattern::Constant(rcstr!("a/b"))
2181 );
2182
2183 assert_eq!(
2184 strip(
2185 Pattern::Alternatives(vec![
2186 Pattern::Constant(rcstr!("a/b/x")),
2187 Pattern::Constant(rcstr!("a/b/y")),
2188 ]),
2189 2
2190 ),
2191 Pattern::Alternatives(vec![
2192 Pattern::Constant(rcstr!("b/x")),
2193 Pattern::Constant(rcstr!("b/y")),
2194 ])
2195 );
2196
2197 assert_eq!(
2198 strip(
2199 Pattern::Concatenation(vec![
2200 Pattern::Constant(rcstr!("a/")),
2201 Pattern::Constant(rcstr!("b")),
2202 Pattern::Constant(rcstr!("/")),
2203 Pattern::Constant(rcstr!("y/")),
2204 Pattern::Dynamic
2205 ]),
2206 4
2207 ),
2208 Pattern::Concatenation(vec![Pattern::Constant(rcstr!("y/")), Pattern::Dynamic]),
2209 );
2210 }
2211
2212 #[test]
2213 fn strip_suffix() {
2214 fn strip(mut pat: Pattern, n: usize) -> Pattern {
2215 pat.strip_suffix_len(n);
2216 pat
2217 }
2218
2219 assert_eq!(
2220 strip(Pattern::Constant(rcstr!("a/b")), 0),
2221 Pattern::Constant(rcstr!("a/b"))
2222 );
2223
2224 assert_eq!(
2225 strip(
2226 Pattern::Alternatives(vec![
2227 Pattern::Constant(rcstr!("x/b/a")),
2228 Pattern::Constant(rcstr!("y/b/a")),
2229 ]),
2230 2
2231 ),
2232 Pattern::Alternatives(vec![
2233 Pattern::Constant(rcstr!("x/b")),
2234 Pattern::Constant(rcstr!("y/b")),
2235 ])
2236 );
2237
2238 assert_eq!(
2239 strip(
2240 Pattern::Concatenation(vec![
2241 Pattern::Dynamic,
2242 Pattern::Constant(rcstr!("/a/")),
2243 Pattern::Constant(rcstr!("b")),
2244 Pattern::Constant(rcstr!("/")),
2245 Pattern::Constant(rcstr!("y/")),
2246 ]),
2247 4
2248 ),
2249 Pattern::Concatenation(vec![Pattern::Dynamic, Pattern::Constant(rcstr!("/a/")),]),
2250 );
2251 }
2252
2253 #[test]
2254 fn spread_into_star() {
2255 let pat = Pattern::Constant(rcstr!("xyz"));
2256 assert_eq!(
2257 pat.spread_into_star("before/after"),
2258 Pattern::Constant(rcstr!("before/after")),
2259 );
2260
2261 let pat =
2262 Pattern::Concatenation(vec![Pattern::Constant(rcstr!("a/b/c/")), Pattern::Dynamic]);
2263 assert_eq!(
2264 pat.spread_into_star("before/*/after"),
2265 Pattern::Concatenation(vec![
2266 Pattern::Constant(rcstr!("before/a/b/c/")),
2267 Pattern::Dynamic,
2268 Pattern::Constant(rcstr!("/after"))
2269 ])
2270 );
2271
2272 let pat = Pattern::Alternatives(vec![
2273 Pattern::Concatenation(vec![Pattern::Constant(rcstr!("a/")), Pattern::Dynamic]),
2274 Pattern::Concatenation(vec![Pattern::Constant(rcstr!("b/")), Pattern::Dynamic]),
2275 ]);
2276 assert_eq!(
2277 pat.spread_into_star("before/*/after"),
2278 Pattern::Alternatives(vec![
2279 Pattern::Concatenation(vec![
2280 Pattern::Constant(rcstr!("before/a/")),
2281 Pattern::Dynamic,
2282 Pattern::Constant(rcstr!("/after"))
2283 ]),
2284 Pattern::Concatenation(vec![
2285 Pattern::Constant(rcstr!("before/b/")),
2286 Pattern::Dynamic,
2287 Pattern::Constant(rcstr!("/after"))
2288 ]),
2289 ])
2290 );
2291
2292 let pat = Pattern::Alternatives(vec![
2293 Pattern::Constant(rcstr!("a")),
2294 Pattern::Constant(rcstr!("b")),
2295 ]);
2296 assert_eq!(
2297 pat.spread_into_star("before/*/*"),
2298 Pattern::Alternatives(vec![
2299 Pattern::Constant(rcstr!("before/a/a")),
2300 Pattern::Constant(rcstr!("before/b/b")),
2301 ])
2302 );
2303
2304 let pat = Pattern::Dynamic;
2305 assert_eq!(
2306 pat.spread_into_star("before/*/*"),
2307 Pattern::Concatenation(vec![
2308 Pattern::Constant(rcstr!("before/")),
2310 Pattern::Dynamic,
2311 Pattern::Constant(rcstr!("/")),
2312 Pattern::Dynamic
2313 ])
2314 );
2315 }
2316
2317 #[rstest]
2318 #[case::dynamic(Pattern::Dynamic)]
2319 #[case::dynamic_concat(Pattern::Concatenation(vec![Pattern::Dynamic, Pattern::Constant(rcstr!(".js"))]))]
2320 fn dynamic_match(#[case] pat: Pattern) {
2321 assert!(pat.could_match(""));
2322 assert!(pat.is_match("index.js"));
2323
2324 assert!(!pat.could_match("./"));
2326 assert!(!pat.is_match("./"));
2327 assert!(!pat.could_match("."));
2328 assert!(!pat.is_match("."));
2329 assert!(!pat.could_match("../"));
2330 assert!(!pat.is_match("../"));
2331 assert!(!pat.could_match(".."));
2332 assert!(!pat.is_match(".."));
2333 assert!(!pat.is_match("./../index.js"));
2334 assert!(!pat.is_match("././index.js"));
2335 assert!(!pat.is_match("./.git/index.js"));
2336 assert!(!pat.is_match("./inner/../index.js"));
2337 assert!(!pat.is_match("./inner/./index.js"));
2338 assert!(!pat.is_match("./inner/.git/index.js"));
2339 assert!(!pat.could_match("./../"));
2340 assert!(!pat.could_match("././"));
2341 assert!(!pat.could_match("./.git/"));
2342 assert!(!pat.could_match("./inner/../"));
2343 assert!(!pat.could_match("./inner/./"));
2344 assert!(!pat.could_match("./inner/.git/"));
2345 assert!(!pat.could_match("dir//"));
2346 assert!(!pat.could_match("dir//dir"));
2347 assert!(!pat.could_match("dir///dir"));
2348 assert!(!pat.could_match("/"));
2349 assert!(!pat.could_match("//"));
2350 assert!(!pat.could_match("/ROOT/"));
2351
2352 assert!(!pat.could_match("node_modules"));
2353 assert!(!pat.could_match("node_modules/package"));
2354 assert!(!pat.could_match("nested/node_modules"));
2355 assert!(!pat.could_match("nested/node_modules/package"));
2356
2357 assert!(pat.could_match("file.map"));
2359 assert!(!pat.is_match("file.map"));
2360 assert!(pat.is_match("file.map/file.js"));
2361 assert!(!pat.is_match("file.d.ts"));
2362 assert!(!pat.is_match("file.d.ts.map"));
2363 assert!(!pat.is_match("file.d.ts.map"));
2364 assert!(!pat.is_match("dir/file.d.ts.map"));
2365 assert!(!pat.is_match("dir/inner/file.d.ts.map"));
2366 assert!(pat.could_match("dir/inner/file.d.ts.map"));
2367 }
2368
2369 #[rstest]
2370 #[case::slash(Pattern::Concatenation(vec![Pattern::Constant(rcstr!("node_modules/")),Pattern::Dynamic]))]
2371 #[case::nested(Pattern::Constant(rcstr!("node_modules")).or_any_nested_file())]
2372 fn dynamic_match_node_modules(#[case] pat: Pattern) {
2373 assert!(!pat.is_match("node_modules/package"));
2374 assert!(!pat.could_match("node_modules/package"));
2375 assert!(!pat.is_match("node_modules/package/index.js"));
2376 assert!(!pat.could_match("node_modules/package/index.js"));
2377 }
2378
2379 #[rstest]
2380 fn dynamic_match2() {
2381 let pat = Pattern::Concatenation(vec![
2382 Pattern::Dynamic,
2383 Pattern::Constant(rcstr!("/")),
2384 Pattern::Dynamic,
2385 ]);
2386 assert!(pat.could_match("dir"));
2387 assert!(pat.could_match("dir/"));
2388 assert!(pat.is_match("dir/index.js"));
2389
2390 assert!(!pat.could_match("./"));
2392 assert!(!pat.is_match("./"));
2393 assert!(!pat.could_match("."));
2394 assert!(!pat.is_match("."));
2395 assert!(!pat.could_match("../"));
2396 assert!(!pat.is_match("../"));
2397 assert!(!pat.could_match(".."));
2398 assert!(!pat.is_match(".."));
2399 assert!(!pat.is_match("./../index.js"));
2400 assert!(!pat.is_match("././index.js"));
2401 assert!(!pat.is_match("./.git/index.js"));
2402 assert!(!pat.is_match("./inner/../index.js"));
2403 assert!(!pat.is_match("./inner/./index.js"));
2404 assert!(!pat.is_match("./inner/.git/index.js"));
2405 assert!(!pat.could_match("./../"));
2406 assert!(!pat.could_match("././"));
2407 assert!(!pat.could_match("./.git/"));
2408 assert!(!pat.could_match("./inner/../"));
2409 assert!(!pat.could_match("./inner/./"));
2410 assert!(!pat.could_match("./inner/.git/"));
2411 assert!(!pat.could_match("dir//"));
2412 assert!(!pat.could_match("dir//dir"));
2413 assert!(!pat.could_match("dir///dir"));
2414 assert!(!pat.could_match("/ROOT/"));
2415
2416 assert!(!pat.could_match("node_modules"));
2417 assert!(!pat.could_match("node_modules/package"));
2418 assert!(!pat.could_match("nested/node_modules"));
2419 assert!(!pat.could_match("nested/node_modules/package"));
2420
2421 assert!(pat.could_match("dir/file.map"));
2423 assert!(!pat.is_match("dir/file.map"));
2424 assert!(pat.is_match("file.map/file.js"));
2425 assert!(!pat.is_match("dir/file.d.ts"));
2426 assert!(!pat.is_match("dir/file.d.ts.map"));
2427 assert!(!pat.is_match("dir/file.d.ts.map"));
2428 assert!(!pat.is_match("dir/file.d.ts.map"));
2429 assert!(!pat.is_match("dir/inner/file.d.ts.map"));
2430 assert!(pat.could_match("dir/inner/file.d.ts.map"));
2431 }
2432
2433 #[rstest]
2434 #[case::dynamic(Pattern::Dynamic)]
2435 #[case::dynamic_concat(Pattern::Concatenation(vec![Pattern::Dynamic, Pattern::Constant(rcstr!(".js"))]))]
2436 #[case::dynamic_concat2(Pattern::Concatenation(vec![
2437 Pattern::Dynamic,
2438 Pattern::Constant(rcstr!("/")),
2439 Pattern::Dynamic,
2440 ]))]
2441 #[case::dynamic_alt_concat(Pattern::alternatives(vec![
2442 Pattern::Concatenation(vec![
2443 Pattern::Dynamic,
2444 Pattern::Constant(rcstr!("/")),
2445 Pattern::Dynamic,
2446 ]),
2447 Pattern::Dynamic,
2448 ]))]
2449 fn split_could_match(#[case] pat: Pattern) {
2450 let (abs, rel) = pat.split_could_match("/ROOT/");
2451 assert!(abs.is_none());
2452 assert!(rel.is_some());
2453 }
2454
2455 #[rstest]
2456 #[case::dynamic(Pattern::Dynamic, "feijf", None)]
2457 #[case::dynamic_concat(
2458 Pattern::Concatenation(vec![Pattern::Dynamic, Pattern::Constant(rcstr!(".js"))]),
2459 "hello.", None
2460 )]
2461 #[case::constant(Pattern::Constant(rcstr!("Hello World")), "Hello ", Some(vec![("World", true)]))]
2462 #[case::alternatives(
2463 Pattern::Alternatives(vec![
2464 Pattern::Constant(rcstr!("Hello World")),
2465 Pattern::Constant(rcstr!("Hello All"))
2466 ]), "Hello ", Some(vec![("World", true), ("All", true)])
2467 )]
2468 #[case::alternatives_non_end(
2469 Pattern::Alternatives(vec![
2470 Pattern::Constant(rcstr!("Hello World")),
2471 Pattern::Constant(rcstr!("Hello All")),
2472 Pattern::Concatenation(vec![Pattern::Constant(rcstr!("Hello more")), Pattern::Dynamic])
2473 ]), "Hello ", Some(vec![("World", true), ("All", true), ("more", false)])
2474 )]
2475 #[case::request_with_extensions(
2476 Pattern::Alternatives(vec![
2477 Pattern::Constant(rcstr!("./file.js")),
2478 Pattern::Constant(rcstr!("./file.ts")),
2479 Pattern::Constant(rcstr!("./file.cjs")),
2480 ]), "./", Some(vec![("file.js", true), ("file.ts", true), ("file.cjs", true)])
2481 )]
2482 fn next_constants(
2483 #[case] pat: Pattern,
2484 #[case] value: &str,
2485 #[case] expected: Option<Vec<(&str, bool)>>,
2486 ) {
2487 assert_eq!(pat.next_constants(value), expected);
2488 }
2489
2490 #[test]
2491 fn replace_final_constants() {
2492 fn f(mut p: Pattern, cb: &impl Fn(&RcStr) -> Option<Pattern>) -> Pattern {
2493 p.replace_final_constants(cb);
2494 p
2495 }
2496
2497 let js_to_ts_tsx = |c: &RcStr| -> Option<Pattern> {
2498 c.strip_suffix(".js").map(|rest| {
2499 let new_ending = Pattern::Alternatives(vec![
2500 Pattern::Constant(rcstr!(".ts")),
2501 Pattern::Constant(rcstr!(".tsx")),
2502 Pattern::Constant(rcstr!(".js")),
2503 ]);
2504 if !rest.is_empty() {
2505 Pattern::Concatenation(vec![Pattern::Constant(rest.into()), new_ending])
2506 } else {
2507 new_ending
2508 }
2509 })
2510 };
2511
2512 assert_eq!(
2513 f(
2514 Pattern::Concatenation(vec![
2515 Pattern::Constant(rcstr!(".")),
2516 Pattern::Constant(rcstr!("/")),
2517 Pattern::Dynamic,
2518 Pattern::Alternatives(vec![
2519 Pattern::Constant(rcstr!(".js")),
2520 Pattern::Constant(rcstr!(".node")),
2521 ])
2522 ]),
2523 &js_to_ts_tsx
2524 ),
2525 Pattern::Concatenation(vec![
2526 Pattern::Constant(rcstr!(".")),
2527 Pattern::Constant(rcstr!("/")),
2528 Pattern::Dynamic,
2529 Pattern::Alternatives(vec![
2530 Pattern::Alternatives(vec![
2531 Pattern::Constant(rcstr!(".ts")),
2532 Pattern::Constant(rcstr!(".tsx")),
2533 Pattern::Constant(rcstr!(".js")),
2534 ]),
2535 Pattern::Constant(rcstr!(".node")),
2536 ])
2537 ]),
2538 );
2539 assert_eq!(
2540 f(
2541 Pattern::Concatenation(vec![
2542 Pattern::Constant(rcstr!(".")),
2543 Pattern::Constant(rcstr!("/")),
2544 Pattern::Constant(rcstr!("abc.js")),
2545 ]),
2546 &js_to_ts_tsx
2547 ),
2548 Pattern::Concatenation(vec![
2549 Pattern::Constant(rcstr!(".")),
2550 Pattern::Constant(rcstr!("/")),
2551 Pattern::Concatenation(vec![
2552 Pattern::Constant(rcstr!("abc")),
2553 Pattern::Alternatives(vec![
2554 Pattern::Constant(rcstr!(".ts")),
2555 Pattern::Constant(rcstr!(".tsx")),
2556 Pattern::Constant(rcstr!(".js")),
2557 ])
2558 ]),
2559 ])
2560 );
2561 }
2562
2563 #[test]
2564 fn match_apply_template() {
2565 assert_eq!(
2566 Pattern::Concatenation(vec![
2567 Pattern::Constant(rcstr!("a/b/")),
2568 Pattern::Dynamic,
2569 Pattern::Constant(rcstr!(".ts")),
2570 ])
2571 .match_apply_template(
2572 "a/b/foo.ts",
2573 &Pattern::Concatenation(vec![
2574 Pattern::Constant(rcstr!("@/a/b/")),
2575 Pattern::Dynamic,
2576 Pattern::Constant(rcstr!(".js")),
2577 ])
2578 )
2579 .as_deref(),
2580 Some("@/a/b/foo.js")
2581 );
2582 assert_eq!(
2583 Pattern::Concatenation(vec![
2584 Pattern::Constant(rcstr!("b/")),
2585 Pattern::Dynamic,
2586 Pattern::Constant(rcstr!(".ts")),
2587 ])
2588 .match_apply_template(
2589 "a/b/foo.ts",
2590 &Pattern::Concatenation(vec![
2591 Pattern::Constant(rcstr!("@/a/b/")),
2592 Pattern::Dynamic,
2593 Pattern::Constant(rcstr!(".js")),
2594 ])
2595 )
2596 .as_deref(),
2597 None,
2598 );
2599 assert_eq!(
2600 Pattern::Concatenation(vec![
2601 Pattern::Constant(rcstr!("a/b/")),
2602 Pattern::Dynamic,
2603 Pattern::Constant(rcstr!(".ts")),
2604 ])
2605 .match_apply_template(
2606 "a/b/foo.ts",
2607 &Pattern::Concatenation(vec![
2608 Pattern::Constant(rcstr!("@/a/b/x")),
2609 Pattern::Constant(rcstr!(".js")),
2610 ])
2611 )
2612 .as_deref(),
2613 None,
2614 );
2615 assert_eq!(
2616 Pattern::Concatenation(vec![Pattern::Constant(rcstr!("./sub/")), Pattern::Dynamic])
2617 .match_apply_template(
2618 "./sub/file1",
2619 &Pattern::Concatenation(vec![
2620 Pattern::Constant(rcstr!("@/sub/")),
2621 Pattern::Dynamic
2622 ])
2623 )
2624 .as_deref(),
2625 Some("@/sub/file1"),
2626 );
2627 }
2628
2629 #[test]
2630 fn test_split_last_segment() {
2631 assert_eq!(split_last_segment(""), ("", ""));
2632 assert_eq!(split_last_segment("a"), ("", "a"));
2633 assert_eq!(split_last_segment("a/"), ("", "a"));
2634 assert_eq!(split_last_segment("a/b"), ("a", "b"));
2635 assert_eq!(split_last_segment("a/b/"), ("a", "b"));
2636 assert_eq!(split_last_segment("a/b/c"), ("a/b", "c"));
2637 assert_eq!(split_last_segment("a/b/."), ("a", "b"));
2638 assert_eq!(split_last_segment("a/b/.."), ("", "a"));
2639 assert_eq!(split_last_segment("a/b/c/.."), ("a", "b"));
2640 assert_eq!(split_last_segment("a/b/c/../.."), ("", "a"));
2641 assert_eq!(split_last_segment("a/b/c/d/../.."), ("a", "b"));
2642 assert_eq!(split_last_segment("a/b/c/../d/.."), ("a", "b"));
2643 assert_eq!(split_last_segment("a/b/../c/d/.."), ("a/b/..", "c"));
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("./../"), ("./..", ""));
2649 assert_eq!(split_last_segment("../../"), ("../..", ""));
2650 assert_eq!(split_last_segment("../../."), ("../..", ""));
2651 assert_eq!(split_last_segment("../.././"), ("../..", ""));
2652 assert_eq!(split_last_segment("a/.."), ("", ""));
2653 assert_eq!(split_last_segment("a/../"), ("", ""));
2654 assert_eq!(split_last_segment("a/../.."), ("a/../..", ""));
2655 assert_eq!(split_last_segment("a/../../"), ("a/../..", ""));
2656 assert_eq!(split_last_segment("a/././../"), ("", ""));
2657 assert_eq!(split_last_segment("../a"), ("..", "a"));
2658 assert_eq!(split_last_segment("../a/"), ("..", "a"));
2659 assert_eq!(split_last_segment("../../a"), ("../..", "a"));
2660 assert_eq!(split_last_segment("../../a/"), ("../..", "a"));
2661 }
2662
2663 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
2664 async fn test_read_matches() {
2665 let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
2666 BackendOptions::default(),
2667 noop_backing_storage(),
2668 ));
2669 tt.run_once(async {
2670 let root = DiskFileSystem::new(
2671 rcstr!("test"),
2672 Path::new(env!("CARGO_MANIFEST_DIR"))
2673 .join("tests/pattern/read_matches")
2674 .to_str()
2675 .unwrap()
2676 .into(),
2677 )
2678 .root()
2679 .owned()
2680 .await?;
2681
2682 assert_eq!(
2684 vec!["index.js", "sub", "sub/", "sub/foo-a.js", "sub/foo-b.js"],
2685 read_matches(
2686 root.clone(),
2687 rcstr!(""),
2688 false,
2689 Pattern::new(Pattern::Dynamic),
2690 )
2691 .await?
2692 .into_iter()
2693 .map(|m| m.name())
2694 .collect::<Vec<_>>()
2695 );
2696
2697 assert_eq!(
2699 vec!["sub/foo-a.js", "sub/foo-b.js"],
2700 read_matches(
2701 root.clone(),
2702 rcstr!(""),
2703 false,
2704 Pattern::new(Pattern::concat([
2705 Pattern::Constant(rcstr!("sub/foo")),
2706 Pattern::Dynamic,
2707 ])),
2708 )
2709 .await?
2710 .into_iter()
2711 .map(|m| m.name())
2712 .collect::<Vec<_>>()
2713 );
2714
2715 assert_eq!(
2718 vec!["node_modules"] as Vec<&str>,
2719 read_matches(
2720 root.clone(),
2721 rcstr!(""),
2722 false,
2723 Pattern::new(Pattern::Constant(rcstr!("node_modules")).or_any_nested_file()),
2724 )
2725 .await?
2726 .into_iter()
2727 .map(|m| m.name())
2728 .collect::<Vec<_>>()
2729 );
2730
2731 anyhow::Ok(())
2732 })
2733 .await
2734 .unwrap();
2735 }
2736}