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::{ChunkableModuleReference, ChunkingType, ChunkingTypeOption},
8    file_source::FileSource,
9    issue::{
10        Issue, IssueExt, IssueSeverity, IssueSource, IssueStage, OptionIssueSource,
11        OptionStyledString, StyledString,
12    },
13    raw_module::RawModule,
14    reference::ModuleReference,
15    resolve::{
16        ModuleResolveResult, RequestKey,
17        pattern::{Pattern, PatternMatch, read_matches},
18        resolve_raw,
19    },
20};
21#[turbo_tasks::value]
22#[derive(Hash, Debug)]
23pub struct FileSourceReference {
24    context_dir: FileSystemPath,
25    path: ResolvedVc<Pattern>,
26    collect_affecting_sources: bool,
27    issue_source: IssueSource,
28}
29
30#[turbo_tasks::value_impl]
31impl FileSourceReference {
32    #[turbo_tasks::function]
33    pub fn new(
34        context_dir: FileSystemPath,
35        path: ResolvedVc<Pattern>,
36        collect_affecting_sources: bool,
37        issue_source: IssueSource,
38    ) -> Vc<Self> {
39        Self::cell(FileSourceReference {
40            context_dir,
41            path,
42            collect_affecting_sources,
43            issue_source,
44        })
45    }
46}
47
48#[turbo_tasks::value_impl]
49impl ModuleReference for FileSourceReference {
50    #[turbo_tasks::function]
51    async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
52        let span = tracing::info_span!(
53            "trace file",
54            pattern = display(self.path.to_string().await?)
55        );
56        async {
57            let result = resolve_raw(
58                self.context_dir.clone(),
59                *self.path,
60                self.collect_affecting_sources,
61                /* force_in_lookup_dir */ false,
62            )
63            .as_raw_module_result()
64            .resolve()
65            .await?;
66            check_and_emit_too_many_matches_warning(
67                result,
68                self.issue_source,
69                self.context_dir.clone(),
70                self.path,
71            )
72            .await?;
73
74            Ok(result)
75        }
76        .instrument(span)
77        .await
78    }
79}
80#[turbo_tasks::value_impl]
81impl ChunkableModuleReference for FileSourceReference {
82    #[turbo_tasks::function]
83    fn chunking_type(&self) -> Vc<ChunkingTypeOption> {
84        Vc::cell(Some(ChunkingType::Traced))
85    }
86}
87
88#[turbo_tasks::value_impl]
89impl ValueToString for FileSourceReference {
90    #[turbo_tasks::function]
91    async fn to_string(&self) -> Result<Vc<RcStr>> {
92        Ok(Vc::cell(
93            format!("raw asset {}", self.path.to_string().await?,).into(),
94        ))
95    }
96}
97
98#[turbo_tasks::value]
99#[derive(Hash, Debug)]
100pub struct DirAssetReference {
101    context_dir: FileSystemPath,
102    path: ResolvedVc<Pattern>,
103    issue_source: IssueSource,
104}
105
106#[turbo_tasks::value_impl]
107impl DirAssetReference {
108    #[turbo_tasks::function]
109    pub fn new(
110        context_dir: FileSystemPath,
111        path: ResolvedVc<Pattern>,
112        issue_source: IssueSource,
113    ) -> Vc<Self> {
114        Self::cell(DirAssetReference {
115            context_dir,
116            path,
117            issue_source,
118        })
119    }
120}
121
122async fn resolve_reference_from_dir(
123    context_dir: FileSystemPath,
124    path: Vc<Pattern>,
125) -> Result<Vc<ModuleResolveResult>> {
126    let path_ref = path.await?;
127    let (abs_path, rel_path) = path_ref.split_could_match("/ROOT/");
128    if abs_path.is_none() && rel_path.is_none() {
129        return Ok(*ModuleResolveResult::unresolvable());
130    }
131
132    let abs_matches = if let Some(abs_path) = &abs_path {
133        Some(
134            read_matches(
135                context_dir.root().owned().await?,
136                rcstr!("/ROOT/"),
137                true,
138                Pattern::new(abs_path.or_any_nested_file()),
139            )
140            .await?,
141        )
142    } else {
143        None
144    };
145    let rel_matches = if let Some(rel_path) = &rel_path {
146        Some(
147            read_matches(
148                context_dir,
149                rcstr!(""),
150                true,
151                Pattern::new(rel_path.or_any_nested_file()),
152            )
153            .await?,
154        )
155    } else {
156        None
157    };
158
159    let matches = abs_matches
160        .into_iter()
161        .flatten()
162        .chain(rel_matches.into_iter().flatten());
163
164    let mut affecting_sources = Vec::new();
165    let mut results = Vec::new();
166    for pat_match in matches {
167        match pat_match {
168            PatternMatch::File(matched_path, file) => {
169                let realpath = file.realpath_with_links().await?;
170                for symlink in &realpath.symlinks {
171                    affecting_sources.push(ResolvedVc::upcast(
172                        FileSource::new(symlink.clone()).to_resolved().await?,
173                    ));
174                }
175                let path: FileSystemPath = match &realpath.path_result {
176                    Ok(path) => path.clone(),
177                    Err(e) => bail!(e.as_error_message(file, &realpath)),
178                };
179                results.push((
180                    RequestKey::new(matched_path.clone()),
181                    ResolvedVc::upcast(
182                        RawModule::new(Vc::upcast(FileSource::new(path)))
183                            .to_resolved()
184                            .await?,
185                    ),
186                ));
187            }
188            PatternMatch::Directory(..) => {}
189        }
190    }
191    Ok(*ModuleResolveResult::modules_with_affecting_sources(
192        results,
193        affecting_sources,
194    ))
195}
196
197#[turbo_tasks::value_impl]
198impl ModuleReference for DirAssetReference {
199    #[turbo_tasks::function]
200    async fn resolve_reference(&self) -> Result<Vc<ModuleResolveResult>> {
201        let span = tracing::info_span!(
202            "trace directory",
203            pattern = display(self.path.to_string().await?)
204        );
205        async {
206            let result = resolve_reference_from_dir(self.context_dir.clone(), *self.path).await?;
207            check_and_emit_too_many_matches_warning(
208                result,
209                self.issue_source,
210                self.context_dir.clone(),
211                self.path,
212            )
213            .await?;
214            Ok(result)
215        }
216        .instrument(span)
217        .await
218    }
219}
220
221#[turbo_tasks::value_impl]
222impl ChunkableModuleReference for DirAssetReference {
223    #[turbo_tasks::function]
224    fn chunking_type(&self) -> Vc<ChunkingTypeOption> {
225        Vc::cell(Some(ChunkingType::Traced))
226    }
227}
228
229#[turbo_tasks::value_impl]
230impl ValueToString for DirAssetReference {
231    #[turbo_tasks::function]
232    async fn to_string(&self) -> Result<Vc<RcStr>> {
233        Ok(Vc::cell(
234            format!("directory assets {}", self.path.to_string().await?,).into(),
235        ))
236    }
237}
238
239/// If a pattern resolves to more than 10000 results, it's likely a mistake so issue a warning.
240const TOO_MANY_MATCHES_LIMIT: usize = 10000;
241
242async fn check_and_emit_too_many_matches_warning(
243    result: Vc<ModuleResolveResult>,
244    issue_source: IssueSource,
245    context_dir: FileSystemPath,
246    pattern: ResolvedVc<Pattern>,
247) -> Result<()> {
248    let num_matches = result.await?.primary.len();
249    if num_matches > TOO_MANY_MATCHES_LIMIT {
250        TooManyMatchesWarning {
251            source: issue_source,
252            context_dir,
253            num_matches,
254            pattern,
255        }
256        .resolved_cell()
257        .emit();
258    }
259    Ok(())
260}
261
262#[turbo_tasks::value(shared)]
263struct TooManyMatchesWarning {
264    source: IssueSource,
265    context_dir: FileSystemPath,
266    num_matches: usize,
267    pattern: ResolvedVc<Pattern>,
268}
269
270#[turbo_tasks::value_impl]
271impl Issue for TooManyMatchesWarning {
272    #[turbo_tasks::function]
273    async fn title(&self) -> Result<Vc<StyledString>> {
274        Ok(StyledString::Text(
275            format!(
276                "The file pattern {pattern} matches {num_matches} files in {context_dir}",
277                pattern = self.pattern.to_string().await?,
278                context_dir = self.context_dir.value_to_string().await?,
279                num_matches = self.num_matches
280            )
281            .into(),
282        )
283        .cell())
284    }
285
286    #[turbo_tasks::function]
287    fn description(&self) -> Vc<OptionStyledString> {
288        Vc::cell(Some(
289            StyledString::Text(rcstr!(
290                "Overly broad patterns can lead to build performance issues and over bundling."
291            ))
292            .resolved_cell(),
293        ))
294    }
295
296    #[turbo_tasks::function]
297    async fn file_path(&self) -> Vc<FileSystemPath> {
298        self.source.file_path()
299    }
300
301    #[turbo_tasks::function]
302    fn stage(&self) -> Vc<IssueStage> {
303        IssueStage::Resolve.cell()
304    }
305
306    fn severity(&self) -> IssueSeverity {
307        IssueSeverity::Warning
308    }
309
310    #[turbo_tasks::function]
311    fn source(&self) -> Vc<OptionIssueSource> {
312        Vc::cell(Some(self.source))
313    }
314}