Skip to main content

turbopack_ecmascript/analyzer/well_known/
require_context.rs

1use std::{
2    hash::{Hash, Hasher},
3    sync::LazyLock,
4};
5
6use anyhow::{Result, bail};
7use turbo_esregex::EsRegex;
8use turbo_rcstr::RcStr;
9use turbo_tasks::{FxIndexMap, Vc};
10
11use crate::{
12    analyzer::{ConstantValue, JsValue},
13    references::require_context::RequireContextMap,
14};
15
16#[derive(Debug, Clone)]
17pub struct RequireContextOptions {
18    pub dir: RcStr,
19    pub include_subdirs: bool,
20    /// this is a regex (pattern, flags)
21    pub filter: EsRegex,
22}
23
24/// Parse the arguments passed to a require.context invocation, validate them
25/// and convert them to the appropriate rust values.
26pub fn parse_require_context(args: &[JsValue<'_>]) -> Result<RequireContextOptions> {
27    if !(1..=3).contains(&args.len()) {
28        // https://linear.app/vercel/issue/WEB-910/add-support-for-requirecontexts-mode-argument
29        bail!("require.context() only supports 1-3 arguments (mode is not supported)");
30    }
31
32    let Some(dir) = args[0].as_str().map(|s| s.into()) else {
33        bail!("require.context(dir, ...) requires dir to be a constant string");
34    };
35
36    let include_subdirs = if let Some(include_subdirs) = args.get(1) {
37        if let Some(include_subdirs) = include_subdirs.as_bool() {
38            include_subdirs
39        } else {
40            bail!(
41                "require.context(..., includeSubdirs, ...) requires includeSubdirs to be a \
42                 constant boolean",
43            );
44        }
45    } else {
46        true
47    };
48
49    let filter = if let Some(filter) = args.get(2) {
50        if let JsValue::Constant(ConstantValue::Regex(box (pattern, flags))) = filter {
51            EsRegex::new(pattern, flags)?
52        } else {
53            bail!("require.context(..., ..., filter) requires filter to be a regex");
54        }
55    } else {
56        // https://webpack.js.org/api/module-methods/#requirecontext
57        // > optional, default /^\.\/.*$/, any file
58        static DEFAULT_REGEX: LazyLock<EsRegex> =
59            LazyLock::new(|| EsRegex::new(r"^\./.*$", "").unwrap());
60
61        DEFAULT_REGEX.clone()
62    };
63
64    Ok(RequireContextOptions {
65        dir,
66        include_subdirs,
67        filter,
68    })
69}
70
71#[derive(Debug, Clone, Eq, PartialEq)]
72pub struct RequireContextValue(pub(crate) FxIndexMap<RcStr, RcStr>);
73
74impl RequireContextValue {
75    pub async fn from_context_map(map: Vc<RequireContextMap>) -> Result<Self> {
76        let mut context_map = FxIndexMap::default();
77
78        for (key, entry) in map.await?.iter() {
79            context_map.insert(key.clone(), entry.origin_relative.clone());
80        }
81
82        Ok(RequireContextValue(context_map))
83    }
84}
85
86impl Hash for RequireContextValue {
87    fn hash<H: Hasher>(&self, state: &mut H) {
88        self.0.len().hash(state);
89        for (i, (k, v)) in self.0.iter().enumerate() {
90            i.hash(state);
91            k.hash(state);
92            v.hash(state);
93        }
94    }
95}