Skip to main content

turbopack_core/resolve/
remap.rs

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