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")]
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
76impl PartialEq for ParseResult {
77    fn eq(&self, other: &Self) -> bool {
78        match (self, other) {
79            (Self::Ok { .. }, Self::Ok { .. }) => false,
80            _ => core::mem::discriminant(self) == core::mem::discriminant(other),
81        }
82    }
83}
84
85/// `original_source_maps_complete` indicates whether the `original_source_maps` cover the whole
86/// map, i.e. whether every module that ended up in `mappings` had an original sourcemap.
87#[instrument(level = "info", name = "generate source map", skip_all)]
88pub fn generate_js_source_map<'a>(
89    files_map: &impl Files,
90    mappings: Vec<(BytePos, LineCol)>,
91    original_source_maps: impl IntoIterator<Item = &'a Rope>,
92    original_source_maps_complete: bool,
93    inline_sources_content: bool,
94) -> Result<Rope> {
95    let original_source_maps = original_source_maps
96        .into_iter()
97        .map(|map| map.to_bytes())
98        .collect::<Vec<_>>();
99    let original_source_maps = original_source_maps
100        .iter()
101        .map(|map| Ok(swc_sourcemap::lazy::decode(map)?.into_source_map()?))
102        .collect::<Result<Vec<_>>>()?;
103
104    let fast_path_single_original_source_map =
105        original_source_maps.len() == 1 && original_source_maps_complete;
106
107    let mut new_mappings = build_source_map(
108        files_map,
109        &mappings,
110        None,
111        &InlineSourcesContentConfig {
112            // If we are going to adjust the source map, we are going to throw the source contents
113            // of this source map away regardless.
114            //
115            // In other words, we don't need the content of `B` in source map chain of A -> B -> C.
116            // We only need the source content of `A`, and a way to map the content of `B` back to
117            // `A`, while constructing the final source map, `C`.
118            inline_sources_content: inline_sources_content && !fast_path_single_original_source_map,
119        },
120    );
121
122    if original_source_maps.is_empty() {
123        // We don't convert sourcemap::SourceMap into raw_sourcemap::SourceMap because we don't
124        // need to adjust mappings
125
126        add_default_ignore_list(&mut new_mappings);
127
128        let mut result = vec![];
129        new_mappings.to_writer(&mut result)?;
130        Ok(Rope::from(result))
131    } else if fast_path_single_original_source_map {
132        let mut map = original_source_maps.into_iter().next().unwrap();
133        // TODO: Make this more efficient
134        map.adjust_mappings(new_mappings);
135
136        // TODO: Enable this when we have a way to handle the ignore list
137        // add_default_ignore_list(&mut map);
138        let map = map.into_raw_sourcemap();
139        let result = serde_json::to_vec(&map)?;
140        Ok(Rope::from(result))
141    } else {
142        let mut map = new_mappings.adjust_mappings_from_multiple(original_source_maps);
143
144        add_default_ignore_list(&mut map);
145
146        let mut result = vec![];
147        map.to_writer(&mut result)?;
148        Ok(Rope::from(result))
149    }
150}
151
152/// A config to generate a source map which includes the source content of every
153/// source file. SWC doesn't inline sources content by default when generating a
154/// sourcemap, so we need to provide a custom config to do it.
155pub struct InlineSourcesContentConfig {
156    inline_sources_content: bool,
157}
158
159impl SourceMapGenConfig for InlineSourcesContentConfig {
160    fn file_name_to_source(&self, f: &FileName) -> String {
161        match f {
162            FileName::Custom(s) => {
163                format!("{SOURCE_URL_PROTOCOL}///{s}")
164            }
165            _ => f.to_string(),
166        }
167    }
168
169    fn inline_sources_content(&self, _f: &FileName) -> bool {
170        self.inline_sources_content
171    }
172}
173
174#[turbo_tasks::function]
175pub async fn parse(
176    source: ResolvedVc<Box<dyn Source>>,
177    ty: EcmascriptModuleAssetType,
178    transforms: ResolvedVc<EcmascriptInputTransforms>,
179    is_external_tracing: bool,
180    inline_helpers: bool,
181) -> Result<Vc<ParseResult>> {
182    let span = tracing::info_span!(
183        "parse ecmascript",
184        name = display(source.ident().to_string().await?),
185        ty = display(&ty)
186    );
187
188    match parse_internal(
189        source,
190        ty,
191        transforms,
192        is_external_tracing && matches!(ty, EcmascriptModuleAssetType::EcmascriptExtensionless),
193        inline_helpers,
194    )
195    .instrument(span)
196    .await
197    {
198        Ok(result) => Ok(result),
199        Err(error) => Err(error.context(format!(
200            "failed to parse {}",
201            source.ident().to_string().await?
202        ))),
203    }
204}
205
206async fn parse_internal(
207    source: ResolvedVc<Box<dyn Source>>,
208    ty: EcmascriptModuleAssetType,
209    transforms: ResolvedVc<EcmascriptInputTransforms>,
210    loose_errors: bool,
211    inline_helpers: bool,
212) -> Result<Vc<ParseResult>> {
213    let content = source.content();
214    let fs_path = source.ident().path().owned().await?;
215    let ident = &*source.ident().to_string().await?;
216    let file_path_hash = hash_xxh3_hash64(&*source.ident().to_string().await?) as u128;
217    let content = match content.await {
218        Ok(content) => content,
219        Err(error) => {
220            let error: RcStr = PrettyPrintError(&error).to_string().into();
221            ReadSourceIssue {
222                source: IssueSource::from_source_only(source),
223                error: error.clone(),
224                severity: if loose_errors {
225                    IssueSeverity::Warning
226                } else {
227                    IssueSeverity::Error
228                },
229            }
230            .resolved_cell()
231            .emit();
232
233            return Ok(ParseResult::Unparsable {
234                messages: Some(vec![error]),
235            }
236            .cell());
237        }
238    };
239    Ok(match &*content {
240        AssetContent::File(file) => match &*file.await? {
241            FileContent::NotFound => ParseResult::NotFound.cell(),
242            FileContent::Content(file) => {
243                match BytesStr::from_utf8(file.content().clone().into_bytes()) {
244                    Ok(string) => {
245                        let transforms = &*transforms.await?;
246                        match parse_file_content(
247                            string,
248                            &fs_path,
249                            ident,
250                            source.ident().await?.query.clone(),
251                            file_path_hash,
252                            source,
253                            ty,
254                            transforms,
255                            loose_errors,
256                            inline_helpers,
257                        )
258                        .await
259                        {
260                            Ok(result) => result,
261                            Err(e) => {
262                                return Err(e).context(anyhow!(
263                                    "Transforming and/or parsing of {} failed",
264                                    source.ident().to_string().await?
265                                ));
266                            }
267                        }
268                    }
269                    Err(error) => {
270                        let error: RcStr = PrettyPrintError(
271                            &anyhow::anyhow!(error).context("failed to convert rope into string"),
272                        )
273                        .to_string()
274                        .into();
275                        ReadSourceIssue {
276                            // Technically we could supply byte offsets to the issue source, but
277                            // that would cause another utf8 error to be produced when we
278                            // attempt to infer line/column
279                            // offsets
280                            source: IssueSource::from_source_only(source),
281                            error: error.clone(),
282                            severity: if loose_errors {
283                                IssueSeverity::Warning
284                            } else {
285                                IssueSeverity::Error
286                            },
287                        }
288                        .resolved_cell()
289                        .emit();
290                        ParseResult::Unparsable {
291                            messages: Some(vec![error]),
292                        }
293                        .cell()
294                    }
295                }
296            }
297        },
298        AssetContent::Redirect { .. } => ParseResult::Unparsable { messages: None }.cell(),
299    })
300}
301
302async fn parse_file_content(
303    string: BytesStr,
304    fs_path: &FileSystemPath,
305    ident: &str,
306    query: RcStr,
307    file_path_hash: u128,
308    source: ResolvedVc<Box<dyn Source>>,
309    ty: EcmascriptModuleAssetType,
310    transforms: &[EcmascriptInputTransform],
311    loose_errors: bool,
312    inline_helpers: bool,
313) -> Result<Vc<ParseResult>> {
314    let source_map: Arc<swc_core::common::SourceMap> = Default::default();
315    let (emitter, collector) = IssueEmitter::new(
316        source,
317        source_map.clone(),
318        Some(rcstr!("Ecmascript file had an error")),
319    );
320    let handler = Handler::with_emitter(true, false, Box::new(emitter));
321
322    let (emitter, collector_parse) = IssueEmitter::new(
323        source,
324        source_map.clone(),
325        Some(rcstr!("Parsing ecmascript source code failed")),
326    );
327    let parser_handler = Handler::with_emitter(true, false, Box::new(emitter));
328    let globals = Arc::new(Globals::new());
329    let globals_ref = &globals;
330
331    let mut result = WrapFuture::new(
332        async {
333            let file_name = FileName::Custom(ident.to_string());
334            let fm = source_map.new_source_file(file_name.clone().into(), string);
335
336            let comments = SwcComments::default();
337
338            let mut parsed_program = {
339                let lexer = Lexer::new(
340                    match ty {
341                        EcmascriptModuleAssetType::Ecmascript
342                        | EcmascriptModuleAssetType::EcmascriptExtensionless => {
343                            Syntax::Es(EsSyntax {
344                                jsx: true,
345                                fn_bind: true,
346                                decorators: true,
347                                decorators_before_export: true,
348                                export_default_from: true,
349                                import_attributes: true,
350                                allow_super_outside_method: true,
351                                allow_return_outside_function: true,
352                                auto_accessors: true,
353                                explicit_resource_management: true,
354                            })
355                        }
356                        EcmascriptModuleAssetType::Typescript { tsx, .. } => {
357                            Syntax::Typescript(TsSyntax {
358                                decorators: true,
359                                dts: false,
360                                tsx,
361                                ..Default::default()
362                            })
363                        }
364                        EcmascriptModuleAssetType::TypescriptDeclaration => {
365                            Syntax::Typescript(TsSyntax {
366                                decorators: true,
367                                dts: true,
368                                tsx: false,
369                                ..Default::default()
370                            })
371                        }
372                    },
373                    EsVersion::latest(),
374                    StringInput::from(&*fm),
375                    Some(&comments),
376                );
377
378                let mut parser = Parser::new_from(lexer);
379                let span = tracing::trace_span!("swc_parse").entered();
380                let program_result = parser.parse_program();
381                drop(span);
382
383                let mut has_errors = vec![];
384                for e in parser.take_errors() {
385                    let mut e = e.into_diagnostic(&parser_handler);
386                    has_errors.extend(e.message.iter().map(|m| m.0.as_str().into()));
387                    e.emit();
388                }
389
390                if !has_errors.is_empty() {
391                    return Ok(ParseResult::Unparsable {
392                        messages: Some(has_errors),
393                    });
394                }
395
396                match program_result {
397                    Ok(parsed_program) => parsed_program,
398                    Err(e) => {
399                        let mut e = e.into_diagnostic(&parser_handler);
400                        let messages = e.message.iter().map(|m| m.0.as_str().into()).collect();
401
402                        e.emit();
403
404                        return Ok(ParseResult::Unparsable {
405                            messages: Some(messages),
406                        });
407                    }
408                }
409            };
410
411            let unresolved_mark = Mark::new();
412            let top_level_mark = Mark::new();
413
414            let is_typescript = matches!(
415                ty,
416                EcmascriptModuleAssetType::Typescript { .. }
417                    | EcmascriptModuleAssetType::TypescriptDeclaration
418            );
419
420            let helpers = Helpers::new(!inline_helpers);
421            let span = tracing::trace_span!("swc_resolver").entered();
422
423            parsed_program.visit_mut_with(&mut resolver(
424                unresolved_mark,
425                top_level_mark,
426                is_typescript,
427            ));
428            drop(span);
429
430            let span = tracing::trace_span!("swc_lint").entered();
431
432            let lint_config = LintConfig::default();
433            let rules = lints::rules::all(LintParams {
434                program: &parsed_program,
435                lint_config: &lint_config,
436                unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
437                top_level_ctxt: SyntaxContext::empty().apply_mark(top_level_mark),
438                es_version: EsVersion::latest(),
439                source_map: source_map.clone(),
440            });
441
442            parsed_program.mutate(lints::rules::lint_pass(rules));
443            drop(span);
444
445            HELPERS.set(&helpers, || {
446                parsed_program.mutate(explicit_resource_management());
447            });
448
449            let var_with_ts_declare = if is_typescript {
450                VarDeclWithTsDeclareCollector::collect(&parsed_program)
451            } else {
452                FxHashSet::default()
453            };
454
455            let mut helpers = helpers.data();
456            let transform_context = TransformContext {
457                comments: &comments,
458                source_map: &source_map,
459                top_level_mark,
460                unresolved_mark,
461                file_path_str: &fs_path.path,
462                file_name_str: fs_path.file_name(),
463                file_name_hash: file_path_hash,
464                query_str: query,
465                file_path: fs_path.clone(),
466                source,
467            };
468            let span = tracing::trace_span!("transforms");
469            async {
470                for transform in transforms.iter() {
471                    helpers = transform
472                        .apply(&mut parsed_program, &transform_context, helpers)
473                        .await?;
474                }
475                anyhow::Ok(())
476            }
477            .instrument(span)
478            .await?;
479
480            if parser_handler.has_errors() {
481                let messages = if let Some(error) = collector_parse.last_emitted_issue() {
482                    // The emitter created in here only uses StyledString::Text
483                    if let StyledString::Text(xx) = &*error.await?.message.await? {
484                        Some(vec![xx.clone()])
485                    } else {
486                        None
487                    }
488                } else {
489                    None
490                };
491                let messages = Some(messages.unwrap_or_else(|| vec![fm.src.clone().into()]));
492                return Ok(ParseResult::Unparsable { messages });
493            }
494
495            let helpers = Helpers::from_data(helpers);
496            HELPERS.set(&helpers, || {
497                parsed_program.mutate(swc_core::ecma::transforms::base::helpers::inject_helpers(
498                    unresolved_mark,
499                ));
500            });
501
502            let eval_context = EvalContext::new(
503                Some(&parsed_program),
504                unresolved_mark,
505                top_level_mark,
506                Arc::new(var_with_ts_declare),
507                Some(&comments),
508                Some(source),
509            );
510
511            Ok::<ParseResult, anyhow::Error>(ParseResult::Ok {
512                program: parsed_program,
513                comments: Arc::new(ImmutableComments::new(comments)),
514                eval_context,
515                // Temporary globals as the current one can't be moved yet, since they are
516                // borrowed
517                globals: Arc::new(Globals::new()),
518                source_map,
519            })
520        },
521        |f, cx| GLOBALS.set(globals_ref, || HANDLER.set(&handler, || f.poll(cx))),
522    )
523    .await?;
524    if let ParseResult::Ok {
525        globals: ref mut g, ..
526    } = result
527    {
528        // Assign the correct globals
529        *g = globals;
530    }
531    collector.emit(loose_errors).await?;
532    collector_parse.emit(loose_errors).await?;
533    Ok(result.cell())
534}
535
536#[turbo_tasks::value]
537struct ReadSourceIssue {
538    source: IssueSource,
539    error: RcStr,
540    severity: IssueSeverity,
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        self.severity
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}