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 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#[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 inline_sources_content: inline_sources_content && !fast_path_single_original_source_map,
139 },
140 );
141
142 if original_source_maps.is_empty() {
143 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 map.adjust_mappings(new_mappings);
155
156 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
172pub 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 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 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 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 *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}