turbopack_core/resolve/
remap.rs

1use std::{borrow::Cow, collections::BTreeMap, fmt::Display, ops::Deref};
2
3use anyhow::{Result, bail};
4use rustc_hash::FxHashMap;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use turbo_rcstr::{RcStr, rcstr};
8use turbo_tasks::FxIndexMap;
9
10use super::{
11    alias_map::{AliasMap, AliasMapIter, AliasPattern, AliasTemplate},
12    options::ConditionValue,
13    pattern::Pattern,
14};
15use crate::resolve::alias_map::AliasKey;
16
17/// A small helper type to differentiate parsing exports and imports fields.
18#[derive(Copy, Clone)]
19enum ExportImport {
20    Export,
21    Import,
22}
23
24impl Display for ExportImport {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        match self {
27            Self::Export => f.write_str("export"),
28            Self::Import => f.write_str("import"),
29        }
30    }
31}
32
33/// The result an "exports"/"imports" field describes. Can represent multiple
34/// alternatives, conditional result, ignored result (null mapping) and a plain
35/// result.
36#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
37pub enum SubpathValue {
38    /// Alternative subpaths, defined with `"path": ["other1", "other2"]`,
39    /// allows for specifying multiple possible remappings to be tried. This
40    /// may be that conditions didn't match, or that a particular path
41    /// wasn't found.
42    Alternatives(Vec<SubpathValue>),
43
44    /// Conditional subpaths, defined with `"path": { "condition": "other"}`,
45    /// allow remapping based on certain predefined conditions. Eg, if using
46    /// ESM import syntax, the `import` condition allows you to remap to a
47    /// file that uses ESM syntax.
48    /// Node defines several conditions in https://nodejs.org/api/packages.html#conditional-exports
49    /// TODO: Should this use an enum of predefined keys?
50    Conditional(Vec<(RcStr, SubpathValue)>),
51
52    /// A result subpath, defined with `"path": "other"`, remaps imports of
53    /// `path` to `other`.
54    Result(RcStr),
55
56    /// An excluded subpath, defined with `"path": null`, prevents importing
57    /// this subpath.
58    Excluded,
59}
60
61/// A `SubpathValue` that was applied to a pattern. See `SubpathValue` for
62/// more details on the variants.
63#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
64pub enum ReplacedSubpathValue {
65    Alternatives(Vec<ReplacedSubpathValue>),
66    Conditional(Vec<(RcStr, ReplacedSubpathValue)>),
67    Result(Pattern),
68    Excluded,
69}
70
71impl AliasTemplate for SubpathValue {
72    type Output<'a>
73        = ReplacedSubpathValue
74    where
75        Self: 'a;
76
77    fn convert(&self) -> ReplacedSubpathValue {
78        match self {
79            SubpathValue::Alternatives(list) => ReplacedSubpathValue::Alternatives(
80                list.iter()
81                    .map(|value: &SubpathValue| value.convert())
82                    .collect::<Vec<_>>(),
83            ),
84            SubpathValue::Conditional(list) => ReplacedSubpathValue::Conditional(
85                list.iter()
86                    .map(|(condition, value)| (condition.clone(), value.convert()))
87                    .collect::<Vec<_>>(),
88            ),
89            SubpathValue::Result(value) => ReplacedSubpathValue::Result(value.clone().into()),
90            SubpathValue::Excluded => ReplacedSubpathValue::Excluded,
91        }
92    }
93
94    fn replace(&self, capture: &Pattern) -> ReplacedSubpathValue {
95        match self {
96            SubpathValue::Alternatives(list) => ReplacedSubpathValue::Alternatives(
97                list.iter()
98                    .map(|value: &SubpathValue| value.replace(capture))
99                    .collect::<Vec<_>>(),
100            ),
101            SubpathValue::Conditional(list) => ReplacedSubpathValue::Conditional(
102                list.iter()
103                    .map(|(condition, value)| (condition.clone(), value.replace(capture)))
104                    .collect::<Vec<_>>(),
105            ),
106            SubpathValue::Result(value) => {
107                ReplacedSubpathValue::Result(capture.spread_into_star(value))
108            }
109            SubpathValue::Excluded => ReplacedSubpathValue::Excluded,
110        }
111    }
112}
113
114impl SubpathValue {
115    /// Returns an iterator over all leaf results.
116    fn results_mut(&mut self) -> ResultsIterMut<'_> {
117        ResultsIterMut { stack: vec![self] }
118    }
119
120    /// Walks the [SubpathValue] and adds results to the `target` vector. It
121    /// uses the `conditions` to skip or enter conditional results.
122    /// The state of conditions is stored within `condition_overrides`, which is
123    /// also exposed to the consumer.
124    pub fn add_results<'a>(
125        &'a self,
126        conditions: &BTreeMap<RcStr, ConditionValue>,
127        unspecified_condition: &ConditionValue,
128        condition_overrides: &mut FxHashMap<&'a str, ConditionValue>,
129        target: &mut Vec<(&'a str, Vec<(&'a str, bool)>)>,
130    ) -> bool {
131        match self {
132            SubpathValue::Alternatives(list) => {
133                for value in list {
134                    if value.add_results(
135                        conditions,
136                        unspecified_condition,
137                        condition_overrides,
138                        target,
139                    ) {
140                        return true;
141                    }
142                }
143                false
144            }
145            SubpathValue::Conditional(list) => {
146                for (condition, value) in list {
147                    let condition_value = if condition == "default" {
148                        &ConditionValue::Set
149                    } else {
150                        condition_overrides
151                            .get(condition.as_str())
152                            .or_else(|| conditions.get(condition))
153                            .unwrap_or(unspecified_condition)
154                    };
155                    match condition_value {
156                        ConditionValue::Set => {
157                            if value.add_results(
158                                conditions,
159                                unspecified_condition,
160                                condition_overrides,
161                                target,
162                            ) {
163                                return true;
164                            }
165                        }
166                        ConditionValue::Unset => {}
167                        ConditionValue::Unknown => {
168                            condition_overrides.insert(condition, ConditionValue::Set);
169                            if value.add_results(
170                                conditions,
171                                unspecified_condition,
172                                condition_overrides,
173                                target,
174                            ) {
175                                condition_overrides.insert(condition, ConditionValue::Unset);
176                            } else {
177                                condition_overrides.remove(condition.as_str());
178                            }
179                        }
180                    }
181                }
182                false
183            }
184            SubpathValue::Result(r) => {
185                target.push((
186                    r,
187                    condition_overrides
188                        .iter()
189                        .filter_map(|(k, v)| match v {
190                            ConditionValue::Set => Some((*k, true)),
191                            ConditionValue::Unset => Some((*k, false)),
192                            ConditionValue::Unknown => None,
193                        })
194                        .collect(),
195                ));
196                true
197            }
198            SubpathValue::Excluded => true,
199        }
200    }
201
202    fn try_new(value: &Value, ty: ExportImport) -> Result<Self> {
203        match value {
204            Value::Null => Ok(SubpathValue::Excluded),
205            Value::String(s) => Ok(SubpathValue::Result(s.as_str().into())),
206            Value::Number(_) => bail!("numeric values are invalid in {ty}s field entries"),
207            Value::Bool(_) => bail!("boolean values are invalid in {ty}s field entries"),
208            Value::Object(object) => Ok(SubpathValue::Conditional(
209                object
210                    .iter()
211                    .map(|(key, value)| {
212                        if key.starts_with('.') {
213                            bail!(
214                                "invalid key \"{}\" in an {ty} field conditions object. Did you \
215                                 mean to place this request at a higher level?",
216                                key
217                            );
218                        }
219
220                        Ok((key.as_str().into(), SubpathValue::try_new(value, ty)?))
221                    })
222                    .collect::<Result<Vec<_>>>()?,
223            )),
224            Value::Array(array) => Ok(SubpathValue::Alternatives(
225                array
226                    .iter()
227                    .map(|value| SubpathValue::try_new(value, ty))
228                    .collect::<Result<Vec<_>>>()?,
229            )),
230        }
231    }
232}
233
234pub struct ReplacedSubpathValueResult<'a, 'b> {
235    pub result_path: Pattern,
236    pub conditions: Vec<(RcStr, bool)>,
237    pub map_prefix: Cow<'a, str>,
238    pub map_key: &'b AliasKey,
239}
240
241impl ReplacedSubpathValue {
242    // TODO
243    #[allow(clippy::type_complexity)]
244    /// Walks the [ReplacedSubpathValue] and adds results to the `target`
245    /// vector. It uses the `conditions` to skip or enter conditional
246    /// results. The state of conditions is stored within
247    /// `condition_overrides`, which is also exposed to the consumer.
248    pub fn add_results<'a, 'b>(
249        self,
250        prefix: Cow<'a, str>,
251        key: &'b AliasKey,
252        conditions: &BTreeMap<RcStr, ConditionValue>,
253        unspecified_condition: &ConditionValue,
254        condition_overrides: &mut FxHashMap<RcStr, ConditionValue>,
255        target: &mut Vec<ReplacedSubpathValueResult<'a, 'b>>,
256    ) -> bool {
257        match self {
258            ReplacedSubpathValue::Alternatives(list) => {
259                for value in list {
260                    if value.add_results(
261                        prefix.clone(),
262                        key,
263                        conditions,
264                        unspecified_condition,
265                        condition_overrides,
266                        target,
267                    ) {
268                        return true;
269                    }
270                }
271                false
272            }
273            ReplacedSubpathValue::Conditional(list) => {
274                for (condition, value) in list {
275                    let condition_value = if condition == "default" {
276                        &ConditionValue::Set
277                    } else {
278                        condition_overrides
279                            .get(condition.as_str())
280                            .or_else(|| conditions.get(&condition))
281                            .unwrap_or(unspecified_condition)
282                    };
283                    match condition_value {
284                        ConditionValue::Set => {
285                            if value.add_results(
286                                prefix.clone(),
287                                key,
288                                conditions,
289                                unspecified_condition,
290                                condition_overrides,
291                                target,
292                            ) {
293                                return true;
294                            }
295                        }
296                        ConditionValue::Unset => {}
297                        ConditionValue::Unknown => {
298                            condition_overrides.insert(condition.clone(), ConditionValue::Set);
299                            if value.add_results(
300                                prefix.clone(),
301                                key,
302                                conditions,
303                                unspecified_condition,
304                                condition_overrides,
305                                target,
306                            ) {
307                                condition_overrides.insert(condition, ConditionValue::Unset);
308                            } else {
309                                condition_overrides.remove(condition.as_str());
310                            }
311                        }
312                    }
313                }
314                false
315            }
316            ReplacedSubpathValue::Result(r) => {
317                target.push(ReplacedSubpathValueResult {
318                    result_path: r,
319                    conditions: condition_overrides
320                        .iter()
321                        .filter_map(|(k, v)| match v {
322                            ConditionValue::Set => Some((k.clone(), true)),
323                            ConditionValue::Unset => Some((k.clone(), false)),
324                            ConditionValue::Unknown => None,
325                        })
326                        .collect(),
327                    map_prefix: prefix,
328                    map_key: key,
329                });
330                true
331            }
332            ReplacedSubpathValue::Excluded => true,
333        }
334    }
335}
336
337struct ResultsIterMut<'a> {
338    stack: Vec<&'a mut SubpathValue>,
339}
340
341impl<'a> Iterator for ResultsIterMut<'a> {
342    type Item = &'a mut RcStr;
343
344    fn next(&mut self) -> Option<Self::Item> {
345        while let Some(value) = self.stack.pop() {
346            match value {
347                SubpathValue::Alternatives(list) => {
348                    for value in list {
349                        self.stack.push(value);
350                    }
351                }
352                SubpathValue::Conditional(list) => {
353                    for (_, value) in list {
354                        self.stack.push(value);
355                    }
356                }
357                SubpathValue::Result(r) => return Some(r),
358                SubpathValue::Excluded => {}
359            }
360        }
361        None
362    }
363}
364
365/// Content of an "exports" field in a package.json
366#[derive(PartialEq, Eq, Serialize, Deserialize)]
367pub struct ExportsField(AliasMap<SubpathValue>);
368
369impl TryFrom<&Value> for ExportsField {
370    type Error = anyhow::Error;
371
372    fn try_from(value: &Value) -> Result<Self> {
373        // The "exports" field can be an object, a string, or an array of strings.
374        // https://nodejs.org/api/packages.html#exports
375        let map = match value {
376            Value::Object(object) => {
377                let mut map = AliasMap::new();
378                // Conditional exports can also be defined at the top-level of the
379                // exports field, where they will apply to the package itself.
380                let mut conditions = vec![];
381
382                for (key, value) in object.iter() {
383                    // NOTE: Node.js does not allow conditional and non-conditional keys
384                    // to be mixed at the top-level, but we do.
385                    if key != "." && !key.starts_with("./") {
386                        conditions.push((key, value));
387                        continue;
388                    }
389
390                    let mut value = SubpathValue::try_new(value, ExportImport::Export)?;
391
392                    let pattern = if is_folder_shorthand(key) {
393                        expand_folder_shorthand(key, &mut value)?
394                    } else {
395                        AliasPattern::parse(key.as_str())
396                    };
397
398                    map.insert(pattern, value);
399                }
400
401                if !conditions.is_empty() {
402                    map.insert(
403                        AliasPattern::Exact(rcstr!(".")),
404                        SubpathValue::Conditional(
405                            conditions
406                                .into_iter()
407                                .map(|(key, value)| {
408                                    Ok((
409                                        key.as_str().into(),
410                                        SubpathValue::try_new(value, ExportImport::Export)?,
411                                    ))
412                                })
413                                .collect::<Result<Vec<_>>>()?,
414                        ),
415                    );
416                }
417
418                map
419            }
420            Value::String(string) => {
421                let mut map = AliasMap::new();
422                map.insert(
423                    AliasPattern::Exact(rcstr!(".")),
424                    SubpathValue::Result(string.as_str().into()),
425                );
426                map
427            }
428            Value::Array(array) => {
429                let mut map = AliasMap::new();
430                map.insert(
431                    AliasPattern::Exact(rcstr!(".")),
432                    // This allows for more complex patterns than the spec allows, since we accept
433                    // the following:
434                    // [{ "node": "./node.js", "default": "./index.js" }, "./index.js"]
435                    SubpathValue::Alternatives(
436                        array
437                            .iter()
438                            .map(|value| SubpathValue::try_new(value, ExportImport::Export))
439                            .collect::<Result<Vec<_>>>()?,
440                    ),
441                );
442                map
443            }
444            _ => {
445                bail!("\"exports\" field must be an object or a string");
446            }
447        };
448        Ok(Self(map))
449    }
450}
451
452impl Deref for ExportsField {
453    type Target = AliasMap<SubpathValue>;
454    fn deref(&self) -> &Self::Target {
455        &self.0
456    }
457}
458
459/// Content of an "imports" field in a package.json
460#[derive(PartialEq, Eq, Serialize, Deserialize)]
461pub struct ImportsField(AliasMap<SubpathValue>);
462
463impl TryFrom<&Value> for ImportsField {
464    type Error = anyhow::Error;
465
466    fn try_from(value: &Value) -> Result<Self> {
467        // The "imports" field must be an object.
468        // https://nodejs.org/api/packages.html#imports
469        let map = match value {
470            Value::Object(object) => {
471                let mut map = AliasMap::new();
472
473                for (key, value) in object.iter() {
474                    if !key.starts_with('#') {
475                        bail!("imports key \"{key}\" must begin with a '#'")
476                    }
477                    let value = SubpathValue::try_new(value, ExportImport::Import)?;
478                    map.insert(AliasPattern::parse(key.as_str()), value);
479                }
480
481                map
482            }
483            _ => bail!("\"imports\" field must be an object"),
484        };
485        Ok(Self(map))
486    }
487}
488
489impl Deref for ImportsField {
490    type Target = AliasMap<SubpathValue>;
491    fn deref(&self) -> &Self::Target {
492        &self.0
493    }
494}
495
496/// Returns true if the given string is a folder path shorthand.
497fn is_folder_shorthand(key: &str) -> bool {
498    key.ends_with('/') && key.find('*').is_none()
499}
500
501/// The exports field supports a shorthand for folders, where:
502///   "./folder/": "./other-folder/"
503/// is equivalent to
504///   "./folder/*": "./other-folder/*"
505/// This is not implemented directly by [`AliasMap`] as it is not
506/// shared behavior with the tsconfig.json `paths` field. Instead,
507/// we do the expansion here.
508fn expand_folder_shorthand(key: &str, value: &mut SubpathValue) -> Result<AliasPattern> {
509    // Transform folder patterns into wildcard patterns.
510    let pattern = AliasPattern::wildcard(key, rcstr!(""));
511
512    // Transform templates into wildcard patterns as well.
513    for result in value.results_mut() {
514        if result.ends_with('/') {
515            if result.find('*').is_none() {
516                let mut buf = result.to_string();
517                buf.push('*');
518                *result = buf.into();
519            } else {
520                bail!(
521                    "invalid exports field value \"{}\" for key \"{}\": \"*\" is not allowed in \
522                     folder exports",
523                    result,
524                    key
525                );
526            }
527        } else {
528            bail!(
529                "invalid exports field value \"{}\" for key \"{}\": folder exports must end with \
530                 \"/\"",
531                result,
532                key
533            );
534        }
535    }
536
537    Ok(pattern)
538}
539
540/// Content of an "alias" configuration
541#[turbo_tasks::value(shared)]
542#[derive(Default)]
543pub struct ResolveAliasMap(#[turbo_tasks(trace_ignore)] AliasMap<SubpathValue>);
544
545impl TryFrom<&FxIndexMap<RcStr, Value>> for ResolveAliasMap {
546    type Error = anyhow::Error;
547
548    fn try_from(object: &FxIndexMap<RcStr, Value>) -> Result<Self> {
549        let mut map = AliasMap::new();
550
551        for (key, value) in object.iter() {
552            let mut value = SubpathValue::try_new(value, ExportImport::Export)?;
553
554            let pattern = if is_folder_shorthand(key) {
555                expand_folder_shorthand(key, &mut value)?
556            } else {
557                AliasPattern::parse(key.as_str())
558            };
559
560            map.insert(pattern, value);
561        }
562        Ok(Self(map))
563    }
564}
565
566impl<'a> IntoIterator for &'a ResolveAliasMap {
567    type Item = (AliasPattern, &'a SubpathValue);
568    type IntoIter = AliasMapIter<'a, SubpathValue>;
569
570    fn into_iter(self) -> Self::IntoIter {
571        (&self.0).into_iter()
572    }
573}