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