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 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#[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 inline_sources_content: inline_sources_content && !fast_path_single_original_source_map,
119 },
120 );
121
122 if original_source_maps.is_empty() {
123 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 map.adjust_mappings(new_mappings);
135
136 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
152pub 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 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 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 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 *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}