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 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#[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 inline_sources_content: inline_sources_content && !fast_path_single_original_source_map,
110 },
111 );
112
113 if original_source_maps.is_empty() {
114 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 map.adjust_mappings(new_mappings);
126
127 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
143pub 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 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 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 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 *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}