turbopack_ecmascript/
parse.rs

1use std::{future::Future, sync::Arc};
2
3use anyhow::{Context, Result, anyhow};
4use rustc_hash::FxHashSet;
5use swc_core::{
6    base::SwcComments,
7    common::{
8        BytePos, FileName, GLOBALS, Globals, LineCol, Mark, SyntaxContext,
9        errors::{HANDLER, Handler},
10        input::StringInput,
11        source_map::SourceMapGenConfig,
12        util::take::Take,
13    },
14    ecma::{
15        ast::{EsVersion, Id, ObjectPatProp, Pat, Program, VarDecl},
16        lints::{config::LintConfig, rules::LintParams},
17        parser::{EsSyntax, Parser, Syntax, TsSyntax, lexer::Lexer},
18        transforms::base::{
19            helpers::{HELPERS, Helpers},
20            resolver,
21        },
22        visit::{Visit, VisitMutWith, VisitWith, noop_visit_type},
23    },
24};
25use tracing::{Instrument, Level, instrument};
26use turbo_rcstr::RcStr;
27use turbo_tasks::{ResolvedVc, Value, ValueToString, Vc, util::WrapFuture};
28use turbo_tasks_fs::{FileContent, FileSystemPath, rope::Rope};
29use turbo_tasks_hash::hash_xxh3_hash64;
30use turbopack_core::{
31    SOURCE_URL_PROTOCOL,
32    asset::{Asset, AssetContent},
33    error::PrettyPrintError,
34    issue::{Issue, IssueExt, IssueSeverity, IssueStage, OptionStyledString, StyledString},
35    source::Source,
36    source_map::utils::add_default_ignore_list,
37};
38use turbopack_swc_utils::emitter::IssueEmitter;
39
40use super::EcmascriptModuleAssetType;
41use crate::{
42    EcmascriptInputTransform,
43    analyzer::{ImportMap, graph::EvalContext},
44    swc_comments::ImmutableComments,
45    transform::{EcmascriptInputTransforms, TransformContext},
46};
47
48#[turbo_tasks::value(shared, serialization = "none", eq = "manual")]
49#[allow(clippy::large_enum_variant)]
50pub enum ParseResult {
51    // Note: Ok must not contain any Vc as it's snapshot by failsafe_parse
52    Ok {
53        #[turbo_tasks(debug_ignore, trace_ignore)]
54        program: Program,
55        #[turbo_tasks(debug_ignore, trace_ignore)]
56        comments: Arc<ImmutableComments>,
57        #[turbo_tasks(debug_ignore, trace_ignore)]
58        eval_context: EvalContext,
59        #[turbo_tasks(debug_ignore, trace_ignore)]
60        globals: Arc<Globals>,
61        #[turbo_tasks(debug_ignore, trace_ignore)]
62        source_map: Arc<swc_core::common::SourceMap>,
63    },
64    Unparseable {
65        messages: Option<Vec<RcStr>>,
66    },
67    NotFound,
68}
69
70impl PartialEq for ParseResult {
71    fn eq(&self, other: &Self) -> bool {
72        match (self, other) {
73            (Self::Ok { .. }, Self::Ok { .. }) => false,
74            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
75        }
76    }
77}
78
79#[turbo_tasks::value_impl]
80impl ParseResult {
81    #[turbo_tasks::function]
82    pub fn empty() -> Vc<ParseResult> {
83        let globals = Globals::new();
84        let eval_context = GLOBALS.set(&globals, || EvalContext {
85            unresolved_mark: Mark::new(),
86            top_level_mark: Mark::new(),
87            imports: ImportMap::default(),
88            force_free_values: Default::default(),
89        });
90        ParseResult::Ok {
91            program: Program::Module(swc_core::ecma::ast::Module::dummy()),
92            comments: Default::default(),
93            eval_context,
94            globals: Arc::new(globals),
95            source_map: Default::default(),
96        }
97        .cell()
98    }
99}
100
101#[instrument(level = Level::INFO, skip_all)]
102pub fn generate_js_source_map(
103    files_map: Arc<swc_core::common::SourceMap>,
104    mappings: Vec<(BytePos, LineCol)>,
105    original_source_map: Option<&Rope>,
106    inline_sources_content: bool,
107) -> Result<Rope> {
108    let input_map = if let Some(original_source_map) = original_source_map {
109        Some(match sourcemap::decode(original_source_map.read())? {
110            sourcemap::DecodedMap::Regular(source_map) => source_map,
111            // swc only accepts flattened sourcemaps as input
112            sourcemap::DecodedMap::Index(source_map_index) => source_map_index.flatten()?,
113            _ => return Err(sourcemap::Error::IncompatibleSourceMap.into()),
114        })
115    } else {
116        None
117    };
118
119    let map = files_map.build_source_map(
120        &mappings,
121        None,
122        InlineSourcesContentConfig {
123            // If we are going to adjust the source map, we are going to throw the source contents
124            // of this source map away regardless.
125            //
126            // In other words, we don't need the content of `B` in source map chain of A -> B -> C.
127            // We only need the source content of `A`, and a way to map the content of `B` back to
128            // `A`, while constructing the final source map, `C`.
129            inline_sources_content: inline_sources_content && input_map.is_none(),
130        },
131    );
132
133    let mut map = match input_map {
134        Some(mut input_map) => {
135            input_map.adjust_mappings(&map);
136            input_map
137        }
138        None => map,
139    };
140    add_default_ignore_list(&mut map);
141
142    let mut result = vec![];
143    map.to_writer(&mut result)?;
144    Ok(Rope::from(result))
145}
146
147/// A config to generate a source map which includes the source content of every
148/// source file. SWC doesn't inline sources content by default when generating a
149/// sourcemap, so we need to provide a custom config to do it.
150pub struct InlineSourcesContentConfig {
151    inline_sources_content: bool,
152}
153
154impl SourceMapGenConfig for InlineSourcesContentConfig {
155    fn file_name_to_source(&self, f: &FileName) -> String {
156        match f {
157            FileName::Custom(s) => {
158                format!("{SOURCE_URL_PROTOCOL}///{s}")
159            }
160            _ => f.to_string(),
161        }
162    }
163
164    fn inline_sources_content(&self, _f: &FileName) -> bool {
165        self.inline_sources_content
166    }
167}
168
169#[turbo_tasks::function]
170pub async fn parse(
171    source: ResolvedVc<Box<dyn Source>>,
172    ty: Value<EcmascriptModuleAssetType>,
173    transforms: Vc<EcmascriptInputTransforms>,
174) -> Result<Vc<ParseResult>> {
175    let name = source.ident().to_string().await?.to_string();
176    let span = tracing::info_span!("parse ecmascript", name = name, ty = display(&*ty));
177
178    match parse_internal(source, ty, transforms)
179        .instrument(span)
180        .await
181    {
182        Ok(result) => Ok(result),
183        Err(error) => Err(error.context(format!(
184            "failed to parse {}",
185            source.ident().to_string().await?
186        ))),
187    }
188}
189
190async fn parse_internal(
191    source: ResolvedVc<Box<dyn Source>>,
192    ty: Value<EcmascriptModuleAssetType>,
193    transforms: Vc<EcmascriptInputTransforms>,
194) -> Result<Vc<ParseResult>> {
195    let content = source.content();
196    let fs_path_vc = source.ident().path();
197    let fs_path = &*fs_path_vc.await?;
198    let ident = &*source.ident().to_string().await?;
199    let file_path_hash = hash_xxh3_hash64(&*source.ident().to_string().await?) as u128;
200    let ty = ty.into_value();
201    let content = match content.await {
202        Ok(content) => content,
203        Err(error) => {
204            let error: RcStr = PrettyPrintError(&error).to_string().into();
205            ReadSourceIssue {
206                source,
207                error: error.clone(),
208            }
209            .resolved_cell()
210            .emit();
211
212            return Ok(ParseResult::Unparseable {
213                messages: Some(vec![error]),
214            }
215            .cell());
216        }
217    };
218    Ok(match &*content {
219        AssetContent::File(file) => match &*file.await? {
220            FileContent::NotFound => ParseResult::NotFound.cell(),
221            FileContent::Content(file) => match file.content().to_str() {
222                Ok(string) => {
223                    let transforms = &*transforms.await?;
224                    match parse_file_content(
225                        string.into_owned(),
226                        fs_path_vc,
227                        fs_path,
228                        ident,
229                        source.ident().query().owned().await?,
230                        file_path_hash,
231                        source,
232                        ty,
233                        transforms,
234                    )
235                    .await
236                    {
237                        Ok(result) => result,
238                        Err(e) => {
239                            return Err(e).context(anyhow!(
240                                "Transforming and/or parsing of {} failed",
241                                source.ident().to_string().await?
242                            ));
243                        }
244                    }
245                }
246                Err(error) => {
247                    let error: RcStr = PrettyPrintError(&error).to_string().into();
248                    ReadSourceIssue {
249                        source,
250                        error: error.clone(),
251                    }
252                    .resolved_cell()
253                    .emit();
254                    ParseResult::Unparseable {
255                        messages: Some(vec![error]),
256                    }
257                    .cell()
258                }
259            },
260        },
261        AssetContent::Redirect { .. } => ParseResult::Unparseable { messages: None }.cell(),
262    })
263}
264
265async fn parse_file_content(
266    string: String,
267    fs_path_vc: Vc<FileSystemPath>,
268    fs_path: &FileSystemPath,
269    ident: &str,
270    query: RcStr,
271    file_path_hash: u128,
272    source: ResolvedVc<Box<dyn Source>>,
273    ty: EcmascriptModuleAssetType,
274    transforms: &[EcmascriptInputTransform],
275) -> Result<Vc<ParseResult>> {
276    let source_map: Arc<swc_core::common::SourceMap> = Default::default();
277    let (emitter, collector) = IssueEmitter::new(
278        source,
279        source_map.clone(),
280        Some("Ecmascript file had an error".into()),
281    );
282    let handler = Handler::with_emitter(true, false, Box::new(emitter));
283
284    let (emitter, collector_parse) = IssueEmitter::new(
285        source,
286        source_map.clone(),
287        Some("Parsing ecmascript source code failed".into()),
288    );
289    let parser_handler = Handler::with_emitter(true, false, Box::new(emitter));
290    let globals = Arc::new(Globals::new());
291    let globals_ref = &globals;
292
293    let mut result = WrapFuture::new(
294        async {
295            let file_name = FileName::Custom(ident.to_string());
296            let fm = source_map.new_source_file(file_name.clone().into(), string);
297
298            let comments = SwcComments::default();
299
300            let mut parsed_program = {
301                let lexer = Lexer::new(
302                    match ty {
303                        EcmascriptModuleAssetType::Ecmascript => Syntax::Es(EsSyntax {
304                            jsx: true,
305                            fn_bind: true,
306                            decorators: true,
307                            decorators_before_export: true,
308                            export_default_from: true,
309                            import_attributes: true,
310                            allow_super_outside_method: true,
311                            allow_return_outside_function: true,
312                            auto_accessors: true,
313                            explicit_resource_management: true,
314                        }),
315                        EcmascriptModuleAssetType::Typescript { tsx, .. } => {
316                            Syntax::Typescript(TsSyntax {
317                                decorators: true,
318                                dts: false,
319                                no_early_errors: true,
320                                tsx,
321                                disallow_ambiguous_jsx_like: false,
322                            })
323                        }
324                        EcmascriptModuleAssetType::TypescriptDeclaration => {
325                            Syntax::Typescript(TsSyntax {
326                                decorators: true,
327                                dts: true,
328                                no_early_errors: true,
329                                tsx: false,
330                                disallow_ambiguous_jsx_like: false,
331                            })
332                        }
333                    },
334                    EsVersion::latest(),
335                    StringInput::from(&*fm),
336                    Some(&comments),
337                );
338
339                let mut parser = Parser::new_from(lexer);
340                let span = tracing::trace_span!("swc_parse").entered();
341                let program_result = parser.parse_program();
342                drop(span);
343
344                let mut has_errors = vec![];
345                for e in parser.take_errors() {
346                    let mut e = e.into_diagnostic(&parser_handler);
347                    has_errors.extend(e.message.iter().map(|m| m.0.as_str().into()));
348                    e.emit();
349                }
350
351                if !has_errors.is_empty() {
352                    return Ok(ParseResult::Unparseable {
353                        messages: Some(has_errors),
354                    });
355                }
356
357                match program_result {
358                    Ok(parsed_program) => parsed_program,
359                    Err(e) => {
360                        let mut e = e.into_diagnostic(&parser_handler);
361                        let messages = e.message.iter().map(|m| m.0.as_str().into()).collect();
362
363                        e.emit();
364
365                        return Ok(ParseResult::Unparseable {
366                            messages: Some(messages),
367                        });
368                    }
369                }
370            };
371
372            let unresolved_mark = Mark::new();
373            let top_level_mark = Mark::new();
374
375            let is_typescript = matches!(
376                ty,
377                EcmascriptModuleAssetType::Typescript { .. }
378                    | EcmascriptModuleAssetType::TypescriptDeclaration
379            );
380            let span = tracing::trace_span!("swc_resolver").entered();
381
382            parsed_program.visit_mut_with(&mut resolver(
383                unresolved_mark,
384                top_level_mark,
385                is_typescript,
386            ));
387            drop(span);
388
389            let span = tracing::trace_span!("swc_lint").entered();
390
391            let lint_config = LintConfig::default();
392            let rules = swc_core::ecma::lints::rules::all(LintParams {
393                program: &parsed_program,
394                lint_config: &lint_config,
395                unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
396                top_level_ctxt: SyntaxContext::empty().apply_mark(top_level_mark),
397                es_version: EsVersion::latest(),
398                source_map: source_map.clone(),
399            });
400
401            parsed_program.mutate(swc_core::ecma::lints::rules::lint_pass(rules));
402            drop(span);
403
404            parsed_program.mutate(swc_core::ecma::transforms::proposal::explicit_resource_management::explicit_resource_management());
405
406            let var_with_ts_declare = if is_typescript {
407                VarDeclWithTsDeclareCollector::collect(&parsed_program)
408            } else {
409                FxHashSet::default()
410            };
411
412            let transform_context = TransformContext {
413                comments: &comments,
414                source_map: &source_map,
415                top_level_mark,
416                unresolved_mark,
417                file_path_str: &fs_path.path,
418                file_name_str: fs_path.file_name(),
419                file_name_hash: file_path_hash,
420                query_str: query,
421                file_path: fs_path_vc.to_resolved().await?,
422            };
423            let span = tracing::trace_span!("transforms");
424            async {
425                for transform in transforms.iter() {
426                    transform
427                        .apply(&mut parsed_program, &transform_context)
428                        .await?;
429                }
430                anyhow::Ok(())
431            }
432                .instrument(span)
433                .await?;
434
435            if parser_handler.has_errors() {
436                let messages = if let Some(error) = collector_parse.last_emitted_issue() {
437                    // The emitter created in here only uses StyledString::Text
438                    if let StyledString::Text(xx) = &*error.await?.message.await? {
439                        Some(vec![xx.clone()])
440                    } else {
441                        None
442                    }
443                } else {
444                    None
445                };
446                let messages =
447                    Some(messages.unwrap_or_else(|| vec![String::clone(&fm.src).into()]));
448                return Ok(ParseResult::Unparseable { messages });
449            }
450
451            parsed_program.visit_mut_with(
452                &mut swc_core::ecma::transforms::base::helpers::inject_helpers(unresolved_mark),
453            );
454
455            let eval_context = EvalContext::new(
456                &parsed_program,
457                unresolved_mark,
458                top_level_mark,
459                Arc::new(var_with_ts_declare),
460                Some(&comments),
461                Some(source),
462            );
463
464            Ok::<ParseResult, anyhow::Error>(ParseResult::Ok {
465                program: parsed_program,
466                comments: Arc::new(ImmutableComments::new(comments)),
467                eval_context,
468                // Temporary globals as the current one can't be moved yet, since they are
469                // borrowed
470                globals: Arc::new(Globals::new()),
471                source_map,
472            })
473        },
474        |f, cx| {
475            GLOBALS.set(globals_ref, || {
476                HANDLER.set(&handler, || HELPERS.set(&Helpers::new(true), || f.poll(cx)))
477            })
478        },
479    )
480        .await?;
481    if let ParseResult::Ok {
482        globals: ref mut g, ..
483    } = result
484    {
485        // Assign the correct globals
486        *g = globals;
487    }
488    collector.emit().await?;
489    collector_parse.emit().await?;
490    Ok(result.cell())
491}
492
493#[turbo_tasks::value]
494struct ReadSourceIssue {
495    source: ResolvedVc<Box<dyn Source>>,
496    error: RcStr,
497}
498
499#[turbo_tasks::value_impl]
500impl Issue for ReadSourceIssue {
501    #[turbo_tasks::function]
502    fn file_path(&self) -> Vc<FileSystemPath> {
503        self.source.ident().path()
504    }
505
506    #[turbo_tasks::function]
507    fn title(&self) -> Vc<StyledString> {
508        StyledString::Text("Reading source code for parsing failed".into()).cell()
509    }
510
511    #[turbo_tasks::function]
512    fn description(&self) -> Vc<OptionStyledString> {
513        Vc::cell(Some(
514            StyledString::Text(
515                format!(
516                    "An unexpected error happened while trying to read the source code to parse: \
517                     {}",
518                    self.error
519                )
520                .into(),
521            )
522            .resolved_cell(),
523        ))
524    }
525
526    #[turbo_tasks::function]
527    fn severity(&self) -> Vc<IssueSeverity> {
528        IssueSeverity::Error.cell()
529    }
530
531    #[turbo_tasks::function]
532    fn stage(&self) -> Vc<IssueStage> {
533        IssueStage::Load.cell()
534    }
535}
536
537struct VarDeclWithTsDeclareCollector {
538    id_with_no_ts_declare: FxHashSet<Id>,
539    id_with_ts_declare: FxHashSet<Id>,
540}
541
542impl VarDeclWithTsDeclareCollector {
543    fn collect<N: VisitWith<VarDeclWithTsDeclareCollector>>(n: &N) -> FxHashSet<Id> {
544        let mut collector = VarDeclWithTsDeclareCollector {
545            id_with_no_ts_declare: Default::default(),
546            id_with_ts_declare: Default::default(),
547        };
548        n.visit_with(&mut collector);
549        collector
550            .id_with_ts_declare
551            .retain(|id| !collector.id_with_no_ts_declare.contains(id));
552        collector.id_with_ts_declare
553    }
554
555    fn handle_pat(&mut self, pat: &Pat, declare: bool) {
556        match pat {
557            Pat::Ident(binding_ident) => {
558                if declare {
559                    self.id_with_ts_declare.insert(binding_ident.to_id());
560                } else {
561                    self.id_with_no_ts_declare.insert(binding_ident.to_id());
562                }
563            }
564            Pat::Array(array_pat) => {
565                for pat in array_pat.elems.iter().flatten() {
566                    self.handle_pat(pat, declare);
567                }
568            }
569            Pat::Object(object_pat) => {
570                for prop in object_pat.props.iter() {
571                    match prop {
572                        ObjectPatProp::KeyValue(key_value_pat_prop) => {
573                            self.handle_pat(&key_value_pat_prop.value, declare);
574                        }
575                        ObjectPatProp::Assign(assign_pat_prop) => {
576                            if declare {
577                                self.id_with_ts_declare.insert(assign_pat_prop.key.to_id());
578                            } else {
579                                self.id_with_no_ts_declare
580                                    .insert(assign_pat_prop.key.to_id());
581                            }
582                        }
583                        _ => {}
584                    }
585                }
586            }
587            _ => {}
588        }
589    }
590}
591
592impl Visit for VarDeclWithTsDeclareCollector {
593    noop_visit_type!();
594
595    fn visit_var_decl(&mut self, node: &VarDecl) {
596        for decl in node.decls.iter() {
597            self.handle_pat(&decl.name, node.declare);
598        }
599    }
600}