Skip to main content

turbopack_ecmascript/references/
raw.rs

1use anyhow::{Result, bail};
2use tracing::Instrument;
3use turbo_rcstr::{RcStr, rcstr};
4use turbo_tasks::{ResolvedVc, ValueToString, Vc};
5use turbo_tasks_fs::FileSystemPath;
6use turbopack_core::{
7    chunk::{ChunkingType, TracedMode},
8    file_source::FileSource,
9    issue::IssueSource,
10    raw_module::RawModule,
11    reference::{DynamicTraceReference, ModuleReference},
12    resolve::{
13        ModuleResolveResult, RequestKey, ResolveResult,
14        pattern::{Pattern, PatternMatch, read_matches},
15        resolve_raw,
16    },
17};
18
19use crate::references::util::check_and_emit_too_many_matches_warning;
20
21/// Look for statically named files in the project root without expanding directories. The
22/// package-local reference is resolved separately with its entire original pattern.
23pub async fn resolve_static_files(
24    context_dir: &Option<FileSystemPath>,
25    path: ResolvedVc<Pattern>,
26    collect_affecting_sources: bool,
27) -> Result<Option<Vc<ResolveResult>>> {
28    let Some(context_dir) = context_dir else {
29        return Ok(None);
30    };
31    let Some(static_pattern) = path.await?.filter_static() else {
32        return Ok(None);
33    };
34    Ok(Some(resolve_raw(
35        context_dir.clone(),
36        Pattern::new(static_pattern),
37        collect_affecting_sources,
38        /* force_in_lookup_dir */ true,
39    )))
40}
41
42#[turbo_tasks::value]
43#[derive(Hash, Debug, ValueToString)]
44#[value_to_string("raw asset {path}")]
45pub struct FileSourceReference {
46    static_files_only_context_dir: Option<FileSystemPath>,
47    fallback_context_dir: FileSystemPath,
48    path: ResolvedVc<Pattern>,
49    collect_affecting_sources: bool,
50    issue_source: IssueSource,
51    /// The dynamic function whose access triggered this reference (e.g.
52    /// `fs.readFileSync`), used to name the call in diagnostics.
53    origin_fn_name: RcStr,
54}
55
56#[turbo_tasks::value_impl]
57impl FileSourceReference {
58    #[turbo_tasks::function]
59    pub fn new(
60        static_files_only_context_dir: Option<FileSystemPath>,
61        fallback_context_dir: FileSystemPath,
62        path: ResolvedVc<Pattern>,
63        collect_affecting_sources: bool,
64        issue_source: IssueSource,
65        origin_fn_name: RcStr,
66    ) -> Vc<Self> {
67        Self::cell(FileSourceReference {
68            static_files_only_context_dir,
69            fallback_context_dir,
70            path,
71            collect_affecting_sources,
72            issue_source,
73            origin_fn_name,
74        })
75    }
76}
77
78#[turbo_tasks::value_impl]
79impl ModuleReference for FileSourceReference {
80    #[turbo_tasks::function]
81    async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
82        let span = tracing::info_span!(
83            "trace file",
84            pattern = display(self.path.to_string().await?)
85        );
86        async {
87            let fallback = resolve_raw(
88                self.fallback_context_dir.clone(),
89                *self.path,
90                self.collect_affecting_sources,
91                /* force_in_lookup_dir */ false,
92            )
93            .as_raw_module_result();
94            let result = if let Some(static_files) = resolve_static_files(
95                &self.static_files_only_context_dir,
96                self.path,
97                self.collect_affecting_sources,
98            )
99            .await?
100            {
101                ModuleResolveResult::concat(vec![static_files.as_raw_module_result(), fallback])
102            } else {
103                fallback
104            };
105            check_and_emit_too_many_matches_warning(
106                result,
107                self.issue_source,
108                self.fallback_context_dir.clone(),
109                self.path,
110            )
111            .await?;
112
113            Ok(result)
114        }
115        .instrument(span)
116        .await
117    }
118
119    fn chunking_type(&self) -> Option<ChunkingType> {
120        Some(ChunkingType::Traced {
121            mode: TracedMode::Entry,
122        })
123    }
124
125    fn source(&self) -> Option<IssueSource> {
126        Some(self.issue_source)
127    }
128}
129
130#[turbo_tasks::value_impl]
131impl DynamicTraceReference for FileSourceReference {
132    fn origin_fn_name(&self) -> RcStr {
133        self.origin_fn_name.clone()
134    }
135}
136
137#[turbo_tasks::value]
138#[derive(Hash, Debug, ValueToString)]
139#[value_to_string("directory assets {path}")]
140pub struct DirAssetReference {
141    static_files_only_context_dir: Option<FileSystemPath>,
142    fallback_context_dir: FileSystemPath,
143    path: ResolvedVc<Pattern>,
144    issue_source: IssueSource,
145    /// The dynamic function whose access triggered this reference (e.g.
146    /// `fs.readdir`), used to name the call in diagnostics.
147    origin_fn_name: RcStr,
148}
149
150#[turbo_tasks::value_impl]
151impl DirAssetReference {
152    #[turbo_tasks::function]
153    pub fn new(
154        static_files_only_context_dir: Option<FileSystemPath>,
155        fallback_context_dir: FileSystemPath,
156        path: ResolvedVc<Pattern>,
157        issue_source: IssueSource,
158        origin_fn_name: RcStr,
159    ) -> Vc<Self> {
160        Self::cell(DirAssetReference {
161            static_files_only_context_dir,
162            fallback_context_dir,
163            path,
164            issue_source,
165            origin_fn_name,
166        })
167    }
168}
169
170async fn resolve_reference_from_dir(
171    context_dir: FileSystemPath,
172    path: Vc<Pattern>,
173) -> Result<Vc<ModuleResolveResult>> {
174    let path_ref = path.await?;
175    let (abs_path, rel_path) = path_ref.split_could_match("/ROOT/");
176    if abs_path.is_none() && rel_path.is_none() {
177        return Ok(*ModuleResolveResult::unresolvable());
178    }
179
180    let abs_matches = if let Some(abs_path) = &abs_path {
181        Some(
182            read_matches(
183                context_dir.root().owned().await?,
184                rcstr!("/ROOT/"),
185                true,
186                Pattern::new(abs_path.or_any_nested_file()),
187            )
188            .await?,
189        )
190    } else {
191        None
192    };
193    let rel_matches = if let Some(rel_path) = &rel_path {
194        Some(
195            read_matches(
196                context_dir,
197                rcstr!(""),
198                true,
199                Pattern::new(rel_path.or_any_nested_file()),
200            )
201            .await?,
202        )
203    } else {
204        None
205    };
206
207    let matches = abs_matches
208        .iter()
209        .flatten()
210        .chain(rel_matches.iter().flatten());
211
212    let mut affecting_sources = Vec::new();
213    let mut results = Vec::new();
214    for pat_match in matches {
215        match pat_match {
216            PatternMatch::File(matched_path, file) => {
217                let realpath = file.realpath_with_links().await?;
218                for symlink in &realpath.symlinks {
219                    affecting_sources.push(ResolvedVc::upcast(
220                        FileSource::new(symlink.clone()).to_resolved().await?,
221                    ));
222                }
223                let path: FileSystemPath = match &realpath.path_result {
224                    Ok(path) => path.clone(),
225                    Err(error) => bail!(error.clone()),
226                };
227                results.push((
228                    RequestKey::new(matched_path.clone()),
229                    ResolvedVc::upcast(
230                        RawModule::new(Vc::upcast(FileSource::new(path.clone())))
231                            .to_resolved()
232                            .await?,
233                    ),
234                ));
235            }
236            PatternMatch::Directory(..) => {}
237        }
238    }
239    Ok(*ModuleResolveResult::modules_with_affecting_sources(
240        results,
241        affecting_sources,
242    ))
243}
244
245#[turbo_tasks::value_impl]
246impl ModuleReference for DirAssetReference {
247    #[turbo_tasks::function]
248    async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
249        let span = tracing::info_span!(
250            "trace directory",
251            pattern = display(self.path.to_string().await?)
252        );
253        async {
254            let fallback =
255                resolve_reference_from_dir(self.fallback_context_dir.clone(), *self.path).await?;
256            let result = if let Some(static_files) =
257                resolve_static_files(&self.static_files_only_context_dir, self.path, true).await?
258            {
259                ModuleResolveResult::concat(vec![static_files.as_raw_module_result(), fallback])
260            } else {
261                fallback
262            };
263            check_and_emit_too_many_matches_warning(
264                result,
265                self.issue_source,
266                self.fallback_context_dir.clone(),
267                self.path,
268            )
269            .await?;
270            Ok(result)
271        }
272        .instrument(span)
273        .await
274    }
275
276    fn chunking_type(&self) -> Option<ChunkingType> {
277        Some(ChunkingType::Traced {
278            mode: TracedMode::Entry,
279        })
280    }
281
282    fn source(&self) -> Option<IssueSource> {
283        Some(self.issue_source)
284    }
285}
286
287#[turbo_tasks::value_impl]
288impl DynamicTraceReference for DirAssetReference {
289    fn origin_fn_name(&self) -> RcStr {
290        self.origin_fn_name.clone()
291    }
292}