Skip to main content

turbopack_ecmascript/references/
util.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use bincode::{Decode, Encode};
4use swc_core::{
5    common::{
6        Span,
7        errors::{DiagnosticId, HANDLER},
8    },
9    ecma::ast::Expr,
10    quote,
11};
12use turbo_rcstr::{RcStr, rcstr};
13use turbo_tasks::{NonLocalValue, ResolvedVc, Vc, trace::TraceRawVcs, turbofmt};
14use turbo_tasks_fs::FileSystemPath;
15use turbopack_core::{
16    self,
17    chunk::ChunkingType,
18    issue::{Issue, IssueExt, IssueSeverity, IssueSource, IssueStage, StyledString},
19    resolve::{ModuleResolveResult, parse::Request, pattern::Pattern},
20};
21
22use crate::errors;
23
24/// Creates a IIFE expression that throws a "Cannot find module" error for the
25/// given request string
26pub fn throw_module_not_found_expr(request: &str) -> Expr {
27    let message = format!("Cannot find module '{request}'");
28    quote!(
29        "(() => { const e = new Error($message); e.code = 'MODULE_NOT_FOUND'; throw e; })()"
30            as Expr,
31        message: Expr = message.into()
32    )
33}
34
35/// Creates a Promise that rejects with a "Cannot find module" error for the
36/// given request string. Use this for async contexts (dynamic imports).
37pub fn throw_module_not_found_expr_async(request: &str) -> Expr {
38    let message = format!("Cannot find module '{request}'");
39    quote!(
40        "Promise.resolve().then(() => { const e = new Error($message); e.code = 'MODULE_NOT_FOUND'; throw e; })"
41            as Expr,
42        message: Expr = message.into()
43    )
44}
45
46/// Creates a IIFE expression that throws a "Cannot find module" error for the
47/// given request string
48pub fn throw_module_not_found_error_expr(request: &str, message: &str) -> Expr {
49    let message = format!("Cannot find module '{request}': {message}");
50    quote!(
51        "(() => { const e = new Error($message); e.code = 'MODULE_NOT_FOUND'; throw e; })()"
52            as Expr,
53        message: Expr = message.into()
54    )
55}
56
57#[turbo_tasks::function]
58pub async fn request_to_string(request: Vc<Request>) -> Result<Vc<RcStr>> {
59    Ok(Vc::cell(
60        request
61            .await?
62            .request()
63            // TODO: Handle Request::Dynamic, Request::Alternatives
64            .unwrap_or(rcstr!("unknown")),
65    ))
66}
67
68/// If a pattern resolves to more than 10000 results, it's likely a mistake so issue a warning.
69const TOO_MANY_MATCHES_LIMIT: usize = 10000;
70
71pub async fn check_and_emit_too_many_matches_warning(
72    result: Vc<ModuleResolveResult>,
73    issue_source: IssueSource,
74    context_dir: FileSystemPath,
75    pattern: ResolvedVc<Pattern>,
76) -> Result<()> {
77    let num_matches = result.await?.primary.len();
78    if num_matches > TOO_MANY_MATCHES_LIMIT {
79        TooManyMatchesWarning {
80            source: issue_source,
81            context_dir,
82            num_matches,
83            pattern,
84        }
85        .resolved_cell()
86        .emit();
87    }
88    Ok(())
89}
90
91#[turbo_tasks::value(shared)]
92struct TooManyMatchesWarning {
93    source: IssueSource,
94    context_dir: FileSystemPath,
95    num_matches: usize,
96    pattern: ResolvedVc<Pattern>,
97}
98
99#[async_trait]
100#[turbo_tasks::value_impl]
101impl Issue for TooManyMatchesWarning {
102    async fn title(&self) -> Result<StyledString> {
103        Ok(StyledString::Text(
104            turbofmt!(
105                "The file pattern {} matches {} files in {}",
106                self.pattern,
107                self.num_matches,
108                self.context_dir
109            )
110            .await?,
111        ))
112    }
113
114    async fn description(&self) -> Result<Option<StyledString>> {
115        Ok(Some(StyledString::Text(rcstr!(
116            "Overly broad patterns can lead to build performance issues and over bundling."
117        ))))
118    }
119
120    async fn file_path(&self) -> Result<FileSystemPath> {
121        self.source.file_path().await
122    }
123
124    fn stage(&self) -> IssueStage {
125        IssueStage::Resolve
126    }
127
128    fn severity(&self) -> IssueSeverity {
129        IssueSeverity::Warning
130    }
131
132    fn source(&self) -> Option<IssueSource> {
133        Some(self.source)
134    }
135}
136
137#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Encode, Decode, TraceRawVcs, NonLocalValue)]
138pub enum SpecifiedChunkingType {
139    Parallel,
140    Shared,
141    None,
142}
143
144impl SpecifiedChunkingType {
145    pub fn as_chunking_type(&self, inherit_async: bool, hoisted: bool) -> Option<ChunkingType> {
146        match self {
147            SpecifiedChunkingType::Parallel => Some(ChunkingType::Parallel {
148                inherit_async,
149                hoisted,
150            }),
151            SpecifiedChunkingType::Shared => Some(ChunkingType::Shared {
152                inherit_async,
153                merge_tag: None,
154            }),
155            SpecifiedChunkingType::None => None,
156        }
157    }
158}
159
160pub fn parse_chunking_type_annotation(
161    span: Span,
162    chunking_type_annotation: &str,
163) -> Option<SpecifiedChunkingType> {
164    match chunking_type_annotation {
165        "parallel" => Some(SpecifiedChunkingType::Parallel),
166        "shared" => Some(SpecifiedChunkingType::Shared),
167        "none" => Some(SpecifiedChunkingType::None),
168        _ => {
169            HANDLER.with(|handler| {
170                handler.span_err_with_code(
171                    span,
172                    &format!(
173                        "Unknown specified chunking-type: \"{chunking_type_annotation}\", \
174                         expected \"parallel\", \"shared\" or \"none\""
175                    ),
176                    DiagnosticId::Error(
177                        errors::failed_to_analyze::ecmascript::CHUNKING_TYPE.into(),
178                    ),
179                );
180            });
181            None
182        }
183    }
184}