1use std::{future::Future, sync::Arc};
2
3use anyhow::{Context, Result, anyhow};
4use rustc_hash::FxHashSet;
5use swc_core::{
6 base::SwcComments,
7 common::{
8 BytePos, FileName, GLOBALS, Globals, LineCol, Mark, SyntaxContext,
9 errors::{HANDLER, Handler},
10 input::StringInput,
11 source_map::SourceMapGenConfig,
12 util::take::Take,
13 },
14 ecma::{
15 ast::{EsVersion, Id, ObjectPatProp, Pat, Program, VarDecl},
16 lints::{config::LintConfig, rules::LintParams},
17 parser::{EsSyntax, Parser, Syntax, TsSyntax, lexer::Lexer},
18 transforms::base::{
19 helpers::{HELPERS, Helpers},
20 resolver,
21 },
22 visit::{Visit, VisitMutWith, VisitWith, noop_visit_type},
23 },
24};
25use tracing::{Instrument, Level, instrument};
26use turbo_rcstr::RcStr;
27use turbo_tasks::{ResolvedVc, Value, ValueToString, Vc, util::WrapFuture};
28use turbo_tasks_fs::{FileContent, FileSystemPath, rope::Rope};
29use turbo_tasks_hash::hash_xxh3_hash64;
30use turbopack_core::{
31 SOURCE_URL_PROTOCOL,
32 asset::{Asset, AssetContent},
33 error::PrettyPrintError,
34 issue::{Issue, IssueExt, IssueSeverity, IssueStage, OptionStyledString, StyledString},
35 source::Source,
36 source_map::utils::add_default_ignore_list,
37};
38use turbopack_swc_utils::emitter::IssueEmitter;
39
40use super::EcmascriptModuleAssetType;
41use crate::{
42 EcmascriptInputTransform,
43 analyzer::{ImportMap, graph::EvalContext},
44 swc_comments::ImmutableComments,
45 transform::{EcmascriptInputTransforms, TransformContext},
46};
47
48#[turbo_tasks::value(shared, serialization = "none", eq = "manual")]
49#[allow(clippy::large_enum_variant)]
50pub enum ParseResult {
51 Ok {
53 #[turbo_tasks(debug_ignore, trace_ignore)]
54 program: Program,
55 #[turbo_tasks(debug_ignore, trace_ignore)]
56 comments: Arc<ImmutableComments>,
57 #[turbo_tasks(debug_ignore, trace_ignore)]
58 eval_context: EvalContext,
59 #[turbo_tasks(debug_ignore, trace_ignore)]
60 globals: Arc<Globals>,
61 #[turbo_tasks(debug_ignore, trace_ignore)]
62 source_map: Arc<swc_core::common::SourceMap>,
63 },
64 Unparseable {
65 messages: Option<Vec<RcStr>>,
66 },
67 NotFound,
68}
69
70impl PartialEq for ParseResult {
71 fn eq(&self, other: &Self) -> bool {
72 match (self, other) {
73 (Self::Ok { .. }, Self::Ok { .. }) => false,
74 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
75 }
76 }
77}
78
79#[turbo_tasks::value_impl]
80impl ParseResult {
81 #[turbo_tasks::function]
82 pub fn empty() -> Vc<ParseResult> {
83 let globals = Globals::new();
84 let eval_context = GLOBALS.set(&globals, || EvalContext {
85 unresolved_mark: Mark::new(),
86 top_level_mark: Mark::new(),
87 imports: ImportMap::default(),
88 force_free_values: Default::default(),
89 });
90 ParseResult::Ok {
91 program: Program::Module(swc_core::ecma::ast::Module::dummy()),
92 comments: Default::default(),
93 eval_context,
94 globals: Arc::new(globals),
95 source_map: Default::default(),
96 }
97 .cell()
98 }
99}
100
101#[instrument(level = Level::INFO, skip_all)]
102pub fn generate_js_source_map(
103 files_map: Arc<swc_core::common::SourceMap>,
104 mappings: Vec<(BytePos, LineCol)>,
105 original_source_map: Option<&Rope>,
106 inline_sources_content: bool,
107) -> Result<Rope> {
108 let input_map = if let Some(original_source_map) = original_source_map {
109 Some(match sourcemap::decode(original_source_map.read())? {
110 sourcemap::DecodedMap::Regular(source_map) => source_map,
111 sourcemap::DecodedMap::Index(source_map_index) => source_map_index.flatten()?,
113 _ => return Err(sourcemap::Error::IncompatibleSourceMap.into()),
114 })
115 } else {
116 None
117 };
118
119 let map = files_map.build_source_map(
120 &mappings,
121 None,
122 InlineSourcesContentConfig {
123 inline_sources_content: inline_sources_content && input_map.is_none(),
130 },
131 );
132
133 let mut map = match input_map {
134 Some(mut input_map) => {
135 input_map.adjust_mappings(&map);
136 input_map
137 }
138 None => map,
139 };
140 add_default_ignore_list(&mut map);
141
142 let mut result = vec![];
143 map.to_writer(&mut result)?;
144 Ok(Rope::from(result))
145}
146
147pub struct InlineSourcesContentConfig {
151 inline_sources_content: bool,
152}
153
154impl SourceMapGenConfig for InlineSourcesContentConfig {
155 fn file_name_to_source(&self, f: &FileName) -> String {
156 match f {
157 FileName::Custom(s) => {
158 format!("{SOURCE_URL_PROTOCOL}///{s}")
159 }
160 _ => f.to_string(),
161 }
162 }
163
164 fn inline_sources_content(&self, _f: &FileName) -> bool {
165 self.inline_sources_content
166 }
167}
168
169#[turbo_tasks::function]
170pub async fn parse(
171 source: ResolvedVc<Box<dyn Source>>,
172 ty: Value<EcmascriptModuleAssetType>,
173 transforms: Vc<EcmascriptInputTransforms>,
174) -> Result<Vc<ParseResult>> {
175 let name = source.ident().to_string().await?.to_string();
176 let span = tracing::info_span!("parse ecmascript", name = name, ty = display(&*ty));
177
178 match parse_internal(source, ty, transforms)
179 .instrument(span)
180 .await
181 {
182 Ok(result) => Ok(result),
183 Err(error) => Err(error.context(format!(
184 "failed to parse {}",
185 source.ident().to_string().await?
186 ))),
187 }
188}
189
190async fn parse_internal(
191 source: ResolvedVc<Box<dyn Source>>,
192 ty: Value<EcmascriptModuleAssetType>,
193 transforms: Vc<EcmascriptInputTransforms>,
194) -> Result<Vc<ParseResult>> {
195 let content = source.content();
196 let fs_path_vc = source.ident().path();
197 let fs_path = &*fs_path_vc.await?;
198 let ident = &*source.ident().to_string().await?;
199 let file_path_hash = hash_xxh3_hash64(&*source.ident().to_string().await?) as u128;
200 let ty = ty.into_value();
201 let content = match content.await {
202 Ok(content) => content,
203 Err(error) => {
204 let error: RcStr = PrettyPrintError(&error).to_string().into();
205 ReadSourceIssue {
206 source,
207 error: error.clone(),
208 }
209 .resolved_cell()
210 .emit();
211
212 return Ok(ParseResult::Unparseable {
213 messages: Some(vec![error]),
214 }
215 .cell());
216 }
217 };
218 Ok(match &*content {
219 AssetContent::File(file) => match &*file.await? {
220 FileContent::NotFound => ParseResult::NotFound.cell(),
221 FileContent::Content(file) => match file.content().to_str() {
222 Ok(string) => {
223 let transforms = &*transforms.await?;
224 match parse_file_content(
225 string.into_owned(),
226 fs_path_vc,
227 fs_path,
228 ident,
229 source.ident().query().owned().await?,
230 file_path_hash,
231 source,
232 ty,
233 transforms,
234 )
235 .await
236 {
237 Ok(result) => result,
238 Err(e) => {
239 return Err(e).context(anyhow!(
240 "Transforming and/or parsing of {} failed",
241 source.ident().to_string().await?
242 ));
243 }
244 }
245 }
246 Err(error) => {
247 let error: RcStr = PrettyPrintError(&error).to_string().into();
248 ReadSourceIssue {
249 source,
250 error: error.clone(),
251 }
252 .resolved_cell()
253 .emit();
254 ParseResult::Unparseable {
255 messages: Some(vec![error]),
256 }
257 .cell()
258 }
259 },
260 },
261 AssetContent::Redirect { .. } => ParseResult::Unparseable { messages: None }.cell(),
262 })
263}
264
265async fn parse_file_content(
266 string: String,
267 fs_path_vc: Vc<FileSystemPath>,
268 fs_path: &FileSystemPath,
269 ident: &str,
270 query: RcStr,
271 file_path_hash: u128,
272 source: ResolvedVc<Box<dyn Source>>,
273 ty: EcmascriptModuleAssetType,
274 transforms: &[EcmascriptInputTransform],
275) -> Result<Vc<ParseResult>> {
276 let source_map: Arc<swc_core::common::SourceMap> = Default::default();
277 let (emitter, collector) = IssueEmitter::new(
278 source,
279 source_map.clone(),
280 Some("Ecmascript file had an error".into()),
281 );
282 let handler = Handler::with_emitter(true, false, Box::new(emitter));
283
284 let (emitter, collector_parse) = IssueEmitter::new(
285 source,
286 source_map.clone(),
287 Some("Parsing ecmascript source code failed".into()),
288 );
289 let parser_handler = Handler::with_emitter(true, false, Box::new(emitter));
290 let globals = Arc::new(Globals::new());
291 let globals_ref = &globals;
292
293 let mut result = WrapFuture::new(
294 async {
295 let file_name = FileName::Custom(ident.to_string());
296 let fm = source_map.new_source_file(file_name.clone().into(), string);
297
298 let comments = SwcComments::default();
299
300 let mut parsed_program = {
301 let lexer = Lexer::new(
302 match ty {
303 EcmascriptModuleAssetType::Ecmascript => Syntax::Es(EsSyntax {
304 jsx: true,
305 fn_bind: true,
306 decorators: true,
307 decorators_before_export: true,
308 export_default_from: true,
309 import_attributes: true,
310 allow_super_outside_method: true,
311 allow_return_outside_function: true,
312 auto_accessors: true,
313 explicit_resource_management: true,
314 }),
315 EcmascriptModuleAssetType::Typescript { tsx, .. } => {
316 Syntax::Typescript(TsSyntax {
317 decorators: true,
318 dts: false,
319 no_early_errors: true,
320 tsx,
321 disallow_ambiguous_jsx_like: false,
322 })
323 }
324 EcmascriptModuleAssetType::TypescriptDeclaration => {
325 Syntax::Typescript(TsSyntax {
326 decorators: true,
327 dts: true,
328 no_early_errors: true,
329 tsx: false,
330 disallow_ambiguous_jsx_like: false,
331 })
332 }
333 },
334 EsVersion::latest(),
335 StringInput::from(&*fm),
336 Some(&comments),
337 );
338
339 let mut parser = Parser::new_from(lexer);
340 let span = tracing::trace_span!("swc_parse").entered();
341 let program_result = parser.parse_program();
342 drop(span);
343
344 let mut has_errors = vec![];
345 for e in parser.take_errors() {
346 let mut e = e.into_diagnostic(&parser_handler);
347 has_errors.extend(e.message.iter().map(|m| m.0.as_str().into()));
348 e.emit();
349 }
350
351 if !has_errors.is_empty() {
352 return Ok(ParseResult::Unparseable {
353 messages: Some(has_errors),
354 });
355 }
356
357 match program_result {
358 Ok(parsed_program) => parsed_program,
359 Err(e) => {
360 let mut e = e.into_diagnostic(&parser_handler);
361 let messages = e.message.iter().map(|m| m.0.as_str().into()).collect();
362
363 e.emit();
364
365 return Ok(ParseResult::Unparseable {
366 messages: Some(messages),
367 });
368 }
369 }
370 };
371
372 let unresolved_mark = Mark::new();
373 let top_level_mark = Mark::new();
374
375 let is_typescript = matches!(
376 ty,
377 EcmascriptModuleAssetType::Typescript { .. }
378 | EcmascriptModuleAssetType::TypescriptDeclaration
379 );
380 let span = tracing::trace_span!("swc_resolver").entered();
381
382 parsed_program.visit_mut_with(&mut resolver(
383 unresolved_mark,
384 top_level_mark,
385 is_typescript,
386 ));
387 drop(span);
388
389 let span = tracing::trace_span!("swc_lint").entered();
390
391 let lint_config = LintConfig::default();
392 let rules = swc_core::ecma::lints::rules::all(LintParams {
393 program: &parsed_program,
394 lint_config: &lint_config,
395 unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
396 top_level_ctxt: SyntaxContext::empty().apply_mark(top_level_mark),
397 es_version: EsVersion::latest(),
398 source_map: source_map.clone(),
399 });
400
401 parsed_program.mutate(swc_core::ecma::lints::rules::lint_pass(rules));
402 drop(span);
403
404 parsed_program.mutate(swc_core::ecma::transforms::proposal::explicit_resource_management::explicit_resource_management());
405
406 let var_with_ts_declare = if is_typescript {
407 VarDeclWithTsDeclareCollector::collect(&parsed_program)
408 } else {
409 FxHashSet::default()
410 };
411
412 let transform_context = TransformContext {
413 comments: &comments,
414 source_map: &source_map,
415 top_level_mark,
416 unresolved_mark,
417 file_path_str: &fs_path.path,
418 file_name_str: fs_path.file_name(),
419 file_name_hash: file_path_hash,
420 query_str: query,
421 file_path: fs_path_vc.to_resolved().await?,
422 };
423 let span = tracing::trace_span!("transforms");
424 async {
425 for transform in transforms.iter() {
426 transform
427 .apply(&mut parsed_program, &transform_context)
428 .await?;
429 }
430 anyhow::Ok(())
431 }
432 .instrument(span)
433 .await?;
434
435 if parser_handler.has_errors() {
436 let messages = if let Some(error) = collector_parse.last_emitted_issue() {
437 if let StyledString::Text(xx) = &*error.await?.message.await? {
439 Some(vec![xx.clone()])
440 } else {
441 None
442 }
443 } else {
444 None
445 };
446 let messages =
447 Some(messages.unwrap_or_else(|| vec![String::clone(&fm.src).into()]));
448 return Ok(ParseResult::Unparseable { messages });
449 }
450
451 parsed_program.visit_mut_with(
452 &mut swc_core::ecma::transforms::base::helpers::inject_helpers(unresolved_mark),
453 );
454
455 let eval_context = EvalContext::new(
456 &parsed_program,
457 unresolved_mark,
458 top_level_mark,
459 Arc::new(var_with_ts_declare),
460 Some(&comments),
461 Some(source),
462 );
463
464 Ok::<ParseResult, anyhow::Error>(ParseResult::Ok {
465 program: parsed_program,
466 comments: Arc::new(ImmutableComments::new(comments)),
467 eval_context,
468 globals: Arc::new(Globals::new()),
471 source_map,
472 })
473 },
474 |f, cx| {
475 GLOBALS.set(globals_ref, || {
476 HANDLER.set(&handler, || HELPERS.set(&Helpers::new(true), || f.poll(cx)))
477 })
478 },
479 )
480 .await?;
481 if let ParseResult::Ok {
482 globals: ref mut g, ..
483 } = result
484 {
485 *g = globals;
487 }
488 collector.emit().await?;
489 collector_parse.emit().await?;
490 Ok(result.cell())
491}
492
493#[turbo_tasks::value]
494struct ReadSourceIssue {
495 source: ResolvedVc<Box<dyn Source>>,
496 error: RcStr,
497}
498
499#[turbo_tasks::value_impl]
500impl Issue for ReadSourceIssue {
501 #[turbo_tasks::function]
502 fn file_path(&self) -> Vc<FileSystemPath> {
503 self.source.ident().path()
504 }
505
506 #[turbo_tasks::function]
507 fn title(&self) -> Vc<StyledString> {
508 StyledString::Text("Reading source code for parsing failed".into()).cell()
509 }
510
511 #[turbo_tasks::function]
512 fn description(&self) -> Vc<OptionStyledString> {
513 Vc::cell(Some(
514 StyledString::Text(
515 format!(
516 "An unexpected error happened while trying to read the source code to parse: \
517 {}",
518 self.error
519 )
520 .into(),
521 )
522 .resolved_cell(),
523 ))
524 }
525
526 #[turbo_tasks::function]
527 fn severity(&self) -> Vc<IssueSeverity> {
528 IssueSeverity::Error.cell()
529 }
530
531 #[turbo_tasks::function]
532 fn stage(&self) -> Vc<IssueStage> {
533 IssueStage::Load.cell()
534 }
535}
536
537struct VarDeclWithTsDeclareCollector {
538 id_with_no_ts_declare: FxHashSet<Id>,
539 id_with_ts_declare: FxHashSet<Id>,
540}
541
542impl VarDeclWithTsDeclareCollector {
543 fn collect<N: VisitWith<VarDeclWithTsDeclareCollector>>(n: &N) -> FxHashSet<Id> {
544 let mut collector = VarDeclWithTsDeclareCollector {
545 id_with_no_ts_declare: Default::default(),
546 id_with_ts_declare: Default::default(),
547 };
548 n.visit_with(&mut collector);
549 collector
550 .id_with_ts_declare
551 .retain(|id| !collector.id_with_no_ts_declare.contains(id));
552 collector.id_with_ts_declare
553 }
554
555 fn handle_pat(&mut self, pat: &Pat, declare: bool) {
556 match pat {
557 Pat::Ident(binding_ident) => {
558 if declare {
559 self.id_with_ts_declare.insert(binding_ident.to_id());
560 } else {
561 self.id_with_no_ts_declare.insert(binding_ident.to_id());
562 }
563 }
564 Pat::Array(array_pat) => {
565 for pat in array_pat.elems.iter().flatten() {
566 self.handle_pat(pat, declare);
567 }
568 }
569 Pat::Object(object_pat) => {
570 for prop in object_pat.props.iter() {
571 match prop {
572 ObjectPatProp::KeyValue(key_value_pat_prop) => {
573 self.handle_pat(&key_value_pat_prop.value, declare);
574 }
575 ObjectPatProp::Assign(assign_pat_prop) => {
576 if declare {
577 self.id_with_ts_declare.insert(assign_pat_prop.key.to_id());
578 } else {
579 self.id_with_no_ts_declare
580 .insert(assign_pat_prop.key.to_id());
581 }
582 }
583 _ => {}
584 }
585 }
586 }
587 _ => {}
588 }
589 }
590}
591
592impl Visit for VarDeclWithTsDeclareCollector {
593 noop_visit_type!();
594
595 fn visit_var_decl(&mut self, node: &VarDecl) {
596 for decl in node.decls.iter() {
597 self.handle_pat(&decl.name, node.declare);
598 }
599 }
600}