turbopack_core/resolve/
alias_map.rs

1use std::{
2    collections::BTreeMap,
3    fmt::{Debug, Formatter},
4    future::Future,
5};
6
7use patricia_tree::PatriciaMap;
8use serde::{
9    Deserialize, Deserializer, Serialize, Serializer,
10    de::{MapAccess, Visitor},
11    ser::SerializeMap,
12};
13use serde_bytes::{ByteBuf, Bytes};
14use turbo_rcstr::RcStr;
15use turbo_tasks::{
16    NonLocalValue,
17    debug::{ValueDebugFormat, ValueDebugFormatString, internal::PassthroughDebug},
18    trace::{TraceRawVcs, TraceRawVcsContext},
19};
20
21use super::pattern::Pattern;
22
23/// A map of [`AliasPattern`]s to the [`Template`]s they resolve to.
24///
25/// If a pattern has a wildcard character (*) within it, it will capture any
26/// number of characters, including path separators. The result of the capture
27/// will then be passed to the template.
28///
29/// If the pattern does not have a wildcard character, it will only match the
30/// exact string, and return the template as-is.
31#[derive(Clone)]
32pub struct AliasMap<T> {
33    map: PatriciaMap<BTreeMap<AliasKey, T>>,
34}
35
36impl<T> Default for AliasMap<T> {
37    fn default() -> Self {
38        Self::new()
39    }
40}
41
42impl<T> PartialEq for AliasMap<T>
43where
44    T: PartialEq,
45{
46    fn eq(&self, other: &Self) -> bool {
47        if self.map.len() != other.map.len() {
48            return false;
49        }
50
51        self.map.iter().zip(other.map.iter()).all(|(a, b)| a == b)
52    }
53}
54
55impl<T> Eq for AliasMap<T> where T: Eq {}
56
57impl<T> Serialize for AliasMap<T>
58where
59    T: Serialize,
60{
61    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
62    where
63        S: Serializer,
64    {
65        let mut map = serializer.serialize_map(Some(self.map.len()))?;
66        for (prefix, value) in self.map.iter() {
67            let key = ByteBuf::from(prefix);
68            map.serialize_entry(&key, value)?;
69        }
70        map.end()
71    }
72}
73
74struct AliasMapVisitor<T> {
75    marker: std::marker::PhantomData<T>,
76}
77
78impl<'de, T> Visitor<'de> for AliasMapVisitor<T>
79where
80    T: Deserialize<'de>,
81{
82    type Value = AliasMap<T>;
83
84    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
85        formatter.write_str("a map of alias patterns to templates")
86    }
87
88    fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
89    where
90        M: MapAccess<'de>,
91    {
92        let mut map = AliasMap::new();
93        while let Some((key, value)) = access.next_entry::<&Bytes, _>()? {
94            map.map.insert(key, value);
95        }
96        Ok(map)
97    }
98}
99
100impl<'a, T> Deserialize<'a> for AliasMap<T>
101where
102    T: Deserialize<'a>,
103{
104    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
105    where
106        D: Deserializer<'a>,
107    {
108        deserializer.deserialize_map(AliasMapVisitor {
109            marker: std::marker::PhantomData,
110        })
111    }
112}
113
114impl<T> TraceRawVcs for AliasMap<T>
115where
116    T: TraceRawVcs,
117{
118    fn trace_raw_vcs(&self, trace_context: &mut TraceRawVcsContext) {
119        for (_, map) in self.map.iter() {
120            for value in map.values() {
121                value.trace_raw_vcs(trace_context);
122            }
123        }
124    }
125}
126
127unsafe impl<T: NonLocalValue> NonLocalValue for AliasMap<T> {}
128
129impl<T> ValueDebugFormat for AliasMap<T>
130where
131    T: ValueDebugFormat,
132{
133    fn value_debug_format(&self, depth: usize) -> ValueDebugFormatString<'_> {
134        if depth == 0 {
135            return ValueDebugFormatString::Sync(std::any::type_name::<Self>().to_string());
136        }
137
138        let values = self
139            .map
140            .iter()
141            .flat_map(|(key, map)| {
142                let key = String::from_utf8(key).expect("invalid UTF-8 key in AliasMap");
143                map.iter().map(move |(alias_key, value)| match alias_key {
144                    AliasKey::Exact => (
145                        key.clone(),
146                        value.value_debug_format(depth.saturating_sub(1)),
147                    ),
148                    AliasKey::Wildcard { suffix } => (
149                        format!("{key}*{suffix}"),
150                        value.value_debug_format(depth.saturating_sub(1)),
151                    ),
152                })
153            })
154            .collect::<Vec<_>>();
155
156        ValueDebugFormatString::Async(Box::pin(async move {
157            let mut values_string = std::collections::HashMap::new();
158            for (key, value) in values {
159                match value {
160                    ValueDebugFormatString::Sync(string) => {
161                        values_string.insert(key, PassthroughDebug::new_string(string));
162                    }
163                    ValueDebugFormatString::Async(future) => {
164                        values_string.insert(key, PassthroughDebug::new_string(future.await?));
165                    }
166                }
167            }
168            Ok(format!("{values_string:#?}"))
169        }))
170    }
171}
172
173impl<T> Debug for AliasMap<T>
174where
175    T: Debug,
176{
177    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
178        f.debug_map()
179            .entries(self.map.iter().flat_map(|(key, map)| {
180                let key = String::from_utf8(key).expect("invalid UTF-8 key in AliasMap");
181                map.iter().map(move |(alias_key, value)| match alias_key {
182                    AliasKey::Exact => (key.clone(), value),
183                    AliasKey::Wildcard { suffix } => (format!("{key}*{suffix}"), value),
184                })
185            }))
186            .finish()
187    }
188}
189
190impl<T> AliasMap<T> {
191    /// Creates a new alias map.
192    pub fn new() -> Self {
193        AliasMap {
194            map: PatriciaMap::new(),
195        }
196    }
197
198    /// Looks up a request in the alias map.
199    ///
200    /// Returns an iterator to all the matching aliases.
201    pub fn lookup<'a>(&'a self, request: &'a Pattern) -> AliasMapLookupIterator<'a, T>
202    where
203        T: Debug,
204    {
205        if matches!(request, Pattern::Alternatives(_)) {
206            panic!("AliasMap::lookup must not be called on alternatives, received {request:?}");
207        }
208
209        // Invariant: prefixes should be sorted by increasing length (base lengths),
210        // according to PATTERN_KEY_COMPARE. Since we're using a prefix tree, this is
211        // the default behavior of the common prefix iterator.
212        let common_prefixes = self
213            .map
214            .common_prefixes(request.constant_prefix().as_bytes());
215        let mut prefixes_stack = common_prefixes
216            .map(|(p, tree)| {
217                let s = match std::str::from_utf8(p) {
218                    Ok(s) => s,
219                    Err(e) => std::str::from_utf8(&p[..e.valid_up_to()]).unwrap(),
220                };
221                (s, tree)
222            })
223            .collect::<Vec<_>>();
224        AliasMapLookupIterator {
225            request,
226            current_prefix_iterator: prefixes_stack
227                .pop()
228                .map(|(prefix, map)| (prefix, map.iter())),
229            prefixes_stack,
230        }
231    }
232
233    /// Looks up a request in the alias map, but only returns aliases where the
234    /// prefix matches a certain predicate.
235    ///
236    /// Returns an iterator to all the matching aliases.
237    pub fn lookup_with_prefix_predicate<'a>(
238        &'a self,
239        request: &'a Pattern,
240        mut prefix_predicate: impl FnMut(&str) -> bool,
241    ) -> AliasMapLookupIterator<'a, T>
242    where
243        T: Debug,
244    {
245        // Invariant: prefixes should be sorted by increasing length (base lengths),
246        // according to PATTERN_KEY_COMPARE. Since we're using a prefix tree, this is
247        // the default behavior of the common prefix iterator.
248        let common_prefixes = self
249            .map
250            .common_prefixes(request.constant_prefix().as_bytes());
251        let mut prefixes_stack = common_prefixes
252            .filter_map(|(p, tree)| {
253                let s = match std::str::from_utf8(p) {
254                    Ok(s) => s,
255                    Err(e) => std::str::from_utf8(&p[..e.valid_up_to()]).unwrap(),
256                };
257                if prefix_predicate(s) {
258                    Some((s, tree))
259                } else {
260                    None
261                }
262            })
263            .collect::<Vec<_>>();
264        AliasMapLookupIterator {
265            request,
266            current_prefix_iterator: prefixes_stack
267                .pop()
268                .map(|(prefix, map)| (prefix, map.iter())),
269            prefixes_stack,
270        }
271    }
272
273    /// Inserts a new alias into the map.
274    ///
275    /// If the map did not have this alias already, `None` is returned.
276    ///
277    /// If the map had this alias, the template is updated, and the old template
278    /// is returned.
279    pub fn insert(&mut self, pattern: AliasPattern, template: T) -> Option<T> {
280        let (prefix_key, alias_key, value) = match pattern {
281            AliasPattern::Exact(exact) => (exact, AliasKey::Exact, template),
282            AliasPattern::Wildcard { prefix, suffix } => {
283                (prefix, AliasKey::Wildcard { suffix }, template)
284            }
285        };
286        // NOTE(alexkirsz) patricia_tree doesn't implement std's `Entry` API,
287        // where we could do:
288        // self.trie
289        //     .entry(key.into())
290        //     .or_insert_with(BTreeSet::new)
291        //     .insert(value)
292        if let Some(map) = self.map.get_mut(&prefix_key) {
293            map.insert(alias_key, value)
294        } else {
295            let mut map = BTreeMap::new();
296            map.insert(alias_key, value);
297            self.map.insert(prefix_key, map);
298            None
299        }
300    }
301}
302
303impl<T> IntoIterator for AliasMap<T> {
304    type Item = (AliasPattern, T);
305
306    type IntoIter = AliasMapIntoIter<T>;
307
308    fn into_iter(self) -> Self::IntoIter {
309        AliasMapIntoIter {
310            iter: self.map.into_iter(),
311            current_prefix_iterator: None,
312        }
313    }
314}
315
316impl<'a, T> IntoIterator for &'a AliasMap<T> {
317    type Item = (AliasPattern, &'a T);
318
319    type IntoIter = AliasMapIter<'a, T>;
320
321    fn into_iter(self) -> Self::IntoIter {
322        AliasMapIter {
323            iter: self.map.iter(),
324            current_prefix_iterator: None,
325        }
326    }
327}
328
329/// An owning iterator over the entries of an `AliasMap`.
330///
331/// Beware: The items are *NOT* returned in the order defined by
332/// [PATTERN_KEY_COMPARE].
333///
334/// [PATTERN_KEY_COMPARE]: https://nodejs.org/api/esm.html#resolver-algorithm-specification
335pub struct AliasMapIntoIter<T> {
336    iter: patricia_tree::map::IntoIter<BTreeMap<AliasKey, T>>,
337    current_prefix_iterator: Option<AliasMapIntoIterItem<T>>,
338}
339
340struct AliasMapIntoIterItem<T> {
341    prefix: RcStr,
342    iterator: std::collections::btree_map::IntoIter<AliasKey, T>,
343}
344
345impl<T> AliasMapIntoIter<T> {
346    fn advance_iter(&mut self) -> Option<&mut AliasMapIntoIterItem<T>> {
347        let (prefix, map) = self.iter.next()?;
348        let prefix = String::from_utf8(prefix)
349            .expect("invalid UTF-8 key in AliasMap")
350            .into();
351        self.current_prefix_iterator = Some(AliasMapIntoIterItem {
352            prefix,
353            iterator: map.into_iter(),
354        });
355        self.current_prefix_iterator.as_mut()
356    }
357}
358
359impl<T> Iterator for AliasMapIntoIter<T> {
360    type Item = (AliasPattern, T);
361
362    fn next(&mut self) -> Option<Self::Item> {
363        let mut current_prefix_iterator = match self.current_prefix_iterator {
364            None => self.advance_iter()?,
365            Some(ref mut current_prefix_iterator) => current_prefix_iterator,
366        };
367        let mut current_value = current_prefix_iterator.iterator.next();
368        loop {
369            match current_value {
370                None => {
371                    current_prefix_iterator = self.advance_iter()?;
372                    current_value = current_prefix_iterator.iterator.next();
373                }
374                Some(current_value) => {
375                    return Some(match current_value {
376                        (AliasKey::Exact, value) => (
377                            AliasPattern::Exact(current_prefix_iterator.prefix.clone()),
378                            value,
379                        ),
380                        (AliasKey::Wildcard { suffix }, value) => (
381                            AliasPattern::Wildcard {
382                                prefix: current_prefix_iterator.prefix.clone(),
383                                suffix,
384                            },
385                            value,
386                        ),
387                    });
388                }
389            }
390        }
391    }
392}
393
394/// A borrowing iterator over the entries of an `AliasMap`.
395///
396/// Beware: The items are *NOT* returned in the order defined by
397/// [PATTERN_KEY_COMPARE].
398///
399/// [PATTERN_KEY_COMPARE]: https://nodejs.org/api/esm.html#resolver-algorithm-specification
400pub struct AliasMapIter<'a, T> {
401    iter: patricia_tree::map::Iter<'a, BTreeMap<AliasKey, T>>,
402    current_prefix_iterator: Option<AliasMapIterItem<'a, T>>,
403}
404
405struct AliasMapIterItem<'a, T> {
406    prefix: RcStr,
407    iterator: std::collections::btree_map::Iter<'a, AliasKey, T>,
408}
409
410impl<T> AliasMapIter<'_, T> {
411    fn advance_iter(&mut self) -> bool {
412        let Some((prefix, map)) = self.iter.next() else {
413            return false;
414        };
415        let prefix = String::from_utf8(prefix)
416            .expect("invalid UTF-8 key in AliasMap")
417            .into();
418        self.current_prefix_iterator = Some(AliasMapIterItem {
419            prefix,
420            iterator: map.iter(),
421        });
422        true
423    }
424}
425
426impl<'a, T> Iterator for AliasMapIter<'a, T> {
427    type Item = (AliasPattern, &'a T);
428
429    fn next(&mut self) -> Option<Self::Item> {
430        let (current_prefix_iterator, current_value) = loop {
431            let Some(current_prefix_iterator) = &mut self.current_prefix_iterator else {
432                if !self.advance_iter() {
433                    return None;
434                }
435                continue;
436            };
437            if let Some(current_value) = current_prefix_iterator.iterator.next() {
438                break (&*current_prefix_iterator, current_value);
439            }
440            self.current_prefix_iterator = None;
441            continue;
442        };
443        Some(match current_value {
444            (AliasKey::Exact, value) => (
445                AliasPattern::Exact(current_prefix_iterator.prefix.clone()),
446                value,
447            ),
448            (AliasKey::Wildcard { suffix }, value) => (
449                AliasPattern::Wildcard {
450                    prefix: current_prefix_iterator.prefix.clone(),
451                    suffix: suffix.clone(),
452                },
453                value,
454            ),
455        })
456    }
457}
458
459impl<T> Extend<(AliasPattern, T)> for AliasMap<T> {
460    fn extend<It>(&mut self, iter: It)
461    where
462        It: IntoIterator<Item = (AliasPattern, T)>,
463    {
464        for (pattern, value) in iter {
465            self.insert(pattern, value);
466        }
467    }
468}
469
470/// An iterator over the aliases that match a request.
471///
472/// The items are returned in the order defined by [PATTERN_KEY_COMPARE].
473///
474/// [PATTERN_KEY_COMPARE]: https://nodejs.org/api/esm.html#resolution-algorithm-specification
475pub struct AliasMapLookupIterator<'a, T> {
476    request: &'a Pattern,
477    prefixes_stack: Vec<(&'a str, &'a BTreeMap<AliasKey, T>)>,
478    current_prefix_iterator: Option<(&'a str, std::collections::btree_map::Iter<'a, AliasKey, T>)>,
479}
480
481impl<'a, T> Iterator for AliasMapLookupIterator<'a, T>
482where
483    T: AliasTemplate,
484{
485    type Item = AliasMatch<'a, T>;
486
487    fn next(&mut self) -> Option<Self::Item> {
488        let (prefix, current_prefix_iterator) = self.current_prefix_iterator.as_mut()?;
489
490        loop {
491            for (key, template) in &mut *current_prefix_iterator {
492                match key {
493                    AliasKey::Exact => {
494                        if self.request.is_match(prefix) {
495                            return Some(AliasMatch::Exact(template.convert()));
496                        }
497                    }
498                    AliasKey::Wildcard { suffix } => {
499                        let mut remaining = self.request.clone();
500                        remaining.strip_prefix(prefix.len());
501                        let remaining_suffix = remaining.constant_suffix();
502                        if !remaining_suffix.ends_with(&**suffix) {
503                            continue;
504                        }
505                        remaining.strip_suffix(suffix.len());
506
507                        let output = template.replace(&remaining);
508                        return Some(AliasMatch::Replaced(output));
509                    }
510                }
511            }
512
513            let (new_prefix, new_current_prefix_iterator) = self.prefixes_stack.pop()?;
514            *prefix = new_prefix;
515            *current_prefix_iterator = new_current_prefix_iterator.iter();
516        }
517    }
518}
519
520/// An alias pattern.
521#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
522pub enum AliasPattern {
523    /// Will match an exact string.
524    Exact(RcStr),
525    /// Will match a pattern with a single wildcard.
526    Wildcard { prefix: RcStr, suffix: RcStr },
527}
528
529impl AliasPattern {
530    /// Parses an alias pattern from a string.
531    ///
532    /// Wildcard characters (*) present in the string will match any number of
533    /// characters, including path separators.
534    pub fn parse<'a, T>(pattern: T) -> Self
535    where
536        T: Into<RcStr> + 'a,
537    {
538        let pattern = pattern.into();
539        if let Some(wildcard_index) = pattern.find('*') {
540            let mut pattern = pattern.into_owned();
541
542            let suffix = pattern[wildcard_index + 1..].into();
543            pattern.truncate(wildcard_index);
544            AliasPattern::Wildcard {
545                prefix: pattern.into(),
546                suffix,
547            }
548        } else {
549            AliasPattern::Exact(pattern)
550        }
551    }
552
553    /// Creates a pattern that will only match exactly what was passed in.
554    pub fn exact<'a, T>(pattern: T) -> Self
555    where
556        T: Into<RcStr> + 'a,
557    {
558        AliasPattern::Exact(pattern.into())
559    }
560
561    /// Creates a pattern that will match, sequentially:
562    /// 1. a prefix; then
563    /// 2. any number of characters, including path separators; then
564    /// 3. a suffix.
565    pub fn wildcard<'p, 's, P, S>(prefix: P, suffix: S) -> Self
566    where
567        P: Into<RcStr> + 'p,
568        S: Into<RcStr> + 's,
569    {
570        AliasPattern::Wildcard {
571            prefix: prefix.into(),
572            suffix: suffix.into(),
573        }
574    }
575}
576
577#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, TraceRawVcs, NonLocalValue)]
578enum AliasKey {
579    Exact,
580    Wildcard { suffix: RcStr },
581}
582
583/// Result of a lookup in the alias map.
584#[derive(Debug, PartialEq)]
585pub enum AliasMatch<'a, T>
586where
587    T: AliasTemplate + 'a,
588{
589    /// The request matched an exact alias.
590    Exact(T::Output<'a>),
591    /// The request matched a wildcard alias.
592    Replaced(T::Output<'a>),
593}
594
595impl<'a, T> AliasMatch<'a, T>
596where
597    T: AliasTemplate,
598{
599    /// Returns the exact match, if any.
600    pub fn as_exact(&self) -> Option<&T::Output<'a>> {
601        if let Self::Exact(v) = self {
602            Some(v)
603        } else {
604            None
605        }
606    }
607
608    /// Returns the replaced match, if any.
609    pub fn as_replaced(&self) -> Option<&T::Output<'a>> {
610        if let Self::Replaced(v) = self {
611            Some(v)
612        } else {
613            None
614        }
615    }
616
617    /// Returns the wrapped value.
618    pub fn as_self(&self) -> &T::Output<'a> {
619        match self {
620            Self::Exact(v) => v,
621            Self::Replaced(v) => v,
622        }
623    }
624}
625
626impl<'a, T, R, E> AliasMatch<'a, T>
627where
628    T: AliasTemplate<Output<'a> = Result<R, E>> + Clone,
629{
630    /// Returns the wrapped value.
631    ///
632    /// Consumes the match.
633    ///
634    /// Only implemented when `T::Output` is some `Result<_, _>`.
635    pub fn try_into_self(self) -> Result<R, E> {
636        Ok(match self {
637            Self::Exact(v) => v?,
638            Self::Replaced(v) => v?,
639        })
640    }
641}
642
643impl<'a, T, R, E, F> AliasMatch<'a, T>
644where
645    F: Future<Output = Result<R, E>>,
646    T: AliasTemplate<Output<'a> = F> + Clone,
647{
648    /// Returns the wrapped value.
649    ///
650    /// Consumes the match.
651    ///
652    /// Only implemented when `T::Output` is some `impl Future<Result<_, _>>`
653    pub async fn try_join_into_self(self) -> Result<R, E> {
654        Ok(match self {
655            Self::Exact(v) => v.await?,
656            Self::Replaced(v) => v.await?,
657        })
658    }
659}
660
661impl PartialOrd for AliasKey {
662    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
663        Some(self.cmp(other))
664    }
665}
666
667impl Ord for AliasKey {
668    /// According to [PATTERN_KEY_COMPARE].
669    ///
670    /// [PATTERN_KEY_COMPARE]: https://nodejs.org/api/esm.html#resolver-algorithm-specification
671    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
672        match (self, other) {
673            (AliasKey::Wildcard { suffix: l_suffix }, AliasKey::Wildcard { suffix: r_suffix }) => {
674                l_suffix
675                    .len()
676                    .cmp(&r_suffix.len())
677                    .reverse()
678                    .then_with(|| l_suffix.cmp(r_suffix))
679            }
680            (AliasKey::Wildcard { .. }, _) => std::cmp::Ordering::Less,
681            (_, AliasKey::Wildcard { .. }) => std::cmp::Ordering::Greater,
682            _ => std::cmp::Ordering::Equal,
683        }
684    }
685}
686
687/// A trait for types that can be used as a template for an alias.
688pub trait AliasTemplate {
689    /// The type of the output of the replacement.
690    type Output<'a>
691    where
692        Self: 'a;
693
694    /// Turn `self` into a `Self::Output`
695    fn convert(&self) -> Self::Output<'_>;
696
697    /// Replaces `capture` within `self`.
698    fn replace<'a>(&'a self, capture: &Pattern) -> Self::Output<'a>;
699}
700
701#[cfg(test)]
702mod test {
703    use std::assert_matches::assert_matches;
704
705    use super::{AliasMap, AliasPattern, AliasTemplate};
706    use crate::resolve::pattern::Pattern;
707
708    /// Asserts that an [`AliasMap`] lookup yields the expected results. The
709    /// order of the results is important.
710    ///
711    /// See below for usage examples.
712    macro_rules! assert_alias_matches {
713        ($map:expr, $request:expr$(, $($tail:tt)*)?) => {
714            let request = Pattern::Constant($request.into());
715            let mut lookup = $map.lookup(&request);
716
717            $(assert_alias_matches!(@next lookup, $($tail)*);)?
718            assert_matches!(lookup.next(), None);
719        };
720
721        (@next $lookup:ident, exact($pattern:expr)$(, $($tail:tt)*)?) => {
722            match $lookup.next().unwrap() {
723                super::AliasMatch::Exact(Pattern::Constant(c)) if c == $pattern => {}
724                m => panic!("unexpected match {:?}", m),
725            }
726            $(assert_alias_matches!(@next $lookup, $($tail)*);)?
727        };
728
729        (@next $lookup:ident, replaced($pattern:expr)$(, $($tail:tt)*)?) => {
730            match $lookup.next().unwrap() {
731                super::AliasMatch::Replaced(Pattern::Constant(c)) if c == $pattern => {}
732                m => panic!("unexpected match {:?}", m),
733            }
734            $(assert_alias_matches!(@next $lookup, $($tail)*);)?
735        };
736
737        (@next $lookup:ident, replaced_owned($value:expr)$(, $($tail:tt)*)?) => {
738            match $lookup.next().unwrap() {
739                super::AliasMatch::Replaced(Pattern::Constant(c)) if c == $value => {}
740                m => panic!("unexpected match {:?}", m),
741            }
742            $(assert_alias_matches!(@next $lookup, $($tail)*);)?
743        };
744
745        // Handle trailing comma.
746        (@next $lookup:ident,) => {};
747    }
748
749    impl<'a> AliasTemplate for &'a str {
750        type Output<'b>
751            = Pattern
752        where
753            Self: 'b;
754
755        fn replace(&self, capture: &Pattern) -> Self::Output<'a> {
756            capture.spread_into_star(self)
757        }
758
759        fn convert(&self) -> Self::Output<'a> {
760            Pattern::Constant(self.to_string().into())
761        }
762    }
763
764    #[test]
765    fn test_one_exact() {
766        let mut map = AliasMap::new();
767        map.insert(AliasPattern::parse("foo"), "bar");
768
769        assert_alias_matches!(map, "");
770        assert_alias_matches!(map, "foo", exact("bar"));
771        assert_alias_matches!(map, "foobar");
772    }
773
774    #[test]
775    fn test_many_exact() {
776        let mut map = AliasMap::new();
777        map.insert(AliasPattern::parse("foo"), "bar");
778        map.insert(AliasPattern::parse("bar"), "foo");
779        map.insert(AliasPattern::parse("foobar"), "barfoo");
780
781        assert_alias_matches!(map, "");
782        assert_alias_matches!(map, "foo", exact("bar"));
783        assert_alias_matches!(map, "bar", exact("foo"));
784        assert_alias_matches!(map, "foobar", exact("barfoo"));
785    }
786
787    #[test]
788    fn test_empty() {
789        let mut map = AliasMap::new();
790        map.insert(AliasPattern::parse(""), "empty");
791        map.insert(AliasPattern::parse("foo"), "bar");
792
793        assert_alias_matches!(map, "", exact("empty"));
794        assert_alias_matches!(map, "foo", exact("bar"));
795    }
796
797    #[test]
798    fn test_left_wildcard() {
799        let mut map = AliasMap::new();
800        map.insert(AliasPattern::parse("foo*"), "bar");
801
802        assert_alias_matches!(map, "");
803        assert_alias_matches!(map, "foo", replaced("bar"));
804        assert_alias_matches!(map, "foobar", replaced("bar"));
805    }
806
807    #[test]
808    fn test_wildcard_replace_suffix() {
809        let mut map = AliasMap::new();
810        map.insert(AliasPattern::parse("foo*"), "bar*");
811        map.insert(AliasPattern::parse("foofoo*"), "barbar*");
812
813        assert_alias_matches!(map, "");
814        assert_alias_matches!(map, "foo", replaced_owned("bar"));
815        assert_alias_matches!(map, "foobar", replaced_owned("barbar"));
816        assert_alias_matches!(
817            map,
818            "foofoobar",
819            // The longer prefix should come first.
820            replaced_owned("barbarbar"),
821            replaced_owned("barfoobar"),
822        );
823    }
824
825    #[test]
826    fn test_wildcard_replace_prefix() {
827        let mut map = AliasMap::new();
828        map.insert(AliasPattern::parse("*foo"), "*bar");
829        map.insert(AliasPattern::parse("*foofoo"), "*barbar");
830
831        assert_alias_matches!(map, "");
832        assert_alias_matches!(map, "foo", replaced_owned("bar"));
833        assert_alias_matches!(map, "barfoo", replaced_owned("barbar"));
834        assert_alias_matches!(
835            map,
836            "barfoofoo",
837            // The longer suffix should come first.
838            replaced_owned("barbarbar"),
839            replaced_owned("barfoobar"),
840        );
841    }
842
843    #[test]
844    fn test_wildcard_replace_infix() {
845        let mut map = AliasMap::new();
846        map.insert(AliasPattern::parse("foo*foo"), "bar*bar");
847        map.insert(AliasPattern::parse("foo*foofoo"), "bar*barbar");
848        map.insert(AliasPattern::parse("foofoo*foo"), "bazbaz*baz");
849
850        assert_alias_matches!(map, "");
851        assert_alias_matches!(map, "foo");
852        assert_alias_matches!(map, "foofoo", replaced_owned("barbar"));
853        assert_alias_matches!(map, "foobazfoo", replaced_owned("barbazbar"));
854        assert_alias_matches!(
855            map,
856            "foofoofoo",
857            // The longer prefix should come first.
858            replaced_owned("bazbazbaz"),
859            // Then the longer suffix.
860            replaced_owned("barbarbar"),
861            replaced_owned("barfoobar"),
862        );
863        assert_alias_matches!(
864            map,
865            "foobazfoofoo",
866            // The longer suffix should come first.
867            replaced_owned("barbazbarbar"),
868            replaced_owned("barbazfoobar"),
869        );
870        assert_alias_matches!(
871            map,
872            "foofoobarfoo",
873            // The longer prefix should come first.
874            replaced_owned("bazbazbarbaz"),
875            replaced_owned("barfoobarbar"),
876        );
877        assert_alias_matches!(
878            map,
879            "foofoofoofoofoo",
880            // The longer prefix should come first.
881            replaced_owned("bazbazfoofoobaz"),
882            // Then the longer suffix.
883            replaced_owned("barfoofoobarbar"),
884            replaced_owned("barfoofoofoobar"),
885        );
886    }
887
888    #[test]
889    fn test_wildcard_replace_only() {
890        let mut map = AliasMap::new();
891        map.insert(AliasPattern::parse("*"), "foo*foo");
892        map.insert(AliasPattern::parse("**"), "bar*foo");
893
894        assert_alias_matches!(map, "", replaced_owned("foofoo"));
895        assert_alias_matches!(map, "bar", replaced_owned("foobarfoo"));
896        assert_alias_matches!(
897            map,
898            "*",
899            replaced_owned("barfoo"),
900            replaced_owned("foo*foo"),
901        );
902        assert_alias_matches!(
903            map,
904            "**",
905            replaced_owned("bar*foo"),
906            replaced_owned("foo**foo")
907        );
908    }
909
910    #[test]
911    fn test_pattern() {
912        let mut map = AliasMap::new();
913        map.insert(AliasPattern::parse("card/*"), "src/cards/*");
914        map.insert(AliasPattern::parse("comp/*/x"), "src/comps/*/x");
915        map.insert(AliasPattern::parse("head/*/x"), "src/heads/*");
916
917        assert_eq!(
918            map.lookup(&Pattern::Concatenation(vec![
919                Pattern::Constant("card/".into()),
920                Pattern::Dynamic
921            ]))
922            .collect::<Vec<_>>(),
923            vec![super::AliasMatch::Replaced(Pattern::Concatenation(vec![
924                Pattern::Constant("src/cards/".into()),
925                Pattern::Dynamic
926            ]))]
927        );
928        assert_eq!(
929            map.lookup(&Pattern::Concatenation(vec![
930                Pattern::Constant("comp/".into()),
931                Pattern::Dynamic,
932                Pattern::Constant("/x".into()),
933            ]))
934            .collect::<Vec<_>>(),
935            vec![super::AliasMatch::Replaced(Pattern::Concatenation(vec![
936                Pattern::Constant("src/comps/".into()),
937                Pattern::Dynamic,
938                Pattern::Constant("/x".into()),
939            ]))]
940        );
941        assert_eq!(
942            map.lookup(&Pattern::Concatenation(vec![
943                Pattern::Constant("head/".into()),
944                Pattern::Dynamic,
945                Pattern::Constant("/x".into()),
946            ]))
947            .collect::<Vec<_>>(),
948            vec![super::AliasMatch::Replaced(Pattern::Concatenation(vec![
949                Pattern::Constant("src/heads/".into()),
950                Pattern::Dynamic,
951            ]))]
952        );
953    }
954}