turbopack_ecmascript/
parse.rs

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